Browse Source

desprout

danger
Duke Leto 4 years ago
parent
commit
22d4d1a06e
  1. 21
      src/wallet/asyncrpcoperation_mergetoaddress.cpp
  2. 4
      src/wallet/asyncrpcoperation_mergetoaddress.h
  3. 3
      src/wallet/asyncrpcoperation_saplingconsolidation.cpp
  4. 18
      src/wallet/asyncrpcoperation_sendmany.cpp
  5. 125
      src/wallet/rpcwallet.cpp

21
src/wallet/asyncrpcoperation_mergetoaddress.cpp

@ -74,19 +74,17 @@ AsyncRPCOperation_mergetoaddress::AsyncRPCOperation_mergetoaddress(
boost::optional<TransactionBuilder> builder,
CMutableTransaction contextualTx,
std::vector<MergeToAddressInputUTXO> utxoInputs,
std::vector<MergeToAddressInputSproutNote> sproutNoteInputs,
std::vector<MergeToAddressInputSaplingNote> saplingNoteInputs,
MergeToAddressRecipient recipient,
CAmount fee,
UniValue contextInfo) :
tx_(contextualTx), utxoInputs_(utxoInputs), sproutNoteInputs_(sproutNoteInputs),
saplingNoteInputs_(saplingNoteInputs), recipient_(recipient), fee_(fee), contextinfo_(contextInfo)
tx_(contextualTx), utxoInputs_(utxoInputs), saplingNoteInputs_(saplingNoteInputs), recipient_(recipient), fee_(fee), contextinfo_(contextInfo)
{
if (fee < 0 || fee > MAX_MONEY) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Fee is out of range");
}
if (utxoInputs.empty() && sproutNoteInputs.empty() && saplingNoteInputs.empty()) {
if (utxoInputs.empty() && saplingNoteInputs.empty()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "No inputs");
}
@ -94,14 +92,6 @@ saplingNoteInputs_(saplingNoteInputs), recipient_(recipient), fee_(fee), context
throw JSONRPCError(RPC_INVALID_PARAMETER, "Recipient parameter missing");
}
if (sproutNoteInputs.size() > 0 && saplingNoteInputs.size() > 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot send from both Sprout and Sapling addresses using z_mergetoaddress");
}
if (sproutNoteInputs.size() > 0 && builder) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Sprout notes are not supported by the TransactionBuilder");
}
isUsingBuilder_ = false;
if (builder) {
isUsingBuilder_ = true;
@ -215,7 +205,7 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
{
assert(isToTaddr_ != isToZaddr_);
bool isPureTaddrOnlyTx = (sproutNoteInputs_.empty() && saplingNoteInputs_.empty() && isToTaddr_);
bool isPureTaddrOnlyTx = (saplingNoteInputs_.empty() && isToTaddr_);
CAmount minersFee = fee_;
size_t numInputs = utxoInputs_.size();
@ -240,9 +230,6 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
}
CAmount z_inputs_total = 0;
for (const MergeToAddressInputSproutNote& t : sproutNoteInputs_) {
z_inputs_total += std::get<2>(t);
}
for (const MergeToAddressInputSaplingNote& t : saplingNoteInputs_) {
z_inputs_total += std::get<2>(t);
@ -293,7 +280,7 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
/**
* SCENARIO #0
*
* Sprout not involved, so we just use the TransactionBuilder and we're done.
* Only sapling involved, so we just use the TransactionBuilder and we're done.
*
* This is based on code from AsyncRPCOperation_sendmany::main_impl() and should be refactored.
*/

4
src/wallet/asyncrpcoperation_mergetoaddress.h

@ -57,7 +57,6 @@ public:
boost::optional<TransactionBuilder> builder,
CMutableTransaction contextualTx,
std::vector<MergeToAddressInputUTXO> utxoInputs,
std::vector<MergeToAddressInputSproutNote> sproutNoteInputs,
std::vector<MergeToAddressInputSaplingNote> saplingNoteInputs,
MergeToAddressRecipient recipient,
CAmount fee = MERGE_TO_ADDRESS_OPERATION_DEFAULT_MINERS_FEE,
@ -94,9 +93,6 @@ private:
uint256 joinSplitPubKey_;
unsigned char joinSplitPrivKey_[crypto_sign_SECRETKEYBYTES];
// The key is the result string from calling JSOutPoint::ToString()
std::unordered_map<std::string, MergeToAddressWitnessAnchorData> jsopWitnessAnchorMap;
std::vector<MergeToAddressInputUTXO> utxoInputs_;
std::vector<MergeToAddressInputSaplingNote> saplingNoteInputs_;

3
src/wallet/asyncrpcoperation_saplingconsolidation.cpp

@ -87,7 +87,6 @@ bool AsyncRPCOperation_saplingconsolidation::main_impl() {
return status;
}
std::vector<CSproutNotePlaintextEntry> sproutEntries;
std::vector<SaplingNoteEntry> saplingEntries;
std::set<libzcash::SaplingPaymentAddress> addresses;
{
@ -95,7 +94,7 @@ bool AsyncRPCOperation_saplingconsolidation::main_impl() {
// We set minDepth to 11 to avoid unconfirmed notes and in anticipation of specifying
// an anchor at height N-10 for each SpendDescription
// Consider, should notes be sorted?
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, "", 11);
pwalletMain->GetFilteredNotes(saplingEntries, "", 11);
if (fConsolidationMapUsed) {
const vector<string>& v = mapMultiArgs["-consolidatesaplingaddress"];
for(int i = 0; i < v.size(); i++) {

18
src/wallet/asyncrpcoperation_sendmany.cpp

@ -248,18 +248,12 @@ bool AsyncRPCOperation_sendmany::main_impl() {
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds, no unspent notes found for zaddr from address.");
}
// At least one of z_sprout_inputs_ and z_sapling_inputs_ must be empty by design
assert(z_sprout_inputs_.empty() || z_sapling_inputs_.empty());
CAmount t_inputs_total = 0;
for (SendManyInputUTXO & t : t_inputs_) {
t_inputs_total += std::get<2>(t);
}
CAmount z_inputs_total = 0;
for (SendManyInputJSOP & t : z_sprout_inputs_) {
z_inputs_total += std::get<2>(t);
}
for (auto t : z_sapling_inputs_) {
z_inputs_total += t.note.value();
}
@ -687,20 +681,10 @@ bool AsyncRPCOperation_sendmany::find_utxos(bool fAcceptCoinbase=false) {
bool AsyncRPCOperation_sendmany::find_unspent_notes() {
std::vector<CSproutNotePlaintextEntry> sproutEntries;
std::vector<SaplingNoteEntry> saplingEntries;
{
LOCK2(cs_main, pwalletMain->cs_wallet);
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, fromaddress_, mindepth_);
}
// If using the TransactionBuilder, we only want Sapling notes.
// If not using it, we only want Sprout notes.
// TODO: Refactor `GetFilteredNotes()` so we only fetch what we need.
if (isUsingBuilder_) {
sproutEntries.clear();
} else {
saplingEntries.clear();
pwalletMain->GetFilteredNotes(saplingEntries, fromaddress_, mindepth_);
}
for (auto entry : saplingEntries) {

125
src/wallet/rpcwallet.cpp

@ -70,7 +70,6 @@ using namespace libzcash;
extern char ASSETCHAINS_SYMBOL[KOMODO_ASSETCHAIN_MAXLEN];
extern std::string ASSETCHAINS_OVERRIDE_PUBKEY;
const std::string ADDR_TYPE_SPROUT = "sprout";
const std::string ADDR_TYPE_SAPLING = "sapling";
extern UniValue TxJoinSplitToJSON(const CTransaction& tx);
extern int32_t KOMODO_INSYNC;
@ -3787,9 +3786,8 @@ UniValue z_listunspent(const UniValue& params, bool fHelp, const CPubKey& mypk)
UniValue results(UniValue::VARR);
if (zaddrs.size() > 0) {
std::vector<CSproutNotePlaintextEntry> sproutEntries;
std::vector<SaplingNoteEntry> saplingEntries;
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, zaddrs, nMinDepth, nMaxDepth, true, !fIncludeWatchonly, false);
pwalletMain->GetFilteredNotes(saplingEntries, zaddrs, nMinDepth, nMaxDepth, true, !fIncludeWatchonly, false);
std::set<std::pair<PaymentAddress, uint256>> nullifierSet = pwalletMain->GetNullifiersForAddresses(zaddrs);
for (auto & entry : saplingEntries) {
@ -4047,10 +4045,9 @@ CAmount getBalanceTaddr(std::string transparentAddress, int minDepth=1, bool ign
CAmount getBalanceZaddr(std::string address, int minDepth = 1, bool ignoreUnspendable=true) {
CAmount balance = 0;
std::vector<CSproutNotePlaintextEntry> sproutEntries;
std::vector<SaplingNoteEntry> saplingEntries;
LOCK2(cs_main, pwalletMain->cs_wallet);
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, address, minDepth, true, ignoreUnspendable);
pwalletMain->GetFilteredNotes(saplingEntries, address, minDepth, true, ignoreUnspendable);
for (auto & entry : saplingEntries) {
balance += CAmount(entry.note.value());
}
@ -4102,16 +4099,14 @@ UniValue z_listreceivedbyaddress(const UniValue& params, bool fHelp, const CPubK
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid zaddr.");
}
// Visitor to support Sprout and Sapling addrs
if (!boost::apply_visitor(PaymentAddressBelongsToWallet(pwalletMain), zaddr) &&
!boost::apply_visitor(IncomingViewingKeyBelongsToWallet(pwalletMain), zaddr)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "From address does not belong to this node, zaddr spending key or viewing key not found.");
}
UniValue result(UniValue::VARR);
std::vector<CSproutNotePlaintextEntry> sproutEntries;
std::vector<SaplingNoteEntry> saplingEntries;
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, fromaddress, nMinDepth, false, false);
pwalletMain->GetFilteredNotes(saplingEntries, fromaddress, nMinDepth, false, false);
std::set<std::pair<PaymentAddress, uint256>> nullifierSet;
auto hasSpendingKey = boost::apply_visitor(HaveSpendingKeyForPaymentAddress(pwalletMain), zaddr);
@ -4223,7 +4218,7 @@ UniValue z_getnotescount(const UniValue& params, bool fHelp,const CPubKey& mypk)
"z_getnotescount\n"
"\nArguments:\n"
"1. minconf (numeric, optional, default=1) Only include notes in transactions confirmed at least this many times.\n"
"\nReturns the number of sprout and sapling notes available in the wallet.\n"
"\nReturns the number of sapling notes available in the wallet.\n"
"\nResult:\n"
"{\n"
" \"sapling\" (numeric) the number of sapling notes in the wallet\n"
@ -4268,7 +4263,7 @@ UniValue z_gettotalbalance(const UniValue& params, bool fHelp, const CPubKey& my
"\nResult:\n"
"{\n"
" \"transparent\": xxxxx, (numeric) the total balance of transparent funds\n"
" \"private\": xxxxx, (numeric) the total balance of private funds (in both Sprout and Sapling addresses)\n"
" \"private\": xxxxx, (numeric) the total balance of private funds\n"
" \"total\": xxxxx, (numeric) the total balance of both transparent and private funds\n"
"}\n"
"\nExamples:\n"
@ -4650,14 +4645,9 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk)
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "From address does not belong to this node, zaddr spending key not found.");
}
// Remember whether this is a Sprout or Sapling address
// Remember whether this is a Sapling address
fromSapling = boost::get<libzcash::SaplingPaymentAddress>(&res) != nullptr;
}
// This logic will need to be updated if we add a new shielded pool
bool fromSprout = !(fromTaddr || fromSapling);
if (fromSprout)
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot send from a Sprout zaddr, only Sapling zaddrs supported.");
UniValue outputs = params[1].get_array();
@ -4667,15 +4657,11 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk)
// Keep track of addresses to spot duplicates
set<std::string> setAddress;
// Track whether we see any Sprout addresses
bool noSproutAddrs = !fromSprout;
// Recipients
std::vector<SendManyRecipient> taddrRecipients;
std::vector<SendManyRecipient> zaddrRecipients;
CAmount nTotalOut = 0;
bool containsSproutOutput = false;
bool containsSaplingOutput = false;
for (const UniValue& o : outputs.getValues()) {
@ -4696,35 +4682,6 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk)
auto res = DecodePaymentAddress(address);
if (IsValidPaymentAddress(res, branchId)) {
isZaddr = true;
bool toSapling = boost::get<libzcash::SaplingPaymentAddress>(&res) != nullptr;
bool toSprout = !toSapling;
noSproutAddrs = noSproutAddrs && toSapling;
containsSproutOutput |= toSprout;
containsSaplingOutput |= toSapling;
// Sending to both Sprout and Sapling is currently unsupported using z_sendmany
if (containsSproutOutput && containsSaplingOutput) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
"Cannot send to both Sprout and Sapling addresses using z_sendmany");
}
if ( GetTime() > KOMODO_SAPLING_DEADLINE )
{
if ( fromSprout || toSprout )
throw JSONRPCError(RPC_INVALID_PARAMETER,"Sprout usage has expired");
}
if ( toSapling && ASSETCHAINS_SYMBOL[0] == 0 )
throw JSONRPCError(RPC_INVALID_PARAMETER,"Sprout usage will expire soon");
// If we are sending from a shielded address, all recipient
// shielded addresses must be of the same type.
if ((fromSprout && toSapling) || (fromSapling && toSprout)) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
"Cannot send between Sprout and Sapling addresses using z_sendmany");
}
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown address format: ")+address );
}
@ -4915,9 +4872,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk)
// Builder (used if Sapling addresses are involved)
boost::optional<TransactionBuilder> builder;
if (noSproutAddrs) {
builder = TransactionBuilder(Params().GetConsensus(), nextBlockHeight, pwalletMain);
}
builder = TransactionBuilder(Params().GetConsensus(), nextBlockHeight, pwalletMain);
// Contextual transaction we will build on
// (used if no Sapling addresses are involved)
@ -5190,11 +5145,11 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp, const CPubKey& myp
return NullUniValue;
string enableArg = "zmergetoaddress";
auto fEnableMergeToAddress = fExperimentalMode && GetBoolArg("-" + enableArg, true);
std::string strDisabledMsg = "";
if (!fEnableMergeToAddress) {
strDisabledMsg = experimentalDisabledHelpMsg("z_mergetoaddress", enableArg);
}
//auto fEnableMergeToAddress = fExperimentalMode && GetBoolArg("-" + enableArg, true);
//std::string strDisabledMsg = "";
//if (!fEnableMergeToAddress) {
// strDisabledMsg = experimentalDisabledHelpMsg("z_mergetoaddress", enableArg);
//}
if (fHelp || params.size() < 2 || params.size() > 7)
throw runtime_error(
@ -5256,7 +5211,6 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp, const CPubKey& myp
THROW_IF_SYNCING(KOMODO_INSYNC);
bool useAnyUTXO = false;
bool useAnySprout = false;
bool useAnySapling = false;
std::set<CTxDestination> taddrs = {};
std::set<libzcash::PaymentAddress> zaddrs = {};
@ -5277,8 +5231,6 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp, const CPubKey& myp
if (address == "ANY_TADDR") {
useAnyUTXO = true;
} else if (address == "ANY_SPROUT") {
useAnySprout = true;
} else if (address == "ANY_SAPLING") {
useAnySapling = true;
} else {
@ -5303,8 +5255,8 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp, const CPubKey& myp
if (useAnyUTXO && taddrs.size() > 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify specific t-addrs when using \"ANY_TADDR\"");
}
if ((useAnySprout || useAnySapling) && zaddrs.size() > 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify specific z-addrs when using \"ANY_SPROUT\" or \"ANY_SAPLING\"");
if ((useAnySapling) && zaddrs.size() > 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify specific z-addrs when using \"ANY_SAPLING\"");
}
const int nextBlockHeight = chainActive.Height() + 1;
@ -5313,7 +5265,6 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp, const CPubKey& myp
// Validate the destination address
auto destaddress = params[1].get_str();
bool isToSproutZaddr = false;
bool isToSaplingZaddr = false;
CTxDestination taddr = DecodeDestination(destaddress);
if (!IsValidDestination(taddr)) {
@ -5326,7 +5277,7 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp, const CPubKey& myp
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, Sapling has not activated");
}
} else {
isToSproutZaddr = true;
throw JSONRPCError(RPC_INVALID_PARAMETER, "Only Sapling zaddrs allowed!");
}
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown address format: ") + destaddress );
@ -5351,14 +5302,12 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp, const CPubKey& myp
}
}
int sproutNoteLimit = MERGE_TO_ADDRESS_DEFAULT_SPROUT_LIMIT;
int saplingNoteLimit = MERGE_TO_ADDRESS_DEFAULT_SAPLING_LIMIT;
if (params.size() > 4) {
int nNoteLimit = params[4].get_int();
if (nNoteLimit < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Limit on maximum number of notes cannot be negative");
}
sproutNoteLimit = nNoteLimit;
saplingNoteLimit = nNoteLimit;
}
@ -5375,7 +5324,7 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp, const CPubKey& myp
std::string memo;
if (params.size() > 6) {
memo = params[6].get_str();
if (!(isToSproutZaddr || isToSaplingZaddr)) {
if (!isToSaplingZaddr) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Memo can not be used with a taddr. It can only be used with a zaddr.");
} else if (!IsHex(memo)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected memo data in hexadecimal format.");
@ -5389,7 +5338,6 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp, const CPubKey& myp
// Prepare to get UTXOs and notes
std::vector<MergeToAddressInputUTXO> utxoInputs;
std::vector<MergeToAddressInputSproutNote> sproutNoteInputs;
std::vector<MergeToAddressInputSaplingNote> saplingNoteInputs;
CAmount mergedUTXOValue = 0;
CAmount mergedNoteValue = 0;
@ -5403,9 +5351,8 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp, const CPubKey& myp
unsigned int max_tx_size = saplingActive ? MAX_TX_SIZE_AFTER_SAPLING : MAX_TX_SIZE_BEFORE_SAPLING;
size_t estimatedTxSize = 200; // tx overhead + wiggle room
if (isToSproutZaddr) {
estimatedTxSize += JOINSPLIT_SIZE;
} else if (isToSaplingZaddr) {
if (isToSaplingZaddr) {
estimatedTxSize += OUTPUTDESCRIPTION_SIZE;
}
@ -5463,29 +5410,10 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp, const CPubKey& myp
}
}
if (useAnySprout || useAnySapling || zaddrs.size() > 0) {
if (useAnySapling || zaddrs.size() > 0) {
// Get available notes
std::vector<CSproutNotePlaintextEntry> sproutEntries;
//std::vector<SaplingNoteEntry> saplingEntries;
//pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, zaddrs);
std::vector<SaplingNoteEntry> saplingEntries,skipsapling;
pwalletMain->GetFilteredNotes(sproutEntries, useAnySprout == 0 ? saplingEntries : skipsapling, zaddrs);
// If Sapling is not active, do not allow sending from a sapling addresses.
if (!saplingActive && saplingEntries.size() > 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, Sapling has not activated");
}
// Sending from both Sprout and Sapling is currently unsupported using z_mergetoaddress
if (sproutEntries.size() > 0 && saplingEntries.size() > 0) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
"Cannot send from both Sprout and Sapling addresses using z_mergetoaddress");
}
// If sending between shielded addresses, they must be the same type
if ((saplingEntries.size() > 0 && isToSproutZaddr) || (sproutEntries.size() > 0 && isToSaplingZaddr)) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
"Cannot send between Sprout and Sapling addresses using z_mergetoaddress");
}
std::vector<SaplingNoteEntry> saplingEntries;
pwalletMain->GetFilteredNotes(saplingEntries, zaddrs);
for (const SaplingNoteEntry& entry : saplingEntries) {
noteCounter++;
@ -5514,7 +5442,7 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp, const CPubKey& myp
}
size_t numUtxos = utxoInputs.size();
size_t numNotes = sproutNoteInputs.size() + saplingNoteInputs.size();
size_t numNotes = saplingNoteInputs.size();
//fprintf(stderr, "num utxos.%li\n", numUtxos);
if (numUtxos < 2 && numNotes == 0) {
@ -5552,22 +5480,19 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp, const CPubKey& myp
CMutableTransaction contextualTx = CreateNewContextualCMutableTransaction(
Params().GetConsensus(),
nextBlockHeight);
bool isSproutShielded = sproutNoteInputs.size() > 0 || isToSproutZaddr;
if (contextualTx.nVersion == 1 && isSproutShielded) {
contextualTx.nVersion = 2; // Tx format should support vjoinsplit
}
// Builder (used if Sapling addresses are involved)
boost::optional<TransactionBuilder> builder;
if (isToSaplingZaddr || saplingNoteInputs.size() > 0) {
builder = TransactionBuilder(Params().GetConsensus(), nextBlockHeight, pwalletMain);
} else
} else {
contextualTx.nExpiryHeight = 0; // set non z-tx to have no expiry height.
}
// Create operation and add to global queue
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();
std::shared_ptr<AsyncRPCOperation> operation(
new AsyncRPCOperation_mergetoaddress(builder, contextualTx, utxoInputs, sproutNoteInputs, saplingNoteInputs, recipient, nFee, contextInfo) );
new AsyncRPCOperation_mergetoaddress(builder, contextualTx, utxoInputs, saplingNoteInputs, recipient, nFee, contextInfo) );
q->addOperation(operation);
AsyncRPCOperationId operationId = operation->getId();

Loading…
Cancel
Save