CW-1194-Add-Arbitrum-Wallet (#2587)

* feat: initialize cw_arbitrum package with essential files - Implement ArbitrumClient for transaction handling - Add ArbitrumWallet and related classes for wallet management - Establish transaction history and info classes for Arbitrum transactions - Add ArbitrumWalletService for wallet operations and management * feat: Add arbitrum related secrets to workflow * feat: Add nodes for arbitrum * feat: Add Arbitrum support to cryptocurrency handling * refactor: Update Arbitrum currency references from 'ARB' to 'ETH' * feat: Add Arbitrum support to wallet type and transaction handling * feat: Add Arbitrum URL schemes to Android and iOS configurations * feat: Add Arbitrum SVG icons and a WebP image * feat: Setup Arbitrum proxy * fix: Add localization * feat: Integrate Arbitrum support across various configurations and files - Updated cakewallet.bat to include Arbitrum in app configuration. - Added Arbitrum node list to pubspec_base.yaml. - Modified Android, iOS, Linux, and macOS app configuration scripts to support Arbitrum. - Introduced Arbitrum output path and generation logic in configure.dart. - Enhanced wallet type and pubspec generation to include Arbitrum. - Added Arbitrum-related secrets in secret_key.dart. * feat: Enhance Arbitrum integration with new URI class and updates across various components - Introduced ArbitrumURI class for handling Arbitrum payment URIs. - Updated seed validator, wallet creation service, and other components to support Arbitrum. - Added Arbitrum node handling in default settings migration and node list management. - Enhanced preferences and settings store to include Arbitrum-specific configurations. - Updated QR utility and token utilities to accommodate Arbitrum functionalities. * feat: Add Arbitrum support to various buy providers - Updated DFXBuyProvider, KryptonimBuyProvider, OnRamperBuyProvider, and RobinhoodBuyProvider to include Arbitrum wallet type and blockchain references. - Enhanced integration for Arbitrum across multiple components to ensure consistent support. * feat: Implement Arbitrum support in wallet selection and settings - Added Arbitrum wallet type handling in common test flows and wallet services. - Integrated Arbitrum icons in desktop wallet selection and menu widgets. - Updated privacy settings to include ArbiScan options. - Enhanced key service to support Arbitrum private and public key retrieval. * feat: Expand Arbitrum support across various view models and components - Integrated Arbitrum wallet type handling in advanced privacy settings, transaction details, and wallet management. - Added Arbitrum-specific methods for transaction and wallet creation, restoration, and fee management. - Updated dashboard and home settings to accommodate Arbitrum functionalities, including token management and transaction display. - Enhanced exchange and send view models to support Arbitrum transactions and currency handling. - Included Arbitrum in wallet address list and privacy settings for improved user experience. * refactor: Simplify token initialization in Arbitrum wallet service - Removed the migration flag from the addInitialTokens method in ArbitrumWalletService. - Updated addInitialTokens method in ArbitrumWallet to handle existing tokens more efficiently. - Enhanced initial token retrieval logic in DefaultArbitrumErc20Tokens to prioritize iconPath assignment. - Improved token update logic in BaseWallet for consistency across wallet services. * chore: Update Dart SDK constraints in pubspec.yaml * fix: Update file paths for arbitrum assets * fix: Correct box name formatting in Arbitrum wallet initialization * Update .github/workflows/pr_test_build_android.yml [skip ci * Update .github/workflows/pr_test_build_android.yml * fix: Add missing evm params * fix: Remove duplicate method declaration * refactor: simplify isSwapsXyzSendingEVMTokenSwap check * fix: Update arbitrum configs with sql storage update * feat: Add estimated fee retrieval for Arbitrum and revamp priority fee handling for EVMChainWallet * refactor: Update transaction priority handling and estimated fee calculations across EVMChain chains, also remove priority fee levels for Arbitrum --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

David Adegoke committed Nov 6, 2025 at 00:30 UTC 2da3ba5730c020f9908a831611c365d66e9e3a43
129 files changed +2115 -311
.github/workflows/automated_integration_test.yml
+2
@@ -164,6 +164,7 @@ jobs:
164 echo "const bitcoinCashTestWalletSeeds = '${{ secrets.BITCOIN_CASH_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
165 echo "const polygonTestWalletSeeds = '${{ secrets.POLYGON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
166 echo "const baseTestWalletSeeds = '${{ secrets.BASE_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
167 + echo "const arbitrumTestWalletSeeds = '${{ secrets.ARBITRUM_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
168 echo "const solanaTestWalletSeeds = '${{ secrets.SOLANA_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
169 echo "const solanaTestWalletSeeds2 = '${{ secrets.SOLANA_TEST_WALLET_SEEDS2 }}';" >> lib/.secrets.g.dart
170 echo "const tronTestWalletSeeds = '${{ secrets.TRON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
@@ -178,6 +179,7 @@ jobs:
179 echo "const bitcoinCashTestWalletReceiveAddress = '${{ secrets.BITCOIN_CASH_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
180 echo "const polygonTestWalletReceiveAddress = '${{ secrets.POLYGON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
181 echo "const baseTestWalletReceiveAddress = '${{ secrets.BASE_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
182 + echo "const arbitrumTestWalletReceiveAddress = '${{ secrets.ARBITRUM_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
183 echo "const solanaTestWalletReceiveAddress = '${{ secrets.SOLANA_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
184 echo "const tronTestWalletReceiveAddress = '${{ secrets.TRON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
185 echo "const nanoTestWalletReceiveAddress = '${{ secrets.NANO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
.github/workflows/no_restricted_imports.yaml
+1
@@ -21,6 +21,7 @@ jobs:
21 "cw_nano"
22 "cw_polygon"
23 "cw_base"
24 + "cw_arbitrum"
25 "cw_solana"
26 "cw_tron"
27 "cw_wownero"
.github/workflows/pr_test_build_android.yml
+2
@@ -158,6 +158,7 @@ jobs:
158 echo "const bitcoinCashTestWalletSeeds = '${{ secrets.BITCOIN_CASH_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
159 echo "const polygonTestWalletSeeds = '${{ secrets.POLYGON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
160 echo "const baseTestWalletSeeds = '${{ secrets.BASE_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
161 + echo "const arbitrumTestWalletSeeds = '${{ secrets.ARBITRUM_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
162 echo "const solanaTestWalletSeeds = '${{ secrets.SOLANA_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
163 echo "const tronTestWalletSeeds = '${{ secrets.TRON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
164 echo "const nanoTestWalletSeeds = '${{ secrets.NANO_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
@@ -170,6 +171,7 @@ jobs:
171 echo "const bitcoinCashTestWalletReceiveAddress = '${{ secrets.BITCOIN_CASH_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
172 echo "const polygonTestWalletReceiveAddress = '${{ secrets.POLYGON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
173 echo "const baseTestWalletReceiveAddress = '${{ secrets.BASE_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
174 + echo "const arbitrumTestWalletReceiveAddress = '${{ secrets.ARBITRUM_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
175 echo "const solanaTestWalletReceiveAddress = '${{ secrets.SOLANA_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
176 echo "const tronTestWalletReceiveAddress = '${{ secrets.TRON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
177 echo "const nanoTestWalletReceiveAddress = '${{ secrets.NANO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
.github/workflows/pr_test_build_linux.yml
+2
@@ -151,6 +151,7 @@ jobs:
151 echo "const bitcoinCashTestWalletSeeds = '${{ secrets.BITCOIN_CASH_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
152 echo "const polygonTestWalletSeeds = '${{ secrets.POLYGON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
153 echo "const baseTestWalletSeeds = '${{ secrets.BASE_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
154 + echo "const arbitrumTestWalletSeeds = '${{ secrets.ARBITRUM_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
155 echo "const solanaTestWalletSeeds = '${{ secrets.SOLANA_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
156 echo "const tronTestWalletSeeds = '${{ secrets.TRON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
157 echo "const nanoTestWalletSeeds = '${{ secrets.NANO_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
@@ -164,6 +165,7 @@ jobs:
165 echo "const bitcoinCashTestWalletReceiveAddress = '${{ secrets.BITCOIN_CASH_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
166 echo "const polygonTestWalletReceiveAddress = '${{ secrets.POLYGON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
167 echo "const baseTestWalletReceiveAddress = '${{ secrets.BASE_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
168 + echo "const arbitrumTestWalletReceiveAddress = '${{ secrets.ARBITRUM_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
169 echo "const solanaTestWalletReceiveAddress = '${{ secrets.SOLANA_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
170 echo "const tronTestWalletReceiveAddress = '${{ secrets.TRON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
171 echo "const nanoTestWalletReceiveAddress = '${{ secrets.NANO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
.gitignore
+1
@@ -142,6 +142,7 @@ lib/zano/zano.dart
142 lib/decred/decred.dart
143 lib/dogecoin/dogecoin.dart
144 lib/base/base.dart
145 +lib/arbitrum/arbitrum.dart
146
147 ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@2x.png
148 ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@2x~ipad.png
README.md
+1
@@ -26,6 +26,7 @@ Cake Wallet includes support for several cryptocurrencies, including:
26 * Bitcoin Cash (BCH)
27 * Polygon (POL)
28 * Base (BASE)
29 +* Arbitrum (ARB)
30 * Solana (SOL)
31 * Tron (TRX)
32 * Nano (XNO)
analysis_options.yaml
+1
@@ -25,6 +25,7 @@ analyzer:
25 lib/wownero/cw_wownero.dart,
26 lib/zano/cw_zano.dart,
27 lib/base/cw_base.dart,
28 + lib/arbitrum/cw_arbitrum.dart,
29 ]
30 language:
31 strict-casts: true
android/app/src/main/AndroidManifestBase.xml
+3
@@ -109,6 +109,9 @@
109 <data android:scheme="base" />
110 <data android:scheme="base-wallet" />
111 <data android:scheme="base_wallet" />
112 + <data android:scheme="arbitrum" />
113 + <data android:scheme="arbitrum-wallet" />
114 + <data android:scheme="arbitrum_wallet" />
115 </intent-filter>
116 <!-- nano-gpt link scheme -->
117 <intent-filter android:autoVerify="true">
assets/arbitrum_node_list.yml new
+12
@@ -0,0 +1,12 @@
1 +-
2 + uri: arbitrum.nownodes.io
3 + is_default: true
4 + useSSL: true
5 + isEnabledForAutoSwitching: true
6 +-
7 + uri: arbitrum.drpc.org
8 + useSSL: true
9 +-
10 + uri: arbitrum-one-rpc.publicnode.com
11 + useSSL: true
12 + isEnabledForAutoSwitching: true
\ No newline at end of file
assets/images/arbitrum_chain_QR.svg new
+27
@@ -0,0 +1,27 @@
1 +<svg width="45" height="45" viewBox="0 0 45 45" fill="none" xmlns="http://www.w3.org/2000/svg">
2 +<rect x="1" y="1" width="43" height="43" rx="6.03125" fill="white"/>
3 +<rect x="1" y="1" width="43" height="43" rx="6.03125" stroke="black" stroke-width="2"/>
4 +<g clip-path="url(#clip0_10599_14372)">
5 +<g clip-path="url(#clip1_10599_14372)">
6 +<mask id="mask0_10599_14372" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="8" y="6" width="29" height="33">
7 +<path d="M36.6426 6.52493H8.0957V38.0249H36.6426V6.52493Z" fill="white"/>
8 +</mask>
9 +<g mask="url(#mask0_10599_14372)">
10 +<path d="M24.7616 24.6685L23.1998 28.85C23.1538 28.966 23.1538 29.0948 23.1998 29.2108L25.8819 36.4077L28.9857 34.6552L25.2625 24.6685C25.1768 24.4366 24.8473 24.4366 24.7616 24.6685Z" fill="black"/>
11 +<path d="M27.8845 17.6328C27.7989 17.4009 27.4694 17.4009 27.3838 17.6328L25.822 21.8144C25.7758 21.9304 25.7758 22.0593 25.822 22.1752L30.2173 33.9596L33.3211 32.207L27.8845 17.6328Z" fill="black"/>
12 +<path d="M22.3691 8.47717C22.4482 8.47717 22.5207 8.4965 22.5932 8.53516L34.4284 15.2166C34.5668 15.2939 34.6525 15.4357 34.6525 15.5903V28.9531C34.6525 29.1078 34.5668 29.2495 34.4284 29.3268L22.5932 36.0147C22.5273 36.0533 22.4482 36.0727 22.3691 36.0727C22.2901 36.0727 22.2175 36.0533 22.1451 36.0147L10.3165 29.3333C10.1781 29.2559 10.0924 29.1142 10.0924 28.9596V15.5903C10.0924 15.4357 10.1781 15.2939 10.3165 15.2166L22.1517 8.53516C22.2175 8.4965 22.2967 8.47717 22.3691 8.47717ZM22.3691 6.52493C21.9474 6.52493 21.5256 6.63446 21.15 6.84708L9.31481 13.5285C8.56358 13.9538 8.0957 14.7398 8.0957 15.5903V28.9531C8.0957 29.8036 8.56358 30.5961 9.31481 31.0214L21.15 37.7027C21.5256 37.9154 21.9474 38.0249 22.3691 38.0249C22.7908 38.0249 23.2127 37.9154 23.5883 37.7027L35.4234 31.0214C36.1813 30.5961 36.6426 29.81 36.6426 28.9531V15.5903C36.6426 14.7398 36.1747 13.9473 35.4234 13.5221L23.5949 6.84708C23.2127 6.63446 22.7908 6.52493 22.3691 6.52493Z" fill="black"/>
13 +<path d="M14.5483 33.9725L15.6357 31.0603L17.8235 32.8386L15.7806 34.6683L14.5483 33.9725Z" fill="black"/>
14 +<path d="M21.374 14.6368H18.3757C18.1517 14.6368 17.9473 14.7721 17.8748 14.9783L11.4432 32.2199L14.547 33.9723L21.631 14.9848C21.6903 14.8172 21.5651 14.6368 21.374 14.6368Z" fill="black"/>
15 +<path d="M26.6255 14.6368H23.6272C23.4032 14.6368 23.1988 14.7721 23.1264 14.9783L15.7788 34.6682L18.8826 36.4208L26.876 14.9912C26.9418 14.8172 26.8101 14.6368 26.6255 14.6368Z" fill="black"/>
16 +</g>
17 +</g>
18 +</g>
19 +<defs>
20 +<clipPath id="clip0_10599_14372">
21 +<rect width="31.5" height="31.5" fill="white" transform="translate(6.52502 6.52499)"/>
22 +</clipPath>
23 +<clipPath id="clip1_10599_14372">
24 +<rect width="28.5469" height="31.5" fill="white" transform="translate(8.1001 6.52496)"/>
25 +</clipPath>
26 +</defs>
27 +</svg>
assets/images/arbitrum_chain_mono.svg new
+3
@@ -0,0 +1,3 @@
1 +<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
2 +<path fill-rule="evenodd" clip-rule="evenodd" d="M16.0956 0C16.524 6.76387e-06 16.9526 0.111199 17.3409 0.327187L29.3571 7.10812C30.1203 7.54011 30.5955 8.34516 30.5956 9.20906V22.7841C30.5956 23.6545 30.127 24.4533 29.3571 24.8853L17.334 31.6725C16.9524 31.8885 16.524 32 16.0956 32C15.6672 32 15.2387 31.8885 14.8571 31.6725L2.83402 24.8853C2.07086 24.4533 1.59558 23.6481 1.59558 22.7841V9.20906C1.59562 8.34513 2.07089 7.54666 2.83402 7.11469L14.8571 0.327187C15.2387 0.111222 15.6672 0 16.0956 0ZM16.0956 1.98312C16.022 1.98312 15.9415 2.00291 15.8746 2.04219L3.85152 8.82969C3.71102 8.90822 3.62406 9.05205 3.62402 9.20906V22.7906C3.62402 22.9476 3.71095 23.0917 3.85152 23.1703L5.70027 24.2144L11.53 8.5875C11.6036 8.37808 11.8111 8.24063 12.0387 8.24062H15.0846C15.2787 8.24064 15.4058 8.42389 15.3456 8.59406L8.85339 25.9953L8.85496 25.9962L9.25527 24.9247L10.4159 25.8681L16.8646 8.5875C16.9382 8.37805 17.1461 8.24062 17.3737 8.24062H20.4196C20.607 8.24072 20.7409 8.42397 20.674 8.60062L13.2575 28.4831L15.8681 29.9578C15.9416 29.997 16.0153 30.0169 16.0956 30.0169C16.1759 30.0169 16.2562 29.997 16.3231 29.9578L18.9596 28.4675L16.9393 23.0459C16.8925 22.9281 16.8925 22.7972 16.9393 22.6794L18.5259 18.4316C18.6129 18.196 18.9475 18.196 19.0346 18.4316L22.1121 26.6862L23.3631 25.9794L19.6031 15.8987C19.5562 15.781 19.5562 15.65 19.6031 15.5322L21.1896 11.2841C21.2766 11.0485 21.6113 11.0486 21.6984 11.2841L26.5156 24.1978L28.3462 23.1637C28.4868 23.0852 28.5737 22.9412 28.5737 22.7841V9.20906C28.5737 9.05203 28.4868 8.90821 28.3462 8.82969L16.3231 2.04219C16.2495 2.00291 16.1759 1.98313 16.0956 1.98312Z" fill="white"/>
3 +</svg>
assets/images/crypto/arbitrum.webp
Binary files /dev/null and b/assets/images/crypto/arbitrum.webp differ
cakewallet.bat
+2 -2
@@ -1,5 +1,5 @@
1 @echo off
2 -set cw_win_app_config=--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --dogecoin --base
2 +set cw_win_app_config=--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --dogecoin --base --arbitrum
3 set cw_root=%cd%
4 set cw_archive_name=Cake Wallet.zip
5 set cw_archive_path=%cw_root%\%cw_archive_name%
@@ -24,7 +24,7 @@ IF NOT EXIST "%secrets_file_path%" (
24 ) ELSE (echo === Using previously/already generated secrets file: %secrets_file_path% ===)
25
26 echo === Generating mobx models ===
27 -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 (
27 +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 cw_arbitrum.) do (
28 cd %%i
29 call flutter pub get > nul
30 call dart run build_runner build --delete-conflicting-outputs > nul
cw_arbitrum/.gitignore new
+31
@@ -0,0 +1,31 @@
1 +# Miscellaneous
2 +*.class
3 +*.log
4 +*.pyc
5 +*.swp
6 +.DS_Store
7 +.atom/
8 +.buildlog/
9 +.history
10 +.svn/
11 +migrate_working_dir/
12 +
13 +# IntelliJ related
14 +*.iml
15 +*.ipr
16 +*.iws
17 +.idea/
18 +
19 +# The .vscode folder contains launch configuration and tasks you configure in
20 +# VS Code which you may wish to be included in version control, so this line
21 +# is commented out by default.
22 +#.vscode/
23 +
24 +# Flutter/Dart/Pub related
25 +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
26 +/pubspec.lock
27 +**/doc/api/
28 +.dart_tool/
29 +.flutter-plugins
30 +.flutter-plugins-dependencies
31 +build/
cw_arbitrum/.metadata new
+10
@@ -0,0 +1,10 @@
1 +# This file tracks properties of this Flutter project.
2 +# Used by Flutter tool to assess capabilities and perform upgrades etc.
3 +#
4 +# This file should be version controlled and should not be manually edited.
5 +
6 +version:
7 + revision: "fcf2c11572af6f390246c056bc905eca609533a0"
8 + channel: "[user-branch]"
9 +
10 +project_type: package
cw_arbitrum/CHANGELOG.md new
+3
@@ -0,0 +1,3 @@
1 +## 0.0.1
2 +
3 +* TODO: Describe initial release.
cw_arbitrum/LICENSE new
+1
@@ -0,0 +1 @@
1 +TODO: Add your license here.
cw_arbitrum/README.md new
+39
@@ -0,0 +1,39 @@
1 +<!--
2 +This README describes the package. If you publish this package to pub.dev,
3 +this README's contents appear on the landing page for your package.
4 +
5 +For information about how to write a good package README, see the guide for
6 +[writing package pages](https://dart.dev/tools/pub/writing-package-pages).
7 +
8 +For general information about developing packages, see the Dart guide for
9 +[creating packages](https://dart.dev/guides/libraries/create-packages)
10 +and the Flutter guide for
11 +[developing packages and plugins](https://flutter.dev/to/develop-packages).
12 +-->
13 +
14 +TODO: Put a short description of the package here that helps potential users
15 +know whether this package might be useful for them.
16 +
17 +## Features
18 +
19 +TODO: List what your package can do. Maybe include images, gifs, or videos.
20 +
21 +## Getting started
22 +
23 +TODO: List prerequisites and provide or point to information on how to
24 +start using the package.
25 +
26 +## Usage
27 +
28 +TODO: Include short and useful examples for package users. Add longer examples
29 +to `/example` folder.
30 +
31 +```dart
32 +const like = 'sample';
33 +```
34 +
35 +## Additional information
36 +
37 +TODO: Tell users more about the package: where to find more information, how to
38 +contribute to the package, how to file issues, what response they can expect
39 +from the package authors, and more.
cw_arbitrum/analysis_options.yaml new
+4
@@ -0,0 +1,4 @@
1 +include: package:flutter_lints/flutter.yaml
2 +
3 +# Additional information about this file can be found at
4 +# https://dart.dev/guides/language/analysis-options
cw_arbitrum/lib/arbirtrum_mnemonics_exception.dart new
+5
@@ -0,0 +1,5 @@
1 +class ArbitrumMnemonicIsIncorrectException implements Exception {
2 + @override
3 + String toString() =>
4 + 'Arbitrum mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.';
5 +}
cw_arbitrum/lib/arbitrum_client.dart new
+107
@@ -0,0 +1,107 @@
1 +import 'dart:convert';
2 +
3 +import 'package:cw_evm/.secrets.g.dart' as secrets;
4 +import 'package:cw_evm/evm_chain_client.dart';
5 +import 'package:cw_evm/evm_chain_transaction_model.dart';
6 +import 'package:flutter/foundation.dart';
7 +import 'package:web3dart/web3dart.dart';
8 +
9 +class ArbitrumClient extends EVMChainClient {
10 + @override
11 + Transaction createTransaction({
12 + required EthereumAddress from,
13 + required EthereumAddress to,
14 + required EtherAmount amount,
15 + EtherAmount? maxPriorityFeePerGas,
16 + Uint8List? data,
17 + int? maxGas,
18 + EtherAmount? gasPrice,
19 + EtherAmount? maxFeePerGas,
20 + }) {
21 + EtherAmount? finalGasPrice = gasPrice;
22 +
23 + if (gasPrice == null && maxFeePerGas != null) {
24 + // If we have EIP-1559 parameters but no legacy gasPrice, then use maxFeePerGas as gasPrice
25 + finalGasPrice = maxFeePerGas;
26 + }
27 +
28 + return Transaction(
29 + from: from,
30 + to: to,
31 + value: amount,
32 + data: data,
33 + maxGas: maxGas,
34 + gasPrice: finalGasPrice,
35 + // maxFeePerGas: maxFeePerGas,
36 + // maxPriorityFeePerGas: maxPriorityFeePerGas,
37 + );
38 + }
39 +
40 + @override
41 + Uint8List prepareSignedTransactionForSending(Uint8List signedTransaction) => signedTransaction;
42 +
43 + @override
44 + int get chainId => 42161;
45 +
46 + @override
47 + Future<List<EVMChainTransactionModel>> fetchTransactions(
48 + String address, {
49 + String? contractAddress,
50 + }) async {
51 + try {
52 + final response = await client.get(
53 + Uri.https("api.etherscan.io", "/v2/api", {
54 + "chainid": "$chainId",
55 + "module": "account",
56 + "action": contractAddress != null ? "tokentx" : "txlist",
57 + if (contractAddress != null) "contractaddress": contractAddress,
58 + "address": address,
59 + "apikey": secrets.etherScanApiKey,
60 + }),
61 + );
62 +
63 + final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
64 +
65 + if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) {
66 + final res = (jsonResponse['result'] as List);
67 +
68 + res.removeWhere((e) => e['value'] == '0');
69 +
70 + return res
71 + .map((e) => EVMChainTransactionModel.fromJson(e as Map<String, dynamic>, 'ETH'))
72 + .toList();
73 + }
74 +
75 + return [];
76 + } catch (e) {
77 + return [];
78 + }
79 + }
80 +
81 + @override
82 + Future<List<EVMChainTransactionModel>> fetchInternalTransactions(String address) async {
83 + try {
84 + final response = await client.get(
85 + Uri.https("api.etherscan.io", "/v2/api", {
86 + "chainid": "$chainId",
87 + "module": "account",
88 + "action": "txlistinternal",
89 + "address": address,
90 + "apikey": secrets.etherScanApiKey,
91 + }),
92 + );
93 +
94 + final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
95 +
96 + if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) {
97 + return (jsonResponse['result'] as List)
98 + .map((e) => EVMChainTransactionModel.fromJson(e as Map<String, dynamic>, 'ETH'))
99 + .toList();
100 + }
101 +
102 + return [];
103 + } catch (_) {
104 + return [];
105 + }
106 + }
107 +}
cw_arbitrum/lib/arbitrum_transaction_history.dart new
+20
@@ -0,0 +1,20 @@
1 +import 'dart:core';
2 +
3 +import 'package:cw_evm/evm_chain_transaction_history.dart';
4 +import 'package:cw_evm/evm_chain_transaction_info.dart';
5 +import 'package:cw_arbitrum/arbitrum_transaction_info.dart';
6 +
7 +class ArbitrumTransactionHistory extends EVMChainTransactionHistory {
8 + ArbitrumTransactionHistory({
9 + required super.walletInfo,
10 + required super.password,
11 + required super.encryptionFileUtils,
12 + });
13 +
14 + @override
15 + String getTransactionHistoryFileName() => 'arbitrum_transactions.json';
16 +
17 + @override
18 + EVMChainTransactionInfo getTransactionInfo(Map<String, dynamic> val) =>
19 + ArbitrumTransactionInfo.fromJson(val);
20 +}
cw_arbitrum/lib/arbitrum_transaction_info.dart new
+41
@@ -0,0 +1,41 @@
1 +import 'package:cw_core/transaction_direction.dart';
2 +import 'package:cw_evm/evm_chain_transaction_info.dart';
3 +
4 +class ArbitrumTransactionInfo extends EVMChainTransactionInfo {
5 + ArbitrumTransactionInfo({
6 + required super.id,
7 + required super.height,
8 + required super.ethAmount,
9 + required super.ethFee,
10 + required super.tokenSymbol,
11 + required super.direction,
12 + required super.isPending,
13 + required super.date,
14 + required super.confirmations,
15 + required super.to,
16 + required super.from,
17 + super.contractAddress,
18 + super.exponent,
19 + });
20 +
21 + factory ArbitrumTransactionInfo.fromJson(Map<String, dynamic> data) {
22 + return ArbitrumTransactionInfo(
23 + id: data['id'] as String,
24 + height: data['height'] as int,
25 + ethAmount: BigInt.parse(data['amount']),
26 + exponent: data['exponent'] as int,
27 + ethFee: BigInt.parse(data['fee']),
28 + direction: parseTransactionDirectionFromInt(data['direction'] as int),
29 + date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
30 + isPending: data['isPending'] as bool,
31 + confirmations: data['confirmations'] as int,
32 + tokenSymbol: data['tokenSymbol'] as String,
33 + to: data['to'],
34 + from: data['from'],
35 + contractAddress: data['contractAddress'],
36 + );
37 + }
38 +
39 + @override
40 + String get feeCurrency => 'ETH';
41 +}
cw_arbitrum/lib/arbitrum_wallet.dart new
+179
@@ -0,0 +1,179 @@
1 +import 'dart:convert';
2 +
3 +import 'package:cw_arbitrum/arbitrum_client.dart';
4 +import 'package:cw_arbitrum/arbitrum_transaction_history.dart';
5 +import 'package:cw_arbitrum/arbitrum_transaction_info.dart';
6 +import 'package:cw_arbitrum/default_arbitrum_erc20_tokens.dart';
7 +import 'package:cw_core/cake_hive.dart';
8 +import 'package:cw_core/crypto_currency.dart';
9 +import 'package:cw_core/encryption_file_utils.dart';
10 +import 'package:cw_core/erc20_token.dart';
11 +import 'package:cw_core/pathForWallet.dart';
12 +import 'package:cw_core/transaction_direction.dart';
13 +import 'package:cw_core/wallet_info.dart';
14 +import 'package:cw_core/wallet_keys_file.dart';
15 +import 'package:cw_evm/evm_chain_transaction_history.dart';
16 +import 'package:cw_evm/evm_chain_transaction_info.dart';
17 +import 'package:cw_evm/evm_chain_transaction_model.dart';
18 +import 'package:cw_evm/evm_chain_wallet.dart';
19 +import 'package:cw_evm/evm_erc20_balance.dart';
20 +
21 +class ArbitrumWallet extends EVMChainWallet {
22 + ArbitrumWallet({
23 + required super.walletInfo,
24 + required super.password,
25 + required super.derivationInfo,
26 + super.mnemonic,
27 + super.initialBalance,
28 + super.privateKey,
29 + required super.client,
30 + required super.encryptionFileUtils,
31 + super.passphrase,
32 + }) : super(nativeCurrency: CryptoCurrency.arbEth);
33 +
34 + @override
35 + bool get hasPriorityFee => false;
36 +
37 + @override
38 + Future<void> initErc20TokensBox() async {
39 + final boxName = "${walletInfo.name.replaceAll(" ", "_")}_${Erc20Token.arbitrumBoxName}";
40 +
41 + evmChainErc20TokensBox = await CakeHive.openBox<Erc20Token>(boxName);
42 + }
43 +
44 + @override
45 + void addInitialTokens() {
46 + final initialErc20Tokens = DefaultArbitrumErc20Tokens().initialArbitrumErc20Tokens;
47 +
48 + for (final token in initialErc20Tokens) {
49 + if (!evmChainErc20TokensBox.containsKey(token.contractAddress)) {
50 + evmChainErc20TokensBox.put(token.contractAddress, token);
51 + } else {
52 + // update existing token
53 + final existingToken = evmChainErc20TokensBox.get(token.contractAddress);
54 + evmChainErc20TokensBox.put(
55 + token.contractAddress,
56 + Erc20Token.copyWith(token, enabled: existingToken!.enabled),
57 + );
58 + }
59 + }
60 + }
61 +
62 + @override
63 + List<String> get getDefaultTokenContractAddresses => DefaultArbitrumErc20Tokens()
64 + .initialArbitrumErc20Tokens
65 + .map((e) => e.contractAddress)
66 + .toList();
67 +
68 + @override
69 + Future<bool> checkIfScanProviderIsEnabled() async {
70 + return (await sharedPrefs.future).getBool("use_arbiscan") ?? true;
71 + }
72 +
73 + @override
74 + String getTransactionHistoryFileName() => 'arbitrum_transactions.json';
75 +
76 + @override
77 + Erc20Token createNewErc20TokenObject(Erc20Token token, String? iconPath) {
78 + return Erc20Token(
79 + name: token.name,
80 + symbol: token.symbol,
81 + contractAddress: token.contractAddress,
82 + decimal: token.decimal,
83 + enabled: token.enabled,
84 + tag: token.tag ?? 'ETH',
85 + iconPath: iconPath,
86 + isPotentialScam: token.isPotentialScam,
87 + );
88 + }
89 +
90 + @override
91 + EVMChainTransactionInfo getTransactionInfo(
92 + EVMChainTransactionModel transactionModel,
93 + String address,
94 + ) {
95 + final model = ArbitrumTransactionInfo(
96 + id: transactionModel.hash,
97 + height: transactionModel.blockNumber,
98 + ethAmount: transactionModel.amount,
99 + direction: transactionModel.from == address
100 + ? TransactionDirection.outgoing
101 + : TransactionDirection.incoming,
102 + isPending: false,
103 + date: transactionModel.date,
104 + confirmations: transactionModel.confirmations,
105 + ethFee: BigInt.from(transactionModel.gasUsed) * transactionModel.gasPrice,
106 + exponent: transactionModel.tokenDecimal ?? 18,
107 + tokenSymbol: transactionModel.tokenSymbol ?? "ETH",
108 + to: transactionModel.to,
109 + from: transactionModel.from,
110 + contractAddress: transactionModel.contractAddress,
111 + );
112 + return model;
113 + }
114 +
115 + @override
116 + EVMChainTransactionHistory setUpTransactionHistory(
117 + WalletInfo walletInfo,
118 + String password,
119 + EncryptionFileUtils encryptionFileUtils,
120 + ) {
121 + return ArbitrumTransactionHistory(
122 + walletInfo: walletInfo,
123 + password: password,
124 + encryptionFileUtils: encryptionFileUtils,
125 + );
126 + }
127 +
128 + static Future<ArbitrumWallet> open({
129 + required String name,
130 + required String password,
131 + required WalletInfo walletInfo,
132 + required EncryptionFileUtils encryptionFileUtils,
133 + }) async {
134 + final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
135 + final path = await pathForWallet(name: name, type: walletInfo.type);
136 +
137 + Map<String, dynamic>? data;
138 + try {
139 + final jsonSource = await encryptionFileUtils.read(path: path, password: password);
140 +
141 + data = json.decode(jsonSource) as Map<String, dynamic>;
142 + } catch (e) {
143 + if (!hasKeysFile) rethrow;
144 + }
145 +
146 + final balance =
147 + EVMChainERC20Balance.fromJSON(data?['balance'] as String?) ??
148 + EVMChainERC20Balance(BigInt.zero);
149 +
150 + final WalletKeysData keysData;
151 + // Migrate wallet from the old scheme to then new .keys file scheme
152 + if (!hasKeysFile) {
153 + final mnemonic = data!['mnemonic'] as String?;
154 + final privateKey = data['private_key'] as String?;
155 + final passphrase = data['passphrase'] as String?;
156 +
157 + keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey, passphrase: passphrase);
158 + } else {
159 + keysData = await WalletKeysFile.readKeysFile(
160 + name,
161 + walletInfo.type,
162 + password,
163 + encryptionFileUtils,
164 + );
165 + }
166 +
167 + return ArbitrumWallet(
168 + walletInfo: walletInfo,
169 + derivationInfo: await walletInfo.getDerivationInfo(),
170 + password: password,
171 + mnemonic: keysData.mnemonic,
172 + privateKey: keysData.privateKey,
173 + passphrase: keysData.passphrase,
174 + initialBalance: balance,
175 + client: ArbitrumClient(),
176 + encryptionFileUtils: encryptionFileUtils,
177 + );
178 + }
179 +}
cw_arbitrum/lib/arbitrum_wallet_service.dart new
+174
@@ -0,0 +1,174 @@
1 +import 'package:bip39/bip39.dart' as bip39;
2 +import 'package:cw_arbitrum/arbirtrum_mnemonics_exception.dart';
3 +import 'package:cw_core/encryption_file_utils.dart';
4 +import 'package:cw_core/wallet_base.dart';
5 +import 'package:cw_core/wallet_info.dart';
6 +import 'package:cw_core/wallet_type.dart';
7 +import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
8 +import 'package:cw_evm/evm_chain_wallet_service.dart';
9 +import 'package:cw_arbitrum/arbitrum_wallet.dart';
10 +import 'package:cw_arbitrum/arbitrum_client.dart';
11 +
12 +class ArbitrumWalletService extends EVMChainWalletService<ArbitrumWallet> {
13 + ArbitrumWalletService(super.isDirect, {required this.client});
14 +
15 + late ArbitrumClient client;
16 +
17 + @override
18 + WalletType getType() => WalletType.arbitrum;
19 +
20 + @override
21 + Future<ArbitrumWallet> create(EVMChainNewWalletCredentials credentials, {bool? isTestnet}) async {
22 + final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
23 +
24 + final mnemonic = credentials.mnemonic ?? bip39.generateMnemonic(strength: strength);
25 +
26 + final wallet = ArbitrumWallet(
27 + walletInfo: credentials.walletInfo!,
28 + derivationInfo: await credentials.walletInfo!.getDerivationInfo(),
29 + mnemonic: mnemonic,
30 + password: credentials.password!,
31 + passphrase: credentials.passphrase,
32 + client: client,
33 + encryptionFileUtils: encryptionFileUtilsFor(isDirect),
34 + );
35 +
36 + await wallet.init();
37 + wallet.addInitialTokens();
38 + await wallet.save();
39 + return wallet;
40 + }
41 +
42 + @override
43 + Future<ArbitrumWallet> openWallet(String name, String password) async {
44 + final walletInfo = await WalletInfo.get(name, getType());
45 + if (walletInfo == null) {
46 + throw Exception('Wallet not found');
47 + }
48 +
49 + try {
50 + final wallet = await ArbitrumWallet.open(
51 + name: name,
52 + password: password,
53 + walletInfo: walletInfo,
54 + encryptionFileUtils: encryptionFileUtilsFor(isDirect),
55 + );
56 +
57 + await wallet.init();
58 + wallet.addInitialTokens();
59 + await wallet.save();
60 + saveBackup(name);
61 + return wallet;
62 + } catch (_) {
63 + await restoreWalletFilesFromBackup(name);
64 +
65 + final wallet = await ArbitrumWallet.open(
66 + name: name,
67 + password: password,
68 + walletInfo: walletInfo,
69 + encryptionFileUtils: encryptionFileUtilsFor(isDirect),
70 + );
71 +
72 + await wallet.init();
73 + wallet.addInitialTokens();
74 + await wallet.save();
75 + return wallet;
76 + }
77 + }
78 +
79 + @override
80 + Future<ArbitrumWallet> restoreFromKeys(
81 + EVMChainRestoreWalletFromPrivateKey credentials, {
82 + bool? isTestnet,
83 + }) async {
84 + final wallet = ArbitrumWallet(
85 + password: credentials.password!,
86 + privateKey: credentials.privateKey,
87 + walletInfo: credentials.walletInfo!,
88 + derivationInfo: await credentials.walletInfo!.getDerivationInfo(),
89 + client: client,
90 + encryptionFileUtils: encryptionFileUtilsFor(isDirect),
91 + );
92 +
93 + await wallet.init();
94 + wallet.addInitialTokens();
95 + await wallet.save();
96 + return wallet;
97 + }
98 +
99 + @override
100 + Future<ArbitrumWallet> restoreFromHardwareWallet(
101 + EVMChainRestoreWalletFromHardware credentials,
102 + ) async {
103 + final derivationInfo = await credentials.walletInfo!.getDerivationInfo();
104 + derivationInfo.derivationType = DerivationType.bip39;
105 + derivationInfo.derivationPath = "m/44'/60'/${credentials.hwAccountData.accountIndex}'/0/0";
106 + await derivationInfo.save();
107 + credentials.walletInfo!.hardwareWalletType = credentials.hardwareWalletType;
108 + credentials.walletInfo!.address = credentials.hwAccountData.address;
109 + await credentials.walletInfo!.save();
110 +
111 + final wallet = ArbitrumWallet(
112 + walletInfo: credentials.walletInfo!,
113 + derivationInfo: derivationInfo,
114 + password: credentials.password!,
115 + client: client,
116 + encryptionFileUtils: encryptionFileUtilsFor(isDirect),
117 + );
118 +
119 + await wallet.init();
120 + wallet.addInitialTokens();
121 + await wallet.save();
122 +
123 + return wallet;
124 + }
125 +
126 + @override
127 + Future<ArbitrumWallet> restoreFromSeed(
128 + EVMChainRestoreWalletFromSeedCredentials credentials, {
129 + bool? isTestnet,
130 + }) async {
131 + if (!bip39.validateMnemonic(credentials.mnemonic)) {
132 + throw ArbitrumMnemonicIsIncorrectException();
133 + }
134 +
135 + final wallet = ArbitrumWallet(
136 + password: credentials.password!,
137 + mnemonic: credentials.mnemonic,
138 + walletInfo: credentials.walletInfo!,
139 + derivationInfo: await credentials.walletInfo!.getDerivationInfo(),
140 + passphrase: credentials.passphrase,
141 + client: client,
142 + encryptionFileUtils: encryptionFileUtilsFor(isDirect),
143 + );
144 +
145 + await wallet.init();
146 + wallet.addInitialTokens();
147 + await wallet.save();
148 +
149 + return wallet;
150 + }
151 +
152 + @override
153 + Future<void> rename(String currentName, String password, String newName) async {
154 + final currentWalletInfo = await WalletInfo.get(currentName, getType());
155 + if (currentWalletInfo == null) {
156 + throw Exception('Wallet not found');
157 + }
158 + final currentWallet = await ArbitrumWallet.open(
159 + password: password,
160 + name: currentName,
161 + walletInfo: currentWalletInfo,
162 + encryptionFileUtils: encryptionFileUtilsFor(isDirect),
163 + );
164 +
165 + await currentWallet.renameWalletFiles(newName);
166 + await saveBackup(newName);
167 +
168 + final newWalletInfo = currentWalletInfo;
169 + newWalletInfo.id = WalletBase.idFor(newName, getType());
170 + newWalletInfo.name = newName;
171 +
172 + newWalletInfo.save();
173 + }
174 +}
cw_arbitrum/lib/cw_arbitrum.dart new
+5
@@ -0,0 +1,5 @@
1 +/// A Calculator.
2 +class Calculator {
3 + /// Returns [value] plus 1.
4 + int addOne(int value) => value + 1;
5 +}
cw_arbitrum/lib/default_arbitrum_erc20_tokens.dart new
+71
@@ -0,0 +1,71 @@
1 +import 'package:cw_core/crypto_currency.dart';
2 +import 'package:cw_core/erc20_token.dart';
3 +
4 +class DefaultArbitrumErc20Tokens {
5 + final List<Erc20Token> _defaultTokens = [
6 + Erc20Token(
7 + name: "USD Coin",
8 + symbol: "USDC",
9 + contractAddress: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
10 + decimal: 6,
11 + enabled: true,
12 + ),
13 + Erc20Token(
14 + name: "USDC.e",
15 + symbol: "USDC.e",
16 + contractAddress: "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8",
17 + decimal: 6,
18 + enabled: true,
19 + ),
20 + Erc20Token(
21 + name: "Wrapped BTC",
22 + symbol: "WBTC",
23 + contractAddress: "0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f",
24 + decimal: 8,
25 + enabled: true,
26 + ),
27 + Erc20Token(
28 + name: "Chainlink Token",
29 + symbol: "LINK",
30 + contractAddress: "0xf97f4df75117a78c1A5a0DBb814Af92458539FB4",
31 + decimal: 18,
32 + enabled: true,
33 + ),
34 + Erc20Token(
35 + name: "Wrapped liquid staked Ether 2.0",
36 + symbol: "wstETH",
37 + contractAddress: "0x0fBcbaEA96Ce0cF7Ee00A8c19c3ab6f5Dc8E1921",
38 + decimal: 18,
39 + enabled: false,
40 + ),
41 + Erc20Token(
42 + name: "Wrapped Ether",
43 + symbol: "WETH",
44 + contractAddress: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
45 + decimal: 18,
46 + enabled: false,
47 + ),
48 + Erc20Token(
49 + name: "DAI",
50 + symbol: "DAI",
51 + contractAddress: "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1",
52 + decimal: 18,
53 + enabled: false,
54 + ),
55 + ];
56 +
57 + List<Erc20Token> get initialArbitrumErc20Tokens => _defaultTokens.map((token) {
58 + String? iconPath;
59 + if (token.iconPath?.isEmpty ?? true) {
60 + try {
61 + iconPath = CryptoCurrency.all
62 + .firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase())
63 + .iconPath;
64 + } catch (_) {}
65 + } else {
66 + iconPath = token.iconPath;
67 + }
68 +
69 + return Erc20Token.copyWith(token, icon: iconPath, tag: 'ARB');
70 + }).toList();
71 +}
cw_arbitrum/pubspec.yaml new
+73
@@ -0,0 +1,73 @@
1 +name: cw_arbitrum
2 +description: "A new Flutter package project."
3 +version: 0.0.1
4 +publish_to: none
5 +homepage: https://cakewallet.com
6 +
7 +environment:
8 + sdk: '>=3.0.6 <4.0.0'
9 + flutter: ">=1.17.0"
10 +
11 +dependencies:
12 + flutter:
13 + sdk: flutter
14 + cw_core:
15 + path: ../cw_core
16 + cw_ethereum:
17 + path: ../cw_ethereum
18 + cw_evm:
19 + path: ../cw_evm
20 + web3dart: ^2.7.1
21 + hive: ^2.2.3
22 + bip39: ^1.0.6
23 + collection: ^1.17.1
24 +
25 +dependency_overrides:
26 + web3dart:
27 + git:
28 + url: https://github.com/cake-tech/web3dart.git
29 + ref: cake
30 + watcher: ^1.1.0
31 +
32 +dev_dependencies:
33 + flutter_test:
34 + sdk: flutter
35 + flutter_lints: ^2.0.0
36 + build_runner: ^2.4.15
37 +
38 +# For information on the generic Dart part of this file, see the
39 +# following page: https://dart.dev/tools/pub/pubspec
40 +
41 +# The following section is specific to Flutter packages.
42 +flutter:
43 +
44 + # To add assets to your package, add an assets section, like this:
45 + # assets:
46 + # - images/a_dot_burr.jpeg
47 + # - images/a_dot_ham.jpeg
48 + #
49 + # For details regarding assets in packages, see
50 + # https://flutter.dev/to/asset-from-package
51 + #
52 + # An image asset can refer to one or more resolution-specific "variants", see
53 + # https://flutter.dev/to/resolution-aware-images
54 +
55 + # To add custom fonts to your package, add a fonts section here,
56 + # in this "flutter" section. Each entry in this list should have a
57 + # "family" key with the font family name, and a "fonts" key with a
58 + # list giving the asset and other descriptors for the font. For
59 + # example:
60 + # fonts:
61 + # - family: Schyler
62 + # fonts:
63 + # - asset: fonts/Schyler-Regular.ttf
64 + # - asset: fonts/Schyler-Italic.ttf
65 + # style: italic
66 + # - family: Trajan Pro
67 + # fonts:
68 + # - asset: fonts/TrajanPro.ttf
69 + # - asset: fonts/TrajanPro_Bold.ttf
70 + # weight: 700
71 + #
72 + # For details regarding fonts in packages, see
73 + # https://flutter.dev/to/font-from-package
cw_arbitrum/test/cw_arbitrum_test.dart new
+12
@@ -0,0 +1,12 @@
1 +import 'package:flutter_test/flutter_test.dart';
2 +
3 +import 'package:cw_arbitrum/cw_arbitrum.dart';
4 +
5 +void main() {
6 + test('adds one to input values', () {
7 + final calculator = Calculator();
8 + expect(calculator.addOne(2), 3);
9 + expect(calculator.addOne(-7), -6);
10 + expect(calculator.addOne(0), 1);
11 + });
12 +}
cw_base/lib/base_wallet.dart
+7 -3
@@ -45,9 +45,13 @@ class BaseWallet extends EVMChainWallet {
45 for (final token in initialErc20Tokens) {
46 if (!evmChainErc20TokensBox.containsKey(token.contractAddress)) {
47 evmChainErc20TokensBox.put(token.contractAddress, token);
48 - } else { // update existing token
48 + } else {
49 + // update existing token
50 final existingToken = evmChainErc20TokensBox.get(token.contractAddress);
50 - evmChainErc20TokensBox.put(token.contractAddress, Erc20Token.copyWith(token, enabled: existingToken!.enabled));
51 + evmChainErc20TokensBox.put(
52 + token.contractAddress,
53 + Erc20Token.copyWith(token, enabled: existingToken!.enabled),
54 + );
55 }
56 }
57 }
@@ -58,7 +62,7 @@ class BaseWallet extends EVMChainWallet {
62
63 @override
64 Future<bool> checkIfScanProviderIsEnabled() async {
61 - return (await sharedPrefs.future).getBool("use_basescan") ?? true;
65 + return (await sharedPrefs.future).getBool("use_basescan") ?? true;
66 }
67
68 @override
cw_core/lib/crypto_currency.dart
+2
@@ -117,6 +117,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
117 CryptoCurrency.deps,
118 CryptoCurrency.baseEth,
119 CryptoCurrency.usde,
120 + CryptoCurrency.arbEth,
121 ];
122
123 static const havenCurrencies = [
@@ -245,6 +246,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
246 static const cbbtc = CryptoCurrency(title: 'CBBTC', tag: 'ETH', fullName: 'Coinbase Wrapped BTC', raw: 103, name: 'cbbtc', iconPath: 'assets/images/cbbtc_icon.png', decimals: 8);
247 static const baseEth = CryptoCurrency(title: 'ETH', tag: 'BASE', fullName: 'Ethereum', raw: 104, name: 'baseth', iconPath: 'assets/images/crypto/base_icon.webp', decimals: 18);
248 static const usde = CryptoCurrency(title: 'USDE', tag: 'BASE', fullName: 'Ethena USDE', raw: 105, name: 'usde', iconPath: 'assets/images/crypto/ethena-usde-logo.png', decimals: 18);
249 + static const arbEth = CryptoCurrency(title: 'ETH', tag: 'ARB', fullName: 'Arbitrum', raw: 106, name: 'arbeth', iconPath: 'assets/images/crypto/arbitrum.webp', decimals: 18);
250
251 static final Map<int, CryptoCurrency> _rawCurrencyMap =
252 [...all, ...havenCurrencies].fold<Map<int, CryptoCurrency>>(<int, CryptoCurrency>{}, (acc, item) {
cw_core/lib/currency_for_wallet_type.dart
+2
@@ -18,6 +18,8 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type, {bool isTestnet = fal
18 return CryptoCurrency.eth;
19 case WalletType.base:
20 return CryptoCurrency.baseEth;
21 + case WalletType.arbitrum:
22 + return CryptoCurrency.arbEth;
23 case WalletType.bitcoinCash:
24 return CryptoCurrency.bch;
25 case WalletType.nano:
cw_core/lib/erc20_token.dart
+1
@@ -71,6 +71,7 @@ class Erc20Token extends CryptoCurrency with HiveObjectMixin {
71 static const ethereumBoxName = 'EthereumErc20Tokens';
72 static const polygonBoxName = 'PolygonErc20Tokens';
73 static const baseBoxName = 'BaseErc20Tokens';
74 + static const arbitrumBoxName = 'ArbitrumErc20Tokens';
75
76 @override
77 bool operator ==(other) =>
cw_core/lib/node.dart
+2
@@ -107,6 +107,7 @@ class Node extends HiveObject with Keyable {
107 case WalletType.ethereum:
108 case WalletType.polygon:
109 case WalletType.base:
110 + case WalletType.arbitrum:
111 case WalletType.solana:
112 case WalletType.tron:
113 case WalletType.zano:
@@ -171,6 +172,7 @@ class Node extends HiveObject with Keyable {
172 case WalletType.ethereum:
173 case WalletType.polygon:
174 case WalletType.base:
175 + case WalletType.arbitrum:
176 case WalletType.solana:
177 case WalletType.tron:
178 case WalletType.dogecoin:
cw_core/lib/wallet_base.dart
+2
@@ -86,6 +86,8 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
86
87 int calculateEstimatedFee(TransactionPriority priority, int? amount);
88
89 + Future<void> updateEstimatedFeesParams(TransactionPriority priority) async {}
90 +
91 // void fetchTransactionsAsync(
92 // void Function(TransactionType transaction) onTransactionLoaded,
93 // {void Function() onFinished});
cw_core/lib/wallet_type.dart
+14
@@ -20,6 +20,7 @@ const walletTypes = [
20 WalletType.decred,
21 WalletType.dogecoin,
22 WalletType.base,
23 + WalletType.arbitrum,
24 ];
25
26 @HiveType(typeId: WALLET_TYPE_TYPE_ID)
@@ -74,6 +75,9 @@ enum WalletType {
75
76 @HiveField(16)
77 base,
78 +
79 + @HiveField(17)
80 + arbitrum,
81 }
82
83 int serializeToInt(WalletType type) {
@@ -110,6 +114,8 @@ int serializeToInt(WalletType type) {
114 return 14;
115 case WalletType.base:
116 return 15;
117 + case WalletType.arbitrum:
118 + return 16;
119 case WalletType.none:
120 return -1;
121 }
@@ -149,6 +155,8 @@ WalletType deserializeFromInt(int raw) {
155 return WalletType.dogecoin;
156 case 15:
157 return WalletType.base;
158 + case 16:
159 + return WalletType.arbitrum;
160 default:
161 throw Exception('Unexpected token: $raw for WalletType deserializeFromInt');
162 }
@@ -188,6 +196,8 @@ String walletTypeToString(WalletType type) {
196 return 'Dogecoin';
197 case WalletType.base:
198 return 'Base';
199 + case WalletType.arbitrum:
200 + return 'Arbitrum';
201 case WalletType.none:
202 return '';
203 }
@@ -227,6 +237,8 @@ String walletTypeToDisplayName(WalletType type) {
237 return 'Dogecoin (DOGE)';
238 case WalletType.base:
239 return 'Base (BASE)';
240 + case WalletType.arbitrum:
241 + return 'Arbitrum (ARB)';
242 case WalletType.none:
243 return '';
244 }
@@ -266,6 +278,8 @@ WalletType? cryptoCurrencyToWalletType(CryptoCurrency type) {
278 return WalletType.dogecoin;
279 case CryptoCurrency.baseEth:
280 return WalletType.base;
281 + case CryptoCurrency.arbEth:
282 + return WalletType.arbitrum;
283 default:
284 return null;
285 }
cw_evm/lib/evm_chain_client.dart
+11 -6
@@ -163,7 +163,7 @@ abstract class EVMChainClient {
163 required BigInt gasFee,
164 required int estimatedGasUnits,
165 required int maxFeePerGas,
166 - required EVMChainTransactionPriority priority,
166 + required EVMChainTransactionPriority? priority,
167 required CryptoCurrency currency,
168 required String feeCurrency,
169 required int exponent,
@@ -172,15 +172,20 @@ abstract class EVMChainClient {
172 int? gasPrice,
173 }) async {
174 assert(currency == CryptoCurrency.eth ||
175 - currency == CryptoCurrency.maticpoly || currency == CryptoCurrency.baseEth ||
175 + currency == CryptoCurrency.maticpoly ||
176 + currency == CryptoCurrency.baseEth ||
177 + currency == CryptoCurrency.arbEth ||
178 contractAddress != null);
179
178 - bool isNativeToken = currency == CryptoCurrency.eth || currency == CryptoCurrency.maticpoly || currency == CryptoCurrency.baseEth;
180 + bool isNativeToken = currency == CryptoCurrency.eth ||
181 + currency == CryptoCurrency.maticpoly ||
182 + currency == CryptoCurrency.baseEth ||
183 + currency == CryptoCurrency.arbEth;
184
185 final Transaction transaction = createTransaction(
186 from: privateKey.address,
187 to: EthereumAddress.fromHex(toAddress),
183 - maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip),
188 + maxPriorityFeePerGas: priority != null ? EtherAmount.fromInt(EtherUnit.gwei, priority.tip) : null,
189 amount: isNativeToken ? EtherAmount.inWei(amount) : EtherAmount.zero(),
190 data: data != null ? hexToBytes(data) : null,
191 maxGas: estimatedGasUnits,
@@ -229,7 +234,7 @@ abstract class EVMChainClient {
234 required String feeCurrency,
235 required int estimatedGasUnits,
236 required int maxFeePerGas,
232 - required EVMChainTransactionPriority priority,
237 + required EVMChainTransactionPriority? priority,
238 required int exponent,
239 required String contractAddress,
240 int? gasPrice,
@@ -237,7 +242,7 @@ abstract class EVMChainClient {
242 final Transaction transaction = createTransaction(
243 from: privateKey.address,
244 to: EthereumAddress.fromHex(contractAddress),
240 - maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip),
245 + maxPriorityFeePerGas:priority != null ? EtherAmount.fromInt(EtherUnit.gwei, priority.tip) : null,
246 amount: EtherAmount.zero(),
247 maxGas: estimatedGasUnits,
248 maxFeePerGas: EtherAmount.fromInt(EtherUnit.wei, maxFeePerGas),
cw_evm/lib/evm_chain_wallet.dart
+175 -143
@@ -115,11 +115,13 @@ abstract class EVMChainWalletBase
115
116 late final EVMChainClient _client;
117
118 - int gasPrice = 0;
119 - int? gasBaseFee = 0;
120 - int estimatedGasUnits = 0;
118 + bool hasPriorityFee = true;
119
122 - Timer? _updateFeesTimer;
120 + @observable
121 + String? nativeTxEstimatedFee;
122 +
123 + @observable
124 + String? erc20TxEstimatedFee;
125
126 bool _isTransactionUpdating;
127
@@ -168,6 +170,16 @@ abstract class EVMChainWalletBase
170 EncryptionFileUtils encryptionFileUtils,
171 );
172
173 + String _getUSDCContractAddress() {
174 + return switch (_client.chainId) {
175 + 1 => "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
176 + 137 => "0x2791bca1f2de4661ed88a30c99a7a9449aa84174",
177 + 8453 => "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
178 + 42161 => "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
179 + _ => throw Exception("Unsupported chain ID: ${_client.chainId}"),
180 + };
181 + }
182 +
183 @override
184 Future<bool> checkNodeHealth() async {
185 try {
@@ -175,26 +187,9 @@ abstract class EVMChainWalletBase
187 await _client.getBalance(_evmChainPrivateKey.address, throwOnError: true);
188
189 // Check USDC token balance
178 - String usdcContractAddress;
179 -
180 - switch (_client.chainId) {
181 - case 1:
182 - usdcContractAddress = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48";
183 - break;
184 - case 137:
185 - usdcContractAddress = "0x2791bca1f2de4661ed88a30c99a7a9449aa84174";
186 - break;
187 - case 8453:
188 - usdcContractAddress = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913";
189 - break;
190 - default:
191 - return true;
192 - }
190 + String usdcContractAddress = _getUSDCContractAddress();
191
194 - await _client.fetchERC20Balances(
195 - _evmChainPrivateKey.address,
196 - usdcContractAddress,
197 - );
192 + await _client.fetchERC20Balances(_evmChainPrivateKey.address, usdcContractAddress);
193
194 return true;
195 } catch (e) {
@@ -279,30 +274,77 @@ abstract class EVMChainWalletBase
274 }
275
276 @override
282 - int calculateEstimatedFee(TransactionPriority priority, int? amount) {
283 - {
284 - try {
285 - if (priority is EVMChainTransactionPriority) {
286 - final priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
287 -
288 - int maxFeePerGas;
289 - if (gasBaseFee != null) {
290 - // MaxFeePerGas with EIP1559;
291 - maxFeePerGas = gasBaseFee! + priorityFee;
292 - } else {
293 - // MaxFeePerGas with gasPrice;
294 - maxFeePerGas = gasPrice;
295 - printV('MaxFeePerGas with gasPrice: $maxFeePerGas');
296 - }
277 + int calculateEstimatedFee(TransactionPriority priority, int? amount) => 0;
278 +
279 + @override
280 + Future<void> updateEstimatedFeesParams(TransactionPriority priority) async =>
281 + await _getEstimatedFees(priority);
282 +
283 + Future<void> _getEstimatedFees(TransactionPriority? priority) async {
284 + final nativeFee = await _getNativeTxFee(priority);
285 + nativeTxEstimatedFee = nativeFee.toString();
286
298 - final totalGasFee = estimatedGasUnits * maxFeePerGas;
299 - return totalGasFee;
287 + final erc20Fee = await _getErc20TxFee(priority);
288 + erc20TxEstimatedFee = erc20Fee.toString();
289 +
290 + printV('Native Estimated Fee: $nativeTxEstimatedFee');
291 + printV('ERC20 Estimated Fee: $erc20TxEstimatedFee');
292 + }
293 +
294 + Future<int> _getNativeTxFee(TransactionPriority? priority) async {
295 + try {
296 + int priorityFee = 0;
297 + if (hasPriorityFee) {
298 + if (priority is EVMChainTransactionPriority) {
299 + priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
300 }
301 + }
302
302 - return 0;
303 - } catch (e) {
304 - return 0;
303 + final gasPrice = await _client.getGasUnitPrice();
304 + final gasBaseFee = await _client.getGasBaseFee();
305 +
306 + final gasUnits = await _client.getEstimatedGasUnitsForTransaction(
307 + senderAddress: evmChainPrivateKey.address,
308 + toAddress: evmChainPrivateKey.address,
309 + gasPrice: EtherAmount.fromInt(EtherUnit.wei, gasPrice),
310 + value: EtherAmount.fromBigInt(EtherUnit.wei, BigInt.from(0.0000000001)),
311 + );
312 +
313 + int maxFeePerGas = gasBaseFee != null ? (gasBaseFee + priorityFee) : (gasPrice + priorityFee);
314 + final totalGasFee = gasUnits * maxFeePerGas;
315 + return totalGasFee;
316 + } catch (e) {
317 + printV(e.toString());
318 + return 0;
319 + }
320 + }
321 +
322 + Future<int> _getErc20TxFee(TransactionPriority? priority) async {
323 + try {
324 + int priorityFee = 0;
325 + if (hasPriorityFee) {
326 + if (priority is EVMChainTransactionPriority) {
327 + priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
328 + }
329 }
330 +
331 + final gasPrice = await _client.getGasUnitPrice();
332 + final gasBaseFee = await _client.getGasBaseFee();
333 +
334 + final gasUnits = await _client.getEstimatedGasUnitsForTransaction(
335 + senderAddress: evmChainPrivateKey.address,
336 + toAddress: evmChainPrivateKey.address,
337 + contractAddress: _getUSDCContractAddress(), // Using USDC for default estimation
338 + gasPrice: EtherAmount.fromInt(EtherUnit.wei, gasPrice),
339 + value: EtherAmount.fromBigInt(EtherUnit.wei, BigInt.from(0.0000000001)),
340 + );
341 +
342 + int maxFeePerGas = gasBaseFee != null ? (gasBaseFee + priorityFee) : (gasPrice + priorityFee);
343 + final totalGasFee = gasUnits * maxFeePerGas;
344 + return totalGasFee;
345 + } catch (e) {
346 + printV(e.toString());
347 + return 0;
348 }
349 }
350
@@ -316,76 +358,81 @@ abstract class EVMChainWalletBase
358 required amount,
359 required String? contractAddress,
360 required String receivingAddressHex,
319 - required TransactionPriority priority,
361 + required TransactionPriority? priority,
362 Uint8List? data,
363 }) async {
364 try {
323 - if (priority is EVMChainTransactionPriority) {
324 - final priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
365 + int priorityFee = 0;
366 + if (hasPriorityFee && priority != null) {
367 + if (priority is EVMChainTransactionPriority) {
368 + priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
369 + }
370 + }
371
326 - int maxFeePerGas;
327 - int adjustedGasPrice;
372 + final gasBaseFee = await _client.getGasBaseFee();
373 + final gasPrice = await _client.getGasUnitPrice();
374
329 - bool isPolygon = _client.chainId == 137;
375 + int maxFeePerGas;
376 + int adjustedGasPrice;
377
331 - if (gasBaseFee != null) {
332 - // MaxFeePerGas with EIP1559;
333 - maxFeePerGas = gasBaseFee! + priorityFee;
334 - } else {
335 - // MaxFeePerGas with gasPrice
336 - maxFeePerGas = gasPrice + priorityFee;
337 - }
378 + bool isPolygon = _client.chainId == 137;
379
339 - adjustedGasPrice = maxFeePerGas;
340 -
341 - // Polygon has a minimum priority fee of 25 gwei
342 - if (isPolygon) {
343 - int minPriorityFee = 25;
344 - int minPriorityFeeWei =
345 - EtherAmount.fromInt(EtherUnit.gwei, minPriorityFee).getInWei.toInt();
346 -
347 - // Calculate user selected priority-based additional fee on top of minimum
348 - int additionalPriorityFee = 0;
349 - switch (priority) {
350 - case EVMChainTransactionPriority.slow:
351 - // We use minimum priority fee only
352 - additionalPriorityFee = 0;
353 - break;
354 - case EVMChainTransactionPriority.medium:
355 - // We add 15 gwei on top of minimum
356 - additionalPriorityFee = EtherAmount.fromInt(EtherUnit.gwei, 15).getInWei.toInt();
357 - break;
358 - case EVMChainTransactionPriority.fast:
359 - // We add 35 gwei on top of minimum
360 - additionalPriorityFee = EtherAmount.fromInt(EtherUnit.gwei, 35).getInWei.toInt();
361 - break;
362 - }
380 + if (gasBaseFee != null) {
381 + // MaxFeePerGas with EIP1559;
382 + maxFeePerGas = gasBaseFee + priorityFee;
383 + } else {
384 + // MaxFeePerGas with gasPrice
385 + maxFeePerGas = gasPrice + priorityFee;
386 + }
387
364 - int totalPriorityFee = minPriorityFeeWei + additionalPriorityFee;
365 - adjustedGasPrice = gasPrice + totalPriorityFee;
366 - maxFeePerGas = gasPrice + totalPriorityFee;
388 + adjustedGasPrice = maxFeePerGas;
389 +
390 + // Polygon has a minimum priority fee of 25 gwei
391 + if (isPolygon) {
392 + int minPriorityFee = 25;
393 + int minPriorityFeeWei =
394 + EtherAmount.fromInt(EtherUnit.gwei, minPriorityFee).getInWei.toInt();
395 +
396 + // Calculate user selected priority-based additional fee on top of minimum
397 + int additionalPriorityFee = 0;
398 + switch (priority) {
399 + case EVMChainTransactionPriority.slow:
400 + // We use minimum priority fee only
401 + additionalPriorityFee = 0;
402 + break;
403 + case EVMChainTransactionPriority.medium:
404 + // We add 15 gwei on top of minimum
405 + additionalPriorityFee = EtherAmount.fromInt(EtherUnit.gwei, 15).getInWei.toInt();
406 + break;
407 + case EVMChainTransactionPriority.fast:
408 + // We add 35 gwei on top of minimum
409 + additionalPriorityFee = EtherAmount.fromInt(EtherUnit.gwei, 35).getInWei.toInt();
410 + break;
411 }
412
369 - final estimatedGas = await _client.getEstimatedGasUnitsForTransaction(
370 - contractAddress: contractAddress,
371 - senderAddress: _evmChainPrivateKey.address,
372 - value: EtherAmount.fromBigInt(EtherUnit.wei, amount!),
373 - gasPrice: EtherAmount.fromInt(EtherUnit.wei, adjustedGasPrice),
374 - toAddress: EthereumAddress.fromHex(receivingAddressHex),
375 - maxFeePerGas: EtherAmount.fromInt(EtherUnit.wei, maxFeePerGas),
376 - data: data,
377 - );
413 + int totalPriorityFee = minPriorityFeeWei + additionalPriorityFee;
414 + adjustedGasPrice = gasPrice + totalPriorityFee;
415 + maxFeePerGas = gasPrice + totalPriorityFee;
416 + }
417
379 - final totalGasFee = estimatedGas * adjustedGasPrice;
418 + final estimatedGas = await _client.getEstimatedGasUnitsForTransaction(
419 + contractAddress: contractAddress,
420 + senderAddress: _evmChainPrivateKey.address,
421 + value: EtherAmount.fromBigInt(EtherUnit.wei, amount!),
422 + gasPrice: EtherAmount.fromInt(EtherUnit.wei, adjustedGasPrice),
423 + toAddress: EthereumAddress.fromHex(receivingAddressHex),
424 + maxFeePerGas: EtherAmount.fromInt(EtherUnit.wei, maxFeePerGas),
425 + data: data,
426 + );
427
381 - return GasParamsHandler(
382 - estimatedGasUnits: estimatedGas,
383 - estimatedGasFee: totalGasFee,
384 - maxFeePerGas: maxFeePerGas,
385 - gasPrice: adjustedGasPrice,
386 - );
387 - }
388 - return GasParamsHandler.zero();
428 + final totalGasFee = estimatedGas * adjustedGasPrice;
429 +
430 + return GasParamsHandler(
431 + estimatedGasUnits: estimatedGas,
432 + estimatedGasFee: totalGasFee,
433 + maxFeePerGas: maxFeePerGas,
434 + gasPrice: adjustedGasPrice,
435 + );
436 } catch (e) {
437 return GasParamsHandler.zero();
438 }
@@ -400,7 +447,6 @@ abstract class EVMChainWalletBase
447 Future<void> close({bool shouldCleanup = false}) async {
448 _client.stop();
449 _transactionsUpdateTimer?.cancel();
403 - _updateFeesTimer?.cancel();
450 }
451
452 @action
@@ -440,30 +486,16 @@ abstract class EVMChainWalletBase
486
487 await _updateBalance();
488 await _updateTransactions();
443 - await _updateEstimatedGasFeeParams();
489 + await _getEstimatedFees(
490 + hasPriorityFee ? EVMChainTransactionPriority.medium : null,
491 + ); // We're using medium priority for default estimation
492
445 - _updateFeesTimer ??= Timer.periodic(const Duration(seconds: 30), (timer) async {
446 - await _updateEstimatedGasFeeParams();
447 - });
493 syncStatus = SyncedSyncStatus();
494 } catch (e) {
495 syncStatus = FailedSyncStatus();
496 }
497 }
498
454 - Future<void> _updateEstimatedGasFeeParams() async {
455 - gasBaseFee = await _client.getGasBaseFee();
456 -
457 - gasPrice = await _client.getGasUnitPrice();
458 -
459 - estimatedGasUnits = await _client.getEstimatedGasUnitsForTransaction(
460 - senderAddress: _evmChainPrivateKey.address,
461 - toAddress: _evmChainPrivateKey.address,
462 - gasPrice: EtherAmount.fromInt(EtherUnit.wei, gasPrice),
463 - value: EtherAmount.fromBigInt(EtherUnit.wei, BigInt.one),
464 - );
465 - }
466 -
499 @override
500 Future<PendingTransaction> createTransaction(Object credentials) async {
501 final _credentials = credentials as EVMChainTransactionCredentials;
@@ -511,7 +543,7 @@ abstract class EVMChainWalletBase
543 final gasFeesModel = await calculateActualEstimatedFeeForCreateTransaction(
544 amount: totalAmount,
545 receivingAddressHex: toAddress,
514 - priority: _credentials.priority!,
546 + priority: _credentials.priority,
547 contractAddress: contractAddress,
548 );
549
@@ -539,7 +571,7 @@ abstract class EVMChainWalletBase
571 final gasFeesModel = await calculateActualEstimatedFeeForCreateTransaction(
572 amount: totalAmount,
573 receivingAddressHex: toAddress,
542 - priority: _credentials.priority!,
574 + priority: _credentials.priority,
575 contractAddress: contractAddress,
576 );
577
@@ -562,7 +594,7 @@ abstract class EVMChainWalletBase
594 final refinedGasFeesModel = await calculateActualEstimatedFeeForCreateTransaction(
595 amount: totalAmount,
596 receivingAddressHex: toAddress,
565 - priority: _credentials.priority!,
597 + priority: _credentials.priority,
598 contractAddress: contractAddress,
599 );
600
@@ -577,7 +609,7 @@ abstract class EVMChainWalletBase
609 totalAmount = (currencyBalance.balance - estimatedFeesForTransaction);
610 }
611
580 - // check the fees on the base currency (Eth/Polygon)
612 + // check the fees on the base currency
613 if (estimatedFeesForTransaction > balance[currency]!.balance) {
614 throw EVMChainTransactionFeesException(currency.title);
615 }
@@ -599,9 +631,9 @@ abstract class EVMChainWalletBase
631 toAddress: toAddress,
632 amount: totalAmount,
633 gasFee: estimatedFeesForTransaction,
602 - priority: _credentials.priority!,
634 + priority: _credentials.priority,
635 currency: transactionCurrency,
604 - feeCurrency: switch (_client.chainId) { 1 => "ETH", 137 => "POL", 8453 => "ETH", _ => "ETH" },
636 + feeCurrency: switch (_client.chainId) { 137 => "POL", _ => "ETH" },
637 maxFeePerGas: maxFeePerGasForTransaction,
638 exponent: exponent,
639 contractAddress:
@@ -614,12 +646,11 @@ abstract class EVMChainWalletBase
646 }
647
648 Future<PendingTransaction> createCallDataTransaction(
617 - String to,
618 - String dataHex,
619 - BigInt valueWei,
620 - EVMChainTransactionPriority priority,
621 - ) async {
622 -
649 + String to,
650 + String dataHex,
651 + BigInt valueWei,
652 + EVMChainTransactionPriority? priority,
653 + ) async {
654 // Estimate gas with the SAME call (sender, to, value, data)
655 final gas = await calculateActualEstimatedFeeForCreateTransaction(
656 amount: valueWei, // native value (usually 0 for ERC20 transfer)
@@ -632,7 +663,8 @@ abstract class EVMChainWalletBase
663 final nativeCurrency = switch (_client.chainId) {
664 137 => CryptoCurrency.maticpoly,
665 8453 => CryptoCurrency.baseEth,
635 - _ => CryptoCurrency.eth,
666 + 42161 => CryptoCurrency.arbEth,
667 + _ => CryptoCurrency.eth,
668 };
669
670 // Fallback for nodes that fail estimate (non-zero)
@@ -656,9 +688,13 @@ abstract class EVMChainWalletBase
688 );
689 }
690
659 -
660 - Future<PendingTransaction> createApprovalTransaction(BigInt amount, String spender,
661 - CryptoCurrency token, EVMChainTransactionPriority priority, String feeCurrency) async {
691 + Future<PendingTransaction> createApprovalTransaction(
692 + BigInt amount,
693 + String spender,
694 + CryptoCurrency token,
695 + EVMChainTransactionPriority? priority,
696 + String feeCurrency,
697 + ) async {
698 final CryptoCurrency transactionCurrency =
699 balance.keys.firstWhere((element) => element.title == token.title);
700 assert(transactionCurrency is Erc20Token);
@@ -840,10 +876,7 @@ abstract class EVMChainWalletBase
876 }
877
878 Future<bool> isApprovalRequired(
843 - String tokenContract,
844 - String spender,
845 - BigInt requiredAmount) async {
846 -
879 + String tokenContract, String spender, BigInt requiredAmount) async {
880 const zero = '0x0000000000000000000000000000000000000000';
881 const evmNative = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE';
882
@@ -859,9 +892,7 @@ abstract class EVMChainWalletBase
892 chainId: _client.chainId,
893 );
894
862 - final allowance = await erc20.allowance(
863 - owner,
864 - EthereumAddress.fromHex(spender));
895 + final allowance = await erc20.allowance(owner, EthereumAddress.fromHex(spender));
896
897 return allowance < requiredAmount;
898 } catch (e) {
@@ -898,7 +929,6 @@ abstract class EVMChainWalletBase
929 @override
930 Future<void> updateTransactionsHistory() async => await _updateTransactions();
931
901 -
932 List<Erc20Token> get erc20Currencies => evmChainErc20TokensBox.values.toList();
933
934 Future<void> addErc20Token(Erc20Token token) async {
@@ -998,6 +1028,8 @@ abstract class EVMChainWalletBase
1028 /// PolygonScan for Polygon.
1029 ///
1030 /// BaseScan for Base.
1031 + ///
1032 + /// ArbiScan for Arbitrum.
1033 void updateScanProviderUsageState(bool isEnabled) {
1034 if (isEnabled) {
1035 _updateTransactions();
integration_test/components/common_test_flows.dart
+4
@@ -356,6 +356,8 @@ class CommonTestFlows {
356 return secrets.solanaTestWalletSeeds;
357 case WalletType.base:
358 return secrets.baseTestWalletSeeds;
359 + case WalletType.arbitrum:
360 + return secrets.arbitrumTestWalletSeeds;
361 case WalletType.tron:
362 return secrets.tronTestWalletSeeds;
363 case WalletType.nano:
@@ -392,6 +394,8 @@ class CommonTestFlows {
394 return secrets.polygonTestWalletReceiveAddress;
395 case WalletType.base:
396 return secrets.baseTestWalletReceiveAddress;
397 + case WalletType.arbitrum:
398 + return secrets.arbitrumTestWalletReceiveAddress;
399 case WalletType.solana:
400 return secrets.solanaTestWalletReceiveAddress;
401 case WalletType.tron:
ios/Runner/InfoBase.plist
+20
@@ -322,6 +322,26 @@
322 <string>base-wallet</string>
323 </array>
324 </dict>
325 + <dict>
326 + <key>CFBundleTypeRole</key>
327 + <string>Editor</string>
328 + <key>CFBundleURLName</key>
329 + <string>arbitrum</string>
330 + <key>CFBundleURLSchemes</key>
331 + <array>
332 + <string>arbitrum</string>
333 + </array>
334 + </dict>
335 + <dict>
336 + <key>CFBundleTypeRole</key>
337 + <string>Viewer</string>
338 + <key>CFBundleURLName</key>
339 + <string>arbitrum-wallet</string>
340 + <key>CFBundleURLSchemes</key>
341 + <array>
342 + <string>arbitrum-wallet</string>
343 + </array>
344 + </dict>
345 </array>
346 <key>CFBundleVersion</key>
347 <string>$(CURRENT_PROJECT_VERSION)</string>
lib/arbitrum/cw_arbitrum.dart new
+244
@@ -0,0 +1,244 @@
1 +part of 'arbitrum.dart';
2 +
3 +class CWArbitrum extends Arbitrum {
4 + @override
5 + List<String> getArbitrumWordList(String language) => EVMChainMnemonics.englishWordlist;
6 +
7 + WalletService createArbitrumWalletService(bool isDirect) =>
8 + ArbitrumWalletService(isDirect, client: ArbitrumClient());
9 +
10 + @override
11 + WalletCredentials createArbitrumNewWalletCredentials({
12 + required String name,
13 + String? mnemonic,
14 + WalletInfo? walletInfo,
15 + String? password,
16 + String? passphrase,
17 + }) =>
18 + EVMChainNewWalletCredentials(
19 + name: name,
20 + walletInfo: walletInfo,
21 + password: password,
22 + mnemonic: mnemonic,
23 + passphrase: passphrase,
24 + );
25 +
26 + @override
27 + WalletCredentials createArbitrumRestoreWalletFromSeedCredentials({
28 + required String name,
29 + required String mnemonic,
30 + required String password,
31 + String? passphrase,
32 + }) =>
33 + EVMChainRestoreWalletFromSeedCredentials(
34 + name: name,
35 + password: password,
36 + mnemonic: mnemonic,
37 + passphrase: passphrase,
38 + );
39 +
40 + @override
41 + WalletCredentials createArbitrumRestoreWalletFromPrivateKey({
42 + required String name,
43 + required String privateKey,
44 + required String password,
45 + }) =>
46 + EVMChainRestoreWalletFromPrivateKey(name: name, password: password, privateKey: privateKey);
47 +
48 + @override
49 + WalletCredentials createArbitrumHardwareWalletCredentials({
50 + required String name,
51 + required HardwareAccountData hwAccountData,
52 + WalletInfo? walletInfo,
53 + }) =>
54 + EVMChainRestoreWalletFromHardware(
55 + name: name,
56 + hwAccountData: hwAccountData,
57 + walletInfo: walletInfo,
58 + );
59 +
60 + @override
61 + String getAddress(WalletBase wallet) => (wallet as ArbitrumWallet).walletAddresses.address;
62 +
63 + @override
64 + String getPrivateKey(WalletBase wallet) {
65 + final privateKeyHolder = (wallet as ArbitrumWallet).evmChainPrivateKey;
66 + if (privateKeyHolder is EthPrivateKey) return bytesToHex(privateKeyHolder.privateKey);
67 + return "";
68 + }
69 +
70 + @override
71 + String getPublicKey(WalletBase wallet) {
72 + final privateKeyInUnitInt = (wallet as ArbitrumWallet).evmChainPrivateKey;
73 + return privateKeyInUnitInt.address.hex;
74 + }
75 +
76 + Object createArbitrumTransactionCredentials(
77 + List<Output> outputs, {
78 + required CryptoCurrency currency,
79 + int? feeRate,
80 + }) =>
81 + EVMChainTransactionCredentials(
82 + outputs
83 + .map(
84 + (out) => OutputInfo(
85 + fiatAmount: out.fiatAmount,
86 + cryptoAmount: out.cryptoAmount,
87 + address: out.address,
88 + note: out.note,
89 + sendAll: out.sendAll,
90 + extractedAddress: out.extractedAddress,
91 + isParsedAddress: out.isParsedAddress,
92 + formattedCryptoAmount: out.formattedCryptoAmount,
93 + ),
94 + )
95 + .toList(),
96 + priority: null,
97 + currency: currency,
98 + feeRate: feeRate,
99 + );
100 +
101 + Object createArbitrumTransactionCredentialsRaw(
102 + List<OutputInfo> outputs, {
103 + required CryptoCurrency currency,
104 + required int feeRate,
105 + }) =>
106 + EVMChainTransactionCredentials(
107 + outputs,
108 + priority: null,
109 + currency: currency,
110 + feeRate: feeRate,
111 + );
112 +
113 + @override
114 + int formatterArbitrumParseAmount(String amount) => EVMChainFormatter.parseEVMChainAmount(amount);
115 +
116 + @override
117 + double formatterArbitrumAmountToDouble({
118 + TransactionInfo? transaction,
119 + BigInt? amount,
120 + int exponent = 18,
121 + }) {
122 + assert(transaction != null || amount != null);
123 +
124 + if (transaction != null) {
125 + transaction as EVMChainTransactionInfo;
126 + return transaction.ethAmount / BigInt.from(10).pow(transaction.exponent);
127 + } else {
128 + return (amount!) / BigInt.from(10).pow(exponent);
129 + }
130 + }
131 +
132 + @override
133 + List<Erc20Token> getERC20Currencies(WalletBase wallet) =>
134 + (wallet as ArbitrumWallet).erc20Currencies;
135 +
136 + @override
137 + Future<void> addErc20Token(WalletBase wallet, CryptoCurrency token) =>
138 + (wallet as ArbitrumWallet).addErc20Token(token as Erc20Token);
139 +
140 + @override
141 + Future<void> deleteErc20Token(WalletBase wallet, CryptoCurrency token) =>
142 + (wallet as ArbitrumWallet).deleteErc20Token(token as Erc20Token);
143 +
144 + @override
145 + Future<void> removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token) =>
146 + (wallet as ArbitrumWallet).removeTokenTransactionsInHistory(token as Erc20Token);
147 +
148 + @override
149 + Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress) =>
150 + (wallet as ArbitrumWallet).getErc20Token(contractAddress, 'arbitrum');
151 +
152 + @override
153 + CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) {
154 + transaction as EVMChainTransactionInfo;
155 + if (transaction.tokenSymbol == CryptoCurrency.arbEth.title ||
156 + transaction.tokenSymbol == "ARB") {
157 + return CryptoCurrency.arbEth;
158 + }
159 +
160 + wallet as ArbitrumWallet;
161 +
162 + return wallet.erc20Currencies.firstWhere(
163 + (element) =>
164 + transaction.contractAddress?.toLowerCase() == element.contractAddress?.toLowerCase(),
165 + );
166 + }
167 +
168 + @override
169 + void updateArbitrumScanUsageState(WalletBase wallet, bool isEnabled) =>
170 + (wallet as ArbitrumWallet).updateScanProviderUsageState(isEnabled);
171 +
172 + @override
173 + Web3Client? getWeb3Client(WalletBase wallet) => (wallet as ArbitrumWallet).getWeb3Client();
174 +
175 + @override
176 + String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress;
177 +
178 + @override
179 + Future<PendingTransaction> createTokenApproval(
180 + WalletBase wallet,
181 + BigInt amount,
182 + String spender,
183 + CryptoCurrency token,
184 + ) =>
185 + (wallet as EVMChainWallet).createApprovalTransaction(
186 + amount,
187 + spender,
188 + token,
189 + null,
190 + "ARB",
191 + );
192 +
193 + @override
194 + Future<void> setHardwareWalletService(WalletBase wallet, HardwareWalletService service) async {
195 + if (service is EVMChainLedgerService) {
196 + ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedgerConnection(
197 + service.ledgerConnection, (await wallet.walletInfo.getDerivationInfo()).derivationPath);
198 + } else if (service is EVMChainBitboxService) {
199 + ((wallet as EVMChainWallet).evmChainPrivateKey as EvmBitboxCredentials)
200 + .setBitbox(service.manager, (await wallet.walletInfo.getDerivationInfo()).derivationPath);
201 + }
202 + return Future.value();
203 + }
204 +
205 + @override
206 + HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection) =>
207 + EVMChainLedgerService(connection);
208 +
209 + @override
210 + HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager) =>
211 + EVMChainBitboxService(manager, chainId: 42161);
212 +
213 + @override
214 + List<String> getDefaultTokenContractAddresses() => DefaultArbitrumErc20Tokens()
215 + .initialArbitrumErc20Tokens
216 + .map((e) => e.contractAddress)
217 + .toList();
218 +
219 + @override
220 + bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress) {
221 + final arbitrumWallet = wallet as ArbitrumWallet;
222 + return arbitrumWallet.erc20Currencies.any(
223 + (element) => element.contractAddress.toLowerCase() == contractAddress.toLowerCase(),
224 + );
225 + }
226 +
227 + @override
228 + Future<bool> isApprovalRequired(
229 + WalletBase wallet, String tokenContract, String spender, BigInt requiredAmount) =>
230 + (wallet as EVMChainWallet).isApprovalRequired(tokenContract, spender, requiredAmount);
231 +
232 + @override
233 + Future<PendingTransaction> createRawCallDataTransaction(
234 + WalletBase wallet, String to, String dataHex, BigInt valueWei) =>
235 + (wallet as EVMChainWallet).createCallDataTransaction(to, dataHex, valueWei, null);
236 +
237 + @override
238 + String? getArbitrumNativeEstimatedFee(WalletBase wallet) =>
239 + (wallet as EVMChainWallet).nativeTxEstimatedFee;
240 +
241 + @override
242 + String? getArbitrumERC20EstimatedFee(WalletBase wallet) =>
243 + (wallet as EVMChainWallet).erc20TxEstimatedFee;
244 +}
lib/base/cw_base.dart
+14 -5
@@ -190,7 +190,8 @@ class CWBase extends Base {
190 String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress;
191
192 @override
193 - Future<bool> isApprovalRequired(WalletBase wallet, String tokenContract,String spender, BigInt requiredAmount) =>
193 + Future<bool> isApprovalRequired(
194 + WalletBase wallet, String tokenContract, String spender, BigInt requiredAmount) =>
195 (wallet as EVMChainWallet).isApprovalRequired(tokenContract, spender, requiredAmount);
196
197 @override
@@ -210,10 +211,10 @@ class CWBase extends Base {
211 );
212
213 @override
213 - Future<PendingTransaction> createRawCallDataTransaction(WalletBase wallet, String to, String dataHex, BigInt valueWei,
214 - TransactionPriority priority) =>
215 - (wallet as EVMChainWallet).createCallDataTransaction(to, dataHex, valueWei, priority as EVMChainTransactionPriority);
216 -
214 + Future<PendingTransaction> createRawCallDataTransaction(WalletBase wallet, String to,
215 + String dataHex, BigInt valueWei, TransactionPriority priority) =>
216 + (wallet as EVMChainWallet).createCallDataTransaction(
217 + to, dataHex, valueWei, priority as EVMChainTransactionPriority);
218
219 @override
220 Future<void> setHardwareWalletService(WalletBase wallet, HardwareWalletService service) async {
@@ -246,4 +247,12 @@ class CWBase extends Base {
247 (element) => element.contractAddress.toLowerCase() == contractAddress.toLowerCase(),
248 );
249 }
250 +
251 + @override
252 + String? getBaseNativeEstimatedFee(WalletBase wallet) =>
253 + (wallet as EVMChainWallet).nativeTxEstimatedFee;
254 +
255 + @override
256 + String? getBaseERC20EstimatedFee(WalletBase wallet) =>
257 + (wallet as EVMChainWallet).erc20TxEstimatedFee;
258 }
lib/buy/dfx/dfx_buy_provider.dart
+1
@@ -136,6 +136,7 @@ class DFXBuyProvider extends BuyProvider {
136 case WalletType.ethereum:
137 case WalletType.polygon:
138 case WalletType.base:
139 + case WalletType.arbitrum:
140 case WalletType.solana:
141 case WalletType.tron:
142 return wallet.signMessage(message);
lib/buy/kryptonim/kryptonim.dart
+2
@@ -219,6 +219,7 @@ class KryptonimBuyProvider extends BuyProvider {
219 String? blockchain = switch (cur.tag) {
220 'ETH' => 'Ethereum',
221 'BASE' => 'Base',
222 + 'ARB' => 'Arbitrum',
223 'POL' => 'Polygon',
224 'AVAXC' => 'Avalanche',
225 'SOL' => 'Solana',
@@ -231,6 +232,7 @@ class KryptonimBuyProvider extends BuyProvider {
232 CryptoCurrency.ltc => 'Litecoin',
233 CryptoCurrency.eth => 'Ethereum',
234 CryptoCurrency.baseEth => 'Base',
235 + CryptoCurrency.arbEth => 'Arbitrum',
236 CryptoCurrency.maticpoly => 'Matic',
237 _ => null,
238 };
lib/buy/onramper/onramper_buy_provider.dart
+2
@@ -324,6 +324,8 @@ class OnRamperBuyProvider extends BuyProvider {
324 switch (tag) {
325 case 'POL':
326 return 'POLYGON';
327 + case 'ARB':
328 + return 'ARBITRUM';
329 case 'ETH':
330 return 'ETHEREUM';
331 case 'TRX':
lib/buy/robinhood/robinhood_buy_provider.dart
+1
@@ -106,6 +106,7 @@ class RobinhoodBuyProvider extends BuyProvider {
106 case WalletType.ethereum:
107 case WalletType.polygon:
108 case WalletType.base:
109 + case WalletType.arbitrum:
110 case WalletType.solana:
111 case WalletType.tron:
112 case WalletType.dogecoin:
lib/core/open_crypto_pay/open_cryptopay_service.dart
+2
@@ -162,6 +162,8 @@ class OpenCryptoPayService {
162 return "Ethereum";
163 case "POL":
164 return "Polygon";
165 + case "ARB":
166 + return "Arbitrum";
167 case "TRX":
168 return "Tron";
169 case "SOL":
lib/core/payment_uris.dart
+15
@@ -101,6 +101,21 @@ class BaseURI extends PaymentURI {
101 }
102 }
103
104 +class ArbitrumURI extends PaymentURI {
105 + ArbitrumURI({required super.amount, required super.address});
106 +
107 + @override
108 + String toString() {
109 + var base = 'arbitrum:$address';
110 +
111 + if (amount.isNotEmpty) {
112 + base += '?amount=${amount.replaceAll(',', '.')}';
113 + }
114 +
115 + return base;
116 + }
117 +}
118 +
119 class BitcoinCashURI extends PaymentURI {
120 BitcoinCashURI({required super.amount, required super.address});
121
lib/core/seed_validator.dart
+3
@@ -1,3 +1,4 @@
1 +import 'package:cake_wallet/arbitrum/arbitrum.dart';
2 import 'package:cake_wallet/base/base.dart';
3 import 'package:cake_wallet/bitcoin/bitcoin.dart';
4 import 'package:cake_wallet/core/validator.dart';
@@ -55,6 +56,8 @@ class SeedValidator extends Validator<MnemonicItem> {
56 return decred!.getDecredWordList();
57 case WalletType.base:
58 return base!.getBaseWordList(language);
59 + case WalletType.arbitrum:
60 + return arbitrum!.getArbitrumWordList(language);
61 case WalletType.none:
62 case WalletType.haven:
63 return [];
lib/core/wallet_creation_service.dart
+1
@@ -79,6 +79,7 @@ class WalletCreationService {
79 case WalletType.ethereum:
80 case WalletType.polygon:
81 case WalletType.base:
82 + case WalletType.arbitrum:
83 case WalletType.solana:
84 case WalletType.tron:
85 case WalletType.dogecoin:
lib/di.dart
+3
@@ -4,6 +4,7 @@ import 'package:cake_wallet/.secrets.g.dart' as secrets;
4 import 'package:cake_wallet/anonpay/anonpay_api.dart';
5 import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
6 import 'package:cake_wallet/anypay/anypay_api.dart';
7 +import 'package:cake_wallet/arbitrum/arbitrum.dart';
8 import 'package:cake_wallet/base/base.dart';
9 import 'package:cake_wallet/bitcoin/bitcoin.dart';
10 import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
@@ -1226,6 +1227,8 @@ Future<void> setup({
1227 return decred!.createDecredWalletService(_unspentCoinsInfoSource);
1228 case WalletType.base:
1229 return base!.createBaseWalletService(SettingsStoreBase.walletPasswordDirectInput);
1230 + case WalletType.arbitrum:
1231 + return arbitrum!.createArbitrumWalletService(SettingsStoreBase.walletPasswordDirectInput);
1232 case WalletType.haven:
1233 return HavenWalletService();
1234 case WalletType.none:
lib/entities/default_settings_migration.dart
+22
@@ -50,6 +50,7 @@ const moneroWorldNodeUri = '.moneroworld.com';
50 const decredDefaultUri = "default-spv-nodes";
51 const dogecoinDefaultNodeUri = 'dogecoin.stackwallet.com:50022';
52 const baseDefaultNodeUri = 'base.nownodes.io';
53 +const arbitrumDefaultNodeUri = 'arbitrum.nownodes.io';
54
55 Future<void> defaultSettingsMigration(
56 {required int version,
@@ -545,6 +546,16 @@ Future<void> defaultSettingsMigration(
546 currentNodePreferenceKey: PreferencesKey.currentBaseNodeIdKey,
547 );
548 break;
549 + case 53:
550 + await addWalletNodeList(nodes: nodes, type: WalletType.arbitrum);
551 + await _changeDefaultNode(
552 + nodes: nodes,
553 + sharedPreferences: sharedPreferences,
554 + type: WalletType.arbitrum,
555 + currentNodePreferenceKey: PreferencesKey.currentArbitrumNodeIdKey,
556 + );
557 + break;
558 +
559 default:
560 break;
561 }
@@ -655,6 +666,8 @@ String _getDefaultNodeUri(WalletType type) {
666 return dogecoinDefaultNodeUri;
667 case WalletType.base:
668 return baseDefaultNodeUri;
669 + case WalletType.arbitrum:
670 + return arbitrumDefaultNodeUri;
671 case WalletType.banano:
672 case WalletType.none:
673 return '';
@@ -1057,6 +1070,7 @@ Future<void> checkCurrentNodes(
1070 final currentEthereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
1071 final currentPolygonNodeId = sharedPreferences.getInt(PreferencesKey.currentPolygonNodeIdKey);
1072 final currentBaseNodeId = sharedPreferences.getInt(PreferencesKey.currentBaseNodeIdKey);
1073 + final currentArbitrumNodeId = sharedPreferences.getInt(PreferencesKey.currentArbitrumNodeIdKey);
1074 final currentNanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
1075 final currentNanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey);
1076 final currentDecredNodeId = sharedPreferences.getInt(PreferencesKey.currentDecredNodeIdKey);
@@ -1082,6 +1096,8 @@ Future<void> checkCurrentNodes(
1096 nodeSource.values.firstWhereOrNull((node) => node.key == currentPolygonNodeId);
1097 final currentBaseNodeServer =
1098 nodeSource.values.firstWhereOrNull((node) => node.key == currentBaseNodeId);
1099 + final currentArbitrumNodeServer =
1100 + nodeSource.values.firstWhereOrNull((node) => node.key == currentArbitrumNodeId);
1101 final currentNanoNodeServer =
1102 nodeSource.values.firstWhereOrNull((node) => node.key == currentNanoNodeId);
1103 final currentDecredNodeServer =
@@ -1179,6 +1195,12 @@ Future<void> checkCurrentNodes(
1195 await sharedPreferences.setInt(PreferencesKey.currentBaseNodeIdKey, node.key as int);
1196 }
1197
1198 + if (currentArbitrumNodeServer == null) {
1199 + final node = Node(uri: arbitrumDefaultNodeUri, type: WalletType.arbitrum);
1200 + await nodeSource.add(node);
1201 + await sharedPreferences.setInt(PreferencesKey.currentArbitrumNodeIdKey, node.key as int);
1202 + }
1203 +
1204 if (currentSolanaNodeServer == null) {
1205 final node = Node(uri: solanaDefaultNodeUri, type: WalletType.solana);
1206 await nodeSource.add(node);
lib/entities/ens_record.dart
+6
@@ -1,3 +1,4 @@
1 +import 'package:cake_wallet/arbitrum/arbitrum.dart';
2 import 'package:cake_wallet/base/base.dart';
3 import 'package:cake_wallet/ethereum/ethereum.dart';
4 import 'package:cake_wallet/polygon/polygon.dart';
@@ -26,6 +27,10 @@ class EnsRecord {
27 _client = base!.getWeb3Client(wallet);
28 }
29
30 + if (wallet != null && wallet.type == WalletType.arbitrum) {
31 + _client = arbitrum!.getWeb3Client(wallet);
32 + }
33 +
34 if (_client == null) {
35 late final client = ProxyWrapper().getHttpIOClient();
36
@@ -48,6 +53,7 @@ class EnsRecord {
53 case WalletType.ethereum:
54 case WalletType.polygon:
55 case WalletType.base:
56 + case WalletType.arbitrum:
57 default:
58 return (await ens.withName(name).getAddress()).hex;
59 }
lib/entities/node_list.dart
+6 -1
@@ -52,6 +52,9 @@ Future<List<Node>> loadDefaultNodes(WalletType type) async {
52 case WalletType.base:
53 path = 'assets/base_node_list.yml';
54 break;
55 + case WalletType.arbitrum:
56 + path = 'assets/arbitrum_node_list.yml';
57 + break;
58 case WalletType.banano:
59 case WalletType.none:
60 path = '';
@@ -104,6 +107,7 @@ Future<void> resetToDefault(Box<Node> nodeSource) async {
107 final zanoNodes = await loadDefaultNodes(WalletType.zano);
108 final dogecoinElectrumServerList = await loadDefaultNodes(WalletType.dogecoin);
109 final baseNodes = await loadDefaultNodes(WalletType.base);
110 + final arbitrumNodes = await loadDefaultNodes(WalletType.arbitrum);
111
112 final nodes = moneroNodes +
113 bitcoinElectrumServerList +
@@ -118,7 +122,8 @@ Future<void> resetToDefault(Box<Node> nodeSource) async {
122 zanoNodes +
123 decredNodes +
124 dogecoinElectrumServerList +
121 - baseNodes;
125 + baseNodes +
126 + arbitrumNodes;
127
128 await nodeSource.clear();
129 await nodeSource.addAll(nodes);
lib/entities/parse_address_from_domain.dart
+1
@@ -115,6 +115,7 @@ class AddressResolver {
115 "pog",
116 "polygon",
117 "base",
118 + "arbitrum"
119 "press",
120 "privacy",
121 "pro",
lib/entities/preferences_key.dart
+2
@@ -9,6 +9,7 @@ class PreferencesKey {
9 static const currentEthereumNodeIdKey = 'current_node_id_eth';
10 static const currentPolygonNodeIdKey = 'current_node_id_matic';
11 static const currentBaseNodeIdKey = 'current_node_id_base';
12 + static const currentArbitrumNodeIdKey = 'current_node_id_arbitrum';
13 static const currentNanoNodeIdKey = 'current_node_id_nano';
14 static const currentNanoPowNodeIdKey = 'current_node_id_nano_pow';
15 static const currentDecredNodeIdKey = 'current_node_id_decred';
@@ -80,6 +81,7 @@ class PreferencesKey {
81 static const useEtherscan = 'use_etherscan';
82 static const usePolygonScan = 'use_polygonscan';
83 static const useBaseScan = 'use_base_scan';
84 + static const useArbiScan = 'use_arbitrum_scan';
85 static const useTronGrid = 'use_trongrid';
86 static const useMempoolFeeAPI = 'use_mempool_fee_api';
87 static const defaultNanoRep = 'default_nano_representative';
lib/entities/priority_for_wallet_type.dart
+2 -1
@@ -1,3 +1,4 @@
1 +import 'package:cake_wallet/arbitrum/arbitrum.dart';
2 import 'package:cake_wallet/base/base.dart';
3 import 'package:cake_wallet/bitcoin/bitcoin.dart';
4 import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
@@ -31,7 +32,7 @@ List<TransactionPriority> priorityForWalletType(WalletType type) {
32 return polygon!.getTransactionPriorities();
33 case WalletType.base:
34 return base!.getTransactionPriorities();
34 - // no such thing for nano/banano/solana/tron:
35 + case WalletType.arbitrum:
36 case WalletType.nano:
37 case WalletType.banano:
38 case WalletType.solana:
lib/ethereum/cw_ethereum.dart
+18 -8
@@ -189,8 +189,8 @@ class CWEthereum extends Ethereum {
189 ((wallet as EVMChainWallet).evmChainPrivateKey as EvmBitboxCredentials)
190 .setBitbox(service.manager, (await wallet.walletInfo.getDerivationInfo()).derivationPath);
191 } else if (service is EVMChainTrezorService) {
192 - ((wallet as EVMChainWallet).evmChainPrivateKey as EvmTrezorCredentials)
193 - .setTrezorConnect(service.connect, (await wallet.walletInfo.getDerivationInfo()).derivationPath);
192 + ((wallet as EVMChainWallet).evmChainPrivateKey as EvmTrezorCredentials).setTrezorConnect(
193 + service.connect, (await wallet.walletInfo.getDerivationInfo()).derivationPath);
194 }
195 }
196
@@ -211,15 +211,16 @@ class CWEthereum extends Ethereum {
211 return DefaultEthereumErc20Tokens().initialErc20Tokens.map((e) => e.contractAddress).toList();
212 }
213
214 -
214 @override
215 bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress) {
216 final ethereumWallet = wallet as EthereumWallet;
218 - return ethereumWallet.erc20Currencies.any((element) => element.contractAddress.toLowerCase() == contractAddress.toLowerCase());
217 + return ethereumWallet.erc20Currencies
218 + .any((element) => element.contractAddress.toLowerCase() == contractAddress.toLowerCase());
219 }
220
221 @override
222 - Future<bool> isApprovalRequired(WalletBase wallet, String tokenContract,String spender, BigInt requiredAmount) =>
222 + Future<bool> isApprovalRequired(
223 + WalletBase wallet, String tokenContract, String spender, BigInt requiredAmount) =>
224 (wallet as EVMChainWallet).isApprovalRequired(tokenContract, spender, requiredAmount);
225
226 @override
@@ -229,9 +230,10 @@ class CWEthereum extends Ethereum {
230 amount, spender, token, priority as EVMChainTransactionPriority, "ETH");
231
232 @override
232 - Future<PendingTransaction> createRawCallDataTransaction(WalletBase wallet, String to, String dataHex, BigInt valueWei,
233 - TransactionPriority priority) =>
234 - (wallet as EVMChainWallet).createCallDataTransaction(to, dataHex, valueWei, priority as EVMChainTransactionPriority);
233 + Future<PendingTransaction> createRawCallDataTransaction(WalletBase wallet, String to,
234 + String dataHex, BigInt valueWei, TransactionPriority priority) =>
235 + (wallet as EVMChainWallet).createCallDataTransaction(
236 + to, dataHex, valueWei, priority as EVMChainTransactionPriority);
237
238 // Integrations
239 @override
@@ -270,4 +272,12 @@ class CWEthereum extends Ethereum {
272 @override
273 Future<PendingTransaction> enableDEuroSaving(WalletBase wallet, TransactionPriority priority) =>
274 DEuro(wallet as EthereumWallet).enableSavings(priority as EVMChainTransactionPriority);
275 +
276 + @override
277 + String? getEthereumNativeEstimatedFee(WalletBase wallet) =>
278 + (wallet as EVMChainWallet).nativeTxEstimatedFee;
279 +
280 + @override
281 + String? getEthereumERC20EstimatedFee(WalletBase wallet) =>
282 + (wallet as EVMChainWallet).erc20TxEstimatedFee;
283 }
lib/exchange/provider/sideshift_exchange_provider.dart
+3
@@ -466,6 +466,8 @@ class SideShiftExchangeProvider extends ExchangeProvider {
466 return 'lightning';
467 case 'POL':
468 return 'polygon';
469 + case 'ARB':
470 + return 'arbitrum';
471 case 'ZEC':
472 return 'zcash';
473 case 'AVAXC':
@@ -481,6 +483,7 @@ class SideShiftExchangeProvider extends ExchangeProvider {
483 'tron' => 'TRX',
484 'lightning' => 'LN',
485 'polygon' => 'POL',
486 + 'arbitrum' => 'ARB',
487 'zcash' => 'ZEC',
488 'avax' => 'AVAXC',
489 _ => network,
lib/main.dart
+1 -1
@@ -260,7 +260,7 @@ Future<void> initializeAppConfigs({bool loadWallet = true}) async {
260 payjoinSessionSource: payjoinSessionSource,
261 anonpayInvoiceInfo: anonpayInvoiceInfo,
262 havenSeedStore: havenSeedStore,
263 - initialMigrationVersion: 52,
263 + initialMigrationVersion: 53,
264 );
265 }
266
lib/polygon/cw_polygon.dart
+76 -58
@@ -14,13 +14,14 @@ class CWPolygon extends Polygon {
14 WalletInfo? walletInfo,
15 String? password,
16 String? passphrase,
17 - }) => EVMChainNewWalletCredentials(
18 - name: name,
19 - walletInfo: walletInfo,
20 - password: password,
21 - mnemonic: mnemonic,
22 - passphrase: passphrase,
23 - );
17 + }) =>
18 + EVMChainNewWalletCredentials(
19 + name: name,
20 + walletInfo: walletInfo,
21 + password: password,
22 + mnemonic: mnemonic,
23 + passphrase: passphrase,
24 + );
25
26 @override
27 WalletCredentials createPolygonRestoreWalletFromSeedCredentials({
@@ -28,30 +29,33 @@ class CWPolygon extends Polygon {
29 required String mnemonic,
30 required String password,
31 String? passphrase,
31 - }) => EVMChainRestoreWalletFromSeedCredentials(
32 - name: name,
33 - password: password,
34 - mnemonic: mnemonic,
35 - passphrase: passphrase,
36 - );
32 + }) =>
33 + EVMChainRestoreWalletFromSeedCredentials(
34 + name: name,
35 + password: password,
36 + mnemonic: mnemonic,
37 + passphrase: passphrase,
38 + );
39
40 @override
41 WalletCredentials createPolygonRestoreWalletFromPrivateKey({
42 required String name,
43 required String privateKey,
44 required String password,
43 - }) => EVMChainRestoreWalletFromPrivateKey(name: name, password: password, privateKey: privateKey);
45 + }) =>
46 + EVMChainRestoreWalletFromPrivateKey(name: name, password: password, privateKey: privateKey);
47
48 @override
49 WalletCredentials createPolygonHardwareWalletCredentials({
50 required String name,
51 required HardwareAccountData hwAccountData,
52 WalletInfo? walletInfo,
50 - }) => EVMChainRestoreWalletFromHardware(
51 - name: name,
52 - hwAccountData: hwAccountData,
53 - walletInfo: walletInfo,
54 - );
53 + }) =>
54 + EVMChainRestoreWalletFromHardware(
55 + name: name,
56 + hwAccountData: hwAccountData,
57 + walletInfo: walletInfo,
58 + );
59
60 @override
61 String getAddress(WalletBase wallet) => (wallet as PolygonWallet).walletAddresses.address;
@@ -87,37 +91,39 @@ class CWPolygon extends Polygon {
91 required TransactionPriority priority,
92 required CryptoCurrency currency,
93 int? feeRate,
90 - }) => EVMChainTransactionCredentials(
91 - outputs
92 - .map(
93 - (out) => OutputInfo(
94 - fiatAmount: out.fiatAmount,
95 - cryptoAmount: out.cryptoAmount,
96 - address: out.address,
97 - note: out.note,
98 - sendAll: out.sendAll,
99 - extractedAddress: out.extractedAddress,
100 - isParsedAddress: out.isParsedAddress,
101 - formattedCryptoAmount: out.formattedCryptoAmount,
102 - ),
103 - )
104 - .toList(),
105 - priority: priority as EVMChainTransactionPriority,
106 - currency: currency,
107 - feeRate: feeRate,
108 - );
94 + }) =>
95 + EVMChainTransactionCredentials(
96 + outputs
97 + .map(
98 + (out) => OutputInfo(
99 + fiatAmount: out.fiatAmount,
100 + cryptoAmount: out.cryptoAmount,
101 + address: out.address,
102 + note: out.note,
103 + sendAll: out.sendAll,
104 + extractedAddress: out.extractedAddress,
105 + isParsedAddress: out.isParsedAddress,
106 + formattedCryptoAmount: out.formattedCryptoAmount,
107 + ),
108 + )
109 + .toList(),
110 + priority: priority as EVMChainTransactionPriority,
111 + currency: currency,
112 + feeRate: feeRate,
113 + );
114
115 Object createPolygonTransactionCredentialsRaw(
116 List<OutputInfo> outputs, {
117 TransactionPriority? priority,
118 required CryptoCurrency currency,
119 required int feeRate,
115 - }) => EVMChainTransactionCredentials(
116 - outputs,
117 - priority: priority as EVMChainTransactionPriority?,
118 - currency: currency,
119 - feeRate: feeRate,
120 - );
120 + }) =>
121 + EVMChainTransactionCredentials(
122 + outputs,
123 + priority: priority as EVMChainTransactionPriority?,
124 + currency: currency,
125 + feeRate: feeRate,
126 + );
127
128 @override
129 int formatterPolygonParseAmount(String amount) => EVMChainFormatter.parseEVMChainAmount(amount);
@@ -169,7 +175,8 @@ class CWPolygon extends Polygon {
175 wallet as PolygonWallet;
176
177 return wallet.erc20Currencies.firstWhere(
172 - (element) => transaction.contractAddress?.toLowerCase() == element.contractAddress?.toLowerCase(),
178 + (element) =>
179 + transaction.contractAddress?.toLowerCase() == element.contractAddress?.toLowerCase(),
180 );
181 }
182
@@ -183,12 +190,14 @@ class CWPolygon extends Polygon {
190 @override
191 String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress;
192
186 - Future<bool> isApprovalRequired(WalletBase wallet, String tokenContract,String spender, BigInt requiredAmount) =>
193 + Future<bool> isApprovalRequired(
194 + WalletBase wallet, String tokenContract, String spender, BigInt requiredAmount) =>
195 (wallet as PolygonWallet).isApprovalRequired(tokenContract, spender, requiredAmount);
196
189 - Future<PendingTransaction> createRawCallDataTransaction(WalletBase wallet, String to, String dataHex, BigInt valueWei,
190 - TransactionPriority priority) =>
191 - (wallet as EVMChainWallet).createCallDataTransaction(to, dataHex, valueWei, priority as EVMChainTransactionPriority);
197 + Future<PendingTransaction> createRawCallDataTransaction(WalletBase wallet, String to,
198 + String dataHex, BigInt valueWei, TransactionPriority priority) =>
199 + (wallet as EVMChainWallet).createCallDataTransaction(
200 + to, dataHex, valueWei, priority as EVMChainTransactionPriority);
201
202 @override
203 Future<PendingTransaction> createTokenApproval(
@@ -197,13 +206,14 @@ class CWPolygon extends Polygon {
206 String spender,
207 CryptoCurrency token,
208 TransactionPriority priority,
200 - ) => (wallet as EVMChainWallet).createApprovalTransaction(
201 - amount,
202 - spender,
203 - token,
204 - priority as EVMChainTransactionPriority,
205 - "POL",
206 - );
209 + ) =>
210 + (wallet as EVMChainWallet).createApprovalTransaction(
211 + amount,
212 + spender,
213 + token,
214 + priority as EVMChainTransactionPriority,
215 + "POL",
216 + );
217
218 @override
219 Future<void> setHardwareWalletService(WalletBase wallet, HardwareWalletService service) async {
@@ -214,8 +224,8 @@ class CWPolygon extends Polygon {
224 ((wallet as EVMChainWallet).evmChainPrivateKey as EvmBitboxCredentials)
225 .setBitbox(service.manager, (await wallet.walletInfo.getDerivationInfo()).derivationPath);
226 } else if (service is EVMChainTrezorService) {
217 - ((wallet as EVMChainWallet).evmChainPrivateKey as EvmTrezorCredentials)
218 - .setTrezorConnect(service.connect, (await wallet.walletInfo.getDerivationInfo()).derivationPath);
227 + ((wallet as EVMChainWallet).evmChainPrivateKey as EvmTrezorCredentials).setTrezorConnect(
228 + service.connect, (await wallet.walletInfo.getDerivationInfo()).derivationPath);
229 }
230 }
231
@@ -242,4 +252,12 @@ class CWPolygon extends Polygon {
252 (element) => element.contractAddress.toLowerCase() == contractAddress.toLowerCase(),
253 );
254 }
255 +
256 + @override
257 + String? getPolygonNativeEstimatedFee(WalletBase wallet) =>
258 + (wallet as EVMChainWallet).nativeTxEstimatedFee;
259 +
260 + @override
261 + String? getPolygonERC20EstimatedFee(WalletBase wallet) =>
262 + (wallet as EVMChainWallet).erc20TxEstimatedFee;
263 }
lib/reactions/fiat_rate_update.dart
+5
@@ -1,4 +1,5 @@
1 import 'dart:async';
2 +import 'package:cake_wallet/arbitrum/arbitrum.dart';
3 import 'package:cake_wallet/base/base.dart';
4 import 'package:cake_wallet/core/fiat_conversion_service.dart';
5 import 'package:cake_wallet/entities/fiat_api_mode.dart';
@@ -49,6 +50,10 @@ Future<void> startFiatRateUpdate(
50 currencies =
51 base!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
52 }
53 + if (appStore.wallet!.type == WalletType.arbitrum) {
54 + currencies =
55 + arbitrum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
56 + }
57
58 if (appStore.wallet!.type == WalletType.solana) {
59 currencies =
lib/reactions/on_current_wallet_change.dart
+5
@@ -1,3 +1,4 @@
1 +import 'package:cake_wallet/arbitrum/arbitrum.dart';
2 import 'package:cake_wallet/base/base.dart';
3 import 'package:cake_wallet/bitcoin/bitcoin.dart';
4 import 'package:cake_wallet/di.dart';
@@ -133,6 +134,10 @@ void startCurrentWalletChangeReaction(
134 currencies =
135 base!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
136 }
137 + if (wallet.type == WalletType.arbitrum) {
138 + currencies =
139 + arbitrum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
140 + }
141 if (wallet.type == WalletType.solana) {
142 currencies =
143 solana!.getSPLTokenCurrencies(appStore.wallet!).where((element) => element.enabled);
lib/reactions/wallet_connect.dart
+12
@@ -9,6 +9,7 @@ bool isEVMCompatibleChain(WalletType walletType) {
9 case WalletType.polygon:
10 case WalletType.ethereum:
11 case WalletType.base:
12 + case WalletType.arbitrum:
13 return true;
14 default:
15 return false;
@@ -21,6 +22,7 @@ bool isNFTACtivatedChain(WalletType walletType) {
22 case WalletType.ethereum:
23 case WalletType.base:
24 case WalletType.solana:
25 + case WalletType.arbitrum:
26 return true;
27 default:
28 return false;
@@ -33,6 +35,7 @@ bool isWalletConnectCompatibleChain(WalletType walletType) {
35 case WalletType.ethereum:
36 case WalletType.base:
37 case WalletType.solana:
38 + case WalletType.arbitrum:
39 return true;
40 default:
41 return false;
@@ -47,6 +50,8 @@ String getChainNameSpaceAndIdBasedOnWalletType(WalletType walletType) {
50 return EVMChainId.polygon.chain();
51 case WalletType.base:
52 return EVMChainId.base.chain();
53 + case WalletType.arbitrum:
54 + return EVMChainId.arbitrum.chain();
55 case WalletType.solana:
56 return SolanaChainId.mainnet.chain();
57 default:
@@ -59,6 +64,7 @@ List<String> getChainSupportedMethodsOnWalletType(WalletType walletType) {
64 case WalletType.ethereum:
65 case WalletType.polygon:
66 case WalletType.base:
67 + case WalletType.arbitrum:
68 return EVMSupportedMethods.values.map((e) => e.name).toList();
69 case WalletType.solana:
70 return SolanaSupportedMethods.values.map((e) => e.name).toList();
@@ -73,6 +79,8 @@ int getChainIdBasedOnWalletType(WalletType walletType) {
79 return 137;
80 case WalletType.base:
81 return 8453;
82 + case WalletType.arbitrum:
83 + return 42161;
84 // For now, we return eth chain Id as the default, we'll modify as we add more wallets
85 case WalletType.ethereum:
86 default:
@@ -88,6 +96,8 @@ String getChainNameBasedOnWalletType(WalletType walletType) {
96 return 'polygon';
97 case WalletType.base:
98 return 'base';
99 + case WalletType.arbitrum:
100 + return 'arbitrum';
101 case WalletType.solana:
102 return 'mainnet';
103 default:
@@ -103,6 +113,8 @@ String getTokenNameBasedOnWalletType(WalletType walletType) {
113 return 'MATIC';
114 case WalletType.base:
115 return 'BASE';
116 + case WalletType.arbitrum:
117 + return 'ARB';
118 case WalletType.solana:
119 return 'SOL';
120 default:
lib/reactions/wallet_utils.dart
+2
@@ -5,6 +5,7 @@ bool isBIP39Wallet(WalletType walletType) {
5 case WalletType.ethereum:
6 case WalletType.polygon:
7 case WalletType.base:
8 + case WalletType.arbitrum:
9 case WalletType.solana:
10 case WalletType.tron:
11 case WalletType.bitcoin:
@@ -43,6 +44,7 @@ bool hasTokens(WalletType walletType) {
44 case WalletType.tron:
45 case WalletType.zano:
46 case WalletType.base:
47 + case WalletType.arbitrum:
48 return true;
49 default:
50 return false;
lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart
+3
@@ -39,6 +39,7 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
39 final polygonIcon = Image.asset('assets/images/crypto/polygon.webp', height: 24, width: 24);
40 final bitcoinCashIcon = Image.asset('assets/images/crypto/bitcoin-cash.webp', height: 24, width: 24);
41 final baseIcon = Image.asset('assets/images/crypto/base_icon.webp', height: 24, width: 24);
42 + final arbitrumIcon = Image.asset('assets/images/crypto/arbitrum.webp', height: 24, width: 24);
43 final nanoIcon = Image.asset('assets/images/crypto/nano.webp', height: 24, width: 24);
44 final bananoIcon = Image.asset('assets/images/crypto/nano.webp', height: 24, width: 24);
45 final solanaIcon = Image.asset('assets/images/crypto/solana.webp', height: 24, width: 24);
@@ -187,6 +188,8 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
188 return dogeIcon;
189 case WalletType.base:
190 return baseIcon;
191 + case WalletType.arbitrum:
192 + return arbitrumIcon;
193 case WalletType.none:
194 return nonWalletTypeIcon;
195 }
lib/src/screens/dashboard/widgets/menu_widget.dart
+4
@@ -34,6 +34,7 @@ class MenuWidgetState extends State<MenuWidget> {
34 this.bitcoinCashIcon = Image.asset('assets/images/crypto/bitcoin-cash.webp'),
35 this.polygonIcon = Image.asset('assets/images/crypto/polygon.webp'),
36 this.baseIcon = Image.asset('assets/images/crypto/base_icon.webp'),
37 + this.arbitrumIcon = Image.asset('assets/images/crypto/arbitrum.webp'),
38 this.solanaIcon = Image.asset('assets/images/crypto/solana.webp'),
39 this.tronIcon = Image.asset('assets/images/crypto/tron.webp'),
40 this.wowneroIcon = Image.asset('assets/images/crypto/wownero.webp'),
@@ -62,6 +63,7 @@ class MenuWidgetState extends State<MenuWidget> {
63 Image bananoIcon;
64 Image polygonIcon;
65 Image baseIcon;
66 + Image arbitrumIcon;
67 Image solanaIcon;
68 Image tronIcon;
69 Image wowneroIcon;
@@ -251,6 +253,8 @@ class MenuWidgetState extends State<MenuWidget> {
253 return solanaIcon;
254 case WalletType.base:
255 return baseIcon;
256 + case WalletType.arbitrum:
257 + return arbitrumIcon;
258 case WalletType.tron:
259 return tronIcon;
260 case WalletType.wownero:
lib/src/screens/exchange_trade/widgets/exchange_trade_card_item_widget.dart
+4 -4
@@ -60,7 +60,7 @@ class ExchangeTradeCardItemWidget extends StatelessWidget {
60 FeeSelectionWidget(
61 feesViewModel: feesViewModel,
62 output: output,
63 - onTap: () => pickTransactionPriority(context),
63 + onTap: () => pickTransactionPriority(context, output),
64 ),
65 if (exchangeTradeViewModel.sendViewModel.hasCoinControl)
66 CoinControlWidget(
@@ -75,7 +75,7 @@ class ExchangeTradeCardItemWidget extends StatelessWidget {
75 );
76 }
77
78 - Future<void> pickTransactionPriority(BuildContext context) async {
78 + Future<void> pickTransactionPriority(BuildContext context, Output output) async {
79 final items = priorityForWalletType(feesViewModel.walletType);
80 final selectedItem = items.indexOf(feesViewModel.transactionPriority);
81 final customItemIndex = feesViewModel.getCustomPriorityIndex(items);
@@ -102,10 +102,10 @@ class ExchangeTradeCardItemWidget extends StatelessWidget {
102 mainAxisAlignment: MainAxisAlignment.center,
103 sliderValue: customFeeRate,
104 onSliderChanged: (double newValue) => setState(() => customFeeRate = newValue),
105 - onItemSelected: (TransactionPriority priority) {
105 + onItemSelected: (TransactionPriority priority) async {
106 feesViewModel.setTransactionPriority(priority);
107 setState(() => selectedIdx = items.indexOf(priority));
108 -
108 + await output.calculateEstimatedFee();
109 if (feesViewModel.isLowFee) {
110 _showFeeAlert(context);
111 }
lib/src/screens/send/widgets/send_card.dart
+5 -6
@@ -542,7 +542,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
542 builder: (_) => GestureDetector(
543 key: ValueKey('send_page_select_fee_priority_button_key'),
544 onTap: sendViewModel.feesViewModel.hasFeesPriority
545 - ? () => pickTransactionPriority(context)
545 + ? () => pickTransactionPriority(context, output)
546 : () {},
547 child: Container(
548 padding: EdgeInsets.only(top: 24),
@@ -799,9 +799,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
799 }
800 }
801
802 - final parsedAddress = output.isParsedAddress
803 - ? output.extractedAddress
804 - : output.address;
802 + final parsedAddress = output.isParsedAddress ? output.extractedAddress : output.address;
803
804 _lastHandledAddress = current;
805 await _handlePaymentFlow(
@@ -837,7 +835,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
835 _effectsInstalled = true;
836 }
837
840 - Future<void> pickTransactionPriority(BuildContext context) async {
838 + Future<void> pickTransactionPriority(BuildContext context, Output output) async {
839 final items = priorityForWalletType(sendViewModel.walletType);
840 final selectedItem = items.indexOf(sendViewModel.feesViewModel.transactionPriority);
841 final customItemIndex = sendViewModel.feesViewModel.getCustomPriorityIndex(items);
@@ -867,9 +865,10 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
865 mainAxisAlignment: MainAxisAlignment.center,
866 sliderValue: customFeeRate,
867 onSliderChanged: (double newValue) => setState(() => customFeeRate = newValue),
870 - onItemSelected: (TransactionPriority priority) {
868 + onItemSelected: (TransactionPriority priority) async {
869 sendViewModel.feesViewModel.setTransactionPriority(priority);
870 setState(() => selectedIdx = items.indexOf(priority));
871 + await output.calculateEstimatedFee();
872 },
873 );
874 },
lib/src/screens/settings/privacy_page.dart
+8
@@ -126,6 +126,14 @@ class PrivacyPage extends BasePage {
126 _privacySettingsViewModel.setUseBaseScan(value);
127 },
128 ),
129 + if (_privacySettingsViewModel.canUseArbiScan)
130 + SettingsSwitcherCell(
131 + title: S.current.arbiscan_history,
132 + value: _privacySettingsViewModel.useArbiScan,
133 + onValueChange: (BuildContext _, bool value) {
134 + _privacySettingsViewModel.setUseArbiScan(value);
135 + },
136 + ),
137 if (_privacySettingsViewModel.canUseTronGrid)
138 SettingsSwitcherCell(
139 title: S.current.trongrid_history,
lib/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart
+5
@@ -1,3 +1,4 @@
1 +import 'package:cake_wallet/arbitrum/arbitrum.dart';
2 import 'package:cake_wallet/base/base.dart';
3 import 'package:cake_wallet/ethereum/ethereum.dart';
4 import 'package:cake_wallet/polygon/polygon.dart';
@@ -26,6 +27,8 @@ class KeyServiceImpl implements WalletConnectKeyService {
27 return polygon!.getPrivateKey(wallet);
28 case WalletType.base:
29 return base!.getPrivateKey(wallet);
30 + case WalletType.arbitrum:
31 + return arbitrum!.getPrivateKey(wallet);
32 case WalletType.solana:
33 return solana!.getPrivateKey(wallet);
34 default:
@@ -41,6 +44,8 @@ class KeyServiceImpl implements WalletConnectKeyService {
44 return polygon!.getPublicKey(wallet);
45 case WalletType.base:
46 return base!.getPublicKey(wallet);
47 + case WalletType.arbitrum:
48 + return arbitrum!.getPublicKey(wallet);
49 case WalletType.solana:
50 return solana!.getPublicKey(wallet);
51 default:
lib/store/settings_store.dart
+28
@@ -110,6 +110,7 @@ abstract class SettingsStoreBase with Store {
110 required this.pinNativeTokenAtTop,
111 required this.useEtherscan,
112 required this.useBaseScan,
113 + required this.useArbiScan,
114 required this.usePolygonScan,
115 required this.useTronGrid,
116 required this.useMempoolFeeAPI,
@@ -467,6 +468,11 @@ abstract class SettingsStoreBase with Store {
468 (_) => useBaseScan,
469 (bool useBaseScan) =>
470 _sharedPreferences.setBool(PreferencesKey.useBaseScan, useBaseScan));
471 +
472 + reaction(
473 + (_) => useArbiScan,
474 + (bool useArbiScan) =>
475 + _sharedPreferences.setBool(PreferencesKey.useArbiScan, useArbiScan));
476
477 reaction((_) => useTronGrid,
478 (bool useTronGrid) => _sharedPreferences.setBool(PreferencesKey.useTronGrid, useTronGrid));
@@ -826,6 +832,9 @@ abstract class SettingsStoreBase with Store {
832 @observable
833 bool useBaseScan;
834
835 + @observable
836 + bool useArbiScan;
837 +
838 @observable
839 bool useTronGrid;
840
@@ -1067,6 +1076,7 @@ abstract class SettingsStoreBase with Store {
1076 final useEtherscan = sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true;
1077 final usePolygonScan = sharedPreferences.getBool(PreferencesKey.usePolygonScan) ?? true;
1078 final useBaseScan = sharedPreferences.getBool(PreferencesKey.useBaseScan) ?? true;
1079 + final useArbiScan = sharedPreferences.getBool(PreferencesKey.useArbiScan) ?? true;
1080 final useTronGrid = sharedPreferences.getBool(PreferencesKey.useTronGrid) ?? true;
1081 final useMempoolFeeAPI = sharedPreferences.getBool(PreferencesKey.useMempoolFeeAPI) ?? true;
1082 final defaultNanoRep = sharedPreferences.getString(PreferencesKey.defaultNanoRep) ?? "";
@@ -1113,6 +1123,7 @@ abstract class SettingsStoreBase with Store {
1123 final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
1124 final polygonNodeId = sharedPreferences.getInt(PreferencesKey.currentPolygonNodeIdKey);
1125 final baseNodeId = sharedPreferences.getInt(PreferencesKey.currentBaseNodeIdKey);
1126 + final arbitrumNodeId = sharedPreferences.getInt(PreferencesKey.currentArbitrumNodeIdKey);
1127 final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
1128 final nanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey);
1129 final solanaNodeId = sharedPreferences.getInt(PreferencesKey.currentSolanaNodeIdKey);
@@ -1135,6 +1146,8 @@ abstract class SettingsStoreBase with Store {
1146 nodeSource.values.firstWhereOrNull((e) => e.uriRaw == polygonDefaultNodeUri);
1147 final baseNode = nodeSource.get(baseNodeId) ??
1148 nodeSource.values.firstWhereOrNull((e) => e.uriRaw == baseDefaultNodeUri);
1149 + final arbitrumNode = nodeSource.get(arbitrumNodeId) ??
1150 + nodeSource.values.firstWhereOrNull((e) => e.uriRaw == arbitrumDefaultNodeUri);
1151 final bitcoinCashElectrumServer = nodeSource.get(bitcoinCashElectrumServerId) ??
1152 nodeSource.values.firstWhereOrNull((e) => e.uriRaw == cakeWalletBitcoinCashDefaultNodeUri);
1153 final nanoNode = nodeSource.get(nanoNodeId) ??
@@ -1212,6 +1225,10 @@ abstract class SettingsStoreBase with Store {
1225 nodes[WalletType.base] = baseNode;
1226 }
1227
1228 + if (arbitrumNode != null) {
1229 + nodes[WalletType.arbitrum] = arbitrumNode;
1230 + }
1231 +
1232 if (bitcoinCashElectrumServer != null) {
1233 nodes[WalletType.bitcoinCash] = bitcoinCashElectrumServer;
1234 }
@@ -1391,6 +1408,7 @@ abstract class SettingsStoreBase with Store {
1408 useEtherscan: useEtherscan,
1409 usePolygonScan: usePolygonScan,
1410 useBaseScan: useBaseScan,
1411 + useArbiScan: useArbiScan,
1412 useTronGrid: useTronGrid,
1413 useMempoolFeeAPI: useMempoolFeeAPI,
1414 defaultNanoRep: defaultNanoRep,
@@ -1588,6 +1606,7 @@ abstract class SettingsStoreBase with Store {
1606 useEtherscan = sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true;
1607 usePolygonScan = sharedPreferences.getBool(PreferencesKey.usePolygonScan) ?? true;
1608 useBaseScan = sharedPreferences.getBool(PreferencesKey.useBaseScan) ?? true;
1609 + useArbiScan = sharedPreferences.getBool(PreferencesKey.useArbiScan) ?? true;
1610 useTronGrid = sharedPreferences.getBool(PreferencesKey.useTronGrid) ?? true;
1611 useMempoolFeeAPI = sharedPreferences.getBool(PreferencesKey.useMempoolFeeAPI) ?? true;
1612 defaultNanoRep = sharedPreferences.getString(PreferencesKey.defaultNanoRep) ?? "";
@@ -1619,6 +1638,7 @@ abstract class SettingsStoreBase with Store {
1638 final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
1639 final polygonNodeId = sharedPreferences.getInt(PreferencesKey.currentPolygonNodeIdKey);
1640 final baseNodeId = sharedPreferences.getInt(PreferencesKey.currentBaseNodeIdKey);
1641 + final arbitrumNodeId = sharedPreferences.getInt(PreferencesKey.currentArbitrumNodeIdKey);
1642 final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
1643 final solanaNodeId = sharedPreferences.getInt(PreferencesKey.currentSolanaNodeIdKey);
1644 final tronNodeId = sharedPreferences.getInt(PreferencesKey.currentTronNodeIdKey);
@@ -1633,6 +1653,7 @@ abstract class SettingsStoreBase with Store {
1653 final ethereumNode = nodeSource.get(ethereumNodeId);
1654 final polygonNode = nodeSource.get(polygonNodeId);
1655 final baseNode = nodeSource.get(baseNodeId);
1656 + final arbitrumNode = nodeSource.get(arbitrumNodeId);
1657 final bitcoinCashNode = nodeSource.get(bitcoinCashElectrumServerId);
1658 final nanoNode = nodeSource.get(nanoNodeId);
1659 final solanaNode = nodeSource.get(solanaNodeId);
@@ -1670,6 +1691,10 @@ abstract class SettingsStoreBase with Store {
1691 nodes[WalletType.base] = baseNode;
1692 }
1693
1694 + if (arbitrumNode != null) {
1695 + nodes[WalletType.arbitrum] = arbitrumNode;
1696 + }
1697 +
1698 if (bitcoinCashNode != null) {
1699 nodes[WalletType.bitcoinCash] = bitcoinCashNode;
1700 }
@@ -1833,6 +1858,9 @@ abstract class SettingsStoreBase with Store {
1858 case WalletType.base:
1859 await _sharedPreferences.setInt(PreferencesKey.currentBaseNodeIdKey, node.key as int);
1860 break;
1861 + case WalletType.arbitrum:
1862 + await _sharedPreferences.setInt(PreferencesKey.currentArbitrumNodeIdKey, node.key as int);
1863 + break;
1864 case WalletType.solana:
1865 await _sharedPreferences.setInt(PreferencesKey.currentSolanaNodeIdKey, node.key as int);
1866 break;
lib/utils/qr_util.dart
+2
@@ -30,6 +30,8 @@ String getQrImage(WalletType type) {
30 return 'assets/images/doge_chain_qr.svg';
31 case WalletType.base:
32 return 'assets/images/base_chain_QR.svg';
33 + case WalletType.arbitrum:
34 + return 'assets/images/arbitrum_chain_QR.svg';
35 case WalletType.banano:
36 case WalletType.haven:
37 case WalletType.none:
lib/utils/token_utilities.dart
+2 -1
@@ -116,6 +116,7 @@ class TokenUtilities {
116 WalletType.ethereum => '${walletKey}_${Erc20Token.ethereumBoxName}',
117 WalletType.polygon => '${walletKey}_${Erc20Token.polygonBoxName}',
118 WalletType.base => '${walletKey}_${Erc20Token.baseBoxName}',
119 + WalletType.arbitrum => '${walletKey}_${Erc20Token.arbitrumBoxName}',
120 _ => '${walletKey}_${Erc20Token.ethereumBoxName}',
121 };
122
@@ -193,7 +194,7 @@ class TokenUtilities {
194 }
195
196 // Arbitrum One
196 - if (title == 'arbitrum' || title == 'arb' || tag == 'arbitrum') {
197 + if (title == 'arbitrum' || title == 'arb' || tag == 'arb') {
198 return 42161;
199 }
200
lib/view_model/advanced_privacy_settings_view_model.dart
+2
@@ -40,6 +40,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
40 case WalletType.dogecoin:
41 case WalletType.polygon:
42 case WalletType.base:
43 + case WalletType.arbitrum:
44 case WalletType.solana:
45 case WalletType.tron:
46 return true;
@@ -81,6 +82,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
82 WalletType.ethereum,
83 WalletType.polygon,
84 WalletType.base,
85 + WalletType.arbitrum,
86 WalletType.tron,
87 WalletType.solana,
88 WalletType.monero,
lib/view_model/dashboard/balance_view_model.dart
+1
@@ -167,6 +167,7 @@ abstract class BalanceViewModelBase with Store {
167 case WalletType.ethereum:
168 case WalletType.polygon:
169 case WalletType.base:
170 + case WalletType.arbitrum:
171 case WalletType.solana:
172 case WalletType.tron:
173 return S.current.xmr_full_balance;
lib/view_model/dashboard/dashboard_view_model.dart
+1
@@ -879,6 +879,7 @@ abstract class DashboardViewModelBase with Store {
879 case WalletType.ethereum:
880 case WalletType.polygon:
881 case WalletType.base:
882 + case WalletType.arbitrum:
883 case WalletType.solana:
884 case WalletType.nano:
885 case WalletType.banano:
lib/view_model/dashboard/home_settings_view_model.dart
+45 -2
@@ -1,5 +1,6 @@
1 import 'dart:convert';
2
3 +import 'package:cake_wallet/arbitrum/arbitrum.dart';
4 import 'package:cake_wallet/base/base.dart';
5 import 'package:cake_wallet/core/fiat_conversion_service.dart';
6 import 'package:cake_wallet/entities/fiat_api_mode.dart';
@@ -111,6 +112,17 @@ abstract class HomeSettingsViewModelBase with Store {
112 );
113 await base!.addErc20Token(_balanceViewModel.wallet, baseToken);
114 }
115 + if (_balanceViewModel.wallet.type == WalletType.arbitrum) {
116 + final arbitrumToken = Erc20Token(
117 + name: token.name,
118 + symbol: token.title,
119 + decimal: token.decimals,
120 + contractAddress: contractAddress.toLowerCase(),
121 + iconPath: token.iconPath,
122 + isPotentialScam: token.isPotentialScam,
123 + );
124 + await arbitrum!.addErc20Token(_balanceViewModel.wallet, arbitrumToken);
125 + }
126
127 if (_balanceViewModel.wallet.type == WalletType.solana) {
128 final splToken = token.copyWith(enabled: true);
@@ -153,6 +165,10 @@ abstract class HomeSettingsViewModelBase with Store {
165 return base!.isTokenAlreadyAdded(_balanceViewModel.wallet, contractAddress);
166 }
167
168 + if (_balanceViewModel.wallet.type == WalletType.arbitrum) {
169 + return arbitrum!.isTokenAlreadyAdded(_balanceViewModel.wallet, contractAddress);
170 + }
171 +
172 if (_balanceViewModel.wallet.type == WalletType.solana) {
173 return solana!.isTokenAlreadyAdded(_balanceViewModel.wallet, contractAddress);
174 }
@@ -184,6 +200,10 @@ abstract class HomeSettingsViewModelBase with Store {
200 await base!.deleteErc20Token(_balanceViewModel.wallet, token as Erc20Token);
201 }
202
203 + if (_balanceViewModel.wallet.type == WalletType.arbitrum) {
204 + await arbitrum!.deleteErc20Token(_balanceViewModel.wallet, token as Erc20Token);
205 + }
206 +
207 if (_balanceViewModel.wallet.type == WalletType.solana) {
208 await solana!.deleteSPLToken(_balanceViewModel.wallet, token);
209 }
@@ -240,6 +260,8 @@ abstract class HomeSettingsViewModelBase with Store {
260 break;
261 case WalletType.base:
262 defaultTokenAddresses = base!.getDefaultTokenContractAddresses();
263 + case WalletType.arbitrum:
264 + defaultTokenAddresses = arbitrum!.getDefaultTokenContractAddresses();
265 break;
266 case WalletType.solana:
267 defaultTokenAddresses = solana!.getDefaultTokenContractAddresses();
@@ -380,6 +402,10 @@ abstract class HomeSettingsViewModelBase with Store {
402 return await base!.getErc20Token(_balanceViewModel.wallet, contractAddress);
403 }
404
405 + if (_balanceViewModel.wallet.type == WalletType.arbitrum) {
406 + return await arbitrum!.getErc20Token(_balanceViewModel.wallet, contractAddress);
407 + }
408 +
409 if (_balanceViewModel.wallet.type == WalletType.solana) {
410 return await solana!.getSPLToken(_balanceViewModel.wallet, contractAddress);
411 }
@@ -426,6 +452,11 @@ abstract class HomeSettingsViewModelBase with Store {
452 if (!value) base!.removeTokenTransactionsInHistory(_balanceViewModel.wallet, token);
453 }
454
455 + if (_balanceViewModel.wallet.type == WalletType.arbitrum) {
456 + arbitrum!.addErc20Token(_balanceViewModel.wallet, token as Erc20Token);
457 + if (!value) arbitrum!.removeTokenTransactionsInHistory(_balanceViewModel.wallet, token);
458 + }
459 +
460 if (_balanceViewModel.wallet.type == WalletType.solana) {
461 final address = solana!.getTokenAddress(token);
462 solana!.addSPLToken(_balanceViewModel.wallet, token, address);
@@ -487,6 +518,14 @@ abstract class HomeSettingsViewModelBase with Store {
518 ..sort(_sortFunc));
519 }
520
521 + if (_balanceViewModel.wallet.type == WalletType.arbitrum) {
522 + tokens.addAll(arbitrum!
523 + .getERC20Currencies(_balanceViewModel.wallet)
524 + .where((element) => _matchesSearchText(element))
525 + .toList()
526 + ..sort(_sortFunc));
527 + }
528 +
529 if (_balanceViewModel.wallet.type == WalletType.solana) {
530 tokens.addAll(solana!
531 .getSPLTokenCurrencies(_balanceViewModel.wallet)
@@ -528,7 +567,7 @@ abstract class HomeSettingsViewModelBase with Store {
567 bool _matchesSearchText(CryptoCurrency asset) {
568 final address = getTokenAddressBasedOnWallet(asset);
569
531 - // The homes settings would only be displayed for either of Tron, Ethereum, Polygon, Base or Solana Wallets.
570 + // The homes settings would only be displayed for either of Tron, EVM or Solana Wallets.
571 if (address == null) return false;
572
573 return searchText.isEmpty ||
@@ -558,11 +597,15 @@ abstract class HomeSettingsViewModelBase with Store {
597 return base!.getTokenAddress(asset);
598 }
599
600 + if (_balanceViewModel.wallet.type == WalletType.arbitrum) {
601 + return arbitrum!.getTokenAddress(asset);
602 + }
603 +
604 if (_balanceViewModel.wallet.type == WalletType.zano) {
605 return zano!.getZanoAssetAddress(asset);
606 }
607
565 - // 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).
608 + // We return null if it's neither Tron, EVM or Solana wallet (which is actually impossible because we only display home settings for either of these four wallets).
609 return null;
610 }
611 }
lib/view_model/dashboard/transaction_list_item.dart
+12
@@ -1,3 +1,4 @@
1 +import 'package:cake_wallet/arbitrum/arbitrum.dart';
2 import 'package:cake_wallet/base/base.dart';
3 import 'package:cake_wallet/decred/decred.dart';
4 import 'package:cake_wallet/entities/balance_display_mode.dart';
@@ -139,6 +140,10 @@ class TransactionListItem extends ActionListItem with Keyable {
140 final asset = base!.assetOfTransaction(balanceViewModel.wallet, transaction);
141 return asset;
142 }
143 + if (balanceViewModel.wallet.type == WalletType.arbitrum) {
144 + final asset = arbitrum!.assetOfTransaction(balanceViewModel.wallet, transaction);
145 + return asset;
146 + }
147
148 if (balanceViewModel.wallet.type == WalletType.solana) {
149 final asset = solana!.assetOfTransaction(balanceViewModel.wallet, transaction);
@@ -199,6 +204,13 @@ class TransactionListItem extends ActionListItem with Keyable {
204 cryptoAmount: base!.formatterBaseAmountToDouble(transaction: transaction),
205 price: price);
206 break;
207 + case WalletType.arbitrum:
208 + final asset = arbitrum!.assetOfTransaction(balanceViewModel.wallet, transaction);
209 + final price = balanceViewModel.fiatConvertationStore.prices[asset];
210 + amount = calculateFiatAmountRaw(
211 + cryptoAmount: arbitrum!.formatterArbitrumAmountToDouble(transaction: transaction),
212 + price: price);
213 + break;
214 case WalletType.nano:
215 amount = calculateFiatAmountRaw(
216 cryptoAmount: double.parse(nanoUtil!.getRawAsUsableString(
lib/view_model/exchange/exchange_trade_view_model.dart
+20 -13
@@ -18,6 +18,7 @@ import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
18 import 'package:cake_wallet/exchange/provider/xoswap_exchange_provider.dart';
19 import 'package:cake_wallet/exchange/trade.dart';
20 import 'package:cake_wallet/generated/i18n.dart';
21 +import 'package:cake_wallet/arbitrum/arbitrum.dart';
22 import 'package:cake_wallet/reactions/wallet_connect.dart';
23 import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_item.dart';
24 import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
@@ -109,14 +110,10 @@ abstract class ExchangeTradeViewModelBase with Store {
110 @observable
111 bool isSendable;
112
112 -
113 - bool get isSwapsXyzSendingEVMTokenSwap => (_provider is SwapsXyzExchangeProvider) &&
114 - (wallet.type == WalletType.ethereum &&
115 - wallet.currency != trade.from ||
116 - (wallet.type == WalletType.polygon &&
117 - wallet.currency != trade.from) ||
118 - (wallet.type == WalletType.base &&
119 - wallet.currency != trade.from));
113 + bool get isSwapsXyzSendingEVMTokenSwap =>
114 + (_provider is SwapsXyzExchangeProvider) &&
115 + isEVMCompatibleChain(wallet.type) &&
116 + wallet.currency != trade.from;
117
118 String get extraInfo => trade.extraId != null && trade.extraId!.isNotEmpty
119 ? '\n\n' + S.current.exchange_extra_info
@@ -203,7 +200,8 @@ abstract class ExchangeTradeViewModelBase with Store {
200
201 sendViewModel.selectedCryptoCurrency = selected;
202
206 - final pendingTransaction = await sendViewModel.createTransaction(provider: _provider, trade: trade);
203 + final pendingTransaction =
204 + await sendViewModel.createTransaction(provider: _provider, trade: trade);
205
206 if (_provider is SwapsXyzExchangeProvider) {
207 final hash = pendingTransaction?.evmTxHashFromRawHex ?? pendingTransaction?.id ?? '';
@@ -336,6 +334,9 @@ abstract class ExchangeTradeViewModelBase with Store {
334 bool _isBaseToken() =>
335 wallet.currency == CryptoCurrency.baseEth && tradeFrom?.tag == CryptoCurrency.baseEth.tag;
336
337 + bool _isArbitrumToken() =>
338 + wallet.currency == CryptoCurrency.arbEth && tradeFrom?.tag == CryptoCurrency.arbEth.tag;
339 +
340 bool _isTronToken() =>
341 wallet.currency == CryptoCurrency.trx && tradeFrom?.tag == CryptoCurrency.trx.title;
342
@@ -348,9 +349,10 @@ abstract class ExchangeTradeViewModelBase with Store {
349 _isPolygonToken() ||
350 _isSplToken() ||
351 _isTronToken() ||
351 - _isBaseToken();
352 + _isBaseToken() ||
353 + _isArbitrumToken();
354 }
353 -
355 +
356 Future<void> registerSwapsXyzTransaction() async {
357 try {
358 if (!(_provider is SwapsXyzExchangeProvider)) return;
@@ -366,7 +368,9 @@ abstract class ExchangeTradeViewModelBase with Store {
368 return;
369 }
370
369 - final txHash = sendViewModel.pendingTransaction?.evmTxHashFromRawHex ?? sendViewModel.pendingTransaction?.id ?? '';
371 + final txHash = sendViewModel.pendingTransaction?.evmTxHashFromRawHex ??
372 + sendViewModel.pendingTransaction?.id ??
373 + '';
374
375 if (txHash.isEmpty) {
376 printV('SwapsXyz: transaction register: skipped (txHash empty)');
@@ -379,7 +383,8 @@ abstract class ExchangeTradeViewModelBase with Store {
383 return;
384 }
385
382 - printV('SwapsXyz: attempting to register transaction: tradeId = ${trade.id}, txHash = $txHash, chainId = $chainId, vmId = $vmId');
386 + printV(
387 + 'SwapsXyz: attempting to register transaction: tradeId = ${trade.id}, txHash = $txHash, chainId = $chainId, vmId = $vmId');
388
389 final registered = await swaps.registerAltVmTx(
390 txId: trade.id,
@@ -423,6 +428,8 @@ abstract class ExchangeTradeViewModelBase with Store {
428 return PolygonURI(amount: amount, address: inputAddress);
429 case WalletType.base:
430 return BaseURI(amount: amount, address: inputAddress);
431 + case WalletType.arbitrum:
432 + return ArbitrumURI(amount: amount, address: inputAddress);
433 case WalletType.solana:
434 return SolanaURI(amount: amount, address: inputAddress);
435 case WalletType.tron:
lib/view_model/exchange/exchange_view_model.dart
+4
@@ -878,6 +878,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
878 depositCurrency = CryptoCurrency.baseEth;
879 receiveCurrency = CryptoCurrency.xmr;
880 break;
881 + case WalletType.arbitrum:
882 + depositCurrency = CryptoCurrency.arbEth;
883 + receiveCurrency = CryptoCurrency.xmr;
884 + break;
885 case WalletType.solana:
886 depositCurrency = CryptoCurrency.sol;
887 receiveCurrency = CryptoCurrency.xmr;
lib/view_model/node_list/node_create_or_edit_view_model.dart
+1
@@ -81,6 +81,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
81 case WalletType.ethereum:
82 case WalletType.polygon:
83 case WalletType.base:
84 + case WalletType.arbitrum:
85 case WalletType.solana:
86 case WalletType.banano:
87 case WalletType.nano:
lib/view_model/payment/payment_view_model.dart
-1
@@ -5,7 +5,6 @@ import 'package:cake_wallet/store/app_store.dart';
5 import 'package:cw_core/wallet_type.dart';
6 import 'package:cw_core/wallet_info.dart';
7 import 'package:cw_core/utils/print_verbose.dart';
8 -import 'package:hive/hive.dart';
8 import 'package:mobx/mobx.dart';
9
10 part 'payment_view_model.g.dart';
lib/view_model/restore/wallet_restore_from_qr_code.dart
+1
@@ -32,6 +32,7 @@ class WalletRestoreFromQRCode {
32 'ethereum-wallet': WalletType.ethereum,
33 'polygon-wallet': WalletType.polygon,
34 'base-wallet': WalletType.base,
35 + 'arbitrum-wallet': WalletType.arbitrum,
36 'nano-wallet': WalletType.nano,
37 'nano_wallet': WalletType.nano,
38 'bitcoincash': WalletType.bitcoinCash,
lib/view_model/send/fees_view_model.dart
+4 -1
@@ -1,3 +1,4 @@
1 +import 'package:cake_wallet/arbitrum/arbitrum.dart';
2 import 'package:cake_wallet/base/base.dart';
3 import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
4 import 'package:cake_wallet/decred/decred.dart';
@@ -102,6 +103,7 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor
103 case WalletType.banano:
104 case WalletType.solana:
105 case WalletType.tron:
106 + case WalletType.arbitrum:
107 return false;
108 }
109 }
@@ -119,7 +121,8 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor
121 wallet.type != WalletType.nano &&
122 wallet.type != WalletType.banano &&
123 wallet.type != WalletType.solana &&
122 - wallet.type != WalletType.tron;
124 + wallet.type != WalletType.tron &&
125 + wallet.type != WalletType.arbitrum;
126
127 @computed
128 bool get isElectrumWallet =>
lib/view_model/send/output.dart
+75 -31
@@ -28,6 +28,7 @@ import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
28 import 'package:cake_wallet/store/settings_store.dart';
29 import 'package:cake_wallet/generated/i18n.dart';
30 import 'package:cake_wallet/bitcoin/bitcoin.dart';
31 +import 'package:cake_wallet/arbitrum/arbitrum.dart';
32
33 import 'package:cake_wallet/entities/contact_base.dart';
34
@@ -49,8 +50,15 @@ abstract class OutputBase with Store {
50 address = '',
51 note = '',
52 extractedAddress = '',
53 + estimatedFee = 0.0,
54 parsedAddress = ParsedAddress(addresses: []) {
55 _setCryptoNumMaximumFractionDigits();
56 + autorun((_) {
57 + final status = _wallet.syncStatus;
58 + printV("Sync status changed to $status. Recalculating fees");
59 +
60 + calculateEstimatedFee();
61 + });
62 }
63
64 Key key;
@@ -118,6 +126,9 @@ abstract class OutputBase with Store {
126 case WalletType.base:
127 _amount = base!.formatterBaseParseAmount(_cryptoAmount);
128 break;
129 + case WalletType.arbitrum:
130 + _amount = arbitrum!.formatterArbitrumParseAmount(_cryptoAmount);
131 + break;
132 case WalletType.wownero:
133 _amount = wownero!.formatterWowneroParseAmount(amount: _cryptoAmount);
134 break;
@@ -145,28 +156,74 @@ abstract class OutputBase with Store {
156 return amount;
157 }
158
148 - @computed
149 - double get estimatedFee {
150 - try {
151 - // forces mobx to rebuild the computed value
152 - final _ = _wallet.syncStatus;
159 + @observable
160 + double estimatedFee;
161
162 + @action
163 + Future<void> calculateEstimatedFee() async {
164 + try {
165 if (_wallet.type == WalletType.tron) {
166 if (cryptoCurrencyHandler() == CryptoCurrency.trx) {
156 - final nativeEstimatedFee = tron!.getTronNativeEstimatedFee(_wallet) ?? 0;
157 - return double.parse(nativeEstimatedFee.toString());
167 + final nativeEstimatedFee = tron!.getTronNativeEstimatedFee(_wallet) ?? '0';
168 + estimatedFee = double.parse(nativeEstimatedFee);
169 } else {
159 - final trc20EstimatedFee = tron!.getTronTRC20EstimatedFee(_wallet) ?? 0;
160 - return double.parse(trc20EstimatedFee.toString());
170 + final trc20EstimatedFee = tron!.getTronTRC20EstimatedFee(_wallet) ?? '0';
171 + estimatedFee = double.parse(trc20EstimatedFee);
172 }
173 }
174
175 + if (isEVMCompatibleChain(_wallet.type)) {
176 + if (_wallet.type == WalletType.arbitrum) {
177 + String? fee = cryptoCurrencyHandler() == CryptoCurrency.arbEth
178 + ? arbitrum!.getArbitrumNativeEstimatedFee(_wallet)
179 + : arbitrum!.getArbitrumERC20EstimatedFee(_wallet);
180 +
181 + estimatedFee = arbitrum!
182 + .formatterArbitrumAmountToDouble(amount: BigInt.from(double.parse(fee ?? '0.0')));
183 + return;
184 + }
185 +
186 + await _wallet.updateEstimatedFeesParams(_settingsStore.priority[_wallet.type]!);
187 +
188 + double calculatedFee = 0.0;
189 +
190 + if (_wallet.type == WalletType.ethereum) {
191 + String? fee = cryptoCurrencyHandler() == CryptoCurrency.eth
192 + ? ethereum!.getEthereumNativeEstimatedFee(_wallet)
193 + : ethereum!.getEthereumERC20EstimatedFee(_wallet);
194 +
195 + calculatedFee = ethereum!
196 + .formatterEthereumAmountToDouble(amount: BigInt.from(double.parse(fee ?? '0.0')));
197 + }
198 +
199 + if (_wallet.type == WalletType.polygon) {
200 + String? fee = cryptoCurrencyHandler() == CryptoCurrency.maticpoly
201 + ? polygon!.getPolygonNativeEstimatedFee(_wallet)
202 + : polygon!.getPolygonERC20EstimatedFee(_wallet);
203 +
204 + calculatedFee = polygon!
205 + .formatterPolygonAmountToDouble(amount: BigInt.from(double.parse(fee ?? '0.0')));
206 + }
207 +
208 + if (_wallet.type == WalletType.base) {
209 + String? fee = cryptoCurrencyHandler() == CryptoCurrency.baseEth
210 + ? base!.getBaseNativeEstimatedFee(_wallet)
211 + : base!.getBaseERC20EstimatedFee(_wallet);
212 + calculatedFee =
213 + base!.formatterBaseAmountToDouble(amount: BigInt.from(double.parse(fee ?? '0.0')));
214 + }
215 +
216 + estimatedFee = calculatedFee;
217 + }
218 +
219 if (_wallet.type == WalletType.solana) {
165 - return solana!.getEstimateFees(_wallet) ?? 0.0;
220 + estimatedFee = solana!.getEstimateFees(_wallet) ?? 0.0;
221 }
222
223 int? fee = _wallet.calculateEstimatedFee(
169 - _settingsStore.priority[_wallet.type]!, formattedCryptoAmount);
224 + _settingsStore.priority[_wallet.type]!,
225 + formattedCryptoAmount,
226 + );
227
228 if (_wallet.type == WalletType.bitcoin) {
229 if (_settingsStore.priority[_wallet.type] ==
@@ -175,48 +232,34 @@ abstract class OutputBase with Store {
232 _wallet, _settingsStore.customBitcoinFeeRate, formattedCryptoAmount);
233 }
234
178 - return bitcoin!.formatterBitcoinAmountToDouble(amount: fee);
235 + estimatedFee = bitcoin!.formatterBitcoinAmountToDouble(amount: fee);
236 }
237
238 if (_wallet.type == WalletType.litecoin ||
239 _wallet.type == WalletType.bitcoinCash ||
240 _wallet.type == WalletType.dogecoin) {
184 - return bitcoin!.formatterBitcoinAmountToDouble(amount: fee);
241 + estimatedFee = bitcoin!.formatterBitcoinAmountToDouble(amount: fee);
242 }
243
244 if (_wallet.type == WalletType.monero) {
188 - return monero!.formatterMoneroAmountToDouble(amount: fee);
245 + estimatedFee = monero!.formatterMoneroAmountToDouble(amount: fee);
246 }
247
248 if (_wallet.type == WalletType.wownero) {
192 - return wownero!.formatterWowneroAmountToDouble(amount: fee);
193 - }
194 -
195 - if (_wallet.type == WalletType.ethereum) {
196 - return ethereum!.formatterEthereumAmountToDouble(amount: BigInt.from(fee));
197 - }
198 -
199 - if (_wallet.type == WalletType.polygon) {
200 - return polygon!.formatterPolygonAmountToDouble(amount: BigInt.from(fee));
201 - }
202 -
203 - if (_wallet.type == WalletType.base) {
204 - return base!.formatterBaseAmountToDouble(amount: BigInt.from(fee));
249 + estimatedFee = wownero!.formatterWowneroAmountToDouble(amount: fee);
250 }
251
252 if (_wallet.type == WalletType.zano) {
208 - return zano!.formatterIntAmountToDouble(
253 + estimatedFee = zano!.formatterIntAmountToDouble(
254 amount: fee, currency: cryptoCurrencyHandler(), forFee: true);
255 }
256
257 if (_wallet.type == WalletType.decred) {
213 - return decred!.formatterDecredAmountToDouble(amount: fee);
258 + estimatedFee = decred!.formatterDecredAmountToDouble(amount: fee);
259 }
260 } catch (e) {
261 printV(e.toString());
262 }
218 -
219 - return 0;
263 }
264
265 @computed
@@ -337,6 +380,7 @@ abstract class OutputBase with Store {
380 case WalletType.ethereum:
381 case WalletType.polygon:
382 case WalletType.base:
383 + case WalletType.arbitrum:
384 case WalletType.solana:
385 case WalletType.tron:
386 case WalletType.haven:
lib/view_model/send/send_template_view_model.dart
+1
@@ -57,6 +57,7 @@ abstract class SendTemplateViewModelBase with Store {
57 _wallet.type != WalletType.ethereum &&
58 _wallet.type != WalletType.polygon &&
59 _wallet.type != WalletType.base &&
60 + _wallet.type != WalletType.arbitrum &&
61 _wallet.type != WalletType.solana &&
62 _wallet.type != WalletType.tron;
63
lib/view_model/send/send_view_model.dart
+54 -3
@@ -29,6 +29,7 @@ import 'package:cake_wallet/generated/i18n.dart';
29 import 'package:cake_wallet/monero/monero.dart';
30 import 'package:cake_wallet/nano/nano.dart';
31 import 'package:cake_wallet/polygon/polygon.dart';
32 +import 'package:cake_wallet/arbitrum/arbitrum.dart';
33 import 'package:cake_wallet/reactions/wallet_connect.dart';
34 import 'package:cake_wallet/routes.dart';
35 import 'package:cake_wallet/solana/solana.dart';
@@ -137,7 +138,6 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
138 bool get isMwebEnabled => balanceViewModel.mwebEnabled;
139
140 bool get isEVMWallet => isEVMCompatibleChain(walletType);
140 -
141 @action
142 void setShowAddressBookPopup(bool value) {
143 _settingsStore.showAddressBookPopupEnabled = value;
@@ -223,6 +223,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
223 case WalletType.ethereum:
224 case WalletType.polygon:
225 case WalletType.base:
226 + case WalletType.arbitrum:
227 case WalletType.tron:
228 case WalletType.solana:
229 return wallet.currency;
@@ -603,6 +604,26 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
604 priority,
605 );
606
607 + _isSwapsXYZCallDataTx = true;
608 + state = ExecutedSuccessfullyState();
609 + return pendingTransaction; // do NOT fall back to regular flow
610 + }
611 + if (walletType == WalletType.arbitrum) {
612 + _pendingApprovalTx = await buildApprovalIfNeeded(
613 + spender: routerTo!,
614 + tokenContract: tokenContract,
615 + requiredAmount: requiredAmount,
616 + sourceTokenDecimals: trade.sourceTokenDecimals,
617 + );
618 +
619 + // Build the callData tx
620 + pendingTransaction = await arbitrum!.createRawCallDataTransaction(
621 + wallet,
622 + routerTo,
623 + routerData,
624 + routerValueWei,
625 + );
626 +
627 _isSwapsXYZCallDataTx = true;
628 state = ExecutedSuccessfullyState();
629 return pendingTransaction; // do NOT fall back to regular flow
@@ -637,7 +658,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
658 return pendingTransaction;
659 }
660 if (walletType == WalletType.base) {
640 - final priority = _settingsStore.priority[WalletType.polygon]!;
661 + final priority = _settingsStore.priority[WalletType.base]!;
662 pendingTransaction = await base!.createRawCallDataTransaction(
663 wallet,
664 routerTo!,
@@ -649,6 +670,17 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
670 state = ExecutedSuccessfullyState();
671 return pendingTransaction;
672 }
673 + if (walletType == WalletType.arbitrum) {
674 + pendingTransaction = await arbitrum!.createRawCallDataTransaction(
675 + wallet,
676 + routerTo!,
677 + routerData,
678 + routerValueWei,
679 + );
680 + _isSwapsXYZCallDataTx = true;
681 + state = ExecutedSuccessfullyState();
682 + return pendingTransaction;
683 + }
684 }
685 }
686
@@ -847,7 +879,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
879 wallet.type != WalletType.nano &&
880 wallet.type != WalletType.banano &&
881 wallet.type != WalletType.solana &&
850 - wallet.type != WalletType.tron) {
882 + wallet.type != WalletType.tron &&
883 + wallet.type != WalletType.arbitrum) {
884 throw Exception('Priority is null for wallet type: ${wallet.type}');
885 }
886
@@ -890,6 +923,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
923 case WalletType.base:
924 return base!.createBaseTransactionCredentials(outputs,
925 priority: priority!, currency: selectedCryptoCurrency);
926 + case WalletType.arbitrum:
927 + return arbitrum!
928 + .createArbitrumTransactionCredentials(outputs, currency: selectedCryptoCurrency);
929 case WalletType.solana:
930 return solana!
931 .createSolanaTransactionCredentials(outputs, currency: selectedCryptoCurrency);
@@ -1016,6 +1052,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
1052 if (walletType == WalletType.ethereum ||
1053 walletType == WalletType.polygon ||
1054 walletType == WalletType.base ||
1055 + walletType == WalletType.arbitrum ||
1056 walletType == WalletType.haven) {
1057 if (errorMessage.contains('gas required exceeds allowance')) {
1058 return S.current.gas_exceeds_allowance;
@@ -1154,6 +1191,13 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
1191 spender,
1192 requiredAmount,
1193 );
1194 + } else if (walletType == WalletType.arbitrum) {
1195 + needsApproval = await arbitrum!.isApprovalRequired(
1196 + wallet,
1197 + tokenContract,
1198 + spender,
1199 + requiredAmount,
1200 + );
1201 }
1202
1203 if (!needsApproval) return null;
@@ -1196,6 +1240,13 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
1240 erc20Token,
1241 priority,
1242 );
1243 + } else if (walletType == WalletType.arbitrum) {
1244 + return await arbitrum!.createTokenApproval(
1245 + wallet,
1246 + requiredAmount,
1247 + spender,
1248 + erc20Token,
1249 + );
1250 }
1251
1252 return null;
lib/view_model/settings/privacy_settings_view_model.dart
+12
@@ -1,3 +1,4 @@
1 +import 'package:cake_wallet/arbitrum/arbitrum.dart';
2 import 'package:cake_wallet/base/base.dart';
3 import 'package:cake_wallet/bitcoin/bitcoin.dart';
4 import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
@@ -79,6 +80,9 @@ abstract class PrivacySettingsViewModelBase with Store {
80 @computed
81 bool get useBaseScan => _settingsStore.useBaseScan;
82
83 + @computed
84 + bool get useArbiScan => _settingsStore.useArbiScan;
85 +
86 @computed
87 bool get useTronGrid => _settingsStore.useTronGrid;
88
@@ -118,6 +122,8 @@ abstract class PrivacySettingsViewModelBase with Store {
122
123 bool get canUseBaseScan => _wallet.type == WalletType.base;
124
125 + bool get canUseArbiScan => _wallet.type == WalletType.arbitrum;
126 +
127 bool get canUseTronGrid => _wallet.type == WalletType.tron;
128
129 bool get canUseMempoolFeeAPI => _wallet.type == WalletType.bitcoin;
@@ -195,6 +201,12 @@ abstract class PrivacySettingsViewModelBase with Store {
201 tron!.updateTronGridUsageState(_wallet, value);
202 }
203
204 + @action
205 + void setUseArbiScan(bool value) {
206 + _settingsStore.useArbiScan = value;
207 + arbitrum!.updateArbitrumScanUsageState(_wallet, value);
208 + }
209 +
210 @action
211 void setUseMempoolFeeAPI(bool value) => _settingsStore.useMempoolFeeAPI = value;
212
lib/view_model/transaction_details_view_model.dart
+57
@@ -77,6 +77,9 @@ abstract class TransactionDetailsViewModelBase with Store {
77 case WalletType.base:
78 _addBaseListItems(tx, dateFormat);
79 break;
80 + case WalletType.arbitrum:
81 + _addArbitrumListItems(tx, dateFormat);
82 + break;
83 case WalletType.solana:
84 _addSolanaListItems(tx, dateFormat);
85 break;
@@ -204,6 +207,8 @@ abstract class TransactionDetailsViewModelBase with Store {
207 return 'https://blockchair.com/dogecoin/transaction/${txId}';
208 case WalletType.base:
209 return 'https://basescan.org/tx/${txId}';
210 + case WalletType.arbitrum:
211 + return 'https://arbiscan.io/tx/${txId}';
212 case WalletType.none:
213 return '';
214 }
@@ -241,6 +246,8 @@ abstract class TransactionDetailsViewModelBase with Store {
246 return S.current.view_transaction_on + 'dcrdata.decred.org';
247 case WalletType.base:
248 return S.current.view_transaction_on + 'basescan.org';
249 + case WalletType.arbitrum:
250 + return S.current.view_transaction_on + 'arbiscan.io';
251 case WalletType.none:
252 return '';
253 }
@@ -606,6 +613,56 @@ abstract class TransactionDetailsViewModelBase with Store {
613 items.addAll(_items);
614 }
615
616 + void _addArbitrumListItems(TransactionInfo tx, DateFormat dateFormat) {
617 + final _items = [
618 + StandartListItem(
619 + title: S.current.transaction_details_transaction_id,
620 + value: tx.txHash,
621 + key: ValueKey('standard_list_item_transaction_details_id_key'),
622 + ),
623 + StandartListItem(
624 + title: S.current.transaction_details_date,
625 + value: dateFormat.format(tx.date),
626 + key: ValueKey('standard_list_item_transaction_details_date_key'),
627 + ),
628 + StandartListItem(
629 + title: S.current.confirmations,
630 + value: tx.confirmations.toString(),
631 + key: ValueKey('standard_list_item_transaction_confirmations_key'),
632 + ),
633 + StandartListItem(
634 + title: S.current.transaction_details_height,
635 + value: '${tx.height}',
636 + key: ValueKey('standard_list_item_transaction_details_height_key'),
637 + ),
638 + StandartListItem(
639 + title: S.current.transaction_details_amount,
640 + value: tx.amountFormatted(),
641 + key: ValueKey('standard_list_item_transaction_details_amount_key'),
642 + ),
643 + if (tx.feeFormatted()?.isNotEmpty ?? false)
644 + StandartListItem(
645 + title: S.current.transaction_details_fee,
646 + value: tx.feeFormatted()!,
647 + key: ValueKey('standard_list_item_transaction_details_fee_key'),
648 + ),
649 + if (showRecipientAddress && tx.to != null && tx.direction == TransactionDirection.outgoing)
650 + StandartListItem(
651 + title: S.current.transaction_details_recipient_address,
652 + value: tx.to!,
653 + key: ValueKey('standard_list_item_transaction_details_recipient_address_key'),
654 + ),
655 + if (tx.direction == TransactionDirection.incoming && tx.from != null)
656 + StandartListItem(
657 + title: S.current.transaction_details_source_address,
658 + value: tx.from!,
659 + key: ValueKey('standard_list_item_transaction_details_source_address_key'),
660 + ),
661 + ];
662 +
663 + items.addAll(_items);
664 + }
665 +
666 void _addSolanaListItems(TransactionInfo tx, DateFormat dateFormat) {
667 final _items = [
668 StandartListItem(
lib/view_model/wallet_address_list/wallet_address_list_view_model.dart
+17
@@ -13,6 +13,7 @@ import 'package:cake_wallet/ethereum/ethereum.dart';
13 import 'package:cake_wallet/generated/i18n.dart';
14 import 'package:cake_wallet/monero/monero.dart';
15 import 'package:cake_wallet/polygon/polygon.dart';
16 +import 'package:cake_wallet/arbitrum/arbitrum.dart';
17 import 'package:cake_wallet/reactions/wallet_utils.dart';
18 import 'package:cake_wallet/solana/solana.dart';
19 import 'package:cake_wallet/decred/decred.dart';
@@ -146,6 +147,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
147 return DogeURI(amount: amount, address: address.address);
148 case WalletType.base:
149 return BaseURI(amount: amount, address: address.address);
150 + case WalletType.arbitrum:
151 + return ArbitrumURI(amount: amount, address: address.address);
152 case WalletType.none:
153 throw Exception('Unexpected type: ${type.toString()}');
154 }
@@ -272,6 +275,12 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
275 addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
276 }
277
278 + if (wallet.type == WalletType.arbitrum) {
279 + final primaryAddress = arbitrum!.getAddress(wallet);
280 +
281 + addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
282 + }
283 +
284 if (wallet.type == WalletType.solana) {
285 final primaryAddress = solana!.getAddress(wallet);
286
@@ -450,6 +459,12 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
459 'assets/images/usdc_icon.svg',
460 'assets/images/more_tokens.svg',
461 ];
462 + case WalletType.arbitrum:
463 + return [
464 + 'assets/images/crypto/arbitrum.webp',
465 + 'assets/images/usdc_icon.svg',
466 + 'assets/images/more_tokens.svg',
467 + ];
468 default:
469 return [];
470 }
@@ -473,6 +488,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
488 return 'assets/images/zano_chain_mono.svg';
489 case WalletType.base:
490 return 'assets/images/base_chain_mono.svg';
491 + case WalletType.arbitrum:
492 + return 'assets/images/arbitrum_chain_mono.svg';
493 default:
494 return 'assets/images/eth_chain_mono.svg';
495 }
lib/view_model/wallet_keys_view_model.dart
+3
@@ -127,6 +127,7 @@ abstract class WalletKeysViewModelBase with Store {
127 case WalletType.ethereum:
128 case WalletType.polygon:
129 case WalletType.base:
130 + case WalletType.arbitrum:
131 case WalletType.solana:
132 case WalletType.tron:
133 items.addAll([
@@ -255,6 +256,8 @@ abstract class WalletKeysViewModelBase with Store {
256 return 'polygon-wallet';
257 case WalletType.base:
258 return 'base-wallet';
259 + case WalletType.arbitrum:
260 + return 'arbitrum-wallet';
261 case WalletType.solana:
262 return 'solana-wallet';
263 case WalletType.tron:
lib/view_model/wallet_new_vm.dart
+8
@@ -1,3 +1,4 @@
1 +import 'package:cake_wallet/arbitrum/arbitrum.dart';
2 import 'package:cake_wallet/base/base.dart';
3 import 'package:cake_wallet/core/new_wallet_arguments.dart';
4 import 'package:cake_wallet/dogecoin/dogecoin.dart';
@@ -97,6 +98,13 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
98 mnemonic: newWalletArguments!.mnemonic,
99 passphrase: passphrase,
100 );
101 + case WalletType.arbitrum:
102 + return arbitrum!.createArbitrumNewWalletCredentials(
103 + name: name,
104 + password: walletPassword,
105 + mnemonic: newWalletArguments!.mnemonic,
106 + passphrase: passphrase,
107 + );
108 case WalletType.bitcoinCash:
109 return bitcoinCash!.createBitcoinCashNewWalletCredentials(
110 name: name,
lib/view_model/wallet_restore_view_model.dart
+16
@@ -1,3 +1,4 @@
1 +import 'package:cake_wallet/arbitrum/arbitrum.dart';
2 import 'package:cake_wallet/base/base.dart';
3 import 'package:cake_wallet/bitcoin/bitcoin.dart';
4 import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
@@ -52,6 +53,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
53 case WalletType.ethereum:
54 case WalletType.polygon:
55 case WalletType.base:
56 + case WalletType.arbitrum:
57 case WalletType.decred:
58 case WalletType.bitcoin:
59 availableModes = [WalletRestoreMode.seed, WalletRestoreMode.keys];
@@ -92,6 +94,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
94 WalletType.ethereum,
95 WalletType.polygon,
96 WalletType.base,
97 + WalletType.arbitrum,
98 WalletType.nano,
99 WalletType.banano,
100 WalletType.solana,
@@ -183,6 +186,13 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
186 password: password,
187 passphrase: passphrase,
188 );
189 + case WalletType.arbitrum:
190 + return arbitrum!.createArbitrumRestoreWalletFromSeedCredentials(
191 + name: name,
192 + mnemonic: seed,
193 + password: password,
194 + passphrase: passphrase,
195 + );
196 case WalletType.solana:
197 return solana!.createSolanaRestoreWalletFromSeedCredentials(
198 name: name,
@@ -276,6 +286,12 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
286 password: password,
287 privateKey: options['private_key'] as String,
288 );
289 + case WalletType.arbitrum:
290 + return arbitrum!.createArbitrumRestoreWalletFromPrivateKey(
291 + name: name,
292 + password: password,
293 + privateKey: options['private_key'] as String,
294 + );
295 case WalletType.solana:
296 return solana!.createSolanaRestoreWalletFromPrivateKey(
297 name: name,
pubspec_base.yaml
+1
@@ -237,6 +237,7 @@ flutter:
237 - assets/decred_node_list.yml
238 - assets/dogecoin_electrum_server_list.yml
239 - assets/base_node_list.yml
240 + - assets/arbitrum_node_list.yml
241 - assets/text/
242 - assets/faq/
243 - assets/animation/
res/values/strings_ar.arb
+1
@@ -60,6 +60,7 @@
60 "approve": "ﺪﻤﺘﻌﻳ",
61 "approve_request": "الموافقة على الطلب",
62 "approve_tokens": "الموافقة على الرموز",
63 + "arbiscan_history": "تاريخ أربيسكان",
64 "arrive_in_this_address": "سيصل ${currency} ${tag}إلى هذا العنوان",
65 "ascending": "تصاعدي",
66 "ask_each_time": "اسأل في كل مرة",
res/values/strings_bg.arb
+1
@@ -60,6 +60,7 @@
60 "approve": "Одобряване",
61 "approve_request": "Одобрете искане",
62 "approve_tokens": "Одобрете жетоните",
63 + "arbiscan_history": "Арбисканска история",
64 "arrive_in_this_address": "${currency} ${tag}ще отидат на този адрес",
65 "ascending": "Възходящ",
66 "ask_each_time": "Питайте всеки път",
res/values/strings_cs.arb
+1
@@ -60,6 +60,7 @@
60 "approve": "Schvalovat",
61 "approve_request": "Schválit žádost",
62 "approve_tokens": "Schválit tokeny",
63 + "arbiscan_history": "Arbiscan History",
64 "arrive_in_this_address": "${currency} ${tag}přijde na tuto adresu",
65 "ascending": "Vzestupné",
66 "ask_each_time": "Zeptejte se pokaždé",
res/values/strings_de.arb
+1
@@ -60,6 +60,7 @@
60 "approve": "Genehmigen",
61 "approve_request": "Anfrage genehmigen",
62 "approve_tokens": "Token genehmigen",
63 + "arbiscan_history": "ArbiScan-Geschichte",
64 "arrive_in_this_address": "${currency} ${tag} wird an dieser Adresse ankommen",
65 "ascending": "Aufsteigend",
66 "ask_each_time": "Jedes Mal fragen",
res/values/strings_en.arb
+1
@@ -60,6 +60,7 @@
60 "approve": "Approve",
61 "approve_request": "Approve Request",
62 "approve_tokens": "Approve tokens",
63 + "arbiscan_history": "ArbiScan History",
64 "arrive_in_this_address": "${currency} ${tag}will arrive in this address",
65 "ascending": "Ascending",
66 "ask_each_time": "Ask each time",
res/values/strings_es.arb
+4 -3
@@ -58,9 +58,10 @@
58 "apk_update": "Actualización de APK",
59 "appearance": "Apariencia",
60 "approve": "Aprobar",
61 - "approve_request": "Aprobar Solicitud",
62 - "approve_tokens": "Autorizar tokens",
63 - "arrive_in_this_address": "${currency} ${tag} llegará a esta dirección",
61 + "approve_request": "Aprobar la solicitud",
62 + "approve_tokens": "Aprobar tokens",
63 + "arbiscan_history": "Historia de ArbiScan",
64 + "arrive_in_this_address": "${currency} ${tag}llegará a esta dirección",
65 "ascending": "Ascendente",
66 "ask_each_time": "Preguntar cada vez",
67 "auth_store_ban_timeout": "tiempo de espera del bloqueo",
res/values/strings_fr.arb
+1
@@ -60,6 +60,7 @@
60 "approve": "Approuver",
61 "approve_request": "Approuver la demande",
62 "approve_tokens": "Approuver les jetons",
63 + "arbiscan_history": "Historique d'ArbiScan",
64 "arrive_in_this_address": "${currency} ${tag}arrivera à cette adresse",
65 "ascending": "Ascendant",
66 "ask_each_time": "Demander à chaque fois",
res/values/strings_ha.arb
+1
@@ -60,6 +60,7 @@
60 "approve": "Amincewa",
61 "approve_request": "Amince da bukata",
62 "approve_tokens": "Amince da Alamu",
63 + "arbiscan_history": "Tarihin Arbiscan",
64 "arrive_in_this_address": "${currency} ${tag} zai je wurin wannan adireshi",
65 "ascending": "Hau",
66 "ask_each_time": "Tambaya kowane lokaci",
res/values/strings_hi.arb
+1
@@ -60,6 +60,7 @@
60 "approve": "मंज़ूरी देना",
61 "approve_request": "अनुरोध को स्वीकृत करें",
62 "approve_tokens": "टोकन को मंजूरी देना",
63 + "arbiscan_history": "अरबीस्कैन इतिहास",
64 "arrive_in_this_address": "${currency} ${tag}इस पते पर पहुंचेंगे",
65 "ascending": "आरोही",
66 "ask_each_time": "हर बार पूछें",
res/values/strings_hr.arb
+1
@@ -60,6 +60,7 @@
60 "approve": "Odobriti",
61 "approve_request": "Odobriti zahtjev",
62 "approve_tokens": "Odobriti tokene",
63 + "arbiscan_history": "Arbiskanska povijest",
64 "arrive_in_this_address": "${currency} ${tag}će stići na ovu adresu",
65 "ascending": "Uzlazni",
66 "ask_each_time": "Pitajte svaki put",
res/values/strings_hy.arb
+1
@@ -60,6 +60,7 @@
60 "approve": "Հաստատել",
61 "approve_request": "Հաստատում է հայցը",
62 "approve_tokens": "Հաստատում է նշանները",
63 + "arbiscan_history": "Արբիսկանի պատմություն",
64 "arrive_in_this_address": "${currency} ${tag}կժամանի այս հասցեում",
65 "ascending": "Աճող",
66 "ask_each_time": "Հարցնել ամեն անգամ",
res/values/strings_id.arb
+1
@@ -60,6 +60,7 @@
60 "approve": "Menyetujui",
61 "approve_request": "Menyetujui permintaan",
62 "approve_tokens": "Menyetujui token",
63 + "arbiscan_history": "Sejarah ArbiScan",
64 "arrive_in_this_address": "${currency} ${tag} akan tiba di alamat ini",
65 "ascending": "Naik",
66 "ask_each_time": "Tanyakan setiap kali",
res/values/strings_it.arb
+1
@@ -60,6 +60,7 @@
60 "approve": "Approvare",
61 "approve_request": "Approvare la richiesta",
62 "approve_tokens": "Approvare i token",
63 + "arbiscan_history": "Storia dell'ArbiScan",
64 "arrive_in_this_address": "${currency} ${tag}arriverà a questo indirizzo",
65 "ascending": "Ascendente",
66 "ask_each_time": "Chiedi ogni volta",
res/values/strings_ja.arb
+1
@@ -60,6 +60,7 @@
60 "approve": "承認する",
61 "approve_request": "リクエストを承認します",
62 "approve_tokens": "トークンを承認します",
63 + "arbiscan_history": "ArbiScan の歴史",
64 "arrive_in_this_address": "${currency} ${tag}はこの住所に到着します",
65 "ascending": "上昇",
66 "ask_each_time": "毎回尋ねてください",
res/values/strings_ko.arb
+1
@@ -60,6 +60,7 @@
60 "approve": "승인",
61 "approve_request": "요청 승인",
62 "approve_tokens": "토큰을 승인합니다",
63 + "arbiscan_history": "ArbiScan 역사",
64 "arrive_in_this_address": "${currency} ${tag}이(가) 이 주소로 도착합니다",
65 "ascending": "오름차순",
66 "ask_each_time": "매번 묻기",
res/values/strings_my.arb
+1
@@ -60,6 +60,7 @@
60 "approve": "လက်မခံပါ။",
61 "approve_request": "တောင်းဆိုမှုကိုအတည်ပြု",
62 "approve_tokens": "တိုကင်အတည်ပြု",
63 + "arbiscan_history": "arbiscan သမိုင်း",
64 "arrive_in_this_address": "${currency} ${tag}ဤလိပ်စာသို့ ရောက်ရှိပါမည်။",
65 "ascending": "တက်",
66 "ask_each_time": "တစ်ခုချင်းစီကိုအချိန်မေးပါ",
res/values/strings_nl.arb
+1
@@ -60,6 +60,7 @@
60 "approve": "Goedkeuren",
61 "approve_request": "Het verzoek goedkeuren",
62 "approve_tokens": "Tokens goedkeuren",
63 + "arbiscan_history": "ArbiScan-geschiedenis",
64 "arrive_in_this_address": "${currency} ${tag}komt aan op dit adres",
65 "ascending": "Stijgend",
66 "ask_each_time": "Vraag het elke keer",
res/values/strings_pl.arb
+11 -10
@@ -57,16 +57,17 @@
57 "anonpay_description": "Wygeneruj ${type}. Odbiorca może ${method} przy użyciu dowolnej obsługiwanej kryptowaluty, a otrzymasz środki w tym portfelu.",
58 "apk_update": "Aktualizacja APK",
59 "appearance": "Wygląd",
60 - "approve": "Zatwierdź",
61 - "approve_request": "Zatwierdź żądanie",
62 - "approve_tokens": "Zatwierdź tokeny",
63 - "arrive_in_this_address": "${currency} ${tag} dotrze na ten adres",
64 - "ascending": "Rosnąco",
65 - "ask_each_time": "Pytaj za każdym razem",
66 - "auth_store_ban_timeout": "czas wygaśnięcia blokady",
67 - "auth_store_banned_for": "Zbanowany na ",
68 - "auth_store_banned_minutes": " minut",
69 - "auth_store_incorrect_password": "Nieprawidłowy PIN",
60 + "approve": "Zatwierdzić",
61 + "approve_request": "Zatwierdzić żądanie",
62 + "approve_tokens": "Zatwierdzić tokeny",
63 + "arbiscan_history": "Historia ArbiScanu",
64 + "arrive_in_this_address": "${currency} ${tag}dotrze na ten adres",
65 + "ascending": "Wznoszący się",
66 + "ask_each_time": "Zapytaj za każdym razem",
67 + "auth_store_ban_timeout": "przekroczenie limitu czasu",
68 + "auth_store_banned_for": "Zablokowany na ",
69 + "auth_store_banned_minutes": " minuty",
70 + "auth_store_incorrect_password": "Niepoprawny PIN",
71 "authenticated": "Uwierzytelniony",
72 "authentication": "Uwierzytelnianie",
73 "auto_generate_addresses": "Automatyczne generowanie adresów",
res/values/strings_pt.arb
+1
@@ -60,6 +60,7 @@
60 "approve": "Aprovar",
61 "approve_request": "Aprovar solicitação",
62 "approve_tokens": "Aprovar tokens",
63 + "arbiscan_history": "História do ArbiScan",
64 "arrive_in_this_address": "${currency} ${tag}chegará neste endereço",
65 "ascending": "Ascendente",
66 "ask_each_time": "Pergunte cada vez",
res/values/strings_ru.arb
+1
@@ -60,6 +60,7 @@
60 "approve": "Утвердить",
61 "approve_request": "Утвердить запрос",
62 "approve_tokens": "Одобрить токены",
63 + "arbiscan_history": "История АрбиСкан",
64 "arrive_in_this_address": "${currency} ${tag}придет на этот адрес",
65 "ascending": "Восходящий",
66 "ask_each_time": "Спросите каждый раз",
res/values/strings_th.arb
+1
@@ -60,6 +60,7 @@
60 "approve": "อนุมัติ",
61 "approve_request": "อนุมัติคำขอ",
62 "approve_tokens": "อนุมัติโทเค็น",
63 + "arbiscan_history": "ประวัติ ArbiScan",
64 "arrive_in_this_address": "${currency} ${tag}จะมาถึงที่อยู่นี้",
65 "ascending": "จากน้อยไปมาก",
66 "ask_each_time": "ถามทุกครั้ง",
res/values/strings_tl.arb
+1
@@ -60,6 +60,7 @@
60 "approve": "Aprubahan",
61 "approve_request": "Aprubahan ang kahilingan",
62 "approve_tokens": "Aprubahan ang mga token",
63 + "arbiscan_history": "Kasaysayan ng Arbiscan",
64 "arrive_in_this_address": "Ang ${currency} ${tag} ay darating sa address na ito",
65 "ascending": "Umakyat",
66 "ask_each_time": "Magtanong sa tuwing",
res/values/strings_tr.arb
+1
@@ -60,6 +60,7 @@
60 "approve": "Onaylamak",
61 "approve_request": "Talebi Onaylama",
62 "approve_tokens": "Jetonları onaylayın",
63 + "arbiscan_history": "ArbiScan Geçmişi",
64 "arrive_in_this_address": "${currency} ${tag}bu adrese ulaşacak",
65 "ascending": "Yükselen",
66 "ask_each_time": "Her seferinde sor",
res/values/strings_uk.arb
+1
@@ -60,6 +60,7 @@
60 "approve": "Затвердити",
61 "approve_request": "Запитайте запит",
62 "approve_tokens": "Затвердити токени",
63 + "arbiscan_history": "Арбісканська історія",
64 "arrive_in_this_address": "${currency} ${tag}надійде на цю адресу",
65 "ascending": "Висхід",
66 "ask_each_time": "Запитайте кожен раз",
res/values/strings_ur.arb
+1
@@ -60,6 +60,7 @@
60 "approve": "ﻭﺮﮐ ﺭﻮﻈﻨﻣ",
61 "approve_request": "درخواست کو منظور کریں",
62 "approve_tokens": "ٹوکن کو منظور کریں",
63 + "arbiscan_history": "اربیسکن تاریخ",
64 "arrive_in_this_address": "${currency} ${tag}اس پتے پر پہنچے گا۔",
65 "ascending": "چڑھنے",
66 "ask_each_time": "ہر بار پوچھیں",
res/values/strings_vi.arb
+1
@@ -60,6 +60,7 @@
60 "approve": "Phê duyệt",
61 "approve_request": "Phê duyệt yêu cầu",
62 "approve_tokens": "Phê duyệt mã thông báo",
63 + "arbiscan_history": "Lịch sử quét Arbi",
64 "arrive_in_this_address": "${currency} ${tag} sẽ đến địa chỉ này",
65 "ascending": "Tăng dần",
66 "ask_each_time": "Hỏi mỗi lần",
res/values/strings_yo.arb
+1
@@ -60,6 +60,7 @@
60 "approve": "Fi ọwọ si",
61 "approve_request": "IKILỌ RẸ",
62 "approve_tokens": "Ṣe fọwọsi awọn àmi",
63 + "arbiscan_history": "Itan Arbiscan",
64 "arrive_in_this_address": "${currency} ${tag} máa dé sí àdírẹ́sì yìí",
65 "ascending": "Goke",
66 "ask_each_time": "Beere lọwọ kọọkan",
res/values/strings_zh.arb
+1
@@ -60,6 +60,7 @@
60 "approve": "批准",
61 "approve_request": "批准请求",
62 "approve_tokens": "批准令牌",
63 + "arbiscan_history": "ArbiScan 历史",
64 "arrive_in_this_address": "${currency} ${tag}将到达此地址",
65 "ascending": "上升",
66 "ask_each_time": "每次问",
scripts/android/pubspec_gen.sh
+1 -1
@@ -10,7 +10,7 @@ case $APP_ANDROID_TYPE in
10 CONFIG_ARGS="--monero"
11 ;;
12 $CAKEWALLET)
13 - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred --dogecoin --base"
13 + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred --dogecoin --base --arbitrum"
14 ;;
15 esac
16
scripts/ios/app_config.sh
+1 -1
@@ -31,7 +31,7 @@ case $APP_IOS_TYPE in
31 ;;
32
33 $CAKEWALLET)
34 - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred --dogecoin --base"
34 + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred --dogecoin --base --arbitrum"
35 ;;
36 esac
37
scripts/linux/app_config.sh
+1 -1
@@ -13,7 +13,7 @@ CONFIG_ARGS=""
13
14 case $APP_LINUX_TYPE in
15 $CAKEWALLET)
16 - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --dogecoin --base --excludeFlutterSecureStorage";;
16 + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --dogecoin --base --arbitrum --excludeFlutterSecureStorage";;
17 esac
18
19 cp -rf pubspec_description.yaml pubspec.yaml
scripts/macos/app_config.sh
+1 -1
@@ -36,7 +36,7 @@ case $APP_MACOS_TYPE in
36 $MONERO_COM)
37 CONFIG_ARGS="--monero";;
38 $CAKEWALLET)
39 - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --dogecoin --base";;
39 + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --dogecoin --base --arbitrum";;
40 esac
41
42 cp -rf pubspec_description.yaml pubspec.yaml
tool/configure.dart
+151 -4
@@ -13,6 +13,7 @@ const zanoOutputPath = 'lib/zano/zano.dart';
13 const decredOutputPath = 'lib/decred/decred.dart';
14 const dogecoinOutputPath = 'lib/dogecoin/dogecoin.dart';
15 const baseOutputPath = 'lib/base/base.dart';
16 +const arbitrumOutputPath = 'lib/arbitrum/arbitrum.dart';
17 const walletTypesPath = 'lib/wallet_types.g.dart';
18 const secureStoragePath = 'lib/core/secure_storage.dart';
19 const pubspecDefaultPath = 'pubspec_default.yaml';
@@ -34,6 +35,7 @@ Future<void> main(List<String> args) async {
35 final hasDecred = args.contains('${prefix}decred');
36 final hasDogecoin = args.contains('${prefix}dogecoin');
37 final hasBase = args.contains('${prefix}base');
38 + final hasArbitrum = args.contains('${prefix}arbitrum');
39 final excludeFlutterSecureStorage = args.contains('${prefix}excludeFlutterSecureStorage');
40
41 await generateBitcoin(hasBitcoin);
@@ -50,6 +52,7 @@ Future<void> main(List<String> args) async {
52 await generateDecred(hasDecred);
53 await generateDogecoin(hasDogecoin);
54 await generateBase(hasBase);
55 + await generateArbitrum(hasArbitrum);
56
57 await generatePubspec(
58 hasMonero: hasMonero,
@@ -67,6 +70,7 @@ Future<void> main(List<String> args) async {
70 hasDecred: hasDecred,
71 hasDogecoin: hasDogecoin,
72 hasBase: hasBase,
73 + hasArbitrum: hasArbitrum,
74 );
75 await generateWalletTypes(
76 hasMonero: hasMonero,
@@ -83,6 +87,7 @@ Future<void> main(List<String> args) async {
87 hasDecred: hasDecred,
88 hasDogecoin: hasDogecoin,
89 hasBase: hasBase,
90 + hasArbitrum: hasArbitrum,
91 );
92 await injectSecureStorage(!excludeFlutterSecureStorage);
93 }
@@ -795,6 +800,8 @@ abstract class Ethereum {
800 HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorConnect connect);
801 List<String> getDefaultTokenContractAddresses();
802 bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress);
803 + String? getEthereumNativeEstimatedFee(WalletBase wallet);
804 + String? getEthereumERC20EstimatedFee(WalletBase wallet);
805 }
806 """;
807
@@ -916,6 +923,8 @@ abstract class Polygon {
923 HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorConnect connect);
924 List<String> getDefaultTokenContractAddresses();
925 bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress);
926 + String? getPolygonNativeEstimatedFee(WalletBase wallet);
927 + String? getPolygonERC20EstimatedFee(WalletBase wallet);
928 }
929 """;
930
@@ -1491,9 +1500,7 @@ abstract class DogeCoin {
1500 final output = '$dogecoinCommonHeaders\n' +
1501 (hasImplementation ? '$dogecoinCWHeaders\n' : '\n') +
1502 (hasImplementation ? '$dogecoinCwPart\n\n' : '\n') +
1494 - (hasImplementation
1495 - ? dogecoinCWDefinition
1496 - : dogecoinEmptyDefinition) +
1503 + (hasImplementation ? dogecoinCWDefinition : dogecoinEmptyDefinition) +
1504 '\n' +
1505 dogecoinContent;
1506
@@ -1612,6 +1619,8 @@ abstract class Base {
1619 HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager);
1620 List<String> getDefaultTokenContractAddresses();
1621 bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress);
1622 + String? getBaseNativeEstimatedFee(WalletBase wallet);
1623 + String? getBaseERC20EstimatedFee(WalletBase wallet);
1624 }
1625 """;
1626
@@ -1632,6 +1641,131 @@ abstract class Base {
1641 await outputFile.writeAsString(output);
1642 }
1643
1644 +Future<void> generateArbitrum(bool hasImplementation) async {
1645 + final outputFile = File(arbitrumOutputPath);
1646 + const arbitrumCommonHeaders = """
1647 +import 'package:cake_wallet/view_model/send/output.dart';
1648 +import 'package:cw_core/crypto_currency.dart';
1649 +import 'package:cw_core/erc20_token.dart';
1650 +import 'package:cw_core/hardware/hardware_account_data.dart';
1651 +import 'package:cw_core/hardware/hardware_wallet_service.dart';
1652 +import 'package:cw_core/output_info.dart';
1653 +import 'package:cw_core/pending_transaction.dart';
1654 +import 'package:cw_core/transaction_info.dart';
1655 +import 'package:cw_core/transaction_priority.dart';
1656 +import 'package:cw_core/wallet_base.dart';
1657 +import 'package:cw_core/wallet_credentials.dart';
1658 +import 'package:cw_core/wallet_info.dart';
1659 +import 'package:cw_core/wallet_service.dart';
1660 +import 'package:hive/hive.dart';
1661 +import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
1662 +import 'package:bitbox_flutter/bitbox_flutter.dart' as bitbox;
1663 +import 'package:web3dart/web3dart.dart';
1664 +
1665 +""";
1666 + const arbitrumCWHeaders = """
1667 +import 'package:cw_evm/evm_chain_formatter.dart';
1668 +import 'package:cw_evm/evm_chain_mnemonics.dart';
1669 +import 'package:cw_evm/evm_chain_transaction_credentials.dart';
1670 +import 'package:cw_evm/evm_chain_transaction_info.dart';
1671 +import 'package:cw_evm/evm_chain_transaction_priority.dart';
1672 +import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
1673 +import 'package:cw_evm/hardware/evm_chain_ledger_credentials.dart';
1674 +import 'package:cw_evm/hardware/evm_chain_bitbox_credentials.dart';
1675 +import 'package:cw_evm/evm_chain_wallet.dart';
1676 +import 'package:cw_evm/hardware/evm_chain_bitbox_service.dart';
1677 +import 'package:cw_evm/hardware/evm_chain_ledger_service.dart';
1678 +
1679 +import 'package:cw_arbitrum/arbitrum_client.dart';
1680 +import 'package:cw_arbitrum/arbitrum_wallet.dart';
1681 +import 'package:cw_arbitrum/arbitrum_wallet_service.dart';
1682 +import 'package:cw_arbitrum/default_arbitrum_erc20_tokens.dart';
1683 +import 'package:eth_sig_util/util/utils.dart';
1684 +
1685 +""";
1686 + const arbitrumCwPart = "part 'cw_arbitrum.dart';";
1687 + const arbitrumContent = """
1688 +abstract class Arbitrum {
1689 + List<String> getArbitrumWordList(String language);
1690 + WalletService createArbitrumWalletService(bool isDirect);
1691 + WalletCredentials createArbitrumNewWalletCredentials(
1692 + {required String name,
1693 + WalletInfo? walletInfo,
1694 + String? password,
1695 + String? mnemonic,
1696 + String? passphrase});
1697 + WalletCredentials createArbitrumRestoreWalletFromSeedCredentials(
1698 + {required String name,
1699 + required String mnemonic,
1700 + required String password,
1701 + String? passphrase});
1702 + WalletCredentials createArbitrumRestoreWalletFromPrivateKey(
1703 + {required String name, required String privateKey, required String password});
1704 + WalletCredentials createArbitrumHardwareWalletCredentials(
1705 + {required String name, required HardwareAccountData hwAccountData, WalletInfo? walletInfo});
1706 + String getAddress(WalletBase wallet);
1707 + String getPrivateKey(WalletBase wallet);
1708 + String getPublicKey(WalletBase wallet);
1709 +
1710 + Object createArbitrumTransactionCredentials(
1711 + List<Output> outputs, {
1712 + required CryptoCurrency currency,
1713 + int? feeRate,
1714 + });
1715 +
1716 + Object createArbitrumTransactionCredentialsRaw(
1717 + List<OutputInfo> outputs, {
1718 + required CryptoCurrency currency,
1719 + required int feeRate,
1720 + });
1721 +
1722 + int formatterArbitrumParseAmount(String amount);
1723 + double formatterArbitrumAmountToDouble(
1724 + {TransactionInfo? transaction, BigInt? amount, int exponent = 18});
1725 + List<Erc20Token> getERC20Currencies(WalletBase wallet);
1726 + Future<void> addErc20Token(WalletBase wallet, CryptoCurrency token);
1727 + Future<void> deleteErc20Token(WalletBase wallet, CryptoCurrency token);
1728 + Future<void> removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token);
1729 + Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress);
1730 +
1731 + Future<PendingTransaction> createTokenApproval(WalletBase wallet, BigInt amount, String spender,
1732 + CryptoCurrency token);
1733 +
1734 + CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction);
1735 + void updateArbitrumScanUsageState(WalletBase wallet, bool isEnabled);
1736 + Web3Client? getWeb3Client(WalletBase wallet);
1737 + String getTokenAddress(CryptoCurrency asset);
1738 +
1739 + Future<void> setHardwareWalletService(WalletBase wallet, HardwareWalletService service);
1740 + HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection);
1741 + HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager);
1742 + List<String> getDefaultTokenContractAddresses();
1743 + bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress);
1744 + Future<bool> isApprovalRequired(WalletBase wallet, String tokenContract, String spender, BigInt requiredAmount);
1745 + Future<PendingTransaction> createRawCallDataTransaction(WalletBase wallet, String to, String dataHex, BigInt valueWei);
1746 + String? getArbitrumNativeEstimatedFee(WalletBase wallet);
1747 + String? getArbitrumERC20EstimatedFee(WalletBase wallet);
1748 +}
1749 +
1750 + """;
1751 +
1752 + const arbitrumEmptyDefinition = 'Arbitrum? arbitrum;\n';
1753 + const arbitrumCWDefinition = 'Arbitrum? arbitrum = CWArbitrum();\n';
1754 +
1755 + final output = '$arbitrumCommonHeaders\n' +
1756 + (hasImplementation ? '$arbitrumCWHeaders\n' : '\n') +
1757 + (hasImplementation ? '$arbitrumCwPart\n\n' : '\n') +
1758 + (hasImplementation ? arbitrumCWDefinition : arbitrumEmptyDefinition) +
1759 + '\n' +
1760 + arbitrumContent;
1761 +
1762 + if (outputFile.existsSync()) {
1763 + await outputFile.delete();
1764 + }
1765 +
1766 + await outputFile.writeAsString(output);
1767 +}
1768 +
1769 Future<void> generatePubspec({
1770 required bool hasMonero,
1771 required bool hasBitcoin,
@@ -1648,6 +1782,7 @@ Future<void> generatePubspec({
1782 required bool hasDecred,
1783 required bool hasDogecoin,
1784 required bool hasBase,
1785 + required bool hasArbitrum,
1786 }) async {
1787 const cwCore = """
1788 cw_core:
@@ -1720,7 +1855,10 @@ Future<void> generatePubspec({
1855 cw_base:
1856 path: ./cw_base
1857 """;
1723 -
1858 + const cwArbitrum = """
1859 + cw_arbitrum:
1860 + path: ./cw_arbitrum
1861 + """;
1862 final inputFile = File(pubspecOutputPath);
1863 final inputText = await inputFile.readAsString();
1864 final inputLines = inputText.split('\n');
@@ -1794,6 +1932,10 @@ Future<void> generatePubspec({
1932 output += '\n$cwBase';
1933 }
1934
1935 + if (hasArbitrum) {
1936 + output += '\n$cwArbitrum';
1937 + }
1938 +
1939 final outputLines = output.split('\n');
1940 inputLines.insertAll(dependenciesIndex + 1, outputLines);
1941 final outputContent = inputLines.join('\n');
@@ -1821,6 +1963,7 @@ Future<void> generateWalletTypes({
1963 required bool hasDecred,
1964 required bool hasDogecoin,
1965 required bool hasBase,
1966 + required bool hasArbitrum,
1967 }) async {
1968 final walletTypesFile = File(walletTypesPath);
1969
@@ -1856,6 +1999,10 @@ Future<void> generateWalletTypes({
1999 outputContent += '\tWalletType.base,\n';
2000 }
2001
2002 + if (hasArbitrum) {
2003 + outputContent += '\tWalletType.arbitrum,\n';
2004 + }
2005 +
2006 if (hasBitcoinCash) {
2007 outputContent += '\tWalletType.bitcoinCash,\n';
2008 }
tool/utils/secret_key.dart
+2
@@ -56,6 +56,7 @@ class SecretKey {
56 SecretKey('bitcoinCashTestWalletSeeds', () => ''),
57 SecretKey('polygonTestWalletSeeds', () => ''),
58 SecretKey('baseTestWalletSeeds', () => ''),
59 + SecretKey('arbitrumTestWalletSeeds', () => ''),
60 SecretKey('solanaTestWalletSeeds', () => ''),
61 SecretKey('tronTestWalletSeeds', () => ''),
62 SecretKey('nanoTestWalletSeeds', () => ''),
@@ -67,6 +68,7 @@ class SecretKey {
68 SecretKey('bitcoinCashTestWalletReceiveAddress', () => ''),
69 SecretKey('polygonTestWalletReceiveAddress', () => ''),
70 SecretKey('baseTestWalletReceiveAddress', () => ''),
71 + SecretKey('arbitrumTestWalletReceiveAddress', () => ''),
72 SecretKey('solanaTestWalletReceiveAddress', () => ''),
73 SecretKey('tronTestWalletReceiveAddress', () => ''),
74 SecretKey('nanoTestWalletReceiveAddress', () => ''),