Skip to content

Commit 3b4fd01

Browse files
committed
Added a new RPC "walletdeniabilizecoin".
It currently supports few basic params, but could be extended with more options if desired.
1 parent 17063f1 commit 3b4fd01

File tree

1 file changed

+102
-0
lines changed

1 file changed

+102
-0
lines changed

src/wallet/rpc/spend.cpp

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1723,4 +1723,106 @@ 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+
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
1736+
{"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"},
1737+
{"sequence", RPCArg::Type::NUM, RPCArg::Optional::NO, "The sequence number"},
1738+
},
1739+
},
1740+
{"conf_target", RPCArg::Type::NUM, RPCArg::DefaultHint{"wallet -txconfirmtarget"}, "Confirmation target in blocks"},
1741+
{"add_to_wallet", RPCArg::Type::BOOL, RPCArg::Default{true}, "When false, returns the serialized transaction without broadcasting or adding it to the wallet"},
1742+
},
1743+
RPCResult{
1744+
RPCResult::Type::OBJ, "", "",
1745+
{
1746+
{RPCResult::Type::STR_HEX, "txid", "The deniabilization transaction id."},
1747+
{RPCResult::Type::STR_AMOUNT, "fee", "The fee used in the deniabilization transaction."},
1748+
{RPCResult::Type::STR_HEX, "hex", /*optional=*/true, "If add_to_wallet is false, the hex-encoded raw transaction with signature(s)"},
1749+
}
1750+
},
1751+
RPCExamples{""
1752+
},
1753+
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1754+
{
1755+
std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
1756+
if (!pwallet) return UniValue::VNULL;
1757+
1758+
CMutableTransaction rawTx;
1759+
AddInputs(rawTx, request.params[0], true);
1760+
1761+
unsigned int confirm_target = !request.params[1].isNull() ? request.params[1].getInt<unsigned int>() : 6;
1762+
const bool add_to_wallet = !request.params[2].isNull() ? request.params[2].get_bool() : true;
1763+
1764+
std::optional<CTxDestination> shared_address;
1765+
std::set<COutPoint> inputs;
1766+
unsigned int deniabilization_cycles = 0;
1767+
for (const auto& txIn: rawTx.vin) {
1768+
const auto& outpoint = txIn.prevout;
1769+
LOCK(pwallet->cs_wallet);
1770+
auto walletTx = pwallet->GetWalletTx(outpoint.hash);
1771+
if (!walletTx) {
1772+
throw JSONRPCError(RPC_WALLET_ERROR, "Transaction not found.");
1773+
}
1774+
const auto& output = walletTx->tx->vout[outpoint.n];
1775+
1776+
isminetype mine = pwallet->IsMine(output);
1777+
if (mine == ISMINE_NO) {
1778+
throw JSONRPCError(RPC_WALLET_ERROR, "Transaction's output doesn't belong to this wallet.");
1779+
}
1780+
1781+
bool spendable = (mine & ISMINE_SPENDABLE) != ISMINE_NO;
1782+
1783+
CTxDestination address;
1784+
if (spendable && ExtractDestination(FindNonChangeParentOutput(*pwallet, outpoint).scriptPubKey, address)) {
1785+
if (!shared_address) {
1786+
shared_address = address;
1787+
}
1788+
else if (!(*shared_address == address)) {
1789+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Inputs must share the same address");
1790+
}
1791+
} else {
1792+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Inputs must be spendable and have a valid address");
1793+
}
1794+
1795+
inputs.emplace(outpoint);
1796+
auto cycles_res = CalculateDeniabilizationCycles(*pwallet, outpoint);
1797+
deniabilization_cycles = std::min(deniabilization_cycles, cycles_res.first);
1798+
}
1799+
1800+
CTransactionRef tx;
1801+
CAmount tx_fee = 0;
1802+
{
1803+
bool sign = !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
1804+
bool insufficient_amount = false;
1805+
auto res = CreateDeniabilizationTransaction(*pwallet, inputs, confirm_target, deniabilization_cycles, sign, insufficient_amount);
1806+
if (!res) {
1807+
throw JSONRPCError(RPC_TRANSACTION_ERROR, ErrorString(res).original);
1808+
}
1809+
tx = res->tx;
1810+
tx_fee = res->fee;
1811+
}
1812+
1813+
UniValue result(UniValue::VOBJ);
1814+
result.pushKV("txid", tx->GetHash().GetHex());
1815+
if (add_to_wallet) {
1816+
pwallet->CommitTransaction(tx, {}, /*orderForm=*/{});
1817+
} else {
1818+
std::string hex{EncodeHexTx(*tx)};
1819+
result.pushKV("hex", hex);
1820+
}
1821+
result.pushKV("fee", ValueFromAmount(tx_fee));
1822+
return result;
1823+
}
1824+
};
1825+
}
1826+
// clang-format on
1827+
17261828
} // namespace wallet

0 commit comments

Comments
 (0)