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

* feat: initialize cw_arbitrum package with essential files

- Implement ArbitrumClient for transaction handling
- Add ArbitrumWallet and related classes for wallet management
- Establish transaction history and info classes for Arbitrum transactions
- Add ArbitrumWalletService for wallet operations and management

* feat: Add arbitrum related secrets to workflow

* feat: Add nodes for arbitrum

* feat: Add Arbitrum support to cryptocurrency handling

* refactor: Update Arbitrum currency references from 'ARB' to 'ETH'

* feat: Add Arbitrum support to wallet type and transaction handling

* feat: Add Arbitrum URL schemes to Android and iOS configurations

* feat: Add Arbitrum SVG icons and a WebP image

* feat: Setup Arbitrum proxy

* fix: Add localization

* feat: Integrate Arbitrum support across various configurations and files

- Updated cakewallet.bat to include Arbitrum in app configuration.
- Added Arbitrum node list to pubspec_base.yaml.
- Modified Android, iOS, Linux, and macOS app configuration scripts to support Arbitrum.
- Introduced Arbitrum output path and generation logic in configure.dart.
- Enhanced wallet type and pubspec generation to include Arbitrum.
- Added Arbitrum-related secrets in secret_key.dart.

* feat: Enhance Arbitrum integration with new URI class and updates across various components

- Introduced ArbitrumURI class for handling Arbitrum payment URIs.
- Updated seed validator, wallet creation service, and other components to support Arbitrum.
- Added Arbitrum node handling in default settings migration and node list management.
- Enhanced preferences and settings store to include Arbitrum-specific configurations.
- Updated QR utility and token utilities to accommodate Arbitrum functionalities.

* feat: Add Arbitrum support to various buy providers

- Updated DFXBuyProvider, KryptonimBuyProvider, OnRamperBuyProvider, and RobinhoodBuyProvider to include Arbitrum wallet type and blockchain references.
- Enhanced integration for Arbitrum across multiple components to ensure consistent support.

* feat: Implement Arbitrum support in wallet selection and settings

- Added Arbitrum wallet type handling in common test flows and wallet services.
- Integrated Arbitrum icons in desktop wallet selection and menu widgets.
- Updated privacy settings to include ArbiScan options.
- Enhanced key service to support Arbitrum private and public key retrieval.

* feat: Expand Arbitrum support across various view models and components

- Integrated Arbitrum wallet type handling in advanced privacy settings, transaction details, and wallet management.
- Added Arbitrum-specific methods for transaction and wallet creation, restoration, and fee management.
- Updated dashboard and home settings to accommodate Arbitrum functionalities, including token management and transaction display.
- Enhanced exchange and send view models to support Arbitrum transactions and currency handling.
- Included Arbitrum in wallet address list and privacy settings for improved user experience.

* refactor: Simplify token initialization in Arbitrum wallet service

- Removed the migration flag from the addInitialTokens method in ArbitrumWalletService.
- Updated addInitialTokens method in ArbitrumWallet to handle existing tokens more efficiently.
- Enhanced initial token retrieval logic in DefaultArbitrumErc20Tokens to prioritize iconPath assignment.
- Improved token update logic in BaseWallet for consistency across wallet services.

* chore: Update Dart SDK constraints in pubspec.yaml

* fix: Update file paths for arbitrum assets

* fix: Correct box name formatting in Arbitrum wallet initialization

