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 (
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;

View File

@ -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;

View File

@ -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;

View File

@ -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)

View File

@ -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;
}
};

View File

@ -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,

View File

@ -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)