- Reject request when not enough fund is available to convert. - MIN_FUND is the minimum fund that should be reserved. - Add funds for conversion via adminID - Added: Refund feature - Automate a refund on currency when convertToCoin request is made when fund is insufficient - ConvertFrom now accepts an non-broadcasted tx_hex from user and broadcast it when convert fund is available. (if convert fund is insufficient, then tx is not broadcasted)
229 lines
8.5 KiB
JavaScript
229 lines
8.5 KiB
JavaScript
'use strict';
|
|
|
|
var collectAndCall; //container for collectAndCall function from backup module
|
|
var chests; //container for blockchain ids (where assets are stored)
|
|
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";
|
|
|
|
const balance_locked = {},
|
|
balance_cache = {},
|
|
callbackCollection = {
|
|
[TYPE_COIN]: {},
|
|
[TYPE_TOKEN]: {},
|
|
[TYPE_CONVERT]: {},
|
|
[TYPE_REFUND]: {},
|
|
[TYPE_BOND]: {},
|
|
[TYPE_FUND]: {}
|
|
};
|
|
|
|
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(quantity, 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
|
|
.sort((a, b) => b[1] - a[1]).map(x => x[0]);
|
|
if (!sinkList.length)
|
|
return reject(`Insufficient balance in chests for asset(${asset})`);
|
|
let sinkID = sinkList.shift();
|
|
getBalance(sinkID, asset).then(balance => {
|
|
if (!(sinkID in balance_cache))
|
|
balance_cache[sinkID] = {};
|
|
balance_cache[sinkID][asset] = balance;
|
|
if (balance > (quantity + (sinkID in balance_locked ? balance_locked[sinkID][asset] || 0 : 0)))
|
|
return resolve(sinkID);
|
|
else
|
|
getSinkID(quantity, asset, sinkList)
|
|
.then(result => resolve(result))
|
|
.catch(error => reject(error))
|
|
}).catch(error => {
|
|
console.error(error);
|
|
getSinkID(quantity, asset, sinkList)
|
|
.then(result => resolve(result))
|
|
.catch(error => reject(error))
|
|
});
|
|
})
|
|
}
|
|
|
|
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)"
|
|
}
|
|
|
|
function sendTx(floID, asset, quantity, sinkID, sinkKey, message) {
|
|
switch (asset) {
|
|
case "FLO":
|
|
return floBlockchainAPI.sendTx(sinkID, floID, quantity, sinkKey, message);
|
|
case "BTC":
|
|
let btc_sinkID = btcOperator.convert.legacy2bech(sinkID),
|
|
btc_receiver = btcOperator.convert.legacy2bech(floID);
|
|
return btcOperator.sendTx(btc_sinkID, sinkKey, btc_receiver, quantity, null);
|
|
default:
|
|
return floTokenAPI.sendToken(sinkKey, quantity, floID, message, asset);
|
|
}
|
|
}
|
|
|
|
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=?"
|
|
};
|
|
|
|
function sendAsset(floID, asset, quantity, type, id) {
|
|
getSinkID(quantity, asset).then(sinkID => {
|
|
let callback = (sinkKey) => {
|
|
//Send asset to user via API
|
|
sendTx(floID, asset, quantity, sinkID, sinkKey, WITHDRAWAL_MESSAGE[type]).then(txid => {
|
|
if (!txid)
|
|
console.error("Transaction not successful");
|
|
else //Transaction was successful, Add in DB
|
|
DB.query(updateSyntax[type], ["WAITING_CONFIRMATION", txid, id])
|
|
.then(_ => null).catch(error => console.error(error));
|
|
}).catch(error => console.error(error)).finally(_ => {
|
|
delete callbackCollection[type][id];
|
|
balance_locked[sinkID][asset] -= quantity;
|
|
});
|
|
}
|
|
collectAndCall(sinkID, callback);
|
|
callbackCollection[type][id] = callback;
|
|
if (!(sinkID in balance_locked))
|
|
balance_locked[sinkID] = {};
|
|
balance_locked[sinkID][asset] = (balance_locked[sinkID][asset] || 0) + quantity;
|
|
}).catch(error => console.error(error))
|
|
}
|
|
|
|
function sendCoin_init(floID, coin, quantity) {
|
|
DB.query("INSERT INTO WithdrawCoin (floID, coin, amount, status) VALUES (?, ?, ?, ?)", [floID, coin, quantity, "PENDING"])
|
|
.then(result => sendAsset(floID, coin, quantity, TYPE_COIN, result.insertId))
|
|
.catch(error => console.error(error))
|
|
}
|
|
|
|
function sendCoin_retry(floID, coin, quantity, id) {
|
|
if (id in callbackCollection[TYPE_COIN])
|
|
console.debug("A callback is already pending for this Coin transfer");
|
|
else sendAsset(floID, coin, quantity, TYPE_COIN, id);
|
|
}
|
|
|
|
function sendToken_init(floID, token, quantity) {
|
|
DB.query("INSERT INTO WithdrawToken (floID, token, amount, status) VALUES (?, ?, ?, ?)", [floID, token, quantity, "PENDING"])
|
|
.then(result => sendAsset(floID, quantity, TYPE_TOKEN, result.insertId))
|
|
.catch(error => console.error(error))
|
|
}
|
|
|
|
function sendToken_retry(floID, token, quantity, id) {
|
|
if (id in callbackCollection[TYPE_TOKEN])
|
|
console.debug("A callback is already pending for this Token transfer");
|
|
else sendAsset(floID, token, quantity, TYPE_TOKEN, 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))
|
|
}
|
|
|
|
function convertToCoin_retry(floID, coin, coin_quantity, id) {
|
|
if (id in callbackCollection[TYPE_CONVERT])
|
|
console.debug("A callback is already pending for this Coin convert");
|
|
else sendAsset(floID, coin, coin_quantity, TYPE_CONVERT, 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, 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, currency_amount, TYPE_CONVERT, id);
|
|
}
|
|
|
|
function bondTransact_retry(floID, amount, id) {
|
|
if (id in callbackCollection[TYPE_BOND])
|
|
console.debug("A callback is already pending for this Bond closing");
|
|
else sendAsset(floID, floGlobals.currency, amount, TYPE_BOND, id);
|
|
}
|
|
|
|
function fundTransact_retry(floID, amount, id) {
|
|
if (id in callbackCollection[TYPE_FUND])
|
|
console.debug("A callback is already pending for this Fund investment closing");
|
|
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;
|
|
},
|
|
get chests() {
|
|
return chests;
|
|
},
|
|
set chests(c) {
|
|
chests = c;
|
|
},
|
|
sendCoin: {
|
|
init: sendCoin_init,
|
|
retry: sendCoin_retry
|
|
},
|
|
sendToken: {
|
|
init: sendToken_init,
|
|
retry: sendToken_retry
|
|
},
|
|
convertToCoin: {
|
|
init: convertToCoin_init,
|
|
retry: convertToCoin_retry
|
|
},
|
|
convertFromCoin: {
|
|
init: convertFromCoin_init,
|
|
retry: convertFromCoin_retry
|
|
},
|
|
bondTransact: {
|
|
retry: bondTransact_retry
|
|
},
|
|
fundTransact: {
|
|
retry: fundTransact_retry
|
|
},
|
|
refundTransact: {
|
|
init: refundTransact_init,
|
|
retry: refundTransact_retry
|
|
},
|
|
set DB(db) {
|
|
DB = db;
|
|
}
|
|
} |