@@ -1723,4 +1723,126 @@ RPCHelpMan walletcreatefundedpsbt()
1723
1723
},
1724
1724
};
1725
1725
}
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
+
1726
1848
} // namespace wallet
0 commit comments