Rbf fixes issues op return data plus ThorChain (#1648)

* total out amount issue * fix empty inputs and outputs addresses for new tx * fix sum value of utxo not spending * Update configure.dart * Update electrum_wallet.dart * receiving address * review fixes * add op_return data * fix rbf transaction with a memo * add memo check for ThorChain trade * code enhancement [skip ci] * code enhancement [skip ci] * directly use fromElectrumBundle function to update transaction info --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

Serhii committed Aug 24, 2024 at 16:30 UTC 40f85d215b2a4064c3586ededd57dba67336af4e
4 files changed +65 -24
cw_bitcoin/lib/electrum_transaction_info.dart
+22 -1
@@ -1,3 +1,5 @@
1 +import 'dart:convert';
2 +
3 import 'package:bitcoin_base/bitcoin_base.dart';
4 import 'package:cw_bitcoin/address_from_output.dart';
5 import 'package:cw_bitcoin/bitcoin_address_record.dart';
@@ -7,10 +9,12 @@ import 'package:cw_core/transaction_direction.dart';
9 import 'package:cw_core/transaction_info.dart';
10 import 'package:cw_core/format_amount.dart';
11 import 'package:cw_core/wallet_type.dart';
12 +import 'package:hex/hex.dart';
13
14 class ElectrumTransactionBundle {
15 ElectrumTransactionBundle(this.originalTransaction,
16 {required this.ins, required this.confirmations, this.time});
17 +
18 final BtcTransaction originalTransaction;
19 final List<BtcTransaction> ins;
20 final int? time;
@@ -125,7 +129,24 @@ class ElectrumTransactionInfo extends TransactionInfo {
129 for (final out in bundle.originalTransaction.outputs) {
130 totalOutAmount += out.amount.toInt();
131 final addressExists = addresses.contains(addressFromOutputScript(out.scriptPubKey, network));
128 - outputAddresses.add(addressFromOutputScript(out.scriptPubKey, network));
132 + final address = addressFromOutputScript(out.scriptPubKey, network);
133 +
134 + if (address.isNotEmpty) outputAddresses.add(address);
135 +
136 + // Check if the script contains OP_RETURN
137 + final script = out.scriptPubKey.script;
138 + if (script.contains('OP_RETURN')) {
139 + final index = script.indexOf('OP_RETURN');
140 + if (index + 1 <= script.length) {
141 + try {
142 + final opReturnData = script[index + 1].toString();
143 + final decodedString = utf8.decode(HEX.decode(opReturnData));
144 + outputAddresses.add('OP_RETURN:$decodedString');
145 + } catch (_) {
146 + outputAddresses.add('OP_RETURN:');
147 + }
148 + }
149 + }
150
151 if (addressExists) {
152 receivedAmounts.add(out.amount.toInt());
cw_bitcoin/lib/electrum_wallet.dart
+27 -21
@@ -43,6 +43,7 @@ import 'package:hive/hive.dart';
43 import 'package:mobx/mobx.dart';
44 import 'package:rxdart/subjects.dart';
45 import 'package:sp_scanner/sp_scanner.dart';
46 +import 'package:hex/hex.dart';
47
48 part 'electrum_wallet.g.dart';
49
@@ -1413,6 +1414,7 @@ abstract class ElectrumWalletBase
1414 List<ECPrivate> privateKeys = [];
1415
1416 var allInputsAmount = 0;
1417 + String? memo;
1418
1419 // Add inputs
1420 for (var i = 0; i < bundle.originalTransaction.inputs.length; i++) {
@@ -1451,6 +1453,22 @@ abstract class ElectrumWalletBase
1453 // Create a list of available outputs
1454 final outputs = <BitcoinOutput>[];
1455 for (final out in bundle.originalTransaction.outputs) {
1456 +
1457 + // Check if the script contains OP_RETURN
1458 + final script = out.scriptPubKey.script;
1459 + if (script.contains('OP_RETURN') && memo == null) {
1460 + final index = script.indexOf('OP_RETURN');
1461 + if (index + 1 <= script.length) {
1462 + try {
1463 + final opReturnData = script[index + 1].toString();
1464 + memo = utf8.decode(HEX.decode(opReturnData));
1465 + continue;
1466 + } catch (_) {
1467 + throw Exception('Cannot decode OP_RETURN data');
1468 + }
1469 + }
1470 + }
1471 +
1472 final address = addressFromOutputScript(out.scriptPubKey, network);
1473 final btcAddress = addressTypeFromStr(address, network);
1474 outputs.add(BitcoinOutput(address: btcAddress, value: BigInt.from(out.amount.toInt())));
@@ -1507,6 +1525,8 @@ abstract class ElectrumWalletBase
1525 outputs: outputs,
1526 fee: BigInt.from(newFee),
1527 network: network,
1528 + memo: memo,
1529 + outputOrdering: BitcoinOrdering.none,
1530 enableRBF: true,
1531 );
1532
@@ -2036,27 +2056,13 @@ abstract class ElectrumWalletBase
2056 tx.inputAddresses!.isEmpty ||
2057 tx.outputAddresses == null ||
2058 tx.outputAddresses!.isEmpty) {
2039 - List<String> inputAddresses = [];
2040 - List<String> outputAddresses = [];
2041 -
2042 - for (int i = 0; i < bundle.originalTransaction.inputs.length; i++) {
2043 - final input = bundle.originalTransaction.inputs[i];
2044 - final inputTransaction = bundle.ins[i];
2045 - final vout = input.txIndex;
2046 - final outTransaction = inputTransaction.outputs[vout];
2047 - final address = addressFromOutputScript(outTransaction.scriptPubKey, network);
2048 -
2049 - if (address.isNotEmpty) inputAddresses.add(address);
2050 - }
2051 -
2052 - for (int i = 0; i < bundle.originalTransaction.outputs.length; i++) {
2053 - final out = bundle.originalTransaction.outputs[i];
2054 - final address = addressFromOutputScript(out.scriptPubKey, network);
2055 -
2056 - if (address.isNotEmpty) outputAddresses.add(address);
2057 - }
2058 - tx.inputAddresses = inputAddresses;
2059 - tx.outputAddresses = outputAddresses;
2059 + tx = ElectrumTransactionInfo.fromElectrumBundle(
2060 + bundle,
2061 + walletInfo.type,
2062 + network,
2063 + addresses: addressesSet,
2064 + height: tx.height,
2065 + );
2066
2067 transactionHistory.addOne(tx);
2068 }
lib/view_model/exchange/exchange_view_model.dart
+7
@@ -845,6 +845,13 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
845 );
846 }
847
848 + if ((trade.memo == null || trade.memo!.isEmpty)) {
849 + return CreateTradeResult(
850 + result: false,
851 + errorMessage: 'Memo is required for Thorchain trade',
852 + );
853 + }
854 +
855 final currenciesToCheckPattern = RegExp('0x[0-9a-zA-Z]');
856
857 // Perform checks for payOutAddress
lib/view_model/transaction_details_view_model.dart
+9 -2
@@ -392,8 +392,15 @@ abstract class TransactionDetailsViewModelBase with Store {
392 }
393
394 if (transactionInfo.outputAddresses != null && transactionInfo.outputAddresses!.isNotEmpty) {
395 - RBFListItems.add(StandardExpandableListItem(
396 - title: S.current.outputs, expandableItems: transactionInfo.outputAddresses!));
395 + final outputAddresses = transactionInfo.outputAddresses!.map((element) {
396 + if (element.contains('OP_RETURN:') && element.length > 40) {
397 + return element.substring(0, 40) + '...';
398 + }
399 + return element;
400 + }).toList();
401 +
402 + RBFListItems.add(
403 + StandardExpandableListItem(title: S.current.outputs, expandableItems: outputAddresses));
404 }
405 }
406