diff --git a/src/main/java/com/sparrowwallet/drongo/psbt/PSBT.java b/src/main/java/com/sparrowwallet/drongo/psbt/PSBT.java index d943699..15e7a5a 100644 --- a/src/main/java/com/sparrowwallet/drongo/psbt/PSBT.java +++ b/src/main/java/com/sparrowwallet/drongo/psbt/PSBT.java @@ -120,8 +120,8 @@ public class PSBT { for(TransactionOutput txOutput : transaction.getOutputs()) { try { Address address = txOutput.getScript().getToAddresses()[0]; - if(address.equals(walletTransaction.getRecipientAddress())) { - outputNodes.add(wallet.getWalletAddresses().getOrDefault(walletTransaction.getRecipientAddress(), null)); + if(walletTransaction.getPayments().stream().anyMatch(payment -> payment.getAddress().equals(address))) { + outputNodes.add(wallet.getWalletAddresses().getOrDefault(address, null)); } else if(address.equals(wallet.getAddress(walletTransaction.getChangeNode()))) { outputNodes.add(walletTransaction.getChangeNode()); } diff --git a/src/main/java/com/sparrowwallet/drongo/wallet/Payment.java b/src/main/java/com/sparrowwallet/drongo/wallet/Payment.java new file mode 100644 index 0000000..ce3998b --- /dev/null +++ b/src/main/java/com/sparrowwallet/drongo/wallet/Payment.java @@ -0,0 +1,49 @@ +package com.sparrowwallet.drongo.wallet; + +import com.sparrowwallet.drongo.address.Address; + +public class Payment { + private Address address; + private String label; + private long amount; + private boolean sendMax; + + public Payment(Address address, String label, long amount, boolean sendMax) { + this.address = address; + this.label = label; + this.amount = amount; + this.sendMax = sendMax; + } + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public long getAmount() { + return amount; + } + + public void setAmount(long amount) { + this.amount = amount; + } + + public boolean isSendMax() { + return sendMax; + } + + public void setSendMax(boolean sendMax) { + this.sendMax = sendMax; + } +} diff --git a/src/main/java/com/sparrowwallet/drongo/wallet/Wallet.java b/src/main/java/com/sparrowwallet/drongo/wallet/Wallet.java index 185d0f0..fa0ada1 100644 --- a/src/main/java/com/sparrowwallet/drongo/wallet/Wallet.java +++ b/src/main/java/com/sparrowwallet/drongo/wallet/Wallet.java @@ -434,8 +434,9 @@ public class Wallet { return getFee(changeOutput, feeRate, longTermFeeRate); } - public WalletTransaction createWalletTransaction(List utxoSelectors, List utxoFilters, Address recipientAddress, long recipientAmount, double feeRate, double longTermFeeRate, Long fee, Integer currentBlockHeight, boolean sendAll, boolean groupByAddress, boolean includeMempoolChange) throws InsufficientFundsException { - long valueRequiredAmt = recipientAmount; + public WalletTransaction createWalletTransaction(List utxoSelectors, List utxoFilters, List payments, double feeRate, double longTermFeeRate, Long fee, Integer currentBlockHeight, boolean groupByAddress, boolean includeMempoolChange) throws InsufficientFundsException { + long totalPaymentAmount = payments.stream().map(Payment::getAmount).mapToLong(v -> v).sum(); + long valueRequiredAmt = totalPaymentAmount; while(true) { Map selectedUtxos = selectInputs(utxoSelectors, utxoFilters, valueRequiredAmt, feeRate, longTermFeeRate, groupByAddress, includeMempoolChange); @@ -457,8 +458,11 @@ public class Wallet { txInput.setSequenceNumber(TransactionInput.SEQUENCE_RBF_ENABLED); } - //Add recipient output - transaction.addOutput(recipientAmount, recipientAddress); + //Add recipient outputs + for(Payment payment : payments) { + transaction.addOutput(payment.getAmount(), payment.getAddress()); + } + int noChangeVSize = transaction.getVirtualSize(); long noChangeFeeRequiredAmt = (fee == null ? (long)(feeRate * noChangeVSize) : fee); @@ -467,13 +471,19 @@ public class Wallet { //If sending all selected utxos, set the recipient amount to equal to total of those utxos less the no change fee long maxSendAmt = totalSelectedAmt - noChangeFeeRequiredAmt; - if(sendAll && recipientAmount != maxSendAmt) { - recipientAmount = maxSendAmt; - continue; + Optional optMaxPayment = payments.stream().filter(payment -> payment.isSendMax()).findFirst(); + if(optMaxPayment.isPresent()) { + Payment maxPayment = optMaxPayment.get(); + maxSendAmt = maxSendAmt - payments.stream().filter(payment -> !maxPayment.equals(payment)).map(Payment::getAmount).mapToLong(v -> v).sum(); + if(maxPayment.getAmount() != maxSendAmt) { + maxPayment.setAmount(maxSendAmt); + totalPaymentAmount = payments.stream().map(Payment::getAmount).mapToLong(v -> v).sum(); + continue; + } } //Calculate what is left over from selected utxos after paying recipient - long differenceAmt = totalSelectedAmt - recipientAmount; + long differenceAmt = totalSelectedAmt - totalPaymentAmount; //If insufficient fee, increase value required from inputs to include the fee and try again if(differenceAmt < noChangeFeeRequiredAmt) { @@ -503,10 +513,10 @@ public class Wallet { //Add change output transaction.addOutput(changeAmt, getOutputScript(changeNode)); - return new WalletTransaction(this, transaction, utxoSelectors, selectedUtxos, recipientAddress, recipientAmount, changeNode, changeAmt, changeFeeRequiredAmt); + return new WalletTransaction(this, transaction, utxoSelectors, selectedUtxos, payments, changeNode, changeAmt, changeFeeRequiredAmt); } - return new WalletTransaction(this, transaction, utxoSelectors, selectedUtxos, recipientAddress, recipientAmount, differenceAmt); + return new WalletTransaction(this, transaction, utxoSelectors, selectedUtxos, payments, differenceAmt); } } diff --git a/src/main/java/com/sparrowwallet/drongo/wallet/WalletTransaction.java b/src/main/java/com/sparrowwallet/drongo/wallet/WalletTransaction.java index a9c4695..8c5bce1 100644 --- a/src/main/java/com/sparrowwallet/drongo/wallet/WalletTransaction.java +++ b/src/main/java/com/sparrowwallet/drongo/wallet/WalletTransaction.java @@ -4,6 +4,7 @@ import com.sparrowwallet.drongo.address.Address; import com.sparrowwallet.drongo.protocol.Transaction; import com.sparrowwallet.drongo.psbt.PSBT; +import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -16,23 +17,21 @@ public class WalletTransaction { private final Transaction transaction; private final List utxoSelectors; private final Map selectedUtxos; - private final Address recipientAddress; - private final long recipientAmount; + private final List payments; private final WalletNode changeNode; private final long changeAmount; private final long fee; - public WalletTransaction(Wallet wallet, Transaction transaction, List utxoSelectors, Map selectedUtxos, Address recipientAddress, long recipientAmount, long fee) { - this(wallet, transaction, utxoSelectors, selectedUtxos, recipientAddress, recipientAmount, null, 0L, fee); + public WalletTransaction(Wallet wallet, Transaction transaction, List utxoSelectors, Map selectedUtxos, List payments, long fee) { + this(wallet, transaction, utxoSelectors, selectedUtxos, payments, null, 0L, fee); } - public WalletTransaction(Wallet wallet, Transaction transaction, List utxoSelectors, Map selectedUtxos, Address recipientAddress, long recipientAmount, WalletNode changeNode, long changeAmount, long fee) { + public WalletTransaction(Wallet wallet, Transaction transaction, List utxoSelectors, Map selectedUtxos, List payments, WalletNode changeNode, long changeAmount, long fee) { this.wallet = wallet; this.transaction = transaction; this.utxoSelectors = utxoSelectors; this.selectedUtxos = selectedUtxos; - this.recipientAddress = recipientAddress; - this.recipientAmount = recipientAmount; + this.payments = payments; this.changeNode = changeNode; this.changeAmount = changeAmount; this.fee = fee; @@ -58,12 +57,8 @@ public class WalletTransaction { return selectedUtxos; } - public Address getRecipientAddress() { - return recipientAddress; - } - - public long getRecipientAmount() { - return recipientAmount; + public List getPayments() { + return payments; } public WalletNode getChangeNode() { @@ -102,19 +97,25 @@ public class WalletTransaction { return !utxoSelectors.isEmpty() && utxoSelectors.get(0) instanceof PresetUtxoSelector; } - public boolean isConsolidationSend() { - if(getRecipientAddress() != null && getWallet() != null) { - return getWallet().isWalletOutputScript(getRecipientAddress().getOutputScript()); + public boolean isConsolidationSend(Payment payment) { + if(payment.getAddress() != null && getWallet() != null) { + return getWallet().isWalletOutputScript(payment.getAddress().getOutputScript()); } return false; } - public WalletNode getConsolidationSendNode() { - if(getRecipientAddress() != null && getWallet() != null) { - return getWallet().getWalletOutputScripts().get(getRecipientAddress().getOutputScript()); + public List getConsolidationSendNodes() { + List walletNodes = new ArrayList<>(); + for(Payment payment : payments) { + if(payment.getAddress() != null && getWallet() != null) { + WalletNode walletNode = getWallet().getWalletOutputScripts().get(payment.getAddress().getOutputScript()); + if(walletNode != null) { + walletNodes.add(walletNode); + } + } } - return null; + return walletNodes; } }