* Update .github/workflows/pr_test_build_android.yml [skip ci

* Update .github/workflows/pr_test_build_android.yml

* fix: Add missing evm params

* fix: Remove duplicate method declaration

* refactor: simplify isSwapsXyzSendingEVMTokenSwap check

* fix: Update arbitrum configs with sql storage update

* feat: Add estimated fee retrieval for Arbitrum and revamp priority fee handling for EVMChainWallet

* refactor: Update transaction priority handling and estimated fee calculations across EVMChain chains, also remove priority fee levels for Arbitrum

---------

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
David Adegoke
2025-11-06 00:30:23 +01:00
committed by GitHub
co-authored by Omar Hatem
parent 569ee04449
commit 2da3ba5730
129 changed files with 2123 additions and 319 deletions
@@ -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
@@ -21,6 +21,7 @@ jobs:
"cw_nano"
"cw_polygon"
"cw_base"
"cw_arbitrum"
"cw_solana"
"cw_tron"
"cw_wownero"
@@ -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
@@ -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
+1
View File
@@ -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
+1
View File
@@ -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)
+1
View File
@@ -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
@@ -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">
+12
View File
@@ -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
+27
View File
@@ -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>

After

Width:  |  Height:  |  Size: 2.7 KiB

+3
View File
@@ -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>

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

