From 40f85d215b2a4064c3586ededd57dba67336af4e Mon Sep 17 00:00:00 2001 From: Serhii Date: Sat, 24 Aug 2024 16:30:01 +0300 Subject: [PATCH] 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 --- cw_bitcoin/lib/electrum_transaction_info.dart | 23 ++++++++- cw_bitcoin/lib/electrum_wallet.dart | 48 +++++++++++-------- .../exchange/exchange_view_model.dart | 7 +++ .../transaction_details_view_model.dart | 11 ++++- 4 files changed, 65 insertions(+), 24 deletions(-) diff --git a/cw_bitcoin/lib/electrum_transaction_info.dart b/cw_bitcoin/lib/electrum_transaction_info.dart index ee3daa0e0..ebd90ff2b 100644 --- a/cw_bitcoin/lib/electrum_transaction_info.dart +++ b/cw_bitcoin/lib/electrum_transaction_info.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:cw_bitcoin/address_from_output.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; @@ -7,10 +9,12 @@ import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/format_amount.dart'; import 'package:cw_core/wallet_type.dart'; +import 'package:hex/hex.dart'; class ElectrumTransactionBundle { ElectrumTransactionBundle(this.originalTransaction, {required this.ins, required this.confirmations, this.time}); + final BtcTransaction originalTransaction; final List ins; final int? time; @@ -125,7 +129,24 @@ class ElectrumTransactionInfo extends TransactionInfo { for (final out in bundle.originalTransaction.outputs) { totalOutAmount += out.amount.toInt(); final addressExists = addresses.contains(addressFromOutputScript(out.scriptPubKey, network)); - outputAddresses.add(addressFromOutputScript(out.scriptPubKey, network)); + final address = addressFromOutputScript(out.scriptPubKey, network); + + if (address.isNotEmpty) outputAddresses.add(address); + + // Check if the script contains OP_RETURN + final script = out.scriptPubKey.script; + if (script.contains('OP_RETURN')) { + final index = script.indexOf('OP_RETURN'); + if (index + 1 <= script.length) { + try { + final opReturnData = script[index + 1].toString(); + final decodedString = utf8.decode(HEX.decode(opReturnData)); + outputAddresses.add('OP_RETURN:$decodedString'); + } catch (_) { + outputAddresses.add('OP_RETURN:'); + } + } + } if (addressExists) { receivedAmounts.add(out.amount.toInt()); diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 2a57c8d5c..b763b175b 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -43,6 +43,7 @@ import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:rxdart/subjects.dart'; import 'package:sp_scanner/sp_scanner.dart'; +import 'package:hex/hex.dart'; part 'electrum_wallet.g.dart'; @@ -1413,6 +1414,7 @@ abstract class ElectrumWalletBase List privateKeys = []; var allInputsAmount = 0; + String? memo; // Add inputs for (var i = 0; i < bundle.originalTransaction.inputs.length; i++) { @@ -1451,6 +1453,22 @@ abstract class ElectrumWalletBase // Create a list of available outputs final outputs = []; for (final out in bundle.originalTransaction.outputs) { + + // Check if the script contains OP_RETURN + final script = out.scriptPubKey.script; + if (script.contains('OP_RETURN') && memo == null) { + final index = script.indexOf('OP_RETURN'); + if (index + 1 <= script.length) { + try { + final opReturnData = script[index + 1].toString(); + memo = utf8.decode(HEX.decode(opReturnData)); + continue; + } catch (_) { + throw Exception('Cannot decode OP_RETURN data'); + } + } + } + final address = addressFromOutputScript(out.scriptPubKey, network); final btcAddress = addressTypeFromStr(address, network); outputs.add(BitcoinOutput(address: btcAddress, value: BigInt.from(out.amount.toInt()))); @@ -1507,6 +1525,8 @@ abstract class ElectrumWalletBase outputs: outputs, fee: BigInt.from(newFee), network: network, + memo: memo, + outputOrdering: BitcoinOrdering.none, enableRBF: true, ); @@ -2036,27 +2056,13 @@ abstract class ElectrumWalletBase tx.inputAddresses!.isEmpty || tx.outputAddresses == null || tx.outputAddresses!.isEmpty) { - List inputAddresses = []; - List outputAddresses = []; - - for (int i = 0; i < bundle.originalTransaction.inputs.length; i++) { - final input = bundle.originalTransaction.inputs[i]; - final inputTransaction = bundle.ins[i]; - final vout = input.txIndex; - final outTransaction = inputTransaction.outputs[vout]; - final address = addressFromOutputScript(outTransaction.scriptPubKey, network); - - if (address.isNotEmpty) inputAddresses.add(address); - } - - for (int i = 0; i < bundle.originalTransaction.outputs.length; i++) { - final out = bundle.originalTransaction.outputs[i]; - final address = addressFromOutputScript(out.scriptPubKey, network); - - if (address.isNotEmpty) outputAddresses.add(address); - } - tx.inputAddresses = inputAddresses; - tx.outputAddresses = outputAddresses; + tx = ElectrumTransactionInfo.fromElectrumBundle( + bundle, + walletInfo.type, + network, + addresses: addressesSet, + height: tx.height, + ); transactionHistory.addOne(tx); } diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index f2ea8eeb4..2bbe9954e 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -845,6 +845,13 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with ); } + if ((trade.memo == null || trade.memo!.isEmpty)) { + return CreateTradeResult( + result: false, + errorMessage: 'Memo is required for Thorchain trade', + ); + } + final currenciesToCheckPattern = RegExp('0x[0-9a-zA-Z]'); // Perform checks for payOutAddress diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart index aa63ce860..e4f9c3786 100644 --- a/lib/view_model/transaction_details_view_model.dart +++ b/lib/view_model/transaction_details_view_model.dart @@ -392,8 +392,15 @@ abstract class TransactionDetailsViewModelBase with Store { } if (transactionInfo.outputAddresses != null && transactionInfo.outputAddresses!.isNotEmpty) { - RBFListItems.add(StandardExpandableListItem( - title: S.current.outputs, expandableItems: transactionInfo.outputAddresses!)); + final outputAddresses = transactionInfo.outputAddresses!.map((element) { + if (element.contains('OP_RETURN:') && element.length > 40) { + return element.substring(0, 40) + '...'; + } + return element; + }).toList(); + + RBFListItems.add( + StandardExpandableListItem(title: S.current.outputs, expandableItems: outputAddresses)); } }