CW-1194-Add-Arbitrum-Wallet (#2587)
What changed, and why it matters
This is a large feature commit that adds support for the Arbitrum (ARB) blockchain to the Cake Wallet app. It introduces a new Arbitrum wallet package, transaction handling, default token lists, node lists, UI icons, and integration with buy/sell providers and settings. There is no explicit security fix or vulnerability disclosure in the commit message or diff. The changes mostly mirror how existing EVM chains (Ethereum, Polygon, Base) are already handled. A few code-quality observations exist, such as a hard-coded Etherscan API key dependency and a minor syntax issue in a domain list, but nothing that clearly creates a new exploitable security flaw based on the supplied materials.
Treat this as a routine feature addition. Reviewers should verify that the new ArbitrumClient correctly validates Etherscan API responses, that the default node list uses reputable endpoints, and that the shared EVM fee-estimation refactor does not regress fee behavior for Ethereum/Polygon/Base. No immediate security patch is indicated by the supplied materials.
Security signals we found
New EVM chain integration using existing shared EVM code paths
Hard-coded dependency on Etherscan v2 API for transaction history (same pattern as other EVM chains)
Default Arbitrum RPC nodes added to assets/arbitrum_node_list.yml
No explicit security fix, vulnerability disclosure, or CVE reference in commit or supplied materials
Evidence from the diff
The commit adds a new cw_arbitrum package and wires it through the existing EVM chain architecture (cw_evm). It defines ArbitrumClient (chainId 42161, Etherscan v2 API for transaction history), ArbitrumWallet, ArbitrumWalletService, default ERC-20 tokens, wallet type enum values, node defaults, migrations, URI schemes, and UI/UX integrations. It also refactors shared EVM fee estimation logic to support chains without priority fees (Arbitrum sets hasPriorityFee = false) and exposes native/ERC20 estimated fee observables. No security-relevant bug fixes or CVE references are present in the commit or supplied references.
Changed components
cw_arbitrum packagecw_evm shared EVM chain librarycw_core wallet type and currency definitionslib/arbitrum/cw_arbitrum.dartlib/entities/default_settings_migration.dartlib/entities/node_list.dartlib/reactions/wallet_connect.dartlib/view_model/send/fees_view_model.dartAndroidManifestBase.xml and iOS InfoBase.plist URL schemesGitHub Actions workflow secretsInspect captured patch +2115 / −311
diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml
index 5ccf4aa..8ff5c13 100644
--- a/.github/workflows/automated_integration_test.yml
+++ b/.github/workflows/automated_integration_test.yml
@@ -164,6 +164,7 @@ jobs:
echo "const bitcoinCashTestWalletSeeds = '${{ secrets.BITCOIN_CASH_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
echo "const polygonTestWalletSeeds = '${{ secrets.POLYGON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
echo "const baseTestWalletSeeds = '${{ secrets.BASE_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
+ echo "const arbitrumTestWalletSeeds = '${{ secrets.ARBITRUM_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
echo "const solanaTestWalletSeeds = '${{ secrets.SOLANA_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
echo "const solanaTestWalletSeeds2 = '${{ secrets.SOLANA_TEST_WALLET_SEEDS2 }}';" >> lib/.secrets.g.dart
echo "const tronTestWalletSeeds = '${{ secrets.TRON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
@@ -178,6 +179,7 @@ jobs:
echo "const bitcoinCashTestWalletReceiveAddress = '${{ secrets.BITCOIN_CASH_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
echo "const polygonTestWalletReceiveAddress = '${{ secrets.POLYGON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
echo "const baseTestWalletReceiveAddress = '${{ secrets.BASE_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
+ echo "const arbitrumTestWalletReceiveAddress = '${{ secrets.ARBITRUM_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
echo "const solanaTestWalletReceiveAddress = '${{ secrets.SOLANA_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
echo "const tronTestWalletReceiveAddress = '${{ secrets.TRON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
echo "const nanoTestWalletReceiveAddress = '${{ secrets.NANO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
diff --git a/.github/workflows/no_restricted_imports.yaml b/.github/workflows/no_restricted_imports.yaml
index 2b07c27..18b228d 100644
--- a/.github/workflows/no_restricted_imports.yaml
+++ b/.github/workflows/no_restricted_imports.yaml
@@ -21,6 +21,7 @@ jobs:
"cw_nano"
"cw_polygon"
"cw_base"
+ "cw_arbitrum"
"cw_solana"
"cw_tron"
"cw_wownero"
diff --git a/.github/workflows/pr_test_build_android.yml b/.github/workflows/pr_test_build_android.yml
index a8ea6aa..02f6e1b 100644
--- a/.github/workflows/pr_test_build_android.yml
+++ b/.github/workflows/pr_test_build_android.yml
@@ -158,6 +158,7 @@ jobs:
echo "const bitcoinCashTestWalletSeeds = '${{ secrets.BITCOIN_CASH_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
echo "const polygonTestWalletSeeds = '${{ secrets.POLYGON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
echo "const baseTestWalletSeeds = '${{ secrets.BASE_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
+ echo "const arbitrumTestWalletSeeds = '${{ secrets.ARBITRUM_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
echo "const solanaTestWalletSeeds = '${{ secrets.SOLANA_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
echo "const tronTestWalletSeeds = '${{ secrets.TRON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
echo "const nanoTestWalletSeeds = '${{ secrets.NANO_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
@@ -170,6 +171,7 @@ jobs:
echo "const bitcoinCashTestWalletReceiveAddress = '${{ secrets.BITCOIN_CASH_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
echo "const polygonTestWalletReceiveAddress = '${{ secrets.POLYGON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
echo "const baseTestWalletReceiveAddress = '${{ secrets.BASE_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
+ echo "const arbitrumTestWalletReceiveAddress = '${{ secrets.ARBITRUM_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
echo "const solanaTestWalletReceiveAddress = '${{ secrets.SOLANA_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
echo "const tronTestWalletReceiveAddress = '${{ secrets.TRON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
echo "const nanoTestWalletReceiveAddress = '${{ secrets.NANO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
diff --git a/.github/workflows/pr_test_build_linux.yml b/.github/workflows/pr_test_build_linux.yml
index 0e736ee..903f77a 100644
--- a/.github/workflows/pr_test_build_linux.yml
+++ b/.github/workflows/pr_test_build_linux.yml
@@ -151,6 +151,7 @@ jobs:
echo "const bitcoinCashTestWalletSeeds = '${{ secrets.BITCOIN_CASH_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
echo "const polygonTestWalletSeeds = '${{ secrets.POLYGON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
echo "const baseTestWalletSeeds = '${{ secrets.BASE_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
+ echo "const arbitrumTestWalletSeeds = '${{ secrets.ARBITRUM_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
echo "const solanaTestWalletSeeds = '${{ secrets.SOLANA_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
echo "const tronTestWalletSeeds = '${{ secrets.TRON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
echo "const nanoTestWalletSeeds = '${{ secrets.NANO_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
@@ -164,6 +165,7 @@ jobs:
echo "const bitcoinCashTestWalletReceiveAddress = '${{ secrets.BITCOIN_CASH_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
echo "const polygonTestWalletReceiveAddress = '${{ secrets.POLYGON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
echo "const baseTestWalletReceiveAddress = '${{ secrets.BASE_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
+ echo "const arbitrumTestWalletReceiveAddress = '${{ secrets.ARBITRUM_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
echo "const solanaTestWalletReceiveAddress = '${{ secrets.SOLANA_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
echo "const tronTestWalletReceiveAddress = '${{ secrets.TRON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
echo "const nanoTestWalletReceiveAddress = '${{ secrets.NANO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
diff --git a/.gitignore b/.gitignore
index be83f1a..cd22305 100644
--- a/.gitignore
+++ b/.gitignore
@@ -142,6 +142,7 @@ lib/zano/zano.dart
lib/decred/decred.dart
lib/dogecoin/dogecoin.dart
lib/base/base.dart
+lib/arbitrum/arbitrum.dart
ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@2x.png
ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@2x~ipad.png
diff --git a/README.md b/README.md
index 1266d08..43a72e0 100644
--- a/README.md
+++ b/README.md
@@ -26,6 +26,7 @@ Cake Wallet includes support for several cryptocurrencies, including:
* Bitcoin Cash (BCH)
* Polygon (POL)
* Base (BASE)
+* Arbitrum (ARB)
* Solana (SOL)
* Tron (TRX)
* Nano (XNO)
diff --git a/analysis_options.yaml b/analysis_options.yaml
index 8b07015..4f6f0f6 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -25,6 +25,7 @@ analyzer:
lib/wownero/cw_wownero.dart,
lib/zano/cw_zano.dart,
lib/base/cw_base.dart,
+ lib/arbitrum/cw_arbitrum.dart,
]
language:
strict-casts: true
diff --git a/android/app/src/main/AndroidManifestBase.xml b/android/app/src/main/AndroidManifestBase.xml
index 287554c..6b3b2cb 100644
--- a/android/app/src/main/AndroidManifestBase.xml
+++ b/android/app/src/main/AndroidManifestBase.xml
@@ -109,6 +109,9 @@
<data android:scheme="base" />
<data android:scheme="base-wallet" />
<data android:scheme="base_wallet" />
+ <data android:scheme="arbitrum" />
+ <data android:scheme="arbitrum-wallet" />
+ <data android:scheme="arbitrum_wallet" />
</intent-filter>
<!-- nano-gpt link scheme -->
<intent-filter android:autoVerify="true">
diff --git a/assets/arbitrum_node_list.yml b/assets/arbitrum_node_list.yml
new file mode 100644
index 0000000..63894f1
--- /dev/null
+++ b/assets/arbitrum_node_list.yml
@@ -0,0 +1,12 @@
+-
+ uri: arbitrum.nownodes.io
+ is_default: true
+ useSSL: true
+ isEnabledForAutoSwitching: true
+-
+ uri: arbitrum.drpc.org
+ useSSL: true
+-
+ uri: arbitrum-one-rpc.publicnode.com
+ useSSL: true
+ isEnabledForAutoSwitching: true
\ No newline at end of file
diff --git a/assets/images/arbitrum_chain_QR.svg b/assets/images/arbitrum_chain_QR.svg
new file mode 100644
index 0000000..fa33383
--- /dev/null
+++ b/assets/images/arbitrum_chain_QR.svg
@@ -0,0 +1,27 @@
+<svg width="45" height="45" viewBox="0 0 45 45" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect x="1" y="1" width="43" height="43" rx="6.03125" fill="white"/>
+<rect x="1" y="1" width="43" height="43" rx="6.03125" stroke="black" stroke-width="2"/>
+<g clip-path="url(#clip0_10599_14372)">
+<g clip-path="url(#clip1_10599_14372)">
+<mask id="mask0_10599_14372" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="8" y="6" width="29" height="33">
+<path d="M36.6426 6.52493H8.0957V38.0249H36.6426V6.52493Z" fill="white"/>
+</mask>
+<g mask="url(#mask0_10599_14372)">
+<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"/>
+<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"/>
+<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"/>
+<path d="M14.5483 33.9725L15.6357 31.0603L17.8235 32.8386L15.7806 34.6683L14.5483 33.9725Z" fill="black"/>
+<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"/>
+<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"/>
+</g>
+</g>
+</g>
+<defs>
+<clipPath id="clip0_10599_14372">
+<rect width="31.5" height="31.5" fill="white" transform="translate(6.52502 6.52499)"/>
+</clipPath>
+<clipPath id="clip1_10599_14372">
+<rect width="28.5469" height="31.5" fill="white" transform="translate(8.1001 6.52496)"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/assets/images/arbitrum_chain_mono.svg b/assets/images/arbitrum_chain_mono.svg
new file mode 100644
index 0000000..480dd1d
--- /dev/null
+++ b/assets/images/arbitrum_chain_mono.svg
@@ -0,0 +1,3 @@
+<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
+<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"/>
+</svg>
diff --git a/assets/images/crypto/arbitrum.webp b/assets/images/crypto/arbitrum.webp
new file mode 100644
index 0000000..b0c78bd
Binary files /dev/null and b/assets/images/crypto/arbitrum.webp differ
diff --git a/cakewallet.bat b/cakewallet.bat
index 8baa910..d601741 100644
--- a/cakewallet.bat
+++ b/cakewallet.bat
@@ -1,5 +1,5 @@
@echo off
-set cw_win_app_config=--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --dogecoin --base
+set cw_win_app_config=--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --dogecoin --base --arbitrum
set cw_root=%cd%
set cw_archive_name=Cake Wallet.zip
set cw_archive_path=%cw_root%\%cw_archive_name%
@@ -24,7 +24,7 @@ IF NOT EXIST "%secrets_file_path%" (
) ELSE (echo === Using previously/already generated secrets file: %secrets_file_path% ===)
echo === Generating mobx models ===
-for /d %%i in (cw_core cw_monero cw_bitcoin cw_ethereum cw_evm cw_polygon cw_nano cw_bitcoin_cash cw_solana cw_tron cw_base.) do (
+for /d %%i in (cw_core cw_monero cw_bitcoin cw_ethereum cw_evm cw_polygon cw_nano cw_bitcoin_cash cw_solana cw_tron cw_base cw_arbitrum.) do (
cd %%i
call flutter pub get > nul
call dart run build_runner build --delete-conflicting-outputs > nul
diff --git a/cw_arbitrum/.gitignore b/cw_arbitrum/.gitignore
new file mode 100644
index 0000000..eb6c05c
--- /dev/null
+++ b/cw_arbitrum/.gitignore
@@ -0,0 +1,31 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
+/pubspec.lock
+**/doc/api/
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+build/
diff --git a/cw_arbitrum/.metadata b/cw_arbitrum/.metadata
new file mode 100644
index 0000000..6f8d1aa
--- /dev/null
+++ b/cw_arbitrum/.metadata
@@ -0,0 +1,10 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+ revision: "fcf2c11572af6f390246c056bc905eca609533a0"
+ channel: "[user-branch]"
+
+project_type: package
diff --git a/cw_arbitrum/CHANGELOG.md b/cw_arbitrum/CHANGELOG.md
new file mode 100644
index 0000000..41cc7d8
--- /dev/null
+++ b/cw_arbitrum/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 0.0.1
+
+* TODO: Describe initial release.
diff --git a/cw_arbitrum/LICENSE b/cw_arbitrum/LICENSE
new file mode 100644
index 0000000..ba75c69
--- /dev/null
+++ b/cw_arbitrum/LICENSE
@@ -0,0 +1 @@
+TODO: Add your license here.
diff --git a/cw_arbitrum/README.md b/cw_arbitrum/README.md
new file mode 100644
index 0000000..4a260d8
--- /dev/null
+++ b/cw_arbitrum/README.md
@@ -0,0 +1,39 @@
+<!--
+This README describes the package. If you publish this package to pub.dev,
+this README's contents appear on the landing page for your package.
+
+For information about how to write a good package README, see the guide for
+[writing package pages](https://dart.dev/tools/pub/writing-package-pages).
+
+For general information about developing packages, see the Dart guide for
+[creating packages](https://dart.dev/guides/libraries/create-packages)
+and the Flutter guide for
+[developing packages and plugins](https://flutter.dev/to/develop-packages).
+-->
+
+TODO: Put a short description of the package here that helps potential users
+know whether this package might be useful for them.
+
+## Features
+
+TODO: List what your package can do. Maybe include images, gifs, or videos.
+
+## Getting started
+
+TODO: List prerequisites and provide or point to information on how to
+start using the package.
+
+## Usage
+
+TODO: Include short and useful examples for package users. Add longer examples
+to `/example` folder.
+
+```dart
+const like = 'sample';
+```
+
+## Additional information
+
+TODO: Tell users more about the package: where to find more information, how to
+contribute to the package, how to file issues, what response they can expect
+from the package authors, and more.
diff --git a/cw_arbitrum/analysis_options.yaml b/cw_arbitrum/analysis_options.yaml
new file mode 100644
index 0000000..a5744c1
--- /dev/null
+++ b/cw_arbitrum/analysis_options.yaml
@@ -0,0 +1,4 @@
+include: package:flutter_lints/flutter.yaml
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
diff --git a/cw_arbitrum/lib/arbirtrum_mnemonics_exception.dart b/cw_arbitrum/lib/arbirtrum_mnemonics_exception.dart
new file mode 100644
index 0000000..8af5612
--- /dev/null
+++ b/cw_arbitrum/lib/arbirtrum_mnemonics_exception.dart
@@ -0,0 +1,5 @@
+class ArbitrumMnemonicIsIncorrectException implements Exception {
+ @override
+ String toString() =>
+ 'Arbitrum mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.';
+}
diff --git a/cw_arbitrum/lib/arbitrum_client.dart b/cw_arbitrum/lib/arbitrum_client.dart
new file mode 100644
index 0000000..f9fb36d
--- /dev/null
+++ b/cw_arbitrum/lib/arbitrum_client.dart
@@ -0,0 +1,107 @@
+import 'dart:convert';
+
+import 'package:cw_evm/.secrets.g.dart' as secrets;
+import 'package:cw_evm/evm_chain_client.dart';
+import 'package:cw_evm/evm_chain_transaction_model.dart';
+import 'package:flutter/foundation.dart';
+import 'package:web3dart/web3dart.dart';
+
+class ArbitrumClient extends EVMChainClient {
+ @override
+ Transaction createTransaction({
+ required EthereumAddress from,
+ required EthereumAddress to,
+ required EtherAmount amount,
+ EtherAmount? maxPriorityFeePerGas,
+ Uint8List? data,
+ int? maxGas,
+ EtherAmount? gasPrice,
+ EtherAmount? maxFeePerGas,
+ }) {
+ EtherAmount? finalGasPrice = gasPrice;
+
+ if (gasPrice == null && maxFeePerGas != null) {
+ // If we have EIP-1559 parameters but no legacy gasPrice, then use maxFeePerGas as gasPrice
+ finalGasPrice = maxFeePerGas;
+ }
+
+ return Transaction(
+ from: from,
+ to: to,
+ value: amount,
+ data: data,
+ maxGas: maxGas,
+ gasPrice: finalGasPrice,
+ // maxFeePerGas: maxFeePerGas,
+ // maxPriorityFeePerGas: maxPriorityFeePerGas,
+ );
+ }
+
+ @override
+ Uint8List prepareSignedTransactionForSending(Uint8List signedTransaction) => signedTransaction;
+
+ @override
+ int get chainId => 42161;
+
+ @override
+ Future<List<EVMChainTransactionModel>> fetchTransactions(
+ String address, {
+ String? contractAddress,
+ }) async {
+ try {
+ final response = await client.get(
+ Uri.https("api.etherscan.io", "/v2/api", {
+ "chainid": "$chainId",
+ "module": "account",
+ "action": contractAddress != null ? "tokentx" : "txlist",
+ if (contractAddress != null) "contractaddress": contractAddress,
+ "address": address,
+ "apikey": secrets.etherScanApiKey,
+ }),
+ );
+
+ final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
+
+ if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) {
+ final res = (jsonResponse['result'] as List);
+
+ res.removeWhere((e) => e['value'] == '0');
+
+ return res
+ .map((e) => EVMChainTransactionModel.fromJson(e as Map<String, dynamic>, 'ETH'))
+ .toList();
+ }
+
+ return [];
+ } catch (e) {
+ return [];
+ }
+ }
+
+ @override
+ Future<List<EVMChainTransactionModel>> fetchInternalTransactions(String address) async {
+ try {
+ final response = await client.get(
+ Uri.https("api.etherscan.io", "/v2/api", {
+ "chainid": "$chainId",
+ "module": "account",
+ "action": "txlistinternal",
+ "address": address,
+ "apikey": secrets.etherScanApiKey,
+ }),
+ );
+
+ final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
+
+ if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) {
+ return (jsonResponse['result'] as List)
+ .map((e) => EVMChainTransactionModel.fromJson(e as Map<String, dynamic>, 'ETH'))
+ .toList();
+ }
+
+ return [];
+ } catch (_) {
+ return [];
+ }
+ }
+}
diff --git a/cw_arbitrum/lib/arbitrum_transaction_history.dart b/cw_arbitrum/lib/arbitrum_transaction_history.dart
new file mode 100644
index 0000000..1319c7a
--- /dev/null
+++ b/cw_arbitrum/lib/arbitrum_transaction_history.dart
@@ -0,0 +1,20 @@
+import 'dart:core';
+
+import 'package:cw_evm/evm_chain_transaction_history.dart';
+import 'package:cw_evm/evm_chain_transaction_info.dart';
+import 'package:cw_arbitrum/arbitrum_transaction_info.dart';
+
+class ArbitrumTransactionHistory extends EVMChainTransactionHistory {
+ ArbitrumTransactionHistory({
+ required super.walletInfo,
+ required super.password,
+ required super.encryptionFileUtils,
+ });
+
+ @override
+ String getTransactionHistoryFileName() => 'arbitrum_transactions.json';
+
+ @override
+ EVMChainTransactionInfo getTransactionInfo(Map<String, dynamic> val) =>
+ ArbitrumTransactionInfo.fromJson(val);
+}
diff --git a/cw_arbitrum/lib/arbitrum_transaction_info.dart b/cw_arbitrum/lib/arbitrum_transaction_info.dart
new file mode 100644
index 0000000..d124999
--- /dev/null
+++ b/cw_arbitrum/lib/arbitrum_transaction_info.dart
@@ -0,0 +1,41 @@
+import 'package:cw_core/transaction_direction.dart';
+import 'package:cw_evm/evm_chain_transaction_info.dart';
+
+class ArbitrumTransactionInfo extends EVMChainTransactionInfo {
+ ArbitrumTransactionInfo({
+ required super.id,
+ required super.height,
+ required super.ethAmount,
+ required super.ethFee,
+ required super.tokenSymbol,
+ required super.direction,
+ required super.isPending,
+ required super.date,
+ required super.confirmations,
+ required super.to,
+ required super.from,
+ super.contractAddress,
+ super.exponent,
+ });
+
+ factory ArbitrumTransactionInfo.fromJson(Map<String, dynamic> data) {
+ return ArbitrumTransactionInfo(
+ id: data['id'] as String,
+ height: data['height'] as int,
+ ethAmount: BigInt.parse(data['amount']),
+ exponent: data['exponent'] as int,
+ ethFee: BigInt.parse(data['fee']),
+ direction: parseTransactionDirectionFromInt(data['direction'] as int),
+ date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
+ isPending: data['isPending'] as bool,
+ confirmations: data['confirmations'] as int,
+ tokenSymbol: data['tokenSymbol'] as String,
+ to: data['to'],
+ from: data['from'],
+ contractAddress: data['contractAddress'],
+ );
+ }
+
+ @override
+ String get feeCurrency => 'ETH';
+}
diff --git a/cw_arbitrum/lib/arbitrum_wallet.dart b/cw_arbitrum/lib/arbitrum_wallet.dart
new file mode 100644
index 0000000..6181f11
--- /dev/null
+++ b/cw_arbitrum/lib/arbitrum_wallet.dart
@@ -0,0 +1,179 @@
+import 'dart:convert';
+
+import 'package:cw_arbitrum/arbitrum_client.dart';
+import 'package:cw_arbitrum/arbitrum_transaction_history.dart';
+import 'package:cw_arbitrum/arbitrum_transaction_info.dart';
+import 'package:cw_arbitrum/default_arbitrum_erc20_tokens.dart';
+import 'package:cw_core/cake_hive.dart';
+import 'package:cw_core/crypto_currency.dart';
+import 'package:cw_core/encryption_file_utils.dart';
+import 'package:cw_core/erc20_token.dart';
+import 'package:cw_core/pathForWallet.dart';
+import 'package:cw_core/transaction_direction.dart';
+import 'package:cw_core/wallet_info.dart';
+import 'package:cw_core/wallet_keys_file.dart';
+import 'package:cw_evm/evm_chain_transaction_history.dart';
+import 'package:cw_evm/evm_chain_transaction_info.dart';
+import 'package:cw_evm/evm_chain_transaction_model.dart';
+import 'package:cw_evm/evm_chain_wallet.dart';
+import 'package:cw_evm/evm_erc20_balance.dart';
+
+class ArbitrumWallet extends EVMChainWallet {
+ ArbitrumWallet({
+ required super.walletInfo,
+ required super.password,
+ required super.derivationInfo,
+ super.mnemonic,
+ super.initialBalance,
+ super.privateKey,
+ required super.client,
+ required super.encryptionFileUtils,
+ super.passphrase,
+ }) : super(nativeCurrency: CryptoCurrency.arbEth);
+
+ @override
+ bool get hasPriorityFee => false;
+
+ @override
+ Future<void> initErc20TokensBox() async {
+ final boxName = "${walletInfo.name.replaceAll(" ", "_")}_${Erc20Token.arbitrumBoxName}";
+
+ evmChainErc20TokensBox = await CakeHive.openBox<Erc20Token>(boxName);
+ }
+
+ @override
+ void addInitialTokens() {
+ final initialErc20Tokens = DefaultArbitrumErc20Tokens().initialArbitrumErc20Tokens;
+
+ for (final token in initialErc20Tokens) {
+ if (!evmChainErc20TokensBox.containsKey(token.contractAddress)) {
+ evmChainErc20TokensBox.put(token.contractAddress, token);
+ } else {
+ // update existing token
+ final existingToken = evmChainErc20TokensBox.get(token.contractAddress);
+ evmChainErc20TokensBox.put(
+ token.contractAddress,
+ Erc20Token.copyWith(token, enabled: existingToken!.enabled),
+ );
+ }
+ }
+ }
+
+ @override
+ List<String> get getDefaultTokenContractAddresses => DefaultArbitrumErc20Tokens()
+ .initialArbitrumErc20Tokens
+ .map((e) => e.contractAddress)
+ .toList();
+
+ @override
+ Future<bool> checkIfScanProviderIsEnabled() async {
+ return (await sharedPrefs.future).getBool("use_arbiscan") ?? true;
+ }
+
+ @override
+ String getTransactionHistoryFileName() => 'arbitrum_transactions.json';
+
+ @override
+ Erc20Token createNewErc20TokenObject(Erc20Token token, String? iconPath) {
+ return Erc20Token(
+ name: token.name,
+ symbol: token.symbol,
+ contractAddress: token.contractAddress,
+ decimal: token.decimal,
+ enabled: token.enabled,
+ tag: token.tag ?? 'ETH',
+ iconPath: iconPath,
+ isPotentialScam: token.isPotentialScam,
+ );
+ }
+
+ @override
+ EVMChainTransactionInfo getTransactionInfo(
+ EVMChainTransactionModel transactionModel,
+ String address,
+ ) {
+ final model = ArbitrumTransactionInfo(
+ id: transactionModel.hash,
+ height: transactionModel.blockNumber,
+ ethAmount: transactionModel.amount,
+ direction: transactionModel.from == address
+ ? TransactionDirection.outgoing
+ : TransactionDirection.incoming,
+ isPending: false,
+ date: transactionModel.date,
+ confirmations: transactionModel.confirmations,
+ ethFee: BigInt.from(transactionModel.gasUsed) * transactionModel.gasPrice,
+ exponent: transactionModel.tokenDecimal ?? 18,
+ tokenSymbol: transactionModel.tokenSymbol ?? "ETH",
+ to: transactionModel.to,
+ from: transactionModel.from,
+ contractAddress: transactionModel.contractAddress,
+ );
+ return model;
+ }
+
+ @override
+ EVMChainTransactionHistory setUpTransactionHistory(
+ WalletInfo walletInfo,
+ String password,
+ EncryptionFileUtils encryptionFileUtils,
+ ) {
+ return ArbitrumTransactionHistory(
+ walletInfo: walletInfo,
+ password: password,
+ encryptionFileUtils: encryptionFileUtils,
+ );
+ }
+
+ static Future<ArbitrumWallet> open({
+ required String name,
+ required String password,
+ required WalletInfo walletInfo,
+ required EncryptionFileUtils encryptionFileUtils,
+ }) async {
+ final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
+ final path = await pathForWallet(name: name, type: walletInfo.type);
+
+ Map<String, dynamic>? data;
+ try {
+ final jsonSource = await encryptionFileUtils.read(path: path, password: password);
+
+ data = json.decode(jsonSource) as Map<String, dynamic>;
+ } catch (e) {
+ if (!hasKeysFile) rethrow;
+ }
+
+ final balance =
+ EVMChainERC20Balance.fromJSON(data?['balance'] as String?) ??
+ EVMChainERC20Balance(BigInt.zero);
+
+ final WalletKeysData keysData;
+ // Migrate wallet from the old scheme to then new .keys file scheme
+ if (!hasKeysFile) {
+ final mnemonic = data!['mnemonic'] as String?;
+ final privateKey = data['private_key'] as String?;
+ final passphrase = data['passphrase'] as String?;
+
+ keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey, passphrase: passphrase);
+ } else {
+ keysData = await WalletKeysFile.readKeysFile(
+ name,
+ walletInfo.type,
+ password,
+ encryptionFileUtils,
+ );
+ }
+
+ return ArbitrumWallet(
+ walletInfo: walletInfo,
+ derivationInfo: await walletInfo.getDerivationInfo(),
+ password: password,
+ mnemonic: keysData.mnemonic,
+ privateKey: keysData.privateKey,
+ passphrase: keysData.passphrase,
+ initialBalance: balance,
+ client: ArbitrumClient(),
+ encryptionFileUtils: encryptionFileUtils,
+ );
+ }
+}
diff --git a/cw_arbitrum/lib/arbitrum_wallet_service.dart b/cw_arbitrum/lib/arbitrum_wallet_service.dart
new file mode 100644
index 0000000..91e0b86
--- /dev/null
+++ b/cw_arbitrum/lib/arbitrum_wallet_service.dart
@@ -0,0 +1,174 @@
+import 'package:bip39/bip39.dart' as bip39;
+import 'package:cw_arbitrum/arbirtrum_mnemonics_exception.dart';
+import 'package:cw_core/encryption_file_utils.dart';
+import 'package:cw_core/wallet_base.dart';
+import 'package:cw_core/wallet_info.dart';
+import 'package:cw_core/wallet_type.dart';
+import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
+import 'package:cw_evm/evm_chain_wallet_service.dart';
+import 'package:cw_arbitrum/arbitrum_wallet.dart';
+import 'package:cw_arbitrum/arbitrum_client.dart';
+
+class ArbitrumWalletService extends EVMChainWalletService<ArbitrumWallet> {
+ ArbitrumWalletService(super.isDirect, {required this.client});
+
+ late ArbitrumClient client;
+
+ @override
+ WalletType getType() => WalletType.arbitrum;
+
+ @override
+ Future<ArbitrumWallet> create(EVMChainNewWalletCredentials credentials, {bool? isTestnet}) async {
+ final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
+
+ final mnemonic = credentials.mnemonic ?? bip39.generateMnemonic(strength: strength);
+
+ final wallet = ArbitrumWallet(
+ walletInfo: credentials.walletInfo!,
+ derivationInfo: await credentials.walletInfo!.getDerivationInfo(),
+ mnemonic: mnemonic,
+ password: credentials.password!,
+ passphrase: credentials.passphrase,
+ client: client,
+ encryptionFileUtils: encryptionFileUtilsFor(isDirect),
+ );
+
+ await wallet.init();
+ wallet.addInitialTokens();
+ await wallet.save();
+ return wallet;
+ }
+
+ @override
+ Future<ArbitrumWallet> openWallet(String name, String password) async {
+ final walletInfo = await WalletInfo.get(name, getType());
+ if (walletInfo == null) {
+ throw Exception('Wallet not found');
+ }
+
+ try {
+ final wallet = await ArbitrumWallet.open(
+ name: name,
+ password: password,
+ walletInfo: walletInfo,
+ encryptionFileUtils: encryptionFileUtilsFor(isDirect),
+ );
+
+ await wallet.init();
+ wallet.addInitialTokens();
+ await wallet.save();
+ saveBackup(name);
+ return wallet;
+ } catch (_) {
+ await restoreWalletFilesFromBackup(name);
+
+ final wallet = await ArbitrumWallet.open(
+ name: name,
+ password: password,
+ walletInfo: walletInfo,
+ encryptionFileUtils: encryptionFileUtilsFor(isDirect),
+ );
+
+ await wallet.init();
+ wallet.addInitialTokens();
+ await wallet.save();
+ return wallet;
+ }
+ }
+
+ @override
+ Future<ArbitrumWallet> restoreFromKeys(
+ EVMChainRestoreWalletFromPrivateKey credentials, {
+ bool? isTestnet,
+ }) async {
+ final wallet = ArbitrumWallet(
+ password: credentials.password!,
+ privateKey: credentials.privateKey,
+ walletInfo: credentials.walletInfo!,
+ derivationInfo: await credentials.walletInfo!.getDerivationInfo(),
+ client: client,
+ encryptionFileUtils: encryptionFileUtilsFor(isDirect),
+ );
+
+ await wallet.init();
+ wallet.addInitialTokens();
+ await wallet.save();
+ return wallet;
+ }
+
+ @override
+ Future<ArbitrumWallet> restoreFromHardwareWallet(
+ EVMChainRestoreWalletFromHardware credentials,
+ ) async {
+ final derivationInfo = await credentials.walletInfo!.getDerivationInfo();
+ derivationInfo.derivationType = DerivationType.bip39;
+ derivationInfo.derivationPath = "m/44'/60'/${credentials.hwAccountData.accountIndex}'/0/0";
+ await derivationInfo.save();
+ credentials.walletInfo!.hardwareWalletType = credentials.hardwareWalletType;
+ credentials.walletInfo!.address = credentials.hwAccountData.address;
+ await credentials.walletInfo!.save();
+
+ final wallet = ArbitrumWallet(
+ walletInfo: credentials.walletInfo!,
+ derivationInfo: derivationInfo,
+ password: credentials.password!,
+ client: client,
+ encryptionFileUtils: encryptionFileUtilsFor(isDirect),
+ );
+
+ await wallet.init();
+ wallet.addInitialTokens();
+ await wallet.save();
+
+ return wallet;
+ }
+
+ @override
+ Future<ArbitrumWallet> restoreFromSeed(
+ EVMChainRestoreWalletFromSeedCredentials credentials, {
+ bool? isTestnet,
+ }) async {
+ if (!bip39.validateMnemonic(credentials.mnemonic)) {
+ throw ArbitrumMnemonicIsIncorrectException();
+ }
+
+ final wallet = ArbitrumWallet(
+ password: credentials.password!,
+ mnemonic: credentials.mnemonic,
+ walletInfo: credentials.walletInfo!,
+ derivationInfo: await credentials.walletInfo!.getDerivationInfo(),
+ passphrase: credentials.passphrase,
+ client: client,
+ encryptionFileUtils: encryptionFileUtilsFor(isDirect),
+ );
+
+ await wallet.init();
+ wallet.addInitialTokens();
+ await wallet.save();
+
+ return wallet;
+ }
+
+ @override
+ Future<void> rename(String currentName, String password, String newName) async {
+ final currentWalletInfo = await WalletInfo.get(currentName, getType());
+ if (currentWalletInfo == null) {
+ throw Exception('Wallet not found');
+ }
+ final currentWallet = await ArbitrumWallet.open(
+ password: password,
+ name: currentName,
+ walletInfo: currentWalletInfo,
+ encryptionFileUtils: encryptionFileUtilsFor(isDirect),
+ );
+
+ await currentWallet.renameWalletFiles(newName);
+ await saveBackup(newName);
+
+ final newWalletInfo = currentWalletInfo;
+ newWalletInfo.id = WalletBase.idFor(newName, getType());
+ newWalletInfo.name = newName;
+
+ newWalletInfo.save();
+ }
+}
diff --git a/cw_arbitrum/lib/cw_arbitrum.dart b/cw_arbitrum/lib/cw_arbitrum.dart
new file mode 100644
index 0000000..298576d
--- /dev/null
+++ b/cw_arbitrum/lib/cw_arbitrum.dart
@@ -0,0 +1,5 @@
+/// A Calculator.
+class Calculator {
+ /// Returns [value] plus 1.
+ int addOne(int value) => value + 1;
+}
diff --git a/cw_arbitrum/lib/default_arbitrum_erc20_tokens.dart b/cw_arbitrum/lib/default_arbitrum_erc20_tokens.dart
new file mode 100644
index 0000000..25cc573
--- /dev/null
+++ b/cw_arbitrum/lib/default_arbitrum_erc20_tokens.dart
@@ -0,0 +1,71 @@
+import 'package:cw_core/crypto_currency.dart';
+import 'package:cw_core/erc20_token.dart';
+
+class DefaultArbitrumErc20Tokens {
+ final List<Erc20Token> _defaultTokens = [
+ Erc20Token(
+ name: "USD Coin",
+ symbol: "USDC",
+ contractAddress: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
+ decimal: 6,
+ enabled: true,
+ ),
+ Erc20Token(
+ name: "USDC.e",
+ symbol: "USDC.e",
+ contractAddress: "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8",
+ decimal: 6,
+ enabled: true,
+ ),
+ Erc20Token(
+ name: "Wrapped BTC",
+ symbol: "WBTC",
+ contractAddress: "0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f",
+ decimal: 8,
+ enabled: true,
+ ),
+ Erc20Token(
+ name: "Chainlink Token",
+ symbol: "LINK",
+ contractAddress: "0xf97f4df75117a78c1A5a0DBb814Af92458539FB4",
+ decimal: 18,
+ enabled: true,
+ ),
+ Erc20Token(
+ name: "Wrapped liquid staked Ether 2.0",
+ symbol: "wstETH",
+ contractAddress: "0x0fBcbaEA96Ce0cF7Ee00A8c19c3ab6f5Dc8E1921",
+ decimal: 18,
+ enabled: false,
+ ),
+ Erc20Token(
+ name: "Wrapped Ether",
+ symbol: "WETH",
+ contractAddress: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
+ decimal: 18,
+ enabled: false,
+ ),
+ Erc20Token(
+ name: "DAI",
+ symbol: "DAI",
+ contractAddress: "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1",
+ decimal: 18,
+ enabled: false,
+ ),
+ ];
+
+ List<Erc20Token> get initialArbitrumErc20Tokens => _defaultTokens.map((token) {
+ String? iconPath;
+ if (token.iconPath?.isEmpty ?? true) {
+ try {
+ iconPath = CryptoCurrency.all
+ .firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase())
+ .iconPath;
+ } catch (_) {}
+ } else {
+ iconPath = token.iconPath;
+ }
+
+ return Erc20Token.copyWith(token, icon: iconPath, tag: 'ARB');
+ }).toList();
+}
diff --git a/cw_arbitrum/pubspec.yaml b/cw_arbitrum/pubspec.yaml
new file mode 100644
index 0000000..f6cb365
--- /dev/null
+++ b/cw_arbitrum/pubspec.yaml
@@ -0,0 +1,73 @@
+name: cw_arbitrum
+description: "A new Flutter package project."
+version: 0.0.1
+publish_to: none
+homepage: https://cakewallet.com
+
+environment:
+ sdk: '>=3.0.6 <4.0.0'
+ flutter: ">=1.17.0"
+
+dependencies:
+ flutter:
+ sdk: flutter
+ cw_core:
+ path: ../cw_core
+ cw_ethereum:
+ path: ../cw_ethereum
+ cw_evm:
+ path: ../cw_evm
+ web3dart: ^2.7.1
+ hive: ^2.2.3
+ bip39: ^1.0.6
+ collection: ^1.17.1
+
+dependency_overrides:
+ web3dart:
+ git:
+ url: https://github.com/cake-tech/web3dart.git
+ ref: cake
+ watcher: ^1.1.0
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+ flutter_lints: ^2.0.0
+ build_runner: ^2.4.15
+
+# For information on the generic Dart part of this file, see the
+# following page: https://dart.dev/tools/pub/pubspec
+
+# The following section is specific to Flutter packages.
+flutter:
+
+ # To add assets to your package, add an assets section, like this:
+ # assets:
+ # - images/a_dot_burr.jpeg
+ # - images/a_dot_ham.jpeg
+ #
+ # For details regarding assets in packages, see
+ # https://flutter.dev/to/asset-from-package
+ #
+ # An image asset can refer to one or more resolution-specific "variants", see
+ # https://flutter.dev/to/resolution-aware-images
+
+ # To add custom fonts to your package, add a fonts section here,
+ # in this "flutter" section. Each entry in this list should have a
+ # "family" key with the font family name, and a "fonts" key with a
+ # list giving the asset and other descriptors for the font. For
+ # example:
+ # fonts:
+ # - family: Schyler
+ # fonts:
+ # - asset: fonts/Schyler-Regular.ttf
+ # - asset: fonts/Schyler-Italic.ttf
+ # style: italic
+ # - family: Trajan Pro
+ # fonts:
+ # - asset: fonts/TrajanPro.ttf
+ # - asset: fonts/TrajanPro_Bold.ttf
+ # weight: 700
+ #
+ # For details regarding fonts in packages, see
+ # https://flutter.dev/to/font-from-package
diff --git a/cw_arbitrum/test/cw_arbitrum_test.dart b/cw_arbitrum/test/cw_arbitrum_test.dart
new file mode 100644
index 0000000..5cf3562
--- /dev/null
+++ b/cw_arbitrum/test/cw_arbitrum_test.dart
@@ -0,0 +1,12 @@
+import 'package:flutter_test/flutter_test.dart';
+
+import 'package:cw_arbitrum/cw_arbitrum.dart';
+
+void main() {
+ test('adds one to input values', () {
+ final calculator = Calculator();
+ expect(calculator.addOne(2), 3);
+ expect(calculator.addOne(-7), -6);
+ expect(calculator.addOne(0), 1);
+ });
+}
diff --git a/cw_base/lib/base_wallet.dart b/cw_base/lib/base_wallet.dart
index d1287da..f3e5878 100644
--- a/cw_base/lib/base_wallet.dart
+++ b/cw_base/lib/base_wallet.dart
@@ -45,9 +45,13 @@ class BaseWallet extends EVMChainWallet {
for (final token in initialErc20Tokens) {
if (!evmChainErc20TokensBox.containsKey(token.contractAddress)) {
evmChainErc20TokensBox.put(token.contractAddress, token);
- } else { // update existing token
+ } else {
+ // update existing token
final existingToken = evmChainErc20TokensBox.get(token.contractAddress);
- evmChainErc20TokensBox.put(token.contractAddress, Erc20Token.copyWith(token, enabled: existingToken!.enabled));
+ evmChainErc20TokensBox.put(
+ token.contractAddress,
+ Erc20Token.copyWith(token, enabled: existingToken!.enabled),
+ );
}
}
}
@@ -58,7 +62,7 @@ class BaseWallet extends EVMChainWallet {
@override
Future<bool> checkIfScanProviderIsEnabled() async {
- return (await sharedPrefs.future).getBool("use_basescan") ?? true;
+ return (await sharedPrefs.future).getBool("use_basescan") ?? true;
}
@override
diff --git a/cw_core/lib/crypto_currency.dart b/cw_core/lib/crypto_currency.dart
index 5593822..4ae3aca 100644
--- a/cw_core/lib/crypto_currency.dart
+++ b/cw_core/lib/crypto_currency.dart
@@ -117,6 +117,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
CryptoCurrency.deps,
CryptoCurrency.baseEth,
CryptoCurrency.usde,
+ CryptoCurrency.arbEth,
];
static const havenCurrencies = [
@@ -245,6 +246,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
static const cbbtc = CryptoCurrency(title: 'CBBTC', tag: 'ETH', fullName: 'Coinbase Wrapped BTC', raw: 103, name: 'cbbtc', iconPath: 'assets/images/cbbtc_icon.png', decimals: 8);
static const baseEth = CryptoCurrency(title: 'ETH', tag: 'BASE', fullName: 'Ethereum', raw: 104, name: 'baseth', iconPath: 'assets/images/crypto/base_icon.webp', decimals: 18);
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);
+ static const arbEth = CryptoCurrency(title: 'ETH', tag: 'ARB', fullName: 'Arbitrum', raw: 106, name: 'arbeth', iconPath: 'assets/images/crypto/arbitrum.webp', decimals: 18);
static final Map<int, CryptoCurrency> _rawCurrencyMap =
[...all, ...havenCurrencies].fold<Map<int, CryptoCurrency>>(<int, CryptoCurrency>{}, (acc, item) {
diff --git a/cw_core/lib/currency_for_wallet_type.dart b/cw_core/lib/currency_for_wallet_type.dart
index 5bceeb1..fe6899c 100644
--- a/cw_core/lib/currency_for_wallet_type.dart
+++ b/cw_core/lib/currency_for_wallet_type.dart
@@ -18,6 +18,8 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type, {bool isTestnet = fal
return CryptoCurrency.eth;
case WalletType.base:
return CryptoCurrency.baseEth;
+ case WalletType.arbitrum:
+ return CryptoCurrency.arbEth;
case WalletType.bitcoinCash:
return CryptoCurrency.bch;
case WalletType.nano:
diff --git a/cw_core/lib/erc20_token.dart b/cw_core/lib/erc20_token.dart
index d090acd..cbc42ad 100644
--- a/cw_core/lib/erc20_token.dart
+++ b/cw_core/lib/erc20_token.dart
@@ -71,6 +71,7 @@ class Erc20Token extends CryptoCurrency with HiveObjectMixin {
static const ethereumBoxName = 'EthereumErc20Tokens';
static const polygonBoxName = 'PolygonErc20Tokens';
static const baseBoxName = 'BaseErc20Tokens';
+ static const arbitrumBoxName = 'ArbitrumErc20Tokens';
@override
bool operator ==(other) =>
diff --git a/cw_core/lib/node.dart b/cw_core/lib/node.dart
index 4908641..a8bd39a 100644
--- a/cw_core/lib/node.dart
+++ b/cw_core/lib/node.dart
@@ -107,6 +107,7 @@ class Node extends HiveObject with Keyable {
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.base:
+ case WalletType.arbitrum:
case WalletType.solana:
case WalletType.tron:
case WalletType.zano:
@@ -171,6 +172,7 @@ class Node extends HiveObject with Keyable {
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.base:
+ case WalletType.arbitrum:
case WalletType.solana:
case WalletType.tron:
case WalletType.dogecoin:
diff --git a/cw_core/lib/wallet_base.dart b/cw_core/lib/wallet_base.dart
index dfcc2c4..4aa0bd9 100644
--- a/cw_core/lib/wallet_base.dart
+++ b/cw_core/lib/wallet_base.dart
@@ -86,6 +86,8 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
int calculateEstimatedFee(TransactionPriority priority, int? amount);
+ Future<void> updateEstimatedFeesParams(TransactionPriority priority) async {}
+
// void fetchTransactionsAsync(
// void Function(TransactionType transaction) onTransactionLoaded,
// {void Function() onFinished});
diff --git a/cw_core/lib/wallet_type.dart b/cw_core/lib/wallet_type.dart
index 01ae372..70a61d1 100644
--- a/cw_core/lib/wallet_type.dart
+++ b/cw_core/lib/wallet_type.dart
@@ -20,6 +20,7 @@ const walletTypes = [
WalletType.decred,
WalletType.dogecoin,
WalletType.base,
+ WalletType.arbitrum,
];
@HiveType(typeId: WALLET_TYPE_TYPE_ID)
@@ -74,6 +75,9 @@ enum WalletType {
@HiveField(16)
base,
+
+ @HiveField(17)
+ arbitrum,
}
int serializeToInt(WalletType type) {
@@ -110,6 +114,8 @@ int serializeToInt(WalletType type) {
return 14;
case WalletType.base:
return 15;
+ case WalletType.arbitrum:
+ return 16;
case WalletType.none:
return -1;
}
@@ -149,6 +155,8 @@ WalletType deserializeFromInt(int raw) {
return WalletType.dogecoin;
case 15:
return WalletType.base;
+ case 16:
+ return WalletType.arbitrum;
default:
throw Exception('Unexpected token: $raw for WalletType deserializeFromInt');
}
@@ -188,6 +196,8 @@ String walletTypeToString(WalletType type) {
return 'Dogecoin';
case WalletType.base:
return 'Base';
+ case WalletType.arbitrum:
+ return 'Arbitrum';
case WalletType.none:
return '';
}
@@ -227,6 +237,8 @@ String walletTypeToDisplayName(WalletType type) {
return 'Dogecoin (DOGE)';
case WalletType.base:
return 'Base (BASE)';
+ case WalletType.arbitrum:
+ return 'Arbitrum (ARB)';
case WalletType.none:
return '';
}
@@ -266,6 +278,8 @@ WalletType? cryptoCurrencyToWalletType(CryptoCurrency type) {
return WalletType.dogecoin;
case CryptoCurrency.baseEth:
return WalletType.base;
+ case CryptoCurrency.arbEth:
+ return WalletType.arbitrum;
default:
return null;
}
diff --git a/cw_evm/lib/evm_chain_client.dart b/cw_evm/lib/evm_chain_client.dart
index fbc4bad..574c2b4 100644
--- a/cw_evm/lib/evm_chain_client.dart
+++ b/cw_evm/lib/evm_chain_client.dart
@@ -163,7 +163,7 @@ abstract class EVMChainClient {
required BigInt gasFee,
required int estimatedGasUnits,
required int maxFeePerGas,
- required EVMChainTransactionPriority priority,
+ required EVMChainTransactionPriority? priority,
required CryptoCurrency currency,
required String feeCurrency,
required int exponent,
@@ -172,15 +172,20 @@ abstract class EVMChainClient {
int? gasPrice,
}) async {
assert(currency == CryptoCurrency.eth ||
- currency == CryptoCurrency.maticpoly || currency == CryptoCurrency.baseEth ||
+ currency == CryptoCurrency.maticpoly ||
+ currency == CryptoCurrency.baseEth ||
+ currency == CryptoCurrency.arbEth ||
contractAddress != null);
- bool isNativeToken = currency == CryptoCurrency.eth || currency == CryptoCurrency.maticpoly || currency == CryptoCurrency.baseEth;
+ bool isNativeToken = currency == CryptoCurrency.eth ||
+ currency == CryptoCurrency.maticpoly ||
+ currency == CryptoCurrency.baseEth ||
+ currency == CryptoCurrency.arbEth;
final Transaction transaction = createTransaction(
from: privateKey.address,
to: EthereumAddress.fromHex(toAddress),
- maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip),
+ maxPriorityFeePerGas: priority != null ? EtherAmount.fromInt(EtherUnit.gwei, priority.tip) : null,
amount: isNativeToken ? EtherAmount.inWei(amount) : EtherAmount.zero(),
data: data != null ? hexToBytes(data) : null,
maxGas: estimatedGasUnits,
@@ -229,7 +234,7 @@ abstract class EVMChainClient {
required String feeCurrency,
required int estimatedGasUnits,
required int maxFeePerGas,
- required EVMChainTransactionPriority priority,
+ required EVMChainTransactionPriority? priority,
required int exponent,
required String contractAddress,
int? gasPrice,
@@ -237,7 +242,7 @@ abstract class EVMChainClient {
final Transaction transaction = createTransaction(
from: privateKey.address,
to: EthereumAddress.fromHex(contractAddress),
- maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip),
+ maxPriorityFeePerGas:priority != null ? EtherAmount.fromInt(EtherUnit.gwei, priority.tip) : null,
amount: EtherAmount.zero(),
maxGas: estimatedGasUnits,
maxFeePerGas: EtherAmount.fromInt(EtherUnit.wei, maxFeePerGas),
diff --git a/cw_evm/lib/evm_chain_wallet.dart b/cw_evm/lib/evm_chain_wallet.dart
index e1058da..75b08b0 100644
--- a/cw_evm/lib/evm_chain_wallet.dart
+++ b/cw_evm/lib/evm_chain_wallet.dart
@@ -115,11 +115,13 @@ abstract class EVMChainWalletBase
late final EVMChainClient _client;
- int gasPrice = 0;
- int? gasBaseFee = 0;
- int estimatedGasUnits = 0;
+ bool hasPriorityFee = true;
- Timer? _updateFeesTimer;
+ @observable
+ String? nativeTxEstimatedFee;
+
+ @observable
+ String? erc20TxEstimatedFee;
bool _isTransactionUpdating;
@@ -168,6 +170,16 @@ abstract class EVMChainWalletBase
EncryptionFileUtils encryptionFileUtils,
);
+ String _getUSDCContractAddress() {
+ return switch (_client.chainId) {
+ 1 => "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
+ 137 => "0x2791bca1f2de4661ed88a30c99a7a9449aa84174",
+ 8453 => "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
+ 42161 => "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
+ _ => throw Exception("Unsupported chain ID: ${_client.chainId}"),
+ };
+ }
+
@override
Future<bool> checkNodeHealth() async {
try {
@@ -175,26 +187,9 @@ abstract class EVMChainWalletBase
await _client.getBalance(_evmChainPrivateKey.address, throwOnError: true);
// Check USDC token balance
- String usdcContractAddress;
-
- switch (_client.chainId) {
- case 1:
- usdcContractAddress = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48";
- break;
- case 137:
- usdcContractAddress = "0x2791bca1f2de4661ed88a30c99a7a9449aa84174";
- break;
- case 8453:
- usdcContractAddress = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913";
- break;
- default:
- return true;
- }
+ String usdcContractAddress = _getUSDCContractAddress();
- await _client.fetchERC20Balances(
- _evmChainPrivateKey.address,
- usdcContractAddress,
- );
+ await _client.fetchERC20Balances(_evmChainPrivateKey.address, usdcContractAddress);
return true;
} catch (e) {
@@ -279,30 +274,77 @@ abstract class EVMChainWalletBase
}
@override
- int calculateEstimatedFee(TransactionPriority priority, int? amount) {
- {
- try {
- if (priority is EVMChainTransactionPriority) {
- final priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
-
- int maxFeePerGas;
- if (gasBaseFee != null) {
- // MaxFeePerGas with EIP1559;
- maxFeePerGas = gasBaseFee! + priorityFee;
- } else {
- // MaxFeePerGas with gasPrice;
- maxFeePerGas = gasPrice;
- printV('MaxFeePerGas with gasPrice: $maxFeePerGas');
- }
+ int calculateEstimatedFee(TransactionPriority priority, int? amount) => 0;
+
+ @override
+ Future<void> updateEstimatedFeesParams(TransactionPriority priority) async =>
+ await _getEstimatedFees(priority);
+
+ Future<void> _getEstimatedFees(TransactionPriority? priority) async {
+ final nativeFee = await _getNativeTxFee(priority);
+ nativeTxEstimatedFee = nativeFee.toString();
- final totalGasFee = estimatedGasUnits * maxFeePerGas;
- return totalGasFee;
+ final erc20Fee = await _getErc20TxFee(priority);
+ erc20TxEstimatedFee = erc20Fee.toString();
+
+ printV('Native Estimated Fee: $nativeTxEstimatedFee');
+ printV('ERC20 Estimated Fee: $erc20TxEstimatedFee');
+ }
+
+ Future<int> _getNativeTxFee(TransactionPriority? priority) async {
+ try {
+ int priorityFee = 0;
+ if (hasPriorityFee) {
+ if (priority is EVMChainTransactionPriority) {
+ priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
}
+ }
- return 0;
- } catch (e) {
- return 0;
+ final gasPrice = await _client.getGasUnitPrice();
+ final gasBaseFee = await _client.getGasBaseFee();
+
+ final gasUnits = await _client.getEstimatedGasUnitsForTransaction(
+ senderAddress: evmChainPrivateKey.address,
+ toAddress: evmChainPrivateKey.address,
+ gasPrice: EtherAmount.fromInt(EtherUnit.wei, gasPrice),
+ value: EtherAmount.fromBigInt(EtherUnit.wei, BigInt.from(0.0000000001)),
+ );
+
+ int maxFeePerGas = gasBaseFee != null ? (gasBaseFee + priorityFee) : (gasPrice + priorityFee);
+ final totalGasFee = gasUnits * maxFeePerGas;
+ return totalGasFee;
+ } catch (e) {
+ printV(e.toString());
+ return 0;
+ }
+ }
+
+ Future<int> _getErc20TxFee(TransactionPriority? priority) async {
+ try {
+ int priorityFee = 0;
+ if (hasPriorityFee) {
+ if (priority is EVMChainTransactionPriority) {
+ priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
+ }
}
+
+ final gasPrice = await _client.getGasUnitPrice();
+ final gasBaseFee = await _client.getGasBaseFee();
+
+ final gasUnits = await _client.getEstimatedGasUnitsForTransaction(
+ senderAddress: evmChainPrivateKey.address,
+ toAddress: evmChainPrivateKey.address,
+ contractAddress: _getUSDCContractAddress(), // Using USDC for default estimation
+ gasPrice: EtherAmount.fromInt(EtherUnit.wei, gasPrice),
+ value: EtherAmount.fromBigInt(EtherUnit.wei, BigInt.from(0.0000000001)),
+ );
+
+ int maxFeePerGas = gasBaseFee != null ? (gasBaseFee + priorityFee) : (gasPrice + priorityFee);
+ final totalGasFee = gasUnits * maxFeePerGas;
+ return totalGasFee;
+ } catch (e) {
+ printV(e.toString());
+ return 0;
}
}
@@ -316,76 +358,81 @@ abstract class EVMChainWalletBase
required amount,
required String? contractAddress,
required String receivingAddressHex,
- required TransactionPriority priority,
+ required TransactionPriority? priority,
Uint8List? data,
}) async {
try {
- if (priority is EVMChainTransactionPriority) {
- final priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
+ int priorityFee = 0;
+ if (hasPriorityFee && priority != null) {
+ if (priority is EVMChainTransactionPriority) {
+ priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
+ }
+ }
- int maxFeePerGas;
- int adjustedGasPrice;
+ final gasBaseFee = await _client.getGasBaseFee();
+ final gasPrice = await _client.getGasUnitPrice();
- bool isPolygon = _client.chainId == 137;
+ int maxFeePerGas;
+ int adjustedGasPrice;
- if (gasBaseFee != null) {
- // MaxFeePerGas with EIP1559;
- maxFeePerGas = gasBaseFee! + priorityFee;
- } else {
- // MaxFeePerGas with gasPrice
- maxFeePerGas = gasPrice + priorityFee;
- }
+ bool isPolygon = _client.chainId == 137;
- adjustedGasPrice = maxFeePerGas;
-
- // Polygon has a minimum priority fee of 25 gwei
- if (isPolygon) {
- int minPriorityFee = 25;
- int minPriorityFeeWei =
- EtherAmount.fromInt(EtherUnit.gwei, minPriorityFee).getInWei.toInt();
-
- // Calculate user selected priority-based additional fee on top of minimum
- int additionalPriorityFee = 0;
- switch (priority) {
- case EVMChainTransactionPriority.slow:
- // We use minimum priority fee only
- additionalPriorityFee = 0;
- break;
- case EVMChainTransactionPriority.medium:
- // We add 15 gwei on top of minimum
- additionalPriorityFee = EtherAmount.fromInt(EtherUnit.gwei, 15).getInWei.toInt();
- break;
- case EVMChainTransactionPriority.fast:
- // We add 35 gwei on top of minimum
- additionalPriorityFee = EtherAmount.fromInt(EtherUnit.gwei, 35).getInWei.toInt();
- break;
- }
+ if (gasBaseFee != null) {
+ // MaxFeePerGas with EIP1559;
+ maxFeePerGas = gasBaseFee + priorityFee;
+ } else {
+ // MaxFeePerGas with gasPrice
+ maxFeePerGas = gasPrice + priorityFee;
+ }
- int totalPriorityFee = minPriorityFeeWei + additionalPriorityFee;
- adjustedGasPrice = gasPrice + totalPriorityFee;
- maxFeePerGas = gasPrice + totalPriorityFee;
+ adjustedGasPrice = maxFeePerGas;
+
+ // Polygon has a minimum priority fee of 25 gwei
+ if (isPolygon) {
+ int minPriorityFee = 25;
+ int minPriorityFeeWei =
+ EtherAmount.fromInt(EtherUnit.gwei, minPriorityFee).getInWei.toInt();
+
+ // Calculate user selected priority-based additional fee on top of minimum
+ int additionalPriorityFee = 0;
+ switch (priority) {
+ case EVMChainTransactionPriority.slow:
+ // We use minimum priority fee only
+ additionalPriorityFee = 0;
+ break;
+ case EVMChainTransactionPriority.medium:
+ // We add 15 gwei on top of minimum
+ additionalPriorityFee = EtherAmount.fromInt(EtherUnit.gwei, 15).getInWei.toInt();
+ break;
+ case EVMChainTransactionPriority.fast:
+ // We add 35 gwei on top of minimum
+ additionalPriorityFee = EtherAmount.fromInt(EtherUnit.gwei, 35).getInWei.toInt();
+ break;
}
- final estimatedGas = await _client.getEstimatedGasUnitsForTransaction(
- contractAddress: contractAddress,
- senderAddress: _evmChainPrivateKey.address,
- value: EtherAmount.fromBigInt(EtherUnit.wei, amount!),
- gasPrice: EtherAmount.fromInt(EtherUnit.wei, adjustedGasPrice),
- toAddress: EthereumAddress.fromHex(receivingAddressHex),
- maxFeePerGas: EtherAmount.fromInt(EtherUnit.wei, maxFeePerGas),
- data: data,
- );
+ int totalPriorityFee = minPriorityFeeWei + additionalPriorityFee;
+ adjustedGasPrice = gasPrice + totalPriorityFee;
+ maxFeePerGas = gasPrice + totalPriorityFee;
+ }
- final totalGasFee = estimatedGas * adjustedGasPrice;
+ final estimatedGas = await _client.getEstimatedGasUnitsForTransaction(
+ contractAddress: contractAddress,
+ senderAddress: _evmChainPrivateKey.address,
+ value: EtherAmount.fromBigInt(EtherUnit.wei, amount!),
+ gasPrice: EtherAmount.fromInt(EtherUnit.wei, adjustedGasPrice),
+ toAddress: EthereumAddress.fromHex(receivingAddressHex),
+ maxFeePerGas: EtherAmount.fromInt(EtherUnit.wei, maxFeePerGas),
+ data: data,
+ );
- return GasParamsHandler(
- estimatedGasUnits: estimatedGas,
- estimatedGasFee: totalGasFee,
- maxFeePerGas: maxFeePerGas,
- gasPrice: adjustedGasPrice,
- );
- }
- return GasParamsHandler.zero();
+ final totalGasFee = estimatedGas * adjustedGasPrice;
+
+ return GasParamsHandler(
+ estimatedGasUnits: estimatedGas,
+ estimatedGasFee: totalGasFee,
+ maxFeePerGas: maxFeePerGas,
+ gasPrice: adjustedGasPrice,
+ );
} catch (e) {
return GasParamsHandler.zero();
}
@@ -400,7 +447,6 @@ abstract class EVMChainWalletBase
Future<void> close({bool shouldCleanup = false}) async {
_client.stop();
_transactionsUpdateTimer?.cancel();
- _updateFeesTimer?.cancel();
}
@action
@@ -440,30 +486,16 @@ abstract class EVMChainWalletBase
await _updateBalance();
await _updateTransactions();
- await _updateEstimatedGasFeeParams();
+ await _getEstimatedFees(
+ hasPriorityFee ? EVMChainTransactionPriority.medium : null,
+ ); // We're using medium priority for default estimation
- _updateFeesTimer ??= Timer.periodic(const Duration(seconds: 30), (timer) async {
- await _updateEstimatedGasFeeParams();
- });
syncStatus = SyncedSyncStatus();
} catch (e) {
syncStatus = FailedSyncStatus();
}
}
- Future<void> _updateEstimatedGasFeeParams() async {
- gasBaseFee = await _client.getGasBaseFee();
-
- gasPrice = await _client.getGasUnitPrice();
-
- estimatedGasUnits = await _client.getEstimatedGasUnitsForTransaction(
- senderAddress: _evmChainPrivateKey.address,
- toAddress: _evmChainPrivateKey.address,
- gasPrice: EtherAmount.fromInt(EtherUnit.wei, gasPrice),
- value: EtherAmount.fromBigInt(EtherUnit.wei, BigInt.one),
- );
- }
-
@override
Future<PendingTransaction> createTransaction(Object credentials) async {
final _credentials = credentials as EVMChainTransactionCredentials;
@@ -511,7 +543,7 @@ abstract class EVMChainWalletBase
final gasFeesModel = await calculateActualEstimatedFeeForCreateTransaction(
amount: totalAmount,
receivingAddressHex: toAddress,
- priority: _credentials.priority!,
+ priority: _credentials.priority,
contractAddress: contractAddress,
);
@@ -539,7 +571,7 @@ abstract class EVMChainWalletBase
final gasFeesModel = await calculateActualEstimatedFeeForCreateTransaction(
amount: totalAmount,
receivingAddressHex: toAddress,
- priority: _credentials.priority!,
+ priority: _credentials.priority,
contractAddress: contractAddress,
);
@@ -562,7 +594,7 @@ abstract class EVMChainWalletBase
final refinedGasFeesModel = await calculateActualEstimatedFeeForCreateTransaction(
amount: totalAmount,
receivingAddressHex: toAddress,
- priority: _credentials.priority!,
+ priority: _credentials.priority,
contractAddress: contractAddress,
);
@@ -577,7 +609,7 @@ abstract class EVMChainWalletBase
totalAmount = (currencyBalance.balance - estimatedFeesForTransaction);
}
- // check the fees on the base currency (Eth/Polygon)
+ // check the fees on the base currency
if (estimatedFeesForTransaction > balance[currency]!.balance) {
throw EVMChainTransactionFeesException(currency.title);
}
@@ -599,9 +631,9 @@ abstract class EVMChainWalletBase
toAddress: toAddress,
amount: totalAmount,
gasFee: estimatedFeesForTransaction,
- priority: _credentials.priority!,
+ priority: _credentials.priority,
currency: transactionCurrency,
- feeCurrency: switch (_client.chainId) { 1 => "ETH", 137 => "POL", 8453 => "ETH", _ => "ETH" },
+ feeCurrency: switch (_client.chainId) { 137 => "POL", _ => "ETH" },
maxFeePerGas: maxFeePerGasForTransaction,
exponent: exponent,
contractAddress:
@@ -614,12 +646,11 @@ abstract class EVMChainWalletBase
}
Future<PendingTransaction> createCallDataTransaction(
- String to,
- String dataHex,
- BigInt valueWei,
- EVMChainTransactionPriority priority,
- ) async {
-
+ String to,
+ String dataHex,
+ BigInt valueWei,
+ EVMChainTransactionPriority? priority,
+ ) async {
// Estimate gas with the SAME call (sender, to, value, data)
final gas = await calculateActualEstimatedFeeForCreateTransaction(
amount: valueWei, // native value (usually 0 for ERC20 transfer)
@@ -632,7 +663,8 @@ abstract class EVMChainWalletBase
final nativeCurrency = switch (_client.chainId) {
137 => CryptoCurrency.maticpoly,
8453 => CryptoCurrency.baseEth,
- _ => CryptoCurrency.eth,
+ 42161 => CryptoCurrency.arbEth,
+ _ => CryptoCurrency.eth,
};
// Fallback for nodes that fail estimate (non-zero)
@@ -656,9 +688,13 @@ abstract class EVMChainWalletBase
);
}
-
- Future<PendingTransaction> createApprovalTransaction(BigInt amount, String spender,
- CryptoCurrency token, EVMChainTransactionPriority priority, String feeCurrency) async {
+ Future<PendingTransaction> createApprovalTransaction(
+ BigInt amount,
+ String spender,
+ CryptoCurrency token,
+ EVMChainTransactionPriority? priority,
+ String feeCurrency,
+ ) async {
final CryptoCurrency transactionCurrency =
balance.keys.firstWhere((element) => element.title == token.title);
assert(transactionCurrency is Erc20Token);
@@ -840,10 +876,7 @@ abstract class EVMChainWalletBase
}
Future<bool> isApprovalRequired(
- String tokenContract,
- String spender,
- BigInt requiredAmount) async {
-
+ String tokenContract, String spender, BigInt requiredAmount) async {
const zero = '0x0000000000000000000000000000000000000000';
const evmNative = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE';
@@ -859,9 +892,7 @@ abstract class EVMChainWalletBase
chainId: _client.chainId,
);
- final allowance = await erc20.allowance(
- owner,
- EthereumAddress.fromHex(spender));
+ final allowance = await erc20.allowance(owner, EthereumAddress.fromHex(spender));
return allowance < requiredAmount;
} catch (e) {
@@ -898,7 +929,6 @@ abstract class EVMChainWalletBase
@override
Future<void> updateTransactionsHistory() async => await _updateTransactions();
-
List<Erc20Token> get erc20Currencies => evmChainErc20TokensBox.values.toList();
Future<void> addErc20Token(Erc20Token token) async {
@@ -998,6 +1028,8 @@ abstract class EVMChainWalletBase
/// PolygonScan for Polygon.
///
/// BaseScan for Base.
+ ///
+ /// ArbiScan for Arbitrum.
void updateScanProviderUsageState(bool isEnabled) {
if (isEnabled) {
_updateTransactions();
diff --git a/integration_test/components/common_test_flows.dart b/integration_test/components/common_test_flows.dart
index 53baf5d..333a659 100644
--- a/integration_test/components/common_test_flows.dart
+++ b/integration_test/components/common_test_flows.dart
@@ -356,6 +356,8 @@ class CommonTestFlows {
return secrets.solanaTestWalletSeeds;
case WalletType.base:
return secrets.baseTestWalletSeeds;
+ case WalletType.arbitrum:
+ return secrets.arbitrumTestWalletSeeds;
case WalletType.tron:
return secrets.tronTestWalletSeeds;
case WalletType.nano:
@@ -392,6 +394,8 @@ class CommonTestFlows {
return secrets.polygonTestWalletReceiveAddress;
case WalletType.base:
return secrets.baseTestWalletReceiveAddress;
+ case WalletType.arbitrum:
+ return secrets.arbitrumTestWalletReceiveAddress;
case WalletType.solana:
return secrets.solanaTestWalletReceiveAddress;
case WalletType.tron:
diff --git a/ios/Runner/InfoBase.plist b/ios/Runner/InfoBase.plist
index 3b11e21..59e3bb9 100644
--- a/ios/Runner/InfoBase.plist
+++ b/ios/Runner/InfoBase.plist
@@ -322,6 +322,26 @@
<string>base-wallet</string>
</array>
</dict>
+ <dict>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>CFBundleURLName</key>
+ <string>arbitrum</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>arbitrum</string>
+ </array>
+ </dict>
+ <dict>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ <key>CFBundleURLName</key>
+ <string>arbitrum-wallet</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>arbitrum-wallet</string>
+ </array>
+ </dict>
</array>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
diff --git a/lib/arbitrum/cw_arbitrum.dart b/lib/arbitrum/cw_arbitrum.dart
new file mode 100644
index 0000000..e4e5b6d
--- /dev/null
+++ b/lib/arbitrum/cw_arbitrum.dart
@@ -0,0 +1,244 @@
+part of 'arbitrum.dart';
+
+class CWArbitrum extends Arbitrum {
+ @override
+ List<String> getArbitrumWordList(String language) => EVMChainMnemonics.englishWordlist;
+
+ WalletService createArbitrumWalletService(bool isDirect) =>
+ ArbitrumWalletService(isDirect, client: ArbitrumClient());
+
+ @override
+ WalletCredentials createArbitrumNewWalletCredentials({
+ required String name,
+ String? mnemonic,
+ WalletInfo? walletInfo,
+ String? password,
+ String? passphrase,
+ }) =>
+ EVMChainNewWalletCredentials(
+ name: name,
+ walletInfo: walletInfo,
+ password: password,
+ mnemonic: mnemonic,
+ passphrase: passphrase,
+ );
+
+ @override
+ WalletCredentials createArbitrumRestoreWalletFromSeedCredentials({
+ required String name,
+ required String mnemonic,
+ required String password,
+ String? passphrase,
+ }) =>
+ EVMChainRestoreWalletFromSeedCredentials(
+ name: name,
+ password: password,
+ mnemonic: mnemonic,
+ passphrase: passphrase,
+ );
+
+ @override
+ WalletCredentials createArbitrumRestoreWalletFromPrivateKey({
+ required String name,
+ required String privateKey,
+ required String password,
+ }) =>
+ EVMChainRestoreWalletFromPrivateKey(name: name, password: password, privateKey: privateKey);
+
+ @override
+ WalletCredentials createArbitrumHardwareWalletCredentials({
+ required String name,
+ required HardwareAccountData hwAccountData,
+ WalletInfo? walletInfo,
+ }) =>
+ EVMChainRestoreWalletFromHardware(
+ name: name,
+ hwAccountData: hwAccountData,
+ walletInfo: walletInfo,
+ );
+
+ @override
+ String getAddress(WalletBase wallet) => (wallet as ArbitrumWallet).walletAddresses.address;
+
+ @override
+ String getPrivateKey(WalletBase wallet) {
+ final privateKeyHolder = (wallet as ArbitrumWallet).evmChainPrivateKey;
+ if (privateKeyHolder is EthPrivateKey) return bytesToHex(privateKeyHolder.privateKey);
+ return "";
+ }
+
+ @override
+ String getPublicKey(WalletBase wallet) {
+ final privateKeyInUnitInt = (wallet as ArbitrumWallet).evmChainPrivateKey;
+ return privateKeyInUnitInt.address.hex;
+ }
+
+ Object createArbitrumTransactionCredentials(
+ List<Output> outputs, {
+ required CryptoCurrency currency,
+ int? feeRate,
+ }) =>
+ EVMChainTransactionCredentials(
+ outputs
+ .map(
+ (out) => OutputInfo(
+ fiatAmount: out.fiatAmount,
+ cryptoAmount: out.cryptoAmount,
+ address: out.address,
+ note: out.note,
+ sendAll: out.sendAll,
+ extractedAddress: out.extractedAddress,
+ isParsedAddress: out.isParsedAddress,
+ formattedCryptoAmount: out.formattedCryptoAmount,
+ ),
+ )
+ .toList(),
+ priority: null,
+ currency: currency,
+ feeRate: feeRate,
+ );
+
+ Object createArbitrumTransactionCredentialsRaw(
+ List<OutputInfo> outputs, {
+ required CryptoCurrency currency,
+ required int feeRate,
+ }) =>
+ EVMChainTransactionCredentials(
+ outputs,
+ priority: null,
+ currency: currency,
+ feeRate: feeRate,
+ );
+
+ @override
+ int formatterArbitrumParseAmount(String amount) => EVMChainFormatter.parseEVMChainAmount(amount);
+
+ @override
+ double formatterArbitrumAmountToDouble({
+ TransactionInfo? transaction,
+ BigInt? amount,
+ int exponent = 18,
+ }) {
+ assert(transaction != null || amount != null);
+
+ if (transaction != null) {
+ transaction as EVMChainTransactionInfo;
+ return transaction.ethAmount / BigInt.from(10).pow(transaction.exponent);
+ } else {
+ return (amount!) / BigInt.from(10).pow(exponent);
+ }
+ }
+
+ @override
+ List<Erc20Token> getERC20Currencies(WalletBase wallet) =>
+ (wallet as ArbitrumWallet).erc20Currencies;
+
+ @override
+ Future<void> addErc20Token(WalletBase wallet, CryptoCurrency token) =>
+ (wallet as ArbitrumWallet).addErc20Token(token as Erc20Token);
+
+ @override
+ Future<void> deleteErc20Token(WalletBase wallet, CryptoCurrency token) =>
+ (wallet as ArbitrumWallet).deleteErc20Token(token as Erc20Token);
+
+ @override
+ Future<void> removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token) =>
+ (wallet as ArbitrumWallet).removeTokenTransactionsInHistory(token as Erc20Token);
+
+ @override
+ Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress) =>
+ (wallet as ArbitrumWallet).getErc20Token(contractAddress, 'arbitrum');
+
+ @override
+ CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) {
+ transaction as EVMChainTransactionInfo;
+ if (transaction.tokenSymbol == CryptoCurrency.arbEth.title ||
+ transaction.tokenSymbol == "ARB") {
+ return CryptoCurrency.arbEth;
+ }
+
+ wallet as ArbitrumWallet;
+
+ return wallet.erc20Currencies.firstWhere(
+ (element) =>
+ transaction.contractAddress?.toLowerCase() == element.contractAddress?.toLowerCase(),
+ );
+ }
+
+ @override
+ void updateArbitrumScanUsageState(WalletBase wallet, bool isEnabled) =>
+ (wallet as ArbitrumWallet).updateScanProviderUsageState(isEnabled);
+
+ @override
+ Web3Client? getWeb3Client(WalletBase wallet) => (wallet as ArbitrumWallet).getWeb3Client();
+
+ @override
+ String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress;
+
+ @override
+ Future<PendingTransaction> createTokenApproval(
+ WalletBase wallet,
+ BigInt amount,
+ String spender,
+ CryptoCurrency token,
+ ) =>
+ (wallet as EVMChainWallet).createApprovalTransaction(
+ amount,
+ spender,
+ token,
+ null,
+ "ARB",
+ );
+
+ @override
+ Future<void> setHardwareWalletService(WalletBase wallet, HardwareWalletService service) async {
+ if (service is EVMChainLedgerService) {
+ ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedgerConnection(
+ service.ledgerConnection, (await wallet.walletInfo.getDerivationInfo()).derivationPath);
+ } else if (service is EVMChainBitboxService) {
+ ((wallet as EVMChainWallet).evmChainPrivateKey as EvmBitboxCredentials)
+ .setBitbox(service.manager, (await wallet.walletInfo.getDerivationInfo()).derivationPath);
+ }
+ return Future.value();
+ }
+
+ @override
+ HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection) =>
+ EVMChainLedgerService(connection);
+
+ @override
+ HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager) =>
+ EVMChainBitboxService(manager, chainId: 42161);
+
+ @override
+ List<String> getDefaultTokenContractAddresses() => DefaultArbitrumErc20Tokens()
+ .initialArbitrumErc20Tokens
+ .map((e) => e.contractAddress)
+ .toList();
+
+ @override
+ bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress) {
+ final arbitrumWallet = wallet as ArbitrumWallet;
+ return arbitrumWallet.erc20Currencies.any(
+ (element) => element.contractAddress.toLowerCase() == contractAddress.toLowerCase(),
+ );
+ }
+
+ @override
+ Future<bool> isApprovalRequired(
+ WalletBase wallet, String tokenContract, String spender, BigInt requiredAmount) =>
+ (wallet as EVMChainWallet).isApprovalRequired(tokenContract, spender, requiredAmount);
+
+ @override
+ Future<PendingTransaction> createRawCallDataTransaction(
+ WalletBase wallet, String to, String dataHex, BigInt valueWei) =>
+ (wallet as EVMChainWallet).createCallDataTransaction(to, dataHex, valueWei, null);
+
+ @override
+ String? getArbitrumNativeEstimatedFee(WalletBase wallet) =>
+ (wallet as EVMChainWallet).nativeTxEstimatedFee;
+
+ @override
+ String? getArbitrumERC20EstimatedFee(WalletBase wallet) =>
+ (wallet as EVMChainWallet).erc20TxEstimatedFee;
+}
diff --git a/lib/base/cw_base.dart b/lib/base/cw_base.dart
index 42a9ad0..5b17abd 100644
--- a/lib/base/cw_base.dart
+++ b/lib/base/cw_base.dart
@@ -190,7 +190,8 @@ class CWBase extends Base {
String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress;
@override
- Future<bool> isApprovalRequired(WalletBase wallet, String tokenContract,String spender, BigInt requiredAmount) =>
+ Future<bool> isApprovalRequired(
+ WalletBase wallet, String tokenContract, String spender, BigInt requiredAmount) =>
(wallet as EVMChainWallet).isApprovalRequired(tokenContract, spender, requiredAmount);
@override
@@ -210,10 +211,10 @@ class CWBase extends Base {
);
@override
- Future<PendingTransaction> createRawCallDataTransaction(WalletBase wallet, String to, String dataHex, BigInt valueWei,
- TransactionPriority priority) =>
- (wallet as EVMChainWallet).createCallDataTransaction(to, dataHex, valueWei, priority as EVMChainTransactionPriority);
-
+ Future<PendingTransaction> createRawCallDataTransaction(WalletBase wallet, String to,
+ String dataHex, BigInt valueWei, TransactionPriority priority) =>
+ (wallet as EVMChainWallet).createCallDataTransaction(
+ to, dataHex, valueWei, priority as EVMChainTransactionPriority);
@override
Future<void> setHardwareWalletService(WalletBase wallet, HardwareWalletService service) async {
@@ -246,4 +247,12 @@ class CWBase extends Base {
(element) => element.contractAddress.toLowerCase() == contractAddress.toLowerCase(),
);
}
+
+ @override
+ String? getBaseNativeEstimatedFee(WalletBase wallet) =>
+ (wallet as EVMChainWallet).nativeTxEstimatedFee;
+
+ @override
+ String? getBaseERC20EstimatedFee(WalletBase wallet) =>
+ (wallet as EVMChainWallet).erc20TxEstimatedFee;
}
diff --git a/lib/buy/dfx/dfx_buy_provider.dart b/lib/buy/dfx/dfx_buy_provider.dart
index c590d82..5f4c668 100644
--- a/lib/buy/dfx/dfx_buy_provider.dart
+++ b/lib/buy/dfx/dfx_buy_provider.dart
@@ -136,6 +136,7 @@ class DFXBuyProvider extends BuyProvider {
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.base:
+ case WalletType.arbitrum:
case WalletType.solana:
case WalletType.tron:
return wallet.signMessage(message);
diff --git a/lib/buy/kryptonim/kryptonim.dart b/lib/buy/kryptonim/kryptonim.dart
index cbe6846..4bedd94 100644
--- a/lib/buy/kryptonim/kryptonim.dart
+++ b/lib/buy/kryptonim/kryptonim.dart
@@ -219,6 +219,7 @@ class KryptonimBuyProvider extends BuyProvider {
String? blockchain = switch (cur.tag) {
'ETH' => 'Ethereum',
'BASE' => 'Base',
+ 'ARB' => 'Arbitrum',
'POL' => 'Polygon',
'AVAXC' => 'Avalanche',
'SOL' => 'Solana',
@@ -231,6 +232,7 @@ class KryptonimBuyProvider extends BuyProvider {
CryptoCurrency.ltc => 'Litecoin',
CryptoCurrency.eth => 'Ethereum',
CryptoCurrency.baseEth => 'Base',
+ CryptoCurrency.arbEth => 'Arbitrum',
CryptoCurrency.maticpoly => 'Matic',
_ => null,
};
diff --git a/lib/buy/onramper/onramper_buy_provider.dart b/lib/buy/onramper/onramper_buy_provider.dart
index 615d510..8d809a7 100644
--- a/lib/buy/onramper/onramper_buy_provider.dart
+++ b/lib/buy/onramper/onramper_buy_provider.dart
@@ -324,6 +324,8 @@ class OnRamperBuyProvider extends BuyProvider {
switch (tag) {
case 'POL':
return 'POLYGON';
+ case 'ARB':
+ return 'ARBITRUM';
case 'ETH':
return 'ETHEREUM';
case 'TRX':
diff --git a/lib/buy/robinhood/robinhood_buy_provider.dart b/lib/buy/robinhood/robinhood_buy_provider.dart
index ee529d3..57ce1ad 100644
--- a/lib/buy/robinhood/robinhood_buy_provider.dart
+++ b/lib/buy/robinhood/robinhood_buy_provider.dart
@@ -106,6 +106,7 @@ class RobinhoodBuyProvider extends BuyProvider {
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.base:
+ case WalletType.arbitrum:
case WalletType.solana:
case WalletType.tron:
case WalletType.dogecoin:
diff --git a/lib/core/open_crypto_pay/open_cryptopay_service.dart b/lib/core/open_crypto_pay/open_cryptopay_service.dart
index d913a61..cc94740 100644
--- a/lib/core/open_crypto_pay/open_cryptopay_service.dart
+++ b/lib/core/open_crypto_pay/open_cryptopay_service.dart
@@ -162,6 +162,8 @@ class OpenCryptoPayService {
return "Ethereum";
case "POL":
return "Polygon";
+ case "ARB":
+ return "Arbitrum";
case "TRX":
return "Tron";
case "SOL":
diff --git a/lib/core/payment_uris.dart b/lib/core/payment_uris.dart
index be4e790..38f4f6d 100644
--- a/lib/core/payment_uris.dart
+++ b/lib/core/payment_uris.dart
@@ -101,6 +101,21 @@ class BaseURI extends PaymentURI {
}
}
+class ArbitrumURI extends PaymentURI {
+ ArbitrumURI({required super.amount, required super.address});
+
+ @override
+ String toString() {
+ var base = 'arbitrum:$address';
+
+ if (amount.isNotEmpty) {
+ base += '?amount=${amount.replaceAll(',', '.')}';
+ }
+
+ return base;
+ }
+}
+
class BitcoinCashURI extends PaymentURI {
BitcoinCashURI({required super.amount, required super.address});
diff --git a/lib/core/seed_validator.dart b/lib/core/seed_validator.dart
index 2643fd0..e7facde 100644
--- a/lib/core/seed_validator.dart
+++ b/lib/core/seed_validator.dart
@@ -1,3 +1,4 @@
+import 'package:cake_wallet/arbitrum/arbitrum.dart';
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/validator.dart';
@@ -55,6 +56,8 @@ class SeedValidator extends Validator<MnemonicItem> {
return decred!.getDecredWordList();
case WalletType.base:
return base!.getBaseWordList(language);
+ case WalletType.arbitrum:
+ return arbitrum!.getArbitrumWordList(language);
case WalletType.none:
case WalletType.haven:
return [];
diff --git a/lib/core/wallet_creation_service.dart b/lib/core/wallet_creation_service.dart
index dec1c80..7f09e7b 100644
--- a/lib/core/wallet_creation_service.dart
+++ b/lib/core/wallet_creation_service.dart
@@ -79,6 +79,7 @@ class WalletCreationService {
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.base:
+ case WalletType.arbitrum:
case WalletType.solana:
case WalletType.tron:
case WalletType.dogecoin:
diff --git a/lib/di.dart b/lib/di.dart
index 1d4a0ac..fde64bd 100644
--- a/lib/di.dart
+++ b/lib/di.dart
@@ -4,6 +4,7 @@ import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cake_wallet/anonpay/anonpay_api.dart';
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
import 'package:cake_wallet/anypay/anypay_api.dart';
+import 'package:cake_wallet/arbitrum/arbitrum.dart';
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
@@ -1226,6 +1227,8 @@ Future<void> setup({
return decred!.createDecredWalletService(_unspentCoinsInfoSource);
case WalletType.base:
return base!.createBaseWalletService(SettingsStoreBase.walletPasswordDirectInput);
+ case WalletType.arbitrum:
+ return arbitrum!.createArbitrumWalletService(SettingsStoreBase.walletPasswordDirectInput);
case WalletType.haven:
return HavenWalletService();
case WalletType.none:
diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart
index 8367147..9f2ac0b 100644
--- a/lib/entities/default_settings_migration.dart
+++ b/lib/entities/default_settings_migration.dart
@@ -50,6 +50,7 @@ const moneroWorldNodeUri = '.moneroworld.com';
const decredDefaultUri = "default-spv-nodes";
const dogecoinDefaultNodeUri = 'dogecoin.stackwallet.com:50022';
const baseDefaultNodeUri = 'base.nownodes.io';
+const arbitrumDefaultNodeUri = 'arbitrum.nownodes.io';
Future<void> defaultSettingsMigration(
{required int version,
@@ -545,6 +546,16 @@ Future<void> defaultSettingsMigration(
currentNodePreferenceKey: PreferencesKey.currentBaseNodeIdKey,
);
break;
+ case 53:
+ await addWalletNodeList(nodes: nodes, type: WalletType.arbitrum);
+ await _changeDefaultNode(
+ nodes: nodes,
+ sharedPreferences: sharedPreferences,
+ type: WalletType.arbitrum,
+ currentNodePreferenceKey: PreferencesKey.currentArbitrumNodeIdKey,
+ );
+ break;
+
default:
break;
}
@@ -655,6 +666,8 @@ String _getDefaultNodeUri(WalletType type) {
return dogecoinDefaultNodeUri;
case WalletType.base:
return baseDefaultNodeUri;
+ case WalletType.arbitrum:
+ return arbitrumDefaultNodeUri;
case WalletType.banano:
case WalletType.none:
return '';
@@ -1057,6 +1070,7 @@ Future<void> checkCurrentNodes(
final currentEthereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
final currentPolygonNodeId = sharedPreferences.getInt(PreferencesKey.currentPolygonNodeIdKey);
final currentBaseNodeId = sharedPreferences.getInt(PreferencesKey.currentBaseNodeIdKey);
+ final currentArbitrumNodeId = sharedPreferences.getInt(PreferencesKey.currentArbitrumNodeIdKey);
final currentNanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
final currentNanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey);
final currentDecredNodeId = sharedPreferences.getInt(PreferencesKey.currentDecredNodeIdKey);
@@ -1082,6 +1096,8 @@ Future<void> checkCurrentNodes(
nodeSource.values.firstWhereOrNull((node) => node.key == currentPolygonNodeId);
final currentBaseNodeServer =
nodeSource.values.firstWhereOrNull((node) => node.key == currentBaseNodeId);
+ final currentArbitrumNodeServer =
+ nodeSource.values.firstWhereOrNull((node) => node.key == currentArbitrumNodeId);
final currentNanoNodeServer =
nodeSource.values.firstWhereOrNull((node) => node.key == currentNanoNodeId);
final currentDecredNodeServer =
@@ -1179,6 +1195,12 @@ Future<void> checkCurrentNodes(
await sharedPreferences.setInt(PreferencesKey.currentBaseNodeIdKey, node.key as int);
}
+ if (currentArbitrumNodeServer == null) {
+ final node = Node(uri: arbitrumDefaultNodeUri, type: WalletType.arbitrum);
+ await nodeSource.add(node);
+ await sharedPreferences.setInt(PreferencesKey.currentArbitrumNodeIdKey, node.key as int);
+ }
+
if (currentSolanaNodeServer == null) {
final node = Node(uri: solanaDefaultNodeUri, type: WalletType.solana);
await nodeSource.add(node);
diff --git a/lib/entities/ens_record.dart b/lib/entities/ens_record.dart
index b49afc0..35ed469 100644
--- a/lib/entities/ens_record.dart
+++ b/lib/entities/ens_record.dart
@@ -1,3 +1,4 @@
+import 'package:cake_wallet/arbitrum/arbitrum.dart';
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/polygon/polygon.dart';
@@ -26,6 +27,10 @@ class EnsRecord {
_client = base!.getWeb3Client(wallet);
}
+ if (wallet != null && wallet.type == WalletType.arbitrum) {
+ _client = arbitrum!.getWeb3Client(wallet);
+ }
+
if (_client == null) {
late final client = ProxyWrapper().getHttpIOClient();
@@ -48,6 +53,7 @@ class EnsRecord {
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.base:
+ case WalletType.arbitrum:
default:
return (await ens.withName(name).getAddress()).hex;
}
diff --git a/lib/entities/node_list.dart b/lib/entities/node_list.dart
index 90bb9dd..4f856db 100644
--- a/lib/entities/node_list.dart
+++ b/lib/entities/node_list.dart
@@ -52,6 +52,9 @@ Future<List<Node>> loadDefaultNodes(WalletType type) async {
case WalletType.base:
path = 'assets/base_node_list.yml';
break;
+ case WalletType.arbitrum:
+ path = 'assets/arbitrum_node_list.yml';
+ break;
case WalletType.banano:
case WalletType.none:
path = '';
@@ -104,6 +107,7 @@ Future<void> resetToDefault(Box<Node> nodeSource) async {
final zanoNodes = await loadDefaultNodes(WalletType.zano);
final dogecoinElectrumServerList = await loadDefaultNodes(WalletType.dogecoin);
final baseNodes = await loadDefaultNodes(WalletType.base);
+ final arbitrumNodes = await loadDefaultNodes(WalletType.arbitrum);
final nodes = moneroNodes +
bitcoinElectrumServerList +
@@ -118,7 +122,8 @@ Future<void> resetToDefault(Box<Node> nodeSource) async {
zanoNodes +
decredNodes +
dogecoinElectrumServerList +
- baseNodes;
+ baseNodes +
+ arbitrumNodes;
await nodeSource.clear();
await nodeSource.addAll(nodes);
diff --git a/lib/entities/parse_address_from_domain.dart b/lib/entities/parse_address_from_domain.dart
index cbcf62d..4428108 100644
--- a/lib/entities/parse_address_from_domain.dart
+++ b/lib/entities/parse_address_from_domain.dart
@@ -115,6 +115,7 @@ class AddressResolver {
"pog",
"polygon",
"base",
+ "arbitrum"
"press",
"privacy",
"pro",
diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart
index eb678a0..a9acc2a 100644
--- a/lib/entities/preferences_key.dart
+++ b/lib/entities/preferences_key.dart
@@ -9,6 +9,7 @@ class PreferencesKey {
static const currentEthereumNodeIdKey = 'current_node_id_eth';
static const currentPolygonNodeIdKey = 'current_node_id_matic';
static const currentBaseNodeIdKey = 'current_node_id_base';
+ static const currentArbitrumNodeIdKey = 'current_node_id_arbitrum';
static const currentNanoNodeIdKey = 'current_node_id_nano';
static const currentNanoPowNodeIdKey = 'current_node_id_nano_pow';
static const currentDecredNodeIdKey = 'current_node_id_decred';
@@ -80,6 +81,7 @@ class PreferencesKey {
static const useEtherscan = 'use_etherscan';
static const usePolygonScan = 'use_polygonscan';
static const useBaseScan = 'use_base_scan';
+ static const useArbiScan = 'use_arbitrum_scan';
static const useTronGrid = 'use_trongrid';
static const useMempoolFeeAPI = 'use_mempool_fee_api';
static const defaultNanoRep = 'default_nano_representative';
diff --git a/lib/entities/priority_for_wallet_type.dart b/lib/entities/priority_for_wallet_type.dart
index 37ad2ab..4538da8 100644
--- a/lib/entities/priority_for_wallet_type.dart
+++ b/lib/entities/priority_for_wallet_type.dart
@@ -1,3 +1,4 @@
+import 'package:cake_wallet/arbitrum/arbitrum.dart';
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
@@ -31,7 +32,7 @@ List<TransactionPriority> priorityForWalletType(WalletType type) {
return polygon!.getTransactionPriorities();
case WalletType.base:
return base!.getTransactionPriorities();
- // no such thing for nano/banano/solana/tron:
+ case WalletType.arbitrum:
case WalletType.nano:
case WalletType.banano:
case WalletType.solana:
diff --git a/lib/ethereum/cw_ethereum.dart b/lib/ethereum/cw_ethereum.dart
index 4433a54..26bb8cd 100644
--- a/lib/ethereum/cw_ethereum.dart
+++ b/lib/ethereum/cw_ethereum.dart
@@ -189,8 +189,8 @@ class CWEthereum extends Ethereum {
((wallet as EVMChainWallet).evmChainPrivateKey as EvmBitboxCredentials)
.setBitbox(service.manager, (await wallet.walletInfo.getDerivationInfo()).derivationPath);
} else if (service is EVMChainTrezorService) {
- ((wallet as EVMChainWallet).evmChainPrivateKey as EvmTrezorCredentials)
- .setTrezorConnect(service.connect, (await wallet.walletInfo.getDerivationInfo()).derivationPath);
+ ((wallet as EVMChainWallet).evmChainPrivateKey as EvmTrezorCredentials).setTrezorConnect(
+ service.connect, (await wallet.walletInfo.getDerivationInfo()).derivationPath);
}
}
@@ -211,15 +211,16 @@ class CWEthereum extends Ethereum {
return DefaultEthereumErc20Tokens().initialErc20Tokens.map((e) => e.contractAddress).toList();
}
-
@override
bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress) {
final ethereumWallet = wallet as EthereumWallet;
- return ethereumWallet.erc20Currencies.any((element) => element.contractAddress.toLowerCase() == contractAddress.toLowerCase());
+ return ethereumWallet.erc20Currencies
+ .any((element) => element.contractAddress.toLowerCase() == contractAddress.toLowerCase());
}
@override
- Future<bool> isApprovalRequired(WalletBase wallet, String tokenContract,String spender, BigInt requiredAmount) =>
+ Future<bool> isApprovalRequired(
+ WalletBase wallet, String tokenContract, String spender, BigInt requiredAmount) =>
(wallet as EVMChainWallet).isApprovalRequired(tokenContract, spender, requiredAmount);
@override
@@ -229,9 +230,10 @@ class CWEthereum extends Ethereum {
amount, spender, token, priority as EVMChainTransactionPriority, "ETH");
@override
- Future<PendingTransaction> createRawCallDataTransaction(WalletBase wallet, String to, String dataHex, BigInt valueWei,
- TransactionPriority priority) =>
- (wallet as EVMChainWallet).createCallDataTransaction(to, dataHex, valueWei, priority as EVMChainTransactionPriority);
+ Future<PendingTransaction> createRawCallDataTransaction(WalletBase wallet, String to,
+ String dataHex, BigInt valueWei, TransactionPriority priority) =>
+ (wallet as EVMChainWallet).createCallDataTransaction(
+ to, dataHex, valueWei, priority as EVMChainTransactionPriority);
// Integrations
@override
@@ -270,4 +272,12 @@ class CWEthereum extends Ethereum {
@override
Future<PendingTransaction> enableDEuroSaving(WalletBase wallet, TransactionPriority priority) =>
DEuro(wallet as EthereumWallet).enableSavings(priority as EVMChainTransactionPriority);
+
+ @override
+ String? getEthereumNativeEstimatedFee(WalletBase wallet) =>
+ (wallet as EVMChainWallet).nativeTxEstimatedFee;
+
+ @override
+ String? getEthereumERC20EstimatedFee(WalletBase wallet) =>
+ (wallet as EVMChainWallet).erc20TxEstimatedFee;
}
diff --git a/lib/exchange/provider/sideshift_exchange_provider.dart b/lib/exchange/provider/sideshift_exchange_provider.dart
index 763064e..629597b 100644
--- a/lib/exchange/provider/sideshift_exchange_provider.dart
+++ b/lib/exchange/provider/sideshift_exchange_provider.dart
@@ -466,6 +466,8 @@ class SideShiftExchangeProvider extends ExchangeProvider {
return 'lightning';
case 'POL':
return 'polygon';
+ case 'ARB':
+ return 'arbitrum';
case 'ZEC':
return 'zcash';
case 'AVAXC':
@@ -481,6 +483,7 @@ class SideShiftExchangeProvider extends ExchangeProvider {
'tron' => 'TRX',
'lightning' => 'LN',
'polygon' => 'POL',
+ 'arbitrum' => 'ARB',
'zcash' => 'ZEC',
'avax' => 'AVAXC',
_ => network,
diff --git a/lib/main.dart b/lib/main.dart
index a5b9be8..fe6e947 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -260,7 +260,7 @@ Future<void> initializeAppConfigs({bool loadWallet = true}) async {
payjoinSessionSource: payjoinSessionSource,
anonpayInvoiceInfo: anonpayInvoiceInfo,
havenSeedStore: havenSeedStore,
- initialMigrationVersion: 52,
+ initialMigrationVersion: 53,
);
}
diff --git a/lib/polygon/cw_polygon.dart b/lib/polygon/cw_polygon.dart
index 8c8c2a7..94396f8 100644
--- a/lib/polygon/cw_polygon.dart
+++ b/lib/polygon/cw_polygon.dart
@@ -14,13 +14,14 @@ class CWPolygon extends Polygon {
WalletInfo? walletInfo,
String? password,
String? passphrase,
- }) => EVMChainNewWalletCredentials(
- name: name,
- walletInfo: walletInfo,
- password: password,
- mnemonic: mnemonic,
- passphrase: passphrase,
- );
+ }) =>
+ EVMChainNewWalletCredentials(
+ name: name,
+ walletInfo: walletInfo,
+ password: password,
+ mnemonic: mnemonic,
+ passphrase: passphrase,
+ );
@override
WalletCredentials createPolygonRestoreWalletFromSeedCredentials({
@@ -28,30 +29,33 @@ class CWPolygon extends Polygon {
required String mnemonic,
required String password,
String? passphrase,
- }) => EVMChainRestoreWalletFromSeedCredentials(
- name: name,
- password: password,
- mnemonic: mnemonic,
- passphrase: passphrase,
- );
+ }) =>
+ EVMChainRestoreWalletFromSeedCredentials(
+ name: name,
+ password: password,
+ mnemonic: mnemonic,
+ passphrase: passphrase,
+ );
@override
WalletCredentials createPolygonRestoreWalletFromPrivateKey({
required String name,
required String privateKey,
required String password,
- }) => EVMChainRestoreWalletFromPrivateKey(name: name, password: password, privateKey: privateKey);
+ }) =>
+ EVMChainRestoreWalletFromPrivateKey(name: name, password: password, privateKey: privateKey);
@override
WalletCredentials createPolygonHardwareWalletCredentials({
required String name,
required HardwareAccountData hwAccountData,
WalletInfo? walletInfo,
- }) => EVMChainRestoreWalletFromHardware(
- name: name,
- hwAccountData: hwAccountData,
- walletInfo: walletInfo,
- );
+ }) =>
+ EVMChainRestoreWalletFromHardware(
+ name: name,
+ hwAccountData: hwAccountData,
+ walletInfo: walletInfo,
+ );
@override
String getAddress(WalletBase wallet) => (wallet as PolygonWallet).walletAddresses.address;
@@ -87,37 +91,39 @@ class CWPolygon extends Polygon {
required TransactionPriority priority,
required CryptoCurrency currency,
int? feeRate,
- }) => EVMChainTransactionCredentials(
- outputs
- .map(
- (out) => OutputInfo(
- fiatAmount: out.fiatAmount,
- cryptoAmount: out.cryptoAmount,
- address: out.address,
- note: out.note,
- sendAll: out.sendAll,
- extractedAddress: out.extractedAddress,
- isParsedAddress: out.isParsedAddress,
- formattedCryptoAmount: out.formattedCryptoAmount,
- ),
- )
- .toList(),
- priority: priority as EVMChainTransactionPriority,
- currency: currency,
- feeRate: feeRate,
- );
+ }) =>
+ EVMChainTransactionCredentials(
+ outputs
+ .map(
+ (out) => OutputInfo(
+ fiatAmount: out.fiatAmount,
+ cryptoAmount: out.cryptoAmount,
+ address: out.address,
+ note: out.note,
+ sendAll: out.sendAll,
+ extractedAddress: out.extractedAddress,
+ isParsedAddress: out.isParsedAddress,
+ formattedCryptoAmount: out.formattedCryptoAmount,
+ ),
+ )
+ .toList(),
+ priority: priority as EVMChainTransactionPriority,
+ currency: currency,
+ feeRate: feeRate,
+ );
Object createPolygonTransactionCredentialsRaw(
List<OutputInfo> outputs, {
TransactionPriority? priority,
required CryptoCurrency currency,
required int feeRate,
- }) => EVMChainTransactionCredentials(
- outputs,
- priority: priority as EVMChainTransactionPriority?,
- currency: currency,
- feeRate: feeRate,
- );
+ }) =>
+ EVMChainTransactionCredentials(
+ outputs,
+ priority: priority as EVMChainTransactionPriority?,
+ currency: currency,
+ feeRate: feeRate,
+ );
@override
int formatterPolygonParseAmount(String amount) => EVMChainFormatter.parseEVMChainAmount(amount);
@@ -169,7 +175,8 @@ class CWPolygon extends Polygon {
wallet as PolygonWallet;
return wallet.erc20Currencies.firstWhere(
- (element) => transaction.contractAddress?.toLowerCase() == element.contractAddress?.toLowerCase(),
+ (element) =>
+ transaction.contractAddress?.toLowerCase() == element.contractAddress?.toLowerCase(),
);
}
@@ -183,12 +190,14 @@ class CWPolygon extends Polygon {
@override
String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress;
- Future<bool> isApprovalRequired(WalletBase wallet, String tokenContract,String spender, BigInt requiredAmount) =>
+ Future<bool> isApprovalRequired(
+ WalletBase wallet, String tokenContract, String spender, BigInt requiredAmount) =>
(wallet as PolygonWallet).isApprovalRequired(tokenContract, spender, requiredAmount);
- Future<PendingTransaction> createRawCallDataTransaction(WalletBase wallet, String to, String dataHex, BigInt valueWei,
- TransactionPriority priority) =>
- (wallet as EVMChainWallet).createCallDataTransaction(to, dataHex, valueWei, priority as EVMChainTransactionPriority);
+ Future<PendingTransaction> createRawCallDataTransaction(WalletBase wallet, String to,
+ String dataHex, BigInt valueWei, TransactionPriority priority) =>
+ (wallet as EVMChainWallet).createCallDataTransaction(
+ to, dataHex, valueWei, priority as EVMChainTransactionPriority);
@override
Future<PendingTransaction> createTokenApproval(
@@ -197,13 +206,14 @@ class CWPolygon extends Polygon {
String spender,
CryptoCurrency token,
TransactionPriority priority,
- ) => (wallet as EVMChainWallet).createApprovalTransaction(
- amount,
- spender,
- token,
- priority as EVMChainTransactionPriority,
- "POL",
- );
+ ) =>
+ (wallet as EVMChainWallet).createApprovalTransaction(
+ amount,
+ spender,
+ token,
+ priority as EVMChainTransactionPriority,
+ "POL",
+ );
@override
Future<void> setHardwareWalletService(WalletBase wallet, HardwareWalletService service) async {
@@ -214,8 +224,8 @@ class CWPolygon extends Polygon {
((wallet as EVMChainWallet).evmChainPrivateKey as EvmBitboxCredentials)
.setBitbox(service.manager, (await wallet.walletInfo.getDerivationInfo()).derivationPath);
} else if (service is EVMChainTrezorService) {
- ((wallet as EVMChainWallet).evmChainPrivateKey as EvmTrezorCredentials)
- .setTrezorConnect(service.connect, (await wallet.walletInfo.getDerivationInfo()).derivationPath);
+ ((wallet as EVMChainWallet).evmChainPrivateKey as EvmTrezorCredentials).setTrezorConnect(
+ service.connect, (await wallet.walletInfo.getDerivationInfo()).derivationPath);
}
}
@@ -242,4 +252,12 @@ class CWPolygon extends Polygon {
(element) => element.contractAddress.toLowerCase() == contractAddress.toLowerCase(),
);
}
+
+ @override
+ String? getPolygonNativeEstimatedFee(WalletBase wallet) =>
+ (wallet as EVMChainWallet).nativeTxEstimatedFee;
+
+ @override
+ String? getPolygonERC20EstimatedFee(WalletBase wallet) =>
+ (wallet as EVMChainWallet).erc20TxEstimatedFee;
}
diff --git a/lib/reactions/fiat_rate_update.dart b/lib/reactions/fiat_rate_update.dart
index abd8b09..a821254 100644
--- a/lib/reactions/fiat_rate_update.dart
+++ b/lib/reactions/fiat_rate_update.dart
@@ -1,4 +1,5 @@
import 'dart:async';
+import 'package:cake_wallet/arbitrum/arbitrum.dart';
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/core/fiat_conversion_service.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart';
@@ -49,6 +50,10 @@ Future<void> startFiatRateUpdate(
currencies =
base!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
}
+ if (appStore.wallet!.type == WalletType.arbitrum) {
+ currencies =
+ arbitrum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
+ }
if (appStore.wallet!.type == WalletType.solana) {
currencies =
diff --git a/lib/reactions/on_current_wallet_change.dart b/lib/reactions/on_current_wallet_change.dart
index 53da945..d6c872c 100644
--- a/lib/reactions/on_current_wallet_change.dart
+++ b/lib/reactions/on_current_wallet_change.dart
@@ -1,3 +1,4 @@
+import 'package:cake_wallet/arbitrum/arbitrum.dart';
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/di.dart';
@@ -133,6 +134,10 @@ void startCurrentWalletChangeReaction(
currencies =
base!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
}
+ if (wallet.type == WalletType.arbitrum) {
+ currencies =
+ arbitrum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
+ }
if (wallet.type == WalletType.solana) {
currencies =
solana!.getSPLTokenCurrencies(appStore.wallet!).where((element) => element.enabled);
diff --git a/lib/reactions/wallet_connect.dart b/lib/reactions/wallet_connect.dart
index 7e969f3..0357b12 100644
--- a/lib/reactions/wallet_connect.dart
+++ b/lib/reactions/wallet_connect.dart
@@ -9,6 +9,7 @@ bool isEVMCompatibleChain(WalletType walletType) {
case WalletType.polygon:
case WalletType.ethereum:
case WalletType.base:
+ case WalletType.arbitrum:
return true;
default:
return false;
@@ -21,6 +22,7 @@ bool isNFTACtivatedChain(WalletType walletType) {
case WalletType.ethereum:
case WalletType.base:
case WalletType.solana:
+ case WalletType.arbitrum:
return true;
default:
return false;
@@ -33,6 +35,7 @@ bool isWalletConnectCompatibleChain(WalletType walletType) {
case WalletType.ethereum:
case WalletType.base:
case WalletType.solana:
+ case WalletType.arbitrum:
return true;
default:
return false;
@@ -47,6 +50,8 @@ String getChainNameSpaceAndIdBasedOnWalletType(WalletType walletType) {
return EVMChainId.polygon.chain();
case WalletType.base:
return EVMChainId.base.chain();
+ case WalletType.arbitrum:
+ return EVMChainId.arbitrum.chain();
case WalletType.solana:
return SolanaChainId.mainnet.chain();
default:
@@ -59,6 +64,7 @@ List<String> getChainSupportedMethodsOnWalletType(WalletType walletType) {
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.base:
+ case WalletType.arbitrum:
return EVMSupportedMethods.values.map((e) => e.name).toList();
case WalletType.solana:
return SolanaSupportedMethods.values.map((e) => e.name).toList();
@@ -73,6 +79,8 @@ int getChainIdBasedOnWalletType(WalletType walletType) {
return 137;
case WalletType.base:
return 8453;
+ case WalletType.arbitrum:
+ return 42161;
// For now, we return eth chain Id as the default, we'll modify as we add more wallets
case WalletType.ethereum:
default:
@@ -88,6 +96,8 @@ String getChainNameBasedOnWalletType(WalletType walletType) {
return 'polygon';
case WalletType.base:
return 'base';
+ case WalletType.arbitrum:
+ return 'arbitrum';
case WalletType.solana:
return 'mainnet';
default:
@@ -103,6 +113,8 @@ String getTokenNameBasedOnWalletType(WalletType walletType) {
return 'MATIC';
case WalletType.base:
return 'BASE';
+ case WalletType.arbitrum:
+ return 'ARB';
case WalletType.solana:
return 'SOL';
default:
diff --git a/lib/reactions/wallet_utils.dart b/lib/reactions/wallet_utils.dart
index 8250dfd..2b48a49 100644
--- a/lib/reactions/wallet_utils.dart
+++ b/lib/reactions/wallet_utils.dart
@@ -5,6 +5,7 @@ bool isBIP39Wallet(WalletType walletType) {
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.base:
+ case WalletType.arbitrum:
case WalletType.solana:
case WalletType.tron:
case WalletType.bitcoin:
@@ -43,6 +44,7 @@ bool hasTokens(WalletType walletType) {
case WalletType.tron:
case WalletType.zano:
case WalletType.base:
+ case WalletType.arbitrum:
return true;
default:
return false;
diff --git a/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart b/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart
index b77346a..655abb5 100644
--- a/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart
+++ b/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart
@@ -39,6 +39,7 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
final polygonIcon = Image.asset('assets/images/crypto/polygon.webp', height: 24, width: 24);
final bitcoinCashIcon = Image.asset('assets/images/crypto/bitcoin-cash.webp', height: 24, width: 24);
final baseIcon = Image.asset('assets/images/crypto/base_icon.webp', height: 24, width: 24);
+ final arbitrumIcon = Image.asset('assets/images/crypto/arbitrum.webp', height: 24, width: 24);
final nanoIcon = Image.asset('assets/images/crypto/nano.webp', height: 24, width: 24);
final bananoIcon = Image.asset('assets/images/crypto/nano.webp', height: 24, width: 24);
final solanaIcon = Image.asset('assets/images/crypto/solana.webp', height: 24, width: 24);
@@ -187,6 +188,8 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
return dogeIcon;
case WalletType.base:
return baseIcon;
+ case WalletType.arbitrum:
+ return arbitrumIcon;
case WalletType.none:
return nonWalletTypeIcon;
}
diff --git a/lib/src/screens/dashboard/widgets/menu_widget.dart b/lib/src/screens/dashboard/widgets/menu_widget.dart
index fcf95d8..9c67c2a 100644
--- a/lib/src/screens/dashboard/widgets/menu_widget.dart
+++ b/lib/src/screens/dashboard/widgets/menu_widget.dart
@@ -34,6 +34,7 @@ class MenuWidgetState extends State<MenuWidget> {
this.bitcoinCashIcon = Image.asset('assets/images/crypto/bitcoin-cash.webp'),
this.polygonIcon = Image.asset('assets/images/crypto/polygon.webp'),
this.baseIcon = Image.asset('assets/images/crypto/base_icon.webp'),
+ this.arbitrumIcon = Image.asset('assets/images/crypto/arbitrum.webp'),
this.solanaIcon = Image.asset('assets/images/crypto/solana.webp'),
this.tronIcon = Image.asset('assets/images/crypto/tron.webp'),
this.wowneroIcon = Image.asset('assets/images/crypto/wownero.webp'),
@@ -62,6 +63,7 @@ class MenuWidgetState extends State<MenuWidget> {
Image bananoIcon;
Image polygonIcon;
Image baseIcon;
+ Image arbitrumIcon;
Image solanaIcon;
Image tronIcon;
Image wowneroIcon;
@@ -251,6 +253,8 @@ class MenuWidgetState extends State<MenuWidget> {
return solanaIcon;
case WalletType.base:
return baseIcon;
+ case WalletType.arbitrum:
+ return arbitrumIcon;
case WalletType.tron:
return tronIcon;
case WalletType.wownero:
diff --git a/lib/src/screens/exchange_trade/widgets/exchange_trade_card_item_widget.dart b/lib/src/screens/exchange_trade/widgets/exchange_trade_card_item_widget.dart
index dabca54..85be26c 100644
--- a/lib/src/screens/exchange_trade/widgets/exchange_trade_card_item_widget.dart
+++ b/lib/src/screens/exchange_trade/widgets/exchange_trade_card_item_widget.dart
@@ -60,7 +60,7 @@ class ExchangeTradeCardItemWidget extends StatelessWidget {
FeeSelectionWidget(
feesViewModel: feesViewModel,
output: output,
- onTap: () => pickTransactionPriority(context),
+ onTap: () => pickTransactionPriority(context, output),
),
if (exchangeTradeViewModel.sendViewModel.hasCoinControl)
CoinControlWidget(
@@ -75,7 +75,7 @@ class ExchangeTradeCardItemWidget extends StatelessWidget {
);
}
- Future<void> pickTransactionPriority(BuildContext context) async {
+ Future<void> pickTransactionPriority(BuildContext context, Output output) async {
final items = priorityForWalletType(feesViewModel.walletType);
final selectedItem = items.indexOf(feesViewModel.transactionPriority);
final customItemIndex = feesViewModel.getCustomPriorityIndex(items);
@@ -102,10 +102,10 @@ class ExchangeTradeCardItemWidget extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
sliderValue: customFeeRate,
onSliderChanged: (double newValue) => setState(() => customFeeRate = newValue),
- onItemSelected: (TransactionPriority priority) {
+ onItemSelected: (TransactionPriority priority) async {
feesViewModel.setTransactionPriority(priority);
setState(() => selectedIdx = items.indexOf(priority));
-
+ await output.calculateEstimatedFee();
if (feesViewModel.isLowFee) {
_showFeeAlert(context);
}
diff --git a/lib/src/screens/send/widgets/send_card.dart b/lib/src/screens/send/widgets/send_card.dart
index fed3ebf..0b960d3 100644
--- a/lib/src/screens/send/widgets/send_card.dart
+++ b/lib/src/screens/send/widgets/send_card.dart
@@ -542,7 +542,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
builder: (_) => GestureDetector(
key: ValueKey('send_page_select_fee_priority_button_key'),
onTap: sendViewModel.feesViewModel.hasFeesPriority
- ? () => pickTransactionPriority(context)
+ ? () => pickTransactionPriority(context, output)
: () {},
child: Container(
padding: EdgeInsets.only(top: 24),
@@ -799,9 +799,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
}
}
- final parsedAddress = output.isParsedAddress
- ? output.extractedAddress
- : output.address;
+ final parsedAddress = output.isParsedAddress ? output.extractedAddress : output.address;
_lastHandledAddress = current;
await _handlePaymentFlow(
@@ -837,7 +835,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
_effectsInstalled = true;
}
- Future<void> pickTransactionPriority(BuildContext context) async {
+ Future<void> pickTransactionPriority(BuildContext context, Output output) async {
final items = priorityForWalletType(sendViewModel.walletType);
final selectedItem = items.indexOf(sendViewModel.feesViewModel.transactionPriority);
final customItemIndex = sendViewModel.feesViewModel.getCustomPriorityIndex(items);
@@ -867,9 +865,10 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
mainAxisAlignment: MainAxisAlignment.center,
sliderValue: customFeeRate,
onSliderChanged: (double newValue) => setState(() => customFeeRate = newValue),
- onItemSelected: (TransactionPriority priority) {
+ onItemSelected: (TransactionPriority priority) async {
sendViewModel.feesViewModel.setTransactionPriority(priority);
setState(() => selectedIdx = items.indexOf(priority));
+ await output.calculateEstimatedFee();
},
);
},
diff --git a/lib/src/screens/settings/privacy_page.dart b/lib/src/screens/settings/privacy_page.dart
index e753efc..fa0a05b 100644
--- a/lib/src/screens/settings/privacy_page.dart
+++ b/lib/src/screens/settings/privacy_page.dart
@@ -126,6 +126,14 @@ class PrivacyPage extends BasePage {
_privacySettingsViewModel.setUseBaseScan(value);
},
),
+ if (_privacySettingsViewModel.canUseArbiScan)
+ SettingsSwitcherCell(
+ title: S.current.arbiscan_history,
+ value: _privacySettingsViewModel.useArbiScan,
+ onValueChange: (BuildContext _, bool value) {
+ _privacySettingsViewModel.setUseArbiScan(value);
+ },
+ ),
if (_privacySettingsViewModel.canUseTronGrid)
SettingsSwitcherCell(
title: S.current.trongrid_history,
diff --git a/lib/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart b/lib/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart
index 7e48928..8672da0 100644
--- a/lib/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart
+++ b/lib/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart
@@ -1,3 +1,4 @@
+import 'package:cake_wallet/arbitrum/arbitrum.dart';
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/polygon/polygon.dart';
@@ -26,6 +27,8 @@ class KeyServiceImpl implements WalletConnectKeyService {
return polygon!.getPrivateKey(wallet);
case WalletType.base:
return base!.getPrivateKey(wallet);
+ case WalletType.arbitrum:
+ return arbitrum!.getPrivateKey(wallet);
case WalletType.solana:
return solana!.getPrivateKey(wallet);
default:
@@ -41,6 +44,8 @@ class KeyServiceImpl implements WalletConnectKeyService {
return polygon!.getPublicKey(wallet);
case WalletType.base:
return base!.getPublicKey(wallet);
+ case WalletType.arbitrum:
+ return arbitrum!.getPublicKey(wallet);
case WalletType.solana:
return solana!.getPublicKey(wallet);
default:
diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart
index 6b3dbc1..a825ee2 100644
--- a/lib/store/settings_store.dart
+++ b/lib/store/settings_store.dart
@@ -110,6 +110,7 @@ abstract class SettingsStoreBase with Store {
required this.pinNativeTokenAtTop,
required this.useEtherscan,
required this.useBaseScan,
+ required this.useArbiScan,
required this.usePolygonScan,
required this.useTronGrid,
required this.useMempoolFeeAPI,
@@ -467,6 +468,11 @@ abstract class SettingsStoreBase with Store {
(_) => useBaseScan,
(bool useBaseScan) =>
_sharedPreferences.setBool(PreferencesKey.useBaseScan, useBaseScan));
+
+ reaction(
+ (_) => useArbiScan,
+ (bool useArbiScan) =>
+ _sharedPreferences.setBool(PreferencesKey.useArbiScan, useArbiScan));
reaction((_) => useTronGrid,
(bool useTronGrid) => _sharedPreferences.setBool(PreferencesKey.useTronGrid, useTronGrid));
@@ -826,6 +832,9 @@ abstract class SettingsStoreBase with Store {
@observable
bool useBaseScan;
+ @observable
+ bool useArbiScan;
+
@observable
bool useTronGrid;
@@ -1067,6 +1076,7 @@ abstract class SettingsStoreBase with Store {
final useEtherscan = sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true;
final usePolygonScan = sharedPreferences.getBool(PreferencesKey.usePolygonScan) ?? true;
final useBaseScan = sharedPreferences.getBool(PreferencesKey.useBaseScan) ?? true;
+ final useArbiScan = sharedPreferences.getBool(PreferencesKey.useArbiScan) ?? true;
final useTronGrid = sharedPreferences.getBool(PreferencesKey.useTronGrid) ?? true;
final useMempoolFeeAPI = sharedPreferences.getBool(PreferencesKey.useMempoolFeeAPI) ?? true;
final defaultNanoRep = sharedPreferences.getString(PreferencesKey.defaultNanoRep) ?? "";
@@ -1113,6 +1123,7 @@ abstract class SettingsStoreBase with Store {
final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
final polygonNodeId = sharedPreferences.getInt(PreferencesKey.currentPolygonNodeIdKey);
final baseNodeId = sharedPreferences.getInt(PreferencesKey.currentBaseNodeIdKey);
+ final arbitrumNodeId = sharedPreferences.getInt(PreferencesKey.currentArbitrumNodeIdKey);
final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
final nanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey);
final solanaNodeId = sharedPreferences.getInt(PreferencesKey.currentSolanaNodeIdKey);
@@ -1135,6 +1146,8 @@ abstract class SettingsStoreBase with Store {
nodeSource.values.firstWhereOrNull((e) => e.uriRaw == polygonDefaultNodeUri);
final baseNode = nodeSource.get(baseNodeId) ??
nodeSource.values.firstWhereOrNull((e) => e.uriRaw == baseDefaultNodeUri);
+ final arbitrumNode = nodeSource.get(arbitrumNodeId) ??
+ nodeSource.values.firstWhereOrNull((e) => e.uriRaw == arbitrumDefaultNodeUri);
final bitcoinCashElectrumServer = nodeSource.get(bitcoinCashElectrumServerId) ??
nodeSource.values.firstWhereOrNull((e) => e.uriRaw == cakeWalletBitcoinCashDefaultNodeUri);
final nanoNode = nodeSource.get(nanoNodeId) ??
@@ -1212,6 +1225,10 @@ abstract class SettingsStoreBase with Store {
nodes[WalletType.base] = baseNode;
}
+ if (arbitrumNode != null) {
+ nodes[WalletType.arbitrum] = arbitrumNode;
+ }
+
if (bitcoinCashElectrumServer != null) {
nodes[WalletType.bitcoinCash] = bitcoinCashElectrumServer;
}
@@ -1391,6 +1408,7 @@ abstract class SettingsStoreBase with Store {
useEtherscan: useEtherscan,
usePolygonScan: usePolygonScan,
useBaseScan: useBaseScan,
+ useArbiScan: useArbiScan,
useTronGrid: useTronGrid,
useMempoolFeeAPI: useMempoolFeeAPI,
defaultNanoRep: defaultNanoRep,
@@ -1588,6 +1606,7 @@ abstract class SettingsStoreBase with Store {
useEtherscan = sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true;
usePolygonScan = sharedPreferences.getBool(PreferencesKey.usePolygonScan) ?? true;
useBaseScan = sharedPreferences.getBool(PreferencesKey.useBaseScan) ?? true;
+ useArbiScan = sharedPreferences.getBool(PreferencesKey.useArbiScan) ?? true;
useTronGrid = sharedPreferences.getBool(PreferencesKey.useTronGrid) ?? true;
useMempoolFeeAPI = sharedPreferences.getBool(PreferencesKey.useMempoolFeeAPI) ?? true;
defaultNanoRep = sharedPreferences.getString(PreferencesKey.defaultNanoRep) ?? "";
@@ -1619,6 +1638,7 @@ abstract class SettingsStoreBase with Store {
final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
final polygonNodeId = sharedPreferences.getInt(PreferencesKey.currentPolygonNodeIdKey);
final baseNodeId = sharedPreferences.getInt(PreferencesKey.currentBaseNodeIdKey);
+ final arbitrumNodeId = sharedPreferences.getInt(PreferencesKey.currentArbitrumNodeIdKey);
final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
final solanaNodeId = sharedPreferences.getInt(PreferencesKey.currentSolanaNodeIdKey);
final tronNodeId = sharedPreferences.getInt(PreferencesKey.currentTronNodeIdKey);
@@ -1633,6 +1653,7 @@ abstract class SettingsStoreBase with Store {
final ethereumNode = nodeSource.get(ethereumNodeId);
final polygonNode = nodeSource.get(polygonNodeId);
final baseNode = nodeSource.get(baseNodeId);
+ final arbitrumNode = nodeSource.get(arbitrumNodeId);
final bitcoinCashNode = nodeSource.get(bitcoinCashElectrumServerId);
final nanoNode = nodeSource.get(nanoNodeId);
final solanaNode = nodeSource.get(solanaNodeId);
@@ -1670,6 +1691,10 @@ abstract class SettingsStoreBase with Store {
nodes[WalletType.base] = baseNode;
}
+ if (arbitrumNode != null) {
+ nodes[WalletType.arbitrum] = arbitrumNode;
+ }
+
if (bitcoinCashNode != null) {
nodes[WalletType.bitcoinCash] = bitcoinCashNode;
}
@@ -1833,6 +1858,9 @@ abstract class SettingsStoreBase with Store {
case WalletType.base:
await _sharedPreferences.setInt(PreferencesKey.currentBaseNodeIdKey, node.key as int);
break;
+ case WalletType.arbitrum:
+ await _sharedPreferences.setInt(PreferencesKey.currentArbitrumNodeIdKey, node.key as int);
+ break;
case WalletType.solana:
await _sharedPreferences.setInt(PreferencesKey.currentSolanaNodeIdKey, node.key as int);
break;
diff --git a/lib/utils/qr_util.dart b/lib/utils/qr_util.dart
index 7e7aec0..3a9981b 100644
--- a/lib/utils/qr_util.dart
+++ b/lib/utils/qr_util.dart
@@ -30,6 +30,8 @@ String getQrImage(WalletType type) {
return 'assets/images/doge_chain_qr.svg';
case WalletType.base:
return 'assets/images/base_chain_QR.svg';
+ case WalletType.arbitrum:
+ return 'assets/images/arbitrum_chain_QR.svg';
case WalletType.banano:
case WalletType.haven:
case WalletType.none:
diff --git a/lib/utils/token_utilities.dart b/lib/utils/token_utilities.dart
index 024c168..184eb56 100644
--- a/lib/utils/token_utilities.dart
+++ b/lib/utils/token_utilities.dart
@@ -116,6 +116,7 @@ class TokenUtilities {
WalletType.ethereum => '${walletKey}_${Erc20Token.ethereumBoxName}',
WalletType.polygon => '${walletKey}_${Erc20Token.polygonBoxName}',
WalletType.base => '${walletKey}_${Erc20Token.baseBoxName}',
+ WalletType.arbitrum => '${walletKey}_${Erc20Token.arbitrumBoxName}',
_ => '${walletKey}_${Erc20Token.ethereumBoxName}',
};
@@ -193,7 +194,7 @@ class TokenUtilities {
}
// Arbitrum One
- if (title == 'arbitrum' || title == 'arb' || tag == 'arbitrum') {
+ if (title == 'arbitrum' || title == 'arb' || tag == 'arb') {
return 42161;
}
diff --git a/lib/view_model/advanced_privacy_settings_view_model.dart b/lib/view_model/advanced_privacy_settings_view_model.dart
index efc2270..429bdf0 100644
--- a/lib/view_model/advanced_privacy_settings_view_model.dart
+++ b/lib/view_model/advanced_privacy_settings_view_model.dart
@@ -40,6 +40,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
case WalletType.dogecoin:
case WalletType.polygon:
case WalletType.base:
+ case WalletType.arbitrum:
case WalletType.solana:
case WalletType.tron:
return true;
@@ -81,6 +82,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
WalletType.ethereum,
WalletType.polygon,
WalletType.base,
+ WalletType.arbitrum,
WalletType.tron,
WalletType.solana,
WalletType.monero,
diff --git a/lib/view_model/dashboard/balance_view_model.dart b/lib/view_model/dashboard/balance_view_model.dart
index 78701b4..552d318 100644
--- a/lib/view_model/dashboard/balance_view_model.dart
+++ b/lib/view_model/dashboard/balance_view_model.dart
@@ -167,6 +167,7 @@ abstract class BalanceViewModelBase with Store {
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.base:
+ case WalletType.arbitrum:
case WalletType.solana:
case WalletType.tron:
return S.current.xmr_full_balance;
diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart
index 25a571f..50e8d62 100644
--- a/lib/view_model/dashboard/dashboard_view_model.dart
+++ b/lib/view_model/dashboard/dashboard_view_model.dart
@@ -879,6 +879,7 @@ abstract class DashboardViewModelBase with Store {
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.base:
+ case WalletType.arbitrum:
case WalletType.solana:
case WalletType.nano:
case WalletType.banano:
diff --git a/lib/view_model/dashboard/home_settings_view_model.dart b/lib/view_model/dashboard/home_settings_view_model.dart
index c54d8b1..46c0248 100644
--- a/lib/view_model/dashboard/home_settings_view_model.dart
+++ b/lib/view_model/dashboard/home_settings_view_model.dart
@@ -1,5 +1,6 @@
import 'dart:convert';
+import 'package:cake_wallet/arbitrum/arbitrum.dart';
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/core/fiat_conversion_service.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart';
@@ -111,6 +112,17 @@ abstract class HomeSettingsViewModelBase with Store {
);
await base!.addErc20Token(_balanceViewModel.wallet, baseToken);
}
+ if (_balanceViewModel.wallet.type == WalletType.arbitrum) {
+ final arbitrumToken = Erc20Token(
+ name: token.name,
+ symbol: token.title,
+ decimal: token.decimals,
+ contractAddress: contractAddress.toLowerCase(),
+ iconPath: token.iconPath,
+ isPotentialScam: token.isPotentialScam,
+ );
+ await arbitrum!.addErc20Token(_balanceViewModel.wallet, arbitrumToken);
+ }
if (_balanceViewModel.wallet.type == WalletType.solana) {
final splToken = token.copyWith(enabled: true);
@@ -153,6 +165,10 @@ abstract class HomeSettingsViewModelBase with Store {
return base!.isTokenAlreadyAdded(_balanceViewModel.wallet, contractAddress);
}
+ if (_balanceViewModel.wallet.type == WalletType.arbitrum) {
+ return arbitrum!.isTokenAlreadyAdded(_balanceViewModel.wallet, contractAddress);
+ }
+
if (_balanceViewModel.wallet.type == WalletType.solana) {
return solana!.isTokenAlreadyAdded(_balanceViewModel.wallet, contractAddress);
}
@@ -184,6 +200,10 @@ abstract class HomeSettingsViewModelBase with Store {
await base!.deleteErc20Token(_balanceViewModel.wallet, token as Erc20Token);
}
+ if (_balanceViewModel.wallet.type == WalletType.arbitrum) {
+ await arbitrum!.deleteErc20Token(_balanceViewModel.wallet, token as Erc20Token);
+ }
+
if (_balanceViewModel.wallet.type == WalletType.solana) {
await solana!.deleteSPLToken(_balanceViewModel.wallet, token);
}
@@ -240,6 +260,8 @@ abstract class HomeSettingsViewModelBase with Store {
break;
case WalletType.base:
defaultTokenAddresses = base!.getDefaultTokenContractAddresses();
+ case WalletType.arbitrum:
+ defaultTokenAddresses = arbitrum!.getDefaultTokenContractAddresses();
break;
case WalletType.solana:
defaultTokenAddresses = solana!.getDefaultTokenContractAddresses();
@@ -380,6 +402,10 @@ abstract class HomeSettingsViewModelBase with Store {
return await base!.getErc20Token(_balanceViewModel.wallet, contractAddress);
}
+ if (_balanceViewModel.wallet.type == WalletType.arbitrum) {
+ return await arbitrum!.getErc20Token(_balanceViewModel.wallet, contractAddress);
+ }
+
if (_balanceViewModel.wallet.type == WalletType.solana) {
return await solana!.getSPLToken(_balanceViewModel.wallet, contractAddress);
}
@@ -426,6 +452,11 @@ abstract class HomeSettingsViewModelBase with Store {
if (!value) base!.removeTokenTransactionsInHistory(_balanceViewModel.wallet, token);
}
+ if (_balanceViewModel.wallet.type == WalletType.arbitrum) {
+ arbitrum!.addErc20Token(_balanceViewModel.wallet, token as Erc20Token);
+ if (!value) arbitrum!.removeTokenTransactionsInHistory(_balanceViewModel.wallet, token);
+ }
+
if (_balanceViewModel.wallet.type == WalletType.solana) {
final address = solana!.getTokenAddress(token);
solana!.addSPLToken(_balanceViewModel.wallet, token, address);
@@ -487,6 +518,14 @@ abstract class HomeSettingsViewModelBase with Store {
..sort(_sortFunc));
}
+ if (_balanceViewModel.wallet.type == WalletType.arbitrum) {
+ tokens.addAll(arbitrum!
+ .getERC20Currencies(_balanceViewModel.wallet)
+ .where((element) => _matchesSearchText(element))
+ .toList()
+ ..sort(_sortFunc));
+ }
+
if (_balanceViewModel.wallet.type == WalletType.solana) {
tokens.addAll(solana!
.getSPLTokenCurrencies(_balanceViewModel.wallet)
@@ -528,7 +567,7 @@ abstract class HomeSettingsViewModelBase with Store {
bool _matchesSearchText(CryptoCurrency asset) {
final address = getTokenAddressBasedOnWallet(asset);
- // The homes settings would only be displayed for either of Tron, Ethereum, Polygon, Base or Solana Wallets.
+ // The homes settings would only be displayed for either of Tron, EVM or Solana Wallets.
if (address == null) return false;
return searchText.isEmpty ||
@@ -558,11 +597,15 @@ abstract class HomeSettingsViewModelBase with Store {
return base!.getTokenAddress(asset);
}
+ if (_balanceViewModel.wallet.type == WalletType.arbitrum) {
+ return arbitrum!.getTokenAddress(asset);
+ }
+
if (_balanceViewModel.wallet.type == WalletType.zano) {
return zano!.getZanoAssetAddress(asset);
}
- // 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).
+ // 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).
return null;
}
}
diff --git a/lib/view_model/dashboard/transaction_list_item.dart b/lib/view_model/dashboard/transaction_list_item.dart
index 874da36..33df3ea 100644
--- a/lib/view_model/dashboard/transaction_list_item.dart
+++ b/lib/view_model/dashboard/transaction_list_item.dart
@@ -1,3 +1,4 @@
+import 'package:cake_wallet/arbitrum/arbitrum.dart';
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/decred/decred.dart';
import 'package:cake_wallet/entities/balance_display_mode.dart';
@@ -139,6 +140,10 @@ class TransactionListItem extends ActionListItem with Keyable {
final asset = base!.assetOfTransaction(balanceViewModel.wallet, transaction);
return asset;
}
+ if (balanceViewModel.wallet.type == WalletType.arbitrum) {
+ final asset = arbitrum!.assetOfTransaction(balanceViewModel.wallet, transaction);
+ return asset;
+ }
if (balanceViewModel.wallet.type == WalletType.solana) {
final asset = solana!.assetOfTransaction(balanceViewModel.wallet, transaction);
@@ -199,6 +204,13 @@ class TransactionListItem extends ActionListItem with Keyable {
cryptoAmount: base!.formatterBaseAmountToDouble(transaction: transaction),
price: price);
break;
+ case WalletType.arbitrum:
+ final asset = arbitrum!.assetOfTransaction(balanceViewModel.wallet, transaction);
+ final price = balanceViewModel.fiatConvertationStore.prices[asset];
+ amount = calculateFiatAmountRaw(
+ cryptoAmount: arbitrum!.formatterArbitrumAmountToDouble(transaction: transaction),
+ price: price);
+ break;
case WalletType.nano:
amount = calculateFiatAmountRaw(
cryptoAmount: double.parse(nanoUtil!.getRawAsUsableString(
diff --git a/lib/view_model/exchange/exchange_trade_view_model.dart b/lib/view_model/exchange/exchange_trade_view_model.dart
index 8fdf029..fec47f3 100644
--- a/lib/view_model/exchange/exchange_trade_view_model.dart
+++ b/lib/view_model/exchange/exchange_trade_view_model.dart
@@ -18,6 +18,7 @@ import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/xoswap_exchange_provider.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/generated/i18n.dart';
+import 'package:cake_wallet/arbitrum/arbitrum.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_item.dart';
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
@@ -109,14 +110,10 @@ abstract class ExchangeTradeViewModelBase with Store {
@observable
bool isSendable;
-
- bool get isSwapsXyzSendingEVMTokenSwap => (_provider is SwapsXyzExchangeProvider) &&
- (wallet.type == WalletType.ethereum &&
- wallet.currency != trade.from ||
- (wallet.type == WalletType.polygon &&
- wallet.currency != trade.from) ||
- (wallet.type == WalletType.base &&
- wallet.currency != trade.from));
+ bool get isSwapsXyzSendingEVMTokenSwap =>
+ (_provider is SwapsXyzExchangeProvider) &&
+ isEVMCompatibleChain(wallet.type) &&
+ wallet.currency != trade.from;
String get extraInfo => trade.extraId != null && trade.extraId!.isNotEmpty
? '\n\n' + S.current.exchange_extra_info
@@ -203,7 +200,8 @@ abstract class ExchangeTradeViewModelBase with Store {
sendViewModel.selectedCryptoCurrency = selected;
- final pendingTransaction = await sendViewModel.createTransaction(provider: _provider, trade: trade);
+ final pendingTransaction =
+ await sendViewModel.createTransaction(provider: _provider, trade: trade);
if (_provider is SwapsXyzExchangeProvider) {
final hash = pendingTransaction?.evmTxHashFromRawHex ?? pendingTransaction?.id ?? '';
@@ -336,6 +334,9 @@ abstract class ExchangeTradeViewModelBase with Store {
bool _isBaseToken() =>
wallet.currency == CryptoCurrency.baseEth && tradeFrom?.tag == CryptoCurrency.baseEth.tag;
+ bool _isArbitrumToken() =>
+ wallet.currency == CryptoCurrency.arbEth && tradeFrom?.tag == CryptoCurrency.arbEth.tag;
+
bool _isTronToken() =>
wallet.currency == CryptoCurrency.trx && tradeFrom?.tag == CryptoCurrency.trx.title;
@@ -348,9 +349,10 @@ abstract class ExchangeTradeViewModelBase with Store {
_isPolygonToken() ||
_isSplToken() ||
_isTronToken() ||
- _isBaseToken();
+ _isBaseToken() ||
+ _isArbitrumToken();
}
-
+
Future<void> registerSwapsXyzTransaction() async {
try {
if (!(_provider is SwapsXyzExchangeProvider)) return;
@@ -366,7 +368,9 @@ abstract class ExchangeTradeViewModelBase with Store {
return;
}
- final txHash = sendViewModel.pendingTransaction?.evmTxHashFromRawHex ?? sendViewModel.pendingTransaction?.id ?? '';
+ final txHash = sendViewModel.pendingTransaction?.evmTxHashFromRawHex ??
+ sendViewModel.pendingTransaction?.id ??
+ '';
if (txHash.isEmpty) {
printV('SwapsXyz: transaction register: skipped (txHash empty)');
@@ -379,7 +383,8 @@ abstract class ExchangeTradeViewModelBase with Store {
return;
}
- printV('SwapsXyz: attempting to register transaction: tradeId = ${trade.id}, txHash = $txHash, chainId = $chainId, vmId = $vmId');
+ printV(
+ 'SwapsXyz: attempting to register transaction: tradeId = ${trade.id}, txHash = $txHash, chainId = $chainId, vmId = $vmId');
final registered = await swaps.registerAltVmTx(
txId: trade.id,
@@ -423,6 +428,8 @@ abstract class ExchangeTradeViewModelBase with Store {
return PolygonURI(amount: amount, address: inputAddress);
case WalletType.base:
return BaseURI(amount: amount, address: inputAddress);
+ case WalletType.arbitrum:
+ return ArbitrumURI(amount: amount, address: inputAddress);
case WalletType.solana:
return SolanaURI(amount: amount, address: inputAddress);
case WalletType.tron:
diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart
index 29d992b..8e47180 100644
--- a/lib/view_model/exchange/exchange_view_model.dart
+++ b/lib/view_model/exchange/exchange_view_model.dart
@@ -878,6 +878,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
depositCurrency = CryptoCurrency.baseEth;
receiveCurrency = CryptoCurrency.xmr;
break;
+ case WalletType.arbitrum:
+ depositCurrency = CryptoCurrency.arbEth;
+ receiveCurrency = CryptoCurrency.xmr;
+ break;
case WalletType.solana:
depositCurrency = CryptoCurrency.sol;
receiveCurrency = CryptoCurrency.xmr;
diff --git a/lib/view_model/node_list/node_create_or_edit_view_model.dart b/lib/view_model/node_list/node_create_or_edit_view_model.dart
index 67a0887..dffa690 100644
--- a/lib/view_model/node_list/node_create_or_edit_view_model.dart
+++ b/lib/view_model/node_list/node_create_or_edit_view_model.dart
@@ -81,6 +81,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.base:
+ case WalletType.arbitrum:
case WalletType.solana:
case WalletType.banano:
case WalletType.nano:
diff --git a/lib/view_model/payment/payment_view_model.dart b/lib/view_model/payment/payment_view_model.dart
index 36bf4f1..5676a6b 100644
--- a/lib/view_model/payment/payment_view_model.dart
+++ b/lib/view_model/payment/payment_view_model.dart
@@ -5,7 +5,6 @@ import 'package:cake_wallet/store/app_store.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/utils/print_verbose.dart';
-import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
part 'payment_view_model.g.dart';
diff --git a/lib/view_model/restore/wallet_restore_from_qr_code.dart b/lib/view_model/restore/wallet_restore_from_qr_code.dart
index c3364ff..3b4709a 100644
--- a/lib/view_model/restore/wallet_restore_from_qr_code.dart
+++ b/lib/view_model/restore/wallet_restore_from_qr_code.dart
@@ -32,6 +32,7 @@ class WalletRestoreFromQRCode {
'ethereum-wallet': WalletType.ethereum,
'polygon-wallet': WalletType.polygon,
'base-wallet': WalletType.base,
+ 'arbitrum-wallet': WalletType.arbitrum,
'nano-wallet': WalletType.nano,
'nano_wallet': WalletType.nano,
'bitcoincash': WalletType.bitcoinCash,
diff --git a/lib/view_model/send/fees_view_model.dart b/lib/view_model/send/fees_view_model.dart
index ff87883..49b2120 100644
--- a/lib/view_model/send/fees_view_model.dart
+++ b/lib/view_model/send/fees_view_model.dart
@@ -1,3 +1,4 @@
+import 'package:cake_wallet/arbitrum/arbitrum.dart';
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/decred/decred.dart';
@@ -102,6 +103,7 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor
case WalletType.banano:
case WalletType.solana:
case WalletType.tron:
+ case WalletType.arbitrum:
return false;
}
}
@@ -119,7 +121,8 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor
wallet.type != WalletType.nano &&
wallet.type != WalletType.banano &&
wallet.type != WalletType.solana &&
- wallet.type != WalletType.tron;
+ wallet.type != WalletType.tron &&
+ wallet.type != WalletType.arbitrum;
@computed
bool get isElectrumWallet =>
diff --git a/lib/view_model/send/output.dart b/lib/view_model/send/output.dart
index a599ae8..1caaf05 100644
--- a/lib/view_model/send/output.dart
+++ b/lib/view_model/send/output.dart
@@ -28,6 +28,7 @@ import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
+import 'package:cake_wallet/arbitrum/arbitrum.dart';
import 'package:cake_wallet/entities/contact_base.dart';
@@ -49,8 +50,15 @@ abstract class OutputBase with Store {
address = '',
note = '',
extractedAddress = '',
+ estimatedFee = 0.0,
parsedAddress = ParsedAddress(addresses: []) {
_setCryptoNumMaximumFractionDigits();
+ autorun((_) {
+ final status = _wallet.syncStatus;
+ printV("Sync status changed to $status. Recalculating fees");
+
+ calculateEstimatedFee();
+ });
}
Key key;
@@ -118,6 +126,9 @@ abstract class OutputBase with Store {
case WalletType.base:
_amount = base!.formatterBaseParseAmount(_cryptoAmount);
break;
+ case WalletType.arbitrum:
+ _amount = arbitrum!.formatterArbitrumParseAmount(_cryptoAmount);
+ break;
case WalletType.wownero:
_amount = wownero!.formatterWowneroParseAmount(amount: _cryptoAmount);
break;
@@ -145,28 +156,74 @@ abstract class OutputBase with Store {
return amount;
}
- @computed
- double get estimatedFee {
- try {
- // forces mobx to rebuild the computed value
- final _ = _wallet.syncStatus;
+ @observable
+ double estimatedFee;
+ @action
+ Future<void> calculateEstimatedFee() async {
+ try {
if (_wallet.type == WalletType.tron) {
if (cryptoCurrencyHandler() == CryptoCurrency.trx) {
- final nativeEstimatedFee = tron!.getTronNativeEstimatedFee(_wallet) ?? 0;
- return double.parse(nativeEstimatedFee.toString());
+ final nativeEstimatedFee = tron!.getTronNativeEstimatedFee(_wallet) ?? '0';
+ estimatedFee = double.parse(nativeEstimatedFee);
} else {
- final trc20EstimatedFee = tron!.getTronTRC20EstimatedFee(_wallet) ?? 0;
- return double.parse(trc20EstimatedFee.toString());
+ final trc20EstimatedFee = tron!.getTronTRC20EstimatedFee(_wallet) ?? '0';
+ estimatedFee = double.parse(trc20EstimatedFee);
}
}
+ if (isEVMCompatibleChain(_wallet.type)) {
+ if (_wallet.type == WalletType.arbitrum) {
+ String? fee = cryptoCurrencyHandler() == CryptoCurrency.arbEth
+ ? arbitrum!.getArbitrumNativeEstimatedFee(_wallet)
+ : arbitrum!.getArbitrumERC20EstimatedFee(_wallet);
+
+ estimatedFee = arbitrum!
+ .formatterArbitrumAmountToDouble(amount: BigInt.from(double.parse(fee ?? '0.0')));
+ return;
+ }
+
+ await _wallet.updateEstimatedFeesParams(_settingsStore.priority[_wallet.type]!);
+
+ double calculatedFee = 0.0;
+
+ if (_wallet.type == WalletType.ethereum) {
+ String? fee = cryptoCurrencyHandler() == CryptoCurrency.eth
+ ? ethereum!.getEthereumNativeEstimatedFee(_wallet)
+ : ethereum!.getEthereumERC20EstimatedFee(_wallet);
+
+ calculatedFee = ethereum!
+ .formatterEthereumAmountToDouble(amount: BigInt.from(double.parse(fee ?? '0.0')));
+ }
+
+ if (_wallet.type == WalletType.polygon) {
+ String? fee = cryptoCurrencyHandler() == CryptoCurrency.maticpoly
+ ? polygon!.getPolygonNativeEstimatedFee(_wallet)
+ : polygon!.getPolygonERC20EstimatedFee(_wallet);
+
+ calculatedFee = polygon!
+ .formatterPolygonAmountToDouble(amount: BigInt.from(double.parse(fee ?? '0.0')));
+ }
+
+ if (_wallet.type == WalletType.base) {
+ String? fee = cryptoCurrencyHandler() == CryptoCurrency.baseEth
+ ? base!.getBaseNativeEstimatedFee(_wallet)
+ : base!.getBaseERC20EstimatedFee(_wallet);
+ calculatedFee =
+ base!.formatterBaseAmountToDouble(amount: BigInt.from(double.parse(fee ?? '0.0')));
+ }
+
+ estimatedFee = calculatedFee;
+ }
+
if (_wallet.type == WalletType.solana) {
- return solana!.getEstimateFees(_wallet) ?? 0.0;
+ estimatedFee = solana!.getEstimateFees(_wallet) ?? 0.0;
}
int? fee = _wallet.calculateEstimatedFee(
- _settingsStore.priority[_wallet.type]!, formattedCryptoAmount);
+ _settingsStore.priority[_wallet.type]!,
+ formattedCryptoAmount,
+ );
if (_wallet.type == WalletType.bitcoin) {
if (_settingsStore.priority[_wallet.type] ==
@@ -175,48 +232,34 @@ abstract class OutputBase with Store {
_wallet, _settingsStore.customBitcoinFeeRate, formattedCryptoAmount);
}
- return bitcoin!.formatterBitcoinAmountToDouble(amount: fee);
+ estimatedFee = bitcoin!.formatterBitcoinAmountToDouble(amount: fee);
}
if (_wallet.type == WalletType.litecoin ||
_wallet.type == WalletType.bitcoinCash ||
_wallet.type == WalletType.dogecoin) {
- return bitcoin!.formatterBitcoinAmountToDouble(amount: fee);
+ estimatedFee = bitcoin!.formatterBitcoinAmountToDouble(amount: fee);
}
if (_wallet.type == WalletType.monero) {
- return monero!.formatterMoneroAmountToDouble(amount: fee);
+ estimatedFee = monero!.formatterMoneroAmountToDouble(amount: fee);
}
if (_wallet.type == WalletType.wownero) {
- return wownero!.formatterWowneroAmountToDouble(amount: fee);
- }
-
- if (_wallet.type == WalletType.ethereum) {
- return ethereum!.formatterEthereumAmountToDouble(amount: BigInt.from(fee));
- }
-
- if (_wallet.type == WalletType.polygon) {
- return polygon!.formatterPolygonAmountToDouble(amount: BigInt.from(fee));
- }
-
- if (_wallet.type == WalletType.base) {
- return base!.formatterBaseAmountToDouble(amount: BigInt.from(fee));
+ estimatedFee = wownero!.formatterWowneroAmountToDouble(amount: fee);
}
if (_wallet.type == WalletType.zano) {
- return zano!.formatterIntAmountToDouble(
+ estimatedFee = zano!.formatterIntAmountToDouble(
amount: fee, currency: cryptoCurrencyHandler(), forFee: true);
}
if (_wallet.type == WalletType.decred) {
- return decred!.formatterDecredAmountToDouble(amount: fee);
+ estimatedFee = decred!.formatterDecredAmountToDouble(amount: fee);
}
} catch (e) {
printV(e.toString());
}
-
- return 0;
}
@computed
@@ -337,6 +380,7 @@ abstract class OutputBase with Store {
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.base:
+ case WalletType.arbitrum:
case WalletType.solana:
case WalletType.tron:
case WalletType.haven:
diff --git a/lib/view_model/send/send_template_view_model.dart b/lib/view_model/send/send_template_view_model.dart
index 4315486..29f576d 100644
--- a/lib/view_model/send/send_template_view_model.dart
+++ b/lib/view_model/send/send_template_view_model.dart
@@ -57,6 +57,7 @@ abstract class SendTemplateViewModelBase with Store {
_wallet.type != WalletType.ethereum &&
_wallet.type != WalletType.polygon &&
_wallet.type != WalletType.base &&
+ _wallet.type != WalletType.arbitrum &&
_wallet.type != WalletType.solana &&
_wallet.type != WalletType.tron;
diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart
index 24a575e..2c038b0 100644
--- a/lib/view_model/send/send_view_model.dart
+++ b/lib/view_model/send/send_view_model.dart
@@ -29,6 +29,7 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/polygon/polygon.dart';
+import 'package:cake_wallet/arbitrum/arbitrum.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/solana/solana.dart';
@@ -137,7 +138,6 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
bool get isMwebEnabled => balanceViewModel.mwebEnabled;
bool get isEVMWallet => isEVMCompatibleChain(walletType);
-
@action
void setShowAddressBookPopup(bool value) {
_settingsStore.showAddressBookPopupEnabled = value;
@@ -223,6 +223,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.base:
+ case WalletType.arbitrum:
case WalletType.tron:
case WalletType.solana:
return wallet.currency;
@@ -603,6 +604,26 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
priority,
);
+ _isSwapsXYZCallDataTx = true;
+ state = ExecutedSuccessfullyState();
+ return pendingTransaction; // do NOT fall back to regular flow
+ }
+ if (walletType == WalletType.arbitrum) {
+ _pendingApprovalTx = await buildApprovalIfNeeded(
+ spender: routerTo!,
+ tokenContract: tokenContract,
+ requiredAmount: requiredAmount,
+ sourceTokenDecimals: trade.sourceTokenDecimals,
+ );
+
+ // Build the callData tx
+ pendingTransaction = await arbitrum!.createRawCallDataTransaction(
+ wallet,
+ routerTo,
+ routerData,
+ routerValueWei,
+ );
+
_isSwapsXYZCallDataTx = true;
state = ExecutedSuccessfullyState();
return pendingTransaction; // do NOT fall back to regular flow
@@ -637,7 +658,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
return pendingTransaction;
}
if (walletType == WalletType.base) {
- final priority = _settingsStore.priority[WalletType.polygon]!;
+ final priority = _settingsStore.priority[WalletType.base]!;
pendingTransaction = await base!.createRawCallDataTransaction(
wallet,
routerTo!,
@@ -649,6 +670,17 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
state = ExecutedSuccessfullyState();
return pendingTransaction;
}
+ if (walletType == WalletType.arbitrum) {
+ pendingTransaction = await arbitrum!.createRawCallDataTransaction(
+ wallet,
+ routerTo!,
+ routerData,
+ routerValueWei,
+ );
+ _isSwapsXYZCallDataTx = true;
+ state = ExecutedSuccessfullyState();
+ return pendingTransaction;
+ }
}
}
@@ -847,7 +879,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
wallet.type != WalletType.nano &&
wallet.type != WalletType.banano &&
wallet.type != WalletType.solana &&
- wallet.type != WalletType.tron) {
+ wallet.type != WalletType.tron &&
+ wallet.type != WalletType.arbitrum) {
throw Exception('Priority is null for wallet type: ${wallet.type}');
}
@@ -890,6 +923,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
case WalletType.base:
return base!.createBaseTransactionCredentials(outputs,
priority: priority!, currency: selectedCryptoCurrency);
+ case WalletType.arbitrum:
+ return arbitrum!
+ .createArbitrumTransactionCredentials(outputs, currency: selectedCryptoCurrency);
case WalletType.solana:
return solana!
.createSolanaTransactionCredentials(outputs, currency: selectedCryptoCurrency);
@@ -1016,6 +1052,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
if (walletType == WalletType.ethereum ||
walletType == WalletType.polygon ||
walletType == WalletType.base ||
+ walletType == WalletType.arbitrum ||
walletType == WalletType.haven) {
if (errorMessage.contains('gas required exceeds allowance')) {
return S.current.gas_exceeds_allowance;
@@ -1154,6 +1191,13 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
spender,
requiredAmount,
);
+ } else if (walletType == WalletType.arbitrum) {
+ needsApproval = await arbitrum!.isApprovalRequired(
+ wallet,
+ tokenContract,
+ spender,
+ requiredAmount,
+ );
}
if (!needsApproval) return null;
@@ -1196,6 +1240,13 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
erc20Token,
priority,
);
+ } else if (walletType == WalletType.arbitrum) {
+ return await arbitrum!.createTokenApproval(
+ wallet,
+ requiredAmount,
+ spender,
+ erc20Token,
+ );
}
return null;
diff --git a/lib/view_model/settings/privacy_settings_view_model.dart b/lib/view_model/settings/privacy_settings_view_model.dart
index 150ccdd..7d328c8 100644
--- a/lib/view_model/settings/privacy_settings_view_model.dart
+++ b/lib/view_model/settings/privacy_settings_view_model.dart
@@ -1,3 +1,4 @@
+import 'package:cake_wallet/arbitrum/arbitrum.dart';
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
@@ -79,6 +80,9 @@ abstract class PrivacySettingsViewModelBase with Store {
@computed
bool get useBaseScan => _settingsStore.useBaseScan;
+ @computed
+ bool get useArbiScan => _settingsStore.useArbiScan;
+
@computed
bool get useTronGrid => _settingsStore.useTronGrid;
@@ -118,6 +122,8 @@ abstract class PrivacySettingsViewModelBase with Store {
bool get canUseBaseScan => _wallet.type == WalletType.base;
+ bool get canUseArbiScan => _wallet.type == WalletType.arbitrum;
+
bool get canUseTronGrid => _wallet.type == WalletType.tron;
bool get canUseMempoolFeeAPI => _wallet.type == WalletType.bitcoin;
@@ -195,6 +201,12 @@ abstract class PrivacySettingsViewModelBase with Store {
tron!.updateTronGridUsageState(_wallet, value);
}
+ @action
+ void setUseArbiScan(bool value) {
+ _settingsStore.useArbiScan = value;
+ arbitrum!.updateArbitrumScanUsageState(_wallet, value);
+ }
+
@action
void setUseMempoolFeeAPI(bool value) => _settingsStore.useMempoolFeeAPI = value;
diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart
index dde5905..ffe22fa 100644
--- a/lib/view_model/transaction_details_view_model.dart
+++ b/lib/view_model/transaction_details_view_model.dart
@@ -77,6 +77,9 @@ abstract class TransactionDetailsViewModelBase with Store {
case WalletType.base:
_addBaseListItems(tx, dateFormat);
break;
+ case WalletType.arbitrum:
+ _addArbitrumListItems(tx, dateFormat);
+ break;
case WalletType.solana:
_addSolanaListItems(tx, dateFormat);
break;
@@ -204,6 +207,8 @@ abstract class TransactionDetailsViewModelBase with Store {
return 'https://blockchair.com/dogecoin/transaction/${txId}';
case WalletType.base:
return 'https://basescan.org/tx/${txId}';
+ case WalletType.arbitrum:
+ return 'https://arbiscan.io/tx/${txId}';
case WalletType.none:
return '';
}
@@ -241,6 +246,8 @@ abstract class TransactionDetailsViewModelBase with Store {
return S.current.view_transaction_on + 'dcrdata.decred.org';
case WalletType.base:
return S.current.view_transaction_on + 'basescan.org';
+ case WalletType.arbitrum:
+ return S.current.view_transaction_on + 'arbiscan.io';
case WalletType.none:
return '';
}
@@ -606,6 +613,56 @@ abstract class TransactionDetailsViewModelBase with Store {
items.addAll(_items);
}
+ void _addArbitrumListItems(TransactionInfo tx, DateFormat dateFormat) {
+ final _items = [
+ StandartListItem(
+ title: S.current.transaction_details_transaction_id,
+ value: tx.txHash,
+ key: ValueKey('standard_list_item_transaction_details_id_key'),
+ ),
+ StandartListItem(
+ title: S.current.transaction_details_date,
+ value: dateFormat.format(tx.date),
+ key: ValueKey('standard_list_item_transaction_details_date_key'),
+ ),
+ StandartListItem(
+ title: S.current.confirmations,
+ value: tx.confirmations.toString(),
+ key: ValueKey('standard_list_item_transaction_confirmations_key'),
+ ),
+ StandartListItem(
+ title: S.current.transaction_details_height,
+ value: '${tx.height}',
+ key: ValueKey('standard_list_item_transaction_details_height_key'),
+ ),
+ StandartListItem(
+ title: S.current.transaction_details_amount,
+ value: tx.amountFormatted(),
+ key: ValueKey('standard_list_item_transaction_details_amount_key'),
+ ),
+ if (tx.feeFormatted()?.isNotEmpty ?? false)
+ StandartListItem(
+ title: S.current.transaction_details_fee,
+ value: tx.feeFormatted()!,
+ key: ValueKey('standard_list_item_transaction_details_fee_key'),
+ ),
+ if (showRecipientAddress && tx.to != null && tx.direction == TransactionDirection.outgoing)
+ StandartListItem(
+ title: S.current.transaction_details_recipient_address,
+ value: tx.to!,
+ key: ValueKey('standard_list_item_transaction_details_recipient_address_key'),
+ ),
+ if (tx.direction == TransactionDirection.incoming && tx.from != null)
+ StandartListItem(
+ title: S.current.transaction_details_source_address,
+ value: tx.from!,
+ key: ValueKey('standard_list_item_transaction_details_source_address_key'),
+ ),
+ ];
+
+ items.addAll(_items);
+ }
+
void _addSolanaListItems(TransactionInfo tx, DateFormat dateFormat) {
final _items = [
StandartListItem(
diff --git a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart
index 7f2a78b..259c015 100644
--- a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart
+++ b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart
@@ -13,6 +13,7 @@ import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/polygon/polygon.dart';
+import 'package:cake_wallet/arbitrum/arbitrum.dart';
import 'package:cake_wallet/reactions/wallet_utils.dart';
import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/decred/decred.dart';
@@ -146,6 +147,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
return DogeURI(amount: amount, address: address.address);
case WalletType.base:
return BaseURI(amount: amount, address: address.address);
+ case WalletType.arbitrum:
+ return ArbitrumURI(amount: amount, address: address.address);
case WalletType.none:
throw Exception('Unexpected type: ${type.toString()}');
}
@@ -272,6 +275,12 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
}
+ if (wallet.type == WalletType.arbitrum) {
+ final primaryAddress = arbitrum!.getAddress(wallet);
+
+ addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
+ }
+
if (wallet.type == WalletType.solana) {
final primaryAddress = solana!.getAddress(wallet);
@@ -450,6 +459,12 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
'assets/images/usdc_icon.svg',
'assets/images/more_tokens.svg',
];
+ case WalletType.arbitrum:
+ return [
+ 'assets/images/crypto/arbitrum.webp',
+ 'assets/images/usdc_icon.svg',
+ 'assets/images/more_tokens.svg',
+ ];
default:
return [];
}
@@ -473,6 +488,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
return 'assets/images/zano_chain_mono.svg';
case WalletType.base:
return 'assets/images/base_chain_mono.svg';
+ case WalletType.arbitrum:
+ return 'assets/images/arbitrum_chain_mono.svg';
default:
return 'assets/images/eth_chain_mono.svg';
}
diff --git a/lib/view_model/wallet_keys_view_model.dart b/lib/view_model/wallet_keys_view_model.dart
index f429cd9..7874ea6 100644
--- a/lib/view_model/wallet_keys_view_model.dart
+++ b/lib/view_model/wallet_keys_view_model.dart
@@ -127,6 +127,7 @@ abstract class WalletKeysViewModelBase with Store {
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.base:
+ case WalletType.arbitrum:
case WalletType.solana:
case WalletType.tron:
items.addAll([
@@ -255,6 +256,8 @@ abstract class WalletKeysViewModelBase with Store {
return 'polygon-wallet';
case WalletType.base:
return 'base-wallet';
+ case WalletType.arbitrum:
+ return 'arbitrum-wallet';
case WalletType.solana:
return 'solana-wallet';
case WalletType.tron:
diff --git a/lib/view_model/wallet_new_vm.dart b/lib/view_model/wallet_new_vm.dart
index 64c5cb6..79ecbdf 100644
--- a/lib/view_model/wallet_new_vm.dart
+++ b/lib/view_model/wallet_new_vm.dart
@@ -1,3 +1,4 @@
+import 'package:cake_wallet/arbitrum/arbitrum.dart';
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/core/new_wallet_arguments.dart';
import 'package:cake_wallet/dogecoin/dogecoin.dart';
@@ -97,6 +98,13 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
mnemonic: newWalletArguments!.mnemonic,
passphrase: passphrase,
);
+ case WalletType.arbitrum:
+ return arbitrum!.createArbitrumNewWalletCredentials(
+ name: name,
+ password: walletPassword,
+ mnemonic: newWalletArguments!.mnemonic,
+ passphrase: passphrase,
+ );
case WalletType.bitcoinCash:
return bitcoinCash!.createBitcoinCashNewWalletCredentials(
name: name,
diff --git a/lib/view_model/wallet_restore_view_model.dart b/lib/view_model/wallet_restore_view_model.dart
index ef16982..ce427b8 100644
--- a/lib/view_model/wallet_restore_view_model.dart
+++ b/lib/view_model/wallet_restore_view_model.dart
@@ -1,3 +1,4 @@
+import 'package:cake_wallet/arbitrum/arbitrum.dart';
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
@@ -52,6 +53,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.base:
+ case WalletType.arbitrum:
case WalletType.decred:
case WalletType.bitcoin:
availableModes = [WalletRestoreMode.seed, WalletRestoreMode.keys];
@@ -92,6 +94,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
WalletType.ethereum,
WalletType.polygon,
WalletType.base,
+ WalletType.arbitrum,
WalletType.nano,
WalletType.banano,
WalletType.solana,
@@ -183,6 +186,13 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
password: password,
passphrase: passphrase,
);
+ case WalletType.arbitrum:
+ return arbitrum!.createArbitrumRestoreWalletFromSeedCredentials(
+ name: name,
+ mnemonic: seed,
+ password: password,
+ passphrase: passphrase,
+ );
case WalletType.solana:
return solana!.createSolanaRestoreWalletFromSeedCredentials(
name: name,
@@ -276,6 +286,12 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
password: password,
privateKey: options['private_key'] as String,
);
+ case WalletType.arbitrum:
+ return arbitrum!.createArbitrumRestoreWalletFromPrivateKey(
+ name: name,
+ password: password,
+ privateKey: options['private_key'] as String,
+ );
case WalletType.solana:
return solana!.createSolanaRestoreWalletFromPrivateKey(
name: name,
diff --git a/pubspec_base.yaml b/pubspec_base.yaml
index 510c3ad..dda4bea 100644
--- a/pubspec_base.yaml
+++ b/pubspec_base.yaml
@@ -237,6 +237,7 @@ flutter:
- assets/decred_node_list.yml
- assets/dogecoin_electrum_server_list.yml
- assets/base_node_list.yml
+ - assets/arbitrum_node_list.yml
- assets/text/
- assets/faq/
- assets/animation/
diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb
index a450aae..e0d6b6d 100644
--- a/res/values/strings_ar.arb
+++ b/res/values/strings_ar.arb
@@ -60,6 +60,7 @@
"approve": "ﺪﻤﺘﻌﻳ",
"approve_request": "الموافقة على الطلب",
"approve_tokens": "الموافقة على الرموز",
+ "arbiscan_history": "تاريخ أربيسكان",
"arrive_in_this_address": "سيصل ${currency} ${tag}إلى هذا العنوان",
"ascending": "تصاعدي",
"ask_each_time": "اسأل في كل مرة",
diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb
index aef0b0d..482a240 100644
--- a/res/values/strings_bg.arb
+++ b/res/values/strings_bg.arb
@@ -60,6 +60,7 @@
"approve": "Одобряване",
"approve_request": "Одобрете искане",
"approve_tokens": "Одобрете жетоните",
+ "arbiscan_history": "Арбисканска история",
"arrive_in_this_address": "${currency} ${tag}ще отидат на този адрес",
"ascending": "Възходящ",
"ask_each_time": "Питайте всеки път",
diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb
index 7e6940b..79331c2 100644
--- a/res/values/strings_cs.arb
+++ b/res/values/strings_cs.arb
@@ -60,6 +60,7 @@
"approve": "Schvalovat",
"approve_request": "Schválit žádost",
"approve_tokens": "Schválit tokeny",
+ "arbiscan_history": "Arbiscan History",
"arrive_in_this_address": "${currency} ${tag}přijde na tuto adresu",
"ascending": "Vzestupné",
"ask_each_time": "Zeptejte se pokaždé",
diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb
index 267ae56..49b4e25 100644
--- a/res/values/strings_de.arb
+++ b/res/values/strings_de.arb
@@ -60,6 +60,7 @@
"approve": "Genehmigen",
"approve_request": "Anfrage genehmigen",
"approve_tokens": "Token genehmigen",
+ "arbiscan_history": "ArbiScan-Geschichte",
"arrive_in_this_address": "${currency} ${tag} wird an dieser Adresse ankommen",
"ascending": "Aufsteigend",
"ask_each_time": "Jedes Mal fragen",
diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb
index cc9271d..c2769ea 100644
--- a/res/values/strings_en.arb
+++ b/res/values/strings_en.arb
@@ -60,6 +60,7 @@
"approve": "Approve",
"approve_request": "Approve Request",
"approve_tokens": "Approve tokens",
+ "arbiscan_history": "ArbiScan History",
"arrive_in_this_address": "${currency} ${tag}will arrive in this address",
"ascending": "Ascending",
"ask_each_time": "Ask each time",
diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb
index a50b8ca..f4b6291 100644
--- a/res/values/strings_es.arb
+++ b/res/values/strings_es.arb
@@ -58,9 +58,10 @@
"apk_update": "Actualización de APK",
"appearance": "Apariencia",
"approve": "Aprobar",
- "approve_request": "Aprobar Solicitud",
- "approve_tokens": "Autorizar tokens",
- "arrive_in_this_address": "${currency} ${tag} llegará a esta dirección",
+ "approve_request": "Aprobar la solicitud",
+ "approve_tokens": "Aprobar tokens",
+ "arbiscan_history": "Historia de ArbiScan",
+ "arrive_in_this_address": "${currency} ${tag}llegará a esta dirección",
"ascending": "Ascendente",
"ask_each_time": "Preguntar cada vez",
"auth_store_ban_timeout": "tiempo de espera del bloqueo",
diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb
index 13ca5e3..42d64b3 100644
--- a/res/values/strings_fr.arb
+++ b/res/values/strings_fr.arb
@@ -60,6 +60,7 @@
"approve": "Approuver",
"approve_request": "Approuver la demande",
"approve_tokens": "Approuver les jetons",
+ "arbiscan_history": "Historique d'ArbiScan",
"arrive_in_this_address": "${currency} ${tag}arrivera à cette adresse",
"ascending": "Ascendant",
"ask_each_time": "Demander à chaque fois",
diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb
index 7ff2956..ad20900 100644
--- a/res/values/strings_ha.arb
+++ b/res/values/strings_ha.arb
@@ -60,6 +60,7 @@
"approve": "Amincewa",
"approve_request": "Amince da bukata",
"approve_tokens": "Amince da Alamu",
+ "arbiscan_history": "Tarihin Arbiscan",
"arrive_in_this_address": "${currency} ${tag} zai je wurin wannan adireshi",
"ascending": "Hau",
"ask_each_time": "Tambaya kowane lokaci",
diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb
index f45d89a..967b2fe 100644
--- a/res/values/strings_hi.arb
+++ b/res/values/strings_hi.arb
@@ -60,6 +60,7 @@
"approve": "मंज़ूरी देना",
"approve_request": "अनुरोध को स्वीकृत करें",
"approve_tokens": "टोकन को मंजूरी देना",
+ "arbiscan_history": "अरबीस्कैन इतिहास",
"arrive_in_this_address": "${currency} ${tag}इस पते पर पहुंचेंगे",
"ascending": "आरोही",
"ask_each_time": "हर बार पूछें",
diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb
index 1cdc496..70a6c2d 100644
--- a/res/values/strings_hr.arb
+++ b/res/values/strings_hr.arb
@@ -60,6 +60,7 @@
"approve": "Odobriti",
"approve_request": "Odobriti zahtjev",
"approve_tokens": "Odobriti tokene",
+ "arbiscan_history": "Arbiskanska povijest",
"arrive_in_this_address": "${currency} ${tag}će stići na ovu adresu",
"ascending": "Uzlazni",
"ask_each_time": "Pitajte svaki put",
diff --git a/res/values/strings_hy.arb b/res/values/strings_hy.arb
index bc85d34..1372624 100644
--- a/res/values/strings_hy.arb
+++ b/res/values/strings_hy.arb
@@ -60,6 +60,7 @@
"approve": "Հաստատել",
"approve_request": "Հաստատում է հայցը",
"approve_tokens": "Հաստատում է նշանները",
+ "arbiscan_history": "Արբիսկանի պատմություն",
"arrive_in_this_address": "${currency} ${tag}կժամանի այս հասցեում",
"ascending": "Աճող",
"ask_each_time": "Հարցնել ամեն անգամ",
diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb
index 34bbf65..0fd485a 100644
--- a/res/values/strings_id.arb
+++ b/res/values/strings_id.arb
@@ -60,6 +60,7 @@
"approve": "Menyetujui",
"approve_request": "Menyetujui permintaan",
"approve_tokens": "Menyetujui token",
+ "arbiscan_history": "Sejarah ArbiScan",
"arrive_in_this_address": "${currency} ${tag} akan tiba di alamat ini",
"ascending": "Naik",
"ask_each_time": "Tanyakan setiap kali",
diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb
index 4b7819d..5880b7e 100644
--- a/res/values/strings_it.arb
+++ b/res/values/strings_it.arb
@@ -60,6 +60,7 @@
"approve": "Approvare",
"approve_request": "Approvare la richiesta",
"approve_tokens": "Approvare i token",
+ "arbiscan_history": "Storia dell'ArbiScan",
"arrive_in_this_address": "${currency} ${tag}arriverà a questo indirizzo",
"ascending": "Ascendente",
"ask_each_time": "Chiedi ogni volta",
diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb
index 7c0c338..9fa6303 100644
--- a/res/values/strings_ja.arb
+++ b/res/values/strings_ja.arb
@@ -60,6 +60,7 @@
"approve": "承認する",
"approve_request": "リクエストを承認します",
"approve_tokens": "トークンを承認します",
+ "arbiscan_history": "ArbiScan の歴史",
"arrive_in_this_address": "${currency} ${tag}はこの住所に到着します",
"ascending": "上昇",
"ask_each_time": "毎回尋ねてください",
diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb
index fce8f8f..a0445aa 100644
--- a/res/values/strings_ko.arb
+++ b/res/values/strings_ko.arb
@@ -60,6 +60,7 @@
"approve": "승인",
"approve_request": "요청 승인",
"approve_tokens": "토큰을 승인합니다",
+ "arbiscan_history": "ArbiScan 역사",
"arrive_in_this_address": "${currency} ${tag}이(가) 이 주소로 도착합니다",
"ascending": "오름차순",
"ask_each_time": "매번 묻기",
diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb
index 213fbcb..6f98ae8 100644
--- a/res/values/strings_my.arb
+++ b/res/values/strings_my.arb
@@ -60,6 +60,7 @@
"approve": "လက်မခံပါ။",
"approve_request": "တောင်းဆိုမှုကိုအတည်ပြု",
"approve_tokens": "တိုကင်အတည်ပြု",
+ "arbiscan_history": "arbiscan သမိုင်း",
"arrive_in_this_address": "${currency} ${tag}ဤလိပ်စာသို့ ရောက်ရှိပါမည်။",
"ascending": "တက်",
"ask_each_time": "တစ်ခုချင်းစီကိုအချိန်မေးပါ",
diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb
index 9c8025e..4777ba9 100644
--- a/res/values/strings_nl.arb
+++ b/res/values/strings_nl.arb
@@ -60,6 +60,7 @@
"approve": "Goedkeuren",
"approve_request": "Het verzoek goedkeuren",
"approve_tokens": "Tokens goedkeuren",
+ "arbiscan_history": "ArbiScan-geschiedenis",
"arrive_in_this_address": "${currency} ${tag}komt aan op dit adres",
"ascending": "Stijgend",
"ask_each_time": "Vraag het elke keer",
diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb
index 5d95b1e..6b0246a 100644
--- a/res/values/strings_pl.arb
+++ b/res/values/strings_pl.arb
@@ -57,16 +57,17 @@
"anonpay_description": "Wygeneruj ${type}. Odbiorca może ${method} przy użyciu dowolnej obsługiwanej kryptowaluty, a otrzymasz środki w tym portfelu.",
"apk_update": "Aktualizacja APK",
"appearance": "Wygląd",
- "approve": "Zatwierdź",
- "approve_request": "Zatwierdź żądanie",
- "approve_tokens": "Zatwierdź tokeny",
- "arrive_in_this_address": "${currency} ${tag} dotrze na ten adres",
- "ascending": "Rosnąco",
- "ask_each_time": "Pytaj za każdym razem",
- "auth_store_ban_timeout": "czas wygaśnięcia blokady",
- "auth_store_banned_for": "Zbanowany na ",
- "auth_store_banned_minutes": " minut",
- "auth_store_incorrect_password": "Nieprawidłowy PIN",
+ "approve": "Zatwierdzić",
+ "approve_request": "Zatwierdzić żądanie",
+ "approve_tokens": "Zatwierdzić tokeny",
+ "arbiscan_history": "Historia ArbiScanu",
+ "arrive_in_this_address": "${currency} ${tag}dotrze na ten adres",
+ "ascending": "Wznoszący się",
+ "ask_each_time": "Zapytaj za każdym razem",
+ "auth_store_ban_timeout": "przekroczenie limitu czasu",
+ "auth_store_banned_for": "Zablokowany na ",
+ "auth_store_banned_minutes": " minuty",
+ "auth_store_incorrect_password": "Niepoprawny PIN",
"authenticated": "Uwierzytelniony",
"authentication": "Uwierzytelnianie",
"auto_generate_addresses": "Automatyczne generowanie adresów",
diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb
index de4b1fb..334c332 100644
--- a/res/values/strings_pt.arb
+++ b/res/values/strings_pt.arb
@@ -60,6 +60,7 @@
"approve": "Aprovar",
"approve_request": "Aprovar solicitação",
"approve_tokens": "Aprovar tokens",
+ "arbiscan_history": "História do ArbiScan",
"arrive_in_this_address": "${currency} ${tag}chegará neste endereço",
"ascending": "Ascendente",
"ask_each_time": "Pergunte cada vez",
diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb
index 93ba682..5e38257 100644
--- a/res/values/strings_ru.arb
+++ b/res/values/strings_ru.arb
@@ -60,6 +60,7 @@
"approve": "Утвердить",
"approve_request": "Утвердить запрос",
"approve_tokens": "Одобрить токены",
+ "arbiscan_history": "История АрбиСкан",
"arrive_in_this_address": "${currency} ${tag}придет на этот адрес",
"ascending": "Восходящий",
"ask_each_time": "Спросите каждый раз",
diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb
index 0e32a15..4c35393 100644
--- a/res/values/strings_th.arb
+++ b/res/values/strings_th.arb
@@ -60,6 +60,7 @@
"approve": "อนุมัติ",
"approve_request": "อนุมัติคำขอ",
"approve_tokens": "อนุมัติโทเค็น",
+ "arbiscan_history": "ประวัติ ArbiScan",
"arrive_in_this_address": "${currency} ${tag}จะมาถึงที่อยู่นี้",
"ascending": "จากน้อยไปมาก",
"ask_each_time": "ถามทุกครั้ง",
diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb
index a5a0e9d..9719ae9 100644
--- a/res/values/strings_tl.arb
+++ b/res/values/strings_tl.arb
@@ -60,6 +60,7 @@
"approve": "Aprubahan",
"approve_request": "Aprubahan ang kahilingan",
"approve_tokens": "Aprubahan ang mga token",
+ "arbiscan_history": "Kasaysayan ng Arbiscan",
"arrive_in_this_address": "Ang ${currency} ${tag} ay darating sa address na ito",
"ascending": "Umakyat",
"ask_each_time": "Magtanong sa tuwing",
diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb
index be16e17..9b10fec 100644
--- a/res/values/strings_tr.arb
+++ b/res/values/strings_tr.arb
@@ -60,6 +60,7 @@
"approve": "Onaylamak",
"approve_request": "Talebi Onaylama",
"approve_tokens": "Jetonları onaylayın",
+ "arbiscan_history": "ArbiScan Geçmişi",
"arrive_in_this_address": "${currency} ${tag}bu adrese ulaşacak",
"ascending": "Yükselen",
"ask_each_time": "Her seferinde sor",
diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb
index ce4a0ee..42c4dfb 100644
--- a/res/values/strings_uk.arb
+++ b/res/values/strings_uk.arb
@@ -60,6 +60,7 @@
"approve": "Затвердити",
"approve_request": "Запитайте запит",
"approve_tokens": "Затвердити токени",
+ "arbiscan_history": "Арбісканська історія",
"arrive_in_this_address": "${currency} ${tag}надійде на цю адресу",
"ascending": "Висхід",
"ask_each_time": "Запитайте кожен раз",
diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb
index bad4b83..94189af 100644
--- a/res/values/strings_ur.arb
+++ b/res/values/strings_ur.arb
@@ -60,6 +60,7 @@
"approve": "ﻭﺮﮐ ﺭﻮﻈﻨﻣ",
"approve_request": "درخواست کو منظور کریں",
"approve_tokens": "ٹوکن کو منظور کریں",
+ "arbiscan_history": "اربیسکن تاریخ",
"arrive_in_this_address": "${currency} ${tag}اس پتے پر پہنچے گا۔",
"ascending": "چڑھنے",
"ask_each_time": "ہر بار پوچھیں",
diff --git a/res/values/strings_vi.arb b/res/values/strings_vi.arb
index 5c5ac55..0866577 100644
--- a/res/values/strings_vi.arb
+++ b/res/values/strings_vi.arb
@@ -60,6 +60,7 @@
"approve": "Phê duyệt",
"approve_request": "Phê duyệt yêu cầu",
"approve_tokens": "Phê duyệt mã thông báo",
+ "arbiscan_history": "Lịch sử quét Arbi",
"arrive_in_this_address": "${currency} ${tag} sẽ đến địa chỉ này",
"ascending": "Tăng dần",
"ask_each_time": "Hỏi mỗi lần",
diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb
index 76d0b3f..ae90c35 100644
--- a/res/values/strings_yo.arb
+++ b/res/values/strings_yo.arb
@@ -60,6 +60,7 @@
"approve": "Fi ọwọ si",
"approve_request": "IKILỌ RẸ",
"approve_tokens": "Ṣe fọwọsi awọn àmi",
+ "arbiscan_history": "Itan Arbiscan",
"arrive_in_this_address": "${currency} ${tag} máa dé sí àdírẹ́sì yìí",
"ascending": "Goke",
"ask_each_time": "Beere lọwọ kọọkan",
diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb
index 077148c..824f994 100644
--- a/res/values/strings_zh.arb
+++ b/res/values/strings_zh.arb
@@ -60,6 +60,7 @@
"approve": "批准",
"approve_request": "批准请求",
"approve_tokens": "批准令牌",
+ "arbiscan_history": "ArbiScan 历史",
"arrive_in_this_address": "${currency} ${tag}将到达此地址",
"ascending": "上升",
"ask_each_time": "每次问",
diff --git a/scripts/android/pubspec_gen.sh b/scripts/android/pubspec_gen.sh
index 66cd4d6..1d4dcad 100755
--- a/scripts/android/pubspec_gen.sh
+++ b/scripts/android/pubspec_gen.sh
@@ -10,7 +10,7 @@ case $APP_ANDROID_TYPE in
CONFIG_ARGS="--monero"
;;
$CAKEWALLET)
- CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred --dogecoin --base"
+ CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred --dogecoin --base --arbitrum"
;;
esac
diff --git a/scripts/ios/app_config.sh b/scripts/ios/app_config.sh
index f69c9f3..9a8ee84 100755
--- a/scripts/ios/app_config.sh
+++ b/scripts/ios/app_config.sh
@@ -31,7 +31,7 @@ case $APP_IOS_TYPE in
;;
$CAKEWALLET)
- CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred --dogecoin --base"
+ CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred --dogecoin --base --arbitrum"
;;
esac
diff --git a/scripts/linux/app_config.sh b/scripts/linux/app_config.sh
index 70f35d9..78f01fa 100755
--- a/scripts/linux/app_config.sh
+++ b/scripts/linux/app_config.sh
@@ -13,7 +13,7 @@ CONFIG_ARGS=""
case $APP_LINUX_TYPE in
$CAKEWALLET)
- CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --dogecoin --base --excludeFlutterSecureStorage";;
+ CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --dogecoin --base --arbitrum --excludeFlutterSecureStorage";;
esac
cp -rf pubspec_description.yaml pubspec.yaml
diff --git a/scripts/macos/app_config.sh b/scripts/macos/app_config.sh
index 0ae44f2..589b5ec 100755
--- a/scripts/macos/app_config.sh
+++ b/scripts/macos/app_config.sh
@@ -36,7 +36,7 @@ case $APP_MACOS_TYPE in
$MONERO_COM)
CONFIG_ARGS="--monero";;
$CAKEWALLET)
- CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --dogecoin --base";;
+ CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --dogecoin --base --arbitrum";;
esac
cp -rf pubspec_description.yaml pubspec.yaml
diff --git a/tool/configure.dart b/tool/configure.dart
index e6b2267..47b3379 100644
--- a/tool/configure.dart
+++ b/tool/configure.dart
@@ -13,6 +13,7 @@ const zanoOutputPath = 'lib/zano/zano.dart';
const decredOutputPath = 'lib/decred/decred.dart';
const dogecoinOutputPath = 'lib/dogecoin/dogecoin.dart';
const baseOutputPath = 'lib/base/base.dart';
+const arbitrumOutputPath = 'lib/arbitrum/arbitrum.dart';
const walletTypesPath = 'lib/wallet_types.g.dart';
const secureStoragePath = 'lib/core/secure_storage.dart';
const pubspecDefaultPath = 'pubspec_default.yaml';
@@ -34,6 +35,7 @@ Future<void> main(List<String> args) async {
final hasDecred = args.contains('${prefix}decred');
final hasDogecoin = args.contains('${prefix}dogecoin');
final hasBase = args.contains('${prefix}base');
+ final hasArbitrum = args.contains('${prefix}arbitrum');
final excludeFlutterSecureStorage = args.contains('${prefix}excludeFlutterSecureStorage');
await generateBitcoin(hasBitcoin);
@@ -50,6 +52,7 @@ Future<void> main(List<String> args) async {
await generateDecred(hasDecred);
await generateDogecoin(hasDogecoin);
await generateBase(hasBase);
+ await generateArbitrum(hasArbitrum);
await generatePubspec(
hasMonero: hasMonero,
@@ -67,6 +70,7 @@ Future<void> main(List<String> args) async {
hasDecred: hasDecred,
hasDogecoin: hasDogecoin,
hasBase: hasBase,
+ hasArbitrum: hasArbitrum,
);
await generateWalletTypes(
hasMonero: hasMonero,
@@ -83,6 +87,7 @@ Future<void> main(List<String> args) async {
hasDecred: hasDecred,
hasDogecoin: hasDogecoin,
hasBase: hasBase,
+ hasArbitrum: hasArbitrum,
);
await injectSecureStorage(!excludeFlutterSecureStorage);
}
@@ -795,6 +800,8 @@ abstract class Ethereum {
HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorConnect connect);
List<String> getDefaultTokenContractAddresses();
bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress);
+ String? getEthereumNativeEstimatedFee(WalletBase wallet);
+ String? getEthereumERC20EstimatedFee(WalletBase wallet);
}
""";
@@ -916,6 +923,8 @@ abstract class Polygon {
HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorConnect connect);
List<String> getDefaultTokenContractAddresses();
bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress);
+ String? getPolygonNativeEstimatedFee(WalletBase wallet);
+ String? getPolygonERC20EstimatedFee(WalletBase wallet);
}
""";
@@ -1491,9 +1500,7 @@ abstract class DogeCoin {
final output = '$dogecoinCommonHeaders\n' +
(hasImplementation ? '$dogecoinCWHeaders\n' : '\n') +
(hasImplementation ? '$dogecoinCwPart\n\n' : '\n') +
- (hasImplementation
- ? dogecoinCWDefinition
- : dogecoinEmptyDefinition) +
+ (hasImplementation ? dogecoinCWDefinition : dogecoinEmptyDefinition) +
'\n' +
dogecoinContent;
@@ -1612,6 +1619,8 @@ abstract class Base {
HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager);
List<String> getDefaultTokenContractAddresses();
bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress);
+ String? getBaseNativeEstimatedFee(WalletBase wallet);
+ String? getBaseERC20EstimatedFee(WalletBase wallet);
}
""";
@@ -1632,6 +1641,131 @@ abstract class Base {
await outputFile.writeAsString(output);
}
+Future<void> generateArbitrum(bool hasImplementation) async {
+ final outputFile = File(arbitrumOutputPath);
+ const arbitrumCommonHeaders = """
+import 'package:cake_wallet/view_model/send/output.dart';
+import 'package:cw_core/crypto_currency.dart';
+import 'package:cw_core/erc20_token.dart';
+import 'package:cw_core/hardware/hardware_account_data.dart';
+import 'package:cw_core/hardware/hardware_wallet_service.dart';
+import 'package:cw_core/output_info.dart';
+import 'package:cw_core/pending_transaction.dart';
+import 'package:cw_core/transaction_info.dart';
+import 'package:cw_core/transaction_priority.dart';
+import 'package:cw_core/wallet_base.dart';
+import 'package:cw_core/wallet_credentials.dart';
+import 'package:cw_core/wallet_info.dart';
+import 'package:cw_core/wallet_service.dart';
+import 'package:hive/hive.dart';
+import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
+import 'package:bitbox_flutter/bitbox_flutter.dart' as bitbox;
+import 'package:web3dart/web3dart.dart';
+
+""";
+ const arbitrumCWHeaders = """
+import 'package:cw_evm/evm_chain_formatter.dart';
+import 'package:cw_evm/evm_chain_mnemonics.dart';
+import 'package:cw_evm/evm_chain_transaction_credentials.dart';
+import 'package:cw_evm/evm_chain_transaction_info.dart';
+import 'package:cw_evm/evm_chain_transaction_priority.dart';
+import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
+import 'package:cw_evm/hardware/evm_chain_ledger_credentials.dart';
+import 'package:cw_evm/hardware/evm_chain_bitbox_credentials.dart';
+import 'package:cw_evm/evm_chain_wallet.dart';
+import 'package:cw_evm/hardware/evm_chain_bitbox_service.dart';
+import 'package:cw_evm/hardware/evm_chain_ledger_service.dart';
+
+import 'package:cw_arbitrum/arbitrum_client.dart';
+import 'package:cw_arbitrum/arbitrum_wallet.dart';
+import 'package:cw_arbitrum/arbitrum_wallet_service.dart';
+import 'package:cw_arbitrum/default_arbitrum_erc20_tokens.dart';
+import 'package:eth_sig_util/util/utils.dart';
+
+""";
+ const arbitrumCwPart = "part 'cw_arbitrum.dart';";
+ const arbitrumContent = """
+abstract class Arbitrum {
+ List<String> getArbitrumWordList(String language);
+ WalletService createArbitrumWalletService(bool isDirect);
+ WalletCredentials createArbitrumNewWalletCredentials(
+ {required String name,
+ WalletInfo? walletInfo,
+ String? password,
+ String? mnemonic,
+ String? passphrase});
+ WalletCredentials createArbitrumRestoreWalletFromSeedCredentials(
+ {required String name,
+ required String mnemonic,
+ required String password,
+ String? passphrase});
+ WalletCredentials createArbitrumRestoreWalletFromPrivateKey(
+ {required String name, required String privateKey, required String password});
+ WalletCredentials createArbitrumHardwareWalletCredentials(
+ {required String name, required HardwareAccountData hwAccountData, WalletInfo? walletInfo});
+ String getAddress(WalletBase wallet);
+ String getPrivateKey(WalletBase wallet);
+ String getPublicKey(WalletBase wallet);
+
+ Object createArbitrumTransactionCredentials(
+ List<Output> outputs, {
+ required CryptoCurrency currency,
+ int? feeRate,
+ });
+
+ Object createArbitrumTransactionCredentialsRaw(
+ List<OutputInfo> outputs, {
+ required CryptoCurrency currency,
+ required int feeRate,
+ });
+
+ int formatterArbitrumParseAmount(String amount);
+ double formatterArbitrumAmountToDouble(
+ {TransactionInfo? transaction, BigInt? amount, int exponent = 18});
+ List<Erc20Token> getERC20Currencies(WalletBase wallet);
+ Future<void> addErc20Token(WalletBase wallet, CryptoCurrency token);
+ Future<void> deleteErc20Token(WalletBase wallet, CryptoCurrency token);
+ Future<void> removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token);
+ Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress);
+
+ Future<PendingTransaction> createTokenApproval(WalletBase wallet, BigInt amount, String spender,
+ CryptoCurrency token);
+
+ CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction);
+ void updateArbitrumScanUsageState(WalletBase wallet, bool isEnabled);
+ Web3Client? getWeb3Client(WalletBase wallet);
+ String getTokenAddress(CryptoCurrency asset);
+
+ Future<void> setHardwareWalletService(WalletBase wallet, HardwareWalletService service);
+ HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection);
+ HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager);
+ List<String> getDefaultTokenContractAddresses();
+ bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress);
+ Future<bool> isApprovalRequired(WalletBase wallet, String tokenContract, String spender, BigInt requiredAmount);
+ Future<PendingTransaction> createRawCallDataTransaction(WalletBase wallet, String to, String dataHex, BigInt valueWei);
+ String? getArbitrumNativeEstimatedFee(WalletBase wallet);
+ String? getArbitrumERC20EstimatedFee(WalletBase wallet);
+}
+
+ """;
+
+ const arbitrumEmptyDefinition = 'Arbitrum? arbitrum;\n';
+ const arbitrumCWDefinition = 'Arbitrum? arbitrum = CWArbitrum();\n';
+
+ final output = '$arbitrumCommonHeaders\n' +
+ (hasImplementation ? '$arbitrumCWHeaders\n' : '\n') +
+ (hasImplementation ? '$arbitrumCwPart\n\n' : '\n') +
+ (hasImplementation ? arbitrumCWDefinition : arbitrumEmptyDefinition) +
+ '\n' +
+ arbitrumContent;
+
+ if (outputFile.existsSync()) {
+ await outputFile.delete();
+ }
+
+ await outputFile.writeAsString(output);
+}
+
Future<void> generatePubspec({
required bool hasMonero,
required bool hasBitcoin,
@@ -1648,6 +1782,7 @@ Future<void> generatePubspec({
required bool hasDecred,
required bool hasDogecoin,
required bool hasBase,
+ required bool hasArbitrum,
}) async {
const cwCore = """
cw_core:
@@ -1720,7 +1855,10 @@ Future<void> generatePubspec({
cw_base:
path: ./cw_base
""";
-
+ const cwArbitrum = """
+ cw_arbitrum:
+ path: ./cw_arbitrum
+ """;
final inputFile = File(pubspecOutputPath);
final inputText = await inputFile.readAsString();
final inputLines = inputText.split('\n');
@@ -1794,6 +1932,10 @@ Future<void> generatePubspec({
output += '\n$cwBase';
}
+ if (hasArbitrum) {
+ output += '\n$cwArbitrum';
+ }
+
final outputLines = output.split('\n');
inputLines.insertAll(dependenciesIndex + 1, outputLines);
final outputContent = inputLines.join('\n');
@@ -1821,6 +1963,7 @@ Future<void> generateWalletTypes({
required bool hasDecred,
required bool hasDogecoin,
required bool hasBase,
+ required bool hasArbitrum,
}) async {
final walletTypesFile = File(walletTypesPath);
@@ -1856,6 +1999,10 @@ Future<void> generateWalletTypes({
outputContent += '\tWalletType.base,\n';
}
+ if (hasArbitrum) {
+ outputContent += '\tWalletType.arbitrum,\n';
+ }
+
if (hasBitcoinCash) {
outputContent += '\tWalletType.bitcoinCash,\n';
}
diff --git a/tool/utils/secret_key.dart b/tool/utils/secret_key.dart
index f6e5018..c02c591 100644
--- a/tool/utils/secret_key.dart
+++ b/tool/utils/secret_key.dart
@@ -56,6 +56,7 @@ class SecretKey {
SecretKey('bitcoinCashTestWalletSeeds', () => ''),
SecretKey('polygonTestWalletSeeds', () => ''),
SecretKey('baseTestWalletSeeds', () => ''),
+ SecretKey('arbitrumTestWalletSeeds', () => ''),
SecretKey('solanaTestWalletSeeds', () => ''),
SecretKey('tronTestWalletSeeds', () => ''),
SecretKey('nanoTestWalletSeeds', () => ''),
@@ -67,6 +68,7 @@ class SecretKey {
SecretKey('bitcoinCashTestWalletReceiveAddress', () => ''),
SecretKey('polygonTestWalletReceiveAddress', () => ''),
SecretKey('baseTestWalletReceiveAddress', () => ''),
+ SecretKey('arbitrumTestWalletReceiveAddress', () => ''),
SecretKey('solanaTestWalletReceiveAddress', () => ''),
SecretKey('tronTestWalletReceiveAddress', () => ''),
SecretKey('nanoTestWalletReceiveAddress', () => ''),
Why this scored 34/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.