|
29 | 29 | #include <wallet/context.h> |
30 | 30 | #include <wallet/rpc/util.h> |
31 | 31 | #include <wallet/spend.h> |
| 32 | +#include <wallet/coincontrol.h> |
32 | 33 | #include <wallet/coinselection.h> |
33 | 34 | #include <wallet/digidollarwallet.h> |
34 | 35 | #include <wallet/walletdb.h> |
@@ -745,7 +746,9 @@ RPCHelpMan mintdigidollar() |
745 | 746 | {RPCResult::Type::NUM, "unlock_height", "Block height when collateral becomes unlockable"}, |
746 | 747 | {RPCResult::Type::NUM, "collateral_ratio", "Effective collateral ratio percentage"}, |
747 | 748 | {RPCResult::Type::STR_AMOUNT, "fee_paid", "Transaction fee paid"}, |
748 | | - {RPCResult::Type::STR, "position_id", "Unique position identifier"} |
| 749 | + {RPCResult::Type::STR, "position_id", "Unique position identifier"}, |
| 750 | + {RPCResult::Type::STR_HEX, "consolidation_txid", /*optional=*/true, "TXID of auto-consolidation transaction (only present if UTXOs were consolidated)"}, |
| 751 | + {RPCResult::Type::BOOL, "utxos_consolidated", /*optional=*/true, "True if wallet UTXOs were auto-consolidated before minting"} |
749 | 752 | } |
750 | 753 | }, |
751 | 754 | RPCExamples{ |
@@ -941,6 +944,82 @@ RPCHelpMan mintdigidollar() |
941 | 944 |
|
942 | 945 | DigiDollar::TxBuilderResult result = builder.BuildMintTransaction(params); |
943 | 946 |
|
| 947 | + // Auto-consolidate if mint failed due to UTXO fragmentation |
| 948 | + std::string consolidation_txid; |
| 949 | + if (!result.success && result.error.find("Too many small UTXOs") != std::string::npos) { |
| 950 | + LogPrintf("DigiDollar RPC Mint: UTXO fragmentation detected (%zu UTXOs). Auto-consolidating...\n", |
| 951 | + availableUtxos.size()); |
| 952 | + |
| 953 | + // Calculate consolidation target: collateral + fees + 10% margin |
| 954 | + CAmount consolidationTarget = result.collateralRequired + |
| 955 | + (result.collateralRequired / 10) + 10000000; // +10% + 0.1 DGB fee buffer |
| 956 | + |
| 957 | + // Cap at available balance |
| 958 | + CAmount totalAvailable = 0; |
| 959 | + for (const auto& [outpoint, value] : utxoValues) { |
| 960 | + totalAvailable += value; |
| 961 | + } |
| 962 | + if (consolidationTarget > totalAvailable) { |
| 963 | + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, |
| 964 | + strprintf("Insufficient funds for collateral. Need %.2f DGB, have %.2f DGB across %zu small UTXOs.", |
| 965 | + result.collateralRequired / 100000000.0, |
| 966 | + totalAvailable / 100000000.0, |
| 967 | + availableUtxos.size())); |
| 968 | + } |
| 969 | + |
| 970 | + // Create consolidation transaction using wallet's standard coin selection |
| 971 | + CTxDestination consolidationDest; |
| 972 | + { |
| 973 | + LOCK(pwallet->cs_wallet); |
| 974 | + auto op_dest = pwallet->GetNewChangeDestination(OutputType::BECH32); |
| 975 | + if (!op_dest) { |
| 976 | + throw JSONRPCError(RPC_WALLET_ERROR, "Failed to get consolidation address"); |
| 977 | + } |
| 978 | + consolidationDest = *op_dest; |
| 979 | + } |
| 980 | + |
| 981 | + wallet::CCoinControl coin_control; |
| 982 | + wallet::CRecipient recipient{consolidationDest, consolidationTarget, false}; |
| 983 | + std::vector<wallet::CRecipient> recipients = {recipient}; |
| 984 | + |
| 985 | + auto consolidation_result = wallet::CreateTransaction(*pwallet, recipients, /*change_pos=*/-1, coin_control, /*sign=*/true); |
| 986 | + if (!consolidation_result) { |
| 987 | + throw JSONRPCError(RPC_WALLET_ERROR, |
| 988 | + strprintf("Auto-consolidation failed: %s. Try manually consolidating UTXOs with: " |
| 989 | + "sendtoaddress <your_address> <amount>", |
| 990 | + util::ErrorString(consolidation_result).original)); |
| 991 | + } |
| 992 | + |
| 993 | + const CTransactionRef& consolidation_tx = consolidation_result->tx; |
| 994 | + consolidation_txid = consolidation_tx->GetHash().GetHex(); |
| 995 | + { |
| 996 | + LOCK(pwallet->cs_wallet); |
| 997 | + pwallet->CommitTransaction(consolidation_tx, {}, {}); |
| 998 | + } |
| 999 | + |
| 1000 | + LogPrintf("DigiDollar RPC Mint: Consolidation tx broadcast: %s (%.2f DGB)\n", |
| 1001 | + consolidation_txid, consolidationTarget / 100000000.0); |
| 1002 | + |
| 1003 | + // Re-gather UTXOs (now includes unconfirmed consolidation output) |
| 1004 | + availableUtxos.clear(); |
| 1005 | + utxoValues.clear(); |
| 1006 | + { |
| 1007 | + LOCK(pwallet->cs_wallet); |
| 1008 | + wallet::CoinsResult coins = wallet::AvailableCoins(*pwallet); |
| 1009 | + for (const wallet::COutput& coin : coins.All()) { |
| 1010 | + availableUtxos.push_back(coin.outpoint); |
| 1011 | + utxoValues[coin.outpoint] = coin.txout.nValue; |
| 1012 | + } |
| 1013 | + } |
| 1014 | + |
| 1015 | + LogPrintf("DigiDollar RPC Mint: After consolidation: %zu UTXOs available\n", availableUtxos.size()); |
| 1016 | + |
| 1017 | + // Rebuild builder with new UTXO map and retry |
| 1018 | + RpcMintTxBuilder retryBuilder(Params(), currentHeight, oraclePriceMicroUSD, utxoValues); |
| 1019 | + params.utxos = availableUtxos; |
| 1020 | + result = retryBuilder.BuildMintTransaction(params); |
| 1021 | + } |
| 1022 | + |
944 | 1023 | if (!result.success) { |
945 | 1024 | throw JSONRPCError(RPC_WALLET_ERROR, "Failed to build mint transaction: " + result.error); |
946 | 1025 | } |
@@ -1024,6 +1103,10 @@ RPCHelpMan mintdigidollar() |
1024 | 1103 | resultObj.pushKV("collateral_ratio", collateralRatio); |
1025 | 1104 | resultObj.pushKV("fee_paid", ValueFromAmount(result.totalFees)); |
1026 | 1105 | resultObj.pushKV("position_id", tx->GetHash().GetHex()); |
| 1106 | + if (!consolidation_txid.empty()) { |
| 1107 | + resultObj.pushKV("consolidation_txid", consolidation_txid); |
| 1108 | + resultObj.pushKV("utxos_consolidated", true); |
| 1109 | + } |
1027 | 1110 |
|
1028 | 1111 | return resultObj; |
1029 | 1112 | }, |
|
0 commit comments