|
|
|
|
@@ -45,6 +45,8 @@ using interfaces::FoundBlock;
|
|
|
|
|
static const std::string WALLET_ENDPOINT_BASE = "/wallet/";
|
|
|
|
|
static const std::string HELP_REQUIRING_PASSPHRASE{"\nRequires wallet passphrase to be set with walletpassphrase call if wallet is encrypted.\n"};
|
|
|
|
|
|
|
|
|
|
static const uint32_t WALLET_BTC_KB_TO_SAT_B = COIN / 1000; // 1 sat / B = 0.00001 BTC / kB
|
|
|
|
|
|
|
|
|
|
static inline bool GetAvoidReuseFlag(const CWallet* const pwallet, const UniValue& param) {
|
|
|
|
|
bool can_avoid_reuse = pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE);
|
|
|
|
|
bool avoid_reuse = param.isNull() ? can_avoid_reuse : param.get_bool();
|
|
|
|
|
@@ -191,6 +193,42 @@ static std::string LabelFromValue(const UniValue& value)
|
|
|
|
|
return label;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Update coin control with fee estimation based on the given parameters
|
|
|
|
|
*
|
|
|
|
|
* @param[in] pwallet Wallet pointer
|
|
|
|
|
* @param[in,out] cc Coin control which is to be updated
|
|
|
|
|
* @param[in] estimate_mode String value (e.g. "ECONOMICAL")
|
|
|
|
|
* @param[in] estimate_param Parameter (blocks to confirm, explicit fee rate, etc)
|
|
|
|
|
* @throws a JSONRPCError if estimate_mode is unknown, or if estimate_param is missing when required
|
|
|
|
|
*/
|
|
|
|
|
static void SetFeeEstimateMode(const CWallet* pwallet, CCoinControl& cc, const UniValue& estimate_mode, const UniValue& estimate_param)
|
|
|
|
|
{
|
|
|
|
|
if (!estimate_mode.isNull()) {
|
|
|
|
|
if (!FeeModeFromString(estimate_mode.get_str(), cc.m_fee_mode)) {
|
|
|
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (cc.m_fee_mode == FeeEstimateMode::BTC_KB || cc.m_fee_mode == FeeEstimateMode::SAT_B) {
|
|
|
|
|
if (estimate_param.isNull()) {
|
|
|
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Selected estimate_mode requires a fee rate");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CAmount fee_rate = AmountFromValue(estimate_param);
|
|
|
|
|
if (cc.m_fee_mode == FeeEstimateMode::SAT_B) {
|
|
|
|
|
fee_rate /= WALLET_BTC_KB_TO_SAT_B;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cc.m_feerate = CFeeRate(fee_rate);
|
|
|
|
|
|
|
|
|
|
// default RBF to true for explicit fee rate modes
|
|
|
|
|
if (cc.m_signal_bip125_rbf == boost::none) cc.m_signal_bip125_rbf = true;
|
|
|
|
|
} else if (!estimate_param.isNull()) {
|
|
|
|
|
cc.m_confirm_target = ParseConfirmTarget(estimate_param, pwallet->chain().estimateMaxBlocks());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static UniValue getnewaddress(const JSONRPCRequest& request)
|
|
|
|
|
{
|
|
|
|
|
RPCHelpMan{"getnewaddress",
|
|
|
|
|
@@ -369,11 +407,9 @@ static UniValue sendtoaddress(const JSONRPCRequest& request)
|
|
|
|
|
{"subtractfeefromamount", RPCArg::Type::BOOL, /* default */ "false", "The fee will be deducted from the amount being sent.\n"
|
|
|
|
|
" The recipient will receive less bitcoins than you enter in the amount field."},
|
|
|
|
|
{"replaceable", RPCArg::Type::BOOL, /* default */ "wallet default", "Allow this transaction to be replaced by a transaction with higher fees via BIP 125"},
|
|
|
|
|
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks)"},
|
|
|
|
|
{"estimate_mode", RPCArg::Type::STR, /* default */ "UNSET", "The fee estimate mode, must be one of:\n"
|
|
|
|
|
" \"UNSET\"\n"
|
|
|
|
|
" \"ECONOMICAL\"\n"
|
|
|
|
|
" \"CONSERVATIVE\""},
|
|
|
|
|
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks), or fee rate (for " + CURRENCY_UNIT + "/kB or " + CURRENCY_ATOM + "/B estimate modes)"},
|
|
|
|
|
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
|
|
|
|
|
" \"" + FeeModes("\"\n\"") + "\""},
|
|
|
|
|
{"avoid_reuse", RPCArg::Type::BOOL, /* default */ "true", "(only available if avoid_reuse wallet flag is set) Avoid spending from dirty addresses; addresses are considered\n"
|
|
|
|
|
" dirty if they have previously been used in a transaction."},
|
|
|
|
|
},
|
|
|
|
|
@@ -384,6 +420,8 @@ static UniValue sendtoaddress(const JSONRPCRequest& request)
|
|
|
|
|
HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1")
|
|
|
|
|
+ HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1 \"donation\" \"seans outpost\"")
|
|
|
|
|
+ HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1 \"\" \"\" true")
|
|
|
|
|
+ HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1 \"\" \"\" false true 0.00002 " + (CURRENCY_UNIT + "/kB"))
|
|
|
|
|
+ HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1 \"\" \"\" false true 2 " + (CURRENCY_ATOM + "/B"))
|
|
|
|
|
+ HelpExampleRpc("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\", 0.1, \"donation\", \"seans outpost\"")
|
|
|
|
|
},
|
|
|
|
|
}.Check(request);
|
|
|
|
|
@@ -425,20 +463,12 @@ static UniValue sendtoaddress(const JSONRPCRequest& request)
|
|
|
|
|
coin_control.m_signal_bip125_rbf = request.params[5].get_bool();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!request.params[6].isNull()) {
|
|
|
|
|
coin_control.m_confirm_target = ParseConfirmTarget(request.params[6], pwallet->chain().estimateMaxBlocks());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!request.params[7].isNull()) {
|
|
|
|
|
if (!FeeModeFromString(request.params[7].get_str(), coin_control.m_fee_mode)) {
|
|
|
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
coin_control.m_avoid_address_reuse = GetAvoidReuseFlag(pwallet, request.params[8]);
|
|
|
|
|
// We also enable partial spend avoidance if reuse avoidance is set.
|
|
|
|
|
coin_control.m_avoid_partial_spends |= coin_control.m_avoid_address_reuse;
|
|
|
|
|
|
|
|
|
|
SetFeeEstimateMode(pwallet, coin_control, request.params[7], request.params[6]);
|
|
|
|
|
|
|
|
|
|
EnsureWalletIsUnlocked(pwallet);
|
|
|
|
|
|
|
|
|
|
CTransactionRef tx = SendMoney(pwallet, dest, nAmount, fSubtractFeeFromAmount, coin_control, std::move(mapValue));
|
|
|
|
|
@@ -780,11 +810,9 @@ static UniValue sendmany(const JSONRPCRequest& request)
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{"replaceable", RPCArg::Type::BOOL, /* default */ "wallet default", "Allow this transaction to be replaced by a transaction with higher fees via BIP 125"},
|
|
|
|
|
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks)"},
|
|
|
|
|
{"estimate_mode", RPCArg::Type::STR, /* default */ "UNSET", "The fee estimate mode, must be one of:\n"
|
|
|
|
|
" \"UNSET\"\n"
|
|
|
|
|
" \"ECONOMICAL\"\n"
|
|
|
|
|
" \"CONSERVATIVE\""},
|
|
|
|
|
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks), or fee rate (for " + CURRENCY_UNIT + "/kB or " + CURRENCY_ATOM + "/B estimate modes)"},
|
|
|
|
|
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
|
|
|
|
|
" \"" + FeeModes("\"\n\"") + "\""},
|
|
|
|
|
},
|
|
|
|
|
RPCResult{
|
|
|
|
|
RPCResult::Type::STR_HEX, "txid", "The transaction id for the send. Only 1 transaction is created regardless of\n"
|
|
|
|
|
@@ -830,15 +858,7 @@ static UniValue sendmany(const JSONRPCRequest& request)
|
|
|
|
|
coin_control.m_signal_bip125_rbf = request.params[5].get_bool();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!request.params[6].isNull()) {
|
|
|
|
|
coin_control.m_confirm_target = ParseConfirmTarget(request.params[6], pwallet->chain().estimateMaxBlocks());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!request.params[7].isNull()) {
|
|
|
|
|
if (!FeeModeFromString(request.params[7].get_str(), coin_control.m_fee_mode)) {
|
|
|
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
SetFeeEstimateMode(pwallet, coin_control, request.params[7], request.params[6]);
|
|
|
|
|
|
|
|
|
|
std::set<CTxDestination> destinations;
|
|
|
|
|
std::vector<CRecipient> vecSend;
|
|
|
|
|
@@ -2986,6 +3006,12 @@ void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& f
|
|
|
|
|
|
|
|
|
|
if (options.exists("feeRate"))
|
|
|
|
|
{
|
|
|
|
|
if (options.exists("conf_target")) {
|
|
|
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both conf_target and feeRate");
|
|
|
|
|
}
|
|
|
|
|
if (options.exists("estimate_mode")) {
|
|
|
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both estimate_mode and feeRate");
|
|
|
|
|
}
|
|
|
|
|
coinControl.m_feerate = CFeeRate(AmountFromValue(options["feeRate"]));
|
|
|
|
|
coinControl.fOverrideFeeRate = true;
|
|
|
|
|
}
|
|
|
|
|
@@ -2996,20 +3022,7 @@ void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& f
|
|
|
|
|
if (options.exists("replaceable")) {
|
|
|
|
|
coinControl.m_signal_bip125_rbf = options["replaceable"].get_bool();
|
|
|
|
|
}
|
|
|
|
|
if (options.exists("conf_target")) {
|
|
|
|
|
if (options.exists("feeRate")) {
|
|
|
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both conf_target and feeRate");
|
|
|
|
|
}
|
|
|
|
|
coinControl.m_confirm_target = ParseConfirmTarget(options["conf_target"], pwallet->chain().estimateMaxBlocks());
|
|
|
|
|
}
|
|
|
|
|
if (options.exists("estimate_mode")) {
|
|
|
|
|
if (options.exists("feeRate")) {
|
|
|
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both estimate_mode and feeRate");
|
|
|
|
|
}
|
|
|
|
|
if (!FeeModeFromString(options["estimate_mode"].get_str(), coinControl.m_fee_mode)) {
|
|
|
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
SetFeeEstimateMode(pwallet, coinControl, options["estimate_mode"], options["conf_target"]);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// if options is null and not a bool
|
|
|
|
|
@@ -3077,11 +3090,9 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request)
|
|
|
|
|
},
|
|
|
|
|
{"replaceable", RPCArg::Type::BOOL, /* default */ "wallet default", "Marks this transaction as BIP125 replaceable.\n"
|
|
|
|
|
" Allows this transaction to be replaced by a transaction with higher fees"},
|
|
|
|
|
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks)"},
|
|
|
|
|
{"estimate_mode", RPCArg::Type::STR, /* default */ "UNSET", "The fee estimate mode, must be one of:\n"
|
|
|
|
|
" \"UNSET\"\n"
|
|
|
|
|
" \"ECONOMICAL\"\n"
|
|
|
|
|
" \"CONSERVATIVE\""},
|
|
|
|
|
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks), or fee rate (for " + CURRENCY_UNIT + "/kB or " + CURRENCY_ATOM + "/B estimate modes)"},
|
|
|
|
|
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
|
|
|
|
|
" \"" + FeeModes("\"\n\"") + "\""},
|
|
|
|
|
},
|
|
|
|
|
"options"},
|
|
|
|
|
{"iswitness", RPCArg::Type::BOOL, /* default */ "depends on heuristic tests", "Whether the transaction hex is a serialized witness transaction.\n"
|
|
|
|
|
@@ -3249,8 +3260,8 @@ static UniValue bumpfee(const JSONRPCRequest& request)
|
|
|
|
|
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The txid to be bumped"},
|
|
|
|
|
{"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "",
|
|
|
|
|
{
|
|
|
|
|
{"confTarget", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks)"},
|
|
|
|
|
{"fee_rate", RPCArg::Type::NUM, /* default */ "fall back to 'confTarget'", "fee rate (NOT total fee) to pay, in " + CURRENCY_UNIT + " per kB\n"
|
|
|
|
|
{"conf_target", RPCArg::Type::NUM, /* default */ "wallet default", "Confirmation target (in blocks)"},
|
|
|
|
|
{"fee_rate", RPCArg::Type::NUM, /* default */ "fall back to 'conf_target'", "fee rate (NOT total fee) to pay, in " + CURRENCY_UNIT + " per kB\n"
|
|
|
|
|
" Specify a fee rate instead of relying on the built-in fee estimator.\n"
|
|
|
|
|
"Must be at least 0.0001 " + CURRENCY_UNIT + " per kB higher than the current transaction fee rate.\n"},
|
|
|
|
|
{"replaceable", RPCArg::Type::BOOL, /* default */ "true", "Whether the new transaction should still be\n"
|
|
|
|
|
@@ -3260,10 +3271,8 @@ static UniValue bumpfee(const JSONRPCRequest& request)
|
|
|
|
|
" so the new transaction will not be explicitly bip-125 replaceable (though it may\n"
|
|
|
|
|
" still be replaceable in practice, for example if it has unconfirmed ancestors which\n"
|
|
|
|
|
" are replaceable)."},
|
|
|
|
|
{"estimate_mode", RPCArg::Type::STR, /* default */ "UNSET", "The fee estimate mode, must be one of:\n"
|
|
|
|
|
" \"UNSET\"\n"
|
|
|
|
|
" \"ECONOMICAL\"\n"
|
|
|
|
|
" \"CONSERVATIVE\""},
|
|
|
|
|
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
|
|
|
|
|
" \"" + FeeModes("\"\n\"") + "\""},
|
|
|
|
|
},
|
|
|
|
|
"options"},
|
|
|
|
|
},
|
|
|
|
|
@@ -3302,15 +3311,24 @@ static UniValue bumpfee(const JSONRPCRequest& request)
|
|
|
|
|
RPCTypeCheckObj(options,
|
|
|
|
|
{
|
|
|
|
|
{"confTarget", UniValueType(UniValue::VNUM)},
|
|
|
|
|
{"conf_target", UniValueType(UniValue::VNUM)},
|
|
|
|
|
{"fee_rate", UniValueType(UniValue::VNUM)},
|
|
|
|
|
{"replaceable", UniValueType(UniValue::VBOOL)},
|
|
|
|
|
{"estimate_mode", UniValueType(UniValue::VSTR)},
|
|
|
|
|
},
|
|
|
|
|
true, true);
|
|
|
|
|
if (options.exists("confTarget") && options.exists("fee_rate")) {
|
|
|
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "confTarget can't be set with fee_rate. Please provide either a confirmation target in blocks for automatic fee estimation, or an explicit fee rate.");
|
|
|
|
|
} else if (options.exists("confTarget")) { // TODO: alias this to conf_target
|
|
|
|
|
coin_control.m_confirm_target = ParseConfirmTarget(options["confTarget"], pwallet->chain().estimateMaxBlocks());
|
|
|
|
|
|
|
|
|
|
if (options.exists("confTarget") && options.exists("conf_target")) {
|
|
|
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "confTarget and conf_target options should not both be set. Use conf_target (confTarget is deprecated).");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto conf_target = options.exists("confTarget") ? options["confTarget"] : options["conf_target"];
|
|
|
|
|
|
|
|
|
|
if (!conf_target.isNull()) {
|
|
|
|
|
if (options.exists("fee_rate")) {
|
|
|
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "conf_target can't be set with fee_rate. Please provide either a confirmation target in blocks for automatic fee estimation, or an explicit fee rate.");
|
|
|
|
|
}
|
|
|
|
|
coin_control.m_confirm_target = ParseConfirmTarget(conf_target, pwallet->chain().estimateMaxBlocks());
|
|
|
|
|
} else if (options.exists("fee_rate")) {
|
|
|
|
|
CFeeRate fee_rate(AmountFromValue(options["fee_rate"]));
|
|
|
|
|
if (fee_rate <= CFeeRate(0)) {
|
|
|
|
|
@@ -3322,11 +3340,7 @@ static UniValue bumpfee(const JSONRPCRequest& request)
|
|
|
|
|
if (options.exists("replaceable")) {
|
|
|
|
|
coin_control.m_signal_bip125_rbf = options["replaceable"].get_bool();
|
|
|
|
|
}
|
|
|
|
|
if (options.exists("estimate_mode")) {
|
|
|
|
|
if (!FeeModeFromString(options["estimate_mode"].get_str(), coin_control.m_fee_mode)) {
|
|
|
|
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
SetFeeEstimateMode(pwallet, coin_control, options["estimate_mode"], conf_target);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Make sure the results are valid at least up to the most recent block
|
|
|
|
|
@@ -4016,10 +4030,8 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request)
|
|
|
|
|
{"replaceable", RPCArg::Type::BOOL, /* default */ "wallet default", "Marks this transaction as BIP125 replaceable.\n"
|
|
|
|
|
" Allows this transaction to be replaced by a transaction with higher fees"},
|
|
|
|
|
{"conf_target", RPCArg::Type::NUM, /* default */ "fall back to wallet's confirmation target (txconfirmtarget)", "Confirmation target (in blocks)"},
|
|
|
|
|
{"estimate_mode", RPCArg::Type::STR, /* default */ "UNSET", "The fee estimate mode, must be one of:\n"
|
|
|
|
|
" \"UNSET\"\n"
|
|
|
|
|
" \"ECONOMICAL\"\n"
|
|
|
|
|
" \"CONSERVATIVE\""},
|
|
|
|
|
{"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n"
|
|
|
|
|
" \"" + FeeModes("\"\n\"") + "\""},
|
|
|
|
|
},
|
|
|
|
|
"options"},
|
|
|
|
|
{"bip32derivs", RPCArg::Type::BOOL, /* default */ "true", "Include BIP 32 derivation paths for public keys if we know them"},
|
|
|
|
|
|