From 259177f63343c48fc404dd7061643fd3cd99f02d Mon Sep 17 00:00:00 2001 From: sairaj mote Date: Fri, 29 Dec 2023 04:28:28 +0530 Subject: [PATCH] Revert "Update btcOperator.js" This reverts commit 45f6c103ebf45be555be25ab21b424d5bc354caa. --- scripts/btcOperator.js | 704 +++++++++-------------------------------- 1 file changed, 143 insertions(+), 561 deletions(-) diff --git a/scripts/btcOperator.js b/scripts/btcOperator.js index d332c01..2abb80f 100644 --- a/scripts/btcOperator.js +++ b/scripts/btcOperator.js @@ -1,379 +1,19 @@ -(function (EXPORTS) { //btcOperator v1.2.0 +(function (EXPORTS) { //btcOperator v1.1.3b /* 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,6 +25,13 @@ }) }; + const SATOSHI_IN_BTC = 1e8; + + const util = btcOperator.util = {}; + + util.Sat_to_BTC = value => parseFloat((value / SATOSHI_IN_BTC).toFixed(8)); + util.BTC_to_Sat = value => parseInt(value * SATOSHI_IN_BTC); + function get_fee_rate() { return new Promise((resolve, reject) => { fetch('https://api.blockchain.info/mempool/fees').then(response => { @@ -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); } @@ -451,9 +92,6 @@ }, bech32Address: { value: key => coinjs.bech32Address(btcOperator.pubkey(key)).address - }, - bech32mAddress: { - value: key => segwit_addr.encode("bc", 1, key) } }); @@ -469,8 +107,6 @@ return btcOperator.segwitAddress(key) === addr; case "bech32": return btcOperator.bech32Address(key) === addr; - case "bech32m": - return btcOperator.bech32mAddress(key) === addr; // Key is a byte array of 32 bytes default: return null; } @@ -480,7 +116,7 @@ if (!addr) return undefined; let type = coinjs.addressDecode(addr).type; - if (["standard", "multisig", "bech32", "multisigBech32", "bech32m"].includes(type)) + if (["standard", "multisig", "bech32", "multisigBech32"].includes(type)) return type; else return false; @@ -626,11 +262,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)) }); @@ -646,7 +281,6 @@ BECH32_OUTPUT_SIZE = 23, BECH32_MULTISIG_OUTPUT_SIZE = 34, SEGWIT_OUTPUT_SIZE = 23; - BECH32M_OUTPUT_SIZE = 35; // Check this later function _redeemScript(addr, key) { let decode = coinjs.addressDecode(addr); @@ -657,15 +291,10 @@ return key ? coinjs.segwitAddress(btcOperator.pubkey(key)).redeemscript : null; case "bech32": return decode.redeemscript; - case "'multisigBech32": - return decode.redeemscript; //Multisig-edit-fee-change1 - case "bech32m": - return decode.outstring; //Maybe the redeemscript will come when input processing happens for bech32m default: return null; } } - btcOperator._redeemScript = _redeemScript; function _sizePerInput(addr, rs) { switch (coinjs.addressDecode(addr).type) { @@ -699,8 +328,6 @@ 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; } @@ -752,14 +379,13 @@ //return return parameters; } - 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 @@ -791,12 +417,11 @@ }).catch(error => reject(error)) }) } - 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 +430,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; @@ -816,9 +441,8 @@ } }) } - 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 +456,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 @@ -873,7 +495,6 @@ }).catch(error => reject(error)) }) } - btcOperator.addUTXOs = addUTXOs; function addOutputs(tx, receivers, amounts, change_address) { let size = 0; @@ -885,7 +506,43 @@ size += _sizePerOutput(change_address); return size; } - 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) => { @@ -896,19 +553,7 @@ } else resolve(deserializeTx(tx)); }) } - btcOperator.tx_fetch_for_editing = tx_fetch_for_editing; - - const extractLastHexStrings = btcOperator.extractLastHexStrings = function (arr) { - const result = []; - for (let i = 0; i < arr.length; i++) { - const innerArray = arr[i]; - if (innerArray.length > 0) { - const lastHexString = innerArray[innerArray.length - 1]; - result.push(lastHexString); - } - } - return result; - } + btcOperator.editFee = function (tx_hex, new_fee, private_keys, change_only = true) { return new Promise((resolve, reject) => { @@ -999,118 +644,10 @@ }) } - btcOperator.editFee_corewallet = function (tx_hex, new_fee, private_keys, change_only = true) { - return new Promise((resolve, reject) => { - if (!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"); - - //editable addresses in output values (for fee increase) - var edit_output_address = new Set(); - if (change_only === true) //allow only change values (ie, sender address) to be edited to inc fee - tx_parsed.inputs.forEach(inp => edit_output_address.add(inp.address)); - else if (change_only === false) //allow all output values to be edited - tx_parsed.outputs.forEach(out => edit_output_address.add(out.address)); - else if (typeof change_only == 'string') // allow only given receiver id output to be edited - edit_output_address.add(change_only); - else if (Array.isArray(change_only)) //allow only given set of receiver id outputs to be edited - change_only.forEach(id => edit_output_address.add(id)); - - //edit output values to increase fee - let inc_fee = util.BTC_to_Sat(new_fee - tx_parsed.fee); - if (inc_fee < MIN_FEE_UPDATE) - return reject(`Insufficient additional fee. Minimum increment: ${MIN_FEE_UPDATE}`); - for (let i = tx.outs.length - 1; i >= 0 && inc_fee > 0; i--) //reduce in reverse order - if (edit_output_address.has(tx_parsed.outputs[i].address)) { - let current_value = tx.outs[i].value; - if (current_value instanceof BigInteger) //convert BigInteger class to inv value - current_value = current_value.intValue(); - //edit the value as required - if (current_value > inc_fee) { - tx.outs[i].value = current_value - inc_fee; - inc_fee = 0; - } else { - 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; //in satoshi - 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 >= DUST_AMT); //remove all output with value less than DUST amount - - //remove existing signatures and reset the scripts - let wif_keys = []; - let 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); - - //find the correct key for addr - var privKey = private_keys.find(pk => verifyKey(addr, pk)); - if (!privKey) - return reject(`Private key missing for ${addr}`); - //find redeemScript (if any) - const rs = _redeemScript(addr, privKey); - rs === false ? wif_keys.unshift(privKey) : wif_keys.push(privKey); //sorting private-keys (wif) - //reset the script for re-signing - var script; - if (!rs || !rs.length) { - //legacy script (derive from address) - let s = coinjs.script(); - s.writeOp(118); //OP_DUP - s.writeOp(169); //OP_HASH160 - s.writeBytes(addr_decode.bytes); - s.writeOp(136); //OP_EQUALVERIFY - s.writeOp(172); //OP_CHECKSIG - script = Crypto.util.bytesToHex(s.buffer); - } else if (((rs.match(/^00/) && rs.length == 44)) || (rs.length == 40 && rs.match(/^[a-f0-9]+$/gi))) { - //redeemScript for segwit/bech32 - 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); - if (addr_decode == "bech32") { witness_position = witness_position + 1; } //bech32 has witness - } else if (addr_decode.type === 'multisigBech32') { - var rs_array = []; - rs_array = btcOperator.extractLastHexStrings(tx.witness); - let redeemScript = rs_array[witness_position]; - witness_position = witness_position + 1; - - //redeemScript multisig (bech32) - 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 //redeemScript for multisig (segwit) - script = rs; - tx.ins[i].script = coinjs.script(script); - } - tx.witness = false; //remove all witness signatures - console.debug("Unsigned:", tx.serialize()); - //re-sign the transaction - new Set(wif_keys).forEach(key => tx.sign(key, 1 /*sighashtype*/)); //Sign the tx using private key WIF - if (btcOperator.checkSigned(tx)) { - resolve(tx.serialize()); - } else { - 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 => { + debugger; broadcastTx(result.transaction.serialize()) .then(txid => resolve(txid)) .catch(error => reject(error)); @@ -1157,9 +694,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 +715,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); @@ -1225,7 +760,7 @@ }) } - const deserializeTx = btcOperator.deserializeTx = function (tx) { + function deserializeTx(tx) { if (typeof tx === 'string' || Array.isArray(tx)) { try { tx = coinjs.transaction().deserialize(tx); @@ -1291,7 +826,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 +888,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 = 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)) });