cake_wallet/cw_bitcoin/lib/pending_bitcoin_transaction.dart
2024-11-07 13:01:32 -03:00

157 lines
4.7 KiB
Dart

import 'package:cw_bitcoin/electrum_worker/electrum_worker_params.dart';
import 'package:cw_bitcoin/electrum_worker/methods/methods.dart';
import 'package:grpc/grpc.dart';
import 'package:cw_bitcoin/exceptions.dart';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_bitcoin/electrum_transaction_info.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_mweb/cw_mweb.dart';
import 'package:cw_mweb/mwebd.pb.dart';
class PendingBitcoinTransaction with PendingTransaction {
PendingBitcoinTransaction(
this._tx,
this.type, {
required this.sendWorker,
required this.amount,
required this.fee,
required this.feeRate,
required this.hasChange,
this.isSendAll = false,
this.hasTaprootInputs = false,
this.isMweb = false,
this.utxos = const [],
}) : _listeners = <void Function(ElectrumTransactionInfo transaction)>[];
final WalletType type;
final BtcTransaction _tx;
Future<dynamic> Function(ElectrumWorkerRequest) sendWorker;
final int amount;
final int fee;
final String feeRate;
final bool isSendAll;
final bool hasChange;
final bool hasTaprootInputs;
List<UtxoWithAddress> utxos;
bool isMweb;
String? changeAddressOverride;
String? idOverride;
String? hexOverride;
List<String>? outputAddresses;
@override
String get id => idOverride ?? _tx.txId();
@override
String get hex => hexOverride ?? _tx.serialize();
@override
String get amountFormatted => BitcoinAmountUtils.bitcoinAmountToString(amount: amount);
@override
String get feeFormatted => BitcoinAmountUtils.bitcoinAmountToString(amount: fee);
@override
int? get outputCount => _tx.outputs.length;
List<TxOutput> get outputs => _tx.outputs;
bool get hasSilentPayment => _tx.hasSilentPayment;
PendingChange? get change {
try {
final change = _tx.outputs.firstWhere((out) => out.isChange);
if (changeAddressOverride != null) {
return PendingChange(changeAddressOverride!, BtcUtils.fromSatoshi(change.amount));
}
return PendingChange(change.scriptPubKey.toAddress(), BtcUtils.fromSatoshi(change.amount));
} catch (_) {
return null;
}
}
final List<void Function(ElectrumTransactionInfo transaction)> _listeners;
Future<void> _commit() async {
int? callId;
final result = await sendWorker(ElectrumWorkerBroadcastRequest(transactionRaw: hex)) as String;
// if (result.isEmpty) {
// if (callId != null) {
// final error = sendWorker(getErrorMessage(callId!));
// if (error.contains("dust")) {
// if (hasChange) {
// throw BitcoinTransactionCommitFailedDustChange();
// } else if (!isSendAll) {
// throw BitcoinTransactionCommitFailedDustOutput();
// } else {
// throw BitcoinTransactionCommitFailedDustOutputSendAll();
// }
// }
// if (error.contains("bad-txns-vout-negative")) {
// throw BitcoinTransactionCommitFailedVoutNegative();
// }
// if (error.contains("non-BIP68-final")) {
// throw BitcoinTransactionCommitFailedBIP68Final();
// }
// if (error.contains("min fee not met")) {
// throw BitcoinTransactionCommitFailedLessThanMin();
// }
// throw BitcoinTransactionCommitFailed(errorMessage: error);
// }
// throw BitcoinTransactionCommitFailed();
// }
}
Future<void> _ltcCommit() async {
try {
final stub = await CwMweb.stub();
final resp = await stub.broadcast(BroadcastRequest(rawTx: BytesUtils.fromHexString(hex)));
idOverride = resp.txid;
} on GrpcError catch (e) {
throw BitcoinTransactionCommitFailed(errorMessage: e.message);
} catch (e) {
throw BitcoinTransactionCommitFailed(errorMessage: "Unknown error: ${e.toString()}");
}
}
@override
Future<void> commit() async {
if (isMweb) {
await _ltcCommit();
} else {
await _commit();
}
_listeners.forEach((listener) => listener(transactionInfo()));
}
void addListener(void Function(ElectrumTransactionInfo transaction) listener) =>
_listeners.add(listener);
ElectrumTransactionInfo transactionInfo() => ElectrumTransactionInfo(
type,
id: id,
height: 0,
amount: amount,
direction: TransactionDirection.outgoing,
date: DateTime.now(),
time: null,
isPending: true,
isReplaced: false,
confirmations: 0,
inputAddresses: _tx.inputs.map((input) => input.txId).toList(),
outputAddresses: outputAddresses,
fee: fee,
);
}