From 006ff9f0a95bc268defcb30c91bf4588e59aacd7 Mon Sep 17 00:00:00 2001 From: sairajzero Date: Fri, 18 Nov 2022 17:49:03 +0530 Subject: [PATCH] btcOperator v1.0.13b: Fee from receiver - Added: fee_from_receiver option to create transaction. - change_address and fee_from_receiver are passed as options for createTx, createSignedTx, sendTx and createMultiSigTx - Fixed: getTx not rejecting properly when txid is invalid --- btcOperator.js | 74 +++++++++++++++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 28 deletions(-) diff --git a/btcOperator.js b/btcOperator.js index bb0b7ca..85ae761 100644 --- a/btcOperator.js +++ b/btcOperator.js @@ -1,4 +1,4 @@ -(function (EXPORTS) { //btcOperator v1.0.12 +(function (EXPORTS) { //btcOperator v1.0.13b /* BTC Crypto and API Operator */ const btcOperator = EXPORTS; @@ -307,8 +307,8 @@ parameters.receivers.forEach(id => !validateAddress(id) ? invalids.push(id) : null); if (invalids.length) throw "Invalid receivers:" + invalids; - if (parameters.change_addr && !validateAddress(parameters.change_addr)) - throw "Invalid change_address:" + parameters.change_addr; + if (parameters.change_address && !validateAddress(parameters.change_address)) + throw "Invalid change_address:" + parameters.change_address; //fee and amounts if ((typeof parameters.fee !== "number" || parameters.fee <= 0) && parameters.fee !== null) //fee = null (auto calc) throw "Invalid fee:" + parameters.fee; @@ -323,18 +323,32 @@ return parameters; } - function createTransaction(senders, redeemScripts, receivers, amounts, fee, change_addr) { + function createTransaction(senders, redeemScripts, receivers, amounts, fee, change_address, fee_from_receiver) { return new Promise((resolve, reject) => { 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_addr); - addInputs(tx, senders, redeemScripts, total_amount, fee, output_size).then(result => { - if (result.change_amount > 0) + 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) //add change amount if any tx.outs[tx.outs.length - 1].value = parseInt(result.change_amount * SATOSHI_IN_BTC); //values are in satoshi - else - tx.outs.pop(); //remove the change output if no change_amount + if (fee_from_receiver) { //deduce fee from receivers if fee_from_receiver + let fee_remaining = parseInt(result.fee * SATOSHI_IN_BTC); + for (let i = 0; i < tx.outs.length - 1 && fee_remaining > 0; i++) { + if (fee_remaining < tx.outs[i].value) { + tx.outs[i].value -= fee_remaining; + fee_remaining = 0; + } else { + fee_remaining -= tx.outs[i].value; + tx.outs[i].value = 0; + } + } + if (fee_remaining > 0) + return reject("Send amount is less than fee"); + + } + tx.outs = tx.outs.filter(o => o.value !== 0); //remove all output with value 0 result.output_size = output_size; - result.output_amount = total_amount; + result.output_amount = total_amount - (fee_from_receiver ? result.fee : 0); result.total_size = BASE_TX_SIZE + output_size + result.input_size; result.transaction = tx; resolve(result); @@ -342,10 +356,10 @@ }) } - function addInputs(tx, senders, redeemScripts, total_amount, fee, output_size) { + function addInputs(tx, senders, redeemScripts, total_amount, fee, output_size, fee_from_receiver) { return new Promise((resolve, reject) => { if (fee !== null) { - addUTXOs(tx, senders, redeemScripts, total_amount + fee, false).then(result => { + addUTXOs(tx, senders, redeemScripts, fee_from_receiver ? total_amount : total_amount + fee, false).then(result => { result.fee = fee; resolve(result); }).catch(error => reject(error)) @@ -353,7 +367,10 @@ get_fee_rate().then(fee_rate => { let net_fee = BASE_TX_SIZE * fee_rate; net_fee += (output_size * fee_rate); - addUTXOs(tx, senders, redeemScripts, total_amount + net_fee, fee_rate).then(result => { + (fee_from_receiver ? + addUTXOs(tx, senders, redeemScripts, total_amount, false) : + addUTXOs(tx, senders, redeemScripts, total_amount + net_fee, fee_rate) + ).then(result => { result.fee = parseFloat((net_fee + (result.input_size * fee_rate)).toFixed(8)); result.fee_rate = fee_rate; resolve(result); @@ -416,14 +433,14 @@ }) } - function addOutputs(tx, receivers, amounts, change_addr) { + 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]); } - tx.addoutput(change_addr, 0); - size += _sizePerOutput(change_addr); + tx.addoutput(change_address, 0); + size += _sizePerOutput(change_address); return size; } @@ -464,9 +481,9 @@ } */ - btcOperator.sendTx = function (senders, privkeys, receivers, amounts, fee, change_addr = null) { + btcOperator.sendTx = function (senders, privkeys, receivers, amounts, fee = null, options = {}) { return new Promise((resolve, reject) => { - createSignedTx(senders, privkeys, receivers, amounts, fee, change_addr).then(result => { + createSignedTx(senders, privkeys, receivers, amounts, fee, options).then(result => { debugger; broadcastTx(result.transaction.serialize()) .then(txid => resolve(txid)) @@ -475,7 +492,7 @@ }) } - const createSignedTx = btcOperator.createSignedTx = function (senders, privkeys, receivers, amounts, fee = null, change_addr = null) { + const createSignedTx = btcOperator.createSignedTx = function (senders, privkeys, receivers, amounts, fee = null, options = {}) { return new Promise((resolve, reject) => { try { ({ @@ -489,7 +506,7 @@ receivers, amounts, fee, - change_addr + change_address: options.change_address })); } catch (e) { return reject(e) @@ -504,7 +521,7 @@ if (redeemScripts.includes(null)) //TODO: segwit return reject("Unable to get redeem-script"); //create transaction - createTransaction(senders, redeemScripts, receivers, amounts, fee, change_addr || senders[0]).then(result => { + createTransaction(senders, redeemScripts, receivers, amounts, fee, options.change_address || senders[0], options.fee_from_receiver).then(result => { let tx = result.transaction; console.debug("Unsigned:", tx.serialize()); new Set(wif_keys).forEach(key => console.debug("Signing key:", key, tx.sign(key, 1 /*sighashtype*/))); //Sign the tx using private key WIF @@ -514,7 +531,7 @@ }) } - btcOperator.createTx = function (senders, receivers, amounts, fee = null, change_addr = null) { + btcOperator.createTx = function (senders, receivers, amounts, fee = null, options = {}) { return new Promise((resolve, reject) => { try { ({ @@ -526,7 +543,7 @@ receivers, amounts, fee, - change_addr + change_address: options.change_address })); } catch (e) { return reject(e) @@ -535,7 +552,7 @@ if (redeemScripts.includes(null)) //TODO: segwit return reject("Unable to get redeem-script"); //create transaction - createTransaction(senders, redeemScripts, receivers, amounts, fee, change_addr || senders[0]).then(result => { + createTransaction(senders, redeemScripts, receivers, amounts, fee, options.change_address || senders[0], options.fee_from_receiver).then(result => { result.tx_hex = result.transaction.serialize(); delete result.transaction; resolve(result); @@ -543,7 +560,7 @@ }) } - btcOperator.createMultiSigTx = function (sender, redeemScript, receivers, amounts, fee = null) { + btcOperator.createMultiSigTx = function (sender, redeemScript, receivers, amounts, fee = null, options = {}) { return new Promise((resolve, reject) => { //validate tx parameters if (validateAddress(sender) !== "multisig") @@ -561,13 +578,14 @@ } = validateTxParameters({ receivers, amounts, - fee + fee, + change_address: options.change_address })); } catch (e) { return reject(e) } //create transaction - createTransaction([sender], [redeemScript], receivers, amounts, fee, sender).then(result => { + createTransaction([sender], [redeemScript], receivers, amounts, fee, options.change_address || sender, options.fee_from_receiver).then(result => { result.tx_hex = result.transaction.serialize(); delete result.transaction; resolve(result); @@ -696,7 +714,7 @@ } btcOperator.getTx = txid => new Promise((resolve, reject) => { - fetch_api(`tx/BTC/${txid}`) + fetch_api(`get_tx/BTC/${txid}`) .then(result => resolve(result.data)) .catch(error => reject(error)) });