|
|
@ -53,6 +53,7 @@ |
|
|
|
#include <stdint.h> |
|
|
|
|
|
|
|
#include <boost/assign/list_of.hpp> |
|
|
|
#include <utf8.h> |
|
|
|
|
|
|
|
#include <univalue.h> |
|
|
|
|
|
|
@ -342,7 +343,7 @@ UniValue setaccount(const UniValue& params, bool fHelp) |
|
|
|
|
|
|
|
CTxDestination dest = DecodeDestination(params[0].get_str()); |
|
|
|
if (!IsValidDestination(dest)) { |
|
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Zcash address"); |
|
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Hush address!"); |
|
|
|
} |
|
|
|
|
|
|
|
string strAccount; |
|
|
@ -389,7 +390,7 @@ UniValue getaccount(const UniValue& params, bool fHelp) |
|
|
|
|
|
|
|
CTxDestination dest = DecodeDestination(params[0].get_str()); |
|
|
|
if (!IsValidDestination(dest)) { |
|
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Zcash address"); |
|
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Hush address!"); |
|
|
|
} |
|
|
|
|
|
|
|
std::string strAccount; |
|
|
@ -449,7 +450,7 @@ static void SendMoney(const CTxDestination &address, CAmount nValue, bool fSubtr |
|
|
|
if (nValue > curBalance) |
|
|
|
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds"); |
|
|
|
|
|
|
|
// Parse Zcash address
|
|
|
|
// Parse Hush address
|
|
|
|
CScript scriptPubKey = GetScriptForDestination(address); |
|
|
|
|
|
|
|
// Create and send the transaction
|
|
|
@ -523,7 +524,7 @@ UniValue sendtoaddress(const UniValue& params, bool fHelp) |
|
|
|
|
|
|
|
CTxDestination dest = DecodeDestination(params[0].get_str()); |
|
|
|
if (!IsValidDestination(dest)) { |
|
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Zcash address"); |
|
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Hush address!"); |
|
|
|
} |
|
|
|
|
|
|
|
// Amount
|
|
|
@ -935,7 +936,7 @@ UniValue getreceivedbyaddress(const UniValue& params, bool fHelp) |
|
|
|
// Bitcoin address
|
|
|
|
CTxDestination dest = DecodeDestination(params[0].get_str()); |
|
|
|
if (!IsValidDestination(dest)) { |
|
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Zcash address"); |
|
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Hush address!"); |
|
|
|
} |
|
|
|
CScript scriptPubKey = GetScriptForDestination(dest); |
|
|
|
if (!IsMine(*pwalletMain, scriptPubKey)) { |
|
|
@ -1389,7 +1390,7 @@ UniValue sendfrom(const UniValue& params, bool fHelp) |
|
|
|
std::string strAccount = AccountFromValue(params[0]); |
|
|
|
CTxDestination dest = DecodeDestination(params[1].get_str()); |
|
|
|
if (!IsValidDestination(dest)) { |
|
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Zcash address"); |
|
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Hush address!"); |
|
|
|
} |
|
|
|
CAmount nAmount = AmountFromValue(params[2]); |
|
|
|
if (nAmount <= 0) |
|
|
@ -1486,7 +1487,7 @@ UniValue sendmany(const UniValue& params, bool fHelp) |
|
|
|
for (const std::string& name_ : keys) { |
|
|
|
CTxDestination dest = DecodeDestination(name_); |
|
|
|
if (!IsValidDestination(dest)) { |
|
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Zcash address: ") + name_); |
|
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Hush address: ") + name_); |
|
|
|
} |
|
|
|
|
|
|
|
CScript scriptPubKey = GetScriptForDestination(dest); |
|
|
@ -2859,7 +2860,7 @@ UniValue listunspent(const UniValue& params, bool fHelp) |
|
|
|
" \"txid\" : \"txid\", (string) the transaction id \n" |
|
|
|
" \"vout\" : n, (numeric) the vout value\n" |
|
|
|
" \"generated\" : true|false (boolean) true if txout is a coinbase transaction output\n" |
|
|
|
" \"address\" : \"address\", (string) the Zcash address\n" |
|
|
|
" \"address\" : \"address\", (string) the Hush address\n" |
|
|
|
" \"account\" : \"account\", (string) DEPRECATED. The associated account, or \"\" for the default account\n" |
|
|
|
" \"scriptPubKey\" : \"key\", (string) the script key\n" |
|
|
|
" \"amount\" : x.xxx, (numeric) the transaction amount in " + CURRENCY_UNIT + "\n" |
|
|
@ -2893,7 +2894,7 @@ UniValue listunspent(const UniValue& params, bool fHelp) |
|
|
|
const UniValue& input = inputs[idx]; |
|
|
|
CTxDestination dest = DecodeDestination(input.get_str()); |
|
|
|
if (!IsValidDestination(dest)) { |
|
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Zcash address: ") + input.get_str()); |
|
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Hush address: ") + input.get_str()); |
|
|
|
} |
|
|
|
if (!destinations.insert(dest).second) { |
|
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated address: ") + input.get_str()); |
|
|
@ -3017,13 +3018,12 @@ UniValue z_listunspent(const UniValue& params, bool fHelp) |
|
|
|
"Optionally filter to only include notes sent to specified addresses.\n" |
|
|
|
"When minconf is 0, unspent notes with zero confirmations are returned, even though they are not immediately spendable.\n" |
|
|
|
"Results are an array of Objects, each of which has:\n" |
|
|
|
"{txid, jsindex, jsoutindex, confirmations, address, amount, memo} (Sprout)\n" |
|
|
|
"{txid, outindex, confirmations, address, amount, memo} (Sapling)\n" |
|
|
|
"\nArguments:\n" |
|
|
|
"1. minconf (numeric, optional, default=1) The minimum confirmations to filter\n" |
|
|
|
"2. maxconf (numeric, optional, default=9999999) The maximum confirmations to filter\n" |
|
|
|
"3. includeWatchonly (bool, optional, default=false) Also include watchonly addresses (see 'z_importviewingkey')\n" |
|
|
|
"4. \"addresses\" (string) A json array of zaddrs (both Sprout and Sapling) to filter on. Duplicate addresses not allowed.\n" |
|
|
|
"4. \"addresses\" (string) A json array of zaddrs to filter on. Duplicate addresses not allowed.\n" |
|
|
|
" [\n" |
|
|
|
" \"address\" (string) zaddr\n" |
|
|
|
" ,...\n" |
|
|
@ -3033,7 +3033,6 @@ UniValue z_listunspent(const UniValue& params, bool fHelp) |
|
|
|
" {\n" |
|
|
|
" \"txid\" : \"txid\", (string) the transaction id \n" |
|
|
|
" \"jsindex\" : n (numeric) the joinsplit index\n" |
|
|
|
" \"jsoutindex\" (sprout) : n (numeric) the output index of the joinsplit\n" |
|
|
|
" \"outindex\" (sapling) : n (numeric) the output index\n" |
|
|
|
" \"confirmations\" : n (numeric) the number of confirmations\n" |
|
|
|
" \"spendable\" : true|false (boolean) true if note can be spent by wallet, false if note has zero confirmations, false if address is watchonly\n" |
|
|
@ -3095,7 +3094,7 @@ UniValue z_listunspent(const UniValue& params, bool fHelp) |
|
|
|
string address = o.get_str(); |
|
|
|
auto zaddr = DecodePaymentAddress(address); |
|
|
|
if (!IsValidPaymentAddress(zaddr)) { |
|
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, address is not a valid zaddr: ") + address); |
|
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, address is not a valid Hush zaddr: ") + address); |
|
|
|
} |
|
|
|
auto hasSpendingKey = boost::apply_visitor(HaveSpendingKeyForPaymentAddress(pwalletMain), zaddr); |
|
|
|
if (!fIncludeWatchonly && !hasSpendingKey) { |
|
|
@ -3111,14 +3110,9 @@ UniValue z_listunspent(const UniValue& params, bool fHelp) |
|
|
|
} |
|
|
|
else { |
|
|
|
// User did not provide zaddrs, so use default i.e. all addresses
|
|
|
|
std::set<libzcash::SproutPaymentAddress> sproutzaddrs = {}; |
|
|
|
pwalletMain->GetSproutPaymentAddresses(sproutzaddrs); |
|
|
|
|
|
|
|
// Sapling support
|
|
|
|
std::set<libzcash::SaplingPaymentAddress> saplingzaddrs = {}; |
|
|
|
pwalletMain->GetSaplingPaymentAddresses(saplingzaddrs); |
|
|
|
|
|
|
|
zaddrs.insert(sproutzaddrs.begin(), sproutzaddrs.end()); |
|
|
|
zaddrs.insert(saplingzaddrs.begin(), saplingzaddrs.end()); |
|
|
|
} |
|
|
|
|
|
|
@ -3130,32 +3124,6 @@ UniValue z_listunspent(const UniValue& params, bool fHelp) |
|
|
|
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, zaddrs, nMinDepth, nMaxDepth, true, !fIncludeWatchonly, false); |
|
|
|
std::set<std::pair<PaymentAddress, uint256>> nullifierSet = pwalletMain->GetNullifiersForAddresses(zaddrs); |
|
|
|
|
|
|
|
for (auto & entry : sproutEntries) { |
|
|
|
UniValue obj(UniValue::VOBJ); |
|
|
|
|
|
|
|
int nHeight = tx_height(entry.jsop.hash); |
|
|
|
int dpowconfs = komodo_dpowconfs(nHeight, entry.confirmations); |
|
|
|
// Only return notarized results when minconf>1
|
|
|
|
if (nMinDepth > 1 && dpowconfs == 1) |
|
|
|
continue; |
|
|
|
|
|
|
|
obj.push_back(Pair("txid", entry.jsop.hash.ToString())); |
|
|
|
obj.push_back(Pair("jsindex", (int)entry.jsop.js )); |
|
|
|
obj.push_back(Pair("jsoutindex", (int)entry.jsop.n)); |
|
|
|
obj.push_back(Pair("confirmations", dpowconfs)); |
|
|
|
obj.push_back(Pair("rawconfirmations", entry.confirmations)); |
|
|
|
bool hasSproutSpendingKey = pwalletMain->HaveSproutSpendingKey(boost::get<libzcash::SproutPaymentAddress>(entry.address)); |
|
|
|
obj.push_back(Pair("spendable", hasSproutSpendingKey)); |
|
|
|
obj.push_back(Pair("address", EncodePaymentAddress(entry.address))); |
|
|
|
obj.push_back(Pair("amount", ValueFromAmount(CAmount(entry.plaintext.value())))); |
|
|
|
std::string data(entry.plaintext.memo().begin(), entry.plaintext.memo().end()); |
|
|
|
obj.push_back(Pair("memo", HexStr(data))); |
|
|
|
if (hasSproutSpendingKey) { |
|
|
|
obj.push_back(Pair("change", pwalletMain->IsNoteSproutChange(nullifierSet, entry.address, entry.jsop))); |
|
|
|
} |
|
|
|
results.push_back(obj); |
|
|
|
} |
|
|
|
|
|
|
|
for (auto & entry : saplingEntries) { |
|
|
|
UniValue obj(UniValue::VOBJ); |
|
|
|
|
|
|
@ -4121,6 +4089,158 @@ UniValue z_gettotalbalance(const UniValue& params, bool fHelp) |
|
|
|
return result; |
|
|
|
} |
|
|
|
|
|
|
|
UniValue z_viewtransaction(const UniValue& params, bool fHelp) |
|
|
|
{ |
|
|
|
if (!EnsureWalletIsAvailable(fHelp)) |
|
|
|
return NullUniValue; |
|
|
|
|
|
|
|
if (fHelp || params.size() != 1) |
|
|
|
throw runtime_error( |
|
|
|
"z_viewtransaction \"txid\"\n" |
|
|
|
"\nGet detailed shielded information about in-wallet transaction <txid>\n" |
|
|
|
"\nArguments:\n" |
|
|
|
"1. \"txid\" (string, required) The transaction id\n" |
|
|
|
"\nResult:\n" |
|
|
|
"{\n" |
|
|
|
" \"txid\" : \"transactionid\", (string) The transaction id\n" |
|
|
|
" \"spends\" : [\n" |
|
|
|
" {\n" |
|
|
|
" \"type\" : \"sapling\", (string) The type of address\n" |
|
|
|
" \"spend\" : n, (numeric) the index of the spend within vShieldedSpend\n" |
|
|
|
" \"txidPrev\" : \"transactionid\", (string) The id for the transaction this note was created in\n" |
|
|
|
" \"outputPrev\" : n, (numeric) the index of the output within the vShieldedOutput\n" |
|
|
|
" \"address\" : \"zcashaddress\", (string) The Hush shielded address involved in the transaction\n" |
|
|
|
" \"value\" : x.xxx (numeric) The amount in " + CURRENCY_UNIT + "\n" |
|
|
|
" \"valueZat\" : xxxx (numeric) The amount in zatoshis\n" |
|
|
|
" }\n" |
|
|
|
" ,...\n" |
|
|
|
" ],\n" |
|
|
|
" \"outputs\" : [\n" |
|
|
|
" {\n" |
|
|
|
" \"type\" : \"sapling\", (string) The type of address\n" |
|
|
|
" \"output\" : n, (numeric) the index of the output within the vShieldedOutput\n" |
|
|
|
" \"address\" : \"hushaddress\", (string) The Hush address involved in the transaction\n" |
|
|
|
" \"recovered\" : true|false (boolean) True if the output is not for an address in the wallet\n" |
|
|
|
" \"value\" : x.xxx (numeric) The amount in " + CURRENCY_UNIT + "\n" |
|
|
|
" \"valueZat\" : xxxx (numeric) The amount in zatoshis\n" |
|
|
|
" \"memo\" : \"hexmemo\", (string) Hexademical string representation of the memo field\n" |
|
|
|
" \"memoStr\" : \"memo\", (string) Only returned if memo contains valid UTF-8 text.\n" |
|
|
|
" }\n" |
|
|
|
" ,...\n" |
|
|
|
" ],\n" |
|
|
|
"}\n" |
|
|
|
|
|
|
|
"\nExamples:\n" |
|
|
|
+ HelpExampleCli("z_viewtransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") |
|
|
|
+ HelpExampleCli("z_viewtransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\" true") |
|
|
|
+ HelpExampleRpc("z_viewtransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") |
|
|
|
); |
|
|
|
|
|
|
|
LOCK2(cs_main, pwalletMain->cs_wallet); |
|
|
|
|
|
|
|
uint256 hash; |
|
|
|
hash.SetHex(params[0].get_str()); |
|
|
|
|
|
|
|
UniValue entry(UniValue::VOBJ); |
|
|
|
|
|
|
|
if (!pwalletMain->mapWallet.count(hash)) |
|
|
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet Hush transaction id!"); |
|
|
|
const CWalletTx& wtx = pwalletMain->mapWallet[hash]; |
|
|
|
|
|
|
|
entry.push_back(Pair("txid", hash.GetHex())); |
|
|
|
|
|
|
|
UniValue spends(UniValue::VARR); |
|
|
|
UniValue outputs(UniValue::VARR); |
|
|
|
char str[64]; |
|
|
|
|
|
|
|
// Sapling spends
|
|
|
|
std::set<uint256> ovks; |
|
|
|
for (size_t i = 0; i < wtx.vShieldedSpend.size(); ++i) { |
|
|
|
auto spend = wtx.vShieldedSpend[i]; |
|
|
|
|
|
|
|
// Fetch the note that is being spent
|
|
|
|
auto res = pwalletMain->mapSaplingNullifiersToNotes.find(spend.nullifier); |
|
|
|
if (res == pwalletMain->mapSaplingNullifiersToNotes.end()) { |
|
|
|
fprintf(stderr,"Could not find spending note %s", uint256_str(str, spend.nullifier)); |
|
|
|
continue; |
|
|
|
} |
|
|
|
auto op = res->second; |
|
|
|
auto wtxPrev = pwalletMain->mapWallet.at(op.hash); |
|
|
|
auto decrypted = wtxPrev.DecryptSaplingNote(op).get(); |
|
|
|
auto notePt = decrypted.first; |
|
|
|
auto pa = decrypted.second; |
|
|
|
|
|
|
|
// Store the OutgoingViewingKey for recovering outputs
|
|
|
|
libzcash::SaplingFullViewingKey fvk; |
|
|
|
assert(pwalletMain->GetSaplingFullViewingKey(wtxPrev.mapSaplingNoteData.at(op).ivk, fvk)); |
|
|
|
ovks.insert(fvk.ovk); |
|
|
|
|
|
|
|
UniValue entry(UniValue::VOBJ); |
|
|
|
entry.push_back(Pair("type", ADDR_TYPE_SAPLING)); |
|
|
|
entry.push_back(Pair("spend", (int)i)); |
|
|
|
entry.push_back(Pair("nullifier", uint256_str(str,spend.nullifier))); |
|
|
|
entry.push_back(Pair("anchor", uint256_str(str,spend.anchor))); |
|
|
|
entry.push_back(Pair("commitment", uint256_str(str,spend.cv))); |
|
|
|
entry.push_back(Pair("rk", uint256_str(str,spend.rk))); |
|
|
|
entry.push_back(Pair("txidPrev", op.hash.GetHex())); |
|
|
|
entry.push_back(Pair("outputPrev", (int)op.n)); |
|
|
|
entry.push_back(Pair("address", EncodePaymentAddress(pa))); |
|
|
|
entry.push_back(Pair("value", ValueFromAmount(notePt.value()))); |
|
|
|
entry.push_back(Pair("valueZat", notePt.value())); |
|
|
|
spends.push_back(entry); |
|
|
|
} |
|
|
|
|
|
|
|
// Sapling outputs
|
|
|
|
for (uint32_t i = 0; i < wtx.vShieldedOutput.size(); ++i) { |
|
|
|
auto op = SaplingOutPoint(hash, i); |
|
|
|
|
|
|
|
SaplingNotePlaintext notePt; |
|
|
|
SaplingPaymentAddress pa; |
|
|
|
bool isRecovered; |
|
|
|
|
|
|
|
auto decrypted = wtx.DecryptSaplingNote(op); |
|
|
|
if (decrypted) { |
|
|
|
notePt = decrypted->first; |
|
|
|
pa = decrypted->second; |
|
|
|
isRecovered = false; |
|
|
|
} else { |
|
|
|
// Try recovering the output
|
|
|
|
auto recovered = wtx.RecoverSaplingNote(op, ovks); |
|
|
|
if (recovered) { |
|
|
|
notePt = recovered->first; |
|
|
|
pa = recovered->second; |
|
|
|
isRecovered = true; |
|
|
|
} else { |
|
|
|
// Unreadable
|
|
|
|
continue; |
|
|
|
} |
|
|
|
} |
|
|
|
auto memo = notePt.memo(); |
|
|
|
|
|
|
|
UniValue entry(UniValue::VOBJ); |
|
|
|
entry.push_back(Pair("type", ADDR_TYPE_SAPLING)); |
|
|
|
entry.push_back(Pair("output", (int)op.n)); |
|
|
|
entry.push_back(Pair("recovered", isRecovered)); |
|
|
|
entry.push_back(Pair("address", EncodePaymentAddress(pa))); |
|
|
|
entry.push_back(Pair("value", ValueFromAmount(notePt.value()))); |
|
|
|
entry.push_back(Pair("valueZat", notePt.value())); |
|
|
|
entry.push_back(Pair("memo", HexStr(memo))); |
|
|
|
if (memo[0] <= 0xf4) { |
|
|
|
auto end = std::find_if(memo.rbegin(), memo.rend(), [](unsigned char v) { return v != 0; }); |
|
|
|
std::string memoStr(memo.begin(), end.base()); |
|
|
|
if (utf8::is_valid(memoStr)) { |
|
|
|
entry.push_back(Pair("memoStr", memoStr)); |
|
|
|
} |
|
|
|
} |
|
|
|
outputs.push_back(entry); |
|
|
|
} |
|
|
|
|
|
|
|
entry.push_back(Pair("spends", spends)); |
|
|
|
entry.push_back(Pair("outputs", outputs)); |
|
|
|
|
|
|
|
return entry; |
|
|
|
} |
|
|
|
|
|
|
|
UniValue z_getoperationresult(const UniValue& params, bool fHelp) |
|
|
|
{ |
|
|
|
if (!EnsureWalletIsAvailable(fHelp)) |
|
|
@ -8287,6 +8407,7 @@ static const CRPCCommand commands[] = |
|
|
|
{ "wallet", "z_importviewingkey", &z_importviewingkey, true }, |
|
|
|
{ "wallet", "z_exportwallet", &z_exportwallet, true }, |
|
|
|
{ "wallet", "z_importwallet", &z_importwallet, true }, |
|
|
|
{ "wallet", "z_viewtransaction", &z_viewtransaction, true }, |
|
|
|
// TODO: rearrange into another category
|
|
|
|
{ "disclosure", "z_getpaymentdisclosure", &z_getpaymentdisclosure, true }, |
|
|
|
{ "disclosure", "z_validatepaymentdisclosure", &z_validatepaymentdisclosure, true } |
|
|
|