diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index 34a4c6b6d..b95b71031 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -163,6 +163,7 @@ jobs: echo "const litecoinTestWalletSeeds = '${{ secrets.LITECOIN_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart echo "const bitcoinCashTestWalletSeeds = '${{ secrets.BITCOIN_CASH_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart echo "const polygonTestWalletSeeds = '${{ secrets.POLYGON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart + echo "const baseTestWalletSeeds = '${{ secrets.BASE_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart echo "const solanaTestWalletSeeds = '${{ secrets.SOLANA_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart echo "const solanaTestWalletSeeds2 = '${{ secrets.SOLANA_TEST_WALLET_SEEDS2 }}';" >> lib/.secrets.g.dart echo "const tronTestWalletSeeds = '${{ secrets.TRON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart @@ -176,6 +177,7 @@ jobs: echo "const litecoinTestWalletReceiveAddress = '${{ secrets.LITECOIN_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart echo "const bitcoinCashTestWalletReceiveAddress = '${{ secrets.BITCOIN_CASH_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart echo "const polygonTestWalletReceiveAddress = '${{ secrets.POLYGON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart + echo "const baseTestWalletReceiveAddress = '${{ secrets.BASE_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart echo "const solanaTestWalletReceiveAddress = '${{ secrets.SOLANA_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart echo "const tronTestWalletReceiveAddress = '${{ secrets.TRON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart echo "const nanoTestWalletReceiveAddress = '${{ secrets.NANO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart diff --git a/.github/workflows/no_restricted_imports.yaml b/.github/workflows/no_restricted_imports.yaml index 03c3de018..2b07c27cd 100644 --- a/.github/workflows/no_restricted_imports.yaml +++ b/.github/workflows/no_restricted_imports.yaml @@ -20,6 +20,7 @@ jobs: "cw_mweb" "cw_nano" "cw_polygon" + "cw_base" "cw_solana" "cw_tron" "cw_wownero" diff --git a/.github/workflows/pr_test_build_android.yml b/.github/workflows/pr_test_build_android.yml index 5d5b52782..05fb8b993 100644 --- a/.github/workflows/pr_test_build_android.yml +++ b/.github/workflows/pr_test_build_android.yml @@ -157,6 +157,7 @@ jobs: echo "const litecoinTestWalletSeeds = '${{ secrets.LITECOIN_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart echo "const bitcoinCashTestWalletSeeds = '${{ secrets.BITCOIN_CASH_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart echo "const polygonTestWalletSeeds = '${{ secrets.POLYGON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart + echo "const baseTestWalletSeeds = '${{ secrets.BASE_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart echo "const solanaTestWalletSeeds = '${{ secrets.SOLANA_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart echo "const tronTestWalletSeeds = '${{ secrets.TRON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart echo "const nanoTestWalletSeeds = '${{ secrets.NANO_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart @@ -168,6 +169,7 @@ jobs: echo "const litecoinTestWalletReceiveAddress = '${{ secrets.LITECOIN_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart echo "const bitcoinCashTestWalletReceiveAddress = '${{ secrets.BITCOIN_CASH_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart echo "const polygonTestWalletReceiveAddress = '${{ secrets.POLYGON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart + echo "const baseTestWalletReceiveAddress = '${{ secrets.BASE_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart echo "const solanaTestWalletReceiveAddress = '${{ secrets.SOLANA_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart echo "const tronTestWalletReceiveAddress = '${{ secrets.TRON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart echo "const nanoTestWalletReceiveAddress = '${{ secrets.NANO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart diff --git a/.github/workflows/pr_test_build_linux.yml b/.github/workflows/pr_test_build_linux.yml index 40318d36c..e9f380926 100644 --- a/.github/workflows/pr_test_build_linux.yml +++ b/.github/workflows/pr_test_build_linux.yml @@ -150,6 +150,7 @@ jobs: echo "const litecoinTestWalletSeeds = '${{ secrets.LITECOIN_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart echo "const bitcoinCashTestWalletSeeds = '${{ secrets.BITCOIN_CASH_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart echo "const polygonTestWalletSeeds = '${{ secrets.POLYGON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart + echo "const baseTestWalletSeeds = '${{ secrets.BASE_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart echo "const solanaTestWalletSeeds = '${{ secrets.SOLANA_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart echo "const tronTestWalletSeeds = '${{ secrets.TRON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart echo "const nanoTestWalletSeeds = '${{ secrets.NANO_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart @@ -162,6 +163,7 @@ jobs: echo "const litecoinTestWalletReceiveAddress = '${{ secrets.LITECOIN_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart echo "const bitcoinCashTestWalletReceiveAddress = '${{ secrets.BITCOIN_CASH_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart echo "const polygonTestWalletReceiveAddress = '${{ secrets.POLYGON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart + echo "const baseTestWalletReceiveAddress = '${{ secrets.BASE_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart echo "const solanaTestWalletReceiveAddress = '${{ secrets.SOLANA_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart echo "const tronTestWalletReceiveAddress = '${{ secrets.TRON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart echo "const nanoTestWalletReceiveAddress = '${{ secrets.NANO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart diff --git a/.gitignore b/.gitignore index 3fa31bd7a..be83f1a62 100644 --- a/.gitignore +++ b/.gitignore @@ -141,6 +141,7 @@ lib/wownero/wownero.dart lib/zano/zano.dart lib/decred/decred.dart lib/dogecoin/dogecoin.dart +lib/base/base.dart ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@2x.png ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@2x~ipad.png diff --git a/README.md b/README.md index 87654b55f..1266d08a5 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Cake Wallet includes support for several cryptocurrencies, including: * Litecoin (LTC) * Bitcoin Cash (BCH) * Polygon (POL) +* Base (BASE) * Solana (SOL) * Tron (TRX) * Nano (XNO) diff --git a/analysis_options.yaml b/analysis_options.yaml index bd35233ba..bc119a66c 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -23,6 +23,7 @@ analyzer: lib/tron/cw_tron.dart, lib/wownero/cw_wownero.dart, lib/zano/cw_zano.dart, + lib/base/cw_base.dart, ] language: strict-casts: true diff --git a/android/app/src/main/AndroidManifestBase.xml b/android/app/src/main/AndroidManifestBase.xml index 1bc56c1d5..287554cd3 100644 --- a/android/app/src/main/AndroidManifestBase.xml +++ b/android/app/src/main/AndroidManifestBase.xml @@ -106,6 +106,9 @@ + + + diff --git a/assets/base_node_list.yml b/assets/base_node_list.yml new file mode 100644 index 000000000..d135f7aea --- /dev/null +++ b/assets/base_node_list.yml @@ -0,0 +1,15 @@ +- + uri: base.nownodes.io + is_default: true + useSSL: true + isEnabledForAutoSwitching: true +- + uri: base.llamarpc.com + useSSL: true +- + uri: base-rpc.publicnode.com + useSSL: true + isEnabledForAutoSwitching: true +- + uri: 1rpc.io/base + useSSL: true \ No newline at end of file diff --git a/assets/images/base_chain_QR.svg b/assets/images/base_chain_QR.svg new file mode 100644 index 000000000..7f801d529 --- /dev/null +++ b/assets/images/base_chain_QR.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/base_chain_mono.svg b/assets/images/base_chain_mono.svg new file mode 100644 index 000000000..48ebe83a3 --- /dev/null +++ b/assets/images/base_chain_mono.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/base_icon.png b/assets/images/base_icon.png new file mode 100644 index 000000000..d7cb8edad Binary files /dev/null and b/assets/images/base_icon.png differ diff --git a/cakewallet.bat b/cakewallet.bat index b9da8762f..8baa910e8 100644 --- a/cakewallet.bat +++ b/cakewallet.bat @@ -1,5 +1,5 @@ @echo off -set cw_win_app_config=--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --dogecoin +set cw_win_app_config=--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --dogecoin --base set cw_root=%cd% set cw_archive_name=Cake Wallet.zip set cw_archive_path=%cw_root%\%cw_archive_name% @@ -24,7 +24,7 @@ IF NOT EXIST "%secrets_file_path%" ( ) ELSE (echo === Using previously/already generated secrets file: %secrets_file_path% ===) echo === Generating mobx models === -for /d %%i in (cw_core cw_monero cw_bitcoin cw_ethereum cw_evm cw_polygon cw_nano cw_bitcoin_cash cw_solana cw_tron .) do ( +for /d %%i in (cw_core cw_monero cw_bitcoin cw_ethereum cw_evm cw_polygon cw_nano cw_bitcoin_cash cw_solana cw_tron cw_base.) do ( cd %%i call flutter pub get > nul call dart run build_runner build --delete-conflicting-outputs > nul diff --git a/cw_base/.gitignore b/cw_base/.gitignore new file mode 100644 index 000000000..eb6c05cd3 --- /dev/null +++ b/cw_base/.gitignore @@ -0,0 +1,31 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +build/ diff --git a/cw_base/.metadata b/cw_base/.metadata new file mode 100644 index 000000000..6f8d1aa9c --- /dev/null +++ b/cw_base/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "fcf2c11572af6f390246c056bc905eca609533a0" + channel: "[user-branch]" + +project_type: package diff --git a/cw_base/CHANGELOG.md b/cw_base/CHANGELOG.md new file mode 100644 index 000000000..41cc7d819 --- /dev/null +++ b/cw_base/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: Describe initial release. diff --git a/cw_base/LICENSE b/cw_base/LICENSE new file mode 100644 index 000000000..ba75c69f7 --- /dev/null +++ b/cw_base/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/cw_base/README.md b/cw_base/README.md new file mode 100644 index 000000000..4a260d8d2 --- /dev/null +++ b/cw_base/README.md @@ -0,0 +1,39 @@ + + +TODO: Put a short description of the package here that helps potential users +know whether this package might be useful for them. + +## Features + +TODO: List what your package can do. Maybe include images, gifs, or videos. + +## Getting started + +TODO: List prerequisites and provide or point to information on how to +start using the package. + +## Usage + +TODO: Include short and useful examples for package users. Add longer examples +to `/example` folder. + +```dart +const like = 'sample'; +``` + +## Additional information + +TODO: Tell users more about the package: where to find more information, how to +contribute to the package, how to file issues, what response they can expect +from the package authors, and more. diff --git a/cw_base/analysis_options.yaml b/cw_base/analysis_options.yaml new file mode 100644 index 000000000..a5744c1cf --- /dev/null +++ b/cw_base/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/cw_base/lib/base_client.dart b/cw_base/lib/base_client.dart new file mode 100644 index 000000000..4cf7bc910 --- /dev/null +++ b/cw_base/lib/base_client.dart @@ -0,0 +1,103 @@ +import 'dart:convert'; + +import 'package:cw_evm/evm_chain_client.dart'; +import 'package:cw_evm/.secrets.g.dart' as secrets; +import 'package:cw_evm/evm_chain_transaction_model.dart'; +import 'package:flutter/foundation.dart'; +import 'package:web3dart/web3dart.dart'; + +class BaseClient extends EVMChainClient { + @override + Transaction createTransaction({ + required EthereumAddress from, + required EthereumAddress to, + required EtherAmount amount, + EtherAmount? maxPriorityFeePerGas, + Uint8List? data, + int? maxGas, + EtherAmount? gasPrice, + EtherAmount? maxFeePerGas, + }) { + EtherAmount? finalGasPrice = gasPrice; + + if (gasPrice == null && maxFeePerGas != null) { + // If we have EIP-1559 parameters but no legacy gasPrice, then use maxFeePerGas as gasPrice + finalGasPrice = maxFeePerGas; + } + + return Transaction( + from: from, + to: to, + value: amount, + data: data, + maxGas: maxGas, + gasPrice: finalGasPrice, + // maxFeePerGas: maxFeePerGas, + // maxPriorityFeePerGas: maxPriorityFeePerGas, + ); + } + + @override + Uint8List prepareSignedTransactionForSending(Uint8List signedTransaction) => signedTransaction; + + @override + int get chainId => 8453; + + @override + Future> fetchTransactions(String address, + {String? contractAddress}) async { + try { + final response = await client.get(Uri.https("api.etherscan.io", "/v2/api", { + "chainid": "$chainId", + "module": "account", + "action": contractAddress != null ? "tokentx" : "txlist", + if (contractAddress != null) "contractaddress": contractAddress, + "address": address, + "apikey": secrets.etherScanApiKey, + })); + + final jsonResponse = json.decode(response.body) as Map; + + if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) { + final res = (jsonResponse['result'] as List); + + res.removeWhere((e) => e['value'] == '0'); + + return res + .map( + (e) => EVMChainTransactionModel.fromJson(e as Map, 'ETH'), + ) + .toList(); + } + + return []; + } catch (e) { + return []; + } + } + + @override + Future> fetchInternalTransactions(String address) async { + try { + final response = await client.get(Uri.https("api.etherscan.io", "/v2/api", { + "chainid": "$chainId", + "module": "account", + "action": "txlistinternal", + "address": address, + "apikey": secrets.etherScanApiKey, + })); + + final jsonResponse = json.decode(response.body) as Map; + + if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) { + return (jsonResponse['result'] as List) + .map((e) => EVMChainTransactionModel.fromJson(e as Map, 'ETH')) + .toList(); + } + + return []; + } catch (_) { + return []; + } + } +} diff --git a/cw_base/lib/base_mnemonics_exception.dart b/cw_base/lib/base_mnemonics_exception.dart new file mode 100644 index 000000000..83f8403d2 --- /dev/null +++ b/cw_base/lib/base_mnemonics_exception.dart @@ -0,0 +1,5 @@ +class BaseMnemonicIsIncorrectException implements Exception { + @override + String toString() => + 'Base mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.'; +} diff --git a/cw_base/lib/base_transaction_history.dart b/cw_base/lib/base_transaction_history.dart new file mode 100644 index 000000000..42d93a1e0 --- /dev/null +++ b/cw_base/lib/base_transaction_history.dart @@ -0,0 +1,20 @@ +import 'dart:core'; + +import 'package:cw_evm/evm_chain_transaction_history.dart'; +import 'package:cw_evm/evm_chain_transaction_info.dart'; +import 'package:cw_base/base_transaction_info.dart'; + +class BaseTransactionHistory extends EVMChainTransactionHistory { + BaseTransactionHistory({ + required super.walletInfo, + required super.password, + required super.encryptionFileUtils, + }); + + @override + String getTransactionHistoryFileName() => 'base_transactions.json'; + + @override + EVMChainTransactionInfo getTransactionInfo(Map val) => + BaseTransactionInfo.fromJson(val); +} diff --git a/cw_base/lib/base_transaction_info.dart b/cw_base/lib/base_transaction_info.dart new file mode 100644 index 000000000..858c4fce7 --- /dev/null +++ b/cw_base/lib/base_transaction_info.dart @@ -0,0 +1,41 @@ +import 'package:cw_core/transaction_direction.dart'; +import 'package:cw_evm/evm_chain_transaction_info.dart'; + +class BaseTransactionInfo extends EVMChainTransactionInfo { + BaseTransactionInfo({ + required super.id, + required super.height, + required super.ethAmount, + required super.ethFee, + required super.tokenSymbol, + required super.direction, + required super.isPending, + required super.date, + required super.confirmations, + required super.to, + required super.from, + super.contractAddress, + super.exponent, + }); + + factory BaseTransactionInfo.fromJson(Map data) { + return BaseTransactionInfo( + id: data['id'] as String, + height: data['height'] as int, + ethAmount: BigInt.parse(data['amount']), + exponent: data['exponent'] as int, + ethFee: BigInt.parse(data['fee']), + direction: parseTransactionDirectionFromInt(data['direction'] as int), + date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int), + isPending: data['isPending'] as bool, + confirmations: data['confirmations'] as int, + tokenSymbol: data['tokenSymbol'] as String, + to: data['to'], + from: data['from'], + contractAddress: data['contractAddress'], + ); + } + + @override + String get feeCurrency => 'ETH'; +} diff --git a/cw_base/lib/base_wallet.dart b/cw_base/lib/base_wallet.dart new file mode 100644 index 000000000..6b26fda25 --- /dev/null +++ b/cw_base/lib/base_wallet.dart @@ -0,0 +1,165 @@ +import 'dart:convert'; + +import 'package:cw_base/base_client.dart'; +import 'package:cw_base/base_transaction_history.dart'; +import 'package:cw_base/base_transaction_info.dart'; +import 'package:cw_base/default_base_erc20_tokens.dart'; +import 'package:cw_core/cake_hive.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/encryption_file_utils.dart'; +import 'package:cw_core/erc20_token.dart'; +import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/transaction_direction.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_keys_file.dart'; +import 'package:cw_evm/evm_chain_transaction_history.dart'; +import 'package:cw_evm/evm_chain_transaction_info.dart'; +import 'package:cw_evm/evm_chain_transaction_model.dart'; +import 'package:cw_evm/evm_chain_wallet.dart'; +import 'package:cw_evm/evm_erc20_balance.dart'; + +class BaseWallet extends EVMChainWallet { + BaseWallet({ + required super.walletInfo, + required super.password, + super.mnemonic, + super.initialBalance, + super.privateKey, + required super.client, + required super.encryptionFileUtils, + super.passphrase, + }) : super(nativeCurrency: CryptoCurrency.baseEth); + + @override + Future initErc20TokensBox() async { + final boxName = "${walletInfo.name.replaceAll(" ", "_")}_ ${Erc20Token.baseBoxName}"; + + evmChainErc20TokensBox = await CakeHive.openBox(boxName); + } + + @override + void addInitialTokens([bool isMigration = false]) { + final initialErc20Tokens = DefaultBaseErc20Tokens().initialBaseErc20Tokens; + + for (final token in initialErc20Tokens) { + if (evmChainErc20TokensBox.containsKey(token.contractAddress)) { + final existingToken = evmChainErc20TokensBox.get(token.contractAddress); + if (existingToken?.tag != token.tag) { + evmChainErc20TokensBox.put(token.contractAddress, token); + } + } else { + if (isMigration) token.enabled = false; + evmChainErc20TokensBox.put(token.contractAddress, token); + } + } + } + + @override + List get getDefaultTokenContractAddresses => + DefaultBaseErc20Tokens().initialBaseErc20Tokens.map((e) => e.contractAddress).toList(); + + @override + Future checkIfScanProviderIsEnabled() async { + return (await sharedPrefs.future).getBool("use_basescan") ?? true; + } + + @override + String getTransactionHistoryFileName() => 'base_transactions.json'; + + @override + Erc20Token createNewErc20TokenObject(Erc20Token token, String? iconPath) { + return Erc20Token( + name: token.name, + symbol: token.symbol, + contractAddress: token.contractAddress, + decimal: token.decimal, + enabled: token.enabled, + tag: token.tag ?? 'ETH', + iconPath: iconPath, + isPotentialScam: token.isPotentialScam, + ); + } + + @override + EVMChainTransactionInfo getTransactionInfo( + EVMChainTransactionModel transactionModel, String address) { + final model = BaseTransactionInfo( + id: transactionModel.hash, + height: transactionModel.blockNumber, + ethAmount: transactionModel.amount, + direction: transactionModel.from == address + ? TransactionDirection.outgoing + : TransactionDirection.incoming, + isPending: false, + date: transactionModel.date, + confirmations: transactionModel.confirmations, + ethFee: BigInt.from(transactionModel.gasUsed) * transactionModel.gasPrice, + exponent: transactionModel.tokenDecimal ?? 18, + tokenSymbol: transactionModel.tokenSymbol ?? "ETH", + to: transactionModel.to, + from: transactionModel.from, + contractAddress: transactionModel.contractAddress, + ); + return model; + } + + @override + EVMChainTransactionHistory setUpTransactionHistory( + WalletInfo walletInfo, String password, EncryptionFileUtils encryptionFileUtils) { + return BaseTransactionHistory( + walletInfo: walletInfo, + password: password, + encryptionFileUtils: encryptionFileUtils, + ); + } + + static Future open({ + required String name, + required String password, + required WalletInfo walletInfo, + required EncryptionFileUtils encryptionFileUtils, + }) async { + final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type); + final path = await pathForWallet(name: name, type: walletInfo.type); + + Map? data; + try { + final jsonSource = await encryptionFileUtils.read(path: path, password: password); + + data = json.decode(jsonSource) as Map; + } catch (e) { + if (!hasKeysFile) rethrow; + } + + final balance = EVMChainERC20Balance.fromJSON(data?['balance'] as String?) ?? + EVMChainERC20Balance(BigInt.zero); + + final WalletKeysData keysData; + // Migrate wallet from the old scheme to then new .keys file scheme + if (!hasKeysFile) { + final mnemonic = data!['mnemonic'] as String?; + final privateKey = data['private_key'] as String?; + final passphrase = data['passphrase'] as String?; + + keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey, passphrase: passphrase); + } else { + keysData = await WalletKeysFile.readKeysFile( + name, + walletInfo.type, + password, + encryptionFileUtils, + ); + } + + return BaseWallet( + walletInfo: walletInfo, + password: password, + mnemonic: keysData.mnemonic, + privateKey: keysData.privateKey, + passphrase: keysData.passphrase, + initialBalance: balance, + client: BaseClient(), + encryptionFileUtils: encryptionFileUtils, + ); + } +} diff --git a/cw_base/lib/base_wallet_service.dart b/cw_base/lib/base_wallet_service.dart new file mode 100644 index 000000000..b6ad9e4e0 --- /dev/null +++ b/cw_base/lib/base_wallet_service.dart @@ -0,0 +1,163 @@ +import 'package:bip39/bip39.dart' as bip39; +import 'package:cw_core/encryption_file_utils.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart'; +import 'package:cw_evm/evm_chain_wallet_service.dart'; +import 'package:cw_base/base_wallet.dart'; +import 'package:cw_base/base_client.dart'; +import 'package:cw_base/base_mnemonics_exception.dart'; + +class BaseWalletService extends EVMChainWalletService { + BaseWalletService( + super.walletInfoSource, + super.isDirect, { + required this.client, + }); + + late BaseClient client; + + @override + WalletType getType() => WalletType.base; + + @override + Future create(EVMChainNewWalletCredentials credentials, {bool? isTestnet}) async { + final strength = credentials.seedPhraseLength == 24 ? 256 : 128; + + final mnemonic = credentials.mnemonic ?? bip39.generateMnemonic(strength: strength); + + final wallet = BaseWallet( + walletInfo: credentials.walletInfo!, + mnemonic: mnemonic, + password: credentials.password!, + passphrase: credentials.passphrase, + client: client, + encryptionFileUtils: encryptionFileUtilsFor(isDirect), + ); + + await wallet.init(); + wallet.addInitialTokens(); + await wallet.save(); + return wallet; + } + + @override + Future openWallet(String name, String password) async { + final walletInfo = + walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType())); + + try { + final wallet = await BaseWallet.open( + name: name, + password: password, + walletInfo: walletInfo, + encryptionFileUtils: encryptionFileUtilsFor(isDirect), + ); + + await wallet.init(); + wallet.addInitialTokens(true); + await wallet.save(); + saveBackup(name); + return wallet; + } catch (_) { + await restoreWalletFilesFromBackup(name); + + final wallet = await BaseWallet.open( + name: name, + password: password, + walletInfo: walletInfo, + encryptionFileUtils: encryptionFileUtilsFor(isDirect), + ); + + await wallet.init(); + wallet.addInitialTokens(true); + await wallet.save(); + return wallet; + } + } + + @override + Future restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials, + {bool? isTestnet}) async { + final wallet = BaseWallet( + password: credentials.password!, + privateKey: credentials.privateKey, + walletInfo: credentials.walletInfo!, + client: client, + encryptionFileUtils: encryptionFileUtilsFor(isDirect), + ); + + await wallet.init(); + wallet.addInitialTokens(); + await wallet.save(); + return wallet; + } + + @override + Future restoreFromHardwareWallet( + EVMChainRestoreWalletFromHardware credentials) async { + credentials.walletInfo!.derivationInfo = DerivationInfo( + derivationType: DerivationType.bip39, + derivationPath: "m/44'/60'/${credentials.hwAccountData.accountIndex}'/0/0"); + credentials.walletInfo!.hardwareWalletType = credentials.hardwareWalletType; + credentials.walletInfo!.address = credentials.hwAccountData.address; + + final wallet = BaseWallet( + walletInfo: credentials.walletInfo!, + password: credentials.password!, + client: client, + encryptionFileUtils: encryptionFileUtilsFor(isDirect), + ); + + await wallet.init(); + wallet.addInitialTokens(); + await wallet.save(); + + return wallet; + } + + @override + Future restoreFromSeed(EVMChainRestoreWalletFromSeedCredentials credentials, + {bool? isTestnet}) async { + if (!bip39.validateMnemonic(credentials.mnemonic)) { + throw BaseMnemonicIsIncorrectException(); + } + + final wallet = BaseWallet( + password: credentials.password!, + mnemonic: credentials.mnemonic, + walletInfo: credentials.walletInfo!, + passphrase: credentials.passphrase, + client: client, + encryptionFileUtils: encryptionFileUtilsFor(isDirect), + ); + + await wallet.init(); + wallet.addInitialTokens(); + await wallet.save(); + + return wallet; + } + + @override + Future rename(String currentName, String password, String newName) async { + final currentWalletInfo = walletInfoSource.values + .firstWhere((info) => info.id == WalletBase.idFor(currentName, getType())); + final currentWallet = await BaseWallet.open( + password: password, + name: currentName, + walletInfo: currentWalletInfo, + encryptionFileUtils: encryptionFileUtilsFor(isDirect), + ); + + await currentWallet.renameWalletFiles(newName); + await saveBackup(newName); + + final newWalletInfo = currentWalletInfo; + newWalletInfo.id = WalletBase.idFor(newName, getType()); + newWalletInfo.name = newName; + + await walletInfoSource.put(currentWalletInfo.key, newWalletInfo); + } +} diff --git a/cw_base/lib/cw_base.dart b/cw_base/lib/cw_base.dart new file mode 100644 index 000000000..298576d81 --- /dev/null +++ b/cw_base/lib/cw_base.dart @@ -0,0 +1,5 @@ +/// A Calculator. +class Calculator { + /// Returns [value] plus 1. + int addOne(int value) => value + 1; +} diff --git a/cw_base/lib/default_base_erc20_tokens.dart b/cw_base/lib/default_base_erc20_tokens.dart new file mode 100644 index 000000000..3f899927c --- /dev/null +++ b/cw_base/lib/default_base_erc20_tokens.dart @@ -0,0 +1,67 @@ +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/erc20_token.dart'; + +class DefaultBaseErc20Tokens { + final List _defaultTokens = [ + Erc20Token( + name: "USD Coin", + symbol: "USDC", + contractAddress: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", + decimal: 6, + enabled: true, + ), + Erc20Token( + name: "USDe", + symbol: "USDe", + contractAddress: "0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34", + decimal: 18, + enabled: true, + ), + Erc20Token( + name: "Dai", + symbol: "DAI", + contractAddress: "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb", + decimal: 18, + enabled: true, + ), + Erc20Token( + name: "Bridged Tether USD", + symbol: "USDT", + contractAddress: "0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2", + decimal: 6, + enabled: true, + ), + Erc20Token( + name: "Wrapped Ether", + symbol: "WETH", + contractAddress: "0x4200000000000000000000000000000000000006", + decimal: 18, + enabled: false, + ), + Erc20Token( + name: "Wrapped BTC", + symbol: "WBTC", + contractAddress: "0x0555E30da8f98308EdB960aa94C0Db47230d2B9c", + decimal: 8, + enabled: false, + ), + Erc20Token( + name: "SPX6900", + symbol: "SPX", + contractAddress: "0x50dA645f148798F68EF2d7dB7C1CB22A6819bb2C", + decimal: 8, + enabled: false, + ), + ]; + + List get initialBaseErc20Tokens => _defaultTokens.map((token) { + String? iconPath; + try { + iconPath = CryptoCurrency.all + .firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase()) + .iconPath; + } catch (_) {} + + return Erc20Token.copyWith(token, iconPath, 'BASE'); + }).toList(); +} diff --git a/cw_base/pubspec.yaml b/cw_base/pubspec.yaml new file mode 100644 index 000000000..f94468bce --- /dev/null +++ b/cw_base/pubspec.yaml @@ -0,0 +1,73 @@ +name: cw_base +description: "Base package for Cake Wallet" +version: 0.0.1 +publish_to: none +homepage: https://cakewallet.com + +environment: + sdk: '>=3.0.6 <4.0.0' + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + cw_core: + path: ../cw_core + cw_ethereum: + path: ../cw_ethereum + cw_evm: + path: ../cw_evm + web3dart: ^2.7.1 + hive: ^2.2.3 + bip39: ^1.0.6 + collection: ^1.17.1 + +dependency_overrides: + web3dart: + git: + url: https://github.com/cake-tech/web3dart.git + ref: cake + watcher: ^1.1.0 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + build_runner: ^2.4.15 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # To add assets to your package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/to/asset-from-package + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # To add custom fonts to your package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/to/font-from-package diff --git a/cw_base/test/cw_base_test.dart b/cw_base/test/cw_base_test.dart new file mode 100644 index 000000000..31f598069 --- /dev/null +++ b/cw_base/test/cw_base_test.dart @@ -0,0 +1,12 @@ +import 'package:flutter_test/flutter_test.dart'; + +import 'package:cw_base/cw_base.dart'; + +void main() { + test('adds one to input values', () { + final calculator = Calculator(); + expect(calculator.addOne(2), 3); + expect(calculator.addOne(-7), -6); + expect(calculator.addOne(0), 1); + }); +} diff --git a/cw_bitcoin/lib/electrum_transaction_info.dart b/cw_bitcoin/lib/electrum_transaction_info.dart index 7a8b3b951..407e405e1 100644 --- a/cw_bitcoin/lib/electrum_transaction_info.dart +++ b/cw_bitcoin/lib/electrum_transaction_info.dart @@ -5,6 +5,7 @@ import 'package:cw_bitcoin/address_from_output.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_amount_format.dart'; import 'package:cw_bitcoin/bitcoin_unspent.dart'; +import 'package:cw_core/currency_for_wallet_type.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/format_amount.dart'; diff --git a/cw_core/lib/crypto_currency.dart b/cw_core/lib/crypto_currency.dart index a5c2a8179..eb8e5ac15 100644 --- a/cw_core/lib/crypto_currency.dart +++ b/cw_core/lib/crypto_currency.dart @@ -115,6 +115,7 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen CryptoCurrency.flip, CryptoCurrency.ndeps, CryptoCurrency.deps, + CryptoCurrency.baseEth, ]; static const havenCurrencies = [ @@ -241,6 +242,7 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen static const deps = CryptoCurrency(title: 'DEPS', tag: 'ETH', fullName: 'Decentralized Euro Protocol Share', raw: 101, name: 'deps', iconPath: 'assets/images/deps_icon.png', decimals: 18); static const kbtc = CryptoCurrency(title: 'KBTC', tag: 'ETH', fullName: 'Kraken Wrapped Bitcoin', raw: 102, name: 'kbtc', iconPath: 'assets/images/kbtc_icon.png', decimals: 8); static const cbbtc = CryptoCurrency(title: 'CBBTC', tag: 'ETH', fullName: 'Coinbase Wrapped BTC', raw: 103, name: 'cbbtc', iconPath: 'assets/images/cbbtc_icon.png', decimals: 8); + static const baseEth = CryptoCurrency(title: 'ETH', tag: 'BASE', fullName: 'Ethereum', raw: 104, name: 'baseth', iconPath: 'assets/images/eth_icon.png', decimals: 18); static final Map _rawCurrencyMap = [...all, ...havenCurrencies].fold>({}, (acc, item) { diff --git a/cw_core/lib/currency_for_wallet_type.dart b/cw_core/lib/currency_for_wallet_type.dart index 495731aa9..33fee93f4 100644 --- a/cw_core/lib/currency_for_wallet_type.dart +++ b/cw_core/lib/currency_for_wallet_type.dart @@ -1,21 +1,23 @@ import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/wallet_type.dart'; -CryptoCurrency currencyForWalletType(WalletType type, {bool? isTestnet}) { +CryptoCurrency walletTypeToCryptoCurrency(WalletType type, {bool isTestnet = false}) { switch (type) { + case WalletType.monero: + return CryptoCurrency.xmr; case WalletType.bitcoin: - if (isTestnet == true) { + if (isTestnet) { return CryptoCurrency.tbtc; } return CryptoCurrency.btc; - case WalletType.monero: - return CryptoCurrency.xmr; case WalletType.litecoin: return CryptoCurrency.ltc; case WalletType.haven: return CryptoCurrency.xhv; case WalletType.ethereum: return CryptoCurrency.eth; + case WalletType.base: + return CryptoCurrency.baseEth; case WalletType.bitcoinCash: return CryptoCurrency.bch; case WalletType.nano: @@ -38,41 +40,15 @@ CryptoCurrency currencyForWalletType(WalletType type, {bool? isTestnet}) { return CryptoCurrency.doge; case WalletType.none: throw Exception( - 'Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType'); + 'Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency'); } } -WalletType? walletTypeForCurrency(CryptoCurrency currency) { - switch (currency) { - case CryptoCurrency.btc: - return WalletType.bitcoin; - case CryptoCurrency.xmr: - return WalletType.monero; - case CryptoCurrency.ltc: - return WalletType.litecoin; - case CryptoCurrency.xhv: - return WalletType.haven; - case CryptoCurrency.eth: - return WalletType.ethereum; - case CryptoCurrency.bch: - return WalletType.bitcoinCash; - case CryptoCurrency.nano: - return WalletType.nano; - case CryptoCurrency.banano: - return WalletType.banano; - case CryptoCurrency.maticpoly: - return WalletType.polygon; - case CryptoCurrency.sol: - return WalletType.solana; - case CryptoCurrency.trx: - return WalletType.tron; - case CryptoCurrency.wow: - return WalletType.wownero; - case CryptoCurrency.zano: - return WalletType.zano; - case CryptoCurrency.dcr: - return WalletType.decred; - default: - return null; +extension CryptoCurrencyForWalletType on CryptoCurrency { + String? get normalizedIconPath { + if (this == CryptoCurrency.baseEth) { + return 'assets/images/base_icon.png'; + } + return iconPath; } } diff --git a/cw_core/lib/erc20_token.dart b/cw_core/lib/erc20_token.dart index fd76d28fc..7302d4eeb 100644 --- a/cw_core/lib/erc20_token.dart +++ b/cw_core/lib/erc20_token.dart @@ -70,6 +70,7 @@ class Erc20Token extends CryptoCurrency with HiveObjectMixin { static const boxName = 'Erc20Tokens'; static const ethereumBoxName = 'EthereumErc20Tokens'; static const polygonBoxName = 'PolygonErc20Tokens'; + static const baseBoxName = 'BaseErc20Tokens'; @override bool operator ==(other) => diff --git a/cw_core/lib/node.dart b/cw_core/lib/node.dart index ddccceddc..4908641b8 100644 --- a/cw_core/lib/node.dart +++ b/cw_core/lib/node.dart @@ -106,6 +106,7 @@ class Node extends HiveObject with Keyable { case WalletType.banano: case WalletType.ethereum: case WalletType.polygon: + case WalletType.base: case WalletType.solana: case WalletType.tron: case WalletType.zano: @@ -169,6 +170,7 @@ class Node extends HiveObject with Keyable { case WalletType.bitcoinCash: case WalletType.ethereum: case WalletType.polygon: + case WalletType.base: case WalletType.solana: case WalletType.tron: case WalletType.dogecoin: diff --git a/cw_core/lib/wallet_base.dart b/cw_core/lib/wallet_base.dart index b492fb041..769b14c3d 100644 --- a/cw_core/lib/wallet_base.dart +++ b/cw_core/lib/wallet_base.dart @@ -24,7 +24,7 @@ abstract class WalletBase walletInfo.type; - CryptoCurrency get currency => currencyForWalletType(type, isTestnet: isTestnet); + CryptoCurrency get currency => walletTypeToCryptoCurrency(type, isTestnet: isTestnet); String get id => walletInfo.id; diff --git a/cw_core/lib/wallet_type.dart b/cw_core/lib/wallet_type.dart index 3f4fa37fa..01ae3725d 100644 --- a/cw_core/lib/wallet_type.dart +++ b/cw_core/lib/wallet_type.dart @@ -19,6 +19,7 @@ const walletTypes = [ WalletType.zano, WalletType.decred, WalletType.dogecoin, + WalletType.base, ]; @HiveType(typeId: WALLET_TYPE_TYPE_ID) @@ -69,7 +70,10 @@ enum WalletType { decred, @HiveField(15) - dogecoin + dogecoin, + + @HiveField(16) + base, } int serializeToInt(WalletType type) { @@ -104,6 +108,8 @@ int serializeToInt(WalletType type) { return 13; case WalletType.dogecoin: return 14; + case WalletType.base: + return 15; case WalletType.none: return -1; } @@ -141,9 +147,10 @@ WalletType deserializeFromInt(int raw) { return WalletType.decred; case 14: return WalletType.dogecoin; + case 15: + return WalletType.base; default: - throw Exception( - 'Unexpected token: $raw for WalletType deserializeFromInt'); + throw Exception('Unexpected token: $raw for WalletType deserializeFromInt'); } } @@ -179,6 +186,8 @@ String walletTypeToString(WalletType type) { return 'Decred'; case WalletType.dogecoin: return 'Dogecoin'; + case WalletType.base: + return 'Base'; case WalletType.none: return ''; } @@ -216,52 +225,13 @@ String walletTypeToDisplayName(WalletType type) { return 'Decred (DCR)'; case WalletType.dogecoin: return 'Dogecoin (DOGE)'; + case WalletType.base: + return 'Base (BASE)'; case WalletType.none: return ''; } } -CryptoCurrency walletTypeToCryptoCurrency(WalletType type, {bool isTestnet = false}) { - switch (type) { - case WalletType.monero: - return CryptoCurrency.xmr; - case WalletType.bitcoin: - if (isTestnet) { - return CryptoCurrency.tbtc; - } - return CryptoCurrency.btc; - case WalletType.litecoin: - return CryptoCurrency.ltc; - case WalletType.haven: - return CryptoCurrency.xhv; - case WalletType.ethereum: - return CryptoCurrency.eth; - case WalletType.bitcoinCash: - return CryptoCurrency.bch; - case WalletType.nano: - return CryptoCurrency.nano; - case WalletType.banano: - return CryptoCurrency.banano; - case WalletType.polygon: - return CryptoCurrency.maticpoly; - case WalletType.solana: - return CryptoCurrency.sol; - case WalletType.tron: - return CryptoCurrency.trx; - case WalletType.wownero: - return CryptoCurrency.wow; - case WalletType.zano: - return CryptoCurrency.zano; - case WalletType.decred: - return CryptoCurrency.dcr; - case WalletType.dogecoin: - return CryptoCurrency.doge; - case WalletType.none: - throw Exception( - 'Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency'); - } -} - WalletType? cryptoCurrencyToWalletType(CryptoCurrency type) { switch (type) { case CryptoCurrency.xmr: @@ -294,6 +264,8 @@ WalletType? cryptoCurrencyToWalletType(CryptoCurrency type) { return WalletType.decred; case CryptoCurrency.doge: return WalletType.dogecoin; + case CryptoCurrency.baseEth: + return WalletType.base; default: return null; } diff --git a/cw_decred/lib/transaction_info.dart b/cw_decred/lib/transaction_info.dart index 4afe09e1b..18913175a 100644 --- a/cw_decred/lib/transaction_info.dart +++ b/cw_decred/lib/transaction_info.dart @@ -1,3 +1,4 @@ +import 'package:cw_core/currency_for_wallet_type.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/format_amount.dart'; diff --git a/cw_evm/lib/evm_chain_client.dart b/cw_evm/lib/evm_chain_client.dart index 29a88dcce..814979d6d 100644 --- a/cw_evm/lib/evm_chain_client.dart +++ b/cw_evm/lib/evm_chain_client.dart @@ -39,15 +39,14 @@ abstract class EVMChainClient { Uri? rpcUri; bool isModifiedNodeUri = false; - if (node.uriRaw == 'eth.nownodes.io' || node.uriRaw == 'matic.nownodes.io') { + if (node.uriRaw.contains('nownodes.io')) { isModifiedNodeUri = true; String nowNodeApiKey = secrets.nowNodesApiKey; rpcUri = Uri.https(node.uriRaw, '/$nowNodeApiKey'); } - _client = - Web3Client(isModifiedNodeUri ? rpcUri!.toString() : node.uri.toString(), client); + _client = Web3Client(isModifiedNodeUri ? rpcUri!.toString() : node.uri.toString(), client); return true; } catch (e) { @@ -127,10 +126,11 @@ abstract class EVMChainClient { final gasEstimate = await _client!.estimateGas( sender: senderAddress, to: EthereumAddress.fromHex(contractAddress), - data: data ?? transfer.encodeCall([ - toAddress, - value.getInWei, - ]), + data: data ?? + transfer.encodeCall([ + toAddress, + value.getInWei, + ]), ); return gasEstimate.toInt(); @@ -171,10 +171,10 @@ abstract class EVMChainClient { int? gasPrice, }) async { assert(currency == CryptoCurrency.eth || - currency == CryptoCurrency.maticpoly || + currency == CryptoCurrency.maticpoly || currency == CryptoCurrency.baseEth || contractAddress != null); - bool isNativeToken = currency == CryptoCurrency.eth || currency == CryptoCurrency.maticpoly; + bool isNativeToken = currency == CryptoCurrency.eth || currency == CryptoCurrency.maticpoly || currency == CryptoCurrency.baseEth; final Transaction transaction = createTransaction( from: privateKey.address, @@ -233,7 +233,6 @@ abstract class EVMChainClient { required String contractAddress, int? gasPrice, }) async { - final Transaction transaction = createTransaction( from: privateKey.address, to: EthereumAddress.fromHex(contractAddress), @@ -264,7 +263,8 @@ abstract class EVMChainClient { feeCurrency: feeCurrency, sendTransaction: () => sendTransaction(signedTransaction), exponent: exponent, - isInfiniteApproval: amount.toRadixString(16) == 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + isInfiniteApproval: amount.toRadixString(16) == + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', ); } @@ -342,7 +342,7 @@ abstract class EVMChainClient { final erc20 = ERC20(address: EthereumAddress.fromHex(contractAddress), client: _client!); final balance = await erc20.balanceOf(userAddress); - int exponent = (await erc20.decimals()).toInt(); + int exponent = (await erc20.decimals()).toInt(); return EVMChainERC20Balance(balance, exponent: exponent); } on RangeError catch (_) { diff --git a/cw_evm/lib/evm_chain_wallet.dart b/cw_evm/lib/evm_chain_wallet.dart index 35420a693..bc6bd37ea 100644 --- a/cw_evm/lib/evm_chain_wallet.dart +++ b/cw_evm/lib/evm_chain_wallet.dart @@ -171,25 +171,29 @@ abstract class EVMChainWalletBase try { // Check native balance await _client.getBalance(_evmChainPrivateKey.address, throwOnError: true); - + // Check USDC token balance String usdcContractAddress; - if (_client.chainId == 1) { - // Ethereum mainnet - usdcContractAddress = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"; - } else if (_client.chainId == 137) { - // Polygon mainnet - usdcContractAddress = "0x2791bca1f2de4661ed88a30c99a7a9449aa84174"; - } else { - // If we are not on Ethereum or Polygon, we skip ERC20 token check - return true; + + switch (_client.chainId) { + case 1: + usdcContractAddress = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"; + break; + case 137: + usdcContractAddress = "0x2791bca1f2de4661ed88a30c99a7a9449aa84174"; + break; + case 8453: + usdcContractAddress = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"; + break; + default: + return true; } - + await _client.fetchERC20Balances( _evmChainPrivateKey.address, usdcContractAddress, ); - + return true; } catch (e) { return false; @@ -542,6 +546,32 @@ abstract class EVMChainWalletBase maxFeePerGasForTransaction = gasFeesModel.maxFeePerGas; if (output.sendAll && transactionCurrency is! Erc20Token) { + if (_client.chainId == 8453) { + // Add a safety buffer for Base chain to account for gas price fluctuations + // Use 1% buffer or minimum 1000 wei (0.000001 ETH), whichever is higher + final gasBufferPercent = + estimatedFeesForTransaction * BigInt.from(101) ~/ BigInt.from(100); + final gasBufferMin = estimatedFeesForTransaction + BigInt.from(1000); + final gasBuffer = gasBufferPercent > gasBufferMin ? gasBufferPercent : gasBufferMin; + + totalAmount = (currencyBalance.balance - gasBuffer); + + // Re-estimate gas with the correct amount to get more accurate gas estimation + final refinedGasFeesModel = await calculateActualEstimatedFeeForCreateTransaction( + amount: totalAmount, + receivingAddressHex: toAddress, + priority: _credentials.priority!, + contractAddress: contractAddress, + ); + + // Use the higher of the two gas estimations to be safe + final refinedGasFee = BigInt.from(refinedGasFeesModel.estimatedGasFee); + estimatedFeesForTransaction = refinedGasFee > gasBuffer ? refinedGasFee : gasBuffer; + estimatedGasUnitsForTransaction = refinedGasFeesModel.estimatedGasUnits; + maxFeePerGasForTransaction = refinedGasFeesModel.maxFeePerGas; + } + + // Final amount calculation with the higher gas estimation totalAmount = (currencyBalance.balance - estimatedFeesForTransaction); } @@ -569,11 +599,7 @@ abstract class EVMChainWalletBase gasFee: estimatedFeesForTransaction, priority: _credentials.priority!, currency: transactionCurrency, - feeCurrency: switch (_client.chainId) { - 1 => "ETH", - 137 => "POL", - _ => "" - }, + feeCurrency: switch (_client.chainId) { 1 => "ETH", 137 => "POL", 8453 => "ETH", _ => "ETH" }, maxFeePerGas: maxFeePerGasForTransaction, exponent: exponent, contractAddress: @@ -890,6 +916,8 @@ abstract class EVMChainWalletBase /// EtherScan for Ethereum. /// /// PolygonScan for Polygon. + /// + /// BaseScan for Base. void updateScanProviderUsageState(bool isEnabled) { if (isEnabled) { _updateTransactions(); diff --git a/integration_test/components/common_test_flows.dart b/integration_test/components/common_test_flows.dart index 9cd3b3627..30cfe4d57 100644 --- a/integration_test/components/common_test_flows.dart +++ b/integration_test/components/common_test_flows.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:cake_wallet/entities/seed_type.dart'; import 'package:cake_wallet/reactions/wallet_utils.dart'; import 'package:cake_wallet/wallet_types.g.dart'; +import 'package:cw_core/currency_for_wallet_type.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -109,7 +110,7 @@ class CommonTestFlows { void confirmAllAvailableWalletTypeIconsDisplayCorrectly() { for (var walletType in availableWalletTypes) { - final imageUrl = walletTypeToCryptoCurrency(walletType).iconPath; + final imageUrl = walletTypeToCryptoCurrency(walletType).normalizedIconPath; final walletIconFinder = find.image( Image.asset( @@ -353,6 +354,8 @@ class CommonTestFlows { return secrets.polygonTestWalletSeeds; case WalletType.solana: return secrets.solanaTestWalletSeeds; + case WalletType.base: + return secrets.baseTestWalletSeeds; case WalletType.tron: return secrets.tronTestWalletSeeds; case WalletType.nano: @@ -387,6 +390,8 @@ class CommonTestFlows { return secrets.bitcoinCashTestWalletReceiveAddress; case WalletType.polygon: return secrets.polygonTestWalletReceiveAddress; + case WalletType.base: + return secrets.baseTestWalletReceiveAddress; case WalletType.solana: return secrets.solanaTestWalletReceiveAddress; case WalletType.tron: diff --git a/ios/Runner/InfoBase.plist b/ios/Runner/InfoBase.plist index 4a95339e1..3b11e216b 100644 --- a/ios/Runner/InfoBase.plist +++ b/ios/Runner/InfoBase.plist @@ -302,6 +302,26 @@ dogecoin-wallet + + CFBundleTypeRole + Editor + CFBundleURLName + base + CFBundleURLSchemes + + base + + + + CFBundleTypeRole + Viewer + CFBundleURLName + base-wallet + CFBundleURLSchemes + + base-wallet + + CFBundleVersion $(CURRENT_PROJECT_VERSION) diff --git a/lib/base/cw_base.dart b/lib/base/cw_base.dart new file mode 100644 index 000000000..02b966938 --- /dev/null +++ b/lib/base/cw_base.dart @@ -0,0 +1,238 @@ +part of 'base.dart'; + +class CWBase extends Base { + @override + List getBaseWordList(String language) => EVMChainMnemonics.englishWordlist; + + WalletService createBaseWalletService(Box walletInfoSource, bool isDirect) => + BaseWalletService(walletInfoSource, isDirect, client: BaseClient()); + + @override + WalletCredentials createBaseNewWalletCredentials({ + required String name, + String? mnemonic, + WalletInfo? walletInfo, + String? password, + String? passphrase, + }) => + EVMChainNewWalletCredentials( + name: name, + walletInfo: walletInfo, + password: password, + mnemonic: mnemonic, + passphrase: passphrase, + ); + + @override + WalletCredentials createBaseRestoreWalletFromSeedCredentials({ + required String name, + required String mnemonic, + required String password, + String? passphrase, + }) => + EVMChainRestoreWalletFromSeedCredentials( + name: name, + password: password, + mnemonic: mnemonic, + passphrase: passphrase, + ); + + @override + WalletCredentials createBaseRestoreWalletFromPrivateKey({ + required String name, + required String privateKey, + required String password, + }) => + EVMChainRestoreWalletFromPrivateKey(name: name, password: password, privateKey: privateKey); + + @override + WalletCredentials createBaseHardwareWalletCredentials({ + required String name, + required HardwareAccountData hwAccountData, + WalletInfo? walletInfo, + }) => + EVMChainRestoreWalletFromHardware( + name: name, + hwAccountData: hwAccountData, + walletInfo: walletInfo, + ); + + @override + String getAddress(WalletBase wallet) => (wallet as BaseWallet).walletAddresses.address; + + @override + String getPrivateKey(WalletBase wallet) { + final privateKeyHolder = (wallet as BaseWallet).evmChainPrivateKey; + if (privateKeyHolder is EthPrivateKey) return bytesToHex(privateKeyHolder.privateKey); + return ""; + } + + @override + String getPublicKey(WalletBase wallet) { + final privateKeyInUnitInt = (wallet as BaseWallet).evmChainPrivateKey; + return privateKeyInUnitInt.address.hex; + } + + @override + TransactionPriority getDefaultTransactionPriority() => EVMChainTransactionPriority.medium; + + @override + TransactionPriority getBaseTransactionPrioritySlow() => EVMChainTransactionPriority.slow; + + @override + List getTransactionPriorities() => EVMChainTransactionPriority.all; + + @override + TransactionPriority deserializeBaseTransactionPriority(int raw) => + EVMChainTransactionPriority.deserialize(raw: raw); + + Object createBaseTransactionCredentials( + List outputs, { + required TransactionPriority priority, + required CryptoCurrency currency, + int? feeRate, + }) => + EVMChainTransactionCredentials( + outputs + .map( + (out) => OutputInfo( + fiatAmount: out.fiatAmount, + cryptoAmount: out.cryptoAmount, + address: out.address, + note: out.note, + sendAll: out.sendAll, + extractedAddress: out.extractedAddress, + isParsedAddress: out.isParsedAddress, + formattedCryptoAmount: out.formattedCryptoAmount, + ), + ) + .toList(), + priority: priority as EVMChainTransactionPriority, + currency: currency, + feeRate: feeRate, + ); + + Object createBaseTransactionCredentialsRaw( + List outputs, { + TransactionPriority? priority, + required CryptoCurrency currency, + required int feeRate, + }) => + EVMChainTransactionCredentials( + outputs, + priority: priority as EVMChainTransactionPriority?, + currency: currency, + feeRate: feeRate, + ); + + @override + int formatterBaseParseAmount(String amount) => EVMChainFormatter.parseEVMChainAmount(amount); + + @override + double formatterBaseAmountToDouble({ + TransactionInfo? transaction, + BigInt? amount, + int exponent = 18, + }) { + assert(transaction != null || amount != null); + + if (transaction != null) { + transaction as EVMChainTransactionInfo; + return transaction.ethAmount / BigInt.from(10).pow(transaction.exponent); + } else { + return (amount!) / BigInt.from(10).pow(exponent); + } + } + + @override + List getERC20Currencies(WalletBase wallet) => (wallet as BaseWallet).erc20Currencies; + + @override + Future addErc20Token(WalletBase wallet, CryptoCurrency token) => + (wallet as BaseWallet).addErc20Token(token as Erc20Token); + + @override + Future deleteErc20Token(WalletBase wallet, CryptoCurrency token) => + (wallet as BaseWallet).deleteErc20Token(token as Erc20Token); + + @override + Future removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token) => + (wallet as BaseWallet).removeTokenTransactionsInHistory(token as Erc20Token); + + @override + Future getErc20Token(WalletBase wallet, String contractAddress) => + (wallet as BaseWallet).getErc20Token(contractAddress, 'base'); + + @override + CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) { + transaction as EVMChainTransactionInfo; + if (transaction.tokenSymbol == CryptoCurrency.baseEth.title || + transaction.tokenSymbol == "BASE") { + return CryptoCurrency.baseEth; + } + + wallet as BaseWallet; + + return wallet.erc20Currencies.firstWhere( + (element) => + transaction.contractAddress?.toLowerCase() == element.contractAddress?.toLowerCase(), + ); + } + + @override + void updateBaseScanUsageState(WalletBase wallet, bool isEnabled) => + (wallet as BaseWallet).updateScanProviderUsageState(isEnabled); + + @override + Web3Client? getWeb3Client(WalletBase wallet) => (wallet as BaseWallet).getWeb3Client(); + + @override + String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress; + + @override + Future createTokenApproval( + WalletBase wallet, + BigInt amount, + String spender, + CryptoCurrency token, + TransactionPriority priority, + ) => + (wallet as EVMChainWallet).createApprovalTransaction( + amount, + spender, + token, + priority as EVMChainTransactionPriority, + "BASE", + ); + + @override + void setHardwareWalletService(WalletBase wallet, HardwareWalletService service) { + if (service is EVMChainLedgerService) { + ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedgerConnection( + service.ledgerConnection, wallet.walletInfo.derivationInfo?.derivationPath); + } else if (service is EVMChainBitboxService) { + ((wallet as EVMChainWallet).evmChainPrivateKey as EvmBitboxCredentials) + .setBitbox(service.manager, wallet.walletInfo.derivationInfo?.derivationPath); + } + } + + @override + HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection) => + EVMChainLedgerService(connection); + + @override + HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager) => + EVMChainBitboxService(manager, chainId: 8453); + + @override + List getDefaultTokenContractAddresses() => + DefaultBaseErc20Tokens().initialBaseErc20Tokens.map((e) => e.contractAddress).toList(); + + @override + bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress) { + final baseWallet = wallet as BaseWallet; + return baseWallet.erc20Currencies.any( + (element) => element.contractAddress.toLowerCase() == contractAddress.toLowerCase(), + ); + } +} diff --git a/lib/buy/dfx/dfx_buy_provider.dart b/lib/buy/dfx/dfx_buy_provider.dart index c88ea37c9..c590d82b3 100644 --- a/lib/buy/dfx/dfx_buy_provider.dart +++ b/lib/buy/dfx/dfx_buy_provider.dart @@ -135,6 +135,7 @@ class DFXBuyProvider extends BuyProvider { switch (wallet.type) { case WalletType.ethereum: case WalletType.polygon: + case WalletType.base: case WalletType.solana: case WalletType.tron: return wallet.signMessage(message); diff --git a/lib/buy/kryptonim/kryptonim.dart b/lib/buy/kryptonim/kryptonim.dart index d79638119..cbe684686 100644 --- a/lib/buy/kryptonim/kryptonim.dart +++ b/lib/buy/kryptonim/kryptonim.dart @@ -218,6 +218,7 @@ class KryptonimBuyProvider extends BuyProvider { String _normalizeBlockChain(CryptoCurrency cur) { String? blockchain = switch (cur.tag) { 'ETH' => 'Ethereum', + 'BASE' => 'Base', 'POL' => 'Polygon', 'AVAXC' => 'Avalanche', 'SOL' => 'Solana', @@ -229,6 +230,7 @@ class KryptonimBuyProvider extends BuyProvider { CryptoCurrency.btc => 'Bitcoin', CryptoCurrency.ltc => 'Litecoin', CryptoCurrency.eth => 'Ethereum', + CryptoCurrency.baseEth => 'Base', CryptoCurrency.maticpoly => 'Matic', _ => null, }; diff --git a/lib/buy/moonpay/moonpay_provider.dart b/lib/buy/moonpay/moonpay_provider.dart index 8e3304446..228f0dc5f 100644 --- a/lib/buy/moonpay/moonpay_provider.dart +++ b/lib/buy/moonpay/moonpay_provider.dart @@ -17,6 +17,7 @@ import 'package:cake_wallet/palette.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/themes/core/material_base_theme.dart'; +import 'package:cw_core/currency_for_wallet_type.dart'; import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/wallet_base.dart'; diff --git a/lib/buy/robinhood/robinhood_buy_provider.dart b/lib/buy/robinhood/robinhood_buy_provider.dart index 5ae7832a2..ee529d3db 100644 --- a/lib/buy/robinhood/robinhood_buy_provider.dart +++ b/lib/buy/robinhood/robinhood_buy_provider.dart @@ -105,6 +105,7 @@ class RobinhoodBuyProvider extends BuyProvider { switch (wallet.type) { case WalletType.ethereum: case WalletType.polygon: + case WalletType.base: case WalletType.solana: case WalletType.tron: case WalletType.dogecoin: diff --git a/lib/buy/wyre/wyre_buy_provider.dart b/lib/buy/wyre/wyre_buy_provider.dart index a0cf1ed7c..a08aff170 100644 --- a/lib/buy/wyre/wyre_buy_provider.dart +++ b/lib/buy/wyre/wyre_buy_provider.dart @@ -4,6 +4,7 @@ import 'package:cake_wallet/order/order_source_description.dart'; import 'package:cake_wallet/buy/pairs_utils.dart'; import 'package:cake_wallet/entities/fiat_currency.dart'; import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/currency_for_wallet_type.dart'; import 'package:cw_core/utils/proxy_wrapper.dart'; import 'package:cake_wallet/buy/buy_amount.dart'; import 'package:cake_wallet/buy/buy_provider.dart'; diff --git a/lib/core/payment_uris.dart b/lib/core/payment_uris.dart index 8aa2616cf..be4e79007 100644 --- a/lib/core/payment_uris.dart +++ b/lib/core/payment_uris.dart @@ -86,6 +86,21 @@ class EthereumURI extends PaymentURI { } } +class BaseURI extends PaymentURI { + BaseURI({required super.amount, required super.address}); + + @override + String toString() { + var base = 'base:$address'; + + if (amount.isNotEmpty) { + base += '?amount=${amount.replaceAll(',', '.')}'; + } + + return base; + } +} + class BitcoinCashURI extends PaymentURI { BitcoinCashURI({required super.amount, required super.address}); diff --git a/lib/core/seed_validator.dart b/lib/core/seed_validator.dart index 2e35e7f5a..2643fd0d7 100644 --- a/lib/core/seed_validator.dart +++ b/lib/core/seed_validator.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/base/base.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/core/validator.dart'; import 'package:cake_wallet/entities/mnemonic_item.dart'; @@ -52,6 +53,8 @@ class SeedValidator extends Validator { return zano!.getWordList(language); case WalletType.decred: return decred!.getDecredWordList(); + case WalletType.base: + return base!.getBaseWordList(language); case WalletType.none: case WalletType.haven: return []; diff --git a/lib/core/wallet_creation_service.dart b/lib/core/wallet_creation_service.dart index 2cf7a7c63..254f8a16f 100644 --- a/lib/core/wallet_creation_service.dart +++ b/lib/core/wallet_creation_service.dart @@ -80,6 +80,7 @@ class WalletCreationService { case WalletType.bitcoinCash: case WalletType.ethereum: case WalletType.polygon: + case WalletType.base: case WalletType.solana: case WalletType.tron: case WalletType.dogecoin: diff --git a/lib/di.dart b/lib/di.dart index 01312dd6f..8f0a712af 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -5,6 +5,7 @@ import 'package:cake_wallet/anonpay/anonpay_api.dart'; import 'package:cake_wallet/anonpay/anonpay_info_base.dart'; import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart'; import 'package:cake_wallet/anypay/anypay_api.dart'; +import 'package:cake_wallet/base/base.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/buy/dfx/dfx_buy_provider.dart'; @@ -1241,6 +1242,8 @@ Future setup({ return zano!.createZanoWalletService(_walletInfoSource); case WalletType.decred: return decred!.createDecredWalletService(_walletInfoSource, _unspentCoinsInfoSource); + case WalletType.base: + return base!.createBaseWalletService(_walletInfoSource, SettingsStoreBase.walletPasswordDirectInput); case WalletType.haven: return HavenWalletService(_walletInfoSource); case WalletType.none: diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index 6077ade30..c25891eeb 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -47,6 +47,7 @@ const zanoDefaultNodeUri = '37.27.100.59:10500'; const moneroWorldNodeUri = '.moneroworld.com'; const decredDefaultUri = "default-spv-nodes"; const dogecoinDefaultNodeUri = 'dogecoin.stackwallet.com:50022'; +const baseDefaultNodeUri = 'base.nownodes.io'; Future defaultSettingsMigration( {required int version, @@ -534,6 +535,15 @@ Future defaultSettingsMigration( currentNodePreferenceKey: PreferencesKey.currentDogecoinNodeIdKey, ); break; + case 52: + await addWalletNodeList(nodes: nodes, type: WalletType.base); + await _changeDefaultNode( + nodes: nodes, + sharedPreferences: sharedPreferences, + type: WalletType.base, + currentNodePreferenceKey: PreferencesKey.currentBaseNodeIdKey, + ); + break; default: break; } @@ -642,6 +652,8 @@ String _getDefaultNodeUri(WalletType type) { return decredDefaultUri; case WalletType.dogecoin: return dogecoinDefaultNodeUri; + case WalletType.base: + return baseDefaultNodeUri; case WalletType.banano: case WalletType.none: return ''; @@ -1041,6 +1053,7 @@ Future checkCurrentNodes( final currentHavenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey); final currentEthereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey); final currentPolygonNodeId = sharedPreferences.getInt(PreferencesKey.currentPolygonNodeIdKey); + final currentBaseNodeId = sharedPreferences.getInt(PreferencesKey.currentBaseNodeIdKey); final currentNanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey); final currentNanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey); final currentDecredNodeId = sharedPreferences.getInt(PreferencesKey.currentDecredNodeIdKey); @@ -1064,6 +1077,8 @@ Future checkCurrentNodes( nodeSource.values.firstWhereOrNull((node) => node.key == currentEthereumNodeId); final currentPolygonNodeServer = nodeSource.values.firstWhereOrNull((node) => node.key == currentPolygonNodeId); + final currentBaseNodeServer = + nodeSource.values.firstWhereOrNull((node) => node.key == currentBaseNodeId); final currentNanoNodeServer = nodeSource.values.firstWhereOrNull((node) => node.key == currentNanoNodeId); final currentDecredNodeServer = @@ -1155,6 +1170,12 @@ Future checkCurrentNodes( await sharedPreferences.setInt(PreferencesKey.currentPolygonNodeIdKey, node.key as int); } + if (currentBaseNodeServer == null) { + final node = Node(uri: baseDefaultNodeUri, type: WalletType.base); + await nodeSource.add(node); + await sharedPreferences.setInt(PreferencesKey.currentBaseNodeIdKey, node.key as int); + } + if (currentSolanaNodeServer == null) { final node = Node(uri: solanaDefaultNodeUri, type: WalletType.solana); await nodeSource.add(node); diff --git a/lib/entities/ens_record.dart b/lib/entities/ens_record.dart index 78b0f4178..b49afc0b3 100644 --- a/lib/entities/ens_record.dart +++ b/lib/entities/ens_record.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/base/base.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cw_core/utils/proxy_wrapper.dart'; @@ -21,6 +22,10 @@ class EnsRecord { _client = polygon!.getWeb3Client(wallet); } + if (wallet != null && wallet.type == WalletType.base) { + _client = base!.getWeb3Client(wallet); + } + if (_client == null) { late final client = ProxyWrapper().getHttpIOClient(); @@ -42,6 +47,7 @@ class EnsRecord { return await ens.withName(name).getCoinAddress(CoinType.XHV); case WalletType.ethereum: case WalletType.polygon: + case WalletType.base: default: return (await ens.withName(name).getAddress()).hex; } diff --git a/lib/entities/evm_transaction_error_fees_handler.dart b/lib/entities/evm_transaction_error_fees_handler.dart index b802f9883..85b40be65 100644 --- a/lib/entities/evm_transaction_error_fees_handler.dart +++ b/lib/entities/evm_transaction_error_fees_handler.dart @@ -32,6 +32,50 @@ class EVMTransactionErrorFeesHandler { String errorMessage, double assetPriceUsd, ) { + // Pattern: "insufficient funds for gas * price + value: have 4728796358953246 want 4728796842182575" + RegExp insufficientFundsRegExp = RegExp(r'have (\d+) want (\d+)'); + Match? insufficientFundsMatch = insufficientFundsRegExp.firstMatch(errorMessage); + + if (insufficientFundsMatch != null) { + try { + // Extract the numerical strings from the new format + String balanceStr = insufficientFundsMatch.group(1)!; + String requiredStr = insufficientFundsMatch.group(2)!; + + // Parse the numerical strings to BigInt + BigInt balanceWei = BigInt.parse(balanceStr); + BigInt requiredWei = BigInt.parse(requiredStr); + + // Calculate overshot (how much more is needed) + BigInt overshotWei = requiredWei - balanceWei; + + // The transaction cost is the required amount + BigInt txCostWei = requiredWei; + + // Convert wei to ETH (1 ETH = 1e18 wei) + double balanceEth = balanceWei.toDouble() / 1e18; + double txCostEth = txCostWei.toDouble() / 1e18; + double overshotEth = overshotWei.toDouble() / 1e18; + + // Calculate the USD values + double balanceUsd = balanceEth * assetPriceUsd; + double txCostUsd = txCostEth * assetPriceUsd; + double overshotUsd = overshotEth * assetPriceUsd; + + return EVMTransactionErrorFeesHandler( + balanceWei: balanceWei.toString(), + balanceEth: balanceEth.toString().safeSubString(0, 12), + balanceUsd: balanceUsd.toString().safeSubString(0, 4), + txCostWei: txCostWei.toString(), + txCostEth: txCostEth.toString().safeSubString(0, 12), + txCostUsd: txCostUsd.toString().safeSubString(0, 4), + overshotWei: overshotWei.toString(), + overshotEth: overshotEth.toString().safeSubString(0, 12), + overshotUsd: overshotUsd.toString().safeSubString(0, 4), + ); + } catch (e) {} + } + // Define Regular Expressions to extract the numerical values RegExp balanceRegExp = RegExp(r'balance (\d+)'); RegExp txCostRegExp = RegExp(r'tx cost (\d+)'); @@ -44,40 +88,44 @@ class EVMTransactionErrorFeesHandler { // Check if all required values are found if (balanceMatch != null && txCostMatch != null && overshotMatch != null) { - // Extract the numerical strings - String balanceStr = balanceMatch.group(1)!; - String txCostStr = txCostMatch.group(1)!; - String overshotStr = overshotMatch.group(1)!; + try { + // Extract the numerical strings + String balanceStr = balanceMatch.group(1)!; + String txCostStr = txCostMatch.group(1)!; + String overshotStr = overshotMatch.group(1)!; - // Parse the numerical strings to BigInt - BigInt balanceWei = BigInt.parse(balanceStr); - BigInt txCostWei = BigInt.parse(txCostStr); - BigInt overshotWei = BigInt.parse(overshotStr); + // Parse the numerical strings to BigInt + BigInt balanceWei = BigInt.parse(balanceStr); + BigInt txCostWei = BigInt.parse(txCostStr); + BigInt overshotWei = BigInt.parse(overshotStr); - // Convert wei to ETH (1 ETH = 1e18 wei) - double balanceEth = balanceWei.toDouble() / 1e18; - double txCostEth = txCostWei.toDouble() / 1e18; - double overshotEth = overshotWei.toDouble() / 1e18; + // Convert wei to ETH (1 ETH = 1e18 wei) + double balanceEth = balanceWei.toDouble() / 1e18; + double txCostEth = txCostWei.toDouble() / 1e18; + double overshotEth = overshotWei.toDouble() / 1e18; - // Calculate the USD values - double balanceUsd = balanceEth * assetPriceUsd; - double txCostUsd = txCostEth * assetPriceUsd; - double overshotUsd = overshotEth * assetPriceUsd; + // Calculate the USD values + double balanceUsd = balanceEth * assetPriceUsd; + double txCostUsd = txCostEth * assetPriceUsd; + double overshotUsd = overshotEth * assetPriceUsd; - return EVMTransactionErrorFeesHandler( - balanceWei: balanceWei.toString(), - balanceEth: balanceEth.toString().safeSubString(0, 12), - balanceUsd: balanceUsd.toString().safeSubString(0, 4), - txCostWei: txCostWei.toString(), - txCostEth: txCostEth.toString().safeSubString(0, 12), - txCostUsd: txCostUsd.toString().safeSubString(0, 4), - overshotWei: overshotWei.toString(), - overshotEth: overshotEth.toString().safeSubString(0, 12), - overshotUsd: overshotUsd.toString().safeSubString(0, 4), - ); - } else { - // If any value is missing, return an error message - return EVMTransactionErrorFeesHandler(error: 'Could not parse the error message.'); + return EVMTransactionErrorFeesHandler( + balanceWei: balanceWei.toString(), + balanceEth: balanceEth.toString().safeSubString(0, 12), + balanceUsd: balanceUsd.toString().safeSubString(0, 4), + txCostWei: txCostWei.toString(), + txCostEth: txCostEth.toString().safeSubString(0, 12), + txCostUsd: txCostUsd.toString().safeSubString(0, 4), + overshotWei: overshotWei.toString(), + overshotEth: overshotEth.toString().safeSubString(0, 12), + overshotUsd: overshotUsd.toString().safeSubString(0, 4), + ); + } catch (e) { + // If parsing fails, continue to error case + } } + + // If both parsing attempts fail, return an error message + return EVMTransactionErrorFeesHandler(error: 'Could not parse the error message.'); } } diff --git a/lib/entities/node_list.dart b/lib/entities/node_list.dart index 40843becf..90bb9ddcb 100644 --- a/lib/entities/node_list.dart +++ b/lib/entities/node_list.dart @@ -49,6 +49,9 @@ Future> loadDefaultNodes(WalletType type) async { case WalletType.dogecoin: path = 'assets/dogecoin_electrum_server_list.yml'; break; + case WalletType.base: + path = 'assets/base_node_list.yml'; + break; case WalletType.banano: case WalletType.none: path = ''; @@ -100,6 +103,7 @@ Future resetToDefault(Box nodeSource) async { final decredNodes = await loadDefaultNodes(WalletType.decred); final zanoNodes = await loadDefaultNodes(WalletType.zano); final dogecoinElectrumServerList = await loadDefaultNodes(WalletType.dogecoin); + final baseNodes = await loadDefaultNodes(WalletType.base); final nodes = moneroNodes + bitcoinElectrumServerList + @@ -113,7 +117,8 @@ Future resetToDefault(Box nodeSource) async { tronNodes + zanoNodes + decredNodes + - dogecoinElectrumServerList; + dogecoinElectrumServerList + + baseNodes; await nodeSource.clear(); await nodeSource.addAll(nodes); diff --git a/lib/entities/parse_address_from_domain.dart b/lib/entities/parse_address_from_domain.dart index b2621a688..cbcf62dad 100644 --- a/lib/entities/parse_address_from_domain.dart +++ b/lib/entities/parse_address_from_domain.dart @@ -114,6 +114,7 @@ class AddressResolver { "podcast", "pog", "polygon", + "base", "press", "privacy", "pro", diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index b607ce7ed..eb678a07a 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -8,6 +8,7 @@ class PreferencesKey { static const currentZanoNodeIdKey = 'current_node_id_zano'; static const currentEthereumNodeIdKey = 'current_node_id_eth'; static const currentPolygonNodeIdKey = 'current_node_id_matic'; + static const currentBaseNodeIdKey = 'current_node_id_base'; static const currentNanoNodeIdKey = 'current_node_id_nano'; static const currentNanoPowNodeIdKey = 'current_node_id_nano_pow'; static const currentDecredNodeIdKey = 'current_node_id_decred'; @@ -52,6 +53,7 @@ class PreferencesKey { static const litecoinTransactionPriority = 'current_fee_priority_litecoin'; static const ethereumTransactionPriority = 'current_fee_priority_ethereum'; static const polygonTransactionPriority = 'current_fee_priority_polygon'; + static const baseTransactionPriority = 'current_fee_priority_base'; static const bitcoinCashTransactionPriority = 'current_fee_priority_bitcoin_cash'; static const zanoTransactionPriority = 'current_fee_priority_zano'; static const wowneroTransactionPriority = 'current_fee_priority_wownero'; @@ -77,6 +79,7 @@ class PreferencesKey { static const pinNativeTokenAtTop = 'pin_native_token_at_top'; static const useEtherscan = 'use_etherscan'; static const usePolygonScan = 'use_polygonscan'; + static const useBaseScan = 'use_base_scan'; static const useTronGrid = 'use_trongrid'; static const useMempoolFeeAPI = 'use_mempool_fee_api'; static const defaultNanoRep = 'default_nano_representative'; diff --git a/lib/entities/priority_for_wallet_type.dart b/lib/entities/priority_for_wallet_type.dart index c85e98597..37ad2ab7e 100644 --- a/lib/entities/priority_for_wallet_type.dart +++ b/lib/entities/priority_for_wallet_type.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/base/base.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/dogecoin/dogecoin.dart'; @@ -28,6 +29,8 @@ List priorityForWalletType(WalletType type) { return dogecoin!.getTransactionPriorities(); case WalletType.polygon: return polygon!.getTransactionPriorities(); + case WalletType.base: + return base!.getTransactionPriorities(); // no such thing for nano/banano/solana/tron: case WalletType.nano: case WalletType.banano: diff --git a/lib/reactions/fiat_rate_update.dart b/lib/reactions/fiat_rate_update.dart index dbc5f9ae4..abd8b097e 100644 --- a/lib/reactions/fiat_rate_update.dart +++ b/lib/reactions/fiat_rate_update.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'package:cake_wallet/base/base.dart'; import 'package:cake_wallet/core/fiat_conversion_service.dart'; import 'package:cake_wallet/entities/fiat_api_mode.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; @@ -44,6 +45,11 @@ Future startFiatRateUpdate( polygon!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled); } + if (appStore.wallet!.type == WalletType.base) { + currencies = + base!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled); + } + if (appStore.wallet!.type == WalletType.solana) { currencies = solana!.getSPLTokenCurrencies(appStore.wallet!).where((element) => element.enabled); diff --git a/lib/reactions/on_current_wallet_change.dart b/lib/reactions/on_current_wallet_change.dart index 53a12b10c..eed86e0a7 100644 --- a/lib/reactions/on_current_wallet_change.dart +++ b/lib/reactions/on_current_wallet_change.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/base/base.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart'; @@ -130,6 +131,10 @@ void startCurrentWalletChangeReaction( currencies = polygon!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled); } + if (wallet.type == WalletType.base) { + currencies = + base!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled); + } if (wallet.type == WalletType.solana) { currencies = solana!.getSPLTokenCurrencies(appStore.wallet!).where((element) => element.enabled); diff --git a/lib/reactions/wallet_connect.dart b/lib/reactions/wallet_connect.dart index 37a0b92ec..7e969f344 100644 --- a/lib/reactions/wallet_connect.dart +++ b/lib/reactions/wallet_connect.dart @@ -8,6 +8,7 @@ bool isEVMCompatibleChain(WalletType walletType) { switch (walletType) { case WalletType.polygon: case WalletType.ethereum: + case WalletType.base: return true; default: return false; @@ -18,6 +19,7 @@ bool isNFTACtivatedChain(WalletType walletType) { switch (walletType) { case WalletType.polygon: case WalletType.ethereum: + case WalletType.base: case WalletType.solana: return true; default: @@ -29,6 +31,7 @@ bool isWalletConnectCompatibleChain(WalletType walletType) { switch (walletType) { case WalletType.polygon: case WalletType.ethereum: + case WalletType.base: case WalletType.solana: return true; default: @@ -42,6 +45,8 @@ String getChainNameSpaceAndIdBasedOnWalletType(WalletType walletType) { return EVMChainId.ethereum.chain(); case WalletType.polygon: return EVMChainId.polygon.chain(); + case WalletType.base: + return EVMChainId.base.chain(); case WalletType.solana: return SolanaChainId.mainnet.chain(); default: @@ -52,8 +57,8 @@ String getChainNameSpaceAndIdBasedOnWalletType(WalletType walletType) { List getChainSupportedMethodsOnWalletType(WalletType walletType) { switch (walletType) { case WalletType.ethereum: - return EVMSupportedMethods.values.map((e) => e.name).toList(); case WalletType.polygon: + case WalletType.base: return EVMSupportedMethods.values.map((e) => e.name).toList(); case WalletType.solana: return SolanaSupportedMethods.values.map((e) => e.name).toList(); @@ -66,7 +71,8 @@ int getChainIdBasedOnWalletType(WalletType walletType) { switch (walletType) { case WalletType.polygon: return 137; - + case WalletType.base: + return 8453; // For now, we return eth chain Id as the default, we'll modify as we add more wallets case WalletType.ethereum: default: @@ -80,6 +86,8 @@ String getChainNameBasedOnWalletType(WalletType walletType) { return 'eth'; case WalletType.polygon: return 'polygon'; + case WalletType.base: + return 'base'; case WalletType.solana: return 'mainnet'; default: @@ -93,6 +101,8 @@ String getTokenNameBasedOnWalletType(WalletType walletType) { return 'ETH'; case WalletType.polygon: return 'MATIC'; + case WalletType.base: + return 'BASE'; case WalletType.solana: return 'SOL'; default: diff --git a/lib/reactions/wallet_utils.dart b/lib/reactions/wallet_utils.dart index d5ff7865f..8250dfd5b 100644 --- a/lib/reactions/wallet_utils.dart +++ b/lib/reactions/wallet_utils.dart @@ -4,6 +4,7 @@ bool isBIP39Wallet(WalletType walletType) { switch (walletType) { case WalletType.ethereum: case WalletType.polygon: + case WalletType.base: case WalletType.solana: case WalletType.tron: case WalletType.bitcoin: @@ -41,6 +42,7 @@ bool hasTokens(WalletType walletType) { case WalletType.solana: case WalletType.tron: case WalletType.zano: + case WalletType.base: return true; default: return false; diff --git a/lib/src/screens/connect_device/select_hardware_wallet_account_page.dart b/lib/src/screens/connect_device/select_hardware_wallet_account_page.dart index d4c40d953..691987a68 100644 --- a/lib/src/screens/connect_device/select_hardware_wallet_account_page.dart +++ b/lib/src/screens/connect_device/select_hardware_wallet_account_page.dart @@ -10,6 +10,7 @@ import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/wallet_hardware_restore_view_model.dart'; +import 'package:cw_core/currency_for_wallet_type.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; @@ -148,7 +149,7 @@ class _SelectHardwareWalletAccountFormState extends State { this.bananoIcon = Image.asset('assets/images/crypto/nano.png'), this.bitcoinCashIcon = Image.asset('assets/images/crypto/bitcoin-cash.png'), this.polygonIcon = Image.asset('assets/images/crypto/polygon.png'), + this.baseIcon = Image.asset('assets/images/base_icon.png'), this.solanaIcon = Image.asset('assets/images/crypto/solana.png'), this.tronIcon = Image.asset('assets/images/crypto/tron.png'), this.wowneroIcon = Image.asset('assets/images/crypto/wownero.png'), @@ -60,6 +61,7 @@ class MenuWidgetState extends State { Image nanoIcon; Image bananoIcon; Image polygonIcon; + Image baseIcon; Image solanaIcon; Image tronIcon; Image wowneroIcon; @@ -247,6 +249,8 @@ class MenuWidgetState extends State { return polygonIcon; case WalletType.solana: return solanaIcon; + case WalletType.base: + return baseIcon; case WalletType.tron: return tronIcon; case WalletType.wownero: diff --git a/lib/src/screens/dashboard/widgets/sign_form.dart b/lib/src/screens/dashboard/widgets/sign_form.dart index 9fe661cda..7f9f1b8d9 100644 --- a/lib/src/screens/dashboard/widgets/sign_form.dart +++ b/lib/src/screens/dashboard/widgets/sign_form.dart @@ -2,6 +2,7 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/widgets/address_text_field.dart'; import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; import 'package:cake_wallet/utils/show_bar.dart'; +import 'package:cw_core/currency_for_wallet_type.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; diff --git a/lib/src/screens/dashboard/widgets/verify_form.dart b/lib/src/screens/dashboard/widgets/verify_form.dart index 97e94015c..318aca9b0 100644 --- a/lib/src/screens/dashboard/widgets/verify_form.dart +++ b/lib/src/screens/dashboard/widgets/verify_form.dart @@ -1,5 +1,6 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/widgets/address_text_field.dart'; +import 'package:cw_core/currency_for_wallet_type.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; diff --git a/lib/src/screens/new_wallet/new_wallet_type_page.dart b/lib/src/screens/new_wallet/new_wallet_type_page.dart index 26ae913a7..5aab45d89 100644 --- a/lib/src/screens/new_wallet/new_wallet_type_page.dart +++ b/lib/src/screens/new_wallet/new_wallet_type_page.dart @@ -15,6 +15,7 @@ import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/new_wallet_type_view_model.dart'; import 'package:cake_wallet/wallet_types.g.dart'; +import 'package:cw_core/currency_for_wallet_type.dart'; import 'package:cw_core/hardware/device_connection_type.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; @@ -142,9 +143,9 @@ class WalletTypeFormState extends State { child: SelectButton( key: ValueKey('new_wallet_type_${type.name}_button_key'), image: Image.asset( - walletTypeToCryptoCurrency(type).iconPath ?? '', - height: 28, - width: 28, + walletTypeToCryptoCurrency(type).normalizedIconPath ?? '', + height: 24, + width: 24, ), text: walletTypeToDisplayName(type), showTrailingIcon: false, diff --git a/lib/src/screens/new_wallet/wallet_group_display_page.dart b/lib/src/screens/new_wallet/wallet_group_display_page.dart index b3edc132f..7c68bb183 100644 --- a/lib/src/screens/new_wallet/wallet_group_display_page.dart +++ b/lib/src/screens/new_wallet/wallet_group_display_page.dart @@ -7,7 +7,7 @@ import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/themes/core/material_base_theme.dart'; import 'package:cake_wallet/view_model/wallet_groups_display_view_model.dart'; -import 'package:cw_core/wallet_type.dart'; +import 'package:cw_core/currency_for_wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; @@ -95,7 +95,7 @@ class WalletGroupsDisplayBody extends StatelessWidget { isSelected: walletGroupsDisplayViewModel.selectedSingleWallet == wallet, leadingWidget: Image.asset( - walletTypeToCryptoCurrency(wallet.type).iconPath!, + walletTypeToCryptoCurrency(wallet.type).normalizedIconPath!, width: 32, height: 32, ), diff --git a/lib/src/screens/new_wallet/widgets/grouped_wallet_expansion_tile.dart b/lib/src/screens/new_wallet/widgets/grouped_wallet_expansion_tile.dart index c028dc621..10fd423b1 100644 --- a/lib/src/screens/new_wallet/widgets/grouped_wallet_expansion_tile.dart +++ b/lib/src/screens/new_wallet/widgets/grouped_wallet_expansion_tile.dart @@ -1,4 +1,4 @@ -import 'package:cw_core/wallet_type.dart'; +import 'package:cw_core/currency_for_wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart'; @@ -99,7 +99,6 @@ class GroupedWalletExpansionTile extends StatelessWidget { final currentColor = item.isCurrent ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.surface; - final walletTypeToCrypto = walletTypeToCryptoCurrency(item.type); return ListTile( contentPadding: EdgeInsets.zero, key: ValueKey(item.name), @@ -124,7 +123,7 @@ class GroupedWalletExpansionTile extends StatelessWidget { : SizedBox(width: 7), SizedBox(width: 24), Image.asset( - walletTypeToCrypto.iconPath!, + walletTypeToCryptoCurrency(item.type).normalizedIconPath!, width: 32, height: 32, ), diff --git a/lib/src/screens/settings/privacy_page.dart b/lib/src/screens/settings/privacy_page.dart index 792891453..e753efc13 100644 --- a/lib/src/screens/settings/privacy_page.dart +++ b/lib/src/screens/settings/privacy_page.dart @@ -118,6 +118,14 @@ class PrivacyPage extends BasePage { _privacySettingsViewModel.setUsePolygonScan(value); }, ), + if (_privacySettingsViewModel.canUseBaseScan) + SettingsSwitcherCell( + title: S.current.basescan_history, + value: _privacySettingsViewModel.useBaseScan, + onValueChange: (BuildContext _, bool value) { + _privacySettingsViewModel.setUseBaseScan(value); + }, + ), if (_privacySettingsViewModel.canUseTronGrid) SettingsSwitcherCell( title: S.current.trongrid_history, diff --git a/lib/src/screens/wallet_connect/services/chain_service/eth/evm_chain_id.dart b/lib/src/screens/wallet_connect/services/chain_service/eth/evm_chain_id.dart index 6ecd09d35..dd1c7a608 100644 --- a/lib/src/screens/wallet_connect/services/chain_service/eth/evm_chain_id.dart +++ b/lib/src/screens/wallet_connect/services/chain_service/eth/evm_chain_id.dart @@ -1,6 +1,7 @@ enum EVMChainId { ethereum, polygon, + base, goerli, mumbai, arbitrum, @@ -17,6 +18,9 @@ extension EVMChainIdX on EVMChainId { case EVMChainId.polygon: name = '137'; break; + case EVMChainId.base: + name = '8453'; + break; case EVMChainId.goerli: name = '5'; break; diff --git a/lib/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart b/lib/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart index a7f40455c..7e489288e 100644 --- a/lib/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart +++ b/lib/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/base/base.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/reactions/wallet_connect.dart'; @@ -23,6 +24,8 @@ class KeyServiceImpl implements WalletConnectKeyService { return ethereum!.getPrivateKey(wallet); case WalletType.polygon: return polygon!.getPrivateKey(wallet); + case WalletType.base: + return base!.getPrivateKey(wallet); case WalletType.solana: return solana!.getPrivateKey(wallet); default: @@ -36,6 +39,8 @@ class KeyServiceImpl implements WalletConnectKeyService { return ethereum!.getPublicKey(wallet); case WalletType.polygon: return polygon!.getPublicKey(wallet); + case WalletType.base: + return base!.getPublicKey(wallet); case WalletType.solana: return solana!.getPublicKey(wallet); default: @@ -51,6 +56,7 @@ class KeyServiceImpl implements WalletConnectKeyService { 'eip155:1', 'eip155:5', 'eip155:137', + 'eip155:8453', 'eip155:42161', 'eip155:80001', ], diff --git a/lib/src/screens/wallet_list/wallet_list_page.dart b/lib/src/screens/wallet_list/wallet_list_page.dart index 2dcaa8d38..42000a666 100644 --- a/lib/src/screens/wallet_list/wallet_list_page.dart +++ b/lib/src/screens/wallet_list/wallet_list_page.dart @@ -25,6 +25,7 @@ import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart'; import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart'; import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart'; import 'package:cake_wallet/wallet_type_utils.dart'; +import 'package:cw_core/currency_for_wallet_type.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; diff --git a/lib/src/widgets/bottom_sheet/payment_confirmation_bottom_sheet.dart b/lib/src/widgets/bottom_sheet/payment_confirmation_bottom_sheet.dart index afe2e404b..56f6a5793 100644 --- a/lib/src/widgets/bottom_sheet/payment_confirmation_bottom_sheet.dart +++ b/lib/src/widgets/bottom_sheet/payment_confirmation_bottom_sheet.dart @@ -1,5 +1,6 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/utils/payment_request.dart'; +import 'package:cw_core/currency_for_wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; diff --git a/lib/src/widgets/bottom_sheet/swap_confirmation_bottom_sheet.dart b/lib/src/widgets/bottom_sheet/swap_confirmation_bottom_sheet.dart index 69f4abf0b..afb1b5d20 100644 --- a/lib/src/widgets/bottom_sheet/swap_confirmation_bottom_sheet.dart +++ b/lib/src/widgets/bottom_sheet/swap_confirmation_bottom_sheet.dart @@ -9,6 +9,7 @@ import 'package:cake_wallet/src/widgets/bottom_sheet/swap_details_bottom_sheet.d import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; import 'package:cake_wallet/utils/address_formatter.dart'; import 'package:cake_wallet/utils/debounce.dart'; +import 'package:cw_core/currency_for_wallet_type.dart'; import 'package:cw_core/crypto_amount_format.dart'; import 'package:flutter/material.dart'; @@ -136,7 +137,7 @@ class SwapConfirmationContentState extends State { @override Widget build(BuildContext context) { - final detectedCurrencyName = walletTypeToCryptoCurrency(widget.paymentFlowResult.walletType!); + final detectedCurrency = walletTypeToCryptoCurrency(widget.paymentFlowResult.walletType!); return Form( key: _formKey, @@ -159,7 +160,7 @@ class SwapConfirmationContentState extends State { const SizedBox(width: 12), CakeImageWidget( imageUrl: - walletTypeToCryptoCurrency(widget.paymentFlowResult.walletType!).iconPath!, + detectedCurrency.iconPath!, width: 32, height: 32, ), @@ -168,7 +169,7 @@ class SwapConfirmationContentState extends State { const SizedBox(height: 16), SwapConfirmationTextfield( key: ValueKey('swap_confirmation_bottomsheet_amount_textfield_key'), - hintText: 'Amount (${detectedCurrencyName})', + hintText: 'Amount (${detectedCurrency})', focusNode: _amountFocus, controller: _amountController, keyboardType: TextInputType.numberWithOptions(decimal: true, signed: false), @@ -226,7 +227,7 @@ class SwapConfirmationContentState extends State { min != null ? Text( key: ValueKey('min_limit_text_key'), - S.of(context).min_value(min, detectedCurrencyName.toString()), + S.of(context).min_value(min, detectedCurrency.toString()), style: Theme.of(context).textTheme.bodySmall!.copyWith( fontSize: 10, height: 1.2, @@ -238,7 +239,7 @@ class SwapConfirmationContentState extends State { max != null ? Text( key: ValueKey('max_limit_text_key'), - S.of(context).max_value(max, detectedCurrencyName.toString()), + S.of(context).max_value(max, detectedCurrency.toString()), style: Theme.of(context).textTheme.bodySmall!.copyWith( fontSize: 10, height: 1.2, diff --git a/lib/src/widgets/bottom_sheet/wallet_switcher_bottom_sheet.dart b/lib/src/widgets/bottom_sheet/wallet_switcher_bottom_sheet.dart index e99e344c4..1b4886a27 100644 --- a/lib/src/widgets/bottom_sheet/wallet_switcher_bottom_sheet.dart +++ b/lib/src/widgets/bottom_sheet/wallet_switcher_bottom_sheet.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cw_core/currency_for_wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart'; import 'package:cake_wallet/src/widgets/standard_list.dart'; @@ -94,7 +95,7 @@ class _WalletSwitcherContent extends StatelessWidget { child: Row( children: [ Image.asset( - walletTypeToCryptoCurrency(wallet.type).iconPath!, + walletTypeToCryptoCurrency(wallet.type).normalizedIconPath!, width: 32, height: 32, ), diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 4b2b11704..6b3dbc108 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'package:cake_wallet/base/base.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/core/utilities.dart'; import 'package:cake_wallet/decred/decred.dart'; @@ -108,6 +109,7 @@ abstract class SettingsStoreBase with Store { required this.sortBalanceBy, required this.pinNativeTokenAtTop, required this.useEtherscan, + required this.useBaseScan, required this.usePolygonScan, required this.useTronGrid, required this.useMempoolFeeAPI, @@ -139,6 +141,7 @@ abstract class SettingsStoreBase with Store { TransactionPriority? initialLitecoinTransactionPriority, TransactionPriority? initialEthereumTransactionPriority, TransactionPriority? initialPolygonTransactionPriority, + TransactionPriority? initialBaseTransactionPriority, TransactionPriority? initialBitcoinCashTransactionPriority, TransactionPriority? initialZanoTransactionPriority, TransactionPriority? initialDecredTransactionPriority, @@ -224,6 +227,10 @@ abstract class SettingsStoreBase with Store { priority[WalletType.polygon] = initialPolygonTransactionPriority; } + if (initialBaseTransactionPriority != null) { + priority[WalletType.base] = initialBaseTransactionPriority; + } + if (initialBitcoinCashTransactionPriority != null) { priority[WalletType.bitcoinCash] = initialBitcoinCashTransactionPriority; } @@ -291,6 +298,9 @@ abstract class SettingsStoreBase with Store { case WalletType.polygon: key = PreferencesKey.polygonTransactionPriority; break; + case WalletType.base: + key = PreferencesKey.baseTransactionPriority; + break; case WalletType.zano: key = PreferencesKey.zanoTransactionPriority; break; @@ -453,6 +463,11 @@ abstract class SettingsStoreBase with Store { (bool usePolygonScan) => _sharedPreferences.setBool(PreferencesKey.usePolygonScan, usePolygonScan)); + reaction( + (_) => useBaseScan, + (bool useBaseScan) => + _sharedPreferences.setBool(PreferencesKey.useBaseScan, useBaseScan)); + reaction((_) => useTronGrid, (bool useTronGrid) => _sharedPreferences.setBool(PreferencesKey.useTronGrid, useTronGrid)); @@ -808,6 +823,9 @@ abstract class SettingsStoreBase with Store { @observable bool usePolygonScan; + @observable + bool useBaseScan; + @observable bool useTronGrid; @@ -949,6 +967,7 @@ abstract class SettingsStoreBase with Store { TransactionPriority? litecoinTransactionPriority; TransactionPriority? ethereumTransactionPriority; TransactionPriority? polygonTransactionPriority; + TransactionPriority? baseTransactionPriority; TransactionPriority? bitcoinCashTransactionPriority; TransactionPriority? wowneroTransactionPriority; TransactionPriority? zanoTransactionPriority; @@ -970,6 +989,10 @@ abstract class SettingsStoreBase with Store { polygonTransactionPriority = polygon?.deserializePolygonTransactionPriority( sharedPreferences.getInt(PreferencesKey.polygonTransactionPriority)!); } + if (sharedPreferences.getInt(PreferencesKey.baseTransactionPriority) != null) { + baseTransactionPriority = base?.deserializeBaseTransactionPriority( + sharedPreferences.getInt(PreferencesKey.baseTransactionPriority)!); + } if (sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority) != null) { bitcoinCashTransactionPriority = bitcoinCash?.deserializeBitcoinCashTransactionPriority( sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority)!); @@ -996,6 +1019,7 @@ abstract class SettingsStoreBase with Store { wowneroTransactionPriority ??= wownero?.getDefaultTransactionPriority(); decredTransactionPriority ??= decred?.getDecredTransactionPriorityMedium(); polygonTransactionPriority ??= polygon?.getDefaultTransactionPriority(); + baseTransactionPriority ??= base?.getDefaultTransactionPriority(); zanoTransactionPriority ??= zano?.getDefaultTransactionPriority(); final currentBalanceDisplayMode = BalanceDisplayMode.deserialize( @@ -1042,6 +1066,7 @@ abstract class SettingsStoreBase with Store { : defaultSeedPhraseLength; final useEtherscan = sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true; final usePolygonScan = sharedPreferences.getBool(PreferencesKey.usePolygonScan) ?? true; + final useBaseScan = sharedPreferences.getBool(PreferencesKey.useBaseScan) ?? true; final useTronGrid = sharedPreferences.getBool(PreferencesKey.useTronGrid) ?? true; final useMempoolFeeAPI = sharedPreferences.getBool(PreferencesKey.useMempoolFeeAPI) ?? true; final defaultNanoRep = sharedPreferences.getString(PreferencesKey.defaultNanoRep) ?? ""; @@ -1087,6 +1112,7 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getInt(PreferencesKey.currentBitcoinCashNodeIdKey); final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey); final polygonNodeId = sharedPreferences.getInt(PreferencesKey.currentPolygonNodeIdKey); + final baseNodeId = sharedPreferences.getInt(PreferencesKey.currentBaseNodeIdKey); final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey); final nanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey); final solanaNodeId = sharedPreferences.getInt(PreferencesKey.currentSolanaNodeIdKey); @@ -1107,6 +1133,8 @@ abstract class SettingsStoreBase with Store { nodeSource.values.firstWhereOrNull((e) => e.uriRaw == ethereumDefaultNodeUri); final polygonNode = nodeSource.get(polygonNodeId) ?? nodeSource.values.firstWhereOrNull((e) => e.uriRaw == polygonDefaultNodeUri); + final baseNode = nodeSource.get(baseNodeId) ?? + nodeSource.values.firstWhereOrNull((e) => e.uriRaw == baseDefaultNodeUri); final bitcoinCashElectrumServer = nodeSource.get(bitcoinCashElectrumServerId) ?? nodeSource.values.firstWhereOrNull((e) => e.uriRaw == cakeWalletBitcoinCashDefaultNodeUri); final nanoNode = nodeSource.get(nanoNodeId) ?? @@ -1180,6 +1208,10 @@ abstract class SettingsStoreBase with Store { nodes[WalletType.polygon] = polygonNode; } + if (baseNode != null) { + nodes[WalletType.base] = baseNode; + } + if (bitcoinCashElectrumServer != null) { nodes[WalletType.bitcoinCash] = bitcoinCashElectrumServer; } @@ -1358,6 +1390,7 @@ abstract class SettingsStoreBase with Store { pinNativeTokenAtTop: pinNativeTokenAtTop, useEtherscan: useEtherscan, usePolygonScan: usePolygonScan, + useBaseScan: useBaseScan, useTronGrid: useTronGrid, useMempoolFeeAPI: useMempoolFeeAPI, defaultNanoRep: defaultNanoRep, @@ -1404,6 +1437,7 @@ abstract class SettingsStoreBase with Store { shouldRequireTOTP2FAForAllSecurityAndBackupSettings, initialEthereumTransactionPriority: ethereumTransactionPriority, initialPolygonTransactionPriority: polygonTransactionPriority, + initialBaseTransactionPriority: baseTransactionPriority, initialSyncMode: savedSyncMode, initialSyncAll: savedSyncAll, shouldShowYatPopup: shouldShowYatPopup, @@ -1455,6 +1489,11 @@ abstract class SettingsStoreBase with Store { priority[WalletType.polygon] = polygon!.deserializePolygonTransactionPriority( sharedPreferences.getInt(PreferencesKey.polygonTransactionPriority)!); } + if (base != null && + sharedPreferences.getInt(PreferencesKey.baseTransactionPriority) != null) { + priority[WalletType.base] = base!.deserializeBaseTransactionPriority( + sharedPreferences.getInt(PreferencesKey.baseTransactionPriority)!); + } if (bitcoinCash != null && sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority) != null) { priority[WalletType.bitcoinCash] = bitcoinCash!.deserializeBitcoinCashTransactionPriority( @@ -1548,6 +1587,7 @@ abstract class SettingsStoreBase with Store { pinNativeTokenAtTop = sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop) ?? true; useEtherscan = sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true; usePolygonScan = sharedPreferences.getBool(PreferencesKey.usePolygonScan) ?? true; + useBaseScan = sharedPreferences.getBool(PreferencesKey.useBaseScan) ?? true; useTronGrid = sharedPreferences.getBool(PreferencesKey.useTronGrid) ?? true; useMempoolFeeAPI = sharedPreferences.getBool(PreferencesKey.useMempoolFeeAPI) ?? true; defaultNanoRep = sharedPreferences.getString(PreferencesKey.defaultNanoRep) ?? ""; @@ -1578,6 +1618,7 @@ abstract class SettingsStoreBase with Store { final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey); final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey); final polygonNodeId = sharedPreferences.getInt(PreferencesKey.currentPolygonNodeIdKey); + final baseNodeId = sharedPreferences.getInt(PreferencesKey.currentBaseNodeIdKey); final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey); final solanaNodeId = sharedPreferences.getInt(PreferencesKey.currentSolanaNodeIdKey); final tronNodeId = sharedPreferences.getInt(PreferencesKey.currentTronNodeIdKey); @@ -1591,6 +1632,7 @@ abstract class SettingsStoreBase with Store { final havenNode = nodeSource.get(havenNodeId); final ethereumNode = nodeSource.get(ethereumNodeId); final polygonNode = nodeSource.get(polygonNodeId); + final baseNode = nodeSource.get(baseNodeId); final bitcoinCashNode = nodeSource.get(bitcoinCashElectrumServerId); final nanoNode = nodeSource.get(nanoNodeId); final solanaNode = nodeSource.get(solanaNodeId); @@ -1624,6 +1666,10 @@ abstract class SettingsStoreBase with Store { nodes[WalletType.polygon] = polygonNode; } + if (baseNode != null) { + nodes[WalletType.base] = baseNode; + } + if (bitcoinCashNode != null) { nodes[WalletType.bitcoinCash] = bitcoinCashNode; } @@ -1784,6 +1830,9 @@ abstract class SettingsStoreBase with Store { case WalletType.polygon: await _sharedPreferences.setInt(PreferencesKey.currentPolygonNodeIdKey, node.key as int); break; + case WalletType.base: + await _sharedPreferences.setInt(PreferencesKey.currentBaseNodeIdKey, node.key as int); + break; case WalletType.solana: await _sharedPreferences.setInt(PreferencesKey.currentSolanaNodeIdKey, node.key as int); break; diff --git a/lib/utils/qr_util.dart b/lib/utils/qr_util.dart index 26b300064..7e7aec066 100644 --- a/lib/utils/qr_util.dart +++ b/lib/utils/qr_util.dart @@ -28,9 +28,11 @@ String getQrImage(WalletType type) { return 'assets/images/dcr_chain_qr.svg'; case WalletType.dogecoin: return 'assets/images/doge_chain_qr.svg'; + case WalletType.base: + return 'assets/images/base_chain_QR.svg'; case WalletType.banano: case WalletType.haven: case WalletType.none: - return 'assets/images/qr-cake.svg'; + return 'assets/images/qr-cake.png'; } } diff --git a/lib/utils/token_utilities.dart b/lib/utils/token_utilities.dart index 969b59687..c9a818230 100644 --- a/lib/utils/token_utilities.dart +++ b/lib/utils/token_utilities.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cw_core/cake_hive.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/erc20_token.dart'; @@ -13,14 +14,14 @@ class TokenUtilities { Box walletInfoSource, ) async { final evmWallets = walletInfoSource.values.where( - (w) => w.type == WalletType.ethereum || w.type == WalletType.polygon, + (w) => isEVMCompatibleChain(w.type), ); final seen = {}; final unique = []; for (final wallet in evmWallets) { - final chain = wallet.type == WalletType.ethereum ? 'ETH' : 'POL'; + final chain = getTokenNameBasedOnWalletType(wallet.type); final box = await _openEvmTokensBoxFor(wallet); for (final t in box.values.where((t) => t.enabled)) { @@ -88,6 +89,7 @@ class TokenUtilities { switch (walletType) { case WalletType.ethereum: case WalletType.polygon: + case WalletType.base: final tokens = await loadAllUniqueEvmTokens(walletInfoSource); for (final t in tokens) { if (t.contractAddress.toLowerCase() == lower) return t; @@ -117,6 +119,7 @@ class TokenUtilities { final boxName = switch (walletInfo.type) { WalletType.ethereum => '${walletKey}_${Erc20Token.ethereumBoxName}', WalletType.polygon => '${walletKey}_${Erc20Token.polygonBoxName}', + WalletType.base => '${walletKey}_${Erc20Token.baseBoxName}', _ => '${walletKey}_${Erc20Token.ethereumBoxName}', }; diff --git a/lib/view_model/advanced_privacy_settings_view_model.dart b/lib/view_model/advanced_privacy_settings_view_model.dart index 620e18681..efc2270a7 100644 --- a/lib/view_model/advanced_privacy_settings_view_model.dart +++ b/lib/view_model/advanced_privacy_settings_view_model.dart @@ -39,6 +39,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store { case WalletType.bitcoinCash: case WalletType.dogecoin: case WalletType.polygon: + case WalletType.base: case WalletType.solana: case WalletType.tron: return true; @@ -79,6 +80,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store { WalletType.bitcoinCash, WalletType.ethereum, WalletType.polygon, + WalletType.base, WalletType.tron, WalletType.solana, WalletType.monero, diff --git a/lib/view_model/anon_invoice_page_view_model.dart b/lib/view_model/anon_invoice_page_view_model.dart index bf519c577..22a40a402 100644 --- a/lib/view_model/anon_invoice_page_view_model.dart +++ b/lib/view_model/anon_invoice_page_view_model.dart @@ -5,6 +5,7 @@ import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/entities/fiat_currency.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/utils/qr_util.dart'; +import 'package:cw_core/currency_for_wallet_type.dart'; import 'package:cw_core/receive_page_option.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cw_core/crypto_currency.dart'; diff --git a/lib/view_model/buy/buy_view_model.dart b/lib/view_model/buy/buy_view_model.dart index 785a75963..4c1b5472d 100644 --- a/lib/view_model/buy/buy_view_model.dart +++ b/lib/view_model/buy/buy_view_model.dart @@ -3,6 +3,7 @@ import 'package:cake_wallet/buy/moonpay/moonpay_provider.dart'; import 'package:cake_wallet/buy/wyre/wyre_buy_provider.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cake_wallet/entities/fiat_currency.dart'; +import 'package:cw_core/currency_for_wallet_type.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cake_wallet/store/settings_store.dart'; diff --git a/lib/view_model/contact_list/contact_list_view_model.dart b/lib/view_model/contact_list/contact_list_view_model.dart index 730d0735c..27aee91e3 100644 --- a/lib/view_model/contact_list/contact_list_view_model.dart +++ b/lib/view_model/contact_list/contact_list_view_model.dart @@ -10,6 +10,7 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/utils/mobx.dart'; import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/currency_for_wallet_type.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:hive/hive.dart'; diff --git a/lib/view_model/dashboard/balance_view_model.dart b/lib/view_model/dashboard/balance_view_model.dart index f09d82be3..78701b44c 100644 --- a/lib/view_model/dashboard/balance_view_model.dart +++ b/lib/view_model/dashboard/balance_view_model.dart @@ -166,6 +166,7 @@ abstract class BalanceViewModelBase with Store { case WalletType.haven: case WalletType.ethereum: case WalletType.polygon: + case WalletType.base: case WalletType.solana: case WalletType.tron: return S.current.xmr_full_balance; diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index 3a6cf2d38..265a48308 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -873,6 +873,7 @@ abstract class DashboardViewModelBase with Store { case WalletType.bitcoinCash: case WalletType.ethereum: case WalletType.polygon: + case WalletType.base: case WalletType.solana: case WalletType.nano: case WalletType.banano: diff --git a/lib/view_model/dashboard/home_settings_view_model.dart b/lib/view_model/dashboard/home_settings_view_model.dart index 44b9b9b7a..c54d8b1b3 100644 --- a/lib/view_model/dashboard/home_settings_view_model.dart +++ b/lib/view_model/dashboard/home_settings_view_model.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:cake_wallet/base/base.dart'; import 'package:cake_wallet/core/fiat_conversion_service.dart'; import 'package:cake_wallet/entities/fiat_api_mode.dart'; import 'package:cake_wallet/entities/erc20_token_info_moralis.dart'; @@ -99,6 +100,18 @@ abstract class HomeSettingsViewModelBase with Store { await polygon!.addErc20Token(_balanceViewModel.wallet, polygonToken); } + if (_balanceViewModel.wallet.type == WalletType.base) { + final baseToken = Erc20Token( + name: token.name, + symbol: token.title, + decimal: token.decimals, + contractAddress: contractAddress.toLowerCase(), + iconPath: token.iconPath, + isPotentialScam: token.isPotentialScam, + ); + await base!.addErc20Token(_balanceViewModel.wallet, baseToken); + } + if (_balanceViewModel.wallet.type == WalletType.solana) { final splToken = token.copyWith(enabled: true); await solana!.addSPLToken( @@ -136,6 +149,10 @@ abstract class HomeSettingsViewModelBase with Store { return polygon!.isTokenAlreadyAdded(_balanceViewModel.wallet, contractAddress); } + if (_balanceViewModel.wallet.type == WalletType.base) { + return base!.isTokenAlreadyAdded(_balanceViewModel.wallet, contractAddress); + } + if (_balanceViewModel.wallet.type == WalletType.solana) { return solana!.isTokenAlreadyAdded(_balanceViewModel.wallet, contractAddress); } @@ -163,6 +180,10 @@ abstract class HomeSettingsViewModelBase with Store { await polygon!.deleteErc20Token(_balanceViewModel.wallet, token as Erc20Token); } + if (_balanceViewModel.wallet.type == WalletType.base) { + await base!.deleteErc20Token(_balanceViewModel.wallet, token as Erc20Token); + } + if (_balanceViewModel.wallet.type == WalletType.solana) { await solana!.deleteSPLToken(_balanceViewModel.wallet, token); } @@ -189,16 +210,14 @@ abstract class HomeSettingsViewModelBase with Store { return false; } - bool isEthereum = _balanceViewModel.wallet.type == WalletType.ethereum; - bool isPotentialScamViaMoralis = await _isPotentialScamTokenViaMoralis( contractAddress, - isEthereum ? 'eth' : 'polygon', + getChainNameBasedOnWalletType(_balanceViewModel.wallet.type), ); bool isUnverifiedContract = await _isContractUnverified( contractAddress, - isEthereum: isEthereum, + chainId: getChainIdBasedOnWalletType(_balanceViewModel.wallet.type).toString(), ); final showWarningForContractAddress = isPotentialScamViaMoralis || isUnverifiedContract; @@ -219,6 +238,9 @@ abstract class HomeSettingsViewModelBase with Store { case WalletType.polygon: defaultTokenAddresses = polygon!.getDefaultTokenContractAddresses(); break; + case WalletType.base: + defaultTokenAddresses = base!.getDefaultTokenContractAddresses(); + break; case WalletType.solana: defaultTokenAddresses = solana!.getDefaultTokenContractAddresses(); break; @@ -306,13 +328,13 @@ abstract class HomeSettingsViewModelBase with Store { Future _isContractUnverified( String contractAddress, { - required bool isEthereum, + required String chainId, }) async { final uri = Uri.https( "api.etherscan.io", "/v2/api", { - "chainid": isEthereum ? "1" : "137", + "chainid": chainId, "module": "contract", "action": "getsourcecode", "address": contractAddress, @@ -354,6 +376,10 @@ abstract class HomeSettingsViewModelBase with Store { return await polygon!.getErc20Token(_balanceViewModel.wallet, contractAddress); } + if (_balanceViewModel.wallet.type == WalletType.base) { + return await base!.getErc20Token(_balanceViewModel.wallet, contractAddress); + } + if (_balanceViewModel.wallet.type == WalletType.solana) { return await solana!.getSPLToken(_balanceViewModel.wallet, contractAddress); } @@ -395,6 +421,11 @@ abstract class HomeSettingsViewModelBase with Store { if (!value) polygon!.removeTokenTransactionsInHistory(_balanceViewModel.wallet, token); } + if (_balanceViewModel.wallet.type == WalletType.base) { + base!.addErc20Token(_balanceViewModel.wallet, token as Erc20Token); + if (!value) base!.removeTokenTransactionsInHistory(_balanceViewModel.wallet, token); + } + if (_balanceViewModel.wallet.type == WalletType.solana) { final address = solana!.getTokenAddress(token); solana!.addSPLToken(_balanceViewModel.wallet, token, address); @@ -448,6 +479,14 @@ abstract class HomeSettingsViewModelBase with Store { ..sort(_sortFunc)); } + if (_balanceViewModel.wallet.type == WalletType.base) { + tokens.addAll(base! + .getERC20Currencies(_balanceViewModel.wallet) + .where((element) => _matchesSearchText(element)) + .toList() + ..sort(_sortFunc)); + } + if (_balanceViewModel.wallet.type == WalletType.solana) { tokens.addAll(solana! .getSPLTokenCurrencies(_balanceViewModel.wallet) @@ -489,7 +528,7 @@ abstract class HomeSettingsViewModelBase with Store { bool _matchesSearchText(CryptoCurrency asset) { final address = getTokenAddressBasedOnWallet(asset); - // The homes settings would only be displayed for either of Tron, Ethereum, Polygon or Solana Wallets. + // The homes settings would only be displayed for either of Tron, Ethereum, Polygon, Base or Solana Wallets. if (address == null) return false; return searchText.isEmpty || @@ -515,11 +554,15 @@ abstract class HomeSettingsViewModelBase with Store { return polygon!.getTokenAddress(asset); } + if (_balanceViewModel.wallet.type == WalletType.base) { + return base!.getTokenAddress(asset); + } + if (_balanceViewModel.wallet.type == WalletType.zano) { return zano!.getZanoAssetAddress(asset); } - // We return null if it's neither Tron, Polygon, Ethereum or Solana wallet (which is actually impossible because we only display home settings for either of these three wallets). + // We return null if it's neither Tron, Polygon, Base, Ethereum or Solana wallet (which is actually impossible because we only display home settings for either of these four wallets). return null; } } diff --git a/lib/view_model/dashboard/transaction_list_item.dart b/lib/view_model/dashboard/transaction_list_item.dart index b59011c01..874da36a6 100644 --- a/lib/view_model/dashboard/transaction_list_item.dart +++ b/lib/view_model/dashboard/transaction_list_item.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/base/base.dart'; import 'package:cake_wallet/decred/decred.dart'; import 'package:cake_wallet/entities/balance_display_mode.dart'; import 'package:cake_wallet/entities/fiat_currency.dart'; @@ -134,6 +135,11 @@ class TransactionListItem extends ActionListItem with Keyable { return asset; } + if (balanceViewModel.wallet.type == WalletType.base) { + final asset = base!.assetOfTransaction(balanceViewModel.wallet, transaction); + return asset; + } + if (balanceViewModel.wallet.type == WalletType.solana) { final asset = solana!.assetOfTransaction(balanceViewModel.wallet, transaction); return asset; @@ -186,6 +192,13 @@ class TransactionListItem extends ActionListItem with Keyable { cryptoAmount: polygon!.formatterPolygonAmountToDouble(transaction: transaction), price: price); break; + case WalletType.base: + final asset = base!.assetOfTransaction(balanceViewModel.wallet, transaction); + final price = balanceViewModel.fiatConvertationStore.prices[asset]; + amount = calculateFiatAmountRaw( + cryptoAmount: base!.formatterBaseAmountToDouble(transaction: transaction), + price: price); + break; case WalletType.nano: amount = calculateFiatAmountRaw( cryptoAmount: double.parse(nanoUtil!.getRawAsUsableString( diff --git a/lib/view_model/exchange/exchange_trade_view_model.dart b/lib/view_model/exchange/exchange_trade_view_model.dart index 3ff7037be..635bf5c65 100644 --- a/lib/view_model/exchange/exchange_trade_view_model.dart +++ b/lib/view_model/exchange/exchange_trade_view_model.dart @@ -17,6 +17,7 @@ import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/xoswap_exchange_provider.dart'; import 'package:cake_wallet/exchange/trade.dart'; import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_item.dart'; import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart'; import 'package:cake_wallet/store/dashboard/trades_store.dart'; @@ -306,6 +307,9 @@ abstract class ExchangeTradeViewModelBase with Store { wallet.currency == CryptoCurrency.maticpoly && tradeFrom?.tag == CryptoCurrency.maticpoly.tag; + bool _isBaseToken() => + wallet.currency == CryptoCurrency.baseEth && tradeFrom?.tag == CryptoCurrency.baseEth.title; + bool _isTronToken() => wallet.currency == CryptoCurrency.trx && tradeFrom?.tag == CryptoCurrency.trx.title; @@ -317,7 +321,8 @@ abstract class ExchangeTradeViewModelBase with Store { _isEthToken() || _isPolygonToken() || _isSplToken() || - _isTronToken(); + _isTronToken() || + _isBaseToken(); } PaymentURI? get paymentUri { @@ -343,6 +348,8 @@ abstract class ExchangeTradeViewModelBase with Store { // TODO: Expand ERC681URI support to Polygon(modify decoding flow for QRs, pay anything, and deep link handling) case WalletType.polygon: return PolygonURI(amount: amount, address: inputAddress); + case WalletType.base: + return BaseURI(amount: amount, address: inputAddress); case WalletType.solana: return SolanaURI(amount: amount, address: inputAddress); case WalletType.tron: @@ -377,7 +384,7 @@ abstract class ExchangeTradeViewModelBase with Store { contractAddress: null, ); } else { - if (wallet.type == WalletType.polygon || wallet.type == WalletType.ethereum) { + if (isEVMCompatibleChain(wallet.type)) { final erc20Token = TokenUtilities.findErc20Token(currency, wallet); if (erc20Token != null) { diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index 8c120887d..4ee51176f 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -878,6 +878,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with depositCurrency = CryptoCurrency.maticpoly; receiveCurrency = CryptoCurrency.xmr; break; + case WalletType.base: + depositCurrency = CryptoCurrency.baseEth; + receiveCurrency = CryptoCurrency.xmr; + break; case WalletType.solana: depositCurrency = CryptoCurrency.sol; receiveCurrency = CryptoCurrency.xmr; diff --git a/lib/view_model/node_list/node_create_or_edit_view_model.dart b/lib/view_model/node_list/node_create_or_edit_view_model.dart index a8511809b..67a08872c 100644 --- a/lib/view_model/node_list/node_create_or_edit_view_model.dart +++ b/lib/view_model/node_list/node_create_or_edit_view_model.dart @@ -80,6 +80,7 @@ abstract class NodeCreateOrEditViewModelBase with Store { switch (_walletType) { case WalletType.ethereum: case WalletType.polygon: + case WalletType.base: case WalletType.solana: case WalletType.banano: case WalletType.nano: diff --git a/lib/view_model/restore/wallet_restore_from_qr_code.dart b/lib/view_model/restore/wallet_restore_from_qr_code.dart index 5fe46da7b..c3364ffec 100644 --- a/lib/view_model/restore/wallet_restore_from_qr_code.dart +++ b/lib/view_model/restore/wallet_restore_from_qr_code.dart @@ -3,11 +3,13 @@ import 'dart:convert'; import 'package:cake_wallet/core/seed_validator.dart'; import 'package:cake_wallet/entities/parse_address_from_domain.dart'; import 'package:cake_wallet/entities/qr_scanner.dart'; +import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/restore/restore_mode.dart'; import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; +import 'package:cw_core/currency_for_wallet_type.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/cupertino.dart'; import 'package:cake_wallet/generated/i18n.dart'; @@ -29,6 +31,7 @@ class WalletRestoreFromQRCode { 'litecoin_wallet': WalletType.litecoin, 'ethereum-wallet': WalletType.ethereum, 'polygon-wallet': WalletType.polygon, + 'base-wallet': WalletType.base, 'nano-wallet': WalletType.nano, 'nano_wallet': WalletType.nano, 'bitcoincash': WalletType.bitcoinCash, @@ -206,15 +209,7 @@ class WalletRestoreFromQRCode { : throw Exception('Unexpected restore mode: spend_key or view_key is invalid'); } - if (type == WalletType.ethereum && credentials.containsKey('private_key')) { - final privateKey = credentials['private_key'] as String; - if (privateKey.isEmpty) { - throw Exception('Unexpected restore mode: private_key'); - } - return WalletRestoreMode.keys; - } - - if (type == WalletType.polygon && credentials.containsKey('private_key')) { + if (isEVMCompatibleChain(type) && credentials.containsKey('private_key')) { final privateKey = credentials['private_key'] as String; if (privateKey.isEmpty) { throw Exception('Unexpected restore mode: private_key'); diff --git a/lib/view_model/send/fees_view_model.dart b/lib/view_model/send/fees_view_model.dart index f56212d4d..ff878838c 100644 --- a/lib/view_model/send/fees_view_model.dart +++ b/lib/view_model/send/fees_view_model.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/base/base.dart'; import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/decred/decred.dart'; import 'package:cake_wallet/dogecoin/dogecoin.dart'; @@ -90,6 +91,8 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor return transactionPriority == bitcoinCash!.getBitcoinCashTransactionPrioritySlow(); case WalletType.polygon: return transactionPriority == polygon!.getPolygonTransactionPrioritySlow(); + case WalletType.base: + return transactionPriority == base!.getBaseTransactionPrioritySlow(); case WalletType.decred: return transactionPriority == decred!.getDecredTransactionPrioritySlow(); case WalletType.dogecoin: @@ -198,6 +201,9 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor case WalletType.polygon: _settingsStore.priority[wallet.type] = polygon!.getDefaultTransactionPriority(); break; + case WalletType.base: + _settingsStore.priority[wallet.type] = base!.getDefaultTransactionPriority(); + break; default: break; } diff --git a/lib/view_model/send/output.dart b/lib/view_model/send/output.dart index 5e7c8d45b..a599ae836 100644 --- a/lib/view_model/send/output.dart +++ b/lib/view_model/send/output.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/base/base.dart'; import 'package:cake_wallet/decred/decred.dart'; import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart'; @@ -114,6 +115,9 @@ abstract class OutputBase with Store { case WalletType.polygon: _amount = polygon!.formatterPolygonParseAmount(_cryptoAmount); break; + case WalletType.base: + _amount = base!.formatterBaseParseAmount(_cryptoAmount); + break; case WalletType.wownero: _amount = wownero!.formatterWowneroParseAmount(amount: _cryptoAmount); break; @@ -196,6 +200,10 @@ abstract class OutputBase with Store { return polygon!.formatterPolygonAmountToDouble(amount: BigInt.from(fee)); } + if (_wallet.type == WalletType.base) { + return base!.formatterBaseAmountToDouble(amount: BigInt.from(fee)); + } + if (_wallet.type == WalletType.zano) { return zano!.formatterIntAmountToDouble( amount: fee, currency: cryptoCurrencyHandler(), forFee: true); @@ -328,6 +336,7 @@ abstract class OutputBase with Store { case WalletType.monero: case WalletType.ethereum: case WalletType.polygon: + case WalletType.base: case WalletType.solana: case WalletType.tron: case WalletType.haven: diff --git a/lib/view_model/send/send_template_view_model.dart b/lib/view_model/send/send_template_view_model.dart index 106da72ff..43154868e 100644 --- a/lib/view_model/send/send_template_view_model.dart +++ b/lib/view_model/send/send_template_view_model.dart @@ -1,6 +1,7 @@ import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/view_model/send/template_view_model.dart'; import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/currency_for_wallet_type.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/entities/template.dart'; @@ -55,6 +56,7 @@ abstract class SendTemplateViewModelBase with Store { _wallet.type != WalletType.haven && _wallet.type != WalletType.ethereum && _wallet.type != WalletType.polygon && + _wallet.type != WalletType.base && _wallet.type != WalletType.solana && _wallet.type != WalletType.tron; diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 2a33f0414..55bfabb4c 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:cake_wallet/base/base.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/core/address_validator.dart'; import 'package:cake_wallet/core/amount_validator.dart'; @@ -45,6 +46,7 @@ import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_mod import 'package:cake_wallet/wownero/wownero.dart'; import 'package:cake_wallet/zano/zano.dart'; import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/currency_for_wallet_type.dart'; import 'package:cw_core/exceptions.dart'; import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/sync_status.dart'; @@ -215,6 +217,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor switch (type) { case WalletType.ethereum: case WalletType.polygon: + case WalletType.base: case WalletType.tron: case WalletType.solana: return wallet.currency; @@ -708,6 +711,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor case WalletType.polygon: return polygon!.createPolygonTransactionCredentials(outputs, priority: priority!, currency: selectedCryptoCurrency); + case WalletType.base: + return base!.createBaseTransactionCredentials(outputs, + priority: priority!, currency: selectedCryptoCurrency); case WalletType.solana: return solana! .createSolanaTransactionCredentials(outputs, currency: selectedCryptoCurrency); @@ -833,6 +839,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor } if (walletType == WalletType.ethereum || walletType == WalletType.polygon || + walletType == WalletType.base || walletType == WalletType.haven) { if (errorMessage.contains('gas required exceeds allowance')) { return S.current.gas_exceeds_allowance; diff --git a/lib/view_model/settings/privacy_settings_view_model.dart b/lib/view_model/settings/privacy_settings_view_model.dart index 22032b7d3..150ccdd3d 100644 --- a/lib/view_model/settings/privacy_settings_view_model.dart +++ b/lib/view_model/settings/privacy_settings_view_model.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/base/base.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart'; @@ -75,6 +76,9 @@ abstract class PrivacySettingsViewModelBase with Store { @computed bool get usePolygonScan => _settingsStore.usePolygonScan; + @computed + bool get useBaseScan => _settingsStore.useBaseScan; + @computed bool get useTronGrid => _settingsStore.useTronGrid; @@ -112,6 +116,8 @@ abstract class PrivacySettingsViewModelBase with Store { bool get canUsePolygonScan => _wallet.type == WalletType.polygon; + bool get canUseBaseScan => _wallet.type == WalletType.base; + bool get canUseTronGrid => _wallet.type == WalletType.tron; bool get canUseMempoolFeeAPI => _wallet.type == WalletType.bitcoin; @@ -177,6 +183,12 @@ abstract class PrivacySettingsViewModelBase with Store { polygon!.updatePolygonScanUsageState(_wallet, value); } + @action + void setUseBaseScan(bool value) { + _settingsStore.useBaseScan = value; + base!.updateBaseScanUsageState(_wallet, value); + } + @action void setUseTronGrid(bool value) { _settingsStore.useTronGrid = value; diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart index e55bc0428..dde59058a 100644 --- a/lib/view_model/transaction_details_view_model.dart +++ b/lib/view_model/transaction_details_view_model.dart @@ -1,5 +1,6 @@ import 'package:cake_wallet/tron/tron.dart'; import 'package:cake_wallet/wownero/wownero.dart'; +import 'package:cw_core/currency_for_wallet_type.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/transaction_info.dart'; @@ -73,6 +74,9 @@ abstract class TransactionDetailsViewModelBase with Store { case WalletType.polygon: _addPolygonListItems(tx, dateFormat); break; + case WalletType.base: + _addBaseListItems(tx, dateFormat); + break; case WalletType.solana: _addSolanaListItems(tx, dateFormat); break; @@ -198,6 +202,8 @@ abstract class TransactionDetailsViewModelBase with Store { return 'https://${wallet.isTestnet ? "testnet" : "dcrdata"}.decred.org/tx/${txId.split(':')[0]}'; case WalletType.dogecoin: return 'https://blockchair.com/dogecoin/transaction/${txId}'; + case WalletType.base: + return 'https://basescan.org/tx/${txId}'; case WalletType.none: return ''; } @@ -233,6 +239,8 @@ abstract class TransactionDetailsViewModelBase with Store { return S.current.view_transaction_on + 'explorer.zano.org'; case WalletType.decred: return S.current.view_transaction_on + 'dcrdata.decred.org'; + case WalletType.base: + return S.current.view_transaction_on + 'basescan.org'; case WalletType.none: return ''; } @@ -548,6 +556,56 @@ abstract class TransactionDetailsViewModelBase with Store { items.addAll(_items); } + void _addBaseListItems(TransactionInfo tx, DateFormat dateFormat) { + final _items = [ + StandartListItem( + title: S.current.transaction_details_transaction_id, + value: tx.txHash, + key: ValueKey('standard_list_item_transaction_details_id_key'), + ), + StandartListItem( + title: S.current.transaction_details_date, + value: dateFormat.format(tx.date), + key: ValueKey('standard_list_item_transaction_details_date_key'), + ), + StandartListItem( + title: S.current.confirmations, + value: tx.confirmations.toString(), + key: ValueKey('standard_list_item_transaction_confirmations_key'), + ), + StandartListItem( + title: S.current.transaction_details_height, + value: '${tx.height}', + key: ValueKey('standard_list_item_transaction_details_height_key'), + ), + StandartListItem( + title: S.current.transaction_details_amount, + value: tx.amountFormatted(), + key: ValueKey('standard_list_item_transaction_details_amount_key'), + ), + if (tx.feeFormatted()?.isNotEmpty ?? false) + StandartListItem( + title: S.current.transaction_details_fee, + value: tx.feeFormatted()!, + key: ValueKey('standard_list_item_transaction_details_fee_key'), + ), + if (showRecipientAddress && tx.to != null && tx.direction == TransactionDirection.outgoing) + StandartListItem( + title: S.current.transaction_details_recipient_address, + value: tx.to!, + key: ValueKey('standard_list_item_transaction_details_recipient_address_key'), + ), + if (tx.direction == TransactionDirection.incoming && tx.from != null) + StandartListItem( + title: S.current.transaction_details_source_address, + value: tx.from!, + key: ValueKey('standard_list_item_transaction_details_source_address_key'), + ), + ]; + + items.addAll(_items); + } + void _addSolanaListItems(TransactionInfo tx, DateFormat dateFormat) { final _items = [ StandartListItem( diff --git a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart index 49ee117b4..7f2a78b0d 100644 --- a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart @@ -1,6 +1,7 @@ import 'dart:developer' as dev; import 'dart:core'; +import 'package:cake_wallet/base/base.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/core/fiat_conversion_service.dart'; import 'package:cake_wallet/core/payment_uris.dart'; @@ -30,6 +31,7 @@ import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_i import 'package:cake_wallet/wownero/wownero.dart'; import 'package:cw_core/amount_converter.dart'; import 'package:cw_core/currency.dart'; +import 'package:cw_core/currency_for_wallet_type.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:intl/intl.dart'; import 'package:mobx/mobx.dart'; @@ -38,7 +40,6 @@ part 'wallet_address_list_view_model.g.dart'; class WalletAddressListViewModel = WalletAddressListViewModelBase with _$WalletAddressListViewModel; - abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewModel with Store { WalletAddressListViewModelBase({ required AppStore appStore, @@ -143,6 +144,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo return DecredURI(amount: amount, address: address.address); case WalletType.dogecoin: return DogeURI(amount: amount, address: address.address); + case WalletType.base: + return BaseURI(amount: amount, address: address.address); case WalletType.none: throw Exception('Unexpected type: ${type.toString()}'); } @@ -263,6 +266,12 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress)); } + if (wallet.type == WalletType.base) { + final primaryAddress = base!.getAddress(wallet); + + addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress)); + } + if (wallet.type == WalletType.solana) { final primaryAddress = solana!.getAddress(wallet); @@ -435,7 +444,12 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo 'assets/images/zano_icon.svg', 'assets/images/more_tokens.svg', ]; - + case WalletType.base: + return [ + 'assets/images/eth_icon.svg', + 'assets/images/usdc_icon.svg', + 'assets/images/more_tokens.svg', + ]; default: return []; } @@ -457,6 +471,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo return 'assets/images/trx_chain_mono.svg'; case WalletType.zano: return 'assets/images/zano_chain_mono.svg'; + case WalletType.base: + return 'assets/images/base_chain_mono.svg'; default: return 'assets/images/eth_chain_mono.svg'; } diff --git a/lib/view_model/wallet_keys_view_model.dart b/lib/view_model/wallet_keys_view_model.dart index 1f7620452..f429cd958 100644 --- a/lib/view_model/wallet_keys_view_model.dart +++ b/lib/view_model/wallet_keys_view_model.dart @@ -126,6 +126,7 @@ abstract class WalletKeysViewModelBase with Store { break; case WalletType.ethereum: case WalletType.polygon: + case WalletType.base: case WalletType.solana: case WalletType.tron: items.addAll([ @@ -252,6 +253,8 @@ abstract class WalletKeysViewModelBase with Store { return 'banano-wallet'; case WalletType.polygon: return 'polygon-wallet'; + case WalletType.base: + return 'base-wallet'; case WalletType.solana: return 'solana-wallet'; case WalletType.tron: diff --git a/lib/view_model/wallet_new_vm.dart b/lib/view_model/wallet_new_vm.dart index 6e7a4cb96..7ee0c0359 100644 --- a/lib/view_model/wallet_new_vm.dart +++ b/lib/view_model/wallet_new_vm.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/base/base.dart'; import 'package:cake_wallet/core/new_wallet_arguments.dart'; import 'package:cake_wallet/dogecoin/dogecoin.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; @@ -90,6 +91,13 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { mnemonic: newWalletArguments!.mnemonic, passphrase: passphrase, ); + case WalletType.base: + return base!.createBaseNewWalletCredentials( + name: name, + password: walletPassword, + mnemonic: newWalletArguments!.mnemonic, + passphrase: passphrase, + ); case WalletType.bitcoinCash: return bitcoinCash!.createBitcoinCashNewWalletCredentials( name: name, diff --git a/lib/view_model/wallet_restore_view_model.dart b/lib/view_model/wallet_restore_view_model.dart index cd10d143e..3a5716ba2 100644 --- a/lib/view_model/wallet_restore_view_model.dart +++ b/lib/view_model/wallet_restore_view_model.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/base/base.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/core/generate_wallet_password.dart'; @@ -51,6 +52,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { case WalletType.haven: case WalletType.ethereum: case WalletType.polygon: + case WalletType.base: case WalletType.decred: case WalletType.bitcoin: availableModes = [WalletRestoreMode.seed, WalletRestoreMode.keys]; @@ -90,6 +92,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { late final bool hasRestoreFromPrivateKey = [ WalletType.ethereum, WalletType.polygon, + WalletType.base, WalletType.nano, WalletType.banano, WalletType.solana, @@ -174,6 +177,13 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { password: password, passphrase: passphrase, ); + case WalletType.base: + return base!.createBaseRestoreWalletFromSeedCredentials( + name: name, + mnemonic: seed, + password: password, + passphrase: passphrase, + ); case WalletType.solana: return solana!.createSolanaRestoreWalletFromSeedCredentials( name: name, @@ -261,6 +271,12 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { password: password, privateKey: options['private_key'] as String, ); + case WalletType.base: + return base!.createBaseRestoreWalletFromPrivateKey( + name: name, + password: password, + privateKey: options['private_key'] as String, + ); case WalletType.solana: return solana!.createSolanaRestoreWalletFromPrivateKey( name: name, diff --git a/model_generator.sh b/model_generator.sh index 4493e62f9..7b720923d 100755 --- a/model_generator.sh +++ b/model_generator.sh @@ -1,7 +1,7 @@ #!/bin/bash set -x -e -for cwcoin in cw_{core,evm,monero,bitcoin,nano,bitcoin_cash,solana,tron,wownero,zano,decred,dogecoin} +for cwcoin in cw_{core,evm,monero,bitcoin,nano,bitcoin_cash,solana,tron,wownero,zano,decred,dogecoin,base} do if [[ "x$1" == "xasync" ]]; then diff --git a/pubspec_base.yaml b/pubspec_base.yaml index c5b8480bc..db229a3f7 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -235,6 +235,7 @@ flutter: - assets/zano_node_list.yml - assets/decred_node_list.yml - assets/dogecoin_electrum_server_list.yml + - assets/base_node_list.yml - assets/text/ - assets/faq/ - assets/animation/ diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index f4c102c00..2ec9aa87e 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -88,6 +88,7 @@ "backup_password": "كلمة مرور النسخ الاحتياطي", "balance": "توازن", "balance_page": "صفحة التوازن", + "basescan_history": "تاريخ باسيسكان", "bill_amount": "مبلغ الفاتورة", "billing_address_info": "إذا طُلب منك عنوان إرسال فواتير ، فأدخل عنوان الشحن الخاص بك", "biometric_auth_reason": "امسح بصمة إصبعك للمصادقة", diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 0fb5c6db6..c6d4eb098 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -88,6 +88,7 @@ "backup_password": "Парола за възстановяване", "balance": "Баланс", "balance_page": "Страница за баланс", + "basescan_history": "История на басескан", "bill_amount": "Искана сума", "billing_address_info": "Ако Ви попитат за билинг адрес, въведето своя адрес за доставка", "biometric_auth_reason": "Сканирайте своя пръстов отпечатък", diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 334c2a85d..473cb612a 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -88,6 +88,7 @@ "backup_password": "Heslo pro zálohy", "balance": "Zůstatek", "balance_page": "Stránka zůstatku", + "basescan_history": "Historie basescan", "bill_amount": "Účtovaná částka", "billing_address_info": "Při dotazu na fakturační adresu, zadejte svou doručovací adresu", "biometric_auth_reason": "Naskenujte otisk prstu pro ověření", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index eb590b216..c1b0ae805 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -88,6 +88,7 @@ "backup_password": "Passwort sichern", "balance": "Saldo", "balance_page": "Guthaben-Seite", + "basescan_history": "Baseskangeschichte", "bill_amount": "Rechnungsbetrag", "billing_address_info": "Wenn Sie nach einer Rechnungsadresse gefragt werden, geben Sie bitte Ihre Lieferadresse an", "biometric_auth_reason": "Scannen Sie Ihren Fingerabdruck zur Authentifizierung", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 2848ce61e..c6b0a6d16 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -88,6 +88,7 @@ "backup_password": "Backup password", "balance": "Balance", "balance_page": "Balance Page", + "basescan_history": "BaseScan History", "bill_amount": "Bill Amount", "billing_address_info": "If asked for a billing address, provide your shipping address", "biometric_auth_reason": "Scan your fingerprint to authenticate", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 834c849f1..95d7bc304 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -88,6 +88,7 @@ "backup_password": "Contraseña de respaldo", "balance": "Balance", "balance_page": "Página de saldo", + "basescan_history": "Historia de Basescan", "bill_amount": "Importe de la factura", "billing_address_info": "Si se le solicita una dirección de facturación, proporcione su dirección de envío", "biometric_auth_reason": "Escanee su huella digital para autenticar", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index c1f54b363..b35883a69 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -88,6 +88,7 @@ "backup_password": "Mot de passe de sauvegarde", "balance": "Solde", "balance_page": "Page Solde", + "basescan_history": "HISTOIRE DE BASESCAN", "bill_amount": "Montant de la facture", "billing_address_info": "Si une adresse de facturation vous est demandée, indiquez votre adresse de livraison", "biometric_auth_reason": "Scannez votre empreinte digitale pour vous authentifier", diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index e9069a429..cfe6f0492 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -88,6 +88,7 @@ "backup_password": "Ajiyayyen kalmar sirri", "balance": "Ma'auni", "balance_page": "Ma'auni Page", + "basescan_history": "Tarihin Basescan", "bill_amount": "Adadin Bill", "billing_address_info": "Idan an nemi adireshin biyan kuɗi, samar da adireshin jigilar kaya", "biometric_auth_reason": "Duba hoton yatsa don tantancewa", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 0515899e0..efb87f457 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -88,6 +88,7 @@ "backup_password": "बैकअप पासवर्ड", "balance": "संतुलन", "balance_page": "बैलेंस पेज", + "basescan_history": "बेसस्कैन इतिहास", "bill_amount": "बिल राशि", "billing_address_info": "यदि बिलिंग पता मांगा जाए, तो अपना शिपिंग पता प्रदान करें", "biometric_auth_reason": "प्रमाणित करने के लिए अपने फ़िंगरप्रिंट को स्कैन करें", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index b65dd079f..c1feb69dd 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -88,6 +88,7 @@ "backup_password": "Lozinka za sigurnosnu kopiju", "balance": "Uravnotežiti", "balance_page": "Stranica sa stanjem", + "basescan_history": "Basescan povijest", "bill_amount": "Iznos računa", "billing_address_info": "Ako se od vas zatraži adresa za naplatu, navedite svoju adresu za dostavu", "biometric_auth_reason": "Skenirajte svoj otisak prsta za autentifikaciju", diff --git a/res/values/strings_hy.arb b/res/values/strings_hy.arb index 12fec6eca..98de9b9da 100644 --- a/res/values/strings_hy.arb +++ b/res/values/strings_hy.arb @@ -88,6 +88,7 @@ "backup_password": "Կրկնօրինակի գաղտնաբառ", "balance": "Հաշվեկշիռ", "balance_page": "Հաշվեկշռի էջ", + "basescan_history": "Բասեսկանի պատմություն", "bill_amount": "Հաշիվը", "billing_address_info": "Եթե խնդրեն հաշվեվճարի հասցե, ապա տրամադրեք ձեր առաքման հասցեն", "biometric_auth_reason": "Սկանավեք ձեր մատնահետքը նույնականացման համար", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 6cc51528a..9b55aa4d2 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -88,6 +88,7 @@ "backup_password": "Kata sandi cadangan", "balance": "Keseimbangan", "balance_page": "Halaman Saldo", + "basescan_history": "Sejarah Basescan", "bill_amount": "Jumlah Tagihan", "billing_address_info": "Jika diminta alamat billing, berikan alamat pengiriman Anda", "biometric_auth_reason": "Pindai sidik jari Anda untuk mengautentikasi", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 9ceff0ccd..a91c2dbcb 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -88,6 +88,7 @@ "backup_password": "Backup password", "balance": "Saldo", "balance_page": "Pagina di saldo", + "basescan_history": "Storia di Basescan", "bill_amount": "Importo della ricevuta", "billing_address_info": "Se ti viene richiesto un indirizzo di fatturazione, fornisci il tuo indirizzo di spedizione", "biometric_auth_reason": "Scansiona la tua impronta per autenticarti", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 7babd77bf..092880673 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -88,6 +88,7 @@ "backup_password": "バックアップパスワード", "balance": "バランス", "balance_page": "残高ページ", + "basescan_history": "Basescanの歴史", "bill_amount": "請求額", "billing_address_info": "請求先住所を尋ねられた場合は、配送先住所を入力してください", "biometric_auth_reason": "प指紋をスキャンして認証する", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index b7c00cd2c..c69cef382 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -88,6 +88,7 @@ "backup_password": "백업 비밀번호", "balance": "잔액", "balance_page": "잔액 페이지", + "basescan_history": "바스 스칸 역사", "bill_amount": "청구 금액", "billing_address_info": "청구지 주소를 묻는 경우 배송 주소를 제공하세요", "biometric_auth_reason": "인증하려면 지문을 스캔하세요", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 4005cc362..46c3087bc 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -88,6 +88,7 @@ "backup_password": "စကားဝှက်ကို အရန်သိမ်းဆည်းပါ။", "balance": "လက်ကျန်ငေှ", "balance_page": "လက်ကျန်စာမျက်နှာ", + "basescan_history": "Bascan သမိုင်း", "bill_amount": "ဘီလ်ပမာဏ", "billing_address_info": "ငွေပေးချေရမည့်လိပ်စာကို တောင်းဆိုပါက သင့်ပို့ဆောင်ရေးလိပ်စာကို ပေးပါ။", "biometric_auth_reason": "စစ်မှန်ကြောင်းအထောက်အထားပြရန် သင့်လက်ဗွေကို စကန်ဖတ်ပါ။", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index df67a9447..8bd0f3605 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -88,6 +88,7 @@ "backup_password": "Reservewachtwoord", "balance": "Evenwicht", "balance_page": "Saldo pagina", + "basescan_history": "BASESCAN GESCHIEDENIS", "bill_amount": "Bill bedrag", "billing_address_info": "Als u om een ​​factuuradres wordt gevraagd, geef dan uw verzendadres op", "biometric_auth_reason": "Scan uw vingerafdruk om te verifiëren", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 273cf14ac..9c67ed7b9 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -88,6 +88,7 @@ "backup_password": "Hasło kpoii zapasowej", "balance": "Saldo", "balance_page": "Strona salda", + "basescan_history": "Historia Basescan", "bill_amount": "Kwota rachunku", "billing_address_info": "Jeśli zostaniesz poproszony o podanie adresu rozliczeniowego, podaj swój adres wysyłki", "biometric_auth_reason": "Zeskanuj swój odcisk palca, aby uwierzytelnić", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 10eef448f..520d32691 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -88,6 +88,7 @@ "backup_password": "Senha de backup", "balance": "Equilíbrio", "balance_page": "Página de saldo", + "basescan_history": "História de Basescan", "bill_amount": "Valor da conta", "billing_address_info": "Se for solicitado um endereço de cobrança, forneça seu endereço de entrega", "biometric_auth_reason": "Digitalize sua impressão digital para autenticar", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 419da09bc..939ceb046 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -88,6 +88,7 @@ "backup_password": "Пароль резервной копии", "balance": "Баланс", "balance_page": "Страница баланса", + "basescan_history": "Басесканская история", "bill_amount": "Сумма счета", "billing_address_info": "Если вас попросят указать платежный адрес, укажите адрес доставки", "biometric_auth_reason": "Отсканируйте свой отпечаток пальца для аутентификации", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index d84e1694e..ff9c693ca 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -88,6 +88,7 @@ "backup_password": "รหัสผ่านสำรองข้อมูล", "balance": "สมดุล", "balance_page": "หน้ายอดคงเหลือ", + "basescan_history": "ประวัติศาสตร์ Basescan", "bill_amount": "จำนวนบิล", "billing_address_info": "ถ้าถูกร้องขอที่อยู่สำหรับการวางบิล ให้ใช้ที่อยู่จัดส่งของคุณ", "biometric_auth_reason": "สแกนลายนิ้วมือของคุณเพื่อยืนยันตัวตน", diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 0c597a8d3..972b42bec 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -88,6 +88,7 @@ "backup_password": "Backup na password", "balance": "Balanse", "balance_page": "Pahina ng Balanse", + "basescan_history": "Kasaysayan ng Basescan", "bill_amount": "Halaga ng Bill", "billing_address_info": "Kung humihingi ng billing address, ibigay ang iyong shipping address", "biometric_auth_reason": "I-scan ang iyong fingerprint para ma-authenticate", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 1ab66df71..2db63187d 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -88,6 +88,7 @@ "backup_password": "Yedek parolası", "balance": "Denge", "balance_page": "Bakiye Sayfası", + "basescan_history": "Basescan tarihi", "bill_amount": "Fatura Tutarı", "billing_address_info": "Eğer fatura adresi istenirse, kargo adresinizi girin", "biometric_auth_reason": "Kimlik doğrulaması için parmak izini okutun", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 00477eef1..76d6dafd7 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -88,6 +88,7 @@ "backup_password": "Пароль резервної копії", "balance": "Балансувати", "balance_page": "Сторінка балансу", + "basescan_history": "Базесканська історія", "bill_amount": "Сума рахунку", "billing_address_info": "Якщо буде запропоновано платіжну адресу, вкажіть свою адресу доставки", "biometric_auth_reason": "Відскануйте свій відбиток пальця для аутентифікації", diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 479fbc574..4799a29bc 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -88,6 +88,7 @@ "backup_password": "بیک اپ پاس ورڈ", "balance": "بقیہ", "balance_page": "بیلنس صفحہ", + "basescan_history": "باسکی تاریخ", "bill_amount": "بل رقم", "billing_address_info": "اگر آپ سے بلنگ کا پتہ پوچھا جائے تو اپنا شپنگ ایڈریس فراہم کریں۔", "biometric_auth_reason": "تصدیق کرنے کے لیے اپنے فنگر پرنٹ کو اسکین کریں۔", diff --git a/res/values/strings_vi.arb b/res/values/strings_vi.arb index f7435ce9c..b9942e8a4 100644 --- a/res/values/strings_vi.arb +++ b/res/values/strings_vi.arb @@ -88,6 +88,7 @@ "backup_password": "Mật khẩu sao lưu", "balance": "Số dư", "balance_page": "Trang số dư", + "basescan_history": "Lịch sử Basescan", "bill_amount": "Số tiền hóa đơn", "billing_address_info": "Nếu được yêu cầu địa chỉ thanh toán, hãy cung cấp địa chỉ giao hàng của bạn", "biometric_auth_reason": "Quét vân tay để xác thực", diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 0e02afcde..94b96bee6 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -88,6 +88,7 @@ "backup_password": "Ṣẹ̀dà ọ̀rọ̀ aṣínà", "balance": "Iwọntunwọnsi", "balance_page": "Oju-iwe iwọntunwọnsi", + "basescan_history": "Itan Basiki", "bill_amount": "Iye ìwé owó", "billing_address_info": "Tí ọlọ́jà bá bèèrè àdírẹ́sì sísan yín, fún òun ni àdírẹ́sì t'á ránṣẹ́ káàdì yìí sí", "biometric_auth_reason": "Ya ìka ọwọ́ yín láti ṣe ìfẹ̀rílàdí", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index ab5121198..6b4cbcbe8 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -88,6 +88,7 @@ "backup_password": "备份密码", "balance": "平衡", "balance_page": "余额页", + "basescan_history": "Basescan历史", "bill_amount": "账单金额", "billing_address_info": "如果要求提供帐单地址,请提供您的送货地址", "biometric_auth_reason": "扫描指纹进行身份认证", diff --git a/scripts/android/pubspec_gen.sh b/scripts/android/pubspec_gen.sh index 8170b7ff4..66cd4d615 100755 --- a/scripts/android/pubspec_gen.sh +++ b/scripts/android/pubspec_gen.sh @@ -10,7 +10,7 @@ case $APP_ANDROID_TYPE in CONFIG_ARGS="--monero" ;; $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred --dogecoin" + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred --dogecoin --base" ;; esac diff --git a/scripts/build_bitbox_flutter.sh b/scripts/build_bitbox_flutter.sh index 3f0a5637c..d21014a38 100755 --- a/scripts/build_bitbox_flutter.sh +++ b/scripts/build_bitbox_flutter.sh @@ -2,7 +2,7 @@ git clone https://github.com/konstantinullrich/bitbox_flutter cd bitbox_flutter -bash ./build_bindings.sh --dont-install +bash ./build_bindings.sh FILE=api.aar if [ -f "$FILE" ]; then diff --git a/scripts/ios/app_config.sh b/scripts/ios/app_config.sh index 78a8f7d82..f69c9f328 100755 --- a/scripts/ios/app_config.sh +++ b/scripts/ios/app_config.sh @@ -31,7 +31,7 @@ case $APP_IOS_TYPE in ;; $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred --dogecoin" + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred --dogecoin --base" ;; esac diff --git a/scripts/linux/app_config.sh b/scripts/linux/app_config.sh index 55cfef60a..b579f2cc6 100755 --- a/scripts/linux/app_config.sh +++ b/scripts/linux/app_config.sh @@ -13,7 +13,7 @@ CONFIG_ARGS="" case $APP_LINUX_TYPE in $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --dogecoin";; + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --dogecoin --base";; esac cp -rf pubspec_description.yaml pubspec.yaml diff --git a/scripts/macos/app_config.sh b/scripts/macos/app_config.sh index dfea3dee2..0ae44f216 100755 --- a/scripts/macos/app_config.sh +++ b/scripts/macos/app_config.sh @@ -36,7 +36,7 @@ case $APP_MACOS_TYPE in $MONERO_COM) CONFIG_ARGS="--monero";; $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --dogecoin";; + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --dogecoin --base";; esac cp -rf pubspec_description.yaml pubspec.yaml diff --git a/tool/configure.dart b/tool/configure.dart index f003ba19d..50930c591 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -12,6 +12,7 @@ const wowneroOutputPath = 'lib/wownero/wownero.dart'; const zanoOutputPath = 'lib/zano/zano.dart'; const decredOutputPath = 'lib/decred/decred.dart'; const dogecoinOutputPath = 'lib/dogecoin/dogecoin.dart'; +const baseOutputPath = 'lib/base/base.dart'; const walletTypesPath = 'lib/wallet_types.g.dart'; const secureStoragePath = 'lib/core/secure_storage.dart'; const pubspecDefaultPath = 'pubspec_default.yaml'; @@ -32,6 +33,7 @@ Future main(List args) async { final hasZano = args.contains('${prefix}zano'); final hasDecred = args.contains('${prefix}decred'); final hasDogecoin = args.contains('${prefix}dogecoin'); + final hasBase = args.contains('${prefix}base'); final excludeFlutterSecureStorage = args.contains('${prefix}excludeFlutterSecureStorage'); await generateBitcoin(hasBitcoin); @@ -47,6 +49,7 @@ Future main(List args) async { // await generateBanano(hasEthereum); await generateDecred(hasDecred); await generateDogecoin(hasDogecoin); + await generateBase(hasBase); await generatePubspec( hasMonero: hasMonero, @@ -63,6 +66,7 @@ Future main(List args) async { hasZano: hasZano, hasDecred: hasDecred, hasDogecoin: hasDogecoin, + hasBase: hasBase, ); await generateWalletTypes( hasMonero: hasMonero, @@ -78,6 +82,7 @@ Future main(List args) async { hasZano: hasZano, hasDecred: hasDecred, hasDogecoin: hasDogecoin, + hasBase: hasBase, ); await injectSecureStorage(!excludeFlutterSecureStorage); } @@ -1497,6 +1502,132 @@ abstract class DogeCoin { await outputFile.writeAsString(output); } +Future generateBase(bool hasImplementation) async { + final outputFile = File(baseOutputPath); + const baseCommonHeaders = """ +import 'package:cake_wallet/view_model/send/output.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/erc20_token.dart'; +import 'package:cw_core/hardware/hardware_account_data.dart'; +import 'package:cw_core/hardware/hardware_wallet_service.dart'; +import 'package:cw_core/output_info.dart'; +import 'package:cw_core/pending_transaction.dart'; +import 'package:cw_core/transaction_info.dart'; +import 'package:cw_core/transaction_priority.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_credentials.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_service.dart'; +import 'package:hive/hive.dart'; +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger; +import 'package:bitbox_flutter/bitbox_flutter.dart' as bitbox; +import 'package:web3dart/web3dart.dart'; + +"""; + const baseCWHeaders = """ +import 'package:cw_evm/evm_chain_formatter.dart'; +import 'package:cw_evm/evm_chain_mnemonics.dart'; +import 'package:cw_evm/evm_chain_transaction_credentials.dart'; +import 'package:cw_evm/evm_chain_transaction_info.dart'; +import 'package:cw_evm/evm_chain_transaction_priority.dart'; +import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart'; +import 'package:cw_evm/hardware/evm_chain_ledger_credentials.dart'; +import 'package:cw_evm/hardware/evm_chain_bitbox_credentials.dart'; +import 'package:cw_evm/evm_chain_wallet.dart'; +import 'package:cw_evm/hardware/evm_chain_bitbox_service.dart'; +import 'package:cw_evm/hardware/evm_chain_ledger_service.dart'; + +import 'package:cw_base/base_client.dart'; +import 'package:cw_base/base_wallet.dart'; +import 'package:cw_base/base_wallet_service.dart'; +import 'package:cw_base/default_base_erc20_tokens.dart'; +import 'package:eth_sig_util/util/utils.dart'; + +"""; + const baseCwPart = "part 'cw_base.dart';"; + const baseContent = """ +abstract class Base { + List getBaseWordList(String language); + WalletService createBaseWalletService(Box walletInfoSource, bool isDirect); + WalletCredentials createBaseNewWalletCredentials( + {required String name, + WalletInfo? walletInfo, + String? password, + String? mnemonic, + String? passphrase}); + WalletCredentials createBaseRestoreWalletFromSeedCredentials( + {required String name, + required String mnemonic, + required String password, + String? passphrase}); + WalletCredentials createBaseRestoreWalletFromPrivateKey( + {required String name, required String privateKey, required String password}); + WalletCredentials createBaseHardwareWalletCredentials( + {required String name, required HardwareAccountData hwAccountData, WalletInfo? walletInfo}); + String getAddress(WalletBase wallet); + String getPrivateKey(WalletBase wallet); + String getPublicKey(WalletBase wallet); + TransactionPriority getDefaultTransactionPriority(); + TransactionPriority getBaseTransactionPrioritySlow(); + List getTransactionPriorities(); + TransactionPriority deserializeBaseTransactionPriority(int raw); + + Object createBaseTransactionCredentials( + List outputs, { + required TransactionPriority priority, + required CryptoCurrency currency, + int? feeRate, + }); + + Object createBaseTransactionCredentialsRaw( + List outputs, { + TransactionPriority? priority, + required CryptoCurrency currency, + required int feeRate, + }); + + int formatterBaseParseAmount(String amount); + double formatterBaseAmountToDouble( + {TransactionInfo? transaction, BigInt? amount, int exponent = 18}); + List getERC20Currencies(WalletBase wallet); + Future addErc20Token(WalletBase wallet, CryptoCurrency token); + Future deleteErc20Token(WalletBase wallet, CryptoCurrency token); + Future removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token); + Future getErc20Token(WalletBase wallet, String contractAddress); + + Future createTokenApproval(WalletBase wallet, BigInt amount, String spender, + CryptoCurrency token, TransactionPriority priority); + + CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction); + void updateBaseScanUsageState(WalletBase wallet, bool isEnabled); + Web3Client? getWeb3Client(WalletBase wallet); + String getTokenAddress(CryptoCurrency asset); + + void setHardwareWalletService(WalletBase wallet, HardwareWalletService service); + HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection); + HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager); + List getDefaultTokenContractAddresses(); + bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress); +} + """; + + const baseEmptyDefinition = 'Base? base;\n'; + const baseCWDefinition = 'Base? base = CWBase();\n'; + + final output = '$baseCommonHeaders\n' + + (hasImplementation ? '$baseCWHeaders\n' : '\n') + + (hasImplementation ? '$baseCwPart\n\n' : '\n') + + (hasImplementation ? baseCWDefinition : baseEmptyDefinition) + + '\n' + + baseContent; + + if (outputFile.existsSync()) { + await outputFile.delete(); + } + + await outputFile.writeAsString(output); +} + Future generatePubspec({ required bool hasMonero, required bool hasBitcoin, @@ -1512,6 +1643,7 @@ Future generatePubspec({ required bool hasZano, required bool hasDecred, required bool hasDogecoin, + required bool hasBase, }) async { const cwCore = """ cw_core: @@ -1580,6 +1712,11 @@ Future generatePubspec({ cw_dogecoin: path: ./cw_dogecoin """; + const cwBase = """ + cw_base: + path: ./cw_base + """; + final inputFile = File(pubspecOutputPath); final inputText = await inputFile.readAsString(); final inputLines = inputText.split('\n'); @@ -1649,6 +1786,10 @@ Future generatePubspec({ output += '\n$cwDogecoin'; } + if (hasBase) { + output += '\n$cwBase'; + } + final outputLines = output.split('\n'); inputLines.insertAll(dependenciesIndex + 1, outputLines); final outputContent = inputLines.join('\n'); @@ -1675,6 +1816,7 @@ Future generateWalletTypes({ required bool hasZano, required bool hasDecred, required bool hasDogecoin, + required bool hasBase, }) async { final walletTypesFile = File(walletTypesPath); @@ -1706,6 +1848,10 @@ Future generateWalletTypes({ outputContent += '\tWalletType.dogecoin,\n'; } + if (hasBase) { + outputContent += '\tWalletType.base,\n'; + } + if (hasBitcoinCash) { outputContent += '\tWalletType.bitcoinCash,\n'; } diff --git a/tool/utils/secret_key.dart b/tool/utils/secret_key.dart index 3bd227506..6a009ff36 100644 --- a/tool/utils/secret_key.dart +++ b/tool/utils/secret_key.dart @@ -55,8 +55,8 @@ class SecretKey { SecretKey('litecoinTestWalletSeeds', () => ''), SecretKey('bitcoinCashTestWalletSeeds', () => ''), SecretKey('polygonTestWalletSeeds', () => ''), + SecretKey('baseTestWalletSeeds', () => ''), SecretKey('solanaTestWalletSeeds', () => ''), - SecretKey('polygonTestWalletSeeds', () => ''), SecretKey('tronTestWalletSeeds', () => ''), SecretKey('nanoTestWalletSeeds', () => ''), SecretKey('wowneroTestWalletSeeds', () => ''), @@ -66,6 +66,7 @@ class SecretKey { SecretKey('litecoinTestWalletReceiveAddress', () => ''), SecretKey('bitcoinCashTestWalletReceiveAddress', () => ''), SecretKey('polygonTestWalletReceiveAddress', () => ''), + SecretKey('baseTestWalletReceiveAddress', () => ''), SecretKey('solanaTestWalletReceiveAddress', () => ''), SecretKey('tronTestWalletReceiveAddress', () => ''), SecretKey('nanoTestWalletReceiveAddress', () => ''),