Convert feature add-ons
- 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)
This commit is contained in:
parent
d8682a2561
commit
fde297fb44
@ -300,6 +300,17 @@ CREATE TABLE DirectConvert(
|
|||||||
PRIMARY KEY(id)
|
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) */
|
/* Backup Feature (Tables & Triggers) */
|
||||||
|
|
||||||
CREATE TABLE _backup (
|
CREATE TABLE _backup (
|
||||||
|
|||||||
@ -514,6 +514,7 @@
|
|||||||
INSUFFICIENT_SELLCHIP: '203',
|
INSUFFICIENT_SELLCHIP: '203',
|
||||||
GREATER_SELLCHIP_BASE: '204',
|
GREATER_SELLCHIP_BASE: '204',
|
||||||
INSUFFICIENT_PERIOD: '206',
|
INSUFFICIENT_PERIOD: '206',
|
||||||
|
INSUFFICIENT_FUND: '207',
|
||||||
|
|
||||||
//OTHERS
|
//OTHERS
|
||||||
NODES_OFFLINE: '404',
|
NODES_OFFLINE: '404',
|
||||||
@ -1243,6 +1244,7 @@
|
|||||||
floID: floID,
|
floID: floID,
|
||||||
txid: txid,
|
txid: txid,
|
||||||
coin: "BTC",
|
coin: "BTC",
|
||||||
|
amount: amount,
|
||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
};
|
};
|
||||||
if (!proxySecret) //Direct signing (without proxy)
|
if (!proxySecret) //Direct signing (without proxy)
|
||||||
@ -1250,6 +1252,7 @@
|
|||||||
request.sign = signRequest({
|
request.sign = signRequest({
|
||||||
type: "convert_to",
|
type: "convert_to",
|
||||||
coin: request.coin,
|
coin: request.coin,
|
||||||
|
amount: amount,
|
||||||
txid: txid,
|
txid: txid,
|
||||||
timestamp: request.timestamp
|
timestamp: request.timestamp
|
||||||
}, proxySecret || privKey);
|
}, proxySecret || privKey);
|
||||||
@ -1276,11 +1279,13 @@
|
|||||||
return reject(ExchangeError(ExchangeError.BAD_REQUEST_CODE, "Invalid Private Key", errorCode.INVALID_PRIVATE_KEY));
|
return reject(ExchangeError(ExchangeError.BAD_REQUEST_CODE, "Invalid Private Key", errorCode.INVALID_PRIVATE_KEY));
|
||||||
let btc_id = btcOperator.convert.legacy2bech(floID),
|
let btc_id = btcOperator.convert.legacy2bech(floID),
|
||||||
btc_sink = btcOperator.convert.legacy2bech(sinkID);
|
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 = {
|
let request = {
|
||||||
floID: floID,
|
floID: floID,
|
||||||
txid: txid,
|
txid: btcOperator.transactionID(result.transaction),
|
||||||
|
tx_hex: result.transaction.serialize(),
|
||||||
coin: "BTC",
|
coin: "BTC",
|
||||||
|
quantity: quantity,
|
||||||
timestamp: Date.now()
|
timestamp: Date.now()
|
||||||
};
|
};
|
||||||
if (!proxySecret) //Direct signing (without proxy)
|
if (!proxySecret) //Direct signing (without proxy)
|
||||||
@ -1288,7 +1293,8 @@
|
|||||||
request.sign = signRequest({
|
request.sign = signRequest({
|
||||||
type: "convert_from",
|
type: "convert_from",
|
||||||
coin: request.coin,
|
coin: request.coin,
|
||||||
txid: txid,
|
quantity: quantity,
|
||||||
|
txid: data.txid,
|
||||||
timestamp: request.timestamp
|
timestamp: request.timestamp
|
||||||
}, proxySecret || privKey);
|
}, proxySecret || privKey);
|
||||||
console.debug(request);
|
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) {
|
exchangeAPI.closeBlockchainBond = function (bond_id, floID, privKey) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!floCrypto.verifyPrivKey(privKey, floID))
|
if (!floCrypto.verifyPrivKey(privKey, floID))
|
||||||
|
|||||||
@ -24,6 +24,9 @@ module.exports = {
|
|||||||
TOP_RANGE: 10 / 100, //top 10%
|
TOP_RANGE: 10 / 100, //top 10%
|
||||||
REC_HISTORY_INTERVAL: 1 * 60 * 60 * 1000, //1 hr
|
REC_HISTORY_INTERVAL: 1 * 60 * 60 * 1000, //1 hr
|
||||||
},
|
},
|
||||||
|
convert: {
|
||||||
|
MIN_FUND: 0.3 // 30%
|
||||||
|
},
|
||||||
backup: {
|
backup: {
|
||||||
SHARE_THRESHOLD: 50 / 100, //50%
|
SHARE_THRESHOLD: 50 / 100, //50%
|
||||||
HASH_N_ROW: 100,
|
HASH_N_ROW: 100,
|
||||||
|
|||||||
@ -88,6 +88,8 @@ module.exports = function App(secret, DB) {
|
|||||||
//convert from or to coin
|
//convert from or to coin
|
||||||
app.post('/convert-to', Request.ConvertTo);
|
app.post('/convert-to', Request.ConvertTo);
|
||||||
app.post('/convert-from', Request.ConvertFrom);
|
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
|
//close blockchain-bond
|
||||||
app.post('/close-blockchain-bonds', Request.CloseBlockchainBond);
|
app.post('/close-blockchain-bonds', Request.CloseBlockchainBond);
|
||||||
|
|||||||
@ -220,12 +220,14 @@ verifyTx.BTC = function (sender, txid) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function verifyConvert() {
|
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 => {
|
results.forEach(r => {
|
||||||
if (mode == _sql.CONVERT_MODE_GET) {
|
if (mode == _sql.CONVERT_MODE_GET) {
|
||||||
verifyTx.token(r.floID, r.in_txid, true).then(({ amount }) => {
|
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 => {
|
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))
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@ -235,8 +237,10 @@ function verifyConvert() {
|
|||||||
});
|
});
|
||||||
} else if (mode == _sql.CONVERT_MODE_PUT) {
|
} else if (mode == _sql.CONVERT_MODE_PUT) {
|
||||||
verifyTx.BTC(r.floID, r.in_txid).then(quantity => {
|
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 => {
|
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))
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@ -283,6 +287,67 @@ function confirmConvert() {
|
|||||||
}).catch(error => console.error(error));
|
}).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() {
|
function retryBondClosing() {
|
||||||
DB.query("SELECT id, floID, amount FROM CloseBondTransact WHERE status=?", ["PENDING"]).then(results => {
|
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))
|
results.forEach(r => blockchain.bondTransact.retry(r.floID, r.amount, r.id))
|
||||||
@ -340,10 +405,14 @@ function processAll() {
|
|||||||
verifyConvert();
|
verifyConvert();
|
||||||
retryConvert();
|
retryConvert();
|
||||||
confirmConvert();
|
confirmConvert();
|
||||||
|
convert_depositFund();
|
||||||
retryBondClosing();
|
retryBondClosing();
|
||||||
confirmBondClosing();
|
confirmBondClosing();
|
||||||
retryFundClosing();
|
retryFundClosing();
|
||||||
confirmFundClosing();
|
confirmFundClosing();
|
||||||
|
verifyRefund();
|
||||||
|
retryRefund();
|
||||||
|
confirmRefund();
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|||||||
@ -7,6 +7,7 @@ var DB; //container for database
|
|||||||
const TYPE_TOKEN = "TOKEN",
|
const TYPE_TOKEN = "TOKEN",
|
||||||
TYPE_COIN = "COIN",
|
TYPE_COIN = "COIN",
|
||||||
TYPE_CONVERT = "CONVERT",
|
TYPE_CONVERT = "CONVERT",
|
||||||
|
TYPE_REFUND = "REFUND",
|
||||||
TYPE_BOND = "BOND",
|
TYPE_BOND = "BOND",
|
||||||
TYPE_FUND = "BOB-FUND";
|
TYPE_FUND = "BOB-FUND";
|
||||||
|
|
||||||
@ -16,6 +17,7 @@ const balance_locked = {},
|
|||||||
[TYPE_COIN]: {},
|
[TYPE_COIN]: {},
|
||||||
[TYPE_TOKEN]: {},
|
[TYPE_TOKEN]: {},
|
||||||
[TYPE_CONVERT]: {},
|
[TYPE_CONVERT]: {},
|
||||||
|
[TYPE_REFUND]: {},
|
||||||
[TYPE_BOND]: {},
|
[TYPE_BOND]: {},
|
||||||
[TYPE_FUND]: {}
|
[TYPE_FUND]: {}
|
||||||
};
|
};
|
||||||
@ -63,6 +65,7 @@ const WITHDRAWAL_MESSAGE = {
|
|||||||
[TYPE_COIN]: "(withdrawal from market)",
|
[TYPE_COIN]: "(withdrawal from market)",
|
||||||
[TYPE_TOKEN]: "(withdrawal from market)",
|
[TYPE_TOKEN]: "(withdrawal from market)",
|
||||||
[TYPE_CONVERT]: "(convert coin)",
|
[TYPE_CONVERT]: "(convert coin)",
|
||||||
|
[TYPE_REFUND]: "(refund from market)",
|
||||||
[TYPE_BOND]: "(bond closing)",
|
[TYPE_BOND]: "(bond closing)",
|
||||||
[TYPE_FUND]: "(fund investment closing)"
|
[TYPE_FUND]: "(fund investment closing)"
|
||||||
}
|
}
|
||||||
@ -84,6 +87,7 @@ const updateSyntax = {
|
|||||||
[TYPE_COIN]: "UPDATE WithdrawCoin SET status=?, txid=? WHERE id=?",
|
[TYPE_COIN]: "UPDATE WithdrawCoin SET status=?, txid=? WHERE id=?",
|
||||||
[TYPE_TOKEN]: "UPDATE WithdrawToken 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_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_BOND]: "UPDATE CloseBondTransact SET status=?, txid=? WHERE id=?",
|
||||||
[TYPE_FUND]: "UPDATE CloseFundTransact 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);
|
else sendAsset(floID, token, quantity, TYPE_TOKEN, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertToCoin_init(floID, coin, currency_amount, coin_quantity, id) {
|
function convertToCoin_init(floID, coin, coin_quantity, id) {
|
||||||
DB.query("UPDATE DirectConvert SET amount=?, quantity=?, status=?, locktime=DEFAULT WHERE id=?", [currency_amount, coin_quantity, "PROCESSING", 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))
|
.then(result => sendAsset(floID, coin, coin_quantity, TYPE_CONVERT, id))
|
||||||
.catch(error => console.error(error))
|
.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);
|
else sendAsset(floID, coin, coin_quantity, TYPE_CONVERT, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertFromCoin_init(floID, currency_amount, coin_quantity, id) {
|
function convertFromCoin_init(floID, currency_amount, id) {
|
||||||
DB.query("UPDATE DirectConvert SET amount=?, quantity=?, status=?, locktime=DEFAULT WHERE id=?", [currency_amount, coin_quantity, "PROCESSING", 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))
|
.then(result => sendAsset(floID, floGlobals.currency, currency_amount, TYPE_CONVERT, id))
|
||||||
.catch(error => console.error(error))
|
.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])
|
if (id in callbackCollection[TYPE_CONVERT])
|
||||||
console.debug("A callback is already pending for this Coin 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) {
|
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);
|
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 = {
|
module.exports = {
|
||||||
set collectAndCall(fn) {
|
set collectAndCall(fn) {
|
||||||
collectAndCall = fn;
|
collectAndCall = fn;
|
||||||
@ -203,6 +219,10 @@ module.exports = {
|
|||||||
fundTransact: {
|
fundTransact: {
|
||||||
retry: fundTransact_retry
|
retry: fundTransact_retry
|
||||||
},
|
},
|
||||||
|
refundTransact: {
|
||||||
|
init: refundTransact_init,
|
||||||
|
retry: refundTransact_retry
|
||||||
|
},
|
||||||
set DB(db) {
|
set DB(db) {
|
||||||
DB = db;
|
DB = db;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -309,9 +309,10 @@ function ConvertTo(req, res) {
|
|||||||
processRequest(res, data.floID, data.pubKey, data.sign, "Conversion", {
|
processRequest(res, data.floID, data.pubKey, data.sign, "Conversion", {
|
||||||
type: "convert_to",
|
type: "convert_to",
|
||||||
coin: data.coin,
|
coin: data.coin,
|
||||||
|
amount: data.amount,
|
||||||
txid: data.txid,
|
txid: data.txid,
|
||||||
timestamp: data.timestamp
|
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) {
|
function ConvertFrom(req, res) {
|
||||||
@ -319,9 +320,38 @@ function ConvertFrom(req, res) {
|
|||||||
processRequest(res, data.floID, data.pubKey, data.sign, "Conversion", {
|
processRequest(res, data.floID, data.pubKey, data.sign, "Conversion", {
|
||||||
type: "convert_from",
|
type: "convert_from",
|
||||||
coin: data.coin,
|
coin: data.coin,
|
||||||
|
quantity: data.quantity,
|
||||||
txid: data.txid,
|
txid: data.txid,
|
||||||
timestamp: data.timestamp
|
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) {
|
function CloseBlockchainBond(req, res) {
|
||||||
@ -543,6 +573,8 @@ module.exports = {
|
|||||||
RemoveDistributor,
|
RemoveDistributor,
|
||||||
ConvertTo,
|
ConvertTo,
|
||||||
ConvertFrom,
|
ConvertFrom,
|
||||||
|
AddConvertCoinFund,
|
||||||
|
AddConvertCurrencyFund,
|
||||||
CloseBlockchainBond,
|
CloseBlockchainBond,
|
||||||
CloseBobsFund,
|
CloseBobsFund,
|
||||||
set trustedIDs(ids) {
|
set trustedIDs(ids) {
|
||||||
|
|||||||
@ -1,4 +1,7 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
const _sql = require('../_constants').sql;
|
const _sql = require('../_constants').sql;
|
||||||
|
const { MIN_FUND } = require('../_constants')['convert'];
|
||||||
const eCode = require('../../docs/scripts/floExchangeAPI').errorCode;
|
const eCode = require('../../docs/scripts/floExchangeAPI').errorCode;
|
||||||
|
|
||||||
const allowedConversion = ["BTC"];
|
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) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!allowedConversion.includes(coin))
|
if (!allowedConversion.includes(coin))
|
||||||
return reject(INVALID(eCode.INVALID_TOKEN_NAME, `Invalid coin (${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)
|
if (result.length)
|
||||||
return reject(INVALID(eCode.DUPLICATE_ENTRY, "Transaction already in process"));
|
return reject(INVALID(eCode.DUPLICATE_ENTRY, "Transaction already in process"));
|
||||||
else
|
checkPoolBalance(coin, amount, _sql.CONVERT_MODE_GET).then(result => {
|
||||||
DB.query("INSERT INTO DirectConvert(floID, in_txid, mode, coin, status) VALUES (?, ?, ?, ?, ?)", [floID, txid, _sql.CONVERT_MODE_GET, coin, "PENDING"])
|
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"))
|
.then(result => resolve("Conversion request in process"))
|
||||||
.catch(error => reject(error));
|
.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))
|
}).catch(error => reject(error))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertFromCoin(floID, txid, coin) {
|
function convertFromCoin(floID, txid, tx_hex, coin, quantity) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!allowedConversion.includes(coin))
|
if (!allowedConversion.includes(coin))
|
||||||
return reject(INVALID(eCode.INVALID_TOKEN_NAME, `Invalid coin (${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)
|
if (result.length)
|
||||||
return reject(INVALID(eCode.DUPLICATE_ENTRY, "Transaction already in process"));
|
return reject(INVALID(eCode.DUPLICATE_ENTRY, "Transaction already in process"));
|
||||||
else
|
checkPoolBalance(coin, quantity, _sql.CONVERT_MODE_PUT).then(result => {
|
||||||
DB.query("INSERT INTO DirectConvert(floID, in_txid, mode, coin, status) VALUES (?, ?, ?, ?, ?)", [floID, txid, _sql.CONVERT_MODE_PUT, coin, "PENDING"])
|
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"))
|
.then(result => resolve("Conversion request in process"))
|
||||||
.catch(error => reject(error));
|
.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))
|
}).catch(error => reject(error))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -79,6 +170,10 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
convertToCoin,
|
convertToCoin,
|
||||||
convertFromCoin,
|
convertFromCoin,
|
||||||
|
addFund: {
|
||||||
|
coin: addCoinFund,
|
||||||
|
currency: addCurrencyFund
|
||||||
|
},
|
||||||
set DB(db) {
|
set DB(db) {
|
||||||
DB = db;
|
DB = db;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user