@@ -1585,6 +1629,17 @@
}
}
>
+
diff --git a/web/cashtab/src/hooks/useBCH.js b/web/cashtab/src/hooks/useBCH.js
--- a/web/cashtab/src/hooks/useBCH.js
+++ b/web/cashtab/src/hooks/useBCH.js
@@ -1024,6 +1024,174 @@
}
};
+ const sendMultiBch = async (
+ BCH,
+ wallet,
+ utxos,
+ destinationAddressAndValueArray,
+ feeInSatsPerByte,
+ ) => {
+ try {
+ if (!destinationAddressAndValueArray) {
+ return null;
+ }
+
+ const inputUtxos = [];
+ let transactionBuilder;
+
+ // instance of transaction builder
+ if (process.env.REACT_APP_NETWORK === `mainnet`)
+ transactionBuilder = new BCH.TransactionBuilder();
+ else transactionBuilder = new BCH.TransactionBuilder('testnet');
+
+ let originalAmount = new BigNumber(0);
+ let txFee = 0;
+ let totalSatoshisToSend = 0;
+
+ // parse UTXOs
+ for (let i = 0; i < utxos.length; i++) {
+ const utxo = utxos[i];
+ originalAmount = originalAmount.plus(utxo.value);
+ const vout = utxo.vout;
+ const txid = utxo.txid;
+ // add input with txid and index of vout
+ transactionBuilder.addInput(txid, vout);
+
+ inputUtxos.push(utxo);
+ txFee += calcFee(BCH, inputUtxos, 2, feeInSatsPerByte);
+ }
+
+ var arrayLength = destinationAddressAndValueArray.length;
+ for (var i = 0; i < arrayLength; i++) {
+ // set the values from before and after the comma from each TextArea input line
+ let destinationAddress =
+ destinationAddressAndValueArray[i].split(',')[0];
+ let outputValue =
+ destinationAddressAndValueArray[i].split(',')[1];
+
+ const value = new BigNumber(outputValue);
+
+ // If user is attempting to send less than minimum accepted by the backend
+ if (
+ value.lt(
+ new BigNumber(
+ fromSmallestDenomination(
+ currency.dustSats,
+ ).toString(),
+ ),
+ )
+ ) {
+ // Throw the same error given by the backend attempting to broadcast such a tx
+ throw new Error('dust');
+ }
+
+ // logic to check for smallest denomination
+ const satoshisToSend = toSmallestDenomination(value);
+
+ // track the total send value throughout this loop
+ totalSatoshisToSend =
+ Number(totalSatoshisToSend) + Number(satoshisToSend);
+
+ // Throw validation error if toSmallestDenomination returns false
+ if (!satoshisToSend) {
+ const error = new Error(
+ `Invalid decimal places for send amount`,
+ );
+ throw error;
+ }
+
+ // add output w/ address and amount to send
+ transactionBuilder.addOutput(
+ BCH.Address.toCashAddress(destinationAddress),
+ parseInt(toSmallestDenomination(value)),
+ );
+ }
+
+ // Get change address from sending utxos
+ // fall back to what is stored in wallet
+ let REMAINDER_ADDR;
+
+ // Validate address
+ let isValidChangeAddress;
+ try {
+ REMAINDER_ADDR = inputUtxos[0].address;
+ isValidChangeAddress =
+ BCH.Address.isCashAddress(REMAINDER_ADDR);
+ } catch (err) {
+ isValidChangeAddress = false;
+ }
+ if (!isValidChangeAddress) {
+ REMAINDER_ADDR = wallet.Path1899.cashAddress;
+ }
+
+ // amount to send back to the remainder address.
+ const remainder = originalAmount
+ .minus(totalSatoshisToSend)
+ .minus(txFee);
+
+ if (remainder.lt(0)) {
+ const error = new Error(`Insufficient funds`);
+ error.code = SEND_BCH_ERRORS.INSUFFICIENT_FUNDS;
+ throw error;
+ }
+
+ if (remainder.gte(new BigNumber(currency.dustSats))) {
+ transactionBuilder.addOutput(
+ REMAINDER_ADDR,
+ parseInt(remainder),
+ );
+ }
+
+ // Sign the transactions with the HD node.
+ for (let i = 0; i < inputUtxos.length; i++) {
+ const utxo = inputUtxos[i];
+ transactionBuilder.sign(
+ i,
+ BCH.ECPair.fromWIF(utxo.wif),
+ undefined,
+ transactionBuilder.hashTypes.SIGHASH_ALL,
+ utxo.value,
+ );
+ }
+
+ // build tx
+ const tx = transactionBuilder.build();
+ // output rawhex
+ const hex = tx.toHex();
+
+ // Broadcast transaction to the network
+ const txidStr = await BCH.RawTransactions.sendRawTransaction([hex]);
+
+ if (txidStr && txidStr[0]) {
+ console.log(`${currency.ticker} txid`, txidStr[0]);
+ }
+ let link;
+ if (process.env.REACT_APP_NETWORK === `mainnet`) {
+ link = `${currency.blockExplorerUrl}/tx/${txidStr}`;
+ } else {
+ link = `${currency.blockExplorerUrlTestnet}/tx/${txidStr}`;
+ }
+ //console.log(`link`, link);
+
+ return link;
+ } catch (err) {
+ if (err.error === 'insufficient priority (code 66)') {
+ err.code = SEND_BCH_ERRORS.INSUFFICIENT_PRIORITY;
+ } else if (err.error === 'txn-mempool-conflict (code 18)') {
+ err.code = SEND_BCH_ERRORS.DOUBLE_SPENDING;
+ } else if (err.error === 'Network Error') {
+ err.code = SEND_BCH_ERRORS.NETWORK_ERROR;
+ } else if (
+ err.error ===
+ 'too-long-mempool-chain, too many unconfirmed ancestors [limit: 25] (code 64)'
+ ) {
+ err.code = SEND_BCH_ERRORS.MAX_UNCONFIRMED_TXS;
+ }
+ console.log(`error: `, err);
+ throw err;
+ }
+ };
+
const getBCH = (apiIndex = 0) => {
let ConstructedSlpWallet;
@@ -1048,6 +1216,7 @@
getRestUrl,
signPkMessage,
sendBch,
+ sendMultiBch,
sendToken,
createToken,
getTokenStats,