@@ -1723,4 +1723,126 @@ RPCHelpMan walletcreatefundedpsbt()
17231723},
17241724 };
17251725}
1726+
1727+ // clang-format off
1728+ RPCHelpMan walletdeniabilizecoin ()
1729+ {
1730+ return RPCHelpMan{" walletdeniabilizecoin" ,
1731+ " \n Deniabilize one or more UTXOs that share the same address.\n " ,
1732+ {
1733+ {" inputs" , RPCArg::Type::ARR, RPCArg::Optional::NO, " Specify inputs (must share the same address). A JSON array of JSON objects" ,
1734+ {
1735+ {" " , RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, " " ,
1736+ {
1737+ {" txid" , RPCArg::Type::STR_HEX, RPCArg::Optional::NO, " The transaction id" },
1738+ {" vout" , RPCArg::Type::NUM, RPCArg::Optional::NO, " The output number" },
1739+ }
1740+ }
1741+ },
1742+ },
1743+ {" conf_target" , RPCArg::Type::NUM, RPCArg::DefaultHint{" wallet -txconfirmtarget" }, " Confirmation target in blocks" },
1744+ {" add_to_wallet" , RPCArg::Type::BOOL, RPCArg::Default{true }, " When false, returns the serialized transaction without broadcasting or adding it to the wallet" },
1745+ },
1746+ RPCResult{
1747+ RPCResult::Type::OBJ, " " , " " ,
1748+ {
1749+ {RPCResult::Type::STR_HEX, " txid" , " The deniabilization transaction id." },
1750+ {RPCResult::Type::STR_AMOUNT, " fee" , " The fee used in the deniabilization transaction." },
1751+ {RPCResult::Type::STR_HEX, " hex" , /* optional=*/ true , " If add_to_wallet is false, the hex-encoded raw transaction with signature(s)" },
1752+ }
1753+ },
1754+ RPCExamples{
1755+ " \n Deniabilize a single UTXO\n "
1756+ + HelpExampleCli (" walletdeniabilizecoin" , " \" [{\" txid\" :\" 4c14d20709daef476854fe7ef75bdfcfd5a7636a431b4622ec9481f297e12e8c\" , \" vout\" : 0}]\" " )
1757+ },
1758+ [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1759+ {
1760+ std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest (request);
1761+ if (!pwallet) return UniValue::VNULL;
1762+
1763+ std::optional<CTxDestination> shared_address;
1764+ std::set<COutPoint> inputs;
1765+ unsigned int deniabilization_cycles = UINT_MAX;
1766+ for (const UniValue& input : request.params [0 ].get_array ().getValues ()) {
1767+ uint256 txid = ParseHashO (input, " txid" );
1768+
1769+ const UniValue& vout_v = input.find_value (" vout" );
1770+ if (!vout_v.isNum ()) {
1771+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Invalid parameter, missing vout key" );
1772+ }
1773+ int nOutput = vout_v.getInt <int >();
1774+ if (nOutput < 0 ) {
1775+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Invalid parameter, vout cannot be negative" );
1776+ }
1777+
1778+ COutPoint outpoint (txid, nOutput);
1779+ LOCK (pwallet->cs_wallet );
1780+ auto walletTx = pwallet->GetWalletTx (outpoint.hash );
1781+ if (!walletTx) {
1782+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Invalid parameter, txid not found in wallet." );
1783+ }
1784+ if (outpoint.n >= walletTx->tx ->vout .size ()) {
1785+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Invalid parameter, vout is out of range" );
1786+ }
1787+ const auto & output = walletTx->tx ->vout [outpoint.n ];
1788+
1789+ isminetype mine = pwallet->IsMine (output);
1790+ if (mine == ISMINE_NO) {
1791+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Invalid parameter, transaction's output doesn't belong to this wallet." );
1792+ }
1793+
1794+ bool spendable = (mine & ISMINE_SPENDABLE) != ISMINE_NO;
1795+
1796+ CTxDestination address;
1797+ if (spendable && ExtractDestination (FindNonChangeParentOutput (*pwallet, outpoint).scriptPubKey , address)) {
1798+ if (!shared_address) {
1799+ shared_address = address;
1800+ }
1801+ else if (!(*shared_address == address)) {
1802+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Invalid parameter, inputs must share the same address" );
1803+ }
1804+ } else {
1805+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Invalid parameter, inputs must be spendable and have a valid address" );
1806+ }
1807+
1808+ inputs.emplace (outpoint);
1809+ auto cycles_res = CalculateDeniabilizationCycles (*pwallet, outpoint);
1810+ deniabilization_cycles = std::min (deniabilization_cycles, cycles_res.first );
1811+ }
1812+
1813+ if (inputs.empty ()) {
1814+ throw JSONRPCError (RPC_INVALID_PARAMETER, " Invalid parameter, inputs must not be empty" );
1815+ }
1816+
1817+ unsigned int confirm_target = !request.params [1 ].isNull () ? request.params [1 ].getInt <unsigned int >() : 6 ;
1818+ const bool add_to_wallet = !request.params [2 ].isNull () ? request.params [2 ].get_bool () : true ;
1819+
1820+ CTransactionRef tx;
1821+ CAmount tx_fee = 0 ;
1822+ {
1823+ bool sign = !pwallet->IsWalletFlagSet (WALLET_FLAG_DISABLE_PRIVATE_KEYS);
1824+ bool insufficient_amount = false ;
1825+ auto res = CreateDeniabilizationTransaction (*pwallet, inputs, confirm_target, deniabilization_cycles, sign, insufficient_amount);
1826+ if (!res) {
1827+ throw JSONRPCError (RPC_TRANSACTION_ERROR, ErrorString (res).original );
1828+ }
1829+ tx = res->tx ;
1830+ tx_fee = res->fee ;
1831+ }
1832+
1833+ UniValue result (UniValue::VOBJ);
1834+ result.pushKV (" txid" , tx->GetHash ().GetHex ());
1835+ if (add_to_wallet) {
1836+ pwallet->CommitTransaction (tx, {}, /* orderForm=*/ {});
1837+ } else {
1838+ std::string hex{EncodeHexTx (*tx)};
1839+ result.pushKV (" hex" , hex);
1840+ }
1841+ result.pushKV (" fee" , ValueFromAmount (tx_fee));
1842+ return result;
1843+ }
1844+ };
1845+ }
1846+ // clang-format on
1847+
17261848} // namespace wallet
0 commit comments