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>
This commit is contained in:
Serhii 2024-08-24 16:30:01 +03:00 committed by GitHub
parent 8524e238b0
commit 40f85d215b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 65 additions and 24 deletions

View file

@ -1,3 +1,5 @@
import 'dart:convert';
import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/address_from_output.dart'; import 'package:cw_bitcoin/address_from_output.dart';
import 'package:cw_bitcoin/bitcoin_address_record.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/transaction_info.dart';
import 'package:cw_core/format_amount.dart'; import 'package:cw_core/format_amount.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:hex/hex.dart';
class ElectrumTransactionBundle { class ElectrumTransactionBundle {
ElectrumTransactionBundle(this.originalTransaction, ElectrumTransactionBundle(this.originalTransaction,
{required this.ins, required this.confirmations, this.time}); {required this.ins, required this.confirmations, this.time});
final BtcTransaction originalTransaction; final BtcTransaction originalTransaction;
final List<BtcTransaction> ins; final List<BtcTransaction> ins;
final int? time; final int? time;
@ -125,7 +129,24 @@ class ElectrumTransactionInfo extends TransactionInfo {
for (final out in bundle.originalTransaction.outputs) { for (final out in bundle.originalTransaction.outputs) {
totalOutAmount += out.amount.toInt(); totalOutAmount += out.amount.toInt();
final addressExists = addresses.contains(addressFromOutputScript(out.scriptPubKey, network)); 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) { if (addressExists) {
receivedAmounts.add(out.amount.toInt()); receivedAmounts.add(out.amount.toInt());

View file

@ -43,6 +43,7 @@ import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:rxdart/subjects.dart'; import 'package:rxdart/subjects.dart';
import 'package:sp_scanner/sp_scanner.dart'; import 'package:sp_scanner/sp_scanner.dart';
import 'package:hex/hex.dart';
part 'electrum_wallet.g.dart'; part 'electrum_wallet.g.dart';
@ -1413,6 +1414,7 @@ abstract class ElectrumWalletBase
List<ECPrivate> privateKeys = []; List<ECPrivate> privateKeys = [];
var allInputsAmount = 0; var allInputsAmount = 0;
String? memo;
// Add inputs // Add inputs
for (var i = 0; i < bundle.originalTransaction.inputs.length; i++) { for (var i = 0; i < bundle.originalTransaction.inputs.length; i++) {
@ -1451,6 +1453,22 @@ abstract class ElectrumWalletBase
// Create a list of available outputs // Create a list of available outputs
final outputs = <BitcoinOutput>[]; final outputs = <BitcoinOutput>[];
for (final out in bundle.originalTransaction.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 address = addressFromOutputScript(out.scriptPubKey, network);
final btcAddress = addressTypeFromStr(address, network); final btcAddress = addressTypeFromStr(address, network);
outputs.add(BitcoinOutput(address: btcAddress, value: BigInt.from(out.amount.toInt()))); outputs.add(BitcoinOutput(address: btcAddress, value: BigInt.from(out.amount.toInt())));
@ -1507,6 +1525,8 @@ abstract class ElectrumWalletBase
outputs: outputs, outputs: outputs,
fee: BigInt.from(newFee), fee: BigInt.from(newFee),
network: network, network: network,
memo: memo,
outputOrdering: BitcoinOrdering.none,
enableRBF: true, enableRBF: true,
); );
@ -2036,27 +2056,13 @@ abstract class ElectrumWalletBase
tx.inputAddresses!.isEmpty || tx.inputAddresses!.isEmpty ||
tx.outputAddresses == null || tx.outputAddresses == null ||
tx.outputAddresses!.isEmpty) { tx.outputAddresses!.isEmpty) {
List<String> inputAddresses = []; tx = ElectrumTransactionInfo.fromElectrumBundle(
List<String> outputAddresses = []; bundle,
walletInfo.type,
for (int i = 0; i < bundle.originalTransaction.inputs.length; i++) { network,
final input = bundle.originalTransaction.inputs[i]; addresses: addressesSet,
final inputTransaction = bundle.ins[i]; height: tx.height,
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;
transactionHistory.addOne(tx); transactionHistory.addOne(tx);
} }

View file

@ -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]'); final currenciesToCheckPattern = RegExp('0x[0-9a-zA-Z]');
// Perform checks for payOutAddress // Perform checks for payOutAddress

View file

@ -392,8 +392,15 @@ abstract class TransactionDetailsViewModelBase with Store {
} }
if (transactionInfo.outputAddresses != null && transactionInfo.outputAddresses!.isNotEmpty) { if (transactionInfo.outputAddresses != null && transactionInfo.outputAddresses!.isNotEmpty) {
RBFListItems.add(StandardExpandableListItem( final outputAddresses = transactionInfo.outputAddresses!.map((element) {
title: S.current.outputs, expandableItems: transactionInfo.outputAddresses!)); 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));
} }
} }