diff --git a/docs/scripts/floExchangeAPI.js b/docs/scripts/floExchangeAPI.js index dd40c14..efe6248 100644 --- a/docs/scripts/floExchangeAPI.js +++ b/docs/scripts/floExchangeAPI.js @@ -885,7 +885,7 @@ return new Promise((resolve, reject) => { if (typeof quantity !== "number" || quantity <= floGlobals.fee) return reject(`Invalid quantity (${quantity})`); - floBlockchainAPI.sendTx(floID, sinkID, quantity, privKey, 'Deposit FLO in market').then(txid => { + floBlockchainAPI.sendTx(floID, sinkID, quantity, privKey, '(deposit in market)').then(txid => { let request = { floID: floID, txid: txid, @@ -947,7 +947,7 @@ return new Promise((resolve, reject) => { if (!floCrypto.verifyPrivKey(privKey, floID)) return reject("Invalid Private Key"); - floTokenAPI.sendToken(privKey, quantity, sinkID, 'Deposit Rupee in market', token).then(txid => { + floTokenAPI.sendToken(privKey, quantity, sinkID, '(deposit in market)', token).then(txid => { let request = { floID: floID, txid: txid, @@ -1069,6 +1069,68 @@ }) } + exchangeAPI.addDistributor = function(distributor, asset, floID, proxySecret) { + return new Promise((resolve, reject) => { + let request = { + floID: floID, + distributor: distributor, + asset: asset, + timestamp: Date.now() + }; + if (floCrypto.getFloID(proxySecret) === floID) //Direct signing (without proxy) + request.pubKey = floCrypto.getPubKeyHex(proxySecret); + request.sign = signRequest({ + type: "add_distributor", + distributor: distributor, + asset: asset, + timestamp: request.timestamp + }, proxySecret); + console.debug(request); + + fetch_api('/add-distributor', { + method: "POST", + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(request) + }).then(result => responseParse(result, false) + .then(result => resolve(result)) + .catch(error => reject(error))) + .catch(error => reject(error)) + }) + } + + exchangeAPI.removeDistributor = function(distributor, asset, floID, proxySecret) { + return new Promise((resolve, reject) => { + let request = { + floID: floID, + distributor: distributor, + asset: asset, + timestamp: Date.now() + }; + if (floCrypto.getFloID(proxySecret) === floID) //Direct signing (without proxy) + request.pubKey = floCrypto.getPubKeyHex(proxySecret); + request.sign = signRequest({ + type: "remove_distributor", + distributor: distributor, + asset: asset, + timestamp: request.timestamp + }, proxySecret); + console.debug(request); + + fetch_api('/remove-distributor', { + method: "POST", + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(request) + }).then(result => responseParse(result, false) + .then(result => resolve(result)) + .catch(error => reject(error))) + .catch(error => reject(error)) + }) + } + exchangeAPI.init = function refreshDataFromBlockchain(adminID = floGlobals.adminID, appName = floGlobals.application) { return new Promise((resolve, reject) => { let nodes, lastTx; diff --git a/docs/scripts/floTokenAPI.js b/docs/scripts/floTokenAPI.js index 3233279..4f2ffdf 100644 --- a/docs/scripts/floTokenAPI.js +++ b/docs/scripts/floTokenAPI.js @@ -42,7 +42,7 @@ return reject("Invalid amount"); this.getBalance(senderID, token).then(bal => { if (amount > bal) - return reject("Insufficiant token balance"); + return reject("Insufficient token balance"); floBlockchainAPI.writeData(senderID, `send ${amount} ${token}# ${message}`, privKey, receiverID) .then(txid => resolve(txid)) .catch(error => reject(error)) diff --git a/src/app.js b/src/app.js index a461304..cafdda0 100644 --- a/src/app.js +++ b/src/app.js @@ -91,6 +91,8 @@ module.exports = function App(secret, DB) { //Manage user tags (Access to trusted IDs only) app.post('/add-tag', Request.AddUserTag); app.post('/remove-tag', Request.RemoveUserTag); + app.post('/add-distributor', Request.AddDistributor); + app.post('/remove-distributor', Request.RemoveDistributor); Request.DB = DB; Request.secret = secret; diff --git a/src/backup/slave.js b/src/backup/slave.js index ea26ba8..bd7b6a3 100644 --- a/src/backup/slave.js +++ b/src/backup/slave.js @@ -186,7 +186,7 @@ function processBackupData(response) { console.log("Backup Sync completed successfully"); self.close(); } else - console.log("Waiting for come re-sync data"); + console.log("Waiting for re-sync data"); }).catch(_ => { console.warn("Backup Sync was not successful"); self.close(); diff --git a/src/coupling.js b/src/coupling.js index 56a92cb..45efe27 100644 --- a/src/coupling.js +++ b/src/coupling.js @@ -178,10 +178,7 @@ function auditBalance(sellerID, buyerID, asset) { module.exports = { initiate: startCouplingForAsset, - group: { - addTag: group.addTag, - removeTag: group.removeTag - }, + group: group, price, set DB(db) { DB = db; diff --git a/src/group.js b/src/group.js index 576cad3..8078295 100644 --- a/src/group.js +++ b/src/group.js @@ -10,7 +10,7 @@ function addTag(floID, tag) { if (error.code === "ER_DUP_ENTRY") reject(INVALID(`${floID} already in ${tag}`)); else if (error.code === "ER_NO_REFERENCED_ROW") - reject(INVALID(`Invalid user-floID and/or Tag`)); + reject(INVALID(`Invalid Tag`)); else reject(error); }); @@ -25,6 +25,37 @@ function removeTag(floID, tag) { }) } +function addDistributor(floID, asset) { + return new Promise((resolve, reject) => { + DB.query("INSERT INTO Distributors (floID, asset) VALUE (?,?)", [floID, asset]) + .then(result => resolve(`Added ${asset} distributor: ${floID}`)) + .catch(error => { + if (error.code === "ER_DUP_ENTRY") + reject(INVALID(`${floID} is already ${asset} disributor`)); + else if (error.code === "ER_NO_REFERENCED_ROW") + reject(INVALID(`Invalid Asset`)); + else + reject(error); + }); + }); +} + +function removeDistributor(floID, asset) { + return new Promise((resolve, reject) => { + DB.query("DELETE FROM Distributors WHERE floID=? AND tag=?", [floID, asset]) + .then(result => resolve(`Removed ${asset} distributor: ${floID}`)) + .catch(error => reject(error)); + }) +} + +function checkDistributor(floID, asset) { + new Promise((resolve, reject) => { + DB.query("SELECT id FROM Distributors WHERE floID=? AND asset=?", [floID, asset]) + .then(result => resolve(result.length ? true : false)) + .catch(error => reject(error)) + }) +} + function getBestPairs(asset, cur_rate) { return new Promise((resolve, reject) => { DB.query("SELECT tag, sellPriority, buyPriority FROM TagList").then(result => { @@ -378,6 +409,9 @@ function verifyBuyOrder(buyOrder, cur_price) { module.exports = { addTag, removeTag, + addDistributor, + removeDistributor, + checkDistributor, getBestPairs, set DB(db) { DB = db; diff --git a/src/market.js b/src/market.js index d438c7e..d73d09c 100644 --- a/src/market.js +++ b/src/market.js @@ -63,37 +63,27 @@ function getBalance(floID, token) { } getBalance.floID_token = (floID, token) => new Promise((resolve, reject) => { - (token === floGlobals.currency ? - DB.query("SELECT IFNULL(SUM(balance), 0) AS balance FROM Cash WHERE floID=?", [floID]) : - DB.query("SELECT IFNULL(SUM(quantity), 0) AS balance FROM Vault WHERE floID=? AND asset=?", [floID, token]) - ).then(result => resolve({ + DB.query("SELECT quantity AS balance FROM UserBalance WHERE floID=? AND token=?", [floID, token]).then(result => resolve({ floID, token, - balance: result[0].balance.toFixed(8) + balance: result.length ? result[0].balance.toFixed(8) : 0 })).catch(error => reject(error)) }); getBalance.floID = (floID) => new Promise((resolve, reject) => { - Promise.all([ - DB.query("SELECT IFNULL(SUM(balance), 0) AS balance FROM Cash WHERE floID=?", [floID]), - DB.query("SELECT asset, IFNULL(SUM(quantity), 0) AS balance FROM Vault WHERE floID=? GROUP BY asset", [floID]) - ]).then(result => { + DB.query("SELECT token, quantity AS balance FROM UserBalance WHERE floID=?", [floID]).then(result => { let response = { floID, balance: {} }; - response.balance[floGlobals.currency] = result[0][0].balance.toFixed(8); - for (let row of result[1]) - response.balance[row.asset] = row.balance.toFixed(8); + for (let row of result) + response.balance[row.token] = row.balance.toFixed(8); resolve(response); }).catch(error => reject(error)) }); getBalance.token = (token) => new Promise((resolve, reject) => { - (token === floGlobals.currency ? - DB.query("SELECT floID, balance FROM Cash") : - DB.query("SELECT floID, IFNULL(SUM(quantity), 0) AS balance FROM Vault WHERE asset = ? GROUP BY floID", [token]) - ).then(result => { + DB.query("SELECT floID, quantity AS balance FROM UserBalance WHERE token=?", [token]).then(result => { let response = { token: token, balance: {} @@ -105,13 +95,12 @@ getBalance.token = (token) => new Promise((resolve, reject) => { }); const getAssetBalance = (floID, asset) => new Promise((resolve, reject) => { - let promises = (asset === floGlobals.currency) ? [ - DB.query("SELECT IFNULL(SUM(balance), 0) AS balance FROM Cash WHERE floID=?", [floID]), - DB.query("SELECT IFNULL(SUM(quantity*maxPrice), 0) AS locked FROM BuyOrder WHERE floID=?", [floID]) - ] : [ - DB.query("SELECT IFNULL(SUM(quantity), 0) AS balance FROM Vault WHERE floID=? AND asset=?", [floID, asset]), + let promises = []; + promises.push(DB.query("SELECT IFNULL(SUM(quantity), 0) AS balance FROM UserBalance WHERE floID=? AND token=?", [floID, asset])); + promises.push(asset === floGlobals.currency ? + DB.query("SELECT IFNULL(SUM(quantity*maxPrice), 0) AS locked FROM BuyOrder WHERE floID=?", [floID]) : DB.query("SELECT IFNULL(SUM(quantity), 0) AS locked FROM SellOrder WHERE floID=? AND asset=?", [floID, asset]) - ]; + ); Promise.all(promises).then(result => resolve({ total: result[0][0].balance, locked: result[1][0].locked, @@ -130,29 +119,9 @@ getAssetBalance.check = (floID, asset, amount) => new Promise((resolve, reject) }).catch(error => reject(error)) }); -const consumeAsset = (floID, asset, amount, txQueries = []) => new Promise((resolve, reject) => { - //If asset/token is currency update Cash else consume from Vault - if (asset === floGlobals.currency) { - txQueries.push(["UPDATE Cash SET balance=balance-? WHERE floID=?", [amount, floID]]); - return resolve(txQueries); - } else - DB.query("SELECT id, quantity FROM Vault WHERE floID=? AND asset=? ORDER BY locktime", [floID, asset]).then(coins => { - let rem = amount; - for (let i = 0; i < coins.length && rem > 0; i++) { - if (rem < coins[i].quantity) { - txQueries.push(["UPDATE Vault SET quantity=quantity-? WHERE id=?", [rem, coins[i].id]]); - rem = 0; - } else { - txQueries.push(["DELETE FROM Vault WHERE id=?", [coins[i].id]]); - rem -= coins[i].quantity; - } - } - if (rem > 0) //should not happen AS the total and net is checked already - reject(INVALID("Insufficient Asset")); - else - resolve(txQueries); - }).catch(error => reject(error)); -}); +const updateBalance = {}; +updateBalance.consume = (floID, token, amount) => ["UPDATE UserBalance SET quantity=quantity-? WHERE floID=? AND token=?", [amount, floID, token]]; +updateBalance.add = (floID, token, amount) => ["INSERT INTO UserBalance (floID, token, quantity) VALUE (?, ?, ?) ON DUPLICATE KEY UPDATE quantity=quantity+?", [floID, token, amount, amount]]; function addSellOrder(floID, asset, quantity, min_price) { return new Promise((resolve, reject) => { @@ -233,8 +202,7 @@ function cancelOrder(type, id, floID) { function getAccountDetails(floID) { return new Promise((resolve, reject) => { let select = []; - select.push(["balance", "Cash"]); - select.push(["asset, AVG(base) AS avg_base, IFNULL(SUM(quantity), 0) AS quantity", "Vault", "GROUP BY asset"]); + select.push(["token, quantity", "UserBalance"]); select.push(["id, asset, quantity, minPrice, time_placed", "SellOrder"]); select.push(["id, asset, quantity, maxPrice, time_placed", "BuyOrder"]); let promises = select.map(a => DB.query(`SELECT ${a[0]} FROM ${a[1]} WHERE floID=? ${a[2] || ""}`, [floID])); @@ -249,15 +217,12 @@ function getAccountDetails(floID) { else switch (i) { case 0: - response.cash = a.value.length ? a.value[0].balance : 0; + response.tokenBalance = a.value; break; case 1: - response.vault = a.value; - break; - case 2: response.sellOrders = a.value; break; - case 3: + case 2: response.buyOrders = a.value; break; } @@ -311,13 +276,14 @@ function transferToken(sender, receivers, token) { if (invalidIDs.length) reject(INVALID(`Invalid receiver (${invalidIDs})`)); else getAssetBalance.check(sender, token, totalAmount).then(_ => { - consumeAsset(sender, token, totalAmount).then(txQueries => { - if (token === floGlobals.currency) + let txQueries = []; + txQueries.push(updateBalance.consume(sender, token, totalAmount)); + for (let floID in receivers) + txQueries.push(updateBalance.add(floID, token, receivers[floID])); + coupling.group.checkDistributor(sender, token).then(result => { + if (result) 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, asset) VALUES (?, ?, ?)", [floID, receivers[floID], token]]); + txQueries.push(["INSERT INTO Vault (floID, asset, quantity) VALUES (?, ?, ?)", [floID, token, receivers[floID]]]); let time = Date.now(); let hash = TRANSFER_HASH_PREFIX + Crypto.SHA256(JSON.stringify({ sender: sender, @@ -364,7 +330,7 @@ function confirmDepositFLO() { results.forEach(req => { confirmDepositFLO.checkTx(req.floID, req.txid).then(amount => { let txQueries = []; - txQueries.push(["INSERT INTO Vault(floID, quantity, asset) VALUES (?, ?, ?)", [req.floID, amount, "FLO"]]); + txQueries.push(updateBalance.add(req.floID, "FLO", amount)); txQueries.push(["UPDATE InputFLO SET status=?, amount=? WHERE id=?", ["SUCCESS", amount, req.id]]); DB.transaction(txQueries) .then(result => console.debug("FLO deposited:", req.floID, amount)) @@ -410,23 +376,23 @@ function withdrawFLO(floID, amount) { else if (typeof amount !== "number" || amount <= 0) return reject(INVALID(`Invalid amount (${amount})`)); getAssetBalance.check(floID, "FLO", amount).then(_ => { - consumeAsset(floID, "FLO", amount).then(txQueries => { - DB.transaction(txQueries).then(result => { - //Send FLO to user via blockchain API - floBlockchainAPI.sendTx(global.sinkID, floID, amount, global.sinkPrivKey, 'Withdraw FLO Coins from Market').then(txid => { - if (!txid) - throw Error("Transaction not successful"); - //Transaction was successful, Add in DB - DB.query("INSERT INTO OutputFLO (floID, amount, txid, status) VALUES (?, ?, ?, ?)", [floID, amount, txid, "WAITING_CONFIRMATION"]) - .then(_ => null).catch(error => console.error(error)) - .finally(_ => resolve("Withdrawal was successful")); - }).catch(error => { - console.error(error); - DB.query("INSERT INTO OutputFLO (floID, amount, status) VALUES (?, ?, ?)", [floID, amount, "PENDING"]) - .then(_ => null).catch(error => console.error(error)) - .finally(_ => resolve("Withdrawal request is in process")); - }); - }).catch(error => reject(error)); + let txQueries = []; + txQueries.push(updateBalance.consume(floID, "FLO", amount)); + DB.transaction(txQueries).then(result => { + //Send FLO to user via blockchain API + floBlockchainAPI.sendTx(global.sinkID, floID, amount, global.sinkPrivKey, '(withdrawal from market)').then(txid => { + if (!txid) + throw Error("Transaction not successful"); + //Transaction was successful, Add in DB + DB.query("INSERT INTO OutputFLO (floID, amount, txid, status) VALUES (?, ?, ?, ?)", [floID, amount, txid, "WAITING_CONFIRMATION"]) + .then(_ => null).catch(error => console.error(error)) + .finally(_ => resolve("Withdrawal was successful")); + }).catch(error => { + console.error(error); + DB.query("INSERT INTO OutputFLO (floID, amount, status) VALUES (?, ?, ?)", [floID, amount, "PENDING"]) + .then(_ => null).catch(error => console.error(error)) + .finally(_ => resolve("Withdrawal request is in process")); + }); }).catch(error => reject(error)); }).catch(error => reject(error)); }); @@ -491,14 +457,11 @@ function confirmDepositToken() { //Add the FLO balance if necessary if (!result.length) { let amount_flo = amounts[2]; - txQueries.push(["INSERT INTO Vault(floID, asset, quantity) VALUES (?, ?, ?)", [req.floID, "FLO", amount_flo]]); + txQueries.push(updateBalance.add(req.floID, "FLO", amount_flo)); txQueries.push(["INSERT INTO InputFLO(txid, floID, amount, status) VALUES (?, ?, ?, ?)", [req.txid, req.floID, amount_flo, "SUCCESS"]]); } txQueries.push(["UPDATE InputToken SET status=?, token=?, amount=? WHERE id=?", ["SUCCESS", token_name, amount_token, req.id]]); - if (token_name === floGlobals.currency) - txQueries.push(["INSERT INTO Cash (floID, balance) VALUE (?, ?) ON DUPLICATE KEY UPDATE balance=balance+?", [req.floID, amount_token, amount_token]]); - else - txQueries.push(["INSERT INTO Vault(floID, asset, quantity) VALUES (?, ?, ?)", [req.floID, token_name, amount_token]]); + txQueries.push(updateBalance.add(req.floID, token_name, amount_token)); DB.transaction(txQueries) .then(result => console.debug("Token deposited:", req.floID, token_name, amount_token)) .catch(error => console.error(error)); @@ -551,24 +514,23 @@ function withdrawToken(floID, token, amount) { let required_flo = floGlobals.sendAmt + floGlobals.fee; getAssetBalance.check(floID, "FLO", required_flo).then(_ => { getAssetBalance.check(floID, token, amount).then(_ => { - consumeAsset(floID, "FLO", required_flo).then(txQueries => { - consumeAsset(floID, token, amount, txQueries).then(txQueries => { - DB.transaction(txQueries).then(result => { - //Send FLO to user via blockchain API - floTokenAPI.sendToken(global.sinkPrivKey, amount, floID, '(withdrawal from market)', token).then(txid => { - if (!txid) throw Error("Transaction not successful"); - //Transaction was successful, Add in DB - DB.query("INSERT INTO OutputToken (floID, token, amount, txid, status) VALUES (?, ?, ?, ?, ?)", [floID, token, amount, txid, "WAITING_CONFIRMATION"]) - .then(_ => null).catch(error => console.error(error)) - .finally(_ => resolve("Withdrawal was successful")); - }).catch(error => { - console.error(error); - DB.query("INSERT INTO OutputToken (floID, token, amount, status) VALUES (?, ?, ?, ?)", [floID, token, amount, "PENDING"]) - .then(_ => null).catch(error => console.error(error)) - .finally(_ => resolve("Withdrawal request is in process")); - }); - }).catch(error => reject(error)); - }).catch(error => reject(error)); + let txQueries = []; + txQueries.push(updateBalance.consume(floID, "FLO", required_flo)); + txQueries.push(updateBalance.consume(floID, token, amount)); + DB.transaction(txQueries).then(result => { + //Send FLO to user via blockchain API + floTokenAPI.sendToken(global.sinkPrivKey, amount, floID, '(withdrawal from market)', token).then(txid => { + if (!txid) throw Error("Transaction not successful"); + //Transaction was successful, Add in DB + DB.query("INSERT INTO OutputToken (floID, token, amount, txid, status) VALUES (?, ?, ?, ?, ?)", [floID, token, amount, txid, "WAITING_CONFIRMATION"]) + .then(_ => null).catch(error => console.error(error)) + .finally(_ => resolve("Withdrawal was successful")); + }).catch(error => { + console.error(error); + DB.query("INSERT INTO OutputToken (floID, token, amount, status) VALUES (?, ?, ?, ?)", [floID, token, amount, "PENDING"]) + .then(_ => null).catch(error => console.error(error)) + .finally(_ => resolve("Withdrawal request is in process")); + }); }).catch(error => reject(error)); }).catch(error => reject(error)); }).catch(error => reject(error)); diff --git a/src/request.js b/src/request.js index d9ad6b7..d89ccfe 100644 --- a/src/request.js +++ b/src/request.js @@ -281,6 +281,34 @@ function RemoveUserTag(req, res) { ); } +function AddDistributor(req, res) { + let data = req.body; + if (!trustedIDs.includes(data.floID)) + res.status(INVALID.e_code).send("Access Denied"); + else processRequest(res, "Add distributor", { + type: "add_distributor", + distributor: data.distributor, + asset: data.asset, + timestamp: data.timestamp + }, data.sign, data.floID, data.pubKey, + () => market.group.addDistributor(data.distributor, data.asset) + ); +} + +function RemoveDistributor(req, res) { + let data = req.body; + if (!trustedIDs.includes(data.floID)) + res.status(INVALID.e_code).send("Access Denied"); + else processRequest(res, "Remove distributor", { + type: "remove_distributor", + distributor: data.distributor, + asset: data.asset, + timestamp: data.timestamp + }, data.sign, data.floID, data.pubKey, + () => market.group.removeDistributor(data.distributor, data.asset) + ); +} + /* Public Requests */ function GetLoginCode(req, res) { @@ -415,6 +443,8 @@ module.exports = { periodicProcess: market.periodicProcess, AddUserTag, RemoveUserTag, + AddDistributor, + RemoveDistributor, set trustedIDs(ids) { trustedIDs = ids; },