Skip to content
Bitcoin Roots

v31.1-roots.1 · Commit 6: [PATCH 06/17] feat(wallet): preserve Roots signing and spend controls on v31

src/wallet/rpc/spend.cpp

Before: src/wallet/rpc/spend.cpp · After: src/wallet/rpc/spend.cpp

File metadata
index 2991a27594ed1379ced89dd3da5e9c6c51a22920..442dacafccd5a31d8096e30279a5ded00e33df7f 100644

Download original patch · Release notes

Diff rows 1–101 of 101. Line numbers refer to the original before/after files.

+ Added− RemovedUnmarked lines are context.

Scroll the code area horizontally to read long lines. The two line-number columns refer to before and after.

src/wallet/rpc/spend.cpp CHANGED
@@ -5,6 +5,7 @@
5
5
  #include <common/messages.h>
6
6
  #include <consensus/validation.h>
7
7
  #include <core_io.h>
8
+ #include <interfaces/wallet.h>
8
9
  #include <key_io.h>
9
10
  #include <node/types.h>
10
11
  #include <policy/policy.h>
@@ -32,6 +33,94 @@ using common::TransactionErrorString;
32
33
  using node::TransactionError;
33
34
 
34
35
  namespace wallet {
36
+ //! Sweep coins controlled by transient keys. The keys are deliberately kept out
37
+ //! of the wallet and are only used by the local signing provider below.
38
+ RPCHelpMan sweepprivkeys()
39
+ {
40
+ return RPCHelpMan{"sweepprivkeys",
41
+ "Sweep confirmed UTXOs from supplied Base58 WIF private keys to a spendable address in this wallet.\n"
42
+ "All valid WIF keys support P2PKH. Compressed keys also support native P2WPKH and wrapped P2SH-P2WPKH. "
43
+ "Taproot and other script forms are not scanned.\n"
44
+ "The destination must be controlled and spendable by this selected wallet; watch-only and private-key-disabled wallets are rejected. "
45
+ "Supplied keys are never imported or saved. Keep an independent backup until the transaction confirms.\n"
46
+ "With broadcast=false (the default), the result is a signed preview and no transaction is submitted. The fee reflects current wallet fee settings and chain state. "
47
+ "Set broadcast=true only after reviewing a fresh preview; broadcast can fail if inputs, fee policy, or chain state changed.\n",
48
+ {
49
+ {"privkeys", RPCArg::Type::ARR, RPCArg::Optional::NO, "Valid Base58 WIF private keys", {{"privatekey", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "private key"}}},
50
+ {"destination", RPCArg::Type::STR, RPCArg::Optional::NO, "A spendable address controlled by this selected wallet"},
51
+ {"broadcast", RPCArg::Type::BOOL, RPCArg::Default{false}, "Submit the signed transaction after a preview has been reviewed"},
52
+ },
53
+ RPCResult{RPCResult::Type::OBJ, "", "", {
54
+ {RPCResult::Type::STR_HEX, "hex", "Signed transaction"},
55
+ {RPCResult::Type::STR_HEX, "txid", "Transaction id"},
56
+ {RPCResult::Type::STR_AMOUNT, "amount", "Total value of swept inputs"},
57
+ {RPCResult::Type::STR_AMOUNT, "fee", "Transaction fee"},
58
+ {RPCResult::Type::NUM, "inputs", "Number of swept inputs"},
59
+ {RPCResult::Type::BOOL, "broadcast", "Whether the transaction was submitted for broadcast"},
60
+ }},
61
+ RPCExamples{HelpExampleCli("sweepprivkeys", "'[\"cV...\"]' bcrt1... false")},
62
+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
63
+ const std::shared_ptr<CWallet> pwallet = GetWalletForJSONRPCRequest(request);
64
+ if (!pwallet) return UniValue::VNULL;
65
+
66
+ FlatSigningProvider provider;
67
+ std::set<CKeyID> seen_keys;
68
+ std::set<CScript> scan_objects;
69
+ for (const UniValue& value : request.params[0].get_array().getValues()) {
70
+ CKey key{DecodeSecret(value.get_str())};
71
+ if (!key.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key or wrong network");
72
+ const CPubKey pubkey{key.GetPubKey()};
73
+ if (!seen_keys.insert(pubkey.GetID()).second) throw JSONRPCError(RPC_INVALID_PARAMETER, "Duplicate private key");
74
+ provider.keys.emplace(pubkey.GetID(), key);
75
+ provider.pubkeys.emplace(pubkey.GetID(), pubkey);
76
+ scan_objects.insert(GetScriptForDestination(PKHash(pubkey)));
77
+ if (pubkey.IsCompressed()) {
78
+ const CScript witness{GetScriptForDestination(WitnessV0KeyHash(pubkey))};
79
+ scan_objects.insert(witness);
80
+ provider.scripts.emplace(CScriptID(witness), witness);
81
+ scan_objects.insert(GetScriptForDestination(ScriptHash(witness)));
82
+ }
83
+ }
84
+ if (seen_keys.empty()) throw JSONRPCError(RPC_INVALID_PARAMETER, "At least one private key is required");
85
+
86
+ const CTxDestination destination{DecodeDestination(request.params[1].get_str())};
87
+ if (!IsValidDestination(destination)) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid destination");
88
+ const CScript destination_script{GetScriptForDestination(destination)};
89
+ {
90
+ LOCK(pwallet->cs_wallet);
91
+ if (!CanSweepTo(*pwallet, destination_script)) {
92
+ throw JSONRPCError(RPC_WALLET_ERROR, "Destination is not controlled by this wallet");
93
+ }
94
+ }
95
+
96
+ std::map<COutPoint, Coin> coins;
97
+ switch (pwallet->chain().findScriptPubKeys(scan_objects, coins)) {
98
+ case interfaces::ScanResult::SUCCESS: break;
99
+ case interfaces::ScanResult::BUSY: throw JSONRPCError(RPC_MISC_ERROR, "UTXO scan already in progress");
100
+ case interfaces::ScanResult::ABORTED: throw JSONRPCError(RPC_MISC_ERROR, "UTXO scan aborted");
101
+ case interfaces::ScanResult::UNAVAILABLE: throw JSONRPCError(RPC_MISC_ERROR, "UTXO scan unavailable");
102
+ }
103
+ const bool broadcast{!request.params[2].isNull() && request.params[2].get_bool()};
104
+ interfaces::SweepAuthorization authorization;
105
+ auto swept{CreateSweepTransaction(*pwallet, provider, coins, destination_script, broadcast ? SweepBroadcastFn{[&](const CTransactionRef& tx, std::string& error) {
106
+ return pwallet->chain().broadcastTransaction(tx, pwallet->m_default_max_tx_fee, node::TxBroadcast::MEMPOOL_AND_BROADCAST_TO_ALL, error);
107
+ }} : SweepBroadcastFn{}, authorization)};
108
+ if (!swept) {
109
+ const std::string error{util::ErrorString(swept).original};
110
+ const int code{error == "No spendable UTXOs found" || error == "Fee exceeds swept value" ? RPC_WALLET_INSUFFICIENT_FUNDS :
111
+ error == "Unable to sign all swept inputs" ? RPC_VERIFY_ERROR : RPC_WALLET_ERROR};
112
+ throw JSONRPCError(code, error);
113
+ }
114
+ UniValue result{UniValue::VOBJ};
115
+ result.pushKV("hex", EncodeHexTx(*swept->tx));
116
+ result.pushKV("txid", swept->tx->GetHash().GetHex());
117
+ result.pushKV("amount", ValueFromAmount(swept->amount));
118
+ result.pushKV("fee", ValueFromAmount(swept->fee));
119
+ result.pushKV("inputs", coins.size());
120
+ result.pushKV("broadcast", broadcast);
121
+ return result;
122
+ }};
123
+ }
35
124
  std::vector<CRecipient> CreateRecipients(const std::vector<std::pair<CTxDestination, CAmount>>& outputs, const std::set<int>& subtract_fee_outputs)
36
125
  {
37
126
  std::vector<CRecipient> recipients;