diff --git a/cw_bitcoin/lib/electrum.dart b/cw_bitcoin/lib/electrum.dart index fd6211b8a..8a8894fb8 100644 --- a/cw_bitcoin/lib/electrum.dart +++ b/cw_bitcoin/lib/electrum.dart @@ -217,23 +217,16 @@ class ElectrumClient { return {}; }); - Future> getTransactionExpanded( - {@required String hash}) async { - try { - final originalTx = await getTransactionRaw(hash: hash); - final vins = originalTx['vin'] as List; - - for (dynamic vin in vins) { - if (vin is Map) { - vin['tx'] = await getTransactionRaw(hash: vin['txid'] as String); + Future getTransactionHex( + {@required String hash}) async => + call(method: 'blockchain.transaction.get', params: [hash, false]) + .then((dynamic result) { + if (result is String) { + return result; } - } - return originalTx; - } catch (_) { - return {}; - } - } + return ''; + }); Future broadcastTransaction( {@required String transactionRaw}) async => diff --git a/cw_bitcoin/lib/electrum_transaction_info.dart b/cw_bitcoin/lib/electrum_transaction_info.dart index 0bf8970a4..b7438f147 100644 --- a/cw_bitcoin/lib/electrum_transaction_info.dart +++ b/cw_bitcoin/lib/electrum_transaction_info.dart @@ -1,3 +1,4 @@ +import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData; @@ -8,6 +9,34 @@ import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/format_amount.dart'; import 'package:cw_core/wallet_type.dart'; +String addressFromOutput(Uint8List script) { + try { + return bitcoin.P2PKH( + data: PaymentData(output: script), + network: bitcoin.bitcoin) + .data + .address; + } catch (_) {} + + try { + return bitcoin.P2WPKH( + data: PaymentData(output: script), + network: bitcoin.bitcoin) + .data + .address; + } catch(_) {} + + return null; +} + +class ElectrumTransactionBundle { + ElectrumTransactionBundle(this.originalTransaction, {this.ins, this.time, this.confirmations}); + final bitcoin.Transaction originalTransaction; + final List ins; + final int time; + final int confirmations; +} + class ElectrumTransactionInfo extends TransactionInfo { ElectrumTransactionInfo(this.type, {@required String id, @@ -84,6 +113,49 @@ class ElectrumTransactionInfo extends TransactionInfo { confirmations: confirmations); } + factory ElectrumTransactionInfo.fromElectrumBundle( + ElectrumTransactionBundle bundle, WalletType type, + {@required Set addresses, int height}) { + final date = DateTime.fromMillisecondsSinceEpoch(bundle.time * 1000); + var direction = TransactionDirection.incoming; + var amount = 0; + var inputAmount = 0; + var totalOutAmount = 0; + + for (var i = 0; i < bundle.originalTransaction.ins.length; i++) { + final input = bundle.originalTransaction.ins[i]; + final inputTransaction = bundle.ins[i]; + final vout = input.index; + final outTransaction = inputTransaction.outs[vout]; + final address = addressFromOutput(outTransaction.script); + inputAmount += outTransaction.value; + if (addresses.contains(address)) { + direction = TransactionDirection.outgoing; + } + } + + for (final out in bundle.originalTransaction.outs) { + totalOutAmount += out.value; + final address = addressFromOutput(out.script); + final addressExists = addresses.contains(address); + if ((direction == TransactionDirection.incoming && addressExists) || + (direction == TransactionDirection.outgoing && !addressExists)) { + amount += out.value; + } + } + + final fee = inputAmount - totalOutAmount; + return ElectrumTransactionInfo(type, + id: bundle.originalTransaction.getId(), + height: height, + isPending: false, + fee: fee, + direction: direction, + amount: amount, + date: date, + confirmations: bundle.confirmations); + } + factory ElectrumTransactionInfo.fromHexAndHeader(WalletType type, String hex, {List addresses, int height, int timestamp, int confirmations}) { final tx = bitcoin.Transaction.fromHex(hex); diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 05f6e4cf2..ac787061e 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:math'; +import 'dart:typed_data'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:hive/hive.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; @@ -31,6 +32,7 @@ import 'package:cw_core/sync_status.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_bitcoin/electrum.dart'; +import 'package:hex/hex.dart'; part 'electrum_wallet.g.dart'; @@ -479,11 +481,35 @@ abstract class ElectrumWalletBase extends WalletBase getTransactionExpanded( + {@required String hash, @required int height}) async { + final verboseTransaction = await electrumClient.getTransactionRaw(hash: hash); + final transactionHex = verboseTransaction['hex'] as String; + final original = bitcoin.Transaction.fromHex(transactionHex); + final ins = []; + final time = verboseTransaction['time'] as int; + final confirmations = verboseTransaction['time'] as int; + + for (final vin in original.ins) { + final id = HEX.encode(vin.hash.reversed.toList()); + final txHex = await electrumClient.getTransactionHex(hash: id); + final tx = bitcoin.Transaction.fromHex(txHex); + ins.add(tx); + } + + return ElectrumTransactionBundle( + original, + ins: ins, + time: time, + confirmations: confirmations); + } + Future fetchTransactionInfo( {@required String hash, @required int height}) async { - final tx = await electrumClient.getTransactionExpanded(hash: hash); - return ElectrumTransactionInfo.fromElectrumVerbose(tx, walletInfo.type, - height: height, addresses: walletAddresses.addresses); + final tx = await getTransactionExpanded(hash: hash, height: height); + final addresses = walletAddresses.addresses.map((addr) => addr.address).toSet(); + return ElectrumTransactionInfo.fromElectrumBundle( + tx,walletInfo.type, addresses: addresses, height: height); } @override @@ -524,7 +550,7 @@ abstract class ElectrumWalletBase extends WalletBase