Inside Bitcoin
Opening the selected file…
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1// Copyright (c) 2011-present The Bitcoin Core developers2// Distributed under the MIT software license, see the accompanying3// file COPYING or http://www.opensource.org/licenses/mit-license.php.5#include <common/messages.h>6#include <consensus/validation.h>7#include <core_io.h>8#include <key_io.h>9#include <node/types.h>10#include <policy/policy.h>11#include <policy/truc_policy.h>12#include <rpc/rawtransaction_util.h>13#include <rpc/util.h>14#include <script/script.h>15#include <util/rbf.h>16#include <util/translation.h>17#include <util/vector.h>18#include <wallet/coincontrol.h>19#include <wallet/feebumper.h>20#include <wallet/fees.h>21#include <wallet/rpc/util.h>22#include <wallet/spend.h>23#include <wallet/wallet.h>25#include <univalue.h>27using common::FeeModeFromString;28using common::FeeModesDetail;29using common::InvalidEstimateModeErrorMessage;30using common::StringForFeeReason;31using common::TransactionErrorString;32using node::TransactionError;34namespace wallet {35std::vector<CRecipient> CreateRecipients(const std::vector<std::pair<CTxDestination, CAmount>>& outputs, const std::set<int>& subtract_fee_outputs)36{37std::vector<CRecipient> recipients;38for (size_t i = 0; i < outputs.size(); ++i) {39const auto& [destination, amount] = outputs.at(i);40CRecipient recipient{destination, amount, subtract_fee_outputs.contains(i)};41recipients.push_back(recipient);42}43return recipients;44}46static void InterpretFeeEstimationInstructions(const UniValue& conf_target, const UniValue& estimate_mode, const UniValue& fee_rate, UniValue& options)47{48if (options.exists("conf_target") || options.exists("estimate_mode")) {49if (!conf_target.isNull() || !estimate_mode.isNull()) {50throw JSONRPCError(RPC_INVALID_PARAMETER, "Pass conf_target and estimate_mode either as arguments or in the options object, but not both");51}52} else {53options.pushKV("conf_target", conf_target);54options.pushKV("estimate_mode", estimate_mode);55}56if (options.exists("fee_rate")) {57if (!fee_rate.isNull()) {58throw JSONRPCError(RPC_INVALID_PARAMETER, "Pass the fee_rate either as an argument, or in the options object, but not both");59}60} else {61options.pushKV("fee_rate", fee_rate);62}63if (!options["conf_target"].isNull() && (options["estimate_mode"].isNull() || (options["estimate_mode"].get_str() == "unset"))) {64throw JSONRPCError(RPC_INVALID_PARAMETER, "Specify estimate_mode");65}66}68std::set<int> InterpretSubtractFeeFromOutputInstructions(const UniValue& sffo_instructions, const std::vector<std::string>& destinations)69{70std::set<int> sffo_set;71if (sffo_instructions.isNull()) return sffo_set;73for (const auto& sffo : sffo_instructions.getValues()) {74int pos{-1};75if (sffo.isStr()) {76auto it = find(destinations.begin(), destinations.end(), sffo.get_str());77if (it == destinations.end()) throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter 'subtract fee from output', destination %s not found in tx outputs", sffo.get_str()));78pos = it - destinations.begin();79} else if (sffo.isNum()) {80pos = sffo.getInt<int>();81} else {82throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter 'subtract fee from output', invalid value type: %s", uvTypeName(sffo.type())));83}85if (sffo_set.contains(pos))86throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter 'subtract fee from output', duplicated position: %d", pos));87if (pos < 0)88throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter 'subtract fee from output', negative position: %d", pos));89if (pos >= int(destinations.size()))90throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter 'subtract fee from output', position too large: %d", pos));91sffo_set.insert(pos);92}93return sffo_set;94}96static UniValue FinishTransaction(const std::shared_ptr<CWallet> pwallet, const UniValue& options, CMutableTransaction& rawTx)97{98bool can_anti_fee_snipe = !options.exists("locktime");100for (const CTxIn& tx_in : rawTx.vin) {101// Checks sequence values consistent with DiscourageFeeSniping102can_anti_fee_snipe = can_anti_fee_snipe && (tx_in.nSequence == CTxIn::MAX_SEQUENCE_NONFINAL || tx_in.nSequence == MAX_BIP125_RBF_SEQUENCE);103}105if (can_anti_fee_snipe) {106LOCK(pwallet->cs_wallet);107FastRandomContext rng_fast;108DiscourageFeeSniping(rawTx, rng_fast, pwallet->chain(), pwallet->GetLastBlockHash(), pwallet->GetLastBlockHeight());109}111// Make a blank psbt112PartiallySignedTransaction psbtx(rawTx, /*version=*/2);114// First fill transaction with our data without signing,115// so external signers are not asked to sign more than once.116bool complete;117pwallet->FillPSBT(psbtx, {.sign = false, .bip32_derivs = true}, complete);118const auto err{pwallet->FillPSBT(psbtx, {.sign = true, .bip32_derivs = false}, complete)};119if (err) {120throw JSONRPCPSBTError(*err);121}123CMutableTransaction mtx;124complete = FinalizeAndExtractPSBT(psbtx, mtx);126UniValue result(UniValue::VOBJ);128const bool psbt_opt_in{options.exists("psbt") && options["psbt"].get_bool()};129bool add_to_wallet{options.exists("add_to_wallet") ? options["add_to_wallet"].get_bool() : true};130if (psbt_opt_in || !complete || !add_to_wallet) {131// Serialize the PSBT132DataStream ssTx{};133ssTx << psbtx;134result.pushKV("psbt", EncodeBase64(ssTx.str()));135}137if (complete) {138std::string hex{EncodeHexTx(CTransaction(mtx))};139CTransactionRef tx(MakeTransactionRef(std::move(mtx)));140result.pushKV("txid", tx->GetHash().GetHex());141if (add_to_wallet && !psbt_opt_in) {142pwallet->CommitTransaction(tx);143} else {144result.pushKV("hex", hex);145}146}147result.pushKV("complete", complete);149return result;150}152static void PreventOutdatedOptions(const UniValue& options)153{154if (options.exists("feeRate")) {155throw JSONRPCError(RPC_INVALID_PARAMETER, "Use fee_rate (" + CURRENCY_ATOM + "/vB) instead of feeRate");156}157if (options.exists("changeAddress")) {158throw JSONRPCError(RPC_INVALID_PARAMETER, "Use change_address instead of changeAddress");159}160if (options.exists("changePosition")) {161throw JSONRPCError(RPC_INVALID_PARAMETER, "Use change_position instead of changePosition");162}163if (options.exists("lockUnspents")) {164throw JSONRPCError(RPC_INVALID_PARAMETER, "Use lock_unspents instead of lockUnspents");165}166if (options.exists("subtractFeeFromOutputs")) {167throw JSONRPCError(RPC_INVALID_PARAMETER, "Use subtract_fee_from_outputs instead of subtractFeeFromOutputs");168}169}171UniValue SendMoney(CWallet& wallet, const CCoinControl &coin_control, std::vector<CRecipient> &recipients, std::optional<std::string> comment, std::optional<std::string> comment_to, bool verbose)172{173EnsureWalletIsUnlocked(wallet);175// This function is only used by sendtoaddress and sendmany.176// This should always try to sign, if we don't have (all) private keys, don't177// try to do anything here.178if (wallet.IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER)) {179throw JSONRPCError(RPC_WALLET_ERROR, "Error: sendtoaddress and sendmany are not supported for wallets with external signers; use send instead");180}181if (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {182throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet");183}185// Shuffle recipient list186std::shuffle(recipients.begin(), recipients.end(), FastRandomContext());188// Send189auto res = CreateTransaction(wallet, recipients, /*change_pos=*/std::nullopt, coin_control, true);190if (!res) {191throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, util::ErrorString(res).original);192}193const CTransactionRef& tx = res->tx;194wallet.CommitTransaction(tx, /*replaces_txid=*/std::nullopt, comment, comment_to);195if (verbose) {196UniValue entry(UniValue::VOBJ);197entry.pushKV("txid", tx->GetHash().GetHex());198entry.pushKV("fee_reason", StringForFeeReason(res->fee_reason));199return entry;200}201return tx->GetHash().GetHex();202}205/**206* Update coin control with fee estimation based on the given parameters207*208* @param[in] wallet Wallet reference209* @param[in,out] cc Coin control to be updated210* @param[in] conf_target UniValue integer; confirmation target in blocks, values between 1 and 1008 are valid per policy/fees/block_policy_estimator.h;211* @param[in] estimate_mode UniValue string; fee estimation mode, valid values are "unset", "economical" or "conservative";212* @param[in] fee_rate UniValue real; fee rate in sat/vB;213* if present, both conf_target and estimate_mode must either be null, or "unset"214* @param[in] override_min_fee bool; whether to set fOverrideFeeRate to true to disable minimum fee rate checks and instead215* verify only that fee_rate is greater than 0216* @throws a JSONRPCError if conf_target, estimate_mode, or fee_rate contain invalid values or are in conflict217*/218static void SetFeeEstimateMode(const CWallet& wallet, CCoinControl& cc, const UniValue& conf_target, const UniValue& estimate_mode, const UniValue& fee_rate, bool override_min_fee)219{220if (!fee_rate.isNull()) {221if (!conf_target.isNull()) {222throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both conf_target and fee_rate. Please provide either a confirmation target in blocks for automatic fee estimation, or an explicit fee rate.");223}224if (!estimate_mode.isNull() && estimate_mode.get_str() != "unset") {225throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both estimate_mode and fee_rate");226}227// Fee rates in sat/vB cannot represent more than 3 significant digits.228cc.m_feerate = CFeeRate{AmountFromValue(fee_rate, /*decimals=*/3)};229if (override_min_fee) cc.fOverrideFeeRate = true;230// Default RBF to true for explicit fee_rate, if unset.231if (!cc.m_signal_bip125_rbf) cc.m_signal_bip125_rbf = true;232return;233}234if (!estimate_mode.isNull() && !FeeModeFromString(estimate_mode.get_str(), cc.m_fee_mode)) {235throw JSONRPCError(RPC_INVALID_PARAMETER, InvalidEstimateModeErrorMessage());236}237if (!conf_target.isNull()) {238cc.m_confirm_target = ParseConfirmTarget(conf_target, wallet.chain().maximumFeeEstimationTargetBlocks());239}240}242RPCMethod sendtoaddress()243{244return RPCMethod{245"sendtoaddress",246"Send an amount to a given address." +247HELP_REQUIRING_PASSPHRASE,248{249{"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to send to."},250{"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The amount in " + CURRENCY_UNIT + " to send. eg 0.1"},