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
This commit is contained in:
sairajzero 2022-04-17 03:44:34 +05:30
parent 9caf3fc9ec
commit f9551c856d
7 changed files with 281 additions and 568 deletions

View File

@ -16,13 +16,12 @@ CREATE TABLE TagList (
tag VARCHAR(50) NOT NULL, tag VARCHAR(50) NOT NULL,
sellPriority INT, sellPriority INT,
buyPriority INT, buyPriority INT,
api TINYTEXT,
PRIMARY KEY(tag) PRIMARY KEY(tag)
); );
CREATE TABLE AssetList ( CREATE TABLE AssetList (
asset VARCHAR(64) NOT NULL, asset VARCHAR(64) NOT NULL,
initialPrice FLOAT, initialPrice DECIMAL(10, 2),
PRIMARY KEY(asset) PRIMARY KEY(asset)
); );
@ -42,21 +41,22 @@ CREATE TABLE UserSession (
PRIMARY KEY(floID) PRIMARY KEY(floID)
); );
CREATE TABLE Cash ( CREATE TABLE UserBalance (
id INT NOT NULL AUTO_INCREMENT, id INT NOT NULL AUTO_INCREMENT,
floID CHAR(34) NOT NULL UNIQUE, floID CHAR(34) NOT NULL,
balance DECIMAL(12, 2) DEFAULT 0.00, token VARCHAR(64) NOT NULL,
KEY(id), quantity DECIMAL(10, 2) NOT NULL DEFAULT 0,
PRIMARY KEY(floID) PRIMARY KEY(floID, token),
); KEY(id)
)
CREATE TABLE Vault ( CREATE TABLE SellChips (
id INT NOT NULL AUTO_INCREMENT, id INT NOT NULL AUTO_INCREMENT,
floID CHAR(34) NOT NULL, floID CHAR(34) NOT NULL,
locktime DATETIME DEFAULT CURRENT_TIMESTAMP, locktime DATETIME DEFAULT CURRENT_TIMESTAMP,
asset VARCHAR(64) NOT NULL, asset VARCHAR(64) NOT NULL,
base DECIMAL(10, 2), base DECIMAL(10, 2) NOT NULL DEFAULT 0,
quantity FLOAT NOT NULL, quantity DECIMAL(10, 2) NOT NULL,
PRIMARY KEY(id), PRIMARY KEY(id),
FOREIGN KEY (asset) REFERENCES AssetList(asset) FOREIGN KEY (asset) REFERENCES AssetList(asset)
); );
@ -70,6 +70,15 @@ CREATE TABLE UserTag (
FOREIGN KEY (tag) REFERENCES TagList(tag) 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 */ /* User Requests */
CREATE TABLE RequestLog( CREATE TABLE RequestLog(
@ -87,7 +96,7 @@ CREATE TABLE SellOrder (
id INT NOT NULL AUTO_INCREMENT, id INT NOT NULL AUTO_INCREMENT,
floID CHAR(34) NOT NULL, floID CHAR(34) NOT NULL,
asset VARCHAR(64) NOT NULL, asset VARCHAR(64) NOT NULL,
quantity FLOAT NOT NULL, quantity DECIMAL(10, 2) NOT NULL,
minPrice DECIMAL(10, 2) NOT NULL, minPrice DECIMAL(10, 2) NOT NULL,
time_placed DATETIME DEFAULT CURRENT_TIMESTAMP, time_placed DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY(id), PRIMARY KEY(id),
@ -98,7 +107,7 @@ CREATE TABLE BuyOrder (
id INT NOT NULL AUTO_INCREMENT, id INT NOT NULL AUTO_INCREMENT,
floID CHAR(34) NOT NULL, floID CHAR(34) NOT NULL,
asset VARCHAR(64) NOT NULL, asset VARCHAR(64) NOT NULL,
quantity FLOAT NOT NULL, quantity DECIMAL(10, 2) NOT NULL,
maxPrice DECIMAL(10, 2) NOT NULL, maxPrice DECIMAL(10, 2) NOT NULL,
time_placed DATETIME DEFAULT CURRENT_TIMESTAMP, time_placed DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY(id), PRIMARY KEY(id),
@ -109,7 +118,7 @@ CREATE TABLE InputFLO (
id INT NOT NULL AUTO_INCREMENT, id INT NOT NULL AUTO_INCREMENT,
txid VARCHAR(128) NOT NULL, txid VARCHAR(128) NOT NULL,
floID CHAR(34) NOT NULL, floID CHAR(34) NOT NULL,
amount FLOAT, amount DECIMAL(10, 2),
status VARCHAR(50) NOT NULL, status VARCHAR(50) NOT NULL,
PRIMARY KEY(id) PRIMARY KEY(id)
); );
@ -118,7 +127,7 @@ CREATE TABLE OutputFLO (
id INT NOT NULL AUTO_INCREMENT, id INT NOT NULL AUTO_INCREMENT,
txid VARCHAR(128), txid VARCHAR(128),
floID CHAR(34) NOT NULL, floID CHAR(34) NOT NULL,
amount FLOAT NOT NULL, amount DECIMAL(10, 2) NOT NULL,
status VARCHAR(50) NOT NULL, status VARCHAR(50) NOT NULL,
PRIMARY KEY(id) PRIMARY KEY(id)
); );
@ -128,7 +137,7 @@ CREATE TABLE InputToken (
txid VARCHAR(128) NOT NULL, txid VARCHAR(128) NOT NULL,
floID CHAR(34) NOT NULL, floID CHAR(34) NOT NULL,
token VARCHAR(64), token VARCHAR(64),
amount FLOAT, amount DECIMAL(10, 2),
status VARCHAR(50) NOT NULL, status VARCHAR(50) NOT NULL,
PRIMARY KEY(id) PRIMARY KEY(id)
); );
@ -138,7 +147,7 @@ CREATE TABLE OutputToken (
txid VARCHAR(128), txid VARCHAR(128),
floID CHAR(34) NOT NULL, floID CHAR(34) NOT NULL,
token VARCHAR(64), token VARCHAR(64),
amount FLOAT NOT NULL, amount DECIMAL(10, 2) NOT NULL,
status VARCHAR(50) NOT NULL, status VARCHAR(50) NOT NULL,
PRIMARY KEY(id) PRIMARY KEY(id)
); );
@ -148,7 +157,7 @@ CREATE TABLE OutputToken (
CREATE TABLE PriceHistory ( CREATE TABLE PriceHistory (
id INT NOT NULL AUTO_INCREMENT, id INT NOT NULL AUTO_INCREMENT,
asset VARCHAR(64) NOT NULL, asset VARCHAR(64) NOT NULL,
rate FLOAT NOT NULL, rate DECIMAL(10, 2) NOT NULL,
rec_time DATETIME DEFAULT CURRENT_TIMESTAMP, rec_time DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY(id), PRIMARY KEY(id),
FOREIGN KEY (asset) REFERENCES AssetList(asset) FOREIGN KEY (asset) REFERENCES AssetList(asset)
@ -159,7 +168,7 @@ CREATE TABLE TransferTransactions (
sender CHAR(34) NOT NULL, sender CHAR(34) NOT NULL,
receiver TEXT NOT NULL, receiver TEXT NOT NULL,
token VARCHAR(64) NOT NULL, token VARCHAR(64) NOT NULL,
totalAmount FLOAT NOT NULL, totalAmount DECIMAL(10, 2) NOT NULL,
tx_time DATETIME DEFAULT CURRENT_TIMESTAMP, tx_time DATETIME DEFAULT CURRENT_TIMESTAMP,
txid VARCHAR(66) NOT NULL, txid VARCHAR(66) NOT NULL,
KEY(id), KEY(id),
@ -171,7 +180,7 @@ CREATE TABLE TradeTransactions (
seller CHAR(34) NOT NULL, seller CHAR(34) NOT NULL,
buyer CHAR(34) NOT NULL, buyer CHAR(34) NOT NULL,
asset VARCHAR(64) NOT NULL, asset VARCHAR(64) NOT NULL,
quantity FLOAT NOT NULL, quantity DECIMAL(10, 2) NOT NULL,
unitValue DECIMAL(10, 2) NOT NULL, unitValue DECIMAL(10, 2) NOT NULL,
tx_time DATETIME DEFAULT CURRENT_TIMESTAMP, tx_time DATETIME DEFAULT CURRENT_TIMESTAMP,
txid VARCHAR(66) NOT NULL, txid VARCHAR(66) NOT NULL,
@ -183,20 +192,20 @@ CREATE TABLE TradeTransactions (
CREATE TABLE AuditTrade( CREATE TABLE AuditTrade(
id INT NOT NULL AUTO_INCREMENT, id INT NOT NULL AUTO_INCREMENT,
rec_time DATETIME DEFAULT CURRENT_TIMESTAMP, rec_time DATETIME DEFAULT CURRENT_TIMESTAMP,
unit_price FLOAT NOT NULL, unit_price DECIMAL(10, 2) NOT NULL,
quantity FLOAT NOT NULL, quantity DECIMAL(10, 2) NOT NULL,
total_cost FLOAT NOT NULL, total_cost DECIMAL(10, 2) NOT NULL,
asset VARCHAR(64) NOT NULL, asset VARCHAR(64) NOT NULL,
sellerID CHAR(34) NOT NULL, sellerID CHAR(34) NOT NULL,
seller_old_asset FLOAT NOT NULL, seller_old_asset DECIMAL(10, 2) NOT NULL,
seller_new_asset FLOAT NOT NULL, seller_new_asset DECIMAL(10, 2) NOT NULL,
seller_old_cash FLOAT NOT NULL, seller_old_cash DECIMAL(10, 2) NOT NULL,
seller_new_cash FLOAT NOT NULL, seller_new_cash DECIMAL(10, 2) NOT NULL,
buyerID CHAR(34) NOT NULL, buyerID CHAR(34) NOT NULL,
buyer_old_asset FLOAT NOT NULL, buyer_old_asset DECIMAL(10, 2) NOT NULL,
buyer_new_asset FLOAT NOT NULL, buyer_new_asset DECIMAL(10, 2) NOT NULL,
buyer_old_cash FLOAT NOT NULL, buyer_old_cash DECIMAL(10, 2) NOT NULL,
buyer_new_cash FLOAT NOT NULL, buyer_new_cash DECIMAL(10, 2) NOT NULL,
PRIMARY KEY(id), PRIMARY KEY(id),
FOREIGN KEY (asset) REFERENCES AssetList(asset) FOREIGN KEY (asset) REFERENCES AssetList(asset)
); );
@ -204,7 +213,7 @@ CREATE TABLE AuditTrade(
/* Backup Feature (Tables & Triggers) */ /* Backup Feature (Tables & Triggers) */
CREATE TABLE _backup ( CREATE TABLE _backup (
t_name VARCHAR(20), t_name TINYTEXT,
id INT, id INT,
mode BOOLEAN DEFAULT TRUE, mode BOOLEAN DEFAULT TRUE,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, 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 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; 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 CREATE TRIGGER UserBalance_I AFTER INSERT ON UserBalance
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('Cash', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('UserBalance', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
CREATE TRIGGER Cash_U AFTER UPDATE ON Cash CREATE TRIGGER UserBalance_U AFTER UPDATE ON UserBalance
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('Cash', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('UserBalance', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
CREATE TRIGGER Cash_D AFTER DELETE ON Cash CREATE TRIGGER UserBalance_D AFTER DELETE ON UserBalance
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('Cash', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT; 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 CREATE TRIGGER SellChips_I AFTER INSERT ON SellChips
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('Vault', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('SellChips', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
CREATE TRIGGER Vault_U AFTER UPDATE ON Vault CREATE TRIGGER SellChips_U AFTER UPDATE ON SellChips
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('Vault', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('SellChips', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
CREATE TRIGGER Vault_D AFTER DELETE ON Vault CREATE TRIGGER SellChips_D AFTER DELETE ON SellChips
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('Vault', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT; 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 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; 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 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; 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 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; 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 CREATE TRIGGER PriceHistory_U AFTER UPDATE ON PriceHistory

View File

@ -9,7 +9,7 @@ module.exports = {
INVALID_SERVER_MSG: "INCORRECT_SERVER_ERROR" //Should be reflected in public backend script INVALID_SERVER_MSG: "INCORRECT_SERVER_ERROR" //Should be reflected in public backend script
}, },
market: { market: {
MINIMUM_BUY_REQUIREMENT: 0, MAXIMUM_LAUNCH_SELL_CHIPS: 1000000,
TRADE_HASH_PREFIX: "z1", TRADE_HASH_PREFIX: "z1",
TRANSFER_HASH_PREFIX: "z0" TRANSFER_HASH_PREFIX: "z0"
}, },

View File

@ -1,6 +1,5 @@
'use strict'; 'use strict';
const group = require("./group");
const price = require("./price"); const price = require("./price");
const { const {
@ -9,42 +8,84 @@ const {
var DB; //container for database 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) { function startCouplingForAsset(asset) {
price.getRates(asset).then(cur_rate => { price.getRates(asset).then(cur_rate => {
cur_rate = cur_rate.toFixed(8); cur_rate = cur_rate.toFixed(8);
group.getBestPairs(asset, cur_rate) processCoupling(asset, cur_rate);
.then(bestPairQueue => processCoupling(bestPairQueue))
.catch(error => console.error("initiateCoupling", error))
}).catch(error => console.error(error)); }).catch(error => console.error(error));
} }
function processCoupling(bestPairQueue) { function getBestPair(asset, cur_rate) {
bestPairQueue.get().then(pair_result => { return new Promise((resolve, reject) => {
let buyer_best = pair_result.buyOrder, Promise.allSettled([getBestBuyer(asset, cur_rate), getBestSeller(asset, cur_rate)]).then(results => {
seller_best = pair_result.sellOrder; if (results[0].status === "fulfilled" && results[1].status === "fulfilled")
//console.debug("Sell:", seller_best); resolve({
//console.debug("Buy:", buyer_best); buy: results[0].value,
spendAsset(bestPairQueue.asset, buyer_best, seller_best, pair_result.null_base).then(spent => { sell: results[1].value,
if (!spent.quantity) { })
//Happens when there are only Null-base assets else
bestPairQueue.next(spent.quantity, spent.incomplete); reject({
processCoupling(bestPairQueue); buy: results[0].reason,
return; sell: results[1].reason
} })
let txQueries = spent.txQueries; }).catch(error => reject(error))
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 => { const getBestSeller = (asset, cur_rate) => new Promise((resolve, reject) => {
//process txn query in SQL DB.query("SELECT SellOrder.id, SellOrder.floID, SellOrder.quantity, SellChips.id AS chip_id, SellChips.quantity AS chip_quantity FROM SellOrder" +
DB.transaction(txQueries).then(_ => { " INNER JOIN UserBalance ON UserBalance.floID = SellOrder.floID AND UserBalance.token = SellOrder.asset" +
bestPairQueue.next(spent.quantity, spent.incomplete); " INNER JOIN SellChips ON SellChips.floID = SellOrder.floID AND SellChips.asset = SellOrder.asset AND SellChips.base <= ?" +
console.log(`Transaction was successful! BuyOrder:${buyer_best.id}| SellOrder:${seller_best.id}`); " LEFT JOIN UserTag ON UserTag.floID = SellOrder.floID" +
audit.end(); " LEFT JOIN TagList ON TagList.tag = UserTag.tag" +
price.updateLastTime(); " WHERE UserBalance.quantity >= SellOrder.quantity AND SellOrder.asset = ? AND SellOrder.minPrice <= ?" +
//Since a tx was successful, match again " ORDER BY TagList.sellPriority DESC, SellChips.locktime ASC, SellOrder.time_placed ASC" +
processCoupling(bestPairQueue); " LIMIT 1", [cur_rate, asset, cur_rate]
}).catch(error => console.error(error)); ).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 => console.error(error)); }).catch(error => console.error(error));
}).catch(error => { }).catch(error => {
@ -55,7 +96,7 @@ function processCoupling(bestPairQueue) {
console.error(error.buy); console.error(error.buy);
noBuy = null; noBuy = null;
} else { } else {
console.log("No valid buyOrders for Asset:", bestPairQueue.asset); console.log("No valid buyOrders for Asset:", asset);
noBuy = true; noBuy = true;
} }
if (error.sell === undefined) if (error.sell === undefined)
@ -64,37 +105,14 @@ function processCoupling(bestPairQueue) {
console.error(error.sell); console.error(error.sell);
noSell = null; noSell = null;
} else { } else {
console.log("No valid sellOrders for Asset:", bestPairQueue.asset); console.log("No valid sellOrders for Asset:", asset);
noSell = true; noSell = true;
} }
price.noOrder(bestPairQueue.asset, noBuy, noSell); price.noOrder(asset, noBuy, noSell);
}); });
} }
function spendAsset(asset, buyOrder, sellOrder, null_base) { function processOrders(seller_best, buyer_best, txQueries, quantity) {
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) {
if (quantity > buyer_best.quantity || quantity > seller_best.quantity) if (quantity > buyer_best.quantity || quantity > seller_best.quantity)
throw Error("Tx quantity cannot be more than order quantity"); throw Error("Tx quantity cannot be more than order quantity");
//Process Buy Order //Process Buy Order
@ -103,19 +121,26 @@ function processOrders(seller_best, buyer_best, txQueries, quantity, clear_sell)
else else
txQueries.push(["UPDATE BuyOrder SET quantity=quantity-? WHERE id=?", [quantity, buyer_best.id]]); txQueries.push(["UPDATE BuyOrder SET quantity=quantity-? WHERE id=?", [quantity, buyer_best.id]]);
//Process Sell Order //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]]); txQueries.push(["DELETE FROM SellOrder WHERE id=?", [seller_best.id]]);
else else
txQueries.push(["UPDATE SellOrder SET quantity=quantity-? WHERE id=?", [quantity, seller_best.id]]); 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) { 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; 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(updateBalance.add(seller_best.floID, floGlobals.currency, totalAmount));
txQueries.push(["UPDATE Cash SET balance=balance-? WHERE floID=?", [totalAmount, buyer_best.floID]]); txQueries.push(updateBalance.consume(buyer_best.floID, floGlobals.currency, totalAmount));
//Add coins to Buyer txQueries.push(updateBalance.add(seller_best.floID, asset, totalAmount));
txQueries.push(["INSERT INTO Vault(floID, asset, base, quantity) VALUES (?, ?, ?, ?)", [buyer_best.floID, asset, cur_price, quantity]]) 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 //Record transaction
let time = Date.now(); let time = Date.now();
let hash = TRADE_HASH_PREFIX + Crypto.SHA256(JSON.stringify({ 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) { function auditBalance(sellerID, buyerID, asset) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let balance = { let balance = {
[sellerID]: {}, [sellerID]: {
[buyerID]: {} cash: 0,
asset: 0
},
[buyerID]: {
cash: 0,
asset: 0
}
}; };
DB.query("SELECT floID, balance FROM Cash WHERE floID IN (?, ?)", [sellerID, buyerID]).then(result => { 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) for (let i in result) {
balance[result[i].floID].cash = result[i].balance; if (result[i].token === floGlobals.currency)
DB.query("SELECT floID, SUM(quantity) as asset_balance FROM Vault WHERE asset=? AND floID IN (?, ?) GROUP BY floID", [asset, sellerID, buyerID]).then(result => { balance[result[i].floID].cash = result[i].quantity;
for (let i in result) else if (result[i].token === asset)
balance[result[i].floID].asset = result[i].asset_balance; balance[result[i].floID].asset = result[i].quantity;
//Set them as 0 if undefined or null }
balance[sellerID].cash = balance[sellerID].cash || 0; resolve(balance);
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))
}).catch(error => reject(error)) }).catch(error => reject(error))
}) })
} }
module.exports = { module.exports = {
initiate: startCouplingForAsset, initiate: startCouplingForAsset,
group: group, updateBalance,
price, price,
set DB(db) { set DB(db) {
DB = db; DB = db;
group.DB = db;
price.DB = db; price.DB = db;
} }
} }

View File

@ -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('<flo-id>', 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;
}
};

View File

@ -75,7 +75,7 @@ function refreshDataFromBlockchain() {
promises.push(DB.query("DELETE FROM TagList WHERE tag=?", [t])); promises.push(DB.query("DELETE FROM TagList WHERE tag=?", [t]));
if (content.Tag.add) if (content.Tag.add)
for (let t in 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) if (content.Tag.update)
for (let t in content.Tag.update) for (let t in content.Tag.update)
for (let a in content.Tag.update[t]) for (let a in content.Tag.update[t])

View File

@ -3,13 +3,17 @@
const coupling = require('./coupling'); const coupling = require('./coupling');
const { const {
MINIMUM_BUY_REQUIREMENT, MAXIMUM_LAUNCH_SELL_CHIPS,
TRADE_HASH_PREFIX, TRADE_HASH_PREFIX,
TRANSFER_HASH_PREFIX TRANSFER_HASH_PREFIX
} = require('./_constants')["market"]; } = require('./_constants')["market"];
const LAUNCH_SELLER_TAG = "launch-seller";
const MINI_PERIOD_INTERVAL = require('./_constants')['app']['PERIOD_INTERVAL'] / 10; const MINI_PERIOD_INTERVAL = require('./_constants')['app']['PERIOD_INTERVAL'] / 10;
const updateBalance = coupling.updateBalance;
var DB, assetList; //container for database and allowed assets var DB, assetList; //container for database and allowed assets
function login(floID, proxyKey) { function login(floID, proxyKey) {
@ -119,10 +123,6 @@ getAssetBalance.check = (floID, asset, amount) => new Promise((resolve, reject)
}).catch(error => reject(error)) }).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) { function addSellOrder(floID, asset, quantity, min_price) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!floCrypto.validateAddr(floID)) if (!floCrypto.validateAddr(floID))
@ -143,17 +143,17 @@ function addSellOrder(floID, asset, quantity, min_price) {
}); });
} }
const checkSellRequirement = (floID, asset) => new Promise((resolve, reject) => { const checkSellRequirement = (floID, asset, quantity) => new Promise((resolve, reject) => {
DB.query("SELECT * FROM UserTag WHERE floID=? AND tag=?", [floID, "MINER"]).then(result => { Promise.all([
if (result.length) DB.query("SELECT IFNULL(SUM(quantity), 0) AS total_chips FROM SellChips WHERE floID=? AND asset=?", [floID, asset]),
return resolve(true); DB.query("SELECT IFNULL(SUM(quantity), 0) AS locked FROM SellOrder WHERE floID=? AND asset=?", [floID, asset])
//TODO: Should seller need to buy same type of asset before selling? ]).then(result => {
DB.query("SELECT IFNULL(SUM(quantity), 0) AS brought FROM TradeTransactions WHERE buyer=? AND asset=?", [floID, asset]).then(result => { let total = result[0].total_chips,
if (result[0].brought >= MINIMUM_BUY_REQUIREMENT) locked = result[1].locked;
resolve(true); if (total > locked + quantity)
else resolve(true);
reject(INVALID(`Sellers required to buy atleast ${MINIMUM_BUY_REQUIREMENT} ${asset} before placing a sell order unless they are a Miner`)); else
}).catch(error => reject(error)) reject(INVALID(`Insufficient sell-chips for ${asset}`));
}).catch(error => reject(error)) }).catch(error => reject(error))
}); });
@ -280,7 +280,7 @@ function transferToken(sender, receivers, token) {
txQueries.push(updateBalance.consume(sender, token, totalAmount)); txQueries.push(updateBalance.consume(sender, token, totalAmount));
for (let floID in receivers) for (let floID in receivers)
txQueries.push(updateBalance.add(floID, token, receivers[floID])); txQueries.push(updateBalance.add(floID, token, receivers[floID]));
coupling.group.checkDistributor(sender, token).then(result => { checkDistributor(sender, token).then(result => {
if (result) if (result)
for (let floID in receivers) for (let floID in receivers)
txQueries.push(["INSERT INTO Vault (floID, asset, quantity) VALUES (?, ?, ?)", [floID, token, receivers[floID]]]); txQueries.push(["INSERT INTO Vault (floID, asset, quantity) VALUES (?, ?, ?)", [floID, token, receivers[floID]]]);
@ -332,6 +332,7 @@ function confirmDepositFLO() {
let txQueries = []; let txQueries = [];
txQueries.push(updateBalance.add(req.floID, "FLO", amount)); txQueries.push(updateBalance.add(req.floID, "FLO", amount));
txQueries.push(["UPDATE InputFLO SET status=?, amount=? WHERE id=?", ["SUCCESS", amount, req.id]]); txQueries.push(["UPDATE InputFLO SET status=?, amount=? WHERE id=?", ["SUCCESS", amount, req.id]]);
DB.transaction(txQueries) DB.transaction(txQueries)
.then(result => console.debug("FLO deposited:", req.floID, amount)) .then(result => console.debug("FLO deposited:", req.floID, amount))
.catch(error => console.error(error)) .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) { function withdrawFLO(floID, amount) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!floCrypto.validateAddr(floID)) if (!floCrypto.validateAddr(floID))
@ -563,6 +589,68 @@ function confirmWithdrawalToken() {
}).catch(error => console.error(error)); }).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() { function periodicProcess() {
blockchainReCheck(); blockchainReCheck();
assetList.forEach(asset => coupling.initiate(asset)); assetList.forEach(asset => coupling.initiate(asset));
@ -603,8 +691,11 @@ module.exports = {
withdrawFLO, withdrawFLO,
depositToken, depositToken,
withdrawToken, withdrawToken,
addTag,
removeTag,
addDistributor,
removeDistributor,
periodicProcess, periodicProcess,
group: coupling.group,
set DB(db) { set DB(db) {
DB = db; DB = db;
coupling.DB = db; coupling.DB = db;

View File

@ -263,7 +263,7 @@ function AddUserTag(req, res) {
tag: data.tag, tag: data.tag,
timestamp: data.timestamp timestamp: data.timestamp
}, data.sign, data.floID, data.pubKey, }, 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, tag: data.tag,
timestamp: data.timestamp timestamp: data.timestamp
}, data.sign, data.floID, data.pubKey, }, 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, asset: data.asset,
timestamp: data.timestamp timestamp: data.timestamp
}, data.sign, data.floID, data.pubKey, }, 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, asset: data.asset,
timestamp: data.timestamp timestamp: data.timestamp
}, data.sign, data.floID, data.pubKey, }, data.sign, data.floID, data.pubKey,
() => market.group.removeDistributor(data.distributor, data.asset) () => market.removeDistributor(data.distributor, data.asset)
); );
} }