exchangemarket/src/market.js
sairajzero 20fd7aaeb1 Deposit and withdraw request processing
- Processing of Deposit/Withdraw request for FLO/Rupee.
- renamed database's TxQuery to transaction
2021-09-16 03:17:33 +05:30

686 lines
34 KiB
JavaScript

const floGlobals = require("./floGlobals");
var net_FLO_price; //container for FLO price (from API or by model)
var DB; //container for database
const REFRESH_INTERVAL = 60 * 1000; //1 min
const tokenAPI = {
fetch_api: function(apicall) {
return new Promise((resolve, reject) => {
console.log(floGlobals.tokenURL + apicall);
fetch(floGlobals.tokenURL + apicall).then(response => {
if (response.ok)
response.json().then(data => resolve(data));
else
reject(response)
}).catch(error => reject(error))
})
},
getBalance: function(floID, token = 'rupee') {
return new Promise((resolve, reject) => {
this.fetch_api(`api/v1.0/getFloAddressBalance?token=${token}&floAddress=${floID}`)
.then(result => resolve(result.balance || 0))
.catch(error => reject(error))
})
},
getTx: function(txID) {
return new Promise((resolve, reject) => {
this.fetch_api(`api/v1.0/getTransactionDetails/${txID}`).then(res => {
if (res.result === "error")
reject(res.description);
else if (!res.parsedFloData)
reject("Data piece (parsedFloData) missing");
else if (!res.transactionDetails)
reject("Data piece (transactionDetails) missing");
else
resolve(res);
}).catch(error => reject(error))
})
},
sendToken: function(privKey, amount, message = "", receiverID = floGlobals.adminID, token = 'rupee') {
return new Promise((resolve, reject) => {
let senderID = floCrypto.getFloID(privKey);
if (typeof amount !== "number" || amount <= 0)
return reject("Invalid amount");
this.getBalance(senderID, token).then(bal => {
if (amount > bal)
return reject("Insufficiant token balance");
floBlockchainAPI.writeData(senderID, `send ${amount} ${token}# ${message}`, privKey, receiverID)
.then(txid => resolve(txid))
.catch(error => reject(error))
}).catch(error => reject(error))
});
}
}
function getRates() {
return new Promise((resolve, reject) => {
getRates.FLO_USD().then(FLO_rate => {
getRates.USD_INR().then(INR_rate => {
net_FLO_price = FLO_rate * INR_rate;
console.debug('Rates:', FLO_rate, INR_rate, net_FLO_price);
resolve(net_FLO_price);
}).catch(error => reject(error))
}).catch(error => reject(error))
});
}
getRates.FLO_USD = function() {
return new Promise((resolve, reject) => {
fetch('https://api.coinlore.net/api/ticker/?id=67').then(response => {
if (response.ok) {
response.json()
.then(result => resolve(result[0].price_usd))
.catch(error => reject(error));
} else
reject(response.status);
}).catch(error => reject(error));
});
}
getRates.USD_INR = function() {
return new Promise((resolve, reject) => {
fetch('https://api.exchangerate-api.com/v4/latest/usd').then(response => {
if (response.ok) {
response.json()
.then(result => resolve(result.rates['INR']))
.catch(error => reject(error));
} else
reject(response.status);
}).catch(error => reject(error));
});
}
function addSellOrder(floID, quantity, min_price) {
return new Promise((resolve, reject) => {
if (!floID || !floCrypto.validateAddr(floID))
return reject(INVALID("Invalid FLO ID"));
else if (typeof quantity !== "number" || quantity <= 0)
return reject(INVALID(`Invalid quantity (${quantity})`));
else if (typeof min_price !== "number" || min_price <= 0)
return reject(INVALID(`Invalid min_price (${min_price})`));
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;
console.debug(total, locked, available);
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));
});
}
function addBuyOrder(floID, quantity, max_price) {
return new Promise((resolve, reject) => {
if (!floID || !floCrypto.validateAddr(floID))
return reject(INVALID("Invalid FLO ID"));
else if (typeof quantity !== "number" || quantity <= 0)
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 Users 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;
console.debug(total, locked, available);
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));
});
}
function cancelOrder(type, id, floID) {
return new Promise((resolve, reject) => {
if (!floID || !floCrypto.validateAddr(floID))
return reject(INVALID("Invalid FLO ID"));
let tableName;
if (type === "buy")
tableName = "BuyOrder";
else if (type === "sell")
tableName = "SellOrder";
else
return reject(INVALID("Invalid Order type! Order type must be buy (or) sell"));
DB.query(`SELECT floID FROM ${tableName} WHERE id=?`, [id]).then(result => {
if (result.length < 1)
return reject(INVALID("Order not found!"));
else if (result[0].floID !== floID)
return reject(INVALID("Order doesnt belong to the current user"));
//Delete the order
DB.query(`DELETE FROM ${tableName} WHERE id=?`, [id])
.then(result => resolve(tableName + "#" + id + " cancelled successfully"))
.catch(error => reject(error));
}).catch(error => reject(error));
});
}
function matchBuyAndSell() {
let cur_price = net_FLO_price;
//get the best buyer
getBestBuyer(cur_price).then(buyer_best => {
//get the best seller
getBestSeller(buyer_best.quantity, cur_price).then(result => {
let seller_best = result.sellOrder,
txQueries = result.txQueries;
console.debug("Sell:", seller_best.id, "Buy:", buyer_best.id);
//process the Txn
var tx_quantity;
if (seller_best.quantity > buyer_best.quantity)
tx_quantity = processBuyOrder(seller_best, buyer_best, txQueries);
else if (seller_best.quantity < buyer_best.quantity)
tx_quantity = processSellOrder(seller_best, buyer_best, txQueries);
else
tx_quantity = processBuyAndSellOrder(seller_best, buyer_best, txQueries);
updateBalance(seller_best, buyer_best, txQueries, cur_price, tx_quantity);
//process txn query in SQL
DB.transaction(txQueries).then(results => {
console.log(`Transaction was successful! BuyOrder:${buyer_best.id}| SellOrder:${seller_best.id}`);
//Since a tx was successful, match again
matchBuyAndSell();
}).catch(error => console.error(error));
}).catch(error => console.error(error));
}).catch(error => console.error(error));
}
function getBestBuyer(cur_price, n = 0) {
return new Promise((resolve, reject) => {
DB.query("SELECT * FROM BuyOrder WHERE maxPrice >= ? ORDER BY time_placed LIMIT ?,1", [cur_price, n]).then(result => {
let buyOrder = result.shift();
if (!buyOrder)
return reject("No valid buyers available");
DB.query("SELECT rupeeBalance as bal FROM Users WHERE floID=?", [buyOrder.floID]).then(result => {
if (result[0].bal < cur_price * buyOrder.quantity) {
//This should not happen unless a buy order is placed when user doesnt have enough rupee balance
console.warn(`Buy order ${buyOrder.id} is active, but rupee# is insufficient`);
getBestBuyer(cur_price, n + 1)
.then(result => resolve(result))
.catch(error => reject(error));
} else
resolve(buyOrder);
}).catch(error => reject(error));
}).catch(error => reject(error));
});
}
function getBestSeller(maxQuantity, cur_price, n = 0) {
return new Promise((resolve, reject) => {
//TODO: Add order conditions for priority.
DB.query("SELECT * FROM SellOrder WHERE minPrice <=? ORDER BY time_placed LIMIT ?,1", [cur_price, n]).then(result => {
let sellOrder = result.shift();
if (!sellOrder)
return reject("No valid sellers available");
DB.query("SELECT id, quantity, base FROM Vault WHERE floID=? ORDER BY base", [sellOrder.floID]).then(result => {
let rem = Math.min(sellOrder.quantity, maxQuantity),
sell_base = 0,
base_quantity = 0,
txQueries = [];
for (let i = 0; i < result.length && rem > 0; i++) {
if (rem < result[i].quantity) {
txQueries.push(["UPDATE Vault SET quantity=quantity-? WHERE id=?", [rem, result[i].id]]);
if (result[i].base) {
sell_base += (rem * result[i].base);
base_quantity += rem
}
rem = 0;
} else {
txQueries.push(["DELETE FROM Vault WHERE id=?", [result[i].id]]);
if (result[i].base) {
sell_base += (result[i].quantity * result[i].base);
base_quantity += result[i].quantity
}
rem -= result[i].quantity;
}
}
if (base_quantity)
sell_base = sell_base / base_quantity;
if (rem > 0 || sell_base > cur_price) {
//1st condition (rem>0) should not happen (sell order placement was success when insufficient FLO).
if (rem > 0)
console.warn(`Sell order ${sellOrder.id} is active, but FLO is insufficient`);
getBestSeller(maxQuantity, cur_price, n + 1)
.then(result => resolve(result))
.catch(error => reject(error));
} else
resolve({
sellOrder,
txQueries
});
}).catch(error => reject(error));
}).catch(error => reject(error));
});
}
function processBuyOrder(seller_best, buyer_best, txQueries) {
let quantity = buyer_best.quantity;
//Buy order is completed, sell order is partially done.
txQueries.push(["DELETE FROM BuyOrder WHERE id=?", [buyer_best.id]]);
txQueries.push(["UPDATE SellOrder SET quantity=quantity-? WHERE id=?", [quantity, seller_best.id]]);
return quantity;
}
function processSellOrder(seller_best, buyer_best, txQueries) {
let quantity = seller_best.quantity;
//Sell order is completed, buy order is partially done.
txQueries.push(["DELETE FROM SellOrder WHERE id=?", [seller_best.id]]);
txQueries.push(["UPDATE BuyOrder SET quantity=quantity-? WHERE id=?", [quantity, buyer_best.id]]);
return quantity;
}
function processBuyAndSellOrder(seller_best, buyer_best, txQueries) {
//Both sell order and buy order is completed
txQueries.push(["DELETE FROM SellOrder WHERE id=?", [seller_best.id]]);
txQueries.push(["DELETE FROM BuyOrder WHERE id=?", [buyer_best.id]]);
return seller_best.quantity;
}
function updateBalance(seller_best, buyer_best, txQueries, cur_price, quantity) {
//Update rupee balance for seller and buyer
let totalAmount = cur_price * quantity;
txQueries.push(["UPDATE Users SET rupeeBalance=rupeeBalance+? WHERE floID=?", [totalAmount, seller_best.floID]]);
txQueries.push(["UPDATE Users SET rupeeBalance=rupeeBalance-? WHERE floID=?", [totalAmount, buyer_best.floID]]);
//Add coins to Buyer
txQueries.push(["INSERT INTO Vault(floID, base, quantity) VALUES (?, ?, ?)", [buyer_best.floID, cur_price, quantity]])
//Record transaction
txQueries.push(["INSERT INTO Transactions (seller, buyer, quantity, unitValue) VALUES (?, ?, ?, ?)", [seller_best.floID, buyer_best.floID, quantity, cur_price]]);
return;
}
function getAccountDetails(floID) {
return new Promise((resolve, reject) => {
let select = [];
select.push(["rupeeBalance", "Users"]);
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]));
Promise.allSettled(promises).then(results => {
let response = {
floID: floID,
time: Date.now()
};
results.forEach((a, i) => {
if (a.status === "rejected")
console.error(a.reason);
else
switch (i) {
case 0:
response.rupee_total = a.value[0].rupeeBalance;
break;
case 1:
response.coins = a.value;
break;
case 2:
response.sellOrders = a.value;
break;
case 3:
response.buyOrders = a.value;
break;
}
});
DB.query("SELECT * FROM Transactions WHERE seller=? OR buyer=?", [floID, floID])
.then(result => response.transactions = result)
.catch(error => console.error(error))
.finally(_ => resolve(response));
});
});
}
function depositCoins(floID, txid) {
return new Promise((resolve, reject) => {
DB.query("SELECT status FROM inputFLO WHERE txid=? AND floID=?", [txid, floID]).then(result => {
if (result.length) {
switch (result[0].status) {
case "PENDING":
return reject(INVALID("Transaction already in process"));
case "REJECTED":
return reject(INVALID("Transaction already rejected"));
case "SUCCESS":
return reject(INVALID("Transaction already used to add coins"));
}
} else
DB.query("INSERT INTO inputFLO(txid, floID, status) VALUES (?, ?, ?)", [txid, floID, "PENDING"])
.then(result => resolve("Deposit request in process"))
.catch(error => reject(error));
}).catch(error => reject(error))
});
}
function confirmDepositFLO() {
DB.query("SELECT id, floID, txid FROM inputFLO WHERE status=?", ["PENDING"]).then(results => {
results.forEach(req => {
confirmDepositFLO.checkTx(req.floID, req.txid).then(amount => {
let txQueries = [];
txQueries.push("INSERT INTO Vault(floID, quantity) VALUES (?, ?)", [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 ", floID))
.catch(error => console.error(error))
}).catch(error => {
if (!Array.isArray(error))
console.error(error);
else if (error[0])
DB.query("UPDATE inputFLO SET status=? WHERE id=?", ["REJECTED", req.id])
.then(_ => null).catch(error => console.error(error));
});
})
}).catch(error => console.error(error))
}
confirmDepositFLO.checkTx = function(sender, txid) {
return new Promise((resolve, reject) => {
const receiver = globals.myFloID; //receiver should be market's floID (ie, adminID)
floBlockchainAPI.getTx(txid).then(tx => {
let vin_sender = tx.vin.filter(v => v.addr === sender).length
if (!vin_sender.length)
return reject([true, "Transaction not sent by the sender"]);
if (vin_sender.length !== tx.vin.length)
return reject([true, "Transaction input containes other floIDs"]);
if (!tx.blockheight)
return reject([false, "Transaction not included in any block yet"]);
if (!tx.confirmations)
return reject([false, "Transaction not confirmed yet"]);
let amount = tx.vout.reduce(v => v.scriptPubKey.addresses[0] === receiver ? a + v.value : a, 0);
if (amount == 0)
return reject([true, "Transaction receiver is not market ID"]);
else
resolve(amount);
}).catch(error => reject([false, error]))
})
}
function withdrawCoins(floID, 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 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 -= result[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(result => {
let txid = result.txid.result;
if (!txid || result.txid.error)
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 => {
DB.query("INSERT INTO outputFLO (floID, amount, txid, status) VALUES (?, ?, ?, ?)", [floID, amount, txid, "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));
});
}
function retryWithdrawalFLO() {
DB.query("SELECT id, floID, amount FROM outputFLO WHERE status=?", ["PENDING"]).then(results => {
results.forEach(req => {
floBlockchainAPI.sendTx(global.myFloID, req.floID, req.amount, global.myPrivKey, 'Withdraw FLO Coins from Market').then(result => {
let txid = result.txid.result;
if (!txid || result.txid.error) {
console.error(result);
return;
}
//Transaction was successful, Add in DB
DB.query("UPDATE outputFLO SET status=? WHERE id=?", ["WAITING_CONFIRMATION", req.id])
.then(_ => null).catch(error => console.error(error));
}).catch(error => console.error(error));
})
}).catch(error => reject(error));
}
function confirmWithdrawalFLO() {
DB.query("SELECT id, floID, 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))
.catch(error => console.error(error))
}).catch(error => console.error(error));
})
}).catch(error => console.error(error));
}
function depositTokens(floID, txid) {
return new Promise((resolve, reject) => {
DB.query("SELECT status FROM inputRupee WHERE txid=? AND floID=?", [txid, floID]).then(result => {
if (result.length) {
switch (result[0].status) {
case "PENDING":
return reject(INVALID("Transaction already in process"));
case "REJECTED":
return reject(INVALID("Transaction already rejected"));
case "SUCCESS":
return reject(INVALID("Transaction already used to add tokens"));
}
} else
DB.query("INSERT INTO inputRupee(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 => {
results.forEach(req => {
confirmDepositRupee.checkTx(req.floID, req.txid).then(amounts => {
DB.query("SELECT id FROM inputFLO where floID=? AND txid=?").then(result => {
let txQueries = [],
amount_rupee = amounts[0];
//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]);
txQueries.push("INSERT INTO inputFLO(txid, floID, status) VALUES (?, ?, ?)", [req.txid, req.floID, "SUCCESS"]);
}
txQueries.push("UPDATE inputRupee SET status=? WHERE id=?", ["SUCCESS", req.id]);
txQueries.push("UPDATE Users SET rupeeBalance=rupeeBalance+? WHERE floID=?", [amount_rupee, req.floID]);
DB.transaction(txQueries)
.then(result => console.debug("Rupee deposited for ", req.floID))
.catch(error => console.error(error));
}).catch(error => reject(error));
}).catch(error => {
if (!Array.isArray(error))
console.error(error);
else if (error[0])
DB.query("UPDATE inputRupee SET status=? WHERE id=?", ["REJECTED", req.id])
.then(_ => null).catch(error => console.error(error));
});
})
}).catch(error => console.error(error))
}
confirmDepositRupee.checkTx = function(sender, txid) {
return new Promise((resolve, reject) => {
const receiver = globals.myFloID; //receiver should be market's floID (ie, adminID)
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;
let vin_sender = tx.transactionDetails.vin.filter(v => v.addr === sender).length
if (!vin_sender.length)
return reject([true, "Transaction not sent by the sender"]);
let amount_flo = tx.transactionDetails.vout.reduce(v => v.scriptPubKey.addresses[0] === receiver ? a + v.value : a, 0);
if (amount_flo == 0)
return reject([true, "Transaction receiver is not market ID"]);
else
resolve([amount_rupee, amount_flo]);
}).catch(error => reject([false, error]))
})
}
function withdrawTokens(floID, 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 < amount)
return reject(INVALID(`Insufficient FLO (Some FLO are locked in sell orders)! Required ${required_flo} FLO to withdraw tokens`));
DB.query("SELECT rupeeBalance FROM Users 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 Users SET rupeeBalance=rupeeBalance-? WHERE floID=?", [amount, floID]]);
DB.transaction(txQueries).then(result => {
//Send FLO to user via blockchain API
tokenAPI.sendToken(global.myPrivKey, amount, '(withdrawal from market)', floID).then(result => {
let txid = result.txid.result;
if (!txid || result.txid.error)
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"])
.then(_ => null).catch(error => console.error(error))
.finally(_ => resolve("Withdrawal was successful"));
}).catch(error => {
DB.query("INSERT INTO outputRupee (floID, amount, txid, status) VALUES (?, ?, ?, ?)", [floID, amount, txid, "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));
});
}
function retryWithdrawalRupee() {
DB.query("SELECT id, floID, amount FROM outputRupee WHERE status=?", ["PENDING"]).then(results => {
results.forEach(req => {
tokenAPI.sendToken(global.myPrivKey, req.amount, '(withdrawal from market)', req.floID).then(result => {
let txid = result.txid.result;
if (!txid || result.txid.error) {
console.error(result);
return;
}
//Transaction was successful, Add in DB
DB.query("UPDATE outputRupee SET status=? WHERE id=?", ["WAITING_CONFIRMATION", 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 => {
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))
.catch(error => console.error(error));
}).catch(error => console.error(error));
})
}).catch(error => console.error(error));
}
function intervalFunction() {
let old_rate = net_FLO_price;
getRates().then(cur_rate => {
transactionReCheck();
matchBuyAndSell();
}).catch(error => console.error(error));
}
function transactionReCheck() {
confirmDepositFLO();
confirmDepositRupee();
retryWithdrawalFLO();
retryWithdrawalRupee();
confirmWithdrawalFLO();
confirmWithdrawalRupee();
}
intervalFunction();
let refresher = setInterval(intervalFunction, REFRESH_INTERVAL);
module.exports = {
addBuyOrder,
addSellOrder,
cancelOrder,
getAccountDetails,
depositCoins,
withdrawCoins,
depositTokens,
withdrawTokens,
set DB(db) {
DB = db;
}
};