add support for multiple payments in a tx

This commit is contained in:
Craig Raw 2020-10-19 11:06:30 +02:00
parent 661e88447f
commit 8b07336d71
4 changed files with 92 additions and 32 deletions

View file

@ -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());
}

View file

@ -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;
}
}

View file

@ -434,8 +434,9 @@ public class Wallet {
return getFee(changeOutput, feeRate, longTermFeeRate);
}
public WalletTransaction createWalletTransaction(List<UtxoSelector> utxoSelectors, List<UtxoFilter> 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<UtxoSelector> utxoSelectors, List<UtxoFilter> utxoFilters, List<Payment> 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<BlockTransactionHashIndex, WalletNode> 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<Payment> 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);
}
}

View file

@ -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<UtxoSelector> utxoSelectors;
private final Map<BlockTransactionHashIndex, WalletNode> selectedUtxos;
private final Address recipientAddress;
private final long recipientAmount;
private final List<Payment> payments;
private final WalletNode changeNode;
private final long changeAmount;
private final long fee;
public WalletTransaction(Wallet wallet, Transaction transaction, List<UtxoSelector> utxoSelectors, Map<BlockTransactionHashIndex, WalletNode> selectedUtxos, Address recipientAddress, long recipientAmount, long fee) {
this(wallet, transaction, utxoSelectors, selectedUtxos, recipientAddress, recipientAmount, null, 0L, fee);
public WalletTransaction(Wallet wallet, Transaction transaction, List<UtxoSelector> utxoSelectors, Map<BlockTransactionHashIndex, WalletNode> selectedUtxos, List<Payment> payments, long fee) {
this(wallet, transaction, utxoSelectors, selectedUtxos, payments, null, 0L, fee);
}
public WalletTransaction(Wallet wallet, Transaction transaction, List<UtxoSelector> utxoSelectors, Map<BlockTransactionHashIndex, WalletNode> selectedUtxos, Address recipientAddress, long recipientAmount, WalletNode changeNode, long changeAmount, long fee) {
public WalletTransaction(Wallet wallet, Transaction transaction, List<UtxoSelector> utxoSelectors, Map<BlockTransactionHashIndex, WalletNode> selectedUtxos, List<Payment> 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<Payment> 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<WalletNode> getConsolidationSendNodes() {
List<WalletNode> 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;
}
}