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