diff --git a/args/schema.sql b/args/schema.sql index 71f9057..ed2a387 100644 --- a/args/schema.sql +++ b/args/schema.sql @@ -157,9 +157,9 @@ CREATE TABLE PriceHistory ( CREATE TABLE TransferTransactions ( id INT NOT NULL AUTO_INCREMENT, sender CHAR(34) NOT NULL, - receiver CHAR(34) NOT NULL, + receiver TEXT NOT NULL, token VARCHAR(64) NOT NULL, - amount FLOAT NOT NULL, + totalAmount FLOAT NOT NULL, tx_time DATETIME DEFAULT CURRENT_TIMESTAMP, txid VARCHAR(66) NOT NULL, KEY(id), diff --git a/public/api.js b/public/api.js index 1d0d388..a759c92 100644 --- a/public/api.js +++ b/public/api.js @@ -396,26 +396,35 @@ function cancelOrder(type, id, floID, proxySecret) { }) } -function transferToken(receiver, token, amount, floID, proxySecret) { +//receiver should be object eg {floID1: amount1, floID2: amount2 ...} +function transferToken(receiver, token, floID, proxySecret) { return new Promise((resolve, reject) => { - if (!floCrypto.validateAddr(receiver)) - return reject(INVALID(`Invalid receiver (${receiver})`)); - else if (typeof amount !== "number" || amount <= 0) - return reject(`Invalid amount (${amount})`); + if (typeof receiver !== Object || receiver === null) + return reject("Invalid receiver: parameter is not an object"); + let invalidIDs = [], + invalidAmt = []; + for (let f in receiver) { + if (!floCrypto.validateAddr(f)) + invalidIDs.push(f); + else if (typeof receiver[f] !== "number" || receiver[f] <= 0) + invalidAmt.push(receiver[f]) + } + if (invalidIDs.length) + return reject(INVALID(`Invalid receiver (${invalidIDs})`)); + else if (invalidAmt.length) + return reject(`Invalid amount (${invalidAmt})`); let request = { floID: floID, token: token, receiver: receiver, - amount: amount, timestamp: Date.now() }; if (floCrypto.getFloID(proxySecret) === floID) //Direct signing (without proxy) request.pubKey = floCrypto.getPubKeyHex(proxySecret); request.sign = signRequest({ type: "transfer_token", - receiver: receiver, + receiver: JSON.stringify(receiver), token: token, - amount: amount, timestamp: request.timestamp }, proxySecret); console.debug(request); diff --git a/src/coupling.js b/src/coupling.js index 1665acd..ebca484 100644 --- a/src/coupling.js +++ b/src/coupling.js @@ -118,7 +118,14 @@ function updateBalance(seller_best, buyer_best, txQueries, asset, cur_price, qua txQueries.push(["INSERT INTO Vault(floID, asset, base, quantity) VALUES (?, ?, ?, ?)", [buyer_best.floID, asset, cur_price, quantity]]) //Record transaction let time = Date.now(); - let hash = TRADE_HASH_PREFIX + Crypto.SHA256([time, seller_best.floID, buyer_best.floID, asset, quantity, cur_price].join("|")); + let hash = TRADE_HASH_PREFIX + Crypto.SHA256(JSON.stringify({ + seller: seller_best.floID, + buyer: buyer_best.floID, + asset: asset, + quantity: quantity, + unitValue: cur_price, + tx_time: time, + })); txQueries.push([ "INSERT INTO TradeTransactions (seller, buyer, asset, quantity, unitValue, tx_time, txid) VALUES (?, ?, ?, ?, ?, ?, ?)", [seller_best.floID, buyer_best.floID, asset, quantity, cur_price, global.convertDateToString(time), hash] diff --git a/src/market.js b/src/market.js index 42b7da6..8e68fb3 100644 --- a/src/market.js +++ b/src/market.js @@ -190,39 +190,56 @@ function getTransactionDetails(txid) { if (result.length) { let details = result[0]; details.type = type; + if (tableName === 'TransferTransactions') //As json object is stored for receiver in transfer (to support one-to-many) + details.receiver = JSON.parse(details.receiver); resolve(details); } else reject(INVALID("Transaction not found")); }).catch(error => reject(error)) } -function transferToken(sender, receiver, token, amount) { +function transferToken(sender, receivers, token) { return new Promise((resolve, reject) => { if (floCrypto.validateAddr(sender)) - return reject(INVALID(`Invalid sender (${sender})`)); - else if (floCrypto.validateAddr(receiver)) - return reject(INVALID(`Invalid receiver (${receiver})`)); - else if (typeof amount !== "number" || amount <= 0) - return reject(INVALID(`Invalid amount (${amount})`)); + reject(INVALID(`Invalid sender (${sender})`)); else if (token !== floGlobals.currency && !assetList.includes(token)) - return reject(INVALID(`Invalid token (${token})`)); - getAssetBalance.check(senderID, token, amount).then(_ => { - consumeAsset(sender, token, amount).then(txQueries => { - if (token === floGlobals.currency) - txQueries.push(["INSERT INTO Cash (floID, balance) VALUE (?, ?) ON DUPLICATE KEY UPDATE balance=balance+?", [receiver, amount, amount]]); + reject(INVALID(`Invalid token (${token})`)); + else { + let invalidIDs = [], + totalAmount = 0; + for (let floID in receivers) + if (!floCrypto.validateAddr(floID)) + invalidIDs.push(floID); else - txQueries.push(["INSERT INTO Vault(floID, quantity) VALUES (?, ?)", [receiver, amount]]); - let time = Date.now(); - let hash = TRANSFER_HASH_PREFIX + Crypto.SHA256([time, sender, receiver, token, amount].join("|")); - txQueries.push([ - "INSERT INTO TransferTransactions (sender, receiver, token, amount, tx_time, txid)", - [sender, receiver, token, amount, global.convertDateToString(time), hash] - ]); - DB.transaction(txQueries) - .then(result => resolve(hash)) - .catch(error => reject(error)) + totalAmount += receivers[floID]; + if (invalidIDs.length) + reject(INVALID(`Invalid receiver (${invalidIDs})`)); + else getAssetBalance.check(senderID, token, totalAmount).then(_ => { + consumeAsset(sender, token, totalAmount).then(txQueries => { + if (token === floGlobals.currency) + for (let floID in receivers) + txQueries.push(["INSERT INTO Cash (floID, balance) VALUE (?, ?) ON DUPLICATE KEY UPDATE balance=balance+?", [floID, receivers[floID], receivers[floID]]]); + else + for (let floID in receivers) + txQueries.push(["INSERT INTO Vault(floID, quantity) VALUES (?, ?)", [floID, receivers[floID]]]); + let time = Date.now(); + let hash = TRANSFER_HASH_PREFIX + Crypto.SHA256(JSON.stringify({ + sender: sender, + receiver: receivers, + token: token, + totalAmount: totalAmount, + tx_time: time, + })); + txQueries.push([ + "INSERT INTO TransferTransactions (sender, receiver, token, totalAmount, tx_time, txid)", + [sender, JSON.stringify(receiver), token, totalAmount, global.convertDateToString(time), hash] + ]); + DB.transaction(txQueries) + .then(result => resolve(hash)) + .catch(error => reject(error)) + }).catch(error => reject(error)) }).catch(error => reject(error)) - }).catch(error => reject(error)) + } }) } diff --git a/src/request.js b/src/request.js index 734ccdd..d1a4738 100644 --- a/src/request.js +++ b/src/request.js @@ -275,12 +275,11 @@ function TransferToken(req, res) { let data = req.body; validateRequest({ type: "transfer_token", - receiver: data.receiver, + receiver: JSON.stringify(data.receiver), token: data.token, - amount: data.amount, timestamp: data.timestamp }, data.sign, data.floID, data.pubKey).then(req_str => { - market.transferToken(data.floID, data.receiver, data.token, data.amount).then(result => { + market.transferToken(data.floID, data.receiver, data.token).then(result => { storeRequest(data.floID, req_str, data.sign, !data.pubKey); res.send(result); }).catch(error => {