diff --git a/docs/scripts/floBlockchainAPI.js b/docs/scripts/floBlockchainAPI.js index 5bb3cbb..473b192 100644 --- a/docs/scripts/floBlockchainAPI.js +++ b/docs/scripts/floBlockchainAPI.js @@ -1,4 +1,4 @@ -(function (EXPORTS) { //floBlockchainAPI v2.3.3c +(function (EXPORTS) { //floBlockchainAPI v2.3.3d /* FLO Blockchain Operator to send/receive data from blockchain using API calls*/ 'use strict'; const floBlockchainAPI = EXPORTS; @@ -485,8 +485,8 @@ floBlockchainAPI.readData = function (addr, options = {}) { options.limit = options.limit || 0; options.ignoreOld = options.ignoreOld || 0; - if (typeof options.sender === "string") options.sender = [options.sender]; - if (typeof options.receiver === "string") options.receiver = [options.receiver]; + if (typeof options.senders === "string") options.senders = [options.senders]; + if (typeof options.receivers === "string") options.receivers = [options.receivers]; return new Promise((resolve, reject) => { promisedAPI(`api/addrs/${addr}/txs?from=0&to=1`).then(response => { var newItems = response.totalItems - options.ignoreOld; @@ -521,10 +521,10 @@ } if (!flag) continue; } - if (Array.isArray(options.sender)) { + if (Array.isArray(options.senders)) { let flag = false; for (let vin of response.items[i].vin) - if (options.sender.includes(vin.addr)) { + if (options.senders.includes(vin.addr)) { flag = true; break; } @@ -539,10 +539,10 @@ } if (!flag) continue; } - if (Array.isArray(options.receiver)) { + if (Array.isArray(options.receivers)) { let flag = false; for (let vout of response.items[i].vout) - if (options.receiver.includes(vout.scriptPubKey.addresses[0])) { + if (options.receivers.includes(vout.scriptPubKey.addresses[0])) { flag = true; break; } diff --git a/src/background.js b/src/background.js index 1623842..8aad551 100644 --- a/src/background.js +++ b/src/background.js @@ -417,7 +417,7 @@ function confirmFundClosing() { let closeFundString = fund_util.stringify.end(r.fund_id, r.floID, r.end_date, r.btc_net, r.usd_net, r.amount, r.ref_sign, r.txid); floBlockchainAPI.writeData(global.myFloID, closeFundString, global.myPrivKey, fund_util.config.adminID).then(txid => { DB.query("UPDATE CloseFundTransact SET r_status=?, close_id=? WHERE id=?", [pCode.STATUS_SUCCESS, txid, r.id]) - .then(result => console.info("Fund investment closed:", r.fund_id)) + .then(result => console.info("Fund investment closed:", r.fund_id, r.floID)) .catch(error => console.error(error)); }).catch(error => console.error(error)) }).catch(error => console.error(error)); diff --git a/src/request.js b/src/request.js index b844dd7..71e3f34 100644 --- a/src/request.js +++ b/src/request.js @@ -387,7 +387,7 @@ function CloseBlockchainBond(req, res) { 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", { + processRequest(res, data.floID, data.pubKey, data.sign, "Blockchain Bond Closing", { type: "close_blockchain_bond", bond_id: data.bond_id, timestamp: data.timestamp @@ -399,7 +399,7 @@ function CloseBobsFund(req, res) { 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", { + processRequest(res, data.floID, data.pubKey, data.sign, "Bob's Fund closing", { type: "close_bobs_fund", fund_id: data.fund_id, timestamp: data.timestamp @@ -631,7 +631,7 @@ module.exports = { }, refreshData(nodeList) { blockchain_bonds.refresh(nodeList); - bobs_fund.refresh(nodeList) + bobs_fund.refresh(nodeList); }, pause() { serving = false; diff --git a/src/services/bobs-fund.js b/src/services/bobs-fund.js index d2171d0..f71a74e 100644 --- a/src/services/bobs-fund.js +++ b/src/services/bobs-fund.js @@ -96,97 +96,97 @@ const bobsFund = (function () { return result.join("|"); } - function stringify_continue(fundID, investments) { + function stringify_continue(fund_id, investments) { return [ `${productStr}`, - `continue: ${fundID}`, + `continue: ${fund_id}`, `Investment(s) (INR): ${investments.map(f => `${f[0].trim()}-${f[1].trim()}`).join("; ")}` ].join("|"); } - function parse_main(data) { - let funds = {}; - if (!Array.isArray(data)) - data = [data]; - data.forEach(fd => { - let cont = /continue: [a-z0-9]{64}\|/.test(fd); - fd.data.split("|").forEach(d => { - d = d.split(': '); - switch (d[0].toLowerCase()) { - case "start date": - cont ? null : funds["start_date"] = d[1]; break; - case "base value": - cont ? null : funds["BTC_base"] = parseNumber(d[1].slice(0, -4)); break; - case "usd inr rate at start": - cont ? null : funds["USD_base"] = parseFloat(d[1]); break; - case "duration": - cont ? null : funds["duration"] = parsePeriod(d[1]); break; - case "management fee": - cont ? null : funds["fee"] = parseFloat(d[1]); break; - case "tapout availability": - let x = d[1].toLowerCase().split("after") - funds["tapoutInterval"] = x[1].match(/\d+ [a-z]+/gi).map(y => parsePeriod(y)) - funds["topoutWindow"] = parsePeriod(x[0]); break; - case "invesment(s) (inr)": - case "investment(s) (inr)": - funds["amounts"] = funds["amounts"] || []; - funds["amounts"].push(d[1].split("; ").map(a => { - a = a.split("-"); - return [a[0], parseNumber(a[1])] - })); break; - } - }); - }) - return funds; - } - function stringify_end(fund_id, floID, end_date, BTC_net, USD_net, amount, ref_sign, payment_ref) { return [ `${productStr}`, - `Fund: ${fund_id}`, + `close: ${fund_id}`, `Investor: ${floID}`, `End value: ${BTC_net} USD`, - `Date of withdrawal: ${end_date}`, + `Date of withdrawal: ${dateFormat(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 fund) send by market nodes - let details = {}; - data.split("|").forEach(d => { - d = d.split(': '); - switch (d[0].toLowerCase()) { - case "fund": - details["fundID"] = d[1]; break; - case "investor": - details["floID"] = d[1]; break; - case "end value": - details["BTC_net"] = parseNumber(d[1].slice(0, -4)); break; - case "date of withdrawal": - 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; + function parse_details(data) { + let funds = {}; + funds.investments = {}; + if (!Array.isArray(data)) + data = [data]; + data.forEach(fd => { + if (!/close: [a-z0-9]{64}\|/.test(fd)) { // not a closing tx + let cont = /continue: [a-z0-9]{64}\|/.test(fd); + fd.split("|").forEach(d => { + d = d.split(': '); + if (["invesment(s) (inr)", "investment(s) (inr)"].includes(d[0].toLowerCase())) + d[1].split(";").forEach(a => { + a = a.split("-"); + let floID = a[0].replace(/\s/g, ''); //for removing spaces (trailing) if any + funds["investments"][floID] = funds["investments"][floID] || {}; + funds["investments"][floID].amount = parseNumber(a[1]) + }); + else if (!cont) + switch (d[0].toLowerCase()) { + case "start date": + funds["start_date"] = new Date(d[1]); break; + case "base value": + funds["BTC_base"] = parseNumber(d[1].slice(0, -4)); break; + case "usd inr rate at start": + funds["USD_base"] = parseFloat(d[1]); break; + case "duration": + funds["duration"] = parsePeriod(d[1]); break; + case "management fee": + funds["fee"] = parseFloat(d[1]); break; + case "tapout availability": + let x = d[1].toLowerCase().split("after") + funds["tapoutInterval"] = x[1].match(/\d+ [a-z]+/gi).map(y => parsePeriod(y)) + funds["topoutWindow"] = parsePeriod(x[0]); break; + } + }); + } else { + let floID, details = {}; + fd.split("|").forEach(d => { + d = d.split(': '); + switch (d[0].toLowerCase()) { + case "investor": + floID = d[1]; break; + case "end value": + details["BTC_net"] = parseNumber(d[1].slice(0, -4)); break; + case "date of withdrawal": + 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; + } + }); + if (floID) { + funds.investments[floID] = funds.investments[floID] || {}; + funds.investments[floID].closed = details; + } } - }) + }); + return funds; } - return { + productStr, dateAdder, dateFormat, calcNetValue, - parse: { - main: parse_main, - end: parse_end - }, + parse: parse_details, stringify: { main: stringify_main, continue: stringify_continue, @@ -207,14 +207,14 @@ function refreshBlockchainData(nodeList = []) { let lastTx = result.length ? result[0].num : 0; floBlockchainAPI.readData(bobsFund.config.adminID, { ignoreOld: lastTx, - senders: [nodeList].concat(bobsFund.config.adminID), //sentOnly: true, + senders: nodeList.concat(bobsFund.config.adminID), //sentOnly: true, tx: true, - filter: d => d.startsWith(bobsFund.config.productStr) + filter: d => d.startsWith(bobsFund.productStr) }).then(result => { let promises = []; - result.data.forEach(d => { - let fund = d.senders.has(bobsFund.config.adminID) ? bobsFund.parse.main(d.data) : null; - if (fund && fund.amount) { + result.data.reverse().forEach(d => { + let fund = bobsFund.parse(d.data); + if (d.senders.has(bobsFund.config.adminID) && !/close:/.test(d.data)) { let fund_id = d.data.match(/continue: [a-z0-9]{64}\|/); if (!fund_id) { fund_id = d.txid; @@ -224,21 +224,27 @@ function refreshBlockchainData(nodeList = []) { 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]])); } else fund_id = fund_id.pop().match(/[a-z0-9]{64}/).pop(); - let investments = fund.amounts.map(i => [fund_id, i[0], i[1]]); - promises.push(DB.query("INSERT INTO BobsFundInvestments(fund_id, floID, amount_in) VALUES ?", [investments])); + 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])); } else { - let details = bobsFund.parse.end(d.data); - if (details.fundID && details.floID && details.amountFinal) - promises.push(DB.query("UPDATE BobsFundInvestments SET close_id=? amount_out=? WHERE fund_id=? AND floID=?", [d.txid, details.amountFinal, details.fundID, details.floID])); + let fund_id = d.data.match(/close: [a-z0-9]{64}\|/); + if (fund_id) { + 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]])) + } } }); - promises.push(DB.query("INSERT INTO LastTx (floID, num) VALUE (?) ON DUPLICATE KEY UPDATE num=?", [[bobsFund.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 fund data might not have been saved in database correctly"); - resolve(result.totalTxs); + 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)); }) }).catch(error => reject(error)) }).catch(error => reject(error)) @@ -247,9 +253,9 @@ function refreshBlockchainData(nodeList = []) { function closeFund(fund_id, floID, ref) { return new Promise((resolve, reject) => { - DB.query("SELECT r_status FROM CloseFundTransact WHERE fund_id=?", [fund_id]).then(result => { + DB.query("SELECT r_status, close_id FROM CloseFundTransact WHERE fund_id=?", [fund_id]).then(result => { if (result.length) - return reject(INVALID(eCode.DUPLICATE_ENTRY, `Fund closing already in process`)); + return reject(INVALID(eCode.DUPLICATE_ENTRY, result[0].r_status == pCode.STATUS_SUCCESS ? `Fund investment already closed (${result[0].close_id})` : `Fund closing already in process`)); DB.query("SELECT * FROM BobsFund WHERE fund_id=?", [fund_id]).then(result => { if (!result.length) return reject(INVALID(eCode.NOT_FOUND, 'Fund not found')); @@ -279,7 +285,7 @@ function closeFund(fund_id, floID, ref) { getRate.BTC_USD().then(btc_rate => { getRate.USD_INR().then(usd_rate => { let net_value = bobsFund.calcNetValue(fund.btc_base, btc_rate, fund.usd_base, usd_rate, investment.amount_in, fund.fee) - DB.query("INSERT INTO CloseFundTransact(fund_id, floID, amount, end_date, btc_net, usd_net, ref_sign, r_status) VALUE ?", [[fund_id, floID, net_value, cur_date, btc_rate, usd_rate, ref, pCode.STATUS_PENDING]]) + DB.query("INSERT INTO CloseFundTransact(fund_id, floID, amount, end_date, btc_net, usd_net, ref_sign, r_status) VALUE (?)", [[fund_id, floID, net_value, cur_date, btc_rate, usd_rate, ref, pCode.STATUS_PENDING]]) .then(result => resolve({ "USD_net": usd_rate, "BTC_net": btc_rate, "amount_out": net_value, "end_date": cur_date })) .catch(error => reject(error)) }).catch(error => reject(error)) @@ -291,7 +297,11 @@ function closeFund(fund_id, floID, ref) { } module.exports = { - refresh: refreshBlockchainData, + refresh(nodeList) { + refreshBlockchainData(nodeList) + .then(result => console.debug("Refreshed Bob's Fund data")) + .catch(error => console.error(error)); + }, set DB(db) { DB = db; }, diff --git a/src/services/bonds.js b/src/services/bonds.js index 9f34068..7808ea4 100644 --- a/src/services/bonds.js +++ b/src/services/bonds.js @@ -88,7 +88,7 @@ const blockchainBond = (function () { return [ `${productStr}`, `Base value: ${BTC_base} USD`, - `Date of bond start: ${start_date}`, + `Date of bond start: ${dateFormat(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}`, @@ -122,7 +122,7 @@ const blockchainBond = (function () { case "flo id of bond holder": details["floID"] = d[1]; break; } - }) + }); return details; } @@ -131,7 +131,7 @@ const blockchainBond = (function () { `${productStr}`, `Bond: ${bond_id}`, `End value: ${BTC_net} USD`, - `Date of bond end: ${end_date}`, + `Date of bond end: ${dateFormat(end_date)}`, `USD INR rate at end: ${USD_net}`, `Amount withdrawn: Rs ${amount} via ${payment_ref}`, `Reference: ${ref_sign}` @@ -158,7 +158,8 @@ const blockchainBond = (function () { case "reference": details["refSign"] = d[1]; break; } - }) + }); + return details; } return { @@ -189,12 +190,12 @@ function refreshBlockchainData(nodeList = []) { let lastTx = result.length ? result[0].num : 0; floBlockchainAPI.readData(blockchainBond.config.adminID, { ignoreOld: lastTx, - senders: [nodeList].concat(blockchainBond.config.adminID), //sentOnly: true, + senders: nodeList.concat(blockchainBond.config.adminID), //sentOnly: true, tx: true, filter: d => d.startsWith(blockchainBond.productStr) }).then(result => { let promises = []; - result.data.forEach(d => { + 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", @@ -202,15 +203,17 @@ function refreshBlockchainData(nodeList = []) { 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("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); + reject("Some bond data might not have been saved in database correctly"); + 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)); }) }).catch(error => reject(error)) }).catch(error => reject(error)) @@ -219,9 +222,9 @@ function refreshBlockchainData(nodeList = []) { function closeBond(bond_id, floID, ref) { return new Promise((resolve, reject) => { - DB.query("SELECT r_status FROM CloseBondTransact WHERE bond_id=?", [bond_id]).then(result => { + DB.query("SELECT r_status, close_id FROM CloseBondTransact WHERE bond_id=?", [bond_id]).then(result => { if (result.length) - return reject(INVALID(eCode.DUPLICATE_ENTRY, `Bond closing already in process`)); + return reject(INVALID(eCode.DUPLICATE_ENTRY, result[0].r_status == pCode.STATUS_SUCCESS ? `Bond already closed (${result[0].close_id})` : `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')); @@ -236,7 +239,7 @@ function closeBond(bond_id, floID, ref) { 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, floID, amount, end_date, btc_net, usd_net, ref_sign, r_status) VALUE ?", [[bond_id, floID, net_value, end_date, btc_rate, usd_rate, ref, pCode.STATUS_PENDING]]) + DB.query("INSERT INTO CloseBondTransact(bond_id, floID, amount, end_date, btc_net, usd_net, ref_sign, r_status) VALUE (?)", [[bond_id, floID, net_value, end_date, btc_rate, usd_rate, ref, pCode.STATUS_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)) @@ -247,7 +250,11 @@ function closeBond(bond_id, floID, ref) { } module.exports = { - refresh: refreshBlockchainData, + refresh(nodeList) { + refreshBlockchainData(nodeList) + .then(result => console.debug("Refreshed Blockchain-bonds data")) + .catch(error => console.error(error)); + }, set DB(db) { DB = db; },