diff --git a/src/wallet/asyncrpcoperation_common.cpp b/src/wallet/asyncrpcoperation_common.cpp index 7d7ba7fbf..512561d6f 100644 --- a/src/wallet/asyncrpcoperation_common.cpp +++ b/src/wallet/asyncrpcoperation_common.cpp @@ -8,7 +8,7 @@ extern UniValue signrawtransaction(const UniValue& params, bool fHelp); // TODO: instead of passing a TestMode flag, tests should override `CommitTransaction` // on the wallet. -UniValue SendTransaction(CTransaction& tx, std::optional> reservekey, bool testmode) { +UniValue SendTransaction(const CTransaction& tx, const std::vector& recipients, std::optional> reservekey, bool testmode) { UniValue o(UniValue::VOBJ); // Send the transaction if (!testmode) { @@ -55,7 +55,7 @@ std::pair SignSendRawTransaction(UniValue obj, std::opti CTransaction tx; stream >> tx; - UniValue sendResult = SendTransaction(tx, reservekey, testmode); + UniValue sendResult = SendTransaction(tx, {}, reservekey, testmode); return std::make_pair(tx, sendResult); } diff --git a/src/wallet/asyncrpcoperation_common.h b/src/wallet/asyncrpcoperation_common.h index 8b9f1959b..eee8e0d43 100644 --- a/src/wallet/asyncrpcoperation_common.h +++ b/src/wallet/asyncrpcoperation_common.h @@ -20,7 +20,11 @@ * If testmode is true, do not commit the transaction, * return {"test": 1, "txid": tx.GetHash().ToString(), "hex": EncodeHexTx(tx)} */ -UniValue SendTransaction(CTransaction& tx, std::optional> reservekey, bool testmode); +UniValue SendTransaction( + const CTransaction& tx, + const std::vector& recipients, + std::optional> reservekey, + bool testmode); /** * Sign and send a raw transaction. diff --git a/src/wallet/asyncrpcoperation_mergetoaddress.cpp b/src/wallet/asyncrpcoperation_mergetoaddress.cpp index 405528760..67e2fc2a6 100644 --- a/src/wallet/asyncrpcoperation_mergetoaddress.cpp +++ b/src/wallet/asyncrpcoperation_mergetoaddress.cpp @@ -364,7 +364,7 @@ bool AsyncRPCOperation_mergetoaddress::main_impl() // Build the transaction tx_ = builder_.Build().GetTxOrThrow(); - UniValue sendResult = SendTransaction(tx_, std::nullopt, testmode); + UniValue sendResult = SendTransaction(tx_, {}, std::nullopt, testmode); set_result(sendResult); return true; diff --git a/src/wallet/asyncrpcoperation_sendmany.cpp b/src/wallet/asyncrpcoperation_sendmany.cpp index a2c21a9fb..7ab968854 100644 --- a/src/wallet/asyncrpcoperation_sendmany.cpp +++ b/src/wallet/asyncrpcoperation_sendmany.cpp @@ -461,7 +461,7 @@ uint256 AsyncRPCOperation_sendmany::main_impl() { auto buildResult = builder_.Build(); auto tx = buildResult.GetTxOrThrow(); - UniValue sendResult = SendTransaction(tx, std::nullopt, testmode); + UniValue sendResult = SendTransaction(tx, recipients_, std::nullopt, testmode); set_result(sendResult); return tx.GetHash(); diff --git a/src/wallet/asyncrpcoperation_shieldcoinbase.cpp b/src/wallet/asyncrpcoperation_shieldcoinbase.cpp index 2fb0fac85..09c7da674 100644 --- a/src/wallet/asyncrpcoperation_shieldcoinbase.cpp +++ b/src/wallet/asyncrpcoperation_shieldcoinbase.cpp @@ -273,7 +273,7 @@ bool ShieldToAddress::operator()(const libzcash::SaplingPaymentAddress &zaddr) c // Build the transaction m_op->tx_ = m_op->builder_.Build().GetTxOrThrow(); - UniValue sendResult = SendTransaction(m_op->tx_, std::nullopt, m_op->testmode); + UniValue sendResult = SendTransaction(m_op->tx_, {}, std::nullopt, m_op->testmode); m_op->set_result(sendResult); return true; @@ -283,7 +283,34 @@ bool ShieldToAddress::operator()(const libzcash::UnifiedAddress &uaddr) const { // TODO check if an Orchard address is present, send to it if so. const auto receiver{uaddr.GetSaplingReceiver()}; if (receiver.has_value()) { - return ShieldToAddress(m_op, sendAmount)(receiver.value()); + m_op->builder_.SetFee(m_op->fee_); + + // Sending from a t-address, which we don't have an ovk for. Instead, + // generate a common one from the HD seed. This ensures the data is + // recoverable, while keeping it logically separate from the ZIP 32 + // Sapling key hierarchy, which the user might not be using. + // FIXME: update to use the ZIP-316 OVK + HDSeed seed = pwalletMain->GetHDSeedForRPC(); + uint256 ovk = ovkForShieldingFromTaddr(seed); + + // Add transparent inputs + CAmount total; + for (auto t : m_op->inputs_) { + m_op->builder_.AddTransparentInput(COutPoint(t.txid, t.vout), t.scriptPubKey, t.amount); + total += t.amount; + } + + // Send all value to the target z-addr + m_op->builder_.SendChangeTo(receiver.value(), ovk); + + // Build the transaction + m_op->tx_ = m_op->builder_.Build().GetTxOrThrow(); + + SendManyRecipient r(uaddr, receiver.value(), total, std::nullopt); + UniValue sendResult = SendTransaction(m_op->tx_, {r}, std::nullopt, m_op->testmode); + m_op->set_result(sendResult); + + return true; } // This UA must contain a transparent address, which can't be the destination of coinbase shielding. return false;