Repository: bitcoin/bitcoin
File: src/wallet/rpc/spend.cpp (C++)

Code before:
            for (CTxOut& out : rawTx.vout) {
                CTxDestination dest;
                ExtractDestination(out.scriptPubKey, dest);
                std::string addr{EncodeDestination(dest)};
                if (addresses_without_amount.count(addr) > 0) {
                    out.nValue = per_output_without_amount;
                    if (!gave_remaining_to_first) {
                        out.nValue += remainder % addresses_without_amount.size();
                        gave_remaining_to_first = true;
                    }
                    if (IsDust(out, pwallet->chain().relayDustFee())) {
                        // Dynamically generated output amount is dust
                        throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Dynamically assigned remainder results in dust output.");
                    }
                } else {
                    if (IsDust(out, pwallet->chain().relayDustFee())) {
                        // Specified output amount is dust
                        throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Specified output amount to %s is below dust threshold.", addr));
                    }
                }
            }

            const bool lock_unspents{options.exists("lock_unspents") ? options["lock_unspents"].get_bool() : false};
            if (lock_unspents) {
                for (const CTxIn& txin : rawTx.vin) {
                    pwallet->LockCoin(txin.prevout);
                }
            }

            return FinishTransaction(pwallet, options, rawTx);
        }
    };
}

RPCHelpMan walletprocesspsbt()
{
    return RPCHelpMan{
        "walletprocesspsbt",
        "Update a PSBT with input information from our wallet and then sign inputs\n"
                "that we can sign for." +
        HELP_REQUIRING_PASSPHRASE,
                {
                    {"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction base64 string"},
                    {"sign", RPCArg::Type::BOOL, RPCArg::Default{true}, "Also sign the transaction when updating (requires wallet to be unlocked)"},
                    {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"DEFAULT for Taproot, ALL otherwise"}, "The signature hash type to sign with if not specified by the PSBT. Must be one of\n"
            "       \"DEFAULT\"\n"
            "       \"ALL\"\n"
            "       \"NONE\"\n"
            "       \"SINGLE\"\n"
            "       \"ALL|ANYONECANPAY\"\n"
            "       \"NONE|ANYONECANPAY\"\n"

Review comment (suggestion):
11d0513379d9cbb375f5f6e8877743872225a0ff: why don't we persist this?

Could be a separate commit if we want to change the behaviour. Other than with MuSig2, where the wallet has to stay loaded throughout the session, there's generally no reason to expect that the user will keep the wallet open while co-signers do their thing with e.g. a PSBT.

(ditto for the other RPC methods)

Code after:
            for (CTxOut& out : rawTx.vout) {
                CTxDestination dest;
                ExtractDestination(out.scriptPubKey, dest);
                std::string addr{EncodeDestination(dest)};
                if (addresses_without_amount.count(addr) > 0) {
                    out.nValue = per_output_without_amount;
                    if (!gave_remaining_to_first) {
                        out.nValue += remainder % addresses_without_amount.size();
                        gave_remaining_to_first = true;
                    }
                    if (IsDust(out, pwallet->chain().relayDustFee())) {
                        // Dynamically generated output amount is dust
                        throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Dynamically assigned remainder results in dust output.");
                    }
                } else {
                    if (IsDust(out, pwallet->chain().relayDustFee())) {
                        // Specified output amount is dust
                        throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Specified output amount to %s is below dust threshold.", addr));
                    }
                }
            }

            const bool lock_unspents{options.exists("lock_unspents") ? options["lock_unspents"].get_bool() : false};
            if (lock_unspents) {
                for (const CTxIn& txin : rawTx.vin) {
                    pwallet->LockCoin(txin.prevout, /*persist=*/false);
                }
            }

            return FinishTransaction(pwallet, options, rawTx);
        }
    };
}

RPCHelpMan walletprocesspsbt()
{
    return RPCHelpMan{
        "walletprocesspsbt",
        "Update a PSBT with input information from our wallet and then sign inputs\n"
                "that we can sign for." +
        HELP_REQUIRING_PASSPHRASE,
                {
                    {"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction base64 string"},
                    {"sign", RPCArg::Type::BOOL, RPCArg::Default{true}, "Also sign the transaction when updating (requires wallet to be unlocked)"},
                    {"sighashtype", RPCArg::Type::STR, RPCArg::Default{"DEFAULT for Taproot, ALL otherwise"}, "The signature hash type to sign with if not specified by the PSBT. Must be one of\n"
            "       \"DEFAULT\"\n"
            "       \"ALL\"\n"
            "       \"NONE\"\n"
            "       \"SINGLE\"\n"
            "       \"ALL|ANYONECANPAY\"\n"
            "       \"NONE|ANYONECANPAY\"\n"