From f9551c856d8666e73cf02bfc7caa3b2bedc3b936 Mon Sep 17 00:00:00 2001 From: sairajzero Date: Sun, 17 Apr 2022 03:44:34 +0530 Subject: [PATCH] Updating sell-requirement - Users can only sell when enough sell-chips (for asset) are available. - sell-chips are obtained by . buying assets . receiving asset from distributor . deposit (FLO only) as launch-seller (maximum of 1 million) - Updated coupling for the requirement - Improved getBestSeller and getBestBuyer: Directly fetch from SQL query - Removed group.js (moved required functions to market.js) - Updated SQL schema --- args/schema.sql | 104 +++++++----- src/_constants.js | 2 +- src/coupling.js | 187 ++++++++++++--------- src/group.js | 419 ---------------------------------------------- src/main.js | 2 +- src/market.js | 127 ++++++++++++-- src/request.js | 8 +- 7 files changed, 281 insertions(+), 568 deletions(-) delete mode 100644 src/group.js diff --git a/args/schema.sql b/args/schema.sql index e8a087e..244a960 100644 --- a/args/schema.sql +++ b/args/schema.sql @@ -16,13 +16,12 @@ CREATE TABLE TagList ( tag VARCHAR(50) NOT NULL, sellPriority INT, buyPriority INT, - api TINYTEXT, PRIMARY KEY(tag) ); CREATE TABLE AssetList ( asset VARCHAR(64) NOT NULL, - initialPrice FLOAT, + initialPrice DECIMAL(10, 2), PRIMARY KEY(asset) ); @@ -42,21 +41,22 @@ CREATE TABLE UserSession ( PRIMARY KEY(floID) ); -CREATE TABLE Cash ( +CREATE TABLE UserBalance ( id INT NOT NULL AUTO_INCREMENT, - floID CHAR(34) NOT NULL UNIQUE, - balance DECIMAL(12, 2) DEFAULT 0.00, - KEY(id), - PRIMARY KEY(floID) -); + floID CHAR(34) NOT NULL, + token VARCHAR(64) NOT NULL, + quantity DECIMAL(10, 2) NOT NULL DEFAULT 0, + PRIMARY KEY(floID, token), + KEY(id) +) -CREATE TABLE Vault ( +CREATE TABLE SellChips ( id INT NOT NULL AUTO_INCREMENT, floID CHAR(34) NOT NULL, locktime DATETIME DEFAULT CURRENT_TIMESTAMP, asset VARCHAR(64) NOT NULL, - base DECIMAL(10, 2), - quantity FLOAT NOT NULL, + base DECIMAL(10, 2) NOT NULL DEFAULT 0, + quantity DECIMAL(10, 2) NOT NULL, PRIMARY KEY(id), FOREIGN KEY (asset) REFERENCES AssetList(asset) ); @@ -70,6 +70,15 @@ CREATE TABLE UserTag ( FOREIGN KEY (tag) REFERENCES TagList(tag) ); +CREATE TABLE Distributors( + id INT NOT NULL AUTO_INCREMENT, + floID CHAR(34) NOT NULL, + asset VARCHAR(64) NOT NULL, + KEY(id), + PRIMARY KEY(floID, asset), + FOREIGN KEY (asset) REFERENCES AssetList(asset) +) + /* User Requests */ CREATE TABLE RequestLog( @@ -87,7 +96,7 @@ CREATE TABLE SellOrder ( id INT NOT NULL AUTO_INCREMENT, floID CHAR(34) NOT NULL, asset VARCHAR(64) NOT NULL, - quantity FLOAT NOT NULL, + quantity DECIMAL(10, 2) NOT NULL, minPrice DECIMAL(10, 2) NOT NULL, time_placed DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY(id), @@ -98,7 +107,7 @@ CREATE TABLE BuyOrder ( id INT NOT NULL AUTO_INCREMENT, floID CHAR(34) NOT NULL, asset VARCHAR(64) NOT NULL, - quantity FLOAT NOT NULL, + quantity DECIMAL(10, 2) NOT NULL, maxPrice DECIMAL(10, 2) NOT NULL, time_placed DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY(id), @@ -109,7 +118,7 @@ CREATE TABLE InputFLO ( id INT NOT NULL AUTO_INCREMENT, txid VARCHAR(128) NOT NULL, floID CHAR(34) NOT NULL, - amount FLOAT, + amount DECIMAL(10, 2), status VARCHAR(50) NOT NULL, PRIMARY KEY(id) ); @@ -118,7 +127,7 @@ CREATE TABLE OutputFLO ( id INT NOT NULL AUTO_INCREMENT, txid VARCHAR(128), floID CHAR(34) NOT NULL, - amount FLOAT NOT NULL, + amount DECIMAL(10, 2) NOT NULL, status VARCHAR(50) NOT NULL, PRIMARY KEY(id) ); @@ -128,7 +137,7 @@ CREATE TABLE InputToken ( txid VARCHAR(128) NOT NULL, floID CHAR(34) NOT NULL, token VARCHAR(64), - amount FLOAT, + amount DECIMAL(10, 2), status VARCHAR(50) NOT NULL, PRIMARY KEY(id) ); @@ -138,7 +147,7 @@ CREATE TABLE OutputToken ( txid VARCHAR(128), floID CHAR(34) NOT NULL, token VARCHAR(64), - amount FLOAT NOT NULL, + amount DECIMAL(10, 2) NOT NULL, status VARCHAR(50) NOT NULL, PRIMARY KEY(id) ); @@ -148,7 +157,7 @@ CREATE TABLE OutputToken ( CREATE TABLE PriceHistory ( id INT NOT NULL AUTO_INCREMENT, asset VARCHAR(64) NOT NULL, - rate FLOAT NOT NULL, + rate DECIMAL(10, 2) NOT NULL, rec_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY(id), FOREIGN KEY (asset) REFERENCES AssetList(asset) @@ -159,7 +168,7 @@ CREATE TABLE TransferTransactions ( sender CHAR(34) NOT NULL, receiver TEXT NOT NULL, token VARCHAR(64) NOT NULL, - totalAmount FLOAT NOT NULL, + totalAmount DECIMAL(10, 2) NOT NULL, tx_time DATETIME DEFAULT CURRENT_TIMESTAMP, txid VARCHAR(66) NOT NULL, KEY(id), @@ -171,7 +180,7 @@ CREATE TABLE TradeTransactions ( seller CHAR(34) NOT NULL, buyer CHAR(34) NOT NULL, asset VARCHAR(64) NOT NULL, - quantity FLOAT NOT NULL, + quantity DECIMAL(10, 2) NOT NULL, unitValue DECIMAL(10, 2) NOT NULL, tx_time DATETIME DEFAULT CURRENT_TIMESTAMP, txid VARCHAR(66) NOT NULL, @@ -183,20 +192,20 @@ CREATE TABLE TradeTransactions ( CREATE TABLE AuditTrade( id INT NOT NULL AUTO_INCREMENT, rec_time DATETIME DEFAULT CURRENT_TIMESTAMP, - unit_price FLOAT NOT NULL, - quantity FLOAT NOT NULL, - total_cost FLOAT NOT NULL, + unit_price DECIMAL(10, 2) NOT NULL, + quantity DECIMAL(10, 2) NOT NULL, + total_cost DECIMAL(10, 2) NOT NULL, asset VARCHAR(64) NOT NULL, sellerID CHAR(34) NOT NULL, - seller_old_asset FLOAT NOT NULL, - seller_new_asset FLOAT NOT NULL, - seller_old_cash FLOAT NOT NULL, - seller_new_cash FLOAT NOT NULL, + seller_old_asset DECIMAL(10, 2) NOT NULL, + seller_new_asset DECIMAL(10, 2) NOT NULL, + seller_old_cash DECIMAL(10, 2) NOT NULL, + seller_new_cash DECIMAL(10, 2) NOT NULL, buyerID CHAR(34) NOT NULL, - buyer_old_asset FLOAT NOT NULL, - buyer_new_asset FLOAT NOT NULL, - buyer_old_cash FLOAT NOT NULL, - buyer_new_cash FLOAT NOT NULL, + buyer_old_asset DECIMAL(10, 2) NOT NULL, + buyer_new_asset DECIMAL(10, 2) NOT NULL, + buyer_old_cash DECIMAL(10, 2) NOT NULL, + buyer_new_cash DECIMAL(10, 2) NOT NULL, PRIMARY KEY(id), FOREIGN KEY (asset) REFERENCES AssetList(asset) ); @@ -204,7 +213,7 @@ CREATE TABLE AuditTrade( /* Backup Feature (Tables & Triggers) */ CREATE TABLE _backup ( - t_name VARCHAR(20), + t_name TINYTEXT, id INT, mode BOOLEAN DEFAULT TRUE, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, @@ -240,19 +249,19 @@ FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('UserSession', NEW.id) ON CREATE TRIGGER UserSession_D AFTER DELETE ON UserSession FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('UserSession', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT; -CREATE TRIGGER Cash_I AFTER INSERT ON Cash -FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('Cash', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; -CREATE TRIGGER Cash_U AFTER UPDATE ON Cash -FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('Cash', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; -CREATE TRIGGER Cash_D AFTER DELETE ON Cash -FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('Cash', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT; +CREATE TRIGGER UserBalance_I AFTER INSERT ON UserBalance +FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('UserBalance', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; +CREATE TRIGGER UserBalance_U AFTER UPDATE ON UserBalance +FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('UserBalance', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; +CREATE TRIGGER UserBalance_D AFTER DELETE ON UserBalance +FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('UserBalance', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT; -CREATE TRIGGER Vault_I AFTER INSERT ON Vault -FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('Vault', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; -CREATE TRIGGER Vault_U AFTER UPDATE ON Vault -FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('Vault', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; -CREATE TRIGGER Vault_D AFTER DELETE ON Vault -FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('Vault', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT; +CREATE TRIGGER SellChips_I AFTER INSERT ON SellChips +FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('SellChips', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; +CREATE TRIGGER SellChips_U AFTER UPDATE ON SellChips +FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('SellChips', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; +CREATE TRIGGER SellChips_D AFTER DELETE ON SellChips +FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('SellChips', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT; CREATE TRIGGER SellOrder_I AFTER INSERT ON SellOrder FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('SellOrder', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; @@ -303,6 +312,13 @@ FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('UserTag', NEW.id) ON DUPL CREATE TRIGGER UserTag_D AFTER DELETE ON UserTag FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('UserTag', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT; +CREATE TRIGGER Distributors_I AFTER INSERT ON Distributors +FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('Distributors', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; +CREATE TRIGGER Distributors_U AFTER UPDATE ON Distributors +FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('Distributors', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; +CREATE TRIGGER Distributors_D AFTER DELETE ON Distributors +FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('Distributors', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT; + CREATE TRIGGER PriceHistory_I AFTER INSERT ON PriceHistory FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('PriceHistory', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; CREATE TRIGGER PriceHistory_U AFTER UPDATE ON PriceHistory diff --git a/src/_constants.js b/src/_constants.js index 3fd31c2..036a63e 100644 --- a/src/_constants.js +++ b/src/_constants.js @@ -9,7 +9,7 @@ module.exports = { INVALID_SERVER_MSG: "INCORRECT_SERVER_ERROR" //Should be reflected in public backend script }, market: { - MINIMUM_BUY_REQUIREMENT: 0, + MAXIMUM_LAUNCH_SELL_CHIPS: 1000000, TRADE_HASH_PREFIX: "z1", TRANSFER_HASH_PREFIX: "z0" }, diff --git a/src/coupling.js b/src/coupling.js index 45efe27..4483c91 100644 --- a/src/coupling.js +++ b/src/coupling.js @@ -1,6 +1,5 @@ 'use strict'; -const group = require("./group"); const price = require("./price"); const { @@ -9,42 +8,84 @@ const { var DB; //container for database +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 startCouplingForAsset(asset) { price.getRates(asset).then(cur_rate => { cur_rate = cur_rate.toFixed(8); - group.getBestPairs(asset, cur_rate) - .then(bestPairQueue => processCoupling(bestPairQueue)) - .catch(error => console.error("initiateCoupling", error)) + processCoupling(asset, cur_rate); }).catch(error => console.error(error)); } -function processCoupling(bestPairQueue) { - bestPairQueue.get().then(pair_result => { - let buyer_best = pair_result.buyOrder, - seller_best = pair_result.sellOrder; - //console.debug("Sell:", seller_best); - //console.debug("Buy:", buyer_best); - spendAsset(bestPairQueue.asset, buyer_best, seller_best, pair_result.null_base).then(spent => { - if (!spent.quantity) { - //Happens when there are only Null-base assets - bestPairQueue.next(spent.quantity, spent.incomplete); - processCoupling(bestPairQueue); - return; - } - let txQueries = spent.txQueries; - processOrders(seller_best, buyer_best, txQueries, spent.quantity, spent.incomplete && pair_result.null_base); - updateBalance(seller_best, buyer_best, txQueries, bestPairQueue.asset, bestPairQueue.cur_rate, spent.quantity); - //begin audit - beginAudit(seller_best.floID, buyer_best.floID, bestPairQueue.asset, bestPairQueue.cur_rate, spent.quantity).then(audit => { - //process txn query in SQL - DB.transaction(txQueries).then(_ => { - bestPairQueue.next(spent.quantity, spent.incomplete); - console.log(`Transaction was successful! BuyOrder:${buyer_best.id}| SellOrder:${seller_best.id}`); - audit.end(); - price.updateLastTime(); - //Since a tx was successful, match again - processCoupling(bestPairQueue); - }).catch(error => console.error(error)); +function getBestPair(asset, cur_rate) { + return new Promise((resolve, reject) => { + Promise.allSettled([getBestBuyer(asset, cur_rate), getBestSeller(asset, cur_rate)]).then(results => { + if (results[0].status === "fulfilled" && results[1].status === "fulfilled") + resolve({ + buy: results[0].value, + sell: results[1].value, + }) + else + reject({ + buy: results[0].reason, + sell: results[1].reason + }) + }).catch(error => reject(error)) + }) +} + +const getBestSeller = (asset, cur_rate) => new Promise((resolve, reject) => { + DB.query("SELECT SellOrder.id, SellOrder.floID, SellOrder.quantity, SellChips.id AS chip_id, SellChips.quantity AS chip_quantity FROM SellOrder" + + " INNER JOIN UserBalance ON UserBalance.floID = SellOrder.floID AND UserBalance.token = SellOrder.asset" + + " INNER JOIN SellChips ON SellChips.floID = SellOrder.floID AND SellChips.asset = SellOrder.asset AND SellChips.base <= ?" + + " LEFT JOIN UserTag ON UserTag.floID = SellOrder.floID" + + " LEFT JOIN TagList ON TagList.tag = UserTag.tag" + + " WHERE UserBalance.quantity >= SellOrder.quantity AND SellOrder.asset = ? AND SellOrder.minPrice <= ?" + + " ORDER BY TagList.sellPriority DESC, SellChips.locktime ASC, SellOrder.time_placed ASC" + + " LIMIT 1", [cur_rate, asset, cur_rate] + ).then(result => { + if (result.length) + resolve(result[0]); + else + reject(null); + }).catch(error => reject(error)) +}); + +const getBestBuyer = (asset, cur_rate) => new Promise((resolve, reject) => { + DB.query("SELECT BuyOrder.id, BuyOrder.floID, BuyOrder.quantity FROM BuyOrder" + + " INNER JOIN UserBalance ON UserBalance.floID = BuyOrder.floID AND UserBalance.token = ?" + + " LEFT JOIN UserTag ON UserTag.floID = BuyOrder.floID" + + " LEFT JOIN TagList ON TagList.tag = UserTag.tag" + + " WHERE UserBalance.quantity >= BuyOrder.maxPrice * BuyOrder.quantity AND BuyOrder.asset = ? AND BuyOrder.maxPrice >= ?" + + " ORDER BY TagList.buyPriority DESC, BuyOrder.time_placed ASC" + + " LIMIT 1", [floGlobals.currency, asset, cur_rate] + ).then(result => { + if (result.length) + resolve(result[0]); + else + reject(null); + }).catch(error => reject(error)) +}); + +function processCoupling(asset, cur_rate) { + getBestPair(asset, cur_rate).then(best => { + //console.debug("Sell:", best.sell); + //console.debug("Buy:", best.buy); + let quantity = Math.min(best.buy.quantity, best.sell.quantity, best.sell.chip_quantity); + let txQueries = []; + processOrders(best.sell, best.buy, txQueries, quantity); + updateBalance(best.sell, best.buy, txQueries, asset, cur_rate, quantity); + //begin audit + beginAudit(best.sell.floID, best.buy.floID, asset, cur_rate, quantity).then(audit => { + //process txn query in SQL + DB.transaction(txQueries).then(_ => { + console.log(`Transaction was successful! BuyOrder:${best.buy.id}| SellOrder:${best.sell.id}`); + audit.end(); + price.updateLastTime(); + //Since a tx was successful, match again + processCoupling(asset, cur_rate); }).catch(error => console.error(error)); }).catch(error => console.error(error)); }).catch(error => { @@ -55,7 +96,7 @@ function processCoupling(bestPairQueue) { console.error(error.buy); noBuy = null; } else { - console.log("No valid buyOrders for Asset:", bestPairQueue.asset); + console.log("No valid buyOrders for Asset:", asset); noBuy = true; } if (error.sell === undefined) @@ -64,37 +105,14 @@ function processCoupling(bestPairQueue) { console.error(error.sell); noSell = null; } else { - console.log("No valid sellOrders for Asset:", bestPairQueue.asset); + console.log("No valid sellOrders for Asset:", asset); noSell = true; } - price.noOrder(bestPairQueue.asset, noBuy, noSell); + price.noOrder(asset, noBuy, noSell); }); } -function spendAsset(asset, buyOrder, sellOrder, null_base) { - return new Promise((resolve, reject) => { - DB.query('SELECT id, quantity FROM Vault WHERE floID=? AND asset=? AND base IS ' + - (null_base ? "NULL ORDER BY locktime" : "NOT NULL ORDER BY base"), [sellOrder.floID, asset]).then(result => { - let rem = Math.min(buyOrder.quantity, sellOrder.quantity), - txQueries = []; - for (let i = 0; i < result.length && rem > 0; i++) - if (rem < result[i].quantity) { - txQueries.push(["UPDATE Vault SET quantity=quantity-? WHERE id=?", [rem, result[i].id]]); - rem = 0; - } else { - txQueries.push(["DELETE FROM Vault WHERE id=?", [result[i].id]]); - rem -= result[i].quantity; - } - resolve({ - quantity: Math.min(buyOrder.quantity, sellOrder.quantity) - rem, - txQueries, - incomplete: rem > 0 - }); - }).catch(error => reject(error)); - }) -} - -function processOrders(seller_best, buyer_best, txQueries, quantity, clear_sell) { +function processOrders(seller_best, buyer_best, txQueries, quantity) { if (quantity > buyer_best.quantity || quantity > seller_best.quantity) throw Error("Tx quantity cannot be more than order quantity"); //Process Buy Order @@ -103,19 +121,26 @@ function processOrders(seller_best, buyer_best, txQueries, quantity, clear_sell) else txQueries.push(["UPDATE BuyOrder SET quantity=quantity-? WHERE id=?", [quantity, buyer_best.id]]); //Process Sell Order - if (quantity == seller_best.quantity || clear_sell) //clear_sell must be true iff an order is placed without enough Asset + if (quantity == seller_best.quantity) txQueries.push(["DELETE FROM SellOrder WHERE id=?", [seller_best.id]]); else txQueries.push(["UPDATE SellOrder SET quantity=quantity-? WHERE id=?", [quantity, seller_best.id]]); + //Process Sell Chip + if (quantity == seller_best.chip_quantity) + txQueries.push(["DELETE FROM SellChips WHERE id=?", [seller_best.chip_id]]); + else + txQueries.push(["UPDATE SellChips SET quantity=quantity-? WHERE id=?", [quantity, seller_best.chip_id]]); } function updateBalance(seller_best, buyer_best, txQueries, asset, cur_price, quantity) { - //Update cash balance for seller and buyer + //Update cash/asset balance for seller and buyer let totalAmount = cur_price * quantity; - txQueries.push(["INSERT INTO Cash (floID, balance) VALUE (?, ?) ON DUPLICATE KEY UPDATE balance=balance+?", [seller_best.floID, totalAmount, totalAmount]]); - txQueries.push(["UPDATE Cash SET balance=balance-? WHERE floID=?", [totalAmount, buyer_best.floID]]); - //Add coins to Buyer - txQueries.push(["INSERT INTO Vault(floID, asset, base, quantity) VALUES (?, ?, ?, ?)", [buyer_best.floID, asset, cur_price, quantity]]) + txQueries.push(updateBalance.add(seller_best.floID, floGlobals.currency, totalAmount)); + txQueries.push(updateBalance.consume(buyer_best.floID, floGlobals.currency, totalAmount)); + txQueries.push(updateBalance.add(seller_best.floID, asset, totalAmount)); + txQueries.push(updateBalance.consume(buyer_best.floID, asset, totalAmount)); + //Add SellChips to Buyer + txQueries.push(["INSERT INTO SellChips(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(JSON.stringify({ @@ -156,33 +181,33 @@ function endAudit(sellerID, buyerID, asset, old_bal, unit_price, quantity) { function auditBalance(sellerID, buyerID, asset) { return new Promise((resolve, reject) => { let balance = { - [sellerID]: {}, - [buyerID]: {} + [sellerID]: { + cash: 0, + asset: 0 + }, + [buyerID]: { + cash: 0, + asset: 0 + } }; - DB.query("SELECT floID, balance FROM Cash WHERE floID IN (?, ?)", [sellerID, buyerID]).then(result => { - for (let i in result) - balance[result[i].floID].cash = result[i].balance; - DB.query("SELECT floID, SUM(quantity) as asset_balance FROM Vault WHERE asset=? AND floID IN (?, ?) GROUP BY floID", [asset, sellerID, buyerID]).then(result => { - for (let i in result) - balance[result[i].floID].asset = result[i].asset_balance; - //Set them as 0 if undefined or null - balance[sellerID].cash = balance[sellerID].cash || 0; - balance[sellerID].asset = balance[sellerID].asset || 0; - balance[buyerID].cash = balance[buyerID].cash || 0; - balance[buyerID].asset = balance[buyerID].asset || 0; - resolve(balance); - }).catch(error => reject(error)) + DB.query("SELECT floID, quantity, token FROM UserBalance WHERE floID IN (?, ?) AND token IN (?, ?)", [sellerID, buyerID, floGlobals.currency, asset]).then(result => { + for (let i in result) { + if (result[i].token === floGlobals.currency) + balance[result[i].floID].cash = result[i].quantity; + else if (result[i].token === asset) + balance[result[i].floID].asset = result[i].quantity; + } + resolve(balance); }).catch(error => reject(error)) }) } module.exports = { initiate: startCouplingForAsset, - group: group, + updateBalance, price, set DB(db) { DB = db; - group.DB = db; price.DB = db; } } \ No newline at end of file diff --git a/src/group.js b/src/group.js deleted file mode 100644 index 8078295..0000000 --- a/src/group.js +++ /dev/null @@ -1,419 +0,0 @@ -'use strict'; - -var DB; //container for database - -function addTag(floID, tag) { - return new Promise((resolve, reject) => { - DB.query("INSERT INTO UserTag (floID, tag) VALUE (?,?)", [floID, tag]) - .then(result => resolve(`Added ${floID} to ${tag}`)) - .catch(error => { - if (error.code === "ER_DUP_ENTRY") - reject(INVALID(`${floID} already in ${tag}`)); - else if (error.code === "ER_NO_REFERENCED_ROW") - reject(INVALID(`Invalid Tag`)); - else - reject(error); - }); - }); -} - -function removeTag(floID, tag) { - return new Promise((resolve, reject) => { - DB.query("DELETE FROM UserTag WHERE floID=? AND tag=?", [floID, tag]) - .then(result => resolve(`Removed ${floID} from ${tag}`)) - .catch(error => reject(error)); - }) -} - -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 => { - //Sorted in Ascending (ie, stack; pop for highest) - let tags_buy = result.sort((a, b) => a.buyPriority > b.buyPriority ? 1 : -1).map(r => r.tag); - let tags_sell = result.sort((a, b) => a.sellPriority > b.sellPriority ? 1 : -1).map(r => r.tag); - resolve(new bestPair(asset, cur_rate, tags_buy, tags_sell)); - }).catch(error => reject(error)) - }) -} - -const bestPair = function(asset, cur_rate, tags_buy, tags_sell) { - - Object.defineProperty(this, 'asset', { - get: () => asset - }); - - Object.defineProperty(this, 'cur_rate', { - get: () => cur_rate, - }); - - this.get = () => new Promise((resolve, reject) => { - Promise.allSettled([getBuyOrder(), getSellOrder()]).then(results => { - if (results[0].status === "fulfilled" && results[1].status === "fulfilled") - resolve({ - buyOrder: results[0].value, - sellOrder: results[1].value, - null_base: getSellOrder.cache.mode_null - }) - else - reject({ - buy: results[0].reason, - sell: results[1].reason - }) - }).catch(error => reject(error)) - }); - - this.next = (tx_quantity, incomplete_sell) => { - let buy = getBuyOrder.cache, - sell = getSellOrder.cache; - if (buy.cur_order && sell.cur_order) { - //buy order - if (tx_quantity < buy.cur_order.quantity) - buy.cur_order.quantity -= tx_quantity; - else if (tx_quantity == buy.cur_order.quantity) - buy.cur_order = null; - else - throw Error("Tx quantity cannot be more than order quantity"); - //sell order - if (tx_quantity < sell.cur_order.quantity) { - sell.cur_order.quantity -= tx_quantity; - if (incomplete_sell) { - if (!sell.mode_null) - sell.null_queue.push(sell.cur_order); - sell.cur_order = null; - } - } else if (tx_quantity == sell.cur_order.quantity) - sell.cur_order = null; - else - throw Error("Tx quantity cannot be more than order quantity"); - } else - throw Error("No current order found"); - }; - - const getSellOrder = () => new Promise((resolve, reject) => { - let cache = getSellOrder.cache; - if (cache.cur_order) { //If cache already has a pending order - verifySellOrder(cache.cur_order, asset, cur_rate, cache.mode_null).then(result => { - cache.cur_order = result; - resolve(result); - }).catch(error => { - if (error !== false) - return reject(error); - //Order not valid (minimum gain) - cache.cur_order = null; - getSellOrder() - .then(result => resolve(result)) - .catch(error => reject(error)) - }) - } else if (cache.orders && cache.orders.length) { //If cache already has orders in priority - getTopValidSellOrder(cache.orders, asset, cur_rate, cache.mode_null).then(result => { - cache.cur_order = result; - resolve(result); - }).catch(error => { - if (error !== false) - return reject(error); - //No valid order found in current tag - cache.orders = null; - getSellOrder() - .then(result => resolve(result)) - .catch(error => reject(error)) - }) - } else if (cache.tags.length) { //If cache has remaining tags - cache.cur_tag = cache.tags.pop(); - getSellOrdersInTag(cache.cur_tag, asset, cur_rate).then(orders => { - cache.orders = orders; - getSellOrder() - .then(result => resolve(result)) - .catch(error => reject(error)) - }).catch(error => reject(error)); - } else if (!cache.end) { //Un-tagged floID's orders (do only once) - getUntaggedSellOrders(asset, cur_rate).then(orders => { - cache.orders = orders; - cache.cur_tag = null; - cache.end = true; - getSellOrder() - .then(result => resolve(result)) - .catch(error => reject(error)) - }).catch(error => reject(error)); - } else if (!cache.mode_null) { //Lowest priority Assets (Brought from other sources) - cache.orders = cache.null_queue.reverse(); //Reverse it so that we can pop the highest priority - cache.mode_null = true; - cache.null_queue = null; - getSellOrder() - .then(result => resolve(result)) - .catch(error => reject(error)) - } else - reject(false); - }); - getSellOrder.cache = { - tags: tags_sell, - null_queue: [], - mode_null: false - }; - - const getBuyOrder = () => new Promise((resolve, reject) => { - let cache = getBuyOrder.cache; - if (cache.cur_order) { //If cache already has a pending order - verifyBuyOrder(cache.cur_order, cur_rate).then(result => { - cache.cur_order = result; - resolve(result); - }).catch(error => { - if (error !== false) - return reject(error); - //Order not valid (cash not available) - cache.cur_order = null; - getBuyOrder() - .then(result => resolve(result)) - .catch(error => reject(error)) - }) - } else if (cache.orders && cache.orders.length) { //If cache already has orders in priority - getTopValidBuyOrder(cache.orders, cur_rate).then(result => { - cache.cur_order = result; - resolve(result); - }).catch(error => { - if (error !== false) - return reject(error); - //No valid order found in current tag - cache.orders = null; - getBuyOrder() - .then(result => resolve(result)) - .catch(error => reject(error)) - }) - } else if (cache.tags.length) { //If cache has remaining tags - cache.cur_tag = cache.tags.pop(); - getBuyOrdersInTag(cache.cur_tag, asset, cur_rate).then(orders => { - cache.orders = orders; - getBuyOrder() - .then(result => resolve(result)) - .catch(error => reject(error)) - }).catch(error => reject(error)); - } else if (!cache.end) { //Un-tagged floID's orders (do only once) - getUntaggedBuyOrders(asset, cur_rate).then(orders => { - cache.orders = orders; - cache.cur_tag = null; - cache.end = true; - getBuyOrder() - .then(result => resolve(result)) - .catch(error => reject(error)) - }).catch(error => reject(error)); - } else - reject(false); - }); - getBuyOrder.cache = { - tags: tags_buy - }; -} - -function getUntaggedSellOrders(asset, cur_price) { - return new Promise((resolve, reject) => { - DB.query("SELECT SellOrder.id, SellOrder.floID, SellOrder.quantity FROM SellOrder" + - " LEFT JOIN UserTag ON UserTag.floID = SellOrder.floID" + - " WHERE UserTag.floID IS NULL AND SellOrder.asset = ? AND SellOrder.minPrice <=?" + - " ORDER BY SellOrder.time_placed", [asset, cur_price]) - .then(orders => resolve(orders)) - .catch(error => reject(error)) - }) -} - -function getUntaggedBuyOrders(asset, cur_price) { - return new Promise((resolve, reject) => { - DB.query("SELECT BuyOrder.id, BuyOrder.floID, BuyOrder.quantity FROM BuyOrder" + - " LEFT JOIN UserTag ON UserTag.floID = BuyOrder.floID" + - " WHERE UserTag.floID IS NULL AND BuyOrder.asset = ? AND BuyOrder.maxPrice >=? " + - " ORDER BY BuyOrder.time_placed", [asset, cur_price]) - .then(orders => resolve(orders)) - .catch(error => reject(error)) - }) -} - -function getSellOrdersInTag(tag, asset, cur_price) { - return new Promise((resolve, reject) => { - DB.query("SELECT SellOrder.id, SellOrder.floID, SellOrder.quantity FROM SellOrder" + - " INNER JOIN UserTag ON UserTag.floID = SellOrder.floID" + - " WHERE UserTag.tag = ? AND SellOrder.asset = ? AND SellOrder.minPrice <=?" + - " ORDER BY SellOrder.time_placed", [tag, asset, cur_price]).then(orders => { - if (orders.length <= 1) // No (or) Only-one order, hence priority sort not required. - resolve(orders); - else - getPointsFromAPI(tag, orders.map(o => o.floID)).then(points => { - let orders_sorted = orders.map(o => [o, points[o.floID]]) - .sort((a, b) => a[1] > b[1] ? -1 : a[1] < b[1] ? 1 : 0) //sort by points (ascending) - .map(x => x[0]); - resolve(orders_sorted); - }).catch(error => reject(error)) - }).catch(error => reject(error)) - }); -} - -function getBuyOrdersInTag(tag, asset, cur_price) { - return new Promise((resolve, reject) => { - DB.query("SELECT BuyOrder.id, BuyOrder.floID, BuyOrder.quantity FROM BuyOrder" + - " INNER JOIN UserTag ON UserTag.floID = BuyOrder.floID" + - " WHERE UserTag.tag = ? AND BuyOrder.asset = ? AND BuyOrder.maxPrice >=?" + - " ORDER BY BuyOrder.time_placed", [tag, asset, cur_price]).then(orders => { - if (orders.length <= 1) // No (or) Only-one order, hence priority sort not required. - resolve(orders); - else - getPointsFromAPI(tag, orders.map(o => o.floID)).then(points => { - let orders_sorted = orders.map(o => [o, points[o.floID]]) - .sort((a, b) => a[1] > b[1] ? -1 : a[1] < b[1] ? 1 : 0) //sort by points (ascending) - .map(x => x[0]); - resolve(orders_sorted); - }).catch(error => reject(error)) - }).catch(error => reject(error)) - }); -} - -function getPointsFromAPI(tag, floIDs) { - floIDs = Array.from(new Set(floIDs)); - return new Promise((resolve, reject) => { - DB.query("SELECT api FROM TagList WHERE tag=?", [tag]).then(result => { - let api = result[0].api; - Promise.allSettled(floIDs.map(id => fetch_api(api, id))).then(result => { - let points = {}; - for (let i in result) - points[floIDs[i]] = result[i].status === "fulfilled" ? result[i].value : 0; - resolve(points); - }) - }).catch(error => reject(error)) - }); -} - -function fetch_api(api, id) { - return new Promise((resolve, reject) => { - //TODO: fetch data from API - let url = api.replace('', id); - global.fetch(url).then(response => { - if (response.ok) - response.text() - .then(result => resolve(result)) - .catch(error => reject(error)) - else - reject(response); - }).catch(error => reject(error)) - }) -} - -function getTopValidSellOrder(orders, asset, cur_price, mode_null) { - return new Promise((resolve, reject) => { - if (!orders.length) - return reject(false) - verifySellOrder(orders.pop(), asset, cur_price, mode_null) //pop: as the orders are sorted in ascending (highest point should be checked 1st) - .then(result => resolve(result)) - .catch(error => { - if (error !== false) - return reject(error); - getTopValidSellOrder(orders, asset, cur_price, mode_null) - .then(result => resolve(result)) - .catch(error => reject(error)); - }); - }); -} - -function verifySellOrder(sellOrder, asset, cur_price, mode_null) { - return new Promise((resolve, reject) => { - if (!mode_null) - DB.query("SELECT quantity, base FROM Vault WHERE floID=? AND asset=? AND base IS NOT NULL ORDER BY base", [sellOrder.floID, asset]).then(result => { - let rem = sellOrder.quantity, - sell_base = 0, - base_quantity = 0; - for (let i = 0; i < result.length && rem > 0; i++) { - if (rem < result[i].quantity) { - sell_base += (rem * result[i].base); - base_quantity += rem; - rem = 0; - } else { - sell_base += (result[i].quantity * result[i].base); - base_quantity += result[i].quantity; - rem -= result[i].quantity; - } - } - if (base_quantity) - sell_base = sell_base / base_quantity; - if (sell_base > cur_price) - reject(false); - else - resolve(sellOrder); - }).catch(error => reject(error)); - else if (mode_null) - DB.query("SELECT IFNULL(SUM(quantity), 0) as total FROM Vault WHERE floID=? AND asset=?", [sellOrder.floID, asset]).then(result => { - if (result[0].total < sellOrder.quantity) - console.warn(`Sell Order ${sellOrder.id} was made without enough Assets. This should not happen`); - if (result[0].total > 0) - resolve(sellOrder); - else - reject(false); - }).catch(error => reject(error)) - }) -} - -function getTopValidBuyOrder(orders, cur_price) { - return new Promise((resolve, reject) => { - if (!orders.length) - return reject(false) - verifyBuyOrder(orders.pop(), cur_price) //pop: as the orders are sorted in ascending (highest point should be checked 1st) - .then(result => resolve(result)) - .catch(error => { - if (error !== false) - return reject(error); - getTopValidBuyOrder(orders, cur_price) - .then(result => resolve(result)) - .catch(error => reject(error)); - }); - }); -} - -function verifyBuyOrder(buyOrder, cur_price) { - return new Promise((resolve, reject) => { - DB.query("SELECT balance FROM Cash WHERE floID=?", [buyOrder.floID]).then(result => { - if (!result.length || result[0].balance < cur_price * buyOrder.quantity) { - //This should not happen unless a buy order is placed when user doesnt have enough cash balance - console.warn(`Buy order ${buyOrder.id} is active, but Cash is insufficient`); - reject(false); - } else - resolve(buyOrder); - }).catch(error => reject(error)); - }) -} - -module.exports = { - addTag, - removeTag, - addDistributor, - removeDistributor, - checkDistributor, - getBestPairs, - set DB(db) { - DB = db; - } -}; \ No newline at end of file diff --git a/src/main.js b/src/main.js index 6c7b1a9..1f33dcb 100644 --- a/src/main.js +++ b/src/main.js @@ -75,7 +75,7 @@ function refreshDataFromBlockchain() { promises.push(DB.query("DELETE FROM TagList WHERE tag=?", [t])); if (content.Tag.add) for (let t in content.Tag.add) - promises.push(DB.query("INSERT INTO TagList (tag, sellPriority, buyPriority, api) VALUE (?,?,?,?) ON DUPLICATE KEY UPDATE tag=tag", [t, content.Tag.add[t].sellPriority, content.Tag.add[t].buyPriority, content.Tag.add[t].api])); + promises.push(DB.query("INSERT INTO TagList (tag, sellPriority, buyPriority) VALUE (?, ?, ?) ON DUPLICATE KEY UPDATE tag=tag", [t, content.Tag.add[t].sellPriority, content.Tag.add[t].buyPriority, content.Tag.add[t].api])); if (content.Tag.update) for (let t in content.Tag.update) for (let a in content.Tag.update[t]) diff --git a/src/market.js b/src/market.js index d73d09c..d9a5476 100644 --- a/src/market.js +++ b/src/market.js @@ -3,13 +3,17 @@ const coupling = require('./coupling'); const { - MINIMUM_BUY_REQUIREMENT, + MAXIMUM_LAUNCH_SELL_CHIPS, TRADE_HASH_PREFIX, TRANSFER_HASH_PREFIX } = require('./_constants')["market"]; +const LAUNCH_SELLER_TAG = "launch-seller"; + const MINI_PERIOD_INTERVAL = require('./_constants')['app']['PERIOD_INTERVAL'] / 10; +const updateBalance = coupling.updateBalance; + var DB, assetList; //container for database and allowed assets function login(floID, proxyKey) { @@ -119,10 +123,6 @@ getAssetBalance.check = (floID, asset, amount) => new Promise((resolve, reject) }).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) => { if (!floCrypto.validateAddr(floID)) @@ -143,17 +143,17 @@ function addSellOrder(floID, asset, quantity, min_price) { }); } -const checkSellRequirement = (floID, asset) => new Promise((resolve, reject) => { - DB.query("SELECT * FROM UserTag WHERE floID=? AND tag=?", [floID, "MINER"]).then(result => { - if (result.length) - return resolve(true); - //TODO: Should seller need to buy same type of asset before selling? - DB.query("SELECT IFNULL(SUM(quantity), 0) AS brought FROM TradeTransactions WHERE buyer=? AND asset=?", [floID, asset]).then(result => { - if (result[0].brought >= MINIMUM_BUY_REQUIREMENT) - resolve(true); - else - reject(INVALID(`Sellers required to buy atleast ${MINIMUM_BUY_REQUIREMENT} ${asset} before placing a sell order unless they are a Miner`)); - }).catch(error => reject(error)) +const checkSellRequirement = (floID, asset, quantity) => new Promise((resolve, reject) => { + Promise.all([ + DB.query("SELECT IFNULL(SUM(quantity), 0) AS total_chips FROM SellChips WHERE floID=? AND asset=?", [floID, asset]), + DB.query("SELECT IFNULL(SUM(quantity), 0) AS locked FROM SellOrder WHERE floID=? AND asset=?", [floID, asset]) + ]).then(result => { + let total = result[0].total_chips, + locked = result[1].locked; + if (total > locked + quantity) + resolve(true); + else + reject(INVALID(`Insufficient sell-chips for ${asset}`)); }).catch(error => reject(error)) }); @@ -280,7 +280,7 @@ function transferToken(sender, receivers, token) { 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 => { + checkDistributor(sender, token).then(result => { if (result) for (let floID in receivers) txQueries.push(["INSERT INTO Vault (floID, asset, quantity) VALUES (?, ?, ?)", [floID, token, receivers[floID]]]); @@ -332,6 +332,7 @@ function confirmDepositFLO() { let txQueries = []; 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)) .catch(error => console.error(error)) @@ -369,6 +370,31 @@ confirmDepositFLO.checkTx = function(sender, txid) { }) } +confirmDepositFLO.addSellChipsIfLaunchSeller = function(floID, quantity) { + return new Promise((resolve, reject) => { + checkTag(req.floID, LAUNCH_SELLER_TAG).then(result => { + if (result) //floID is launch-seller + Promise.all([ + DB.query("SELECT SUM(quantity) FROM TradeTransactions WHERE seller=? AND asset=?", [floID, 'FLO']), + DB.query("SELECT SUM(quantity) FROM TradeTransactions WHERE buyer=? AND asset=?", [floID, 'FLO']), + DB.query("SELECT SUM(quantity) FROM SellChips WHERE floID=? AND asset=?", [floID, 'FLO']), + ]).then(result => { + let sold = result[0], + brought = result[1], + chips = result[2]; + let remLaunchChips = MAXIMUM_LAUNCH_SELL_CHIPS - (sold + chips) + brought; + quantity = Math.min(quantity, remLaunchChips); + if (quantity > 0) + resolve(["INSERT INTO SellChips(floID, asset, quantity) VALUES (?, ?, ?)", [floID, 'FLO', quantity]]); + else + resolve([]); + }).catch(error => reject(error)) + else //floID is not launch-seller + resolve([]); + }).catch(error => reject(error)) + }) +} + function withdrawFLO(floID, amount) { return new Promise((resolve, reject) => { if (!floCrypto.validateAddr(floID)) @@ -563,6 +589,68 @@ function confirmWithdrawalToken() { }).catch(error => console.error(error)); } +function addTag(floID, tag) { + return new Promise((resolve, reject) => { + DB.query("INSERT INTO UserTag (floID, tag) VALUE (?,?)", [floID, tag]) + .then(result => resolve(`Added ${floID} to ${tag}`)) + .catch(error => { + if (error.code === "ER_DUP_ENTRY") + reject(INVALID(`${floID} already in ${tag}`)); + else if (error.code === "ER_NO_REFERENCED_ROW") + reject(INVALID(`Invalid Tag`)); + else + reject(error); + }); + }); +} + +function removeTag(floID, tag) { + return new Promise((resolve, reject) => { + DB.query("DELETE FROM UserTag WHERE floID=? AND tag=?", [floID, tag]) + .then(result => resolve(`Removed ${floID} from ${tag}`)) + .catch(error => reject(error)); + }) +} + +function checkTag(floID, tag) { + new Promise((resolve, reject) => { + DB.query("SELECT id FROM UserTag WHERE floID=? AND tag=?", [floID, tag]) + .then(result => resolve(result.length ? true : false)) + .catch(error => reject(error)) + }) +} + +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 periodicProcess() { blockchainReCheck(); assetList.forEach(asset => coupling.initiate(asset)); @@ -603,8 +691,11 @@ module.exports = { withdrawFLO, depositToken, withdrawToken, + addTag, + removeTag, + addDistributor, + removeDistributor, periodicProcess, - group: coupling.group, set DB(db) { DB = db; coupling.DB = db; diff --git a/src/request.js b/src/request.js index d89ccfe..dbb3bc3 100644 --- a/src/request.js +++ b/src/request.js @@ -263,7 +263,7 @@ function AddUserTag(req, res) { tag: data.tag, timestamp: data.timestamp }, data.sign, data.floID, data.pubKey, - () => market.group.addTag(data.user, data.tag) + () => market.addTag(data.user, data.tag) ); } @@ -277,7 +277,7 @@ function RemoveUserTag(req, res) { tag: data.tag, timestamp: data.timestamp }, data.sign, data.floID, data.pubKey, - () => market.group.removeTag(data.user, data.tag) + () => market.removeTag(data.user, data.tag) ); } @@ -291,7 +291,7 @@ function AddDistributor(req, res) { asset: data.asset, timestamp: data.timestamp }, data.sign, data.floID, data.pubKey, - () => market.group.addDistributor(data.distributor, data.asset) + () => market.addDistributor(data.distributor, data.asset) ); } @@ -305,7 +305,7 @@ function RemoveDistributor(req, res) { asset: data.asset, timestamp: data.timestamp }, data.sign, data.floID, data.pubKey, - () => market.group.removeDistributor(data.distributor, data.asset) + () => market.removeDistributor(data.distributor, data.asset) ); }