From 2a74317aff2357a80494f88e2642d1e8383f2e8a Mon Sep 17 00:00:00 2001 From: David Adegoke <64401859+Blazebrain@users.noreply.github.com> Date: Sat, 11 Oct 2025 02:24:32 +0100 Subject: [PATCH] Add Base Wallet (#2558) * chore: Create Base package template * chore: Update configuration scripts and dependencies to include 'base' coin support * chore: Update pubspec.yaml, adjust SDK constraints, and add new dependencies * feat: Add support for base wallet type and cryptocurrency in related configurations * feat: Implement BaseWallet and DefaultBaseTokens classes for managing base cryptocurrency transactions * feat: Add BaseTransactionHistory and BaseTransactionInfo classes for handling base cryptocurrency transaction data * feat: Implement BaseClient, BaseWalletService, and BaseMnemonicIsIncorrectException * feat: Setup base proxy for main package and adjust token class name * feat: Setup initial base node configuration * feat: Integrate support for base wallet type across various functions and services * feat: Add base support to exchange and outputs * feat: Add base wallet type integration to wallet creation and node management * feat: Add base support to transaction details and transaction list item * feat: Implement BaseURI class and add base support across wallet creation and keys view models * feat: Add support for BaseScan preference in settings store * feat: Integrate Base support into various workflows and documentation * feat: Addingbase config to windows and secret key support * feat: Add base support to settings and address handling * chore: Move walletTypeToCurrency to another file and adjust across codebase * feat: Add 'basescan_history' string localization * feat: Remove Cryptocurrency.base, it doesn't have a base currency * feat: Set eth as native currency * feat: Enhance Base support across various sending, exchange, transaction details, fees, dashboard and balance * feat: Integrate Base wallet support in advanced privacy settings and home settings view models * feat: Add Base wallet support with icon and integration across providers, default settings migration, preferences, wallet connect, settings and token utilities * feat: Update currency handling to use baseEth for Base wallet integration across various components * fix: Update default token symbol to ETH in Base wallet transaction model * refactor: Change default currency references from BASE to ETH across client, transaction info, and wallet components * feat: Introduce normalizedIconPath for CryptoCurrency to standardize icon retrieval across wallet components * fix: Include baseEth as a native token in EVMChainClient for currency handling * chore: Update base_icon.png * feat: Add more nodes to base_node_list.yml * refactor: Update wallet icon retrieval to use normalizedIconPath * chore: Remove unused cases and cleanup impl. * feat: Add base proxy generation to configure * refactor: Update base proxy and generation code in configure.dart * feat: Add missing wallet and mono images * fix: Wrong image path for pr and add missing base qr image * feat: Enhance EVMTransactionErrorFeesHandler to parse new insufficient funds error format and calculate transaction fees in ETH and USD * feat: Add gas price safety buffer for Base chain transactions to improve fee estimation accuracy, also fixes send all not calculating correctly * Update model_generator.sh [skip ci] * Update assets/base_node_list.yml [skip ci] * Update cw_base/lib/default_base_erc20_tokens.dart [skip ci] * Review fixes * fixes --------- Co-authored-by: OmarHatem --- .../workflows/automated_integration_test.yml | 2 + .github/workflows/no_restricted_imports.yaml | 1 + .github/workflows/pr_test_build_android.yml | 2 + .github/workflows/pr_test_build_linux.yml | 2 + .gitignore | 1 + README.md | 1 + analysis_options.yaml | 1 + android/app/src/main/AndroidManifestBase.xml | 3 + assets/base_node_list.yml | 15 ++ assets/images/base_chain_QR.svg | 5 + assets/images/base_chain_mono.svg | 3 + assets/images/base_icon.png | Bin 0 -> 11208 bytes cakewallet.bat | 4 +- cw_base/.gitignore | 31 +++ cw_base/.metadata | 10 + cw_base/CHANGELOG.md | 3 + cw_base/LICENSE | 1 + cw_base/README.md | 39 +++ cw_base/analysis_options.yaml | 4 + cw_base/lib/base_client.dart | 103 ++++++++ cw_base/lib/base_mnemonics_exception.dart | 5 + cw_base/lib/base_transaction_history.dart | 20 ++ cw_base/lib/base_transaction_info.dart | 41 +++ cw_base/lib/base_wallet.dart | 165 ++++++++++++ cw_base/lib/base_wallet_service.dart | 163 ++++++++++++ cw_base/lib/cw_base.dart | 5 + cw_base/lib/default_base_erc20_tokens.dart | 67 +++++ cw_base/pubspec.yaml | 73 ++++++ cw_base/test/cw_base_test.dart | 12 + cw_bitcoin/lib/electrum_transaction_info.dart | 1 + cw_core/lib/crypto_currency.dart | 2 + cw_core/lib/currency_for_wallet_type.dart | 50 +--- cw_core/lib/erc20_token.dart | 1 + cw_core/lib/node.dart | 2 + cw_core/lib/wallet_base.dart | 2 +- cw_core/lib/wallet_type.dart | 60 ++--- cw_decred/lib/transaction_info.dart | 1 + cw_evm/lib/evm_chain_client.dart | 24 +- cw_evm/lib/evm_chain_wallet.dart | 62 +++-- .../components/common_test_flows.dart | 7 +- ios/Runner/InfoBase.plist | 20 ++ lib/base/cw_base.dart | 238 ++++++++++++++++++ lib/buy/dfx/dfx_buy_provider.dart | 1 + lib/buy/kryptonim/kryptonim.dart | 2 + lib/buy/moonpay/moonpay_provider.dart | 1 + lib/buy/robinhood/robinhood_buy_provider.dart | 1 + lib/buy/wyre/wyre_buy_provider.dart | 1 + lib/core/payment_uris.dart | 15 ++ lib/core/seed_validator.dart | 3 + lib/core/wallet_creation_service.dart | 1 + lib/di.dart | 3 + lib/entities/default_settings_migration.dart | 21 ++ lib/entities/ens_record.dart | 6 + .../evm_transaction_error_fees_handler.dart | 108 +++++--- lib/entities/node_list.dart | 7 +- lib/entities/parse_address_from_domain.dart | 1 + lib/entities/preferences_key.dart | 3 + lib/entities/priority_for_wallet_type.dart | 3 + lib/reactions/fiat_rate_update.dart | 6 + lib/reactions/on_current_wallet_change.dart | 5 + lib/reactions/wallet_connect.dart | 14 +- lib/reactions/wallet_utils.dart | 2 + .../select_hardware_wallet_account_page.dart | 3 +- .../desktop_wallet_selection_dropdown.dart | 3 + .../dashboard/widgets/menu_widget.dart | 4 + .../screens/dashboard/widgets/sign_form.dart | 1 + .../dashboard/widgets/verify_form.dart | 1 + .../new_wallet/new_wallet_type_page.dart | 7 +- .../new_wallet/wallet_group_display_page.dart | 4 +- .../grouped_wallet_expansion_tile.dart | 5 +- lib/src/screens/settings/privacy_page.dart | 8 + .../chain_service/eth/evm_chain_id.dart | 4 + .../wallet_connect_key_service.dart | 6 + .../screens/wallet_list/wallet_list_page.dart | 1 + .../payment_confirmation_bottom_sheet.dart | 1 + .../swap_confirmation_bottom_sheet.dart | 11 +- .../wallet_switcher_bottom_sheet.dart | 3 +- lib/store/settings_store.dart | 49 ++++ lib/utils/qr_util.dart | 4 +- lib/utils/token_utilities.dart | 7 +- .../advanced_privacy_settings_view_model.dart | 2 + .../anon_invoice_page_view_model.dart | 1 + lib/view_model/buy/buy_view_model.dart | 1 + .../contact_list/contact_list_view_model.dart | 1 + .../dashboard/balance_view_model.dart | 1 + .../dashboard/dashboard_view_model.dart | 1 + .../dashboard/home_settings_view_model.dart | 59 ++++- .../dashboard/transaction_list_item.dart | 13 + .../exchange/exchange_trade_view_model.dart | 11 +- .../exchange/exchange_view_model.dart | 4 + .../node_create_or_edit_view_model.dart | 1 + .../restore/wallet_restore_from_qr_code.dart | 13 +- lib/view_model/send/fees_view_model.dart | 6 + lib/view_model/send/output.dart | 9 + .../send/send_template_view_model.dart | 2 + lib/view_model/send/send_view_model.dart | 7 + .../settings/privacy_settings_view_model.dart | 12 + .../transaction_details_view_model.dart | 58 +++++ .../wallet_address_list_view_model.dart | 20 +- lib/view_model/wallet_keys_view_model.dart | 3 + lib/view_model/wallet_new_vm.dart | 8 + lib/view_model/wallet_restore_view_model.dart | 16 ++ model_generator.sh | 2 +- pubspec_base.yaml | 1 + res/values/strings_ar.arb | 1 + res/values/strings_bg.arb | 1 + res/values/strings_cs.arb | 1 + res/values/strings_de.arb | 1 + res/values/strings_en.arb | 1 + res/values/strings_es.arb | 1 + res/values/strings_fr.arb | 1 + res/values/strings_ha.arb | 1 + res/values/strings_hi.arb | 1 + res/values/strings_hr.arb | 1 + res/values/strings_hy.arb | 1 + res/values/strings_id.arb | 1 + res/values/strings_it.arb | 1 + res/values/strings_ja.arb | 1 + res/values/strings_ko.arb | 1 + res/values/strings_my.arb | 1 + res/values/strings_nl.arb | 1 + res/values/strings_pl.arb | 1 + res/values/strings_pt.arb | 1 + res/values/strings_ru.arb | 1 + res/values/strings_th.arb | 1 + res/values/strings_tl.arb | 1 + res/values/strings_tr.arb | 1 + res/values/strings_uk.arb | 1 + res/values/strings_ur.arb | 1 + res/values/strings_vi.arb | 1 + res/values/strings_yo.arb | 1 + res/values/strings_zh.arb | 1 + scripts/android/pubspec_gen.sh | 2 +- scripts/build_bitbox_flutter.sh | 2 +- scripts/ios/app_config.sh | 2 +- scripts/linux/app_config.sh | 2 +- scripts/macos/app_config.sh | 2 +- tool/configure.dart | 146 +++++++++++ tool/utils/secret_key.dart | 3 +- 139 files changed, 1824 insertions(+), 193 deletions(-) create mode 100644 assets/base_node_list.yml create mode 100644 assets/images/base_chain_QR.svg create mode 100644 assets/images/base_chain_mono.svg create mode 100644 assets/images/base_icon.png create mode 100644 cw_base/.gitignore create mode 100644 cw_base/.metadata create mode 100644 cw_base/CHANGELOG.md create mode 100644 cw_base/LICENSE create mode 100644 cw_base/README.md create mode 100644 cw_base/analysis_options.yaml create mode 100644 cw_base/lib/base_client.dart create mode 100644 cw_base/lib/base_mnemonics_exception.dart create mode 100644 cw_base/lib/base_transaction_history.dart create mode 100644 cw_base/lib/base_transaction_info.dart create mode 100644 cw_base/lib/base_wallet.dart create mode 100644 cw_base/lib/base_wallet_service.dart create mode 100644 cw_base/lib/cw_base.dart create mode 100644 cw_base/lib/default_base_erc20_tokens.dart create mode 100644 cw_base/pubspec.yaml create mode 100644 cw_base/test/cw_base_test.dart create mode 100644 lib/base/cw_base.dart 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 0000000000000000000000000000000000000000..d7cb8edadf9e1bbce4d6b3d776012c45ec5fb4c9 GIT binary patch literal 11208 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 literal 0 HcmV?d00001 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', () => ''),