rbf-fixes-recomended-fee-rate (#1684)

* rbf fixes

* Revert "rbf fixes"

* fix replaced transactions

* re-format electrum_wallet.dart [skip ci]

* minor fixes [skip ci]

---------

Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>
This commit is contained in:
Serhii
2024-09-20 23:57:43 +03:00
committed by GitHub
co-authored by OmarHatem
parent 4e2e5e708c
commit 32e119e24f
13 changed files with 105 additions and 45 deletions
+7 -1
View File
@@ -411,12 +411,18 @@ class CWBitcoin extends Bitcoin {
}
@override
Future<bool> canReplaceByFee(Object wallet, Object transactionInfo) async {
Future<String?> canReplaceByFee(Object wallet, Object transactionInfo) async {
final bitcoinWallet = wallet as ElectrumWallet;
final tx = transactionInfo as ElectrumTransactionInfo;
return bitcoinWallet.canReplaceByFee(tx);
}
@override
int getTransactionVSize(Object wallet, String transactionHex) {
final bitcoinWallet = wallet as ElectrumWallet;
return bitcoinWallet.transactionVSize(transactionHex);
}
@override
Future<bool> isChangeSufficientForFee(Object wallet, String txId, String newFee) async {
final bitcoinWallet = wallet as ElectrumWallet;
+32 -18
View File
@@ -1080,21 +1080,40 @@ Future<void> setup({
param1: derivations,
)));
getIt.registerFactoryParam<TransactionDetailsViewModel, TransactionInfo, void>(
(TransactionInfo transactionInfo, _) {
final wallet = getIt.get<AppStore>().wallet!;
return TransactionDetailsViewModel(
transactionInfo: transactionInfo,
transactionDescriptionBox: _transactionDescriptionBox,
wallet: wallet,
settingsStore: getIt.get<SettingsStore>(),
sendViewModel: getIt.get<SendViewModel>());
});
getIt.registerFactoryParam<TransactionDetailsViewModel, List<dynamic>, void>(
(params, _) {
final transactionInfo = params[0] as TransactionInfo;
final canReplaceByFee = params[1] as bool? ?? false;
final wallet = getIt.get<AppStore>().wallet!;
return TransactionDetailsViewModel(
transactionInfo: transactionInfo,
transactionDescriptionBox: _transactionDescriptionBox,
wallet: wallet,
settingsStore: getIt.get<SettingsStore>(),
sendViewModel: getIt.get<SendViewModel>(),
canReplaceByFee: canReplaceByFee,
);
}
);
getIt.registerFactoryParam<TransactionDetailsPage, TransactionInfo, void>(
(TransactionInfo transactionInfo, _) => TransactionDetailsPage(
transactionDetailsViewModel:
getIt.get<TransactionDetailsViewModel>(param1: transactionInfo)));
(TransactionInfo transactionInfo, _) => TransactionDetailsPage(
transactionDetailsViewModel: getIt.get<TransactionDetailsViewModel>(
param1: [transactionInfo, false])));
getIt.registerFactoryParam<RBFDetailsPage, List<dynamic>, void>(
(params, _) {
final transactionInfo = params[0] as TransactionInfo;
final txHex = params[1] as String;
return RBFDetailsPage(
transactionDetailsViewModel: getIt.get<TransactionDetailsViewModel>(
param1: [transactionInfo, true],
),
rawTransaction: txHex,
);
}
);
getIt.registerFactoryParam<NewWalletTypePage, NewWalletTypeArguments, void>(
(newWalletTypeArguments, _) {
@@ -1265,11 +1284,6 @@ Future<void> setup({
getIt.registerFactory(() => CakePayAccountPage(getIt.get<CakePayAccountViewModel>()));
getIt.registerFactoryParam<RBFDetailsPage, TransactionInfo, void>(
(TransactionInfo transactionInfo, _) => RBFDetailsPage(
transactionDetailsViewModel:
getIt.get<TransactionDetailsViewModel>(param1: transactionInfo)));
getIt.registerFactory(() => AnonPayApi(
useTorOnly: getIt.get<SettingsStore>().exchangeStatus == ExchangeApiMode.torOnly,
wallet: getIt.get<AppStore>().wallet!));
+1 -1
View File
@@ -375,7 +375,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.bumpFeePage:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<RBFDetailsPage>(param1: settings.arguments as TransactionInfo));
builder: (_) => getIt.get<RBFDetailsPage>(param1: settings.arguments as List<dynamic>));
case Routes.newSubaddress:
return CupertinoPageRoute<void>(
@@ -84,10 +84,7 @@ class TransactionsPage extends StatelessWidget {
}
final transaction = item.transaction;
final transactionType = dashboardViewModel.type == WalletType.ethereum &&
transaction.evmSignatureName == 'approval'
? ' (${transaction.evmSignatureName})'
: '';
final transactionType = dashboardViewModel.getTransactionType(transaction);
return Observer(
builder: (_) => TransactionRow(
@@ -102,7 +99,7 @@ class TransactionsPage extends StatelessWidget {
: item.formattedFiatAmount,
isPending: transaction.isPending,
title:
item.formattedTitle + item.formattedStatus + ' $transactionType',
item.formattedTitle + item.formattedStatus + transactionType,
isReceivedSilentPayment:
dashboardViewModel.type == WalletType.bitcoin &&
bitcoin!.txIsReceivedSilentPayment(transaction),
@@ -24,19 +24,22 @@ import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
class RBFDetailsPage extends BasePage {
RBFDetailsPage({required this.transactionDetailsViewModel});
RBFDetailsPage({required this.transactionDetailsViewModel, required this.rawTransaction}) {
transactionDetailsViewModel.addBumpFeesListItems(
transactionDetailsViewModel.transactionInfo, rawTransaction);
}
@override
String get title => S.current.bump_fee;
final TransactionDetailsViewModel transactionDetailsViewModel;
final String rawTransaction;
bool _effectsInstalled = false;
@override
Widget body(BuildContext context) {
_setEffects(context);
return Column(
children: [
Expanded(
@@ -166,7 +169,9 @@ class RBFDetailsPage extends BasePage {
actionRightButton: () async {
Navigator.of(popupContext).pop();
await transactionDetailsViewModel.sendViewModel.commitTransaction();
// transactionStatePopup();
try {
Navigator.of(popupContext).pop();
} catch (_) {}
},
actionLeftButton: () => Navigator.of(popupContext).pop(),
feeFiatAmount:
@@ -75,7 +75,7 @@ class TransactionDetailsPage extends BasePage {
text: S.of(context).bump_fee,
onTap: () async {
Navigator.of(context).pushNamed(Routes.bumpFeePage,
arguments: transactionDetailsViewModel.transactionInfo);
arguments: [transactionDetailsViewModel.transactionInfo, transactionDetailsViewModel.rawTransaction]);
},
),
);
@@ -764,6 +764,16 @@ abstract class DashboardViewModelBase with Store {
}
}
String getTransactionType(TransactionInfo tx) {
if (wallet.type == WalletType.bitcoin) {
if (tx.isReplaced == true) return ' (replaced)';
}
if (wallet.type == WalletType.ethereum && tx.evmSignatureName == 'approval')
return ' (${tx.evmSignatureName})';
return '';
}
Future<void> refreshDashboard() async {
reconnect();
}
@@ -36,7 +36,8 @@ abstract class TransactionDetailsViewModelBase with Store {
required this.transactionDescriptionBox,
required this.wallet,
required this.settingsStore,
required this.sendViewModel})
required this.sendViewModel,
this.canReplaceByFee = false})
: items = [],
RBFListItems = [],
newFee = 0,
@@ -51,8 +52,7 @@ abstract class TransactionDetailsViewModelBase with Store {
break;
case WalletType.bitcoin:
_addElectrumListItems(tx, dateFormat);
_addBumpFeesListItems(tx);
_checkForRBF(tx);
if(!canReplaceByFee)_checkForRBF(tx);
break;
case WalletType.litecoin:
case WalletType.bitcoinCash:
@@ -139,13 +139,11 @@ abstract class TransactionDetailsViewModelBase with Store {
bool showRecipientAddress;
bool isRecipientAddressShown;
int newFee;
String? rawTransaction;
TransactionPriority? transactionPriority;
@observable
bool _canReplaceByFee = false;
@computed
bool get canReplaceByFee => _canReplaceByFee /*&& transactionInfo.confirmations <= 0*/;
bool canReplaceByFee;
String _explorerUrl(WalletType type, String txId) {
switch (type) {
@@ -347,7 +345,7 @@ abstract class TransactionDetailsViewModelBase with Store {
items.addAll(_items);
}
void _addBumpFeesListItems(TransactionInfo tx) {
void addBumpFeesListItems(TransactionInfo tx, String rawTransaction) {
transactionPriority = bitcoin!.getBitcoinTransactionPriorityMedium();
final inputsCount = (transactionInfo.inputAddresses?.isEmpty ?? true)
? 1
@@ -361,6 +359,14 @@ abstract class TransactionDetailsViewModelBase with Store {
RBFListItems.add(StandartListItem(title: S.current.old_fee, value: tx.feeFormatted() ?? '0.0'));
if (transactionInfo.fee != null && rawTransaction.isNotEmpty) {
final size = bitcoin!.getTransactionVSize(wallet, rawTransaction);
final recommendedRate = (transactionInfo.fee! / size).round() + 1;
RBFListItems.add(
StandartListItem(title: 'New recommended fee rate', value: '$recommendedRate sat/byte'));
}
final priorities = priorityForWalletType(wallet.type);
final selectedItem = priorities.indexOf(sendViewModel.transactionPriority);
final customItem = priorities
@@ -429,8 +435,9 @@ abstract class TransactionDetailsViewModelBase with Store {
Future<void> _checkForRBF(TransactionInfo tx) async {
if (wallet.type == WalletType.bitcoin &&
transactionInfo.direction == TransactionDirection.outgoing) {
if (await bitcoin!.canReplaceByFee(wallet, tx)) {
_canReplaceByFee = true;
rawTransaction = await bitcoin!.canReplaceByFee(wallet, tx);
if (rawTransaction != null) {
canReplaceByFee = true;
}
}
}