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:
parent
9caf3fc9ec
commit
f9551c856d
104
args/schema.sql
104
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
|
||||
|
||||
@ -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"
|
||||
},
|
||||
|
||||
187
src/coupling.js
187
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;
|
||||
}
|
||||
}
|
||||
419
src/group.js
419
src/group.js
@ -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;
|
||||
}
|
||||
};
|
||||
@ -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])
|
||||
|
||||
127
src/market.js
127
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;
|
||||
|
||||
@ -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)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user