Merge pull request #13 from ranchimall/dev

new flosight
This commit is contained in:
Sai Raj 2023-05-19 03:41:25 +05:30 committed by GitHub
commit 5d67b6e826
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 2569 additions and 1036 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.tmp*

21
LICENCE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Sai Raj
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,29 +1,40 @@
(function (EXPORTS) { //btcOperator v1.0.12 (function (EXPORTS) { //btcOperator v1.1.2a
/* BTC Crypto and API Operator */ /* BTC Crypto and API Operator */
const btcOperator = EXPORTS; const btcOperator = EXPORTS;
//This library uses API provided by chain.so (https://chain.so/) //This library uses API provided by chain.so (https://chain.so/)
const URL = "https://chain.so/api/v2/"; const URL = "https://blockchain.info/";
const fetch_api = btcOperator.fetch = function (api) { const fetch_api = btcOperator.fetch = function (api, json_res = true) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
console.debug(URL + api); console.debug(URL + api);
fetch(URL + api).then(response => { fetch(URL + api).then(response => {
response.json() if (response.ok) {
.then(result => result.status === "success" ? resolve(result) : reject(result)) (json_res ? response.json() : response.text())
.catch(error => reject(error)) .then(result => resolve(result))
.catch(error => reject(error))
} else {
response.json()
.then(result => reject(result))
.catch(error => reject(error))
}
}).catch(error => reject(error)) }).catch(error => reject(error))
}) })
}; };
const SATOSHI_IN_BTC = 1e8; const SATOSHI_IN_BTC = 1e8;
const util = btcOperator.util = {};
util.Sat_to_BTC = value => parseFloat((value / SATOSHI_IN_BTC).toFixed(8));
util.BTC_to_Sat = value => parseInt(value * SATOSHI_IN_BTC);
function get_fee_rate() { function get_fee_rate() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
fetch('https://api.blockchain.info/mempool/fees').then(response => { fetch('https://api.blockchain.info/mempool/fees').then(response => {
if (response.ok) if (response.ok)
response.json() response.json()
.then(result => resolve(parseFloat((result.regular / SATOSHI_IN_BTC).toFixed(8)))) .then(result => resolve(util.Sat_to_BTC(result.regular)))
.catch(error => reject(error)); .catch(error => reject(error));
else else
reject(response); reject(response);
@ -102,64 +113,101 @@
if (!addr) if (!addr)
return undefined; return undefined;
let type = coinjs.addressDecode(addr).type; let type = coinjs.addressDecode(addr).type;
if (["standard", "multisig", "bech32"].includes(type)) if (["standard", "multisig", "bech32", "multisigBech32"].includes(type))
return type; return type;
else else
return false; return false;
} }
btcOperator.multiSigAddress = function (pubKeys, minRequired) { btcOperator.multiSigAddress = function (pubKeys, minRequired, bech32 = true) {
if (!Array.isArray(pubKeys)) if (!Array.isArray(pubKeys))
throw "pubKeys must be an array of public keys"; throw "pubKeys must be an array of public keys";
else if (pubKeys.length < minRequired) else if (pubKeys.length < minRequired)
throw "minimum required should be less than the number of pubKeys"; throw "minimum required should be less than the number of pubKeys";
return coinjs.pubkeys2MultisigAddress(pubKeys, minRequired); if (bech32)
return coinjs.pubkeys2MultisigAddressBech32(pubKeys, minRequired);
else
return coinjs.pubkeys2MultisigAddress(pubKeys, minRequired);
}
btcOperator.decodeRedeemScript = function (redeemScript, bech32 = true) {
let script = coinjs.script();
let decoded = (bech32) ?
script.decodeRedeemScriptBech32(redeemScript) :
script.decodeRedeemScript(redeemScript);
if (!decoded)
return null;
return {
address: decoded.address,
pubKeys: decoded.pubkeys,
redeemScript: decoded.redeemscript,
required: decoded.signaturesRequired
}
} }
//convert from one blockchain to another blockchain (target version) //convert from one blockchain to another blockchain (target version)
btcOperator.convert = {}; btcOperator.convert = {};
btcOperator.convert.wif = function (source_wif, target_version = coinjs.priv) { btcOperator.convert.wif = function (source_wif, target_version = coinjs.priv) {
let keyHex = decodeLegacy(source_wif).hex; let keyHex = util.decodeLegacy(source_wif).hex;
if (!keyHex || keyHex.length < 66 || !/01$/.test(keyHex)) if (!keyHex || keyHex.length < 66 || !/01$/.test(keyHex))
return null; return null;
else else
return encodeLegacy(keyHex, target_version); return util.encodeLegacy(keyHex, target_version);
} }
btcOperator.convert.legacy2legacy = function (source_addr, target_version = coinjs.pub) { btcOperator.convert.legacy2legacy = function (source_addr, target_version = coinjs.pub) {
let rawHex = decodeLegacy(source_addr).hex; let rawHex = util.decodeLegacy(source_addr).hex;
if (!rawHex) if (!rawHex)
return null; return null;
else else
return encodeLegacy(rawHex, target_version); return util.encodeLegacy(rawHex, target_version);
} }
btcOperator.convert.legacy2bech = function (source_addr, target_version = coinjs.bech32.version, target_hrp = coinjs.bech32.hrp) { btcOperator.convert.legacy2bech = function (source_addr, target_version = coinjs.bech32.version, target_hrp = coinjs.bech32.hrp) {
let rawHex = decodeLegacy(source_addr).hex; let rawHex = util.decodeLegacy(source_addr).hex;
if (!rawHex) if (!rawHex)
return null; return null;
else else
return encodeBech32(rawHex, target_version, target_hrp); return util.encodeBech32(rawHex, target_version, target_hrp);
} }
btcOperator.convert.bech2bech = function (source_addr, target_version = coinjs.bech32.version, target_hrp = coinjs.bech32.hrp) { btcOperator.convert.bech2bech = function (source_addr, target_version = coinjs.bech32.version, target_hrp = coinjs.bech32.hrp) {
let rawHex = decodeBech32(source_addr).hex; let rawHex = util.decodeBech32(source_addr).hex;
if (!rawHex) if (!rawHex)
return null; return null;
else else
return encodeBech32(rawHex, target_version, target_hrp); return util.encodeBech32(rawHex, target_version, target_hrp);
} }
btcOperator.convert.bech2legacy = function (source_addr, target_version = coinjs.pub) { btcOperator.convert.bech2legacy = function (source_addr, target_version = coinjs.pub) {
let rawHex = decodeBech32(source_addr).hex; let rawHex = util.decodeBech32(source_addr).hex;
if (!rawHex) if (!rawHex)
return null; return null;
else else
return encodeLegacy(rawHex, target_version); return util.encodeLegacy(rawHex, target_version);
} }
function decodeLegacy(source) { btcOperator.convert.multisig2multisig = function (source_addr, target_version = coinjs.multisig) {
let rawHex = util.decodeLegacy(source_addr).hex;
if (!rawHex)
return null;
else
return util.encodeLegacy(rawHex, target_version);
}
btcOperator.convert.bech2multisig = function (source_addr, target_version = coinjs.multisig) {
let rawHex = util.decodeBech32(source_addr).hex;
if (!rawHex)
return null;
else {
rawHex = Crypto.util.bytesToHex(ripemd160(Crypto.util.hexToBytes(rawHex), { asBytes: true }));
return util.encodeLegacy(rawHex, target_version);
}
}
util.decodeLegacy = function (source) {
var decode = coinjs.base58decode(source); var decode = coinjs.base58decode(source);
var raw = decode.slice(0, decode.length - 4), var raw = decode.slice(0, decode.length - 4),
checksum = decode.slice(decode.length - 4); checksum = decode.slice(decode.length - 4);
@ -169,7 +217,7 @@
asBytes: true asBytes: true
}); });
if (hash[0] != checksum[0] || hash[1] != checksum[1] || hash[2] != checksum[2] || hash[3] != checksum[3]) if (hash[0] != checksum[0] || hash[1] != checksum[1] || hash[2] != checksum[2] || hash[3] != checksum[3])
return null; return false;
let version = raw.shift(); let version = raw.shift();
return { return {
version: version, version: version,
@ -177,7 +225,7 @@
} }
} }
function encodeLegacy(hex, version) { util.encodeLegacy = function (hex, version) {
var bytes = Crypto.util.hexToBytes(hex); var bytes = Crypto.util.hexToBytes(hex);
bytes.unshift(version); bytes.unshift(version);
var hash = Crypto.SHA256(Crypto.SHA256(bytes, { var hash = Crypto.SHA256(Crypto.SHA256(bytes, {
@ -189,10 +237,10 @@
return coinjs.base58encode(bytes.concat(checksum)); return coinjs.base58encode(bytes.concat(checksum));
} }
function decodeBech32(source) { util.decodeBech32 = function (source) {
let decode = coinjs.bech32_decode(source); let decode = coinjs.bech32_decode(source);
if (!decode) if (!decode)
return null; return false;
var raw = decode.data; var raw = decode.data;
let version = raw.shift(); let version = raw.shift();
raw = coinjs.bech32_convert(raw, 5, 8, false); raw = coinjs.bech32_convert(raw, 5, 8, false);
@ -203,7 +251,7 @@
} }
} }
function encodeBech32(hex, version, hrp) { util.encodeBech32 = function (hex, version, hrp) {
var bytes = Crypto.util.hexToBytes(hex); var bytes = Crypto.util.hexToBytes(hex);
bytes = coinjs.bech32_convert(bytes, 8, 5, true); bytes = coinjs.bech32_convert(bytes, 8, 5, true);
bytes.unshift(version) bytes.unshift(version)
@ -213,8 +261,8 @@
//BTC blockchain APIs //BTC blockchain APIs
btcOperator.getBalance = addr => new Promise((resolve, reject) => { btcOperator.getBalance = addr => new Promise((resolve, reject) => {
fetch_api(`get_address_balance/BTC/${addr}`) fetch_api(`q/addressbalance/${addr}`)
.then(result => resolve(parseFloat(result.data.confirmed_balance))) .then(result => resolve(util.Sat_to_BTC(result)))
.catch(error => reject(error)) .catch(error => reject(error))
}); });
@ -222,11 +270,13 @@
BASE_INPUT_SIZE = 41, BASE_INPUT_SIZE = 41,
LEGACY_INPUT_SIZE = 107, LEGACY_INPUT_SIZE = 107,
BECH32_INPUT_SIZE = 27, BECH32_INPUT_SIZE = 27,
BECH32_MULTISIG_INPUT_SIZE = 35,
SEGWIT_INPUT_SIZE = 59, SEGWIT_INPUT_SIZE = 59,
MULTISIG_INPUT_SIZE_ES = 351, MULTISIG_INPUT_SIZE_ES = 351,
BASE_OUTPUT_SIZE = 9, BASE_OUTPUT_SIZE = 9,
LEGACY_OUTPUT_SIZE = 25, LEGACY_OUTPUT_SIZE = 25,
BECH32_OUTPUT_SIZE = 23, BECH32_OUTPUT_SIZE = 23,
BECH32_MULTISIG_OUTPUT_SIZE = 34,
SEGWIT_OUTPUT_SIZE = 23; SEGWIT_OUTPUT_SIZE = 23;
function _redeemScript(addr, key) { function _redeemScript(addr, key) {
@ -249,6 +299,8 @@
return BASE_INPUT_SIZE + LEGACY_INPUT_SIZE; return BASE_INPUT_SIZE + LEGACY_INPUT_SIZE;
case "bech32": case "bech32":
return BASE_INPUT_SIZE + BECH32_INPUT_SIZE; return BASE_INPUT_SIZE + BECH32_INPUT_SIZE;
case "multisigBech32":
return BASE_INPUT_SIZE + BECH32_MULTISIG_INPUT_SIZE;
case "multisig": case "multisig":
switch (coinjs.script().decodeRedeemScript(rs).type) { switch (coinjs.script().decodeRedeemScript(rs).type) {
case "segwit__": case "segwit__":
@ -269,6 +321,8 @@
return BASE_OUTPUT_SIZE + LEGACY_OUTPUT_SIZE; return BASE_OUTPUT_SIZE + LEGACY_OUTPUT_SIZE;
case "bech32": case "bech32":
return BASE_OUTPUT_SIZE + BECH32_OUTPUT_SIZE; return BASE_OUTPUT_SIZE + BECH32_OUTPUT_SIZE;
case "multisigBech32":
return BASE_OUTPUT_SIZE + BECH32_MULTISIG_OUTPUT_SIZE;
case "multisig": case "multisig":
return BASE_OUTPUT_SIZE + SEGWIT_OUTPUT_SIZE; return BASE_OUTPUT_SIZE + SEGWIT_OUTPUT_SIZE;
default: default:
@ -299,7 +353,7 @@
parameters.privkeys[i] = coinjs.privkey2wif(key); parameters.privkeys[i] = coinjs.privkey2wif(key);
}); });
if (invalids.length) if (invalids.length)
throw "Invalid keys:" + invalids; throw "Invalid private key for address:" + invalids;
} }
//receiver-ids (and change-id) //receiver-ids (and change-id)
if (!Array.isArray(parameters.receivers)) if (!Array.isArray(parameters.receivers))
@ -307,8 +361,8 @@
parameters.receivers.forEach(id => !validateAddress(id) ? invalids.push(id) : null); parameters.receivers.forEach(id => !validateAddress(id) ? invalids.push(id) : null);
if (invalids.length) if (invalids.length)
throw "Invalid receivers:" + invalids; throw "Invalid receivers:" + invalids;
if (parameters.change_addr && !validateAddress(parameters.change_addr)) if (parameters.change_address && !validateAddress(parameters.change_address))
throw "Invalid change_address:" + parameters.change_addr; throw "Invalid change_address:" + parameters.change_address;
//fee and amounts //fee and amounts
if ((typeof parameters.fee !== "number" || parameters.fee <= 0) && parameters.fee !== null) //fee = null (auto calc) if ((typeof parameters.fee !== "number" || parameters.fee <= 0) && parameters.fee !== null) //fee = null (auto calc)
throw "Invalid fee:" + parameters.fee; throw "Invalid fee:" + parameters.fee;
@ -323,18 +377,32 @@
return parameters; return parameters;
} }
function createTransaction(senders, redeemScripts, receivers, amounts, fee, change_addr) { function createTransaction(senders, redeemScripts, receivers, amounts, fee, change_address, fee_from_receiver) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let total_amount = parseFloat(amounts.reduce((t, a) => t + a, 0).toFixed(8)); let total_amount = parseFloat(amounts.reduce((t, a) => t + a, 0).toFixed(8));
const tx = coinjs.transaction(); const tx = coinjs.transaction();
let output_size = addOutputs(tx, receivers, amounts, change_addr); let output_size = addOutputs(tx, receivers, amounts, change_address);
addInputs(tx, senders, redeemScripts, total_amount, fee, output_size).then(result => { addInputs(tx, senders, redeemScripts, total_amount, fee, output_size, fee_from_receiver).then(result => {
if (result.change_amount > 0) if (result.change_amount > 0 && result.change_amount > result.fee) //add change amount if any (ignore dust change)
tx.outs[tx.outs.length - 1].value = parseInt(result.change_amount * SATOSHI_IN_BTC); //values are in satoshi tx.outs[tx.outs.length - 1].value = util.BTC_to_Sat(result.change_amount); //values are in satoshi
else if (fee_from_receiver) { //deduce fee from receivers if fee_from_receiver
tx.outs.pop(); //remove the change output if no change_amount let fee_remaining = util.BTC_to_Sat(result.fee);
for (let i = 0; i < tx.outs.length - 1 && fee_remaining > 0; i++) {
if (fee_remaining < tx.outs[i].value) {
tx.outs[i].value -= fee_remaining;
fee_remaining = 0;
} else {
fee_remaining -= tx.outs[i].value;
tx.outs[i].value = 0;
}
}
if (fee_remaining > 0)
return reject("Send amount is less than fee");
}
tx.outs = tx.outs.filter(o => o.value != 0); //remove all output with value 0
result.output_size = output_size; result.output_size = output_size;
result.output_amount = total_amount; result.output_amount = total_amount - (fee_from_receiver ? result.fee : 0);
result.total_size = BASE_TX_SIZE + output_size + result.input_size; result.total_size = BASE_TX_SIZE + output_size + result.input_size;
result.transaction = tx; result.transaction = tx;
resolve(result); resolve(result);
@ -342,10 +410,10 @@
}) })
} }
function addInputs(tx, senders, redeemScripts, total_amount, fee, output_size) { function addInputs(tx, senders, redeemScripts, total_amount, fee, output_size, fee_from_receiver) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (fee !== null) { if (fee !== null) {
addUTXOs(tx, senders, redeemScripts, total_amount + fee, false).then(result => { addUTXOs(tx, senders, redeemScripts, fee_from_receiver ? total_amount : total_amount + fee, false).then(result => {
result.fee = fee; result.fee = fee;
resolve(result); resolve(result);
}).catch(error => reject(error)) }).catch(error => reject(error))
@ -353,7 +421,10 @@
get_fee_rate().then(fee_rate => { get_fee_rate().then(fee_rate => {
let net_fee = BASE_TX_SIZE * fee_rate; let net_fee = BASE_TX_SIZE * fee_rate;
net_fee += (output_size * fee_rate); net_fee += (output_size * fee_rate);
addUTXOs(tx, senders, redeemScripts, total_amount + net_fee, fee_rate).then(result => { (fee_from_receiver ?
addUTXOs(tx, senders, redeemScripts, total_amount, false) :
addUTXOs(tx, senders, redeemScripts, total_amount + net_fee, fee_rate)
).then(result => {
result.fee = parseFloat((net_fee + (result.input_size * fee_rate)).toFixed(8)); result.fee = parseFloat((net_fee + (result.input_size * fee_rate)).toFixed(8));
result.fee_rate = fee_rate; result.fee_rate = fee_rate;
resolve(result); resolve(result);
@ -381,30 +452,31 @@
return reject("Insufficient Balance"); return reject("Insufficient Balance");
let addr = senders[rec_args.n], let addr = senders[rec_args.n],
rs = redeemScripts[rec_args.n]; rs = redeemScripts[rec_args.n];
let addr_type = coinjs.addressDecode(addr).type;
let size_per_input = _sizePerInput(addr, rs); let size_per_input = _sizePerInput(addr, rs);
fetch_api(`get_tx_unspent/BTC/${addr}`).then(result => { fetch_api(`unspent?active=${addr}`).then(result => {
let utxos = result.data.txs; let utxos = result.unspent_outputs;
console.debug("add-utxo", addr, rs, required_amount, utxos); console.debug("add-utxo", addr, rs, required_amount, utxos);
for (let i = 0; i < utxos.length && required_amount > 0; i++) { for (let i = 0; i < utxos.length && required_amount > 0; i++) {
if (!utxos[i].confirmations) //ignore unconfirmed utxo if (!utxos[i].confirmations) //ignore unconfirmed utxo
continue; continue;
var script; var script;
if (!rs || !rs.length) //legacy script if (!rs || !rs.length) //legacy script
script = utxos[i].script_hex; script = utxos[i].script;
else if (((rs.match(/^00/) && rs.length == 44)) || (rs.length == 40 && rs.match(/^[a-f0-9]+$/gi))) { else if (((rs.match(/^00/) && rs.length == 44)) || (rs.length == 40 && rs.match(/^[a-f0-9]+$/gi)) || addr_type === 'multisigBech32') {
//redeemScript for segwit/bech32 //redeemScript for segwit/bech32 and multisig (bech32)
let s = coinjs.script(); let s = coinjs.script();
s.writeBytes(Crypto.util.hexToBytes(rs)); s.writeBytes(Crypto.util.hexToBytes(rs));
s.writeOp(0); s.writeOp(0);
s.writeBytes(coinjs.numToBytes((utxos[i].value * SATOSHI_IN_BTC).toFixed(0), 8)); s.writeBytes(coinjs.numToBytes(utxos[i].value.toFixed(0), 8));
script = Crypto.util.bytesToHex(s.buffer); script = Crypto.util.bytesToHex(s.buffer);
} else //redeemScript for multisig } else //redeemScript for multisig (segwit)
script = rs; script = rs;
tx.addinput(utxos[i].txid, utxos[i].output_no, script, 0xfffffffd /*sequence*/); //0xfffffffd for Replace-by-fee tx.addinput(utxos[i].tx_hash_big_endian, utxos[i].tx_output_n, script, 0xfffffffd /*sequence*/); //0xfffffffd for Replace-by-fee
//update track values //update track values
rec_args.input_size += size_per_input; rec_args.input_size += size_per_input;
rec_args.input_amount += parseFloat(utxos[i].value); rec_args.input_amount += util.Sat_to_BTC(utxos[i].value);
required_amount -= parseFloat(utxos[i].value); required_amount -= util.Sat_to_BTC(utxos[i].value);
if (fee_rate) //automatic fee calculation (dynamic) if (fee_rate) //automatic fee calculation (dynamic)
required_amount += size_per_input * fee_rate; required_amount += size_per_input * fee_rate;
} }
@ -416,14 +488,14 @@
}) })
} }
function addOutputs(tx, receivers, amounts, change_addr) { function addOutputs(tx, receivers, amounts, change_address) {
let size = 0; let size = 0;
for (let i in receivers) { for (let i in receivers) {
tx.addoutput(receivers[i], amounts[i]); tx.addoutput(receivers[i], amounts[i]);
size += _sizePerOutput(receivers[i]); size += _sizePerOutput(receivers[i]);
} }
tx.addoutput(change_addr, 0); tx.addoutput(change_address, 0);
size += _sizePerOutput(change_addr); size += _sizePerOutput(change_address);
return size; return size;
} }
@ -464,9 +536,9 @@
} }
*/ */
btcOperator.sendTx = function (senders, privkeys, receivers, amounts, fee, change_addr = null) { btcOperator.sendTx = function (senders, privkeys, receivers, amounts, fee = null, options = {}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
createSignedTx(senders, privkeys, receivers, amounts, fee, change_addr).then(result => { createSignedTx(senders, privkeys, receivers, amounts, fee, options).then(result => {
debugger; debugger;
broadcastTx(result.transaction.serialize()) broadcastTx(result.transaction.serialize())
.then(txid => resolve(txid)) .then(txid => resolve(txid))
@ -475,7 +547,7 @@
}) })
} }
const createSignedTx = btcOperator.createSignedTx = function (senders, privkeys, receivers, amounts, fee = null, change_addr = null) { const createSignedTx = btcOperator.createSignedTx = function (senders, privkeys, receivers, amounts, fee = null, options = {}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
({ ({
@ -489,7 +561,7 @@
receivers, receivers,
amounts, amounts,
fee, fee,
change_addr change_address: options.change_address
})); }));
} catch (e) { } catch (e) {
return reject(e) return reject(e)
@ -504,7 +576,7 @@
if (redeemScripts.includes(null)) //TODO: segwit if (redeemScripts.includes(null)) //TODO: segwit
return reject("Unable to get redeem-script"); return reject("Unable to get redeem-script");
//create transaction //create transaction
createTransaction(senders, redeemScripts, receivers, amounts, fee, change_addr || senders[0]).then(result => { createTransaction(senders, redeemScripts, receivers, amounts, fee, options.change_address || senders[0], options.fee_from_receiver).then(result => {
let tx = result.transaction; let tx = result.transaction;
console.debug("Unsigned:", tx.serialize()); console.debug("Unsigned:", tx.serialize());
new Set(wif_keys).forEach(key => console.debug("Signing key:", key, tx.sign(key, 1 /*sighashtype*/))); //Sign the tx using private key WIF new Set(wif_keys).forEach(key => console.debug("Signing key:", key, tx.sign(key, 1 /*sighashtype*/))); //Sign the tx using private key WIF
@ -514,7 +586,7 @@
}) })
} }
btcOperator.createTx = function (senders, receivers, amounts, fee = null, change_addr = null) { btcOperator.createTx = function (senders, receivers, amounts, fee = null, options = {}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
({ ({
@ -526,7 +598,7 @@
receivers, receivers,
amounts, amounts,
fee, fee,
change_addr change_address: options.change_address
})); }));
} catch (e) { } catch (e) {
return reject(e) return reject(e)
@ -535,7 +607,7 @@
if (redeemScripts.includes(null)) //TODO: segwit if (redeemScripts.includes(null)) //TODO: segwit
return reject("Unable to get redeem-script"); return reject("Unable to get redeem-script");
//create transaction //create transaction
createTransaction(senders, redeemScripts, receivers, amounts, fee, change_addr || senders[0]).then(result => { createTransaction(senders, redeemScripts, receivers, amounts, fee, options.change_address || senders[0], options.fee_from_receiver).then(result => {
result.tx_hex = result.transaction.serialize(); result.tx_hex = result.transaction.serialize();
delete result.transaction; delete result.transaction;
resolve(result); resolve(result);
@ -543,14 +615,17 @@
}) })
} }
btcOperator.createMultiSigTx = function (sender, redeemScript, receivers, amounts, fee = null) { btcOperator.createMultiSigTx = function (sender, redeemScript, receivers, amounts, fee = null, options = {}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
//validate tx parameters //validate tx parameters
if (validateAddress(sender) !== "multisig") let addr_type = validateAddress(sender);
if (!(["multisig", "multisigBech32"].includes(addr_type)))
return reject("Invalid sender (multisig):" + sender); return reject("Invalid sender (multisig):" + sender);
else { else {
let script = coinjs.script(); let script = coinjs.script();
let decode = script.decodeRedeemScript(redeemScript); let decode = (addr_type == "multisig") ?
script.decodeRedeemScript(redeemScript) :
script.decodeRedeemScriptBech32(redeemScript);
if (!decode || decode.address !== sender) if (!decode || decode.address !== sender)
return reject("Invalid redeem-script"); return reject("Invalid redeem-script");
} }
@ -561,13 +636,14 @@
} = validateTxParameters({ } = validateTxParameters({
receivers, receivers,
amounts, amounts,
fee fee,
change_address: options.change_address
})); }));
} catch (e) { } catch (e) {
return reject(e) return reject(e)
} }
//create transaction //create transaction
createTransaction([sender], [redeemScript], receivers, amounts, fee, sender).then(result => { createTransaction([sender], [redeemScript], receivers, amounts, fee, options.change_address || sender, options.fee_from_receiver).then(result => {
result.tx_hex = result.transaction.serialize(); result.tx_hex = result.transaction.serialize();
delete result.transaction; delete result.transaction;
resolve(result); resolve(result);
@ -604,10 +680,10 @@
let n = []; let n = [];
for (let i in tx.ins) { for (let i in tx.ins) {
var s = tx.extractScriptKey(i); var s = tx.extractScriptKey(i);
if (s['type'] !== 'multisig') if (s['type'] !== 'multisig' && s['type'] !== 'multisig_bech32')
n.push(s.signed == 'true' || (tx.witness[i] && tx.witness[i].length == 2)) n.push(s.signed == 'true' || (tx.witness[i] && tx.witness[i].length == 2))
else { else {
var rs = coinjs.script().decodeRedeemScript(s.script); var rs = coinjs.script().decodeRedeemScript(s.script); //will work for bech32 too, as only address is diff
let x = { let x = {
s: s['signatures'], s: s['signatures'],
r: rs['signaturesRequired'], r: rs['signaturesRequired'],
@ -627,20 +703,23 @@
btcOperator.checkIfSameTx = function (tx1, tx2) { btcOperator.checkIfSameTx = function (tx1, tx2) {
tx1 = deserializeTx(tx1); tx1 = deserializeTx(tx1);
tx2 = deserializeTx(tx2); tx2 = deserializeTx(tx2);
//compare input and output length
if (tx1.ins.length !== tx2.ins.length || tx1.outs.length !== tx2.outs.length) if (tx1.ins.length !== tx2.ins.length || tx1.outs.length !== tx2.outs.length)
return false; return false;
//compare inputs
for (let i = 0; i < tx1.ins.length; i++) for (let i = 0; i < tx1.ins.length; i++)
if (tx1.ins[i].outpoint.hash !== tx2.ins[i].outpoint.hash || tx1.ins[i].outpoint.index !== tx2.ins[i].outpoint.index) if (tx1.ins[i].outpoint.hash !== tx2.ins[i].outpoint.hash || tx1.ins[i].outpoint.index !== tx2.ins[i].outpoint.index)
return false; return false;
for (let i = 0; i < tx2.ins.length; i++) //compare outputs
for (let i = 0; i < tx1.outs.length; i++)
if (tx1.outs[i].value !== tx2.outs[i].value || Crypto.util.bytesToHex(tx1.outs[i].script.buffer) !== Crypto.util.bytesToHex(tx2.outs[i].script.buffer)) if (tx1.outs[i].value !== tx2.outs[i].value || Crypto.util.bytesToHex(tx1.outs[i].script.buffer) !== Crypto.util.bytesToHex(tx2.outs[i].script.buffer))
return false; return false;
return true; return true;
} }
const getTxOutput = (txid, i) => new Promise((resolve, reject) => { const getTxOutput = (txid, i) => new Promise((resolve, reject) => {
fetch_api(`get_tx_outputs/BTC/${txid}/${i}`) fetch_api(`rawtx/${txid}`)
.then(result => resolve(result.data.outputs)) .then(result => resolve(result.out[i]))
.catch(error => reject(error)) .catch(error => reject(error))
}); });
@ -654,8 +733,8 @@
promises.push(getTxOutput(tx.ins[i].outpoint.hash, tx.ins[i].outpoint.index)); promises.push(getTxOutput(tx.ins[i].outpoint.hash, tx.ins[i].outpoint.index));
Promise.all(promises).then(inputs => { Promise.all(promises).then(inputs => {
result.inputs = inputs.map(inp => Object({ result.inputs = inputs.map(inp => Object({
address: inp.address, address: inp.addr,
value: parseFloat(inp.value) value: util.Sat_to_BTC(inp.value)
})); }));
let signed = checkSigned(tx, false); let signed = checkSigned(tx, false);
result.inputs.forEach((inp, i) => inp.signed = signed[i]); result.inputs.forEach((inp, i) => inp.signed = signed[i]);
@ -663,18 +742,18 @@
result.outputs = tx.outs.map(out => { result.outputs = tx.outs.map(out => {
var address; var address;
switch (out.script.chunks[0]) { switch (out.script.chunks[0]) {
case 0: //bech32 case 0: //bech32, multisig-bech32
address = encodeBech32(Crypto.util.bytesToHex(out.script.chunks[1]), coinjs.bech32.version, coinjs.bech32.hrp); address = util.encodeBech32(Crypto.util.bytesToHex(out.script.chunks[1]), coinjs.bech32.version, coinjs.bech32.hrp);
break; break;
case 169: //multisig, segwit case 169: //segwit, multisig-segwit
address = encodeLegacy(Crypto.util.bytesToHex(out.script.chunks[1]), coinjs.multisig); address = util.encodeLegacy(Crypto.util.bytesToHex(out.script.chunks[1]), coinjs.multisig);
break; break;
case 118: //legacy case 118: //legacy
address = encodeLegacy(Crypto.util.bytesToHex(out.script.chunks[2]), coinjs.pub); address = util.encodeLegacy(Crypto.util.bytesToHex(out.script.chunks[2]), coinjs.pub);
} }
return { return {
address, address,
value: parseFloat(out.value / SATOSHI_IN_BTC) value: util.Sat_to_BTC(out.value)
} }
}); });
//Parse Totals //Parse Totals
@ -695,22 +774,115 @@
return Crypto.util.bytesToHex(txid); return Crypto.util.bytesToHex(txid);
} }
btcOperator.getTx = txid => new Promise((resolve, reject) => { const getLatestBlock = btcOperator.getLatestBlock = () => new Promise((resolve, reject) => {
fetch_api(`tx/BTC/${txid}`) fetch_api(`q/getblockcount`)
.then(result => resolve(result.data)) .then(result => resolve(result))
.catch(error => reject(error)) .catch(error => reject(error))
})
btcOperator.getTx = txid => new Promise((resolve, reject) => {
fetch_api(`rawtx/${txid}`).then(result => {
getLatestBlock().then(latest_block => resolve({
block: result.block_height,
txid: result.hash,
time: result.time * 1000,
confirmations: result.block_height === null ? 0 : latest_block - result.block_height, //calculate confirmations using latest block number as api doesnt relay it
size: result.size,
fee: util.Sat_to_BTC(result.fee),
inputs: result.inputs.map(i => Object({ address: i.prev_out.addr, value: util.Sat_to_BTC(i.prev_out.value) })),
total_input_value: util.Sat_to_BTC(result.inputs.reduce((a, i) => a + i.prev_out.value, 0)),
outputs: result.out.map(o => Object({ address: o.addr, value: util.Sat_to_BTC(o.value) })),
total_output_value: util.Sat_to_BTC(result.out.reduce((a, o) => a += o.value, 0)),
}))
}).catch(error => reject(error))
}); });
btcOperator.getAddressData = addr => new Promise((resolve, reject) => { btcOperator.getTx.hex = txid => new Promise((resolve, reject) => {
fetch_api(`address/BTC/${addr}`) fetch_api(`rawtx/${txid}?format=hex`, false)
.then(result => resolve(result.data)) .then(result => resolve(result))
.catch(error => reject(error)) .catch(error => reject(error))
})
btcOperator.getAddressData = address => new Promise((resolve, reject) => {
fetch_api(`rawaddr/${address}`).then(data => {
let details = {};
details.balance = util.Sat_to_BTC(data.final_balance);
details.address = data.address;
details.txs = data.txs.map(tx => {
let d = {
txid: tx.hash,
time: tx.time * 1000, //s to ms
block: tx.block_height,
}
//sender list
d.tx_senders = {};
tx.inputs.forEach(i => {
if (i.prev_out.addr in d.tx_senders)
d.tx_senders[i.prev_out.addr] += i.prev_out.value;
else d.tx_senders[i.prev_out.addr] = i.prev_out.value;
});
d.tx_input_value = 0;
for (let s in d.tx_senders) {
let val = d.tx_senders[s];
d.tx_senders[s] = util.Sat_to_BTC(val);
d.tx_input_value += val;
}
d.tx_input_value = util.Sat_to_BTC(d.tx_input_value);
//receiver list
d.tx_receivers = {};
tx.out.forEach(o => {
if (o.addr in d.tx_receivers)
d.tx_receivers[o.addr] += o.value;
else d.tx_receivers[o.addr] = o.value;
});
d.tx_output_value = 0;
for (let r in d.tx_receivers) {
let val = d.tx_receivers[r];
d.tx_receivers[r] = util.Sat_to_BTC(val);
d.tx_output_value += val;
}
d.tx_output_value = util.Sat_to_BTC(d.tx_output_value);
d.tx_fee = util.Sat_to_BTC(tx.fee);
//tx type
if (tx.result > 0) { //net > 0, balance inc => type=in
d.type = "in";
d.amount = util.Sat_to_BTC(tx.result);
d.sender = Object.keys(d.tx_senders).filter(s => s !== address);
} else if (Object.keys(d.tx_receivers).some(r => r !== address)) { //net < 0, balance dec & receiver present => type=out
d.type = "out";
d.amount = util.Sat_to_BTC(tx.result * -1);
d.receiver = Object.keys(d.tx_receivers).filter(r => r !== address);
d.fee = d.tx_fee;
} else { //net < 0 (fee) & no other id in receiver list => type=self
d.type = "self";
d.amount = d.tx_receivers[address];
d.address = address
}
return d;
})
resolve(details);
}).catch(error => reject(error))
}); });
btcOperator.getBlock = block => new Promise((resolve, reject) => { btcOperator.getBlock = block => new Promise((resolve, reject) => {
fetch_api(`get_block/BTC/${block}`) fetch_api(`rawblock/${block}`).then(result => resolve({
.then(result => resolve(result.data)) height: result.height,
.catch(error => reject(error)) hash: result.hash,
merkle_root: result.mrkl_root,
prev_block: result.prev_block,
next_block: result.next_block[0],
size: result.size,
time: result.time * 1000, //s to ms
txs: result.tx.map(t => Object({
fee: t.fee,
size: t.size,
inputs: t.inputs.map(i => Object({ address: i.prev_out.addr, value: util.Sat_to_BTC(i.prev_out.value) })),
total_input_value: util.Sat_to_BTC(t.inputs.reduce((a, i) => a + i.prev_out.value, 0)),
outputs: t.out.map(o => Object({ address: o.addr, value: util.Sat_to_BTC(o.value) })),
total_output_value: util.Sat_to_BTC(t.out.reduce((a, o) => a += o.value, 0)),
}))
})).catch(error => reject(error))
}); });
})('object' === typeof module ? module.exports : window.btcOperator = {}); })('object' === typeof module ? module.exports : window.btcOperator = {});

View File

@ -1,4 +1,4 @@
(function(EXPORTS) { //compactIDB v2.1.0 (function (EXPORTS) { //compactIDB v2.1.2
/* Compact IndexedDB operations */ /* Compact IndexedDB operations */
'use strict'; 'use strict';
const compactIDB = EXPORTS; const compactIDB = EXPORTS;
@ -59,7 +59,7 @@
}) })
} }
compactIDB.initDB = function(dbName, objectStores = {}) { compactIDB.initDB = function (dbName, objectStores = {}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!(objectStores instanceof Object)) if (!(objectStores instanceof Object))
return reject('ObjectStores must be an object or array') return reject('ObjectStores must be an object or array')
@ -87,14 +87,14 @@
resolve("Initiated IndexedDB"); resolve("Initiated IndexedDB");
else else
upgradeDB(dbName, a_obs, d_obs) upgradeDB(dbName, a_obs, d_obs)
.then(result => resolve(result)) .then(result => resolve(result))
.catch(error => reject(error)) .catch(error => reject(error))
db.close(); db.close();
} }
}); });
} }
const openDB = compactIDB.openDB = function(dbName = defaultDB) { const openDB = compactIDB.openDB = function (dbName = defaultDB) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
var idb = indexedDB.open(dbName); var idb = indexedDB.open(dbName);
idb.onerror = (event) => reject("Error in opening IndexedDB"); 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) => { return new Promise((resolve, reject) => {
var deleteReq = indexedDB.deleteDatabase(dbName);; var deleteReq = indexedDB.deleteDatabase(dbName);;
deleteReq.onerror = (event) => reject("Error deleting database!"); 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) => { return new Promise((resolve, reject) => {
openDB(dbName).then(db => { openDB(dbName).then(db => {
var obs = db.transaction(obsName, "readwrite").objectStore(obsName); 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) => { return new Promise((resolve, reject) => {
openDB(dbName).then(db => { openDB(dbName).then(db => {
var obs = db.transaction(obsName, "readwrite").objectStore(obsName); 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) => { return new Promise((resolve, reject) => {
openDB(dbName).then(db => { openDB(dbName).then(db => {
var obs = db.transaction(obsName, "readwrite").objectStore(obsName); 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) => { return new Promise((resolve, reject) => {
openDB(dbName).then(db => { openDB(dbName).then(db => {
var obs = db.transaction(obsName, "readwrite").objectStore(obsName); 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) => { return new Promise((resolve, reject) => {
openDB(dbName).then(db => { openDB(dbName).then(db => {
var obs = db.transaction(obsName, "readonly").objectStore(obsName); 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) => { return new Promise((resolve, reject) => {
openDB(dbName).then(db => { openDB(dbName).then(db => {
var obs = db.transaction(obsName, "readonly").objectStore(obsName); 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.lowerKey = options.atKey || options.lowerKey || 0
options.upperKey = options.atKey || options.upperKey || false options.upperKey = options.atKey || options.upperKey || false
options.patternEval = options.patternEval || ((k, v) => { options.patternEval = options.patternEval || ((k, v) => true);
return true
})
options.limit = options.limit || false; options.limit = options.limit || false;
options.reverse = options.reverse || false;
options.lastOnly = options.lastOnly || false options.lastOnly = options.lastOnly || false
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
openDB(dbName).then(db => { openDB(dbName).then(db => {
@ -237,17 +236,16 @@
var filteredResult = {} var filteredResult = {}
let curReq = obs.openCursor( let curReq = obs.openCursor(
options.upperKey ? IDBKeyRange.bound(options.lowerKey, options.upperKey) : IDBKeyRange.lowerBound(options.lowerKey), 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) => { curReq.onsuccess = (evt) => {
var cursor = evt.target.result; var cursor = evt.target.result;
if (cursor) { if (!cursor || (options.limit && options.limit <= Object.keys(filteredResult).length))
if (options.patternEval(cursor.primaryKey, cursor.value)) { return resolve(filteredResult); //reached end of key list or limit reached
filteredResult[cursor.primaryKey] = cursor.value; else if (options.patternEval(cursor.primaryKey, cursor.value)) {
options.lastOnly ? resolve(filteredResult) : cursor.continue(); filteredResult[cursor.primaryKey] = cursor.value;
} else options.lastOnly ? resolve(filteredResult) : cursor.continue();
cursor.continue();
} else } else
resolve(filteredResult); cursor.continue();
} }
curReq.onerror = (evt) => reject(`Search unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`); curReq.onerror = (evt) => reject(`Search unsuccessful [${evt.target.error.name}] ${evt.target.error.message}`);
db.close(); db.close();

View File

@ -1,4 +1,4 @@
(function(EXPORTS) { //floBlockchainAPI v2.3.3b (function (EXPORTS) { //floBlockchainAPI v2.5.6a
/* FLO Blockchain Operator to send/receive data from blockchain using API calls*/ /* FLO Blockchain Operator to send/receive data from blockchain using API calls*/
'use strict'; 'use strict';
const floBlockchainAPI = EXPORTS; const floBlockchainAPI = EXPORTS;
@ -6,14 +6,24 @@
const DEFAULT = { const DEFAULT = {
blockchain: floGlobals.blockchain, blockchain: floGlobals.blockchain,
apiURL: { apiURL: {
FLO: ['https://flosight.duckdns.org/'], FLO: ['https://flosight.ranchimall.net/'],
FLO_TEST: ['https://testnet-flosight.duckdns.org', 'https://testnet.flocha.in/'] FLO_TEST: ['https://flosight-testnet.ranchimall.net/']
}, },
sendAmt: 0.001, sendAmt: 0.0003,
fee: 0.0005, fee: 0.0002,
minChangeAmt: 0.0002,
receiverID: floGlobals.adminID receiverID: floGlobals.adminID
}; };
const SATOSHI_IN_BTC = 1e8;
const isUndefined = val => typeof val === 'undefined';
const util = floBlockchainAPI.util = {};
util.Sat_to_FLO = value => parseFloat((value / SATOSHI_IN_BTC).toFixed(8));
util.FLO_to_Sat = value => parseInt(value * SATOSHI_IN_BTC);
util.toFixed = value => parseFloat((value).toFixed(8));
Object.defineProperties(floBlockchainAPI, { Object.defineProperties(floBlockchainAPI, {
sendAmt: { sendAmt: {
get: () => DEFAULT.sendAmt, get: () => DEFAULT.sendAmt,
@ -102,9 +112,11 @@
}); });
//Promised function to get data from API //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) => { 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) fetch_api(apicall)
.then(result => resolve(result)) .then(result => resolve(result))
.catch(error => reject(error)); .catch(error => reject(error));
@ -112,25 +124,55 @@
} }
//Get balance for the given Address //Get balance for the given Address
const getBalance = floBlockchainAPI.getBalance = function(addr) { const getBalance = floBlockchainAPI.getBalance = function (addr, after = null) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
promisedAPI(`api/addr/${addr}/balance`) let api = `api/addr/${addr}/balance`, query_params = {};
.then(balance => resolve(parseFloat(balance))) if (after) {
.catch(error => reject(error)); if (typeof after === 'string' && /^[0-9a-z]{64}$/i.test(after))
query_params.after = after;
else return reject("Invalid 'after' parameter");
}
promisedAPI(api, query_params).then(result => {
if (typeof result === 'object' && result.lastItem) {
getBalance(addr, result.lastItem)
.then(r => resolve(util.toFixed(r + result.data)))
.catch(error => reject(error))
} else resolve(result);
}).catch(error => reject(error))
}); });
} }
//Send Tx to blockchain const getUTXOs = address => new Promise((resolve, reject) => {
const sendTx = floBlockchainAPI.sendTx = function(senderAddr, receiverAddr, sendAmt, privKey, floData = '', strict_utxo = true) { promisedAPI(`api/addr/${address}/utxo`)
.then(utxo => resolve(utxo))
.catch(error => reject(error))
})
const getUnconfirmedSpent = address => new Promise((resolve, reject) => {
readTxs(address, { mempool: "only" }).then(result => {
let unconfirmedSpent = {};
for (let tx of result.items)
if (tx.confirmations == 0)
for (let vin of tx.vin)
if (vin.addr === address) {
if (Array.isArray(unconfirmedSpent[vin.txid]))
unconfirmedSpent[vin.txid].push(vin.vout);
else
unconfirmedSpent[vin.txid] = [vin.vout];
}
resolve(unconfirmedSpent);
}).catch(error => reject(error))
})
//create a transaction with single sender
const createTx = function (senderAddr, receiverAddr, sendAmt, floData = '', strict_utxo = true) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!floCrypto.validateASCII(floData)) if (!floCrypto.validateASCII(floData))
return reject("Invalid FLO_Data: only printable ASCII characters are allowed"); return reject("Invalid FLO_Data: only printable ASCII characters are allowed");
else if (!floCrypto.validateFloID(senderAddr)) else if (!floCrypto.validateFloID(senderAddr, true))
return reject(`Invalid address : ${senderAddr}`); return reject(`Invalid address : ${senderAddr}`);
else if (!floCrypto.validateFloID(receiverAddr)) else if (!floCrypto.validateFloID(receiverAddr))
return reject(`Invalid address : ${receiverAddr}`); return reject(`Invalid address : ${receiverAddr}`);
else if (privKey.length < 1 || !floCrypto.verifyPrivKey(privKey, senderAddr))
return reject("Invalid Private key!");
else if (typeof sendAmt !== 'number' || sendAmt <= 0) else if (typeof sendAmt !== 'number' || sendAmt <= 0)
return reject(`Invalid sendAmt : ${sendAmt}`); return reject(`Invalid sendAmt : ${sendAmt}`);
@ -138,56 +180,63 @@
var fee = DEFAULT.fee; var fee = DEFAULT.fee;
if (balance < sendAmt + fee) if (balance < sendAmt + fee)
return reject("Insufficient FLO balance!"); return reject("Insufficient FLO balance!");
//get unconfirmed tx list getUnconfirmedSpent(senderAddr).then(unconfirmedSpent => {
promisedAPI(`api/addr/${senderAddr}`).then(result => { getUTXOs(senderAddr).then(utxos => {
readTxs(senderAddr, 0, result.unconfirmedTxApperances).then(result => { //form/construct the transaction data
let unconfirmedSpent = {}; var trx = bitjs.transaction();
for (let tx of result.items) var utxoAmt = 0.0;
if (tx.confirmations == 0) for (var i = utxos.length - 1;
for (let vin of tx.vin) (i >= 0) && (utxoAmt < sendAmt + fee); i--) {
if (vin.addr === senderAddr) { //use only utxos with confirmations (strict_utxo mode)
if (Array.isArray(unconfirmedSpent[vin.txid])) if (utxos[i].confirmations || !strict_utxo) {
unconfirmedSpent[vin.txid].push(vin.vout); if (utxos[i].txid in unconfirmedSpent && unconfirmedSpent[utxos[i].txid].includes(utxos[i].vout))
else continue; //A transaction has already used the utxo, but is unconfirmed.
unconfirmedSpent[vin.txid] = [vin.vout]; trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey);
} utxoAmt += utxos[i].amount;
//get utxos list };
promisedAPI(`api/addr/${senderAddr}/utxo`).then(utxos => { }
//form/construct the transaction data if (utxoAmt < sendAmt + fee)
var trx = bitjs.transaction(); reject("Insufficient FLO: Some UTXOs are unconfirmed");
var utxoAmt = 0.0; else {
for (var i = utxos.length - 1; trx.addoutput(receiverAddr, sendAmt);
(i >= 0) && (utxoAmt < sendAmt + fee); i--) { var change = utxoAmt - sendAmt - fee;
//use only utxos with confirmations (strict_utxo mode) if (change > DEFAULT.minChangeAmt)
if (utxos[i].confirmations || !strict_utxo) { trx.addoutput(senderAddr, change);
if (utxos[i].txid in unconfirmedSpent && unconfirmedSpent[utxos[i].txid].includes(utxos[i].vout)) trx.addflodata(floData.replace(/\n/g, ' '));
continue; //A transaction has already used the utxo, but is unconfirmed. resolve(trx);
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)) }).catch(error => reject(error))
}).catch(error => reject(error)) }).catch(error => reject(error))
})
}
floBlockchainAPI.createTx = function (senderAddr, receiverAddr, sendAmt, floData = '', strict_utxo = true) {
return new Promise((resolve, reject) => {
createTx(senderAddr, receiverAddr, sendAmt, floData, strict_utxo)
.then(trx => resolve(trx.serialize()))
.catch(error => reject(error))
})
}
//Send Tx to blockchain
const sendTx = floBlockchainAPI.sendTx = function (senderAddr, receiverAddr, sendAmt, privKey, floData = '', strict_utxo = true) {
return new Promise((resolve, reject) => {
if (!floCrypto.validateFloID(senderAddr, true))
return reject(`Invalid address : ${senderAddr}`);
else if (privKey.length < 1 || !floCrypto.verifyPrivKey(privKey, senderAddr))
return reject("Invalid Private key!");
createTx(senderAddr, receiverAddr, sendAmt, floData, strict_utxo).then(trx => {
var signedTxHash = trx.sign(privKey, 1);
broadcastTx(signedTxHash)
.then(txid => resolve(txid))
.catch(error => reject(error))
}).catch(error => reject(error))
}); });
} }
//Write Data into blockchain //Write Data into blockchain
floBlockchainAPI.writeData = function(senderAddr, data, privKey, receiverAddr = DEFAULT.receiverID, options = {}) { floBlockchainAPI.writeData = function (senderAddr, data, privKey, receiverAddr = DEFAULT.receiverID, options = {}) {
let strict_utxo = options.strict_utxo === false ? false : true, let strict_utxo = options.strict_utxo === false ? false : true,
sendAmt = isNaN(options.sendAmt) ? DEFAULT.sendAmt : options.sendAmt; sendAmt = isNaN(options.sendAmt) ? DEFAULT.sendAmt : options.sendAmt;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -200,9 +249,9 @@
} }
//merge all UTXOs of a given floID into a single UTXO //merge all UTXOs of a given floID into a single UTXO
floBlockchainAPI.mergeUTXOs = function(floID, privKey, floData = '') { floBlockchainAPI.mergeUTXOs = function (floID, privKey, floData = '') {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!floCrypto.validateFloID(floID)) if (!floCrypto.validateFloID(floID, true))
return reject(`Invalid floID`); return reject(`Invalid floID`);
if (!floCrypto.verifyPrivKey(privKey, floID)) if (!floCrypto.verifyPrivKey(privKey, floID))
return reject("Invalid Private Key"); return reject("Invalid Private Key");
@ -211,7 +260,7 @@
var trx = bitjs.transaction(); var trx = bitjs.transaction();
var utxoAmt = 0.0; var utxoAmt = 0.0;
var fee = DEFAULT.fee; var fee = DEFAULT.fee;
promisedAPI(`api/addr/${floID}/utxo`).then(utxos => { getUTXOs(floID).then(utxos => {
for (var i = utxos.length - 1; i >= 0; i--) for (var i = utxos.length - 1; i >= 0; i--)
if (utxos[i].confirmations) { if (utxos[i].confirmations) {
trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey); trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey);
@ -227,6 +276,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(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--) {
//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 /**Write data into blockchain from (and/or) to multiple floID
* @param {Array} senderPrivKeys List of sender private-keys * @param {Array} senderPrivKeys List of sender private-keys
* @param {string} data FLO data of the txn * @param {string} data FLO data of the txn
@ -234,11 +333,11 @@
* @param {boolean} preserveRatio (optional) preserve ratio or equal contribution * @param {boolean} preserveRatio (optional) preserve ratio or equal contribution
* @return {Promise} * @return {Promise}
*/ */
floBlockchainAPI.writeDataMultiple = function(senderPrivKeys, data, receivers = [DEFAULT.receiverID], preserveRatio = true) { floBlockchainAPI.writeDataMultiple = function (senderPrivKeys, data, receivers = [DEFAULT.receiverID], options = {}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!Array.isArray(senderPrivKeys)) if (!Array.isArray(senderPrivKeys))
return reject("Invalid senderPrivKeys: SenderPrivKeys must be Array"); return reject("Invalid senderPrivKeys: SenderPrivKeys must be Array");
if (!preserveRatio) { if (options.preserveRatio === false) {
let tmp = {}; let tmp = {};
let amount = (DEFAULT.sendAmt * receivers.length) / senderPrivKeys.length; let amount = (DEFAULT.sendAmt * receivers.length) / senderPrivKeys.length;
senderPrivKeys.forEach(key => tmp[key] = amount); senderPrivKeys.forEach(key => tmp[key] = amount);
@ -248,7 +347,7 @@
return reject("Invalid receivers: Receivers must be Array"); return reject("Invalid receivers: Receivers must be Array");
else { else {
let tmp = {}; let tmp = {};
let amount = DEFAULT.sendAmt; let amount = options.sendAmt || DEFAULT.sendAmt;
receivers.forEach(floID => tmp[floID] = amount); receivers.forEach(floID => tmp[floID] = amount);
receivers = tmp receivers = tmp
} }
@ -266,7 +365,7 @@
* @param {string} floData FLO data of the txn * @param {string} floData FLO data of the txn
* @return {Promise} * @return {Promise}
*/ */
const sendTxMultiple = floBlockchainAPI.sendTxMultiple = function(senderPrivKeys, receivers, floData = '') { const sendTxMultiple = floBlockchainAPI.sendTxMultiple = function (senderPrivKeys, receivers, floData = '') {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!floCrypto.validateASCII(floData)) if (!floCrypto.validateASCII(floData))
return reject("Invalid FLO_Data: only printable ASCII characters are allowed"); return reject("Invalid FLO_Data: only printable ASCII characters are allowed");
@ -378,9 +477,8 @@
//Get the UTXOs of the senders //Get the UTXOs of the senders
let promises = []; let promises = [];
for (let floID in senders) for (let floID in senders)
promises.push(promisedAPI(`api/addr/${floID}/utxo`)); promises.push(getUTXOs(floID));
Promise.all(promises).then(results => { Promise.all(promises).then(results => {
let wifSeq = [];
var trx = bitjs.transaction(); var trx = bitjs.transaction();
for (let floID in senders) { for (let floID in senders) {
let utxos = results.shift(); let utxos = results.shift();
@ -390,13 +488,11 @@
sendAmt = totalSendAmt * ratio; sendAmt = totalSendAmt * ratio;
} else } else
sendAmt = senders[floID].coins + dividedFee; sendAmt = senders[floID].coins + dividedFee;
let wif = senders[floID].wif;
let utxoAmt = 0.0; let utxoAmt = 0.0;
for (let i = utxos.length - 1; for (let i = utxos.length - 1;
(i >= 0) && (utxoAmt < sendAmt); i--) { (i >= 0) && (utxoAmt < sendAmt); i--) {
if (utxos[i].confirmations) { if (utxos[i].confirmations) {
trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey); trx.addinput(utxos[i].txid, utxos[i].vout, utxos[i].scriptPubKey);
wifSeq.push(wif);
utxoAmt += utxos[i].amount; utxoAmt += utxos[i].amount;
} }
} }
@ -409,8 +505,8 @@
for (let floID in receivers) for (let floID in receivers)
trx.addoutput(floID, receivers[floID]); trx.addoutput(floID, receivers[floID]);
trx.addflodata(floData.replace(/\n/g, ' ')); trx.addflodata(floData.replace(/\n/g, ' '));
for (let i = 0; i < wifSeq.length; i++) for (let floID in senders)
trx.signinput(i, wifSeq[i], 1); trx.sign(senders[floID].wif, 1);
var signedTxHash = trx.serialize(); var signedTxHash = trx.serialize();
broadcastTx(signedTxHash) broadcastTx(signedTxHash)
.then(txid => resolve(txid)) .then(txid => resolve(txid))
@ -420,8 +516,261 @@
}) })
} }
//Create a multisig transaction
const createMultisigTx = function (redeemScript, receivers, amounts, floData = '', strict_utxo = true) {
return new Promise((resolve, reject) => {
var multisig = floCrypto.decodeRedeemScript(redeemScript);
//validate multisig script and flodata
if (!multisig)
return reject(`Invalid redeemScript`);
var senderAddr = multisig.address;
if (!floCrypto.validateFloID(senderAddr))
return reject(`Invalid multisig : ${senderAddr}`);
else if (!floCrypto.validateASCII(floData))
return reject("Invalid FLO_Data: only printable ASCII characters are allowed");
//validate receiver addresses
if (!Array.isArray(receivers))
receivers = [receivers];
for (let r of receivers)
if (!floCrypto.validateFloID(r))
return reject(`Invalid address : ${r}`);
//validate amounts
if (!Array.isArray(amounts))
amounts = [amounts];
if (amounts.length != receivers.length)
return reject("Receivers and amounts have different length");
var sendAmt = 0;
for (let a of amounts) {
if (typeof a !== 'number' || a <= 0)
return reject(`Invalid amount : ${a}`);
sendAmt += a;
}
getBalance(senderAddr).then(balance => {
var fee = DEFAULT.fee;
if (balance < sendAmt + fee)
return reject("Insufficient FLO balance!");
getUnconfirmedSpent(senderAddr).then(unconfirmedSpent => {
getUTXOs(senderAddr).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, redeemScript); //for multisig, script=redeemScript
utxoAmt += utxos[i].amount;
};
}
if (utxoAmt < sendAmt + fee)
reject("Insufficient FLO: Some UTXOs are unconfirmed");
else {
for (let i in receivers)
trx.addoutput(receivers[i], amounts[i]);
var change = utxoAmt - sendAmt - fee;
if (change > DEFAULT.minChangeAmt)
trx.addoutput(senderAddr, change);
trx.addflodata(floData.replace(/\n/g, ' '));
resolve(trx);
}
}).catch(error => reject(error))
}).catch(error => reject(error))
}).catch(error => reject(error))
});
}
//Same as above, but explict call should return serialized tx-hex
floBlockchainAPI.createMultisigTx = function (redeemScript, receivers, amounts, floData = '', strict_utxo = true) {
return new Promise((resolve, reject) => {
createMultisigTx(redeemScript, receivers, amounts, floData, strict_utxo)
.then(trx => resolve(trx.serialize()))
.catch(error => reject(error))
})
}
//Create and send multisig transaction
const sendMultisigTx = floBlockchainAPI.sendMultisigTx = function (redeemScript, privateKeys, receivers, amounts, floData = '', strict_utxo = true) {
return new Promise((resolve, reject) => {
var multisig = floCrypto.decodeRedeemScript(redeemScript);
if (!multisig)
return reject(`Invalid redeemScript`);
if (privateKeys.length < multisig.required)
return reject(`Insufficient privateKeys (required ${multisig.required})`);
for (let pk of privateKeys) {
var flag = false;
for (let pub of multisig.pubkeys)
if (floCrypto.verifyPrivKey(pk, pub, false))
flag = true;
if (!flag)
return reject(`Invalid Private key`);
}
createMultisigTx(redeemScript, receivers, amounts, floData, strict_utxo).then(trx => {
for (let pk of privateKeys)
trx.sign(pk, 1);
var signedTxHash = trx.serialize();
broadcastTx(signedTxHash)
.then(txid => resolve(txid))
.catch(error => reject(error))
}).catch(error => reject(error))
})
}
floBlockchainAPI.writeMultisigData = function (redeemScript, data, privatekeys, 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 (!floCrypto.validateFloID(receiverAddr))
return reject(`Invalid receiver: ${receiverAddr}`);
sendMultisigTx(redeemScript, privatekeys, receiverAddr, sendAmt, data, strict_utxo)
.then(txid => resolve(txid))
.catch(error => reject(error))
})
}
function deserializeTx(tx) {
if (typeof tx === 'string' || Array.isArray(tx)) {
try {
tx = bitjs.transaction(tx);
} catch {
throw "Invalid transaction hex";
}
} else if (typeof tx !== 'object' || typeof tx.sign !== 'function')
throw "Invalid transaction object";
return tx;
}
floBlockchainAPI.signTx = function (tx, privateKey, sighashtype = 1) {
if (!floCrypto.getFloID(privateKey))
throw "Invalid Private key";
//deserialize if needed
tx = deserializeTx(tx);
var signedTxHex = tx.sign(privateKey, sighashtype);
return signedTxHex;
}
const checkSigned = floBlockchainAPI.checkSigned = function (tx, bool = true) {
tx = deserializeTx(tx);
let n = [];
for (let i = 0; i < tx.inputs.length; i++) {
var s = tx.scriptDecode(i);
if (s['type'] === 'scriptpubkey')
n.push(s.signed);
else if (s['type'] === 'multisig') {
var rs = tx.decodeRedeemScript(s['rs']);
let x = {
s: 0,
r: rs['required'],
t: rs['pubkeys'].length
};
//check input script for signatures
var script = Array.from(tx.inputs[i].script);
if (script[0] == 0) { //script with signatures
script = tx.parseScript(script);
for (var k = 0; k < script.length; k++)
if (Array.isArray(script[k]) && script[k][0] == 48) //0x30 DERSequence
x.s++;
}
//validate counts
if (x.r > x.t)
throw "signaturesRequired is more than publicKeys";
else if (x.s < x.r)
n.push(x);
else
n.push(true);
}
}
return bool ? !(n.filter(x => x !== true).length) : n;
}
floBlockchainAPI.checkIfSameTx = function (tx1, tx2) {
tx1 = deserializeTx(tx1);
tx2 = deserializeTx(tx2);
//compare input and output length
if (tx1.inputs.length !== tx2.inputs.length || tx1.outputs.length !== tx2.outputs.length)
return false;
//compare flodata
if (tx1.floData !== tx2.floData)
return false
//compare inputs
for (let i = 0; i < tx1.inputs.length; i++)
if (tx1.inputs[i].outpoint.hash !== tx2.inputs[i].outpoint.hash || tx1.inputs[i].outpoint.index !== tx2.inputs[i].outpoint.index)
return false;
//compare outputs
for (let i = 0; i < tx1.outputs.length; i++)
if (tx1.outputs[i].value !== tx2.outputs[i].value || Crypto.util.bytesToHex(tx1.outputs[i].script) !== Crypto.util.bytesToHex(tx2.outputs[i].script))
return false;
return true;
}
floBlockchainAPI.transactionID = function (tx) {
tx = deserializeTx(tx);
let clone = bitjs.clone(tx);
let raw_bytes = Crypto.util.hexToBytes(clone.serialize());
let txid = Crypto.SHA256(Crypto.SHA256(raw_bytes, { asBytes: true }), { asBytes: true }).reverse();
return Crypto.util.bytesToHex(txid);
}
const getTxOutput = (txid, i) => new Promise((resolve, reject) => {
promisedAPI(`api/tx/${txid}`)
.then(result => resolve(result.vout[i]))
.catch(error => reject(error))
});
function getOutputAddress(outscript) {
var bytes, version;
switch (outscript[0]) {
case 118: //legacy
bytes = outscript.slice(3, outscript.length - 2);
version = bitjs.pub;
break
case 169: //multisig
bytes = outscript.slice(2, outscript.length - 1);
version = bitjs.multisig;
break;
default: return; //unknown
}
bytes.unshift(version);
var hash = Crypto.SHA256(Crypto.SHA256(bytes, { asBytes: true }), { asBytes: true });
var checksum = hash.slice(0, 4);
return bitjs.Base58.encode(bytes.concat(checksum));
}
floBlockchainAPI.parseTransaction = function (tx) {
return new Promise((resolve, reject) => {
tx = deserializeTx(tx);
let result = {};
let promises = [];
//Parse Inputs
for (let i = 0; i < tx.inputs.length; i++)
promises.push(getTxOutput(tx.inputs[i].outpoint.hash, tx.inputs[i].outpoint.index));
Promise.all(promises).then(inputs => {
result.inputs = inputs.map(inp => Object({
address: inp.scriptPubKey.addresses[0],
value: parseFloat(inp.value)
}));
let signed = checkSigned(tx, false);
result.inputs.forEach((inp, i) => inp.signed = signed[i]);
//Parse Outputs
result.outputs = tx.outputs.map(out => Object({
address: getOutputAddress(out.script),
value: util.Sat_to_FLO(out.value)
}))
//Parse Totals
result.total_input = parseFloat(result.inputs.reduce((a, inp) => a += inp.value, 0).toFixed(8));
result.total_output = parseFloat(result.outputs.reduce((a, out) => a += out.value, 0).toFixed(8));
result.fee = parseFloat((result.total_input - result.total_output).toFixed(8));
result.floData = tx.floData;
resolve(result);
}).catch(error => reject(error))
})
}
//Broadcast signed Tx in blockchain using API //Broadcast signed Tx in blockchain using API
const broadcastTx = floBlockchainAPI.broadcastTx = function(signedTxHash) { const broadcastTx = floBlockchainAPI.broadcastTx = function (signedTxHash) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (signedTxHash.length < 1) if (signedTxHash.length < 1)
return reject("Empty Signature"); return reject("Empty Signature");
@ -441,7 +790,7 @@
}) })
} }
floBlockchainAPI.getTx = function(txid) { const getTx = floBlockchainAPI.getTx = function (txid) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
promisedAPI(`api/tx/${txid}`) promisedAPI(`api/tx/${txid}`)
.then(response => resolve(response)) .then(response => resolve(response))
@ -449,30 +798,89 @@
}) })
} }
//Read Txs of Address between from and to /**Wait for the given txid to get confirmation in blockchain
const readTxs = floBlockchainAPI.readTxs = function(addr, from, to) { * @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) => { return new Promise((resolve, reject) => {
promisedAPI(`api/addrs/${addr}/txs?from=${from}&to=${to}`) 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)
})
}
//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 query_params = {};
if (!isUndefined(options.after) || !isUndefined(options.before)) {
if (!isUndefined(options.after))
query_params.after = options.after;
if (!isUndefined(options.before))
query_params.before = options.before;
} else {
if (!isUndefined(options.from))
query_params.from = options.from;
if (!isUndefined(options.to))
query_params.to = options.to;
}
if (!isUndefined(options.latest))
query_params.latest = latest;
if (!isUndefined(options.mempool))
query_params.mempool = options.mempool;
promisedAPI(api, query_params)
.then(response => resolve(response)) .then(response => resolve(response))
.catch(error => reject(error)) .catch(error => reject(error))
}); });
} }
//Read All Txs of Address (newest first) //Read All Txs of Address (newest first)
floBlockchainAPI.readAllTxs = function(addr) { const readAllTxs = floBlockchainAPI.readAllTxs = function (addr, options = {}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
promisedAPI(`api/addrs/${addr}/txs?from=0&to=1`).then(response => { readTxs(addr, options).then(response => {
promisedAPI(`api/addrs/${addr}/txs?from=0&to=${response.totalItems}0`) if (response.incomplete) {
.then(response => resolve(response.items)) let next_options = Object.assign({}, options);
.catch(error => reject(error)); if (options.latest)
}).catch(error => reject(error)) 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);
}).catch(error => reject(error))
} else
resolve({
lastItem: response.lastItem || options.after,
items: response.items
});
})
}); });
} }
/*Read flo Data from txs of given Address /*Read flo Data from txs of given Address
options can be used to filter data options can be used to filter data
limit : maximum number of filtered data (default = 1000, negative = no limit) after : query after the given txid
ignoreOld : ignore old txs (default = 0) 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 sentOnly : filters only sent data
receivedOnly: filters only received data receivedOnly: filters only received data
pattern : filters data that with JSON pattern pattern : filters data that with JSON pattern
@ -481,97 +889,150 @@
sender : flo-id(s) of sender sender : flo-id(s) of sender
receiver : flo-id(s) of receiver receiver : flo-id(s) of receiver
*/ */
floBlockchainAPI.readData = function(addr, options = {}) { 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];
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
promisedAPI(`api/addrs/${addr}/txs?from=0&to=1`).then(response => {
var newItems = response.totalItems - options.ignoreOld;
promisedAPI(`api/addrs/${addr}/txs?from=0&to=${newItems*2}`).then(response => {
if (options.limit <= 0)
options.limit = response.items.length;
var filteredData = [];
let numToRead = response.totalItems - options.ignoreOld,
unconfirmedCount = 0;
for (let i = 0; i < numToRead && filteredData.length < options.limit; i++) {
if (!response.items[i].confirmations) { //unconfirmed transactions
unconfirmedCount++;
if (numToRead < response.items[i].length)
numToRead++;
continue;
}
if (options.pattern) {
try {
let jsonContent = JSON.parse(response.items[i].floData);
if (!Object.keys(jsonContent).includes(options.pattern))
continue;
} catch (error) {
continue;
}
}
if (options.sentOnly) {
let flag = false;
for (let vin of response.items[i].vin)
if (vin.addr === addr) {
flag = true;
break;
}
if (!flag) continue;
}
if (Array.isArray(options.sender)) {
let flag = false;
for (let vin of response.items[i].vin)
if (options.sender.includes(vin.addr)) {
flag = true;
break;
}
if (!flag) continue;
}
if (options.receivedOnly) {
let flag = false;
for (let vout of response.items[i].vout)
if (vout.scriptPubKey.addresses[0] === addr) {
flag = true;
break;
}
if (!flag) continue;
}
if (Array.isArray(options.receiver)) {
let flag = false;
for (let vout of response.items[i].vout)
if (options.receiver.includes(vout.scriptPubKey.addresses[0])) {
flag = true;
break;
}
if (!flag) continue;
}
if (options.filter && !options.filter(response.items[i].floData))
continue;
if (options.tx) { //fetch options
let d = {} let query_options = {};
d.txid = response.items[i].txid; query_options.mempool = isUndefined(options.mempool) ? false : options.mempool; //DEFAULT: ignore unconfirmed tx
d.time = response.items[i].time; if (!isUndefined(options.after) || !isUndefined(options.before)) {
d.blockheight = response.items[i].blockheight; if (!isUndefined(options.ignoreOld)) //Backward support
d.data = response.items[i].floData; return reject("Invalid options: cannot use after/before and ignoreOld in same query");
filteredData.push(d); //use passed after and/or before options (options remain undefined if not passed)
} else query_options.after = options.after;
filteredData.push(response.items[i].floData); query_options.before = options.before;
}
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
if (typeof options.senders === "string") options.senders = [options.senders];
if (typeof options.receivers === "string") options.receivers = [options.receivers];
//filter the txs based on options
const filteredData = response.items.filter(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;
if (options.pattern) {
try {
let jsonContent = JSON.parse(tx.floData);
if (!Object.keys(jsonContent).includes(options.pattern))
return false;
} catch {
return false;
}
} }
resolve({
totalTxs: response.totalItems - unconfirmedCount, if (options.filter && !options.filter(tx.floData))
data: filteredData return false;
});
}).catch(error => { return true;
reject(error); }).map(tx => options.tx ? {
}); txid: tx.txid,
}).catch(error => { time: tx.time,
reject(error); 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
} : tx.floData);
const result = { lastItem: response.lastItem };
if (options.tx)
result.items = filteredData;
else
result.data = filteredData
resolve(result);
}).catch(error => reject(error))
})
} }
/*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 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, query_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 = { lastItem: 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.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 lastItem
else
resolve({ lastItem: response.lastItem });
}).catch(error => reject(error))
})
}
})('object' === typeof module ? module.exports : window.floBlockchainAPI = {}); })('object' === typeof module ? module.exports : window.floBlockchainAPI = {});

View File

@ -1,4 +1,4 @@
(function(EXPORTS) { //floCloudAPI v2.4.2e (function (EXPORTS) { //floCloudAPI v2.4.3a
/* FLO Cloud operations to send/request application data*/ /* FLO Cloud operations to send/request application data*/
'use strict'; 'use strict';
const floCloudAPI = EXPORTS; const floCloudAPI = EXPORTS;
@ -8,6 +8,7 @@
SNStorageID: floGlobals.SNStorageID || "FNaN9McoBAEFUjkRmNQRYLmBF8SpS7Tgfk", SNStorageID: floGlobals.SNStorageID || "FNaN9McoBAEFUjkRmNQRYLmBF8SpS7Tgfk",
adminID: floGlobals.adminID, adminID: floGlobals.adminID,
application: floGlobals.application, application: floGlobals.application,
SNStorageName: "SuperNodeStorage",
callback: (d, e) => console.debug(d, e) callback: (d, e) => console.debug(d, e)
}; };
@ -58,6 +59,9 @@
SNStorageID: { SNStorageID: {
get: () => DEFAULT.SNStorageID get: () => DEFAULT.SNStorageID
}, },
SNStorageName: {
get: () => DEFAULT.SNStorageName
},
adminID: { adminID: {
get: () => DEFAULT.adminID get: () => DEFAULT.adminID
}, },
@ -94,7 +98,7 @@
}); });
var kBucket; var kBucket;
const K_Bucket = floCloudAPI.K_Bucket = function(masterID, nodeList) { const K_Bucket = floCloudAPI.K_Bucket = function (masterID, nodeList) {
const decodeID = floID => { const decodeID = floID => {
let k = bitjs.Base58.decode(floID); let k = bitjs.Base58.decode(floID);
@ -128,7 +132,7 @@
}); });
self.isNode = floID => _CO.includes(floID); self.isNode = floID => _CO.includes(floID);
self.innerNodes = function(id1, id2) { self.innerNodes = function (id1, id2) {
if (!_CO.includes(id1) || !_CO.includes(id2)) if (!_CO.includes(id1) || !_CO.includes(id2))
throw Error('Given nodes are not supernode'); throw Error('Given nodes are not supernode');
let iNodes = [] let iNodes = []
@ -139,7 +143,7 @@
} }
return iNodes return iNodes
} }
self.outterNodes = function(id1, id2) { self.outterNodes = function (id1, id2) {
if (!_CO.includes(id1) || !_CO.includes(id2)) if (!_CO.includes(id1) || !_CO.includes(id2))
throw Error('Given nodes are not supernode'); throw Error('Given nodes are not supernode');
let oNodes = [] let oNodes = []
@ -150,7 +154,7 @@
} }
return oNodes return oNodes
} }
self.prevNode = function(id, N = 1) { self.prevNode = function (id, N = 1) {
let n = N || _CO.length; let n = N || _CO.length;
if (!_CO.includes(id)) if (!_CO.includes(id))
throw Error('Given node is not supernode'); throw Error('Given node is not supernode');
@ -164,7 +168,7 @@
} }
return (N == 1 ? pNodes[0] : pNodes) return (N == 1 ? pNodes[0] : pNodes)
} }
self.nextNode = function(id, N = 1) { self.nextNode = function (id, N = 1) {
let n = N || _CO.length; let n = N || _CO.length;
if (!_CO.includes(id)) if (!_CO.includes(id))
throw Error('Given node is not supernode'); throw Error('Given node is not supernode');
@ -179,7 +183,7 @@
} }
return (N == 1 ? nNodes[0] : nNodes) return (N == 1 ? nNodes[0] : nNodes)
} }
self.closestNode = function(id, N = 1) { self.closestNode = function (id, N = 1) {
let decodedId = decodeID(id); let decodedId = decodeID(id);
let n = N || _CO.length; let n = N || _CO.length;
let cNodes = _KB.closest(decodedId, n) let cNodes = _KB.closest(decodedId, n)
@ -293,8 +297,8 @@
fetch_ActiveAPI(floID, data).then(response => { fetch_ActiveAPI(floID, data).then(response => {
if (response.ok) if (response.ok)
response.json() response.json()
.then(result => resolve(result)) .then(result => resolve(result))
.catch(error => reject(error)) .catch(error => reject(error))
else response.text() else response.text()
.then(result => reject(response.status + ": " + result)) //Error Message from Node .then(result => reject(response.status + ": " + result)) //Error Message from Node
.catch(error => reject(error)) .catch(error => reject(error))
@ -370,21 +374,21 @@
const util = floCloudAPI.util = {}; const util = floCloudAPI.util = {};
const encodeMessage = util.encodeMessage = function(message) { const encodeMessage = util.encodeMessage = function (message) {
return btoa(unescape(encodeURIComponent(JSON.stringify(message)))) return btoa(unescape(encodeURIComponent(JSON.stringify(message))))
} }
const decodeMessage = util.decodeMessage = function(message) { const decodeMessage = util.decodeMessage = function (message) {
return JSON.parse(decodeURIComponent(escape(atob(message)))) return JSON.parse(decodeURIComponent(escape(atob(message))))
} }
const filterKey = util.filterKey = function(type, options = {}) { const filterKey = util.filterKey = function (type, options = {}) {
return type + (options.comment ? ':' + options.comment : '') + return type + (options.comment ? ':' + options.comment : '') +
'|' + (options.group || options.receiverID || DEFAULT.adminID) + '|' + (options.group || options.receiverID || DEFAULT.adminID) +
'|' + (options.application || DEFAULT.application); '|' + (options.application || DEFAULT.application);
} }
const proxyID = util.proxyID = function(address) { const proxyID = util.proxyID = function (address) {
if (!address) if (!address)
return; return;
var bytes; var bytes;
@ -489,7 +493,7 @@
} }
//set status as online for user_id //set status as online for user_id
floCloudAPI.setStatus = function(options = {}) { floCloudAPI.setStatus = function (options = {}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let callback = options.callback instanceof Function ? options.callback : DEFAULT.callback; let callback = options.callback instanceof Function ? options.callback : DEFAULT.callback;
var request = { var request = {
@ -508,7 +512,7 @@
} }
//request status of floID(s) in trackList //request status of floID(s) in trackList
floCloudAPI.requestStatus = function(trackList, options = {}) { floCloudAPI.requestStatus = function (trackList, options = {}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!Array.isArray(trackList)) if (!Array.isArray(trackList))
trackList = [trackList]; trackList = [trackList];
@ -525,7 +529,7 @@
} }
//send any message to supernode cloud storage //send any message to supernode cloud storage
const sendApplicationData = floCloudAPI.sendApplicationData = function(message, type, options = {}) { const sendApplicationData = floCloudAPI.sendApplicationData = function (message, type, options = {}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
var data = { var data = {
senderID: user.id, senderID: user.id,
@ -547,7 +551,7 @@
} }
//request any data from supernode cloud //request any data from supernode cloud
const requestApplicationData = floCloudAPI.requestApplicationData = function(type, options = {}) { const requestApplicationData = floCloudAPI.requestApplicationData = function (type, options = {}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
var request = { var request = {
receiverID: options.receiverID || DEFAULT.adminID, receiverID: options.receiverID || DEFAULT.adminID,
@ -650,7 +654,7 @@
*/ */
//tag data in supernode cloud (subAdmin access only) //tag data in supernode cloud (subAdmin access only)
floCloudAPI.tagApplicationData = function(vectorClock, tag, options = {}) { floCloudAPI.tagApplicationData = function (vectorClock, tag, options = {}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!floGlobals.subAdmins.includes(user.id)) if (!floGlobals.subAdmins.includes(user.id))
return reject("Only subAdmins can tag data") return reject("Only subAdmins can tag data")
@ -671,7 +675,7 @@
} }
//note data in supernode cloud (receiver only or subAdmin allowed if receiver is adminID) //note data in supernode cloud (receiver only or subAdmin allowed if receiver is adminID)
floCloudAPI.noteApplicationData = function(vectorClock, note, options = {}) { floCloudAPI.noteApplicationData = function (vectorClock, note, options = {}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
var request = { var request = {
receiverID: options.receiverID || DEFAULT.adminID, receiverID: options.receiverID || DEFAULT.adminID,
@ -690,7 +694,7 @@
} }
//send general data //send general data
floCloudAPI.sendGeneralData = function(message, type, options = {}) { floCloudAPI.sendGeneralData = function (message, type, options = {}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (options.encrypt) { if (options.encrypt) {
let encryptionKey = options.encrypt === true ? let encryptionKey = options.encrypt === true ?
@ -704,7 +708,7 @@
} }
//request general data //request general data
floCloudAPI.requestGeneralData = function(type, options = {}) { floCloudAPI.requestGeneralData = function (type, options = {}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
var fk = filterKey(type, options) var fk = filterKey(type, options)
lastVC[fk] = parseInt(lastVC[fk]) || 0; lastVC[fk] = parseInt(lastVC[fk]) || 0;
@ -728,7 +732,7 @@
} }
//request an object data from supernode cloud //request an object data from supernode cloud
floCloudAPI.requestObjectData = function(objectName, options = {}) { floCloudAPI.requestObjectData = function (objectName, options = {}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
options.lowerVectorClock = options.lowerVectorClock || lastVC[objectName] + 1; options.lowerVectorClock = options.lowerVectorClock || lastVC[objectName] + 1;
options.senderID = [false, null].includes(options.senderID) ? null : options.senderID = [false, null].includes(options.senderID) ? null :
@ -765,7 +769,7 @@
}) })
} }
floCloudAPI.closeRequest = function(requestID) { floCloudAPI.closeRequest = function (requestID) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let conn = _liveRequest[requestID] let conn = _liveRequest[requestID]
if (!conn) if (!conn)
@ -779,7 +783,7 @@
} }
//reset or initialize an object and send it to cloud //reset or initialize an object and send it to cloud
floCloudAPI.resetObjectData = function(objectName, options = {}) { floCloudAPI.resetObjectData = function (objectName, options = {}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let message = { let message = {
reset: appObjects[objectName] reset: appObjects[objectName]
@ -793,7 +797,7 @@
} }
//update the diff and send it to cloud //update the diff and send it to cloud
floCloudAPI.updateObjectData = function(objectName, options = {}) { floCloudAPI.updateObjectData = function (objectName, options = {}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let message = { let message = {
diff: diff.find(lastCommit.get(objectName), appObjects[ diff: diff.find(lastCommit.get(objectName), appObjects[
@ -812,7 +816,7 @@
findDiff(original, updatedObj) returns an object with the added, deleted and updated differences findDiff(original, updatedObj) returns an object with the added, deleted and updated differences
mergeDiff(original, allDiff) returns a new object from original object merged with all differences (allDiff is returned object of findDiff) mergeDiff(original, allDiff) returns a new object from original object merged with all differences (allDiff is returned object of findDiff)
*/ */
var diff = (function() { var diff = (function () {
const isDate = d => d instanceof Date; const isDate = d => d instanceof Date;
const isEmpty = o => Object.keys(o).length === 0; const isEmpty = o => Object.keys(o).length === 0;
const isObject = o => o != null && typeof o === 'object'; const isObject = o => o != null && typeof o === 'object';
@ -986,23 +990,23 @@
}, {}); }, {});
}; };
const mergeRecursive = (obj1, obj2) => { const mergeRecursive = (obj1, obj2, deleteMode = false) => {
for (var p in obj2) { for (var p in obj2) {
try { try {
if (obj2[p].constructor == Object) if (obj2[p].constructor == Object)
obj1[p] = mergeRecursive(obj1[p], obj2[p]); obj1[p] = mergeRecursive(obj1[p], obj2[p], deleteMode);
// Property in destination object set; update its value. // Property in destination object set; update its value.
else if (Ext.isArray(obj2[p])) { else if (Array.isArray(obj2[p])) {
// obj1[p] = []; // obj1[p] = [];
if (obj2[p].length < 1) if (obj2[p].length < 1)
obj1[p] = obj2[p]; obj1[p] = obj2[p];
else else
obj1[p] = mergeRecursive(obj1[p], obj2[p]); obj1[p] = mergeRecursive(obj1[p], obj2[p], deleteMode);
} else } else
obj1[p] = obj2[p]; obj1[p] = deleteMode && obj2[p] === null ? undefined : obj2[p];
} catch (e) { } catch (e) {
// Property in destination object not set; create it and set its value. // Property in destination object not set; create it and set its value.
obj1[p] = obj2[p]; obj1[p] = deleteMode && obj2[p] === null ? undefined : obj2[p];
} }
} }
return obj1; return obj1;
@ -1011,20 +1015,13 @@
const cleanse = (obj) => { const cleanse = (obj) => {
Object.keys(obj).forEach(key => { Object.keys(obj).forEach(key => {
var value = obj[key]; var value = obj[key];
if (typeof value === "object" && value !== null) { if (typeof value === "object" && value !== null)
// Recurse... obj[key] = cleanse(value);
cleanse(value); else if (typeof value === 'undefined')
// ...and remove if now "empty" (NOTE: insert your definition of "empty" here) delete obj[key]; // undefined, remove it
//if (!Object.keys(value).length)
// delete obj[key];
} else if (value === null)
delete obj[key]; // null, remove it
}); });
if (obj.constructor.toString().indexOf("Array") != -1) { if (Array.isArray(obj))
obj = obj.filter(function(el) { obj = obj.filter(v => typeof v !== 'undefined');
return el != null;
});
}
return obj; return obj;
} }
@ -1040,7 +1037,7 @@
if (Object.keys(diff.updated).length !== 0) if (Object.keys(diff.updated).length !== 0)
obj = mergeRecursive(obj, diff.updated) obj = mergeRecursive(obj, diff.updated)
if (Object.keys(diff.deleted).length !== 0) { if (Object.keys(diff.deleted).length !== 0) {
obj = mergeRecursive(obj, diff.deleted) obj = mergeRecursive(obj, diff.deleted, true)
obj = cleanse(obj) obj = cleanse(obj)
} }
if (Object.keys(diff.added).length !== 0) if (Object.keys(diff.added).length !== 0)

View File

@ -1,4 +1,4 @@
(function(EXPORTS) { //floCrypto v2.3.3d (function (EXPORTS) { //floCrypto v2.3.6a
/* FLO Crypto Operators */ /* FLO Crypto Operators */
'use strict'; 'use strict';
const floCrypto = EXPORTS; const floCrypto = EXPORTS;
@ -78,14 +78,14 @@
} }
//generate a random Interger within range //generate a random Interger within range
floCrypto.randInt = function(min, max) { floCrypto.randInt = function (min, max) {
min = Math.ceil(min); min = Math.ceil(min);
max = Math.floor(max); max = Math.floor(max);
return Math.floor(securedMathRandom() * (max - min + 1)) + min; return Math.floor(securedMathRandom() * (max - min + 1)) + min;
} }
//generate a random String within length (options : alphaNumeric chars only) //generate a random String within length (options : alphaNumeric chars only)
floCrypto.randString = function(length, alphaNumeric = true) { floCrypto.randString = function (length, alphaNumeric = true) {
var result = ''; var result = '';
var characters = alphaNumeric ? 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' : var characters = alphaNumeric ? 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' :
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_+-./*?@#&$<>=[]{}():'; 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_+-./*?@#&$<>=[]{}():';
@ -95,7 +95,7 @@
} }
//Encrypt Data using public-key //Encrypt Data using public-key
floCrypto.encryptData = function(data, receiverPublicKeyHex) { floCrypto.encryptData = function (data, receiverPublicKeyHex) {
var senderECKeyData = getSenderPublicKeyString(); var senderECKeyData = getSenderPublicKeyString();
var senderDerivedKey = deriveSharedKeySender(receiverPublicKeyHex, senderECKeyData.privateKey); var senderDerivedKey = deriveSharedKeySender(receiverPublicKeyHex, senderECKeyData.privateKey);
let senderKey = senderDerivedKey.XValue + senderDerivedKey.YValue; let senderKey = senderDerivedKey.XValue + senderDerivedKey.YValue;
@ -107,7 +107,7 @@
} }
//Decrypt Data using private-key //Decrypt Data using private-key
floCrypto.decryptData = function(data, privateKeyHex) { floCrypto.decryptData = function (data, privateKeyHex) {
var receiverECKeyData = {}; var receiverECKeyData = {};
if (typeof privateKeyHex !== "string") throw new Error("No private key found."); if (typeof privateKeyHex !== "string") throw new Error("No private key found.");
let privateKey = wifToDecimal(privateKeyHex, true); let privateKey = wifToDecimal(privateKeyHex, true);
@ -120,7 +120,7 @@
} }
//Sign data using private-key //Sign data using private-key
floCrypto.signData = function(data, privateKeyHex) { floCrypto.signData = function (data, privateKeyHex) {
var key = new Bitcoin.ECKey(privateKeyHex); var key = new Bitcoin.ECKey(privateKeyHex);
var messageHash = Crypto.SHA256(data); var messageHash = Crypto.SHA256(data);
var messageSign = Bitcoin.ECDSA.sign(messageHash, key.priv); var messageSign = Bitcoin.ECDSA.sign(messageHash, key.priv);
@ -129,7 +129,7 @@
} }
//Verify signatue of the data using public-key //Verify signatue of the data using public-key
floCrypto.verifySign = function(data, signatureHex, publicKeyHex) { floCrypto.verifySign = function (data, signatureHex, publicKeyHex) {
var msgHash = Crypto.SHA256(data); var msgHash = Crypto.SHA256(data);
var sigBytes = Crypto.util.hexToBytes(signatureHex); var sigBytes = Crypto.util.hexToBytes(signatureHex);
var publicKeyPoint = ecparams.getCurve().decodePointHex(publicKeyHex); var publicKeyPoint = ecparams.getCurve().decodePointHex(publicKeyHex);
@ -138,7 +138,7 @@
} }
//Generates a new flo ID and returns private-key, public-key and floID //Generates a new flo ID and returns private-key, public-key and floID
const generateNewID = floCrypto.generateNewID = function() { const generateNewID = floCrypto.generateNewID = function () {
var key = new Bitcoin.ECKey(false); var key = new Bitcoin.ECKey(false);
key.setCompressed(true); key.setCompressed(true);
return { return {
@ -152,6 +152,19 @@
newID: { newID: {
get: () => generateNewID() get: () => generateNewID()
}, },
hashID: {
value: (str) => {
let bytes = ripemd160(Crypto.SHA256(str, { asBytes: true }), { asBytes: true });
bytes.unshift(bitjs.pub);
var hash = Crypto.SHA256(Crypto.SHA256(bytes, {
asBytes: true
}), {
asBytes: true
});
var checksum = hash.slice(0, 4);
return bitjs.Base58.encode(bytes.concat(checksum));
}
},
tmpID: { tmpID: {
get: () => { get: () => {
let bytes = Crypto.util.randomBytes(20); let bytes = Crypto.util.randomBytes(20);
@ -168,7 +181,7 @@
}); });
//Returns public-key from private-key //Returns public-key from private-key
floCrypto.getPubKeyHex = function(privateKeyHex) { floCrypto.getPubKeyHex = function (privateKeyHex) {
if (!privateKeyHex) if (!privateKeyHex)
return null; return null;
var key = new Bitcoin.ECKey(privateKeyHex); var key = new Bitcoin.ECKey(privateKeyHex);
@ -179,7 +192,7 @@
} }
//Returns flo-ID from public-key or private-key //Returns flo-ID from public-key or private-key
floCrypto.getFloID = function(keyHex) { floCrypto.getFloID = function (keyHex) {
if (!keyHex) if (!keyHex)
return null; return null;
try { try {
@ -192,7 +205,7 @@
} }
} }
floCrypto.getAddress = function(privateKeyHex, strict = false) { floCrypto.getAddress = function (privateKeyHex, strict = false) {
if (!privateKeyHex) if (!privateKeyHex)
return; return;
var key = new Bitcoin.ECKey(privateKeyHex); var key = new Bitcoin.ECKey(privateKeyHex);
@ -212,7 +225,7 @@
} }
//Verify the private-key for the given public-key or flo-ID //Verify the private-key for the given public-key or flo-ID
floCrypto.verifyPrivKey = function(privateKeyHex, pubKey_floID, isfloID = true) { floCrypto.verifyPrivKey = function (privateKeyHex, pubKey_floID, isfloID = true) {
if (!privateKeyHex || !pubKey_floID) if (!privateKeyHex || !pubKey_floID)
return false; return false;
try { try {
@ -222,7 +235,7 @@
key.setCompressed(true); key.setCompressed(true);
if (isfloID && pubKey_floID == key.getBitcoinAddress()) if (isfloID && pubKey_floID == key.getBitcoinAddress())
return true; return true;
else if (!isfloID && pubKey_floID == key.getPubKeyHex()) else if (!isfloID && pubKey_floID.toUpperCase() == key.getPubKeyHex().toUpperCase())
return true; return true;
else else
return false; return false;
@ -231,12 +244,36 @@
} }
} }
floCrypto.getMultisigAddress = function (publicKeyList, requiredSignatures) {
if (!Array.isArray(publicKeyList) || !publicKeyList.length)
return null;
if (!Number.isInteger(requiredSignatures) || requiredSignatures < 1 || requiredSignatures > publicKeyList.length)
return null;
try {
var multisig = bitjs.pubkeys2multisig(publicKeyList, requiredSignatures);
return multisig;
} catch {
return null;
}
}
floCrypto.decodeRedeemScript = function (redeemScript) {
try {
var decoded = bitjs.transaction().decodeRedeemScript(redeemScript);
return decoded;
} catch {
return null;
}
}
//Check if the given flo-id is valid or not //Check if the given flo-id is valid or not
floCrypto.validateFloID = function(floID) { floCrypto.validateFloID = function (floID, regularOnly = false) {
if (!floID) if (!floID)
return false; return false;
try { try {
let addr = new Bitcoin.Address(floID); let addr = new Bitcoin.Address(floID);
if (regularOnly && addr.version != Bitcoin.Address.standardVersion)
return false;
return true; return true;
} catch { } catch {
return false; return false;
@ -244,7 +281,7 @@
} }
//Check if the given address (any blockchain) is valid or not //Check if the given address (any blockchain) is valid or not
floCrypto.validateAddr = function(address, std = true, bech = true) { floCrypto.validateAddr = function (address, std = true, bech = true) {
let raw = decodeAddress(address); let raw = decodeAddress(address);
if (!raw) if (!raw)
return false; return false;
@ -266,22 +303,30 @@
return false; return false;
} }
//Check the public-key for the address (any blockchain) //Check the public-key (or redeem-script) for the address (any blockchain)
floCrypto.verifyPubKey = function(pubKeyHex, address) { floCrypto.verifyPubKey = function (pubKeyHex, address) {
let raw = decodeAddress(address), let raw = decodeAddress(address);
pub_hash = Crypto.util.bytesToHex(ripemd160(Crypto.SHA256(Crypto.util.hexToBytes(pubKeyHex), { if (!raw)
asBytes: true return;
}))); let pub_hash = Crypto.util.bytesToHex(ripemd160(Crypto.SHA256(Crypto.util.hexToBytes(pubKeyHex), { asBytes: true })));
return raw ? pub_hash === raw.hex : false; if (typeof raw.bech_version !== 'undefined' && raw.bytes.length == 32) //bech32-multisig
raw.hex = Crypto.util.bytesToHex(ripemd160(raw.bytes, { asBytes: true }));
return pub_hash === raw.hex;
} }
//Convert the given address (any blockchain) to equivalent floID //Convert the given address (any blockchain) to equivalent floID
floCrypto.toFloID = function(address) { floCrypto.toFloID = function (address, options = null) {
if (!address) if (!address)
return; return;
let raw = decodeAddress(address); let raw = decodeAddress(address);
if (!raw) if (!raw)
return; return;
else if (options) { //if (optional) version check is passed
if (typeof raw.version !== 'undefined' && (!options.std || !options.std.includes(raw.version)))
return;
if (typeof raw.bech_version !== 'undefined' && (!options.bech || !options.bech.includes(raw.bech_version)))
return;
}
raw.bytes.unshift(bitjs.pub); raw.bytes.unshift(bitjs.pub);
let hash = Crypto.SHA256(Crypto.SHA256(raw.bytes, { let hash = Crypto.SHA256(Crypto.SHA256(raw.bytes, {
asBytes: true asBytes: true
@ -291,19 +336,68 @@
return bitjs.Base58.encode(raw.bytes.concat(hash.slice(0, 4))); return bitjs.Base58.encode(raw.bytes.concat(hash.slice(0, 4)));
} }
//Convert raw address bytes to floID
floCrypto.rawToFloID = function (raw_bytes) {
if (typeof raw_bytes === 'string')
raw_bytes = Crypto.util.hexToBytes(raw_bytes);
if (raw_bytes.length != 20)
return null;
raw_bytes.unshift(bitjs.pub);
let hash = Crypto.SHA256(Crypto.SHA256(raw_bytes, {
asBytes: true
}), {
asBytes: true
});
return bitjs.Base58.encode(raw_bytes.concat(hash.slice(0, 4)));
}
//Convert the given multisig address (any blockchain) to equivalent multisig floID
floCrypto.toMultisigFloID = function (address, options = null) {
if (!address)
return;
let raw = decodeAddress(address);
if (!raw)
return;
else if (options) { //if (optional) version check is passed
if (typeof raw.version !== 'undefined' && (!options.std || !options.std.includes(raw.version)))
return;
if (typeof raw.bech_version !== 'undefined' && (!options.bech || !options.bech.includes(raw.bech_version)))
return;
}
if (typeof raw.bech_version !== 'undefined') {
if (raw.bytes.length != 32) return; //multisig bech address have 32 bytes
//multisig-bech:hash=SHA256 whereas multisig:hash=r160(SHA265), thus ripemd160 the bytes from multisig-bech
raw.bytes = ripemd160(raw.bytes, {
asBytes: true
});
}
raw.bytes.unshift(bitjs.multisig);
let hash = Crypto.SHA256(Crypto.SHA256(raw.bytes, {
asBytes: true
}), {
asBytes: true
});
return bitjs.Base58.encode(raw.bytes.concat(hash.slice(0, 4)));
}
//Checks if the given addresses (any blockchain) are same (w.r.t keys) //Checks if the given addresses (any blockchain) are same (w.r.t keys)
floCrypto.isSameAddr = function(addr1, addr2) { floCrypto.isSameAddr = function (addr1, addr2) {
if (!addr1 || !addr2) if (!addr1 || !addr2)
return; return;
let raw1 = decodeAddress(addr1), let raw1 = decodeAddress(addr1),
raw2 = decodeAddress(addr2); raw2 = decodeAddress(addr2);
if (!raw1 || !raw2) if (!raw1 || !raw2)
return false; return false;
else else {
if (typeof raw1.bech_version !== 'undefined' && raw1.bytes.length == 32) //bech32-multisig
raw1.hex = Crypto.util.bytesToHex(ripemd160(raw1.bytes, { asBytes: true }));
if (typeof raw2.bech_version !== 'undefined' && raw2.bytes.length == 32) //bech32-multisig
raw2.hex = Crypto.util.bytesToHex(ripemd160(raw2.bytes, { asBytes: true }));
return raw1.hex === raw2.hex; return raw1.hex === raw2.hex;
}
} }
const decodeAddress = floCrypto.decodeAddr = function(address) { const decodeAddress = floCrypto.decodeAddr = function (address) {
if (!address) if (!address)
return; return;
else if (address.length == 33 || address.length == 34) { //legacy encoding else if (address.length == 33 || address.length == 34) { //legacy encoding
@ -320,7 +414,7 @@
hex: Crypto.util.bytesToHex(bytes), hex: Crypto.util.bytesToHex(bytes),
bytes bytes
} }
} else if (address.length == 42) { //bech encoding } else if (address.length == 42 || address.length == 62) { //bech encoding
let decode = coinjs.bech32_decode(address); let decode = coinjs.bech32_decode(address);
if (decode) { if (decode) {
let bytes = decode.data; let bytes = decode.data;
@ -338,7 +432,7 @@
} }
//Split the str using shamir's Secret and Returns the shares //Split the str using shamir's Secret and Returns the shares
floCrypto.createShamirsSecretShares = function(str, total_shares, threshold_limit) { floCrypto.createShamirsSecretShares = function (str, total_shares, threshold_limit) {
try { try {
if (str.length > 0) { if (str.length > 0) {
var strHex = shamirSecretShare.str2hex(str); var strHex = shamirSecretShare.str2hex(str);
@ -352,7 +446,7 @@
} }
//Returns the retrived secret by combining the shamirs shares //Returns the retrived secret by combining the shamirs shares
const retrieveShamirSecret = floCrypto.retrieveShamirSecret = function(sharesArray) { const retrieveShamirSecret = floCrypto.retrieveShamirSecret = function (sharesArray) {
try { try {
if (sharesArray.length > 0) { if (sharesArray.length > 0) {
var comb = shamirSecretShare.combine(sharesArray.slice(0, sharesArray.length)); var comb = shamirSecretShare.combine(sharesArray.slice(0, sharesArray.length));
@ -366,7 +460,7 @@
} }
//Verifies the shares and str //Verifies the shares and str
floCrypto.verifyShamirsSecret = function(sharesArray, str) { floCrypto.verifyShamirsSecret = function (sharesArray, str) {
if (!str) if (!str)
return null; return null;
else if (retrieveShamirSecret(sharesArray) === str) else if (retrieveShamirSecret(sharesArray) === str)
@ -375,7 +469,7 @@
return false; return false;
} }
const validateASCII = floCrypto.validateASCII = function(string, bool = true) { const validateASCII = floCrypto.validateASCII = function (string, bool = true) {
if (typeof string !== "string") if (typeof string !== "string")
return null; return null;
if (bool) { if (bool) {
@ -393,8 +487,8 @@
if (x < 32 || x > 127) if (x < 32 || x > 127)
if (x in invalids) if (x in invalids)
invalids[string[i]].push(i) invalids[string[i]].push(i)
else else
invalids[string[i]] = [i]; invalids[string[i]] = [i];
} }
if (Object.keys(invalids).length) if (Object.keys(invalids).length)
return invalids; return invalids;
@ -403,7 +497,7 @@
} }
} }
floCrypto.convertToASCII = function(string, mode = 'soft-remove') { floCrypto.convertToASCII = function (string, mode = 'soft-remove') {
let chars = validateASCII(string, false); let chars = validateASCII(string, false);
if (chars === true) if (chars === true)
return string; return string;
@ -414,9 +508,9 @@
ascii_alternatives.split('\n').forEach(a => refAlt[a[0]] = a.slice(2)); ascii_alternatives.split('\n').forEach(a => refAlt[a[0]] = a.slice(2));
mode = mode.toLowerCase(); mode = mode.toLowerCase();
if (mode === "hard-unicode") if (mode === "hard-unicode")
convertor = (c) => `\\u${('000'+c.charCodeAt().toString(16)).slice(-4)}`; convertor = (c) => `\\u${('000' + c.charCodeAt().toString(16)).slice(-4)}`;
else if (mode === "soft-unicode") else if (mode === "soft-unicode")
convertor = (c) => refAlt[c] || `\\u${('000'+c.charCodeAt().toString(16)).slice(-4)}`; convertor = (c) => refAlt[c] || `\\u${('000' + c.charCodeAt().toString(16)).slice(-4)}`;
else if (mode === "hard-remove") else if (mode === "hard-remove")
convertor = c => ""; convertor = c => "";
else if (mode === "soft-remove") else if (mode === "soft-remove")
@ -428,7 +522,7 @@
return result; return result;
} }
floCrypto.revertUnicode = function(string) { floCrypto.revertUnicode = function (string) {
return string.replace(/\\u[\dA-F]{4}/gi, return string.replace(/\\u[\dA-F]{4}/gi,
m => String.fromCharCode(parseInt(m.replace(/\\u/g, ''), 16))); m => String.fromCharCode(parseInt(m.replace(/\\u/g, ''), 16)));
} }

View File

@ -1,4 +1,4 @@
(function (EXPORTS) { //floDapps v2.3.5 (function (EXPORTS) { //floDapps v2.4.0
/* General functions for FLO Dapps*/ /* General functions for FLO Dapps*/
'use strict'; 'use strict';
const floDapps = EXPORTS; const floDapps = EXPORTS;
@ -258,13 +258,15 @@
if (!startUpOptions.cloud) if (!startUpOptions.cloud)
return resolve("No cloud for this app"); return resolve("No cloud for this app");
compactIDB.readData("lastTx", floCloudAPI.SNStorageID, DEFAULT.root).then(lastTx => { compactIDB.readData("lastTx", floCloudAPI.SNStorageID, DEFAULT.root).then(lastTx => {
floBlockchainAPI.readData(floCloudAPI.SNStorageID, { var query_options = { sentOnly: true, pattern: floCloudAPI.SNStorageName };
ignoreOld: lastTx, if (typeof lastTx == 'number') //lastTx is tx count (*backward support)
sentOnly: true, query_options.ignoreOld = lastTx;
pattern: "SuperNodeStorage" else if (typeof lastTx == 'string') //lastTx is txid of last tx
}).then(result => { 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--) { 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) for (let sn in content.removeNodes)
compactIDB.removeData("supernodes", sn, DEFAULT.root); compactIDB.removeData("supernodes", sn, DEFAULT.root);
for (let sn in content.newNodes) for (let sn in content.newNodes)
@ -276,7 +278,7 @@
compactIDB.writeData("supernodes", r, sn, DEFAULT.root); 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 => { compactIDB.readAllData("supernodes", DEFAULT.root).then(nodes => {
floCloudAPI.init(nodes) floCloudAPI.init(nodes)
.then(result => resolve("Loaded Supernode list\n" + result)) .then(result => resolve("Loaded Supernode list\n" + result))
@ -292,11 +294,13 @@
if (!startUpOptions.app_config) if (!startUpOptions.app_config)
return resolve("No configs for this app"); return resolve("No configs for this app");
compactIDB.readData("lastTx", `${DEFAULT.application}|${DEFAULT.adminID}`, DEFAULT.root).then(lastTx => { compactIDB.readData("lastTx", `${DEFAULT.application}|${DEFAULT.adminID}`, DEFAULT.root).then(lastTx => {
floBlockchainAPI.readData(DEFAULT.adminID, { var query_options = { sentOnly: true, pattern: DEFAULT.application };
ignoreOld: lastTx, if (typeof lastTx == 'number') //lastTx is tx count (*backward support)
sentOnly: true, query_options.ignoreOld = lastTx;
pattern: DEFAULT.application else if (typeof lastTx == 'string') //lastTx is txid of last tx
}).then(result => { 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--) { for (var i = result.data.length - 1; i >= 0; i--) {
var content = JSON.parse(result.data[i])[DEFAULT.application]; var content = JSON.parse(result.data[i])[DEFAULT.application];
if (!content || typeof content !== "object") if (!content || typeof content !== "object")
@ -317,7 +321,7 @@
for (let l in content.settings) for (let l in content.settings)
compactIDB.writeData("settings", content.settings[l], l) 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 => { compactIDB.readAllData("subAdmins").then(result => {
subAdmins = Object.keys(result); subAdmins = Object.keys(result);
compactIDB.readAllData("trustedIDs").then(result => { compactIDB.readAllData("trustedIDs").then(result => {

166
scripts/floTokenAPI.js Normal file
View File

@ -0,0 +1,166 @@
(function (EXPORTS) { //floTokenAPI v1.0.4a
/* Token Operator to send/receive tokens via blockchain using API calls*/
'use strict';
const tokenAPI = EXPORTS;
const DEFAULT = {
apiURL: floGlobals.tokenURL || "https://ranchimallflo.duckdns.org/",
currency: floGlobals.currency || "rupee"
}
Object.defineProperties(tokenAPI, {
URL: {
get: () => DEFAULT.apiURL
},
currency: {
get: () => DEFAULT.currency,
set: currency => DEFAULT.currency = currency
}
});
if (floGlobals.currency) tokenAPI.currency = floGlobals.currency;
Object.defineProperties(floGlobals, {
currency: {
get: () => DEFAULT.currency,
set: currency => DEFAULT.currency = currency
}
});
const fetch_api = tokenAPI.fetch = function (apicall) {
return new Promise((resolve, reject) => {
console.debug(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))
});
}
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.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...}")
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}`)
.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 = {});

File diff suppressed because it is too large Load Diff