diff --git a/docs/scripts/floBlockchainAPI.js b/docs/scripts/floBlockchainAPI.js index 8d3d857..fcc73b9 100644 --- a/docs/scripts/floBlockchainAPI.js +++ b/docs/scripts/floBlockchainAPI.js @@ -1,9 +1,54 @@ -(function(EXPORTS) { //floBlockchainAPI v2.3.0 +(function(EXPORTS) { //floBlockchainAPI v2.3.3 /* FLO Blockchain Operator to send/receive data from blockchain using API calls*/ 'use strict'; const floBlockchainAPI = EXPORTS; - const serverList = floGlobals.apiURL[floGlobals.blockchain].slice(0); + const DEFAULT = { + blockchain: floGlobals.blockchain, + apiURL: { + FLO: ['https://livenet.flocha.in/', 'https://flosight.duckdns.org/'], + FLO_TEST: ['https://testnet-flosight.duckdns.org', 'https://testnet.flocha.in/'] + }, + sendAmt: 0.001, + fee: 0.0005, + receiverID: floGlobals.adminID + }; + + Object.defineProperties(floBlockchainAPI, { + sendAmt: { + get: () => DEFAULT.sendAmt, + set: amt => !isNaN(amt) ? DEFAULT.sendAmt = amt : null + }, + fee: { + get: () => DEFAULT.fee, + set: fee => !isNaN(fee) ? DEFAULT.fee = fee : null + }, + defaultReceiver: { + get: () => DEFAULT.receiverID, + set: floID => DEFAULT.receiverID = floID + }, + blockchain: { + get: () => DEFAULT.blockchain + } + }); + + if (floGlobals.sendAmt) floBlockchainAPI.sendAmt = floGlobals.sendAmt; + if (floGlobals.fee) floBlockchainAPI.fee = floGlobals.fee; + + Object.defineProperties(floGlobals, { + sendAmt: { + get: () => DEFAULT.sendAmt, + set: amt => !isNaN(amt) ? DEFAULT.sendAmt = amt : null + }, + fee: { + get: () => DEFAULT.fee, + set: fee => !isNaN(fee) ? DEFAULT.fee = fee : null + } + }); + + const allServerList = new Set(floGlobals.apiURL && floGlobals.apiURL[DEFAULT.blockchain] ? floGlobals.apiURL[DEFAULT.blockchain] : DEFAULT.apiURL[DEFAULT.blockchain]); + + var serverList = Array.from(allServerList); var curPos = floCrypto.randInt(0, serverList - 1); function fetch_retry(apicall, rm_flosight) { @@ -11,17 +56,24 @@ let i = serverList.indexOf(rm_flosight) if (i != -1) serverList.splice(i, 1); curPos = floCrypto.randInt(0, serverList.length - 1); - fetch_api(apicall) + fetch_api(apicall, false) .then(result => resolve(result)) .catch(error => reject(error)); }) } - function fetch_api(apicall) { + function fetch_api(apicall, ic = true) { return new Promise((resolve, reject) => { - if (serverList.length === 0) - reject("No floSight server working"); - else { + if (serverList.length === 0) { + if (ic) { + serverList = Array.from(allServerList); + curPos = floCrypto.randInt(0, serverList.length - 1); + fetch_api(apicall, false) + .then(result => resolve(result)) + .catch(error => reject(error)); + } else + reject("No floSight server working"); + } else { let flosight = serverList[curPos]; fetch(flosight + apicall).then(response => { if (response.ok) @@ -40,12 +92,17 @@ }) } - Object.defineProperty(floBlockchainAPI, 'current_server', { - get: () => serverList[curPos] + Object.defineProperties(floBlockchainAPI, { + serverList: { + get: () => Array.from(serverList) + }, + current_server: { + get: () => serverList[curPos] + } }); //Promised function to get data from API - const promisedAPI = floBlockchainAPI.promisedAPI = function(apicall) { + const promisedAPI = floBlockchainAPI.promisedAPI = floBlockchainAPI.fetch = function(apicall) { return new Promise((resolve, reject) => { //console.log(apicall); fetch_api(apicall) @@ -77,48 +134,52 @@ else if (typeof sendAmt !== 'number' || sendAmt <= 0) return reject(`Invalid sendAmt : ${sendAmt}`); - //get unconfirmed tx list - promisedAPI(`api/addr/${senderAddr}`).then(result => { - readTxs(senderAddr, 0, result.unconfirmedTxApperances).then(result => { - let unconfirmedSpent = {}; - for (let tx of result.items) - if (tx.confirmations == 0) - for (let vin of tx.vin) - if (vin.addr === senderAddr) { - if (Array.isArray(unconfirmedSpent[vin.txid])) - unconfirmedSpent[vin.txid].push(vin.vout); - else - unconfirmedSpent[vin.txid] = [vin.vout]; - } - //get utxos list - promisedAPI(`api/addr/${senderAddr}/utxo`).then(utxos => { - //form/construct the transaction data - var trx = bitjs.transaction(); - var utxoAmt = 0.0; - var fee = floGlobals.fee; - for (var i = utxos.length - 1; - (i >= 0) && (utxoAmt < sendAmt + 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 < sendAmt + fee) - reject("Insufficient FLO balance!"); - else { - trx.addoutput(receiverAddr, sendAmt); - var change = utxoAmt - sendAmt - fee; - if (change > 0) - trx.addoutput(senderAddr, change); - trx.addflodata(floData.replace(/\n/g, ' ')); - var signedTxHash = trx.sign(privKey, 1); - broadcastTx(signedTxHash) - .then(txid => resolve(txid)) - .catch(error => reject(error)) - } + getBalance(senderAddr).then(balance => { + var fee = DEFAULT.fee; + if (balance < sendAmt + fee) + return reject("Insufficient FLO balance!"); + //get unconfirmed tx list + promisedAPI(`api/addr/${senderAddr}`).then(result => { + readTxs(senderAddr, 0, result.unconfirmedTxApperances).then(result => { + let unconfirmedSpent = {}; + for (let tx of result.items) + if (tx.confirmations == 0) + for (let vin of tx.vin) + if (vin.addr === senderAddr) { + if (Array.isArray(unconfirmedSpent[vin.txid])) + unconfirmedSpent[vin.txid].push(vin.vout); + else + unconfirmedSpent[vin.txid] = [vin.vout]; + } + //get utxos list + promisedAPI(`api/addr/${senderAddr}/utxo`).then(utxos => { + //form/construct the transaction data + var trx = bitjs.transaction(); + var utxoAmt = 0.0; + for (var i = utxos.length - 1; + (i >= 0) && (utxoAmt < sendAmt + 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 < sendAmt + fee) + reject("Insufficient FLO: Some UTXOs are unconfirmed"); + else { + trx.addoutput(receiverAddr, sendAmt); + var change = utxoAmt - sendAmt - fee; + if (change > 0) + trx.addoutput(senderAddr, 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)) }).catch(error => reject(error)) @@ -126,11 +187,13 @@ } //Write Data into blockchain - floBlockchainAPI.writeData = function(senderAddr, data, privKey, receiverAddr = floGlobals.adminID, strict_utxo = true) { + floBlockchainAPI.writeData = function(senderAddr, data, privKey, receiverAddr = DEFAULT.receiverID, options = {}) { + let strict_utxo = options.strict_utxo === false ? false : true, + sendAmt = isNaN(options.sendAmt) ? DEFAULT.sendAmt : options.sendAmt; return new Promise((resolve, reject) => { if (typeof data != "string") data = JSON.stringify(data); - sendTx(senderAddr, receiverAddr, floGlobals.sendAmt, privKey, data, strict_utxo) + sendTx(senderAddr, receiverAddr, sendAmt, privKey, data, strict_utxo) .then(txid => resolve(txid)) .catch(error => reject(error)); }); @@ -147,7 +210,7 @@ return reject("Invalid FLO_Data: only printable ASCII characters are allowed"); var trx = bitjs.transaction(); var utxoAmt = 0.0; - var fee = floGlobals.fee; + var fee = DEFAULT.fee; promisedAPI(`api/addr/${floID}/utxo`).then(utxos => { for (var i = utxos.length - 1; i >= 0; i--) if (utxos[i].confirmations) { @@ -171,13 +234,13 @@ * @param {boolean} preserveRatio (optional) preserve ratio or equal contribution * @return {Promise} */ - floBlockchainAPI.writeDataMultiple = function(senderPrivKeys, data, receivers = [floGlobals.adminID], preserveRatio = true) { + floBlockchainAPI.writeDataMultiple = function(senderPrivKeys, data, receivers = [DEFAULT.receiverID], preserveRatio = true) { return new Promise((resolve, reject) => { if (!Array.isArray(senderPrivKeys)) return reject("Invalid senderPrivKeys: SenderPrivKeys must be Array"); if (!preserveRatio) { let tmp = {}; - let amount = (floGlobals.sendAmt * receivers.length) / senderPrivKeys.length; + let amount = (DEFAULT.sendAmt * receivers.length) / senderPrivKeys.length; senderPrivKeys.forEach(key => tmp[key] = amount); senderPrivKeys = tmp; } @@ -185,7 +248,7 @@ return reject("Invalid receivers: Receivers must be Array"); else { let tmp = {}; - let amount = floGlobals.sendAmt; + let amount = DEFAULT.sendAmt; receivers.forEach(floID => tmp[floID] = amount); receivers = tmp } @@ -288,7 +351,7 @@ promises.push(getBalance(floID)); Promise.all(promises).then(results => { let totalBalance = 0, - totalFee = floGlobals.fee, + totalFee = DEFAULT.fee, balance = {}; //Divide fee among sender if not for preserveRatio if (!preserveRatio) diff --git a/docs/scripts/floGlobals.js b/docs/scripts/floGlobals.js index 2b0b974..5501626 100644 --- a/docs/scripts/floGlobals.js +++ b/docs/scripts/floGlobals.js @@ -2,17 +2,8 @@ const floGlobals = { //Required for all blockchain: "FLO", - - //Required for blockchain API operators - apiURL: { - FLO: ['https://livenet.flocha.in/', 'https://flosight.duckdns.org/'], - FLO_TEST: ['https://testnet-flosight.duckdns.org', 'https://testnet.flocha.in/'] - }, application: "exchange", adminID: "FMxYC7gYZhouzqtHZukGnPiQ8nvG4CMzXM", - sendAmt: 0.001, - fee: 0.0005, - tokenURL: "https://ranchimallflo.duckdns.org/", currency: "rupee" }; diff --git a/docs/scripts/floTokenAPI.js b/docs/scripts/floTokenAPI.js index 13c17f5..a87876f 100644 --- a/docs/scripts/floTokenAPI.js +++ b/docs/scripts/floTokenAPI.js @@ -1,53 +1,95 @@ -'use strict'; +(function(EXPORTS) { //floTokenAPI v1.0.3a + /* Token Operator to send/receive tokens via blockchain using API calls*/ + 'use strict'; + const tokenAPI = EXPORTS; -/* Token Operator to send/receive tokens from blockchain using API calls*/ -(function(GLOBAL) { - const floTokenAPI = GLOBAL.floTokenAPI = { - fetch_api: function(apicall) { - return new Promise((resolve, reject) => { - console.log(floGlobals.tokenURL + apicall); - fetch(floGlobals.tokenURL + apicall).then(response => { - if (response.ok) - response.json().then(data => resolve(data)); - else - reject(response) - }).catch(error => reject(error)) - }) - }, - getBalance: function(floID, token = floGlobals.currency) { - return new Promise((resolve, reject) => { - this.fetch_api(`api/v1.0/getFloAddressBalance?token=${token}&floAddress=${floID}`) - .then(result => resolve(result.balance || 0)) - .catch(error => reject(error)) - }) - }, - getTx: function(txID) { - return new Promise((resolve, reject) => { - this.fetch_api(`api/v1.0/getTransactionDetails/${txID}`).then(res => { - if (res.result === "error") - reject(res.description); - else if (!res.parsedFloData) - reject("Data piece (parsedFloData) missing"); - else if (!res.transactionDetails) - reject("Data piece (transactionDetails) missing"); - else - resolve(res); - }).catch(error => reject(error)) - }) - }, - sendToken: function(privKey, amount, receiverID, message = "", token = floGlobals.currency) { - return new Promise((resolve, reject) => { - let senderID = floCrypto.getFloID(privKey); - if (typeof amount !== "number" || amount <= 0) - return reject("Invalid amount"); - this.getBalance(senderID, token).then(bal => { - if (amount > bal) - return reject(`Insufficient ${token}# balance`); - floBlockchainAPI.writeData(senderID, `send ${amount} ${token}# ${message}`, privKey, receiverID) - .then(txid => resolve(txid)) - .catch(error => reject(error)) - }).catch(error => reject(error)) - }); - } + const DEFAULT = { + apiURL: floGlobals.tokenURL || "https://ranchimallflo.duckdns.org/", + currency: "rupee" } -})(typeof global !== "undefined" ? global : window); \ No newline at end of file + + Object.defineProperties(tokenAPI, { + URL: { + get: () => DEFAULT.apiURL + }, + currency: { + get: () => DEFAULT.currency, + set: currency => DEFAULT.currency = currency + } + }); + + if (floGlobals.currency) tokenAPI.currency = floGlobals.currency; + + const fetch_api = tokenAPI.fetch = function(apicall) { + return new Promise((resolve, reject) => { + console.log(DEFAULT.apiURL + apicall); + fetch(DEFAULT.apiURL + apicall).then(response => { + if (response.ok) + response.json().then(data => resolve(data)); + else + reject(response) + }).catch(error => reject(error)) + }) + } + + const getBalance = tokenAPI.getBalance = function(floID, token = DEFAULT.currency) { + return new Promise((resolve, reject) => { + fetch_api(`api/v1.0/getFloAddressBalance?token=${token}&floAddress=${floID}`) + .then(result => resolve(result.balance || 0)) + .catch(error => reject(error)) + }) + } + + tokenAPI.getTx = function(txID) { + return new Promise((resolve, reject) => { + fetch_api(`api/v1.0/getTransactionDetails/${txID}`).then(res => { + if (res.result === "error") + reject(res.description); + else if (!res.parsedFloData) + reject("Data piece (parsedFloData) missing"); + else if (!res.transactionDetails) + reject("Data piece (transactionDetails) missing"); + else + resolve(res); + }).catch(error => reject(error)) + }) + } + + tokenAPI.sendToken = function(privKey, amount, receiverID, message = "", token = DEFAULT.currency, options = {}) { + return new Promise((resolve, reject) => { + let senderID = floCrypto.getFloID(privKey); + if (typeof amount !== "number" || isNaN(amount) || amount <= 0) + return reject("Invalid amount"); + getBalance(senderID, token).then(bal => { + if (amount > bal) + return reject(`Insufficient ${token}# balance`); + floBlockchainAPI.writeData(senderID, `send ${amount} ${token}# ${message}`, privKey, receiverID, options) + .then(txid => resolve(txid)) + .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}`) + .then(result => resolve(result)) + .catch(error => reject(error)) + }) + } + + const util = tokenAPI.util = {}; + + util.parseTxData = function(txData) { + let parsedData = {}; + for (let p in txData.parsedFloData) + parsedData[p] = txData.parsedFloData[p]; + parsedData.sender = txData.transactionDetails.vin[0].addr; + for (let vout of txData.transactionDetails.vout) + if (vout.scriptPubKey.addresses[0] !== parsedData.sender) + parsedData.receiver = vout.scriptPubKey.addresses[0]; + parsedData.time = txData.transactionDetails.time; + return parsedData; + } + +})('object' === typeof module ? module.exports : window.floTokenAPI = {}); \ No newline at end of file diff --git a/src/main.js b/src/main.js index e2d4dc0..6440c21 100644 --- a/src/main.js +++ b/src/main.js @@ -4,7 +4,7 @@ require('./set_globals'); require('../docs/scripts/lib'); global.floCrypto = require('../docs/scripts/floCrypto'); global.floBlockchainAPI = require('../docs/scripts/floBlockchainAPI'); -require('../docs/scripts/floTokenAPI'); +global.floTokenAPI = require('../docs/scripts/floTokenAPI'); const Database = require("./database"); const App = require('./app');