From bdd79a1a76c5f7ac0b31b5f247148c517760f904 Mon Sep 17 00:00:00 2001 From: sairajzero Date: Fri, 30 Sep 2022 05:52:30 +0530 Subject: [PATCH] Adding support for BTC withdrawal --- args/schema.sql | 32 +++--- args/truncateAll.sql | 4 +- docs/scripts/btcOperator.js | 12 +-- src/background.js | 200 ++++++++++++++++++++++++++++++++++++ src/blockchain.js | 68 +++++++----- src/market.js | 196 +++-------------------------------- 6 files changed, 284 insertions(+), 228 deletions(-) create mode 100644 src/background.js diff --git a/args/schema.sql b/args/schema.sql index 8d677c9..1034969 100644 --- a/args/schema.sql +++ b/args/schema.sql @@ -114,19 +114,21 @@ CREATE TABLE BuyOrder ( FOREIGN KEY (asset) REFERENCES AssetList(asset) ); -CREATE TABLE InputFLO ( +CREATE TABLE InputCoin ( id INT NOT NULL AUTO_INCREMENT, txid VARCHAR(128) NOT NULL, floID CHAR(34) NOT NULL, + coin VARCHAR(8) NOT NULL, amount DECIMAL(16, 8), status VARCHAR(50) NOT NULL, PRIMARY KEY(id) ); -CREATE TABLE OutputFLO ( +CREATE TABLE OutputCoin ( id INT NOT NULL AUTO_INCREMENT, txid VARCHAR(128), floID CHAR(34) NOT NULL, + coin VARCHAR(8) NOT NULL, amount DECIMAL(16, 8) NOT NULL, status VARCHAR(50) NOT NULL, PRIMARY KEY(id) @@ -146,7 +148,7 @@ CREATE TABLE OutputToken ( id INT NOT NULL AUTO_INCREMENT, txid VARCHAR(128), floID CHAR(34) NOT NULL, - token VARCHAR(64), + token VARCHAR(64) NOT NULL, amount DECIMAL(16, 8) NOT NULL, status VARCHAR(50) NOT NULL, PRIMARY KEY(id) @@ -277,19 +279,19 @@ FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('BuyOrder', NEW.id) ON DUP CREATE TRIGGER BuyOrder_D AFTER DELETE ON BuyOrder FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('BuyOrder', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT; -CREATE TRIGGER InputFLO_I AFTER INSERT ON InputFLO -FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('InputFLO', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; -CREATE TRIGGER InputFLO_U AFTER UPDATE ON InputFLO -FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('InputFLO', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; -CREATE TRIGGER InputFLO_D AFTER DELETE ON InputFLO -FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('InputFLO', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT; +CREATE TRIGGER InputCoin_I AFTER INSERT ON InputCoin +FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('InputCoin', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; +CREATE TRIGGER InputCoin_U AFTER UPDATE ON InputCoin +FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('InputCoin', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; +CREATE TRIGGER InputCoin_D AFTER DELETE ON InputCoin +FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('InputCoin', OLD.id) ON DUPLICATE KEY UPDATE mode=NULL, timestamp=DEFAULT; -CREATE TRIGGER OutputFLO_I AFTER INSERT ON OutputFLO -FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('OutputFLO', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; -CREATE TRIGGER OutputFLO_U AFTER UPDATE ON OutputFLO -FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('OutputFLO', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; -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 OutputCoin_I AFTER INSERT ON OutputCoin +FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('OutputCoin', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; +CREATE TRIGGER OutputCoin_U AFTER UPDATE ON OutputCoin +FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('OutputCoin', NEW.id) ON DUPLICATE KEY UPDATE mode=TRUE, timestamp=DEFAULT; +CREATE TRIGGER OutputCoin_D AFTER DELETE ON OutputCoin +FOR EACH ROW INSERT INTO _backup (t_name, id) VALUES ('OutputCoin', 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; diff --git a/args/truncateAll.sql b/args/truncateAll.sql index b565e79..f32563d 100644 --- a/args/truncateAll.sql +++ b/args/truncateAll.sql @@ -4,9 +4,9 @@ TRUNCATE _backupCache; TRUNCATE AuditTrade; TRUNCATE BuyOrder; TRUNCATE Distributors; -TRUNCATE InputFLO; +TRUNCATE InputCoin; TRUNCATE InputToken; -TRUNCATE OutputFLO; +TRUNCATE OutputCoin; TRUNCATE OutputToken; TRUNCATE PriceHistory; TRUNCATE RequestLog; diff --git a/docs/scripts/btcOperator.js b/docs/scripts/btcOperator.js index 1d0147a..7406ba2 100644 --- a/docs/scripts/btcOperator.js +++ b/docs/scripts/btcOperator.js @@ -1,4 +1,4 @@ -(function(EXPORTS) { //btcOperator v1.0.10 +(function(EXPORTS) { //btcOperator v1.0.10a /* BTC Crypto and API Operator */ const btcOperator = EXPORTS; @@ -31,16 +31,16 @@ }) } - const broadcast = btcOperator.broadcast = rawtx => new Promise((resolve, reject) => { + const broadcastTx = btcOperator.broadcastTx = rawTxHex => new Promise((resolve, reject) => { $.ajax({ type: "POST", url: URL + "send_tx/BTC/", data: { - "tx_hex": rawtx + "tx_hex": rawTxHex }, dataType: "json", error: e => reject(e.responseJSON), - success: r => r.status === "success" ? resolve(r.data) : reject(r) + success: r => r.status === "success" ? resolve(r.data.txid) : reject(r) }) }); @@ -485,8 +485,8 @@ new Set(wif_keys).forEach(key => console.debug("Signing key:", key, tx.sign(key, 1 /*sighashtype*/ ))); //Sign the tx using private key WIF console.debug("Signed:", tx.serialize()); debugger; - broadcast(tx.serialize()) - .then(result => resolve(result)) + broadcastTx(tx.serialize()) + .then(txid => resolve(txid)) .catch(error => reject(error)); }).catch(error => reject(error)); }) diff --git a/src/background.js b/src/background.js new file mode 100644 index 0000000..421ab41 --- /dev/null +++ b/src/background.js @@ -0,0 +1,200 @@ +'use strict'; +const blockchain = require('./blockchain'); + +const { + LAUNCH_SELLER_TAG, + MAXIMUM_LAUNCH_SELL_CHIPS, +} = require('./_constants')["market"]; + +function checkTag(floID, tag) { + return new Promise((resolve, reject) => { + DB.query("SELECT id FROM UserTag WHERE floID=? AND tag=?", [floID, tag]) + .then(result => resolve(result.length ? true : false)) + .catch(error => reject(error)) + }) +} + +function confirmDepositFLO() { + DB.query("SELECT id, floID, txid FROM InputCoin WHERE coin=? AND status=?", ["FLO", "PENDING"]).then(results => { + results.forEach(req => { + verifyDepositFLO(req.floID, req.txid).then(amount => { + addSellChipsIfLaunchSeller(req.floID, amount).then(txQueries => { + txQueries.push(updateBalance.add(req.floID, "FLO", amount)); + txQueries.push(["UPDATE InputCoin SET status=?, amount=? WHERE id=?", ["SUCCESS", amount, req.id]]); + DB.transaction(txQueries) + .then(result => console.debug("FLO deposited:", req.floID, amount)) + .catch(error => console.error(error)) + }).catch(error => console.error(error)) + }).catch(error => { + console.error(error); + if (error[0]) + DB.query("UPDATE InputCoin SET status=? WHERE id=?", ["REJECTED", req.id]) + .then(_ => null).catch(error => console.error(error)); + }); + }) + }).catch(error => console.error(error)) +} + +function verifyDepositFLO(sender, txid) { + return new Promise((resolve, reject) => { + floBlockchainAPI.getTx(txid).then(tx => { + let vin_sender = tx.vin.filter(v => v.addr === sender) + 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((a, v) => blockchain.chests.includes(v.scriptPubKey.addresses[0]) ? a + v.value : a, 0); + if (amount == 0) + return reject([true, "Transaction receiver is not market ID"]); //Maybe reject as false? (to compensate delay in chestsList loading from other nodes) + else + resolve(amount); + }).catch(error => reject([false, error])) + }) +} + +function addSellChipsIfLaunchSeller(floID, quantity) { + return new Promise((resolve, reject) => { + checkTag(floID, LAUNCH_SELLER_TAG).then(result => { + if (result) //floID is launch-seller + Promise.all([ + DB.query("SELECT IFNULL(SUM(quantity), 0) AS sold FROM TradeTransactions WHERE seller=? AND asset=?", [floID, 'FLO']), + DB.query("SELECT IFNULL(SUM(quantity), 0) AS brought FROM TradeTransactions WHERE buyer=? AND asset=?", [floID, 'FLO']), + DB.query("SELECT IFNULL(SUM(quantity), 0) AS chips FROM SellChips WHERE floID=? AND asset=?", [floID, 'FLO']), + ]).then(result => { + let sold = result[0][0].sold, + brought = result[1][0].brought, + chips = result[2][0].chips; + let remLaunchChips = MAXIMUM_LAUNCH_SELL_CHIPS - (sold + chips) + brought; + quantity = Math.min(quantity, remLaunchChips); + if (quantity > 0) + resolve([ + ["INSERT INTO SellChips(floID, asset, quantity) VALUES (?, ?, ?)", [floID, 'FLO', quantity]] + ]); + else + resolve([]); + }).catch(error => reject(error)) + else //floID is not launch-seller + resolve([]); + }).catch(error => reject(error)) + }) +} + +function confirmDepositToken() { + DB.query("SELECT id, floID, txid FROM InputToken WHERE status=?", ["PENDING"]).then(results => { + results.forEach(req => { + verifyDepositToken(req.floID, req.txid).then(amounts => { + DB.query("SELECT id FROM InputCoin where floID=? AND coin=? AND txid=?", [req.floID, "FLO", req.txid]).then(result => { + let txQueries = [], + token_name = amounts[0], + amount_token = amounts[1]; + //Add the FLO balance if necessary + if (!result.length) { + let amount_flo = amounts[2]; + txQueries.push(updateBalance.add(req.floID, "FLO", amount_flo)); + txQueries.push(["INSERT INTO InputCoin(txid, floID, coin, amount, status) VALUES (?, ?, ?, ?, ?)", [req.txid, req.floID, "FLO", amount_flo, "SUCCESS"]]); + } + txQueries.push(["UPDATE InputToken SET status=?, token=?, amount=? WHERE id=?", ["SUCCESS", token_name, amount_token, req.id]]); + txQueries.push(updateBalance.add(req.floID, token_name, amount_token)); + DB.transaction(txQueries) + .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 InputToken SET status=? WHERE id=?", ["REJECTED", req.id]) + .then(_ => null).catch(error => console.error(error)); + }); + }) + }).catch(error => console.error(error)) +} + +function verifyDepositToken(sender, txid) { + return new Promise((resolve, reject) => { + floTokenAPI.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'"]); + var token_name = tx.parsedFloData.tokenIdentification, + amount_token = tx.parsedFloData.tokenAmount; + if ((!assetList.includes(token_name) && token_name !== floGlobals.currency) || 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"]); + let amount_flo = tx.transactionDetails.vout.reduce((a, v) => blockchain.chests.includes(v.scriptPubKey.addresses[0]) ? a + v.value : a, 0); + if (amount_flo == 0) + return reject([true, "Transaction receiver is not market ID"]); //Maybe reject as false? (to compensate delay in chestsList loading from other nodes) + else + resolve([token_name, amount_token, amount_flo]); + }).catch(error => reject([false, error])) + }) +} + +function retryWithdrawalCoin() { + DB.query("SELECT id, floID, coin, amount FROM OutputCoin WHERE status=?", ["PENDING"]).then(results => { + results.forEach(req => blockchain.sendCoin.retry(req.floID, req.coin, req.amount, req.id)); + }).catch(error => reject(error)); +} + +function retryWithdrawalToken() { + DB.query("SELECT id, floID, token, amount FROM OutputToken WHERE status=?", ["PENDING"]).then(results => { + results.forEach(req => blockchain.sendToken.retry(req.floID, req.token, req.amount, req.id)); + }).catch(error => reject(error)); +} + +function confirmWithdrawalFLO() { + DB.query("SELECT id, floID, amount, txid FROM OutputCoin WHERE coin=? AND status=?", ["FLO", "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 OutputCoin SET status=? WHERE id=?", ["SUCCESS", req.id]) + .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 confirmWithdrawalBTC() { + DB.query("SELECT id, floID, amount, txid FROM OutputCoin WHERE coin=? AND status=?", ["BTC", "WAITING_CONFIRMATION"]).then(results => { + results.forEach(req => { + btcOperator.getTx(req.txid).then(tx => { + if (!tx.blockhash || !tx.confirmations) //Still not confirmed + return; + DB.query("UPDATE OutputCoin SET status=? WHERE id=?", ["SUCCESS", req.id]) + .then(result => console.debug("BTC withdrawed:", req.floID, req.amount)) + .catch(error => console.error(error)) + }).catch(error => console.error(error)); + }) + }).catch(error => console.error(error)); +} + +function confirmWithdrawalToken() { + DB.query("SELECT id, floID, token, amount, txid FROM OutputToken WHERE status=?", ["WAITING_CONFIRMATION"]).then(results => { + results.forEach(req => { + floTokenAPI.getTx(req.txid).then(tx => { + 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)); + }) + }).catch(error => console.error(error)); +} + +module.exports = { + blockchain, + confirmDepositFLO, + confirmDepositToken, + retryWithdrawalCoin, + retryWithdrawalToken, + confirmWithdrawalFLO, + confirmWithdrawalBTC, + confirmWithdrawalToken +} \ No newline at end of file diff --git a/src/blockchain.js b/src/blockchain.js index bf9e400..8146250 100644 --- a/src/blockchain.js +++ b/src/blockchain.js @@ -3,14 +3,28 @@ var collectAndCall; //container for collectAndCall function from backup module var chests; //container for blockchain ids (where assets are stored) +const WITHDRAWAL_MESSAGE = "(withdrawal from market)"; + const balance_locked = {}, balance_cache = {}, callbackCollection = { - FLO: {}, + Coin: {}, token: {} }; -function getSinkID(amount, asset = "FLO", sinkList = null) { +function getBalance(sinkID, asset) { + switch (asset) { + case "FLO": + return floBlockchainAPI.getBalance(sinkID); + case "BTC": + let btc_id = btcOperator.convert.legacy2bech(sinkID); + return btcOperator.getBalance(btc_id); + default: + return floTokenAPI.getBalance(sinkID, asset); + } +} + +function getSinkID(amount, asset, sinkList = null) { return new Promise((resolve, reject) => { if (!sinkList) sinkList = chests.list.map(s => [s, s in balance_cache ? balance_cache[s][asset] || 0 : 0]) //TODO: improve sorting @@ -18,7 +32,7 @@ function getSinkID(amount, asset = "FLO", sinkList = null) { if (!sinkList.length) return reject(`Insufficient balance in chests for asset(${asset})`); let sinkID = sinkList.shift(); - (asset === "FLO" ? floBlockchainAPI.getBalance(sinkID) : floTokenAPI.getBalance(sinkID, asset)).then(balance => { + getBalance(sinkID, asset).then(balance => { if (!(sinkID in balance_cache)) balance_cache[sinkID] = {}; balance_cache[sinkID][asset] = balance; @@ -37,47 +51,55 @@ function getSinkID(amount, asset = "FLO", sinkList = null) { }) } -function sendFLO(floID, amount, id) { - getSinkID(amount).then(sinkID => { +function sendTx(floID, coin, amount, sinkID, sinkKey) { + switch (coin) { + case "FLO": + return floBlockchainAPI.sendTx(sinkID, floID, amount, sinkKey, WITHDRAWAL_MESSAGE); + case "BTC": + } +} + +function sendCoin(floID, coin, amount, id) { + getSinkID(amount, coin).then(sinkID => { let callback = (sinkKey) => { - //Send FLO to user via blockchain API - floBlockchainAPI.sendTx(sinkID, floID, amount, sinkKey, '(withdrawal from market)').then(txid => { + //Send Coin to user via blockchain API + sendTx(floID, coin, amount, sinkID, sinkKey).then(txid => { if (!txid) throw Error("Transaction not successful"); //Transaction was successful, Add in DB - DB.query("UPDATE OutputFLO SET status=?, txid=? WHERE id=?", ["WAITING_CONFIRMATION", txid, id]) + DB.query("UPDATE OutputCoin SET status=?, txid=? WHERE id=?", ["WAITING_CONFIRMATION", txid, id]) .then(_ => null).catch(error => console.error(error)); }).catch(error => console.error(error)).finally(_ => { - delete callbackCollection.FLO[id]; - balance_locked[sinkID].FLO -= amount; + delete callbackCollection.Coin[id]; + balance_locked[sinkID][coin] -= amount; }); } collectAndCall(sinkID, callback); - callbackCollection.FLO[id] = callback; + callbackCollection.Coin[id] = callback; if (!(sinkID in balance_locked)) balance_locked[sinkID] = {}; - balance_locked[sinkID].FLO = (balance_locked[sinkID].FLO || 0) + amount; + balance_locked[sinkID][coin] = (balance_locked[sinkID][coin] || 0) + amount; }).catch(error => console.error(error)) } -function sendFLO_init(floID, amount) { - DB.query("INSERT INTO OutputFLO (floID, amount, status) VALUES (?, ?, ?)", [floID, amount, "PENDING"]) - .then(result => sendFLO(floID, amount, result.insertId)) +function sendCoin_init(floID, coin, amount) { + DB.query("INSERT INTO OutputCoin (floID, coin, amount, status) VALUES (?, ?, ?, ?)", [floID, coin, amount, "PENDING"]) + .then(result => sendCoin(floID, coin, amount, result.insertId)) .catch(error => console.error(error)) } -function sendFLO_retry(floID, amount, id) { - if (id in callbackCollection.FLO) +function sendCoin_retry(floID, coin, amount, id) { + if (id in callbackCollection.Coin) console.debug("A callback is already pending for this FLO transfer"); else - sendFLO(floID, amount, id); + sendCoin(floID, coin, amount, id); } function sendToken(floID, token, amount, id) { getSinkID(amount, token).then(sinkID => { let callback = (sinkKey) => { //Send Token to user via token API - floTokenAPI.sendToken(sinkKey, amount, floID, '(withdrawal from market)', token).then(txid => { + floTokenAPI.sendToken(sinkKey, amount, floID, WITHDRAWAL_MESSAGE, token).then(txid => { if (!txid) throw Error("Transaction not successful"); //Transaction was successful, Add in DB @@ -96,7 +118,7 @@ function sendToken(floID, token, amount, id) { }).catch(error => console.error(error)) } -function sendToken_init() { +function sendToken_init(floID, token, amount) { DB.query("INSERT INTO OutputToken (floID, token, amount, status) VALUES (?, ?, ?, ?)", [floID, token, amount, "PENDING"]) .then(result => sendToken(floID, amount, result.insertId)) .catch(error => console.error(error)) @@ -119,9 +141,9 @@ module.exports = { set chests(c) { chests = c; }, - sendFLO: { - init: sendFLO_init, - retry: sendFLO_retry + sendCoin: { + init: sendCoin_init, + retry: sendCoin_retry }, sendToken: { init: sendToken_init, diff --git a/src/market.js b/src/market.js index d322e66..cc9dfa3 100644 --- a/src/market.js +++ b/src/market.js @@ -1,13 +1,12 @@ 'use strict'; const coupling = require('./coupling'); -const blockchain = require('./blockchain'); +const background = require('./background'); +const blockchain = background.blockchain; const { PERIOD_INTERVAL, WAIT_TIME, - LAUNCH_SELLER_TAG, - MAXIMUM_LAUNCH_SELL_CHIPS, TRADE_HASH_PREFIX, TRANSFER_HASH_PREFIX } = require('./_constants')["market"]; @@ -258,9 +257,9 @@ function getAccountDetails(floID) { function getUserTransacts(floID) { return new Promise((resolve, reject) => { DB.query("(SELECT 'deposit' as type, txid, token, amount, status FROM InputToken WHERE floID=?)" + - "UNION (SELECT 'deposit' as type, txid, 'FLO' as token, amount, status FROM InputFLO WHERE floID=?)" + + "UNION (SELECT 'deposit' as type, txid, coin as token, amount, status FROM InputCoin WHERE floID=?)" + "UNION (SELECT 'withdraw' as type, txid, token, amount, status FROM OutputToken WHERE floID=?)" + - "UNION (SELECT 'withdraw' as type, txid, 'FLO' as token, amount, status FROM OutputFLO WHERE floID=?)", + "UNION (SELECT 'withdraw' as type, txid, coin as token, amount, status FROM OutputCoin WHERE floID=?)", [floID, floID, floID, floID]) .then(result => resolve(result)) .catch(error => reject(error)) @@ -339,7 +338,7 @@ function transferToken(sender, receivers, token) { function depositFLO(floID, txid) { return new Promise((resolve, reject) => { - DB.query("SELECT status FROM InputFLO WHERE txid=? AND floID=?", [txid, floID]).then(result => { + DB.query("SELECT status FROM InputCoin WHERE txid=? AND floID=? AND coin=?", [txid, floID, "FLO"]).then(result => { if (result.length) { switch (result[0].status) { case "PENDING": @@ -350,82 +349,13 @@ function depositFLO(floID, txid) { return reject(INVALID(eCode.DUPLICATE_ENTRY, "Transaction already used to add coins")); } } else - DB.query("INSERT INTO InputFLO(txid, floID, status) VALUES (?, ?, ?)", [txid, floID, "PENDING"]) + DB.query("INSERT INTO InputCoin(txid, floID, coin, status) VALUES (?, ?, ?, ?)", [txid, floID, "FLO", "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 => { - confirmDepositFLO.addSellChipsIfLaunchSeller(req.floID, amount).then(txQueries => { - txQueries.push(updateBalance.add(req.floID, "FLO", amount)); - txQueries.push(["UPDATE InputFLO SET status=?, amount=? WHERE id=?", ["SUCCESS", amount, req.id]]); - DB.transaction(txQueries) - .then(result => console.debug("FLO deposited:", req.floID, amount)) - .catch(error => console.error(error)) - }).catch(error => console.error(error)) - }).catch(error => { - console.error(error); - 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) => { - floBlockchainAPI.getTx(txid).then(tx => { - let vin_sender = tx.vin.filter(v => v.addr === sender) - 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((a, v) => blockchain.chests.includes(v.scriptPubKey.addresses[0]) ? a + v.value : a, 0); - if (amount == 0) - return reject([true, "Transaction receiver is not market ID"]); //Maybe reject as false? (to compensate delay in chestsList loading from other nodes) - else - resolve(amount); - }).catch(error => reject([false, error])) - }) -} - -confirmDepositFLO.addSellChipsIfLaunchSeller = function(floID, quantity) { - return new Promise((resolve, reject) => { - checkTag(floID, LAUNCH_SELLER_TAG).then(result => { - if (result) //floID is launch-seller - Promise.all([ - DB.query("SELECT IFNULL(SUM(quantity), 0) AS sold FROM TradeTransactions WHERE seller=? AND asset=?", [floID, 'FLO']), - DB.query("SELECT IFNULL(SUM(quantity), 0) AS brought FROM TradeTransactions WHERE buyer=? AND asset=?", [floID, 'FLO']), - DB.query("SELECT IFNULL(SUM(quantity), 0) AS chips FROM SellChips WHERE floID=? AND asset=?", [floID, 'FLO']), - ]).then(result => { - let sold = result[0][0].sold, - brought = result[1][0].brought, - chips = result[2][0].chips; - let remLaunchChips = MAXIMUM_LAUNCH_SELL_CHIPS - (sold + chips) + brought; - quantity = Math.min(quantity, remLaunchChips); - if (quantity > 0) - resolve([ - ["INSERT INTO SellChips(floID, asset, quantity) VALUES (?, ?, ?)", [floID, 'FLO', quantity]] - ]); - else - resolve([]); - }).catch(error => reject(error)) - else //floID is not launch-seller - resolve([]); - }).catch(error => reject(error)) - }) -} - function withdrawFLO(floID, amount) { return new Promise((resolve, reject) => { if (!floCrypto.validateAddr(floID)) @@ -436,33 +366,13 @@ function withdrawFLO(floID, amount) { let txQueries = []; txQueries.push(updateBalance.consume(floID, "FLO", amount)); DB.transaction(txQueries).then(result => { - blockchain.sendFLO.init(floID, amount); + blockchain.sendCoin.init(floID, "FLO", amount); resolve("Withdrawal request is in process"); }).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 => blockchain.sendFLO.retry(req.floID, req.amount, req.id)) - }).catch(error => reject(error)); -} - -function confirmWithdrawalFLO() { - 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:", req.floID, req.amount)) - .catch(error => console.error(error)) - }).catch(error => console.error(error)); - }) - }).catch(error => console.error(error)); -} - function depositToken(floID, txid) { return new Promise((resolve, reject) => { DB.query("SELECT status FROM InputToken WHERE txid=? AND floID=?", [txid, floID]).then(result => { @@ -483,59 +393,6 @@ function depositToken(floID, txid) { }); } -function confirmDepositToken() { - DB.query("SELECT id, floID, txid FROM InputToken WHERE status=?", ["PENDING"]).then(results => { - results.forEach(req => { - 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 = [], - token_name = amounts[0], - amount_token = amounts[1]; - //Add the FLO balance if necessary - if (!result.length) { - let amount_flo = amounts[2]; - txQueries.push(updateBalance.add(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 InputToken SET status=?, token=?, amount=? WHERE id=?", ["SUCCESS", token_name, amount_token, req.id]]); - txQueries.push(updateBalance.add(req.floID, token_name, amount_token)); - DB.transaction(txQueries) - .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 InputToken SET status=? WHERE id=?", ["REJECTED", req.id]) - .then(_ => null).catch(error => console.error(error)); - }); - }) - }).catch(error => console.error(error)) -} - -confirmDepositToken.checkTx = function(sender, txid) { - return new Promise((resolve, reject) => { - floTokenAPI.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'"]); - var token_name = tx.parsedFloData.tokenIdentification, - amount_token = tx.parsedFloData.tokenAmount; - if ((!assetList.includes(token_name) && token_name !== floGlobals.currency) || 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"]); - let amount_flo = tx.transactionDetails.vout.reduce((a, v) => blockchain.chests.includes(v.scriptPubKey.addresses[0]) ? a + v.value : a, 0); - if (amount_flo == 0) - return reject([true, "Transaction receiver is not market ID"]); //Maybe reject as false? (to compensate delay in chestsList loading from other nodes) - else - resolve([token_name, amount_token, amount_flo]); - }).catch(error => reject([false, error])) - }) -} - function withdrawToken(floID, token, amount) { return new Promise((resolve, reject) => { if (!floCrypto.validateAddr(floID)) @@ -561,24 +418,6 @@ function withdrawToken(floID, token, amount) { }); } -function retryWithdrawalToken() { - DB.query("SELECT id, floID, token, amount FROM OutputToken WHERE status=?", ["PENDING"]).then(results => { - results.forEach(req => blockchain.sendToken.retry(req.floID, req.token, req.amount, req.id)); - }).catch(error => reject(error)); -} - -function confirmWithdrawalToken() { - DB.query("SELECT id, floID, token, amount, txid FROM OutputToken WHERE status=?", ["WAITING_CONFIRMATION"]).then(results => { - results.forEach(req => { - floTokenAPI.getTx(req.txid).then(tx => { - 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)); - }) - }).catch(error => console.error(error)); -} - function addTag(floID, tag) { return new Promise((resolve, reject) => { DB.query("INSERT INTO UserTag (floID, tag) VALUE (?,?)", [floID, tag]) @@ -602,14 +441,6 @@ function removeTag(floID, tag) { }) } -function checkTag(floID, tag) { - return new Promise((resolve, reject) => { - DB.query("SELECT id FROM UserTag WHERE floID=? AND tag=?", [floID, tag]) - .then(result => resolve(result.length ? true : false)) - .catch(error => reject(error)) - }) -} - function addDistributor(floID, asset) { return new Promise((resolve, reject) => { DB.query("INSERT INTO Distributors (floID, asset) VALUE (?,?)", [floID, asset]) @@ -675,12 +506,13 @@ function blockchainReCheck() { floBlockchainAPI.promisedAPI('api/blocks?limit=1').then(result => { if (lastSyncBlockHeight < result.blocks[0].height) { lastSyncBlockHeight = result.blocks[0].height; - confirmDepositFLO(); - confirmDepositToken(); - retryWithdrawalFLO(); - retryWithdrawalToken(); - confirmWithdrawalFLO(); - confirmWithdrawalToken(); + background.confirmDepositFLO(); + background.confirmDepositToken(); + background.retryWithdrawalCoin(); + background.retryWithdrawalToken(); + background.confirmWithdrawalFLO(); + background.confirmWithdrawalBTC(); + background.confirmWithdrawalToken(); console.debug("Last Block :", lastSyncBlockHeight); } }).catch(error => console.error(error));