]>
Commit | Line | Data |
---|---|---|
e3bc5698 | 1 | // Copyright (c) 2010 Satoshi Nakamoto |
f914f1a7 | 2 | // Copyright (c) 2009-2014 The Bitcoin Core developers |
72fb3d29 | 3 | // Distributed under the MIT software license, see the accompanying |
e3bc5698 JG |
4 | // file COPYING or http://www.opensource.org/licenses/mit-license.php. |
5 | ||
eda37330 | 6 | #include "amount.h" |
be126699 | 7 | #include "consensus/upgrades.h" |
ae775b5b | 8 | #include "core_io.h" |
e3bc5698 | 9 | #include "init.h" |
3d31e09c | 10 | #include "key_io.h" |
8a893c94 | 11 | #include "main.h" |
51ed9ec9 BD |
12 | #include "net.h" |
13 | #include "netbase.h" | |
4519a766 | 14 | #include "rpc/server.h" |
14f888ca | 15 | #include "timedata.h" |
36e2141d | 16 | #include "transaction_builder.h" |
51ed9ec9 | 17 | #include "util.h" |
b93173de | 18 | #include "utilmoneystr.h" |
51ed9ec9 BD |
19 | #include "wallet.h" |
20 | #include "walletdb.h" | |
8cb25088 | 21 | #include "primitives/transaction.h" |
6962bb3d | 22 | #include "zcbenchmarks.h" |
6aae9d1a | 23 | #include "script/interpreter.h" |
70b4ad2d | 24 | #include "zcash/zip32.h" |
51ed9ec9 | 25 | |
fc72c078 S |
26 | #include "utiltime.h" |
27 | #include "asyncrpcoperation.h" | |
ed21d5bd | 28 | #include "asyncrpcqueue.h" |
6e9c7629 | 29 | #include "wallet/asyncrpcoperation_mergetoaddress.h" |
6e82d728 | 30 | #include "wallet/asyncrpcoperation_saplingmigration.h" |
fc72c078 | 31 | #include "wallet/asyncrpcoperation_sendmany.h" |
06c19063 | 32 | #include "wallet/asyncrpcoperation_shieldcoinbase.h" |
fc72c078 | 33 | |
320f2cc7 SB |
34 | #include "sodium.h" |
35 | ||
51ed9ec9 BD |
36 | #include <stdint.h> |
37 | ||
38 | #include <boost/assign/list_of.hpp> | |
25cf6f3d | 39 | |
a10a6e2a | 40 | #include <univalue.h> |
e3bc5698 | 41 | |
bcbde86a S |
42 | #include <numeric> |
43 | ||
e3bc5698 JG |
44 | using namespace std; |
45 | ||
2dc35992 SB |
46 | using namespace libzcash; |
47 | ||
eec85c43 JG |
48 | const std::string ADDR_TYPE_SPROUT = "sprout"; |
49 | const std::string ADDR_TYPE_SAPLING = "sapling"; | |
50 | ||
0d37ae3a | 51 | extern UniValue TxJoinSplitToJSON(const CTransaction& tx); |
f7cfb52d | 52 | |
51ed9ec9 | 53 | int64_t nWalletUnlockTime; |
e3bc5698 JG |
54 | static CCriticalSection cs_nWalletUnlockTime; |
55 | ||
c1eae280 | 56 | // Private method: |
0d37ae3a | 57 | UniValue z_getoperationstatus_IMPL(const UniValue&, bool); |
c1eae280 | 58 | |
bdab0cf5 | 59 | std::string HelpRequiringPassphrase() |
e3bc5698 | 60 | { |
b0730874 | 61 | return pwalletMain && pwalletMain->IsCrypted() |
a6099ef3 | 62 | ? "\nRequires wallet passphrase to be set with walletpassphrase call." |
e3bc5698 JG |
63 | : ""; |
64 | } | |
65 | ||
b9fb692d JS |
66 | bool EnsureWalletIsAvailable(bool avoidException) |
67 | { | |
68 | if (!pwalletMain) | |
69 | { | |
70 | if (!avoidException) | |
71 | throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found (disabled)"); | |
72 | else | |
73 | return false; | |
74 | } | |
75 | return true; | |
76 | } | |
77 | ||
bdab0cf5 | 78 | void EnsureWalletIsUnlocked() |
e3bc5698 JG |
79 | { |
80 | if (pwalletMain->IsLocked()) | |
738835d7 | 81 | throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Please enter the wallet passphrase with walletpassphrase first."); |
e3bc5698 JG |
82 | } |
83 | ||
d014114d | 84 | void WalletTxToJSON(const CWalletTx& wtx, UniValue& entry) |
e3bc5698 JG |
85 | { |
86 | int confirms = wtx.GetDepthInMainChain(); | |
87 | entry.push_back(Pair("confirmations", confirms)); | |
e07c8e91 LD |
88 | if (wtx.IsCoinBase()) |
89 | entry.push_back(Pair("generated", true)); | |
2b72d46f | 90 | if (confirms > 0) |
e3bc5698 JG |
91 | { |
92 | entry.push_back(Pair("blockhash", wtx.hashBlock.GetHex())); | |
93 | entry.push_back(Pair("blockindex", wtx.nIndex)); | |
209377a7 | 94 | entry.push_back(Pair("blocktime", mapBlockIndex[wtx.hashBlock]->GetBlockTime())); |
9bb37bf0 | 95 | entry.push_back(Pair("expiryheight", (int64_t)wtx.nExpiryHeight)); |
e3bc5698 | 96 | } |
805344dc | 97 | uint256 hash = wtx.GetHash(); |
731b89b8 | 98 | entry.push_back(Pair("txid", hash.GetHex())); |
38fc4b70 | 99 | UniValue conflicts(UniValue::VARR); |
731b89b8 GA |
100 | BOOST_FOREACH(const uint256& conflict, wtx.GetConflicts()) |
101 | conflicts.push_back(conflict.GetHex()); | |
102 | entry.push_back(Pair("walletconflicts", conflicts)); | |
d56e30ca | 103 | entry.push_back(Pair("time", wtx.GetTxTime())); |
4b61a6a4 | 104 | entry.push_back(Pair("timereceived", (int64_t)wtx.nTimeReceived)); |
e3bc5698 JG |
105 | BOOST_FOREACH(const PAIRTYPE(string,string)& item, wtx.mapValue) |
106 | entry.push_back(Pair(item.first, item.second)); | |
f7cfb52d S |
107 | |
108 | entry.push_back(Pair("vjoinsplit", TxJoinSplitToJSON(wtx))); | |
e3bc5698 JG |
109 | } |
110 | ||
d014114d | 111 | string AccountFromValue(const UniValue& value) |
e3bc5698 JG |
112 | { |
113 | string strAccount = value.get_str(); | |
3c31eb24 | 114 | if (strAccount != "") |
7b3351ff | 115 | throw JSONRPCError(RPC_WALLET_ACCOUNTS_UNSUPPORTED, "Accounts are unsupported"); |
e3bc5698 JG |
116 | return strAccount; |
117 | } | |
118 | ||
d014114d | 119 | UniValue getnewaddress(const UniValue& params, bool fHelp) |
e3bc5698 | 120 | { |
b9fb692d | 121 | if (!EnsureWalletIsAvailable(fHelp)) |
9756b7bd | 122 | return NullUniValue; |
70454796 | 123 | |
e3bc5698 JG |
124 | if (fHelp || params.size() > 1) |
125 | throw runtime_error( | |
a6099ef3 | 126 | "getnewaddress ( \"account\" )\n" |
58c4c0bb | 127 | "\nReturns a new Zcash address for receiving payments.\n" |
a6099ef3 | 128 | "\nArguments:\n" |
3c31eb24 | 129 | "1. \"account\" (string, optional) DEPRECATED. If provided, it MUST be set to the empty string \"\" to represent the default account. Passing any other string will result in an error.\n" |
a6099ef3 | 130 | "\nResult:\n" |
d2c1e4a8 | 131 | "\"zcashaddress\" (string) The new Zcash address\n" |
a6099ef3 | 132 | "\nExamples:\n" |
133 | + HelpExampleCli("getnewaddress", "") | |
7b782f5b | 134 | + HelpExampleRpc("getnewaddress", "") |
a6099ef3 | 135 | ); |
e3bc5698 | 136 | |
4401b2d7 EL |
137 | LOCK2(cs_main, pwalletMain->cs_wallet); |
138 | ||
e3bc5698 JG |
139 | // Parse the account first so we don't generate a key if there's an error |
140 | string strAccount; | |
141 | if (params.size() > 0) | |
142 | strAccount = AccountFromValue(params[0]); | |
143 | ||
144 | if (!pwalletMain->IsLocked()) | |
145 | pwalletMain->TopUpKeyPool(); | |
146 | ||
147 | // Generate a new key that is added to wallet | |
148 | CPubKey newKey; | |
71ac5052 | 149 | if (!pwalletMain->GetKeyFromPool(newKey)) |
738835d7 | 150 | throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); |
e3bc5698 JG |
151 | CKeyID keyID = newKey.GetID(); |
152 | ||
a41d5fe0 | 153 | pwalletMain->SetAddressBook(keyID, strAccount, "receive"); |
e3bc5698 | 154 | |
07444da1 | 155 | return EncodeDestination(keyID); |
e3bc5698 JG |
156 | } |
157 | ||
158 | ||
07444da1 | 159 | CTxDestination GetAccountAddress(std::string strAccount, bool bForceNew=false) |
e3bc5698 JG |
160 | { |
161 | CWalletDB walletdb(pwalletMain->strWalletFile); | |
162 | ||
163 | CAccount account; | |
164 | walletdb.ReadAccount(strAccount, account); | |
165 | ||
166 | bool bKeyUsed = false; | |
167 | ||
168 | // Check if the current key has been used | |
169 | if (account.vchPubKey.IsValid()) | |
170 | { | |
0be990ba | 171 | CScript scriptPubKey = GetScriptForDestination(account.vchPubKey.GetID()); |
e3bc5698 JG |
172 | for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); |
173 | it != pwalletMain->mapWallet.end() && account.vchPubKey.IsValid(); | |
174 | ++it) | |
175 | { | |
176 | const CWalletTx& wtx = (*it).second; | |
177 | BOOST_FOREACH(const CTxOut& txout, wtx.vout) | |
178 | if (txout.scriptPubKey == scriptPubKey) | |
179 | bKeyUsed = true; | |
180 | } | |
181 | } | |
182 | ||
183 | // Generate a new key | |
184 | if (!account.vchPubKey.IsValid() || bForceNew || bKeyUsed) | |
185 | { | |
71ac5052 | 186 | if (!pwalletMain->GetKeyFromPool(account.vchPubKey)) |
738835d7 | 187 | throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); |
e3bc5698 | 188 | |
a41d5fe0 | 189 | pwalletMain->SetAddressBook(account.vchPubKey.GetID(), strAccount, "receive"); |
e3bc5698 JG |
190 | walletdb.WriteAccount(strAccount, account); |
191 | } | |
192 | ||
07444da1 | 193 | return account.vchPubKey.GetID(); |
e3bc5698 JG |
194 | } |
195 | ||
d014114d | 196 | UniValue getaccountaddress(const UniValue& params, bool fHelp) |
e3bc5698 | 197 | { |
b9fb692d | 198 | if (!EnsureWalletIsAvailable(fHelp)) |
9756b7bd | 199 | return NullUniValue; |
70454796 | 200 | |
e3bc5698 JG |
201 | if (fHelp || params.size() != 1) |
202 | throw runtime_error( | |
a6099ef3 | 203 | "getaccountaddress \"account\"\n" |
58c4c0bb | 204 | "\nDEPRECATED. Returns the current Zcash address for receiving payments to this account.\n" |
a6099ef3 | 205 | "\nArguments:\n" |
3c31eb24 | 206 | "1. \"account\" (string, required) MUST be set to the empty string \"\" to represent the default account. Passing any other string will result in an error.\n" |
a6099ef3 | 207 | "\nResult:\n" |
d2c1e4a8 | 208 | "\"zcashaddress\" (string) The account Zcash address\n" |
a6099ef3 | 209 | "\nExamples:\n" |
210 | + HelpExampleCli("getaccountaddress", "") | |
211 | + HelpExampleCli("getaccountaddress", "\"\"") | |
212 | + HelpExampleCli("getaccountaddress", "\"myaccount\"") | |
213 | + HelpExampleRpc("getaccountaddress", "\"myaccount\"") | |
214 | ); | |
e3bc5698 | 215 | |
4401b2d7 EL |
216 | LOCK2(cs_main, pwalletMain->cs_wallet); |
217 | ||
e3bc5698 JG |
218 | // Parse the account first so we don't generate a key if there's an error |
219 | string strAccount = AccountFromValue(params[0]); | |
220 | ||
d014114d | 221 | UniValue ret(UniValue::VSTR); |
e3bc5698 | 222 | |
07444da1 | 223 | ret = EncodeDestination(GetAccountAddress(strAccount)); |
e3bc5698 JG |
224 | return ret; |
225 | } | |
226 | ||
227 | ||
d014114d | 228 | UniValue getrawchangeaddress(const UniValue& params, bool fHelp) |
e5e9904c | 229 | { |
b9fb692d | 230 | if (!EnsureWalletIsAvailable(fHelp)) |
9756b7bd | 231 | return NullUniValue; |
70454796 | 232 | |
e5e9904c JG |
233 | if (fHelp || params.size() > 1) |
234 | throw runtime_error( | |
235 | "getrawchangeaddress\n" | |
58c4c0bb | 236 | "\nReturns a new Zcash address, for receiving change.\n" |
a6099ef3 | 237 | "This is for use with raw transactions, NOT normal use.\n" |
238 | "\nResult:\n" | |
239 | "\"address\" (string) The address\n" | |
240 | "\nExamples:\n" | |
241 | + HelpExampleCli("getrawchangeaddress", "") | |
242 | + HelpExampleRpc("getrawchangeaddress", "") | |
243 | ); | |
e5e9904c | 244 | |
4401b2d7 EL |
245 | LOCK2(cs_main, pwalletMain->cs_wallet); |
246 | ||
e5e9904c JG |
247 | if (!pwalletMain->IsLocked()) |
248 | pwalletMain->TopUpKeyPool(); | |
249 | ||
250 | CReserveKey reservekey(pwalletMain); | |
251 | CPubKey vchPubKey; | |
252 | if (!reservekey.GetReservedKey(vchPubKey)) | |
6c37f7fd | 253 | throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); |
e5e9904c JG |
254 | |
255 | reservekey.KeepKey(); | |
256 | ||
257 | CKeyID keyID = vchPubKey.GetID(); | |
258 | ||
07444da1 | 259 | return EncodeDestination(keyID); |
e5e9904c JG |
260 | } |
261 | ||
e3bc5698 | 262 | |
d014114d | 263 | UniValue setaccount(const UniValue& params, bool fHelp) |
e3bc5698 | 264 | { |
b9fb692d | 265 | if (!EnsureWalletIsAvailable(fHelp)) |
9756b7bd | 266 | return NullUniValue; |
70454796 | 267 | |
e3bc5698 JG |
268 | if (fHelp || params.size() < 1 || params.size() > 2) |
269 | throw runtime_error( | |
58c4c0bb | 270 | "setaccount \"zcashaddress\" \"account\"\n" |
7b782f5b | 271 | "\nDEPRECATED. Sets the account associated with the given address.\n" |
a6099ef3 | 272 | "\nArguments:\n" |
d2c1e4a8 | 273 | "1. \"zcashaddress\" (string, required) The Zcash address to be associated with an account.\n" |
3c31eb24 | 274 | "2. \"account\" (string, required) MUST be set to the empty string \"\" to represent the default account. Passing any other string will result in an error.\n" |
a6099ef3 | 275 | "\nExamples:\n" |
70454796 JG |
276 | + HelpExampleCli("setaccount", "\"t14oHp2v54vfmdgQ3v3SNuQga8JKHTNi2a1\" \"tabby\"") |
277 | + HelpExampleRpc("setaccount", "\"t14oHp2v54vfmdgQ3v3SNuQga8JKHTNi2a1\", \"tabby\"") | |
a6099ef3 | 278 | ); |
e3bc5698 | 279 | |
4401b2d7 EL |
280 | LOCK2(cs_main, pwalletMain->cs_wallet); |
281 | ||
07444da1 PW |
282 | CTxDestination dest = DecodeDestination(params[0].get_str()); |
283 | if (!IsValidDestination(dest)) { | |
58c4c0bb | 284 | throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Zcash address"); |
07444da1 | 285 | } |
e3bc5698 | 286 | |
e3bc5698 JG |
287 | string strAccount; |
288 | if (params.size() > 1) | |
289 | strAccount = AccountFromValue(params[1]); | |
290 | ||
31d6390f | 291 | // Only add the account if the address is yours. |
07444da1 | 292 | if (IsMine(*pwalletMain, dest)) { |
31d6390f | 293 | // Detect when changing the account of an address that is the 'unused current key' of another account: |
07444da1 PW |
294 | if (pwalletMain->mapAddressBook.count(dest)) { |
295 | std::string strOldAccount = pwalletMain->mapAddressBook[dest].name; | |
296 | if (dest == GetAccountAddress(strOldAccount)) { | |
31d6390f | 297 | GetAccountAddress(strOldAccount, true); |
07444da1 | 298 | } |
31d6390f | 299 | } |
07444da1 | 300 | pwalletMain->SetAddressBook(dest, strAccount, "receive"); |
e3bc5698 | 301 | } |
31d6390f ES |
302 | else |
303 | throw JSONRPCError(RPC_MISC_ERROR, "setaccount can only be used with own address"); | |
e3bc5698 | 304 | |
ed21d5bd | 305 | return NullUniValue; |
e3bc5698 JG |
306 | } |
307 | ||
308 | ||
d014114d | 309 | UniValue getaccount(const UniValue& params, bool fHelp) |
e3bc5698 | 310 | { |
b9fb692d | 311 | if (!EnsureWalletIsAvailable(fHelp)) |
9756b7bd | 312 | return NullUniValue; |
70454796 | 313 | |
e3bc5698 JG |
314 | if (fHelp || params.size() != 1) |
315 | throw runtime_error( | |
58c4c0bb | 316 | "getaccount \"zcashaddress\"\n" |
7b782f5b | 317 | "\nDEPRECATED. Returns the account associated with the given address.\n" |
a6099ef3 | 318 | "\nArguments:\n" |
d2c1e4a8 | 319 | "1. \"zcashaddress\" (string, required) The Zcash address for account lookup.\n" |
a6099ef3 | 320 | "\nResult:\n" |
321 | "\"accountname\" (string) the account address\n" | |
322 | "\nExamples:\n" | |
70454796 JG |
323 | + HelpExampleCli("getaccount", "\"t14oHp2v54vfmdgQ3v3SNuQga8JKHTNi2a1\"") |
324 | + HelpExampleRpc("getaccount", "\"t14oHp2v54vfmdgQ3v3SNuQga8JKHTNi2a1\"") | |
a6099ef3 | 325 | ); |
e3bc5698 | 326 | |
4401b2d7 EL |
327 | LOCK2(cs_main, pwalletMain->cs_wallet); |
328 | ||
07444da1 PW |
329 | CTxDestination dest = DecodeDestination(params[0].get_str()); |
330 | if (!IsValidDestination(dest)) { | |
58c4c0bb | 331 | throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Zcash address"); |
07444da1 | 332 | } |
e3bc5698 | 333 | |
07444da1 PW |
334 | std::string strAccount; |
335 | std::map<CTxDestination, CAddressBookData>::iterator mi = pwalletMain->mapAddressBook.find(dest); | |
336 | if (mi != pwalletMain->mapAddressBook.end() && !(*mi).second.name.empty()) { | |
61885513 | 337 | strAccount = (*mi).second.name; |
07444da1 | 338 | } |
e3bc5698 JG |
339 | return strAccount; |
340 | } | |
341 | ||
342 | ||
d014114d | 343 | UniValue getaddressesbyaccount(const UniValue& params, bool fHelp) |
e3bc5698 | 344 | { |
b9fb692d | 345 | if (!EnsureWalletIsAvailable(fHelp)) |
9756b7bd | 346 | return NullUniValue; |
70454796 | 347 | |
e3bc5698 JG |
348 | if (fHelp || params.size() != 1) |
349 | throw runtime_error( | |
a6099ef3 | 350 | "getaddressesbyaccount \"account\"\n" |
7b782f5b | 351 | "\nDEPRECATED. Returns the list of addresses for the given account.\n" |
a6099ef3 | 352 | "\nArguments:\n" |
3c31eb24 | 353 | "1. \"account\" (string, required) MUST be set to the empty string \"\" to represent the default account. Passing any other string will result in an error.\n" |
a6099ef3 | 354 | "\nResult:\n" |
355 | "[ (json array of string)\n" | |
d2c1e4a8 | 356 | " \"zcashaddress\" (string) a Zcash address associated with the given account\n" |
a6099ef3 | 357 | " ,...\n" |
358 | "]\n" | |
359 | "\nExamples:\n" | |
360 | + HelpExampleCli("getaddressesbyaccount", "\"tabby\"") | |
361 | + HelpExampleRpc("getaddressesbyaccount", "\"tabby\"") | |
362 | ); | |
e3bc5698 | 363 | |
4401b2d7 EL |
364 | LOCK2(cs_main, pwalletMain->cs_wallet); |
365 | ||
e3bc5698 JG |
366 | string strAccount = AccountFromValue(params[0]); |
367 | ||
368 | // Find all addresses that have the given account | |
38fc4b70 | 369 | UniValue ret(UniValue::VARR); |
07444da1 PW |
370 | for (const std::pair<CTxDestination, CAddressBookData>& item : pwalletMain->mapAddressBook) { |
371 | const CTxDestination& dest = item.first; | |
372 | const std::string& strName = item.second.name; | |
373 | if (strName == strAccount) { | |
374 | ret.push_back(EncodeDestination(dest)); | |
375 | } | |
e3bc5698 JG |
376 | } |
377 | return ret; | |
378 | } | |
379 | ||
292623ad | 380 | static void SendMoney(const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, CWalletTx& wtxNew) |
b93173de | 381 | { |
25cf6f3d PK |
382 | CAmount curBalance = pwalletMain->GetBalance(); |
383 | ||
b93173de PJ |
384 | // Check amount |
385 | if (nValue <= 0) | |
4be639ea | 386 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid amount"); |
b93173de | 387 | |
25cf6f3d | 388 | if (nValue > curBalance) |
b93173de PJ |
389 | throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds"); |
390 | ||
58c4c0bb | 391 | // Parse Zcash address |
b93173de PJ |
392 | CScript scriptPubKey = GetScriptForDestination(address); |
393 | ||
394 | // Create and send the transaction | |
395 | CReserveKey reservekey(pwalletMain); | |
396 | CAmount nFeeRequired; | |
25cf6f3d | 397 | std::string strError; |
292623ad CL |
398 | vector<CRecipient> vecSend; |
399 | int nChangePosRet = -1; | |
400 | CRecipient recipient = {scriptPubKey, nValue, fSubtractFeeFromAmount}; | |
401 | vecSend.push_back(recipient); | |
402 | if (!pwalletMain->CreateTransaction(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strError)) { | |
403 | if (!fSubtractFeeFromAmount && nValue + nFeeRequired > pwalletMain->GetBalance()) | |
404 | strError = strprintf("Error: This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds!", FormatMoney(nFeeRequired)); | |
405 | throw JSONRPCError(RPC_WALLET_ERROR, strError); | |
b93173de PJ |
406 | } |
407 | if (!pwalletMain->CommitTransaction(wtxNew, reservekey)) | |
408 | throw JSONRPCError(RPC_WALLET_ERROR, "Error: The transaction was rejected! This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here."); | |
b93173de PJ |
409 | } |
410 | ||
d014114d | 411 | UniValue sendtoaddress(const UniValue& params, bool fHelp) |
e3bc5698 | 412 | { |
b9fb692d | 413 | if (!EnsureWalletIsAvailable(fHelp)) |
9756b7bd | 414 | return NullUniValue; |
70454796 | 415 | |
292623ad | 416 | if (fHelp || params.size() < 2 || params.size() > 5) |
e3bc5698 | 417 | throw runtime_error( |
58c4c0bb | 418 | "sendtoaddress \"zcashaddress\" amount ( \"comment\" \"comment-to\" subtractfeefromamount )\n" |
e743678d | 419 | "\nSend an amount to a given address. The amount is a real and is rounded to the nearest 0.00000001\n" |
a6099ef3 | 420 | + HelpRequiringPassphrase() + |
421 | "\nArguments:\n" | |
c938fb1f | 422 | "1. \"zcashaddress\" (string, required) The Zcash address to send to.\n" |
091b2116 | 423 | "2. \"amount\" (numeric, required) The amount in " + CURRENCY_UNIT + " to send. eg 0.1\n" |
a6099ef3 | 424 | "3. \"comment\" (string, optional) A comment used to store what the transaction is for. \n" |
425 | " This is not part of the transaction, just kept in your wallet.\n" | |
426 | "4. \"comment-to\" (string, optional) A comment to store the name of the person or organization \n" | |
427 | " to which you're sending the transaction. This is not part of the \n" | |
428 | " transaction, just kept in your wallet.\n" | |
292623ad | 429 | "5. subtractfeefromamount (boolean, optional, default=false) The fee will be deducted from the amount being sent.\n" |
c1652849 | 430 | " The recipient will receive less Zcash than you enter in the amount field.\n" |
a6099ef3 | 431 | "\nResult:\n" |
b5ef85c7 | 432 | "\"transactionid\" (string) The transaction id.\n" |
a6099ef3 | 433 | "\nExamples:\n" |
70454796 JG |
434 | + HelpExampleCli("sendtoaddress", "\"t1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.1") |
435 | + HelpExampleCli("sendtoaddress", "\"t1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.1 \"donation\" \"seans outpost\"") | |
436 | + HelpExampleCli("sendtoaddress", "\"t1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.1 \"\" \"\" true") | |
437 | + HelpExampleRpc("sendtoaddress", "\"t1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\", 0.1, \"donation\", \"seans outpost\"") | |
a6099ef3 | 438 | ); |
e3bc5698 | 439 | |
4401b2d7 EL |
440 | LOCK2(cs_main, pwalletMain->cs_wallet); |
441 | ||
07444da1 PW |
442 | CTxDestination dest = DecodeDestination(params[0].get_str()); |
443 | if (!IsValidDestination(dest)) { | |
58c4c0bb | 444 | throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Zcash address"); |
07444da1 | 445 | } |
e3bc5698 JG |
446 | |
447 | // Amount | |
a372168e | 448 | CAmount nAmount = AmountFromValue(params[1]); |
e76a3849 WL |
449 | if (nAmount <= 0) |
450 | throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); | |
e3bc5698 JG |
451 | |
452 | // Wallet comments | |
453 | CWalletTx wtx; | |
ed21d5bd | 454 | if (params.size() > 2 && !params[2].isNull() && !params[2].get_str().empty()) |
e3bc5698 | 455 | wtx.mapValue["comment"] = params[2].get_str(); |
ed21d5bd | 456 | if (params.size() > 3 && !params[3].isNull() && !params[3].get_str().empty()) |
e3bc5698 JG |
457 | wtx.mapValue["to"] = params[3].get_str(); |
458 | ||
292623ad CL |
459 | bool fSubtractFeeFromAmount = false; |
460 | if (params.size() > 4) | |
461 | fSubtractFeeFromAmount = params[4].get_bool(); | |
462 | ||
f914c7a1 | 463 | EnsureWalletIsUnlocked(); |
e3bc5698 | 464 | |
07444da1 | 465 | SendMoney(dest, nAmount, fSubtractFeeFromAmount, wtx); |
e3bc5698 | 466 | |
805344dc | 467 | return wtx.GetHash().GetHex(); |
e3bc5698 JG |
468 | } |
469 | ||
d014114d | 470 | UniValue listaddressgroupings(const UniValue& params, bool fHelp) |
22dfd735 | 471 | { |
b9fb692d | 472 | if (!EnsureWalletIsAvailable(fHelp)) |
9756b7bd | 473 | return NullUniValue; |
70454796 | 474 | |
22dfd735 | 475 | if (fHelp) |
b1093efa GM |
476 | throw runtime_error( |
477 | "listaddressgroupings\n" | |
a6099ef3 | 478 | "\nLists groups of addresses which have had their common ownership\n" |
b1093efa | 479 | "made public by common use as inputs or as the resulting change\n" |
a6099ef3 | 480 | "in past transactions\n" |
481 | "\nResult:\n" | |
482 | "[\n" | |
483 | " [\n" | |
484 | " [\n" | |
58c4c0bb | 485 | " \"zcashaddress\", (string) The zcash address\n" |
091b2116 | 486 | " amount, (numeric) The amount in " + CURRENCY_UNIT + "\n" |
7b782f5b | 487 | " \"account\" (string, optional) The account (DEPRECATED)\n" |
a6099ef3 | 488 | " ]\n" |
489 | " ,...\n" | |
490 | " ]\n" | |
491 | " ,...\n" | |
492 | "]\n" | |
493 | "\nExamples:\n" | |
494 | + HelpExampleCli("listaddressgroupings", "") | |
495 | + HelpExampleRpc("listaddressgroupings", "") | |
496 | ); | |
22dfd735 | 497 | |
4401b2d7 EL |
498 | LOCK2(cs_main, pwalletMain->cs_wallet); |
499 | ||
38fc4b70 | 500 | UniValue jsonGroupings(UniValue::VARR); |
07444da1 PW |
501 | std::map<CTxDestination, CAmount> balances = pwalletMain->GetAddressBalances(); |
502 | for (const std::set<CTxDestination>& grouping : pwalletMain->GetAddressGroupings()) { | |
38fc4b70 | 503 | UniValue jsonGrouping(UniValue::VARR); |
07444da1 | 504 | for (const CTxDestination& address : grouping) |
22dfd735 | 505 | { |
38fc4b70 | 506 | UniValue addressInfo(UniValue::VARR); |
07444da1 | 507 | addressInfo.push_back(EncodeDestination(address)); |
22dfd735 | 508 | addressInfo.push_back(ValueFromAmount(balances[address])); |
509 | { | |
07444da1 PW |
510 | if (pwalletMain->mapAddressBook.find(address) != pwalletMain->mapAddressBook.end()) { |
511 | addressInfo.push_back(pwalletMain->mapAddressBook.find(address)->second.name); | |
512 | } | |
22dfd735 | 513 | } |
514 | jsonGrouping.push_back(addressInfo); | |
515 | } | |
516 | jsonGroupings.push_back(jsonGrouping); | |
517 | } | |
518 | return jsonGroupings; | |
519 | } | |
520 | ||
d014114d | 521 | UniValue signmessage(const UniValue& params, bool fHelp) |
e3bc5698 | 522 | { |
b9fb692d | 523 | if (!EnsureWalletIsAvailable(fHelp)) |
9756b7bd | 524 | return NullUniValue; |
70454796 | 525 | |
e3bc5698 JG |
526 | if (fHelp || params.size() != 2) |
527 | throw runtime_error( | |
30bf78ca JDL |
528 | "signmessage \"t-addr\" \"message\"\n" |
529 | "\nSign a message with the private key of a t-addr" | |
a6099ef3 | 530 | + HelpRequiringPassphrase() + "\n" |
531 | "\nArguments:\n" | |
30bf78ca | 532 | "1. \"t-addr\" (string, required) The transparent address to use for the private key.\n" |
a6099ef3 | 533 | "2. \"message\" (string, required) The message to create a signature of.\n" |
534 | "\nResult:\n" | |
535 | "\"signature\" (string) The signature of the message encoded in base 64\n" | |
536 | "\nExamples:\n" | |
537 | "\nUnlock the wallet for 30 seconds\n" | |
538 | + HelpExampleCli("walletpassphrase", "\"mypassphrase\" 30") + | |
539 | "\nCreate the signature\n" | |
70454796 | 540 | + HelpExampleCli("signmessage", "\"t14oHp2v54vfmdgQ3v3SNuQga8JKHTNi2a1\" \"my message\"") + |
a6099ef3 | 541 | "\nVerify the signature\n" |
70454796 | 542 | + HelpExampleCli("verifymessage", "\"t14oHp2v54vfmdgQ3v3SNuQga8JKHTNi2a1\" \"signature\" \"my message\"") + |
a6099ef3 | 543 | "\nAs json rpc\n" |
70454796 | 544 | + HelpExampleRpc("signmessage", "\"t14oHp2v54vfmdgQ3v3SNuQga8JKHTNi2a1\", \"my message\"") |
a6099ef3 | 545 | ); |
e3bc5698 | 546 | |
4401b2d7 EL |
547 | LOCK2(cs_main, pwalletMain->cs_wallet); |
548 | ||
e3bc5698 JG |
549 | EnsureWalletIsUnlocked(); |
550 | ||
551 | string strAddress = params[0].get_str(); | |
552 | string strMessage = params[1].get_str(); | |
553 | ||
07444da1 PW |
554 | CTxDestination dest = DecodeDestination(strAddress); |
555 | if (!IsValidDestination(dest)) { | |
738835d7 | 556 | throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address"); |
07444da1 | 557 | } |
e3bc5698 | 558 | |
07444da1 PW |
559 | const CKeyID *keyID = boost::get<CKeyID>(&dest); |
560 | if (!keyID) { | |
738835d7 | 561 | throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key"); |
07444da1 | 562 | } |
e3bc5698 JG |
563 | |
564 | CKey key; | |
07444da1 | 565 | if (!pwalletMain->GetKey(*keyID, key)) { |
738835d7 | 566 | throw JSONRPCError(RPC_WALLET_ERROR, "Private key not available"); |
07444da1 | 567 | } |
e3bc5698 | 568 | |
8980a509 | 569 | CHashWriter ss(SER_GETHASH, 0); |
e3bc5698 JG |
570 | ss << strMessageMagic; |
571 | ss << strMessage; | |
572 | ||
573 | vector<unsigned char> vchSig; | |
8980a509 | 574 | if (!key.SignCompact(ss.GetHash(), vchSig)) |
738835d7 | 575 | throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Sign failed"); |
e3bc5698 JG |
576 | |
577 | return EncodeBase64(&vchSig[0], vchSig.size()); | |
578 | } | |
579 | ||
d014114d | 580 | UniValue getreceivedbyaddress(const UniValue& params, bool fHelp) |
e3bc5698 | 581 | { |
b9fb692d | 582 | if (!EnsureWalletIsAvailable(fHelp)) |
9756b7bd | 583 | return NullUniValue; |
70454796 | 584 | |
e3bc5698 JG |
585 | if (fHelp || params.size() < 1 || params.size() > 2) |
586 | throw runtime_error( | |
58c4c0bb | 587 | "getreceivedbyaddress \"zcashaddress\" ( minconf )\n" |
c1652849 | 588 | "\nReturns the total amount received by the given Zcash address in transactions with at least minconf confirmations.\n" |
a6099ef3 | 589 | "\nArguments:\n" |
d2c1e4a8 | 590 | "1. \"zcashaddress\" (string, required) The Zcash address for transactions.\n" |
a6099ef3 | 591 | "2. minconf (numeric, optional, default=1) Only include transactions confirmed at least this many times.\n" |
592 | "\nResult:\n" | |
091b2116 | 593 | "amount (numeric) The total amount in " + CURRENCY_UNIT + " received at this address.\n" |
a6099ef3 | 594 | "\nExamples:\n" |
595 | "\nThe amount from transactions with at least 1 confirmation\n" | |
70454796 | 596 | + HelpExampleCli("getreceivedbyaddress", "\"t14oHp2v54vfmdgQ3v3SNuQga8JKHTNi2a1\"") + |
a6099ef3 | 597 | "\nThe amount including unconfirmed transactions, zero confirmations\n" |
70454796 | 598 | + HelpExampleCli("getreceivedbyaddress", "\"t14oHp2v54vfmdgQ3v3SNuQga8JKHTNi2a1\" 0") + |
c938fb1f | 599 | "\nThe amount with at least 6 confirmations, very safe\n" |
70454796 | 600 | + HelpExampleCli("getreceivedbyaddress", "\"t14oHp2v54vfmdgQ3v3SNuQga8JKHTNi2a1\" 6") + |
a6099ef3 | 601 | "\nAs a json rpc call\n" |
70454796 | 602 | + HelpExampleRpc("getreceivedbyaddress", "\"t14oHp2v54vfmdgQ3v3SNuQga8JKHTNi2a1\", 6") |
a6099ef3 | 603 | ); |
e3bc5698 | 604 | |
4401b2d7 EL |
605 | LOCK2(cs_main, pwalletMain->cs_wallet); |
606 | ||
e3bc5698 | 607 | // Bitcoin address |
07444da1 PW |
608 | CTxDestination dest = DecodeDestination(params[0].get_str()); |
609 | if (!IsValidDestination(dest)) { | |
58c4c0bb | 610 | throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Zcash address"); |
07444da1 PW |
611 | } |
612 | CScript scriptPubKey = GetScriptForDestination(dest); | |
613 | if (!IsMine(*pwalletMain, scriptPubKey)) { | |
b37b4b2f | 614 | return ValueFromAmount(0); |
07444da1 | 615 | } |
e3bc5698 JG |
616 | |
617 | // Minimum confirmations | |
618 | int nMinDepth = 1; | |
619 | if (params.size() > 1) | |
620 | nMinDepth = params[1].get_int(); | |
621 | ||
622 | // Tally | |
a372168e | 623 | CAmount nAmount = 0; |
e3bc5698 JG |
624 | for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) |
625 | { | |
626 | const CWalletTx& wtx = (*it).second; | |
75a4d512 | 627 | if (wtx.IsCoinBase() || !CheckFinalTx(wtx)) |
e3bc5698 JG |
628 | continue; |
629 | ||
630 | BOOST_FOREACH(const CTxOut& txout, wtx.vout) | |
631 | if (txout.scriptPubKey == scriptPubKey) | |
632 | if (wtx.GetDepthInMainChain() >= nMinDepth) | |
633 | nAmount += txout.nValue; | |
634 | } | |
635 | ||
636 | return ValueFromAmount(nAmount); | |
637 | } | |
638 | ||
639 | ||
d014114d | 640 | UniValue getreceivedbyaccount(const UniValue& params, bool fHelp) |
e3bc5698 | 641 | { |
b9fb692d | 642 | if (!EnsureWalletIsAvailable(fHelp)) |
9756b7bd | 643 | return NullUniValue; |
70454796 | 644 | |
e3bc5698 JG |
645 | if (fHelp || params.size() < 1 || params.size() > 2) |
646 | throw runtime_error( | |
a6099ef3 | 647 | "getreceivedbyaccount \"account\" ( minconf )\n" |
7b782f5b | 648 | "\nDEPRECATED. Returns the total amount received by addresses with <account> in transactions with at least [minconf] confirmations.\n" |
a6099ef3 | 649 | "\nArguments:\n" |
3c31eb24 | 650 | "1. \"account\" (string, required) MUST be set to the empty string \"\" to represent the default account. Passing any other string will result in an error.\n" |
a6099ef3 | 651 | "2. minconf (numeric, optional, default=1) Only include transactions confirmed at least this many times.\n" |
652 | "\nResult:\n" | |
091b2116 | 653 | "amount (numeric) The total amount in " + CURRENCY_UNIT + " received for this account.\n" |
a6099ef3 | 654 | "\nExamples:\n" |
655 | "\nAmount received by the default account with at least 1 confirmation\n" | |
656 | + HelpExampleCli("getreceivedbyaccount", "\"\"") + | |
657 | "\nAmount received at the tabby account including unconfirmed amounts with zero confirmations\n" | |
658 | + HelpExampleCli("getreceivedbyaccount", "\"tabby\" 0") + | |
659 | "\nThe amount with at least 6 confirmation, very safe\n" | |
660 | + HelpExampleCli("getreceivedbyaccount", "\"tabby\" 6") + | |
661 | "\nAs a json rpc call\n" | |
662 | + HelpExampleRpc("getreceivedbyaccount", "\"tabby\", 6") | |
663 | ); | |
e3bc5698 | 664 | |
4401b2d7 EL |
665 | LOCK2(cs_main, pwalletMain->cs_wallet); |
666 | ||
e3bc5698 JG |
667 | // Minimum confirmations |
668 | int nMinDepth = 1; | |
669 | if (params.size() > 1) | |
670 | nMinDepth = params[1].get_int(); | |
671 | ||
672 | // Get the set of pub keys assigned to account | |
673 | string strAccount = AccountFromValue(params[0]); | |
3624356e | 674 | set<CTxDestination> setAddress = pwalletMain->GetAccountAddresses(strAccount); |
e3bc5698 JG |
675 | |
676 | // Tally | |
a372168e | 677 | CAmount nAmount = 0; |
e3bc5698 JG |
678 | for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) |
679 | { | |
680 | const CWalletTx& wtx = (*it).second; | |
75a4d512 | 681 | if (wtx.IsCoinBase() || !CheckFinalTx(wtx)) |
e3bc5698 JG |
682 | continue; |
683 | ||
684 | BOOST_FOREACH(const CTxOut& txout, wtx.vout) | |
685 | { | |
686 | CTxDestination address; | |
687 | if (ExtractDestination(txout.scriptPubKey, address) && IsMine(*pwalletMain, address) && setAddress.count(address)) | |
688 | if (wtx.GetDepthInMainChain() >= nMinDepth) | |
689 | nAmount += txout.nValue; | |
690 | } | |
691 | } | |
692 | ||
b37b4b2f | 693 | return ValueFromAmount(nAmount); |
e3bc5698 JG |
694 | } |
695 | ||
696 | ||
a372168e | 697 | CAmount GetAccountBalance(CWalletDB& walletdb, const string& strAccount, int nMinDepth, const isminefilter& filter) |
e3bc5698 | 698 | { |
a372168e | 699 | CAmount nBalance = 0; |
e3bc5698 JG |
700 | |
701 | // Tally wallet transactions | |
702 | for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) | |
703 | { | |
704 | const CWalletTx& wtx = (*it).second; | |
75a4d512 | 705 | if (!CheckFinalTx(wtx) || wtx.GetBlocksToMaturity() > 0 || wtx.GetDepthInMainChain() < 0) |
e3bc5698 JG |
706 | continue; |
707 | ||
a372168e | 708 | CAmount nReceived, nSent, nFee; |
d4640d7d | 709 | wtx.GetAccountAmounts(strAccount, nReceived, nSent, nFee, filter); |
e3bc5698 JG |
710 | |
711 | if (nReceived != 0 && wtx.GetDepthInMainChain() >= nMinDepth) | |
712 | nBalance += nReceived; | |
e07c8e91 | 713 | nBalance -= nSent + nFee; |
e3bc5698 JG |
714 | } |
715 | ||
716 | // Tally internal accounting entries | |
717 | nBalance += walletdb.GetAccountCreditDebit(strAccount); | |
718 | ||
719 | return nBalance; | |
720 | } | |
721 | ||
a372168e | 722 | CAmount GetAccountBalance(const string& strAccount, int nMinDepth, const isminefilter& filter) |
e3bc5698 JG |
723 | { |
724 | CWalletDB walletdb(pwalletMain->strWalletFile); | |
d4640d7d | 725 | return GetAccountBalance(walletdb, strAccount, nMinDepth, filter); |
e3bc5698 JG |
726 | } |
727 | ||
728 | ||
d014114d | 729 | UniValue getbalance(const UniValue& params, bool fHelp) |
e3bc5698 | 730 | { |
b9fb692d | 731 | if (!EnsureWalletIsAvailable(fHelp)) |
9756b7bd | 732 | return NullUniValue; |
70454796 | 733 | |
d4640d7d | 734 | if (fHelp || params.size() > 3) |
e3bc5698 | 735 | throw runtime_error( |
d4640d7d | 736 | "getbalance ( \"account\" minconf includeWatchonly )\n" |
3c31eb24 | 737 | "\nReturns the server's total available balance.\n" |
a6099ef3 | 738 | "\nArguments:\n" |
715e5bbe | 739 | "1. \"account\" (string, optional) DEPRECATED. If provided, it MUST be set to the empty string \"\" or to the string \"*\", either of which will give the total available balance. Passing any other string will result in an error.\n" |
a6099ef3 | 740 | "2. minconf (numeric, optional, default=1) Only include transactions confirmed at least this many times.\n" |
d4640d7d | 741 | "3. includeWatchonly (bool, optional, default=false) Also include balance in watchonly addresses (see 'importaddress')\n" |
a6099ef3 | 742 | "\nResult:\n" |
091b2116 | 743 | "amount (numeric) The total amount in " + CURRENCY_UNIT + " received for this account.\n" |
a6099ef3 | 744 | "\nExamples:\n" |
7b782f5b | 745 | "\nThe total amount in the wallet\n" |
a6099ef3 | 746 | + HelpExampleCli("getbalance", "") + |
7b782f5b | 747 | "\nThe total amount in the wallet at least 5 blocks confirmed\n" |
3cf1f436 | 748 | + HelpExampleCli("getbalance", "\"*\" 6") + |
a6099ef3 | 749 | "\nAs a json rpc call\n" |
7b782f5b | 750 | + HelpExampleRpc("getbalance", "\"*\", 6") |
a6099ef3 | 751 | ); |
e3bc5698 | 752 | |
4401b2d7 EL |
753 | LOCK2(cs_main, pwalletMain->cs_wallet); |
754 | ||
e3bc5698 JG |
755 | if (params.size() == 0) |
756 | return ValueFromAmount(pwalletMain->GetBalance()); | |
757 | ||
758 | int nMinDepth = 1; | |
759 | if (params.size() > 1) | |
760 | nMinDepth = params[1].get_int(); | |
a3e192a3 | 761 | isminefilter filter = ISMINE_SPENDABLE; |
a5c6c5d6 J |
762 | if(params.size() > 2) |
763 | if(params[2].get_bool()) | |
a3e192a3 | 764 | filter = filter | ISMINE_WATCH_ONLY; |
e3bc5698 JG |
765 | |
766 | if (params[0].get_str() == "*") { | |
767 | // Calculate total balance a different way from GetBalance() | |
768 | // (GetBalance() sums up all unspent TxOuts) | |
c9fd9078 | 769 | // getbalance and "getbalance * 1 true" should return the same number |
a372168e | 770 | CAmount nBalance = 0; |
e3bc5698 JG |
771 | for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) |
772 | { | |
773 | const CWalletTx& wtx = (*it).second; | |
c9fd9078 | 774 | if (!CheckFinalTx(wtx) || wtx.GetBlocksToMaturity() > 0 || wtx.GetDepthInMainChain() < 0) |
e3bc5698 JG |
775 | continue; |
776 | ||
a372168e | 777 | CAmount allFee; |
e3bc5698 | 778 | string strSentAccount; |
1b4568cb CL |
779 | list<COutputEntry> listReceived; |
780 | list<COutputEntry> listSent; | |
d4640d7d | 781 | wtx.GetAmounts(listReceived, listSent, allFee, strSentAccount, filter); |
e3bc5698 JG |
782 | if (wtx.GetDepthInMainChain() >= nMinDepth) |
783 | { | |
1b4568cb CL |
784 | BOOST_FOREACH(const COutputEntry& r, listReceived) |
785 | nBalance += r.amount; | |
e3bc5698 | 786 | } |
1b4568cb CL |
787 | BOOST_FOREACH(const COutputEntry& s, listSent) |
788 | nBalance -= s.amount; | |
e3bc5698 | 789 | nBalance -= allFee; |
e3bc5698 JG |
790 | } |
791 | return ValueFromAmount(nBalance); | |
792 | } | |
793 | ||
794 | string strAccount = AccountFromValue(params[0]); | |
795 | ||
a372168e | 796 | CAmount nBalance = GetAccountBalance(strAccount, nMinDepth, filter); |
e3bc5698 JG |
797 | |
798 | return ValueFromAmount(nBalance); | |
799 | } | |
800 | ||
d014114d | 801 | UniValue getunconfirmedbalance(const UniValue ¶ms, bool fHelp) |
6027b460 | 802 | { |
b9fb692d | 803 | if (!EnsureWalletIsAvailable(fHelp)) |
9756b7bd | 804 | return NullUniValue; |
70454796 | 805 | |
6027b460 MB |
806 | if (fHelp || params.size() > 0) |
807 | throw runtime_error( | |
808 | "getunconfirmedbalance\n" | |
809 | "Returns the server's total unconfirmed balance\n"); | |
4401b2d7 EL |
810 | |
811 | LOCK2(cs_main, pwalletMain->cs_wallet); | |
812 | ||
6027b460 MB |
813 | return ValueFromAmount(pwalletMain->GetUnconfirmedBalance()); |
814 | } | |
815 | ||
e3bc5698 | 816 | |
d014114d | 817 | UniValue movecmd(const UniValue& params, bool fHelp) |
e3bc5698 | 818 | { |
b9fb692d | 819 | if (!EnsureWalletIsAvailable(fHelp)) |
9756b7bd | 820 | return NullUniValue; |
70454796 | 821 | |
e3bc5698 JG |
822 | if (fHelp || params.size() < 3 || params.size() > 5) |
823 | throw runtime_error( | |
a6099ef3 | 824 | "move \"fromaccount\" \"toaccount\" amount ( minconf \"comment\" )\n" |
7b782f5b | 825 | "\nDEPRECATED. Move a specified amount from one account in your wallet to another.\n" |
a6099ef3 | 826 | "\nArguments:\n" |
3c31eb24 JG |
827 | "1. \"fromaccount\" (string, required) MUST be set to the empty string \"\" to represent the default account. Passing any other string will result in an error.\n" |
828 | "2. \"toaccount\" (string, required) MUST be set to the empty string \"\" to represent the default account. Passing any other string will result in an error.\n" | |
091b2116 RN |
829 | "3. amount (numeric) Quantity of " + CURRENCY_UNIT + " to move between accounts.\n" |
830 | "4. minconf (numeric, optional, default=1) Only use funds with at least this many confirmations.\n" | |
831 | "5. \"comment\" (string, optional) An optional comment, stored in the wallet only.\n" | |
a6099ef3 | 832 | "\nResult:\n" |
45bfa137 | 833 | "true|false (boolean) true if successful.\n" |
a6099ef3 | 834 | "\nExamples:\n" |
091b2116 | 835 | "\nMove 0.01 " + CURRENCY_UNIT + " from the default account to the account named tabby\n" |
a6099ef3 | 836 | + HelpExampleCli("move", "\"\" \"tabby\" 0.01") + |
091b2116 | 837 | "\nMove 0.01 " + CURRENCY_UNIT + " timotei to akiko with a comment and funds have 6 confirmations\n" |
a6099ef3 | 838 | + HelpExampleCli("move", "\"timotei\" \"akiko\" 0.01 6 \"happy birthday!\"") + |
839 | "\nAs a json rpc call\n" | |
840 | + HelpExampleRpc("move", "\"timotei\", \"akiko\", 0.01, 6, \"happy birthday!\"") | |
841 | ); | |
e3bc5698 | 842 | |
4401b2d7 EL |
843 | LOCK2(cs_main, pwalletMain->cs_wallet); |
844 | ||
e3bc5698 JG |
845 | string strFrom = AccountFromValue(params[0]); |
846 | string strTo = AccountFromValue(params[1]); | |
a372168e | 847 | CAmount nAmount = AmountFromValue(params[2]); |
e76a3849 WL |
848 | if (nAmount <= 0) |
849 | throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); | |
e3bc5698 JG |
850 | if (params.size() > 3) |
851 | // unused parameter, used to be nMinDepth, keep type-checking it though | |
852 | (void)params[3].get_int(); | |
853 | string strComment; | |
854 | if (params.size() > 4) | |
855 | strComment = params[4].get_str(); | |
856 | ||
857 | CWalletDB walletdb(pwalletMain->strWalletFile); | |
858 | if (!walletdb.TxnBegin()) | |
738835d7 | 859 | throw JSONRPCError(RPC_DATABASE_ERROR, "database error"); |
e3bc5698 | 860 | |
51ed9ec9 | 861 | int64_t nNow = GetAdjustedTime(); |
e3bc5698 JG |
862 | |
863 | // Debit | |
864 | CAccountingEntry debit; | |
4291e8fe | 865 | debit.nOrderPos = pwalletMain->IncOrderPosNext(&walletdb); |
e3bc5698 JG |
866 | debit.strAccount = strFrom; |
867 | debit.nCreditDebit = -nAmount; | |
868 | debit.nTime = nNow; | |
869 | debit.strOtherAccount = strTo; | |
870 | debit.strComment = strComment; | |
871 | walletdb.WriteAccountingEntry(debit); | |
872 | ||
873 | // Credit | |
874 | CAccountingEntry credit; | |
4291e8fe | 875 | credit.nOrderPos = pwalletMain->IncOrderPosNext(&walletdb); |
e3bc5698 JG |
876 | credit.strAccount = strTo; |
877 | credit.nCreditDebit = nAmount; | |
878 | credit.nTime = nNow; | |
879 | credit.strOtherAccount = strFrom; | |
880 | credit.strComment = strComment; | |
881 | walletdb.WriteAccountingEntry(credit); | |
882 | ||
883 | if (!walletdb.TxnCommit()) | |
738835d7 | 884 | throw JSONRPCError(RPC_DATABASE_ERROR, "database error"); |
e3bc5698 JG |
885 | |
886 | return true; | |
887 | } | |
888 | ||
889 | ||
d014114d | 890 | UniValue sendfrom(const UniValue& params, bool fHelp) |
e3bc5698 | 891 | { |
b9fb692d | 892 | if (!EnsureWalletIsAvailable(fHelp)) |
9756b7bd | 893 | return NullUniValue; |
70454796 | 894 | |
e3bc5698 JG |
895 | if (fHelp || params.size() < 3 || params.size() > 6) |
896 | throw runtime_error( | |
58c4c0bb | 897 | "sendfrom \"fromaccount\" \"tozcashaddress\" amount ( minconf \"comment\" \"comment-to\" )\n" |
d2c1e4a8 | 898 | "\nDEPRECATED (use sendtoaddress). Sent an amount from an account to a Zcash address.\n" |
a6099ef3 | 899 | "The amount is a real and is rounded to the nearest 0.00000001." |
900 | + HelpRequiringPassphrase() + "\n" | |
901 | "\nArguments:\n" | |
3c31eb24 | 902 | "1. \"fromaccount\" (string, required) MUST be set to the empty string \"\" to represent the default account. Passing any other string will result in an error.\n" |
c938fb1f | 903 | "2. \"tozcashaddress\" (string, required) The Zcash address to send funds to.\n" |
091b2116 | 904 | "3. amount (numeric, required) The amount in " + CURRENCY_UNIT + " (transaction fee is added on top).\n" |
a6099ef3 | 905 | "4. minconf (numeric, optional, default=1) Only use funds with at least this many confirmations.\n" |
906 | "5. \"comment\" (string, optional) A comment used to store what the transaction is for. \n" | |
907 | " This is not part of the transaction, just kept in your wallet.\n" | |
908 | "6. \"comment-to\" (string, optional) An optional comment to store the name of the person or organization \n" | |
909 | " to which you're sending the transaction. This is not part of the transaction, \n" | |
910 | " it is just kept in your wallet.\n" | |
911 | "\nResult:\n" | |
b5ef85c7 | 912 | "\"transactionid\" (string) The transaction id.\n" |
a6099ef3 | 913 | "\nExamples:\n" |
091b2116 | 914 | "\nSend 0.01 " + CURRENCY_UNIT + " from the default account to the address, must have at least 1 confirmation\n" |
70454796 | 915 | + HelpExampleCli("sendfrom", "\"\" \"t1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.01") + |
a6099ef3 | 916 | "\nSend 0.01 from the tabby account to the given address, funds must have at least 6 confirmations\n" |
70454796 | 917 | + HelpExampleCli("sendfrom", "\"tabby\" \"t1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.01 6 \"donation\" \"seans outpost\"") + |
a6099ef3 | 918 | "\nAs a json rpc call\n" |
70454796 | 919 | + HelpExampleRpc("sendfrom", "\"tabby\", \"t1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\", 0.01, 6, \"donation\", \"seans outpost\"") |
a6099ef3 | 920 | ); |
e3bc5698 | 921 | |
4401b2d7 EL |
922 | LOCK2(cs_main, pwalletMain->cs_wallet); |
923 | ||
07444da1 PW |
924 | std::string strAccount = AccountFromValue(params[0]); |
925 | CTxDestination dest = DecodeDestination(params[1].get_str()); | |
926 | if (!IsValidDestination(dest)) { | |
58c4c0bb | 927 | throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Zcash address"); |
07444da1 | 928 | } |
a372168e | 929 | CAmount nAmount = AmountFromValue(params[2]); |
e76a3849 WL |
930 | if (nAmount <= 0) |
931 | throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); | |
e3bc5698 JG |
932 | int nMinDepth = 1; |
933 | if (params.size() > 3) | |
934 | nMinDepth = params[3].get_int(); | |
935 | ||
936 | CWalletTx wtx; | |
937 | wtx.strFromAccount = strAccount; | |
ed21d5bd | 938 | if (params.size() > 4 && !params[4].isNull() && !params[4].get_str().empty()) |
e3bc5698 | 939 | wtx.mapValue["comment"] = params[4].get_str(); |
ed21d5bd | 940 | if (params.size() > 5 && !params[5].isNull() && !params[5].get_str().empty()) |
e3bc5698 JG |
941 | wtx.mapValue["to"] = params[5].get_str(); |
942 | ||
943 | EnsureWalletIsUnlocked(); | |
944 | ||
945 | // Check funds | |
a372168e | 946 | CAmount nBalance = GetAccountBalance(strAccount, nMinDepth, ISMINE_SPENDABLE); |
e3bc5698 | 947 | if (nAmount > nBalance) |
738835d7 | 948 | throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Account has insufficient funds"); |
e3bc5698 | 949 | |
07444da1 | 950 | SendMoney(dest, nAmount, false, wtx); |
e3bc5698 | 951 | |
805344dc | 952 | return wtx.GetHash().GetHex(); |
e3bc5698 JG |
953 | } |
954 | ||
955 | ||
d014114d | 956 | UniValue sendmany(const UniValue& params, bool fHelp) |
e3bc5698 | 957 | { |
b9fb692d | 958 | if (!EnsureWalletIsAvailable(fHelp)) |
9756b7bd | 959 | return NullUniValue; |
70454796 | 960 | |
292623ad | 961 | if (fHelp || params.size() < 2 || params.size() > 5) |
e3bc5698 | 962 | throw runtime_error( |
40a75733 | 963 | "sendmany \"fromaccount\" {\"address\":amount,...} ( minconf \"comment\" [\"address\",...] )\n" |
92b42d28 | 964 | "\nSend multiple times. Amounts are decimal numbers with at most 8 digits of precision." |
a6099ef3 | 965 | + HelpRequiringPassphrase() + "\n" |
966 | "\nArguments:\n" | |
3c31eb24 | 967 | "1. \"fromaccount\" (string, required) MUST be set to the empty string \"\" to represent the default account. Passing any other string will result in an error.\n" |
a6099ef3 | 968 | "2. \"amounts\" (string, required) A json object with addresses and amounts\n" |
969 | " {\n" | |
c938fb1f | 970 | " \"address\":amount (numeric) The Zcash address is the key, the numeric amount in " + CURRENCY_UNIT + " is the value\n" |
a6099ef3 | 971 | " ,...\n" |
972 | " }\n" | |
973 | "3. minconf (numeric, optional, default=1) Only use the balance confirmed at least this many times.\n" | |
974 | "4. \"comment\" (string, optional) A comment\n" | |
40a75733 | 975 | "5. subtractfeefromamount (string, optional) A json array with addresses.\n" |
292623ad | 976 | " The fee will be equally deducted from the amount of each selected address.\n" |
c1652849 | 977 | " Those recipients will receive less Zcash than you enter in their corresponding amount field.\n" |
40a75733 LD |
978 | " If no addresses are specified here, the sender pays the fee.\n" |
979 | " [\n" | |
980 | " \"address\" (string) Subtract fee from this address\n" | |
292623ad | 981 | " ,...\n" |
40a75733 | 982 | " ]\n" |
a6099ef3 | 983 | "\nResult:\n" |
984 | "\"transactionid\" (string) The transaction id for the send. Only 1 transaction is created regardless of \n" | |
b5ef85c7 | 985 | " the number of addresses.\n" |
a6099ef3 | 986 | "\nExamples:\n" |
987 | "\nSend two amounts to two different addresses:\n" | |
70454796 | 988 | + HelpExampleCli("sendmany", "\"\" \"{\\\"t14oHp2v54vfmdgQ3v3SNuQga8JKHTNi2a1\\\":0.01,\\\"t1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\"") + |
a6099ef3 | 989 | "\nSend two amounts to two different addresses setting the confirmation and comment:\n" |
70454796 | 990 | + HelpExampleCli("sendmany", "\"\" \"{\\\"t14oHp2v54vfmdgQ3v3SNuQga8JKHTNi2a1\\\":0.01,\\\"t1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\" 6 \"testing\"") + |
292623ad | 991 | "\nSend two amounts to two different addresses, subtract fee from amount:\n" |
70454796 | 992 | + HelpExampleCli("sendmany", "\"\" \"{\\\"t14oHp2v54vfmdgQ3v3SNuQga8JKHTNi2a1\\\":0.01,\\\"t1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\" 1 \"\" \"[\\\"t14oHp2v54vfmdgQ3v3SNuQga8JKHTNi2a1\\\",\\\"t1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\"]\"") + |
a6099ef3 | 993 | "\nAs a json rpc call\n" |
70454796 | 994 | + HelpExampleRpc("sendmany", "\"\", \"{\\\"t14oHp2v54vfmdgQ3v3SNuQga8JKHTNi2a1\\\":0.01,\\\"t1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\", 6, \"testing\"") |
a6099ef3 | 995 | ); |
e3bc5698 | 996 | |
4401b2d7 EL |
997 | LOCK2(cs_main, pwalletMain->cs_wallet); |
998 | ||
e3bc5698 | 999 | string strAccount = AccountFromValue(params[0]); |
851f58f9 | 1000 | UniValue sendTo = params[1].get_obj(); |
e3bc5698 JG |
1001 | int nMinDepth = 1; |
1002 | if (params.size() > 2) | |
1003 | nMinDepth = params[2].get_int(); | |
1004 | ||
1005 | CWalletTx wtx; | |
1006 | wtx.strFromAccount = strAccount; | |
ed21d5bd | 1007 | if (params.size() > 3 && !params[3].isNull() && !params[3].get_str().empty()) |
e3bc5698 JG |
1008 | wtx.mapValue["comment"] = params[3].get_str(); |
1009 | ||
38fc4b70 | 1010 | UniValue subtractFeeFromAmount(UniValue::VARR); |
292623ad | 1011 | if (params.size() > 4) |
40a75733 | 1012 | subtractFeeFromAmount = params[4].get_array(); |
292623ad | 1013 | |
07444da1 PW |
1014 | std::set<CTxDestination> destinations; |
1015 | std::vector<CRecipient> vecSend; | |
e3bc5698 | 1016 | |
a372168e | 1017 | CAmount totalAmount = 0; |
07444da1 PW |
1018 | std::vector<std::string> keys = sendTo.getKeys(); |
1019 | for (const std::string& name_ : keys) { | |
1020 | CTxDestination dest = DecodeDestination(name_); | |
1021 | if (!IsValidDestination(dest)) { | |
1022 | throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Zcash address: ") + name_); | |
1023 | } | |
e3bc5698 | 1024 | |
07444da1 PW |
1025 | if (destinations.count(dest)) { |
1026 | throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated address: ") + name_); | |
1027 | } | |
1028 | destinations.insert(dest); | |
e3bc5698 | 1029 | |
07444da1 | 1030 | CScript scriptPubKey = GetScriptForDestination(dest); |
ed21d5bd | 1031 | CAmount nAmount = AmountFromValue(sendTo[name_]); |
e76a3849 WL |
1032 | if (nAmount <= 0) |
1033 | throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); | |
e3bc5698 JG |
1034 | totalAmount += nAmount; |
1035 | ||
292623ad | 1036 | bool fSubtractFeeFromAmount = false; |
cc71666a | 1037 | for (size_t idx = 0; idx < subtractFeeFromAmount.size(); idx++) { |
d014114d | 1038 | const UniValue& addr = subtractFeeFromAmount[idx]; |
9756b7bd | 1039 | if (addr.get_str() == name_) |
292623ad | 1040 | fSubtractFeeFromAmount = true; |
9756b7bd | 1041 | } |
292623ad CL |
1042 | |
1043 | CRecipient recipient = {scriptPubKey, nAmount, fSubtractFeeFromAmount}; | |
1044 | vecSend.push_back(recipient); | |
e3bc5698 JG |
1045 | } |
1046 | ||
1047 | EnsureWalletIsUnlocked(); | |
1048 | ||
1049 | // Check funds | |
a372168e | 1050 | CAmount nBalance = GetAccountBalance(strAccount, nMinDepth, ISMINE_SPENDABLE); |
e3bc5698 | 1051 | if (totalAmount > nBalance) |
738835d7 | 1052 | throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Account has insufficient funds"); |
e3bc5698 JG |
1053 | |
1054 | // Send | |
1055 | CReserveKey keyChange(pwalletMain); | |
a372168e | 1056 | CAmount nFeeRequired = 0; |
292623ad | 1057 | int nChangePosRet = -1; |
1f00f4e9 | 1058 | string strFailReason; |
292623ad | 1059 | bool fCreated = pwalletMain->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, nChangePosRet, strFailReason); |
e3bc5698 | 1060 | if (!fCreated) |
1f00f4e9 | 1061 | throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason); |
e3bc5698 | 1062 | if (!pwalletMain->CommitTransaction(wtx, keyChange)) |
738835d7 | 1063 | throw JSONRPCError(RPC_WALLET_ERROR, "Transaction commit failed"); |
e3bc5698 | 1064 | |
805344dc | 1065 | return wtx.GetHash().GetHex(); |
e3bc5698 JG |
1066 | } |
1067 | ||
4b184205 | 1068 | // Defined in rpc/misc.cpp |
d014114d | 1069 | extern CScript _createmultisig_redeemScript(const UniValue& params); |
34226be7 | 1070 | |
d014114d | 1071 | UniValue addmultisigaddress(const UniValue& params, bool fHelp) |
34226be7 | 1072 | { |
b9fb692d | 1073 | if (!EnsureWalletIsAvailable(fHelp)) |
9756b7bd | 1074 | return NullUniValue; |
70454796 | 1075 | |
34226be7 GA |
1076 | if (fHelp || params.size() < 2 || params.size() > 3) |
1077 | { | |
a6099ef3 | 1078 | string msg = "addmultisigaddress nrequired [\"key\",...] ( \"account\" )\n" |
1079 | "\nAdd a nrequired-to-sign multisignature address to the wallet.\n" | |
58c4c0bb | 1080 | "Each key is a Zcash address or hex-encoded public key.\n" |
7b782f5b | 1081 | "If 'account' is specified (DEPRECATED), assign address to that account.\n" |
a6099ef3 | 1082 | |
1083 | "\nArguments:\n" | |
1084 | "1. nrequired (numeric, required) The number of required signatures out of the n keys or addresses.\n" | |
d2c1e4a8 | 1085 | "2. \"keysobject\" (string, required) A json array of Zcash addresses or hex-encoded public keys\n" |
a6099ef3 | 1086 | " [\n" |
d2c1e4a8 | 1087 | " \"address\" (string) Zcash address or hex-encoded public key\n" |
a6099ef3 | 1088 | " ...,\n" |
1089 | " ]\n" | |
3c31eb24 | 1090 | "3. \"account\" (string, optional) DEPRECATED. If provided, MUST be set to the empty string \"\" to represent the default account. Passing any other string will result in an error.\n" |
a6099ef3 | 1091 | |
1092 | "\nResult:\n" | |
d2c1e4a8 | 1093 | "\"zcashaddress\" (string) A Zcash address associated with the keys.\n" |
a6099ef3 | 1094 | |
1095 | "\nExamples:\n" | |
1096 | "\nAdd a multisig address from 2 addresses\n" | |
70454796 | 1097 | + HelpExampleCli("addmultisigaddress", "2 \"[\\\"t16sSauSf5pF2UkUwvKGq4qjNRzBZYqgEL5\\\",\\\"t171sgjn4YtPu27adkKGrdDwzRTxnRkBfKV\\\"]\"") + |
a6099ef3 | 1098 | "\nAs json rpc call\n" |
70454796 | 1099 | + HelpExampleRpc("addmultisigaddress", "2, \"[\\\"t16sSauSf5pF2UkUwvKGq4qjNRzBZYqgEL5\\\",\\\"t171sgjn4YtPu27adkKGrdDwzRTxnRkBfKV\\\"]\"") |
a6099ef3 | 1100 | ; |
34226be7 GA |
1101 | throw runtime_error(msg); |
1102 | } | |
1103 | ||
4401b2d7 EL |
1104 | LOCK2(cs_main, pwalletMain->cs_wallet); |
1105 | ||
34226be7 GA |
1106 | string strAccount; |
1107 | if (params.size() > 2) | |
1108 | strAccount = AccountFromValue(params[2]); | |
e3bc5698 JG |
1109 | |
1110 | // Construct using pay-to-script-hash: | |
787ee0c9 | 1111 | CScript inner = _createmultisig_redeemScript(params); |
066e2a14 | 1112 | CScriptID innerID(inner); |
e3bc5698 JG |
1113 | pwalletMain->AddCScript(inner); |
1114 | ||
a41d5fe0 | 1115 | pwalletMain->SetAddressBook(innerID, strAccount, "send"); |
07444da1 | 1116 | return EncodeDestination(innerID); |
e3bc5698 JG |
1117 | } |
1118 | ||
1119 | ||
1120 | struct tallyitem | |
1121 | { | |
a372168e | 1122 | CAmount nAmount; |
e3bc5698 | 1123 | int nConf; |
62c9b115 | 1124 | vector<uint256> txids; |
0fa2f889 | 1125 | bool fIsWatchonly; |
e3bc5698 JG |
1126 | tallyitem() |
1127 | { | |
1128 | nAmount = 0; | |
1129 | nConf = std::numeric_limits<int>::max(); | |
0fa2f889 | 1130 | fIsWatchonly = false; |
e3bc5698 JG |
1131 | } |
1132 | }; | |
1133 | ||
d014114d | 1134 | UniValue ListReceived(const UniValue& params, bool fByAccounts) |
e3bc5698 JG |
1135 | { |
1136 | // Minimum confirmations | |
1137 | int nMinDepth = 1; | |
1138 | if (params.size() > 0) | |
1139 | nMinDepth = params[0].get_int(); | |
1140 | ||
1141 | // Whether to include empty accounts | |
1142 | bool fIncludeEmpty = false; | |
1143 | if (params.size() > 1) | |
1144 | fIncludeEmpty = params[1].get_bool(); | |
1145 | ||
a3e192a3 | 1146 | isminefilter filter = ISMINE_SPENDABLE; |
0fa2f889 J |
1147 | if(params.size() > 2) |
1148 | if(params[2].get_bool()) | |
a3e192a3 | 1149 | filter = filter | ISMINE_WATCH_ONLY; |
0fa2f889 | 1150 | |
e3bc5698 | 1151 | // Tally |
07444da1 PW |
1152 | std::map<CTxDestination, tallyitem> mapTally; |
1153 | for (const std::pair<uint256, CWalletTx>& pairWtx : pwalletMain->mapWallet) { | |
1154 | const CWalletTx& wtx = pairWtx.second; | |
e3bc5698 | 1155 | |
75a4d512 | 1156 | if (wtx.IsCoinBase() || !CheckFinalTx(wtx)) |
e3bc5698 JG |
1157 | continue; |
1158 | ||
1159 | int nDepth = wtx.GetDepthInMainChain(); | |
1160 | if (nDepth < nMinDepth) | |
1161 | continue; | |
1162 | ||
1163 | BOOST_FOREACH(const CTxOut& txout, wtx.vout) | |
1164 | { | |
1165 | CTxDestination address; | |
0fa2f889 J |
1166 | if (!ExtractDestination(txout.scriptPubKey, address)) |
1167 | continue; | |
1168 | ||
1169 | isminefilter mine = IsMine(*pwalletMain, address); | |
f28707a8 | 1170 | if(!(mine & filter)) |
e3bc5698 JG |
1171 | continue; |
1172 | ||
1173 | tallyitem& item = mapTally[address]; | |
1174 | item.nAmount += txout.nValue; | |
1175 | item.nConf = min(item.nConf, nDepth); | |
805344dc | 1176 | item.txids.push_back(wtx.GetHash()); |
a3e192a3 | 1177 | if (mine & ISMINE_WATCH_ONLY) |
0fa2f889 | 1178 | item.fIsWatchonly = true; |
e3bc5698 JG |
1179 | } |
1180 | } | |
1181 | ||
1182 | // Reply | |
38fc4b70 | 1183 | UniValue ret(UniValue::VARR); |
07444da1 PW |
1184 | std::map<std::string, tallyitem> mapAccountTally; |
1185 | for (const std::pair<CTxDestination, CAddressBookData>& item : pwalletMain->mapAddressBook) { | |
1186 | const CTxDestination& dest = item.first; | |
1187 | const std::string& strAccount = item.second.name; | |
1188 | std::map<CTxDestination, tallyitem>::iterator it = mapTally.find(dest); | |
e3bc5698 JG |
1189 | if (it == mapTally.end() && !fIncludeEmpty) |
1190 | continue; | |
1191 | ||
a372168e | 1192 | CAmount nAmount = 0; |
e3bc5698 | 1193 | int nConf = std::numeric_limits<int>::max(); |
0fa2f889 | 1194 | bool fIsWatchonly = false; |
e3bc5698 JG |
1195 | if (it != mapTally.end()) |
1196 | { | |
1197 | nAmount = (*it).second.nAmount; | |
1198 | nConf = (*it).second.nConf; | |
0fa2f889 | 1199 | fIsWatchonly = (*it).second.fIsWatchonly; |
e3bc5698 JG |
1200 | } |
1201 | ||
1202 | if (fByAccounts) | |
1203 | { | |
1204 | tallyitem& item = mapAccountTally[strAccount]; | |
1205 | item.nAmount += nAmount; | |
1206 | item.nConf = min(item.nConf, nConf); | |
0fa2f889 | 1207 | item.fIsWatchonly = fIsWatchonly; |
e3bc5698 JG |
1208 | } |
1209 | else | |
1210 | { | |
38fc4b70 | 1211 | UniValue obj(UniValue::VOBJ); |
0fa2f889 J |
1212 | if(fIsWatchonly) |
1213 | obj.push_back(Pair("involvesWatchonly", true)); | |
07444da1 | 1214 | obj.push_back(Pair("address", EncodeDestination(dest))); |
e3bc5698 JG |
1215 | obj.push_back(Pair("account", strAccount)); |
1216 | obj.push_back(Pair("amount", ValueFromAmount(nAmount))); | |
1217 | obj.push_back(Pair("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf))); | |
38fc4b70 | 1218 | UniValue transactions(UniValue::VARR); |
1a204694 | 1219 | if (it != mapTally.end()) |
62c9b115 | 1220 | { |
1a204694 A |
1221 | BOOST_FOREACH(const uint256& item, (*it).second.txids) |
1222 | { | |
1223 | transactions.push_back(item.GetHex()); | |
1224 | } | |
62c9b115 A |
1225 | } |
1226 | obj.push_back(Pair("txids", transactions)); | |
e3bc5698 JG |
1227 | ret.push_back(obj); |
1228 | } | |
1229 | } | |
1230 | ||
1231 | if (fByAccounts) | |
1232 | { | |
1233 | for (map<string, tallyitem>::iterator it = mapAccountTally.begin(); it != mapAccountTally.end(); ++it) | |
1234 | { | |
a372168e | 1235 | CAmount nAmount = (*it).second.nAmount; |
e3bc5698 | 1236 | int nConf = (*it).second.nConf; |
38fc4b70 | 1237 | UniValue obj(UniValue::VOBJ); |
0fa2f889 J |
1238 | if((*it).second.fIsWatchonly) |
1239 | obj.push_back(Pair("involvesWatchonly", true)); | |
e3bc5698 JG |
1240 | obj.push_back(Pair("account", (*it).first)); |
1241 | obj.push_back(Pair("amount", ValueFromAmount(nAmount))); | |
1242 | obj.push_back(Pair("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf))); | |
1243 | ret.push_back(obj); | |
1244 | } | |
1245 | } | |
1246 | ||
1247 | return ret; | |
1248 | } | |
1249 | ||
d014114d | 1250 | UniValue listreceivedbyaddress(const UniValue& params, bool fHelp) |
e3bc5698 | 1251 | { |
b9fb692d | 1252 | if (!EnsureWalletIsAvailable(fHelp)) |
9756b7bd | 1253 | return NullUniValue; |
70454796 | 1254 | |
0fa2f889 | 1255 | if (fHelp || params.size() > 3) |
e3bc5698 | 1256 | throw runtime_error( |
0fa2f889 | 1257 | "listreceivedbyaddress ( minconf includeempty includeWatchonly)\n" |
a6099ef3 | 1258 | "\nList balances by receiving address.\n" |
1259 | "\nArguments:\n" | |
1260 | "1. minconf (numeric, optional, default=1) The minimum number of confirmations before payments are included.\n" | |
5617267c | 1261 | "2. includeempty (numeric, optional, default=false) Whether to include addresses that haven't received any payments.\n" |
0fa2f889 | 1262 | "3. includeWatchonly (bool, optional, default=false) Whether to include watchonly addresses (see 'importaddress').\n" |
a6099ef3 | 1263 | |
1264 | "\nResult:\n" | |
1265 | "[\n" | |
1266 | " {\n" | |
8f6860a0 | 1267 | " \"involvesWatchonly\" : true, (bool) Only returned if imported addresses were involved in transaction\n" |
a6099ef3 | 1268 | " \"address\" : \"receivingaddress\", (string) The receiving address\n" |
7b782f5b | 1269 | " \"account\" : \"accountname\", (string) DEPRECATED. The account of the receiving address. The default account is \"\".\n" |
091b2116 | 1270 | " \"amount\" : x.xxx, (numeric) The total amount in " + CURRENCY_UNIT + " received by the address\n" |
a6099ef3 | 1271 | " \"confirmations\" : n (numeric) The number of confirmations of the most recent transaction included\n" |
1272 | " }\n" | |
1273 | " ,...\n" | |
1274 | "]\n" | |
1275 | ||
1276 | "\nExamples:\n" | |
1277 | + HelpExampleCli("listreceivedbyaddress", "") | |
1278 | + HelpExampleCli("listreceivedbyaddress", "6 true") | |
0fa2f889 | 1279 | + HelpExampleRpc("listreceivedbyaddress", "6, true, true") |
a6099ef3 | 1280 | ); |
e3bc5698 | 1281 | |
4401b2d7 EL |
1282 | LOCK2(cs_main, pwalletMain->cs_wallet); |
1283 | ||
e3bc5698 JG |
1284 | return ListReceived(params, false); |
1285 | } | |
1286 | ||
d014114d | 1287 | UniValue listreceivedbyaccount(const UniValue& params, bool fHelp) |
e3bc5698 | 1288 | { |
b9fb692d | 1289 | if (!EnsureWalletIsAvailable(fHelp)) |
9756b7bd | 1290 | return NullUniValue; |
70454796 | 1291 | |
0fa2f889 | 1292 | if (fHelp || params.size() > 3) |
e3bc5698 | 1293 | throw runtime_error( |
0fa2f889 | 1294 | "listreceivedbyaccount ( minconf includeempty includeWatchonly)\n" |
7b782f5b | 1295 | "\nDEPRECATED. List balances by account.\n" |
a6099ef3 | 1296 | "\nArguments:\n" |
1297 | "1. minconf (numeric, optional, default=1) The minimum number of confirmations before payments are included.\n" | |
1298 | "2. includeempty (boolean, optional, default=false) Whether to include accounts that haven't received any payments.\n" | |
0fa2f889 | 1299 | "3. includeWatchonly (bool, optional, default=false) Whether to include watchonly addresses (see 'importaddress').\n" |
a6099ef3 | 1300 | |
1301 | "\nResult:\n" | |
1302 | "[\n" | |
1303 | " {\n" | |
8f6860a0 | 1304 | " \"involvesWatchonly\" : true, (bool) Only returned if imported addresses were involved in transaction\n" |
a6099ef3 | 1305 | " \"account\" : \"accountname\", (string) The account name of the receiving account\n" |
1306 | " \"amount\" : x.xxx, (numeric) The total amount received by addresses with this account\n" | |
1307 | " \"confirmations\" : n (numeric) The number of confirmations of the most recent transaction included\n" | |
1308 | " }\n" | |
1309 | " ,...\n" | |
1310 | "]\n" | |
1311 | ||
1312 | "\nExamples:\n" | |
1313 | + HelpExampleCli("listreceivedbyaccount", "") | |
1314 | + HelpExampleCli("listreceivedbyaccount", "6 true") | |
0fa2f889 | 1315 | + HelpExampleRpc("listreceivedbyaccount", "6, true, true") |
a6099ef3 | 1316 | ); |
e3bc5698 | 1317 | |
4401b2d7 EL |
1318 | LOCK2(cs_main, pwalletMain->cs_wallet); |
1319 | ||
e3bc5698 JG |
1320 | return ListReceived(params, true); |
1321 | } | |
1322 | ||
851f58f9 | 1323 | static void MaybePushAddress(UniValue & entry, const CTxDestination &dest) |
cc6cfab3 | 1324 | { |
07444da1 PW |
1325 | if (IsValidDestination(dest)) { |
1326 | entry.push_back(Pair("address", EncodeDestination(dest))); | |
1327 | } | |
cc6cfab3 LD |
1328 | } |
1329 | ||
d014114d | 1330 | void ListTransactions(const CWalletTx& wtx, const string& strAccount, int nMinDepth, bool fLong, UniValue& ret, const isminefilter& filter) |
e3bc5698 | 1331 | { |
a372168e | 1332 | CAmount nFee; |
e3bc5698 | 1333 | string strSentAccount; |
1b4568cb CL |
1334 | list<COutputEntry> listReceived; |
1335 | list<COutputEntry> listSent; | |
e3bc5698 | 1336 | |
d7d5d23b | 1337 | wtx.GetAmounts(listReceived, listSent, nFee, strSentAccount, filter); |
e3bc5698 JG |
1338 | |
1339 | bool fAllAccounts = (strAccount == string("*")); | |
a3e192a3 | 1340 | bool involvesWatchonly = wtx.IsFromMe(ISMINE_WATCH_ONLY); |
e3bc5698 | 1341 | |
e3bc5698 JG |
1342 | // Sent |
1343 | if ((!listSent.empty() || nFee != 0) && (fAllAccounts || strAccount == strSentAccount)) | |
1344 | { | |
1b4568cb | 1345 | BOOST_FOREACH(const COutputEntry& s, listSent) |
e3bc5698 | 1346 | { |
38fc4b70 | 1347 | UniValue entry(UniValue::VOBJ); |
1b4568cb | 1348 | if(involvesWatchonly || (::IsMine(*pwalletMain, s.destination) & ISMINE_WATCH_ONLY)) |
952877e0 | 1349 | entry.push_back(Pair("involvesWatchonly", true)); |
e3bc5698 | 1350 | entry.push_back(Pair("account", strSentAccount)); |
1b4568cb | 1351 | MaybePushAddress(entry, s.destination); |
b96f6a77 | 1352 | entry.push_back(Pair("category", "send")); |
1b4568cb CL |
1353 | entry.push_back(Pair("amount", ValueFromAmount(-s.amount))); |
1354 | entry.push_back(Pair("vout", s.vout)); | |
e3bc5698 JG |
1355 | entry.push_back(Pair("fee", ValueFromAmount(-nFee))); |
1356 | if (fLong) | |
1357 | WalletTxToJSON(wtx, entry); | |
a8e5ae92 | 1358 | entry.push_back(Pair("size", static_cast<uint64_t>(GetSerializeSize(static_cast<CTransaction>(wtx), SER_NETWORK, PROTOCOL_VERSION)))); |
e3bc5698 JG |
1359 | ret.push_back(entry); |
1360 | } | |
1361 | } | |
1362 | ||
1363 | // Received | |
1364 | if (listReceived.size() > 0 && wtx.GetDepthInMainChain() >= nMinDepth) | |
1365 | { | |
1b4568cb | 1366 | BOOST_FOREACH(const COutputEntry& r, listReceived) |
e3bc5698 JG |
1367 | { |
1368 | string account; | |
1b4568cb CL |
1369 | if (pwalletMain->mapAddressBook.count(r.destination)) |
1370 | account = pwalletMain->mapAddressBook[r.destination].name; | |
e3bc5698 JG |
1371 | if (fAllAccounts || (account == strAccount)) |
1372 | { | |
38fc4b70 | 1373 | UniValue entry(UniValue::VOBJ); |
1b4568cb | 1374 | if(involvesWatchonly || (::IsMine(*pwalletMain, r.destination) & ISMINE_WATCH_ONLY)) |
952877e0 | 1375 | entry.push_back(Pair("involvesWatchonly", true)); |
e3bc5698 | 1376 | entry.push_back(Pair("account", account)); |
1b4568cb | 1377 | MaybePushAddress(entry, r.destination); |
e07c8e91 LD |
1378 | if (wtx.IsCoinBase()) |
1379 | { | |
1380 | if (wtx.GetDepthInMainChain() < 1) | |
1381 | entry.push_back(Pair("category", "orphan")); | |
1382 | else if (wtx.GetBlocksToMaturity() > 0) | |
1383 | entry.push_back(Pair("category", "immature")); | |
1384 | else | |
1385 | entry.push_back(Pair("category", "generate")); | |
1386 | } | |
1387 | else | |
2b72d46f | 1388 | { |
b96f6a77 | 1389 | entry.push_back(Pair("category", "receive")); |
2b72d46f | 1390 | } |
1b4568cb CL |
1391 | entry.push_back(Pair("amount", ValueFromAmount(r.amount))); |
1392 | entry.push_back(Pair("vout", r.vout)); | |
e3bc5698 JG |
1393 | if (fLong) |
1394 | WalletTxToJSON(wtx, entry); | |
a8e5ae92 | 1395 | entry.push_back(Pair("size", static_cast<uint64_t>(GetSerializeSize(static_cast<CTransaction>(wtx), SER_NETWORK, PROTOCOL_VERSION)))); |
e3bc5698 JG |
1396 | ret.push_back(entry); |
1397 | } | |
1398 | } | |
1399 | } | |
1400 | } | |
1401 | ||
d014114d | 1402 | void AcentryToJSON(const CAccountingEntry& acentry, const string& strAccount, UniValue& ret) |
e3bc5698 JG |
1403 | { |
1404 | bool fAllAccounts = (strAccount == string("*")); | |
1405 | ||
1406 | if (fAllAccounts || acentry.strAccount == strAccount) | |
1407 | { | |
38fc4b70 | 1408 | UniValue entry(UniValue::VOBJ); |
e3bc5698 JG |
1409 | entry.push_back(Pair("account", acentry.strAccount)); |
1410 | entry.push_back(Pair("category", "move")); | |
d56e30ca | 1411 | entry.push_back(Pair("time", acentry.nTime)); |
e3bc5698 JG |
1412 | entry.push_back(Pair("amount", ValueFromAmount(acentry.nCreditDebit))); |
1413 | entry.push_back(Pair("otheraccount", acentry.strOtherAccount)); | |
1414 | entry.push_back(Pair("comment", acentry.strComment)); | |
1415 | ret.push_back(entry); | |
1416 | } | |
1417 | } | |
1418 | ||
d014114d | 1419 | UniValue listtransactions(const UniValue& params, bool fHelp) |
e3bc5698 | 1420 | { |
b9fb692d | 1421 | if (!EnsureWalletIsAvailable(fHelp)) |
9756b7bd | 1422 | return NullUniValue; |
70454796 | 1423 | |
d7d5d23b | 1424 | if (fHelp || params.size() > 4) |
e3bc5698 | 1425 | throw runtime_error( |
d7d5d23b | 1426 | "listtransactions ( \"account\" count from includeWatchonly)\n" |
a6099ef3 | 1427 | "\nReturns up to 'count' most recent transactions skipping the first 'from' transactions for account 'account'.\n" |
1428 | "\nArguments:\n" | |
7b782f5b | 1429 | "1. \"account\" (string, optional) DEPRECATED. The account name. Should be \"*\".\n" |
a6099ef3 | 1430 | "2. count (numeric, optional, default=10) The number of transactions to return\n" |
1431 | "3. from (numeric, optional, default=0) The number of transactions to skip\n" | |
d7d5d23b | 1432 | "4. includeWatchonly (bool, optional, default=false) Include transactions to watchonly addresses (see 'importaddress')\n" |
a6099ef3 | 1433 | "\nResult:\n" |
1434 | "[\n" | |
1435 | " {\n" | |
7b782f5b | 1436 | " \"account\":\"accountname\", (string) DEPRECATED. The account name associated with the transaction. \n" |
a6099ef3 | 1437 | " It will be \"\" for the default account.\n" |
d2c1e4a8 | 1438 | " \"address\":\"zcashaddress\", (string) The Zcash address of the transaction. Not present for \n" |
a6099ef3 | 1439 | " move transactions (category = move).\n" |
1440 | " \"category\":\"send|receive|move\", (string) The transaction category. 'move' is a local (off blockchain)\n" | |
1441 | " transaction between accounts, and not associated with an address,\n" | |
1442 | " transaction id or block. 'send' and 'receive' transactions are \n" | |
1443 | " associated with an address, transaction id and block details\n" | |
091b2116 | 1444 | " \"amount\": x.xxx, (numeric) The amount in " + CURRENCY_UNIT + ". This is negative for the 'send' category, and for the\n" |
a6099ef3 | 1445 | " 'move' category for moves outbound. It is positive for the 'receive' category,\n" |
1446 | " and for the 'move' category for inbound funds.\n" | |
1b4568cb | 1447 | " \"vout\" : n, (numeric) the vout value\n" |
091b2116 | 1448 | " \"fee\": x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n" |
a6099ef3 | 1449 | " 'send' category of transactions.\n" |
1450 | " \"confirmations\": n, (numeric) The number of confirmations for the transaction. Available for 'send' and \n" | |
1451 | " 'receive' category of transactions.\n" | |
1452 | " \"blockhash\": \"hashvalue\", (string) The block hash containing the transaction. Available for 'send' and 'receive'\n" | |
1453 | " category of transactions.\n" | |
1454 | " \"blockindex\": n, (numeric) The block index containing the transaction. Available for 'send' and 'receive'\n" | |
1455 | " category of transactions.\n" | |
b5ef85c7 | 1456 | " \"txid\": \"transactionid\", (string) The transaction id. Available for 'send' and 'receive' category of transactions.\n" |
a6099ef3 | 1457 | " \"time\": xxx, (numeric) The transaction time in seconds since epoch (midnight Jan 1 1970 GMT).\n" |
1458 | " \"timereceived\": xxx, (numeric) The time received in seconds since epoch (midnight Jan 1 1970 GMT). Available \n" | |
1459 | " for 'send' and 'receive' category of transactions.\n" | |
1460 | " \"comment\": \"...\", (string) If a comment is associated with the transaction.\n" | |
1461 | " \"otheraccount\": \"accountname\", (string) For the 'move' category of transactions, the account the funds came \n" | |
1462 | " from (for receiving funds, positive amounts), or went to (for sending funds,\n" | |
1463 | " negative amounts).\n" | |
f32cade8 | 1464 | " \"size\": n, (numeric) Transaction size in bytes\n" |
a6099ef3 | 1465 | " }\n" |
1466 | "]\n" | |
1467 | ||
1468 | "\nExamples:\n" | |
1469 | "\nList the most recent 10 transactions in the systems\n" | |
1470 | + HelpExampleCli("listtransactions", "") + | |
7b782f5b LD |
1471 | "\nList transactions 100 to 120\n" |
1472 | + HelpExampleCli("listtransactions", "\"*\" 20 100") + | |
a6099ef3 | 1473 | "\nAs a json rpc call\n" |
7b782f5b | 1474 | + HelpExampleRpc("listtransactions", "\"*\", 20, 100") |
a6099ef3 | 1475 | ); |
e3bc5698 | 1476 | |
4401b2d7 EL |
1477 | LOCK2(cs_main, pwalletMain->cs_wallet); |
1478 | ||
e3bc5698 JG |
1479 | string strAccount = "*"; |
1480 | if (params.size() > 0) | |
1481 | strAccount = params[0].get_str(); | |
1482 | int nCount = 10; | |
1483 | if (params.size() > 1) | |
1484 | nCount = params[1].get_int(); | |
1485 | int nFrom = 0; | |
1486 | if (params.size() > 2) | |
1487 | nFrom = params[2].get_int(); | |
a3e192a3 | 1488 | isminefilter filter = ISMINE_SPENDABLE; |
a5c6c5d6 J |
1489 | if(params.size() > 3) |
1490 | if(params[3].get_bool()) | |
a3e192a3 | 1491 | filter = filter | ISMINE_WATCH_ONLY; |
e3bc5698 JG |
1492 | |
1493 | if (nCount < 0) | |
738835d7 | 1494 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative count"); |
e3bc5698 | 1495 | if (nFrom < 0) |
738835d7 | 1496 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative from"); |
e3bc5698 | 1497 | |
38fc4b70 | 1498 | UniValue ret(UniValue::VARR); |
e3bc5698 | 1499 | |
ddb709e9 LD |
1500 | std::list<CAccountingEntry> acentries; |
1501 | CWallet::TxItems txOrdered = pwalletMain->OrderedTxItems(acentries, strAccount); | |
e3bc5698 JG |
1502 | |
1503 | // iterate backwards until we have nCount items to return: | |
c3f95ef1 | 1504 | for (CWallet::TxItems::reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) |
e3bc5698 JG |
1505 | { |
1506 | CWalletTx *const pwtx = (*it).second.first; | |
1507 | if (pwtx != 0) | |
d7d5d23b | 1508 | ListTransactions(*pwtx, strAccount, 0, true, ret, filter); |
e3bc5698 JG |
1509 | CAccountingEntry *const pacentry = (*it).second.second; |
1510 | if (pacentry != 0) | |
1511 | AcentryToJSON(*pacentry, strAccount, ret); | |
1512 | ||
1513 | if ((int)ret.size() >= (nCount+nFrom)) break; | |
1514 | } | |
1515 | // ret is newest to oldest | |
1516 | ||
1517 | if (nFrom > (int)ret.size()) | |
1518 | nFrom = ret.size(); | |
1519 | if ((nFrom + nCount) > (int)ret.size()) | |
1520 | nCount = ret.size() - nFrom; | |
ed21d5bd JG |
1521 | |
1522 | vector<UniValue> arrTmp = ret.getValues(); | |
1523 | ||
1524 | vector<UniValue>::iterator first = arrTmp.begin(); | |
e3bc5698 | 1525 | std::advance(first, nFrom); |
ed21d5bd | 1526 | vector<UniValue>::iterator last = arrTmp.begin(); |
e3bc5698 JG |
1527 | std::advance(last, nFrom+nCount); |
1528 | ||
ed21d5bd JG |
1529 | if (last != arrTmp.end()) arrTmp.erase(last, arrTmp.end()); |
1530 | if (first != arrTmp.begin()) arrTmp.erase(arrTmp.begin(), first); | |
1531 | ||
1532 | std::reverse(arrTmp.begin(), arrTmp.end()); // Return oldest to newest | |
e3bc5698 | 1533 | |
ed21d5bd | 1534 | ret.clear(); |
38fc4b70 | 1535 | ret.setArray(); |
ed21d5bd | 1536 | ret.push_backV(arrTmp); |
e3bc5698 JG |
1537 | |
1538 | return ret; | |
1539 | } | |
1540 | ||
d014114d | 1541 | UniValue listaccounts(const UniValue& params, bool fHelp) |
e3bc5698 | 1542 | { |
b9fb692d | 1543 | if (!EnsureWalletIsAvailable(fHelp)) |
9756b7bd | 1544 | return NullUniValue; |
70454796 | 1545 | |
83f3543f | 1546 | if (fHelp || params.size() > 2) |
e3bc5698 | 1547 | throw runtime_error( |
83f3543f | 1548 | "listaccounts ( minconf includeWatchonly)\n" |
7b782f5b | 1549 | "\nDEPRECATED. Returns Object that has account names as keys, account balances as values.\n" |
a6099ef3 | 1550 | "\nArguments:\n" |
5617267c | 1551 | "1. minconf (numeric, optional, default=1) Only include transactions with at least this many confirmations\n" |
83f3543f | 1552 | "2. includeWatchonly (bool, optional, default=false) Include balances in watchonly addresses (see 'importaddress')\n" |
a6099ef3 | 1553 | "\nResult:\n" |
1554 | "{ (json object where keys are account names, and values are numeric balances\n" | |
1555 | " \"account\": x.xxx, (numeric) The property name is the account name, and the value is the total balance for the account.\n" | |
1556 | " ...\n" | |
1557 | "}\n" | |
1558 | "\nExamples:\n" | |
1559 | "\nList account balances where there at least 1 confirmation\n" | |
1560 | + HelpExampleCli("listaccounts", "") + | |
1561 | "\nList account balances including zero confirmation transactions\n" | |
1562 | + HelpExampleCli("listaccounts", "0") + | |
1563 | "\nList account balances for 6 or more confirmations\n" | |
1564 | + HelpExampleCli("listaccounts", "6") + | |
1565 | "\nAs json rpc call\n" | |
1566 | + HelpExampleRpc("listaccounts", "6") | |
1567 | ); | |
e3bc5698 | 1568 | |
4401b2d7 EL |
1569 | LOCK2(cs_main, pwalletMain->cs_wallet); |
1570 | ||
e3bc5698 JG |
1571 | int nMinDepth = 1; |
1572 | if (params.size() > 0) | |
1573 | nMinDepth = params[0].get_int(); | |
a3e192a3 | 1574 | isminefilter includeWatchonly = ISMINE_SPENDABLE; |
a5c6c5d6 J |
1575 | if(params.size() > 1) |
1576 | if(params[1].get_bool()) | |
a3e192a3 | 1577 | includeWatchonly = includeWatchonly | ISMINE_WATCH_ONLY; |
e3bc5698 | 1578 | |
a372168e | 1579 | map<string, CAmount> mapAccountBalances; |
61885513 | 1580 | BOOST_FOREACH(const PAIRTYPE(CTxDestination, CAddressBookData)& entry, pwalletMain->mapAddressBook) { |
83f3543f | 1581 | if (IsMine(*pwalletMain, entry.first) & includeWatchonly) // This address belongs to me |
61885513 | 1582 | mapAccountBalances[entry.second.name] = 0; |
e3bc5698 JG |
1583 | } |
1584 | ||
1585 | for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) | |
1586 | { | |
1587 | const CWalletTx& wtx = (*it).second; | |
a372168e | 1588 | CAmount nFee; |
e3bc5698 | 1589 | string strSentAccount; |
1b4568cb CL |
1590 | list<COutputEntry> listReceived; |
1591 | list<COutputEntry> listSent; | |
93a18a36 GA |
1592 | int nDepth = wtx.GetDepthInMainChain(); |
1593 | if (wtx.GetBlocksToMaturity() > 0 || nDepth < 0) | |
731b89b8 | 1594 | continue; |
83f3543f | 1595 | wtx.GetAmounts(listReceived, listSent, nFee, strSentAccount, includeWatchonly); |
e3bc5698 | 1596 | mapAccountBalances[strSentAccount] -= nFee; |
1b4568cb CL |
1597 | BOOST_FOREACH(const COutputEntry& s, listSent) |
1598 | mapAccountBalances[strSentAccount] -= s.amount; | |
93a18a36 | 1599 | if (nDepth >= nMinDepth) |
e3bc5698 | 1600 | { |
1b4568cb CL |
1601 | BOOST_FOREACH(const COutputEntry& r, listReceived) |
1602 | if (pwalletMain->mapAddressBook.count(r.destination)) | |
1603 | mapAccountBalances[pwalletMain->mapAddressBook[r.destination].name] += r.amount; | |
e3bc5698 | 1604 | else |
1b4568cb | 1605 | mapAccountBalances[""] += r.amount; |
e3bc5698 JG |
1606 | } |
1607 | } | |
1608 | ||
1609 | list<CAccountingEntry> acentries; | |
1610 | CWalletDB(pwalletMain->strWalletFile).ListAccountCreditDebit("*", acentries); | |
1611 | BOOST_FOREACH(const CAccountingEntry& entry, acentries) | |
1612 | mapAccountBalances[entry.strAccount] += entry.nCreditDebit; | |
1613 | ||
38fc4b70 | 1614 | UniValue ret(UniValue::VOBJ); |
a372168e | 1615 | BOOST_FOREACH(const PAIRTYPE(string, CAmount)& accountBalance, mapAccountBalances) { |
e3bc5698 JG |
1616 | ret.push_back(Pair(accountBalance.first, ValueFromAmount(accountBalance.second))); |
1617 | } | |
1618 | return ret; | |
1619 | } | |
1620 | ||
d014114d | 1621 | UniValue listsinceblock(const UniValue& params, bool fHelp) |
e3bc5698 | 1622 | { |
b9fb692d | 1623 | if (!EnsureWalletIsAvailable(fHelp)) |
9756b7bd | 1624 | return NullUniValue; |
70454796 | 1625 | |
e3bc5698 JG |
1626 | if (fHelp) |
1627 | throw runtime_error( | |
d7d5d23b | 1628 | "listsinceblock ( \"blockhash\" target-confirmations includeWatchonly)\n" |
a6099ef3 | 1629 | "\nGet all transactions in blocks since block [blockhash], or all transactions if omitted\n" |
1630 | "\nArguments:\n" | |
1631 | "1. \"blockhash\" (string, optional) The block hash to list transactions since\n" | |
1632 | "2. target-confirmations: (numeric, optional) The confirmations required, must be 1 or more\n" | |
d7d5d23b | 1633 | "3. includeWatchonly: (bool, optional, default=false) Include transactions to watchonly addresses (see 'importaddress')" |
a6099ef3 | 1634 | "\nResult:\n" |
1635 | "{\n" | |
1636 | " \"transactions\": [\n" | |
7b782f5b | 1637 | " \"account\":\"accountname\", (string) DEPRECATED. The account name associated with the transaction. Will be \"\" for the default account.\n" |
d2c1e4a8 | 1638 | " \"address\":\"zcashaddress\", (string) The Zcash address of the transaction. Not present for move transactions (category = move).\n" |
a6099ef3 | 1639 | " \"category\":\"send|receive\", (string) The transaction category. 'send' has negative amounts, 'receive' has positive amounts.\n" |
091b2116 | 1640 | " \"amount\": x.xxx, (numeric) The amount in " + CURRENCY_UNIT + ". This is negative for the 'send' category, and for the 'move' category for moves \n" |
a6099ef3 | 1641 | " outbound. It is positive for the 'receive' category, and for the 'move' category for inbound funds.\n" |
1b4568cb | 1642 | " \"vout\" : n, (numeric) the vout value\n" |
091b2116 | 1643 | " \"fee\": x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the 'send' category of transactions.\n" |
a6099ef3 | 1644 | " \"confirmations\": n, (numeric) The number of confirmations for the transaction. Available for 'send' and 'receive' category of transactions.\n" |
1645 | " \"blockhash\": \"hashvalue\", (string) The block hash containing the transaction. Available for 'send' and 'receive' category of transactions.\n" | |
1646 | " \"blockindex\": n, (numeric) The block index containing the transaction. Available for 'send' and 'receive' category of transactions.\n" | |
1647 | " \"blocktime\": xxx, (numeric) The block time in seconds since epoch (1 Jan 1970 GMT).\n" | |
b5ef85c7 | 1648 | " \"txid\": \"transactionid\", (string) The transaction id. Available for 'send' and 'receive' category of transactions.\n" |
a6099ef3 | 1649 | " \"time\": xxx, (numeric) The transaction time in seconds since epoch (Jan 1 1970 GMT).\n" |
1650 | " \"timereceived\": xxx, (numeric) The time received in seconds since epoch (Jan 1 1970 GMT). Available for 'send' and 'receive' category of transactions.\n" | |
1651 | " \"comment\": \"...\", (string) If a comment is associated with the transaction.\n" | |
1652 | " \"to\": \"...\", (string) If a comment to is associated with the transaction.\n" | |
1653 | " ],\n" | |
1654 | " \"lastblock\": \"lastblockhash\" (string) The hash of the last block\n" | |
1655 | "}\n" | |
1656 | "\nExamples:\n" | |
1657 | + HelpExampleCli("listsinceblock", "") | |
1658 | + HelpExampleCli("listsinceblock", "\"000000000000000bacf66f7497b7dc45ef753ee9a7d38571037cdb1a57f663ad\" 6") | |
1659 | + HelpExampleRpc("listsinceblock", "\"000000000000000bacf66f7497b7dc45ef753ee9a7d38571037cdb1a57f663ad\", 6") | |
1660 | ); | |
e3bc5698 | 1661 | |
4401b2d7 EL |
1662 | LOCK2(cs_main, pwalletMain->cs_wallet); |
1663 | ||
e3bc5698 JG |
1664 | CBlockIndex *pindex = NULL; |
1665 | int target_confirms = 1; | |
a3e192a3 | 1666 | isminefilter filter = ISMINE_SPENDABLE; |
e3bc5698 JG |
1667 | |
1668 | if (params.size() > 0) | |
1669 | { | |
4f152496 | 1670 | uint256 blockId; |
e3bc5698 JG |
1671 | |
1672 | blockId.SetHex(params[0].get_str()); | |
145d5be8 | 1673 | BlockMap::iterator it = mapBlockIndex.find(blockId); |
e4daecda PW |
1674 | if (it != mapBlockIndex.end()) |
1675 | pindex = it->second; | |
e3bc5698 JG |
1676 | } |
1677 | ||
1678 | if (params.size() > 1) | |
1679 | { | |
1680 | target_confirms = params[1].get_int(); | |
1681 | ||
1682 | if (target_confirms < 1) | |
738835d7 | 1683 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter"); |
e3bc5698 JG |
1684 | } |
1685 | ||
a5c6c5d6 J |
1686 | if(params.size() > 2) |
1687 | if(params[2].get_bool()) | |
a3e192a3 | 1688 | filter = filter | ISMINE_WATCH_ONLY; |
a5c6c5d6 | 1689 | |
4c6d41b8 | 1690 | int depth = pindex ? (1 + chainActive.Height() - pindex->nHeight) : -1; |
e3bc5698 | 1691 | |
38fc4b70 | 1692 | UniValue transactions(UniValue::VARR); |
e3bc5698 JG |
1693 | |
1694 | for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); it++) | |
1695 | { | |
1696 | CWalletTx tx = (*it).second; | |
1697 | ||
1698 | if (depth == -1 || tx.GetDepthInMainChain() < depth) | |
d7d5d23b | 1699 | ListTransactions(tx, "*", 0, true, transactions, filter); |
e3bc5698 JG |
1700 | } |
1701 | ||
4c6d41b8 | 1702 | CBlockIndex *pblockLast = chainActive[chainActive.Height() + 1 - target_confirms]; |
4f152496 | 1703 | uint256 lastblock = pblockLast ? pblockLast->GetBlockHash() : uint256(); |
e3bc5698 | 1704 | |
38fc4b70 | 1705 | UniValue ret(UniValue::VOBJ); |
e3bc5698 JG |
1706 | ret.push_back(Pair("transactions", transactions)); |
1707 | ret.push_back(Pair("lastblock", lastblock.GetHex())); | |
1708 | ||
1709 | return ret; | |
1710 | } | |
1711 | ||
d014114d | 1712 | UniValue gettransaction(const UniValue& params, bool fHelp) |
e3bc5698 | 1713 | { |
b9fb692d | 1714 | if (!EnsureWalletIsAvailable(fHelp)) |
9756b7bd | 1715 | return NullUniValue; |
70454796 | 1716 | |
f87ba3df | 1717 | if (fHelp || params.size() < 1 || params.size() > 2) |
e3bc5698 | 1718 | throw runtime_error( |
57e1716d | 1719 | "gettransaction \"txid\" ( includeWatchonly )\n" |
a6099ef3 | 1720 | "\nGet detailed information about in-wallet transaction <txid>\n" |
1721 | "\nArguments:\n" | |
1722 | "1. \"txid\" (string, required) The transaction id\n" | |
f87ba3df | 1723 | "2. \"includeWatchonly\" (bool, optional, default=false) Whether to include watchonly addresses in balance calculation and details[]\n" |
a6099ef3 | 1724 | "\nResult:\n" |
1725 | "{\n" | |
091b2116 | 1726 | " \"amount\" : x.xxx, (numeric) The transaction amount in " + CURRENCY_UNIT + "\n" |
a6099ef3 | 1727 | " \"confirmations\" : n, (numeric) The number of confirmations\n" |
1728 | " \"blockhash\" : \"hash\", (string) The block hash\n" | |
1729 | " \"blockindex\" : xx, (numeric) The block index\n" | |
1730 | " \"blocktime\" : ttt, (numeric) The time in seconds since epoch (1 Jan 1970 GMT)\n" | |
b5ef85c7 | 1731 | " \"txid\" : \"transactionid\", (string) The transaction id.\n" |
a6099ef3 | 1732 | " \"time\" : ttt, (numeric) The transaction time in seconds since epoch (1 Jan 1970 GMT)\n" |
1733 | " \"timereceived\" : ttt, (numeric) The time received in seconds since epoch (1 Jan 1970 GMT)\n" | |
1734 | " \"details\" : [\n" | |
1735 | " {\n" | |
7b782f5b | 1736 | " \"account\" : \"accountname\", (string) DEPRECATED. The account name involved in the transaction, can be \"\" for the default account.\n" |
d2c1e4a8 | 1737 | " \"address\" : \"zcashaddress\", (string) The Zcash address involved in the transaction\n" |
a6099ef3 | 1738 | " \"category\" : \"send|receive\", (string) The category, either 'send' or 'receive'\n" |
091b2116 | 1739 | " \"amount\" : x.xxx (numeric) The amount in " + CURRENCY_UNIT + "\n" |
1b4568cb | 1740 | " \"vout\" : n, (numeric) the vout value\n" |
a6099ef3 | 1741 | " }\n" |
1742 | " ,...\n" | |
3a1c20b7 | 1743 | " ],\n" |
f7cfb52d S |
1744 | " \"vjoinsplit\" : [\n" |
1745 | " {\n" | |
1746 | " \"anchor\" : \"treestateref\", (string) Merkle root of note commitment tree\n" | |
1747 | " \"nullifiers\" : [ string, ... ] (string) Nullifiers of input notes\n" | |
1748 | " \"commitments\" : [ string, ... ] (string) Note commitments for note outputs\n" | |
1749 | " \"macs\" : [ string, ... ] (string) Message authentication tags\n" | |
1750 | " \"vpub_old\" : x.xxx (numeric) The amount removed from the transparent value pool\n" | |
1751 | " \"vpub_new\" : x.xxx, (numeric) The amount added to the transparent value pool\n" | |
1752 | " }\n" | |
1753 | " ,...\n" | |
1754 | " ],\n" | |
3a1c20b7 | 1755 | " \"hex\" : \"data\" (string) Raw data for transaction\n" |
a6099ef3 | 1756 | "}\n" |
1757 | ||
ab45ddb5 | 1758 | "\nExamples:\n" |
a6099ef3 | 1759 | + HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") |
57e1716d | 1760 | + HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\" true") |
a6099ef3 | 1761 | + HelpExampleRpc("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") |
1762 | ); | |
e3bc5698 | 1763 | |
4401b2d7 EL |
1764 | LOCK2(cs_main, pwalletMain->cs_wallet); |
1765 | ||
e3bc5698 JG |
1766 | uint256 hash; |
1767 | hash.SetHex(params[0].get_str()); | |
1768 | ||
a3e192a3 | 1769 | isminefilter filter = ISMINE_SPENDABLE; |
f87ba3df J |
1770 | if(params.size() > 1) |
1771 | if(params[1].get_bool()) | |
a3e192a3 | 1772 | filter = filter | ISMINE_WATCH_ONLY; |
f87ba3df | 1773 | |
38fc4b70 | 1774 | UniValue entry(UniValue::VOBJ); |
e3bc5698 | 1775 | if (!pwalletMain->mapWallet.count(hash)) |
738835d7 | 1776 | throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id"); |
e3bc5698 JG |
1777 | const CWalletTx& wtx = pwalletMain->mapWallet[hash]; |
1778 | ||
ccca27a7 | 1779 | CAmount nCredit = wtx.GetCredit(filter); |
a372168e MF |
1780 | CAmount nDebit = wtx.GetDebit(filter); |
1781 | CAmount nNet = nCredit - nDebit; | |
1782 | CAmount nFee = (wtx.IsFromMe(filter) ? wtx.GetValueOut() - nDebit : 0); | |
e3bc5698 JG |
1783 | |
1784 | entry.push_back(Pair("amount", ValueFromAmount(nNet - nFee))); | |
80dda36a | 1785 | if (wtx.IsFromMe(filter)) |
e3bc5698 JG |
1786 | entry.push_back(Pair("fee", ValueFromAmount(nFee))); |
1787 | ||
1788 | WalletTxToJSON(wtx, entry); | |
1789 | ||
38fc4b70 | 1790 | UniValue details(UniValue::VARR); |
f87ba3df | 1791 | ListTransactions(wtx, "*", 0, false, details, filter); |
e3bc5698 JG |
1792 | entry.push_back(Pair("details", details)); |
1793 | ||
ae775b5b | 1794 | string strHex = EncodeHexTx(static_cast<CTransaction>(wtx)); |
3a1c20b7 WL |
1795 | entry.push_back(Pair("hex", strHex)); |
1796 | ||
e3bc5698 JG |
1797 | return entry; |
1798 | } | |
1799 | ||
1800 | ||
d014114d | 1801 | UniValue backupwallet(const UniValue& params, bool fHelp) |
e3bc5698 | 1802 | { |
b9fb692d | 1803 | if (!EnsureWalletIsAvailable(fHelp)) |
9756b7bd | 1804 | return NullUniValue; |
70454796 | 1805 | |
e3bc5698 JG |
1806 | if (fHelp || params.size() != 1) |
1807 | throw runtime_error( | |
a6099ef3 | 1808 | "backupwallet \"destination\"\n" |
9064d73b | 1809 | "\nSafely copies wallet.dat to destination filename\n" |
a6099ef3 | 1810 | "\nArguments:\n" |
9064d73b S |
1811 | "1. \"destination\" (string, required) The destination filename, saved in the directory set by -exportdir option.\n" |
1812 | "\nResult:\n" | |
1813 | "\"path\" (string) The full path of the destination file\n" | |
a6099ef3 | 1814 | "\nExamples:\n" |
9064d73b S |
1815 | + HelpExampleCli("backupwallet", "\"backupdata\"") |
1816 | + HelpExampleRpc("backupwallet", "\"backupdata\"") | |
a6099ef3 | 1817 | ); |
e3bc5698 | 1818 | |
4401b2d7 EL |
1819 | LOCK2(cs_main, pwalletMain->cs_wallet); |
1820 | ||
9064d73b S |
1821 | boost::filesystem::path exportdir; |
1822 | try { | |
1823 | exportdir = GetExportDir(); | |
1824 | } catch (const std::runtime_error& e) { | |
1825 | throw JSONRPCError(RPC_INTERNAL_ERROR, e.what()); | |
1826 | } | |
1827 | if (exportdir.empty()) { | |
1828 | throw JSONRPCError(RPC_WALLET_ERROR, "Cannot backup wallet until the -exportdir option has been set"); | |
1829 | } | |
1830 | std::string unclean = params[0].get_str(); | |
1831 | std::string clean = SanitizeFilename(unclean); | |
1832 | if (clean.compare(unclean) != 0) { | |
1833 | throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Filename is invalid as only alphanumeric characters are allowed. Try '%s' instead.", clean)); | |
1834 | } | |
1835 | boost::filesystem::path exportfilepath = exportdir / clean; | |
1836 | ||
1837 | if (!BackupWallet(*pwalletMain, exportfilepath.string())) | |
ad525e9c | 1838 | throw JSONRPCError(RPC_WALLET_ERROR, "Error: Wallet backup failed!"); |
e3bc5698 | 1839 | |
9064d73b | 1840 | return exportfilepath.string(); |
e3bc5698 JG |
1841 | } |
1842 | ||
1843 | ||
d014114d | 1844 | UniValue keypoolrefill(const UniValue& params, bool fHelp) |
e3bc5698 | 1845 | { |
b9fb692d | 1846 | if (!EnsureWalletIsAvailable(fHelp)) |
9756b7bd | 1847 | return NullUniValue; |
70454796 | 1848 | |
36bd46f1 | 1849 | if (fHelp || params.size() > 1) |
e3bc5698 | 1850 | throw runtime_error( |
a6099ef3 | 1851 | "keypoolrefill ( newsize )\n" |
1852 | "\nFills the keypool." | |
1853 | + HelpRequiringPassphrase() + "\n" | |
1854 | "\nArguments\n" | |
1855 | "1. newsize (numeric, optional, default=100) The new keypool size\n" | |
1856 | "\nExamples:\n" | |
1857 | + HelpExampleCli("keypoolrefill", "") | |
1858 | + HelpExampleRpc("keypoolrefill", "") | |
1859 | ); | |
e3bc5698 | 1860 | |
4401b2d7 EL |
1861 | LOCK2(cs_main, pwalletMain->cs_wallet); |
1862 | ||
f914c7a1 PK |
1863 | // 0 is interpreted by TopUpKeyPool() as the default keypool size given by -keypool |
1864 | unsigned int kpSize = 0; | |
36bd46f1 JG |
1865 | if (params.size() > 0) { |
1866 | if (params[0].get_int() < 0) | |
f914c7a1 PK |
1867 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected valid size."); |
1868 | kpSize = (unsigned int)params[0].get_int(); | |
36bd46f1 JG |
1869 | } |
1870 | ||
e3bc5698 | 1871 | EnsureWalletIsUnlocked(); |
36bd46f1 | 1872 | pwalletMain->TopUpKeyPool(kpSize); |
e3bc5698 | 1873 | |
36bd46f1 | 1874 | if (pwalletMain->GetKeyPoolSize() < kpSize) |
738835d7 | 1875 | throw JSONRPCError(RPC_WALLET_ERROR, "Error refreshing keypool."); |
e3bc5698 | 1876 | |
ed21d5bd | 1877 | return NullUniValue; |
e3bc5698 JG |
1878 | } |
1879 | ||
1880 | ||
92f2c1fe | 1881 | static void LockWallet(CWallet* pWallet) |
e3bc5698 | 1882 | { |
92f2c1fe GA |
1883 | LOCK(cs_nWalletUnlockTime); |
1884 | nWalletUnlockTime = 0; | |
1885 | pWallet->Lock(); | |
e3bc5698 JG |
1886 | } |
1887 | ||
d014114d | 1888 | UniValue walletpassphrase(const UniValue& params, bool fHelp) |
e3bc5698 | 1889 | { |
b9fb692d | 1890 | if (!EnsureWalletIsAvailable(fHelp)) |
9756b7bd | 1891 | return NullUniValue; |
70454796 | 1892 | |
e3bc5698 JG |
1893 | if (pwalletMain->IsCrypted() && (fHelp || params.size() != 2)) |
1894 | throw runtime_error( | |
a6099ef3 | 1895 | "walletpassphrase \"passphrase\" timeout\n" |
1896 | "\nStores the wallet decryption key in memory for 'timeout' seconds.\n" | |
c1652849 | 1897 | "This is needed prior to performing transactions related to private keys such as sending Zcash\n" |
a6099ef3 | 1898 | "\nArguments:\n" |
1899 | "1. \"passphrase\" (string, required) The wallet passphrase\n" | |
1900 | "2. timeout (numeric, required) The time to keep the decryption key in seconds.\n" | |
6c0db81c WL |
1901 | "\nNote:\n" |
1902 | "Issuing the walletpassphrase command while the wallet is already unlocked will set a new unlock\n" | |
1903 | "time that overrides the old one.\n" | |
a6099ef3 | 1904 | "\nExamples:\n" |
1905 | "\nunlock the wallet for 60 seconds\n" | |
1906 | + HelpExampleCli("walletpassphrase", "\"my pass phrase\" 60") + | |
1907 | "\nLock the wallet again (before 60 seconds)\n" | |
1908 | + HelpExampleCli("walletlock", "") + | |
1909 | "\nAs json rpc call\n" | |
1910 | + HelpExampleRpc("walletpassphrase", "\"my pass phrase\", 60") | |
1911 | ); | |
1912 | ||
4401b2d7 EL |
1913 | LOCK2(cs_main, pwalletMain->cs_wallet); |
1914 | ||
e3bc5698 JG |
1915 | if (fHelp) |
1916 | return true; | |
1917 | if (!pwalletMain->IsCrypted()) | |
738835d7 | 1918 | throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletpassphrase was called."); |
e3bc5698 | 1919 | |
e3bc5698 JG |
1920 | // Note that the walletpassphrase is stored in params[0] which is not mlock()ed |
1921 | SecureString strWalletPass; | |
1922 | strWalletPass.reserve(100); | |
1923 | // TODO: get rid of this .c_str() by implementing SecureString::operator=(std::string) | |
1924 | // Alternately, find a way to make params[0] mlock()'d to begin with. | |
1925 | strWalletPass = params[0].get_str().c_str(); | |
1926 | ||
1927 | if (strWalletPass.length() > 0) | |
1928 | { | |
1929 | if (!pwalletMain->Unlock(strWalletPass)) | |
738835d7 | 1930 | throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect."); |
e3bc5698 JG |
1931 | } |
1932 | else | |
1933 | throw runtime_error( | |
1934 | "walletpassphrase <passphrase> <timeout>\n" | |
1935 | "Stores the wallet decryption key in memory for <timeout> seconds."); | |
1936 | ||
6e263a5f | 1937 | // No need to check return values, because the wallet was unlocked above |
1a62587e | 1938 | pwalletMain->UpdateNullifierNoteMap(); |
92f2c1fe GA |
1939 | pwalletMain->TopUpKeyPool(); |
1940 | ||
51ed9ec9 | 1941 | int64_t nSleepTime = params[1].get_int64(); |
92f2c1fe GA |
1942 | LOCK(cs_nWalletUnlockTime); |
1943 | nWalletUnlockTime = GetTime() + nSleepTime; | |
1944 | RPCRunLater("lockwallet", boost::bind(LockWallet, pwalletMain), nSleepTime); | |
e3bc5698 | 1945 | |
ed21d5bd | 1946 | return NullUniValue; |
e3bc5698 JG |
1947 | } |
1948 | ||
1949 | ||
d014114d | 1950 | UniValue walletpassphrasechange(const UniValue& params, bool fHelp) |
e3bc5698 | 1951 | { |
b9fb692d | 1952 | if (!EnsureWalletIsAvailable(fHelp)) |
9756b7bd | 1953 | return NullUniValue; |
70454796 | 1954 | |
e3bc5698 JG |
1955 | if (pwalletMain->IsCrypted() && (fHelp || params.size() != 2)) |
1956 | throw runtime_error( | |
a6099ef3 | 1957 | "walletpassphrasechange \"oldpassphrase\" \"newpassphrase\"\n" |
1958 | "\nChanges the wallet passphrase from 'oldpassphrase' to 'newpassphrase'.\n" | |
1959 | "\nArguments:\n" | |
1960 | "1. \"oldpassphrase\" (string) The current passphrase\n" | |
1961 | "2. \"newpassphrase\" (string) The new passphrase\n" | |
1962 | "\nExamples:\n" | |
1963 | + HelpExampleCli("walletpassphrasechange", "\"old one\" \"new one\"") | |
1964 | + HelpExampleRpc("walletpassphrasechange", "\"old one\", \"new one\"") | |
1965 | ); | |
1966 | ||
4401b2d7 EL |
1967 | LOCK2(cs_main, pwalletMain->cs_wallet); |
1968 | ||
e3bc5698 JG |
1969 | if (fHelp) |
1970 | return true; | |
1971 | if (!pwalletMain->IsCrypted()) | |
738835d7 | 1972 | throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletpassphrasechange was called."); |
e3bc5698 JG |
1973 | |
1974 | // TODO: get rid of these .c_str() calls by implementing SecureString::operator=(std::string) | |
1975 | // Alternately, find a way to make params[0] mlock()'d to begin with. | |
1976 | SecureString strOldWalletPass; | |
1977 | strOldWalletPass.reserve(100); | |
1978 | strOldWalletPass = params[0].get_str().c_str(); | |
1979 | ||
1980 | SecureString strNewWalletPass; | |
1981 | strNewWalletPass.reserve(100); | |
1982 | strNewWalletPass = params[1].get_str().c_str(); | |
1983 | ||
1984 | if (strOldWalletPass.length() < 1 || strNewWalletPass.length() < 1) | |
1985 | throw runtime_error( | |
1986 | "walletpassphrasechange <oldpassphrase> <newpassphrase>\n" | |
1987 | "Changes the wallet passphrase from <oldpassphrase> to <newpassphrase>."); | |
1988 | ||
1989 | if (!pwalletMain->ChangeWalletPassphrase(strOldWalletPass, strNewWalletPass)) | |
738835d7 | 1990 | throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect."); |
e3bc5698 | 1991 | |
ed21d5bd | 1992 | return NullUniValue; |
e3bc5698 JG |
1993 | } |
1994 | ||
1995 | ||
d014114d | 1996 | UniValue walletlock(const UniValue& params, bool fHelp) |
e3bc5698 | 1997 | { |
b9fb692d | 1998 | if (!EnsureWalletIsAvailable(fHelp)) |
9756b7bd | 1999 | return NullUniValue; |
70454796 | 2000 | |
e3bc5698 JG |
2001 | if (pwalletMain->IsCrypted() && (fHelp || params.size() != 0)) |
2002 | throw runtime_error( | |
2003 | "walletlock\n" | |
a6099ef3 | 2004 | "\nRemoves the wallet encryption key from memory, locking the wallet.\n" |
e3bc5698 | 2005 | "After calling this method, you will need to call walletpassphrase again\n" |
a6099ef3 | 2006 | "before being able to call any methods which require the wallet to be unlocked.\n" |
2007 | "\nExamples:\n" | |
2008 | "\nSet the passphrase for 2 minutes to perform a transaction\n" | |
2009 | + HelpExampleCli("walletpassphrase", "\"my pass phrase\" 120") + | |
2010 | "\nPerform a send (requires passphrase set)\n" | |
70454796 | 2011 | + HelpExampleCli("sendtoaddress", "\"t1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 1.0") + |
a6099ef3 | 2012 | "\nClear the passphrase since we are done before 2 minutes is up\n" |
2013 | + HelpExampleCli("walletlock", "") + | |
2014 | "\nAs json rpc call\n" | |
2015 | + HelpExampleRpc("walletlock", "") | |
2016 | ); | |
2017 | ||
4401b2d7 EL |
2018 | LOCK2(cs_main, pwalletMain->cs_wallet); |
2019 | ||
e3bc5698 JG |
2020 | if (fHelp) |
2021 | return true; | |
2022 | if (!pwalletMain->IsCrypted()) | |
738835d7 | 2023 | throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletlock was called."); |
e3bc5698 JG |
2024 | |
2025 | { | |
2026 | LOCK(cs_nWalletUnlockTime); | |
2027 | pwalletMain->Lock(); | |
2028 | nWalletUnlockTime = 0; | |
2029 | } | |
2030 | ||
ed21d5bd | 2031 | return NullUniValue; |
e3bc5698 JG |
2032 | } |
2033 | ||
2034 | ||
d014114d | 2035 | UniValue encryptwallet(const UniValue& params, bool fHelp) |
e3bc5698 | 2036 | { |
b9fb692d | 2037 | if (!EnsureWalletIsAvailable(fHelp)) |
9756b7bd | 2038 | return NullUniValue; |
62c0aa9e | 2039 | |
66dfcc13 LR |
2040 | string enableArg = "developerencryptwallet"; |
2041 | auto fEnableWalletEncryption = fExperimentalMode && GetBoolArg("-" + enableArg, false); | |
62c0aa9e JG |
2042 | |
2043 | std::string strWalletEncryptionDisabledMsg = ""; | |
2044 | if (!fEnableWalletEncryption) { | |
66dfcc13 | 2045 | strWalletEncryptionDisabledMsg = experimentalDisabledHelpMsg("encryptwallet", enableArg); |
62c0aa9e JG |
2046 | } |
2047 | ||
e3bc5698 JG |
2048 | if (!pwalletMain->IsCrypted() && (fHelp || params.size() != 1)) |
2049 | throw runtime_error( | |
a6099ef3 | 2050 | "encryptwallet \"passphrase\"\n" |
62c0aa9e | 2051 | + strWalletEncryptionDisabledMsg + |
a6099ef3 | 2052 | "\nEncrypts the wallet with 'passphrase'. This is for first time encryption.\n" |
2053 | "After this, any calls that interact with private keys such as sending or signing \n" | |
2054 | "will require the passphrase to be set prior the making these calls.\n" | |
2055 | "Use the walletpassphrase call for this, and then walletlock call.\n" | |
2056 | "If the wallet is already encrypted, use the walletpassphrasechange call.\n" | |
2057 | "Note that this will shutdown the server.\n" | |
2058 | "\nArguments:\n" | |
2059 | "1. \"passphrase\" (string) The pass phrase to encrypt the wallet with. It must be at least 1 character, but should be long.\n" | |
2060 | "\nExamples:\n" | |
2061 | "\nEncrypt you wallet\n" | |
2062 | + HelpExampleCli("encryptwallet", "\"my pass phrase\"") + | |
c1652849 | 2063 | "\nNow set the passphrase to use the wallet, such as for signing or sending Zcash\n" |
a6099ef3 | 2064 | + HelpExampleCli("walletpassphrase", "\"my pass phrase\"") + |
2065 | "\nNow we can so something like sign\n" | |
58c4c0bb | 2066 | + HelpExampleCli("signmessage", "\"zcashaddress\" \"test message\"") + |
a6099ef3 | 2067 | "\nNow lock the wallet again by removing the passphrase\n" |
2068 | + HelpExampleCli("walletlock", "") + | |
2069 | "\nAs a json rpc call\n" | |
2070 | + HelpExampleRpc("encryptwallet", "\"my pass phrase\"") | |
2071 | ); | |
2072 | ||
4401b2d7 EL |
2073 | LOCK2(cs_main, pwalletMain->cs_wallet); |
2074 | ||
e3bc5698 JG |
2075 | if (fHelp) |
2076 | return true; | |
62c0aa9e | 2077 | if (!fEnableWalletEncryption) { |
1532cb75 | 2078 | throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: wallet encryption is disabled."); |
62c0aa9e | 2079 | } |
e3bc5698 | 2080 | if (pwalletMain->IsCrypted()) |
738835d7 | 2081 | throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an encrypted wallet, but encryptwallet was called."); |
e3bc5698 JG |
2082 | |
2083 | // TODO: get rid of this .c_str() by implementing SecureString::operator=(std::string) | |
2084 | // Alternately, find a way to make params[0] mlock()'d to begin with. | |
2085 | SecureString strWalletPass; | |
2086 | strWalletPass.reserve(100); | |
2087 | strWalletPass = params[0].get_str().c_str(); | |
2088 | ||
2089 | if (strWalletPass.length() < 1) | |
2090 | throw runtime_error( | |
2091 | "encryptwallet <passphrase>\n" | |
2092 | "Encrypts the wallet with <passphrase>."); | |
2093 | ||
2094 | if (!pwalletMain->EncryptWallet(strWalletPass)) | |
738835d7 | 2095 | throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: Failed to encrypt the wallet."); |
e3bc5698 JG |
2096 | |
2097 | // BDB seems to have a bad habit of writing old data into | |
2098 | // slack space in .dat files; that is bad if the old data is | |
331544bc | 2099 | // unencrypted private keys. So: |
e3bc5698 | 2100 | StartShutdown(); |
58c4c0bb | 2101 | return "wallet encrypted; Zcash server stopping, restart to run with encrypted wallet. The keypool has been flushed, you need to make a new backup."; |
e3bc5698 JG |
2102 | } |
2103 | ||
d014114d | 2104 | UniValue lockunspent(const UniValue& params, bool fHelp) |
fdbb537d | 2105 | { |
b9fb692d | 2106 | if (!EnsureWalletIsAvailable(fHelp)) |
9756b7bd | 2107 | return NullUniValue; |
70454796 | 2108 | |
fdbb537d JG |
2109 | if (fHelp || params.size() < 1 || params.size() > 2) |
2110 | throw runtime_error( | |
a6099ef3 | 2111 | "lockunspent unlock [{\"txid\":\"txid\",\"vout\":n},...]\n" |
2112 | "\nUpdates list of temporarily unspendable outputs.\n" | |
90fd8737 | 2113 | "Temporarily lock (unlock=false) or unlock (unlock=true) specified transaction outputs.\n" |
c1652849 | 2114 | "A locked transaction output will not be chosen by automatic coin selection, when spending Zcash.\n" |
a6099ef3 | 2115 | "Locks are stored in memory only. Nodes start with zero locked outputs, and the locked output list\n" |
2116 | "is always cleared (by virtue of process exit) when a node stops or fails.\n" | |
2117 | "Also see the listunspent call\n" | |
2118 | "\nArguments:\n" | |
2119 | "1. unlock (boolean, required) Whether to unlock (true) or lock (false) the specified transactions\n" | |
2120 | "2. \"transactions\" (string, required) A json array of objects. Each object the txid (string) vout (numeric)\n" | |
2121 | " [ (json array of json objects)\n" | |
2122 | " {\n" | |
2123 | " \"txid\":\"id\", (string) The transaction id\n" | |
2124 | " \"vout\": n (numeric) The output number\n" | |
2125 | " }\n" | |
2126 | " ,...\n" | |
2127 | " ]\n" | |
2128 | ||
2129 | "\nResult:\n" | |
2130 | "true|false (boolean) Whether the command was successful or not\n" | |
2131 | ||
2132 | "\nExamples:\n" | |
2133 | "\nList the unspent transactions\n" | |
2134 | + HelpExampleCli("listunspent", "") + | |
2135 | "\nLock an unspent transaction\n" | |
2136 | + HelpExampleCli("lockunspent", "false \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"") + | |
2137 | "\nList the locked transactions\n" | |
2138 | + HelpExampleCli("listlockunspent", "") + | |
2139 | "\nUnlock the transaction again\n" | |
2140 | + HelpExampleCli("lockunspent", "true \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"") + | |
2141 | "\nAs a json rpc call\n" | |
2142 | + HelpExampleRpc("lockunspent", "false, \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"") | |
2143 | ); | |
fdbb537d | 2144 | |
4401b2d7 EL |
2145 | LOCK2(cs_main, pwalletMain->cs_wallet); |
2146 | ||
fdbb537d | 2147 | if (params.size() == 1) |
ed21d5bd | 2148 | RPCTypeCheck(params, boost::assign::list_of(UniValue::VBOOL)); |
fdbb537d | 2149 | else |
ed21d5bd | 2150 | RPCTypeCheck(params, boost::assign::list_of(UniValue::VBOOL)(UniValue::VARR)); |
fdbb537d JG |
2151 | |
2152 | bool fUnlock = params[0].get_bool(); | |
2153 | ||
2154 | if (params.size() == 1) { | |
2155 | if (fUnlock) | |
2156 | pwalletMain->UnlockAllCoins(); | |
2157 | return true; | |
2158 | } | |
2159 | ||
851f58f9 | 2160 | UniValue outputs = params[1].get_array(); |
cc71666a | 2161 | for (size_t idx = 0; idx < outputs.size(); idx++) { |
ed21d5bd JG |
2162 | const UniValue& output = outputs[idx]; |
2163 | if (!output.isObject()) | |
15117692 | 2164 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected object"); |
d014114d | 2165 | const UniValue& o = output.get_obj(); |
fdbb537d | 2166 | |
ed21d5bd | 2167 | RPCTypeCheckObj(o, boost::assign::map_list_of("txid", UniValue::VSTR)("vout", UniValue::VNUM)); |
fdbb537d JG |
2168 | |
2169 | string txid = find_value(o, "txid").get_str(); | |
2170 | if (!IsHex(txid)) | |
15117692 | 2171 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected hex txid"); |
fdbb537d JG |
2172 | |
2173 | int nOutput = find_value(o, "vout").get_int(); | |
2174 | if (nOutput < 0) | |
15117692 | 2175 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout must be positive"); |
fdbb537d | 2176 | |
34cdc411 | 2177 | COutPoint outpt(uint256S(txid), nOutput); |
fdbb537d JG |
2178 | |
2179 | if (fUnlock) | |
2180 | pwalletMain->UnlockCoin(outpt); | |
2181 | else | |
2182 | pwalletMain->LockCoin(outpt); | |
2183 | } | |
2184 | ||
2185 | return true; | |
2186 | } | |
2187 | ||
d014114d | 2188 | UniValue listlockunspent(const UniValue& params, bool fHelp) |
fdbb537d | 2189 | { |
b9fb692d | 2190 | if (!EnsureWalletIsAvailable(fHelp)) |
9756b7bd | 2191 | return NullUniValue; |
70454796 | 2192 | |
fdbb537d JG |
2193 | if (fHelp || params.size() > 0) |
2194 | throw runtime_error( | |
2195 | "listlockunspent\n" | |
a6099ef3 | 2196 | "\nReturns list of temporarily unspendable outputs.\n" |
2197 | "See the lockunspent call to lock and unlock transactions for spending.\n" | |
2198 | "\nResult:\n" | |
2199 | "[\n" | |
2200 | " {\n" | |
2201 | " \"txid\" : \"transactionid\", (string) The transaction id locked\n" | |
2202 | " \"vout\" : n (numeric) The vout value\n" | |
2203 | " }\n" | |
2204 | " ,...\n" | |
2205 | "]\n" | |
2206 | "\nExamples:\n" | |
2207 | "\nList the unspent transactions\n" | |
2208 | + HelpExampleCli("listunspent", "") + | |
2209 | "\nLock an unspent transaction\n" | |
2210 | + HelpExampleCli("lockunspent", "false \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"") + | |
2211 | "\nList the locked transactions\n" | |
2212 | + HelpExampleCli("listlockunspent", "") + | |
2213 | "\nUnlock the transaction again\n" | |
2214 | + HelpExampleCli("lockunspent", "true \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"") + | |
2215 | "\nAs a json rpc call\n" | |
2216 | + HelpExampleRpc("listlockunspent", "") | |
2217 | ); | |
fdbb537d | 2218 | |
4401b2d7 EL |
2219 | LOCK2(cs_main, pwalletMain->cs_wallet); |
2220 | ||
fdbb537d JG |
2221 | vector<COutPoint> vOutpts; |
2222 | pwalletMain->ListLockedCoins(vOutpts); | |
2223 | ||
38fc4b70 | 2224 | UniValue ret(UniValue::VARR); |
fdbb537d JG |
2225 | |
2226 | BOOST_FOREACH(COutPoint &outpt, vOutpts) { | |
38fc4b70 | 2227 | UniValue o(UniValue::VOBJ); |
fdbb537d JG |
2228 | |
2229 | o.push_back(Pair("txid", outpt.hash.GetHex())); | |
2230 | o.push_back(Pair("vout", (int)outpt.n)); | |
2231 | ret.push_back(o); | |
2232 | } | |
2233 | ||
2234 | return ret; | |
2235 | } | |
2236 | ||
d014114d | 2237 | UniValue settxfee(const UniValue& params, bool fHelp) |
a943bde6 | 2238 | { |
b9fb692d | 2239 | if (!EnsureWalletIsAvailable(fHelp)) |
9756b7bd | 2240 | return NullUniValue; |
70454796 | 2241 | |
a943bde6 WL |
2242 | if (fHelp || params.size() < 1 || params.size() > 1) |
2243 | throw runtime_error( | |
2244 | "settxfee amount\n" | |
6943cb9b | 2245 | "\nSet the transaction fee per kB.\n" |
a943bde6 | 2246 | "\nArguments:\n" |
091b2116 | 2247 | "1. amount (numeric, required) The transaction fee in " + CURRENCY_UNIT + "/kB rounded to the nearest 0.00000001\n" |
a943bde6 WL |
2248 | "\nResult\n" |
2249 | "true|false (boolean) Returns true if successful\n" | |
2250 | "\nExamples:\n" | |
2251 | + HelpExampleCli("settxfee", "0.00001") | |
2252 | + HelpExampleRpc("settxfee", "0.00001") | |
2253 | ); | |
2254 | ||
4401b2d7 EL |
2255 | LOCK2(cs_main, pwalletMain->cs_wallet); |
2256 | ||
a943bde6 | 2257 | // Amount |
e76a3849 | 2258 | CAmount nAmount = AmountFromValue(params[0]); |
a943bde6 | 2259 | |
c6cb21d1 | 2260 | payTxFee = CFeeRate(nAmount, 1000); |
a943bde6 WL |
2261 | return true; |
2262 | } | |
2263 | ||
d014114d | 2264 | UniValue getwalletinfo(const UniValue& params, bool fHelp) |
a00ebb51 | 2265 | { |
b9fb692d | 2266 | if (!EnsureWalletIsAvailable(fHelp)) |
9756b7bd | 2267 | return NullUniValue; |
70454796 | 2268 | |
a00ebb51 DN |
2269 | if (fHelp || params.size() != 0) |
2270 | throw runtime_error( | |
2271 | "getwalletinfo\n" | |
2272 | "Returns an object containing various wallet state info.\n" | |
2273 | "\nResult:\n" | |
2274 | "{\n" | |
2275 | " \"walletversion\": xxxxx, (numeric) the wallet version\n" | |
091b2116 RN |
2276 | " \"balance\": xxxxxxx, (numeric) the total confirmed balance of the wallet in " + CURRENCY_UNIT + "\n" |
2277 | " \"unconfirmed_balance\": xxx, (numeric) the total unconfirmed balance of the wallet in " + CURRENCY_UNIT + "\n" | |
2278 | " \"immature_balance\": xxxxxx, (numeric) the total immature balance of the wallet in " + CURRENCY_UNIT + "\n" | |
a00ebb51 DN |
2279 | " \"txcount\": xxxxxxx, (numeric) the total number of transactions in the wallet\n" |
2280 | " \"keypoololdest\": xxxxxx, (numeric) the timestamp (seconds since GMT epoch) of the oldest pre-generated key in the key pool\n" | |
2281 | " \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated\n" | |
2282 | " \"unlocked_until\": ttt, (numeric) the timestamp in seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is unlocked for transfers, or 0 if the wallet is locked\n" | |
091b2116 | 2283 | " \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n" |
82e71233 | 2284 | " \"seedfp\": \"uint256\", (string) the BLAKE2b-256 hash of the HD seed\n" |
a00ebb51 DN |
2285 | "}\n" |
2286 | "\nExamples:\n" | |
2287 | + HelpExampleCli("getwalletinfo", "") | |
2288 | + HelpExampleRpc("getwalletinfo", "") | |
2289 | ); | |
2290 | ||
4401b2d7 EL |
2291 | LOCK2(cs_main, pwalletMain->cs_wallet); |
2292 | ||
38fc4b70 | 2293 | UniValue obj(UniValue::VOBJ); |
a00ebb51 DN |
2294 | obj.push_back(Pair("walletversion", pwalletMain->GetVersion())); |
2295 | obj.push_back(Pair("balance", ValueFromAmount(pwalletMain->GetBalance()))); | |
8024d67d GM |
2296 | obj.push_back(Pair("unconfirmed_balance", ValueFromAmount(pwalletMain->GetUnconfirmedBalance()))); |
2297 | obj.push_back(Pair("immature_balance", ValueFromAmount(pwalletMain->GetImmatureBalance()))); | |
a00ebb51 | 2298 | obj.push_back(Pair("txcount", (int)pwalletMain->mapWallet.size())); |
d56e30ca | 2299 | obj.push_back(Pair("keypoololdest", pwalletMain->GetOldestKeyPoolTime())); |
a00ebb51 DN |
2300 | obj.push_back(Pair("keypoolsize", (int)pwalletMain->GetKeyPoolSize())); |
2301 | if (pwalletMain->IsCrypted()) | |
d56e30ca | 2302 | obj.push_back(Pair("unlocked_until", nWalletUnlockTime)); |
6699b425 | 2303 | obj.push_back(Pair("paytxfee", ValueFromAmount(payTxFee.GetFeePerK()))); |
82e71233 JS |
2304 | uint256 seedFp = pwalletMain->GetHDChain().seedFp; |
2305 | if (!seedFp.IsNull()) | |
2306 | obj.push_back(Pair("seedfp", seedFp.GetHex())); | |
a00ebb51 DN |
2307 | return obj; |
2308 | } | |
0f5954c4 | 2309 | |
d014114d | 2310 | UniValue resendwallettransactions(const UniValue& params, bool fHelp) |
0f5954c4 | 2311 | { |
b9fb692d | 2312 | if (!EnsureWalletIsAvailable(fHelp)) |
9756b7bd | 2313 | return NullUniValue; |
70454796 | 2314 | |
0f5954c4 GA |
2315 | if (fHelp || params.size() != 0) |
2316 | throw runtime_error( | |
2317 | "resendwallettransactions\n" | |
2318 | "Immediately re-broadcast unconfirmed wallet transactions to all peers.\n" | |
2319 | "Intended only for testing; the wallet code periodically re-broadcasts\n" | |
2320 | "automatically.\n" | |
2321 | "Returns array of transaction ids that were re-broadcast.\n" | |
2322 | ); | |
2323 | ||
2324 | LOCK2(cs_main, pwalletMain->cs_wallet); | |
2325 | ||
2326 | std::vector<uint256> txids = pwalletMain->ResendWalletTransactionsBefore(GetTime()); | |
38fc4b70 | 2327 | UniValue result(UniValue::VARR); |
0f5954c4 GA |
2328 | BOOST_FOREACH(const uint256& txid, txids) |
2329 | { | |
2330 | result.push_back(txid.ToString()); | |
2331 | } | |
2332 | return result; | |
2333 | } | |
0b9dc9c8 | 2334 | |
d014114d | 2335 | UniValue listunspent(const UniValue& params, bool fHelp) |
0b9dc9c8 JS |
2336 | { |
2337 | if (!EnsureWalletIsAvailable(fHelp)) | |
9756b7bd | 2338 | return NullUniValue; |
70454796 | 2339 | |
0b9dc9c8 | 2340 | if (fHelp || params.size() > 3) |
ea9e82df JS |
2341 | throw runtime_error( |
2342 | "listunspent ( minconf maxconf [\"address\",...] )\n" | |
2343 | "\nReturns array of unspent transaction outputs\n" | |
2344 | "with between minconf and maxconf (inclusive) confirmations.\n" | |
2345 | "Optionally filter to only include txouts paid to specified addresses.\n" | |
2346 | "Results are an array of Objects, each of which has:\n" | |
2347 | "{txid, vout, scriptPubKey, amount, confirmations}\n" | |
2348 | "\nArguments:\n" | |
2349 | "1. minconf (numeric, optional, default=1) The minimum confirmations to filter\n" | |
2350 | "2. maxconf (numeric, optional, default=9999999) The maximum confirmations to filter\n" | |
d2c1e4a8 | 2351 | "3. \"addresses\" (string) A json array of Zcash addresses to filter\n" |
ea9e82df | 2352 | " [\n" |
d2c1e4a8 | 2353 | " \"address\" (string) Zcash address\n" |
ea9e82df JS |
2354 | " ,...\n" |
2355 | " ]\n" | |
2356 | "\nResult\n" | |
2357 | "[ (array of json object)\n" | |
2358 | " {\n" | |
181c511c | 2359 | " \"txid\" : \"txid\", (string) the transaction id \n" |
ea9e82df | 2360 | " \"vout\" : n, (numeric) the vout value\n" |
d77a0ac4 | 2361 | " \"generated\" : true|false (boolean) true if txout is a coinbase transaction output\n" |
181c511c PB |
2362 | " \"address\" : \"address\", (string) the Zcash address\n" |
2363 | " \"account\" : \"account\", (string) DEPRECATED. The associated account, or \"\" for the default account\n" | |
2364 | " \"scriptPubKey\" : \"key\", (string) the script key\n" | |
091b2116 | 2365 | " \"amount\" : x.xxx, (numeric) the transaction amount in " + CURRENCY_UNIT + "\n" |
181c511c PB |
2366 | " \"confirmations\" : n, (numeric) The number of confirmations\n" |
2367 | " \"redeemScript\" : n (string) The redeemScript if scriptPubKey is P2SH\n" | |
2368 | " \"spendable\" : xxx (bool) Whether we have the private keys to spend this output\n" | |
ea9e82df JS |
2369 | " }\n" |
2370 | " ,...\n" | |
2371 | "]\n" | |
2372 | ||
2373 | "\nExamples\n" | |
2374 | + HelpExampleCli("listunspent", "") | |
70454796 JG |
2375 | + HelpExampleCli("listunspent", "6 9999999 \"[\\\"t1PGFqEzfmQch1gKD3ra4k18PNj3tTUUSqg\\\",\\\"t1LtvqCaApEdUGFkpKMM4MstjcaL4dKg8SP\\\"]\"") |
2376 | + HelpExampleRpc("listunspent", "6, 9999999 \"[\\\"t1PGFqEzfmQch1gKD3ra4k18PNj3tTUUSqg\\\",\\\"t1LtvqCaApEdUGFkpKMM4MstjcaL4dKg8SP\\\"]\"") | |
ea9e82df JS |
2377 | ); |
2378 | ||
9756b7bd | 2379 | RPCTypeCheck(params, boost::assign::list_of(UniValue::VNUM)(UniValue::VNUM)(UniValue::VARR)); |
ea9e82df | 2380 | |
0b9dc9c8 JS |
2381 | int nMinDepth = 1; |
2382 | if (params.size() > 0) | |
ea9e82df JS |
2383 | nMinDepth = params[0].get_int(); |
2384 | ||
0b9dc9c8 JS |
2385 | int nMaxDepth = 9999999; |
2386 | if (params.size() > 1) | |
ea9e82df JS |
2387 | nMaxDepth = params[1].get_int(); |
2388 | ||
07444da1 | 2389 | std::set<CTxDestination> destinations; |
0b9dc9c8 | 2390 | if (params.size() > 2) { |
851f58f9 | 2391 | UniValue inputs = params[2].get_array(); |
cc71666a | 2392 | for (size_t idx = 0; idx < inputs.size(); idx++) { |
d014114d | 2393 | const UniValue& input = inputs[idx]; |
07444da1 PW |
2394 | CTxDestination dest = DecodeDestination(input.get_str()); |
2395 | if (!IsValidDestination(dest)) { | |
2396 | throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Zcash address: ") + input.get_str()); | |
2397 | } | |
2398 | if (!destinations.insert(dest).second) { | |
2399 | throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated address: ") + input.get_str()); | |
2400 | } | |
0b9dc9c8 JS |
2401 | } |
2402 | } | |
ea9e82df | 2403 | |
38fc4b70 | 2404 | UniValue results(UniValue::VARR); |
0b9dc9c8 JS |
2405 | vector<COutput> vecOutputs; |
2406 | assert(pwalletMain != NULL); | |
2407 | LOCK2(cs_main, pwalletMain->cs_wallet); | |
219953ce | 2408 | pwalletMain->AvailableCoins(vecOutputs, false, NULL, true); |
0b9dc9c8 JS |
2409 | BOOST_FOREACH(const COutput& out, vecOutputs) { |
2410 | if (out.nDepth < nMinDepth || out.nDepth > nMaxDepth) | |
ea9e82df JS |
2411 | continue; |
2412 | ||
181c511c PB |
2413 | CTxDestination address; |
2414 | const CScript& scriptPubKey = out.tx->vout[out.i].scriptPubKey; | |
2415 | bool fValidAddress = ExtractDestination(scriptPubKey, address); | |
ea9e82df | 2416 | |
07444da1 | 2417 | if (destinations.size() && (!fValidAddress || !destinations.count(address))) |
181c511c | 2418 | continue; |
ea9e82df | 2419 | |
38fc4b70 | 2420 | UniValue entry(UniValue::VOBJ); |
805344dc | 2421 | entry.push_back(Pair("txid", out.tx->GetHash().GetHex())); |
0b9dc9c8 | 2422 | entry.push_back(Pair("vout", out.i)); |
d77a0ac4 | 2423 | entry.push_back(Pair("generated", out.tx->IsCoinBase())); |
181c511c PB |
2424 | |
2425 | if (fValidAddress) { | |
07444da1 | 2426 | entry.push_back(Pair("address", EncodeDestination(address))); |
181c511c | 2427 | |
0b9dc9c8 | 2428 | if (pwalletMain->mapAddressBook.count(address)) |
ea9e82df | 2429 | entry.push_back(Pair("account", pwalletMain->mapAddressBook[address].name)); |
181c511c PB |
2430 | |
2431 | if (scriptPubKey.IsPayToScriptHash()) { | |
8b08d953 | 2432 | const CScriptID& hash = boost::get<CScriptID>(address); |
0b9dc9c8 JS |
2433 | CScript redeemScript; |
2434 | if (pwalletMain->GetCScript(hash, redeemScript)) | |
ea9e82df | 2435 | entry.push_back(Pair("redeemScript", HexStr(redeemScript.begin(), redeemScript.end()))); |
0b9dc9c8 JS |
2436 | } |
2437 | } | |
181c511c PB |
2438 | |
2439 | entry.push_back(Pair("scriptPubKey", HexStr(scriptPubKey.begin(), scriptPubKey.end()))); | |
2440 | entry.push_back(Pair("amount", ValueFromAmount(out.tx->vout[out.i].nValue))); | |
2441 | entry.push_back(Pair("confirmations", out.nDepth)); | |
0b9dc9c8 JS |
2442 | entry.push_back(Pair("spendable", out.fSpendable)); |
2443 | results.push_back(entry); | |
2444 | } | |
ea9e82df | 2445 | |
0b9dc9c8 | 2446 | return results; |
c9fd9078 | 2447 | } |
730790f7 | 2448 | |
d72c19a6 S |
2449 | |
2450 | UniValue z_listunspent(const UniValue& params, bool fHelp) | |
2451 | { | |
2452 | if (!EnsureWalletIsAvailable(fHelp)) | |
2453 | return NullUniValue; | |
2454 | ||
2455 | if (fHelp || params.size() > 4) | |
2456 | throw runtime_error( | |
2457 | "z_listunspent ( minconf maxconf includeWatchonly [\"zaddr\",...] )\n" | |
2458 | "\nReturns array of unspent shielded notes with between minconf and maxconf (inclusive) confirmations.\n" | |
2459 | "Optionally filter to only include notes sent to specified addresses.\n" | |
2460 | "When minconf is 0, unspent notes with zero confirmations are returned, even though they are not immediately spendable.\n" | |
2461 | "Results are an array of Objects, each of which has:\n" | |
5f57babd S |
2462 | "{txid, jsindex, jsoutindex, confirmations, address, amount, memo} (Sprout)\n" |
2463 | "{txid, outindex, confirmations, address, amount, memo} (Sapling)\n" | |
d72c19a6 S |
2464 | "\nArguments:\n" |
2465 | "1. minconf (numeric, optional, default=1) The minimum confirmations to filter\n" | |
2466 | "2. maxconf (numeric, optional, default=9999999) The maximum confirmations to filter\n" | |
2467 | "3. includeWatchonly (bool, optional, default=false) Also include watchonly addresses (see 'z_importviewingkey')\n" | |
66795a40 | 2468 | "4. \"addresses\" (string) A json array of zaddrs (both Sprout and Sapling) to filter on. Duplicate addresses not allowed.\n" |
d72c19a6 S |
2469 | " [\n" |
2470 | " \"address\" (string) zaddr\n" | |
2471 | " ,...\n" | |
2472 | " ]\n" | |
2473 | "\nResult\n" | |
2474 | "[ (array of json object)\n" | |
2475 | " {\n" | |
2476 | " \"txid\" : \"txid\", (string) the transaction id \n" | |
4c646bb4 GD |
2477 | " \"jsindex\" (sprout) : n, (numeric) the joinsplit index\n" |
2478 | " \"jsoutindex\" (sprout) : n, (numeric) the output index of the joinsplit\n" | |
2479 | " \"outindex\" (sapling) : n, (numeric) the output index\n" | |
2480 | " \"confirmations\" : n, (numeric) the number of confirmations\n" | |
2481 | " \"spendable\" : true|false, (boolean) true if note can be spent by wallet, false if address is watchonly\n" | |
d72c19a6 S |
2482 | " \"address\" : \"address\", (string) the shielded address\n" |
2483 | " \"amount\": xxxxx, (numeric) the amount of value in the note\n" | |
2484 | " \"memo\": xxxxx, (string) hexademical string representation of memo field\n" | |
0646f749 | 2485 | " \"change\": true|false, (boolean) true if the address that received the note is also one of the sending addresses\n" |
d72c19a6 S |
2486 | " }\n" |
2487 | " ,...\n" | |
2488 | "]\n" | |
2489 | ||
2490 | "\nExamples\n" | |
2491 | + HelpExampleCli("z_listunspent", "") | |
2492 | + HelpExampleCli("z_listunspent", "6 9999999 false \"[\\\"ztbx5DLDxa5ZLFTchHhoPNkKs57QzSyib6UqXpEdy76T1aUdFxJt1w9318Z8DJ73XzbnWHKEZP9Yjg712N5kMmP4QzS9iC9\\\",\\\"ztfaW34Gj9FrnGUEf833ywDVL62NWXBM81u6EQnM6VR45eYnXhwztecW1SjxA7JrmAXKJhxhj3vDNEpVCQoSvVoSpmbhtjf\\\"]\"") | |
2493 | + HelpExampleRpc("z_listunspent", "6 9999999 false \"[\\\"ztbx5DLDxa5ZLFTchHhoPNkKs57QzSyib6UqXpEdy76T1aUdFxJt1w9318Z8DJ73XzbnWHKEZP9Yjg712N5kMmP4QzS9iC9\\\",\\\"ztfaW34Gj9FrnGUEf833ywDVL62NWXBM81u6EQnM6VR45eYnXhwztecW1SjxA7JrmAXKJhxhj3vDNEpVCQoSvVoSpmbhtjf\\\"]\"") | |
2494 | ); | |
2495 | ||
2496 | RPCTypeCheck(params, boost::assign::list_of(UniValue::VNUM)(UniValue::VNUM)(UniValue::VBOOL)(UniValue::VARR)); | |
2497 | ||
2498 | int nMinDepth = 1; | |
2499 | if (params.size() > 0) { | |
2500 | nMinDepth = params[0].get_int(); | |
2501 | } | |
2502 | if (nMinDepth < 0) { | |
2503 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Minimum number of confirmations cannot be less than 0"); | |
2504 | } | |
2505 | ||
2506 | int nMaxDepth = 9999999; | |
2507 | if (params.size() > 1) { | |
2508 | nMaxDepth = params[1].get_int(); | |
2509 | } | |
2510 | if (nMaxDepth < nMinDepth) { | |
2511 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Maximum number of confirmations must be greater or equal to the minimum number of confirmations"); | |
2512 | } | |
2513 | ||
2514 | std::set<libzcash::PaymentAddress> zaddrs = {}; | |
2515 | ||
2516 | bool fIncludeWatchonly = false; | |
2517 | if (params.size() > 2) { | |
2518 | fIncludeWatchonly = params[2].get_bool(); | |
2519 | } | |
2520 | ||
2521 | LOCK2(cs_main, pwalletMain->cs_wallet); | |
2522 | ||
2523 | // User has supplied zaddrs to filter on | |
2524 | if (params.size() > 3) { | |
2525 | UniValue addresses = params[3].get_array(); | |
2526 | if (addresses.size()==0) | |
2527 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, addresses array is empty."); | |
2528 | ||
2529 | // Keep track of addresses to spot duplicates | |
2530 | set<std::string> setAddress; | |
2531 | ||
2532 | // Sources | |
2533 | for (const UniValue& o : addresses.getValues()) { | |
2534 | if (!o.isStr()) { | |
2535 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected string"); | |
2536 | } | |
2537 | string address = o.get_str(); | |
80ed13d5 | 2538 | auto zaddr = DecodePaymentAddress(address); |
27a6a99c | 2539 | if (!IsValidPaymentAddress(zaddr)) { |
d72c19a6 S |
2540 | throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, address is not a valid zaddr: ") + address); |
2541 | } | |
27a6a99c LR |
2542 | auto hasSpendingKey = boost::apply_visitor(HaveSpendingKeyForPaymentAddress(pwalletMain), zaddr); |
2543 | if (!fIncludeWatchonly && !hasSpendingKey) { | |
2544 | throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, spending key for address does not belong to wallet: ") + address); | |
2545 | } | |
2546 | zaddrs.insert(zaddr); | |
d72c19a6 S |
2547 | |
2548 | if (setAddress.count(address)) { | |
2549 | throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, duplicated address: ") + address); | |
2550 | } | |
2551 | setAddress.insert(address); | |
2552 | } | |
2553 | } | |
2554 | else { | |
2555 | // User did not provide zaddrs, so use default i.e. all addresses | |
e5eab182 | 2556 | std::set<libzcash::SproutPaymentAddress> sproutzaddrs = {}; |
25d5e80c | 2557 | pwalletMain->GetSproutPaymentAddresses(sproutzaddrs); |
66795a40 | 2558 | |
cd1c6e37 | 2559 | // Sapling support |
66795a40 JG |
2560 | std::set<libzcash::SaplingPaymentAddress> saplingzaddrs = {}; |
2561 | pwalletMain->GetSaplingPaymentAddresses(saplingzaddrs); | |
2562 | ||
e5eab182 | 2563 | zaddrs.insert(sproutzaddrs.begin(), sproutzaddrs.end()); |
66795a40 | 2564 | zaddrs.insert(saplingzaddrs.begin(), saplingzaddrs.end()); |
d72c19a6 S |
2565 | } |
2566 | ||
2567 | UniValue results(UniValue::VARR); | |
2568 | ||
2569 | if (zaddrs.size() > 0) { | |
a630f503 | 2570 | std::vector<SproutNoteEntry> sproutEntries; |
9396b85d | 2571 | std::vector<SaplingNoteEntry> saplingEntries; |
e92414f9 | 2572 | pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, zaddrs, nMinDepth, nMaxDepth, true, !fIncludeWatchonly, false); |
0646f749 | 2573 | std::set<std::pair<PaymentAddress, uint256>> nullifierSet = pwalletMain->GetNullifiersForAddresses(zaddrs); |
66795a40 | 2574 | |
9396b85d | 2575 | for (auto & entry : sproutEntries) { |
d72c19a6 | 2576 | UniValue obj(UniValue::VOBJ); |
0646f749 | 2577 | obj.push_back(Pair("txid", entry.jsop.hash.ToString())); |
d72c19a6 S |
2578 | obj.push_back(Pair("jsindex", (int)entry.jsop.js )); |
2579 | obj.push_back(Pair("jsoutindex", (int)entry.jsop.n)); | |
9396b85d | 2580 | obj.push_back(Pair("confirmations", entry.confirmations)); |
79298516 EOW |
2581 | bool hasSproutSpendingKey = pwalletMain->HaveSproutSpendingKey(boost::get<libzcash::SproutPaymentAddress>(entry.address)); |
2582 | obj.push_back(Pair("spendable", hasSproutSpendingKey)); | |
80ed13d5 | 2583 | obj.push_back(Pair("address", EncodePaymentAddress(entry.address))); |
a630f503 E |
2584 | obj.push_back(Pair("amount", ValueFromAmount(CAmount(entry.note.value())))); |
2585 | std::string data(entry.memo.begin(), entry.memo.end()); | |
d72c19a6 | 2586 | obj.push_back(Pair("memo", HexStr(data))); |
79298516 | 2587 | if (hasSproutSpendingKey) { |
e4f0d6a8 | 2588 | obj.push_back(Pair("change", pwalletMain->IsNoteSproutChange(nullifierSet, entry.address, entry.jsop))); |
79298516 | 2589 | } |
d72c19a6 S |
2590 | results.push_back(obj); |
2591 | } | |
66795a40 | 2592 | |
9396b85d | 2593 | for (auto & entry : saplingEntries) { |
5f57babd S |
2594 | UniValue obj(UniValue::VOBJ); |
2595 | obj.push_back(Pair("txid", entry.op.hash.ToString())); | |
2596 | obj.push_back(Pair("outindex", (int)entry.op.n)); | |
9396b85d | 2597 | obj.push_back(Pair("confirmations", entry.confirmations)); |
5f57babd S |
2598 | libzcash::SaplingIncomingViewingKey ivk; |
2599 | libzcash::SaplingFullViewingKey fvk; | |
2600 | pwalletMain->GetSaplingIncomingViewingKey(boost::get<libzcash::SaplingPaymentAddress>(entry.address), ivk); | |
2601 | pwalletMain->GetSaplingFullViewingKey(ivk, fvk); | |
2602 | bool hasSaplingSpendingKey = pwalletMain->HaveSaplingSpendingKey(fvk); | |
2603 | obj.push_back(Pair("spendable", hasSaplingSpendingKey)); | |
2604 | obj.push_back(Pair("address", EncodePaymentAddress(entry.address))); | |
2605 | obj.push_back(Pair("amount", ValueFromAmount(CAmount(entry.note.value())))); // note.value() is equivalent to plaintext.value() | |
2606 | obj.push_back(Pair("memo", HexStr(entry.memo))); | |
2607 | if (hasSaplingSpendingKey) { | |
2608 | obj.push_back(Pair("change", pwalletMain->IsNoteSaplingChange(nullifierSet, entry.address, entry.op))); | |
2609 | } | |
2610 | results.push_back(obj); | |
06f2a8f9 | 2611 | } |
d72c19a6 S |
2612 | } |
2613 | ||
2614 | return results; | |
2615 | } | |
2616 | ||
2617 | ||
3d8013a0 MC |
2618 | UniValue fundrawtransaction(const UniValue& params, bool fHelp) |
2619 | { | |
2620 | if (!EnsureWalletIsAvailable(fHelp)) | |
2621 | return NullUniValue; | |
2622 | ||
2623 | if (fHelp || params.size() != 1) | |
2624 | throw runtime_error( | |
2625 | "fundrawtransaction \"hexstring\"\n" | |
2626 | "\nAdd inputs to a transaction until it has enough in value to meet its out value.\n" | |
2627 | "This will not modify existing inputs, and will add one change output to the outputs.\n" | |
2628 | "Note that inputs which were signed may need to be resigned after completion since in/outputs have been added.\n" | |
2629 | "The inputs added will not be signed, use signrawtransaction for that.\n" | |
2630 | "\nArguments:\n" | |
2631 | "1. \"hexstring\" (string, required) The hex string of the raw transaction\n" | |
2632 | "\nResult:\n" | |
2633 | "{\n" | |
2634 | " \"hex\": \"value\", (string) The resulting raw transaction (hex-encoded string)\n" | |
2635 | " \"fee\": n, (numeric) The fee added to the transaction\n" | |
2636 | " \"changepos\": n (numeric) The position of the added change output, or -1\n" | |
2637 | "}\n" | |
2638 | "\"hex\" \n" | |
2639 | "\nExamples:\n" | |
2640 | "\nCreate a transaction with no inputs\n" | |
2641 | + HelpExampleCli("createrawtransaction", "\"[]\" \"{\\\"myaddress\\\":0.01}\"") + | |
2642 | "\nAdd sufficient unsigned inputs to meet the output value\n" | |
2643 | + HelpExampleCli("fundrawtransaction", "\"rawtransactionhex\"") + | |
2644 | "\nSign the transaction\n" | |
2645 | + HelpExampleCli("signrawtransaction", "\"fundedtransactionhex\"") + | |
2646 | "\nSend the transaction\n" | |
2647 | + HelpExampleCli("sendrawtransaction", "\"signedtransactionhex\"") | |
2648 | ); | |
2649 | ||
2650 | RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR)); | |
2651 | ||
2652 | // parse hex string from parameter | |
2653 | CTransaction origTx; | |
2654 | if (!DecodeHexTx(origTx, params[0].get_str())) | |
2655 | throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); | |
2656 | ||
2657 | CMutableTransaction tx(origTx); | |
2658 | CAmount nFee; | |
2659 | string strFailReason; | |
2660 | int nChangePos = -1; | |
2661 | if(!pwalletMain->FundTransaction(tx, nFee, nChangePos, strFailReason)) | |
2662 | throw JSONRPCError(RPC_INTERNAL_ERROR, strFailReason); | |
2663 | ||
2664 | UniValue result(UniValue::VOBJ); | |
2665 | result.push_back(Pair("hex", EncodeHexTx(tx))); | |
2666 | result.push_back(Pair("changepos", nChangePos)); | |
2667 | result.push_back(Pair("fee", ValueFromAmount(nFee))); | |
2668 | ||
2669 | return result; | |
2670 | } | |
2671 | ||
0d37ae3a | 2672 | UniValue zc_sample_joinsplit(const UniValue& params, bool fHelp) |
1737627c SB |
2673 | { |
2674 | if (fHelp) { | |
2675 | throw runtime_error( | |
2676 | "zcsamplejoinsplit\n" | |
2677 | "\n" | |
2678 | "Perform a joinsplit and return the JSDescription.\n" | |
2679 | ); | |
2680 | } | |
2681 | ||
2682 | LOCK(cs_main); | |
2683 | ||
e1a3461c | 2684 | uint256 joinSplitPubKey; |
4fc309f0 | 2685 | uint256 anchor = SproutMerkleTree().root(); |
34f6ea95 | 2686 | JSDescription samplejoinsplit(true, |
b7a6c321 | 2687 | *pzcashParams, |
e1a3461c | 2688 | joinSplitPubKey, |
1737627c SB |
2689 | anchor, |
2690 | {JSInput(), JSInput()}, | |
2691 | {JSOutput(), JSOutput()}, | |
2692 | 0, | |
2693 | 0); | |
2694 | ||
f55029e7 | 2695 | CDataStream ss(SER_NETWORK, SAPLING_TX_VERSION | (1 << 31)); |
1737627c SB |
2696 | ss << samplejoinsplit; |
2697 | ||
2698 | return HexStr(ss.begin(), ss.end()); | |
2699 | } | |
2700 | ||
0d37ae3a | 2701 | UniValue zc_benchmark(const UniValue& params, bool fHelp) |
6962bb3d TH |
2702 | { |
2703 | if (!EnsureWalletIsAvailable(fHelp)) { | |
0d37ae3a | 2704 | return NullUniValue; |
6962bb3d TH |
2705 | } |
2706 | ||
2707 | if (fHelp || params.size() < 2) { | |
2708 | throw runtime_error( | |
2709 | "zcbenchmark benchmarktype samplecount\n" | |
2710 | "\n" | |
2711 | "Runs a benchmark of the selected type samplecount times,\n" | |
2712 | "returning the running times of each sample.\n" | |
2713 | "\n" | |
2714 | "Output: [\n" | |
2715 | " {\n" | |
2716 | " \"runningtime\": runningtime\n" | |
2717 | " },\n" | |
2718 | " {\n" | |
2719 | " \"runningtime\": runningtime\n" | |
2720 | " }\n" | |
2721 | " ...\n" | |
2722 | "]\n" | |
2723 | ); | |
2724 | } | |
2725 | ||
2726 | LOCK(cs_main); | |
2727 | ||
2728 | std::string benchmarktype = params[0].get_str(); | |
2729 | int samplecount = params[1].get_int(); | |
2730 | ||
2731 | if (samplecount <= 0) { | |
2732 | throw JSONRPCError(RPC_TYPE_ERROR, "Invalid samplecount"); | |
2733 | } | |
2734 | ||
2735 | std::vector<double> sample_times; | |
2736 | ||
1737627c | 2737 | JSDescription samplejoinsplit; |
2fbbde59 SB |
2738 | |
2739 | if (benchmarktype == "verifyjoinsplit") { | |
f55029e7 | 2740 | CDataStream ss(ParseHexV(params[2].get_str(), "js"), SER_NETWORK, SAPLING_TX_VERSION | (1 << 31)); |
1737627c | 2741 | ss >> samplejoinsplit; |
2fbbde59 SB |
2742 | } |
2743 | ||
6962bb3d TH |
2744 | for (int i = 0; i < samplecount; i++) { |
2745 | if (benchmarktype == "sleep") { | |
2746 | sample_times.push_back(benchmark_sleep()); | |
2747 | } else if (benchmarktype == "parameterloading") { | |
2748 | sample_times.push_back(benchmark_parameter_loading()); | |
2749 | } else if (benchmarktype == "createjoinsplit") { | |
4082dcb1 JG |
2750 | if (params.size() < 3) { |
2751 | sample_times.push_back(benchmark_create_joinsplit()); | |
2752 | } else { | |
2753 | int nThreads = params[2].get_int(); | |
2754 | std::vector<double> vals = benchmark_create_joinsplit_threaded(nThreads); | |
2755 | // Divide by nThreads^2 to get average seconds per JoinSplit because | |
2756 | // we are running one JoinSplit per thread. | |
2757 | sample_times.push_back(std::accumulate(vals.begin(), vals.end(), 0.0) / (nThreads*nThreads)); | |
2758 | } | |
6962bb3d | 2759 | } else if (benchmarktype == "verifyjoinsplit") { |
1737627c | 2760 | sample_times.push_back(benchmark_verify_joinsplit(samplejoinsplit)); |
2cc0a252 | 2761 | #ifdef ENABLE_MINING |
bf8def97 | 2762 | } else if (benchmarktype == "solveequihash") { |
f7478de6 | 2763 | if (params.size() < 3) { |
9e52ca32 | 2764 | sample_times.push_back(benchmark_solve_equihash()); |
f7478de6 JG |
2765 | } else { |
2766 | int nThreads = params[2].get_int(); | |
9e52ca32 JG |
2767 | std::vector<double> vals = benchmark_solve_equihash_threaded(nThreads); |
2768 | sample_times.insert(sample_times.end(), vals.begin(), vals.end()); | |
f7478de6 | 2769 | } |
2cc0a252 | 2770 | #endif |
bf8def97 | 2771 | } else if (benchmarktype == "verifyequihash") { |
a1cd1a27 | 2772 | sample_times.push_back(benchmark_verify_equihash()); |
f5edc37f | 2773 | } else if (benchmarktype == "validatelargetx") { |
818b94f9 | 2774 | // Number of inputs in the spending transaction that we will simulate |
ddcee7e1 | 2775 | int nInputs = 11130; |
818b94f9 JG |
2776 | if (params.size() >= 3) { |
2777 | nInputs = params[2].get_int(); | |
2778 | } | |
2779 | sample_times.push_back(benchmark_large_tx(nInputs)); | |
0fbab55b | 2780 | } else if (benchmarktype == "trydecryptnotes") { |
3e5cc59c EOW |
2781 | int nKeys = params[2].get_int(); |
2782 | sample_times.push_back(benchmark_try_decrypt_sprout_notes(nKeys)); | |
89e75c8c | 2783 | } else if (benchmarktype == "trydecryptsaplingnotes") { |
3e5cc59c EOW |
2784 | int nKeys = params[2].get_int(); |
2785 | sample_times.push_back(benchmark_try_decrypt_sapling_notes(nKeys)); | |
0bb3d40f JG |
2786 | } else if (benchmarktype == "incnotewitnesses") { |
2787 | int nTxs = params[2].get_int(); | |
8a1d1930 EOW |
2788 | sample_times.push_back(benchmark_increment_sprout_note_witnesses(nTxs)); |
2789 | } else if (benchmarktype == "incsaplingnotewitnesses") { | |
3e5cc59c | 2790 | int nTxs = params[2].get_int(); |
8a1d1930 | 2791 | sample_times.push_back(benchmark_increment_sapling_note_witnesses(nTxs)); |
c66c731a JG |
2792 | } else if (benchmarktype == "connectblockslow") { |
2793 | if (Params().NetworkIDString() != "regtest") { | |
2794 | throw JSONRPCError(RPC_TYPE_ERROR, "Benchmark must be run in regtest mode"); | |
2795 | } | |
2796 | sample_times.push_back(benchmark_connectblock_slow()); | |
a76174b7 JG |
2797 | } else if (benchmarktype == "sendtoaddress") { |
2798 | if (Params().NetworkIDString() != "regtest") { | |
2799 | throw JSONRPCError(RPC_TYPE_ERROR, "Benchmark must be run in regtest mode"); | |
2800 | } | |
2801 | auto amount = AmountFromValue(params[2]); | |
2802 | sample_times.push_back(benchmark_sendtoaddress(amount)); | |
2e8aefdc AG |
2803 | } else if (benchmarktype == "loadwallet") { |
2804 | if (Params().NetworkIDString() != "regtest") { | |
2805 | throw JSONRPCError(RPC_TYPE_ERROR, "Benchmark must be run in regtest mode"); | |
2806 | } | |
2807 | sample_times.push_back(benchmark_loadwallet()); | |
99dd50c3 JG |
2808 | } else if (benchmarktype == "listunspent") { |
2809 | sample_times.push_back(benchmark_listunspent()); | |
67d2b797 S |
2810 | } else if (benchmarktype == "createsaplingspend") { |
2811 | sample_times.push_back(benchmark_create_sapling_spend()); | |
2812 | } else if (benchmarktype == "createsaplingoutput") { | |
2813 | sample_times.push_back(benchmark_create_sapling_output()); | |
2814 | } else if (benchmarktype == "verifysaplingspend") { | |
2815 | sample_times.push_back(benchmark_verify_sapling_spend()); | |
2816 | } else if (benchmarktype == "verifysaplingoutput") { | |
2817 | sample_times.push_back(benchmark_verify_sapling_output()); | |
6962bb3d TH |
2818 | } else { |
2819 | throw JSONRPCError(RPC_TYPE_ERROR, "Invalid benchmarktype"); | |
2820 | } | |
2821 | } | |
2822 | ||
0d37ae3a | 2823 | UniValue results(UniValue::VARR); |
9e52ca32 | 2824 | for (auto time : sample_times) { |
0d37ae3a | 2825 | UniValue result(UniValue::VOBJ); |
9e52ca32 | 2826 | result.push_back(Pair("runningtime", time)); |
6962bb3d TH |
2827 | results.push_back(result); |
2828 | } | |
2829 | ||
2830 | return results; | |
2831 | } | |
2832 | ||
0d37ae3a | 2833 | UniValue zc_raw_receive(const UniValue& params, bool fHelp) |
a8ac403d | 2834 | { |
f15b9549 | 2835 | if (!EnsureWalletIsAvailable(fHelp)) { |
0d37ae3a | 2836 | return NullUniValue; |
f15b9549 NW |
2837 | } |
2838 | ||
2839 | if (fHelp || params.size() != 2) { | |
2840 | throw runtime_error( | |
eae37941 | 2841 | "zcrawreceive zcsecretkey encryptednote\n" |
f15b9549 | 2842 | "\n" |
ca0ec80b | 2843 | "DEPRECATED. Decrypts encryptednote and checks if the coin commitments\n" |
f15b9549 NW |
2844 | "are in the blockchain as indicated by the \"exists\" result.\n" |
2845 | "\n" | |
2846 | "Output: {\n" | |
2847 | " \"amount\": value,\n" | |
4bc00dc1 | 2848 | " \"note\": noteplaintext,\n" |
f15b9549 NW |
2849 | " \"exists\": exists\n" |
2850 | "}\n" | |
2851 | ); | |
2852 | } | |
2853 | ||
0d37ae3a | 2854 | RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR)(UniValue::VSTR)); |
f15b9549 | 2855 | |
a8ac403d SB |
2856 | LOCK(cs_main); |
2857 | ||
472f75bc | 2858 | auto spendingkey = DecodeSpendingKey(params[0].get_str()); |
e5eab182 | 2859 | if (!IsValidSpendingKey(spendingkey)) { |
472f75bc JG |
2860 | throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid spending key"); |
2861 | } | |
e5eab182 JG |
2862 | if (boost::get<libzcash::SproutSpendingKey>(&spendingkey) == nullptr) { |
2863 | throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Only works with Sprout spending keys"); | |
2864 | } | |
2865 | SproutSpendingKey k = boost::get<libzcash::SproutSpendingKey>(spendingkey); | |
a8ac403d | 2866 | |
6c36a9fe SB |
2867 | uint256 epk; |
2868 | unsigned char nonce; | |
2869 | ZCNoteEncryption::Ciphertext ct; | |
2dc35992 | 2870 | uint256 h_sig; |
6c36a9fe SB |
2871 | |
2872 | { | |
4bc00dc1 | 2873 | CDataStream ssData(ParseHexV(params[1], "encrypted_note"), SER_NETWORK, PROTOCOL_VERSION); |
6c36a9fe SB |
2874 | try { |
2875 | ssData >> nonce; | |
2876 | ssData >> epk; | |
2877 | ssData >> ct; | |
2dc35992 | 2878 | ssData >> h_sig; |
6c36a9fe SB |
2879 | } catch(const std::exception &) { |
2880 | throw runtime_error( | |
4bc00dc1 | 2881 | "encrypted_note could not be decoded" |
6c36a9fe SB |
2882 | ); |
2883 | } | |
2884 | } | |
2885 | ||
642a1caf | 2886 | ZCNoteDecryption decryptor(k.receiving_key()); |
a8ac403d | 2887 | |
5020a936 | 2888 | SproutNotePlaintext npt = SproutNotePlaintext::decrypt( |
2dc35992 SB |
2889 | decryptor, |
2890 | ct, | |
2891 | epk, | |
2892 | h_sig, | |
2893 | nonce | |
2894 | ); | |
e5eab182 | 2895 | SproutPaymentAddress payment_addr = k.address(); |
b230fe68 | 2896 | SproutNote decrypted_note = npt.note(payment_addr); |
a8ac403d SB |
2897 | |
2898 | assert(pwalletMain != NULL); | |
8ea8ef98 | 2899 | std::vector<boost::optional<SproutWitness>> witnesses; |
a8ac403d | 2900 | uint256 anchor; |
2dc35992 | 2901 | uint256 commitment = decrypted_note.cm(); |
4bc00dc1 | 2902 | pwalletMain->WitnessNoteCommitment( |
2dc35992 SB |
2903 | {commitment}, |
2904 | witnesses, | |
2905 | anchor | |
2906 | ); | |
a8ac403d SB |
2907 | |
2908 | CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); | |
2dc35992 | 2909 | ss << npt; |
a8ac403d | 2910 | |
0d37ae3a | 2911 | UniValue result(UniValue::VOBJ); |
5d99e3e9 | 2912 | result.push_back(Pair("amount", ValueFromAmount(decrypted_note.value()))); |
4bc00dc1 | 2913 | result.push_back(Pair("note", HexStr(ss.begin(), ss.end()))); |
2dc35992 | 2914 | result.push_back(Pair("exists", (bool) witnesses[0])); |
a8ac403d SB |
2915 | return result; |
2916 | } | |
2917 | ||
2dc35992 SB |
2918 | |
2919 | ||
0d37ae3a | 2920 | UniValue zc_raw_joinsplit(const UniValue& params, bool fHelp) |
730790f7 | 2921 | { |
f15b9549 | 2922 | if (!EnsureWalletIsAvailable(fHelp)) { |
0d37ae3a | 2923 | return NullUniValue; |
f15b9549 NW |
2924 | } |
2925 | ||
2926 | if (fHelp || params.size() != 5) { | |
2927 | throw runtime_error( | |
eae37941 | 2928 | "zcrawjoinsplit rawtx inputs outputs vpub_old vpub_new\n" |
4bc00dc1 | 2929 | " inputs: a JSON object mapping {note: zcsecretkey, ...}\n" |
f15b9549 NW |
2930 | " outputs: a JSON object mapping {zcaddr: value, ...}\n" |
2931 | "\n" | |
ca0ec80b | 2932 | "DEPRECATED. Splices a joinsplit into rawtx. Inputs are unilaterally confidential.\n" |
f15b9549 NW |
2933 | "Outputs are confidential between sender/receiver. The vpub_old and\n" |
2934 | "vpub_new values are globally public and move transparent value into\n" | |
2935 | "or out of the confidential value store, respectively.\n" | |
2936 | "\n" | |
2937 | "Note: The caller is responsible for delivering the output enc1 and\n" | |
2938 | "enc2 to the appropriate recipients, as well as signing rawtxout and\n" | |
2939 | "ensuring it is mined. (A future RPC call will deliver the confidential\n" | |
2940 | "payments in-band on the blockchain.)\n" | |
2941 | "\n" | |
2942 | "Output: {\n" | |
4bc00dc1 DH |
2943 | " \"encryptednote1\": enc1,\n" |
2944 | " \"encryptednote2\": enc2,\n" | |
f15b9549 NW |
2945 | " \"rawtxn\": rawtxout\n" |
2946 | "}\n" | |
2947 | ); | |
2948 | } | |
2949 | ||
a8ac403d | 2950 | LOCK(cs_main); |
730790f7 SB |
2951 | |
2952 | CTransaction tx; | |
2953 | if (!DecodeHexTx(tx, params[0].get_str())) | |
2954 | throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); | |
2955 | ||
0d37ae3a JG |
2956 | UniValue inputs = params[1].get_obj(); |
2957 | UniValue outputs = params[2].get_obj(); | |
730790f7 SB |
2958 | |
2959 | CAmount vpub_old(0); | |
2960 | CAmount vpub_new(0); | |
2961 | ||
2962 | if (params[3].get_real() != 0.0) | |
2963 | vpub_old = AmountFromValue(params[3]); | |
2964 | ||
2965 | if (params[4].get_real() != 0.0) | |
2966 | vpub_new = AmountFromValue(params[4]); | |
2967 | ||
b7e4abd6 SB |
2968 | std::vector<JSInput> vjsin; |
2969 | std::vector<JSOutput> vjsout; | |
b230fe68 | 2970 | std::vector<SproutNote> notes; |
e5eab182 | 2971 | std::vector<SproutSpendingKey> keys; |
2dc35992 | 2972 | std::vector<uint256> commitments; |
a8ac403d | 2973 | |
0d37ae3a | 2974 | for (const string& name_ : inputs.getKeys()) { |
472f75bc | 2975 | auto spendingkey = DecodeSpendingKey(inputs[name_].get_str()); |
e5eab182 | 2976 | if (!IsValidSpendingKey(spendingkey)) { |
472f75bc JG |
2977 | throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid spending key"); |
2978 | } | |
e5eab182 JG |
2979 | if (boost::get<libzcash::SproutSpendingKey>(&spendingkey) == nullptr) { |
2980 | throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Only works with Sprout spending keys"); | |
2981 | } | |
2982 | SproutSpendingKey k = boost::get<libzcash::SproutSpendingKey>(spendingkey); | |
a8ac403d | 2983 | |
2dc35992 | 2984 | keys.push_back(k); |
a8ac403d | 2985 | |
5020a936 | 2986 | SproutNotePlaintext npt; |
a8ac403d | 2987 | |
2dc35992 | 2988 | { |
0d37ae3a | 2989 | CDataStream ssData(ParseHexV(name_, "note"), SER_NETWORK, PROTOCOL_VERSION); |
2dc35992 | 2990 | ssData >> npt; |
a8ac403d SB |
2991 | } |
2992 | ||
e5eab182 | 2993 | SproutPaymentAddress addr = k.address(); |
b230fe68 | 2994 | SproutNote note = npt.note(addr); |
2dc35992 SB |
2995 | notes.push_back(note); |
2996 | commitments.push_back(note.cm()); | |
2997 | } | |
2998 | ||
2999 | uint256 anchor; | |
8ea8ef98 | 3000 | std::vector<boost::optional<SproutWitness>> witnesses; |
4bc00dc1 | 3001 | pwalletMain->WitnessNoteCommitment(commitments, witnesses, anchor); |
2dc35992 SB |
3002 | |
3003 | assert(witnesses.size() == notes.size()); | |
3004 | assert(notes.size() == keys.size()); | |
3005 | ||
3006 | { | |
3007 | for (size_t i = 0; i < witnesses.size(); i++) { | |
3008 | if (!witnesses[i]) { | |
3009 | throw runtime_error( | |
b7e4abd6 | 3010 | "joinsplit input could not be found in tree" |
2dc35992 SB |
3011 | ); |
3012 | } | |
3013 | ||
b7e4abd6 | 3014 | vjsin.push_back(JSInput(*witnesses[i], notes[i], keys[i])); |
2dc35992 | 3015 | } |
730790f7 | 3016 | } |
730790f7 | 3017 | |
b7e4abd6 SB |
3018 | while (vjsin.size() < ZC_NUM_JS_INPUTS) { |
3019 | vjsin.push_back(JSInput()); | |
a8ac403d | 3020 | } |
730790f7 | 3021 | |
0d37ae3a | 3022 | for (const string& name_ : outputs.getKeys()) { |
80ed13d5 | 3023 | auto addrTo = DecodePaymentAddress(name_); |
e5eab182 | 3024 | if (!IsValidPaymentAddress(addrTo)) { |
80ed13d5 JG |
3025 | throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid recipient address."); |
3026 | } | |
e5eab182 JG |
3027 | if (boost::get<libzcash::SproutPaymentAddress>(&addrTo) == nullptr) { |
3028 | throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Only works with Sprout payment addresses"); | |
3029 | } | |
0d37ae3a | 3030 | CAmount nAmount = AmountFromValue(outputs[name_]); |
730790f7 | 3031 | |
e5eab182 | 3032 | vjsout.push_back(JSOutput(boost::get<libzcash::SproutPaymentAddress>(addrTo), nAmount)); |
730790f7 SB |
3033 | } |
3034 | ||
b7e4abd6 SB |
3035 | while (vjsout.size() < ZC_NUM_JS_OUTPUTS) { |
3036 | vjsout.push_back(JSOutput()); | |
730790f7 SB |
3037 | } |
3038 | ||
3039 | // TODO | |
b7e4abd6 SB |
3040 | if (vjsout.size() != ZC_NUM_JS_INPUTS || vjsin.size() != ZC_NUM_JS_OUTPUTS) { |
3041 | throw runtime_error("unsupported joinsplit input/output counts"); | |
730790f7 SB |
3042 | } |
3043 | ||
320f2cc7 SB |
3044 | uint256 joinSplitPubKey; |
3045 | unsigned char joinSplitPrivKey[crypto_sign_SECRETKEYBYTES]; | |
3046 | crypto_sign_keypair(joinSplitPubKey.begin(), joinSplitPrivKey); | |
6aae9d1a TH |
3047 | |
3048 | CMutableTransaction mtx(tx); | |
3049 | mtx.nVersion = 2; | |
3050 | mtx.joinSplitPubKey = joinSplitPubKey; | |
3051 | ||
b7a6c321 SB |
3052 | JSDescription jsdesc(false, |
3053 | *pzcashParams, | |
22de1602 SB |
3054 | joinSplitPubKey, |
3055 | anchor, | |
3056 | {vjsin[0], vjsin[1]}, | |
3057 | {vjsout[0], vjsout[1]}, | |
3058 | vpub_old, | |
3059 | vpub_new); | |
320f2cc7 | 3060 | |
bc59f537 SB |
3061 | { |
3062 | auto verifier = libzcash::ProofVerifier::Strict(); | |
3063 | assert(jsdesc.Verify(*pzcashParams, verifier, joinSplitPubKey)); | |
3064 | } | |
730790f7 | 3065 | |
22de1602 | 3066 | mtx.vjoinsplit.push_back(jsdesc); |
730790f7 | 3067 | |
6aae9d1a TH |
3068 | // Empty output script. |
3069 | CScript scriptCode; | |
3070 | CTransaction signTx(mtx); | |
be126699 JG |
3071 | auto consensusBranchId = CurrentEpochBranchId(chainActive.Height() + 1, Params().GetConsensus()); |
3072 | uint256 dataToBeSigned = SignatureHash(scriptCode, signTx, NOT_AN_INPUT, SIGHASH_ALL, 0, consensusBranchId); | |
6aae9d1a TH |
3073 | |
3074 | // Add the signature | |
320f2cc7 SB |
3075 | assert(crypto_sign_detached(&mtx.joinSplitSig[0], NULL, |
3076 | dataToBeSigned.begin(), 32, | |
3077 | joinSplitPrivKey | |
3078 | ) == 0); | |
3079 | ||
3080 | // Sanity check | |
3081 | assert(crypto_sign_verify_detached(&mtx.joinSplitSig[0], | |
3082 | dataToBeSigned.begin(), 32, | |
3083 | mtx.joinSplitPubKey.begin() | |
3084 | ) == 0); | |
6aae9d1a | 3085 | |
730790f7 SB |
3086 | CTransaction rawTx(mtx); |
3087 | ||
3088 | CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); | |
3089 | ss << rawTx; | |
3090 | ||
4bc00dc1 DH |
3091 | std::string encryptedNote1; |
3092 | std::string encryptedNote2; | |
6c36a9fe SB |
3093 | { |
3094 | CDataStream ss2(SER_NETWORK, PROTOCOL_VERSION); | |
3095 | ss2 << ((unsigned char) 0x00); | |
22de1602 SB |
3096 | ss2 << jsdesc.ephemeralKey; |
3097 | ss2 << jsdesc.ciphertexts[0]; | |
3098 | ss2 << jsdesc.h_sig(*pzcashParams, joinSplitPubKey); | |
6c36a9fe | 3099 | |
4bc00dc1 | 3100 | encryptedNote1 = HexStr(ss2.begin(), ss2.end()); |
6c36a9fe SB |
3101 | } |
3102 | { | |
3103 | CDataStream ss2(SER_NETWORK, PROTOCOL_VERSION); | |
3104 | ss2 << ((unsigned char) 0x01); | |
22de1602 SB |
3105 | ss2 << jsdesc.ephemeralKey; |
3106 | ss2 << jsdesc.ciphertexts[1]; | |
3107 | ss2 << jsdesc.h_sig(*pzcashParams, joinSplitPubKey); | |
6c36a9fe | 3108 | |
4bc00dc1 | 3109 | encryptedNote2 = HexStr(ss2.begin(), ss2.end()); |
6c36a9fe SB |
3110 | } |
3111 | ||
0d37ae3a | 3112 | UniValue result(UniValue::VOBJ); |
4bc00dc1 DH |
3113 | result.push_back(Pair("encryptednote1", encryptedNote1)); |
3114 | result.push_back(Pair("encryptednote2", encryptedNote2)); | |
730790f7 SB |
3115 | result.push_back(Pair("rawtxn", HexStr(ss.begin(), ss.end()))); |
3116 | return result; | |
3117 | } | |
3118 | ||
0d37ae3a | 3119 | UniValue zc_raw_keygen(const UniValue& params, bool fHelp) |
730790f7 | 3120 | { |
f15b9549 | 3121 | if (!EnsureWalletIsAvailable(fHelp)) { |
0d37ae3a | 3122 | return NullUniValue; |
f15b9549 NW |
3123 | } |
3124 | ||
3125 | if (fHelp || params.size() != 0) { | |
3126 | throw runtime_error( | |
eae37941 | 3127 | "zcrawkeygen\n" |
f15b9549 | 3128 | "\n" |
ca0ec80b | 3129 | "DEPRECATED. Generate a zcaddr which can send and receive confidential values.\n" |
f15b9549 NW |
3130 | "\n" |
3131 | "Output: {\n" | |
3132 | " \"zcaddress\": zcaddr,\n" | |
3133 | " \"zcsecretkey\": zcsecretkey,\n" | |
7b8d4f87 | 3134 | " \"zcviewingkey\": zcviewingkey,\n" |
f15b9549 NW |
3135 | "}\n" |
3136 | ); | |
3137 | } | |
3138 | ||
e5eab182 | 3139 | auto k = SproutSpendingKey::random(); |
2dc35992 SB |
3140 | auto addr = k.address(); |
3141 | auto viewing_key = k.viewing_key(); | |
730790f7 | 3142 | |
0d37ae3a | 3143 | UniValue result(UniValue::VOBJ); |
80ed13d5 | 3144 | result.push_back(Pair("zcaddress", EncodePaymentAddress(addr))); |
472f75bc | 3145 | result.push_back(Pair("zcsecretkey", EncodeSpendingKey(k))); |
8bf3a3d7 | 3146 | result.push_back(Pair("zcviewingkey", EncodeViewingKey(viewing_key))); |
730790f7 | 3147 | return result; |
f15b9549 | 3148 | } |
c1c45943 S |
3149 | |
3150 | ||
0d37ae3a | 3151 | UniValue z_getnewaddress(const UniValue& params, bool fHelp) |
c1c45943 S |
3152 | { |
3153 | if (!EnsureWalletIsAvailable(fHelp)) | |
0d37ae3a | 3154 | return NullUniValue; |
c1c45943 | 3155 | |
6eec2812 | 3156 | std::string defaultType = ADDR_TYPE_SAPLING; |
eec85c43 JG |
3157 | |
3158 | if (fHelp || params.size() > 1) | |
c1c45943 | 3159 | throw runtime_error( |
eec85c43 JG |
3160 | "z_getnewaddress ( type )\n" |
3161 | "\nReturns a new shielded address for receiving payments.\n" | |
6eec2812 | 3162 | "\nWith no arguments, returns a Sapling address.\n" |
c1c45943 | 3163 | "\nArguments:\n" |
eec85c43 JG |
3164 | "1. \"type\" (string, optional, default=\"" + defaultType + "\") The type of address. One of [\"" |
3165 | + ADDR_TYPE_SPROUT + "\", \"" + ADDR_TYPE_SAPLING + "\"].\n" | |
c1c45943 | 3166 | "\nResult:\n" |
eec85c43 | 3167 | "\"zcashaddress\" (string) The new shielded address.\n" |
c1c45943 S |
3168 | "\nExamples:\n" |
3169 | + HelpExampleCli("z_getnewaddress", "") | |
eec85c43 | 3170 | + HelpExampleCli("z_getnewaddress", ADDR_TYPE_SAPLING) |
c1c45943 S |
3171 | + HelpExampleRpc("z_getnewaddress", "") |
3172 | ); | |
3173 | ||
3174 | LOCK2(cs_main, pwalletMain->cs_wallet); | |
3175 | ||
73699cea S |
3176 | EnsureWalletIsUnlocked(); |
3177 | ||
eec85c43 JG |
3178 | auto addrType = defaultType; |
3179 | if (params.size() > 0) { | |
3180 | addrType = params[0].get_str(); | |
3181 | } | |
3182 | ||
3183 | if (addrType == ADDR_TYPE_SPROUT) { | |
92fc29a3 | 3184 | return EncodePaymentAddress(pwalletMain->GenerateNewSproutZKey()); |
34e222c1 | 3185 | } else if (addrType == ADDR_TYPE_SAPLING) { |
eec85c43 JG |
3186 | return EncodePaymentAddress(pwalletMain->GenerateNewSaplingZKey()); |
3187 | } else { | |
3188 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid address type"); | |
3189 | } | |
c1c45943 S |
3190 | } |
3191 | ||
e709997f | 3192 | |
0d37ae3a | 3193 | UniValue z_listaddresses(const UniValue& params, bool fHelp) |
e709997f S |
3194 | { |
3195 | if (!EnsureWalletIsAvailable(fHelp)) | |
0d37ae3a | 3196 | return NullUniValue; |
e709997f S |
3197 | |
3198 | if (fHelp || params.size() > 1) | |
3199 | throw runtime_error( | |
44e37656 | 3200 | "z_listaddresses ( includeWatchonly )\n" |
eec85c43 | 3201 | "\nReturns the list of Sprout and Sapling shielded addresses belonging to the wallet.\n" |
e709997f | 3202 | "\nArguments:\n" |
44e37656 | 3203 | "1. includeWatchonly (bool, optional, default=false) Also include watchonly addresses (see 'z_importviewingkey')\n" |
e709997f S |
3204 | "\nResult:\n" |
3205 | "[ (json array of string)\n" | |
3206 | " \"zaddr\" (string) a zaddr belonging to the wallet\n" | |
3207 | " ,...\n" | |
3208 | "]\n" | |
3209 | "\nExamples:\n" | |
3210 | + HelpExampleCli("z_listaddresses", "") | |
3211 | + HelpExampleRpc("z_listaddresses", "") | |
3212 | ); | |
3213 | ||
3214 | LOCK2(cs_main, pwalletMain->cs_wallet); | |
3215 | ||
44e37656 JG |
3216 | bool fIncludeWatchonly = false; |
3217 | if (params.size() > 0) { | |
3218 | fIncludeWatchonly = params[0].get_bool(); | |
3219 | } | |
3220 | ||
0d37ae3a | 3221 | UniValue ret(UniValue::VARR); |
eec85c43 JG |
3222 | { |
3223 | std::set<libzcash::SproutPaymentAddress> addresses; | |
25d5e80c | 3224 | pwalletMain->GetSproutPaymentAddresses(addresses); |
fbd029d9 | 3225 | for (auto addr : addresses) { |
25d5e80c | 3226 | if (fIncludeWatchonly || pwalletMain->HaveSproutSpendingKey(addr)) { |
eec85c43 JG |
3227 | ret.push_back(EncodePaymentAddress(addr)); |
3228 | } | |
3229 | } | |
3230 | } | |
3231 | { | |
3232 | std::set<libzcash::SaplingPaymentAddress> addresses; | |
3233 | pwalletMain->GetSaplingPaymentAddresses(addresses); | |
3234 | libzcash::SaplingIncomingViewingKey ivk; | |
3235 | libzcash::SaplingFullViewingKey fvk; | |
fbd029d9 | 3236 | for (auto addr : addresses) { |
eec85c43 JG |
3237 | if (fIncludeWatchonly || ( |
3238 | pwalletMain->GetSaplingIncomingViewingKey(addr, ivk) && | |
3239 | pwalletMain->GetSaplingFullViewingKey(ivk, fvk) && | |
3240 | pwalletMain->HaveSaplingSpendingKey(fvk) | |
3241 | )) { | |
3242 | ret.push_back(EncodePaymentAddress(addr)); | |
3243 | } | |
44e37656 | 3244 | } |
e709997f S |
3245 | } |
3246 | return ret; | |
3247 | } | |
3248 | ||
44e37656 | 3249 | CAmount getBalanceTaddr(std::string transparentAddress, int minDepth=1, bool ignoreUnspendable=true) { |
b6be3e88 | 3250 | std::set<CTxDestination> destinations; |
a0a3334c | 3251 | vector<COutput> vecOutputs; |
70454796 JG |
3252 | CAmount balance = 0; |
3253 | ||
a0a3334c | 3254 | if (transparentAddress.length() > 0) { |
b6be3e88 JG |
3255 | CTxDestination taddr = DecodeDestination(transparentAddress); |
3256 | if (!IsValidDestination(taddr)) { | |
a0a3334c S |
3257 | throw std::runtime_error("invalid transparent address"); |
3258 | } | |
b6be3e88 | 3259 | destinations.insert(taddr); |
a0a3334c | 3260 | } |
70454796 | 3261 | |
a0a3334c S |
3262 | LOCK2(cs_main, pwalletMain->cs_wallet); |
3263 | ||
3264 | pwalletMain->AvailableCoins(vecOutputs, false, NULL, true); | |
3265 | ||
3266 | BOOST_FOREACH(const COutput& out, vecOutputs) { | |
3267 | if (out.nDepth < minDepth) { | |
3268 | continue; | |
3269 | } | |
3270 | ||
44e37656 JG |
3271 | if (ignoreUnspendable && !out.fSpendable) { |
3272 | continue; | |
3273 | } | |
3274 | ||
b6be3e88 | 3275 | if (destinations.size()) { |
a0a3334c S |
3276 | CTxDestination address; |
3277 | if (!ExtractDestination(out.tx->vout[out.i].scriptPubKey, address)) { | |
3278 | continue; | |
3279 | } | |
3280 | ||
b6be3e88 | 3281 | if (!destinations.count(address)) { |
a0a3334c S |
3282 | continue; |
3283 | } | |
3284 | } | |
70454796 | 3285 | |
a0a3334c S |
3286 | CAmount nValue = out.tx->vout[out.i].nValue; |
3287 | balance += nValue; | |
3288 | } | |
3289 | return balance; | |
3290 | } | |
3291 | ||
44e37656 | 3292 | CAmount getBalanceZaddr(std::string address, int minDepth = 1, bool ignoreUnspendable=true) { |
a0a3334c | 3293 | CAmount balance = 0; |
a630f503 | 3294 | std::vector<SproutNoteEntry> sproutEntries; |
94e99acd | 3295 | std::vector<SaplingNoteEntry> saplingEntries; |
a0a3334c | 3296 | LOCK2(cs_main, pwalletMain->cs_wallet); |
94e99acd JG |
3297 | pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, address, minDepth, true, ignoreUnspendable); |
3298 | for (auto & entry : sproutEntries) { | |
a630f503 | 3299 | balance += CAmount(entry.note.value()); |
a0a3334c | 3300 | } |
053cb349 JG |
3301 | for (auto & entry : saplingEntries) { |
3302 | balance += CAmount(entry.note.value()); | |
3303 | } | |
a0a3334c S |
3304 | return balance; |
3305 | } | |
3306 | ||
3307 | ||
0d37ae3a | 3308 | UniValue z_listreceivedbyaddress(const UniValue& params, bool fHelp) |
6c41028f S |
3309 | { |
3310 | if (!EnsureWalletIsAvailable(fHelp)) | |
0d37ae3a | 3311 | return NullUniValue; |
6c41028f S |
3312 | |
3313 | if (fHelp || params.size()==0 || params.size() >2) | |
3314 | throw runtime_error( | |
3315 | "z_listreceivedbyaddress \"address\" ( minconf )\n" | |
23c9deaf | 3316 | "\nReturn a list of amounts received by a zaddr belonging to the node's wallet.\n" |
6c41028f S |
3317 | "\nArguments:\n" |
3318 | "1. \"address\" (string) The private address.\n" | |
3319 | "2. minconf (numeric, optional, default=1) Only include transactions confirmed at least this many times.\n" | |
3320 | "\nResult:\n" | |
3321 | "{\n" | |
4c646bb4 | 3322 | " \"txid\": \"txid\", (string) the transaction id\n" |
0646f749 | 3323 | " \"amount\": xxxxx, (numeric) the amount of value in the note\n" |
4c646bb4 GD |
3324 | " \"memo\": xxxxx, (string) hexadecimal string representation of memo field\n" |
3325 | " \"jsindex\" (sprout) : n, (numeric) the joinsplit index\n" | |
3326 | " \"jsoutindex\" (sprout) : n, (numeric) the output index of the joinsplit\n" | |
3327 | " \"outindex\" (sapling) : n, (numeric) the output index\n" | |
0646f749 | 3328 | " \"change\": true|false, (boolean) true if the address that received the note is also one of the sending addresses\n" |
6c41028f | 3329 | "}\n" |
337a99a2 JG |
3330 | "\nExamples:\n" |
3331 | + HelpExampleCli("z_listreceivedbyaddress", "\"ztfaW34Gj9FrnGUEf833ywDVL62NWXBM81u6EQnM6VR45eYnXhwztecW1SjxA7JrmAXKJhxhj3vDNEpVCQoSvVoSpmbhtjf\"") | |
3332 | + HelpExampleRpc("z_listreceivedbyaddress", "\"ztfaW34Gj9FrnGUEf833ywDVL62NWXBM81u6EQnM6VR45eYnXhwztecW1SjxA7JrmAXKJhxhj3vDNEpVCQoSvVoSpmbhtjf\"") | |
6c41028f S |
3333 | ); |
3334 | ||
3335 | LOCK2(cs_main, pwalletMain->cs_wallet); | |
3336 | ||
3337 | int nMinDepth = 1; | |
3338 | if (params.size() > 1) { | |
3339 | nMinDepth = params[1].get_int(); | |
3340 | } | |
3341 | if (nMinDepth < 0) { | |
3342 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Minimum number of confirmations cannot be less than 0"); | |
3343 | } | |
70454796 | 3344 | |
6c41028f S |
3345 | // Check that the from address is valid. |
3346 | auto fromaddress = params[0].get_str(); | |
3347 | ||
80ed13d5 | 3348 | auto zaddr = DecodePaymentAddress(fromaddress); |
e5eab182 | 3349 | if (!IsValidPaymentAddress(zaddr)) { |
6c41028f S |
3350 | throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid zaddr."); |
3351 | } | |
3352 | ||
e4f0d6a8 LR |
3353 | // Visitor to support Sprout and Sapling addrs |
3354 | if (!boost::apply_visitor(PaymentAddressBelongsToWallet(pwalletMain), zaddr)) { | |
44e37656 | 3355 | throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "From address does not belong to this node, zaddr spending key or viewing key not found."); |
6c41028f | 3356 | } |
70454796 | 3357 | |
0d37ae3a | 3358 | UniValue result(UniValue::VARR); |
a630f503 | 3359 | std::vector<SproutNoteEntry> sproutEntries; |
94e99acd JG |
3360 | std::vector<SaplingNoteEntry> saplingEntries; |
3361 | pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, fromaddress, nMinDepth, false, false); | |
e4f0d6a8 LR |
3362 | |
3363 | std::set<std::pair<PaymentAddress, uint256>> nullifierSet; | |
3364 | auto hasSpendingKey = boost::apply_visitor(HaveSpendingKeyForPaymentAddress(pwalletMain), zaddr); | |
3365 | if (hasSpendingKey) { | |
3366 | nullifierSet = pwalletMain->GetNullifiersForAddresses({zaddr}); | |
3367 | } | |
3368 | ||
3369 | if (boost::get<libzcash::SproutPaymentAddress>(&zaddr) != nullptr) { | |
a630f503 | 3370 | for (SproutNoteEntry & entry : sproutEntries) { |
e4f0d6a8 LR |
3371 | UniValue obj(UniValue::VOBJ); |
3372 | obj.push_back(Pair("txid", entry.jsop.hash.ToString())); | |
a630f503 E |
3373 | obj.push_back(Pair("amount", ValueFromAmount(CAmount(entry.note.value())))); |
3374 | std::string data(entry.memo.begin(), entry.memo.end()); | |
e4f0d6a8 LR |
3375 | obj.push_back(Pair("memo", HexStr(data))); |
3376 | obj.push_back(Pair("jsindex", entry.jsop.js)); | |
3377 | obj.push_back(Pair("jsoutindex", entry.jsop.n)); | |
3378 | if (hasSpendingKey) { | |
3379 | obj.push_back(Pair("change", pwalletMain->IsNoteSproutChange(nullifierSet, entry.address, entry.jsop))); | |
3380 | } | |
3381 | result.push_back(obj); | |
3382 | } | |
3383 | } else if (boost::get<libzcash::SaplingPaymentAddress>(&zaddr) != nullptr) { | |
3384 | for (SaplingNoteEntry & entry : saplingEntries) { | |
3385 | UniValue obj(UniValue::VOBJ); | |
3386 | obj.push_back(Pair("txid", entry.op.hash.ToString())); | |
3387 | obj.push_back(Pair("amount", ValueFromAmount(CAmount(entry.note.value())))); | |
3388 | obj.push_back(Pair("memo", HexStr(entry.memo))); | |
3389 | obj.push_back(Pair("outindex", (int)entry.op.n)); | |
3390 | if (hasSpendingKey) { | |
3391 | obj.push_back(Pair("change", pwalletMain->IsNoteSaplingChange(nullifierSet, entry.address, entry.op))); | |
3392 | } | |
3393 | result.push_back(obj); | |
79298516 | 3394 | } |
6c41028f S |
3395 | } |
3396 | return result; | |
3397 | } | |
3398 | ||
0d37ae3a | 3399 | UniValue z_getbalance(const UniValue& params, bool fHelp) |
a0a3334c S |
3400 | { |
3401 | if (!EnsureWalletIsAvailable(fHelp)) | |
0d37ae3a | 3402 | return NullUniValue; |
a0a3334c S |
3403 | |
3404 | if (fHelp || params.size()==0 || params.size() >2) | |
3405 | throw runtime_error( | |
3406 | "z_getbalance \"address\" ( minconf )\n" | |
23c9deaf | 3407 | "\nReturns the balance of a taddr or zaddr belonging to the node's wallet.\n" |
573de712 JG |
3408 | "\nCAUTION: If the wallet has only an incoming viewing key for this address, then spends cannot be" |
3409 | "\ndetected, and so the returned balance may be larger than the actual balance.\n" | |
a0a3334c S |
3410 | "\nArguments:\n" |
3411 | "1. \"address\" (string) The selected address. It may be a transparent or private address.\n" | |
3412 | "2. minconf (numeric, optional, default=1) Only include transactions confirmed at least this many times.\n" | |
3413 | "\nResult:\n" | |
6dec2d03 | 3414 | "amount (numeric) The total amount in " + CURRENCY_UNIT + " received for this address.\n" |
a0a3334c S |
3415 | "\nExamples:\n" |
3416 | "\nThe total amount received by address \"myaddress\"\n" | |
3417 | + HelpExampleCli("z_getbalance", "\"myaddress\"") + | |
3418 | "\nThe total amount received by address \"myaddress\" at least 5 blocks confirmed\n" | |
3419 | + HelpExampleCli("z_getbalance", "\"myaddress\" 5") + | |
3420 | "\nAs a json rpc call\n" | |
3421 | + HelpExampleRpc("z_getbalance", "\"myaddress\", 5") | |
3422 | ); | |
3423 | ||
3424 | LOCK2(cs_main, pwalletMain->cs_wallet); | |
3425 | ||
3426 | int nMinDepth = 1; | |
3427 | if (params.size() > 1) { | |
3428 | nMinDepth = params[1].get_int(); | |
3429 | } | |
12448b64 S |
3430 | if (nMinDepth < 0) { |
3431 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Minimum number of confirmations cannot be less than 0"); | |
3432 | } | |
70454796 | 3433 | |
a0a3334c S |
3434 | // Check that the from address is valid. |
3435 | auto fromaddress = params[0].get_str(); | |
3436 | bool fromTaddr = false; | |
b6be3e88 JG |
3437 | CTxDestination taddr = DecodeDestination(fromaddress); |
3438 | fromTaddr = IsValidDestination(taddr); | |
a0a3334c | 3439 | if (!fromTaddr) { |
80ed13d5 | 3440 | auto res = DecodePaymentAddress(fromaddress); |
e5eab182 | 3441 | if (!IsValidPaymentAddress(res)) { |
a0a3334c S |
3442 | throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid from address, should be a taddr or zaddr."); |
3443 | } | |
053cb349 JG |
3444 | if (!boost::apply_visitor(PaymentAddressBelongsToWallet(pwalletMain), res)) { |
3445 | throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "From address does not belong to this node, spending key or viewing key not found."); | |
12448b64 | 3446 | } |
a0a3334c S |
3447 | } |
3448 | ||
3449 | CAmount nBalance = 0; | |
3450 | if (fromTaddr) { | |
44e37656 | 3451 | nBalance = getBalanceTaddr(fromaddress, nMinDepth, false); |
a0a3334c | 3452 | } else { |
44e37656 | 3453 | nBalance = getBalanceZaddr(fromaddress, nMinDepth, false); |
a0a3334c S |
3454 | } |
3455 | ||
3456 | return ValueFromAmount(nBalance); | |
3457 | } | |
3458 | ||
3459 | ||
0d37ae3a | 3460 | UniValue z_gettotalbalance(const UniValue& params, bool fHelp) |
a0a3334c S |
3461 | { |
3462 | if (!EnsureWalletIsAvailable(fHelp)) | |
0d37ae3a | 3463 | return NullUniValue; |
a0a3334c | 3464 | |
44e37656 | 3465 | if (fHelp || params.size() > 2) |
a0a3334c | 3466 | throw runtime_error( |
44e37656 | 3467 | "z_gettotalbalance ( minconf includeWatchonly )\n" |
23c9deaf | 3468 | "\nReturn the total value of funds stored in the node's wallet.\n" |
573de712 JG |
3469 | "\nCAUTION: If the wallet contains any addresses for which it only has incoming viewing keys," |
3470 | "\nthe returned private balance may be larger than the actual balance, because spends cannot" | |
3471 | "\nbe detected with incoming viewing keys.\n" | |
a0a3334c S |
3472 | "\nArguments:\n" |
3473 | "1. minconf (numeric, optional, default=1) Only include private and transparent transactions confirmed at least this many times.\n" | |
44e37656 | 3474 | "2. includeWatchonly (bool, optional, default=false) Also include balance in watchonly addresses (see 'importaddress' and 'z_importviewingkey')\n" |
a0a3334c S |
3475 | "\nResult:\n" |
3476 | "{\n" | |
3477 | " \"transparent\": xxxxx, (numeric) the total balance of transparent funds\n" | |
053cb349 | 3478 | " \"private\": xxxxx, (numeric) the total balance of private funds (in both Sprout and Sapling addresses)\n" |
a0a3334c S |
3479 | " \"total\": xxxxx, (numeric) the total balance of both transparent and private funds\n" |
3480 | "}\n" | |
3481 | "\nExamples:\n" | |
3482 | "\nThe total amount in the wallet\n" | |
3483 | + HelpExampleCli("z_gettotalbalance", "") + | |
3484 | "\nThe total amount in the wallet at least 5 blocks confirmed\n" | |
3485 | + HelpExampleCli("z_gettotalbalance", "5") + | |
3486 | "\nAs a json rpc call\n" | |
3487 | + HelpExampleRpc("z_gettotalbalance", "5") | |
3488 | ); | |
3489 | ||
3490 | LOCK2(cs_main, pwalletMain->cs_wallet); | |
3491 | ||
3492 | int nMinDepth = 1; | |
44e37656 | 3493 | if (params.size() > 0) { |
a0a3334c S |
3494 | nMinDepth = params[0].get_int(); |
3495 | } | |
12448b64 S |
3496 | if (nMinDepth < 0) { |
3497 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Minimum number of confirmations cannot be less than 0"); | |
3498 | } | |
a0a3334c | 3499 | |
44e37656 JG |
3500 | bool fIncludeWatchonly = false; |
3501 | if (params.size() > 1) { | |
3502 | fIncludeWatchonly = params[1].get_bool(); | |
3503 | } | |
3504 | ||
a0a3334c | 3505 | // getbalance and "getbalance * 1 true" should return the same number |
70454796 | 3506 | // but they don't because wtx.GetAmounts() does not handle tx where there are no outputs |
a0a3334c S |
3507 | // pwalletMain->GetBalance() does not accept min depth parameter |
3508 | // so we use our own method to get balance of utxos. | |
44e37656 JG |
3509 | CAmount nBalance = getBalanceTaddr("", nMinDepth, !fIncludeWatchonly); |
3510 | CAmount nPrivateBalance = getBalanceZaddr("", nMinDepth, !fIncludeWatchonly); | |
a0a3334c | 3511 | CAmount nTotalBalance = nBalance + nPrivateBalance; |
0d37ae3a | 3512 | UniValue result(UniValue::VOBJ); |
f54db399 JG |
3513 | result.push_back(Pair("transparent", FormatMoney(nBalance))); |
3514 | result.push_back(Pair("private", FormatMoney(nPrivateBalance))); | |
3515 | result.push_back(Pair("total", FormatMoney(nTotalBalance))); | |
a0a3334c S |
3516 | return result; |
3517 | } | |
3518 | ||
0d37ae3a | 3519 | UniValue z_getoperationresult(const UniValue& params, bool fHelp) |
c1eae280 S |
3520 | { |
3521 | if (!EnsureWalletIsAvailable(fHelp)) | |
0d37ae3a | 3522 | return NullUniValue; |
c1eae280 S |
3523 | |
3524 | if (fHelp || params.size() > 1) | |
3525 | throw runtime_error( | |
3526 | "z_getoperationresult ([\"operationid\", ... ]) \n" | |
3527 | "\nRetrieve the result and status of an operation which has finished, and then remove the operation from memory." | |
3528 | + HelpRequiringPassphrase() + "\n" | |
3529 | "\nArguments:\n" | |
3530 | "1. \"operationid\" (array, optional) A list of operation ids we are interested in. If not provided, examine all operations known to the node.\n" | |
3531 | "\nResult:\n" | |
3532 | "\" [object, ...]\" (array) A list of JSON objects\n" | |
337a99a2 JG |
3533 | "\nExamples:\n" |
3534 | + HelpExampleCli("z_getoperationresult", "'[\"operationid\", ... ]'") | |
3535 | + HelpExampleRpc("z_getoperationresult", "'[\"operationid\", ... ]'") | |
c1eae280 | 3536 | ); |
70454796 | 3537 | |
c1eae280 S |
3538 | // This call will remove finished operations |
3539 | return z_getoperationstatus_IMPL(params, true); | |
3540 | } | |
fc72c078 | 3541 | |
0d37ae3a | 3542 | UniValue z_getoperationstatus(const UniValue& params, bool fHelp) |
fc72c078 S |
3543 | { |
3544 | if (!EnsureWalletIsAvailable(fHelp)) | |
0d37ae3a | 3545 | return NullUniValue; |
fc72c078 | 3546 | |
34f0001c | 3547 | if (fHelp || params.size() > 1) |
fc72c078 | 3548 | throw runtime_error( |
34f0001c | 3549 | "z_getoperationstatus ([\"operationid\", ... ]) \n" |
c1eae280 | 3550 | "\nGet operation status and any associated result or error data. The operation will remain in memory." |
fc72c078 S |
3551 | + HelpRequiringPassphrase() + "\n" |
3552 | "\nArguments:\n" | |
c1eae280 | 3553 | "1. \"operationid\" (array, optional) A list of operation ids we are interested in. If not provided, examine all operations known to the node.\n" |
fc72c078 | 3554 | "\nResult:\n" |
34f0001c | 3555 | "\" [object, ...]\" (array) A list of JSON objects\n" |
337a99a2 JG |
3556 | "\nExamples:\n" |
3557 | + HelpExampleCli("z_getoperationstatus", "'[\"operationid\", ... ]'") | |
3558 | + HelpExampleRpc("z_getoperationstatus", "'[\"operationid\", ... ]'") | |
fc72c078 | 3559 | ); |
70454796 | 3560 | |
c1eae280 S |
3561 | // This call is idempotent so we don't want to remove finished operations |
3562 | return z_getoperationstatus_IMPL(params, false); | |
3563 | } | |
fc72c078 | 3564 | |
0d37ae3a | 3565 | UniValue z_getoperationstatus_IMPL(const UniValue& params, bool fRemoveFinishedOperations=false) |
c1eae280 | 3566 | { |
fc72c078 S |
3567 | LOCK2(cs_main, pwalletMain->cs_wallet); |
3568 | ||
34f0001c S |
3569 | std::set<AsyncRPCOperationId> filter; |
3570 | if (params.size()==1) { | |
0d37ae3a | 3571 | UniValue ids = params[0].get_array(); |
c24109ec | 3572 | for (const UniValue & v : ids.getValues()) { |
34f0001c S |
3573 | filter.insert(v.get_str()); |
3574 | } | |
fc72c078 | 3575 | } |
34f0001c | 3576 | bool useFilter = (filter.size()>0); |
fc72c078 | 3577 | |
0d37ae3a | 3578 | UniValue ret(UniValue::VARR); |
34f0001c S |
3579 | std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue(); |
3580 | std::vector<AsyncRPCOperationId> ids = q->getAllOperationIds(); | |
3581 | ||
3582 | for (auto id : ids) { | |
3583 | if (useFilter && !filter.count(id)) | |
3584 | continue; | |
70454796 | 3585 | |
34f0001c S |
3586 | std::shared_ptr<AsyncRPCOperation> operation = q->getOperationForId(id); |
3587 | if (!operation) { | |
3588 | continue; | |
3589 | // It's possible that the operation was removed from the internal queue and map during this loop | |
3590 | // throw JSONRPCError(RPC_INVALID_PARAMETER, "No operation exists for that id."); | |
3591 | } | |
fc72c078 | 3592 | |
2f21206c S |
3593 | UniValue obj = operation->getStatus(); |
3594 | std::string s = obj["status"].get_str(); | |
c1eae280 S |
3595 | if (fRemoveFinishedOperations) { |
3596 | // Caller is only interested in retrieving finished results | |
2f21206c S |
3597 | if ("success"==s || "failed"==s || "cancelled"==s) { |
3598 | ret.push_back(obj); | |
c1eae280 S |
3599 | q->popOperationForId(id); |
3600 | } | |
3601 | } else { | |
2f21206c | 3602 | ret.push_back(obj); |
34f0001c | 3603 | } |
fc72c078 S |
3604 | } |
3605 | ||
0d37ae3a JG |
3606 | std::vector<UniValue> arrTmp = ret.getValues(); |
3607 | ||
2d2f3d18 | 3608 | // sort results chronologically by creation_time |
0d37ae3a | 3609 | std::sort(arrTmp.begin(), arrTmp.end(), [](UniValue a, UniValue b) -> bool { |
2d2f3d18 S |
3610 | const int64_t t1 = find_value(a.get_obj(), "creation_time").get_int64(); |
3611 | const int64_t t2 = find_value(b.get_obj(), "creation_time").get_int64(); | |
3612 | return t1 < t2; | |
3613 | }); | |
3614 | ||
0d37ae3a JG |
3615 | ret.clear(); |
3616 | ret.setArray(); | |
3617 | ret.push_backV(arrTmp); | |
3618 | ||
34f0001c | 3619 | return ret; |
fc72c078 S |
3620 | } |
3621 | ||
3920292b | 3622 | |
892ae945 JG |
3623 | // JSDescription size depends on the transaction version |
3624 | #define V3_JS_DESCRIPTION_SIZE (GetSerializeSize(JSDescription(), SER_NETWORK, (OVERWINTER_TX_VERSION | (1 << 31)))) | |
3920292b S |
3625 | // Here we define the maximum number of zaddr outputs that can be included in a transaction. |
3626 | // If input notes are small, we might actually require more than one joinsplit per zaddr output. | |
3627 | // For now though, we assume we use one joinsplit per zaddr output (and the second output note is change). | |
3628 | // We reduce the result by 1 to ensure there is room for non-joinsplit CTransaction data. | |
892ae945 | 3629 | #define Z_SENDMANY_MAX_ZADDR_OUTPUTS_BEFORE_SAPLING ((MAX_TX_SIZE_BEFORE_SAPLING / V3_JS_DESCRIPTION_SIZE) - 1) |
3920292b S |
3630 | |
3631 | // transaction.h comment: spending taddr output requires CTxIn >= 148 bytes and typical taddr txout is 34 bytes | |
3632 | #define CTXIN_SPEND_DUST_SIZE 148 | |
3633 | #define CTXOUT_REGULAR_SIZE 34 | |
3634 | ||
0d37ae3a | 3635 | UniValue z_sendmany(const UniValue& params, bool fHelp) |
fc72c078 S |
3636 | { |
3637 | if (!EnsureWalletIsAvailable(fHelp)) | |
0d37ae3a | 3638 | return NullUniValue; |
fc72c078 | 3639 | |
af53da02 | 3640 | if (fHelp || params.size() < 2 || params.size() > 4) |
fc72c078 | 3641 | throw runtime_error( |
af53da02 | 3642 | "z_sendmany \"fromaddress\" [{\"address\":... ,\"amount\":...},...] ( minconf ) ( fee )\n" |
92b42d28 | 3643 | "\nSend multiple times. Amounts are decimal numbers with at most 8 digits of precision." |
da85cdfe | 3644 | "\nChange generated from a taddr flows to a new taddr address, while change generated from a zaddr returns to itself." |
48f9c65b | 3645 | "\nWhen sending coinbase UTXOs to a zaddr, change is not allowed. The entire value of the UTXO(s) must be consumed." |
892ae945 | 3646 | + strprintf("\nBefore Sapling activates, the maximum number of zaddr outputs is %d due to transaction size limits.\n", Z_SENDMANY_MAX_ZADDR_OUTPUTS_BEFORE_SAPLING) |
fc72c078 S |
3647 | + HelpRequiringPassphrase() + "\n" |
3648 | "\nArguments:\n" | |
3649 | "1. \"fromaddress\" (string, required) The taddr or zaddr to send the funds from.\n" | |
3650 | "2. \"amounts\" (array, required) An array of json objects representing the amounts to send.\n" | |
3651 | " [{\n" | |
3652 | " \"address\":address (string, required) The address is a taddr or zaddr\n" | |
6dec2d03 | 3653 | " \"amount\":amount (numeric, required) The numeric amount in " + CURRENCY_UNIT + " is the value\n" |
fc72c078 S |
3654 | " \"memo\":memo (string, optional) If the address is a zaddr, raw data represented in hexadecimal string format\n" |
3655 | " }, ... ]\n" | |
3656 | "3. minconf (numeric, optional, default=1) Only use funds confirmed at least this many times.\n" | |
af53da02 S |
3657 | "4. fee (numeric, optional, default=" |
3658 | + strprintf("%s", FormatMoney(ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE)) + ") The fee amount to attach to this transaction.\n" | |
fc72c078 S |
3659 | "\nResult:\n" |
3660 | "\"operationid\" (string) An operationid to pass to z_getoperationstatus to get the result of the operation.\n" | |
337a99a2 JG |
3661 | "\nExamples:\n" |
3662 | + HelpExampleCli("z_sendmany", "\"t1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" '[{\"address\": \"ztfaW34Gj9FrnGUEf833ywDVL62NWXBM81u6EQnM6VR45eYnXhwztecW1SjxA7JrmAXKJhxhj3vDNEpVCQoSvVoSpmbhtjf\" ,\"amount\": 5.0}]'") | |
3663 | + HelpExampleRpc("z_sendmany", "\"t1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\", [{\"address\": \"ztfaW34Gj9FrnGUEf833ywDVL62NWXBM81u6EQnM6VR45eYnXhwztecW1SjxA7JrmAXKJhxhj3vDNEpVCQoSvVoSpmbhtjf\" ,\"amount\": 5.0}]") | |
fc72c078 S |
3664 | ); |
3665 | ||
3666 | LOCK2(cs_main, pwalletMain->cs_wallet); | |
3667 | ||
fc72c078 S |
3668 | // Check that the from address is valid. |
3669 | auto fromaddress = params[0].get_str(); | |
3670 | bool fromTaddr = false; | |
af4057b9 | 3671 | bool fromSapling = false; |
b6be3e88 JG |
3672 | CTxDestination taddr = DecodeDestination(fromaddress); |
3673 | fromTaddr = IsValidDestination(taddr); | |
fc72c078 | 3674 | if (!fromTaddr) { |
80ed13d5 | 3675 | auto res = DecodePaymentAddress(fromaddress); |
e5eab182 | 3676 | if (!IsValidPaymentAddress(res)) { |
fc72c078 S |
3677 | // invalid |
3678 | throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid from address, should be a taddr or zaddr."); | |
3679 | } | |
fc72c078 | 3680 | |
af4057b9 JG |
3681 | // Check that we have the spending key |
3682 | if (!boost::apply_visitor(HaveSpendingKeyForPaymentAddress(pwalletMain), res)) { | |
fc72c078 S |
3683 | throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "From address does not belong to this node, zaddr spending key not found."); |
3684 | } | |
af4057b9 JG |
3685 | |
3686 | // Remember whether this is a Sprout or Sapling address | |
af4057b9 | 3687 | fromSapling = boost::get<libzcash::SaplingPaymentAddress>(&res) != nullptr; |
fc72c078 | 3688 | } |
07d85a64 JG |
3689 | // This logic will need to be updated if we add a new shielded pool |
3690 | bool fromSprout = !(fromTaddr || fromSapling); | |
fc72c078 | 3691 | |
0d37ae3a | 3692 | UniValue outputs = params[1].get_array(); |
fc72c078 S |
3693 | |
3694 | if (outputs.size()==0) | |
3695 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, amounts array is empty."); | |
3696 | ||
3697 | // Keep track of addresses to spot duplicates | |
3698 | set<std::string> setAddress; | |
3699 | ||
af4057b9 | 3700 | // Track whether we see any Sprout addresses |
07d85a64 | 3701 | bool noSproutAddrs = !fromSprout; |
af4057b9 | 3702 | |
fc72c078 | 3703 | // Recipients |
dafb8161 S |
3704 | std::vector<SendManyRecipient> taddrRecipients; |
3705 | std::vector<SendManyRecipient> zaddrRecipients; | |
af53da02 | 3706 | CAmount nTotalOut = 0; |
fc72c078 | 3707 | |
0917c84d EOW |
3708 | bool containsSproutOutput = false; |
3709 | bool containsSaplingOutput = false; | |
3710 | ||
0d37ae3a JG |
3711 | for (const UniValue& o : outputs.getValues()) { |
3712 | if (!o.isObject()) | |
fc72c078 | 3713 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected object"); |
fc72c078 | 3714 | |
fc72c078 | 3715 | // sanity check, report error if unknown key-value pairs |
0d37ae3a JG |
3716 | for (const string& name_ : o.getKeys()) { |
3717 | std::string s = name_; | |
fc72c078 S |
3718 | if (s != "address" && s != "amount" && s!="memo") |
3719 | throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown key: ")+s); | |
3720 | } | |
3721 | ||
3722 | string address = find_value(o, "address").get_str(); | |
3723 | bool isZaddr = false; | |
b6be3e88 JG |
3724 | CTxDestination taddr = DecodeDestination(address); |
3725 | if (!IsValidDestination(taddr)) { | |
af4057b9 JG |
3726 | auto res = DecodePaymentAddress(address); |
3727 | if (IsValidPaymentAddress(res)) { | |
fc72c078 | 3728 | isZaddr = true; |
af4057b9 JG |
3729 | |
3730 | bool toSapling = boost::get<libzcash::SaplingPaymentAddress>(&res) != nullptr; | |
07d85a64 | 3731 | bool toSprout = !toSapling; |
af4057b9 JG |
3732 | noSproutAddrs = noSproutAddrs && toSapling; |
3733 | ||
0917c84d EOW |
3734 | containsSproutOutput |= toSprout; |
3735 | containsSaplingOutput |= toSapling; | |
3736 | ||
3737 | // Sending to both Sprout and Sapling is currently unsupported using z_sendmany | |
3738 | if (containsSproutOutput && containsSaplingOutput) { | |
3739 | throw JSONRPCError( | |
3740 | RPC_INVALID_PARAMETER, | |
3741 | "Cannot send to both Sprout and Sapling addresses using z_sendmany"); | |
3742 | } | |
3743 | ||
12040363 EOW |
3744 | // If sending between shielded addresses, they must be the same type |
3745 | if ((fromSprout && toSapling) || (fromSapling && toSprout)) { | |
7c02acc5 JG |
3746 | throw JSONRPCError( |
3747 | RPC_INVALID_PARAMETER, | |
12040363 | 3748 | "Cannot send between Sprout and Sapling addresses using z_sendmany"); |
af4057b9 | 3749 | } |
80ed13d5 | 3750 | } else { |
fc72c078 S |
3751 | throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown address format: ")+address ); |
3752 | } | |
3753 | } | |
3754 | ||
3755 | if (setAddress.count(address)) | |
3756 | throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, duplicated address: ")+address); | |
3757 | setAddress.insert(address); | |
3758 | ||
0d37ae3a | 3759 | UniValue memoValue = find_value(o, "memo"); |
fc72c078 | 3760 | string memo; |
0d37ae3a | 3761 | if (!memoValue.isNull()) { |
fc72c078 S |
3762 | memo = memoValue.get_str(); |
3763 | if (!isZaddr) { | |
c938fb1f | 3764 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Memo cannot be used with a taddr. It can only be used with a zaddr."); |
fc72c078 S |
3765 | } else if (!IsHex(memo)) { |
3766 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected memo data in hexadecimal format."); | |
3767 | } | |
6114cfe7 | 3768 | if (memo.length() > ZC_MEMO_SIZE*2) { |
dafb8161 S |
3769 | throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, size of memo is larger than maximum allowed %d", ZC_MEMO_SIZE )); |
3770 | } | |
fc72c078 S |
3771 | } |
3772 | ||
0d37ae3a | 3773 | UniValue av = find_value(o, "amount"); |
fc72c078 S |
3774 | CAmount nAmount = AmountFromValue( av ); |
3775 | if (nAmount < 0) | |
3776 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, amount must be positive"); | |
3777 | ||
dafb8161 S |
3778 | if (isZaddr) { |
3779 | zaddrRecipients.push_back( SendManyRecipient(address, nAmount, memo) ); | |
3780 | } else { | |
3781 | taddrRecipients.push_back( SendManyRecipient(address, nAmount, memo) ); | |
3782 | } | |
af53da02 S |
3783 | |
3784 | nTotalOut += nAmount; | |
fc72c078 S |
3785 | } |
3786 | ||
15ec5525 | 3787 | int nextBlockHeight = chainActive.Height() + 1; |
892ae945 JG |
3788 | CMutableTransaction mtx; |
3789 | mtx.fOverwintered = true; | |
3790 | mtx.nVersionGroupId = SAPLING_VERSION_GROUP_ID; | |
3791 | mtx.nVersion = SAPLING_TX_VERSION; | |
25fee350 | 3792 | unsigned int max_tx_size = MAX_TX_SIZE_AFTER_SAPLING; |
15ec5525 | 3793 | if (!NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING)) { |
892ae945 JG |
3794 | if (NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER)) { |
3795 | mtx.nVersionGroupId = OVERWINTER_VERSION_GROUP_ID; | |
3796 | mtx.nVersion = OVERWINTER_TX_VERSION; | |
3797 | } else { | |
3798 | mtx.fOverwintered = false; | |
3799 | mtx.nVersion = 2; | |
3800 | } | |
3801 | ||
15ec5525 | 3802 | max_tx_size = MAX_TX_SIZE_BEFORE_SAPLING; |
15ec5525 | 3803 | |
892ae945 JG |
3804 | // Check the number of zaddr outputs does not exceed the limit. |
3805 | if (zaddrRecipients.size() > Z_SENDMANY_MAX_ZADDR_OUTPUTS_BEFORE_SAPLING) { | |
3806 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, too many zaddr outputs"); | |
3807 | } | |
3920292b S |
3808 | } |
3809 | ||
de1b86a4 S |
3810 | // If Sapling is not active, do not allow sending from or sending to Sapling addresses. |
3811 | if (!NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING)) { | |
3812 | if (fromSapling || containsSaplingOutput) { | |
3813 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, Sapling has not activated"); | |
3814 | } | |
3815 | } | |
3816 | ||
3920292b S |
3817 | // As a sanity check, estimate and verify that the size of the transaction will be valid. |
3818 | // Depending on the input notes, the actual tx size may turn out to be larger and perhaps invalid. | |
3819 | size_t txsize = 0; | |
3920292b | 3820 | for (int i = 0; i < zaddrRecipients.size(); i++) { |
4dcc48b8 S |
3821 | auto address = std::get<0>(zaddrRecipients[i]); |
3822 | auto res = DecodePaymentAddress(address); | |
3823 | bool toSapling = boost::get<libzcash::SaplingPaymentAddress>(&res) != nullptr; | |
3824 | if (toSapling) { | |
3825 | mtx.vShieldedOutput.push_back(OutputDescription()); | |
3826 | } else { | |
3827 | JSDescription jsdesc; | |
3828 | if (mtx.fOverwintered && (mtx.nVersion >= SAPLING_TX_VERSION)) { | |
3829 | jsdesc.proof = GrothProof(); | |
3830 | } | |
3831 | mtx.vjoinsplit.push_back(jsdesc); | |
b7a6c321 | 3832 | } |
3920292b S |
3833 | } |
3834 | CTransaction tx(mtx); | |
a8e5ae92 | 3835 | txsize += GetSerializeSize(tx, SER_NETWORK, tx.nVersion); |
3920292b S |
3836 | if (fromTaddr) { |
3837 | txsize += CTXIN_SPEND_DUST_SIZE; | |
3838 | txsize += CTXOUT_REGULAR_SIZE; // There will probably be taddr change | |
3839 | } | |
3840 | txsize += CTXOUT_REGULAR_SIZE * taddrRecipients.size(); | |
15ec5525 JG |
3841 | if (txsize > max_tx_size) { |
3842 | throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Too many outputs, size of raw transaction would be larger than limit of %d bytes", max_tx_size )); | |
3920292b S |
3843 | } |
3844 | ||
fc72c078 S |
3845 | // Minimum confirmations |
3846 | int nMinDepth = 1; | |
12448b64 | 3847 | if (params.size() > 2) { |
fc72c078 | 3848 | nMinDepth = params[2].get_int(); |
12448b64 S |
3849 | } |
3850 | if (nMinDepth < 0) { | |
3851 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Minimum number of confirmations cannot be less than 0"); | |
3852 | } | |
fc72c078 | 3853 | |
af53da02 | 3854 | // Fee in Zatoshis, not currency format) |
e5aa9f61 DL |
3855 | CAmount nFee = ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE; |
3856 | CAmount nDefaultFee = nFee; | |
3857 | ||
af53da02 | 3858 | if (params.size() > 3) { |
7eccce4e S |
3859 | if (params[3].get_real() == 0.0) { |
3860 | nFee = 0; | |
3861 | } else { | |
3862 | nFee = AmountFromValue( params[3] ); | |
3863 | } | |
3864 | ||
0b6eeac3 | 3865 | // Check that the user specified fee is not absurd. |
e5aa9f61 DL |
3866 | // This allows amount=0 (and all amount < nDefaultFee) transactions to use the default network fee |
3867 | // or anything less than nDefaultFee instead of being forced to use a custom fee and leak metadata | |
3868 | if (nTotalOut < nDefaultFee) { | |
3869 | if (nFee > nDefaultFee) { | |
3870 | throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Small transaction amount %s has fee %s that is greater than the default fee %s", FormatMoney(nTotalOut), FormatMoney(nFee), FormatMoney(nDefaultFee))); | |
3871 | } | |
3872 | } else { | |
0b6eeac3 | 3873 | // Check that the user specified fee is not absurd. |
e5aa9f61 | 3874 | if (nFee > nTotalOut) { |
4b8c52c6 | 3875 | throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Fee %s is greater than the sum of outputs %s and also greater than the default fee", FormatMoney(nFee), FormatMoney(nTotalOut))); |
e5aa9f61 DL |
3876 | } |
3877 | } | |
af53da02 S |
3878 | } |
3879 | ||
8aa7937d | 3880 | // Use input parameters as the optional context info to be returned by z_getoperationstatus and z_getoperationresult. |
0d37ae3a | 3881 | UniValue o(UniValue::VOBJ); |
8aa7937d S |
3882 | o.push_back(Pair("fromaddress", params[0])); |
3883 | o.push_back(Pair("amounts", params[1])); | |
3884 | o.push_back(Pair("minconf", nMinDepth)); | |
3885 | o.push_back(Pair("fee", std::stod(FormatMoney(nFee)))); | |
0d37ae3a | 3886 | UniValue contextInfo = o; |
8aa7937d | 3887 | |
36e2141d JG |
3888 | // Builder (used if Sapling addresses are involved) |
3889 | boost::optional<TransactionBuilder> builder; | |
af4057b9 | 3890 | if (noSproutAddrs) { |
5eb7129d | 3891 | builder = TransactionBuilder(Params().GetConsensus(), nextBlockHeight, expiryDelta, pwalletMain); |
36e2141d JG |
3892 | } |
3893 | ||
072099d7 | 3894 | // Contextual transaction we will build on |
36e2141d | 3895 | // (used if no Sapling addresses are involved) |
9bb37bf0 | 3896 | CMutableTransaction contextualTx = CreateNewContextualCMutableTransaction(Params().GetConsensus(), nextBlockHeight); |
e6cd2a83 S |
3897 | bool isShielded = !fromTaddr || zaddrRecipients.size() > 0; |
3898 | if (contextualTx.nVersion == 1 && isShielded) { | |
072099d7 S |
3899 | contextualTx.nVersion = 2; // Tx format should support vjoinsplits |
3900 | } | |
3901 | ||
dafb8161 | 3902 | // Create operation and add to global queue |
fc72c078 | 3903 | std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue(); |
36e2141d | 3904 | std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(builder, contextualTx, fromaddress, taddrRecipients, zaddrRecipients, nMinDepth, nFee, contextInfo) ); |
fc72c078 S |
3905 | q->addOperation(operation); |
3906 | AsyncRPCOperationId operationId = operation->getId(); | |
3907 | return operationId; | |
fc72c078 | 3908 | } |
34f0001c | 3909 | |
81a45d69 E |
3910 | UniValue z_setmigration(const UniValue& params, bool fHelp) { |
3911 | if (!EnsureWalletIsAvailable(fHelp)) | |
3912 | return NullUniValue; | |
3913 | if (fHelp || params.size() != 1) | |
3914 | throw runtime_error( | |
3915 | "z_setmigration enabled\n" | |
3916 | "When enabled the Sprout to Sapling migration will attempt to migrate all funds from this wallet’s\n" | |
699288b4 E |
3917 | "Sprout addresses to either the address for Sapling account 0 or the address specified by the parameter\n" |
3918 | "'-migrationdestaddress'.\n" | |
3919 | "\n" | |
3920 | "This migration is designed to minimize information leakage. As a result for wallets with a significant\n" | |
3921 | "Sprout balance, this process may take several weeks. The migration works by sending, up to 5, as many\n" | |
3922 | "transactions as possible whenever the blockchain reaches a height equal to 499 modulo 500. The transaction\n" | |
3923 | "amounts are picked according to the random distribution specified in ZIP 308. The migration will end once\n" | |
9615caa8 | 3924 | "the wallet’s Sprout balance is below " + strprintf("%s %s", FormatMoney(CENT), CURRENCY_UNIT) + ".\n" |
81a45d69 E |
3925 | "\nArguments:\n" |
3926 | "1. enabled (boolean, required) 'true' or 'false' to enable or disable respectively.\n" | |
81a45d69 | 3927 | ); |
162bfc3a E |
3928 | LOCK(pwalletMain->cs_wallet); |
3929 | pwalletMain->fSaplingMigrationEnabled = params[0].get_bool(); | |
699288b4 | 3930 | return NullUniValue; |
81a45d69 | 3931 | } |
34f0001c | 3932 | |
6e82d728 E |
3933 | UniValue z_getmigrationstatus(const UniValue& params, bool fHelp) { |
3934 | if (!EnsureWalletIsAvailable(fHelp)) | |
3935 | return NullUniValue; | |
3936 | if (fHelp || params.size() != 0) | |
3937 | throw runtime_error( | |
3938 | "z_getmigrationstatus\n" | |
3939 | "Returns information about the status of the Sprout to Sapling migration.\n" | |
406b8ff5 E |
3940 | "Note: A transaction is defined as finalized if it has at least ten confirmations.\n" |
3941 | "Also, it is possible that manually created transactions involving this wallet\n" | |
6e82d728 E |
3942 | "will be included in the result.\n" |
3943 | "\nResult:\n" | |
3944 | "{\n" | |
3945 | " \"enabled\": true|false, (boolean) Whether or not migration is enabled\n" | |
108e587c | 3946 | " \"destination_address\": \"zaddr\", (string) The Sapling address that will receive Sprout funds\n" |
6e82d728 E |
3947 | " \"unmigrated_amount\": nnn.n, (numeric) The total amount of unmigrated " + CURRENCY_UNIT +" \n" |
3948 | " \"unfinalized_migrated_amount\": nnn.n, (numeric) The total amount of unfinalized " + CURRENCY_UNIT + " \n" | |
3949 | " \"finalized_migrated_amount\": nnn.n, (numeric) The total amount of finalized " + CURRENCY_UNIT + " \n" | |
3950 | " \"finalized_migration_transactions\": nnn, (numeric) The number of migration transactions involving this wallet\n" | |
345177cf | 3951 | " \"time_started\": ttt, (numeric, optional) The block time of the first migration transaction as a Unix timestamp\n" |
6e82d728 E |
3952 | " \"migration_txids\": [txids] (json array of strings) An array of all migration txids involving this wallet\n" |
3953 | "}\n" | |
3954 | ); | |
3955 | LOCK2(cs_main, pwalletMain->cs_wallet); | |
3956 | UniValue migrationStatus(UniValue::VOBJ); | |
3957 | migrationStatus.push_back(Pair("enabled", pwalletMain->fSaplingMigrationEnabled)); | |
108e587c | 3958 | // The "destination_address" field MAY be omitted if the "-migrationdestaddress" |
6e82d728 E |
3959 | // parameter is not set and no default address has yet been generated. |
3960 | // Note: The following function may return the default address even if it has not been added to the wallet | |
3961 | auto destinationAddress = AsyncRPCOperation_saplingmigration::getMigrationDestAddress(pwalletMain->GetHDSeedForRPC()); | |
3962 | migrationStatus.push_back(Pair("destination_address", EncodePaymentAddress(destinationAddress))); | |
3963 | // The values of "unmigrated_amount" and "migrated_amount" MUST take into | |
3964 | // account failed transactions, that were not mined within their expiration | |
3965 | // height. | |
3966 | { | |
a630f503 | 3967 | std::vector<SproutNoteEntry> sproutEntries; |
6e82d728 | 3968 | std::vector<SaplingNoteEntry> saplingEntries; |
94e419f9 E |
3969 | std::set<PaymentAddress> noFilter; |
3970 | // Here we are looking for any and all Sprout notes for which we have the spending key, including those | |
3971 | // which are locked and/or only exist in the mempool, as they should be included in the unmigrated amount. | |
3972 | pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, noFilter, 0, INT_MAX, true, true, false); | |
6e82d728 E |
3973 | CAmount unmigratedAmount = 0; |
3974 | for (const auto& sproutEntry : sproutEntries) { | |
a630f503 | 3975 | unmigratedAmount += sproutEntry.note.value(); |
6e82d728 E |
3976 | } |
3977 | migrationStatus.push_back(Pair("unmigrated_amount", FormatMoney(unmigratedAmount))); | |
3978 | } | |
3979 | // "migration_txids" is a list of strings representing transaction IDs of all | |
3980 | // known migration transactions involving this wallet, as lowercase hexadecimal | |
3981 | // in RPC byte order. | |
3982 | UniValue migrationTxids(UniValue::VARR); | |
6e82d728 E |
3983 | CAmount unfinalizedMigratedAmount = 0; |
3984 | CAmount finalizedMigratedAmount = 0; | |
3985 | int numFinalizedMigrationTxs = 0; | |
3986 | uint64_t timeStarted = 0; | |
3987 | for (const auto& txPair : pwalletMain->mapWallet) { | |
3988 | CWalletTx tx = txPair.second; | |
3989 | // A given transaction is defined as a migration transaction iff it has: | |
3990 | // * one or more Sprout JoinSplits with nonzero vpub_new field; and | |
3991 | // * no Sapling Spends, and; | |
3992 | // * one or more Sapling Outputs. | |
345177cf | 3993 | if (tx.vjoinsplit.size() > 0 && tx.vShieldedSpend.empty() && tx.vShieldedOutput.size() > 0) { |
e9530f40 | 3994 | bool nonZeroVPubNew = false; |
6e82d728 | 3995 | for (const auto& js : tx.vjoinsplit) { |
e9530f40 E |
3996 | if (js.vpub_new > 0) { |
3997 | nonZeroVPubNew = true; | |
3998 | break; | |
3999 | } | |
6e82d728 | 4000 | } |
e9530f40 | 4001 | if (!nonZeroVPubNew) { |
6e82d728 E |
4002 | continue; |
4003 | } | |
4004 | migrationTxids.push_back(txPair.first.ToString()); | |
e9530f40 | 4005 | // A transaction is "finalized" iff it has at least 10 confirmations. |
6e82d728 | 4006 | // TODO: subject to change, if the recommended number of confirmations changes. |
e14cf966 | 4007 | if (tx.GetDepthInMainChain() >= 10) { |
e9530f40 | 4008 | finalizedMigratedAmount -= tx.valueBalance; |
6e82d728 E |
4009 | ++numFinalizedMigrationTxs; |
4010 | } else { | |
e9530f40 | 4011 | unfinalizedMigratedAmount -= tx.valueBalance; |
6e82d728 | 4012 | } |
7cf4749d E |
4013 | // If the transaction is in the mempool it will not be associated with a block yet |
4014 | if (tx.hashBlock.IsNull() || mapBlockIndex[tx.hashBlock] == nullptr) { | |
4015 | continue; | |
4016 | } | |
4017 | CBlockIndex* blockIndex = mapBlockIndex[tx.hashBlock]; | |
6e82d728 E |
4018 | // The value of "time_started" is the earliest Unix timestamp of any known |
4019 | // migration transaction involving this wallet; if there is no such transaction, | |
4020 | // then the field is absent. | |
4021 | if (timeStarted == 0 || timeStarted > blockIndex->GetBlockTime()) { | |
4022 | timeStarted = blockIndex->GetBlockTime(); | |
4023 | } | |
4024 | } | |
4025 | } | |
4026 | migrationStatus.push_back(Pair("unfinalized_migrated_amount", FormatMoney(unfinalizedMigratedAmount))); | |
4027 | migrationStatus.push_back(Pair("finalized_migrated_amount", FormatMoney(finalizedMigratedAmount))); | |
4028 | migrationStatus.push_back(Pair("finalized_migration_transactions", numFinalizedMigrationTxs)); | |
4029 | if (timeStarted > 0) { | |
4030 | migrationStatus.push_back(Pair("time_started", timeStarted)); | |
4031 | } | |
4032 | migrationStatus.push_back(Pair("migration_txids", migrationTxids)); | |
4033 | return migrationStatus; | |
4034 | } | |
4035 | ||
06c19063 S |
4036 | /** |
4037 | When estimating the number of coinbase utxos we can shield in a single transaction: | |
4038 | 1. Joinsplit description is 1802 bytes. | |
4039 | 2. Transaction overhead ~ 100 bytes | |
4040 | 3. Spending a typical P2PKH is >=148 bytes, as defined in CTXIN_SPEND_DUST_SIZE. | |
4041 | 4. Spending a multi-sig P2SH address can vary greatly: | |
4042 | https://github.com/bitcoin/bitcoin/blob/c3ad56f4e0b587d8d763af03d743fdfc2d180c9b/src/main.cpp#L517 | |
4043 | In real-world coinbase utxos, we consider a 3-of-3 multisig, where the size is roughly: | |
4044 | (3*(33+1))+3 = 105 byte redeem script | |
4045 | 105 + 1 + 3*(73+1) = 328 bytes of scriptSig, rounded up to 400 based on testnet experiments. | |
4046 | */ | |
4047 | #define CTXIN_SPEND_P2SH_SIZE 400 | |
4048 | ||
c5dabd2b S |
4049 | #define SHIELD_COINBASE_DEFAULT_LIMIT 50 |
4050 | ||
06c19063 S |
4051 | UniValue z_shieldcoinbase(const UniValue& params, bool fHelp) |
4052 | { | |
4053 | if (!EnsureWalletIsAvailable(fHelp)) | |
4054 | return NullUniValue; | |
4055 | ||
c5dabd2b | 4056 | if (fHelp || params.size() < 2 || params.size() > 4) |
06c19063 | 4057 | throw runtime_error( |
c5dabd2b | 4058 | "z_shieldcoinbase \"fromaddress\" \"tozaddress\" ( fee ) ( limit )\n" |
06c19063 S |
4059 | "\nShield transparent coinbase funds by sending to a shielded zaddr. This is an asynchronous operation and utxos" |
4060 | "\nselected for shielding will be locked. If there is an error, they are unlocked. The RPC call `listlockunspent`" | |
c5dabd2b | 4061 | "\ncan be used to return a list of locked utxos. The number of coinbase utxos selected for shielding can be limited" |
31afbcc5 JG |
4062 | "\nby the caller. If the limit parameter is set to zero, and Overwinter is not yet active, the -mempooltxinputlimit" |
4063 | "\noption will determine the number of uxtos. Any limit is constrained by the consensus rule defining a maximum" | |
4064 | "\ntransaction size of " | |
25fee350 | 4065 | + strprintf("%d bytes before Sapling, and %d bytes once Sapling activates.", MAX_TX_SIZE_BEFORE_SAPLING, MAX_TX_SIZE_AFTER_SAPLING) |
06c19063 S |
4066 | + HelpRequiringPassphrase() + "\n" |
4067 | "\nArguments:\n" | |
4068 | "1. \"fromaddress\" (string, required) The address is a taddr or \"*\" for all taddrs belonging to the wallet.\n" | |
4069 | "2. \"toaddress\" (string, required) The address is a zaddr.\n" | |
4070 | "3. fee (numeric, optional, default=" | |
4071 | + strprintf("%s", FormatMoney(SHIELD_COINBASE_DEFAULT_MINERS_FEE)) + ") The fee amount to attach to this transaction.\n" | |
c5dabd2b | 4072 | "4. limit (numeric, optional, default=" |
31afbcc5 | 4073 | + strprintf("%d", SHIELD_COINBASE_DEFAULT_LIMIT) + ") Limit on the maximum number of utxos to shield. Set to 0 to use node option -mempooltxinputlimit (before Overwinter), or as many as will fit in the transaction (after Overwinter).\n" |
06c19063 S |
4074 | "\nResult:\n" |
4075 | "{\n" | |
06c19063 S |
4076 | " \"remainingUTXOs\": xxx (numeric) Number of coinbase utxos still available for shielding.\n" |
4077 | " \"remainingValue\": xxx (numeric) Value of coinbase utxos still available for shielding.\n" | |
9eb8089e JG |
4078 | " \"shieldingUTXOs\": xxx (numeric) Number of coinbase utxos being shielded.\n" |
4079 | " \"shieldingValue\": xxx (numeric) Value of coinbase utxos being shielded.\n" | |
4080 | " \"opid\": xxx (string) An operationid to pass to z_getoperationstatus to get the result of the operation.\n" | |
06c19063 | 4081 | "}\n" |
337a99a2 JG |
4082 | "\nExamples:\n" |
4083 | + HelpExampleCli("z_shieldcoinbase", "\"t1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" \"ztfaW34Gj9FrnGUEf833ywDVL62NWXBM81u6EQnM6VR45eYnXhwztecW1SjxA7JrmAXKJhxhj3vDNEpVCQoSvVoSpmbhtjf\"") | |
4084 | + HelpExampleRpc("z_shieldcoinbase", "\"t1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\", \"ztfaW34Gj9FrnGUEf833ywDVL62NWXBM81u6EQnM6VR45eYnXhwztecW1SjxA7JrmAXKJhxhj3vDNEpVCQoSvVoSpmbhtjf\"") | |
06c19063 S |
4085 | ); |
4086 | ||
4087 | LOCK2(cs_main, pwalletMain->cs_wallet); | |
4088 | ||
4089 | // Validate the from address | |
4090 | auto fromaddress = params[0].get_str(); | |
4091 | bool isFromWildcard = fromaddress == "*"; | |
b6be3e88 | 4092 | CTxDestination taddr; |
06c19063 | 4093 | if (!isFromWildcard) { |
b6be3e88 JG |
4094 | taddr = DecodeDestination(fromaddress); |
4095 | if (!IsValidDestination(taddr)) { | |
06c19063 S |
4096 | throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid from address, should be a taddr or \"*\"."); |
4097 | } | |
4098 | } | |
4099 | ||
4100 | // Validate the destination address | |
4101 | auto destaddress = params[1].get_str(); | |
e5eab182 | 4102 | if (!IsValidPaymentAddressString(destaddress)) { |
06c19063 S |
4103 | throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown address format: ") + destaddress ); |
4104 | } | |
4105 | ||
4106 | // Convert fee from currency format to zatoshis | |
4107 | CAmount nFee = SHIELD_COINBASE_DEFAULT_MINERS_FEE; | |
4108 | if (params.size() > 2) { | |
4109 | if (params[2].get_real() == 0.0) { | |
4110 | nFee = 0; | |
4111 | } else { | |
4112 | nFee = AmountFromValue( params[2] ); | |
4113 | } | |
4114 | } | |
4115 | ||
c5dabd2b S |
4116 | int nLimit = SHIELD_COINBASE_DEFAULT_LIMIT; |
4117 | if (params.size() > 3) { | |
4118 | nLimit = params[3].get_int(); | |
4119 | if (nLimit < 0) { | |
4120 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Limit on maximum number of utxos cannot be negative"); | |
4121 | } | |
4122 | } | |
4123 | ||
31afbcc5 JG |
4124 | int nextBlockHeight = chainActive.Height() + 1; |
4125 | bool overwinterActive = NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER); | |
25fee350 | 4126 | unsigned int max_tx_size = MAX_TX_SIZE_AFTER_SAPLING; |
15ec5525 JG |
4127 | if (!NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING)) { |
4128 | max_tx_size = MAX_TX_SIZE_BEFORE_SAPLING; | |
4129 | } | |
31afbcc5 | 4130 | |
de1b86a4 S |
4131 | // If Sapling is not active, do not allow sending to a Sapling address. |
4132 | if (!NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING)) { | |
4133 | auto res = DecodePaymentAddress(destaddress); | |
4134 | if (IsValidPaymentAddress(res)) { | |
4135 | bool toSapling = boost::get<libzcash::SaplingPaymentAddress>(&res) != nullptr; | |
4136 | if (toSapling) { | |
4137 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, Sapling has not activated"); | |
4138 | } | |
4139 | } else { | |
4140 | throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown address format: ") + destaddress ); | |
4141 | } | |
4142 | } | |
4143 | ||
06c19063 S |
4144 | // Prepare to get coinbase utxos |
4145 | std::vector<ShieldCoinbaseUTXO> inputs; | |
4146 | CAmount shieldedValue = 0; | |
4147 | CAmount remainingValue = 0; | |
4148 | size_t estimatedTxSize = 2000; // 1802 joinsplit description + tx overhead + wiggle room | |
4149 | size_t utxoCounter = 0; | |
4150 | bool maxedOutFlag = false; | |
31afbcc5 | 4151 | size_t mempoolLimit = (nLimit != 0) ? nLimit : (overwinterActive ? 0 : (size_t)GetArg("-mempooltxinputlimit", 0)); |
06c19063 S |
4152 | |
4153 | // Set of addresses to filter utxos by | |
b6be3e88 | 4154 | std::set<CTxDestination> destinations = {}; |
06c19063 | 4155 | if (!isFromWildcard) { |
b6be3e88 | 4156 | destinations.insert(taddr); |
06c19063 S |
4157 | } |
4158 | ||
4159 | // Get available utxos | |
4160 | vector<COutput> vecOutputs; | |
4161 | pwalletMain->AvailableCoins(vecOutputs, true, NULL, false, true); | |
4162 | ||
4163 | // Find unspent coinbase utxos and update estimated size | |
4164 | BOOST_FOREACH(const COutput& out, vecOutputs) { | |
4165 | if (!out.fSpendable) { | |
4166 | continue; | |
4167 | } | |
4168 | ||
4169 | CTxDestination address; | |
4170 | if (!ExtractDestination(out.tx->vout[out.i].scriptPubKey, address)) { | |
4171 | continue; | |
4172 | } | |
4173 | // If taddr is not wildcard "*", filter utxos | |
b6be3e88 | 4174 | if (destinations.size() > 0 && !destinations.count(address)) { |
06c19063 S |
4175 | continue; |
4176 | } | |
4177 | ||
4178 | if (!out.tx->IsCoinBase()) { | |
4179 | continue; | |
4180 | } | |
4181 | ||
4182 | utxoCounter++; | |
5f91a956 | 4183 | auto scriptPubKey = out.tx->vout[out.i].scriptPubKey; |
06c19063 S |
4184 | CAmount nValue = out.tx->vout[out.i].nValue; |
4185 | ||
4186 | if (!maxedOutFlag) { | |
b6be3e88 | 4187 | size_t increase = (boost::get<CScriptID>(&address) != nullptr) ? CTXIN_SPEND_P2SH_SIZE : CTXIN_SPEND_DUST_SIZE; |
15ec5525 | 4188 | if (estimatedTxSize + increase >= max_tx_size || |
06c19063 S |
4189 | (mempoolLimit > 0 && utxoCounter > mempoolLimit)) |
4190 | { | |
4191 | maxedOutFlag = true; | |
4192 | } else { | |
4193 | estimatedTxSize += increase; | |
5f91a956 | 4194 | ShieldCoinbaseUTXO utxo = {out.tx->GetHash(), out.i, scriptPubKey, nValue}; |
06c19063 S |
4195 | inputs.push_back(utxo); |
4196 | shieldedValue += nValue; | |
4197 | } | |
4198 | } | |
4199 | ||
4200 | if (maxedOutFlag) { | |
4201 | remainingValue += nValue; | |
4202 | } | |
4203 | } | |
4204 | ||
4205 | size_t numUtxos = inputs.size(); | |
4206 | ||
4207 | if (numUtxos == 0) { | |
4208 | throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Could not find any coinbase funds to shield."); | |
4209 | } | |
4210 | ||
4211 | if (shieldedValue < nFee) { | |
4212 | throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, | |
4213 | strprintf("Insufficient coinbase funds, have %s, which is less than miners fee %s", | |
4214 | FormatMoney(shieldedValue), FormatMoney(nFee))); | |
4215 | } | |
4216 | ||
4217 | // Check that the user specified fee is sane (if too high, it can result in error -25 absurd fee) | |
4218 | CAmount netAmount = shieldedValue - nFee; | |
4219 | if (nFee > netAmount) { | |
4220 | throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Fee %s is greater than the net amount to be shielded %s", FormatMoney(nFee), FormatMoney(netAmount))); | |
4221 | } | |
4222 | ||
4223 | // Keep record of parameters in context object | |
4224 | UniValue contextInfo(UniValue::VOBJ); | |
4225 | contextInfo.push_back(Pair("fromaddress", params[0])); | |
4226 | contextInfo.push_back(Pair("toaddress", params[1])); | |
4227 | contextInfo.push_back(Pair("fee", ValueFromAmount(nFee))); | |
4228 | ||
5f91a956 JG |
4229 | // Builder (used if Sapling addresses are involved) |
4230 | TransactionBuilder builder = TransactionBuilder( | |
5eb7129d | 4231 | Params().GetConsensus(), nextBlockHeight, expiryDelta, pwalletMain); |
5f91a956 | 4232 | |
072099d7 | 4233 | // Contextual transaction we will build on |
5f91a956 | 4234 | // (used if no Sapling addresses are involved) |
072099d7 | 4235 | CMutableTransaction contextualTx = CreateNewContextualCMutableTransaction( |
9bb37bf0 | 4236 | Params().GetConsensus(), nextBlockHeight); |
072099d7 S |
4237 | if (contextualTx.nVersion == 1) { |
4238 | contextualTx.nVersion = 2; // Tx format should support vjoinsplits | |
4239 | } | |
4240 | ||
06c19063 S |
4241 | // Create operation and add to global queue |
4242 | std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue(); | |
5f91a956 | 4243 | std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_shieldcoinbase(builder, contextualTx, inputs, destaddress, nFee, contextInfo) ); |
06c19063 S |
4244 | q->addOperation(operation); |
4245 | AsyncRPCOperationId operationId = operation->getId(); | |
4246 | ||
4247 | // Return continuation information | |
4248 | UniValue o(UniValue::VOBJ); | |
4ff92bb6 | 4249 | o.push_back(Pair("remainingUTXOs", static_cast<uint64_t>(utxoCounter - numUtxos))); |
06c19063 | 4250 | o.push_back(Pair("remainingValue", ValueFromAmount(remainingValue))); |
4ff92bb6 | 4251 | o.push_back(Pair("shieldingUTXOs", static_cast<uint64_t>(numUtxos))); |
06c19063 S |
4252 | o.push_back(Pair("shieldingValue", ValueFromAmount(shieldedValue))); |
4253 | o.push_back(Pair("opid", operationId)); | |
4254 | return o; | |
4255 | } | |
4256 | ||
4257 | ||
6e9c7629 | 4258 | #define MERGE_TO_ADDRESS_DEFAULT_TRANSPARENT_LIMIT 50 |
487c9020 EOW |
4259 | #define MERGE_TO_ADDRESS_DEFAULT_SPROUT_LIMIT 20 |
4260 | #define MERGE_TO_ADDRESS_DEFAULT_SAPLING_LIMIT 200 | |
6e9c7629 | 4261 | |
a8e5ae92 | 4262 | #define JOINSPLIT_SIZE GetSerializeSize(JSDescription(), SER_NETWORK, PROTOCOL_VERSION) |
487c9020 EOW |
4263 | #define OUTPUTDESCRIPTION_SIZE GetSerializeSize(OutputDescription(), SER_NETWORK, PROTOCOL_VERSION) |
4264 | #define SPENDDESCRIPTION_SIZE GetSerializeSize(SpendDescription(), SER_NETWORK, PROTOCOL_VERSION) | |
6e9c7629 JG |
4265 | |
4266 | UniValue z_mergetoaddress(const UniValue& params, bool fHelp) | |
4267 | { | |
4268 | if (!EnsureWalletIsAvailable(fHelp)) | |
4269 | return NullUniValue; | |
4270 | ||
66dfcc13 LR |
4271 | string enableArg = "zmergetoaddress"; |
4272 | auto fEnableMergeToAddress = fExperimentalMode && GetBoolArg("-" + enableArg, false); | |
553a5c1a JG |
4273 | std::string strDisabledMsg = ""; |
4274 | if (!fEnableMergeToAddress) { | |
66dfcc13 | 4275 | strDisabledMsg = experimentalDisabledHelpMsg("z_mergetoaddress", enableArg); |
553a5c1a JG |
4276 | } |
4277 | ||
6e9c7629 JG |
4278 | if (fHelp || params.size() < 2 || params.size() > 6) |
4279 | throw runtime_error( | |
4280 | "z_mergetoaddress [\"fromaddress\", ... ] \"toaddress\" ( fee ) ( transparent_limit ) ( shielded_limit ) ( memo )\n" | |
553a5c1a | 4281 | + strDisabledMsg + |
6e9c7629 JG |
4282 | "\nMerge multiple UTXOs and notes into a single UTXO or note. Coinbase UTXOs are ignored; use `z_shieldcoinbase`" |
4283 | "\nto combine those into a single note." | |
4284 | "\n\nThis is an asynchronous operation, and UTXOs selected for merging will be locked. If there is an error, they" | |
4285 | "\nare unlocked. The RPC call `listlockunspent` can be used to return a list of locked UTXOs." | |
4286 | "\n\nThe number of UTXOs and notes selected for merging can be limited by the caller. If the transparent limit" | |
31afbcc5 | 4287 | "\nparameter is set to zero, and Overwinter is not yet active, the -mempooltxinputlimit option will determine the" |
25f1f7dc EOW |
4288 | "\nnumber of UTXOs. After Overwinter has activated -mempooltxinputlimit is ignored and having a transparent" |
4289 | "\ninput limit of zero will mean limit the number of UTXOs based on the size of the transaction. Any limit is" | |
4290 | "\nconstrained by the consensus rule defining a maximum transaction size of " | |
4291 | + strprintf("%d bytes before Sapling, and %d", MAX_TX_SIZE_BEFORE_SAPLING, MAX_TX_SIZE_AFTER_SAPLING) | |
4292 | + "\nbytes once Sapling activates." | |
6e9c7629 JG |
4293 | + HelpRequiringPassphrase() + "\n" |
4294 | "\nArguments:\n" | |
25f1f7dc | 4295 | "1. fromaddresses (array, required) A JSON array with addresses.\n" |
6e9c7629 | 4296 | " The following special strings are accepted inside the array:\n" |
25f1f7dc EOW |
4297 | " - \"ANY_TADDR\": Merge UTXOs from any taddrs belonging to the wallet.\n" |
4298 | " - \"ANY_SPROUT\": Merge notes from any Sprout zaddrs belonging to the wallet.\n" | |
4299 | " - \"ANY_SAPLING\": Merge notes from any Sapling zaddrs belonging to the wallet.\n" | |
4300 | " If a special string is given, any given addresses of that type will be counted as duplicates and cause an error.\n" | |
6e9c7629 | 4301 | " [\n" |
25f1f7dc | 4302 | " \"address\" (string) Can be a taddr or a zaddr\n" |
6e9c7629 JG |
4303 | " ,...\n" |
4304 | " ]\n" | |
25f1f7dc | 4305 | "2. \"toaddress\" (string, required) The taddr or zaddr to send the funds to.\n" |
6e9c7629 JG |
4306 | "3. fee (numeric, optional, default=" |
4307 | + strprintf("%s", FormatMoney(MERGE_TO_ADDRESS_OPERATION_DEFAULT_MINERS_FEE)) + ") The fee amount to attach to this transaction.\n" | |
4308 | "4. transparent_limit (numeric, optional, default=" | |
31afbcc5 | 4309 | + strprintf("%d", MERGE_TO_ADDRESS_DEFAULT_TRANSPARENT_LIMIT) + ") Limit on the maximum number of UTXOs to merge. Set to 0 to use node option -mempooltxinputlimit (before Overwinter), or as many as will fit in the transaction (after Overwinter).\n" |
25f1f7dc | 4310 | "5. shielded_limit (numeric, optional, default=" |
487c9020 | 4311 | + strprintf("%d Sprout or %d Sapling Notes", MERGE_TO_ADDRESS_DEFAULT_SPROUT_LIMIT, MERGE_TO_ADDRESS_DEFAULT_SAPLING_LIMIT) + ") Limit on the maximum number of notes to merge. Set to 0 to merge as many as will fit in the transaction.\n" |
25f1f7dc | 4312 | "6. \"memo\" (string, optional) Encoded as hex. When toaddress is a zaddr, this will be stored in the memo field of the new note.\n" |
6e9c7629 JG |
4313 | "\nResult:\n" |
4314 | "{\n" | |
4315 | " \"remainingUTXOs\": xxx (numeric) Number of UTXOs still available for merging.\n" | |
4316 | " \"remainingTransparentValue\": xxx (numeric) Value of UTXOs still available for merging.\n" | |
4317 | " \"remainingNotes\": xxx (numeric) Number of notes still available for merging.\n" | |
4318 | " \"remainingShieldedValue\": xxx (numeric) Value of notes still available for merging.\n" | |
4319 | " \"mergingUTXOs\": xxx (numeric) Number of UTXOs being merged.\n" | |
4320 | " \"mergingTransparentValue\": xxx (numeric) Value of UTXOs being merged.\n" | |
4321 | " \"mergingNotes\": xxx (numeric) Number of notes being merged.\n" | |
4322 | " \"mergingShieldedValue\": xxx (numeric) Value of notes being merged.\n" | |
25f1f7dc | 4323 | " \"opid\": xxx (string) An operationid to pass to z_getoperationstatus to get the result of the operation.\n" |
6e9c7629 JG |
4324 | "}\n" |
4325 | "\nExamples:\n" | |
25f1f7dc EOW |
4326 | + HelpExampleCli("z_mergetoaddress", "'[\"ANY_SAPLING\", \"t1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\"]' ztestsapling19rnyu293v44f0kvtmszhx35lpdug574twc0lwyf4s7w0umtkrdq5nfcauxrxcyfmh3m7slemqsj") |
4327 | + HelpExampleRpc("z_mergetoaddress", "[\"ANY_SAPLING\", \"t1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\"], \"ztestsapling19rnyu293v44f0kvtmszhx35lpdug574twc0lwyf4s7w0umtkrdq5nfcauxrxcyfmh3m7slemqsj\"") | |
6e9c7629 JG |
4328 | ); |
4329 | ||
553a5c1a | 4330 | if (!fEnableMergeToAddress) { |
25f1f7dc | 4331 | throw JSONRPCError(RPC_WALLET_ERROR, "Error: z_mergetoaddress is disabled. Run './zcash-cli help z_mergetoaddress' for instructions on how to enable this feature."); |
553a5c1a JG |
4332 | } |
4333 | ||
6e9c7629 JG |
4334 | LOCK2(cs_main, pwalletMain->cs_wallet); |
4335 | ||
6e9c7629 | 4336 | bool useAnyUTXO = false; |
487c9020 EOW |
4337 | bool useAnySprout = false; |
4338 | bool useAnySapling = false; | |
b6be3e88 | 4339 | std::set<CTxDestination> taddrs = {}; |
6e9c7629 JG |
4340 | std::set<libzcash::PaymentAddress> zaddrs = {}; |
4341 | ||
4342 | UniValue addresses = params[0].get_array(); | |
4343 | if (addresses.size()==0) | |
4344 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, fromaddresses array is empty."); | |
4345 | ||
4346 | // Keep track of addresses to spot duplicates | |
4347 | std::set<std::string> setAddress; | |
4348 | ||
4349 | // Sources | |
4350 | for (const UniValue& o : addresses.getValues()) { | |
4351 | if (!o.isStr()) | |
4352 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected string"); | |
4353 | ||
4354 | std::string address = o.get_str(); | |
487c9020 EOW |
4355 | |
4356 | if (address == "ANY_TADDR") { | |
6e9c7629 | 4357 | useAnyUTXO = true; |
487c9020 EOW |
4358 | } else if (address == "ANY_SPROUT") { |
4359 | useAnySprout = true; | |
4360 | } else if (address == "ANY_SAPLING") { | |
4361 | useAnySapling = true; | |
6e9c7629 | 4362 | } else { |
b6be3e88 JG |
4363 | CTxDestination taddr = DecodeDestination(address); |
4364 | if (IsValidDestination(taddr)) { | |
487c9020 | 4365 | taddrs.insert(taddr); |
6e9c7629 | 4366 | } else { |
80ed13d5 | 4367 | auto zaddr = DecodePaymentAddress(address); |
e5eab182 | 4368 | if (IsValidPaymentAddress(zaddr)) { |
487c9020 | 4369 | zaddrs.insert(zaddr); |
80ed13d5 | 4370 | } else { |
487c9020 | 4371 | throw JSONRPCError(RPC_INVALID_PARAMETER, string("Unknown address format: ") + address); |
6e9c7629 JG |
4372 | } |
4373 | } | |
4374 | } | |
4375 | ||
4376 | if (setAddress.count(address)) | |
4377 | throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, duplicated address: ") + address); | |
4378 | setAddress.insert(address); | |
4379 | } | |
4380 | ||
487c9020 | 4381 | if (useAnyUTXO && taddrs.size() > 0) { |
25f1f7dc | 4382 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify specific taddrs when using \"ANY_TADDR\""); |
487c9020 EOW |
4383 | } |
4384 | if ((useAnySprout || useAnySapling) && zaddrs.size() > 0) { | |
25f1f7dc | 4385 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify specific zaddrs when using \"ANY_SPROUT\" or \"ANY_SAPLING\""); |
487c9020 EOW |
4386 | } |
4387 | ||
4388 | const int nextBlockHeight = chainActive.Height() + 1; | |
4389 | const bool overwinterActive = NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER); | |
4390 | const bool saplingActive = NetworkUpgradeActive(nextBlockHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING); | |
4391 | ||
6e9c7629 JG |
4392 | // Validate the destination address |
4393 | auto destaddress = params[1].get_str(); | |
487c9020 | 4394 | bool isToSproutZaddr = false; |
61caa466 | 4395 | bool isToSaplingZaddr = false; |
b6be3e88 JG |
4396 | CTxDestination taddr = DecodeDestination(destaddress); |
4397 | if (!IsValidDestination(taddr)) { | |
487c9020 EOW |
4398 | auto decodeAddr = DecodePaymentAddress(destaddress); |
4399 | if (IsValidPaymentAddress(decodeAddr)) { | |
4400 | if (boost::get<libzcash::SaplingPaymentAddress>(&decodeAddr) != nullptr) { | |
4401 | isToSaplingZaddr = true; | |
4402 | // If Sapling is not active, do not allow sending to a sapling addresses. | |
4403 | if (!saplingActive) { | |
4404 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, Sapling has not activated"); | |
4405 | } | |
61caa466 | 4406 | } else { |
487c9020 | 4407 | isToSproutZaddr = true; |
61caa466 | 4408 | } |
80ed13d5 | 4409 | } else { |
6e9c7629 JG |
4410 | throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown address format: ") + destaddress ); |
4411 | } | |
4412 | } | |
4413 | ||
4414 | // Convert fee from currency format to zatoshis | |
4415 | CAmount nFee = SHIELD_COINBASE_DEFAULT_MINERS_FEE; | |
4416 | if (params.size() > 2) { | |
4417 | if (params[2].get_real() == 0.0) { | |
4418 | nFee = 0; | |
4419 | } else { | |
4420 | nFee = AmountFromValue( params[2] ); | |
4421 | } | |
4422 | } | |
4423 | ||
4424 | int nUTXOLimit = MERGE_TO_ADDRESS_DEFAULT_TRANSPARENT_LIMIT; | |
4425 | if (params.size() > 3) { | |
4426 | nUTXOLimit = params[3].get_int(); | |
4427 | if (nUTXOLimit < 0) { | |
4428 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Limit on maximum number of UTXOs cannot be negative"); | |
4429 | } | |
4430 | } | |
4431 | ||
487c9020 EOW |
4432 | int sproutNoteLimit = MERGE_TO_ADDRESS_DEFAULT_SPROUT_LIMIT; |
4433 | int saplingNoteLimit = MERGE_TO_ADDRESS_DEFAULT_SAPLING_LIMIT; | |
6e9c7629 | 4434 | if (params.size() > 4) { |
487c9020 | 4435 | int nNoteLimit = params[4].get_int(); |
6e9c7629 JG |
4436 | if (nNoteLimit < 0) { |
4437 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Limit on maximum number of notes cannot be negative"); | |
4438 | } | |
487c9020 EOW |
4439 | sproutNoteLimit = nNoteLimit; |
4440 | saplingNoteLimit = nNoteLimit; | |
6e9c7629 JG |
4441 | } |
4442 | ||
4443 | std::string memo; | |
4444 | if (params.size() > 5) { | |
4445 | memo = params[5].get_str(); | |
487c9020 | 4446 | if (!(isToSproutZaddr || isToSaplingZaddr)) { |
6e9c7629 JG |
4447 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Memo can not be used with a taddr. It can only be used with a zaddr."); |
4448 | } else if (!IsHex(memo)) { | |
4449 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected memo data in hexadecimal format."); | |
4450 | } | |
4451 | if (memo.length() > ZC_MEMO_SIZE*2) { | |
4452 | throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, size of memo is larger than maximum allowed %d", ZC_MEMO_SIZE )); | |
4453 | } | |
4454 | } | |
4455 | ||
4456 | MergeToAddressRecipient recipient(destaddress, memo); | |
4457 | ||
4458 | // Prepare to get UTXOs and notes | |
4459 | std::vector<MergeToAddressInputUTXO> utxoInputs; | |
487c9020 EOW |
4460 | std::vector<MergeToAddressInputSproutNote> sproutNoteInputs; |
4461 | std::vector<MergeToAddressInputSaplingNote> saplingNoteInputs; | |
6e9c7629 JG |
4462 | CAmount mergedUTXOValue = 0; |
4463 | CAmount mergedNoteValue = 0; | |
4464 | CAmount remainingUTXOValue = 0; | |
4465 | CAmount remainingNoteValue = 0; | |
4466 | size_t utxoCounter = 0; | |
4467 | size_t noteCounter = 0; | |
4468 | bool maxedOutUTXOsFlag = false; | |
4469 | bool maxedOutNotesFlag = false; | |
31afbcc5 | 4470 | size_t mempoolLimit = (nUTXOLimit != 0) ? nUTXOLimit : (overwinterActive ? 0 : (size_t)GetArg("-mempooltxinputlimit", 0)); |
6e9c7629 | 4471 | |
487c9020 | 4472 | unsigned int max_tx_size = saplingActive ? MAX_TX_SIZE_AFTER_SAPLING : MAX_TX_SIZE_BEFORE_SAPLING; |
6e9c7629 | 4473 | size_t estimatedTxSize = 200; // tx overhead + wiggle room |
487c9020 | 4474 | if (isToSproutZaddr) { |
6e9c7629 | 4475 | estimatedTxSize += JOINSPLIT_SIZE; |
487c9020 EOW |
4476 | } else if (isToSaplingZaddr) { |
4477 | estimatedTxSize += OUTPUTDESCRIPTION_SIZE; | |
6e9c7629 JG |
4478 | } |
4479 | ||
487c9020 | 4480 | if (useAnyUTXO || taddrs.size() > 0) { |
6e9c7629 JG |
4481 | // Get available utxos |
4482 | vector<COutput> vecOutputs; | |
4483 | pwalletMain->AvailableCoins(vecOutputs, true, NULL, false, false); | |
4484 | ||
4485 | // Find unspent utxos and update estimated size | |
4486 | for (const COutput& out : vecOutputs) { | |
4487 | if (!out.fSpendable) { | |
4488 | continue; | |
4489 | } | |
4490 | ||
487c9020 EOW |
4491 | CScript scriptPubKey = out.tx->vout[out.i].scriptPubKey; |
4492 | ||
6e9c7629 | 4493 | CTxDestination address; |
487c9020 | 4494 | if (!ExtractDestination(scriptPubKey, address)) { |
6e9c7629 JG |
4495 | continue; |
4496 | } | |
4497 | // If taddr is not wildcard "*", filter utxos | |
4498 | if (taddrs.size() > 0 && !taddrs.count(address)) { | |
4499 | continue; | |
4500 | } | |
4501 | ||
4502 | utxoCounter++; | |
4503 | CAmount nValue = out.tx->vout[out.i].nValue; | |
4504 | ||
4505 | if (!maxedOutUTXOsFlag) { | |
b6be3e88 | 4506 | size_t increase = (boost::get<CScriptID>(&address) != nullptr) ? CTXIN_SPEND_P2SH_SIZE : CTXIN_SPEND_DUST_SIZE; |
15ec5525 | 4507 | if (estimatedTxSize + increase >= max_tx_size || |
6e9c7629 JG |
4508 | (mempoolLimit > 0 && utxoCounter > mempoolLimit)) |
4509 | { | |
4510 | maxedOutUTXOsFlag = true; | |
4511 | } else { | |
4512 | estimatedTxSize += increase; | |
4513 | COutPoint utxo(out.tx->GetHash(), out.i); | |
487c9020 | 4514 | utxoInputs.emplace_back(utxo, nValue, scriptPubKey); |
6e9c7629 JG |
4515 | mergedUTXOValue += nValue; |
4516 | } | |
4517 | } | |
4518 | ||
4519 | if (maxedOutUTXOsFlag) { | |
4520 | remainingUTXOValue += nValue; | |
4521 | } | |
4522 | } | |
4523 | } | |
4524 | ||
487c9020 | 4525 | if (useAnySprout || useAnySapling || zaddrs.size() > 0) { |
6e9c7629 | 4526 | // Get available notes |
a630f503 | 4527 | std::vector<SproutNoteEntry> sproutEntries; |
94e99acd JG |
4528 | std::vector<SaplingNoteEntry> saplingEntries; |
4529 | pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, zaddrs); | |
6e9c7629 | 4530 | |
487c9020 EOW |
4531 | // If Sapling is not active, do not allow sending from a sapling addresses. |
4532 | if (!saplingActive && saplingEntries.size() > 0) { | |
4533 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, Sapling has not activated"); | |
4534 | } | |
a8055cfe E |
4535 | // Do not include Sprout/Sapling notes if using "ANY_SAPLING"/"ANY_SPROUT" respectively |
4536 | if (useAnySprout) { | |
4537 | saplingEntries.clear(); | |
4538 | } | |
4539 | if (useAnySapling) { | |
4540 | sproutEntries.clear(); | |
4541 | } | |
12040363 | 4542 | // Sending from both Sprout and Sapling is currently unsupported using z_mergetoaddress |
a8055cfe | 4543 | if ((sproutEntries.size() > 0 && saplingEntries.size() > 0) || (useAnySprout && useAnySapling)) { |
487c9020 EOW |
4544 | throw JSONRPCError( |
4545 | RPC_INVALID_PARAMETER, | |
4546 | "Cannot send from both Sprout and Sapling addresses using z_mergetoaddress"); | |
12040363 EOW |
4547 | } |
4548 | // If sending between shielded addresses, they must be the same type | |
4549 | if ((saplingEntries.size() > 0 && isToSproutZaddr) || (sproutEntries.size() > 0 && isToSaplingZaddr)) { | |
487c9020 EOW |
4550 | throw JSONRPCError( |
4551 | RPC_INVALID_PARAMETER, | |
12040363 | 4552 | "Cannot send between Sprout and Sapling addresses using z_mergetoaddress"); |
487c9020 EOW |
4553 | } |
4554 | ||
6e9c7629 | 4555 | // Find unspent notes and update estimated size |
a630f503 | 4556 | for (const SproutNoteEntry& entry : sproutEntries) { |
6e9c7629 | 4557 | noteCounter++; |
a630f503 | 4558 | CAmount nValue = entry.note.value(); |
6e9c7629 JG |
4559 | |
4560 | if (!maxedOutNotesFlag) { | |
4561 | // If we haven't added any notes yet and the merge is to a | |
4562 | // z-address, we have already accounted for the first JoinSplit. | |
487c9020 | 4563 | size_t increase = (sproutNoteInputs.empty() && !isToSproutZaddr) || (sproutNoteInputs.size() % 2 == 0) ? JOINSPLIT_SIZE : 0; |
15ec5525 | 4564 | if (estimatedTxSize + increase >= max_tx_size || |
487c9020 | 4565 | (sproutNoteLimit > 0 && noteCounter > sproutNoteLimit)) |
6e9c7629 JG |
4566 | { |
4567 | maxedOutNotesFlag = true; | |
4568 | } else { | |
4569 | estimatedTxSize += increase; | |
94e99acd | 4570 | auto zaddr = entry.address; |
e5eab182 | 4571 | SproutSpendingKey zkey; |
25d5e80c | 4572 | pwalletMain->GetSproutSpendingKey(zaddr, zkey); |
a630f503 | 4573 | sproutNoteInputs.emplace_back(entry.jsop, entry.note, nValue, zkey); |
487c9020 EOW |
4574 | mergedNoteValue += nValue; |
4575 | } | |
4576 | } | |
4577 | ||
4578 | if (maxedOutNotesFlag) { | |
4579 | remainingNoteValue += nValue; | |
4580 | } | |
4581 | } | |
4582 | ||
4583 | for (const SaplingNoteEntry& entry : saplingEntries) { | |
4584 | noteCounter++; | |
4585 | CAmount nValue = entry.note.value(); | |
4586 | if (!maxedOutNotesFlag) { | |
4587 | size_t increase = SPENDDESCRIPTION_SIZE; | |
4588 | if (estimatedTxSize + increase >= max_tx_size || | |
4589 | (saplingNoteLimit > 0 && noteCounter > saplingNoteLimit)) | |
4590 | { | |
4591 | maxedOutNotesFlag = true; | |
4592 | } else { | |
4593 | estimatedTxSize += increase; | |
4594 | libzcash::SaplingExtendedSpendingKey extsk; | |
4595 | if (!pwalletMain->GetSaplingExtendedSpendingKey(entry.address, extsk)) { | |
4596 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Could not find spending key for payment address."); | |
4597 | } | |
4598 | saplingNoteInputs.emplace_back(entry.op, entry.note, nValue, extsk.expsk); | |
6e9c7629 JG |
4599 | mergedNoteValue += nValue; |
4600 | } | |
4601 | } | |
4602 | ||
4603 | if (maxedOutNotesFlag) { | |
4604 | remainingNoteValue += nValue; | |
4605 | } | |
4606 | } | |
4607 | } | |
4608 | ||
4609 | size_t numUtxos = utxoInputs.size(); | |
487c9020 | 4610 | size_t numNotes = sproutNoteInputs.size() + saplingNoteInputs.size(); |
6e9c7629 JG |
4611 | |
4612 | if (numUtxos == 0 && numNotes == 0) { | |
4613 | throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Could not find any funds to merge."); | |
4614 | } | |
4615 | ||
4616 | // Sanity check: Don't do anything if: | |
4617 | // - We only have one from address | |
4618 | // - It's equal to toaddress | |
4619 | // - The address only contains a single UTXO or note | |
4620 | if (setAddress.size() == 1 && setAddress.count(destaddress) && (numUtxos + numNotes) == 1) { | |
4621 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Destination address is also the only source address, and all its funds are already merged."); | |
4622 | } | |
4623 | ||
4624 | CAmount mergedValue = mergedUTXOValue + mergedNoteValue; | |
4625 | if (mergedValue < nFee) { | |
4626 | throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, | |
4627 | strprintf("Insufficient funds, have %s, which is less than miners fee %s", | |
4628 | FormatMoney(mergedValue), FormatMoney(nFee))); | |
4629 | } | |
4630 | ||
4631 | // Check that the user specified fee is sane (if too high, it can result in error -25 absurd fee) | |
4632 | CAmount netAmount = mergedValue - nFee; | |
4633 | if (nFee > netAmount) { | |
4634 | throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Fee %s is greater than the net amount to be shielded %s", FormatMoney(nFee), FormatMoney(netAmount))); | |
4635 | } | |
4636 | ||
4637 | // Keep record of parameters in context object | |
4638 | UniValue contextInfo(UniValue::VOBJ); | |
4639 | contextInfo.push_back(Pair("fromaddresses", params[0])); | |
4640 | contextInfo.push_back(Pair("toaddress", params[1])); | |
4641 | contextInfo.push_back(Pair("fee", ValueFromAmount(nFee))); | |
4642 | ||
4643 | // Contextual transaction we will build on | |
4644 | CMutableTransaction contextualTx = CreateNewContextualCMutableTransaction( | |
4645 | Params().GetConsensus(), | |
7b92f27e | 4646 | nextBlockHeight); |
487c9020 EOW |
4647 | bool isSproutShielded = sproutNoteInputs.size() > 0 || isToSproutZaddr; |
4648 | if (contextualTx.nVersion == 1 && isSproutShielded) { | |
6e9c7629 JG |
4649 | contextualTx.nVersion = 2; // Tx format should support vjoinsplit |
4650 | } | |
4651 | ||
487c9020 EOW |
4652 | // Builder (used if Sapling addresses are involved) |
4653 | boost::optional<TransactionBuilder> builder; | |
4654 | if (isToSaplingZaddr || saplingNoteInputs.size() > 0) { | |
5eb7129d | 4655 | builder = TransactionBuilder(Params().GetConsensus(), nextBlockHeight, expiryDelta, pwalletMain); |
487c9020 | 4656 | } |
6e9c7629 JG |
4657 | // Create operation and add to global queue |
4658 | std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue(); | |
4659 | std::shared_ptr<AsyncRPCOperation> operation( | |
487c9020 | 4660 | new AsyncRPCOperation_mergetoaddress(builder, contextualTx, utxoInputs, sproutNoteInputs, saplingNoteInputs, recipient, nFee, contextInfo) ); |
6e9c7629 JG |
4661 | q->addOperation(operation); |
4662 | AsyncRPCOperationId operationId = operation->getId(); | |
4663 | ||
4664 | // Return continuation information | |
4665 | UniValue o(UniValue::VOBJ); | |
4ff92bb6 | 4666 | o.push_back(Pair("remainingUTXOs", static_cast<uint64_t>(utxoCounter - numUtxos))); |
6e9c7629 | 4667 | o.push_back(Pair("remainingTransparentValue", ValueFromAmount(remainingUTXOValue))); |
4ff92bb6 | 4668 | o.push_back(Pair("remainingNotes", static_cast<uint64_t>(noteCounter - numNotes))); |
6e9c7629 | 4669 | o.push_back(Pair("remainingShieldedValue", ValueFromAmount(remainingNoteValue))); |
4ff92bb6 | 4670 | o.push_back(Pair("mergingUTXOs", static_cast<uint64_t>(numUtxos))); |
6e9c7629 | 4671 | o.push_back(Pair("mergingTransparentValue", ValueFromAmount(mergedUTXOValue))); |
4ff92bb6 | 4672 | o.push_back(Pair("mergingNotes", static_cast<uint64_t>(numNotes))); |
6e9c7629 JG |
4673 | o.push_back(Pair("mergingShieldedValue", ValueFromAmount(mergedNoteValue))); |
4674 | o.push_back(Pair("opid", operationId)); | |
4675 | return o; | |
4676 | } | |
4677 | ||
4678 | ||
0d37ae3a | 4679 | UniValue z_listoperationids(const UniValue& params, bool fHelp) |
34f0001c S |
4680 | { |
4681 | if (!EnsureWalletIsAvailable(fHelp)) | |
0d37ae3a | 4682 | return NullUniValue; |
34f0001c S |
4683 | |
4684 | if (fHelp || params.size() > 1) | |
4685 | throw runtime_error( | |
4686 | "z_listoperationids\n" | |
4687 | "\nReturns the list of operation ids currently known to the wallet.\n" | |
4688 | "\nArguments:\n" | |
c938fb1f | 4689 | "1. \"status\" (string, optional) Filter result by the operation's state e.g. \"success\".\n" |
34f0001c S |
4690 | "\nResult:\n" |
4691 | "[ (json array of string)\n" | |
4692 | " \"operationid\" (string) an operation id belonging to the wallet\n" | |
4693 | " ,...\n" | |
4694 | "]\n" | |
4695 | "\nExamples:\n" | |
4696 | + HelpExampleCli("z_listoperationids", "") | |
4697 | + HelpExampleRpc("z_listoperationids", "") | |
4698 | ); | |
4699 | ||
4700 | LOCK2(cs_main, pwalletMain->cs_wallet); | |
4701 | ||
4702 | std::string filter; | |
4703 | bool useFilter = false; | |
4704 | if (params.size()==1) { | |
4705 | filter = params[0].get_str(); | |
4706 | useFilter = true; | |
4707 | } | |
4708 | ||
0d37ae3a | 4709 | UniValue ret(UniValue::VARR); |
34f0001c S |
4710 | std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue(); |
4711 | std::vector<AsyncRPCOperationId> ids = q->getAllOperationIds(); | |
4712 | for (auto id : ids) { | |
4713 | std::shared_ptr<AsyncRPCOperation> operation = q->getOperationForId(id); | |
4714 | if (!operation) { | |
4715 | continue; | |
4716 | } | |
4717 | std::string state = operation->getStateAsString(); | |
4718 | if (useFilter && filter.compare(state)!=0) | |
4719 | continue; | |
4720 | ret.push_back(id); | |
4721 | } | |
4722 | ||
4723 | return ret; | |
4724 | } | |
34aca1b0 JS |
4725 | |
4726 | extern UniValue dumpprivkey(const UniValue& params, bool fHelp); // in rpcdump.cpp | |
4727 | extern UniValue importprivkey(const UniValue& params, bool fHelp); | |
4728 | extern UniValue importaddress(const UniValue& params, bool fHelp); | |
4729 | extern UniValue dumpwallet(const UniValue& params, bool fHelp); | |
4730 | extern UniValue importwallet(const UniValue& params, bool fHelp); | |
4731 | extern UniValue z_exportkey(const UniValue& params, bool fHelp); | |
4732 | extern UniValue z_importkey(const UniValue& params, bool fHelp); | |
4733 | extern UniValue z_exportviewingkey(const UniValue& params, bool fHelp); | |
4734 | extern UniValue z_importviewingkey(const UniValue& params, bool fHelp); | |
4735 | extern UniValue z_exportwallet(const UniValue& params, bool fHelp); | |
4736 | extern UniValue z_importwallet(const UniValue& params, bool fHelp); | |
4737 | ||
4738 | extern UniValue z_getpaymentdisclosure(const UniValue& params, bool fHelp); // in rpcdisclosure.cpp | |
4739 | extern UniValue z_validatepaymentdisclosure(const UniValue ¶ms, bool fHelp); | |
4740 | ||
a9496b08 | 4741 | static const CRPCCommand commands[] = |
34aca1b0 JS |
4742 | { // category name actor (function) okSafeMode |
4743 | // --------------------- ------------------------ ----------------------- ---------- | |
4744 | { "rawtransactions", "fundrawtransaction", &fundrawtransaction, false }, | |
4745 | { "hidden", "resendwallettransactions", &resendwallettransactions, true }, | |
4746 | { "wallet", "addmultisigaddress", &addmultisigaddress, true }, | |
4747 | { "wallet", "backupwallet", &backupwallet, true }, | |
4748 | { "wallet", "dumpprivkey", &dumpprivkey, true }, | |
4749 | { "wallet", "dumpwallet", &dumpwallet, true }, | |
4750 | { "wallet", "encryptwallet", &encryptwallet, true }, | |
4751 | { "wallet", "getaccountaddress", &getaccountaddress, true }, | |
4752 | { "wallet", "getaccount", &getaccount, true }, | |
4753 | { "wallet", "getaddressesbyaccount", &getaddressesbyaccount, true }, | |
4754 | { "wallet", "getbalance", &getbalance, false }, | |
4755 | { "wallet", "getnewaddress", &getnewaddress, true }, | |
4756 | { "wallet", "getrawchangeaddress", &getrawchangeaddress, true }, | |
4757 | { "wallet", "getreceivedbyaccount", &getreceivedbyaccount, false }, | |
4758 | { "wallet", "getreceivedbyaddress", &getreceivedbyaddress, false }, | |
4759 | { "wallet", "gettransaction", &gettransaction, false }, | |
4760 | { "wallet", "getunconfirmedbalance", &getunconfirmedbalance, false }, | |
4761 | { "wallet", "getwalletinfo", &getwalletinfo, false }, | |
4762 | { "wallet", "importprivkey", &importprivkey, true }, | |
4763 | { "wallet", "importwallet", &importwallet, true }, | |
4764 | { "wallet", "importaddress", &importaddress, true }, | |
4765 | { "wallet", "keypoolrefill", &keypoolrefill, true }, | |
4766 | { "wallet", "listaccounts", &listaccounts, false }, | |
4767 | { "wallet", "listaddressgroupings", &listaddressgroupings, false }, | |
4768 | { "wallet", "listlockunspent", &listlockunspent, false }, | |
4769 | { "wallet", "listreceivedbyaccount", &listreceivedbyaccount, false }, | |
4770 | { "wallet", "listreceivedbyaddress", &listreceivedbyaddress, false }, | |
4771 | { "wallet", "listsinceblock", &listsinceblock, false }, | |
4772 | { "wallet", "listtransactions", &listtransactions, false }, | |
4773 | { "wallet", "listunspent", &listunspent, false }, | |
4774 | { "wallet", "lockunspent", &lockunspent, true }, | |
4775 | { "wallet", "move", &movecmd, false }, | |
4776 | { "wallet", "sendfrom", &sendfrom, false }, | |
4777 | { "wallet", "sendmany", &sendmany, false }, | |
4778 | { "wallet", "sendtoaddress", &sendtoaddress, false }, | |
4779 | { "wallet", "setaccount", &setaccount, true }, | |
4780 | { "wallet", "settxfee", &settxfee, true }, | |
4781 | { "wallet", "signmessage", &signmessage, true }, | |
4782 | { "wallet", "walletlock", &walletlock, true }, | |
4783 | { "wallet", "walletpassphrasechange", &walletpassphrasechange, true }, | |
4784 | { "wallet", "walletpassphrase", &walletpassphrase, true }, | |
4785 | { "wallet", "zcbenchmark", &zc_benchmark, true }, | |
4786 | { "wallet", "zcrawkeygen", &zc_raw_keygen, true }, | |
4787 | { "wallet", "zcrawjoinsplit", &zc_raw_joinsplit, true }, | |
4788 | { "wallet", "zcrawreceive", &zc_raw_receive, true }, | |
4789 | { "wallet", "zcsamplejoinsplit", &zc_sample_joinsplit, true }, | |
4790 | { "wallet", "z_listreceivedbyaddress", &z_listreceivedbyaddress, false }, | |
4791 | { "wallet", "z_listunspent", &z_listunspent, false }, | |
4792 | { "wallet", "z_getbalance", &z_getbalance, false }, | |
4793 | { "wallet", "z_gettotalbalance", &z_gettotalbalance, false }, | |
4794 | { "wallet", "z_mergetoaddress", &z_mergetoaddress, false }, | |
4795 | { "wallet", "z_sendmany", &z_sendmany, false }, | |
81a45d69 | 4796 | { "wallet", "z_setmigration", &z_setmigration, false }, |
6e82d728 | 4797 | { "wallet", "z_getmigrationstatus", &z_getmigrationstatus, false }, |
34aca1b0 JS |
4798 | { "wallet", "z_shieldcoinbase", &z_shieldcoinbase, false }, |
4799 | { "wallet", "z_getoperationstatus", &z_getoperationstatus, true }, | |
4800 | { "wallet", "z_getoperationresult", &z_getoperationresult, true }, | |
4801 | { "wallet", "z_listoperationids", &z_listoperationids, true }, | |
4802 | { "wallet", "z_getnewaddress", &z_getnewaddress, true }, | |
4803 | { "wallet", "z_listaddresses", &z_listaddresses, true }, | |
4804 | { "wallet", "z_exportkey", &z_exportkey, true }, | |
4805 | { "wallet", "z_importkey", &z_importkey, true }, | |
4806 | { "wallet", "z_exportviewingkey", &z_exportviewingkey, true }, | |
4807 | { "wallet", "z_importviewingkey", &z_importviewingkey, true }, | |
4808 | { "wallet", "z_exportwallet", &z_exportwallet, true }, | |
4809 | { "wallet", "z_importwallet", &z_importwallet, true }, | |
4810 | // TODO: rearrange into another category | |
4811 | { "disclosure", "z_getpaymentdisclosure", &z_getpaymentdisclosure, true }, | |
4812 | { "disclosure", "z_validatepaymentdisclosure", &z_validatepaymentdisclosure, true } | |
4813 | }; | |
4814 | ||
a9496b08 | 4815 | void RegisterWalletRPCCommands(CRPCTable &tableRPC) |
34aca1b0 | 4816 | { |
a9496b08 WL |
4817 | for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) |
4818 | tableRPC.appendCommand(commands[vcidx].name, &commands[vcidx]); | |
34aca1b0 | 4819 | } |