/*
* Copyright 2012 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.devcoin.tools;
import com.google.devcoin.core.*;
import com.google.devcoin.crypto.KeyCrypterException;
import com.google.devcoin.discovery.DnsDiscovery;
import com.google.devcoin.discovery.PeerDiscovery;
import com.google.devcoin.params.MainNetParams;
import com.google.devcoin.params.RegTestParams;
import com.google.devcoin.params.TestNet3Params;
import com.google.devcoin.store.*;
import com.google.devcoin.utils.BriefLogFormatter;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import joptsimple.util.DateConverter;
import org.devcoinj.wallet.Protos;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.util.encoders.Hex;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.ParseException;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.LogManager;
/**
* A command line tool for manipulating wallets and working with Bitcoin.<p>
*/
public class WalletTool {
private static final Logger log = LoggerFactory.getLogger(WalletTool.class);
private static final String HELP_TEXT =
"WalletTool: print and manipulate wallets\n\n" +
"Usage:\n" +
">>> GENERAL OPTIONS\n" +
" --debuglog Enables logging from the core library.\n" +
" --net=XXX Which network to connect to, defaults to PROD, can also be TEST or REGTEST.\n" +
" --mode=FULL/SPV Whether to do full verification of the chain or just light mode.\n" +
" --wallet=<file> Specifies what wallet file to load and save.\n" +
" --chain=<file> Specifies the name of the file that stores the block chain.\n" +
" --force Overrides any safety checks on the requested action.\n" +
" --date Provide a date in form YYYY/MM/DD to any action that requires one.\n" +
" --peers=1.2.3.4 Comma separated IP addresses/domain names for connections instead of peer discovery.\n" +
" --offline If specified when sending, don't try and connect, just write the tx to the wallet.\n" +
" --condition=... Allows you to specify a numeric condition for other commands. The format is\n" +
" one of the following operators = < > <= >= immediately followed by a number.\n" +
" For example --condition=\">5.10\" or --condition=\"<=1\"\n" +
" --password=... For an encrypted wallet, the password is provided here.\n" +
"\n>>> ACTIONS\n" +
" --action=DUMP Loads and prints the given wallet in textual form to stdout.\n" +
" --action=RAW_DUMP Prints the wallet as a raw protobuf with no parsing or sanity checking applied.\n" +
" --action=CREATE Makes a new wallet in the file specified by --wallet.\n" +
" Will complain and require --force if the wallet already exists.\n" +
" --action=ADD_KEY Adds a new key to the wallet, either specified or freshly generated.\n" +
" If --date is specified, that's the creation date.\n" +
" If --unixtime is specified, that's the creation time and it overrides --date.\n" +
" If --privkey is specified, use as a hex/base58 encoded private key.\n" +
" Don't specify --pubkey in that case, it will be derived automatically.\n" +
" If --pubkey is specified, use as a hex/base58 encoded non-compressed public key.\n" +
" --action=DELETE_KEY Removes the key specified by --pubkey or --addr from the wallet.\n" +
" --action=SYNC Sync the wallet with the latest block chain (download new transactions).\n" +
" If the chain file does not exist this will RESET the wallet.\n" +
" --action=RESET Deletes all transactions from the wallet, for if you want to replay the chain.\n" +
" --action=SEND Creates a transaction with the given --output from this wallet and broadcasts, eg:\n" +
" --output=1GthXFQMktFLWdh5EPNGqbq3H6WdG8zsWj:1.245\n" +
" You can repeat --output=address:value multiple times.\n" +
" If the output destination starts with 04 and is 65 or 33 bytes long it will be\n" +
" treated as a public key instead of an address and the send will use \n" +
" <key> CHECKSIG as the script.\n" +
" Other options include:\n" +
" --fee=0.01 sets the tx fee\n" +
" --locktime=1234 sets the lock time to block 1234\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" +
"\n>>> WAITING\n" +
"You can wait for the condition specified by the --waitfor flag to become true. Transactions and new\n" +
"blocks will be processed during this time. When the waited for condition is met, the tx/block hash\n" +
"will be printed. Waiting occurs after the --action is performed, if any is specified.\n\n" +
" --waitfor=EVER Never quit.\n" +
" --waitfor=WALLET_TX Any transaction that sends coins to or from the wallet.\n" +
" --waitfor=BLOCK A new block that builds on the best chain.\n" +
" --waitfor=BALANCE Waits until the wallets balance meets the --condition.\n";
private static OptionSpec<String> walletFileName;
private static OptionSpec<ActionEnum> actionFlag;
private static OptionSpec<NetworkEnum> netFlag;
private static OptionSpec<Date> dateFlag;
private static OptionSpec<Integer> unixtimeFlag;
private static OptionSpec<WaitForEnum> waitForFlag;
private static OptionSpec<ValidationMode> modeFlag;
private static OptionSpec<String> conditionFlag;
private static NetworkParameters params;
private static File walletFile;
private static OptionSet options;
private static java.util.logging.Logger logger;
private static BlockStore store;
private static AbstractBlockChain chain;
private static PeerGroup peers;
private static Wallet wallet;
private static File chainFileName;
private static PeerDiscovery discovery;
private static ValidationMode mode;
private static String password;
public static class Condition {
public enum Type {
// Less than, greater than, less than or equal, greater than or equal.
EQUAL, LT, GT, LTE, GTE
}
Type type;
String value;
public Condition(String from) {
if (from.length() < 2) throw new RuntimeException("Condition string too short: " + from);
if (from.startsWith("<=")) type = Type.LTE;
else if (from.startsWith(">=")) type = Type.GTE;
else if (from.startsWith("<")) type = Type.LT;
else if (from.startsWith("=")) type = Type.EQUAL;
else if (from.startsWith(">")) type = Type.GT;
else throw new RuntimeException("Unknown operator in condition: " + from);
String s;
switch (type) {
case LT:
case GT:
case EQUAL:
s = from.substring(1);
break;
case LTE:
case GTE:
s = from.substring(2);
break;
default:
throw new RuntimeException("Unreachable");
}
value = s;
}
public boolean matchBitcoins(BigInteger comparison) {
try {
BigInteger units = Utils.toNanoCoins(value);
switch (type) {
case LT: return comparison.compareTo(units) < 0;
case GT: return comparison.compareTo(units) > 0;
case EQUAL: return comparison.compareTo(units) == 0;
case LTE: return comparison.compareTo(units) <= 0;
case GTE: return comparison.compareTo(units) >= 0;
default:
throw new RuntimeException("Unreachable");
}
} catch (NumberFormatException e) {
System.err.println("Could not parse value from condition string: " + value);
System.exit(1);
return false;
}
}
}
private static Condition condition;
public enum ActionEnum {
NONE,
DUMP,
RAW_DUMP,
CREATE,
ADD_KEY,
DELETE_KEY,
SYNC,
RESET,
SEND
};
public enum WaitForEnum {
EVER,
WALLET_TX,
BLOCK,
BALANCE
};
public enum NetworkEnum {
PROD,
TEST,
REGTEST
}
public enum ValidationMode {
FULL,
SPV
}
public static void main(String[] args) throws Exception {
OptionParser parser = new OptionParser();
parser.accepts("help");
parser.accepts("force");
parser.accepts("debuglog");
walletFileName = parser.accepts("wallet")
.withRequiredArg()
.defaultsTo("wallet");
actionFlag = parser.accepts("action")
.withRequiredArg()
.ofType(ActionEnum.class);
netFlag = parser.accepts("net")
.withOptionalArg()
.ofType(NetworkEnum.class)
.defaultsTo(NetworkEnum.PROD);
dateFlag = parser.accepts("date")
.withRequiredArg()
.ofType(Date.class)
.withValuesConvertedBy(DateConverter.datePattern("yyyy/MM/dd"));
waitForFlag = parser.accepts("waitfor")
.withRequiredArg()
.ofType(WaitForEnum.class);
modeFlag = parser.accepts("mode")
.withRequiredArg()
.ofType(ValidationMode.class)
.defaultsTo(ValidationMode.SPV);
OptionSpec<String> chainFlag = parser.accepts("chain").withRequiredArg();
// For addkey/delkey.
parser.accepts("pubkey").withRequiredArg();
parser.accepts("privkey").withRequiredArg();
parser.accepts("addr").withRequiredArg();
parser.accepts("peers").withRequiredArg();
OptionSpec<String> outputFlag = parser.accepts("output").withRequiredArg();
parser.accepts("value").withRequiredArg();
parser.accepts("fee").withRequiredArg();
unixtimeFlag = parser.accepts("unixtime").withRequiredArg().ofType(Integer.class);
conditionFlag = parser.accepts("condition").withRequiredArg();
parser.accepts("locktime").withRequiredArg();
parser.accepts("allow-unconfirmed");
parser.accepts("offline");
OptionSpec<String> passwordFlag = parser.accepts("password").withRequiredArg();
options = parser.parse(args);
if (args.length == 0 || options.has("help") || options.nonOptionArguments().size() > 0) {
System.out.println(HELP_TEXT);
return;
}
if (options.has("debuglog")) {
BriefLogFormatter.init();
log.info("Starting up ...");
} else {
// Disable logspam unless there is a flag.
logger = LogManager.getLogManager().getLogger("");
logger.setLevel(Level.SEVERE);
}
switch (netFlag.value(options)) {
case PROD:
params = MainNetParams.get();
chainFileName = new File("prodnet.chain");
break;
case TEST:
params = TestNet3Params.get();
chainFileName = new File("testnet.chain");
break;
case REGTEST:
params = RegTestParams.get();
chainFileName = new File("regtest.chain");
break;
default:
throw new RuntimeException("Unreachable.");
}
mode = modeFlag.value(options);
// Allow the user to override the name of the chain used.
if (options.has(chainFlag)) {
chainFileName = new File(chainFlag.value(options));
}
if (options.has("condition")) {
condition = new Condition(conditionFlag.value(options));
}
if (options.has(passwordFlag)) {
password = passwordFlag.value(options);
}
ActionEnum action = ActionEnum.NONE;
if (options.has(actionFlag))
action = actionFlag.value(options);
walletFile = new File(walletFileName.value(options));
if (action == ActionEnum.CREATE) {
createWallet(options, params, walletFile);
return; // We're done.
}
if (!walletFile.exists()) {
System.err.println("Specified wallet file " + walletFile + " does not exist. Try --action=CREATE");
return;
}
if (action == ActionEnum.RAW_DUMP) {
// Just parse the protobuf and print, then bail out. Don't try and do a real deserialization. This is
// useful mostly for investigating corrupted wallets.
FileInputStream stream = new FileInputStream(walletFile);
try {
Protos.Wallet proto = WalletProtobufSerializer.parseToProto(stream);
System.out.println(proto.toString());
return;
} finally {
stream.close();
}
}
try {
WalletProtobufSerializer loader = new WalletProtobufSerializer();
wallet = loader.readWallet(new BufferedInputStream(new FileInputStream(walletFile)));
if (!wallet.getParams().equals(params)) {
System.err.println("Wallet does not match requested network parameters: " +
wallet.getParams().getId() + " vs " + params.getId());
return;
}
} catch (Exception e) {
System.err.println("Failed to load wallet '" + walletFile + "': " + e.getMessage());
e.printStackTrace();
return;
}
// What should we do?
switch (action) {
case DUMP: dumpWallet(); break;
case ADD_KEY: addKey(); break;
case DELETE_KEY: deleteKey(); break;
case RESET: reset(); break;
case SYNC: syncChain(); break;
case SEND:
if (!options.has(outputFlag)) {
System.err.println("You must specify at least one --output=addr:value.");
return;
}
BigInteger fee = BigInteger.ZERO;
if (options.has("fee")) {
fee = Utils.toNanoCoins((String)options.valueOf("fee"));
}
String lockTime = null;
if (options.has("locktime")) {
lockTime = (String) options.valueOf("locktime");
}
boolean allowUnconfirmed = options.has("allow-unconfirmed");
send(outputFlag.values(options), fee, lockTime, allowUnconfirmed);
break;
}
if (!wallet.isConsistent()) {
System.err.println("************** WALLET IS INCONSISTENT *****************");
return;
}
saveWallet(walletFile);
if (options.has(waitForFlag)) {
WaitForEnum value;
try {
value = waitForFlag.value(options);
} catch (Exception e) {
System.err.println("Could not understand the --waitfor flag: Valid options are WALLET_TX, BLOCK, " +
"BALANCE and EVER");
return;
}
wait(value);
if (!wallet.isConsistent()) {
System.err.println("************** WALLET IS INCONSISTENT *****************");
return;
}
saveWallet(walletFile);
}
shutdown();
}
private static void send(List<String> outputs, BigInteger fee, String lockTimeStr, boolean allowUnconfirmed) throws VerificationException {
try {
// Convert the input strings to outputs.
Transaction t = new Transaction(params);
for (String spec : outputs) {
String[] parts = spec.split(":");
if (parts.length != 2) {
System.err.println("Malformed output specification, must have two parts separated by :");
return;
}
String destination = parts[0];
try {
BigInteger value = Utils.toNanoCoins(parts[1]);
if (destination.startsWith("04") && (destination.length() == 130 || destination.length() == 66)) {
// Treat as a raw public key.
BigInteger pubKey = new BigInteger(destination, 16);
ECKey key = new ECKey(null, pubKey);
t.addOutput(value, key);
} else {
// Treat as an address.
Address addr = new Address(params, destination);
t.addOutput(value, addr);
}
} catch (WrongNetworkException e) {
System.err.println("Malformed output specification, address is for a different network: " + parts[0]);
return;
} catch (AddressFormatException e) {
System.err.println("Malformed output specification, could not parse as address: " + parts[0]);
return;
} catch (NumberFormatException e) {
System.err.println("Malformed output specification, could not parse as value: " + parts[1]);
}
}
Wallet.SendRequest req = Wallet.SendRequest.forTx(t);
if (t.getOutputs().size() == 1 && t.getOutput(0).getValue().equals(wallet.getBalance())) {
log.info("Emptying out wallet, recipient may get less than what you expect");
req.emptyWallet = true;
}
req.fee = fee;
if (allowUnconfirmed) {
wallet.allowSpendingUnconfirmedTransactions();
}
if (password != null) {
if (!wallet.checkPassword(password)) {
System.err.println("Password is incorrect.");
return;
}
req.aesKey = wallet.getKeyCrypter().deriveKey(password);
}
if (!wallet.completeTx(req)) {
System.err.println("Insufficient funds: have " + Utils.bitcoinValueToFriendlyString(wallet.getBalance()));
return;
}
try {
if (lockTimeStr != null) {
t.setLockTime(Transaction.parseLockTimeStr(lockTimeStr));
// For lock times to take effect, at least one output must have a non-final sequence number.
t.getInputs().get(0).setSequenceNumber(0);
// And because we modified the transaction after it was completed, we must re-sign the inputs.
t.signInputs(Transaction.SigHash.ALL, wallet);
}
} catch (ParseException e) {
System.err.println("Could not understand --locktime of " + lockTimeStr);
return;
} catch (ScriptException e) {
throw new RuntimeException(e);
} catch (KeyCrypterException e) {
throw new RuntimeException(e);
}
t = req.tx; // Not strictly required today.
System.out.println(t.getHashAsString());
if (options.has("offline")) {
wallet.commitTx(t);
return;
}
setup();
peers.startAndWait();
// Wait for peers to connect, the tx to be sent to one of them and for it to be propagated across the
// network. Once propagation is complete and we heard the transaction back from all our peers, it will
// be committed to the wallet.
peers.broadcastTransaction(t).get();
if (peers.getMinBroadcastConnections() == 1) {
// Crap hack to work around some issue with Netty where the write future
// completes before the remote peer actually hears the message.
Thread.sleep(5000);
}
} catch (BlockStoreException e) {
throw new RuntimeException(e);
} catch (KeyCrypterException e) {
throw new RuntimeException(e);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
private static void wait(WaitForEnum waitFor) throws BlockStoreException {
final CountDownLatch latch = new CountDownLatch(1);
setup();
switch (waitFor) {
case EVER:
break;
case WALLET_TX:
wallet.addEventListener(new AbstractWalletEventListener() {
private void handleTx(Transaction tx) {
System.out.println(tx.getHashAsString());
latch.countDown(); // Wake up main thread.
}
@Override
public void onCoinsReceived(Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance) {
// Runs in a peer thread.
super.onCoinsReceived(wallet, tx, prevBalance, newBalance);
handleTx(tx);
}
@Override
public void onCoinsSent(Wallet wallet, Transaction tx, BigInteger prevBalance,
BigInteger newBalance) {
// Runs in a peer thread.
super.onCoinsSent(wallet, tx, prevBalance, newBalance);
handleTx(tx);
}
});
break;
case BLOCK:
peers.addEventListener(new AbstractPeerEventListener() {
@Override
public void onBlocksDownloaded(Peer peer, Block block, int blocksLeft) {
super.onBlocksDownloaded(peer, block, blocksLeft);
// Check if we already ran. This can happen if a block being received triggers download of more
// blocks, or if we receive another block whilst the peer group is shutting down.
if (latch.getCount() == 0) return;
System.out.println(block.getHashAsString());
latch.countDown();
}
});
break;
case BALANCE:
// Check if the balance already meets the given condition.
if (condition.matchBitcoins(wallet.getBalance(Wallet.BalanceType.ESTIMATED))) {
latch.countDown();
break;
}
wallet.addEventListener(new AbstractWalletEventListener() {
@Override
public synchronized void onChange() {
super.onChange();
saveWallet(walletFile);
BigInteger balance = wallet.getBalance(Wallet.BalanceType.ESTIMATED);
if (condition.matchBitcoins(balance)) {
System.out.println(Utils.bitcoinValueToFriendlyString(balance));
latch.countDown();
}
}
});
break;
}
peers.start();
try {
latch.await();
} catch (InterruptedException e) {
}
}
private static void reset() {
// Delete the transactions and save. In future, reset the chain head pointer.
wallet.clearTransactions(0);
saveWallet(walletFile);
}
// Sets up all objects needed for network communication but does not bring up the peers.
private static void setup() throws BlockStoreException {
if (store != null) return; // Already done.
// Will create a fresh chain if one doesn't exist or there is an issue with this one.
if (!chainFileName.exists() && wallet.getTransactions(true).size() > 0) {
// No chain, so reset the wallet as we will be downloading from scratch.
System.out.println("Chain file is missing so clearing transactions from the wallet.");
reset();
}
if (mode == ValidationMode.SPV) {
store = new SPVBlockStore(params, chainFileName);
chain = new BlockChain(params, wallet, store);
} else if (mode == ValidationMode.FULL) {
FullPrunedBlockStore s = new H2FullPrunedBlockStore(params, chainFileName.getAbsolutePath(), 5000);
store = s;
chain = new FullPrunedBlockChain(params, wallet, s);
}
// This will ensure the wallet is saved when it changes.
wallet.autosaveToFile(walletFile, 200, TimeUnit.MILLISECONDS, null);
peers = new PeerGroup(params, chain);
peers.setUserAgent("WalletTool", "1.0");
peers.addWallet(wallet);
if (options.has("peers")) {
String peersFlag = (String) options.valueOf("peers");
String[] peerAddrs = peersFlag.split(",");
for (String peer : peerAddrs) {
try {
peers.addAddress(new PeerAddress(InetAddress.getByName(peer), params.getPort()));
} catch (UnknownHostException e) {
System.err.println("Could not understand peer domain name/IP address: " + peer + ": " + e.getMessage());
System.exit(1);
}
}
} else {
peers.addPeerDiscovery(new DnsDiscovery(params));
}
}
private static void syncChain() {
try {
setup();
int startTransactions = wallet.getTransactions(true).size();
DownloadListener listener = new DownloadListener();
peers.startAndWait();
peers.startBlockChainDownload(listener);
try {
listener.await();
} catch (InterruptedException e) {
System.err.println("Chain download interrupted, quitting ...");
System.exit(1);
}
int endTransactions = wallet.getTransactions(true).size();
if (endTransactions > startTransactions) {
System.out.println("Synced " + (endTransactions - startTransactions) + " transactions.");
}
} catch (BlockStoreException e) {
System.err.println("Error reading block chain file " + chainFileName + ": " + e.getMessage());
e.printStackTrace();
}
}
private static void shutdown() {
try {
if (peers == null) return; // setup() never called so nothing to do.
peers.stopAndWait();
saveWallet(walletFile);
store.close();
wallet = null;
} catch (BlockStoreException e) {
throw new RuntimeException(e);
}
}
private static void createWallet(OptionSet options, NetworkParameters params, File walletFile) throws IOException {
if (walletFile.exists() && !options.has("force")) {
System.err.println("Wallet creation requested but " + walletFile + " already exists, use --force");
return;
}
wallet = new Wallet(params);
if (password != null) {
wallet.encrypt(password);
wallet.addNewEncryptedKey(password);
}
wallet.saveToFile(walletFile);
}
private static void saveWallet(File walletFile) {
try {
// This will save the new state of the wallet to a temp file then rename, in case anything goes wrong.
wallet.saveToFile(walletFile);
} catch (IOException e) {
System.err.println("Failed to save wallet! Old wallet should be left untouched.");
e.printStackTrace();
System.exit(1);
}
}
private static void addKey() {
ECKey key;
long creationTimeSeconds = 0;
if (options.has(unixtimeFlag)) {
creationTimeSeconds = unixtimeFlag.value(options);
} else if (options.has(dateFlag)) {
creationTimeSeconds = dateFlag.value(options).getTime() / 1000;
}
if (options.has("privkey")) {
String data = (String) options.valueOf("privkey");
if (data.charAt(0) == 'L') {
DumpedPrivateKey dpk;
try {
dpk = new DumpedPrivateKey(params, data);
} catch (AddressFormatException e) {
System.err.println("Could not parse dumped private key " + data);
return;
}
key = dpk.getKey();
} else {
byte[] decode = Utils.parseAsHexOrBase58(data);
if (decode == null) {
System.err.println("Could not understand --privkey as either hex or base58: " + data);
return;
}
key = new ECKey(new BigInteger(1, decode));
}
if (options.has("pubkey")) {
// Give the user a hint.
System.out.println("You don't have to specify --pubkey when a private key is supplied.");
}
key.setCreationTimeSeconds(creationTimeSeconds);
} else if (options.has("pubkey")) {
byte[] pubkey = Utils.parseAsHexOrBase58((String) options.valueOf("pubkey"));
key = new ECKey(null, pubkey);
key.setCreationTimeSeconds(creationTimeSeconds);
} else {
// Freshly generated key.
key = new ECKey();
if (creationTimeSeconds > 0)
key.setCreationTimeSeconds(creationTimeSeconds);
}
if (wallet.findKeyFromPubKey(key.getPubKey()) != null) {
System.err.println("That key already exists in this wallet.");
return;
}
try {
if (wallet.isEncrypted()) {
if (password == null || !wallet.checkPassword(password)) {
System.err.println("The password is incorrect.");
return;
}
key = key.encrypt(wallet.getKeyCrypter(), wallet.getKeyCrypter().deriveKey(password));
}
wallet.addKey(key);
} catch (KeyCrypterException kce) {
System.err.println("There was an encryption related error when adding the key. The error was '" + kce.getMessage() + "'.");
}
System.out.println(key.toAddress(params) + " " + key);
}
private static void deleteKey() {
String pubkey = (String) options.valueOf("pubkey");
String addr = (String) options.valueOf("addr");
if (pubkey == null && addr == null) {
System.err.println("One of --pubkey or --addr must be specified.");
return;
}
ECKey key = null;
if (pubkey != null) {
key = wallet.findKeyFromPubKey(Hex.decode(pubkey));
} else {
try {
Address address = new Address(wallet.getParams(), addr);
key = wallet.findKeyFromPubHash(address.getHash160());
} catch (AddressFormatException e) {
System.err.println(addr + " does not parse as a Bitcoin address of the right network parameters.");
return;
}
}
if (key == null) {
System.err.println("Wallet does not seem to contain that key.");
return;
}
wallet.removeKey(key);
}
private static void dumpWallet() throws BlockStoreException {
// Setup to get the chain height so we can estimate lock times, but don't wipe the transactions if it's not
// there just for the dump case.
if (chainFileName.exists())
setup();
System.out.println(wallet.toString(true, true, true, chain));
}
}