Blockchain-bonds and Bobs-fund balance check API

- Added API to check the balance and closing-amount needed for blockchain-bonds and bobs fund (checkBlockchainBond, checkBobsFund) in floExchangeAPI.
- API resolves values in
  .pending: bond/fund closed by user, but not yet finished processing
  .ready: bond/fund ready to close by users
  .upcoming: bond/fund that will be out of lockin period (by given prior_time parameter)

- Fixed: Bonds and Funds closing data not written to DB due to async
- Fixed: floExchangeAPI not reading trusted-id list from blockchain
- Added floExchangeAPI.config: getters for trustedList, assetList and tagList
This commit is contained in:
sairajzero 2022-11-17 17:52:42 +05:30
parent 28e4815636
commit 65c0d3c91f
5 changed files with 246 additions and 33 deletions

View File

@ -1527,6 +1527,66 @@
})
}
exchangeAPI.checkBlockchainBond = function (prior_time, floID, proxySecret) {
return new Promise((resolve, reject) => {
let request = {
floID: floID,
prior_time,
timestamp: Date.now()
};
if (floCrypto.getFloID(proxySecret) === floID) //Direct signing (without proxy)
request.pubKey = floCrypto.getPubKeyHex(proxySecret);
request.sign = signRequest({
type: "check_blockchain_bond",
prior_time,
timestamp: request.timestamp
}, proxySecret);
console.debug(request);
fetch_api('/check-blockchain-bond', {
method: "POST",
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(request)
}).then(result => {
responseParse(result)
.then(result => resolve(result))
.catch(error => reject(error))
}).catch(error => reject(error))
})
}
exchangeAPI.checkBobsFund = function (prior_time, floID, proxySecret) {
return new Promise((resolve, reject) => {
let request = {
floID: floID,
prior_time,
timestamp: Date.now()
};
if (floCrypto.getFloID(proxySecret) === floID) //Direct signing (without proxy)
request.pubKey = floCrypto.getPubKeyHex(proxySecret);
request.sign = signRequest({
type: "check_bobs_fund",
prior_time,
timestamp: request.timestamp
}, proxySecret);
console.debug(request);
fetch_api('/check-bobs-fund', {
method: "POST",
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(request)
}).then(result => {
responseParse(result)
.then(result => resolve(result))
.catch(error => reject(error))
}).catch(error => reject(error))
})
}
exchangeAPI.closeBobsFundInvestment = function (fund_id, floID, privKey) {
return new Promise((resolve, reject) => {
if (!floCrypto.verifyPrivKey(privKey, floID))
@ -1660,9 +1720,10 @@
exchangeAPI.init = function refreshDataFromBlockchain() {
return new Promise((resolve, reject) => {
let nodes, assets, tags, lastTx;
let nodes, trusted = new Set(), assets, tags, lastTx;
try {
nodes = JSON.parse(localStorage.getItem('exchange-nodes'));
trusted = new Set((localStorage.getItem('exchange-trusted') || "").split(','));
assets = new Set((localStorage.getItem('exchange-assets') || "").split(','));
tags = new Set((localStorage.getItem('exchange-tags') || "").split(','));
if (typeof nodes !== 'object' || nodes === null)
@ -1671,6 +1732,7 @@
lastTx = parseInt(localStorage.getItem('exchange-lastTx')) || 0;
} catch (error) {
nodes = {};
trusted = new Set();
assets = new Set();
tags = new Set();
lastTx = 0;
@ -1691,6 +1753,15 @@
for (let n in content.Nodes.add)
nodes[n] = content.Nodes.add[n];
}
//Trusted List
if (content.Trusted) {
if (content.Trusted.remove)
for (let id of content.Trusted.remove)
trusted.delete(id);
if (content.Trusted.add)
for (let id of content.Trusted.add)
trusted.add(id);
}
//Asset List
if (content.Assets) {
for (let a in content.Assets)
@ -1708,6 +1779,7 @@
});
localStorage.setItem('exchange-lastTx', result.totalTxs);
localStorage.setItem('exchange-nodes', JSON.stringify(nodes));
localStorage.setItem('exchange-trusted', Array.from(trusted).join(","));
localStorage.setItem('exchange-assets', Array.from(assets).join(","));
localStorage.setItem('exchange-tags', Array.from(tags).join(","));
nodeURL = nodes;
@ -1718,8 +1790,21 @@
})
}
const config = exchangeAPI.config = {
get trustedList() {
return new Set((localStorage.getItem('exchange-trusted') || "").split(','));
},
get assetList() {
return new Set((localStorage.getItem('exchange-assets') || "").split(','));
},
get tagList() {
return new Set((localStorage.getItem('exchange-tags') || "").split(','));
}
}
exchangeAPI.clearAllLocalData = function () {
localStorage.removeItem('exchange-nodes');
localStorage.removeItem('exchange-trusted');
localStorage.removeItem('exchange-assets');
localStorage.removeItem('exchange-tags');
localStorage.removeItem('exchange-lastTx');

View File

@ -102,11 +102,15 @@ module.exports = function App(secret) {
app.post('/withdraw-convert-coin-fund', Request.WithdrawConvertCoinFund);
app.post('/withdraw-convert-currency-fund', Request.WithdrawConvertCurrencyFund);
//close blockchain-bond
//close blockchain-bond and bobs-fund-investment
app.post('/close-blockchain-bonds', Request.CloseBlockchainBond);
app.post('/close-bobs-fund-investment', Request.CloseBobsFund);
//Manage user tags (Access to trusted IDs only)
//check balance for blockchain-bond and bobs-fund (trusted IDs only)
app.post('/check-blockchain-bond', Request.CheckBlockchainBondBalance);
app.post('/check-bobs-fund', Request.CheckBobsFundBalance);
//Manage user tags (trusted IDs only)
app.post('/add-tag', Request.AddUserTag);
app.post('/remove-tag', Request.RemoveUserTag);
app.post('/add-distributor', Request.AddDistributor);

View File

@ -104,10 +104,10 @@ function logRequest(floID, req_str, sign, proxy = false) {
.then(_ => null).catch(error => console.error(error));
}
function processRequest(res, floID, pubKey, sign, rText, validateObj, marketFn) {
function processRequest(res, floID, pubKey, sign, rText, validateObj, marketFn, log = true) {
validateRequest(validateObj, sign, floID, pubKey).then(req_str => {
marketFn().then(result => {
logRequest(floID, req_str, sign, !pubKey);
if (log) logRequest(floID, req_str, sign, !pubKey);
res.send(result);
}).catch(error => {
if (error instanceof INVALID)
@ -452,6 +452,28 @@ function CloseBobsFund(req, res) {
}, () => bobs_fund.closeFund(data.fund_id, data.floID, `${data.timestamp}.${data.sign}`));
}
function CheckBlockchainBondBalance(req, res) {
let data = req.body;
if (!trustedIDs.includes(data.floID))
res.status(INVALID.e_code).send(INVALID.str(eCode.ACCESS_DENIED, "Access Denied"));
else processRequest(res, data.floID, data.pubKey, data.sign, "Check blockchain-bond", {
type: "check_blockchain_bond",
prior_time: data.prior_time,
timestamp: data.timestamp
}, () => blockchain_bonds.checkBondBalance(data.prior_time), false);
}
function CheckBobsFundBalance(req, res) {
let data = req.body;
if (!trustedIDs.includes(data.floID))
res.status(INVALID.e_code).send(INVALID.str(eCode.ACCESS_DENIED, "Access Denied"));
else processRequest(res, data.floID, data.pubKey, data.sign, "Check bobs-fund", {
type: "check_bobs_fund",
prior_time: data.prior_time,
timestamp: data.timestamp
}, () => bobs_fund.checkFundBalance(data.prior_time), false);
}
/* Public Requests */
function GetLoginCode(req, res) {
@ -695,6 +717,8 @@ module.exports = {
WithdrawConvertCurrencyFund,
CloseBlockchainBond,
CloseBobsFund,
CheckBlockchainBondBalance,
CheckBobsFundBalance,
set trustedIDs(ids) {
trustedIDs = ids;
},

View File

@ -1,6 +1,7 @@
'use strict';
const DB = require("../database");
const { sink_chest, sink_groups } = require("../keys");
const eCode = require('../../docs/scripts/floExchangeAPI').errorCode;
const pCode = require('../../docs/scripts/floExchangeAPI').processCode;
const getRate = require('./conversion').getRate;
@ -210,7 +211,7 @@ function refreshBlockchainData(nodeList = []) {
tx: true,
filter: d => d.startsWith(bobsFund.productStr)
}).then(result => {
let promises = [];
let txQueries = [];
result.data.reverse().forEach(d => {
let fund = bobsFund.parse(d.data);
if (d.senders.has(bobsFund.config.adminID) && !/close:/.test(d.data)) {
@ -220,11 +221,11 @@ function refreshBlockchainData(nodeList = []) {
let values = [fund_id, fund.start_date, fund.BTC_base, fund.USD_base, fund.fee, fund.duration];
if (fund.tapoutInterval)
values.push(fund.topoutWindow, fund.tapoutInterval.join(','));
promises.push(DB.query(`INSERT INTO BobsFund(fund_id, begin_date, btc_base, usd_base, fee, duration ${fund.tapoutInterval ? ", tapout_window, tapout_interval" : ""}) VALUES ? ON DUPLICATE KEY UPDATE fund_id=fund_id`, [[values]]));
txQueries.push([`INSERT INTO BobsFund(fund_id, begin_date, btc_base, usd_base, fee, duration ${fund.tapoutInterval ? ", tapout_window, tapout_interval" : ""}) VALUE (?) ON DUPLICATE KEY UPDATE fund_id=fund_id`, [values]])
} else
fund_id = fund_id.pop().match(/[a-z0-9]{64}/).pop();
let investments = Object.entries(fund.investments).map(a => [fund_id, a[0], a[1].amount]);
promises.push(DB.query("INSERT INTO BobsFundInvestments(fund_id, floID, amount_in) VALUES ? ON DUPLICATE KEY UPDATE floID=floID", [investments]));
txQueries.push(["INSERT INTO BobsFundInvestments(fund_id, floID, amount_in) VALUES ? ON DUPLICATE KEY UPDATE floID=floID", [investments]]);
}
else {
let fund_id = d.data.match(/close: [a-z0-9]{64}\|/);
@ -232,20 +233,16 @@ function refreshBlockchainData(nodeList = []) {
fund_id = fund_id.pop().match(/[a-z0-9]{64}/).pop();
let closing_details = Object.entries(fund.investments).filter(a => typeof a[1].closed === "object" && a[1].closed.amountFinal).pop(); //only one close-fund will be there in a tx
if (closing_details)
promises.push(DB.query("UPDATE BobsFundInvestments SET close_id=?, amount_out=? WHERE fund_id=? AND floID=?", [d.txid, closing_details[1].closed.amountFinal, fund_id, closing_details[0]]))
txQueries.push(["UPDATE BobsFundInvestments SET close_id=?, amount_out=? WHERE fund_id=? AND floID=?",
[d.txid, closing_details[1].closed.amountFinal, fund_id, closing_details[0]]])
}
}
});
Promise.allSettled(promises).then(results => {
if (results.reduce((a, r) => r.status === "rejected" ? ++a : a, 0)) {
console.debug(results.filter(r => r.status === "rejected"));
reject("Some fund data might not have been saved in database correctly");
}
else
DB.query("INSERT INTO LastTx (floID, num) VALUE (?) ON DUPLICATE KEY UPDATE num=?", [[bobsFund.config.adminID, result.totalTxs], result.totalTxs])
.then(_ => resolve(result.totalTxs))
.catch(error => reject(error));
})
txQueries.push(["INSERT INTO LastTx (floID, num) VALUE (?) ON DUPLICATE KEY UPDATE num=?",
[[bobsFund.config.adminID, result.totalTxs], result.totalTxs]])
DB.transaction(txQueries)
.then(_ => resolve(result.totalTxs))
.catch(error => reject(["Bobs-Fund refresh data failed!", error]));
}).catch(error => reject(error))
}).catch(error => reject(error))
})
@ -296,6 +293,59 @@ function closeFund(fund_id, floID, ref) {
})
}
function checkFundBalance(prior_time) {
return new Promise((resolve, reject) => {
prior_time = new Date(prior_time);
let cur_date = Date.now();
if (isNaN(prior_time) || prior_time.toString() == "Invalid Date")
return reject(INVALID(eCode.INVALID_VALUE, `Invalid Date for prior_time`));
let sql_query = "SELECT bf.begin_date, bf.btc_base, bf.usd_base, bf.fee, bf.duration, fi.amount_in, cf.amount AS amount_close FROM BobsFund AS bf" +
" INNER JOIN BobsFundInvestments AS fi ON bf.fund_id = fi.fund_id" +
" LEFT JOIN CloseFundTransact AS cf ON fi.fund_id = cf.fund_id AND fi.floID = cf.floID" +
" WHERE fi.close_id IS NULL AND (cf.r_status IS NULL OR cf.r_status NOT IN (?))";
DB.query(sql_query, [[pCode.STATUS_SUCCESS, pCode.STATUS_CONFIRMATION]]).then(result => {
getRate.BTC_USD().then(btc_rate => {
getRate.USD_INR().then(usd_rate => {
let pending = { require_amount_cash: 0, n_investment: 0 },
ready = { require_amount_cash: 0, n_investment: 0 },
upcoming = { require_amount_cash: 0, n_investment: 0 }
result.forEach(i => {
if (i.amount_close) {
pending.require_amount_cash += i.amount_close;
pending.n_investment++;
} else {
let end_date = bobsFund.dateAdder(i.begin_date, i.duration);
if (end_date < prior_time) {
let net_value = bobsFund.calcNetValue(i.btc_base, btc_rate, i.usd_base, usd_rate, i.amount_in, i.fee);
if (end_date > cur_date) {
upcoming.require_amount_cash += net_value;
upcoming.n_investment++;
} else {
ready.require_amount_cash += net_value;
ready.n_investment++;
}
}
}
})
pending.require_amount_cash = global.toStandardDecimal(pending.require_amount_cash);
ready.require_amount_cash = global.toStandardDecimal(ready.require_amount_cash);
upcoming.require_amount_cash = global.toStandardDecimal(upcoming.require_amount_cash);
pending.require_amount_btc = global.toStandardDecimal(pending.require_amount_cash / (btc_rate * usd_rate));
ready.require_amount_btc = global.toStandardDecimal(ready.require_amount_cash / (btc_rate * usd_rate));
upcoming.require_amount_btc = global.toStandardDecimal(upcoming.require_amount_cash / (btc_rate * usd_rate));
Promise.allSettled(sink_chest.list(sink_groups.BOBS_FUND)
.map(id => btcOperator.getBalance(btcOperator.convert.legacy2bech(id)))).then(result => {
let balance = result.filter(r => r.status === 'fulfilled').reduce((a, bal) => a += bal, 0);
resolve({ pending, ready, upcoming, balance });
}).catch(error => reject(error))
}).catch(error => reject(error))
}).catch(error => reject(error))
}).catch(error => reject(error))
})
}
module.exports = {
refresh(nodeList) {
refreshBlockchainData(nodeList)
@ -303,5 +353,6 @@ module.exports = {
.catch(error => console.error(error));
},
util: bobsFund,
checkFundBalance,
closeFund
}

View File

@ -1,6 +1,7 @@
'use strict';
const DB = require("../database");
const { sink_chest, sink_groups } = require("../keys");
const eCode = require('../../docs/scripts/floExchangeAPI').errorCode;
const pCode = require('../../docs/scripts/floExchangeAPI').processCode;
const getRate = require('./conversion').getRate;
@ -193,28 +194,24 @@ function refreshBlockchainData(nodeList = []) {
tx: true,
filter: d => d.startsWith(blockchainBond.productStr)
}).then(result => {
let promises = [];
let txQueries = [];
result.data.reverse().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]]]));
txQueries.push(["INSERT INTO BlockchainBonds(bond_id, floID, amount_in, begin_date, btc_base, usd_base, gain_cut, min_ipa, max_period, lockin_period) VALUE (?) 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]));
txQueries.push(["UPDATE BlockchainBonds SET close_id=?, amount_out=? WHERE bond_id=?",
[d.txid, details.amountFinal, details.bondID]]);
}
});
Promise.allSettled(promises).then(results => {
if (results.reduce((a, r) => r.status === "rejected" ? ++a : a, 0)) {
reject("Some bond data might not have been saved in database correctly");
console.debug(results.filter(r => r.status === "rejected"));
}
else
DB.query("INSERT INTO LastTx (floID, num) VALUE (?) ON DUPLICATE KEY UPDATE num=?", [[blockchainBond.config.adminID, result.totalTxs], result.totalTxs])
.then(_ => resolve(result.totalTxs))
.catch(error => reject(error));
})
txQueries.push(["INSERT INTO LastTx (floID, num) VALUE (?) ON DUPLICATE KEY UPDATE num=?",
[[blockchainBond.config.adminID, result.totalTxs], result.totalTxs]])
DB.transaction(txQueries)
.then(_ => resolve(result.totalTxs))
.catch(error => reject(["Blockchain-bonds refresh data failed!", error]));
}).catch(error => reject(error))
}).catch(error => reject(error))
})
@ -249,6 +246,57 @@ function closeBond(bond_id, floID, ref) {
})
}
function checkBondBalance(prior_time) {
return new Promise((resolve, reject) => {
prior_time = new Date(prior_time);
let cur_date = Date.now();
if (isNaN(prior_time) || prior_time.toString() == "Invalid Date")
return reject(INVALID(eCode.INVALID_VALUE, `Invalid Date for prior_time`));
let sql_query = "SELECT bb.*, cb.amount AS amount_close FROM BlockchainBonds AS bb" +
" LEFT JOIN CloseBondTransact AS cb ON bb.bond_id = cb.bond_id" +
" WHERE bb.close_id IS NULL AND (cb.r_status IS NULL OR cb.r_status NOT IN (?))";
DB.query(sql_query, [[pCode.STATUS_SUCCESS, pCode.STATUS_CONFIRMATION]]).then(result => {
getRate.BTC_USD().then(btc_rate => {
getRate.USD_INR().then(usd_rate => {
let pending = { require_amount_cash: 0, n_bond: 0 },
ready = { require_amount_cash: 0, n_bond: 0 },
upcoming = { require_amount_cash: 0, n_bond: 0 }
result.forEach(bond => {
if (bond.amount_close) {
pending.require_amount_cash += bond.amount_close;
pending.n_bond++;
} else {
let end_date = blockchainBond.dateAdder(bond.begin_date, bond.lockin_period)
if (end_date < prior_time) {
let 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);
if (end_date > cur_date) {
upcoming.require_amount_cash += net_value;
upcoming.n_bond++;
} else {
ready.require_amount_cash += net_value;
ready.n_bond++;
}
}
}
});
pending.require_amount_cash = global.toStandardDecimal(pending.require_amount_cash);
ready.require_amount_cash = global.toStandardDecimal(ready.require_amount_cash);
upcoming.require_amount_cash = global.toStandardDecimal(upcoming.require_amount_cash);
pending.require_amount_btc = global.toStandardDecimal(pending.require_amount_cash / (btc_rate * usd_rate));
ready.require_amount_btc = global.toStandardDecimal(ready.require_amount_cash / (btc_rate * usd_rate));
upcoming.require_amount_btc = global.toStandardDecimal(upcoming.require_amount_cash / (btc_rate * usd_rate));
Promise.allSettled(sink_chest.list(sink_groups.BLOCKCHAIN_BONDS)
.map(id => btcOperator.getBalance(btcOperator.convert.legacy2bech(id)))).then(result => {
let balance = result.filter(r => r.status === 'fulfilled').reduce((a, bal) => a += bal, 0);
resolve({ pending, ready, upcoming, balance });
}).catch(error => reject(error))
}).catch(error => reject(error))
}).catch(error => reject(error))
}).catch(error => reject(error))
})
}
module.exports = {
refresh(nodeList) {
refreshBlockchainData(nodeList)
@ -256,5 +304,6 @@ module.exports = {
.catch(error => console.error(error));
},
util: blockchainBond,
checkBondBalance,
closeBond
}