mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-12-26 18:16:45 +00:00
add support for multiple payments in a tx
This commit is contained in:
parent
661e88447f
commit
8b07336d71
4 changed files with 92 additions and 32 deletions
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
49
src/main/java/com/sparrowwallet/drongo/wallet/Payment.java
Normal file
49
src/main/java/com/sparrowwallet/drongo/wallet/Payment.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue