mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-18 05:51:34 +02:00
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:
@@ -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}";
|
||||
|
||||
@@ -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}";
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user