Close-Blockchain-bond support

- Adding feature to close blockchain bonds upon user requests (when lock-in period is over)
- Improved some syntax and structure of coding
This commit is contained in:
sairajzero 2022-10-10 19:19:18 +05:30
parent fef11b4712
commit 90d2a4764e
5 changed files with 423 additions and 98 deletions

View File

@ -214,6 +214,39 @@ CREATE TABLE AuditTrade(
/* External Service */
CREATE TABLE BlockchainBonds(
bond_id VARCHAR(128) NOT NULL,
floID CHAR(34) NOT NULL,
amount_in DECIMAL(16, 2) NOT NULL,
begin_date DATE NOT NULL,
btc_base DECIMAL(16, 2) NOT NULL,
usd_base DECIMAL(16, 2) NOT NULL,
gain_cut DECIMAL(6, 5) NOT NULL,
min_ipa DECIMAL(6, 5) NOT NULL,
max_period VARCHAR(10) NOT NULL,
lockin_period VARCHAR(10) NOT NULL,
close_id VARCHAR(128),
amount_out DECIMAL(16, 2),
PRIMARY KEY(bond_id)
);
CREATE TABLE CloseBondTransact(
id INT NOT NULL AUTO_INCREMENT,
bond_id VARCHAR(128) NOT NULL,
floID CHAR(34) NOT NULL,
amount DECIMAL(16, 2) NOT NULL,
end_date DATE NOT NULL,
ref_sign VARCHAR(180) NOT NULL,
btc_net DECIMAL(16, 2) NOT NULL,
usd_net DECIMAL(16, 2) NOT NULL,
txid VARCHAR(128),
close_id VARCHAR(128),
status VARCHAR(50) NOT NULL,
KEY(id),
PRIMARY KEY(bond_id),
FOREIGN KEY (bond_id) REFERENCES BlockchainBonds(bond_id)
);
CREATE TABLE DirectConvert(
id INT NOT NULL AUTO_INCREMENT,
floID CHAR(34) NOT NULL,
@ -225,6 +258,7 @@ CREATE TABLE DirectConvert(
out_txid VARCHAR(128),
locktime TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status VARCHAR(50) NOT NULL,
PRIMARY KEY(id)
);
/* Backup Feature (Tables & Triggers) */

View File

@ -1,12 +1,14 @@
'use strict';
const blockchain = require('./blockchain');
const conversion_rates = require('./services/conversion').getRate;
const bond_util = require('./services/bonds').util;
const {
LAUNCH_SELLER_TAG,
MAXIMUM_LAUNCH_SELL_CHIPS,
} = require('./_constants')["market"];
var DB; //container for database
const _sql = require('./_constants').sql;
var updateBalance; // container for updateBalance function
@ -15,19 +17,19 @@ const verifyTx = {};
function confirmDepositFLO() {
DB.query("SELECT id, floID, txid FROM DepositCoin WHERE coin=? AND status=?", ["FLO", "PENDING"]).then(results => {
results.forEach(req => {
verifyTx.FLO(req.floID, req.txid).then(amount => {
addSellChipsIfLaunchSeller(req.floID, amount).then(txQueries => {
txQueries.push(updateBalance.add(req.floID, "FLO", amount));
txQueries.push(["UPDATE DepositCoin SET status=?, amount=? WHERE id=?", ["SUCCESS", amount, req.id]]);
results.forEach(r => {
verifyTx.FLO(r.floID, r.txid).then(amount => {
addSellChipsIfLaunchSeller(r.floID, amount).then(txQueries => {
txQueries.push(updateBalance.add(r.floID, "FLO", amount));
txQueries.push(["UPDATE DepositCoin SET status=?, amount=? WHERE id=?", ["SUCCESS", amount, r.id]]);
DB.transaction(txQueries)
.then(result => console.debug("FLO deposited:", req.floID, amount))
.then(result => console.debug("FLO deposited:", r.floID, amount))
.catch(error => console.error(error))
}).catch(error => console.error(error))
}).catch(error => {
console.error(error);
if (error[0])
DB.query("UPDATE DepositCoin SET status=? WHERE id=?", ["REJECTED", req.id])
DB.query("UPDATE DepositCoin SET status=? WHERE id=?", ["REJECTED", r.id])
.then(_ => null).catch(error => console.error(error));
});
})
@ -90,25 +92,25 @@ function addSellChipsIfLaunchSeller(floID, quantity) {
function confirmDepositToken() {
DB.query("SELECT id, floID, txid FROM DepositToken WHERE status=?", ["PENDING"]).then(results => {
results.forEach(req => {
verifyTx.token(req.floID, req.txid).then(({ token, amount, flo_amount }) => {
DB.query("SELECT id FROM DepositCoin where floID=? AND coin=? AND txid=?", [req.floID, "FLO", req.txid]).then(result => {
results.forEach(r => {
verifyTx.token(r.floID, r.txid).then(({ token, amount, flo_amount }) => {
DB.query("SELECT id FROM DepositCoin where floID=? AND coin=? AND txid=?", [r.floID, "FLO", r.txid]).then(result => {
let txQueries = [];
//Add the FLO balance if necessary
if (!result.length) {
txQueries.push(updateBalance.add(req.floID, "FLO", flo_amount));
txQueries.push(["INSERT INTO DepositCoin(txid, floID, coin, amount, status) VALUES (?, ?, ?, ?, ?)", [req.txid, req.floID, "FLO", flo_amount, "SUCCESS"]]);
txQueries.push(updateBalance.add(r.floID, "FLO", flo_amount));
txQueries.push(["INSERT INTO DepositCoin(txid, floID, coin, amount, status) VALUES (?, ?, ?, ?, ?)", [r.txid, r.floID, "FLO", flo_amount, "SUCCESS"]]);
}
txQueries.push(["UPDATE DepositToken SET status=?, token=?, amount=? WHERE id=?", ["SUCCESS", token, amount, req.id]]);
txQueries.push(updateBalance.add(req.floID, token, amount));
txQueries.push(["UPDATE DepositToken SET status=?, token=?, amount=? WHERE id=?", ["SUCCESS", token, amount, r.id]]);
txQueries.push(updateBalance.add(r.floID, token, amount));
DB.transaction(txQueries)
.then(result => console.debug("Token deposited:", req.floID, token, amount))
.then(result => console.debug("Token deposited:", r.floID, token, amount))
.catch(error => console.error(error));
}).catch(error => console.error(error));
}).catch(error => {
console.error(error);
if (error[0])
DB.query("UPDATE DepositToken SET status=? WHERE id=?", ["REJECTED", req.id])
DB.query("UPDATE DepositToken SET status=? WHERE id=?", ["REJECTED", r.id])
.then(_ => null).catch(error => console.error(error));
});
})
@ -142,24 +144,24 @@ verifyTx.token = function (sender, txid, currencyOnly = false) {
function retryWithdrawalCoin() {
DB.query("SELECT id, floID, coin, amount FROM WithdrawCoin WHERE status=?", ["PENDING"]).then(results => {
results.forEach(req => blockchain.sendCoin.retry(req.floID, req.coin, req.amount, req.id));
results.forEach(r => blockchain.sendCoin.retry(r.floID, r.coin, r.amount, r.id));
}).catch(error => console.error(error));
}
function retryWithdrawalToken() {
DB.query("SELECT id, floID, token, amount FROM WithdrawToken WHERE status=?", ["PENDING"]).then(results => {
results.forEach(req => blockchain.sendToken.retry(req.floID, req.token, req.amount, req.id));
results.forEach(r => blockchain.sendToken.retry(r.floID, r.token, r.amount, r.id));
}).catch(error => console.error(error));
}
function confirmWithdrawalFLO() {
DB.query("SELECT id, floID, amount, txid FROM WithdrawCoin WHERE coin=? AND status=?", ["FLO", "WAITING_CONFIRMATION"]).then(results => {
results.forEach(req => {
floBlockchainAPI.getTx(req.txid).then(tx => {
results.forEach(r => {
floBlockchainAPI.getTx(r.txid).then(tx => {
if (!tx.blockheight || !tx.confirmations) //Still not confirmed
return;
DB.query("UPDATE WithdrawCoin SET status=? WHERE id=?", ["SUCCESS", req.id])
.then(result => console.debug("FLO withdrawed:", req.floID, req.amount))
DB.query("UPDATE WithdrawCoin SET status=? WHERE id=?", ["SUCCESS", r.id])
.then(result => console.debug("FLO withdrawed:", r.floID, r.amount))
.catch(error => console.error(error))
}).catch(error => console.error(error));
})
@ -168,12 +170,12 @@ function confirmWithdrawalFLO() {
function confirmWithdrawalBTC() {
DB.query("SELECT id, floID, amount, txid FROM WithdrawCoin WHERE coin=? AND status=?", ["BTC", "WAITING_CONFIRMATION"]).then(results => {
results.forEach(req => {
btcOperator.getTx(req.txid).then(tx => {
results.forEach(r => {
btcOperator.getTx(r.txid).then(tx => {
if (!tx.blockhash || !tx.confirmations) //Still not confirmed
return;
DB.query("UPDATE WithdrawCoin SET status=? WHERE id=?", ["SUCCESS", req.id])
.then(result => console.debug("BTC withdrawed:", req.floID, req.amount))
DB.query("UPDATE WithdrawCoin SET status=? WHERE id=?", ["SUCCESS", r.id])
.then(result => console.debug("BTC withdrawed:", r.floID, r.amount))
.catch(error => console.error(error))
}).catch(error => console.error(error));
})
@ -182,10 +184,10 @@ function confirmWithdrawalBTC() {
function confirmWithdrawalToken() {
DB.query("SELECT id, floID, token, amount, txid FROM WithdrawToken WHERE status=?", ["WAITING_CONFIRMATION"]).then(results => {
results.forEach(req => {
floTokenAPI.getTx(req.txid).then(tx => {
DB.query("UPDATE WithdrawToken SET status=? WHERE id=?", ["SUCCESS", req.id])
.then(result => console.debug("Token withdrawed:", req.floID, req.token, req.amount))
results.forEach(r => {
floTokenAPI.getTx(r.txid).then(tx => {
DB.query("UPDATE WithdrawToken SET status=? WHERE id=?", ["SUCCESS", r.id])
.then(result => console.debug("Token withdrawed:", r.floID, r.token, r.amount))
.catch(error => console.error(error));
}).catch(error => console.error(error));
})
@ -216,27 +218,27 @@ 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 => {
results.forEach(req => {
results.forEach(r => {
if (mode == _sql.CONVERT_MODE_GET) {
verifyTx.token(req.floID, req.in_txid, true).then(({ amount }) => {
verifyTx.token(r.floID, r.in_txid, true).then(({ amount }) => {
conversion_rates.BTC_INR().then(rate => {
blockchain.convertToCoin.init(req.floID, "BTC", amount, amount / rate, req.id)
blockchain.convertToCoin.init(r.floID, "BTC", amount, amount / rate, r.id)
}).catch(error => console.error(error))
}).catch(error => {
console.error(error);
if (error[0])
DB.query("UPDATE DirectConvert SET status=? WHERE id=?", ["REJECTED", req.id])
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(req.floID, req.in_txid).then(quantity => {
verifyTx.BTC(r.floID, r.in_txid).then(quantity => {
conversion_rates.BTC_INR().then(rate => {
blockchain.convertFromCoin.init(req.floID, quantity * rate, quantity, req.id)
blockchain.convertFromCoin.init(r.floID, quantity * rate, quantity, r.id)
}).catch(error => console.error(error))
}).catch(error => {
console.error(error);
if (error[0])
DB.query("UPDATE DirectConvert SET status=? WHERE id=?", ["REJECTED", req.id])
DB.query("UPDATE DirectConvert SET status=? WHERE id=?", ["REJECTED", r.id])
.then(_ => null).catch(error => console.error(error));
});
}
@ -246,48 +248,79 @@ function verifyConvert() {
function retryConvert() {
DB.query("SELECT id, floID, mode, amount, quantity FROM DirectConvert WHERE status=? AND coin=?", ["PROCESSING", "BTC"]).then(results => {
results.forEach(req => {
results.forEach(r => {
if (mode == _sql.CONVERT_MODE_GET)
blockchain.convertToCoin.retry(req.floID, "BTC", req.quantity, req.id);
blockchain.convertToCoin.retry(r.floID, "BTC", r.quantity, r.id);
else if (mode == _sql.CONVERT_MODE_PUT)
blockchain.convertFromCoin.retry(req.floID, req.amount, req.id)
blockchain.convertFromCoin.retry(r.floID, r.amount, r.id)
})
}).catch(error => console.error(error))
}
function confirmConvert() {
DB.query("SELECT id, floID, mode, amount, quantity, out_txid FROM DirectConvert WHERE status=? AND coin=?", ["WAITING_CONFIRMATION", "BTC"]).then(results => {
results.forEach(req => {
results.forEach(r => {
if (mode == _sql.CONVERT_MODE_GET)
btcOperator.getTx(req.out_txid).then(tx => {
btcOperator.getTx(r.out_txid).then(tx => {
if (!tx.blockhash || !tx.confirmations) //Still not confirmed
return;
DB.query("UPDATE DirectConvert SET status=? WHERE id=?", ["SUCCESS", req.id])
.then(result => console.debug(`${req.floID} converted ${amount} to ${req.quantity} BTC`))
DB.query("UPDATE DirectConvert SET status=? WHERE id=?", ["SUCCESS", r.id])
.then(result => console.debug(`${r.floID} converted ${amount} to ${r.quantity} BTC`))
.catch(error => console.error(error))
}).catch(error => console.error(error));
else if (mode == _sql.CONVERT_MODE_PUT)
floTokenAPI.getTx(req.out_txid).then(tx => {
DB.query("UPDATE DirectConvert SET status=? WHERE id=?", ["SUCCESS", req.id])
.then(result => console.debug(`${req.floID} converted ${req.quantity} BTC to ${amount}`))
floTokenAPI.getTx(r.out_txid).then(tx => {
DB.query("UPDATE DirectConvert SET status=? WHERE id=?", ["SUCCESS", r.id])
.then(result => console.debug(`${r.floID} converted ${r.quantity} BTC to ${amount}`))
.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))
}).catch(error => console.error(error))
}
function confirmBondClosing() {
DB.query("SELECT * FROM CloseBondTransact WHERE status=?", ["WAITING_CONFIRMATION"]).then(result => {
results.forEach(r => {
floTokenAPI.getTx(r.txid).then(tx => {
let closeBondString = bond_util.stringify.end(r.bond_id, r.end_date, r.btc_net, r.usd_net, r.amount, r.ref_sign, r.txid);
floBlockchainAPI.writeData(global.myFloID, closeBondString, global.myPrivKey, bond_util.config.adminID).then(txid => {
DB.query("UPDATE CloseBondTransact SET status=?, close_id=? WHERE id=?", ["SUCCESS", txid, r.id])
.then(result => console.debug("Bond closed:", r.bond_id))
.catch(error => console.error(error));
}).catch(error => console.error(error))
}).catch(error => console.error(error));
})
}).catch(error => reject(error))
}
function processAll() {
confirmDepositFLO();
confirmDepositToken();
retryWithdrawalCoin();
retryWithdrawalToken();
confirmWithdrawalFLO();
confirmWithdrawalBTC();
confirmWithdrawalToken();
verifyConvert();
retryConvert();
confirmConvert();
retryBondClosing();
confirmBondClosing();
}
module.exports = {
blockchain,
confirmDepositFLO,
confirmDepositToken,
retryWithdrawalCoin,
retryWithdrawalToken,
confirmWithdrawalFLO,
confirmWithdrawalBTC,
confirmWithdrawalToken,
verifyConvert,
retryConvert,
confirmConvert,
process: processAll,
set DB(db) {
DB = db;
blockchain.DB = db;
},
set updateBalance(f) {
updateBalance = f;
}

View File

@ -2,18 +2,20 @@
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 WITHDRAWAL_MESSAGE = "(withdrawal from market)",
TYPE_TOKEN = "TOKEN",
const TYPE_TOKEN = "TOKEN",
TYPE_COIN = "COIN",
TYPE_CONVERT = "CONVERT";
TYPE_CONVERT = "CONVERT",
TYPE_BOND = "BOND";
const balance_locked = {},
balance_cache = {},
callbackCollection = {
[TYPE_COIN]: {},
[TYPE_TOKEN]: {},
[TYPE_CONVERT]: {}
[TYPE_CONVERT]: {},
[TYPE_BOND]: {}
};
function getBalance(sinkID, asset) {
@ -55,43 +57,43 @@ function getSinkID(quantity, asset, sinkList = null) {
})
}
function sendTx(floID, asset, quantity, sinkID, sinkKey) {
const WITHDRAWAL_MESSAGE = {
[TYPE_COIN]: "(withdrawal from market)",
[TYPE_TOKEN]: "(withdrawal from market)",
[TYPE_CONVERT]: "(convert coin)",
[TYPE_BOND]: "(bond closing)"
}
function sendTx(floID, asset, quantity, sinkID, sinkKey, message) {
switch (asset) {
case "FLO":
return floBlockchainAPI.sendTx(sinkID, floID, quantity, sinkKey, WITHDRAWAL_MESSAGE);
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, WITHDRAWAL_MESSAGE, asset);
return floTokenAPI.sendToken(sinkKey, quantity, floID, message, asset);
}
}
const tableUpdate = {
[TYPE_COIN]: (id, txid) => {
DB.query("UPDATE WithdrawCoin SET status=?, txid=? WHERE id=?", ["WAITING_CONFIRMATION", txid, id])
.then(_ => null).catch(error => console.error(error))
},
[TYPE_TOKEN]: (id, txid) => {
DB.query("UPDATE WithdrawToken SET status=?, txid=? WHERE id=?", ["WAITING_CONFIRMATION", txid, id])
.then(_ => null).catch(error => console.error(error))
},
[TYPE_CONVERT]: (id, txid) => {
DB.query("UPDATE DirectConvert SET status=?, out_txid=? WHERE id=?", ["WAITING_CONFIRMATION", txid, id])
.then(_ => null).catch(error => console.error(error));
}
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_BOND]: "UPDATE CloseBondTransact 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).then(txid => {
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
tableUpdate[type](id, txid);
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;
@ -114,8 +116,7 @@ function sendCoin_init(floID, coin, quantity) {
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);
else sendAsset(floID, coin, quantity, TYPE_COIN, id);
}
function sendToken_init(floID, token, quantity) {
@ -127,8 +128,7 @@ function sendToken_init(floID, token, quantity) {
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);
else sendAsset(floID, token, quantity, TYPE_TOKEN, id);
}
function convertToCoin_init(floID, coin, currency_amount, coin_quantity, id) {
@ -139,9 +139,8 @@ function convertToCoin_init(floID, coin, currency_amount, coin_quantity, id) {
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);
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, coin_quantity, id) {
@ -153,8 +152,13 @@ function convertFromCoin_init(floID, currency_amount, coin_quantity, id) {
function convertFromCoin_retry(floID, current_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, current_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);
}
module.exports = {
@ -182,5 +186,11 @@ module.exports = {
convertFromCoin: {
init: convertFromCoin_init,
retry: convertFromCoin_retry
},
bondTransact: {
retry: bondTransact_retry
},
set DB(db) {
DB = db;
}
}

View File

@ -2,6 +2,7 @@
const coupling = require('./coupling');
const background = require('./background');
const blockchain = background.blockchain;
const {
@ -504,16 +505,7 @@ function periodicProcess() {
floBlockchainAPI.promisedAPI('api/blocks?limit=1').then(result => {
if (lastSyncBlockHeight < result.blocks[0].height) {
lastSyncBlockHeight = result.blocks[0].height;
background.confirmDepositFLO();
background.confirmDepositToken();
background.retryWithdrawalCoin();
background.retryWithdrawalToken();
background.confirmWithdrawalFLO();
background.confirmWithdrawalBTC();
background.confirmWithdrawalToken();
background.verifyConvert();
background.retryConvert();
background.confirmConvert();
background.process();
console.debug("Last Block :", lastSyncBlockHeight);
}
}).catch(error => console.error(error));
@ -558,6 +550,7 @@ module.exports = {
set DB(db) {
DB = db;
coupling.DB = db;
blockchain.DB = db;
},
set assetList(assets) {
assetList = assets;

255
src/services/bonds.js Normal file
View File

@ -0,0 +1,255 @@
'use strict';
const eCode = require('../eCode_test')// require('../../docs/scripts/floExchangeAPI').errorCode;
const getRate = require('./conversion').getRate;
var DB; //container for database
const blockchainBond = (function () {
const magnitude = m => {
switch (m) {
case "thousand": return 1000;
case "lakh": case "lakhs": return 100000;
case "million": return 1000000;
case "crore": case "crores": return 10000000;
default: return null;
}
}
const parseNumber = (str) => {
let n = 0,
g = 0;
str.toLowerCase().replace(/,/g, '').split(" ").forEach(s => {
if (!isNaN(s))
g = parseFloat(s);
else {
let m = magnitude(s);
if (m !== null) {
n += m * g;
g = 0;
}
}
});
return n + g;
}
const parsePeriod = (str) => {
let P = '', n = 0;
str.toLowerCase().replace(/,/g, '').split(" ").forEach(s => {
if (!isNaN(s))
n = parseFloat(s);
else switch (s) {
case "year(s)": case "year": case "years": P += (n + 'Y'); n = 0; break;
case "month(s)": case "month": case "months": P += (n + 'M'); n = 0; break;
case "day(s)": case "day": case "days": P += (n + 'D'); n = 0; break;
}
});
return P;
}
const dateFormat = (date = null) => {
let d = (date ? new Date(date) : new Date()).toDateString();
return [d.substring(8, 10), d.substring(4, 7), d.substring(11, 15)].join(" ");
}
const yearDiff = (d1 = null, d2 = null) => {
d1 = d1 ? new Date(d1) : new Date();
d2 = d2 ? new Date(d2) : new Date();
let y = d1.getYear() - d2.getYear(),
m = d1.getMonth() - d2.getMonth(),
d = d1.getDate() - d2.getDate()
return y + m / 12 + d / 365;
}
const dateAdder = function (start_date, duration) {
let date = new Date(start_date);
let y = parseInt(duration.match(/\d+Y/)),
m = parseInt(duration.match(/\d+M/)),
d = parseInt(duration.match(/\d+D/));
if (!isNaN(y))
date.setFullYear(date.getFullYear() + y);
if (!isNaN(m))
date.setMonth(date.getMonth() + m);
if (!isNaN(d))
date.setDate(date.getDate() + d);
return date;
}
function calcNetValue(BTC_base, BTC_net, startDate, minIpa, maxPeriod, cut, amount, USD_base, USD_net) {
let gain, duration, interest, net;
gain = (BTC_net - BTC_base) / BTC_base;
duration = yearDiff(Math.min(Date.now(), dateAdder(startDate, maxPeriod).getTime()), startDate);
interest = Math.max(cut * gain, minIpa * duration);
net = amount / USD_base;
net += net * interest;
return net * USD_net;
}
function stringify_main(BTC_base, start_date, guaranteed_interest, guarantee_period, gain_cut, amount, USD_base, lockin_period, floID) {
return [
`Product: RanchiMall Bitcoin Bond`,
`Base value: ${BTC_base} USD`,
`Date of bond start: ${start_date}`,
`Guaranteed interest: ${guaranteed_interest}% per annum simple for ${guarantee_period}`,
`Bond value: guaranteed interest or ${gain_cut}% of the gains whichever is higher`,
`Amount invested: Rs ${amount}`,
`USD INR rate at start: ${USD_base}`,
`Lockin period: ${lockin_period}`,
`FLO ID of Bond Holder: ${floID}`
].join("|");
}
function parse_main(data) {
//Data (add bond) sent by admin
let details = {};
data.split("|").forEach(d => {
d = d.split(': ');
switch (d[0].toLowerCase()) {
case "base value":
details["BTC_base"] = parseNumber(d[1].slice(0, -4)); break;
case "date of bond start":
details["startDate"] = new Date(d[1]); break;
case "guaranteed interest":
details["minIpa"] = parseFloat(d[1].match(/\d+%/)) / 100;
details["maxPeriod"] = parsePeriod(d[1].match(/for .+/).toString()); break;
case "bond value":
details["cut"] = parseFloat(d[1].match(/\d+%/)) / 100; break;
case "amount invested":
details["amount"] = parseNumber(d[1].substring(3)); break;
case "usd inr rate at start":
details["USD_base"] = parseFloat(d[1]); break;
case "lockin period":
details["lockinPeriod"] = parsePeriod(d[1]); break;
case "flo id of bond holder":
details["floID"] = d[1]; break;
}
})
return details;
}
function stringify_end(bond_id, end_date, BTC_net, USD_net, amount, ref_sign, payment_ref) {
return [
`Product: RanchiMall Bitcoin Bond`,
`Bond: ${bond_id}`,
`End value: ${BTC_net} USD`,
`Date of bond end: ${end_date}`,
`USD INR rate at end: ${USD_net}`,
`Amount withdrawn: Rs ${amount} via ${payment_ref}`,
`Reference: ${ref_sign}`
].join("|");
}
function parse_end(data) {
//Data (end bond) send by market nodes
let details = {};
data.split("|").forEach(d => {
d = d.split(': ');
switch (d[0].toLowerCase()) {
case "bond":
details["bondID"] = d[1];
case "end value":
details["BTC_net"] = parseNumber(d[1].slice(0, -4)); break;
case "date of bond end":
details["endDate"] = new Date(d[1]); break;
case "amount withdrawn":
details["amountFinal"] = parseNumber(d[1].match(/\d.+ via/).toString());
details["payment_refRef"] = d[1].match(/via .+/).toString().substring(4); break;
case "usd inr rate at end":
details["USD_net"] = parseFloat(d[1]); break;
case "reference":
details["refSign"] = d[1]; break;
}
})
}
return {
dateAdder,
dateFormat,
calcNetValue,
parse: {
main: parse_main,
end: parse_end
},
stringify: {
main: stringify_main,
end: stringify_end
}
}
})();
blockchainBond.config = {
adminID: "FBBstZ2GretgQqDP55yt8iVd4KNZkdvEzH",
application: "BlockchainBonds",
productStr: "Product: RanchiMall Bitcoin Bond",
}
function refreshBlockchainData(nodeList = []) {
return new Promise((resolve, reject) => {
DB.query("SELECT num FROM LastTx WHERE floID=?", [blockchainBond.config.adminID]).then(result => {
let lastTx = result.length ? result[0].num : 0;
floBlockchainAPI.readData(blockchainBond.config.adminID, {
ignoreOld: lastTx,
senders: [nodeList].concat(blockchainBond.config.adminID), //sentOnly: true,
tx: true,
filter: d => d.startsWith(blockchainBond.config.productStr)
}).then(result => {
let promises = [];
result.data.forEach(d => {
let bond = d.senders.has(blockchainBond.config.adminID) ? blockchainBond.parse.main(d.data) : null;
if (bond && bond.amount)
promises.push(DB.query("INSERT INTO BlockchainBonds(bond_id, floID, amount_in, begin_date, btc_base, usd_base, gain_cut, min_ipa, max_period, lockin_period) VALUES ? ON DUPLICATE KEY UPDATE bond_id=bond_id",
[[[d.txid, bond.floID, bond.amount, bond.startDate, bond.BTC_base, bond.USD_base, bond.cut, bond.minIpa, bond.maxPeriod, bond.lockinPeriod]]]));
else {
let details = blockchainBond.parse.end(d.data);
if (details.bondID && details.amountFinal)
promises.push(DB.query("UPDATE BlockchainBonds SET close_id=? amount_out=? WHERE bond_id=?", [d.txid, details.amountFinal, details.bondID]));
}
});
promises.push(DB.query("INSERT INTO LastTx (floID, num) VALUE (?, ?) ON DUPLICATE KEY UPDATE num=?", [blockchainBond.config.adminID, result.totalTxs, result.totalTxs]));
Promise.allSettled(promises).then(results => {
//console.debug(results.filter(r => r.status === "rejected"));
if (results.reduce((a, r) => r.status === "rejected" ? ++a : a, 0))
console.warn("Some bond data might not have been saved in database correctly");
resolve(result.totalTxs);
})
}).catch(error => reject(error))
}).catch(error => reject(error))
})
}
function closeBond(bond_id, floID, ref) {
return new Promise((resolve, reject) => {
DB.query("SELECT status FROM CloseBondTransact WHERE bond_id=?", [bond_id]).then(result => {
if (result.length)
return reject(INVALID(eCode.DUPLICATE_ENTRY, `Bond closing already in process`));
DB.query("SELECT * FROM BlockchainBonds WHERE bond_id=?", [bond_id]).then(result => {
if (!result.length)
return reject(INVALID(eCode.NOT_FOUND, 'Bond not found'));
let bond = result[0];
if (bond.floID !== floID)
return reject(INVALID(eCode.NOT_OWNER, 'Bond doesnot belong to the user'));
if (bond.close_id)
return reject(INVALID(eCode.DUPLICATE_ENTRY, `Bond already closed (${bond.close_id})`));
if (Date.now() < blockchainBond.dateAdder(bond.begin_date, bond.lockin_period).getTime())
return reject(INVALID(eCode.INSUFFICIENT_PERIOD, 'Bond still in lock-in period'));
getRate.BTC_USD().then(btc_rate => {
getRate.USD_INR().then(usd_rate => {
let end_date = new Date(),
net_value = blockchainBond.calcNetValue(bond.btc_base, btc_rate, bond.begin_date, bond.min_ipa, bond.max_period, bond.gain_cut, bond.amount_in, bond.usd_base, usd_rate);
DB.query("INSERT INTO CloseBondTransact(bond_id, amount, end_date, btc_net, usd_net, ref_sign, status) VALUE ?", [[bond_id, net_value, end_date, btc_rate, usd_rate, ref, "PENDING"]])
.then(result => resolve({ "USD_net": usd_rate, "BTC_net": btc_rate, "amount_out": net_value, "end_date": end_date }))
.catch(error => reject(error))
}).catch(error => reject(error))
}).catch(error => reject(error))
}).catch(error => reject(error))
}).catch(error => reject(error))
})
}
module.exports = {
refresh: refreshBlockchainData,
set DB(db) {
DB = db;
},
util: blockchainBond,
closeBond
}