From 456e9097de6d802fae40edc9f8e0cb1732d02641 Mon Sep 17 00:00:00 2001 From: sairajzero Date: Sat, 29 Apr 2023 03:03:45 +0530 Subject: [PATCH 01/13] floBlockchainAPI v2.5.2: Added splitUTXOs - splitUTXOs: split sufficient UTXOs of a given floID for a parallel sending --- floBlockchainAPI.js | 52 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/floBlockchainAPI.js b/floBlockchainAPI.js index 4b13f39..cce86b3 100644 --- a/floBlockchainAPI.js +++ b/floBlockchainAPI.js @@ -1,4 +1,4 @@ -(function (EXPORTS) { //floBlockchainAPI v2.5.1 +(function (EXPORTS) { //floBlockchainAPI v2.5.2 /* FLO Blockchain Operator to send/receive data from blockchain using API calls*/ 'use strict'; const floBlockchainAPI = EXPORTS; @@ -273,6 +273,56 @@ }) } + //split sufficient UTXOs of a given floID for a parallel sending + floBlockchainAPI.splitUTXOs = function (floID, privKey, count, floData = '') { + return new Promise((resolve, reject) => { + if (!floCrypto.validateFloID(floID, true)) + return reject(`Invalid floID`); + if (!floCrypto.verifyPrivKey(privKey, floID)) + return reject("Invalid Private Key"); + if (!floCrypto.validateASCII(floData)) + return reject("Invalid FLO_Data: only printable ASCII characters are allowed"); + var fee = DEFAULT.fee; + var splitAmt = DEFAULT.sendAmt + fee; + var totalAmt = splitAmt * count; + getBalance(floID).then(balance => { + var fee = DEFAULT.fee; + if (balance < totalAmt + fee) + return reject("Insufficient FLO balance!"); + //get unconfirmed tx list + getUnconfirmedSpent(senderAddr).then(unconfirmedSpent => { + getUTXOs(senderAddr).then(utxos => { + var trx = bitjs.transaction(); + var utxoAmt = 0.0; + for (let i = utxos.length - 1; (i >= 0) && (utxoAmt < totalAmt + fee); i--) { + //use only utxos with confirmations (strict_utxo mode) + if (utxos[i].confirmations || !strict_utxo) { + if (utxos[i].txid in unconfirmedSpent && unconfirmedSpent[utxos[i].txid].includes(utxos[i].vout)) + continue; //A transaction has already used the utxo, but is unconfirmed. + trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey); + utxoAmt += utxos[i].amount; + }; + } + if (utxoAmt < totalAmt + fee) + reject("Insufficient FLO: Some UTXOs are unconfirmed"); + else { + for (let i = 0; i < count; i++) + trx.addoutput(floID, splitAmt); + var change = utxoAmt - totalAmt - fee; + if (change > DEFAULT.minChangeAmt) + trx.addoutput(floID, change); + trx.addflodata(floData.replace(/\n/g, ' ')); + var signedTxHash = trx.sign(privKey, 1); + broadcastTx(signedTxHash) + .then(txid => resolve(txid)) + .catch(error => reject(error)) + } + }).catch(error => reject(error)) + }).catch(error => reject(error)) + }).catch(error => reject(error)) + }) + } + /**Write data into blockchain from (and/or) to multiple floID * @param {Array} senderPrivKeys List of sender private-keys * @param {string} data FLO data of the txn From 7bd528d55ed0ce4d35eed25be0ecc29afae339ec Mon Sep 17 00:00:00 2001 From: sairajzero Date: Sat, 29 Apr 2023 03:22:38 +0530 Subject: [PATCH 02/13] floBlockchainAPI v2.5.3 - Added waitForConfirmation: Wait for the given txid to get confirmation in blockchain --- floBlockchainAPI.js | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/floBlockchainAPI.js b/floBlockchainAPI.js index cce86b3..35a3c3f 100644 --- a/floBlockchainAPI.js +++ b/floBlockchainAPI.js @@ -1,4 +1,4 @@ -(function (EXPORTS) { //floBlockchainAPI v2.5.2 +(function (EXPORTS) { //floBlockchainAPI v2.5.3 /* FLO Blockchain Operator to send/receive data from blockchain using API calls*/ 'use strict'; const floBlockchainAPI = EXPORTS; @@ -787,7 +787,7 @@ }) } - floBlockchainAPI.getTx = function (txid) { + const getTx = floBlockchainAPI.getTx = function (txid) { return new Promise((resolve, reject) => { promisedAPI(`api/tx/${txid}`) .then(response => resolve(response)) @@ -795,6 +795,33 @@ }) } + /**Wait for the given txid to get confirmation in blockchain + * @param {string} txid of the transaction to wait for + * @param {int} max_retry: maximum number of retries before exiting wait. negative number = Infinite retries (DEFAULT: -1 ie, infinite retries) + * @param {Array} retry_timeout: time (seconds) between retries (DEFAULT: 20 seconds) + * @return {Promise} resolves when tx gets confirmation + */ + const waitForConfirmation = floBlockchainAPI.waitForConfirmation = function (txid, max_retry = -1, retry_timeout = 20) { + return new Promise((resolve, reject) => { + setTimeout(function () { + getTx(txid).then(tx => { + if (!tx) + return reject("Transaction not found"); + if (tx.confirmations) + return resolve(tx); + else if (max_retry === 0) //no more retries + return reject("Waiting timeout: tx still not confirmed"); + else { + max_retry = max_retry < 0 ? -1 : max_retry - 1; //decrease retry count (unless infinite retries) + waitForConfirmation(txid, max_retry, retry_timeout) + .then(result => resolve(result)) + .catch(error => reject(error)) + } + }).catch(error => reject(error)) + }, retry_timeout * 1000) + }) + } + const isUndefined = val => typeof val === 'undefined'; //Read Txs of Address between from and to From fe33250b472e1b10ee11a48c5a95128bf30b6b60 Mon Sep 17 00:00:00 2001 From: sairajzero Date: Sat, 29 Apr 2023 03:28:33 +0530 Subject: [PATCH 03/13] floBlockchainAPI v2.5.4 - Adding support for 'before' and 'latest' option in readTxs and its derivatives --- floBlockchainAPI.js | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/floBlockchainAPI.js b/floBlockchainAPI.js index 35a3c3f..73cf7e3 100644 --- a/floBlockchainAPI.js +++ b/floBlockchainAPI.js @@ -1,4 +1,4 @@ -(function (EXPORTS) { //floBlockchainAPI v2.5.3 +(function (EXPORTS) { //floBlockchainAPI v2.5.4 /* FLO Blockchain Operator to send/receive data from blockchain using API calls*/ 'use strict'; const floBlockchainAPI = EXPORTS; @@ -830,14 +830,19 @@ let api = `api/addrs/${addr}/txs`; //API options let api_options = []; - if (!isUndefined(options.after)) - api_options.push(`after=${options.after}`); - else { + if (!isUndefined(options.after) || !isUndefined(options.before)) { + if (!isUndefined(options.after)) + api_options.push(`after=${options.after}`); + if (!isUndefined(options.before)) + api_options.push(`before=${options.before}`); + } else { if (!isUndefined(options.from)) api_options.push(`from=${options.from}`); if (!isUndefined(options.to)) api_options.push(`to=${options.to}`); } + if (!isUndefined(options.latest)) + api_options.push('latest'); if (!isUndefined(options.mempool)) api_options.push(`mempool=${options.mempool}`) if (api_options.length) @@ -854,7 +859,10 @@ readTxs(addr, options).then(response => { if (response.incomplete) { let next_options = Object.assign({}, options); - next_options.after = response.lastItem; + if (options.latest) + next_options.before = response.initItem; //update before for chain query (latest 1st) + else + next_options.after = response.lastItem; //update after for chain query (oldest 1st) readAllTxs(addr, next_options).then(r => { r.items = r.items.concat(response.items); //latest tx are 1st in array resolve(r); @@ -871,6 +879,7 @@ /*Read flo Data from txs of given Address options can be used to filter data after : query after the given txid + before : query before the given txid mempool : query mempool tx or not (options same as readAllTx, DEFAULT=false: ignore unconfirmed tx) ignoreOld : ignore old txs (deprecated: support for backward compatibility only, cannot be used with 'after') sentOnly : filters only sent data @@ -887,15 +896,16 @@ //fetch options let fetch_options = {}; fetch_options.mempool = isUndefined(options.mempool) ? 'false' : options.mempool; //DEFAULT: ignore unconfirmed tx - if (!isUndefined(options.after)) { + if (!isUndefined(options.after) || !isUndefined(options.before)) { if (!isUndefined(options.ignoreOld)) //Backward support - return reject("Invalid options: cannot use after and ignoreOld in same query"); - else - fetch_options.after = options.after; + return reject("Invalid options: cannot use after/before and ignoreOld in same query"); + //use passed after and/or before options (options remain undefined if not passed) + fetch_options.after = options.after; + fetch_options.before = options.before; } readAllTxs(addr, fetch_options).then(response => { - if (Number.isInteger(options.ignoreOld)) //backward support, cannot be used with options.after + if (Number.isInteger(options.ignoreOld)) //backward support, cannot be used with options.after or options.before response.items.splice(-options.ignoreOld); //negative to count from end of the array if (typeof options.senders === "string") options.senders = [options.senders]; From 8afde73e7862f8442101fa705b2065784af41763 Mon Sep 17 00:00:00 2001 From: sairajzero Date: Sat, 29 Apr 2023 05:29:39 +0530 Subject: [PATCH 04/13] floBlockchainAPI v2.5.5: adding getLatestData - Added getLatestData: Get the latest flo Data that match the caseFn from txs of given Address --- floBlockchainAPI.js | 79 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 2 deletions(-) diff --git a/floBlockchainAPI.js b/floBlockchainAPI.js index 73cf7e3..8b84c36 100644 --- a/floBlockchainAPI.js +++ b/floBlockchainAPI.js @@ -1,4 +1,4 @@ -(function (EXPORTS) { //floBlockchainAPI v2.5.4 +(function (EXPORTS) { //floBlockchainAPI v2.5.5 /* FLO Blockchain Operator to send/receive data from blockchain using API calls*/ 'use strict'; const floBlockchainAPI = EXPORTS; @@ -895,7 +895,7 @@ //fetch options let fetch_options = {}; - fetch_options.mempool = isUndefined(options.mempool) ? 'false' : options.mempool; //DEFAULT: ignore unconfirmed tx + fetch_options.mempool = isUndefined(options.mempool) ? false : options.mempool; //DEFAULT: ignore unconfirmed tx if (!isUndefined(options.after) || !isUndefined(options.before)) { if (!isUndefined(options.ignoreOld)) //Backward support return reject("Invalid options: cannot use after/before and ignoreOld in same query"); @@ -961,4 +961,79 @@ }) } + /*Get the latest flo Data that match the caseFn from txs of given Address + caseFn: (function) flodata => return bool value + options can be used to filter data + after : query after the given txid + before : query before the given txid + mempool : query mempool tx or not (options same as readAllTx, DEFAULT=false: ignore unconfirmed tx) + sentOnly : filters only sent data + receivedOnly: filters only received data + tx : (boolean) resolve tx data or not (resolves an Array of Object with tx details) + sender : flo-id(s) of sender + receiver : flo-id(s) of receiver + */ + const getLatestData = floBlockchainAPI.getLatestData = function (addr, caseFn, options = {}) { + return new Promise((resolve, reject) => { + //fetch options + let fetch_options = { latest: true }; + fetch_options.mempool = isUndefined(options.mempool) ? false : options.mempool; //DEFAULT: ignore unconfirmed tx + if (!isUndefined(options.after)) fetch_options.after = options.after; + if (!isUndefined(options.before)) fetch_options.before = options.before; + + readTxs(addr, fetch_options).then(response => { + + if (typeof options.senders === "string") options.senders = [options.senders]; + if (typeof options.receivers === "string") options.receivers = [options.receivers]; + + var item = response.items.find(tx => { + if (!tx.confirmations) //unconfirmed transactions: this should not happen as we send mempool=false in API query + return false; + + if (options.sentOnly && !tx.vin.some(vin => vin.addr === addr)) + return false; + else if (Array.isArray(options.senders) && !tx.vin.some(vin => options.senders.includes(vin.addr))) + return false; + + if (options.receivedOnly && !tx.vout.some(vout => vout.scriptPubKey.addresses[0] === addr)) + return false; + else if (Array.isArray(options.receivers) && !tx.vout.some(vout => options.receivers.includes(vout.scriptPubKey.addresses[0]))) + return false; + + return caseFn(tx.floData) ? true : false; //return only bool for find fn + }); + + //if item found, then resolve the result + if (!isUndefined(item)) { + const result = { lastKey: response.lastItem }; + if (options.tx) { + result.item = { + txid: tx.txid, + time: tx.time, + blockheight: tx.blockheight, + senders: new Set(tx.vin.map(v => v.addr)), + receivers: new Set(tx.vout.map(v => v.scriptPubKey.addresses[0])), + data: tx.floData + } + } else + result.data = tx.floData; + return resolve(result); + } + //else if address needs chain query + else if (response.incomplete) { + let next_options = Object.assign({}, options); + options.before = response.initItem; //this fn uses latest option, so using before to chain query + getLatestData(addr, caseFn, next_options).then(r => { + r.lastKey = response.lastItem; //update last key as it should be the newest tx + resolve(r); + }).catch(error => reject(error)) + } + //no data match the caseFn, resolve just the lastKey + else + resolve({ lastKey: response.lastItem }); + + }).catch(error => reject(error)) + }) + } + })('object' === typeof module ? module.exports : window.floBlockchainAPI = {}); \ No newline at end of file From b09c88f0df02b04cd0c12dc7182f41d49e543e44 Mon Sep 17 00:00:00 2001 From: sairajzero Date: Mon, 1 May 2023 20:44:44 +0530 Subject: [PATCH 05/13] floDapps v2.3.5 - Added get property trustedIDs to floGlobals ie, floGlobals.trustedIDs will give the list of trustedIDs for the app --- floDapps.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/floDapps.js b/floDapps.js index 4668e4e..8924153 100644 --- a/floDapps.js +++ b/floDapps.js @@ -1,4 +1,4 @@ -(function (EXPORTS) { //floDapps v2.3.4 +(function (EXPORTS) { //floDapps v2.3.5 /* General functions for FLO Dapps*/ 'use strict'; const floDapps = EXPORTS; @@ -144,11 +144,14 @@ } }); - var subAdmins, settings + var subAdmins, trustedIDs, settings; Object.defineProperties(floGlobals, { subAdmins: { get: () => subAdmins }, + trustedIDs: { + get: () => trustedIDs + }, settings: { get: () => settings }, @@ -317,9 +320,12 @@ compactIDB.writeData("lastTx", result.totalTxs, `${DEFAULT.application}|${DEFAULT.adminID}`, DEFAULT.root); compactIDB.readAllData("subAdmins").then(result => { subAdmins = Object.keys(result); - compactIDB.readAllData("settings").then(result => { - settings = result; - resolve("Read app configuration from blockchain"); + compactIDB.readAllData("trustedIDs").then(result => { + trustedIDs = Object.keys(result); + compactIDB.readAllData("settings").then(result => { + settings = result; + resolve("Read app configuration from blockchain"); + }) }) }) }) From a762f4441b84c022c32423a8dd63e30712e1e2d1 Mon Sep 17 00:00:00 2001 From: sairajzero Date: Tue, 2 May 2023 21:47:48 +0530 Subject: [PATCH 06/13] floBlockchainAPI v2.5.5a: bug fix --- floBlockchainAPI.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/floBlockchainAPI.js b/floBlockchainAPI.js index 8b84c36..39be710 100644 --- a/floBlockchainAPI.js +++ b/floBlockchainAPI.js @@ -1,4 +1,4 @@ -(function (EXPORTS) { //floBlockchainAPI v2.5.5 +(function (EXPORTS) { //floBlockchainAPI v2.5.5a /* FLO Blockchain Operator to send/receive data from blockchain using API calls*/ 'use strict'; const floBlockchainAPI = EXPORTS; @@ -7,7 +7,7 @@ blockchain: floGlobals.blockchain, apiURL: { FLO: ['https://flosight.duckdns.org/', 'https://flosight.ranchimall.net/'], - FLO_TEST: ['https://testnet-flosight.duckdns.org', 'https://testnet.flocha.in/'] + FLO_TEST: ['https://flosight-testnet.ranchimall.net/'] }, sendAmt: 0.0003, fee: 0.0002, @@ -290,8 +290,8 @@ if (balance < totalAmt + fee) return reject("Insufficient FLO balance!"); //get unconfirmed tx list - getUnconfirmedSpent(senderAddr).then(unconfirmedSpent => { - getUTXOs(senderAddr).then(utxos => { + getUnconfirmedSpent(floID).then(unconfirmedSpent => { + getUTXOs(floID).then(utxos => { var trx = bitjs.transaction(); var utxoAmt = 0.0; for (let i = utxos.length - 1; (i >= 0) && (utxoAmt < totalAmt + fee); i--) { From 7e618c98f23979dd1949d123f6dfb8e4b2310074 Mon Sep 17 00:00:00 2001 From: sairajzero Date: Tue, 2 May 2023 22:15:54 +0530 Subject: [PATCH 07/13] floTokenAPI v1.0.4: Added bunkTransferTokens - Added bunkTransferTokens: bulk transfer tokens to multiple receivers - usage: floWebWallet.bunkTransferTokens(sender, privKey, token, receivers) sender: floID of sender privKey: private key of sender token: token to send receivers: an object representing receiver floID and token amount ie, {receiverID1: amount1, receiverID2: amount2....} eg: {Fxyz: 123, Fpqr:321, Fabc: 987} --- floTokenAPI.js | 66 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/floTokenAPI.js b/floTokenAPI.js index efbd2ae..346ab65 100644 --- a/floTokenAPI.js +++ b/floTokenAPI.js @@ -1,4 +1,4 @@ -(function (EXPORTS) { //floTokenAPI v1.0.3c +(function (EXPORTS) { //floTokenAPI v1.0.4 /* Token Operator to send/receive tokens via blockchain using API calls*/ 'use strict'; const tokenAPI = EXPORTS; @@ -77,6 +77,70 @@ }); } + function sendTokens_raw(privKey, receiverID, token, amount, utxo, vout, scriptPubKey) { + return new Promise((resolve, reject) => { + var trx = bitjs.transaction(); + trx.addinput(utxo, vout, scriptPubKey) + trx.addoutput(receiverID, floBlockchainAPI.sendAmt); + trx.addflodata(`send ${amount} ${token}#`); + var signedTxHash = trx.sign(privKey, 1); + floBlockchainAPI.broadcastTx(signedTxHash) + .then(txid => resolve([receiverID, txid])) + .catch(error => reject([receiverID, error])) + }) + } + + //bulk transfer tokens + tokenAPI.bunkTransferTokens = function (sender, privKey, token, receivers) { + return new Promise((resolve, reject) => { + if (typeof receivers !== 'object') + return reject("receivers must be object in format {receiver1: amount1, receiver2:amount2...}") + + let receiver_list = Object.keys(receivers), amount_list = Object.values(receivers); + let invalidReceivers = receiver_list.filter(id => !floCrypto.validateFloID(id)); + let invalidAmount = amount_list.filter(val => typeof val !== 'number' || val <= 0); + if (invalidReceivers.length) + return reject(`Invalid receivers: ${invalidReceivers}`); + else if (invalidAmount.length) + return reject(`Invalid amounts: ${invalidAmount}`); + + if (receiver_list.length == 0) + return reject("Receivers cannot be empty"); + + if (receiver_list.length == 1) { + let receiver = receiver_list[0], amount = amount_list[0]; + floTokenAPI.sendToken(privKey, amount, receiver, "", token) + .then(txid => resolve({ success: { [receiver]: txid } })) + .catch(error => reject(error)) + } else { + //check for token balance + floTokenAPI.getBalance(sender, token).then(token_balance => { + let total_token_amout = amount_list.reduce((a, e) => a + e, 0); + if (total_token_amout > token_balance) + return reject(`Insufficient ${token}# balance`); + + //split utxos + floBlockchainAPI.splitUTXOs(sender, privKey, receiver_list.length).then(split_txid => { + //wait for the split utxo to get confirmation + floBlockchainAPI.waitForConfirmation(split_txid).then(split_tx => { + //send tokens using the split-utxo + var scriptPubKey = split_tx.vout[0].scriptPubKey.hex; + let promises = []; + for (let i in receiver_list) + promises.push(sendTokens_raw(privKey, receiver_list[i], token, amount_list[i], split_txid, i, scriptPubKey)); + Promise.allSettled(promises).then(results => { + let success = Object.fromEntries(results.filter(r => r.status == 'fulfilled').map(r => r.value)); + let failed = Object.fromEntries(results.filter(r => r.status == 'rejected').map(r => r.reason)); + resolve({ success, failed }); + }) + }).catch(error => reject(error)) + }).catch(error => reject(error)) + }).catch(error => reject(error)) + } + + }) + } + tokenAPI.getAllTxs = function (floID, token = DEFAULT.currency) { return new Promise((resolve, reject) => { fetch_api(`api/v1.0/getFloAddressTransactions?token=${token}&floAddress=${floID}`) From 704def4db1a0d6473430fbed89f702c513489a48 Mon Sep 17 00:00:00 2001 From: sairajzero Date: Thu, 4 May 2023 16:59:10 +0530 Subject: [PATCH 08/13] floCloudAPI v2.4.3a - Added property: SNStorageName (string content "SuperNodeStorage" --- floCloudAPI.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/floCloudAPI.js b/floCloudAPI.js index 921fa69..c2d2c3f 100644 --- a/floCloudAPI.js +++ b/floCloudAPI.js @@ -1,4 +1,4 @@ -(function (EXPORTS) { //floCloudAPI v2.4.3 +(function (EXPORTS) { //floCloudAPI v2.4.3a /* FLO Cloud operations to send/request application data*/ 'use strict'; const floCloudAPI = EXPORTS; @@ -8,6 +8,7 @@ SNStorageID: floGlobals.SNStorageID || "FNaN9McoBAEFUjkRmNQRYLmBF8SpS7Tgfk", adminID: floGlobals.adminID, application: floGlobals.application, + SNStorageName: "SuperNodeStorage", callback: (d, e) => console.debug(d, e) }; @@ -58,6 +59,9 @@ SNStorageID: { get: () => DEFAULT.SNStorageID }, + SNStorageName: { + get: () => DEFAULT.SNStorageName + }, adminID: { get: () => DEFAULT.adminID }, From 9e10a02dc8e7b1ff0083d913b181c4198629724d Mon Sep 17 00:00:00 2001 From: sairajzero Date: Thu, 4 May 2023 17:04:55 +0530 Subject: [PATCH 09/13] floBlockchainAPI v2.5.6 - All search query (get method) now uses URLSearchParams to construct the string - renamed all lastKey property to lastItem to match flosight response --- floBlockchainAPI.js | 67 ++++++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/floBlockchainAPI.js b/floBlockchainAPI.js index 39be710..d34e3ef 100644 --- a/floBlockchainAPI.js +++ b/floBlockchainAPI.js @@ -1,4 +1,4 @@ -(function (EXPORTS) { //floBlockchainAPI v2.5.5a +(function (EXPORTS) { //floBlockchainAPI v2.5.6 /* FLO Blockchain Operator to send/receive data from blockchain using API calls*/ 'use strict'; const floBlockchainAPI = EXPORTS; @@ -16,6 +16,7 @@ }; const SATOSHI_IN_BTC = 1e8; + const isUndefined = val => typeof val === 'undefined'; const util = floBlockchainAPI.util = {}; @@ -111,9 +112,11 @@ }); //Promised function to get data from API - const promisedAPI = floBlockchainAPI.promisedAPI = floBlockchainAPI.fetch = function (apicall) { + const promisedAPI = floBlockchainAPI.promisedAPI = floBlockchainAPI.fetch = function (apicall, query_params = undefined) { return new Promise((resolve, reject) => { - //console.log(apicall); + if (!isUndefined(query_params)) + apicall += '?' + new URLSearchParams(JSON.parse(JSON.stringify(query_params))).toString(); + //console.debug(apicall); fetch_api(apicall) .then(result => resolve(result)) .catch(error => reject(error)); @@ -123,13 +126,13 @@ //Get balance for the given Address const getBalance = floBlockchainAPI.getBalance = function (addr, after = null) { return new Promise((resolve, reject) => { - let api = `api/addr/${addr}/balance`; + let api = `api/addr/${addr}/balance`, query_params = {}; if (after) { if (typeof after === 'string' && /^[0-9a-z]{64}$/i.test(after)) - api += '?after=' + after; + query_params.after = after; else return reject("Invalid 'after' parameter"); } - promisedAPI(api).then(result => { + promisedAPI(api, query_params).then(result => { if (typeof result === 'object' && result.lastItem) { getBalance(addr, result.lastItem) .then(r => resolve(util.toFixed(r + result.data))) @@ -822,32 +825,28 @@ }) } - const isUndefined = val => typeof val === 'undefined'; - //Read Txs of Address between from and to const readTxs = floBlockchainAPI.readTxs = function (addr, options = {}) { return new Promise((resolve, reject) => { let api = `api/addrs/${addr}/txs`; //API options - let api_options = []; + let query_params = {}; if (!isUndefined(options.after) || !isUndefined(options.before)) { if (!isUndefined(options.after)) - api_options.push(`after=${options.after}`); + query_params.after = options.after; if (!isUndefined(options.before)) - api_options.push(`before=${options.before}`); + query_params.before = options.before; } else { if (!isUndefined(options.from)) - api_options.push(`from=${options.from}`); + query_params.from = options.from; if (!isUndefined(options.to)) - api_options.push(`to=${options.to}`); + query_params.to = options.to; } if (!isUndefined(options.latest)) - api_options.push('latest'); + query_params.latest = latest; if (!isUndefined(options.mempool)) - api_options.push(`mempool=${options.mempool}`) - if (api_options.length) - api += "?" + api_options.join('&'); - promisedAPI(api) + query_params.mempool = options.mempool; + promisedAPI(api, query_params) .then(response => resolve(response)) .catch(error => reject(error)) }); @@ -869,7 +868,7 @@ }).catch(error => reject(error)) } else resolve({ - lastKey: response.lastItem || options.after, + lastItem: response.lastItem || options.after, items: response.items }); }) @@ -894,16 +893,16 @@ return new Promise((resolve, reject) => { //fetch options - let fetch_options = {}; - fetch_options.mempool = isUndefined(options.mempool) ? false : options.mempool; //DEFAULT: ignore unconfirmed tx + let query_options = {}; + query_options.mempool = isUndefined(options.mempool) ? false : options.mempool; //DEFAULT: ignore unconfirmed tx if (!isUndefined(options.after) || !isUndefined(options.before)) { if (!isUndefined(options.ignoreOld)) //Backward support return reject("Invalid options: cannot use after/before and ignoreOld in same query"); //use passed after and/or before options (options remain undefined if not passed) - fetch_options.after = options.after; - fetch_options.before = options.before; + query_options.after = options.after; + query_options.before = options.before; } - readAllTxs(addr, fetch_options).then(response => { + readAllTxs(addr, query_options).then(response => { if (Number.isInteger(options.ignoreOld)) //backward support, cannot be used with options.after or options.before response.items.splice(-options.ignoreOld); //negative to count from end of the array @@ -950,7 +949,7 @@ data: tx.floData } : tx.floData); - const result = { lastKey: response.lastKey }; + const result = { lastItem: response.lastItem }; if (options.tx) result.items = filteredData; else @@ -976,12 +975,12 @@ const getLatestData = floBlockchainAPI.getLatestData = function (addr, caseFn, options = {}) { return new Promise((resolve, reject) => { //fetch options - let fetch_options = { latest: true }; - fetch_options.mempool = isUndefined(options.mempool) ? false : options.mempool; //DEFAULT: ignore unconfirmed tx - if (!isUndefined(options.after)) fetch_options.after = options.after; - if (!isUndefined(options.before)) fetch_options.before = options.before; + let query_options = { latest: true }; + query_options.mempool = isUndefined(options.mempool) ? false : options.mempool; //DEFAULT: ignore unconfirmed tx + if (!isUndefined(options.after)) query_options.after = options.after; + if (!isUndefined(options.before)) query_options.before = options.before; - readTxs(addr, fetch_options).then(response => { + readTxs(addr, query_options).then(response => { if (typeof options.senders === "string") options.senders = [options.senders]; if (typeof options.receivers === "string") options.receivers = [options.receivers]; @@ -1005,7 +1004,7 @@ //if item found, then resolve the result if (!isUndefined(item)) { - const result = { lastKey: response.lastItem }; + const result = { lastItem: response.lastItem }; if (options.tx) { result.item = { txid: tx.txid, @@ -1024,13 +1023,13 @@ let next_options = Object.assign({}, options); options.before = response.initItem; //this fn uses latest option, so using before to chain query getLatestData(addr, caseFn, next_options).then(r => { - r.lastKey = response.lastItem; //update last key as it should be the newest tx + r.lastItem = response.lastItem; //update last key as it should be the newest tx resolve(r); }).catch(error => reject(error)) } - //no data match the caseFn, resolve just the lastKey + //no data match the caseFn, resolve just the lastItem else - resolve({ lastKey: response.lastItem }); + resolve({ lastItem: response.lastItem }); }).catch(error => reject(error)) }) From 46ae8581e4d5e4bb8459ba23288f3f1eb5546f95 Mon Sep 17 00:00:00 2001 From: sairajzero Date: Thu, 4 May 2023 17:09:02 +0530 Subject: [PATCH 10/13] floDapps v2.4.0 - Update for new flosight version - requires floBlockchainAPI >= v2.5.6 - requires floCloudAPI >= v2.4.3a - lastTx now stores the lastItem property (ie, txid) - backward-support: maintaining support for idb data (lastTx) from old floDapps module to be converted to this version --- floDapps.js | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/floDapps.js b/floDapps.js index 8924153..4d525a2 100644 --- a/floDapps.js +++ b/floDapps.js @@ -1,4 +1,4 @@ -(function (EXPORTS) { //floDapps v2.3.5 +(function (EXPORTS) { //floDapps v2.4.0 /* General functions for FLO Dapps*/ 'use strict'; const floDapps = EXPORTS; @@ -258,13 +258,15 @@ if (!startUpOptions.cloud) return resolve("No cloud for this app"); compactIDB.readData("lastTx", floCloudAPI.SNStorageID, DEFAULT.root).then(lastTx => { - floBlockchainAPI.readData(floCloudAPI.SNStorageID, { - ignoreOld: lastTx, - sentOnly: true, - pattern: "SuperNodeStorage" - }).then(result => { + var query_options = { sentOnly: true, pattern: floCloudAPI.SNStorageName }; + if (typeof lastTx == 'number') //lastTx is tx count (*backward support) + query_options.ignoreOld = lastTx; + else if (typeof lastTx == 'string') //lastTx is txid of last tx + query_options.after = lastTx; + //fetch data from flosight + floBlockchainAPI.readData(floCloudAPI.SNStorageID, query_options).then(result => { for (var i = result.data.length - 1; i >= 0; i--) { - var content = JSON.parse(result.data[i]).SuperNodeStorage; + var content = JSON.parse(result.data[i])[floCloudAPI.SNStorageName]; for (let sn in content.removeNodes) compactIDB.removeData("supernodes", sn, DEFAULT.root); for (let sn in content.newNodes) @@ -276,7 +278,7 @@ compactIDB.writeData("supernodes", r, sn, DEFAULT.root); }); } - compactIDB.writeData("lastTx", result.totalTxs, floCloudAPI.SNStorageID, DEFAULT.root); + compactIDB.writeData("lastTx", result.lastItem, floCloudAPI.SNStorageID, DEFAULT.root); compactIDB.readAllData("supernodes", DEFAULT.root).then(nodes => { floCloudAPI.init(nodes) .then(result => resolve("Loaded Supernode list\n" + result)) @@ -292,11 +294,13 @@ if (!startUpOptions.app_config) return resolve("No configs for this app"); compactIDB.readData("lastTx", `${DEFAULT.application}|${DEFAULT.adminID}`, DEFAULT.root).then(lastTx => { - floBlockchainAPI.readData(DEFAULT.adminID, { - ignoreOld: lastTx, - sentOnly: true, - pattern: DEFAULT.application - }).then(result => { + var query_options = { sentOnly: true, pattern: DEFAULT.application }; + if (typeof lastTx == 'number') //lastTx is tx count (*backward support) + query_options.ignoreOld = lastTx; + else if (typeof lastTx == 'string') //lastTx is txid of last tx + query_options.after = lastTx; + //fetch data from flosight + floBlockchainAPI.readData(DEFAULT.adminID, query_options).then(result => { for (var i = result.data.length - 1; i >= 0; i--) { var content = JSON.parse(result.data[i])[DEFAULT.application]; if (!content || typeof content !== "object") @@ -317,7 +321,7 @@ for (let l in content.settings) compactIDB.writeData("settings", content.settings[l], l) } - compactIDB.writeData("lastTx", result.totalTxs, `${DEFAULT.application}|${DEFAULT.adminID}`, DEFAULT.root); + compactIDB.writeData("lastTx", result.lastItem, `${DEFAULT.application}|${DEFAULT.adminID}`, DEFAULT.root); compactIDB.readAllData("subAdmins").then(result => { subAdmins = Object.keys(result); compactIDB.readAllData("trustedIDs").then(result => { From 1390c91907c79b0c80e6260630fe1a27c586b01a Mon Sep 17 00:00:00 2001 From: sairajzero Date: Wed, 10 May 2023 01:46:36 +0530 Subject: [PATCH 11/13] floBlockchainAPI v2.5.6a - minor bug fix: default value for options missing in readAllTxs fn - removed flosight.duckdns.org from apiURL --- floBlockchainAPI.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/floBlockchainAPI.js b/floBlockchainAPI.js index d34e3ef..460775b 100644 --- a/floBlockchainAPI.js +++ b/floBlockchainAPI.js @@ -1,4 +1,4 @@ -(function (EXPORTS) { //floBlockchainAPI v2.5.6 +(function (EXPORTS) { //floBlockchainAPI v2.5.6a /* FLO Blockchain Operator to send/receive data from blockchain using API calls*/ 'use strict'; const floBlockchainAPI = EXPORTS; @@ -6,7 +6,7 @@ const DEFAULT = { blockchain: floGlobals.blockchain, apiURL: { - FLO: ['https://flosight.duckdns.org/', 'https://flosight.ranchimall.net/'], + FLO: ['https://flosight.ranchimall.net/'], FLO_TEST: ['https://flosight-testnet.ranchimall.net/'] }, sendAmt: 0.0003, @@ -853,7 +853,7 @@ } //Read All Txs of Address (newest first) - const readAllTxs = floBlockchainAPI.readAllTxs = function (addr, options) { + const readAllTxs = floBlockchainAPI.readAllTxs = function (addr, options = {}) { return new Promise((resolve, reject) => { readTxs(addr, options).then(response => { if (response.incomplete) { From 94fb4f4849e12e83e61b21bef00bb48a62c11ba6 Mon Sep 17 00:00:00 2001 From: sairajzero Date: Wed, 10 May 2023 01:50:46 +0530 Subject: [PATCH 12/13] compactIDB v2.1.2 - Fixed: options.limit not working in searchData - Added option reverse to searchData (if set to true, the ordering will be reversed --- compactIDB.js | 46 ++++++++++++++++++++++------------------------ 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/compactIDB.js b/compactIDB.js index 624db0a..ba843ec 100644 --- a/compactIDB.js +++ b/compactIDB.js @@ -1,4 +1,4 @@ -(function(EXPORTS) { //compactIDB v2.1.0 +(function (EXPORTS) { //compactIDB v2.1.2 /* Compact IndexedDB operations */ 'use strict'; const compactIDB = EXPORTS; @@ -59,7 +59,7 @@ }) } - compactIDB.initDB = function(dbName, objectStores = {}) { + compactIDB.initDB = function (dbName, objectStores = {}) { return new Promise((resolve, reject) => { if (!(objectStores instanceof Object)) return reject('ObjectStores must be an object or array') @@ -87,14 +87,14 @@ resolve("Initiated IndexedDB"); else upgradeDB(dbName, a_obs, d_obs) - .then(result => resolve(result)) - .catch(error => reject(error)) + .then(result => resolve(result)) + .catch(error => reject(error)) db.close(); } }); } - const openDB = compactIDB.openDB = function(dbName = defaultDB) { + const openDB = compactIDB.openDB = function (dbName = defaultDB) { return new Promise((resolve, reject) => { var idb = indexedDB.open(dbName); idb.onerror = (event) => reject("Error in opening IndexedDB"); @@ -106,7 +106,7 @@ }); } - const deleteDB = compactIDB.deleteDB = function(dbName = defaultDB) { + const deleteDB = compactIDB.deleteDB = function (dbName = defaultDB) { return new Promise((resolve, reject) => { var deleteReq = indexedDB.deleteDatabase(dbName);; deleteReq.onerror = (event) => reject("Error deleting database!"); @@ -114,7 +114,7 @@ }); } - compactIDB.writeData = function(obsName, data, key = false, dbName = defaultDB) { + compactIDB.writeData = function (obsName, data, key = false, dbName = defaultDB) { return new Promise((resolve, reject) => { openDB(dbName).then(db => { var obs = db.transaction(obsName, "readwrite").objectStore(obsName); @@ -128,7 +128,7 @@ }); } - compactIDB.addData = function(obsName, data, key = false, dbName = defaultDB) { + compactIDB.addData = function (obsName, data, key = false, dbName = defaultDB) { return new Promise((resolve, reject) => { openDB(dbName).then(db => { var obs = db.transaction(obsName, "readwrite").objectStore(obsName); @@ -142,7 +142,7 @@ }); } - compactIDB.removeData = function(obsName, key, dbName = defaultDB) { + compactIDB.removeData = function (obsName, key, dbName = defaultDB) { return new Promise((resolve, reject) => { openDB(dbName).then(db => { var obs = db.transaction(obsName, "readwrite").objectStore(obsName); @@ -156,7 +156,7 @@ }); } - compactIDB.clearData = function(obsName, dbName = defaultDB) { + compactIDB.clearData = function (obsName, dbName = defaultDB) { return new Promise((resolve, reject) => { openDB(dbName).then(db => { var obs = db.transaction(obsName, "readwrite").objectStore(obsName); @@ -168,7 +168,7 @@ }); } - compactIDB.readData = function(obsName, key, dbName = defaultDB) { + compactIDB.readData = function (obsName, key, dbName = defaultDB) { return new Promise((resolve, reject) => { openDB(dbName).then(db => { var obs = db.transaction(obsName, "readonly").objectStore(obsName); @@ -182,7 +182,7 @@ }); } - compactIDB.readAllData = function(obsName, dbName = defaultDB) { + compactIDB.readAllData = function (obsName, dbName = defaultDB) { return new Promise((resolve, reject) => { openDB(dbName).then(db => { var obs = db.transaction(obsName, "readonly").objectStore(obsName); @@ -223,13 +223,12 @@ }) }*/ - compactIDB.searchData = function(obsName, options = {}, dbName = defaultDB) { + compactIDB.searchData = function (obsName, options = {}, dbName = defaultDB) { options.lowerKey = options.atKey || options.lowerKey || 0 options.upperKey = options.atKey || options.upperKey || false - options.patternEval = options.patternEval || ((k, v) => { - return true - }) + options.patternEval = options.patternEval || ((k, v) => true); options.limit = options.limit || false; + options.reverse = options.reverse || false; options.lastOnly = options.lastOnly || false return new Promise((resolve, reject) => { openDB(dbName).then(db => { @@ -237,17 +236,16 @@ var filteredResult = {} let curReq = obs.openCursor( options.upperKey ? IDBKeyRange.bound(options.lowerKey, options.upperKey) : IDBKeyRange.lowerBound(options.lowerKey), - options.lastOnly ? "prev" : "next"); + options.lastOnly || options.reverse ? "prev" : "next"); curReq.onsuccess = (evt) => { var cursor = evt.target.result; - if (cursor) { - if (options.patternEval(cursor.primaryKey, cursor.value)) { - filteredResult[cursor.primaryKey] = cursor.value; - options.lastOnly ? resolve(filteredResult) : cursor.continue(); - } else - cursor.continue(); + if (!cursor || (options.limit && options.limit <= Object.keys(filteredResult).length)) + return resolve(filteredResult); //reached end of key list or limit reached + else if (options.patternEval(cursor.primaryKey, cursor.value)) { + filteredResult[cursor.primaryKey] = cursor.value; + options.lastOnly ? resolve(filteredResult) : cursor.continue(); } else - resolve(filteredResult); + cursor.continue(); } curReq.onerror = (evt) => reject(`Search unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`); db.close(); From d777303097a038f27f72d7618467fadc6c4e935b Mon Sep 17 00:00:00 2001 From: sairajzero Date: Wed, 10 May 2023 02:01:47 +0530 Subject: [PATCH 13/13] floTokenAPI v1.0.4a - fixed: typo in bulkTransferTokens --- floTokenAPI.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/floTokenAPI.js b/floTokenAPI.js index 346ab65..2456b88 100644 --- a/floTokenAPI.js +++ b/floTokenAPI.js @@ -1,4 +1,4 @@ -(function (EXPORTS) { //floTokenAPI v1.0.4 +(function (EXPORTS) { //floTokenAPI v1.0.4a /* Token Operator to send/receive tokens via blockchain using API calls*/ 'use strict'; const tokenAPI = EXPORTS; @@ -91,7 +91,7 @@ } //bulk transfer tokens - tokenAPI.bunkTransferTokens = function (sender, privKey, token, receivers) { + tokenAPI.bulkTransferTokens = function (sender, privKey, token, receivers) { return new Promise((resolve, reject) => { if (typeof receivers !== 'object') return reject("receivers must be object in format {receiver1: amount1, receiver2:amount2...}")