diff --git a/args/schema.sql b/args/schema.sql index 54edc52..00194e7 100644 --- a/args/schema.sql +++ b/args/schema.sql @@ -300,6 +300,17 @@ CREATE TABLE DirectConvert( PRIMARY KEY(id) ); +CREATE TABLE RefundTransact( + id INT NOT NULL AUTO_INCREMENT, + floID CHAR(34) NOT NULL, + amount DECIMAL(16, 8), + in_txid VARCHAR(128), + out_txid VARCHAR(128), + locktime TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + status VARCHAR(50) NOT NULL, + PRIMARY KEY(id) +) + /* Backup Feature (Tables & Triggers) */ CREATE TABLE _backup ( diff --git a/docs/scripts/floExchangeAPI.js b/docs/scripts/floExchangeAPI.js index 984d1b0..015fdc6 100644 --- a/docs/scripts/floExchangeAPI.js +++ b/docs/scripts/floExchangeAPI.js @@ -514,6 +514,7 @@ INSUFFICIENT_SELLCHIP: '203', GREATER_SELLCHIP_BASE: '204', INSUFFICIENT_PERIOD: '206', + INSUFFICIENT_FUND: '207', //OTHERS NODES_OFFLINE: '404', @@ -1243,6 +1244,7 @@ floID: floID, txid: txid, coin: "BTC", + amount: amount, timestamp: Date.now() }; if (!proxySecret) //Direct signing (without proxy) @@ -1250,6 +1252,7 @@ request.sign = signRequest({ type: "convert_to", coin: request.coin, + amount: amount, txid: txid, timestamp: request.timestamp }, proxySecret || privKey); @@ -1276,11 +1279,13 @@ return reject(ExchangeError(ExchangeError.BAD_REQUEST_CODE, "Invalid Private Key", errorCode.INVALID_PRIVATE_KEY)); let btc_id = btcOperator.convert.legacy2bech(floID), btc_sink = btcOperator.convert.legacy2bech(sinkID); - btcOperator.sendTx(btc_id, privKey, btc_sink, quantity, null).then(txid => { + btcOperator.createSignedTx(btc_id, privKey, btc_sink, quantity, null).then(result => { let request = { floID: floID, - txid: txid, + txid: btcOperator.transactionID(result.transaction), + tx_hex: result.transaction.serialize(), coin: "BTC", + quantity: quantity, timestamp: Date.now() }; if (!proxySecret) //Direct signing (without proxy) @@ -1288,7 +1293,8 @@ request.sign = signRequest({ type: "convert_from", coin: request.coin, - txid: txid, + quantity: quantity, + txid: data.txid, timestamp: request.timestamp }, proxySecret || privKey); console.debug(request); @@ -1308,6 +1314,80 @@ }) } + exchangeAPI.addConvertFundCurrency = function (amount, floID, sinkID, privKey) { + return new Promise((resolve, reject) => { + if (!floCrypto.verifyPrivKey(privKey, floID)) + return reject(ExchangeError(ExchangeError.BAD_REQUEST_CODE, "Invalid Private Key", errorCode.INVALID_PRIVATE_KEY)); + if (floID !== floGlobals.adminID) + return reject(ExchangeError(ExchangeError.BAD_REQUEST_CODE, "Access Denied", errorCode.ACCESS_DENIED)); + floTokenAPI.sendToken(privKey, amount, sinkID, '(add convert fund)', floGlobals.currency).then(txid => { + let request = { + floID: floID, + txid: txid, + coin: "BTC", + timestamp: Date.now() + }; + request.sign = signRequest({ + type: "add_convert_currency_fund", + coin: request.coin, + txid: txid, + timestamp: request.timestamp + }, privKey); + console.debug(request); + + fetch_api('/add-convert-currency-fund', { + method: "POST", + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(request) + }).then(result => { + responseParse(result, false) + .then(result => resolve(result)) + .catch(error => reject(error)) + }).catch(error => reject(error)) + }).catch(error => reject(error)) + }) + } + + exchangeAPI.addConvertFundBTC = function (quantity, floID, sinkID, privKey) { + return new Promise((resolve, reject) => { + if (!floCrypto.verifyPrivKey(privKey, floID)) + return reject(ExchangeError(ExchangeError.BAD_REQUEST_CODE, "Invalid Private Key", errorCode.INVALID_PRIVATE_KEY)); + if (floID !== floGlobals.adminID) + return reject(ExchangeError(ExchangeError.BAD_REQUEST_CODE, "Access Denied", errorCode.ACCESS_DENIED)); + let btc_id = btcOperator.convert.legacy2bech(floID), + btc_sink = btcOperator.convert.legacy2bech(sinkID); + btcOperator.sendTx(btc_id, privKey, btc_sink, quantity, null).then(txid => { + let request = { + floID: floID, + txid: txid, + coin: "BTC", + timestamp: Date.now() + }; + request.sign = signRequest({ + type: "add_convert_coin_fund", + coin: request.coin, + txid: data.txid, + timestamp: request.timestamp + }, proxySecret || privKey); + console.debug(request); + + fetch_api('/add-convert-coin-fund', { + method: "POST", + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(request) + }).then(result => { + responseParse(result, false) + .then(result => resolve(result)) + .catch(error => reject(error)) + }).catch(error => reject(error)) + }).catch(error => reject(error)) + }) + } + exchangeAPI.closeBlockchainBond = function (bond_id, floID, privKey) { return new Promise((resolve, reject) => { if (!floCrypto.verifyPrivKey(privKey, floID)) diff --git a/src/_constants.js b/src/_constants.js index e6650ad..34e0246 100644 --- a/src/_constants.js +++ b/src/_constants.js @@ -24,6 +24,9 @@ module.exports = { TOP_RANGE: 10 / 100, //top 10% REC_HISTORY_INTERVAL: 1 * 60 * 60 * 1000, //1 hr }, + convert: { + MIN_FUND: 0.3 // 30% + }, backup: { SHARE_THRESHOLD: 50 / 100, //50% HASH_N_ROW: 100, diff --git a/src/app.js b/src/app.js index b3dfa1d..ccbc8a4 100644 --- a/src/app.js +++ b/src/app.js @@ -88,6 +88,8 @@ module.exports = function App(secret, DB) { //convert from or to coin app.post('/convert-to', Request.ConvertTo); app.post('/convert-from', Request.ConvertFrom); + app.post('/add-convert-coin-fund', Request.AddConvertCoinFund); + app.post('/add-convert-currency-fund', Request.AddConvertCurrencyFund); //close blockchain-bond app.post('/close-blockchain-bonds', Request.CloseBlockchainBond); diff --git a/src/background.js b/src/background.js index f31ce2c..ce079a3 100644 --- a/src/background.js +++ b/src/background.js @@ -220,12 +220,14 @@ verifyTx.BTC = function (sender, txid) { } function verifyConvert() { - DB.query("SELECT id, floID, mode, in_txid FROM DirectConvert WHERE status=? AND coin=?", ["PENDING", "BTC"]).then(results => { + DB.query("SELECT id, floID, mode, in_txid, amount, quantity FROM DirectConvert WHERE status=? AND coin=?", ["PENDING", "BTC"]).then(results => { results.forEach(r => { if (mode == _sql.CONVERT_MODE_GET) { verifyTx.token(r.floID, r.in_txid, true).then(({ amount }) => { + if (r.amount !== amount) + throw ([true, "Transaction amount mismatched in blockchain"]); conversion_rates.BTC_INR().then(rate => { - blockchain.convertToCoin.init(r.floID, "BTC", amount, amount / rate, r.id) + blockchain.convertToCoin.init(r.floID, "BTC", amount / rate, r.id) }).catch(error => console.error(error)) }).catch(error => { console.error(error); @@ -235,8 +237,10 @@ function verifyConvert() { }); } else if (mode == _sql.CONVERT_MODE_PUT) { verifyTx.BTC(r.floID, r.in_txid).then(quantity => { + if (r.quantity !== quantity) + throw ([true, "Transaction quantity mismatched in blockchain"]); conversion_rates.BTC_INR().then(rate => { - blockchain.convertFromCoin.init(r.floID, quantity * rate, quantity, r.id) + blockchain.convertFromCoin.init(r.floID, quantity * rate, r.id) }).catch(error => console.error(error)) }).catch(error => { console.error(error); @@ -283,6 +287,67 @@ function confirmConvert() { }).catch(error => console.error(error)); } +function convert_depositFund() { + DB.query("SELECT id, floID, mode, in_txid FROM DirectConvert WHERE status=? AND coin=?", ["DEPOSIT_PENDING", "BTC"]).then(results => { + results.forEach(r => { + if (mode == _sql.CONVERT_MODE_GET) { + verifyTx.token(r.floID, r.in_txid, true).then(({ amount }) => { + DB.query("UPDATE DirectConvert SET status=?, amount=? WHERE id=?", ["DEPOSIT_SUCCESS", amount, r.id]) + .then(_ => null).catch(error => console.error(error)); + }).catch(error => { + console.error(error); + if (error[0]) + DB.query("UPDATE DirectConvert SET status=? WHERE id=?", ["REJECTED", r.id]) + .then(_ => null).catch(error => console.error(error)); + }); + } else if (mode == _sql.CONVERT_MODE_PUT) { + verifyTx.BTC(r.floID, r.in_txid).then(quantity => { + DB.query("UPDATE DirectConvert SET status=?, quantity=? WHERE id=?", ["DEPOSIT_SUCCESS", quantity, r.id]) + .then(_ => null).catch(error => console.error(error)); + }).catch(error => { + console.error(error); + if (error[0]) + DB.query("UPDATE DirectConvert SET status=? WHERE id=?", ["REJECTED", r.id]) + .then(_ => null).catch(error => console.error(error)); + }); + } + }) + }).catch(error => console.error(error)) +} + +function verifyRefund() { + DB.query("SELECT id, floID, in_txid FROM RefundTransact WHERE status=?", ["PENDING"]).then(results => { + verifyTx.token(r.floID, r.in_txid, true) + .then(({ amount }) => blockchain.refundTransact.init(r.floID, amount, r.id)) + .catch(error => { + console.error(error); + if (error[0]) + DB.query("UPDATE RefundTransact SET status=? WHERE id=?", ["REJECTED", r.id]) + .then(_ => null).catch(error => console.error(error)); + }); + }).catch(error => console.error(error)) +} + +function retryRefund() { + DB.query("SELECT id, floID, amount FROM RefundTransact WHERE status=?", ["PROCESSING"]).then(results => { + results.forEach(r => blockchain.refundTransact.retry(r.floID, r.amount, r.id)) + }).catch(error => console.error(error)) +} + +function confirmRefund() { + DB.query("SELECT * FROM RefundTransact WHERE status=?", ["WAITING_CONFIRMATION"]).then(result => { + results.forEach(r => { + floTokenAPI.getTx(r.txid).then(tx => { + if (!tx.transactionDetails.blockheight || !tx.transactionDetails.confirmations) //Still not confirmed + return; + DB.query("UPDATE RefundTransact SET status=? WHERE id=?", ["SUCCESS", r.id]) + .then(result => console.debug(`Refunded ${r.amount} to ${r.floID}`)) + .catch(error => console.error(error)); + }).catch(error => console.error(error)); + }) + }).catch(error => console.error(error)) +} + function retryBondClosing() { DB.query("SELECT id, floID, amount FROM CloseBondTransact WHERE status=?", ["PENDING"]).then(results => { results.forEach(r => blockchain.bondTransact.retry(r.floID, r.amount, r.id)) @@ -340,10 +405,14 @@ function processAll() { verifyConvert(); retryConvert(); confirmConvert(); + convert_depositFund(); retryBondClosing(); confirmBondClosing(); retryFundClosing(); confirmFundClosing(); + verifyRefund(); + retryRefund(); + confirmRefund(); } module.exports = { diff --git a/src/blockchain.js b/src/blockchain.js index 8e3c59b..a33e631 100644 --- a/src/blockchain.js +++ b/src/blockchain.js @@ -7,6 +7,7 @@ var DB; //container for database const TYPE_TOKEN = "TOKEN", TYPE_COIN = "COIN", TYPE_CONVERT = "CONVERT", + TYPE_REFUND = "REFUND", TYPE_BOND = "BOND", TYPE_FUND = "BOB-FUND"; @@ -16,6 +17,7 @@ const balance_locked = {}, [TYPE_COIN]: {}, [TYPE_TOKEN]: {}, [TYPE_CONVERT]: {}, + [TYPE_REFUND]: {}, [TYPE_BOND]: {}, [TYPE_FUND]: {} }; @@ -63,6 +65,7 @@ const WITHDRAWAL_MESSAGE = { [TYPE_COIN]: "(withdrawal from market)", [TYPE_TOKEN]: "(withdrawal from market)", [TYPE_CONVERT]: "(convert coin)", + [TYPE_REFUND]: "(refund from market)", [TYPE_BOND]: "(bond closing)", [TYPE_FUND]: "(fund investment closing)" } @@ -84,6 +87,7 @@ const updateSyntax = { [TYPE_COIN]: "UPDATE WithdrawCoin SET status=?, txid=? WHERE id=?", [TYPE_TOKEN]: "UPDATE WithdrawToken SET status=?, txid=? WHERE id=?", [TYPE_CONVERT]: "UPDATE DirectConvert SET status=?, out_txid=? WHERE id=?", + [TYPE_REFUND]: "UPDATE RefundTransact SET status=?, out_txid=? WHERE id=?", [TYPE_BOND]: "UPDATE CloseBondTransact SET status=?, txid=? WHERE id=?", [TYPE_FUND]: "UPDATE CloseFundTransact SET status=?, txid=? WHERE id=?" }; @@ -135,8 +139,8 @@ function sendToken_retry(floID, token, quantity, id) { else sendAsset(floID, token, quantity, TYPE_TOKEN, id); } -function convertToCoin_init(floID, coin, currency_amount, coin_quantity, id) { - DB.query("UPDATE DirectConvert SET amount=?, quantity=?, status=?, locktime=DEFAULT WHERE id=?", [currency_amount, coin_quantity, "PROCESSING", id]) +function convertToCoin_init(floID, coin, coin_quantity, id) { + DB.query("UPDATE DirectConvert SET quantity=?, status=?, locktime=DEFAULT WHERE id=?", [coin_quantity, "PROCESSING", id]) .then(result => sendAsset(floID, coin, coin_quantity, TYPE_CONVERT, id)) .catch(error => console.error(error)) } @@ -147,16 +151,16 @@ function convertToCoin_retry(floID, coin, coin_quantity, id) { else sendAsset(floID, coin, coin_quantity, TYPE_CONVERT, id); } -function convertFromCoin_init(floID, currency_amount, coin_quantity, id) { - DB.query("UPDATE DirectConvert SET amount=?, quantity=?, status=?, locktime=DEFAULT WHERE id=?", [currency_amount, coin_quantity, "PROCESSING", id]) +function convertFromCoin_init(floID, currency_amount, id) { + DB.query("UPDATE DirectConvert SET amount=?, status=?, locktime=DEFAULT WHERE id=?", [currency_amount, "PROCESSING", id]) .then(result => sendAsset(floID, floGlobals.currency, currency_amount, TYPE_CONVERT, id)) .catch(error => console.error(error)) } -function convertFromCoin_retry(floID, current_amount, id) { +function convertFromCoin_retry(floID, currency_amount, id) { if (id in callbackCollection[TYPE_CONVERT]) console.debug("A callback is already pending for this Coin Convert"); - else sendAsset(floID, floGlobals.currency, current_amount, TYPE_CONVERT, id); + else sendAsset(floID, floGlobals.currency, currency_amount, TYPE_CONVERT, id); } function bondTransact_retry(floID, amount, id) { @@ -171,6 +175,18 @@ function fundTransact_retry(floID, amount, id) { else sendAsset(floID, floGlobals.currency, amount, TYPE_FUND, id); } +function refundTransact_init(floID, amount, id) { + DB.query("UPDATE RefundTransact SET amount=?, status=?, locktime=DEFAULT WHERE id=?", [amount, "PROCESSING", id]) + .then(result => sendAsset(floID, floGlobals.currency, amount, TYPE_REFUND, id)) + .catch(error => console.error(error)) +} + +function refundTransact_retry(floID, amount, id) { + if (id in callbackCollection[TYPE_REFUND]) + console.debug("A callback is already pending for this Refund"); + else sendAsset(floID, floGlobals.currency, amount, TYPE_REFUND, id); +} + module.exports = { set collectAndCall(fn) { collectAndCall = fn; @@ -203,6 +219,10 @@ module.exports = { fundTransact: { retry: fundTransact_retry }, + refundTransact: { + init: refundTransact_init, + retry: refundTransact_retry + }, set DB(db) { DB = db; } diff --git a/src/request.js b/src/request.js index d4719cf..1c606fa 100644 --- a/src/request.js +++ b/src/request.js @@ -309,9 +309,10 @@ function ConvertTo(req, res) { processRequest(res, data.floID, data.pubKey, data.sign, "Conversion", { type: "convert_to", coin: data.coin, + amount: data.amount, txid: data.txid, timestamp: data.timestamp - }, () => conversion.convertToCoin(data.floID, data.txid, data.coin)); + }, () => conversion.convertToCoin(data.floID, data.txid, data.coin, data.amount)); } function ConvertFrom(req, res) { @@ -319,9 +320,38 @@ function ConvertFrom(req, res) { processRequest(res, data.floID, data.pubKey, data.sign, "Conversion", { type: "convert_from", coin: data.coin, + quantity: data.quantity, txid: data.txid, timestamp: data.timestamp - }, () => conversion.convertFromCoin(data.floID, data.txid, data.coin)); + }, () => conversion.convertFromCoin(data.floID, data.txid, data.tx_hex, data.coin, data.quantity)); +} + +function AddConvertCoinFund(req, res) { + let data = req.body; + if (data.floID !== floGlobals.adminID) + res.status(INVALID.e_code).send(INVALID.str(eCode.ACCESS_DENIED, "Access Denied")); + else if (!data.pubKey) + res.status(INVALID.e_code).send(INVALID.str(eCode.MISSING_PARAMETER, "Public key missing")); + else processRequest(res, data.floID, data.pubKey, data.sign, "Conversion Fund", { + type: "add_convert_coin_fund", + coin: data.coin, + txid: data.txid, + timestamp: data.timestamp + }, () => conversion.addFund.coin(data.floID, data.txid, data.coin)); +} + +function AddConvertCurrencyFund(req, res) { + let data = req.body; + if (data.floID !== floGlobals.adminID) + res.status(INVALID.e_code).send(INVALID.str(eCode.ACCESS_DENIED, "Access Denied")); + else if (!data.pubKey) + res.status(INVALID.e_code).send(INVALID.str(eCode.MISSING_PARAMETER, "Public key missing")); + else processRequest(res, data.floID, data.pubKey, data.sign, "Conversion Fund", { + type: "add_convert_currency_fund", + coin: data.coin, + txid: data.txid, + timestamp: data.timestamp + }, () => conversion.addFund.currency(data.floID, data.txid, data.coin)); } function CloseBlockchainBond(req, res) { @@ -543,6 +573,8 @@ module.exports = { RemoveDistributor, ConvertTo, ConvertFrom, + AddConvertCoinFund, + AddConvertCurrencyFund, CloseBlockchainBond, CloseBobsFund, set trustedIDs(ids) { diff --git a/src/services/conversion.js b/src/services/conversion.js index 11ea830..bc58303 100644 --- a/src/services/conversion.js +++ b/src/services/conversion.js @@ -1,4 +1,7 @@ +'use strict'; + const _sql = require('../_constants').sql; +const { MIN_FUND } = require('../_constants')['convert']; const eCode = require('../../docs/scripts/floExchangeAPI').errorCode; const allowedConversion = ["BTC"]; @@ -41,32 +44,120 @@ function USD_INR() { }); } -function convertToCoin(floID, txid, coin) { +function checkPoolBalance(coin, req_value, mode) { return new Promise((resolve, reject) => { if (!allowedConversion.includes(coin)) return reject(INVALID(eCode.INVALID_TOKEN_NAME, `Invalid coin (${coin})`)); - DB.query("SELECT status FROM DirectConvert WHERE in_txid=? AND floID=? mode=?", [txid, floID, _sql.CONVERT_MODE_GET]).then(result => { + DB.query("SELECT mode, SUM(quantity) AS coin_val, SUM(amount) AS cash_val FROM DirectConvert WHERE coin=? AND status NOT IN (?) GROUP BY mode", [coin, ["REJECTED", "REFUND"]]).then(result => { + let coin_net = 0, cash_net = 0; + for (let r of result) + if (r.mode == _sql.CONVERT_MODE_GET) { + coin_net -= r.coin_val; + cash_net += r.cash_val; + } else if (r.mode == _sql.CONVERT_MODE_PUT) { + coin_net += r.coin_val; + cash_net -= r.cash_val; + } + BTC_INR().then(rate => { + coin_net = coin_net * rate; + let availability = -1; + if (mode == _sql.CONVERT_MODE_GET) + availability = coin_net - cash_net * MIN_FUND; + else if (mode == _sql.CONVERT_MODE_PUT) { + availability = cash_net - coin_net * MIN_FUND; + req_value = req_value * rate; //convert to currency value + } + if (req_value > availability) + reject(INVALID(eCode.INSUFFICIENT_FUND, `Insufficient convert! Availability: ${availability > 0 ? availability : 0}`)); + else + resolve(true); + }).catch(error => reject(error)) + }).catch(error => reject(error)) + }) +} + +function convertToCoin(floID, txid, coin, amount) { + return new Promise((resolve, reject) => { + if (!allowedConversion.includes(coin)) + return reject(INVALID(eCode.INVALID_TOKEN_NAME, `Invalid coin (${coin})`)); + else if (typeof amount !== "number" || amount <= 0) + return reject(INVALID(eCode.INVALID_NUMBER, `Invalid amount (${amount})`)); + DB.query("SELECT status FROM DirectConvert WHERE in_txid=? AND floID=? AND mode=?", [txid, floID, _sql.CONVERT_MODE_GET]).then(result => { if (result.length) return reject(INVALID(eCode.DUPLICATE_ENTRY, "Transaction already in process")); - else - DB.query("INSERT INTO DirectConvert(floID, in_txid, mode, coin, status) VALUES (?, ?, ?, ?, ?)", [floID, txid, _sql.CONVERT_MODE_GET, coin, "PENDING"]) + checkPoolBalance(coin, amount, _sql.CONVERT_MODE_GET).then(result => { + DB.query("INSERT INTO DirectConvert(floID, in_txid, mode, coin, amount, status) VALUES (?)", [[floID, txid, _sql.CONVERT_MODE_GET, coin, amount, "PENDING"]]) .then(result => resolve("Conversion request in process")) .catch(error => reject(error)); + }).catch(error => { + if (error instanceof INVALID && error.ecode === eCode.INSUFFICIENT_FUND) + DB.query("INSERT INTO DirectConvert(floID, in_txid, mode, coin, amount, status) VALUES (?)", [[floID, txid, _sql.CONVERT_MODE_GET, coin, amount, "REFUND"]]).then(result => { + DB.query("INSERT INTO RefundTransact(floID, in_txid, amount, status) VALUES (?)", [[floID, txid, amount, "PENDING"]]) + .then(_ => null).catch(error => console.error(error)); + }).catch(error => console.error(error)) + reject(error); + }) }).catch(error => reject(error)) }); } -function convertFromCoin(floID, txid, coin) { +function convertFromCoin(floID, txid, tx_hex, coin, quantity) { return new Promise((resolve, reject) => { if (!allowedConversion.includes(coin)) return reject(INVALID(eCode.INVALID_TOKEN_NAME, `Invalid coin (${coin})`)); - DB.query("SELECT status FROM DirectConvert WHERE in_txid=? AND floID=? mode=?", [txid, floID, _sql.CONVERT_MODE_PUT]).then(result => { + else if (typeof quantity !== "number" || quantity <= 0) + return reject(INVALID(eCode.INVALID_NUMBER, `Invalid quantity (${quantity})`)); + else if (btcOperator.transactionID(tx_hex) !== txid) + return reject(INVALID(eCode.INVALID_TX_ID, `txid ${txid} doesnt match the tx-hex`)); + DB.query("SELECT status FROM DirectConvert WHERE in_txid=? AND floID=? AND mode=?", [txid, floID, _sql.CONVERT_MODE_PUT]).then(result => { if (result.length) return reject(INVALID(eCode.DUPLICATE_ENTRY, "Transaction already in process")); - else - DB.query("INSERT INTO DirectConvert(floID, in_txid, mode, coin, status) VALUES (?, ?, ?, ?, ?)", [floID, txid, _sql.CONVERT_MODE_PUT, coin, "PENDING"]) - .then(result => resolve("Conversion request in process")) - .catch(error => reject(error)); + checkPoolBalance(coin, quantity, _sql.CONVERT_MODE_PUT).then(result => { + btcOperator.broadcastTx(tx_hex).then(b_txid => { + if (b_txid !== txid) + console.warn("broadcast TX-ID is not same as calculated TX-ID"); + DB.query("INSERT INTO DirectConvert(floID, in_txid, mode, coin, quantity, status) VALUES (?)", [[floID, b_txid, _sql.CONVERT_MODE_PUT, coin, quantity, "PENDING"]]) + .then(result => resolve("Conversion request in process")) + .catch(error => reject(error)); + }).catch(error => { + if (error === null) + reject(INVALID(eCode.INVALID_TX_ID, `Invalid transaction hex`)); + else + reject(error); + }) + }).catch(error => reject(error)) + }).catch(error => reject(error)) + }) +} + +function addCurrencyFund(floID, txid, coin) { + return new Promise((resolve, reject) => { + if (floID !== floGlobals.adminID) + return reject(INVALID(eCode.ACCESS_DENIED, 'Access Denied')); + else if (!allowedConversion.includes(coin)) + return reject(INVALID(eCode.INVALID_TOKEN_NAME, `Invalid coin (${coin})`)); + DB.query("SELECT status FROM DirectConvert WHERE in_txid=? AND floID=? AND mode=?", [txid, floID, _sql.CONVERT_MODE_GET]).then(result => { + if (result.length) + return reject(INVALID(eCode.DUPLICATE_ENTRY, "Transaction already in process")); + DB.query("INSERT INTO DirectConvert(floID, in_txid, mode, coin, status) VALUES (?)", [[floID, b_txid, _sql.CONVERT_MODE_GET, coin, "DEPOSIT_PENDING"]]) + .then(result => resolve("Add currency fund in process")) + .catch(error => reject(error)) + }).catch(error => reject(error)) + }) +} + +function addCoinFund(floID, txid, coin) { + return new Promise((resolve, reject) => { + if (floID !== floGlobals.adminID) + return reject(INVALID(eCode.ACCESS_DENIED, 'Access Denied')); + else if (!allowedConversion.includes(coin)) + return reject(INVALID(eCode.INVALID_TOKEN_NAME, `Invalid coin (${coin})`)); + DB.query("SELECT status FROM DirectConvert WHERE in_txid=? AND floID=? AND mode=?", [txid, floID, _sql.CONVERT_MODE_PUT]).then(result => { + if (result.length) + return reject(INVALID(eCode.DUPLICATE_ENTRY, "Transaction already in process")); + DB.query("INSERT INTO DirectConvert(floID, in_txid, mode, coin, status) VALUES (?)", [[floID, b_txid, _sql.CONVERT_MODE_PUT, coin, "DEPOSIT_PENDING"]]) + .then(result => resolve("Add coin fund in process")) + .catch(error => reject(error)) }).catch(error => reject(error)) }) } @@ -79,6 +170,10 @@ module.exports = { }, convertToCoin, convertFromCoin, + addFund: { + coin: addCoinFund, + currency: addCurrencyFund + }, set DB(db) { DB = db; }