|
11 | 11 | #include <outputtype.h>
|
12 | 12 | #include <policy/feerate.h>
|
13 | 13 | #include <policy/fees.h>
|
| 14 | +#include <policy/policy.h> |
14 | 15 | #include <policy/rbf.h>
|
15 | 16 | #include <rpc/rawtransaction_util.h>
|
16 | 17 | #include <rpc/server.h>
|
@@ -2955,16 +2956,20 @@ void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& f
|
2955 | 2956 | RPCTypeCheckObj(options,
|
2956 | 2957 | {
|
2957 | 2958 | {"add_inputs", UniValueType(UniValue::VBOOL)},
|
| 2959 | + {"add_to_wallet", UniValueType(UniValue::VBOOL)}, |
2958 | 2960 | {"changeAddress", UniValueType(UniValue::VSTR)},
|
2959 | 2961 | {"change_address", UniValueType(UniValue::VSTR)},
|
2960 | 2962 | {"changePosition", UniValueType(UniValue::VNUM)},
|
2961 | 2963 | {"change_position", UniValueType(UniValue::VNUM)},
|
2962 | 2964 | {"change_type", UniValueType(UniValue::VSTR)},
|
2963 | 2965 | {"includeWatching", UniValueType(UniValue::VBOOL)},
|
2964 | 2966 | {"include_watching", UniValueType(UniValue::VBOOL)},
|
| 2967 | + {"inputs", UniValueType(UniValue::VARR)}, |
2965 | 2968 | {"lockUnspents", UniValueType(UniValue::VBOOL)},
|
2966 | 2969 | {"lock_unspents", UniValueType(UniValue::VBOOL)},
|
2967 |
| - {"feeRate", UniValueType()}, // will be checked below |
| 2970 | + {"locktime", UniValueType(UniValue::VNUM)}, |
| 2971 | + {"feeRate", UniValueType()}, // will be checked below, |
| 2972 | + {"psbt", UniValueType(UniValue::VBOOL)}, |
2968 | 2973 | {"subtractFeeFromOutputs", UniValueType(UniValue::VARR)},
|
2969 | 2974 | {"subtract_fee_from_outputs", UniValueType(UniValue::VARR)},
|
2970 | 2975 | {"replaceable", UniValueType(UniValue::VBOOL)},
|
@@ -3866,6 +3871,185 @@ static UniValue listlabels(const JSONRPCRequest& request)
|
3866 | 3871 | return ret;
|
3867 | 3872 | }
|
3868 | 3873 |
|
| 3874 | +static RPCHelpMan send() |
| 3875 | +{ |
| 3876 | + return RPCHelpMan{"send", |
| 3877 | + "\nSend a transaction.\n", |
| 3878 | + { |
| 3879 | + {"outputs", RPCArg::Type::ARR, RPCArg::Optional::NO, "a json array with outputs (key-value pairs), where none of the keys are duplicated.\n" |
| 3880 | + "That is, each address can only appear once and there can only be one 'data' object.\n" |
| 3881 | + "For convenience, a dictionary, which holds the key-value pairs directly, is also accepted.", |
| 3882 | + { |
| 3883 | + {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", |
| 3884 | + { |
| 3885 | + {"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in " + CURRENCY_UNIT + ""}, |
| 3886 | + }, |
| 3887 | + }, |
| 3888 | + {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", |
| 3889 | + { |
| 3890 | + {"data", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A key-value pair. The key must be \"data\", the value is hex-encoded data"}, |
| 3891 | + }, |
| 3892 | + }, |
| 3893 | + }, |
| 3894 | + }, |
| 3895 | + {"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)"}, |
| 3896 | + {"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n" |
| 3897 | + " \"" + FeeModes("\"\n\"") + "\""}, |
| 3898 | + {"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "", |
| 3899 | + { |
| 3900 | + {"add_inputs", RPCArg::Type::BOOL, /* default */ "false", "If inputs are specified, automatically include more if they are not enough."}, |
| 3901 | + {"add_to_wallet", RPCArg::Type::BOOL, /* default */ "true", "When false, returns a serialized transaction which will not be added to the wallet or broadcast"}, |
| 3902 | + {"change_address", RPCArg::Type::STR_HEX, /* default */ "pool address", "The bitcoin address to receive the change"}, |
| 3903 | + {"change_position", RPCArg::Type::NUM, /* default */ "random", "The index of the change output"}, |
| 3904 | + {"change_type", RPCArg::Type::STR, /* default */ "set by -changetype", "The output type to use. Only valid if change_address is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."}, |
| 3905 | + {"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)"}, |
| 3906 | + {"estimate_mode", RPCArg::Type::STR, /* default */ "unset", std::string() + "The fee estimate mode, must be one of (case insensitive):\n" |
| 3907 | + " \"" + FeeModes("\"\n\"") + "\""}, |
| 3908 | + {"include_watching", RPCArg::Type::BOOL, /* default */ "true for watch-only wallets, otherwise false", "Also select inputs which are watch only.\n" |
| 3909 | + "Only solvable inputs can be used. Watch-only destinations are solvable if the public key and/or output script was imported,\n" |
| 3910 | + "e.g. with 'importpubkey' or 'importmulti' with the 'pubkeys' or 'desc' field."}, |
| 3911 | + {"inputs", RPCArg::Type::ARR, /* default */ "empty array", "Specify inputs instead of adding them automatically. A json array of json objects", |
| 3912 | + { |
| 3913 | + {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"}, |
| 3914 | + {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"}, |
| 3915 | + {"sequence", RPCArg::Type::NUM, RPCArg::Optional::NO, "The sequence number"}, |
| 3916 | + }, |
| 3917 | + }, |
| 3918 | + {"locktime", RPCArg::Type::NUM, /* default */ "0", "Raw locktime. Non-0 value also locktime-activates inputs"}, |
| 3919 | + {"lock_unspents", RPCArg::Type::BOOL, /* default */ "false", "Lock selected unspent outputs"}, |
| 3920 | + {"psbt", RPCArg::Type::BOOL, /* default */ "automatic", "Always return a PSBT, implies add_to_wallet=false."}, |
| 3921 | + {"subtract_fee_from_outputs", RPCArg::Type::ARR, /* default */ "empty array", "A json array of integers.\n" |
| 3922 | + "The fee will be equally deducted from the amount of each specified output.\n" |
| 3923 | + "Those recipients will receive less bitcoins than you enter in their corresponding amount field.\n" |
| 3924 | + "If no outputs are specified here, the sender pays the fee.", |
| 3925 | + { |
| 3926 | + {"vout_index", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The zero-based output index, before a change output is added."}, |
| 3927 | + }, |
| 3928 | + }, |
| 3929 | + {"replaceable", RPCArg::Type::BOOL, /* default */ "wallet default", "Marks this transaction as BIP125 replaceable.\n" |
| 3930 | + " Allows this transaction to be replaced by a transaction with higher fees"}, |
| 3931 | + }, |
| 3932 | + "options"}, |
| 3933 | + }, |
| 3934 | + RPCResult{ |
| 3935 | + RPCResult::Type::OBJ, "", "", |
| 3936 | + { |
| 3937 | + {RPCResult::Type::BOOL, "complete", "If the transaction has a complete set of signatures"}, |
| 3938 | + {RPCResult::Type::STR_HEX, "txid", "The transaction id for the send. Only 1 transaction is created regardless of the number of addresses."}, |
| 3939 | + {RPCResult::Type::STR_HEX, "hex", "If add_to_wallet is false, the hex-encoded raw transaction with signature(s)"}, |
| 3940 | + {RPCResult::Type::STR, "psbt", "If more signatures are needed, or if add_to_wallet is false, the base64-encoded (partially) signed transaction"} |
| 3941 | + } |
| 3942 | + }, |
| 3943 | + RPCExamples{"" |
| 3944 | + "\nSend with a fee rate of 1 satoshi per byte\n" |
| 3945 | + + HelpExampleCli("send", "'{\"" + EXAMPLE_ADDRESS[0] + "\": 0.1}' 1 sat/b\n" + |
| 3946 | + "\nCreate a transaction that should confirm the next block, with a specific input, and return result without adding to wallet or broadcasting to the network\n") |
| 3947 | + + HelpExampleCli("send", "'{\"" + EXAMPLE_ADDRESS[0] + "\": 0.1}' 1 economical '{\"add_to_wallet\": false, \"inputs\": [{\"txid\":\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\", \"vout\":1}]}'") |
| 3948 | + }, |
| 3949 | + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue |
| 3950 | + { |
| 3951 | + RPCTypeCheck(request.params, { |
| 3952 | + UniValueType(), // ARR or OBJ, checked later |
| 3953 | + UniValue::VNUM, |
| 3954 | + UniValue::VSTR, |
| 3955 | + UniValue::VOBJ |
| 3956 | + }, true |
| 3957 | + ); |
| 3958 | + |
| 3959 | + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); |
| 3960 | + if (!wallet) return NullUniValue; |
| 3961 | + CWallet* const pwallet = wallet.get(); |
| 3962 | + |
| 3963 | + UniValue options = request.params[3]; |
| 3964 | + if (options.exists("feeRate") || options.exists("fee_rate") || options.exists("estimate_mode") || options.exists("conf_target")) { |
| 3965 | + if (!request.params[1].isNull() || !request.params[2].isNull()) { |
| 3966 | + throw JSONRPCError(RPC_INVALID_PARAMETER, "Use either conf_target and estimate_mode or the options dictionary to control fee rate"); |
| 3967 | + } |
| 3968 | + } else { |
| 3969 | + options.pushKV("conf_target", request.params[1]); |
| 3970 | + options.pushKV("estimate_mode", request.params[2]); |
| 3971 | + } |
| 3972 | + if (!options["conf_target"].isNull() && (options["estimate_mode"].isNull() || (options["estimate_mode"].get_str() == "unset"))) { |
| 3973 | + throw JSONRPCError(RPC_INVALID_PARAMETER, "Specify estimate_mode"); |
| 3974 | + } |
| 3975 | + if (options.exists("changeAddress")) { |
| 3976 | + throw JSONRPCError(RPC_INVALID_PARAMETER, "Use change_address"); |
| 3977 | + } |
| 3978 | + if (options.exists("changePosition")) { |
| 3979 | + throw JSONRPCError(RPC_INVALID_PARAMETER, "Use change_position"); |
| 3980 | + } |
| 3981 | + if (options.exists("includeWatching")) { |
| 3982 | + throw JSONRPCError(RPC_INVALID_PARAMETER, "Use include_watching"); |
| 3983 | + } |
| 3984 | + if (options.exists("lockUnspents")) { |
| 3985 | + throw JSONRPCError(RPC_INVALID_PARAMETER, "Use lock_unspents"); |
| 3986 | + } |
| 3987 | + if (options.exists("subtractFeeFromOutputs")) { |
| 3988 | + throw JSONRPCError(RPC_INVALID_PARAMETER, "Use subtract_fee_from_outputs"); |
| 3989 | + } |
| 3990 | + |
| 3991 | + const bool psbt_opt_in = options.exists("psbt") && options["psbt"].get_bool(); |
| 3992 | + |
| 3993 | + CAmount fee; |
| 3994 | + int change_position; |
| 3995 | + bool rbf = pwallet->m_signal_rbf; |
| 3996 | + if (options.exists("replaceable")) { |
| 3997 | + rbf = options["add_to_wallet"].get_bool(); |
| 3998 | + } |
| 3999 | + CMutableTransaction rawTx = ConstructTransaction(options["inputs"], request.params[0], options["locktime"], rbf); |
| 4000 | + CCoinControl coin_control; |
| 4001 | + // Automatically select coins, unless at least one is manually selected. Can |
| 4002 | + // be overriden by options.add_inputs. |
| 4003 | + coin_control.m_add_inputs = rawTx.vin.size() == 0; |
| 4004 | + FundTransaction(pwallet, rawTx, fee, change_position, options, coin_control); |
| 4005 | + |
| 4006 | + bool add_to_wallet = true; |
| 4007 | + if (options.exists("add_to_wallet")) { |
| 4008 | + add_to_wallet = options["add_to_wallet"].get_bool(); |
| 4009 | + } |
| 4010 | + |
| 4011 | + // Make a blank psbt |
| 4012 | + PartiallySignedTransaction psbtx(rawTx); |
| 4013 | + |
| 4014 | + // Fill transaction with out data and sign |
| 4015 | + bool complete = true; |
| 4016 | + const TransactionError err = pwallet->FillPSBT(psbtx, complete, SIGHASH_ALL, true, false); |
| 4017 | + if (err != TransactionError::OK) { |
| 4018 | + throw JSONRPCTransactionError(err); |
| 4019 | + } |
| 4020 | + |
| 4021 | + CMutableTransaction mtx; |
| 4022 | + complete = FinalizeAndExtractPSBT(psbtx, mtx); |
| 4023 | + |
| 4024 | + UniValue result(UniValue::VOBJ); |
| 4025 | + |
| 4026 | + // Serialize the PSBT |
| 4027 | + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); |
| 4028 | + ssTx << psbtx; |
| 4029 | + const std::string result_str = EncodeBase64(ssTx.str()); |
| 4030 | + |
| 4031 | + if (psbt_opt_in || !complete || !add_to_wallet) { |
| 4032 | + result.pushKV("psbt", result_str); |
| 4033 | + } |
| 4034 | + |
| 4035 | + if (complete) { |
| 4036 | + std::string err_string; |
| 4037 | + std::string hex = EncodeHexTx(CTransaction(mtx)); |
| 4038 | + CTransactionRef tx(MakeTransactionRef(std::move(mtx))); |
| 4039 | + result.pushKV("txid", tx->GetHash().GetHex()); |
| 4040 | + if (add_to_wallet && !psbt_opt_in) { |
| 4041 | + pwallet->CommitTransaction(tx, {}, {} /* orderForm */); |
| 4042 | + } else { |
| 4043 | + result.pushKV("hex", hex); |
| 4044 | + } |
| 4045 | + } |
| 4046 | + result.pushKV("complete", complete); |
| 4047 | + |
| 4048 | + return result; |
| 4049 | + } |
| 4050 | + }; |
| 4051 | +} |
| 4052 | + |
3869 | 4053 | UniValue sethdseed(const JSONRPCRequest& request)
|
3870 | 4054 | {
|
3871 | 4055 | RPCHelpMan{"sethdseed",
|
@@ -4223,6 +4407,7 @@ static const CRPCCommand commands[] =
|
4223 | 4407 | { "wallet", "lockunspent", &lockunspent, {"unlock","transactions"} },
|
4224 | 4408 | { "wallet", "removeprunedfunds", &removeprunedfunds, {"txid"} },
|
4225 | 4409 | { "wallet", "rescanblockchain", &rescanblockchain, {"start_height", "stop_height"} },
|
| 4410 | + { "wallet", "send", &send, {"outputs","conf_target","estimate_mode","options"} }, |
4226 | 4411 | { "wallet", "sendmany", &sendmany, {"dummy","amounts","minconf","comment","subtractfeefrom","replaceable","conf_target","estimate_mode"} },
|
4227 | 4412 | { "wallet", "sendtoaddress", &sendtoaddress, {"address","amount","comment","comment_to","subtractfeefromamount","replaceable","conf_target","estimate_mode","avoid_reuse"} },
|
4228 | 4413 | { "wallet", "sethdseed", &sethdseed, {"newkeypool","seed"} },
|
|
0 commit comments