refactor: Optimize gas fee calculations for Base chain transactions (#2613)

* refactor: Optimize gas fee calculations for Base chain transactions

* feat: Added an abstract method for priority fee calculations in EVMChainWallet class, and implemented it across the evm wallets.

* fix: Estimated fee on send screen for base wallet different from fee on swipe to send bottomsheet
This commit is contained in:
David Adegoke
2025-11-06 15:10:36 +01:00
committed by GitHub
parent 507be1402e
commit 68dd3f2809
6 changed files with 77 additions and 68 deletions
+4
View File
@@ -15,6 +15,7 @@ import 'package:cw_core/wallet_keys_file.dart';
import 'package:cw_evm/evm_chain_transaction_history.dart';
import 'package:cw_evm/evm_chain_transaction_info.dart';
import 'package:cw_evm/evm_chain_transaction_model.dart';
import 'package:cw_evm/evm_chain_transaction_priority.dart';
import 'package:cw_evm/evm_chain_wallet.dart';
import 'package:cw_evm/evm_erc20_balance.dart';
@@ -34,6 +35,9 @@ class ArbitrumWallet extends EVMChainWallet {
@override
bool get hasPriorityFee => false;
@override
int getTotalPriorityFee(EVMChainTransactionPriority priority) => 0;
@override
Future<void> initErc20TokensBox() async {
final boxName = "${walletInfo.name.replaceAll(" ", "_")}_${Erc20Token.arbitrumBoxName}";
+12
View File
@@ -15,8 +15,10 @@ import 'package:cw_core/wallet_keys_file.dart';
import 'package:cw_evm/evm_chain_transaction_history.dart';
import 'package:cw_evm/evm_chain_transaction_info.dart';
import 'package:cw_evm/evm_chain_transaction_model.dart';
import 'package:cw_evm/evm_chain_transaction_priority.dart';
import 'package:cw_evm/evm_chain_wallet.dart';
import 'package:cw_evm/evm_erc20_balance.dart';
import 'package:web3dart/web3dart.dart';
class BaseWallet extends EVMChainWallet {
BaseWallet({
@@ -31,6 +33,16 @@ class BaseWallet extends EVMChainWallet {
super.passphrase,
}) : super(nativeCurrency: CryptoCurrency.baseEth);
@override
int getTotalPriorityFee(EVMChainTransactionPriority priority) {
return switch (priority) {
EVMChainTransactionPriority.fast => EtherAmount.fromInt(EtherUnit.mwei, 5).getInWei.toInt(),
EVMChainTransactionPriority.medium => EtherAmount.fromInt(EtherUnit.mwei, 3).getInWei.toInt(),
EVMChainTransactionPriority.slow => EtherAmount.fromInt(EtherUnit.mwei, 1).getInWei.toInt(),
_ => EtherAmount.fromInt(EtherUnit.mwei, 1).getInWei.toInt(),
};
}
@override
Future<void> initErc20TokensBox() async {
final boxName = "${walletInfo.name.replaceAll(" ", "_")}_${Erc20Token.baseBoxName}";
+11 -2
View File
@@ -15,8 +15,10 @@ import 'package:cw_ethereum/ethereum_transaction_info.dart';
import 'package:cw_evm/evm_chain_transaction_history.dart';
import 'package:cw_evm/evm_chain_transaction_info.dart';
import 'package:cw_evm/evm_chain_transaction_model.dart';
import 'package:cw_evm/evm_chain_transaction_priority.dart';
import 'package:cw_evm/evm_chain_wallet.dart';
import 'package:cw_evm/evm_erc20_balance.dart';
import 'package:web3dart/web3dart.dart';
class EthereumWallet extends EVMChainWallet {
EthereumWallet({
@@ -31,6 +33,11 @@ class EthereumWallet extends EVMChainWallet {
super.passphrase,
}) : super(nativeCurrency: CryptoCurrency.eth);
@override
int getTotalPriorityFee(EVMChainTransactionPriority priority) {
return EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
}
@override
void addInitialTokens() {
final initialErc20Tokens = DefaultEthereumErc20Tokens().initialErc20Tokens;
@@ -38,9 +45,11 @@ class EthereumWallet extends EVMChainWallet {
for (final token in initialErc20Tokens) {
if (!evmChainErc20TokensBox.containsKey(token.contractAddress)) {
evmChainErc20TokensBox.put(token.contractAddress, token);
} else { // update existing token
} else {
// update existing token
final existingToken = evmChainErc20TokensBox.get(token.contractAddress);
evmChainErc20TokensBox.put(token.contractAddress, Erc20Token.copyWith(token, enabled: existingToken!.enabled));
evmChainErc20TokensBox.put(
token.contractAddress, Erc20Token.copyWith(token, enabled: existingToken!.enabled));
}
}
}
+1 -1
View File
@@ -61,7 +61,7 @@ abstract class EVMChainTransactionInfo extends TransactionInfo {
@override
String feeFormatted() {
final amount = (ethFee / BigInt.from(10).pow(18)).toString();
return '${amount.substring(0, min(10, amount.length))} $feeCurrency';
return '${amount.substring(0, min(18, amount.length))} $feeCurrency';
}
Map<String, dynamic> toJson() => {
+19 -63
View File
@@ -296,7 +296,7 @@ abstract class EVMChainWalletBase
int priorityFee = 0;
if (hasPriorityFee) {
if (priority is EVMChainTransactionPriority) {
priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
priorityFee = getTotalPriorityFee(priority);
}
}
@@ -324,7 +324,7 @@ abstract class EVMChainWalletBase
int priorityFee = 0;
if (hasPriorityFee) {
if (priority is EVMChainTransactionPriority) {
priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
priorityFee = getTotalPriorityFee(priority);
}
}
@@ -348,6 +348,8 @@ abstract class EVMChainWalletBase
}
}
int getTotalPriorityFee(EVMChainTransactionPriority priority);
/// Allows more customization to the fetch estimatedFees flow.
///
/// We are able to pass in:
@@ -365,7 +367,7 @@ abstract class EVMChainWalletBase
int priorityFee = 0;
if (hasPriorityFee && priority != null) {
if (priority is EVMChainTransactionPriority) {
priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
priorityFee = getTotalPriorityFee(priority);
}
}
@@ -375,46 +377,10 @@ abstract class EVMChainWalletBase
int maxFeePerGas;
int adjustedGasPrice;
bool isPolygon = _client.chainId == 137;
if (gasBaseFee != null) {
// MaxFeePerGas with EIP1559;
maxFeePerGas = gasBaseFee + priorityFee;
} else {
// MaxFeePerGas with gasPrice
maxFeePerGas = gasPrice + priorityFee;
}
maxFeePerGas = gasBaseFee != null ? (gasBaseFee + priorityFee) : (gasPrice + priorityFee);
adjustedGasPrice = maxFeePerGas;
// Polygon has a minimum priority fee of 25 gwei
if (isPolygon) {
int minPriorityFee = 25;
int minPriorityFeeWei =
EtherAmount.fromInt(EtherUnit.gwei, minPriorityFee).getInWei.toInt();
// Calculate user selected priority-based additional fee on top of minimum
int additionalPriorityFee = 0;
switch (priority) {
case EVMChainTransactionPriority.slow:
// We use minimum priority fee only
additionalPriorityFee = 0;
break;
case EVMChainTransactionPriority.medium:
// We add 15 gwei on top of minimum
additionalPriorityFee = EtherAmount.fromInt(EtherUnit.gwei, 15).getInWei.toInt();
break;
case EVMChainTransactionPriority.fast:
// We add 35 gwei on top of minimum
additionalPriorityFee = EtherAmount.fromInt(EtherUnit.gwei, 35).getInWei.toInt();
break;
}
int totalPriorityFee = minPriorityFeeWei + additionalPriorityFee;
adjustedGasPrice = gasPrice + totalPriorityFee;
maxFeePerGas = gasPrice + totalPriorityFee;
}
final estimatedGas = await _client.getEstimatedGasUnitsForTransaction(
contractAddress: contractAddress,
senderAddress: _evmChainPrivateKey.address,
@@ -561,7 +527,9 @@ abstract class EVMChainWalletBase
EVMChainFormatter.parseEVMChainAmountToDouble(output.formattedCryptoAmount ?? 0);
totalAmount = parseFixed(
EVMChainFormatter.truncateDecimals(totalOriginalAmount.toString(), exponent), exponent);
EVMChainFormatter.truncateDecimals(totalOriginalAmount.toString(), exponent),
exponent,
);
}
if (output.sendAll && transactionCurrency is Erc20Token) {
@@ -581,32 +549,20 @@ abstract class EVMChainWalletBase
if (output.sendAll && transactionCurrency is! Erc20Token) {
if (_client.chainId == 8453) {
// Add a safety buffer for Base chain to account for gas price fluctuations
// Use 1% buffer or minimum 1000 wei (0.000001 ETH), whichever is higher
final gasBufferPercent =
estimatedFeesForTransaction * BigInt.from(101) ~/ BigInt.from(100);
final gasBufferMin = estimatedFeesForTransaction + BigInt.from(1000);
// Applying a small buffer to account for gas price fluctuations
// 10% or minimum 10,000 wei, whichever is higher
final refinedGasFee = estimatedFeesForTransaction;
final gasBufferPercent = refinedGasFee * BigInt.from(110) ~/ BigInt.from(100);
final gasBufferMin = refinedGasFee + BigInt.from(10000);
final gasBuffer = gasBufferPercent > gasBufferMin ? gasBufferPercent : gasBufferMin;
// Using the buffered fee for the final amount
totalAmount = (currencyBalance.balance - gasBuffer);
// Re-estimate gas with the correct amount to get more accurate gas estimation
final refinedGasFeesModel = await calculateActualEstimatedFeeForCreateTransaction(
amount: totalAmount,
receivingAddressHex: toAddress,
priority: _credentials.priority,
contractAddress: contractAddress,
);
// Use the higher of the two gas estimations to be safe
final refinedGasFee = BigInt.from(refinedGasFeesModel.estimatedGasFee);
estimatedFeesForTransaction = refinedGasFee > gasBuffer ? refinedGasFee : gasBuffer;
estimatedGasUnitsForTransaction = refinedGasFeesModel.estimatedGasUnits;
maxFeePerGasForTransaction = refinedGasFeesModel.maxFeePerGas;
estimatedFeesForTransaction = gasBuffer;
} else {
// Calculating the final amount with the estimated gas fee
totalAmount = (currencyBalance.balance - estimatedFeesForTransaction);
}
// Final amount calculation with the higher gas estimation
totalAmount = (currencyBalance.balance - estimatedFeesForTransaction);
}
// check the fees on the base currency
+30 -2
View File
@@ -11,12 +11,14 @@ import 'package:cw_core/wallet_keys_file.dart';
import 'package:cw_evm/evm_chain_transaction_history.dart';
import 'package:cw_evm/evm_chain_transaction_info.dart';
import 'package:cw_evm/evm_chain_transaction_model.dart';
import 'package:cw_evm/evm_chain_transaction_priority.dart';
import 'package:cw_evm/evm_chain_wallet.dart';
import 'package:cw_evm/evm_erc20_balance.dart';
import 'package:cw_polygon/default_polygon_erc20_tokens.dart';
import 'package:cw_polygon/polygon_client.dart';
import 'package:cw_polygon/polygon_transaction_history.dart';
import 'package:cw_polygon/polygon_transaction_info.dart';
import 'package:web3dart/web3dart.dart';
class PolygonWallet extends EVMChainWallet {
PolygonWallet({
@@ -31,6 +33,30 @@ class PolygonWallet extends EVMChainWallet {
super.passphrase,
}) : super(nativeCurrency: CryptoCurrency.maticpoly);
@override
int getTotalPriorityFee(EVMChainTransactionPriority priority) {
// Polygon has a minimum priority fee of 25 gwei
int minPriorityFee = 25;
int minPriorityFeeWei = EtherAmount.fromInt(EtherUnit.gwei, minPriorityFee).getInWei.toInt();
// Calculate user selected priority-based additional fee on top of minimum
int additionalPriorityFee = 0;
switch (priority) {
case EVMChainTransactionPriority.slow:
additionalPriorityFee = 0;
break;
case EVMChainTransactionPriority.medium:
additionalPriorityFee = EtherAmount.fromInt(EtherUnit.gwei, 15).getInWei.toInt();
break;
case EVMChainTransactionPriority.fast:
additionalPriorityFee = EtherAmount.fromInt(EtherUnit.gwei, 35).getInWei.toInt();
break;
}
return (minPriorityFeeWei + additionalPriorityFee);
}
@override
Future<void> initErc20TokensBox() async {
final boxName = "${walletInfo.name.replaceAll(" ", "_")}_ ${Erc20Token.polygonBoxName}";
@@ -48,9 +74,11 @@ class PolygonWallet extends EVMChainWallet {
for (final token in initialErc20Tokens) {
if (!evmChainErc20TokensBox.containsKey(token.contractAddress)) {
evmChainErc20TokensBox.put(token.contractAddress, token);
} else { // update existing token
} else {
// update existing token
final existingToken = evmChainErc20TokensBox.get(token.contractAddress);
evmChainErc20TokensBox.put(token.contractAddress, Erc20Token.copyWith(token, enabled: existingToken!.enabled));
evmChainErc20TokensBox.put(
token.contractAddress, Erc20Token.copyWith(token, enabled: existingToken!.enabled));
}
}
}