|
27 | 27 | #include <util/translation.h> |
28 | 28 | #include <util/strencodings.h> // for strprintf |
29 | 29 | #include <wallet/coincontrol.h> |
| 30 | +#include <wallet/spend.h> // for AvailableCoins, CreateTransaction |
30 | 31 | #include <wallet/wallet.h> // for CRecipient |
31 | 32 | #include <univalue.h> |
32 | 33 | #include <wallet/digidollarwallet.h> // for DigiDollarWallet |
@@ -923,6 +924,143 @@ WalletModel::DigiDollarMintResult WalletModel::mintDigiDollar(CAmount ddAmount, |
923 | 924 |
|
924 | 925 | DigiDollar::TxBuilderResult result = builder.BuildMintTransaction(params); |
925 | 926 |
|
| 927 | + // Auto-consolidate if mint failed due to UTXO fragmentation |
| 928 | + // (mirrors the RPC mintdigidollar consolidation logic) |
| 929 | + std::string consolidation_txid; |
| 930 | + if (!result.success && result.error.find("Too many small UTXOs") != std::string::npos) { |
| 931 | + LogPrintf("DigiDollar Qt: UTXO fragmentation detected (%zu UTXOs). Auto-consolidating...\n", |
| 932 | + availableUtxos.size()); |
| 933 | + |
| 934 | + CAmount minRequired = result.collateralRequired + 20000000; // collateral + 0.2 DGB margin |
| 935 | + if (totalAvailable < minRequired) { |
| 936 | + return DigiDollarMintResult(AmountExceedsBalance, "", "", |
| 937 | + QString("Insufficient funds for collateral. Need %1 DGB, have %2 DGB.") |
| 938 | + .arg(result.collateralRequired / 100000000.0, 0, 'f', 2) |
| 939 | + .arg(totalAvailable / 100000000.0, 0, 'f', 2)); |
| 940 | + } |
| 941 | + |
| 942 | + CTxDestination consolidationDest; |
| 943 | + { |
| 944 | + LOCK(pWallet->cs_wallet); |
| 945 | + auto op_dest = pWallet->GetNewChangeDestination(OutputType::BECH32); |
| 946 | + if (!op_dest) { |
| 947 | + return DigiDollarMintResult(TransactionCreationFailed, "", "", |
| 948 | + "Failed to get consolidation address"); |
| 949 | + } |
| 950 | + consolidationDest = *op_dest; |
| 951 | + } |
| 952 | + |
| 953 | + // Multi-pass consolidation: MAX_STANDARD_TX_WEIGHT is 400k WU. |
| 954 | + // P2WPKH input ~271 WU. Conservative limit: 1400 inputs per pass. |
| 955 | + static const size_t MAX_CONSOLIDATION_INPUTS = 1400; |
| 956 | + static const int MAX_CONSOLIDATION_PASSES = 10; |
| 957 | + int pass = 0; |
| 958 | + |
| 959 | + while (availableUtxos.size() > MAX_CONSOLIDATION_INPUTS && pass < MAX_CONSOLIDATION_PASSES) { |
| 960 | + ++pass; |
| 961 | + size_t batch_size = std::min(availableUtxos.size(), MAX_CONSOLIDATION_INPUTS); |
| 962 | + LogPrintf("DigiDollar Qt: Consolidation pass %d — sweeping %zu of %zu UTXOs\n", |
| 963 | + pass, batch_size, availableUtxos.size()); |
| 964 | + |
| 965 | + wallet::CCoinControl coin_control; |
| 966 | + CAmount batchTotal = 0; |
| 967 | + for (size_t i = 0; i < batch_size; ++i) { |
| 968 | + coin_control.Select(availableUtxos[i]); |
| 969 | + batchTotal += utxoValues[availableUtxos[i]]; |
| 970 | + } |
| 971 | + coin_control.m_allow_other_inputs = false; |
| 972 | + |
| 973 | + wallet::CRecipient recipient{consolidationDest, batchTotal, /*subtract_fee=*/true}; |
| 974 | + std::vector<wallet::CRecipient> recipients = {recipient}; |
| 975 | + |
| 976 | + auto consolidation_result = wallet::CreateTransaction(*pWallet, recipients, /*change_pos=*/-1, coin_control, /*sign=*/true); |
| 977 | + if (!consolidation_result) { |
| 978 | + return DigiDollarMintResult(TransactionCreationFailed, "", "", |
| 979 | + QString("Auto-consolidation pass %1 failed: %2") |
| 980 | + .arg(pass) |
| 981 | + .arg(QString::fromStdString(util::ErrorString(consolidation_result).original))); |
| 982 | + } |
| 983 | + |
| 984 | + const CTransactionRef& consolidation_tx = consolidation_result->tx; |
| 985 | + consolidation_txid = consolidation_tx->GetHash().GetHex(); |
| 986 | + { |
| 987 | + LOCK(pWallet->cs_wallet); |
| 988 | + pWallet->CommitTransaction(consolidation_tx, {}, {}); |
| 989 | + } |
| 990 | + |
| 991 | + LogPrintf("DigiDollar Qt: Consolidation pass %d tx: %s (swept %.2f DGB from %zu inputs)\n", |
| 992 | + pass, consolidation_txid, batchTotal / 100000000.0, batch_size); |
| 993 | + |
| 994 | + // Refresh UTXO set after consolidation |
| 995 | + availableUtxos.clear(); |
| 996 | + utxoValues.clear(); |
| 997 | + totalAvailable = 0; |
| 998 | + { |
| 999 | + LOCK(pWallet->cs_wallet); |
| 1000 | + wallet::CoinsResult coins = wallet::AvailableCoins(*pWallet); |
| 1001 | + for (const wallet::COutput& coin : coins.All()) { |
| 1002 | + availableUtxos.push_back(coin.outpoint); |
| 1003 | + utxoValues[coin.outpoint] = coin.txout.nValue; |
| 1004 | + totalAvailable += coin.txout.nValue; |
| 1005 | + } |
| 1006 | + } |
| 1007 | + LogPrintf("DigiDollar Qt: After pass %d: %zu UTXOs available\n", pass, availableUtxos.size()); |
| 1008 | + } |
| 1009 | + |
| 1010 | + // Single-pass consolidation for <= 1400 UTXOs |
| 1011 | + if (consolidation_txid.empty() && availableUtxos.size() <= MAX_CONSOLIDATION_INPUTS) { |
| 1012 | + wallet::CCoinControl coin_control; |
| 1013 | + CAmount batchTotal = 0; |
| 1014 | + for (const auto& utxo : availableUtxos) { |
| 1015 | + coin_control.Select(utxo); |
| 1016 | + batchTotal += utxoValues[utxo]; |
| 1017 | + } |
| 1018 | + coin_control.m_allow_other_inputs = false; |
| 1019 | + |
| 1020 | + wallet::CRecipient recipient{consolidationDest, batchTotal, /*subtract_fee=*/true}; |
| 1021 | + std::vector<wallet::CRecipient> recipients = {recipient}; |
| 1022 | + |
| 1023 | + auto consolidation_result = wallet::CreateTransaction(*pWallet, recipients, /*change_pos=*/-1, coin_control, /*sign=*/true); |
| 1024 | + if (!consolidation_result) { |
| 1025 | + return DigiDollarMintResult(TransactionCreationFailed, "", "", |
| 1026 | + QString("Auto-consolidation failed: %1") |
| 1027 | + .arg(QString::fromStdString(util::ErrorString(consolidation_result).original))); |
| 1028 | + } |
| 1029 | + |
| 1030 | + const CTransactionRef& consolidation_tx = consolidation_result->tx; |
| 1031 | + consolidation_txid = consolidation_tx->GetHash().GetHex(); |
| 1032 | + { |
| 1033 | + LOCK(pWallet->cs_wallet); |
| 1034 | + pWallet->CommitTransaction(consolidation_tx, {}, {}); |
| 1035 | + } |
| 1036 | + |
| 1037 | + LogPrintf("DigiDollar Qt: Single-pass consolidation tx: %s (swept %.2f DGB from %zu inputs)\n", |
| 1038 | + consolidation_txid, batchTotal / 100000000.0, availableUtxos.size()); |
| 1039 | + |
| 1040 | + // Refresh UTXO set after consolidation |
| 1041 | + availableUtxos.clear(); |
| 1042 | + utxoValues.clear(); |
| 1043 | + totalAvailable = 0; |
| 1044 | + { |
| 1045 | + LOCK(pWallet->cs_wallet); |
| 1046 | + wallet::CoinsResult coins = wallet::AvailableCoins(*pWallet); |
| 1047 | + for (const wallet::COutput& coin : coins.All()) { |
| 1048 | + availableUtxos.push_back(coin.outpoint); |
| 1049 | + utxoValues[coin.outpoint] = coin.txout.nValue; |
| 1050 | + totalAvailable += coin.txout.nValue; |
| 1051 | + } |
| 1052 | + } |
| 1053 | + } |
| 1054 | + |
| 1055 | + LogPrintf("DigiDollar Qt: After consolidation: %zu UTXOs available (passes: %d)\n", |
| 1056 | + availableUtxos.size(), pass); |
| 1057 | + |
| 1058 | + // Retry mint with consolidated UTXOs |
| 1059 | + QtMintTxBuilder retryBuilder(Params(), currentHeight, oraclePrice, utxoValues); |
| 1060 | + params.utxos = availableUtxos; |
| 1061 | + result = retryBuilder.BuildMintTransaction(params); |
| 1062 | + } |
| 1063 | + |
926 | 1064 | if (!result.success) { |
927 | 1065 | LogPrintf("DigiDollar Qt: ERROR - BuildMintTransaction failed: %s\n", result.error); |
928 | 1066 | return DigiDollarMintResult(TransactionCreationFailed, "", "", |
|
0 commit comments