From 229b7c1e93251dad1d16e0e71465e444efefc954 Mon Sep 17 00:00:00 2001 From: sairaj mote Date: Wed, 3 Jan 2024 22:51:44 +0530 Subject: [PATCH] adding multi API support --- scripts/btcOperator.js | 594 ++++++++++++++++++++++++++++--------- scripts/btcOperator.min.js | 2 +- 2 files changed, 448 insertions(+), 148 deletions(-) diff --git a/scripts/btcOperator.js b/scripts/btcOperator.js index d6d23f3..3248fbf 100644 --- a/scripts/btcOperator.js +++ b/scripts/btcOperator.js @@ -1,19 +1,371 @@ -(function (EXPORTS) { //btcOperator v1.1.3c +(function (EXPORTS) { //btcOperator v1.2.5 /* BTC Crypto and API Operator */ const btcOperator = EXPORTS; + 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); + const checkIfTor = btcOperator.checkIfTor = () => { + return fetch('https://check.torproject.org/api/ip', { + mode: 'no-cors' + }) + .then(response => response.json()) + .then(result => result.IsTor) + .catch(error => false) + } + let isTor = false; + checkIfTor().then(result => isTor = result); + + // NOTE: some APIs may not support all functions properly hence they are omitted + const APIs = btcOperator.APIs = [ + { + url: 'https://api.blockcypher.com/v1/btc/main/', + name: 'Blockcypher', + balance({ addr }) { + return fetch_api(`addrs/${addr}/balance`, { url: this.url }) + .then(result => util.Sat_to_BTC(result.balance)) + }, + async block({ id }) { + try { + let block = await fetch_api(`blocks/${id}`, { url: this.url }) + return formatBlock(block) + } catch (e) { + console.log(e) + } + }, + broadcast({ rawTxHex, url }) { + return fetch(`${this.url}txs/push`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ tx: rawTxHex }) + }).then(response => response.json()) + .then(result => result.hash) + } + }, + { + url: 'https://blockstream.info/api/', + name: 'Blockstream', + hasOnion: true, + onionUrl: `http://explorerzydxu5ecjrkwceayqybizmpjjznk5izmitf2modhcusuqlid.onion/api/`, + balance({ addr, url }) { + return fetch_api(`address/${addr}/utxo`, { url: url || this.url }) + .then(result => { + const balance = result.reduce((t, u) => t + u.value, 0) + return util.Sat_to_BTC(balance) + }) + }, + // tx({ txid, url }) { + // return fetch_api(`tx/${txid}`, { url: url || this.url }) + // .then(result => formatTx(result)) + // }, + // txHex({ txid, url }) { + // return fetch_api(`tx/${txid}/hex`, { url: url || this.url, asText: true }) + // }, + // txs({ addr, before, after, url }) { + // return fetch_api(`address/${addr}/txs${before ? `?before=${before}` : ''}${after ? `?after=${after}` : ''}`, { url: url || this.url }) + // }, + async block({ id, url }) { + // if id is hex string then it is block hash + try { + let blockHash = id + if (!/^[0-9a-f]{64}$/i.test(id)) + blockHash = await fetch_api(`block-height/${id}`, { url: url || this.url, asText: true }) + const block = await fetch_api(`block/${blockHash}`, { url: url || this.url }) + return formatBlock(block) + } catch (e) { + console.error(e) + } + }, + broadcast({ rawTxHex, url }) { + return fetch(`${url || this.url}tx`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ tx: rawTxHex }) + }).then(response => response.text()) + } + }, + { + url: 'https://mempool.space/api/', + name: 'Mempool', + balance({ addr }) { + return fetch_api(`address/${addr}`, { url: this.url }) + .then(result => util.Sat_to_BTC(result.chain_stats.funded_txo_sum - result.chain_stats.spent_txo_sum)) + }, + tx({ txid }) { + return fetch_api(`tx/${txid}`, { url: this.url }) + .then(result => formatTx(result)) + + }, + txHex({ txid }) { + return fetch_api(`tx/${txid}/hex`, { url: this.url, asText: true }) + }, + txs({ addr, before, after }) { + return fetch_api(`address/${addr}/txs${before ? `?before=${before}` : ''}${after ? `?after=${after}` : ''}`, { url: this.url }) + }, + async block({ id }) { + // if id is hex string then it is block hash + try { + let blockHash = id + if (!/^[0-9a-f]{64}$/i.test(id)) + blockHash = await fetch_api(`block-height/${id}`, { url: this.url, asText: true }) + const block = await fetch_api(`block/${blockHash}`, { url: this.url }) + return formatBlock(block) + } catch (e) { + console.error(e) + } + }, + broadcast({ rawTxHex, url }) { + return fetch(`${this.url}tx`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ tx: rawTxHex }) + }).then(response => response.text()) + } + }, + { + url: 'https://blockchain.info/', + name: 'Blockchain', + balance({ addr }) { + return fetch_api(`q/addressbalance/${addr}`, { url: this.url }) + .then(result => util.Sat_to_BTC(result)) + }, + unspent({ addr, allowUnconfirmedUtxos = false }) { + return fetch_api(`unspent?active=${addr}`, { url: this.url }) + .then(result => formatUtxos(result.unspent_outputs, allowUnconfirmedUtxos)) + }, + tx({ txid }) { + return fetch_api(`rawtx/${txid}`, { url: this.url }) + .then(result => formatTx(result)) + }, + txHex({ txid }) { + return fetch_api(`rawtx/${txid}?format=hex`, { url: this.url, asText: true }) + }, + txs({ addr, before, after }) { + return fetch_api(`rawaddr/${addr}${before ? `?before=${before}` : ''}${after ? `?after=${after}` : ''}`, { url: this.url }) + .then(result => result.txs) + }, + async block({ id }) { + try { + let block + // if id is hex string then it is block hash + if (/^[0-9a-f]{64}$/i.test(id)) + block = await fetch_api(`rawblock/${id}`, { url: this.url }) + else { + const result = await fetch_api(`block-height/${id}?format=json`, { url: this.url }) + block = result.blocks[0] + } + return formatBlock(block) + } catch (e) { + console.error(e) + } + }, + async blockTxs({ id }) { + try { + let block + // if id is hex string then it is block hash + if (/^[0-9a-f]{64}$/i.test(id)) + block = await fetch_api(`rawblock/${id}`, { url: this.url }) + else { + const result = await fetch_api(`block-height/${id}?format=json`, { url: this.url }) + block = result.blocks[0] + } + return block.tx + } catch (e) { + console.error(e) + } + } + } + ] + + btcOperator.util.format = {} // functions to homogenize API results + const formatBlock = btcOperator.util.format.block = async (block) => { + try { + const { height, hash, id, time, timestamp, mrkl_root, merkle_root, prev_block, next_block, size } = block; + const details = { + height, + hash: hash || id, + time: (time || timestamp) * 1000, + merkle_root: merkle_root || mrkl_root, + size, + } + if (prev_block) + details.prev_block = prev_block + if (next_block) + details.next_block = next_block[0] + return details + } catch (e) { + console.error(e) + } + } + const formatUtxos = btcOperator.util.format.utxos = async (utxos, allowUnconfirmedUtxos = false) => { + try { + if (!allowUnconfirmedUtxos && !utxos || !Array.isArray(utxos)) + throw { + message: "No utxos found", + code: 1000 //error code for when issue is not from API but situational (like no utxos found) + } + return utxos.map(utxo => { + const { tx_hash, tx_hash_big_endian, txid, tx_output_n, vout, value, script, confirmations, status: { confirmed } = {} } = utxo; + return { + confirmations: confirmations || confirmed, + tx_hash_big_endian: tx_hash_big_endian || tx_hash || txid, + tx_output_n: tx_output_n || vout, + value, + script + } + }) + } catch (e) { + throw e + } + } + + const formatTx = btcOperator.util.format.tx = async (tx) => { + try { + const { txid, hash, time, block_height, fee, fees, received, + confirmed, size, double_spend, block_hash, confirmations, + status: { block_height: statusBlockHeight, block_hash: statusBlockHash, block_time } = {} + } = tx; + const inputs = tx.vin || tx.inputs; + const outputs = tx.vout || tx.outputs || tx.out; + return { + hash: hash || txid, + size: size, + fee: fee || fees, + double_spend, + time: (time * 1000) || new Date(confirmed || received).getTime() || block_time * 1000 || Date.now(), + block_height: block_height || statusBlockHeight, + block_hash: block_hash || statusBlockHash, + confirmations, + inputs: inputs.map(input => { + return { + index: input.n || input.output_index || input.vout, + prev_out: { + addr: input.prev_out?.addr || input.addresses?.[0] || input.prev_out?.address || input.addr || input.prevout.scriptpubkey_address, + value: input.prev_out?.value || input.output_value || input.prevout.value, + }, + } + }), + out: outputs.map(output => { + return { + addr: output.scriptpubkey_address || output.addresses?.[0] || output.scriptpubkey_address || output.addr, + value: output.value || output.scriptpubkey_value, + } + }) + } + } catch (e) { + console.error(e) + } + } + + const multiApi = btcOperator.multiApi = async (fnName, { index = 0, ...args } = {}) => { + try { + let triedOnion = false; + while (index < APIs.length) { + if (!APIs[index][fnName] || (APIs[index].coolDownTime && APIs[index].coolDownTime > new Date().getTime())) { + index += 1; + continue; + } + return await APIs[index][fnName](args); + } + if (isTor && !triedOnion) { + triedOnion = true; + index = 0; + while (index < APIs.length) { + if (!APIs[index].hasOnion || (APIs[index].coolDownTime && APIs[index].coolDownTime > new Date().getTime())) { + index += 1; + continue; + } + return await multiApi(fnName, { index: index + 1, ...args, url: APIs[index].onionUrl }); + } + } + throw "No API available" + } catch (error) { + if (error.code && error.code === 1000) { + throw error.message; + } else { + console.debug(error); + APIs[index].coolDownTime = new Date().getTime() + 1000 * 60 * 10; // 10 minutes + return multiApi(fnName, { index: index + 1, ...args }); + } + } + }; + + function parseTx(tx, addressOfTx) { + const { txid, hash, time, block_height, inputs, outputs, out, vin, vout, fee, fees, received, confirmed, status: { block_height: statusBlockHeight, block_time } = {} } = tx; + let parsedTx = { + txid: hash || txid, + time: (time * 1000) || new Date(confirmed || received).getTime() || block_time * 1000 || Date.now(), + block: block_height || statusBlockHeight, + } + //sender list + parsedTx.tx_senders = {}; + (inputs || vin).forEach(i => { + const address = i.prev_out?.addr || i.addresses?.[0] || i.prev_out?.address || i.addr || i.prevout.scriptpubkey_address; + const value = i.prev_out?.value || i.output_value || i.value || i.prevout.value; + if (address in parsedTx.tx_senders) + parsedTx.tx_senders[address] += value; + else parsedTx.tx_senders[address] = value; + }); + parsedTx.tx_input_value = 0; + for (let senderAddr in parsedTx.tx_senders) { + let val = parsedTx.tx_senders[senderAddr]; + parsedTx.tx_senders[senderAddr] = util.Sat_to_BTC(val); + parsedTx.tx_input_value += val; + } + parsedTx.tx_input_value = util.Sat_to_BTC(parsedTx.tx_input_value); + //receiver list + parsedTx.tx_receivers = {}; + (outputs || out || vout).forEach(o => { + const address = o.scriptpubkey_address || o.addresses?.[0] || o.scriptpubkey_address || o.addr; + const value = o.value || o.scriptpubkey_value; + if (address in parsedTx.tx_receivers) + parsedTx.tx_receivers[address] += value; + else parsedTx.tx_receivers[address] = value; + }); + parsedTx.tx_output_value = 0; + for (let receiverAddr in parsedTx.tx_receivers) { + let val = parsedTx.tx_receivers[receiverAddr]; + parsedTx.tx_receivers[receiverAddr] = util.Sat_to_BTC(val); + parsedTx.tx_output_value += val; + } + parsedTx.tx_output_value = util.Sat_to_BTC(parsedTx.tx_output_value); + // tx fee + parsedTx.tx_fee = util.Sat_to_BTC(fee || fees || (parsedTx.tx_input_value - parsedTx.tx_output_value)); + //detect tx type (in, out, self) + if (Object.keys(parsedTx.tx_receivers).length === 1 && Object.keys(parsedTx.tx_senders).length === 1 && Object.keys(parsedTx.tx_senders)[0] === Object.keys(parsedTx.tx_receivers)[0]) { + parsedTx.type = 'self'; + parsedTx.amount = parsedTx.tx_receivers[addressOfTx]; + parsedTx.address = addressOfTx; + } else if (addressOfTx in parsedTx.tx_senders && Object.keys(parsedTx.tx_receivers).some(addr => addr !== addressOfTx)) { + parsedTx.type = 'out'; + parsedTx.receiver = Object.keys(parsedTx.tx_receivers).filter(addr => addr != addressOfTx); + parsedTx.amount = parsedTx.receiver.reduce((t, addr) => t + parsedTx.tx_receivers[addr], 0) + parsedTx.tx_fee; + } else { + parsedTx.type = 'in'; + parsedTx.sender = Object.keys(parsedTx.tx_senders).filter(addr => addr != addressOfTx); + parsedTx.amount = parsedTx.tx_receivers[addressOfTx]; + } + return parsedTx; + } - //This library uses API provided by chain.so (https://chain.so/) - const URL = "https://blockchain.info/"; const DUST_AMT = 546, MIN_FEE_UPDATE = 219; - const fetch_api = btcOperator.fetch = function (api, json_res = true) { + const fetch_api = btcOperator.fetch = function (api, { asText = false, url = 'https://blockchain.info/' } = {}) { return new Promise((resolve, reject) => { - console.debug(URL + api); - fetch(URL + api).then(response => { + console.debug(url + api); + fetch(url + api).then(response => { if (response.ok) { - (json_res ? response.json() : response.text()) + (asText ? response.text() : response.json()) .then(result => resolve(result)) .catch(error => reject(error)) } else { @@ -25,14 +377,7 @@ }) }; - 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); - - const get_fee_rate = btcOperator.getFeeRate = () => { + function get_fee_rate() { return new Promise((resolve, reject) => { fetch('https://api.blockchain.info/mempool/fees').then(response => { if (response.ok) @@ -46,6 +391,7 @@ } const broadcastTx = btcOperator.broadcastTx = rawTxHex => new Promise((resolve, reject) => { + console.log('txHex:', rawTxHex) let url = 'https://coinb.in/api/?uid=1&key=12345678901234567890123456789012&setmodule=bitcoin&request=sendrawtransaction'; fetch(url, { method: 'POST', @@ -54,6 +400,7 @@ }, body: "rawtx=" + rawTxHex }).then(response => { + // multiApi('broadcast', { rawTxHex }).then(response => { response.text().then(resultText => { let r = resultText.match(/.*<\/result>/); if (!r) @@ -64,7 +411,12 @@ let txid = resultText.match(/.*<\/txid>/).pop().replace('', '').replace('', ''); resolve(txid); } else if (r == '0') { - let error = resultText.match(/.*<\/response>/).pop().replace('', '').replace('', ''); + let error + if (resultText.includes('')) { + error = resultText.match(/.*<\/message>/).pop().replace('', '').replace('', ''); + } else { + error = resultText.match(/.*<\/response>/).pop().replace('', '').replace('', ''); + } reject(decodeURIComponent(error.replace(/\+/g, " "))); } else reject(resultText); } @@ -267,10 +619,11 @@ } //BTC blockchain APIs - btcOperator.getBalance = addr => new Promise((resolve, reject) => { - fetch_api(`q/addressbalance/${addr}`) - .then(result => resolve(util.Sat_to_BTC(result))) + if (!validateAddress(addr)) + return reject("Invalid address"); + multiApi('balance', { addr }) + .then(result => resolve(result)) .catch(error => reject(error)) }); @@ -394,12 +747,16 @@ } btcOperator.validateTxParameters = validateTxParameters; - function createTransaction(senders, redeemScripts, receivers, amounts, fee, change_address, fee_from_receiver) { + const createTransaction = btcOperator.createTransaction = ({ + senders, redeemScripts, receivers, amounts, fee, change_address, + fee_from_receiver, allowUnconfirmedUtxos = false, sendingTx = false, + hasInsufficientBalance = false + }) => { return new Promise((resolve, reject) => { let total_amount = parseFloat(amounts.reduce((t, a) => t + a, 0).toFixed(8)); const tx = coinjs.transaction(); let output_size = addOutputs(tx, receivers, amounts, change_address); - addInputs(tx, senders, redeemScripts, total_amount, fee, output_size, fee_from_receiver).then(result => { + addInputs(tx, senders, redeemScripts, total_amount, fee, output_size, fee_from_receiver, allowUnconfirmedUtxos).then(result => { 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 = util.BTC_to_Sat(result.change_amount); //values are in satoshi if (fee_from_receiver) { //deduce fee from receivers if fee_from_receiver @@ -427,16 +784,21 @@ result.output_amount = total_amount - (fee_from_receiver ? result.fee : 0); result.total_size = BASE_TX_SIZE + output_size + result.input_size; result.transaction = tx; - resolve(result); + if (sendingTx && result.hasOwnProperty('hasInsufficientBalance') && result.hasInsufficientBalance) + reject({ + message: "Insufficient balance", + ...result + }); + else + resolve(result); }).catch(error => reject(error)) }) } - btcOperator.createTransaction = createTransaction; - function addInputs(tx, senders, redeemScripts, total_amount, fee, output_size, fee_from_receiver) { + function addInputs(tx, senders, redeemScripts, total_amount, fee, output_size, fee_from_receiver, allowUnconfirmedUtxos = false) { return new Promise((resolve, reject) => { if (fee !== null) { - addUTXOs(tx, senders, redeemScripts, fee_from_receiver ? total_amount : total_amount + fee, false).then(result => { + addUTXOs(tx, senders, redeemScripts, fee_from_receiver ? total_amount : total_amount + fee, false, { allowUnconfirmedUtxos }).then(result => { result.fee = fee; resolve(result); }).catch(error => reject(error)) @@ -445,8 +807,8 @@ let net_fee = BASE_TX_SIZE * fee_rate; net_fee += (output_size * fee_rate); (fee_from_receiver ? - addUTXOs(tx, senders, redeemScripts, total_amount, false) : - addUTXOs(tx, senders, redeemScripts, total_amount + net_fee, fee_rate) + addUTXOs(tx, senders, redeemScripts, total_amount, false, { allowUnconfirmedUtxos }) : + addUTXOs(tx, senders, redeemScripts, total_amount + net_fee, fee_rate, { allowUnconfirmedUtxos }) ).then(result => { result.fee = parseFloat((net_fee + (result.input_size * fee_rate)).toFixed(8)); result.fee_rate = fee_rate; @@ -458,7 +820,7 @@ } btcOperator.addInputs = addInputs; - function addUTXOs(tx, senders, redeemScripts, required_amount, fee_rate, rec_args = {}) { + function addUTXOs(tx, senders, redeemScripts, required_amount, fee_rate, rec_args = { allowUnconfirmedUtxos: false }) { return new Promise((resolve, reject) => { required_amount = parseFloat(required_amount.toFixed(8)); if (typeof rec_args.n === "undefined") { @@ -472,17 +834,24 @@ input_amount: rec_args.input_amount, change_amount: required_amount * -1 //required_amount will be -ve of change_amount }); - else if (rec_args.n >= senders.length) - return reject("Insufficient Balance"); + else if (rec_args.n >= senders.length) { + return resolve({ + hasInsufficientBalance: true, + input_size: rec_args.input_size, + input_amount: rec_args.input_amount, + change_amount: required_amount * -1 + }); + } let addr = senders[rec_args.n], rs = redeemScripts[rec_args.n]; let addr_type = coinjs.addressDecode(addr).type; let size_per_input = _sizePerInput(addr, rs); - fetch_api(`unspent?active=${addr}`).then(result => { - let utxos = result.unspent_outputs; + multiApi('unspent', { addr, allowUnconfirmedUtxos: rec_args.allowUnconfirmedUtxos }).then(utxos => { //console.debug("add-utxo", addr, rs, required_amount, utxos); for (let i = 0; i < utxos.length && required_amount > 0; i++) { - if (!utxos[i].confirmations) //ignore unconfirmed utxo + if (utxos.length === 1 && rec_args.allowUnconfirmedUtxos) { + console.log('allowing unconfirmed utxos') + } else if (!utxos[i].confirmations) //ignore unconfirmed utxo continue; var script; if (!rs || !rs.length) //legacy script @@ -525,43 +894,6 @@ } btcOperator.addOutputs = addOutputs; - /* - function autoFeeCalc(tx) { - return new Promise((resolve, reject) => { - get_fee_rate().then(fee_rate => { - let tx_size = tx.size(); - for (var i = 0; i < this.ins.length; i++) - switch (tx.extractScriptKey(i).type) { - case 'scriptpubkey': - tx_size += SIGN_SIZE; - break; - case 'segwit': - case 'multisig': - tx_size += SIGN_SIZE * 0.25; - break; - default: - console.warn('Unknown script-type'); - tx_size += SIGN_SIZE; - } - resolve(tx_size * fee_rate); - }).catch(error => reject(error)) - }) - } - - function editFee(tx, current_fee, target_fee, index = -1) { - //values are in satoshi - index = parseInt(index >= 0 ? index : tx.outs.length - index); - if (index < 0 || index >= tx.outs.length) - throw "Invalid index"; - let edit_value = parseInt(current_fee - target_fee), //rip of any decimal places - current_value = tx.outs[index].value; //could be BigInterger - if (edit_value < 0 && edit_value > current_value) - throw "Insufficient value at vout"; - tx.outs[index].value = current_value instanceof BigInteger ? - current_value.add(new BigInteger('' + edit_value)) : parseInt(current_value + edit_value); - } - */ - function tx_fetch_for_editing(tx) { return new Promise((resolve, reject) => { if (typeof tx == 'string' && /^[0-9a-f]{64}$/i.test(tx)) { //tx is txid @@ -784,9 +1116,9 @@ btcOperator.sendTx = function (senders, privkeys, receivers, amounts, fee = null, options = {}) { + options.sendingTx = true; return new Promise((resolve, reject) => { createSignedTx(senders, privkeys, receivers, amounts, fee, options).then(result => { - // debugger; broadcastTx(result.transaction.serialize()) .then(txid => resolve(txid)) .catch(error => reject(error)); @@ -808,7 +1140,7 @@ receivers, amounts, fee, - change_address: options.change_address + ...options })); } catch (e) { return reject(e) @@ -823,7 +1155,11 @@ if (redeemScripts.includes(null)) //TODO: segwit return reject("Unable to get redeem-script"); //create transaction - createTransaction(senders, redeemScripts, receivers, amounts, fee, options.change_address || senders[0], options.fee_from_receiver).then(result => { + createTransaction({ + senders, redeemScripts, receivers, amounts, fee, + change_address: options.change_address || senders[0], + ...options + }).then(result => { let tx = result.transaction; console.debug("Unsigned:", tx.serialize()); new Set(wif_keys).forEach(key => tx.sign(key, 1 /*sighashtype*/)); //Sign the tx using private key WIF @@ -833,7 +1169,9 @@ }) } - btcOperator.createTx = function (senders, receivers, amounts, fee = null, options = {}) { + btcOperator.createTx = function (senders, receivers, amounts, fee = null, options = { + allowUnconfirmedUtxos: false + }) { return new Promise((resolve, reject) => { try { ({ @@ -854,7 +1192,11 @@ if (redeemScripts.includes(null)) //TODO: segwit return reject("Unable to get redeem-script"); //create transaction - createTransaction(senders, redeemScripts, receivers, amounts, fee, options.change_address || senders[0], options.fee_from_receiver).then(result => { + createTransaction({ + senders, redeemScripts, receivers, amounts, fee, + change_address: options.change_address || senders[0], + ...options + }).then(result => { result.tx_hex = result.transaction.serialize(); delete result.transaction; resolve(result); @@ -890,7 +1232,12 @@ return reject(e) } //create transaction - createTransaction([sender], [redeemScript], receivers, amounts, fee, options.change_address || sender, options.fee_from_receiver).then(result => { + createTransaction({ + senders: [sender], redeemScripts: [redeemScript], + receivers, amounts, fee, + change_address: options.change_address || sender, + ...options + }).then(result => { result.tx_hex = result.transaction.serialize(); delete result.transaction; resolve(result); @@ -965,7 +1312,7 @@ } const getTxOutput = (txid, i) => new Promise((resolve, reject) => { - fetch_api(`rawtx/${txid}`) + multiApi('tx', { txid }) .then(result => resolve(result.out[i])) .catch(error => reject(error)) }); @@ -1027,87 +1374,40 @@ .catch(error => reject(error)) }) - const getTx = btcOperator.getTx = txid => new Promise((resolve, reject) => { - fetch_api(`rawtx/${txid}`).then(result => { - getLatestBlock().then(latest_block => resolve({ + const getTx = btcOperator.getTx = txid => new Promise(async (resolve, reject) => { + try { + const result = await multiApi('tx', { txid }); + if (!result.hasOwnProperty('confirmations')) + result.confirmations = await getLatestBlock() - result.block_height; + 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 + time: result.time, 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)) - }); + }) + } catch (error) { + reject(error) + } + }).catch(error => reject(error)) - getTx.hex = btcOperator.getTx.hex = txid => new Promise((resolve, reject) => { - fetch_api(`rawtx/${txid}?format=hex`, false) - .then(result => resolve(result)) - .catch(error => reject(error)) - }) + getTx.hex = btcOperator.getTx.hex = txid => multiApi('txHex', { txid }); 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); + Promise.all([ + multiApi('balance', { addr: address }), + multiApi('txs', { addr: address }) + ]).then(([balance, txs]) => { + const parsedTxs = txs.map(tx => parseTx(tx, address)); + resolve({ + address, + balance, + txs: parsedTxs + }); }).catch(error => reject(error)) }); diff --git a/scripts/btcOperator.min.js b/scripts/btcOperator.min.js index d4a1c1d..3e4eb65 100644 --- a/scripts/btcOperator.min.js +++ b/scripts/btcOperator.min.js @@ -1 +1 @@ -!function(EXPORTS){const btcOperator="object"===typeof module?module.exports:window.btcOperator={},URL="https://blockchain.info/",fetch_api=btcOperator.fetch=function(api,json_res=!0){return new Promise(((resolve,reject)=>{console.debug(URL+api),fetch(URL+api).then((response=>{response.ok?(json_res?response.json():response.text()).then((result=>resolve(result))).catch((error=>reject(error))):response.json().then((result=>reject(result))).catch((error=>reject(error)))})).catch((error=>reject(error)))}))},util=btcOperator.util={};util.Sat_to_BTC=value=>parseFloat((value/1e8).toFixed(8)),util.BTC_to_Sat=value=>parseInt(1e8*value);const get_fee_rate=btcOperator.getFeeRate=()=>new Promise(((resolve,reject)=>{fetch("https://api.blockchain.info/mempool/fees").then((response=>{response.ok?response.json().then((result=>resolve(util.Sat_to_BTC(result.regular)))).catch((error=>reject(error))):reject(response)})).catch((error=>reject(error)))})),broadcastTx=btcOperator.broadcastTx=rawTxHex=>new Promise(((resolve,reject)=>{fetch("https://coinb.in/api/?uid=1&key=12345678901234567890123456789012&setmodule=bitcoin&request=sendrawtransaction",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:"rawtx="+rawTxHex}).then((response=>{response.text().then((resultText=>{let r=resultText.match(/.*<\/result>/);if(r)if(r=r.pop().replace("","").replace("",""),"1"==r){let txid=resultText.match(/.*<\/txid>/).pop().replace("","").replace("","");resolve(txid)}else if("0"==r){let error=resultText.match(/.*<\/response>/).pop().replace("","").replace("","");reject(decodeURIComponent(error.replace(/\+/g," ")))}else reject(resultText);else reject(resultText)})).catch((error=>reject(error)))})).catch((error=>reject(error)))}));Object.defineProperties(btcOperator,{newKeys:{get:()=>{let r=coinjs.newKeys();return r.segwitAddress=coinjs.segwitAddress(r.pubkey).address,r.bech32Address=coinjs.bech32Address(r.pubkey).address,r}},pubkey:{value:key=>key.length>=66?key:64==key.length?coinjs.newPubkey(key):coinjs.wif2pubkey(key).pubkey},address:{value:(key,prefix=void 0)=>coinjs.pubkey2address(btcOperator.pubkey(key),prefix)},segwitAddress:{value:key=>coinjs.segwitAddress(btcOperator.pubkey(key)).address},bech32Address:{value:key=>coinjs.bech32Address(btcOperator.pubkey(key)).address},bech32mAddress:{value:key=>segwit_addr.encode("bc",1,key)}}),coinjs.compressed=!0;const verifyKey=btcOperator.verifyKey=function(addr,key){if(addr&&key)switch(coinjs.addressDecode(addr).type){case"standard":return btcOperator.address(key)===addr;case"multisig":return btcOperator.segwitAddress(key)===addr;case"bech32":return btcOperator.bech32Address(key)===addr;case"bech32m":return btcOperator.bech32mAddress(key)===addr;default:return null}},validateAddress=btcOperator.validateAddress=function(addr){if(!addr)return;let type=coinjs.addressDecode(addr).type;return!!["standard","multisig","bech32","multisigBech32","bech32m"].includes(type)&&type};btcOperator.multiSigAddress=function(pubKeys,minRequired,bech32=!0){if(!Array.isArray(pubKeys))throw"pubKeys must be an array of public keys";if(pubKeys.lengthnew Promise(((resolve,reject)=>{fetch_api(`q/addressbalance/${addr}`).then((result=>resolve(util.Sat_to_BTC(result)))).catch((error=>reject(error)))}));const BASE_TX_SIZE=12,BASE_INPUT_SIZE=41,LEGACY_INPUT_SIZE=107,BECH32_INPUT_SIZE=27,BECH32_MULTISIG_INPUT_SIZE=35,SEGWIT_INPUT_SIZE=59,MULTISIG_INPUT_SIZE_ES=351,BASE_OUTPUT_SIZE=9,LEGACY_OUTPUT_SIZE=25,BECH32_OUTPUT_SIZE=23,BECH32_MULTISIG_OUTPUT_SIZE=34,SEGWIT_OUTPUT_SIZE=23;function _redeemScript(addr,key){let decode=coinjs.addressDecode(addr);switch(decode.type){case"standard":return!1;case"multisig":return key?coinjs.segwitAddress(btcOperator.pubkey(key)).redeemscript:null;case"bech32":case"'multisigBech32":return decode.redeemscript;case"bech32m":return decode.outstring;default:return null}}function _sizePerOutput(addr){switch(coinjs.addressDecode(addr).type){case"standard":return BASE_OUTPUT_SIZE+LEGACY_OUTPUT_SIZE;case"bech32":return BASE_OUTPUT_SIZE+BECH32_OUTPUT_SIZE;case"multisigBech32":return BASE_OUTPUT_SIZE+BECH32_MULTISIG_OUTPUT_SIZE;case"multisig":return BASE_OUTPUT_SIZE+SEGWIT_OUTPUT_SIZE;case"bech32m":return BASE_OUTPUT_SIZE+BECH32M_OUTPUT_SIZE;default:return null}}function validateTxParameters(parameters){let invalids=[];if(parameters.senders&&(Array.isArray(parameters.senders)||(parameters.senders=[parameters.senders]),parameters.senders.forEach((id=>validateAddress(id)?null:invalids.push(id))),invalids.length))throw"Invalid senders:"+invalids;if(parameters.privkeys){if(Array.isArray(parameters.privkeys)||(parameters.privkeys=[parameters.privkeys]),parameters.senders.length!=parameters.privkeys.length)throw"Array length for senders and privkeys should be equal";if(parameters.senders.forEach(((id,i)=>{let key=parameters.privkeys[i];verifyKey(id,key)||invalids.push(id),64===key.length&&(parameters.privkeys[i]=coinjs.privkey2wif(key))})),invalids.length)throw"Invalid private key for address:"+invalids}if(Array.isArray(parameters.receivers)||(parameters.receivers=[parameters.receivers]),parameters.receivers.forEach((id=>validateAddress(id)?null:invalids.push(id))),invalids.length)throw"Invalid receivers:"+invalids;if(parameters.change_address&&!validateAddress(parameters.change_address))throw"Invalid change_address:"+parameters.change_address;if(("number"!=typeof parameters.fee||parameters.fee<=0)&&null!==parameters.fee)throw"Invalid fee:"+parameters.fee;if(Array.isArray(parameters.amounts)||(parameters.amounts=[parameters.amounts]),parameters.receivers.length!=parameters.amounts.length)throw"Array length for receivers and amounts should be equal";if(parameters.amounts.forEach((a=>"number"!=typeof a||a<=0?invalids.push(a):null)),invalids.length)throw"Invalid amounts:"+invalids;return parameters}function createTransaction(senders,redeemScripts,receivers,amounts,fee,change_address,fee_from_receiver){return new Promise(((resolve,reject)=>{let total_amount=parseFloat(amounts.reduce(((t,a)=>t+a),0).toFixed(8));const tx=coinjs.transaction();let output_size=addOutputs(tx,receivers,amounts,change_address);addInputs(tx,senders,redeemScripts,total_amount,fee,output_size,fee_from_receiver).then((result=>{if(result.change_amount>0&&result.change_amount>result.fee&&(tx.outs[tx.outs.length-1].value=util.BTC_to_Sat(result.change_amount)),fee_from_receiver){let fee_remaining=util.BTC_to_Sat(result.fee);for(let i=0;i0;i++)fee_remaining0)return reject("Send amount is less than fee")}let filtered_outputs=[],dust_value=0;tx.outs.forEach((o=>o.value>=546?filtered_outputs.push(o):dust_value+=o.value)),tx.outs=filtered_outputs,result.fee+=util.Sat_to_BTC(dust_value),result.output_size=output_size,result.output_amount=total_amount-(fee_from_receiver?result.fee:0),result.total_size=BASE_TX_SIZE+output_size+result.input_size,result.transaction=tx,resolve(result)})).catch((error=>reject(error)))}))}function addInputs(tx,senders,redeemScripts,total_amount,fee,output_size,fee_from_receiver){return new Promise(((resolve,reject)=>{null!==fee?addUTXOs(tx,senders,redeemScripts,fee_from_receiver?total_amount:total_amount+fee,!1).then((result=>{result.fee=fee,resolve(result)})).catch((error=>reject(error))):get_fee_rate().then((fee_rate=>{let net_fee=BASE_TX_SIZE*fee_rate;net_fee+=output_size*fee_rate,(fee_from_receiver?addUTXOs(tx,senders,redeemScripts,total_amount,!1):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_rate=fee_rate,resolve(result)})).catch((error=>reject(error)))})).catch((error=>reject(error)))}))}function addUTXOs(tx,senders,redeemScripts,required_amount,fee_rate,rec_args={}){return new Promise(((resolve,reject)=>{if(required_amount=parseFloat(required_amount.toFixed(8)),void 0===rec_args.n&&(rec_args.n=0,rec_args.input_size=0,rec_args.input_amount=0),required_amount<=0)return resolve({input_size:rec_args.input_size,input_amount:rec_args.input_amount,change_amount:-1*required_amount});if(rec_args.n>=senders.length)return reject("Insufficient Balance");let addr=senders[rec_args.n],rs=redeemScripts[rec_args.n],addr_type=coinjs.addressDecode(addr).type,size_per_input=function(addr,rs){switch(coinjs.addressDecode(addr).type){case"standard":return BASE_INPUT_SIZE+LEGACY_INPUT_SIZE;case"bech32":return BASE_INPUT_SIZE+BECH32_INPUT_SIZE;case"multisigBech32":return BASE_INPUT_SIZE+BECH32_MULTISIG_INPUT_SIZE;case"multisig":switch(coinjs.script().decodeRedeemScript(rs).type){case"segwit__":return BASE_INPUT_SIZE+SEGWIT_INPUT_SIZE;case"multisig__":return BASE_INPUT_SIZE+MULTISIG_INPUT_SIZE_ES;default:return null}default:return null}}(addr,rs);fetch_api(`unspent?active=${addr}`).then((result=>{let utxos=result.unspent_outputs;for(let i=0;i0;i++)if(utxos[i].confirmations){var script;if(rs&&rs.length)if(rs.match(/^00/)&&44==rs.length||40==rs.length&&rs.match(/^[a-f0-9]+$/gi)||"multisigBech32"===addr_type){let s=coinjs.script();s.writeBytes(Crypto.util.hexToBytes(rs)),s.writeOp(0),s.writeBytes(coinjs.numToBytes(utxos[i].value.toFixed(0),8)),script=Crypto.util.bytesToHex(s.buffer)}else script=rs;else script=utxos[i].script;tx.addinput(utxos[i].tx_hash_big_endian,utxos[i].tx_output_n,script,4294967293),rec_args.input_size+=size_per_input,rec_args.input_amount+=util.Sat_to_BTC(utxos[i].value),required_amount-=util.Sat_to_BTC(utxos[i].value),fee_rate&&(required_amount+=size_per_input*fee_rate)}rec_args.n+=1,addUTXOs(tx,senders,redeemScripts,required_amount,fee_rate,rec_args).then((result=>resolve(result))).catch((error=>reject(error)))})).catch((error=>reject(error)))}))}function addOutputs(tx,receivers,amounts,change_address){let size=0;for(let i in receivers)tx.addoutput(receivers[i],amounts[i]),size+=_sizePerOutput(receivers[i]);return tx.addoutput(change_address,0),size+=_sizePerOutput(change_address),size}function tx_fetch_for_editing(tx){return new Promise(((resolve,reject)=>{"string"==typeof tx&&/^[0-9a-f]{64}$/i.test(tx)?getTx.hex(tx).then((txhex=>resolve(deserializeTx(txhex)))).catch((error=>reject(error))):resolve(deserializeTx(tx))}))}BECH32M_OUTPUT_SIZE=35,btcOperator._redeemScript=_redeemScript,btcOperator.validateTxParameters=validateTxParameters,btcOperator.createTransaction=createTransaction,btcOperator.addInputs=addInputs,btcOperator.addUTXOs=addUTXOs,btcOperator.addOutputs=addOutputs,btcOperator.tx_fetch_for_editing=tx_fetch_for_editing;btcOperator.extractLastHexStrings=function(arr){const result=[];for(let i=0;i0){const lastHexString=innerArray[innerArray.length-1];result.push(lastHexString)}}return result};btcOperator.editFee=function(tx_hex,new_fee,private_keys,change_only=!0){return new Promise(((resolve,reject)=>{Array.isArray(private_keys)||(private_keys=[private_keys]),tx_fetch_for_editing(tx_hex).then((tx=>{parseTransaction(tx).then((tx_parsed=>{if(tx_parsed.fee>=new_fee)return reject("Fees can only be increased");var edit_output_address=new Set;!0===change_only?tx_parsed.inputs.forEach((inp=>edit_output_address.add(inp.address))):!1===change_only?tx_parsed.outputs.forEach((out=>edit_output_address.add(out.address))):"string"==typeof change_only?edit_output_address.add(change_only):Array.isArray(change_only)&&change_only.forEach((id=>edit_output_address.add(id)));let inc_fee=util.BTC_to_Sat(new_fee-tx_parsed.fee);if(inc_fee<219)return reject("Insufficient additional fee. Minimum increment: 219");for(let i=tx.outs.length-1;i>=0&&inc_fee>0;i--)if(edit_output_address.has(tx_parsed.outputs[i].address)){let current_value=tx.outs[i].value;current_value instanceof BigInteger&&(current_value=current_value.intValue()),current_value>inc_fee?(tx.outs[i].value=current_value-inc_fee,inc_fee=0):(inc_fee-=current_value,tx.outs[i].value=0)}if(inc_fee>0){let max_possible_fee=util.BTC_to_Sat(new_fee)-inc_fee;return reject(`Insufficient output values to increase fee. Maximum fee possible: ${util.Sat_to_BTC(max_possible_fee)}`)}tx.outs=tx.outs.filter((o=>o.value>=546));let wif_keys=[];for(let i in tx.ins){var addr=tx_parsed.inputs[i].address,value=util.BTC_to_Sat(tx_parsed.inputs[i].value);let addr_decode=coinjs.addressDecode(addr);var privKey=private_keys.find((pk=>verifyKey(addr,pk)));if(!privKey)return reject(`Private key missing for ${addr}`);const rs=_redeemScript(addr,privKey);var script;if(!1===rs?wif_keys.unshift(privKey):wif_keys.push(privKey),rs&&rs.length)if(rs.match(/^00/)&&44==rs.length||40==rs.length&&rs.match(/^[a-f0-9]+$/gi)||"multisigBech32"===addr_decode.type){let s=coinjs.script();s.writeBytes(Crypto.util.hexToBytes(rs)),s.writeOp(0),s.writeBytes(coinjs.numToBytes(value.toFixed(0),8)),script=Crypto.util.bytesToHex(s.buffer)}else script=rs;else{let s=coinjs.script();s.writeOp(118),s.writeOp(169),s.writeBytes(addr_decode.bytes),s.writeOp(136),s.writeOp(172),script=Crypto.util.bytesToHex(s.buffer)}tx.ins[i].script=coinjs.script(script)}tx.witness=!1,console.debug("Unsigned:",tx.serialize()),new Set(wif_keys).forEach((key=>tx.sign(key,1))),resolve(tx.serialize())})).catch((error=>reject(error)))})).catch((error=>reject(error)))}))},btcOperator.editFee_corewallet=function(tx_hex,new_fee,private_keys,change_only=!0){return new Promise(((resolve,reject)=>{Array.isArray(private_keys)||(private_keys=[private_keys]),tx_fetch_for_editing(tx_hex).then((tx=>{parseTransaction(tx).then((tx_parsed=>{if(tx_parsed.fee>=new_fee)return reject("Fees can only be increased");var edit_output_address=new Set;!0===change_only?tx_parsed.inputs.forEach((inp=>edit_output_address.add(inp.address))):!1===change_only?tx_parsed.outputs.forEach((out=>edit_output_address.add(out.address))):"string"==typeof change_only?edit_output_address.add(change_only):Array.isArray(change_only)&&change_only.forEach((id=>edit_output_address.add(id)));let inc_fee=util.BTC_to_Sat(new_fee-tx_parsed.fee);if(inc_fee<219)return reject("Insufficient additional fee. Minimum increment: 219");for(let i=tx.outs.length-1;i>=0&&inc_fee>0;i--)if(edit_output_address.has(tx_parsed.outputs[i].address)){let current_value=tx.outs[i].value;current_value instanceof BigInteger&&(current_value=current_value.intValue()),current_value>inc_fee?(tx.outs[i].value=current_value-inc_fee,inc_fee=0):(inc_fee-=current_value,tx.outs[i].value=0)}if(inc_fee>0){let max_possible_fee=util.BTC_to_Sat(new_fee)-inc_fee;return reject(`Insufficient output values to increase fee. Maximum fee possible: ${util.Sat_to_BTC(max_possible_fee)}`)}tx.outs=tx.outs.filter((o=>o.value>=546));let wif_keys=[],witness_position=0;for(let i in tx.ins){var addr=tx_parsed.inputs[i].address,value=util.BTC_to_Sat(tx_parsed.inputs[i].value);let addr_decode=coinjs.addressDecode(addr);var privKey=private_keys.find((pk=>verifyKey(addr,pk)));if(!privKey)return reject(`Private key missing for ${addr}`);const rs=_redeemScript(addr,privKey);var script;if(!1===rs?wif_keys.unshift(privKey):wif_keys.push(privKey),rs&&rs.length)if(rs.match(/^00/)&&44==rs.length||40==rs.length&&rs.match(/^[a-f0-9]+$/gi)){let s=coinjs.script();s.writeBytes(Crypto.util.hexToBytes(rs)),s.writeOp(0),s.writeBytes(coinjs.numToBytes(value.toFixed(0),8)),script=Crypto.util.bytesToHex(s.buffer),"bech32"==addr_decode&&(witness_position+=1)}else if("multisigBech32"===addr_decode.type){let redeemScript=btcOperator.extractLastHexStrings(tx.witness)[witness_position];witness_position+=1;let s=coinjs.script();s.writeBytes(Crypto.util.hexToBytes(redeemScript)),s.writeOp(0),s.writeBytes(coinjs.numToBytes(value.toFixed(0),8)),script=Crypto.util.bytesToHex(s.buffer)}else script=rs;else{let s=coinjs.script();s.writeOp(118),s.writeOp(169),s.writeBytes(addr_decode.bytes),s.writeOp(136),s.writeOp(172),script=Crypto.util.bytesToHex(s.buffer)}tx.ins[i].script=coinjs.script(script)}tx.witness=!1,console.debug("Unsigned:",tx.serialize()),new Set(wif_keys).forEach((key=>tx.sign(key,1))),btcOperator.checkSigned(tx)?resolve(tx.serialize()):reject("All private keys not present")})).catch((error=>reject(error)))})).catch((error=>reject(error)))}))},btcOperator.sendTx=function(senders,privkeys,receivers,amounts,fee=null,options={}){return new Promise(((resolve,reject)=>{createSignedTx(senders,privkeys,receivers,amounts,fee,options).then((result=>{broadcastTx(result.transaction.serialize()).then((txid=>resolve(txid))).catch((error=>reject(error)))})).catch((error=>reject(error)))}))};const createSignedTx=btcOperator.createSignedTx=function(senders,privkeys,receivers,amounts,fee=null,options={}){return new Promise(((resolve,reject)=>{try{({senders:senders,privkeys:privkeys,receivers:receivers,amounts:amounts}=validateTxParameters({senders:senders,privkeys:privkeys,receivers:receivers,amounts:amounts,fee:fee,change_address:options.change_address}))}catch(e){return reject(e)}let redeemScripts=[],wif_keys=[];for(let i in senders){let rs=_redeemScript(senders[i],privkeys[i]);redeemScripts.push(rs),!1===rs?wif_keys.unshift(privkeys[i]):wif_keys.push(privkeys[i])}if(redeemScripts.includes(null))return reject("Unable to get redeem-script");createTransaction(senders,redeemScripts,receivers,amounts,fee,options.change_address||senders[0],options.fee_from_receiver).then((result=>{let tx=result.transaction;console.debug("Unsigned:",tx.serialize()),new Set(wif_keys).forEach((key=>tx.sign(key,1))),console.debug("Signed:",tx.serialize()),resolve(result)})).catch((error=>reject(error)))}))};btcOperator.createTx=function(senders,receivers,amounts,fee=null,options={}){return new Promise(((resolve,reject)=>{try{({senders:senders,receivers:receivers,amounts:amounts}=validateTxParameters({senders:senders,receivers:receivers,amounts:amounts,fee:fee,change_address:options.change_address}))}catch(e){return reject(e)}let redeemScripts=senders.map((id=>_redeemScript(id)));if(redeemScripts.includes(null))return reject("Unable to get redeem-script");createTransaction(senders,redeemScripts,receivers,amounts,fee,options.change_address||senders[0],options.fee_from_receiver).then((result=>{result.tx_hex=result.transaction.serialize(),delete result.transaction,resolve(result)})).catch((error=>reject(error)))}))},btcOperator.createMultiSigTx=function(sender,redeemScript,receivers,amounts,fee=null,options={}){return new Promise(((resolve,reject)=>{let addr_type=validateAddress(sender);if(!["multisig","multisigBech32"].includes(addr_type))return reject("Invalid sender (multisig):"+sender);{let script=coinjs.script(),decode="multisig"==addr_type?script.decodeRedeemScript(redeemScript):script.decodeRedeemScriptBech32(redeemScript);if(!decode||decode.address!==sender)return reject("Invalid redeem-script")}try{({receivers:receivers,amounts:amounts}=validateTxParameters({receivers:receivers,amounts:amounts,fee:fee,change_address:options.change_address}))}catch(e){return reject(e)}createTransaction([sender],[redeemScript],receivers,amounts,fee,options.change_address||sender,options.fee_from_receiver).then((result=>{result.tx_hex=result.transaction.serialize(),delete result.transaction,resolve(result)})).catch((error=>reject(error)))}))};const deserializeTx=btcOperator.deserializeTx=function(tx){if("string"==typeof tx||Array.isArray(tx))try{tx=coinjs.transaction().deserialize(tx)}catch{throw"Invalid transaction hex"}else if("object"!=typeof tx||"function"!=typeof tx.sign)throw"Invalid transaction object";return tx};btcOperator.signTx=function(tx,privkeys,sighashtype=1){tx=deserializeTx(tx),Array.isArray(privkeys)||(privkeys=[privkeys]);for(let i in privkeys)64===privkeys[i].length&&(privkeys[i]=coinjs.privkey2wif(privkeys[i]));return new Set(privkeys).forEach((key=>tx.sign(key,sighashtype))),tx.serialize()};const checkSigned=btcOperator.checkSigned=function(tx,bool=!0){tx=deserializeTx(tx);let n=[];for(let i in tx.ins){var s=tx.extractScriptKey(i);if("multisig"!==s.type&&"multisig_bech32"!==s.type)n.push("true"==s.signed||tx.witness[i]&&2==tx.witness[i].length);else{var rs=coinjs.script().decodeRedeemScript(s.script);let x={s:s.signatures,r:rs.signaturesRequired,t:rs.pubkeys.length};if(x.r>x.t)throw"signaturesRequired is more than publicKeys";x.s!0!==x)).length:n};btcOperator.checkIfSameTx=function(tx1,tx2){if(tx1=deserializeTx(tx1),tx2=deserializeTx(tx2),tx1.ins.length!==tx2.ins.length||tx1.outs.length!==tx2.outs.length)return!1;for(let i=0;inew Promise(((resolve,reject)=>{fetch_api(`rawtx/${txid}`).then((result=>resolve(result.out[i]))).catch((error=>reject(error)))})),parseTransaction=btcOperator.parseTransaction=function(tx){return new Promise(((resolve,reject)=>{tx=deserializeTx(tx);let result={},promises=[];for(let i=0;i{result.inputs=inputs.map((inp=>Object({address:inp.addr,value:util.Sat_to_BTC(inp.value)})));let signed=checkSigned(tx,!1);result.inputs.forEach(((inp,i)=>inp.signed=signed[i])),result.outputs=tx.outs.map((out=>{var address;switch(out.script.chunks[0]){case 0:address=util.encodeBech32(Crypto.util.bytesToHex(out.script.chunks[1]),coinjs.bech32.version,coinjs.bech32.hrp);break;case 169:address=util.encodeLegacy(Crypto.util.bytesToHex(out.script.chunks[1]),coinjs.multisig);break;case 118:address=util.encodeLegacy(Crypto.util.bytesToHex(out.script.chunks[2]),coinjs.pub)}return{address:address,value:util.Sat_to_BTC(out.value)}})),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)),resolve(result)})).catch((error=>reject(error)))}))};btcOperator.transactionID=function(tx){tx=deserializeTx(tx);let clone=coinjs.clone(tx);clone.witness=null;let raw_bytes=Crypto.util.hexToBytes(clone.serialize()),txid=Crypto.SHA256(Crypto.SHA256(raw_bytes,{asBytes:!0}),{asBytes:!0}).reverse();return Crypto.util.bytesToHex(txid)};const getLatestBlock=btcOperator.getLatestBlock=()=>new Promise(((resolve,reject)=>{fetch_api("q/getblockcount").then((result=>resolve(result))).catch((error=>reject(error)))})),getTx=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:1e3*result.time,confirmations:null===result.block_height?0:latest_block-result.block_height,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)))}));getTx.hex=btcOperator.getTx.hex=txid=>new Promise(((resolve,reject)=>{fetch_api(`rawtx/${txid}?format=hex`,!1).then((result=>resolve(result))).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:1e3*tx.time,block:tx.block_height,tx_senders:{}};tx.inputs.forEach((i=>{i.prev_out.addr in d.tx_senders?d.tx_senders[i.prev_out.addr]+=i.prev_out.value: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),d.tx_receivers={},tx.out.forEach((o=>{o.addr in d.tx_receivers?d.tx_receivers[o.addr]+=o.value: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}return d.tx_output_value=util.Sat_to_BTC(d.tx_output_value),d.tx_fee=util.Sat_to_BTC(tx.fee),tx.result>0?(d.type="in",d.amount=util.Sat_to_BTC(tx.result),d.sender=Object.keys(d.tx_senders).filter((s=>s!==address))):Object.keys(d.tx_receivers).some((r=>r!==address))?(d.type="out",d.amount=util.Sat_to_BTC(-1*tx.result),d.receiver=Object.keys(d.tx_receivers).filter((r=>r!==address)),d.fee=d.tx_fee):(d.type="self",d.amount=d.tx_receivers[address],d.address=address),d})),resolve(details)})).catch((error=>reject(error)))})),btcOperator.getBlock=block=>new Promise(((resolve,reject)=>{fetch_api(`rawblock/${block}`).then((result=>resolve({height:result.height,hash:result.hash,merkle_root:result.mrkl_root,prev_block:result.prev_block,next_block:result.next_block[0],size:result.size,time:1e3*result.time,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)))}))}(); \ No newline at end of file +!function(EXPORTS){const btcOperator="object"===typeof module?module.exports:window.btcOperator={},util=btcOperator.util={};util.Sat_to_BTC=value=>parseFloat((value/1e8).toFixed(8)),util.BTC_to_Sat=value=>parseInt(1e8*value);const checkIfTor=btcOperator.checkIfTor=()=>fetch("https://check.torproject.org/api/ip",{mode:"no-cors"}).then((response=>response.json())).then((result=>result.IsTor)).catch((error=>!1));let isTor=!1;checkIfTor().then((result=>isTor=result));const APIs=btcOperator.APIs=[{url:"https://api.blockcypher.com/v1/btc/main/",name:"Blockcypher",balance({addr:addr}){return fetch_api(`addrs/${addr}/balance`,{url:this.url}).then((result=>util.Sat_to_BTC(result.balance)))},async block({id:id}){try{let block=await fetch_api(`blocks/${id}`,{url:this.url});return formatBlock(block)}catch(e){console.log(e)}},broadcast({rawTxHex:rawTxHex,url:url}){return fetch(`${this.url}txs/push`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({tx:rawTxHex})}).then((response=>response.json())).then((result=>result.hash))}},{url:"https://blockstream.info/api/",name:"Blockstream",hasOnion:!0,onionUrl:"http://explorerzydxu5ecjrkwceayqybizmpjjznk5izmitf2modhcusuqlid.onion/api/",balance({addr:addr,url:url}){return fetch_api(`address/${addr}/utxo`,{url:url||this.url}).then((result=>{const balance=result.reduce(((t,u)=>t+u.value),0);return util.Sat_to_BTC(balance)}))},async block({id:id,url:url}){try{let blockHash=id;/^[0-9a-f]{64}$/i.test(id)||(blockHash=await fetch_api(`block-height/${id}`,{url:url||this.url,asText:!0}));const block=await fetch_api(`block/${blockHash}`,{url:url||this.url});return formatBlock(block)}catch(e){console.error(e)}},broadcast({rawTxHex:rawTxHex,url:url}){return fetch(`${url||this.url}tx`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({tx:rawTxHex})}).then((response=>response.text()))}},{url:"https://mempool.space/api/",name:"Mempool",balance({addr:addr}){return fetch_api(`address/${addr}`,{url:this.url}).then((result=>util.Sat_to_BTC(result.chain_stats.funded_txo_sum-result.chain_stats.spent_txo_sum)))},tx({txid:txid}){return fetch_api(`tx/${txid}`,{url:this.url}).then((result=>formatTx(result)))},txHex({txid:txid}){return fetch_api(`tx/${txid}/hex`,{url:this.url,asText:!0})},txs({addr:addr,before:before,after:after}){return fetch_api(`address/${addr}/txs${before?`?before=${before}`:""}${after?`?after=${after}`:""}`,{url:this.url})},async block({id:id}){try{let blockHash=id;/^[0-9a-f]{64}$/i.test(id)||(blockHash=await fetch_api(`block-height/${id}`,{url:this.url,asText:!0}));const block=await fetch_api(`block/${blockHash}`,{url:this.url});return formatBlock(block)}catch(e){console.error(e)}},broadcast({rawTxHex:rawTxHex,url:url}){return fetch(`${this.url}tx`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({tx:rawTxHex})}).then((response=>response.text()))}},{url:"https://blockchain.info/",name:"Blockchain",balance({addr:addr}){return fetch_api(`q/addressbalance/${addr}`,{url:this.url}).then((result=>util.Sat_to_BTC(result)))},unspent({addr:addr,allowUnconfirmedUtxos:allowUnconfirmedUtxos=!1}){return fetch_api(`unspent?active=${addr}`,{url:this.url}).then((result=>formatUtxos(result.unspent_outputs,allowUnconfirmedUtxos)))},tx({txid:txid}){return fetch_api(`rawtx/${txid}`,{url:this.url}).then((result=>formatTx(result)))},txHex({txid:txid}){return fetch_api(`rawtx/${txid}?format=hex`,{url:this.url,asText:!0})},txs({addr:addr,before:before,after:after}){return fetch_api(`rawaddr/${addr}${before?`?before=${before}`:""}${after?`?after=${after}`:""}`,{url:this.url}).then((result=>result.txs))},async block({id:id}){try{let block;if(/^[0-9a-f]{64}$/i.test(id))block=await fetch_api(`rawblock/${id}`,{url:this.url});else{block=(await fetch_api(`block-height/${id}?format=json`,{url:this.url})).blocks[0]}return formatBlock(block)}catch(e){console.error(e)}},async blockTxs({id:id}){try{let block;if(/^[0-9a-f]{64}$/i.test(id))block=await fetch_api(`rawblock/${id}`,{url:this.url});else{block=(await fetch_api(`block-height/${id}?format=json`,{url:this.url})).blocks[0]}return block.tx}catch(e){console.error(e)}}}];btcOperator.util.format={};const formatBlock=btcOperator.util.format.block=async block=>{try{const{height:height,hash:hash,id:id,time:time,timestamp:timestamp,mrkl_root:mrkl_root,merkle_root:merkle_root,prev_block:prev_block,next_block:next_block,size:size}=block,details={height:height,hash:hash||id,time:1e3*(time||timestamp),merkle_root:merkle_root||mrkl_root,size:size};return prev_block&&(details.prev_block=prev_block),next_block&&(details.next_block=next_block[0]),details}catch(e){console.error(e)}},formatUtxos=btcOperator.util.format.utxos=async(utxos,allowUnconfirmedUtxos=!1)=>{try{if(!allowUnconfirmedUtxos&&!utxos||!Array.isArray(utxos))throw{message:"No utxos found",code:1e3};return utxos.map((utxo=>{const{tx_hash:tx_hash,tx_hash_big_endian:tx_hash_big_endian,txid:txid,tx_output_n:tx_output_n,vout:vout,value:value,script:script,confirmations:confirmations,status:{confirmed:confirmed}={}}=utxo;return{confirmations:confirmations||confirmed,tx_hash_big_endian:tx_hash_big_endian||tx_hash||txid,tx_output_n:tx_output_n||vout,value:value,script:script}}))}catch(e){throw e}},formatTx=btcOperator.util.format.tx=async tx=>{try{const{txid:txid,hash:hash,time:time,block_height:block_height,fee:fee,fees:fees,received:received,confirmed:confirmed,size:size,double_spend:double_spend,block_hash:block_hash,confirmations:confirmations,status:{block_height:statusBlockHeight,block_hash:statusBlockHash,block_time:block_time}={}}=tx,inputs=tx.vin||tx.inputs,outputs=tx.vout||tx.outputs||tx.out;return{hash:hash||txid,size:size,fee:fee||fees,double_spend:double_spend,time:1e3*time||new Date(confirmed||received).getTime()||1e3*block_time||Date.now(),block_height:block_height||statusBlockHeight,block_hash:block_hash||statusBlockHash,confirmations:confirmations,inputs:inputs.map((input=>({index:input.n||input.output_index||input.vout,prev_out:{addr:input.prev_out?.addr||input.addresses?.[0]||input.prev_out?.address||input.addr||input.prevout.scriptpubkey_address,value:input.prev_out?.value||input.output_value||input.prevout.value}}))),out:outputs.map((output=>({addr:output.scriptpubkey_address||output.addresses?.[0]||output.scriptpubkey_address||output.addr,value:output.value||output.scriptpubkey_value})))}}catch(e){console.error(e)}},multiApi=btcOperator.multiApi=async(fnName,{index:index=0,...args}={})=>{try{let triedOnion=!1;for(;index(new Date).getTime()))return await APIs[index][fnName](args);index+=1}if(isTor&&!triedOnion)for(triedOnion=!0,index=0;index(new Date).getTime()))return await multiApi(fnName,{index:index+1,...args,url:APIs[index].onionUrl});index+=1}throw"No API available"}catch(error){if(error.code&&1e3===error.code)throw error.message;return console.debug(error),APIs[index].coolDownTime=(new Date).getTime()+6e5,multiApi(fnName,{index:index+1,...args})}};const fetch_api=btcOperator.fetch=function(api,{asText:asText=!1,url:url="https://blockchain.info/"}={}){return new Promise(((resolve,reject)=>{console.debug(url+api),fetch(url+api).then((response=>{response.ok?(asText?response.text():response.json()).then((result=>resolve(result))).catch((error=>reject(error))):response.json().then((result=>reject(result))).catch((error=>reject(error)))})).catch((error=>reject(error)))}))};const broadcastTx=btcOperator.broadcastTx=rawTxHex=>new Promise(((resolve,reject)=>{console.log("txHex:",rawTxHex);fetch("https://coinb.in/api/?uid=1&key=12345678901234567890123456789012&setmodule=bitcoin&request=sendrawtransaction",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:"rawtx="+rawTxHex}).then((response=>{response.text().then((resultText=>{let r=resultText.match(/.*<\/result>/);if(r)if(r=r.pop().replace("","").replace("",""),"1"==r){let txid=resultText.match(/.*<\/txid>/).pop().replace("","").replace("","");resolve(txid)}else if("0"==r){let error;error=resultText.includes("")?resultText.match(/.*<\/message>/).pop().replace("","").replace("",""):resultText.match(/.*<\/response>/).pop().replace("","").replace("",""),reject(decodeURIComponent(error.replace(/\+/g," ")))}else reject(resultText);else reject(resultText)})).catch((error=>reject(error)))})).catch((error=>reject(error)))}));Object.defineProperties(btcOperator,{newKeys:{get:()=>{let r=coinjs.newKeys();return r.segwitAddress=coinjs.segwitAddress(r.pubkey).address,r.bech32Address=coinjs.bech32Address(r.pubkey).address,r}},pubkey:{value:key=>key.length>=66?key:64==key.length?coinjs.newPubkey(key):coinjs.wif2pubkey(key).pubkey},address:{value:(key,prefix=void 0)=>coinjs.pubkey2address(btcOperator.pubkey(key),prefix)},segwitAddress:{value:key=>coinjs.segwitAddress(btcOperator.pubkey(key)).address},bech32Address:{value:key=>coinjs.bech32Address(btcOperator.pubkey(key)).address},bech32mAddress:{value:key=>segwit_addr.encode("bc",1,key)}}),coinjs.compressed=!0;const verifyKey=btcOperator.verifyKey=function(addr,key){if(addr&&key)switch(coinjs.addressDecode(addr).type){case"standard":return btcOperator.address(key)===addr;case"multisig":return btcOperator.segwitAddress(key)===addr;case"bech32":return btcOperator.bech32Address(key)===addr;case"bech32m":return btcOperator.bech32mAddress(key)===addr;default:return null}},validateAddress=btcOperator.validateAddress=function(addr){if(!addr)return;let type=coinjs.addressDecode(addr).type;return!!["standard","multisig","bech32","multisigBech32","bech32m"].includes(type)&&type};btcOperator.multiSigAddress=function(pubKeys,minRequired,bech32=!0){if(!Array.isArray(pubKeys))throw"pubKeys must be an array of public keys";if(pubKeys.lengthnew Promise(((resolve,reject)=>{if(!validateAddress(addr))return reject("Invalid address");multiApi("balance",{addr:addr}).then((result=>resolve(result))).catch((error=>reject(error)))}));const BASE_INPUT_SIZE=41,LEGACY_INPUT_SIZE=107,BECH32_INPUT_SIZE=27,BECH32_MULTISIG_INPUT_SIZE=35,SEGWIT_INPUT_SIZE=59,MULTISIG_INPUT_SIZE_ES=351,BASE_OUTPUT_SIZE=9,LEGACY_OUTPUT_SIZE=25,BECH32_OUTPUT_SIZE=23,BECH32_MULTISIG_OUTPUT_SIZE=34,SEGWIT_OUTPUT_SIZE=23;function _redeemScript(addr,key){let decode=coinjs.addressDecode(addr);switch(decode.type){case"standard":return!1;case"multisig":return key?coinjs.segwitAddress(btcOperator.pubkey(key)).redeemscript:null;case"bech32":case"'multisigBech32":return decode.redeemscript;case"bech32m":return decode.outstring;default:return null}}function _sizePerOutput(addr){switch(coinjs.addressDecode(addr).type){case"standard":return BASE_OUTPUT_SIZE+LEGACY_OUTPUT_SIZE;case"bech32":return BASE_OUTPUT_SIZE+BECH32_OUTPUT_SIZE;case"multisigBech32":return BASE_OUTPUT_SIZE+BECH32_MULTISIG_OUTPUT_SIZE;case"multisig":return BASE_OUTPUT_SIZE+SEGWIT_OUTPUT_SIZE;case"bech32m":return BASE_OUTPUT_SIZE+BECH32M_OUTPUT_SIZE;default:return null}}function validateTxParameters(parameters){let invalids=[];if(parameters.senders&&(Array.isArray(parameters.senders)||(parameters.senders=[parameters.senders]),parameters.senders.forEach((id=>validateAddress(id)?null:invalids.push(id))),invalids.length))throw"Invalid senders:"+invalids;if(parameters.privkeys){if(Array.isArray(parameters.privkeys)||(parameters.privkeys=[parameters.privkeys]),parameters.senders.length!=parameters.privkeys.length)throw"Array length for senders and privkeys should be equal";if(parameters.senders.forEach(((id,i)=>{let key=parameters.privkeys[i];verifyKey(id,key)||invalids.push(id),64===key.length&&(parameters.privkeys[i]=coinjs.privkey2wif(key))})),invalids.length)throw"Invalid private key for address:"+invalids}if(Array.isArray(parameters.receivers)||(parameters.receivers=[parameters.receivers]),parameters.receivers.forEach((id=>validateAddress(id)?null:invalids.push(id))),invalids.length)throw"Invalid receivers:"+invalids;if(parameters.change_address&&!validateAddress(parameters.change_address))throw"Invalid change_address:"+parameters.change_address;if(("number"!=typeof parameters.fee||parameters.fee<=0)&&null!==parameters.fee)throw"Invalid fee:"+parameters.fee;if(Array.isArray(parameters.amounts)||(parameters.amounts=[parameters.amounts]),parameters.receivers.length!=parameters.amounts.length)throw"Array length for receivers and amounts should be equal";if(parameters.amounts.forEach((a=>"number"!=typeof a||a<=0?invalids.push(a):null)),invalids.length)throw"Invalid amounts:"+invalids;return parameters}BECH32M_OUTPUT_SIZE=35,btcOperator._redeemScript=_redeemScript,btcOperator.validateTxParameters=validateTxParameters;const createTransaction=btcOperator.createTransaction=({senders:senders,redeemScripts:redeemScripts,receivers:receivers,amounts:amounts,fee:fee,change_address:change_address,fee_from_receiver:fee_from_receiver,allowUnconfirmedUtxos:allowUnconfirmedUtxos=!1,sendingTx:sendingTx=!1,hasInsufficientBalance:hasInsufficientBalance=!1})=>new Promise(((resolve,reject)=>{let total_amount=parseFloat(amounts.reduce(((t,a)=>t+a),0).toFixed(8));const tx=coinjs.transaction();let output_size=addOutputs(tx,receivers,amounts,change_address);addInputs(tx,senders,redeemScripts,total_amount,fee,output_size,fee_from_receiver,allowUnconfirmedUtxos).then((result=>{if(result.change_amount>0&&result.change_amount>result.fee&&(tx.outs[tx.outs.length-1].value=util.BTC_to_Sat(result.change_amount)),fee_from_receiver){let fee_remaining=util.BTC_to_Sat(result.fee);for(let i=0;i0;i++)fee_remaining0)return reject("Send amount is less than fee")}let filtered_outputs=[],dust_value=0;tx.outs.forEach((o=>o.value>=546?filtered_outputs.push(o):dust_value+=o.value)),tx.outs=filtered_outputs,result.fee+=util.Sat_to_BTC(dust_value),result.output_size=output_size,result.output_amount=total_amount-(fee_from_receiver?result.fee:0),result.total_size=12+output_size+result.input_size,result.transaction=tx,sendingTx&&result.hasOwnProperty("hasInsufficientBalance")&&result.hasInsufficientBalance?reject({message:"Insufficient balance",...result}):resolve(result)})).catch((error=>reject(error)))}));function addInputs(tx,senders,redeemScripts,total_amount,fee,output_size,fee_from_receiver,allowUnconfirmedUtxos=!1){return new Promise(((resolve,reject)=>{null!==fee?addUTXOs(tx,senders,redeemScripts,fee_from_receiver?total_amount:total_amount+fee,!1,{allowUnconfirmedUtxos:allowUnconfirmedUtxos}).then((result=>{result.fee=fee,resolve(result)})).catch((error=>reject(error))):new Promise(((resolve,reject)=>{fetch("https://api.blockchain.info/mempool/fees").then((response=>{response.ok?response.json().then((result=>resolve(util.Sat_to_BTC(result.regular)))).catch((error=>reject(error))):reject(response)})).catch((error=>reject(error)))})).then((fee_rate=>{let net_fee=12*fee_rate;net_fee+=output_size*fee_rate,(fee_from_receiver?addUTXOs(tx,senders,redeemScripts,total_amount,!1,{allowUnconfirmedUtxos:allowUnconfirmedUtxos}):addUTXOs(tx,senders,redeemScripts,total_amount+net_fee,fee_rate,{allowUnconfirmedUtxos:allowUnconfirmedUtxos})).then((result=>{result.fee=parseFloat((net_fee+result.input_size*fee_rate).toFixed(8)),result.fee_rate=fee_rate,resolve(result)})).catch((error=>reject(error)))})).catch((error=>reject(error)))}))}function addUTXOs(tx,senders,redeemScripts,required_amount,fee_rate,rec_args={allowUnconfirmedUtxos:!1}){return new Promise(((resolve,reject)=>{if(required_amount=parseFloat(required_amount.toFixed(8)),void 0===rec_args.n&&(rec_args.n=0,rec_args.input_size=0,rec_args.input_amount=0),required_amount<=0)return resolve({input_size:rec_args.input_size,input_amount:rec_args.input_amount,change_amount:-1*required_amount});if(rec_args.n>=senders.length)return resolve({hasInsufficientBalance:!0,input_size:rec_args.input_size,input_amount:rec_args.input_amount,change_amount:-1*required_amount});let addr=senders[rec_args.n],rs=redeemScripts[rec_args.n],addr_type=coinjs.addressDecode(addr).type,size_per_input=function(addr,rs){switch(coinjs.addressDecode(addr).type){case"standard":return BASE_INPUT_SIZE+LEGACY_INPUT_SIZE;case"bech32":return BASE_INPUT_SIZE+BECH32_INPUT_SIZE;case"multisigBech32":return BASE_INPUT_SIZE+BECH32_MULTISIG_INPUT_SIZE;case"multisig":switch(coinjs.script().decodeRedeemScript(rs).type){case"segwit__":return BASE_INPUT_SIZE+SEGWIT_INPUT_SIZE;case"multisig__":return BASE_INPUT_SIZE+MULTISIG_INPUT_SIZE_ES;default:return null}default:return null}}(addr,rs);multiApi("unspent",{addr:addr,allowUnconfirmedUtxos:rec_args.allowUnconfirmedUtxos}).then((utxos=>{for(let i=0;i0;i++){if(1===utxos.length&&rec_args.allowUnconfirmedUtxos)console.log("allowing unconfirmed utxos");else if(!utxos[i].confirmations)continue;var script;if(rs&&rs.length)if(rs.match(/^00/)&&44==rs.length||40==rs.length&&rs.match(/^[a-f0-9]+$/gi)||"multisigBech32"===addr_type){let s=coinjs.script();s.writeBytes(Crypto.util.hexToBytes(rs)),s.writeOp(0),s.writeBytes(coinjs.numToBytes(utxos[i].value.toFixed(0),8)),script=Crypto.util.bytesToHex(s.buffer)}else script=rs;else script=utxos[i].script;tx.addinput(utxos[i].tx_hash_big_endian,utxos[i].tx_output_n,script,4294967293),rec_args.input_size+=size_per_input,rec_args.input_amount+=util.Sat_to_BTC(utxos[i].value),required_amount-=util.Sat_to_BTC(utxos[i].value),fee_rate&&(required_amount+=size_per_input*fee_rate)}rec_args.n+=1,addUTXOs(tx,senders,redeemScripts,required_amount,fee_rate,rec_args).then((result=>resolve(result))).catch((error=>reject(error)))})).catch((error=>reject(error)))}))}function addOutputs(tx,receivers,amounts,change_address){let size=0;for(let i in receivers)tx.addoutput(receivers[i],amounts[i]),size+=_sizePerOutput(receivers[i]);return tx.addoutput(change_address,0),size+=_sizePerOutput(change_address),size}function tx_fetch_for_editing(tx){return new Promise(((resolve,reject)=>{"string"==typeof tx&&/^[0-9a-f]{64}$/i.test(tx)?getTx.hex(tx).then((txhex=>resolve(deserializeTx(txhex)))).catch((error=>reject(error))):resolve(deserializeTx(tx))}))}btcOperator.addInputs=addInputs,btcOperator.addUTXOs=addUTXOs,btcOperator.addOutputs=addOutputs,btcOperator.tx_fetch_for_editing=tx_fetch_for_editing;btcOperator.extractLastHexStrings=function(arr){const result=[];for(let i=0;i0){const lastHexString=innerArray[innerArray.length-1];result.push(lastHexString)}}return result};btcOperator.editFee=function(tx_hex,new_fee,private_keys,change_only=!0){return new Promise(((resolve,reject)=>{Array.isArray(private_keys)||(private_keys=[private_keys]),tx_fetch_for_editing(tx_hex).then((tx=>{parseTransaction(tx).then((tx_parsed=>{if(tx_parsed.fee>=new_fee)return reject("Fees can only be increased");var edit_output_address=new Set;!0===change_only?tx_parsed.inputs.forEach((inp=>edit_output_address.add(inp.address))):!1===change_only?tx_parsed.outputs.forEach((out=>edit_output_address.add(out.address))):"string"==typeof change_only?edit_output_address.add(change_only):Array.isArray(change_only)&&change_only.forEach((id=>edit_output_address.add(id)));let inc_fee=util.BTC_to_Sat(new_fee-tx_parsed.fee);if(inc_fee<219)return reject("Insufficient additional fee. Minimum increment: 219");for(let i=tx.outs.length-1;i>=0&&inc_fee>0;i--)if(edit_output_address.has(tx_parsed.outputs[i].address)){let current_value=tx.outs[i].value;current_value instanceof BigInteger&&(current_value=current_value.intValue()),current_value>inc_fee?(tx.outs[i].value=current_value-inc_fee,inc_fee=0):(inc_fee-=current_value,tx.outs[i].value=0)}if(inc_fee>0){let max_possible_fee=util.BTC_to_Sat(new_fee)-inc_fee;return reject(`Insufficient output values to increase fee. Maximum fee possible: ${util.Sat_to_BTC(max_possible_fee)}`)}tx.outs=tx.outs.filter((o=>o.value>=546));let wif_keys=[];for(let i in tx.ins){var addr=tx_parsed.inputs[i].address,value=util.BTC_to_Sat(tx_parsed.inputs[i].value);let addr_decode=coinjs.addressDecode(addr);var privKey=private_keys.find((pk=>verifyKey(addr,pk)));if(!privKey)return reject(`Private key missing for ${addr}`);const rs=_redeemScript(addr,privKey);var script;if(!1===rs?wif_keys.unshift(privKey):wif_keys.push(privKey),rs&&rs.length)if(rs.match(/^00/)&&44==rs.length||40==rs.length&&rs.match(/^[a-f0-9]+$/gi)||"multisigBech32"===addr_decode.type){let s=coinjs.script();s.writeBytes(Crypto.util.hexToBytes(rs)),s.writeOp(0),s.writeBytes(coinjs.numToBytes(value.toFixed(0),8)),script=Crypto.util.bytesToHex(s.buffer)}else script=rs;else{let s=coinjs.script();s.writeOp(118),s.writeOp(169),s.writeBytes(addr_decode.bytes),s.writeOp(136),s.writeOp(172),script=Crypto.util.bytesToHex(s.buffer)}tx.ins[i].script=coinjs.script(script)}tx.witness=!1,console.debug("Unsigned:",tx.serialize()),new Set(wif_keys).forEach((key=>tx.sign(key,1))),resolve(tx.serialize())})).catch((error=>reject(error)))})).catch((error=>reject(error)))}))},btcOperator.editFee_corewallet=function(tx_hex,new_fee,private_keys,change_only=!0){return new Promise(((resolve,reject)=>{Array.isArray(private_keys)||(private_keys=[private_keys]),tx_fetch_for_editing(tx_hex).then((tx=>{parseTransaction(tx).then((tx_parsed=>{if(tx_parsed.fee>=new_fee)return reject("Fees can only be increased");var edit_output_address=new Set;!0===change_only?tx_parsed.inputs.forEach((inp=>edit_output_address.add(inp.address))):!1===change_only?tx_parsed.outputs.forEach((out=>edit_output_address.add(out.address))):"string"==typeof change_only?edit_output_address.add(change_only):Array.isArray(change_only)&&change_only.forEach((id=>edit_output_address.add(id)));let inc_fee=util.BTC_to_Sat(new_fee-tx_parsed.fee);if(inc_fee<219)return reject("Insufficient additional fee. Minimum increment: 219");for(let i=tx.outs.length-1;i>=0&&inc_fee>0;i--)if(edit_output_address.has(tx_parsed.outputs[i].address)){let current_value=tx.outs[i].value;current_value instanceof BigInteger&&(current_value=current_value.intValue()),current_value>inc_fee?(tx.outs[i].value=current_value-inc_fee,inc_fee=0):(inc_fee-=current_value,tx.outs[i].value=0)}if(inc_fee>0){let max_possible_fee=util.BTC_to_Sat(new_fee)-inc_fee;return reject(`Insufficient output values to increase fee. Maximum fee possible: ${util.Sat_to_BTC(max_possible_fee)}`)}tx.outs=tx.outs.filter((o=>o.value>=546));let wif_keys=[],witness_position=0;for(let i in tx.ins){var addr=tx_parsed.inputs[i].address,value=util.BTC_to_Sat(tx_parsed.inputs[i].value);let addr_decode=coinjs.addressDecode(addr);var privKey=private_keys.find((pk=>verifyKey(addr,pk)));if(!privKey)return reject(`Private key missing for ${addr}`);const rs=_redeemScript(addr,privKey);var script;if(!1===rs?wif_keys.unshift(privKey):wif_keys.push(privKey),rs&&rs.length)if(rs.match(/^00/)&&44==rs.length||40==rs.length&&rs.match(/^[a-f0-9]+$/gi)){let s=coinjs.script();s.writeBytes(Crypto.util.hexToBytes(rs)),s.writeOp(0),s.writeBytes(coinjs.numToBytes(value.toFixed(0),8)),script=Crypto.util.bytesToHex(s.buffer),"bech32"==addr_decode&&(witness_position+=1)}else if("multisigBech32"===addr_decode.type){let redeemScript=btcOperator.extractLastHexStrings(tx.witness)[witness_position];witness_position+=1;let s=coinjs.script();s.writeBytes(Crypto.util.hexToBytes(redeemScript)),s.writeOp(0),s.writeBytes(coinjs.numToBytes(value.toFixed(0),8)),script=Crypto.util.bytesToHex(s.buffer)}else script=rs;else{let s=coinjs.script();s.writeOp(118),s.writeOp(169),s.writeBytes(addr_decode.bytes),s.writeOp(136),s.writeOp(172),script=Crypto.util.bytesToHex(s.buffer)}tx.ins[i].script=coinjs.script(script)}tx.witness=!1,console.debug("Unsigned:",tx.serialize()),new Set(wif_keys).forEach((key=>tx.sign(key,1))),btcOperator.checkSigned(tx)?resolve(tx.serialize()):reject("All private keys not present")})).catch((error=>reject(error)))})).catch((error=>reject(error)))}))},btcOperator.sendTx=function(senders,privkeys,receivers,amounts,fee=null,options={}){return options.sendingTx=!0,new Promise(((resolve,reject)=>{createSignedTx(senders,privkeys,receivers,amounts,fee,options).then((result=>{broadcastTx(result.transaction.serialize()).then((txid=>resolve(txid))).catch((error=>reject(error)))})).catch((error=>reject(error)))}))};const createSignedTx=btcOperator.createSignedTx=function(senders,privkeys,receivers,amounts,fee=null,options={}){return new Promise(((resolve,reject)=>{try{({senders:senders,privkeys:privkeys,receivers:receivers,amounts:amounts}=validateTxParameters({senders:senders,privkeys:privkeys,receivers:receivers,amounts:amounts,fee:fee,...options}))}catch(e){return reject(e)}let redeemScripts=[],wif_keys=[];for(let i in senders){let rs=_redeemScript(senders[i],privkeys[i]);redeemScripts.push(rs),!1===rs?wif_keys.unshift(privkeys[i]):wif_keys.push(privkeys[i])}if(redeemScripts.includes(null))return reject("Unable to get redeem-script");createTransaction({senders:senders,redeemScripts:redeemScripts,receivers:receivers,amounts:amounts,fee:fee,change_address:options.change_address||senders[0],...options}).then((result=>{let tx=result.transaction;console.debug("Unsigned:",tx.serialize()),new Set(wif_keys).forEach((key=>tx.sign(key,1))),console.debug("Signed:",tx.serialize()),resolve(result)})).catch((error=>reject(error)))}))};btcOperator.createTx=function(senders,receivers,amounts,fee=null,options={allowUnconfirmedUtxos:!1}){return new Promise(((resolve,reject)=>{try{({senders:senders,receivers:receivers,amounts:amounts}=validateTxParameters({senders:senders,receivers:receivers,amounts:amounts,fee:fee,change_address:options.change_address}))}catch(e){return reject(e)}let redeemScripts=senders.map((id=>_redeemScript(id)));if(redeemScripts.includes(null))return reject("Unable to get redeem-script");createTransaction({senders:senders,redeemScripts:redeemScripts,receivers:receivers,amounts:amounts,fee:fee,change_address:options.change_address||senders[0],...options}).then((result=>{result.tx_hex=result.transaction.serialize(),delete result.transaction,resolve(result)})).catch((error=>reject(error)))}))},btcOperator.createMultiSigTx=function(sender,redeemScript,receivers,amounts,fee=null,options={}){return new Promise(((resolve,reject)=>{let addr_type=validateAddress(sender);if(!["multisig","multisigBech32"].includes(addr_type))return reject("Invalid sender (multisig):"+sender);{let script=coinjs.script(),decode="multisig"==addr_type?script.decodeRedeemScript(redeemScript):script.decodeRedeemScriptBech32(redeemScript);if(!decode||decode.address!==sender)return reject("Invalid redeem-script")}try{({receivers:receivers,amounts:amounts}=validateTxParameters({receivers:receivers,amounts:amounts,fee:fee,change_address:options.change_address}))}catch(e){return reject(e)}createTransaction({senders:[sender],redeemScripts:[redeemScript],receivers:receivers,amounts:amounts,fee:fee,change_address:options.change_address||sender,...options}).then((result=>{result.tx_hex=result.transaction.serialize(),delete result.transaction,resolve(result)})).catch((error=>reject(error)))}))};const deserializeTx=btcOperator.deserializeTx=function(tx){if("string"==typeof tx||Array.isArray(tx))try{tx=coinjs.transaction().deserialize(tx)}catch{throw"Invalid transaction hex"}else if("object"!=typeof tx||"function"!=typeof tx.sign)throw"Invalid transaction object";return tx};btcOperator.signTx=function(tx,privkeys,sighashtype=1){tx=deserializeTx(tx),Array.isArray(privkeys)||(privkeys=[privkeys]);for(let i in privkeys)64===privkeys[i].length&&(privkeys[i]=coinjs.privkey2wif(privkeys[i]));return new Set(privkeys).forEach((key=>tx.sign(key,sighashtype))),tx.serialize()};const checkSigned=btcOperator.checkSigned=function(tx,bool=!0){tx=deserializeTx(tx);let n=[];for(let i in tx.ins){var s=tx.extractScriptKey(i);if("multisig"!==s.type&&"multisig_bech32"!==s.type)n.push("true"==s.signed||tx.witness[i]&&2==tx.witness[i].length);else{var rs=coinjs.script().decodeRedeemScript(s.script);let x={s:s.signatures,r:rs.signaturesRequired,t:rs.pubkeys.length};if(x.r>x.t)throw"signaturesRequired is more than publicKeys";x.s!0!==x)).length:n};btcOperator.checkIfSameTx=function(tx1,tx2){if(tx1=deserializeTx(tx1),tx2=deserializeTx(tx2),tx1.ins.length!==tx2.ins.length||tx1.outs.length!==tx2.outs.length)return!1;for(let i=0;inew Promise(((resolve,reject)=>{multiApi("tx",{txid:txid}).then((result=>resolve(result.out[i]))).catch((error=>reject(error)))})),parseTransaction=btcOperator.parseTransaction=function(tx){return new Promise(((resolve,reject)=>{tx=deserializeTx(tx);let result={},promises=[];for(let i=0;i{result.inputs=inputs.map((inp=>Object({address:inp.addr,value:util.Sat_to_BTC(inp.value)})));let signed=checkSigned(tx,!1);result.inputs.forEach(((inp,i)=>inp.signed=signed[i])),result.outputs=tx.outs.map((out=>{var address;switch(out.script.chunks[0]){case 0:address=util.encodeBech32(Crypto.util.bytesToHex(out.script.chunks[1]),coinjs.bech32.version,coinjs.bech32.hrp);break;case 169:address=util.encodeLegacy(Crypto.util.bytesToHex(out.script.chunks[1]),coinjs.multisig);break;case 118:address=util.encodeLegacy(Crypto.util.bytesToHex(out.script.chunks[2]),coinjs.pub)}return{address:address,value:util.Sat_to_BTC(out.value)}})),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)),resolve(result)})).catch((error=>reject(error)))}))};btcOperator.transactionID=function(tx){tx=deserializeTx(tx);let clone=coinjs.clone(tx);clone.witness=null;let raw_bytes=Crypto.util.hexToBytes(clone.serialize()),txid=Crypto.SHA256(Crypto.SHA256(raw_bytes,{asBytes:!0}),{asBytes:!0}).reverse();return Crypto.util.bytesToHex(txid)};const getLatestBlock=btcOperator.getLatestBlock=()=>new Promise(((resolve,reject)=>{fetch_api("q/getblockcount").then((result=>resolve(result))).catch((error=>reject(error)))})),getTx=btcOperator.getTx=txid=>new Promise((async(resolve,reject)=>{try{const result=await multiApi("tx",{txid:txid});result.hasOwnProperty("confirmations")||(result.confirmations=await getLatestBlock()-result.block_height),resolve({block:result.block_height,txid:result.hash,time:result.time,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)}})).catch((error=>reject(error)));getTx.hex=btcOperator.getTx.hex=txid=>multiApi("txHex",{txid:txid}),btcOperator.getAddressData=address=>new Promise(((resolve,reject)=>{Promise.all([multiApi("balance",{addr:address}),multiApi("txs",{addr:address})]).then((([balance,txs])=>{const parsedTxs=txs.map((tx=>function(tx,addressOfTx){const{txid:txid,hash:hash,time:time,block_height:block_height,inputs:inputs,outputs:outputs,out:out,vin:vin,vout:vout,fee:fee,fees:fees,received:received,confirmed:confirmed,status:{block_height:statusBlockHeight,block_time:block_time}={}}=tx;let parsedTx={txid:hash||txid,time:1e3*time||new Date(confirmed||received).getTime()||1e3*block_time||Date.now(),block:block_height||statusBlockHeight,tx_senders:{}};(inputs||vin).forEach((i=>{const address=i.prev_out?.addr||i.addresses?.[0]||i.prev_out?.address||i.addr||i.prevout.scriptpubkey_address,value=i.prev_out?.value||i.output_value||i.value||i.prevout.value;address in parsedTx.tx_senders?parsedTx.tx_senders[address]+=value:parsedTx.tx_senders[address]=value})),parsedTx.tx_input_value=0;for(let senderAddr in parsedTx.tx_senders){let val=parsedTx.tx_senders[senderAddr];parsedTx.tx_senders[senderAddr]=util.Sat_to_BTC(val),parsedTx.tx_input_value+=val}parsedTx.tx_input_value=util.Sat_to_BTC(parsedTx.tx_input_value),parsedTx.tx_receivers={},(outputs||out||vout).forEach((o=>{const address=o.scriptpubkey_address||o.addresses?.[0]||o.scriptpubkey_address||o.addr,value=o.value||o.scriptpubkey_value;address in parsedTx.tx_receivers?parsedTx.tx_receivers[address]+=value:parsedTx.tx_receivers[address]=value})),parsedTx.tx_output_value=0;for(let receiverAddr in parsedTx.tx_receivers){let val=parsedTx.tx_receivers[receiverAddr];parsedTx.tx_receivers[receiverAddr]=util.Sat_to_BTC(val),parsedTx.tx_output_value+=val}return parsedTx.tx_output_value=util.Sat_to_BTC(parsedTx.tx_output_value),parsedTx.tx_fee=util.Sat_to_BTC(fee||fees||parsedTx.tx_input_value-parsedTx.tx_output_value),1===Object.keys(parsedTx.tx_receivers).length&&1===Object.keys(parsedTx.tx_senders).length&&Object.keys(parsedTx.tx_senders)[0]===Object.keys(parsedTx.tx_receivers)[0]?(parsedTx.type="self",parsedTx.amount=parsedTx.tx_receivers[addressOfTx],parsedTx.address=addressOfTx):addressOfTx in parsedTx.tx_senders&&Object.keys(parsedTx.tx_receivers).some((addr=>addr!==addressOfTx))?(parsedTx.type="out",parsedTx.receiver=Object.keys(parsedTx.tx_receivers).filter((addr=>addr!=addressOfTx)),parsedTx.amount=parsedTx.receiver.reduce(((t,addr)=>t+parsedTx.tx_receivers[addr]),0)+parsedTx.tx_fee):(parsedTx.type="in",parsedTx.sender=Object.keys(parsedTx.tx_senders).filter((addr=>addr!=addressOfTx)),parsedTx.amount=parsedTx.tx_receivers[addressOfTx]),parsedTx}(tx,address)));resolve({address:address,balance:balance,txs:parsedTxs})})).catch((error=>reject(error)))})),btcOperator.getBlock=block=>new Promise(((resolve,reject)=>{fetch_api(`rawblock/${block}`).then((result=>resolve({height:result.height,hash:result.hash,merkle_root:result.mrkl_root,prev_block:result.prev_block,next_block:result.next_block[0],size:result.size,time:1e3*result.time,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)))}))}(); \ No newline at end of file