<NME> rpcwallet.cpp <BEF> // Copyright (c) 2010 Satoshi Nakamoto // Copyright (c) 2009-2020 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <amount.h> #include <core_io.h> #include <interfaces/chain.h> #include <key_io.h> #include <node/context.h> #include <outputtype.h> #include <policy/feerate.h> #include <policy/fees.h> #include <policy/policy.h> #include <policy/rbf.h> #include <rpc/rawtransaction_util.h> #include <rpc/server.h> #include <rpc/util.h> #include <script/descriptor.h> #include <script/sign.h> #include <util/bip32.h> #include <util/fees.h> #include <util/message.h> // For MessageSign() #include <util/moneystr.h> #include <util/string.h> #include <util/system.h> #include <util/translation.h> #include <util/url.h> #include <util/vector.h> #include <wallet/coincontrol.h> #include <wallet/context.h> #include <wallet/feebumper.h> #include <wallet/load.h> #include <wallet/rpcwallet.h> #include <wallet/wallet.h> #include <wallet/walletdb.h> #include <wallet/walletutil.h> #include <qtum/qtumdelegation.h> #include <util/signstr.h> #include <interfaces/wallet.h> #include <rpc/contract_util.h> #include <util/tokenstr.h> #include <optional> #include <stdint.h> #include <univalue.h> 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 inline bool GetAvoidReuseFlag(const CWallet& wallet, const UniValue& param) { bool can_avoid_reuse = wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE); bool avoid_reuse = param.isNull() ? can_avoid_reuse : param.get_bool(); if (avoid_reuse && !can_avoid_reuse) { throw JSONRPCError(RPC_WALLET_ERROR, "wallet does not have the \"avoid reuse\" feature enabled"); } return avoid_reuse; } /** Used by RPC commands that have an include_watchonly parameter. * We default to true for watchonly wallets if include_watchonly isn't * explicitly set. */ static bool ParseIncludeWatchonly(const UniValue& include_watchonly, const CWallet& wallet) { if (include_watchonly.isNull()) { // if include_watchonly isn't explicitly set, then check if we have a watchonly wallet return wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); } // otherwise return whatever include_watchonly was set to return include_watchonly.get_bool(); } /** Checks if a CKey is in the given CWallet compressed or otherwise*/ bool HaveKey(const SigningProvider& wallet, const CKey& key) { CKey key2; key2.Set(key.begin(), key.end(), !key.IsCompressed()); return wallet.HaveKey(key.GetPubKey().GetID()) || wallet.HaveKey(key2.GetPubKey().GetID()); } bool GetWalletNameFromJSONRPCRequest(const JSONRPCRequest& request, std::string& wallet_name) { if (URL_DECODE && request.URI.substr(0, WALLET_ENDPOINT_BASE.size()) == WALLET_ENDPOINT_BASE) { // wallet endpoint was used wallet_name = URL_DECODE(request.URI.substr(WALLET_ENDPOINT_BASE.size())); return true; } return false; } std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& request) { CHECK_NONFATAL(request.mode == JSONRPCRequest::EXECUTE); std::string wallet_name; if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) { std::shared_ptr<CWallet> pwallet = GetWallet(wallet_name); if (!pwallet) throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded"); return pwallet; } std::vector<std::shared_ptr<CWallet>> wallets = GetWallets(); if (wallets.size() == 1) { return wallets[0]; } if (wallets.empty()) { throw JSONRPCError( RPC_WALLET_NOT_FOUND, "No wallet is loaded. Load a wallet using loadwallet or create a new one with createwallet. (Note: A default wallet is no longer automatically created)"); } throw JSONRPCError(RPC_WALLET_NOT_SPECIFIED, "Wallet file not specified (must request wallet RPC through /wallet/<filename> uri-path)."); } void EnsureWalletIsUnlocked(const CWallet& wallet) { if (wallet.IsLocked()) { throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Please enter the wallet passphrase with walletpassphrase first."); } if (wallet.m_wallet_unlock_staking_only) { throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Wallet is unlocked for staking only."); } } WalletContext& EnsureWalletContext(const std::any& context) { auto wallet_context = util::AnyPtr<WalletContext>(context); if (!wallet_context) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Wallet context not found"); } return *wallet_context; } // also_create should only be set to true only when the RPC is expected to add things to a blank wallet and make it no longer blank LegacyScriptPubKeyMan& EnsureLegacyScriptPubKeyMan(CWallet& wallet, bool also_create) { LegacyScriptPubKeyMan* spk_man = wallet.GetLegacyScriptPubKeyMan(); if (!spk_man && also_create) { spk_man = wallet.GetOrCreateLegacyScriptPubKeyMan(); } if (!spk_man) { throw JSONRPCError(RPC_WALLET_ERROR, "This type of wallet does not support this command"); } return *spk_man; } static void WalletTxToJSON(interfaces::Chain& chain, const CWalletTx& wtx, UniValue& entry) { int confirms = wtx.GetDepthInMainChain(); entry.pushKV("confirmations", confirms); if (wtx.IsCoinBase() || wtx.IsCoinStake()) entry.pushKV("generated", true); if (confirms > 0) { entry.pushKV("blockhash", wtx.m_confirm.hashBlock.GetHex()); entry.pushKV("blockheight", wtx.m_confirm.block_height); entry.pushKV("blockindex", wtx.m_confirm.nIndex); int64_t block_time; CHECK_NONFATAL(chain.findBlock(wtx.m_confirm.hashBlock, FoundBlock().time(block_time))); entry.pushKV("blocktime", block_time); } else { entry.pushKV("trusted", wtx.IsTrusted()); } uint256 hash = wtx.GetHash(); entry.pushKV("txid", hash.GetHex()); UniValue conflicts(UniValue::VARR); for (const uint256& conflict : wtx.GetConflicts()) conflicts.push_back(conflict.GetHex()); entry.pushKV("walletconflicts", conflicts); entry.pushKV("time", wtx.GetTxTime()); entry.pushKV("timereceived", (int64_t)wtx.nTimeReceived); // Add opt-in RBF status std::string rbfStatus = "no"; if (confirms <= 0) { RBFTransactionState rbfState = chain.isRBFOptIn(*wtx.tx); if (rbfState == RBFTransactionState::UNKNOWN) rbfStatus = "unknown"; else if (rbfState == RBFTransactionState::REPLACEABLE_BIP125) rbfStatus = "yes"; } entry.pushKV("bip125-replaceable", rbfStatus); for (const std::pair<const std::string, std::string>& item : wtx.mapValue) entry.pushKV(item.first, item.second); } static std::string LabelFromValue(const UniValue& value) { std::string label = value.get_str(); if (label == "*") throw JSONRPCError(RPC_WALLET_INVALID_LABEL_NAME, "Invalid label name"); return label; } /** * Update coin control with fee estimation based on the given parameters * * @param[in] wallet Wallet reference * @param[in,out] cc Coin control to be updated * @param[in] conf_target UniValue integer; confirmation target in blocks, values between 1 and 1008 are valid per policy/fees.h; * @param[in] estimate_mode UniValue string; fee estimation mode, valid values are "unset", "economical" or "conservative"; * @param[in] fee_rate UniValue real; fee rate in sat/vB; * if present, both conf_target and estimate_mode must either be null, or "unset" * @param[in] override_min_fee bool; whether to set fOverrideFeeRate to true to disable minimum fee rate checks and instead * verify only that fee_rate is greater than 0 * @throws a JSONRPCError if conf_target, estimate_mode, or fee_rate contain invalid values or are in conflict */ static void SetFeeEstimateMode(const CWallet& wallet, CCoinControl& cc, const UniValue& conf_target, const UniValue& estimate_mode, const UniValue& fee_rate, bool override_min_fee) { if (!fee_rate.isNull()) { if (!conf_target.isNull()) { throw 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."); } if (!estimate_mode.isNull() && estimate_mode.get_str() != "unset") { throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both estimate_mode and fee_rate"); } // Fee rates in sat/vB cannot represent more than 3 significant digits. cc.m_feerate = CFeeRate{AmountFromValue(fee_rate, /* decimals */ 3)}; if (override_min_fee) cc.fOverrideFeeRate = true; // Default RBF to true for explicit fee_rate, if unset. if (!cc.m_signal_bip125_rbf) cc.m_signal_bip125_rbf = true; return; } if (!estimate_mode.isNull() && !FeeModeFromString(estimate_mode.get_str(), cc.m_fee_mode)) { throw JSONRPCError(RPC_INVALID_PARAMETER, InvalidEstimateModeErrorMessage()); } if (!conf_target.isNull()) { cc.m_confirm_target = ParseConfirmTarget(conf_target, wallet.chain().estimateMaxBlocks()); } } bool SetDefaultPayForContractAddress(const CWallet& wallet, CCoinControl & coinControl) { // Set default coin to pay for the contract // Select any valid unspent output that can be used to pay for the contract std::vector<COutput> vecOutputs; coinControl.fAllowOtherInputs=true; coinControl.m_include_unsafe_inputs = true; wallet.AvailableCoins(vecOutputs, &coinControl); for (const COutput& out : vecOutputs) { CTxDestination destAdress; const CScript& scriptPubKey = out.tx->tx->vout[out.i].scriptPubKey; bool fValidAddress = out.fSpendable && ExtractDestination(scriptPubKey, destAdress) && IsValidContractSenderAddress(destAdress); if (!fValidAddress) continue; coinControl.Select(COutPoint(out.tx->GetHash(),out.i)); break; } return coinControl.HasSelected(); } bool SetDefaultSignSenderAddress(const CWallet& wallet, CTxDestination& destAdress, CCoinControl & coinControl) { // Set default sender address if none provided // Select any valid unspent output that can be used for contract sender address std::vector<COutput> vecOutputs; coinControl.fAllowOtherInputs=true; coinControl.m_include_unsafe_inputs = true; wallet.AvailableCoins(vecOutputs, &coinControl); for (const COutput& out : vecOutputs) { const CScript& scriptPubKey = out.tx->tx->vout[out.i].scriptPubKey; bool fValidAddress = out.fSpendable && ExtractDestination(scriptPubKey, destAdress) && IsValidContractSenderAddress(destAdress); if (!fValidAddress) continue; break; } return !std::holds_alternative<CNoDestination>(destAdress); } static RPCHelpMan getnewaddress() { return RPCHelpMan{"getnewaddress", "\nReturns a new Qtum address for receiving payments.\n" "If 'label' is specified, it is added to the address book \n" "so payments received with the address will be associated with 'label'.\n", { {"label", RPCArg::Type::STR, RPCArg::Default{""}, "The label name for the address to be linked to. It can also be set to the empty string \"\" to represent the default label. The label does not need to exist, it will be created if there is no label by the given name."}, {"address_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -addresstype"}, "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."}, }, RPCResult{ RPCResult::Type::STR, "address", "The new qtum address" }, RPCExamples{ HelpExampleCli("getnewaddress", "") + HelpExampleRpc("getnewaddress", "") }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); if (!pwallet) return NullUniValue; LOCK(pwallet->cs_wallet); if (!pwallet->CanGetAddresses()) { throw JSONRPCError(RPC_WALLET_ERROR, "Error: This wallet has no available keys"); } // Parse the label first so we don't generate a key if there's an error std::string label; if (!request.params[0].isNull()) label = LabelFromValue(request.params[0]); OutputType output_type = pwallet->m_default_address_type; if (!request.params[1].isNull()) { if (!ParseOutputType(request.params[1].get_str(), output_type)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[1].get_str())); } if (output_type == OutputType::BECH32M && pwallet->GetLegacyScriptPubKeyMan()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Legacy wallets cannot provide bech32m addresses"); } } CTxDestination dest; std::string error; if (!pwallet->GetNewDestination(output_type, label, dest, error)) { throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, error); } return EncodeDestination(dest); }, }; } static RPCHelpMan getrawchangeaddress() { return RPCHelpMan{"getrawchangeaddress", "\nReturns a new Qtum address, for receiving change.\n" "This is for use with raw transactions, NOT normal use.\n", { {"address_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."}, }, RPCResult{ RPCResult::Type::STR, "address", "The address" }, RPCExamples{ HelpExampleCli("getrawchangeaddress", "") + HelpExampleRpc("getrawchangeaddress", "") }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); if (!pwallet) return NullUniValue; LOCK(pwallet->cs_wallet); if (!pwallet->CanGetAddresses(true)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error: This wallet has no available keys"); } OutputType output_type = pwallet->m_default_change_type.value_or(pwallet->m_default_address_type); if (!request.params[0].isNull()) { if (!ParseOutputType(request.params[0].get_str(), output_type)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[0].get_str())); } if (output_type == OutputType::BECH32M && pwallet->GetLegacyScriptPubKeyMan()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Legacy wallets cannot provide bech32m addresses"); } } CTxDestination dest; std::string error; if (!pwallet->GetNewChangeDestination(output_type, dest, error)) { throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, error); } return EncodeDestination(dest); }, }; } static RPCHelpMan setlabel() { return RPCHelpMan{"setlabel", "\nSets the label associated with the given address.\n", { {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The qtum address to be associated with a label."}, {"label", RPCArg::Type::STR, RPCArg::Optional::NO, "The label to assign to the address."}, }, RPCResult{RPCResult::Type::NONE, "", ""}, RPCExamples{ HelpExampleCli("setlabel", "\"" + EXAMPLE_ADDRESS[0] + "\" \"tabby\"") + HelpExampleRpc("setlabel", "\"" + EXAMPLE_ADDRESS[0] + "\", \"tabby\"") }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); if (!pwallet) return NullUniValue; LOCK(pwallet->cs_wallet); CTxDestination dest = DecodeDestination(request.params[0].get_str()); if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Qtum address"); } std::string label = LabelFromValue(request.params[1]); if (pwallet->IsMine(dest)) { pwallet->SetAddressBook(dest, label, "receive"); } else { pwallet->SetAddressBook(dest, label, "send"); } return NullUniValue; }, }; } void ParseRecipients(const UniValue& address_amounts, const UniValue& subtract_fee_outputs, std::vector<CRecipient> &recipients) { std::set<CTxDestination> destinations; int i = 0; for (const std::string& address: address_amounts.getKeys()) { CTxDestination dest = DecodeDestination(address); if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Qtum address: ") + address); } if (destinations.count(dest)) { throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated address: ") + address); } destinations.insert(dest); CScript script_pub_key = GetScriptForDestination(dest); CAmount amount = AmountFromValue(address_amounts[i++]); bool subtract_fee = false; for (unsigned int idx = 0; idx < subtract_fee_outputs.size(); idx++) { const UniValue& addr = subtract_fee_outputs[idx]; if (addr.get_str() == address) { subtract_fee = true; } } CRecipient recipient = {script_pub_key, amount, subtract_fee}; recipients.push_back(recipient); } } UniValue SendMoney(CWallet& wallet, const CCoinControl &coin_control, std::vector<CRecipient> &recipients, mapValue_t map_value, bool verbose, bool hasSender = false) { EnsureWalletIsUnlocked(wallet); // This function is only used by sendtoaddress and sendmany. // This should always try to sign, if we don't have private keys, don't try to do anything here. if (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet"); } if (wallet.m_wallet_unlock_staking_only) { throw JSONRPCError(RPC_WALLET_ERROR, "Error: Wallet unlocked for staking only, unable to create transaction."); } // Shuffle recipient list std::shuffle(recipients.begin(), recipients.end(), FastRandomContext()); // Send CAmount nFeeRequired = 0; int nChangePosRet = -1; bilingual_str error; CTransactionRef tx; FeeCalculation fee_calc_out; const bool fCreated = wallet.CreateTransaction(recipients, tx, nFeeRequired, nChangePosRet, error, coin_control, fee_calc_out, true, 0, hasSender); if (!fCreated) { throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, error.original); } wallet.CommitTransaction(tx, std::move(map_value), {} /* orderForm */); if (verbose) { UniValue entry(UniValue::VOBJ); entry.pushKV("txid", tx->GetHash().GetHex()); entry.pushKV("fee_reason", StringForFeeReason(fee_calc_out.reason)); return entry; } return tx->GetHash().GetHex(); } void SplitRemainder(std::vector<CRecipient>& vecSend, CAmount& remainder, CAmount maxValue) { if(remainder > 0) { for(int i = vecSend.size() - 1; i >= 0 ; i--) { CAmount diffAmount = maxValue - vecSend[i].nAmount; if(diffAmount > 0) { if((remainder - diffAmount) > 0) { vecSend[i].nAmount = vecSend[i].nAmount + diffAmount; remainder -= diffAmount; } else { vecSend[i].nAmount = vecSend[i].nAmount + remainder; remainder = 0; } } if(remainder <= 0) break; } } } static CTransactionRef SplitUTXOs(std::shared_ptr<CWallet> const pwallet, const CTxDestination &address, CAmount nValue, CAmount maxValue, const CCoinControl& coin_control, CAmount nTotal, int maxOutputs, CAmount& nSplited, bool sign) { // Check amount if (nValue <= 0) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid amount"); if (nValue > nTotal) throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds"); // Parse Qtum address CScript scriptPubKey = GetScriptForDestination(address); // Split into utxos with nValue CAmount nFeeRequired = 0; bilingual_str error; std::vector<CRecipient> vecSend; int nChangePosRet = -1; int numOfRecipients = static_cast<int>(nTotal / nValue); // Compute the number of recipients CAmount remainder = nTotal % nValue; if(remainder == 0 && numOfRecipients > 0) { numOfRecipients -= 1; remainder = nValue; } if(numOfRecipients > maxOutputs) { numOfRecipients = maxOutputs; remainder = 0; } // Split coins between recipients CAmount nTxAmount = 0; nSplited = 0; CRecipient recipient = {scriptPubKey, nValue, false}; for(int i = 0; i < numOfRecipients; i++) { vecSend.push_back(recipient); } SplitRemainder(vecSend, remainder, maxValue); // Get the total amount of the outputs for(CRecipient rec : vecSend) { nTxAmount += rec.nAmount; } // Create the transaction CTransactionRef tx; if((nTxAmount + pwallet->m_default_max_tx_fee) <= nTotal) { FeeCalculation fee_calc_out; if (!pwallet->CreateTransaction(vecSend, tx, nFeeRequired, nChangePosRet, error, coin_control, fee_calc_out, sign, 0, false)) { throw JSONRPCError(RPC_WALLET_ERROR, error.original); } nSplited = nFeeRequired; } else if (vecSend.size() > 0) { // Pay the fee for the tx with the last recipient CRecipient lastRecipient = vecSend[vecSend.size() - 1]; lastRecipient.fSubtractFeeFromAmount = true; vecSend[vecSend.size() - 1] = lastRecipient; FeeCalculation fee_calc_out; if (!pwallet->CreateTransaction(vecSend, tx, nFeeRequired, nChangePosRet, error, coin_control, fee_calc_out, sign, 0, false)) { throw JSONRPCError(RPC_WALLET_ERROR, error.original); } // Combine the last 2 outputs when the last output have value less than nValue due to paying the fee if(vecSend.size() >= 2) { if((lastRecipient.nAmount - nFeeRequired) < nValue) { bool payFeeRemainder = (nTotal - nTxAmount) > nFeeRequired * 1.1; if(payFeeRemainder) { // Pay the fee with the remainder lastRecipient.fSubtractFeeFromAmount = false; vecSend.pop_back(); vecSend.push_back(lastRecipient); } else { // Combine the last 2 outputs CAmount nValueLast2 = lastRecipient.nAmount + vecSend[vecSend.size() - 2].nAmount; lastRecipient.nAmount = lastRecipient.nAmount + nFeeRequired; lastRecipient.fSubtractFeeFromAmount = true; nValueLast2 -= lastRecipient.nAmount; vecSend.pop_back(); vecSend.pop_back(); vecSend.push_back(lastRecipient); // Split the rest with the others SplitRemainder(vecSend, nValueLast2, maxValue); } FeeCalculation fee_calc_out; if((!pwallet->CreateTransaction(vecSend, tx, nFeeRequired, nChangePosRet, error, coin_control, fee_calc_out, sign, 0, false))) { throw JSONRPCError(RPC_WALLET_ERROR, error.original); } if(payFeeRemainder) { nSplited = nFeeRequired; } } } } // Compute the splited amount for(CRecipient rec : vecSend) { nSplited += rec.nAmount; } // Send the transaction if(sign) pwallet->CommitTransaction(tx, {} /* mapValue */, {} /* orderForm */); return tx; } static RPCHelpMan sendtoaddress() { return RPCHelpMan{"sendtoaddress", "\nSend an amount to a given address." + HELP_REQUIRING_PASSPHRASE, { {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The qtum address to send to."}, {"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The amount in " + CURRENCY_UNIT + " to send. eg 0.1"}, {"comment", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "A comment used to store what the transaction is for.\n" "This is not part of the transaction, just kept in your wallet."}, {"comment_to", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "A comment to store the name of the person or organization\n" "to which you're sending the transaction. This is not part of the \n" "transaction, just kept in your wallet."}, {"subtractfeefromamount", RPCArg::Type::BOOL, RPCArg::Default{false}, "The fee will be deducted from the amount being sent.\n" "The recipient will receive less qtums than you enter in the amount field."}, {"replaceable", RPCArg::Type::BOOL, RPCArg::DefaultHint{"wallet default"}, "Allow this transaction to be replaced by a transaction with higher fees via BIP 125"}, {"conf_target", RPCArg::Type::NUM, RPCArg::DefaultHint{"wallet -txconfirmtarget"}, "Confirmation target in blocks"}, {"estimate_mode", RPCArg::Type::STR, RPCArg::Default{"unset"}, std::string() + "The fee estimate mode, must be one of (case insensitive):\n" " \"" + FeeModes("\"\n\"") + "\""}, {"avoid_reuse", RPCArg::Type::BOOL, RPCArg::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. If true, this also activates avoidpartialspends, grouping outputs by their addresses."}, {"fee_rate", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"not set, fall back to wallet fee estimation"}, "Specify a fee rate in " + CURRENCY_ATOM + "/vB."}, {"verbose", RPCArg::Type::BOOL, RPCArg::Default{false}, "If true, return extra information about the transaction."}, {"senderaddress", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "The qtum address that will be used to send money from."}, {"changetosender", RPCArg::Type::BOOL, RPCArg::Default{false}, "Return the change to the sender."}, }, { RPCResult{"if verbose is not set or set to false", RPCResult::Type::STR_HEX, "txid", "The transaction id." }, RPCResult{"if verbose is set to true", RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR_HEX, "txid", "The transaction id."}, {RPCResult::Type::STR, "fee reason", "The transaction fee reason."} }, }, }, RPCExamples{ "\nSend 0.1 QTUM\n" + HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1") + "\nSend 0.1 QTUM with a confirmation target of 6 blocks in economical fee estimate mode using positional arguments\n" + HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1 \"donation\" \"sean's outpost\" false true 6 economical") + "\nSend 0.1 QTUM with a fee rate of 1.1 " + CURRENCY_ATOM + "/vB, subtract fee from amount, BIP125-replaceable, using positional arguments\n" + HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\" 0.1 \"drinks\" \"room77\" true true null \"unset\" null 1.1") + "\nSend 0.2 QTUM with a confirmation target of 6 blocks in economical fee estimate mode using named arguments\n" + HelpExampleCli("-named sendtoaddress", "address=\"" + EXAMPLE_ADDRESS[0] + "\" amount=0.2 conf_target=6 estimate_mode=\"economical\"") + "\nSend 0.5 QTUM with a fee rate of 25 " + CURRENCY_ATOM + "/vB using named arguments\n" + HelpExampleCli("-named sendtoaddress", "address=\"" + EXAMPLE_ADDRESS[0] + "\" amount=0.5 fee_rate=25") + HelpExampleCli("-named sendtoaddress", "address=\"" + EXAMPLE_ADDRESS[0] + "\" amount=0.5 fee_rate=25 subtractfeefromamount=false replaceable=true avoid_reuse=true comment=\"2 pizzas\" comment_to=\"jeremy\" verbose=true") + HelpExampleCli("sendtoaddress", "\"" + EXAMPLE_ADDRESS[0] + "\", 0.1, \"donation\", \"seans outpost\", false, null, null, \"\", false, \"" + EXAMPLE_ADDRESS[1] + "\", true") }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); if (!pwallet) return NullUniValue; // 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(); LOCK(pwallet->cs_wallet); // Wallet comments mapValue_t mapValue; if (!request.params[2].isNull() && !request.params[2].get_str().empty()) mapValue["comment"] = request.params[2].get_str(); if (!request.params[3].isNull() && !request.params[3].get_str().empty()) mapValue["to"] = request.params[3].get_str(); bool fSubtractFeeFromAmount = false; if (!request.params[4].isNull()) { fSubtractFeeFromAmount = request.params[4].get_bool(); } CCoinControl coin_control; if (!request.params[5].isNull()) { coin_control.m_signal_bip125_rbf = request.params[5].get_bool(); } 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, /* conf_target */ request.params[6], /* estimate_mode */ request.params[7], /* fee_rate */ request.params[9], /* override_min_fee */ false); EnsureWalletIsUnlocked(*pwallet); UniValue address_amounts(UniValue::VOBJ); const std::string address = request.params[0].get_str(); address_amounts.pushKV(address, request.params[1]); UniValue subtractFeeFromAmount(UniValue::VARR); if (fSubtractFeeFromAmount) { subtractFeeFromAmount.push_back(address); } std::vector<CRecipient> recipients; ParseRecipients(address_amounts, subtractFeeFromAmount, recipients); const bool verbose{request.params[10].isNull() ? false : request.params[10].get_bool()}; bool fHasSender=false; CTxDestination senderAddress; if (!request.params[11].isNull()){ senderAddress = DecodeDestination(request.params[11].get_str()); if (!IsValidDestination(senderAddress)) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Qtum address to send from"); else fHasSender=true; } bool fChangeToSender=false; if (!request.params[12].isNull()){ fChangeToSender=request.params[12].get_bool(); } if(fHasSender){ //find a UTXO with sender address UniValue results(UniValue::VARR); std::vector<COutput> vecOutputs; coin_control.fAllowOtherInputs=true; assert(pwallet != NULL); pwallet->AvailableCoins(vecOutputs, NULL); for(const COutput& out : vecOutputs) { CTxDestination destAdress; const CScript& scriptPubKey = out.tx->tx->vout[out.i].scriptPubKey; bool fValidAddress = ExtractDestination(scriptPubKey, destAdress); if (!fValidAddress || senderAddress != destAdress) continue; coin_control.Select(COutPoint(out.tx->GetHash(),out.i)); break; } if(!coin_control.HasSelected()){ throw JSONRPCError(RPC_TYPE_ERROR, "Sender address does not have any unspent outputs"); } if(fChangeToSender){ coin_control.destChange=senderAddress; } } return SendMoney(*pwallet, coin_control, recipients, mapValue, verbose, fHasSender); }, }; } static RPCHelpMan splitutxosforaddress() { return RPCHelpMan{"splitutxosforaddress", "\nSplit an address coins into utxo between min and max value." + HELP_REQUIRING_PASSPHRASE, { {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The qtum address to split utxos."}, {"minvalue", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "Select utxo which value is smaller than value (minimum 0.1 COIN)"}, {"maxvalue", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "Select utxo which value is greater than value (minimum 0.1 COIN)"}, {"maxoutputs", RPCArg::Type::NUM, RPCArg::Default{100}, "Maximum outputs to create"}, {"psbt", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED_NAMED_ARG, "Create partially signed transaction."}, }, RPCResult{ RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR_HEX, "txid", "The hex-encoded transaction id"}, {RPCResult::Type::STR_AMOUNT, "selected", "Selected amount of coins"}, {RPCResult::Type::STR_AMOUNT, "splited", "Splited amount of coins"}, } }, RPCExamples{ HelpExampleCli("splitutxosforaddress", "\"QM72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 100 200") + HelpExampleCli("splitutxosforaddress", "\"QM72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 100 200 100") + HelpExampleRpc("splitutxosforaddress", "\"QM72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 100 200") + HelpExampleRpc("splitutxosforaddress", "\"QM72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 100 200 100") }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); if (!pwallet) return NullUniValue; // 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(); LOCK(pwallet->cs_wallet); // Address CTxDestination address = DecodeDestination(request.params[0].get_str()); if (!IsValidDestination(address)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Qtum address"); } CScript scriptPubKey = GetScriptForDestination(address); if (!pwallet->IsMine(scriptPubKey)) { throw JSONRPCError(RPC_WALLET_ERROR, "Address not found in wallet"); } // minimum value CAmount minValue = AmountFromValue(request.params[1]); // maximum value CAmount maxValue = AmountFromValue(request.params[2]); if (minValue < COIN/10 || maxValue <= 0 || minValue > maxValue) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid values for minimum and maximum"); } // Maximum outputs int maxOutputs = !request.params[3].isNull() ? request.params[3].get_int() : 100; if (maxOutputs < 1) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid value for maximum outputs"); } // Is psbt bool fPsbt=pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); if (!request.params[4].isNull()){ fPsbt=request.params[4].get_bool(); } // Amount CAmount nSplitAmount = minValue; CAmount nRequiredAmount = nSplitAmount * maxOutputs; CCoinControl coin_control; coin_control.destChange = address; if(fPsbt) coin_control.fAllowWatchOnly = true; // Find UTXOs for a address with value smaller than minValue and greater then maxValue std::vector<COutput> vecOutputs; coin_control.fAllowOtherInputs=true; assert(pwallet != NULL); pwallet->AvailableCoins(vecOutputs, NULL); CAmount total = 0; CAmount nSelectedAmount = 0; for(const COutput& out : vecOutputs) { CTxDestination destAdress; const CScript& scriptPubKey = out.tx->tx->vout[out.i].scriptPubKey; bool fValidAddress = ExtractDestination(scriptPubKey, destAdress); CAmount val = out.tx->tx.get()->vout[out.i].nValue; if (!fValidAddress || address != destAdress || (val >= minValue && val <= maxValue ) ) continue; if(nSelectedAmount <= nRequiredAmount) { coin_control.Select(COutPoint(out.tx->GetHash(),out.i)); nSelectedAmount += val; } total += val; } CAmount splited = 0; UniValue obj(UniValue::VOBJ); if(coin_control.HasSelected() && nSplitAmount < nSelectedAmount){ EnsureWalletIsUnlocked(*pwallet); CTransactionRef tx = SplitUTXOs(pwallet, address, nSplitAmount, maxValue, coin_control, nSelectedAmount, maxOutputs, splited, !fPsbt); if(fPsbt){ // Make a blank psbt PartiallySignedTransaction psbtx; CMutableTransaction rawTx = CMutableTransaction(*tx); 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()); } // Fill transaction with out data but don't sign bool bip32derivs = true; bool complete = true; const TransactionError err = pwallet->FillPSBT(psbtx, complete, 1, false, bip32derivs); if (err != TransactionError::OK) { throw JSONRPCTransactionError(err); } // Serialize the PSBT CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); ssTx << psbtx; obj.pushKV("psbt", EncodeBase64(ssTx.str())); } else { obj.pushKV("txid", tx->GetHash().GetHex()); } } obj.pushKV("selected", FormatMoney(total)); obj.pushKV("splited", FormatMoney(splited)); return obj; }, }; } PSBTOutput GetPsbtOutput(const CTxOut& v, CWallet& wallet) { PSBTOutput out; if(v.scriptPubKey.HasOpSender()) { CScript senderPubKey; PKHash pkhash ; CPubKey vchPubKeyOut; KeyOriginInfo info; bool ok = GetSenderPubKey(v.scriptPubKey, senderPubKey); if(ok) { pkhash = ExtractPublicKeyHash(senderPubKey); ok &= pkhash != PKHash(); } if(ok) ok &= wallet.GetPubKey(pkhash, vchPubKeyOut); if(ok) ok &= wallet.GetKeyOrigin(pkhash, info); if(ok) out.hd_keypaths[vchPubKeyOut] = info; } return out; } void getDgpData(uint64_t& blockGasLimit, uint64_t& minGasPrice, CAmount& nGasPrice, int* pHeight = nullptr, ChainstateManager* chainman = nullptr) { static uint64_t BlockGasLimit = DEFAULT_BLOCK_GAS_LIMIT_DGP; static uint64_t MinGasPrice = DEFAULT_MIN_GAS_PRICE_DGP; blockGasLimit = BlockGasLimit; minGasPrice = MinGasPrice; if(globalState.get() && chainman) { LOCK(cs_main); QtumDGP qtumDGP(globalState.get(), chainman->ActiveChainstate(), fGettingValuesDGP); int height = chainman->ActiveChain().Height(); blockGasLimit = qtumDGP.getBlockGasLimit(height); minGasPrice = CAmount(qtumDGP.getMinGasPrice(height)); if(pHeight) *pHeight = height; BlockGasLimit = blockGasLimit; MinGasPrice = minGasPrice; } nGasPrice = (minGasPrice>DEFAULT_GAS_PRICE)?minGasPrice:DEFAULT_GAS_PRICE; } static RPCHelpMan createcontract() { uint64_t blockGasLimit = 0, minGasPrice = 0; CAmount nGasPrice = 0; getDgpData(blockGasLimit, minGasPrice, nGasPrice); return RPCHelpMan{"createcontract", "\nCreate a contract with bytcode." + HELP_REQUIRING_PASSPHRASE, { {"bytecode", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "contract bytcode."}, {"gaslimit", RPCArg::Type::AMOUNT, RPCArg::Optional::OMITTED_NAMED_ARG, "gasLimit, default: "+i64tostr(DEFAULT_GAS_LIMIT_OP_CREATE)+", max: "+i64tostr(blockGasLimit)}, {"gasprice", RPCArg::Type::AMOUNT, RPCArg::Optional::OMITTED_NAMED_ARG, "gasPrice QTUM price per gas unit, default: "+FormatMoney(nGasPrice)+", min:"+FormatMoney(minGasPrice)}, {"senderaddress", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED_NAMED_ARG, "The qtum address that will be used to create the contract."}, {"broadcast", RPCArg::Type::BOOL, RPCArg::Default{true}, "Whether to broadcast the transaction or not."}, {"changetosender", RPCArg::Type::BOOL, RPCArg::Default{true}, "Return the change to the sender."}, {"psbt", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED_NAMED_ARG, "Create partially signed transaction."}, }, { RPCResult{"if broadcast is set to true", RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR, "psbt", "The base64-encoded unsigned PSBT of the new transaction. Only returned when wallet private keys are disabled."}, {RPCResult::Type::STR_HEX, "txid", "The transaction id. Only returned when wallet private keys are enabled."}, {RPCResult::Type::STR, "sender", CURRENCY_UNIT + " address of the sender"}, {RPCResult::Type::STR_HEX, "hash160", "Ripemd-160 hash of the sender"}, {RPCResult::Type::STR, "address", "Expected contract address"} }, }, RPCResult{"if broadcast is set to false", RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR_HEX, "raw transaction", "The hex string of the raw transaction"} }, }, }, RPCExamples{ HelpExampleCli("createcontract", "\"60606040525b33600060006101000a81548173ffffffffffffffffffffffffffffffffffffffff02191690836c010000000000000000000000009081020402179055506103786001600050819055505b600c80605b6000396000f360606040526008565b600256\"") + HelpExampleCli("createcontract", "\"60606040525b33600060006101000a81548173ffffffffffffffffffffffffffffffffffffffff02191690836c010000000000000000000000009081020402179055506103786001600050819055505b600c80605b6000396000f360606040526008565b600256\" 6000000 "+FormatMoney(minGasPrice)+" \"QM72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" true") }, [&,blockGasLimit,minGasPrice,nGasPrice](const RPCHelpMan& self, const JSONRPCRequest& request) mutable -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); if (!pwallet) return NullUniValue; ChainstateManager& chainman = pwallet->chain().chainman(); int height = 0; getDgpData(blockGasLimit, minGasPrice, nGasPrice, &height, &chainman); LOCK(pwallet->cs_wallet); std::string bytecode=request.params[0].get_str(); if(bytecode.size() % 2 != 0 || !CheckHex(bytecode)) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid data (data not hex)"); uint64_t nGasLimit=DEFAULT_GAS_LIMIT_OP_CREATE; if (!request.params[1].isNull()){ nGasLimit = request.params[1].get_int64(); if (nGasLimit > blockGasLimit) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid value for gasLimit (Maximum is: "+i64tostr(blockGasLimit)+")"); if (nGasLimit < MINIMUM_GAS_LIMIT) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid value for gasLimit (Minimum is: "+i64tostr(MINIMUM_GAS_LIMIT)+")"); if (nGasLimit <= 0) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid value for gasLimit"); } if (!request.params[2].isNull()){ nGasPrice = AmountFromValue(request.params[2]); if (nGasPrice <= 0) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid value for gasPrice"); CAmount maxRpcGasPrice = gArgs.GetArg("-rpcmaxgasprice", MAX_RPC_GAS_PRICE); if (nGasPrice > (int64_t)maxRpcGasPrice) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid value for gasPrice, Maximum allowed in RPC calls is: "+FormatMoney(maxRpcGasPrice)+" (use -rpcmaxgasprice to change it)"); if (nGasPrice < (int64_t)minGasPrice) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid value for gasPrice (Minimum is: "+FormatMoney(minGasPrice)+")"); } bool fHasSender=false; CTxDestination senderAddress; if (!request.params[3].isNull()){ senderAddress = DecodeDestination(request.params[3].get_str()); if (!IsValidDestination(senderAddress)) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Qtum address to send from"); if (!IsValidContractSenderAddress(senderAddress)) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid contract sender address. Only P2PK and P2PKH allowed"); else fHasSender=true; } bool fBroadcast=true; if (!request.params[4].isNull()){ fBroadcast=request.params[4].get_bool(); } bool fChangeToSender=true; if (!request.params[5].isNull()){ fChangeToSender=request.params[5].get_bool(); } bool fPsbt=pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); if (!request.params[6].isNull()){ fPsbt=request.params[6].get_bool(); } if(fPsbt) fBroadcast=false; CCoinControl coinControl; if(fPsbt) coinControl.fAllowWatchOnly = true; CTxDestination signSenderAddress = CNoDestination(); if(fHasSender){ // Find a UTXO with sender address std::vector<COutput> vecOutputs; coinControl.fAllowOtherInputs=true; assert(pwallet != NULL); pwallet->AvailableCoins(vecOutputs, NULL); for (const COutput& out : vecOutputs) { CTxDestination destAdress; const CScript& scriptPubKey = out.tx->tx->vout[out.i].scriptPubKey; bool fValidAddress = out.fSpendable && ExtractDestination(scriptPubKey, destAdress); if (!fValidAddress || senderAddress != destAdress) continue; coinControl.Select(COutPoint(out.tx->GetHash(),out.i)); break; } if(coinControl.HasSelected()) { // Change to the sender if(fChangeToSender){ coinControl.destChange=senderAddress; } } else { // Create op sender transaction when op sender is activated if(!(height >= Params().GetConsensus().QIP5Height)) throw JSONRPCError(RPC_TYPE_ERROR, "Sender address does not have any unspent outputs"); } if(height >= Params().GetConsensus().QIP5Height) { // Set the sender address signSenderAddress = senderAddress; } } else { if(height >= Params().GetConsensus().QIP5Height) { // If no sender address provided set to the default sender address SetDefaultSignSenderAddress(*pwallet, signSenderAddress, coinControl); } } EnsureWalletIsUnlocked(*pwallet); CAmount nGasFee=nGasPrice*nGasLimit; const auto bal = pwallet->GetBalance(); CAmount curBalance = bal.m_mine_trusted; if(fPsbt) curBalance += bal.m_watchonly_trusted; // Check amount if (nGasFee <= 0) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid amount"); if (nGasFee > curBalance) throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds"); // Select default coin that will pay for the contract if none selected if(!coinControl.HasSelected() && !SetDefaultPayForContractAddress(*pwallet, coinControl)) throw JSONRPCError(RPC_TYPE_ERROR, "Does not have any P2PK or P2PKH unspent outputs to pay for the contract."); // Build OP_EXEC script CScript scriptPubKey = CScript() << CScriptNum(VersionVM::GetEVMDefault().toRaw()) << CScriptNum(nGasLimit) << CScriptNum(nGasPrice) << ParseHex(bytecode) <<OP_CREATE; if(height >= Params().GetConsensus().QIP5Height) { if(IsValidDestination(signSenderAddress)) { if (!pwallet->HasPrivateKey(signSenderAddress, coinControl.fAllowWatchOnly)) { throw JSONRPCError(RPC_WALLET_ERROR, "Private key not available"); } CKeyID key_id = pwallet->GetKeyForDestination(signSenderAddress); std::vector<unsigned char> scriptSig; scriptPubKey = (CScript() << CScriptNum(addresstype::PUBKEYHASH) << ToByteVector(key_id) << ToByteVector(scriptSig) << OP_SENDER) + scriptPubKey; } else { // OP_SENDER will always be used when QIP5Height is active throw JSONRPCError(RPC_TYPE_ERROR, "Sender address fail to set for OP_SENDER."); } } // Create and send the transaction CAmount nFeeRequired; bilingual_str error; std::string strError; std::vector<CRecipient> vecSend; int nChangePosRet = -1; CRecipient recipient = {scriptPubKey, 0, false}; vecSend.push_back(recipient); bool sign = !fPsbt; CTransactionRef tx; FeeCalculation fee_calc_out; if (!pwallet->CreateTransaction(vecSend, tx, nFeeRequired, nChangePosRet, error, coinControl, fee_calc_out, sign, nGasFee, true, signSenderAddress)) { if (nFeeRequired > curBalance) strError = strprintf("Error: This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds!", FormatMoney(nFeeRequired)); else strError = error.original; throw JSONRPCError(RPC_WALLET_ERROR, strError); } CTxDestination txSenderDest; pwallet->GetSenderDest(*tx, txSenderDest, sign); if (fHasSender && !(senderAddress == txSenderDest)){ throw JSONRPCError(RPC_TYPE_ERROR, "Sender could not be set, transaction was not committed!"); } UniValue result(UniValue::VOBJ); if(fPsbt){ // Make a blank psbt PartiallySignedTransaction psbtx; CMutableTransaction rawTx = CMutableTransaction(*tx); 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(GetPsbtOutput(rawTx.vout[i], *pwallet)); } // Fill transaction with out data but don't sign bool bip32derivs = true; bool complete = true; const TransactionError err = pwallet->FillPSBT(psbtx, complete, 1, false, bip32derivs); if (err != TransactionError::OK) { throw JSONRPCTransactionError(err); } // Serialize the PSBT CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); ssTx << psbtx; result.pushKV("psbt", EncodeBase64(ssTx.str())); // Add sender information CTxDestination txSenderAdress(txSenderDest); CKeyID keyid = pwallet->GetKeyForDestination(txSenderAdress); result.pushKV("sender", EncodeDestination(txSenderAdress)); result.pushKV("hash160", HexStr(valtype(keyid.begin(),keyid.end()))); } else if(fBroadcast){ pwallet->CommitTransaction(tx, {}, {}); std::string txId=tx->GetHash().GetHex(); result.pushKV("txid", txId); CTxDestination txSenderAdress(txSenderDest); CKeyID keyid = pwallet->GetKeyForDestination(txSenderAdress); result.pushKV("sender", EncodeDestination(txSenderAdress)); result.pushKV("hash160", HexStr(valtype(keyid.begin(),keyid.end()))); std::vector<unsigned char> SHA256TxVout(32); std::vector<unsigned char> contractAddress(20); std::vector<unsigned char> txIdAndVout(tx->GetHash().begin(), tx->GetHash().end()); uint32_t voutNumber=0; for (const CTxOut& txout : tx->vout) { if(txout.scriptPubKey.HasOpCreate()){ std::vector<unsigned char> voutNumberChrs; if (voutNumberChrs.size() < sizeof(voutNumber))voutNumberChrs.resize(sizeof(voutNumber)); std::memcpy(voutNumberChrs.data(), &voutNumber, sizeof(voutNumber)); txIdAndVout.insert(txIdAndVout.end(),voutNumberChrs.begin(),voutNumberChrs.end()); break; } voutNumber++; } CSHA256().Write(txIdAndVout.data(), txIdAndVout.size()).Finalize(SHA256TxVout.data()); CRIPEMD160().Write(SHA256TxVout.data(), SHA256TxVout.size()).Finalize(contractAddress.data()); result.pushKV("address", HexStr(contractAddress)); }else{ std::string strHex = EncodeHexTx(*tx, RPCSerializationFlags()); result.pushKV("raw transaction", strHex); } return result; }, }; } UniValue SendToContract(CWallet& wallet, const UniValue& params, ChainstateManager& chainman) { uint64_t blockGasLimit = 0, minGasPrice = 0; CAmount nGasPrice = 0; int height = 0; getDgpData(blockGasLimit, minGasPrice, nGasPrice, &height, &chainman); std::string contractaddress = params[0].get_str(); if(contractaddress.size() != 40 || !CheckHex(contractaddress)) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Incorrect contract address"); dev::Address addrAccount(contractaddress); { LOCK(cs_main); if(!globalState->addressInUse(addrAccount)) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "contract address does not exist"); } std::string datahex = params[1].get_str(); if(datahex.size() % 2 != 0 || !CheckHex(datahex)) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid data (data not hex)"); CAmount nAmount = 0; if (!params[2].isNull()){ nAmount = AmountFromValue(params[2]); if (nAmount < 0) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); } uint64_t nGasLimit=DEFAULT_GAS_LIMIT_OP_SEND; if (!params[3].isNull()){ nGasLimit = params[3].get_int64(); if (nGasLimit > blockGasLimit) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid value for gasLimit (Maximum is: "+i64tostr(blockGasLimit)+")"); if (nGasLimit < MINIMUM_GAS_LIMIT) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid value for gasLimit (Minimum is: "+i64tostr(MINIMUM_GAS_LIMIT)+")"); if (nGasLimit <= 0) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid value for gasLimit"); } if (!params[4].isNull()){ nGasPrice = AmountFromValue(params[4]); if (nGasPrice <= 0) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid value for gasPrice"); CAmount maxRpcGasPrice = gArgs.GetArg("-rpcmaxgasprice", MAX_RPC_GAS_PRICE); if (nGasPrice > (int64_t)maxRpcGasPrice) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid value for gasPrice, Maximum allowed in RPC calls is: "+FormatMoney(maxRpcGasPrice)+" (use -rpcmaxgasprice to change it)"); if (nGasPrice < (int64_t)minGasPrice) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid value for gasPrice (Minimum is: "+FormatMoney(minGasPrice)+")"); } bool fHasSender=false; CTxDestination senderAddress; if (!params[5].isNull()){ senderAddress = DecodeDestination(params[5].get_str()); if (!IsValidDestination(senderAddress)) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Qtum address to send from"); if (!IsValidContractSenderAddress(senderAddress)) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid contract sender address. Only P2PK and P2PKH allowed"); else fHasSender=true; } bool fBroadcast=true; if (!params[6].isNull()){ fBroadcast=params[6].get_bool(); } bool fChangeToSender=true; if (!params[7].isNull()){ fChangeToSender=params[7].get_bool(); } bool fPsbt=wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); if (!params[8].isNull()){ fPsbt=params[8].get_bool(); } if(fPsbt) fBroadcast=false; CCoinControl coinControl; if(fPsbt) coinControl.fAllowWatchOnly = true; CTxDestination signSenderAddress = CNoDestination(); if(fHasSender){ // Find a UTXO with sender address std::vector<COutput> vecOutputs; coinControl.fAllowOtherInputs=true; wallet.AvailableCoins(vecOutputs, NULL); for (const COutput& out : vecOutputs) { CTxDestination destAdress; const CScript& scriptPubKey = out.tx->tx->vout[out.i].scriptPubKey; bool fValidAddress = out.fSpendable && ExtractDestination(scriptPubKey, destAdress); if (!fValidAddress || senderAddress != destAdress) continue; coinControl.Select(COutPoint(out.tx->GetHash(),out.i)); break; } if(coinControl.HasSelected()) { // Change to the sender if(fChangeToSender){ coinControl.destChange=senderAddress; } } else { // Create op sender transaction when op sender is activated if(!(height >= Params().GetConsensus().QIP5Height)) throw JSONRPCError(RPC_TYPE_ERROR, "Sender address does not have any unspent outputs"); } if(height >= Params().GetConsensus().QIP5Height) { // Set the sender address signSenderAddress = senderAddress; } } else { if(height >= Params().GetConsensus().QIP5Height) { // If no sender address provided set to the default sender address SetDefaultSignSenderAddress(wallet, signSenderAddress, coinControl); } } EnsureWalletIsUnlocked(wallet); CAmount nGasFee=nGasPrice*nGasLimit; const auto bal = wallet.GetBalance(); CAmount curBalance = bal.m_mine_trusted; if(fPsbt) curBalance += bal.m_watchonly_trusted; // Check amount if (nGasFee <= 0) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid amount for gas fee"); if (nAmount+nGasFee > curBalance) throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds"); // Select default coin that will pay for the contract if none selected if(!coinControl.HasSelected() && !SetDefaultPayForContractAddress(wallet, coinControl)) throw JSONRPCError(RPC_TYPE_ERROR, "Does not have any P2PK or P2PKH unspent outputs to pay for the contract."); // Build OP_EXEC_ASSIGN script CScript scriptPubKey = CScript() << CScriptNum(VersionVM::GetEVMDefault().toRaw()) << CScriptNum(nGasLimit) << CScriptNum(nGasPrice) << ParseHex(datahex) << ParseHex(contractaddress) << OP_CALL; if(height >= Params().GetConsensus().QIP5Height) { if(IsValidDestination(signSenderAddress)) { if (!wallet.HasPrivateKey(signSenderAddress, coinControl.fAllowWatchOnly)) { throw JSONRPCError(RPC_WALLET_ERROR, "Private key not available"); } CKeyID key_id = wallet.GetKeyForDestination(signSenderAddress); std::vector<unsigned char> scriptSig; scriptPubKey = (CScript() << CScriptNum(addresstype::PUBKEYHASH) << ToByteVector(key_id) << ToByteVector(scriptSig) << OP_SENDER) + scriptPubKey; } else { // OP_SENDER will always be used when QIP5Height is active throw JSONRPCError(RPC_TYPE_ERROR, "Sender address fail to set for OP_SENDER."); } } // Create and send the transaction CAmount nFeeRequired; bilingual_str error; std::string strError; std::vector<CRecipient> vecSend; int nChangePosRet = -1; CRecipient recipient = {scriptPubKey, nAmount, false}; vecSend.push_back(recipient); bool sign = !fPsbt; CTransactionRef tx; FeeCalculation fee_calc_out; if (!wallet.CreateTransaction(vecSend, tx, nFeeRequired, nChangePosRet, error, coinControl, fee_calc_out, sign, nGasFee, true, signSenderAddress)) { if (nFeeRequired > curBalance) strError = strprintf("Error: This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds!", FormatMoney(nFeeRequired)); else strError = error.original; throw JSONRPCError(RPC_WALLET_ERROR, strError); } CTxDestination txSenderDest; wallet.GetSenderDest(*tx, txSenderDest, sign); if (fHasSender && !(senderAddress == txSenderDest)){ throw JSONRPCError(RPC_TYPE_ERROR, "Sender could not be set, transaction was not committed!"); } UniValue result(UniValue::VOBJ); if(fPsbt){ // Make a blank psbt PartiallySignedTransaction psbtx; CMutableTransaction rawTx = CMutableTransaction(*tx); 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(GetPsbtOutput(rawTx.vout[i], wallet)); } // Fill transaction with out data but don't sign bool bip32derivs = true; bool complete = true; const TransactionError err = wallet.FillPSBT(psbtx, complete, 1, false, bip32derivs); if (err != TransactionError::OK) { throw JSONRPCTransactionError(err); } // Serialize the PSBT CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); ssTx << psbtx; result.pushKV("psbt", EncodeBase64(ssTx.str())); // Add sender information CTxDestination txSenderAdress(txSenderDest); CKeyID keyid = wallet.GetKeyForDestination(txSenderAdress); result.pushKV("sender", EncodeDestination(txSenderAdress)); result.pushKV("hash160", HexStr(valtype(keyid.begin(),keyid.end()))); } else if(fBroadcast){ wallet.CommitTransaction(tx, {}, {}); std::string txId=tx->GetHash().GetHex(); result.pushKV("txid", txId); CTxDestination txSenderAdress(txSenderDest); CKeyID keyid = wallet.GetKeyForDestination(txSenderAdress); result.pushKV("sender", EncodeDestination(txSenderAdress)); result.pushKV("hash160", HexStr(valtype(keyid.begin(),keyid.end()))); }else{ std::string strHex = EncodeHexTx(*tx, RPCSerializationFlags()); result.pushKV("raw transaction", strHex); } return result; } /** * @brief The SendToken class Write token data */ class SendToken : public CallToken { public: SendToken(CWallet& _wallet, ChainstateManager& _chainman): CallToken(_chainman), wallet(_wallet) {} bool execValid(const int& func, const bool& sendTo) override { return sendTo ? func != -1 : CallToken::execValid(func, sendTo); } bool exec(const bool& sendTo, const std::map<std::string, std::string>& lstParams, std::string& result, std::string& message) override { if(!sendTo) return CallToken::exec(sendTo, lstParams, result, message); UniValue params(UniValue::VARR); // Set address auto it = lstParams.find(paramAddress()); if(it != lstParams.end()) params.push_back(it->second); else return false; // Set data it = lstParams.find(paramDatahex()); if(it != lstParams.end()) params.push_back(it->second); else return false; // Set amount it = lstParams.find(paramAmount()); if(it != lstParams.end()) { if(params.size() == 2) params.push_back(it->second); else return false; } // Set gas limit it = lstParams.find(paramGasLimit()); if(it != lstParams.end()) { if(params.size() == 3) { UniValue param(UniValue::VNUM); param.setInt(atoi64(it->second)); params.push_back(param); } else return false; } // Set gas price it = lstParams.find(paramGasPrice()); if(it != lstParams.end()) { if(params.size() == 4) params.push_back(it->second); else return false; } // Set sender it = lstParams.find(paramSender()); if(it != lstParams.end()) { if(params.size() == 5) params.push_back(it->second); else return false; } // Set broadcast it = lstParams.find(paramBroadcast()); if(it != lstParams.end()) { if(params.size() == 6) { bool val = it->second == "true" ? true : false; UniValue param(UniValue::VBOOL); param.setBool(val); params.push_back(param); } else return false; } // Set change to sender it = lstParams.find(paramChangeToSender()); if(it != lstParams.end()) { if(params.size() == 7) { bool val = it->second == "true" ? true : false; UniValue param(UniValue::VBOOL); param.setBool(val); params.push_back(param); } else return false; } // Set psbt it = lstParams.find(paramPsbt()); if(it != lstParams.end()) { if(params.size() == 8) { bool val = it->second == "true" ? true : false; UniValue param(UniValue::VBOOL); param.setBool(val); params.push_back(param); } else return false; } // Get execution result UniValue response = SendToContract(wallet, params, chainman); if(!response.isObject()) return false; if(privateKeysDisabled()) { if(!response.exists("psbt")) return false; } else { if(!response.exists("txid")) return false; } result = privateKeysDisabled() ? response["psbt"].get_str() : response["txid"].get_str(); return true; } bool privateKeysDisabled() override { return wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); } private: CWallet& wallet; }; static RPCHelpMan sendtocontract() { uint64_t blockGasLimit = 0, minGasPrice = 0; CAmount nGasPrice = 0; getDgpData(blockGasLimit, minGasPrice, nGasPrice); return RPCHelpMan{"sendtocontract", "\nSend funds and data to a contract." + HELP_REQUIRING_PASSPHRASE, { {"contractaddress", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The contract address that will receive the funds and data."}, {"datahex", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "data to send."}, {"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::OMITTED_NAMED_ARG, "The amount in " + CURRENCY_UNIT + " to send. eg 0.1, default: 0"}, {"gaslimit", RPCArg::Type::AMOUNT, RPCArg::Optional::OMITTED_NAMED_ARG, "gasLimit, default: "+i64tostr(DEFAULT_GAS_LIMIT_OP_SEND)+", max: "+i64tostr(blockGasLimit)}, {"gasprice", RPCArg::Type::AMOUNT, RPCArg::Optional::OMITTED_NAMED_ARG, "gasPrice Qtum price per gas unit, default: "+FormatMoney(nGasPrice)+", min:"+FormatMoney(minGasPrice)}, {"senderaddress", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED_NAMED_ARG, "The qtum address that will be used as sender."}, {"broadcast", RPCArg::Type::BOOL, RPCArg::Default{true}, "Whether to broadcast the transaction or not."}, {"changetosender", RPCArg::Type::BOOL, RPCArg::Default{true}, "Return the change to the sender."}, {"psbt", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED_NAMED_ARG, "Create partially signed transaction."}, }, { RPCResult{"if broadcast is set to true", RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR, "psbt", "The base64-encoded unsigned PSBT of the new transaction. Only returned when wallet private keys are disabled."}, {RPCResult::Type::STR_HEX, "txid", "The transaction id. Only returned when wallet private keys are enabled."}, {RPCResult::Type::STR, "sender", CURRENCY_UNIT + " address of the sender"}, {RPCResult::Type::STR_HEX, "hash160", "Ripemd-160 hash of the sender"} }, }, RPCResult{"if broadcast is set to false", RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR_HEX, "raw transaction", "The hex string of the raw transaction"} }, }, }, RPCExamples{ HelpExampleCli("sendtocontract", "\"c6ca2697719d00446d4ea51f6fac8fd1e9310214\" \"54f6127f\"") + HelpExampleCli("sendtocontract", "\"c6ca2697719d00446d4ea51f6fac8fd1e9310214\" \"54f6127f\" 12.0015 6000000 "+FormatMoney(minGasPrice)+" \"QM72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\"") }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); if (!pwallet) return NullUniValue; ChainstateManager& chainman = pwallet->chain().chainman(); LOCK(pwallet->cs_wallet); return SendToContract(*pwallet, request.params, chainman); }, }; } static RPCHelpMan removedelegationforaddress() { uint64_t blockGasLimit = 0, minGasPrice = 0; CAmount nGasPrice = 0; getDgpData(blockGasLimit, minGasPrice, nGasPrice); return RPCHelpMan{"removedelegationforaddress", "\nRemove delegation for address." + HELP_REQUIRING_PASSPHRASE, { {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The qtum address to remove delegation, the address will be used as sender too."}, {"gaslimit", RPCArg::Type::AMOUNT, RPCArg::Optional::OMITTED_NAMED_ARG, "gasLimit, default: "+i64tostr(DEFAULT_GAS_LIMIT_OP_SEND)+", max: "+i64tostr(blockGasLimit)}, {"gasprice", RPCArg::Type::AMOUNT, RPCArg::Optional::OMITTED_NAMED_ARG, "gasPrice Qtum price per gas unit, default: "+FormatMoney(nGasPrice)+", min:"+FormatMoney(minGasPrice)}, }, RPCResult{ RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR, "psbt", "The base64-encoded unsigned PSBT of the new transaction. Only returned when wallet private keys are disabled."}, {RPCResult::Type::STR_HEX, "txid", "The transaction id. Only returned when wallet private keys are enabled."}, {RPCResult::Type::STR, "sender", CURRENCY_UNIT + " address of the sender"}, {RPCResult::Type::STR_HEX, "hash160", "Ripemd-160 hash of the sender"} } }, RPCExamples{ HelpExampleCli("removedelegationforaddress", " \"QM72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 6000000 "+FormatMoney(minGasPrice)) }, [&,nGasPrice](const RPCHelpMan& self, const JSONRPCRequest& request) mutable -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); if (!pwallet) return NullUniValue; ChainstateManager& chainman = pwallet->chain().chainman(); LOCK(pwallet->cs_wallet); // Get send to contract parameters for removing delegation for address UniValue params(UniValue::VARR); UniValue contractaddress = HexStr(Params().GetConsensus().delegationsAddress); UniValue datahex = QtumDelegation::BytecodeRemove(); UniValue amount = 0; UniValue gasLimit = !request.params[1].isNull() ? request.params[1] : DEFAULT_GAS_LIMIT_OP_SEND; UniValue gasPrice = !request.params[2].isNull() ? request.params[2] : FormatMoney(nGasPrice); UniValue senderaddress = request.params[0]; // Add the send to contract parameters to the list params.push_back(contractaddress); params.push_back(datahex); params.push_back(amount); params.push_back(gasLimit); params.push_back(gasPrice); params.push_back(senderaddress); // Send to contract return SendToContract(*pwallet, params, chainman); }, }; } static RPCHelpMan setdelegateforaddress() { uint64_t blockGasLimit = 0, minGasPrice = 0; CAmount nGasPrice = 0; getDgpData(blockGasLimit, minGasPrice, nGasPrice); return RPCHelpMan{"setdelegateforaddress", "\nSet delegate for address." + HELP_REQUIRING_PASSPHRASE, { {"staker", RPCArg::Type::STR, RPCArg::Optional::NO, "The qtum address for the staker."}, {"fee", RPCArg::Type::NUM, RPCArg::Optional::NO, "Percentage of the reward that will be paid to the staker."}, {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The qtum address that contain the coins that will be delegated to the staker, the address will be used as sender too."}, {"gaslimit", RPCArg::Type::AMOUNT, RPCArg::Optional::OMITTED_NAMED_ARG, "gasLimit, default: "+i64tostr(DEFAULT_GAS_LIMIT_OP_CREATE)+", max: "+i64tostr(blockGasLimit)}, {"gasprice", RPCArg::Type::AMOUNT, RPCArg::Optional::OMITTED_NAMED_ARG, "gasPrice Qtum price per gas unit, default: "+FormatMoney(nGasPrice)+", min:"+FormatMoney(minGasPrice)}, }, RPCResult{ RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR, "psbt", "The base64-encoded unsigned PSBT of the new transaction. Only returned when wallet private keys are disabled."}, {RPCResult::Type::STR_HEX, "txid", "The transaction id. Only returned when wallet private keys are enabled."}, {RPCResult::Type::STR, "sender", CURRENCY_UNIT + " address of the sender"}, {RPCResult::Type::STR_HEX, "hash160", "Ripemd-160 hash of the sender"} } }, RPCExamples{ HelpExampleCli("setdelegateforaddress", " \"QM72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 10 \"QX1GkJdye9WoUnrE2v6ZQhQ72EUVDtGXQX\" 6000000 "+FormatMoney(minGasPrice)) }, [&,nGasPrice](const RPCHelpMan& self, const JSONRPCRequest& request) mutable -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); if (!pwallet) return NullUniValue; ChainstateManager& chainman = pwallet->chain().chainman(); LOCK(pwallet->cs_wallet); // Get send to contract parameters for add delegation for address UniValue params(UniValue::VARR); UniValue contractaddress = HexStr(Params().GetConsensus().delegationsAddress); UniValue amount = 0; UniValue gasLimit = !request.params[3].isNull() ? request.params[3] : DEFAULT_GAS_LIMIT_OP_CREATE; UniValue gasPrice = !request.params[4].isNull() ? request.params[4] : FormatMoney(nGasPrice); UniValue senderaddress = request.params[2]; bool fPsbt=pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); // Parse the staker address CTxDestination destStaker = DecodeDestination(request.params[0].get_str()); if (!std::holds_alternative<PKHash>(destStaker)) { throw JSONRPCError(RPC_TYPE_ERROR, "Invalid contract address for staker. Only P2PK and P2PKH allowed"); } PKHash pkhStaker = std::get<PKHash>(destStaker); // Parse the staker fee int fee = request.params[1].get_int(); if(fee < 0 || fee > 100) throw JSONRPCError(RPC_PARSE_ERROR, "The staker fee need to be between 0 and 100"); // Parse the sender address CTxDestination destSender = DecodeDestination(senderaddress.get_str()); if (!std::holds_alternative<PKHash>(destSender)) { throw JSONRPCError(RPC_TYPE_ERROR, "Invalid contract sender address. Only P2PK and P2PKH allowed"); } PKHash pkhSender = std::get<PKHash>(destSender); // Get the private key for the sender address if (!pwallet->HasPrivateKey(destSender, fPsbt)) { throw JSONRPCError(RPC_WALLET_ERROR, "Private key not available for the sender address"); } // Sign the staker address std::vector<unsigned char> PoD; std::string hexStaker = ToKeyID(pkhStaker).GetReverseHex(); if(fPsbt) { PoD.insert(PoD.end(), hexStaker.begin(), hexStaker.end()); PoD.resize(CPubKey::COMPACT_SIGNATURE_SIZE, 0); } else { std::string str_sig; SigningResult res = pwallet->SignMessage(hexStaker, pkhSender, str_sig); if(res == SigningResult::PRIVATE_KEY_NOT_AVAILABLE) { throw JSONRPCError(RPC_WALLET_ERROR, "Private key not available for the sender address"); } if(res == SigningResult::SIGNING_FAILED) { throw JSONRPCError(RPC_WALLET_ERROR, "Fail to sign the staker address"); } PoD = DecodeBase64(str_sig.c_str()); } // Serialize the data std::string datahex; std::string errorMessage; if(!QtumDelegation::BytecodeAdd(hexStaker, fee, PoD, datahex, errorMessage)) throw JSONRPCError(RPC_TYPE_ERROR, errorMessage); // Add the send to contract parameters to the list params.push_back(contractaddress); params.push_back(datahex); params.push_back(amount); params.push_back(gasLimit); params.push_back(gasPrice); params.push_back(senderaddress); // Send to contract return SendToContract(*pwallet, params, chainman); }, }; } UniValue GetJsonSuperStakerConfig(const CSuperStakerInfo& superStaker) { // Fill the json object with information UniValue result(UniValue::VOBJ); result.pushKV("address", EncodeDestination(PKHash(superStaker.stakerAddress))); result.pushKV("customconfig", superStaker.fCustomConfig); if(superStaker.fCustomConfig) { result.pushKV("stakingminfee", (int64_t)superStaker.nMinFee); result.pushKV("stakingminutxovalue", FormatMoney(superStaker.nMinDelegateUtxo)); UniValue addressList(UniValue::VARR); for(uint160 address : superStaker.delegateAddressList) { addressList.push_back(EncodeDestination(PKHash(address))); } if(interfaces::AllowList == superStaker.nDelegateAddressType) { result.pushKV("allow", addressList); } if(interfaces::ExcludeList == superStaker.nDelegateAddressType) { result.pushKV("exclude", addressList); } } return result; } static RPCHelpMan setsuperstakervaluesforaddress() { return RPCHelpMan{"setsuperstakervaluesforaddress", "\nList super staker configuration values for address." + HELP_REQUIRING_PASSPHRASE, { {"params", RPCArg::Type::OBJ, RPCArg::Optional::NO, "", { {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The address of the staker"}, {"stakingminutxovalue", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The output number"}, {"stakingminfee", RPCArg::Type::NUM, RPCArg::Optional::NO, "depends on the value of the 'replaceable' and 'locktime' arguments", "The sequence number"}, {"allow", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "A json array with allow delegate addresses.", { {"address", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The delegate address"}, }, }, {"exclude", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "A json array with exclude delegate addresses.", { {"address", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The delegate address"}, }, }, }, }, }, RPCResult{ RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR, "address", "Address of the staker."}, {RPCResult::Type::BOOL, "customconfig", "Custom configuration exist."}, {RPCResult::Type::NUM, "stakingminfee", true, "Minimum fee for delegate."}, {RPCResult::Type::NUM, "stakingminutxovalue", true, "Minimum UTXO value for delegate."}, {RPCResult::Type::ARR, "allow", true, "List of allowed delegate addresses.", { {RPCResult::Type::STR, "address", "The delegate address"}, }, }, {RPCResult::Type::ARR, "exclude", true, "List of excluded delegate addresses.", { {RPCResult::Type::STR, "address", "The delegate address"}, }, } } }, RPCExamples{ HelpExampleCli("setsuperstakervaluesforaddress", "\"{\\\"address\\\":\\\"QM72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\\\",\\\"stakingminutxovalue\\\": \\\"100\\\",\\\"stakingminfee\\\": 10}\"") + HelpExampleCli("setsuperstakervaluesforaddress", "\"{\\\"address\\\":\\\"QM72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\\\",\\\"stakingminutxovalue\\\": \\\"100\\\",\\\"stakingminfee\\\": 10,\\\"allow\\\":[\\\"QD1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\"]}\"") + HelpExampleCli("setsuperstakervaluesforaddress", "\"{\\\"address\\\":\\\"QM72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\\\",\\\"stakingminutxovalue\\\": \\\"100\\\",\\\"stakingminfee\\\": 10,\\\"exclude\\\":[\\\"QD1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\"]}\"") + HelpExampleRpc("setsuperstakervaluesforaddress", "\"{\\\"address\\\":\\\"QM72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\\\",\\\"stakingminutxovalue\\\": \\\"100\\\",\\\"stakingminfee\\\": 10}\"") + HelpExampleRpc("setsuperstakervaluesforaddress", "\"{\\\"address\\\":\\\"QM72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\\\",\\\"stakingminutxovalue\\\": \\\"100\\\",\\\"stakingminfee\\\": 10,\\\"allow\\\":[\\\"QD1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\"]}\"") + HelpExampleRpc("setsuperstakervaluesforaddress", "\"{\\\"address\\\":\\\"QM72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\\\",\\\"stakingminutxovalue\\\": \\\"100\\\",\\\"stakingminfee\\\": 10,\\\"exclude\\\":[\\\"QD1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\"]}\"") }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); if (!pwallet) return NullUniValue; LOCK(pwallet->cs_wallet); // Get params for the super staker UniValue params(UniValue::VOBJ); params = request.params[0].get_obj(); // Parse the super staker address if(!params.exists("address")) throw JSONRPCError(RPC_TYPE_ERROR, "The super staker address doesn't exist"); CTxDestination destStaker = DecodeDestination(params["address"].get_str()); if (!std::holds_alternative<PKHash>(destStaker)) { throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address for staker. Only P2PK and P2PKH allowed"); } PKHash pkhStaker = std::get<PKHash>(destStaker); // Parse the staking min utxo value if(!params.exists("stakingminutxovalue")) throw JSONRPCError(RPC_TYPE_ERROR, "The staking min utxo value doesn't exist"); CAmount nMinUtxoValue = AmountFromValue(params["stakingminutxovalue"]); if (nMinUtxoValue < 0) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for staking min utxo value"); // Parse the staking min fee if(!params.exists("stakingminfee")) throw JSONRPCError(RPC_TYPE_ERROR, "The staking min fee doesn't exist"); CAmount nMinFee = params["stakingminfee"].get_int(); if (nMinFee < 0 || nMinFee > 100) throw JSONRPCError(RPC_TYPE_ERROR, "Invalid value for staking min fee"); // Parse the delegation address lists if(params.exists("allow") && params.exists("exclude")) throw JSONRPCError(RPC_TYPE_ERROR, "The delegation address lists can be empty, or have either allow list or exclude list"); // Parse the delegation address lists int nDelegateAddressType = interfaces::AcceptAll; std::vector<UniValue> addressList; if(params.exists("allow")) { addressList = params["allow"].get_array().getValues(); nDelegateAddressType = interfaces::AllowList; } else if(params.exists("exclude")) { addressList = params["exclude"].get_array().getValues(); nDelegateAddressType = interfaces::ExcludeList; } std::vector<uint160> delegateAddressList; for(UniValue address : addressList) { CTxDestination destAddress = DecodeDestination(address.get_str()); if (!std::holds_alternative<PKHash>(destAddress)) { throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address for delegate in allow list or exclude list. Only P2PK and P2PKH allowed"); } PKHash pkhAddress = std::get<PKHash>(destAddress); delegateAddressList.push_back(uint160(pkhAddress)); } // Search for super staker CSuperStakerInfo superStaker; bool found = false; for(auto item : pwallet->mapSuperStaker) { if(PKHash(item.second.stakerAddress) == pkhStaker) { superStaker = item.second; found = true; break; } } if(found) { // Set custom configuration superStaker.fCustomConfig = true; superStaker.nMinFee = nMinFee; superStaker.nMinDelegateUtxo = nMinUtxoValue; superStaker.nDelegateAddressType = nDelegateAddressType; superStaker.delegateAddressList = delegateAddressList; // Update super staker data if(!pwallet->AddSuperStakerEntry(superStaker)) throw JSONRPCError(RPC_TYPE_ERROR, "Failed to update the super staker"); } else { throw JSONRPCError(RPC_TYPE_ERROR, "Failed to find the super staker"); } return GetJsonSuperStakerConfig(superStaker); }, }; } static RPCHelpMan listsuperstakercustomvalues() { return RPCHelpMan{"listsuperstakercustomvalues", "\nList custom super staker configurations values." + HELP_REQUIRING_PASSPHRASE, {}, RPCResult{ RPCResult::Type::ARR, "", "", { {RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR, "address", "Address of the staker."}, {RPCResult::Type::BOOL, "customconfig", "Custom configuration exist."}, {RPCResult::Type::NUM, "stakingminfee", true, "Minimum fee for delegate."}, {RPCResult::Type::NUM, "stakingminutxovalue", true, "Minimum UTXO value for delegate."}, {RPCResult::Type::ARR, "allow", true, "List of allowed delegate addresses.", { {RPCResult::Type::STR, "address", "The delegate address"}, }, }, {RPCResult::Type::ARR, "exclude", true, "List of excluded delegate addresses.", { {RPCResult::Type::STR, "address", "The delegate address"}, }, } }} }, }, RPCExamples{ HelpExampleCli("listsuperstakercustomvalues", "") + HelpExampleRpc("listsuperstakercustomvalues", "") }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); if (!pwallet) return NullUniValue; LOCK(pwallet->cs_wallet); // Search for super stakers UniValue result(UniValue::VARR); for(auto item : pwallet->mapSuperStaker) { CSuperStakerInfo superStaker = item.second; if(superStaker.fCustomConfig) { result.push_back(GetJsonSuperStakerConfig(superStaker)); } } return result; }, }; } static RPCHelpMan listsuperstakervaluesforaddress() { return RPCHelpMan{"listsuperstakervaluesforaddress", "\nList super staker configuration values for address." + HELP_REQUIRING_PASSPHRASE, { {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The super staker Qtum address."}, }, RPCResult{ RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR, "address", "Address of the staker."}, {RPCResult::Type::BOOL, "customconfig", "Custom configuration exist."}, {RPCResult::Type::NUM, "stakingminfee", true, "Minimum fee for delegate."}, {RPCResult::Type::NUM, "stakingminutxovalue", true, "Minimum UTXO value for delegate."}, {RPCResult::Type::ARR, "allow", true, "List of allowed delegate addresses.", { {RPCResult::Type::STR, "address", "The delegate address"}, }, }, {RPCResult::Type::ARR, "exclude", true, "List of excluded delegate addresses.", { {RPCResult::Type::STR, "address", "The delegate address"}, }, } } }, RPCExamples{ HelpExampleCli("listsuperstakervaluesforaddress", "QM72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd") + HelpExampleRpc("listsuperstakervaluesforaddress", "QM72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd") }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); if (!pwallet) return NullUniValue; LOCK(pwallet->cs_wallet); // Parse the super staker address CTxDestination destStaker = DecodeDestination(request.params[0].get_str()); if (!std::holds_alternative<PKHash>(destStaker)) { throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address for staker. Only P2PK and P2PKH allowed"); } PKHash pkhStaker = std::get<PKHash>(destStaker); // Search for super staker CSuperStakerInfo superStaker; bool found = false; for(auto item : pwallet->mapSuperStaker) { if(PKHash(item.second.stakerAddress) == pkhStaker) { superStaker = item.second; found = true; break; } } if(!found) { throw JSONRPCError(RPC_TYPE_ERROR, "Failed to find the super staker"); } return GetJsonSuperStakerConfig(superStaker); }, }; } static RPCHelpMan removesuperstakervaluesforaddress() { return RPCHelpMan{"removesuperstakervaluesforaddress", "\nRemove super staker configuration values for address." + HELP_REQUIRING_PASSPHRASE, { {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The super staker Qtum address."}, }, RPCResult{RPCResult::Type::NONE, "", ""}, RPCExamples{ HelpExampleCli("removesuperstakervaluesforaddress", "QM72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd") + HelpExampleRpc("removesuperstakervaluesforaddress", "QM72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd") }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); if (!pwallet) return NullUniValue; LOCK(pwallet->cs_wallet); // Parse the super staker address CTxDestination destStaker = DecodeDestination(request.params[0].get_str()); if (!std::holds_alternative<PKHash>(destStaker)) { throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address for staker. Only P2PK and P2PKH allowed"); } PKHash pkhStaker = std::get<PKHash>(destStaker); // Search for super staker CSuperStakerInfo superStaker; bool found = false; for(auto item : pwallet->mapSuperStaker) { if(PKHash(item.second.stakerAddress) == pkhStaker && item.second.fCustomConfig) { superStaker = item.second; found = true; break; } } if(found) { // Remove custom configuration superStaker.fCustomConfig = false; superStaker.nMinFee = 0; superStaker.nMinDelegateUtxo = 0; superStaker.nDelegateAddressType = 0; superStaker.delegateAddressList.clear(); // Update super staker data if(!pwallet->AddSuperStakerEntry(superStaker)) throw JSONRPCError(RPC_TYPE_ERROR, "Failed to update the super staker"); } else { throw JSONRPCError(RPC_TYPE_ERROR, "Failed to find the super staker"); } return NullUniValue; }, }; } static RPCHelpMan listaddressgroupings() { return RPCHelpMan{"listaddressgroupings", "\nLists groups of addresses which have had their common ownership\n" "made public by common use as inputs or as the resulting change\n" "in past transactions\n", {}, RPCResult{ RPCResult::Type::ARR, "", "", { {RPCResult::Type::ARR, "", "", { {RPCResult::Type::ARR_FIXED, "", "", { {RPCResult::Type::STR, "address", "The qtum address"}, {RPCResult::Type::STR_AMOUNT, "amount", "The amount in " + CURRENCY_UNIT}, {RPCResult::Type::STR, "label", /* optional */ true, "The label"}, }}, }}, } }, RPCExamples{ HelpExampleCli("listaddressgroupings", "") + HelpExampleRpc("listaddressgroupings", "") }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); if (!pwallet) return NullUniValue; // 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(); LOCK(pwallet->cs_wallet); UniValue jsonGroupings(UniValue::VARR); std::map<CTxDestination, CAmount> balances = pwallet->GetAddressBalances(); for (const std::set<CTxDestination>& grouping : pwallet->GetAddressGroupings()) { UniValue jsonGrouping(UniValue::VARR); for (const CTxDestination& address : grouping) { UniValue addressInfo(UniValue::VARR); addressInfo.push_back(EncodeDestination(address)); addressInfo.push_back(ValueFromAmount(balances[address])); { const auto* address_book_entry = pwallet->FindAddressBookEntry(address); if (address_book_entry) { addressInfo.push_back(address_book_entry->GetLabel()); } } jsonGrouping.push_back(addressInfo); } jsonGroupings.push_back(jsonGrouping); } return jsonGroupings; }, }; } static RPCHelpMan signmessage() { return RPCHelpMan{"signmessage", "\nSign a message with the private key of an address" + HELP_REQUIRING_PASSPHRASE, { {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The qtum address to use for the private key."}, {"message", RPCArg::Type::STR, RPCArg::Optional::NO, "The message to create a signature of."}, }, RPCResult{ RPCResult::Type::STR, "signature", "The signature of the message encoded in base 64" }, RPCExamples{ "\nUnlock the wallet for 30 seconds\n" + HelpExampleCli("walletpassphrase", "\"mypassphrase\" 30") + "\nCreate the signature\n" + HelpExampleCli("signmessage", "\"QD1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"my message\"") + "\nVerify the signature\n" + HelpExampleCli("verifymessage", "\"QD1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"signature\" \"my message\"") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("signmessage", "\"QD1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"my message\"") }, [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); if (!pwallet) return NullUniValue; LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(*pwallet); std::string strAddress = request.params[0].get_str(); std::string strMessage = request.params[1].get_str(); CTxDestination dest = DecodeDestination(strAddress); if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); } const PKHash* pkhash = std::get_if<PKHash>(&dest); if (!pkhash) { throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key"); } std::string signature; SigningResult err = pwallet->SignMessage(strMessage, *pkhash, signature); if (err == SigningResult::SIGNING_FAILED) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, SigningResultString(err)); } else if (err != SigningResult::OK){ throw JSONRPCError(RPC_WALLET_ERROR, SigningResultString(err)); } return signature; }, }; } static CAmount GetReceived(const CWallet& wallet, const UniValue& params, bool by_label) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { std::set<CTxDestination> address_set; if (by_label) { // Get the set of addresses assigned to label std::string label = LabelFromValue(params[0]); address_set = wallet.GetLabelAddresses(label); } else { // Get the address CTxDestination dest = DecodeDestination(params[0].get_str()); if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Qtum address"); } CScript script_pub_key = GetScriptForDestination(dest); if (!wallet.IsMine(script_pub_key)) { throw JSONRPCError(RPC_WALLET_ERROR, "Address not found in wallet"); } address_set.insert(dest); } // Minimum confirmations int min_depth = 1; if (!params[1].isNull()) min_depth = params[1].get_int(); // Tally CAmount amount = 0; for (const std::pair<const uint256, CWalletTx>& wtx_pair : wallet.mapWallet) { const CWalletTx& wtx = wtx_pair.second; if (wtx.IsCoinBase() || wtx.IsCoinStake() || !wallet.chain().checkFinalTx(*wtx.tx) || wtx.GetDepthInMainChain() < min_depth) { continue; } for (const CTxOut& txout : wtx.tx->vout) { CTxDestination address; if (ExtractDestination(txout.scriptPubKey, address) && wallet.IsMine(address) && address_set.count(address)) { amount += txout.nValue; } } } return amount; } static RPCHelpMan getreceivedbyaddress() { return RPCHelpMan{"getreceivedbyaddress", "\nReturns the total amount received by the given address in transactions with at least minconf confirmations.\n", { {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The qtum address for transactions."}, {"minconf", RPCArg::Type::NUM, RPCArg::Default{1}, "Only include transactions confirmed at least this many times."}, }, RPCResult{ RPCResult::Type::STR_AMOUNT, "amount", "The total amount in " + CURRENCY_UNIT + " received at this address." }, RPCExamples{ "\nThe amount from transactions with at least 1 confirmation\n" + HelpExampleCli("getreceivedbyaddress", "\"" + EXAMPLE_ADDRESS[0] + "\"") + "\nThe amount including unconfirmed transactions, zero confirmations\n" + HelpExampleCli("getreceivedbyaddress", "\"" + EXAMPLE_ADDRESS[0] +
{'addition_count': 0, 'commit_subject': 'refactor: Remove unused function', 'deletion_count': 13, 'file_extension': '.cpp', 'lang': 'cpp', 'license': 'mit', 'repo_name': 'qtumproject/qtum'}