From e479233f27d50112eec4a595de35f3b5197dd6bb Mon Sep 17 00:00:00 2001 From: sairaj mote Date: Fri, 29 Dec 2023 04:27:45 +0530 Subject: [PATCH] Revert "Update btcOperator.js" This reverts commit 7064345bc96baf1d6a97cd56d05199a405ba3eee. --- scripts/btcOperator.js | 561 +++++++++++------------------------------ 1 file changed, 141 insertions(+), 420 deletions(-) diff --git a/scripts/btcOperator.js b/scripts/btcOperator.js index d332c01..d6d23f3 100644 --- a/scripts/btcOperator.js +++ b/scripts/btcOperator.js @@ -1,379 +1,19 @@ -(function (EXPORTS) { //btcOperator v1.2.0 +(function (EXPORTS) { //btcOperator v1.1.3c /* 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 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)) - }, - unspent({ addr }) { - return fetch_api(`addrs/${addr}?unspentOnly=true&includeScript=true`, { url: this.url }) - .then(result => formatUtxos(result.txrefs)) - }, - tx({ txid }) { - return fetch_api(`txs/${txid}`, { url: this.url }) - .then(result => formatTx(result)) - }, - txHex({ txid }) { - return fetch_api(`txs/${txid}?includeHex=true`, { url: this.url }) - .then(result => result.hex) - }, - txs({ addr, before, after }) { - return fetch_api(`addrs/${addr}/full?limit=50${before ? `&before=${before}` : ''}${after ? `&after=${after}` : ''}`, { url: this.url }) - .then(result => result.txs) - - }, - async block({ id }) { - try { - let block = await fetch_api(`blocks/${id}`, { url: this.url }) - return formatBlock(block) - } catch (e) { - console.log(e) - } - }, - broadcast(rawTxHex) { - return fetch_api('txs/push', { - url: this.url, - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ tx: rawTxHex }) - }) - .then(result => result.hash) - } - }, - { - url: 'https://blockstream.info/api/', - name: 'Blockstream', - balance({ addr }) { - return fetch_api(`address/${addr}/utxo`, { url: this.url }) - .then(result => { - const balance = result.reduce((t, u) => t + u.value, 0) - return util.Sat_to_BTC(balance) - }) - }, - // unspent({ addr }) { // API doesn't return utxo script - // return fetch_api(`address/${addr}/utxo`, { url: this.url }) - // .then(result => formatUtxos(result)) - // }, - 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, asJson: false }) - }, - 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, asJson: false }) - const block = await fetch_api(`block/${blockHash}`, { url: this.url }) - return formatBlock(block) - } catch (e) { - console.error(e) - } - }, - broadcast(rawTxHex) { - return fetch_api('tx', { - url: this.url, - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ tx: rawTxHex }) - }, { asJson: false }) - } - }, - { - 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)) - }, - // unspent({ addr }) { // API doesn't return utxo script - // return fetch_api(`address/${addr}/utxo`, { url: this.url }) - // .then(result => formatUtxos(result)) - // }, - 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, asJson: false }) - }, - 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, asJson: false }) - const block = await fetch_api(`block/${blockHash}`, { url: this.url }) - return formatBlock(block) - } catch (e) { - console.error(e) - } - }, - broadcast(rawTxHex) { - return fetch_api('tx', { - url: this.url, - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ tx: rawTxHex }) - }, { asJson: false }) - } - }, - { - 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 }) { - return fetch_api(`unspent?active=${addr}`, { url: this.url }) - .then(result => formatUtxos(result.unspent_outputs)) - }, - 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, asJson: false }) - }, - 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) - } - }, - broadcast(rawTxHex) { - return fetch_api('pushtx', { - url: this.url, - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - }, - body: "tx=" + rawTxHex - }) - } - } - ] - - 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) => { - try { - 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) { - console.error(e) - } - } - - const formatTx = btcOperator.util.format.tx = async (tx, addressOfTx) => { - try { - const { txid, hash, time, block_height, fee, fees, received, - confirmed, size, double_spend, block_hash, confirmations, - status: { block_height: statusBlockHeight, block_hash: statusBlockHash } = {} - } = 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_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, - value: input.prev_out?.value || input.output_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 = (fnName, { index = 0, ...args } = {}) => { - return new Promise((resolve, reject) => { - if (index >= APIs.length) - return reject("All APIs failed"); - if (!APIs[index][fnName] || APIs[index].coolDownTime && APIs[index].coolDownTime > new Date().getTime()) - return multiApi(fnName, { index: index + 1, ...args }) - .then(result => resolve(result)) - .catch(error => reject(error)) - APIs[index][fnName](args) - .then(result => resolve(result)) - .catch(error => { - // if failed add a cool down and try next API - console.debug(error); - APIs[index].coolDownTime = new Date().getTime() + 1000 * 60 * 10; // 10 minutes - multiApi(fnName, { index: index + 1, ...args }) - .then(result => resolve(result)) - .catch(error => reject(error)) - }) - }) - } - - - function parseTx(tx, maddressOfTx) { - const { txid, hash, time, block_height, inputs, outputs, out, vin, vout, fee, fees, received, confirmed, status: { block_height: statusBlockHeight } = {} } = tx; - let parsedTx = { - txid: hash || txid, - time: (time * 1000) || new Date(confirmed || received).getTime(), - 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; - const value = i.prev_out?.value || i.output_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[maddressOfTx]; - parsedTx.address = maddressOfTx; - } else if (maddressOfTx in parsedTx.tx_senders && Object.keys(parsedTx.tx_receivers).some(addr => addr !== maddressOfTx)) { - parsedTx.type = 'out'; - parsedTx.receiver = Object.keys(parsedTx.tx_receivers).filter(addr => addr != maddressOfTx); - 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 != maddressOfTx); - parsedTx.amount = parsedTx.tx_receivers[maddressOfTx]; - } - 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, { asJson = true, url = 'https://blockchain.info/' } = {}) { + const fetch_api = btcOperator.fetch = function (api, json_res = true) { 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) { - (asJson ? response.json() : response.text()) + (json_res ? response.json() : response.text()) .then(result => resolve(result)) .catch(error => reject(error)) } else { @@ -385,7 +25,14 @@ }) }; - function get_fee_rate() { + 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 = () => { return new Promise((resolve, reject) => { fetch('https://api.blockchain.info/mempool/fees').then(response => { if (response.ok) @@ -399,7 +46,6 @@ } 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', @@ -418,12 +64,7 @@ let txid = resultText.match(/.*<\/txid>/).pop().replace('', '').replace('', ''); resolve(txid); } else if (r == '0') { - let error - if (resultText.includes('')) { - error = resultText.match(/.*<\/message>/).pop().replace('', '').replace('', ''); - } else { - error = resultText.match(/.*<\/response>/).pop().replace('', '').replace('', ''); - } + let error = resultText.match(/.*<\/response>/).pop().replace('', '').replace('', ''); reject(decodeURIComponent(error.replace(/\+/g, " "))); } else reject(resultText); } @@ -626,11 +267,10 @@ } //BTC blockchain APIs + btcOperator.getBalance = addr => new Promise((resolve, reject) => { - if (!validateAddress(addr)) - return reject("Invalid address"); - multiApi('balance', { addr }) - .then(result => resolve(result)) + fetch_api(`q/addressbalance/${addr}`) + .then(result => resolve(util.Sat_to_BTC(result))) .catch(error => reject(error)) }); @@ -754,12 +394,12 @@ } btcOperator.validateTxParameters = validateTxParameters; - function createTransaction(senders, redeemScripts, receivers, amounts, fee, change_address, fee_from_receiver, allowUnconfirmedUtxos = false) { + 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, allowUnconfirmedUtxos).then(result => { + addInputs(tx, senders, redeemScripts, total_amount, fee, output_size, fee_from_receiver).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 @@ -793,10 +433,10 @@ } btcOperator.createTransaction = createTransaction; - function addInputs(tx, senders, redeemScripts, total_amount, fee, output_size, fee_from_receiver, allowUnconfirmedUtxos = false) { + function addInputs(tx, senders, redeemScripts, total_amount, fee, output_size, fee_from_receiver) { return new Promise((resolve, reject) => { if (fee !== null) { - addUTXOs(tx, senders, redeemScripts, fee_from_receiver ? total_amount : total_amount + fee, false, { allowUnconfirmedUtxos }).then(result => { + addUTXOs(tx, senders, redeemScripts, fee_from_receiver ? total_amount : total_amount + fee, false).then(result => { result.fee = fee; resolve(result); }).catch(error => reject(error)) @@ -805,8 +445,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, { allowUnconfirmedUtxos }) : - addUTXOs(tx, senders, redeemScripts, total_amount + net_fee, fee_rate, { allowUnconfirmedUtxos }) + 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_rate = fee_rate; @@ -818,7 +458,7 @@ } btcOperator.addInputs = addInputs; - function addUTXOs(tx, senders, redeemScripts, required_amount, fee_rate, rec_args = { allowUnconfirmedUtxos: false }) { + function addUTXOs(tx, senders, redeemScripts, required_amount, fee_rate, rec_args = {}) { return new Promise((resolve, reject) => { required_amount = parseFloat(required_amount.toFixed(8)); if (typeof rec_args.n === "undefined") { @@ -832,19 +472,17 @@ 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) { + else if (rec_args.n >= senders.length) return reject("Insufficient Balance"); - } 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); - multiApi('unspent', { addr }).then(utxos => { + fetch_api(`unspent?active=${addr}`).then(result => { + let utxos = result.unspent_outputs; //console.debug("add-utxo", addr, rs, required_amount, utxos); for (let i = 0; i < utxos.length && required_amount > 0; i++) { - if (utxos.length === 1 && rec_args.allowUnconfirmedUtxos) { - console.log('allowing unconfirmed utxos') - } else if (!utxos[i].confirmations) //ignore unconfirmed utxo + if (!utxos[i].confirmations) //ignore unconfirmed utxo continue; var script; if (!rs || !rs.length) //legacy script @@ -887,6 +525,43 @@ } 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 @@ -1111,6 +786,7 @@ btcOperator.sendTx = function (senders, privkeys, receivers, amounts, fee = null, options = {}) { 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)); @@ -1157,9 +833,7 @@ }) } - btcOperator.createTx = function (senders, receivers, amounts, fee = null, options = { - allowUnconfirmedUtxos: false - }) { + btcOperator.createTx = function (senders, receivers, amounts, fee = null, options = {}) { return new Promise((resolve, reject) => { try { ({ @@ -1180,7 +854,7 @@ 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, options.allowUnconfirmedUtxos).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(); delete result.transaction; resolve(result); @@ -1291,7 +965,7 @@ } const getTxOutput = (txid, i) => new Promise((resolve, reject) => { - multiApi('tx', { txid }) + fetch_api(`rawtx/${txid}`) .then(result => resolve(result.out[i])) .catch(error => reject(error)) }); @@ -1353,40 +1027,87 @@ .catch(error => reject(error)) }) - 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({ + const 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: result.time, + 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) - } - }).catch(error => reject(error)) + })) + }).catch(error => reject(error)) + }); - getTx.hex = btcOperator.getTx.hex = txid => multiApi('txHex', { txid }); + 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)) + }) 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 => parseTx(tx, address)); - resolve({ - address, - balance, - txs: parsedTxs - }); + 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)) });