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:
parent
512dc3bcbc
commit
67c31d79a9
@ -28,7 +28,7 @@ FOREIGN KEY (floID) REFERENCES Users(floID)
|
||||
CREATE TABLE Cash (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
floID CHAR(34) NOT NULL UNIQUE,
|
||||
rupeeBalance DECIMAL(12, 2) DEFAULT 0.00,
|
||||
balance DECIMAL(12, 2) DEFAULT 0.00,
|
||||
PRIMARY KEY(id),
|
||||
FOREIGN KEY (floID) REFERENCES Users(floID)
|
||||
);
|
||||
@ -37,6 +37,7 @@ CREATE TABLE Vault (
|
||||
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,
|
||||
PRIMARY KEY(id),
|
||||
@ -93,20 +94,22 @@ PRIMARY KEY(id),
|
||||
FOREIGN KEY (floID) REFERENCES Users(floID)
|
||||
);
|
||||
|
||||
CREATE TABLE inputRupee (
|
||||
CREATE TABLE inputToken (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
txid VARCHAR(128) NOT NULL,
|
||||
floID CHAR(34) NOT NULL,
|
||||
token VARCHAR(64),
|
||||
amount FLOAT,
|
||||
status VARCHAR(50) NOT NULL,
|
||||
PRIMARY KEY(id),
|
||||
FOREIGN KEY (floID) REFERENCES Users(floID)
|
||||
);
|
||||
|
||||
CREATE TABLE outputRupee (
|
||||
CREATE TABLE outputToken (
|
||||
id INT NOT NULL AUTO_INCREMENT,
|
||||
txid VARCHAR(128),
|
||||
floID CHAR(34) NOT NULL,
|
||||
token VARCHAR(64),
|
||||
amount FLOAT NOT NULL,
|
||||
status VARCHAR(50) NOT NULL,
|
||||
PRIMARY KEY(id),
|
||||
@ -159,16 +162,17 @@ rec_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
unit_price FLOAT NOT NULL,
|
||||
quantity FLOAT NOT NULL,
|
||||
total_cost FLOAT NOT NULL,
|
||||
asset VARCHAR(64) NOT NULL,
|
||||
sellerID CHAR(34) NOT NULL,
|
||||
FLO_seller_old FLOAT NOT NULL,
|
||||
FLO_seller_new FLOAT NOT NULL,
|
||||
Rupee_seller_old FLOAT NOT NULL,
|
||||
Rupee_seller_new FLOAT 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,
|
||||
buyerID CHAR(34) NOT NULL,
|
||||
FLO_buyer_old FLOAT NOT NULL,
|
||||
FLO_buyer_new FLOAT NOT NULL,
|
||||
Rupee_buyer_old FLOAT NOT NULL,
|
||||
Rupee_buyer_new FLOAT 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,
|
||||
FOREIGN KEY (sellerID) 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
|
||||
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
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('inputRupee', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
CREATE TRIGGER inputRupee_U AFTER UPDATE ON inputRupee
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('inputRupee', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
CREATE TRIGGER inputRupee_D AFTER DELETE ON inputRupee
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('inputRupee', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT;
|
||||
CREATE TRIGGER inputToken_I AFTER INSERT ON inputToken
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('inputToken', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
CREATE TRIGGER inputToken_U AFTER UPDATE ON inputToken
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('inputToken', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
CREATE TRIGGER inputToken_D AFTER DELETE ON inputToken
|
||||
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
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('outputRupee', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
CREATE TRIGGER outputRupee_U AFTER UPDATE ON outputRupee
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('outputRupee', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
CREATE TRIGGER outputRupee_D AFTER DELETE ON outputRupee
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('outputRupee', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT;
|
||||
CREATE TRIGGER outputToken_I AFTER INSERT ON outputToken
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('outputToken', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
CREATE TRIGGER outputToken_U AFTER UPDATE ON outputToken
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('outputToken', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
CREATE TRIGGER outputToken_D AFTER DELETE ON outputToken
|
||||
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
|
||||
FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('Tags', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT;
|
||||
|
||||
@ -4,9 +4,9 @@ TRUNCATE auditTransaction;
|
||||
TRUNCATE BuyOrder;
|
||||
TRUNCATE Cash;
|
||||
TRUNCATE inputFLO;
|
||||
TRUNCATE inputRupee;
|
||||
TRUNCATE inputToken;
|
||||
TRUNCATE outputFLO;
|
||||
TRUNCATE outputRupee;
|
||||
TRUNCATE outputToken;
|
||||
TRUNCATE priceHistory;
|
||||
TRUNCATE Request_Log;
|
||||
TRUNCATE SellOrder;
|
||||
|
||||
@ -13,7 +13,7 @@ const floGlobals = {
|
||||
sendAmt: 0.001,
|
||||
fee: 0.0005,
|
||||
tokenURL: "https://ranchimallflo.duckdns.org/",
|
||||
token: "rupee"
|
||||
currency: "rupee"
|
||||
};
|
||||
|
||||
(typeof global !== "undefined" ? global : window).cryptocoin = floGlobals.blockchain;
|
||||
|
||||
@ -80,8 +80,8 @@ module.exports = function App(secret, DB) {
|
||||
//withdraw and deposit request
|
||||
app.post('/deposit-flo', Request.DepositFLO);
|
||||
app.post('/withdraw-flo', Request.WithdrawFLO);
|
||||
app.post('/deposit-rupee', Request.DepositRupee);
|
||||
app.post('/withdraw-rupee', Request.WithdrawRupee);
|
||||
app.post('/deposit-token', Request.DepositToken);
|
||||
app.post('/withdraw-token', Request.WithdrawToken);
|
||||
|
||||
//Manage user tags (Access to trusted IDs only)
|
||||
|
||||
|
||||
261
src/market.js
261
src/market.js
@ -3,9 +3,59 @@
|
||||
const coupling = require('./coupling');
|
||||
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) => {
|
||||
if (!floID || !floCrypto.validateAddr(floID))
|
||||
return reject(INVALID("Invalid FLO ID"));
|
||||
@ -13,30 +63,23 @@ function addSellOrder(floID, quantity, min_price) {
|
||||
return reject(INVALID(`Invalid quantity (${quantity})`));
|
||||
else if (typeof min_price !== "number" || min_price <= 0)
|
||||
return reject(INVALID(`Invalid min_price (${min_price})`));
|
||||
else if (!allowedAssets.includes(asset))
|
||||
return reject(INVALID(`Invalid asset (${asset})`));
|
||||
getAssetBalance.check(floID, asset, quantity).then(_ => {
|
||||
checkSellRequirement(floID).then(_ => {
|
||||
DB.query("SELECT SUM(quantity) AS total FROM Vault WHERE floID=?", [floID]).then(result => {
|
||||
let total = result.pop()["total"] || 0;
|
||||
if (total < quantity)
|
||||
return reject(INVALID("Insufficient FLO"));
|
||||
DB.query("SELECT SUM(quantity) AS locked FROM SellOrder WHERE floID=?", [floID]).then(result => {
|
||||
let locked = result.pop()["locked"] || 0;
|
||||
let available = total - locked;
|
||||
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) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const checkSellRequirement = floID => new Promise((resolve, reject) => {
|
||||
DB.query("SELECT * FROM Tags 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 SUM(quantity) AS brought FROM Transactions WHERE buyer=?", [floID]).then(result => {
|
||||
if (result[0].brought >= MINIMUM_BUY_REQUIREMENT)
|
||||
resolve(true);
|
||||
@ -44,10 +87,9 @@ function checkSellRequirement(floID) {
|
||||
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))
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
function addBuyOrder(floID, quantity, max_price) {
|
||||
function addBuyOrder(floID, asset, quantity, max_price) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!floID || !floCrypto.validateAddr(floID))
|
||||
return reject(INVALID("Invalid FLO ID"));
|
||||
@ -55,22 +97,13 @@ function addBuyOrder(floID, quantity, max_price) {
|
||||
return reject(INVALID(`Invalid quantity (${quantity})`));
|
||||
else if (typeof max_price !== "number" || max_price <= 0)
|
||||
return reject(INVALID(`Invalid max_price (${max_price})`));
|
||||
DB.query("SELECT rupeeBalance FROM Cash WHERE floID=?", [floID]).then(result => {
|
||||
if (result.length < 1)
|
||||
return reject(INVALID("FLO ID not registered"));
|
||||
let total = result.pop()["rupeeBalance"];
|
||||
if (total < quantity * max_price)
|
||||
return reject(INVALID("Insufficient Rupee balance"));
|
||||
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)"));
|
||||
else if (!allowedAssets.includes(asset))
|
||||
return reject(INVALID(`Invalid asset (${asset})`));
|
||||
getAssetBalance.check(floID, asset, quantity).then(_ => {
|
||||
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));
|
||||
});
|
||||
}
|
||||
|
||||
@ -101,11 +134,11 @@ function cancelOrder(type, id, floID) {
|
||||
function getAccountDetails(floID) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let select = [];
|
||||
select.push(["rupeeBalance", "Cash"]);
|
||||
select.push(["base, quantity", "Vault"]);
|
||||
select.push(["id, quantity, minPrice, time_placed", "SellOrder"]);
|
||||
select.push(["id, quantity, maxPrice, time_placed", "BuyOrder"]);
|
||||
let promises = select.map(a => DB.query("SELECT " + a[0] + " FROM " + a[1] + " WHERE floID=?", [floID]));
|
||||
select.push(["balance", "Cash"]);
|
||||
select.push(["asset, AVG(base) AS avg_base, SUM(quantity) AS quantity", "Vault", "GROUP BY asset"]);
|
||||
select.push(["id, asset, quantity, minPrice, time_placed", "SellOrder"]);
|
||||
select.push(["id, asset, quantity, maxPrice, time_placed", "BuyOrder"]);
|
||||
let promises = select.map(a => DB.query(`SELECT ${a[0]} FROM ${a[1]} WHERE floID=? ${a[2] || ""}`, [floID]));
|
||||
Promise.allSettled(promises).then(results => {
|
||||
let response = {
|
||||
floID: floID,
|
||||
@ -117,10 +150,10 @@ function getAccountDetails(floID) {
|
||||
else
|
||||
switch (i) {
|
||||
case 0:
|
||||
response.rupee_total = a.value[0].rupeeBalance;
|
||||
response.cash = a.value[0].balance;
|
||||
break;
|
||||
case 1:
|
||||
response.coins = a.value;
|
||||
response.vault = a.value;
|
||||
break;
|
||||
case 2:
|
||||
response.sellOrders = a.value;
|
||||
@ -166,7 +199,7 @@ function confirmDepositFLO() {
|
||||
txQueries.push(["INSERT INTO Vault(floID, quantity) VALUES (?, ?)", [req.floID, amount]]);
|
||||
txQueries.push(["UPDATE inputFLO SET status=?, amount=? WHERE id=?", ["SUCCESS", amount, req.id]]);
|
||||
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);
|
||||
@ -206,29 +239,8 @@ function withdrawFLO(floID, amount) {
|
||||
return reject(INVALID("Invalid FLO ID"));
|
||||
else if (typeof amount !== "number" || amount <= 0)
|
||||
return reject(INVALID(`Invalid amount (${amount})`));
|
||||
DB.query("SELECT SUM(quantity) AS total FROM Vault WHERE floID=?", [floID]).then(result => {
|
||||
let total = result.pop()["total"] || 0;
|
||||
if (total < amount)
|
||||
return reject(INVALID("Insufficient FLO"));
|
||||
DB.query("SELECT SUM(quantity) AS locked FROM SellOrder WHERE floID=?", [floID]).then(result => {
|
||||
let locked = result.pop()["locked"] || 0;
|
||||
let available = total - locked;
|
||||
if (available < amount)
|
||||
return reject(INVALID("Insufficient FLO (Some FLO are locked in sell orders)"));
|
||||
DB.query("SELECT id, quantity, base FROM Vault WHERE floID=? ORDER BY locktime", [floID]).then(coins => {
|
||||
let rem = amount,
|
||||
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 -= coins[i].quantity;
|
||||
}
|
||||
}
|
||||
if (rem > 0) //should not happen AS the total and net is checked already
|
||||
return reject(INVALID("Insufficient FLO"));
|
||||
getAssetBalance.check(floID, "FLO", amount).then(_ => {
|
||||
consumeAsset(floID, "FLO", amount).then(txQueries => {
|
||||
DB.transaction(txQueries).then(result => {
|
||||
//Send FLO to user via blockchain API
|
||||
floBlockchainAPI.sendTx(global.myFloID, floID, amount, global.myPrivKey, 'Withdraw FLO Coins from Market').then(txid => {
|
||||
@ -247,7 +259,6 @@ function withdrawFLO(floID, amount) {
|
||||
}).catch(error => reject(error));
|
||||
}).catch(error => reject(error));
|
||||
}).catch(error => reject(error));
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
@ -266,22 +277,22 @@ function retryWithdrawalFLO() {
|
||||
}
|
||||
|
||||
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 => {
|
||||
floBlockchainAPI.getTx(req.txid).then(tx => {
|
||||
if (!tx.blockheight || !tx.confirmations) //Still not confirmed
|
||||
return;
|
||||
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));
|
||||
}
|
||||
|
||||
function depositRupee(floID, txid) {
|
||||
function depositToken(floID, txid) {
|
||||
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) {
|
||||
switch (result[0].status) {
|
||||
case "PENDING":
|
||||
@ -292,53 +303,58 @@ function depositRupee(floID, txid) {
|
||||
return reject(INVALID("Transaction already used to add tokens"));
|
||||
}
|
||||
} 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"))
|
||||
.catch(error => reject(error));
|
||||
}).catch(error => reject(error))
|
||||
});
|
||||
}
|
||||
|
||||
function confirmDepositRupee() {
|
||||
DB.query("SELECT id, floID, txid FROM inputRupee WHERE status=?", ["PENDING"]).then(results => {
|
||||
function confirmDepositToken() {
|
||||
DB.query("SELECT id, floID, txid FROM inputToken WHERE status=?", ["PENDING"]).then(results => {
|
||||
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 => {
|
||||
let txQueries = [],
|
||||
amount_rupee = amounts[0];
|
||||
token_name = amounts[0],
|
||||
amount_token = amounts[1];
|
||||
//Add the FLO balance if necessary
|
||||
if (!result.length) {
|
||||
let amount_flo = amounts[1];
|
||||
txQueries.push(["INSERT INTO Vault(floID, quantity) VALUES (?, ?)", [req.floID, amount_flo]]);
|
||||
let amount_flo = amounts[2];
|
||||
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(["UPDATE inputRupee SET status=? WHERE id=?", ["SUCCESS", req.id]]);
|
||||
txQueries.push(["UPDATE Cash SET rupeeBalance=rupeeBalance+? WHERE floID=?", [amount_rupee, req.floID]]);
|
||||
txQueries.push(["UPDATE inputToken SET status=?, token=?, amount=? WHERE id=?", ["SUCCESS", token_name, amount_token, req.id]]);
|
||||
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)
|
||||
.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);
|
||||
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));
|
||||
});
|
||||
})
|
||||
}).catch(error => console.error(error))
|
||||
}
|
||||
|
||||
confirmDepositRupee.checkTx = function(sender, txid) {
|
||||
confirmDepositToken.checkTx = function(sender, txid) {
|
||||
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 => {
|
||||
if (tx.parsedFloData.type !== "transfer")
|
||||
return reject([true, "Transaction type not 'transfer'"]);
|
||||
else if (tx.parsedFloData.transferType !== "token")
|
||||
return reject([true, "Transaction transfer is not 'token'"]);
|
||||
else if (tx.parsedFloData.tokenIdentification !== "rupee")
|
||||
return reject([true, "Transaction token is not 'rupee'"]);
|
||||
var amount_rupee = tx.parsedFloData.tokenAmount;
|
||||
var token_name = tx.parsedFloData.tokenIdentification,
|
||||
amount_token = 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)
|
||||
if (!vin_sender.length)
|
||||
return reject([true, "Transaction not sent by the sender"]);
|
||||
@ -346,60 +362,36 @@ confirmDepositRupee.checkTx = function(sender, txid) {
|
||||
if (amount_flo == 0)
|
||||
return reject([true, "Transaction receiver is not market ID"]);
|
||||
else
|
||||
resolve([amount_rupee, amount_flo]);
|
||||
resolve([token_name, amount_token, amount_flo]);
|
||||
}).catch(error => reject([false, error]))
|
||||
})
|
||||
}
|
||||
|
||||
function withdrawRupee(floID, amount) {
|
||||
function withdrawToken(floID, token, amount) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!floID || !floCrypto.validateAddr(floID))
|
||||
return reject(INVALID("Invalid FLO ID"));
|
||||
else if (typeof amount !== "number" || amount <= 0)
|
||||
return reject(INVALID(`Invalid amount (${amount})`));
|
||||
DB.query("SELECT SUM(quantity) AS total FROM Vault WHERE floID=?", [floID]).then(result => {
|
||||
let required_flo = floGlobals.sendAmt + floGlobals.fee,
|
||||
total = result.pop()["total"] || 0;
|
||||
if (total < required_flo)
|
||||
return reject(INVALID(`Insufficient FLO! Required ${required_flo} FLO to withdraw tokens`));
|
||||
DB.query("SELECT SUM(quantity) AS locked FROM SellOrder WHERE floID=?", [floID]).then(result => {
|
||||
let locked = result.pop()["locked"] || 0;
|
||||
let available = total - locked;
|
||||
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]]);
|
||||
|
||||
else if (!allowedAssets.includes(token) || token === "FLO")
|
||||
return reject(INVALID("Invalid Token"));
|
||||
//Check for FLO balance (transaction fee)
|
||||
const required_flo = floGlobals.sendAmt + floGlobals.fee;
|
||||
getAssetBalance.check(floID, "FLO", required_flo).then(_ => {
|
||||
getAssetBalance.check(floID, token, amount).then(_ => {
|
||||
consumeAsset(floID, "FLO", required_flo).then(txQueries => {
|
||||
consumeAsset(floID, token, amount, txQueries).then(txQueries => {
|
||||
DB.transaction(txQueries).then(result => {
|
||||
//Send FLO to user via blockchain API
|
||||
tokenAPI.sendToken(global.myPrivKey, amount, '(withdrawal from market)', floID).then(txid => {
|
||||
if (!txid)
|
||||
throw Error("Transaction not successful");
|
||||
tokenAPI.sendToken(global.myPrivKey, amount, '(withdrawal from market)', floID, token).then(txid => {
|
||||
if (!txid) throw Error("Transaction not successful");
|
||||
//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))
|
||||
.finally(_ => resolve("Withdrawal was successful"));
|
||||
}).catch(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))
|
||||
.finally(_ => resolve("Withdrawal request is in process"));
|
||||
});
|
||||
@ -411,26 +403,26 @@ function withdrawRupee(floID, amount) {
|
||||
});
|
||||
}
|
||||
|
||||
function retryWithdrawalRupee() {
|
||||
DB.query("SELECT id, floID, amount FROM outputRupee WHERE status=?", ["PENDING"]).then(results => {
|
||||
function retryWithdrawalToken() {
|
||||
DB.query("SELECT id, floID, token, amount FROM outputToken WHERE status=?", ["PENDING"]).then(results => {
|
||||
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)
|
||||
throw Error("Transaction not successful");
|
||||
//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));
|
||||
}).catch(error => console.error(error));
|
||||
});
|
||||
}).catch(error => reject(error));
|
||||
}
|
||||
|
||||
function confirmWithdrawalRupee() {
|
||||
DB.query("SELECT id, floID, amount, txid FROM outputRupee WHERE status=?", ["WAITING_CONFIRMATION"]).then(results => {
|
||||
function confirmWithdrawalToken() {
|
||||
DB.query("SELECT id, floID, token, amount, txid FROM outputToken WHERE status=?", ["WAITING_CONFIRMATION"]).then(results => {
|
||||
results.forEach(req => {
|
||||
tokenAPI.getTx(req.txid).then(tx => {
|
||||
DB.query("UPDATE outputRupee SET status=? WHERE id=?", ["SUCCESS", req.id])
|
||||
.then(result => console.debug("Rupee withdrawed for ", req.floID))
|
||||
DB.query("UPDATE outputToken SET status=? WHERE id=?", ["SUCCESS", req.id])
|
||||
.then(result => console.debug("Token withdrawed:", req.floID, req.token, req.amount))
|
||||
.catch(error => console.error(error));
|
||||
}).catch(error => console.error(error));
|
||||
})
|
||||
@ -444,11 +436,11 @@ function periodicProcess() {
|
||||
|
||||
function blockchainReCheck() {
|
||||
confirmDepositFLO();
|
||||
confirmDepositRupee();
|
||||
confirmDepositToken();
|
||||
retryWithdrawalFLO();
|
||||
retryWithdrawalRupee();
|
||||
retryWithdrawalToken();
|
||||
confirmWithdrawalFLO();
|
||||
confirmWithdrawalRupee();
|
||||
confirmWithdrawalToken();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
@ -461,12 +453,15 @@ module.exports = {
|
||||
getAccountDetails,
|
||||
depositFLO,
|
||||
withdrawFLO,
|
||||
depositRupee,
|
||||
withdrawRupee,
|
||||
depositToken,
|
||||
withdrawToken,
|
||||
periodicProcess,
|
||||
group: coupling.group,
|
||||
set DB(db) {
|
||||
DB = db;
|
||||
coupling.DB = db;
|
||||
},
|
||||
set allowedAssets(assets) {
|
||||
allowedAssets = assets;
|
||||
}
|
||||
};
|
||||
@ -159,11 +159,12 @@ function PlaceSellOrder(req, res) {
|
||||
let data = req.body;
|
||||
validateRequestFromFloID({
|
||||
type: "sell_order",
|
||||
asset: data.asset,
|
||||
quantity: data.quantity,
|
||||
min_price: data.min_price,
|
||||
timestamp: data.timestamp
|
||||
}, 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 => {
|
||||
storeRequest(data.floID, req_str, data.sign);
|
||||
res.send('Sell Order placed successfully');
|
||||
@ -189,11 +190,12 @@ function PlaceBuyOrder(req, res) {
|
||||
let data = req.body;
|
||||
validateRequestFromFloID({
|
||||
type: "buy_order",
|
||||
asset: data.asset,
|
||||
quantity: data.quantity,
|
||||
max_price: data.max_price,
|
||||
timestamp: data.timestamp
|
||||
}, 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 => {
|
||||
storeRequest(data.floID, req_str, data.sign);
|
||||
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;
|
||||
validateRequestFromFloID({
|
||||
type: "deposit_Rupee",
|
||||
type: "deposit_Token",
|
||||
txid: data.txid,
|
||||
timestamp: data.timestamp
|
||||
}, 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);
|
||||
res.send(result);
|
||||
}).catch(error => {
|
||||
@ -379,14 +381,15 @@ function DepositRupee(req, res) {
|
||||
});
|
||||
}
|
||||
|
||||
function WithdrawRupee(req, res) {
|
||||
function WithdrawToken(req, res) {
|
||||
let data = req.body;
|
||||
validateRequestFromFloID({
|
||||
type: "withdraw_Rupee",
|
||||
type: "withdraw_Token",
|
||||
token: data.token,
|
||||
amount: data.amount,
|
||||
timestamp: data.timestamp
|
||||
}, 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);
|
||||
res.send(result);
|
||||
}).catch(error => {
|
||||
@ -485,8 +488,8 @@ module.exports = {
|
||||
Account,
|
||||
DepositFLO,
|
||||
WithdrawFLO,
|
||||
DepositRupee,
|
||||
WithdrawRupee,
|
||||
DepositToken,
|
||||
WithdrawToken,
|
||||
periodicProcess: market.periodicProcess,
|
||||
addUserTag,
|
||||
removeUserTag,
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
}).catch(error => reject(error))
|
||||
})
|
||||
},
|
||||
getBalance: function(floID, token = floGlobals.token) {
|
||||
getBalance: function(floID, token = floGlobals.currency) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.fetch_api(`api/v1.0/getFloAddressBalance?token=${token}&floAddress=${floID}`)
|
||||
.then(result => resolve(result.balance || 0))
|
||||
@ -35,7 +35,7 @@
|
||||
}).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) => {
|
||||
let senderID = floCrypto.getFloID(privKey);
|
||||
if (typeof amount !== "number" || amount <= 0)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user