Multiple asset support

2 types of property:
1. Cash - main/central currency used for trading
2. Asset - (FLO/tokens) Can be brought or sold in the exchange market .

- Allow multiple tokens to act as asset in addition to FLO.
- Changes required for the above in deposits, withdraws, placing orders
- some code optimization in market.js
- Updated SQL schema
This commit is contained in:
sairajzero 2022-02-02 04:17:47 +05:30
parent 512dc3bcbc
commit 67c31d79a9
7 changed files with 206 additions and 204 deletions

View File

@ -28,7 +28,7 @@ FOREIGN KEY (floID) REFERENCES Users(floID)
CREATE TABLE Cash ( CREATE TABLE Cash (
id INT NOT NULL AUTO_INCREMENT, id INT NOT NULL AUTO_INCREMENT,
floID CHAR(34) NOT NULL UNIQUE, floID CHAR(34) NOT NULL UNIQUE,
rupeeBalance DECIMAL(12, 2) DEFAULT 0.00, balance DECIMAL(12, 2) DEFAULT 0.00,
PRIMARY KEY(id), PRIMARY KEY(id),
FOREIGN KEY (floID) REFERENCES Users(floID) FOREIGN KEY (floID) REFERENCES Users(floID)
); );
@ -37,6 +37,7 @@ CREATE TABLE Vault (
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,
base DECIMAL(10, 2), base DECIMAL(10, 2),
quantity FLOAT NOT NULL, quantity FLOAT NOT NULL,
PRIMARY KEY(id), PRIMARY KEY(id),
@ -93,20 +94,22 @@ PRIMARY KEY(id),
FOREIGN KEY (floID) REFERENCES Users(floID) FOREIGN KEY (floID) REFERENCES Users(floID)
); );
CREATE TABLE inputRupee ( CREATE TABLE inputToken (
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,
token VARCHAR(64),
amount FLOAT, amount FLOAT,
status VARCHAR(50) NOT NULL, status VARCHAR(50) NOT NULL,
PRIMARY KEY(id), PRIMARY KEY(id),
FOREIGN KEY (floID) REFERENCES Users(floID) FOREIGN KEY (floID) REFERENCES Users(floID)
); );
CREATE TABLE outputRupee ( CREATE TABLE outputToken (
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,
token VARCHAR(64),
amount FLOAT NOT NULL, amount FLOAT NOT NULL,
status VARCHAR(50) NOT NULL, status VARCHAR(50) NOT NULL,
PRIMARY KEY(id), PRIMARY KEY(id),
@ -159,16 +162,17 @@ rec_time DATETIME DEFAULT CURRENT_TIMESTAMP,
unit_price FLOAT NOT NULL, unit_price FLOAT NOT NULL,
quantity FLOAT NOT NULL, quantity FLOAT NOT NULL,
total_cost FLOAT NOT NULL, total_cost FLOAT NOT NULL,
asset VARCHAR(64) NOT NULL,
sellerID CHAR(34) NOT NULL, sellerID CHAR(34) NOT NULL,
FLO_seller_old FLOAT NOT NULL, seller_old_asset FLOAT NOT NULL,
FLO_seller_new FLOAT NOT NULL, seller_new_asset FLOAT NOT NULL,
Rupee_seller_old FLOAT NOT NULL, seller_old_cash FLOAT NOT NULL,
Rupee_seller_new FLOAT NOT NULL, seller_new_cash FLOAT NOT NULL,
buyerID CHAR(34) NOT NULL, buyerID CHAR(34) NOT NULL,
FLO_buyer_old FLOAT NOT NULL, buyer_old_asset FLOAT NOT NULL,
FLO_buyer_new FLOAT NOT NULL, buyer_new_asset FLOAT NOT NULL,
Rupee_buyer_old FLOAT NOT NULL, buyer_old_cash FLOAT NOT NULL,
Rupee_buyer_new FLOAT NOT NULL, buyer_new_cash FLOAT NOT NULL,
FOREIGN KEY (sellerID) REFERENCES Users(floID), FOREIGN KEY (sellerID) REFERENCES Users(floID),
FOREIGN KEY (buyerID) REFERENCES Users(floID) FOREIGN KEY (buyerID) REFERENCES Users(floID)
); );
@ -239,19 +243,19 @@ FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('outputFLO', NEW.id) ON DU
CREATE TRIGGER outputFLO_D AFTER DELETE ON outputFLO CREATE TRIGGER outputFLO_D AFTER DELETE ON outputFLO
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('outputFLO', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT; FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('outputFLO', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT;
CREATE TRIGGER inputRupee_I AFTER INSERT ON inputRupee CREATE TRIGGER inputToken_I AFTER INSERT ON inputToken
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('inputRupee', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('inputToken', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
CREATE TRIGGER inputRupee_U AFTER UPDATE ON inputRupee CREATE TRIGGER inputToken_U AFTER UPDATE ON inputToken
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('inputRupee', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('inputToken', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
CREATE TRIGGER inputRupee_D AFTER DELETE ON inputRupee CREATE TRIGGER inputToken_D AFTER DELETE ON inputToken
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('inputRupee', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT; FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('inputToken', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT;
CREATE TRIGGER outputRupee_I AFTER INSERT ON outputRupee CREATE TRIGGER outputToken_I AFTER INSERT ON outputToken
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('outputRupee', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('outputToken', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
CREATE TRIGGER outputRupee_U AFTER UPDATE ON outputRupee CREATE TRIGGER outputToken_U AFTER UPDATE ON outputToken
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('outputRupee', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('outputToken', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
CREATE TRIGGER outputRupee_D AFTER DELETE ON outputRupee CREATE TRIGGER outputToken_D AFTER DELETE ON outputToken
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('outputRupee', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT; FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('outputToken', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT;
CREATE TRIGGER Tags_I AFTER INSERT ON Tags CREATE TRIGGER Tags_I AFTER INSERT ON Tags
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('Tags', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('Tags', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;

View File

@ -4,9 +4,9 @@ TRUNCATE auditTransaction;
TRUNCATE BuyOrder; TRUNCATE BuyOrder;
TRUNCATE Cash; TRUNCATE Cash;
TRUNCATE inputFLO; TRUNCATE inputFLO;
TRUNCATE inputRupee; TRUNCATE inputToken;
TRUNCATE outputFLO; TRUNCATE outputFLO;
TRUNCATE outputRupee; TRUNCATE outputToken;
TRUNCATE priceHistory; TRUNCATE priceHistory;
TRUNCATE Request_Log; TRUNCATE Request_Log;
TRUNCATE SellOrder; TRUNCATE SellOrder;

View File

@ -13,7 +13,7 @@ const floGlobals = {
sendAmt: 0.001, sendAmt: 0.001,
fee: 0.0005, fee: 0.0005,
tokenURL: "https://ranchimallflo.duckdns.org/", tokenURL: "https://ranchimallflo.duckdns.org/",
token: "rupee" currency: "rupee"
}; };
(typeof global !== "undefined" ? global : window).cryptocoin = floGlobals.blockchain; (typeof global !== "undefined" ? global : window).cryptocoin = floGlobals.blockchain;

View File

@ -80,8 +80,8 @@ module.exports = function App(secret, DB) {
//withdraw and deposit request //withdraw and deposit request
app.post('/deposit-flo', Request.DepositFLO); app.post('/deposit-flo', Request.DepositFLO);
app.post('/withdraw-flo', Request.WithdrawFLO); app.post('/withdraw-flo', Request.WithdrawFLO);
app.post('/deposit-rupee', Request.DepositRupee); app.post('/deposit-token', Request.DepositToken);
app.post('/withdraw-rupee', Request.WithdrawRupee); app.post('/withdraw-token', Request.WithdrawToken);
//Manage user tags (Access to trusted IDs only) //Manage user tags (Access to trusted IDs only)

View File

@ -3,9 +3,59 @@
const coupling = require('./coupling'); const coupling = require('./coupling');
const MINIMUM_BUY_REQUIREMENT = 0.1; const MINIMUM_BUY_REQUIREMENT = 0.1;
var DB; //container for database var DB, allowedAssets; //container for database and allowed assets
function addSellOrder(floID, quantity, min_price) { const getAssetBalance = (floID, asset) => new Promise((resolve, reject) => {
let promises = (asset === floGlobals.currency) ? [
DB.query("SELECT balance FROM Cash WHERE floID=?", [floID]),
DB.query("SELECT SUM(quantity*maxPrice) AS locked FROM BuyOrder WHERE floID=?", [floID])
] : [
DB.query("SELECT SUM(quantity) AS balance FROM Vault WHERE floID=?, asset=?", [floID, asset]),
DB.query("SELECT SUM(quantity) AS locked FROM SellOrder WHERE floID=? AND asset=?", [floID, asset])
];
Promise.all(promises).then(result => resolve({
total: result[0][0].balance,
locked: result[1][0].locked,
net: result[0][0].balance - result[1][0].locked
})).catch(error => reject(error))
});
getAssetBalance.check = (floID, asset, amount) => new Promise((resolve, reject) => {
getAssetBalance(floID, asset).then(balance => {
if (balance.total < amount)
reject(INVALID(`Insufficient ${asset}`));
else if (balance.net < amount)
reject(INVALID(`Insufficient ${asset} (Some are locked in orders)`));
else
resolve(true);
}).catch(error => reject(error))
})
const consumeAsset = (floID, asset, amount, txQueries = []) => new Promise((resolve, reject) => {
//If asset/token is currency update Cash else consume from Vault
if (asset === floGlobals.currency) {
txQueries.push(["UPDATE Cash SET balance=balance-? WHERE floID=?", [amount, floID]]);
return resolve(txQueries);
} else
DB.query("SELECT id, quantity FROM Vault WHERE floID=? AND asset=? ORDER BY locktime", [floID, asset]).then(coins => {
let rem = amount;
for (let i = 0; i < coins.length && rem > 0; i++) {
if (rem < coins[i].quantity) {
txQueries.push(["UPDATE Vault SET quantity=quantity-? WHERE id=?", [rem, coins[i].id]]);
rem = 0;
} else {
txQueries.push(["DELETE FROM Vault WHERE id=?", [coins[i].id]]);
rem -= result[i].quantity;
}
}
if (rem > 0) //should not happen AS the total and net is checked already
reject(INVALID("Insufficient Asset"));
else
resolve(txQueries);
}).catch(error => reject(error));
});
function addSellOrder(floID, asset, quantity, min_price) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!floID || !floCrypto.validateAddr(floID)) if (!floID || !floCrypto.validateAddr(floID))
return reject(INVALID("Invalid FLO ID")); return reject(INVALID("Invalid FLO ID"));
@ -13,41 +63,33 @@ function addSellOrder(floID, quantity, min_price) {
return reject(INVALID(`Invalid quantity (${quantity})`)); return reject(INVALID(`Invalid quantity (${quantity})`));
else if (typeof min_price !== "number" || min_price <= 0) else if (typeof min_price !== "number" || min_price <= 0)
return reject(INVALID(`Invalid min_price (${min_price})`)); return reject(INVALID(`Invalid min_price (${min_price})`));
checkSellRequirement(floID).then(_ => { else if (!allowedAssets.includes(asset))
DB.query("SELECT SUM(quantity) AS total FROM Vault WHERE floID=?", [floID]).then(result => { return reject(INVALID(`Invalid asset (${asset})`));
let total = result.pop()["total"] || 0; getAssetBalance.check(floID, asset, quantity).then(_ => {
if (total < quantity) checkSellRequirement(floID).then(_ => {
return reject(INVALID("Insufficient FLO")); DB.query("INSERT INTO SellOrder(floID, quantity, minPrice) VALUES (?, ?, ?)", [floID, quantity, min_price])
DB.query("SELECT SUM(quantity) AS locked FROM SellOrder WHERE floID=?", [floID]).then(result => { .then(result => resolve("Added SellOrder to DB"))
let locked = result.pop()["locked"] || 0; .catch(error => reject(error));
let available = total - locked; }).catch(error => reject(error))
if (available < quantity)
return reject(INVALID("Insufficient FLO (Some FLO are locked in another sell order)"));
DB.query("INSERT INTO SellOrder(floID, quantity, minPrice) VALUES (?, ?, ?)", [floID, quantity, min_price])
.then(result => resolve("Added SellOrder to DB"))
.catch(error => reject(error));
}).catch(error => reject(error));
}).catch(error => reject(error));
}).catch(error => reject(error)); }).catch(error => reject(error));
}); });
} }
function checkSellRequirement(floID) { const checkSellRequirement = floID => new Promise((resolve, reject) => {
return new Promise((resolve, reject) => { DB.query("SELECT * FROM Tags WHERE floID=? AND tag=?", [floID, "MINER"]).then(result => {
DB.query("SELECT * FROM Tags WHERE floID=? AND tag=?", [floID, "MINER"]).then(result => { if (result.length)
if (result.length) return resolve(true);
return resolve(true); //TODO: Should seller need to buy same type of asset before selling?
DB.query("SELECT SUM(quantity) AS brought FROM Transactions WHERE buyer=?", [floID]).then(result => { DB.query("SELECT SUM(quantity) AS brought FROM Transactions WHERE buyer=?", [floID]).then(result => {
if (result[0].brought >= MINIMUM_BUY_REQUIREMENT) if (result[0].brought >= MINIMUM_BUY_REQUIREMENT)
resolve(true); resolve(true);
else else
reject(INVALID(`Sellers required to buy atleast ${MINIMUM_BUY_REQUIREMENT} FLO before placing a sell order unless they are a Miner`)); reject(INVALID(`Sellers required to buy atleast ${MINIMUM_BUY_REQUIREMENT} FLO before placing a sell order unless they are a Miner`));
}).catch(error => reject(error))
}).catch(error => reject(error)) }).catch(error => reject(error))
}) }).catch(error => reject(error))
} });
function addBuyOrder(floID, quantity, max_price) { function addBuyOrder(floID, asset, quantity, max_price) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!floID || !floCrypto.validateAddr(floID)) if (!floID || !floCrypto.validateAddr(floID))
return reject(INVALID("Invalid FLO ID")); return reject(INVALID("Invalid FLO ID"));
@ -55,21 +97,12 @@ function addBuyOrder(floID, quantity, max_price) {
return reject(INVALID(`Invalid quantity (${quantity})`)); return reject(INVALID(`Invalid quantity (${quantity})`));
else if (typeof max_price !== "number" || max_price <= 0) else if (typeof max_price !== "number" || max_price <= 0)
return reject(INVALID(`Invalid max_price (${max_price})`)); return reject(INVALID(`Invalid max_price (${max_price})`));
DB.query("SELECT rupeeBalance FROM Cash WHERE floID=?", [floID]).then(result => { else if (!allowedAssets.includes(asset))
if (result.length < 1) return reject(INVALID(`Invalid asset (${asset})`));
return reject(INVALID("FLO ID not registered")); getAssetBalance.check(floID, asset, quantity).then(_ => {
let total = result.pop()["rupeeBalance"]; DB.query("INSERT INTO BuyOrder(floID, quantity, maxPrice) VALUES (?, ?, ?)", [floID, quantity, max_price])
if (total < quantity * max_price) .then(result => resolve("Added BuyOrder to DB"))
return reject(INVALID("Insufficient Rupee balance")); .catch(error => reject(error));
DB.query("SELECT SUM(maxPrice * quantity) AS locked FROM BuyOrder WHERE floID=?", [floID]).then(result => {
let locked = result.pop()["locked"] || 0;
let available = total - locked;
if (available < quantity * max_price)
return reject(INVALID("Insufficient Rupee balance (Some rupee tokens are locked in another buy order)"));
DB.query("INSERT INTO BuyOrder(floID, quantity, maxPrice) VALUES (?, ?, ?)", [floID, quantity, max_price])
.then(result => resolve("Added BuyOrder to DB"))
.catch(error => reject(error));
}).catch(error => reject(error));
}).catch(error => reject(error)); }).catch(error => reject(error));
}); });
} }
@ -101,11 +134,11 @@ function cancelOrder(type, id, floID) {
function getAccountDetails(floID) { function getAccountDetails(floID) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let select = []; let select = [];
select.push(["rupeeBalance", "Cash"]); select.push(["balance", "Cash"]);
select.push(["base, quantity", "Vault"]); select.push(["asset, AVG(base) AS avg_base, SUM(quantity) AS quantity", "Vault", "GROUP BY asset"]);
select.push(["id, quantity, minPrice, time_placed", "SellOrder"]); select.push(["id, asset, quantity, minPrice, time_placed", "SellOrder"]);
select.push(["id, quantity, maxPrice, time_placed", "BuyOrder"]); select.push(["id, asset, quantity, maxPrice, time_placed", "BuyOrder"]);
let promises = select.map(a => DB.query("SELECT " + a[0] + " FROM " + a[1] + " WHERE floID=?", [floID])); let promises = select.map(a => DB.query(`SELECT ${a[0]} FROM ${a[1]} WHERE floID=? ${a[2] || ""}`, [floID]));
Promise.allSettled(promises).then(results => { Promise.allSettled(promises).then(results => {
let response = { let response = {
floID: floID, floID: floID,
@ -117,10 +150,10 @@ function getAccountDetails(floID) {
else else
switch (i) { switch (i) {
case 0: case 0:
response.rupee_total = a.value[0].rupeeBalance; response.cash = a.value[0].balance;
break; break;
case 1: case 1:
response.coins = a.value; response.vault = a.value;
break; break;
case 2: case 2:
response.sellOrders = a.value; response.sellOrders = a.value;
@ -166,7 +199,7 @@ function confirmDepositFLO() {
txQueries.push(["INSERT INTO Vault(floID, quantity) VALUES (?, ?)", [req.floID, amount]]); txQueries.push(["INSERT INTO Vault(floID, quantity) VALUES (?, ?)", [req.floID, 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 for ", req.floID)) .then(result => console.debug("FLO deposited:", req.floID, amount))
.catch(error => console.error(error)) .catch(error => console.error(error))
}).catch(error => { }).catch(error => {
console.error(error); console.error(error);
@ -206,45 +239,23 @@ function withdrawFLO(floID, amount) {
return reject(INVALID("Invalid FLO ID")); return reject(INVALID("Invalid FLO ID"));
else if (typeof amount !== "number" || amount <= 0) else if (typeof amount !== "number" || amount <= 0)
return reject(INVALID(`Invalid amount (${amount})`)); return reject(INVALID(`Invalid amount (${amount})`));
DB.query("SELECT SUM(quantity) AS total FROM Vault WHERE floID=?", [floID]).then(result => { getAssetBalance.check(floID, "FLO", amount).then(_ => {
let total = result.pop()["total"] || 0; consumeAsset(floID, "FLO", amount).then(txQueries => {
if (total < amount) DB.transaction(txQueries).then(result => {
return reject(INVALID("Insufficient FLO")); //Send FLO to user via blockchain API
DB.query("SELECT SUM(quantity) AS locked FROM SellOrder WHERE floID=?", [floID]).then(result => { floBlockchainAPI.sendTx(global.myFloID, floID, amount, global.myPrivKey, 'Withdraw FLO Coins from Market').then(txid => {
let locked = result.pop()["locked"] || 0; if (!txid)
let available = total - locked; throw Error("Transaction not successful");
if (available < amount) //Transaction was successful, Add in DB
return reject(INVALID("Insufficient FLO (Some FLO are locked in sell orders)")); DB.query("INSERT INTO outputFLO (floID, amount, txid, status) VALUES (?, ?, ?, ?)", [floID, amount, txid, "WAITING_CONFIRMATION"])
DB.query("SELECT id, quantity, base FROM Vault WHERE floID=? ORDER BY locktime", [floID]).then(coins => { .then(_ => null).catch(error => console.error(error))
let rem = amount, .finally(_ => resolve("Withdrawal was successful"));
txQueries = []; }).catch(error => {
for (let i = 0; i < coins.length && rem > 0; i++) { console.debug(error);
if (rem < coins[i].quantity) { DB.query("INSERT INTO outputFLO (floID, amount, status) VALUES (?, ?, ?)", [floID, amount, "PENDING"])
txQueries.push(["UPDATE Vault SET quantity=quantity-? WHERE id=?", [rem, coins[i].id]]); .then(_ => null).catch(error => console.error(error))
rem = 0; .finally(_ => resolve("Withdrawal request is in process"));
} else { });
txQueries.push(["DELETE FROM Vault WHERE id=?", [coins[i].id]]);
rem -= coins[i].quantity;
}
}
if (rem > 0) //should not happen AS the total and net is checked already
return reject(INVALID("Insufficient FLO"));
DB.transaction(txQueries).then(result => {
//Send FLO to user via blockchain API
floBlockchainAPI.sendTx(global.myFloID, floID, amount, global.myPrivKey, 'Withdraw FLO Coins from Market').then(txid => {
if (!txid)
throw Error("Transaction not successful");
//Transaction was successful, Add in DB
DB.query("INSERT INTO outputFLO (floID, amount, txid, status) VALUES (?, ?, ?, ?)", [floID, amount, txid, "WAITING_CONFIRMATION"])
.then(_ => null).catch(error => console.error(error))
.finally(_ => resolve("Withdrawal was successful"));
}).catch(error => {
console.debug(error);
DB.query("INSERT INTO outputFLO (floID, amount, status) VALUES (?, ?, ?)", [floID, amount, "PENDING"])
.then(_ => null).catch(error => console.error(error))
.finally(_ => resolve("Withdrawal request is in process"));
});
}).catch(error => reject(error));
}).catch(error => reject(error)); }).catch(error => reject(error));
}).catch(error => reject(error)); }).catch(error => reject(error));
}).catch(error => reject(error)); }).catch(error => reject(error));
@ -266,22 +277,22 @@ function retryWithdrawalFLO() {
} }
function confirmWithdrawalFLO() { function confirmWithdrawalFLO() {
DB.query("SELECT id, floID, txid FROM outputFLO WHERE status=?", ["WAITING_CONFIRMATION"]).then(results => { DB.query("SELECT id, floID, amount, txid FROM outputFLO WHERE status=?", ["WAITING_CONFIRMATION"]).then(results => {
results.forEach(req => { results.forEach(req => {
floBlockchainAPI.getTx(req.txid).then(tx => { floBlockchainAPI.getTx(req.txid).then(tx => {
if (!tx.blockheight || !tx.confirmations) //Still not confirmed if (!tx.blockheight || !tx.confirmations) //Still not confirmed
return; return;
DB.query("UPDATE outputFLO SET status=? WHERE id=?", ["SUCCESS", req.id]) DB.query("UPDATE outputFLO SET status=? WHERE id=?", ["SUCCESS", req.id])
.then(result => console.debug("FLO withdrawed for ", req.floID)) .then(result => console.debug("FLO withdrawed:", req.floID, req.amount))
.catch(error => console.error(error)) .catch(error => console.error(error))
}).catch(error => console.error(error)); }).catch(error => console.error(error));
}) })
}).catch(error => console.error(error)); }).catch(error => console.error(error));
} }
function depositRupee(floID, txid) { function depositToken(floID, txid) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
DB.query("SELECT status FROM inputRupee WHERE txid=? AND floID=?", [txid, floID]).then(result => { DB.query("SELECT status FROM inputToken WHERE txid=? AND floID=?", [txid, floID]).then(result => {
if (result.length) { if (result.length) {
switch (result[0].status) { switch (result[0].status) {
case "PENDING": case "PENDING":
@ -292,53 +303,58 @@ function depositRupee(floID, txid) {
return reject(INVALID("Transaction already used to add tokens")); return reject(INVALID("Transaction already used to add tokens"));
} }
} else } else
DB.query("INSERT INTO inputRupee(txid, floID, status) VALUES (?, ?, ?)", [txid, floID, "PENDING"]) DB.query("INSERT INTO inputToken(txid, floID, status) VALUES (?, ?, ?)", [txid, floID, "PENDING"])
.then(result => resolve("Deposit request in process")) .then(result => resolve("Deposit request in process"))
.catch(error => reject(error)); .catch(error => reject(error));
}).catch(error => reject(error)) }).catch(error => reject(error))
}); });
} }
function confirmDepositRupee() { function confirmDepositToken() {
DB.query("SELECT id, floID, txid FROM inputRupee WHERE status=?", ["PENDING"]).then(results => { DB.query("SELECT id, floID, txid FROM inputToken WHERE status=?", ["PENDING"]).then(results => {
results.forEach(req => { results.forEach(req => {
confirmDepositRupee.checkTx(req.floID, req.txid).then(amounts => { confirmDepositToken.checkTx(req.floID, req.txid).then(amounts => {
DB.query("SELECT id FROM inputFLO where floID=? AND txid=?", [req.floID, req.txid]).then(result => { DB.query("SELECT id FROM inputFLO where floID=? AND txid=?", [req.floID, req.txid]).then(result => {
let txQueries = [], let txQueries = [],
amount_rupee = amounts[0]; token_name = amounts[0],
amount_token = amounts[1];
//Add the FLO balance if necessary //Add the FLO balance if necessary
if (!result.length) { if (!result.length) {
let amount_flo = amounts[1]; let amount_flo = amounts[2];
txQueries.push(["INSERT INTO Vault(floID, quantity) VALUES (?, ?)", [req.floID, amount_flo]]); txQueries.push(["INSERT INTO Vault(floID, asset, quantity) VALUES (?, ?, ?)", [req.floID, "FLO", amount_flo]]);
txQueries.push(["INSERT INTO inputFLO(txid, floID, amount, status) VALUES (?, ?, ?, ?)", [req.txid, req.floID, amount_flo, "SUCCESS"]]); txQueries.push(["INSERT INTO inputFLO(txid, floID, amount, status) VALUES (?, ?, ?, ?)", [req.txid, req.floID, amount_flo, "SUCCESS"]]);
} }
txQueries.push(["UPDATE inputRupee SET status=? WHERE id=?", ["SUCCESS", req.id]]); txQueries.push(["UPDATE inputToken SET status=?, token=?, amount=? WHERE id=?", ["SUCCESS", token_name, amount_token, req.id]]);
txQueries.push(["UPDATE Cash SET rupeeBalance=rupeeBalance+? WHERE floID=?", [amount_rupee, req.floID]]); if (token_name === floGlobals.currency)
txQueries.push(["UPDATE Cash SET balance=balance+? WHERE floID=?", [amount_token, req.floID]]);
else
txQueries.push(["INSERT INTO Vault(floID, asset, quantity) VALUES (?, ?, ?)", [req.floID, token_name, amount_token]]);
DB.transaction(txQueries) DB.transaction(txQueries)
.then(result => console.debug("Rupee deposited for ", req.floID)) .then(result => console.debug("Token deposited:", req.floID, token_name, amount_token))
.catch(error => console.error(error)); .catch(error => console.error(error));
}).catch(error => console.error(error)); }).catch(error => console.error(error));
}).catch(error => { }).catch(error => {
console.error(error); console.error(error);
if (error[0]) if (error[0])
DB.query("UPDATE inputRupee SET status=? WHERE id=?", ["REJECTED", req.id]) DB.query("UPDATE inputToken SET status=? WHERE id=?", ["REJECTED", req.id])
.then(_ => null).catch(error => console.error(error)); .then(_ => null).catch(error => console.error(error));
}); });
}) })
}).catch(error => console.error(error)) }).catch(error => console.error(error))
} }
confirmDepositRupee.checkTx = function(sender, txid) { confirmDepositToken.checkTx = function(sender, txid) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const receiver = global.myFloID; //receiver should be market's floID (ie, adminID) const receiver = global.sinkID; //receiver should be market's floID (ie, sinkID)
tokenAPI.getTx(txid).then(tx => { tokenAPI.getTx(txid).then(tx => {
if (tx.parsedFloData.type !== "transfer") if (tx.parsedFloData.type !== "transfer")
return reject([true, "Transaction type not 'transfer'"]); return reject([true, "Transaction type not 'transfer'"]);
else if (tx.parsedFloData.transferType !== "token") else if (tx.parsedFloData.transferType !== "token")
return reject([true, "Transaction transfer is not 'token'"]); return reject([true, "Transaction transfer is not 'token'"]);
else if (tx.parsedFloData.tokenIdentification !== "rupee") var token_name = tx.parsedFloData.tokenIdentification,
return reject([true, "Transaction token is not 'rupee'"]); amount_token = tx.parsedFloData.tokenAmount;
var amount_rupee = tx.parsedFloData.tokenAmount; if (!allowedAssets.includes(token_name) || token_name === "FLO")
return reject([true, "Token not authorised"]);
let vin_sender = tx.transactionDetails.vin.filter(v => v.addr === sender) let vin_sender = tx.transactionDetails.vin.filter(v => v.addr === sender)
if (!vin_sender.length) if (!vin_sender.length)
return reject([true, "Transaction not sent by the sender"]); return reject([true, "Transaction not sent by the sender"]);
@ -346,60 +362,36 @@ confirmDepositRupee.checkTx = function(sender, txid) {
if (amount_flo == 0) if (amount_flo == 0)
return reject([true, "Transaction receiver is not market ID"]); return reject([true, "Transaction receiver is not market ID"]);
else else
resolve([amount_rupee, amount_flo]); resolve([token_name, amount_token, amount_flo]);
}).catch(error => reject([false, error])) }).catch(error => reject([false, error]))
}) })
} }
function withdrawRupee(floID, amount) { function withdrawToken(floID, token, amount) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!floID || !floCrypto.validateAddr(floID)) if (!floID || !floCrypto.validateAddr(floID))
return reject(INVALID("Invalid FLO ID")); return reject(INVALID("Invalid FLO ID"));
else if (typeof amount !== "number" || amount <= 0) else if (typeof amount !== "number" || amount <= 0)
return reject(INVALID(`Invalid amount (${amount})`)); return reject(INVALID(`Invalid amount (${amount})`));
DB.query("SELECT SUM(quantity) AS total FROM Vault WHERE floID=?", [floID]).then(result => { else if (!allowedAssets.includes(token) || token === "FLO")
let required_flo = floGlobals.sendAmt + floGlobals.fee, return reject(INVALID("Invalid Token"));
total = result.pop()["total"] || 0; //Check for FLO balance (transaction fee)
if (total < required_flo) const required_flo = floGlobals.sendAmt + floGlobals.fee;
return reject(INVALID(`Insufficient FLO! Required ${required_flo} FLO to withdraw tokens`)); getAssetBalance.check(floID, "FLO", required_flo).then(_ => {
DB.query("SELECT SUM(quantity) AS locked FROM SellOrder WHERE floID=?", [floID]).then(result => { getAssetBalance.check(floID, token, amount).then(_ => {
let locked = result.pop()["locked"] || 0; consumeAsset(floID, "FLO", required_flo).then(txQueries => {
let available = total - locked; consumeAsset(floID, token, amount, txQueries).then(txQueries => {
if (available < required_flo)
return reject(INVALID(`Insufficient FLO (Some FLO are locked in sell orders)! Required ${required_flo} FLO to withdraw tokens`));
DB.query("SELECT rupeeBalance FROM Cash WHERE floID=?", [floID]).then(result => {
if (result.length < 1)
return reject(INVALID(`FLO_ID: ${floID} not registered`));
if (result[0].rupeeBalance < amount)
return reject(INVALID('Insufficient Rupee balance'));
DB.query("SELECT id, quantity, base FROM Vault WHERE floID=? ORDER BY locktime", [floID]).then(coins => {
let rem = required_flo,
txQueries = [];
for (let i = 0; i < coins.length && rem > 0; i++) {
if (rem < coins[i].quantity) {
txQueries.push(["UPDATE Vault SET quantity=quantity-? WHERE id=?", [rem, coins[i].id]]);
rem = 0;
} else {
txQueries.push(["DELETE FROM Vault WHERE id=?", [coins[i].id]]);
rem -= result[i].quantity;
}
}
if (rem > 0) //should not happen AS the total and net is checked already
return reject(INVALID("Insufficient FLO"));
txQueries.push(["UPDATE Cash SET rupeeBalance=rupeeBalance-? WHERE floID=?", [amount, floID]]);
DB.transaction(txQueries).then(result => { DB.transaction(txQueries).then(result => {
//Send FLO to user via blockchain API //Send FLO to user via blockchain API
tokenAPI.sendToken(global.myPrivKey, amount, '(withdrawal from market)', floID).then(txid => { tokenAPI.sendToken(global.myPrivKey, amount, '(withdrawal from market)', floID, token).then(txid => {
if (!txid) if (!txid) throw Error("Transaction not successful");
throw Error("Transaction not successful");
//Transaction was successful, Add in DB //Transaction was successful, Add in DB
DB.query("INSERT INTO outputRupee (floID, amount, txid, status) VALUES (?, ?, ?, ?)", [floID, amount, txid, "WAITING_CONFIRMATION"]) DB.query("INSERT INTO outputToken (floID, token, amount, txid, status) VALUES (?, ?, ?, ?, ?)", [floID, token, amount, txid, "WAITING_CONFIRMATION"])
.then(_ => null).catch(error => console.error(error)) .then(_ => null).catch(error => console.error(error))
.finally(_ => resolve("Withdrawal was successful")); .finally(_ => resolve("Withdrawal was successful"));
}).catch(error => { }).catch(error => {
console.debug(error); console.debug(error);
DB.query("INSERT INTO outputRupee (floID, amount, status) VALUES (?, ?, ?)", [floID, amount, "PENDING"]) DB.query("INSERT INTO outputToken (floID, token, amount, status) VALUES (?, ?, ?, ?)", [floID, token, amount, "PENDING"])
.then(_ => null).catch(error => console.error(error)) .then(_ => null).catch(error => console.error(error))
.finally(_ => resolve("Withdrawal request is in process")); .finally(_ => resolve("Withdrawal request is in process"));
}); });
@ -411,26 +403,26 @@ function withdrawRupee(floID, amount) {
}); });
} }
function retryWithdrawalRupee() { function retryWithdrawalToken() {
DB.query("SELECT id, floID, amount FROM outputRupee WHERE status=?", ["PENDING"]).then(results => { DB.query("SELECT id, floID, token, amount FROM outputToken WHERE status=?", ["PENDING"]).then(results => {
results.forEach(req => { results.forEach(req => {
tokenAPI.sendToken(global.myPrivKey, req.amount, '(withdrawal from market)', req.floID).then(txid => { tokenAPI.sendToken(global.myPrivKey, req.amount, '(withdrawal from market)', req.floID, req.token).then(txid => {
if (!txid) if (!txid)
throw Error("Transaction not successful"); throw Error("Transaction not successful");
//Transaction was successful, Add in DB //Transaction was successful, Add in DB
DB.query("UPDATE outputRupee SET status=?, txid=? WHERE id=?", ["WAITING_CONFIRMATION", txid, req.id]) DB.query("UPDATE outputToken SET status=?, txid=? WHERE id=?", ["WAITING_CONFIRMATION", txid, req.id])
.then(_ => null).catch(error => console.error(error)); .then(_ => null).catch(error => console.error(error));
}).catch(error => console.error(error)); }).catch(error => console.error(error));
}); });
}).catch(error => reject(error)); }).catch(error => reject(error));
} }
function confirmWithdrawalRupee() { function confirmWithdrawalToken() {
DB.query("SELECT id, floID, amount, txid FROM outputRupee WHERE status=?", ["WAITING_CONFIRMATION"]).then(results => { DB.query("SELECT id, floID, token, amount, txid FROM outputToken WHERE status=?", ["WAITING_CONFIRMATION"]).then(results => {
results.forEach(req => { results.forEach(req => {
tokenAPI.getTx(req.txid).then(tx => { tokenAPI.getTx(req.txid).then(tx => {
DB.query("UPDATE outputRupee SET status=? WHERE id=?", ["SUCCESS", req.id]) DB.query("UPDATE outputToken SET status=? WHERE id=?", ["SUCCESS", req.id])
.then(result => console.debug("Rupee withdrawed for ", req.floID)) .then(result => console.debug("Token withdrawed:", req.floID, req.token, req.amount))
.catch(error => console.error(error)); .catch(error => console.error(error));
}).catch(error => console.error(error)); }).catch(error => console.error(error));
}) })
@ -444,11 +436,11 @@ function periodicProcess() {
function blockchainReCheck() { function blockchainReCheck() {
confirmDepositFLO(); confirmDepositFLO();
confirmDepositRupee(); confirmDepositToken();
retryWithdrawalFLO(); retryWithdrawalFLO();
retryWithdrawalRupee(); retryWithdrawalToken();
confirmWithdrawalFLO(); confirmWithdrawalFLO();
confirmWithdrawalRupee(); confirmWithdrawalToken();
} }
module.exports = { module.exports = {
@ -461,12 +453,15 @@ module.exports = {
getAccountDetails, getAccountDetails,
depositFLO, depositFLO,
withdrawFLO, withdrawFLO,
depositRupee, depositToken,
withdrawRupee, withdrawToken,
periodicProcess, periodicProcess,
group: coupling.group, group: coupling.group,
set DB(db) { set DB(db) {
DB = db; DB = db;
coupling.DB = db; coupling.DB = db;
},
set allowedAssets(assets) {
allowedAssets = assets;
} }
}; };

View File

@ -159,11 +159,12 @@ function PlaceSellOrder(req, res) {
let data = req.body; let data = req.body;
validateRequestFromFloID({ validateRequestFromFloID({
type: "sell_order", type: "sell_order",
asset: data.asset,
quantity: data.quantity, quantity: data.quantity,
min_price: data.min_price, min_price: data.min_price,
timestamp: data.timestamp timestamp: data.timestamp
}, data.sign, data.floID).then(req_str => { }, data.sign, data.floID).then(req_str => {
market.addSellOrder(data.floID, data.quantity, data.min_price) market.addSellOrder(data.floID, data.asset, data.quantity, data.min_price)
.then(result => { .then(result => {
storeRequest(data.floID, req_str, data.sign); storeRequest(data.floID, req_str, data.sign);
res.send('Sell Order placed successfully'); res.send('Sell Order placed successfully');
@ -189,11 +190,12 @@ function PlaceBuyOrder(req, res) {
let data = req.body; let data = req.body;
validateRequestFromFloID({ validateRequestFromFloID({
type: "buy_order", type: "buy_order",
asset: data.asset,
quantity: data.quantity, quantity: data.quantity,
max_price: data.max_price, max_price: data.max_price,
timestamp: data.timestamp timestamp: data.timestamp
}, data.sign, data.floID).then(req_str => { }, data.sign, data.floID).then(req_str => {
market.addBuyOrder(data.floID, data.quantity, data.max_price) market.addBuyOrder(data.floID, data.asset, data.quantity, data.max_price)
.then(result => { .then(result => {
storeRequest(data.floID, req_str, data.sign); storeRequest(data.floID, req_str, data.sign);
res.send('Buy Order placed successfully'); res.send('Buy Order placed successfully');
@ -351,14 +353,14 @@ function WithdrawFLO(req, res) {
}); });
} }
function DepositRupee(req, res) { function DepositToken(req, res) {
let data = req.body; let data = req.body;
validateRequestFromFloID({ validateRequestFromFloID({
type: "deposit_Rupee", type: "deposit_Token",
txid: data.txid, txid: data.txid,
timestamp: data.timestamp timestamp: data.timestamp
}, data.sign, data.floID).then(req_str => { }, data.sign, data.floID).then(req_str => {
market.depositRupee(data.floID, data.txid).then(result => { market.depositToken(data.floID, data.txid).then(result => {
storeRequest(data.floID, req_str, data.sign); storeRequest(data.floID, req_str, data.sign);
res.send(result); res.send(result);
}).catch(error => { }).catch(error => {
@ -379,14 +381,15 @@ function DepositRupee(req, res) {
}); });
} }
function WithdrawRupee(req, res) { function WithdrawToken(req, res) {
let data = req.body; let data = req.body;
validateRequestFromFloID({ validateRequestFromFloID({
type: "withdraw_Rupee", type: "withdraw_Token",
token: data.token,
amount: data.amount, amount: data.amount,
timestamp: data.timestamp timestamp: data.timestamp
}, data.sign, data.floID).then(req_str => { }, data.sign, data.floID).then(req_str => {
market.withdrawRupee(data.floID, data.amount).then(result => { market.withdrawToken(data.floID, data.token, data.amount).then(result => {
storeRequest(data.floID, req_str, data.sign); storeRequest(data.floID, req_str, data.sign);
res.send(result); res.send(result);
}).catch(error => { }).catch(error => {
@ -485,8 +488,8 @@ module.exports = {
Account, Account,
DepositFLO, DepositFLO,
WithdrawFLO, WithdrawFLO,
DepositRupee, DepositToken,
WithdrawRupee, WithdrawToken,
periodicProcess: market.periodicProcess, periodicProcess: market.periodicProcess,
addUserTag, addUserTag,
removeUserTag, removeUserTag,

View File

@ -14,7 +14,7 @@
}).catch(error => reject(error)) }).catch(error => reject(error))
}) })
}, },
getBalance: function(floID, token = floGlobals.token) { getBalance: function(floID, token = floGlobals.currency) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.fetch_api(`api/v1.0/getFloAddressBalance?token=${token}&floAddress=${floID}`) this.fetch_api(`api/v1.0/getFloAddressBalance?token=${token}&floAddress=${floID}`)
.then(result => resolve(result.balance || 0)) .then(result => resolve(result.balance || 0))
@ -35,7 +35,7 @@
}).catch(error => reject(error)) }).catch(error => reject(error))
}) })
}, },
sendToken: function(privKey, amount, message = "", receiverID = floGlobals.adminID, token = floGlobals.token) { sendToken: function(privKey, amount, message = "", receiverID = floGlobals.adminID, token = floGlobals.currency) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let senderID = floCrypto.getFloID(privKey); let senderID = floCrypto.getFloID(privKey);
if (typeof amount !== "number" || amount <= 0) if (typeof amount !== "number" || amount <= 0)