diff options
Diffstat (limited to 'src/wallet/rpcwallet.cpp')
-rw-r--r-- | src/wallet/rpcwallet.cpp | 511 |
1 files changed, 421 insertions, 90 deletions
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 893310cf2f..78ea5eac92 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3432,95 +3432,25 @@ static UniValue listunspent(const JSONRPCRequest& request) return results; } -static UniValue fundrawtransaction(const JSONRPCRequest& request) +void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& fee_out, int& change_position, UniValue options) { - std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); - CWallet* const pwallet = wallet.get(); - - if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { - return NullUniValue; - } - - if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) - throw std::runtime_error( - "fundrawtransaction \"hexstring\" ( options iswitness )\n" - "\nAdd inputs to a transaction until it has enough in value to meet its out value.\n" - "This will not modify existing inputs, and will add at most one change output to the outputs.\n" - "No existing outputs will be modified unless \"subtractFeeFromOutputs\" is specified.\n" - "Note that inputs which were signed may need to be resigned after completion since in/outputs have been added.\n" - "The inputs added will not be signed, use signrawtransaction for that.\n" - "Note that all existing inputs must have their previous output transaction be in the wallet.\n" - "Note that all inputs selected must be of standard form and P2SH scripts must be\n" - "in the wallet using importaddress or addmultisigaddress (to calculate fees).\n" - "You can see whether this is the case by checking the \"solvable\" field in the listunspent output.\n" - "Only pay-to-pubkey, multisig, and P2SH versions thereof are currently supported for watch-only\n" - "\nArguments:\n" - "1. \"hexstring\" (string, required) The hex string of the raw transaction\n" - "2. options (object, optional)\n" - " {\n" - " \"changeAddress\" (string, optional, default pool address) The bitcoin address to receive the change\n" - " \"changePosition\" (numeric, optional, default random) The index of the change output\n" - " \"change_type\" (string, optional) The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -changetype.\n" - " \"includeWatching\" (boolean, optional, default false) Also select inputs which are watch only\n" - " \"lockUnspents\" (boolean, optional, default false) Lock selected unspent outputs\n" - " \"feeRate\" (numeric, optional, default not set: makes wallet determine the fee) Set a specific fee rate in " + CURRENCY_UNIT + "/kB\n" - " \"subtractFeeFromOutputs\" (array, optional) A json array of integers.\n" - " The fee will be equally deducted from the amount of each specified output.\n" - " The outputs are specified by their zero-based index, before any change output is added.\n" - " Those recipients will receive less bitcoins than you enter in their corresponding amount field.\n" - " If no outputs are specified here, the sender pays the fee.\n" - " [vout_index,...]\n" - " \"replaceable\" (boolean, optional) Marks this transaction as BIP125 replaceable.\n" - " Allows this transaction to be replaced by a transaction with higher fees\n" - " \"conf_target\" (numeric, optional) Confirmation target (in blocks)\n" - " \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n" - " \"UNSET\"\n" - " \"ECONOMICAL\"\n" - " \"CONSERVATIVE\"\n" - " }\n" - " for backward compatibility: passing in a true instead of an object will result in {\"includeWatching\":true}\n" - "3. iswitness (boolean, optional) Whether the transaction hex is a serialized witness transaction \n" - " If iswitness is not present, heuristic tests will be used in decoding\n" - - "\nResult:\n" - "{\n" - " \"hex\": \"value\", (string) The resulting raw transaction (hex-encoded string)\n" - " \"fee\": n, (numeric) Fee in " + CURRENCY_UNIT + " the resulting transaction pays\n" - " \"changepos\": n (numeric) The position of the added change output, or -1\n" - "}\n" - "\nExamples:\n" - "\nCreate a transaction with no inputs\n" - + HelpExampleCli("createrawtransaction", "\"[]\" \"{\\\"myaddress\\\":0.01}\"") + - "\nAdd sufficient unsigned inputs to meet the output value\n" - + HelpExampleCli("fundrawtransaction", "\"rawtransactionhex\"") + - "\nSign the transaction\n" - + HelpExampleCli("signrawtransaction", "\"fundedtransactionhex\"") + - "\nSend the transaction\n" - + HelpExampleCli("sendrawtransaction", "\"signedtransactionhex\"") - ); - - RPCTypeCheck(request.params, {UniValue::VSTR}); - // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); CCoinControl coinControl; - int changePosition = -1; + change_position = -1; bool lockUnspents = false; UniValue subtractFeeFromOutputs; std::set<int> setSubtractFeeFromOutputs; - if (!request.params[1].isNull()) { - if (request.params[1].type() == UniValue::VBOOL) { + if (!options.isNull()) { + if (options.type() == UniValue::VBOOL) { // backward compatibility bool only fallback - coinControl.fAllowWatchOnly = request.params[1].get_bool(); + coinControl.fAllowWatchOnly = options.get_bool(); } else { - RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VOBJ, UniValue::VBOOL}); - - UniValue options = request.params[1]; - + RPCTypeCheckArgument(options, UniValue::VOBJ); RPCTypeCheckObj(options, { {"changeAddress", UniValueType(UniValue::VSTR)}, @@ -3547,7 +3477,7 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request) } if (options.exists("changePosition")) - changePosition = options["changePosition"].get_int(); + change_position = options["changePosition"].get_int(); if (options.exists("change_type")) { if (options.exists("changeAddress")) { @@ -3594,18 +3524,10 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request) } } - // parse hex string from parameter - CMutableTransaction tx; - bool try_witness = request.params[2].isNull() ? true : request.params[2].get_bool(); - bool try_no_witness = request.params[2].isNull() ? true : !request.params[2].get_bool(); - if (!DecodeHexTx(tx, request.params[0].get_str(), try_no_witness, try_witness)) { - throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); - } - if (tx.vout.size() == 0) throw JSONRPCError(RPC_INVALID_PARAMETER, "TX must have at least one output"); - if (changePosition != -1 && (changePosition < 0 || (unsigned int)changePosition > tx.vout.size())) + if (change_position != -1 && (change_position < 0 || (unsigned int)change_position > tx.vout.size())) throw JSONRPCError(RPC_INVALID_PARAMETER, "changePosition out of bounds"); for (unsigned int idx = 0; idx < subtractFeeFromOutputs.size(); idx++) { @@ -3619,17 +3541,98 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request) setSubtractFeeFromOutputs.insert(pos); } - CAmount nFeeOut; std::string strFailReason; - if (!pwallet->FundTransaction(tx, nFeeOut, changePosition, strFailReason, lockUnspents, setSubtractFeeFromOutputs, coinControl)) { + if (!pwallet->FundTransaction(tx, fee_out, change_position, strFailReason, lockUnspents, setSubtractFeeFromOutputs, coinControl)) { throw JSONRPCError(RPC_WALLET_ERROR, strFailReason); } +} + +static UniValue fundrawtransaction(const JSONRPCRequest& request) +{ + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) + throw std::runtime_error( + "fundrawtransaction \"hexstring\" ( options iswitness )\n" + "\nAdd inputs to a transaction until it has enough in value to meet its out value.\n" + "This will not modify existing inputs, and will add at most one change output to the outputs.\n" + "No existing outputs will be modified unless \"subtractFeeFromOutputs\" is specified.\n" + "Note that inputs which were signed may need to be resigned after completion since in/outputs have been added.\n" + "The inputs added will not be signed, use signrawtransaction for that.\n" + "Note that all existing inputs must have their previous output transaction be in the wallet.\n" + "Note that all inputs selected must be of standard form and P2SH scripts must be\n" + "in the wallet using importaddress or addmultisigaddress (to calculate fees).\n" + "You can see whether this is the case by checking the \"solvable\" field in the listunspent output.\n" + "Only pay-to-pubkey, multisig, and P2SH versions thereof are currently supported for watch-only\n" + "\nArguments:\n" + "1. \"hexstring\" (string, required) The hex string of the raw transaction\n" + "2. options (object, optional)\n" + " {\n" + " \"changeAddress\" (string, optional, default pool address) The bitcoin address to receive the change\n" + " \"changePosition\" (numeric, optional, default random) The index of the change output\n" + " \"change_type\" (string, optional) The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -changetype.\n" + " \"includeWatching\" (boolean, optional, default false) Also select inputs which are watch only\n" + " \"lockUnspents\" (boolean, optional, default false) Lock selected unspent outputs\n" + " \"feeRate\" (numeric, optional, default not set: makes wallet determine the fee) Set a specific fee rate in " + CURRENCY_UNIT + "/kB\n" + " \"subtractFeeFromOutputs\" (array, optional) A json array of integers.\n" + " The fee will be equally deducted from the amount of each specified output.\n" + " The outputs are specified by their zero-based index, before any change output is added.\n" + " Those recipients will receive less bitcoins than you enter in their corresponding amount field.\n" + " If no outputs are specified here, the sender pays the fee.\n" + " [vout_index,...]\n" + " \"replaceable\" (boolean, optional) Marks this transaction as BIP125 replaceable.\n" + " Allows this transaction to be replaced by a transaction with higher fees\n" + " \"conf_target\" (numeric, optional) Confirmation target (in blocks)\n" + " \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n" + " \"UNSET\"\n" + " \"ECONOMICAL\"\n" + " \"CONSERVATIVE\"\n" + " }\n" + " for backward compatibility: passing in a true instead of an object will result in {\"includeWatching\":true}\n" + "3. iswitness (boolean, optional) Whether the transaction hex is a serialized witness transaction \n" + " If iswitness is not present, heuristic tests will be used in decoding\n" + + "\nResult:\n" + "{\n" + " \"hex\": \"value\", (string) The resulting raw transaction (hex-encoded string)\n" + " \"fee\": n, (numeric) Fee in " + CURRENCY_UNIT + " the resulting transaction pays\n" + " \"changepos\": n (numeric) The position of the added change output, or -1\n" + "}\n" + "\nExamples:\n" + "\nCreate a transaction with no inputs\n" + + HelpExampleCli("createrawtransaction", "\"[]\" \"{\\\"myaddress\\\":0.01}\"") + + "\nAdd sufficient unsigned inputs to meet the output value\n" + + HelpExampleCli("fundrawtransaction", "\"rawtransactionhex\"") + + "\nSign the transaction\n" + + HelpExampleCli("signrawtransaction", "\"fundedtransactionhex\"") + + "\nSend the transaction\n" + + HelpExampleCli("sendrawtransaction", "\"signedtransactionhex\"") + ); + + RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType(), UniValue::VBOOL}); + + // parse hex string from parameter + CMutableTransaction tx; + bool try_witness = request.params[2].isNull() ? true : request.params[2].get_bool(); + bool try_no_witness = request.params[2].isNull() ? true : !request.params[2].get_bool(); + if (!DecodeHexTx(tx, request.params[0].get_str(), try_no_witness, try_witness)) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); + } + + CAmount fee; + int change_position; + FundTransaction(pwallet, tx, fee, change_position, request.params[1]); UniValue result(UniValue::VOBJ); result.pushKV("hex", EncodeHexTx(tx)); - result.pushKV("changepos", changePosition); - result.pushKV("fee", ValueFromAmount(nFeeOut)); + result.pushKV("fee", ValueFromAmount(fee)); + result.pushKV("changepos", change_position); return result; } @@ -4400,6 +4403,332 @@ UniValue sethdseed(const JSONRPCRequest& request) return NullUniValue; } +bool ParseHDKeypath(std::string keypath_str, std::vector<uint32_t>& keypath) +{ + std::stringstream ss(keypath_str); + std::string item; + bool first = true; + while (std::getline(ss, item, '/')) { + if (item.compare("m") == 0) { + if (first) { + first = false; + continue; + } + return false; + } + // Finds whether it is hardened + uint32_t path = 0; + size_t pos = item.find("'"); + if (pos != std::string::npos) { + // The hardened tick can only be in the last index of the string + if (pos != item.size() - 1) { + return false; + } + path |= 0x80000000; + item = item.substr(0, item.size() - 1); // Drop the last character which is the hardened tick + } + + // Ensure this is only numbers + if (item.find_first_not_of( "0123456789" ) != std::string::npos) { + return false; + } + uint32_t number; + ParseUInt32(item, &number); + path |= number; + + keypath.push_back(path); + first = false; + } + return true; +} + +void AddKeypathToMap(const CWallet* pwallet, const CKeyID& keyID, std::map<CPubKey, std::vector<uint32_t>>& hd_keypaths) +{ + CPubKey vchPubKey; + if (!pwallet->GetPubKey(keyID, vchPubKey)) { + return; + } + CKeyMetadata meta; + auto it = pwallet->mapKeyMetadata.find(keyID); + if (it != pwallet->mapKeyMetadata.end()) { + meta = it->second; + } + std::vector<uint32_t> keypath; + if (!meta.hdKeypath.empty()) { + if (!ParseHDKeypath(meta.hdKeypath, keypath)) { + throw JSONRPCError(RPC_INTERNAL_ERROR, "Internal keypath is broken"); + } + // Get the proper master key id + CKey key; + pwallet->GetKey(meta.hd_seed_id, key); + CExtKey masterKey; + masterKey.SetSeed(key.begin(), key.size()); + // Add to map + keypath.insert(keypath.begin(), ReadLE32(masterKey.key.GetPubKey().GetID().begin())); + } else { // Single pubkeys get the master fingerprint of themselves + keypath.insert(keypath.begin(), ReadLE32(vchPubKey.GetID().begin())); + } + hd_keypaths.emplace(vchPubKey, keypath); +} + +bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, const CTransaction* txConst, int sighash_type, bool sign, bool bip32derivs) +{ + LOCK(pwallet->cs_wallet); + // Get all of the previous transactions + bool complete = true; + for (unsigned int i = 0; i < txConst->vin.size(); ++i) { + const CTxIn& txin = txConst->vin[i]; + PSBTInput& input = psbtx.inputs.at(i); + + // If we don't know about this input, skip it and let someone else deal with it + const uint256& txhash = txin.prevout.hash; + const auto& it = pwallet->mapWallet.find(txhash); + if (it != pwallet->mapWallet.end()) { + const CWalletTx& wtx = it->second; + CTxOut utxo = wtx.tx->vout[txin.prevout.n]; + input.non_witness_utxo = wtx.tx; + input.witness_utxo = utxo; + } + + // Get the Sighash type + if (sign && input.sighash_type > 0 && input.sighash_type != sighash_type) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Specified Sighash and sighash in PSBT do not match."); + } + + SignatureData sigdata; + if (sign) { + complete &= SignPSBTInput(*pwallet, *psbtx.tx, input, sigdata, i, sighash_type); + } else { + complete &= SignPSBTInput(PublicOnlySigningProvider(pwallet), *psbtx.tx, input, sigdata, i, sighash_type); + } + + // Drop the unnecessary UTXO + if (sigdata.witness) { + input.non_witness_utxo = nullptr; + } else { + input.witness_utxo.SetNull(); + } + + // Get public key paths + if (bip32derivs) { + for (const auto& pubkey_it : sigdata.misc_pubkeys) { + AddKeypathToMap(pwallet, pubkey_it.first, input.hd_keypaths); + } + } + } + + // Fill in the bip32 keypaths and redeemscripts for the outputs so that hardware wallets can identify change + for (unsigned int i = 0; i < txConst->vout.size(); ++i) { + const CTxOut& out = txConst->vout.at(i); + PSBTOutput& psbt_out = psbtx.outputs.at(i); + + // Dummy tx so we can use ProduceSignature to get stuff out + CMutableTransaction dummy_tx; + dummy_tx.vin.push_back(CTxIn()); + dummy_tx.vout.push_back(CTxOut()); + + // Fill a SignatureData with output info + SignatureData sigdata; + psbt_out.FillSignatureData(sigdata); + + MutableTransactionSignatureCreator creator(psbtx.tx.get_ptr(), 0, out.nValue, 1); + ProduceSignature(*pwallet, creator, out.scriptPubKey, sigdata); + psbt_out.FromSignatureData(sigdata); + + // Get public key paths + if (bip32derivs) { + for (const auto& pubkey_it : sigdata.misc_pubkeys) { + AddKeypathToMap(pwallet, pubkey_it.first, psbt_out.hd_keypaths); + } + } + } + return complete; +} + +UniValue walletprocesspsbt(const JSONRPCRequest& request) +{ + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() < 1 || request.params.size() > 4) + throw std::runtime_error( + "walletprocesspsbt \"psbt\" ( sign \"sighashtype\" bip32derivs )\n" + "\nUpdate a PSBT with input information from our wallet and then sign inputs\n" + "that we can sign for.\n" + + HelpRequiringPassphrase(pwallet) + "\n" + + "\nArguments:\n" + "1. \"psbt\" (string, required) The transaction base64 string\n" + "2. sign (boolean, optional, default=true) Also sign the transaction when updating\n" + "3. \"sighashtype\" (string, optional, default=ALL) The signature hash type to sign with if not specified by the PSBT. Must be one of\n" + " \"ALL\"\n" + " \"NONE\"\n" + " \"SINGLE\"\n" + " \"ALL|ANYONECANPAY\"\n" + " \"NONE|ANYONECANPAY\"\n" + " \"SINGLE|ANYONECANPAY\"\n" + "4. bip32derivs (boolean, optiona, default=false) If true, includes the BIP 32 derivation paths for public keys if we know them\n" + + "\nResult:\n" + "{\n" + " \"psbt\" : \"value\", (string) The base64-encoded partially signed transaction\n" + " \"complete\" : true|false, (boolean) If the transaction has a complete set of signatures\n" + " ]\n" + "}\n" + + "\nExamples:\n" + + HelpExampleCli("walletprocesspsbt", "\"psbt\"") + ); + + RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL, UniValue::VSTR}); + + // Unserialize the transaction + PartiallySignedTransaction psbtx; + std::string error; + if (!DecodePSBT(psbtx, request.params[0].get_str(), error)) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error)); + } + + // Get the sighash type + int nHashType = ParseSighashString(request.params[2]); + + // Use CTransaction for the constant parts of the + // transaction to avoid rehashing. + const CTransaction txConst(*psbtx.tx); + + // Fill transaction with our data and also sign + bool sign = request.params[1].isNull() ? true : request.params[1].get_bool(); + bool bip32derivs = request.params[3].isNull() ? false : request.params[3].get_bool(); + bool complete = FillPSBT(pwallet, psbtx, &txConst, nHashType, sign, bip32derivs); + + UniValue result(UniValue::VOBJ); + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << psbtx; + result.push_back(Pair("psbt", EncodeBase64((unsigned char*)ssTx.data(), ssTx.size()))); + result.push_back(Pair("complete", complete)); + + return result; +} + +UniValue walletcreatefundedpsbt(const JSONRPCRequest& request) +{ + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() < 2 || request.params.size() > 6) + throw std::runtime_error( + "walletcreatefundedpsbt [{\"txid\":\"id\",\"vout\":n},...] [{\"address\":amount},{\"data\":\"hex\"},...] ( locktime ) ( replaceable ) ( options bip32derivs )\n" + "\nCreates and funds a transaction in the Partially Signed Transaction format. Inputs will be added if supplied inputs are not enough\n" + "Implements the Creator and Updater roles.\n" + "\nArguments:\n" + "1. \"inputs\" (array, required) A json array of json objects\n" + " [\n" + " {\n" + " \"txid\":\"id\", (string, required) The transaction id\n" + " \"vout\":n, (numeric, required) The output number\n" + " \"sequence\":n (numeric, optional) The sequence number\n" + " } \n" + " ,...\n" + " ]\n" + "2. \"outputs\" (array, required) a json array with outputs (key-value pairs)\n" + " [\n" + " {\n" + " \"address\": x.xxx, (obj, optional) A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in " + CURRENCY_UNIT + "\n" + " },\n" + " {\n" + " \"data\": \"hex\" (obj, optional) A key-value pair. The key must be \"data\", the value is hex encoded data\n" + " }\n" + " ,... More key-value pairs of the above form. For compatibility reasons, a dictionary, which holds the key-value pairs directly, is also\n" + " accepted as second parameter.\n" + " ]\n" + "3. locktime (numeric, optional, default=0) Raw locktime. Non-0 value also locktime-activates inputs\n" + "4. replaceable (boolean, optional, default=false) Marks this transaction as BIP125 replaceable.\n" + " Allows this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible.\n" + "5. options (object, optional)\n" + " {\n" + " \"changeAddress\" (string, optional, default pool address) The bitcoin address to receive the change\n" + " \"changePosition\" (numeric, optional, default random) The index of the change output\n" + " \"change_type\" (string, optional) The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -changetype.\n" + " \"includeWatching\" (boolean, optional, default false) Also select inputs which are watch only\n" + " \"lockUnspents\" (boolean, optional, default false) Lock selected unspent outputs\n" + " \"feeRate\" (numeric, optional, default not set: makes wallet determine the fee) Set a specific fee rate in " + CURRENCY_UNIT + "/kB\n" + " \"subtractFeeFromOutputs\" (array, optional) A json array of integers.\n" + " The fee will be equally deducted from the amount of each specified output.\n" + " The outputs are specified by their zero-based index, before any change output is added.\n" + " Those recipients will receive less bitcoins than you enter in their corresponding amount field.\n" + " If no outputs are specified here, the sender pays the fee.\n" + " [vout_index,...]\n" + " \"replaceable\" (boolean, optional) Marks this transaction as BIP125 replaceable.\n" + " Allows this transaction to be replaced by a transaction with higher fees\n" + " \"conf_target\" (numeric, optional) Confirmation target (in blocks)\n" + " \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n" + " \"UNSET\"\n" + " \"ECONOMICAL\"\n" + " \"CONSERVATIVE\"\n" + " }\n" + "6. bip32derivs (boolean, optiona, default=false) If true, includes the BIP 32 derivation paths for public keys if we know them\n" + "\nResult:\n" + "{\n" + " \"psbt\": \"value\", (string) The resulting raw transaction (base64-encoded string)\n" + " \"fee\": n, (numeric) Fee in " + CURRENCY_UNIT + " the resulting transaction pays\n" + " \"changepos\": n (numeric) The position of the added change output, or -1\n" + "}\n" + "\nExamples:\n" + "\nCreate a transaction with no inputs\n" + + HelpExampleCli("walletcreatefundedpsbt", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") + ); + + RPCTypeCheck(request.params, { + UniValue::VARR, + UniValueType(), // ARR or OBJ, checked later + UniValue::VNUM, + UniValue::VBOOL, + UniValue::VOBJ + }, true + ); + + CAmount fee; + int change_position; + CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]); + FundTransaction(pwallet, rawTx, fee, change_position, request.params[4]); + + // Make a blank psbt + PartiallySignedTransaction psbtx; + psbtx.tx = rawTx; + for (unsigned int i = 0; i < rawTx.vin.size(); ++i) { + psbtx.inputs.push_back(PSBTInput()); + } + for (unsigned int i = 0; i < rawTx.vout.size(); ++i) { + psbtx.outputs.push_back(PSBTOutput()); + } + + // Use CTransaction for the constant parts of the + // transaction to avoid rehashing. + const CTransaction txConst(*psbtx.tx); + + // Fill transaction with out data but don't sign + bool bip32derivs = request.params[5].isNull() ? false : request.params[5].get_bool(); + FillPSBT(pwallet, psbtx, &txConst, 1, false, bip32derivs); + + // Serialize the PSBT + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << psbtx; + + UniValue result(UniValue::VOBJ); + result.pushKV("psbt", EncodeBase64((unsigned char*)ssTx.data(), ssTx.size())); + result.pushKV("fee", ValueFromAmount(fee)); + result.pushKV("changepos", change_position); + return result; +} + extern UniValue abortrescan(const JSONRPCRequest& request); // in rpcdump.cpp extern UniValue dumpprivkey(const JSONRPCRequest& request); // in rpcdump.cpp extern UniValue importprivkey(const JSONRPCRequest& request); @@ -4416,6 +4745,8 @@ static const CRPCCommand commands[] = { // category name actor (function) argNames // --------------------- ------------------------ ----------------------- ---------- { "rawtransactions", "fundrawtransaction", &fundrawtransaction, {"hexstring","options","iswitness"} }, + { "wallet", "walletprocesspsbt", &walletprocesspsbt, {"psbt","sign","sighashtype","bip32derivs"} }, + { "wallet", "walletcreatefundedpsbt", &walletcreatefundedpsbt, {"inputs","outputs","locktime","replaceable","options","bip32derivs"} }, { "hidden", "resendwallettransactions", &resendwallettransactions, {} }, { "wallet", "abandontransaction", &abandontransaction, {"txid"} }, { "wallet", "abortrescan", &abortrescan, {} }, |