mirror of
https://github.com/bitcoinj/bitcoinj.git
synced 2025-03-10 17:26:28 +01:00
WalletTool: Implement two coin selection options for sending, --select-addr and --select-output.
This commit is contained in:
parent
d3505b8949
commit
484275678d
1 changed files with 78 additions and 6 deletions
|
@ -17,6 +17,8 @@
|
||||||
|
|
||||||
package org.bitcoinj.tools;
|
package org.bitcoinj.tools;
|
||||||
|
|
||||||
|
import org.bitcoinj.core.Sha256Hash;
|
||||||
|
import org.bitcoinj.core.TransactionOutput;
|
||||||
import org.bitcoinj.crypto.*;
|
import org.bitcoinj.crypto.*;
|
||||||
import org.bitcoinj.params.MainNetParams;
|
import org.bitcoinj.params.MainNetParams;
|
||||||
import org.bitcoinj.params.RegTestParams;
|
import org.bitcoinj.params.RegTestParams;
|
||||||
|
@ -31,6 +33,8 @@ import org.bitcoinj.store.*;
|
||||||
import org.bitcoinj.uri.BitcoinURI;
|
import org.bitcoinj.uri.BitcoinURI;
|
||||||
import org.bitcoinj.uri.BitcoinURIParseException;
|
import org.bitcoinj.uri.BitcoinURIParseException;
|
||||||
import org.bitcoinj.utils.BriefLogFormatter;
|
import org.bitcoinj.utils.BriefLogFormatter;
|
||||||
|
import org.bitcoinj.wallet.CoinSelection;
|
||||||
|
import org.bitcoinj.wallet.CoinSelector;
|
||||||
import org.bitcoinj.wallet.DeterministicKeyChain;
|
import org.bitcoinj.wallet.DeterministicKeyChain;
|
||||||
import org.bitcoinj.wallet.DeterministicSeed;
|
import org.bitcoinj.wallet.DeterministicSeed;
|
||||||
|
|
||||||
|
@ -95,6 +99,7 @@ import java.security.SecureRandom;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
|
@ -152,6 +157,7 @@ public class WalletTool implements Callable<Integer> {
|
||||||
" --payment-request=http://merchant.com/pay.php?123%n" +
|
" --payment-request=http://merchant.com/pay.php?123%n" +
|
||||||
" Other options include:%n" +
|
" Other options include:%n" +
|
||||||
" --fee-per-vkb or --fee-sat-per-vbyte sets the network fee, see below%n" +
|
" --fee-per-vkb or --fee-sat-per-vbyte sets the network fee, see below%n" +
|
||||||
|
" --select-addr or --select-output to select specific outputs%n" +
|
||||||
" --locktime=1234 sets the lock time to block 1234%n" +
|
" --locktime=1234 sets the lock time to block 1234%n" +
|
||||||
" --locktime=2013/01/01 sets the lock time to 1st Jan 2013%n" +
|
" --locktime=2013/01/01 sets the lock time to 1st Jan 2013%n" +
|
||||||
" --allow-unconfirmed will let you create spends of pending non-change outputs.%n" +
|
" --allow-unconfirmed will let you create spends of pending non-change outputs.%n" +
|
||||||
|
@ -206,6 +212,10 @@ public class WalletTool implements Callable<Integer> {
|
||||||
private String peersStr;
|
private String peersStr;
|
||||||
@CommandLine.Option(names = "--xpubkeys", description = "Specifies external public keys.")
|
@CommandLine.Option(names = "--xpubkeys", description = "Specifies external public keys.")
|
||||||
private String xpubKeysStr;
|
private String xpubKeysStr;
|
||||||
|
@CommandLine.Option(names = "--select-addr", description = "When sending, only pick coins from this address.")
|
||||||
|
private String selectAddrStr;
|
||||||
|
@CommandLine.Option(names = "--select-output", description = "When sending, only pick coins from this output.")
|
||||||
|
private String selectOutputStr;
|
||||||
@CommandLine.Option(names = "--output", description = "Creates an output with the specified amount, separated by a colon. The special amount ALL is used to use the entire balance.")
|
@CommandLine.Option(names = "--output", description = "Creates an output with the specified amount, separated by a colon. The special amount ALL is used to use the entire balance.")
|
||||||
private List<String> outputsStr;
|
private List<String> outputsStr;
|
||||||
@CommandLine.Option(names = "--fee-per-vkb", description = "Sets the network fee in Bitcoin per kilobyte when sending, e.g. --fee-per-vkb=0.0005")
|
@CommandLine.Option(names = "--fee-per-vkb", description = "Sets the network fee in Bitcoin per kilobyte when sending, e.g. --fee-per-vkb=0.0005")
|
||||||
|
@ -454,7 +464,62 @@ public class WalletTool implements Callable<Integer> {
|
||||||
feePerVkb = parseCoin(feePerVkbStr);
|
feePerVkb = parseCoin(feePerVkbStr);
|
||||||
if (feeSatPerVbyteStr != null)
|
if (feeSatPerVbyteStr != null)
|
||||||
feePerVkb = Coin.valueOf(Long.parseLong(feeSatPerVbyteStr) * 1000);
|
feePerVkb = Coin.valueOf(Long.parseLong(feeSatPerVbyteStr) * 1000);
|
||||||
send(outputsStr, feePerVkb, lockTimeStr, allowUnconfirmed);
|
if (selectAddrStr != null && selectOutputStr != null) {
|
||||||
|
System.err.println("--select-addr and --select-output cannot be used together.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
CoinSelector coinSelector = null;
|
||||||
|
if (selectAddrStr != null) {
|
||||||
|
Address selectAddr = null;
|
||||||
|
try {
|
||||||
|
selectAddr = Address.fromString(params, selectAddrStr);
|
||||||
|
} catch (AddressFormatException x) {
|
||||||
|
System.err.println("Could not parse given address, or wrong network: " + selectAddrStr);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
final Address validSelectAddr = selectAddr;
|
||||||
|
coinSelector = new CoinSelector() {
|
||||||
|
@Override
|
||||||
|
public CoinSelection select(Coin target, List<TransactionOutput> candidates) {
|
||||||
|
Coin valueGathered = Coin.ZERO;
|
||||||
|
List<TransactionOutput> gathered = new LinkedList<TransactionOutput>();
|
||||||
|
for (TransactionOutput candidate : candidates) {
|
||||||
|
try {
|
||||||
|
Address candidateAddr = candidate.getScriptPubKey().getToAddress(params);
|
||||||
|
if (validSelectAddr.equals(candidateAddr)) {
|
||||||
|
gathered.add(candidate);
|
||||||
|
valueGathered = valueGathered.add(candidate.getValue());
|
||||||
|
}
|
||||||
|
} catch (ScriptException x) {
|
||||||
|
// swallow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new CoinSelection(valueGathered, gathered);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (selectOutputStr != null) {
|
||||||
|
String[] parts = selectOutputStr.split(":", 2);
|
||||||
|
Sha256Hash selectTransactionHash = Sha256Hash.wrap(parts[0]);
|
||||||
|
int selectIndex = Integer.parseInt(parts[1]);
|
||||||
|
coinSelector = new CoinSelector() {
|
||||||
|
@Override
|
||||||
|
public CoinSelection select(Coin target, List<TransactionOutput> candidates) {
|
||||||
|
Coin valueGathered = Coin.ZERO;
|
||||||
|
List<TransactionOutput> gathered = new LinkedList<TransactionOutput>();
|
||||||
|
for (TransactionOutput candidate : candidates) {
|
||||||
|
int candicateIndex = candidate.getIndex();
|
||||||
|
final Sha256Hash candidateTransactionHash = candidate.getParentTransactionHash();
|
||||||
|
if (selectIndex == candicateIndex && selectTransactionHash.equals(candidateTransactionHash)) {
|
||||||
|
gathered.add(candidate);
|
||||||
|
valueGathered = valueGathered.add(candidate.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new CoinSelection(valueGathered, gathered);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
send(coinSelector, outputsStr, feePerVkb, lockTimeStr, allowUnconfirmed);
|
||||||
} else if (paymentRequestLocationStr != null) {
|
} else if (paymentRequestLocationStr != null) {
|
||||||
sendPaymentRequest(paymentRequestLocationStr, !noPki);
|
sendPaymentRequest(paymentRequestLocationStr, !noPki);
|
||||||
} else {
|
} else {
|
||||||
|
@ -622,14 +687,17 @@ public class WalletTool implements Callable<Integer> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void send(List<String> outputs, Coin feePerVkb, String lockTimeStr, boolean allowUnconfirmed) throws VerificationException {
|
private void send(CoinSelector coinSelector, List<String> outputs, Coin feePerVkb, String lockTimeStr,
|
||||||
|
boolean allowUnconfirmed)
|
||||||
|
throws VerificationException {
|
||||||
|
Coin balance = coinSelector != null ? wallet.getBalance(coinSelector) : wallet.getBalance(allowUnconfirmed ?
|
||||||
|
BalanceType.ESTIMATED : BalanceType.AVAILABLE);
|
||||||
// Convert the input strings to outputs.
|
// Convert the input strings to outputs.
|
||||||
Transaction t = new Transaction(params);
|
Transaction t = new Transaction(params);
|
||||||
for (String spec : outputs) {
|
for (String spec : outputs) {
|
||||||
try {
|
try {
|
||||||
OutputSpec outputSpec = new OutputSpec(spec);
|
OutputSpec outputSpec = new OutputSpec(spec);
|
||||||
Coin value = outputSpec.value != null ? outputSpec.value :
|
Coin value = outputSpec.value != null ? outputSpec.value : balance;
|
||||||
wallet.getBalance(allowUnconfirmed ? BalanceType.ESTIMATED : BalanceType.AVAILABLE);
|
|
||||||
if (outputSpec.isAddress())
|
if (outputSpec.isAddress())
|
||||||
t.addOutput(value, outputSpec.addr);
|
t.addOutput(value, outputSpec.addr);
|
||||||
else
|
else
|
||||||
|
@ -649,7 +717,11 @@ public class WalletTool implements Callable<Integer> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SendRequest req = SendRequest.forTx(t);
|
SendRequest req = SendRequest.forTx(t);
|
||||||
if (t.getOutputs().size() == 1 && t.getOutput(0).getValue().equals(wallet.getBalance())) {
|
if (coinSelector != null) {
|
||||||
|
req.coinSelector = coinSelector;
|
||||||
|
req.recipientsPayFees = true;
|
||||||
|
}
|
||||||
|
if (t.getOutputs().size() == 1 && t.getOutput(0).getValue().equals(balance)) {
|
||||||
log.info("Emptying out wallet, recipient may get less than what you expect");
|
log.info("Emptying out wallet, recipient may get less than what you expect");
|
||||||
req.emptyWallet = true;
|
req.emptyWallet = true;
|
||||||
}
|
}
|
||||||
|
@ -701,7 +773,7 @@ public class WalletTool implements Callable<Integer> {
|
||||||
} catch (BlockStoreException | ExecutionException | InterruptedException | KeyCrypterException e) {
|
} catch (BlockStoreException | ExecutionException | InterruptedException | KeyCrypterException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
} catch (InsufficientMoneyException e) {
|
} catch (InsufficientMoneyException e) {
|
||||||
System.err.println("Insufficient funds: have " + wallet.getBalance().toFriendlyString());
|
System.err.println("Insufficient funds: have " + balance.toFriendlyString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue