CW-1250: Refactor L2 Chains Code (#2677)

* feat(evm): add ChainConfig model and related classes - Add ChainConfig class for immutable EVM chain configuration - Add ChainCapabilities class for chain feature flags - Add FeeType enum and FeeModel class for fee configuration * feat(evm): add EvmChainRegistry for centralized chain management - Add singleton EvmChainRegistry class with chain configuration storage - Initialize with Ethereum, Polygon, Base, and Arbitrum chains - Add mappings for WalletType, tag, and CAIP-2 to chainId lookups - Provide lookup methods for chain configurations by various identifiers - Support for querying available chains and registered chain IDs * feat(evm): add EVMChainClientFactory and consolidate chain clients - Create EVMChainClientFactory for creating chain-specific clients based on chainId - Move all chain clients (EthereumClient, PolygonClient, BaseClient, ArbitrumClient) into cw_evm/lib/clients/ folder - Refactor EVMChainClient from abstract to concrete class with default implementations - Add default implementations for fetchTransactions, fetchInternalTransactions, and prepareSignedTransactionForSending - Update all client classes to use super(chainId: X) constructor pattern - Factory returns EVMChainClient directly for unregistered chains instead of throwing - Remove dependencies on cw_ethereum, cw_polygon, cw_base, cw_arbitrum packages from pubspec.yaml - Consolidate all EVM chain client implementations in one location for easier maintenance * add ui for evm switcher on eth * add inputs for evm network list edit switches * refactor(evm): Make EVMChainWallet concrete and unify transaction classes - Make EVMChainWallet and EVMChainWalletBase concrete classes (removed abstract) - Implement all previously abstract methods in EVMChainWallet - Make EVMChainTransactionInfo and EVMChainTransactionHistory concrete - Reorganize default tokens into tokens/ folder: - Refactor EVMChainDefaultTokens to import and use token classes from tokens/ folder - Move clients to clients/ folder: - Move evm_chain_formatter.dart to utils - Update chain-specific transaction history classes to pass walletType to fromJson - Update old wallet classes (EthereumWallet, PolygonWallet, etc.) to pass walletType - Maintain compatibility with existing per-chain wallet classes This refactoring enables the unified EVMChainWallet architecture while maintaining full backward compatibility with existing per-chain wallet implementations. * feat(evm): Add chain selection capability to EVMChainWallet - Add selectedChainId observable field to track currently selected chain - Add selectChain(chainId) action method to switch between EVM chains - Add selectedChainConfig computed getter for accessing chain configuration - Initialize selectedChainId from client.chainId in constructor - Add _getClientForCurrentChain() helper method for future use This enables chain switching functionality while maintaining full backward compatibility. The selectedChainId is initialized from the existing client, and all existing methods continue to work unchanged. Future increments will update methods to use selectedChainId internally. * refactor(evm): Update client immediately on chain selection - Update selectChain() to immediately create new client for selected chain - Remove _getClientForCurrentChain() helper method - Simplify all methods to use _client directly instead of helper calls - Client is now always in sync with selectedChainId This improves code simplicity and performance by eliminating repeated client checks. The client is updated synchronously when selectChain() is called, ensuring state consistency. * featr(evm): Add unified evm proxy setup and fix chain initialization - Add unified Evm proxy - Add evm.dart to .gitignore for generated proxy - Add generateEVM() to configure.dart for proxy generation - Fix selectedChainId to initialize from registry based on walletInfo.type Ensures selectedChainId always matches wallet type on initialization and adds unified proxy infrastructure. * refactor(evm): Update backward compat helpers to use registry via proxy - Add registry helper methods to unified EVM proxy - Update all lib/ files to use proxy instead of direct cw_evm imports - Enforce proxy pattern: no direct imports from cw_evm in main app - Update configure.dart to include registry methods in generated proxy Maintains backward compatibility while centralizing chain data access through the unified proxy pattern. * feat(evm): Add chain selection UI to dashboard - Add chain selection methods to DashboardViewModel (isEVMWallet, availableChains, currentChain, selectChain) - Add chain dropdown widget to balance page for EVM wallets - Dropdown shows current chain and allows switching between all registered EVM chains - Only visible for EVM-compatible wallets - Add chain selection methods to EVM proxy (getAllChains, getCurrentChain, selectChain) Users can now switch between EVM chains (Ethereum, Polygon, Base, Arbitrum) directly from the dashboard without creating separate wallets. All chain-related code follows the established proxy pattern. * feat(evm): Add persistence for selected chain ID - Add initialChainId parameter to EVMChainWallet constructor - Save selectedChainId to wallet JSON in toJSON() method - Load selectedChainId from JSON in open() method - Handle backward compatibility: wallets without saved chain ID use wallet type's default chain ID - Client is created with saved chain ID when opening wallet Selected chain preference is now persisted across app sessions. Users will see their previously selected chain when reopening wallets. * chore: Minor cleanup * refactor(evm): Remove old per-chain packages, consolidate to unified cw_evm - Delete cw_ethereum, cw_polygon, cw_base, cw_arbitrum packages - Remove old proxy files (lib/ethereum/, lib/polygon/, lib/base/, lib/arbitrum/) - Update all view models to use unified evm! proxy - Move DEuro functionality to cw_evm/lib/deuro/ - Remove dependencies from pubspec.yaml and configure.dart - Remove walletType parameter from EVM proxy methods All EVM chains now use unified cw_evm package through single proxy interface. lib/ only depends on cw_evm, simplifying architecture and enabling easy addition of new L2 chains. BREAKING CHANGE: Old per-chain packages removed * refactor(evm): Update wallet handling to use unified EVM proxy - Replace individual chain imports (Ethereum, Polygon, Base, Arbitrum) with a single EVM proxy import. - Refactor wallet-related methods across various view models to utilize the new evm proxy for various usecases. This change enhances code maintainability and prepares the codebase for the addition of new EVM chains. * refactor(evm): Update model generator and enhance EVM classes - Modify model_generator.sh to remove previous chain folders from the loop. - Change EVMChainTransactionHistoryBase and EVMChainWalletBase classes to be abstract. - Update import paths in configure.dart to reflect the new structure of the cw_evm package. * add evm switcher icons to pubspec_base * change evm switcher to sliding animation * formatting fix * dpi agnostic width * feat(evm): Enhance chain handling and transaction history management - Introduce getCurrentChainId function to EVMChainTransactionHistoryBase for dynamic transaction history file naming based on the current chain ID. - Update EVMChainTransactionInfo to include chainId, ensuring accurate transaction data representation. - Refactor EVMChainWalletBase to support automatic node connection and transaction history loading upon chain selection. - Implement saveBackup and restoreWalletFilesFromBackup methods in EVMChainWalletService to manage wallet backups based on wallet type. - Update various methods across the codebase to accommodate the new chain handling logic, improving overall functionality and user experience. * chore: Cleanups and updates * feat: add WalletType.evm and deprecate individual EVM chain types Add unified WalletType.evm enum value for all EVM-compatible chains. Deprecate old individual EVM types (ethereum, polygon, base, arbitrum) with guidance to use WalletType.evm for new code. Update serialization/deserialization functions to support WalletType.evm: - serializeToInt() returns 18 for WalletType.evm - deserializeFromInt() handles case 18 - walletTypeToString() and walletTypeToDisplayName() return 'EVM' - cryptoCurrencyToWalletType() maps all EVM currencies to WalletType.evm Old EVM types remain functional for backward compatibility with existing wallets. This is the foundation for unifying EVM chain management. * feat: update EVM detection functions to support WalletType.evm Update all EVM detection and chain helper functions to support the unified WalletType.evm while maintaining backward compatibility with old EVM types. All functions require chainId parameter when used with WalletType.evm, ensuring proper chain-specific behavior. Old EVM types continue to work without changes for backward compatibility. * feat: add currency mapping support for unified EVM wallet type - Update currency mapping to support WalletType.evm with chainId-based currency resolution. - Override EVMChainWallet.currency getter to use selectedChainId from registry. - Replace walletTypeToCryptoCurrency calls with wallet.currency in view models. - Add WalletType.evm cases to switch statements and consolidate EVM transaction list item methods. - Add explorer URL method to EVM interface * feat: Update wallet operations with unified WalletType.evm - Update wallet service to always create new wallets and restores with WalletType.evm, defaulting to Ethereum. - Preserve old wallet types when opening existing wallets. Update wallet instance creation and opening logic to handle unified EVM type with chainId-based config lookup. * Add WalletType.evm support across all switch statements - Update all switch statements and conditionals to include WalletType.evm alongside existing EVM types. - Add chainId-based node selection support for unified EVM wallets. - Fix redundant code in exchange view model. - Ensure backward compatibility with old EVM wallet types. * feat(evm): Implement chainId-based node management for WalletType.evm - Add chainId-based node storage for unified EVM wallets, enabling separate node preferences per chain. - Replace unsafe dynamic casts with type-safe getSelectedChainId method. Update all node retrieval and switching logic to support chainId-based selection. * feat: Update UI components to show unified EVM wallet option - Filter out old EVM wallet types (ethereum, polygon, base, arbitrum) from wallet creation and restore UI, showing only unified WalletType.evm option. - Update wallet type generation to include WalletType.evm instead of individual chain types. - Update configure.dart to generate WalletType.evm in availableWalletTypes - Add filtering checks to prevent old EVM types from appearing * fix: Fix WalletType.evm support and complete remaining edge cases Add missing WalletType.evm cases to switch statements to resolve compilation errors. Complete remaining unification items: - Add WalletType.evm support to node, wallet utils, and view models - Update EVMChainDefaultTokens and EVMChainUtils to support chainId - Fix token initialization and priority fee calculations for WalletType.evm - Update payment view model to handle WalletType.evm with chainId All identified edge cases from the unification plan are now complete. * fix: Fix network switching issues for EVM wallets - Fix null check errors when creating and restoring an EVM wallet - Fix null check errors for balance getter when switching networks - Fix fiat amount display for native currencies in transaction history * refactor: Simplify EVM wallet chain selection in CryptoBalanceWidget - Removed the old dropdown for chain selection and replaced it with a more integrated EvmSwitcher dialog. - Updated EvmSwitcher to accept a list of available chains and handle chain selection more efficiently. - Enhanced EvmSwitcherDataItem to include chainId for better identification of chains. - Improved the overall UI flow for EVM wallet interactions. * refactor: Enhance EVM wallet handling and chainId integration - Simplified chainId checks across various components to ensure consistent handling for WalletType.evm. - Updated EVMChainDefaultTokens and EVMChainUtils checks to allow all EVM wallets for chainId operations - Handled backward compatibility for older wallet types in transaction info * refactor: More enhancement and backward compatibility fixes for EVM Wallet - Introduced a new method to retrieve cryptocurrency by chainId, improving the handling of WalletType.evm. - Updated transaction history management to filter transactions based on the current chainId - Refactored EVMChainTransactionInfo to accept chainId directly - Modified utility functions in EVMChainUtils and TokenUtilities for better chainId handling * refactor: Streamline EVM token retrieval and enhance chainId handling * feat: Introduce comprehensive guide for adding new EVM L2 networks - Added a detailed documentation file outlining the steps to integrate new EVM-compatible L2 networks into Cake Wallet. - Included prerequisites, architecture overview, and a step-by-step guide covering chain configuration, native currency addition, default tokens, and node setup. - Emphasized the unified architecture for EVM chains and backward compatibility considerations. - Removed the outdated guide on adding new L2 networks to streamline documentation. * feat: Reposition EVM switcher button on dashboard page * just adding reminders for the refactoring [skip ci] * more todos [skip ci] * feat: enhance wallet functionality with chainId support - Added chainId to `WalletBase` and updated currency method to use it. - Modified EvmChainRegistry to initialize registry on call. - Updated various components to pass chainId where necessary for currency conversions. - Improved handling of chain IDs in wallet-related view models and UI components for better compatibility with EVM networks. * fix: remove default chainId fallback in payment confirmation widget * feat: Adapt anypay to new unified flow WIP * fix: Issues with handling non evm swaps for anypay * fixes to flow structure * Add persistence to hidden chains on switcher * refactor: simplify equality operator in Erc20Token class * feat: add excluded arbutrum tokens for chainflip * chore: Remove print statement [skip ci] * refactor: dispaly QR image display for evm based on selected chain ID * feat: enhance EVM wallet compatibility check for raw address input * chore: Update doc to reflect changes [skip ci] * feat: Integrate Blink for EVM wallets for anti-sandwich attacks. - Add toggle in privacy settings to enable/disable - When enabled, route all evm broadcast rpc calls via Blink * feat: Integrate Blink for EVM wallets for anti-sandwich attacks. - Add toggle in privacy settings to enable/disable - When enabled, route all evm broadcast rpc calls via Blink * feat: Integrate Blink for EVM wallets for anti-sandwich attacks. - Add toggle in privacy settings to enable/disable - When enabled, route all evm broadcast rpc calls via Blink * feat: Handle same chainId scenario for pasted evm addresses * feat: Add Blink Setting to Advanced Settings Page * fix: Handle Blink supported chains * fix: Fixes to evm swap * fix: Add fallback for currency fetch * fix: Add evm to supported list * fix: Better handle balance errors * fix: Update token check to use contract address comparison, resolves the usdt/usdt0 issue * refactor: Simplify checks by cearing all tokens on chain switch, removing unnecessary currency checks * fix: Simplify checks based from feedback on review * fix: Exclude arbitrum from tx priority using chainId * refactor: Handle chains not having transaction priority within refactor * fix: Unified evm wallet fixes - Handle switching networks while on tx history page - Update NFTs when network is switching on listing page - Fix arbitrum tx priority error on swap screen - Switch check to display deuro from wallet type to chainId - Add fallback check for getting token info for evm - Better error message for max fee per gas less than block base fee error * refactor: Update wallet checks from type to chainId for EVM compatibility checks * refactor: Update eth chain utils and remove verified contract criteria in scam detection checks * refactor: Enhance validation for deuro calls * fix : Remove unneeded validation check for savings edit * feat: Hide EVM chain switcher behind feature flag * refactor: Remove EVM wallet type references and update related logic * chore: Clean up wallet type references and remove repeated imports * feat: Add fallback for derivation path and add arbitrum support to app scripts * fix: NFT popup error * fix: Transaction details fee regression * fix: Fixes and improvements - Add nonce parameter to transaction methods in EVM clients - Remove brackets for create swaps - Display contacts for selected wallettype alone - Remove tx priorty switching for arb * feat: Enhance Arbitrum support across swap providers * fix: Add missing secret keys * fix: display sending currency icon in send from external page * refactor: Handle arbitrum edgecase for wallet list display image * fix: Handle more cases of arbitrum image display for contact book * exploring possible issues with tx history * chore: switch to printV * fix: Add an extra check for keys * fix: Filter out eth spam transaction and modify parsing format * fix: Handle contract decimals for sending tokens * fix: Display proper balance error * Update cw_evm/lib/utils/evm_chain_formatter.dart [skip ci] --------- Co-authored-by: Robert Malikowski <malikowskirobert@gmail.com> Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

David Adegoke committed Jan 16, 2026 at 02:45 UTC 2a77d359f1732af665fb50c3ee8bfcbd59cd08f8
251 files changed +7306 -7761
.github/workflows/automated_integration_test.yml
-1
@@ -66,7 +66,6 @@ jobs:
66 echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
67 echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
68 echo "const nowNodesApiKey = '${{ secrets.EVM_NOWNODES_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
69 -
69 echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart
70 echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart
71 echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart
.github/workflows/pr_test_build_linux.yml
+1 -1
@@ -53,8 +53,8 @@ jobs:
53 echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
54 echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
55 echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
56 + echo "const blinkApiKey = '${{ secrets.BLINK_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
57 echo "const nowNodesApiKey = '${{ secrets.EVM_NOWNODES_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
57 -
58 echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart
59 echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart
60 echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart
.github/workflows/reusable-build.yml
+1 -1
@@ -68,7 +68,7 @@ jobs:
68 echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
69 echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
70 echo "const nowNodesApiKey = '${{ secrets.EVM_NOWNODES_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
71 -
71 + echo "const blinkApiKey = '${{ secrets.BLINK_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
72 echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart
73 echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart
74 echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart
.gitignore
+1
@@ -143,6 +143,7 @@ lib/decred/decred.dart
143 lib/dogecoin/dogecoin.dart
144 lib/base/base.dart
145 lib/arbitrum/arbitrum.dart
146 +lib/evm/evm.dart
147
148 ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
149 ios/Runner/Assets.xcassets/AppIcon.appiconset/*.png
assets/images/evm_switcher.svg new
+4
@@ -0,0 +1,4 @@
1 +<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
2 + <path d="M15 0C23.2843 0 30 6.71573 30 15C30 23.2843 23.2843 30 15 30C6.71586 29.9999 6.59743e-07 23.2842 0 15C0 6.71582 6.71586 0.000149433 15 0ZM21.2227 16.3311L14.9941 20.0166L8.76758 16.3428C8.74823 16.3314 8.72327 16.3351 8.70801 16.3516C8.69296 16.3681 8.69129 16.3928 8.7041 16.4111L14.9561 25.2158C14.9619 25.224 14.9706 25.2302 14.9805 25.2334C14.9897 25.2362 15.0001 25.2364 15.0098 25.2334C15.0196 25.2302 15.0284 25.2239 15.0342 25.2158L21.2861 16.3994C21.2989 16.3811 21.2973 16.3563 21.2822 16.3398C21.267 16.3234 21.242 16.3198 21.2227 16.3311ZM15.0078 4.74902C14.9901 4.74424 14.9719 4.75125 14.96 4.76465C14.959 4.76576 14.957 4.76644 14.9561 4.76758C14.9554 4.76847 14.9547 4.76957 14.9541 4.77051L8.70215 15.1523C8.69578 15.1631 8.69428 15.1763 8.69727 15.1885C8.70041 15.2007 8.70794 15.2113 8.71875 15.2178L14.9678 18.9043C14.9692 18.9054 14.9701 18.9073 14.9717 18.9082C14.9864 18.9163 15.005 18.9157 15.0195 18.9072L21.2715 15.2188C21.2822 15.2123 21.2908 15.2015 21.2939 15.1895C21.297 15.1773 21.2944 15.1641 21.2881 15.1533L15.0361 4.77148L15.0342 4.76953C15.0296 4.76259 15.0232 4.75772 15.0156 4.75391C15.0143 4.75324 15.0131 4.75159 15.0117 4.75098C15.0104 4.75049 15.0091 4.74946 15.0078 4.74902Z" fill="#91B0FF"/>
3 + <path d="M15 0C23.2843 0 30 6.71573 30 15C30 23.2843 23.2843 30 15 30C6.71586 29.9999 6.59743e-07 23.2842 0 15C0 6.71582 6.71586 0.000149433 15 0ZM21.2227 16.3311L14.9941 20.0166L8.76758 16.3428C8.74823 16.3314 8.72327 16.3351 8.70801 16.3516C8.69296 16.3681 8.69129 16.3928 8.7041 16.4111L14.9561 25.2158C14.9619 25.224 14.9706 25.2302 14.9805 25.2334C14.9897 25.2362 15.0001 25.2364 15.0098 25.2334C15.0196 25.2302 15.0284 25.2239 15.0342 25.2158L21.2861 16.3994C21.2989 16.3811 21.2973 16.3563 21.2822 16.3398C21.267 16.3234 21.242 16.3198 21.2227 16.3311ZM15.0078 4.74902C14.9901 4.74424 14.9719 4.75125 14.96 4.76465C14.959 4.76576 14.957 4.76644 14.9561 4.76758C14.9554 4.76847 14.9547 4.76957 14.9541 4.77051L8.70215 15.1523C8.69578 15.1631 8.69428 15.1763 8.69727 15.1885C8.70041 15.2007 8.70794 15.2113 8.71875 15.2178L14.9678 18.9043C14.9692 18.9054 14.9701 18.9073 14.9717 18.9082C14.9864 18.9163 15.005 18.9157 15.0195 18.9072L21.2715 15.2188C21.2822 15.2123 21.2908 15.2015 21.2939 15.1895C21.297 15.1773 21.2944 15.1641 21.2881 15.1533L15.0361 4.77148L15.0342 4.76953C15.0296 4.76259 15.0232 4.75772 15.0156 4.75391C15.0143 4.75324 15.0131 4.75159 15.0117 4.75098C15.0104 4.75049 15.0091 4.74946 15.0078 4.74902Z" fill="#91B0FF"/>
4 +</svg>
assets/images/evm_switcher_arrow_left.svg new
+3
@@ -0,0 +1,3 @@
1 +<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2 + <path d="M10.65 14.6666L11.8334 13.4833L6.35004 7.99992L11.8334 2.51659L10.65 1.33325L3.98337 7.99992L10.65 14.6666Z" fill="#91B0FF"/>
3 +</svg>
assets/images/evm_switcher_arrow_right.svg new
+3
@@ -0,0 +1,3 @@
1 +<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2 + <path d="M5.34996 14.6666L4.16663 13.4833L9.64996 7.99992L4.16663 2.51659L5.34996 1.33325L12.0166 7.99992L5.34996 14.6666Z" fill="#91B0FF"/>
3 +</svg>
assets/images/evm_switcher_checkmark.svg new
+10
@@ -0,0 +1,10 @@
1 +<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
2 + <g clip-path="url(#clip0_10958_56952)">
3 + <path d="M7.74 13.14L14.085 6.795L12.825 5.535L7.74 10.62L5.175 8.055L3.915 9.315L7.74 13.14ZM9 18C7.755 18 6.585 17.7638 5.49 17.2913C4.395 16.8188 3.4425 16.1775 2.6325 15.3675C1.8225 14.5575 1.18125 13.605 0.70875 12.51C0.23625 11.415 0 10.245 0 9C0 7.755 0.23625 6.585 0.70875 5.49C1.18125 4.395 1.8225 3.4425 2.6325 2.6325C3.4425 1.8225 4.395 1.18125 5.49 0.70875C6.585 0.23625 7.755 0 9 0C10.245 0 11.415 0.23625 12.51 0.70875C13.605 1.18125 14.5575 1.8225 15.3675 2.6325C16.1775 3.4425 16.8188 4.395 17.2913 5.49C17.7638 6.585 18 7.755 18 9C18 10.245 17.7638 11.415 17.2913 12.51C16.8188 13.605 16.1775 14.5575 15.3675 15.3675C14.5575 16.1775 13.605 16.8188 12.51 17.2913C11.415 17.7638 10.245 18 9 18Z" fill="#91B0FF"/>
4 + </g>
5 + <defs>
6 + <clipPath id="clip0_10958_56952">
7 + <rect width="18" height="18" fill="white"/>
8 + </clipPath>
9 + </defs>
10 +</svg>
assets/images/evm_switcher_icons/arbitrum.svg new
+20
@@ -0,0 +1,20 @@
1 +<svg width="15" height="16" viewBox="0 0 15 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2 + <g clip-path="url(#clip0_10958_57031)">
3 + <mask id="mask0_10958_57031" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="-1" y="0" width="16" height="16">
4 + <path d="M14.4978 0H-0.00219727V16H14.4978V0Z" fill="white"/>
5 + </mask>
6 + <g mask="url(#mask0_10958_57031)">
7 + <path d="M8.46295 9.21579L7.66965 11.3397C7.64625 11.3986 7.64625 11.4641 7.66965 11.523L9.03195 15.1786L10.6085 14.2884L8.71735 9.21579C8.6738 9.09799 8.50645 9.09799 8.46295 9.21579Z" fill="#D7E2F7"/>
8 + <path d="M10.0492 5.64206C10.0057 5.52426 9.8384 5.52426 9.7949 5.64206L9.0016 7.76601C8.97815 7.82496 8.97815 7.89041 9.0016 7.94931L11.2341 13.935L12.8107 13.0448L10.0492 5.64206Z" fill="#D7E2F7"/>
9 + <path d="M7.2478 0.991615C7.28795 0.991615 7.3248 1.00143 7.3616 1.02107L13.3732 4.41481C13.4435 4.45408 13.487 4.52608 13.487 4.60463V11.3921C13.487 11.4707 13.4435 11.5426 13.3732 11.5819L7.3616 14.9789C7.32815 14.9985 7.28795 15.0084 7.2478 15.0084C7.20765 15.0084 7.1708 14.9985 7.134 14.9789L1.1258 11.5852C1.05551 11.5459 1.012 11.4739 1.012 11.3954V4.60463C1.012 4.52608 1.05551 4.45408 1.1258 4.41481L7.13735 1.02107C7.1708 1.00143 7.211 0.991615 7.2478 0.991615ZM7.2478 0C7.0336 0 6.81935 0.055635 6.62855 0.163632L0.617033 3.55737C0.235453 3.77337 -0.00219727 4.17264 -0.00219727 4.60463V11.3921C-0.00219727 11.8241 0.235453 12.2267 0.617033 12.4426L6.62855 15.8364C6.81935 15.9444 7.0336 16 7.2478 16C7.462 16 7.67625 15.9444 7.86705 15.8364L13.8786 12.4426C14.2635 12.2267 14.4978 11.8274 14.4978 11.3921V4.60463C14.4978 4.17264 14.2602 3.7701 13.8786 3.5541L7.8704 0.163632C7.67625 0.055635 7.462 0 7.2478 0Z" fill="#D7E2F7"/>
10 + <path d="M3.27527 13.9416L3.82755 12.4624L4.93882 13.3657L3.90119 14.2951L3.27527 13.9416Z" fill="#D7E2F7"/>
11 + <path d="M6.74233 4.12036H5.21938C5.10558 4.12036 5.00178 4.18909 4.96497 4.29381L1.69812 13.0514L3.27465 13.9416L6.87288 4.29709C6.90298 4.212 6.83938 4.12036 6.74233 4.12036Z" fill="#D7E2F7"/>
12 + <path d="M9.40972 4.12036H7.88677C7.77297 4.12036 7.66917 4.18909 7.63237 4.29381L3.90027 14.295L5.47677 15.1852L9.53692 4.30036C9.57037 4.212 9.50347 4.12036 9.40972 4.12036Z" fill="#D7E2F7"/>
13 + </g>
14 + </g>
15 + <defs>
16 + <clipPath id="clip0_10958_57031">
17 + <rect width="14.5" height="16" fill="white"/>
18 + </clipPath>
19 + </defs>
20 +</svg>
assets/images/evm_switcher_icons/base.svg new
+10
@@ -0,0 +1,10 @@
1 +<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2 + <g clip-path="url(#clip0_10958_57048)">
3 + <path d="M7.986 15.9951C12.412 15.9951 16 12.4144 16 7.99754C16 3.58061 12.412 0 7.986 0C3.78691 0 0.342123 3.22294 0 7.32525H10.5926V8.66975H0C0.342123 12.7721 3.78691 15.9951 7.986 15.9951Z" fill="#D7E2F7"/>
4 + </g>
5 + <defs>
6 + <clipPath id="clip0_10958_57048">
7 + <rect width="16" height="16" fill="white"/>
8 + </clipPath>
9 + </defs>
10 +</svg>
assets/images/evm_switcher_icons/ethereum.svg new
+3
@@ -0,0 +1,3 @@
1 +<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2 + <path d="M12.8702 9.05127C12.8821 9.06418 12.8832 9.08379 12.8732 9.09814L7.99036 15.9819C7.9859 15.9888 7.9801 15.9948 7.9718 15.9976C7.95636 16.0024 7.93919 15.9971 7.92981 15.9839L3.047 9.10791C3.03691 9.09358 3.03808 9.07396 3.04993 9.06104C3.06172 9.04833 3.08079 9.04563 3.09583 9.0542L7.95911 11.9224L12.8243 9.04443C12.8394 9.03582 12.8584 9.03861 12.8702 9.05127ZM7.97375 0.00244141C7.98122 0.00535592 7.98798 0.0100481 7.99231 0.0170898L12.8751 8.12549C12.8802 8.13389 12.8813 8.14429 12.879 8.15381C12.8766 8.16331 12.8708 8.17216 12.8624 8.17725L7.97864 11.0581C7.96719 11.0646 7.953 11.0647 7.94153 11.0581L3.05872 8.17725C3.05028 8.17216 3.04355 8.16336 3.04114 8.15381C3.03885 8.14433 3.04004 8.13387 3.04504 8.12549L7.92786 0.0170898C7.92847 0.0160823 7.92911 0.0150907 7.92981 0.0141602C7.93158 0.011717 7.93532 0.0112365 7.93762 0.00927734C7.94096 0.00664239 7.94338 0.00287135 7.94739 0.00146484C7.94835 0.00110723 7.94932 0.000765826 7.95032 0.000488281H7.96985L7.97375 0.00244141Z" fill="#D7E2F7"/>
3 +</svg>
assets/images/evm_switcher_icons/gnosis.svg new
+18
@@ -0,0 +1,18 @@
1 +<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2 + <g clip-path="url(#clip0_10958_57056)">
3 + <mask id="mask0_10958_57056" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="16" height="16">
4 + <path d="M15.9701 0.0297852H0V15.9999H15.9701V0.0297852Z" fill="white"/>
5 + </mask>
6 + <g mask="url(#mask0_10958_57056)">
7 + <path d="M2.62806 7.0571C2.62078 6.59436 2.77125 6.14292 3.05473 5.7771L5.96459 8.68697C5.59783 8.96865 5.14704 9.1189 4.68459 9.11363C4.13987 9.11139 3.6181 8.89401 3.2329 8.50881C2.84771 8.12362 2.63032 7.60185 2.62806 7.0571Z" fill="#D7E2F7"/>
8 + <path d="M11.2851 9.11148C11.5576 9.11261 11.8276 9.05981 12.0795 8.95613C12.3315 8.85245 12.5604 8.69996 12.7531 8.5074C12.9459 8.31485 13.0986 8.08607 13.2026 7.83423C13.3065 7.58237 13.3596 7.31246 13.3587 7.04001C13.3638 6.57761 13.2136 6.12688 12.9321 5.76001L10.0137 8.67841C10.3764 8.96219 10.8246 9.11483 11.2851 9.11148Z" fill="#D7E2F7"/>
9 + <path d="M13.5359 5.1692C13.9845 5.69601 14.231 6.36528 14.2313 7.0572C14.2308 7.83538 13.9213 8.58147 13.3708 9.13153C12.8204 9.68156 12.0741 9.99053 11.2959 9.99053C10.6076 9.99132 9.94111 9.74876 9.41428 9.30573L8.00202 10.718L6.58975 9.30573C6.06256 9.74955 5.39515 9.99218 4.70602 9.99053C4.31982 9.99194 3.93712 9.91713 3.57989 9.77037C3.22265 9.6236 2.89789 9.40779 2.6242 9.1353C2.35052 8.86279 2.13328 8.53897 1.98496 8.18239C1.83665 7.82578 1.76016 7.44342 1.75988 7.0572C1.76044 6.36845 2.0028 5.70179 2.44468 5.17347L1.78548 4.51427L1.15616 3.87427C0.39805 5.12071 -0.00201957 6.55192 -0.000115304 8.0108C-0.000395751 9.0594 0.205975 10.0978 0.607189 11.0666C1.0084 12.0354 1.5966 12.9156 2.33817 13.657C3.07973 14.3983 3.96014 14.9863 4.92904 15.3873C5.89795 15.7882 6.93635 15.9943 7.98495 15.9937C10.1011 15.9932 12.1305 15.1529 13.6276 13.6574C15.1247 12.1618 15.9672 10.1333 15.97 8.0172C15.9832 6.55856 15.587 5.12547 14.8265 3.88067L13.5359 5.1692Z" fill="#D7E2F7"/>
10 + <path d="M13.7684 2.50445C13.0238 1.72155 12.1276 1.0984 11.1344 0.672961C10.1412 0.247521 9.07185 0.0286866 7.99138 0.0297893C6.91067 0.0292664 5.84115 0.248353 4.8477 0.67375C3.85425 1.09915 2.95759 1.72199 2.21218 2.50445C2.01805 2.71778 1.82819 2.93112 1.65112 3.15938L7.98498 9.49112L14.3188 3.15298C14.1516 2.92354 13.9677 2.70677 13.7684 2.50445ZM7.99138 8.01272L3.08471 3.10605C3.72623 2.45771 4.4906 1.94377 5.3331 1.59432C6.17559 1.24486 7.07929 1.06691 7.99138 1.07086C8.90363 1.06583 9.80766 1.2433 10.6503 1.59281C11.4929 1.94232 12.2572 2.45683 12.898 3.10605L7.99138 8.01272Z" fill="#D7E2F7"/>
11 + </g>
12 + </g>
13 + <defs>
14 + <clipPath id="clip0_10958_57056">
15 + <rect width="16" height="16" fill="white"/>
16 + </clipPath>
17 + </defs>
18 +</svg>
assets/images/evm_switcher_icons/polygon.svg new
+3
@@ -0,0 +1,3 @@
1 +<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2 + <path d="M11.2511 1.05078L7.04255 3.46781V11.0115L4.72052 12.3575L2.38428 11.0104V8.31716L4.72052 6.98309L6.22272 7.85424V5.67523L4.70742 4.81497L0.5 7.25927V12.0944L4.72161 14.5257L8.92904 12.0944V4.55187L11.2652 3.20471L13.6004 4.55187V7.23311L11.2652 8.59227L9.74999 7.71343V9.88154L11.2511 10.7473L15.5 8.33024V3.46781L11.2511 1.05078Z" fill="#D7E2F7"/>
3 +</svg>
cakewallet.bat
+1 -1
@@ -1,5 +1,5 @@
1 @echo off
2 -set cw_win_app_config=--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --dogecoin --base
2 +set cw_win_app_config=--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --dogecoin --base --arbitrum
3 set cw_root=%cd%
4 set cw_archive_name=Cake Wallet.zip
5 set cw_archive_path=%cw_root%\%cw_archive_name%
cw_arbitrum/.gitignore deleted
-31
@@ -1,31 +0,0 @@
1 -# Miscellaneous
2 -*.class
3 -*.log
4 -*.pyc
5 -*.swp
6 -.DS_Store
7 -.atom/
8 -.buildlog/
9 -.history
10 -.svn/
11 -migrate_working_dir/
12 -
13 -# IntelliJ related
14 -*.iml
15 -*.ipr
16 -*.iws
17 -.idea/
18 -
19 -# The .vscode folder contains launch configuration and tasks you configure in
20 -# VS Code which you may wish to be included in version control, so this line
21 -# is commented out by default.
22 -#.vscode/
23 -
24 -# Flutter/Dart/Pub related
25 -# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
26 -/pubspec.lock
27 -**/doc/api/
28 -.dart_tool/
29 -.flutter-plugins
30 -.flutter-plugins-dependencies
31 -build/
cw_arbitrum/.metadata deleted
-10
@@ -1,10 +0,0 @@
1 -# This file tracks properties of this Flutter project.
2 -# Used by Flutter tool to assess capabilities and perform upgrades etc.
3 -#
4 -# This file should be version controlled and should not be manually edited.
5 -
6 -version:
7 - revision: "fcf2c11572af6f390246c056bc905eca609533a0"
8 - channel: "[user-branch]"
9 -
10 -project_type: package
cw_arbitrum/CHANGELOG.md deleted
-3
@@ -1,3 +0,0 @@
1 -## 0.0.1
2 -
3 -* TODO: Describe initial release.
cw_arbitrum/LICENSE deleted
-1
@@ -1 +0,0 @@
1 -TODO: Add your license here.
cw_arbitrum/README.md deleted
-39
@@ -1,39 +0,0 @@
1 -<!--
2 -This README describes the package. If you publish this package to pub.dev,
3 -this README's contents appear on the landing page for your package.
4 -
5 -For information about how to write a good package README, see the guide for
6 -[writing package pages](https://dart.dev/tools/pub/writing-package-pages).
7 -
8 -For general information about developing packages, see the Dart guide for
9 -[creating packages](https://dart.dev/guides/libraries/create-packages)
10 -and the Flutter guide for
11 -[developing packages and plugins](https://flutter.dev/to/develop-packages).
12 --->
13 -
14 -TODO: Put a short description of the package here that helps potential users
15 -know whether this package might be useful for them.
16 -
17 -## Features
18 -
19 -TODO: List what your package can do. Maybe include images, gifs, or videos.
20 -
21 -## Getting started
22 -
23 -TODO: List prerequisites and provide or point to information on how to
24 -start using the package.
25 -
26 -## Usage
27 -
28 -TODO: Include short and useful examples for package users. Add longer examples
29 -to `/example` folder.
30 -
31 -```dart
32 -const like = 'sample';
33 -```
34 -
35 -## Additional information
36 -
37 -TODO: Tell users more about the package: where to find more information, how to
38 -contribute to the package, how to file issues, what response they can expect
39 -from the package authors, and more.
cw_arbitrum/analysis_options.yaml deleted
-4
@@ -1,4 +0,0 @@
1 -include: package:flutter_lints/flutter.yaml
2 -
3 -# Additional information about this file can be found at
4 -# https://dart.dev/guides/language/analysis-options
cw_arbitrum/lib/arbirtrum_mnemonics_exception.dart deleted
-5
@@ -1,5 +0,0 @@
1 -class ArbitrumMnemonicIsIncorrectException implements Exception {
2 - @override
3 - String toString() =>
4 - 'Arbitrum mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.';
5 -}
cw_arbitrum/lib/arbitrum_client.dart deleted
-107
@@ -1,107 +0,0 @@
1 -import 'dart:convert';
2 -
3 -import 'package:cw_evm/.secrets.g.dart' as secrets;
4 -import 'package:cw_evm/evm_chain_client.dart';
5 -import 'package:cw_evm/evm_chain_transaction_model.dart';
6 -import 'package:flutter/foundation.dart';
7 -import 'package:web3dart/web3dart.dart';
8 -
9 -class ArbitrumClient extends EVMChainClient {
10 - @override
11 - Transaction createTransaction({
12 - required EthereumAddress from,
13 - required EthereumAddress to,
14 - required EtherAmount amount,
15 - EtherAmount? maxPriorityFeePerGas,
16 - Uint8List? data,
17 - int? maxGas,
18 - EtherAmount? gasPrice,
19 - EtherAmount? maxFeePerGas,
20 - }) {
21 - EtherAmount? finalGasPrice = gasPrice;
22 -
23 - if (gasPrice == null && maxFeePerGas != null) {
24 - // If we have EIP-1559 parameters but no legacy gasPrice, then use maxFeePerGas as gasPrice
25 - finalGasPrice = maxFeePerGas;
26 - }
27 -
28 - return Transaction(
29 - from: from,
30 - to: to,
31 - value: amount,
32 - data: data,
33 - maxGas: maxGas,
34 - gasPrice: finalGasPrice,
35 - // maxFeePerGas: maxFeePerGas,
36 - // maxPriorityFeePerGas: maxPriorityFeePerGas,
37 - );
38 - }
39 -
40 - @override
41 - Uint8List prepareSignedTransactionForSending(Uint8List signedTransaction) => signedTransaction;
42 -
43 - @override
44 - int get chainId => 42161;
45 -
46 - @override
47 - Future<List<EVMChainTransactionModel>> fetchTransactions(
48 - String address, {
49 - String? contractAddress,
50 - }) async {
51 - try {
52 - final response = await client.get(
53 - Uri.https("api.etherscan.io", "/v2/api", {
54 - "chainid": "$chainId",
55 - "module": "account",
56 - "action": contractAddress != null ? "tokentx" : "txlist",
57 - if (contractAddress != null) "contractaddress": contractAddress,
58 - "address": address,
59 - "apikey": secrets.etherScanApiKey,
60 - }),
61 - );
62 -
63 - final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
64 -
65 - if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) {
66 - final res = (jsonResponse['result'] as List);
67 -
68 - res.removeWhere((e) => e['value'] == '0');
69 -
70 - return res
71 - .map((e) => EVMChainTransactionModel.fromJson(e as Map<String, dynamic>, 'ETH'))
72 - .toList();
73 - }
74 -
75 - return [];
76 - } catch (e) {
77 - return [];
78 - }
79 - }
80 -
81 - @override
82 - Future<List<EVMChainTransactionModel>> fetchInternalTransactions(String address) async {
83 - try {
84 - final response = await client.get(
85 - Uri.https("api.etherscan.io", "/v2/api", {
86 - "chainid": "$chainId",
87 - "module": "account",
88 - "action": "txlistinternal",
89 - "address": address,
90 - "apikey": secrets.etherScanApiKey,
91 - }),
92 - );
93 -
94 - final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
95 -
96 - if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) {
97 - return (jsonResponse['result'] as List)
98 - .map((e) => EVMChainTransactionModel.fromJson(e as Map<String, dynamic>, 'ETH'))
99 - .toList();
100 - }
101 -
102 - return [];
103 - } catch (_) {
104 - return [];
105 - }
106 - }
107 -}
cw_arbitrum/lib/arbitrum_transaction_history.dart deleted
-20
@@ -1,20 +0,0 @@
1 -import 'dart:core';
2 -
3 -import 'package:cw_evm/evm_chain_transaction_history.dart';
4 -import 'package:cw_evm/evm_chain_transaction_info.dart';
5 -import 'package:cw_arbitrum/arbitrum_transaction_info.dart';
6 -
7 -class ArbitrumTransactionHistory extends EVMChainTransactionHistory {
8 - ArbitrumTransactionHistory({
9 - required super.walletInfo,
10 - required super.password,
11 - required super.encryptionFileUtils,
12 - });
13 -
14 - @override
15 - String getTransactionHistoryFileName() => 'arbitrum_transactions.json';
16 -
17 - @override
18 - EVMChainTransactionInfo getTransactionInfo(Map<String, dynamic> val) =>
19 - ArbitrumTransactionInfo.fromJson(val);
20 -}
cw_arbitrum/lib/arbitrum_transaction_info.dart deleted
-41
@@ -1,41 +0,0 @@
1 -import 'package:cw_core/transaction_direction.dart';
2 -import 'package:cw_evm/evm_chain_transaction_info.dart';
3 -
4 -class ArbitrumTransactionInfo extends EVMChainTransactionInfo {
5 - ArbitrumTransactionInfo({
6 - required super.id,
7 - required super.height,
8 - required super.ethAmount,
9 - required super.ethFee,
10 - required super.tokenSymbol,
11 - required super.direction,
12 - required super.isPending,
13 - required super.date,
14 - required super.confirmations,
15 - required super.to,
16 - required super.from,
17 - super.contractAddress,
18 - super.exponent,
19 - });
20 -
21 - factory ArbitrumTransactionInfo.fromJson(Map<String, dynamic> data) {
22 - return ArbitrumTransactionInfo(
23 - id: data['id'] as String,
24 - height: data['height'] as int,
25 - ethAmount: BigInt.parse(data['amount']),
26 - exponent: data['exponent'] as int,
27 - ethFee: BigInt.parse(data['fee']),
28 - direction: parseTransactionDirectionFromInt(data['direction'] as int),
29 - date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
30 - isPending: data['isPending'] as bool,
31 - confirmations: data['confirmations'] as int,
32 - tokenSymbol: data['tokenSymbol'] as String,
33 - to: data['to'],
34 - from: data['from'],
35 - contractAddress: data['contractAddress'],
36 - );
37 - }
38 -
39 - @override
40 - String get feeCurrency => 'ETH';
41 -}
cw_arbitrum/lib/arbitrum_wallet.dart deleted
-183
@@ -1,183 +0,0 @@
1 -import 'dart:convert';
2 -
3 -import 'package:cw_arbitrum/arbitrum_client.dart';
4 -import 'package:cw_arbitrum/arbitrum_transaction_history.dart';
5 -import 'package:cw_arbitrum/arbitrum_transaction_info.dart';
6 -import 'package:cw_arbitrum/default_arbitrum_erc20_tokens.dart';
7 -import 'package:cw_core/cake_hive.dart';
8 -import 'package:cw_core/crypto_currency.dart';
9 -import 'package:cw_core/encryption_file_utils.dart';
10 -import 'package:cw_core/erc20_token.dart';
11 -import 'package:cw_core/pathForWallet.dart';
12 -import 'package:cw_core/transaction_direction.dart';
13 -import 'package:cw_core/wallet_info.dart';
14 -import 'package:cw_core/wallet_keys_file.dart';
15 -import 'package:cw_evm/evm_chain_transaction_history.dart';
16 -import 'package:cw_evm/evm_chain_transaction_info.dart';
17 -import 'package:cw_evm/evm_chain_transaction_model.dart';
18 -import 'package:cw_evm/evm_chain_transaction_priority.dart';
19 -import 'package:cw_evm/evm_chain_wallet.dart';
20 -import 'package:cw_evm/evm_erc20_balance.dart';
21 -
22 -class ArbitrumWallet extends EVMChainWallet {
23 - ArbitrumWallet({
24 - required super.walletInfo,
25 - required super.password,
26 - required super.derivationInfo,
27 - super.mnemonic,
28 - super.initialBalance,
29 - super.privateKey,
30 - required super.client,
31 - required super.encryptionFileUtils,
32 - super.passphrase,
33 - }) : super(nativeCurrency: CryptoCurrency.arbEth);
34 -
35 - @override
36 - bool get hasPriorityFee => false;
37 -
38 - @override
39 - int getTotalPriorityFee(EVMChainTransactionPriority priority) => 0;
40 -
41 - @override
42 - Future<void> initErc20TokensBox() async {
43 - final boxName = "${walletInfo.name.replaceAll(" ", "_")}_${Erc20Token.arbitrumBoxName}";
44 -
45 - evmChainErc20TokensBox = await CakeHive.openBox<Erc20Token>(boxName);
46 - }
47 -
48 - @override
49 - void addInitialTokens() {
50 - final initialErc20Tokens = DefaultArbitrumErc20Tokens().initialArbitrumErc20Tokens;
51 -
52 - for (final token in initialErc20Tokens) {
53 - if (!evmChainErc20TokensBox.containsKey(token.contractAddress)) {
54 - evmChainErc20TokensBox.put(token.contractAddress, token);
55 - } else {
56 - // update existing token
57 - final existingToken = evmChainErc20TokensBox.get(token.contractAddress);
58 - evmChainErc20TokensBox.put(
59 - token.contractAddress,
60 - Erc20Token.copyWith(token, enabled: existingToken!.enabled),
61 - );
62 - }
63 - }
64 - }
65 -
66 - @override
67 - List<String> get getDefaultTokenContractAddresses => DefaultArbitrumErc20Tokens()
68 - .initialArbitrumErc20Tokens
69 - .map((e) => e.contractAddress)
70 - .toList();
71 -
72 - @override
73 - Future<bool> checkIfScanProviderIsEnabled() async {
74 - return (await sharedPrefs.future).getBool("use_arbiscan") ?? true;
75 - }
76 -
77 - @override
78 - String getTransactionHistoryFileName() => 'arbitrum_transactions.json';
79 -
80 - @override
81 - Erc20Token createNewErc20TokenObject(Erc20Token token, String? iconPath) {
82 - return Erc20Token(
83 - name: token.name,
84 - symbol: token.symbol,
85 - contractAddress: token.contractAddress,
86 - decimal: token.decimal,
87 - enabled: token.enabled,
88 - tag: token.tag ?? 'ETH',
89 - iconPath: iconPath,
90 - isPotentialScam: token.isPotentialScam,
91 - );
92 - }
93 -
94 - @override
95 - EVMChainTransactionInfo getTransactionInfo(
96 - EVMChainTransactionModel transactionModel,
97 - String address,
98 - ) {
99 - final model = ArbitrumTransactionInfo(
100 - id: transactionModel.hash,
101 - height: transactionModel.blockNumber,
102 - ethAmount: transactionModel.amount,
103 - direction: transactionModel.from == address
104 - ? TransactionDirection.outgoing
105 - : TransactionDirection.incoming,
106 - isPending: false,
107 - date: transactionModel.date,
108 - confirmations: transactionModel.confirmations,
109 - ethFee: BigInt.from(transactionModel.gasUsed) * transactionModel.gasPrice,
110 - exponent: transactionModel.tokenDecimal ?? 18,
111 - tokenSymbol: transactionModel.tokenSymbol ?? "ETH",
112 - to: transactionModel.to,
113 - from: transactionModel.from,
114 - contractAddress: transactionModel.contractAddress,
115 - );
116 - return model;
117 - }
118 -
119 - @override
120 - EVMChainTransactionHistory setUpTransactionHistory(
121 - WalletInfo walletInfo,
122 - String password,
123 - EncryptionFileUtils encryptionFileUtils,
124 - ) {
125 - return ArbitrumTransactionHistory(
126 - walletInfo: walletInfo,
127 - password: password,
128 - encryptionFileUtils: encryptionFileUtils,
129 - );
130 - }
131 -
132 - static Future<ArbitrumWallet> open({
133 - required String name,
134 - required String password,
135 - required WalletInfo walletInfo,
136 - required EncryptionFileUtils encryptionFileUtils,
137 - }) async {
138 - final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
139 - final path = await pathForWallet(name: name, type: walletInfo.type);
140 -
141 - Map<String, dynamic>? data;
142 - try {
143 - final jsonSource = await encryptionFileUtils.read(path: path, password: password);
144 -
145 - data = json.decode(jsonSource) as Map<String, dynamic>;
146 - } catch (e) {
147 - if (!hasKeysFile) rethrow;
148 - }
149 -
150 - final balance =
151 - EVMChainERC20Balance.fromJSON(data?['balance'] as String?) ??
152 - EVMChainERC20Balance(BigInt.zero);
153 -
154 - final WalletKeysData keysData;
155 - // Migrate wallet from the old scheme to then new .keys file scheme
156 - if (!hasKeysFile) {
157 - final mnemonic = data!['mnemonic'] as String?;
158 - final privateKey = data['private_key'] as String?;
159 - final passphrase = data['passphrase'] as String?;
160 -
161 - keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey, passphrase: passphrase);
162 - } else {
163 - keysData = await WalletKeysFile.readKeysFile(
164 - name,
165 - walletInfo.type,
166 - password,
167 - encryptionFileUtils,
168 - );
169 - }
170 -
171 - return ArbitrumWallet(
172 - walletInfo: walletInfo,
173 - derivationInfo: await walletInfo.getDerivationInfo(),
174 - password: password,
175 - mnemonic: keysData.mnemonic,
176 - privateKey: keysData.privateKey,
177 - passphrase: keysData.passphrase,
178 - initialBalance: balance,
179 - client: ArbitrumClient(),
180 - encryptionFileUtils: encryptionFileUtils,
181 - );
182 - }
183 -}
cw_arbitrum/lib/arbitrum_wallet_service.dart deleted
-174
@@ -1,174 +0,0 @@
1 -import 'package:bip39/bip39.dart' as bip39;
2 -import 'package:cw_arbitrum/arbirtrum_mnemonics_exception.dart';
3 -import 'package:cw_core/encryption_file_utils.dart';
4 -import 'package:cw_core/wallet_base.dart';
5 -import 'package:cw_core/wallet_info.dart';
6 -import 'package:cw_core/wallet_type.dart';
7 -import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
8 -import 'package:cw_evm/evm_chain_wallet_service.dart';
9 -import 'package:cw_arbitrum/arbitrum_wallet.dart';
10 -import 'package:cw_arbitrum/arbitrum_client.dart';
11 -
12 -class ArbitrumWalletService extends EVMChainWalletService<ArbitrumWallet> {
13 - ArbitrumWalletService(super.isDirect, {required this.client});
14 -
15 - late ArbitrumClient client;
16 -
17 - @override
18 - WalletType getType() => WalletType.arbitrum;
19 -
20 - @override
21 - Future<ArbitrumWallet> create(EVMChainNewWalletCredentials credentials, {bool? isTestnet}) async {
22 - final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
23 -
24 - final mnemonic = credentials.mnemonic ?? bip39.generateMnemonic(strength: strength);
25 -
26 - final wallet = ArbitrumWallet(
27 - walletInfo: credentials.walletInfo!,
28 - derivationInfo: await credentials.walletInfo!.getDerivationInfo(),
29 - mnemonic: mnemonic,
30 - password: credentials.password!,
31 - passphrase: credentials.passphrase,
32 - client: client,
33 - encryptionFileUtils: encryptionFileUtilsFor(isDirect),
34 - );
35 -
36 - await wallet.init();
37 - wallet.addInitialTokens();
38 - await wallet.save();
39 - return wallet;
40 - }
41 -
42 - @override
43 - Future<ArbitrumWallet> openWallet(String name, String password) async {
44 - final walletInfo = await WalletInfo.get(name, getType());
45 - if (walletInfo == null) {
46 - throw Exception('Wallet not found');
47 - }
48 -
49 - try {
50 - final wallet = await ArbitrumWallet.open(
51 - name: name,
52 - password: password,
53 - walletInfo: walletInfo,
54 - encryptionFileUtils: encryptionFileUtilsFor(isDirect),
55 - );
56 -
57 - await wallet.init();
58 - wallet.addInitialTokens();
59 - await wallet.save();
60 - saveBackup(name);
61 - return wallet;
62 - } catch (_) {
63 - await restoreWalletFilesFromBackup(name);
64 -
65 - final wallet = await ArbitrumWallet.open(
66 - name: name,
67 - password: password,
68 - walletInfo: walletInfo,
69 - encryptionFileUtils: encryptionFileUtilsFor(isDirect),
70 - );
71 -
72 - await wallet.init();
73 - wallet.addInitialTokens();
74 - await wallet.save();
75 - return wallet;
76 - }
77 - }
78 -
79 - @override
80 - Future<ArbitrumWallet> restoreFromKeys(
81 - EVMChainRestoreWalletFromPrivateKey credentials, {
82 - bool? isTestnet,
83 - }) async {
84 - final wallet = ArbitrumWallet(
85 - password: credentials.password!,
86 - privateKey: credentials.privateKey,
87 - walletInfo: credentials.walletInfo!,
88 - derivationInfo: await credentials.walletInfo!.getDerivationInfo(),
89 - client: client,
90 - encryptionFileUtils: encryptionFileUtilsFor(isDirect),
91 - );
92 -
93 - await wallet.init();
94 - wallet.addInitialTokens();
95 - await wallet.save();
96 - return wallet;
97 - }
98 -
99 - @override
100 - Future<ArbitrumWallet> restoreFromHardwareWallet(
101 - EVMChainRestoreWalletFromHardware credentials,
102 - ) async {
103 - final derivationInfo = await credentials.walletInfo!.getDerivationInfo();
104 - derivationInfo.derivationType = DerivationType.bip39;
105 - derivationInfo.derivationPath = "m/44'/60'/${credentials.hwAccountData.accountIndex}'/0/0";
106 - await derivationInfo.save();
107 - credentials.walletInfo!.hardwareWalletType = credentials.hardwareWalletType;
108 - credentials.walletInfo!.address = credentials.hwAccountData.address;
109 - await credentials.walletInfo!.save();
110 -
111 - final wallet = ArbitrumWallet(
112 - walletInfo: credentials.walletInfo!,
113 - derivationInfo: derivationInfo,
114 - password: credentials.password!,
115 - client: client,
116 - encryptionFileUtils: encryptionFileUtilsFor(isDirect),
117 - );
118 -
119 - await wallet.init();
120 - wallet.addInitialTokens();
121 - await wallet.save();
122 -
123 - return wallet;
124 - }
125 -
126 - @override
127 - Future<ArbitrumWallet> restoreFromSeed(
128 - EVMChainRestoreWalletFromSeedCredentials credentials, {
129 - bool? isTestnet,
130 - }) async {
131 - if (!bip39.validateMnemonic(credentials.mnemonic)) {
132 - throw ArbitrumMnemonicIsIncorrectException();
133 - }
134 -
135 - final wallet = ArbitrumWallet(
136 - password: credentials.password!,
137 - mnemonic: credentials.mnemonic,
138 - walletInfo: credentials.walletInfo!,
139 - derivationInfo: await credentials.walletInfo!.getDerivationInfo(),
140 - passphrase: credentials.passphrase,
141 - client: client,
142 - encryptionFileUtils: encryptionFileUtilsFor(isDirect),
143 - );
144 -
145 - await wallet.init();
146 - wallet.addInitialTokens();
147 - await wallet.save();
148 -
149 - return wallet;
150 - }
151 -
152 - @override
153 - Future<void> rename(String currentName, String password, String newName) async {
154 - final currentWalletInfo = await WalletInfo.get(currentName, getType());
155 - if (currentWalletInfo == null) {
156 - throw Exception('Wallet not found');
157 - }
158 - final currentWallet = await ArbitrumWallet.open(
159 - password: password,
160 - name: currentName,
161 - walletInfo: currentWalletInfo,
162 - encryptionFileUtils: encryptionFileUtilsFor(isDirect),
163 - );
164 -
165 - await currentWallet.renameWalletFiles(newName);
166 - await saveBackup(newName);
167 -
168 - final newWalletInfo = currentWalletInfo;
169 - newWalletInfo.id = WalletBase.idFor(newName, getType());
170 - newWalletInfo.name = newName;
171 -
172 - newWalletInfo.save();
173 - }
174 -}
cw_arbitrum/lib/cw_arbitrum.dart deleted
-5
@@ -1,5 +0,0 @@
1 -/// A Calculator.
2 -class Calculator {
3 - /// Returns [value] plus 1.
4 - int addOne(int value) => value + 1;
5 -}
cw_arbitrum/lib/default_arbitrum_erc20_tokens.dart deleted
-71
@@ -1,71 +0,0 @@
1 -import 'package:cw_core/crypto_currency.dart';
2 -import 'package:cw_core/erc20_token.dart';
3 -
4 -class DefaultArbitrumErc20Tokens {
5 - final List<Erc20Token> _defaultTokens = [
6 - Erc20Token(
7 - name: "USD Coin",
8 - symbol: "USDC",
9 - contractAddress: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
10 - decimal: 6,
11 - enabled: true,
12 - ),
13 - Erc20Token(
14 - name: "USDC.e",
15 - symbol: "USDC.e",
16 - contractAddress: "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8",
17 - decimal: 6,
18 - enabled: true,
19 - ),
20 - Erc20Token(
21 - name: "Wrapped BTC",
22 - symbol: "WBTC",
23 - contractAddress: "0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f",
24 - decimal: 8,
25 - enabled: true,
26 - ),
27 - Erc20Token(
28 - name: "Chainlink Token",
29 - symbol: "LINK",
30 - contractAddress: "0xf97f4df75117a78c1A5a0DBb814Af92458539FB4",
31 - decimal: 18,
32 - enabled: true,
33 - ),
34 - Erc20Token(
35 - name: "Wrapped liquid staked Ether 2.0",
36 - symbol: "wstETH",
37 - contractAddress: "0x0fBcbaEA96Ce0cF7Ee00A8c19c3ab6f5Dc8E1921",
38 - decimal: 18,
39 - enabled: false,
40 - ),
41 - Erc20Token(
42 - name: "Wrapped Ether",
43 - symbol: "WETH",
44 - contractAddress: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
45 - decimal: 18,
46 - enabled: false,
47 - ),
48 - Erc20Token(
49 - name: "DAI",
50 - symbol: "DAI",
51 - contractAddress: "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1",
52 - decimal: 18,
53 - enabled: false,
54 - ),
55 - ];
56 -
57 - List<Erc20Token> get initialArbitrumErc20Tokens => _defaultTokens.map((token) {
58 - String? iconPath;
59 - if (token.iconPath?.isEmpty ?? true) {
60 - try {
61 - iconPath = CryptoCurrency.all
62 - .firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase())
63 - .iconPath;
64 - } catch (_) {}
65 - } else {
66 - iconPath = token.iconPath;
67 - }
68 -
69 - return Erc20Token.copyWith(token, icon: iconPath, tag: 'ARB');
70 - }).toList();
71 -}
cw_arbitrum/pubspec.yaml deleted
-73
@@ -1,73 +0,0 @@
1 -name: cw_arbitrum
2 -description: "A new Flutter package project."
3 -version: 0.0.1
4 -publish_to: none
5 -homepage: https://cakewallet.com
6 -
7 -environment:
8 - sdk: '>=3.0.6 <4.0.0'
9 - flutter: ">=1.17.0"
10 -
11 -dependencies:
12 - flutter:
13 - sdk: flutter
14 - cw_core:
15 - path: ../cw_core
16 - cw_ethereum:
17 - path: ../cw_ethereum
18 - cw_evm:
19 - path: ../cw_evm
20 - web3dart: ^2.7.1
21 - hive: ^2.2.3
22 - bip39: ^1.0.6
23 - collection: ^1.17.1
24 -
25 -dependency_overrides:
26 - web3dart:
27 - git:
28 - url: https://github.com/cake-tech/web3dart.git
29 - ref: cake
30 - watcher: ^1.1.0
31 -
32 -dev_dependencies:
33 - flutter_test:
34 - sdk: flutter
35 - flutter_lints: ^2.0.0
36 - build_runner: ^2.4.15
37 -
38 -# For information on the generic Dart part of this file, see the
39 -# following page: https://dart.dev/tools/pub/pubspec
40 -
41 -# The following section is specific to Flutter packages.
42 -flutter:
43 -
44 - # To add assets to your package, add an assets section, like this:
45 - # assets:
46 - # - images/a_dot_burr.jpeg
47 - # - images/a_dot_ham.jpeg
48 - #
49 - # For details regarding assets in packages, see
50 - # https://flutter.dev/to/asset-from-package
51 - #
52 - # An image asset can refer to one or more resolution-specific "variants", see
53 - # https://flutter.dev/to/resolution-aware-images
54 -
55 - # To add custom fonts to your package, add a fonts section here,
56 - # in this "flutter" section. Each entry in this list should have a
57 - # "family" key with the font family name, and a "fonts" key with a
58 - # list giving the asset and other descriptors for the font. For
59 - # example:
60 - # fonts:
61 - # - family: Schyler
62 - # fonts:
63 - # - asset: fonts/Schyler-Regular.ttf
64 - # - asset: fonts/Schyler-Italic.ttf
65 - # style: italic
66 - # - family: Trajan Pro
67 - # fonts:
68 - # - asset: fonts/TrajanPro.ttf
69 - # - asset: fonts/TrajanPro_Bold.ttf
70 - # weight: 700
71 - #
72 - # For details regarding fonts in packages, see
73 - # https://flutter.dev/to/font-from-package
cw_arbitrum/test/cw_arbitrum_test.dart deleted
-12
@@ -1,12 +0,0 @@
1 -import 'package:flutter_test/flutter_test.dart';
2 -
3 -import 'package:cw_arbitrum/cw_arbitrum.dart';
4 -
5 -void main() {
6 - test('adds one to input values', () {
7 - final calculator = Calculator();
8 - expect(calculator.addOne(2), 3);
9 - expect(calculator.addOne(-7), -6);
10 - expect(calculator.addOne(0), 1);
11 - });
12 -}
cw_base/.gitignore deleted
-31
@@ -1,31 +0,0 @@
1 -# Miscellaneous
2 -*.class
3 -*.log
4 -*.pyc
5 -*.swp
6 -.DS_Store
7 -.atom/
8 -.buildlog/
9 -.history
10 -.svn/
11 -migrate_working_dir/
12 -
13 -# IntelliJ related
14 -*.iml
15 -*.ipr
16 -*.iws
17 -.idea/
18 -
19 -# The .vscode folder contains launch configuration and tasks you configure in
20 -# VS Code which you may wish to be included in version control, so this line
21 -# is commented out by default.
22 -#.vscode/
23 -
24 -# Flutter/Dart/Pub related
25 -# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
26 -/pubspec.lock
27 -**/doc/api/
28 -.dart_tool/
29 -.flutter-plugins
30 -.flutter-plugins-dependencies
31 -build/
cw_base/.metadata deleted
-10
@@ -1,10 +0,0 @@
1 -# This file tracks properties of this Flutter project.
2 -# Used by Flutter tool to assess capabilities and perform upgrades etc.
3 -#
4 -# This file should be version controlled and should not be manually edited.
5 -
6 -version:
7 - revision: "fcf2c11572af6f390246c056bc905eca609533a0"
8 - channel: "[user-branch]"
9 -
10 -project_type: package
cw_base/CHANGELOG.md deleted
-3
@@ -1,3 +0,0 @@
1 -## 0.0.1
2 -
3 -* TODO: Describe initial release.
cw_base/LICENSE deleted
-1
@@ -1 +0,0 @@
1 -TODO: Add your license here.
cw_base/README.md deleted
-39
@@ -1,39 +0,0 @@
1 -<!--
2 -This README describes the package. If you publish this package to pub.dev,
3 -this README's contents appear on the landing page for your package.
4 -
5 -For information about how to write a good package README, see the guide for
6 -[writing package pages](https://dart.dev/tools/pub/writing-package-pages).
7 -
8 -For general information about developing packages, see the Dart guide for
9 -[creating packages](https://dart.dev/guides/libraries/create-packages)
10 -and the Flutter guide for
11 -[developing packages and plugins](https://flutter.dev/to/develop-packages).
12 --->
13 -
14 -TODO: Put a short description of the package here that helps potential users
15 -know whether this package might be useful for them.
16 -
17 -## Features
18 -
19 -TODO: List what your package can do. Maybe include images, gifs, or videos.
20 -
21 -## Getting started
22 -
23 -TODO: List prerequisites and provide or point to information on how to
24 -start using the package.
25 -
26 -## Usage
27 -
28 -TODO: Include short and useful examples for package users. Add longer examples
29 -to `/example` folder.
30 -
31 -```dart
32 -const like = 'sample';
33 -```
34 -
35 -## Additional information
36 -
37 -TODO: Tell users more about the package: where to find more information, how to
38 -contribute to the package, how to file issues, what response they can expect
39 -from the package authors, and more.
cw_base/analysis_options.yaml deleted
-4
@@ -1,4 +0,0 @@
1 -include: package:flutter_lints/flutter.yaml
2 -
3 -# Additional information about this file can be found at
4 -# https://dart.dev/guides/language/analysis-options
cw_base/devtools_options.yaml deleted
-3
@@ -1,3 +0,0 @@
1 -description: This file stores settings for Dart & Flutter DevTools.
2 -documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
3 -extensions:
cw_base/lib/base_client.dart deleted
-103
@@ -1,103 +0,0 @@
1 -import 'dart:convert';
2 -
3 -import 'package:cw_evm/evm_chain_client.dart';
4 -import 'package:cw_evm/.secrets.g.dart' as secrets;
5 -import 'package:cw_evm/evm_chain_transaction_model.dart';
6 -import 'package:flutter/foundation.dart';
7 -import 'package:web3dart/web3dart.dart';
8 -
9 -class BaseClient extends EVMChainClient {
10 - @override
11 - Transaction createTransaction({
12 - required EthereumAddress from,
13 - required EthereumAddress to,
14 - required EtherAmount amount,
15 - EtherAmount? maxPriorityFeePerGas,
16 - Uint8List? data,
17 - int? maxGas,
18 - EtherAmount? gasPrice,
19 - EtherAmount? maxFeePerGas,
20 - }) {
21 - EtherAmount? finalGasPrice = gasPrice;
22 -
23 - if (gasPrice == null && maxFeePerGas != null) {
24 - // If we have EIP-1559 parameters but no legacy gasPrice, then use maxFeePerGas as gasPrice
25 - finalGasPrice = maxFeePerGas;
26 - }
27 -
28 - return Transaction(
29 - from: from,
30 - to: to,
31 - value: amount,
32 - data: data,
33 - maxGas: maxGas,
34 - gasPrice: finalGasPrice,
35 - // maxFeePerGas: maxFeePerGas,
36 - // maxPriorityFeePerGas: maxPriorityFeePerGas,
37 - );
38 - }
39 -
40 - @override
41 - Uint8List prepareSignedTransactionForSending(Uint8List signedTransaction) => signedTransaction;
42 -
43 - @override
44 - int get chainId => 8453;
45 -
46 - @override
47 - Future<List<EVMChainTransactionModel>> fetchTransactions(String address,
48 - {String? contractAddress}) async {
49 - try {
50 - final response = await client.get(Uri.https("api.etherscan.io", "/v2/api", {
51 - "chainid": "$chainId",
52 - "module": "account",
53 - "action": contractAddress != null ? "tokentx" : "txlist",
54 - if (contractAddress != null) "contractaddress": contractAddress,
55 - "address": address,
56 - "apikey": secrets.etherScanApiKey,
57 - }));
58 -
59 - final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
60 -
61 - if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) {
62 - final res = (jsonResponse['result'] as List);
63 -
64 - res.removeWhere((e) => e['value'] == '0');
65 -
66 - return res
67 - .map(
68 - (e) => EVMChainTransactionModel.fromJson(e as Map<String, dynamic>, 'ETH'),
69 - )
70 - .toList();
71 - }
72 -
73 - return [];
74 - } catch (e) {
75 - return [];
76 - }
77 - }
78 -
79 - @override
80 - Future<List<EVMChainTransactionModel>> fetchInternalTransactions(String address) async {
81 - try {
82 - final response = await client.get(Uri.https("api.etherscan.io", "/v2/api", {
83 - "chainid": "$chainId",
84 - "module": "account",
85 - "action": "txlistinternal",
86 - "address": address,
87 - "apikey": secrets.etherScanApiKey,
88 - }));
89 -
90 - final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
91 -
92 - if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) {
93 - return (jsonResponse['result'] as List)
94 - .map((e) => EVMChainTransactionModel.fromJson(e as Map<String, dynamic>, 'ETH'))
95 - .toList();
96 - }
97 -
98 - return [];
99 - } catch (_) {
100 - return [];
101 - }
102 - }
103 -}
cw_base/lib/base_mnemonics_exception.dart deleted
-5
@@ -1,5 +0,0 @@
1 -class BaseMnemonicIsIncorrectException implements Exception {
2 - @override
3 - String toString() =>
4 - 'Base mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.';
5 -}
cw_base/lib/base_transaction_history.dart deleted
-20
@@ -1,20 +0,0 @@
1 -import 'dart:core';
2 -
3 -import 'package:cw_evm/evm_chain_transaction_history.dart';
4 -import 'package:cw_evm/evm_chain_transaction_info.dart';
5 -import 'package:cw_base/base_transaction_info.dart';
6 -
7 -class BaseTransactionHistory extends EVMChainTransactionHistory {
8 - BaseTransactionHistory({
9 - required super.walletInfo,
10 - required super.password,
11 - required super.encryptionFileUtils,
12 - });
13 -
14 - @override
15 - String getTransactionHistoryFileName() => 'base_transactions.json';
16 -
17 - @override
18 - EVMChainTransactionInfo getTransactionInfo(Map<String, dynamic> val) =>
19 - BaseTransactionInfo.fromJson(val);
20 -}
cw_base/lib/base_transaction_info.dart deleted
-41
@@ -1,41 +0,0 @@
1 -import 'package:cw_core/transaction_direction.dart';
2 -import 'package:cw_evm/evm_chain_transaction_info.dart';
3 -
4 -class BaseTransactionInfo extends EVMChainTransactionInfo {
5 - BaseTransactionInfo({
6 - required super.id,
7 - required super.height,
8 - required super.ethAmount,
9 - required super.ethFee,
10 - required super.tokenSymbol,
11 - required super.direction,
12 - required super.isPending,
13 - required super.date,
14 - required super.confirmations,
15 - required super.to,
16 - required super.from,
17 - super.contractAddress,
18 - super.exponent,
19 - });
20 -
21 - factory BaseTransactionInfo.fromJson(Map<String, dynamic> data) {
22 - return BaseTransactionInfo(
23 - id: data['id'] as String,
24 - height: data['height'] as int,
25 - ethAmount: BigInt.parse(data['amount']),
26 - exponent: data['exponent'] as int,
27 - ethFee: BigInt.parse(data['fee']),
28 - direction: parseTransactionDirectionFromInt(data['direction'] as int),
29 - date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
30 - isPending: data['isPending'] as bool,
31 - confirmations: data['confirmations'] as int,
32 - tokenSymbol: data['tokenSymbol'] as String,
33 - to: data['to'],
34 - from: data['from'],
35 - contractAddress: data['contractAddress'],
36 - );
37 - }
38 -
39 - @override
40 - String get feeCurrency => 'ETH';
41 -}
cw_base/lib/base_wallet.dart deleted
-180
@@ -1,180 +0,0 @@
1 -import 'dart:convert';
2 -
3 -import 'package:cw_base/base_client.dart';
4 -import 'package:cw_base/base_transaction_history.dart';
5 -import 'package:cw_base/base_transaction_info.dart';
6 -import 'package:cw_base/default_base_erc20_tokens.dart';
7 -import 'package:cw_core/cake_hive.dart';
8 -import 'package:cw_core/crypto_currency.dart';
9 -import 'package:cw_core/encryption_file_utils.dart';
10 -import 'package:cw_core/erc20_token.dart';
11 -import 'package:cw_core/pathForWallet.dart';
12 -import 'package:cw_core/transaction_direction.dart';
13 -import 'package:cw_core/wallet_info.dart';
14 -import 'package:cw_core/wallet_keys_file.dart';
15 -import 'package:cw_evm/evm_chain_transaction_history.dart';
16 -import 'package:cw_evm/evm_chain_transaction_info.dart';
17 -import 'package:cw_evm/evm_chain_transaction_model.dart';
18 -import 'package:cw_evm/evm_chain_transaction_priority.dart';
19 -import 'package:cw_evm/evm_chain_wallet.dart';
20 -import 'package:cw_evm/evm_erc20_balance.dart';
21 -import 'package:web3dart/web3dart.dart';
22 -
23 -class BaseWallet extends EVMChainWallet {
24 - BaseWallet({
25 - required super.walletInfo,
26 - required super.password,
27 - required super.derivationInfo,
28 - super.mnemonic,
29 - super.initialBalance,
30 - super.privateKey,
31 - required super.client,
32 - required super.encryptionFileUtils,
33 - super.passphrase,
34 - }) : super(nativeCurrency: CryptoCurrency.baseEth);
35 -
36 - @override
37 - int getTotalPriorityFee(EVMChainTransactionPriority priority) {
38 - return switch (priority) {
39 - EVMChainTransactionPriority.fast => EtherAmount.fromInt(EtherUnit.mwei, 5).getInWei.toInt(),
40 - EVMChainTransactionPriority.medium => EtherAmount.fromInt(EtherUnit.mwei, 3).getInWei.toInt(),
41 - EVMChainTransactionPriority.slow => EtherAmount.fromInt(EtherUnit.mwei, 1).getInWei.toInt(),
42 - _ => EtherAmount.fromInt(EtherUnit.mwei, 1).getInWei.toInt(),
43 - };
44 - }
45 -
46 - @override
47 - Future<void> initErc20TokensBox() async {
48 - final boxName = "${walletInfo.name.replaceAll(" ", "_")}_${Erc20Token.baseBoxName}";
49 -
50 - evmChainErc20TokensBox = await CakeHive.openBox<Erc20Token>(boxName);
51 - }
52 -
53 - @override
54 - void addInitialTokens() {
55 - final initialErc20Tokens = DefaultBaseErc20Tokens().initialBaseErc20Tokens;
56 -
57 - for (final token in initialErc20Tokens) {
58 - if (!evmChainErc20TokensBox.containsKey(token.contractAddress)) {
59 - evmChainErc20TokensBox.put(token.contractAddress, token);
60 - } else {
61 - // update existing token
62 - final existingToken = evmChainErc20TokensBox.get(token.contractAddress);
63 - evmChainErc20TokensBox.put(
64 - token.contractAddress,
65 - Erc20Token.copyWith(token, enabled: existingToken!.enabled),
66 - );
67 - }
68 - }
69 - }
70 -
71 - @override
72 - List<String> get getDefaultTokenContractAddresses =>
73 - DefaultBaseErc20Tokens().initialBaseErc20Tokens.map((e) => e.contractAddress).toList();
74 -
75 - @override
76 - Future<bool> checkIfScanProviderIsEnabled() async {
77 - return (await sharedPrefs.future).getBool("use_basescan") ?? true;
78 - }
79 -
80 - @override
81 - String getTransactionHistoryFileName() => 'base_transactions.json';
82 -
83 - @override
84 - Erc20Token createNewErc20TokenObject(Erc20Token token, String? iconPath) {
85 - return Erc20Token(
86 - name: token.name,
87 - symbol: token.symbol,
88 - contractAddress: token.contractAddress,
89 - decimal: token.decimal,
90 - enabled: token.enabled,
91 - tag: token.tag ?? 'ETH',
92 - iconPath: iconPath,
93 - isPotentialScam: token.isPotentialScam,
94 - );
95 - }
96 -
97 - @override
98 - EVMChainTransactionInfo getTransactionInfo(
99 - EVMChainTransactionModel transactionModel, String address) {
100 - final model = BaseTransactionInfo(
101 - id: transactionModel.hash,
102 - height: transactionModel.blockNumber,
103 - ethAmount: transactionModel.amount,
104 - direction: transactionModel.from == address
105 - ? TransactionDirection.outgoing
106 - : TransactionDirection.incoming,
107 - isPending: false,
108 - date: transactionModel.date,
109 - confirmations: transactionModel.confirmations,
110 - ethFee: BigInt.from(transactionModel.gasUsed) * transactionModel.gasPrice,
111 - exponent: transactionModel.tokenDecimal ?? 18,
112 - tokenSymbol: transactionModel.tokenSymbol ?? "ETH",
113 - to: transactionModel.to,
114 - from: transactionModel.from,
115 - contractAddress: transactionModel.contractAddress,
116 - );
117 - return model;
118 - }
119 -
120 - @override
121 - EVMChainTransactionHistory setUpTransactionHistory(
122 - WalletInfo walletInfo, String password, EncryptionFileUtils encryptionFileUtils) {
123 - return BaseTransactionHistory(
124 - walletInfo: walletInfo,
125 - password: password,
126 - encryptionFileUtils: encryptionFileUtils,
127 - );
128 - }
129 -
130 - static Future<BaseWallet> open({
131 - required String name,
132 - required String password,
133 - required WalletInfo walletInfo,
134 - required EncryptionFileUtils encryptionFileUtils,
135 - }) async {
136 - final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
137 - final path = await pathForWallet(name: name, type: walletInfo.type);
138 -
139 - Map<String, dynamic>? data;
140 - try {
141 - final jsonSource = await encryptionFileUtils.read(path: path, password: password);
142 -
143 - data = json.decode(jsonSource) as Map<String, dynamic>;
144 - } catch (e) {
145 - if (!hasKeysFile) rethrow;
146 - }
147 -
148 - final balance = EVMChainERC20Balance.fromJSON(data?['balance'] as String?) ??
149 - EVMChainERC20Balance(BigInt.zero);
150 -
151 - final WalletKeysData keysData;
152 - // Migrate wallet from the old scheme to then new .keys file scheme
153 - if (!hasKeysFile) {
154 - final mnemonic = data!['mnemonic'] as String?;
155 - final privateKey = data['private_key'] as String?;
156 - final passphrase = data['passphrase'] as String?;
157 -
158 - keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey, passphrase: passphrase);
159 - } else {
160 - keysData = await WalletKeysFile.readKeysFile(
161 - name,
162 - walletInfo.type,
163 - password,
164 - encryptionFileUtils,
165 - );
166 - }
167 -
168 - return BaseWallet(
169 - walletInfo: walletInfo,
170 - derivationInfo: await walletInfo.getDerivationInfo(),
171 - password: password,
172 - mnemonic: keysData.mnemonic,
173 - privateKey: keysData.privateKey,
174 - passphrase: keysData.passphrase,
175 - initialBalance: balance,
176 - client: BaseClient(),
177 - encryptionFileUtils: encryptionFileUtils,
178 - );
179 - }
180 -}
cw_base/lib/base_wallet_service.dart deleted
-172
@@ -1,172 +0,0 @@
1 -import 'package:bip39/bip39.dart' as bip39;
2 -import 'package:cw_core/encryption_file_utils.dart';
3 -import 'package:cw_core/wallet_base.dart';
4 -import 'package:cw_core/wallet_info.dart';
5 -import 'package:cw_core/wallet_type.dart';
6 -import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
7 -import 'package:cw_evm/evm_chain_wallet_service.dart';
8 -import 'package:cw_base/base_wallet.dart';
9 -import 'package:cw_base/base_client.dart';
10 -import 'package:cw_base/base_mnemonics_exception.dart';
11 -
12 -class BaseWalletService extends EVMChainWalletService<BaseWallet> {
13 - BaseWalletService(
14 - super.isDirect, {
15 - required this.client,
16 - });
17 -
18 - late BaseClient client;
19 -
20 - @override
21 - WalletType getType() => WalletType.base;
22 -
23 - @override
24 - Future<BaseWallet> create(EVMChainNewWalletCredentials credentials, {bool? isTestnet}) async {
25 - final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
26 -
27 - final mnemonic = credentials.mnemonic ?? bip39.generateMnemonic(strength: strength);
28 -
29 - final wallet = BaseWallet(
30 - walletInfo: credentials.walletInfo!,
31 - derivationInfo: await credentials.walletInfo!.getDerivationInfo(),
32 - mnemonic: mnemonic,
33 - password: credentials.password!,
34 - passphrase: credentials.passphrase,
35 - client: client,
36 - encryptionFileUtils: encryptionFileUtilsFor(isDirect),
37 - );
38 -
39 - await wallet.init();
40 - wallet.addInitialTokens();
41 - await wallet.save();
42 - return wallet;
43 - }
44 -
45 - @override
46 - Future<BaseWallet> openWallet(String name, String password) async {
47 - final walletInfo = await WalletInfo.get(name, getType());
48 - if (walletInfo == null) {
49 - throw Exception('Wallet not found');
50 - }
51 -
52 - try {
53 - final wallet = await BaseWallet.open(
54 - name: name,
55 - password: password,
56 - walletInfo: walletInfo,
57 - encryptionFileUtils: encryptionFileUtilsFor(isDirect),
58 - );
59 -
60 - await wallet.init();
61 - wallet.addInitialTokens();
62 - await wallet.save();
63 - saveBackup(name);
64 - return wallet;
65 - } catch (_) {
66 - await restoreWalletFilesFromBackup(name);
67 -
68 - final wallet = await BaseWallet.open(
69 - name: name,
70 - password: password,
71 - walletInfo: walletInfo,
72 - encryptionFileUtils: encryptionFileUtilsFor(isDirect),
73 - );
74 -
75 - await wallet.init();
76 - wallet.addInitialTokens();
77 - await wallet.save();
78 - return wallet;
79 - }
80 - }
81 -
82 - @override
83 - Future<BaseWallet> restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials,
84 - {bool? isTestnet}) async {
85 - final wallet = BaseWallet(
86 - password: credentials.password!,
87 - privateKey: credentials.privateKey,
88 - walletInfo: credentials.walletInfo!,
89 - derivationInfo: await credentials.walletInfo!.getDerivationInfo(),
90 - client: client,
91 - encryptionFileUtils: encryptionFileUtilsFor(isDirect),
92 - );
93 -
94 - await wallet.init();
95 - wallet.addInitialTokens();
96 - await wallet.save();
97 - return wallet;
98 - }
99 -
100 - @override
101 - Future<BaseWallet> restoreFromHardwareWallet(
102 - EVMChainRestoreWalletFromHardware credentials) async {
103 - final derivationInfo = await credentials.walletInfo!.getDerivationInfo();
104 - derivationInfo.derivationType = DerivationType.bip39;
105 - derivationInfo.derivationPath = "m/44'/60'/${credentials.hwAccountData.accountIndex}'/0/0";
106 - await derivationInfo.save();
107 - credentials.walletInfo!.hardwareWalletType = credentials.hardwareWalletType;
108 - credentials.walletInfo!.address = credentials.hwAccountData.address;
109 - await credentials.walletInfo!.save();
110 -
111 - final wallet = BaseWallet(
112 - walletInfo: credentials.walletInfo!,
113 - derivationInfo: derivationInfo,
114 - password: credentials.password!,
115 - client: client,
116 - encryptionFileUtils: encryptionFileUtilsFor(isDirect),
117 - );
118 -
119 - await wallet.init();
120 - wallet.addInitialTokens();
121 - await wallet.save();
122 -
123 - return wallet;
124 - }
125 -
126 - @override
127 - Future<BaseWallet> restoreFromSeed(EVMChainRestoreWalletFromSeedCredentials credentials,
128 - {bool? isTestnet}) async {
129 - if (!bip39.validateMnemonic(credentials.mnemonic)) {
130 - throw BaseMnemonicIsIncorrectException();
131 - }
132 -
133 - final wallet = BaseWallet(
134 - password: credentials.password!,
135 - mnemonic: credentials.mnemonic,
136 - walletInfo: credentials.walletInfo!,
137 - derivationInfo: await credentials.walletInfo!.getDerivationInfo(),
138 - passphrase: credentials.passphrase,
139 - client: client,
140 - encryptionFileUtils: encryptionFileUtilsFor(isDirect),
141 - );
142 -
143 - await wallet.init();
144 - wallet.addInitialTokens();
145 - await wallet.save();
146 -
147 - return wallet;
148 - }
149 -
150 - @override
151 - Future<void> rename(String currentName, String password, String newName) async {
152 - final currentWalletInfo = await WalletInfo.get(currentName, getType());
153 - if (currentWalletInfo == null) {
154 - throw Exception('Wallet not found');
155 - }
156 - final currentWallet = await BaseWallet.open(
157 - password: password,
158 - name: currentName,
159 - walletInfo: currentWalletInfo,
160 - encryptionFileUtils: encryptionFileUtilsFor(isDirect),
161 - );
162 -
163 - await currentWallet.renameWalletFiles(newName);
164 - await saveBackup(newName);
165 -
166 - final newWalletInfo = currentWalletInfo;
167 - newWalletInfo.id = WalletBase.idFor(newName, getType());
168 - newWalletInfo.name = newName;
169 -
170 - newWalletInfo.save();
171 - }
172 -}
cw_base/lib/cw_base.dart deleted
-5
@@ -1,5 +0,0 @@
1 -/// A Calculator.
2 -class Calculator {
3 - /// Returns [value] plus 1.
4 - int addOne(int value) => value + 1;
5 -}
cw_base/lib/default_base_erc20_tokens.dart deleted
-71
@@ -1,71 +0,0 @@
1 -import 'package:cw_core/crypto_currency.dart';
2 -import 'package:cw_core/erc20_token.dart';
3 -
4 -class DefaultBaseErc20Tokens {
5 - final List<Erc20Token> _defaultTokens = [
6 - Erc20Token(
7 - name: "USD Coin",
8 - symbol: "USDC",
9 - contractAddress: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
10 - decimal: 6,
11 - enabled: true,
12 - ),
13 - Erc20Token(
14 - name: "USDe",
15 - symbol: "USDe",
16 - contractAddress: "0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34",
17 - decimal: 18,
18 - enabled: true,
19 - ),
20 - Erc20Token(
21 - name: "Dai",
22 - symbol: "DAI",
23 - contractAddress: "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb",
24 - decimal: 18,
25 - enabled: true,
26 - ),
27 - Erc20Token(
28 - name: "Bridged Tether USD",
29 - symbol: "USDT",
30 - contractAddress: "0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2",
31 - decimal: 6,
32 - enabled: true,
33 - ),
34 - Erc20Token(
35 - name: "Wrapped Ether",
36 - symbol: "WETH",
37 - contractAddress: "0x4200000000000000000000000000000000000006",
38 - decimal: 18,
39 - enabled: false,
40 - ),
41 - Erc20Token(
42 - name: "Wrapped BTC",
43 - symbol: "WBTC",
44 - contractAddress: "0x0555E30da8f98308EdB960aa94C0Db47230d2B9c",
45 - decimal: 8,
46 - enabled: false,
47 - ),
48 - Erc20Token(
49 - name: "SPX6900",
50 - symbol: "SPX",
51 - contractAddress: "0x50dA645f148798F68EF2d7dB7C1CB22A6819bb2C",
52 - decimal: 8,
53 - enabled: false,
54 - ),
55 - ];
56 -
57 - List<Erc20Token> get initialBaseErc20Tokens => _defaultTokens.map((token) {
58 - String? iconPath;
59 - if (token.iconPath?.isEmpty ?? true) {
60 - try {
61 - iconPath = CryptoCurrency.all
62 - .firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase())
63 - .iconPath;
64 - } catch (_) {}
65 - } else {
66 - iconPath = token.iconPath;
67 - }
68 -
69 - return Erc20Token.copyWith(token, icon: iconPath, tag: 'BASE');
70 - }).toList();
71 -}
cw_base/pubspec.yaml deleted
-73
@@ -1,73 +0,0 @@
1 -name: cw_base
2 -description: "Base package for Cake Wallet"
3 -version: 0.0.1
4 -publish_to: none
5 -homepage: https://cakewallet.com
6 -
7 -environment:
8 - sdk: '>=3.0.6 <4.0.0'
9 - flutter: ">=1.17.0"
10 -
11 -dependencies:
12 - flutter:
13 - sdk: flutter
14 - cw_core:
15 - path: ../cw_core
16 - cw_ethereum:
17 - path: ../cw_ethereum
18 - cw_evm:
19 - path: ../cw_evm
20 - web3dart: ^2.7.1
21 - hive: ^2.2.3
22 - bip39: ^1.0.6
23 - collection: ^1.17.1
24 -
25 -dependency_overrides:
26 - web3dart:
27 - git:
28 - url: https://github.com/cake-tech/web3dart.git
29 - ref: cake
30 - watcher: ^1.1.0
31 -
32 -dev_dependencies:
33 - flutter_test:
34 - sdk: flutter
35 - flutter_lints: ^2.0.0
36 - build_runner: ^2.4.15
37 -
38 -# For information on the generic Dart part of this file, see the
39 -# following page: https://dart.dev/tools/pub/pubspec
40 -
41 -# The following section is specific to Flutter packages.
42 -flutter:
43 -
44 - # To add assets to your package, add an assets section, like this:
45 - # assets:
46 - # - images/a_dot_burr.jpeg
47 - # - images/a_dot_ham.jpeg
48 - #
49 - # For details regarding assets in packages, see
50 - # https://flutter.dev/to/asset-from-package
51 - #
52 - # An image asset can refer to one or more resolution-specific "variants", see
53 - # https://flutter.dev/to/resolution-aware-images
54 -
55 - # To add custom fonts to your package, add a fonts section here,
56 - # in this "flutter" section. Each entry in this list should have a
57 - # "family" key with the font family name, and a "fonts" key with a
58 - # list giving the asset and other descriptors for the font. For
59 - # example:
60 - # fonts:
61 - # - family: Schyler
62 - # fonts:
63 - # - asset: fonts/Schyler-Regular.ttf
64 - # - asset: fonts/Schyler-Italic.ttf
65 - # style: italic
66 - # - family: Trajan Pro
67 - # fonts:
68 - # - asset: fonts/TrajanPro.ttf
69 - # - asset: fonts/TrajanPro_Bold.ttf
70 - # weight: 700
71 - #
72 - # For details regarding fonts in packages, see
73 - # https://flutter.dev/to/font-from-package
cw_base/test/cw_base_test.dart deleted
-12
@@ -1,12 +0,0 @@
1 -import 'package:flutter_test/flutter_test.dart';
2 -
3 -import 'package:cw_base/cw_base.dart';
4 -
5 -void main() {
6 - test('adds one to input values', () {
7 - final calculator = Calculator();
8 - expect(calculator.addOne(2), 3);
9 - expect(calculator.addOne(-7), -6);
10 - expect(calculator.addOne(0), 1);
11 - });
12 -}
cw_core/lib/crypto_currency.dart
+3 -1
@@ -250,7 +250,8 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
250 static const cbbtc = CryptoCurrency(title: 'CBBTC', tag: 'ETH', fullName: 'Coinbase Wrapped BTC', raw: 103, name: 'cbbtc', iconPath: 'assets/images/cbbtc_icon.png', decimals: 8);
251 static const baseEth = CryptoCurrency(title: 'ETH', tag: 'BASE', fullName: 'Ethereum', raw: 104, name: 'baseth', iconPath: 'assets/images/crypto/base_icon.webp', decimals: 18);
252 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);
253 - static const arbEth = CryptoCurrency(title: 'ETH', tag: 'ARB', fullName: 'Arbitrum', raw: 106, name: 'arbeth', iconPath: 'assets/images/crypto/arbitrum.webp', decimals: 18);
253 + static const arbEth = CryptoCurrency(title: 'ETH', tag: 'ARB', fullName: 'Ethereum (Arbitrum One)', raw: 106, name: 'arbeth', iconPath: 'assets/images/crypto/ethereum.webp', decimals: 18);
254 + static const usdcArb = CryptoCurrency(title: 'USDC', tag: 'ARB', fullName: 'USDC Coin', raw: 107, name: 'usdcarb', iconPath: 'assets/images/crypto/usdc.webp', decimals: 6);
255
256 static final Map<int, CryptoCurrency> _rawCurrencyMap =
257 [...all, ...havenCurrencies].fold<Map<int, CryptoCurrency>>(<int, CryptoCurrency>{}, (acc, item) {
@@ -289,6 +290,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
290 'shibainu': shib,
291 'zcash': zec,
292 'base': baseEth,
293 + 'arbitrum': arbEth,
294 };
295
296 static CryptoCurrency deserialize({required int raw}) {
cw_core/lib/currency_for_wallet_type.dart
+45 -1
@@ -1,7 +1,11 @@
1 import 'package:cw_core/crypto_currency.dart';
2 import 'package:cw_core/wallet_type.dart';
3
4 -CryptoCurrency walletTypeToCryptoCurrency(WalletType type, {bool isTestnet = false}) {
4 +CryptoCurrency walletTypeToCryptoCurrency(WalletType type, {bool isTestnet = false, int? chainId}) {
5 + if (chainId != null) {
6 + return getCryptoCurrencyByChainId(chainId);
7 + }
8 +
9 switch (type) {
10 case WalletType.monero:
11 return CryptoCurrency.xmr;
@@ -45,3 +49,43 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type, {bool isTestnet = fal
49 'Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency');
50 }
51 }
52 +
53 +CryptoCurrency getCryptoCurrencyByChainId(int chainId) {
54 + switch (chainId) {
55 + case 1:
56 + return CryptoCurrency.eth;
57 + case 137:
58 + return CryptoCurrency.maticpoly;
59 + case 8453:
60 + return CryptoCurrency.baseEth;
61 + case 42161:
62 + return CryptoCurrency.arbEth;
63 + default:
64 + return CryptoCurrency.eth;
65 + }
66 +}
67 +
68 +/// Get chainId from CryptoCurrency for EVM chains
69 +/// Returns null if currency is not an EVM chain
70 +int? getChainIdByCryptoCurrency(CryptoCurrency currency) {
71 + switch (currency) {
72 + case CryptoCurrency.eth:
73 + return 1;
74 + case CryptoCurrency.maticpoly:
75 + return 137;
76 + case CryptoCurrency.baseEth:
77 + return 8453;
78 + case CryptoCurrency.arbEth:
79 + return 42161;
80 + default:
81 + return null;
82 + }
83 +}
84 +
85 +CryptoCurrency getCryptoCurrencyForWalletListItem(WalletType type, {bool isTestnet = false, int? chainId}) {
86 + if (type == WalletType.arbitrum) {
87 + return CryptoCurrency.arb;
88 + }
89 +
90 + return walletTypeToCryptoCurrency(type, isTestnet: isTestnet, chainId: chainId);
91 +}
cw_core/lib/erc20_token.dart
+1 -3
@@ -74,9 +74,7 @@ class Erc20Token extends CryptoCurrency with HiveObjectMixin {
74 static const arbitrumBoxName = 'ArbitrumErc20Tokens';
75
76 @override
77 - bool operator ==(other) =>
78 - (other is Erc20Token && other.contractAddress == contractAddress) ||
79 - (other is CryptoCurrency && other.title == title);
77 + bool operator ==(Object other) => other is Erc20Token && other.contractAddress == contractAddress;
78
79 @override
80 int get hashCode => contractAddress.hashCode;
cw_core/lib/hardware/device_connection_type.dart
+2 -2
@@ -15,7 +15,7 @@ enum DeviceConnectionType {
15 WalletType.bitcoin,
16 // WalletType.litecoin,
17 WalletType.ethereum,
18 - WalletType.polygon
18 + WalletType.polygon,
19 ].contains(walletType);
20 break;
21 case HardwareWalletType.ledger:
@@ -24,7 +24,7 @@ enum DeviceConnectionType {
24 WalletType.bitcoin,
25 WalletType.litecoin,
26 WalletType.ethereum,
27 - WalletType.polygon
27 + WalletType.polygon,
28 ].contains(walletType);
29 break;
30 case HardwareWalletType.trezor:
cw_core/lib/wallet_base.dart
+7 -1
@@ -25,7 +25,13 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
25
26 WalletType get type => walletInfo.type;
27
28 - CryptoCurrency get currency => walletTypeToCryptoCurrency(type, isTestnet: isTestnet);
28 + int? get chainId => null;
29 +
30 + CryptoCurrency get currency => walletTypeToCryptoCurrency(
31 + type,
32 + chainId: chainId,
33 + isTestnet: isTestnet,
34 + );
35
36 String get id => walletInfo.id;
37
cw_core/lib/wallet_type.dart
+6 -6
@@ -257,14 +257,18 @@ WalletType? cryptoCurrencyToWalletType(CryptoCurrency type) {
257 return WalletType.haven;
258 case CryptoCurrency.eth:
259 return WalletType.ethereum;
260 + case CryptoCurrency.maticpoly:
261 + return WalletType.polygon;
262 + case CryptoCurrency.baseEth:
263 + return WalletType.base;
264 + case CryptoCurrency.arbEth:
265 + return WalletType.arbitrum;
266 case CryptoCurrency.bch:
267 return WalletType.bitcoinCash;
268 case CryptoCurrency.nano:
269 return WalletType.nano;
270 case CryptoCurrency.banano:
271 return WalletType.banano;
266 - case CryptoCurrency.maticpoly:
267 - return WalletType.polygon;
272 case CryptoCurrency.sol:
273 return WalletType.solana;
274 case CryptoCurrency.trx:
@@ -277,10 +281,6 @@ WalletType? cryptoCurrencyToWalletType(CryptoCurrency type) {
281 return WalletType.decred;
282 case CryptoCurrency.doge:
283 return WalletType.dogecoin;
280 - case CryptoCurrency.baseEth:
281 - return WalletType.base;
282 - case CryptoCurrency.arbEth:
283 - return WalletType.arbitrum;
284 default:
285 return null;
286 }
cw_ethereum/.gitignore deleted
-30
@@ -1,30 +0,0 @@
1 -# Miscellaneous
2 -*.class
3 -*.log
4 -*.pyc
5 -*.swp
6 -.DS_Store
7 -.atom/
8 -.buildlog/
9 -.history
10 -.svn/
11 -migrate_working_dir/
12 -
13 -# IntelliJ related
14 -*.iml
15 -*.ipr
16 -*.iws
17 -.idea/
18 -
19 -# The .vscode folder contains launch configuration and tasks you configure in
20 -# VS Code which you may wish to be included in version control, so this line
21 -# is commented out by default.
22 -#.vscode/
23 -
24 -# Flutter/Dart/Pub related
25 -# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
26 -/pubspec.lock
27 -**/doc/api/
28 -.dart_tool/
29 -.packages
30 -build/
cw_ethereum/.metadata deleted
-10
@@ -1,10 +0,0 @@
1 -# This file tracks properties of this Flutter project.
2 -# Used by Flutter tool to assess capabilities and perform upgrades etc.
3 -#
4 -# This file should be version controlled and should not be manually edited.
5 -
6 -version:
7 - revision: eb6d86ee27deecba4a83536aa20f366a6044895c
8 - channel: stable
9 -
10 -project_type: package
cw_ethereum/CHANGELOG.md deleted
-3
@@ -1,3 +0,0 @@
1 -## 0.0.1
2 -
3 -* TODO: Describe initial release.
cw_ethereum/LICENSE deleted
-1
@@ -1 +0,0 @@
1 -TODO: Add your license here.
cw_ethereum/README.md deleted
-64
@@ -1,64 +0,0 @@
1 -## cw_ethereum
2 -
3 -Ethereum wallet module built on `cw_evm`. Supports native ETH and ERC‑20 tokens, with history fetched via Etherscan.
4 -
5 -### Features
6 -
7 -- EVM client specialized for Ethereum mainnet (chainId 1).
8 -- Default ERC‑20 token list and wallet‑scoped token storage/migration.
9 -- History via Etherscan v2 API (external, internal, and token transfers).
10 -- EIP‑1559 fee support; gas estimation per transaction intent.
11 -- Create/sign native and ERC‑20 transfers; approvals; broadcast.
12 -- Manage ERC‑20 tokens and balances; metadata lookup when needed.
13 -- Message signing and verification.
14 -- Node health checks for native and USDC token balance.
15 -
16 -### Getting started
17 -
18 -Add shared EVM secrets (see `cw_evm` README):
19 -
20 -```dart
21 -// cw_evm/lib/.secrets.g.dart (DO NOT COMMIT)
22 -const String etherScanApiKey = 'YOUR_ETHERSCAN_KEY';
23 -const String nowNodesApiKey = 'YOUR_NOWNODES_KEY'; // if using eth.nownodes.io
24 -const String moralisApiKey = 'YOUR_MORALIS_KEY'; // optional
25 -```
26 -
27 -Connect and sync:
28 -
29 -```dart
30 -final service = EthereumWalletService(walletInfoBox, true, client: EthereumClient());
31 -final wallet = await service.create(EVMChainNewWalletCredentials(name: 'My ETH', password: 'secret'));
32 -await wallet.connectToNode(node: Node(uriRaw: 'eth.llamarpc.com', isSSL: true));
33 -await wallet.startSync();
34 -```
35 -
36 -### Usage
37 -
38 -Send ETH:
39 -
40 -```dart
41 -final pending = await wallet.createTransaction(
42 - EVMChainTransactionCredentials.single(
43 - address: '0x...',
44 - cryptoAmount: '0.05',
45 - currency: CryptoCurrency.eth,
46 - priority: EVMChainTransactionPriority.medium,
47 - ),
48 -);
49 -final hash = await pending.commit();
50 -```
51 -
52 -Add an ERC‑20 token and refresh balance:
53 -
54 -```dart
55 -final token = await wallet.getErc20Token('0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', 'eth'); // USDC
56 -if (token != null) {
57 - await wallet.addErc20Token(token);
58 -}
59 -```
60 -
61 -### Additional information
62 -
63 -- Toggle Etherscan usage via shared preferences key `use_etherscan`.
64 -- See `lib/` for APIs: `EthereumClient`, `EthereumWallet`, `EthereumWalletService`.
cw_ethereum/analysis_options.yaml deleted
-4
@@ -1,4 +0,0 @@
1 -include: package:flutter_lints/flutter.yaml
2 -
3 -# Additional information about this file can be found at
4 -# https://dart.dev/guides/language/analysis-options
cw_ethereum/lib/cw_ethereum.dart deleted
-7
@@ -1,7 +0,0 @@
1 -library cw_ethereum;
2 -
3 -/// A Calculator.
4 -class Calculator {
5 - /// Returns [value] plus 1.
6 - int addOne(int value) => value + 1;
7 -}
cw_ethereum/lib/default_ethereum_erc20_tokens.dart deleted
-863
@@ -1,863 +0,0 @@
1 -import 'package:cw_core/crypto_currency.dart';
2 -import 'package:cw_core/erc20_token.dart';
3 -
4 -class DefaultEthereumErc20Tokens {
5 - final List<Erc20Token> _defaultTokens = [
6 - Erc20Token(
7 - name: "USD Coin",
8 - symbol: "USDC",
9 - contractAddress: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
10 - decimal: 6,
11 - enabled: true,
12 - ),
13 - Erc20Token(
14 - name: "USDT Tether",
15 - symbol: "USDT",
16 - contractAddress: "0xdac17f958d2ee523a2206206994597c13d831ec7",
17 - decimal: 6,
18 - enabled: true,
19 - ),
20 - Erc20Token(
21 - name: "Decentralized Euro",
22 - symbol: "DEURO",
23 - contractAddress: "0xbA3f535bbCcCcA2A154b573Ca6c5A49BAAE0a3ea",
24 - decimal: 18,
25 - enabled: true,
26 - ),
27 - Erc20Token(
28 - name: "Dai",
29 - symbol: "DAI",
30 - contractAddress: "0x6B175474E89094C44Da98b954EedeAC495271d0F",
31 - decimal: 18,
32 - enabled: true,
33 - ),
34 - Erc20Token(
35 - name: "Wrapped Ether",
36 - symbol: "WETH",
37 - contractAddress: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
38 - decimal: 18,
39 - enabled: false,
40 - ),
41 - Erc20Token(
42 - name: "Pepe",
43 - symbol: "PEPE",
44 - contractAddress: "0x6982508145454ce325ddbe47a25d4ec3d2311933",
45 - decimal: 18,
46 - enabled: false,
47 - ),
48 - Erc20Token(
49 - name: "SHIBA INU",
50 - symbol: "SHIB",
51 - contractAddress: "0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce",
52 - decimal: 18,
53 - enabled: false,
54 - ),
55 - Erc20Token(
56 - name: "ApeCoin",
57 - symbol: "APE",
58 - contractAddress: "0x4d224452801aced8b2f0aebe155379bb5d594381",
59 - decimal: 18,
60 - enabled: false,
61 - ),
62 - Erc20Token(
63 - name: "Matic Token",
64 - symbol: "MATIC",
65 - contractAddress: "0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0",
66 - decimal: 18,
67 - enabled: false,
68 - ),
69 - Erc20Token(
70 - name: "Wrapped BTC",
71 - symbol: "WBTC",
72 - contractAddress: "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599",
73 - decimal: 8,
74 - enabled: false,
75 - ),
76 - Erc20Token(
77 - name: "Gitcoin",
78 - symbol: "GTC",
79 - contractAddress: "0xde30da39c46104798bb5aa3fe8b9e0e1f348163f",
80 - decimal: 18,
81 - enabled: false,
82 - ),
83 - Erc20Token(
84 - name: "Compound",
85 - symbol: "COMP",
86 - contractAddress: "0xc00e94cb662c3520282e6f5717214004a7f26888",
87 - decimal: 18,
88 - enabled: false,
89 - ),
90 - Erc20Token(
91 - name: "Aave Token",
92 - symbol: "AAVE",
93 - contractAddress: "0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9",
94 - decimal: 18,
95 - enabled: false,
96 - ),
97 - Erc20Token(
98 - name: "Uniswap",
99 - symbol: "UNI",
100 - contractAddress: "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984",
101 - decimal: 18,
102 - enabled: false,
103 - ),
104 - Erc20Token(
105 - name: "Decentraland",
106 - symbol: "MANA",
107 - contractAddress: "0x0F5D2fB29fb7d3CFeE444a200298f468908cC942",
108 - decimal: 18,
109 - enabled: false,
110 - ),
111 - Erc20Token(
112 - name: "Storj",
113 - symbol: "STORJ",
114 - contractAddress: "0xb64ef51c888972c908cfacf59b47c1afbc0ab8ac",
115 - decimal: 8,
116 - enabled: false,
117 - ),
118 - Erc20Token(
119 - name: "Maker",
120 - symbol: "MKR",
121 - contractAddress: "0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2",
122 - decimal: 18,
123 - enabled: false,
124 - ),
125 - Erc20Token(
126 - name: "Orchid",
127 - symbol: "OXT",
128 - contractAddress: "0x4575f41308EC1483f3d399aa9a2826d74Da13Deb",
129 - decimal: 18,
130 - enabled: false,
131 - ),
132 - Erc20Token(
133 - name: "Paxos Gold",
134 - symbol: "PAXG",
135 - contractAddress: "0x45804880De22913dAFE09f4980848ECE6EcbAf78",
136 - decimal: 18,
137 - enabled: false,
138 - ),
139 - Erc20Token(
140 - name: "Binance Coin",
141 - symbol: "BNB",
142 - contractAddress: "0xB8c77482e45F1F44dE1745F52C74426C631bDD52",
143 - decimal: 18,
144 - enabled: false,
145 - ),
146 - Erc20Token(
147 - name: "stETH",
148 - symbol: "stETH",
149 - contractAddress: "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84",
150 - decimal: 18,
151 - enabled: false,
152 - ),
153 - Erc20Token(
154 - name: "Lido DAO",
155 - symbol: "LDO",
156 - contractAddress: "0x5A98FcBEA516Cf06857215779Fd812CA3beF1B32",
157 - decimal: 18,
158 - enabled: false,
159 - ),
160 - Erc20Token(
161 - name: "Arbitrum",
162 - symbol: "ARB",
163 - contractAddress: "0xB50721BCf8d664c30412Cfbc6cf7a15145234ad1",
164 - decimal: 18,
165 - enabled: false,
166 - ),
167 - Erc20Token(
168 - name: "Graph Token",
169 - symbol: "GRT",
170 - contractAddress: "0xc944E90C64B2c07662A292be6244BDf05Cda44a7",
171 - decimal: 18,
172 - enabled: false,
173 - ),
174 - Erc20Token(
175 - name: "Frax",
176 - symbol: "FRAX",
177 - contractAddress: "0x853d955aCEf822Db058eb8505911ED77F175b99e",
178 - decimal: 18,
179 - enabled: false,
180 - ),
181 - Erc20Token(
182 - name: "Gemini dollar",
183 - symbol: "GUSD",
184 - contractAddress: "0x056Fd409E1d7A124BD7017459dFEa2F387b6d5Cd",
185 - decimal: 2,
186 - enabled: false,
187 - ),
188 - Erc20Token(
189 - name: "Compound Ether",
190 - symbol: "cETH",
191 - contractAddress: "0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5",
192 - decimal: 8,
193 - enabled: false,
194 - ),
195 - Erc20Token(
196 - name: "Binance USD",
197 - symbol: "BUSD",
198 - contractAddress: "0x4Fabb145d64652a948d72533023f6E7A623C7C53",
199 - decimal: 18,
200 - enabled: false,
201 - ),
202 - Erc20Token(
203 - name: "TrueUSD",
204 - symbol: "TUSD",
205 - contractAddress: "0x0000000000085d4780B73119b644AE5ecd22b376",
206 - decimal: 18,
207 - enabled: false,
208 - ),
209 - Erc20Token(
210 - name: "Cronos Coin",
211 - symbol: "CRO",
212 - contractAddress: "0xA0b73E1Ff0B80914AB6fe0444E65848C4C34450b",
213 - decimal: 8,
214 - enabled: false,
215 - ),
216 - Erc20Token(
217 - name: "Pax Dollar",
218 - symbol: "USDP",
219 - contractAddress: "0x8E870D67F660D95d5be530380D0eC0bd388289E1",
220 - decimal: 18,
221 - enabled: false,
222 - ),
223 - Erc20Token(
224 - name: "Fantom Token",
225 - symbol: "FTM",
226 - contractAddress: "0x4E15361FD6b4BB609Fa63C81A2be19d873717870",
227 - decimal: 18,
228 - enabled: false,
229 - ),
230 - Erc20Token(
231 - name: "BitTorrent",
232 - symbol: "BTT",
233 - contractAddress: "0xC669928185DbCE49d2230CC9B0979BE6DC797957",
234 - decimal: 18,
235 - enabled: false,
236 - ),
237 - Erc20Token(
238 - name: "Nexo",
239 - symbol: "NEXO",
240 - contractAddress: "0xB62132e35a6c13ee1EE0f84dC5d40bad8d815206",
241 - decimal: 18,
242 - enabled: false,
243 - ),
244 - Erc20Token(
245 - name: "dYdX",
246 - symbol: "DYDX",
247 - contractAddress: "0x92D6C1e31e14520e676a687F0a93788B716BEff5",
248 - decimal: 18,
249 - enabled: false,
250 - ),
251 - Erc20Token(
252 - name: "PancakeSwap Token",
253 - symbol: "Cake",
254 - contractAddress: "0x152649eA73beAb28c5b49B26eb48f7EAD6d4c898",
255 - decimal: 18,
256 - enabled: false,
257 - ),
258 - Erc20Token(
259 - name: "BAT",
260 - symbol: "BAT",
261 - contractAddress: "0x0D8775F648430679A709E98d2b0Cb6250d2887EF",
262 - decimal: 18,
263 - enabled: false,
264 - ),
265 - Erc20Token(
266 - name: "1INCH Token",
267 - symbol: "1INCH",
268 - contractAddress: "0x111111111117dC0aa78b770fA6A738034120C302",
269 - decimal: 18,
270 - enabled: false,
271 - ),
272 - Erc20Token(
273 - name: "Ethereum Name Service",
274 - symbol: "ENS",
275 - contractAddress: "0xC18360217D8F7Ab5e7c516566761Ea12Ce7F9D72",
276 - decimal: 18,
277 - enabled: false,
278 - ),
279 - Erc20Token(
280 - name: "ZRX",
281 - symbol: "ZRX",
282 - contractAddress: "0xE41d2489571d322189246DaFA5ebDe1F4699F498",
283 - decimal: 18,
284 - enabled: false,
285 - ),
286 - Erc20Token(
287 - name: "Verse",
288 - symbol: "VERSE",
289 - contractAddress: "0x249cA82617eC3DfB2589c4c17ab7EC9765350a18",
290 - decimal: 18,
291 - enabled: false,
292 - ),
293 - Erc20Token(
294 - name: "PayPal USD",
295 - symbol: "PYUSD",
296 - contractAddress: "0x6c3ea9036406852006290770bedfcaba0e23a0e8",
297 - decimal: 6,
298 - enabled: false,
299 - ),
300 - Erc20Token(
301 - name: "Chainflip",
302 - symbol: "FLIP",
303 - contractAddress: "0x826180541412D574cf1336d22c0C0a287822678A",
304 - decimal: 18,
305 - enabled: false,
306 - ),
307 - Erc20Token(
308 - name: "Native Decentralized Euro Protocol Share",
309 - symbol: "NDEPS",
310 - contractAddress: "0xc71104001A3CCDA1BEf1177d765831Bd1bfE8eE6",
311 - decimal: 18,
312 - enabled: false,
313 - ),
314 - Erc20Token(
315 - name: "Decentralized Euro Protocol Share",
316 - symbol: "DEPS",
317 - contractAddress: "0x103747924e74708139a9400e4ab4bea79fffa380",
318 - decimal: 18,
319 - enabled: false,
320 - ),
321 - Erc20Token(
322 - name: "Kraken Wrapped Bitcoin",
323 - symbol: "KBTC",
324 - contractAddress: "0x73E0C0d45E048D25Fc26Fa3159b0aA04BfA4Db98",
325 - decimal: 8,
326 - enabled: false,
327 - ),
328 - Erc20Token(
329 - name: "Coinbase Wrapped BTC",
330 - symbol: "CBBTC",
331 - contractAddress: "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf",
332 - decimal: 8,
333 - enabled: false,
334 - ),
335 - Erc20Token(
336 - name: 'Abbott xStock',
337 - symbol: 'ABTx',
338 - contractAddress: '0x89233399708c18ac6887f90a2b4cd8ba5fedd06e',
339 - decimal: 8,
340 - enabled: false,
341 - iconPath: 'assets/images/stocks/abtx.webp',
342 - ),
343 - Erc20Token(
344 - name: 'AbbVie xStock',
345 - symbol: 'ABBVx',
346 - contractAddress: '0xfbf2398df672cee4afcc2a4a733222331c742a6a',
347 - decimal: 8,
348 - enabled: false,
349 - iconPath: 'assets/images/stocks/abbv.webp',
350 - ),
351 - Erc20Token(
352 - name: 'Accenture xStock',
353 - symbol: 'ACNx',
354 - contractAddress: '0x03183ce31b1656b72a55fa6056e287f50c35bbeb',
355 - decimal: 8,
356 - enabled: false,
357 - iconPath: 'assets/images/stocks/acnx.webp',
358 - ),
359 - Erc20Token(
360 - name: 'Alphabet xStock',
361 - symbol: 'GOOGLx',
362 - contractAddress: '0xe92f673ca36c5e2efd2de7628f815f84807e803f',
363 - decimal: 8,
364 - enabled: false,
365 - iconPath: 'assets/images/stocks/googlx.webp',
366 - ),
367 - Erc20Token(
368 - name: 'Amazon xStock',
369 - symbol: 'AMZNx',
370 - contractAddress: '0x3557ba345b01efa20a1bddc61f573bfd87195081',
371 - decimal: 8,
372 - enabled: false,
373 - iconPath: 'assets/images/stocks/amznx.webp',
374 - ),
375 - Erc20Token(
376 - name: 'Amber xStock',
377 - symbol: 'AMBRx',
378 - contractAddress: '0x2f9a35ab5ddfbc49927bfdeab98a86c53dc6e763',
379 - decimal: 8,
380 - enabled: false,
381 - iconPath: 'assets/images/stocks/ambrx.webp',
382 - ),
383 - Erc20Token(
384 - name: 'Apple xStock',
385 - symbol: 'AAPLx',
386 - contractAddress: '0x9d275685dc284c8eb1c79f6aba7a63dc75ec890a',
387 - decimal: 8,
388 - enabled: false,
389 - iconPath: 'assets/images/stocks/apple.webp',
390 - ),
391 - Erc20Token(
392 - name: 'AppLovin xStock',
393 - symbol: 'APPx',
394 - contractAddress: '0x50a1291f69d9d3853def8209cfb1af0b46927be1',
395 - decimal: 8,
396 - enabled: false,
397 - iconPath: 'assets/images/stocks/appx.webp',
398 - ),
399 - Erc20Token(
400 - name: 'AstraZeneca xStock',
401 - symbol: 'AZNx',
402 - contractAddress: '0x5d642505fe1a28897eb3baba665f454755d8daa2',
403 - decimal: 8,
404 - enabled: false,
405 - iconPath: 'assets/images/stocks/aznx.webp',
406 - ),
407 - Erc20Token(
408 - name: 'Bank of America xStock',
409 - symbol: 'BACx',
410 - contractAddress: '0x314938c596f5ce31c3f75307d2979338c346d7f2',
411 - decimal: 8,
412 - enabled: false,
413 - iconPath: 'assets/images/stocks/bacx.webp',
414 - ),
415 - Erc20Token(
416 - name: 'Berkshire Hathaway xStock',
417 - symbol: 'BRK.Bx',
418 - contractAddress: '0x12992613fdd35abe95dec5a4964331b1ee23b50d',
419 - decimal: 8,
420 - enabled: false,
421 - iconPath: 'assets/images/stocks/brkbx.webp',
422 - ),
423 - Erc20Token(
424 - name: 'Broadcom xStock',
425 - symbol: 'AVGOx',
426 - contractAddress: '0x38bac69cbbd28156796e4163b2b6dcb81e336565',
427 - decimal: 8,
428 - enabled: false,
429 - iconPath: 'assets/images/stocks/avgox.webp',
430 - ),
431 - Erc20Token(
432 - name: 'Chevron xStock',
433 - symbol: 'CVXx',
434 - contractAddress: '0xad5cdc3340904285b8159089974a99a1a09eb4c0',
435 - decimal: 8,
436 - enabled: false,
437 - iconPath: 'assets/images/stocks/cvxx.webp',
438 - ),
439 - Erc20Token(
440 - name: 'Circle xStock',
441 - symbol: 'CRCLx',
442 - contractAddress: '0xfebded1b0986a8ee107f5ab1a1c5a813491deceb',
443 - decimal: 8,
444 - enabled: false,
445 - iconPath: 'assets/images/stocks/crclx.webp',
446 - ),
447 - Erc20Token(
448 - name: 'Cisco xStock',
449 - symbol: 'CSCOx',
450 - contractAddress: '0x053c784cd87b74f42e0c089f98643e79c1a3ff16',
451 - decimal: 8,
452 - enabled: false,
453 - iconPath: 'assets/images/stocks/cscox.webp',
454 - ),
455 - Erc20Token(
456 - name: 'Coca-Cola xStock',
457 - symbol: 'KOx',
458 - contractAddress: '0xdcc1a2699441079da889b1f49e12b69cc791129b',
459 - decimal: 8,
460 - enabled: false,
461 - iconPath: 'assets/images/stocks/kox.webp',
462 - ),
463 - Erc20Token(
464 - name: 'Coinbase xStock',
465 - symbol: 'COINx',
466 - contractAddress: '0x364f210f430ec2448fc68a49203040f6124096f0',
467 - decimal: 8,
468 - enabled: false,
469 - iconPath: 'assets/images/stocks/coinx.webp',
470 - ),
471 - Erc20Token(
472 - name: 'Comcast xStock',
473 - symbol: 'CMCSAx',
474 - contractAddress: '0xbc7170a1280be28513b4e940c681537eb25e39f4',
475 - decimal: 8,
476 - enabled: false,
477 - iconPath: 'assets/images/stocks/cmcsax.webp',
478 - ),
479 - Erc20Token(
480 - name: 'CrowdStrike xStock',
481 - symbol: 'CRWDx',
482 - contractAddress: '0x214151022c2a5e380ab80cdac31f23ae554a7345',
483 - decimal: 8,
484 - enabled: false,
485 - iconPath: 'assets/images/stocks/crwdx.webp',
486 - ),
487 - Erc20Token(
488 - name: 'Danaher xStock',
489 - symbol: 'DHRx',
490 - contractAddress: '0xdba228936f4079daf9aa906fd48a87f2300405f4',
491 - decimal: 8,
492 - enabled: false,
493 - iconPath: 'assets/images/stocks/dhrx.webp',
494 - ),
495 - Erc20Token(
496 - name: 'DFDV xStock',
497 - symbol: 'DFDVx',
498 - contractAddress: '0x521860bb5df5468358875266b89bfe90d990c6e7',
499 - decimal: 8,
500 - enabled: false,
501 - iconPath: 'assets/images/stocks/dfdvx.webp',
502 - ),
503 - Erc20Token(
504 - name: 'Eli Lilly xStock',
505 - symbol: 'LLYx',
506 - contractAddress: '0x19c41ea77b34bbdee61c3a87a75d1abda2ed0be4',
507 - decimal: 8,
508 - enabled: false,
509 - iconPath: 'assets/images/stocks/llyx.webp',
510 - ),
511 - Erc20Token(
512 - name: 'Exxon Mobil xStock',
513 - symbol: 'XOMx',
514 - contractAddress: '0xeedb0273c5af792745180e9ff568cd01550ffa13',
515 - decimal: 8,
516 - enabled: false,
517 - iconPath: 'assets/images/stocks/xomx.webp',
518 - ),
519 - Erc20Token(
520 - name: 'Gamestop xStock',
521 - symbol: 'GMEx',
522 - contractAddress: '0xe5f6d3b2405abdfe6f660e63202b25d23763160d',
523 - decimal: 8,
524 - enabled: false,
525 - iconPath: 'assets/images/stocks/gmex.webp',
526 - ),
527 - Erc20Token(
528 - name: 'Gold xStock',
529 - symbol: 'GLDx',
530 - contractAddress: '0x2380f2673c640fb67e2d6b55b44c62f0e0e69da9',
531 - decimal: 8,
532 - enabled: false,
533 - iconPath: 'assets/images/stocks/gldx.webp',
534 - ),
535 - Erc20Token(
536 - name: 'Goldman Sachs xStock',
537 - symbol: 'GSx',
538 - contractAddress: '0x3ee7e9b3a992fd23cd1c363b0e296856b04ab149',
539 - decimal: 8,
540 - enabled: false,
541 - iconPath: 'assets/images/stocks/gsx.webp',
542 - ),
543 - Erc20Token(
544 - name: 'Home Depot xStock',
545 - symbol: 'HDx',
546 - contractAddress: '0x766b0cd6ed6d90b5d49d2c36a3761e9728501ba9',
547 - decimal: 8,
548 - enabled: false,
549 - iconPath: 'assets/images/stocks/hdx.webp',
550 - ),
551 - Erc20Token(
552 - name: 'Honeywell xStock',
553 - symbol: 'HONx',
554 - contractAddress: '0x62a48560861b0b451654bfffdb5be6e47aa8ff1b',
555 - decimal: 8,
556 - enabled: false,
557 - iconPath: 'assets/images/stocks/honx.webp',
558 - ),
559 - Erc20Token(
560 - name: 'Intel xStock',
561 - symbol: 'INTCx',
562 - contractAddress: '0xf8a80d1cb9cfd70d03d655d9df42339846f3b3c8',
563 - decimal: 8,
564 - enabled: false,
565 - iconPath: 'assets/images/stocks/intcx.webp',
566 - ),
567 - Erc20Token(
568 - name: 'International Business Machines xStock',
569 - symbol: 'IBMx',
570 - contractAddress: '0xd9913208647671fe0f48f7f260076b2c6f310aac',
571 - decimal: 8,
572 - enabled: false,
573 - iconPath: 'assets/images/stocks/ibmx.webp',
574 - ),
575 - Erc20Token(
576 - name: 'Johnson & Johnson xStock',
577 - symbol: 'JNJx',
578 - contractAddress: '0xdb0482cfad4789798623e64b15eeba01b16e917c',
579 - decimal: 8,
580 - enabled: false,
581 - iconPath: 'assets/images/stocks/jnjx.webp',
582 - ),
583 - Erc20Token(
584 - name: 'JPMorgan Chase xStock',
585 - symbol: 'JPMx',
586 - contractAddress: '0xd9fc3e075d45254a1d834fea18af8041207dea0a',
587 - decimal: 8,
588 - enabled: false,
589 - iconPath: 'assets/images/stocks/jpmx.webp',
590 - ),
591 - Erc20Token(
592 - name: 'Linde xStock',
593 - symbol: 'LINx',
594 - contractAddress: '0x15059c599c16fd8f70b633ade165502d6402cd49',
595 - decimal: 8,
596 - enabled: false,
597 - iconPath: 'assets/images/stocks/linx.webp',
598 - ),
599 - Erc20Token(
600 - name: 'Marvell xStock',
601 - symbol: 'MRVLx',
602 - contractAddress: '0xeaad46f4146ded5a47b55aa7f6c48c191deaec88',
603 - decimal: 8,
604 - enabled: false,
605 - iconPath: 'assets/images/stocks/mrvlx.webp',
606 - ),
607 - Erc20Token(
608 - name: 'Mastercard xStock',
609 - symbol: 'MAx',
610 - contractAddress: '0xb365cd2588065f522d379ad19e903304f6b622c6',
611 - decimal: 8,
612 - enabled: false,
613 - iconPath: 'assets/images/stocks/mastercard.webp',
614 - ),
615 - Erc20Token(
616 - name: 'McDonald\'s xStock',
617 - symbol: 'MCDx',
618 - contractAddress: '0x80a77a372c1e12accda84299492f404902e2da67',
619 - decimal: 8,
620 - enabled: false,
621 - iconPath: 'assets/images/stocks/mcdonalds.webp',
622 - ),
623 - Erc20Token(
624 - name: 'Medtronic xStock',
625 - symbol: 'MDTx',
626 - contractAddress: '0x0588e851ec0418d660bee81230d6c678daf21d46',
627 - decimal: 8,
628 - enabled: false,
629 - iconPath: 'assets/images/stocks/mdtx.webp',
630 - ),
631 - Erc20Token(
632 - name: 'Merck xStock',
633 - symbol: 'MRKx',
634 - contractAddress: '0x17d8186ed8f68059124190d147174d0f6697dc40',
635 - decimal: 8,
636 - enabled: false,
637 - iconPath: 'assets/images/stocks/mrkx.webp',
638 - ),
639 - Erc20Token(
640 - name: 'Meta xStock',
641 - symbol: 'METAx',
642 - contractAddress: '0x96702be57cd9777f835117a809c7124fe4ec989a',
643 - decimal: 8,
644 - enabled: false,
645 - iconPath: 'assets/images/stocks/metax.webp',
646 - ),
647 - Erc20Token(
648 - name: 'Microsoft xStock',
649 - symbol: 'MSFTx',
650 - contractAddress: '0x5621737f42dae558b81269fcb9e9e70c19aa6b35',
651 - decimal: 8,
652 - enabled: false,
653 - iconPath: 'assets/images/stocks/msftx.webp',
654 - ),
655 - Erc20Token(
656 - name: 'MicroStrategy xStock',
657 - symbol: 'MSTRx',
658 - contractAddress: '0xae2f842ef90c0d5213259ab82639d5bbf649b08e',
659 - decimal: 8,
660 - enabled: false,
661 - iconPath: 'assets/images/stocks/mstrx.webp',
662 - ),
663 - Erc20Token(
664 - name: 'Nasdaq xStock',
665 - symbol: 'QQQx',
666 - contractAddress: '0xa753a7395cae905cd615da0b82a53e0560f250af',
667 - decimal: 8,
668 - enabled: false,
669 - iconPath: 'assets/images/stocks/qqqx.webp',
670 - ),
671 - Erc20Token(
672 - name: 'Netflix xStock',
673 - symbol: 'NFLXx',
674 - contractAddress: '0xa6a65ac27e76cd53cb790473e4345c46e5ebf961',
675 - decimal: 8,
676 - enabled: false,
677 - iconPath: 'assets/images/stocks/nflxx.webp',
678 - ),
679 - Erc20Token(
680 - name: 'Novo Nordisk xStock',
681 - symbol: 'NVOx',
682 - contractAddress: '0xf9523e369c5f55ad72dbaa75b0a9b92b3d8b147e',
683 - decimal: 8,
684 - enabled: false,
685 - iconPath: 'assets/images/stocks/nvox.webp',
686 - ),
687 - Erc20Token(
688 - name: 'NVIDIA xStock',
689 - symbol: 'NVDAx',
690 - contractAddress: '0xc845b2894dbddd03858fd2d643b4ef725fe0849d',
691 - decimal: 8,
692 - enabled: false,
693 - iconPath: 'assets/images/stocks/nvdax.webp',
694 - ),
695 - Erc20Token(
696 - name: 'OPEN xStock',
697 - symbol: 'OPENx',
698 - contractAddress: '0xbee6b69345f376598fe16abd5592c6f844825e66',
699 - decimal: 8,
700 - enabled: false,
701 - iconPath: 'assets/images/stocks/openx.webp',
702 - ),
703 - Erc20Token(
704 - name: 'Oracle xStock',
705 - symbol: 'ORCLx',
706 - contractAddress: '0x548308e91ec9f285c7bff05295badbd56a6e4971',
707 - decimal: 8,
708 - enabled: false,
709 - iconPath: 'assets/images/stocks/orclx.webp',
710 - ),
711 - Erc20Token(
712 - name: 'Palantir xStock',
713 - symbol: 'PLTRx',
714 - contractAddress: '0x6d482cec5f9dd1f05ccee9fd3ff79b246170f8e2',
715 - decimal: 8,
716 - enabled: false,
717 - iconPath: 'assets/images/stocks/pltrx.webp',
718 - ),
719 - Erc20Token(
720 - name: 'PepsiCo xStock',
721 - symbol: 'PEPx',
722 - contractAddress: '0x36c424a6ec0e264b1616102ad63ed2ad7857413e',
723 - decimal: 8,
724 - enabled: false,
725 - iconPath: 'assets/images/stocks/pepx.webp',
726 - ),
727 - Erc20Token(
728 - name: 'Pfizer xStock',
729 - symbol: 'PFEx',
730 - contractAddress: '0x1ac765b5bea23184802c7d2d497f7c33f1444a9e',
731 - decimal: 8,
732 - enabled: false,
733 - iconPath: 'assets/images/stocks/pfex.webp',
734 - ),
735 - Erc20Token(
736 - name: 'Philip Morris xStock',
737 - symbol: 'PMx',
738 - contractAddress: '0x02a6c1789c3b4fdb1a7a3dfa39f90e5d3c94f4f9',
739 - decimal: 8,
740 - enabled: false,
741 - iconPath: 'assets/images/stocks/pmx.webp',
742 - ),
743 - Erc20Token(
744 - name: 'Procter & Gamble xStock',
745 - symbol: 'PGx',
746 - contractAddress: '0xa90424d5d3e770e8644103ab503ed775dd1318fd',
747 - decimal: 8,
748 - enabled: false,
749 - iconPath: 'assets/images/stocks/pgx.webp',
750 - ),
751 - Erc20Token(
752 - name: 'Robinhood xStock',
753 - symbol: 'HOODx',
754 - contractAddress: '0xe1385fdd5ffb10081cd52c56584f25efa9084015',
755 - decimal: 8,
756 - enabled: false,
757 - iconPath: 'assets/images/stocks/hoodx.webp',
758 - ),
759 - Erc20Token(
760 - name: 'Salesforce xStock',
761 - symbol: 'CRMx',
762 - contractAddress: '0x4a4073f2eaf299a1be22254dcd2c41727f6f54a2',
763 - decimal: 8,
764 - enabled: false,
765 - iconPath: 'assets/images/stocks/crmx.webp',
766 - ),
767 - Erc20Token(
768 - name: 'SP500 xStock',
769 - symbol: 'SPYx',
770 - contractAddress: '0x90a2a4c76b5d8c0bc892a69ea28aa775a8f2dd48',
771 - decimal: 8,
772 - enabled: false,
773 - iconPath: 'assets/images/stocks/spyx.webp',
774 - ),
775 - Erc20Token(
776 - name: 'TBLL xStock',
777 - symbol: 'TBLLx',
778 - contractAddress: '0x4cbf89ed7bb30b8a860fa86d3c96e9c72931299b',
779 - decimal: 8,
780 - enabled: false,
781 - iconPath: 'assets/images/stocks/tbllx.webp',
782 - ),
783 - Erc20Token(
784 - name: 'Tesla xStock',
785 - symbol: 'TSLAx',
786 - contractAddress: '0x8ad3c73f833d3f9a523ab01476625f269aeb7cf0',
787 - decimal: 8,
788 - enabled: false,
789 - iconPath: 'assets/images/stocks/tslax.webp',
790 - ),
791 - Erc20Token(
792 - name: 'Thermo Fisher xStock',
793 - symbol: 'TMOx',
794 - contractAddress: '0xaf072f109a2c173d822a4fe9af311a1b18f83d19',
795 - decimal: 8,
796 - enabled: false,
797 - iconPath: 'assets/images/stocks/tmox.webp',
798 - ),
799 - Erc20Token(
800 - name: 'TON xStock',
801 - symbol: 'TONXx',
802 - contractAddress: '0xe95ab205e333443d7970336d5fd827ef9ed97608',
803 - decimal: 8,
804 - enabled: false,
805 - iconPath: 'assets/images/stocks/tonxx.webp',
806 - ),
807 - Erc20Token(
808 - name: 'TQQQ xStock',
809 - symbol: 'TQQQx',
810 - contractAddress: '0xfdddb57878ef9d6f681ec4381dcb626b9e69ac86',
811 - decimal: 8,
812 - enabled: false,
813 - iconPath: 'assets/images/stocks/tqqqx.webp',
814 - ),
815 - Erc20Token(
816 - name: 'UnitedHealth xStock',
817 - symbol: 'UNHx',
818 - contractAddress: '0x167a6375da1efc4a5be0f470e73ecefd66245048',
819 - decimal: 8,
820 - enabled: false,
821 - iconPath: 'assets/images/stocks/unhx.webp',
822 - ),
823 - Erc20Token(
824 - name: 'Vanguard xStock',
825 - symbol: 'VTIx',
826 - contractAddress: '0xbd730e618bcd88c82ddee52e10275cf2f88a4777',
827 - decimal: 8,
828 - enabled: false,
829 - iconPath: 'assets/images/stocks/vtix.webp',
830 - ),
831 - Erc20Token(
832 - name: 'Visa xStock',
833 - symbol: 'Vx',
834 - contractAddress: '0x2363fd1235c1b6d3a5088ddf8df3a0b3a30c5293',
835 - decimal: 8,
836 - enabled: false,
837 - iconPath: 'assets/images/stocks/vx.webp',
838 - ),
839 - Erc20Token(
840 - name: 'Walmart xStock',
841 - symbol: 'WMTx',
842 - contractAddress: '0x7aefc9965699fbea943e03264d96e50cd4a97b21',
843 - decimal: 8,
844 - enabled: false,
845 - iconPath: 'assets/images/stocks/wmtx.webp',
846 - ),
847 - ];
848 -
849 - List<Erc20Token> get initialErc20Tokens => _defaultTokens.map((token) {
850 - String? iconPath;
851 - if (token.iconPath?.isEmpty ?? true) {
852 - try {
853 - iconPath = CryptoCurrency.all
854 - .firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase())
855 - .iconPath;
856 - } catch (_) {}
857 - } else {
858 - iconPath = token.iconPath;
859 - }
860 -
861 - return Erc20Token.copyWith(token, icon: iconPath, tag: 'ETH');
862 - }).toList();
863 -}
cw_ethereum/lib/ethereum_client.dart deleted
-76
@@ -1,76 +0,0 @@
1 -import 'dart:convert';
2 -import 'dart:developer';
3 -import 'dart:typed_data';
4 -
5 -import 'package:cw_evm/evm_chain_client.dart';
6 -import 'package:cw_evm/.secrets.g.dart' as secrets;
7 -import 'package:cw_evm/evm_chain_transaction_model.dart';
8 -import 'package:web3dart/web3dart.dart';
9 -
10 -class EthereumClient extends EVMChainClient {
11 - @override
12 - int get chainId => 1;
13 -
14 - @override
15 - Uint8List prepareSignedTransactionForSending(Uint8List signedTransaction) =>
16 - prependTransactionType(0x02, signedTransaction);
17 -
18 - @override
19 - Future<List<EVMChainTransactionModel>> fetchTransactions(String address,
20 - {String? contractAddress}) async {
21 - try {
22 - final response = await client.get(Uri.https("api.etherscan.io", "/v2/api", {
23 - "chainid": "$chainId",
24 - "module": "account",
25 - "action": contractAddress != null ? "tokentx" : "txlist",
26 - if (contractAddress != null) "contractaddress": contractAddress,
27 - "address": address,
28 - "apikey": secrets.etherScanApiKey,
29 - }));
30 -
31 - final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
32 -
33 - if (jsonResponse['result'] is String) {
34 - log(jsonResponse['result']);
35 - return [];
36 - }
37 -
38 - if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) {
39 - return (jsonResponse['result'] as List)
40 - .map((e) => EVMChainTransactionModel.fromJson(e as Map<String, dynamic>, 'ETH'))
41 - .toList();
42 - }
43 -
44 - return [];
45 - } catch (e) {
46 - log(e.toString());
47 - return [];
48 - }
49 - }
50 -
51 - @override
52 - Future<List<EVMChainTransactionModel>> fetchInternalTransactions(String address) async {
53 - try {
54 - final response = await client.get(Uri.https("api.etherscan.io", "/v2/api", {
55 - "chainid": "$chainId",
56 - "module": "account",
57 - "action": "txlistinternal",
58 - "address": address,
59 - "apikey": secrets.etherScanApiKey,
60 - }));
61 -
62 - final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
63 -
64 - if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) {
65 - return (jsonResponse['result'] as List)
66 - .map((e) => EVMChainTransactionModel.fromJson(e as Map<String, dynamic>, 'ETH'))
67 - .toList();
68 - }
69 -
70 - return [];
71 - } catch (e) {
72 - log(e.toString());
73 - return [];
74 - }
75 - }
76 -}
cw_ethereum/lib/ethereum_mnemonics_exception.dart deleted
-5
@@ -1,5 +0,0 @@
1 -class EthereumMnemonicIsIncorrectException implements Exception {
2 - @override
3 - String toString() =>
4 - 'Ethereum mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.';
5 -}
cw_ethereum/lib/ethereum_transaction_history.dart deleted
-19
@@ -1,19 +0,0 @@
1 -import 'dart:core';
2 -import 'package:cw_ethereum/ethereum_transaction_info.dart';
3 -import 'package:cw_evm/evm_chain_transaction_history.dart';
4 -import 'package:cw_evm/evm_chain_transaction_info.dart';
5 -
6 -class EthereumTransactionHistory extends EVMChainTransactionHistory {
7 - EthereumTransactionHistory({
8 - required super.walletInfo,
9 - required super.password,
10 - required super.encryptionFileUtils,
11 - });
12 -
13 - @override
14 - String getTransactionHistoryFileName() => 'transactions.json';
15 -
16 - @override
17 - EVMChainTransactionInfo getTransactionInfo(Map<String, dynamic> val) =>
18 - EthereumTransactionInfo.fromJson(val);
19 -}
cw_ethereum/lib/ethereum_transaction_info.dart deleted
-43
@@ -1,43 +0,0 @@
1 -import 'package:cw_core/transaction_direction.dart';
2 -import 'package:cw_evm/evm_chain_transaction_info.dart';
3 -
4 -class EthereumTransactionInfo extends EVMChainTransactionInfo {
5 - EthereumTransactionInfo({
6 - required super.id,
7 - required super.height,
8 - required super.ethAmount,
9 - required super.ethFee,
10 - required super.tokenSymbol,
11 - required super.direction,
12 - required super.isPending,
13 - required super.date,
14 - required super.confirmations,
15 - required super.to,
16 - required super.from,
17 - super.contractAddress,
18 - super.evmSignatureName,
19 - super.exponent,
20 - });
21 -
22 - factory EthereumTransactionInfo.fromJson(Map<String, dynamic> data) {
23 - return EthereumTransactionInfo(
24 - id: data['id'] as String,
25 - height: data['height'] as int,
26 - ethAmount: BigInt.parse(data['amount']),
27 - exponent: data['exponent'] as int,
28 - ethFee: BigInt.parse(data['fee']),
29 - direction: parseTransactionDirectionFromInt(data['direction'] as int),
30 - date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
31 - isPending: data['isPending'] as bool,
32 - confirmations: data['confirmations'] as int,
33 - tokenSymbol: data['tokenSymbol'] as String,
34 - to: data['to'],
35 - from: data['from'],
36 - evmSignatureName: data['evmSignatureName'],
37 - contractAddress: data['contractAddress'],
38 - );
39 - }
40 -
41 - @override
42 - String get feeCurrency => 'ETH';
43 -}
cw_ethereum/lib/ethereum_wallet.dart deleted
-199
@@ -1,199 +0,0 @@
1 -import 'dart:convert';
2 -
3 -import 'package:cw_core/cake_hive.dart';
4 -import 'package:cw_core/crypto_currency.dart';
5 -import 'package:cw_core/encryption_file_utils.dart';
6 -import 'package:cw_core/erc20_token.dart';
7 -import 'package:cw_core/pathForWallet.dart';
8 -import 'package:cw_core/transaction_direction.dart';
9 -import 'package:cw_core/wallet_info.dart';
10 -import 'package:cw_core/wallet_keys_file.dart';
11 -import 'package:cw_ethereum/default_ethereum_erc20_tokens.dart';
12 -import 'package:cw_ethereum/ethereum_client.dart';
13 -import 'package:cw_ethereum/ethereum_transaction_history.dart';
14 -import 'package:cw_ethereum/ethereum_transaction_info.dart';
15 -import 'package:cw_evm/evm_chain_transaction_history.dart';
16 -import 'package:cw_evm/evm_chain_transaction_info.dart';
17 -import 'package:cw_evm/evm_chain_transaction_model.dart';
18 -import 'package:cw_evm/evm_chain_transaction_priority.dart';
19 -import 'package:cw_evm/evm_chain_wallet.dart';
20 -import 'package:cw_evm/evm_erc20_balance.dart';
21 -import 'package:web3dart/web3dart.dart';
22 -
23 -class EthereumWallet extends EVMChainWallet {
24 - EthereumWallet({
25 - required super.client,
26 - required super.password,
27 - required super.walletInfo,
28 - required super.derivationInfo,
29 - super.mnemonic,
30 - super.initialBalance,
31 - super.privateKey,
32 - required super.encryptionFileUtils,
33 - super.passphrase,
34 - }) : super(nativeCurrency: CryptoCurrency.eth);
35 -
36 - @override
37 - int getTotalPriorityFee(EVMChainTransactionPriority priority) {
38 - return EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
39 - }
40 -
41 - @override
42 - void addInitialTokens() {
43 - final initialErc20Tokens = DefaultEthereumErc20Tokens().initialErc20Tokens;
44 -
45 - for (final token in initialErc20Tokens) {
46 - if (!evmChainErc20TokensBox.containsKey(token.contractAddress)) {
47 - evmChainErc20TokensBox.put(token.contractAddress, token);
48 - } else {
49 - // update existing token
50 - final existingToken = evmChainErc20TokensBox.get(token.contractAddress);
51 - evmChainErc20TokensBox.put(
52 - token.contractAddress, Erc20Token.copyWith(token, enabled: existingToken!.enabled));
53 - }
54 - }
55 - }
56 -
57 - @override
58 - Future<bool> checkIfScanProviderIsEnabled() async {
59 - bool isEtherscanEnabled = (await sharedPrefs.future).getBool("use_etherscan") ?? true;
60 - return isEtherscanEnabled;
61 - }
62 -
63 - @override
64 - Future<void> initErc20TokensBox() async {
65 - // This is for ethereum wallets,
66 - // Other wallets would override and initialize their respective boxes with their boxNames.
67 - await movePreviousErc20BoxConfigsToNewBox();
68 - }
69 -
70 - /// Majorly for backward compatibility for previous configs that have been set.
71 - Future<void> movePreviousErc20BoxConfigsToNewBox() async {
72 - // Opens a box specific to this wallet
73 - evmChainErc20TokensBox = await CakeHive.openBox<Erc20Token>(
74 - "${walletInfo.name.replaceAll(" ", "_")}_${Erc20Token.ethereumBoxName}");
75 -
76 - //Open the previous token configs box
77 - erc20TokensBox = await CakeHive.openBox<Erc20Token>(Erc20Token.boxName);
78 -
79 - // Check if it's empty, if it is, we stop the flow and return.
80 - if (erc20TokensBox.isEmpty) {
81 - // If it's empty, but the new wallet specific box is also empty,
82 - // we load the initial tokens to the new box.
83 - if (evmChainErc20TokensBox.isEmpty) addInitialTokens();
84 - return;
85 - }
86 -
87 - final allValues = erc20TokensBox.values.toList();
88 -
89 - // Clear and delete the old token box
90 - await erc20TokensBox.clear();
91 - await erc20TokensBox.deleteFromDisk();
92 -
93 - // Add all the previous tokens with configs to the new box
94 - await evmChainErc20TokensBox.addAll(allValues);
95 - }
96 -
97 - @override
98 - List<String> get getDefaultTokenContractAddresses =>
99 - DefaultEthereumErc20Tokens().initialErc20Tokens.map((e) => e.contractAddress).toList();
100 -
101 - @override
102 - EVMChainTransactionInfo getTransactionInfo(
103 - EVMChainTransactionModel transactionModel, String address) {
104 - final model = EthereumTransactionInfo(
105 - id: transactionModel.hash,
106 - height: transactionModel.blockNumber,
107 - ethAmount: transactionModel.amount,
108 - direction: transactionModel.from == address
109 - ? TransactionDirection.outgoing
110 - : TransactionDirection.incoming,
111 - isPending: false,
112 - date: transactionModel.date,
113 - confirmations: transactionModel.confirmations,
114 - ethFee: BigInt.from(transactionModel.gasUsed) * transactionModel.gasPrice,
115 - exponent: transactionModel.tokenDecimal ?? 18,
116 - tokenSymbol: transactionModel.tokenSymbol ?? "ETH",
117 - to: transactionModel.to,
118 - from: transactionModel.from,
119 - evmSignatureName: transactionModel.evmSignatureName,
120 - contractAddress: transactionModel.contractAddress,
121 - );
122 - return model;
123 - }
124 -
125 - @override
126 - String getTransactionHistoryFileName() => 'transactions.json';
127 -
128 - @override
129 - Erc20Token createNewErc20TokenObject(Erc20Token token, String? iconPath) {
130 - return Erc20Token(
131 - name: token.name,
132 - symbol: token.symbol,
133 - contractAddress: token.contractAddress,
134 - decimal: token.decimal,
135 - enabled: token.enabled,
136 - tag: token.tag ?? "ETH",
137 - iconPath: iconPath,
138 - isPotentialScam: token.isPotentialScam,
139 - );
140 - }
141 -
142 - @override
143 - EVMChainTransactionHistory setUpTransactionHistory(
144 - WalletInfo walletInfo, String password, EncryptionFileUtils encryptionFileUtils) {
145 - return EthereumTransactionHistory(
146 - walletInfo: walletInfo, password: password, encryptionFileUtils: encryptionFileUtils);
147 - }
148 -
149 - static Future<EthereumWallet> open({
150 - required String name,
151 - required String password,
152 - required WalletInfo walletInfo,
153 - required EncryptionFileUtils encryptionFileUtils,
154 - }) async {
155 - final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
156 - final path = await pathForWallet(name: name, type: walletInfo.type);
157 -
158 - Map<String, dynamic>? data;
159 - try {
160 - final jsonSource = await encryptionFileUtils.read(path: path, password: password);
161 -
162 - data = json.decode(jsonSource) as Map<String, dynamic>;
163 - } catch (e) {
164 - if (!hasKeysFile) rethrow;
165 - }
166 -
167 - final balance = EVMChainERC20Balance.fromJSON(data?['balance'] as String?) ??
168 - EVMChainERC20Balance(BigInt.zero);
169 -
170 - final WalletKeysData keysData;
171 - // Migrate wallet from the old scheme to then new .keys file scheme
172 - if (!hasKeysFile) {
173 - final mnemonic = data!['mnemonic'] as String?;
174 - final privateKey = data['private_key'] as String?;
175 - final passphrase = data['passphrase'] as String?;
176 -
177 - keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey, passphrase: passphrase);
178 - } else {
179 - keysData = await WalletKeysFile.readKeysFile(
180 - name,
181 - walletInfo.type,
182 - password,
183 - encryptionFileUtils,
184 - );
185 - }
186 -
187 - return EthereumWallet(
188 - walletInfo: walletInfo,
189 - derivationInfo: await walletInfo.getDerivationInfo(),
190 - password: password,
191 - mnemonic: keysData.mnemonic,
192 - privateKey: keysData.privateKey,
193 - passphrase: keysData.passphrase,
194 - initialBalance: balance,
195 - client: EthereumClient(),
196 - encryptionFileUtils: encryptionFileUtils,
197 - );
198 - }
199 -}
cw_ethereum/lib/ethereum_wallet_service.dart deleted
-170
@@ -1,170 +0,0 @@
1 -import 'package:bip39/bip39.dart' as bip39;
2 -import 'package:cw_core/encryption_file_utils.dart';
3 -import 'package:cw_core/wallet_base.dart';
4 -import 'package:cw_core/wallet_info.dart';
5 -import 'package:cw_core/wallet_type.dart';
6 -import 'package:cw_ethereum/ethereum_client.dart';
7 -import 'package:cw_ethereum/ethereum_mnemonics_exception.dart';
8 -import 'package:cw_ethereum/ethereum_wallet.dart';
9 -import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
10 -import 'package:cw_evm/evm_chain_wallet_service.dart';
11 -
12 -class EthereumWalletService extends EVMChainWalletService<EthereumWallet> {
13 - EthereumWalletService(super.isDirect, {required this.client});
14 -
15 - late EthereumClient client;
16 -
17 - @override
18 - WalletType getType() => WalletType.ethereum;
19 -
20 - @override
21 - Future<EthereumWallet> create(EVMChainNewWalletCredentials credentials, {bool? isTestnet}) async {
22 - final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
23 -
24 - final mnemonic = credentials.mnemonic ?? bip39.generateMnemonic(strength: strength);
25 -
26 - final wallet = EthereumWallet(
27 - walletInfo: credentials.walletInfo!,
28 - derivationInfo: await credentials.walletInfo!.getDerivationInfo(),
29 - mnemonic: mnemonic,
30 - password: credentials.password!,
31 - passphrase: credentials.passphrase,
32 - client: client,
33 - encryptionFileUtils: encryptionFileUtilsFor(isDirect),
34 - );
35 -
36 - await wallet.init();
37 - wallet.addInitialTokens();
38 - await wallet.save();
39 -
40 - return wallet;
41 - }
42 -
43 - @override
44 - Future<EthereumWallet> openWallet(String name, String password) async {
45 - final walletInfo = await WalletInfo.get(name, getType());
46 - if (walletInfo == null) {
47 - throw Exception('Wallet not found');
48 - }
49 -
50 - try {
51 - final wallet = await EthereumWallet.open(
52 - name: name,
53 - password: password,
54 - walletInfo: walletInfo,
55 - encryptionFileUtils: encryptionFileUtilsFor(isDirect),
56 - );
57 -
58 - await wallet.init();
59 - wallet.addInitialTokens();
60 - await wallet.save();
61 - saveBackup(name);
62 - return wallet;
63 - } catch (_) {
64 - await restoreWalletFilesFromBackup(name);
65 -
66 - final wallet = await EthereumWallet.open(
67 - name: name,
68 - password: password,
69 - walletInfo: walletInfo,
70 - encryptionFileUtils: encryptionFileUtilsFor(isDirect),
71 - );
72 - await wallet.init();
73 - wallet.addInitialTokens();
74 - await wallet.save();
75 - return wallet;
76 - }
77 - }
78 -
79 - @override
80 - Future<void> rename(String currentName, String password, String newName) async {
81 - final currentWalletInfo = await WalletInfo.get(currentName, getType());
82 - if (currentWalletInfo == null) {
83 - throw Exception('Wallet not found');
84 - }
85 - final currentWallet = await EthereumWallet.open(
86 - password: password,
87 - name: currentName,
88 - walletInfo: currentWalletInfo,
89 - encryptionFileUtils: encryptionFileUtilsFor(isDirect),
90 - );
91 -
92 - await currentWallet.renameWalletFiles(newName);
93 - await saveBackup(newName);
94 -
95 - final newWalletInfo = currentWalletInfo;
96 - newWalletInfo.id = WalletBase.idFor(newName, getType());
97 - newWalletInfo.name = newName;
98 -
99 - await newWalletInfo.save();
100 - }
101 -
102 - @override
103 - Future<EthereumWallet> restoreFromHardwareWallet(
104 - EVMChainRestoreWalletFromHardware credentials) async {
105 - final di = await credentials.walletInfo!.getDerivationInfo();
106 - di.derivationType = DerivationType.bip39;
107 - di.derivationPath = "m/44'/60'/${credentials.hwAccountData.accountIndex}'/0/0";
108 - await di.save();
109 - credentials.walletInfo!.hardwareWalletType = credentials.hardwareWalletType;
110 - credentials.walletInfo!.address = credentials.hwAccountData.address;
111 - credentials.walletInfo!.save();
112 -
113 - final wallet = EthereumWallet(
114 - walletInfo: credentials.walletInfo!,
115 - derivationInfo: di,
116 - password: credentials.password!,
117 - client: client,
118 - encryptionFileUtils: encryptionFileUtilsFor(isDirect),
119 - );
120 -
121 - await wallet.init();
122 - wallet.addInitialTokens();
123 - await wallet.save();
124 -
125 - return wallet;
126 - }
127 -
128 - @override
129 - Future<EthereumWallet> restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials,
130 - {bool? isTestnet}) async {
131 - final wallet = EthereumWallet(
132 - password: credentials.password!,
133 - privateKey: credentials.privateKey,
134 - walletInfo: credentials.walletInfo!,
135 - derivationInfo: await credentials.walletInfo!.getDerivationInfo(),
136 - client: client,
137 - encryptionFileUtils: encryptionFileUtilsFor(isDirect),
138 - );
139 -
140 - await wallet.init();
141 - wallet.addInitialTokens();
142 - await wallet.save();
143 -
144 - return wallet;
145 - }
146 -
147 - @override
148 - Future<EthereumWallet> restoreFromSeed(EVMChainRestoreWalletFromSeedCredentials credentials,
149 - {bool? isTestnet}) async {
150 - if (!bip39.validateMnemonic(credentials.mnemonic)) {
151 - throw EthereumMnemonicIsIncorrectException();
152 - }
153 -
154 - final wallet = EthereumWallet(
155 - password: credentials.password!,
156 - mnemonic: credentials.mnemonic,
157 - walletInfo: credentials.walletInfo!,
158 - derivationInfo: await credentials.walletInfo!.getDerivationInfo(),
159 - passphrase: credentials.passphrase,
160 - client: client,
161 - encryptionFileUtils: encryptionFileUtilsFor(isDirect),
162 - );
163 -
164 - await wallet.init();
165 - wallet.addInitialTokens();
166 - await wallet.save();
167 -
168 - return wallet;
169 - }
170 -}
cw_ethereum/pubspec.yaml deleted
-43
@@ -1,43 +0,0 @@
1 -name: cw_ethereum
2 -description: A new Flutter package project.
3 -version: 0.0.1
4 -publish_to: none
5 -author: Cake Wallet
6 -homepage: https://cakewallet.com
7 -
8 -environment:
9 - sdk: ^3.5.0
10 - flutter: ">=1.17.0"
11 -
12 -dependencies:
13 - flutter:
14 - sdk: flutter
15 - web3dart: ^2.7.1
16 - cw_core:
17 - path: ../cw_core
18 - cw_evm:
19 - path: ../cw_evm
20 - hive: ^2.2.3
21 -
22 -dependency_overrides:
23 - web3dart:
24 - git:
25 - url: https://github.com/cake-tech/web3dart.git
26 - ref: cake
27 - watcher: ^1.1.0
28 -
29 -dev_dependencies:
30 - flutter_test:
31 - sdk: flutter
32 - build_runner: ^2.4.15
33 -
34 -flutter:
35 - # assets:
36 - # - images/a_dot_burr.jpeg
37 - # - images/a_dot_ham.jpeg
38 - # fonts:
39 - # - family: Schyler
40 - # fonts:
41 - # - asset: fonts/Schyler-Regular.ttf
42 - # - asset: fonts/Schyler-Italic.ttf
43 - # style: italic
cw_ethereum/test/cw_ethereum_test.dart deleted
-12
@@ -1,12 +0,0 @@
1 -import 'package:flutter_test/flutter_test.dart';
2 -
3 -import 'package:cw_ethereum/cw_ethereum.dart';
4 -
5 -void main() {
6 - test('adds one to input values', () {
7 - final calculator = Calculator();
8 - expect(calculator.addOne(2), 3);
9 - expect(calculator.addOne(-7), -6);
10 - expect(calculator.addOne(0), 1);
11 - });
12 -}
cw_evm/lib/clients/arbitrum_client.dart new
+46
@@ -0,0 +1,46 @@
1 +import 'package:cw_evm/clients/evm_chain_client.dart';
2 +import 'package:flutter/foundation.dart';
3 +import 'package:web3dart/web3dart.dart';
4 +
5 +class ArbitrumClient extends EVMChainClient {
6 + ArbitrumClient() : super(chainId: 42161);
7 +
8 + @override
9 + Transaction createTransaction({
10 + required EthereumAddress from,
11 + required EthereumAddress to,
12 + required EtherAmount amount,
13 + EtherAmount? maxPriorityFeePerGas,
14 + Uint8List? data,
15 + int? maxGas,
16 + EtherAmount? gasPrice,
17 + EtherAmount? maxFeePerGas,
18 + int? nonce,
19 + }) {
20 + EtherAmount? finalGasPrice = gasPrice;
21 +
22 + if (gasPrice == null && maxFeePerGas != null) {
23 + // If we have EIP-1559 parameters but no legacy gasPrice, then use maxFeePerGas as gasPrice
24 + finalGasPrice = maxFeePerGas;
25 + }
26 +
27 + return Transaction(
28 + from: from,
29 + to: to,
30 + value: amount,
31 + data: data,
32 + maxGas: maxGas,
33 + gasPrice: finalGasPrice,
34 + nonce: nonce,
35 + // maxFeePerGas: maxFeePerGas,
36 + // maxPriorityFeePerGas: maxPriorityFeePerGas,
37 + );
38 + }
39 +
40 + @override
41 + Uint8List prepareSignedTransactionForSending(Uint8List signedTransaction) => signedTransaction;
42 +
43 + @override
44 + int get chainId => 42161;
45 +}
46 +
cw_evm/lib/clients/base_client.dart new
+46
@@ -0,0 +1,46 @@
1 +import 'package:cw_evm/clients/evm_chain_client.dart';
2 +import 'package:flutter/foundation.dart';
3 +import 'package:web3dart/web3dart.dart';
4 +
5 +class BaseClient extends EVMChainClient {
6 + BaseClient() : super(chainId: 8453);
7 +
8 + @override
9 + Transaction createTransaction({
10 + required EthereumAddress from,
11 + required EthereumAddress to,
12 + required EtherAmount amount,
13 + EtherAmount? maxPriorityFeePerGas,
14 + Uint8List? data,
15 + int? maxGas,
16 + EtherAmount? gasPrice,
17 + EtherAmount? maxFeePerGas,
18 + int? nonce,
19 + }) {
20 + EtherAmount? finalGasPrice = gasPrice;
21 +
22 + if (gasPrice == null && maxFeePerGas != null) {
23 + // If we have EIP-1559 parameters but no legacy gasPrice, then use maxFeePerGas as gasPrice
24 + finalGasPrice = maxFeePerGas;
25 + }
26 +
27 + return Transaction(
28 + from: from,
29 + to: to,
30 + value: amount,
31 + data: data,
32 + maxGas: maxGas,
33 + gasPrice: finalGasPrice,
34 + nonce: nonce,
35 + // maxFeePerGas: maxFeePerGas,
36 + // maxPriorityFeePerGas: maxPriorityFeePerGas,
37 + );
38 + }
39 +
40 + @override
41 + Uint8List prepareSignedTransactionForSending(Uint8List signedTransaction) => signedTransaction;
42 +
43 + @override
44 + int get chainId => 8453;
45 +}
46 +
cw_evm/lib/clients/ethereum_client.dart new
+13
@@ -0,0 +1,13 @@
1 +import 'dart:typed_data';
2 +
3 +import 'package:cw_evm/clients/evm_chain_client.dart';
4 +import 'package:web3dart/web3dart.dart';
5 +
6 +class EthereumClient extends EVMChainClient {
7 + EthereumClient() : super(chainId: 1);
8 +
9 + @override
10 + Uint8List prepareSignedTransactionForSending(Uint8List signedTransaction) =>
11 + prependTransactionType(0x02, signedTransaction);
12 +}
13 +
cw_evm/lib/clients/evm_chain_client.dart renamed
+219 -66
@@ -5,32 +5,128 @@ import 'dart:developer';
5 import 'package:cw_core/crypto_currency.dart';
6 import 'package:cw_core/erc20_token.dart';
7 import 'package:cw_core/node.dart';
8 +import 'package:cw_core/utils/print_verbose.dart';
9 import 'package:cw_core/utils/proxy_wrapper.dart';
10 import 'package:cw_evm/evm_chain_transaction_model.dart';
11 import 'package:cw_evm/evm_chain_transaction_priority.dart';
12 import 'package:cw_evm/evm_erc20_balance.dart';
13 import 'package:cw_evm/pending_evm_chain_transaction.dart';
14 import 'package:cw_evm/.secrets.g.dart' as secrets;
15 +import 'package:cw_evm/utils/evm_chain_utils.dart';
16 import 'package:flutter/foundation.dart';
17 import 'package:hex/hex.dart' as hex;
18 import 'package:web3dart/web3dart.dart';
19
18 -import 'contract/erc20.dart';
20 +import '../contract/erc20.dart';
21
20 -abstract class EVMChainClient {
22 +class EVMChainClient {
23 late final client = ProxyWrapper().getHttpIOClient();
24 Web3Client? _client;
25 + final int _chainId;
26
24 - //! To be overridden by all child classes
27 + EVMChainClient({required int chainId}) : _chainId = chainId;
28
26 - int get chainId;
29 + //! Can be overridden by child classes
30 +
31 + int get chainId => _chainId;
32
33 Future<List<EVMChainTransactionModel>> fetchTransactions(String address,
29 - {String? contractAddress});
34 + {String? contractAddress}) async {
35 + try {
36 + if (secrets.etherScanApiKey.isEmpty) {
37 + printV('Etherscan API key is empty, cannot fetch transactions');
38 + return [];
39 + }
40 +
41 + /// when adding new chains, make sure they are supported by the same api through https://docs.etherscan.io/supported-chains
42 + final response = await client.get(Uri.https("api.etherscan.io", "/v2/api", {
43 + "chainid": "$chainId",
44 + "module": "account",
45 + "action": contractAddress != null ? "tokentx" : "txlist",
46 + if (contractAddress != null) "contractaddress": contractAddress,
47 + "address": address,
48 + "apikey": secrets.etherScanApiKey,
49 + }));
50 +
51 + final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
52 +
53 + if (jsonResponse['result'] is String) {
54 + log(jsonResponse['result']);
55 + return [];
56 + }
57 +
58 + if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) {
59 + final res = (jsonResponse['result'] as List);
60 + res.removeWhere((e) => e['value'] == '0');
61 +
62 + // Filter out spam native transactions below 0.00001 ETH (10000000000000 wei)
63 + if (contractAddress == null) {
64 + final spamThresholdWei = BigInt.from(10000000000000);
65 + res.removeWhere((e) {
66 + try {
67 + final value = BigInt.parse(e['value'] ?? '0');
68 + final isIncoming = e['to']?.toLowerCase() == address.toLowerCase() &&
69 + e['from']?.toLowerCase() != address.toLowerCase();
70 + return isIncoming && value < spamThresholdWei;
71 + } catch (_) {
72 + return false;
73 + }
74 + });
75 + }
76 +
77 + final symbol = EVMChainUtils.getFeeCurrency(chainId);
78 +
79 + return res
80 + .map(
81 + (e) => EVMChainTransactionModel.fromJson(e as Map<String, dynamic>, symbol, chainId),
82 + )
83 + .toList();
84 + }
85 +
86 + return [];
87 + } catch (e) {
88 + log(e.toString());
89 + return [];
90 + }
91 + }
92 +
93 + Future<List<EVMChainTransactionModel>> fetchInternalTransactions(String address) async {
94 + try {
95 + if (secrets.etherScanApiKey.isEmpty) {
96 + printV('Etherscan API key is empty, cannot fetch internal transactions');
97 + return [];
98 + }
99 +
100 + final response = await client.get(Uri.https("api.etherscan.io", "/v2/api", {
101 + "chainid": "$chainId",
102 + "module": "account",
103 + "action": "txlistinternal",
104 + "address": address,
105 + "apikey": secrets.etherScanApiKey,
106 + }));
107 +
108 + final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
109
31 - Future<List<EVMChainTransactionModel>> fetchInternalTransactions(String address);
110 + if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) {
111 + final symbol = EVMChainUtils.getFeeCurrency(chainId);
112
33 - Uint8List prepareSignedTransactionForSending(Uint8List signedTransaction);
113 + return (jsonResponse['result'] as List)
114 + .map((e) =>
115 + EVMChainTransactionModel.fromJson(e as Map<String, dynamic>, symbol, chainId))
116 + .toList();
117 + }
118 +
119 + printV(
120 + 'Etherscan API returned invalid response for internal transactions: status=${jsonResponse['status']}, statusCode=${response.statusCode}');
121 + return [];
122 + } catch (e, stackTrace) {
123 + printV('Error fetching internal transactions: ${e.toString()}');
124 + printV('Stack trace: ${stackTrace.toString()}');
125 + return [];
126 + }
127 + }
128 +
129 + Uint8List prepareSignedTransactionForSending(Uint8List signedTransaction) => signedTransaction;
130
131 //! Common methods across all child classes
132
@@ -43,6 +139,11 @@ abstract class EVMChainClient {
139 isModifiedNodeUri = true;
140 String nowNodeApiKey = secrets.nowNodesApiKey;
141
142 + if (nowNodeApiKey.isEmpty) {
143 + printV('NowNodes API key is empty, cannot connect to ${node.uriRaw}');
144 + return false;
145 + }
146 +
147 rpcUri = Uri.https(node.uriRaw, '/$nowNodeApiKey');
148 }
149
@@ -50,6 +151,7 @@ abstract class EVMChainClient {
151
152 return true;
153 } catch (e) {
154 + printV('Error connecting to node ${node.uriRaw}: ${e.toString()}');
155 return false;
156 }
157 }
@@ -64,14 +166,11 @@ abstract class EVMChainClient {
166 // });
167 }
168
67 - Future<EtherAmount> getBalance(EthereumAddress address, {bool throwOnError = false}) async {
169 + Future<EtherAmount> getBalance(EthereumAddress address) async {
170 try {
171 return await _client!.getBalance(address);
172 } catch (_) {
71 - if (throwOnError) {
72 - rethrow;
73 - }
74 - return EtherAmount.zero();
173 + rethrow;
174 }
175 }
176
@@ -80,8 +179,9 @@ abstract class EVMChainClient {
179 final gasPrice = await _client!.getGasPrice();
180
181 return gasPrice.getInWei.toInt();
83 - } catch (_) {
84 - return 0;
182 + } catch (e) {
183 + printV('Error getting gas unit price: ${e.toString()}');
184 + rethrow;
185 }
186 }
187
@@ -91,8 +191,9 @@ abstract class EVMChainClient {
191 final baseFee = blockInfo.baseFeePerGas;
192
193 return baseFee?.getInWei.toInt();
94 - } catch (_) {
95 - return 0;
194 + } catch (e) {
195 + printV('Error getting gas base fee: ${e.toString()}');
196 + return null;
197 }
198 }
199
@@ -170,6 +271,7 @@ abstract class EVMChainClient {
271 String? contractAddress,
272 String? data,
273 int? gasPrice,
274 + bool useBlinkProtection = true,
275 }) async {
276 assert(currency == CryptoCurrency.eth ||
277 currency == CryptoCurrency.maticpoly ||
@@ -182,15 +284,24 @@ abstract class EVMChainClient {
284 currency == CryptoCurrency.baseEth ||
285 currency == CryptoCurrency.arbEth;
286
287 + // Get nonce with "pending" block tag to include pending transactions
288 + // This prevents "Nonce too low" errors when sending multiple transactions quickly
289 + final nonce = await _client!.getTransactionCount(
290 + privateKey.address,
291 + atBlock: const BlockNum.pending(),
292 + );
293 +
294 final Transaction transaction = createTransaction(
295 from: privateKey.address,
296 to: EthereumAddress.fromHex(toAddress),
188 - maxPriorityFeePerGas: priority != null ? EtherAmount.fromInt(EtherUnit.gwei, priority.tip) : null,
297 + maxPriorityFeePerGas:
298 + priority != null ? EtherAmount.fromInt(EtherUnit.gwei, priority.tip) : null,
299 amount: isNativeToken ? EtherAmount.inWei(amount) : EtherAmount.zero(),
300 data: data != null ? hexToBytes(data) : null,
301 maxGas: estimatedGasUnits,
302 maxFeePerGas: EtherAmount.fromInt(EtherUnit.wei, maxFeePerGas),
303 gasPrice: gasPrice != null ? EtherAmount.fromInt(EtherUnit.wei, gasPrice) : null,
304 + nonce: nonce,
305 );
306
307 Uint8List signedTransaction;
@@ -214,7 +325,8 @@ abstract class EVMChainClient {
325 );
326 }
327
217 - _sendTransaction = () async => await sendTransaction(signedTransaction);
328 + _sendTransaction = () async =>
329 + await sendTransaction(signedTransaction, useBlinkProtection: useBlinkProtection);
330
331 return PendingEVMChainTransaction(
332 signedTransaction: prepareSignedTransactionForSending(signedTransaction),
@@ -238,15 +350,23 @@ abstract class EVMChainClient {
350 required int exponent,
351 required String contractAddress,
352 int? gasPrice,
353 + bool useBlinkProtection = true,
354 }) async {
355 + final nonce = await _client!.getTransactionCount(
356 + privateKey.address,
357 + atBlock: const BlockNum.pending(),
358 + );
359 +
360 final Transaction transaction = createTransaction(
361 from: privateKey.address,
362 to: EthereumAddress.fromHex(contractAddress),
245 - maxPriorityFeePerGas:priority != null ? EtherAmount.fromInt(EtherUnit.gwei, priority.tip) : null,
363 + maxPriorityFeePerGas:
364 + priority != null ? EtherAmount.fromInt(EtherUnit.gwei, priority.tip) : null,
365 amount: EtherAmount.zero(),
366 maxGas: estimatedGasUnits,
367 maxFeePerGas: EtherAmount.fromInt(EtherUnit.wei, maxFeePerGas),
368 gasPrice: gasPrice != null ? EtherAmount.fromInt(EtherUnit.wei, gasPrice) : null,
369 + nonce: nonce,
370 );
371
372 final erc20 = ERC20(
@@ -267,7 +387,8 @@ abstract class EVMChainClient {
387 amount: amount.toString(),
388 fee: gasFee,
389 feeCurrency: feeCurrency,
270 - sendTransaction: () => sendTransaction(signedTransaction),
390 + sendTransaction: () =>
391 + sendTransaction(signedTransaction, useBlinkProtection: useBlinkProtection),
392 exponent: exponent,
393 isInfiniteApproval: amount.toRadixString(16) ==
394 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
@@ -283,6 +404,7 @@ abstract class EVMChainClient {
404 EtherAmount? maxFeePerGas,
405 Uint8List? data,
406 int? maxGas,
407 + int? nonce,
408 }) {
409 return Transaction(
410 from: from,
@@ -293,11 +415,28 @@ abstract class EVMChainClient {
415 maxGas: maxGas,
416 gasPrice: gasPrice,
417 maxFeePerGas: maxFeePerGas,
418 + nonce: nonce,
419 );
420 }
421
299 - Future<String> sendTransaction(Uint8List signedTransaction) async {
300 - return await _client!.sendRawTransaction(prepareSignedTransactionForSending(signedTransaction));
422 + String _blinkUrl(String apiKey) => 'https://eth.blinklabs.xyz/v1/$apiKey';
423 +
424 + Future<String> sendTransaction(
425 + Uint8List signedTransaction, {
426 + bool useBlinkProtection = false,
427 + }) async {
428 + final prepared = prepareSignedTransactionForSending(signedTransaction);
429 +
430 + if (useBlinkProtection && secrets.blinkApiKey.isNotEmpty) {
431 + final blinkClient = Web3Client(_blinkUrl(secrets.blinkApiKey), client);
432 + try {
433 + return await blinkClient.sendRawTransaction(prepared);
434 + } finally {
435 + await blinkClient.dispose();
436 + }
437 + }
438 +
439 + return await _client!.sendRawTransaction(prepared);
440 }
441
442 Future getTransactionDetails(String transactionHash) async {
@@ -360,56 +499,70 @@ abstract class EVMChainClient {
499
500 Future<Erc20Token?> getErc20Token(String contractAddress, String chainName) async {
501 try {
363 - final uri = Uri.https(
364 - 'deep-index.moralis.io',
365 - '/api/v2.2/erc20/metadata',
366 - {
367 - "chain": chainName,
368 - "addresses": contractAddress,
369 - },
370 - );
502 + final token = await getErcTokenInfoFromNode(contractAddress, chainName);
503
372 - final response = await client.get(
373 - uri,
374 - headers: {
375 - "Accept": "application/json",
376 - "X-API-Key": secrets.moralisApiKey,
377 - },
378 - );
504 + if (token == null || token.name.isEmpty || token.symbol.isEmpty) {
505 + return await getErc20TokenFromMoralis(contractAddress, chainName);
506 + }
507
380 - final decodedResponse = jsonDecode(response.body)[0] as Map<String, dynamic>;
508 + return token;
509 + } catch (e) {
510 + try {
511 + return await getErc20TokenFromMoralis(contractAddress, chainName);
512 + } catch (e) {
513 + return null;
514 + }
515 + }
516 + }
517
382 - final symbol = (decodedResponse['symbol'] ?? '') as String;
383 - String filteredSymbol = symbol.replaceFirst(RegExp('^\\\$'), '');
518 + Future<Erc20Token?> getErc20TokenFromMoralis(String contractAddress, String chainName) async {
519 + final uri = Uri.https(
520 + 'deep-index.moralis.io',
521 + '/api/v2.2/erc20/metadata',
522 + {
523 + "chain": chainName,
524 + "addresses": contractAddress,
525 + },
526 + );
527
385 - final name = decodedResponse['name'] ?? '';
386 - final decimal = decodedResponse['decimals'] ?? '0';
387 - final iconPath = decodedResponse['logo'] ?? '';
528 + final response = await client.get(
529 + uri,
530 + headers: {
531 + "Accept": "application/json",
532 + "X-API-Key": secrets.moralisApiKey,
533 + },
534 + );
535
389 - return Erc20Token(
390 - name: name,
391 - symbol: filteredSymbol,
392 - contractAddress: contractAddress,
393 - decimal: int.tryParse(decimal) ?? 0,
394 - iconPath: iconPath,
395 - );
396 - } catch (e) {
397 - try {
398 - final erc20 = ERC20(address: EthereumAddress.fromHex(contractAddress), client: _client!);
399 - final name = await erc20.name();
400 - final symbol = await erc20.symbol();
401 - final decimal = await erc20.decimals();
402 -
403 - return Erc20Token(
404 - name: name,
405 - symbol: symbol,
406 - contractAddress: contractAddress,
407 - decimal: decimal.toInt(),
408 - );
409 - } catch (_) {}
536 + final decodedResponse = jsonDecode(response.body)[0] as Map<String, dynamic>;
537
411 - return null;
412 - }
538 + final symbol = (decodedResponse['symbol'] ?? '') as String;
539 + String filteredSymbol = symbol.replaceFirst(RegExp('^\\\$'), '');
540 +
541 + final name = (decodedResponse['name'] ?? '').toString();
542 + final decimal = decodedResponse['decimals'] ?? '0';
543 + final iconPath = decodedResponse['logo'] ?? '';
544 +
545 + return Erc20Token(
546 + name: name,
547 + symbol: filteredSymbol,
548 + contractAddress: contractAddress,
549 + decimal: int.tryParse(decimal) ?? 0,
550 + iconPath: iconPath,
551 + );
552 + }
553 +
554 + Future<Erc20Token?> getErcTokenInfoFromNode(String contractAddress, String chainName) async {
555 + final erc20 = ERC20(address: EthereumAddress.fromHex(contractAddress), client: _client!);
556 + final name = await erc20.name();
557 + final symbol = await erc20.symbol();
558 + final decimal = await erc20.decimals();
559 +
560 + return Erc20Token(
561 + name: name,
562 + symbol: symbol,
563 + contractAddress: contractAddress,
564 + decimal: decimal.toInt(),
565 + );
566 }
567
568 Uint8List hexToBytes(String hexString) {
cw_evm/lib/clients/polygon_client.dart new
+46
@@ -0,0 +1,46 @@
1 +import 'package:cw_evm/clients/evm_chain_client.dart';
2 +import 'package:flutter/foundation.dart';
3 +import 'package:web3dart/web3dart.dart';
4 +
5 +class PolygonClient extends EVMChainClient {
6 + PolygonClient() : super(chainId: 137);
7 +
8 + @override
9 + Transaction createTransaction({
10 + required EthereumAddress from,
11 + required EthereumAddress to,
12 + required EtherAmount amount,
13 + EtherAmount? maxPriorityFeePerGas,
14 + Uint8List? data,
15 + int? maxGas,
16 + EtherAmount? gasPrice,
17 + EtherAmount? maxFeePerGas,
18 + int? nonce,
19 + }) {
20 + EtherAmount? finalGasPrice = gasPrice;
21 +
22 + if (gasPrice == null && maxFeePerGas != null) {
23 + // If we have EIP-1559 parameters but no legacy gasPrice, then use maxFeePerGas as gasPrice
24 + finalGasPrice = maxFeePerGas;
25 + }
26 +
27 + return Transaction(
28 + from: from,
29 + to: to,
30 + value: amount,
31 + data: data,
32 + maxGas: maxGas,
33 + gasPrice: finalGasPrice,
34 + nonce: nonce,
35 + // maxFeePerGas: maxFeePerGas,
36 + // maxPriorityFeePerGas: maxPriorityFeePerGas,
37 + );
38 + }
39 +
40 + @override
41 + Uint8List prepareSignedTransactionForSending(Uint8List signedTransaction) => signedTransaction;
42 +
43 + @override
44 + int get chainId => 137;
45 +}
46 +
cw_evm/lib/deuro/deuro_savings.dart renamed
+5 -4
@@ -1,6 +1,6 @@
1 import 'package:cw_core/crypto_currency.dart';
2 -import 'package:cw_ethereum/deuro/deuro_savings_contract.dart';
3 -import 'package:cw_ethereum/ethereum_wallet.dart';
2 +import 'package:cw_evm/deuro/deuro_savings_contract.dart';
3 +import 'package:cw_evm/evm_chain_wallet.dart';
4 import 'package:cw_evm/contract/erc20.dart';
5 import 'package:cw_evm/evm_chain_exceptions.dart';
6 import 'package:cw_evm/evm_chain_transaction_priority.dart';
@@ -16,9 +16,9 @@ const String frontendCode = "0x00000000000000000000000000000000000000000043616b6
16 class DEuro {
17 final SavingsGateway _savingsGateway;
18 final ERC20 _dEuro;
19 - final EthereumWallet _wallet;
19 + final EVMChainWallet _wallet;
20
21 - DEuro(EthereumWallet wallet)
21 + DEuro(EVMChainWallet wallet)
22 : _wallet = wallet,
23 _savingsGateway = _getSavingsGateway(wallet.getWeb3Client()!),
24 _dEuro = _getDEuroToken(wallet.getWeb3Client()!);
@@ -200,3 +200,4 @@ class DEuro {
200 }
201 }
202 }
203 +
cw_evm/lib/deuro/deuro_savings_contract.dart renamed
cw_evm/lib/evm_chain_client_factory.dart new
+36
@@ -0,0 +1,36 @@
1 +import 'package:cw_evm/clients/arbitrum_client.dart';
2 +import 'package:cw_evm/clients/base_client.dart';
3 +import 'package:cw_evm/clients/ethereum_client.dart';
4 +import 'package:cw_evm/clients/polygon_client.dart';
5 +import 'package:cw_evm/clients/evm_chain_client.dart';
6 +import 'package:cw_evm/evm_chain_registry.dart';
7 +
8 +/// Factory to create appropriate EVMChainClient based on chainId
9 +class EVMChainClientFactory {
10 + static final EvmChainRegistry _registry = EvmChainRegistry();
11 +
12 + /// Create an EVMChainClient for the given chainId
13 + ///
14 + /// Throws an exception if chainId is not registered
15 + static EVMChainClient createClient(int chainId) {
16 + final config = _registry.getChainConfig(chainId);
17 +
18 + if (config == null) {
19 + throw Exception('Chain config not found for chainId: $chainId');
20 + }
21 +
22 + // Check if chain needs custom client
23 + switch (chainId) {
24 + case 1:
25 + return EthereumClient();
26 + case 137:
27 + return PolygonClient();
28 + case 8453:
29 + return BaseClient();
30 + case 42161:
31 + return ArbitrumClient();
32 + default:
33 + return EVMChainClient(chainId: chainId);
34 + }
35 + }
36 +}
cw_evm/lib/evm_chain_default_tokens.dart new
+22
@@ -0,0 +1,22 @@
1 +import 'package:cw_core/erc20_token.dart';
2 +import 'package:cw_evm/tokens/arbitrum_tokens.dart';
3 +import 'package:cw_evm/tokens/base_tokens.dart';
4 +import 'package:cw_evm/tokens/ethereum_tokens.dart';
5 +import 'package:cw_evm/tokens/polygon_tokens.dart';
6 +
7 +/// Default ERC20 tokens for each EVM chain
8 +class EVMChainDefaultTokens {
9 + static List<Erc20Token> getDefaultTokensByChainId(int chainId) {
10 + return switch (chainId) {
11 + 1 => EthereumTokens.tokens,
12 + 137 => PolygonTokens.tokens,
13 + 8453 => BaseTokens.tokens,
14 + 42161 => ArbitrumTokens.tokens,
15 + _ => [],
16 + };
17 + }
18 +
19 + static List<String> getDefaultTokenAddresses(int chainId) {
20 + return getDefaultTokensByChainId(chainId).map((token) => token.contractAddress).toList();
21 + }
22 +}
cw_evm/lib/evm_chain_exceptions.dart
+4 -1
@@ -16,7 +16,10 @@ class EVMChainTransactionCreationException implements Exception {
16 class EVMChainTransactionFeesException implements Exception {
17 final String exceptionMessage;
18
19 - EVMChainTransactionFeesException(String currency)
19 + EVMChainTransactionFeesException(String message)
20 + : exceptionMessage = message;
21 +
22 + EVMChainTransactionFeesException.fromCurrency(String currency)
23 : exceptionMessage = 'Transaction failed due to insufficient $currency balance to cover the fees.';
24
25 @override
cw_evm/lib/evm_chain_formatter.dart deleted
-43
@@ -1,43 +0,0 @@
1 -import 'dart:math';
2 -
3 -class EVMChainFormatter {
4 - static int _divider = 0;
5 -
6 - static int parseEVMChainAmount(String amount) {
7 - try {
8 - final decimalLength = _getDividerForInput(amount);
9 - _divider = decimalLength;
10 - return (double.parse(amount) * pow(10, decimalLength)).round();
11 - } catch (_) {
12 - return 0;
13 - }
14 - }
15 -
16 - static double parseEVMChainAmountToDouble(int amount) {
17 - try {
18 - return amount / pow(10, _divider);
19 - } catch (_) {
20 - return 0;
21 - }
22 - }
23 -
24 - static String truncateDecimals(String amount, int decimals) {
25 - final parts = amount.split(".");
26 -
27 - if (parts.length == 2) {
28 - parts[1] = parts[1].substring(0, parts[1].length > decimals ? decimals : parts[1].length);
29 - }
30 -
31 - return parts.join(".");
32 - }
33 -
34 - static int _getDividerForInput(String amount) {
35 - final result = amount.split('.');
36 - if (result.length > 1) {
37 - final decimalLength = result[1].length;
38 - return decimalLength;
39 - } else {
40 - return 0;
41 - }
42 - }
43 -}
cw_evm/lib/evm_chain_registry.dart new
+203
@@ -0,0 +1,203 @@
1 +import 'package:cw_core/crypto_currency.dart';
2 +import 'package:cw_core/wallet_type.dart';
3 +import 'package:cw_evm/utils/network_chain_utils.dart';
4 +
5 +/// Centralized registry for all EVM chain configurations
6 +class EvmChainRegistry {
7 + static final EvmChainRegistry _instance = EvmChainRegistry._internal();
8 + factory EvmChainRegistry() => _instance;
9 + EvmChainRegistry._internal() {
10 + initialize();
11 + }
12 +
13 + final Map<int, ChainConfig> _chains = {};
14 + final Map<WalletType, int> _walletTypeToChainId = {};
15 + final Map<int, WalletType> _chainIdToWalletType = {};
16 + final Map<String, int> _tagToChainId = {};
17 + final Map<String, int> _caip2ToChainId = {};
18 +
19 + bool _initialized = false;
20 +
21 + /// Initialize registry with all supported EVM chains
22 + void initialize() {
23 + if (_initialized) return;
24 + _initialized = true;
25 +
26 + // Ethereum Mainnet
27 + _registerChain(
28 + const ChainConfig(
29 + chainId: 1,
30 + name: 'Ethereum',
31 + shortCode: 'eth',
32 + caip2: 'eip155:1',
33 + nativeCurrency: CryptoCurrency.eth,
34 + capabilities: ChainCapabilities(
35 + supportsERC20: true,
36 + supportsEIP1559: true,
37 + supportsInternalTx: true,
38 + supportsSubscriptions: false,
39 + supportsENS: true,
40 + ),
41 + defaultRpcEndpoints: [
42 + 'ethereum-rpc.publicnode.com',
43 + 'eth.llamarpc.com',
44 + 'rpc.flashbots.net',
45 + 'eth-mainnet.public.blastapi.io',
46 + 'eth.nownodes.io',
47 + 'ethereum.publicnode.com',
48 + ],
49 + explorerUrls: [
50 + 'https://etherscan.io',
51 + ],
52 + feeModel: FeeModel(
53 + type: FeeType.eip1559,
54 + defaultGasLimit: 21000,
55 + ),
56 + ),
57 + WalletType.ethereum,
58 + 'ETH',
59 + );
60 +
61 + // Polygon
62 + _registerChain(
63 + const ChainConfig(
64 + chainId: 137,
65 + name: 'Polygon',
66 + shortCode: 'polygon',
67 + caip2: 'eip155:137',
68 + nativeCurrency: CryptoCurrency.maticpoly,
69 + capabilities: ChainCapabilities(
70 + supportsERC20: true,
71 + supportsEIP1559: true,
72 + supportsInternalTx: true,
73 + supportsSubscriptions: false,
74 + supportsENS: false,
75 + ),
76 + defaultRpcEndpoints: [
77 + 'polygon-rpc.com',
78 + 'polygon-bor-rpc.publicnode.com',
79 + 'polygon.llamarpc.com',
80 + 'matic.nownodes.io',
81 + ],
82 + explorerUrls: [
83 + 'https://polygonscan.com',
84 + ],
85 + feeModel: FeeModel(
86 + type: FeeType.eip1559,
87 + defaultGasLimit: 21000,
88 + ),
89 + ),
90 + WalletType.polygon,
91 + 'POL',
92 + );
93 +
94 + // Base
95 + _registerChain(
96 + const ChainConfig(
97 + chainId: 8453,
98 + name: 'Base',
99 + shortCode: 'base',
100 + caip2: 'eip155:8453',
101 + nativeCurrency: CryptoCurrency.baseEth,
102 + capabilities: ChainCapabilities(
103 + supportsERC20: true,
104 + supportsEIP1559: true,
105 + supportsInternalTx: true,
106 + supportsSubscriptions: false,
107 + supportsENS: false,
108 + ),
109 + defaultRpcEndpoints: [
110 + 'base.nownodes.io',
111 + 'base.llamarpc.com',
112 + 'base-rpc.publicnode.com',
113 + '1rpc.io/base',
114 + ],
115 + explorerUrls: [
116 + 'https://basescan.org',
117 + ],
118 + feeModel: FeeModel(
119 + type: FeeType.eip1559,
120 + defaultGasLimit: 21000,
121 + ),
122 + ),
123 + WalletType.base,
124 + 'BASE',
125 + );
126 +
127 + // Arbitrum
128 + _registerChain(
129 + const ChainConfig(
130 + chainId: 42161,
131 + name: 'Arbitrum',
132 + shortCode: 'arbitrum',
133 + caip2: 'eip155:42161',
134 + nativeCurrency: CryptoCurrency.arbEth,
135 + capabilities: ChainCapabilities(
136 + supportsERC20: true,
137 + supportsEIP1559: true,
138 + supportsInternalTx: true,
139 + supportsSubscriptions: false,
140 + supportsENS: false,
141 + ),
142 + defaultRpcEndpoints: [
143 + 'arbitrum.nownodes.io',
144 + 'arbitrum.drpc.org',
145 + 'arbitrum-one-rpc.publicnode.com',
146 + ],
147 + explorerUrls: [
148 + 'https://arbiscan.io',
149 + ],
150 + feeModel: FeeModel(
151 + type: FeeType.eip1559,
152 + defaultGasLimit: 21000,
153 + ),
154 + ),
155 + WalletType.arbitrum,
156 + 'ARB',
157 + );
158 + }
159 +
160 + void _registerChain(
161 + ChainConfig config,
162 + WalletType walletType,
163 + String tag,
164 + ) {
165 + _chains[config.chainId] = config;
166 + _walletTypeToChainId[walletType] = config.chainId;
167 + _chainIdToWalletType[config.chainId] = walletType;
168 + _tagToChainId[tag.toUpperCase()] = config.chainId;
169 + _caip2ToChainId[config.caip2] = config.chainId;
170 + }
171 +
172 + ChainConfig? getChainConfig(int chainId) => _chains[chainId];
173 +
174 + ChainConfig? getChainConfigByWalletType(WalletType walletType) {
175 + final chainId = _walletTypeToChainId[walletType];
176 + return chainId != null ? _chains[chainId] : null;
177 + }
178 +
179 + /// Get chain configuration by tag (e.g., 'ETH', 'POL', 'BASE', 'ARB')
180 + ChainConfig? getChainConfigByTag(String tag) {
181 + final chainId = _tagToChainId[tag.toUpperCase()];
182 + return chainId != null ? _chains[chainId] : null;
183 + }
184 +
185 + /// Get chain configuration by CAIP-2 identifier (e.g. 'eip155:1')
186 + ChainConfig? getChainConfigByCaip2(String caip2) {
187 + final chainId = _caip2ToChainId[caip2];
188 + return chainId != null ? _chains[chainId] : null;
189 + }
190 +
191 + WalletType? getWalletTypeByChainId(int chainId) => _chainIdToWalletType[chainId];
192 +
193 + int? getChainIdByWalletType(WalletType walletType) => _walletTypeToChainId[walletType];
194 +
195 + bool isChainRegistered(int chainId) => _chains.containsKey(chainId);
196 +
197 + List<int> getRegisteredChainIds() => _chains.keys.toList();
198 +
199 + List<ChainConfig> getAllChains() => _chains.values.toList();
200 +
201 + List<WalletType> getRegisteredWalletTypes() =>
202 + _walletTypeToChainId.keys.toList();
203 +}
cw_evm/lib/evm_chain_transaction_credentials.dart
+2
@@ -8,10 +8,12 @@ class EVMChainTransactionCredentials {
8 required this.priority,
9 required this.currency,
10 this.feeRate,
11 + this.useBlinkProtection = true,
12 });
13
14 final List<OutputInfo> outputs;
15 final EVMChainTransactionPriority? priority;
16 final int? feeRate;
17 final CryptoCurrency currency;
18 + final bool useBlinkProtection;
19 }
cw_evm/lib/evm_chain_transaction_history.dart
+50 -14
@@ -3,21 +3,25 @@ import 'dart:core';
3 import 'dart:developer';
4 import 'package:cw_core/encryption_file_utils.dart';
5 import 'package:cw_core/pathForWallet.dart';
6 +import 'package:cw_core/utils/print_verbose.dart';
7 import 'package:cw_core/wallet_info.dart';
8 import 'package:cw_evm/evm_chain_transaction_info.dart';
9 +import 'package:cw_evm/utils/evm_chain_utils.dart';
10 import 'package:mobx/mobx.dart';
11 import 'package:cw_core/transaction_history.dart';
12
13 part 'evm_chain_transaction_history.g.dart';
14
13 -abstract class EVMChainTransactionHistory = EVMChainTransactionHistoryBase
14 - with _$EVMChainTransactionHistory;
15 +class EVMChainTransactionHistory = EVMChainTransactionHistoryBase with _$EVMChainTransactionHistory;
16
17 abstract class EVMChainTransactionHistoryBase
18 extends TransactionHistoryBase<EVMChainTransactionInfo> with Store {
18 - EVMChainTransactionHistoryBase(
19 - {required this.walletInfo, required String password, required this.encryptionFileUtils})
20 - : _password = password {
19 + EVMChainTransactionHistoryBase({
20 + required this.walletInfo,
21 + required String password,
22 + required this.encryptionFileUtils,
23 + required this.getCurrentChainId,
24 + }) : _password = password {
25 transactions = ObservableMap<String, EVMChainTransactionInfo>();
26 }
27
@@ -26,13 +30,17 @@ abstract class EVMChainTransactionHistoryBase
30 final WalletInfo walletInfo;
31 final EncryptionFileUtils encryptionFileUtils;
32
29 - //! Method to be overridden by all child classes
33 + /// Function to get the current chain ID (allows transaction history to use correct file)
34 + final int Function() getCurrentChainId;
35
31 - String getTransactionHistoryFileName();
32 -
33 - EVMChainTransactionInfo getTransactionInfo(Map<String, dynamic> val);
36 + /// Get transaction history file name based on current chain ID
37 + String getTransactionHistoryFileName() {
38 + return EVMChainUtils.getTransactionHistoryFileName(getCurrentChainId());
39 + }
40
35 - //! Common methods across all child classes
41 + EVMChainTransactionInfo getTransactionInfo(Map<String, dynamic> val) {
42 + return EVMChainTransactionInfo.fromJson(val, getCurrentChainId());
43 + }
44
45 Future<void> init() async {
46 clear();
@@ -45,7 +53,19 @@ abstract class EVMChainTransactionHistoryBase
53 try {
54 final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
55 String path = '$dirPath/$transactionsHistoryFileNameForWallet';
48 - final data = json.encode({'transactions': transactions});
56 +
57 + // Filter transactions by current chainId before saving
58 + // This ensures we only save transactions for the current chain, preventing
59 + // transactions from other chains from being saved to the wrong file
60 + final currentChainId = getCurrentChainId();
61 + final filteredTransactions = <String, EVMChainTransactionInfo>{};
62 + for (final entry in transactions.entries) {
63 + if (entry.value.chainId == currentChainId) {
64 + filteredTransactions[entry.key] = entry.value;
65 + }
66 + }
67 +
68 + final data = json.encode({'transactions': filteredTransactions});
69 await encryptionFileUtils.write(path: path, password: _password, data: data);
70 } catch (e, s) {
71 log('Error while saving ${walletInfo.type.name} transaction history: ${e.toString()}');
@@ -54,11 +74,27 @@ abstract class EVMChainTransactionHistoryBase
74 }
75
76 @override
57 - void addOne(EVMChainTransactionInfo transaction) => transactions[transaction.id] = transaction;
77 + void addOne(EVMChainTransactionInfo transaction) {
78 + if (transaction.chainId == getCurrentChainId()) {
79 + transactions[transaction.id] = transaction;
80 + }
81 + }
82
83 @override
60 - void addMany(Map<String, EVMChainTransactionInfo> transactions) =>
61 - this.transactions.addAll(transactions);
84 + void addMany(Map<String, EVMChainTransactionInfo> transactionsToAdd) {
85 + final currentChainId = getCurrentChainId();
86 +
87 + // First, remove any transactions that don't match the current chainId
88 + // This prevents transactions from other chains from persisting in the map
89 + transactions.removeWhere((key, value) => value.chainId != currentChainId);
90 +
91 + // Then add/update transactions for the current chain
92 + for (final entry in transactionsToAdd.entries) {
93 + if (entry.value.chainId == currentChainId) {
94 + transactions[entry.key] = entry.value;
95 + }
96 + }
97 + }
98
99 Future<Map<String, dynamic>> _read() async {
100 final transactionsHistoryFileNameForWallet = getTransactionHistoryFileName();
cw_evm/lib/evm_chain_transaction_info.dart
+30 -5
@@ -3,10 +3,12 @@
3 import 'dart:math';
4
5 import 'package:cw_core/format_amount.dart';
6 +import 'package:cw_core/format_fixed.dart';
7 import 'package:cw_core/transaction_direction.dart';
8 import 'package:cw_core/transaction_info.dart';
9 +import 'package:cw_evm/utils/evm_chain_utils.dart';
10
9 -abstract class EVMChainTransactionInfo extends TransactionInfo {
11 +class EVMChainTransactionInfo extends TransactionInfo {
12 EVMChainTransactionInfo({
13 required this.id,
14 required this.height,
@@ -22,6 +24,7 @@ abstract class EVMChainTransactionInfo extends TransactionInfo {
24 required this.from,
25 this.evmSignatureName,
26 this.contractAddress,
27 + required this.chainId,
28 }) : amount = ethAmount.toInt(),
29 fee = ethFee.toInt();
30
@@ -42,9 +45,10 @@ abstract class EVMChainTransactionInfo extends TransactionInfo {
45 final String? from;
46 final String? evmSignatureName;
47 final String? contractAddress;
48 + final int chainId;
49
46 - //! Getter to be overridden in child classes
47 - String get feeCurrency;
50 + /// Get fee currency symbol based on wallet type
51 + String get feeCurrency => EVMChainUtils.getFeeCurrency(chainId);
52
53 @override
54 String amountFormatted() {
@@ -60,8 +64,28 @@ abstract class EVMChainTransactionInfo extends TransactionInfo {
64
65 @override
66 String feeFormatted() {
63 - final amount = (ethFee / BigInt.from(10).pow(18)).toString();
64 - return '${amount.substring(0, min(18, amount.length))} $feeCurrency';
67 + final amount = formatFixed(ethFee, 18);
68 + return '$amount $feeCurrency';
69 + }
70 +
71 + factory EVMChainTransactionInfo.fromJson(Map<String, dynamic> data, int chainId) {
72 + return EVMChainTransactionInfo(
73 + id: data['id'] as String,
74 + height: data['height'] as int,
75 + ethAmount: BigInt.parse(data['amount'] as String),
76 + exponent: data['exponent'] as int? ?? 18,
77 + ethFee: BigInt.parse(data['fee'] as String),
78 + direction: TransactionDirection.values[data['direction'] as int],
79 + date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
80 + isPending: data['isPending'] as bool? ?? false,
81 + confirmations: data['confirmations'] as int,
82 + tokenSymbol: data['tokenSymbol'] as String,
83 + to: data['to'] as String?,
84 + from: data['from'] as String?,
85 + evmSignatureName: data['evmSignatureName'] as String?,
86 + contractAddress: data['contractAddress'] as String?,
87 + chainId: chainId,
88 + );
89 }
90
91 Map<String, dynamic> toJson() => {
@@ -79,5 +103,6 @@ abstract class EVMChainTransactionInfo extends TransactionInfo {
103 'from': from,
104 'evmSignatureName': evmSignatureName,
105 'contractAddress': contractAddress,
106 + 'chainId': chainId,
107 };
108 }
cw_evm/lib/evm_chain_transaction_model.dart
+4 -1
@@ -13,6 +13,7 @@ class EVMChainTransactionModel {
13 final int? tokenDecimal;
14 final bool isError;
15 final String input;
16 + final int chainId;
17 String? evmSignatureName;
18
19 EVMChainTransactionModel({
@@ -30,10 +31,11 @@ class EVMChainTransactionModel {
31 required this.tokenDecimal,
32 required this.isError,
33 required this.input,
34 + required this.chainId,
35 this.evmSignatureName,
36 });
37
36 - factory EVMChainTransactionModel.fromJson(Map<String, dynamic> json, String defaultSymbol) =>
38 + factory EVMChainTransactionModel.fromJson(Map<String, dynamic> json, String defaultSymbol, int chainId) =>
39 EVMChainTransactionModel(
40 date: DateTime.fromMillisecondsSinceEpoch(int.parse(json["timeStamp"]) * 1000),
41 hash: json["hash"] ?? "",
@@ -50,5 +52,6 @@ class EVMChainTransactionModel {
52 isError: json["isError"] == "1",
53 input: json["input"] ?? "",
54 evmSignatureName: json["evmSignatureName"],
55 + chainId: chainId,
56 );
57 }
cw_evm/lib/evm_chain_wallet.dart
+474 -78
@@ -10,10 +10,10 @@ import 'package:cw_core/crypto_currency.dart';
10 import 'package:cw_core/encryption_file_utils.dart';
11 import 'package:cw_core/erc20_token.dart';
12 import 'package:cw_core/node.dart';
13 -import 'package:cw_core/parse_fixed.dart';
13 import 'package:cw_core/pathForWallet.dart';
14 import 'package:cw_core/pending_transaction.dart';
15 import 'package:cw_core/sync_status.dart';
16 +import 'package:cw_core/transaction_direction.dart';
17 import 'package:cw_core/transaction_priority.dart';
18 import 'package:cw_core/utils/print_verbose.dart';
19 import 'package:cw_core/wallet_addresses.dart';
@@ -21,13 +21,18 @@ import 'package:cw_core/wallet_base.dart';
21 import 'package:cw_core/wallet_info.dart';
22 import 'package:cw_core/wallet_keys_file.dart';
23 import 'package:cw_core/wallet_type.dart';
24 -import 'package:cw_evm/evm_chain_client.dart';
24 +import 'package:cw_evm/clients/evm_chain_client.dart';
25 +import 'package:cw_evm/evm_chain_client_factory.dart';
26 +import 'package:cw_evm/evm_chain_default_tokens.dart';
27 import 'package:cw_evm/evm_chain_exceptions.dart';
26 -import 'package:cw_evm/evm_chain_formatter.dart';
28 +import 'package:cw_evm/evm_chain_registry.dart';
29 import 'package:cw_evm/evm_chain_transaction_credentials.dart';
30 +import 'package:cw_evm/utils/evm_chain_formatter.dart';
31 import 'package:cw_evm/evm_chain_transaction_history.dart';
32 import 'package:cw_evm/evm_chain_transaction_model.dart';
33 import 'package:cw_evm/evm_chain_transaction_priority.dart';
34 +import 'package:cw_evm/utils/evm_chain_utils.dart';
35 +import 'package:cw_evm/utils/network_chain_utils.dart';
36 import 'package:cw_evm/evm_chain_wallet_addresses.dart';
37 import 'package:cw_evm/hardware/evm_chain_bitbox_credentials.dart';
38 import 'package:cw_evm/hardware/evm_chain_ledger_credentials.dart';
@@ -60,7 +65,7 @@ const Map<String, String> methodSignatureToType = {
65 '0xd505accf': 'permit',
66 };
67
63 -abstract class EVMChainWallet = EVMChainWalletBase with _$EVMChainWallet;
68 +class EVMChainWallet = EVMChainWalletBase with _$EVMChainWallet;
69
70 abstract class EVMChainWalletBase
71 extends WalletBase<EVMChainERC20Balance, EVMChainTransactionHistory, EVMChainTransactionInfo>
@@ -76,16 +81,17 @@ abstract class EVMChainWalletBase
81 EVMChainERC20Balance? initialBalance,
82 required this.encryptionFileUtils,
83 this.passphrase,
84 + int? initialChainId,
85 }) : syncStatus = const NotConnectedSyncStatus(),
86 _password = password,
87 _mnemonic = mnemonic,
88 _hexPrivateKey = privateKey,
89 _isTransactionUpdating = false,
90 _client = client,
91 + selectedChainId = initialChainId ?? _getInitialChainId(walletInfo.type),
92 walletAddresses = EVMChainWalletAddresses(walletInfo),
93 balance = ObservableMap<CryptoCurrency, EVMChainERC20Balance>.of(
94 {
88 - // Not sure of this yet, will it work? will it not?
95 nativeCurrency: initialBalance ?? EVMChainERC20Balance(BigInt.zero),
96 },
97 ),
@@ -107,15 +113,47 @@ abstract class EVMChainWalletBase
113
114 late final Box<Erc20Token> erc20TokensBox;
115
110 - late final Box<Erc20Token> evmChainErc20TokensBox;
116 + late Box<Erc20Token> evmChainErc20TokensBox;
117
118 late final Credentials _evmChainPrivateKey;
119
120 Credentials get evmChainPrivateKey => _evmChainPrivateKey;
121
116 - late final EVMChainClient _client;
122 + late EVMChainClient _client;
123
118 - bool hasPriorityFee = true;
124 + @override
125 + int? get chainId => selectedChainId;
126 +
127 + /// Currently selected chain ID for this wallet
128 + @observable
129 + int selectedChainId;
130 +
131 + /// Get chain configuration for currently selected chain
132 + @computed
133 + ChainConfig? get selectedChainConfig {
134 + final registry = EvmChainRegistry();
135 + return registry.getChainConfig(selectedChainId);
136 + }
137 +
138 + @override
139 + @computed
140 + CryptoCurrency get currency {
141 + final config = selectedChainConfig;
142 + if (config != null) {
143 + return config.nativeCurrency;
144 + }
145 +
146 + return super.currency;
147 + }
148 +
149 + bool get hasPriorityFee => EVMChainUtils.hasPriorityFee(selectedChainId);
150 +
151 + /// Get initial chain ID from registry based on wallet type
152 + static int _getInitialChainId(WalletType walletType) {
153 + final registry = EvmChainRegistry();
154 + final chainConfig = registry.getChainConfigByWalletType(walletType);
155 + return chainConfig?.chainId ?? 1; // Default to Ethereum if not found
156 + }
157
158 @observable
159 String? nativeTxEstimatedFee;
@@ -125,7 +163,6 @@ abstract class EVMChainWalletBase
163
164 bool _isTransactionUpdating;
165
128 - // TODO: remove after integrating our own node and having eth_newPendingTransactionFilter
166 Timer? _transactionsUpdateTimer;
167
168 @override
@@ -141,42 +178,197 @@ abstract class EVMChainWalletBase
178
179 Completer<SharedPreferences> sharedPrefs = Completer();
180
144 - //! Methods to be overridden by every child
181 + //! Chain selection methods
182 +
183 + /// Select a different EVM network chain for this wallet
184 + ///
185 + /// This allows switching between EVM networks (Ethereum, Polygon, Base, Arbitrum, etc.)
186 + /// without creating a new wallet. The selected chain ID is stored, the client is
187 + /// immediately updated, and the wallet automatically connects to the node, updates
188 + /// balance, and refreshes transactions for the selected network.
189 + ///
190 + /// Transactions are stored in separate files per network (based on chainId), so switching
191 + /// networks automatically loads transactions from the correct file.
192 + @action
193 + Future<void> selectChain(int chainId, {required Node node}) async {
194 + if (EvmChainRegistry().getChainConfig(chainId) == null) {
195 + throw Exception('Chain config not found for chainId: $chainId');
196 + }
197 +
198 + if (selectedChainId == chainId) return;
199
146 - void addInitialTokens();
200 + _client.stop();
201
148 - // Future<EVMChainWallet> open({
149 - // required String name,
150 - // required String password,
151 - // required WalletInfo walletInfo,
152 - // });
202 + balance.clear();
203
154 - List<String> get getDefaultTokenContractAddresses;
204 + selectedChainId = chainId;
205 + _client = EVMChainClientFactory.createClient(selectedChainId);
206
156 - Future<void> initErc20TokensBox();
207 + // Automatically connect to node for the selected chain
208 + await connectToNode(node: node);
209 +
210 + // Reload ERC20 tokens box for the new chain
211 + await initErc20TokensBox();
212 +
213 + // Reload transaction history from the new chain's file
214 + await transactionHistory.init();
215 +
216 + await save();
217 +
218 + await startSync();
219 + }
220 +
221 + void addInitialTokens() {
222 + final initialErc20Tokens = EVMChainDefaultTokens.getDefaultTokensByChainId(selectedChainId);
223 +
224 + for (final token in initialErc20Tokens) {
225 + if (!evmChainErc20TokensBox.containsKey(token.contractAddress)) {
226 + evmChainErc20TokensBox.put(token.contractAddress, token);
227 + } else {
228 + // update existing token
229 + final existingToken = evmChainErc20TokensBox.get(token.contractAddress);
230 + evmChainErc20TokensBox.put(
231 + token.contractAddress,
232 + Erc20Token.copyWith(token, enabled: existingToken!.enabled),
233 + );
234 + }
235 + }
236 + }
237 +
238 + List<String> get getDefaultTokenContractAddresses =>
239 + EVMChainDefaultTokens.getDefaultTokenAddresses(selectedChainId);
240 +
241 + Future<void> initErc20TokensBox() async {
242 + // Migration for old WalletType.ethereum wallets:
243 + // Old wallets used a global erc20TokensBox (shared across all wallets).
244 + // New system uses wallet-specific, chain-specific boxes.
245 + // This checks if migration is needed and runs it once.
246 + if (walletInfo.type == WalletType.ethereum) {
247 + try {
248 + // Try to access erc20TokensBox - if it exists, migration already ran
249 + final _ = erc20TokensBox;
250 + // Migration done, proceed with normal chain-specific logic below
251 + } catch (_) {
252 + // erc20TokensBox doesn't exist yet, run migration from global box
253 + await _initEthereumErc20TokensBox();
254 + return;
255 + }
256 + }
257
158 - String getTransactionHistoryFileName();
258 + final chainId = selectedChainId;
259
160 - Future<bool> checkIfScanProviderIsEnabled();
260 + final boxName = EVMChainUtils.getErc20TokensBoxName(walletInfo.name, chainId);
261 +
262 + // Close existing box if it's already open (for chain switching)
263 + try {
264 + if (evmChainErc20TokensBox.isOpen) {
265 + await evmChainErc20TokensBox.close();
266 + }
267 + } catch (_) {
268 + // Box might not be initialized yet, ignore
269 + }
270 +
271 + // Check if box is already open, if so use it, otherwise open it
272 + if (CakeHive.isBoxOpen(boxName)) {
273 + evmChainErc20TokensBox = CakeHive.box<Erc20Token>(boxName);
274 + } else {
275 + evmChainErc20TokensBox = await CakeHive.openBox<Erc20Token>(boxName);
276 + }
277 +
278 + addInitialTokens();
279 + }
280 +
281 + /// Ethereum-specific initialization with backward compatibility
282 + Future<void> _initEthereumErc20TokensBox() async {
283 + // Opens a box specific to this wallet
284 + evmChainErc20TokensBox = await CakeHive.openBox<Erc20Token>(
285 + "${walletInfo.name.replaceAll(" ", "_")}_${Erc20Token.ethereumBoxName}",
286 + );
287 +
288 + erc20TokensBox = await CakeHive.openBox<Erc20Token>(Erc20Token.boxName);
289 +
290 + if (erc20TokensBox.isEmpty) {
291 + if (evmChainErc20TokensBox.isEmpty) addInitialTokens();
292 + return;
293 + }
294 +
295 + final allValues = erc20TokensBox.values.toList();
296 +
297 + // Clear and delete the old token box
298 + await erc20TokensBox.clear();
299 + await erc20TokensBox.deleteFromDisk();
300 +
301 + // Add all the previous tokens with configs to the new box
302 + await evmChainErc20TokensBox.addAll(allValues);
303 + }
304 +
305 + String getTransactionHistoryFileName() =>
306 + EVMChainUtils.getTransactionHistoryFileName(selectedChainId);
307 +
308 + Future<bool> checkIfScanProviderIsEnabled() async {
309 + final key = EVMChainUtils.getScanProviderPreferenceKey(selectedChainId);
310 + return (await sharedPrefs.future).getBool(key) ?? true;
311 + }
312
313 EVMChainTransactionInfo getTransactionInfo(
163 - EVMChainTransactionModel transactionModel, String address);
314 + EVMChainTransactionModel transactionModel,
315 + String address,
316 + ) {
317 + return EVMChainTransactionInfo(
318 + id: transactionModel.hash,
319 + height: transactionModel.blockNumber,
320 + ethAmount: transactionModel.amount,
321 + direction: transactionModel.from == address
322 + ? TransactionDirection.outgoing
323 + : TransactionDirection.incoming,
324 + isPending: false,
325 + date: transactionModel.date,
326 + confirmations: transactionModel.confirmations,
327 + ethFee: BigInt.from(transactionModel.gasUsed) * transactionModel.gasPrice,
328 + exponent: transactionModel.tokenDecimal ?? 18,
329 + tokenSymbol: transactionModel.tokenSymbol ??
330 + EVMChainUtils.getDefaultTokenSymbol(transactionModel.chainId),
331 + to: transactionModel.to,
332 + from: transactionModel.from,
333 + evmSignatureName: transactionModel.evmSignatureName,
334 + contractAddress: transactionModel.contractAddress,
335 + chainId: transactionModel.chainId,
336 + );
337 + }
338
165 - Erc20Token createNewErc20TokenObject(Erc20Token token, String? iconPath);
339 + Erc20Token createNewErc20TokenObject(Erc20Token token, String? iconPath) {
340 + return Erc20Token(
341 + name: token.name,
342 + symbol: token.symbol,
343 + contractAddress: token.contractAddress,
344 + decimal: token.decimal,
345 + enabled: token.enabled,
346 + tag: token.tag ?? EVMChainUtils.getDefaultTokenTag(selectedChainId),
347 + iconPath: iconPath,
348 + isPotentialScam: token.isPotentialScam,
349 + );
350 + }
351
352 EVMChainTransactionHistory setUpTransactionHistory(
353 WalletInfo walletInfo,
354 String password,
355 EncryptionFileUtils encryptionFileUtils,
171 - );
356 + ) {
357 + return EVMChainTransactionHistory(
358 + walletInfo: walletInfo,
359 + password: password,
360 + encryptionFileUtils: encryptionFileUtils,
361 + getCurrentChainId: () => selectedChainId,
362 + );
363 + }
364
365 String _getUSDCContractAddress() {
174 - return switch (_client.chainId) {
366 + return switch (selectedChainId) {
367 1 => "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
368 137 => "0x2791bca1f2de4661ed88a30c99a7a9449aa84174",
369 8453 => "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
370 42161 => "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
179 - _ => throw Exception("Unsupported chain ID: ${_client.chainId}"),
371 + _ => throw Exception("Unsupported chain ID: $selectedChainId"),
372 };
373 }
374
@@ -184,7 +376,7 @@ abstract class EVMChainWalletBase
376 Future<bool> checkNodeHealth() async {
377 try {
378 // Check native balance
187 - await _client.getBalance(_evmChainPrivateKey.address, throwOnError: true);
379 + await _client.getBalance(_evmChainPrivateKey.address);
380
381 // Check USDC token balance
382 String usdcContractAddress = _getUSDCContractAddress();
@@ -210,7 +402,7 @@ abstract class EVMChainWalletBase
402 // check for Already existing scam tokens, cuz users can get scammed twice ¯\_(ツ)_/¯
403 await _checkForExistingScamTokens();
404
213 - switch(walletInfo.hardwareWalletType) {
405 + switch (walletInfo.hardwareWalletType) {
406 case HardwareWalletType.ledger:
407 _evmChainPrivateKey = EvmLedgerCredentials(walletInfo.address);
408 walletAddresses.address = walletInfo.address;
@@ -238,6 +430,12 @@ abstract class EVMChainWalletBase
430 walletAddresses.address = _evmChainPrivateKey.address.hexEip55;
431 break;
432 }
433 +
434 + // Ensure balance is initialized for current currency (in case currency changed)
435 + if (!balance.containsKey(currency)) {
436 + balance[currency] = EVMChainERC20Balance(BigInt.zero);
437 + }
438 +
439 await save();
440 }
441
@@ -296,9 +494,6 @@ abstract class EVMChainWalletBase
494
495 final erc20Fee = await _getErc20TxFee(priority);
496 erc20TxEstimatedFee = erc20Fee.toString();
299 -
300 - printV('Native Estimated Fee: $nativeTxEstimatedFee');
301 - printV('ERC20 Estimated Fee: $erc20TxEstimatedFee');
497 }
498
499 Future<int> _getNativeTxFee(TransactionPriority? priority) async {
@@ -358,7 +553,8 @@ abstract class EVMChainWalletBase
553 }
554 }
555
361 - int getTotalPriorityFee(EVMChainTransactionPriority priority);
556 + int getTotalPriorityFee(EVMChainTransactionPriority priority) =>
557 + EVMChainUtils.getTotalPriorityFee(priority, selectedChainId);
558
559 /// Allows more customization to the fetch estimatedFees flow.
560 ///
@@ -384,10 +580,31 @@ abstract class EVMChainWalletBase
580 final gasBaseFee = await _client.getGasBaseFee();
581 final gasPrice = await _client.getGasUnitPrice();
582
583 + // Validate that we got valid gas price
584 + if (gasPrice <= 0) {
585 + printV('Invalid gas price received: $gasPrice');
586 + throw EVMChainTransactionFeesException('Failed to retrieve gas price from node');
587 + }
588 +
589 int maxFeePerGas;
590 int adjustedGasPrice;
591
390 - maxFeePerGas = gasBaseFee != null ? (gasBaseFee + priorityFee) : (gasPrice + priorityFee);
592 + if (gasBaseFee != null && gasBaseFee > 0) {
593 + // For chains with base fee, add priority fee (if supported) and a buffer to account for base fee increases
594 + // Base fee can increase between estimation and transaction submission
595 + final baseFeeWithPriority = gasBaseFee + priorityFee;
596 +
597 + // For chains without priority fees (e.g., Arbitrum), use a 5% buffer
598 + // For chains with priority fees (e.g., Ethereum), use a 15% buffer to account for base fee volatility
599 + // Base fee can increase significantly during high network activity
600 + final bufferMultiplier = hasPriorityFee ? 115 : 105;
601 + final bufferPercent = (baseFeeWithPriority * bufferMultiplier) ~/ 100;
602 + final bufferMin = baseFeeWithPriority + (baseFeeWithPriority ~/ 100);
603 + maxFeePerGas = bufferPercent > bufferMin ? bufferPercent : bufferMin;
604 + } else {
605 + // Fallback to gasPrice if baseFee is not available
606 + maxFeePerGas = gasPrice + priorityFee;
607 + }
608
609 adjustedGasPrice = maxFeePerGas;
610
@@ -410,7 +627,12 @@ abstract class EVMChainWalletBase
627 gasPrice: adjustedGasPrice,
628 );
629 } catch (e) {
413 - return GasParamsHandler.zero();
630 + printV('Error calculating estimated fee: ${e.toString()}');
631 + if (e is EVMChainTransactionFeesException) {
632 + rethrow;
633 + }
634 + throw EVMChainTransactionFeesException(
635 + 'Failed to calculate transaction fees: ${e.toString()}');
636 }
637 }
638
@@ -459,12 +681,14 @@ abstract class EVMChainWalletBase
681 syncStatus = FailedSyncStatus();
682 return;
683 }
462 -
684 await _updateBalance();
464 - await _updateTransactions();
465 - await _getEstimatedFees(
466 - hasPriorityFee ? EVMChainTransactionPriority.medium : null,
467 - ); // We're using medium priority for default estimation
685 +
686 + await Future.wait([
687 + _updateTransactions(),
688 + _getEstimatedFees(
689 + hasPriorityFee ? EVMChainTransactionPriority.medium : null,
690 + ), // We're using medium priority for default estimation
691 + ]);
692
693 syncStatus = SyncedSyncStatus();
694 } catch (e) {
@@ -487,9 +711,10 @@ abstract class EVMChainWalletBase
711 }
712
713 final transactionCurrency = balance.keys.firstWhere(
490 - (currency) =>
491 - currency.title == _credentials.currency.title &&
492 - currency.tag == _credentials.currency.tag,
714 + (currency) =>
715 + currency.title == _credentials.currency.title &&
716 + (currency.tag == _credentials.currency.tag ||
717 + currency.tag == _credentials.currency.title),
718 orElse: () => throw Exception(
719 'Currency ${_credentials.currency.title} ${_credentials.currency.tag} is not accessible in the wallet, try to enable it first.'));
720
@@ -514,11 +739,19 @@ abstract class EVMChainWalletBase
739 throw EVMChainTransactionCreationException(transactionCurrency);
740 }
741
517 - final totalOriginalAmount = EVMChainFormatter.parseEVMChainAmountToDouble(
518 - outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0)));
519 -
520 - totalAmount = parseFixed(
521 - EVMChainFormatter.truncateDecimals(totalOriginalAmount.toString(), exponent), exponent);
742 + totalAmount = outputs.fold<BigInt>(
743 + BigInt.zero,
744 + (acc, output) {
745 + if (output.cryptoAmount != null && output.cryptoAmount!.isNotEmpty) {
746 + return acc +
747 + EVMChainFormatter.parseEVMChainAmountToBigInt(
748 + output.cryptoAmount!,
749 + decimals: exponent,
750 + );
751 + }
752 + return acc;
753 + },
754 + );
755
756 final gasFeesModel = await calculateActualEstimatedFeeForCreateTransaction(
757 amount: totalAmount,
@@ -537,13 +770,14 @@ abstract class EVMChainWalletBase
770 } else {
771 final output = outputs.first;
772 if (!output.sendAll) {
540 - final totalOriginalAmount =
541 - EVMChainFormatter.parseEVMChainAmountToDouble(output.formattedCryptoAmount ?? 0);
542 -
543 - totalAmount = parseFixed(
544 - EVMChainFormatter.truncateDecimals(totalOriginalAmount.toString(), exponent),
545 - exponent,
546 - );
773 + if (output.cryptoAmount != null && output.cryptoAmount!.isNotEmpty) {
774 + totalAmount = EVMChainFormatter.parseEVMChainAmountToBigInt(
775 + output.cryptoAmount!,
776 + decimals: exponent,
777 + );
778 + } else {
779 + totalAmount = BigInt.zero;
780 + }
781 }
782
783 if (output.sendAll && transactionCurrency is Erc20Token) {
@@ -562,7 +796,7 @@ abstract class EVMChainWalletBase
796 maxFeePerGasForTransaction = gasFeesModel.maxFeePerGas;
797
798 if (output.sendAll && transactionCurrency is! Erc20Token) {
565 - if (_client.chainId == 8453) {
799 + if (selectedChainId == 8453) {
800 // Applying a small buffer to account for gas price fluctuations
801 // 10% or minimum 10,000 wei, whichever is higher
802 final refinedGasFee = estimatedFeesForTransaction;
@@ -581,7 +815,7 @@ abstract class EVMChainWalletBase
815
816 // check the fees on the base currency
817 if (estimatedFeesForTransaction > balance[currency]!.balance) {
584 - throw EVMChainTransactionFeesException(currency.title);
818 + throw EVMChainTransactionFeesException.fromCurrency(currency.title);
819 }
820
821 if (currencyBalance.balance < totalAmount) {
@@ -592,7 +826,7 @@ abstract class EVMChainWalletBase
826 if (transactionCurrency is Erc20Token &&
827 walletInfo.hardwareWalletType == HardwareWalletType.ledger) {
828 await (_evmChainPrivateKey as EvmLedgerCredentials)
595 - .provideERC20Info(transactionCurrency.contractAddress, _client.chainId);
829 + .provideERC20Info(transactionCurrency.contractAddress, selectedChainId);
830 }
831
832 final pendingEVMChainTransaction = await _client.signTransaction(
@@ -603,13 +837,14 @@ abstract class EVMChainWalletBase
837 gasFee: estimatedFeesForTransaction,
838 priority: _credentials.priority,
839 currency: transactionCurrency,
606 - feeCurrency: switch (_client.chainId) { 137 => "POL", _ => "ETH" },
840 + feeCurrency: switch (selectedChainId) { 137 => "POL", _ => "ETH" },
841 maxFeePerGas: maxFeePerGasForTransaction,
842 exponent: exponent,
843 contractAddress:
844 transactionCurrency is Erc20Token ? transactionCurrency.contractAddress : null,
845 data: hexOpReturnMemo,
846 gasPrice: maxFeePerGasForTransaction,
847 + useBlinkProtection: _credentials.useBlinkProtection,
848 );
849
850 return pendingEVMChainTransaction;
@@ -619,8 +854,9 @@ abstract class EVMChainWalletBase
854 String to,
855 String dataHex,
856 BigInt valueWei,
622 - EVMChainTransactionPriority? priority,
623 - ) async {
857 + EVMChainTransactionPriority? priority, {
858 + bool useBlinkProtection = true,
859 + }) async {
860 // Estimate gas with the SAME call (sender, to, value, data)
861 final gas = await calculateActualEstimatedFeeForCreateTransaction(
862 amount: valueWei, // native value (usually 0 for ERC20 transfer)
@@ -630,7 +866,7 @@ abstract class EVMChainWalletBase
866 data: _client.hexToBytes(dataHex),
867 );
868
633 - final nativeCurrency = switch (_client.chainId) {
869 + final nativeCurrency = switch (selectedChainId) {
870 137 => CryptoCurrency.maticpoly,
871 8453 => CryptoCurrency.baseEth,
872 42161 => CryptoCurrency.arbEth,
@@ -655,16 +891,13 @@ abstract class EVMChainWalletBase
891 contractAddress: null,
892 data: dataHex,
893 gasPrice: gas.gasPrice,
894 + useBlinkProtection: useBlinkProtection,
895 );
896 }
897
661 - Future<PendingTransaction> createApprovalTransaction(
662 - BigInt amount,
663 - String spender,
664 - CryptoCurrency token,
665 - EVMChainTransactionPriority? priority,
666 - String feeCurrency,
667 - ) async {
898 + Future<PendingTransaction> createApprovalTransaction(BigInt amount, String spender,
899 + CryptoCurrency token, EVMChainTransactionPriority? priority, String feeCurrency,
900 + {bool useBlinkProtection = true}) async {
901 final CryptoCurrency transactionCurrency =
902 balance.keys.firstWhere((element) => element.title == token.title);
903 assert(transactionCurrency is Erc20Token);
@@ -695,6 +928,7 @@ abstract class EVMChainWalletBase
928 exponent: transactionCurrency.decimal,
929 contractAddress: transactionCurrency.contractAddress,
930 gasPrice: gasFeesModel.gasPrice,
931 + useBlinkProtection: useBlinkProtection,
932 );
933 }
934
@@ -814,8 +1048,9 @@ abstract class EVMChainWalletBase
1048 String toJSON() => json.encode({
1049 'mnemonic': _mnemonic,
1050 'private_key': privateKey,
817 - 'balance': balance[currency]!.toJSON(),
1051 + 'balance': balance[currency]?.toJSON() ?? EVMChainERC20Balance(BigInt.zero).toJSON(),
1052 'passphrase': passphrase,
1053 + 'selected_chain_id': selectedChainId,
1054 });
1055
1056 Future<void> _updateBalance() async {
@@ -826,12 +1061,66 @@ abstract class EVMChainWalletBase
1061 }
1062
1063 Future<EVMChainERC20Balance> _fetchEVMChainBalance() async {
829 - final balance = await _client.getBalance(_evmChainPrivateKey.address);
830 - return EVMChainERC20Balance(balance.getInWei);
1064 + try {
1065 + final balance = await _client.getBalance(_evmChainPrivateKey.address);
1066 +
1067 + return EVMChainERC20Balance(balance.getInWei);
1068 + } catch (_) {
1069 + return balance[currency] ?? EVMChainERC20Balance(BigInt.zero);
1070 + }
1071 + }
1072 +
1073 + bool _isTokenMatchingChain(Erc20Token token) {
1074 + final registry = EvmChainRegistry();
1075 +
1076 + if (token.tag != null) {
1077 + final chainConfig = registry.getChainConfigByTag(token.tag!);
1078 + if (chainConfig != null) return chainConfig.chainId == selectedChainId;
1079 + }
1080 +
1081 + if (currency.tag == null) return token.tag == currency.title;
1082 +
1083 + return token.tag?.toLowerCase() == currency.tag?.toLowerCase();
1084 }
1085
1086 Future<void> _fetchErc20Balances() async {
834 - for (var token in evmChainErc20TokensBox.values) {
1087 + // Check if box is open before accessing it
1088 + if (!evmChainErc20TokensBox.isOpen) {
1089 + return;
1090 + }
1091 +
1092 + // First, clean up any tokens in balance map that don't belong to current chain
1093 + // This handles tokens from previous chains that might still be in the balance map
1094 + final tokensInBalance = balance.keys.whereType<Erc20Token>().toList();
1095 + final tokensInBox = evmChainErc20TokensBox.values.toList();
1096 + final boxTokenAddresses = tokensInBox.map((t) => t.contractAddress.toLowerCase()).toSet();
1097 +
1098 + for (var token in tokensInBalance) {
1099 + // Remove token if it's not in the current box or doesn't match current chain
1100 + if (!boxTokenAddresses.contains(token.contractAddress.toLowerCase()) ||
1101 + !_isTokenMatchingChain(token)) {
1102 + balance.remove(token);
1103 + }
1104 + }
1105 +
1106 + // Get a snapshot of tokens from current box to avoid issues if box is closed during iteration
1107 + final tokens = tokensInBox;
1108 +
1109 + for (var token in tokens) {
1110 + // Check if box is still open before operating on tokens
1111 + if (!evmChainErc20TokensBox.isOpen) break;
1112 +
1113 + if (!_isTokenMatchingChain(token)) {
1114 + printV('NOTEE!!!: Token ${token.title} is not matching the currency ${currency.title}');
1115 + try {
1116 + await deleteErc20Token(token, shouldUpdateBalance: false);
1117 + } catch (e) {
1118 + balance.remove(token);
1119 + printV('Error deleting token ${token.title}: $e');
1120 + }
1121 + continue;
1122 + }
1123 +
1124 try {
1125 if (token.enabled) {
1126 balance[token] = await _client.fetchERC20Balances(
@@ -859,7 +1148,7 @@ abstract class EVMChainWalletBase
1148 final erc20 = ERC20(
1149 client: _client.getWeb3Client()!,
1150 address: EthereumAddress.fromHex(tokenContract),
862 - chainId: _client.chainId,
1151 + chainId: selectedChainId,
1152 );
1153
1154 final allowance = await erc20.allowance(owner, EthereumAddress.fromHex(spender));
@@ -899,7 +1188,15 @@ abstract class EVMChainWalletBase
1188 @override
1189 Future<void> updateTransactionsHistory() async => await _updateTransactions();
1190
902 - List<Erc20Token> get erc20Currencies => evmChainErc20TokensBox.values.toList();
1191 + List<Erc20Token> get erc20Currencies {
1192 + try {
1193 + if (!evmChainErc20TokensBox.isOpen) return [];
1194 +
1195 + return evmChainErc20TokensBox.values.toList();
1196 + } catch (_) {
1197 + return [];
1198 + }
1199 + }
1200
1201 Future<void> addErc20Token(Erc20Token token) async {
1202 String? iconPath;
@@ -935,12 +1232,25 @@ abstract class EVMChainWalletBase
1232 }
1233 }
1234
938 - Future<void> deleteErc20Token(Erc20Token token) async {
939 - await token.delete();
1235 + Future<void> deleteErc20Token(Erc20Token token, {bool shouldUpdateBalance = true}) async {
1236 + // Check if box is open before trying to delete
1237 + if (!evmChainErc20TokensBox.isOpen) {
1238 + balance.remove(token);
1239 + return;
1240 + }
1241 +
1242 + try {
1243 + await token.delete();
1244 + } catch (e) {
1245 + // Token might be from a closed box, just remove from balance
1246 + printV('Error deleting token from box: $e');
1247 + }
1248
1249 balance.remove(token);
1250 await removeTokenTransactionsInHistory(token);
943 - _updateBalance();
1251 + if (shouldUpdateBalance) {
1252 + _updateBalance();
1253 + }
1254 }
1255
1256 Future<void> removeTokenTransactionsInHistory(Erc20Token token) async {
@@ -948,14 +1258,100 @@ abstract class EVMChainWalletBase
1258 await transactionHistory.save();
1259 }
1260
951 - Future<Erc20Token?> getErc20Token(String contractAddress, String chainName) async =>
952 - await _client.getErc20Token(contractAddress, chainName);
1261 + Future<Erc20Token?> getErc20Token(String contractAddress, String chainName) async {
1262 + try {
1263 + return await _client.getErc20Token(contractAddress, chainName);
1264 + } catch (e) {
1265 + printV('Error getting ERC20 token: $e');
1266 + rethrow;
1267 + }
1268 + }
1269
1270 void _onNewTransaction() {
1271 _updateBalance();
1272 _updateTransactions();
1273 }
1274
1275 + /// Static method to open an existing wallet
1276 + static Future<EVMChainWallet> open({
1277 + required String name,
1278 + required String password,
1279 + required WalletInfo walletInfo,
1280 + required EncryptionFileUtils encryptionFileUtils,
1281 + }) async {
1282 + final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
1283 + final path = await pathForWallet(name: name, type: walletInfo.type);
1284 +
1285 + Map<String, dynamic>? data;
1286 + try {
1287 + final jsonSource = await encryptionFileUtils.read(path: path, password: password);
1288 + data = json.decode(jsonSource) as Map<String, dynamic>;
1289 + } catch (e) {
1290 + if (!hasKeysFile) rethrow;
1291 + }
1292 +
1293 + final balance = EVMChainERC20Balance.fromJSON(data?['balance'] as String?) ??
1294 + EVMChainERC20Balance(BigInt.zero);
1295 +
1296 + final WalletKeysData keysData;
1297 + // Migrate wallet from the old scheme to the new .keys file scheme
1298 + if (!hasKeysFile) {
1299 + final mnemonic = data!['mnemonic'] as String?;
1300 + final privateKey = data['private_key'] as String?;
1301 + final passphrase = data['passphrase'] as String?;
1302 +
1303 + keysData = WalletKeysData(
1304 + mnemonic: mnemonic,
1305 + privateKey: privateKey,
1306 + passphrase: passphrase,
1307 + );
1308 + } else {
1309 + keysData = await WalletKeysFile.readKeysFile(
1310 + name,
1311 + walletInfo.type,
1312 + password,
1313 + encryptionFileUtils,
1314 + );
1315 + }
1316 +
1317 + final savedChainId = data?['selected_chain_id'] as int?;
1318 +
1319 + final registry = EvmChainRegistry();
1320 +
1321 + // Get chainId from wallet type, use saved chainId if available (for chain switching)
1322 + final defaultChainId = registry.getChainConfigByWalletType(walletInfo.type)?.chainId;
1323 + if (defaultChainId == null) {
1324 + throw Exception('Chain config not found for wallet type: ${walletInfo.type}');
1325 + }
1326 +
1327 + // Use saved chainId if available, otherwise default to wallet type's chainId
1328 + final chainId = savedChainId ?? defaultChainId;
1329 +
1330 + final chainConfig = registry.getChainConfig(chainId);
1331 + if (chainConfig == null) {
1332 + throw Exception('Chain config not found for chainId: $chainId');
1333 + }
1334 +
1335 + final client = EVMChainClientFactory.createClient(chainId);
1336 +
1337 + // Use saved chainId if available, otherwise use the computed chainId
1338 + final initialChainIdForWallet = savedChainId ?? chainId;
1339 +
1340 + return EVMChainWallet(
1341 + walletInfo: walletInfo,
1342 + derivationInfo: await walletInfo.getDerivationInfo(),
1343 + password: password,
1344 + mnemonic: keysData.mnemonic,
1345 + privateKey: keysData.privateKey,
1346 + passphrase: keysData.passphrase,
1347 + initialBalance: balance,
1348 + client: client,
1349 + nativeCurrency: chainConfig.nativeCurrency,
1350 + encryptionFileUtils: encryptionFileUtils,
1351 + initialChainId: initialChainIdForWallet,
1352 + );
1353 + }
1354 +
1355 @override
1356 Future<void> renameWalletFiles(String newWalletName) async {
1357 final transactionHistoryFileNameForWallet = getTransactionHistoryFileName();
cw_evm/lib/evm_chain_wallet_service.dart
+337 -14
@@ -1,16 +1,24 @@
1 import 'dart:io';
2
3 -import 'package:collection/collection.dart';
3 +import 'package:bip39/bip39.dart' as bip39;
4 +import 'package:cw_core/encryption_file_utils.dart';
5 import 'package:cw_core/pathForWallet.dart';
6 import 'package:cw_core/wallet_base.dart';
7 import 'package:cw_core/wallet_info.dart';
8 import 'package:cw_core/wallet_service.dart';
9 import 'package:cw_core/wallet_type.dart';
10 +import 'package:cw_evm/clients/evm_chain_client.dart';
11 +import 'package:cw_evm/evm_chain_client_factory.dart';
12 +import 'package:cw_evm/evm_chain_registry.dart';
13 import 'package:cw_evm/evm_chain_wallet.dart';
14 import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
11 -import 'package:hive/hive.dart';
15
13 -abstract class EVMChainWalletService<T extends EVMChainWallet> extends WalletService<
16 +/// Unified service for all EVM chains (Ethereum, Polygon, Base, Arbitrum, etc.)
17 +///
18 +/// This service dynamically determines which chain to use based on WalletType
19 +/// from credentials or walletInfo, eliminating the need for separate service
20 +/// classes per chain.
21 +class EVMChainWalletService extends WalletService<
22 EVMChainNewWalletCredentials,
23 EVMChainRestoreWalletFromSeedCredentials,
24 EVMChainRestoreWalletFromPrivateKey,
@@ -18,39 +26,354 @@ abstract class EVMChainWalletService<T extends EVMChainWallet> extends WalletSer
26 EVMChainWalletService(this.isDirect);
27
28 final bool isDirect;
29 + final EvmChainRegistry _registry = EvmChainRegistry();
30
31 + List<WalletType> get _evmWalletTypes {
32 + return _registry.getRegisteredWalletTypes();
33 + }
34 +
35 + Future<WalletInfo?> _findWalletByName(String name) async {
36 + for (final type in _evmWalletTypes) {
37 + final walletInfo = await WalletInfo.get(name, type);
38 + if (walletInfo != null) {
39 + return walletInfo;
40 + }
41 + }
42 + return null;
43 + }
44 +
45 + /// getType() is not meaningful for this unified service, it throws to prevent misuse
46 @override
23 - WalletType getType();
47 + WalletType getType() {
48 + throw UnsupportedError(
49 + 'EVMChainWalletService is unified and does not have a single type. '
50 + 'Use walletInfo.type instead.',
51 + );
52 + }
53
54 + /// Override saveBackup to look up walletType from wallet name
55 + /// Optionally accepts walletInfo to avoid lookup (useful during rename)
56 @override
26 - Future<T> create(EVMChainNewWalletCredentials credentials, {bool? isTestnet});
57 + Future<void> saveBackup(String name, {WalletInfo? walletInfo}) async {
58 + final info = walletInfo ?? await _findWalletByName(name);
59 + if (info == null) {
60 + throw Exception('Wallet not found: $name');
61 + }
62 +
63 + final backupWalletDirPath = await pathForWalletDir(name: "$name.backup", type: info.type);
64 + final walletDirPath = await pathForWalletDir(name: name, type: info.type);
65 +
66 + if (File(walletDirPath).existsSync()) {
67 + await File(walletDirPath).copy(backupWalletDirPath);
68 + }
69 + }
70
71 + /// Override restoreWalletFilesFromBackup to look up walletType from wallet name
72 @override
29 - Future<T> restoreFromHardwareWallet(EVMChainRestoreWalletFromHardware credentials);
73 + Future<void> restoreWalletFilesFromBackup(String name) async {
74 + final walletInfo = await _findWalletByName(name);
75 + if (walletInfo == null) {
76 + throw Exception('Wallet not found: $name');
77 + }
78 +
79 + final backupWalletDirPath = await pathForWalletDir(name: "$name.backup", type: walletInfo.type);
80 + final walletDirPath = await pathForWalletDir(name: name, type: walletInfo.type);
81 +
82 + if (File(backupWalletDirPath).existsSync()) {
83 + await File(backupWalletDirPath).copy(walletDirPath);
84 + }
85 + }
86
87 @override
32 - Future<T> openWallet(String name, String password);
88 + Future<EVMChainWallet> create(
89 + EVMChainNewWalletCredentials credentials, {
90 + bool? isTestnet,
91 + }) async {
92 + final walletInfo = credentials.walletInfo!;
93 +
94 + // Get chainId from wallet type
95 + final chainConfig = _registry.getChainConfigByWalletType(walletInfo.type);
96 + if (chainConfig == null) {
97 + throw Exception('Chain config not found for wallet type: ${walletInfo.type}');
98 + }
99 + final initialChainId = chainConfig.chainId;
100 +
101 + final client = EVMChainClientFactory.createClient(initialChainId);
102 + final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
103 + final mnemonic = credentials.mnemonic ?? bip39.generateMnemonic(strength: strength);
104 +
105 + final derivationInfo = await walletInfo.getDerivationInfo();
106 + if (derivationInfo.derivationPath == null || derivationInfo.derivationPath!.isEmpty) {
107 + derivationInfo.derivationPath = "m/44'/60'/0'/0";
108 + derivationInfo.derivationType = DerivationType.bip39;
109 + await derivationInfo.save();
110 + }
111 +
112 + final wallet = _createWalletInstance(
113 + walletType: walletInfo.type,
114 + walletInfo: walletInfo,
115 + derivationInfo: derivationInfo,
116 + mnemonic: mnemonic,
117 + password: credentials.password!,
118 + passphrase: credentials.passphrase,
119 + client: client,
120 + encryptionFileUtils: encryptionFileUtilsFor(isDirect),
121 + initialChainId: initialChainId,
122 + );
123 +
124 + await wallet.init();
125 + wallet.addInitialTokens();
126 + await wallet.save();
127 + return wallet;
128 + }
129 +
130 + @override
131 + Future<EVMChainWallet> openWallet(String name, String password) async {
132 + final walletInfo = await _findWalletByName(name);
133 + if (walletInfo == null) {
134 + throw Exception('Wallet not found');
135 + }
136 +
137 + try {
138 + final wallet = await _openWalletInstance(
139 + name: name,
140 + password: password,
141 + walletInfo: walletInfo,
142 + encryptionFileUtils: encryptionFileUtilsFor(isDirect),
143 + );
144 +
145 + await wallet.init();
146 + wallet.addInitialTokens();
147 + await wallet.save();
148 + await saveBackup(name);
149 + return wallet;
150 + } catch (_) {
151 + await restoreWalletFilesFromBackup(name);
152 +
153 + final wallet = await _openWalletInstance(
154 + name: name,
155 + password: password,
156 + walletInfo: walletInfo,
157 + encryptionFileUtils: encryptionFileUtilsFor(isDirect),
158 + );
159 +
160 + await wallet.init();
161 + wallet.addInitialTokens();
162 + await wallet.save();
163 + return wallet;
164 + }
165 + }
166 +
167 + @override
168 + Future<void> rename(String currentName, String password, String newName) async {
169 + final currentWalletInfo = await _findWalletByName(currentName);
170 + if (currentWalletInfo == null) {
171 + throw Exception('Wallet not found');
172 + }
173 +
174 + final currentWallet = await _openWalletInstance(
175 + password: password,
176 + name: currentName,
177 + walletInfo: currentWalletInfo,
178 + encryptionFileUtils: encryptionFileUtilsFor(isDirect),
179 + );
180 +
181 + await currentWallet.renameWalletFiles(newName);
182 +
183 + // Update walletInfo with new name before saving backup
184 + final newWalletInfo = currentWalletInfo;
185 + newWalletInfo.id = WalletBase.idFor(newName, currentWalletInfo.type);
186 + newWalletInfo.name = newName;
187 +
188 + // Pass walletInfo to saveBackup to avoid lookup (since WalletInfo not saved yet)
189 + await saveBackup(newName, walletInfo: newWalletInfo);
190 +
191 + await newWalletInfo.save();
192 + }
193
194 @override
35 - Future<void> rename(String currentName, String password, String newName);
195 + Future<EVMChainWallet> restoreFromSeed(
196 + EVMChainRestoreWalletFromSeedCredentials credentials, {
197 + bool? isTestnet,
198 + }) async {
199 + final walletInfo = credentials.walletInfo!;
200 +
201 + // Get chainId from wallet type
202 + final chainConfig = _registry.getChainConfigByWalletType(walletInfo.type);
203 + if (chainConfig == null) {
204 + throw Exception('Chain config not found for wallet type: ${walletInfo.type}');
205 + }
206 + final initialChainId = chainConfig.chainId;
207 +
208 + final client = EVMChainClientFactory.createClient(initialChainId);
209 +
210 + final derivationInfo = await walletInfo.getDerivationInfo();
211 + if (derivationInfo.derivationPath == null || derivationInfo.derivationPath!.isEmpty) {
212 + derivationInfo.derivationPath = "m/44'/60'/0'/0";
213 + derivationInfo.derivationType = DerivationType.bip39;
214 + await derivationInfo.save();
215 + }
216 +
217 + final wallet = _createWalletInstance(
218 + walletType: walletInfo.type,
219 + walletInfo: walletInfo,
220 + derivationInfo: derivationInfo,
221 + mnemonic: credentials.mnemonic,
222 + password: credentials.password!,
223 + passphrase: credentials.passphrase,
224 + client: client,
225 + encryptionFileUtils: encryptionFileUtilsFor(isDirect),
226 + initialChainId: initialChainId,
227 + );
228 +
229 + await wallet.init();
230 + wallet.addInitialTokens();
231 + await wallet.save();
232 + return wallet;
233 + }
234
235 @override
38 - Future<T> restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials, {bool? isTestnet});
236 + Future<EVMChainWallet> restoreFromKeys(
237 + EVMChainRestoreWalletFromPrivateKey credentials, {
238 + bool? isTestnet,
239 + }) async {
240 + final walletInfo = credentials.walletInfo!;
241 +
242 + // Get chainId from wallet type
243 + final chainConfig = _registry.getChainConfigByWalletType(walletInfo.type);
244 + if (chainConfig == null) {
245 + throw Exception('Chain config not found for wallet type: ${walletInfo.type}');
246 + }
247 + final initialChainId = chainConfig.chainId;
248 +
249 + final client = EVMChainClientFactory.createClient(initialChainId);
250 +
251 + final derivationInfo = await walletInfo.getDerivationInfo();
252 + if (derivationInfo.derivationPath == null || derivationInfo.derivationPath!.isEmpty) {
253 + derivationInfo.derivationPath = "m/44'/60'/0'/0";
254 + derivationInfo.derivationType = DerivationType.bip39;
255 + await derivationInfo.save();
256 + }
257 +
258 + final wallet = _createWalletInstance(
259 + walletType: walletInfo.type,
260 + walletInfo: walletInfo,
261 + derivationInfo: derivationInfo,
262 + privateKey: credentials.privateKey,
263 + password: credentials.password!,
264 + client: client,
265 + encryptionFileUtils: encryptionFileUtilsFor(isDirect),
266 + initialChainId: initialChainId,
267 + );
268 +
269 + await wallet.init();
270 + wallet.addInitialTokens();
271 + await wallet.save();
272 + return wallet;
273 + }
274
275 @override
41 - Future<T> restoreFromSeed(EVMChainRestoreWalletFromSeedCredentials credentials, {bool? isTestnet});
276 + Future<EVMChainWallet> restoreFromHardwareWallet(
277 + EVMChainRestoreWalletFromHardware credentials,
278 + ) async {
279 + final walletInfo = credentials.walletInfo!;
280 +
281 + // Get chainId from wallet type
282 + final chainConfig = _registry.getChainConfigByWalletType(walletInfo.type);
283 + if (chainConfig == null) {
284 + throw Exception('Chain config not found for wallet type: ${walletInfo.type}');
285 + }
286 + final initialChainId = chainConfig.chainId;
287 +
288 + final client = EVMChainClientFactory.createClient(initialChainId);
289 + final derivationInfo = await walletInfo.getDerivationInfo();
290 + derivationInfo.derivationType = DerivationType.bip39;
291 + derivationInfo.derivationPath = "m/44'/60'/${credentials.hwAccountData.accountIndex}'/0/0";
292 + await derivationInfo.save();
293 + walletInfo.hardwareWalletType = credentials.hardwareWalletType;
294 + walletInfo.address = credentials.hwAccountData.address;
295 + await walletInfo.save();
296 +
297 + final wallet = _createWalletInstance(
298 + walletType: walletInfo.type,
299 + walletInfo: walletInfo,
300 + derivationInfo: derivationInfo,
301 + password: credentials.password!,
302 + client: client,
303 + encryptionFileUtils: encryptionFileUtilsFor(isDirect),
304 + initialChainId: initialChainId,
305 + );
306 +
307 + await wallet.init();
308 + wallet.addInitialTokens();
309 + await wallet.save();
310 + return wallet;
311 + }
312
313 @override
44 - Future<bool> isWalletExit(String name) async =>
45 - File(await pathForWallet(name: name, type: getType())).existsSync();
314 + Future<bool> isWalletExit(String name) async {
315 + for (final type in _evmWalletTypes) {
316 + if (File(await pathForWallet(name: name, type: type)).existsSync()) {
317 + return true;
318 + }
319 + }
320 + return false;
321 + }
322
323 @override
324 Future<void> remove(String wallet) async {
49 - File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true);
50 - final walletInfo = await WalletInfo.get(wallet, getType());
325 + final walletInfo = await _findWalletByName(wallet);
326 if (walletInfo == null) {
327 throw Exception('Wallet not found');
328 }
329 +
330 + File(await pathForWalletDir(name: wallet, type: walletInfo.type)).delete(recursive: true);
331 await WalletInfo.delete(walletInfo);
332 }
333 +
334 + EVMChainWallet _createWalletInstance({
335 + required WalletType walletType,
336 + required WalletInfo walletInfo,
337 + required DerivationInfo derivationInfo,
338 + String? mnemonic,
339 + String? privateKey,
340 + required String password,
341 + required EVMChainClient client,
342 + required EncryptionFileUtils encryptionFileUtils,
343 + String? passphrase,
344 + int? initialChainId,
345 + }) {
346 + final chainConfig = _registry.getChainConfigByWalletType(walletType);
347 +
348 + if (chainConfig == null) {
349 + throw Exception('Chain config not found for wallet type: $walletType');
350 + }
351 +
352 + return EVMChainWallet(
353 + walletInfo: walletInfo,
354 + derivationInfo: derivationInfo,
355 + mnemonic: mnemonic,
356 + privateKey: privateKey,
357 + password: password,
358 + passphrase: passphrase,
359 + client: client,
360 + nativeCurrency: chainConfig.nativeCurrency,
361 + encryptionFileUtils: encryptionFileUtils,
362 + initialChainId: initialChainId,
363 + );
364 + }
365 +
366 + Future<EVMChainWallet> _openWalletInstance({
367 + required String name,
368 + required String password,
369 + required WalletInfo walletInfo,
370 + required EncryptionFileUtils encryptionFileUtils,
371 + }) {
372 + return EVMChainWalletBase.open(
373 + name: name,
374 + password: password,
375 + walletInfo: walletInfo,
376 + encryptionFileUtils: encryptionFileUtils,
377 + );
378 + }
379 }
cw_evm/lib/tokens/arbitrum_tokens.dart new
+83
@@ -0,0 +1,83 @@
1 +import 'package:cw_core/crypto_currency.dart';
2 +import 'package:cw_core/erc20_token.dart';
3 +
4 +/// Default ERC20 tokens for Arbitrum
5 +class ArbitrumTokens {
6 + static List<Erc20Token> get tokens {
7 + final tokens = [
8 + Erc20Token(
9 + name: "Arbitrum",
10 + symbol: "ARB",
11 + contractAddress: "0x912CE59144191C1204E64559FE8253a0e49E6548",
12 + decimal: 18,
13 + enabled: true,
14 + ),
15 + Erc20Token(
16 + name: "USD Coin",
17 + symbol: "USDC",
18 + contractAddress: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
19 + decimal: 6,
20 + enabled: true,
21 + ),
22 + Erc20Token(
23 + name: "USDC.e",
24 + symbol: "USDC.e",
25 + contractAddress: "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8",
26 + decimal: 6,
27 + enabled: true,
28 + ),
29 + Erc20Token(
30 + name: "Wrapped BTC",
31 + symbol: "WBTC",
32 + contractAddress: "0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f",
33 + decimal: 8,
34 + enabled: true,
35 + ),
36 + Erc20Token(
37 + name: "Chainlink Token",
38 + symbol: "LINK",
39 + contractAddress: "0xf97f4df75117a78c1A5a0DBb814Af92458539FB4",
40 + decimal: 18,
41 + enabled: true,
42 + ),
43 + Erc20Token(
44 + name: "Wrapped liquid staked Ether 2.0",
45 + symbol: "wstETH",
46 + contractAddress: "0x0fBcbaEA96Ce0cF7Ee00A8c19c3ab6f5Dc8E1921",
47 + decimal: 18,
48 + enabled: false,
49 + ),
50 + Erc20Token(
51 + name: "Wrapped Ether",
52 + symbol: "WETH",
53 + contractAddress: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
54 + decimal: 18,
55 + enabled: false,
56 + ),
57 + Erc20Token(
58 + name: "DAI",
59 + symbol: "DAI",
60 + contractAddress: "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1",
61 + decimal: 18,
62 + enabled: false,
63 + ),
64 + ];
65 +
66 + return tokens.map((token) {
67 + String? iconPath;
68 + if (token.iconPath?.isEmpty ?? true) {
69 + try {
70 + iconPath = CryptoCurrency.all
71 + .firstWhere((element) =>
72 + element.title.toUpperCase() == token.symbol.toUpperCase())
73 + .iconPath;
74 + } catch (_) {}
75 + } else {
76 + iconPath = token.iconPath;
77 + }
78 +
79 + return Erc20Token.copyWith(token, icon: iconPath, tag: 'ARB');
80 + }).toList();
81 + }
82 +}
83 +
cw_evm/lib/tokens/base_tokens.dart new
+76
@@ -0,0 +1,76 @@
1 +import 'package:cw_core/crypto_currency.dart';
2 +import 'package:cw_core/erc20_token.dart';
3 +
4 +/// Default ERC20 tokens for Base
5 +class BaseTokens {
6 + static List<Erc20Token> get tokens {
7 + final tokens = [
8 + Erc20Token(
9 + name: "USD Coin",
10 + symbol: "USDC",
11 + contractAddress: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
12 + decimal: 6,
13 + enabled: true,
14 + ),
15 + Erc20Token(
16 + name: "USDe",
17 + symbol: "USDe",
18 + contractAddress: "0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34",
19 + decimal: 18,
20 + enabled: true,
21 + ),
22 + Erc20Token(
23 + name: "Dai",
24 + symbol: "DAI",
25 + contractAddress: "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb",
26 + decimal: 18,
27 + enabled: true,
28 + ),
29 + Erc20Token(
30 + name: "Bridged Tether USD",
31 + symbol: "USDT",
32 + contractAddress: "0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2",
33 + decimal: 6,
34 + enabled: true,
35 + ),
36 + Erc20Token(
37 + name: "Wrapped Ether",
38 + symbol: "WETH",
39 + contractAddress: "0x4200000000000000000000000000000000000006",
40 + decimal: 18,
41 + enabled: false,
42 + ),
43 + Erc20Token(
44 + name: "Wrapped BTC",
45 + symbol: "WBTC",
46 + contractAddress: "0x0555E30da8f98308EdB960aa94C0Db47230d2B9c",
47 + decimal: 8,
48 + enabled: false,
49 + ),
50 + Erc20Token(
51 + name: "SPX6900",
52 + symbol: "SPX",
53 + contractAddress: "0x50dA645f148798F68EF2d7dB7C1CB22A6819bb2C",
54 + decimal: 8,
55 + enabled: false,
56 + ),
57 + ];
58 +
59 + return tokens.map((token) {
60 + String? iconPath;
61 + if (token.iconPath?.isEmpty ?? true) {
62 + try {
63 + iconPath = CryptoCurrency.all
64 + .firstWhere((element) =>
65 + element.title.toUpperCase() == token.symbol.toUpperCase())
66 + .iconPath;
67 + } catch (_) {}
68 + } else {
69 + iconPath = token.iconPath;
70 + }
71 +
72 + return Erc20Token.copyWith(token, icon: iconPath, tag: 'BASE');
73 + }).toList();
74 + }
75 +}
76 +
cw_evm/lib/tokens/ethereum_tokens.dart new
+97
@@ -0,0 +1,97 @@
1 +import 'package:cw_core/crypto_currency.dart';
2 +import 'package:cw_core/erc20_token.dart';
3 +
4 +/// Default ERC20 tokens for Ethereum Mainnet
5 +class EthereumTokens {
6 + static List<Erc20Token> get tokens {
7 + final tokens = [
8 + Erc20Token(
9 + name: "USD Coin",
10 + symbol: "USDC",
11 + contractAddress: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
12 + decimal: 6,
13 + enabled: true,
14 + ),
15 + Erc20Token(
16 + name: "USDT Tether",
17 + symbol: "USDT",
18 + contractAddress: "0xdac17f958d2ee523a2206206994597c13d831ec7",
19 + decimal: 6,
20 + enabled: true,
21 + ),
22 + Erc20Token(
23 + name: "Decentralized Euro",
24 + symbol: "DEURO",
25 + contractAddress: "0xbA3f535bbCcCcA2A154b573Ca6c5A49BAAE0a3ea",
26 + decimal: 18,
27 + enabled: true,
28 + ),
29 + Erc20Token(
30 + name: "Dai",
31 + symbol: "DAI",
32 + contractAddress: "0x6B175474E89094C44Da98b954EedeAC495271d0F",
33 + decimal: 18,
34 + enabled: true,
35 + ),
36 + Erc20Token(
37 + name: "Wrapped Ether",
38 + symbol: "WETH",
39 + contractAddress: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
40 + decimal: 18,
41 + enabled: false,
42 + ),
43 + Erc20Token(
44 + name: "Pepe",
45 + symbol: "PEPE",
46 + contractAddress: "0x6982508145454ce325ddbe47a25d4ec3d2311933",
47 + decimal: 18,
48 + enabled: false,
49 + ),
50 + Erc20Token(
51 + name: "SHIBA INU",
52 + symbol: "SHIB",
53 + contractAddress: "0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce",
54 + decimal: 18,
55 + enabled: false,
56 + ),
57 + Erc20Token(
58 + name: "ApeCoin",
59 + symbol: "APE",
60 + contractAddress: "0x4d224452801aced8b2f0aebe155379bb5d594381",
61 + decimal: 18,
62 + enabled: false,
63 + ),
64 + Erc20Token(
65 + name: "Matic Token",
66 + symbol: "MATIC",
67 + contractAddress: "0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0",
68 + decimal: 18,
69 + enabled: false,
70 + ),
71 + Erc20Token(
72 + name: "Wrapped BTC",
73 + symbol: "WBTC",
74 + contractAddress: "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599",
75 + decimal: 8,
76 + enabled: false,
77 + ),
78 + ];
79 +
80 + return tokens.map((token) {
81 + String? iconPath;
82 + if (token.iconPath?.isEmpty ?? true) {
83 + try {
84 + iconPath = CryptoCurrency.all
85 + .firstWhere((element) =>
86 + element.title.toUpperCase() == token.symbol.toUpperCase())
87 + .iconPath;
88 + } catch (_) {}
89 + } else {
90 + iconPath = token.iconPath;
91 + }
92 +
93 + return Erc20Token.copyWith(token, icon: iconPath, tag: 'ETH');
94 + }).toList();
95 + }
96 +}
97 +
cw_evm/lib/tokens/polygon_tokens.dart new
+90
@@ -0,0 +1,90 @@
1 +import 'package:cw_core/crypto_currency.dart';
2 +import 'package:cw_core/erc20_token.dart';
3 +
4 +/// Default ERC20 tokens for Polygon
5 +class PolygonTokens {
6 + static List<Erc20Token> get tokens {
7 + final tokens = [
8 + Erc20Token(
9 + name: "Wrapped Ether",
10 + symbol: "WETH",
11 + contractAddress: "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619",
12 + decimal: 18,
13 + enabled: false,
14 + ),
15 + Erc20Token(
16 + name: "Tether USD (PoS)",
17 + symbol: "USDT",
18 + contractAddress: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F",
19 + decimal: 6,
20 + enabled: true,
21 + ),
22 + Erc20Token(
23 + name: "USD Coin",
24 + symbol: "USDC",
25 + contractAddress: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
26 + decimal: 6,
27 + enabled: true,
28 + ),
29 + Erc20Token(
30 + name: "USD Coin (POS)",
31 + symbol: "USDC.e",
32 + contractAddress: "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
33 + decimal: 6,
34 + enabled: true,
35 + ),
36 + Erc20Token(
37 + name: "Decentralized Euro",
38 + symbol: "DEURO",
39 + contractAddress: "0xC2ff25dD99e467d2589b2c26EDd270F220F14E47",
40 + decimal: 18,
41 + enabled: true,
42 + ),
43 + Erc20Token(
44 + name: "Avalanche Token",
45 + symbol: "AVAX",
46 + contractAddress: "0x2C89bbc92BD86F8075d1DEcc58C7F4E0107f286b",
47 + decimal: 18,
48 + enabled: false,
49 + ),
50 + Erc20Token(
51 + name: "Wrapped BTC (PoS)",
52 + symbol: "WBTC",
53 + contractAddress: "0x1BFD67037B42Cf73acF2047067bd4F2C47D9BfD6",
54 + decimal: 8,
55 + enabled: false,
56 + ),
57 + Erc20Token(
58 + name: "Dai (PoS)",
59 + symbol: "DAI",
60 + contractAddress: "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063",
61 + decimal: 18,
62 + enabled: true,
63 + ),
64 + Erc20Token(
65 + name: "SHIBA INU (PoS)",
66 + symbol: "SHIB",
67 + contractAddress: "0x6f8a06447Ff6FcF75d803135a7de15CE88C1d4ec",
68 + decimal: 18,
69 + enabled: false,
70 + ),
71 + ];
72 +
73 + return tokens.map((token) {
74 + String? iconPath;
75 + if (token.iconPath?.isEmpty ?? true) {
76 + try {
77 + iconPath = CryptoCurrency.all
78 + .firstWhere((element) =>
79 + element.title.toUpperCase() == token.symbol.toUpperCase())
80 + .iconPath;
81 + } catch (_) {}
82 + } else {
83 + iconPath = token.iconPath;
84 + }
85 +
86 + return Erc20Token.copyWith(token, icon: iconPath, tag: 'POL');
87 + }).toList();
88 + }
89 +}
90 +
cw_evm/lib/utils/evm_chain_formatter.dart new
+57
@@ -0,0 +1,57 @@
1 +import 'dart:math';
2 +
3 +class EVMChainFormatter {
4 + static const int evmDecimals = 18;
5 +
6 + static int parseEVMChainAmount(String amount) {
7 + try {
8 + return (double.parse(amount) * pow(10, evmDecimals)).round();
9 + } catch (_) {
10 + return 0;
11 + }
12 + }
13 +
14 + /// Parse EVM chain amount to BigInt to avoid integer overflow for large amounts
15 + /// [decimals] defaults to 18 for native ETH/POL, but should be set to token decimals for ERC20 tokens
16 + static BigInt parseEVMChainAmountToBigInt(String amount, {int decimals = evmDecimals}) {
17 + try {
18 + String cleanAmount = amount.replaceAll(',', '.');
19 +
20 + bool isNegative = cleanAmount.startsWith('-');
21 + if (isNegative) {
22 + cleanAmount = cleanAmount.substring(1);
23 + }
24 +
25 + final parts = cleanAmount.split('.');
26 + String whole = parts[0].isEmpty ? '0' : parts[0];
27 + String fraction = parts.length > 1 ? parts[1] : '';
28 +
29 + // 3. Strict Truncation Logic
30 + // If fraction is longer than decimals, strictly cut it off.
31 + // If shorter, pad it with zeros.
32 + if (fraction.length > decimals) {
33 + fraction = fraction.substring(0, decimals);
34 + } else {
35 + fraction = fraction.padRight(decimals, '0');
36 + }
37 +
38 + final wholeBigInt = BigInt.parse(whole);
39 + final fractionBigInt = BigInt.parse(fraction);
40 + final multiplier = BigInt.from(10).pow(decimals);
41 +
42 + return (wholeBigInt * multiplier) + fractionBigInt;
43 + } catch (_) {
44 + return BigInt.zero;
45 + }
46 + }
47 +
48 + static String truncateDecimals(String amount, int decimals) {
49 + final parts = amount.split(".");
50 +
51 + if (parts.length == 2) {
52 + parts[1] = parts[1].substring(0, parts[1].length > decimals ? decimals : parts[1].length);
53 + }
54 +
55 + return parts.join(".");
56 + }
57 +}
cw_evm/lib/utils/evm_chain_utils.dart new
+115
@@ -0,0 +1,115 @@
1 +import 'package:cw_core/erc20_token.dart';
2 +import 'package:cw_evm/evm_chain_transaction_priority.dart';
3 +import 'package:web3dart/web3dart.dart' show EtherAmount, EtherUnit;
4 +
5 +/// Utility class for chain-specific EVM chain operations
6 +class EVMChainUtils {
7 + static int getTotalPriorityFee(EVMChainTransactionPriority priority, int chainId) {
8 + return switch (chainId) {
9 + 1 => _ethereumPriorityFee(priority),
10 + 137 => _polygonPriorityFee(priority),
11 + 8453 => _basePriorityFee(priority),
12 + 42161 => 0, // Arbitrum doesn't use priority fees
13 + _ => _ethereumPriorityFee(priority),
14 + };
15 + }
16 +
17 + static bool hasPriorityFee(int chainId) {
18 + return switch (chainId) {
19 + 42161 => false, // Arbitrum doesn't use priority fees
20 + _ => true,
21 + };
22 + }
23 +
24 + static String getErc20TokensBoxName(String walletName, int chainId) {
25 + final sanitizedName = walletName.replaceAll(" ", "_");
26 +
27 + return switch (chainId) {
28 + 1 => "${sanitizedName}_${Erc20Token.ethereumBoxName}",
29 + 137 => "${sanitizedName}_${Erc20Token.polygonBoxName}",
30 + 8453 => "${sanitizedName}_${Erc20Token.baseBoxName}",
31 + 42161 => "${sanitizedName}_${Erc20Token.arbitrumBoxName}",
32 + _ => "${sanitizedName}_${Erc20Token.ethereumBoxName}",
33 + };
34 + }
35 +
36 + static String getTransactionHistoryFileName(int chainId) {
37 + return switch (chainId) {
38 + 1 => 'transactions.json', // Ethereum
39 + 137 => 'polygon_transactions.json',
40 + 8453 => 'base_transactions.json',
41 + 42161 => 'arbitrum_transactions.json',
42 + _ => 'transactions_$chainId.json', // Generic format for other chains
43 + };
44 + }
45 +
46 + /// Get scan provider preference key for a wallet type
47 + static String getScanProviderPreferenceKey(int chainId) {
48 + return switch (chainId) {
49 + 1 => 'use_etherscan',
50 + 137 => 'use_polygonscan',
51 + 8453 => 'use_basescan',
52 + 42161 => 'use_arbiscan',
53 + _ => 'use_etherscan',
54 + };
55 + }
56 +
57 + static String getDefaultTokenTag(int chainId) {
58 + return switch (chainId) {
59 + 1 => 'ETH',
60 + 137 => 'POL',
61 + 8453 => 'BASE',
62 + 42161 => 'ARB',
63 + _ => 'ETH',
64 + };
65 + }
66 +
67 + static String getFeeCurrency(int chainId) {
68 + return switch (chainId) {
69 + 1 => 'ETH',
70 + 137 => 'MATIC',
71 + 8453 => 'ETH',
72 + 42161 => 'ETH',
73 + _ => 'ETH',
74 + };
75 + }
76 +
77 + static String getDefaultTokenSymbol(int chainId) {
78 + return switch (chainId) {
79 + 1 => 'ETH',
80 + 137 => 'MATIC',
81 + 8453 => 'BASE',
82 + 42161 => 'ARBITRUM',
83 + _ => 'ETH',
84 + };
85 + }
86 +
87 + static int _ethereumPriorityFee(EVMChainTransactionPriority priority) {
88 + return EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
89 + }
90 +
91 + // Polygon priority fee calculation (minimum 25 gwei + additional based on priority)
92 + static int _polygonPriorityFee(EVMChainTransactionPriority priority) {
93 + const int minPriorityFee = 25;
94 + final minPriorityFeeWei = EtherAmount.fromInt(EtherUnit.gwei, minPriorityFee).getInWei.toInt();
95 +
96 + final int additionalPriorityFee = switch (priority) {
97 + EVMChainTransactionPriority.slow => 0,
98 + EVMChainTransactionPriority.medium =>
99 + EtherAmount.fromInt(EtherUnit.gwei, 15).getInWei.toInt(),
100 + EVMChainTransactionPriority.fast => EtherAmount.fromInt(EtherUnit.gwei, 35).getInWei.toInt(),
101 + _ => 0,
102 + };
103 +
104 + return minPriorityFeeWei + additionalPriorityFee;
105 + }
106 +
107 + static int _basePriorityFee(EVMChainTransactionPriority priority) {
108 + return switch (priority) {
109 + EVMChainTransactionPriority.fast => EtherAmount.fromInt(EtherUnit.mwei, 5).getInWei.toInt(),
110 + EVMChainTransactionPriority.medium => EtherAmount.fromInt(EtherUnit.mwei, 3).getInWei.toInt(),
111 + EVMChainTransactionPriority.slow => EtherAmount.fromInt(EtherUnit.mwei, 1).getInWei.toInt(),
112 + _ => EtherAmount.fromInt(EtherUnit.mwei, 1).getInWei.toInt(),
113 + };
114 + }
115 +}
cw_evm/lib/utils/network_chain_utils.dart new
+63
@@ -0,0 +1,63 @@
1 +import 'package:cw_core/crypto_currency.dart';
2 +
3 +/// Immutable configuration for an EVM chain
4 +class ChainConfig {
5 + final int chainId;
6 + final String name;
7 + final String shortCode;
8 + final String caip2; // e.g., "eip155:1"
9 + final CryptoCurrency nativeCurrency;
10 + final ChainCapabilities capabilities;
11 + final List<String> defaultRpcEndpoints;
12 + final List<String> explorerUrls;
13 + final FeeModel feeModel;
14 +
15 + const ChainConfig({
16 + required this.chainId,
17 + required this.name,
18 + required this.shortCode,
19 + required this.caip2,
20 + required this.nativeCurrency,
21 + required this.capabilities,
22 + required this.defaultRpcEndpoints,
23 + required this.explorerUrls,
24 + required this.feeModel,
25 + });
26 +}
27 +
28 +/// Capabilities supported by an EVM chain
29 +class ChainCapabilities {
30 + final bool supportsERC20;
31 + final bool supportsEIP1559;
32 + final bool supportsInternalTx;
33 + final bool supportsSubscriptions;
34 + final bool supportsENS;
35 +
36 + const ChainCapabilities({
37 + required this.supportsERC20,
38 + required this.supportsEIP1559,
39 + required this.supportsInternalTx,
40 + required this.supportsSubscriptions,
41 + required this.supportsENS,
42 + });
43 +}
44 +
45 +/// Fee model type for EVM chains
46 +enum FeeType {
47 + legacy,
48 + eip1559,
49 +}
50 +
51 +/// Fee model configuration for an EVM chain
52 +class FeeModel {
53 + final FeeType type;
54 + final int defaultGasLimit;
55 + final int? maxPriorityFee;
56 +
57 + const FeeModel({
58 + required this.type,
59 + required this.defaultGasLimit,
60 + this.maxPriorityFee,
61 + });
62 +}
63 +
cw_evm/test/cw_evm_test.dart
+1 -1
@@ -1,6 +1,6 @@
1 import "dart:typed_data";
2
3 -import "package:cw_evm/evm_chain_formatter.dart";
3 +import "package:cw_evm/utils/evm_chain_formatter.dart";
4 import "package:cw_evm/utils/rlp_decode.dart";
5 import "package:flutter_test/flutter_test.dart";
6 import "package:web3dart/crypto.dart";
cw_polygon/.gitignore deleted
-30
@@ -1,30 +0,0 @@
1 -# Miscellaneous
2 -*.class
3 -*.log
4 -*.pyc
5 -*.swp
6 -.DS_Store
7 -.atom/
8 -.buildlog/
9 -.history
10 -.svn/
11 -migrate_working_dir/
12 -
13 -# IntelliJ related
14 -*.iml
15 -*.ipr
16 -*.iws
17 -.idea/
18 -
19 -# The .vscode folder contains launch configuration and tasks you configure in
20 -# VS Code which you may wish to be included in version control, so this line
21 -# is commented out by default.
22 -#.vscode/
23 -
24 -# Flutter/Dart/Pub related
25 -# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
26 -/pubspec.lock
27 -**/doc/api/
28 -.dart_tool/
29 -.packages
30 -build/
cw_polygon/.metadata deleted
-10
@@ -1,10 +0,0 @@
1 -# This file tracks properties of this Flutter project.
2 -# Used by Flutter tool to assess capabilities and perform upgrades etc.
3 -#
4 -# This file should be version controlled and should not be manually edited.
5 -
6 -version:
7 - revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
8 - channel: stable
9 -
10 -project_type: package
cw_polygon/CHANGELOG.md deleted
-3
@@ -1,3 +0,0 @@
1 -## 0.0.1
2 -
3 -* TODO: Describe initial release.
cw_polygon/LICENSE deleted
-1
@@ -1 +0,0 @@
1 -TODO: Add your license here.
cw_polygon/README.md deleted
-63
@@ -1,63 +0,0 @@
1 -## cw_polygon
2 -
3 -Polygon (PoS) wallet module built on the shared EVM base (`cw_evm`). Supports native MATIC and ERC‑20 tokens with PolygonScan-backed history.
4 -
5 -### Features
6 -
7 -- EVM chain integration (chainId 137) with `web3dart`.
8 -- Default ERC‑20 token list and per‑wallet token box.
9 -- History via Etherscan v2 API (Polygon chain id) and internal tx support.
10 -- Fee handling tuned for Polygon (priority fee floor, legacy gasPrice when needed).
11 -- Create/sign native and ERC‑20 transfers; approvals; send/broadcast.
12 -- Manage tokens (enable/disable, add/remove) and balances.
13 -- Message signing and verification.
14 -
15 -### Getting started
16 -
17 -Provide secrets used by the shared EVM layer in `cw_evm/lib/.secrets.g.dart`:
18 -
19 -```dart
20 -// cw_evm/lib/.secrets.g.dart (DO NOT COMMIT)
21 -const String etherScanApiKey = 'YOUR_ETHERSCAN_KEY';
22 -const String nowNodesApiKey = 'YOUR_NOWNODES_KEY'; // if using matic.nownodes.io
23 -const String moralisApiKey = 'YOUR_MORALIS_KEY'; // optional: ERC20 metadata
24 -```
25 -
26 -Connect and sync:
27 -
28 -```dart
29 -final service = PolygonWalletService(walletInfoBox, true, client: PolygonClient());
30 -final wallet = await service.create(EVMChainNewWalletCredentials(name: 'My POL', password: 'secret'));
31 -await wallet.connectToNode(node: Node(uriRaw: 'polygon-rpc.com', isSSL: true));
32 -await wallet.startSync();
33 -```
34 -
35 -### Usage
36 -
37 -Send MATIC:
38 -
39 -```dart
40 -final pending = await wallet.createTransaction(
41 - EVMChainTransactionCredentials.single(
42 - address: '0x...',
43 - cryptoAmount: '0.2',
44 - currency: CryptoCurrency.maticpoly,
45 - priority: EVMChainTransactionPriority.medium,
46 - ),
47 -);
48 -final hash = await pending.commit();
49 -```
50 -
51 -Add an ERC‑20 token and refresh balance:
52 -
53 -```dart
54 -final token = await wallet.getErc20Token('0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', 'polygon'); // USDC
55 -if (token != null) {
56 - await wallet.addErc20Token(token);
57 -}
58 -```
59 -
60 -### Additional information
61 -
62 -- Toggle PolygonScan usage via shared preferences key `use_polygonscan`.
63 -- See `lib/` for APIs: `PolygonClient`, `PolygonWallet`, `PolygonWalletService`.
cw_polygon/analysis_options.yaml deleted
-4
@@ -1,4 +0,0 @@
1 -include: package:flutter_lints/flutter.yaml
2 -
3 -# Additional information about this file can be found at
4 -# https://dart.dev/guides/language/analysis-options
cw_polygon/devtools_options.yaml deleted
-3
@@ -1,3 +0,0 @@
1 -description: This file stores settings for Dart & Flutter DevTools.
2 -documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
3 -extensions:
cw_polygon/lib/cw_polygon.dart deleted
-7
@@ -1,7 +0,0 @@
1 -library cw_polygon;
2 -
3 -/// A Calculator.
4 -class Calculator {
5 - /// Returns [value] plus 1.
6 - int addOne(int value) => value + 1;
7 -}
cw_polygon/lib/default_polygon_erc20_tokens.dart deleted
-93
@@ -1,93 +0,0 @@
1 -import 'package:cw_core/crypto_currency.dart';
2 -import 'package:cw_core/erc20_token.dart';
3 -
4 -class DefaultPolygonErc20Tokens {
5 - final List<Erc20Token> _defaultTokens = [
6 - Erc20Token(
7 - name: "Wrapped Ether",
8 - symbol: "WETH",
9 - contractAddress: "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619",
10 - decimal: 18,
11 - enabled: false,
12 - ),
13 - Erc20Token(
14 - name: "Tether USD (PoS)",
15 - symbol: "USDT",
16 - contractAddress: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F",
17 - decimal: 6,
18 - enabled: true,
19 - ),
20 - Erc20Token(
21 - name: "USD Coin",
22 - symbol: "USDC",
23 - contractAddress: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
24 - decimal: 6,
25 - enabled: true,
26 - ),
27 - Erc20Token(
28 - name: "USD Coin (POS)",
29 - symbol: "USDC.e",
30 - contractAddress: "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
31 - decimal: 6,
32 - enabled: true,
33 - ),
34 - Erc20Token(
35 - name: "Decentralized Euro",
36 - symbol: "DEURO",
37 - contractAddress: "0xC2ff25dD99e467d2589b2c26EDd270F220F14E47",
38 - decimal: 18,
39 - enabled: true,
40 - ),
41 - Erc20Token(
42 - name: "Avalanche Token",
43 - symbol: "AVAX",
44 - contractAddress: "0x2C89bbc92BD86F8075d1DEcc58C7F4E0107f286b",
45 - decimal: 18,
46 - enabled: false,
47 - ),
48 - Erc20Token(
49 - name: "Wrapped BTC (PoS)",
50 - symbol: "WBTC",
51 - contractAddress: "0x1BFD67037B42Cf73acF2047067bd4F2C47D9BfD6",
52 - decimal: 8,
53 - enabled: false,
54 - ),
55 - Erc20Token(
56 - name: "Dai (PoS)",
57 - symbol: "DAI",
58 - contractAddress: "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063",
59 - decimal: 18,
60 - enabled: true,
61 - ),
62 - Erc20Token(
63 - name: "SHIBA INU (PoS)",
64 - symbol: "SHIB",
65 - contractAddress: "0x6f8a06447Ff6FcF75d803135a7de15CE88C1d4ec",
66 - decimal: 18,
67 - enabled: false,
68 - ),
69 - Erc20Token(
70 - name: "Uniswap (PoS)",
71 - symbol: "UNI",
72 - contractAddress: "0xb33EaAd8d922B1083446DC23f610c2567fB5180f",
73 - decimal: 18,
74 - enabled: false,
75 - ),
76 - ];
77 -
78 - List<Erc20Token> get initialPolygonErc20Tokens => _defaultTokens.map((token) {
79 - String? iconPath;
80 - if (token.iconPath?.isEmpty ?? true) {
81 - try {
82 - iconPath = CryptoCurrency.all
83 - .firstWhere((element) =>
84 - element.title.toUpperCase() == token.symbol.split(".").first.toUpperCase())
85 - .iconPath;
86 - } catch (_) {}
87 - } else {
88 - iconPath = token.iconPath;
89 - }
90 -
91 - return Erc20Token.copyWith(token, icon: iconPath, tag: 'POL');
92 - }).toList();
93 -}
cw_polygon/lib/polygon_client.dart deleted
-103
@@ -1,103 +0,0 @@
1 -import 'dart:convert';
2 -
3 -import 'package:cw_evm/evm_chain_client.dart';
4 -import 'package:cw_evm/.secrets.g.dart' as secrets;
5 -import 'package:cw_evm/evm_chain_transaction_model.dart';
6 -import 'package:flutter/foundation.dart';
7 -import 'package:web3dart/web3dart.dart';
8 -
9 -class PolygonClient extends EVMChainClient {
10 - @override
11 - Transaction createTransaction({
12 - required EthereumAddress from,
13 - required EthereumAddress to,
14 - required EtherAmount amount,
15 - EtherAmount? maxPriorityFeePerGas,
16 - Uint8List? data,
17 - int? maxGas,
18 - EtherAmount? gasPrice,
19 - EtherAmount? maxFeePerGas,
20 - }) {
21 - EtherAmount? finalGasPrice = gasPrice;
22 -
23 - if (gasPrice == null && maxFeePerGas != null) {
24 - // If we have EIP-1559 parameters but no legacy gasPrice, then use maxFeePerGas as gasPrice
25 - finalGasPrice = maxFeePerGas;
26 - }
27 -
28 - return Transaction(
29 - from: from,
30 - to: to,
31 - value: amount,
32 - data: data,
33 - maxGas: maxGas,
34 - gasPrice: finalGasPrice,
35 - // maxFeePerGas: maxFeePerGas,
36 - // maxPriorityFeePerGas: maxPriorityFeePerGas,
37 - );
38 - }
39 -
40 - @override
41 - Uint8List prepareSignedTransactionForSending(Uint8List signedTransaction) => signedTransaction;
42 -
43 - @override
44 - int get chainId => 137;
45 -
46 - @override
47 - Future<List<EVMChainTransactionModel>> fetchTransactions(String address,
48 - {String? contractAddress}) async {
49 - try {
50 - final response = await client.get(Uri.https("api.etherscan.io", "/v2/api", {
51 - "chainid": "$chainId",
52 - "module": "account",
53 - "action": contractAddress != null ? "tokentx" : "txlist",
54 - if (contractAddress != null) "contractaddress": contractAddress,
55 - "address": address,
56 - "apikey": secrets.etherScanApiKey,
57 - }));
58 -
59 - final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
60 -
61 - if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) {
62 - final res = (jsonResponse['result'] as List);
63 -
64 - res.removeWhere((e) => e['value'] == '0');
65 -
66 - return res
67 - .map(
68 - (e) => EVMChainTransactionModel.fromJson(e as Map<String, dynamic>, 'MATIC'),
69 - )
70 - .toList();
71 - }
72 -
73 - return [];
74 - } catch (e) {
75 - return [];
76 - }
77 - }
78 -
79 - @override
80 - Future<List<EVMChainTransactionModel>> fetchInternalTransactions(String address) async {
81 - try {
82 - final response = await client.get(Uri.https("api.etherscan.io", "/v2/api", {
83 - "chainid": "$chainId",
84 - "module": "account",
85 - "action": "txlistinternal",
86 - "address": address,
87 - "apikey": secrets.etherScanApiKey,
88 - }));
89 -
90 - final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
91 -
92 - if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) {
93 - return (jsonResponse['result'] as List)
94 - .map((e) => EVMChainTransactionModel.fromJson(e as Map<String, dynamic>, 'MATIC'))
95 - .toList();
96 - }
97 -
98 - return [];
99 - } catch (_) {
100 - return [];
101 - }
102 - }
103 -}
cw_polygon/lib/polygon_mnemonics_exception.dart deleted
-5
@@ -1,5 +0,0 @@
1 -class PolygonMnemonicIsIncorrectException implements Exception {
2 - @override
3 - String toString() =>
4 - 'Polygon mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.';
5 -}
cw_polygon/lib/polygon_transaction_history.dart deleted
-20
@@ -1,20 +0,0 @@
1 -import 'dart:core';
2 -
3 -import 'package:cw_evm/evm_chain_transaction_history.dart';
4 -import 'package:cw_evm/evm_chain_transaction_info.dart';
5 -import 'package:cw_polygon/polygon_transaction_info.dart';
6 -
7 -class PolygonTransactionHistory extends EVMChainTransactionHistory {
8 - PolygonTransactionHistory({
9 - required super.walletInfo,
10 - required super.password,
11 - required super.encryptionFileUtils,
12 - });
13 -
14 - @override
15 - String getTransactionHistoryFileName() => 'polygon_transactions.json';
16 -
17 - @override
18 - EVMChainTransactionInfo getTransactionInfo(Map<String, dynamic> val) =>
19 - PolygonTransactionInfo.fromJson(val);
20 -}
cw_polygon/lib/polygon_transaction_info.dart deleted
-41
@@ -1,41 +0,0 @@
1 -import 'package:cw_core/transaction_direction.dart';
2 -import 'package:cw_evm/evm_chain_transaction_info.dart';
3 -
4 -class PolygonTransactionInfo extends EVMChainTransactionInfo {
5 - PolygonTransactionInfo({
6 - required super.id,
7 - required super.height,
8 - required super.ethAmount,
9 - required super.ethFee,
10 - required super.tokenSymbol,
11 - required super.direction,
12 - required super.isPending,
13 - required super.date,
14 - required super.confirmations,
15 - required super.to,
16 - required super.from,
17 - super.contractAddress,
18 - super.exponent,
19 - });
20 -
21 - factory PolygonTransactionInfo.fromJson(Map<String, dynamic> data) {
22 - return PolygonTransactionInfo(
23 - id: data['id'] as String,
24 - height: data['height'] as int,
25 - ethAmount: BigInt.parse(data['amount']),
26 - exponent: data['exponent'] as int,
27 - ethFee: BigInt.parse(data['fee']),
28 - direction: parseTransactionDirectionFromInt(data['direction'] as int),
29 - date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
30 - isPending: data['isPending'] as bool,
31 - confirmations: data['confirmations'] as int,
32 - tokenSymbol: data['tokenSymbol'] as String,
33 - to: data['to'],
34 - from: data['from'],
35 - contractAddress: data['contractAddress'],
36 - );
37 - }
38 -
39 - @override
40 - String get feeCurrency => 'MATIC';
41 -}
cw_polygon/lib/polygon_wallet.dart deleted
-198
@@ -1,198 +0,0 @@
1 -import 'dart:convert';
2 -
3 -import 'package:cw_core/cake_hive.dart';
4 -import 'package:cw_core/crypto_currency.dart';
5 -import 'package:cw_core/encryption_file_utils.dart';
6 -import 'package:cw_core/erc20_token.dart';
7 -import 'package:cw_core/pathForWallet.dart';
8 -import 'package:cw_core/transaction_direction.dart';
9 -import 'package:cw_core/wallet_info.dart';
10 -import 'package:cw_core/wallet_keys_file.dart';
11 -import 'package:cw_evm/evm_chain_transaction_history.dart';
12 -import 'package:cw_evm/evm_chain_transaction_info.dart';
13 -import 'package:cw_evm/evm_chain_transaction_model.dart';
14 -import 'package:cw_evm/evm_chain_transaction_priority.dart';
15 -import 'package:cw_evm/evm_chain_wallet.dart';
16 -import 'package:cw_evm/evm_erc20_balance.dart';
17 -import 'package:cw_polygon/default_polygon_erc20_tokens.dart';
18 -import 'package:cw_polygon/polygon_client.dart';
19 -import 'package:cw_polygon/polygon_transaction_history.dart';
20 -import 'package:cw_polygon/polygon_transaction_info.dart';
21 -import 'package:web3dart/web3dart.dart';
22 -
23 -class PolygonWallet extends EVMChainWallet {
24 - PolygonWallet({
25 - required super.walletInfo,
26 - required super.derivationInfo,
27 - required super.password,
28 - super.mnemonic,
29 - super.initialBalance,
30 - super.privateKey,
31 - required super.client,
32 - required super.encryptionFileUtils,
33 - super.passphrase,
34 - }) : super(nativeCurrency: CryptoCurrency.maticpoly);
35 -
36 - @override
37 - int getTotalPriorityFee(EVMChainTransactionPriority priority) {
38 - // Polygon has a minimum priority fee of 25 gwei
39 -
40 - int minPriorityFee = 25;
41 - int minPriorityFeeWei = EtherAmount.fromInt(EtherUnit.gwei, minPriorityFee).getInWei.toInt();
42 -
43 - // Calculate user selected priority-based additional fee on top of minimum
44 - int additionalPriorityFee = 0;
45 - switch (priority) {
46 - case EVMChainTransactionPriority.slow:
47 - additionalPriorityFee = 0;
48 - break;
49 - case EVMChainTransactionPriority.medium:
50 - additionalPriorityFee = EtherAmount.fromInt(EtherUnit.gwei, 15).getInWei.toInt();
51 - break;
52 - case EVMChainTransactionPriority.fast:
53 - additionalPriorityFee = EtherAmount.fromInt(EtherUnit.gwei, 35).getInWei.toInt();
54 - break;
55 - }
56 -
57 - return (minPriorityFeeWei + additionalPriorityFee);
58 - }
59 -
60 - @override
61 - Future<void> initErc20TokensBox() async {
62 - final boxName = "${walletInfo.name.replaceAll(" ", "_")}_ ${Erc20Token.polygonBoxName}";
63 - if (await CakeHive.boxExists(boxName)) {
64 - evmChainErc20TokensBox = await CakeHive.openBox<Erc20Token>(boxName);
65 - } else {
66 - evmChainErc20TokensBox = await CakeHive.openBox<Erc20Token>(boxName.replaceAll(" ", ""));
67 - }
68 - }
69 -
70 - @override
71 - void addInitialTokens() {
72 - final initialErc20Tokens = DefaultPolygonErc20Tokens().initialPolygonErc20Tokens;
73 -
74 - for (final token in initialErc20Tokens) {
75 - if (!evmChainErc20TokensBox.containsKey(token.contractAddress)) {
76 - evmChainErc20TokensBox.put(token.contractAddress, token);
77 - } else {
78 - // update existing token
79 - final existingToken = evmChainErc20TokensBox.get(token.contractAddress);
80 - evmChainErc20TokensBox.put(
81 - token.contractAddress, Erc20Token.copyWith(token, enabled: existingToken!.enabled));
82 - }
83 - }
84 - }
85 -
86 - @override
87 - List<String> get getDefaultTokenContractAddresses =>
88 - DefaultPolygonErc20Tokens().initialPolygonErc20Tokens.map((e) => e.contractAddress).toList();
89 -
90 - @override
91 - Future<bool> checkIfScanProviderIsEnabled() async {
92 - bool isPolygonScanEnabled = (await sharedPrefs.future).getBool("use_polygonscan") ?? true;
93 - return isPolygonScanEnabled;
94 - }
95 -
96 - @override
97 - String getTransactionHistoryFileName() => 'polygon_transactions.json';
98 -
99 - @override
100 - Erc20Token createNewErc20TokenObject(Erc20Token token, String? iconPath) {
101 - return Erc20Token(
102 - name: token.name,
103 - symbol: token.symbol,
104 - contractAddress: token.contractAddress,
105 - decimal: token.decimal,
106 - enabled: token.enabled,
107 - tag: token.tag ?? 'POL',
108 - iconPath: iconPath,
109 - isPotentialScam: token.isPotentialScam,
110 - );
111 - }
112 -
113 - @override
114 - EVMChainTransactionInfo getTransactionInfo(
115 - EVMChainTransactionModel transactionModel, String address) {
116 - final model = PolygonTransactionInfo(
117 - id: transactionModel.hash,
118 - height: transactionModel.blockNumber,
119 - ethAmount: transactionModel.amount,
120 - direction: transactionModel.from == address
121 - ? TransactionDirection.outgoing
122 - : TransactionDirection.incoming,
123 - isPending: false,
124 - date: transactionModel.date,
125 - confirmations: transactionModel.confirmations,
126 - ethFee: BigInt.from(transactionModel.gasUsed) * transactionModel.gasPrice,
127 - exponent: transactionModel.tokenDecimal ?? 18,
128 - tokenSymbol: transactionModel.tokenSymbol ?? "MATIC",
129 - to: transactionModel.to,
130 - from: transactionModel.from,
131 - contractAddress: transactionModel.contractAddress,
132 - );
133 - return model;
134 - }
135 -
136 - @override
137 - EVMChainTransactionHistory setUpTransactionHistory(
138 - WalletInfo walletInfo, String password, EncryptionFileUtils encryptionFileUtils) {
139 - return PolygonTransactionHistory(
140 - walletInfo: walletInfo,
141 - password: password,
142 - encryptionFileUtils: encryptionFileUtils,
143 - );
144 - }
145 -
146 - static Future<PolygonWallet> open({
147 - required String name,
148 - required String password,
149 - required WalletInfo walletInfo,
150 - required EncryptionFileUtils encryptionFileUtils,
151 - }) async {
152 - final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
153 - final path = await pathForWallet(name: name, type: walletInfo.type);
154 -
155 - Map<String, dynamic>? data;
156 - try {
157 - final jsonSource = await encryptionFileUtils.read(path: path, password: password);
158 -
159 - data = json.decode(jsonSource) as Map<String, dynamic>;
160 - } catch (e) {
161 - if (!hasKeysFile) rethrow;
162 - }
163 -
164 - final balance = EVMChainERC20Balance.fromJSON(data?['balance'] as String?) ??
165 - EVMChainERC20Balance(BigInt.zero);
166 -
167 - final WalletKeysData keysData;
168 - // Migrate wallet from the old scheme to then new .keys file scheme
169 - if (!hasKeysFile) {
170 - final mnemonic = data!['mnemonic'] as String?;
171 - final privateKey = data['private_key'] as String?;
172 - final passphrase = data['passphrase'] as String?;
173 -
174 - keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey, passphrase: passphrase);
175 - } else {
176 - keysData = await WalletKeysFile.readKeysFile(
177 - name,
178 - walletInfo.type,
179 - password,
180 - encryptionFileUtils,
181 - );
182 - }
183 -
184 - final derivationInfo = await walletInfo.getDerivationInfo();
185 -
186 - return PolygonWallet(
187 - walletInfo: walletInfo,
188 - derivationInfo: derivationInfo,
189 - password: password,
190 - mnemonic: keysData.mnemonic,
191 - privateKey: keysData.privateKey,
192 - passphrase: keysData.passphrase,
193 - initialBalance: balance,
194 - client: PolygonClient(),
195 - encryptionFileUtils: encryptionFileUtils,
196 - );
197 - }
198 -}
cw_polygon/lib/polygon_wallet_service.dart deleted
-169
@@ -1,169 +0,0 @@
1 -import 'package:bip39/bip39.dart' as bip39;
2 -import 'package:cw_core/encryption_file_utils.dart';
3 -import 'package:cw_core/wallet_base.dart';
4 -import 'package:cw_core/wallet_info.dart';
5 -import 'package:cw_core/wallet_type.dart';
6 -import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
7 -import 'package:cw_evm/evm_chain_wallet_service.dart';
8 -import 'package:cw_polygon/polygon_client.dart';
9 -import 'package:cw_polygon/polygon_mnemonics_exception.dart';
10 -import 'package:cw_polygon/polygon_wallet.dart';
11 -
12 -class PolygonWalletService extends EVMChainWalletService<PolygonWallet> {
13 - PolygonWalletService(super.isDirect, {
14 - required this.client,
15 - });
16 -
17 - late PolygonClient client;
18 -
19 - @override
20 - WalletType getType() => WalletType.polygon;
21 -
22 - @override
23 - Future<PolygonWallet> create(EVMChainNewWalletCredentials credentials, {bool? isTestnet}) async {
24 - final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
25 -
26 - final mnemonic = credentials.mnemonic ?? bip39.generateMnemonic(strength: strength);
27 -
28 - final wallet = PolygonWallet(
29 - walletInfo: credentials.walletInfo!,
30 - derivationInfo: await credentials.walletInfo!.getDerivationInfo(),
31 - mnemonic: mnemonic,
32 - password: credentials.password!,
33 - passphrase: credentials.passphrase,
34 - client: client,
35 - encryptionFileUtils: encryptionFileUtilsFor(isDirect),
36 - );
37 -
38 - await wallet.init();
39 - wallet.addInitialTokens();
40 - await wallet.save();
41 - return wallet;
42 - }
43 -
44 - @override
45 - Future<PolygonWallet> openWallet(String name, String password) async {
46 - final walletInfo = await WalletInfo.get(name, getType());
47 - if (walletInfo == null) {
48 - throw Exception('Wallet not found');
49 - }
50 -
51 - try {
52 - final wallet = await PolygonWallet.open(
53 - name: name,
54 - password: password,
55 - walletInfo: walletInfo,
56 - encryptionFileUtils: encryptionFileUtilsFor(isDirect),
57 - );
58 -
59 - await wallet.init();
60 - wallet.addInitialTokens();
61 - await wallet.save();
62 - saveBackup(name);
63 - return wallet;
64 - } catch (_) {
65 - await restoreWalletFilesFromBackup(name);
66 -
67 - final wallet = await PolygonWallet.open(
68 - name: name,
69 - password: password,
70 - walletInfo: walletInfo,
71 - encryptionFileUtils: encryptionFileUtilsFor(isDirect),
72 - );
73 -
74 - await wallet.init();
75 - await wallet.save();
76 - return wallet;
77 - }
78 - }
79 -
80 - @override
81 - Future<PolygonWallet> restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials,
82 - {bool? isTestnet}) async {
83 - final wallet = PolygonWallet(
84 - password: credentials.password!,
85 - privateKey: credentials.privateKey,
86 - walletInfo: credentials.walletInfo!,
87 - derivationInfo: await credentials.walletInfo!.getDerivationInfo(),
88 - client: client,
89 - encryptionFileUtils: encryptionFileUtilsFor(isDirect),
90 - );
91 -
92 - await wallet.init();
93 - wallet.addInitialTokens();
94 - await wallet.save();
95 - return wallet;
96 - }
97 -
98 - @override
99 - Future<PolygonWallet> restoreFromHardwareWallet(
100 - EVMChainRestoreWalletFromHardware credentials) async {
101 - final derivationInfo = await credentials.walletInfo!.getDerivationInfo();
102 - derivationInfo.derivationType = DerivationType.bip39;
103 - derivationInfo.derivationPath = "m/44'/60'/${credentials.hwAccountData.accountIndex}'/0/0";
104 - derivationInfo.save();
105 - credentials.walletInfo!.hardwareWalletType = credentials.hardwareWalletType;
106 - credentials.walletInfo!.address = credentials.hwAccountData.address;
107 -
108 - final wallet = PolygonWallet(
109 - walletInfo: credentials.walletInfo!,
110 - derivationInfo: derivationInfo,
111 - password: credentials.password!,
112 - client: client,
113 - encryptionFileUtils: encryptionFileUtilsFor(isDirect),
114 - );
115 -
116 - await wallet.init();
117 - wallet.addInitialTokens();
118 - await wallet.save();
119 -
120 - return wallet;
121 - }
122 -
123 - @override
124 - Future<PolygonWallet> restoreFromSeed(EVMChainRestoreWalletFromSeedCredentials credentials,
125 - {bool? isTestnet}) async {
126 - if (!bip39.validateMnemonic(credentials.mnemonic)) {
127 - throw PolygonMnemonicIsIncorrectException();
128 - }
129 -
130 - final wallet = PolygonWallet(
131 - password: credentials.password!,
132 - mnemonic: credentials.mnemonic,
133 - walletInfo: credentials.walletInfo!,
134 - derivationInfo: await credentials.walletInfo!.getDerivationInfo(),
135 - passphrase: credentials.passphrase,
136 - client: client,
137 - encryptionFileUtils: encryptionFileUtilsFor(isDirect),
138 - );
139 -
140 - await wallet.init();
141 - wallet.addInitialTokens();
142 - await wallet.save();
143 -
144 - return wallet;
145 - }
146 -
147 - @override
148 - Future<void> rename(String currentName, String password, String newName) async {
149 - final currentWalletInfo = await WalletInfo.get(currentName, getType());
150 - if (currentWalletInfo == null) {
151 - throw Exception('Wallet not found');
152 - }
153 - final currentWallet = await PolygonWallet.open(
154 - password: password,
155 - name: currentName,
156 - walletInfo: currentWalletInfo,
157 - encryptionFileUtils: encryptionFileUtilsFor(isDirect),
158 - );
159 -
160 - await currentWallet.renameWalletFiles(newName);
161 - await saveBackup(newName);
162 -
163 - final newWalletInfo = currentWalletInfo;
164 - newWalletInfo.id = WalletBase.idFor(newName, getType());
165 - newWalletInfo.name = newName;
166 -
167 - await newWalletInfo.save();
168 - }
169 -}
cw_polygon/pubspec.yaml deleted
-75
@@ -1,75 +0,0 @@
1 -name: cw_polygon
2 -description: A new Flutter package project.
3 -version: 0.0.1
4 -publish_to: none
5 -author: Cake Wallet
6 -homepage: https://cakewallet.com
7 -
8 -environment:
9 - sdk: '>=3.0.6 <4.0.0'
10 - flutter: ">=1.17.0"
11 -
12 -dependencies:
13 - flutter:
14 - sdk: flutter
15 - cw_core:
16 - path: ../cw_core
17 - cw_ethereum:
18 - path: ../cw_ethereum
19 - cw_evm:
20 - path: ../cw_evm
21 - web3dart: ^2.7.1
22 - hive: ^2.2.3
23 - bip39: ^1.0.6
24 - collection: ^1.17.1
25 -
26 -dependency_overrides:
27 - web3dart:
28 - git:
29 - url: https://github.com/cake-tech/web3dart.git
30 - ref: cake
31 - watcher: ^1.1.0
32 -
33 -dev_dependencies:
34 - flutter_test:
35 - sdk: flutter
36 - flutter_lints: ^2.0.0
37 - build_runner: ^2.4.15
38 -
39 -
40 -# For information on the generic Dart part of this file, see the
41 -# following page: https://dart.dev/tools/pub/pubspec
42 -
43 -# The following section is specific to Flutter packages.
44 -flutter:
45 -
46 - # To add assets to your package, add an assets section, like this:
47 - # assets:
48 - # - images/a_dot_burr.jpeg
49 - # - images/a_dot_ham.jpeg
50 - #
51 - # For details regarding assets in packages, see
52 - # https://flutter.dev/assets-and-images/#from-packages
53 - #
54 - # An image asset can refer to one or more resolution-specific "variants", see
55 - # https://flutter.dev/assets-and-images/#resolution-aware
56 -
57 - # To add custom fonts to your package, add a fonts section here,
58 - # in this "flutter" section. Each entry in this list should have a
59 - # "family" key with the font family name, and a "fonts" key with a
60 - # list giving the asset and other descriptors for the font. For
61 - # example:
62 - # fonts:
63 - # - family: Schyler
64 - # fonts:
65 - # - asset: fonts/Schyler-Regular.ttf
66 - # - asset: fonts/Schyler-Italic.ttf
67 - # style: italic
68 - # - family: Trajan Pro
69 - # fonts:
70 - # - asset: fonts/TrajanPro.ttf
71 - # - asset: fonts/TrajanPro_Bold.ttf
72 - # weight: 700
73 - #
74 - # For details regarding fonts in packages, see
75 - # https://flutter.dev/custom-fonts/#from-packages
cw_polygon/test/cw_polygon_test.dart deleted
-12
@@ -1,12 +0,0 @@
1 -import 'package:flutter_test/flutter_test.dart';
2 -
3 -import 'package:cw_polygon/cw_polygon.dart';
4 -
5 -void main() {
6 - test('adds one to input values', () {
7 - final calculator = Calculator();
8 - expect(calculator.addOne(2), 3);
9 - expect(calculator.addOne(-7), -6);
10 - expect(calculator.addOne(0), 1);
11 - });
12 -}
docs/ADDING_EVM_L2_WALLET_NETWORK_TYPES.md new
+589
@@ -0,0 +1,589 @@
1 +# Guide: Adding a New L2 Network
2 +
3 +This guide provides step-by-step instructions for adding a new EVM-compatible L2 network to Cake Wallet.
4 +
5 +## Prerequisites
6 +
7 +- The network must be EVM-compatible
8 +- You need the following information:
9 + - Chain ID
10 + - Network name
11 + - Native currency (usually a `CryptoCurrency` instance)
12 + - RPC endpoints
13 + - Block explorer URLs
14 + - Supported features (ERC20, EIP-1559, internal transactions, etc.)
15 + - Default ERC20 tokens (optional but recommended)
16 +
17 +## Architecture Overview
18 +
19 +With the unified EVM architecture, all chains are managed through:
20 +- **EvmChainRegistry**: Centralized registry for chain configurations
21 +- **EVMChainWallet**: Single wallet class that handles all EVM chains via `selectedChainId`
22 +- **ChainId-based operations**: All operations use `chainId` instead of `WalletType`
23 +- **Backward compatibility**: Old wallet types (ethereum, polygon, base, arbitrum) still work
24 +
25 +**Key Principle**: New chains should use `WalletType.evm` and be identified by their `chainId`. Old chains maintain backward compatibility.
26 +
27 +## Step-by-Step Guide
28 +
29 +### Step 1: Add Chain Configuration to Registry
30 +
31 +**File**: `cw_evm/lib/evm_chain_registry.dart`
32 +
33 +Add your chain configuration in the `initialize()` method:
34 +
35 +```dart
36 +// Example: Adding Optimism
37 +_registerChain(
38 + const ChainConfig(
39 + chainId: 10, // Optimism mainnet
40 + name: 'Optimism',
41 + shortCode: 'op',
42 + caip2: 'eip155:10',
43 + nativeCurrency: CryptoCurrency.op, // Must exist in cw_core/lib/crypto_currency.dart
44 + capabilities: ChainCapabilities(
45 + supportsERC20: true,
46 + supportsEIP1559: true,
47 + supportsInternalTx: true,
48 + supportsSubscriptions: false,
49 + supportsENS: false,
50 + ),
51 + defaultRpcEndpoints: [
52 + 'mainnet.optimism.io',
53 + 'optimism.publicnode.com',
54 + // Add more RPC endpoints
55 + ],
56 + explorerUrls: [
57 + 'https://optimistic.etherscan.io',
58 + ],
59 + feeModel: FeeModel(
60 + type: FeeType.eip1559,
61 + defaultGasLimit: 21000,
62 + ),
63 + ),
64 + WalletType.evm, // Use WalletType.evm for new chains (or old type if backward compatibility needed)
65 + 'OP', // Native currency symbol
66 +);
67 +```
68 +
69 +**Notes**:
70 +- For **new chains**, use `WalletType.evm` (unified type)
71 +- For **backward compatibility** with existing wallets, you can map to an old `WalletType` (e.g., `WalletType.optimism` if it exists)
72 +- The registry automatically creates mappings: `chainId` → `WalletType`, `tag` → `chainId`, `caip2` → `chainId`
73 +- If the chain uses a standard EVM client, you can use the default `EVMChainClient` (no custom client needed)
74 +
75 +### Step 2: Add Native Currency (If New)
76 +
77 +**File**: `cw_core/lib/crypto_currency.dart`
78 +
79 +If your chain's native currency doesn't exist, add it:
80 +
81 +```dart
82 +// Example: Adding Optimism native currency
83 +static const CryptoCurrency op = CryptoCurrency(
84 + name: 'Optimism',
85 + title: 'OP',
86 + raw: 126,
87 + iconPath: 'assets/images/op.png', // Add icon asset
88 + tag: 'OP',
89 + decimals: 18,
90 +);
91 +```
92 +
93 +**Notes**:
94 +- The `tag` should match the symbol used in the registry
95 +- Add the currency icon to `assets/images/`
96 +- Update currency lists if needed (e.g., `all`, `fiat`, etc.)
97 +
98 +### Step 2b: Wire Currency ↔ chainId Mappings
99 +
100 +The unified EVM and PayAnything flows rely on a **two-way mapping** between
101 +`CryptoCurrency` and `chainId`.
102 +
103 +**File**: `cw_core/lib/currency_for_wallet_type.dart`
104 +
105 +1. **Map `chainId` → `CryptoCurrency`** in `getCryptoCurrencyByChainId`:
106 +
107 +```dart
108 +CryptoCurrency getCryptoCurrencyByChainId(int chainId) {
109 + switch (chainId) {
110 + case 1:
111 + return CryptoCurrency.eth;
112 + case 137:
113 + return CryptoCurrency.maticpoly;
114 + case 8453:
115 + return CryptoCurrency.baseEth;
116 + case 42161:
117 + return CryptoCurrency.arbEth;
118 + case 10:
119 + return CryptoCurrency.op; // NEW: Optimism
120 + default:
121 + return CryptoCurrency.eth;
122 + }
123 +}
124 +```
125 +
126 +2. **Map `CryptoCurrency` → `chainId`** in `getChainIdByCryptoCurrency`:
127 +
128 +```dart
129 +int? getChainIdByCryptoCurrency(CryptoCurrency currency) {
130 + switch (currency) {
131 + case CryptoCurrency.eth:
132 + return 1;
133 + case CryptoCurrency.maticpoly:
134 + return 137;
135 + case CryptoCurrency.baseEth:
136 + return 8453;
137 + case CryptoCurrency.arbEth:
138 + return 42161;
139 + case CryptoCurrency.op: // NEW: Optimism
140 + return 10;
141 + default:
142 + return null;
143 + }
144 +}
145 +```
146 +
147 +**Why this matters**:
148 +- `UniversalAddressDetector` and `PaymentViewModel` use these helpers to
149 + derive `chainId` from detected currencies (QR codes, URIs, raw EVM
150 + addresses).
151 +- The EVM PayAnything flow and `EVMPaymentFlowBottomSheet` depend on having
152 + the correct `chainId` for network and token selection.
153 +
154 +### Step 3: Create Default Tokens File (Optional but Recommended)
155 +
156 +**File**: `cw_evm/lib/tokens/optimism_tokens.dart` (example)
157 +
158 +Create a new file following the pattern of existing token files:
159 +
160 +```dart
161 +import 'package:cw_core/erc20_token.dart';
162 +
163 +/// Default ERC20 tokens for Optimism Mainnet
164 +class OptimismTokens {
165 + static List<Erc20Token> get tokens {
166 + return [
167 + Erc20Token(
168 + name: 'USD Coin',
169 + symbol: 'USDC',
170 + contractAddress: '0x7f5c764cbc14f9669b88837ca1490cca17c31607',
171 + decimal: 6,
172 + enabled: true,
173 + ),
174 + Erc20Token(
175 + name: 'Tether USD',
176 + symbol: 'USDT',
177 + contractAddress: '0x94b008aa00579c1307b0ef2c499ad98a8ce58e58',
178 + decimal: 6,
179 + enabled: true,
180 + ),
181 + // Add more default tokens
182 + ];
183 + }
184 +}
185 +```
186 +
187 +**File**: `cw_evm/lib/evm_chain_default_tokens.dart`
188 +
189 +Add your chain's tokens to the switch statement:
190 +
191 +```dart
192 +static List<Erc20Token> getDefaultTokensByChainId(int chainId) {
193 + return switch (chainId) {
194 + 1 => EthereumTokens.tokens,
195 + 137 => PolygonTokens.tokens,
196 + 8453 => BaseTokens.tokens,
197 + 42161 => ArbitrumTokens.tokens,
198 + 10 => OptimismTokens.tokens, // NEW
199 + _ => [],
200 + };
201 +}
202 +```
203 +
204 +**Notes**:
205 +- Default tokens are automatically loaded when a wallet is created or when switching to that chain
206 +- Users can add/remove tokens later via the UI
207 +- Only include well-known, verified tokens
208 +
209 +### Step 4: Update Chain Utilities (If Needed)
210 +
211 +**File**: `cw_evm/lib/utils/evm_chain_utils.dart`
212 +
213 +Add chain-specific logic if your chain has special requirements:
214 +
215 +#### 4.1 Priority Fees
216 +
217 +```dart
218 +static int getTotalPriorityFee(EVMChainTransactionPriority priority, int chainId) {
219 + return switch (chainId) {
220 + 1 => _ethereumPriorityFee(priority),
221 + 137 => _polygonPriorityFee(priority),
222 + 8453 => _basePriorityFee(priority),
223 + 42161 => 0, // Arbitrum doesn't use priority fees
224 + 10 => _optimismPriorityFee(priority), // NEW - if custom logic needed
225 + _ => _ethereumPriorityFee(priority), // Default to Ethereum logic
226 + };
227 +}
228 +
229 +static bool hasPriorityFee(int chainId) {
230 + return switch (chainId) {
231 + 42161 => false, // Arbitrum doesn't use priority fees
232 + 10 => true, // Optimism uses priority fees
233 + _ => true,
234 + };
235 +}
236 +```
237 +
238 +#### 4.2 ERC20 Tokens Box Name
239 +
240 +```dart
241 +static String getErc20TokensBoxName(String walletName, int chainId) {
242 + final sanitizedName = walletName.replaceAll(" ", "_");
243 + return switch (chainId) {
244 + 1 => "${sanitizedName}_${Erc20Token.ethereumBoxName}",
245 + 137 => "${sanitizedName}_${Erc20Token.polygonBoxName}",
246 + 8453 => "${sanitizedName}_${Erc20Token.baseBoxName}",
247 + 42161 => "${sanitizedName}_${Erc20Token.arbitrumBoxName}",
248 + 10 => "${sanitizedName}_${Erc20Token.optimismBoxName}", // NEW - if custom box name needed
249 + _ => "${sanitizedName}_${Erc20Token.ethereumBoxName}", // Default
250 + };
251 +}
252 +```
253 +
254 +**Note**: If you don't add a case, it will use the default (Ethereum box name pattern). Only add if you need a specific box name.
255 +
256 +#### 4.3 Transaction History File Name
257 +
258 +```dart
259 +static String getTransactionHistoryFileName(int chainId) {
260 + return switch (chainId) {
261 + 1 => 'transactions.json',
262 + 137 => 'polygon_transactions.json',
263 + 8453 => 'base_transactions.json',
264 + 42161 => 'arbitrum_transactions.json',
265 + 10 => 'optimism_transactions.json', // NEW
266 + _ => 'transactions_$chainId.json', // Generic format for other chains
267 + };
268 +}
269 +```
270 +
271 +#### 4.4 Scan Provider Preference Key
272 +
273 +```dart
274 +static String getScanProviderPreferenceKey(int chainId) {
275 + return switch (chainId) {
276 + 1 => 'use_etherscan',
277 + 137 => 'use_polygonscan',
278 + 8453 => 'use_basescan',
279 + 42161 => 'use_arbiscan',
280 + 10 => 'use_optimismscan', // NEW
281 + _ => 'use_etherscan', // Default
282 + };
283 +}
284 +```
285 +
286 +#### 4.5 Default Token Tag
287 +
288 +```dart
289 +static String getDefaultTokenTag(int chainId) {
290 + return switch (chainId) {
291 + 1 => 'ETH',
292 + 137 => 'POL',
293 + 8453 => 'ETH',
294 + 42161 => 'ETH',
295 + 10 => 'OP', // NEW
296 + _ => 'ETH', // Default
297 + };
298 +}
299 +```
300 +
301 +#### 4.6 Fee Currency Symbol
302 +
303 +```dart
304 +static String getFeeCurrency(int chainId) {
305 + return switch (chainId) {
306 + 1 => 'ETH',
307 + 137 => 'MATIC', // Polygon uses MATIC, not POL
308 + 8453 => 'ETH',
309 + 42161 => 'ETH',
310 + 10 => 'ETH', // Optimism uses ETH
311 + _ => 'ETH', // Default
312 + };
313 +}
314 +```
315 +
316 +**Note**: This is used in transaction fetching APIs. Polygon uses 'MATIC' even though the currency tag is 'POL'.
317 +
318 +#### 4.7 Default Token Symbol
319 +
320 +```dart
321 +static String getDefaultTokenSymbol(int chainId) {
322 + return switch (chainId) {
323 + 1 => 'ETH',
324 + 137 => 'MATIC',
325 + 8453 => 'ETH',
326 + 42161 => 'ETH',
327 + 10 => 'ETH', // Optimism uses ETH
328 + _ => 'ETH', // Default
329 + };
330 +}
331 +```
332 +
333 +### Step 5: Create Custom Client (Only If Needed)
334 +
335 +**Only needed if the chain requires custom transaction/balance fetching behavior**
336 +
337 +Most chains can use the default `EVMChainClient` which handles:
338 +- Standard ERC20 token operations
339 +- EIP-1559 transactions
340 +- Internal transactions
341 +- Balance fetching
342 +
343 +**File**: `cw_evm/lib/clients/optimism_client.dart` (example)
344 +
345 +```dart
346 +import 'package:cw_evm/clients/evm_chain_client.dart';
347 +
348 +class OptimismClient extends EVMChainClient {
349 + OptimismClient() : super(chainId: 10);
350 +
351 + // Only override methods if custom behavior is needed
352 + // For example, if Optimism has special transaction formatting:
353 +
354 + // @override
355 + // Future<List<EVMChainTransactionModel>> fetchTransactions(...) async {
356 + // // Custom implementation
357 + // }
358 +}
359 +```
360 +
361 +**File**: `cw_evm/lib/clients/evm_chain_client_factory.dart`
362 +
363 +Add your custom client to the factory:
364 +
365 +```dart
366 +static EVMChainClient createClient(int chainId) {
367 + switch (chainId) {
368 + case 1: // Ethereum
369 + return EthereumClient();
370 + case 137: // Polygon
371 + return PolygonClient();
372 + case 8453: // Base
373 + return BaseClient();
374 + case 42161: // Arbitrum
375 + return ArbitrumClient();
376 + case 10: // Optimism - NEW
377 + return OptimismClient();
378 + default:
379 + // Default client works for most chains
380 + return EVMChainClient(chainId: chainId);
381 + }
382 +}
383 +```
384 +
385 +**Note**: If you don't create a custom client, the default `EVMChainClient(chainId: chainId)` will be used automatically.
386 +
387 +### Step 6: Add Node List YAML
388 +
389 +**File**: `assets/optimism_node_list.yml`
390 +
391 +Create a YAML file with default RPC endpoints:
392 +
393 +```yaml
394 +- uri: mainnet.optimism.io
395 + useSSL: true
396 + isEnabledForAutoSwitching: true
397 +- uri: optimism.publicnode.com
398 + useSSL: true
399 + isEnabledForAutoSwitching: true
400 +- uri: 1rpc.io/op
401 + useSSL: true
402 + isEnabledForAutoSwitching: true
403 +```
404 +
405 +**File**: `lib/entities/node_list.dart`
406 +
407 +Add node loading for your chain:
408 +
409 +```dart
410 +Future<List<Node>> loadDefaultNodes(WalletType type) async {
411 + String path;
412 + switch (type) {
413 + // ... existing cases ...
414 + case WalletType.evm: // For new chains using WalletType.evm
415 + // Nodes are loaded based on chainId, not WalletType
416 + // This is handled automatically by the node switching service
417 + return [];
418 + case WalletType.optimism: // Only if using old WalletType for backward compatibility
419 + path = 'assets/optimism_node_list.yml';
420 + break;
421 + }
422 + // ... rest of the function ...
423 +}
424 +```
425 +
426 +**Note**: For `WalletType.evm` wallets, nodes are managed dynamically based on `chainId`. The node switching service automatically loads the correct nodes.
427 +
428 +### Step 7: Update DI Registration (If Using Old WalletType)
429 +
430 +**Only needed if you're adding a new `WalletType` enum value for backward compatibility**
431 +
432 +**File**: `cw_core/lib/wallet_type.dart`
433 +
434 +If you need a new `WalletType` (not recommended for new chains):
435 +
436 +```dart
437 +enum WalletType {
438 + // ... existing types ...
439 + @HiveField(19) // Next available field ID
440 + optimism, // Only if needed for backward compatibility
441 +}
442 +```
443 +
444 +**File**: `lib/di.dart`
445 +
446 +Add your wallet type to the `WalletService` factory:
447 +
448 +```dart
449 +factory WalletService(WalletType type, bool isDirect) {
450 + switch (type) {
451 + // ... existing cases ...
452 + case WalletType.optimism: // Only if using old WalletType
453 + return evm!.createEVMWalletService(type, isDirect);
454 + case WalletType.evm: // For new unified wallets
455 + return evm!.createEVMWalletService(type, isDirect);
456 + }
457 +}
458 +```
459 +
460 +**Note**: **For new chains, use `WalletType.evm`** - no DI changes needed! The unified proxy already handles all EVM chains.
461 +
462 +### Step 8: Add Erc20Token Box Name Constant (If Needed)
463 +
464 +**File**: `cw_core/lib/erc20_token.dart`
465 +
466 +If you need a specific box name pattern:
467 +
468 +```dart
469 +class Erc20Token extends CryptoCurrency {
470 + // ... existing code ...
471 +
472 + static const String optimismBoxName = 'optimism_erc20_tokens';
473 +}
474 +```
475 +
476 +**Note**: Only needed if you want a custom box name. Otherwise, the default pattern will be used.
477 +
478 +## What Happens Automatically
479 +
480 +Once you've completed the steps above, the following will work automatically:
481 +
482 +✅ **Chain appears in dropdown** - The chain selection UI (`EvmSwitcher`) automatically shows your new chain from the registry
483 +✅ **Wallet creation** - Users can create `WalletType.evm` wallets and switch to your chain
484 +✅ **Chain switching** - Users can switch between chains seamlessly
485 +✅ **All operations** - Balance fetching, transaction sending, etc. all work
486 +✅ **Transaction filtering** - Transactions are automatically filtered by `chainId`
487 +✅ **Node connection** - Automatic node connection when switching chains (uses `chainId` to find correct nodes)
488 +✅ **Balance updates** - Automatic balance refresh when switching chains
489 +✅ **ERC20 tokens** - Default tokens are automatically loaded
490 +✅ **Transaction history** - Separate history files per chain
491 +✅ **Backward compatibility** - Old wallet types continue to work
492 +
493 +## Testing Checklist
494 +
495 +- [ ] Create a new `WalletType.evm` wallet
496 +- [ ] Switch to your new chain and verify it appears in the chain switcher
497 +- [ ] Verify balances update correctly
498 +- [ ] Switch between chains and verify balances update
499 +- [ ] Send a transaction on the new chain
500 +- [ ] Verify transactions are filtered correctly (only show transactions for current chain)
501 +- [ ] Test node connection and switching
502 +- [ ] Verify default tokens are loaded
503 +- [ ] Test wallet backup/restore
504 +- [ ] Verify transaction history is separate per chain
505 +- [ ] Test on old wallet types (if applicable) to ensure backward compatibility
506 +
507 +## Common Issues
508 +
509 +### Issue: Chain doesn't appear in dropdown
510 +
511 +**Solution**:
512 +- Verify the chain is registered in `EvmChainRegistry.initialize()`
513 +- Check that `EvmChainRegistry().initialize()` is called during app startup
514 +- Verify the registry is initialized before the UI tries to load chains
515 +
516 +### Issue: Node connection fails
517 +
518 +**Solution**: Check that:
519 +- Node list YAML file exists and is properly formatted
520 +- RPC endpoints are correct and accessible
521 +- For `WalletType.evm` wallets, nodes are retrieved using `chainId` via `settingsStore.getCurrentNode(WalletType.evm, chainId: chainId)`
522 +- Node switching service uses `isEVMCompatibleChain()` to handle all EVM wallets
523 +
524 +### Issue: Transactions not showing
525 +
526 +**Solution**: Verify:
527 +- `chainId` is correctly set in transaction info
528 +- Transaction filtering logic uses `chainId` (not `walletType`)
529 +- Transactions are being saved with the correct `chainId` in `EVMChainTransactionHistory`
530 +- `EVMChainTransactionInfo.fromJson()` correctly infers `chainId` for old transactions
531 +
532 +### Issue: Default tokens not loading
533 +
534 +**Solution**:
535 +- Verify tokens are added to `EVMChainDefaultTokens.getDefaultTokensByChainId()`
536 +- Check that `addInitialTokens()` is called during wallet initialization
537 +- Ensure token file follows the pattern: `class OptimismTokens { static List<Erc20Token> get tokens { ... } }`
538 +
539 +### Issue: Balance not updating after chain switch
540 +
541 +**Solution**:
542 +- Verify `selectChain()` is called with the correct `chainId`
543 +- Check that `initErc20TokensBox()` switches to the new chain's box
544 +- Ensure `_fetchErc20Balances()` is called after chain switch
545 +- Verify `erc20Currencies` getter handles closed boxes gracefully
546 +
547 +### Issue: "Box has already been closed" error
548 +
549 +**Solution**:
550 +- This can happen during chain switching if code accesses `evmChainErc20TokensBox` while it's being closed
551 +- Ensure all access to `evmChainErc20TokensBox` checks `isOpen` first
552 +- The `erc20Currencies` getter should return empty list if box is closed
553 +- Use try-catch blocks when accessing the box during async operations
554 +
555 +## Summary
556 +
557 +### Minimum Steps for Standard EVM Chain
558 +
559 +1. **Add chain config to Registry** (Step 1) - Required
560 +2. **Add native currency** (Step 2) - Required if currency doesn't exist
561 +3. **Add default tokens** (Step 3) - Recommended
562 +4. **Add node list YAML** (Step 6) - Required
563 +5. **Update chain utilities** (Step 4) - Only if chain has special requirements
564 +
565 +### For Chains with Custom Behavior
566 +
567 +- Add **Step 5** (Custom Client) only if needed
568 +- Add **Step 7** (DI Registration) only if using old `WalletType` (not recommended)
569 +- Add **Step 8** (Box Name Constant) only if custom box name needed
570 +
571 +### Key Points
572 +
573 +✅ **Use `WalletType.evm` for new chains** - No need to create new `WalletType` enum values
574 +✅ **Everything is `chainId`-based** - All operations use `chainId`, not `walletType`
575 +✅ **Registry-driven** - Chain configuration is centralized in `EvmChainRegistry`
576 +✅ **Backward compatible** - Old wallet types (ethereum, polygon, base, arbitrum) still work
577 +✅ **No proxy files needed** - The unified `evm` proxy handles all chains
578 +✅ **Automatic chain switching** - Users can switch chains without creating new wallets
579 +
580 +### What You DON'T Need to Do
581 +
582 +❌ Create a new `WalletType` enum value (use `WalletType.evm`)
583 +❌ Create a new proxy file (unified proxy handles all chains)
584 +❌ Create a new wallet service (unified service handles all chains)
585 +❌ Create a new wallet class (unified `EVMChainWallet` handles all chains)
586 +❌ Update view models (they work with any EVM chain via proxy)
587 +❌ Update UI components (chain switcher auto-populates from registry)
588 +
589 +**Key Point**: With the unified EVM architecture, adding new L2 chains is now much simpler - most chains only require Registry configuration and default tokens!
docs/ADDING_NEW_WALLET_TYPES.md new
+1033
@@ -0,0 +1,1033 @@
1 +# Guide to Adding a New Wallet Type in Cake Wallet
2 +
3 +## Important: EVM-Compatible Wallets
4 +
5 +**If you're adding an EVM-compatible wallet (Ethereum, Polygon, Base, Arbitrum, Optimism, etc.)**, please refer to the **[Adding New L2 Network Guide](./adding_new_l2_network_guide.md)** instead. EVM wallets use a unified architecture and don't require the full setup described in this guide.
6 +
7 +**For EVM wallets:**
8 +- Use `WalletType.evm` (unified type for all EVM chains)
9 +- Add chain configuration to `EvmChainRegistry`
10 +- No proxy files needed (unified `evm` proxy handles all chains)
11 +- See the L2 guide for detailed steps
12 +
13 +---
14 +
15 +## Wallet Integration Overview
16 +
17 +**Note**: Throughout this guide, `walletx` refers to the specific wallet type you want to add. If you're adding `BNB` to CakeWallet, then `walletx` for you here is `bnb`.
18 +
19 +This guide covers adding **non-EVM** wallet types. The process involves:
20 +1. Core package setup (`cw_walletx`)
21 +2. Proxy setup (communication layer)
22 +3. Configuration files
23 +4. Dependency injection
24 +5. Node setup
25 +6. UI integration
26 +7. Feature integration (send, receive, exchange, etc.)
27 +
28 +---
29 +
30 +## Step 1: Core Package Setup
31 +
32 +### 1.1 Add WalletType Enum
33 +
34 +**File**: `cw_core/lib/wallet_type.dart`
35 +
36 +Add your new wallet type to the enum:
37 +
38 +```dart
39 +enum WalletType {
40 + // ... existing types ...
41 + @HiveField(19) // Use next available field ID
42 + walletx,
43 +}
44 +```
45 +
46 +**Update Required Functions**:
47 +- `serializeToInt()`: Add `case WalletType.walletx: return 19;`
48 +- `deserializeFromInt()`: Add `case 19: return WalletType.walletx;`
49 +- `walletTypeToString()`: Add `case WalletType.walletx: return 'WalletX';`
50 +- `walletTypeToDisplayName()`: Add `case WalletType.walletx: return 'WalletX';`
51 +
52 +### 1.2 Add Currency Mapping
53 +
54 +**File**: `cw_core/lib/currency_for_wallet_type.dart`
55 +
56 +Add a case in the `currencyForWalletType` function:
57 +
58 +```dart
59 +CryptoCurrency currencyForWalletType(WalletType type) {
60 + switch (type) {
61 + // ... existing cases ...
62 + case WalletType.walletx:
63 + return CryptoCurrency.walletx; // Must exist in crypto_currency.dart
64 + }
65 +}
66 +```
67 +
68 +### 1.3 Add Native Currency (If New)
69 +
70 +**File**: `cw_core/lib/crypto_currency.dart`
71 +
72 +If the cryptocurrency doesn't exist, add it:
73 +
74 +```dart
75 +static const CryptoCurrency walletx = CryptoCurrency(
76 + name: 'WalletX',
77 + title: 'WLTX',
78 + raw: 'walletx',
79 + iconPath: 'assets/images/walletx.png', // Add icon asset
80 + tag: 'WLTX', // Optional tag for identification
81 +);
82 +```
83 +
84 +**Important**: Add the currency to the `all` list in the same file:
85 +
86 +```dart
87 +static List<CryptoCurrency> get all => [
88 + // ... existing currencies ...
89 + walletx,
90 +];
91 +```
92 +
93 +### 1.4 Create Core Package
94 +
95 +Create a new package for wallet-specific integration: `cw_walletx/`
96 +
97 +**Required Files** (create as needed):
98 +- `walletx_transaction_history.dart` - Transaction history management
99 +- `walletx_transaction_info.dart` - Transaction information model
100 +- `walletx_mnemonics_exception.dart` - Mnemonic-related exceptions
101 +- `walletx_tokens.dart` - Token management (if applicable)
102 +- `walletx_wallet_service.dart` - Wallet service implementation
103 +- `walletx_wallet.dart` - Main wallet class
104 +- `walletx_client.dart` - Network client (if applicable)
105 +- `walletx_new_wallet_credentials.dart` - New wallet credentials
106 +- `walletx_restore_wallet_credentials.dart` - Restore wallet credentials
107 +- etc.
108 +
109 +### 1.5 Add Code Generation
110 +
111 +**File**: `model_generator.sh`
112 +
113 +Add code generation for your package:
114 +
115 +```bash
116 +cd cw_walletx && flutter pub get && dart run build_runner build --delete-conflicting-outputs && cd ..
117 +```
118 +
119 +**Add dev_dependencies** to `cw_walletx/pubspec.yaml`:
120 +```yaml
121 +dev_dependencies:
122 + build_runner: ^2.0.0
123 + mobx_codegen: ^2.0.0
124 + hive_generator: ^2.0.0
125 +```
126 +
127 +---
128 +
129 +## Step 2: Proxy Setup
130 +
131 +A **Proxy** class is used to communicate with the wallet package. Instead of directly importing from `cw_walletx` within the `lib` directory, we use a proxy to access these functionalities. This maintains separation of concerns and allows conditional compilation.
132 +
133 +### 2.1 Create Proxy Directory
134 +
135 +Create a proxy folder: `lib/walletx/`
136 +
137 +It should contain 2 files:
138 +- `cw_walletx.dart` - Implementation class (links `cw_walletx` package to `lib`)
139 +- `walletx.dart` - Abstract class (generated, defines the interface)
140 +
141 +### 2.2 Add to .gitignore
142 +
143 +**File**: `.gitignore`
144 +
145 +Add the generated abstract class file:
146 +
147 +```
148 +lib/walletx/walletx.dart
149 +```
150 +
151 +**Note**: The `walletx.dart` file is generated by `configure.dart` and should not be committed.
152 +
153 +### 2.3 Create Implementation File
154 +
155 +**File**: `lib/walletx/cw_walletx.dart`
156 +
157 +Create the implementation class:
158 +
159 +```dart
160 +part of 'walletx.dart';
161 +
162 +class CWWalletX extends WalletX {
163 + @override
164 + List<String> getWalletXWordList(String language) => WalletXMnemonics.englishWordlist;
165 +
166 + @override
167 + WalletService createWalletXWalletService(bool isDirect) =>
168 + WalletXWalletService(isDirect);
169 +
170 + @override
171 + WalletCredentials createWalletXNewWalletCredentials({
172 + required String name,
173 + String? mnemonic,
174 + WalletInfo? walletInfo,
175 + String? password,
176 + String? passphrase,
177 + }) =>
178 + WalletXNewWalletCredentials(
179 + name: name,
180 + walletInfo: walletInfo,
181 + password: password,
182 + mnemonic: mnemonic,
183 + passphrase: passphrase,
184 + );
185 +
186 + // Add other required methods from the abstract class
187 +}
188 +```
189 +
190 +---
191 +
192 +## Step 3: Configuration Files Setup
193 +
194 +### 3.1 Update configure.dart
195 +
196 +**File**: `tool/configure.dart`
197 +
198 +#### 3.1.1 Add Output Path Constant
199 +
200 +```dart
201 +const walletxOutputPath = 'lib/walletx/walletx.dart';
202 +```
203 +
204 +#### 3.1.2 Add Activation Variable
205 +
206 +In the `main()` function:
207 +
208 +```dart
209 +final hasWalletX = args.contains('${prefix}walletx');
210 +```
211 +
212 +#### 3.1.3 Create Generation Function
213 +
214 +Add a function to generate the abstract class:
215 +
216 +```dart
217 +Future<void> generateWalletX(bool hasImplementation) async {
218 + final outputFile = File(walletxOutputPath);
219 + const walletxCommonHeaders = """
220 +import 'package:cw_core/wallet_base.dart';
221 +import 'package:cw_core/wallet_credentials.dart';
222 +import 'package:cw_core/wallet_info.dart';
223 +import 'package:cw_core/wallet_service.dart';
224 +import 'package:cw_core/crypto_currency.dart';
225 +import 'package:cw_core/transaction_info.dart';
226 +// Add other necessary imports
227 +""";
228 +
229 + const walletxCWHeaders = """
230 +import 'package:cw_walletx/walletx_wallet.dart';
231 +import 'package:cw_walletx/walletx_wallet_service.dart';
232 +import 'package:cw_walletx/walletx_mnemonics.dart';
233 +// Add other necessary imports from cw_walletx
234 +""";
235 +
236 + const walletxCwPart = """
237 +part 'cw_walletx.dart';
238 +""";
239 +
240 + const walletxContent = """
241 +abstract class WalletX {
242 + List<String> getWalletXWordList(String language);
243 + WalletService createWalletXWalletService(bool isDirect);
244 + WalletCredentials createWalletXNewWalletCredentials({
245 + required String name,
246 + String? mnemonic,
247 + WalletInfo? walletInfo,
248 + String? password,
249 + String? passphrase,
250 + });
251 + // Add other abstract methods as needed
252 +}
253 +""";
254 +
255 + const walletxEmptyDefinition = 'WalletX? walletx;\n';
256 + const walletxCwDefinition = 'WalletX? walletx = CWWalletX();\n';
257 +
258 + final output = '$walletxCommonHeaders\n' +
259 + (hasImplementation ? '$walletxCWHeaders\n' : '\n') +
260 + (hasImplementation ? '$walletxCwPart\n\n' : '\n') +
261 + (hasImplementation ? walletxCwDefinition : walletxEmptyDefinition) +
262 + '\n' +
263 + walletxContent;
264 +
265 + if (outputFile.existsSync()) {
266 + await outputFile.delete();
267 + }
268 +
269 + await outputFile.writeAsString(output);
270 +}
271 +```
272 +
273 +#### 3.1.4 Call Generation Function
274 +
275 +In `main()`, add:
276 +
277 +```dart
278 +await generateWalletX(hasWalletX);
279 +```
280 +
281 +#### 3.1.5 Update generatePubspec Function
282 +
283 +Add parameter and logic:
284 +
285 +```dart
286 +Future<void> generatePubspec({
287 + // ... existing parameters ...
288 + required bool hasWalletX,
289 +}) async {
290 + // ... existing code ...
291 +
292 + const cwWalletX = """
293 + cw_walletx:
294 + path: ./cw_walletx
295 + """;
296 +
297 + // ... existing code ...
298 +
299 + if (hasWalletX) {
300 + output += '\n$cwWalletX';
301 + }
302 +}
303 +```
304 +
305 +#### 3.1.6 Update generateWalletTypes Function
306 +
307 +Add parameter and logic:
308 +
309 +```dart
310 +Future<void> generateWalletTypes({
311 + // ... existing parameters ...
312 + required bool hasWalletX,
313 +}) async {
314 + // ... existing code ...
315 +
316 + if (hasWalletX) {
317 + outputContent += '\tWalletType.walletx,\n';
318 + }
319 +}
320 +```
321 +
322 +### 3.2 Update Build Scripts
323 +
324 +**Files**:
325 +- `scripts/android/app_config.sh`
326 +- `scripts/ios/app_config.sh`
327 +- `scripts/macos/app_config.sh`
328 +
329 +Add `--walletx` to the `CONFIG_ARGS` under `$CAKEWALLET`:
330 +
331 +```bash
332 +CONFIG_ARGS="--monero --bitcoin --walletx"
333 +```
334 +
335 +### 3.3 Run Configuration
336 +
337 +Open a terminal and run:
338 +
339 +```bash
340 +cd scripts/android/
341 +source ./app_env.sh cakewallet
342 +./app_config.sh
343 +
344 +cd cw_walletx && flutter pub get && dart run build_runner build --delete-conflicting-outputs && cd ..
345 +dart run build_runner build --delete-conflicting-outputs
346 +```
347 +
348 +This will:
349 +- Generate the proxy abstract class
350 +- Add wallet type to available types
351 +- Add `cw_walletx` to `pubspec.yaml`
352 +
353 +---
354 +
355 +## Step 4: Dependency Injection
356 +
357 +### 4.1 Register Wallet Service
358 +
359 +**File**: `lib/di.dart`
360 +
361 +In the `registerWalletService` function, add your case:
362 +
363 +```dart
364 +getIt.registerFactoryParam<WalletService, WalletType, void>((WalletType param1, __) {
365 + switch (param1) {
366 + // ... existing cases ...
367 + case WalletType.walletx:
368 + return walletx!.createWalletXWalletService(SettingsStoreBase.walletPasswordDirectInput);
369 + }
370 +});
371 +```
372 +
373 +**Note**: Import the proxy at the top of the file:
374 +
375 +```dart
376 +import 'package:cake_wallet/walletx/walletx.dart';
377 +```
378 +
379 +### 4.2 Register Wallet Creation Credentials
380 +
381 +**File**: `lib/view_model/wallet_new_vm.dart`
382 +
383 +In the `getCredentials` method, add your case:
384 +
385 +```dart
386 +@override
387 +WalletCredentials getCredentials(dynamic _options) {
388 + // ... existing code ...
389 +
390 + switch (type) {
391 + // ... existing cases ...
392 + case WalletType.walletx:
393 + return walletx!.createWalletXNewWalletCredentials(
394 + name: name,
395 + password: walletPassword,
396 + mnemonic: newWalletArguments!.mnemonic,
397 + passphrase: passphrase,
398 + );
399 + }
400 +}
401 +```
402 +
403 +**Note**: Import the proxy:
404 +
405 +```dart
406 +import 'package:cake_wallet/walletx/walletx.dart';
407 +```
408 +
409 +---
410 +
411 +## Step 5: Node Setup
412 +
413 +### 5.1 Create Node List YAML
414 +
415 +**File**: `assets/walletx_node_list.yml`
416 +
417 +Create a YAML file with default RPC endpoints:
418 +
419 +```yaml
420 +- uri: api.walletx.io
421 + is_default: true
422 + useSSL: true
423 + isEnabledForAutoSwitching: true
424 +- uri: walletx.publicnode.com
425 + useSSL: true
426 + isEnabledForAutoSwitching: true
427 +```
428 +
429 +### 5.2 Add to pubspec.yaml
430 +
431 +**File**: `pubspec_base.yaml`
432 +
433 +Add the asset path:
434 +
435 +```yaml
436 +flutter:
437 + assets:
438 + - assets/walletx_node_list.yml
439 +```
440 +
441 +### 5.3 Load Nodes
442 +
443 +**File**: `lib/entities/node_list.dart`
444 +
445 +Add a function to load nodes:
446 +
447 +```dart
448 +Future<List<Node>> loadDefaultWalletXNodes() async {
449 + final nodesRaw = await rootBundle.loadString('assets/walletx_node_list.yml');
450 + final loadedNodes = loadYaml(nodesRaw) as YamlList;
451 + final nodes = <Node>[];
452 + for (final raw in loadedNodes) {
453 + if (raw is Map) {
454 + final node = Node.fromMap(Map<String, Object>.from(raw));
455 + node.type = WalletType.walletx;
456 + nodes.add(node);
457 + }
458 + }
459 + return nodes;
460 +}
461 +```
462 +
463 +In the `resetToDefault` function, call it:
464 +
465 +```dart
466 +final walletxNodes = await loadDefaultWalletXNodes();
467 +nodes.addAll(walletxNodes);
468 +```
469 +
470 +### 5.4 Add Default Node Migration
471 +
472 +**File**: `lib/entities/default_settings_migration.dart`
473 +
474 +#### 5.4.1 Define Default Node URI
475 +
476 +At the top of the file:
477 +
478 +```dart
479 +const String walletXDefaultNodeUri = 'api.walletx.io';
480 +```
481 +
482 +#### 5.4.2 Add Helper Functions
483 +
484 +```dart
485 +Node? getWalletXDefaultNode({required Box<Node> nodes}) {
486 + return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == walletXDefaultNodeUri) ??
487 + nodes.values.firstWhereOrNull((node) => node.type == WalletType.walletx);
488 +}
489 +
490 +Future<void> addWalletXNodeList({required Box<Node> nodes}) async {
491 + final nodeList = await loadDefaultWalletXNodes();
492 + for (var node in nodeList) {
493 + if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
494 + await nodes.add(node);
495 + }
496 + }
497 +}
498 +
499 +Future<void> changeWalletXCurrentNodeToDefault({
500 + required SharedPreferences sharedPreferences,
501 + required Box<Node> nodes,
502 +}) async {
503 + final node = getWalletXDefaultNode(nodes: nodes);
504 + final nodeId = node?.key as int? ?? 0;
505 + await sharedPreferences.setInt(PreferencesKey.currentWalletXNodeIdKey, nodeId);
506 +}
507 +```
508 +
509 +#### 5.4.3 Add Preference Key
510 +
511 +**File**: `lib/entities/preference_key.dart`
512 +
513 +```dart
514 +static const currentWalletXNodeIdKey = 'current_walletx_node_id';
515 +```
516 +
517 +#### 5.4.4 Add Migration Case
518 +
519 +In `defaultSettingsMigration`, add:
520 +
521 +```dart
522 +case "next-number-increment": // Increment from current version
523 + await addWalletXNodeList(nodes: nodes);
524 + await changeWalletXCurrentNodeToDefault(
525 + sharedPreferences: sharedPreferences,
526 + nodes: nodes,
527 + );
528 + break;
529 +```
530 +
531 +#### 5.4.5 Update Migration Version
532 +
533 +**File**: `lib/main.dart`
534 +
535 +Increment `initialMigrationVersion` to the new case number.
536 +
537 +### 5.5 Update Node List ViewModel
538 +
539 +**File**: `lib/view_model/node_list/node_list_view_model.dart`
540 +
541 +In the `reset` function, add:
542 +
543 +```dart
544 +case WalletType.walletx:
545 + node = getWalletXDefaultNode(nodes: _nodeSource)!;
546 + break;
547 +```
548 +
549 +### 5.6 Update Node Class
550 +
551 +**File**: `cw_core/lib/node.dart`
552 +
553 +#### 5.6.1 Update URI Getter
554 +
555 +In the `uri` getter, add:
556 +
557 +```dart
558 +case WalletType.walletx:
559 + return useSSL ? Uri.https(uriRaw, '') : Uri.http(uriRaw, '');
560 +```
561 +
562 +#### 5.6.2 Update requestNode Method
563 +
564 +Add case for `WalletType.walletx` in the `requestNode` method.
565 +
566 +### 5.7 Update Settings Store
567 +
568 +**File**: `lib/store/settings_store.dart`
569 +
570 +In the `load` function:
571 +
572 +```dart
573 +final walletXNodeId = sharedPreferences.getInt(PreferencesKey.currentWalletXNodeIdKey);
574 +final walletXNode = nodeSource.get(walletXNodeId);
575 +if (walletXNode != null) {
576 + nodes[WalletType.walletx] = walletXNode;
577 +}
578 +```
579 +
580 +Repeat in the `reload` function.
581 +
582 +In `_saveCurrentNode`, add:
583 +
584 +```dart
585 +case WalletType.walletx:
586 + await sharedPreferences.setInt(PreferencesKey.currentWalletXNodeIdKey, node.key as int);
587 + break;
588 +```
589 +
590 +### 5.8 Run Code Generation
591 +
592 +```bash
593 +cd cw_core && flutter pub get && dart run build_runner build --delete-conflicting-outputs && cd ..
594 +dart run build_runner build --delete-conflicting-outputs
595 +```
596 +
597 +---
598 +
599 +## Step 6: UI Integration
600 +
601 +### 6.1 Add Wallet Icons
602 +
603 +**File**: `lib/src/dashboard/widgets/menu_widget.dart`
604 +
605 +Add icon for walletx.
606 +
607 +**File**: `lib/src/screens/wallet_list/wallet_list_page.dart`
608 +
609 +Add icon and case in `imageFor` method:
610 +
611 +```dart
612 +case WalletType.walletx:
613 + return 'assets/images/walletx_icon.png';
614 +```
615 +
616 +**File**: `lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart`
617 +
618 +Add icon similarly.
619 +
620 +### 6.2 Update Wallet Creation
621 +
622 +**File**: `lib/view_model/wallet_new_vm.dart`
623 +
624 +In the `seedPhraseWordsLength` getter, add:
625 +
626 +```dart
627 +case WalletType.walletx:
628 + return 12; // or appropriate length
629 +```
630 +
631 +---
632 +
633 +## Step 7: Wallet Restoration
634 +
635 +### 7.1 Update Seed Validator
636 +
637 +**File**: `lib/core/seed_validator.dart`
638 +
639 +In `getWordList`, add:
640 +
641 +```dart
642 +case WalletType.walletx:
643 + return WalletXMnemonics.englishWordlist; // or appropriate wordlist
644 +```
645 +
646 +### 7.2 Update Restore ViewModel
647 +
648 +**File**: `lib/view_model/wallet_restore_view_model.dart`
649 +
650 +#### 7.2.1 Update Available Modes
651 +
652 +In the constructor, add to appropriate restore mode lists:
653 +
654 +```dart
655 +case WalletType.walletx:
656 + availableModes = [WalletRestoreMode.seed, WalletRestoreMode.keys]; // or as appropriate
657 + break;
658 +```
659 +
660 +#### 7.2.2 Update hasRestoreFromPrivateKey
661 +
662 +Add to the list if supported:
663 +
664 +```dart
665 +late final bool hasRestoreFromPrivateKey = [
666 + // ... existing types ...
667 + WalletType.walletx,
668 +].contains(type);
669 +```
670 +
671 +#### 7.2.3 Update getCredentials
672 +
673 +In the `getCredentials` method, add cases for restore modes:
674 +
675 +```dart
676 +case WalletType.walletx:
677 + if (mode == WalletRestoreMode.seed) {
678 + return walletx!.createWalletXRestoreWalletFromSeedCredentials(
679 + name: name,
680 + mnemonic: seed,
681 + password: password,
682 + passphrase: passphrase,
683 + );
684 + } else if (mode == WalletRestoreMode.keys) {
685 + return walletx!.createWalletXRestoreWalletFromPrivateKey(
686 + name: name,
687 + privateKey: options['privateKey'] as String,
688 + password: password,
689 + );
690 + }
691 + break;
692 +```
693 +
694 +---
695 +
696 +## Step 8: Display Seeds/Keys
697 +
698 +**File**: `lib/view_model/wallet_keys_view_model.dart`
699 +
700 +In the `populateItems` function, add:
701 +
702 +```dart
703 +case WalletType.walletx:
704 + // Add items for displaying seed/keys
705 + break;
706 +```
707 +
708 +---
709 +
710 +## Step 9: Receive Functionality
711 +
712 +**File**: `lib/view_model/wallet_address_list/wallet_address_list_view_model.dart`
713 +
714 +### 9.1 Create PaymentUri Implementation
715 +
716 +```dart
717 +class WalletXPaymentUri implements PaymentUri {
718 + final String address;
719 + final CryptoCurrency currency;
720 +
721 + WalletXPaymentUri(this.address, this.currency);
722 +
723 + @override
724 + String get uri => 'walletx:$address'; // Adjust scheme as needed
725 +}
726 +```
727 +
728 +### 9.2 Update URI Getter
729 +
730 +In the `uri` getter:
731 +
732 +```dart
733 +case WalletType.walletx:
734 + return WalletXPaymentUri(address, currency);
735 +```
736 +
737 +### 9.3 Update Address List
738 +
739 +In the `addressList` getter, add logic to return addresses for walletx.
740 +
741 +---
742 +
743 +## Step 10: Balance Screen
744 +
745 +### 10.1 Update Balance ViewModel
746 +
747 +**File**: `lib/view_model/dashboard/balance_view_model.dart`
748 +
749 +- Update `isHomeScreenSettingsEnabled` if needed
750 +- Add cases to `availableBalanceLabel` and `additionalBalanceLabel` getters
751 +
752 +### 10.2 Update Fiat Rate Updates
753 +
754 +**File**: `lib/reactions/fiat_rate_update.dart`
755 +
756 +In `startFiatRateUpdate`, add:
757 +
758 +```dart
759 +if (appStore.wallet!.type == WalletType.walletx) {
760 + currencies = walletx!.getWalletXTokenCurrencies(appStore.wallet!)
761 + .where((element) => element.enabled);
762 +}
763 +```
764 +
765 +**File**: `lib/reactions/on_current_wallet_change.dart`
766 +
767 +Similarly update `startCurrentWalletChangeReaction`.
768 +
769 +### 10.3 Update Transaction List Item
770 +
771 +**File**: `lib/view_model/dashboard/transaction_list_item.dart`
772 +
773 +In `formattedFiatAmount`, add:
774 +
775 +```dart
776 +case WalletType.walletx:
777 + // Handle fiat amount conversion
778 + break;
779 +```
780 +
781 +---
782 +
783 +## Step 11: Send Functionality
784 +
785 +**File**: `lib/view_model/send/send_view_model.dart`
786 +
787 +- Update `_credentials` function to handle `WalletType.walletx`
788 +- Update `hasMultipleTokens` if walletx supports tokens
789 +
790 +---
791 +
792 +## Step 12: Exchange Integration
793 +
794 +### 12.1 Update Exchange ViewModel
795 +
796 +**File**: `lib/view_model/exchange/exchange_view_model.dart`
797 +
798 +In `initialPairBasedOnWallet`, add:
799 +
800 +```dart
801 +case WalletType.walletx:
802 + depositCurrency = CryptoCurrency.walletx;
803 + break;
804 +```
805 +
806 +### 12.2 Update Exchange Trade ViewModel
807 +
808 +**File**: `lib/view_model/exchange/exchange_trade_view_model.dart`
809 +
810 +If walletx has tokens, update `_checkIfCanSend`:
811 +
812 +```dart
813 +bool _isWalletXToken(CryptoCurrency currency) {
814 + return currency is WalletXToken; // Adjust based on your token type
815 +}
816 +
817 +// In _checkIfCanSend:
818 +if (_isWalletXToken(from)) {
819 + return true;
820 +}
821 +```
822 +
823 +---
824 +
825 +## Step 13: Home Settings (Token Management)
826 +
827 +**File**: `lib/view_model/dashboard/home_settings_view_model.dart`
828 +
829 +- Update `_updateTokensList` to add walletx tokens if applicable
830 +- Update `getTokenAddressBasedOnWallet` to handle walletx tokens
831 +- Update `getToken`, `addToken`, `deleteToken`, and `changeTokenAvailability` methods
832 +
833 +---
834 +
835 +## Step 14: Buy and Sell
836 +
837 +**File**: `lib/entities/provider_types.dart`
838 +
839 +- Add case in `getAvailableBuyProviderTypes` for `WalletType.walletx`
840 +- Add case in `getAvailableSellProviderTypes` for `WalletType.walletx`
841 +
842 +---
843 +
844 +## Step 15: QR Code Restoration
845 +
846 +### 15.1 Update QR Restore ViewModel
847 +
848 +**File**: `lib/view_model/restore/wallet_restore_from_qr_code.dart`
849 +
850 +Add scheme to `_walletTypeMap`:
851 +
852 +```dart
853 +'walletx': WalletType.walletx,
854 +```
855 +
856 +Update `_determineWalletRestoreMode` if needed.
857 +
858 +**File**: `lib/view_model/restore/restore_from_qr_vm.dart`
859 +
860 +Update `getCredentialsFromRestoredWallet` method.
861 +
862 +### 15.2 Update Address Validator
863 +
864 +**File**: `lib/core/address_validator.dart`
865 +
866 +In `getAddressFromStringPattern`, add:
867 +
868 +```dart
869 +case WalletType.walletx:
870 + // Add address pattern matching logic
871 + break;
872 +```
873 +
874 +If walletx has tokens, add them to the switch case as well.
875 +
876 +### 15.3 Update Platform Manifests
877 +
878 +**Android**: `AndroidManifestBase.xml`
879 +
880 +Add intent filter:
881 +
882 +```xml
883 +<intent-filter>
884 + <action android:name="android.intent.action.VIEW" />
885 + <category android:name="android.intent.category.DEFAULT" />
886 + <category android:name="android.intent.category.BROWSABLE" />
887 + <data android:scheme="walletx" />
888 +</intent-filter>
889 +```
890 +
891 +**iOS**: `InfoBase.plist`
892 +
893 +Add URL scheme:
894 +
895 +```xml
896 +<key>CFBundleURLTypes</key>
897 +<array>
898 + <dict>
899 + <key>CFBundleURLSchemes</key>
900 + <array>
901 + <string>walletx</string>
902 + </array>
903 + </dict>
904 +</array>
905 +```
906 +
907 +---
908 +
909 +## Step 16: Transaction History
910 +
911 +**File**: `lib/view_model/transaction_details_view_model.dart`
912 +
913 +- Add case for `WalletType.walletx` to add items to detailed view
914 +- Update `_explorerUrl` to return blockchain explorer link
915 +- Update `_explorerDescription` to display explorer name
916 +
917 +---
918 +
919 +## Step 17: Secrets Management
920 +
921 +### 17.1 Create Secrets Config
922 +
923 +**File**: `wallet-secrets-config.json`
924 +
925 +Create with empty object: `{}`
926 +
927 +### 17.2 Update Secret Key Utility
928 +
929 +**File**: `tool/utils/secret_key.dart`
930 +
931 +Add entry for walletx.
932 +
933 +### 17.3 Update Generate Secrets
934 +
935 +**File**: `tool/generate_secrets_config.dart`
936 +
937 +Add generation logic for walletx (don't forget to call `secrets.clear()` before adding new logic).
938 +
939 +### 17.4 Update Import Secrets
940 +
941 +**File**: `tool/import_secrets_config.dart`
942 +
943 +Add import logic for walletx.
944 +
945 +### 17.5 Update .gitignore
946 +
947 +Add:
948 +```
949 +**/tool/.walletx-secrets-config.json
950 +**/cw_walletx/lib/.secrets.g.dart
951 +```
952 +
953 +---
954 +
955 +## Testing Checklist
956 +
957 +- [ ] Create a new wallet for the type
958 +- [ ] Restore wallet from seed
959 +- [ ] Restore wallet from private key (if supported)
960 +- [ ] Display seed/keys correctly
961 +- [ ] Send transaction
962 +- [ ] Receive transaction
963 +- [ ] View transaction history
964 +- [ ] View transaction details
965 +- [ ] Exchange integration (if applicable)
966 +- [ ] Buy/Sell integration (if applicable)
967 +- [ ] Token management (if applicable)
968 +- [ ] QR code restoration
969 +- [ ] Node connection and switching
970 +- [ ] Wallet backup/restore
971 +
972 +---
973 +
974 +## Important Notes
975 +
976 +1. **No Direct Imports**: Never import directly from `cw_walletx` in `lib/`. Always use the proxy layer (`lib/walletx/walletx.dart`).
977 +
978 +2. **Proxy Pattern**: The proxy pattern ensures:
979 + - Conditional compilation (wallets can be excluded from builds)
980 + - Separation of concerns
981 + - Easier testing and mocking
982 +
983 +3. **Code Generation**: Always run `build_runner` after making changes to MobX/Hive classes:
984 + ```bash
985 + dart run build_runner build --delete-conflicting-outputs
986 + ```
987 +
988 +4. **EVM Wallets**: If adding an EVM-compatible wallet, use the [L2 Network Guide](./adding_new_l2_network_guide.md) instead.
989 +
990 +5. **Token Support**: If your wallet supports tokens (like ERC20, SPL, TRC20):
991 + - Add token checks in `exchange_trade_view_model.dart`
992 + - Add token tag checks where needed
993 + - Implement token management in home settings
994 +
995 +6. **Buy/Sell Providers**: Check which providers support your wallet currency and add them in `provider_types.dart`.
996 +
997 +7. **Build Verification**: Try building a minimal version (e.g., Monero-only) to catch any missing imports or compilation errors.
998 +
999 +---
1000 +
1001 +## Common Issues
1002 +
1003 +### Issue: Proxy not found
1004 +
1005 +**Solution**:
1006 +- Verify `configure.dart` is set up correctly
1007 +- Run the configuration scripts
1008 +- Check that `walletx.dart` is generated (even if empty)
1009 +
1010 +### Issue: Wallet type not appearing
1011 +
1012 +**Solution**:
1013 +- Verify `generateWalletTypes` includes your wallet type
1014 +- Check that configuration scripts are run
1015 +- Verify `lib/wallet_types.g.dart` includes your type
1016 +
1017 +### Issue: Node connection fails
1018 +
1019 +**Solution**:
1020 +- Verify node list YAML is properly formatted
1021 +- Check that nodes are loaded in `default_settings_migration.dart`
1022 +- Verify preference keys are set correctly
1023 +
1024 +### Issue: Compilation errors
1025 +
1026 +**Solution**:
1027 +- Ensure all proxy methods are implemented in `cw_walletx.dart`
1028 +- Check that all imports are through proxy, not direct
1029 +- Verify code generation is run
1030 +
1031 +---
1032 +
1033 +Copyright (C) 2018-2023 Cake Labs LLC
docs/NEW_WALLET_TYPES.md deleted
-301
@@ -1,301 +0,0 @@
1 -# Guide to adding a new wallet type in Cake Wallet
2 -
3 -## Wallet Integration
4 -
5 -**N:B** Throughout this guide, `walletx` refers to the specific wallet type you want to add. If you're adding `BNB` to CakeWallet, then `walletx` for you here is `bnb`.
6 -
7 -**Core Folder/Files Setup**
8 -- Identify your core component/package (major project component), which would power the integration e.g web3dart, solana, onchain etc
9 -- Add a new entry to `WalletType` class in `cw_core/wallet_type.dart`.
10 -- Fill out the necessary information in the various functions in the files, concerning the wallet name, the native currency type, symbol etc.
11 -- Go to `cw_core/lib/currency_for_wallet_type.dart`, in the `currencyForWalletType` function, add a case for `walletx`, returning the native cryptocurrency for `walletx`.
12 -- If the cryptocurrency for walletx is not available among the default cryptocurrencies, add a new cryptocurrency entry in `cw_core/lib/cryptocurrency.dart`.
13 -- Add the newly created cryptocurrency name to the list named `all` in this file.
14 -- Create a package for the wallet specific integration, name it. `cw_walletx`
15 -- Add the following initial common files and replicate to fit the wallet
16 - - walletx_transaction_history.dart
17 - - walletx_transaction_info.dart
18 - - walletx_mnemonics_exception.dart
19 - - walletx_tokens.dart
20 - - walletx_wallet_service.dart:
21 - - walletx_wallet.dart
22 - - etc.
23 -
24 -- Add the code to run the code generation needed for the files in the `cw_walletx` package to the `model_generator.sh` script
25 -
26 - cd cw_walletx && flutter pub get && dart run build_runner build --delete-conflicting-outputs && cd ..
27 -
28 -- Add the relevant dev_dependencies for generating the files also
29 - - build_runner
30 - - mobx_codegen
31 - - hive_generator
32 -
33 -**WalletX Proxy Setup**
34 -
35 -A `Proxy` class is used to communicate with the specific wallet package we have. Instead of directly making use of methods and parameters in `cw_walletx` within the `lib` directory, we use a proxy to access these data. All important functions, calls and interactions we want to make with our `cw_walletx` package would be defined and done through the proxy class. The class would define the import
36 -
37 -- Create a proxy folder titled `walletx` to handle the wallet operations. It would contain 2 files: `cw_walletx.dart` and `walletx.dart`.
38 -- `cw_walletx.dart` file would hold an implementation class containing major operations to be done in the lib directory. It serves as the link between the cw_walletx package and the rest of the codebase(lib directory files and folders).
39 -- `walletx.dart` would contain the abstract class highlighting the methods that would bring the functionalities and features in the `cw_walletx` package to the rest of the `lib` directory.
40 -- Add `walletx.dart` to `.gitignore` as we won’t be pushing it: `lib/tron/tron.dart`.
41 -- `walletx.dart` would always be generated based on the configure files we would be setting up in the next step.
42 -
43 -**Configuration Files Setup**
44 -- Before we populate the field, head over to `tool/configure.dart` to setup the necessary configurations for the `walletx` proxy.
45 -- Define the output path, it’ll follow the format `lib/walletx/walletx.dart`.
46 -- Add the variable to check if `walletx` is to be activated
47 -- Define the function that would generate the abstract class for the proxy.(We will flesh out this function in the next steps).
48 -- Add the defined variable in step 2 to the `generatePubspec` and `generateWalletTypes`.
49 -- Next, modify the following functions:
50 - - generatePubspec function
51 - 1. Add the parameters to the method params (i.e required bool hasWalletX)
52 - 2. Define a variable to hold the entry for the pubspec.yaml file
53 -
54 - const cwWalletX = """
55 - cw_tron:
56 - path: ./cw_walletx
57 - """;
58 -
59 - 3. Add an if block that takes in the passed parameter and adds the defined variable(inn the previous step) to the list of outputs
60 -
61 - if (hasWalletX) {
62 - output += '\n$cwWalletX’;
63 - }
64 -
65 - - generateWalletTypes function
66 - 1. Add the parameters to the method params (i.e required bool hasWalletX)
67 - 2. Add an if block to add the wallet type to the list of outputs this function generates
68 -
69 - if (hasWalletX) {
70 - outputContent += '\tWalletType.walletx,\n’;
71 - }
72 -
73 -- Head over to `scripts/android/pubspec_gen.sh` script, and modify the `CONFIG_ARGS` under `$CAKEWALLET`. Add `"—walletx”` to the end of the passed in params.
74 -- Repeat this in `scripts/ios/app_config.sh` and `scripts/macos/app_config.sh`
75 -- Open a terminal and cd into `scripts/android/`. Run the following commands to run setup configuration scripts(proxy class, add walletx to list of wallet types and add cw_walletx to pubspec).
76 -
77 - source ./app_env.sh cakewallet
78 -
79 - ./app_config.sh
80 -
81 - cd cw_walletx && flutter pub get && dart run build_runner build
82 -
83 - dart run build_runner build --delete-conflicting-outputs
84 -
85 -Moving forward, our interactions with the cw_walletx package would be through the proxy class and its methods.
86 -
87 -**Pre-Wallet Creation for WalletX**
88 -- Go to `di.dart` and locate the block to `registerWalletService`. In this, add the case to handle creating the WalletXWalletService
89 -
90 - case WalletType.walletx:
91 - return walletx!.createWalletXWalletService(_walletInfoSource);
92 -
93 -- Go to `lib/view_model/wallet_new_vm.dart`, in the getCredentials method, which gets the new wallet credentials for walletX add the case for the new wallet
94 -
95 - case WalletType.walletx:
96 - return walletx!.createWalletXNewWalletCredentials(name: name);
97 -
98 -**Node Setup**
99 -- Before we can be able to successfully create a new wallet of wallet type walletx we need to setup the node that the wallet would use:
100 -- In the assets directory, create a new file and name it `walletx_node_list.yml`. This yml file would contain the details for nodes to be used for walletX. An example structure for each node entry
101 -
102 - uri: "api.nodeurl.io"
103 - is_default: true
104 - useSSL: true
105 -
106 -You can add as many node entries as desired.
107 -
108 -- Add the path to the yml file created to the `pubspec_base.yaml` file (`“assets/walletx_node_list.yml”`)
109 -- Go to `lib/entities/node_list.dart`, add a function to load the node entries we made in `walletx_node_list.yml` for walletx.
110 -- Name your function `loadDefaultWalletXNodes()`. The function would handle loading the yml file as a string and parsing it into a Node Object to be used within the app. Here’s a template for the function.
111 -
112 - Future<List<Node>> loadDefaultWalletXNodes() async {
113 - final nodesRaw = await rootBundle.loadString('assets/tron_node_list.yml');
114 - final loadedNodes = loadYaml(nodesRaw) as YamlList;
115 - final nodes = <Node>[];
116 - for (final raw in loadedNodes) {
117 - if (raw is Map) {
118 - final node = Node.fromMap(Map<String, Object>.from(raw));
119 - node.type = WalletType.tron;
120 - nodes.add(node);
121 - }
122 - }
123 - return nodes;
124 - }
125 -
126 -- Inside the `resetToDefault` function, call the function you created and add the result to the nodes result variable.
127 -- Go to `lib/entities/default_settings_migration.dart` file, we’ll be adding the following to the file.
128 -- At the top of the file, after the imports, define the default nodeUrl for wallet-name.
129 -- Next, write a function to fetch the node for this default uri you added above.
130 -
131 - Node? getWalletXDefaultNode({required Box<Node> nodes}) {
132 - return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == walletXDefaultNodeUri) ??
133 - nodes.values.firstWhereOrNull((node) => node.type == WalletType.walletx);
134 - }
135 -
136 -- Next, write a function that will add the list of nodes we declared in the `walletx_node_list.yml` file to the Nodes Box, to be used in the app. Here’s the format for this function
137 -
138 - Future<void> addWalletXNodeList({required Box<Node> nodes}) async {
139 - final nodeList = await loadDefaultWalletXNodes();
140 - for (var node in nodeList) {
141 - if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
142 - await nodes.add(node);
143 - }
144 - }
145 - }
146 -
147 -- Next, we’ll write the function to change walletX current node to default. A handy function we would make use of later on. Add a new preference key in `lib/entities/preference_key.dart` with the format `PreferencesKey.currentWalletXNodeIdKey`, we’ll use it to identify the current node id.
148 -
149 - Future<void> changeWalletXCurrentNodeToDefault(
150 - {required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
151 - final node = getWalletXDefaultNode(nodes: nodes);
152 - final nodeId = node?.key as int? ?? 0;
153 - await sharedPreferences.setInt(PreferencesKey.currentWalletXNodeIdKey, nodeId);
154 - }
155 -
156 -- Next, in the `defaultSettingsMigration` function at the top of the file, add a new case to handle both `addWalletXNodeList` and `changeWalletXCurrentNodeToDefault`
157 -
158 - case “next-number-increment”:
159 - await addWalletXNodeList(nodes: nodes);
160 - await changeWalletXCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
161 - break;
162 -
163 -- Next, increase the `initialMigrationVersion` number in `main.dart` to be the new case entry number you entered in the step above for the `defaultSettingsMigration` function.
164 -- Next, go to `lib/view_model/node_list/node_list_view_model.dart`
165 -- In the `reset` function, add a case for walletX:
166 -
167 - case WalletType.tron:
168 - node = getTronDefaultNode(nodes: _nodeSource)!;
169 - break;
170 -
171 -- Lastly, go to `cw_core/lib/node.dart`,
172 -- In the uri getter, add a case to handle the uri setup for walletX. If the node uses http, return `Uri.http`, if not, return `Uri.https`
173 -
174 - case WalletType.walletX:
175 - return Uri.https(uriRaw, ‘’);
176 -
177 -- Also, in the `requestNode` method, add a case for `WalletType.walletx`
178 -- Next is the modifications to `lib/store/settings_store.dart` file:
179 -- In the `load` function, create a variable to fetch the currentWalletxNodeId using the `PreferencesKey.currentWalletXNodeIdKey` we created earlier.
180 -- Create another variable `walletXNode` which gets the walletx node using the nodeId variable assigned in the step above.
181 -- Add a check to see if walletXNode is not null, if it’s not null, assign the created tronNode variable to the nodeMap with a type of walletX
182 -
183 - final walletXNode = nodeSource.get(walletXNodeId);
184 - final walletXNodeId = sharedPreferences.getInt(PreferencesKey.currentWalletXNodeIdKey);
185 - if (walletXNode != null) {
186 - nodes[WalletType.walletx] = walletXNode;
187 - }
188 -
189 -- Repeat the steps above in the `reload` function
190 -- Next, add a case for walletX in the `_saveCurrentNode` function.
191 -
192 -- Run the following commands after to generate modified files in cw_core and lib
193 -
194 - cd cw_core && flutter pub get && dart run build_runner build --delete-conflicting-outputs && cd ..
195 -
196 - dart run build_runner build --delete-conflicting-outputs
197 -
198 -- Lastly, before we run the app to test what we’ve done so far,
199 -- Go to `lib/src/dashboard/widgets/menu_widget.dart` and add an icon for walletX to be used within the app.
200 -- Go to `lib/src/screens/wallet_list/wallet_list_page.dart` and add an icon for walletx, add a case for walletx also in the `imageFor` method.
201 -- Do the same thing in `lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart`
202 -
203 -- One last thing before we can create a wallet for walletx, go to `lib/view_model/wallet_new_vm.dart`
204 -- Modify the `seedPhraseWordsLength` getter by adding a case for `WalletType.walletx`
205 -
206 -Now you can run the codebase and successfully create a wallet for type walletX successfully.
207 -
208 -**Display Seeds/Keys**
209 -- Next, we want to set up our wallet to display the seeds and/or keys in the security page of the app.
210 -- Go to `lib/view_model/wallet_keys_view_model.dart`
211 -- Modify the `populateItems` function by adding a case for `WalletType.walletx` in it.
212 -- Now your seeds and/or keys should display when you go to Security and Backup -> Show seed/keys page within the app.
213 -
214 -**Restore Wallet**
215 -- Go to `lib/core/seed_validator.dart`
216 -- In the `getWordList` method, add a case to handle `WalletType.walletx` which would return the word list to be used to validate the passed in seeds.
217 -- Next, go to `lib/wallet_restore_view_model.dart`
218 -- Modify the `hasRestoreFromPrivateKey` to reflect if walletx supports restore from Key
219 -- Add a switch case to handle the various restore modes that walletX supports
220 -- Modify the `getCredential` method to handle the restore flows for `WalletType.walletx`
221 -- Run the build_runner code generation command
222 -
223 -**Receive**
224 -- Go to `lib/view_model/wallet_address_list/wallet_address_list_view_model.dart`
225 -- Create an implementation of `PaymentUri` for type WalletX.
226 -- In the uri getter, add a case for `WalletType.walletx` returning the implementation class for `PaymentUri`
227 -- Modify the `addressList` getter to return the address/addresses for walletx
228 -
229 -**Balance Screen**
230 -- Go to `lib/view_model/dashboard/balance_view_model.dart`
231 -- Modify the function to adjust the way the balance is being displayed on the app: `isHomeScreenSettingsEnabled`
232 -- Add a case to the `availableBalanceLabel` getter to modify the text being displayed (Available or confirmed)
233 -- Same for `additionalBalanceLabel`
234 -- Next, go to `lib/reactions/fiat_rate_update.dart`
235 -- Modify the `startFiatRateUpdate` function and add a check for `WalletType.walletx` to return all the token currencies
236 -- Next, go to `lib/reactions/on_current_wallet_change.dart`
237 -- Modify the `startCurrentWalletChangeReaction` function and add a check for `WalletType.walletx` to return all the token currencies
238 -- Lastly, go to `lib/view_model/dashboard/transaction_list_item.dart`
239 -- In the `formattedFiatAmount` getter, add a case to handle the fiat amount conversion for `WalletType.walletx`
240 -
241 -**Send ViewModel**
242 -- Go to `lib/view_model/send/send_view_model.dart`
243 -- Modify the `_credentials` function to reflect `WalletType.walletx`
244 -- Modify `hasMultipleTokens` to reflect wallets
245 -
246 -**Exchange**
247 -- Go to lib/view_model/exchange/exchange_view_model.dart
248 -- First, add a case for WalletType.walletx in the `initialPairBasedOnWallet` method.
249 -- If WalletX supports tokens, go to `lib/view_model/exchange/exchange_trade_view_model.dart`
250 -- Modify the `_checkIfCanSend` method by creating a `_isWalletXToken` that checks if the from currency is WalletX and if its tag is for walletx
251 -- Add `_isWalletXToken` to the return logic for the method.
252 -
253 -**Secrets**
254 -- Create a json file named `wallet-secrets-config.json` and put an empty curly bracket “{}” in it
255 -- Add a new entry to `tool/utils/secret_key.dart` for walletx
256 -- Modify the `tool/generate_secrets_config.dart` file for walletx, don’t forget to call `secrets.clear()` before adding a new set of generation logic
257 -- Modify the `tool/import_secrets_config.dart` file for walletx
258 -- In the `.gitignore` file, add `**/tool/.walletx-secrets-config.json` and `**/cw_walletx/lib/.secrets.g.dart`
259 -
260 -**HomeSettings: WalletX Tokens Display and Management**
261 -- Go to `lib/view_model/dashboard/home_settings_view_model.dart`
262 -- Modify the `_updateTokensList` method to add all walletx tokens if the wallet type is `WalletType.walletx`.
263 -- Modify the `getTokenAddressBasedOnWallet` method to include a case to fetch the address for a WalletX token.
264 -- Modify the `getToken` method to return a specific walletx token
265 -- Modify the `addToken`, `deleteToken` and `changeTokenAvailability` methods to handle cases where the walletType is walletx
266 -
267 -**Buy and Sell WalletX**
268 -- Go to `lib/entities/provider_types.dart`
269 -- Add a case for `WalletType.walletx` in the `getAvailableBuyProviderTypes` method. Return a list of providers that support buying WalletX.
270 -- Add a case for `WalletType.walletx` in the `getAvailableSellProviderTypes` method. Return a list of providers that support selling WalletX.
271 -
272 -**Restore QR setup**
273 -- Go to `lib/view_model/restore/wallet_restore_from_qr_code.dart`
274 -- Add the scheme for walletx in `_walletTypeMap`
275 -- Also modify `_determineWalletRestoreMode` to include a case for walletx
276 -- Go to `lib/view_model/restore/restore_from_qr_vm.dart`
277 -- Modify `getCredentialsFromRestoredWallet` method
278 -- Go to `lib/core/address_validator.dart`
279 -- Modify the `getAddressFromStringPattern` method to add a case for `WalletType.walletx`
280 -- and if it has tokens (ex. erc20, trc20, spl tokens) then add them to the switch case as well
281 -- Add the scheme for walletx for both Android in `AndroidManifestBase.xml` and iOS in `InfoBase.plist`
282 -
283 -**Transaction History**
284 -- Go to `lib/view_model/transaction_details_view_model.dart`
285 -- Add a case for `WalletType.walletx` to add the items to be displayed on the detailed view
286 -- Modify the `_explorerUrl` method to add the blockchain explorer link for WalletX in order to view the more info on a transaction
287 -- Modify the `_explorerDescription` to display the name of the explorer
288 -
289 -
290 -
291 -
292 -# Points to note when adding the new wallet type
293 -
294 -1. if it has tokens (ex. ERC20, SPL, etc...) make sure to add that to this function `_checkIfCanSend` in `exchange_trade_view_model.dart`
295 -1. if it has tokens (ex. ERC20, SPL, etc...) make sure to add a check for the tags as well in the
296 -2. Check On/Off ramp providers that support the new wallet currency and add them accordingly in `provider_types.dart`
297 -3. Add support for wallet uri scheme to restore from QR for both Android in `AndroidManifestBase.xml` and iOS in `InfoBase.plist`
298 -4. Make sure no imports are using the wallet internal package files directly, instead use the proxy layers that is created in the main lib `lib/cw_ethereum.dart` for example. (i.e try building Monero.com if you get compilation errors, then you probably missed something)
299 -
300 -
301 -Copyright (C) 2018-2023 Cake Labs LLC
lib/arbitrum/cw_arbitrum.dart deleted
-244
@@ -1,244 +0,0 @@
1 -part of 'arbitrum.dart';
2 -
3 -class CWArbitrum extends Arbitrum {
4 - @override
5 - List<String> getArbitrumWordList(String language) => EVMChainMnemonics.englishWordlist;
6 -
7 - WalletService createArbitrumWalletService(bool isDirect) =>
8 - ArbitrumWalletService(isDirect, client: ArbitrumClient());
9 -
10 - @override
11 - WalletCredentials createArbitrumNewWalletCredentials({
12 - required String name,
13 - String? mnemonic,
14 - WalletInfo? walletInfo,
15 - String? password,
16 - String? passphrase,
17 - }) =>
18 - EVMChainNewWalletCredentials(
19 - name: name,
20 - walletInfo: walletInfo,
21 - password: password,
22 - mnemonic: mnemonic,
23 - passphrase: passphrase,
24 - );
25 -
26 - @override
27 - WalletCredentials createArbitrumRestoreWalletFromSeedCredentials({
28 - required String name,
29 - required String mnemonic,
30 - required String password,
31 - String? passphrase,
32 - }) =>
33 - EVMChainRestoreWalletFromSeedCredentials(
34 - name: name,
35 - password: password,
36 - mnemonic: mnemonic,
37 - passphrase: passphrase,
38 - );
39 -
40 - @override
41 - WalletCredentials createArbitrumRestoreWalletFromPrivateKey({
42 - required String name,
43 - required String privateKey,
44 - required String password,
45 - }) =>
46 - EVMChainRestoreWalletFromPrivateKey(name: name, password: password, privateKey: privateKey);
47 -
48 - @override
49 - WalletCredentials createArbitrumHardwareWalletCredentials({
50 - required String name,
51 - required HardwareAccountData hwAccountData,
52 - WalletInfo? walletInfo,
53 - }) =>
54 - EVMChainRestoreWalletFromHardware(
55 - name: name,
56 - hwAccountData: hwAccountData,
57 - walletInfo: walletInfo,
58 - );
59 -
60 - @override
61 - String getAddress(WalletBase wallet) => (wallet as ArbitrumWallet).walletAddresses.address;
62 -
63 - @override
64 - String getPrivateKey(WalletBase wallet) {
65 - final privateKeyHolder = (wallet as ArbitrumWallet).evmChainPrivateKey;
66 - if (privateKeyHolder is EthPrivateKey) return bytesToHex(privateKeyHolder.privateKey);
67 - return "";
68 - }
69 -
70 - @override
71 - String getPublicKey(WalletBase wallet) {
72 - final privateKeyInUnitInt = (wallet as ArbitrumWallet).evmChainPrivateKey;
73 - return privateKeyInUnitInt.address.hex;
74 - }
75 -
76 - Object createArbitrumTransactionCredentials(
77 - List<Output> outputs, {
78 - required CryptoCurrency currency,
79 - int? feeRate,
80 - }) =>
81 - EVMChainTransactionCredentials(
82 - outputs
83 - .map(
84 - (out) => OutputInfo(
85 - fiatAmount: out.fiatAmount,
86 - cryptoAmount: out.cryptoAmount,
87 - address: out.address,
88 - note: out.note,
89 - sendAll: out.sendAll,
90 - extractedAddress: out.extractedAddress,
91 - isParsedAddress: out.isParsedAddress,
92 - formattedCryptoAmount: out.formattedCryptoAmount,
93 - ),
94 - )
95 - .toList(),
96 - priority: null,
97 - currency: currency,
98 - feeRate: feeRate,
99 - );
100 -
101 - Object createArbitrumTransactionCredentialsRaw(
102 - List<OutputInfo> outputs, {
103 - required CryptoCurrency currency,
104 - required int feeRate,
105 - }) =>
106 - EVMChainTransactionCredentials(
107 - outputs,
108 - priority: null,
109 - currency: currency,
110 - feeRate: feeRate,
111 - );
112 -
113 - @override
114 - int formatterArbitrumParseAmount(String amount) => EVMChainFormatter.parseEVMChainAmount(amount);
115 -
116 - @override
117 - double formatterArbitrumAmountToDouble({
118 - TransactionInfo? transaction,
119 - BigInt? amount,
120 - int exponent = 18,
121 - }) {
122 - assert(transaction != null || amount != null);
123 -
124 - if (transaction != null) {
125 - transaction as EVMChainTransactionInfo;
126 - return transaction.ethAmount / BigInt.from(10).pow(transaction.exponent);
127 - } else {
128 - return (amount!) / BigInt.from(10).pow(exponent);
129 - }
130 - }
131 -
132 - @override
133 - List<Erc20Token> getERC20Currencies(WalletBase wallet) =>
134 - (wallet as ArbitrumWallet).erc20Currencies;
135 -
136 - @override
137 - Future<void> addErc20Token(WalletBase wallet, CryptoCurrency token) =>
138 - (wallet as ArbitrumWallet).addErc20Token(token as Erc20Token);
139 -
140 - @override
141 - Future<void> deleteErc20Token(WalletBase wallet, CryptoCurrency token) =>
142 - (wallet as ArbitrumWallet).deleteErc20Token(token as Erc20Token);
143 -
144 - @override
145 - Future<void> removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token) =>
146 - (wallet as ArbitrumWallet).removeTokenTransactionsInHistory(token as Erc20Token);
147 -
148 - @override
149 - Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress) =>
150 - (wallet as ArbitrumWallet).getErc20Token(contractAddress, 'arbitrum');
151 -
152 - @override
153 - CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) {
154 - transaction as EVMChainTransactionInfo;
155 - if (transaction.tokenSymbol == CryptoCurrency.arbEth.title ||
156 - transaction.tokenSymbol == "ARB") {
157 - return CryptoCurrency.arbEth;
158 - }
159 -
160 - wallet as ArbitrumWallet;
161 -
162 - return wallet.erc20Currencies.firstWhere(
163 - (element) =>
164 - transaction.contractAddress?.toLowerCase() == element.contractAddress?.toLowerCase(),
165 - );
166 - }
167 -
168 - @override
169 - void updateArbitrumScanUsageState(WalletBase wallet, bool isEnabled) =>
170 - (wallet as ArbitrumWallet).updateScanProviderUsageState(isEnabled);
171 -
172 - @override
173 - Web3Client? getWeb3Client(WalletBase wallet) => (wallet as ArbitrumWallet).getWeb3Client();
174 -
175 - @override
176 - String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress;
177 -
178 - @override
179 - Future<PendingTransaction> createTokenApproval(
180 - WalletBase wallet,
181 - BigInt amount,
182 - String spender,
183 - CryptoCurrency token,
184 - ) =>
185 - (wallet as EVMChainWallet).createApprovalTransaction(
186 - amount,
187 - spender,
188 - token,
189 - null,
190 - "ARB",
191 - );
192 -
193 - @override
194 - Future<void> setHardwareWalletService(WalletBase wallet, HardwareWalletService service) async {
195 - if (service is EVMChainLedgerService) {
196 - ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedgerConnection(
197 - service.ledgerConnection, (await wallet.walletInfo.getDerivationInfo()).derivationPath);
198 - } else if (service is EVMChainBitboxService) {
199 - ((wallet as EVMChainWallet).evmChainPrivateKey as EvmBitboxCredentials)
200 - .setBitbox(service.manager, (await wallet.walletInfo.getDerivationInfo()).derivationPath);
201 - }
202 - return Future.value();
203 - }
204 -
205 - @override
206 - HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection) =>
207 - EVMChainLedgerService(connection);
208 -
209 - @override
210 - HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager) =>
211 - EVMChainBitboxService(manager, chainId: 42161);
212 -
213 - @override
214 - List<String> getDefaultTokenContractAddresses() => DefaultArbitrumErc20Tokens()
215 - .initialArbitrumErc20Tokens
216 - .map((e) => e.contractAddress)
217 - .toList();
218 -
219 - @override
220 - bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress) {
221 - final arbitrumWallet = wallet as ArbitrumWallet;
222 - return arbitrumWallet.erc20Currencies.any(
223 - (element) => element.contractAddress.toLowerCase() == contractAddress.toLowerCase(),
224 - );
225 - }
226 -
227 - @override
228 - Future<bool> isApprovalRequired(
229 - WalletBase wallet, String tokenContract, String spender, BigInt requiredAmount) =>
230 - (wallet as EVMChainWallet).isApprovalRequired(tokenContract, spender, requiredAmount);
231 -
232 - @override
233 - Future<PendingTransaction> createRawCallDataTransaction(
234 - WalletBase wallet, String to, String dataHex, BigInt valueWei) =>
235 - (wallet as EVMChainWallet).createCallDataTransaction(to, dataHex, valueWei, null);
236 -
237 - @override
238 - String? getArbitrumNativeEstimatedFee(WalletBase wallet) =>
239 - (wallet as EVMChainWallet).nativeTxEstimatedFee;
240 -
241 - @override
242 - String? getArbitrumERC20EstimatedFee(WalletBase wallet) =>
243 - (wallet as EVMChainWallet).erc20TxEstimatedFee;
244 -}
lib/base/cw_base.dart deleted
-258
@@ -1,258 +0,0 @@
1 -part of 'base.dart';
2 -
3 -class CWBase extends Base {
4 - @override
5 - List<String> getBaseWordList(String language) => EVMChainMnemonics.englishWordlist;
6 -
7 - WalletService createBaseWalletService(bool isDirect) =>
8 - BaseWalletService(isDirect, client: BaseClient());
9 -
10 - @override
11 - WalletCredentials createBaseNewWalletCredentials({
12 - required String name,
13 - String? mnemonic,
14 - WalletInfo? walletInfo,
15 - String? password,
16 - String? passphrase,
17 - }) =>
18 - EVMChainNewWalletCredentials(
19 - name: name,
20 - walletInfo: walletInfo,
21 - password: password,
22 - mnemonic: mnemonic,
23 - passphrase: passphrase,
24 - );
25 -
26 - @override
27 - WalletCredentials createBaseRestoreWalletFromSeedCredentials({
28 - required String name,
29 - required String mnemonic,
30 - required String password,
31 - String? passphrase,
32 - }) =>
33 - EVMChainRestoreWalletFromSeedCredentials(
34 - name: name,
35 - password: password,
36 - mnemonic: mnemonic,
37 - passphrase: passphrase,
38 - );
39 -
40 - @override
41 - WalletCredentials createBaseRestoreWalletFromPrivateKey({
42 - required String name,
43 - required String privateKey,
44 - required String password,
45 - }) =>
46 - EVMChainRestoreWalletFromPrivateKey(name: name, password: password, privateKey: privateKey);
47 -
48 - @override
49 - WalletCredentials createBaseHardwareWalletCredentials({
50 - required String name,
51 - required HardwareAccountData hwAccountData,
52 - WalletInfo? walletInfo,
53 - }) =>
54 - EVMChainRestoreWalletFromHardware(
55 - name: name,
56 - hwAccountData: hwAccountData,
57 - walletInfo: walletInfo,
58 - );
59 -
60 - @override
61 - String getAddress(WalletBase wallet) => (wallet as BaseWallet).walletAddresses.address;
62 -
63 - @override
64 - String getPrivateKey(WalletBase wallet) {
65 - final privateKeyHolder = (wallet as BaseWallet).evmChainPrivateKey;
66 - if (privateKeyHolder is EthPrivateKey) return bytesToHex(privateKeyHolder.privateKey);
67 - return "";
68 - }
69 -
70 - @override
71 - String getPublicKey(WalletBase wallet) {
72 - final privateKeyInUnitInt = (wallet as BaseWallet).evmChainPrivateKey;
73 - return privateKeyInUnitInt.address.hex;
74 - }
75 -
76 - @override
77 - TransactionPriority getDefaultTransactionPriority() => EVMChainTransactionPriority.medium;
78 -
79 - @override
80 - TransactionPriority getBaseTransactionPrioritySlow() => EVMChainTransactionPriority.slow;
81 -
82 - @override
83 - List<TransactionPriority> getTransactionPriorities() => EVMChainTransactionPriority.all;
84 -
85 - @override
86 - TransactionPriority deserializeBaseTransactionPriority(int raw) =>
87 - EVMChainTransactionPriority.deserialize(raw: raw);
88 -
89 - Object createBaseTransactionCredentials(
90 - List<Output> outputs, {
91 - required TransactionPriority priority,
92 - required CryptoCurrency currency,
93 - int? feeRate,
94 - }) =>
95 - EVMChainTransactionCredentials(
96 - outputs
97 - .map(
98 - (out) => OutputInfo(
99 - fiatAmount: out.fiatAmount,
100 - cryptoAmount: out.cryptoAmount,
101 - address: out.address,
102 - note: out.note,
103 - sendAll: out.sendAll,
104 - extractedAddress: out.extractedAddress,
105 - isParsedAddress: out.isParsedAddress,
106 - formattedCryptoAmount: out.formattedCryptoAmount,
107 - ),
108 - )
109 - .toList(),
110 - priority: priority as EVMChainTransactionPriority,
111 - currency: currency,
112 - feeRate: feeRate,
113 - );
114 -
115 - Object createBaseTransactionCredentialsRaw(
116 - List<OutputInfo> outputs, {
117 - TransactionPriority? priority,
118 - required CryptoCurrency currency,
119 - required int feeRate,
120 - }) =>
121 - EVMChainTransactionCredentials(
122 - outputs,
123 - priority: priority as EVMChainTransactionPriority?,
124 - currency: currency,
125 - feeRate: feeRate,
126 - );
127 -
128 - @override
129 - int formatterBaseParseAmount(String amount) => EVMChainFormatter.parseEVMChainAmount(amount);
130 -
131 - @override
132 - double formatterBaseAmountToDouble({
133 - TransactionInfo? transaction,
134 - BigInt? amount,
135 - int exponent = 18,
136 - }) {
137 - assert(transaction != null || amount != null);
138 -
139 - if (transaction != null) {
140 - transaction as EVMChainTransactionInfo;
141 - return transaction.ethAmount / BigInt.from(10).pow(transaction.exponent);
142 - } else {
143 - return (amount!) / BigInt.from(10).pow(exponent);
144 - }
145 - }
146 -
147 - @override
148 - List<Erc20Token> getERC20Currencies(WalletBase wallet) => (wallet as BaseWallet).erc20Currencies;
149 -
150 - @override
151 - Future<void> addErc20Token(WalletBase wallet, CryptoCurrency token) =>
152 - (wallet as BaseWallet).addErc20Token(token as Erc20Token);
153 -
154 - @override
155 - Future<void> deleteErc20Token(WalletBase wallet, CryptoCurrency token) =>
156 - (wallet as BaseWallet).deleteErc20Token(token as Erc20Token);
157 -
158 - @override
159 - Future<void> removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token) =>
160 - (wallet as BaseWallet).removeTokenTransactionsInHistory(token as Erc20Token);
161 -
162 - @override
163 - Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress) =>
164 - (wallet as BaseWallet).getErc20Token(contractAddress, 'base');
165 -
166 - @override
167 - CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) {
168 - transaction as EVMChainTransactionInfo;
169 - if (transaction.tokenSymbol == CryptoCurrency.baseEth.title ||
170 - transaction.tokenSymbol == "BASE") {
171 - return CryptoCurrency.baseEth;
172 - }
173 -
174 - wallet as BaseWallet;
175 -
176 - return wallet.erc20Currencies.firstWhere(
177 - (element) =>
178 - transaction.contractAddress?.toLowerCase() == element.contractAddress?.toLowerCase(),
179 - );
180 - }
181 -
182 - @override
183 - void updateBaseScanUsageState(WalletBase wallet, bool isEnabled) =>
184 - (wallet as BaseWallet).updateScanProviderUsageState(isEnabled);
185 -
186 - @override
187 - Web3Client? getWeb3Client(WalletBase wallet) => (wallet as BaseWallet).getWeb3Client();
188 -
189 - @override
190 - String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress;
191 -
192 - @override
193 - Future<bool> isApprovalRequired(
194 - WalletBase wallet, String tokenContract, String spender, BigInt requiredAmount) =>
195 - (wallet as EVMChainWallet).isApprovalRequired(tokenContract, spender, requiredAmount);
196 -
197 - @override
198 - Future<PendingTransaction> createTokenApproval(
199 - WalletBase wallet,
200 - BigInt amount,
201 - String spender,
202 - CryptoCurrency token,
203 - TransactionPriority priority,
204 - ) =>
205 - (wallet as EVMChainWallet).createApprovalTransaction(
206 - amount,
207 - spender,
208 - token,
209 - priority as EVMChainTransactionPriority,
210 - "BASE",
211 - );
212 -
213 - @override
214 - Future<PendingTransaction> createRawCallDataTransaction(WalletBase wallet, String to,
215 - String dataHex, BigInt valueWei, TransactionPriority priority) =>
216 - (wallet as EVMChainWallet).createCallDataTransaction(
217 - to, dataHex, valueWei, priority as EVMChainTransactionPriority);
218 -
219 - @override
220 - Future<void> setHardwareWalletService(WalletBase wallet, HardwareWalletService service) async {
221 - if (service is EVMChainLedgerService) {
222 - ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedgerConnection(
223 - service.ledgerConnection, (await wallet.walletInfo.getDerivationInfo()).derivationPath);
224 - } else if (service is EVMChainBitboxService) {
225 - ((wallet as EVMChainWallet).evmChainPrivateKey as EvmBitboxCredentials)
226 - .setBitbox(service.manager, (await wallet.walletInfo.getDerivationInfo()).derivationPath);
227 - }
228 - return Future.value();
229 - }
230 -
231 - @override
232 - HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection) =>
233 - EVMChainLedgerService(connection);
234 -
235 - @override
236 - HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager) =>
237 - EVMChainBitboxService(manager, chainId: 8453);
238 -
239 - @override
240 - List<String> getDefaultTokenContractAddresses() =>
241 - DefaultBaseErc20Tokens().initialBaseErc20Tokens.map((e) => e.contractAddress).toList();
242 -
243 - @override
244 - bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress) {
245 - final baseWallet = wallet as BaseWallet;
246 - return baseWallet.erc20Currencies.any(
247 - (element) => element.contractAddress.toLowerCase() == contractAddress.toLowerCase(),
248 - );
249 - }
250 -
251 - @override
252 - String? getBaseNativeEstimatedFee(WalletBase wallet) =>
253 - (wallet as EVMChainWallet).nativeTxEstimatedFee;
254 -
255 - @override
256 - String? getBaseERC20EstimatedFee(WalletBase wallet) =>
257 - (wallet as EVMChainWallet).erc20TxEstimatedFee;
258 -}
lib/buy/moonpay/moonpay_provider.dart
+1 -1
@@ -82,7 +82,7 @@ class MoonPayProvider extends BuyProvider {
82
83 static String get _apiKey => secrets.moonPayApiKey;
84
85 - String get currencyCode => walletTypeToCryptoCurrency(wallet.type).title.toLowerCase();
85 + String get currencyCode => walletTypeToCryptoCurrency(wallet.type, chainId: wallet.chainId).title.toLowerCase();
86
87 String get trackUrl => baseBuyUrl + '/transaction_receipt?transactionId=';
88
lib/buy/wyre/wyre_buy_provider.dart
+2 -2
@@ -70,7 +70,7 @@ class WyreBuyProvider extends BuyProvider {
70 final body = {
71 'amount': amount,
72 'sourceCurrency': sourceCurrency,
73 - 'destCurrency': walletTypeToCryptoCurrency(wallet.type).title,
73 + 'destCurrency': walletTypeToCryptoCurrency(wallet.type, chainId: wallet.chainId).title,
74 'dest': walletTypeToString(wallet.type).toLowerCase() + ':' + wallet.walletAddresses.address,
75 'referrerAccountId': _accountId,
76 'lockFields': ['amount', 'sourceCurrency', 'destCurrency', 'dest']
@@ -100,7 +100,7 @@ class WyreBuyProvider extends BuyProvider {
100 final body = {
101 'amount': amount,
102 'sourceCurrency': sourceCurrency,
103 - 'destCurrency': walletTypeToCryptoCurrency(wallet.type).title,
103 + 'destCurrency': walletTypeToCryptoCurrency(wallet.type, chainId: wallet.chainId).title,
104 'dest': walletTypeToString(wallet.type).toLowerCase() + ':' + wallet.walletAddresses.address,
105 'accountId': _accountId,
106 'country': _countryCode
lib/core/address_validator.dart
+1
@@ -338,6 +338,7 @@ class AddressValidator extends TextValidator {
338 case CryptoCurrency.maticpoly:
339 case CryptoCurrency.baseEth:
340 case CryptoCurrency.arbEth:
341 + case CryptoCurrency.arb:
342 pattern = '0x[0-9a-zA-Z]+';
343 case CryptoCurrency.nano:
344 pattern = 'nano_[0-9a-zA-Z]{60}';
lib/core/background_sync.dart
+42 -25
@@ -5,6 +5,7 @@ import 'package:cake_wallet/core/key_service.dart';
5 import 'package:cake_wallet/core/wallet_loading_service.dart';
6 import 'package:cake_wallet/di.dart';
7 import 'package:cake_wallet/entities/preferences_key.dart';
8 +import 'package:cake_wallet/reactions/wallet_connect.dart';
9 import 'package:cake_wallet/store/settings_store.dart';
10 import 'package:cake_wallet/utils/feature_flag.dart';
11 import 'package:cake_wallet/utils/tor.dart';
@@ -16,7 +17,7 @@ import 'package:cw_core/utils/print_verbose.dart';
17 import 'package:cw_core/wallet_type.dart';
18 import 'package:flutter_local_notifications/flutter_local_notifications.dart';
19 import 'package:shared_preferences/shared_preferences.dart';
19 -import 'package:flutter/foundation.dart';
20 +import 'package:cake_wallet/evm/evm.dart';
21
22 class BackgroundSync {
23 final FlutterLocalNotificationsPlugin _notificationsPlugin = FlutterLocalNotificationsPlugin();
@@ -26,7 +27,7 @@ class BackgroundSync {
27 if (_isInitialized) return;
28
29 const androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher');
29 -
30 +
31 const iosSettings = DarwinInitializationSettings(
32 requestAlertPermission: true,
33 requestBadgePermission: true,
@@ -45,16 +46,18 @@ class BackgroundSync {
46 Future<bool> requestPermissions() async {
47 if (Platform.isIOS || Platform.isMacOS) {
48 return await _notificationsPlugin
48 - .resolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>()
49 - ?.requestPermissions(
50 - alert: true,
51 - badge: true,
52 - sound: true,
53 - ) ?? false;
49 + .resolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>()
50 + ?.requestPermissions(
51 + alert: true,
52 + badge: true,
53 + sound: true,
54 + ) ??
55 + false;
56 } else if (Platform.isAndroid) {
57 return await _notificationsPlugin
56 - .resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
57 - ?.areNotificationsEnabled() ?? false;
58 + .resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
59 + ?.areNotificationsEnabled() ??
60 + false;
61 }
62 return false;
63 }
@@ -62,7 +65,7 @@ class BackgroundSync {
65 Future<void> showNotification(String title, String content) async {
66 await _initializeNotifications();
67 final hasPermission = await requestPermissions();
65 -
68 +
69 if (!hasPermission) {
70 printV('Notification permissions not granted');
71 return;
@@ -112,10 +115,11 @@ class BackgroundSync {
115 .where((element) => ![WalletType.haven, WalletType.decred].contains(element.type))
116 .toList();
117 for (int i = 0; i < moneroWallets.length; i++) {
115 - final wallet = await walletLoadingService.load(moneroWallets[i].type, moneroWallets[i].name, isBackground: true);
118 + final wallet = await walletLoadingService.load(moneroWallets[i].type, moneroWallets[i].name,
119 + isBackground: true);
120 int syncedTicks = 0;
121 final keyService = getIt.get<KeyService>();
118 -
122 +
123 int stuckTicks = 0;
124
125 inner:
@@ -123,7 +127,9 @@ class BackgroundSync {
127 await Future.delayed(const Duration(seconds: 1));
128 final syncStatus = wallet.syncStatus;
129 final progress = syncStatus.progress();
126 - if (syncStatus is ConnectedSyncStatus || syncStatus is AttemptingSyncStatus || syncStatus is NotConnectedSyncStatus) {
130 + if (syncStatus is ConnectedSyncStatus ||
131 + syncStatus is AttemptingSyncStatus ||
132 + syncStatus is NotConnectedSyncStatus) {
133 stuckTicks++;
134 if (stuckTicks > 30) {
135 printV("${wallet.name} STUCK SYNCING");
@@ -134,7 +140,13 @@ class BackgroundSync {
140 }
141 if (syncStatus is NotConnectedSyncStatus) {
142 printV("${wallet.name} NOT CONNECTED");
137 - final node = settingsStore.getCurrentNode(wallet.type);
143 +
144 + int? chainId;
145 + if (isEVMCompatibleChain(wallet.type)) {
146 + chainId = evm!.getSelectedChainId(wallet);
147 + }
148 +
149 + final node = settingsStore.getCurrentNode(wallet.type, chainId: chainId);
150 await wallet.connectToNode(node: node);
151 await wallet.startBackgroundSync();
152 printV("STARTED SYNC");
@@ -147,7 +159,8 @@ class BackgroundSync {
159 syncedTicks = 0;
160 printV("WALLET $i SYNCED");
161 try {
150 - await wallet.stopBackgroundSync((await keyService.getWalletPassword(walletName: wallet.name)));
162 + await wallet.stopBackgroundSync(
163 + (await keyService.getWalletPassword(walletName: wallet.name)));
164 } catch (e) {
165 printV("error stopping sync: $e");
166 }
@@ -185,25 +198,29 @@ class BackgroundSync {
198 final sortedTxs = txs.transactions.values.toList()..sort((a, b) => a.date.compareTo(b.date));
199 final sharedPreferences = await SharedPreferences.getInstance();
200 for (final tx in sortedTxs) {
188 - final lastTriggerString = sharedPreferences.getString(PreferencesKey.backgroundSyncLastTrigger(wallet.name));
189 - final lastTriggerDate = lastTriggerString != null
190 - ? DateTime.parse(lastTriggerString)
191 - : DateTime.now();
201 + final lastTriggerString =
202 + sharedPreferences.getString(PreferencesKey.backgroundSyncLastTrigger(wallet.name));
203 + final lastTriggerDate =
204 + lastTriggerString != null ? DateTime.parse(lastTriggerString) : DateTime.now();
205 final keys = sharedPreferences.getKeys();
206 if (tx.date.isBefore(lastTriggerDate)) {
194 - printV("w: ${wallet.name}, tx: ${tx.date} is before $lastTriggerDate (lastTriggerString: $lastTriggerString) (k: ${keys.length})");
207 + printV(
208 + "w: ${wallet.name}, tx: ${tx.date} is before $lastTriggerDate (lastTriggerString: $lastTriggerString) (k: ${keys.length})");
209 continue;
210 }
197 - await sharedPreferences.setString(PreferencesKey.backgroundSyncLastTrigger(wallet.name), tx.date.add(Duration(minutes: 1)).toIso8601String());
211 + await sharedPreferences.setString(PreferencesKey.backgroundSyncLastTrigger(wallet.name),
212 + tx.date.add(Duration(minutes: 1)).toIso8601String());
213 final action = tx.direction == TransactionDirection.incoming ? "Received" : "Sent";
214 if (sharedPreferences.getBool(PreferencesKey.backgroundSyncNotificationsEnabled) ?? false) {
200 - await showNotification("$action ${wallet.currency.fullName} in ${wallet.name}", "${tx.amountFormatted()}");
215 + await showNotification(
216 + "$action ${wallet.currency.fullName} in ${wallet.name}", "${tx.amountFormatted()}");
217 }
202 - printV("${wallet.currency.fullName} in ${wallet.name}: TX: ${tx.date} ${tx.amount} ${tx.direction}");
218 + printV(
219 + "${wallet.currency.fullName} in ${wallet.name}: TX: ${tx.date} ${tx.amount} ${tx.direction}");
220 }
221 wallet.id;
222 await wallet.stopBackgroundSync(await keyService.getWalletPassword(walletName: wallet.name));
223 await wallet.close(shouldCleanup: true);
224 }
225 }
209 -}
\ No newline at end of file
226 +}
lib/core/node_switching_service.dart
+25 -14
@@ -7,6 +7,8 @@ import 'package:cake_wallet/utils/feature_flag.dart';
7 import 'package:cw_core/utils/print_verbose.dart';
8 import 'package:hive/hive.dart';
9 import 'package:connectivity_plus/connectivity_plus.dart';
10 +import 'package:cake_wallet/evm/evm.dart';
11 +import 'package:cake_wallet/reactions/wallet_connect.dart';
12
13 class NodeSwitchingService {
14 NodeSwitchingService({
@@ -142,12 +144,21 @@ class NodeSwitchingService {
144 return;
145 }
146
145 - final walletType = appStore.wallet!.type;
146 - final currentNode = settingsStore.getCurrentNode(walletType);
147 + final wallet = appStore.wallet!;
148 + final walletType = wallet.type;
149 +
150 + WalletType nodeWalletType = walletType;
151 +
152 + int? chainId;
153 + if (isEVMCompatibleChain(walletType)) {
154 + chainId = evm!.getSelectedChainId(appStore.wallet!);
155 + }
156 +
157 + final currentNode = settingsStore.getCurrentNode(nodeWalletType, chainId: chainId);
158
159 // Get all trusted nodes for this wallet type
160 final trustedNodes = nodeSource.values
150 - .where((node) => node.type == walletType && node.isEnabledForAutoSwitching)
161 + .where((node) => node.type == nodeWalletType && node.isEnabledForAutoSwitching)
162 .toList();
163
164 if (trustedNodes.isEmpty) {
@@ -157,26 +168,26 @@ class NodeSwitchingService {
168 }
169
170 // Initialize used nodes list for this wallet type if it does not exist
160 - _usedNodeKeys.putIfAbsent(walletType, () => []);
171 + _usedNodeKeys.putIfAbsent(nodeWalletType, () => []);
172
173 // Add current node to used list if not already there
163 - if (!_usedNodeKeys[walletType]!.contains(currentNode.key)) {
164 - _usedNodeKeys[walletType]!.add(currentNode.key);
174 + if (!_usedNodeKeys[nodeWalletType]!.contains(currentNode.key)) {
175 + _usedNodeKeys[nodeWalletType]!.add(currentNode.key);
176 }
177
178 // Try to find an active unused node
168 - Node? nextNode = await _findActiveNode(trustedNodes, walletType);
179 + Node? nextNode = await _findActiveNode(trustedNodes, nodeWalletType);
180
181 // If all trusted nodes have been used, check if we should reset
182 if (nextNode == null) {
172 - printV('All trusted nodes have been tried for wallet type: $walletType');
183 + printV('All trusted nodes have been tried for wallet type: $nodeWalletType');
184
185 // If we've tried all nodes and still haven't reached max attempts, reset and try again
186 if (_switchingAttempts < _maxNodeSwitchingAttempts) {
187 printV('Resetting used nodes list and trying again');
177 - _usedNodeKeys[walletType]!.clear();
188 + _usedNodeKeys[nodeWalletType]!.clear();
189 // Try again with cleared used list
179 - nextNode = await _findActiveNode(trustedNodes, walletType);
190 + nextNode = await _findActiveNode(trustedNodes, nodeWalletType);
191 }
192
193 // If still no active node found, we give up
@@ -188,16 +199,16 @@ class NodeSwitchingService {
199 }
200
201 // Ensure the selected node is marked as used
191 - if (!_usedNodeKeys[walletType]!.contains(nextNode.key)) {
192 - _usedNodeKeys[walletType]!.add(nextNode.key);
202 + if (!_usedNodeKeys[nodeWalletType]!.contains(nextNode.key)) {
203 + _usedNodeKeys[nodeWalletType]!.add(nextNode.key);
204 }
205
206 printV(
207 'Switching from ${currentNode.uriRaw} to ${nextNode.uriRaw} (attempt $_switchingAttempts/$_maxNodeSwitchingAttempts)');
197 - printV('Used nodes for ${walletType}: ${_usedNodeKeys[walletType]}');
208 + printV('Used nodes for ${nodeWalletType}: ${_usedNodeKeys[nodeWalletType]}');
209
210 // Update the current node in settings
200 - settingsStore.nodes[walletType] = nextNode;
211 + settingsStore.nodes[nodeWalletType] = nextNode;
212
213 // Connect the wallet to the new node
214 await appStore.wallet!.connectToNode(node: nextNode);
lib/core/seed_validator.dart
+5 -11
@@ -1,12 +1,9 @@
1 -import 'package:cake_wallet/arbitrum/arbitrum.dart';
2 -import 'package:cake_wallet/base/base.dart';
1 import 'package:cake_wallet/bitcoin/bitcoin.dart';
2 import 'package:cake_wallet/core/validator.dart';
3 import 'package:cake_wallet/entities/mnemonic_item.dart';
6 -import 'package:cake_wallet/ethereum/ethereum.dart';
4 +import 'package:cake_wallet/evm/evm.dart';
5 import 'package:cake_wallet/monero/monero.dart';
6 import 'package:cake_wallet/nano/nano.dart';
9 -import 'package:cake_wallet/polygon/polygon.dart';
7 import 'package:cake_wallet/solana/solana.dart';
8 import 'package:cake_wallet/tron/tron.dart';
9 import 'package:cake_wallet/wownero/wownero.dart';
@@ -36,14 +33,15 @@ class SeedValidator extends Validator<MnemonicItem> {
33 case WalletType.monero:
34 return monero!.getMoneroWordList(language);
35 case WalletType.ethereum:
39 - return ethereum!.getEthereumWordList(language);
36 + case WalletType.polygon:
37 + case WalletType.base:
38 + case WalletType.arbitrum:
39 + return evm!.getEVMWordList(language);
40 case WalletType.bitcoinCash:
41 return getBitcoinWordList(language);
42 case WalletType.nano:
43 case WalletType.banano:
44 return nano!.getNanoWordList(language);
45 - case WalletType.polygon:
46 - return polygon!.getPolygonWordList(language);
45 case WalletType.solana:
46 return solana!.getSolanaWordList(language);
47 case WalletType.tron:
@@ -54,10 +52,6 @@ class SeedValidator extends Validator<MnemonicItem> {
52 return zano!.getWordList(language);
53 case WalletType.decred:
54 return decred!.getDecredWordList();
57 - case WalletType.base:
58 - return base!.getBaseWordList(language);
59 - case WalletType.arbitrum:
60 - return arbitrum!.getArbitrumWordList(language);
55 case WalletType.none:
56 case WalletType.haven:
57 return [];
lib/core/trade_monitor.dart
-2
@@ -95,7 +95,6 @@ class TradeMonitor {
95 }
96
97 if (_tradeTimers.containsKey(trade.id)) {
98 - printV('Trade ${trade.id} is already being monitored');
98 continue;
99 } else {
100 _startTradeMonitoring(trade, provider!);
@@ -141,7 +140,6 @@ class TradeMonitor {
140 }
141
142 if (_isFinalState(trade.state)) {
144 - printV('Skipping trade ${trade.id} because it\'s in a final state');
143 return true;
144 }
145
lib/core/universal_address_detector.dart
+12 -2
@@ -2,6 +2,7 @@ import 'package:cake_wallet/utils/payment_request.dart';
2 import 'package:cake_wallet/core/address_validator.dart';
3 import 'package:cw_core/crypto_currency.dart';
4 import 'package:cw_core/wallet_type.dart';
5 +import 'package:cw_core/currency_for_wallet_type.dart';
6
7 class AddressDetectionResult {
8 AddressDetectionResult({
@@ -16,6 +17,7 @@ class AddressDetectionResult {
17 this.callbackMessage,
18 required this.isValid,
19 this.errorMessage,
20 + this.chainId,
21 });
22
23 final String address;
@@ -29,6 +31,7 @@ class AddressDetectionResult {
31 final String? callbackMessage;
32 final bool isValid;
33 final String? errorMessage;
34 + final int? chainId;
35 }
36
37 /// Universal address detector that can identify cryptocurrency addresses from various formats
@@ -70,11 +73,13 @@ class UniversalAddressDetector {
73
74 // Determine currency from scheme
75 final currency = CryptoCurrency.fromString(uri.scheme.toLowerCase());
76 + final walletType = cryptoCurrencyToWalletType(currency);
77 + final chainId = getChainIdByCryptoCurrency(currency);
78
79 return AddressDetectionResult(
80 address: paymentRequest.address,
81 detectedCurrency: currency,
77 - detectedWalletType: cryptoCurrencyToWalletType(currency),
82 + detectedWalletType: walletType,
83 amount: paymentRequest.amount,
84 note: paymentRequest.note,
85 scheme: paymentRequest.scheme,
@@ -82,6 +87,7 @@ class UniversalAddressDetector {
87 callbackUrl: paymentRequest.callbackUrl,
88 callbackMessage: paymentRequest.callbackMessage,
89 isValid: true,
90 + chainId: chainId,
91 );
92 } catch (e) {
93 return AddressDetectionResult(
@@ -226,11 +232,15 @@ class UniversalAddressDetector {
232 // Test each pattern in order of specificity
233 for (final pattern in detectionPatterns) {
234 if (pattern.pattern.hasMatch(cleanInput)) {
235 + final walletType = cryptoCurrencyToWalletType(pattern.currency);
236 + final chainId = getChainIdByCryptoCurrency(pattern.currency);
237 +
238 return AddressDetectionResult(
239 address: cleanInput,
240 detectedCurrency: pattern.currency,
232 - detectedWalletType: cryptoCurrencyToWalletType(pattern.currency),
241 + detectedWalletType: walletType,
242 isValid: true,
243 + chainId: chainId,
244 );
245 }
246 }
lib/di.dart
+5 -11
@@ -4,10 +4,9 @@ import 'package:cake_wallet/.secrets.g.dart' as secrets;
4 import 'package:cake_wallet/anonpay/anonpay_api.dart';
5 import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
6 import 'package:cake_wallet/anypay/anypay_api.dart';
7 -import 'package:cake_wallet/arbitrum/arbitrum.dart';
8 -import 'package:cake_wallet/base/base.dart';
7 import 'package:cake_wallet/bitcoin/bitcoin.dart';
8 import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
9 +import 'package:cake_wallet/evm/evm.dart';
10 import 'package:cake_wallet/buy/dfx/dfx_buy_provider.dart';
11 import 'package:cake_wallet/buy/moonpay/moonpay_provider.dart';
12 import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
@@ -87,12 +86,10 @@ import 'package:cake_wallet/entities/preferences_key.dart';
86 import 'package:cake_wallet/entities/qr_view_data.dart';
87 import 'package:cake_wallet/entities/template.dart';
88 import 'package:cake_wallet/entities/transaction_description.dart';
90 -import 'package:cake_wallet/ethereum/ethereum.dart';
89 import 'package:cake_wallet/exchange/exchange_template.dart';
90 import 'package:cake_wallet/exchange/trade.dart';
91 import 'package:cake_wallet/monero/monero.dart';
92 import 'package:cake_wallet/nano/nano.dart';
95 -import 'package:cake_wallet/polygon/polygon.dart';
93 import 'package:cake_wallet/decred/decred.dart';
94 import 'package:cake_wallet/reactions/on_authentication_state_change.dart';
95 import 'package:cake_wallet/routes.dart';
@@ -1204,7 +1201,10 @@ Future<void> setup({
1201 SettingsStoreBase.walletPasswordDirectInput,
1202 );
1203 case WalletType.ethereum:
1207 - return ethereum!.createEthereumWalletService(SettingsStoreBase.walletPasswordDirectInput);
1204 + case WalletType.polygon:
1205 + case WalletType.base:
1206 + case WalletType.arbitrum:
1207 + return evm!.createEVMWalletService(param1, SettingsStoreBase.walletPasswordDirectInput);
1208 case WalletType.bitcoinCash:
1209 return bitcoinCash!.createBitcoinCashWalletService(_unspentCoinsInfoSource, SettingsStoreBase.walletPasswordDirectInput);
1210 case WalletType.dogecoin:
@@ -1212,8 +1212,6 @@ Future<void> setup({
1212 case WalletType.nano:
1213 case WalletType.banano:
1214 return nano!.createNanoWalletService(SettingsStoreBase.walletPasswordDirectInput);
1215 - case WalletType.polygon:
1216 - return polygon!.createPolygonWalletService(SettingsStoreBase.walletPasswordDirectInput);
1215 case WalletType.solana:
1216 return solana!.createSolanaWalletService(SettingsStoreBase.walletPasswordDirectInput);
1217 case WalletType.tron:
@@ -1224,10 +1222,6 @@ Future<void> setup({
1222 return zano!.createZanoWalletService();
1223 case WalletType.decred:
1224 return decred!.createDecredWalletService(_unspentCoinsInfoSource);
1227 - case WalletType.base:
1228 - return base!.createBaseWalletService(SettingsStoreBase.walletPasswordDirectInput);
1229 - case WalletType.arbitrum:
1230 - return arbitrum!.createArbitrumWalletService(SettingsStoreBase.walletPasswordDirectInput);
1225 case WalletType.haven:
1226 return HavenWalletService();
1227 case WalletType.none:
lib/entities/ens_record.dart
+4 -20
@@ -1,7 +1,5 @@
1 -import 'package:cake_wallet/arbitrum/arbitrum.dart';
2 -import 'package:cake_wallet/base/base.dart';
3 -import 'package:cake_wallet/ethereum/ethereum.dart';
4 -import 'package:cake_wallet/polygon/polygon.dart';
1 +import 'package:cake_wallet/evm/evm.dart';
2 +import 'package:cake_wallet/reactions/wallet_connect.dart';
3 import 'package:cw_core/utils/proxy_wrapper.dart';
4 import 'package:cw_core/utils/print_verbose.dart';
5 import 'package:cw_core/wallet_base.dart';
@@ -10,25 +8,11 @@ import 'package:ens_dart/ens_dart.dart';
8 import 'package:web3dart/web3dart.dart';
9
10 class EnsRecord {
13 -
11 static Future<String> fetchEnsAddress(String name, {WalletBase? wallet}) async {
15 -
12 Web3Client? _client;
13
18 - if (wallet != null && wallet.type == WalletType.ethereum) {
19 - _client = ethereum!.getWeb3Client(wallet);
20 - }
21 -
22 - if (wallet != null && wallet.type == WalletType.polygon) {
23 - _client = polygon!.getWeb3Client(wallet);
24 - }
25 -
26 - if (wallet != null && wallet.type == WalletType.base) {
27 - _client = base!.getWeb3Client(wallet);
28 - }
29 -
30 - if (wallet != null && wallet.type == WalletType.arbitrum) {
31 - _client = arbitrum!.getWeb3Client(wallet);
14 + if (wallet != null && (isEVMCompatibleChain(wallet.type))) {
15 + _client = evm!.getWeb3Client(wallet);
16 }
17
18 if (_client == null) {
lib/entities/node_list.dart
+1
@@ -19,6 +19,7 @@ Future<List<Node>> loadDefaultNodes(WalletType type) async {
19 case WalletType.haven:
20 path = 'assets/haven_node_list.yml';
21 break;
22 + // TODO: (refactoring) each wallet would have its path, so `wallet.nodePath` would be decided based on chain id in Evm wallet
23 case WalletType.ethereum:
24 path = 'assets/ethereum_server_list.yml';
25 break;
lib/entities/preferences_key.dart
+2
@@ -84,6 +84,7 @@ class PreferencesKey {
84 static const useArbiScan = 'use_arbitrum_scan';
85 static const useTronGrid = 'use_trongrid';
86 static const useMempoolFeeAPI = 'use_mempool_fee_api';
87 + static const evmHiddenChainIds = 'evm_hidden_chain_ids';
88 static const defaultNanoRep = 'default_nano_representative';
89 static const defaultBananoRep = 'default_banano_representative';
90 static const lookupsTwitter = 'looks_up_twitter';
@@ -94,6 +95,7 @@ class PreferencesKey {
95 static const lookupsOpenAlias = 'looks_up_open_alias';
96 static const lookupsENS = 'looks_up_ens';
97 static const lookupsWellKnown = 'looks_up_well_known';
98 + static const useBlinkProtection = 'use_blink_protection';
99 static const usePayjoin = 'use_payjoin';
100 static const showPayjoinCard = 'show_payjoin_card';
101 static const showCameraConsent = 'show_camera_consent';
lib/entities/priority_for_wallet_type.dart
+4 -9
@@ -1,11 +1,8 @@
1 -import 'package:cake_wallet/arbitrum/arbitrum.dart';
2 -import 'package:cake_wallet/base/base.dart';
1 import 'package:cake_wallet/bitcoin/bitcoin.dart';
2 import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
3 import 'package:cake_wallet/dogecoin/dogecoin.dart';
6 -import 'package:cake_wallet/ethereum/ethereum.dart';
4 +import 'package:cake_wallet/evm/evm.dart';
5 import 'package:cake_wallet/monero/monero.dart';
8 -import 'package:cake_wallet/polygon/polygon.dart';
6 import 'package:cake_wallet/wownero/wownero.dart';
7 import 'package:cake_wallet/zano/zano.dart';
8 import 'package:cake_wallet/decred/decred.dart';
@@ -23,15 +20,13 @@ List<TransactionPriority> priorityForWalletType(WalletType type) {
20 case WalletType.litecoin:
21 return bitcoin!.getLitecoinTransactionPriorities();
22 case WalletType.ethereum:
26 - return ethereum!.getTransactionPriorities();
23 + case WalletType.polygon:
24 + case WalletType.base:
25 + return evm!.getTransactionPriorities();
26 case WalletType.bitcoinCash:
27 return bitcoinCash!.getTransactionPriorities();
28 case WalletType.dogecoin:
29 return dogecoin!.getTransactionPriorities();
31 - case WalletType.polygon:
32 - return polygon!.getTransactionPriorities();
33 - case WalletType.base:
34 - return base!.getTransactionPriorities();
30 case WalletType.arbitrum:
31 case WalletType.nano:
32 case WalletType.banano:
lib/entities/wallet_contact.dart
+6 -2
@@ -1,9 +1,9 @@
1 import 'package:cake_wallet/entities/contact_base.dart';
2 import 'package:cw_core/crypto_currency.dart';
3 +import 'package:cw_core/wallet_type.dart';
4
5 class WalletContact implements ContactBase {
5 - WalletContact(this.address, this.name, this.type);
6 - //: super(name, address, type);
6 + WalletContact(this.address, this.name, this.type, {this.walletType});
7
8 @override
9 String address;
@@ -13,4 +13,8 @@ class WalletContact implements ContactBase {
13
14 @override
15 CryptoCurrency type;
16 +
17 + /// Wallet type of the wallet this contact belongs to
18 + /// Used for EVM chain filtering
19 + final WalletType? walletType;
20 }
lib/ethereum/cw_ethereum.dart deleted
-283
@@ -1,283 +0,0 @@
1 -part of 'ethereum.dart';
2 -
3 -class CWEthereum extends Ethereum {
4 - @override
5 - List<String> getEthereumWordList(String language) => EVMChainMnemonics.englishWordlist;
6 -
7 - WalletService createEthereumWalletService(bool isDirect) =>
8 - EthereumWalletService(isDirect, client: EthereumClient());
9 -
10 - @override
11 - WalletCredentials createEthereumNewWalletCredentials({
12 - required String name,
13 - String? mnemonic,
14 - WalletInfo? walletInfo,
15 - String? password,
16 - String? passphrase,
17 - }) =>
18 - EVMChainNewWalletCredentials(
19 - name: name,
20 - walletInfo: walletInfo,
21 - password: password,
22 - mnemonic: mnemonic,
23 - passphrase: passphrase,
24 - );
25 -
26 - @override
27 - WalletCredentials createEthereumRestoreWalletFromSeedCredentials({
28 - required String name,
29 - required String mnemonic,
30 - required String password,
31 - String? passphrase,
32 - }) =>
33 - EVMChainRestoreWalletFromSeedCredentials(
34 - name: name,
35 - password: password,
36 - mnemonic: mnemonic,
37 - passphrase: passphrase,
38 - );
39 -
40 - @override
41 - WalletCredentials createEthereumRestoreWalletFromPrivateKey({
42 - required String name,
43 - required String privateKey,
44 - required String password,
45 - }) =>
46 - EVMChainRestoreWalletFromPrivateKey(name: name, password: password, privateKey: privateKey);
47 -
48 - @override
49 - WalletCredentials createEthereumHardwareWalletCredentials({
50 - required String name,
51 - required HardwareAccountData hwAccountData,
52 - WalletInfo? walletInfo,
53 - }) =>
54 - EVMChainRestoreWalletFromHardware(
55 - name: name, hwAccountData: hwAccountData, walletInfo: walletInfo);
56 -
57 - @override
58 - String getAddress(WalletBase wallet) => (wallet as EthereumWallet).walletAddresses.address;
59 -
60 - @override
61 - String getPrivateKey(WalletBase wallet) {
62 - final privateKeyHolder = (wallet as EthereumWallet).evmChainPrivateKey;
63 - if (privateKeyHolder is EthPrivateKey) return bytesToHex(privateKeyHolder.privateKey);
64 - return "";
65 - }
66 -
67 - @override
68 - String getPublicKey(WalletBase wallet) {
69 - final privateKeyInUnitInt = (wallet as EthereumWallet).evmChainPrivateKey;
70 - return privateKeyInUnitInt.address.hex;
71 - }
72 -
73 - @override
74 - TransactionPriority getDefaultTransactionPriority() => EVMChainTransactionPriority.medium;
75 -
76 - @override
77 - TransactionPriority getEthereumTransactionPrioritySlow() => EVMChainTransactionPriority.slow;
78 -
79 - @override
80 - List<TransactionPriority> getTransactionPriorities() => EVMChainTransactionPriority.all;
81 -
82 - @override
83 - TransactionPriority deserializeEthereumTransactionPriority(int raw) =>
84 - EVMChainTransactionPriority.deserialize(raw: raw);
85 -
86 - Object createEthereumTransactionCredentials(
87 - List<Output> outputs, {
88 - required TransactionPriority priority,
89 - required CryptoCurrency currency,
90 - int? feeRate,
91 - }) =>
92 - EVMChainTransactionCredentials(
93 - outputs
94 - .map((out) => OutputInfo(
95 - fiatAmount: out.fiatAmount,
96 - cryptoAmount: out.cryptoAmount,
97 - address: out.address,
98 - note: out.note,
99 - sendAll: out.sendAll,
100 - extractedAddress: out.extractedAddress,
101 - isParsedAddress: out.isParsedAddress,
102 - formattedCryptoAmount: out.formattedCryptoAmount,
103 - memo: out.memo))
104 - .toList(),
105 - priority: priority as EVMChainTransactionPriority,
106 - currency: currency,
107 - feeRate: feeRate,
108 - );
109 -
110 - Object createEthereumTransactionCredentialsRaw(
111 - List<OutputInfo> outputs, {
112 - TransactionPriority? priority,
113 - required CryptoCurrency currency,
114 - required int feeRate,
115 - }) =>
116 - EVMChainTransactionCredentials(
117 - outputs,
118 - priority: priority as EVMChainTransactionPriority?,
119 - currency: currency,
120 - feeRate: feeRate,
121 - );
122 -
123 - @override
124 - int formatterEthereumParseAmount(String amount) => EVMChainFormatter.parseEVMChainAmount(amount);
125 -
126 - @override
127 - double formatterEthereumAmountToDouble(
128 - {TransactionInfo? transaction, BigInt? amount, int exponent = 18}) {
129 - assert(transaction != null || amount != null);
130 -
131 - if (transaction != null) {
132 - transaction as EVMChainTransactionInfo;
133 - return transaction.ethAmount / BigInt.from(10).pow(transaction.exponent);
134 - } else {
135 - return (amount!) / BigInt.from(10).pow(exponent);
136 - }
137 - }
138 -
139 - @override
140 - List<Erc20Token> getERC20Currencies(WalletBase wallet) =>
141 - (wallet as EthereumWallet).erc20Currencies;
142 -
143 - @override
144 - Future<void> addErc20Token(WalletBase wallet, CryptoCurrency token) =>
145 - (wallet as EthereumWallet).addErc20Token(token as Erc20Token);
146 -
147 - @override
148 - Future<void> deleteErc20Token(WalletBase wallet, CryptoCurrency token) =>
149 - (wallet as EthereumWallet).deleteErc20Token(token as Erc20Token);
150 -
151 - @override
152 - Future<void> removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token) =>
153 - (wallet as EthereumWallet).removeTokenTransactionsInHistory(token as Erc20Token);
154 -
155 - @override
156 - Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress) =>
157 - (wallet as EthereumWallet).getErc20Token(contractAddress, 'eth');
158 -
159 - @override
160 - CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) {
161 - transaction as EVMChainTransactionInfo;
162 - if (transaction.tokenSymbol == CryptoCurrency.eth.title) {
163 - return CryptoCurrency.eth;
164 - }
165 -
166 - wallet as EthereumWallet;
167 -
168 - return wallet.erc20Currencies.firstWhere(
169 - (element) => transaction.tokenSymbol == element.symbol,
170 - );
171 - }
172 -
173 - @override
174 - void updateEtherscanUsageState(WalletBase wallet, bool isEnabled) =>
175 - (wallet as EthereumWallet).updateScanProviderUsageState(isEnabled);
176 -
177 - @override
178 - Web3Client? getWeb3Client(WalletBase wallet) => (wallet as EthereumWallet).getWeb3Client();
179 -
180 - @override
181 - String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress;
182 -
183 - @override
184 - Future<void> setHardwareWalletService(WalletBase wallet, HardwareWalletService service) async {
185 - if (service is EVMChainLedgerService) {
186 - ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedgerConnection(
187 - service.ledgerConnection, (await wallet.walletInfo.getDerivationInfo()).derivationPath);
188 - } else if (service is EVMChainBitboxService) {
189 - ((wallet as EVMChainWallet).evmChainPrivateKey as EvmBitboxCredentials)
190 - .setBitbox(service.manager, (await wallet.walletInfo.getDerivationInfo()).derivationPath);
191 - } else if (service is EVMChainTrezorService) {
192 - ((wallet as EVMChainWallet).evmChainPrivateKey as EvmTrezorCredentials).setTrezorConnect(
193 - service.connect, (await wallet.walletInfo.getDerivationInfo()).derivationPath);
194 - }
195 - }
196 -
197 - @override
198 - HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection) =>
199 - EVMChainLedgerService(connection);
200 -
201 - @override
202 - HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager) =>
203 - EVMChainBitboxService(manager);
204 -
205 - @override
206 - HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorConnect connect) =>
207 - EVMChainTrezorService(connect);
208 -
209 - @override
210 - List<String> getDefaultTokenContractAddresses() {
211 - return DefaultEthereumErc20Tokens().initialErc20Tokens.map((e) => e.contractAddress).toList();
212 - }
213 -
214 - @override
215 - bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress) {
216 - final ethereumWallet = wallet as EthereumWallet;
217 - return ethereumWallet.erc20Currencies
218 - .any((element) => element.contractAddress.toLowerCase() == contractAddress.toLowerCase());
219 - }
220 -
221 - @override
222 - Future<bool> isApprovalRequired(
223 - WalletBase wallet, String tokenContract, String spender, BigInt requiredAmount) =>
224 - (wallet as EVMChainWallet).isApprovalRequired(tokenContract, spender, requiredAmount);
225 -
226 - @override
227 - Future<PendingTransaction> createTokenApproval(WalletBase wallet, BigInt amount, String spender,
228 - CryptoCurrency token, TransactionPriority priority) =>
229 - (wallet as EVMChainWallet).createApprovalTransaction(
230 - amount, spender, token, priority as EVMChainTransactionPriority, "ETH");
231 -
232 - @override
233 - Future<PendingTransaction> createRawCallDataTransaction(WalletBase wallet, String to,
234 - String dataHex, BigInt valueWei, TransactionPriority priority) =>
235 - (wallet as EVMChainWallet).createCallDataTransaction(
236 - to, dataHex, valueWei, priority as EVMChainTransactionPriority);
237 -
238 - // Integrations
239 - @override
240 - Future<BigInt> getDEuroSavingsBalance(WalletBase wallet) =>
241 - DEuro(wallet as EthereumWallet).savingsBalance;
242 -
243 - @override
244 - Future<BigInt> getDEuroAccruedInterest(WalletBase wallet) =>
245 - DEuro(wallet as EthereumWallet).accruedInterest;
246 -
247 - @override
248 - Future<BigInt> getDEuroInterestRate(WalletBase wallet) =>
249 - DEuro(wallet as EthereumWallet).interestRate;
250 -
251 - @override
252 - Future<BigInt> getDEuroSavingsApproved(WalletBase wallet) =>
253 - DEuro(wallet as EthereumWallet).approvedBalance;
254 -
255 - @override
256 - Future<PendingTransaction> addDEuroSaving(
257 - WalletBase wallet, BigInt amount, TransactionPriority priority) =>
258 - DEuro(wallet as EthereumWallet)
259 - .depositSavings(amount, priority as EVMChainTransactionPriority);
260 -
261 - @override
262 - Future<PendingTransaction> removeDEuroSaving(
263 - WalletBase wallet, BigInt amount, TransactionPriority priority) =>
264 - DEuro(wallet as EthereumWallet)
265 - .withdrawSavings(amount, priority as EVMChainTransactionPriority);
266 -
267 - @override
268 - Future<PendingTransaction> reinvestDEuroInterest(
269 - WalletBase wallet, TransactionPriority priority) =>
270 - DEuro(wallet as EthereumWallet).reinvestInterest(priority as EVMChainTransactionPriority);
271 -
272 - @override
273 - Future<PendingTransaction> enableDEuroSaving(WalletBase wallet, TransactionPriority priority) =>
274 - DEuro(wallet as EthereumWallet).enableSavings(priority as EVMChainTransactionPriority);
275 -
276 - @override
277 - String? getEthereumNativeEstimatedFee(WalletBase wallet) =>
278 - (wallet as EVMChainWallet).nativeTxEstimatedFee;
279 -
280 - @override
281 - String? getEthereumERC20EstimatedFee(WalletBase wallet) =>
282 - (wallet as EVMChainWallet).erc20TxEstimatedFee;
283 -}
lib/evm/cw_evm.dart new
+516
@@ -0,0 +1,516 @@
1 +part of 'evm.dart';
2 +
3 +class CWEVM extends EVM {
4 + @override
5 + List<String> getEVMWordList(String language) => EVMChainMnemonics.englishWordlist;
6 +
7 + @override
8 + WalletService createEVMWalletService(WalletType walletType, bool isDirect) {
9 + return EVMChainWalletService(isDirect);
10 + }
11 +
12 + @override
13 + WalletCredentials createEVMNewWalletCredentials({
14 + required String name,
15 + WalletInfo? walletInfo,
16 + String? password,
17 + String? mnemonic,
18 + String? passphrase,
19 + }) {
20 + return EVMChainNewWalletCredentials(
21 + name: name,
22 + walletInfo: walletInfo,
23 + password: password,
24 + mnemonic: mnemonic,
25 + passphrase: passphrase,
26 + );
27 + }
28 +
29 + @override
30 + WalletCredentials createEVMRestoreWalletFromSeedCredentials({
31 + required String name,
32 + required String mnemonic,
33 + required String password,
34 + String? passphrase,
35 + }) {
36 + return EVMChainRestoreWalletFromSeedCredentials(
37 + name: name,
38 + password: password,
39 + mnemonic: mnemonic,
40 + passphrase: passphrase,
41 + );
42 + }
43 +
44 + @override
45 + WalletCredentials createEVMRestoreWalletFromPrivateKey({
46 + required String name,
47 + required String privateKey,
48 + required String password,
49 + }) {
50 + return EVMChainRestoreWalletFromPrivateKey(
51 + name: name,
52 + password: password,
53 + privateKey: privateKey,
54 + );
55 + }
56 +
57 + @override
58 + WalletCredentials createEVMHardwareWalletCredentials({
59 + required String name,
60 + required HardwareAccountData hwAccountData,
61 + WalletInfo? walletInfo,
62 + }) {
63 + return EVMChainRestoreWalletFromHardware(
64 + name: name,
65 + hwAccountData: hwAccountData,
66 + walletInfo: walletInfo,
67 + );
68 + }
69 +
70 + @override
71 + String getAddress(WalletBase wallet) => (wallet as EVMChainWallet).walletAddresses.address;
72 +
73 + @override
74 + String getPrivateKey(WalletBase wallet) {
75 + final privateKeyHolder = (wallet as EVMChainWallet).evmChainPrivateKey;
76 + if (privateKeyHolder is EthPrivateKey) {
77 + return bytesToHex(privateKeyHolder.privateKey);
78 + }
79 + return "";
80 + }
81 +
82 + @override
83 + String getPublicKey(WalletBase wallet) {
84 + final privateKeyInUnitInt = (wallet as EVMChainWallet).evmChainPrivateKey;
85 + return privateKeyInUnitInt.address.hex;
86 + }
87 +
88 + @override
89 + TransactionPriority getDefaultTransactionPriority() => EVMChainTransactionPriority.medium;
90 +
91 + @override
92 + TransactionPriority getEVMTransactionPrioritySlow() => EVMChainTransactionPriority.slow;
93 +
94 + @override
95 + List<TransactionPriority> getTransactionPriorities() => EVMChainTransactionPriority.all;
96 +
97 + @override
98 + TransactionPriority deserializeEVMTransactionPriority(int raw) =>
99 + EVMChainTransactionPriority.deserialize(raw: raw);
100 +
101 + @override
102 + Object createEVMTransactionCredentials(
103 + List<Output> outputs, {
104 + required CryptoCurrency currency,
105 + TransactionPriority? priority,
106 + int? feeRate,
107 + bool useBlinkProtection = true,
108 + }) {
109 + return EVMChainTransactionCredentials(
110 + outputs
111 + .map((out) => OutputInfo(
112 + fiatAmount: out.fiatAmount,
113 + cryptoAmount: out.cryptoAmount,
114 + address: out.address,
115 + note: out.note,
116 + sendAll: out.sendAll,
117 + extractedAddress: out.extractedAddress,
118 + isParsedAddress: out.isParsedAddress,
119 + formattedCryptoAmount: out.formattedCryptoAmount,
120 + memo: out.memo))
121 + .toList(),
122 + priority: priority as EVMChainTransactionPriority?,
123 + currency: currency,
124 + feeRate: feeRate,
125 + useBlinkProtection: useBlinkProtection,
126 + );
127 + }
128 +
129 + @override
130 + Object createEVMTransactionCredentialsRaw(
131 + List<OutputInfo> outputs, {
132 + TransactionPriority? priority,
133 + required CryptoCurrency currency,
134 + required int feeRate,
135 + bool useBlinkProtection = true,
136 + }) {
137 + return EVMChainTransactionCredentials(
138 + outputs,
139 + priority: priority as EVMChainTransactionPriority?,
140 + currency: currency,
141 + feeRate: feeRate,
142 + useBlinkProtection: useBlinkProtection,
143 + );
144 + }
145 +
146 + @override
147 + int formatterEVMParseAmount(String amount) => EVMChainFormatter.parseEVMChainAmount(amount);
148 +
149 + @override
150 + double formatterEVMAmountToDouble({
151 + TransactionInfo? transaction,
152 + BigInt? amount,
153 + int exponent = 18,
154 + }) {
155 + assert(transaction != null || amount != null);
156 +
157 + if (transaction != null) {
158 + transaction as EVMChainTransactionInfo;
159 + return transaction.ethAmount / BigInt.from(10).pow(transaction.exponent);
160 + } else {
161 + return (amount!) / BigInt.from(10).pow(exponent);
162 + }
163 + }
164 +
165 + @override
166 + List<Erc20Token> getERC20Currencies(WalletBase wallet) =>
167 + (wallet as EVMChainWallet).erc20Currencies;
168 +
169 + @override
170 + Future<void> addErc20Token(WalletBase wallet, CryptoCurrency token) =>
171 + (wallet as EVMChainWallet).addErc20Token(token as Erc20Token);
172 +
173 + @override
174 + Future<void> deleteErc20Token(WalletBase wallet, CryptoCurrency token) =>
175 + (wallet as EVMChainWallet).deleteErc20Token(token as Erc20Token);
176 +
177 + @override
178 + Future<void> removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token) =>
179 + (wallet as EVMChainWallet).removeTokenTransactionsInHistory(token as Erc20Token);
180 +
181 + @override
182 + Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress) {
183 + final evmWallet = wallet as EVMChainWallet;
184 + final chainName = EVMChainUtils.getDefaultTokenSymbol(evmWallet.selectedChainId).toLowerCase();
185 + return evmWallet.getErc20Token(contractAddress, chainName);
186 + }
187 +
188 + @override
189 + CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) {
190 + transaction as EVMChainTransactionInfo;
191 + final evmWallet = wallet as EVMChainWallet;
192 +
193 + final nativeCurrency = evmWallet.currency;
194 + final nativeCurrencyTitle = nativeCurrency.title;
195 + final currentChainId = evmWallet.selectedChainId;
196 +
197 + // If transaction is from a different chain, we will return native currency as fallback
198 + // This can happen during chain switching when old transactions are still visible
199 + if (transaction.chainId != currentChainId) {
200 + return nativeCurrency;
201 + }
202 +
203 + if (transaction.tokenSymbol == CryptoCurrency.maticpoly.title ||
204 + transaction.tokenSymbol == "MATIC") {
205 + return CryptoCurrency.maticpoly;
206 + }
207 +
208 + if (transaction.tokenSymbol == nativeCurrencyTitle) {
209 + return nativeCurrency;
210 + }
211 +
212 + // Otherwise, it's an ERC20 token
213 + // Also using firstWhereOrNull to handle cases where token isn't found (e.g., during chain switch)
214 + final erc20Token = evmWallet.erc20Currencies.firstWhereOrNull(
215 + (element) =>
216 + transaction.contractAddress?.toLowerCase() == element.contractAddress.toLowerCase(),
217 + );
218 +
219 + return erc20Token ?? nativeCurrency;
220 + }
221 +
222 + @override
223 + void updateScanProviderUsageState(WalletBase wallet, bool isEnabled) =>
224 + (wallet as EVMChainWallet).updateScanProviderUsageState(isEnabled);
225 +
226 + @override
227 + Web3Client? getWeb3Client(WalletBase wallet) => (wallet as EVMChainWallet).getWeb3Client();
228 +
229 + @override
230 + String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress;
231 +
232 + @override
233 + Future<bool> isApprovalRequired(
234 + WalletBase wallet,
235 + String tokenContract,
236 + String spender,
237 + BigInt requiredAmount,
238 + ) =>
239 + (wallet as EVMChainWallet).isApprovalRequired(tokenContract, spender, requiredAmount);
240 +
241 + @override
242 + Future<PendingTransaction> createTokenApproval(
243 + WalletBase wallet,
244 + BigInt amount,
245 + String spender,
246 + CryptoCurrency token,
247 + TransactionPriority? priority, {
248 + bool useBlinkProtection = true,
249 + }) {
250 + final evmWallet = wallet as EVMChainWallet;
251 + final feeCurrency = EVMChainUtils.getFeeCurrency(evmWallet.selectedChainId);
252 + return evmWallet.createApprovalTransaction(
253 + amount,
254 + spender,
255 + token,
256 + priority as EVMChainTransactionPriority?,
257 + feeCurrency,
258 + useBlinkProtection: useBlinkProtection,
259 + );
260 + }
261 +
262 + @override
263 + Future<PendingTransaction> createRawCallDataTransaction(
264 + WalletBase wallet,
265 + String to,
266 + String dataHex,
267 + BigInt valueWei,
268 + TransactionPriority? priority, {
269 + bool useBlinkProtection = true,
270 + }) =>
271 + (wallet as EVMChainWallet).createCallDataTransaction(
272 + to,
273 + dataHex,
274 + valueWei,
275 + priority as EVMChainTransactionPriority?,
276 + useBlinkProtection: useBlinkProtection,
277 + );
278 +
279 + @override
280 + Future<void> setHardwareWalletService(
281 + WalletBase wallet,
282 + HardwareWalletService service,
283 + ) async {
284 + final evmWallet = wallet as EVMChainWallet;
285 + final privateKey = evmWallet.evmChainPrivateKey;
286 + final derivationPath = (await wallet.walletInfo.getDerivationInfo()).derivationPath;
287 +
288 + if (service is EVMChainLedgerService) {
289 + (privateKey as EvmLedgerCredentials)
290 + .setLedgerConnection(service.ledgerConnection, derivationPath);
291 + } else if (service is EVMChainBitboxService) {
292 + (privateKey as EvmBitboxCredentials).setBitbox(service.manager, derivationPath);
293 + } else if (service is EVMChainTrezorService) {
294 + (privateKey as EvmTrezorCredentials).setTrezorConnect(service.connect, derivationPath);
295 + }
296 + }
297 +
298 + @override
299 + HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection) =>
300 + EVMChainLedgerService(connection);
301 +
302 + @override
303 + HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager) =>
304 + EVMChainBitboxService(manager);
305 +
306 + @override
307 + HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorConnect connect) =>
308 + EVMChainTrezorService(connect);
309 +
310 + @override
311 + List<String> getDefaultTokenContractAddresses(WalletBase wallet) {
312 + final chainId = getSelectedChainId(wallet);
313 + if (chainId == null) return [];
314 + return EVMChainDefaultTokens.getDefaultTokenAddresses(chainId);
315 + }
316 +
317 + @override
318 + bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress) {
319 + final evmWallet = wallet as EVMChainWallet;
320 + return evmWallet.erc20Currencies
321 + .any((element) => element.contractAddress.toLowerCase() == contractAddress.toLowerCase());
322 + }
323 +
324 + @override
325 + String? getEVMNativeEstimatedFee(WalletBase wallet) =>
326 + (wallet as EVMChainWallet).nativeTxEstimatedFee;
327 +
328 + @override
329 + String? getEVMERC20EstimatedFee(WalletBase wallet) =>
330 + (wallet as EVMChainWallet).erc20TxEstimatedFee;
331 +
332 + // Chain-specific integrations (only for Ethereum)
333 + @override
334 + Future<BigInt>? getDEuroSavingsBalance(WalletBase wallet) {
335 + if (wallet.chainId == 1 && wallet is EVMChainWallet) {
336 + return DEuro(wallet).savingsBalance;
337 + }
338 + return null;
339 + }
340 +
341 + @override
342 + Future<BigInt>? getDEuroAccruedInterest(WalletBase wallet) {
343 + if (wallet.chainId == 1 && wallet is EVMChainWallet) {
344 + return DEuro(wallet).accruedInterest;
345 + }
346 + return null;
347 + }
348 +
349 + @override
350 + Future<BigInt>? getDEuroInterestRate(WalletBase wallet) {
351 + if (wallet.chainId == 1 && wallet is EVMChainWallet) {
352 + return DEuro(wallet).interestRate;
353 + }
354 + return null;
355 + }
356 +
357 + @override
358 + Future<BigInt>? getDEuroSavingsApproved(WalletBase wallet) {
359 + if (wallet.chainId == 1 && wallet is EVMChainWallet) {
360 + return DEuro(wallet).approvedBalance;
361 + }
362 + return null;
363 + }
364 +
365 + @override
366 + Future<PendingTransaction>? addDEuroSaving(
367 + WalletBase wallet, BigInt amount, TransactionPriority priority) {
368 + if (wallet.chainId == 1 && wallet is EVMChainWallet) {
369 + return DEuro(wallet).depositSavings(amount, priority as EVMChainTransactionPriority);
370 + }
371 + return null;
372 + }
373 +
374 + @override
375 + Future<PendingTransaction>? removeDEuroSaving(
376 + WalletBase wallet, BigInt amount, TransactionPriority priority) {
377 + if (wallet.chainId == 1 && wallet is EVMChainWallet) {
378 + return DEuro(wallet).withdrawSavings(amount, priority as EVMChainTransactionPriority);
379 + }
380 + return null;
381 + }
382 +
383 + @override
384 + Future<PendingTransaction>? reinvestDEuroInterest(
385 + WalletBase wallet, TransactionPriority priority) {
386 + if (wallet.chainId == 1 && wallet is EVMChainWallet) {
387 + return DEuro(wallet).reinvestInterest(priority as EVMChainTransactionPriority);
388 + }
389 + return null;
390 + }
391 +
392 + @override
393 + Future<PendingTransaction>? enableDEuroSaving(WalletBase wallet, TransactionPriority priority) {
394 + if (wallet.chainId == 1 && wallet is EVMChainWallet) {
395 + return DEuro(wallet).enableSavings(priority as EVMChainTransactionPriority);
396 + }
397 + return null;
398 + }
399 +
400 + // Registry helper methods
401 + static final EvmChainRegistry _registry = EvmChainRegistry();
402 +
403 + @override
404 + int getChainIdByWalletType(WalletType walletType) {
405 + final config = _registry.getChainConfigByWalletType(walletType);
406 + return config?.chainId ?? 1; // Default to Ethereum
407 + }
408 +
409 + @override
410 + String getChainNameByWalletType(WalletType walletType) {
411 + final config = _registry.getChainConfigByWalletType(walletType);
412 + return config?.shortCode ?? 'eth';
413 + }
414 +
415 + @override
416 + String getTokenNameByWalletType(WalletType walletType) {
417 + final config = _registry.getChainConfigByWalletType(walletType);
418 + return config?.nativeCurrency.title ?? 'ETH';
419 + }
420 +
421 + @override
422 + String getCaip2ByChainId(int chainId) {
423 + final config = _registry.getChainConfig(chainId);
424 + return config?.caip2 ?? 'eip155:1';
425 + }
426 +
427 + @override
428 + String getChainNameByChainId(int chainId) {
429 + final config = _registry.getChainConfig(chainId);
430 + return config?.shortCode ?? 'eth';
431 + }
432 +
433 + @override
434 + String getTokenNameByChainId(int chainId) {
435 + final config = _registry.getChainConfig(chainId);
436 + return config?.nativeCurrency.title ?? 'ETH';
437 + }
438 +
439 + @override
440 + int? getChainIdByTag(String tag) {
441 + final config = _registry.getChainConfigByTag(tag);
442 + return config?.chainId;
443 + }
444 +
445 + @override
446 + int? getChainIdByTitle(String title) {
447 + // Try as tag first (uppercase)
448 + final tagResult = getChainIdByTag(title.toUpperCase());
449 + if (tagResult != null) return tagResult;
450 +
451 + // Try as lowercase title
452 + return getChainIdByTag(title.toLowerCase());
453 + }
454 +
455 + @override
456 + WalletType? getWalletTypeByChainId(int chainId) {
457 + return _registry.getWalletTypeByChainId(chainId);
458 + }
459 +
460 + @override
461 + List<ChainInfo> getAllChains() {
462 + final allChains = _registry.getAllChains();
463 + return allChains
464 + .map((config) => ChainInfo(
465 + chainId: config.chainId,
466 + name: config.name,
467 + shortCode: config.shortCode,
468 + ))
469 + .toList();
470 + }
471 +
472 + @override
473 + ChainInfo? getCurrentChain(WalletBase wallet) {
474 + if (wallet is EVMChainWallet) {
475 + final config = wallet.selectedChainConfig;
476 + if (config == null) return null;
477 + return ChainInfo(
478 + chainId: config.chainId,
479 + name: config.name,
480 + shortCode: config.shortCode,
481 + );
482 + }
483 + return null;
484 + }
485 +
486 + @override
487 + int? getSelectedChainId(WalletBase wallet) {
488 + if (wallet is EVMChainWallet) {
489 + return wallet.selectedChainId;
490 + }
491 + return null;
492 + }
493 +
494 + @override
495 + Future<void> selectChain(WalletBase wallet, int chainId, {required Node node}) async {
496 + if (wallet is EVMChainWallet) {
497 + await wallet.selectChain(chainId, node: node);
498 + }
499 + }
500 +
501 + @override
502 + String? getExplorerUrlForChainId(int chainId, {bool showProtocol = true}) {
503 + final config = _registry.getChainConfig(chainId);
504 +
505 + if (config != null && config.explorerUrls.isNotEmpty) {
506 + final url = config.explorerUrls.first;
507 + return showProtocol
508 + ? url
509 + : url.replaceAll('https://', '').replaceAll('http://', '').split('/')[0];
510 + }
511 + return null;
512 + }
513 +
514 + @override
515 + bool hasPriorityFee(int chainId) => EVMChainUtils.hasPriorityFee(chainId);
516 +}
lib/exchange/provider/chainflip_exchange_provider.dart
+1 -1
@@ -29,7 +29,7 @@ class ChainflipExchangeProvider extends ExchangeProvider {
29 CryptoCurrency.sol,
30 CryptoCurrency.usdcsol,
31 CryptoCurrency.arbEth,
32 - // TODO: Add CryptoCurrency.usdcarb
32 + CryptoCurrency.usdcArb,
33 // TODO: Add CryptoCurrency.dot
34 ];
35
lib/exchange/provider/changenow_exchange_provider.dart
+2
@@ -353,6 +353,8 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
353 return 'lightning';
354 case 'AVAXC':
355 return 'cchain';
356 + case 'ARB':
357 + return 'arbitrum';
358 default:
359 return tag.toLowerCase();
360 }
lib/exchange/provider/exolix_exchange_provider.dart
+2
@@ -443,6 +443,8 @@ class ExolixExchangeProvider extends ExchangeProvider {
443 switch (tag) {
444 case 'POLY':
445 return 'Polygon';
446 + case 'ARB':
447 + return 'Arbitrum';
448 default:
449 return tag;
450 }
lib/exchange/provider/letsexchange_exchange_provider.dart
+2
@@ -433,6 +433,8 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
433 return 'ERC20';
434 case 'BSC':
435 return 'BEP20';
436 + case 'ARB':
437 + return 'ARBITRUM';
438 default:
439 return currency.tag!;
440 }
lib/exchange/provider/stealth_ex_exchange_provider.dart
+5 -14
@@ -346,15 +346,13 @@ class StealthExExchangeProvider extends ExchangeProvider {
346 // Parsing 'from' currency with network tag
347 final fromCurrency = deposit['symbol'] as String;
348 final fromNetwork = deposit['network'] as String?;
349 - final _normalizedFromNetwork = _normalizeNetworkType(fromNetwork ?? '');
350 - final fromTag = _normalizedFromNetwork == 'mainnet' ? null : fromNetwork;
349 + final fromTag = fromNetwork == 'mainnet' ? null : fromNetwork;
350 final from = CryptoCurrency.safeParseCurrencyFromString(fromCurrency, tag: fromTag);
351
352 // Parsing 'to' currency with network tag
353 final toCurrency = withdrawal['symbol'] as String;
354 final toNetwork = withdrawal['network'] as String?;
356 - final _normalizedToNetwork = _normalizeNetworkType(toNetwork ?? '');
357 - final toTag = _normalizedToNetwork == 'mainnet' ? null : toNetwork;
355 + final toTag = toNetwork == 'mainnet' ? null : toNetwork;
356 final to = CryptoCurrency.safeParseCurrencyFromString(toCurrency, tag: toTag);
357
358 final payoutAddress = withdrawal['address'] as String;
@@ -381,7 +379,8 @@ class StealthExExchangeProvider extends ExchangeProvider {
379 createdAt: createdAt,
380 isRefund: status == 'refunded',
381 extraId: extraId,
384 - userCurrencyFromRaw: '${fromCurrency.toUpperCase()}' + '_' + '${fromTag?.toUpperCase() ?? ''}',
382 + userCurrencyFromRaw:
383 + '${fromCurrency.toUpperCase()}' + '_' + '${fromTag?.toUpperCase() ?? ''}',
384 userCurrencyToRaw: '${toCurrency.toUpperCase()}' + '_' + '${toTag?.toUpperCase() ?? ''}',
385 );
386 }
@@ -436,21 +435,13 @@ class StealthExExchangeProvider extends ExchangeProvider {
435 return null;
436 }
437
439 -
438 String _getName(CryptoCurrency currency) {
439 if (currency == CryptoCurrency.usdcEPoly) return 'usdce';
440 return currency.title.toLowerCase();
441 }
442
445 - String _normalizeNetworkType(String network) {
446 - return switch (network.toUpperCase()) {
447 - 'ARBITRUM' => 'mainnet',
448 - _ => network,
449 - };
450 - }
451 -
443 String _getNetwork(CryptoCurrency currency) {
453 - if (currency == CryptoCurrency.arb) return 'arbitrum';
444 + if (currency == CryptoCurrency.arb || currency.tag == 'ARB') return 'arbitrum';
445 if (currency.tag == null) return 'mainnet';
446
447 if (currency == CryptoCurrency.maticpoly) return 'mainnet';
lib/exchange/provider/swapsxyz_exchange_provider.dart
+1
@@ -751,6 +751,7 @@ class SwapsXyzExchangeProvider extends ExchangeProvider {
751 'KAS' => 'KASPA',
752 'TON' => 'TONCOIN',
753 'BCH' => 'BITCOIN CASH',
754 + 'ARB' => 'ARBITRUM',
755 _ => network.toUpperCase(),
756 };
757 }
lib/exchange/provider/trocador_exchange_provider.dart
+30 -24
@@ -64,7 +64,6 @@ class TrocadorExchangeProvider extends ExchangeProvider {
64 static const coinPath = '/coin';
65 static const providersListPath = '/exchanges';
66
67 -
67 String _lastUsedRateId;
68 List<dynamic> _provider;
69
@@ -101,7 +100,6 @@ class TrocadorExchangeProvider extends ExchangeProvider {
100
101 final uri = await _getUri(coinPath, params);
102 final response = await ProxyWrapper().get(clearnetUri: uri, headers: {'API-Key': apiKey});
104 -
103
104 if (response.statusCode != 200)
105 throw Exception('Unexpected http status: ${response.statusCode}');
@@ -119,13 +117,12 @@ class TrocadorExchangeProvider extends ExchangeProvider {
117 }
118
119 @override
122 - Future<double> fetchRate({
123 - required CryptoCurrency from,
124 - required CryptoCurrency to,
125 - required double amount,
126 - required bool isFixedRateMode,
127 - required bool isReceiveAmount
128 - }) async {
120 + Future<double> fetchRate(
121 + {required CryptoCurrency from,
122 + required CryptoCurrency to,
123 + required double amount,
124 + required bool isFixedRateMode,
125 + required bool isReceiveAmount}) async {
126 try {
127 if (amount == 0) return 0.0;
128
@@ -143,7 +140,6 @@ class TrocadorExchangeProvider extends ExchangeProvider {
140
141 final uri = await _getUri(newRatePath, params);
142 final response = await ProxyWrapper().get(clearnetUri: uri, headers: {'API-Key': apiKey});
146 -
143
144 final responseJSON = json.decode(response.body) as Map<String, dynamic>;
145 final fromAmount = double.parse(responseJSON['amount_from'].toString());
@@ -240,7 +236,7 @@ class TrocadorExchangeProvider extends ExchangeProvider {
236 if (isFixedRateMode) 'amount_to': request.toAmount,
237 'address': request.toAddress,
238 'refund': request.refundAddress,
243 - 'refund_memo' : '0',
239 + 'refund_memo': '0',
240 };
241
242 if (isFixedRateMode) {
@@ -279,13 +275,12 @@ class TrocadorExchangeProvider extends ExchangeProvider {
275
276 final uri = await _getUri(createTradePath, params);
277 final response = await ProxyWrapper().get(clearnetUri: uri, headers: {'API-Key': apiKey});
282 -
283 -
278 +
279 if (response.statusCode == 400) {
280 final responseJSON = json.decode(response.body) as Map<String, dynamic>;
281 final error = responseJSON['error'] as String;
282 final message = responseJSON['message'] as String;
288 -
283 +
284 ExchangeProviderLogger.logError(
285 provider: description,
286 function: 'createTrade',
@@ -304,7 +299,7 @@ class TrocadorExchangeProvider extends ExchangeProvider {
299 'url': uri.toString(),
300 },
301 );
307 -
302 +
303 throw Exception('${error}\n$message');
304 }
305
@@ -402,10 +397,10 @@ class TrocadorExchangeProvider extends ExchangeProvider {
397 @override
398 Future<Trade> findTradeById({required String id}) async {
399 final uri = await _getUri(tradePath, {'id': id});
405 - return ProxyWrapper().get(clearnetUri: uri, headers: {'API-Key': apiKey}).then((response) async {
400 + return ProxyWrapper()
401 + .get(clearnetUri: uri, headers: {'API-Key': apiKey}).then((response) async {
402 if (response.statusCode != 200)
403 throw Exception('Unexpected http status: ${response.statusCode}');
408 -
404
405 final responseListJson = json.decode(response.body) as List;
406 final responseJSON = responseListJson.first;
@@ -422,15 +417,22 @@ class TrocadorExchangeProvider extends ExchangeProvider {
417 final fromCurrency = responseJSON['ticker_from'] as String;
418 final fromNetwork = responseJSON['network_from'] as String?;
419 final _normalizedFromNetwork = _normalizeNetworkType(fromNetwork ?? '');
425 - final fromTag = _normalizedFromNetwork.isEmpty || _normalizedFromNetwork == fromCurrency.toUpperCase() || _normalizedFromNetwork == 'Mainnet'
426 - ? null : _normalizedFromNetwork;
420 + final fromTag = _normalizedFromNetwork.isEmpty ||
421 + _normalizedFromNetwork == fromCurrency.toUpperCase() ||
422 + _normalizedFromNetwork == 'Mainnet'
423 + ? null
424 + : _normalizedFromNetwork;
425
426 final from = CryptoCurrency.safeParseCurrencyFromString(fromCurrency, tag: fromTag);
427
428 final toCurrency = responseJSON['ticker_to'] as String;
429 final networkTo = responseJSON['network_to'] as String?;
430 final _normalizedToNetwork = _normalizeNetworkType(networkTo ?? '');
433 - final toTag = _normalizedToNetwork.isEmpty || _normalizedToNetwork == toCurrency.toUpperCase() || _normalizedFromNetwork == 'Mainnet' ? null : _normalizedToNetwork;
431 + final toTag = _normalizedToNetwork.isEmpty ||
432 + _normalizedToNetwork == toCurrency.toUpperCase() ||
433 + _normalizedFromNetwork == 'Mainnet'
434 + ? null
435 + : _normalizedToNetwork;
436 final to = CryptoCurrency.safeParseCurrencyFromString(toCurrency, tag: toTag);
437
438 return Trade(
@@ -448,7 +450,8 @@ class TrocadorExchangeProvider extends ExchangeProvider {
450 providerId: providerId,
451 providerName: providerName,
452 extraId: addressProviderMemo,
451 - userCurrencyFromRaw: '${fromCurrency.toUpperCase()}' + '_' + '${fromTag?.toUpperCase() ?? ''}',
453 + userCurrencyFromRaw:
454 + '${fromCurrency.toUpperCase()}' + '_' + '${fromTag?.toUpperCase() ?? ''}',
455 userCurrencyToRaw: '${toCurrency.toUpperCase()}' + '_' + '${toTag?.toUpperCase() ?? ''}',
456 );
457 });
@@ -457,7 +460,6 @@ class TrocadorExchangeProvider extends ExchangeProvider {
460 Future<List<TrocadorPartners>> fetchProviders() async {
461 final uri = await _getUri(providersListPath, {'api_key': apiKey});
462 final response = await ProxyWrapper().get(clearnetUri: uri);
460 -
463
464 if (response.statusCode != 200)
465 throw Exception('Unexpected http status: ${response.statusCode}');
@@ -485,6 +487,8 @@ class TrocadorExchangeProvider extends ExchangeProvider {
487 return 'MATIC';
488 case CryptoCurrency.zec:
489 return 'Mainnet';
490 + case CryptoCurrency.arb:
491 + return 'Mainnet';
492 default:
493 return currency.tag != null ? _normalizeTag(currency.tag!) : 'Mainnet';
494 }
@@ -502,6 +506,10 @@ class TrocadorExchangeProvider extends ExchangeProvider {
506 }
507
508 String _normalizeTag(String tag) {
509 + if (tag.contains('ARB')) {
510 + return 'Arbitrum';
511 + }
512 +
513 switch (tag) {
514 case 'ETH':
515 return 'ERC20';
@@ -527,8 +535,6 @@ class TrocadorExchangeProvider extends ExchangeProvider {
535 };
536 }
537
530 -
531 -
538 Future<Uri> _getUri(String path, Map<String, String> queryParams) async {
539 final uri = Uri.http(onionApiAuthority, path, queryParams);
540
lib/exchange/provider/xoswap_exchange_provider.dart
+2
@@ -44,6 +44,7 @@ class XOSwapExchangeProvider extends ExchangeProvider {
44 'EOS': 'eosio',
45 'XLM': 'stellar',
46 'BASE': 'basemainnet',
47 + 'ARB': 'arbitrum',
48 };
49
50 static const supportedTags = [
@@ -64,6 +65,7 @@ class XOSwapExchangeProvider extends ExchangeProvider {
65 'EOS',
66 'XLM',
67 'BASE',
68 + 'ARB',
69 ];
70
71
lib/exchange/trade.dart
+26 -5
@@ -1,3 +1,5 @@
1 +import 'package:cake_wallet/entities/generate_name.dart';
2 +import 'package:cake_wallet/evm/evm.dart';
3 import 'package:cake_wallet/exchange/exchange_provider_description.dart';
4 import 'package:cake_wallet/exchange/trade_state.dart';
5 import 'package:cw_core/crypto_currency.dart';
@@ -41,12 +43,13 @@ class Trade extends HiveObject {
43 this.routerValue,
44 this.routerChainId,
45 this.sourceTokenAmountRaw,
44 - this.requiresTokenApproval
46 + this.requiresTokenApproval,
47 + this.chainId,
48 }) {
49 if (provider != null) providerRaw = provider.raw;
50
51 fromRaw = from?.raw ?? -1;
49 - toRaw = to?.raw ?? -1;
52 + toRaw = to?.raw ?? -1;
53
54 if (state != null) stateRaw = state.raw;
55 }
@@ -128,7 +131,9 @@ class Trade extends HiveObject {
131 bool? isRefund;
132
133 @HiveField(21)
131 - bool? isSendAll; /// Must be set on createTrade;
134 + bool? isSendAll;
135 +
136 + /// Must be set on createTrade;
137
138 @HiveField(22)
139 String? router;
@@ -167,13 +172,18 @@ class Trade extends HiveObject {
172 @HiveField(33, defaultValue: false)
173 bool? requiresTokenApproval;
174
175 + @HiveField(34)
176 + int? chainId;
177 +
178 CryptoCurrency? get userCurrencyFrom {
179 if (userCurrencyFromRaw == null || userCurrencyFromRaw!.isEmpty) {
180 return null;
181 }
182 final underscoreIndex = userCurrencyFromRaw!.indexOf('_');
183 final title = userCurrencyFromRaw!.substring(0, underscoreIndex);
176 - final tag = userCurrencyFromRaw!.substring(underscoreIndex + 1);
184 + String tag = userCurrencyFromRaw!.substring(underscoreIndex + 1);
185 +
186 + if (tag.contains('ARB')) tag = 'ARB';
187
188 return CryptoCurrency(
189 title: title,
@@ -201,6 +211,12 @@ class Trade extends HiveObject {
211 );
212 }
213
214 + String get chainName {
215 + if (chainId == null) return '';
216 +
217 + return evm!.getChainNameByChainId(chainId!).capitalized();
218 + }
219 +
220 static Trade fromMap(Map<String, Object?> map) {
221 return Trade(
222 id: map['id'] as String,
@@ -219,6 +235,7 @@ class Trade extends HiveObject {
235 isSendAll: map['isSendAll'] as bool?,
236 router: map['router'] as String?,
237 extraId: map['extra_id'] as String?,
238 + chainId: map['chain_id'] as int?,
239 );
240 }
241
@@ -239,6 +256,7 @@ class Trade extends HiveObject {
256 'isSendAll': isSendAll,
257 'router': router,
258 'extra_id': extraId,
259 + 'chain_id': chainId,
260 };
261 }
262
@@ -291,6 +309,7 @@ class TradeAdapter extends TypeAdapter<Trade> {
309 routerChainId: fields[31] as int?,
310 sourceTokenAmountRaw: fields[32] as String?,
311 requiresTokenApproval: fields[33] as bool?,
312 + chainId: fields[34] as int?,
313 )
314 ..providerRaw = fields[1] == null ? 0 : fields[1] as int
315 ..fromRaw = (fields[2] as int?) ?? -1
@@ -369,7 +388,9 @@ class TradeAdapter extends TypeAdapter<Trade> {
388 ..writeByte(32)
389 ..write(obj.sourceTokenAmountRaw)
390 ..writeByte(33)
372 - ..write(obj.requiresTokenApproval);
391 + ..write(obj.requiresTokenApproval)
392 + ..writeByte(34)
393 + ..write(obj.chainId);
394 }
395
396 @override
lib/polygon/cw_polygon.dart deleted
-263
@@ -1,263 +0,0 @@
1 -part of 'polygon.dart';
2 -
3 -class CWPolygon extends Polygon {
4 - @override
5 - List<String> getPolygonWordList(String language) => EVMChainMnemonics.englishWordlist;
6 -
7 - WalletService createPolygonWalletService(bool isDirect) =>
8 - PolygonWalletService(isDirect, client: PolygonClient());
9 -
10 - @override
11 - WalletCredentials createPolygonNewWalletCredentials({
12 - required String name,
13 - String? mnemonic,
14 - WalletInfo? walletInfo,
15 - String? password,
16 - String? passphrase,
17 - }) =>
18 - EVMChainNewWalletCredentials(
19 - name: name,
20 - walletInfo: walletInfo,
21 - password: password,
22 - mnemonic: mnemonic,
23 - passphrase: passphrase,
24 - );
25 -
26 - @override
27 - WalletCredentials createPolygonRestoreWalletFromSeedCredentials({
28 - required String name,
29 - required String mnemonic,
30 - required String password,
31 - String? passphrase,
32 - }) =>
33 - EVMChainRestoreWalletFromSeedCredentials(
34 - name: name,
35 - password: password,
36 - mnemonic: mnemonic,
37 - passphrase: passphrase,
38 - );
39 -
40 - @override
41 - WalletCredentials createPolygonRestoreWalletFromPrivateKey({
42 - required String name,
43 - required String privateKey,
44 - required String password,
45 - }) =>
46 - EVMChainRestoreWalletFromPrivateKey(name: name, password: password, privateKey: privateKey);
47 -
48 - @override
49 - WalletCredentials createPolygonHardwareWalletCredentials({
50 - required String name,
51 - required HardwareAccountData hwAccountData,
52 - WalletInfo? walletInfo,
53 - }) =>
54 - EVMChainRestoreWalletFromHardware(
55 - name: name,
56 - hwAccountData: hwAccountData,
57 - walletInfo: walletInfo,
58 - );
59 -
60 - @override
61 - String getAddress(WalletBase wallet) => (wallet as PolygonWallet).walletAddresses.address;
62 -
63 - @override
64 - String getPrivateKey(WalletBase wallet) {
65 - final privateKeyHolder = (wallet as PolygonWallet).evmChainPrivateKey;
66 - if (privateKeyHolder is EthPrivateKey) return bytesToHex(privateKeyHolder.privateKey);
67 - return "";
68 - }
69 -
70 - @override
71 - String getPublicKey(WalletBase wallet) {
72 - final privateKeyInUnitInt = (wallet as PolygonWallet).evmChainPrivateKey;
73 - return privateKeyInUnitInt.address.hex;
74 - }
75 -
76 - @override
77 - TransactionPriority getDefaultTransactionPriority() => EVMChainTransactionPriority.medium;
78 -
79 - @override
80 - TransactionPriority getPolygonTransactionPrioritySlow() => EVMChainTransactionPriority.slow;
81 -
82 - @override
83 - List<TransactionPriority> getTransactionPriorities() => EVMChainTransactionPriority.all;
84 -
85 - @override
86 - TransactionPriority deserializePolygonTransactionPriority(int raw) =>
87 - EVMChainTransactionPriority.deserialize(raw: raw);
88 -
89 - Object createPolygonTransactionCredentials(
90 - List<Output> outputs, {
91 - required TransactionPriority priority,
92 - required CryptoCurrency currency,
93 - int? feeRate,
94 - }) =>
95 - EVMChainTransactionCredentials(
96 - outputs
97 - .map(
98 - (out) => OutputInfo(
99 - fiatAmount: out.fiatAmount,
100 - cryptoAmount: out.cryptoAmount,
101 - address: out.address,
102 - note: out.note,
103 - sendAll: out.sendAll,
104 - extractedAddress: out.extractedAddress,
105 - isParsedAddress: out.isParsedAddress,
106 - formattedCryptoAmount: out.formattedCryptoAmount,
107 - ),
108 - )
109 - .toList(),
110 - priority: priority as EVMChainTransactionPriority,
111 - currency: currency,
112 - feeRate: feeRate,
113 - );
114 -
115 - Object createPolygonTransactionCredentialsRaw(
116 - List<OutputInfo> outputs, {
117 - TransactionPriority? priority,
118 - required CryptoCurrency currency,
119 - required int feeRate,
120 - }) =>
121 - EVMChainTransactionCredentials(
122 - outputs,
123 - priority: priority as EVMChainTransactionPriority?,
124 - currency: currency,
125 - feeRate: feeRate,
126 - );
127 -
128 - @override
129 - int formatterPolygonParseAmount(String amount) => EVMChainFormatter.parseEVMChainAmount(amount);
130 -
131 - @override
132 - double formatterPolygonAmountToDouble({
133 - TransactionInfo? transaction,
134 - BigInt? amount,
135 - int exponent = 18,
136 - }) {
137 - assert(transaction != null || amount != null);
138 -
139 - if (transaction != null) {
140 - transaction as EVMChainTransactionInfo;
141 - return transaction.ethAmount / BigInt.from(10).pow(transaction.exponent);
142 - } else {
143 - return (amount!) / BigInt.from(10).pow(exponent);
144 - }
145 - }
146 -
147 - @override
148 - List<Erc20Token> getERC20Currencies(WalletBase wallet) =>
149 - (wallet as PolygonWallet).erc20Currencies;
150 -
151 - @override
152 - Future<void> addErc20Token(WalletBase wallet, CryptoCurrency token) =>
153 - (wallet as PolygonWallet).addErc20Token(token as Erc20Token);
154 -
155 - @override
156 - Future<void> deleteErc20Token(WalletBase wallet, CryptoCurrency token) =>
157 - (wallet as PolygonWallet).deleteErc20Token(token as Erc20Token);
158 -
159 - @override
160 - Future<void> removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token) =>
161 - (wallet as PolygonWallet).removeTokenTransactionsInHistory(token as Erc20Token);
162 -
163 - @override
164 - Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress) =>
165 - (wallet as PolygonWallet).getErc20Token(contractAddress, 'polygon');
166 -
167 - @override
168 - CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) {
169 - transaction as EVMChainTransactionInfo;
170 - if (transaction.tokenSymbol == CryptoCurrency.maticpoly.title ||
171 - transaction.tokenSymbol == "MATIC") {
172 - return CryptoCurrency.maticpoly;
173 - }
174 -
175 - wallet as PolygonWallet;
176 -
177 - return wallet.erc20Currencies.firstWhere(
178 - (element) =>
179 - transaction.contractAddress?.toLowerCase() == element.contractAddress?.toLowerCase(),
180 - );
181 - }
182 -
183 - @override
184 - void updatePolygonScanUsageState(WalletBase wallet, bool isEnabled) =>
185 - (wallet as PolygonWallet).updateScanProviderUsageState(isEnabled);
186 -
187 - @override
188 - Web3Client? getWeb3Client(WalletBase wallet) => (wallet as PolygonWallet).getWeb3Client();
189 -
190 - @override
191 - String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress;
192 -
193 - Future<bool> isApprovalRequired(
194 - WalletBase wallet, String tokenContract, String spender, BigInt requiredAmount) =>
195 - (wallet as PolygonWallet).isApprovalRequired(tokenContract, spender, requiredAmount);
196 -
197 - Future<PendingTransaction> createRawCallDataTransaction(WalletBase wallet, String to,
198 - String dataHex, BigInt valueWei, TransactionPriority priority) =>
199 - (wallet as EVMChainWallet).createCallDataTransaction(
200 - to, dataHex, valueWei, priority as EVMChainTransactionPriority);
201 -
202 - @override
203 - Future<PendingTransaction> createTokenApproval(
204 - WalletBase wallet,
205 - BigInt amount,
206 - String spender,
207 - CryptoCurrency token,
208 - TransactionPriority priority,
209 - ) =>
210 - (wallet as EVMChainWallet).createApprovalTransaction(
211 - amount,
212 - spender,
213 - token,
214 - priority as EVMChainTransactionPriority,
215 - "POL",
216 - );
217 -
218 - @override
219 - Future<void> setHardwareWalletService(WalletBase wallet, HardwareWalletService service) async {
220 - if (service is EVMChainLedgerService) {
221 - ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedgerConnection(
222 - service.ledgerConnection, (await wallet.walletInfo.getDerivationInfo()).derivationPath);
223 - } else if (service is EVMChainBitboxService) {
224 - ((wallet as EVMChainWallet).evmChainPrivateKey as EvmBitboxCredentials)
225 - .setBitbox(service.manager, (await wallet.walletInfo.getDerivationInfo()).derivationPath);
226 - } else if (service is EVMChainTrezorService) {
227 - ((wallet as EVMChainWallet).evmChainPrivateKey as EvmTrezorCredentials).setTrezorConnect(
228 - service.connect, (await wallet.walletInfo.getDerivationInfo()).derivationPath);
229 - }
230 - }
231 -
232 - @override
233 - HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection) =>
234 - EVMChainLedgerService(connection);
235 -
236 - @override
237 - HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager) =>
238 - EVMChainBitboxService(manager, chainId: 137);
239 -
240 - @override
241 - HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorConnect connect) =>
242 - EVMChainTrezorService(connect, chainId: 137);
243 -
244 - @override
245 - List<String> getDefaultTokenContractAddresses() =>
246 - DefaultPolygonErc20Tokens().initialPolygonErc20Tokens.map((e) => e.contractAddress).toList();
247 -
248 - @override
249 - bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress) {
250 - final polygonWallet = wallet as PolygonWallet;
251 - return polygonWallet.erc20Currencies.any(
252 - (element) => element.contractAddress.toLowerCase() == contractAddress.toLowerCase(),
253 - );
254 - }
255 -
256 - @override
257 - String? getPolygonNativeEstimatedFee(WalletBase wallet) =>
258 - (wallet as EVMChainWallet).nativeTxEstimatedFee;
259 -
260 - @override
261 - String? getPolygonERC20EstimatedFee(WalletBase wallet) =>
262 - (wallet as EVMChainWallet).erc20TxEstimatedFee;
263 -}
lib/reactions/check_connection.dart
+11 -3
@@ -1,5 +1,6 @@
1 import 'dart:async';
2
3 +import 'package:cake_wallet/reactions/wallet_connect.dart';
4 import 'package:cake_wallet/utils/tor.dart';
5 import 'package:connectivity_plus/connectivity_plus.dart';
6 import 'package:cw_core/utils/print_verbose.dart';
@@ -7,6 +8,7 @@ import 'package:cw_core/wallet_base.dart';
8 import 'package:cw_core/sync_status.dart';
9 import 'package:cw_core/wallet_type.dart';
10 import 'package:cake_wallet/store/settings_store.dart';
11 +import 'package:cake_wallet/evm/evm.dart';
12
13 Timer? _checkConnectionTimer;
14
@@ -34,14 +36,20 @@ void startCheckConnectionReaction(WalletBase wallet, SettingsStore settingsStore
36 if (wallet.type != WalletType.bitcoin &&
37 (wallet.syncStatus is LostConnectionSyncStatus ||
38 wallet.syncStatus is FailedSyncStatus)) {
37 - final alive = await settingsStore.getCurrentNode(wallet.type).requestNode();
39 + int? chainId;
40 + if (isEVMCompatibleChain(wallet.type)) {
41 + chainId = evm!.getSelectedChainId(wallet);
42 + }
43 +
44 + final node = settingsStore.getCurrentNode(wallet.type, chainId: chainId);
45 + final alive = await node.requestNode();
46
47 if (alive) {
48 if (settingsStore.currentBuiltinTor) {
49 await ensureTorStarted(context: null);
50 }
43 -
44 - await wallet.connectToNode(node: settingsStore.getCurrentNode(wallet.type));
51 +
52 + await wallet.connectToNode(node: node);
53 }
54 }
55 } catch (e) {
lib/reactions/fiat_rate_update.dart
+4 -20
@@ -1,10 +1,8 @@
1 import 'dart:async';
2 -import 'package:cake_wallet/arbitrum/arbitrum.dart';
3 -import 'package:cake_wallet/base/base.dart';
2 import 'package:cake_wallet/core/fiat_conversion_service.dart';
3 import 'package:cake_wallet/entities/fiat_api_mode.dart';
6 -import 'package:cake_wallet/ethereum/ethereum.dart';
7 -import 'package:cake_wallet/polygon/polygon.dart';
4 +import 'package:cake_wallet/evm/evm.dart';
5 +import 'package:cake_wallet/reactions/wallet_connect.dart';
6 import 'package:cake_wallet/solana/solana.dart';
7 import 'package:cake_wallet/store/app_store.dart';
8 import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
@@ -36,23 +34,9 @@ Future<void> startFiatRateUpdate(
34 torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly);
35
36 Iterable<CryptoCurrency>? currencies;
39 - if (appStore.wallet!.type == WalletType.ethereum) {
37 + if (isEVMCompatibleChain(appStore.wallet!.type)) {
38 currencies =
41 - ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
42 - }
43 -
44 - if (appStore.wallet!.type == WalletType.polygon) {
45 - currencies =
46 - polygon!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
47 - }
48 -
49 - if (appStore.wallet!.type == WalletType.base) {
50 - currencies =
51 - base!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
52 - }
53 - if (appStore.wallet!.type == WalletType.arbitrum) {
54 - currencies =
55 - arbitrum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
39 + evm!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
40 }
41
42 if (appStore.wallet!.type == WalletType.solana) {
lib/reactions/on_current_wallet_change.dart
+11 -21
@@ -1,12 +1,10 @@
1 -import 'package:cake_wallet/arbitrum/arbitrum.dart';
2 -import 'package:cake_wallet/base/base.dart';
1 import 'package:cake_wallet/bitcoin/bitcoin.dart';
2 import 'package:cake_wallet/di.dart';
3 import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
4 import 'package:cake_wallet/entities/fiat_api_mode.dart';
5 import 'package:cake_wallet/entities/wallet_manager.dart';
8 -import 'package:cake_wallet/ethereum/ethereum.dart';
9 -import 'package:cake_wallet/polygon/polygon.dart';
6 +import 'package:cake_wallet/evm/evm.dart';
7 +import 'package:cake_wallet/reactions/wallet_connect.dart';
8 import 'package:cake_wallet/solana/solana.dart';
9 import 'package:cake_wallet/tron/tron.dart';
10 import 'package:cake_wallet/utils/tor.dart';
@@ -67,7 +65,12 @@ void startCurrentWalletChangeReaction(
65
66 await getIt.get<WalletManager>().ensureGroupHasHashedIdentifier(wallet);
67
70 - final node = settingsStore.getCurrentNode(wallet.type);
68 + int? chainId;
69 + if (isEVMCompatibleChain(wallet.type)) {
70 + chainId = evm!.getSelectedChainId(wallet);
71 + }
72 +
73 + final node = settingsStore.getCurrentNode(wallet.type, chainId: chainId);
74
75 startWalletSyncStatusChangeReaction(wallet, settingsStore);
76 startCheckConnectionReaction(wallet, settingsStore);
@@ -91,7 +94,7 @@ void startCurrentWalletChangeReaction(
94 if (settingsStore.currentBuiltinTor) {
95 await ensureTorStarted(context: null);
96 }
94 -
97 +
98 await wallet.connectToNode(node: node);
99 SyncingSyncStatus.blockHistory.clear();
100 if (wallet.type == WalletType.nano || wallet.type == WalletType.banano) {
@@ -124,21 +127,8 @@ void startCurrentWalletChangeReaction(
127 torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly);
128
129 Iterable<CryptoCurrency>? currencies;
127 - if (wallet.type == WalletType.ethereum) {
128 - currencies =
129 - ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
130 - }
131 - if (wallet.type == WalletType.polygon) {
132 - currencies =
133 - polygon!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
134 - }
135 - if (wallet.type == WalletType.base) {
136 - currencies =
137 - base!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
138 - }
139 - if (wallet.type == WalletType.arbitrum) {
140 - currencies =
141 - arbitrum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
130 + if (isEVMCompatibleChain(wallet.type)) {
131 + currencies = evm!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
132 }
133 if (wallet.type == WalletType.solana) {
134 currencies =
lib/reactions/wallet_connect.dart
+44 -48
@@ -3,6 +3,7 @@ import 'package:cake_wallet/src/screens/wallet_connect/services/chain_service/et
3 import 'package:cake_wallet/src/screens/wallet_connect/services/chain_service/solana/solana_chain_id.dart';
4 import 'package:cake_wallet/src/screens/wallet_connect/services/chain_service/solana/solana_supported_methods.dart';
5 import 'package:cw_core/wallet_type.dart';
6 +import 'package:cake_wallet/evm/evm.dart';
7
8 bool isEVMCompatibleChain(WalletType walletType) {
9 switch (walletType) {
@@ -16,13 +17,29 @@ bool isEVMCompatibleChain(WalletType walletType) {
17 }
18 }
19
19 -bool isNFTACtivatedChain(WalletType walletType) {
20 +// Blink Protection is supported on Ethereum and Base chains
21 +//TODO: Add BNB Smart Chain to this list when we integrate it
22 +bool canSupportBlinkProtection(int? chainId) {
23 + if (chainId == null) return false;
24 +
25 + return chainId == 1 || chainId == 8453;
26 +}
27 +
28 +bool isNFTACtivatedChain(WalletType walletType, int? chainId) {
29 + if (chainId != null) {
30 + switch (chainId) {
31 + case 1:
32 + case 8453:
33 + case 137:
34 + case 42161:
35 + return true;
36 + default:
37 + return false;
38 + }
39 + }
40 +
41 switch (walletType) {
21 - case WalletType.polygon:
22 - case WalletType.ethereum:
23 - case WalletType.base:
42 case WalletType.solana:
25 - case WalletType.arbitrum:
43 return true;
44 default:
45 return false;
@@ -31,10 +48,10 @@ bool isNFTACtivatedChain(WalletType walletType) {
48
49 bool isWalletConnectCompatibleChain(WalletType walletType) {
50 switch (walletType) {
51 + case WalletType.solana:
52 case WalletType.polygon:
53 case WalletType.ethereum:
54 case WalletType.base:
37 - case WalletType.solana:
55 case WalletType.arbitrum:
56 return true;
57 default:
@@ -42,7 +59,11 @@ bool isWalletConnectCompatibleChain(WalletType walletType) {
59 }
60 }
61
45 -String getChainNameSpaceAndIdBasedOnWalletType(WalletType walletType) {
62 +String getChainNameSpaceAndIdBasedOnWalletType(WalletType walletType, {int? chainId}) {
63 + if (chainId != null) {
64 + return evm!.getCaip2ByChainId(chainId);
65 + }
66 +
67 switch (walletType) {
68 case WalletType.ethereum:
69 return EVMChainId.ethereum.chain();
@@ -73,51 +94,26 @@ List<String> getChainSupportedMethodsOnWalletType(WalletType walletType) {
94 }
95 }
96
76 -int getChainIdBasedOnWalletType(WalletType walletType) {
77 - switch (walletType) {
78 - case WalletType.polygon:
79 - return 137;
80 - case WalletType.base:
81 - return 8453;
82 - case WalletType.arbitrum:
83 - return 42161;
84 - // For now, we return eth chain Id as the default, we'll modify as we add more wallets
85 - case WalletType.ethereum:
86 - default:
87 - return 1;
97 +String getChainNameBasedOnWalletType(WalletType walletType, {int? chainId}) {
98 + if (walletType == WalletType.solana) {
99 + return 'mainnet';
100 }
89 -}
101
91 -String getChainNameBasedOnWalletType(WalletType walletType) {
92 - switch (walletType) {
93 - case WalletType.ethereum:
94 - return 'eth';
95 - case WalletType.polygon:
96 - return 'polygon';
97 - case WalletType.base:
98 - return 'base';
99 - case WalletType.arbitrum:
100 - return 'arbitrum';
101 - case WalletType.solana:
102 - return 'mainnet';
103 - default:
104 - return '';
102 + if (chainId != null) {
103 + return evm!.getChainNameByChainId(chainId);
104 }
105 +
106 + return evm!.getChainNameByWalletType(walletType);
107 }
108
108 -String getTokenNameBasedOnWalletType(WalletType walletType) {
109 - switch (walletType) {
110 - case WalletType.ethereum:
111 - return 'ETH';
112 - case WalletType.polygon:
113 - return 'MATIC';
114 - case WalletType.base:
115 - return 'BASE';
116 - case WalletType.arbitrum:
117 - return 'ARB';
118 - case WalletType.solana:
119 - return 'SOL';
120 - default:
121 - return '';
109 +String getTokenNameBasedOnWalletType(WalletType walletType, {int? chainId}) {
110 + if (walletType == WalletType.solana) {
111 + return 'SOL';
112 }
113 +
114 + if (chainId != null) {
115 + return evm!.getTokenNameByChainId(chainId);
116 + }
117 +
118 + return evm!.getTokenNameByWalletType(walletType);
119 }
lib/src/screens/connect_device/select_hardware_wallet_account_page.dart
+3 -1
@@ -149,7 +149,9 @@ class _SelectHardwareWalletAccountFormState extends State<SelectHardwareWalletAc
149 padding: EdgeInsets.only(top: 10),
150 child: SelectButton(
151 image: Image.asset(
152 - walletTypeToCryptoCurrency(_walletHardwareRestoreVM.type).iconPath ??
152 + getCryptoCurrencyForWalletListItem(
153 + _walletHardwareRestoreVM.type,
154 + ).iconPath ??
155 '',
156 height: 24,
157 width: 24,
lib/src/screens/dashboard/dashboard_page.dart
+32 -1
@@ -6,11 +6,13 @@ import 'package:cake_wallet/src/screens/dashboard/pages/cake_features_page.dart'
6 import 'package:cake_wallet/src/screens/dashboard/widgets/page_indicator.dart';
7 import 'package:cake_wallet/src/screens/wallet_connect/widgets/bottom_sheet/bottom_sheet_listener_widget.dart';
8 import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart';
9 +import 'package:cake_wallet/src/widgets/evm_switcher.dart';
10 import 'package:cake_wallet/src/widgets/gradient_background.dart';
11 import 'package:cake_wallet/src/widgets/haven_wallet_removal_popup.dart';
12 import 'package:cake_wallet/src/widgets/services_updates_widget.dart';
13 import 'package:cake_wallet/src/widgets/vulnerable_seeds_popup.dart';
14 import 'package:cake_wallet/utils/device_info.dart';
15 +import 'package:cake_wallet/utils/feature_flag.dart';
16 import 'package:cake_wallet/utils/version_comparator.dart';
17 import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.dart';
18 import 'package:cake_wallet/generated/i18n.dart';
@@ -31,7 +33,6 @@ import 'package:flutter_mobx/flutter_mobx.dart';
33 import 'package:flutter_svg/flutter_svg.dart';
34 import 'package:mobx/mobx.dart';
35 import 'package:shared_preferences/shared_preferences.dart';
34 -import 'package:smooth_page_indicator/smooth_page_indicator.dart';
36 import 'package:cake_wallet/main.dart';
37 import 'package:cake_wallet/src/screens/release_notes/release_notes_screen.dart';
38 import 'package:cake_wallet/themes/core/theme_extension.dart';
@@ -144,6 +145,36 @@ class _DashboardPageView extends BasePage {
145
146 @override
147 Widget leading(BuildContext context) {
148 + if (FeatureFlag.isEVMChainSwitcherEnabled &&
149 + dashboardViewModel.isEVMWallet &&
150 + dashboardViewModel.availableChains.isNotEmpty) {
151 + return TextButton(
152 + style: TextButton.styleFrom(
153 + minimumSize: Size(50, 30),
154 + tapTargetSize: MaterialTapTargetSize.shrinkWrap,
155 + alignment: Alignment.centerLeft,
156 + ),
157 + onPressed: () => showDialog(
158 + context: context,
159 + builder: (context) => EvmSwitcher(
160 + chains: dashboardViewModel.availableChains,
161 + currentChain: dashboardViewModel.currentChain,
162 + onChainSelected: (chainId) => dashboardViewModel.selectChain(chainId),
163 + hiddenChainIds: dashboardViewModel.settingsStore.evmHiddenChainIds,
164 + onHiddenChanged: (hidden) =>
165 + dashboardViewModel.settingsStore.setEvmHiddenChainIds(hidden),
166 + ),
167 + ),
168 + child: Container(
169 + child: SvgPicture.asset(
170 + 'assets/images/evm_switcher.svg',
171 + color: Theme.of(context).colorScheme.onSurfaceVariant,
172 + height: 30,
173 + ),
174 + ),
175 + );
176 + }
177 +
178 return Observer(
179 builder: (context) {
180 return ServicesUpdatesWidget(
lib/src/screens/dashboard/pages/address_page.dart
+37 -32
@@ -207,40 +207,45 @@ class AddressPage extends BasePage {
207 ),
208 ),
209 SizedBox(height: 20),
210 - Center(
211 - child: SizedBox(
212 - height: 40,
213 - width: addressListViewModel.walletImages.length * 32.0,
214 - child: Stack(
215 - children: [
216 - for (int i = addressListViewModel.walletImages.length - 1; i >= 0; i--)
217 - Positioned(
218 - left: i * 25.0,
219 - child: Container(
220 - decoration: BoxDecoration(
221 - border: Border.all(
222 - color: Theme.of(context).colorScheme.surfaceContainer,
223 - width: 3,
210 + Observer(
211 + builder: (_) {
212 + final walletImages = addressListViewModel
213 + .getWalletImages(addressListViewModel.selectedChainId);
214 + return Center(
215 + child: SizedBox(
216 + height: 40,
217 + width: walletImages.length * 32.0,
218 + child: Stack(
219 + children: [
220 + for (int i = walletImages.length - 1; i >= 0; i--)
221 + Positioned(
222 + left: i * 25.0,
223 + child: Container(
224 + decoration: BoxDecoration(
225 + border: Border.all(
226 + color: Theme.of(context).colorScheme.surfaceContainer,
227 + width: 3,
228 + ),
229 + color: Theme.of(context).colorScheme.surfaceContainer,
230 + borderRadius: BorderRadius.circular(24),
231 + ),
232 + child: ClipOval(
233 + child: CakeImageWidget(
234 + height: 35,
235 + width: 35,
236 + imageUrl: walletImages[i],
237 + color: walletImages.last == walletImages[i]
238 + ? Theme.of(context).colorScheme.onSurfaceVariant
239 + : null,
240 + ),
241 + ),
242 ),
225 - color: Theme.of(context).colorScheme.surfaceContainer,
226 - borderRadius: BorderRadius.circular(24),
243 ),
228 - child: ClipOval(
229 - child: CakeImageWidget(
230 - height: 35,
231 - width: 35,
232 - imageUrl: addressListViewModel.walletImages[i],
233 - color: addressListViewModel.walletImages.last ==
234 - addressListViewModel.walletImages[i]
235 - ? Theme.of(context).colorScheme.onSurfaceVariant
236 - : null,
237 - ),
238 - ),
239 - ),
240 - ),
241 - ],
242 - ),
243 - ),
244 + ],
245 + ),
246 + ),
247 + );
248 + },
249 ),
250 ],
251 ),
lib/src/screens/dashboard/pages/balance/balance_page.dart
+1 -1
@@ -22,7 +22,7 @@ class BalancePage extends StatelessWidget {
22 Widget build(BuildContext context) {
23 return Observer(
24 builder: (context) {
25 - final isNFTActivated = isNFTACtivatedChain(dashboardViewModel.type);
25 + final isNFTActivated = isNFTACtivatedChain(dashboardViewModel.type, dashboardViewModel.wallet.chainId);
26 return DefaultTabController(
27 key: ValueKey<bool>(isNFTActivated),
28 length: isNFTActivated ? 2 : 1,
lib/src/screens/dashboard/pages/balance/crypto_balance_widget.dart
+15
@@ -169,6 +169,21 @@ class CryptoBalanceWidget extends StatelessWidget {
169 }),
170 Observer(
171 builder: (_) {
172 + if (dashboardViewModel.balanceViewModel.formattedBalances.isEmpty) {
173 + return Center(
174 + child: Container(
175 + child: Text(
176 + 'Loading balances...',
177 + style: Theme.of(context).textTheme.bodyMedium?.copyWith(
178 + color: Theme.of(context).colorScheme.onSurfaceVariant,
179 + height: 1,
180 + ),
181 + textAlign: TextAlign.center,
182 + ),
183 + ),
184 + );
185 + }
186 +
187 return ListView.separated(
188 physics: NeverScrollableScrollPhysics(),
189 shrinkWrap: true,
lib/src/screens/dashboard/pages/cake_features_page.dart
+1 -1
@@ -59,7 +59,7 @@ class CakeFeaturesPage extends StatelessWidget {
59 ),
60 ),
61 Observer(builder: (_) {
62 - if (dashboardViewModel.type == WalletType.ethereum) {
62 + if (dashboardViewModel.wallet.chainId == 1) {
63 return DashBoardRoundedCardWidget(
64 shadowBlur: dashboardViewModel.getShadowBlur(),
65 shadowSpread: dashboardViewModel.getShadowSpread(),
lib/src/screens/dashboard/sign_page.dart
+2
@@ -24,11 +24,13 @@ class SignPage extends BasePage {
24 _pages.add(SignForm(
25 key: signFormKey,
26 type: signViewModel.wallet.type,
27 + chainId: signViewModel.wallet.chainId,
28 includeAddress: signViewModel.signIncludesAddress,
29 ));
30 _pages.add(VerifyForm(
31 key: verifyFormKey,
32 type: signViewModel.wallet.type,
33 + chainId: signViewModel.wallet.chainId,
34 ));
35 }
36
lib/src/screens/dashboard/widgets/sign_form.dart
+4 -2
@@ -11,12 +11,14 @@ class SignForm extends StatefulWidget {
11 SignForm({
12 Key? key,
13 required this.type,
14 + required this.chainId,
15 required this.includeAddress,
16 }) : super(key: key);
17
18 final WalletType type;
19 final bool includeAddress;
19 -
20 + final int? chainId;
21 +
22 @override
23 SignFormState createState() => SignFormState();
24 }
@@ -76,7 +78,7 @@ class SignFormState extends State<SignForm> with AutomaticKeepAliveClientMixin {
78 onSelectedContact: (contact) {
79 addressController.text = contact.address;
80 },
79 - selectedCurrency: walletTypeToCryptoCurrency(widget.type),
81 + selectedCurrency: walletTypeToCryptoCurrency(widget.type, chainId: widget.chainId),
82 fillColor: Theme.of(context).colorScheme.surface,
83 ),
84 ],
lib/src/screens/dashboard/widgets/trade_row.dart
+32 -31
@@ -33,39 +33,40 @@ class TradeRow extends StatelessWidget {
33 final receiveAmountCrypto = to.toString();
34
35 return InkWell(
36 - onTap: onTap,
37 - child: Container(
38 - padding: EdgeInsets.fromLTRB(24, 8, 24, 8),
39 - color: Colors.transparent,
40 - child: Row(
41 - mainAxisSize: MainAxisSize.max,
42 - crossAxisAlignment: CrossAxisAlignment.center,
43 - children: [
44 - Stack(
45 - clipBehavior: Clip.none,
46 - children: [
47 - ClipRRect(
48 - borderRadius: BorderRadius.circular(50),
49 - child: ImageUtil.getImageFromPath(
50 - imagePath: provider.image, height: 36, width: 36),),
51 - Positioned(
52 - right: 0,
53 - bottom: 2,
54 - child: Container(
55 - height: 8,
56 - width: 8,
57 - padding: EdgeInsets.symmetric(horizontal: 8, vertical: 2),
58 - decoration: BoxDecoration(
59 - color: _statusColor(context, swapState),
60 - borderRadius: BorderRadius.circular(12),
61 - ),
36 + onTap: onTap,
37 + child: Container(
38 + padding: EdgeInsets.fromLTRB(24, 8, 24, 8),
39 + color: Colors.transparent,
40 + child: Row(
41 + mainAxisSize: MainAxisSize.max,
42 + crossAxisAlignment: CrossAxisAlignment.center,
43 + children: [
44 + Stack(
45 + clipBehavior: Clip.none,
46 + children: [
47 + ClipRRect(
48 + borderRadius: BorderRadius.circular(50),
49 + child:
50 + ImageUtil.getImageFromPath(imagePath: provider.image, height: 36, width: 36),
51 + ),
52 + Positioned(
53 + right: 0,
54 + bottom: 2,
55 + child: Container(
56 + height: 8,
57 + width: 8,
58 + padding: EdgeInsets.symmetric(horizontal: 8, vertical: 2),
59 + decoration: BoxDecoration(
60 + color: _statusColor(context, swapState),
61 + borderRadius: BorderRadius.circular(12),
62 ),
63 ),
64 - ],
65 - ),
66 - SizedBox(width: 12),
67 - Expanded(
68 - child: Column(
64 + ),
65 + ],
66 + ),
67 + SizedBox(width: 12),
68 + Expanded(
69 + child: Column(
70 mainAxisSize: MainAxisSize.min,
71 children: [
72 Row(
lib/src/screens/dashboard/widgets/verify_form.dart
+4 -2
@@ -8,10 +8,12 @@ class VerifyForm extends StatefulWidget {
8 VerifyForm({
9 Key? key,
10 required this.type,
11 + required this.chainId,
12 }) : super(key: key);
13
14 final WalletType type;
14 -
15 + final int? chainId;
16 +
17 @override
18 VerifyFormState createState() => VerifyFormState();
19 }
@@ -66,7 +68,7 @@ class VerifyFormState extends State<VerifyForm> with AutomaticKeepAliveClientMix
68 onSelectedContact: (contact) {
69 addressController.text = contact.address;
70 },
69 - selectedCurrency: walletTypeToCryptoCurrency(widget.type),
71 + selectedCurrency: walletTypeToCryptoCurrency(widget.type, chainId: widget.chainId),
72 ),
73 const SizedBox(height: 20),
74 AddressTextField(
lib/src/screens/exchange_trade/exchange_trade_external_send_page.dart
+2 -2
@@ -72,7 +72,7 @@ class ExchangeTradeExternalSendPage extends BasePage {
72 context,
73 Routes.fullscreenQR,
74 arguments: QrViewData(
75 - embeddedImagePath: exchangeTradeViewModel.qrImage,
75 + embeddedImagePath: exchangeTradeViewModel.trade.from?.iconPath,
76 data: exchangeTradeViewModel.paymentUri?.toString() ??
77 exchangeTradeViewModel.trade.inputAddress ??
78 fetchingLabel,
@@ -96,7 +96,7 @@ class ExchangeTradeExternalSendPage extends BasePage {
96 data: exchangeTradeViewModel.paymentUri?.toString() ??
97 exchangeTradeViewModel.trade.inputAddress ??
98 fetchingLabel,
99 - embeddedImagePath: exchangeTradeViewModel.qrImage,
99 + embeddedImagePath: exchangeTradeViewModel.trade.from?.iconPath,
100 size: 230,
101 ),
102 ),
lib/src/screens/new_wallet/advanced_privacy_settings_page.dart
+8
@@ -257,6 +257,14 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
257 widget.privacySettingsViewModel.setDisableBulletin(value);
258 },
259 ),
260 + if (widget.privacySettingsViewModel.canUseBlinkProtection)
261 + SettingsSwitcherCell(
262 + title: S.current.use_blink_protection,
263 + value: widget.privacySettingsViewModel.useBlinkProtection,
264 + onValueChange: (BuildContext _, bool value) {
265 + widget.privacySettingsViewModel.setUseBlinkProtection(value);
266 + },
267 + ),
268 SettingsSwitcherCell(
269 title: S.current.add_custom_node,
270 value: widget.privacySettingsViewModel.addCustomNode,
lib/src/screens/new_wallet/new_wallet_type_page.dart
+1 -1
@@ -143,7 +143,7 @@ class WalletTypeFormState extends State<WalletTypeForm> {
143 child: SelectButton(
144 key: ValueKey('new_wallet_type_${type.name}_button_key'),
145 image: Image.asset(
146 - walletTypeToCryptoCurrency(type).iconPath ?? '',
146 + getCryptoCurrencyForWalletListItem(type).iconPath ?? '',
147 height: 24,
148 width: 24,
149 ),
lib/src/screens/new_wallet/wallet_group_display_page.dart
+1 -1
@@ -95,7 +95,7 @@ class WalletGroupsDisplayBody extends StatelessWidget {
95 isSelected:
96 walletGroupsDisplayViewModel.selectedSingleWallet == wallet,
97 leadingWidget: Image.asset(
98 - walletTypeToCryptoCurrency(wallet.type).iconPath!,
98 + getCryptoCurrencyForWalletListItem(wallet.type).iconPath!,
99 width: 32,
100 height: 32,
101 ),
lib/src/screens/new_wallet/widgets/grouped_wallet_expansion_tile.dart
+1 -1
@@ -123,7 +123,7 @@ class GroupedWalletExpansionTile extends StatelessWidget {
123 : SizedBox(width: 7),
124 SizedBox(width: 24),
125 Image.asset(
126 - walletTypeToCryptoCurrency(item.type).iconPath!,
126 + getCryptoCurrencyForWalletListItem(item.type).iconPath!,
127 width: 32,
128 height: 32,
129 ),
lib/src/screens/send/widgets/send_card.dart
+151 -27
@@ -37,6 +37,9 @@ import 'package:cake_wallet/src/widgets/address_text_field.dart';
37 import 'package:cake_wallet/generated/i18n.dart';
38 import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
39 import 'package:cake_wallet/di.dart';
40 +import 'package:cake_wallet/evm/evm.dart';
41 +import 'package:cake_wallet/reactions/wallet_connect.dart';
42 +import 'package:cake_wallet/store/app_store.dart';
43
44 class SendCard extends StatefulWidget {
45 SendCard({
@@ -176,6 +179,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
179 walletSwitcherViewModel,
180 paymentRequest,
181 );
182 +
183 break;
184 case PaymentFlowType.currentWalletCompatible:
185 case PaymentFlowType.error:
@@ -221,7 +225,14 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
225 paymentRequest,
226 result,
227 ),
224 - onSwap: () => _handleSwapFlow(paymentViewModel, result),
228 + onSwap: (bottomSheetContext) =>
229 + _handleSwapFlow(paymentViewModel, result, bottomSheetContext),
230 + onSwitchNetwork: () => _handleSwitchNetwork(
231 + paymentViewModel,
232 + walletSwitcherViewModel,
233 + paymentRequest,
234 + result,
235 + ),
236 );
237 },
238 );
@@ -245,10 +256,14 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
256 paymentViewModel: paymentViewModel,
257 paymentRequest: paymentRequest,
258 onNext: (PaymentFlowResult newResult) {
248 - if (newResult.addressDetectionResult!.detectedWalletType ==
249 - paymentViewModel.currentWalletType) {
259 + final selectedChainId = newResult.chainId;
260 + final isCompatible = selectedChainId == evm!.getSelectedChainId(sendViewModel.wallet);
261 +
262 + if (isCompatible) {
263 sendViewModel.setSelectedCryptoCurrency(
251 - newResult.addressDetectionResult!.detectedCurrency!.title);
264 + newResult.addressDetectionResult!.detectedCurrency!.title,
265 + );
266 + _applyPaymentRequest(paymentRequest);
267 } else {
268 _showPaymentConfirmation(
269 paymentViewModel,
@@ -286,9 +301,22 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
301 final success = await walletSwitcherViewModel.switchToSelectedWallet();
302
303 if (success) {
304 + if (isEVMCompatibleChain(sendViewModel.wallet.type) && result.chainId != null) {
305 + final appStore = getIt.get<AppStore>();
306 + final node = appStore.settingsStore.getCurrentNode(
307 + sendViewModel.wallet.type,
308 + chainId: result.chainId,
309 + );
310 + await evm!.selectChain(sendViewModel.wallet, result.chainId!, node: node);
311 + }
312 +
313 await sendViewModel.wallet.updateBalance();
290 - sendViewModel
291 - .setSelectedCryptoCurrency(result.addressDetectionResult!.detectedCurrency!.title);
314 +
315 + final detectedCurrency = result.addressDetectionResult!.detectedCurrency;
316 + if (detectedCurrency != null) {
317 + sendViewModel.setSelectedCryptoCurrency(detectedCurrency.title);
318 + }
319 +
320 _applyPaymentRequest(paymentRequest);
321 }
322 }
@@ -303,7 +331,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
331 Navigator.of(context).pop();
332 }
333
306 - if (result.wallet != null) {
334 + if (result.type == PaymentFlowType.singleWallet && result.wallet != null) {
335 walletSwitcherViewModel.selectWallet(result.wallet!);
336 final success = await walletSwitcherViewModel.switchToSelectedWallet();
337 if (success) {
@@ -321,6 +349,17 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
349 );
350 }
351 });
352 +
353 + // If EVM wallet and chainId is specified, switch to that chain
354 + if (isEVMCompatibleChain(sendViewModel.wallet.type) && result.chainId != null) {
355 + final appStore = getIt.get<AppStore>();
356 + final node = appStore.settingsStore.getCurrentNode(
357 + sendViewModel.wallet.type,
358 + chainId: result.chainId,
359 + );
360 + await evm!.selectChain(sendViewModel.wallet, result.chainId!, node: node);
361 + }
362 +
363 await Future.delayed(const Duration(seconds: 2));
364 if (loadingBottomSheetContext != null && loadingBottomSheetContext!.mounted) {
365 Navigator.of(loadingBottomSheetContext!).pop();
@@ -331,6 +370,94 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
370 .setSelectedCryptoCurrency(result.addressDetectionResult!.detectedCurrency!.title);
371 _applyPaymentRequest(paymentRequest);
372 }
373 + } else if (result.wallets.isNotEmpty && result.wallets.length == 1) {
374 + walletSwitcherViewModel.selectWallet(result.wallets.first);
375 + final success = await walletSwitcherViewModel.switchToSelectedWallet();
376 + if (success) {
377 + WidgetsBinding.instance.addPostFrameCallback((_) {
378 + if (context.mounted) {
379 + showModalBottomSheet<void>(
380 + context: context,
381 + isDismissible: false,
382 + builder: (BuildContext context) {
383 + loadingBottomSheetContext = context;
384 + return LoadingBottomSheet(
385 + titleText: S.of(context).loading_your_wallet,
386 + );
387 + },
388 + );
389 + }
390 + });
391 +
392 + // If EVM wallet and chainId is specified, switch to that chain
393 + if (isEVMCompatibleChain(sendViewModel.wallet.type) && result.chainId != null) {
394 + final appStore = getIt.get<AppStore>();
395 + final node = appStore.settingsStore.getCurrentNode(
396 + sendViewModel.wallet.type,
397 + chainId: result.chainId,
398 + );
399 + await evm!.selectChain(sendViewModel.wallet, result.chainId!, node: node);
400 + }
401 +
402 + await Future.delayed(const Duration(seconds: 2));
403 + if (loadingBottomSheetContext != null && loadingBottomSheetContext!.mounted) {
404 + Navigator.of(loadingBottomSheetContext!).pop();
405 + }
406 +
407 + await sendViewModel.wallet.updateBalance();
408 + sendViewModel
409 + .setSelectedCryptoCurrency(result.addressDetectionResult!.detectedCurrency!.title);
410 + _applyPaymentRequest(paymentRequest);
411 + }
412 + }
413 + }
414 +
415 + Future<void> _handleSwitchNetwork(
416 + PaymentViewModel paymentViewModel,
417 + WalletSwitcherViewModel walletSwitcherViewModel,
418 + PaymentRequest paymentRequest,
419 + PaymentFlowResult result,
420 + ) async {
421 + if (result.type != PaymentFlowType.evmNetworkSelection || result.wallet == null) return;
422 +
423 + if (context.mounted && Navigator.of(context).canPop()) {
424 + Navigator.of(context).pop();
425 + }
426 +
427 + try {
428 + WidgetsBinding.instance.addPostFrameCallback((_) {
429 + if (context.mounted) {
430 + showModalBottomSheet<void>(
431 + context: context,
432 + isDismissible: false,
433 + builder: (BuildContext context) {
434 + loadingBottomSheetContext = context;
435 + return LoadingBottomSheet(
436 + titleText: S.of(context).loading_your_wallet,
437 + );
438 + },
439 + );
440 + }
441 + });
442 +
443 + await paymentViewModel.selectChain();
444 +
445 + await Future.delayed(const Duration(seconds: 2));
446 + if (loadingBottomSheetContext != null && loadingBottomSheetContext!.mounted) {
447 + Navigator.of(loadingBottomSheetContext!).pop();
448 + }
449 +
450 + await sendViewModel.wallet.updateBalance();
451 + final detectedCurrency = result.addressDetectionResult?.detectedCurrency;
452 + if (detectedCurrency != null) {
453 + sendViewModel.setSelectedCryptoCurrency(detectedCurrency.title);
454 + }
455 + _applyPaymentRequest(paymentRequest);
456 + } catch (e) {
457 + if (loadingBottomSheetContext != null && loadingBottomSheetContext!.mounted) {
458 + Navigator.of(loadingBottomSheetContext!).pop();
459 + }
460 + printV('Switch network error: $e');
461 }
462 }
463
@@ -346,10 +473,17 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
473 noteController.text = paymentRequest.note;
474 }
475
349 - Future<void> _handleSwapFlow(PaymentViewModel paymentViewModel, PaymentFlowResult result) async {
350 - if (mounted && Navigator.of(context).canPop()) {
351 - Navigator.of(context).pop();
352 - }
476 + Future<void> _handleSwapFlow(
477 + PaymentViewModel paymentViewModel,
478 + PaymentFlowResult result,
479 + BuildContext bottomSheetContext,
480 + ) async {
481 + Navigator.of(bottomSheetContext).pop();
482 +
483 + await Future.delayed(const Duration(milliseconds: 100));
484 +
485 + if (!mounted) return;
486 +
487 final bottomSheet = getIt.get<SwapConfirmationBottomSheet>(param1: result);
488 await showModalBottomSheet<Trade?>(
489 context: context,
@@ -549,31 +683,21 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
683 builder: (context, snapshot) {
684 return GestureDetector(
685 onTap: () {
552 - sendViewModel.balanceViewModel
553 - .switchBalanceValue();
686 + sendViewModel.balanceViewModel.switchBalanceValue();
687 },
688 child: Observer(builder: (_) {
556 - final hidden = sendViewModel
557 - .balanceViewModel.displayMode ==
689 + final hidden = sendViewModel.balanceViewModel.displayMode ==
690 BalanceDisplayMode.hiddenBalance;
691 return Text(
692 hidden
693 ? S.of(context).show_balance_send_page
562 - : (snapshot.data ??
563 - sendViewModel.balance),
694 + : (snapshot.data ?? sendViewModel.balance),
695 // default to balance while loading
565 - style: Theme.of(context)
566 - .textTheme
567 - .bodySmall!
568 - .copyWith(
696 + style: Theme.of(context).textTheme.bodySmall!.copyWith(
697 fontWeight: FontWeight.w600,
698 color: hidden
571 - ? Theme.of(context)
572 - .colorScheme
573 - .primary
574 - : Theme.of(context)
575 - .colorScheme
576 - .onSurfaceVariant,
699 + ? Theme.of(context).colorScheme.primary
700 + : Theme.of(context).colorScheme.onSurfaceVariant,
701 ),
702 );
703 }),
lib/src/screens/settings/privacy_page.dart
+8
@@ -102,6 +102,14 @@ class PrivacyPage extends BasePage {
102 _privacySettingsViewModel.setDisableBulletin(value);
103 },
104 ),
105 + if (_privacySettingsViewModel.canUseBlinkProtection)
106 + SettingsSwitcherCell(
107 + title: S.current.use_blink_protection,
108 + value: _privacySettingsViewModel.useBlinkProtection,
109 + onValueChange: (BuildContext _, bool value) {
110 + _privacySettingsViewModel.setUseBlinkProtection(value);
111 + },
112 + ),
113 if (_privacySettingsViewModel.canUseEtherscan)
114 SettingsSwitcherCell(
115 title: S.current.etherscan_history,
lib/src/screens/wallet_connect/services/chain_service/eth/evm_chain_id.dart
+16 -24
@@ -1,37 +1,29 @@
1 +import 'package:cake_wallet/evm/evm.dart';
2 +
3 enum EVMChainId {
4 ethereum,
5 polygon,
6 base,
5 - goerli,
6 - mumbai,
7 arbitrum,
8 }
9
10 extension EVMChainIdX on EVMChainId {
11 String chain() {
12 - String name = '';
12 + final chainId = _getChainIdForEnum(this);
13 +
14 + if (chainId == null) return 'eip155:1';
15
14 - switch (this) {
15 - case EVMChainId.ethereum:
16 - name = '1';
17 - break;
18 - case EVMChainId.polygon:
19 - name = '137';
20 - break;
21 - case EVMChainId.base:
22 - name = '8453';
23 - break;
24 - case EVMChainId.goerli:
25 - name = '5';
26 - break;
27 - case EVMChainId.arbitrum:
28 - name = '42161';
29 - break;
30 - case EVMChainId.mumbai:
31 - name = '80001';
32 - break;
33 - }
16 + return evm!.getCaip2ByChainId(chainId);
17 + }
18
35 - return 'eip155:$name';
19 + int? _getChainIdForEnum(EVMChainId id) {
20 + return switch (id) {
21 + EVMChainId.ethereum => 1,
22 + EVMChainId.polygon => 137,
23 + EVMChainId.base => 8453,
24 + EVMChainId.arbitrum => 42161,
25 + };
26 }
27 +
28 + int? get chainId => _getChainIdForEnum(this);
29 }
lib/src/screens/wallet_connect/services/chain_service/eth/evm_chain_service.dart
+15 -3
@@ -40,7 +40,7 @@ class EvmChainServiceImpl {
40 Web3Client? web3Client,
41 }) : ethClient = web3Client ??
42 Web3Client(
43 - appStore.settingsStore.getCurrentNode(appStore.wallet!.type).uri.toString(),
43 + _getNodeUriForChain(reference, appStore),
44 ProxyWrapper().getHttpIOClient(),
45 ) {
46 for (final event in EventsConstants.allEvents) {
@@ -77,6 +77,19 @@ class EvmChainServiceImpl {
77
78 String getChainId() => reference.chain();
79
80 + static String _getNodeUriForChain(EVMChainId reference, AppStore appStore) {
81 + final walletType = appStore.wallet!.type;
82 +
83 + if (isEVMCompatibleChain(walletType)) {
84 + final chainId = reference.chainId;
85 +
86 + return appStore.settingsStore.getCurrentNode(walletType, chainId: chainId).uri.toString();
87 + }
88 +
89 + // For old wallet types, use the wallet type directly
90 + return appStore.settingsStore.getCurrentNode(walletType).uri.toString();
91 + }
92 +
93 Future<void> personalSign(String topic, dynamic parameters) async {
94 debugPrint('personalSign request: $parameters');
95
@@ -502,7 +515,7 @@ class EvmChainServiceImpl {
515
516 // Get the primary type and types
517 final primaryType = typedData['primaryType']?.toString() ?? '';
505 - final types = typedData['types'] as Map<String, dynamic>? ?? {};
518 + final types = typedData['types'] as Map<String, dynamic>? ?? {};
519 final message = typedData['message'] as Map<String, dynamic>? ?? {};
520
521 // Build a readable message based on the primary type and its structure
@@ -584,7 +597,6 @@ $messageDetails''';
597 },
598 );
599
587 -
600 final decodedResponse = jsonDecode(response.body)[0] as Map<String, dynamic>;
601
602 final symbol = (decodedResponse['symbol'] ?? '') as String;
lib/src/screens/wallet_connect/services/key_service/wallet_connect_key_service.dart
+9 -13
@@ -1,7 +1,4 @@
1 -import 'package:cake_wallet/arbitrum/arbitrum.dart';
2 -import 'package:cake_wallet/base/base.dart';
3 -import 'package:cake_wallet/ethereum/ethereum.dart';
4 -import 'package:cake_wallet/polygon/polygon.dart';
1 +import 'package:cake_wallet/evm/evm.dart';
2 import 'package:cake_wallet/reactions/wallet_connect.dart';
3 import 'package:cake_wallet/solana/solana.dart';
4 import 'package:cake_wallet/src/screens/wallet_connect/services/key_service/chain_key_model.dart';
@@ -22,13 +19,10 @@ class KeyServiceImpl implements WalletConnectKeyService {
19 static String _getPrivateKeyForWallet(WalletBase wallet) {
20 switch (wallet.type) {
21 case WalletType.ethereum:
25 - return ethereum!.getPrivateKey(wallet);
22 case WalletType.polygon:
27 - return polygon!.getPrivateKey(wallet);
23 case WalletType.base:
29 - return base!.getPrivateKey(wallet);
24 case WalletType.arbitrum:
31 - return arbitrum!.getPrivateKey(wallet);
25 + return evm!.getPrivateKey(wallet);
26 case WalletType.solana:
27 return solana!.getPrivateKey(wallet);
28 default:
@@ -39,13 +33,10 @@ class KeyServiceImpl implements WalletConnectKeyService {
33 static String _getPublicKeyForWallet(WalletBase wallet) {
34 switch (wallet.type) {
35 case WalletType.ethereum:
42 - return ethereum!.getPublicKey(wallet);
36 case WalletType.polygon:
44 - return polygon!.getPublicKey(wallet);
37 case WalletType.base:
46 - return base!.getPublicKey(wallet);
38 case WalletType.arbitrum:
48 - return arbitrum!.getPublicKey(wallet);
39 + return evm!.getPublicKey(wallet);
40 case WalletType.solana:
41 return solana!.getPublicKey(wallet);
42 default:
@@ -82,7 +73,12 @@ class KeyServiceImpl implements WalletConnectKeyService {
73
74 @override
75 List<ChainKeyModel> getKeysForChain(WalletBase wallet) {
85 - final chain = getChainNameSpaceAndIdBasedOnWalletType(wallet.type);
76 + int? chainId;
77 + if (isEVMCompatibleChain(wallet.type)) {
78 + final chainInfo = evm!.getCurrentChain(wallet);
79 + chainId = chainInfo?.chainId;
80 + }
81 + final chain = getChainNameSpaceAndIdBasedOnWalletType(wallet.type, chainId: chainId);
82
83 final keys = getKeys(wallet);
84
lib/src/screens/wallet_connect/services/walletkit_service.dart
+47 -14
@@ -105,14 +105,22 @@ abstract class WalletKitServiceBase with Store {
105 List<ChainKeyModel> chainKeys = walletKeyService.getKeys(appStore.wallet!);
106 for (final chainKey in chainKeys) {
107 for (final chainId in chainKey.chains) {
108 - final chainNameSpace = getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type);
109 - if (chainNameSpace == chainId) {
110 - final account = '$chainId:${chainKey.publicKey}';
111 - debugPrint('registerAccount $account');
108 + if (isEVMCompatibleChain(appStore.wallet!.type)) {
109 + // Register account for all EVM chains (chainId is already in eip155:format)
110 _walletKit.registerAccount(
111 chainId: chainId,
112 accountAddress: chainKey.publicKey,
113 );
114 + } else {
115 + final chainNameSpace = getChainNameSpaceAndIdBasedOnWalletType(
116 + appStore.wallet!.type,
117 + );
118 + if (chainNameSpace == chainId) {
119 + _walletKit.registerAccount(
120 + chainId: chainId,
121 + accountAddress: chainKey.publicKey,
122 + );
123 + }
124 }
125 }
126 }
@@ -382,6 +390,14 @@ abstract class WalletKitServiceBase with Store {
390 @action
391 void _onPairingCreate(PairingEvent? args) {
392 debugPrint('_onPairingCreate $args');
393 +
394 + if (args != null && args.topic != null && args.topic!.isNotEmpty) {
395 + // Save the pairing topic when pairing is created
396 + savePairingTopicToLocalStorage(args.topic!);
397 +
398 + // Refresh pairings to show the new pairing in the list
399 + _refreshPairings();
400 + }
401 }
402
403 Future<void> _onSessionAuthRequest(SessionAuthRequest? args) async {
@@ -562,9 +578,11 @@ abstract class WalletKitServiceBase with Store {
578 final filteredPairings = allPairings.where(
579 (pairing) {
580 bool isInCurrentTopics = currentTopicsForWallet.contains(pairing.topic);
565 - bool isActive = pairing.active;
581 + // bool isActive = pairing.active;
582 + // bool hasSession = sessions.any((session) => session.pairingTopic == pairing.topic);
583
567 - return isInCurrentTopics && isActive;
584 + // return isInCurrentTopics && isActive;
585 + return isInCurrentTopics;
586 },
587 ).toList();
588
@@ -577,16 +595,30 @@ abstract class WalletKitServiceBase with Store {
595 }
596
597 String getKeyForStoringTopicsForWallet() {
580 - List<ChainKeyModel> chainKeys = walletKeyService.getKeysForChain(appStore.wallet!);
598 + try {
599 + // For EVM wallets, use getKeys() to get all EVM keys
600 + // since the same address works across all EVM chains. For non-EVM wallets, use getKeysForChain()
601 + // to get keys specific to the current chain.
602 + List<ChainKeyModel> chainKeys;
603 + if (isEVMCompatibleChain(appStore.wallet!.type)) {
604 + chainKeys = walletKeyService.getKeys(appStore.wallet!);
605 +
606 + chainKeys = chainKeys
607 + .where((key) => key.chains.any((chain) => chain.startsWith('eip155:')))
608 + .toList();
609 + } else {
610 + chainKeys = walletKeyService.getKeysForChain(appStore.wallet!);
611 + }
612
582 - if (chainKeys.isEmpty) {
583 - return '';
584 - }
613 + if (chainKeys.isEmpty) return '';
614
586 - final keyForPairingTopic =
587 - PreferencesKey.walletConnectPairingTopicsListForWallet(chainKeys.first.publicKey);
615 + final publicKey = chainKeys.first.publicKey;
616 + if (publicKey.isEmpty) return '';
617
589 - return keyForPairingTopic;
618 + return PreferencesKey.walletConnectPairingTopicsListForWallet(publicKey);
619 + } catch (e) {
620 + return '';
621 + }
622 }
623
624 List<String> getPairingTopicsForWallet(String key) {
@@ -615,7 +647,8 @@ abstract class WalletKitServiceBase with Store {
647 final pairingTopicsForWallet = getPairingTopicsForWallet(key);
648
649 bool isPairingTopicAlreadySaved = pairingTopicsForWallet.contains(pairingTopic);
618 - debugPrint('Is Pairing Topic Saved: $isPairingTopicAlreadySaved');
650 + debugPrint(
651 + 'Is Pairing Topic Saved: $isPairingTopicAlreadySaved, Key: $key, Topic: $pairingTopic');
652
653 if (!isPairingTopicAlreadySaved) {
654 // Update the list with the most recent pairing topic
lib/src/screens/wallet_connect/widgets/bottom_sheet/bottom_sheet_listener_widget.dart
+69 -59
@@ -30,71 +30,81 @@ class BottomSheetListenerState extends State<BottomSheetListener> {
30 }
31
32 Future<void> _showBottomSheet() async {
33 + if (!mounted) {
34 + return;
35 + }
36 if (widget.bottomSheetService.currentSheet.value != null) {
37 BottomSheetQueueItemModel item = widget.bottomSheetService.currentSheet.value!;
35 - final value = await showModalBottomSheet(
36 - context: context,
37 - isDismissible: item.isModalDismissible,
38 - backgroundColor: Color.fromARGB(0, 0, 0, 0),
39 - isScrollControlled: true,
40 - constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.9),
41 - builder: (context) {
42 - if (item.closeAfter > 0) {
43 - Future.delayed(Duration(seconds: item.closeAfter), () {
44 - try {
45 - if (!mounted) return;
46 - if (Navigator.canPop(context)) {
47 - Navigator.pop(context);
38 + try {
39 + final value = await showModalBottomSheet(
40 + context: context,
41 + isDismissible: item.isModalDismissible,
42 + backgroundColor: Color.fromARGB(0, 0, 0, 0),
43 + isScrollControlled: true,
44 + constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.9),
45 + builder: (context) {
46 + if (item.closeAfter > 0) {
47 + Future.delayed(Duration(seconds: item.closeAfter), () {
48 + try {
49 + if (!mounted) return;
50 + if (Navigator.canPop(context)) {
51 + Navigator.pop(context);
52 + }
53 + } catch (e, s) {
54 + debugPrint('[$runtimeType] close $e $s');
55 }
49 - } catch (e, s) {
50 - debugPrint('[$runtimeType] close $e $s');
51 - }
52 - });
53 - }
54 - return Material(
55 - color: Theme.of(context).colorScheme.surface,
56 - borderRadius: BorderRadius.all(Radius.circular(16)),
57 - child: Padding(
58 - padding: EdgeInsets.only(
59 - top: 16,
60 - left: 16,
61 - right: 16,
62 - bottom: MediaQuery.of(context).viewInsets.bottom + 24,
63 - ),
64 - child: Column(
65 - mainAxisSize: MainAxisSize.min,
66 - children: [
67 - // Row(
68 - // mainAxisAlignment: MainAxisAlignment.end,
69 - // children: [
70 - // IconButton(
71 - // color: Theme.of(context).colorScheme.surfaceContainerHighest,
72 - // padding: const EdgeInsets.all(0.0),
73 - // visualDensity: VisualDensity.compact,
74 - // onPressed: () {
75 - // if (Navigator.canPop(context)) {
76 - // Navigator.pop(context);
77 - // }
78 - // },
79 - // icon: Icon(
80 - // Icons.close_sharp,
81 - // color: Theme.of(context).colorScheme.onSurfaceVariant,
82 - // ),
83 - // ),
84 - // ],
85 - // ),
86 - Flexible(child: item.widget),
87 - ],
56 + });
57 + }
58 + return Material(
59 + color: Theme.of(context).colorScheme.surface,
60 + borderRadius: BorderRadius.all(Radius.circular(16)),
61 + child: Padding(
62 + padding: EdgeInsets.only(
63 + top: 16,
64 + left: 16,
65 + right: 16,
66 + bottom: MediaQuery.of(context).viewInsets.bottom + 24,
67 + ),
68 + child: Column(
69 + mainAxisSize: MainAxisSize.min,
70 + children: [
71 + // Row(
72 + // mainAxisAlignment: MainAxisAlignment.end,
73 + // children: [
74 + // IconButton(
75 + // color: Theme.of(context).colorScheme.surfaceContainerHighest,
76 + // padding: const EdgeInsets.all(0.0),
77 + // visualDensity: VisualDensity.compact,
78 + // onPressed: () {
79 + // if (Navigator.canPop(context)) {
80 + // Navigator.pop(context);
81 + // }
82 + // },
83 + // icon: Icon(
84 + // Icons.close_sharp,
85 + // color: Theme.of(context).colorScheme.onSurfaceVariant,
86 + // ),
87 + // ),
88 + // ],
89 + // ),
90 + Flexible(child: item.widget),
91 + ],
92 + ),
93 ),
89 - ),
90 - );
91 - },
92 - );
94 + );
95 + },
96 + );
97
94 - if (!item.completer.isCompleted) {
95 - item.completer.complete(value);
98 + if (!item.completer.isCompleted) {
99 + item.completer.complete(value);
100 + }
101 + widget.bottomSheetService.showNext();
102 + } catch (e) {
103 + if (!item.completer.isCompleted) {
104 + item.completer.complete(null);
105 + }
106 + widget.bottomSheetService.showNext();
107 }
97 - widget.bottomSheetService.showNext();
108 }
109 }
110
lib/src/screens/wallet_list/wallet_list_page.dart
+16 -15
@@ -48,20 +48,19 @@ class WalletListPage extends BasePage {
48 String get title => S.current.wallets;
49
50 @override
51 - Widget body(BuildContext context) => Observer(
52 - builder: (_) {
53 - if (walletListViewModel.singleWalletsList.isEmpty && walletListViewModel.multiWalletGroups.isEmpty) {
54 - return Center(
55 - child: CircularProgressIndicator(),
51 + Widget body(BuildContext context) => Observer(builder: (_) {
52 + if (walletListViewModel.singleWalletsList.isEmpty &&
53 + walletListViewModel.multiWalletGroups.isEmpty) {
54 + return Center(
55 + child: CircularProgressIndicator(),
56 + );
57 + }
58 + return WalletListBody(
59 + walletListViewModel: walletListViewModel,
60 + authService: authService,
61 + onWalletLoaded: onWalletLoaded ?? (context) => Navigator.of(context).pop(),
62 );
57 - }
58 - return WalletListBody(
59 - walletListViewModel: walletListViewModel,
60 - authService: authService,
61 - onWalletLoaded: onWalletLoaded ?? (context) => Navigator.of(context).pop(),
62 - );
63 - }
64 - );
63 + });
64
65 @override
66 Widget trailing(BuildContext context) {
@@ -222,7 +221,7 @@ class WalletListBodyState extends State<WalletListBody> {
221 return item.isCurrent
222 ? SizedBox.shrink()
223 : EditWalletButtonWidget(
225 - width: 60,
224 + width: 64,
225 onTap: () => Navigator.of(context).pushNamed(
226 Routes.walletEdit,
227 arguments: WalletEditPageArguments(
@@ -288,7 +287,9 @@ class WalletListBodyState extends State<WalletListBody> {
287 )
288 : SizedBox(width: 6),
289 Image.asset(
291 - walletTypeToCryptoCurrency(wallet.type).iconPath!,
290 + getCryptoCurrencyForWalletListItem(
291 + wallet.type,
292 + ).iconPath!,
293 width: 32,
294 height: 32,
295 ),
lib/src/widgets/bottom_sheet/evm_payment_flow_bottom_sheet.dart
+68 -46
@@ -1,6 +1,6 @@
1 import 'package:cake_wallet/core/universal_address_detector.dart';
2 +import 'package:cake_wallet/evm/evm.dart';
3 import 'package:cake_wallet/generated/i18n.dart';
3 -import 'package:cake_wallet/reactions/wallet_connect.dart';
4 import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
5 import 'package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart';
6 import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
@@ -11,7 +11,6 @@ import 'package:cake_wallet/utils/qr_util.dart';
11 import 'package:cake_wallet/utils/show_pop_up.dart';
12 import 'package:cake_wallet/utils/token_utilities.dart';
13 import 'package:cake_wallet/view_model/payment/payment_view_model.dart';
14 -import 'package:cake_wallet/wallet_types.g.dart';
14 import 'package:cw_core/crypto_currency.dart';
15 import 'package:cw_core/currency.dart';
16 import 'package:cw_core/utils/print_verbose.dart';
@@ -60,21 +59,21 @@ class _EVMPaymentFlowContent extends StatefulWidget {
59 }
60
61 class _EVMPaymentFlowContentState extends State<_EVMPaymentFlowContent> {
63 - WalletType? selectedNetwork;
62 + int? selectedChainId;
63 CryptoCurrency? selectedToken;
64
65 @override
66 void initState() {
67 super.initState();
69 - selectedNetwork = WalletType.ethereum;
68 + selectedChainId = widget.paymentViewModel.detectedChainId ?? 1;
69 _autoSelectToken();
70 }
71
72 Future<void> _autoSelectToken() async {
74 - if (selectedNetwork == null) return;
73 + if (selectedChainId == null) return;
74
75 try {
77 - final tokens = await TokenUtilities.getAvailableTokensForNetwork(selectedNetwork!);
76 + final tokens = await TokenUtilities.getAvailableTokensForChainId(selectedChainId!);
77 if (tokens.isNotEmpty) {
78 setState(() {
79 selectedToken = tokens.first;
@@ -124,11 +123,11 @@ class _EVMPaymentFlowContentState extends State<_EVMPaymentFlowContent> {
123 ),
124 const SizedBox(height: 32),
125 EVMTileWidget(
127 - value: selectedNetwork != null
128 - ? walletTypeToString(selectedNetwork!)
126 + value: selectedChainId != null
127 + ? _getChainName(selectedChainId!)
128 : S.current.select_network,
130 - imagePath: selectedNetwork != null ? getChainMonoImage(selectedNetwork!) : null,
131 - color: selectedNetwork != null ? Theme.of(context).colorScheme.primary : null,
129 + imagePath: selectedChainId != null ? _getChainImagePath(selectedChainId!) : null,
130 + color: selectedChainId != null ? Theme.of(context).colorScheme.primary : null,
131 enabled: true,
132 onTap: () => _showNetworkSelection(context),
133 ),
@@ -136,7 +135,7 @@ class _EVMPaymentFlowContentState extends State<_EVMPaymentFlowContent> {
135 EVMTileWidget(
136 imagePath: selectedToken != null ? selectedToken!.iconPath : null,
137 value: selectedToken != null ? selectedToken!.title : S.current.select_token,
139 - enabled: selectedNetwork != null,
138 + enabled: selectedChainId != null,
139 onTap: () => _showTokenSelection(context),
140 color: selectedToken == null ? Theme.of(context).colorScheme.primary : null,
141 ),
@@ -145,7 +144,7 @@ class _EVMPaymentFlowContentState extends State<_EVMPaymentFlowContent> {
144 text: S.current.restore_next,
145 color: Theme.of(context).colorScheme.primary,
146 textColor: Theme.of(context).colorScheme.onPrimary,
148 - onPressed: selectedNetwork != null && selectedToken != null
147 + onPressed: selectedChainId != null && selectedToken != null
148 ? () async => await _handleNext(context)
149 : null,
150 ),
@@ -156,30 +155,34 @@ class _EVMPaymentFlowContentState extends State<_EVMPaymentFlowContent> {
155 }
156
157 void _showNetworkSelection(BuildContext context) async {
159 - final evmNetworks =
160 - availableWalletTypes.where((walletType) => isEVMCompatibleChain(walletType)).toList();
161 - final selectedIndex = evmNetworks.indexOf(selectedNetwork ?? WalletType.ethereum);
158 + final allChains = evm!.getAllChains();
159 + final chainIds = allChains.map((chainInfo) => chainInfo.chainId).toList();
160 +
161 + final selectedIndex = selectedChainId != null ? chainIds.indexOf(selectedChainId!) : 0;
162
163 await showPopUp<void>(
164 context: context,
165 builder: (BuildContext context) {
166 return Picker(
167 - items: evmNetworks,
168 - displayItem: (WalletType network) => walletTypeToString(network),
169 - selectedAtIndex: selectedIndex,
167 + items: chainIds,
168 + displayItem: (int chainId) => _getChainName(chainId),
169 + selectedAtIndex: selectedIndex >= 0 ? selectedIndex : 0,
170 title: S.current.select_network,
171 closeOnItemSelected: true,
172 hasTitleSpacing: true,
173 - images: evmNetworks
174 - .map((network) => CakeImageWidget(
175 - imageUrl: getChainMonoImage(network),
176 - width: 20,
177 - height: 20,
178 - color: Theme.of(context).colorScheme.primary))
179 - .toList(),
180 - onItemSelected: (WalletType network) {
173 + images: chainIds.map((chainId) {
174 + final imagePath = _getChainImagePath(chainId);
175 + return imagePath != null
176 + ? CakeImageWidget(
177 + imageUrl: imagePath,
178 + width: 20,
179 + height: 20,
180 + color: Theme.of(context).colorScheme.primary)
181 + : const SizedBox(width: 20, height: 20);
182 + }).toList(),
183 + onItemSelected: (int chainId) {
184 setState(() {
182 - selectedNetwork = network;
185 + selectedChainId = chainId;
186 selectedToken = null;
187 });
188 _autoSelectToken();
@@ -189,11 +192,24 @@ class _EVMPaymentFlowContentState extends State<_EVMPaymentFlowContent> {
192 );
193 }
194
195 + String _getChainName(int chainId) {
196 + final allChains = evm!.getAllChains();
197 + final chainInfo = allChains.firstWhere(
198 + (chain) => chain.chainId == chainId,
199 + orElse: () => ChainInfo(chainId: chainId, name: 'Unknown Network', shortCode: 'unknown'),
200 + );
201 + return chainInfo.name;
202 + }
203 +
204 + String? _getChainImagePath(int chainId) {
205 + return getChainMonoImage(WalletType.ethereum, selectedChainId: chainId);
206 + }
207 +
208 void _showTokenSelection(BuildContext context) async {
193 - if (selectedNetwork == null) return;
209 + if (selectedChainId == null) return;
210
211 try {
196 - final availableTokens = await TokenUtilities.getAvailableTokensForNetwork(selectedNetwork!);
212 + final availableTokens = await TokenUtilities.getAvailableTokensForChainId(selectedChainId!);
213
214 if (availableTokens.isEmpty) return;
215
@@ -220,30 +236,36 @@ class _EVMPaymentFlowContentState extends State<_EVMPaymentFlowContent> {
236 }
237
238 Future<void> _handleNext(BuildContext context) async {
223 - if (selectedNetwork == null || selectedToken == null) return;
239 + if (selectedChainId == null || selectedToken == null) return;
240
241 Navigator.of(context).pop();
242
227 - final compatibleWallets = await widget.paymentViewModel.getWalletsByType(selectedNetwork!);
243 + final allEVMWallets = await widget.paymentViewModel.getEVMCompatibleWallets();
244 +
245 + final walletType = evm!.getWalletTypeByChainId(selectedChainId!) ?? WalletType.ethereum;
246 +
247 + final detectionResult = AddressDetectionResult(
248 + address: widget.paymentRequest.address,
249 + detectedWalletType: walletType,
250 + detectedCurrency: selectedToken!,
251 + chainId: selectedChainId,
252 + isValid: true,
253 + amount: widget.paymentRequest.amount,
254 + note: widget.paymentRequest.note,
255 + scheme: widget.paymentRequest.scheme,
256 + pjUri: widget.paymentRequest.pjUri,
257 + callbackUrl: widget.paymentRequest.callbackUrl,
258 + callbackMessage: widget.paymentRequest.callbackMessage,
259 + );
260
261 final newResult = PaymentFlowResult.evmNetworkSelection(
230 - AddressDetectionResult(
231 - address: widget.paymentRequest.address,
232 - detectedWalletType: selectedNetwork!,
233 - detectedCurrency: selectedToken!,
234 - isValid: true,
235 - amount: widget.paymentRequest.amount,
236 - note: widget.paymentRequest.note,
237 - scheme: widget.paymentRequest.scheme,
238 - pjUri: widget.paymentRequest.pjUri,
239 - callbackUrl: widget.paymentRequest.callbackUrl,
240 - callbackMessage: widget.paymentRequest.callbackMessage,
241 - ),
242 - compatibleWallets: compatibleWallets,
243 - wallet: compatibleWallets.isNotEmpty ? compatibleWallets.first : null,
262 + detectionResult,
263 + compatibleWallets: allEVMWallets,
264 + wallet: allEVMWallets.isNotEmpty ? allEVMWallets.first : null,
265 );
266
246 - widget.paymentViewModel.detectedWalletType = selectedNetwork!;
267 + widget.paymentViewModel.detectedWalletType = walletType;
268 +
269 widget.onNext(newResult);
270 }
271 }
lib/src/widgets/bottom_sheet/payment_confirmation_bottom_sheet.dart
+70 -12
@@ -1,3 +1,5 @@
1 +import 'package:cake_wallet/entities/generate_name.dart';
2 +import 'package:cake_wallet/evm/evm.dart';
3 import 'package:cake_wallet/generated/i18n.dart';
4 import 'package:cake_wallet/utils/payment_request.dart';
5 import 'package:cw_core/currency_for_wallet_type.dart';
@@ -7,6 +9,7 @@ import 'package:cake_wallet/src/widgets/primary_button.dart';
9 import 'package:cw_core/wallet_type.dart';
10 import 'package:cake_wallet/view_model/payment/payment_view_model.dart';
11 import 'package:cake_wallet/view_model/wallet_switcher_view_model.dart';
12 +import 'package:cake_wallet/reactions/wallet_connect.dart';
13 import 'package:flutter_mobx/flutter_mobx.dart';
14
15 class PaymentConfirmationBottomSheet extends BaseBottomSheet {
@@ -19,6 +22,7 @@ class PaymentConfirmationBottomSheet extends BaseBottomSheet {
22 required this.onSelectWallet,
23 required this.onChangeWallet,
24 required this.onSwap,
25 + this.onSwitchNetwork,
26 }) : super(
27 titleText: '',
28 footerType: FooterType.none,
@@ -31,7 +35,8 @@ class PaymentConfirmationBottomSheet extends BaseBottomSheet {
35 final PaymentRequest paymentRequest;
36 final VoidCallback onSelectWallet;
37 final VoidCallback onChangeWallet;
34 - final VoidCallback onSwap;
38 + final void Function(BuildContext) onSwap;
39 + final VoidCallback? onSwitchNetwork;
40
41 @override
42 Widget contentWidget(BuildContext context) {
@@ -43,6 +48,7 @@ class PaymentConfirmationBottomSheet extends BaseBottomSheet {
48 onSelectWallet: onSelectWallet,
49 onChangeWallet: onChangeWallet,
50 onSwap: onSwap,
51 + onSwitchNetwork: onSwitchNetwork,
52 );
53 }
54 }
@@ -56,6 +62,7 @@ class _PaymentConfirmationContent extends StatelessWidget {
62 required this.onSelectWallet,
63 required this.onChangeWallet,
64 required this.onSwap,
65 + this.onSwitchNetwork,
66 });
67
68 final PaymentFlowResult paymentFlowResult;
@@ -64,7 +71,8 @@ class _PaymentConfirmationContent extends StatelessWidget {
71 final PaymentRequest paymentRequest;
72 final VoidCallback onSelectWallet;
73 final VoidCallback onChangeWallet;
67 - final VoidCallback onSwap;
74 + final void Function(BuildContext) onSwap;
75 + final VoidCallback? onSwitchNetwork;
76
77 /// Checks if the given address is a MWEB or SP (Silent Payment) address
78 bool _isMwebOrSpAddress(String address) {
@@ -86,7 +94,9 @@ class _PaymentConfirmationContent extends StatelessWidget {
94 return Observer(
95 builder: (_) {
96 final currencyName = walletTypeToString(paymentViewModel.detectedWalletType!);
89 - final currentWalletName = walletTypeToString(paymentViewModel.currentWalletType);
97 + final currentWalletName = isEVMCompatibleChain(paymentViewModel.currentWalletType)
98 + ? evm!.getChainNameByChainId(paymentViewModel.currentChainId!)
99 + : walletTypeToString(paymentViewModel.currentWalletType);
100
101 final hasSingleWallet = paymentFlowResult.type == PaymentFlowType.singleWallet ||
102 paymentFlowResult.wallets.length == 1;
@@ -102,6 +112,15 @@ class _PaymentConfirmationContent extends StatelessWidget {
112 final isMwebOrSpAddress =
113 _isMwebOrSpAddress(paymentFlowResult.addressDetectionResult?.address ?? '');
114
115 + /// If the wallet is EVM but the detected chainId is different from the currently selected chainId
116 + final isEVMWalletButDifferentChainId =
117 + paymentFlowResult.type == PaymentFlowType.evmNetworkSelection &&
118 + paymentFlowResult.wallet != null &&
119 + isEVMCompatibleChain(paymentViewModel.currentWalletType);
120 +
121 + final otherWalletsCount = paymentFlowResult.wallets.length;
122 + final hasMultipleOtherWallets = otherWalletsCount > 1;
123 +
124 return Container(
125 padding: const EdgeInsets.fromLTRB(24, 0, 24, 24),
126 child: Column(
@@ -121,7 +140,10 @@ class _PaymentConfirmationContent extends StatelessWidget {
140 bottom: 0,
141 right: 0,
142 child: Image.asset(
124 - walletTypeToCryptoCurrency(paymentViewModel.detectedWalletType!).iconPath!,
143 + getCryptoCurrencyForWalletListItem(
144 + paymentViewModel.detectedWalletType!,
145 + chainId: paymentViewModel.detectedChainId,
146 + ).iconPath!,
147 width: 32,
148 height: 32,
149 ),
@@ -141,7 +163,7 @@ class _PaymentConfirmationContent extends StatelessWidget {
163 ),
164 const SizedBox(height: 24),
165 Text(
144 - '''Would you like to ${!noAvailableWallets ? 'switch to a $currencyName wallet or' : ''} swap $currentWalletName for ${paymentFlowResult.addressDetectionResult?.detectedCurrency} (${walletTypeToString(paymentFlowResult.walletType!)}) for this payment?''',
166 + '''Would you like to ${!noAvailableWallets ? 'switch to a $currencyName wallet or' : ''} swap ${currentWalletName.capitalized()} for ${paymentFlowResult.addressDetectionResult?.detectedCurrency} (${walletTypeToString(paymentFlowResult.walletType!)}) for this payment?''',
167 textAlign: TextAlign.center,
168 style: Theme.of(context).textTheme.bodyMedium!.copyWith(
169 fontSize: 16,
@@ -152,7 +174,10 @@ class _PaymentConfirmationContent extends StatelessWidget {
174 ),
175 ] else ...[
176 Image.asset(
155 - walletTypeToCryptoCurrency(paymentViewModel.detectedWalletType!).iconPath!,
177 + getCryptoCurrencyForWalletListItem(
178 + paymentViewModel.detectedWalletType!,
179 + chainId: paymentViewModel.detectedChainId,
180 + ).iconPath!,
181 width: 118,
182 height: 118,
183 ),
@@ -170,7 +195,7 @@ class _PaymentConfirmationContent extends StatelessWidget {
195 const SizedBox(height: 24),
196 Text(
197 '''Looks like you scanned a $currencyName address.\n\n'''
173 - '''Would you like to ${!noAvailableWallets ? 'switch to a $currencyName wallet or' : ''} swap $currentWalletName for $currencyName for this payment?''',
198 + '''Would you like to ${!noAvailableWallets ? 'switch to a $currencyName wallet or' : ''} swap ${currentWalletName.capitalized()} for $currencyName for this payment?''',
199 textAlign: TextAlign.center,
200 style: Theme.of(context).textTheme.bodyMedium!.copyWith(
201 fontSize: 16,
@@ -181,11 +206,44 @@ class _PaymentConfirmationContent extends StatelessWidget {
206 ),
207 ],
208 const SizedBox(height: 72),
184 - if (hasAtLeastOneWallet) ...[
209 + if (isEVMWalletButDifferentChainId) ...[
210 + if (!isMwebOrSpAddress) ...[
211 + PrimaryButton(
212 + onPressed: () => onSwap(context),
213 + text: 'Create In App Swap',
214 + color: hasAtLeastOneWallet
215 + ? Theme.of(context).colorScheme.surfaceContainer
216 + : Theme.of(context).colorScheme.primary,
217 + textColor: hasAtLeastOneWallet
218 + ? Theme.of(context).colorScheme.onSecondaryContainer
219 + : Theme.of(context).colorScheme.onPrimary,
220 + ),
221 + const SizedBox(height: 10),
222 + ],
223 + if (onSwitchNetwork != null) ...[
224 + PrimaryButton(
225 + onPressed: onSwitchNetwork,
226 + text:
227 + 'Switch to ${evm!.getChainNameByChainId(paymentViewModel.detectedChainId!).toUpperCase()} Network',
228 + color: Theme.of(context).colorScheme.surfaceContainer,
229 + textColor: Theme.of(context).colorScheme.onSecondaryContainer,
230 + ),
231 + const SizedBox(height: 10),
232 + ],
233 + if (hasAtLeastOneWallet) ...[
234 + PrimaryButton(
235 + onPressed: hasMultipleOtherWallets ? onSelectWallet : onChangeWallet,
236 + text: S.current.change_wallet_alert_title,
237 + color: Theme.of(context).colorScheme.primary,
238 + textColor: Theme.of(context).colorScheme.onPrimary,
239 + ),
240 + const SizedBox(height: 10),
241 + ],
242 + ] else if (hasAtLeastOneWallet) ...[
243 if (!isMwebOrSpAddress) ...[
244 PrimaryButton(
187 - onPressed: onSwap,
188 - text: '${S.current.swap} $currentWalletName',
245 + onPressed: () => onSwap(context),
246 + text: '${S.current.swap} ${currentWalletName.capitalized()}',
247 color: Theme.of(context).colorScheme.surfaceContainer,
248 textColor: Theme.of(context).colorScheme.onSecondaryContainer,
249 ),
@@ -208,8 +266,8 @@ class _PaymentConfirmationContent extends StatelessWidget {
266 const SizedBox(height: 10),
267 if (!isMwebOrSpAddress) ...[
268 PrimaryButton(
211 - onPressed: onSwap,
212 - text: '${S.current.swap} $currentWalletName',
269 + onPressed: () => onSwap(context),
270 + text: '${S.current.swap} ${currentWalletName.capitalized()}',
271 color: hasAtLeastOneWallet
272 ? Theme.of(context).colorScheme.surfaceContainer
273 : Theme.of(context).colorScheme.primary,
lib/src/widgets/bottom_sheet/swap_confirmation_bottom_sheet.dart
+14 -9
@@ -3,6 +3,7 @@ import 'package:cake_wallet/core/auth_service.dart';
3 import 'package:cake_wallet/di.dart';
4 import 'package:cake_wallet/exchange/limits_state.dart';
5 import 'package:cake_wallet/generated/i18n.dart';
6 +import 'package:cake_wallet/reactions/wallet_connect.dart';
7 import 'package:cake_wallet/src/screens/exchange/widgets/present_provider_picker.dart';
8 import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
9 import 'package:cake_wallet/src/widgets/bottom_sheet/swap_details_bottom_sheet.dart';
@@ -166,16 +167,20 @@ class SwapConfirmationContentState extends State<SwapConfirmationContent> {
167 width: 32,
168 height: 32,
169 ),
169 - Positioned(
170 - bottom: -4,
171 - right: -4,
172 - child: CakeImageWidget(
173 - imageUrl: walletTypeToCryptoCurrency(widget.paymentFlowResult.walletType!)
174 - .iconPath!,
175 - width: 16,
176 - height: 16,
170 + if (isEVMCompatibleChain(widget.paymentFlowResult.walletType!)) ...[
171 + Positioned(
172 + bottom: -4,
173 + right: -4,
174 + child: CakeImageWidget(
175 + imageUrl: getCryptoCurrencyForWalletListItem(
176 + widget.paymentFlowResult.walletType!,
177 + chainId: widget.paymentFlowResult.chainId,
178 + ).iconPath!,
179 + width: 16,
180 + height: 16,
181 + ),
182 ),
178 - ),
183 + ],
184 ],
185 ),
186 ],
lib/src/widgets/bottom_sheet/swap_details_bottom_sheet.dart
+63 -18
@@ -34,11 +34,14 @@ class _SwapDetailsBottomSheetState extends State<SwapDetailsBottomSheet> {
34 bool _effectsInstalled = false;
35 ReactionDisposer? _exchangeStateReaction;
36 BuildContext? _loadingBottomSheetContext;
37 + bool _showingFailureDialog = false;
38
39 @override
40 void initState() {
41 super.initState();
41 - WidgetsBinding.instance.addPostFrameCallback((_) => _setEffects());
42 + WidgetsBinding.instance.addPostFrameCallback((_) {
43 + if (mounted) _setEffects();
44 + });
45 }
46
47 @override
@@ -49,7 +52,39 @@ class _SwapDetailsBottomSheetState extends State<SwapDetailsBottomSheet> {
52 }
53
54 void _setEffects() {
52 - if (_effectsInstalled) return;
55 + if (_effectsInstalled) {
56 + printV('Swap details bottom sheet effects already installed');
57 + return;
58 + }
59 +
60 + final initialState = widget.exchangeTradeViewModel.sendViewModel.state;
61 +
62 + if (initialState is FailureState && !_showingFailureDialog) {
63 + _showingFailureDialog = true;
64 + printV('Initial failure state: $initialState');
65 + WidgetsBinding.instance.addPostFrameCallback((_) {
66 + if (mounted && context.mounted) {
67 + showPopUp<void>(
68 + context: context,
69 + builder: (BuildContext popupContext) {
70 + return AlertWithOneAction(
71 + key: ValueKey('swap_details_send_failure_dialog_key'),
72 + buttonKey: ValueKey('swap_details_send_failure_dialog_button_key'),
73 + alertTitle: S.of(popupContext).error,
74 + alertContent: initialState.error,
75 + buttonText: S.of(popupContext).ok,
76 + buttonAction: () {
77 + _showingFailureDialog = false;
78 + Navigator.of(popupContext).pop();
79 + },
80 + );
81 + },
82 + );
83 + } else {
84 + _showingFailureDialog = false;
85 + }
86 + });
87 + }
88
89 _exchangeStateReaction = reaction(
90 (_) => widget.exchangeTradeViewModel.sendViewModel.state,
@@ -61,21 +96,29 @@ class _SwapDetailsBottomSheetState extends State<SwapDetailsBottomSheet> {
96 Navigator.of(_loadingBottomSheetContext!).pop();
97 }
98
64 - if (state is FailureState) {
99 + if (state is FailureState && !_showingFailureDialog) {
100 + _showingFailureDialog = true;
101 WidgetsBinding.instance.addPostFrameCallback((_) {
66 - showPopUp<void>(
67 - context: context,
68 - builder: (BuildContext popupContext) {
69 - return AlertWithOneAction(
70 - key: ValueKey('swap_details_send_failure_dialog_key'),
71 - buttonKey: ValueKey('swap_details_send_failure_dialog_button_key'),
72 - alertTitle: S.of(popupContext).error,
73 - alertContent: state.error,
74 - buttonText: S.of(popupContext).ok,
75 - buttonAction: () => Navigator.of(popupContext).pop(),
76 - );
77 - },
78 - );
102 + if (mounted && context.mounted) {
103 + showPopUp<void>(
104 + context: context,
105 + builder: (BuildContext popupContext) {
106 + return AlertWithOneAction(
107 + key: ValueKey('swap_details_send_failure_dialog_key'),
108 + buttonKey: ValueKey('swap_details_send_failure_dialog_button_key'),
109 + alertTitle: S.of(popupContext).error,
110 + alertContent: state.error,
111 + buttonText: S.of(popupContext).ok,
112 + buttonAction: () {
113 + _showingFailureDialog = false;
114 + Navigator.of(popupContext).pop();
115 + },
116 + );
117 + },
118 + );
119 + } else {
120 + _showingFailureDialog = false;
121 + }
122 });
123 }
124
@@ -272,13 +315,15 @@ class _SwapDetailsContent extends StatelessWidget {
315 children: [
316 _SwapDetailsTile(
317 label: 'You Send',
275 - value: '${trade.amount} ${trade.from?.title ?? trade.userCurrencyFrom?.title ?? ''}',
318 + value:
319 + '${trade.amount} ${trade.from?.title ?? trade.userCurrencyFrom?.title ?? ''}',
320 valueFiatFormatted: exchangeTradeViewModel.sendAmountFiatFormatted,
321 ),
322 const SizedBox(height: 8),
323 _SwapDetailsTile(
324 label: 'You Get',
281 - value: '${trade.receiveAmount ?? '0'} ${trade.to?.title ?? trade.userCurrencyTo?.title ?? ''}',
325 + value:
326 + '${trade.receiveAmount ?? '0'} ${trade.to?.title ?? trade.userCurrencyTo?.title ?? ''}',
327 valueFiatFormatted: exchangeTradeViewModel
328 .getReceiveAmountFiatFormatted(trade.receiveAmount ?? '0.0'),
329 ),
lib/src/widgets/bottom_sheet/wallet_switcher_bottom_sheet.dart
+6 -4
@@ -57,7 +57,7 @@ class _WalletSwitcherContent extends StatelessWidget {
57 builder: (context, snapshot) => Observer(
58 builder: (_) {
59 final List<WalletInfo> wallets = (snapshot.data ?? []);
60 -
60 +
61 if (viewModel.isProcessing) {
62 return Container(
63 height: 200,
@@ -68,7 +68,7 @@ class _WalletSwitcherContent extends StatelessWidget {
68 ),
69 );
70 }
71 -
71 +
72 return Container(
73 height: 400,
74 child: Column(
@@ -79,7 +79,7 @@ class _WalletSwitcherContent extends StatelessWidget {
79 itemCount: wallets.length,
80 itemBuilder: (context, index) {
81 final wallet = wallets[index];
82 -
82 +
83 return InkWell(
84 onTap: () {
85 viewModel.selectWallet(wallet);
@@ -97,7 +97,9 @@ class _WalletSwitcherContent extends StatelessWidget {
97 child: Row(
98 children: [
99 Image.asset(
100 - walletTypeToCryptoCurrency(wallet.type).iconPath!,
100 + getCryptoCurrencyForWalletListItem(
101 + wallet.type,
102 + ).iconPath!,
103 width: 32,
104 height: 32,
105 ),
lib/src/widgets/evm_switcher.dart new
+376
@@ -0,0 +1,376 @@
1 +import 'package:cake_wallet/evm/evm.dart';
2 +import 'package:flutter/material.dart';
3 +import 'package:flutter_svg/flutter_svg.dart';
4 +
5 +import 'evm_switcher_row.dart';
6 +
7 +class EvmSwitcherDataItem {
8 + final String name;
9 + final String svgPath;
10 + final int chainId;
11 +
12 + const EvmSwitcherDataItem({
13 + required this.name,
14 + required this.svgPath,
15 + required this.chainId,
16 + });
17 +
18 + static const ethereum = EvmSwitcherDataItem(
19 + name: 'Ethereum',
20 + svgPath: 'assets/images/evm_switcher_icons/ethereum.svg',
21 + chainId: 1,
22 + );
23 +
24 + static const polygon = EvmSwitcherDataItem(
25 + name: 'Polygon',
26 + svgPath: 'assets/images/evm_switcher_icons/polygon.svg',
27 + chainId: 137,
28 + );
29 +
30 + static const arbitrum = EvmSwitcherDataItem(
31 + name: 'Arbitrum',
32 + svgPath: 'assets/images/evm_switcher_icons/arbitrum.svg',
33 + chainId: 42161,
34 + );
35 +
36 + static const base = EvmSwitcherDataItem(
37 + name: 'Base',
38 + svgPath: 'assets/images/evm_switcher_icons/base.svg',
39 + chainId: 8453,
40 + );
41 +
42 + static const items = [
43 + ethereum,
44 + polygon,
45 + arbitrum,
46 + base,
47 + ];
48 +}
49 +
50 +String _getSvgPathForChain(String chainName) {
51 + final name = chainName.toLowerCase();
52 + if (name.contains('ethereum')) {
53 + return 'assets/images/evm_switcher_icons/ethereum.svg';
54 + } else if (name.contains('polygon')) {
55 + return 'assets/images/evm_switcher_icons/polygon.svg';
56 + } else if (name.contains('arbitrum')) {
57 + return 'assets/images/evm_switcher_icons/arbitrum.svg';
58 + } else if (name.contains('base')) {
59 + return 'assets/images/evm_switcher_icons/base.svg';
60 + }
61 + // Default to ethereum if unknown
62 + return 'assets/images/evm_switcher_icons/ethereum.svg';
63 +}
64 +
65 +class EvmSwitcher extends StatefulWidget {
66 + const EvmSwitcher({
67 + super.key,
68 + required this.chains,
69 + required this.currentChain,
70 + required this.onChainSelected,
71 + required this.hiddenChainIds,
72 + required this.onHiddenChanged,
73 + });
74 +
75 + final List<ChainInfo> chains;
76 + final ChainInfo? currentChain;
77 + final Future<void> Function(int chainId) onChainSelected;
78 + final Set<int> hiddenChainIds;
79 + final void Function(Set<int> hiddenChainIds) onHiddenChanged;
80 +
81 + static const editModeAnimDuration = Duration(milliseconds: 200);
82 +
83 + @override
84 + State<EvmSwitcher> createState() => _EvmSwitcherState();
85 +}
86 +
87 +class _EvmSwitcherState extends State<EvmSwitcher> {
88 + bool _editMode = false;
89 + var optionsEnabled = <bool>[];
90 +
91 + @override
92 + void initState() {
93 + super.initState();
94 + _syncOptionsEnabled();
95 + }
96 +
97 + @override
98 + void didUpdateWidget(covariant EvmSwitcher oldWidget) {
99 + super.didUpdateWidget(oldWidget);
100 + if (oldWidget.chains != widget.chains ||
101 + !_hiddenSetsEqual(oldWidget.hiddenChainIds, widget.hiddenChainIds)) {
102 + _syncOptionsEnabled();
103 + }
104 + }
105 +
106 + void _syncOptionsEnabled() {
107 + optionsEnabled = widget.chains
108 + .map((chain) => !widget.hiddenChainIds.contains(chain.chainId))
109 + .toList(growable: false);
110 + }
111 +
112 + bool _hiddenSetsEqual(Set<int> a, Set<int> b) =>
113 + a.length == b.length && a.containsAll(b);
114 +
115 + int get _selectedIndex {
116 + if (widget.currentChain == null) return -1;
117 + return widget.chains.indexWhere(
118 + (chain) => chain.chainId == widget.currentChain!.chainId,
119 + );
120 + }
121 +
122 + bool shouldBuildSeparator(int index) {
123 + final nextEnabled = optionsEnabled.indexWhere(
124 + (e) => e,
125 + index + 1,
126 + );
127 +
128 + return index != _selectedIndex &&
129 + optionsEnabled[index] &&
130 + nextEnabled != _selectedIndex &&
131 + nextEnabled != -1;
132 + }
133 +
134 + Set<int> _currentHiddenChainIds() {
135 + final hidden = <int>{};
136 + for (var i = 0; i < widget.chains.length; i++) {
137 + if (i < optionsEnabled.length && !optionsEnabled[i]) {
138 + hidden.add(widget.chains[i].chainId);
139 + }
140 + }
141 + return hidden;
142 + }
143 +
144 + @override
145 + Widget build(BuildContext context) {
146 + final double popupWidth = MediaQuery.of(context).size.width * 0.9;
147 + return Center(
148 + child: Column(
149 + spacing: 25.0,
150 + mainAxisAlignment: MainAxisAlignment.center,
151 + children: [
152 + Text(
153 + _editMode ? "Customize options" : "Select Network",
154 + style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
155 + ),
156 + ClipRRect(
157 + borderRadius: BorderRadius.circular(20),
158 + child: Container(
159 + decoration: BoxDecoration(
160 + borderRadius: BorderRadius.circular(20),
161 + color: Theme.of(context).colorScheme.surfaceContainer,
162 + ),
163 + child: AnimatedContainer(
164 + duration: EvmSwitcher.editModeAnimDuration,
165 + width: popupWidth,
166 + child: Row(
167 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
168 + children: [
169 + AnimatedSize(
170 + curve: Curves.easeOutCubic,
171 + duration: EvmSwitcher.editModeAnimDuration,
172 + child: Container(
173 + width: _editMode ? 0 : popupWidth,
174 + child: Column(
175 + mainAxisSize: MainAxisSize.min,
176 + children: [
177 + ListView.separated(
178 + shrinkWrap: true,
179 + physics: const NeverScrollableScrollPhysics(),
180 + itemBuilder: (context, index) {
181 + if (optionsEnabled[index]) {
182 + final chain = widget.chains[index];
183 + final data = EvmSwitcherDataItem(
184 + name: chain.name,
185 + svgPath: _getSvgPathForChain(chain.name),
186 + chainId: chain.chainId,
187 + );
188 + return EvmSwitcherRow(
189 + key: ValueKey(chain.chainId),
190 + data: data,
191 + editMode: false,
192 + selected: index == _selectedIndex,
193 + onTap: () {
194 + widget.onChainSelected(chain.chainId);
195 + if (mounted) {
196 + Navigator.of(context).pop();
197 + }
198 + },
199 + editSwitchValue: true,
200 + animDuration: EvmSwitcher.editModeAnimDuration,
201 + );
202 + } else {
203 + return Container();
204 + }
205 + },
206 + separatorBuilder: (context, index) {
207 + if (shouldBuildSeparator(index))
208 + return Padding(
209 + padding: const EdgeInsets.symmetric(horizontal: 18),
210 + child: Container(
211 + height: 1,
212 + width: double.infinity,
213 + color:
214 + Theme.of(context).colorScheme.surfaceContainerHigh,
215 + ));
216 + else
217 + return Container(height: 1);
218 + },
219 + itemCount: widget.chains.length),
220 + EvmSwitcherAdditionalOption(
221 + title: "Customize options",
222 + svgPath: "assets/images/evm_switcher_arrow_right.svg",
223 + animDuration: EvmSwitcher.editModeAnimDuration,
224 + topSeparator: true,
225 + bottomSeparator: false,
226 + visible: true,
227 + onTap: () {
228 + setState(() {
229 + _editMode = true;
230 + });
231 + },
232 + iconOnRight: true)
233 + ],
234 + ),
235 + ),
236 + ),
237 + AnimatedSize(
238 + curve: Curves.easeOutCubic,
239 + duration: EvmSwitcher.editModeAnimDuration,
240 + child: Container(
241 + width: _editMode ? popupWidth : 0,
242 + height: _editMode ? null : 0,
243 + child: Column(
244 + mainAxisSize: MainAxisSize.min,
245 + children: [
246 + EvmSwitcherAdditionalOption(
247 + title: "Back",
248 + svgPath: "assets/images/evm_switcher_arrow_left.svg",
249 + animDuration: EvmSwitcher.editModeAnimDuration,
250 + topSeparator: false,
251 + bottomSeparator: true,
252 + visible: true,
253 + onTap: () {
254 + setState(() {
255 + _editMode = false;
256 + });
257 + },
258 + iconOnRight: false),
259 + ListView.separated(
260 + shrinkWrap: true,
261 + physics: const NeverScrollableScrollPhysics(),
262 + itemBuilder: (context, index) {
263 + final chain = widget.chains[index];
264 + final data = EvmSwitcherDataItem(
265 + name: chain.name,
266 + svgPath: _getSvgPathForChain(chain.name),
267 + chainId: chain.chainId,
268 + );
269 + return EvmSwitcherRow(
270 + key: ValueKey(chain.chainId),
271 + data: data,
272 + editMode: _editMode,
273 + selected: index == _selectedIndex,
274 + onTap: () {
275 + setState(() {
276 + optionsEnabled[index] = !optionsEnabled[index];
277 + });
278 + widget.onHiddenChanged(
279 + _currentHiddenChainIds(),
280 + );
281 + },
282 + editSwitchValue: optionsEnabled[index],
283 + animDuration: EvmSwitcher.editModeAnimDuration,
284 + );
285 + },
286 + separatorBuilder: (context, index) {
287 + return Padding(
288 + padding: const EdgeInsets.symmetric(horizontal: 18),
289 + child: Container(
290 + height: 1,
291 + width: double.infinity,
292 + color: Theme.of(context).colorScheme.surfaceContainerHigh,
293 + ));
294 + },
295 + itemCount: widget.chains.length),
296 + ],
297 + ),
298 + ),
299 + ),
300 + ],
301 + )),
302 + ),
303 + ),
304 + ],
305 + ),
306 + );
307 + }
308 +}
309 +
310 +class EvmSwitcherAdditionalOption extends StatelessWidget {
311 + const EvmSwitcherAdditionalOption(
312 + {super.key,
313 + required this.title,
314 + required this.svgPath,
315 + required this.animDuration,
316 + required this.topSeparator,
317 + required this.bottomSeparator,
318 + required this.visible,
319 + required this.onTap,
320 + required this.iconOnRight});
321 +
322 + final String title;
323 + final String svgPath;
324 + final Duration animDuration;
325 + final bool topSeparator;
326 + final bool bottomSeparator;
327 + final bool visible;
328 + final VoidCallback onTap;
329 + final bool iconOnRight;
330 +
331 + @override
332 + Widget build(BuildContext context) {
333 + return GestureDetector(
334 + onTap: onTap,
335 + behavior: HitTestBehavior.translucent,
336 + child: Container(
337 + child: Column(
338 + mainAxisSize: MainAxisSize.max,
339 + children: [
340 + if (topSeparator)
341 + Padding(
342 + padding: const EdgeInsets.symmetric(horizontal: 18),
343 + child: Container(
344 + height: 1,
345 + width: double.infinity,
346 + color: Theme.of(context).colorScheme.surfaceContainerHigh,
347 + )),
348 + Padding(
349 + padding: const EdgeInsets.all(18),
350 + child: Row(
351 + mainAxisSize: MainAxisSize.max,
352 + spacing: 8.0,
353 + children: [
354 + if (!iconOnRight) SvgPicture.asset(svgPath, width: 16, height: 16),
355 + Text(
356 + title,
357 + style: TextStyle(color: Theme.of(context).colorScheme.primary),
358 + ),
359 + if (iconOnRight) SvgPicture.asset(svgPath, width: 16, height: 16),
360 + ],
361 + ),
362 + ),
363 + if (bottomSeparator)
364 + Padding(
365 + padding: const EdgeInsets.symmetric(horizontal: 18),
366 + child: Container(
367 + height: 1,
368 + width: double.infinity,
369 + color: Theme.of(context).colorScheme.surfaceContainerHigh,
370 + )),
371 + ],
372 + ),
373 + ),
374 + );
375 + }
376 +}
lib/src/widgets/evm_switcher_row.dart new
+71
@@ -0,0 +1,71 @@
1 +import 'package:cake_wallet/src/widgets/evm_switcher.dart';
2 +import 'package:cake_wallet/src/widgets/standard_switch.dart';
3 +import 'package:flutter/material.dart';
4 +import 'package:flutter_svg/flutter_svg.dart';
5 +
6 +class EvmSwitcherRow extends StatelessWidget {
7 + const EvmSwitcherRow({
8 + super.key,
9 + required this.editMode,
10 + required this.selected,
11 + required this.data,
12 + required this.onTap,
13 + required this.animDuration,
14 + required this.editSwitchValue,
15 + });
16 +
17 + final bool editMode;
18 + final bool selected;
19 + final EvmSwitcherDataItem data;
20 + final VoidCallback onTap;
21 + final Duration animDuration;
22 + final bool editSwitchValue;
23 +
24 + @override
25 + Widget build(BuildContext context) {
26 + final Color resolvedForegroundColor = editMode || selected
27 + ? Theme.of(context).colorScheme.onSurface
28 + : Theme.of(context).colorScheme.primary;
29 +
30 + final Color resolvedBackgroundColor = !editMode && selected
31 + ? Theme.of(context).colorScheme.surfaceContainerHighest
32 + : Theme.of(context).colorScheme.surfaceContainerHighest.withAlpha(0);
33 +
34 + return GestureDetector(
35 + onTap: onTap,
36 + child: AnimatedContainer(
37 + duration: animDuration,
38 + color: resolvedBackgroundColor,
39 + child: Padding(
40 + padding: const EdgeInsets.all(18.0),
41 + child: Row(
42 + mainAxisSize: MainAxisSize.max,
43 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
44 + children: [
45 + Row(
46 + spacing: 8.0,
47 + children: [
48 + SvgPicture.asset(
49 + data.svgPath,
50 + width: 16,
51 + height: 16,
52 + colorFilter: ColorFilter.mode(
53 + resolvedForegroundColor, BlendMode.srcIn),
54 + ),
55 + Text(data.name,
56 + style: TextStyle(
57 + color: resolvedForegroundColor, fontSize: 14)),
58 + if (selected && !editMode)
59 + SvgPicture.asset("assets/images/evm_switcher_checkmark.svg",
60 + width: 18, height: 18),
61 + ],
62 + ),
63 + if (editMode)
64 + StandardSwitch(value: editSwitchValue, onTapped: onTap)
65 + ],
66 + ),
67 + ),
68 + ),
69 + );
70 + }
71 +}
lib/store/dashboard/trade_filter_store.dart
+4 -1
@@ -173,7 +173,10 @@ abstract class TradeFilterStoreBase with Store {
173
174 List<TradeListItem> filtered({required List<TradeListItem> trades, required WalletBase wallet}) {
175 final _trades = trades
176 - .where((item) => item.trade.walletId == wallet.id && isTradeInAccount(item, wallet))
176 + .where((item) {
177 + final isSameChain = item.trade.chainId != null ? item.trade.chainId == wallet.chainId : true; // returning default as true here so it falls back to the default checks if there's no chainId
178 + return item.trade.walletId == wallet.id && isTradeInAccount(item, wallet) && isSameChain;
179 + })
180 .toList();
181 final needToFilter = !displayAllTrades;
182
lib/store/settings_store.dart
+169 -91
@@ -2,7 +2,6 @@ import 'dart:async';
2 import 'dart:convert';
3 import 'dart:io';
4
5 -import 'package:cake_wallet/base/base.dart';
5 import 'package:cake_wallet/bitcoin/bitcoin.dart';
6 import 'package:cake_wallet/core/utilities.dart';
7 import 'package:cake_wallet/decred/decred.dart';
@@ -27,13 +26,13 @@ import 'package:cake_wallet/entities/seed_type.dart';
26 import 'package:cake_wallet/entities/sort_balance_types.dart';
27 import 'package:cake_wallet/entities/sync_status_display_mode.dart';
28 import 'package:cake_wallet/entities/wallet_list_order_types.dart';
30 -import 'package:cake_wallet/ethereum/ethereum.dart';
29 +import 'package:cake_wallet/evm/evm.dart';
30 +import 'package:cake_wallet/reactions/wallet_connect.dart';
31 import 'package:cake_wallet/wownero/wownero.dart';
32 import 'package:cake_wallet/zano/zano.dart';
33 import 'package:cw_core/transaction_priority.dart';
34 import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
35 import 'package:cake_wallet/monero/monero.dart';
36 -import 'package:cake_wallet/polygon/polygon.dart';
36 import 'package:cake_wallet/utils/device_info.dart';
37 import 'package:cake_wallet/utils/package_info.dart';
38 import 'package:cake_wallet/view_model/settings/sync_mode.dart';
@@ -71,6 +70,7 @@ abstract class SettingsStoreBase with Store {
70 required FilterListOrderType initialWalletListOrder,
71 required FilterListOrderType initialContactListOrder,
72 required bool initialDisableBulletin,
73 + required this.useBlinkProtection,
74 required bool initialWalletListAscending,
75 required bool initialContactListAscending,
76 required FiatApiMode initialFiatMode,
@@ -115,6 +115,7 @@ abstract class SettingsStoreBase with Store {
115 required this.usePolygonScan,
116 required this.useTronGrid,
117 required this.useMempoolFeeAPI,
118 + required List<int> initialEvmHiddenChainIds,
119 required this.defaultNanoRep,
120 required this.defaultBananoRep,
121 required this.lookupsTwitter,
@@ -142,6 +143,7 @@ abstract class SettingsStoreBase with Store {
143 TransactionPriority? initialHavenTransactionPriority,
144 TransactionPriority? initialLitecoinTransactionPriority,
145 TransactionPriority? initialEthereumTransactionPriority,
146 + TransactionPriority? initialEVMTransactionPriority,
147 TransactionPriority? initialPolygonTransactionPriority,
148 TransactionPriority? initialBaseTransactionPriority,
149 TransactionPriority? initialBitcoinCashTransactionPriority,
@@ -199,6 +201,7 @@ abstract class SettingsStoreBase with Store {
201 currentBuiltinTor = initialBuiltinTor,
202 enableAutomaticNodeSwitching = initialEnableAutomaticNodeSwitching,
203 backgroundImage = initialBackgroundImage,
204 + evmHiddenChainIds = ObservableSet.of(initialEvmHiddenChainIds),
205 priority = ObservableMap<WalletType, TransactionPriority>() {
206 //this.nodes = ObservableMap<WalletType, Node>.of(nodes);
207
@@ -226,6 +229,7 @@ abstract class SettingsStoreBase with Store {
229 priority[WalletType.ethereum] = initialEthereumTransactionPriority;
230 }
231
232 +
233 if (initialPolygonTransactionPriority != null) {
234 priority[WalletType.polygon] = initialPolygonTransactionPriority;
235 }
@@ -254,14 +258,11 @@ abstract class SettingsStoreBase with Store {
258 (FiatCurrency fiatCurrency) => sharedPreferences.setString(
259 PreferencesKey.currentFiatCurrencyKey, fiatCurrency.serialize()));
260
257 - reaction(
258 - (_) => selectedCakePayCountry,
259 - (Country? country) {
260 - if (country != null) {
261 - sharedPreferences.setString(
262 - PreferencesKey.currentCakePayCountry, country.raw);
263 - }
264 - });
261 + reaction((_) => selectedCakePayCountry, (Country? country) {
262 + if (country != null) {
263 + sharedPreferences.setString(PreferencesKey.currentCakePayCountry, country.raw);
264 + }
265 + });
266
267 reaction(
268 (_) => shouldShowYatPopup,
@@ -270,8 +271,8 @@ abstract class SettingsStoreBase with Store {
271
272 reaction(
273 (_) => shouldShowDEuroDisclaimer,
273 - (bool shouldShowDEuroDisclaimer) =>
274 - sharedPreferences.setBool(PreferencesKey.shouldShowDEuroDisclaimer, shouldShowDEuroDisclaimer));
274 + (bool shouldShowDEuroDisclaimer) => sharedPreferences.setBool(
275 + PreferencesKey.shouldShowDEuroDisclaimer, shouldShowDEuroDisclaimer));
276
277 reaction((_) => shouldShowRepWarning,
278 (bool val) => sharedPreferences.setBool(PreferencesKey.shouldShowRepWarning, val));
@@ -333,11 +334,16 @@ abstract class SettingsStoreBase with Store {
334 });
335 }
336
336 - reaction((_) => disableTradeOption,
337 - (bool disableTradeOption) => sharedPreferences.setBool(PreferencesKey.disableTradeOption, disableTradeOption));
337 + reaction(
338 + (_) => disableTradeOption,
339 + (bool disableTradeOption) =>
340 + sharedPreferences.setBool(PreferencesKey.disableTradeOption, disableTradeOption));
341
339 - reaction((_) => disableAutomaticExchangeStatusUpdates,
340 - (bool disableAutomaticExchangeStatusUpdates) => sharedPreferences.setBool(PreferencesKey.disableAutomaticExchangeStatusUpdates, disableAutomaticExchangeStatusUpdates));
342 + reaction(
343 + (_) => disableAutomaticExchangeStatusUpdates,
344 + (bool disableAutomaticExchangeStatusUpdates) => sharedPreferences.setBool(
345 + PreferencesKey.disableAutomaticExchangeStatusUpdates,
346 + disableAutomaticExchangeStatusUpdates));
347
348 reaction(
349 (_) => disableBulletin,
@@ -350,8 +356,8 @@ abstract class SettingsStoreBase with Store {
356 sharedPreferences.setInt(PreferencesKey.walletListOrder, walletListOrder.index));
357
358 reaction(
353 - (_) => contactListOrder,
354 - (FilterListOrderType contactListOrder) =>
359 + (_) => contactListOrder,
360 + (FilterListOrderType contactListOrder) =>
361 sharedPreferences.setInt(PreferencesKey.contactListOrder, contactListOrder.index));
362
363 reaction(
@@ -360,8 +366,8 @@ abstract class SettingsStoreBase with Store {
366 sharedPreferences.setBool(PreferencesKey.walletListAscending, walletListAscending));
367
368 reaction(
363 - (_) => contactListAscending,
364 - (bool contactListAscending) =>
369 + (_) => contactListAscending,
370 + (bool contactListAscending) =>
371 sharedPreferences.setBool(PreferencesKey.contactListAscending, contactListAscending));
372
373 reaction(
@@ -400,13 +406,13 @@ abstract class SettingsStoreBase with Store {
406 sharedPreferences.setBool(PreferencesKey.shouldShowMarketPlaceInDashboard, value));
407
408 reaction(
403 - (_) => showAddressBookPopupEnabled,
404 - (bool value) =>
409 + (_) => showAddressBookPopupEnabled,
410 + (bool value) =>
411 sharedPreferences.setBool(PreferencesKey.showAddressBookPopupEnabled, value));
412
413 reaction(
408 - (_) => syncStatusDisplayMode,
409 - (SyncStatusDisplayMode value) =>
414 + (_) => syncStatusDisplayMode,
415 + (SyncStatusDisplayMode value) =>
416 sharedPreferences.setString(PreferencesKey.syncStatusDisplayMode, value.toJson()));
417
418 reaction((_) => pinCodeLength,
@@ -440,7 +446,6 @@ abstract class SettingsStoreBase with Store {
446 sharedPreferences.setBool(PreferencesKey.builtinTorKey, builtinTor);
447 });
448
443 -
449 reaction(
450 (_) => exchangeStatus,
451 (ExchangeApiMode mode) =>
@@ -466,15 +471,11 @@ abstract class SettingsStoreBase with Store {
471 (bool usePolygonScan) =>
472 _sharedPreferences.setBool(PreferencesKey.usePolygonScan, usePolygonScan));
473
469 - reaction(
470 - (_) => useBaseScan,
471 - (bool useBaseScan) =>
472 - _sharedPreferences.setBool(PreferencesKey.useBaseScan, useBaseScan));
473 -
474 - reaction(
475 - (_) => useArbiScan,
476 - (bool useArbiScan) =>
477 - _sharedPreferences.setBool(PreferencesKey.useArbiScan, useArbiScan));
474 + reaction((_) => useBaseScan,
475 + (bool useBaseScan) => _sharedPreferences.setBool(PreferencesKey.useBaseScan, useBaseScan));
476 +
477 + reaction((_) => useArbiScan,
478 + (bool useArbiScan) => _sharedPreferences.setBool(PreferencesKey.useArbiScan, useArbiScan));
479
480 reaction((_) => useTronGrid,
481 (bool useTronGrid) => _sharedPreferences.setBool(PreferencesKey.useTronGrid, useTronGrid));
@@ -484,6 +485,9 @@ abstract class SettingsStoreBase with Store {
485 (bool useMempoolFeeAPI) =>
486 _sharedPreferences.setBool(PreferencesKey.useMempoolFeeAPI, useMempoolFeeAPI));
487
488 + reaction((_) => useBlinkProtection,
489 + (bool value) => _sharedPreferences.setBool(PreferencesKey.useBlinkProtection, value));
490 +
491 reaction((_) => defaultNanoRep,
492 (String nanoRep) => _sharedPreferences.setString(PreferencesKey.defaultNanoRep, nanoRep));
493
@@ -528,15 +532,13 @@ abstract class SettingsStoreBase with Store {
532 (bool looksUpWellKnown) =>
533 _sharedPreferences.setBool(PreferencesKey.lookupsWellKnown, looksUpWellKnown));
534
531 - reaction(
532 - (_) => usePayjoin,
533 - (bool usePayjoin) =>
534 - _sharedPreferences.setBool(PreferencesKey.usePayjoin, usePayjoin));
535 + reaction((_) => usePayjoin,
536 + (bool usePayjoin) => _sharedPreferences.setBool(PreferencesKey.usePayjoin, usePayjoin));
537
538 reaction(
539 (_) => showPayjoinCard,
538 - (bool showPayjoinCard) => _sharedPreferences.setBool(
539 - PreferencesKey.showPayjoinCard, showPayjoinCard));
540 + (bool showPayjoinCard) =>
541 + _sharedPreferences.setBool(PreferencesKey.showPayjoinCard, showPayjoinCard));
542
543 // secure storage keys:
544 reaction(
@@ -546,10 +548,9 @@ abstract class SettingsStoreBase with Store {
548 value: biometricalAuthentication.toString()));
549
550 reaction(
549 - (_) => enableDuressPin,
550 - (bool enableDuressPin) => secureStorage.write(
551 - key: SecureKey.enableDuressPin,
552 - value: enableDuressPin.toString()));
551 + (_) => enableDuressPin,
552 + (bool enableDuressPin) =>
553 + secureStorage.write(key: SecureKey.enableDuressPin, value: enableDuressPin.toString()));
554
555 reaction(
556 (_) => selectedCake2FAPreset,
@@ -637,6 +638,11 @@ abstract class SettingsStoreBase with Store {
638 (bool mwebAlwaysScan) =>
639 _sharedPreferences.setBool(PreferencesKey.mwebAlwaysScan, mwebAlwaysScan));
640
641 + reaction(
642 + (_) => evmHiddenChainIds.toList(growable: false),
643 + (List<int> hiddenIds) => _sharedPreferences.setStringList(
644 + PreferencesKey.evmHiddenChainIds, hiddenIds.map((id) => id.toString()).toList()));
645 +
646 reaction(
647 (_) => mwebCardDisplay,
648 (bool mwebCardDisplay) =>
@@ -654,7 +660,7 @@ abstract class SettingsStoreBase with Store {
660 (_) => mwebNodeUri,
661 (String mwebNodeUri) =>
662 _sharedPreferences.setString(PreferencesKey.mwebNodeUri, mwebNodeUri));
657 -
663 +
664 reaction(
665 (_) => enableAutomaticNodeSwitching,
666 (bool enableAutomaticNodeSwitching) => _sharedPreferences.setBool(
@@ -662,8 +668,8 @@ abstract class SettingsStoreBase with Store {
668
669 reaction(
670 (_) => backgroundImage,
665 - (String backgroundImage) => _sharedPreferences.setString(
666 - PreferencesKey.backgroundImage, backgroundImage));
671 + (String backgroundImage) =>
672 + _sharedPreferences.setString(PreferencesKey.backgroundImage, backgroundImage));
673
674 this.nodes.observe((change) {
675 if (change.newValue != null && change.key != null) {
@@ -676,8 +682,6 @@ abstract class SettingsStoreBase with Store {
682 _saveCurrentPowNode(change.newValue!, change.key!);
683 }
684 });
679 -
680 -
685 }
686
687 static const defaultPinLength = 4;
@@ -747,6 +751,9 @@ abstract class SettingsStoreBase with Store {
751 @observable
752 bool disableAutomaticExchangeStatusUpdates;
753
754 + @observable
755 + bool useBlinkProtection;
756 +
757 @observable
758 FilterListOrderType contactListOrder;
759
@@ -852,6 +859,9 @@ abstract class SettingsStoreBase with Store {
859 @observable
860 bool useMempoolFeeAPI;
861
862 + @observable
863 + ObservableSet<int> evmHiddenChainIds;
864 +
865 @observable
866 String defaultNanoRep;
867
@@ -934,16 +944,45 @@ abstract class SettingsStoreBase with Store {
944 ObservableMap<WalletType, Node> nodes;
945 ObservableMap<WalletType, Node> powNodes;
946
937 - Node getCurrentNode(WalletType walletType) {
938 - final node = nodes[walletType];
947 + Node getCurrentNode(WalletType walletType, {int? chainId}) {
948 + if (chainId != null && isEVMCompatibleChain(walletType)) {
949 + final preferenceKey = _getEVMNodePreferenceKey(chainId);
950 + final nodeId = _sharedPreferences.getInt(preferenceKey);
951 +
952 + if (nodeId != null) {
953 + final walletTypeForChain = evm!.getWalletTypeByChainId(chainId);
954 + if (walletTypeForChain != null) {
955 + final node = nodes[walletTypeForChain];
956 + if (node != null) return node;
957 + }
958 + }
959 +
960 + throw Exception('No node found for EVM wallet type with chainId: $chainId');
961 + }
962
963 + final node = nodes[walletType];
964 if (node == null) {
965 throw Exception('No node found for wallet type: ${walletType.toString()}');
966 }
943 -
967 return node;
968 }
969
970 + String _getEVMNodePreferenceKey(int chainId) {
971 + switch (chainId) {
972 + case 1:
973 + return PreferencesKey.currentEthereumNodeIdKey;
974 + case 137:
975 + return PreferencesKey.currentPolygonNodeIdKey;
976 + case 8453:
977 + return PreferencesKey.currentBaseNodeIdKey;
978 + case 42161:
979 + return PreferencesKey.currentArbitrumNodeIdKey;
980 + default:
981 + // Default to Ethereum for unknown chainIds
982 + return PreferencesKey.currentEthereumNodeIdKey;
983 + }
984 + }
985 +
986 Node getCurrentPowNode(WalletType walletType) {
987 final node = powNodes[walletType];
988
@@ -954,6 +993,17 @@ abstract class SettingsStoreBase with Store {
993 return node;
994 }
995
996 + TransactionPriority? getPriority(WalletType walletType, {int? chainId}) {
997 + if (isEVMCompatibleChain(walletType)) {
998 + if (chainId != null && !evm!.hasPriorityFee(chainId)) return null;
999 + return priority[walletType];
1000 + }
1001 +
1002 + return priority[walletType];
1003 + }
1004 +
1005 + void setPriority(WalletType walletType, TransactionPriority priority, {int? chainId}) => this.priority[walletType] = priority;
1006 +
1007 bool isBitcoinBuyEnabled;
1008
1009 bool get shouldShowReceiveWarning =>
@@ -972,10 +1022,10 @@ abstract class SettingsStoreBase with Store {
1022 final secureStorage = await getIt.get<SecureStorage>();
1023 final currentFiatCurrency = FiatCurrency.deserialize(
1024 raw: sharedPreferences.getString(PreferencesKey.currentFiatCurrencyKey)!);
975 - final savedCakePayCountryRaw = sharedPreferences.getString(PreferencesKey.currentCakePayCountry);
976 - final currentCakePayCountry = savedCakePayCountryRaw != null
977 - ? Country.deserialize(raw: savedCakePayCountryRaw)
978 - : null;
1025 + final savedCakePayCountryRaw =
1026 + sharedPreferences.getString(PreferencesKey.currentCakePayCountry);
1027 + final currentCakePayCountry =
1028 + savedCakePayCountryRaw != null ? Country.deserialize(raw: savedCakePayCountryRaw) : null;
1029
1030 TransactionPriority? moneroTransactionPriority = monero?.deserializeMoneroTransactionPriority(
1031 raw: sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority)!);
@@ -986,6 +1036,7 @@ abstract class SettingsStoreBase with Store {
1036 TransactionPriority? havenTransactionPriority;
1037 TransactionPriority? litecoinTransactionPriority;
1038 TransactionPriority? ethereumTransactionPriority;
1039 + TransactionPriority? evmTransactionPriority;
1040 TransactionPriority? polygonTransactionPriority;
1041 TransactionPriority? baseTransactionPriority;
1042 TransactionPriority? bitcoinCashTransactionPriority;
@@ -1002,15 +1053,17 @@ abstract class SettingsStoreBase with Store {
1053 sharedPreferences.getInt(PreferencesKey.litecoinTransactionPriority)!);
1054 }
1055 if (sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority) != null) {
1005 - ethereumTransactionPriority = ethereum?.deserializeEthereumTransactionPriority(
1056 + ethereumTransactionPriority = evm?.deserializeEVMTransactionPriority(
1057 + sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!);
1058 + evmTransactionPriority = evm?.deserializeEVMTransactionPriority(
1059 sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!);
1060 }
1061 if (sharedPreferences.getInt(PreferencesKey.polygonTransactionPriority) != null) {
1009 - polygonTransactionPriority = polygon?.deserializePolygonTransactionPriority(
1062 + polygonTransactionPriority = evm?.deserializeEVMTransactionPriority(
1063 sharedPreferences.getInt(PreferencesKey.polygonTransactionPriority)!);
1064 }
1065 if (sharedPreferences.getInt(PreferencesKey.baseTransactionPriority) != null) {
1013 - baseTransactionPriority = base?.deserializeBaseTransactionPriority(
1066 + baseTransactionPriority = evm?.deserializeEVMTransactionPriority(
1067 sharedPreferences.getInt(PreferencesKey.baseTransactionPriority)!);
1068 }
1069 if (sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority) != null) {
@@ -1034,12 +1087,13 @@ abstract class SettingsStoreBase with Store {
1087 bitcoinTransactionPriority ??= bitcoin?.getMediumTransactionPriority();
1088 havenTransactionPriority ??= monero?.getDefaultTransactionPriority();
1089 litecoinTransactionPriority ??= bitcoin?.getLitecoinTransactionPriorityMedium();
1037 - ethereumTransactionPriority ??= ethereum?.getDefaultTransactionPriority();
1090 + ethereumTransactionPriority ??= evm?.getDefaultTransactionPriority();
1091 + evmTransactionPriority ??= evm?.getDefaultTransactionPriority();
1092 bitcoinCashTransactionPriority ??= bitcoinCash?.getDefaultTransactionPriority();
1093 wowneroTransactionPriority ??= wownero?.getDefaultTransactionPriority();
1094 decredTransactionPriority ??= decred?.getDecredTransactionPriorityMedium();
1041 - polygonTransactionPriority ??= polygon?.getDefaultTransactionPriority();
1042 - baseTransactionPriority ??= base?.getDefaultTransactionPriority();
1095 + polygonTransactionPriority ??= evm?.getDefaultTransactionPriority();
1096 + baseTransactionPriority ??= evm?.getDefaultTransactionPriority();
1097 zanoTransactionPriority ??= zano?.getDefaultTransactionPriority();
1098
1099 final currentBalanceDisplayMode = BalanceDisplayMode.deserialize(
@@ -1048,13 +1102,15 @@ abstract class SettingsStoreBase with Store {
1102 final shouldSaveRecipientAddress =
1103 sharedPreferences.getBool(PreferencesKey.shouldSaveRecipientAddressKey) ?? false;
1104 final isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? false;
1051 - final disableTradeOption = sharedPreferences.getBool(PreferencesKey.disableTradeOption) ?? false;
1052 - final disableAutomaticExchangeStatusUpdates = sharedPreferences.getBool(PreferencesKey.disableAutomaticExchangeStatusUpdates) ?? false;
1105 + final disableTradeOption =
1106 + sharedPreferences.getBool(PreferencesKey.disableTradeOption) ?? false;
1107 + final disableAutomaticExchangeStatusUpdates =
1108 + sharedPreferences.getBool(PreferencesKey.disableAutomaticExchangeStatusUpdates) ?? false;
1109 final disableBulletin = sharedPreferences.getBool(PreferencesKey.disableBulletinKey) ?? false;
1110 final walletListOrder =
1111 FilterListOrderType.values[sharedPreferences.getInt(PreferencesKey.walletListOrder) ?? 0];
1112 final contactListOrder =
1057 - FilterListOrderType.values[sharedPreferences.getInt(PreferencesKey.contactListOrder) ?? 0];
1113 + FilterListOrderType.values[sharedPreferences.getInt(PreferencesKey.contactListOrder) ?? 0];
1114 final walletListAscending =
1115 sharedPreferences.getBool(PreferencesKey.walletListAscending) ?? true;
1116 final contactListAscending =
@@ -1090,6 +1146,11 @@ abstract class SettingsStoreBase with Store {
1146 final useArbiScan = sharedPreferences.getBool(PreferencesKey.useArbiScan) ?? true;
1147 final useTronGrid = sharedPreferences.getBool(PreferencesKey.useTronGrid) ?? true;
1148 final useMempoolFeeAPI = sharedPreferences.getBool(PreferencesKey.useMempoolFeeAPI) ?? true;
1149 + final useBlinkProtection = sharedPreferences.getBool(PreferencesKey.useBlinkProtection) ?? true;
1150 + final evmHiddenChainIdsRaw =
1151 + sharedPreferences.getStringList(PreferencesKey.evmHiddenChainIds) ?? const <String>[];
1152 + final evmHiddenChainIds =
1153 + evmHiddenChainIdsRaw.map((value) => int.tryParse(value)).whereType<int>().toList();
1154 final defaultNanoRep = sharedPreferences.getString(PreferencesKey.defaultNanoRep) ?? "";
1155 final defaultBananoRep = sharedPreferences.getString(PreferencesKey.defaultBananoRep) ?? "";
1156 final lookupsTwitter = sharedPreferences.getBool(PreferencesKey.lookupsTwitter) ?? true;
@@ -1181,7 +1242,8 @@ abstract class SettingsStoreBase with Store {
1242 final packageInfo = await PackageInfo.fromPlatform();
1243 final deviceName = await _getDeviceName() ?? '';
1244 final shouldShowYatPopup = sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? true;
1184 - final shouldShowDEuroDisclaimer = sharedPreferences.getBool(PreferencesKey.shouldShowDEuroDisclaimer) ?? true;
1245 + final shouldShowDEuroDisclaimer =
1246 + sharedPreferences.getBool(PreferencesKey.shouldShowDEuroDisclaimer) ?? true;
1247 final shouldShowRepWarning =
1248 sharedPreferences.getBool(PreferencesKey.shouldShowRepWarning) ?? true;
1249
@@ -1277,7 +1339,8 @@ abstract class SettingsStoreBase with Store {
1339 }
1340
1341 final savedSyncMode = SyncMode.all.firstWhere((element) {
1280 - return element.type.index == (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 2); // default to 2 - daily sync
1342 + return element.type.index ==
1343 + (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 2); // default to 2 - daily sync
1344 });
1345 final savedSyncAll = sharedPreferences.getBool(PreferencesKey.syncAllKey) ?? true;
1346 final builtinTor = sharedPreferences.getBool(PreferencesKey.builtinTorKey) ?? false;
@@ -1430,6 +1493,8 @@ abstract class SettingsStoreBase with Store {
1493 useArbiScan: useArbiScan,
1494 useTronGrid: useTronGrid,
1495 useMempoolFeeAPI: useMempoolFeeAPI,
1496 + useBlinkProtection: useBlinkProtection,
1497 + initialEvmHiddenChainIds: evmHiddenChainIds,
1498 defaultNanoRep: defaultNanoRep,
1499 defaultBananoRep: defaultBananoRep,
1500 lookupsTwitter: lookupsTwitter,
@@ -1473,6 +1538,7 @@ abstract class SettingsStoreBase with Store {
1538 initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings:
1539 shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
1540 initialEthereumTransactionPriority: ethereumTransactionPriority,
1541 + initialEVMTransactionPriority: evmTransactionPriority,
1542 initialPolygonTransactionPriority: polygonTransactionPriority,
1543 initialBaseTransactionPriority: baseTransactionPriority,
1544 initialSyncMode: savedSyncMode,
@@ -1516,19 +1582,18 @@ abstract class SettingsStoreBase with Store {
1582 priority[WalletType.litecoin] = bitcoin!.deserializeLitecoinTransactionPriority(
1583 sharedPreferences.getInt(PreferencesKey.litecoinTransactionPriority)!);
1584 }
1519 - if (ethereum != null &&
1585 + if (evm != null &&
1586 sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority) != null) {
1521 - priority[WalletType.ethereum] = ethereum!.deserializeEthereumTransactionPriority(
1587 + priority[WalletType.ethereum] = evm!.deserializeEVMTransactionPriority(
1588 sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!);
1589 }
1524 - if (polygon != null &&
1590 + if (evm != null &&
1591 sharedPreferences.getInt(PreferencesKey.polygonTransactionPriority) != null) {
1526 - priority[WalletType.polygon] = polygon!.deserializePolygonTransactionPriority(
1592 + priority[WalletType.polygon] = evm!.deserializeEVMTransactionPriority(
1593 sharedPreferences.getInt(PreferencesKey.polygonTransactionPriority)!);
1594 }
1529 - if (base != null &&
1530 - sharedPreferences.getInt(PreferencesKey.baseTransactionPriority) != null) {
1531 - priority[WalletType.base] = base!.deserializeBaseTransactionPriority(
1595 + if (evm != null && sharedPreferences.getInt(PreferencesKey.baseTransactionPriority) != null) {
1596 + priority[WalletType.base] = evm!.deserializeEVMTransactionPriority(
1597 sharedPreferences.getInt(PreferencesKey.baseTransactionPriority)!);
1598 }
1599 if (bitcoinCash != null &&
@@ -1538,7 +1603,7 @@ abstract class SettingsStoreBase with Store {
1603 }
1604 if (zano != null && sharedPreferences.getInt(PreferencesKey.zanoTransactionPriority) != null) {
1605 priority[WalletType.zano] = zano!.deserializeMoneroTransactionPriority(
1541 - raw: sharedPreferences.getInt(PreferencesKey.zanoTransactionPriority)!);
1606 + raw: sharedPreferences.getInt(PreferencesKey.zanoTransactionPriority)!);
1607 }
1608 if (decred != null &&
1609 sharedPreferences.getInt(PreferencesKey.decredTransactionPriority) != null) {
@@ -1578,14 +1643,17 @@ abstract class SettingsStoreBase with Store {
1643 numberOfFailedTokenTrials =
1644 sharedPreferences.getInt(PreferencesKey.failedTotpTokenTrials) ?? numberOfFailedTokenTrials;
1645 isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? isAppSecure;
1581 - disableTradeOption = sharedPreferences.getBool(PreferencesKey.disableTradeOption) ?? disableTradeOption;
1582 - disableAutomaticExchangeStatusUpdates = sharedPreferences.getBool(PreferencesKey.disableAutomaticExchangeStatusUpdates) ?? disableAutomaticExchangeStatusUpdates;
1646 + disableTradeOption =
1647 + sharedPreferences.getBool(PreferencesKey.disableTradeOption) ?? disableTradeOption;
1648 + disableAutomaticExchangeStatusUpdates =
1649 + sharedPreferences.getBool(PreferencesKey.disableAutomaticExchangeStatusUpdates) ??
1650 + disableAutomaticExchangeStatusUpdates;
1651 disableBulletin =
1652 sharedPreferences.getBool(PreferencesKey.disableBulletinKey) ?? disableBulletin;
1653 walletListOrder =
1654 FilterListOrderType.values[sharedPreferences.getInt(PreferencesKey.walletListOrder) ?? 0];
1655 contactListOrder =
1588 - FilterListOrderType.values[sharedPreferences.getInt(PreferencesKey.contactListOrder) ?? 0];
1656 + FilterListOrderType.values[sharedPreferences.getInt(PreferencesKey.contactListOrder) ?? 0];
1657 walletListAscending = sharedPreferences.getBool(PreferencesKey.walletListAscending) ?? true;
1658 contactListAscending = sharedPreferences.getBool(PreferencesKey.contactListAscending) ?? true;
1659 shouldShowMarketPlaceInDashboard =
@@ -1616,7 +1684,8 @@ abstract class SettingsStoreBase with Store {
1684 shouldShowYatPopup =
1685 sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? shouldShowYatPopup;
1686 shouldShowDEuroDisclaimer =
1619 - sharedPreferences.getBool(PreferencesKey.shouldShowDEuroDisclaimer) ?? shouldShowDEuroDisclaimer;
1687 + sharedPreferences.getBool(PreferencesKey.shouldShowDEuroDisclaimer) ??
1688 + shouldShowDEuroDisclaimer;
1689 shouldShowRepWarning =
1690 sharedPreferences.getBool(PreferencesKey.shouldShowRepWarning) ?? shouldShowRepWarning;
1691 sortBalanceBy = SortBalanceBy
@@ -1628,6 +1697,12 @@ abstract class SettingsStoreBase with Store {
1697 useArbiScan = sharedPreferences.getBool(PreferencesKey.useArbiScan) ?? true;
1698 useTronGrid = sharedPreferences.getBool(PreferencesKey.useTronGrid) ?? true;
1699 useMempoolFeeAPI = sharedPreferences.getBool(PreferencesKey.useMempoolFeeAPI) ?? true;
1700 + useBlinkProtection = sharedPreferences.getBool(PreferencesKey.useBlinkProtection) ?? true;
1701 + final hiddenChainIdsRaw =
1702 + sharedPreferences.getStringList(PreferencesKey.evmHiddenChainIds) ?? const <String>[];
1703 + evmHiddenChainIds
1704 + ..clear()
1705 + ..addAll(hiddenChainIdsRaw.map((value) => int.tryParse(value)).whereType<int>());
1706 defaultNanoRep = sharedPreferences.getString(PreferencesKey.defaultNanoRep) ?? "";
1707 defaultBananoRep = sharedPreferences.getString(PreferencesKey.defaultBananoRep) ?? "";
1708 lookupsTwitter = sharedPreferences.getBool(PreferencesKey.lookupsTwitter) ?? true;
@@ -1732,7 +1807,6 @@ abstract class SettingsStoreBase with Store {
1807
1808 if (wowneroNode != null) {
1809 nodes[WalletType.wownero] = wowneroNode;
1735 -
1810 }
1811
1812 if (zanoNode != null) {
@@ -1862,7 +1936,13 @@ abstract class SettingsStoreBase with Store {
1936 await _sharedPreferences.setInt(PreferencesKey.currentHavenNodeIdKey, node.key as int);
1937 break;
1938 case WalletType.ethereum:
1865 - await _sharedPreferences.setInt(PreferencesKey.currentEthereumNodeIdKey, node.key as int);
1939 + case WalletType.polygon:
1940 + case WalletType.base:
1941 + case WalletType.arbitrum:
1942 + final chainId = evm!.getChainIdByWalletType(node.type);
1943 + final preferenceKey = _getEVMNodePreferenceKey(chainId);
1944 + await _sharedPreferences.setInt(preferenceKey, node.key as int);
1945 + nodes[node.type] = node;
1946 break;
1947 case WalletType.bitcoinCash:
1948 await _sharedPreferences.setInt(
@@ -1871,15 +1951,6 @@ abstract class SettingsStoreBase with Store {
1951 case WalletType.nano:
1952 await _sharedPreferences.setInt(PreferencesKey.currentNanoNodeIdKey, node.key as int);
1953 break;
1874 - case WalletType.polygon:
1875 - await _sharedPreferences.setInt(PreferencesKey.currentPolygonNodeIdKey, node.key as int);
1876 - break;
1877 - case WalletType.base:
1878 - await _sharedPreferences.setInt(PreferencesKey.currentBaseNodeIdKey, node.key as int);
1879 - break;
1880 - case WalletType.arbitrum:
1881 - await _sharedPreferences.setInt(PreferencesKey.currentArbitrumNodeIdKey, node.key as int);
1882 - break;
1954 case WalletType.solana:
1955 await _sharedPreferences.setInt(PreferencesKey.currentSolanaNodeIdKey, node.key as int);
1956 break;
@@ -1917,6 +1988,13 @@ abstract class SettingsStoreBase with Store {
1988 powNodes[walletType] = node;
1989 }
1990
1991 + @action
1992 + void setEvmHiddenChainIds(Set<int> chainIds) {
1993 + evmHiddenChainIds
1994 + ..clear()
1995 + ..addAll(chainIds);
1996 + }
1997 +
1998 @action
1999 Future<void> updateAllTrocadorProviderStates(List<String> availableProviders) async {
2000 final jsonKey = PreferencesKey.trocadorProviderStatesKey;
lib/utils/feature_flag.dart
+1
@@ -14,5 +14,6 @@ class FeatureFlag {
14 static const bool hasBitcoinViewOnly = true;
15 static const bool customBackgroundEnabled = false;
16 static const bool duressPinEnabled = true;
17 + static const bool isEVMChainSwitcherEnabled = false;
18 static const bool isAutomaticNodeSwitchingEnabled = false;
19 }
lib/utils/qr_util.dart
+33 -14
@@ -1,9 +1,22 @@
1 +import 'package:cake_wallet/reactions/wallet_connect.dart';
2 import 'package:cw_core/wallet_type.dart';
3
3 -String getQrImage(WalletType type) {
4 +String getQrImage(WalletType type, {int? selectedChainId}) {
5 + if (isEVMCompatibleChain(type) && selectedChainId != null) {
6 + switch (selectedChainId) {
7 + case 1:
8 + return 'assets/images/eth_chain_qr.svg';
9 + case 137:
10 + return 'assets/images/pol_chain_qr.svg';
11 + case 8453:
12 + return 'assets/images/base_chain_QR.svg';
13 + case 42161:
14 + return 'assets/images/arbitrum_chain_QR.svg';
15 + default:
16 + return 'assets/images/eth_chain_qr.svg';
17 + }
18 + }
19 switch (type) {
5 - case WalletType.ethereum:
6 - return 'assets/images/eth_chain_qr.svg';
20 case WalletType.solana:
21 return 'assets/images/sol_chain_qr.svg';
22 case WalletType.polygon:
@@ -28,21 +41,31 @@ String getQrImage(WalletType type) {
41 return 'assets/images/dcr_chain_qr.svg';
42 case WalletType.dogecoin:
43 return 'assets/images/doge_chain_qr.svg';
31 - case WalletType.base:
32 - return 'assets/images/base_chain_QR.svg';
33 - case WalletType.arbitrum:
34 - return 'assets/images/arbitrum_chain_QR.svg';
44 case WalletType.banano:
45 case WalletType.haven:
46 case WalletType.none:
47 + default:
48 return 'assets/images/qr-cake.png';
49 }
50 }
51
42 -String getChainMonoImage(WalletType type) {
52 +String getChainMonoImage(WalletType type, {int? selectedChainId}) {
53 + if (isEVMCompatibleChain(type) && selectedChainId != null) {
54 + switch (selectedChainId) {
55 + case 1:
56 + return 'assets/images/eth_chain_mono.svg';
57 + case 137:
58 + return 'assets/images/pol_chain_mono.svg';
59 + case 8453:
60 + return 'assets/images/base_chain_mono.svg';
61 + case 42161:
62 + return 'assets/images/arbitrum_chain_mono.svg';
63 + default:
64 + return 'assets/images/eth_chain_mono.svg';
65 + }
66 + }
67 +
68 switch (type) {
44 - case WalletType.ethereum:
45 - return 'assets/images/eth_chain_mono.svg';
69 case WalletType.solana:
70 return 'assets/images/sol_chain_mono.svg';
71 case WalletType.polygon:
@@ -51,10 +74,6 @@ String getChainMonoImage(WalletType type) {
74 return 'assets/images/trx_chain_mono.svg';
75 case WalletType.zano:
76 return 'assets/images/zano_chain_mono.svg';
54 - case WalletType.base:
55 - return 'assets/images/base_chain_mono.svg';
56 - case WalletType.arbitrum:
57 - return 'assets/images/arbitrum_chain_mono.svg';
77 default:
78 return 'assets/images/eth_chain_mono.svg';
79 }
lib/utils/token_utilities.dart
+93 -76
@@ -1,4 +1,5 @@
1 import 'package:cake_wallet/reactions/wallet_connect.dart';
2 +import 'package:cake_wallet/evm/evm.dart';
3 import 'package:cw_core/cake_hive.dart';
4 import 'package:cw_core/crypto_currency.dart';
5 import 'package:cw_core/currency_for_wallet_type.dart';
@@ -21,13 +22,18 @@ class TokenUtilities {
22 final unique = <Erc20Token>[];
23
24 for (final wallet in evmWallets) {
24 - final chain = getTokenNameBasedOnWalletType(wallet.type);
25 - final box = await _openEvmTokensBoxFor(wallet);
26 -
27 - for (final t in box.values.where((t) => t.enabled)) {
28 - final key = '$chain|${t.contractAddress.toLowerCase()}';
29 - if (seen.add(key)) {
30 - unique.add(t);
25 + final allChains = evm!.getAllChains();
26 +
27 + for (final chainInfo in allChains) {
28 + final chainId = chainInfo.chainId;
29 + final chain = getTokenNameBasedOnWalletType(wallet.type, chainId: chainId);
30 + final box = await _openEvmTokensBoxFor(wallet, chainId);
31 +
32 + for (final t in box.values.where((t) => t.enabled)) {
33 + final key = '$chain|${t.contractAddress.toLowerCase()}';
34 + if (seen.add(key)) {
35 + unique.add(t);
36 + }
37 }
38 }
39 }
@@ -109,17 +115,9 @@ class TokenUtilities {
115 }
116 }
117
112 - static Future<Box<Erc20Token>> _openEvmTokensBoxFor(
113 - WalletInfo walletInfo,
114 - ) async {
118 + static Future<Box<Erc20Token>> _openEvmTokensBoxFor(WalletInfo walletInfo, int chainId) async {
119 final walletKey = walletInfo.name.replaceAll(' ', '_');
116 - final boxName = switch (walletInfo.type) {
117 - WalletType.ethereum => '${walletKey}_${Erc20Token.ethereumBoxName}',
118 - WalletType.polygon => '${walletKey}_${Erc20Token.polygonBoxName}',
119 - WalletType.base => '${walletKey}_${Erc20Token.baseBoxName}',
120 - WalletType.arbitrum => '${walletKey}_${Erc20Token.arbitrumBoxName}',
121 - _ => '${walletKey}_${Erc20Token.ethereumBoxName}',
122 - };
120 + final boxName = _getErc20TokensBoxName(walletKey, chainId);
121
122 if (CakeHive.isBoxOpen(boxName)) {
123 return CakeHive.box<Erc20Token>(boxName);
@@ -127,6 +125,16 @@ class TokenUtilities {
125 return CakeHive.openBox<Erc20Token>(boxName);
126 }
127
128 + static String _getErc20TokensBoxName(String sanitizedName, int chainId) {
129 + return switch (chainId) {
130 + 1 => "${sanitizedName}_${Erc20Token.ethereumBoxName}",
131 + 137 => "${sanitizedName}_${Erc20Token.polygonBoxName}",
132 + 8453 => "${sanitizedName}_${Erc20Token.baseBoxName}",
133 + 42161 => "${sanitizedName}_${Erc20Token.arbitrumBoxName}",
134 + _ => "${sanitizedName}_${Erc20Token.ethereumBoxName}",
135 + };
136 + }
137 +
138 static Future<Box<SPLToken>> _openSolTokensBoxFor(WalletInfo wallet) async {
139 final boxName = '${wallet.name.replaceAll(' ', '_')}_${SPLToken.boxName}';
140 if (CakeHive.isBoxOpen(boxName)) {
@@ -135,9 +143,7 @@ class TokenUtilities {
143 return CakeHive.openBox<SPLToken>(boxName);
144 }
145
138 - static Future<Box<TronToken>> _openTronTokensBoxFor(
139 - WalletInfo walletInfo,
140 - ) async {
146 + static Future<Box<TronToken>> _openTronTokensBoxFor(WalletInfo walletInfo) async {
147 final boxName = '${walletInfo.name.replaceAll(' ', '_')}_${TronToken.boxName}';
148 if (CakeHive.isBoxOpen(boxName)) {
149 return CakeHive.box<TronToken>(boxName);
@@ -176,41 +182,49 @@ class TokenUtilities {
182 }
183
184 static int getChainId(CryptoCurrency currency) {
185 + final tag = currency.tag?.toUpperCase();
186 final title = currency.title.toLowerCase();
180 - final tag = currency.tag?.toLowerCase();
187
182 - // Polygon
183 - if (title == 'polygon' || title == 'matic' || tag == 'polygon') {
184 - return 137;
188 + // Only check EVM registry for currencies that might be EVM-related
189 + final isPotentialEVM = title == 'eth' ||
190 + title == 'ethereum' ||
191 + title == 'polygon' ||
192 + title == 'matic' ||
193 + title == 'base' ||
194 + title == 'arbitrum' ||
195 + (tag != null && (tag == 'ETH' || tag == 'POL' || tag == 'BASE' || tag == 'ARB')) ||
196 + isNativeToken(currency);
197 +
198 + if (isPotentialEVM) {
199 + // Try by tag first if available (e.g., 'POL', 'BASE', 'ARB')
200 + if (tag != null) {
201 + final chainId = evm!.getChainIdByTag(tag);
202 + if (chainId != null) return chainId;
203 + }
204 +
205 + // Try by title (case-insensitive)
206 + final titleChainId = evm!.getChainIdByTitle(title);
207 + if (titleChainId != null) return titleChainId;
208 }
209
210 + // Fallback to hardcoded values for chains not in registry yet
211 // BSC (Binance Smart Chain)
188 - if (title == 'bsc' || title == 'bnb' || tag == 'bsc') {
212 + if (title == 'bsc' || title == 'bnb' || tag == 'BSC') {
213 return 56;
214 }
215
216 // Avalanche C-Chain
193 - if (title == 'avalanche' || title == 'avax' || tag == 'avalanche') {
217 + if (title == 'avalanche' || title == 'avax' || tag == 'AVALANCHE') {
218 return 43114;
219 }
220
197 - // Arbitrum One
198 - if (title == 'arbitrum' || title == 'arb' || tag == 'arb') {
199 - return 42161;
200 - }
201 -
221 // Optimism
203 - if (title == 'optimism' || title == 'op' || tag == 'optimism') {
222 + if (title == 'optimism' || title == 'op' || tag == 'OPTIMISM') {
223 return 10;
224 }
225
207 - // Base
208 - if (title == 'base' || tag == 'base') {
209 - return 8453;
210 - }
211 -
226 // Fantom Opera
213 - if (title == 'fantom' || title == 'ftm' || tag == 'fantom') {
227 + if (title == 'fantom' || title == 'ftm' || tag == 'FANTOM') {
228 return 250;
229 }
230
@@ -218,18 +232,40 @@ class TokenUtilities {
232 return 1;
233 }
234
221 - static Future<List<CryptoCurrency>> getAvailableTokensForNetwork(
222 - WalletType network,
223 - ) async {
224 - final baseCurrency = walletTypeToCryptoCurrency(network);
235 + static bool _shouldAddToken(
236 + List<CryptoCurrency> existingTokens,
237 + CryptoCurrency token,
238 + Set<String> addedAddresses,
239 + ) {
240 + if (token is Erc20Token) {
241 + final address = token.contractAddress.toLowerCase();
242 + if (addedAddresses.contains(address)) {
243 + return false;
244 + }
245 + if (existingTokens.any((existing) => _matchesCurrency(existing, token))) {
246 + return false;
247 + }
248 + return true;
249 + }
250 +
251 + return !existingTokens.any((existing) => _matchesCurrency(existing, token));
252 + }
253 +
254 + static bool _matchesCurrency(CryptoCurrency a, CryptoCurrency b) {
255 + return a.title.toUpperCase() == b.title.toUpperCase() &&
256 + (a.tag?.toUpperCase() == b.tag?.toUpperCase());
257 + }
258 +
259 + static Future<List<CryptoCurrency>> getAvailableTokensForChainId(int chainId) async {
260 + // Get native currency for the chain
261 + final baseCurrency = getCryptoCurrencyByChainId(chainId);
262 final allTokens = <CryptoCurrency>[];
263 final addedAddresses = <String>{};
264
265 allTokens.add(baseCurrency);
266
267 + // Add currencies that match this chain
268 for (final currency in CryptoCurrency.all) {
231 - // For EVM networks: ETH has no tag, POL/BASE have tags
232 - // Match by tag for POL/BASE, match by title==tag for ETH
269 final matches = (baseCurrency.tag == null && baseCurrency.title == currency.tag) ||
270 (baseCurrency.tag != null &&
271 currency.tag?.toLowerCase() == baseCurrency.tag?.toLowerCase());
@@ -239,8 +275,8 @@ class TokenUtilities {
275 }
276 }
277
242 - // Add user tokens that don't already exist
243 - final userTokens = await _getUserTokensForNetwork(baseCurrency);
278 + // Add user tokens for this chain
279 + final userTokens = await _getUserTokensForChainId(chainId);
280 for (final token in userTokens) {
281 if (_shouldAddToken(allTokens, token, addedAddresses)) {
282 allTokens.add(token);
@@ -253,38 +289,19 @@ class TokenUtilities {
289 return allTokens;
290 }
291
256 - static bool _shouldAddToken(
257 - List<CryptoCurrency> existingTokens,
258 - CryptoCurrency token,
259 - Set<String> addedAddresses,
260 - ) {
261 - if (token is Erc20Token) {
262 - final address = token.contractAddress.toLowerCase();
263 - if (addedAddresses.contains(address)) {
264 - return false;
265 - }
266 - if (existingTokens.any((existing) => _matchesCurrency(existing, token))) {
267 - return false;
268 - }
269 - return true;
270 - }
271 -
272 - return !existingTokens.any((existing) => _matchesCurrency(existing, token));
273 - }
274 -
275 - static bool _matchesCurrency(CryptoCurrency a, CryptoCurrency b) {
276 - return a.title.toUpperCase() == b.title.toUpperCase() &&
277 - (a.tag?.toUpperCase() == b.tag?.toUpperCase());
278 - }
292 + static Future<List<CryptoCurrency>> _getUserTokensForChainId(int chainId) async {
293 + final allWi = await WalletInfo.getAll();
294 + final evmWallets = allWi.where((w) => isEVMCompatibleChain(w.type));
295
280 - static Future<List<CryptoCurrency>> _getUserTokensForNetwork(CryptoCurrency baseCurrency) async {
281 - final tokens = await TokenUtilities.loadAllUniqueEvmTokens();
296 + final tokens = <Erc20Token>[];
297 + for (final wallet in evmWallets) {
298 + final box = await _openEvmTokensBoxFor(wallet, chainId);
299
283 - return tokens.where((token) {
284 - // Match by tag, except for ETH which has no tag - match by title instead
285 - if (baseCurrency.tag == null) return token.tag == baseCurrency.title;
300 + for (final t in box.values.where((t) => t.enabled)) {
301 + tokens.add(t);
302 + }
303 + }
304
287 - return token.tag?.toLowerCase() == baseCurrency.tag?.toLowerCase();
288 - }).toList();
305 + return tokens.cast<CryptoCurrency>();
306 }
307 }
lib/view_model/advanced_privacy_settings_view_model.dart
+17
@@ -2,6 +2,8 @@ import 'package:cake_wallet/entities/exchange_api_mode.dart';
2 import 'package:cake_wallet/entities/fiat_api_mode.dart';
3 import 'package:cake_wallet/entities/seed_phrase_length.dart';
4 import 'package:cake_wallet/entities/seed_type.dart';
5 +import 'package:cake_wallet/evm/evm.dart';
6 +import 'package:cake_wallet/reactions/wallet_connect.dart';
7 import 'package:cake_wallet/store/settings_store.dart';
8 import 'package:cw_core/wallet_type.dart';
9 import 'package:mobx/mobx.dart';
@@ -23,6 +25,18 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
25 @computed
26 bool get disableBulletin => _settingsStore.disableBulletin;
27
28 + @computed
29 + bool get useBlinkProtection => _settingsStore.useBlinkProtection;
30 +
31 + bool get canUseBlinkProtection {
32 + if (!isEVMCompatibleChain(type)) return false;
33 +
34 + // Get the chainId from the wallet type
35 + final chainId = evm!.getChainIdByWalletType(type);
36 +
37 + return canSupportBlinkProtection(chainId);
38 + }
39 +
40 @observable
41 bool _addCustomNode = false;
42
@@ -109,6 +123,9 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
123 @action
124 void setDisableBulletin(bool value) => _settingsStore.disableBulletin = value;
125
126 + @action
127 + void setUseBlinkProtection(bool value) => _settingsStore.useBlinkProtection = value;
128 +
129 @action
130 void toggleAddCustomNode() => _addCustomNode = !_addCustomNode;
131
lib/view_model/anon_invoice_page_view_model.dart
+8 -9
@@ -11,7 +11,6 @@ import 'package:cake_wallet/store/settings_store.dart';
11 import 'package:cw_core/crypto_currency.dart';
12 import 'package:cw_core/currency.dart';
13 import 'package:cw_core/wallet_base.dart';
14 -import 'package:cw_core/wallet_type.dart';
14 import 'package:hive/hive.dart';
15 import 'package:mobx/mobx.dart';
16 import 'package:shared_preferences/shared_preferences.dart';
@@ -34,13 +33,13 @@ abstract class AnonInvoicePageViewModelBase with Store {
33 description = '',
34 amount = '',
35 state = InitialExecutionState(),
37 - selectedCurrency = walletTypeToCryptoCurrency(_wallet.type),
38 - cryptoCurrency = walletTypeToCryptoCurrency(_wallet.type) {
36 + selectedCurrency = _wallet.currency,
37 + cryptoCurrency = _wallet.currency {
38 _getPreviousDonationLink();
39 _fetchLimits();
40 }
41
43 - List<Currency> get currencies => [walletTypeToCryptoCurrency(_wallet.type), ...FiatCurrency.all];
42 + List<Currency> get currencies => [_wallet.currency, ...FiatCurrency.all];
43 final AnonPayApi anonPayApi;
44 final String address;
45 final SettingsStore settingsStore;
@@ -85,7 +84,7 @@ abstract class AnonInvoicePageViewModelBase with Store {
84 if (currency is CryptoCurrency) {
85 cryptoCurrency = currency;
86 } else {
88 - cryptoCurrency = walletTypeToCryptoCurrency(_wallet.type);
87 + cryptoCurrency = _wallet.currency;
88 }
89
90 _fetchLimits();
@@ -118,7 +117,7 @@ abstract class AnonInvoicePageViewModelBase with Store {
117 email: receipientEmail,
118 name: receipientName,
119 fiatEquivalent:
121 - selectedCurrency is FiatCurrency ? (selectedCurrency as FiatCurrency).raw : null,
120 + selectedCurrency is FiatCurrency ? (selectedCurrency as FiatCurrency).raw : null,
121 ));
122
123 _anonpayInvoiceInfoSource.add(result);
@@ -178,12 +177,12 @@ abstract class AnonInvoicePageViewModelBase with Store {
177 String get currentWalletName => _wallet.name;
178
179 @computed
181 - String get qrImage => getQrImage(_wallet.type);
180 + String get qrImage => getQrImage(_wallet.type, selectedChainId: _wallet.chainId);
181
182 @action
183 void reset() {
185 - selectedCurrency = walletTypeToCryptoCurrency(_wallet.type);
186 - cryptoCurrency = walletTypeToCryptoCurrency(_wallet.type);
184 + selectedCurrency = _wallet.currency;
185 + cryptoCurrency = _wallet.currency;
186 receipientEmail = '';
187 receipientName = '';
188 description = '';
lib/view_model/buy/buy_view_model.dart
+11 -13
@@ -1,9 +1,7 @@
1 import 'package:cake_wallet/buy/buy_provider.dart';
2 -import 'package:cake_wallet/buy/moonpay/moonpay_provider.dart';
2 import 'package:cake_wallet/buy/wyre/wyre_buy_provider.dart';
3 import 'package:cw_core/crypto_currency.dart';
4 import 'package:cake_wallet/entities/fiat_currency.dart';
6 -import 'package:cw_core/currency_for_wallet_type.dart';
5 import 'package:cw_core/utils/print_verbose.dart';
6 import 'package:cw_core/wallet_type.dart';
7 import 'package:cake_wallet/store/settings_store.dart';
@@ -20,12 +18,12 @@ part 'buy_view_model.g.dart';
18 class BuyViewModel = BuyViewModelBase with _$BuyViewModel;
19
20 abstract class BuyViewModelBase with Store {
23 - BuyViewModelBase(this.ordersSource, this.ordersStore, this.settingsStore,
24 - this.buyAmountViewModel, {required this.wallet})
25 - : isRunning = false,
26 - isDisabled = true,
27 - isShowProviderButtons = false,
28 - items = <BuyItem>[] {
21 + BuyViewModelBase(this.ordersSource, this.ordersStore, this.settingsStore, this.buyAmountViewModel,
22 + {required this.wallet})
23 + : isRunning = false,
24 + isDisabled = true,
25 + isShowProviderButtons = false,
26 + items = <BuyItem>[] {
27 _fetchBuyItems();
28 }
29
@@ -57,9 +55,9 @@ abstract class BuyViewModelBase with Store {
55 @computed
56 FiatCurrency get fiatCurrency => buyAmountViewModel.fiatCurrency;
57
60 - CryptoCurrency get cryptoCurrency => walletTypeToCryptoCurrency(type);
58 + CryptoCurrency get cryptoCurrency => wallet.currency;
59
62 - Future <String> fetchUrl() async {
60 + Future<String> fetchUrl() async {
61 String _url = '';
62
63 try {
@@ -95,8 +93,8 @@ abstract class BuyViewModelBase with Store {
93 _providerList.add(WyreBuyProvider(wallet: wallet));
94 }
95
98 - items = _providerList.map((provider) =>
99 - BuyItem(provider: provider, buyAmountViewModel: buyAmountViewModel))
96 + items = _providerList
97 + .map((provider) => BuyItem(provider: provider, buyAmountViewModel: buyAmountViewModel))
98 .toList();
99 }
102 -}
\ No newline at end of file
100 +}
lib/view_model/contact_list/contact_list_view_model.dart
+21 -8
@@ -7,9 +7,12 @@ import 'package:cake_wallet/entities/contact_record.dart';
7 import 'package:cake_wallet/entities/wallet_contact.dart';
8 import 'package:cake_wallet/entities/wallet_list_order_types.dart';
9 import 'package:cake_wallet/generated/i18n.dart';
10 +import 'package:cake_wallet/evm/evm.dart';
11 +import 'package:cake_wallet/reactions/wallet_connect.dart';
12 import 'package:cake_wallet/store/settings_store.dart';
13 import 'package:cake_wallet/utils/mobx.dart';
14 import 'package:cw_core/crypto_currency.dart';
15 +import 'package:cw_core/erc20_token.dart';
16 import 'package:cw_core/currency_for_wallet_type.dart';
17 import 'package:cw_core/wallet_info.dart';
18 import 'package:cw_core/wallet_type.dart';
@@ -44,7 +47,10 @@ abstract class ContactListViewModelBase with Store {
47 walletContacts.add(WalletContact(
48 address.address,
49 name,
47 - walletTypeToCryptoCurrency(info.type),
50 + getCryptoCurrencyForWalletListItem(
51 + info.type,
52 + ),
53 + walletType: info.type,
54 ));
55 }
56 }
@@ -56,7 +62,10 @@ abstract class ContactListViewModelBase with Store {
62 walletContacts.add(WalletContact(
63 address,
64 name,
59 - walletTypeToCryptoCurrency(info.type),
65 + getCryptoCurrencyForWalletListItem(
66 + info.type,
67 + ),
68 + walletType: info.type,
69 ));
70 } else {
71 addresses.forEach((address, label) {
@@ -67,10 +76,12 @@ abstract class ContactListViewModelBase with Store {
76 walletContacts.add(WalletContact(
77 address,
78 name,
70 - walletTypeToCryptoCurrency(info.type,
71 - isTestnet: info.network == null
72 - ? false
73 - : info.network!.toLowerCase().contains("testnet")),
79 + getCryptoCurrencyForWalletListItem(
80 + info.type,
81 + isTestnet:
82 + info.network == null ? false : info.network!.toLowerCase().contains("testnet"),
83 + ),
84 + walletType: info.type,
85 ));
86 });
87 }
@@ -81,7 +92,10 @@ abstract class ContactListViewModelBase with Store {
92 key: [WalletType.monero, WalletType.wownero, WalletType.haven].contains(info.type)
93 ? 0
94 : null),
84 - walletTypeToCryptoCurrency(info.type),
95 + getCryptoCurrencyForWalletListItem(
96 + info.type,
97 + ),
98 + walletType: info.type,
99 ));
100 }
101 }
@@ -203,7 +217,6 @@ abstract class ContactListViewModelBase with Store {
217 await sortGroupByType();
218 break;
219 case FilterListOrderType.Custom:
206 - default:
220 reorderAccordingToContactList();
221 break;
222 }
lib/view_model/dashboard/balance_view_model.dart
+20 -10
@@ -2,6 +2,7 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart';
2 import 'package:cake_wallet/entities/fiat_api_mode.dart';
3 import 'package:cake_wallet/entities/sort_balance_types.dart';
4 import 'package:cake_wallet/reactions/wallet_connect.dart';
5 +import 'package:cake_wallet/evm/evm.dart';
6 import 'package:cw_core/transaction_history.dart';
7 import 'package:cw_core/wallet_base.dart';
8 import 'package:cw_core/balance.dart';
@@ -20,8 +21,7 @@ part 'balance_view_model.g.dart';
21
22 class BalanceRecord {
23 const BalanceRecord(
23 - {
24 - required this.availableBalance,
24 + {required this.availableBalance,
25 required this.additionalBalance,
26 required this.secondAvailableBalance,
27 required this.secondAdditionalBalance,
@@ -114,6 +114,9 @@ abstract class BalanceViewModelBase with Store {
114 wallet.type == WalletType.tron ||
115 wallet.type == WalletType.zano;
116
117 + @computed
118 + bool get isEVMCompatible => isEVMCompatibleChain(wallet.type);
119 +
120 @computed
121 bool get hasAccounts => wallet.type == WalletType.monero || wallet.type == WalletType.wownero;
122
@@ -125,7 +128,16 @@ abstract class BalanceViewModelBase with Store {
128
129 @computed
130 String get asset {
128 - final typeFormatted = walletTypeToString(appStore.wallet!.type);
131 + if (isEVMCompatibleChain(wallet.type)) {
132 + final currentChain = evm!.getCurrentChain(wallet);
133 + if (currentChain != null) {
134 + return currentChain.name;
135 + }
136 +
137 + return walletTypeToString(wallet.type);
138 + }
139 +
140 + final typeFormatted = walletTypeToString(wallet.type);
141
142 switch (wallet.type) {
143 case WalletType.haven:
@@ -150,18 +162,15 @@ abstract class BalanceViewModelBase with Store {
162
163 @computed
164 String get availableBalanceLabel {
153 -
165 if (displayMode == BalanceDisplayMode.hiddenBalance) {
166 return S.current.show_balance;
156 - }
157 - else {
167 + } else {
168 return S.current.xmr_available_balance;
169 }
170 }
171
172 @computed
173 String get additionalBalanceLabel {
164 -
174 switch (wallet.type) {
175 case WalletType.haven:
176 case WalletType.ethereum:
@@ -225,8 +234,10 @@ abstract class BalanceViewModelBase with Store {
234 fiatAdditionalBalance: isFiatDisabled ? '' : '${fiatCurrency.toString()} ●●●●●',
235 fiatAvailableBalance: isFiatDisabled ? '' : '${fiatCurrency.toString()} ●●●●●',
236 fiatFrozenBalance: isFiatDisabled ? '' : '',
228 - fiatSecondAvailableBalance: isFiatDisabled ? '' : '${fiatCurrency.toString()} ●●●●●',
229 - fiatSecondAdditionalBalance: isFiatDisabled ? '' : '${fiatCurrency.toString()} ●●●●●',
237 + fiatSecondAvailableBalance:
238 + isFiatDisabled ? '' : '${fiatCurrency.toString()} ●●●●●',
239 + fiatSecondAdditionalBalance:
240 + isFiatDisabled ? '' : '${fiatCurrency.toString()} ●●●●●',
241 asset: key,
242 formattedAssetTitle: _formatterAsset(key)));
243 }
@@ -395,7 +406,6 @@ abstract class BalanceViewModelBase with Store {
406 return balance;
407 }
408
398 -
409 @observable
410 bool isShowCard;
411
lib/view_model/dashboard/dashboard_view_model.dart
+95 -24
@@ -44,7 +44,6 @@ import 'package:cake_wallet/view_model/dashboard/transaction_list_item.dart';
44 import 'package:cake_wallet/view_model/settings/sync_mode.dart';
45 import 'package:cryptography/cryptography.dart';
46 import 'package:cw_core/balance.dart';
47 -import 'package:cw_core/cake_hive.dart';
47 import 'package:cw_core/pathForWallet.dart';
48 import 'package:cw_core/sync_status.dart';
49 import 'package:cw_core/transaction_history.dart';
@@ -63,6 +62,8 @@ import 'package:permission_handler/permission_handler.dart';
62 import 'package:shared_preferences/shared_preferences.dart';
63
64 import 'package:cake_wallet/core/trade_monitor.dart';
65 +import 'package:cake_wallet/reactions/wallet_connect.dart';
66 +import 'package:cake_wallet/evm/evm.dart';
67
68 part 'dashboard_view_model.g.dart';
69
@@ -336,6 +337,26 @@ abstract class DashboardViewModelBase with Store {
337
338 bool _isTransactionDisposerCallbackRunning = false;
339
340 + @action
341 + void _reloadTransactions() {
342 + if (wallet.type == WalletType.monero || wallet.type == WalletType.wownero) {
343 + return; // Monero/Wownero transactions are handled separately
344 + }
345 +
346 + transactions.clear();
347 +
348 + transactions.addAll(
349 + wallet.transactionHistory.transactions.values.map(
350 + (transaction) => TransactionListItem(
351 + transaction: transaction,
352 + balanceViewModel: balanceViewModel,
353 + settingsStore: appStore.settingsStore,
354 + key: ValueKey('${wallet.type.name}_transaction_history_item_${transaction.id}_key'),
355 + ),
356 + ),
357 + );
358 + }
359 +
360 void _transactionDisposerCallback(int _) async {
361 // Simple check to prevent the callback from being called multiple times in the same frame
362 if (_isTransactionDisposerCallbackRunning) return;
@@ -443,7 +464,10 @@ abstract class DashboardViewModelBase with Store {
464
465 @computed
466 List<TradeListItem> get trades =>
446 - tradesStore.trades.where((trade) => trade.trade.walletId == wallet.id).toList();
467 + tradesStore.trades.where((trade) {
468 + final isSameChain = trade.trade.chainId != null ? trade.trade.chainId == wallet.chainId : true; // returning default as true here so it falls back to the default checks if there's no chainId
469 + return trade.trade.walletId == wallet.id && isSameChain;
470 + }).toList();
471
472 @computed
473 List<OrderListItem> get orders =>
@@ -566,6 +590,30 @@ abstract class DashboardViewModelBase with Store {
590 @computed
591 bool get showSilentPaymentsCard => hasSilentPayments && settingsStore.silentPaymentsCardDisplay;
592
593 + @computed
594 + bool get isEVMWallet => isEVMCompatibleChain(wallet.type);
595 +
596 + @computed
597 + List<ChainInfo> get availableChains {
598 + if (!isEVMWallet) return [];
599 + return evm!.getAllChains();
600 + }
601 +
602 + @computed
603 + ChainInfo? get currentChain {
604 + if (!isEVMWallet) return null;
605 + return evm!.getCurrentChain(wallet);
606 + }
607 +
608 + @action
609 + Future<void> selectChain(int chainId) async {
610 + if (!isEVMWallet) return;
611 +
612 + final node = appStore.settingsStore.getCurrentNode(wallet.type, chainId: chainId);
613 +
614 + await evm!.selectChain(wallet, chainId, node: node);
615 + }
616 +
617 final KeyService keyService;
618 final SharedPreferences sharedPreferences;
619
@@ -880,6 +928,8 @@ abstract class DashboardViewModelBase with Store {
928
929 ReactionDisposer? _transactionDisposer;
930
931 + ReactionDisposer? _chainChangeDisposer;
932 +
933 @computed
934 bool get hasPowNodes => [WalletType.nano, WalletType.banano].contains(wallet.type);
935
@@ -925,7 +975,12 @@ abstract class DashboardViewModelBase with Store {
975 }
976
977 Future<void> reconnect() async {
928 - final node = appStore.settingsStore.getCurrentNode(wallet.type);
978 + int? chainId;
979 + if (isEVMWallet) {
980 + chainId = evm!.getSelectedChainId(wallet);
981 + }
982 +
983 + final node = appStore.settingsStore.getCurrentNode(wallet.type, chainId: chainId);
984 await wallet.connectToNode(node: node);
985 if (hasPowNodes) {
986 final powNode = settingsStore.getCurrentPowNode(wallet.type);
@@ -983,22 +1038,25 @@ abstract class DashboardViewModelBase with Store {
1038 // subname = null;
1039 subname = '';
1040
986 - transactions.clear();
987 -
988 - transactions.addAll(
989 - wallet.transactionHistory.transactions.values.map(
990 - (transaction) => TransactionListItem(
991 - transaction: transaction,
992 - balanceViewModel: balanceViewModel,
993 - settingsStore: appStore.settingsStore,
994 - key: ValueKey('${wallet.type.name}_transaction_history_item_${transaction.id}_key'),
995 - ),
996 - ),
997 - );
1041 + _reloadTransactions();
1042 }
1043
1044 _transactionDisposer?.reaction.dispose();
1045
1046 + if (isEVMCompatibleChain(wallet.type)) {
1047 + _chainChangeDisposer?.reaction.dispose();
1048 + _chainChangeDisposer = reaction((_) {
1049 + // Access selectedChainId through proxy to track chain changes
1050 + return evm!.getSelectedChainId(wallet);
1051 + }, (_) {
1052 + // When chain switches, reload transactions for the new chain
1053 + _reloadTransactions();
1054 + });
1055 + } else {
1056 + _chainChangeDisposer?.reaction.dispose();
1057 + _chainChangeDisposer = null;
1058 + }
1059 +
1060 _transactionDisposer = reaction((_) {
1061 final length = appStore.wallet!.transactionHistory.transactions.length;
1062 if (length == 0) {
@@ -1085,10 +1143,11 @@ abstract class DashboardViewModelBase with Store {
1143 @action
1144 void setBuiltinTor(bool value, BuildContext context) {
1145 if (value) {
1088 - unawaited(showPopUp<bool>(
1089 - context: context,
1090 - builder: (BuildContext context) {
1091 - return AlertWithOneAction(
1146 + unawaited(
1147 + showPopUp<bool>(
1148 + context: context,
1149 + builder: (BuildContext context) {
1150 + return AlertWithOneAction(
1151 alertTitle: S.of(context).tor_connection,
1152 alertContent: S.of(context).tor_experimental,
1153 buttonText: S.of(context).ok,
@@ -1101,13 +1160,25 @@ abstract class DashboardViewModelBase with Store {
1160 settingsStore.currentBuiltinTor = value;
1161 if (value) {
1162 unawaited(ensureTorStarted(context: context).then((_) async {
1104 - if (settingsStore.currentBuiltinTor == false) return; // return when tor got disabled in the meantime;
1105 - await wallet.connectToNode(node: appStore.settingsStore.getCurrentNode(wallet.type));
1163 + if (settingsStore.currentBuiltinTor == false)
1164 + return; // return when tor got disabled in the meantime;
1165 + int? chainId;
1166 + if (isEVMWallet) {
1167 + chainId = evm!.getSelectedChainId(wallet);
1168 + }
1169 + await wallet.connectToNode(
1170 + node: appStore.settingsStore.getCurrentNode(wallet.type, chainId: chainId));
1171 }));
1172 } else {
1173 unawaited(ensureTorStopped(context: context).then((_) async {
1109 - if (settingsStore.currentBuiltinTor == true) return; // return when tor got enabled in the meantime;
1110 - await wallet.connectToNode(node: appStore.settingsStore.getCurrentNode(wallet.type));
1174 + if (settingsStore.currentBuiltinTor == true)
1175 + return; // return when tor got enabled in the meantime;
1176 + int? chainId;
1177 + if (isEVMWallet) {
1178 + chainId = evm!.getSelectedChainId(wallet);
1179 + }
1180 + await wallet.connectToNode(
1181 + node: appStore.settingsStore.getCurrentNode(wallet.type, chainId: chainId));
1182 }));
1183 }
1184 }
@@ -1211,7 +1282,7 @@ abstract class DashboardViewModelBase with Store {
1282 if (tx.isReplaced == true) return ' (replaced)';
1283 }
1284
1214 - if (wallet.type == WalletType.ethereum && tx.evmSignatureName == 'approval')
1285 + if (wallet.chainId == 1 && tx.evmSignatureName == 'approval')
1286 return ' (${tx.evmSignatureName})';
1287 return '';
1288 }
lib/view_model/dashboard/home_settings_view_model.dart
+39 -153
@@ -1,13 +1,10 @@
1 import 'dart:convert';
2
3 -import 'package:cake_wallet/arbitrum/arbitrum.dart';
4 -import 'package:cake_wallet/base/base.dart';
3 import 'package:cake_wallet/core/fiat_conversion_service.dart';
4 import 'package:cake_wallet/entities/fiat_api_mode.dart';
5 import 'package:cake_wallet/entities/erc20_token_info_moralis.dart';
6 import 'package:cake_wallet/entities/sort_balance_types.dart';
9 -import 'package:cake_wallet/ethereum/ethereum.dart';
10 -import 'package:cake_wallet/polygon/polygon.dart';
7 +import 'package:cake_wallet/evm/evm.dart';
8 import 'package:cake_wallet/reactions/wallet_connect.dart';
9 import 'package:cake_wallet/solana/solana.dart';
10 import 'package:cake_wallet/store/settings_store.dart';
@@ -33,6 +30,23 @@ abstract class HomeSettingsViewModelBase with Store {
30 isDeletingToken = false,
31 isValidatingContractAddress = false {
32 _updateTokensList();
33 +
34 + // React to wallet changes
35 + reaction((_) => _balanceViewModel.wallet, (_) {
36 + _updateTokensList();
37 + });
38 + reaction((_) {
39 + final wallet = _balanceViewModel.wallet;
40 + if (isEVMCompatibleChain(wallet.type)) {
41 + final selectedChainId = evm!.getSelectedChainId(wallet);
42 + final erc20Currencies = evm!.getERC20Currencies(wallet);
43 + return '${wallet.currency.title}_${selectedChainId}_${erc20Currencies.length}';
44 + }
45 + return null;
46 + }, (_) async {
47 + await Future.delayed(const Duration(milliseconds: 200));
48 + _updateTokensList();
49 + });
50 }
51
52 final SettingsStore _settingsStore;
@@ -76,21 +90,9 @@ abstract class HomeSettingsViewModelBase with Store {
90 }) async {
91 try {
92 isAddingToken = true;
79 - if (_balanceViewModel.wallet.type == WalletType.ethereum) {
80 - final erc20token = Erc20Token(
81 - name: token.name,
82 - symbol: token.title,
83 - decimal: token.decimals,
84 - contractAddress: contractAddress.toLowerCase(),
85 - iconPath: token.iconPath,
86 - isPotentialScam: token.isPotentialScam,
87 - );
93
89 - await ethereum!.addErc20Token(_balanceViewModel.wallet, erc20token);
90 - }
91 -
92 - if (_balanceViewModel.wallet.type == WalletType.polygon) {
93 - final polygonToken = Erc20Token(
94 + if (isEVMCompatibleChain(_balanceViewModel.wallet.type)) {
95 + final evmToken = Erc20Token(
96 name: token.name,
97 symbol: token.title,
98 decimal: token.decimals,
@@ -98,30 +100,7 @@ abstract class HomeSettingsViewModelBase with Store {
100 iconPath: token.iconPath,
101 isPotentialScam: token.isPotentialScam,
102 );
101 - await polygon!.addErc20Token(_balanceViewModel.wallet, polygonToken);
102 - }
103 -
104 - if (_balanceViewModel.wallet.type == WalletType.base) {
105 - final baseToken = Erc20Token(
106 - name: token.name,
107 - symbol: token.title,
108 - decimal: token.decimals,
109 - contractAddress: contractAddress.toLowerCase(),
110 - iconPath: token.iconPath,
111 - isPotentialScam: token.isPotentialScam,
112 - );
113 - await base!.addErc20Token(_balanceViewModel.wallet, baseToken);
114 - }
115 - if (_balanceViewModel.wallet.type == WalletType.arbitrum) {
116 - final arbitrumToken = Erc20Token(
117 - name: token.name,
118 - symbol: token.title,
119 - decimal: token.decimals,
120 - contractAddress: contractAddress.toLowerCase(),
121 - iconPath: token.iconPath,
122 - isPotentialScam: token.isPotentialScam,
123 - );
124 - await arbitrum!.addErc20Token(_balanceViewModel.wallet, arbitrumToken);
103 + await evm!.addErc20Token(_balanceViewModel.wallet, evmToken);
104 }
105
106 if (_balanceViewModel.wallet.type == WalletType.solana) {
@@ -153,20 +132,8 @@ abstract class HomeSettingsViewModelBase with Store {
132
133 @action
134 bool checkIfTokenIsAlreadyAdded(String contractAddress) {
156 - if (_balanceViewModel.wallet.type == WalletType.ethereum) {
157 - return ethereum!.isTokenAlreadyAdded(_balanceViewModel.wallet, contractAddress);
158 - }
159 -
160 - if (_balanceViewModel.wallet.type == WalletType.polygon) {
161 - return polygon!.isTokenAlreadyAdded(_balanceViewModel.wallet, contractAddress);
162 - }
163 -
164 - if (_balanceViewModel.wallet.type == WalletType.base) {
165 - return base!.isTokenAlreadyAdded(_balanceViewModel.wallet, contractAddress);
166 - }
167 -
168 - if (_balanceViewModel.wallet.type == WalletType.arbitrum) {
169 - return arbitrum!.isTokenAlreadyAdded(_balanceViewModel.wallet, contractAddress);
135 + if (isEVMCompatibleChain(_balanceViewModel.wallet.type)) {
136 + return evm!.isTokenAlreadyAdded(_balanceViewModel.wallet, contractAddress);
137 }
138
139 if (_balanceViewModel.wallet.type == WalletType.solana) {
@@ -188,20 +155,8 @@ abstract class HomeSettingsViewModelBase with Store {
155 Future<void> deleteToken(CryptoCurrency token) async {
156 try {
157 isDeletingToken = true;
191 - if (_balanceViewModel.wallet.type == WalletType.ethereum) {
192 - await ethereum!.deleteErc20Token(_balanceViewModel.wallet, token as Erc20Token);
193 - }
194 -
195 - if (_balanceViewModel.wallet.type == WalletType.polygon) {
196 - await polygon!.deleteErc20Token(_balanceViewModel.wallet, token as Erc20Token);
197 - }
198 -
199 - if (_balanceViewModel.wallet.type == WalletType.base) {
200 - await base!.deleteErc20Token(_balanceViewModel.wallet, token as Erc20Token);
201 - }
202 -
203 - if (_balanceViewModel.wallet.type == WalletType.arbitrum) {
204 - await arbitrum!.deleteErc20Token(_balanceViewModel.wallet, token as Erc20Token);
158 + if (isEVMCompatibleChain(_balanceViewModel.wallet.type)) {
159 + await evm!.deleteErc20Token(_balanceViewModel.wallet, token as Erc20Token);
160 }
161
162 if (_balanceViewModel.wallet.type == WalletType.solana) {
@@ -237,7 +192,7 @@ abstract class HomeSettingsViewModelBase with Store {
192
193 bool isUnverifiedContract = await _isContractUnverified(
194 contractAddress,
240 - chainId: getChainIdBasedOnWalletType(_balanceViewModel.wallet.type).toString(),
195 + chainId: evm!.getSelectedChainId(_balanceViewModel.wallet).toString(),
196 );
197
198 final showWarningForContractAddress = isPotentialScamViaMoralis || isUnverifiedContract;
@@ -253,16 +208,10 @@ abstract class HomeSettingsViewModelBase with Store {
208 List<String> defaultTokenAddresses = [];
209 switch (_balanceViewModel.wallet.type) {
210 case WalletType.ethereum:
256 - defaultTokenAddresses = ethereum!.getDefaultTokenContractAddresses();
257 - break;
211 case WalletType.polygon:
259 - defaultTokenAddresses = polygon!.getDefaultTokenContractAddresses();
260 - break;
212 case WalletType.base:
262 - defaultTokenAddresses = base!.getDefaultTokenContractAddresses();
263 - break;
213 case WalletType.arbitrum:
265 - defaultTokenAddresses = arbitrum!.getDefaultTokenContractAddresses();
214 + defaultTokenAddresses = evm!.getDefaultTokenContractAddresses(_balanceViewModel.wallet);
215 break;
216 case WalletType.solana:
217 defaultTokenAddresses = solana!.getDefaultTokenContractAddresses();
@@ -323,9 +272,9 @@ abstract class HomeSettingsViewModelBase with Store {
272 }
273
274 // Tokens whose contract have not been verified are potentially risky tokens.
326 - if (tokenInfo.verifiedContract == false) {
327 - return true;
328 - }
275 + // if (tokenInfo.verifiedContract == false) {
276 + // return true;
277 + // }
278
279 // Tokens with a security score less than 40 are potentially risky, requiring caution when dealing with them.
280 if (tokenInfo.securityScore != null && tokenInfo.securityScore! < 40) {
@@ -391,20 +340,8 @@ abstract class HomeSettingsViewModelBase with Store {
340 }
341
342 Future<CryptoCurrency?> getToken(String contractAddress) async {
394 - if (_balanceViewModel.wallet.type == WalletType.ethereum) {
395 - return await ethereum!.getErc20Token(_balanceViewModel.wallet, contractAddress);
396 - }
397 -
398 - if (_balanceViewModel.wallet.type == WalletType.polygon) {
399 - return await polygon!.getErc20Token(_balanceViewModel.wallet, contractAddress);
400 - }
401 -
402 - if (_balanceViewModel.wallet.type == WalletType.base) {
403 - return await base!.getErc20Token(_balanceViewModel.wallet, contractAddress);
404 - }
405 -
406 - if (_balanceViewModel.wallet.type == WalletType.arbitrum) {
407 - return await arbitrum!.getErc20Token(_balanceViewModel.wallet, contractAddress);
343 + if (isEVMCompatibleChain(_balanceViewModel.wallet.type)) {
344 + return await evm!.getErc20Token(_balanceViewModel.wallet, contractAddress);
345 }
346
347 if (_balanceViewModel.wallet.type == WalletType.solana) {
@@ -438,24 +375,9 @@ abstract class HomeSettingsViewModelBase with Store {
375 void changeTokenAvailability(CryptoCurrency token, bool value) async {
376 token.enabled = value;
377
441 - if (_balanceViewModel.wallet.type == WalletType.ethereum) {
442 - ethereum!.addErc20Token(_balanceViewModel.wallet, token as Erc20Token);
443 - if (!value) ethereum!.removeTokenTransactionsInHistory(_balanceViewModel.wallet, token);
444 - }
445 -
446 - if (_balanceViewModel.wallet.type == WalletType.polygon) {
447 - polygon!.addErc20Token(_balanceViewModel.wallet, token as Erc20Token);
448 - if (!value) polygon!.removeTokenTransactionsInHistory(_balanceViewModel.wallet, token);
449 - }
450 -
451 - if (_balanceViewModel.wallet.type == WalletType.base) {
452 - base!.addErc20Token(_balanceViewModel.wallet, token as Erc20Token);
453 - if (!value) base!.removeTokenTransactionsInHistory(_balanceViewModel.wallet, token);
454 - }
455 -
456 - if (_balanceViewModel.wallet.type == WalletType.arbitrum) {
457 - arbitrum!.addErc20Token(_balanceViewModel.wallet, token as Erc20Token);
458 - if (!value) arbitrum!.removeTokenTransactionsInHistory(_balanceViewModel.wallet, token);
378 + if (isEVMCompatibleChain(_balanceViewModel.wallet.type)) {
379 + evm!.addErc20Token(_balanceViewModel.wallet, token as Erc20Token);
380 + if (!value) evm!.removeTokenTransactionsInHistory(_balanceViewModel.wallet, token);
381 }
382
383 if (_balanceViewModel.wallet.type == WalletType.solana) {
@@ -495,32 +417,8 @@ abstract class HomeSettingsViewModelBase with Store {
417
418 tokens.clear();
419
498 - if (_balanceViewModel.wallet.type == WalletType.ethereum) {
499 - tokens.addAll(ethereum!
500 - .getERC20Currencies(_balanceViewModel.wallet)
501 - .where((element) => _matchesSearchText(element))
502 - .toList()
503 - ..sort(_sortFunc));
504 - }
505 -
506 - if (_balanceViewModel.wallet.type == WalletType.polygon) {
507 - tokens.addAll(polygon!
508 - .getERC20Currencies(_balanceViewModel.wallet)
509 - .where((element) => _matchesSearchText(element))
510 - .toList()
511 - ..sort(_sortFunc));
512 - }
513 -
514 - if (_balanceViewModel.wallet.type == WalletType.base) {
515 - tokens.addAll(base!
516 - .getERC20Currencies(_balanceViewModel.wallet)
517 - .where((element) => _matchesSearchText(element))
518 - .toList()
519 - ..sort(_sortFunc));
520 - }
521 -
522 - if (_balanceViewModel.wallet.type == WalletType.arbitrum) {
523 - tokens.addAll(arbitrum!
420 + if (isEVMCompatibleChain(_balanceViewModel.wallet.type)) {
421 + tokens.addAll(evm!
422 .getERC20Currencies(_balanceViewModel.wallet)
423 .where((element) => _matchesSearchText(element))
424 .toList()
@@ -586,20 +484,8 @@ abstract class HomeSettingsViewModelBase with Store {
484 return solana!.getTokenAddress(asset);
485 }
486
589 - if (_balanceViewModel.wallet.type == WalletType.ethereum) {
590 - return ethereum!.getTokenAddress(asset);
591 - }
592 -
593 - if (_balanceViewModel.wallet.type == WalletType.polygon) {
594 - return polygon!.getTokenAddress(asset);
595 - }
596 -
597 - if (_balanceViewModel.wallet.type == WalletType.base) {
598 - return base!.getTokenAddress(asset);
599 - }
600 -
601 - if (_balanceViewModel.wallet.type == WalletType.arbitrum) {
602 - return arbitrum!.getTokenAddress(asset);
487 + if (isEVMCompatibleChain(_balanceViewModel.wallet.type)) {
488 + return evm!.getTokenAddress(asset);
489 }
490
491 if (_balanceViewModel.wallet.type == WalletType.zano) {
lib/view_model/dashboard/nft_view_model.dart
+27 -14
@@ -6,6 +6,7 @@ import 'package:cake_wallet/generated/i18n.dart';
6 import 'package:cake_wallet/reactions/wallet_connect.dart';
7 import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart';
8 import 'package:cake_wallet/src/screens/wallet_connect/widgets/bottom_sheet/bottom_sheet_message_display_widget.dart';
9 +import 'package:cake_wallet/evm/evm.dart';
10 import 'package:cw_core/wallet_type.dart';
11 import 'package:cw_core/utils/proxy_wrapper.dart';
12 import 'package:mobx/mobx.dart';
@@ -23,7 +24,16 @@ abstract class NFTViewModelBase with Store {
24 : isLoading = false,
25 isImportNFTLoading = false,
26 nftAssetByWalletModels = ObservableList(),
26 - solanaNftAssetModels = ObservableList();
27 + solanaNftAssetModels = ObservableList() {
28 + if (isEVMCompatibleChain(appStore.wallet!.type)) {
29 + reaction((_) {
30 + final wallet = appStore.wallet;
31 + if (wallet != null) return wallet.chainId;
32 +
33 + return null;
34 + }, (_) => getNFTAssetByWallet());
35 + }
36 + }
37
38 final AppStore appStore;
39 final BottomSheetService bottomSheetService;
@@ -40,14 +50,14 @@ abstract class NFTViewModelBase with Store {
50
51 @action
52 Future<void> getNFTAssetByWallet() async {
43 - final walletType = appStore.wallet!.type;
53 + final wallet = appStore.wallet!;
54
45 - if (!isNFTACtivatedChain(walletType)) return;
55 + if (!isNFTACtivatedChain(wallet.type, wallet.chainId)) return;
56
47 - final walletAddress = appStore.wallet!.walletInfo.address;
57 + final walletAddress = wallet.walletInfo.address;
58 log('Fetching wallet NFTs for $walletAddress');
59
50 - final chainName = getChainNameBasedOnWalletType(walletType);
60 + final chainName = getChainNameBasedOnWalletType(wallet.type, chainId: wallet.chainId);
61 // the [chain] refers to the chain network that the nft is on
62 // the [format] refers to the number format type of the responses
63 // the [normalizedMetadata] field is a boolean that determines if
@@ -56,7 +66,7 @@ abstract class NFTViewModelBase with Store {
66 // the [excludeSpam] field is a boolean that determines if spam nfts be excluded from the response.
67
68 Uri uri;
59 - if (walletType == WalletType.solana) {
69 + if (wallet.type == WalletType.solana) {
70 uri = Uri.https(
71 'solana-gateway.moralis.io',
72 '/account/$chainName/$walletAddress/nft',
@@ -87,11 +97,10 @@ abstract class NFTViewModelBase with Store {
97 "X-API-Key": secrets.moralisApiKey,
98 },
99 );
90 -
100
92 - final decodedResponse = jsonDecode(response.body) as Map<String, dynamic>;
101 + final decodedResponse = jsonDecode(response.body);
102
94 - if (walletType == WalletType.solana) {
103 + if (wallet.type == WalletType.solana) {
104 final results = await Future.wait(
105 (decodedResponse as List<dynamic>).map(
106 (x) {
@@ -106,8 +115,7 @@ abstract class NFTViewModelBase with Store {
115
116 solanaNftAssetModels.addAll(results);
117 } else {
109 - final result =
110 - WalletNFTsResponseModel.fromJson(decodedResponse as Map<String, dynamic>).result ?? [];
118 + final result = WalletNFTsResponseModel.fromJson(decodedResponse as Map<String, dynamic>).result ?? [];
119
120 nftAssetByWalletModels.clear();
121
@@ -139,7 +147,7 @@ abstract class NFTViewModelBase with Store {
147 "X-API-Key": secrets.moralisApiKey,
148 },
149 );
142 -
150 +
151 final decodedResponse = jsonDecode(response.body) as Map<String, dynamic>;
152
153 return SolanaNFTAssetModel.fromJson(decodedResponse);
@@ -147,7 +155,12 @@ abstract class NFTViewModelBase with Store {
155
156 @action
157 Future<void> importNFT(String tokenAddress, String? tokenId) async {
150 - final chainName = getChainNameBasedOnWalletType(appStore.wallet!.type);
158 + final walletType = appStore.wallet!.type;
159 + int? chainId;
160 + if (isEVMCompatibleChain(walletType)) {
161 + chainId = evm!.getSelectedChainId(appStore.wallet!);
162 + }
163 + final chainName = getChainNameBasedOnWalletType(walletType, chainId: chainId);
164 // the [chain] refers to the chain network that the nft is on
165 // the [format] refers to the number format type of the responses
166 // the [normalizedMetadata] field is a boolean that determines if
@@ -179,7 +192,7 @@ abstract class NFTViewModelBase with Store {
192 "X-API-Key": secrets.moralisApiKey,
193 },
194 );
182 -
195 +
196 final decodedResponse = jsonDecode(response.body) as Map<String, dynamic>;
197
198 final nftAsset = NFTAssetModel.fromJson(decodedResponse);
lib/view_model/dashboard/transaction_list_item.dart
+11 -44
@@ -1,12 +1,9 @@
1 -import 'package:cake_wallet/arbitrum/arbitrum.dart';
2 -import 'package:cake_wallet/base/base.dart';
1 import 'package:cake_wallet/decred/decred.dart';
2 import 'package:cake_wallet/entities/balance_display_mode.dart';
3 import 'package:cake_wallet/entities/fiat_currency.dart';
6 -import 'package:cake_wallet/ethereum/ethereum.dart';
4 +import 'package:cake_wallet/evm/evm.dart';
5 import 'package:cake_wallet/generated/i18n.dart';
6 import 'package:cake_wallet/nano/nano.dart';
9 -import 'package:cake_wallet/polygon/polygon.dart';
7 import 'package:cake_wallet/reactions/wallet_connect.dart';
8 import 'package:cake_wallet/solana/solana.dart';
9 import 'package:cake_wallet/tron/tron.dart';
@@ -126,22 +123,8 @@ class TransactionListItem extends ActionListItem with Keyable {
123
124 CryptoCurrency? get assetOfTransaction {
125 try {
129 - if (balanceViewModel.wallet.type == WalletType.ethereum) {
130 - final asset = ethereum!.assetOfTransaction(balanceViewModel.wallet, transaction);
131 - return asset;
132 - }
133 -
134 - if (balanceViewModel.wallet.type == WalletType.polygon) {
135 - final asset = polygon!.assetOfTransaction(balanceViewModel.wallet, transaction);
136 - return asset;
137 - }
138 -
139 - if (balanceViewModel.wallet.type == WalletType.base) {
140 - final asset = base!.assetOfTransaction(balanceViewModel.wallet, transaction);
141 - return asset;
142 - }
143 - if (balanceViewModel.wallet.type == WalletType.arbitrum) {
144 - final asset = arbitrum!.assetOfTransaction(balanceViewModel.wallet, transaction);
126 + if (isEVMCompatibleChain(balanceViewModel.wallet.type)) {
127 + final asset = evm!.assetOfTransaction(balanceViewModel.wallet, transaction);
128 return asset;
129 }
130
@@ -184,32 +167,15 @@ class TransactionListItem extends ActionListItem with Keyable {
167 price: price);
168 break;
169 case WalletType.ethereum:
187 - final asset = ethereum!.assetOfTransaction(balanceViewModel.wallet, transaction);
188 - final price = balanceViewModel.fiatConvertationStore.prices[asset];
189 - amount = calculateFiatAmountRaw(
190 - cryptoAmount: ethereum!.formatterEthereumAmountToDouble(transaction: transaction),
191 - price: price);
192 - break;
170 case WalletType.polygon:
194 - final asset = polygon!.assetOfTransaction(balanceViewModel.wallet, transaction);
195 - final price = balanceViewModel.fiatConvertationStore.prices[asset];
196 - amount = calculateFiatAmountRaw(
197 - cryptoAmount: polygon!.formatterPolygonAmountToDouble(transaction: transaction),
198 - price: price);
199 - break;
171 case WalletType.base:
201 - final asset = base!.assetOfTransaction(balanceViewModel.wallet, transaction);
202 - final price = balanceViewModel.fiatConvertationStore.prices[asset];
203 - amount = calculateFiatAmountRaw(
204 - cryptoAmount: base!.formatterBaseAmountToDouble(transaction: transaction),
205 - price: price);
206 - break;
172 case WalletType.arbitrum:
208 - final asset = arbitrum!.assetOfTransaction(balanceViewModel.wallet, transaction);
173 + final asset = evm!.assetOfTransaction(balanceViewModel.wallet, transaction);
174 final price = balanceViewModel.fiatConvertationStore.prices[asset];
175 amount = calculateFiatAmountRaw(
211 - cryptoAmount: arbitrum!.formatterArbitrumAmountToDouble(transaction: transaction),
212 - price: price);
176 + cryptoAmount: evm!.formatterEVMAmountToDouble(transaction: transaction),
177 + price: price,
178 + );
179 break;
180 case WalletType.nano:
181 amount = calculateFiatAmountRaw(
@@ -242,9 +208,10 @@ class TransactionListItem extends ActionListItem with Keyable {
208 }
209 final price = balanceViewModel.fiatConvertationStore.prices[asset];
210 amount = calculateFiatAmountRaw(
245 - cryptoAmount: zano!.formatterIntAmountToDouble(amount: transaction.amount, currency: asset, forFee: false),
246 - price: price);
247 - break;
211 + cryptoAmount: zano!.formatterIntAmountToDouble(
212 + amount: transaction.amount, currency: asset, forFee: false),
213 + price: price);
214 + break;
215 case WalletType.decred:
216 amount = calculateFiatAmountRaw(
217 cryptoAmount: decred!.formatterDecredAmountToDouble(amount: transaction.amount),
lib/view_model/exchange/exchange_trade_view_model.dart
+4 -3
@@ -19,7 +19,6 @@ import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
19 import 'package:cake_wallet/exchange/provider/xoswap_exchange_provider.dart';
20 import 'package:cake_wallet/exchange/trade.dart';
21 import 'package:cake_wallet/generated/i18n.dart';
22 -import 'package:cake_wallet/arbitrum/arbitrum.dart';
22 import 'package:cake_wallet/reactions/wallet_connect.dart';
23 import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_item.dart';
24 import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
@@ -339,7 +338,9 @@ abstract class ExchangeTradeViewModelBase with Store {
338 wallet.currency == CryptoCurrency.baseEth && tradeFrom?.tag == CryptoCurrency.baseEth.tag;
339
340 bool _isArbitrumToken() =>
342 - wallet.currency == CryptoCurrency.arbEth && tradeFrom?.tag == CryptoCurrency.arbEth.tag;
341 + wallet.currency == CryptoCurrency.arbEth &&
342 + (tradeFrom?.tag == CryptoCurrency.arbEth.tag ||
343 + tradeFrom?.title == CryptoCurrency.arbEth.tag); // This is to handle the CryptoCurrency.arb that doesn't have a tag but fully belongs to the Arbitrum chain
344
345 bool _isTronToken() =>
346 wallet.currency == CryptoCurrency.trx && tradeFrom?.tag == CryptoCurrency.trx.title;
@@ -485,5 +486,5 @@ abstract class ExchangeTradeViewModelBase with Store {
486 }
487
488 @computed
488 - String get qrImage => getQrImage(wallet.type);
489 + String get qrImage => getQrImage(wallet.type, selectedChainId: wallet.chainId);
490 }
lib/view_model/exchange/exchange_view_model.dart
+21 -3
@@ -27,7 +27,6 @@ import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
27 import 'package:cake_wallet/exchange/provider/exolix_exchange_provider.dart';
28 import 'package:cake_wallet/exchange/provider/near_Intents_exchange_provider.dart';
29 import 'package:cake_wallet/exchange/provider/stealth_ex_exchange_provider.dart';
30 -import 'package:cake_wallet/exchange/provider/swapsxyz_exchange_provider.dart';
30 import 'package:cake_wallet/exchange/provider/swaptrade_exchange_provider.dart';
31 import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
32 import 'package:cake_wallet/exchange/provider/xoswap_exchange_provider.dart';
@@ -39,6 +38,7 @@ import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
38 import 'package:cake_wallet/store/dashboard/trades_store.dart';
39 import 'package:cake_wallet/store/settings_store.dart';
40 import 'package:cake_wallet/store/templates/exchange_template_store.dart';
41 +import 'package:cake_wallet/reactions/wallet_connect.dart';
42 import 'package:cake_wallet/utils/feature_flag.dart';
43 import 'package:cake_wallet/utils/token_utilities.dart';
44 import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
@@ -186,6 +186,17 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
186 if (isElectrumWallet) {
187 bitcoin!.updateFeeRates(wallet);
188 }
189 + reaction((_) => wallet.currency, (_) async {
190 + // When currency changes (e.g., EVM chain switch), update currencies
191 + await Future.delayed(const Duration(milliseconds: 100));
192 + receiveCurrency = wallet.currency;
193 + depositCurrency = wallet.currency;
194 +
195 + // Only refresh ETH tokens for EVM wallets
196 + if (isEVMCompatibleChain(wallet.type)) {
197 + _injectUserEthTokensIntoCurrencyLists();
198 + }
199 + });
200 }
201
202 bool useSameWalletAddress(CryptoCurrency currency) =>
@@ -332,7 +343,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
343
344 @computed
345 TransactionPriority get transactionPriority {
335 - final priority = _settingsStore.priority[wallet.type];
346 + final priority = _settingsStore.getPriority(wallet.type, chainId: wallet.chainId);
347
348 if (priority == null) {
349 throw Exception('Unexpected type ${wallet.type.toString()}');
@@ -711,6 +722,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
722 isSendAll: isSendAllEnabled,
723 );
724 trade.walletId = wallet.id;
725 + trade.chainId = wallet.chainId;
726 trade.fromWalletAddress = wallet.walletAddresses.address;
727
728 final canCreateTrade = await isCanCreateTrade(trade);
@@ -785,7 +797,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
797 WalletType.bitcoinCash,
798 WalletType.dogecoin,
799 ].contains(wallet.type)) {
788 - final priority = _settingsStore.priority[wallet.type]!;
800 + final priority = _settingsStore.getPriority(wallet.type)!;
801
802 final amount = await bitcoin!.estimateFakeSendAllTxAmount(
803 wallet,
@@ -835,6 +847,12 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
847 }
848
849 void _initialPairBasedOnWallet() {
850 + if (isEVMCompatibleChain(wallet.type)) {
851 + depositCurrency = wallet.currency;
852 + receiveCurrency = CryptoCurrency.xmr;
853 + return;
854 + }
855 +
856 switch (wallet.type) {
857 case WalletType.monero:
858 depositCurrency = CryptoCurrency.xmr;
lib/view_model/hardware_wallet/bitbox_view_model.dart
+3 -6
@@ -3,8 +3,7 @@ import 'dart:io';
3
4 import 'package:cake_wallet/bitcoin/bitcoin.dart';
5 import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
6 -import 'package:cake_wallet/ethereum/ethereum.dart';
7 -import 'package:cake_wallet/polygon/polygon.dart';
6 +import 'package:cake_wallet/evm/evm.dart';
7 import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
8 import 'package:cake_wallet/wallet_type_utils.dart';
9 import 'package:cw_core/hardware/hardware_wallet_service.dart';
@@ -89,9 +88,8 @@ abstract class BitboxViewModelBase extends HardwareWalletViewModel with Store {
88 case WalletType.litecoin:
89 return bitcoin!.getBitboxHardwareWalletService(bitboxManager, false);
90 case WalletType.ethereum:
92 - return ethereum!.getBitboxHardwareWalletService(bitboxManager);
91 case WalletType.polygon:
94 - return polygon!.getBitboxHardwareWalletService(bitboxManager);
92 + return evm!.getBitboxHardwareWalletService(bitboxManager);
93 default:
94 throw UnimplementedError();
95 }
@@ -104,9 +102,8 @@ abstract class BitboxViewModelBase extends HardwareWalletViewModel with Store {
102 case WalletType.litecoin:
103 return bitcoin!.setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
104 case WalletType.ethereum:
107 - return ethereum!.setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
105 case WalletType.polygon:
109 - return polygon!.setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
106 + return evm!.setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
107 default:
108 throw Exception('Unexpected wallet type: ${wallet.type}');
109 }
lib/view_model/hardware_wallet/ledger_view_model.dart
+3 -6
@@ -3,11 +3,10 @@ import 'dart:io';
3
4 import 'package:cake_wallet/bitcoin/bitcoin.dart';
5 import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
6 -import 'package:cake_wallet/ethereum/ethereum.dart';
6 +import 'package:cake_wallet/evm/evm.dart';
7 import 'package:cake_wallet/generated/i18n.dart';
8 import 'package:cake_wallet/main.dart';
9 import 'package:cake_wallet/monero/monero.dart';
10 -import 'package:cake_wallet/polygon/polygon.dart';
10 import 'package:cake_wallet/routes.dart';
11 import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
12 import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
@@ -187,9 +186,8 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
186 case WalletType.litecoin:
187 return bitcoin!.setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
188 case WalletType.ethereum:
190 - return ethereum!.setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
189 case WalletType.polygon:
192 - return polygon!.setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
190 + return evm!.setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
191 default:
192 throw Exception('Unexpected wallet type: ${wallet.type}');
193 }
@@ -203,9 +201,8 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
201 case WalletType.litecoin:
202 return bitcoin!.getLedgerHardwareWalletService(connection, false);
203 case WalletType.ethereum:
206 - return ethereum!.getLedgerHardwareWalletService(connection);
204 case WalletType.polygon:
208 - return polygon!.getLedgerHardwareWalletService(connection);
205 + return evm!.getLedgerHardwareWalletService(connection);
206 default:
207 throw UnimplementedError();
208 }
lib/view_model/hardware_wallet/trezor_view_model.dart
+3 -6
@@ -2,8 +2,7 @@ import 'dart:async';
2
3 import 'package:cake_wallet/bitcoin/bitcoin.dart';
4 import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
5 -import 'package:cake_wallet/ethereum/ethereum.dart';
6 -import 'package:cake_wallet/polygon/polygon.dart';
5 +import 'package:cake_wallet/evm/evm.dart';
6 import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
7 import 'package:cw_core/hardware/hardware_wallet_service.dart';
8 import 'package:cw_core/wallet_base.dart';
@@ -57,9 +56,8 @@ abstract class TrezorViewModelBase extends HardwareWalletViewModel with Store {
56 case WalletType.litecoin:
57 return bitcoin!.getTrezorHardwareWalletService(trezorConnect, false);
58 case WalletType.ethereum:
60 - return ethereum!.getTrezorHardwareWalletService(trezorConnect);
59 case WalletType.polygon:
62 - return polygon!.getTrezorHardwareWalletService(trezorConnect);
60 + return evm!.getTrezorHardwareWalletService(trezorConnect);
61 default:
62 throw UnimplementedError();
63 }
@@ -72,9 +70,8 @@ abstract class TrezorViewModelBase extends HardwareWalletViewModel with Store {
70 case WalletType.litecoin:
71 return bitcoin!.setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
72 case WalletType.ethereum:
75 - return ethereum!.setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
73 case WalletType.polygon:
77 - return polygon!.setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
74 + return evm!.setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
75 default:
76 throw Exception('Unexpected wallet type: ${wallet.type}');
77 }
lib/view_model/integrations/deuro_view_model.dart
+54 -20
@@ -2,7 +2,7 @@ import 'package:cake_wallet/core/execution_state.dart';
2 import 'package:cake_wallet/core/utilities.dart';
3 import 'package:cake_wallet/entities/calculate_fiat_amount.dart';
4 import 'package:cake_wallet/entities/fiat_currency.dart';
5 -import 'package:cake_wallet/ethereum/ethereum.dart';
5 +import 'package:cake_wallet/evm/evm.dart';
6 import 'package:cake_wallet/store/app_store.dart';
7 import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
8 import 'package:cake_wallet/store/settings_store.dart';
@@ -13,7 +13,6 @@ import 'package:cw_core/crypto_currency.dart';
13 import 'package:cw_core/parse_fixed.dart';
14 import 'package:cw_core/pending_transaction.dart';
15 import 'package:cw_core/wallet_base.dart';
16 -import 'package:cw_core/wallet_type.dart';
16 import 'package:mobx/mobx.dart';
17
18 part 'deuro_view_model.g.dart';
@@ -25,8 +24,8 @@ abstract class DEuroViewModelBase with Store {
24
25 static BigInt get MIN_ACCRUED_INTEREST => BigInt.parse("1000000000000");
26
28 - DEuroViewModelBase(this._appStore, this.balanceViewModel, this._settingsStore,
29 - this._fiatConversationStore,
27 + DEuroViewModelBase(
28 + this._appStore, this.balanceViewModel, this._settingsStore, this._fiatConversationStore,
29 [this.hardwareWalletViewModel]) {
30 reloadInterestRate();
31 reloadSavingsUserData();
@@ -84,7 +83,7 @@ abstract class DEuroViewModelBase with Store {
83
84 @computed
85 String get savingsBalanceFormated =>
87 - ethereum!.formatterEthereumAmountToDouble(amount: savingsBalance).toStringAsFixed(6);
86 + evm!.formatterEVMAmountToDouble(amount: savingsBalance).toStringAsFixed(6);
87
88 @computed
89 String get fiatSavingsBalanceFormated => _getDEuroFiatAmount(savingsBalanceFormated);
@@ -100,7 +99,7 @@ abstract class DEuroViewModelBase with Store {
99
100 @computed
101 String get accruedInterestFormated =>
103 - ethereum!.formatterEthereumAmountToDouble(amount: accruedInterest).toStringAsFixed(6);
102 + evm!.formatterEVMAmountToDouble(amount: accruedInterest).toStringAsFixed(6);
103
104 @computed
105 String get fiatAccruedInterestFormated => _getDEuroFiatAmount(accruedInterestFormated);
@@ -128,15 +127,15 @@ abstract class DEuroViewModelBase with Store {
127
128 @action
129 Future<void> reloadSavingsUserData() async {
131 - approvedTokens = await ethereum!.getDEuroSavingsApproved(_appStore.wallet!);
132 - savingsBalance = await ethereum!.getDEuroSavingsBalance(_appStore.wallet!);
133 - accruedInterest = await ethereum!.getDEuroAccruedInterest(_appStore.wallet!);
130 + approvedTokens = await evm!.getDEuroSavingsApproved(_appStore.wallet!) ?? BigInt.zero;
131 + savingsBalance = await evm!.getDEuroSavingsBalance(_appStore.wallet!) ?? BigInt.zero;
132 + accruedInterest = await evm!.getDEuroAccruedInterest(_appStore.wallet!) ?? BigInt.zero;
133 isLoading = false;
134 }
135
136 @action
137 Future<void> reloadInterestRate() async {
139 - final interestRateRaw = await ethereum!.getDEuroInterestRate(_appStore.wallet!);
138 + final interestRateRaw = await evm!.getDEuroInterestRate(_appStore.wallet!) ?? BigInt.zero;
139
140 interestRateFormated = (interestRateRaw / BigInt.from(10000)).toString();
141 }
@@ -150,8 +149,12 @@ abstract class DEuroViewModelBase with Store {
149 }
150 try {
151 state = TransactionCommitting();
153 - final priority = _appStore.settingsStore.priority[WalletType.ethereum]!;
154 - approvalTransaction = await ethereum!.enableDEuroSaving(_appStore.wallet!, priority);
152 + final priority = _appStore.settingsStore.getPriority(wallet.type, chainId: wallet.chainId)!;
153 + final approval = await evm!.enableDEuroSaving(_appStore.wallet!, priority);
154 + if (approval == null) {
155 + throw Exception('DEuro saving not available');
156 + }
157 + approvalTransaction = approval;
158 state = InitialExecutionState();
159 } catch (e) {
160 state = FailureState(e.toString());
@@ -162,26 +165,57 @@ abstract class DEuroViewModelBase with Store {
165 Future<void> prepareSavingsEdit(String amountRaw, bool isAdding) async {
166 try {
167 state = TransactionCommitting();
165 - final amount = parseFixed(amountRaw, 18);
166 - final priority = _appStore.settingsStore.priority[WalletType.ethereum]!;
168 +
169 + if (amountRaw.isEmpty || amountRaw.trim().isEmpty) {
170 + throw Exception('Invalid amount: amount cannot be empty');
171 + }
172 +
173 + final amount = tryParseFixed(amountRaw, 18);
174 +
175 + if (amount == BigInt.zero || amount == null) {
176 + throw Exception('Invalid amount: amount cannot be zero');
177 + }
178 +
179 + final priority = _appStore.settingsStore.getPriority(wallet.type, chainId: wallet.chainId)!;
180 actionType = isAdding ? DEuroActionType.deposit : DEuroActionType.withdraw;
168 - transaction = await (isAdding
169 - ? ethereum!.addDEuroSaving(_appStore.wallet!, amount, priority)
170 - : ethereum!.removeDEuroSaving(_appStore.wallet!, amount, priority));
181 + final tx = await (isAdding
182 + ? evm!.addDEuroSaving(_appStore.wallet!, amount, priority)
183 + : evm!.removeDEuroSaving(_appStore.wallet!, amount, priority));
184 + if (tx == null) {
185 + throw Exception('DEuro saving not available');
186 + }
187 + transaction = tx;
188 state = InitialExecutionState();
189 } catch (e) {
190 state = FailureState(e.toString());
191 }
192 }
193
177 - Future<void> prepareCollectInterest() => prepareSavingsEdit(accruedInterestFormated, false);
194 + Future<void> prepareCollectInterest() async {
195 + if (accruedInterest < MIN_ACCRUED_INTEREST) {
196 + state = FailureState('Accrued interest is below minimum threshold');
197 + return;
198 + }
199 +
200 + final formatted = accruedInterestFormated;
201 + if (formatted.isEmpty || formatted == '0.000000') {
202 + state = FailureState('Invalid accrued interest amount');
203 + return;
204 + }
205 +
206 + await prepareSavingsEdit(formatted, false);
207 + }
208
209 Future<void> prepareReinvestInterest() async {
210 try {
211 state = TransactionCommitting();
212 actionType = DEuroActionType.reinvest;
183 - final priority = _appStore.settingsStore.priority[WalletType.ethereum]!;
184 - transaction = await ethereum!.reinvestDEuroInterest(_appStore.wallet!, priority);
213 + final priority = _appStore.settingsStore.getPriority(wallet.type, chainId: wallet.chainId)!;
214 + final tx = await evm!.reinvestDEuroInterest(_appStore.wallet!, priority);
215 + if (tx == null) {
216 + throw Exception('DEuro saving not available');
217 + }
218 + transaction = tx;
219 state = InitialExecutionState();
220 } catch (e) {
221 state = FailureState(e.toString());
lib/view_model/node_list/node_list_view_model.dart
+86 -7
@@ -10,6 +10,8 @@ import 'package:cw_core/node.dart';
10 import 'package:cake_wallet/entities/node_list.dart';
11 import 'package:cake_wallet/entities/default_settings_migration.dart';
12 import 'package:cw_core/wallet_type.dart';
13 +import 'package:cake_wallet/evm/evm.dart';
14 +import 'package:cake_wallet/reactions/wallet_connect.dart';
15
16 part 'node_list_view_model.g.dart';
17
@@ -24,16 +26,37 @@ abstract class NodeListViewModelBase with Store {
26 reaction((_) => _appStore.wallet, (WalletBase? _wallet) {
27 _bindNodes();
28 });
29 +
30 + reaction((_) {
31 + final wallet = _appStore.wallet;
32 + if (wallet != null && isEVMCompatibleChain(wallet.type)) {
33 + // Access selectedChainId to track changes
34 + return evm!.getSelectedChainId(wallet);
35 + }
36 + return null;
37 + }, (_) {
38 + _bindNodes();
39 + });
40 }
41
42 @computed
43 Node get currentNode {
31 - final node = settingsStore.nodes[_appStore.wallet!.type];
44 + final wallet = _appStore.wallet!;
45 + final walletType = wallet.type;
46
33 - if (node == null) {
34 - throw Exception('No node for wallet type: ${_appStore.wallet!.type}');
47 + int? chainId;
48 + if (isEVMCompatibleChain(walletType)) {
49 + chainId = evm!.getSelectedChainId(wallet);
50 }
51
52 + if (isEVMCompatibleChain(walletType) && chainId != null) {
53 + return settingsStore.getCurrentNode(walletType, chainId: chainId);
54 + }
55 +
56 + final node = settingsStore.nodes[walletType];
57 + if (node == null) {
58 + throw Exception('No node for wallet type: $walletType');
59 + }
60 return node;
61 }
62
@@ -56,11 +79,27 @@ abstract class NodeListViewModelBase with Store {
79 Future<void> reset() async {
80 await resetToDefault(_nodeSource);
81
82 + final wallet = _appStore.wallet!;
83 + final walletType = wallet.type;
84 +
85 Node node;
60 - if (_appStore.wallet!.type == WalletType.bitcoin && _appStore.wallet!.isTestnet) {
86 + if (walletType == WalletType.bitcoin && wallet.isTestnet) {
87 node = getBitcoinTestnetDefaultElectrumServer(nodes: _nodeSource)!;
88 + } else if (isEVMCompatibleChain(walletType)) {
89 + final chainId = evm!.getSelectedChainId(wallet);
90 + if (chainId != null) {
91 + final nodeWalletType = evm!.getWalletTypeByChainId(chainId);
92 + if (nodeWalletType != null) {
93 + node = getDefaultNode(nodes: _nodeSource, type: nodeWalletType)!;
94 + } else {
95 + throw Exception(
96 + 'Cannot reset node for EVM wallet: wallet type not found for chainId: $chainId');
97 + }
98 + } else {
99 + throw Exception('Cannot reset node for EVM wallet: chainId is null');
100 + }
101 } else {
63 - node = getDefaultNode(nodes: _nodeSource, type: _appStore.wallet!.type)!;
102 + node = getDefaultNode(nodes: _nodeSource, type: walletType)!;
103 }
104
105 await setAsCurrent(node);
@@ -70,14 +109,54 @@ abstract class NodeListViewModelBase with Store {
109 Future<void> delete(Node node) async => node.delete();
110
111 @action
73 - Future<void> setAsCurrent(Node node) async => settingsStore.nodes[_appStore.wallet!.type] = node;
112 + Future<void> setAsCurrent(Node node) async {
113 + final wallet = _appStore.wallet!;
114 + final walletType = wallet.type;
115 +
116 + if (isEVMCompatibleChain(walletType)) {
117 + final chainId = evm!.getSelectedChainId(wallet);
118 + if (chainId != null) {
119 + final nodeWalletType = evm!.getWalletTypeByChainId(chainId);
120 + if (nodeWalletType != null) {
121 + settingsStore.nodes[nodeWalletType] = node;
122 + return;
123 + }
124 + }
125 + throw Exception('Cannot set node for EVM wallet: chainId or wallet type not found');
126 + }
127 +
128 + // For non-EVM wallets, use the wallet type directly
129 + settingsStore.nodes[walletType] = node;
130 + }
131
132 @action
133 void _bindNodes() {
134 nodes.clear();
135 + final wallet = _appStore.wallet!;
136 + final walletType = wallet.type;
137 +
138 + // We filter nodes by the wallet type corresponding to current chainId for EVM wallets
139 + if (isEVMCompatibleChain(walletType)) {
140 + final chainId = evm!.getSelectedChainId(wallet);
141 + if (chainId != null) {
142 + final nodeWalletType = evm!.getWalletTypeByChainId(chainId);
143 + if (nodeWalletType != null) {
144 + _nodeSource.bindToList(
145 + nodes,
146 + filter: (val) => val.type == nodeWalletType,
147 + initialFire: true,
148 + );
149 + return;
150 + }
151 + }
152 + // If chainId is null or wallet type not found, show no nodes
153 + return;
154 + }
155 +
156 + // For non-EVM wallets, use the wallet type directly
157 _nodeSource.bindToList(
158 nodes,
80 - filter: (val) => val.type == _appStore.wallet!.type,
159 + filter: (val) => val.type == walletType,
160 initialFire: true,
161 );
162 }
lib/view_model/payment/payment_view_model.dart
+189 -32
@@ -1,6 +1,8 @@
1 import 'dart:async';
2
3 import 'package:cake_wallet/core/universal_address_detector.dart';
4 +import 'package:cake_wallet/evm/evm.dart';
5 +import 'package:cake_wallet/reactions/wallet_connect.dart';
6 import 'package:cake_wallet/store/app_store.dart';
7 import 'package:cw_core/wallet_type.dart';
8 import 'package:cw_core/wallet_info.dart';
@@ -14,21 +16,47 @@ part 'payment_view_model.g.dart';
16 class PaymentViewModel = PaymentViewModelBase with _$PaymentViewModel;
17
18 abstract class PaymentViewModelBase with Store {
17 - PaymentViewModelBase({
18 - required this.appStore,
19 - });
19 + PaymentViewModelBase({required this.appStore});
20
21 final AppStore appStore;
22
23 @observable
24 WalletType? detectedWalletType;
25
26 + @observable
27 + AddressDetectionResult? _lastDetectionResult;
28 +
29 + @computed
30 + int? get detectedChainId {
31 + if (detectedWalletType == null) return null;
32 +
33 + if (!isEVMCompatibleChain(detectedWalletType!)) return null;
34 +
35 + if (_lastDetectionResult?.chainId != null) {
36 + return _lastDetectionResult!.chainId;
37 + }
38 +
39 + // If detected wallet type is EVM-compatible, get chainId from detected currency
40 + if (_lastDetectionResult?.detectedCurrency != null) {
41 + return getChainIdByCryptoCurrency(_lastDetectionResult!.detectedCurrency!);
42 + }
43 +
44 + return evm!.getChainIdByWalletType(detectedWalletType!);
45 + }
46 +
47 @observable
48 bool isProcessing = false;
49
50 @computed
51 WalletType get currentWalletType => appStore.wallet!.type;
52
53 + @computed
54 + int? get currentChainId {
55 + if (!isEVMCompatibleChain(currentWalletType)) return null;
56 +
57 + return evm!.getSelectedChainId(appStore.wallet!);
58 + }
59 +
60 /// Main entry point - detect address type and check compatibility
61 @action
62 Future<PaymentFlowResult> processAddress(String addressData) async {
@@ -39,18 +67,79 @@ abstract class PaymentViewModelBase with Store {
67 // Detect address type
68 final detectionResult = UniversalAddressDetector.detectAddress(addressData);
69
70 + _lastDetectionResult = detectionResult;
71 detectedWalletType = detectionResult.detectedWalletType;
72
73 if (!detectionResult.isValid || detectedWalletType == null) {
74 return PaymentFlowResult.incompatible('Unable to detect address type');
75 }
76
48 - if (!addressData.contains(':') && _isEVMAddress(detectionResult.address)) {
49 - return PaymentFlowResult.evmNetworkSelection(detectionResult);
77 + final currentWallet = appStore.wallet;
78 +
79 + if (isEVMCompatibleChain(detectedWalletType!)) {
80 + final isRawEvmInput = !addressData.contains(':') && _isEVMAddress(detectionResult.address);
81 +
82 + // Check if the current wallet is also EVM
83 + if (currentWallet != null && isEVMCompatibleChain(currentWallet.type)) {
84 + final currentChainId = evm!.getSelectedChainId(currentWallet);
85 + final detectedChainIdValue = this.detectedChainId;
86 +
87 + if (detectedChainIdValue != null && currentChainId != null) {
88 + // For raw EVM address input that only defaulted to chainId 1,
89 + // always force the EVM ecosystem bottom sheet so the user
90 + // can pick the actual network and token.
91 + if (isRawEvmInput && detectedChainIdValue == 1) {
92 + final allEVMWallets = await getEVMCompatibleWallets();
93 +
94 + final currentWalletInfo = currentWallet.walletInfo;
95 +
96 + final otherEVMWallets =
97 + allEVMWallets.where((w) => w.name != currentWallet.name).toList();
98 +
99 + return PaymentFlowResult.evmNetworkSelection(
100 + detectionResult,
101 + compatibleWallets: otherEVMWallets,
102 + wallet: currentWalletInfo,
103 + );
104 + }
105 +
106 + if (detectedChainIdValue == currentChainId) {
107 + return PaymentFlowResult.currentWalletCompatible();
108 + }
109 +
110 + final allEVMWallets = await getEVMCompatibleWallets();
111 +
112 + final currentWalletInfo = currentWallet.walletInfo;
113 +
114 + final otherEVMWallets =
115 + allEVMWallets.where((w) => w.name != currentWallet.name).toList();
116 +
117 + return PaymentFlowResult.evmNetworkSelection(
118 + detectionResult,
119 + compatibleWallets: otherEVMWallets,
120 + wallet: currentWalletInfo,
121 + );
122 + }
123 + }
124 +
125 + // If the current wallet is not EVM or the chainId comparison failed
126 + // We proceed with other checks
127 + if (!addressData.contains(':') && _isEVMAddress(detectionResult.address)) {
128 + final allEVMWallets = await getEVMCompatibleWallets();
129 + return PaymentFlowResult.evmNetworkSelection(
130 + detectionResult,
131 + compatibleWallets: allEVMWallets,
132 + );
133 + }
134 +
135 + // For EVM URIs, show network selection
136 + final allEVMWallets = await getEVMCompatibleWallets();
137 + return PaymentFlowResult.evmNetworkSelection(
138 + detectionResult,
139 + compatibleWallets: allEVMWallets,
140 + );
141 }
142
52 - // Check if current wallet is compatible
53 - final currentWallet = appStore.wallet;
143 if (currentWallet != null && currentWallet.type == detectedWalletType) {
144 return PaymentFlowResult.currentWalletCompatible();
145 }
@@ -73,6 +162,16 @@ abstract class PaymentViewModelBase with Store {
162 }
163 }
164
165 + @action
166 + Future<void> selectChain() async {
167 + if (detectedWalletType == null) return;
168 +
169 + final node =
170 + appStore.settingsStore.getCurrentNode(detectedWalletType!, chainId: detectedChainId);
171 +
172 + await evm!.selectChain(appStore.wallet!, detectedChainId!, node: node);
173 + }
174 +
175 bool _isEVMAddress(String address) {
176 return RegExp(r'^0x[a-fA-F0-9]{40}$').hasMatch(address);
177 }
@@ -80,12 +179,18 @@ abstract class PaymentViewModelBase with Store {
179 Future<List<WalletInfo>> getWalletsByType(WalletType walletType) async {
180 return (await WalletInfo.getAll()).where((wallet) => wallet.type == walletType).toList();
181 }
182 +
183 + Future<List<WalletInfo>> getEVMCompatibleWallets() async {
184 + final allWallets = await WalletInfo.getAll();
185 + return allWallets.where((wallet) => isEVMCompatibleChain(wallet.type)).toList();
186 + }
187 }
188
189 class PaymentFlowResult {
190 final PaymentFlowType type;
191 final String? message;
192 final WalletInfo? wallet;
193 + final int? chainId;
194 final List<WalletInfo> wallets;
195 final WalletType? walletType;
196 final AddressDetectionResult? addressDetectionResult;
@@ -94,6 +199,7 @@ class PaymentFlowResult {
199 required this.type,
200 this.message,
201 this.wallet,
202 + this.chainId,
203 this.wallets = const [],
204 this.walletType,
205 this.addressDetectionResult,
@@ -105,14 +211,24 @@ class PaymentFlowResult {
211 AddressDetectionResult addressDetectionResult, {
212 List<WalletInfo>? compatibleWallets,
213 WalletInfo? wallet,
108 - }) =>
109 - PaymentFlowResult._(
110 - type: PaymentFlowType.evmNetworkSelection,
111 - addressDetectionResult: addressDetectionResult,
112 - walletType: addressDetectionResult.detectedWalletType,
113 - wallets: compatibleWallets ?? [],
114 - wallet: wallet,
115 - );
214 + }) {
215 + int? chainId = addressDetectionResult.chainId;
216 + if (chainId == null && addressDetectionResult.detectedCurrency != null) {
217 + chainId = getChainIdByCryptoCurrency(addressDetectionResult.detectedCurrency!);
218 + }
219 + if (chainId == null && addressDetectionResult.detectedWalletType != null) {
220 + chainId = evm!.getChainIdByWalletType(addressDetectionResult.detectedWalletType!);
221 + }
222 +
223 + return PaymentFlowResult._(
224 + type: PaymentFlowType.evmNetworkSelection,
225 + addressDetectionResult: addressDetectionResult,
226 + walletType: addressDetectionResult.detectedWalletType,
227 + chainId: chainId,
228 + wallets: compatibleWallets ?? [],
229 + wallet: wallet,
230 + );
231 + }
232
233 /// Current wallet is compatible
234 factory PaymentFlowResult.currentWalletCompatible() =>
@@ -122,29 +238,66 @@ class PaymentFlowResult {
238 factory PaymentFlowResult.singleWallet(
239 WalletInfo wallet,
240 AddressDetectionResult addressDetectionResult,
125 - ) =>
126 - PaymentFlowResult._(
127 - type: PaymentFlowType.singleWallet,
128 - wallet: wallet,
129 - walletType: wallet.type,
130 - addressDetectionResult: addressDetectionResult);
241 + ) {
242 + int? chainId = addressDetectionResult.chainId;
243 + if (chainId == null && addressDetectionResult.detectedCurrency != null) {
244 + chainId = getChainIdByCryptoCurrency(addressDetectionResult.detectedCurrency!);
245 + }
246 + if (chainId == null) {
247 + chainId = evm!.getChainIdByWalletType(wallet.type);
248 + }
249 +
250 + return PaymentFlowResult._(
251 + type: PaymentFlowType.singleWallet,
252 + wallet: wallet,
253 + walletType: wallet.type,
254 + chainId: chainId,
255 + addressDetectionResult: addressDetectionResult,
256 + );
257 + }
258
259 /// Multiple compatible wallets available
260 factory PaymentFlowResult.multipleWallets(
134 - List<WalletInfo> wallets, AddressDetectionResult addressDetectionResult) =>
135 - PaymentFlowResult._(
136 - type: PaymentFlowType.multipleWallets,
137 - wallets: wallets,
138 - walletType: wallets.first.type,
139 - addressDetectionResult: addressDetectionResult);
261 + List<WalletInfo> wallets,
262 + AddressDetectionResult addressDetectionResult,
263 + ) {
264 + int? chainId = addressDetectionResult.chainId;
265 + if (chainId == null && addressDetectionResult.detectedCurrency != null) {
266 + chainId = getChainIdByCryptoCurrency(addressDetectionResult.detectedCurrency!);
267 + }
268 + if (chainId == null) {
269 + chainId = evm!.getChainIdByWalletType(wallets.first.type);
270 + }
271 +
272 + return PaymentFlowResult._(
273 + type: PaymentFlowType.multipleWallets,
274 + wallets: wallets,
275 + walletType: wallets.first.type,
276 + addressDetectionResult: addressDetectionResult,
277 + chainId: chainId,
278 + );
279 + }
280
281 /// No compatible wallets available
282 factory PaymentFlowResult.noWallets(
143 - WalletType walletType, AddressDetectionResult addressDetectionResult) =>
144 - PaymentFlowResult._(
145 - type: PaymentFlowType.noWallets,
146 - walletType: walletType,
147 - addressDetectionResult: addressDetectionResult);
283 + WalletType walletType,
284 + AddressDetectionResult addressDetectionResult,
285 + ) {
286 + int? chainId = addressDetectionResult.chainId;
287 + if (chainId == null && addressDetectionResult.detectedCurrency != null) {
288 + chainId = getChainIdByCryptoCurrency(addressDetectionResult.detectedCurrency!);
289 + }
290 + if (chainId == null) {
291 + chainId = evm!.getChainIdByWalletType(walletType);
292 + }
293 +
294 + return PaymentFlowResult._(
295 + type: PaymentFlowType.noWallets,
296 + walletType: walletType,
297 + addressDetectionResult: addressDetectionResult,
298 + chainId: chainId,
299 + );
300 + }
301
302 /// Error occurred
303 factory PaymentFlowResult.error(String message) =>
@@ -159,6 +312,10 @@ class PaymentFlowResult {
312 return addressDetectionResult?.detectedCurrency;
313 }
314 if (walletType != null) {
315 + if (isEVMCompatibleChain(walletType!)) {
316 + return walletTypeToCryptoCurrency(walletType!, chainId: chainId);
317 + }
318 +
319 return walletTypeToCryptoCurrency(walletType!);
320 }
321 return null;
lib/view_model/restore/wallet_restore_from_qr_code.dart
+13 -10
@@ -85,7 +85,9 @@ class WalletRestoreFromQRCode {
85 try {
86 return AddressResolver.extractAddressByType(
87 raw: rawString,
88 - type: walletTypeToCryptoCurrency(type),
88 + type: walletTypeToCryptoCurrency(
89 + type,
90 + ),
91 requireSurroundingWhitespaces: false,
92 );
93 } catch (_) {
@@ -118,7 +120,11 @@ class WalletRestoreFromQRCode {
120
121 String formattedUri = '';
122 WalletType? walletType = _extractWalletType(code);
121 - final prefix = code.startsWith('xpub') ? 'xpub' : code.startsWith('zpub') ? 'zpub' : '????';
123 + final prefix = code.startsWith('xpub')
124 + ? 'xpub'
125 + : code.startsWith('zpub')
126 + ? 'zpub'
127 + : '????';
128 if (walletType == null) {
129 await _specifyWalletAssets(context, "Can't determine wallet type, please pick it manually");
130 walletType =
@@ -129,15 +135,13 @@ class WalletRestoreFromQRCode {
135
136 formattedUri = seedPhrase != null
137 ? '$walletType:?seed=$seedPhrase'
132 - : code.startsWith(prefix)
133 - ? '$walletType:?$prefix=$code'
134 - : throw Exception('Failed to determine valid seed phrase');
138 + : code.startsWith(prefix)
139 + ? '$walletType:?$prefix=$code'
140 + : throw Exception('Failed to determine valid seed phrase');
141 } else {
142 final index = code.indexOf(':');
143 final query = code.substring(index + 1).replaceAll('?', '&');
138 - formattedUri = code.startsWith(prefix)
139 - ? '$walletType:?$prefix=$code'
140 - :'$walletType:?$query';
144 + formattedUri = code.startsWith(prefix) ? '$walletType:?$prefix=$code' : '$walletType:?$query';
145 }
146
147 final uri = Uri.parse(formattedUri);
@@ -174,8 +178,7 @@ class WalletRestoreFromQRCode {
178 throw Exception('Unexpected restore mode: tx_payment_id is invalid');
179 }
180
177 - if (credentials.containsKey("xpub") ||
178 - credentials.containsKey("zpub")) {
181 + if (credentials.containsKey("xpub") || credentials.containsKey("zpub")) {
182 return WalletRestoreMode.keys;
183 }
184
lib/view_model/send/fees_view_model.dart
+30 -31
@@ -1,13 +1,10 @@
1 -import 'package:cake_wallet/arbitrum/arbitrum.dart';
2 -import 'package:cake_wallet/base/base.dart';
1 import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
2 import 'package:cake_wallet/decred/decred.dart';
3 import 'package:cake_wallet/dogecoin/dogecoin.dart';
4 import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
5 import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
8 -import 'package:cake_wallet/ethereum/ethereum.dart';
6 +import 'package:cake_wallet/evm/evm.dart';
7 import 'package:cake_wallet/monero/monero.dart';
10 -import 'package:cake_wallet/polygon/polygon.dart';
8 import 'package:cake_wallet/store/app_store.dart';
9 import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
10 import 'package:cw_core/crypto_currency.dart';
@@ -29,14 +26,15 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor
26 this.balanceViewModel,
27 ) : _settingsStore = appStore.settingsStore,
28 super(appStore: appStore) {
32 - if (wallet.type == WalletType.bitcoin &&
33 - _settingsStore.priority[wallet.type] == bitcoinTransactionPriorityCustom) {
29 + final priority = _settingsStore.getPriority(wallet.type, chainId: wallet.chainId);
30 +
31 + if (wallet.type == WalletType.bitcoin && priority == bitcoinTransactionPriorityCustom) {
32 setTransactionPriority(bitcoinTransactionPriorityMedium);
33 }
36 - final priority = _settingsStore.priority[wallet.type];
34 +
35 final priorities = priorityForWalletType(wallet.type);
38 - if (!priorityForWalletType(wallet.type).contains(priority) && priorities.isNotEmpty) {
39 - _settingsStore.priority[wallet.type] = priorities.first;
36 + if (!priorities.contains(priority) && priorities.isNotEmpty) {
37 + _settingsStore.setPriority(wallet.type, priorities.first, chainId: wallet.chainId);
38 }
39 }
40
@@ -49,13 +47,13 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor
47 final BalanceViewModel balanceViewModel;
48
49 TransactionPriority get transactionPriority {
52 - final priority = _settingsStore.priority[wallet.type];
50 + final priority = _settingsStore.getPriority(wallet.type, chainId: wallet.chainId);
51
54 - if (priority == null) {
52 + if (priority == null && hasFeesPriority) {
53 throw Exception('Unexpected type ${wallet.type}');
54 }
55
58 - return priority;
56 + return priority!;
57 }
58
59 int? getCustomPriorityIndex(List<TransactionPriority> priorities) {
@@ -76,6 +74,8 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor
74 }
75
76 bool get isLowFee {
77 + if (wallet.chainId == 42161) return false;
78 +
79 switch (wallet.type) {
80 case WalletType.monero:
81 case WalletType.wownero:
@@ -87,13 +87,11 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor
87 case WalletType.litecoin:
88 return transactionPriority == bitcoin!.getLitecoinTransactionPrioritySlow();
89 case WalletType.ethereum:
90 - return transactionPriority == ethereum!.getEthereumTransactionPrioritySlow();
91 - case WalletType.bitcoinCash:
92 - return transactionPriority == bitcoinCash!.getBitcoinCashTransactionPrioritySlow();
90 case WalletType.polygon:
94 - return transactionPriority == polygon!.getPolygonTransactionPrioritySlow();
91 case WalletType.base:
96 - return transactionPriority == base!.getBaseTransactionPrioritySlow();
92 + return transactionPriority == evm!.getEVMTransactionPrioritySlow();
93 + case WalletType.bitcoinCash:
94 + return transactionPriority == bitcoinCash!.getBitcoinCashTransactionPrioritySlow();
95 case WalletType.decred:
96 return transactionPriority == decred!.getDecredTransactionPrioritySlow();
97 case WalletType.dogecoin:
@@ -122,7 +120,8 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor
120 wallet.type != WalletType.banano &&
121 wallet.type != WalletType.solana &&
122 wallet.type != WalletType.tron &&
125 - wallet.type != WalletType.arbitrum;
123 + wallet.chainId !=
124 + 42161; // Wallet type is generic for all EVM chains, so we need to check the chainId
125
126 @computed
127 bool get isElectrumWallet =>
@@ -140,7 +139,7 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor
139
140 @action
141 void setTransactionPriority(TransactionPriority priority) =>
143 - _settingsStore.priority[wallet.type] = priority;
142 + _settingsStore.setPriority(wallet.type, priority, chainId: wallet.chainId);
143
144 bool showAlertForCustomFeeRate() {
145 if (wallet.type != WalletType.bitcoin || isLowFee) {
@@ -184,28 +183,28 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor
183 case WalletType.haven:
184 case WalletType.wownero:
185 case WalletType.zano:
187 - _settingsStore.priority[wallet.type] = monero!.getMoneroTransactionPriorityAutomatic();
186 + _settingsStore.setPriority(wallet.type, monero!.getMoneroTransactionPriorityAutomatic());
187 break;
188 case WalletType.bitcoin:
190 - _settingsStore.priority[wallet.type] = bitcoin!.getBitcoinTransactionPriorityMedium();
189 + _settingsStore.setPriority(wallet.type, bitcoin!.getBitcoinTransactionPriorityMedium());
190 break;
191 case WalletType.litecoin:
193 - _settingsStore.priority[wallet.type] = bitcoin!.getLitecoinTransactionPriorityMedium();
192 + _settingsStore.setPriority(wallet.type, bitcoin!.getLitecoinTransactionPriorityMedium());
193 break;
194 case WalletType.ethereum:
196 - _settingsStore.priority[wallet.type] = ethereum!.getDefaultTransactionPriority();
195 + case WalletType.polygon:
196 + case WalletType.base:
197 + _settingsStore.setPriority(
198 + wallet.type,
199 + evm!.getDefaultTransactionPriority(),
200 + chainId: wallet.chainId,
201 + );
202 break;
203 case WalletType.bitcoinCash:
199 - _settingsStore.priority[wallet.type] = bitcoinCash!.getDefaultTransactionPriority();
204 + _settingsStore.setPriority(wallet.type, bitcoinCash!.getDefaultTransactionPriority());
205 break;
206 case WalletType.dogecoin:
202 - _settingsStore.priority[wallet.type] = dogecoin!.getDefaultTransactionPriority();
203 - break;
204 - case WalletType.polygon:
205 - _settingsStore.priority[wallet.type] = polygon!.getDefaultTransactionPriority();
206 - break;
207 - case WalletType.base:
208 - _settingsStore.priority[wallet.type] = base!.getDefaultTransactionPriority();
207 + _settingsStore.setPriority(wallet.type, dogecoin!.getDefaultTransactionPriority());
208 break;
209 default:
210 break;
lib/view_model/send/output.dart
+14 -40
@@ -1,7 +1,4 @@
1 import 'dart:math';
2 -
3 -import 'package:cake_wallet/arbitrum/arbitrum.dart';
4 -import 'package:cake_wallet/base/base.dart';
2 import 'package:cake_wallet/bitcoin/bitcoin.dart';
3 import 'package:cake_wallet/decred/decred.dart';
4 import 'package:cake_wallet/di.dart';
@@ -10,10 +7,9 @@ import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart';
7 import 'package:cake_wallet/entities/contact_base.dart';
8 import 'package:cake_wallet/entities/parse_address_from_domain.dart';
9 import 'package:cake_wallet/entities/parsed_address.dart';
13 -import 'package:cake_wallet/ethereum/ethereum.dart';
10 +import 'package:cake_wallet/evm/evm.dart';
11 import 'package:cake_wallet/generated/i18n.dart';
12 import 'package:cake_wallet/monero/monero.dart';
16 -import 'package:cake_wallet/polygon/polygon.dart';
13 import 'package:cake_wallet/reactions/wallet_connect.dart';
14 import 'package:cake_wallet/solana/solana.dart';
15 import 'package:cake_wallet/src/screens/send/widgets/extract_address_from_parsed.dart';
@@ -117,16 +113,10 @@ abstract class OutputBase with Store {
113 _amount = decred!.formatterStringDoubleToDecredAmount(_cryptoAmount);
114 break;
115 case WalletType.ethereum:
120 - _amount = ethereum!.formatterEthereumParseAmount(_cryptoAmount);
121 - break;
116 case WalletType.polygon:
123 - _amount = polygon!.formatterPolygonParseAmount(_cryptoAmount);
124 - break;
117 case WalletType.base:
126 - _amount = base!.formatterBaseParseAmount(_cryptoAmount);
127 - break;
118 case WalletType.arbitrum:
129 - _amount = arbitrum!.formatterArbitrumParseAmount(_cryptoAmount);
119 + _amount = evm!.formatterEVMParseAmount(_cryptoAmount);
120 break;
121 case WalletType.wownero:
122 _amount = wownero!.formatterWowneroParseAmount(amount: _cryptoAmount);
@@ -161,14 +151,15 @@ abstract class OutputBase with Store {
151 @action
152 Future<void> calculateEstimatedFee() async {
153 try {
154 + final priority = _settingsStore.getPriority(_wallet.type, chainId: _wallet.chainId);
155 if (isEVMCompatibleChain(_wallet.type)) {
165 - await _wallet.updateEstimatedFeesParams(_settingsStore.priority[_wallet.type]!);
156 + await _wallet.updateEstimatedFeesParams(priority);
157 }
158
159 int fee = 0;
169 - if (_settingsStore.priority[_wallet.type] != null) {
160 + if (_settingsStore.getPriority(_wallet.type, chainId: _wallet.chainId) != null) {
161 fee = _wallet.calculateEstimatedFee(
171 - _settingsStore.priority[_wallet.type]!,
162 + _settingsStore.getPriority(_wallet.type, chainId: _wallet.chainId)!,
163 formattedCryptoAmount,
164 );
165 }
@@ -178,7 +169,7 @@ abstract class OutputBase with Store {
169 estimatedFee = monero!.formatterMoneroAmountToDouble(amount: fee).toString();
170 break;
171 case WalletType.bitcoin:
181 - if (_settingsStore.priority[_wallet.type] ==
172 + if (_settingsStore.getPriority(_wallet.type) ==
173 bitcoin!.getBitcoinTransactionPriorityCustom()) {
174 fee = bitcoin!.getEstimatedFeeWithFeeRate(
175 _wallet, _settingsStore.customBitcoinFeeRate, formattedCryptoAmount);
@@ -216,33 +207,16 @@ abstract class OutputBase with Store {
207
208 /// EVMs
209 case WalletType.ethereum:
219 - String? fee = cryptoCurrencyHandler() == CryptoCurrency.eth
220 - ? ethereum!.getEthereumNativeEstimatedFee(_wallet)
221 - : ethereum!.getEthereumERC20EstimatedFee(_wallet);
222 -
223 - estimatedFee = formatFixed(BigInt.parse(fee ?? '0.0'), 18, fractionalDigits: 12);
224 - break;
225 -
210 case WalletType.polygon:
227 - String? fee = cryptoCurrencyHandler() == CryptoCurrency.maticpoly
228 - ? polygon!.getPolygonNativeEstimatedFee(_wallet)
229 - : polygon!.getPolygonERC20EstimatedFee(_wallet);
230 -
231 - estimatedFee = formatFixed(BigInt.parse(fee ?? '0.0'), 18, fractionalDigits: 12);
232 - break;
233 -
211 case WalletType.base:
235 - String? fee = cryptoCurrencyHandler() == CryptoCurrency.baseEth
236 - ? base!.getBaseNativeEstimatedFee(_wallet)
237 - : base!.getBaseERC20EstimatedFee(_wallet);
238 -
239 - estimatedFee = formatFixed(BigInt.parse(fee ?? '0.0'), 18, fractionalDigits: 12);
240 - break;
241 -
212 case WalletType.arbitrum:
243 - String? fee = cryptoCurrencyHandler() == CryptoCurrency.arbEth
244 - ? arbitrum!.getArbitrumNativeEstimatedFee(_wallet)
245 - : arbitrum!.getArbitrumERC20EstimatedFee(_wallet);
213 + final isNative = cryptoCurrencyHandler() == CryptoCurrency.eth ||
214 + cryptoCurrencyHandler() == CryptoCurrency.maticpoly ||
215 + cryptoCurrencyHandler() == CryptoCurrency.baseEth ||
216 + cryptoCurrencyHandler() == CryptoCurrency.arbEth;
217 + String? fee = isNative
218 + ? evm!.getEVMNativeEstimatedFee(_wallet)
219 + : evm!.getEVMERC20EstimatedFee(_wallet);
220
221 estimatedFee = formatFixed(BigInt.parse(fee ?? '0.0'), 18, fractionalDigits: 12);
222 break;
lib/view_model/send/send_template_view_model.dart
+12 -7
@@ -1,3 +1,4 @@
1 +import 'package:cake_wallet/evm/evm.dart';
2 import 'package:cake_wallet/reactions/wallet_connect.dart';
3 import 'package:cake_wallet/view_model/send/template_view_model.dart';
4 import 'package:cw_core/crypto_currency.dart';
@@ -44,8 +45,12 @@ abstract class SendTemplateViewModelBase with Store {
45 recipients.remove(recipient);
46 }
47
47 - AmountValidator get amountValidator =>
48 - AmountValidator(currency: walletTypeToCryptoCurrency(_wallet.type));
48 + AmountValidator get amountValidator => AmountValidator(
49 + currency: walletTypeToCryptoCurrency(
50 + _wallet.type,
51 + chainId: _wallet.chainId,
52 + ),
53 + );
54
55 AddressValidator get addressValidator =>
56 AddressValidator(type: _wallet.currency, isTestnet: _wallet.isTestnet);
@@ -54,12 +59,12 @@ abstract class SendTemplateViewModelBase with Store {
59
60 bool get hasMultiRecipient =>
61 _wallet.type != WalletType.haven &&
57 - _wallet.type != WalletType.ethereum &&
58 - _wallet.type != WalletType.polygon &&
59 - _wallet.type != WalletType.base &&
60 - _wallet.type != WalletType.arbitrum &&
62 _wallet.type != WalletType.solana &&
62 - _wallet.type != WalletType.tron;
63 + _wallet.type != WalletType.tron &&
64 + _wallet.chainId != 1 &&
65 + _wallet.chainId != 137 &&
66 + _wallet.chainId != 8453 &&
67 + _wallet.chainId != 42161;
68
69 @computed
70 CryptoCurrency get cryptoCurrency => _wallet.currency;
lib/view_model/send/send_view_model.dart
+65 -183
@@ -1,6 +1,5 @@
1 import 'dart:async';
2
3 -import 'package:cake_wallet/base/base.dart';
3 import 'package:cake_wallet/bitcoin/bitcoin.dart';
4 import 'package:cake_wallet/core/address_validator.dart';
5 import 'package:cake_wallet/core/amount_validator.dart';
@@ -20,7 +19,7 @@ import 'package:cake_wallet/entities/preferences_key.dart';
19 import 'package:cake_wallet/entities/template.dart';
20 import 'package:cake_wallet/entities/transaction_description.dart';
21 import 'package:cake_wallet/entities/wallet_contact.dart';
23 -import 'package:cake_wallet/ethereum/ethereum.dart';
22 +import 'package:cake_wallet/evm/evm.dart';
23 import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
24 import 'package:cake_wallet/exchange/provider/swapsxyz_exchange_provider.dart';
25 import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart';
@@ -28,8 +27,6 @@ import 'package:cake_wallet/exchange/trade.dart';
27 import 'package:cake_wallet/generated/i18n.dart';
28 import 'package:cake_wallet/monero/monero.dart';
29 import 'package:cake_wallet/nano/nano.dart';
31 -import 'package:cake_wallet/polygon/polygon.dart';
32 -import 'package:cake_wallet/arbitrum/arbitrum.dart';
30 import 'package:cake_wallet/reactions/wallet_connect.dart';
31 import 'package:cake_wallet/routes.dart';
32 import 'package:cake_wallet/solana/solana.dart';
@@ -50,7 +47,6 @@ import 'package:cake_wallet/wownero/wownero.dart';
47 import 'package:cake_wallet/zano/zano.dart';
48 import 'package:cw_core/crypto_currency.dart';
49 import 'package:cw_core/erc20_token.dart';
53 -import 'package:cw_core/currency_for_wallet_type.dart';
50 import 'package:cw_core/exceptions.dart';
51 import 'package:cw_core/pending_transaction.dart';
52 import 'package:cw_core/sync_status.dart';
@@ -73,7 +69,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
69 void onWalletChange(wallet) {
70 currencies = wallet.balance.keys.toList();
71 selectedCryptoCurrency = wallet.currency;
76 - hasMultipleTokens = isEVMCompatibleChain(wallet.type) ||
72 + hasMultipleTokens = isEVMWallet ||
73 wallet.type == WalletType.solana ||
74 wallet.type == WalletType.tron ||
75 wallet.type == WalletType.zano;
@@ -109,6 +105,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
105 appStore.wallet!.type == WalletType.solana ||
106 appStore.wallet!.type == WalletType.tron ||
107 appStore.wallet!.type == WalletType.zano,
108 + selectedChainId = appStore.wallet!.chainId,
109 outputs = ObservableList<Output>(),
110 _settingsStore = appStore.settingsStore,
111 fiatFromSettings = appStore.settingsStore.fiatCurrency,
@@ -119,6 +116,20 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
116 unspentCoinsListViewModel.initialSetup().then((_) {
117 unspentCoinsListViewModel.resetUnspentCoinsInfoSelections();
118 });
119 +
120 + reaction((_) {
121 + if (isEVMCompatibleChain(wallet.type)) {
122 + // Access currency which depends on selectedChainId, so MobX tracks the change
123 + return wallet.currency;
124 + }
125 + return null;
126 + }, (_) async {
127 + // When chain changes, update currencies and selected currency
128 + await Future.delayed(const Duration(milliseconds: 100));
129 + currencies = wallet.balance.keys.toList();
130 + selectedCryptoCurrency = wallet.currency;
131 + updateSendingBalance();
132 + });
133 }
134
135 PendingTransaction? _pendingApprovalTx;
@@ -137,6 +148,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
148 bool get isMwebEnabled => balanceViewModel.mwebEnabled;
149
150 bool get isEVMWallet => isEVMCompatibleChain(walletType);
151 +
152 @action
153 void setShowAddressBookPopup(bool value) {
154 _settingsStore.showAddressBookPopupEnabled = value;
@@ -237,7 +249,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
249 CryptoCurrency get currency => wallet.currency;
250
251 Validator<String> amountValidator(Output output) => AmountValidator(
240 - currency: walletTypeToCryptoCurrency(wallet.type),
252 + currency: wallet.currency,
253 minValue: isSendToSilentPayments(output)
254 ?
255 // TODO: get from server
@@ -266,7 +278,12 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
278 coinTypeToSpendFrom == UnspentCoinType.nonMweb) {
279 return balanceViewModel.balances.values.first.availableBalance;
280 }
269 - return wallet.balance[selectedCryptoCurrency]!.formattedFullAvailableBalance;
281 + // Handle case where balance might not be available yet (e.g., during chain switch)
282 + final balanceForCurrency = wallet.balance[selectedCryptoCurrency];
283 + if (balanceForCurrency == null) {
284 + return wallet.formatCryptoAmount('0');
285 + }
286 + return balanceForCurrency.formattedFullAvailableBalance;
287 }
288
289 @action
@@ -383,6 +400,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
400 @observable
401 bool hasMultipleTokens;
402
403 + @observable
404 + int? selectedChainId;
405 +
406 @computed
407 List<ContactRecord> get contactsToShow => contactListViewModel.contacts
408 .where((element) => element.type == selectedCryptoCurrency)
@@ -540,30 +560,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
560 (trade.requiresTokenApproval ?? false) && !_isPreparedTransfer;
561
562 if (requiresTokenApproval && tokenContract.isNotEmpty && requiredAmount > BigInt.zero) {
543 - if (walletType == WalletType.ethereum) {
544 - final priority = _settingsStore.priority[WalletType.ethereum]!;
545 - _pendingApprovalTx = await buildApprovalIfNeeded(
546 - spender: routerTo!, // if API provides a specific spender, use that instead
547 - tokenContract: tokenContract,
548 - requiredAmount: requiredAmount,
549 - sourceTokenDecimals: trade.sourceTokenDecimals,
550 - );
551 -
552 - // Build the callData tx
553 - pendingTransaction = await ethereum!.createRawCallDataTransaction(
554 - wallet,
555 - routerTo,
556 - routerData,
557 - routerValueWei,
558 - priority,
559 - );
560 -
561 - _isSwapsXYZCallDataTx = true;
562 - state = ExecutedSuccessfullyState();
563 - return pendingTransaction; // do NOT fall back to regular flow
564 - }
565 - if (walletType == WalletType.polygon) {
566 - final priority = _settingsStore.priority[WalletType.polygon]!;
563 + if (isEVMWallet) {
564 + final priority = _settingsStore.getPriority(walletType, chainId: selectedChainId);
565 _pendingApprovalTx = await buildApprovalIfNeeded(
566 spender: routerTo!,
567 tokenContract: tokenContract,
@@ -572,54 +570,15 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
570 );
571
572 // Build the callData tx
575 - pendingTransaction = await polygon!.createRawCallDataTransaction(
573 + pendingTransaction = await evm!.createRawCallDataTransaction(
574 wallet,
575 routerTo,
576 routerData,
577 routerValueWei,
578 priority,
581 - );
582 -
583 - _isSwapsXYZCallDataTx = true;
584 - state = ExecutedSuccessfullyState();
585 - return pendingTransaction; // do NOT fall back to regular flow
586 - }
587 - if (walletType == WalletType.base) {
588 - final priority = _settingsStore.priority[WalletType.base]!;
589 - _pendingApprovalTx = await buildApprovalIfNeeded(
590 - spender: routerTo!,
591 - tokenContract: tokenContract,
592 - requiredAmount: requiredAmount,
593 - sourceTokenDecimals: trade.sourceTokenDecimals,
594 - );
595 -
596 - // Build the callData tx
597 - pendingTransaction = await base!.createRawCallDataTransaction(
598 - wallet,
599 - routerTo,
600 - routerData,
601 - routerValueWei,
602 - priority,
603 - );
604 -
605 - _isSwapsXYZCallDataTx = true;
606 - state = ExecutedSuccessfullyState();
607 - return pendingTransaction; // do NOT fall back to regular flow
608 - }
609 - if (walletType == WalletType.arbitrum) {
610 - _pendingApprovalTx = await buildApprovalIfNeeded(
611 - spender: routerTo!,
612 - tokenContract: tokenContract,
613 - requiredAmount: requiredAmount,
614 - sourceTokenDecimals: trade.sourceTokenDecimals,
615 - );
616 -
617 - // Build the callData tx
618 - pendingTransaction = await arbitrum!.createRawCallDataTransaction(
619 - wallet,
620 - routerTo,
621 - routerData,
622 - routerValueWei,
579 + useBlinkProtection: canSupportBlinkProtection(selectedChainId)
580 + ? _settingsStore.useBlinkProtection
581 + : false,
582 );
583
584 _isSwapsXYZCallDataTx = true;
@@ -629,51 +588,17 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
588 }
589
590 // No approval needed (or prepared transfer): send exactly what backend prepared
632 - if (walletType == WalletType.ethereum) {
633 - final priority = _settingsStore.priority[WalletType.ethereum]!;
634 - pendingTransaction = await ethereum!.createRawCallDataTransaction(
635 - wallet,
636 - routerTo!,
637 - routerData,
638 - routerValueWei,
639 - priority,
640 - );
641 - _isSwapsXYZCallDataTx = true;
642 - state = ExecutedSuccessfullyState();
643 - return pendingTransaction;
644 - }
645 - if (walletType == WalletType.polygon) {
646 - final priority = _settingsStore.priority[WalletType.polygon]!;
647 - pendingTransaction = await polygon!.createRawCallDataTransaction(
648 - wallet,
649 - routerTo!,
650 - routerData,
651 - routerValueWei,
652 - priority,
653 - );
654 - _isSwapsXYZCallDataTx = true;
655 - state = ExecutedSuccessfullyState();
656 - return pendingTransaction;
657 - }
658 - if (walletType == WalletType.base) {
659 - final priority = _settingsStore.priority[WalletType.base]!;
660 - pendingTransaction = await base!.createRawCallDataTransaction(
591 + if (isEVMWallet) {
592 + final priority = _settingsStore.getPriority(walletType, chainId: selectedChainId);
593 + pendingTransaction = await evm!.createRawCallDataTransaction(
594 wallet,
595 routerTo!,
596 routerData,
597 routerValueWei,
598 priority,
666 - );
667 - _isSwapsXYZCallDataTx = true;
668 - state = ExecutedSuccessfullyState();
669 - return pendingTransaction;
670 - }
671 - if (walletType == WalletType.arbitrum) {
672 - pendingTransaction = await arbitrum!.createRawCallDataTransaction(
673 - wallet,
674 - routerTo!,
675 - routerData,
676 - routerValueWei,
599 + useBlinkProtection: canSupportBlinkProtection(selectedChainId)
600 + ? _settingsStore.useBlinkProtection
601 + : false,
602 );
603 _isSwapsXYZCallDataTx = true;
604 state = ExecutedSuccessfullyState();
@@ -871,7 +796,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
796 }
797
798 Object _credentials([ExchangeProvider? provider]) {
874 - final priority = _settingsStore.priority[wallet.type];
799 + final priority = _settingsStore.getPriority(wallet.type, chainId: wallet.chainId);
800
801 if (priority == null &&
802 wallet.type != WalletType.nano &&
@@ -879,6 +804,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
804 wallet.type != WalletType.solana &&
805 wallet.type != WalletType.tron &&
806 wallet.type != WalletType.arbitrum) {
807 + // Wallet type is generic for all EVM chains, so we need to check the chainId
808 throw Exception('Priority is null for wallet type: ${wallet.type}');
809 }
810
@@ -911,19 +837,19 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
837 .createWowneroTransactionCreationCredentials(outputs: outputs, priority: priority!);
838
839 case WalletType.ethereum:
914 - return ethereum!.createEthereumTransactionCredentials(outputs,
915 - priority: priority!, currency: selectedCryptoCurrency);
916 - case WalletType.nano:
917 - return nano!.createNanoTransactionCredentials(outputs);
840 case WalletType.polygon:
919 - return polygon!.createPolygonTransactionCredentials(outputs,
920 - priority: priority!, currency: selectedCryptoCurrency);
841 case WalletType.base:
922 - return base!.createBaseTransactionCredentials(outputs,
923 - priority: priority!, currency: selectedCryptoCurrency);
842 case WalletType.arbitrum:
925 - return arbitrum!
926 - .createArbitrumTransactionCredentials(outputs, currency: selectedCryptoCurrency);
843 + return evm!.createEVMTransactionCredentials(
844 + outputs,
845 + priority: priority,
846 + currency: selectedCryptoCurrency,
847 + useBlinkProtection: canSupportBlinkProtection(selectedChainId)
848 + ? _settingsStore.useBlinkProtection
849 + : false,
850 + );
851 + case WalletType.nano:
852 + return nano!.createNanoTransactionCredentials(outputs);
853 case WalletType.solana:
854 return solana!
855 .createSolanaTransactionCredentials(outputs, currency: selectedCryptoCurrency);
@@ -1063,11 +989,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
989
990 return errorMessage;
991 }
1066 - if (walletType == WalletType.ethereum ||
1067 - walletType == WalletType.polygon ||
1068 - walletType == WalletType.base ||
1069 - walletType == WalletType.arbitrum ||
1070 - walletType == WalletType.haven) {
992 + if (isEVMWallet || walletType == WalletType.haven) {
993 if (errorMessage.contains('gas required exceeds allowance')) {
994 return S.current.gas_exceeds_allowance;
995 }
@@ -1096,6 +1018,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
1018 '''${S.current.overshot}: ${parsedErrorMessageResult.overshotEth} ${walletType == WalletType.polygon ? "POL" : "ETH"} (${parsedErrorMessageResult.overshotUsd} ${fiatFromSettings.name})''';
1019 }
1020
1021 + if (errorMessage.contains('max fee per gas less than block base fee')) {
1022 + return S.current.tx_retry_message;
1023 + }
1024 +
1025 return errorMessage;
1026 }
1027
@@ -1105,7 +1031,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
1031 }
1032
1033 if (errorMessage.contains('Transaction expired')) {
1108 - return 'An error occurred while processing the transaction. Please retry the transaction';
1034 + return S.current.tx_retry_message;
1035 }
1036 }
1037
@@ -1184,29 +1110,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
1110 if (requiredAmount <= BigInt.zero) return null;
1111
1112 bool needsApproval = false;
1187 - if (walletType == WalletType.ethereum) {
1188 - needsApproval = await ethereum!.isApprovalRequired(
1189 - wallet,
1190 - tokenContract,
1191 - spender,
1192 - requiredAmount,
1193 - );
1194 - } else if (walletType == WalletType.polygon) {
1195 - needsApproval = await polygon!.isApprovalRequired(
1196 - wallet,
1197 - tokenContract,
1198 - spender,
1199 - requiredAmount,
1200 - );
1201 - } else if (walletType == WalletType.base) {
1202 - needsApproval = await base!.isApprovalRequired(
1203 - wallet,
1204 - tokenContract,
1205 - spender,
1206 - requiredAmount,
1207 - );
1208 - } else if (walletType == WalletType.arbitrum) {
1209 - needsApproval = await arbitrum!.isApprovalRequired(
1113 + if (isEVMWallet) {
1114 + needsApproval = await evm!.isApprovalRequired(
1115 wallet,
1116 tokenContract,
1117 spender,
@@ -1227,39 +1132,16 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
1132 ),
1133 );
1134
1230 - if (walletType == WalletType.ethereum) {
1231 - final priority = _settingsStore.priority[WalletType.ethereum]!;
1232 - return await ethereum!.createTokenApproval(
1135 + if (isEVMWallet) {
1136 + final priority = _settingsStore.getPriority(walletType, chainId: selectedChainId);
1137 + return await evm!.createTokenApproval(
1138 wallet,
1139 requiredAmount,
1140 spender,
1141 erc20Token,
1142 priority,
1238 - );
1239 - } else if (walletType == WalletType.polygon) {
1240 - final priority = _settingsStore.priority[WalletType.polygon]!;
1241 - return await polygon!.createTokenApproval(
1242 - wallet,
1243 - requiredAmount,
1244 - spender,
1245 - erc20Token,
1246 - priority,
1247 - );
1248 - } else if (walletType == WalletType.base) {
1249 - final priority = _settingsStore.priority[WalletType.base]!;
1250 - return await base!.createTokenApproval(
1251 - wallet,
1252 - requiredAmount,
1253 - spender,
1254 - erc20Token,
1255 - priority,
1256 - );
1257 - } else if (walletType == WalletType.arbitrum) {
1258 - return await arbitrum!.createTokenApproval(
1259 - wallet,
1260 - requiredAmount,
1261 - spender,
1262 - erc20Token,
1143 + useBlinkProtection:
1144 + canSupportBlinkProtection(selectedChainId) ? _settingsStore.useBlinkProtection : false,
1145 );
1146 }
1147
lib/view_model/settings/other_settings_view_model.dart
+22 -21
@@ -15,27 +15,27 @@ import 'package:mobx/mobx.dart';
15
16 part 'other_settings_view_model.g.dart';
17
18 -class OtherSettingsViewModel = OtherSettingsViewModelBase
19 - with _$OtherSettingsViewModel;
18 +class OtherSettingsViewModel = OtherSettingsViewModelBase with _$OtherSettingsViewModel;
19
20 abstract class OtherSettingsViewModelBase with Store {
21 OtherSettingsViewModelBase(this._settingsStore, this._wallet, this.sendViewModel)
22 : walletType = _wallet.type,
23 + chainId = _wallet.chainId,
24 currentVersion = '' {
25 - PackageInfo.fromPlatform().then(
26 - (PackageInfo packageInfo) => currentVersion = packageInfo.version);
25 + PackageInfo.fromPlatform()
26 + .then((PackageInfo packageInfo) => currentVersion = packageInfo.version);
27
28 - final priority = _settingsStore.priority[_wallet.type];
28 + final priority = _settingsStore.getPriority(_wallet.type, chainId: _wallet.chainId);
29 final priorities = priorityForWalletType(_wallet.type);
30
31 if (!priorities.contains(priority) && priorities.isNotEmpty) {
32 - _settingsStore.priority[_wallet.type] = priorities.first;
32 + _settingsStore.setPriority(_wallet.type, priorities.first, chainId: _wallet.chainId);
33 }
34 }
35
36 final WalletType walletType;
37 - final WalletBase<Balance, TransactionHistoryBase<TransactionInfo>,
38 - TransactionInfo> _wallet;
37 + final int? chainId;
38 + final WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> _wallet;
39
40 @observable
41 String currentVersion;
@@ -45,7 +45,7 @@ abstract class OtherSettingsViewModelBase with Store {
45
46 @computed
47 TransactionPriority get transactionPriority {
48 - final priority = _settingsStore.priority[walletType];
48 + final priority = _settingsStore.getPriority(walletType, chainId: chainId);
49
50 if (priority == null) {
51 throw Exception('Unexpected type ${walletType.toString()}');
@@ -66,8 +66,12 @@ abstract class OtherSettingsViewModelBase with Store {
66 ].contains(_wallet.hardwareWalletType);
67
68 @computed
69 - bool get displayTransactionPriority =>
70 - !(changeRepresentativeEnabled || [WalletType.solana, WalletType.tron].contains(_wallet.type));
69 + bool get displayTransactionPriority => !(changeRepresentativeEnabled ||
70 + [
71 + WalletType.solana,
72 + WalletType.tron,
73 + WalletType.arbitrum,
74 + ].contains(_wallet.type));
75
76 String getDisplayPriority(dynamic priority) {
77 final _priority = priority as TransactionPriority;
@@ -95,22 +99,20 @@ abstract class OtherSettingsViewModelBase with Store {
99 WalletType.dogecoin,
100 ].contains(_wallet.type)) {
101 final rate = bitcoin!.getFeeRate(_wallet, _priority);
98 - return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate,
99 - customRate: customValue);
102 + return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate, customRate: customValue);
103 }
104
105 return priority.toString();
106 }
107
108 void onDisplayPrioritySelected(TransactionPriority priority) =>
106 - _settingsStore.priority[walletType] = priority;
109 + _settingsStore.setPriority(walletType, priority, chainId: chainId);
110
108 - void onDisplayBitcoinPrioritySelected(
109 - TransactionPriority priority, double customValue) {
111 + void onDisplayBitcoinPrioritySelected(TransactionPriority priority, double customValue) {
112 if (_wallet.type == WalletType.bitcoin) {
113 _settingsStore.customBitcoinFeeRate = customValue.round();
114 }
113 - _settingsStore.priority[_wallet.type] = priority;
115 + _settingsStore.setPriority(_wallet.type, priority, chainId: _wallet.chainId);
116 }
117
118 @action
@@ -120,13 +122,12 @@ abstract class OtherSettingsViewModelBase with Store {
122 }
123
124 @computed
123 - double get customBitcoinFeeRate =>
124 - _settingsStore.customBitcoinFeeRate.toDouble();
125 + double get customBitcoinFeeRate => _settingsStore.customBitcoinFeeRate.toDouble();
126
127 int? get customPriorityItemIndex {
128 final priorities = priorityForWalletType(walletType);
128 - final customItem = priorities.firstWhereOrNull(
129 - (element) => element == bitcoin!.getBitcoinTransactionPriorityCustom());
129 + final customItem = priorities
130 + .firstWhereOrNull((element) => element == bitcoin!.getBitcoinTransactionPriorityCustom());
131 return customItem != null ? priorities.indexOf(customItem) : null;
132 }
133
lib/view_model/settings/privacy_settings_view_model.dart
+18 -12
@@ -1,12 +1,10 @@
1 -import 'package:cake_wallet/arbitrum/arbitrum.dart';
2 -import 'package:cake_wallet/base/base.dart';
1 import 'package:cake_wallet/bitcoin/bitcoin.dart';
2 import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
3 import 'package:cake_wallet/entities/exchange_api_mode.dart';
6 -import 'package:cake_wallet/ethereum/ethereum.dart';
7 -import 'package:cake_wallet/polygon/polygon.dart';
4 +import 'package:cake_wallet/evm/evm.dart';
5 import 'package:cake_wallet/store/settings_store.dart';
6 import 'package:cake_wallet/tron/tron.dart';
7 +import 'package:cake_wallet/reactions/wallet_connect.dart';
8 import 'package:cake_wallet/utils/device_info.dart';
9 import 'package:cw_core/balance.dart';
10 import 'package:cw_core/transaction_history.dart';
@@ -89,6 +87,11 @@ abstract class PrivacySettingsViewModelBase with Store {
87 @computed
88 bool get useMempoolFeeAPI => _settingsStore.useMempoolFeeAPI;
89
90 + @computed
91 + bool get useBlinkProtection => _settingsStore.useBlinkProtection;
92 +
93 + bool get canUseBlinkProtection => canSupportBlinkProtection(_wallet.chainId);
94 +
95 @computed
96 bool get lookupTwitter => _settingsStore.lookupsTwitter;
97
@@ -116,13 +119,13 @@ abstract class PrivacySettingsViewModelBase with Store {
119 @computed
120 bool get usePayjoin => _settingsStore.usePayjoin;
121
119 - bool get canUseEtherscan => _wallet.type == WalletType.ethereum;
122 + bool get canUseEtherscan => _wallet.chainId == 1;
123
121 - bool get canUsePolygonScan => _wallet.type == WalletType.polygon;
124 + bool get canUsePolygonScan => _wallet.chainId == 137;
125
123 - bool get canUseBaseScan => _wallet.type == WalletType.base;
126 + bool get canUseBaseScan => _wallet.chainId == 8453;
127
125 - bool get canUseArbiScan => _wallet.type == WalletType.arbitrum;
128 + bool get canUseArbiScan => _wallet.chainId == 42161;
129
130 bool get canUseTronGrid => _wallet.type == WalletType.tron;
131
@@ -180,19 +183,19 @@ abstract class PrivacySettingsViewModelBase with Store {
183 @action
184 void setUseEtherscan(bool value) {
185 _settingsStore.useEtherscan = value;
183 - ethereum!.updateEtherscanUsageState(_wallet, value);
186 + evm!.updateScanProviderUsageState(_wallet, value);
187 }
188
189 @action
190 void setUsePolygonScan(bool value) {
191 _settingsStore.usePolygonScan = value;
189 - polygon!.updatePolygonScanUsageState(_wallet, value);
192 + evm!.updateScanProviderUsageState(_wallet, value);
193 }
194
195 @action
196 void setUseBaseScan(bool value) {
197 _settingsStore.useBaseScan = value;
195 - base!.updateBaseScanUsageState(_wallet, value);
198 + evm!.updateScanProviderUsageState(_wallet, value);
199 }
200
201 @action
@@ -204,12 +207,15 @@ abstract class PrivacySettingsViewModelBase with Store {
207 @action
208 void setUseArbiScan(bool value) {
209 _settingsStore.useArbiScan = value;
207 - arbitrum!.updateArbitrumScanUsageState(_wallet, value);
210 + evm!.updateScanProviderUsageState(_wallet, value);
211 }
212
213 @action
214 void setUseMempoolFeeAPI(bool value) => _settingsStore.useMempoolFeeAPI = value;
215
216 + @action
217 + void setUseBlinkProtection(bool value) => _settingsStore.useBlinkProtection = value;
218 +
219 @action
220 void setUsePayjoin(bool value) {
221 _settingsStore.usePayjoin = value;
lib/view_model/transaction_details_view_model.dart
+30 -174
@@ -1,11 +1,12 @@
1 import 'package:cake_wallet/core/address_validator.dart';
2 import 'package:cake_wallet/tron/tron.dart';
3 import 'package:cake_wallet/wownero/wownero.dart';
4 -import 'package:cw_core/currency_for_wallet_type.dart';
4 import 'package:cw_core/utils/print_verbose.dart';
5 import 'package:cw_core/wallet_base.dart';
6 import 'package:cw_core/transaction_info.dart';
7 import 'package:cw_core/wallet_type.dart';
8 +import 'package:cake_wallet/reactions/wallet_connect.dart';
9 +import 'package:cake_wallet/evm/evm.dart';
10 import 'package:cake_wallet/bitcoin/bitcoin.dart';
11 import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
12 import 'package:cake_wallet/entities/transaction_description.dart';
@@ -68,19 +69,13 @@ abstract class TransactionDetailsViewModelBase with Store {
69 _addHavenListItems(tx, dateFormat);
70 break;
71 case WalletType.ethereum:
71 - _addEthereumListItems(tx, dateFormat);
72 - break;
73 - case WalletType.nano:
74 - _addNanoListItems(tx, dateFormat);
75 - break;
72 case WalletType.polygon:
77 - _addPolygonListItems(tx, dateFormat);
78 - break;
73 case WalletType.base:
80 - _addBaseListItems(tx, dateFormat);
81 - break;
74 case WalletType.arbitrum:
83 - _addArbitrumListItems(tx, dateFormat);
75 + _addEVMListItems(tx, dateFormat);
76 + break;
77 + case WalletType.nano:
78 + _addNanoListItems(tx, dateFormat);
79 break;
80 case WalletType.solana:
81 _addSolanaListItems(tx, dateFormat);
@@ -129,10 +124,10 @@ abstract class TransactionDetailsViewModelBase with Store {
124 items.add(
125 BlockExplorerListItem(
126 title: S.current.view_in_block_explorer,
132 - value: _explorerDescription(type),
127 + value: _explorerDescription(type, wallet.chainId),
128 onTap: () async {
129 try {
135 - final uri = Uri.parse(_explorerUrl(type, tx.txHash));
130 + final uri = Uri.parse(_explorerUrl(type, tx.txHash, wallet.chainId));
131 if (await canLaunchUrl(uri)) await launchUrl(uri, mode: LaunchMode.externalApplication);
132 } catch (e) {}
133 },
@@ -175,7 +170,12 @@ abstract class TransactionDetailsViewModelBase with Store {
170 @observable
171 bool canReplaceByFee;
172
178 - String _explorerUrl(WalletType type, String txId) {
173 + String _explorerUrl(WalletType type, String txId, int? chainId) {
174 + if (chainId != null) {
175 + final explorerUrl = evm!.getExplorerUrlForChainId(chainId);
176 + if (explorerUrl != null) return '$explorerUrl/tx/${txId}';
177 + }
178 +
179 switch (type) {
180 case WalletType.monero:
181 return 'https://monero.com/tx/${txId}';
@@ -189,12 +189,16 @@ abstract class TransactionDetailsViewModelBase with Store {
189 return 'https://explorer.havenprotocol.org/search?value=${txId}';
190 case WalletType.ethereum:
191 return 'https://etherscan.io/tx/${txId}';
192 + case WalletType.base:
193 + return 'https://basescan.org/tx/${txId}';
194 + case WalletType.arbitrum:
195 + return 'https://arbiscan.io/tx/${txId}';
196 + case WalletType.polygon:
197 + return 'https://polygonscan.com/tx/${txId}';
198 case WalletType.nano:
199 return 'https://nanexplorer.com/nano/block/${txId}';
200 case WalletType.banano:
201 return 'https://nanexplorer.com/banano/block/${txId}';
196 - case WalletType.polygon:
197 - return 'https://polygonscan.com/tx/${txId}';
202 case WalletType.solana:
203 return 'https://solscan.io/tx/${txId}';
204 case WalletType.tron:
@@ -207,16 +211,19 @@ abstract class TransactionDetailsViewModelBase with Store {
211 return 'https://${wallet.isTestnet ? "testnet" : "dcrdata"}.decred.org/tx/${txId.split(':')[0]}';
212 case WalletType.dogecoin:
213 return 'https://blockchair.com/dogecoin/transaction/${txId}';
210 - case WalletType.base:
211 - return 'https://basescan.org/tx/${txId}';
212 - case WalletType.arbitrum:
213 - return 'https://arbiscan.io/tx/${txId}';
214 +
215 case WalletType.none:
216 return '';
217 }
218 }
219
219 - String _explorerDescription(WalletType type) {
220 + String _explorerDescription(WalletType type, int? chainId) {
221 + if (chainId != null) {
222 + final explorerUrl = evm!.getExplorerUrlForChainId(chainId, showProtocol: false);
223 + if (explorerUrl != null) {
224 + return S.current.view_transaction_on + explorerUrl;
225 + }
226 + }
227 switch (type) {
228 case WalletType.monero:
229 return S.current.view_transaction_on + 'Monero.com';
@@ -445,7 +452,7 @@ abstract class TransactionDetailsViewModelBase with Store {
452 ]);
453 }
454
448 - void _addEthereumListItems(TransactionInfo tx, DateFormat dateFormat) {
455 + void _addEVMListItems(TransactionInfo tx, DateFormat dateFormat) {
456 final _items = [
457 StandartListItem(
458 title: S.current.transaction_details_transaction_id,
@@ -539,156 +546,6 @@ abstract class TransactionDetailsViewModelBase with Store {
546 items.addAll(_items);
547 }
548
542 - void _addPolygonListItems(TransactionInfo tx, DateFormat dateFormat) {
543 - final _items = [
544 - StandartListItem(
545 - title: S.current.transaction_details_transaction_id,
546 - value: tx.txHash,
547 - key: ValueKey('standard_list_item_transaction_details_id_key'),
548 - ),
549 - StandartListItem(
550 - title: S.current.transaction_details_date,
551 - value: dateFormat.format(tx.date),
552 - key: ValueKey('standard_list_item_transaction_details_date_key'),
553 - ),
554 - StandartListItem(
555 - title: S.current.confirmations,
556 - value: tx.confirmations.toString(),
557 - key: ValueKey('standard_list_item_transaction_confirmations_key'),
558 - ),
559 - StandartListItem(
560 - title: S.current.transaction_details_height,
561 - value: '${tx.height}',
562 - key: ValueKey('standard_list_item_transaction_details_height_key'),
563 - ),
564 - StandartListItem(
565 - title: S.current.transaction_details_amount,
566 - value: tx.amountFormatted(),
567 - key: ValueKey('standard_list_item_transaction_details_amount_key'),
568 - ),
569 - if (tx.feeFormatted()?.isNotEmpty ?? false)
570 - StandartListItem(
571 - title: S.current.transaction_details_fee,
572 - value: tx.feeFormatted()!,
573 - key: ValueKey('standard_list_item_transaction_details_fee_key'),
574 - ),
575 - if (showRecipientAddress && tx.to != null && tx.direction == TransactionDirection.outgoing)
576 - StandartListItem(
577 - title: S.current.transaction_details_recipient_address,
578 - value: tx.to!,
579 - key: ValueKey('standard_list_item_transaction_details_recipient_address_key'),
580 - ),
581 - if (tx.direction == TransactionDirection.incoming && tx.from != null)
582 - StandartListItem(
583 - title: S.current.transaction_details_source_address,
584 - value: tx.from!,
585 - key: ValueKey('standard_list_item_transaction_details_source_address_key'),
586 - ),
587 - ];
588 -
589 - items.addAll(_items);
590 - }
591 -
592 - void _addBaseListItems(TransactionInfo tx, DateFormat dateFormat) {
593 - final _items = [
594 - StandartListItem(
595 - title: S.current.transaction_details_transaction_id,
596 - value: tx.txHash,
597 - key: ValueKey('standard_list_item_transaction_details_id_key'),
598 - ),
599 - StandartListItem(
600 - title: S.current.transaction_details_date,
601 - value: dateFormat.format(tx.date),
602 - key: ValueKey('standard_list_item_transaction_details_date_key'),
603 - ),
604 - StandartListItem(
605 - title: S.current.confirmations,
606 - value: tx.confirmations.toString(),
607 - key: ValueKey('standard_list_item_transaction_confirmations_key'),
608 - ),
609 - StandartListItem(
610 - title: S.current.transaction_details_height,
611 - value: '${tx.height}',
612 - key: ValueKey('standard_list_item_transaction_details_height_key'),
613 - ),
614 - StandartListItem(
615 - title: S.current.transaction_details_amount,
616 - value: tx.amountFormatted(),
617 - key: ValueKey('standard_list_item_transaction_details_amount_key'),
618 - ),
619 - if (tx.feeFormatted()?.isNotEmpty ?? false)
620 - StandartListItem(
621 - title: S.current.transaction_details_fee,
622 - value: tx.feeFormatted()!,
623 - key: ValueKey('standard_list_item_transaction_details_fee_key'),
624 - ),
625 - if (showRecipientAddress && tx.to != null && tx.direction == TransactionDirection.outgoing)
626 - StandartListItem(
627 - title: S.current.transaction_details_recipient_address,
628 - value: tx.to!,
629 - key: ValueKey('standard_list_item_transaction_details_recipient_address_key'),
630 - ),
631 - if (tx.direction == TransactionDirection.incoming && tx.from != null)
632 - StandartListItem(
633 - title: S.current.transaction_details_source_address,
634 - value: tx.from!,
635 - key: ValueKey('standard_list_item_transaction_details_source_address_key'),
636 - ),
637 - ];
638 -
639 - items.addAll(_items);
640 - }
641 -
642 - void _addArbitrumListItems(TransactionInfo tx, DateFormat dateFormat) {
643 - final _items = [
644 - StandartListItem(
645 - title: S.current.transaction_details_transaction_id,
646 - value: tx.txHash,
647 - key: ValueKey('standard_list_item_transaction_details_id_key'),
648 - ),
649 - StandartListItem(
650 - title: S.current.transaction_details_date,
651 - value: dateFormat.format(tx.date),
652 - key: ValueKey('standard_list_item_transaction_details_date_key'),
653 - ),
654 - StandartListItem(
655 - title: S.current.confirmations,
656 - value: tx.confirmations.toString(),
657 - key: ValueKey('standard_list_item_transaction_confirmations_key'),
658 - ),
659 - StandartListItem(
660 - title: S.current.transaction_details_height,
661 - value: '${tx.height}',
662 - key: ValueKey('standard_list_item_transaction_details_height_key'),
663 - ),
664 - StandartListItem(
665 - title: S.current.transaction_details_amount,
666 - value: tx.amountFormatted(),
667 - key: ValueKey('standard_list_item_transaction_details_amount_key'),
668 - ),
669 - if (tx.feeFormatted()?.isNotEmpty ?? false)
670 - StandartListItem(
671 - title: S.current.transaction_details_fee,
672 - value: tx.feeFormatted()!,
673 - key: ValueKey('standard_list_item_transaction_details_fee_key'),
674 - ),
675 - if (showRecipientAddress && tx.to != null && tx.direction == TransactionDirection.outgoing)
676 - StandartListItem(
677 - title: S.current.transaction_details_recipient_address,
678 - value: tx.to!,
679 - key: ValueKey('standard_list_item_transaction_details_recipient_address_key'),
680 - ),
681 - if (tx.direction == TransactionDirection.incoming && tx.from != null)
682 - StandartListItem(
683 - title: S.current.transaction_details_source_address,
684 - value: tx.from!,
685 - key: ValueKey('standard_list_item_transaction_details_source_address_key'),
686 - ),
687 - ];
688 -
689 - items.addAll(_items);
690 - }
691 -
549 void _addSolanaListItems(TransactionInfo tx, DateFormat dateFormat) {
550 final _items = [
551 StandartListItem(
@@ -768,8 +625,7 @@ abstract class TransactionDetailsViewModelBase with Store {
625 StandardPickerListItem(
626 key: ValueKey('standard_picker_list_item_transaction_priorities_key'),
627 title: S.current.estimated_new_fee,
771 - value: bitcoin!.formatterBitcoinAmountToString(amount: newFee) +
772 - ' ${walletTypeToCryptoCurrency(wallet.type)}',
628 + value: bitcoin!.formatterBitcoinAmountToString(amount: newFee) + ' ${wallet.currency}',
629 items: priorityForWalletType(wallet.type),
630 customValue: settingsStore.customBitcoinFeeRate.toDouble(),
631 maxValue: maxCustomFeeRate,
lib/view_model/wallet_address_list/wallet_address_list_view_model.dart
+78 -64
@@ -1,7 +1,6 @@
1 import 'dart:developer' as dev;
2 import 'dart:core';
3
4 -import 'package:cake_wallet/base/base.dart';
4 import 'package:cake_wallet/bitcoin/bitcoin.dart';
5 import 'package:cake_wallet/core/fiat_conversion_service.dart';
6 import 'package:cake_wallet/core/payment_uris.dart';
@@ -9,11 +8,10 @@ import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
8 import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
9 import 'package:cake_wallet/entities/fiat_api_mode.dart';
10 import 'package:cake_wallet/entities/fiat_currency.dart';
12 -import 'package:cake_wallet/ethereum/ethereum.dart';
11 +import 'package:cake_wallet/evm/evm.dart';
12 import 'package:cake_wallet/generated/i18n.dart';
13 import 'package:cake_wallet/monero/monero.dart';
15 -import 'package:cake_wallet/polygon/polygon.dart';
16 -import 'package:cake_wallet/arbitrum/arbitrum.dart';
14 +import 'package:cake_wallet/reactions/wallet_connect.dart';
15 import 'package:cake_wallet/reactions/wallet_utils.dart';
16 import 'package:cake_wallet/solana/solana.dart';
17 import 'package:cake_wallet/decred/decred.dart';
@@ -47,7 +45,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
45 required this.yatStore,
46 required this.fiatConversionStore,
47 }) : _baseItems = <ListItem>[],
50 - selectedCurrency = walletTypeToCryptoCurrency(appStore.wallet!.type),
48 + selectedCurrency = appStore.wallet!.currency,
49 _cryptoNumberFormat = NumberFormat(_cryptoNumberPattern),
50 hasAccounts = [WalletType.monero, WalletType.wownero, WalletType.haven]
51 .contains(appStore.wallet!.type),
@@ -57,11 +55,14 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
55 _init();
56 }
57
58 + @computed
59 + int? get selectedChainId => wallet.chainId;
60 +
61 @override
62 void onWalletChange(wallet) {
63 _init();
64
64 - selectedCurrency = walletTypeToCryptoCurrency(wallet.type);
65 + selectedCurrency = wallet.currency;
66 hasAccounts = [WalletType.monero, WalletType.wownero, WalletType.haven].contains(wallet.type);
67 }
68
@@ -75,7 +76,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
76 double? _fiatRate;
77 String _rawAmount = '';
78
78 - List<Currency> get currencies => [walletTypeToCryptoCurrency(wallet.type), ...FiatCurrency.all];
79 + List<Currency> get currencies => [wallet.currency, ...FiatCurrency.all];
80
81 String get buttonTitle {
82 if (isElectrumWallet) {
@@ -114,6 +115,21 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
115
116 @computed
117 PaymentURI get uri {
118 + if (isEVMCompatibleChain(wallet.type) && selectedChainId != null) {
119 + switch (selectedChainId) {
120 + case 1:
121 + return EthereumURI(amount: amount, address: address.address);
122 + case 137:
123 + return PolygonURI(amount: amount, address: address.address);
124 + case 8453:
125 + return BaseURI(amount: amount, address: address.address);
126 + case 42161:
127 + return ArbitrumURI(amount: amount, address: address.address);
128 + default:
129 + return EthereumURI(amount: amount, address: address.address);
130 + }
131 + }
132 +
133 switch (wallet.type) {
134 case WalletType.monero:
135 return MoneroURI(amount: amount, address: address.address);
@@ -205,7 +221,9 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
221 address: address.address,
222 txCount: address.txCount,
223 balance: AmountConverter.amountIntToString(
208 - walletTypeToCryptoCurrency(type), address.balance),
224 + walletTypeToCryptoCurrency(type),
225 + address.balance,
226 + ),
227 isChange: address.isChange,
228 );
229 });
@@ -221,7 +239,9 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
239 address: address.address,
240 txCount: address.txCount,
241 balance: AmountConverter.amountIntToString(
224 - walletTypeToCryptoCurrency(type), address.balance),
242 + walletTypeToCryptoCurrency(type),
243 + address.balance,
244 + ),
245 isChange: address.isChange,
246 isOneTimeReceiveAddress: true,
247 );
@@ -238,7 +258,9 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
258 address: subaddress.address,
259 txCount: subaddress.txCount,
260 balance: AmountConverter.amountIntToString(
241 - walletTypeToCryptoCurrency(type), subaddress.balance),
261 + walletTypeToCryptoCurrency(type),
262 + subaddress.balance,
263 + ),
264 isChange: subaddress.isChange);
265 });
266
@@ -257,26 +279,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
279 }
280 }
281
260 - if (wallet.type == WalletType.ethereum) {
261 - final primaryAddress = ethereum!.getAddress(wallet);
262 -
263 - addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
264 - }
265 -
266 - if (wallet.type == WalletType.polygon) {
267 - final primaryAddress = polygon!.getAddress(wallet);
268 -
269 - addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
270 - }
271 -
272 - if (wallet.type == WalletType.base) {
273 - final primaryAddress = base!.getAddress(wallet);
274 -
275 - addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
276 - }
277 -
278 - if (wallet.type == WalletType.arbitrum) {
279 - final primaryAddress = arbitrum!.getAddress(wallet);
282 + if (isEVMCompatibleChain(wallet.type)) {
283 + final primaryAddress = evm!.getAddress(wallet);
284
285 addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
286 }
@@ -415,17 +419,47 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
419 WalletType.dogecoin
420 ].contains(wallet.type);
421
418 - @computed
419 - List<String> get walletImages {
422 + List<String> getWalletImages(int? chainId) {
423 + if (chainId != null) {
424 + switch (chainId) {
425 + case 1:
426 + return [
427 + 'assets/images/eth_icon.svg',
428 + 'assets/images/usdc_icon.svg',
429 + 'assets/images/usdt_wallet_icon.svg',
430 + 'assets/images/deuro_icon.svg',
431 + 'assets/images/more_tokens.svg',
432 + ];
433 + case 137:
434 + return [
435 + 'assets/images/pol_icon.svg',
436 + 'assets/images/eth_pol_icon.svg',
437 + 'assets/images/usdc_icon.svg',
438 + 'assets/images/usdt_wallet_icon.svg',
439 + 'assets/images/more_tokens.svg',
440 + ];
441 + case 8453:
442 + return [
443 + 'assets/images/eth_icon.svg',
444 + 'assets/images/usdc_icon.svg',
445 + 'assets/images/more_tokens.svg',
446 + ];
447 + case 42161:
448 + return [
449 + 'assets/images/crypto/arbitrum.webp',
450 + 'assets/images/usdc_icon.svg',
451 + 'assets/images/more_tokens.svg',
452 + ];
453 + default:
454 + return [
455 + 'assets/images/eth_icon.svg',
456 + 'assets/images/usdc_icon.svg',
457 + 'assets/images/usdt_wallet_icon.svg',
458 + ];
459 + }
460 + }
461 +
462 switch (wallet.type) {
421 - case WalletType.ethereum:
422 - return [
423 - 'assets/images/eth_icon.svg',
424 - 'assets/images/usdc_icon.svg',
425 - 'assets/images/usdt_wallet_icon.svg',
426 - 'assets/images/deuro_icon.svg',
427 - 'assets/images/more_tokens.svg',
428 - ];
463 case WalletType.solana:
464 return [
465 'assets/images/sol_icon.svg',
@@ -433,14 +467,6 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
467 'assets/images/usdt_wallet_icon.svg',
468 'assets/images/more_tokens.svg',
469 ];
436 - case WalletType.polygon:
437 - return [
438 - 'assets/images/pol_icon.svg',
439 - 'assets/images/eth_pol_icon.svg',
440 - 'assets/images/usdc_icon.svg',
441 - 'assets/images/usdt_wallet_icon.svg',
442 - 'assets/images/more_tokens.svg',
443 - ];
470 case WalletType.tron:
471 return [
472 'assets/images/trx_icon.svg',
@@ -453,28 +479,16 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
479 'assets/images/zano_icon.svg',
480 'assets/images/more_tokens.svg',
481 ];
456 - case WalletType.base:
457 - return [
458 - 'assets/images/eth_icon.svg',
459 - 'assets/images/usdc_icon.svg',
460 - 'assets/images/more_tokens.svg',
461 - ];
462 - case WalletType.arbitrum:
463 - return [
464 - 'assets/images/crypto/arbitrum.webp',
465 - 'assets/images/usdc_icon.svg',
466 - 'assets/images/more_tokens.svg',
467 - ];
482 default:
483 return [];
484 }
485 }
486
487 @computed
474 - String get qrImage => getQrImage(type);
488 + String get qrImage => getQrImage(type, selectedChainId: selectedChainId);
489
490 @computed
477 - String get monoImage => getChainMonoImage(type);
491 + String get monoImage => getChainMonoImage(type, selectedChainId: selectedChainId);
492
493 @computed
494 bool get isBalanceAvailable => isElectrumWallet;
@@ -544,7 +558,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
558 selectedCurrency = currency;
559
560 if (currency is FiatCurrency && _settingsStore.fiatCurrency != currency) {
547 - final cryptoCurrency = walletTypeToCryptoCurrency(wallet.type);
561 + final cryptoCurrency = wallet.currency;
562
563 dev.log("Requesting Fiat rate for $cryptoCurrency-$currency");
564 FiatConversionService.fetchPrice(
@@ -575,7 +589,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
589
590 @action
591 void _convertAmountToCrypto() {
578 - final cryptoCurrency = walletTypeToCryptoCurrency(wallet.type);
592 + final cryptoCurrency = wallet.currency;
593 final fiatRate = _fiatRate ?? (fiatConversionStore.prices[cryptoCurrency] ?? 0.0);
594
595 if (fiatRate <= 0.0) {
lib/view_model/wallet_hardware_restore_view_model.dart
+5 -6
@@ -1,10 +1,9 @@
1 import 'package:cake_wallet/bitcoin/bitcoin.dart';
2 import 'package:cake_wallet/core/generate_wallet_password.dart';
3 import 'package:cake_wallet/core/wallet_creation_service.dart';
4 -import 'package:cake_wallet/ethereum/ethereum.dart';
4 +import 'package:cake_wallet/evm/evm.dart';
5 import 'package:cake_wallet/generated/i18n.dart';
6 import 'package:cake_wallet/monero/monero.dart';
7 -import 'package:cake_wallet/polygon/polygon.dart';
7 import 'package:cake_wallet/store/app_store.dart';
8 import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
9 import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
@@ -79,11 +78,11 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with
78 bitcoin!.createBitcoinHardwareWalletCredentials(name: name, accountData: selectedAccount!);
79 break;
80 case WalletType.ethereum:
82 - credentials =
83 - ethereum!.createEthereumHardwareWalletCredentials(name: name, hwAccountData: selectedAccount!);
84 - break;
81 case WalletType.polygon:
86 - credentials = polygon!.createPolygonHardwareWalletCredentials(name: name, hwAccountData: selectedAccount!);
82 + credentials = evm!.createEVMHardwareWalletCredentials(
83 + name: name,
84 + hwAccountData: selectedAccount!,
85 + );
86 break;
87 case WalletType.monero:
88 final password = walletPassword ?? generateWalletPassword();
lib/view_model/wallet_keys_view_model.dart
+19 -4
@@ -14,6 +14,8 @@ import 'package:flutter/foundation.dart';
14 import 'package:mobx/mobx.dart';
15 import 'package:cake_wallet/decred/decred.dart';
16 import 'package:polyseed/polyseed.dart';
17 +import 'package:cake_wallet/evm/evm.dart';
18 +import 'package:cake_wallet/reactions/wallet_connect.dart';
19
20 part 'wallet_keys_view_model.g.dart';
21
@@ -21,12 +23,12 @@ class WalletKeysViewModel = WalletKeysViewModelBase with _$WalletKeysViewModel;
23
24 abstract class WalletKeysViewModelBase with Store {
25 WalletKeysViewModelBase(this._appStore)
24 - : title = '${walletTypeToString(_appStore.wallet!.type)} ${S.current.wallet_keys}',
25 - _wallet = _appStore.wallet!,
26 + : _wallet = _appStore.wallet!,
27 _walletName = _appStore.wallet!.type.name,
28 _restoreHeight = _appStore.wallet!.walletInfo.restoreHeight,
29 _restoreHeightByTransactions = 0,
29 - items = ObservableList<StandartListItem>() {
30 + items = ObservableList<StandartListItem>(),
31 + _title = _getInitialTitle(_appStore.wallet!) {
32 _populateKeysItems();
33
34 reaction((_) => _appStore.wallet, (WalletBase? _wallet) {
@@ -49,11 +51,24 @@ abstract class WalletKeysViewModelBase with Store {
51 }
52 }
53
54 + static String _getInitialTitle(WalletBase wallet) {
55 + if (isEVMCompatibleChain(wallet.type)) {
56 + final currentChain = evm!.getCurrentChain(wallet);
57 + return '${currentChain?.name ?? walletTypeToString(wallet.type)} ${S.current.wallet_keys}';
58 + }
59 +
60 + return '${walletTypeToString(wallet.type)} ${S.current.wallet_keys}';
61 + }
62 +
63 bool get isBitcoin => _wallet.type == WalletType.bitcoin;
64
65 final ObservableList<StandartListItem> items;
66
56 - final String title;
67 + @observable
68 + String _title;
69 +
70 + String get title => _title;
71 +
72 final WalletBase _wallet;
73 final String _walletName;
74 final AppStore _appStore;
lib/view_model/wallet_new_vm.dart
+5 -29
@@ -1,14 +1,11 @@
1 -import 'package:cake_wallet/arbitrum/arbitrum.dart';
2 -import 'package:cake_wallet/base/base.dart';
1 import 'package:cake_wallet/core/new_wallet_arguments.dart';
2 import 'package:cake_wallet/dogecoin/dogecoin.dart';
5 -import 'package:cake_wallet/ethereum/ethereum.dart';
3 +import 'package:cake_wallet/evm/evm.dart';
4 import 'package:cake_wallet/zano/zano.dart';
5 import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
6 import 'package:cake_wallet/solana/solana.dart';
7 import 'package:cake_wallet/tron/tron.dart';
8 import 'package:cake_wallet/wownero/wownero.dart';
11 -import 'package:hive/hive.dart';
9 import 'package:mobx/mobx.dart';
10 import 'package:cake_wallet/bitcoin/bitcoin.dart';
11 import 'package:cake_wallet/core/wallet_creation_service.dart';
@@ -21,10 +18,7 @@ import 'package:cake_wallet/view_model/wallet_creation_vm.dart';
18 import 'package:cake_wallet/decred/decred.dart';
19 import 'package:cw_core/wallet_base.dart';
20 import 'package:cw_core/wallet_credentials.dart';
24 -import 'package:cw_core/wallet_info.dart';
21 import 'package:cw_core/wallet_type.dart';
26 -
27 -import '../polygon/polygon.dart';
22 import 'advanced_privacy_settings_view_model.dart';
23
24 part 'wallet_new_vm.g.dart';
@@ -51,8 +45,7 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
45 bool get hasLanguageSelector =>
46 [WalletType.monero, WalletType.haven, WalletType.wownero].contains(type);
47
54 - bool get showLanguageSelector =>
55 - newWalletArguments?.mnemonic == null && hasLanguageSelector;
48 + bool get showLanguageSelector => newWalletArguments?.mnemonic == null && hasLanguageSelector;
49
50 bool get hasSeedType =>
51 newWalletArguments?.mnemonic == null &&
@@ -85,21 +78,10 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
78 mnemonic: newWalletArguments!.mnemonic,
79 );
80 case WalletType.ethereum:
88 - return ethereum!.createEthereumNewWalletCredentials(
89 - name: name,
90 - password: walletPassword,
91 - mnemonic: newWalletArguments!.mnemonic,
92 - passphrase: passphrase,
93 - );
81 + case WalletType.polygon:
82 case WalletType.base:
95 - return base!.createBaseNewWalletCredentials(
96 - name: name,
97 - password: walletPassword,
98 - mnemonic: newWalletArguments!.mnemonic,
99 - passphrase: passphrase,
100 - );
83 case WalletType.arbitrum:
102 - return arbitrum!.createArbitrumNewWalletCredentials(
84 + return evm!.createEVMNewWalletCredentials(
85 name: name,
86 password: walletPassword,
87 mnemonic: newWalletArguments!.mnemonic,
@@ -127,13 +109,7 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
109 mnemonic: newWalletArguments!.mnemonic,
110 passphrase: passphrase,
111 );
130 - case WalletType.polygon:
131 - return polygon!.createPolygonNewWalletCredentials(
132 - name: name,
133 - password: walletPassword,
134 - mnemonic: newWalletArguments!.mnemonic,
135 - passphrase: passphrase,
136 - );
112 +
113 case WalletType.solana:
114 return solana!.createSolanaNewWalletCredentials(
115 name: name,
lib/view_model/wallet_restore_view_model.dart
+35 -65
@@ -1,15 +1,13 @@
1 -import 'package:cake_wallet/arbitrum/arbitrum.dart';
2 -import 'package:cake_wallet/base/base.dart';
1 import 'package:cake_wallet/bitcoin/bitcoin.dart';
2 import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
3 import 'package:cake_wallet/core/generate_wallet_password.dart';
4 import 'package:cake_wallet/core/wallet_creation_service.dart';
5 import 'package:cake_wallet/di.dart';
6 import 'package:cake_wallet/dogecoin/dogecoin.dart';
9 -import 'package:cake_wallet/ethereum/ethereum.dart';
7 +import 'package:cake_wallet/evm/evm.dart';
8 import 'package:cake_wallet/monero/monero.dart';
9 import 'package:cake_wallet/nano/nano.dart';
12 -import 'package:cake_wallet/polygon/polygon.dart';
10 +import 'package:cake_wallet/reactions/wallet_connect.dart';
11 import 'package:cake_wallet/solana/solana.dart';
12 import 'package:cake_wallet/store/app_store.dart';
13 import 'package:cake_wallet/tron/tron.dart';
@@ -68,7 +66,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
66 }
67 walletCreationService.changeWalletType(type: type);
68 if (restoredWallet != null) {
71 - if(restoredWallet!.restoreMode == WalletRestoreMode.seed) {
69 + if (restoredWallet!.restoreMode == WalletRestoreMode.seed) {
70 seedSettingsViewModel.setPassphrase(restoredWallet!.passphrase);
71 }
72 }
@@ -78,18 +76,12 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
76 static const decredSeedMnemonicLength = 15;
77
78 late List<WalletRestoreMode> availableModes;
81 - late final bool hasSeedLanguageSelector = [
82 - WalletType.monero,
83 - WalletType.haven,
84 - WalletType.wownero
85 - ].contains(type);
79 + late final bool hasSeedLanguageSelector =
80 + [WalletType.monero, WalletType.haven, WalletType.wownero].contains(type);
81 +
82 + late final bool hasBlockchainHeightSelector =
83 + [WalletType.monero, WalletType.haven, WalletType.wownero].contains(type);
84
87 - late final bool hasBlockchainHeightSelector = [
88 - WalletType.monero,
89 - WalletType.haven,
90 - WalletType.wownero
91 - ].contains(type);
92 -
85 late final bool hasRestoreFromPrivateKey = [
86 WalletType.ethereum,
87 WalletType.polygon,
@@ -101,10 +93,8 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
93 WalletType.tron
94 ].contains(type);
95
104 - late final bool onlyViewKeyRestore = [
105 - if (FeatureFlag.hasBitcoinViewOnly) WalletType.bitcoin,
106 - WalletType.decred
107 - ].contains(type);
96 + late final bool onlyViewKeyRestore =
97 + [if (FeatureFlag.hasBitcoinViewOnly) WalletType.bitcoin, WalletType.decred].contains(type);
98
99 final RestoredWallet? restoredWallet;
100 final HardwareWalletType? hardwareWalletType;
@@ -131,7 +121,11 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
121 switch (type) {
122 case WalletType.monero:
123 return monero!.createMoneroRestoreWalletFromSeedCredentials(
134 - name: name, height: height, mnemonic: seed, password: password, passphrase: passphrase??'');
124 + name: name,
125 + height: height,
126 + mnemonic: seed,
127 + password: password,
128 + passphrase: passphrase ?? '');
129 case WalletType.bitcoin:
130 case WalletType.litecoin:
131 return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials(
@@ -142,13 +136,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
136 derivationType: derivationInfo!.derivationType!,
137 derivationPath: derivationInfo.derivationPath!,
138 );
145 - case WalletType.ethereum:
146 - return ethereum!.createEthereumRestoreWalletFromSeedCredentials(
147 - name: name,
148 - mnemonic: seed,
149 - password: password,
150 - passphrase: passphrase,
151 - );
139 +
140 case WalletType.bitcoinCash:
141 return bitcoinCash!.createBitcoinCashRestoreWalletFromSeedCredentials(
142 name: name,
@@ -172,22 +160,11 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
160 derivationType: derivationInfo!.derivationType!,
161 passphrase: passphrase,
162 );
163 + case WalletType.ethereum:
164 case WalletType.polygon:
176 - return polygon!.createPolygonRestoreWalletFromSeedCredentials(
177 - name: name,
178 - mnemonic: seed,
179 - password: password,
180 - passphrase: passphrase,
181 - );
165 case WalletType.base:
183 - return base!.createBaseRestoreWalletFromSeedCredentials(
184 - name: name,
185 - mnemonic: seed,
186 - password: password,
187 - passphrase: passphrase,
188 - );
166 case WalletType.arbitrum:
190 - return arbitrum!.createArbitrumRestoreWalletFromSeedCredentials(
167 + return evm!.createEVMRestoreWalletFromSeedCredentials(
168 name: name,
169 mnemonic: seed,
170 password: password,
@@ -212,7 +189,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
189 name: name,
190 mnemonic: seed,
191 password: password,
215 - passphrase: passphrase??'',
192 + passphrase: passphrase ?? '',
193 height: height,
194 );
195 case WalletType.zano:
@@ -220,14 +197,14 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
197 name: name,
198 password: password,
199 height: height,
223 - passphrase: passphrase??'',
200 + passphrase: passphrase ?? '',
201 mnemonic: seed,
202 );
203 case WalletType.decred:
204 return decred!.createDecredRestoreWalletFromSeedCredentials(
228 - name: name,
229 - mnemonic: seed,
230 - password: password,
205 + name: name,
206 + mnemonic: seed,
207 + password: password,
208 );
209 case WalletType.none:
210 case WalletType.haven:
@@ -271,13 +248,6 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
248 language: 'English',
249 );
250
274 - case WalletType.ethereum:
275 - return ethereum!.createEthereumRestoreWalletFromPrivateKey(
276 - name: name,
277 - privateKey: options['private_key'] as String,
278 - password: password,
279 - );
280 -
251 case WalletType.nano:
252 return nano!.createNanoRestoreWalletFromKeysCredentials(
253 name: name,
@@ -285,20 +255,11 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
255 seedKey: options['private_key'] as String,
256 derivationType: derivationInfo!.derivationType!,
257 );
258 + case WalletType.ethereum:
259 case WalletType.polygon:
289 - return polygon!.createPolygonRestoreWalletFromPrivateKey(
290 - name: name,
291 - password: password,
292 - privateKey: options['private_key'] as String,
293 - );
260 case WalletType.base:
295 - return base!.createBaseRestoreWalletFromPrivateKey(
296 - name: name,
297 - password: password,
298 - privateKey: options['private_key'] as String,
299 - );
261 case WalletType.arbitrum:
301 - return arbitrum!.createArbitrumRestoreWalletFromPrivateKey(
262 + return evm!.createEVMRestoreWalletFromPrivateKey(
263 name: name,
264 password: password,
265 privateKey: options['private_key'] as String,
@@ -343,7 +304,16 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
304 var list = <DerivationInfo>[];
305 var walletType = credentials["walletType"] as WalletType;
306 var appStore = getIt.get<AppStore>();
346 - var node = appStore.settingsStore.getCurrentNode(walletType);
307 +
308 + int? chainId;
309 + if (isEVMCompatibleChain(walletType)) {
310 + if (appStore.wallet != null) {
311 + chainId = evm!.getSelectedChainId(appStore.wallet!);
312 + }
313 + chainId ??= evm!.getChainIdByWalletType(walletType);
314 + }
315 +
316 + var node = appStore.settingsStore.getCurrentNode(walletType, chainId: chainId);
317
318 switch (walletType) {
319 case WalletType.bitcoin:
lib/view_model/wallet_seed_view_model.dart
+12 -1
@@ -6,6 +6,8 @@ import 'package:flutter/foundation.dart';
6 import 'package:cw_core/wallet_type.dart';
7 import 'package:mobx/mobx.dart';
8 import 'package:cw_core/wallet_base.dart';
9 +import 'package:cake_wallet/evm/evm.dart';
10 +import 'package:cake_wallet/reactions/wallet_connect.dart';
11
12 part 'wallet_seed_view_model.g.dart';
13
@@ -15,11 +17,20 @@ abstract class WalletSeedViewModelBase with Store {
17 WalletSeedViewModelBase(WalletBase wallet)
18 : name = wallet.name,
19 seed = wallet.seed!,
18 - walletType = walletTypeToString(wallet.type),
20 + walletType = _getWalletTypeName(wallet),
21 currentOptions = ObservableList<String>(),
22 verificationIndices = ObservableList<int>() {
23 setupSeedVerification();
24 }
25 +
26 + static String _getWalletTypeName(WalletBase wallet) {
27 + if (isEVMCompatibleChain(wallet.type)) {
28 + final currentChain = evm!.getCurrentChain(wallet);
29 + return currentChain?.name ?? walletTypeToString(wallet.type);
30 + } else {
31 + return walletTypeToString(wallet.type);
32 + }
33 + }
34
35 @observable
36 String name;
lib/view_model/wallet_switcher_view_model.dart
+7 -2
@@ -1,5 +1,6 @@
1 import 'dart:async';
2
3 +import 'package:cake_wallet/reactions/wallet_connect.dart';
4 import 'package:cake_wallet/store/app_store.dart';
5 import 'package:cake_wallet/core/wallet_loading_service.dart';
6 import 'package:cw_core/wallet_info.dart';
@@ -29,8 +30,12 @@ abstract class WalletSwitcherViewModelBase with Store {
30 @action
31 Future<List<WalletInfo>> getWallets(WalletType? walletType) async {
32 final wiList = await WalletInfo.getAll();
32 - if (walletType == null) {
33 - return wiList;
33 + if (walletType == null) return wiList;
34 +
35 + // For EVM-compatible wallet types, show all EVM-compatible wallets
36 + // This allows users to switch between any EVM wallet regardless of the specific chain
37 + if (isEVMCompatibleChain(walletType)) {
38 + return wiList.where((wallet) => isEVMCompatibleChain(wallet.type)).toList();
39 }
40
41 return wiList.where((wallet) => wallet.type == walletType).toList();
model_generator.sh
+2 -2
@@ -1,7 +1,7 @@
1 #!/bin/bash
2 set -x -e
3
4 -for cwcoin in cw_{core,evm,monero,bitcoin,nano,bitcoin_cash,solana,tron,wownero,zano,decred,dogecoin,base,arbitrum}
4 +for cwcoin in cw_{core,evm,monero,bitcoin,nano,bitcoin_cash,solana,tron,wownero,zano,decred,dogecoin}
5 do
6 if [[ "x$1" == "xasync" ]];
7 then
@@ -10,7 +10,7 @@ do
10 cd $cwcoin; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd ..
11 fi
12 done
13 -for cwcoin in cw_{polygon,ethereum,mweb};
13 +for cwcoin in cw_mweb;
14 do
15 if [[ "x$1" == "xasync" ]];
16 then
pubspec_base.yaml
+1
@@ -217,6 +217,7 @@ flutter:
217
218 assets:
219 - assets/images/
220 + - assets/images/evm_switcher_icons/
221 - assets/images/flags/
222 - assets/images/hardware_wallet/
223 - assets/images/crypto/
res/values/strings_ar.arb
+2
@@ -1054,6 +1054,7 @@
1054 "tx_rejected_dust_output": "المعاملة التي يتم رفضها بموجب قواعد الشبكة ، وكمية الإخراج المنخفض (الغبار). يرجى زيادة المبلغ.",
1055 "tx_rejected_dust_output_send_all": "المعاملة التي يتم رفضها بموجب قواعد الشبكة ، وكمية الإخراج المنخفض (الغبار). يرجى التحقق من رصيد العملات المعدنية المحددة تحت التحكم في العملة.",
1056 "tx_rejected_vout_negative": "لا يوجد ما يكفي من الرصيد لدفع رسوم هذه الصفقة. يرجى التحقق من رصيد العملات المعدنية تحت السيطرة على العملة.",
1057 + "tx_retry_message": "حدث خطأ أثناء معالجة المعاملة. يرجى إعادة محاولة المعاملة.",
1058 "tx_wrong_balance_exception": "ليس لديك ما يكفي من ${currency} لإرسال هذا المبلغ.",
1059 "tx_wrong_balance_with_amount_exception": "ليس لديك ما يكفي ${currency} لإرسال المبلغ الإجمالي ${amount}",
1060 "tx_zero_fee_exception": "لا يمكن إرسال معاملة مع 0 رسوم. حاول زيادة المعدل أو التحقق من اتصالك للحصول على أحدث التقديرات.",
@@ -1074,6 +1075,7 @@
1075 "upto": "حتى ${value}",
1076 "usb": "USB",
1077 "use": "التبديل إلى",
1078 + "use_blink_protection": "استخدم الحماية من الرمش",
1079 "use_card_info_three": "استخدم البطاقة الرقمية عبر الإنترنت أو مع طرق الدفع غير التلامسية.",
1080 "use_card_info_two": "يتم تحويل الأموال إلى الدولار الأمريكي عند الاحتفاظ بها في الحساب المدفوع مسبقًا ، وليس بالعملات الرقمية.",
1081 "use_device_theme": "استخدم موضوع الجهاز",
res/values/strings_bg.arb
+2
@@ -1054,6 +1054,7 @@
1054 "tx_rejected_dust_output": "Транзакция, отхвърлена от мрежови правила, ниска стойност на изхода (прах). Моля, увеличете сумата.",
1055 "tx_rejected_dust_output_send_all": "Транзакция, отхвърлена от мрежови правила, ниска стойност на изхода (прах). Моля, проверете баланса на монетите, избрани под контрол на монети.",
1056 "tx_rejected_vout_negative": "Няма достатъчно баланс, за да платите за таксите на тази транзакция. Моля, проверете баланса на монетите под контрол на монетите.",
1057 + "tx_retry_message": "Възникна грешка при обработката на транзакцията. Моля, опитайте отново транзакцията.",
1058 "tx_wrong_balance_exception": "Нямате достатъчно ${currency}, за да изпратите тази сума.",
1059 "tx_wrong_balance_with_amount_exception": "Нямате достатъчно ${currency} За да изпратите общата сума на ${amount}",
1060 "tx_zero_fee_exception": "Не може да изпраща транзакция с 0 такса. Опитайте да увеличите скоростта или да проверите връзката си за най -новите оценки.",
@@ -1074,6 +1075,7 @@
1075 "upto": "до ${value}",
1076 "usb": "USB",
1077 "use": "Смяна на ",
1078 + "use_blink_protection": "Използвайте защита от мигане",
1079 "use_card_info_three": "Използвайте дигиталната карта онлайн или чрез безконтактен метод на плащане.",
1080 "use_card_info_two": "Средствата се обръщат в USD, когато биват запазени в предплатената карта, а не в дигитална валута.",
1081 "use_device_theme": "Използвайте темата за устройството",
res/values/strings_cs.arb
+2
@@ -1054,6 +1054,7 @@
1054 "tx_rejected_dust_output": "Transakce zamítnuta síťovými pravidly, nízkým množstvím výstupu (prach). Zvyšte prosím částku.",
1055 "tx_rejected_dust_output_send_all": "Transakce zamítnuta síťovými pravidly, nízkým množstvím výstupu (prach). Zkontrolujte prosím zůstatek mincí vybraných pod kontrolou mincí.",
1056 "tx_rejected_vout_negative": "Nedostatek zůstatek na zaplacení poplatků za tuto transakci. Zkontrolujte prosím zůstatek mincí pod kontrolou mincí.",
1057 + "tx_retry_message": "Při zpracování transakce došlo k chybě. Zkuste transakci opakovat.",
1058 "tx_wrong_balance_exception": "Nemáte dost ${currency} pro odeslání této částky.",
1059 "tx_wrong_balance_with_amount_exception": "Nemáte dost ${currency} na odeslání celkové částky ${amount}",
1060 "tx_zero_fee_exception": "Nelze odeslat transakci s 0 poplatkem. Zkuste zvýšit sazbu nebo zkontrolovat připojení pro nejnovější odhady.",
@@ -1074,6 +1075,7 @@
1075 "upto": "až ${value}",
1076 "usb": "USB",
1077 "use": "Přepnout na ",
1078 + "use_blink_protection": "Použijte ochranu proti mrknutí",
1079 "use_card_info_three": "Použijte tuto digitální kartu online nebo bezkontaktními platebními metodami.",
1080 "use_card_info_two": "Prostředky jsou převedeny na USD, když jsou drženy na předplaceném účtu, nikoliv na digitální měnu.",
1081 "use_device_theme": "Použijte téma zařízení",
res/values/strings_de.arb
+3 -1
@@ -1055,6 +1055,7 @@
1055 "tx_rejected_dust_output": "Transaktion durch Netzwerkregeln, niedriger Ausgangsmenge (Dust) abgelehnt. Bitte erhöhen Sie den Betrag.",
1056 "tx_rejected_dust_output_send_all": "Transaktion durch Netzwerkregeln, niedriger Ausgangsmenge (Dust) abgelehnt. Bitte überprüfen Sie das Guthabe der unter Coinkontrolle ausgewählten Coins.",
1057 "tx_rejected_vout_negative": "Nicht genug Guthaben, um die Gebühren dieser Transaktion zu bezahlen. Bitte überprüfen Sie den Restbetrag der Coins unter Coinkontrolle.",
1058 + "tx_retry_message": "Bei der Verarbeitung der Transaktion ist ein Fehler aufgetreten. Bitte versuchen Sie die Transaktion erneut.",
1059 "tx_wrong_balance_exception": "Sie haben nicht genug ${currency}, um diesen Betrag zu senden.",
1060 "tx_wrong_balance_with_amount_exception": "Sie haben nicht genug ${currency}, um die Gesamtmenge von ${amount} zu senden",
1061 "tx_zero_fee_exception": "Transaktion kann nicht mit 0 Gebühren gesendet werden. Versuchen Sie, die Rate zu erhöhen oder Ihre Verbindung auf die neuesten Schätzungen zu überprüfen.",
@@ -1076,6 +1077,7 @@
1077 "upto": "bis zu ${value}",
1078 "usb": "USB",
1079 "use": "Wechsel zu ",
1080 + "use_blink_protection": "Verwenden Sie den Blinzelschutz",
1081 "use_card_info_three": "Verwenden Sie die digitale Karte online oder mit kontaktlosen Zahlungsmethoden.",
1082 "use_card_info_two": "Guthaben werden auf dem Prepaid-Konto in USD umgerechnet, nicht in digitale Währung.",
1083 "use_device_theme": "Verwenden Sie das Gerätethema",
@@ -1173,4 +1175,4 @@
1175 "youCanGoBackToYourDapp": "Sie können jetzt zu Ihrem Dapp zurückkehren",
1176 "your": "Dein",
1177 "yy": "YY"
1176 -}
1178 +}
\ No newline at end of file
res/values/strings_en.arb
+2
@@ -1055,6 +1055,7 @@
1055 "tx_rejected_dust_output": "Transaction rejected by network rules, low output amount (dust). Please increase the amount.",
1056 "tx_rejected_dust_output_send_all": "Transaction rejected by network rules, low output amount (dust). Please check the balance of coins selected under Coin Control.",
1057 "tx_rejected_vout_negative": "Not enough balance to pay for this transaction's fees. Please check the balance of coins under Coin Control.",
1058 + "tx_retry_message": "An error occurred while processing the transaction. Please retry the transaction.",
1059 "tx_wrong_balance_exception": "You do not have enough ${currency} to send this amount.",
1060 "tx_wrong_balance_with_amount_exception": "You do not have enough ${currency} to send the total amount of ${amount}",
1061 "tx_zero_fee_exception": "Cannot send transaction with 0 fee. Try increasing the rate or checking your connection for latest estimates.",
@@ -1075,6 +1076,7 @@
1076 "upto": "up to ${value}",
1077 "usb": "USB",
1078 "use": "Switch to ",
1079 + "use_blink_protection": "Use Blink Protection",
1080 "use_card_info_three": "Use the digital card online or with contactless payment methods.",
1081 "use_card_info_two": "Funds are converted to USD when they're held in the prepaid account, not in digital currencies.",
1082 "use_device_theme": "Use Device Theme",
res/values/strings_es.arb
+2
@@ -1055,6 +1055,7 @@
1055 "tx_rejected_dust_output": "Transacción rechazada por reglas de la red, cantidad de salida baja (dust). Por favor, aumenta la cantidad.",
1056 "tx_rejected_dust_output_send_all": "Transacción rechazada por reglas de la red, baja cantidad de salida (dust). Verifique el saldo de las monedas seleccionadas bajo Control de Monedas.",
1057 "tx_rejected_vout_negative": "No hay suficiente saldo para pagar las tarifas de esta transacción. Por favor, verifica el saldo de monedas en Control de Monedas.",
1058 + "tx_retry_message": "Se produjo un error al procesar la transacción. Vuelva a intentar la transacción.",
1059 "tx_wrong_balance_exception": "No tienes suficiente ${currency} para enviar este monto.",
1060 "tx_wrong_balance_with_amount_exception": "No tienes suficiente ${currency} para enviar la cantidad total de ${amount}",
1061 "tx_zero_fee_exception": "No se puede enviar una transacción con tarifa 0. Intente aumentar la tarifa o verifique su conexión para obtener las últimas estimaciones.",
@@ -1075,6 +1076,7 @@
1076 "upto": "hasta ${value}",
1077 "usb": "USB",
1078 "use": "Cambiar a ",
1079 + "use_blink_protection": "Usar protección contra parpadeos",
1080 "use_card_info_three": "Utiliza la tarjeta digital en línea o con métodos de pago sin contacto.",
1081 "use_card_info_two": "Los fondos se convierten a USD cuando se mantienen en la cuenta prepaga, no en monedas digitales.",
1082 "use_device_theme": "Usar el tema del dispositivo",
res/values/strings_fr.arb
+2
@@ -1054,6 +1054,7 @@
1054 "tx_rejected_dust_output": "Transaction rejetée par les règles du réseau : faible quantité de sortie (poussière). Veuillez augmenter le montant.",
1055 "tx_rejected_dust_output_send_all": "Transaction rejetée par les règles du réseau : montant de sortie trop faible (poussière). Veuillez vérifier le solde des pièces sélectionnées dans le contrôle des pièces.",
1056 "tx_rejected_vout_negative": "Solde insuffisant pour payer les frais de cette transaction. Veuillez vérifier le solde des pièces dans le contrôle des pièces.",
1057 + "tx_retry_message": "Une erreur s'est produite lors du traitement de la transaction. Veuillez réessayer la transaction.",
1058 "tx_wrong_balance_exception": "Vous n'avez pas assez ${currency} pour envoyer ce montant.",
1059 "tx_wrong_balance_with_amount_exception": "Vous n'avez pas assez ${currency} pour envoyer le montant total de ${amount}",
1060 "tx_zero_fee_exception": "Impossible d'envoyer une transaction avec 0 frais. Essayez d'augmenter le taux ou de vérifier votre connexion pour les dernières estimations.",
@@ -1074,6 +1075,7 @@
1075 "upto": "jusqu'à ${value}",
1076 "usb": "USB",
1077 "use": "Changer vers code PIN à ",
1078 + "use_blink_protection": "Utiliser la protection contre les clignements",
1079 "use_card_info_three": "Utilisez la carte numérique en ligne ou avec des méthodes de paiement sans contact.",
1080 "use_card_info_two": "Les fonds sont convertis en USD lorsqu'ils sont détenus sur le compte prépayé, et non en devises numériques.",
1081 "use_device_theme": "Utiliser le thème de l'appareil",
res/values/strings_gn.arb
+2
@@ -820,6 +820,7 @@
820 "tx_rejected_dust_output": "Oñemboyke pe transacción red rembiapo rupi: osẽva michĩeterei . Embohetave pe monto.",
821 "tx_rejected_dust_output_send_all": "Oñemboyke pe transacción red rembiapo rupi: Osẽva michĩeterei . Ehecha porã moneda reiporavóva moneda ñangarekohápe.",
822 "tx_rejected_vout_negative": "Ndaipóri mba’eve saldo hepyme’ẽ haguã tarifa ko transacción rehegua. Ehecha porã pe moneda saldo oĩva moneda ñangarekohápe.",
823 + "tx_retry_message": "Oiko peteĩ jejavy oñemboguata aja pe transacción. Eñeha’ã jey pe transacción rehe.",
824 "tx_wrong_balance_exception": "Ndaipóri heta ${currency} remondo hag̃ua ko monto.",
825 "tx_wrong_balance_with_amount_exception": "Ndaipóri heta ${currency} remondo hag̃ua enteroite monto. ${amount}",
826 "tx_zero_fee_exception": "Ndaikatúi emondo transacción 0 tarifa-va. Eha’ã embohetave pe tasa térã ehecha nde joaju ikatu hag̃ua eguereko umi tarifa pyahu ipahaitepe g̃uara.",
@@ -837,6 +838,7 @@
838 "upto": "Kóva peve ${value}",
839 "usb": "USB",
840 "use": "Reipuru pe",
841 + "use_blink_protection": "Eipuru ñeñangareko parpadeo rehegua .",
842 "use_card_info_three": "Eipuru peteĩ tarjeta línea rehegua térã ambue jehepyme’ẽ ojepokó’ỹva.",
843 "use_card_info_two": "Pe viru oñemoambue USD-pe ojeguerekóramo cuenta prepaga-pe; ndaha’éi moneda digital-pe.",
844 "use_ssl": "Eipuru SSL",
res/values/strings_ha.arb
+2
@@ -1056,6 +1056,7 @@
1056 "tx_rejected_dust_output": "Ma'adar da aka ƙi ta dokokin cibiyar sadarwa, ƙananan fitarwa (ƙura). Da fatan za a ƙara adadin.",
1057 "tx_rejected_dust_output_send_all": "Ma'adar da aka ƙi ta dokokin cibiyar sadarwa, ƙananan fitarwa (ƙura). Da fatan za a duba daidaiton tsabar kudi a ƙarƙashin ikon tsabar kudin.",
1058 "tx_rejected_vout_negative": "Bai isa daidai ba don biyan wannan kudin ma'amala. Da fatan za a duba daidaiton tsabar kudi a ƙarƙashin ikon tsabar kudin.",
1059 + "tx_retry_message": "An sami kuskure yayin sarrafa ma'amala. Da fatan za a sake gwada ciniki.",
1060 "tx_wrong_balance_exception": "Ba ku da isasshen ${currency} don aika wannan adadin.",
1061 "tx_wrong_balance_with_amount_exception": "Ba ku da isasshen ${currency} don aika jimlar adadin ${amount}",
1062 "tx_zero_fee_exception": "Ba zai iya aika ma'amala da kuɗi 0 ba. Gwada ƙara ƙimar ko bincika haɗin ku don mahimmin ƙididdiga.",
@@ -1076,6 +1077,7 @@
1077 "upto": "har zuwa ${value}",
1078 "usb": "Alib",
1079 "use": "Canja zuwa",
1080 + "use_blink_protection": "Yi amfani da Kariyar Blink",
1081 "use_card_info_three": "Yi amfani da katin dijital akan layi ko tare da hanyoyin biyan kuɗi mara lamba.",
1082 "use_card_info_two": "Ana canza kuɗi zuwa dalar Amurka lokacin da ake riƙe su a cikin asusun da aka riga aka biya, ba cikin agogon dijital ba.",
1083 "use_device_theme": "Yi amfani da taken na'urar",
res/values/strings_hi.arb
+2
@@ -1056,6 +1056,7 @@
1056 "tx_rejected_dust_output": "नेटवर्क नियमों, कम आउटपुट राशि (धूल) द्वारा खारिज किए गए लेनदेन। कृपया राशि बढ़ाएं।",
1057 "tx_rejected_dust_output_send_all": "नेटवर्क नियमों, कम आउटपुट राशि (धूल) द्वारा खारिज किए गए लेनदेन। कृपया सिक्का नियंत्रण के तहत चुने गए सिक्कों के संतुलन की जाँच करें।",
1058 "tx_rejected_vout_negative": "इस लेनदेन की फीस के लिए भुगतान करने के लिए पर्याप्त शेष राशि नहीं है। कृपया सिक्के नियंत्रण के तहत सिक्कों के संतुलन की जाँच करें।",
1059 + "tx_retry_message": "लेन-देन संसाधित करते समय एक त्रुटि उत्पन्न हुई. कृपया लेन-देन का पुनः प्रयास करें.",
1060 "tx_wrong_balance_exception": "इस राशि को भेजने के लिए आपके पास पर्याप्त ${currency} नहीं है।",
1061 "tx_wrong_balance_with_amount_exception": "आपके पास पर्याप्त नहीं है${currency} ${amount} की कुल राशि भेजने के लिए",
1062 "tx_zero_fee_exception": "0 शुल्क के साथ लेनदेन नहीं भेज सकते। नवीनतम अनुमानों के लिए दर बढ़ाने या अपने कनेक्शन की जांच करने का प्रयास करें।",
@@ -1076,6 +1077,7 @@
1077 "upto": "${value} तक",
1078 "usb": "USB",
1079 "use": "उपयोग ",
1080 + "use_blink_protection": "ब्लिंक प्रोटेक्शन का उपयोग करें",
1081 "use_card_info_three": "डिजिटल कार्ड का ऑनलाइन या संपर्क रहित भुगतान विधियों के साथ उपयोग करें।",
1082 "use_card_info_two": "डिजिटल मुद्राओं में नहीं, प्रीपेड खाते में रखे जाने पर निधियों को यूएसडी में बदल दिया जाता है।",
1083 "use_device_theme": "डिवाइस थीम का उपयोग करें",
res/values/strings_hr.arb
+2
@@ -1054,6 +1054,7 @@
1054 "tx_rejected_dust_output": "Transakcija odbijena mrežnim pravilima, niska količina izlaza (prašina). Molimo povećajte iznos.",
1055 "tx_rejected_dust_output_send_all": "Transakcija odbijena mrežnim pravilima, niska količina izlaza (prašina). Molimo provjerite ravnotežu kovanica odabranih pod kontrolom novčića.",
1056 "tx_rejected_vout_negative": "Nema dovoljno salda za plaćanje naknada ove transakcije. Molimo provjerite ravnotežu kovanica pod kontrolom novčića.",
1057 + "tx_retry_message": "Došlo je do pogreške prilikom obrade transakcije. Molimo pokušajte ponovno transakciju.",
1058 "tx_wrong_balance_exception": "Nemate dovoljno ${currency} da biste poslali ovaj iznos.",
1059 "tx_wrong_balance_with_amount_exception": "Nemate dovoljno ${currency} za slanje ukupne količine ${amount}",
1060 "tx_zero_fee_exception": "Ne mogu poslati transakciju s 0 naknade. Pokušajte povećati stopu ili provjeriti vezu za najnovije procjene.",
@@ -1074,6 +1075,7 @@
1075 "upto": "do ${value}",
1076 "usb": "USB",
1077 "use": "Prebaci na",
1078 + "use_blink_protection": "Koristite zaštitu od treptanja",
1079 "use_card_info_three": "Koristite digitalnu karticu online ili s beskontaktnim metodama plaćanja.",
1080 "use_card_info_two": "Sredstva se pretvaraju u USD kada se drže na prepaid računu, a ne u digitalnim valutama.",
1081 "use_device_theme": "Koristite temu uređaja",
res/values/strings_hy.arb
+2
@@ -1052,6 +1052,7 @@
1052 "tx_rejected_dust_output": "Փոխանցումը մերժվել է ցածր ելքային գումարով (փոշի): Խնդրում ենք ավելացնել գումարը",
1053 "tx_rejected_dust_output_send_all": "Փոխանցումը մերժվել է ցածր ելքային գումարով (փոշի): Խնդրում ենք ստուգել արժույթների հաշիվը մուտքային վերահսկողության տակ",
1054 "tx_rejected_vout_negative": "Բավարար մնացորդ չկա այս փոխանցման վճարների համար։ Խնդրում ենք ստուգել արժույթների մնացորդը Coin Control-ում։",
1055 + "tx_retry_message": "Գործարքը մշակելիս սխալ տեղի ունեցավ: Խնդրում ենք նորից փորձել գործարքը:",
1056 "tx_wrong_balance_exception": "Դուք չունեք բավարար ${currency} այս գումարը ուղարկելու համար։",
1057 "tx_wrong_balance_with_amount_exception": "Դուք չունեք բավարար ${currency} ${amount} գումարը ուղարկելու համար։",
1058 "tx_zero_fee_exception": "Չի կարող ուղարկվել Փոխանցումը առանց վճարի։ Փորձեք բարձրացնել գինը կամ ստուգել ձեր կապը վերջին գնահատականների համար։",
@@ -1072,6 +1073,7 @@
1073 "upto": "մինչև ${value}",
1074 "usb": "USB",
1075 "use": "Փոխեք ",
1076 + "use_blink_protection": "Օգտագործեք Blink Protection-ը",
1077 "use_card_info_three": "Օգտագործեք թվային քարտը առցանց կամ անշփման վճարման մեթոդներով։",
1078 "use_card_info_two": "Միջոցները փոխարկվում են ԱՄՆ դոլար երբ դրանք պահվում են կանխավճարային հաշվեկշռում, ոչ թե թվային արժույթներում։",
1079 "use_device_theme": "Օգտագործեք սարքի թեման",
res/values/strings_id.arb
+2
@@ -1057,6 +1057,7 @@
1057 "tx_rejected_dust_output": "Transaksi ditolak oleh aturan jaringan, jumlah output rendah (debu). Harap tingkatkan jumlahnya.",
1058 "tx_rejected_dust_output_send_all": "Transaksi ditolak oleh aturan jaringan, jumlah output rendah (debu). Silakan periksa saldo koin yang dipilih di bawah kontrol koin.",
1059 "tx_rejected_vout_negative": "Tidak cukup saldo untuk membayar biaya transaksi ini. Silakan periksa saldo koin di bawah kendali koin.",
1060 + "tx_retry_message": "Terjadi kesalahan saat memproses transaksi. Silakan coba lagi transaksinya.",
1061 "tx_wrong_balance_exception": "Anda tidak memiliki cukup ${currency} untuk mengirim jumlah ini.",
1062 "tx_wrong_balance_with_amount_exception": "Anda tidak memiliki cukup ${currency} untuk mengirim jumlah total ${amount}",
1063 "tx_zero_fee_exception": "Tidak dapat mengirim transaksi dengan biaya 0. Coba tingkatkan tarif atau periksa koneksi Anda untuk perkiraan terbaru.",
@@ -1077,6 +1078,7 @@
1078 "upto": "hingga ${value}",
1079 "usb": "USB",
1080 "use": "Beralih ke ",
1081 + "use_blink_protection": "Gunakan Perlindungan Berkedip",
1082 "use_card_info_three": "Gunakan kartu digital secara online atau dengan metode pembayaran tanpa kontak.",
1083 "use_card_info_two": "Dana dikonversi ke USD ketika disimpan dalam akun pra-bayar, bukan dalam mata uang digital.",
1084 "use_device_theme": "Gunakan tema perangkat",
res/values/strings_it.arb
+2
@@ -1055,6 +1055,7 @@
1055 "tx_rejected_dust_output": "Transazione respinta dalle regole di rete, bassa quantità di output (dust). Si prega di aumentare l'importo.",
1056 "tx_rejected_dust_output_send_all": "Transazione respinta dalle regole di rete, bassa quantità di output (dust). Si prega di controllare il saldo delle monete selezionate sotto controllo delle monete.",
1057 "tx_rejected_vout_negative": "Il saldo disponibile non è sufficiente per pagare le commissioni di questa transazione. Si prega di controllare il saldo delle monete sotto Controllo monete.",
1058 + "tx_retry_message": "Si è verificato un errore durante l'elaborazione della transazione. Riprova la transazione.",
1059 "tx_wrong_balance_exception": "Non hai abbastanza ${currency} per inviare questo importo.",
1060 "tx_wrong_balance_with_amount_exception": "Non hai abbastanza ${currency} per inviare la quantità totale di ${amount}",
1061 "tx_zero_fee_exception": "Impossibile inviare transazioni con 0 commissioni. Prova ad aumentare le commissioni, o controlla la connessione per le ultime stime.",
@@ -1075,6 +1076,7 @@
1076 "upto": "fino a ${value}",
1077 "usb": "USB",
1078 "use": "Passa a ",
1079 + "use_blink_protection": "Utilizza la protezione dagli occhi chiusi",
1080 "use_card_info_three": "Utilizza la carta digitale online o con metodi di pagamento contactless.",
1081 "use_card_info_two": "I fondi vengono convertiti in USD quando sono detenuti nel conto prepagato, non in valute digitali.",
1082 "use_device_theme": "Usa il tema del dispositivo",
res/values/strings_ja.arb
+2
@@ -1055,6 +1055,7 @@
1055 "tx_rejected_dust_output": "ネットワークルール、低出力量(ダスト)によって拒否されたトランザクション。金額を増やしてください。",
1056 "tx_rejected_dust_output_send_all": "ネットワークルール、低出力量(ダスト)によって拒否されたトランザクション。コイン管理下で選択されたコインのバランスを確認してください。",
1057 "tx_rejected_vout_negative": "この取引の料金に支払うのに十分な残高はありません。コイン制御下のコインのバランスを確認してください。",
1058 + "tx_retry_message": "トランザクションの処理中にエラーが発生しました。トランザクションを再試行してください。",
1059 "tx_wrong_balance_exception": "この金額を送信するのに十分な${currency}はありません。",
1060 "tx_wrong_balance_with_amount_exception": "あなたは十分なものを持っていませ${currency} ${amount}",
1061 "tx_zero_fee_exception": "0料金でトランザクションを送信できません。レートを上げて、最新の見積もりについて接続を確認してみてください。",
@@ -1075,6 +1076,7 @@
1076 "upto": "up up ${value}",
1077 "usb": "USB",
1078 "use": "使用する ",
1079 + "use_blink_protection": "まばたき防止を使用する",
1080 "use_card_info_three": "デジタルカードをオンラインまたは非接触型決済方法で使用してください。",
1081 "use_card_info_two": "デジタル通貨ではなく、プリペイドアカウントで保持されている場合、資金は米ドルに変換されます。",
1082 "use_device_theme": "デバイステーマを使用します",
res/values/strings_ko.arb
+2
@@ -1055,6 +1055,7 @@
1055 "tx_rejected_dust_output": "네트워크 규칙에 의해 트랜잭션 거부됨, 낮은 출력 금액 (더스트). 금액을 늘리세요.",
1056 "tx_rejected_dust_output_send_all": "네트워크 규칙에 의해 트랜잭션 거부됨, 낮은 출력 금액 (더스트). 코인 제어에서 선택한 코인 잔액을 확인하세요.",
1057 "tx_rejected_vout_negative": "이 트랜잭션 수수료를 지불하기에 잔액이 부족합니다. 코인 제어에서 코인 잔액을 확인하세요.",
1058 + "tx_retry_message": "거래를 처리하는 동안 오류가 발생했습니다. 거래를 다시 시도하십시오.",
1059 "tx_wrong_balance_exception": "이 금액을 보내기에 ${currency}이(가) 충분하지 않습니다.",
1060 "tx_wrong_balance_with_amount_exception": "총 금액 ${amount}을(를) 보내기에 ${currency}이(가) 충분하지 않습니다.",
1061 "tx_zero_fee_exception": "수수료 0으로 트랜잭션을 보낼 수 없습니다. 요율을 높이거나 연결을 확인하여 최신 예상치를 확인하세요.",
@@ -1075,6 +1076,7 @@
1076 "upto": "${value}까지",
1077 "usb": "USB",
1078 "use": "다음으로 전환 ",
1079 + "use_blink_protection": "깜박임 방지 사용",
1080 "use_card_info_three": "디지털 카드를 온라인 또는 비접촉 결제 방법으로 사용하세요.",
1081 "use_card_info_two": "자금은 디지털 통화가 아닌 선불 계정에 보관될 때 USD로 변환됩니다.",
1082 "use_device_theme": "장치 테마를 사용하십시오",
res/values/strings_my.arb
+2
@@ -1054,6 +1054,7 @@
1054 "tx_rejected_dust_output": "Network စည်းမျဉ်းစည်းကမ်းများဖြင့် ပယ်ချ. ငွေပေးချေမှုသည် output output (ဖုန်မှုန့်) ဖြင့်ပယ်ချခဲ့သည်။ ကျေးဇူးပြုပြီးငွေပမာဏကိုတိုးမြှင့်ပေးပါ။",
1055 "tx_rejected_dust_output_send_all": "Network စည်းမျဉ်းစည်းကမ်းများဖြင့် ပယ်ချ. ငွေပေးချေမှုသည် output output (ဖုန်မှုန့်) ဖြင့်ပယ်ချခဲ့သည်။ ဒင်္ဂါးပြားထိန်းချုပ်မှုအောက်တွင်ရွေးချယ်ထားသောဒင်္ဂါးများ၏လက်ကျန်ငွေကိုစစ်ဆေးပါ။",
1056 "tx_rejected_vout_negative": "ဒီငွေပေးငွေယူရဲ့အခကြေးငွေအတွက်ပေးဆောင်ဖို့လုံလောက်တဲ့ဟန်ချက်မလုံလောက်။ ဒင်္ဂါးပြား၏လက်ကျန်ငွေလက်ကျန်ငွေကိုစစ်ဆေးပါ။",
1057 + "tx_retry_message": "ငွေပေးငွေယူလုပ်ဆောင်နေစဉ် အမှားအယွင်းတစ်ခု ဖြစ်ပွားခဲ့သည်။ ငွေပေးငွေယူကို ပြန်စမ်းကြည့်ပါ။",
1058 "tx_wrong_balance_exception": "ဤငွေပမာဏကိုပေးပို့ရန်သင့်တွင် ${currency} မရှိပါ။",
1059 "tx_wrong_balance_with_amount_exception": "သင့်တွင်လုံလောက်မှုမရှိပါ${currency} ${amount}",
1060 "tx_zero_fee_exception": "0 ကြေးနှင့်အတူငွေပေးငွေယူပေးပို့လို့မရပါဘူး။ နှုန်းကိုတိုးမြှင့်ခြင်းသို့မဟုတ်နောက်ဆုံးခန့်မှန်းချက်များအတွက်သင်၏ connection ကိုစစ်ဆေးပါ။",
@@ -1074,6 +1075,7 @@
1075 "upto": "${value} အထိ",
1076 "usb": "ယူအက်စ်ဘီ",
1077 "use": "သို့ပြောင်းပါ။",
1078 + "use_blink_protection": "Blink Protection ကိုသုံးပါ။",
1079 "use_card_info_three": "ဒစ်ဂျစ်တယ်ကတ်ကို အွန်လိုင်း သို့မဟုတ် ထိတွေ့မှုမဲ့ ငွေပေးချေမှုနည်းလမ်းများဖြင့် အသုံးပြုပါ။",
1080 "use_card_info_two": "ဒစ်ဂျစ်တယ်ငွေကြေးများဖြင့်မဟုတ်ဘဲ ကြိုတင်ငွေပေးချေသည့်အကောင့်တွင် သိမ်းထားသည့်အခါ ရန်ပုံငွေများကို USD သို့ ပြောင်းလဲပါသည်။",
1081 "use_device_theme": "Device Theme ကိုသုံးပါ",
res/values/strings_nl.arb
+2
@@ -1054,6 +1054,7 @@
1054 "tx_rejected_dust_output": "Transactie afgewezen door netwerkregels, laag outputbedrag (stof). Verhoog het bedrag.",
1055 "tx_rejected_dust_output_send_all": "Transactie afgewezen door netwerkregels, laag outputbedrag (stof). Controleer het saldo van munten die zijn geselecteerd onder muntcontrole.",
1056 "tx_rejected_vout_negative": "Niet genoeg saldo om te betalen voor de kosten van deze transactie. Controleer het saldo van munten onder muntcontrole.",
1057 + "tx_retry_message": "Er is een fout opgetreden tijdens het verwerken van de transactie. Probeer de transactie opnieuw.",
1058 "tx_wrong_balance_exception": "Je hebt niet genoeg ${currency} om dit bedrag te verzenden.",
1059 "tx_wrong_balance_with_amount_exception": "Je hebt niet genoeg ${currency} om de totale hoeveelheid ${amount} te verzenden",
1060 "tx_zero_fee_exception": "Kan geen transactie verzenden met 0 kosten. Probeer het tarief te verhogen of uw verbinding te controleren op de laatste schattingen.",
@@ -1074,6 +1075,7 @@
1075 "upto": "tot ${value}",
1076 "usb": "USB",
1077 "use": "Gebruik ",
1078 + "use_blink_protection": "Gebruik Knipperbeveiliging",
1079 "use_card_info_three": "Gebruik de digitale kaart online of met contactloze betaalmethoden.",
1080 "use_card_info_two": "Tegoeden worden omgezet naar USD wanneer ze op de prepaid-rekening staan, niet in digitale valuta.",
1081 "use_device_theme": "Gebruik apparaatthema",
res/values/strings_pl.arb
+2
@@ -1053,6 +1053,7 @@
1053 "tx_rejected_dust_output": "Transakcja odrzucona zgodnie z regułami sieci, niska kwota wyjścia (dust). Proszę zwiększyć kwotę.",
1054 "tx_rejected_dust_output_send_all": "Transakcja odrzucona przez zasady sieci, niska kwota wyjścia (dust). Sprawdź saldo wybranych monet w sekcji Coin Control.",
1055 "tx_rejected_vout_negative": "Niewystarczające saldo, aby pokryć opłaty tej transakcji. Sprawdź saldo w sekcji Coin Control.",
1056 + "tx_retry_message": "Wystąpił błąd podczas przetwarzania transakcji. Spróbuj ponownie przeprowadzić transakcję.",
1057 "tx_wrong_balance_exception": "Nie masz wystarczającej ilości ${currency}, aby wysłać tę kwotę.",
1058 "tx_wrong_balance_with_amount_exception": "Nie masz wystarczająco dużo ${currency}, aby wysłać całkowitą kwotę ${amount}",
1059 "tx_zero_fee_exception": "Nie można wysłać transakcji z zerową opłatą. Spróbuj zwiększyć opłatę lub sprawdzić połączenie, aby uzyskać najnowsze szacunki.",
@@ -1073,6 +1074,7 @@
1074 "upto": "do ${value}",
1075 "usb": "USB",
1076 "use": "Przełącz na ",
1077 + "use_blink_protection": "Użyj ochrony przed mruganiem",
1078 "use_card_info_three": "Użyj cyfrowej karty online lub przy pomocy zbliżeniowych metod płatności.",
1079 "use_card_info_two": "Środki są konwertowane na USD, gdy są przechowywane na koncie przedpłaconym, a nie w kryptowalutach.",
1080 "use_device_theme": "Użyj motywu systemowego",
res/values/strings_pt.arb
+2
@@ -1056,6 +1056,7 @@
1056 "tx_rejected_dust_output": "Transação rejeitada por regras de rede, baixa quantidade de saída (poeira). Por favor, aumente o valor.",
1057 "tx_rejected_dust_output_send_all": "Transação rejeitada por regras de rede, baixa quantidade de saída (poeira). Por favor, verifique o saldo de moedas selecionadas sob controle de moedas.",
1058 "tx_rejected_vout_negative": "Não há saldo suficiente para pagar as taxas desta transação. Por favor, verifique o saldo de moedas sob controle de moedas.",
1059 + "tx_retry_message": "Ocorreu um erro ao processar a transação. Por favor, tente novamente a transação.",
1060 "tx_wrong_balance_exception": "Você não tem o suficiente ${currency} para enviar esse valor.",
1061 "tx_wrong_balance_with_amount_exception": "Você não tem o suficiente ${currency} para enviar o valor total de ${amount}",
1062 "tx_zero_fee_exception": "Não pode enviar transação com taxa 0. Tente aumentar a taxa ou verificar sua conexão para obter as estimativas mais recentes.",
@@ -1076,6 +1077,7 @@
1077 "upto": "até ${value}",
1078 "usb": "USB",
1079 "use": "Use PIN de ",
1080 + "use_blink_protection": "Use proteção contra piscar",
1081 "use_card_info_three": "Use o cartão digital online ou com métodos de pagamento sem contato.",
1082 "use_card_info_two": "Os fundos são convertidos para USD quando mantidos na conta pré-paga, não em moedas digitais.",
1083 "use_device_theme": "Use o tema do dispositivo",
res/values/strings_ru.arb
+2
@@ -1055,6 +1055,7 @@
1055 "tx_rejected_dust_output": "Транзакция отклоняется в соответствии с правилами сети, низкой выходной суммой (пыль). Пожалуйста, увеличьте сумму.",
1056 "tx_rejected_dust_output_send_all": "Транзакция отклоняется в соответствии с правилами сети, низкой выходной суммой (пыль). Пожалуйста, проверьте баланс монет, выбранных под контролем монет.",
1057 "tx_rejected_vout_negative": "Недостаточно баланс, чтобы оплатить плату этой транзакции. Пожалуйста, проверьте баланс монет под контролем монет.",
1058 + "tx_retry_message": "При обработке транзакции произошла ошибка. Пожалуйста, повторите транзакцию.",
1059 "tx_wrong_balance_exception": "У вас не хватает ${currency}, чтобы отправить эту сумму.",
1060 "tx_wrong_balance_with_amount_exception": "У вас недостаточно ${currency}, чтобы отправить общее количество ${amount}",
1061 "tx_zero_fee_exception": "Не может отправить транзакцию с платой 0. Попробуйте увеличить ставку или проверить соединение на наличие последних оценок.",
@@ -1075,6 +1076,7 @@
1076 "upto": "до ${value}",
1077 "usb": "USB",
1078 "use": "Использовать ",
1079 + "use_blink_protection": "Используйте защиту от моргания",
1080 "use_card_info_three": "Используйте цифровую карту онлайн или с помощью бесконтактных способов оплаты.",
1081 "use_card_info_two": "Средства конвертируются в доллары США, когда они хранятся на предоплаченном счете, а не в цифровых валютах.",
1082 "use_device_theme": "Используйте тему устройства",
res/values/strings_th.arb
+2
@@ -1054,6 +1054,7 @@
1054 "tx_rejected_dust_output": "การทำธุรกรรมถูกปฏิเสธโดยกฎเครือข่ายจำนวนเอาต์พุตต่ำ (ฝุ่น) โปรดเพิ่มจำนวนเงิน",
1055 "tx_rejected_dust_output_send_all": "การทำธุรกรรมถูกปฏิเสธโดยกฎเครือข่ายจำนวนเอาต์พุตต่ำ (ฝุ่น) โปรดตรวจสอบยอดคงเหลือของเหรียญที่เลือกภายใต้การควบคุมเหรียญ",
1056 "tx_rejected_vout_negative": "ยอดคงเหลือไม่เพียงพอที่จะจ่ายสำหรับค่าธรรมเนียมการทำธุรกรรมนี้ โปรดตรวจสอบยอดคงเหลือของเหรียญภายใต้การควบคุมเหรียญ",
1057 + "tx_retry_message": "เกิดข้อผิดพลาดขณะประมวลผลธุรกรรม กรุณาทำธุรกรรมอีกครั้ง",
1058 "tx_wrong_balance_exception": "คุณมีไม่เพียงพอ ${currency} ในการส่งจำนวนนี้",
1059 "tx_wrong_balance_with_amount_exception": "คุณมีไม่เพียงพอ ${currency} เพื่อส่งจำนวนทั้งหมดของ ${amount}",
1060 "tx_zero_fee_exception": "ไม่สามารถส่งธุรกรรมด้วยค่าธรรมเนียม 0 ลองเพิ่มอัตราหรือตรวจสอบการเชื่อมต่อของคุณสำหรับการประมาณการล่าสุด",
@@ -1074,6 +1075,7 @@
1075 "upto": "สูงสุด ${value}",
1076 "usb": "ยูเอสบี",
1077 "use": "สลับไปที่ ",
1078 + "use_blink_protection": "ใช้การป้องกันการกะพริบตา",
1079 "use_card_info_three": "ใช้บัตรดิจิตอลออนไลน์หรือผ่านวิธีการชำระเงินแบบไม่ต้องใช้บัตรกระดาษ",
1080 "use_card_info_two": "เงินจะถูกแปลงค่าเป็นดอลลาร์สหรัฐเมื่อถือไว้ในบัญชีสำรองเงิน ไม่ใช่สกุลเงินดิจิตอล",
1081 "use_device_theme": "ใช้ธีมอุปกรณ์",
res/values/strings_tl.arb
+2
@@ -1054,6 +1054,7 @@
1054 "tx_rejected_dust_output": "Ang transaksyon na tinanggihan ng mga patakaran sa network, mababang halaga ng output (dust). Mangyaring dagdagan ang halaga.",
1055 "tx_rejected_dust_output_send_all": "Ang transaksyon na tinanggihan ng mga patakaran sa network, mababang halaga ng output (dust). Mangyaring suriin ang balanse ng mga barya na napili sa ilalim ng Coin Control.",
1056 "tx_rejected_vout_negative": "Hindi sapat na balanse upang magbayad para sa mga fee ng transaksyon na ito. Mangyaring suriin ang balanse ng mga barya sa ilalim ng Coin Control.",
1057 + "tx_retry_message": "Nagkaroon ng error habang pinoproseso ang transaksyon. Pakisubukang muli ang transaksyon.",
1058 "tx_wrong_balance_exception": "Wala kang sapat na ${currency} upang maipadala ang halagang ito.",
1059 "tx_wrong_balance_with_amount_exception": "Wala kang sapat ${currency} upang ipadala ang kabuuang halaga ng ${amount}",
1060 "tx_zero_fee_exception": "Hindi maaaring magpadala ng transaksyon na may 0 fee. Subukan ang pagtaas ng rate o pagsuri sa iyong koneksyon para sa pinakabagong mga pagtatantya.",
@@ -1074,6 +1075,7 @@
1075 "upto": "hanggang sa ${value}",
1076 "usb": "USB",
1077 "use": "Lumipat sa ",
1078 + "use_blink_protection": "Gumamit ng Blink Protection",
1079 "use_card_info_three": "Gamitin ang digital card online o sa mga paraan ng pagbabayad na walang contact.",
1080 "use_card_info_two": "Ang mga pondo ay na-convert sa USD kapag hawak sa prepaid account, hindi sa mga digital na pera.",
1081 "use_device_theme": "Gumamit ng tema ng aparato",
res/values/strings_tr.arb
+2
@@ -1054,6 +1054,7 @@
1054 "tx_rejected_dust_output": "Ağ kurallarına göre reddedilen işlem, düşük çıktı miktarı (toz). Lütfen miktarı artırın.",
1055 "tx_rejected_dust_output_send_all": "Ağ kurallarına göre reddedilen işlem, düşük çıktı miktarı (toz). Lütfen madeni para kontrolü altında seçilen madeni para dengesini kontrol edin.",
1056 "tx_rejected_vout_negative": "Bu işlem ücretleri için ödeme yapmak için yeterli bakiye yok. Lütfen madeni para kontrolü altındaki madeni para dengesini kontrol edin.",
1057 + "tx_retry_message": "İşlem gerçekleştirilirken bir hata oluştu. Lütfen işlemi yeniden deneyin.",
1058 "tx_wrong_balance_exception": "Bu miktarı göndermek için yeterli ${currency} yok.",
1059 "tx_wrong_balance_with_amount_exception": "Yeterli değilsiniz ${currency} toplam ${amount} miktarını göndermek için",
1060 "tx_zero_fee_exception": "0 ücret ile işlem gönderilemez. En son tahminler için oranı artırmayı veya bağlantınızı kontrol etmeyi deneyin.",
@@ -1074,6 +1075,7 @@
1075 "upto": "Şu miktara kadar: ${value}",
1076 "usb": "USB",
1077 "use": "Şuna geç: ",
1078 + "use_blink_protection": "Göz Kırpma Korumasını Kullan",
1079 "use_card_info_three": "Dijital kartı çevrimiçi olarak veya temassız ödeme yöntemleriyle kullanın.",
1080 "use_card_info_two": "Paralar, dijital para birimlerinde değil, ön ödemeli hesapta tutulduğunda USD'ye dönüştürülür.",
1081 "use_device_theme": "Cihaz temasını kullanın",
res/values/strings_uk.arb
+2
@@ -1055,6 +1055,7 @@
1055 "tx_rejected_dust_output": "Транзакція відхилена за допомогою мережевих правил, низька кількість вихідної кількості (пил). Будь ласка, збільшуйте суму.",
1056 "tx_rejected_dust_output_send_all": "Транзакція відхилена за допомогою мережевих правил, низька кількість вихідної кількості (пил). Будь ласка, перевірте баланс монет, вибраних під контролем монет.",
1057 "tx_rejected_vout_negative": "Недостатньо балансу, щоб оплатити плату за цю транзакцію. Будь ласка, перевірте баланс монет під контролем монет.",
1058 + "tx_retry_message": "Під час обробки транзакції сталася помилка. Повторіть спробу транзакції.",
1059 "tx_wrong_balance_exception": "У вас недостатньо ${currency}, щоб надіслати цю суму.",
1060 "tx_wrong_balance_with_amount_exception": "У вас недостатньо ${currency}, щоб надіслати загальну кількість ${amount}",
1061 "tx_zero_fee_exception": "Не вдається відправити транзакцію з 0 платежами. Спробуйте збільшити ставку або перевірити з'єднання на останні оцінки.",
@@ -1075,6 +1076,7 @@
1076 "upto": "до ${value}",
1077 "usb": "USB",
1078 "use": "Використати ",
1079 + "use_blink_protection": "Використовуйте захист від моргання",
1080 "use_card_info_three": "Використовуйте цифрову картку онлайн або за допомогою безконтактних методів оплати.",
1081 "use_card_info_two": "Кошти конвертуються в долари США, якщо вони зберігаються на передплаченому рахунку, а не в цифрових валютах.",
1082 "use_device_theme": "Використовуйте тему пристрою",
res/values/strings_ur.arb
+2
@@ -1056,6 +1056,7 @@
1056 "tx_rejected_dust_output": "لین دین کو نیٹ ورک کے قواعد ، کم آؤٹ پٹ رقم (دھول) کے ذریعہ مسترد کردیا گیا۔ براہ کرم رقم میں اضافہ کریں۔",
1057 "tx_rejected_dust_output_send_all": "لین دین کو نیٹ ورک کے قواعد ، کم آؤٹ پٹ رقم (دھول) کے ذریعہ مسترد کردیا گیا۔ براہ کرم سکے کے کنٹرول میں منتخب کردہ سکے کا توازن چیک کریں۔",
1058 "tx_rejected_vout_negative": "اس لین دین کی فیسوں کی ادائیگی کے لئے کافی توازن نہیں ہے۔ براہ کرم سکے کے کنٹرول میں سکے کا توازن چیک کریں۔",
1059 + "tx_retry_message": "لین دین پر کارروائی کرتے وقت ایک خرابی پیش آگئی۔ براہ کرم ٹرانزیکشن کی دوبارہ کوشش کریں۔",
1060 "tx_wrong_balance_exception": "آپ کے پاس یہ رقم بھیجنے کے لئے کافی ${currency} نہیں ہے۔",
1061 "tx_wrong_balance_with_amount_exception": "آپ کے پاس کافی نہیں ہے ${currency} ${amount}",
1062 "tx_zero_fee_exception": "0 فیس کے ساتھ لین دین نہیں بھیج سکتا۔ شرح کو بڑھانے یا تازہ ترین تخمینے کے ل your اپنے کنکشن کی جانچ پڑتال کرنے کی کوشش کریں۔",
@@ -1076,6 +1077,7 @@
1077 "upto": "${value} تک",
1078 "usb": "یو ایس بی",
1079 "use": "تبدیل کرنا",
1080 + "use_blink_protection": "پلک جھپکنے کے تحفظ کا استعمال کریں",
1081 "use_card_info_three": "ڈیجیٹل کارڈ آن لائن یا کنٹیکٹ لیس ادائیگی کے طریقوں کے ساتھ استعمال کریں۔",
1082 "use_card_info_two": "رقوم کو امریکی ڈالر میں تبدیل کیا جاتا ہے جب پری پیڈ اکاؤنٹ میں رکھا جاتا ہے، ڈیجیٹل کرنسیوں میں نہیں۔",
1083 "use_device_theme": "ڈیوائس تھیم استعمال کریں",
res/values/strings_vi.arb
+2
@@ -1051,6 +1051,7 @@
1051 "tx_rejected_dust_output": "Giao dịch bị từ chối bởi quy tắc mạng, số tiền đầu ra thấp (dust). Vui lòng tăng số tiền.",
1052 "tx_rejected_dust_output_send_all": "Giao dịch bị từ chối bởi quy tắc mạng, số tiền đầu ra thấp (dust). Vui lòng kiểm tra số dư của các đồng tiền được chọn dưới Coin Control.",
1053 "tx_rejected_vout_negative": "Không đủ số dư để thanh toán phí giao dịch này. Vui lòng kiểm tra số dư của các đồng tiền dưới Coin Control.",
1054 + "tx_retry_message": "Đã xảy ra lỗi khi xử lý giao dịch. Vui lòng thử lại giao dịch.",
1055 "tx_wrong_balance_exception": "Bạn không có đủ ${currency} để gửi số tiền này.",
1056 "tx_wrong_balance_with_amount_exception": "Bạn không có đủ ${currency} để gửi tổng số tiền ${amount}",
1057 "tx_zero_fee_exception": "Không thể gửi giao dịch với phí bằng 0. Thử tăng tỷ lệ phí hoặc kiểm tra kết nối của bạn để biết ước lượng mới nhất.",
@@ -1071,6 +1072,7 @@
1072 "upto": "lên đến ${value}",
1073 "usb": "USB",
1074 "use": "Chuyển sang",
1075 + "use_blink_protection": "Sử dụng Bảo vệ chớp mắt",
1076 "use_card_info_three": "Sử dụng thẻ kỹ thuật số trực tuyến hoặc với các phương thức thanh toán không tiếp xúc.",
1077 "use_card_info_two": "Các khoản tiền được chuyển đổi thành USD khi chúng được giữ trong tài khoản trả trước, không phải trong các loại tiền kỹ thuật số.",
1078 "use_device_theme": "Sử dụng chủ đề thiết bị",
res/values/strings_yo.arb
+2
@@ -1055,6 +1055,7 @@
1055 "tx_rejected_dust_output": "Idunadura kọ nipasẹ awọn ofin nẹtiwọọki, iye ti o wuwe kekere (eruku). Jọwọ mu iye naa pọ si.",
1056 "tx_rejected_dust_output_send_all": "Idunadura kọ nipasẹ awọn ofin nẹtiwọọki, iye ti o wuwe kekere (eruku). Jọwọ ṣayẹwo dọgbadọgba ti awọn owo ti a yan labẹ iṣakoso owo.",
1057 "tx_rejected_vout_negative": "Iwontunws.funfun ti o to lati sanwo fun awọn idiyele iṣowo yii. Jọwọ ṣayẹwo iwọntunwọnsi ti awọn owo labẹ iṣakoso owo.",
1058 + "tx_retry_message": "Aṣiṣe waye lakoko ṣiṣe iṣowo naa. Jọwọ tun idunadura naa gbiyanju.",
1059 "tx_wrong_balance_exception": "O ko ni to ${currency} lati firanṣẹ iye yii.",
1060 "tx_wrong_balance_with_amount_exception": "O ko ni to ${currency} Lati firanṣẹ lapapọ iye ${amount}",
1061 "tx_zero_fee_exception": "Ko le firanṣẹ idunadura pẹlu ọya 0. Gbiyanju jijẹ oṣuwọn tabi ṣayẹwo asopọ rẹ fun awọn iṣiro tuntun.",
@@ -1075,6 +1076,7 @@
1076 "upto": "kò tóbi ju ${value}",
1077 "usb": "USB",
1078 "use": "Lo",
1079 + "use_blink_protection": "Lo Idaabobo Seju",
1080 "use_card_info_three": "Ẹ lo káàdí ayélujára lórí wẹ́ẹ̀bù tàbí ẹ lò ó lórí àwọn ẹ̀rọ̀ ìrajà tíwọn kò kò.",
1081 "use_card_info_two": "A pààrọ̀ owó sí owó Amẹ́ríkà tó bá wà nínú àkanti t'á ti fikún tẹ́lẹ̀tẹ́lẹ̀. A kò kó owó náà nínú owó ayélujára.",
1082 "use_device_theme": "Lo akori ẹrọ",
res/values/strings_zh.arb
+2
@@ -1054,6 +1054,7 @@
1054 "tx_rejected_dust_output": "交易被网络规则拒绝,输出金额过低(尘额)。请增加金额。",
1055 "tx_rejected_dust_output_send_all": "交易被网络规则拒绝,低输出量(灰尘)拒绝。请检查在硬币控制下选择的硬币的余额。",
1056 "tx_rejected_vout_negative": "没有足够的余额来支付此交易费用。请检查硬币控制下的硬币余额。",
1057 + "tx_retry_message": "处理交易时发生错误。请重试交易。",
1058 "tx_wrong_balance_exception": "您没有足够的${currency}来发送此金额。",
1059 "tx_wrong_balance_with_amount_exception": "您没有足够的${currency} ${amount}",
1060 "tx_zero_fee_exception": "无法以0手续费发送交易。尝试提高速率或检查连接以获取最新估计。",
@@ -1074,6 +1075,7 @@
1075 "upto": "最高 ${value}",
1076 "usb": "USB",
1077 "use": "切换使用",
1078 + "use_blink_protection": "使用眨眼保护",
1079 "use_card_info_three": "在线使用电子卡或使用非接触式支付方式。",
1080 "use_card_info_two": "预付账户中的资金转换为美元,不是数字货币。",
1081 "use_device_theme": "使用设备主题",
scripts/android/pubspec_gen.sh
+1 -1
@@ -10,7 +10,7 @@ case $APP_ANDROID_TYPE in
10 CONFIG_ARGS="--monero"
11 ;;
12 $CAKEWALLET)
13 - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred --dogecoin --base" # --arbitrum
13 + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred --dogecoin --base --arbitrum"
14 ;;
15 esac
16
scripts/ios/app_config.sh
+1 -1
@@ -31,7 +31,7 @@ case $APP_IOS_TYPE in
31 ;;
32
33 $CAKEWALLET)
34 - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred --dogecoin --base" # --arbitrum
34 + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred --dogecoin --base --arbitrum"
35 ;;
36 esac
37
scripts/linux/app_config.sh
+1 -1
@@ -13,7 +13,7 @@ CONFIG_ARGS=""
13
14 case $APP_LINUX_TYPE in
15 $CAKEWALLET)
16 - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --dogecoin --base --excludeFlutterSecureStorage";; # --arbitrum
16 + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --dogecoin --base --arbitrum --excludeFlutterSecureStorage";;
17 esac
18
19 cp -rf pubspec_description.yaml pubspec.yaml
scripts/macos/app_config.sh
+1 -1
@@ -36,7 +36,7 @@ case $APP_MACOS_TYPE in
36 $MONERO_COM)
37 CONFIG_ARGS="--monero";;
38 $CAKEWALLET)
39 - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --dogecoin --base";; # --arbitrum
39 + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --dogecoin --base --arbitrum";;
40 esac
41
42 cp -rf pubspec_description.yaml pubspec.yaml
tool/configure.dart
+169 -478
@@ -2,18 +2,15 @@ import 'dart:io';
2
3 const bitcoinOutputPath = 'lib/bitcoin/bitcoin.dart';
4 const moneroOutputPath = 'lib/monero/monero.dart';
5 -const ethereumOutputPath = 'lib/ethereum/ethereum.dart';
5 const bitcoinCashOutputPath = 'lib/bitcoin_cash/bitcoin_cash.dart';
6 const nanoOutputPath = 'lib/nano/nano.dart';
8 -const polygonOutputPath = 'lib/polygon/polygon.dart';
7 const solanaOutputPath = 'lib/solana/solana.dart';
8 const tronOutputPath = 'lib/tron/tron.dart';
9 const wowneroOutputPath = 'lib/wownero/wownero.dart';
10 const zanoOutputPath = 'lib/zano/zano.dart';
11 const decredOutputPath = 'lib/decred/decred.dart';
12 const dogecoinOutputPath = 'lib/dogecoin/dogecoin.dart';
15 -const baseOutputPath = 'lib/base/base.dart';
16 -const arbitrumOutputPath = 'lib/arbitrum/arbitrum.dart';
13 +const evmOutputPath = 'lib/evm/evm.dart';
14 const walletTypesPath = 'lib/wallet_types.g.dart';
15 const secureStoragePath = 'lib/core/secure_storage.dart';
16 const pubspecDefaultPath = 'pubspec_default.yaml';
@@ -36,14 +33,13 @@ Future<void> main(List<String> args) async {
33 final hasDogecoin = args.contains('${prefix}dogecoin');
34 final hasBase = args.contains('${prefix}base');
35 final hasArbitrum = args.contains('${prefix}arbitrum');
36 + final hasEVM = hasEthereum || hasPolygon || hasBase || hasArbitrum;
37 final excludeFlutterSecureStorage = args.contains('${prefix}excludeFlutterSecureStorage');
38
39 await generateBitcoin(hasBitcoin);
40 await generateMonero(hasMonero);
43 - await generateEthereum(hasEthereum);
41 await generateBitcoinCash(hasBitcoinCash);
42 await generateNano(hasNano);
46 - await generatePolygon(hasPolygon);
43 await generateSolana(hasSolana);
44 await generateTron(hasTron);
45 await generateWownero(hasWownero);
@@ -51,8 +47,7 @@ Future<void> main(List<String> args) async {
47 // await generateBanano(hasEthereum);
48 await generateDecred(hasDecred);
49 await generateDogecoin(hasDogecoin);
54 - await generateBase(hasBase);
55 - await generateArbitrum(hasArbitrum);
50 + await generateEVM(hasEVM);
51
52 await generatePubspec(
53 hasMonero: hasMonero,
@@ -698,262 +693,6 @@ abstract class WowneroAccountList {
693 await outputFile.writeAsString(output);
694 }
695
701 -Future<void> generateEthereum(bool hasImplementation) async {
702 - final outputFile = File(ethereumOutputPath);
703 - const ethereumCommonHeaders = """
704 -import 'package:cake_wallet/view_model/send/output.dart';
705 -import 'package:cw_core/crypto_currency.dart';
706 -import 'package:cw_core/erc20_token.dart';
707 -import 'package:cw_core/hardware/hardware_account_data.dart';
708 -import 'package:cw_core/hardware/hardware_wallet_service.dart';
709 -import 'package:cw_core/output_info.dart';
710 -import 'package:cw_core/pending_transaction.dart';
711 -import 'package:cw_core/transaction_info.dart';
712 -import 'package:cw_core/transaction_priority.dart';
713 -import 'package:cw_core/wallet_base.dart';
714 -import 'package:cw_core/wallet_credentials.dart';
715 -import 'package:cw_core/wallet_info.dart';
716 -import 'package:cw_core/wallet_service.dart';
717 -import 'package:cw_core/utils/print_verbose.dart';
718 -import 'package:hive/hive.dart';
719 -import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
720 -import 'package:bitbox_flutter/bitbox_flutter.dart' as bitbox;
721 -import 'package:trezor_connect/trezor_connect.dart' as trezor;
722 -import 'package:web3dart/web3dart.dart';
723 -
724 -""";
725 - const ethereumCWHeaders = """
726 -import 'package:cw_evm/evm_chain_formatter.dart';
727 -import 'package:cw_evm/evm_chain_mnemonics.dart';
728 -import 'package:cw_evm/evm_chain_transaction_credentials.dart';
729 -import 'package:cw_evm/evm_chain_transaction_info.dart';
730 -import 'package:cw_evm/evm_chain_transaction_priority.dart';
731 -import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
732 -import 'package:cw_evm/hardware/evm_chain_bitbox_credentials.dart';
733 -import 'package:cw_evm/hardware/evm_chain_ledger_credentials.dart';
734 -import 'package:cw_evm/hardware/evm_chain_trezor_credentials.dart';
735 -import 'package:cw_evm/evm_chain_wallet.dart';
736 -import 'package:cw_evm/hardware/evm_chain_bitbox_service.dart';
737 -import 'package:cw_evm/hardware/evm_chain_ledger_service.dart';
738 -import 'package:cw_evm/hardware/evm_chain_trezor_service.dart';
739 -
740 -import 'package:cw_ethereum/ethereum_client.dart';
741 -import 'package:cw_ethereum/ethereum_wallet.dart';
742 -import 'package:cw_ethereum/ethereum_wallet_service.dart';
743 -import 'package:cw_ethereum/default_ethereum_erc20_tokens.dart';
744 -import 'package:cw_ethereum/deuro/deuro_savings.dart';
745 -
746 -import 'package:eth_sig_util/util/utils.dart';
747 -
748 -""";
749 - const ethereumCwPart = "part 'cw_ethereum.dart';";
750 - const ethereumContent = """
751 -abstract class Ethereum {
752 - List<String> getEthereumWordList(String language);
753 - WalletService createEthereumWalletService(bool isDirect);
754 - WalletCredentials createEthereumNewWalletCredentials({required String name, WalletInfo? walletInfo, String? password, String? mnemonic, String? passphrase});
755 - WalletCredentials createEthereumRestoreWalletFromSeedCredentials({required String name, required String mnemonic, required String password, String? passphrase});
756 - WalletCredentials createEthereumRestoreWalletFromPrivateKey({required String name, required String privateKey, required String password});
757 - WalletCredentials createEthereumHardwareWalletCredentials({required String name, required HardwareAccountData hwAccountData, WalletInfo? walletInfo});
758 - String getAddress(WalletBase wallet);
759 - String getPrivateKey(WalletBase wallet);
760 - String getPublicKey(WalletBase wallet);
761 - TransactionPriority getDefaultTransactionPriority();
762 - TransactionPriority getEthereumTransactionPrioritySlow();
763 - List<TransactionPriority> getTransactionPriorities();
764 - TransactionPriority deserializeEthereumTransactionPriority(int raw);
765 -
766 - Object createEthereumTransactionCredentials(
767 - List<Output> outputs, {
768 - required TransactionPriority priority,
769 - required CryptoCurrency currency,
770 - int? feeRate,
771 - });
772 -
773 - Object createEthereumTransactionCredentialsRaw(
774 - List<OutputInfo> outputs, {
775 - TransactionPriority? priority,
776 - required CryptoCurrency currency,
777 - required int feeRate,
778 - });
779 -
780 - int formatterEthereumParseAmount(String amount);
781 - double formatterEthereumAmountToDouble({TransactionInfo? transaction, BigInt? amount, int exponent = 18});
782 - List<Erc20Token> getERC20Currencies(WalletBase wallet);
783 - Future<void> addErc20Token(WalletBase wallet, CryptoCurrency token);
784 - Future<void> deleteErc20Token(WalletBase wallet, CryptoCurrency token);
785 - Future<void> removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token);
786 - Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress);
787 -
788 - CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction);
789 - void updateEtherscanUsageState(WalletBase wallet, bool isEnabled);
790 - Web3Client? getWeb3Client(WalletBase wallet);
791 - String getTokenAddress(CryptoCurrency asset);
792 -
793 - Future<bool> isApprovalRequired(WalletBase wallet, String tokenContract, String spender, BigInt requiredAmount);
794 - Future<PendingTransaction> createTokenApproval(WalletBase wallet, BigInt amount, String spender, CryptoCurrency token, TransactionPriority priority);
795 - Future<PendingTransaction> createRawCallDataTransaction(WalletBase wallet, String to, String dataHex, BigInt valueWei, TransactionPriority priority);
796 -
797 - Future<BigInt> getDEuroSavingsBalance(WalletBase wallet);
798 - Future<BigInt> getDEuroAccruedInterest(WalletBase wallet);
799 - Future<BigInt> getDEuroInterestRate(WalletBase wallet);
800 - Future<BigInt> getDEuroSavingsApproved(WalletBase wallet);
801 - Future<PendingTransaction> addDEuroSaving(WalletBase wallet, BigInt amount, TransactionPriority priority);
802 - Future<PendingTransaction> removeDEuroSaving(WalletBase wallet, BigInt amount, TransactionPriority priority);
803 - Future<PendingTransaction> reinvestDEuroInterest(WalletBase wallet, TransactionPriority priority);
804 - Future<PendingTransaction> enableDEuroSaving(WalletBase wallet, TransactionPriority priority);
805 -
806 - Future<void> setHardwareWalletService(WalletBase wallet, HardwareWalletService service);
807 - HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection);
808 - HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager);
809 - HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorConnect connect);
810 - List<String> getDefaultTokenContractAddresses();
811 - bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress);
812 - String? getEthereumNativeEstimatedFee(WalletBase wallet);
813 - String? getEthereumERC20EstimatedFee(WalletBase wallet);
814 -}
815 - """;
816 -
817 - const ethereumEmptyDefinition = 'Ethereum? ethereum;\n';
818 - const ethereumCWDefinition = 'Ethereum? ethereum = CWEthereum();\n';
819 -
820 - final output = '$ethereumCommonHeaders\n' +
821 - (hasImplementation ? '$ethereumCWHeaders\n' : '\n') +
822 - (hasImplementation ? '$ethereumCwPart\n\n' : '\n') +
823 - (hasImplementation ? ethereumCWDefinition : ethereumEmptyDefinition) +
824 - '\n' +
825 - ethereumContent;
826 -
827 - if (outputFile.existsSync()) {
828 - await outputFile.delete();
829 - }
830 -
831 - await outputFile.writeAsString(output);
832 -}
833 -
834 -Future<void> generatePolygon(bool hasImplementation) async {
835 - final outputFile = File(polygonOutputPath);
836 - const polygonCommonHeaders = """
837 -import 'package:cake_wallet/view_model/send/output.dart';
838 -import 'package:cw_core/crypto_currency.dart';
839 -import 'package:cw_core/erc20_token.dart';
840 -import 'package:cw_core/hardware/hardware_account_data.dart';
841 -import 'package:cw_core/hardware/hardware_wallet_service.dart';
842 -import 'package:cw_core/output_info.dart';
843 -import 'package:cw_core/pending_transaction.dart';
844 -import 'package:cw_core/transaction_info.dart';
845 -import 'package:cw_core/transaction_priority.dart';
846 -import 'package:cw_core/wallet_base.dart';
847 -import 'package:cw_core/wallet_credentials.dart';
848 -import 'package:cw_core/wallet_info.dart';
849 -import 'package:cw_core/wallet_service.dart';
850 -import 'package:cw_core/utils/print_verbose.dart';
851 -import 'package:hive/hive.dart';
852 -import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
853 -import 'package:bitbox_flutter/bitbox_flutter.dart' as bitbox;
854 -import 'package:trezor_connect/trezor_connect.dart' as trezor;
855 -import 'package:web3dart/web3dart.dart';
856 -
857 -""";
858 - const polygonCWHeaders = """
859 -import 'package:cw_evm/evm_chain_formatter.dart';
860 -import 'package:cw_evm/evm_chain_mnemonics.dart';
861 -import 'package:cw_evm/evm_chain_transaction_credentials.dart';
862 -import 'package:cw_evm/evm_chain_transaction_info.dart';
863 -import 'package:cw_evm/evm_chain_transaction_priority.dart';
864 -import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
865 -import 'package:cw_evm/hardware/evm_chain_bitbox_credentials.dart';
866 -import 'package:cw_evm/hardware/evm_chain_ledger_credentials.dart';
867 -import 'package:cw_evm/hardware/evm_chain_trezor_credentials.dart';
868 -import 'package:cw_evm/evm_chain_wallet.dart';
869 -import 'package:cw_evm/hardware/evm_chain_bitbox_service.dart';
870 -import 'package:cw_evm/hardware/evm_chain_ledger_service.dart';
871 -import 'package:cw_evm/hardware/evm_chain_trezor_service.dart';
872 -
873 -import 'package:cw_polygon/polygon_client.dart';
874 -import 'package:cw_polygon/polygon_wallet.dart';
875 -import 'package:cw_polygon/polygon_wallet_service.dart';
876 -import 'package:cw_polygon/default_polygon_erc20_tokens.dart';
877 -
878 -import 'package:eth_sig_util/util/utils.dart';
879 -
880 -""";
881 - const polygonCwPart = "part 'cw_polygon.dart';";
882 - const polygonContent = """
883 -abstract class Polygon {
884 - List<String> getPolygonWordList(String language);
885 - WalletService createPolygonWalletService(bool isDirect);
886 - WalletCredentials createPolygonNewWalletCredentials({required String name, WalletInfo? walletInfo, String? password, String? mnemonic, String? passphrase});
887 - WalletCredentials createPolygonRestoreWalletFromSeedCredentials({required String name, required String mnemonic, required String password, String? passphrase});
888 - WalletCredentials createPolygonRestoreWalletFromPrivateKey({required String name, required String privateKey, required String password});
889 - WalletCredentials createPolygonHardwareWalletCredentials({required String name, required HardwareAccountData hwAccountData, WalletInfo? walletInfo});
890 - String getAddress(WalletBase wallet);
891 - String getPrivateKey(WalletBase wallet);
892 - String getPublicKey(WalletBase wallet);
893 - TransactionPriority getDefaultTransactionPriority();
894 - TransactionPriority getPolygonTransactionPrioritySlow();
895 - List<TransactionPriority> getTransactionPriorities();
896 - TransactionPriority deserializePolygonTransactionPriority(int raw);
897 -
898 - Object createPolygonTransactionCredentials(
899 - List<Output> outputs, {
900 - required TransactionPriority priority,
901 - required CryptoCurrency currency,
902 - int? feeRate,
903 - });
904 -
905 - Object createPolygonTransactionCredentialsRaw(
906 - List<OutputInfo> outputs, {
907 - TransactionPriority? priority,
908 - required CryptoCurrency currency,
909 - required int feeRate,
910 - });
911 -
912 - int formatterPolygonParseAmount(String amount);
913 - double formatterPolygonAmountToDouble({TransactionInfo? transaction, BigInt? amount, int exponent = 18});
914 - List<Erc20Token> getERC20Currencies(WalletBase wallet);
915 - Future<void> addErc20Token(WalletBase wallet, CryptoCurrency token);
916 - Future<void> deleteErc20Token(WalletBase wallet, CryptoCurrency token);
917 - Future<void> removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token);
918 - Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress);
919 -
920 - Future<bool> isApprovalRequired(WalletBase wallet, String tokenContract, String spender, BigInt requiredAmount);
921 - Future<PendingTransaction> createTokenApproval(WalletBase wallet, BigInt amount, String spender, CryptoCurrency token, TransactionPriority priority);
922 - Future<PendingTransaction> createRawCallDataTransaction(WalletBase wallet, String to, String dataHex, BigInt valueWei, TransactionPriority priority);
923 -
924 - CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction);
925 - void updatePolygonScanUsageState(WalletBase wallet, bool isEnabled);
926 - Web3Client? getWeb3Client(WalletBase wallet);
927 - String getTokenAddress(CryptoCurrency asset);
928 -
929 - Future<void> setHardwareWalletService(WalletBase wallet, HardwareWalletService service);
930 - HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection);
931 - HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager);
932 - HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorConnect connect);
933 - List<String> getDefaultTokenContractAddresses();
934 - bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress);
935 - String? getPolygonNativeEstimatedFee(WalletBase wallet);
936 - String? getPolygonERC20EstimatedFee(WalletBase wallet);
937 -}
938 - """;
939 -
940 - const polygonEmptyDefinition = 'Polygon? polygon;\n';
941 - const polygonCWDefinition = 'Polygon? polygon = CWPolygon();\n';
942 -
943 - final output = '$polygonCommonHeaders\n' +
944 - (hasImplementation ? '$polygonCWHeaders\n' : '\n') +
945 - (hasImplementation ? '$polygonCwPart\n\n' : '\n') +
946 - (hasImplementation ? polygonCWDefinition : polygonEmptyDefinition) +
947 - '\n' +
948 - polygonContent;
949 -
950 - if (outputFile.existsSync()) {
951 - await outputFile.delete();
952 - }
953 -
954 - await outputFile.writeAsString(output);
955 -}
956 -
696 Future<void> generateBitcoinCash(bool hasImplementation) async {
697 final outputFile = File(bitcoinCashOutputPath);
698 const bitcoinCashCommonHeaders = """
@@ -1520,9 +1259,10 @@ abstract class DogeCoin {
1259 await outputFile.writeAsString(output);
1260 }
1261
1523 -Future<void> generateBase(bool hasImplementation) async {
1524 - final outputFile = File(baseOutputPath);
1525 - const baseCommonHeaders = """
1262 +Future<void> generateEVM(bool hasImplementation) async {
1263 + final outputFile = File(evmOutputPath);
1264 + const evmCommonHeaders = """
1265 +import 'package:cake_wallet/core/utilities.dart';
1266 import 'package:cake_wallet/view_model/send/output.dart';
1267 import 'package:cw_core/crypto_currency.dart';
1268 import 'package:cw_core/erc20_token.dart';
@@ -1536,237 +1276,220 @@ import 'package:cw_core/wallet_base.dart';
1276 import 'package:cw_core/wallet_credentials.dart';
1277 import 'package:cw_core/wallet_info.dart';
1278 import 'package:cw_core/wallet_service.dart';
1539 -import 'package:hive/hive.dart';
1279 +import 'package:cw_core/wallet_type.dart';
1280 +import 'package:cw_core/node.dart';
1281 import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
1282 import 'package:bitbox_flutter/bitbox_flutter.dart' as bitbox;
1283 +import 'package:trezor_connect/trezor_connect.dart' as trezor;
1284 import 'package:web3dart/web3dart.dart';
1285
1286 """;
1545 - const baseCWHeaders = """
1546 -import 'package:cw_evm/evm_chain_formatter.dart';
1287 + const evmCWHeaders = """
1288 +import 'package:cw_evm/utils/evm_chain_formatter.dart';
1289 import 'package:cw_evm/evm_chain_mnemonics.dart';
1290 +import 'package:cw_evm/evm_chain_registry.dart';
1291 import 'package:cw_evm/evm_chain_transaction_credentials.dart';
1292 import 'package:cw_evm/evm_chain_transaction_info.dart';
1293 import 'package:cw_evm/evm_chain_transaction_priority.dart';
1551 -import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
1552 -import 'package:cw_evm/hardware/evm_chain_ledger_credentials.dart';
1294 import 'package:cw_evm/hardware/evm_chain_bitbox_credentials.dart';
1295 +import 'package:cw_evm/hardware/evm_chain_ledger_credentials.dart';
1296 +import 'package:cw_evm/hardware/evm_chain_trezor_credentials.dart';
1297 import 'package:cw_evm/evm_chain_wallet.dart';
1298 import 'package:cw_evm/hardware/evm_chain_bitbox_service.dart';
1299 import 'package:cw_evm/hardware/evm_chain_ledger_service.dart';
1557 -
1558 -import 'package:cw_base/base_client.dart';
1559 -import 'package:cw_base/base_wallet.dart';
1560 -import 'package:cw_base/base_wallet_service.dart';
1561 -import 'package:cw_base/default_base_erc20_tokens.dart';
1300 +import 'package:cw_evm/hardware/evm_chain_trezor_service.dart';
1301 +import 'package:cw_evm/evm_chain_wallet_service.dart';
1302 +import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
1303 +import 'package:cw_evm/utils/evm_chain_utils.dart';
1304 +import 'package:cw_evm/evm_chain_default_tokens.dart';
1305 +import 'package:cw_evm/deuro/deuro_savings.dart';
1306 import 'package:eth_sig_util/util/utils.dart';
1307
1308 """;
1565 - const baseCwPart = "part 'cw_base.dart';";
1566 - const baseContent = """
1567 -abstract class Base {
1568 - List<String> getBaseWordList(String language);
1569 - WalletService createBaseWalletService(bool isDirect);
1570 - WalletCredentials createBaseNewWalletCredentials(
1571 - {required String name,
1572 - WalletInfo? walletInfo,
1573 - String? password,
1574 - String? mnemonic,
1575 - String? passphrase});
1576 - WalletCredentials createBaseRestoreWalletFromSeedCredentials(
1577 - {required String name,
1578 - required String mnemonic,
1579 - required String password,
1580 - String? passphrase});
1581 - WalletCredentials createBaseRestoreWalletFromPrivateKey(
1582 - {required String name, required String privateKey, required String password});
1583 - WalletCredentials createBaseHardwareWalletCredentials(
1584 - {required String name, required HardwareAccountData hwAccountData, WalletInfo? walletInfo});
1309 + const evmCwPart = "part 'cw_evm.dart';";
1310 + const evmContent = """
1311 +/// Unified abstract class for all EVM chains
1312 +///
1313 +/// This replaces separate proxy classes (Ethereum, Polygon, Base, Arbitrum)
1314 +/// with a single unified interface that works for all EVM chains.
1315 +/// Methods take WalletType parameter to determine chain-specific behavior.
1316 +abstract class EVM {
1317 + List<String> getEVMWordList(String language);
1318 +
1319 + /// Create unified wallet service for any EVM chain
1320 + WalletService createEVMWalletService(WalletType walletType, bool isDirect);
1321 +
1322 + /// Generic credential creation - uses WalletType
1323 + WalletCredentials createEVMNewWalletCredentials({
1324 + required String name,
1325 + WalletInfo? walletInfo,
1326 + String? password,
1327 + String? mnemonic,
1328 + String? passphrase,
1329 + });
1330 +
1331 + WalletCredentials createEVMRestoreWalletFromSeedCredentials({
1332 + required String name,
1333 + required String mnemonic,
1334 + required String password,
1335 + String? passphrase,
1336 + });
1337 +
1338 + WalletCredentials createEVMRestoreWalletFromPrivateKey({
1339 + required String name,
1340 + required String privateKey,
1341 + required String password,
1342 + });
1343 +
1344 + WalletCredentials createEVMHardwareWalletCredentials({
1345 + required String name,
1346 + required HardwareAccountData hwAccountData,
1347 + WalletInfo? walletInfo,
1348 + });
1349 +
1350 + // Generic methods that work for all EVM chains
1351 String getAddress(WalletBase wallet);
1352 String getPrivateKey(WalletBase wallet);
1353 String getPublicKey(WalletBase wallet);
1354 TransactionPriority getDefaultTransactionPriority();
1589 - TransactionPriority getBaseTransactionPrioritySlow();
1355 + TransactionPriority getEVMTransactionPrioritySlow();
1356 List<TransactionPriority> getTransactionPriorities();
1591 - TransactionPriority deserializeBaseTransactionPriority(int raw);
1592 -
1593 - Object createBaseTransactionCredentials(
1357 + TransactionPriority deserializeEVMTransactionPriority(int raw);
1358 +
1359 + Object createEVMTransactionCredentials(
1360 List<Output> outputs, {
1595 - required TransactionPriority priority,
1361 + required TransactionPriority? priority,
1362 required CryptoCurrency currency,
1363 int? feeRate,
1364 + bool useBlinkProtection = true,
1365 });
1599 -
1600 - Object createBaseTransactionCredentialsRaw(
1366 +
1367 + Object createEVMTransactionCredentialsRaw(
1368 List<OutputInfo> outputs, {
1369 TransactionPriority? priority,
1370 required CryptoCurrency currency,
1371 required int feeRate,
1372 + bool useBlinkProtection = true,
1373 });
1606 -
1607 - int formatterBaseParseAmount(String amount);
1608 - double formatterBaseAmountToDouble(
1609 - {TransactionInfo? transaction, BigInt? amount, int exponent = 18});
1374 +
1375 + int formatterEVMParseAmount(String amount);
1376 + double formatterEVMAmountToDouble({
1377 + TransactionInfo? transaction,
1378 + BigInt? amount,
1379 + int exponent = 18,
1380 + });
1381 +
1382 List<Erc20Token> getERC20Currencies(WalletBase wallet);
1383 Future<void> addErc20Token(WalletBase wallet, CryptoCurrency token);
1384 Future<void> deleteErc20Token(WalletBase wallet, CryptoCurrency token);
1385 Future<void> removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token);
1386 Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress);
1615 -
1616 - Future<bool> isApprovalRequired(WalletBase wallet, String tokenContract, String spender, BigInt requiredAmount);
1617 - Future<PendingTransaction> createTokenApproval(WalletBase wallet, BigInt amount, String spender,
1618 - CryptoCurrency token, TransactionPriority priority);
1619 - Future<PendingTransaction> createRawCallDataTransaction(WalletBase wallet, String to, String dataHex, BigInt valueWei, TransactionPriority priority);
1620 -
1387 +
1388 CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction);
1622 - void updateBaseScanUsageState(WalletBase wallet, bool isEnabled);
1389 + void updateScanProviderUsageState(WalletBase wallet, bool isEnabled);
1390 Web3Client? getWeb3Client(WalletBase wallet);
1391 String getTokenAddress(CryptoCurrency asset);
1625 -
1392 +
1393 + Future<bool> isApprovalRequired(
1394 + WalletBase wallet,
1395 + String tokenContract,
1396 + String spender,
1397 + BigInt requiredAmount,
1398 + );
1399 +
1400 + Future<PendingTransaction> createTokenApproval(
1401 + WalletBase wallet,
1402 + BigInt amount,
1403 + String spender,
1404 + CryptoCurrency token,
1405 + TransactionPriority? priority,
1406 + {bool useBlinkProtection = true}
1407 + );
1408 +
1409 + Future<PendingTransaction> createRawCallDataTransaction(
1410 + WalletBase wallet,
1411 + String to,
1412 + String dataHex,
1413 + BigInt valueWei,
1414 + TransactionPriority? priority,
1415 + {bool useBlinkProtection = true}
1416 + );
1417 +
1418 + // Hardware wallet methods
1419 Future<void> setHardwareWalletService(WalletBase wallet, HardwareWalletService service);
1420 HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection);
1421 HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager);
1629 - List<String> getDefaultTokenContractAddresses();
1422 + HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorConnect connect);
1423 +
1424 + // Utility methods
1425 + List<String> getDefaultTokenContractAddresses(WalletBase wallet);
1426 bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress);
1631 - String? getBaseNativeEstimatedFee(WalletBase wallet);
1632 - String? getBaseERC20EstimatedFee(WalletBase wallet);
1633 -}
1634 - """;
1635 -
1636 - const baseEmptyDefinition = 'Base? base;\n';
1637 - const baseCWDefinition = 'Base? base = CWBase();\n';
1638 -
1639 - final output = '$baseCommonHeaders\n' +
1640 - (hasImplementation ? '$baseCWHeaders\n' : '\n') +
1641 - (hasImplementation ? '$baseCwPart\n\n' : '\n') +
1642 - (hasImplementation ? baseCWDefinition : baseEmptyDefinition) +
1643 - '\n' +
1644 - baseContent;
1645 -
1646 - if (outputFile.existsSync()) {
1647 - await outputFile.delete();
1648 - }
1427 + String? getEVMNativeEstimatedFee(WalletBase wallet);
1428 + String? getEVMERC20EstimatedFee(WalletBase wallet);
1429 +
1430 + // Chain-specific integrations (optional, can be null for non-Ethereum chains)
1431 + Future<BigInt>? getDEuroSavingsBalance(WalletBase wallet) => null;
1432 + Future<BigInt>? getDEuroAccruedInterest(WalletBase wallet) => null;
1433 + Future<BigInt>? getDEuroInterestRate(WalletBase wallet) => null;
1434 + Future<BigInt>? getDEuroSavingsApproved(WalletBase wallet) => null;
1435 + Future<PendingTransaction>? addDEuroSaving(WalletBase wallet, BigInt amount, TransactionPriority priority) => null;
1436 + Future<PendingTransaction>? removeDEuroSaving(WalletBase wallet, BigInt amount, TransactionPriority priority) => null;
1437 + Future<PendingTransaction>? reinvestDEuroInterest(WalletBase wallet, TransactionPriority priority) => null;
1438 + Future<PendingTransaction>? enableDEuroSaving(WalletBase wallet, TransactionPriority priority) => null;
1439 +
1440 + // Registry helper methods (for backward compatibility helpers)
1441 + int getChainIdByWalletType(WalletType walletType);
1442 + String getChainNameByWalletType(WalletType walletType);
1443 + String getTokenNameByWalletType(WalletType walletType);
1444 + String getCaip2ByChainId(int chainId);
1445 + int? getChainIdByTag(String tag);
1446 + int? getChainIdByTitle(String title);
1447 + WalletType? getWalletTypeByChainId(int chainId);
1448 + String getChainNameByChainId(int chainId);
1449 + String getTokenNameByChainId(int chainId);
1450 +
1451 + // Chain selection methods
1452 + List<ChainInfo> getAllChains();
1453 + ChainInfo? getCurrentChain(WalletBase wallet);
1454
1650 - await outputFile.writeAsString(output);
1455 + int? getSelectedChainId(WalletBase wallet);
1456 + Future<void> selectChain(WalletBase wallet, int chainId, {required Node node});
1457 +
1458 + String? getExplorerUrlForChainId(int chainId, {bool showProtocol = true});
1459 +
1460 + bool hasPriorityFee(int chainId);
1461 }
1462
1653 -Future<void> generateArbitrum(bool hasImplementation) async {
1654 - final outputFile = File(arbitrumOutputPath);
1655 - const arbitrumCommonHeaders = """
1656 -import 'package:cake_wallet/view_model/send/output.dart';
1657 -import 'package:cw_core/crypto_currency.dart';
1658 -import 'package:cw_core/erc20_token.dart';
1659 -import 'package:cw_core/hardware/hardware_account_data.dart';
1660 -import 'package:cw_core/hardware/hardware_wallet_service.dart';
1661 -import 'package:cw_core/output_info.dart';
1662 -import 'package:cw_core/pending_transaction.dart';
1663 -import 'package:cw_core/transaction_info.dart';
1664 -import 'package:cw_core/transaction_priority.dart';
1665 -import 'package:cw_core/wallet_base.dart';
1666 -import 'package:cw_core/wallet_credentials.dart';
1667 -import 'package:cw_core/wallet_info.dart';
1668 -import 'package:cw_core/wallet_service.dart';
1669 -import 'package:hive/hive.dart';
1670 -import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
1671 -import 'package:bitbox_flutter/bitbox_flutter.dart' as bitbox;
1672 -import 'package:web3dart/web3dart.dart';
1673 -
1674 -""";
1675 - const arbitrumCWHeaders = """
1676 -import 'package:cw_evm/evm_chain_formatter.dart';
1677 -import 'package:cw_evm/evm_chain_mnemonics.dart';
1678 -import 'package:cw_evm/evm_chain_transaction_credentials.dart';
1679 -import 'package:cw_evm/evm_chain_transaction_info.dart';
1680 -import 'package:cw_evm/evm_chain_transaction_priority.dart';
1681 -import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
1682 -import 'package:cw_evm/hardware/evm_chain_ledger_credentials.dart';
1683 -import 'package:cw_evm/hardware/evm_chain_bitbox_credentials.dart';
1684 -import 'package:cw_evm/evm_chain_wallet.dart';
1685 -import 'package:cw_evm/hardware/evm_chain_bitbox_service.dart';
1686 -import 'package:cw_evm/hardware/evm_chain_ledger_service.dart';
1687 -
1688 -import 'package:cw_arbitrum/arbitrum_client.dart';
1689 -import 'package:cw_arbitrum/arbitrum_wallet.dart';
1690 -import 'package:cw_arbitrum/arbitrum_wallet_service.dart';
1691 -import 'package:cw_arbitrum/default_arbitrum_erc20_tokens.dart';
1692 -import 'package:eth_sig_util/util/utils.dart';
1693 -
1694 -""";
1695 - const arbitrumCwPart = "part 'cw_arbitrum.dart';";
1696 - const arbitrumContent = """
1697 -abstract class Arbitrum {
1698 - List<String> getArbitrumWordList(String language);
1699 - WalletService createArbitrumWalletService(bool isDirect);
1700 - WalletCredentials createArbitrumNewWalletCredentials(
1701 - {required String name,
1702 - WalletInfo? walletInfo,
1703 - String? password,
1704 - String? mnemonic,
1705 - String? passphrase});
1706 - WalletCredentials createArbitrumRestoreWalletFromSeedCredentials(
1707 - {required String name,
1708 - required String mnemonic,
1709 - required String password,
1710 - String? passphrase});
1711 - WalletCredentials createArbitrumRestoreWalletFromPrivateKey(
1712 - {required String name, required String privateKey, required String password});
1713 - WalletCredentials createArbitrumHardwareWalletCredentials(
1714 - {required String name, required HardwareAccountData hwAccountData, WalletInfo? walletInfo});
1715 - String getAddress(WalletBase wallet);
1716 - String getPrivateKey(WalletBase wallet);
1717 - String getPublicKey(WalletBase wallet);
1718 -
1719 - Object createArbitrumTransactionCredentials(
1720 - List<Output> outputs, {
1721 - required CryptoCurrency currency,
1722 - int? feeRate,
1723 - });
1724 -
1725 - Object createArbitrumTransactionCredentialsRaw(
1726 - List<OutputInfo> outputs, {
1727 - required CryptoCurrency currency,
1728 - required int feeRate,
1463 +class ChainInfo {
1464 + const ChainInfo({
1465 + required this.chainId,
1466 + required this.name,
1467 + required this.shortCode,
1468 });
1469 +
1470 + final int chainId;
1471 + final String name;
1472 + final String shortCode;
1473
1731 - int formatterArbitrumParseAmount(String amount);
1732 - double formatterArbitrumAmountToDouble(
1733 - {TransactionInfo? transaction, BigInt? amount, int exponent = 18});
1734 - List<Erc20Token> getERC20Currencies(WalletBase wallet);
1735 - Future<void> addErc20Token(WalletBase wallet, CryptoCurrency token);
1736 - Future<void> deleteErc20Token(WalletBase wallet, CryptoCurrency token);
1737 - Future<void> removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token);
1738 - Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress);
1739 -
1740 - Future<PendingTransaction> createTokenApproval(WalletBase wallet, BigInt amount, String spender,
1741 - CryptoCurrency token);
1742 -
1743 - CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction);
1744 - void updateArbitrumScanUsageState(WalletBase wallet, bool isEnabled);
1745 - Web3Client? getWeb3Client(WalletBase wallet);
1746 - String getTokenAddress(CryptoCurrency asset);
1474 + @override
1475 + bool operator ==(Object other) =>
1476 + identical(this, other) ||
1477 + other is ChainInfo && runtimeType == other.runtimeType && chainId == other.chainId;
1478
1748 - Future<void> setHardwareWalletService(WalletBase wallet, HardwareWalletService service);
1749 - HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection);
1750 - HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager);
1751 - List<String> getDefaultTokenContractAddresses();
1752 - bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress);
1753 - Future<bool> isApprovalRequired(WalletBase wallet, String tokenContract, String spender, BigInt requiredAmount);
1754 - Future<PendingTransaction> createRawCallDataTransaction(WalletBase wallet, String to, String dataHex, BigInt valueWei);
1755 - String? getArbitrumNativeEstimatedFee(WalletBase wallet);
1756 - String? getArbitrumERC20EstimatedFee(WalletBase wallet);
1479 + @override
1480 + int get hashCode => chainId.hashCode;
1481 }
1758 -
1482 """;
1483
1761 - const arbitrumEmptyDefinition = 'Arbitrum? arbitrum;\n';
1762 - const arbitrumCWDefinition = 'Arbitrum? arbitrum = CWArbitrum();\n';
1484 + const evmEmptyDefinition = 'EVM? evm;\n';
1485 + const evmCWDefinition = 'EVM? evm = CWEVM();\n';
1486
1764 - final output = '$arbitrumCommonHeaders\n' +
1765 - (hasImplementation ? '$arbitrumCWHeaders\n' : '\n') +
1766 - (hasImplementation ? '$arbitrumCwPart\n\n' : '\n') +
1767 - (hasImplementation ? arbitrumCWDefinition : arbitrumEmptyDefinition) +
1487 + final output = '$evmCommonHeaders\n' +
1488 + (hasImplementation ? '$evmCWHeaders\n' : '\n') +
1489 + (hasImplementation ? '$evmCwPart\n\n' : '\n') +
1490 + (hasImplementation ? evmCWDefinition : evmEmptyDefinition) +
1491 '\n' +
1769 - arbitrumContent;
1492 + evmContent;
1493
1494 if (outputFile.existsSync()) {
1495 await outputFile.delete();
@@ -1812,10 +1535,6 @@ Future<void> generatePubspec({
1535 path: flutter_secure_storage
1536 ref: ca897a08677edb443b366352dd7412735e098e7b
1537 """;
1815 - const cwEthereum = """
1816 - cw_ethereum:
1817 - path: ./cw_ethereum
1818 - """;
1538 const cwBitcoinCash = """
1539 cw_bitcoin_cash:
1540 path: ./cw_bitcoin_cash
@@ -1828,10 +1547,6 @@ Future<void> generatePubspec({
1547 cw_banano:
1548 path: ./cw_banano
1549 """;
1831 - const cwPolygon = """
1832 - cw_polygon:
1833 - path: ./cw_polygon
1834 - """;
1550 const cwSolana = """
1551 cw_solana:
1552 path: ./cw_solana
@@ -1860,14 +1575,6 @@ Future<void> generatePubspec({
1575 cw_dogecoin:
1576 path: ./cw_dogecoin
1577 """;
1863 - const cwBase = """
1864 - cw_base:
1865 - path: ./cw_base
1866 - """;
1867 - const cwArbitrum = """
1868 - cw_arbitrum:
1869 - path: ./cw_arbitrum
1870 - """;
1578 final inputFile = File(pubspecOutputPath);
1579 final inputText = await inputFile.readAsString();
1580 final inputLines = inputText.split('\n');
@@ -1885,10 +1592,6 @@ Future<void> generatePubspec({
1592 output += '\n$cwBitcoin';
1593 }
1594
1888 - if (hasEthereum) {
1889 - output += '\n$cwEthereum';
1890 - }
1891 -
1595 if (hasNano) {
1596 output += '\n$cwNano';
1597 }
@@ -1901,10 +1604,6 @@ Future<void> generatePubspec({
1604 output += '\n$cwBitcoinCash';
1605 }
1606
1904 - if (hasPolygon) {
1905 - output += '\n$cwPolygon';
1906 - }
1907 -
1607 if (hasSolana) {
1608 output += '\n$cwSolana';
1609 }
@@ -1921,7 +1620,7 @@ Future<void> generatePubspec({
1620 output += '\n$flutterSecureStorage\n';
1621 }
1622
1924 - if (hasEthereum || hasPolygon) {
1623 + if (hasEthereum || hasPolygon || hasBase || hasArbitrum) {
1624 output += '\n$cwEVM';
1625 }
1626
@@ -1937,14 +1636,6 @@ Future<void> generatePubspec({
1636 output += '\n$cwDogecoin';
1637 }
1638
1940 - if (hasBase) {
1941 - output += '\n$cwBase';
1942 - }
1943 -
1944 - if (hasArbitrum) {
1945 - output += '\n$cwArbitrum';
1946 - }
1947 -
1639 final outputLines = output.split('\n');
1640 inputLines.insertAll(dependenciesIndex + 1, outputLines);
1641 final outputContent = inputLines.join('\n');
tool/utils/secret_key.dart
+1
@@ -95,6 +95,7 @@ class SecretKey {
95 SecretKey('polygonScanApiKey', () => ''),
96 SecretKey('moralisApiKey', () => ''),
97 SecretKey('nowNodesApiKey ', () => ''),
98 + SecretKey('blinkApiKey', () => ''),
99 ];
100
101 static final solanaSecrets = [