From 36f090a1e70f2432c49ce1e1d98f95791ad99788 Mon Sep 17 00:00:00 2001 From: julian Date: Wed, 10 Jan 2024 10:08:12 -0600 Subject: [PATCH] WIP eth refactor --- .../blockchain_data/v2/transaction_v2.dart | 6 + .../edit_wallet_tokens_view.dart | 7 +- .../helpers/restore_create_backup.dart | 4 +- lib/pages/token_view/my_tokens_view.dart | 5 +- .../sub_widgets/my_token_select_item.dart | 2 +- lib/pages/wallets_view/wallets_overview.dart | 4 +- .../wallet_view/sub_widgets/my_wallet.dart | 4 +- lib/providers/wallet_provider.dart | 2 +- lib/services/coins/coin_service.dart | 9 +- .../coins/ethereum/ethereum_wallet.dart | 1983 +++++++---------- .../ethereum/ethereum_token_service.dart | 22 +- .../crypto_currency/coins/ethereum.dart | 37 + lib/wallets/isar/models/wallet_info.dart | 19 + lib/wallets/wallet/impl/ethereum_wallet.dart | 380 ++++ .../wallet/private_key_based_wallet.dart | 39 - lib/wallets/wallet/wallet.dart | 11 +- .../private_key_interface.dart | 37 + lib/widgets/master_wallet_card.dart | 4 +- lib/widgets/wallet_card.dart | 3 +- .../sub_widgets/wallet_info_row_balance.dart | 6 +- 20 files changed, 1318 insertions(+), 1266 deletions(-) create mode 100644 lib/wallets/crypto_currency/coins/ethereum.dart create mode 100644 lib/wallets/wallet/impl/ethereum_wallet.dart delete mode 100644 lib/wallets/wallet/private_key_based_wallet.dart create mode 100644 lib/wallets/wallet/wallet_mixin_interfaces/private_key_interface.dart diff --git a/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart b/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart index fe46f9a82..b813c403a 100644 --- a/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart +++ b/lib/models/isar/models/blockchain_data/v2/transaction_v2.dart @@ -122,6 +122,12 @@ class TransactionV2 { ) - getFee(coin: coin); + // negative amounts are likely an error or can happen with coins such as eth + // that don't use the btc style inputs/outputs + if (amount.raw < BigInt.zero) { + return Amount.zeroWith(fractionDigits: coin.decimals); + } + return amount; } diff --git a/lib/pages/add_wallet_views/add_token_view/edit_wallet_tokens_view.dart b/lib/pages/add_wallet_views/add_token_view/edit_wallet_tokens_view.dart index 6c111c501..ca3b776d5 100644 --- a/lib/pages/add_wallet_views/add_token_view/edit_wallet_tokens_view.dart +++ b/lib/pages/add_wallet_views/add_token_view/edit_wallet_tokens_view.dart @@ -24,7 +24,6 @@ import 'package:stackwallet/pages/add_wallet_views/add_token_view/sub_widgets/ad import 'package:stackwallet/pages/home_view/home_view.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_home_view.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart'; -import 'package:stackwallet/services/coins/ethereum/ethereum_wallet.dart'; import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; @@ -32,6 +31,7 @@ import 'package:stackwallet/utilities/default_eth_tokens.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart'; +import 'package:stackwallet/wallets/wallet/impl/ethereum_wallet.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; @@ -121,7 +121,7 @@ class _EditWalletTokensViewState extends ConsumerState { unawaited( showFloatingFlushBar( type: FlushBarType.success, - message: "${ethWallet.walletName} tokens saved", + message: "${ethWallet.info.name} tokens saved", context: context, ), ); @@ -182,7 +182,8 @@ class _EditWalletTokensViewState extends ConsumerState { final walletContracts = (ref.read(pWallets).getWallet(widget.walletId) as EthereumWallet) - .getWalletTokenContractAddresses(); + .info + .tokenContractAddresses; final shouldMarkAsSelectedContracts = [ ...walletContracts, diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart index 0bbc1c891..504ad05cd 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart @@ -42,9 +42,9 @@ import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/prefs.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/wallets/isar/models/wallet_info.dart'; -import 'package:stackwallet/wallets/wallet/private_key_based_wallet.dart'; import 'package:stackwallet/wallets/wallet/wallet.dart'; import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart'; +import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/private_key_interface.dart'; import 'package:tuple/tuple.dart'; import 'package:uuid/uuid.dart'; import 'package:wakelock/wakelock.dart'; @@ -298,7 +298,7 @@ abstract class SWB { backupWallet['mnemonic'] = await wallet.getMnemonic(); backupWallet['mnemonicPassphrase'] = await wallet.getMnemonicPassphrase(); - } else if (wallet is PrivateKeyBasedWallet) { + } else if (wallet is PrivateKeyInterface) { backupWallet['privateKey'] = await wallet.getPrivateKey(); } backupWallet['coinName'] = wallet.info.coin.name; diff --git a/lib/pages/token_view/my_tokens_view.dart b/lib/pages/token_view/my_tokens_view.dart index 7a8b3d1d3..9813e3eff 100644 --- a/lib/pages/token_view/my_tokens_view.dart +++ b/lib/pages/token_view/my_tokens_view.dart @@ -16,13 +16,13 @@ import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/pages/add_wallet_views/add_token_view/edit_wallet_tokens_view.dart'; import 'package:stackwallet/pages/token_view/sub_widgets/my_tokens_list.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart'; -import 'package:stackwallet/services/coins/ethereum/ethereum_wallet.dart'; import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart'; +import 'package:stackwallet/wallets/wallet/impl/ethereum_wallet.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; @@ -236,7 +236,8 @@ class _MyTokensViewState extends ConsumerState { tokenContracts: ref .watch(pWallets.select((value) => value.getWallet(widget.walletId) as EthereumWallet)) - .getWalletTokenContractAddresses(), + .info + .tokenContractAddresses, ), ), ], diff --git a/lib/pages/token_view/sub_widgets/my_token_select_item.dart b/lib/pages/token_view/sub_widgets/my_token_select_item.dart index e678dfa19..0907a4adc 100644 --- a/lib/pages/token_view/sub_widgets/my_token_select_item.dart +++ b/lib/pages/token_view/sub_widgets/my_token_select_item.dart @@ -15,7 +15,6 @@ import 'package:stackwallet/pages/token_view/token_view.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_token_view.dart'; import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/providers/providers.dart'; -import 'package:stackwallet/services/coins/ethereum/ethereum_wallet.dart'; import 'package:stackwallet/services/ethereum/cached_eth_token_balance.dart'; import 'package:stackwallet/services/ethereum/ethereum_token_service.dart'; import 'package:stackwallet/services/transaction_notification_tracker.dart'; @@ -27,6 +26,7 @@ import 'package:stackwallet/utilities/show_loading.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart'; +import 'package:stackwallet/wallets/wallet/impl/ethereum_wallet.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/dialogs/basic_dialog.dart'; import 'package:stackwallet/widgets/icon_widgets/eth_token_icon.dart'; diff --git a/lib/pages/wallets_view/wallets_overview.dart b/lib/pages/wallets_view/wallets_overview.dart index 3fd2d6c9a..7b05aaf24 100644 --- a/lib/pages/wallets_view/wallets_overview.dart +++ b/lib/pages/wallets_view/wallets_overview.dart @@ -18,7 +18,6 @@ import 'package:stackwallet/pages/add_wallet_views/create_or_restore_wallet_view import 'package:stackwallet/pages_desktop_specific/my_stack_view/dialogs/desktop_expanding_wallet_card.dart'; import 'package:stackwallet/providers/db/main_db_provider.dart'; import 'package:stackwallet/providers/providers.dart'; -import 'package:stackwallet/services/coins/ethereum/ethereum_wallet.dart'; import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; @@ -26,6 +25,7 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/wallets/isar/models/wallet_info.dart'; +import 'package:stackwallet/wallets/wallet/impl/ethereum_wallet.dart'; import 'package:stackwallet/wallets/wallet/wallet.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; @@ -121,7 +121,7 @@ class _EthWalletsOverviewState extends ConsumerState { final List contracts = []; final wallet = ref.read(pWallets).getWallet(data.walletId); final contractAddresses = - (wallet as EthereumWallet).getWalletTokenContractAddresses(); + (wallet as EthereumWallet).info.tokenContractAddresses; // fetch each contract for (final contractAddress in contractAddresses) { diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/my_wallet.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/my_wallet.dart index 2c2a16c21..a330cb781 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/my_wallet.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/my_wallet.dart @@ -10,7 +10,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/pages/wallet_view/sub_widgets/transactions_list.dart'; +import 'package:stackwallet/pages/wallet_view/transaction_views/tx_v2/transaction_v2_list.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_token_send.dart'; @@ -90,7 +90,7 @@ class _MyWalletState extends ConsumerState { constraints: BoxConstraints( maxHeight: MediaQuery.of(context).size.height - 362, ), - child: TransactionsList( + child: TransactionsV2List( walletId: widget.walletId, ), ), diff --git a/lib/providers/wallet_provider.dart b/lib/providers/wallet_provider.dart index 7930660c3..a25331973 100644 --- a/lib/providers/wallet_provider.dart +++ b/lib/providers/wallet_provider.dart @@ -13,10 +13,10 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/providers/db/main_db_provider.dart'; import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart'; -import 'package:stackwallet/services/coins/ethereum/ethereum_wallet.dart'; import 'package:stackwallet/services/ethereum/ethereum_token_service.dart'; import 'package:stackwallet/services/transaction_notification_tracker.dart'; import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/wallets/wallet/impl/ethereum_wallet.dart'; class ContractWalletId implements Equatable { final String walletId; diff --git a/lib/services/coins/coin_service.dart b/lib/services/coins/coin_service.dart index 959a3a395..6968b3794 100644 --- a/lib/services/coins/coin_service.dart +++ b/lib/services/coins/coin_service.dart @@ -14,7 +14,6 @@ import 'package:stackwallet/models/balance.dart'; import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models; import 'package:stackwallet/models/node_model.dart'; import 'package:stackwallet/models/paymint/fee_object_model.dart'; -import 'package:stackwallet/services/coins/ethereum/ethereum_wallet.dart'; import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart'; import 'package:stackwallet/services/coins/particl/particl_wallet.dart'; import 'package:stackwallet/services/coins/stellar/stellar_wallet.dart'; @@ -103,13 +102,7 @@ abstract class CoinServiceAPI { throw UnimplementedError("moved"); case Coin.ethereum: - return EthereumWallet( - walletId: walletId, - walletName: walletName, - coin: coin, - secureStore: secureStorageInterface, - tracker: tracker, - ); + throw UnimplementedError("moved"); case Coin.monero: throw UnimplementedError("moved"); diff --git a/lib/services/coins/ethereum/ethereum_wallet.dart b/lib/services/coins/ethereum/ethereum_wallet.dart index fc9de7bc7..1a9c76723 100644 --- a/lib/services/coins/ethereum/ethereum_wallet.dart +++ b/lib/services/coins/ethereum/ethereum_wallet.dart @@ -1,1187 +1,796 @@ -/* - * This file is part of Stack Wallet. - * - * Copyright (c) 2023 Cypher Stack - * All Rights Reserved. - * The code is distributed under GPLv3 license, see LICENSE file for details. - * Generated by Cypher Stack on 2023-05-26 - * - */ - -import 'dart:async'; - -import 'package:bip39/bip39.dart' as bip39; -import 'package:ethereum_addresses/ethereum_addresses.dart'; -import 'package:http/http.dart'; -import 'package:isar/isar.dart'; -import 'package:stackwallet/db/hive/db.dart'; -import 'package:stackwallet/db/isar/main_db.dart'; -import 'package:stackwallet/models/balance.dart'; -import 'package:stackwallet/models/isar/models/isar_models.dart'; -import 'package:stackwallet/models/node_model.dart'; -import 'package:stackwallet/models/paymint/fee_object_model.dart'; -import 'package:stackwallet/services/coins/coin_service.dart'; -import 'package:stackwallet/services/ethereum/ethereum_api.dart'; -import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart'; -import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart'; -import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart'; -import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; -import 'package:stackwallet/services/event_bus/global_event_bus.dart'; -import 'package:stackwallet/services/mixins/eth_token_cache.dart'; -import 'package:stackwallet/services/mixins/wallet_cache.dart'; -import 'package:stackwallet/services/mixins/wallet_db.dart'; -import 'package:stackwallet/services/node_service.dart'; -import 'package:stackwallet/services/transaction_notification_tracker.dart'; -import 'package:stackwallet/utilities/amount/amount.dart'; -import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/default_nodes.dart'; -import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; -import 'package:stackwallet/utilities/eth_commons.dart'; -import 'package:stackwallet/utilities/extensions/extensions.dart'; -import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; -import 'package:stackwallet/utilities/logger.dart'; -import 'package:stackwallet/utilities/prefs.dart'; -import 'package:stackwallet/widgets/crypto_notifications.dart'; -import 'package:tuple/tuple.dart'; -import 'package:web3dart/web3dart.dart' as web3; - -const int MINIMUM_CONFIRMATIONS = 3; - -class EthereumWallet extends CoinServiceAPI with WalletCache, WalletDB { - EthereumWallet({ - required String walletId, - required String walletName, - required Coin coin, - required SecureStorageInterface secureStore, - required TransactionNotificationTracker tracker, - MainDB? mockableOverride, - }) { - txTracker = tracker; - _walletId = walletId; - _walletName = walletName; - _coin = coin; - _secureStore = secureStore; - initCache(walletId, coin); - initWalletDB(mockableOverride: mockableOverride); - } - - NodeModel? _ethNode; - - final _gasLimit = 21000; - - Timer? timer; - Timer? _networkAliveTimer; - - Future updateTokenContracts(List contractAddresses) async { - // final set = getWalletTokenContractAddresses().toSet(); - // set.addAll(contractAddresses); - await updateWalletTokenContractAddresses(contractAddresses); - - GlobalEventBus.instance.fire( - UpdatedInBackgroundEvent( - "$contractAddresses updated/added for: $walletId $walletName", - walletId, - ), - ); - } - - Balance getCachedTokenBalance(EthContract contract) { - final jsonString = DB.instance.get( - boxName: _walletId, - key: TokenCacheKeys.tokenBalance(contract.address), - ) as String?; - if (jsonString == null) { - return Balance( - total: Amount( - rawValue: BigInt.zero, - fractionDigits: contract.decimals, - ), - spendable: Amount( - rawValue: BigInt.zero, - fractionDigits: contract.decimals, - ), - blockedTotal: Amount( - rawValue: BigInt.zero, - fractionDigits: contract.decimals, - ), - pendingSpendable: Amount( - rawValue: BigInt.zero, - fractionDigits: contract.decimals, - ), - ); - } - return Balance.fromJson( - jsonString, - contract.decimals, - ); - } - - // Future removeTokenContract(String contractAddress) async { - // final set = getWalletTokenContractAddresses().toSet(); - // set.removeWhere((e) => e == contractAddress); - // await updateWalletTokenContractAddresses(set.toList()); - // - // GlobalEventBus.instance.fire( - // UpdatedInBackgroundEvent( - // "$contractAddress removed for: $walletId $walletName", - // walletId, - // ), - // ); - // } - - @override - String get walletId => _walletId; - late String _walletId; - - @override - String get walletName => _walletName; - late String _walletName; - - @override - set walletName(String newName) => _walletName = newName; - - @override - set isFavorite(bool markFavorite) { - _isFavorite = markFavorite; - updateCachedIsFavorite(markFavorite); - } - - @override - bool get isFavorite => _isFavorite ??= getCachedIsFavorite(); - bool? _isFavorite; - - @override - Coin get coin => _coin; - late Coin _coin; - - late SecureStorageInterface _secureStore; - late final TransactionNotificationTracker txTracker; - final _prefs = Prefs.instance; - bool longMutex = false; - - NodeModel getCurrentNode() { - return _ethNode ?? - NodeService(secureStorageInterface: _secureStore) - .getPrimaryNodeFor(coin: coin) ?? - DefaultNodes.getNodeFor(coin); - } - - web3.Web3Client getEthClient() { - final node = getCurrentNode(); - return web3.Web3Client(node.host, Client()); - } - - late web3.EthPrivateKey _credentials; - - bool _shouldAutoSync = false; - - @override - bool get shouldAutoSync => _shouldAutoSync; - - @override - set shouldAutoSync(bool shouldAutoSync) { - if (_shouldAutoSync != shouldAutoSync) { - _shouldAutoSync = shouldAutoSync; - if (!shouldAutoSync) { - timer?.cancel(); - timer = null; - stopNetworkAlivePinging(); - } else { - startNetworkAlivePinging(); - refresh(); - } - } - } - - @override - Future> get utxos => db.getUTXOs(walletId).findAll(); - - @override - Future> get transactions => db - .getTransactions(walletId) - .filter() - .otherDataEqualTo( - null) // eth txns with other data where other data is the token contract address - .sortByTimestampDesc() - .findAll(); - - @override - Future get currentReceivingAddress async { - final address = await _currentReceivingAddress; - return checksumEthereumAddress( - address?.value ?? _credentials.address.hexEip55); - } - - Future get _currentReceivingAddress => db - .getAddresses(walletId) - .filter() - .typeEqualTo(AddressType.ethereum) - .subTypeEqualTo(AddressSubType.receiving) - .sortByDerivationIndexDesc() - .findFirst(); - - @override - Balance get balance => _balance ??= getCachedBalance(); - Balance? _balance; - - Future updateBalance() async { - web3.Web3Client client = getEthClient(); - - final address = web3.EthereumAddress.fromHex(await currentReceivingAddress); - web3.EtherAmount ethBalance = await client.getBalance(address); - _balance = Balance( - total: Amount( - rawValue: ethBalance.getInWei, - fractionDigits: coin.decimals, - ), - spendable: Amount( - rawValue: ethBalance.getInWei, - fractionDigits: coin.decimals, - ), - blockedTotal: Amount( - rawValue: BigInt.zero, - fractionDigits: coin.decimals, - ), - pendingSpendable: Amount( - rawValue: BigInt.zero, - fractionDigits: coin.decimals, - ), - ); - await updateCachedBalance(_balance!); - } - - @override - Future estimateFeeFor(Amount amount, int feeRate) async { - return estimateFee(feeRate, _gasLimit, coin.decimals); - } - - @override - Future exit() async { - _hasCalledExit = true; - timer?.cancel(); - timer = null; - stopNetworkAlivePinging(); - } - - @override - Future get fees => EthereumAPI.getFees(); - - @override - Future fullRescan( - int maxUnusedAddressGap, - int maxNumberOfIndexesToCheck, - ) async { - await db.deleteWalletBlockchainData(walletId); - await _generateAndSaveAddress( - (await mnemonicString)!, - (await mnemonicPassphrase)!, - ); - await updateBalance(); - await _refreshTransactions(isRescan: true); - } - - @override - Future generateNewAddress() { - // not used for ETH - throw UnimplementedError(); - } - - bool _hasCalledExit = false; - - @override - bool get hasCalledExit => _hasCalledExit; - - @override - Future initializeExisting() async { - Logging.instance.log( - "initializeExisting() ${coin.prettyName} wallet", - level: LogLevel.Info, - ); - - await _initCredentials( - (await mnemonicString)!, - (await mnemonicPassphrase)!, - ); - - if (getCachedId() == null) { - throw Exception( - "Attempted to initialize an existing wallet using an unknown wallet ID!"); - } - await _prefs.init(); - } - - @override - Future initializeNew( - ({String mnemonicPassphrase, int wordCount})? data, - ) async { - Logging.instance.log( - "Generating new ${coin.prettyName} wallet.", - level: LogLevel.Info, - ); - - if (getCachedId() != null) { - throw Exception( - "Attempted to initialize a new wallet using an existing wallet ID!"); - } - - await _prefs.init(); - - try { - await _generateNewWallet(data); - } catch (e, s) { - Logging.instance.log( - "Exception rethrown from initializeNew(): $e\n$s", - level: LogLevel.Fatal, - ); - rethrow; - } - await Future.wait([ - updateCachedId(walletId), - updateCachedIsFavorite(false), - ]); - } - - Future _generateNewWallet( - ({String mnemonicPassphrase, int wordCount})? data, - ) async { - // Logging.instance - // .log("IS_INTEGRATION_TEST: $integrationTestFlag", level: LogLevel.Info); - // if (!integrationTestFlag) { - // try { - // final features = await electrumXClient.getServerFeatures(); - // Logging.instance.log("features: $features", level: LogLevel.Info); - // switch (coin) { - // case Coin.namecoin: - // if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { - // throw Exception("genesis hash does not match main net!"); - // } - // break; - // default: - // throw Exception( - // "Attempted to generate a EthereumWallet using a non eth coin type: ${coin.name}"); - // } - // } catch (e, s) { - // Logging.instance.log("$e/n$s", level: LogLevel.Info); - // } - // } - - // this should never fail - sanity check - if ((await mnemonicString) != null || (await mnemonicPassphrase) != null) { - throw Exception( - "Attempted to overwrite mnemonic on generate new wallet!"); - } - - final int strength; - if (data == null || data.wordCount == 12) { - strength = 128; - } else if (data.wordCount == 24) { - strength = 256; - } else { - throw Exception("Invalid word count"); - } - final String mnemonic = bip39.generateMnemonic(strength: strength); - final String passphrase = data?.mnemonicPassphrase ?? ""; - await _secureStore.write(key: '${_walletId}_mnemonic', value: mnemonic); - await _secureStore.write( - key: '${_walletId}_mnemonicPassphrase', - value: passphrase, - ); - - await _generateAndSaveAddress(mnemonic, passphrase); - - Logging.instance.log("_generateNewWalletFinished", level: LogLevel.Info); - } - - Future _initCredentials( - String mnemonic, - String mnemonicPassphrase, - ) async { - String privateKey = getPrivateKey(mnemonic, mnemonicPassphrase); - _credentials = web3.EthPrivateKey.fromHex(privateKey); - } - - Future _generateAndSaveAddress( - String mnemonic, - String mnemonicPassphrase, - ) async { - await _initCredentials(mnemonic, mnemonicPassphrase); - - final address = Address( - walletId: walletId, - value: _credentials.address.hexEip55, - publicKey: [], // maybe store address bytes here? seems a waste of space though - derivationIndex: 0, - derivationPath: DerivationPath()..value = "$hdPathEthereum/0", - type: AddressType.ethereum, - subType: AddressSubType.receiving, - ); - - await db.putAddress(address); - } - - bool _isConnected = false; - - @override - bool get isConnected => _isConnected; - - @override - bool get isRefreshing => refreshMutex; - - bool refreshMutex = false; - - @override - Future get maxFee async { - throw UnimplementedError("Not used for eth"); - } - - @override - Future> get mnemonic => _getMnemonicList(); - - @override - Future get mnemonicString => - _secureStore.read(key: '${_walletId}_mnemonic'); - - @override - Future get mnemonicPassphrase => _secureStore.read( - key: '${_walletId}_mnemonicPassphrase', - ); - - Future get chainHeight async { - web3.Web3Client client = getEthClient(); - try { - final height = await client.getBlockNumber(); - await updateCachedChainHeight(height); - if (height > storedChainHeight) { - GlobalEventBus.instance.fire( - UpdatedInBackgroundEvent( - "Updated current chain height in $walletId $walletName!", - walletId, - ), - ); - } - return height; - } catch (e, s) { - Logging.instance.log("Exception caught in chainHeight: $e\n$s", - level: LogLevel.Error); - return storedChainHeight; - } - } - - @override - int get storedChainHeight => getCachedChainHeight(); - - Future> _getMnemonicList() async { - final _mnemonicString = await mnemonicString; - if (_mnemonicString == null) { - return []; - } - final List data = _mnemonicString.split(' '); - return data; - } - - @override - Future> prepareSend({ - required String address, - required Amount amount, - Map? args, - }) async { - final feeRateType = args?["feeRate"]; - int fee = 0; - final feeObject = await fees; - switch (feeRateType) { - case FeeRateType.fast: - fee = feeObject.fast; - break; - case FeeRateType.average: - fee = feeObject.medium; - break; - case FeeRateType.slow: - fee = feeObject.slow; - break; - } - - final feeEstimate = await estimateFeeFor(amount, fee); - - // bool isSendAll = false; - // final availableBalance = balance.spendable; - // if (satoshiAmount == availableBalance) { - // isSendAll = true; - // } - // - // if (isSendAll) { - // //Subtract fee amount from send amount - // satoshiAmount -= feeEstimate; - // } - - final client = getEthClient(); - - final myAddress = await currentReceivingAddress; - final myWeb3Address = web3.EthereumAddress.fromHex(myAddress); - - final est = await client.estimateGas( - sender: myWeb3Address, - to: web3.EthereumAddress.fromHex(address), - gasPrice: web3.EtherAmount.fromUnitAndValue( - web3.EtherUnit.wei, - fee, - ), - amountOfGas: BigInt.from(_gasLimit), - value: web3.EtherAmount.inWei(amount.raw), - ); - - final nonce = args?["nonce"] as int? ?? - await client.getTransactionCount(myWeb3Address, - atBlock: const web3.BlockNum.pending()); - - final nResponse = await EthereumAPI.getAddressNonce(address: myAddress); - print("=============================================================="); - print("ETH client.estimateGas: $est"); - print("ETH estimateFeeFor : $feeEstimate"); - print("ETH nonce custom response: $nResponse"); - print("ETH actual nonce : $nonce"); - print("=============================================================="); - - final tx = web3.Transaction( - to: web3.EthereumAddress.fromHex(address), - gasPrice: web3.EtherAmount.fromUnitAndValue( - web3.EtherUnit.wei, - fee, - ), - maxGas: _gasLimit, - value: web3.EtherAmount.inWei(amount.raw), - nonce: nonce, - ); - - Map txData = { - "fee": feeEstimate, - "feeInWei": fee, - "address": address, - "recipientAmt": amount, - "ethTx": tx, - "chainId": (await client.getChainId()).toInt(), - "nonce": tx.nonce, - }; - - return txData; - } - - @override - Future confirmSend({required Map txData}) async { - web3.Web3Client client = getEthClient(); - - final txid = await client.sendTransaction( - _credentials, - txData["ethTx"] as web3.Transaction, - chainId: txData["chainId"] as int, - ); - - return txid; - } - - @override - Future recoverFromMnemonic({ - required String mnemonic, - String? mnemonicPassphrase, - required int maxUnusedAddressGap, - required int maxNumberOfIndexesToCheck, - required int height, - }) async { - longMutex = true; - final start = DateTime.now(); - - try { - // check to make sure we aren't overwriting a mnemonic - // this should never fail - if ((await mnemonicString) != null || - (await this.mnemonicPassphrase) != null) { - longMutex = false; - throw Exception("Attempted to overwrite mnemonic on restore!"); - } - - await _secureStore.write( - key: '${_walletId}_mnemonic', value: mnemonic.trim()); - await _secureStore.write( - key: '${_walletId}_mnemonicPassphrase', - value: mnemonicPassphrase ?? "", - ); - - String privateKey = - getPrivateKey(mnemonic.trim(), mnemonicPassphrase ?? ""); - _credentials = web3.EthPrivateKey.fromHex(privateKey); - - final address = Address( - walletId: walletId, - value: _credentials.address.hexEip55, - publicKey: [], // maybe store address bytes here? seems a waste of space though - derivationIndex: 0, - derivationPath: DerivationPath()..value = "$hdPathEthereum/0", - type: AddressType.ethereum, - subType: AddressSubType.receiving, - ); - - await db.putAddress(address); - - await Future.wait([ - updateCachedId(walletId), - updateCachedIsFavorite(false), - ]); - } catch (e, s) { - Logging.instance.log( - "Exception rethrown from recoverFromMnemonic(): $e\n$s", - level: LogLevel.Error); - longMutex = false; - rethrow; - } - - longMutex = false; - final end = DateTime.now(); - Logging.instance.log( - "$walletName recovery time: ${end.difference(start).inMilliseconds} millis", - level: LogLevel.Info); - } - - Future> _fetchAllOwnAddresses() => db - .getAddresses(walletId) - .filter() - .not() - .typeEqualTo(AddressType.nonWallet) - .and() - .group((q) => q - .subTypeEqualTo(AddressSubType.receiving) - .or() - .subTypeEqualTo(AddressSubType.change)) - .findAll(); - - Future refreshIfThereIsNewData() async { - if (longMutex) return false; - if (_hasCalledExit) return false; - final currentChainHeight = await chainHeight; - - try { - bool needsRefresh = false; - Set txnsToCheck = {}; - - for (final String txid in txTracker.pendings) { - if (!txTracker.wasNotifiedConfirmed(txid)) { - txnsToCheck.add(txid); - } - } - - for (String txid in txnsToCheck) { - final response = await EthereumAPI.getEthTransactionByHash(txid); - final txBlockNumber = response.value?.blockNumber; - - if (txBlockNumber != null) { - final int txConfirmations = currentChainHeight - txBlockNumber; - bool isUnconfirmed = txConfirmations < MINIMUM_CONFIRMATIONS; - if (!isUnconfirmed) { - needsRefresh = true; - break; - } - } - } - if (!needsRefresh) { - var allOwnAddresses = await _fetchAllOwnAddresses(); - final response = await EthereumAPI.getEthTransactions( - address: allOwnAddresses.elementAt(0).value, - ); - if (response.value != null) { - final allTxs = response.value!; - for (final element in allTxs) { - final txid = element.hash; - if ((await db - .getTransactions(walletId) - .filter() - .txidMatches(txid) - .findFirst()) == - null) { - Logging.instance.log( - " txid not found in address history already $txid", - level: LogLevel.Info); - needsRefresh = true; - break; - } - } - } else { - Logging.instance.log( - " refreshIfThereIsNewData get eth transactions failed: ${response.exception}", - level: LogLevel.Error, - ); - } - } - return needsRefresh; - } catch (e, s) { - Logging.instance.log( - "Exception caught in refreshIfThereIsNewData: $e\n$s", - level: LogLevel.Error); - rethrow; - } - } - - Future getAllTxsToWatch() async { - if (_hasCalledExit) return; - List unconfirmedTxnsToNotifyPending = []; - List unconfirmedTxnsToNotifyConfirmed = []; - - final currentChainHeight = await chainHeight; - - final txCount = await db.getTransactions(walletId).count(); - - const paginateLimit = 50; - - for (int i = 0; i < txCount; i += paginateLimit) { - final transactions = await db - .getTransactions(walletId) - .offset(i) - .limit(paginateLimit) - .findAll(); - for (final tx in transactions) { - if (tx.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) { - // get all transactions that were notified as pending but not as confirmed - if (txTracker.wasNotifiedPending(tx.txid) && - !txTracker.wasNotifiedConfirmed(tx.txid)) { - unconfirmedTxnsToNotifyConfirmed.add(tx); - } - } else { - // get all transactions that were not notified as pending yet - if (!txTracker.wasNotifiedPending(tx.txid)) { - unconfirmedTxnsToNotifyPending.add(tx); - } - } - } - } - - // notify on unconfirmed transactions - for (final tx in unconfirmedTxnsToNotifyPending) { - final confirmations = tx.getConfirmations(currentChainHeight); - - if (tx.type == TransactionType.incoming) { - CryptoNotificationsEventBus.instance.fire( - CryptoNotificationEvent( - title: "Incoming transaction", - walletId: walletId, - date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), - shouldWatchForUpdates: confirmations < MINIMUM_CONFIRMATIONS, - txid: tx.txid, - confirmations: confirmations, - requiredConfirmations: MINIMUM_CONFIRMATIONS, - walletName: walletName, - coin: coin, - ), - ); - - await txTracker.addNotifiedPending(tx.txid); - } else if (tx.type == TransactionType.outgoing) { - CryptoNotificationsEventBus.instance.fire( - CryptoNotificationEvent( - title: "Sending transaction", - walletId: walletId, - date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), - shouldWatchForUpdates: confirmations < MINIMUM_CONFIRMATIONS, - txid: tx.txid, - confirmations: confirmations, - requiredConfirmations: MINIMUM_CONFIRMATIONS, - walletName: walletName, - coin: coin, - ), - ); - - await txTracker.addNotifiedPending(tx.txid); - } - } - - // notify on confirmed - for (final tx in unconfirmedTxnsToNotifyConfirmed) { - if (tx.type == TransactionType.incoming) { - CryptoNotificationsEventBus.instance.fire( - CryptoNotificationEvent( - title: "Incoming transaction confirmed", - walletId: walletId, - date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), - shouldWatchForUpdates: false, - txid: tx.txid, - requiredConfirmations: MINIMUM_CONFIRMATIONS, - walletName: walletName, - coin: coin, - ), - ); - - await txTracker.addNotifiedConfirmed(tx.txid); - } else if (tx.type == TransactionType.outgoing) { - CryptoNotificationsEventBus.instance.fire( - CryptoNotificationEvent( - title: "Outgoing transaction confirmed", - walletId: walletId, - date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), - shouldWatchForUpdates: false, - txid: tx.txid, - requiredConfirmations: MINIMUM_CONFIRMATIONS, - walletName: walletName, - coin: coin, - ), - ); - - await txTracker.addNotifiedConfirmed(tx.txid); - } - } - } - - @override - Future refresh() async { - if (refreshMutex) { - Logging.instance.log("$walletId $walletName refreshMutex denied", - level: LogLevel.Info); - return; - } else { - refreshMutex = true; - } - - try { - GlobalEventBus.instance.fire( - WalletSyncStatusChangedEvent( - WalletSyncStatus.syncing, - walletId, - coin, - ), - ); - - GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.0, walletId)); - GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.1, walletId)); - - final currentHeight = await chainHeight; - const storedHeight = 1; //await storedChainHeight; - - Logging.instance - .log("chain height: $currentHeight", level: LogLevel.Info); - Logging.instance - .log("cached height: $storedHeight", level: LogLevel.Info); - - if (currentHeight != storedHeight) { - GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId)); - - final newTxDataFuture = _refreshTransactions(); - GlobalEventBus.instance - .fire(RefreshPercentChangedEvent(0.50, walletId)); - - // final feeObj = _getFees(); - GlobalEventBus.instance - .fire(RefreshPercentChangedEvent(0.60, walletId)); - - GlobalEventBus.instance - .fire(RefreshPercentChangedEvent(0.70, walletId)); - // _feeObject = Future(() => feeObj); - GlobalEventBus.instance - .fire(RefreshPercentChangedEvent(0.80, walletId)); - - final allTxsToWatch = getAllTxsToWatch(); - await Future.wait([ - updateBalance(), - newTxDataFuture, - // feeObj, - allTxsToWatch, - ]); - GlobalEventBus.instance - .fire(RefreshPercentChangedEvent(0.90, walletId)); - } - refreshMutex = false; - GlobalEventBus.instance.fire(RefreshPercentChangedEvent(1.0, walletId)); - GlobalEventBus.instance.fire( - WalletSyncStatusChangedEvent( - WalletSyncStatus.synced, - walletId, - coin, - ), - ); - - if (shouldAutoSync) { - timer ??= Timer.periodic(const Duration(seconds: 30), (timer) async { - Logging.instance.log( - "Periodic refresh check for $walletId $walletName in object instance: $hashCode", - level: LogLevel.Info); - if (await refreshIfThereIsNewData()) { - await refresh(); - GlobalEventBus.instance.fire(UpdatedInBackgroundEvent( - "New data found in $walletId $walletName in background!", - walletId)); - } - }); - } - } catch (error, strace) { - refreshMutex = false; - GlobalEventBus.instance.fire( - NodeConnectionStatusChangedEvent( - NodeConnectionStatus.disconnected, - walletId, - coin, - ), - ); - GlobalEventBus.instance.fire( - WalletSyncStatusChangedEvent( - WalletSyncStatus.unableToSync, - walletId, - coin, - ), - ); - Logging.instance.log( - "Caught exception in $walletName $walletId refresh(): $error\n$strace", - level: LogLevel.Warning, - ); - } - } - - @override - Future testNetworkConnection() async { - web3.Web3Client client = getEthClient(); - try { - await client.getBlockNumber(); - return true; - } catch (_) { - return false; - } - } - - void _periodicPingCheck() async { - bool hasNetwork = await testNetworkConnection(); - _isConnected = hasNetwork; - if (_isConnected != hasNetwork) { - NodeConnectionStatus status = hasNetwork - ? NodeConnectionStatus.connected - : NodeConnectionStatus.disconnected; - GlobalEventBus.instance - .fire(NodeConnectionStatusChangedEvent(status, walletId, coin)); - } - } - - @override - Future updateNode(bool shouldRefresh) async { - _ethNode = NodeService(secureStorageInterface: _secureStore) - .getPrimaryNodeFor(coin: coin) ?? - DefaultNodes.getNodeFor(coin); - - if (shouldRefresh) { - unawaited(refresh()); - } - } - - @override - Future updateSentCachedTxData(Map txData) async { - final txid = txData["txid"] as String; - final addressString = checksumEthereumAddress(txData["address"] as String); - final response = await EthereumAPI.getEthTransactionByHash(txid); - - final transaction = Transaction( - walletId: walletId, - txid: txid, - timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, - type: TransactionType.outgoing, - subType: TransactionSubType.none, - // precision may be lost here hence the following amountString - amount: (txData["recipientAmt"] as Amount).raw.toInt(), - amountString: (txData["recipientAmt"] as Amount).toJsonString(), - fee: (txData["fee"] as Amount).raw.toInt(), - height: null, - isCancelled: false, - isLelantus: false, - otherData: null, - slateId: null, - nonce: (txData["nonce"] as int?) ?? - response.value?.nonce.toBigIntFromHex.toInt(), - inputs: [], - outputs: [], - numberOfMessages: null, - ); - - Address? address = await db.getAddress( - walletId, - addressString, - ); - - address ??= Address( - walletId: walletId, - value: addressString, - publicKey: [], - derivationIndex: -1, - derivationPath: null, - type: AddressType.ethereum, - subType: AddressSubType.nonWallet, - ); - - await db.addNewTransactionData( - [ - Tuple2(transaction, address), - ], - walletId, - ); - } - - @override - bool validateAddress(String address) { - return isValidEthereumAddress(address); - } - - Future _refreshTransactions({bool isRescan = false}) async { - String thisAddress = await currentReceivingAddress; - - int firstBlock = 0; - - if (!isRescan) { - firstBlock = - await db.getTransactions(walletId).heightProperty().max() ?? 0; - - if (firstBlock > 10) { - // add some buffer - firstBlock -= 10; - } - } - - final response = await EthereumAPI.getEthTransactions( - address: thisAddress, - firstBlock: isRescan ? 0 : firstBlock, - includeTokens: true, - ); - - if (response.value == null) { - Logging.instance.log( - "Failed to refresh transactions for ${coin.prettyName} $walletName " - "$walletId: ${response.exception}", - level: LogLevel.Warning, - ); - return; - } - - if (response.value!.isEmpty) { - // no new transactions found - return; - } - - final txsResponse = - await EthereumAPI.getEthTransactionNonces(response.value!); - - if (txsResponse.value != null) { - final allTxs = txsResponse.value!; - final List> txnsData = []; - for (final tuple in allTxs) { - final element = tuple.item1; - - Amount transactionAmount = element.value; - - bool isIncoming; - bool txFailed = false; - if (checksumEthereumAddress(element.from) == thisAddress) { - if (element.isError) { - txFailed = true; - } - isIncoming = false; - } else if (checksumEthereumAddress(element.to) == thisAddress) { - isIncoming = true; - } else { - continue; - } - - //Calculate fees (GasLimit * gasPrice) - // int txFee = element.gasPrice * element.gasUsed; - Amount txFee = element.gasCost; - - final String addressString = checksumEthereumAddress(element.to); - final int height = element.blockNumber; - - final txn = Transaction( - walletId: walletId, - txid: element.hash, - timestamp: element.timestamp, - type: - isIncoming ? TransactionType.incoming : TransactionType.outgoing, - subType: TransactionSubType.none, - amount: transactionAmount.raw.toInt(), - amountString: transactionAmount.toJsonString(), - fee: txFee.raw.toInt(), - height: height, - isCancelled: txFailed, - isLelantus: false, - slateId: null, - otherData: null, - nonce: tuple.item2, - inputs: [], - outputs: [], - numberOfMessages: null, - ); - - Address? transactionAddress = await db - .getAddresses(walletId) - .filter() - .valueEqualTo(addressString) - .findFirst(); - - if (transactionAddress == null) { - if (isIncoming) { - transactionAddress = Address( - walletId: walletId, - value: addressString, - publicKey: [], - derivationIndex: 0, - derivationPath: DerivationPath()..value = "$hdPathEthereum/0", - type: AddressType.ethereum, - subType: AddressSubType.receiving, - ); - } else { - final myRcvAddr = await currentReceivingAddress; - final isSentToSelf = myRcvAddr == addressString; - - transactionAddress = Address( - walletId: walletId, - value: addressString, - publicKey: [], - derivationIndex: isSentToSelf ? 0 : -1, - derivationPath: isSentToSelf - ? (DerivationPath()..value = "$hdPathEthereum/0") - : null, - type: AddressType.ethereum, - subType: isSentToSelf - ? AddressSubType.receiving - : AddressSubType.nonWallet, - ); - } - } - - txnsData.add(Tuple2(txn, transactionAddress)); - } - await db.addNewTransactionData(txnsData, walletId); - - // quick hack to notify manager to call notifyListeners if - // transactions changed - if (txnsData.isNotEmpty) { - GlobalEventBus.instance.fire( - UpdatedInBackgroundEvent( - "Transactions updated/added for: $walletId $walletName ", - walletId, - ), - ); - } - } else { - Logging.instance.log( - "Failed to refresh transactions with nonces for ${coin.prettyName} " - "$walletName $walletId: ${txsResponse.exception}", - level: LogLevel.Warning, - ); - } - } - - void stopNetworkAlivePinging() { - _networkAliveTimer?.cancel(); - _networkAliveTimer = null; - } - - void startNetworkAlivePinging() { - // call once on start right away - _periodicPingCheck(); - - // then periodically check - _networkAliveTimer = Timer.periodic( - Constants.networkAliveTimerDuration, - (_) async { - _periodicPingCheck(); - }, - ); - } -} +// /* +// * This file is part of Stack Wallet. +// * +// * Copyright (c) 2023 Cypher Stack +// * All Rights Reserved. +// * The code is distributed under GPLv3 license, see LICENSE file for details. +// * Generated by Cypher Stack on 2023-05-26 +// * +// */ +// +// import 'dart:async'; +// +// import 'package:bip39/bip39.dart' as bip39; +// import 'package:ethereum_addresses/ethereum_addresses.dart'; +// import 'package:http/http.dart'; +// import 'package:isar/isar.dart'; +// import 'package:stackwallet/db/hive/db.dart'; +// import 'package:stackwallet/db/isar/main_db.dart'; +// import 'package:stackwallet/models/balance.dart'; +// import 'package:stackwallet/models/isar/models/isar_models.dart'; +// import 'package:stackwallet/models/node_model.dart'; +// import 'package:stackwallet/models/paymint/fee_object_model.dart'; +// import 'package:stackwallet/services/coins/coin_service.dart'; +// import 'package:stackwallet/services/ethereum/ethereum_api.dart'; +// import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart'; +// import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart'; +// import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart'; +// import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; +// import 'package:stackwallet/services/event_bus/global_event_bus.dart'; +// import 'package:stackwallet/services/mixins/eth_token_cache.dart'; +// import 'package:stackwallet/services/mixins/wallet_cache.dart'; +// import 'package:stackwallet/services/mixins/wallet_db.dart'; +// import 'package:stackwallet/services/node_service.dart'; +// import 'package:stackwallet/services/transaction_notification_tracker.dart'; +// import 'package:stackwallet/utilities/amount/amount.dart'; +// import 'package:stackwallet/utilities/constants.dart'; +// import 'package:stackwallet/utilities/default_nodes.dart'; +// import 'package:stackwallet/utilities/enums/coin_enum.dart'; +// import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; +// import 'package:stackwallet/utilities/eth_commons.dart'; +// import 'package:stackwallet/utilities/extensions/extensions.dart'; +// import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; +// import 'package:stackwallet/utilities/logger.dart'; +// import 'package:stackwallet/utilities/prefs.dart'; +// import 'package:stackwallet/widgets/crypto_notifications.dart'; +// import 'package:tuple/tuple.dart'; +// import 'package:web3dart/web3dart.dart' as web3; +// +// const int MINIMUM_CONFIRMATIONS = 3; +// +// class EthereumWallet extends CoinServiceAPI with WalletCache, WalletDB { +// EthereumWallet({ +// required String walletId, +// required String walletName, +// required Coin coin, +// required SecureStorageInterface secureStore, +// required TransactionNotificationTracker tracker, +// MainDB? mockableOverride, +// }) { +// txTracker = tracker; +// _walletId = walletId; +// _walletName = walletName; +// _coin = coin; +// _secureStore = secureStore; +// initCache(walletId, coin); +// initWalletDB(mockableOverride: mockableOverride); +// } +// +// Balance getCachedTokenBalance(EthContract contract) { +// final jsonString = DB.instance.get( +// boxName: _walletId, +// key: TokenCacheKeys.tokenBalance(contract.address), +// ) as String?; +// if (jsonString == null) { +// return Balance( +// total: Amount( +// rawValue: BigInt.zero, +// fractionDigits: contract.decimals, +// ), +// spendable: Amount( +// rawValue: BigInt.zero, +// fractionDigits: contract.decimals, +// ), +// blockedTotal: Amount( +// rawValue: BigInt.zero, +// fractionDigits: contract.decimals, +// ), +// pendingSpendable: Amount( +// rawValue: BigInt.zero, +// fractionDigits: contract.decimals, +// ), +// ); +// } +// return Balance.fromJson( +// jsonString, +// contract.decimals, +// ); +// } +// +// bool longMutex = false; +// +// @override +// Future> get transactions => db +// .getTransactions(walletId) +// .filter() +// .otherDataEqualTo( +// null) // eth txns with other data where other data is the token contract address +// .sortByTimestampDesc() +// .findAll(); +// +// @override +// Future get currentReceivingAddress async { +// final address = await _currentReceivingAddress; +// return checksumEthereumAddress( +// address?.value ?? _credentials.address.hexEip55); +// } +// +// @override +// Future initializeExisting() async { +// Logging.instance.log( +// "initializeExisting() ${coin.prettyName} wallet", +// level: LogLevel.Info, +// ); +// +// await _initCredentials( +// (await mnemonicString)!, +// (await mnemonicPassphrase)!, +// ); +// +// if (getCachedId() == null) { +// throw Exception( +// "Attempted to initialize an existing wallet using an unknown wallet ID!"); +// } +// await _prefs.init(); +// } +// +// @override +// Future initializeNew( +// ({String mnemonicPassphrase, int wordCount})? data, +// ) async { +// Logging.instance.log( +// "Generating new ${coin.prettyName} wallet.", +// level: LogLevel.Info, +// ); +// +// if (getCachedId() != null) { +// throw Exception( +// "Attempted to initialize a new wallet using an existing wallet ID!"); +// } +// +// await _prefs.init(); +// +// try { +// await _generateNewWallet(data); +// } catch (e, s) { +// Logging.instance.log( +// "Exception rethrown from initializeNew(): $e\n$s", +// level: LogLevel.Fatal, +// ); +// rethrow; +// } +// await Future.wait([ +// updateCachedId(walletId), +// updateCachedIsFavorite(false), +// ]); +// } +// +// Future _generateNewWallet( +// ({String mnemonicPassphrase, int wordCount})? data, +// ) async { +// // Logging.instance +// // .log("IS_INTEGRATION_TEST: $integrationTestFlag", level: LogLevel.Info); +// // if (!integrationTestFlag) { +// // try { +// // final features = await electrumXClient.getServerFeatures(); +// // Logging.instance.log("features: $features", level: LogLevel.Info); +// // switch (coin) { +// // case Coin.namecoin: +// // if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { +// // throw Exception("genesis hash does not match main net!"); +// // } +// // break; +// // default: +// // throw Exception( +// // "Attempted to generate a EthereumWallet using a non eth coin type: ${coin.name}"); +// // } +// // } catch (e, s) { +// // Logging.instance.log("$e/n$s", level: LogLevel.Info); +// // } +// // } +// +// // this should never fail - sanity check +// if ((await mnemonicString) != null || (await mnemonicPassphrase) != null) { +// throw Exception( +// "Attempted to overwrite mnemonic on generate new wallet!"); +// } +// +// final int strength; +// if (data == null || data.wordCount == 12) { +// strength = 128; +// } else if (data.wordCount == 24) { +// strength = 256; +// } else { +// throw Exception("Invalid word count"); +// } +// final String mnemonic = bip39.generateMnemonic(strength: strength); +// final String passphrase = data?.mnemonicPassphrase ?? ""; +// await _secureStore.write(key: '${_walletId}_mnemonic', value: mnemonic); +// await _secureStore.write( +// key: '${_walletId}_mnemonicPassphrase', +// value: passphrase, +// ); +// +// await _generateAndSaveAddress(mnemonic, passphrase); +// +// Logging.instance.log("_generateNewWalletFinished", level: LogLevel.Info); +// } +// +// bool _isConnected = false; +// +// @override +// bool get isConnected => _isConnected; +// +// @override +// bool get isRefreshing => refreshMutex; +// +// bool refreshMutex = false; +// +// @override +// Future> prepareSend({ +// required String address, +// required Amount amount, +// Map? args, +// }) async { +// final feeRateType = args?["feeRate"]; +// int fee = 0; +// final feeObject = await fees; +// switch (feeRateType) { +// case FeeRateType.fast: +// fee = feeObject.fast; +// break; +// case FeeRateType.average: +// fee = feeObject.medium; +// break; +// case FeeRateType.slow: +// fee = feeObject.slow; +// break; +// } +// +// final feeEstimate = await estimateFeeFor(amount, fee); +// +// // bool isSendAll = false; +// // final availableBalance = balance.spendable; +// // if (satoshiAmount == availableBalance) { +// // isSendAll = true; +// // } +// // +// // if (isSendAll) { +// // //Subtract fee amount from send amount +// // satoshiAmount -= feeEstimate; +// // } +// +// final client = getEthClient(); +// +// final myAddress = await currentReceivingAddress; +// final myWeb3Address = web3.EthereumAddress.fromHex(myAddress); +// +// final est = await client.estimateGas( +// sender: myWeb3Address, +// to: web3.EthereumAddress.fromHex(address), +// gasPrice: web3.EtherAmount.fromUnitAndValue( +// web3.EtherUnit.wei, +// fee, +// ), +// amountOfGas: BigInt.from(_gasLimit), +// value: web3.EtherAmount.inWei(amount.raw), +// ); +// +// final nonce = args?["nonce"] as int? ?? +// await client.getTransactionCount(myWeb3Address, +// atBlock: const web3.BlockNum.pending()); +// +// final nResponse = await EthereumAPI.getAddressNonce(address: myAddress); +// print("=============================================================="); +// print("ETH client.estimateGas: $est"); +// print("ETH estimateFeeFor : $feeEstimate"); +// print("ETH nonce custom response: $nResponse"); +// print("ETH actual nonce : $nonce"); +// print("=============================================================="); +// +// final tx = web3.Transaction( +// to: web3.EthereumAddress.fromHex(address), +// gasPrice: web3.EtherAmount.fromUnitAndValue( +// web3.EtherUnit.wei, +// fee, +// ), +// maxGas: _gasLimit, +// value: web3.EtherAmount.inWei(amount.raw), +// nonce: nonce, +// ); +// +// Map txData = { +// "fee": feeEstimate, +// "feeInWei": fee, +// "address": address, +// "recipientAmt": amount, +// "ethTx": tx, +// "chainId": (await client.getChainId()).toInt(), +// "nonce": tx.nonce, +// }; +// +// return txData; +// } +// +// @override +// Future confirmSend({required Map txData}) async { +// web3.Web3Client client = getEthClient(); +// +// final txid = await client.sendTransaction( +// _credentials, +// txData["ethTx"] as web3.Transaction, +// chainId: txData["chainId"] as int, +// ); +// +// return txid; +// } +// +// @override +// Future recoverFromMnemonic({ +// required String mnemonic, +// String? mnemonicPassphrase, +// required int maxUnusedAddressGap, +// required int maxNumberOfIndexesToCheck, +// required int height, +// }) async { +// longMutex = true; +// final start = DateTime.now(); +// +// try { +// // check to make sure we aren't overwriting a mnemonic +// // this should never fail +// if ((await mnemonicString) != null || +// (await this.mnemonicPassphrase) != null) { +// longMutex = false; +// throw Exception("Attempted to overwrite mnemonic on restore!"); +// } +// +// await _secureStore.write( +// key: '${_walletId}_mnemonic', value: mnemonic.trim()); +// await _secureStore.write( +// key: '${_walletId}_mnemonicPassphrase', +// value: mnemonicPassphrase ?? "", +// ); +// +// String privateKey = +// getPrivateKey(mnemonic.trim(), mnemonicPassphrase ?? ""); +// _credentials = web3.EthPrivateKey.fromHex(privateKey); +// +// final address = Address( +// walletId: walletId, +// value: _credentials.address.hexEip55, +// publicKey: [], // maybe store address bytes here? seems a waste of space though +// derivationIndex: 0, +// derivationPath: DerivationPath()..value = "$hdPathEthereum/0", +// type: AddressType.ethereum, +// subType: AddressSubType.receiving, +// ); +// +// await db.putAddress(address); +// +// await Future.wait([ +// updateCachedId(walletId), +// updateCachedIsFavorite(false), +// ]); +// } catch (e, s) { +// Logging.instance.log( +// "Exception rethrown from recoverFromMnemonic(): $e\n$s", +// level: LogLevel.Error); +// longMutex = false; +// rethrow; +// } +// +// longMutex = false; +// final end = DateTime.now(); +// Logging.instance.log( +// "$walletName recovery time: ${end.difference(start).inMilliseconds} millis", +// level: LogLevel.Info); +// } +// +// Future> _fetchAllOwnAddresses() => db +// .getAddresses(walletId) +// .filter() +// .not() +// .typeEqualTo(AddressType.nonWallet) +// .and() +// .group((q) => q +// .subTypeEqualTo(AddressSubType.receiving) +// .or() +// .subTypeEqualTo(AddressSubType.change)) +// .findAll(); +// +// Future refreshIfThereIsNewData() async { +// if (longMutex) return false; +// if (_hasCalledExit) return false; +// final currentChainHeight = await chainHeight; +// +// try { +// bool needsRefresh = false; +// Set txnsToCheck = {}; +// +// for (final String txid in txTracker.pendings) { +// if (!txTracker.wasNotifiedConfirmed(txid)) { +// txnsToCheck.add(txid); +// } +// } +// +// for (String txid in txnsToCheck) { +// final response = await EthereumAPI.getEthTransactionByHash(txid); +// final txBlockNumber = response.value?.blockNumber; +// +// if (txBlockNumber != null) { +// final int txConfirmations = currentChainHeight - txBlockNumber; +// bool isUnconfirmed = txConfirmations < MINIMUM_CONFIRMATIONS; +// if (!isUnconfirmed) { +// needsRefresh = true; +// break; +// } +// } +// } +// if (!needsRefresh) { +// var allOwnAddresses = await _fetchAllOwnAddresses(); +// final response = await EthereumAPI.getEthTransactions( +// address: allOwnAddresses.elementAt(0).value, +// ); +// if (response.value != null) { +// final allTxs = response.value!; +// for (final element in allTxs) { +// final txid = element.hash; +// if ((await db +// .getTransactions(walletId) +// .filter() +// .txidMatches(txid) +// .findFirst()) == +// null) { +// Logging.instance.log( +// " txid not found in address history already $txid", +// level: LogLevel.Info); +// needsRefresh = true; +// break; +// } +// } +// } else { +// Logging.instance.log( +// " refreshIfThereIsNewData get eth transactions failed: ${response.exception}", +// level: LogLevel.Error, +// ); +// } +// } +// return needsRefresh; +// } catch (e, s) { +// Logging.instance.log( +// "Exception caught in refreshIfThereIsNewData: $e\n$s", +// level: LogLevel.Error); +// rethrow; +// } +// } +// +// Future getAllTxsToWatch() async { +// if (_hasCalledExit) return; +// List unconfirmedTxnsToNotifyPending = []; +// List unconfirmedTxnsToNotifyConfirmed = []; +// +// final currentChainHeight = await chainHeight; +// +// final txCount = await db.getTransactions(walletId).count(); +// +// const paginateLimit = 50; +// +// for (int i = 0; i < txCount; i += paginateLimit) { +// final transactions = await db +// .getTransactions(walletId) +// .offset(i) +// .limit(paginateLimit) +// .findAll(); +// for (final tx in transactions) { +// if (tx.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) { +// // get all transactions that were notified as pending but not as confirmed +// if (txTracker.wasNotifiedPending(tx.txid) && +// !txTracker.wasNotifiedConfirmed(tx.txid)) { +// unconfirmedTxnsToNotifyConfirmed.add(tx); +// } +// } else { +// // get all transactions that were not notified as pending yet +// if (!txTracker.wasNotifiedPending(tx.txid)) { +// unconfirmedTxnsToNotifyPending.add(tx); +// } +// } +// } +// } +// +// // notify on unconfirmed transactions +// for (final tx in unconfirmedTxnsToNotifyPending) { +// final confirmations = tx.getConfirmations(currentChainHeight); +// +// if (tx.type == TransactionType.incoming) { +// CryptoNotificationsEventBus.instance.fire( +// CryptoNotificationEvent( +// title: "Incoming transaction", +// walletId: walletId, +// date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), +// shouldWatchForUpdates: confirmations < MINIMUM_CONFIRMATIONS, +// txid: tx.txid, +// confirmations: confirmations, +// requiredConfirmations: MINIMUM_CONFIRMATIONS, +// walletName: walletName, +// coin: coin, +// ), +// ); +// +// await txTracker.addNotifiedPending(tx.txid); +// } else if (tx.type == TransactionType.outgoing) { +// CryptoNotificationsEventBus.instance.fire( +// CryptoNotificationEvent( +// title: "Sending transaction", +// walletId: walletId, +// date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), +// shouldWatchForUpdates: confirmations < MINIMUM_CONFIRMATIONS, +// txid: tx.txid, +// confirmations: confirmations, +// requiredConfirmations: MINIMUM_CONFIRMATIONS, +// walletName: walletName, +// coin: coin, +// ), +// ); +// +// await txTracker.addNotifiedPending(tx.txid); +// } +// } +// +// // notify on confirmed +// for (final tx in unconfirmedTxnsToNotifyConfirmed) { +// if (tx.type == TransactionType.incoming) { +// CryptoNotificationsEventBus.instance.fire( +// CryptoNotificationEvent( +// title: "Incoming transaction confirmed", +// walletId: walletId, +// date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), +// shouldWatchForUpdates: false, +// txid: tx.txid, +// requiredConfirmations: MINIMUM_CONFIRMATIONS, +// walletName: walletName, +// coin: coin, +// ), +// ); +// +// await txTracker.addNotifiedConfirmed(tx.txid); +// } else if (tx.type == TransactionType.outgoing) { +// CryptoNotificationsEventBus.instance.fire( +// CryptoNotificationEvent( +// title: "Outgoing transaction confirmed", +// walletId: walletId, +// date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), +// shouldWatchForUpdates: false, +// txid: tx.txid, +// requiredConfirmations: MINIMUM_CONFIRMATIONS, +// walletName: walletName, +// coin: coin, +// ), +// ); +// +// await txTracker.addNotifiedConfirmed(tx.txid); +// } +// } +// } +// +// @override +// Future updateSentCachedTxData(Map txData) async { +// final txid = txData["txid"] as String; +// final addressString = checksumEthereumAddress(txData["address"] as String); +// final response = await EthereumAPI.getEthTransactionByHash(txid); +// +// final transaction = Transaction( +// walletId: walletId, +// txid: txid, +// timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, +// type: TransactionType.outgoing, +// subType: TransactionSubType.none, +// // precision may be lost here hence the following amountString +// amount: (txData["recipientAmt"] as Amount).raw.toInt(), +// amountString: (txData["recipientAmt"] as Amount).toJsonString(), +// fee: (txData["fee"] as Amount).raw.toInt(), +// height: null, +// isCancelled: false, +// isLelantus: false, +// otherData: null, +// slateId: null, +// nonce: (txData["nonce"] as int?) ?? +// response.value?.nonce.toBigIntFromHex.toInt(), +// inputs: [], +// outputs: [], +// numberOfMessages: null, +// ); +// +// Address? address = await db.getAddress( +// walletId, +// addressString, +// ); +// +// address ??= Address( +// walletId: walletId, +// value: addressString, +// publicKey: [], +// derivationIndex: -1, +// derivationPath: null, +// type: AddressType.ethereum, +// subType: AddressSubType.nonWallet, +// ); +// +// await db.addNewTransactionData( +// [ +// Tuple2(transaction, address), +// ], +// walletId, +// ); +// } +// +// // @override +// // bool validateAddress(String address) { +// // return isValidEthereumAddress(address); +// // } +// +// Future _refreshTransactions({bool isRescan = false}) async { +// String thisAddress = await currentReceivingAddress; +// +// int firstBlock = 0; +// +// if (!isRescan) { +// firstBlock = +// await db.getTransactions(walletId).heightProperty().max() ?? 0; +// +// if (firstBlock > 10) { +// // add some buffer +// firstBlock -= 10; +// } +// } +// +// final response = await EthereumAPI.getEthTransactions( +// address: thisAddress, +// firstBlock: isRescan ? 0 : firstBlock, +// includeTokens: true, +// ); +// +// if (response.value == null) { +// Logging.instance.log( +// "Failed to refresh transactions for ${coin.prettyName} $walletName " +// "$walletId: ${response.exception}", +// level: LogLevel.Warning, +// ); +// return; +// } +// +// if (response.value!.isEmpty) { +// // no new transactions found +// return; +// } +// +// final txsResponse = +// await EthereumAPI.getEthTransactionNonces(response.value!); +// +// if (txsResponse.value != null) { +// final allTxs = txsResponse.value!; +// final List> txnsData = []; +// for (final tuple in allTxs) { +// final element = tuple.item1; +// +// Amount transactionAmount = element.value; +// +// bool isIncoming; +// bool txFailed = false; +// if (checksumEthereumAddress(element.from) == thisAddress) { +// if (element.isError) { +// txFailed = true; +// } +// isIncoming = false; +// } else if (checksumEthereumAddress(element.to) == thisAddress) { +// isIncoming = true; +// } else { +// continue; +// } +// +// //Calculate fees (GasLimit * gasPrice) +// // int txFee = element.gasPrice * element.gasUsed; +// Amount txFee = element.gasCost; +// +// final String addressString = checksumEthereumAddress(element.to); +// final int height = element.blockNumber; +// +// final txn = Transaction( +// walletId: walletId, +// txid: element.hash, +// timestamp: element.timestamp, +// type: +// isIncoming ? TransactionType.incoming : TransactionType.outgoing, +// subType: TransactionSubType.none, +// amount: transactionAmount.raw.toInt(), +// amountString: transactionAmount.toJsonString(), +// fee: txFee.raw.toInt(), +// height: height, +// isCancelled: txFailed, +// isLelantus: false, +// slateId: null, +// otherData: null, +// nonce: tuple.item2, +// inputs: [], +// outputs: [], +// numberOfMessages: null, +// ); +// +// Address? transactionAddress = await db +// .getAddresses(walletId) +// .filter() +// .valueEqualTo(addressString) +// .findFirst(); +// +// if (transactionAddress == null) { +// if (isIncoming) { +// transactionAddress = Address( +// walletId: walletId, +// value: addressString, +// publicKey: [], +// derivationIndex: 0, +// derivationPath: DerivationPath()..value = "$hdPathEthereum/0", +// type: AddressType.ethereum, +// subType: AddressSubType.receiving, +// ); +// } else { +// final myRcvAddr = await currentReceivingAddress; +// final isSentToSelf = myRcvAddr == addressString; +// +// transactionAddress = Address( +// walletId: walletId, +// value: addressString, +// publicKey: [], +// derivationIndex: isSentToSelf ? 0 : -1, +// derivationPath: isSentToSelf +// ? (DerivationPath()..value = "$hdPathEthereum/0") +// : null, +// type: AddressType.ethereum, +// subType: isSentToSelf +// ? AddressSubType.receiving +// : AddressSubType.nonWallet, +// ); +// } +// } +// +// txnsData.add(Tuple2(txn, transactionAddress)); +// } +// await db.addNewTransactionData(txnsData, walletId); +// +// // quick hack to notify manager to call notifyListeners if +// // transactions changed +// if (txnsData.isNotEmpty) { +// GlobalEventBus.instance.fire( +// UpdatedInBackgroundEvent( +// "Transactions updated/added for: $walletId $walletName ", +// walletId, +// ), +// ); +// } +// } else { +// Logging.instance.log( +// "Failed to refresh transactions with nonces for ${coin.prettyName} " +// "$walletName $walletId: ${txsResponse.exception}", +// level: LogLevel.Warning, +// ); +// } +// } +// +// void stopNetworkAlivePinging() { +// _networkAliveTimer?.cancel(); +// _networkAliveTimer = null; +// } +// +// void startNetworkAlivePinging() { +// // call once on start right away +// _periodicPingCheck(); +// +// // then periodically check +// _networkAliveTimer = Timer.periodic( +// Constants.networkAliveTimerDuration, +// (_) async { +// _periodicPingCheck(); +// }, +// ); +// } +// } diff --git a/lib/services/ethereum/ethereum_token_service.dart b/lib/services/ethereum/ethereum_token_service.dart index e3febd13b..c83c95996 100644 --- a/lib/services/ethereum/ethereum_token_service.dart +++ b/lib/services/ethereum/ethereum_token_service.dart @@ -21,7 +21,6 @@ import 'package:stackwallet/models/balance.dart'; import 'package:stackwallet/models/isar/models/isar_models.dart'; import 'package:stackwallet/models/node_model.dart'; import 'package:stackwallet/models/paymint/fee_object_model.dart'; -import 'package:stackwallet/services/coins/ethereum/ethereum_wallet.dart'; import 'package:stackwallet/services/ethereum/ethereum_api.dart'; import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart'; import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; @@ -39,6 +38,7 @@ import 'package:stackwallet/utilities/extensions/impl/contract_abi.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/wallets/models/tx_data.dart'; +import 'package:stackwallet/wallets/wallet/impl/ethereum_wallet.dart'; import 'package:tuple/tuple.dart'; import 'package:web3dart/web3dart.dart' as web3dart; @@ -182,7 +182,7 @@ class EthTokenWallet extends ChangeNotifier with EthTokenCache { numberOfMessages: null, ); - Address? address = await ethWallet.db.getAddress( + Address? address = await ethWallet.mainDB.getAddress( ethWallet.walletId, addressString, ); @@ -197,7 +197,7 @@ class EthTokenWallet extends ChangeNotifier with EthTokenCache { subType: AddressSubType.nonWallet, ); - await ethWallet.db.addNewTransactionData( + await ethWallet.mainDB.addNewTransactionData( [ Tuple2(transaction, address), ], @@ -211,7 +211,7 @@ class EthTokenWallet extends ChangeNotifier with EthTokenCache { address?.value ?? _credentials.address.toString()); } - Future get _currentReceivingAddress => ethWallet.db + Future get _currentReceivingAddress => ethWallet.mainDB .getAddresses(ethWallet.walletId) .filter() .typeEqualTo(AddressType.ethereum) @@ -255,12 +255,12 @@ class EthTokenWallet extends ChangeNotifier with EthTokenCache { ); } - String? mnemonicString = await ethWallet.mnemonicString; + String? mnemonicString = await ethWallet.getMnemonic(); //Get private key for given mnemonic String privateKey = getPrivateKey( mnemonicString!, - (await ethWallet.mnemonicPassphrase) ?? "", + (await ethWallet.getMnemonicPassphrase()) ?? "", ); _credentials = web3dart.EthPrivateKey.fromHex(privateKey); @@ -382,7 +382,7 @@ class EthTokenWallet extends ChangeNotifier with EthTokenCache { await _refreshTransactions(); } catch (e, s) { Logging.instance.log( - "Caught exception in ${tokenContract.name} ${ethWallet.walletName} ${ethWallet.walletId} refresh(): $e\n$s", + "Caught exception in ${tokenContract.name} ${ethWallet.info.name} ${ethWallet.walletId} refresh(): $e\n$s", level: LogLevel.Warning, ); } finally { @@ -429,7 +429,7 @@ class EthTokenWallet extends ChangeNotifier with EthTokenCache { } } - Future> get transactions => ethWallet.db + Future> get transactions => ethWallet.mainDB .getTransactions(ethWallet.walletId) .filter() .otherDataEqualTo(tokenContract.address) @@ -557,7 +557,7 @@ class EthTokenWallet extends ChangeNotifier with EthTokenCache { numberOfMessages: null, ); - Address? transactionAddress = await ethWallet.db + Address? transactionAddress = await ethWallet.mainDB .getAddresses(ethWallet.walletId) .filter() .valueEqualTo(toAddress) @@ -580,14 +580,14 @@ class EthTokenWallet extends ChangeNotifier with EthTokenCache { txnsData.add(Tuple2(txn, transactionAddress)); } } - await ethWallet.db.addNewTransactionData(txnsData, ethWallet.walletId); + await ethWallet.mainDB.addNewTransactionData(txnsData, ethWallet.walletId); // quick hack to notify manager to call notifyListeners if // transactions changed if (txnsData.isNotEmpty) { GlobalEventBus.instance.fire( UpdatedInBackgroundEvent( - "${tokenContract.name} transactions updated/added for: ${ethWallet.walletId} ${ethWallet.walletName}", + "${tokenContract.name} transactions updated/added for: ${ethWallet.walletId} ${ethWallet.info.name}", ethWallet.walletId, ), ); diff --git a/lib/wallets/crypto_currency/coins/ethereum.dart b/lib/wallets/crypto_currency/coins/ethereum.dart new file mode 100644 index 000000000..624fcd3d2 --- /dev/null +++ b/lib/wallets/crypto_currency/coins/ethereum.dart @@ -0,0 +1,37 @@ +import 'package:ethereum_addresses/ethereum_addresses.dart'; +import 'package:stackwallet/models/node_model.dart'; +import 'package:stackwallet/utilities/default_nodes.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart'; +import 'package:stackwallet/wallets/crypto_currency/intermediate/bip39_currency.dart'; + +class Ethereum extends Bip39Currency { + Ethereum(super.network) { + switch (network) { + case CryptoCurrencyNetwork.main: + coin = Coin.ethereum; + default: + throw Exception("Unsupported network: $network"); + } + } + + int get gasLimit => 21000; + + @override + bool get hasTokenSupport => true; + + @override + NodeModel get defaultNode => DefaultNodes.ethereum; + + @override + // Not used for eth + String get genesisHash => throw UnimplementedError(); + + @override + int get minConfirms => 3; + + @override + bool validateAddress(String address) { + return isValidEthereumAddress(address); + } +} diff --git a/lib/wallets/isar/models/wallet_info.dart b/lib/wallets/isar/models/wallet_info.dart index e18cba80d..96c8e6416 100644 --- a/lib/wallets/isar/models/wallet_info.dart +++ b/lib/wallets/isar/models/wallet_info.dart @@ -339,6 +339,25 @@ class WalletInfo implements IsarId { } } + /// copies this with a new name and updates the db + Future updateContractAddresses({ + required Set newContractAddresses, + required Isar isar, + }) async { + // only update if there were changes to the name + if (tokenContractAddresses + .toSet() + .difference(newContractAddresses) + .isNotEmpty) { + await updateOtherData( + newEntries: { + WalletInfoKeys.tokenContractAddresses: newContractAddresses.toList(), + }, + isar: isar, + ); + } + } + //============================================================================ WalletInfo({ diff --git a/lib/wallets/wallet/impl/ethereum_wallet.dart b/lib/wallets/wallet/impl/ethereum_wallet.dart new file mode 100644 index 000000000..64190e0e5 --- /dev/null +++ b/lib/wallets/wallet/impl/ethereum_wallet.dart @@ -0,0 +1,380 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:ethereum_addresses/ethereum_addresses.dart'; +import 'package:http/http.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/models/balance.dart'; +import 'package:stackwallet/models/isar/models/blockchain_data/address.dart'; +import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart'; +import 'package:stackwallet/models/isar/models/blockchain_data/v2/input_v2.dart'; +import 'package:stackwallet/models/isar/models/blockchain_data/v2/output_v2.dart'; +import 'package:stackwallet/models/isar/models/blockchain_data/v2/transaction_v2.dart'; +import 'package:stackwallet/models/paymint/fee_object_model.dart'; +import 'package:stackwallet/services/ethereum/ethereum_api.dart'; +import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart'; +import 'package:stackwallet/services/event_bus/global_event_bus.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/eth_commons.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/wallets/crypto_currency/coins/ethereum.dart'; +import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart'; +import 'package:stackwallet/wallets/models/tx_data.dart'; +import 'package:stackwallet/wallets/wallet/intermediate/bip39_wallet.dart'; +import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/private_key_interface.dart'; +import 'package:web3dart/web3dart.dart' as web3; + +// Eth can not use tor with web3dart + +class EthereumWallet extends Bip39Wallet with PrivateKeyInterface { + EthereumWallet(CryptoCurrencyNetwork network) : super(Ethereum(network)); + + Timer? timer; + late web3.EthPrivateKey _credentials; + + Future updateTokenContracts(List contractAddresses) async { + await info.updateContractAddresses( + newContractAddresses: contractAddresses.toSet(), + isar: mainDB.isar, + ); + + GlobalEventBus.instance.fire( + UpdatedInBackgroundEvent( + "$contractAddresses updated/added for: $walletId ${info.name}", + walletId, + ), + ); + } + + web3.Web3Client getEthClient() { + final node = getCurrentNode(); + + // Eth can not use tor with web3dart as Client does not support proxies + final client = Client(); + + return web3.Web3Client(node.host, client); + } + + // ==================== Private ============================================== + + Future _initCredentials( + String mnemonic, + String mnemonicPassphrase, + ) async { + String privateKey = getPrivateKey(mnemonic, mnemonicPassphrase); + _credentials = web3.EthPrivateKey.fromHex(privateKey); + } + + Future _generateAndSaveAddress( + String mnemonic, + String mnemonicPassphrase, + ) async { + await _initCredentials(mnemonic, mnemonicPassphrase); + + final address = Address( + walletId: walletId, + value: _credentials.address.hexEip55, + publicKey: [], // maybe store address bytes here? seems a waste of space though + derivationIndex: 0, + derivationPath: DerivationPath()..value = "$hdPathEthereum/0", + type: AddressType.ethereum, + subType: AddressSubType.receiving, + ); + + await mainDB.updateOrPutAddresses([address]); + } + + // delete + @override + Future
getCurrentReceivingAddress() async { + return Address( + walletId: walletId, + value: + checksumEthereumAddress("0x6Cc3006944070B32D80107D51d843a66EaC00686"), + publicKey: [], // maybe store address bytes here? seems a waste of space though + derivationIndex: 0, + derivationPath: DerivationPath()..value = "$hdPathEthereum/0", + type: AddressType.ethereum, + subType: AddressSubType.receiving, + ); + } + + // ==================== Overrides ============================================ + + @override + FilterOperation? get changeAddressFilterOperation => + FilterGroup.and(standardChangeAddressFilters); + + @override + FilterOperation? get receivingAddressFilterOperation => + FilterGroup.and(standardReceivingAddressFilters); + + @override + Future init({bool? isRestore}) { + // TODO: implement init + return super.init(); + } + + @override + Future estimateFeeFor(Amount amount, int feeRate) async { + return estimateFee( + feeRate, + (cryptoCurrency as Ethereum).gasLimit, + cryptoCurrency.fractionDigits, + ); + } + + @override + Future get fees => EthereumAPI.getFees(); + + @override + Future pingCheck() async { + web3.Web3Client client = getEthClient(); + try { + await client.getBlockNumber(); + return true; + } catch (_) { + return false; + } + } + + @override + Future updateBalance() async { + try { + final client = getEthClient(); + + final addressHex = (await getCurrentReceivingAddress())!.value; + final address = web3.EthereumAddress.fromHex(addressHex); + web3.EtherAmount ethBalance = await client.getBalance(address); + final balance = Balance( + total: Amount( + rawValue: ethBalance.getInWei, + fractionDigits: cryptoCurrency.fractionDigits, + ), + spendable: Amount( + rawValue: ethBalance.getInWei, + fractionDigits: cryptoCurrency.fractionDigits, + ), + blockedTotal: Amount.zeroWith( + fractionDigits: cryptoCurrency.fractionDigits, + ), + pendingSpendable: Amount.zeroWith( + fractionDigits: cryptoCurrency.fractionDigits, + ), + ); + await info.updateBalance( + newBalance: balance, + isar: mainDB.isar, + ); + } catch (e, s) { + Logging.instance.log( + "$runtimeType wallet failed to update balance: $e\n$s", + level: LogLevel.Warning, + ); + } + } + + @override + Future updateChainHeight() async { + try { + final client = getEthClient(); + final height = await client.getBlockNumber(); + + await info.updateCachedChainHeight( + newHeight: height, + isar: mainDB.isar, + ); + } catch (e, s) { + Logging.instance.log( + "$runtimeType Exception caught in chainHeight: $e\n$s", + level: LogLevel.Warning, + ); + } + } + + @override + Future updateNode() async { + // do nothing + } + + @override + Future updateTransactions({bool isRescan = false}) async { + String thisAddress = (await getCurrentReceivingAddress())!.value; + + int firstBlock = 0; + + if (!isRescan) { + firstBlock = await mainDB.isar.transactionV2s + .where() + .walletIdEqualTo(walletId) + .heightProperty() + .max() ?? + 0; + + if (firstBlock > 10) { + // add some buffer + firstBlock -= 10; + } + } + + final response = await EthereumAPI.getEthTransactions( + address: thisAddress, + firstBlock: isRescan ? 0 : firstBlock, + includeTokens: true, + ); + + if (response.value == null) { + Logging.instance.log( + "Failed to refresh transactions for ${cryptoCurrency.coin.prettyName} ${info.name} " + "$walletId: ${response.exception}", + level: LogLevel.Warning, + ); + return; + } + + if (response.value!.isEmpty) { + // no new transactions found + return; + } + + final txsResponse = + await EthereumAPI.getEthTransactionNonces(response.value!); + + if (txsResponse.value != null) { + final allTxs = txsResponse.value!; + final List txns = []; + for (final tuple in allTxs) { + final element = tuple.item1; + + //Calculate fees (GasLimit * gasPrice) + // int txFee = element.gasPrice * element.gasUsed; + Amount txFee = element.gasCost; + final transactionAmount = element.value; + final addressFrom = checksumEthereumAddress(element.from); + final addressTo = checksumEthereumAddress(element.to); + + bool isIncoming; + bool txFailed = false; + if (addressFrom == thisAddress) { + if (element.isError) { + txFailed = true; + } + isIncoming = false; + } else if (addressTo == thisAddress) { + isIncoming = true; + } else { + continue; + } + + // hack epic tx data into inputs and outputs + final List outputs = []; + final List inputs = []; + + OutputV2 output = OutputV2.isarCantDoRequiredInDefaultConstructor( + scriptPubKeyHex: "00", + valueStringSats: transactionAmount.raw.toString(), + addresses: [ + addressTo, + ], + walletOwns: addressTo == thisAddress, + ); + InputV2 input = InputV2.isarCantDoRequiredInDefaultConstructor( + scriptSigHex: null, + sequence: null, + outpoint: null, + addresses: [addressFrom], + valueStringSats: transactionAmount.raw.toString(), + witness: null, + innerRedeemScriptAsm: null, + coinbase: null, + walletOwns: addressFrom == thisAddress, + ); + + final TransactionType txType; + if (isIncoming) { + if (addressFrom == addressTo) { + txType = TransactionType.sentToSelf; + } else { + txType = TransactionType.incoming; + } + } else { + txType = TransactionType.outgoing; + } + + outputs.add(output); + inputs.add(input); + + final otherData = { + "nonce": tuple.item2, + "isCancelled": txFailed, + "anonFees": txFee.toJsonString(), + }; + + final txn = TransactionV2( + walletId: walletId, + blockHash: element.blockHash, + hash: element.hash, + txid: element.hash, + timestamp: element.timestamp, + height: element.blockNumber, + inputs: List.unmodifiable(inputs), + outputs: List.unmodifiable(outputs), + version: -1, + type: txType, + subType: TransactionSubType.none, + otherData: jsonEncode(otherData), + ); + + txns.add(txn); + } + await mainDB.updateOrPutTransactionV2s(txns); + } else { + Logging.instance.log( + "Failed to refresh transactions with nonces for ${cryptoCurrency.coin.prettyName} " + "${info.name} $walletId: ${txsResponse.exception}", + level: LogLevel.Warning, + ); + } + } + + @override + Future updateUTXOs() async { + // not used in eth + return false; + } + + @override + Future confirmSend({required TxData txData}) { + // TODO: implement confirmSend + throw UnimplementedError(); + } + + @override + Future prepareSend({required TxData txData}) { + // TODO: implement prepareSend + throw UnimplementedError(); + } + + @override + Future recover({required bool isRescan}) async { + if (isRescan) { + await mainDB.deleteWalletBlockchainData(walletId); + await _generateAndSaveAddress( + await getMnemonic(), + await getMnemonicPassphrase(), + ); + await updateBalance(); + await updateTransactions(isRescan: true); + } else { + // + } + } + + @override + Future exit() async { + timer?.cancel(); + timer = null; + await super.exit(); + } +} diff --git a/lib/wallets/wallet/private_key_based_wallet.dart b/lib/wallets/wallet/private_key_based_wallet.dart deleted file mode 100644 index 74f2afd04..000000000 --- a/lib/wallets/wallet/private_key_based_wallet.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'package:stackwallet/exceptions/sw_exception.dart'; -import 'package:stackwallet/wallets/models/tx_data.dart'; -import 'package:stackwallet/wallets/wallet/wallet.dart'; - -abstract class PrivateKeyBasedWallet extends Wallet { - PrivateKeyBasedWallet(super.cryptoCurrency); - - Future getPrivateKey() async { - final privateKey = await secureStorageInterface.read( - key: Wallet.privateKeyKey(walletId: info.walletId), - ); - - if (privateKey == null) { - throw SWException("privateKey has not been set"); - } - - return privateKey; - } - - // ========== Overrides ====================================================== - - @override - Future confirmSend({required TxData txData}) { - // TODO: implement confirmSend - throw UnimplementedError(); - } - - @override - Future prepareSend({required TxData txData}) { - // TODO: implement prepareSend - throw UnimplementedError(); - } - - @override - Future recover({required bool isRescan}) { - // TODO: implement recover - throw UnimplementedError(); - } -} diff --git a/lib/wallets/wallet/wallet.dart b/lib/wallets/wallet/wallet.dart index 8a8846a4a..403e0e51d 100644 --- a/lib/wallets/wallet/wallet.dart +++ b/lib/wallets/wallet/wallet.dart @@ -28,6 +28,7 @@ import 'package:stackwallet/wallets/wallet/impl/bitcoincash_wallet.dart'; import 'package:stackwallet/wallets/wallet/impl/dogecoin_wallet.dart'; import 'package:stackwallet/wallets/wallet/impl/ecash_wallet.dart'; import 'package:stackwallet/wallets/wallet/impl/epiccash_wallet.dart'; +import 'package:stackwallet/wallets/wallet/impl/ethereum_wallet.dart'; import 'package:stackwallet/wallets/wallet/impl/firo_wallet.dart'; import 'package:stackwallet/wallets/wallet/impl/litecoin_wallet.dart'; import 'package:stackwallet/wallets/wallet/impl/monero_wallet.dart'; @@ -37,11 +38,11 @@ import 'package:stackwallet/wallets/wallet/impl/particl_wallet.dart'; import 'package:stackwallet/wallets/wallet/impl/tezos_wallet.dart'; import 'package:stackwallet/wallets/wallet/impl/wownero_wallet.dart'; import 'package:stackwallet/wallets/wallet/intermediate/cryptonote_wallet.dart'; -import 'package:stackwallet/wallets/wallet/private_key_based_wallet.dart'; import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/electrumx_interface.dart'; import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/lelantus_interface.dart'; import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart'; import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/multi_address_interface.dart'; +import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/private_key_interface.dart'; import 'package:stackwallet/wallets/wallet/wallet_mixin_interfaces/spark_interface.dart'; abstract class Wallet { @@ -156,7 +157,10 @@ abstract class Wallet { } } - if (wallet is PrivateKeyBasedWallet) { + // TODO [prio=low] handle eth differently? + // This would need to be changed if we actually end up allowing eth wallets + // to be created with a private key instead of mnemonic only + if (wallet is PrivateKeyInterface && wallet is! EthereumWallet) { await secureStorageInterface.write( key: privateKeyKey(walletId: walletInfo.walletId), value: privateKey!, @@ -278,6 +282,9 @@ abstract class Wallet { case Coin.epicCash: return EpiccashWallet(CryptoCurrencyNetwork.main); + case Coin.ethereum: + return EthereumWallet(CryptoCurrencyNetwork.main); + case Coin.firo: return FiroWallet(CryptoCurrencyNetwork.main); case Coin.firoTestNet: diff --git a/lib/wallets/wallet/wallet_mixin_interfaces/private_key_interface.dart b/lib/wallets/wallet/wallet_mixin_interfaces/private_key_interface.dart new file mode 100644 index 000000000..b5095cda1 --- /dev/null +++ b/lib/wallets/wallet/wallet_mixin_interfaces/private_key_interface.dart @@ -0,0 +1,37 @@ +import 'package:stackwallet/exceptions/sw_exception.dart'; +import 'package:stackwallet/wallets/crypto_currency/crypto_currency.dart'; +import 'package:stackwallet/wallets/wallet/wallet.dart'; + +mixin PrivateKeyInterface on Wallet { + Future getPrivateKey() async { + final privateKey = await secureStorageInterface.read( + key: Wallet.privateKeyKey(walletId: info.walletId), + ); + + if (privateKey == null) { + throw SWException("privateKey has not been set"); + } + + return privateKey; + } + + // ========== Overrides ====================================================== + + // @override + // Future confirmSend({required TxData txData}) { + // // TODO: implement confirmSend + // throw UnimplementedError(); + // } + // + // @override + // Future prepareSend({required TxData txData}) { + // // TODO: implement prepareSend + // throw UnimplementedError(); + // } + // + // @override + // Future recover({required bool isRescan}) { + // // TODO: implement recover + // throw UnimplementedError(); + // } +} diff --git a/lib/widgets/master_wallet_card.dart b/lib/widgets/master_wallet_card.dart index 7bb5d6566..ec73604d4 100644 --- a/lib/widgets/master_wallet_card.dart +++ b/lib/widgets/master_wallet_card.dart @@ -12,11 +12,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:stackwallet/providers/providers.dart'; -import 'package:stackwallet/services/coins/ethereum/ethereum_wallet.dart'; import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/wallets/wallet/impl/ethereum_wallet.dart'; import 'package:stackwallet/widgets/animated_widgets/rotate_icon.dart'; import 'package:stackwallet/widgets/expandable.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; @@ -47,7 +47,7 @@ class _MasterWalletCardState extends ConsumerState { final ethWallet = ref.read(pWallets).getWallet(widget.walletId) as EthereumWallet; - tokenContractAddresses = ethWallet.getWalletTokenContractAddresses(); + tokenContractAddresses = ethWallet.info.tokenContractAddresses; super.initState(); } diff --git a/lib/widgets/wallet_card.dart b/lib/widgets/wallet_card.dart index 04dd7c917..e2814ffea 100644 --- a/lib/widgets/wallet_card.dart +++ b/lib/widgets/wallet_card.dart @@ -20,7 +20,6 @@ import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/des import 'package:stackwallet/providers/db/main_db_provider.dart'; import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/providers/providers.dart'; -import 'package:stackwallet/services/coins/ethereum/ethereum_wallet.dart'; import 'package:stackwallet/services/ethereum/ethereum_token_service.dart'; import 'package:stackwallet/services/transaction_notification_tracker.dart'; import 'package:stackwallet/utilities/constants.dart'; @@ -28,6 +27,7 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/show_loading.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/wallets/wallet/impl/ethereum_wallet.dart'; import 'package:stackwallet/wallets/wallet/wallet.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; @@ -58,7 +58,6 @@ class SimpleWalletCard extends ConsumerWidget { ref.read(tokenServiceStateProvider.state).state = EthTokenWallet( token: contract, secureStore: ref.read(secureStoreProvider), - // TODO: [prio=high] FIX THIS BAD as CAST ethWallet: wallet as EthereumWallet, tracker: TransactionNotificationTracker( walletId: walletId, diff --git a/lib/widgets/wallet_info_row/sub_widgets/wallet_info_row_balance.dart b/lib/widgets/wallet_info_row/sub_widgets/wallet_info_row_balance.dart index 09a4ef628..b49e21cc7 100644 --- a/lib/widgets/wallet_info_row/sub_widgets/wallet_info_row_balance.dart +++ b/lib/widgets/wallet_info_row/sub_widgets/wallet_info_row_balance.dart @@ -13,13 +13,13 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/db/isar/main_db.dart'; import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart'; import 'package:stackwallet/providers/providers.dart'; -import 'package:stackwallet/services/coins/ethereum/ethereum_wallet.dart'; import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/amount/amount_formatter.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/wallets/isar/providers/wallet_info_provider.dart'; +import 'package:stackwallet/wallets/wallet/impl/ethereum_wallet.dart'; class WalletInfoRowBalance extends ConsumerWidget { const WalletInfoRowBalance({ @@ -47,7 +47,9 @@ class WalletInfoRowBalance extends ConsumerWidget { final ethWallet = ref.watch(pWallets).getWallet(walletId) as EthereumWallet; contract = MainDB.instance.getEthContractSync(contractAddress!)!; - totalBalance = ethWallet.getCachedTokenBalance(contract).total; + //TODO: [prio=high] fix this for token service/interface + // totalBalance = ethWallet.getCachedTokenBalance(contract).total; + totalBalance = Amount.zero; } return Text(