import 'package:bitbox/bitbox.dart' as bitbox; import 'package:bitbox/src/utils/network.dart' as bitbox_utils; import 'package:isar/isar.dart'; import '../../../models/isar/models/blockchain_data/v2/input_v2.dart'; import '../../../models/isar/models/blockchain_data/v2/output_v2.dart'; import '../../../models/isar/models/blockchain_data/v2/transaction_v2.dart'; import '../../../models/isar/models/isar_models.dart'; import '../../../models/signing_data.dart'; import '../../../utilities/logger.dart'; import '../../crypto_currency/crypto_currency.dart'; import '../../crypto_currency/interfaces/electrumx_currency_interface.dart'; import '../../models/tx_data.dart'; import '../intermediate/bip39_hd_wallet.dart'; import 'electrumx_interface.dart'; mixin BCashInterface on Bip39HDWallet, ElectrumXInterface { @override Future buildTransaction({ required TxData txData, required List utxoSigningData, }) async { Logging.instance .log("Starting buildTransaction ----------", level: LogLevel.Info); // TODO: use coinlib final builder = bitbox.Bitbox.transactionBuilder( testnet: cryptoCurrency.network == CryptoCurrencyNetwork.test, ); builder.setVersion(cryptoCurrency.transactionVersion); // temp tx data to show in gui while waiting for real data from server final List tempInputs = []; final List tempOutputs = []; // Add transaction inputs for (int i = 0; i < utxoSigningData.length; i++) { builder.addInput( utxoSigningData[i].utxo.txid, utxoSigningData[i].utxo.vout, ); tempInputs.add( InputV2.isarCantDoRequiredInDefaultConstructor( scriptSigHex: "000000", scriptSigAsm: null, sequence: 0xffffffff - 1, outpoint: OutpointV2.isarCantDoRequiredInDefaultConstructor( txid: utxoSigningData[i].utxo.txid, vout: utxoSigningData[i].utxo.vout, ), addresses: utxoSigningData[i].utxo.address == null ? [] : [utxoSigningData[i].utxo.address!], valueStringSats: utxoSigningData[i].utxo.value.toString(), witness: null, innerRedeemScriptAsm: null, coinbase: null, walletOwns: true, ), ); } // Add transaction output for (var i = 0; i < txData.recipients!.length; i++) { builder.addOutput( normalizeAddress(txData.recipients![i].address), txData.recipients![i].amount.raw.toInt(), ); tempOutputs.add( OutputV2.isarCantDoRequiredInDefaultConstructor( scriptPubKeyHex: "000000", valueStringSats: txData.recipients![i].amount.raw.toString(), addresses: [ txData.recipients![i].address.toString(), ], walletOwns: (await mainDB.isar.addresses .where() .walletIdEqualTo(walletId) .filter() .valueEqualTo(txData.recipients![i].address) .or() .valueEqualTo(normalizeAddress(txData.recipients![i].address)) .valueProperty() .findFirst()) != null, ), ); } try { // Sign the transaction accordingly for (int i = 0; i < utxoSigningData.length; i++) { final bitboxEC = bitbox.ECPair.fromPrivateKey( utxoSigningData[i].keyPair!.privateKey.data, network: bitbox_utils.Network( cryptoCurrency.networkParams.privHDPrefix, cryptoCurrency.networkParams.pubHDPrefix, cryptoCurrency.network == CryptoCurrencyNetwork.test, cryptoCurrency.networkParams.p2pkhPrefix, cryptoCurrency.networkParams.wifPrefix, cryptoCurrency.networkParams.p2pkhPrefix, ), compressed: utxoSigningData[i].keyPair!.privateKey.compressed, ); builder.sign( i, bitboxEC, utxoSigningData[i].utxo.value, ); } } catch (e, s) { Logging.instance.log( "Caught exception while signing transaction: $e\n$s", level: LogLevel.Error, ); rethrow; } final builtTx = builder.build(); final vSize = builtTx.virtualSize(); return txData.copyWith( raw: builtTx.toHex(), vSize: vSize, tempTx: TransactionV2( walletId: walletId, blockHash: null, hash: builtTx.getId(), txid: builtTx.getId(), height: null, timestamp: DateTime.timestamp().millisecondsSinceEpoch ~/ 1000, inputs: List.unmodifiable(tempInputs), outputs: List.unmodifiable(tempOutputs), version: builtTx.version, type: tempOutputs.map((e) => e.walletOwns).fold(true, (p, e) => p &= e) && txData.paynymAccountLite == null ? TransactionType.sentToSelf : TransactionType.outgoing, subType: TransactionSubType.none, otherData: null, ), ); } }