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()) { for(TransactionOutput txOutput : transaction.getOutputs()) {
try { try {
Address address = txOutput.getScript().getToAddresses()[0]; Address address = txOutput.getScript().getToAddresses()[0];
if(address.equals(walletTransaction.getRecipientAddress())) { if(walletTransaction.getPayments().stream().anyMatch(payment -> payment.getAddress().equals(address))) {
outputNodes.add(wallet.getWalletAddresses().getOrDefault(walletTransaction.getRecipientAddress(), null)); outputNodes.add(wallet.getWalletAddresses().getOrDefault(address, null));
} else if(address.equals(wallet.getAddress(walletTransaction.getChangeNode()))) { } else if(address.equals(wallet.getAddress(walletTransaction.getChangeNode()))) {
outputNodes.add(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); 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 { 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 valueRequiredAmt = recipientAmount; long totalPaymentAmount = payments.stream().map(Payment::getAmount).mapToLong(v -> v).sum();
long valueRequiredAmt = totalPaymentAmount;
while(true) { while(true) {
Map<BlockTransactionHashIndex, WalletNode> selectedUtxos = selectInputs(utxoSelectors, utxoFilters, valueRequiredAmt, feeRate, longTermFeeRate, groupByAddress, includeMempoolChange); 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); txInput.setSequenceNumber(TransactionInput.SEQUENCE_RBF_ENABLED);
} }
//Add recipient output //Add recipient outputs
transaction.addOutput(recipientAmount, recipientAddress); for(Payment payment : payments) {
transaction.addOutput(payment.getAmount(), payment.getAddress());
}
int noChangeVSize = transaction.getVirtualSize(); int noChangeVSize = transaction.getVirtualSize();
long noChangeFeeRequiredAmt = (fee == null ? (long)(feeRate * noChangeVSize) : fee); 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 //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; long maxSendAmt = totalSelectedAmt - noChangeFeeRequiredAmt;
if(sendAll && recipientAmount != maxSendAmt) { Optional<Payment> optMaxPayment = payments.stream().filter(payment -> payment.isSendMax()).findFirst();
recipientAmount = maxSendAmt; if(optMaxPayment.isPresent()) {
continue; 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 //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 insufficient fee, increase value required from inputs to include the fee and try again
if(differenceAmt < noChangeFeeRequiredAmt) { if(differenceAmt < noChangeFeeRequiredAmt) {
@ -503,10 +513,10 @@ public class Wallet {
//Add change output //Add change output
transaction.addOutput(changeAmt, getOutputScript(changeNode)); 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.protocol.Transaction;
import com.sparrowwallet.drongo.psbt.PSBT; import com.sparrowwallet.drongo.psbt.PSBT;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -16,23 +17,21 @@ public class WalletTransaction {
private final Transaction transaction; private final Transaction transaction;
private final List<UtxoSelector> utxoSelectors; private final List<UtxoSelector> utxoSelectors;
private final Map<BlockTransactionHashIndex, WalletNode> selectedUtxos; private final Map<BlockTransactionHashIndex, WalletNode> selectedUtxos;
private final Address recipientAddress; private final List<Payment> payments;
private final long recipientAmount;
private final WalletNode changeNode; private final WalletNode changeNode;
private final long changeAmount; private final long changeAmount;
private final long fee; private final long fee;
public WalletTransaction(Wallet wallet, Transaction transaction, List<UtxoSelector> utxoSelectors, Map<BlockTransactionHashIndex, WalletNode> selectedUtxos, Address recipientAddress, long recipientAmount, long fee) { public WalletTransaction(Wallet wallet, Transaction transaction, List<UtxoSelector> utxoSelectors, Map<BlockTransactionHashIndex, WalletNode> selectedUtxos, List<Payment> payments, long fee) {
this(wallet, transaction, utxoSelectors, selectedUtxos, recipientAddress, recipientAmount, null, 0L, 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.wallet = wallet;
this.transaction = transaction; this.transaction = transaction;
this.utxoSelectors = utxoSelectors; this.utxoSelectors = utxoSelectors;
this.selectedUtxos = selectedUtxos; this.selectedUtxos = selectedUtxos;
this.recipientAddress = recipientAddress; this.payments = payments;
this.recipientAmount = recipientAmount;
this.changeNode = changeNode; this.changeNode = changeNode;
this.changeAmount = changeAmount; this.changeAmount = changeAmount;
this.fee = fee; this.fee = fee;
@ -58,12 +57,8 @@ public class WalletTransaction {
return selectedUtxos; return selectedUtxos;
} }
public Address getRecipientAddress() { public List<Payment> getPayments() {
return recipientAddress; return payments;
}
public long getRecipientAmount() {
return recipientAmount;
} }
public WalletNode getChangeNode() { public WalletNode getChangeNode() {
@ -102,19 +97,25 @@ public class WalletTransaction {
return !utxoSelectors.isEmpty() && utxoSelectors.get(0) instanceof PresetUtxoSelector; return !utxoSelectors.isEmpty() && utxoSelectors.get(0) instanceof PresetUtxoSelector;
} }
public boolean isConsolidationSend() { public boolean isConsolidationSend(Payment payment) {
if(getRecipientAddress() != null && getWallet() != null) { if(payment.getAddress() != null && getWallet() != null) {
return getWallet().isWalletOutputScript(getRecipientAddress().getOutputScript()); return getWallet().isWalletOutputScript(payment.getAddress().getOutputScript());
} }
return false; return false;
} }
public WalletNode getConsolidationSendNode() { public List<WalletNode> getConsolidationSendNodes() {
if(getRecipientAddress() != null && getWallet() != null) { List<WalletNode> walletNodes = new ArrayList<>();
return getWallet().getWalletOutputScripts().get(getRecipientAddress().getOutputScript()); 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;
} }
} }