diff --git a/cw_ethereum/lib/ethereum_client.dart b/cw_ethereum/lib/ethereum_client.dart index d73dba715..eaf5018e9 100644 --- a/cw_ethereum/lib/ethereum_client.dart +++ b/cw_ethereum/lib/ethereum_client.dart @@ -1,4 +1,5 @@ import 'package:cw_core/node.dart'; +import 'package:cw_ethereum/ethereum_transaction_priority.dart'; import 'package:http/http.dart'; import 'package:web3dart/web3dart.dart'; @@ -21,5 +22,16 @@ class EthereumClient { return _client.getBalance(private.address); } - Future getGasPrice() async => _client.getGasPrice(); -} \ No newline at end of file + Future getGasUnitPrice() async { + final gasPrice = await _client.getGasPrice(); + return gasPrice.getInWei.toInt(); + } + + Future> getEstimatedGasForPriorities() async { + final result = await Future.wait(EthereumTransactionPriority.all.map((priority) => + _client.estimateGas( + maxPriorityFeePerGas: EtherAmount.fromUnitAndValue(EtherUnit.gwei, priority.value)))); + + return result.map((e) => e.toInt()).toList(); + } +} diff --git a/cw_ethereum/lib/ethereum_transaction_priority.dart b/cw_ethereum/lib/ethereum_transaction_priority.dart new file mode 100644 index 000000000..9b8684ace --- /dev/null +++ b/cw_ethereum/lib/ethereum_transaction_priority.dart @@ -0,0 +1,52 @@ +import 'package:cw_core/transaction_priority.dart'; + +class EthereumTransactionPriority extends TransactionPriority { + final int value; + + const EthereumTransactionPriority({required String title, required int raw, required this.value}) + : super(title: title, raw: raw); + + static const List all = [fast, medium, slow]; + static const EthereumTransactionPriority slow = + EthereumTransactionPriority(title: 'Slow', raw: 0, value: 2); + static const EthereumTransactionPriority medium = + EthereumTransactionPriority(title: 'Medium', raw: 1, value: 5); + static const EthereumTransactionPriority fast = + EthereumTransactionPriority(title: 'Fast', raw: 2, value: 10); + + static EthereumTransactionPriority deserialize({required int raw}) { + switch (raw) { + case 0: + return slow; + case 1: + return medium; + case 2: + return fast; + default: + throw Exception('Unexpected token: $raw for EthereumTransactionPriority deserialize'); + } + } + + String get units => 'gas'; + + @override + String toString() { + var label = ''; + + switch (this) { + case EthereumTransactionPriority.slow: + label = 'slow'; + break; + case EthereumTransactionPriority.medium: + label = 'Medium'; + break; + case EthereumTransactionPriority.fast: + label = 'Fast'; + break; + default: + break; + } + + return label; + } +} diff --git a/cw_ethereum/lib/ethereum_wallet.dart b/cw_ethereum/lib/ethereum_wallet.dart index ada98bb8d..2800e35ef 100644 --- a/cw_ethereum/lib/ethereum_wallet.dart +++ b/cw_ethereum/lib/ethereum_wallet.dart @@ -14,6 +14,7 @@ import 'package:cw_ethereum/ethereum_balance.dart'; import 'package:cw_ethereum/ethereum_client.dart'; import 'package:cw_ethereum/ethereum_transaction_history.dart'; import 'package:cw_ethereum/ethereum_transaction_info.dart'; +import 'package:cw_ethereum/ethereum_transaction_priority.dart'; import 'package:cw_ethereum/ethereum_wallet_addresses.dart'; import 'package:cw_ethereum/file.dart'; import 'package:mobx/mobx.dart'; @@ -37,6 +38,7 @@ abstract class EthereumWalletBase }) : syncStatus = NotConnectedSyncStatus(), _password = password, _mnemonic = mnemonic, + _feeRates = [], walletAddresses = EthereumWalletAddresses(walletInfo), balance = ObservableMap.of( {CryptoCurrency.eth: initialBalance ?? EthereumBalance(available: 0, additional: 0)}), @@ -51,7 +53,8 @@ abstract class EthereumWalletBase late EthereumClient _client; - EtherAmount? _gasPrice; + List _feeRates; + int? _gasPrice; @override WalletAddresses walletAddresses; @@ -134,10 +137,13 @@ abstract class EthereumWalletBase try { syncStatus = AttemptingSyncStatus(); await _updateBalance(); - _gasPrice = await _client.getGasPrice(); + _gasPrice = await _client.getGasUnitPrice(); + _feeRates = await _client.getEstimatedGasForPriorities(); Timer.periodic( - const Duration(minutes: 1), (timer) async => _gasPrice = await _client.getGasPrice()); + const Duration(minutes: 1), (timer) async => _gasPrice = await _client.getGasUnitPrice()); + Timer.periodic(const Duration(minutes: 1), + (timer) async => _feeRates = await _client.getEstimatedGasForPriorities()); syncStatus = SyncedSyncStatus(); } catch (e, stacktrace) { @@ -147,12 +153,16 @@ abstract class EthereumWalletBase } } - int feeRate() { - if (_gasPrice != null) { - return _gasPrice!.getInEther.toInt(); - } + int feeRate(TransactionPriority priority) { + try { + if (priority is EthereumTransactionPriority) { + return _feeRates[priority.raw] * _gasPrice!; + } - return 0; + return 0; + } catch (e) { + return 0; + } } Future makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type); @@ -199,8 +209,10 @@ abstract class EthereumWalletBase Future getPrivateKey(String mnemonic, String password) async { final seed = bip39.mnemonicToSeedHex(mnemonic); - final master = await ED25519_HD_KEY.getMasterKeyFromSeed(HEX.decode(seed), - masterSecret: password); + final master = await ED25519_HD_KEY.getMasterKeyFromSeed( + HEX.decode(seed), + masterSecret: password, + ); final privateKey = HEX.encode(master.key); return privateKey; } diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index b9ffc2aff..042531e30 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -25,6 +25,7 @@ class PreferencesKey { static const bitcoinTransactionPriority = 'current_fee_priority_bitcoin'; static const havenTransactionPriority = 'current_fee_priority_haven'; static const litecoinTransactionPriority = 'current_fee_priority_litecoin'; + static const ethereumTransactionPriority = 'current_fee_priority_ethereum'; static const shouldShowReceiveWarning = 'should_show_receive_warning'; static const shouldShowYatPopup = 'should_show_yat_popup'; static const moneroWalletPasswordUpdateV1Base = 'monero_wallet_update_v1'; diff --git a/lib/entities/priority_for_wallet_type.dart b/lib/entities/priority_for_wallet_type.dart index 927ab8803..eb9417763 100644 --- a/lib/entities/priority_for_wallet_type.dart +++ b/lib/entities/priority_for_wallet_type.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/haven/haven.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cw_core/transaction_priority.dart'; @@ -14,6 +15,8 @@ List priorityForWalletType(WalletType type) { return bitcoin!.getLitecoinTransactionPriorities(); case WalletType.haven: return haven!.getTransactionPriorities(); + case WalletType.ethereum: + return ethereum!.getTransactionPriorities(); default: return []; } diff --git a/lib/ethereum/cw_ethereum.dart b/lib/ethereum/cw_ethereum.dart index c15a97e06..db562c2ea 100644 --- a/lib/ethereum/cw_ethereum.dart +++ b/lib/ethereum/cw_ethereum.dart @@ -24,4 +24,20 @@ class CWEthereum extends Ethereum { @override String getAddress(WalletBase wallet) => (wallet as EthereumWallet).walletAddresses.address; + + @override + TransactionPriority getDefaultTransactionPriority() => EthereumTransactionPriority.medium; + + @override + List getTransactionPriorities() => EthereumTransactionPriority.all; + + @override + TransactionPriority deserializeEthereumTransactionPriority(int raw) => + EthereumTransactionPriority.deserialize(raw: raw); + + @override + int getEstimatedFee(Object wallet, TransactionPriority priority) { + final ethereumWallet = wallet as EthereumWallet; + return ethereumWallet.feeRate(priority); + } } diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 081cb33a6..ba9ca146e 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -1,6 +1,7 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/entities/pin_code_required_duration.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; +import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/themes/theme_list.dart'; @@ -45,7 +46,8 @@ abstract class SettingsStoreBase with Store { TransactionPriority? initialBitcoinTransactionPriority, TransactionPriority? initialMoneroTransactionPriority, TransactionPriority? initialHavenTransactionPriority, - TransactionPriority? initialLitecoinTransactionPriority}) + TransactionPriority? initialLitecoinTransactionPriority, + TransactionPriority? initialEthereumTransactionPriority}) : nodes = ObservableMap.of(nodes), _sharedPreferences = sharedPreferences, fiatCurrency = initialFiatCurrency, @@ -76,6 +78,10 @@ abstract class SettingsStoreBase with Store { priority[WalletType.litecoin] = initialLitecoinTransactionPriority; } + if (initialEthereumTransactionPriority != null) { + priority[WalletType.ethereum] = initialEthereumTransactionPriority; + } + reaction( (_) => fiatCurrency, (FiatCurrency fiatCurrency) => sharedPreferences.setString( @@ -101,6 +107,9 @@ abstract class SettingsStoreBase with Store { case WalletType.haven: key = PreferencesKey.havenTransactionPriority; break; + case WalletType.ethereum: + key = PreferencesKey.ethereumTransactionPriority; + break; default: key = null; } @@ -257,6 +266,7 @@ abstract class SettingsStoreBase with Store { TransactionPriority? havenTransactionPriority; TransactionPriority? litecoinTransactionPriority; + TransactionPriority? ethereumTransactionPriority; if (sharedPreferences.getInt(PreferencesKey.havenTransactionPriority) != null) { havenTransactionPriority = monero?.deserializeMoneroTransactionPriority( @@ -266,11 +276,16 @@ abstract class SettingsStoreBase with Store { litecoinTransactionPriority = bitcoin?.deserializeLitecoinTransactionPriority( sharedPreferences.getInt(PreferencesKey.litecoinTransactionPriority)!); } + if (sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority) != null) { + ethereumTransactionPriority = bitcoin?.deserializeLitecoinTransactionPriority( + sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!); + } moneroTransactionPriority ??= monero?.getDefaultTransactionPriority(); bitcoinTransactionPriority ??= bitcoin?.getMediumTransactionPriority(); havenTransactionPriority ??= monero?.getDefaultTransactionPriority(); litecoinTransactionPriority ??= bitcoin?.getLitecoinTransactionPriorityMedium(); + ethereumTransactionPriority ??= ethereum?.getDefaultTransactionPriority(); final currentBalanceDisplayMode = BalanceDisplayMode.deserialize( raw: sharedPreferences @@ -371,6 +386,7 @@ abstract class SettingsStoreBase with Store { initialBitcoinTransactionPriority: bitcoinTransactionPriority, initialHavenTransactionPriority: havenTransactionPriority, initialLitecoinTransactionPriority: litecoinTransactionPriority, + initialEthereumTransactionPriority: ethereumTransactionPriority, shouldShowYatPopup: shouldShowYatPopup); } @@ -398,6 +414,11 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getInt(PreferencesKey.litecoinTransactionPriority)!) ?? priority[WalletType.litecoin]!; } + if (sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority) != null) { + priority[WalletType.ethereum] = ethereum?.deserializeEthereumTransactionPriority( + sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!) ?? + priority[WalletType.ethereum]!; + } balanceDisplayMode = BalanceDisplayMode.deserialize( raw: sharedPreferences diff --git a/tool/configure.dart b/tool/configure.dart index 60a4c8c18..18b62ccf0 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -477,6 +477,7 @@ Future generateEthereum(bool hasImplementation) async { const ethereumCommonHeaders = """ """; const ethereumCWHeaders = """ +import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; @@ -485,6 +486,7 @@ import 'package:cw_ethereum/ethereum_mnemonics.dart'; import 'package:cw_ethereum/ethereum_wallet.dart'; import 'package:cw_ethereum/ethereum_wallet_creation_credentials.dart'; import 'package:cw_ethereum/ethereum_wallet_service.dart'; +import 'package:cw_ethereum/ethereum_transaction_priority.dart'; import 'package:hive/hive.dart'; """; const ethereumCwPart = "part 'cw_ethereum.dart';"; @@ -495,6 +497,10 @@ abstract class Ethereum { WalletCredentials createEthereumNewWalletCredentials({required String name, WalletInfo? walletInfo}); WalletCredentials createEthereumRestoreWalletFromSeedCredentials({required String name, required String mnemonic, required String password}); String getAddress(WalletBase wallet); + TransactionPriority getDefaultTransactionPriority(); + List getTransactionPriorities(); + TransactionPriority deserializeEthereumTransactionPriority(int raw); + int getEstimatedFee(Object wallet, TransactionPriority priority); } """;