+2 -2
View File
@@ -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
+31
View File
@@ -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/
+10
View File
@@ -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
+3
View File
@@ -0,0 +1,3 @@
## 0.0.1
* TODO: Describe initial release.
+1
View File
@@ -0,0 +1 @@
TODO: Add your license here.
+39
View File
@@ -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.
+4
View File
@@ -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
@@ -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.';
}
+107
View File
@@ -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 [];
}
}
}
@@ -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);
}
@@ -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';
}
+179
View File
@@ -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,
);
}
}
@@ -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();
}
}
+5
View File
@@ -0,0 +1,5 @@
/// A Calculator.
class Calculator {
/// Returns [value] plus 1.
int addOne(int value) => value + 1;
}
@@ -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();
}
+73
View File
@@ -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
+12
View File
@@ -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);
});
}
+7 -3
View File
@@ -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
+2
View File
@@ -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) {
@@ -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:
+1
View File
@@ -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) =>
+2
View File
@@ -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:
+2
View File
@@ -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});
+14
View File
@@ -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;
}
+11 -6
View File
@@ -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),
+183 -151
View File
@@ -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;
String usdcContractAddress = _getUSDCContractAddress();
switch (_client.chainId) {
case 1:
usdcContractAddress = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48";
break;
case 137:
usdcContractAddress = "0x2791bca1f2de4661ed88a30c99a7a9449aa84174";
break;
case 8453:
usdcContractAddress = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913";
break;
default:
return true;
}
await _client.fetchERC20Balances(
_evmChainPrivateKey.address,
usdcContractAddress,
);
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 {
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 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) {
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');
}
final totalGasFee = estimatedGasUnits * maxFeePerGas;
return totalGasFee;
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 maxFeePerGas;
int adjustedGasPrice;
bool isPolygon = _client.chainId == 137;
if (gasBaseFee != null) {
// MaxFeePerGas with EIP1559;
maxFeePerGas = gasBaseFee! + priorityFee;
} else {
// MaxFeePerGas with gasPrice
maxFeePerGas = gasPrice + priorityFee;
int priorityFee = 0;
if (hasPriorityFee && priority != null) {
if (priority is EVMChainTransactionPriority) {
priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
}
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;
}
int totalPriorityFee = minPriorityFeeWei + additionalPriorityFee;
adjustedGasPrice = gasPrice + totalPriorityFee;
maxFeePerGas = gasPrice + totalPriorityFee;
}
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,
);
final totalGasFee = estimatedGas * adjustedGasPrice;
return GasParamsHandler(
estimatedGasUnits: estimatedGas,
estimatedGasFee: totalGasFee,
maxFeePerGas: maxFeePerGas,
gasPrice: adjustedGasPrice,
);
}
return GasParamsHandler.zero();
final gasBaseFee = await _client.getGasBaseFee();
final gasPrice = await _client.getGasUnitPrice();
int maxFeePerGas;
int adjustedGasPrice;
bool isPolygon = _client.chainId == 137;
if (gasBaseFee != null) {
// MaxFeePerGas with EIP1559;
maxFeePerGas = gasBaseFee + priorityFee;
} else {
// MaxFeePerGas with gasPrice
maxFeePerGas = gasPrice + priorityFee;
}
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;
}
int totalPriorityFee = minPriorityFeeWei + additionalPriorityFee;
adjustedGasPrice = gasPrice + totalPriorityFee;
maxFeePerGas = gasPrice + totalPriorityFee;
}
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,
);
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();
@@ -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:
+20
View File
@@ -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>
+244
View File
@@ -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;
}
+14 -5
View File
@@ -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;
}
+1
View File
@@ -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);
+2
View File
@@ -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,
};
@@ -324,6 +324,8 @@ class OnRamperBuyProvider extends BuyProvider {
switch (tag) {
case 'POL':
return 'POLYGON';
case 'ARB':
return 'ARBITRUM';
case 'ETH':
return 'ETHEREUM';
case 'TRX':
@@ -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:
@@ -162,6 +162,8 @@ class OpenCryptoPayService {
return "Ethereum";
case "POL":
return "Polygon";
case "ARB":
return "Arbitrum";
case "TRX":
return "Tron";
case "SOL":
+15
View File
@@ -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});
+3
View File
@@ -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 [];
+1
View File
@@ -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:
+3
View File
@@ -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:
@@ -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);
+6
View File
@@ -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;
}
+6 -1
View File
@@ -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);
@@ -115,6 +115,7 @@ class AddressResolver {
"pog",
"polygon",
"base",
"arbitrum"
"press",
"privacy",
"pro",
+2
View File
@@ -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';
+2 -1
View File
@@ -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:
+18 -8
View File
@@ -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;
}
@@ -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,
+1 -1
View File
@@ -260,7 +260,7 @@ Future<void> initializeAppConfigs({bool loadWallet = true}) async {
payjoinSessionSource: payjoinSessionSource,
anonpayInvoiceInfo: anonpayInvoiceInfo,
havenSeedStore: havenSeedStore,
initialMigrationVersion: 52,
initialMigrationVersion: 53,
);
}
+76 -58
View File
@@ -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;
}
+5
View File
@@ -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 =
@@ -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);
+12
View File
@@ -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:
+2
View File
@@ -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;
@@ -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;
}
@@ -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:
@@ -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);
}
+5 -6
View File
@@ -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();
},
);
},
@@ -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,
@@ -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:
+28
View File
@@ -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;
+2
View File
@@ -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:
+2 -1
View File
@@ -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;
}
@@ -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,
@@ -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;
@@ -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:
@@ -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;
}
}
@@ -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(
@@ -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:
@@ -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;
@@ -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:
@@ -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';
@@ -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,
+4 -1
View File
@@ -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 =>
+75 -31
View File
@@ -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:
@@ -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;
+54 -3
View File
@@ -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;
@@ -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;
@@ -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(
@@ -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';
}
@@ -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:
+8
View File
@@ -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,
@@ -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,
+1
View File
@@ -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/
+1
View File
@@ -60,6 +60,7 @@
"approve": "ﺪﻤﺘﻌﻳ",
"approve_request": "الموافقة على الطلب",
"approve_tokens": "الموافقة على الرموز",
"arbiscan_history": "تاريخ أربيسكان",
"arrive_in_this_address": "سيصل ${currency} ${tag}إلى هذا العنوان",
"ascending": "تصاعدي",
"ask_each_time": "اسأل في كل مرة",
+1
View File
@@ -60,6 +60,7 @@
"approve": "Одобряване",
"approve_request": "Одобрете искане",
"approve_tokens": "Одобрете жетоните",
"arbiscan_history": "Арбисканска история",
"arrive_in_this_address": "${currency} ${tag}ще отидат на този адрес",
"ascending": "Възходящ",
"ask_each_time": "Питайте всеки път",
+1
View File
@@ -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é",
+1
View File
@@ -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",
+1
View File
@@ -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",

Some files were not shown because too many files have changed in this diff Show More