|
1 | 1 | import 'package:isar_community/isar.dart'; |
2 | 2 |
|
3 | 3 | import '../../../dto/ordinals/inscription_data.dart'; |
| 4 | +import '../../../models/input.dart'; |
| 5 | +import '../../../models/isar/models/blockchain_data/address.dart'; |
| 6 | +import '../../../models/isar/models/blockchain_data/utxo.dart'; |
4 | 7 | import '../../../models/isar/ordinal.dart'; |
5 | 8 | import '../../../services/ord_api.dart'; |
| 9 | +import '../../../utilities/amount/amount.dart'; |
| 10 | +import '../../../utilities/enums/fee_rate_type_enum.dart'; |
6 | 11 | import '../../../utilities/logger.dart'; |
7 | 12 | import '../../crypto_currency/interfaces/electrumx_currency_interface.dart'; |
| 13 | +import '../../models/tx_data.dart'; |
8 | 14 | import 'electrumx_interface.dart'; |
9 | 15 |
|
10 | 16 | mixin OrdinalsInterface<T extends ElectrumXCurrencyInterface> |
@@ -86,6 +92,75 @@ mixin OrdinalsInterface<T extends ElectrumXCurrencyInterface> |
86 | 92 | } |
87 | 93 | } |
88 | 94 |
|
| 95 | + /// Build a transaction that sends the ordinal UTXO to [recipientAddress]. |
| 96 | + /// |
| 97 | + /// Uses coin-control send-all from the single ordinal UTXO so the ordinal |
| 98 | + /// (at input offset 0) lands on the only output (the recipient) via FIFO. |
| 99 | + /// If the UTXO value can't cover the fee, an exception is thrown. |
| 100 | + Future<TxData> prepareOrdinalSend({ |
| 101 | + required UTXO ordinalUtxo, |
| 102 | + required String recipientAddress, |
| 103 | + FeeRateType feeRateType = FeeRateType.average, |
| 104 | + }) async { |
| 105 | + // Temporarily unblock so coinSelection accepts it. |
| 106 | + final wasBlocked = ordinalUtxo.isBlocked; |
| 107 | + // utxoForTx is the in-memory object passed to coinSelection; it must have |
| 108 | + // isBlocked=false or the spendable-outputs filter will reject it. |
| 109 | + UTXO utxoForTx = ordinalUtxo; |
| 110 | + if (wasBlocked) { |
| 111 | + final unblocked = ordinalUtxo.copyWith( |
| 112 | + isBlocked: false, |
| 113 | + blockedReason: null, |
| 114 | + ); |
| 115 | + unblocked.id = ordinalUtxo.id; |
| 116 | + await mainDB.putUTXO(unblocked); |
| 117 | + utxoForTx = unblocked; |
| 118 | + } |
| 119 | + |
| 120 | + try { |
| 121 | + final utxoValue = Amount( |
| 122 | + rawValue: BigInt.from(ordinalUtxo.value), |
| 123 | + fractionDigits: cryptoCurrency.fractionDigits, |
| 124 | + ); |
| 125 | + |
| 126 | + final txData = TxData( |
| 127 | + feeRateType: feeRateType, |
| 128 | + recipients: [ |
| 129 | + TxRecipient( |
| 130 | + address: recipientAddress, |
| 131 | + amount: utxoValue, |
| 132 | + isChange: false, |
| 133 | + addressType: cryptoCurrency.getAddressType(recipientAddress) ?? |
| 134 | + AddressType.unknown, |
| 135 | + ), |
| 136 | + ], |
| 137 | + utxos: {StandardInput(utxoForTx)}, |
| 138 | + ignoreCachedBalanceChecks: true, |
| 139 | + note: "Send ordinal #${(await mainDB.isar.ordinals |
| 140 | + .where() |
| 141 | + .filter() |
| 142 | + .walletIdEqualTo(walletId) |
| 143 | + .and() |
| 144 | + .utxoTXIDEqualTo(ordinalUtxo.txid) |
| 145 | + .and() |
| 146 | + .utxoVOUTEqualTo(ordinalUtxo.vout) |
| 147 | + .findFirst())?.inscriptionNumber ?? "unknown"}", |
| 148 | + ); |
| 149 | + |
| 150 | + return await prepareSend(txData: txData); |
| 151 | + } finally { |
| 152 | + // Re-block regardless of success or failure. |
| 153 | + if (wasBlocked) { |
| 154 | + final reblocked = ordinalUtxo.copyWith( |
| 155 | + isBlocked: true, |
| 156 | + blockedReason: "Ordinal", |
| 157 | + ); |
| 158 | + reblocked.id = ordinalUtxo.id; |
| 159 | + await mainDB.putUTXO(reblocked); |
| 160 | + } |
| 161 | + } |
| 162 | + } |
| 163 | + |
89 | 164 | // =================== Overrides ============================================= |
90 | 165 |
|
91 | 166 | @override |
|
0 commit comments