Skip to content

Commit f231492

Browse files
committed
Added a new 'walletdeniabilizecoin' RPC.
1 parent 92b64d8 commit f231492

File tree

3 files changed

+127
-0
lines changed

3 files changed

+127
-0
lines changed

src/rpc/client.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ static const CRPCConvertParam vRPCConvertParams[] =
130130
{ "walletcreatefundedpsbt", 2, "locktime" },
131131
{ "walletcreatefundedpsbt", 3, "options" },
132132
{ "walletcreatefundedpsbt", 4, "bip32derivs" },
133+
{ "walletdeniabilizecoin", 0, "inputs" },
134+
{ "walletdeniabilizecoin", 1, "conf_target" },
135+
{ "walletdeniabilizecoin", 2, "add_to_wallet" },
133136
{ "walletprocesspsbt", 1, "sign" },
134137
{ "walletprocesspsbt", 3, "bip32derivs" },
135138
{ "walletprocesspsbt", 4, "finalize" },

src/wallet/rpc/spend.cpp

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1723,4 +1723,126 @@ RPCHelpMan walletcreatefundedpsbt()
17231723
},
17241724
};
17251725
}
1726+
1727+
// clang-format off
1728+
RPCHelpMan walletdeniabilizecoin()
1729+
{
1730+
return RPCHelpMan{"walletdeniabilizecoin",
1731+
"\nDeniabilize 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+
"\nDeniabilize 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

src/wallet/rpc/wallet.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -869,6 +869,7 @@ RPCHelpMan send();
869869
RPCHelpMan sendall();
870870
RPCHelpMan walletprocesspsbt();
871871
RPCHelpMan walletcreatefundedpsbt();
872+
RPCHelpMan walletdeniabilizecoin();
872873
RPCHelpMan signrawtransactionwithwallet();
873874

874875
// signmessage
@@ -956,6 +957,7 @@ Span<const CRPCCommand> GetWalletRPCCommands()
956957
{"wallet", &walletpassphrase},
957958
{"wallet", &walletpassphrasechange},
958959
{"wallet", &walletprocesspsbt},
960+
{"wallet", &walletdeniabilizecoin},
959961
};
960962
return commands;
961963
}

0 commit comments

Comments
 (0)