diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 2fc3b780fd..36281e69ee 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -167,8 +167,17 @@ static const CRPCConvertParam vRPCConvertParams[] = { "combineblocksigs", 1, "signatures" }, { "sendtomainchain", 1, "amount" }, { "sendtomainchain", 2, "subtractfeefromamount" }, + { "dumpissuanceblindingkey", 1, "vin" }, + { "importissuanceblindingkey", 1, "vin" }, { "rawissueasset", 1, "issuances" }, { "rawreissueasset", 1, "reissuances" }, + { "getnewblockhex", 0, "required_age" }, + { "testproposedblock", 1, "acceptnonstd" }, + { "issueasset", 0, "assetamount" }, + { "issueasset", 1, "tokenamount" }, + { "issueasset", 2, "blind" }, + { "reissueasset", 1, "assetamount" }, + { "initpegoutwallet", 1, "bip32_counter"}, { "rawblindrawtransaction", 1, "inputblinder" }, { "rawblindrawtransaction", 2, "inputamount" }, { "rawblindrawtransaction", 3, "inputasset" }, @@ -177,6 +186,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "blindrawtransaction", 1, "assetcommitments" }, { "blindrawtransaction", 2, "ignoreblindfail" }, { "blindrawtransaction", 3, "blind_issuances" }, + { "destroyamount", 1, "amount" }, { "sendmany", 7 , "output_assets" }, { "sendmany", 8 , "ignoreblindfail" }, { "sendtoaddress", 9 , "ignoreblindfail" }, diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 7cb3cac26f..54639d7ef0 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -1400,6 +1400,81 @@ UniValue importblindingkey(const JSONRPCRequest& request) return NullUniValue; } +UniValue importissuanceblindingkey(const JSONRPCRequest& request) +{ + std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() != 3) + throw std::runtime_error( + "importissuanceblindingkey \"txid\" vin \"blindingkey\"\n" + "\nImports a private blinding key in hex for an asset issuance." + "\nArguments:\n" + + "1. \"txid\" (string, required) The transaction id of the issuance\n" + "2. \"vin\" (numeric, required) The input number of the issuance in the transaction.\n" + "3. \"blindingkey\" (string, required) The blinding key in hex\n" + "\nExample:\n" + + HelpExampleCli("importblindingkey", "\"my blinded CT address\" ") + ); + + LOCK2(cs_main, pwallet->cs_wallet); + + if (!request.params[0].isStr() || !IsHex(request.params[0].get_str()) || request.params[0].get_str().size() != 64) { + throw JSONRPCError(RPC_TYPE_ERROR, "First argument must be a txid string"); + } + std::string txidstr = request.params[0].get_str(); + uint256 txid; + txid.SetHex(txidstr); + + uint32_t vindex; + if (!request.params[1].isNum()) { + throw JSONRPCError(RPC_TYPE_ERROR, "vin must be an integer"); + } + vindex = request.params[1].get_int(); + + if (!request.params[2].isStr() || !IsHex(request.params[2].get_str()) || request.params[2].get_str().size() != 64) { + throw JSONRPCError(RPC_TYPE_ERROR, "blinding key must be a hex string of length 64"); + } + + std::vector keydata = ParseHex(request.params[2].get_str()); + if (keydata.size() != 32) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid hexadecimal key length"); + } + CKey key; + key.Set(keydata.begin(), keydata.end(), true); + + // Process as issuance key dump + for (std::map::const_iterator it = pwallet->mapWallet.begin(); it != pwallet->mapWallet.end(); ++it) { + const CWalletTx* pcoin = &(*it).second; + if (pcoin->GetHash() != txid) { + continue; + } + if (pcoin->tx->vin.size() <= vindex) { + throw JSONRPCError(RPC_WALLET_ERROR, "Transaction is in wallet but vin does not exist"); + } + if (pcoin->tx->vin[vindex].assetIssuance.IsNull()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Transaction input has no issuance"); + } + + // Import the key in that slot + uint256 keyval; + memcpy(keyval.begin(), &keydata[0], 32); + CScript blindingScript(CScript() << OP_RETURN << std::vector(pcoin->tx->vin[vindex].prevout.hash.begin(), pcoin->tx->vin[vindex].prevout.hash.end()) << pcoin->tx->vin[vindex].prevout.n); + if (!pwallet->AddSpecificBlindingKey(CScriptID(blindingScript), keyval)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Failed to import blinding key"); + } + pwallet->MarkDirty(); + return NullUniValue; + } + + throw JSONRPCError(RPC_WALLET_ERROR, "Transaction is unknown to wallet."); +} + UniValue dumpblindingkey(const JSONRPCRequest& request) { std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request); @@ -1443,3 +1518,66 @@ UniValue dumpblindingkey(const JSONRPCRequest& request) throw JSONRPCError(RPC_WALLET_ERROR, "Blinding key for address is unknown"); } + +UniValue dumpissuanceblindingkey(const JSONRPCRequest& request) +{ + std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() != 2) + throw std::runtime_error( + "dumpissuanceblindingkey \"txid\" vin\n" + "\nDumps the private blinding key for an asset issuance in wallet." + "\nArguments:\n" + "1. \"txid\" (string, required) The transaction id of the issuance\n" + "2. \"vin\" (numeric, required) The input number of the issuance in the transaction.\n" + "\nResult:\n" + "\"blindingkey\" (string) The blinding key\n" + "\nExample:\n" + + HelpExampleCli("dumpissuanceblindingkey", "\"\", 0") + ); + + LOCK2(cs_main, pwallet->cs_wallet); + + if (!request.params[0].isStr() || !IsHex(request.params[0].get_str()) || request.params[0].get_str().size() != 64) { + throw JSONRPCError(RPC_TYPE_ERROR, "First argument must be a txid string"); + } + std::string txidstr = request.params[0].get_str(); + uint256 txid; + txid.SetHex(txidstr); + + uint32_t vindex; + if (!request.params[1].isNum()) { + throw JSONRPCError(RPC_TYPE_ERROR, "vin must be an integer"); + } + vindex = request.params[1].get_int(); + + // Process as issuance key dump + for (std::map::const_iterator it = pwallet->mapWallet.begin(); it != pwallet->mapWallet.end(); ++it) { + const CWalletTx* pcoin = &(*it).second; + if (pcoin->tx->GetHash() != txid) { + continue; + } + if (pcoin->tx->vin.size() <= vindex) { + throw JSONRPCError(RPC_WALLET_ERROR, "Transaction is in wallet but vin does not exist"); + } + if (pcoin->tx->vin[vindex].assetIssuance.IsNull()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Transaction input has no issuance"); + } + // We can actually deblind the input + if (pcoin->GetIssuanceAmount(vindex, false) != -1 ) { + CScript blindingScript(CScript() << OP_RETURN << std::vector(pcoin->tx->vin[vindex].prevout.hash.begin(), pcoin->tx->vin[vindex].prevout.hash.end()) << pcoin->tx->vin[vindex].prevout.n); + CKey key; + key = pwallet->GetBlindingKey(&blindingScript); + return HexStr(key.begin(), key.end()); + } else { + // We don't know how to deblind this using our wallet + throw JSONRPCError(RPC_WALLET_ERROR, "Unable to unblind issuance with wallet blinding key."); + } + } + throw JSONRPCError(RPC_WALLET_ERROR, "Transaction is unknown to wallet."); +} diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index a1573e1752..ded7ccedde 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -5648,6 +5648,386 @@ static UniValue unblindrawtransaction(const JSONRPCRequest& request) return result; } +static CTransactionRef SendGenerationTransaction(const CScript& asset_script, const CPubKey &asset_pubkey, const CScript& token_script, const CPubKey &token_pubkey, CAmount asset_amount, CAmount token_amount, IssuanceDetails* issuance_details, CWallet* pwallet) +{ + CAsset reissue_token = issuance_details->reissuance_token; + CAmount curBalance = pwallet->GetBalance()[reissue_token]; + + if (!reissue_token.IsNull() && curBalance <= 0) { + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "No available reissuance tokens in wallet."); + } + + // Might need up to 3 change keys: policyAsset, asset, and reissuance token + std::vector> change_keys; + for (unsigned int i = 0; i < 3; i++) { + change_keys.push_back(std::unique_ptr(new CReserveKey(pwallet))); + } + + std::vector vecSend; + // Signal outputs to skip "funding" with fixed asset numbers 1, 2, ... + // We don't know the asset during initial issuance until inputs are chosen + if (asset_script.size() > 0) { + vecSend.push_back({asset_script, asset_amount, CAsset(uint256S("1")), asset_pubkey, false}); + } + if (token_script.size() > 0) { + CRecipient recipient = {token_script, token_amount, CAsset(uint256S("2")), token_pubkey, false}; + // We need to select the issuance token(s) to spend + if (!reissue_token.IsNull()) { + recipient.asset = reissue_token; + recipient.nAmount = curBalance; // Or 1? + // If the issuance token *is* the fee asset, subtract fee from this output + if (reissue_token == ::policyAsset) { + recipient.fSubtractFeeFromAmount = true; + } + } + vecSend.push_back(recipient); + } + + CAmount nFeeRequired; + int nChangePosRet = -1; + std::string strError; + CCoinControl dummy_control; + BlindDetails blind_details; + CTransactionRef tx_ref(MakeTransactionRef()); + if (!pwallet->CreateTransaction(vecSend, tx_ref, change_keys, nFeeRequired, nChangePosRet, strError, dummy_control, true, &blind_details, issuance_details)) { + throw JSONRPCError(RPC_WALLET_ERROR, strError); + } + + CValidationState state; + mapValue_t map_value; + if (!pwallet->CommitTransaction(tx_ref, std::move(map_value), {} /* orderForm */, change_keys, g_connman.get(), state, &blind_details)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error: The transaction was rejected! This might happen if some of the coins in your wallet were already spent, such as if you used a copy of the wallet and coins were spent in the copy but not marked as spent here."); + } + + return tx_ref; +} + +UniValue issueasset(const JSONRPCRequest& request) +{ + std::shared_ptr 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() > 3) + throw std::runtime_error( + "issueasset assetamount tokenamount ( blind )\n" + "\nCreate an asset. Must have funds in wallet to do so. Returns asset hex id.\n" + "\nArguments:\n" + "1. \"assetamount\" (numeric or string, required) Amount of asset to generate.\n" + "2. \"tokenamount\" (numeric or string, required) Amount of reissuance tokens to generate. These will allow you to reissue the asset if in wallet using `reissueasset`. These tokens are not consumed during reissuance.\n" + "3. \"blind\" (bool, optional, default=true) Whether to blind the issuances.\n" + "\nResult:\n" + "{ (json object)\n" + " \"txid\":\"\", (string) Transaction id for issuance.\n" + " \"vin\":\"n\", (numeric) The input position of the issuance in the transaction.\n" + " \"entropy\":\"\", (string) Entropy of the asset type.\n" + " \"asset\":\"\", (string) Asset type for issuance.\n" + " \"token\":\"\", (string) Token type for issuance.\n" + "}\n" + "\nExamples:\n" + + HelpExampleCli("issueasset", "10 0") + + HelpExampleRpc("issueasset", "10, 0") + ); + + LOCK2(cs_main, pwallet->cs_wallet); + + CAmount nAmount = AmountFromValue(request.params[0]); + CAmount nTokens = AmountFromValue(request.params[1]); + if (nAmount == 0 && nTokens == 0) { + throw JSONRPCError(RPC_TYPE_ERROR, "Issuance must have one non-zero component"); + } + + bool blind_issuances = request.params.size() < 3 || request.params[2].get_bool(); + + if (!pwallet->IsLocked()) + pwallet->TopUpKeyPool(); + + // Generate a new key that is added to wallet + CPubKey newKey; + CTxDestination asset_dest; + CTxDestination token_dest; + CPubKey asset_dest_blindpub; + CPubKey token_dest_blindpub; + + if (nAmount > 0) { + if (!pwallet->GetKeyFromPool(newKey)) { + throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); + } + asset_dest = PKHash(newKey.GetID()); + pwallet->SetAddressBook(asset_dest, "", "receive"); + asset_dest_blindpub = pwallet->GetBlindingPubKey(GetScriptForDestination(asset_dest)); + } + if (nTokens > 0) { + if (!pwallet->GetKeyFromPool(newKey)) { + throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); + } + token_dest = PKHash(newKey.GetID()); + pwallet->SetAddressBook(token_dest, "", "receive"); + token_dest_blindpub = pwallet->GetBlindingPubKey(GetScriptForDestination(token_dest)); + } + + uint256 dummyentropy; + CAsset dummyasset; + IssuanceDetails issuance_details; + issuance_details.blind_issuance = blind_issuances; + CTransactionRef tx_ref = SendGenerationTransaction(GetScriptForDestination(asset_dest), asset_dest_blindpub, GetScriptForDestination(token_dest), token_dest_blindpub, nAmount, nTokens, &issuance_details, pwallet); + + // Calculate asset type, assumes first vin is used for issuance + CAsset asset; + CAsset token; + assert(!tx_ref->vin.empty()); + GenerateAssetEntropy(issuance_details.entropy, tx_ref->vin[0].prevout, uint256()); + CalculateAsset(asset, issuance_details.entropy); + CalculateReissuanceToken(token, issuance_details.entropy, blind_issuances); + + UniValue ret(UniValue::VOBJ); + ret.pushKV("txid", tx_ref->GetHash().GetHex()); + ret.pushKV("vin", 0); + ret.pushKV("entropy", issuance_details.entropy.GetHex()); + ret.pushKV("asset", asset.GetHex()); + ret.pushKV("token", token.GetHex()); + return ret; +} + +UniValue reissueasset(const JSONRPCRequest& request) +{ + std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() != 2) + throw std::runtime_error( + "reissueasset asset assetamount\n" + "\nCreate more of an already issued asset. Must have reissuance token in wallet to do so. Reissuing does not affect your reissuance token balance, only asset.\n" + "\nArguments:\n" + "1. \"asset\" (string, required) The asset you want to re-issue. The corresponding token must be in your wallet.\n" + "2. \"assetamount\" (numeric or string, required) Amount of additional asset to generate.\n" + "\nResult:\n" + "{ (json object)\n" + " \"txid\":\"\", (string) Transaction id for issuance.\n" + " \"vin\":\"n\", (numeric) The input position of the issuance in the transaction.\n" + "}\n" + "\nExamples:\n" + + HelpExampleCli("reissueasset", " 0") + + HelpExampleRpc("reissueasset", ", 0") + ); + + LOCK2(cs_main, pwallet->cs_wallet); + + std::string assetstr = request.params[0].get_str(); + CAsset asset = GetAssetFromString(assetstr); + + CAmount nAmount = AmountFromValue(request.params[1]); + if (nAmount <= 0) { + throw JSONRPCError(RPC_TYPE_ERROR, "Reissuance must create a non-zero amount."); + } + + if (!pwallet->IsLocked()) { + pwallet->TopUpKeyPool(); + } + + // Find the entropy and reissuance token in wallet + IssuanceDetails issuance_details; + issuance_details.reissuance_asset = asset; + std::map > tokenMap = pwallet->GetReissuanceTokenTypes(); + for (const auto& it : tokenMap) { + if (it.second.second == asset) { + issuance_details.entropy = it.first; + issuance_details.reissuance_token = it.second.first; + } + if (it.second.first == asset) { + throw JSONRPCError(RPC_WALLET_ERROR, "Asset given is a reissuance token type and can not be reissued."); + } + } + if (issuance_details.reissuance_token.IsNull()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Asset reissuance token definition could not be found in wallet."); + } + + // Add destination for the to-be-created asset + CPubKey newAssetKey; + if (!pwallet->GetKeyFromPool(newAssetKey)) { + throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); + } + CTxDestination asset_dest = PKHash(newAssetKey.GetID()); + pwallet->SetAddressBook(asset_dest, "", "receive"); + CPubKey asset_dest_blindpub = pwallet->GetBlindingPubKey(GetScriptForDestination(asset_dest)); + + // Add destination for tokens we are moving + CPubKey newTokenKey; + if (!pwallet->GetKeyFromPool(newTokenKey)) { + throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); + } + CTxDestination token_dest = PKHash(newTokenKey.GetID()); + pwallet->SetAddressBook(token_dest, "", "receive"); + CPubKey token_dest_blindpub = pwallet->GetBlindingPubKey(GetScriptForDestination(token_dest)); + + // Attempt a send. + CTransactionRef tx_ref = SendGenerationTransaction(GetScriptForDestination(asset_dest), asset_dest_blindpub, GetScriptForDestination(token_dest), token_dest_blindpub, nAmount, -1, &issuance_details, pwallet); + assert(!tx_ref->vin.empty()); + + UniValue obj(UniValue::VOBJ); + obj.pushKV("txid", tx_ref->GetHash().GetHex()); + for (uint64_t i = 0; i < tx_ref->vin.size(); i++) { + if (!tx_ref->vin[i].assetIssuance.IsNull()) { + obj.pushKV("vin", i); + break; + } + } + + return obj; +} + +UniValue listissuances(const JSONRPCRequest& request) +{ + std::shared_ptr const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() > 1) + throw std::runtime_error( + "listissuances ( asset ) \n" + "\nList all issuances known to the wallet for the given asset, or for all issued assets if none provided.\n" + "\nArguments:\n" + "1. \"asset\" (string, optional) The asset whose issaunces you wish to list. Accepts either the asset hex or the locally assigned asset label.\n" + "\nResult:\n" + "[ (json array of objects)\n" + " {\n" + " \"txid\":\"\", (string) Transaction id for issuance.\n" + " \"entropy\":\"\" (string) Entropy of the asset type.\n" + " \"asset\":\"\", (string) Asset type for issuance if known.\n" + " \"assetlabel\":\"\", (string) Asset label for issuance if set.\n" + " \"token\":\"\", (string) Token type for issuance.\n" + " \"vin\":\"n\", (numeric) The input position of the issuance in the transaction.\n" + " \"assetamount\":\"X.XX\", (numeric) The amount of asset issued. Is -1 if blinded and unknown to wallet.\n" + " \"tokenamount\":\"X.XX\", (numeric) The reissuance token amount issued. Is -1 if blinded and unknown to wallet.\n" + " \"isreissuance\":\"\", (bool) True if this is a reissuance.\n" + " \"assetblinds\":\"\" (string) Hex blinding factor for asset amounts.\n" + " \"tokenblinds\":\"\" (string) Hex blinding factor for token amounts.\n" + " }\n" + " ,...\n" + "]\n" + "\"\" (array) List of transaction issuances and information in wallet\n" + + HelpExampleCli("listissuances", "") + + HelpExampleRpc("listissuances", "") + ); + + LOCK2(cs_main, pwallet->cs_wallet); + + std::string assetstr; + CAsset asset_filter; + if (request.params.size() > 0) { + assetstr = request.params[0].get_str(); + asset_filter = GetAssetFromString(assetstr); + } + + UniValue issuancelist(UniValue::VARR); + for (const auto& it : pwallet->mapWallet) { + const CWalletTx* pcoin = &it.second; + CAsset asset; + CAsset token; + uint256 entropy; + for (uint64_t vinIndex = 0; vinIndex < pcoin->tx->vin.size(); vinIndex++) { + UniValue item(UniValue::VOBJ); + const CAssetIssuance& issuance = pcoin->tx->vin[vinIndex].assetIssuance; + if (issuance.IsNull()) { + continue; + } + if (issuance.assetBlindingNonce.IsNull()) { + GenerateAssetEntropy(entropy, pcoin->tx->vin[vinIndex].prevout, issuance.assetEntropy); + CalculateAsset(asset, entropy); + // Null is considered explicit + CalculateReissuanceToken(token, entropy, issuance.nAmount.IsCommitment()); + item.pushKV("isreissuance", false); + item.pushKV("token", token.GetHex()); + CAmount itamount = pcoin->GetIssuanceAmount(vinIndex, true); + item.pushKV("tokenamount", (itamount == -1 ) ? -1 : ValueFromAmount(itamount)); + item.pushKV("tokenblinds", pcoin->GetIssuanceBlindingFactor(vinIndex, true).GetHex()); + item.pushKV("entropy", entropy.GetHex()); + } else { + CalculateAsset(asset, issuance.assetEntropy); + item.pushKV("isreissuance", true); + item.pushKV("entropy", issuance.assetEntropy.GetHex()); + } + item.pushKV("txid", pcoin->tx->GetHash().GetHex()); + item.pushKV("vin", vinIndex); + item.pushKV("asset", asset.GetHex()); + const std::string label = gAssetsDir.GetLabel(asset); + if (label != "") { + item.pushKV("assetlabel", label); + } + CAmount iaamount = pcoin->GetIssuanceAmount(vinIndex, false); + item.pushKV("assetamount", (iaamount == -1 ) ? -1 : ValueFromAmount(iaamount)); + item.pushKV("assetblinds", pcoin->GetIssuanceBlindingFactor(vinIndex, false).GetHex()); + if (!asset_filter.IsNull() && asset_filter != asset) { + continue; + } + issuancelist.push_back(item); + } + } + return issuancelist; + +} + +UniValue destroyamount(const JSONRPCRequest& request) +{ + std::shared_ptr 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( + "destroyamount asset amount ( \"comment\" )\n" + "\nDestroy an amount of a given asset.\n\n" + "\nArguments:\n" + "1. \"asset\" (string, required) Hex asset id or asset label to destroy.\n" + "2. \"amount\" (numeric or string, required) The amount to destroy (8 decimals above the minimal unit).\n" + "3. \"comment\" (string, optional) A comment used to store what the transaction is for. \n" + " This is not part of the transaction, just kept in your wallet.\n" + "\nResult:\n" + "\"transactionid\" (string) The transaction id.\n" + "\nExamples:\n" + + HelpExampleCli("destroyamount", "\"bitcoin\" 100") + + HelpExampleCli("destroyamount", "\"bitcoin\" 100 \"destroy assets\"") + + HelpExampleRpc("destroyamount", "\"bitcoin\" 100 \"destroy assets\"") + ); + + LOCK2(cs_main, pwallet->cs_wallet); + + std::string strasset = request.params[0].get_str(); + CAsset asset = GetAssetFromString(strasset); + + CAmount nAmount = AmountFromValue(request.params[1]); + if (nAmount <= 0) { + throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount to destroy"); + } + + mapValue_t mapValue; + if (request.params.size() > 2 && !request.params[2].isNull() && !request.params[2].get_str().empty()) { + mapValue["comment"] = request.params[2].get_str(); + } + + EnsureWalletIsUnlocked(pwallet); + + NullData nulldata; + CTxDestination address(nulldata); + CCoinControl no_coin_control; // This is a deprecated API + CTransactionRef tx = SendMoney(pwallet, address, nAmount, asset, false, no_coin_control, std::move(mapValue), true); + + return tx->GetHash().GetHex(); +} + // END ELEMENTS commands // @@ -5655,7 +6035,9 @@ static UniValue unblindrawtransaction(const JSONRPCRequest& request) UniValue abortrescan(const JSONRPCRequest& request); // in rpcdump.cpp UniValue dumpprivkey(const JSONRPCRequest& request); // in rpcdump.cpp UniValue importblindingkey(const JSONRPCRequest& request); // in rpcdump.cpp +UniValue importissuanceblindingkey(const JSONRPCRequest& request); // in rpcdump.cpp UniValue dumpblindingkey(const JSONRPCRequest& request); // in rpcdump.cpp +UniValue dumpissuanceblindingkey(const JSONRPCRequest& request); // in rpcdump.cpp UniValue importprivkey(const JSONRPCRequest& request); UniValue importaddress(const JSONRPCRequest& request); UniValue importpubkey(const JSONRPCRequest& request); @@ -5736,8 +6118,14 @@ static const CRPCCommand commands[] = { "wallet", "initpegoutwallet", &initpegoutwallet, {"bitcoin_descriptor", "bip32_counter", "liquid_pak"} }, { "wallet", "getwalletpakinfo", &getwalletpakinfo, {} }, { "wallet", "importblindingkey", &importblindingkey, {"address", "hexkey", "key_is_master"}}, + { "wallet", "importissuanceblindingkey", &importissuanceblindingkey, {"txid", "vin", "blindingkey"}}, { "wallet", "dumpblindingkey", &dumpblindingkey, {"address"}}, + { "wallet", "dumpissuanceblindingkey", &dumpissuanceblindingkey, {"txid", "vin"}}, { "wallet", "signblock", &signblock, {"blockhex"}}, + { "wallet", "listissuances", &listissuances, {"asset"}}, + { "wallet", "issueasset", &issueasset, {"assetamount", "tokenamount", "blind"}}, + { "wallet", "reissueasset", &reissueasset, {"asset, assetamount"}}, + { "wallet", "destroyamount", &destroyamount, {"asset", "amount", "comment"} }, }; // clang-format on