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>
This commit is contained in:
David Adegoke
2026-01-16 02:45:49 +01:00
committed by GitHub
co-authored by Robert Malikowski Omar Hatem
parent 0e4855132c
commit 2a77d359f1
251 changed files with 7333 additions and 7788 deletions
-244
View File
@@ -1,244 +0,0 @@
part of 'arbitrum.dart';
class CWArbitrum extends Arbitrum {
@override
List<String> getArbitrumWordList(String language) => EVMChainMnemonics.englishWordlist;
WalletService createArbitrumWalletService(bool isDirect) =>
ArbitrumWalletService(isDirect, client: ArbitrumClient());
@override
WalletCredentials createArbitrumNewWalletCredentials({
required String name,
String? mnemonic,
WalletInfo? walletInfo,
String? password,
String? passphrase,
}) =>
EVMChainNewWalletCredentials(
name: name,
walletInfo: walletInfo,
password: password,
mnemonic: mnemonic,
passphrase: passphrase,
);
@override
WalletCredentials createArbitrumRestoreWalletFromSeedCredentials({
required String name,
required String mnemonic,
required String password,
String? passphrase,
}) =>
EVMChainRestoreWalletFromSeedCredentials(
name: name,
password: password,
mnemonic: mnemonic,
passphrase: passphrase,
);
@override
WalletCredentials createArbitrumRestoreWalletFromPrivateKey({
required String name,
required String privateKey,
required String password,
}) =>
EVMChainRestoreWalletFromPrivateKey(name: name, password: password, privateKey: privateKey);
@override
WalletCredentials createArbitrumHardwareWalletCredentials({
required String name,
required HardwareAccountData hwAccountData,
WalletInfo? walletInfo,
}) =>
EVMChainRestoreWalletFromHardware(
name: name,
hwAccountData: hwAccountData,
walletInfo: walletInfo,
);
@override
String getAddress(WalletBase wallet) => (wallet as ArbitrumWallet).walletAddresses.address;
@override
String getPrivateKey(WalletBase wallet) {
final privateKeyHolder = (wallet as ArbitrumWallet).evmChainPrivateKey;
if (privateKeyHolder is EthPrivateKey) return bytesToHex(privateKeyHolder.privateKey);
return "";
}
@override
String getPublicKey(WalletBase wallet) {
final privateKeyInUnitInt = (wallet as ArbitrumWallet).evmChainPrivateKey;
return privateKeyInUnitInt.address.hex;
}
Object createArbitrumTransactionCredentials(
List<Output> outputs, {
required CryptoCurrency currency,
int? feeRate,
}) =>
EVMChainTransactionCredentials(
outputs
.map(
(out) => OutputInfo(
fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmount,
address: out.address,
note: out.note,
sendAll: out.sendAll,
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
formattedCryptoAmount: out.formattedCryptoAmount,
),
)
.toList(),
priority: null,
currency: currency,
feeRate: feeRate,
);
Object createArbitrumTransactionCredentialsRaw(
List<OutputInfo> outputs, {
required CryptoCurrency currency,
required int feeRate,
}) =>
EVMChainTransactionCredentials(
outputs,
priority: null,
currency: currency,
feeRate: feeRate,
);
@override
int formatterArbitrumParseAmount(String amount) => EVMChainFormatter.parseEVMChainAmount(amount);
@override
double formatterArbitrumAmountToDouble({
TransactionInfo? transaction,
BigInt? amount,
int exponent = 18,
}) {
assert(transaction != null || amount != null);
if (transaction != null) {
transaction as EVMChainTransactionInfo;
return transaction.ethAmount / BigInt.from(10).pow(transaction.exponent);
} else {
return (amount!) / BigInt.from(10).pow(exponent);
}
}
@override
List<Erc20Token> getERC20Currencies(WalletBase wallet) =>
(wallet as ArbitrumWallet).erc20Currencies;
@override
Future<void> addErc20Token(WalletBase wallet, CryptoCurrency token) =>
(wallet as ArbitrumWallet).addErc20Token(token as Erc20Token);
@override
Future<void> deleteErc20Token(WalletBase wallet, CryptoCurrency token) =>
(wallet as ArbitrumWallet).deleteErc20Token(token as Erc20Token);
@override
Future<void> removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token) =>
(wallet as ArbitrumWallet).removeTokenTransactionsInHistory(token as Erc20Token);
@override
Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress) =>
(wallet as ArbitrumWallet).getErc20Token(contractAddress, 'arbitrum');
@override
CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) {
transaction as EVMChainTransactionInfo;
if (transaction.tokenSymbol == CryptoCurrency.arbEth.title ||
transaction.tokenSymbol == "ARB") {
return CryptoCurrency.arbEth;
}
wallet as ArbitrumWallet;
return wallet.erc20Currencies.firstWhere(
(element) =>
transaction.contractAddress?.toLowerCase() == element.contractAddress?.toLowerCase(),
);
}
@override
void updateArbitrumScanUsageState(WalletBase wallet, bool isEnabled) =>
(wallet as ArbitrumWallet).updateScanProviderUsageState(isEnabled);
@override
Web3Client? getWeb3Client(WalletBase wallet) => (wallet as ArbitrumWallet).getWeb3Client();
@override
String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress;
@override
Future<PendingTransaction> createTokenApproval(
WalletBase wallet,
BigInt amount,
String spender,
CryptoCurrency token,
) =>
(wallet as EVMChainWallet).createApprovalTransaction(
amount,
spender,
token,
null,
"ARB",
);
@override
Future<void> setHardwareWalletService(WalletBase wallet, HardwareWalletService service) async {
if (service is EVMChainLedgerService) {
((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedgerConnection(
service.ledgerConnection, (await wallet.walletInfo.getDerivationInfo()).derivationPath);
} else if (service is EVMChainBitboxService) {
((wallet as EVMChainWallet).evmChainPrivateKey as EvmBitboxCredentials)
.setBitbox(service.manager, (await wallet.walletInfo.getDerivationInfo()).derivationPath);
}
return Future.value();
}
@override
HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection) =>
EVMChainLedgerService(connection);
@override
HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager) =>
EVMChainBitboxService(manager, chainId: 42161);
@override
List<String> getDefaultTokenContractAddresses() => DefaultArbitrumErc20Tokens()
.initialArbitrumErc20Tokens
.map((e) => e.contractAddress)
.toList();
@override
bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress) {
final arbitrumWallet = wallet as ArbitrumWallet;
return arbitrumWallet.erc20Currencies.any(
(element) => element.contractAddress.toLowerCase() == contractAddress.toLowerCase(),
);
}
@override
Future<bool> isApprovalRequired(
WalletBase wallet, String tokenContract, String spender, BigInt requiredAmount) =>
(wallet as EVMChainWallet).isApprovalRequired(tokenContract, spender, requiredAmount);
@override
Future<PendingTransaction> createRawCallDataTransaction(
WalletBase wallet, String to, String dataHex, BigInt valueWei) =>
(wallet as EVMChainWallet).createCallDataTransaction(to, dataHex, valueWei, null);
@override
String? getArbitrumNativeEstimatedFee(WalletBase wallet) =>
(wallet as EVMChainWallet).nativeTxEstimatedFee;
@override
String? getArbitrumERC20EstimatedFee(WalletBase wallet) =>
(wallet as EVMChainWallet).erc20TxEstimatedFee;
}
-258
View File
@@ -1,258 +0,0 @@
part of 'base.dart';
class CWBase extends Base {
@override
List<String> getBaseWordList(String language) => EVMChainMnemonics.englishWordlist;
WalletService createBaseWalletService(bool isDirect) =>
BaseWalletService(isDirect, client: BaseClient());
@override
WalletCredentials createBaseNewWalletCredentials({
required String name,
String? mnemonic,
WalletInfo? walletInfo,
String? password,
String? passphrase,
}) =>
EVMChainNewWalletCredentials(
name: name,
walletInfo: walletInfo,
password: password,
mnemonic: mnemonic,
passphrase: passphrase,
);
@override
WalletCredentials createBaseRestoreWalletFromSeedCredentials({
required String name,
required String mnemonic,
required String password,
String? passphrase,
}) =>
EVMChainRestoreWalletFromSeedCredentials(
name: name,
password: password,
mnemonic: mnemonic,
passphrase: passphrase,
);
@override
WalletCredentials createBaseRestoreWalletFromPrivateKey({
required String name,
required String privateKey,
required String password,
}) =>
EVMChainRestoreWalletFromPrivateKey(name: name, password: password, privateKey: privateKey);
@override
WalletCredentials createBaseHardwareWalletCredentials({
required String name,
required HardwareAccountData hwAccountData,
WalletInfo? walletInfo,
}) =>
EVMChainRestoreWalletFromHardware(
name: name,
hwAccountData: hwAccountData,
walletInfo: walletInfo,
);
@override
String getAddress(WalletBase wallet) => (wallet as BaseWallet).walletAddresses.address;
@override
String getPrivateKey(WalletBase wallet) {
final privateKeyHolder = (wallet as BaseWallet).evmChainPrivateKey;
if (privateKeyHolder is EthPrivateKey) return bytesToHex(privateKeyHolder.privateKey);
return "";
}
@override
String getPublicKey(WalletBase wallet) {
final privateKeyInUnitInt = (wallet as BaseWallet).evmChainPrivateKey;
return privateKeyInUnitInt.address.hex;
}
@override
TransactionPriority getDefaultTransactionPriority() => EVMChainTransactionPriority.medium;
@override
TransactionPriority getBaseTransactionPrioritySlow() => EVMChainTransactionPriority.slow;
@override
List<TransactionPriority> getTransactionPriorities() => EVMChainTransactionPriority.all;
@override
TransactionPriority deserializeBaseTransactionPriority(int raw) =>
EVMChainTransactionPriority.deserialize(raw: raw);
Object createBaseTransactionCredentials(
List<Output> outputs, {
required TransactionPriority priority,
required CryptoCurrency currency,
int? feeRate,
}) =>
EVMChainTransactionCredentials(
outputs
.map(
(out) => OutputInfo(
fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmount,
address: out.address,
note: out.note,
sendAll: out.sendAll,
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
formattedCryptoAmount: out.formattedCryptoAmount,
),
)
.toList(),
priority: priority as EVMChainTransactionPriority,
currency: currency,
feeRate: feeRate,
);
Object createBaseTransactionCredentialsRaw(
List<OutputInfo> outputs, {
TransactionPriority? priority,
required CryptoCurrency currency,
required int feeRate,
}) =>
EVMChainTransactionCredentials(
outputs,
priority: priority as EVMChainTransactionPriority?,
currency: currency,
feeRate: feeRate,
);
@override
int formatterBaseParseAmount(String amount) => EVMChainFormatter.parseEVMChainAmount(amount);
@override
double formatterBaseAmountToDouble({
TransactionInfo? transaction,
BigInt? amount,
int exponent = 18,
}) {
assert(transaction != null || amount != null);
if (transaction != null) {
transaction as EVMChainTransactionInfo;
return transaction.ethAmount / BigInt.from(10).pow(transaction.exponent);
} else {
return (amount!) / BigInt.from(10).pow(exponent);
}
}
@override
List<Erc20Token> getERC20Currencies(WalletBase wallet) => (wallet as BaseWallet).erc20Currencies;
@override
Future<void> addErc20Token(WalletBase wallet, CryptoCurrency token) =>
(wallet as BaseWallet).addErc20Token(token as Erc20Token);
@override
Future<void> deleteErc20Token(WalletBase wallet, CryptoCurrency token) =>
(wallet as BaseWallet).deleteErc20Token(token as Erc20Token);
@override
Future<void> removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token) =>
(wallet as BaseWallet).removeTokenTransactionsInHistory(token as Erc20Token);
@override
Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress) =>
(wallet as BaseWallet).getErc20Token(contractAddress, 'base');
@override
CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) {
transaction as EVMChainTransactionInfo;
if (transaction.tokenSymbol == CryptoCurrency.baseEth.title ||
transaction.tokenSymbol == "BASE") {
return CryptoCurrency.baseEth;
}
wallet as BaseWallet;
return wallet.erc20Currencies.firstWhere(
(element) =>
transaction.contractAddress?.toLowerCase() == element.contractAddress?.toLowerCase(),
);
}
@override
void updateBaseScanUsageState(WalletBase wallet, bool isEnabled) =>
(wallet as BaseWallet).updateScanProviderUsageState(isEnabled);
@override
Web3Client? getWeb3Client(WalletBase wallet) => (wallet as BaseWallet).getWeb3Client();
@override
String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress;
@override
Future<bool> isApprovalRequired(
WalletBase wallet, String tokenContract, String spender, BigInt requiredAmount) =>
(wallet as EVMChainWallet).isApprovalRequired(tokenContract, spender, requiredAmount);
@override
Future<PendingTransaction> createTokenApproval(
WalletBase wallet,
BigInt amount,
String spender,
CryptoCurrency token,
TransactionPriority priority,
) =>
(wallet as EVMChainWallet).createApprovalTransaction(
amount,
spender,
token,
priority as EVMChainTransactionPriority,
"BASE",
);
@override
Future<PendingTransaction> createRawCallDataTransaction(WalletBase wallet, String to,
String dataHex, BigInt valueWei, TransactionPriority priority) =>
(wallet as EVMChainWallet).createCallDataTransaction(
to, dataHex, valueWei, priority as EVMChainTransactionPriority);
@override
Future<void> setHardwareWalletService(WalletBase wallet, HardwareWalletService service) async {
if (service is EVMChainLedgerService) {
((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedgerConnection(
service.ledgerConnection, (await wallet.walletInfo.getDerivationInfo()).derivationPath);
} else if (service is EVMChainBitboxService) {
((wallet as EVMChainWallet).evmChainPrivateKey as EvmBitboxCredentials)
.setBitbox(service.manager, (await wallet.walletInfo.getDerivationInfo()).derivationPath);
}
return Future.value();
}
@override
HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection) =>
EVMChainLedgerService(connection);
@override
HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager) =>
EVMChainBitboxService(manager, chainId: 8453);
@override
List<String> getDefaultTokenContractAddresses() =>
DefaultBaseErc20Tokens().initialBaseErc20Tokens.map((e) => e.contractAddress).toList();
@override
bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress) {
final baseWallet = wallet as BaseWallet;
return baseWallet.erc20Currencies.any(
(element) => element.contractAddress.toLowerCase() == contractAddress.toLowerCase(),
);
}
@override
String? getBaseNativeEstimatedFee(WalletBase wallet) =>
(wallet as EVMChainWallet).nativeTxEstimatedFee;
@override
String? getBaseERC20EstimatedFee(WalletBase wallet) =>
(wallet as EVMChainWallet).erc20TxEstimatedFee;
}
+1 -1
View File
@@ -82,7 +82,7 @@ class MoonPayProvider extends BuyProvider {
static String get _apiKey => secrets.moonPayApiKey;
String get currencyCode => walletTypeToCryptoCurrency(wallet.type).title.toLowerCase();
String get currencyCode => walletTypeToCryptoCurrency(wallet.type, chainId: wallet.chainId).title.toLowerCase();
String get trackUrl => baseBuyUrl + '/transaction_receipt?transactionId=';
+2 -2
View File
@@ -70,7 +70,7 @@ class WyreBuyProvider extends BuyProvider {
final body = {
'amount': amount,
'sourceCurrency': sourceCurrency,
'destCurrency': walletTypeToCryptoCurrency(wallet.type).title,
'destCurrency': walletTypeToCryptoCurrency(wallet.type, chainId: wallet.chainId).title,
'dest': walletTypeToString(wallet.type).toLowerCase() + ':' + wallet.walletAddresses.address,
'referrerAccountId': _accountId,
'lockFields': ['amount', 'sourceCurrency', 'destCurrency', 'dest']
@@ -100,7 +100,7 @@ class WyreBuyProvider extends BuyProvider {
final body = {
'amount': amount,
'sourceCurrency': sourceCurrency,
'destCurrency': walletTypeToCryptoCurrency(wallet.type).title,
'destCurrency': walletTypeToCryptoCurrency(wallet.type, chainId: wallet.chainId).title,
'dest': walletTypeToString(wallet.type).toLowerCase() + ':' + wallet.walletAddresses.address,
'accountId': _accountId,
'country': _countryCode
+1
View File
@@ -338,6 +338,7 @@ class AddressValidator extends TextValidator {
case CryptoCurrency.maticpoly:
case CryptoCurrency.baseEth:
case CryptoCurrency.arbEth:
case CryptoCurrency.arb:
pattern = '0x[0-9a-zA-Z]+';
case CryptoCurrency.nano:
pattern = 'nano_[0-9a-zA-Z]{60}';
+42 -25
View File
@@ -5,6 +5,7 @@ import 'package:cake_wallet/core/key_service.dart';
import 'package:cake_wallet/core/wallet_loading_service.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/utils/tor.dart';
@@ -16,7 +17,7 @@ import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter/foundation.dart';
import 'package:cake_wallet/evm/evm.dart';
class BackgroundSync {
final FlutterLocalNotificationsPlugin _notificationsPlugin = FlutterLocalNotificationsPlugin();
@@ -26,7 +27,7 @@ class BackgroundSync {
if (_isInitialized) return;
const androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher');
const iosSettings = DarwinInitializationSettings(
requestAlertPermission: true,
requestBadgePermission: true,
@@ -45,16 +46,18 @@ class BackgroundSync {
Future<bool> requestPermissions() async {
if (Platform.isIOS || Platform.isMacOS) {
return await _notificationsPlugin
.resolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>()
?.requestPermissions(
alert: true,
badge: true,
sound: true,
) ?? false;
.resolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>()
?.requestPermissions(
alert: true,
badge: true,
sound: true,
) ??
false;
} else if (Platform.isAndroid) {
return await _notificationsPlugin
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
?.areNotificationsEnabled() ?? false;
.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
?.areNotificationsEnabled() ??
false;
}
return false;
}
@@ -62,7 +65,7 @@ class BackgroundSync {
Future<void> showNotification(String title, String content) async {
await _initializeNotifications();
final hasPermission = await requestPermissions();
if (!hasPermission) {
printV('Notification permissions not granted');
return;
@@ -112,10 +115,11 @@ class BackgroundSync {
.where((element) => ![WalletType.haven, WalletType.decred].contains(element.type))
.toList();
for (int i = 0; i < moneroWallets.length; i++) {
final wallet = await walletLoadingService.load(moneroWallets[i].type, moneroWallets[i].name, isBackground: true);
final wallet = await walletLoadingService.load(moneroWallets[i].type, moneroWallets[i].name,
isBackground: true);
int syncedTicks = 0;
final keyService = getIt.get<KeyService>();
int stuckTicks = 0;
inner:
@@ -123,7 +127,9 @@ class BackgroundSync {
await Future.delayed(const Duration(seconds: 1));
final syncStatus = wallet.syncStatus;
final progress = syncStatus.progress();
if (syncStatus is ConnectedSyncStatus || syncStatus is AttemptingSyncStatus || syncStatus is NotConnectedSyncStatus) {
if (syncStatus is ConnectedSyncStatus ||
syncStatus is AttemptingSyncStatus ||
syncStatus is NotConnectedSyncStatus) {
stuckTicks++;
if (stuckTicks > 30) {
printV("${wallet.name} STUCK SYNCING");
@@ -134,7 +140,13 @@ class BackgroundSync {
}
if (syncStatus is NotConnectedSyncStatus) {
printV("${wallet.name} NOT CONNECTED");
final node = settingsStore.getCurrentNode(wallet.type);
int? chainId;
if (isEVMCompatibleChain(wallet.type)) {
chainId = evm!.getSelectedChainId(wallet);
}
final node = settingsStore.getCurrentNode(wallet.type, chainId: chainId);
await wallet.connectToNode(node: node);
await wallet.startBackgroundSync();
printV("STARTED SYNC");
@@ -147,7 +159,8 @@ class BackgroundSync {
syncedTicks = 0;
printV("WALLET $i SYNCED");
try {
await wallet.stopBackgroundSync((await keyService.getWalletPassword(walletName: wallet.name)));
await wallet.stopBackgroundSync(
(await keyService.getWalletPassword(walletName: wallet.name)));
} catch (e) {
printV("error stopping sync: $e");
}
@@ -185,25 +198,29 @@ class BackgroundSync {
final sortedTxs = txs.transactions.values.toList()..sort((a, b) => a.date.compareTo(b.date));
final sharedPreferences = await SharedPreferences.getInstance();
for (final tx in sortedTxs) {
final lastTriggerString = sharedPreferences.getString(PreferencesKey.backgroundSyncLastTrigger(wallet.name));
final lastTriggerDate = lastTriggerString != null
? DateTime.parse(lastTriggerString)
: DateTime.now();
final lastTriggerString =
sharedPreferences.getString(PreferencesKey.backgroundSyncLastTrigger(wallet.name));
final lastTriggerDate =
lastTriggerString != null ? DateTime.parse(lastTriggerString) : DateTime.now();
final keys = sharedPreferences.getKeys();
if (tx.date.isBefore(lastTriggerDate)) {
printV("w: ${wallet.name}, tx: ${tx.date} is before $lastTriggerDate (lastTriggerString: $lastTriggerString) (k: ${keys.length})");
printV(
"w: ${wallet.name}, tx: ${tx.date} is before $lastTriggerDate (lastTriggerString: $lastTriggerString) (k: ${keys.length})");
continue;
}
await sharedPreferences.setString(PreferencesKey.backgroundSyncLastTrigger(wallet.name), tx.date.add(Duration(minutes: 1)).toIso8601String());
await sharedPreferences.setString(PreferencesKey.backgroundSyncLastTrigger(wallet.name),
tx.date.add(Duration(minutes: 1)).toIso8601String());
final action = tx.direction == TransactionDirection.incoming ? "Received" : "Sent";
if (sharedPreferences.getBool(PreferencesKey.backgroundSyncNotificationsEnabled) ?? false) {
await showNotification("$action ${wallet.currency.fullName} in ${wallet.name}", "${tx.amountFormatted()}");
await showNotification(
"$action ${wallet.currency.fullName} in ${wallet.name}", "${tx.amountFormatted()}");
}
printV("${wallet.currency.fullName} in ${wallet.name}: TX: ${tx.date} ${tx.amount} ${tx.direction}");
printV(
"${wallet.currency.fullName} in ${wallet.name}: TX: ${tx.date} ${tx.amount} ${tx.direction}");
}
wallet.id;
await wallet.stopBackgroundSync(await keyService.getWalletPassword(walletName: wallet.name));
await wallet.close(shouldCleanup: true);
}
}
}
}
+25 -14
View File
@@ -7,6 +7,8 @@ import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:hive/hive.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
class NodeSwitchingService {
NodeSwitchingService({
@@ -142,12 +144,21 @@ class NodeSwitchingService {
return;
}
final walletType = appStore.wallet!.type;
final currentNode = settingsStore.getCurrentNode(walletType);
final wallet = appStore.wallet!;
final walletType = wallet.type;
WalletType nodeWalletType = walletType;
int? chainId;
if (isEVMCompatibleChain(walletType)) {
chainId = evm!.getSelectedChainId(appStore.wallet!);
}
final currentNode = settingsStore.getCurrentNode(nodeWalletType, chainId: chainId);
// Get all trusted nodes for this wallet type
final trustedNodes = nodeSource.values
.where((node) => node.type == walletType && node.isEnabledForAutoSwitching)
.where((node) => node.type == nodeWalletType && node.isEnabledForAutoSwitching)
.toList();
if (trustedNodes.isEmpty) {
@@ -157,26 +168,26 @@ class NodeSwitchingService {
}
// Initialize used nodes list for this wallet type if it does not exist
_usedNodeKeys.putIfAbsent(walletType, () => []);
_usedNodeKeys.putIfAbsent(nodeWalletType, () => []);
// Add current node to used list if not already there
if (!_usedNodeKeys[walletType]!.contains(currentNode.key)) {
_usedNodeKeys[walletType]!.add(currentNode.key);
if (!_usedNodeKeys[nodeWalletType]!.contains(currentNode.key)) {
_usedNodeKeys[nodeWalletType]!.add(currentNode.key);
}
// Try to find an active unused node
Node? nextNode = await _findActiveNode(trustedNodes, walletType);
Node? nextNode = await _findActiveNode(trustedNodes, nodeWalletType);
// If all trusted nodes have been used, check if we should reset
if (nextNode == null) {
printV('All trusted nodes have been tried for wallet type: $walletType');
printV('All trusted nodes have been tried for wallet type: $nodeWalletType');
// If we've tried all nodes and still haven't reached max attempts, reset and try again
if (_switchingAttempts < _maxNodeSwitchingAttempts) {
printV('Resetting used nodes list and trying again');
_usedNodeKeys[walletType]!.clear();
_usedNodeKeys[nodeWalletType]!.clear();
// Try again with cleared used list
nextNode = await _findActiveNode(trustedNodes, walletType);
nextNode = await _findActiveNode(trustedNodes, nodeWalletType);
}
// If still no active node found, we give up
@@ -188,16 +199,16 @@ class NodeSwitchingService {
}
// Ensure the selected node is marked as used
if (!_usedNodeKeys[walletType]!.contains(nextNode.key)) {
_usedNodeKeys[walletType]!.add(nextNode.key);
if (!_usedNodeKeys[nodeWalletType]!.contains(nextNode.key)) {
_usedNodeKeys[nodeWalletType]!.add(nextNode.key);
}
printV(
'Switching from ${currentNode.uriRaw} to ${nextNode.uriRaw} (attempt $_switchingAttempts/$_maxNodeSwitchingAttempts)');
printV('Used nodes for ${walletType}: ${_usedNodeKeys[walletType]}');
printV('Used nodes for ${nodeWalletType}: ${_usedNodeKeys[nodeWalletType]}');
// Update the current node in settings
settingsStore.nodes[walletType] = nextNode;
settingsStore.nodes[nodeWalletType] = nextNode;
// Connect the wallet to the new node
await appStore.wallet!.connectToNode(node: nextNode);
+5 -11
View File
@@ -1,12 +1,9 @@
import 'package:cake_wallet/arbitrum/arbitrum.dart';
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/validator.dart';
import 'package:cake_wallet/entities/mnemonic_item.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/wownero/wownero.dart';
@@ -36,14 +33,15 @@ class SeedValidator extends Validator<MnemonicItem> {
case WalletType.monero:
return monero!.getMoneroWordList(language);
case WalletType.ethereum:
return ethereum!.getEthereumWordList(language);
case WalletType.polygon:
case WalletType.base:
case WalletType.arbitrum:
return evm!.getEVMWordList(language);
case WalletType.bitcoinCash:
return getBitcoinWordList(language);
case WalletType.nano:
case WalletType.banano:
return nano!.getNanoWordList(language);
case WalletType.polygon:
return polygon!.getPolygonWordList(language);
case WalletType.solana:
return solana!.getSolanaWordList(language);
case WalletType.tron:
@@ -54,10 +52,6 @@ class SeedValidator extends Validator<MnemonicItem> {
return zano!.getWordList(language);
case WalletType.decred:
return decred!.getDecredWordList();
case WalletType.base:
return base!.getBaseWordList(language);
case WalletType.arbitrum:
return arbitrum!.getArbitrumWordList(language);
case WalletType.none:
case WalletType.haven:
return [];
-2
View File
@@ -95,7 +95,6 @@ class TradeMonitor {
}
if (_tradeTimers.containsKey(trade.id)) {
printV('Trade ${trade.id} is already being monitored');
continue;
} else {
_startTradeMonitoring(trade, provider!);
@@ -141,7 +140,6 @@ class TradeMonitor {
}
if (_isFinalState(trade.state)) {
printV('Skipping trade ${trade.id} because it\'s in a final state');
return true;
}
+12 -2
View File
@@ -2,6 +2,7 @@ import 'package:cake_wallet/utils/payment_request.dart';
import 'package:cake_wallet/core/address_validator.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
class AddressDetectionResult {
AddressDetectionResult({
@@ -16,6 +17,7 @@ class AddressDetectionResult {
this.callbackMessage,
required this.isValid,
this.errorMessage,
this.chainId,
});
final String address;
@@ -29,6 +31,7 @@ class AddressDetectionResult {
final String? callbackMessage;
final bool isValid;
final String? errorMessage;
final int? chainId;
}
/// Universal address detector that can identify cryptocurrency addresses from various formats
@@ -70,11 +73,13 @@ class UniversalAddressDetector {
// Determine currency from scheme
final currency = CryptoCurrency.fromString(uri.scheme.toLowerCase());
final walletType = cryptoCurrencyToWalletType(currency);
final chainId = getChainIdByCryptoCurrency(currency);
return AddressDetectionResult(
address: paymentRequest.address,
detectedCurrency: currency,
detectedWalletType: cryptoCurrencyToWalletType(currency),
detectedWalletType: walletType,
amount: paymentRequest.amount,
note: paymentRequest.note,
scheme: paymentRequest.scheme,
@@ -82,6 +87,7 @@ class UniversalAddressDetector {
callbackUrl: paymentRequest.callbackUrl,
callbackMessage: paymentRequest.callbackMessage,
isValid: true,
chainId: chainId,
);
} catch (e) {
return AddressDetectionResult(
@@ -226,11 +232,15 @@ class UniversalAddressDetector {
// Test each pattern in order of specificity
for (final pattern in detectionPatterns) {
if (pattern.pattern.hasMatch(cleanInput)) {
final walletType = cryptoCurrencyToWalletType(pattern.currency);
final chainId = getChainIdByCryptoCurrency(pattern.currency);
return AddressDetectionResult(
address: cleanInput,
detectedCurrency: pattern.currency,
detectedWalletType: cryptoCurrencyToWalletType(pattern.currency),
detectedWalletType: walletType,
isValid: true,
chainId: chainId,
);
}
}
+5 -11
View File
@@ -4,10 +4,9 @@ import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cake_wallet/anonpay/anonpay_api.dart';
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
import 'package:cake_wallet/anypay/anypay_api.dart';
import 'package:cake_wallet/arbitrum/arbitrum.dart';
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/buy/dfx/dfx_buy_provider.dart';
import 'package:cake_wallet/buy/moonpay/moonpay_provider.dart';
import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
@@ -87,12 +86,10 @@ import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/entities/qr_view_data.dart';
import 'package:cake_wallet/entities/template.dart';
import 'package:cake_wallet/entities/transaction_description.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/exchange/exchange_template.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/decred/decred.dart';
import 'package:cake_wallet/reactions/on_authentication_state_change.dart';
import 'package:cake_wallet/routes.dart';
@@ -1204,7 +1201,10 @@ Future<void> setup({
SettingsStoreBase.walletPasswordDirectInput,
);
case WalletType.ethereum:
return ethereum!.createEthereumWalletService(SettingsStoreBase.walletPasswordDirectInput);
case WalletType.polygon:
case WalletType.base:
case WalletType.arbitrum:
return evm!.createEVMWalletService(param1, SettingsStoreBase.walletPasswordDirectInput);
case WalletType.bitcoinCash:
return bitcoinCash!.createBitcoinCashWalletService(_unspentCoinsInfoSource, SettingsStoreBase.walletPasswordDirectInput);
case WalletType.dogecoin:
@@ -1212,8 +1212,6 @@ Future<void> setup({
case WalletType.nano:
case WalletType.banano:
return nano!.createNanoWalletService(SettingsStoreBase.walletPasswordDirectInput);
case WalletType.polygon:
return polygon!.createPolygonWalletService(SettingsStoreBase.walletPasswordDirectInput);
case WalletType.solana:
return solana!.createSolanaWalletService(SettingsStoreBase.walletPasswordDirectInput);
case WalletType.tron:
@@ -1224,10 +1222,6 @@ Future<void> setup({
return zano!.createZanoWalletService();
case WalletType.decred:
return decred!.createDecredWalletService(_unspentCoinsInfoSource);
case WalletType.base:
return base!.createBaseWalletService(SettingsStoreBase.walletPasswordDirectInput);
case WalletType.arbitrum:
return arbitrum!.createArbitrumWalletService(SettingsStoreBase.walletPasswordDirectInput);
case WalletType.haven:
return HavenWalletService();
case WalletType.none:
+4 -20
View File
@@ -1,7 +1,5 @@
import 'package:cake_wallet/arbitrum/arbitrum.dart';
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_base.dart';
@@ -10,25 +8,11 @@ import 'package:ens_dart/ens_dart.dart';
import 'package:web3dart/web3dart.dart';
class EnsRecord {
static Future<String> fetchEnsAddress(String name, {WalletBase? wallet}) async {
Web3Client? _client;
if (wallet != null && wallet.type == WalletType.ethereum) {
_client = ethereum!.getWeb3Client(wallet);
}
if (wallet != null && wallet.type == WalletType.polygon) {
_client = polygon!.getWeb3Client(wallet);
}
if (wallet != null && wallet.type == WalletType.base) {
_client = base!.getWeb3Client(wallet);
}
if (wallet != null && wallet.type == WalletType.arbitrum) {
_client = arbitrum!.getWeb3Client(wallet);
if (wallet != null && (isEVMCompatibleChain(wallet.type))) {
_client = evm!.getWeb3Client(wallet);
}
if (_client == null) {
+1
View File
@@ -19,6 +19,7 @@ Future<List<Node>> loadDefaultNodes(WalletType type) async {
case WalletType.haven:
path = 'assets/haven_node_list.yml';
break;
// TODO: (refactoring) each wallet would have its path, so `wallet.nodePath` would be decided based on chain id in Evm wallet
case WalletType.ethereum:
path = 'assets/ethereum_server_list.yml';
break;
+2
View File
@@ -84,6 +84,7 @@ class PreferencesKey {
static const useArbiScan = 'use_arbitrum_scan';
static const useTronGrid = 'use_trongrid';
static const useMempoolFeeAPI = 'use_mempool_fee_api';
static const evmHiddenChainIds = 'evm_hidden_chain_ids';
static const defaultNanoRep = 'default_nano_representative';
static const defaultBananoRep = 'default_banano_representative';
static const lookupsTwitter = 'looks_up_twitter';
@@ -94,6 +95,7 @@ class PreferencesKey {
static const lookupsOpenAlias = 'looks_up_open_alias';
static const lookupsENS = 'looks_up_ens';
static const lookupsWellKnown = 'looks_up_well_known';
static const useBlinkProtection = 'use_blink_protection';
static const usePayjoin = 'use_payjoin';
static const showPayjoinCard = 'show_payjoin_card';
static const showCameraConsent = 'show_camera_consent';
+4 -9
View File
@@ -1,11 +1,8 @@
import 'package:cake_wallet/arbitrum/arbitrum.dart';
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/dogecoin/dogecoin.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cake_wallet/zano/zano.dart';
import 'package:cake_wallet/decred/decred.dart';
@@ -23,15 +20,13 @@ List<TransactionPriority> priorityForWalletType(WalletType type) {
case WalletType.litecoin:
return bitcoin!.getLitecoinTransactionPriorities();
case WalletType.ethereum:
return ethereum!.getTransactionPriorities();
case WalletType.polygon:
case WalletType.base:
return evm!.getTransactionPriorities();
case WalletType.bitcoinCash:
return bitcoinCash!.getTransactionPriorities();
case WalletType.dogecoin:
return dogecoin!.getTransactionPriorities();
case WalletType.polygon:
return polygon!.getTransactionPriorities();
case WalletType.base:
return base!.getTransactionPriorities();
case WalletType.arbitrum:
case WalletType.nano:
case WalletType.banano:
+6 -2
View File
@@ -1,9 +1,9 @@
import 'package:cake_wallet/entities/contact_base.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/wallet_type.dart';
class WalletContact implements ContactBase {
WalletContact(this.address, this.name, this.type);
//: super(name, address, type);
WalletContact(this.address, this.name, this.type, {this.walletType});
@override
String address;
@@ -13,4 +13,8 @@ class WalletContact implements ContactBase {
@override
CryptoCurrency type;
/// Wallet type of the wallet this contact belongs to
/// Used for EVM chain filtering
final WalletType? walletType;
}
-283
View File
@@ -1,283 +0,0 @@
part of 'ethereum.dart';
class CWEthereum extends Ethereum {
@override
List<String> getEthereumWordList(String language) => EVMChainMnemonics.englishWordlist;
WalletService createEthereumWalletService(bool isDirect) =>
EthereumWalletService(isDirect, client: EthereumClient());
@override
WalletCredentials createEthereumNewWalletCredentials({
required String name,
String? mnemonic,
WalletInfo? walletInfo,
String? password,
String? passphrase,
}) =>
EVMChainNewWalletCredentials(
name: name,
walletInfo: walletInfo,
password: password,
mnemonic: mnemonic,
passphrase: passphrase,
);
@override
WalletCredentials createEthereumRestoreWalletFromSeedCredentials({
required String name,
required String mnemonic,
required String password,
String? passphrase,
}) =>
EVMChainRestoreWalletFromSeedCredentials(
name: name,
password: password,
mnemonic: mnemonic,
passphrase: passphrase,
);
@override
WalletCredentials createEthereumRestoreWalletFromPrivateKey({
required String name,
required String privateKey,
required String password,
}) =>
EVMChainRestoreWalletFromPrivateKey(name: name, password: password, privateKey: privateKey);
@override
WalletCredentials createEthereumHardwareWalletCredentials({
required String name,
required HardwareAccountData hwAccountData,
WalletInfo? walletInfo,
}) =>
EVMChainRestoreWalletFromHardware(
name: name, hwAccountData: hwAccountData, walletInfo: walletInfo);
@override
String getAddress(WalletBase wallet) => (wallet as EthereumWallet).walletAddresses.address;
@override
String getPrivateKey(WalletBase wallet) {
final privateKeyHolder = (wallet as EthereumWallet).evmChainPrivateKey;
if (privateKeyHolder is EthPrivateKey) return bytesToHex(privateKeyHolder.privateKey);
return "";
}
@override
String getPublicKey(WalletBase wallet) {
final privateKeyInUnitInt = (wallet as EthereumWallet).evmChainPrivateKey;
return privateKeyInUnitInt.address.hex;
}
@override
TransactionPriority getDefaultTransactionPriority() => EVMChainTransactionPriority.medium;
@override
TransactionPriority getEthereumTransactionPrioritySlow() => EVMChainTransactionPriority.slow;
@override
List<TransactionPriority> getTransactionPriorities() => EVMChainTransactionPriority.all;
@override
TransactionPriority deserializeEthereumTransactionPriority(int raw) =>
EVMChainTransactionPriority.deserialize(raw: raw);
Object createEthereumTransactionCredentials(
List<Output> outputs, {
required TransactionPriority priority,
required CryptoCurrency currency,
int? feeRate,
}) =>
EVMChainTransactionCredentials(
outputs
.map((out) => OutputInfo(
fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmount,
address: out.address,
note: out.note,
sendAll: out.sendAll,
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
formattedCryptoAmount: out.formattedCryptoAmount,
memo: out.memo))
.toList(),
priority: priority as EVMChainTransactionPriority,
currency: currency,
feeRate: feeRate,
);
Object createEthereumTransactionCredentialsRaw(
List<OutputInfo> outputs, {
TransactionPriority? priority,
required CryptoCurrency currency,
required int feeRate,
}) =>
EVMChainTransactionCredentials(
outputs,
priority: priority as EVMChainTransactionPriority?,
currency: currency,
feeRate: feeRate,
);
@override
int formatterEthereumParseAmount(String amount) => EVMChainFormatter.parseEVMChainAmount(amount);
@override
double formatterEthereumAmountToDouble(
{TransactionInfo? transaction, BigInt? amount, int exponent = 18}) {
assert(transaction != null || amount != null);
if (transaction != null) {
transaction as EVMChainTransactionInfo;
return transaction.ethAmount / BigInt.from(10).pow(transaction.exponent);
} else {
return (amount!) / BigInt.from(10).pow(exponent);
}
}
@override
List<Erc20Token> getERC20Currencies(WalletBase wallet) =>
(wallet as EthereumWallet).erc20Currencies;
@override
Future<void> addErc20Token(WalletBase wallet, CryptoCurrency token) =>
(wallet as EthereumWallet).addErc20Token(token as Erc20Token);
@override
Future<void> deleteErc20Token(WalletBase wallet, CryptoCurrency token) =>
(wallet as EthereumWallet).deleteErc20Token(token as Erc20Token);
@override
Future<void> removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token) =>
(wallet as EthereumWallet).removeTokenTransactionsInHistory(token as Erc20Token);
@override
Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress) =>
(wallet as EthereumWallet).getErc20Token(contractAddress, 'eth');
@override
CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) {
transaction as EVMChainTransactionInfo;
if (transaction.tokenSymbol == CryptoCurrency.eth.title) {
return CryptoCurrency.eth;
}
wallet as EthereumWallet;
return wallet.erc20Currencies.firstWhere(
(element) => transaction.tokenSymbol == element.symbol,
);
}
@override
void updateEtherscanUsageState(WalletBase wallet, bool isEnabled) =>
(wallet as EthereumWallet).updateScanProviderUsageState(isEnabled);
@override
Web3Client? getWeb3Client(WalletBase wallet) => (wallet as EthereumWallet).getWeb3Client();
@override
String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress;
@override
Future<void> setHardwareWalletService(WalletBase wallet, HardwareWalletService service) async {
if (service is EVMChainLedgerService) {
((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedgerConnection(
service.ledgerConnection, (await wallet.walletInfo.getDerivationInfo()).derivationPath);
} else if (service is EVMChainBitboxService) {
((wallet as EVMChainWallet).evmChainPrivateKey as EvmBitboxCredentials)
.setBitbox(service.manager, (await wallet.walletInfo.getDerivationInfo()).derivationPath);
} else if (service is EVMChainTrezorService) {
((wallet as EVMChainWallet).evmChainPrivateKey as EvmTrezorCredentials).setTrezorConnect(
service.connect, (await wallet.walletInfo.getDerivationInfo()).derivationPath);
}
}
@override
HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection) =>
EVMChainLedgerService(connection);
@override
HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager) =>
EVMChainBitboxService(manager);
@override
HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorConnect connect) =>
EVMChainTrezorService(connect);
@override
List<String> getDefaultTokenContractAddresses() {
return DefaultEthereumErc20Tokens().initialErc20Tokens.map((e) => e.contractAddress).toList();
}
@override
bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress) {
final ethereumWallet = wallet as EthereumWallet;
return ethereumWallet.erc20Currencies
.any((element) => element.contractAddress.toLowerCase() == contractAddress.toLowerCase());
}
@override
Future<bool> isApprovalRequired(
WalletBase wallet, String tokenContract, String spender, BigInt requiredAmount) =>
(wallet as EVMChainWallet).isApprovalRequired(tokenContract, spender, requiredAmount);
@override
Future<PendingTransaction> createTokenApproval(WalletBase wallet, BigInt amount, String spender,
CryptoCurrency token, TransactionPriority priority) =>
(wallet as EVMChainWallet).createApprovalTransaction(
amount, spender, token, priority as EVMChainTransactionPriority, "ETH");
@override
Future<PendingTransaction> createRawCallDataTransaction(WalletBase wallet, String to,
String dataHex, BigInt valueWei, TransactionPriority priority) =>
(wallet as EVMChainWallet).createCallDataTransaction(
to, dataHex, valueWei, priority as EVMChainTransactionPriority);
// Integrations
@override
Future<BigInt> getDEuroSavingsBalance(WalletBase wallet) =>
DEuro(wallet as EthereumWallet).savingsBalance;
@override
Future<BigInt> getDEuroAccruedInterest(WalletBase wallet) =>
DEuro(wallet as EthereumWallet).accruedInterest;
@override
Future<BigInt> getDEuroInterestRate(WalletBase wallet) =>
DEuro(wallet as EthereumWallet).interestRate;
@override
Future<BigInt> getDEuroSavingsApproved(WalletBase wallet) =>
DEuro(wallet as EthereumWallet).approvedBalance;
@override
Future<PendingTransaction> addDEuroSaving(
WalletBase wallet, BigInt amount, TransactionPriority priority) =>
DEuro(wallet as EthereumWallet)
.depositSavings(amount, priority as EVMChainTransactionPriority);
@override
Future<PendingTransaction> removeDEuroSaving(
WalletBase wallet, BigInt amount, TransactionPriority priority) =>
DEuro(wallet as EthereumWallet)
.withdrawSavings(amount, priority as EVMChainTransactionPriority);
@override
Future<PendingTransaction> reinvestDEuroInterest(
WalletBase wallet, TransactionPriority priority) =>
DEuro(wallet as EthereumWallet).reinvestInterest(priority as EVMChainTransactionPriority);
@override
Future<PendingTransaction> enableDEuroSaving(WalletBase wallet, TransactionPriority priority) =>
DEuro(wallet as EthereumWallet).enableSavings(priority as EVMChainTransactionPriority);
@override
String? getEthereumNativeEstimatedFee(WalletBase wallet) =>
(wallet as EVMChainWallet).nativeTxEstimatedFee;
@override
String? getEthereumERC20EstimatedFee(WalletBase wallet) =>
(wallet as EVMChainWallet).erc20TxEstimatedFee;
}
+516
View File
@@ -0,0 +1,516 @@
part of 'evm.dart';
class CWEVM extends EVM {
@override
List<String> getEVMWordList(String language) => EVMChainMnemonics.englishWordlist;
@override
WalletService createEVMWalletService(WalletType walletType, bool isDirect) {
return EVMChainWalletService(isDirect);
}
@override
WalletCredentials createEVMNewWalletCredentials({
required String name,
WalletInfo? walletInfo,
String? password,
String? mnemonic,
String? passphrase,
}) {
return EVMChainNewWalletCredentials(
name: name,
walletInfo: walletInfo,
password: password,
mnemonic: mnemonic,
passphrase: passphrase,
);
}
@override
WalletCredentials createEVMRestoreWalletFromSeedCredentials({
required String name,
required String mnemonic,
required String password,
String? passphrase,
}) {
return EVMChainRestoreWalletFromSeedCredentials(
name: name,
password: password,
mnemonic: mnemonic,
passphrase: passphrase,
);
}
@override
WalletCredentials createEVMRestoreWalletFromPrivateKey({
required String name,
required String privateKey,
required String password,
}) {
return EVMChainRestoreWalletFromPrivateKey(
name: name,
password: password,
privateKey: privateKey,
);
}
@override
WalletCredentials createEVMHardwareWalletCredentials({
required String name,
required HardwareAccountData hwAccountData,
WalletInfo? walletInfo,
}) {
return EVMChainRestoreWalletFromHardware(
name: name,
hwAccountData: hwAccountData,
walletInfo: walletInfo,
);
}
@override
String getAddress(WalletBase wallet) => (wallet as EVMChainWallet).walletAddresses.address;
@override
String getPrivateKey(WalletBase wallet) {
final privateKeyHolder = (wallet as EVMChainWallet).evmChainPrivateKey;
if (privateKeyHolder is EthPrivateKey) {
return bytesToHex(privateKeyHolder.privateKey);
}
return "";
}
@override
String getPublicKey(WalletBase wallet) {
final privateKeyInUnitInt = (wallet as EVMChainWallet).evmChainPrivateKey;
return privateKeyInUnitInt.address.hex;
}
@override
TransactionPriority getDefaultTransactionPriority() => EVMChainTransactionPriority.medium;
@override
TransactionPriority getEVMTransactionPrioritySlow() => EVMChainTransactionPriority.slow;
@override
List<TransactionPriority> getTransactionPriorities() => EVMChainTransactionPriority.all;
@override
TransactionPriority deserializeEVMTransactionPriority(int raw) =>
EVMChainTransactionPriority.deserialize(raw: raw);
@override
Object createEVMTransactionCredentials(
List<Output> outputs, {
required CryptoCurrency currency,
TransactionPriority? priority,
int? feeRate,
bool useBlinkProtection = true,
}) {
return EVMChainTransactionCredentials(
outputs
.map((out) => OutputInfo(
fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmount,
address: out.address,
note: out.note,
sendAll: out.sendAll,
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
formattedCryptoAmount: out.formattedCryptoAmount,
memo: out.memo))
.toList(),
priority: priority as EVMChainTransactionPriority?,
currency: currency,
feeRate: feeRate,
useBlinkProtection: useBlinkProtection,
);
}
@override
Object createEVMTransactionCredentialsRaw(
List<OutputInfo> outputs, {
TransactionPriority? priority,
required CryptoCurrency currency,
required int feeRate,
bool useBlinkProtection = true,
}) {
return EVMChainTransactionCredentials(
outputs,
priority: priority as EVMChainTransactionPriority?,
currency: currency,
feeRate: feeRate,
useBlinkProtection: useBlinkProtection,
);
}
@override
int formatterEVMParseAmount(String amount) => EVMChainFormatter.parseEVMChainAmount(amount);
@override
double formatterEVMAmountToDouble({
TransactionInfo? transaction,
BigInt? amount,
int exponent = 18,
}) {
assert(transaction != null || amount != null);
if (transaction != null) {
transaction as EVMChainTransactionInfo;
return transaction.ethAmount / BigInt.from(10).pow(transaction.exponent);
} else {
return (amount!) / BigInt.from(10).pow(exponent);
}
}
@override
List<Erc20Token> getERC20Currencies(WalletBase wallet) =>
(wallet as EVMChainWallet).erc20Currencies;
@override
Future<void> addErc20Token(WalletBase wallet, CryptoCurrency token) =>
(wallet as EVMChainWallet).addErc20Token(token as Erc20Token);
@override
Future<void> deleteErc20Token(WalletBase wallet, CryptoCurrency token) =>
(wallet as EVMChainWallet).deleteErc20Token(token as Erc20Token);
@override
Future<void> removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token) =>
(wallet as EVMChainWallet).removeTokenTransactionsInHistory(token as Erc20Token);
@override
Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress) {
final evmWallet = wallet as EVMChainWallet;
final chainName = EVMChainUtils.getDefaultTokenSymbol(evmWallet.selectedChainId).toLowerCase();
return evmWallet.getErc20Token(contractAddress, chainName);
}
@override
CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) {
transaction as EVMChainTransactionInfo;
final evmWallet = wallet as EVMChainWallet;
final nativeCurrency = evmWallet.currency;
final nativeCurrencyTitle = nativeCurrency.title;
final currentChainId = evmWallet.selectedChainId;
// If transaction is from a different chain, we will return native currency as fallback
// This can happen during chain switching when old transactions are still visible
if (transaction.chainId != currentChainId) {
return nativeCurrency;
}
if (transaction.tokenSymbol == CryptoCurrency.maticpoly.title ||
transaction.tokenSymbol == "MATIC") {
return CryptoCurrency.maticpoly;
}
if (transaction.tokenSymbol == nativeCurrencyTitle) {
return nativeCurrency;
}
// Otherwise, it's an ERC20 token
// Also using firstWhereOrNull to handle cases where token isn't found (e.g., during chain switch)
final erc20Token = evmWallet.erc20Currencies.firstWhereOrNull(
(element) =>
transaction.contractAddress?.toLowerCase() == element.contractAddress.toLowerCase(),
);
return erc20Token ?? nativeCurrency;
}
@override
void updateScanProviderUsageState(WalletBase wallet, bool isEnabled) =>
(wallet as EVMChainWallet).updateScanProviderUsageState(isEnabled);
@override
Web3Client? getWeb3Client(WalletBase wallet) => (wallet as EVMChainWallet).getWeb3Client();
@override
String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress;
@override
Future<bool> isApprovalRequired(
WalletBase wallet,
String tokenContract,
String spender,
BigInt requiredAmount,
) =>
(wallet as EVMChainWallet).isApprovalRequired(tokenContract, spender, requiredAmount);
@override
Future<PendingTransaction> createTokenApproval(
WalletBase wallet,
BigInt amount,
String spender,
CryptoCurrency token,
TransactionPriority? priority, {
bool useBlinkProtection = true,
}) {
final evmWallet = wallet as EVMChainWallet;
final feeCurrency = EVMChainUtils.getFeeCurrency(evmWallet.selectedChainId);
return evmWallet.createApprovalTransaction(
amount,
spender,
token,
priority as EVMChainTransactionPriority?,
feeCurrency,
useBlinkProtection: useBlinkProtection,
);
}
@override
Future<PendingTransaction> createRawCallDataTransaction(
WalletBase wallet,
String to,
String dataHex,
BigInt valueWei,
TransactionPriority? priority, {
bool useBlinkProtection = true,
}) =>
(wallet as EVMChainWallet).createCallDataTransaction(
to,
dataHex,
valueWei,
priority as EVMChainTransactionPriority?,
useBlinkProtection: useBlinkProtection,
);
@override
Future<void> setHardwareWalletService(
WalletBase wallet,
HardwareWalletService service,
) async {
final evmWallet = wallet as EVMChainWallet;
final privateKey = evmWallet.evmChainPrivateKey;
final derivationPath = (await wallet.walletInfo.getDerivationInfo()).derivationPath;
if (service is EVMChainLedgerService) {
(privateKey as EvmLedgerCredentials)
.setLedgerConnection(service.ledgerConnection, derivationPath);
} else if (service is EVMChainBitboxService) {
(privateKey as EvmBitboxCredentials).setBitbox(service.manager, derivationPath);
} else if (service is EVMChainTrezorService) {
(privateKey as EvmTrezorCredentials).setTrezorConnect(service.connect, derivationPath);
}
}
@override
HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection) =>
EVMChainLedgerService(connection);
@override
HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager) =>
EVMChainBitboxService(manager);
@override
HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorConnect connect) =>
EVMChainTrezorService(connect);
@override
List<String> getDefaultTokenContractAddresses(WalletBase wallet) {
final chainId = getSelectedChainId(wallet);
if (chainId == null) return [];
return EVMChainDefaultTokens.getDefaultTokenAddresses(chainId);
}
@override
bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress) {
final evmWallet = wallet as EVMChainWallet;
return evmWallet.erc20Currencies
.any((element) => element.contractAddress.toLowerCase() == contractAddress.toLowerCase());
}
@override
String? getEVMNativeEstimatedFee(WalletBase wallet) =>
(wallet as EVMChainWallet).nativeTxEstimatedFee;
@override
String? getEVMERC20EstimatedFee(WalletBase wallet) =>
(wallet as EVMChainWallet).erc20TxEstimatedFee;
// Chain-specific integrations (only for Ethereum)
@override
Future<BigInt>? getDEuroSavingsBalance(WalletBase wallet) {
if (wallet.chainId == 1 && wallet is EVMChainWallet) {
return DEuro(wallet).savingsBalance;
}
return null;
}
@override
Future<BigInt>? getDEuroAccruedInterest(WalletBase wallet) {
if (wallet.chainId == 1 && wallet is EVMChainWallet) {
return DEuro(wallet).accruedInterest;
}
return null;
}
@override
Future<BigInt>? getDEuroInterestRate(WalletBase wallet) {
if (wallet.chainId == 1 && wallet is EVMChainWallet) {
return DEuro(wallet).interestRate;
}
return null;
}
@override
Future<BigInt>? getDEuroSavingsApproved(WalletBase wallet) {
if (wallet.chainId == 1 && wallet is EVMChainWallet) {
return DEuro(wallet).approvedBalance;
}
return null;
}
@override
Future<PendingTransaction>? addDEuroSaving(
WalletBase wallet, BigInt amount, TransactionPriority priority) {
if (wallet.chainId == 1 && wallet is EVMChainWallet) {
return DEuro(wallet).depositSavings(amount, priority as EVMChainTransactionPriority);
}
return null;
}
@override
Future<PendingTransaction>? removeDEuroSaving(
WalletBase wallet, BigInt amount, TransactionPriority priority) {
if (wallet.chainId == 1 && wallet is EVMChainWallet) {
return DEuro(wallet).withdrawSavings(amount, priority as EVMChainTransactionPriority);
}
return null;
}
@override
Future<PendingTransaction>? reinvestDEuroInterest(
WalletBase wallet, TransactionPriority priority) {
if (wallet.chainId == 1 && wallet is EVMChainWallet) {
return DEuro(wallet).reinvestInterest(priority as EVMChainTransactionPriority);
}
return null;
}
@override
Future<PendingTransaction>? enableDEuroSaving(WalletBase wallet, TransactionPriority priority) {
if (wallet.chainId == 1 && wallet is EVMChainWallet) {
return DEuro(wallet).enableSavings(priority as EVMChainTransactionPriority);
}
return null;
}
// Registry helper methods
static final EvmChainRegistry _registry = EvmChainRegistry();
@override
int getChainIdByWalletType(WalletType walletType) {
final config = _registry.getChainConfigByWalletType(walletType);
return config?.chainId ?? 1; // Default to Ethereum
}
@override
String getChainNameByWalletType(WalletType walletType) {
final config = _registry.getChainConfigByWalletType(walletType);
return config?.shortCode ?? 'eth';
}
@override
String getTokenNameByWalletType(WalletType walletType) {
final config = _registry.getChainConfigByWalletType(walletType);
return config?.nativeCurrency.title ?? 'ETH';
}
@override
String getCaip2ByChainId(int chainId) {
final config = _registry.getChainConfig(chainId);
return config?.caip2 ?? 'eip155:1';
}
@override
String getChainNameByChainId(int chainId) {
final config = _registry.getChainConfig(chainId);
return config?.shortCode ?? 'eth';
}
@override
String getTokenNameByChainId(int chainId) {
final config = _registry.getChainConfig(chainId);
return config?.nativeCurrency.title ?? 'ETH';
}
@override
int? getChainIdByTag(String tag) {
final config = _registry.getChainConfigByTag(tag);
return config?.chainId;
}
@override
int? getChainIdByTitle(String title) {
// Try as tag first (uppercase)
final tagResult = getChainIdByTag(title.toUpperCase());
if (tagResult != null) return tagResult;
// Try as lowercase title
return getChainIdByTag(title.toLowerCase());
}
@override
WalletType? getWalletTypeByChainId(int chainId) {
return _registry.getWalletTypeByChainId(chainId);
}
@override
List<ChainInfo> getAllChains() {
final allChains = _registry.getAllChains();
return allChains
.map((config) => ChainInfo(
chainId: config.chainId,
name: config.name,
shortCode: config.shortCode,
))
.toList();
}
@override
ChainInfo? getCurrentChain(WalletBase wallet) {
if (wallet is EVMChainWallet) {
final config = wallet.selectedChainConfig;
if (config == null) return null;
return ChainInfo(
chainId: config.chainId,
name: config.name,
shortCode: config.shortCode,
);
}
return null;
}
@override
int? getSelectedChainId(WalletBase wallet) {
if (wallet is EVMChainWallet) {
return wallet.selectedChainId;
}
return null;
}
@override
Future<void> selectChain(WalletBase wallet, int chainId, {required Node node}) async {
if (wallet is EVMChainWallet) {
await wallet.selectChain(chainId, node: node);
}
}
@override
String? getExplorerUrlForChainId(int chainId, {bool showProtocol = true}) {
final config = _registry.getChainConfig(chainId);
if (config != null && config.explorerUrls.isNotEmpty) {
final url = config.explorerUrls.first;
return showProtocol
? url
: url.replaceAll('https://', '').replaceAll('http://', '').split('/')[0];
}
return null;
}
@override
bool hasPriorityFee(int chainId) => EVMChainUtils.hasPriorityFee(chainId);
}
@@ -29,7 +29,7 @@ class ChainflipExchangeProvider extends ExchangeProvider {
CryptoCurrency.sol,
CryptoCurrency.usdcsol,
CryptoCurrency.arbEth,
// TODO: Add CryptoCurrency.usdcarb
CryptoCurrency.usdcArb,
// TODO: Add CryptoCurrency.dot
];
@@ -353,6 +353,8 @@ class ChangeNowExchangeProvider extends ExchangeProvider {
return 'lightning';
case 'AVAXC':
return 'cchain';
case 'ARB':
return 'arbitrum';
default:
return tag.toLowerCase();
}
@@ -443,6 +443,8 @@ class ExolixExchangeProvider extends ExchangeProvider {
switch (tag) {
case 'POLY':
return 'Polygon';
case 'ARB':
return 'Arbitrum';
default:
return tag;
}
@@ -433,6 +433,8 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
return 'ERC20';
case 'BSC':
return 'BEP20';
case 'ARB':
return 'ARBITRUM';
default:
return currency.tag!;
}
@@ -346,15 +346,13 @@ class StealthExExchangeProvider extends ExchangeProvider {
// Parsing 'from' currency with network tag
final fromCurrency = deposit['symbol'] as String;
final fromNetwork = deposit['network'] as String?;
final _normalizedFromNetwork = _normalizeNetworkType(fromNetwork ?? '');
final fromTag = _normalizedFromNetwork == 'mainnet' ? null : fromNetwork;
final fromTag = fromNetwork == 'mainnet' ? null : fromNetwork;
final from = CryptoCurrency.safeParseCurrencyFromString(fromCurrency, tag: fromTag);
// Parsing 'to' currency with network tag
final toCurrency = withdrawal['symbol'] as String;
final toNetwork = withdrawal['network'] as String?;
final _normalizedToNetwork = _normalizeNetworkType(toNetwork ?? '');
final toTag = _normalizedToNetwork == 'mainnet' ? null : toNetwork;
final toTag = toNetwork == 'mainnet' ? null : toNetwork;
final to = CryptoCurrency.safeParseCurrencyFromString(toCurrency, tag: toTag);
final payoutAddress = withdrawal['address'] as String;
@@ -381,7 +379,8 @@ class StealthExExchangeProvider extends ExchangeProvider {
createdAt: createdAt,
isRefund: status == 'refunded',
extraId: extraId,
userCurrencyFromRaw: '${fromCurrency.toUpperCase()}' + '_' + '${fromTag?.toUpperCase() ?? ''}',
userCurrencyFromRaw:
'${fromCurrency.toUpperCase()}' + '_' + '${fromTag?.toUpperCase() ?? ''}',
userCurrencyToRaw: '${toCurrency.toUpperCase()}' + '_' + '${toTag?.toUpperCase() ?? ''}',
);
}
@@ -436,21 +435,13 @@ class StealthExExchangeProvider extends ExchangeProvider {
return null;
}
String _getName(CryptoCurrency currency) {
if (currency == CryptoCurrency.usdcEPoly) return 'usdce';
return currency.title.toLowerCase();
}
String _normalizeNetworkType(String network) {
return switch (network.toUpperCase()) {
'ARBITRUM' => 'mainnet',
_ => network,
};
}
String _getNetwork(CryptoCurrency currency) {
if (currency == CryptoCurrency.arb) return 'arbitrum';
if (currency == CryptoCurrency.arb || currency.tag == 'ARB') return 'arbitrum';
if (currency.tag == null) return 'mainnet';
if (currency == CryptoCurrency.maticpoly) return 'mainnet';
@@ -751,6 +751,7 @@ class SwapsXyzExchangeProvider extends ExchangeProvider {
'KAS' => 'KASPA',
'TON' => 'TONCOIN',
'BCH' => 'BITCOIN CASH',
'ARB' => 'ARBITRUM',
_ => network.toUpperCase(),
};
}
@@ -64,7 +64,6 @@ class TrocadorExchangeProvider extends ExchangeProvider {
static const coinPath = '/coin';
static const providersListPath = '/exchanges';
String _lastUsedRateId;
List<dynamic> _provider;
@@ -101,7 +100,6 @@ class TrocadorExchangeProvider extends ExchangeProvider {
final uri = await _getUri(coinPath, params);
final response = await ProxyWrapper().get(clearnetUri: uri, headers: {'API-Key': apiKey});
if (response.statusCode != 200)
throw Exception('Unexpected http status: ${response.statusCode}');
@@ -119,13 +117,12 @@ class TrocadorExchangeProvider extends ExchangeProvider {
}
@override
Future<double> fetchRate({
required CryptoCurrency from,
required CryptoCurrency to,
required double amount,
required bool isFixedRateMode,
required bool isReceiveAmount
}) async {
Future<double> fetchRate(
{required CryptoCurrency from,
required CryptoCurrency to,
required double amount,
required bool isFixedRateMode,
required bool isReceiveAmount}) async {
try {
if (amount == 0) return 0.0;
@@ -143,7 +140,6 @@ class TrocadorExchangeProvider extends ExchangeProvider {
final uri = await _getUri(newRatePath, params);
final response = await ProxyWrapper().get(clearnetUri: uri, headers: {'API-Key': apiKey});
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final fromAmount = double.parse(responseJSON['amount_from'].toString());
@@ -240,7 +236,7 @@ class TrocadorExchangeProvider extends ExchangeProvider {
if (isFixedRateMode) 'amount_to': request.toAmount,
'address': request.toAddress,
'refund': request.refundAddress,
'refund_memo' : '0',
'refund_memo': '0',
};
if (isFixedRateMode) {
@@ -279,13 +275,12 @@ class TrocadorExchangeProvider extends ExchangeProvider {
final uri = await _getUri(createTradePath, params);
final response = await ProxyWrapper().get(clearnetUri: uri, headers: {'API-Key': apiKey});
if (response.statusCode == 400) {
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final error = responseJSON['error'] as String;
final message = responseJSON['message'] as String;
ExchangeProviderLogger.logError(
provider: description,
function: 'createTrade',
@@ -304,7 +299,7 @@ class TrocadorExchangeProvider extends ExchangeProvider {
'url': uri.toString(),
},
);
throw Exception('${error}\n$message');
}
@@ -402,10 +397,10 @@ class TrocadorExchangeProvider extends ExchangeProvider {
@override
Future<Trade> findTradeById({required String id}) async {
final uri = await _getUri(tradePath, {'id': id});
return ProxyWrapper().get(clearnetUri: uri, headers: {'API-Key': apiKey}).then((response) async {
return ProxyWrapper()
.get(clearnetUri: uri, headers: {'API-Key': apiKey}).then((response) async {
if (response.statusCode != 200)
throw Exception('Unexpected http status: ${response.statusCode}');
final responseListJson = json.decode(response.body) as List;
final responseJSON = responseListJson.first;
@@ -422,15 +417,22 @@ class TrocadorExchangeProvider extends ExchangeProvider {
final fromCurrency = responseJSON['ticker_from'] as String;
final fromNetwork = responseJSON['network_from'] as String?;
final _normalizedFromNetwork = _normalizeNetworkType(fromNetwork ?? '');
final fromTag = _normalizedFromNetwork.isEmpty || _normalizedFromNetwork == fromCurrency.toUpperCase() || _normalizedFromNetwork == 'Mainnet'
? null : _normalizedFromNetwork;
final fromTag = _normalizedFromNetwork.isEmpty ||
_normalizedFromNetwork == fromCurrency.toUpperCase() ||
_normalizedFromNetwork == 'Mainnet'
? null
: _normalizedFromNetwork;
final from = CryptoCurrency.safeParseCurrencyFromString(fromCurrency, tag: fromTag);
final toCurrency = responseJSON['ticker_to'] as String;
final networkTo = responseJSON['network_to'] as String?;
final _normalizedToNetwork = _normalizeNetworkType(networkTo ?? '');
final toTag = _normalizedToNetwork.isEmpty || _normalizedToNetwork == toCurrency.toUpperCase() || _normalizedFromNetwork == 'Mainnet' ? null : _normalizedToNetwork;
final toTag = _normalizedToNetwork.isEmpty ||
_normalizedToNetwork == toCurrency.toUpperCase() ||
_normalizedFromNetwork == 'Mainnet'
? null
: _normalizedToNetwork;
final to = CryptoCurrency.safeParseCurrencyFromString(toCurrency, tag: toTag);
return Trade(
@@ -448,7 +450,8 @@ class TrocadorExchangeProvider extends ExchangeProvider {
providerId: providerId,
providerName: providerName,
extraId: addressProviderMemo,
userCurrencyFromRaw: '${fromCurrency.toUpperCase()}' + '_' + '${fromTag?.toUpperCase() ?? ''}',
userCurrencyFromRaw:
'${fromCurrency.toUpperCase()}' + '_' + '${fromTag?.toUpperCase() ?? ''}',
userCurrencyToRaw: '${toCurrency.toUpperCase()}' + '_' + '${toTag?.toUpperCase() ?? ''}',
);
});
@@ -457,7 +460,6 @@ class TrocadorExchangeProvider extends ExchangeProvider {
Future<List<TrocadorPartners>> fetchProviders() async {
final uri = await _getUri(providersListPath, {'api_key': apiKey});
final response = await ProxyWrapper().get(clearnetUri: uri);
if (response.statusCode != 200)
throw Exception('Unexpected http status: ${response.statusCode}');
@@ -485,6 +487,8 @@ class TrocadorExchangeProvider extends ExchangeProvider {
return 'MATIC';
case CryptoCurrency.zec:
return 'Mainnet';
case CryptoCurrency.arb:
return 'Mainnet';
default:
return currency.tag != null ? _normalizeTag(currency.tag!) : 'Mainnet';
}
@@ -502,6 +506,10 @@ class TrocadorExchangeProvider extends ExchangeProvider {
}
String _normalizeTag(String tag) {
if (tag.contains('ARB')) {
return 'Arbitrum';
}
switch (tag) {
case 'ETH':
return 'ERC20';
@@ -527,8 +535,6 @@ class TrocadorExchangeProvider extends ExchangeProvider {
};
}
Future<Uri> _getUri(String path, Map<String, String> queryParams) async {
final uri = Uri.http(onionApiAuthority, path, queryParams);
@@ -44,6 +44,7 @@ class XOSwapExchangeProvider extends ExchangeProvider {
'EOS': 'eosio',
'XLM': 'stellar',
'BASE': 'basemainnet',
'ARB': 'arbitrum',
};
static const supportedTags = [
@@ -64,6 +65,7 @@ class XOSwapExchangeProvider extends ExchangeProvider {
'EOS',
'XLM',
'BASE',
'ARB',
];
+26 -5
View File
@@ -1,3 +1,5 @@
import 'package:cake_wallet/entities/generate_name.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/exchange/trade_state.dart';
import 'package:cw_core/crypto_currency.dart';
@@ -41,12 +43,13 @@ class Trade extends HiveObject {
this.routerValue,
this.routerChainId,
this.sourceTokenAmountRaw,
this.requiresTokenApproval
this.requiresTokenApproval,
this.chainId,
}) {
if (provider != null) providerRaw = provider.raw;
fromRaw = from?.raw ?? -1;
toRaw = to?.raw ?? -1;
toRaw = to?.raw ?? -1;
if (state != null) stateRaw = state.raw;
}
@@ -128,7 +131,9 @@ class Trade extends HiveObject {
bool? isRefund;
@HiveField(21)
bool? isSendAll; /// Must be set on createTrade;
bool? isSendAll;
/// Must be set on createTrade;
@HiveField(22)
String? router;
@@ -167,13 +172,18 @@ class Trade extends HiveObject {
@HiveField(33, defaultValue: false)
bool? requiresTokenApproval;
@HiveField(34)
int? chainId;
CryptoCurrency? get userCurrencyFrom {
if (userCurrencyFromRaw == null || userCurrencyFromRaw!.isEmpty) {
return null;
}
final underscoreIndex = userCurrencyFromRaw!.indexOf('_');
final title = userCurrencyFromRaw!.substring(0, underscoreIndex);
final tag = userCurrencyFromRaw!.substring(underscoreIndex + 1);
String tag = userCurrencyFromRaw!.substring(underscoreIndex + 1);
if (tag.contains('ARB')) tag = 'ARB';
return CryptoCurrency(
title: title,
@@ -201,6 +211,12 @@ class Trade extends HiveObject {
);
}
String get chainName {
if (chainId == null) return '';
return evm!.getChainNameByChainId(chainId!).capitalized();
}
static Trade fromMap(Map<String, Object?> map) {
return Trade(
id: map['id'] as String,
@@ -219,6 +235,7 @@ class Trade extends HiveObject {
isSendAll: map['isSendAll'] as bool?,
router: map['router'] as String?,
extraId: map['extra_id'] as String?,
chainId: map['chain_id'] as int?,
);
}
@@ -239,6 +256,7 @@ class Trade extends HiveObject {
'isSendAll': isSendAll,
'router': router,
'extra_id': extraId,
'chain_id': chainId,
};
}
@@ -291,6 +309,7 @@ class TradeAdapter extends TypeAdapter<Trade> {
routerChainId: fields[31] as int?,
sourceTokenAmountRaw: fields[32] as String?,
requiresTokenApproval: fields[33] as bool?,
chainId: fields[34] as int?,
)
..providerRaw = fields[1] == null ? 0 : fields[1] as int
..fromRaw = (fields[2] as int?) ?? -1
@@ -369,7 +388,9 @@ class TradeAdapter extends TypeAdapter<Trade> {
..writeByte(32)
..write(obj.sourceTokenAmountRaw)
..writeByte(33)
..write(obj.requiresTokenApproval);
..write(obj.requiresTokenApproval)
..writeByte(34)
..write(obj.chainId);
}
@override
-263
View File
@@ -1,263 +0,0 @@
part of 'polygon.dart';
class CWPolygon extends Polygon {
@override
List<String> getPolygonWordList(String language) => EVMChainMnemonics.englishWordlist;
WalletService createPolygonWalletService(bool isDirect) =>
PolygonWalletService(isDirect, client: PolygonClient());
@override
WalletCredentials createPolygonNewWalletCredentials({
required String name,
String? mnemonic,
WalletInfo? walletInfo,
String? password,
String? passphrase,
}) =>
EVMChainNewWalletCredentials(
name: name,
walletInfo: walletInfo,
password: password,
mnemonic: mnemonic,
passphrase: passphrase,
);
@override
WalletCredentials createPolygonRestoreWalletFromSeedCredentials({
required String name,
required String mnemonic,
required String password,
String? passphrase,
}) =>
EVMChainRestoreWalletFromSeedCredentials(
name: name,
password: password,
mnemonic: mnemonic,
passphrase: passphrase,
);
@override
WalletCredentials createPolygonRestoreWalletFromPrivateKey({
required String name,
required String privateKey,
required String password,
}) =>
EVMChainRestoreWalletFromPrivateKey(name: name, password: password, privateKey: privateKey);
@override
WalletCredentials createPolygonHardwareWalletCredentials({
required String name,
required HardwareAccountData hwAccountData,
WalletInfo? walletInfo,
}) =>
EVMChainRestoreWalletFromHardware(
name: name,
hwAccountData: hwAccountData,
walletInfo: walletInfo,
);
@override
String getAddress(WalletBase wallet) => (wallet as PolygonWallet).walletAddresses.address;
@override
String getPrivateKey(WalletBase wallet) {
final privateKeyHolder = (wallet as PolygonWallet).evmChainPrivateKey;
if (privateKeyHolder is EthPrivateKey) return bytesToHex(privateKeyHolder.privateKey);
return "";
}
@override
String getPublicKey(WalletBase wallet) {
final privateKeyInUnitInt = (wallet as PolygonWallet).evmChainPrivateKey;
return privateKeyInUnitInt.address.hex;
}
@override
TransactionPriority getDefaultTransactionPriority() => EVMChainTransactionPriority.medium;
@override
TransactionPriority getPolygonTransactionPrioritySlow() => EVMChainTransactionPriority.slow;
@override
List<TransactionPriority> getTransactionPriorities() => EVMChainTransactionPriority.all;
@override
TransactionPriority deserializePolygonTransactionPriority(int raw) =>
EVMChainTransactionPriority.deserialize(raw: raw);
Object createPolygonTransactionCredentials(
List<Output> outputs, {
required TransactionPriority priority,
required CryptoCurrency currency,
int? feeRate,
}) =>
EVMChainTransactionCredentials(
outputs
.map(
(out) => OutputInfo(
fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmount,
address: out.address,
note: out.note,
sendAll: out.sendAll,
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
formattedCryptoAmount: out.formattedCryptoAmount,
),
)
.toList(),
priority: priority as EVMChainTransactionPriority,
currency: currency,
feeRate: feeRate,
);
Object createPolygonTransactionCredentialsRaw(
List<OutputInfo> outputs, {
TransactionPriority? priority,
required CryptoCurrency currency,
required int feeRate,
}) =>
EVMChainTransactionCredentials(
outputs,
priority: priority as EVMChainTransactionPriority?,
currency: currency,
feeRate: feeRate,
);
@override
int formatterPolygonParseAmount(String amount) => EVMChainFormatter.parseEVMChainAmount(amount);
@override
double formatterPolygonAmountToDouble({
TransactionInfo? transaction,
BigInt? amount,
int exponent = 18,
}) {
assert(transaction != null || amount != null);
if (transaction != null) {
transaction as EVMChainTransactionInfo;
return transaction.ethAmount / BigInt.from(10).pow(transaction.exponent);
} else {
return (amount!) / BigInt.from(10).pow(exponent);
}
}
@override
List<Erc20Token> getERC20Currencies(WalletBase wallet) =>
(wallet as PolygonWallet).erc20Currencies;
@override
Future<void> addErc20Token(WalletBase wallet, CryptoCurrency token) =>
(wallet as PolygonWallet).addErc20Token(token as Erc20Token);
@override
Future<void> deleteErc20Token(WalletBase wallet, CryptoCurrency token) =>
(wallet as PolygonWallet).deleteErc20Token(token as Erc20Token);
@override
Future<void> removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token) =>
(wallet as PolygonWallet).removeTokenTransactionsInHistory(token as Erc20Token);
@override
Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress) =>
(wallet as PolygonWallet).getErc20Token(contractAddress, 'polygon');
@override
CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) {
transaction as EVMChainTransactionInfo;
if (transaction.tokenSymbol == CryptoCurrency.maticpoly.title ||
transaction.tokenSymbol == "MATIC") {
return CryptoCurrency.maticpoly;
}
wallet as PolygonWallet;
return wallet.erc20Currencies.firstWhere(
(element) =>
transaction.contractAddress?.toLowerCase() == element.contractAddress?.toLowerCase(),
);
}
@override
void updatePolygonScanUsageState(WalletBase wallet, bool isEnabled) =>
(wallet as PolygonWallet).updateScanProviderUsageState(isEnabled);
@override
Web3Client? getWeb3Client(WalletBase wallet) => (wallet as PolygonWallet).getWeb3Client();
@override
String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress;
Future<bool> isApprovalRequired(
WalletBase wallet, String tokenContract, String spender, BigInt requiredAmount) =>
(wallet as PolygonWallet).isApprovalRequired(tokenContract, spender, requiredAmount);
Future<PendingTransaction> createRawCallDataTransaction(WalletBase wallet, String to,
String dataHex, BigInt valueWei, TransactionPriority priority) =>
(wallet as EVMChainWallet).createCallDataTransaction(
to, dataHex, valueWei, priority as EVMChainTransactionPriority);
@override
Future<PendingTransaction> createTokenApproval(
WalletBase wallet,
BigInt amount,
String spender,
CryptoCurrency token,
TransactionPriority priority,
) =>
(wallet as EVMChainWallet).createApprovalTransaction(
amount,
spender,
token,
priority as EVMChainTransactionPriority,
"POL",
);
@override
Future<void> setHardwareWalletService(WalletBase wallet, HardwareWalletService service) async {
if (service is EVMChainLedgerService) {
((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedgerConnection(
service.ledgerConnection, (await wallet.walletInfo.getDerivationInfo()).derivationPath);
} else if (service is EVMChainBitboxService) {
((wallet as EVMChainWallet).evmChainPrivateKey as EvmBitboxCredentials)
.setBitbox(service.manager, (await wallet.walletInfo.getDerivationInfo()).derivationPath);
} else if (service is EVMChainTrezorService) {
((wallet as EVMChainWallet).evmChainPrivateKey as EvmTrezorCredentials).setTrezorConnect(
service.connect, (await wallet.walletInfo.getDerivationInfo()).derivationPath);
}
}
@override
HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection) =>
EVMChainLedgerService(connection);
@override
HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager) =>
EVMChainBitboxService(manager, chainId: 137);
@override
HardwareWalletService getTrezorHardwareWalletService(trezor.TrezorConnect connect) =>
EVMChainTrezorService(connect, chainId: 137);
@override
List<String> getDefaultTokenContractAddresses() =>
DefaultPolygonErc20Tokens().initialPolygonErc20Tokens.map((e) => e.contractAddress).toList();
@override
bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress) {
final polygonWallet = wallet as PolygonWallet;
return polygonWallet.erc20Currencies.any(
(element) => element.contractAddress.toLowerCase() == contractAddress.toLowerCase(),
);
}
@override
String? getPolygonNativeEstimatedFee(WalletBase wallet) =>
(wallet as EVMChainWallet).nativeTxEstimatedFee;
@override
String? getPolygonERC20EstimatedFee(WalletBase wallet) =>
(wallet as EVMChainWallet).erc20TxEstimatedFee;
}
+11 -3
View File
@@ -1,5 +1,6 @@
import 'dart:async';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/utils/tor.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:cw_core/utils/print_verbose.dart';
@@ -7,6 +8,7 @@ import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/sync_status.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/evm/evm.dart';
Timer? _checkConnectionTimer;
@@ -34,14 +36,20 @@ void startCheckConnectionReaction(WalletBase wallet, SettingsStore settingsStore
if (wallet.type != WalletType.bitcoin &&
(wallet.syncStatus is LostConnectionSyncStatus ||
wallet.syncStatus is FailedSyncStatus)) {
final alive = await settingsStore.getCurrentNode(wallet.type).requestNode();
int? chainId;
if (isEVMCompatibleChain(wallet.type)) {
chainId = evm!.getSelectedChainId(wallet);
}
final node = settingsStore.getCurrentNode(wallet.type, chainId: chainId);
final alive = await node.requestNode();
if (alive) {
if (settingsStore.currentBuiltinTor) {
await ensureTorStarted(context: null);
}
await wallet.connectToNode(node: settingsStore.getCurrentNode(wallet.type));
await wallet.connectToNode(node: node);
}
}
} catch (e) {
+4 -20
View File
@@ -1,10 +1,8 @@
import 'dart:async';
import 'package:cake_wallet/arbitrum/arbitrum.dart';
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/core/fiat_conversion_service.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
@@ -36,23 +34,9 @@ Future<void> startFiatRateUpdate(
torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly);
Iterable<CryptoCurrency>? currencies;
if (appStore.wallet!.type == WalletType.ethereum) {
if (isEVMCompatibleChain(appStore.wallet!.type)) {
currencies =
ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
}
if (appStore.wallet!.type == WalletType.polygon) {
currencies =
polygon!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
}
if (appStore.wallet!.type == WalletType.base) {
currencies =
base!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
}
if (appStore.wallet!.type == WalletType.arbitrum) {
currencies =
arbitrum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
evm!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
}
if (appStore.wallet!.type == WalletType.solana) {
+11 -21
View File
@@ -1,12 +1,10 @@
import 'package:cake_wallet/arbitrum/arbitrum.dart';
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cake_wallet/entities/wallet_manager.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/utils/tor.dart';
@@ -67,7 +65,12 @@ void startCurrentWalletChangeReaction(
await getIt.get<WalletManager>().ensureGroupHasHashedIdentifier(wallet);
final node = settingsStore.getCurrentNode(wallet.type);
int? chainId;
if (isEVMCompatibleChain(wallet.type)) {
chainId = evm!.getSelectedChainId(wallet);
}
final node = settingsStore.getCurrentNode(wallet.type, chainId: chainId);
startWalletSyncStatusChangeReaction(wallet, settingsStore);
startCheckConnectionReaction(wallet, settingsStore);
@@ -91,7 +94,7 @@ void startCurrentWalletChangeReaction(
if (settingsStore.currentBuiltinTor) {
await ensureTorStarted(context: null);
}
await wallet.connectToNode(node: node);
SyncingSyncStatus.blockHistory.clear();
if (wallet.type == WalletType.nano || wallet.type == WalletType.banano) {
@@ -124,21 +127,8 @@ void startCurrentWalletChangeReaction(
torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly);
Iterable<CryptoCurrency>? currencies;
if (wallet.type == WalletType.ethereum) {
currencies =
ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
}
if (wallet.type == WalletType.polygon) {
currencies =
polygon!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
}
if (wallet.type == WalletType.base) {
currencies =
base!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
}
if (wallet.type == WalletType.arbitrum) {
currencies =
arbitrum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
if (isEVMCompatibleChain(wallet.type)) {
currencies = evm!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
}
if (wallet.type == WalletType.solana) {
currencies =
+44 -48
View File
@@ -3,6 +3,7 @@ import 'package:cake_wallet/src/screens/wallet_connect/services/chain_service/et
import 'package:cake_wallet/src/screens/wallet_connect/services/chain_service/solana/solana_chain_id.dart';
import 'package:cake_wallet/src/screens/wallet_connect/services/chain_service/solana/solana_supported_methods.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/evm/evm.dart';
bool isEVMCompatibleChain(WalletType walletType) {
switch (walletType) {
@@ -16,13 +17,29 @@ bool isEVMCompatibleChain(WalletType walletType) {
}
}
bool isNFTACtivatedChain(WalletType walletType) {
// Blink Protection is supported on Ethereum and Base chains
//TODO: Add BNB Smart Chain to this list when we integrate it
bool canSupportBlinkProtection(int? chainId) {
if (chainId == null) return false;
return chainId == 1 || chainId == 8453;
}
bool isNFTACtivatedChain(WalletType walletType, int? chainId) {
if (chainId != null) {
switch (chainId) {
case 1:
case 8453:
case 137:
case 42161:
return true;
default:
return false;
}
}
switch (walletType) {
case WalletType.polygon:
case WalletType.ethereum:
case WalletType.base:
case WalletType.solana:
case WalletType.arbitrum:
return true;
default:
return false;
@@ -31,10 +48,10 @@ bool isNFTACtivatedChain(WalletType walletType) {
bool isWalletConnectCompatibleChain(WalletType walletType) {
switch (walletType) {
case WalletType.solana:
case WalletType.polygon:
case WalletType.ethereum:
case WalletType.base:
case WalletType.solana:
case WalletType.arbitrum:
return true;
default:
@@ -42,7 +59,11 @@ bool isWalletConnectCompatibleChain(WalletType walletType) {
}
}
String getChainNameSpaceAndIdBasedOnWalletType(WalletType walletType) {
String getChainNameSpaceAndIdBasedOnWalletType(WalletType walletType, {int? chainId}) {
if (chainId != null) {
return evm!.getCaip2ByChainId(chainId);
}
switch (walletType) {
case WalletType.ethereum:
return EVMChainId.ethereum.chain();
@@ -73,51 +94,26 @@ List<String> getChainSupportedMethodsOnWalletType(WalletType walletType) {
}
}
int getChainIdBasedOnWalletType(WalletType walletType) {
switch (walletType) {
case WalletType.polygon:
return 137;
case WalletType.base:
return 8453;
case WalletType.arbitrum:
return 42161;
// For now, we return eth chain Id as the default, we'll modify as we add more wallets
case WalletType.ethereum:
default:
return 1;
String getChainNameBasedOnWalletType(WalletType walletType, {int? chainId}) {
if (walletType == WalletType.solana) {
return 'mainnet';
}
if (chainId != null) {
return evm!.getChainNameByChainId(chainId);
}
return evm!.getChainNameByWalletType(walletType);
}
String getChainNameBasedOnWalletType(WalletType walletType) {
switch (walletType) {
case WalletType.ethereum:
return 'eth';
case WalletType.polygon:
return 'polygon';
case WalletType.base:
return 'base';
case WalletType.arbitrum:
return 'arbitrum';
case WalletType.solana:
return 'mainnet';
default:
return '';
String getTokenNameBasedOnWalletType(WalletType walletType, {int? chainId}) {
if (walletType == WalletType.solana) {
return 'SOL';
}
}
String getTokenNameBasedOnWalletType(WalletType walletType) {
switch (walletType) {
case WalletType.ethereum:
return 'ETH';
case WalletType.polygon:
return 'MATIC';
case WalletType.base:
return 'BASE';
case WalletType.arbitrum:
return 'ARB';
case WalletType.solana:
return 'SOL';
default:
return '';
if (chainId != null) {
return evm!.getTokenNameByChainId(chainId);
}
return evm!.getTokenNameByWalletType(walletType);
}
@@ -149,7 +149,9 @@ class _SelectHardwareWalletAccountFormState extends State<SelectHardwareWalletAc
padding: EdgeInsets.only(top: 10),
child: SelectButton(
image: Image.asset(
walletTypeToCryptoCurrency(_walletHardwareRestoreVM.type).iconPath ??
getCryptoCurrencyForWalletListItem(
_walletHardwareRestoreVM.type,
).iconPath ??
'',
height: 24,
width: 24,
+32 -1
View File
@@ -6,11 +6,13 @@ import 'package:cake_wallet/src/screens/dashboard/pages/cake_features_page.dart'
import 'package:cake_wallet/src/screens/dashboard/widgets/page_indicator.dart';
import 'package:cake_wallet/src/screens/wallet_connect/widgets/bottom_sheet/bottom_sheet_listener_widget.dart';
import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart';
import 'package:cake_wallet/src/widgets/evm_switcher.dart';
import 'package:cake_wallet/src/widgets/gradient_background.dart';
import 'package:cake_wallet/src/widgets/haven_wallet_removal_popup.dart';
import 'package:cake_wallet/src/widgets/services_updates_widget.dart';
import 'package:cake_wallet/src/widgets/vulnerable_seeds_popup.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/utils/version_comparator.dart';
import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.dart';
import 'package:cake_wallet/generated/i18n.dart';
@@ -31,7 +33,6 @@ import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:smooth_page_indicator/smooth_page_indicator.dart';
import 'package:cake_wallet/main.dart';
import 'package:cake_wallet/src/screens/release_notes/release_notes_screen.dart';
import 'package:cake_wallet/themes/core/theme_extension.dart';
@@ -144,6 +145,36 @@ class _DashboardPageView extends BasePage {
@override
Widget leading(BuildContext context) {
if (FeatureFlag.isEVMChainSwitcherEnabled &&
dashboardViewModel.isEVMWallet &&
dashboardViewModel.availableChains.isNotEmpty) {
return TextButton(
style: TextButton.styleFrom(
minimumSize: Size(50, 30),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
alignment: Alignment.centerLeft,
),
onPressed: () => showDialog(
context: context,
builder: (context) => EvmSwitcher(
chains: dashboardViewModel.availableChains,
currentChain: dashboardViewModel.currentChain,
onChainSelected: (chainId) => dashboardViewModel.selectChain(chainId),
hiddenChainIds: dashboardViewModel.settingsStore.evmHiddenChainIds,
onHiddenChanged: (hidden) =>
dashboardViewModel.settingsStore.setEvmHiddenChainIds(hidden),
),
),
child: Container(
child: SvgPicture.asset(
'assets/images/evm_switcher.svg',
color: Theme.of(context).colorScheme.onSurfaceVariant,
height: 30,
),
),
);
}
return Observer(
builder: (context) {
return ServicesUpdatesWidget(
@@ -207,40 +207,45 @@ class AddressPage extends BasePage {
),
),
SizedBox(height: 20),
Center(
child: SizedBox(
height: 40,
width: addressListViewModel.walletImages.length * 32.0,
child: Stack(
children: [
for (int i = addressListViewModel.walletImages.length - 1; i >= 0; i--)
Positioned(
left: i * 25.0,
child: Container(
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).colorScheme.surfaceContainer,
width: 3,
),
color: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: BorderRadius.circular(24),
),
child: ClipOval(
child: CakeImageWidget(
height: 35,
width: 35,
imageUrl: addressListViewModel.walletImages[i],
color: addressListViewModel.walletImages.last ==
addressListViewModel.walletImages[i]
? Theme.of(context).colorScheme.onSurfaceVariant
: null,
Observer(
builder: (_) {
final walletImages = addressListViewModel
.getWalletImages(addressListViewModel.selectedChainId);
return Center(
child: SizedBox(
height: 40,
width: walletImages.length * 32.0,
child: Stack(
children: [
for (int i = walletImages.length - 1; i >= 0; i--)
Positioned(
left: i * 25.0,
child: Container(
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).colorScheme.surfaceContainer,
width: 3,
),
color: Theme.of(context).colorScheme.surfaceContainer,
borderRadius: BorderRadius.circular(24),
),
child: ClipOval(
child: CakeImageWidget(
height: 35,
width: 35,
imageUrl: walletImages[i],
color: walletImages.last == walletImages[i]
? Theme.of(context).colorScheme.onSurfaceVariant
: null,
),
),
),
),
),
),
],
),
),
],
),
),
);
},
),
],
),
@@ -22,7 +22,7 @@ class BalancePage extends StatelessWidget {
Widget build(BuildContext context) {
return Observer(
builder: (context) {
final isNFTActivated = isNFTACtivatedChain(dashboardViewModel.type);
final isNFTActivated = isNFTACtivatedChain(dashboardViewModel.type, dashboardViewModel.wallet.chainId);
return DefaultTabController(
key: ValueKey<bool>(isNFTActivated),
length: isNFTActivated ? 2 : 1,
@@ -169,6 +169,21 @@ class CryptoBalanceWidget extends StatelessWidget {
}),
Observer(
builder: (_) {
if (dashboardViewModel.balanceViewModel.formattedBalances.isEmpty) {
return Center(
child: Container(
child: Text(
'Loading balances...',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
height: 1,
),
textAlign: TextAlign.center,
),
),
);
}
return ListView.separated(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
@@ -59,7 +59,7 @@ class CakeFeaturesPage extends StatelessWidget {
),
),
Observer(builder: (_) {
if (dashboardViewModel.type == WalletType.ethereum) {
if (dashboardViewModel.wallet.chainId == 1) {
return DashBoardRoundedCardWidget(
shadowBlur: dashboardViewModel.getShadowBlur(),
shadowSpread: dashboardViewModel.getShadowSpread(),
+2
View File
@@ -24,11 +24,13 @@ class SignPage extends BasePage {
_pages.add(SignForm(
key: signFormKey,
type: signViewModel.wallet.type,
chainId: signViewModel.wallet.chainId,
includeAddress: signViewModel.signIncludesAddress,
));
_pages.add(VerifyForm(
key: verifyFormKey,
type: signViewModel.wallet.type,
chainId: signViewModel.wallet.chainId,
));
}
@@ -11,12 +11,14 @@ class SignForm extends StatefulWidget {
SignForm({
Key? key,
required this.type,
required this.chainId,
required this.includeAddress,
}) : super(key: key);
final WalletType type;
final bool includeAddress;
final int? chainId;
@override
SignFormState createState() => SignFormState();
}
@@ -76,7 +78,7 @@ class SignFormState extends State<SignForm> with AutomaticKeepAliveClientMixin {
onSelectedContact: (contact) {
addressController.text = contact.address;
},
selectedCurrency: walletTypeToCryptoCurrency(widget.type),
selectedCurrency: walletTypeToCryptoCurrency(widget.type, chainId: widget.chainId),
fillColor: Theme.of(context).colorScheme.surface,
),
],
@@ -33,39 +33,40 @@ class TradeRow extends StatelessWidget {
final receiveAmountCrypto = to.toString();
return InkWell(
onTap: onTap,
child: Container(
padding: EdgeInsets.fromLTRB(24, 8, 24, 8),
color: Colors.transparent,
child: Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Stack(
clipBehavior: Clip.none,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(50),
child: ImageUtil.getImageFromPath(
imagePath: provider.image, height: 36, width: 36),),
Positioned(
right: 0,
bottom: 2,
child: Container(
height: 8,
width: 8,
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: _statusColor(context, swapState),
borderRadius: BorderRadius.circular(12),
),
onTap: onTap,
child: Container(
padding: EdgeInsets.fromLTRB(24, 8, 24, 8),
color: Colors.transparent,
child: Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Stack(
clipBehavior: Clip.none,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(50),
child:
ImageUtil.getImageFromPath(imagePath: provider.image, height: 36, width: 36),
),
Positioned(
right: 0,
bottom: 2,
child: Container(
height: 8,
width: 8,
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: _statusColor(context, swapState),
borderRadius: BorderRadius.circular(12),
),
),
],
),
SizedBox(width: 12),
Expanded(
child: Column(
),
],
),
SizedBox(width: 12),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
@@ -8,10 +8,12 @@ class VerifyForm extends StatefulWidget {
VerifyForm({
Key? key,
required this.type,
required this.chainId,
}) : super(key: key);
final WalletType type;
final int? chainId;
@override
VerifyFormState createState() => VerifyFormState();
}
@@ -66,7 +68,7 @@ class VerifyFormState extends State<VerifyForm> with AutomaticKeepAliveClientMix
onSelectedContact: (contact) {
addressController.text = contact.address;
},
selectedCurrency: walletTypeToCryptoCurrency(widget.type),
selectedCurrency: walletTypeToCryptoCurrency(widget.type, chainId: widget.chainId),
),
const SizedBox(height: 20),
AddressTextField(
@@ -72,7 +72,7 @@ class ExchangeTradeExternalSendPage extends BasePage {
context,
Routes.fullscreenQR,
arguments: QrViewData(
embeddedImagePath: exchangeTradeViewModel.qrImage,
embeddedImagePath: exchangeTradeViewModel.trade.from?.iconPath,
data: exchangeTradeViewModel.paymentUri?.toString() ??
exchangeTradeViewModel.trade.inputAddress ??
fetchingLabel,
@@ -96,7 +96,7 @@ class ExchangeTradeExternalSendPage extends BasePage {
data: exchangeTradeViewModel.paymentUri?.toString() ??
exchangeTradeViewModel.trade.inputAddress ??
fetchingLabel,
embeddedImagePath: exchangeTradeViewModel.qrImage,
embeddedImagePath: exchangeTradeViewModel.trade.from?.iconPath,
size: 230,
),
),
@@ -257,6 +257,14 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
widget.privacySettingsViewModel.setDisableBulletin(value);
},
),
if (widget.privacySettingsViewModel.canUseBlinkProtection)
SettingsSwitcherCell(
title: S.current.use_blink_protection,
value: widget.privacySettingsViewModel.useBlinkProtection,
onValueChange: (BuildContext _, bool value) {
widget.privacySettingsViewModel.setUseBlinkProtection(value);
},
),
SettingsSwitcherCell(
title: S.current.add_custom_node,
value: widget.privacySettingsViewModel.addCustomNode,
@@ -143,7 +143,7 @@ class WalletTypeFormState extends State<WalletTypeForm> {
child: SelectButton(
key: ValueKey('new_wallet_type_${type.name}_button_key'),
image: Image.asset(
walletTypeToCryptoCurrency(type).iconPath ?? '',
getCryptoCurrencyForWalletListItem(type).iconPath ?? '',
height: 24,
width: 24,
),
@@ -95,7 +95,7 @@ class WalletGroupsDisplayBody extends StatelessWidget {
isSelected:
walletGroupsDisplayViewModel.selectedSingleWallet == wallet,
leadingWidget: Image.asset(
walletTypeToCryptoCurrency(wallet.type).iconPath!,
getCryptoCurrencyForWalletListItem(wallet.type).iconPath!,
width: 32,
height: 32,
),
@@ -123,7 +123,7 @@ class GroupedWalletExpansionTile extends StatelessWidget {
: SizedBox(width: 7),
SizedBox(width: 24),
Image.asset(
walletTypeToCryptoCurrency(item.type).iconPath!,
getCryptoCurrencyForWalletListItem(item.type).iconPath!,
width: 32,
height: 32,
),
+151 -27
View File
@@ -37,6 +37,9 @@ import 'package:cake_wallet/src/widgets/address_text_field.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/store/app_store.dart';
class SendCard extends StatefulWidget {
SendCard({
@@ -176,6 +179,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
walletSwitcherViewModel,
paymentRequest,
);
break;
case PaymentFlowType.currentWalletCompatible:
case PaymentFlowType.error:
@@ -221,7 +225,14 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
paymentRequest,
result,
),
onSwap: () => _handleSwapFlow(paymentViewModel, result),
onSwap: (bottomSheetContext) =>
_handleSwapFlow(paymentViewModel, result, bottomSheetContext),
onSwitchNetwork: () => _handleSwitchNetwork(
paymentViewModel,
walletSwitcherViewModel,
paymentRequest,
result,
),
);
},
);
@@ -245,10 +256,14 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
paymentViewModel: paymentViewModel,
paymentRequest: paymentRequest,
onNext: (PaymentFlowResult newResult) {
if (newResult.addressDetectionResult!.detectedWalletType ==
paymentViewModel.currentWalletType) {
final selectedChainId = newResult.chainId;
final isCompatible = selectedChainId == evm!.getSelectedChainId(sendViewModel.wallet);
if (isCompatible) {
sendViewModel.setSelectedCryptoCurrency(
newResult.addressDetectionResult!.detectedCurrency!.title);
newResult.addressDetectionResult!.detectedCurrency!.title,
);
_applyPaymentRequest(paymentRequest);
} else {
_showPaymentConfirmation(
paymentViewModel,
@@ -286,9 +301,22 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
final success = await walletSwitcherViewModel.switchToSelectedWallet();
if (success) {
if (isEVMCompatibleChain(sendViewModel.wallet.type) && result.chainId != null) {
final appStore = getIt.get<AppStore>();
final node = appStore.settingsStore.getCurrentNode(
sendViewModel.wallet.type,
chainId: result.chainId,
);
await evm!.selectChain(sendViewModel.wallet, result.chainId!, node: node);
}
await sendViewModel.wallet.updateBalance();
sendViewModel
.setSelectedCryptoCurrency(result.addressDetectionResult!.detectedCurrency!.title);
final detectedCurrency = result.addressDetectionResult!.detectedCurrency;
if (detectedCurrency != null) {
sendViewModel.setSelectedCryptoCurrency(detectedCurrency.title);
}
_applyPaymentRequest(paymentRequest);
}
}
@@ -303,7 +331,7 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
Navigator.of(context).pop();
}
if (result.wallet != null) {
if (result.type == PaymentFlowType.singleWallet && result.wallet != null) {
walletSwitcherViewModel.selectWallet(result.wallet!);
final success = await walletSwitcherViewModel.switchToSelectedWallet();
if (success) {
@@ -321,6 +349,17 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
);
}
});
// If EVM wallet and chainId is specified, switch to that chain
if (isEVMCompatibleChain(sendViewModel.wallet.type) && result.chainId != null) {
final appStore = getIt.get<AppStore>();
final node = appStore.settingsStore.getCurrentNode(
sendViewModel.wallet.type,
chainId: result.chainId,
);
await evm!.selectChain(sendViewModel.wallet, result.chainId!, node: node);
}
await Future.delayed(const Duration(seconds: 2));
if (loadingBottomSheetContext != null && loadingBottomSheetContext!.mounted) {
Navigator.of(loadingBottomSheetContext!).pop();
@@ -331,6 +370,94 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
.setSelectedCryptoCurrency(result.addressDetectionResult!.detectedCurrency!.title);
_applyPaymentRequest(paymentRequest);
}
} else if (result.wallets.isNotEmpty && result.wallets.length == 1) {
walletSwitcherViewModel.selectWallet(result.wallets.first);
final success = await walletSwitcherViewModel.switchToSelectedWallet();
if (success) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (context.mounted) {
showModalBottomSheet<void>(
context: context,
isDismissible: false,
builder: (BuildContext context) {
loadingBottomSheetContext = context;
return LoadingBottomSheet(
titleText: S.of(context).loading_your_wallet,
);
},
);
}
});
// If EVM wallet and chainId is specified, switch to that chain
if (isEVMCompatibleChain(sendViewModel.wallet.type) && result.chainId != null) {
final appStore = getIt.get<AppStore>();
final node = appStore.settingsStore.getCurrentNode(
sendViewModel.wallet.type,
chainId: result.chainId,
);
await evm!.selectChain(sendViewModel.wallet, result.chainId!, node: node);
}
await Future.delayed(const Duration(seconds: 2));
if (loadingBottomSheetContext != null && loadingBottomSheetContext!.mounted) {
Navigator.of(loadingBottomSheetContext!).pop();
}
await sendViewModel.wallet.updateBalance();
sendViewModel
.setSelectedCryptoCurrency(result.addressDetectionResult!.detectedCurrency!.title);
_applyPaymentRequest(paymentRequest);
}
}
}
Future<void> _handleSwitchNetwork(
PaymentViewModel paymentViewModel,
WalletSwitcherViewModel walletSwitcherViewModel,
PaymentRequest paymentRequest,
PaymentFlowResult result,
) async {
if (result.type != PaymentFlowType.evmNetworkSelection || result.wallet == null) return;
if (context.mounted && Navigator.of(context).canPop()) {
Navigator.of(context).pop();
}
try {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (context.mounted) {
showModalBottomSheet<void>(
context: context,
isDismissible: false,
builder: (BuildContext context) {
loadingBottomSheetContext = context;
return LoadingBottomSheet(
titleText: S.of(context).loading_your_wallet,
);
},
);
}
});
await paymentViewModel.selectChain();
await Future.delayed(const Duration(seconds: 2));
if (loadingBottomSheetContext != null && loadingBottomSheetContext!.mounted) {
Navigator.of(loadingBottomSheetContext!).pop();
}
await sendViewModel.wallet.updateBalance();
final detectedCurrency = result.addressDetectionResult?.detectedCurrency;
if (detectedCurrency != null) {
sendViewModel.setSelectedCryptoCurrency(detectedCurrency.title);
}
_applyPaymentRequest(paymentRequest);
} catch (e) {
if (loadingBottomSheetContext != null && loadingBottomSheetContext!.mounted) {
Navigator.of(loadingBottomSheetContext!).pop();
}
printV('Switch network error: $e');
}
}
@@ -346,10 +473,17 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
noteController.text = paymentRequest.note;
}
Future<void> _handleSwapFlow(PaymentViewModel paymentViewModel, PaymentFlowResult result) async {
if (mounted && Navigator.of(context).canPop()) {
Navigator.of(context).pop();
}
Future<void> _handleSwapFlow(
PaymentViewModel paymentViewModel,
PaymentFlowResult result,
BuildContext bottomSheetContext,
) async {
Navigator.of(bottomSheetContext).pop();
await Future.delayed(const Duration(milliseconds: 100));
if (!mounted) return;
final bottomSheet = getIt.get<SwapConfirmationBottomSheet>(param1: result);
await showModalBottomSheet<Trade?>(
context: context,
@@ -549,31 +683,21 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
builder: (context, snapshot) {
return GestureDetector(
onTap: () {
sendViewModel.balanceViewModel
.switchBalanceValue();
sendViewModel.balanceViewModel.switchBalanceValue();
},
child: Observer(builder: (_) {
final hidden = sendViewModel
.balanceViewModel.displayMode ==
final hidden = sendViewModel.balanceViewModel.displayMode ==
BalanceDisplayMode.hiddenBalance;
return Text(
hidden
? S.of(context).show_balance_send_page
: (snapshot.data ??
sendViewModel.balance),
: (snapshot.data ?? sendViewModel.balance),
// default to balance while loading
style: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
style: Theme.of(context).textTheme.bodySmall!.copyWith(
fontWeight: FontWeight.w600,
color: hidden
? Theme.of(context)
.colorScheme
.primary
: Theme.of(context)
.colorScheme
.onSurfaceVariant,
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSurfaceVariant,
),
);
}),
@@ -102,6 +102,14 @@ class PrivacyPage extends BasePage {
_privacySettingsViewModel.setDisableBulletin(value);
},
),
if (_privacySettingsViewModel.canUseBlinkProtection)
SettingsSwitcherCell(
title: S.current.use_blink_protection,
value: _privacySettingsViewModel.useBlinkProtection,
onValueChange: (BuildContext _, bool value) {
_privacySettingsViewModel.setUseBlinkProtection(value);
},
),
if (_privacySettingsViewModel.canUseEtherscan)
SettingsSwitcherCell(
title: S.current.etherscan_history,
@@ -1,37 +1,29 @@
import 'package:cake_wallet/evm/evm.dart';
enum EVMChainId {
ethereum,
polygon,
base,
goerli,
mumbai,
arbitrum,
}
extension EVMChainIdX on EVMChainId {
String chain() {
String name = '';
final chainId = _getChainIdForEnum(this);
switch (this) {
case EVMChainId.ethereum:
name = '1';
break;
case EVMChainId.polygon:
name = '137';
break;
case EVMChainId.base:
name = '8453';
break;
case EVMChainId.goerli:
name = '5';
break;
case EVMChainId.arbitrum:
name = '42161';
break;
case EVMChainId.mumbai:
name = '80001';
break;
}
if (chainId == null) return 'eip155:1';
return 'eip155:$name';
return evm!.getCaip2ByChainId(chainId);
}
int? _getChainIdForEnum(EVMChainId id) {
return switch (id) {
EVMChainId.ethereum => 1,
EVMChainId.polygon => 137,
EVMChainId.base => 8453,
EVMChainId.arbitrum => 42161,
};
}
int? get chainId => _getChainIdForEnum(this);
}
@@ -40,7 +40,7 @@ class EvmChainServiceImpl {
Web3Client? web3Client,
}) : ethClient = web3Client ??
Web3Client(
appStore.settingsStore.getCurrentNode(appStore.wallet!.type).uri.toString(),
_getNodeUriForChain(reference, appStore),
ProxyWrapper().getHttpIOClient(),
) {
for (final event in EventsConstants.allEvents) {
@@ -77,6 +77,19 @@ class EvmChainServiceImpl {
String getChainId() => reference.chain();
static String _getNodeUriForChain(EVMChainId reference, AppStore appStore) {
final walletType = appStore.wallet!.type;
if (isEVMCompatibleChain(walletType)) {
final chainId = reference.chainId;
return appStore.settingsStore.getCurrentNode(walletType, chainId: chainId).uri.toString();
}
// For old wallet types, use the wallet type directly
return appStore.settingsStore.getCurrentNode(walletType).uri.toString();
}
Future<void> personalSign(String topic, dynamic parameters) async {
debugPrint('personalSign request: $parameters');
@@ -502,7 +515,7 @@ class EvmChainServiceImpl {
// Get the primary type and types
final primaryType = typedData['primaryType']?.toString() ?? '';
final types = typedData['types'] as Map<String, dynamic>? ?? {};
final types = typedData['types'] as Map<String, dynamic>? ?? {};
final message = typedData['message'] as Map<String, dynamic>? ?? {};
// Build a readable message based on the primary type and its structure
@@ -584,7 +597,6 @@ $messageDetails''';
},
);
final decodedResponse = jsonDecode(response.body)[0] as Map<String, dynamic>;
final symbol = (decodedResponse['symbol'] ?? '') as String;
@@ -1,7 +1,4 @@
import 'package:cake_wallet/arbitrum/arbitrum.dart';
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/src/screens/wallet_connect/services/key_service/chain_key_model.dart';
@@ -22,13 +19,10 @@ class KeyServiceImpl implements WalletConnectKeyService {
static String _getPrivateKeyForWallet(WalletBase wallet) {
switch (wallet.type) {
case WalletType.ethereum:
return ethereum!.getPrivateKey(wallet);
case WalletType.polygon:
return polygon!.getPrivateKey(wallet);
case WalletType.base:
return base!.getPrivateKey(wallet);
case WalletType.arbitrum:
return arbitrum!.getPrivateKey(wallet);
return evm!.getPrivateKey(wallet);
case WalletType.solana:
return solana!.getPrivateKey(wallet);
default:
@@ -39,13 +33,10 @@ class KeyServiceImpl implements WalletConnectKeyService {
static String _getPublicKeyForWallet(WalletBase wallet) {
switch (wallet.type) {
case WalletType.ethereum:
return ethereum!.getPublicKey(wallet);
case WalletType.polygon:
return polygon!.getPublicKey(wallet);
case WalletType.base:
return base!.getPublicKey(wallet);
case WalletType.arbitrum:
return arbitrum!.getPublicKey(wallet);
return evm!.getPublicKey(wallet);
case WalletType.solana:
return solana!.getPublicKey(wallet);
default:
@@ -82,7 +73,12 @@ class KeyServiceImpl implements WalletConnectKeyService {
@override
List<ChainKeyModel> getKeysForChain(WalletBase wallet) {
final chain = getChainNameSpaceAndIdBasedOnWalletType(wallet.type);
int? chainId;
if (isEVMCompatibleChain(wallet.type)) {
final chainInfo = evm!.getCurrentChain(wallet);
chainId = chainInfo?.chainId;
}
final chain = getChainNameSpaceAndIdBasedOnWalletType(wallet.type, chainId: chainId);
final keys = getKeys(wallet);
@@ -105,14 +105,22 @@ abstract class WalletKitServiceBase with Store {
List<ChainKeyModel> chainKeys = walletKeyService.getKeys(appStore.wallet!);
for (final chainKey in chainKeys) {
for (final chainId in chainKey.chains) {
final chainNameSpace = getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type);
if (chainNameSpace == chainId) {
final account = '$chainId:${chainKey.publicKey}';
debugPrint('registerAccount $account');
if (isEVMCompatibleChain(appStore.wallet!.type)) {
// Register account for all EVM chains (chainId is already in eip155:format)
_walletKit.registerAccount(
chainId: chainId,
accountAddress: chainKey.publicKey,
);
} else {
final chainNameSpace = getChainNameSpaceAndIdBasedOnWalletType(
appStore.wallet!.type,
);
if (chainNameSpace == chainId) {
_walletKit.registerAccount(
chainId: chainId,
accountAddress: chainKey.publicKey,
);
}
}
}
}
@@ -382,6 +390,14 @@ abstract class WalletKitServiceBase with Store {
@action
void _onPairingCreate(PairingEvent? args) {
debugPrint('_onPairingCreate $args');
if (args != null && args.topic != null && args.topic!.isNotEmpty) {
// Save the pairing topic when pairing is created
savePairingTopicToLocalStorage(args.topic!);
// Refresh pairings to show the new pairing in the list
_refreshPairings();
}
}
Future<void> _onSessionAuthRequest(SessionAuthRequest? args) async {
@@ -562,9 +578,11 @@ abstract class WalletKitServiceBase with Store {
final filteredPairings = allPairings.where(
(pairing) {
bool isInCurrentTopics = currentTopicsForWallet.contains(pairing.topic);
bool isActive = pairing.active;
// bool isActive = pairing.active;
// bool hasSession = sessions.any((session) => session.pairingTopic == pairing.topic);
return isInCurrentTopics && isActive;
// return isInCurrentTopics && isActive;
return isInCurrentTopics;
},
).toList();
@@ -577,16 +595,30 @@ abstract class WalletKitServiceBase with Store {
}
String getKeyForStoringTopicsForWallet() {
List<ChainKeyModel> chainKeys = walletKeyService.getKeysForChain(appStore.wallet!);
try {
// For EVM wallets, use getKeys() to get all EVM keys
// since the same address works across all EVM chains. For non-EVM wallets, use getKeysForChain()
// to get keys specific to the current chain.
List<ChainKeyModel> chainKeys;
if (isEVMCompatibleChain(appStore.wallet!.type)) {
chainKeys = walletKeyService.getKeys(appStore.wallet!);
if (chainKeys.isEmpty) {
chainKeys = chainKeys
.where((key) => key.chains.any((chain) => chain.startsWith('eip155:')))
.toList();
} else {
chainKeys = walletKeyService.getKeysForChain(appStore.wallet!);
}
if (chainKeys.isEmpty) return '';
final publicKey = chainKeys.first.publicKey;
if (publicKey.isEmpty) return '';
return PreferencesKey.walletConnectPairingTopicsListForWallet(publicKey);
} catch (e) {
return '';
}
final keyForPairingTopic =
PreferencesKey.walletConnectPairingTopicsListForWallet(chainKeys.first.publicKey);
return keyForPairingTopic;
}
List<String> getPairingTopicsForWallet(String key) {
@@ -615,7 +647,8 @@ abstract class WalletKitServiceBase with Store {
final pairingTopicsForWallet = getPairingTopicsForWallet(key);
bool isPairingTopicAlreadySaved = pairingTopicsForWallet.contains(pairingTopic);
debugPrint('Is Pairing Topic Saved: $isPairingTopicAlreadySaved');
debugPrint(
'Is Pairing Topic Saved: $isPairingTopicAlreadySaved, Key: $key, Topic: $pairingTopic');
if (!isPairingTopicAlreadySaved) {
// Update the list with the most recent pairing topic
@@ -30,71 +30,81 @@ class BottomSheetListenerState extends State<BottomSheetListener> {
}
Future<void> _showBottomSheet() async {
if (!mounted) {
return;
}
if (widget.bottomSheetService.currentSheet.value != null) {
BottomSheetQueueItemModel item = widget.bottomSheetService.currentSheet.value!;
final value = await showModalBottomSheet(
context: context,
isDismissible: item.isModalDismissible,
backgroundColor: Color.fromARGB(0, 0, 0, 0),
isScrollControlled: true,
constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.9),
builder: (context) {
if (item.closeAfter > 0) {
Future.delayed(Duration(seconds: item.closeAfter), () {
try {
if (!mounted) return;
if (Navigator.canPop(context)) {
Navigator.pop(context);
try {
final value = await showModalBottomSheet(
context: context,
isDismissible: item.isModalDismissible,
backgroundColor: Color.fromARGB(0, 0, 0, 0),
isScrollControlled: true,
constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.9),
builder: (context) {
if (item.closeAfter > 0) {
Future.delayed(Duration(seconds: item.closeAfter), () {
try {
if (!mounted) return;
if (Navigator.canPop(context)) {
Navigator.pop(context);
}
} catch (e, s) {
debugPrint('[$runtimeType] close $e $s');
}
} catch (e, s) {
debugPrint('[$runtimeType] close $e $s');
}
});
}
return Material(
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.all(Radius.circular(16)),
child: Padding(
padding: EdgeInsets.only(
top: 16,
left: 16,
right: 16,
bottom: MediaQuery.of(context).viewInsets.bottom + 24,
});
}
return Material(
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.all(Radius.circular(16)),
child: Padding(
padding: EdgeInsets.only(
top: 16,
left: 16,
right: 16,
bottom: MediaQuery.of(context).viewInsets.bottom + 24,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Row(
// mainAxisAlignment: MainAxisAlignment.end,
// children: [
// IconButton(
// color: Theme.of(context).colorScheme.surfaceContainerHighest,
// padding: const EdgeInsets.all(0.0),
// visualDensity: VisualDensity.compact,
// onPressed: () {
// if (Navigator.canPop(context)) {
// Navigator.pop(context);
// }
// },
// icon: Icon(
// Icons.close_sharp,
// color: Theme.of(context).colorScheme.onSurfaceVariant,
// ),
// ),
// ],
// ),
Flexible(child: item.widget),
],
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Row(
// mainAxisAlignment: MainAxisAlignment.end,
// children: [
// IconButton(
// color: Theme.of(context).colorScheme.surfaceContainerHighest,
// padding: const EdgeInsets.all(0.0),
// visualDensity: VisualDensity.compact,
// onPressed: () {
// if (Navigator.canPop(context)) {
// Navigator.pop(context);
// }
// },
// icon: Icon(
// Icons.close_sharp,
// color: Theme.of(context).colorScheme.onSurfaceVariant,
// ),
// ),
// ],
// ),
Flexible(child: item.widget),
],
),
),
);
},
);
);
},
);
if (!item.completer.isCompleted) {
item.completer.complete(value);
if (!item.completer.isCompleted) {
item.completer.complete(value);
}
widget.bottomSheetService.showNext();
} catch (e) {
if (!item.completer.isCompleted) {
item.completer.complete(null);
}
widget.bottomSheetService.showNext();
}
widget.bottomSheetService.showNext();
}
}
@@ -48,20 +48,19 @@ class WalletListPage extends BasePage {
String get title => S.current.wallets;
@override
Widget body(BuildContext context) => Observer(
builder: (_) {
if (walletListViewModel.singleWalletsList.isEmpty && walletListViewModel.multiWalletGroups.isEmpty) {
return Center(
child: CircularProgressIndicator(),
Widget body(BuildContext context) => Observer(builder: (_) {
if (walletListViewModel.singleWalletsList.isEmpty &&
walletListViewModel.multiWalletGroups.isEmpty) {
return Center(
child: CircularProgressIndicator(),
);
}
return WalletListBody(
walletListViewModel: walletListViewModel,
authService: authService,
onWalletLoaded: onWalletLoaded ?? (context) => Navigator.of(context).pop(),
);
}
return WalletListBody(
walletListViewModel: walletListViewModel,
authService: authService,
onWalletLoaded: onWalletLoaded ?? (context) => Navigator.of(context).pop(),
);
}
);
});
@override
Widget trailing(BuildContext context) {
@@ -222,7 +221,7 @@ class WalletListBodyState extends State<WalletListBody> {
return item.isCurrent
? SizedBox.shrink()
: EditWalletButtonWidget(
width: 60,
width: 64,
onTap: () => Navigator.of(context).pushNamed(
Routes.walletEdit,
arguments: WalletEditPageArguments(
@@ -288,7 +287,9 @@ class WalletListBodyState extends State<WalletListBody> {
)
: SizedBox(width: 6),
Image.asset(
walletTypeToCryptoCurrency(wallet.type).iconPath!,
getCryptoCurrencyForWalletListItem(
wallet.type,
).iconPath!,
width: 32,
height: 32,
),
@@ -1,6 +1,6 @@
import 'package:cake_wallet/core/universal_address_detector.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
@@ -11,7 +11,6 @@ import 'package:cake_wallet/utils/qr_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/utils/token_utilities.dart';
import 'package:cake_wallet/view_model/payment/payment_view_model.dart';
import 'package:cake_wallet/wallet_types.g.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/currency.dart';
import 'package:cw_core/utils/print_verbose.dart';
@@ -60,21 +59,21 @@ class _EVMPaymentFlowContent extends StatefulWidget {
}
class _EVMPaymentFlowContentState extends State<_EVMPaymentFlowContent> {
WalletType? selectedNetwork;
int? selectedChainId;
CryptoCurrency? selectedToken;
@override
void initState() {
super.initState();
selectedNetwork = WalletType.ethereum;
selectedChainId = widget.paymentViewModel.detectedChainId ?? 1;
_autoSelectToken();
}
Future<void> _autoSelectToken() async {
if (selectedNetwork == null) return;
if (selectedChainId == null) return;
try {
final tokens = await TokenUtilities.getAvailableTokensForNetwork(selectedNetwork!);
final tokens = await TokenUtilities.getAvailableTokensForChainId(selectedChainId!);
if (tokens.isNotEmpty) {
setState(() {
selectedToken = tokens.first;
@@ -124,11 +123,11 @@ class _EVMPaymentFlowContentState extends State<_EVMPaymentFlowContent> {
),
const SizedBox(height: 32),
EVMTileWidget(
value: selectedNetwork != null
? walletTypeToString(selectedNetwork!)
value: selectedChainId != null
? _getChainName(selectedChainId!)
: S.current.select_network,
imagePath: selectedNetwork != null ? getChainMonoImage(selectedNetwork!) : null,
color: selectedNetwork != null ? Theme.of(context).colorScheme.primary : null,
imagePath: selectedChainId != null ? _getChainImagePath(selectedChainId!) : null,
color: selectedChainId != null ? Theme.of(context).colorScheme.primary : null,
enabled: true,
onTap: () => _showNetworkSelection(context),
),
@@ -136,7 +135,7 @@ class _EVMPaymentFlowContentState extends State<_EVMPaymentFlowContent> {
EVMTileWidget(
imagePath: selectedToken != null ? selectedToken!.iconPath : null,
value: selectedToken != null ? selectedToken!.title : S.current.select_token,
enabled: selectedNetwork != null,
enabled: selectedChainId != null,
onTap: () => _showTokenSelection(context),
color: selectedToken == null ? Theme.of(context).colorScheme.primary : null,
),
@@ -145,7 +144,7 @@ class _EVMPaymentFlowContentState extends State<_EVMPaymentFlowContent> {
text: S.current.restore_next,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
onPressed: selectedNetwork != null && selectedToken != null
onPressed: selectedChainId != null && selectedToken != null
? () async => await _handleNext(context)
: null,
),
@@ -156,30 +155,34 @@ class _EVMPaymentFlowContentState extends State<_EVMPaymentFlowContent> {
}
void _showNetworkSelection(BuildContext context) async {
final evmNetworks =
availableWalletTypes.where((walletType) => isEVMCompatibleChain(walletType)).toList();
final selectedIndex = evmNetworks.indexOf(selectedNetwork ?? WalletType.ethereum);
final allChains = evm!.getAllChains();
final chainIds = allChains.map((chainInfo) => chainInfo.chainId).toList();
final selectedIndex = selectedChainId != null ? chainIds.indexOf(selectedChainId!) : 0;
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return Picker(
items: evmNetworks,
displayItem: (WalletType network) => walletTypeToString(network),
selectedAtIndex: selectedIndex,
items: chainIds,
displayItem: (int chainId) => _getChainName(chainId),
selectedAtIndex: selectedIndex >= 0 ? selectedIndex : 0,
title: S.current.select_network,
closeOnItemSelected: true,
hasTitleSpacing: true,
images: evmNetworks
.map((network) => CakeImageWidget(
imageUrl: getChainMonoImage(network),
width: 20,
height: 20,
color: Theme.of(context).colorScheme.primary))
.toList(),
onItemSelected: (WalletType network) {
images: chainIds.map((chainId) {
final imagePath = _getChainImagePath(chainId);
return imagePath != null
? CakeImageWidget(
imageUrl: imagePath,
width: 20,
height: 20,
color: Theme.of(context).colorScheme.primary)
: const SizedBox(width: 20, height: 20);
}).toList(),
onItemSelected: (int chainId) {
setState(() {
selectedNetwork = network;
selectedChainId = chainId;
selectedToken = null;
});
_autoSelectToken();
@@ -189,11 +192,24 @@ class _EVMPaymentFlowContentState extends State<_EVMPaymentFlowContent> {
);
}
String _getChainName(int chainId) {
final allChains = evm!.getAllChains();
final chainInfo = allChains.firstWhere(
(chain) => chain.chainId == chainId,
orElse: () => ChainInfo(chainId: chainId, name: 'Unknown Network', shortCode: 'unknown'),
);
return chainInfo.name;
}
String? _getChainImagePath(int chainId) {
return getChainMonoImage(WalletType.ethereum, selectedChainId: chainId);
}
void _showTokenSelection(BuildContext context) async {
if (selectedNetwork == null) return;
if (selectedChainId == null) return;
try {
final availableTokens = await TokenUtilities.getAvailableTokensForNetwork(selectedNetwork!);
final availableTokens = await TokenUtilities.getAvailableTokensForChainId(selectedChainId!);
if (availableTokens.isEmpty) return;
@@ -220,30 +236,36 @@ class _EVMPaymentFlowContentState extends State<_EVMPaymentFlowContent> {
}
Future<void> _handleNext(BuildContext context) async {
if (selectedNetwork == null || selectedToken == null) return;
if (selectedChainId == null || selectedToken == null) return;
Navigator.of(context).pop();
final compatibleWallets = await widget.paymentViewModel.getWalletsByType(selectedNetwork!);
final allEVMWallets = await widget.paymentViewModel.getEVMCompatibleWallets();
final newResult = PaymentFlowResult.evmNetworkSelection(
AddressDetectionResult(
address: widget.paymentRequest.address,
detectedWalletType: selectedNetwork!,
detectedCurrency: selectedToken!,
isValid: true,
amount: widget.paymentRequest.amount,
note: widget.paymentRequest.note,
scheme: widget.paymentRequest.scheme,
pjUri: widget.paymentRequest.pjUri,
callbackUrl: widget.paymentRequest.callbackUrl,
callbackMessage: widget.paymentRequest.callbackMessage,
),
compatibleWallets: compatibleWallets,
wallet: compatibleWallets.isNotEmpty ? compatibleWallets.first : null,
final walletType = evm!.getWalletTypeByChainId(selectedChainId!) ?? WalletType.ethereum;
final detectionResult = AddressDetectionResult(
address: widget.paymentRequest.address,
detectedWalletType: walletType,
detectedCurrency: selectedToken!,
chainId: selectedChainId,
isValid: true,
amount: widget.paymentRequest.amount,
note: widget.paymentRequest.note,
scheme: widget.paymentRequest.scheme,
pjUri: widget.paymentRequest.pjUri,
callbackUrl: widget.paymentRequest.callbackUrl,
callbackMessage: widget.paymentRequest.callbackMessage,
);
widget.paymentViewModel.detectedWalletType = selectedNetwork!;
final newResult = PaymentFlowResult.evmNetworkSelection(
detectionResult,
compatibleWallets: allEVMWallets,
wallet: allEVMWallets.isNotEmpty ? allEVMWallets.first : null,
);
widget.paymentViewModel.detectedWalletType = walletType;
widget.onNext(newResult);
}
}
@@ -1,3 +1,5 @@
import 'package:cake_wallet/entities/generate_name.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/utils/payment_request.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
@@ -7,6 +9,7 @@ import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/view_model/payment/payment_view_model.dart';
import 'package:cake_wallet/view_model/wallet_switcher_view_model.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class PaymentConfirmationBottomSheet extends BaseBottomSheet {
@@ -19,6 +22,7 @@ class PaymentConfirmationBottomSheet extends BaseBottomSheet {
required this.onSelectWallet,
required this.onChangeWallet,
required this.onSwap,
this.onSwitchNetwork,
}) : super(
titleText: '',
footerType: FooterType.none,
@@ -31,7 +35,8 @@ class PaymentConfirmationBottomSheet extends BaseBottomSheet {
final PaymentRequest paymentRequest;
final VoidCallback onSelectWallet;
final VoidCallback onChangeWallet;
final VoidCallback onSwap;
final void Function(BuildContext) onSwap;
final VoidCallback? onSwitchNetwork;
@override
Widget contentWidget(BuildContext context) {
@@ -43,6 +48,7 @@ class PaymentConfirmationBottomSheet extends BaseBottomSheet {
onSelectWallet: onSelectWallet,
onChangeWallet: onChangeWallet,
onSwap: onSwap,
onSwitchNetwork: onSwitchNetwork,
);
}
}
@@ -56,6 +62,7 @@ class _PaymentConfirmationContent extends StatelessWidget {
required this.onSelectWallet,
required this.onChangeWallet,
required this.onSwap,
this.onSwitchNetwork,
});
final PaymentFlowResult paymentFlowResult;
@@ -64,7 +71,8 @@ class _PaymentConfirmationContent extends StatelessWidget {
final PaymentRequest paymentRequest;
final VoidCallback onSelectWallet;
final VoidCallback onChangeWallet;
final VoidCallback onSwap;
final void Function(BuildContext) onSwap;
final VoidCallback? onSwitchNetwork;
/// Checks if the given address is a MWEB or SP (Silent Payment) address
bool _isMwebOrSpAddress(String address) {
@@ -86,7 +94,9 @@ class _PaymentConfirmationContent extends StatelessWidget {
return Observer(
builder: (_) {
final currencyName = walletTypeToString(paymentViewModel.detectedWalletType!);
final currentWalletName = walletTypeToString(paymentViewModel.currentWalletType);
final currentWalletName = isEVMCompatibleChain(paymentViewModel.currentWalletType)
? evm!.getChainNameByChainId(paymentViewModel.currentChainId!)
: walletTypeToString(paymentViewModel.currentWalletType);
final hasSingleWallet = paymentFlowResult.type == PaymentFlowType.singleWallet ||
paymentFlowResult.wallets.length == 1;
@@ -102,6 +112,15 @@ class _PaymentConfirmationContent extends StatelessWidget {
final isMwebOrSpAddress =
_isMwebOrSpAddress(paymentFlowResult.addressDetectionResult?.address ?? '');
/// If the wallet is EVM but the detected chainId is different from the currently selected chainId
final isEVMWalletButDifferentChainId =
paymentFlowResult.type == PaymentFlowType.evmNetworkSelection &&
paymentFlowResult.wallet != null &&
isEVMCompatibleChain(paymentViewModel.currentWalletType);
final otherWalletsCount = paymentFlowResult.wallets.length;
final hasMultipleOtherWallets = otherWalletsCount > 1;
return Container(
padding: const EdgeInsets.fromLTRB(24, 0, 24, 24),
child: Column(
@@ -121,7 +140,10 @@ class _PaymentConfirmationContent extends StatelessWidget {
bottom: 0,
right: 0,
child: Image.asset(
walletTypeToCryptoCurrency(paymentViewModel.detectedWalletType!).iconPath!,
getCryptoCurrencyForWalletListItem(
paymentViewModel.detectedWalletType!,
chainId: paymentViewModel.detectedChainId,
).iconPath!,
width: 32,
height: 32,
),
@@ -141,7 +163,7 @@ class _PaymentConfirmationContent extends StatelessWidget {
),
const SizedBox(height: 24),
Text(
'''Would you like to ${!noAvailableWallets ? 'switch to a $currencyName wallet or' : ''} swap $currentWalletName for ${paymentFlowResult.addressDetectionResult?.detectedCurrency} (${walletTypeToString(paymentFlowResult.walletType!)}) for this payment?''',
'''Would you like to ${!noAvailableWallets ? 'switch to a $currencyName wallet or' : ''} swap ${currentWalletName.capitalized()} for ${paymentFlowResult.addressDetectionResult?.detectedCurrency} (${walletTypeToString(paymentFlowResult.walletType!)}) for this payment?''',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 16,
@@ -152,7 +174,10 @@ class _PaymentConfirmationContent extends StatelessWidget {
),
] else ...[
Image.asset(
walletTypeToCryptoCurrency(paymentViewModel.detectedWalletType!).iconPath!,
getCryptoCurrencyForWalletListItem(
paymentViewModel.detectedWalletType!,
chainId: paymentViewModel.detectedChainId,
).iconPath!,
width: 118,
height: 118,
),
@@ -170,7 +195,7 @@ class _PaymentConfirmationContent extends StatelessWidget {
const SizedBox(height: 24),
Text(
'''Looks like you scanned a $currencyName address.\n\n'''
'''Would you like to ${!noAvailableWallets ? 'switch to a $currencyName wallet or' : ''} swap $currentWalletName for $currencyName for this payment?''',
'''Would you like to ${!noAvailableWallets ? 'switch to a $currencyName wallet or' : ''} swap ${currentWalletName.capitalized()} for $currencyName for this payment?''',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 16,
@@ -181,11 +206,44 @@ class _PaymentConfirmationContent extends StatelessWidget {
),
],
const SizedBox(height: 72),
if (hasAtLeastOneWallet) ...[
if (isEVMWalletButDifferentChainId) ...[
if (!isMwebOrSpAddress) ...[
PrimaryButton(
onPressed: onSwap,
text: '${S.current.swap} $currentWalletName',
onPressed: () => onSwap(context),
text: 'Create In App Swap',
color: hasAtLeastOneWallet
? Theme.of(context).colorScheme.surfaceContainer
: Theme.of(context).colorScheme.primary,
textColor: hasAtLeastOneWallet
? Theme.of(context).colorScheme.onSecondaryContainer
: Theme.of(context).colorScheme.onPrimary,
),
const SizedBox(height: 10),
],
if (onSwitchNetwork != null) ...[
PrimaryButton(
onPressed: onSwitchNetwork,
text:
'Switch to ${evm!.getChainNameByChainId(paymentViewModel.detectedChainId!).toUpperCase()} Network',
color: Theme.of(context).colorScheme.surfaceContainer,
textColor: Theme.of(context).colorScheme.onSecondaryContainer,
),
const SizedBox(height: 10),
],
if (hasAtLeastOneWallet) ...[
PrimaryButton(
onPressed: hasMultipleOtherWallets ? onSelectWallet : onChangeWallet,
text: S.current.change_wallet_alert_title,
color: Theme.of(context).colorScheme.primary,
textColor: Theme.of(context).colorScheme.onPrimary,
),
const SizedBox(height: 10),
],
] else if (hasAtLeastOneWallet) ...[
if (!isMwebOrSpAddress) ...[
PrimaryButton(
onPressed: () => onSwap(context),
text: '${S.current.swap} ${currentWalletName.capitalized()}',
color: Theme.of(context).colorScheme.surfaceContainer,
textColor: Theme.of(context).colorScheme.onSecondaryContainer,
),
@@ -208,8 +266,8 @@ class _PaymentConfirmationContent extends StatelessWidget {
const SizedBox(height: 10),
if (!isMwebOrSpAddress) ...[
PrimaryButton(
onPressed: onSwap,
text: '${S.current.swap} $currentWalletName',
onPressed: () => onSwap(context),
text: '${S.current.swap} ${currentWalletName.capitalized()}',
color: hasAtLeastOneWallet
? Theme.of(context).colorScheme.surfaceContainer
: Theme.of(context).colorScheme.primary,
@@ -3,6 +3,7 @@ import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/exchange/limits_state.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/present_provider_picker.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/swap_details_bottom_sheet.dart';
@@ -166,16 +167,20 @@ class SwapConfirmationContentState extends State<SwapConfirmationContent> {
width: 32,
height: 32,
),
Positioned(
bottom: -4,
right: -4,
child: CakeImageWidget(
imageUrl: walletTypeToCryptoCurrency(widget.paymentFlowResult.walletType!)
.iconPath!,
width: 16,
height: 16,
if (isEVMCompatibleChain(widget.paymentFlowResult.walletType!)) ...[
Positioned(
bottom: -4,
right: -4,
child: CakeImageWidget(
imageUrl: getCryptoCurrencyForWalletListItem(
widget.paymentFlowResult.walletType!,
chainId: widget.paymentFlowResult.chainId,
).iconPath!,
width: 16,
height: 16,
),
),
),
],
],
),
],
@@ -34,11 +34,14 @@ class _SwapDetailsBottomSheetState extends State<SwapDetailsBottomSheet> {
bool _effectsInstalled = false;
ReactionDisposer? _exchangeStateReaction;
BuildContext? _loadingBottomSheetContext;
bool _showingFailureDialog = false;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => _setEffects());
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) _setEffects();
});
}
@override
@@ -49,7 +52,39 @@ class _SwapDetailsBottomSheetState extends State<SwapDetailsBottomSheet> {
}
void _setEffects() {
if (_effectsInstalled) return;
if (_effectsInstalled) {
printV('Swap details bottom sheet effects already installed');
return;
}
final initialState = widget.exchangeTradeViewModel.sendViewModel.state;
if (initialState is FailureState && !_showingFailureDialog) {
_showingFailureDialog = true;
printV('Initial failure state: $initialState');
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted && context.mounted) {
showPopUp<void>(
context: context,
builder: (BuildContext popupContext) {
return AlertWithOneAction(
key: ValueKey('swap_details_send_failure_dialog_key'),
buttonKey: ValueKey('swap_details_send_failure_dialog_button_key'),
alertTitle: S.of(popupContext).error,
alertContent: initialState.error,
buttonText: S.of(popupContext).ok,
buttonAction: () {
_showingFailureDialog = false;
Navigator.of(popupContext).pop();
},
);
},
);
} else {
_showingFailureDialog = false;
}
});
}
_exchangeStateReaction = reaction(
(_) => widget.exchangeTradeViewModel.sendViewModel.state,
@@ -61,21 +96,29 @@ class _SwapDetailsBottomSheetState extends State<SwapDetailsBottomSheet> {
Navigator.of(_loadingBottomSheetContext!).pop();
}
if (state is FailureState) {
if (state is FailureState && !_showingFailureDialog) {
_showingFailureDialog = true;
WidgetsBinding.instance.addPostFrameCallback((_) {
showPopUp<void>(
context: context,
builder: (BuildContext popupContext) {
return AlertWithOneAction(
key: ValueKey('swap_details_send_failure_dialog_key'),
buttonKey: ValueKey('swap_details_send_failure_dialog_button_key'),
alertTitle: S.of(popupContext).error,
alertContent: state.error,
buttonText: S.of(popupContext).ok,
buttonAction: () => Navigator.of(popupContext).pop(),
);
},
);
if (mounted && context.mounted) {
showPopUp<void>(
context: context,
builder: (BuildContext popupContext) {
return AlertWithOneAction(
key: ValueKey('swap_details_send_failure_dialog_key'),
buttonKey: ValueKey('swap_details_send_failure_dialog_button_key'),
alertTitle: S.of(popupContext).error,
alertContent: state.error,
buttonText: S.of(popupContext).ok,
buttonAction: () {
_showingFailureDialog = false;
Navigator.of(popupContext).pop();
},
);
},
);
} else {
_showingFailureDialog = false;
}
});
}
@@ -272,13 +315,15 @@ class _SwapDetailsContent extends StatelessWidget {
children: [
_SwapDetailsTile(
label: 'You Send',
value: '${trade.amount} ${trade.from?.title ?? trade.userCurrencyFrom?.title ?? ''}',
value:
'${trade.amount} ${trade.from?.title ?? trade.userCurrencyFrom?.title ?? ''}',
valueFiatFormatted: exchangeTradeViewModel.sendAmountFiatFormatted,
),
const SizedBox(height: 8),
_SwapDetailsTile(
label: 'You Get',
value: '${trade.receiveAmount ?? '0'} ${trade.to?.title ?? trade.userCurrencyTo?.title ?? ''}',
value:
'${trade.receiveAmount ?? '0'} ${trade.to?.title ?? trade.userCurrencyTo?.title ?? ''}',
valueFiatFormatted: exchangeTradeViewModel
.getReceiveAmountFiatFormatted(trade.receiveAmount ?? '0.0'),
),
@@ -57,7 +57,7 @@ class _WalletSwitcherContent extends StatelessWidget {
builder: (context, snapshot) => Observer(
builder: (_) {
final List<WalletInfo> wallets = (snapshot.data ?? []);
if (viewModel.isProcessing) {
return Container(
height: 200,
@@ -68,7 +68,7 @@ class _WalletSwitcherContent extends StatelessWidget {
),
);
}
return Container(
height: 400,
child: Column(
@@ -79,7 +79,7 @@ class _WalletSwitcherContent extends StatelessWidget {
itemCount: wallets.length,
itemBuilder: (context, index) {
final wallet = wallets[index];
return InkWell(
onTap: () {
viewModel.selectWallet(wallet);
@@ -97,7 +97,9 @@ class _WalletSwitcherContent extends StatelessWidget {
child: Row(
children: [
Image.asset(
walletTypeToCryptoCurrency(wallet.type).iconPath!,
getCryptoCurrencyForWalletListItem(
wallet.type,
).iconPath!,
width: 32,
height: 32,
),
+376
View File
@@ -0,0 +1,376 @@
import 'package:cake_wallet/evm/evm.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'evm_switcher_row.dart';
class EvmSwitcherDataItem {
final String name;
final String svgPath;
final int chainId;
const EvmSwitcherDataItem({
required this.name,
required this.svgPath,
required this.chainId,
});
static const ethereum = EvmSwitcherDataItem(
name: 'Ethereum',
svgPath: 'assets/images/evm_switcher_icons/ethereum.svg',
chainId: 1,
);
static const polygon = EvmSwitcherDataItem(
name: 'Polygon',
svgPath: 'assets/images/evm_switcher_icons/polygon.svg',
chainId: 137,
);
static const arbitrum = EvmSwitcherDataItem(
name: 'Arbitrum',
svgPath: 'assets/images/evm_switcher_icons/arbitrum.svg',
chainId: 42161,
);
static const base = EvmSwitcherDataItem(
name: 'Base',
svgPath: 'assets/images/evm_switcher_icons/base.svg',
chainId: 8453,
);
static const items = [
ethereum,
polygon,
arbitrum,
base,
];
}
String _getSvgPathForChain(String chainName) {
final name = chainName.toLowerCase();
if (name.contains('ethereum')) {
return 'assets/images/evm_switcher_icons/ethereum.svg';
} else if (name.contains('polygon')) {
return 'assets/images/evm_switcher_icons/polygon.svg';
} else if (name.contains('arbitrum')) {
return 'assets/images/evm_switcher_icons/arbitrum.svg';
} else if (name.contains('base')) {
return 'assets/images/evm_switcher_icons/base.svg';
}
// Default to ethereum if unknown
return 'assets/images/evm_switcher_icons/ethereum.svg';
}
class EvmSwitcher extends StatefulWidget {
const EvmSwitcher({
super.key,
required this.chains,
required this.currentChain,
required this.onChainSelected,
required this.hiddenChainIds,
required this.onHiddenChanged,
});
final List<ChainInfo> chains;
final ChainInfo? currentChain;
final Future<void> Function(int chainId) onChainSelected;
final Set<int> hiddenChainIds;
final void Function(Set<int> hiddenChainIds) onHiddenChanged;
static const editModeAnimDuration = Duration(milliseconds: 200);
@override
State<EvmSwitcher> createState() => _EvmSwitcherState();
}
class _EvmSwitcherState extends State<EvmSwitcher> {
bool _editMode = false;
var optionsEnabled = <bool>[];
@override
void initState() {
super.initState();
_syncOptionsEnabled();
}
@override
void didUpdateWidget(covariant EvmSwitcher oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.chains != widget.chains ||
!_hiddenSetsEqual(oldWidget.hiddenChainIds, widget.hiddenChainIds)) {
_syncOptionsEnabled();
}
}
void _syncOptionsEnabled() {
optionsEnabled = widget.chains
.map((chain) => !widget.hiddenChainIds.contains(chain.chainId))
.toList(growable: false);
}
bool _hiddenSetsEqual(Set<int> a, Set<int> b) =>
a.length == b.length && a.containsAll(b);
int get _selectedIndex {
if (widget.currentChain == null) return -1;
return widget.chains.indexWhere(
(chain) => chain.chainId == widget.currentChain!.chainId,
);
}
bool shouldBuildSeparator(int index) {
final nextEnabled = optionsEnabled.indexWhere(
(e) => e,
index + 1,
);
return index != _selectedIndex &&
optionsEnabled[index] &&
nextEnabled != _selectedIndex &&
nextEnabled != -1;
}
Set<int> _currentHiddenChainIds() {
final hidden = <int>{};
for (var i = 0; i < widget.chains.length; i++) {
if (i < optionsEnabled.length && !optionsEnabled[i]) {
hidden.add(widget.chains[i].chainId);
}
}
return hidden;
}
@override
Widget build(BuildContext context) {
final double popupWidth = MediaQuery.of(context).size.width * 0.9;
return Center(
child: Column(
spacing: 25.0,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
_editMode ? "Customize options" : "Select Network",
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
),
ClipRRect(
borderRadius: BorderRadius.circular(20),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: Theme.of(context).colorScheme.surfaceContainer,
),
child: AnimatedContainer(
duration: EvmSwitcher.editModeAnimDuration,
width: popupWidth,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
AnimatedSize(
curve: Curves.easeOutCubic,
duration: EvmSwitcher.editModeAnimDuration,
child: Container(
width: _editMode ? 0 : popupWidth,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
if (optionsEnabled[index]) {
final chain = widget.chains[index];
final data = EvmSwitcherDataItem(
name: chain.name,
svgPath: _getSvgPathForChain(chain.name),
chainId: chain.chainId,
);
return EvmSwitcherRow(
key: ValueKey(chain.chainId),
data: data,
editMode: false,
selected: index == _selectedIndex,
onTap: () {
widget.onChainSelected(chain.chainId);
if (mounted) {
Navigator.of(context).pop();
}
},
editSwitchValue: true,
animDuration: EvmSwitcher.editModeAnimDuration,
);
} else {
return Container();
}
},
separatorBuilder: (context, index) {
if (shouldBuildSeparator(index))
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 18),
child: Container(
height: 1,
width: double.infinity,
color:
Theme.of(context).colorScheme.surfaceContainerHigh,
));
else
return Container(height: 1);
},
itemCount: widget.chains.length),
EvmSwitcherAdditionalOption(
title: "Customize options",
svgPath: "assets/images/evm_switcher_arrow_right.svg",
animDuration: EvmSwitcher.editModeAnimDuration,
topSeparator: true,
bottomSeparator: false,
visible: true,
onTap: () {
setState(() {
_editMode = true;
});
},
iconOnRight: true)
],
),
),
),
AnimatedSize(
curve: Curves.easeOutCubic,
duration: EvmSwitcher.editModeAnimDuration,
child: Container(
width: _editMode ? popupWidth : 0,
height: _editMode ? null : 0,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
EvmSwitcherAdditionalOption(
title: "Back",
svgPath: "assets/images/evm_switcher_arrow_left.svg",
animDuration: EvmSwitcher.editModeAnimDuration,
topSeparator: false,
bottomSeparator: true,
visible: true,
onTap: () {
setState(() {
_editMode = false;
});
},
iconOnRight: false),
ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
final chain = widget.chains[index];
final data = EvmSwitcherDataItem(
name: chain.name,
svgPath: _getSvgPathForChain(chain.name),
chainId: chain.chainId,
);
return EvmSwitcherRow(
key: ValueKey(chain.chainId),
data: data,
editMode: _editMode,
selected: index == _selectedIndex,
onTap: () {
setState(() {
optionsEnabled[index] = !optionsEnabled[index];
});
widget.onHiddenChanged(
_currentHiddenChainIds(),
);
},
editSwitchValue: optionsEnabled[index],
animDuration: EvmSwitcher.editModeAnimDuration,
);
},
separatorBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 18),
child: Container(
height: 1,
width: double.infinity,
color: Theme.of(context).colorScheme.surfaceContainerHigh,
));
},
itemCount: widget.chains.length),
],
),
),
),
],
)),
),
),
],
),
);
}
}
class EvmSwitcherAdditionalOption extends StatelessWidget {
const EvmSwitcherAdditionalOption(
{super.key,
required this.title,
required this.svgPath,
required this.animDuration,
required this.topSeparator,
required this.bottomSeparator,
required this.visible,
required this.onTap,
required this.iconOnRight});
final String title;
final String svgPath;
final Duration animDuration;
final bool topSeparator;
final bool bottomSeparator;
final bool visible;
final VoidCallback onTap;
final bool iconOnRight;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
behavior: HitTestBehavior.translucent,
child: Container(
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
if (topSeparator)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 18),
child: Container(
height: 1,
width: double.infinity,
color: Theme.of(context).colorScheme.surfaceContainerHigh,
)),
Padding(
padding: const EdgeInsets.all(18),
child: Row(
mainAxisSize: MainAxisSize.max,
spacing: 8.0,
children: [
if (!iconOnRight) SvgPicture.asset(svgPath, width: 16, height: 16),
Text(
title,
style: TextStyle(color: Theme.of(context).colorScheme.primary),
),
if (iconOnRight) SvgPicture.asset(svgPath, width: 16, height: 16),
],
),
),
if (bottomSeparator)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 18),
child: Container(
height: 1,
width: double.infinity,
color: Theme.of(context).colorScheme.surfaceContainerHigh,
)),
],
),
),
);
}
}
+71
View File
@@ -0,0 +1,71 @@
import 'package:cake_wallet/src/widgets/evm_switcher.dart';
import 'package:cake_wallet/src/widgets/standard_switch.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
class EvmSwitcherRow extends StatelessWidget {
const EvmSwitcherRow({
super.key,
required this.editMode,
required this.selected,
required this.data,
required this.onTap,
required this.animDuration,
required this.editSwitchValue,
});
final bool editMode;
final bool selected;
final EvmSwitcherDataItem data;
final VoidCallback onTap;
final Duration animDuration;
final bool editSwitchValue;
@override
Widget build(BuildContext context) {
final Color resolvedForegroundColor = editMode || selected
? Theme.of(context).colorScheme.onSurface
: Theme.of(context).colorScheme.primary;
final Color resolvedBackgroundColor = !editMode && selected
? Theme.of(context).colorScheme.surfaceContainerHighest
: Theme.of(context).colorScheme.surfaceContainerHighest.withAlpha(0);
return GestureDetector(
onTap: onTap,
child: AnimatedContainer(
duration: animDuration,
color: resolvedBackgroundColor,
child: Padding(
padding: const EdgeInsets.all(18.0),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
spacing: 8.0,
children: [
SvgPicture.asset(
data.svgPath,
width: 16,
height: 16,
colorFilter: ColorFilter.mode(
resolvedForegroundColor, BlendMode.srcIn),
),
Text(data.name,
style: TextStyle(
color: resolvedForegroundColor, fontSize: 14)),
if (selected && !editMode)
SvgPicture.asset("assets/images/evm_switcher_checkmark.svg",
width: 18, height: 18),
],
),
if (editMode)
StandardSwitch(value: editSwitchValue, onTapped: onTap)
],
),
),
),
);
}
}
+4 -1
View File
@@ -173,7 +173,10 @@ abstract class TradeFilterStoreBase with Store {
List<TradeListItem> filtered({required List<TradeListItem> trades, required WalletBase wallet}) {
final _trades = trades
.where((item) => item.trade.walletId == wallet.id && isTradeInAccount(item, wallet))
.where((item) {
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
return item.trade.walletId == wallet.id && isTradeInAccount(item, wallet) && isSameChain;
})
.toList();
final needToFilter = !displayAllTrades;
+169 -91
View File
@@ -2,7 +2,6 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/utilities.dart';
import 'package:cake_wallet/decred/decred.dart';
@@ -27,13 +26,13 @@ import 'package:cake_wallet/entities/seed_type.dart';
import 'package:cake_wallet/entities/sort_balance_types.dart';
import 'package:cake_wallet/entities/sync_status_display_mode.dart';
import 'package:cake_wallet/entities/wallet_list_order_types.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cake_wallet/zano/zano.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/package_info.dart';
import 'package:cake_wallet/view_model/settings/sync_mode.dart';
@@ -71,6 +70,7 @@ abstract class SettingsStoreBase with Store {
required FilterListOrderType initialWalletListOrder,
required FilterListOrderType initialContactListOrder,
required bool initialDisableBulletin,
required this.useBlinkProtection,
required bool initialWalletListAscending,
required bool initialContactListAscending,
required FiatApiMode initialFiatMode,
@@ -115,6 +115,7 @@ abstract class SettingsStoreBase with Store {
required this.usePolygonScan,
required this.useTronGrid,
required this.useMempoolFeeAPI,
required List<int> initialEvmHiddenChainIds,
required this.defaultNanoRep,
required this.defaultBananoRep,
required this.lookupsTwitter,
@@ -142,6 +143,7 @@ abstract class SettingsStoreBase with Store {
TransactionPriority? initialHavenTransactionPriority,
TransactionPriority? initialLitecoinTransactionPriority,
TransactionPriority? initialEthereumTransactionPriority,
TransactionPriority? initialEVMTransactionPriority,
TransactionPriority? initialPolygonTransactionPriority,
TransactionPriority? initialBaseTransactionPriority,
TransactionPriority? initialBitcoinCashTransactionPriority,
@@ -199,6 +201,7 @@ abstract class SettingsStoreBase with Store {
currentBuiltinTor = initialBuiltinTor,
enableAutomaticNodeSwitching = initialEnableAutomaticNodeSwitching,
backgroundImage = initialBackgroundImage,
evmHiddenChainIds = ObservableSet.of(initialEvmHiddenChainIds),
priority = ObservableMap<WalletType, TransactionPriority>() {
//this.nodes = ObservableMap<WalletType, Node>.of(nodes);
@@ -226,6 +229,7 @@ abstract class SettingsStoreBase with Store {
priority[WalletType.ethereum] = initialEthereumTransactionPriority;
}
if (initialPolygonTransactionPriority != null) {
priority[WalletType.polygon] = initialPolygonTransactionPriority;
}
@@ -254,14 +258,11 @@ abstract class SettingsStoreBase with Store {
(FiatCurrency fiatCurrency) => sharedPreferences.setString(
PreferencesKey.currentFiatCurrencyKey, fiatCurrency.serialize()));
reaction(
(_) => selectedCakePayCountry,
(Country? country) {
if (country != null) {
sharedPreferences.setString(
PreferencesKey.currentCakePayCountry, country.raw);
}
});
reaction((_) => selectedCakePayCountry, (Country? country) {
if (country != null) {
sharedPreferences.setString(PreferencesKey.currentCakePayCountry, country.raw);
}
});
reaction(
(_) => shouldShowYatPopup,
@@ -270,8 +271,8 @@ abstract class SettingsStoreBase with Store {
reaction(
(_) => shouldShowDEuroDisclaimer,
(bool shouldShowDEuroDisclaimer) =>
sharedPreferences.setBool(PreferencesKey.shouldShowDEuroDisclaimer, shouldShowDEuroDisclaimer));
(bool shouldShowDEuroDisclaimer) => sharedPreferences.setBool(
PreferencesKey.shouldShowDEuroDisclaimer, shouldShowDEuroDisclaimer));
reaction((_) => shouldShowRepWarning,
(bool val) => sharedPreferences.setBool(PreferencesKey.shouldShowRepWarning, val));
@@ -333,11 +334,16 @@ abstract class SettingsStoreBase with Store {
});
}
reaction((_) => disableTradeOption,
(bool disableTradeOption) => sharedPreferences.setBool(PreferencesKey.disableTradeOption, disableTradeOption));
reaction(
(_) => disableTradeOption,
(bool disableTradeOption) =>
sharedPreferences.setBool(PreferencesKey.disableTradeOption, disableTradeOption));
reaction((_) => disableAutomaticExchangeStatusUpdates,
(bool disableAutomaticExchangeStatusUpdates) => sharedPreferences.setBool(PreferencesKey.disableAutomaticExchangeStatusUpdates, disableAutomaticExchangeStatusUpdates));
reaction(
(_) => disableAutomaticExchangeStatusUpdates,
(bool disableAutomaticExchangeStatusUpdates) => sharedPreferences.setBool(
PreferencesKey.disableAutomaticExchangeStatusUpdates,
disableAutomaticExchangeStatusUpdates));
reaction(
(_) => disableBulletin,
@@ -350,8 +356,8 @@ abstract class SettingsStoreBase with Store {
sharedPreferences.setInt(PreferencesKey.walletListOrder, walletListOrder.index));
reaction(
(_) => contactListOrder,
(FilterListOrderType contactListOrder) =>
(_) => contactListOrder,
(FilterListOrderType contactListOrder) =>
sharedPreferences.setInt(PreferencesKey.contactListOrder, contactListOrder.index));
reaction(
@@ -360,8 +366,8 @@ abstract class SettingsStoreBase with Store {
sharedPreferences.setBool(PreferencesKey.walletListAscending, walletListAscending));
reaction(
(_) => contactListAscending,
(bool contactListAscending) =>
(_) => contactListAscending,
(bool contactListAscending) =>
sharedPreferences.setBool(PreferencesKey.contactListAscending, contactListAscending));
reaction(
@@ -400,13 +406,13 @@ abstract class SettingsStoreBase with Store {
sharedPreferences.setBool(PreferencesKey.shouldShowMarketPlaceInDashboard, value));
reaction(
(_) => showAddressBookPopupEnabled,
(bool value) =>
(_) => showAddressBookPopupEnabled,
(bool value) =>
sharedPreferences.setBool(PreferencesKey.showAddressBookPopupEnabled, value));
reaction(
(_) => syncStatusDisplayMode,
(SyncStatusDisplayMode value) =>
(_) => syncStatusDisplayMode,
(SyncStatusDisplayMode value) =>
sharedPreferences.setString(PreferencesKey.syncStatusDisplayMode, value.toJson()));
reaction((_) => pinCodeLength,
@@ -440,7 +446,6 @@ abstract class SettingsStoreBase with Store {
sharedPreferences.setBool(PreferencesKey.builtinTorKey, builtinTor);
});
reaction(
(_) => exchangeStatus,
(ExchangeApiMode mode) =>
@@ -466,15 +471,11 @@ abstract class SettingsStoreBase with Store {
(bool usePolygonScan) =>
_sharedPreferences.setBool(PreferencesKey.usePolygonScan, usePolygonScan));
reaction(
(_) => useBaseScan,
(bool useBaseScan) =>
_sharedPreferences.setBool(PreferencesKey.useBaseScan, useBaseScan));
reaction(
(_) => useArbiScan,
(bool useArbiScan) =>
_sharedPreferences.setBool(PreferencesKey.useArbiScan, useArbiScan));
reaction((_) => useBaseScan,
(bool useBaseScan) => _sharedPreferences.setBool(PreferencesKey.useBaseScan, useBaseScan));
reaction((_) => useArbiScan,
(bool useArbiScan) => _sharedPreferences.setBool(PreferencesKey.useArbiScan, useArbiScan));
reaction((_) => useTronGrid,
(bool useTronGrid) => _sharedPreferences.setBool(PreferencesKey.useTronGrid, useTronGrid));
@@ -484,6 +485,9 @@ abstract class SettingsStoreBase with Store {
(bool useMempoolFeeAPI) =>
_sharedPreferences.setBool(PreferencesKey.useMempoolFeeAPI, useMempoolFeeAPI));
reaction((_) => useBlinkProtection,
(bool value) => _sharedPreferences.setBool(PreferencesKey.useBlinkProtection, value));
reaction((_) => defaultNanoRep,
(String nanoRep) => _sharedPreferences.setString(PreferencesKey.defaultNanoRep, nanoRep));
@@ -528,15 +532,13 @@ abstract class SettingsStoreBase with Store {
(bool looksUpWellKnown) =>
_sharedPreferences.setBool(PreferencesKey.lookupsWellKnown, looksUpWellKnown));
reaction(
(_) => usePayjoin,
(bool usePayjoin) =>
_sharedPreferences.setBool(PreferencesKey.usePayjoin, usePayjoin));
reaction((_) => usePayjoin,
(bool usePayjoin) => _sharedPreferences.setBool(PreferencesKey.usePayjoin, usePayjoin));
reaction(
(_) => showPayjoinCard,
(bool showPayjoinCard) => _sharedPreferences.setBool(
PreferencesKey.showPayjoinCard, showPayjoinCard));
(bool showPayjoinCard) =>
_sharedPreferences.setBool(PreferencesKey.showPayjoinCard, showPayjoinCard));
// secure storage keys:
reaction(
@@ -546,10 +548,9 @@ abstract class SettingsStoreBase with Store {
value: biometricalAuthentication.toString()));
reaction(
(_) => enableDuressPin,
(bool enableDuressPin) => secureStorage.write(
key: SecureKey.enableDuressPin,
value: enableDuressPin.toString()));
(_) => enableDuressPin,
(bool enableDuressPin) =>
secureStorage.write(key: SecureKey.enableDuressPin, value: enableDuressPin.toString()));
reaction(
(_) => selectedCake2FAPreset,
@@ -637,6 +638,11 @@ abstract class SettingsStoreBase with Store {
(bool mwebAlwaysScan) =>
_sharedPreferences.setBool(PreferencesKey.mwebAlwaysScan, mwebAlwaysScan));
reaction(
(_) => evmHiddenChainIds.toList(growable: false),
(List<int> hiddenIds) => _sharedPreferences.setStringList(
PreferencesKey.evmHiddenChainIds, hiddenIds.map((id) => id.toString()).toList()));
reaction(
(_) => mwebCardDisplay,
(bool mwebCardDisplay) =>
@@ -654,7 +660,7 @@ abstract class SettingsStoreBase with Store {
(_) => mwebNodeUri,
(String mwebNodeUri) =>
_sharedPreferences.setString(PreferencesKey.mwebNodeUri, mwebNodeUri));
reaction(
(_) => enableAutomaticNodeSwitching,
(bool enableAutomaticNodeSwitching) => _sharedPreferences.setBool(
@@ -662,8 +668,8 @@ abstract class SettingsStoreBase with Store {
reaction(
(_) => backgroundImage,
(String backgroundImage) => _sharedPreferences.setString(
PreferencesKey.backgroundImage, backgroundImage));
(String backgroundImage) =>
_sharedPreferences.setString(PreferencesKey.backgroundImage, backgroundImage));
this.nodes.observe((change) {
if (change.newValue != null && change.key != null) {
@@ -676,8 +682,6 @@ abstract class SettingsStoreBase with Store {
_saveCurrentPowNode(change.newValue!, change.key!);
}
});
}
static const defaultPinLength = 4;
@@ -747,6 +751,9 @@ abstract class SettingsStoreBase with Store {
@observable
bool disableAutomaticExchangeStatusUpdates;
@observable
bool useBlinkProtection;
@observable
FilterListOrderType contactListOrder;
@@ -852,6 +859,9 @@ abstract class SettingsStoreBase with Store {
@observable
bool useMempoolFeeAPI;
@observable
ObservableSet<int> evmHiddenChainIds;
@observable
String defaultNanoRep;
@@ -934,16 +944,45 @@ abstract class SettingsStoreBase with Store {
ObservableMap<WalletType, Node> nodes;
ObservableMap<WalletType, Node> powNodes;
Node getCurrentNode(WalletType walletType) {
final node = nodes[walletType];
Node getCurrentNode(WalletType walletType, {int? chainId}) {
if (chainId != null && isEVMCompatibleChain(walletType)) {
final preferenceKey = _getEVMNodePreferenceKey(chainId);
final nodeId = _sharedPreferences.getInt(preferenceKey);
if (nodeId != null) {
final walletTypeForChain = evm!.getWalletTypeByChainId(chainId);
if (walletTypeForChain != null) {
final node = nodes[walletTypeForChain];
if (node != null) return node;
}
}
throw Exception('No node found for EVM wallet type with chainId: $chainId');
}
final node = nodes[walletType];
if (node == null) {
throw Exception('No node found for wallet type: ${walletType.toString()}');
}
return node;
}
String _getEVMNodePreferenceKey(int chainId) {
switch (chainId) {
case 1:
return PreferencesKey.currentEthereumNodeIdKey;
case 137:
return PreferencesKey.currentPolygonNodeIdKey;
case 8453:
return PreferencesKey.currentBaseNodeIdKey;
case 42161:
return PreferencesKey.currentArbitrumNodeIdKey;
default:
// Default to Ethereum for unknown chainIds
return PreferencesKey.currentEthereumNodeIdKey;
}
}
Node getCurrentPowNode(WalletType walletType) {
final node = powNodes[walletType];
@@ -954,6 +993,17 @@ abstract class SettingsStoreBase with Store {
return node;
}
TransactionPriority? getPriority(WalletType walletType, {int? chainId}) {
if (isEVMCompatibleChain(walletType)) {
if (chainId != null && !evm!.hasPriorityFee(chainId)) return null;
return priority[walletType];
}
return priority[walletType];
}
void setPriority(WalletType walletType, TransactionPriority priority, {int? chainId}) => this.priority[walletType] = priority;
bool isBitcoinBuyEnabled;
bool get shouldShowReceiveWarning =>
@@ -972,10 +1022,10 @@ abstract class SettingsStoreBase with Store {
final secureStorage = await getIt.get<SecureStorage>();
final currentFiatCurrency = FiatCurrency.deserialize(
raw: sharedPreferences.getString(PreferencesKey.currentFiatCurrencyKey)!);
final savedCakePayCountryRaw = sharedPreferences.getString(PreferencesKey.currentCakePayCountry);
final currentCakePayCountry = savedCakePayCountryRaw != null
? Country.deserialize(raw: savedCakePayCountryRaw)
: null;
final savedCakePayCountryRaw =
sharedPreferences.getString(PreferencesKey.currentCakePayCountry);
final currentCakePayCountry =
savedCakePayCountryRaw != null ? Country.deserialize(raw: savedCakePayCountryRaw) : null;
TransactionPriority? moneroTransactionPriority = monero?.deserializeMoneroTransactionPriority(
raw: sharedPreferences.getInt(PreferencesKey.moneroTransactionPriority)!);
@@ -986,6 +1036,7 @@ abstract class SettingsStoreBase with Store {
TransactionPriority? havenTransactionPriority;
TransactionPriority? litecoinTransactionPriority;
TransactionPriority? ethereumTransactionPriority;
TransactionPriority? evmTransactionPriority;
TransactionPriority? polygonTransactionPriority;
TransactionPriority? baseTransactionPriority;
TransactionPriority? bitcoinCashTransactionPriority;
@@ -1002,15 +1053,17 @@ abstract class SettingsStoreBase with Store {
sharedPreferences.getInt(PreferencesKey.litecoinTransactionPriority)!);
}
if (sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority) != null) {
ethereumTransactionPriority = ethereum?.deserializeEthereumTransactionPriority(
ethereumTransactionPriority = evm?.deserializeEVMTransactionPriority(
sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!);
evmTransactionPriority = evm?.deserializeEVMTransactionPriority(
sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!);
}
if (sharedPreferences.getInt(PreferencesKey.polygonTransactionPriority) != null) {
polygonTransactionPriority = polygon?.deserializePolygonTransactionPriority(
polygonTransactionPriority = evm?.deserializeEVMTransactionPriority(
sharedPreferences.getInt(PreferencesKey.polygonTransactionPriority)!);
}
if (sharedPreferences.getInt(PreferencesKey.baseTransactionPriority) != null) {
baseTransactionPriority = base?.deserializeBaseTransactionPriority(
baseTransactionPriority = evm?.deserializeEVMTransactionPriority(
sharedPreferences.getInt(PreferencesKey.baseTransactionPriority)!);
}
if (sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority) != null) {
@@ -1034,12 +1087,13 @@ abstract class SettingsStoreBase with Store {
bitcoinTransactionPriority ??= bitcoin?.getMediumTransactionPriority();
havenTransactionPriority ??= monero?.getDefaultTransactionPriority();
litecoinTransactionPriority ??= bitcoin?.getLitecoinTransactionPriorityMedium();
ethereumTransactionPriority ??= ethereum?.getDefaultTransactionPriority();
ethereumTransactionPriority ??= evm?.getDefaultTransactionPriority();
evmTransactionPriority ??= evm?.getDefaultTransactionPriority();
bitcoinCashTransactionPriority ??= bitcoinCash?.getDefaultTransactionPriority();
wowneroTransactionPriority ??= wownero?.getDefaultTransactionPriority();
decredTransactionPriority ??= decred?.getDecredTransactionPriorityMedium();
polygonTransactionPriority ??= polygon?.getDefaultTransactionPriority();
baseTransactionPriority ??= base?.getDefaultTransactionPriority();
polygonTransactionPriority ??= evm?.getDefaultTransactionPriority();
baseTransactionPriority ??= evm?.getDefaultTransactionPriority();
zanoTransactionPriority ??= zano?.getDefaultTransactionPriority();
final currentBalanceDisplayMode = BalanceDisplayMode.deserialize(
@@ -1048,13 +1102,15 @@ abstract class SettingsStoreBase with Store {
final shouldSaveRecipientAddress =
sharedPreferences.getBool(PreferencesKey.shouldSaveRecipientAddressKey) ?? false;
final isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? false;
final disableTradeOption = sharedPreferences.getBool(PreferencesKey.disableTradeOption) ?? false;
final disableAutomaticExchangeStatusUpdates = sharedPreferences.getBool(PreferencesKey.disableAutomaticExchangeStatusUpdates) ?? false;
final disableTradeOption =
sharedPreferences.getBool(PreferencesKey.disableTradeOption) ?? false;
final disableAutomaticExchangeStatusUpdates =
sharedPreferences.getBool(PreferencesKey.disableAutomaticExchangeStatusUpdates) ?? false;
final disableBulletin = sharedPreferences.getBool(PreferencesKey.disableBulletinKey) ?? false;
final walletListOrder =
FilterListOrderType.values[sharedPreferences.getInt(PreferencesKey.walletListOrder) ?? 0];
final contactListOrder =
FilterListOrderType.values[sharedPreferences.getInt(PreferencesKey.contactListOrder) ?? 0];
FilterListOrderType.values[sharedPreferences.getInt(PreferencesKey.contactListOrder) ?? 0];
final walletListAscending =
sharedPreferences.getBool(PreferencesKey.walletListAscending) ?? true;
final contactListAscending =
@@ -1090,6 +1146,11 @@ abstract class SettingsStoreBase with Store {
final useArbiScan = sharedPreferences.getBool(PreferencesKey.useArbiScan) ?? true;
final useTronGrid = sharedPreferences.getBool(PreferencesKey.useTronGrid) ?? true;
final useMempoolFeeAPI = sharedPreferences.getBool(PreferencesKey.useMempoolFeeAPI) ?? true;
final useBlinkProtection = sharedPreferences.getBool(PreferencesKey.useBlinkProtection) ?? true;
final evmHiddenChainIdsRaw =
sharedPreferences.getStringList(PreferencesKey.evmHiddenChainIds) ?? const <String>[];
final evmHiddenChainIds =
evmHiddenChainIdsRaw.map((value) => int.tryParse(value)).whereType<int>().toList();
final defaultNanoRep = sharedPreferences.getString(PreferencesKey.defaultNanoRep) ?? "";
final defaultBananoRep = sharedPreferences.getString(PreferencesKey.defaultBananoRep) ?? "";
final lookupsTwitter = sharedPreferences.getBool(PreferencesKey.lookupsTwitter) ?? true;
@@ -1181,7 +1242,8 @@ abstract class SettingsStoreBase with Store {
final packageInfo = await PackageInfo.fromPlatform();
final deviceName = await _getDeviceName() ?? '';
final shouldShowYatPopup = sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? true;
final shouldShowDEuroDisclaimer = sharedPreferences.getBool(PreferencesKey.shouldShowDEuroDisclaimer) ?? true;
final shouldShowDEuroDisclaimer =
sharedPreferences.getBool(PreferencesKey.shouldShowDEuroDisclaimer) ?? true;
final shouldShowRepWarning =
sharedPreferences.getBool(PreferencesKey.shouldShowRepWarning) ?? true;
@@ -1277,7 +1339,8 @@ abstract class SettingsStoreBase with Store {
}
final savedSyncMode = SyncMode.all.firstWhere((element) {
return element.type.index == (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 2); // default to 2 - daily sync
return element.type.index ==
(sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 2); // default to 2 - daily sync
});
final savedSyncAll = sharedPreferences.getBool(PreferencesKey.syncAllKey) ?? true;
final builtinTor = sharedPreferences.getBool(PreferencesKey.builtinTorKey) ?? false;
@@ -1430,6 +1493,8 @@ abstract class SettingsStoreBase with Store {
useArbiScan: useArbiScan,
useTronGrid: useTronGrid,
useMempoolFeeAPI: useMempoolFeeAPI,
useBlinkProtection: useBlinkProtection,
initialEvmHiddenChainIds: evmHiddenChainIds,
defaultNanoRep: defaultNanoRep,
defaultBananoRep: defaultBananoRep,
lookupsTwitter: lookupsTwitter,
@@ -1473,6 +1538,7 @@ abstract class SettingsStoreBase with Store {
initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings:
shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
initialEthereumTransactionPriority: ethereumTransactionPriority,
initialEVMTransactionPriority: evmTransactionPriority,
initialPolygonTransactionPriority: polygonTransactionPriority,
initialBaseTransactionPriority: baseTransactionPriority,
initialSyncMode: savedSyncMode,
@@ -1516,19 +1582,18 @@ abstract class SettingsStoreBase with Store {
priority[WalletType.litecoin] = bitcoin!.deserializeLitecoinTransactionPriority(
sharedPreferences.getInt(PreferencesKey.litecoinTransactionPriority)!);
}
if (ethereum != null &&
if (evm != null &&
sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority) != null) {
priority[WalletType.ethereum] = ethereum!.deserializeEthereumTransactionPriority(
priority[WalletType.ethereum] = evm!.deserializeEVMTransactionPriority(
sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!);
}
if (polygon != null &&
if (evm != null &&
sharedPreferences.getInt(PreferencesKey.polygonTransactionPriority) != null) {
priority[WalletType.polygon] = polygon!.deserializePolygonTransactionPriority(
priority[WalletType.polygon] = evm!.deserializeEVMTransactionPriority(
sharedPreferences.getInt(PreferencesKey.polygonTransactionPriority)!);
}
if (base != null &&
sharedPreferences.getInt(PreferencesKey.baseTransactionPriority) != null) {
priority[WalletType.base] = base!.deserializeBaseTransactionPriority(
if (evm != null && sharedPreferences.getInt(PreferencesKey.baseTransactionPriority) != null) {
priority[WalletType.base] = evm!.deserializeEVMTransactionPriority(
sharedPreferences.getInt(PreferencesKey.baseTransactionPriority)!);
}
if (bitcoinCash != null &&
@@ -1538,7 +1603,7 @@ abstract class SettingsStoreBase with Store {
}
if (zano != null && sharedPreferences.getInt(PreferencesKey.zanoTransactionPriority) != null) {
priority[WalletType.zano] = zano!.deserializeMoneroTransactionPriority(
raw: sharedPreferences.getInt(PreferencesKey.zanoTransactionPriority)!);
raw: sharedPreferences.getInt(PreferencesKey.zanoTransactionPriority)!);
}
if (decred != null &&
sharedPreferences.getInt(PreferencesKey.decredTransactionPriority) != null) {
@@ -1578,14 +1643,17 @@ abstract class SettingsStoreBase with Store {
numberOfFailedTokenTrials =
sharedPreferences.getInt(PreferencesKey.failedTotpTokenTrials) ?? numberOfFailedTokenTrials;
isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? isAppSecure;
disableTradeOption = sharedPreferences.getBool(PreferencesKey.disableTradeOption) ?? disableTradeOption;
disableAutomaticExchangeStatusUpdates = sharedPreferences.getBool(PreferencesKey.disableAutomaticExchangeStatusUpdates) ?? disableAutomaticExchangeStatusUpdates;
disableTradeOption =
sharedPreferences.getBool(PreferencesKey.disableTradeOption) ?? disableTradeOption;
disableAutomaticExchangeStatusUpdates =
sharedPreferences.getBool(PreferencesKey.disableAutomaticExchangeStatusUpdates) ??
disableAutomaticExchangeStatusUpdates;
disableBulletin =
sharedPreferences.getBool(PreferencesKey.disableBulletinKey) ?? disableBulletin;
walletListOrder =
FilterListOrderType.values[sharedPreferences.getInt(PreferencesKey.walletListOrder) ?? 0];
contactListOrder =
FilterListOrderType.values[sharedPreferences.getInt(PreferencesKey.contactListOrder) ?? 0];
FilterListOrderType.values[sharedPreferences.getInt(PreferencesKey.contactListOrder) ?? 0];
walletListAscending = sharedPreferences.getBool(PreferencesKey.walletListAscending) ?? true;
contactListAscending = sharedPreferences.getBool(PreferencesKey.contactListAscending) ?? true;
shouldShowMarketPlaceInDashboard =
@@ -1616,7 +1684,8 @@ abstract class SettingsStoreBase with Store {
shouldShowYatPopup =
sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? shouldShowYatPopup;
shouldShowDEuroDisclaimer =
sharedPreferences.getBool(PreferencesKey.shouldShowDEuroDisclaimer) ?? shouldShowDEuroDisclaimer;
sharedPreferences.getBool(PreferencesKey.shouldShowDEuroDisclaimer) ??
shouldShowDEuroDisclaimer;
shouldShowRepWarning =
sharedPreferences.getBool(PreferencesKey.shouldShowRepWarning) ?? shouldShowRepWarning;
sortBalanceBy = SortBalanceBy
@@ -1628,6 +1697,12 @@ abstract class SettingsStoreBase with Store {
useArbiScan = sharedPreferences.getBool(PreferencesKey.useArbiScan) ?? true;
useTronGrid = sharedPreferences.getBool(PreferencesKey.useTronGrid) ?? true;
useMempoolFeeAPI = sharedPreferences.getBool(PreferencesKey.useMempoolFeeAPI) ?? true;
useBlinkProtection = sharedPreferences.getBool(PreferencesKey.useBlinkProtection) ?? true;
final hiddenChainIdsRaw =
sharedPreferences.getStringList(PreferencesKey.evmHiddenChainIds) ?? const <String>[];
evmHiddenChainIds
..clear()
..addAll(hiddenChainIdsRaw.map((value) => int.tryParse(value)).whereType<int>());
defaultNanoRep = sharedPreferences.getString(PreferencesKey.defaultNanoRep) ?? "";
defaultBananoRep = sharedPreferences.getString(PreferencesKey.defaultBananoRep) ?? "";
lookupsTwitter = sharedPreferences.getBool(PreferencesKey.lookupsTwitter) ?? true;
@@ -1732,7 +1807,6 @@ abstract class SettingsStoreBase with Store {
if (wowneroNode != null) {
nodes[WalletType.wownero] = wowneroNode;
}
if (zanoNode != null) {
@@ -1862,7 +1936,13 @@ abstract class SettingsStoreBase with Store {
await _sharedPreferences.setInt(PreferencesKey.currentHavenNodeIdKey, node.key as int);
break;
case WalletType.ethereum:
await _sharedPreferences.setInt(PreferencesKey.currentEthereumNodeIdKey, node.key as int);
case WalletType.polygon:
case WalletType.base:
case WalletType.arbitrum:
final chainId = evm!.getChainIdByWalletType(node.type);
final preferenceKey = _getEVMNodePreferenceKey(chainId);
await _sharedPreferences.setInt(preferenceKey, node.key as int);
nodes[node.type] = node;
break;
case WalletType.bitcoinCash:
await _sharedPreferences.setInt(
@@ -1871,15 +1951,6 @@ abstract class SettingsStoreBase with Store {
case WalletType.nano:
await _sharedPreferences.setInt(PreferencesKey.currentNanoNodeIdKey, node.key as int);
break;
case WalletType.polygon:
await _sharedPreferences.setInt(PreferencesKey.currentPolygonNodeIdKey, node.key as int);
break;
case WalletType.base:
await _sharedPreferences.setInt(PreferencesKey.currentBaseNodeIdKey, node.key as int);
break;
case WalletType.arbitrum:
await _sharedPreferences.setInt(PreferencesKey.currentArbitrumNodeIdKey, node.key as int);
break;
case WalletType.solana:
await _sharedPreferences.setInt(PreferencesKey.currentSolanaNodeIdKey, node.key as int);
break;
@@ -1917,6 +1988,13 @@ abstract class SettingsStoreBase with Store {
powNodes[walletType] = node;
}
@action
void setEvmHiddenChainIds(Set<int> chainIds) {
evmHiddenChainIds
..clear()
..addAll(chainIds);
}
@action
Future<void> updateAllTrocadorProviderStates(List<String> availableProviders) async {
final jsonKey = PreferencesKey.trocadorProviderStatesKey;
+1
View File
@@ -14,5 +14,6 @@ class FeatureFlag {
static const bool hasBitcoinViewOnly = true;
static const bool customBackgroundEnabled = false;
static const bool duressPinEnabled = true;
static const bool isEVMChainSwitcherEnabled = false;
static const bool isAutomaticNodeSwitchingEnabled = false;
}
+33 -14
View File
@@ -1,9 +1,22 @@
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cw_core/wallet_type.dart';
String getQrImage(WalletType type) {
String getQrImage(WalletType type, {int? selectedChainId}) {
if (isEVMCompatibleChain(type) && selectedChainId != null) {
switch (selectedChainId) {
case 1:
return 'assets/images/eth_chain_qr.svg';
case 137:
return 'assets/images/pol_chain_qr.svg';
case 8453:
return 'assets/images/base_chain_QR.svg';
case 42161:
return 'assets/images/arbitrum_chain_QR.svg';
default:
return 'assets/images/eth_chain_qr.svg';
}
}
switch (type) {
case WalletType.ethereum:
return 'assets/images/eth_chain_qr.svg';
case WalletType.solana:
return 'assets/images/sol_chain_qr.svg';
case WalletType.polygon:
@@ -28,21 +41,31 @@ String getQrImage(WalletType type) {
return 'assets/images/dcr_chain_qr.svg';
case WalletType.dogecoin:
return 'assets/images/doge_chain_qr.svg';
case WalletType.base:
return 'assets/images/base_chain_QR.svg';
case WalletType.arbitrum:
return 'assets/images/arbitrum_chain_QR.svg';
case WalletType.banano:
case WalletType.haven:
case WalletType.none:
default:
return 'assets/images/qr-cake.png';
}
}
String getChainMonoImage(WalletType type) {
String getChainMonoImage(WalletType type, {int? selectedChainId}) {
if (isEVMCompatibleChain(type) && selectedChainId != null) {
switch (selectedChainId) {
case 1:
return 'assets/images/eth_chain_mono.svg';
case 137:
return 'assets/images/pol_chain_mono.svg';
case 8453:
return 'assets/images/base_chain_mono.svg';
case 42161:
return 'assets/images/arbitrum_chain_mono.svg';
default:
return 'assets/images/eth_chain_mono.svg';
}
}
switch (type) {
case WalletType.ethereum:
return 'assets/images/eth_chain_mono.svg';
case WalletType.solana:
return 'assets/images/sol_chain_mono.svg';
case WalletType.polygon:
@@ -51,10 +74,6 @@ String getChainMonoImage(WalletType type) {
return 'assets/images/trx_chain_mono.svg';
case WalletType.zano:
return 'assets/images/zano_chain_mono.svg';
case WalletType.base:
return 'assets/images/base_chain_mono.svg';
case WalletType.arbitrum:
return 'assets/images/arbitrum_chain_mono.svg';
default:
return 'assets/images/eth_chain_mono.svg';
}
+96 -79
View File
@@ -1,4 +1,5 @@
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cw_core/cake_hive.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
@@ -21,13 +22,18 @@ class TokenUtilities {
final unique = <Erc20Token>[];
for (final wallet in evmWallets) {
final chain = getTokenNameBasedOnWalletType(wallet.type);
final box = await _openEvmTokensBoxFor(wallet);
final allChains = evm!.getAllChains();
for (final t in box.values.where((t) => t.enabled)) {
final key = '$chain|${t.contractAddress.toLowerCase()}';
if (seen.add(key)) {
unique.add(t);
for (final chainInfo in allChains) {
final chainId = chainInfo.chainId;
final chain = getTokenNameBasedOnWalletType(wallet.type, chainId: chainId);
final box = await _openEvmTokensBoxFor(wallet, chainId);
for (final t in box.values.where((t) => t.enabled)) {
final key = '$chain|${t.contractAddress.toLowerCase()}';
if (seen.add(key)) {
unique.add(t);
}
}
}
}
@@ -109,17 +115,9 @@ class TokenUtilities {
}
}
static Future<Box<Erc20Token>> _openEvmTokensBoxFor(
WalletInfo walletInfo,
) async {
static Future<Box<Erc20Token>> _openEvmTokensBoxFor(WalletInfo walletInfo, int chainId) async {
final walletKey = walletInfo.name.replaceAll(' ', '_');
final boxName = switch (walletInfo.type) {
WalletType.ethereum => '${walletKey}_${Erc20Token.ethereumBoxName}',
WalletType.polygon => '${walletKey}_${Erc20Token.polygonBoxName}',
WalletType.base => '${walletKey}_${Erc20Token.baseBoxName}',
WalletType.arbitrum => '${walletKey}_${Erc20Token.arbitrumBoxName}',
_ => '${walletKey}_${Erc20Token.ethereumBoxName}',
};
final boxName = _getErc20TokensBoxName(walletKey, chainId);
if (CakeHive.isBoxOpen(boxName)) {
return CakeHive.box<Erc20Token>(boxName);
@@ -127,6 +125,16 @@ class TokenUtilities {
return CakeHive.openBox<Erc20Token>(boxName);
}
static String _getErc20TokensBoxName(String sanitizedName, int chainId) {
return switch (chainId) {
1 => "${sanitizedName}_${Erc20Token.ethereumBoxName}",
137 => "${sanitizedName}_${Erc20Token.polygonBoxName}",
8453 => "${sanitizedName}_${Erc20Token.baseBoxName}",
42161 => "${sanitizedName}_${Erc20Token.arbitrumBoxName}",
_ => "${sanitizedName}_${Erc20Token.ethereumBoxName}",
};
}
static Future<Box<SPLToken>> _openSolTokensBoxFor(WalletInfo wallet) async {
final boxName = '${wallet.name.replaceAll(' ', '_')}_${SPLToken.boxName}';
if (CakeHive.isBoxOpen(boxName)) {
@@ -135,9 +143,7 @@ class TokenUtilities {
return CakeHive.openBox<SPLToken>(boxName);
}
static Future<Box<TronToken>> _openTronTokensBoxFor(
WalletInfo walletInfo,
) async {
static Future<Box<TronToken>> _openTronTokensBoxFor(WalletInfo walletInfo) async {
final boxName = '${walletInfo.name.replaceAll(' ', '_')}_${TronToken.boxName}';
if (CakeHive.isBoxOpen(boxName)) {
return CakeHive.box<TronToken>(boxName);
@@ -176,41 +182,49 @@ class TokenUtilities {
}
static int getChainId(CryptoCurrency currency) {
final tag = currency.tag?.toUpperCase();
final title = currency.title.toLowerCase();
final tag = currency.tag?.toLowerCase();
// Polygon
if (title == 'polygon' || title == 'matic' || tag == 'polygon') {
return 137;
// Only check EVM registry for currencies that might be EVM-related
final isPotentialEVM = title == 'eth' ||
title == 'ethereum' ||
title == 'polygon' ||
title == 'matic' ||
title == 'base' ||
title == 'arbitrum' ||
(tag != null && (tag == 'ETH' || tag == 'POL' || tag == 'BASE' || tag == 'ARB')) ||
isNativeToken(currency);
if (isPotentialEVM) {
// Try by tag first if available (e.g., 'POL', 'BASE', 'ARB')
if (tag != null) {
final chainId = evm!.getChainIdByTag(tag);
if (chainId != null) return chainId;
}
// Try by title (case-insensitive)
final titleChainId = evm!.getChainIdByTitle(title);
if (titleChainId != null) return titleChainId;
}
// Fallback to hardcoded values for chains not in registry yet
// BSC (Binance Smart Chain)
if (title == 'bsc' || title == 'bnb' || tag == 'bsc') {
if (title == 'bsc' || title == 'bnb' || tag == 'BSC') {
return 56;
}
// Avalanche C-Chain
if (title == 'avalanche' || title == 'avax' || tag == 'avalanche') {
if (title == 'avalanche' || title == 'avax' || tag == 'AVALANCHE') {
return 43114;
}
// Arbitrum One
if (title == 'arbitrum' || title == 'arb' || tag == 'arb') {
return 42161;
}
// Optimism
if (title == 'optimism' || title == 'op' || tag == 'optimism') {
if (title == 'optimism' || title == 'op' || tag == 'OPTIMISM') {
return 10;
}
// Base
if (title == 'base' || tag == 'base') {
return 8453;
}
// Fantom Opera
if (title == 'fantom' || title == 'ftm' || tag == 'fantom') {
if (title == 'fantom' || title == 'ftm' || tag == 'FANTOM') {
return 250;
}
@@ -218,41 +232,6 @@ class TokenUtilities {
return 1;
}
static Future<List<CryptoCurrency>> getAvailableTokensForNetwork(
WalletType network,
) async {
final baseCurrency = walletTypeToCryptoCurrency(network);
final allTokens = <CryptoCurrency>[];
final addedAddresses = <String>{};
allTokens.add(baseCurrency);
for (final currency in CryptoCurrency.all) {
// For EVM networks: ETH has no tag, POL/BASE have tags
// Match by tag for POL/BASE, match by title==tag for ETH
final matches = (baseCurrency.tag == null && baseCurrency.title == currency.tag) ||
(baseCurrency.tag != null &&
currency.tag?.toLowerCase() == baseCurrency.tag?.toLowerCase());
if (matches && _shouldAddToken(allTokens, currency, addedAddresses)) {
allTokens.add(currency);
}
}
// Add user tokens that don't already exist
final userTokens = await _getUserTokensForNetwork(baseCurrency);
for (final token in userTokens) {
if (_shouldAddToken(allTokens, token, addedAddresses)) {
allTokens.add(token);
if (token is Erc20Token) {
addedAddresses.add(token.contractAddress.toLowerCase());
}
}
}
return allTokens;
}
static bool _shouldAddToken(
List<CryptoCurrency> existingTokens,
CryptoCurrency token,
@@ -277,14 +256,52 @@ class TokenUtilities {
(a.tag?.toUpperCase() == b.tag?.toUpperCase());
}
static Future<List<CryptoCurrency>> _getUserTokensForNetwork(CryptoCurrency baseCurrency) async {
final tokens = await TokenUtilities.loadAllUniqueEvmTokens();
static Future<List<CryptoCurrency>> getAvailableTokensForChainId(int chainId) async {
// Get native currency for the chain
final baseCurrency = getCryptoCurrencyByChainId(chainId);
final allTokens = <CryptoCurrency>[];
final addedAddresses = <String>{};
return tokens.where((token) {
// Match by tag, except for ETH which has no tag - match by title instead
if (baseCurrency.tag == null) return token.tag == baseCurrency.title;
allTokens.add(baseCurrency);
return token.tag?.toLowerCase() == baseCurrency.tag?.toLowerCase();
}).toList();
// Add currencies that match this chain
for (final currency in CryptoCurrency.all) {
final matches = (baseCurrency.tag == null && baseCurrency.title == currency.tag) ||
(baseCurrency.tag != null &&
currency.tag?.toLowerCase() == baseCurrency.tag?.toLowerCase());
if (matches && _shouldAddToken(allTokens, currency, addedAddresses)) {
allTokens.add(currency);
}
}
// Add user tokens for this chain
final userTokens = await _getUserTokensForChainId(chainId);
for (final token in userTokens) {
if (_shouldAddToken(allTokens, token, addedAddresses)) {
allTokens.add(token);
if (token is Erc20Token) {
addedAddresses.add(token.contractAddress.toLowerCase());
}
}
}
return allTokens;
}
static Future<List<CryptoCurrency>> _getUserTokensForChainId(int chainId) async {
final allWi = await WalletInfo.getAll();
final evmWallets = allWi.where((w) => isEVMCompatibleChain(w.type));
final tokens = <Erc20Token>[];
for (final wallet in evmWallets) {
final box = await _openEvmTokensBoxFor(wallet, chainId);
for (final t in box.values.where((t) => t.enabled)) {
tokens.add(t);
}
}
return tokens.cast<CryptoCurrency>();
}
}
@@ -2,6 +2,8 @@ import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cake_wallet/entities/seed_phrase_length.dart';
import 'package:cake_wallet/entities/seed_type.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:mobx/mobx.dart';
@@ -23,6 +25,18 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
@computed
bool get disableBulletin => _settingsStore.disableBulletin;
@computed
bool get useBlinkProtection => _settingsStore.useBlinkProtection;
bool get canUseBlinkProtection {
if (!isEVMCompatibleChain(type)) return false;
// Get the chainId from the wallet type
final chainId = evm!.getChainIdByWalletType(type);
return canSupportBlinkProtection(chainId);
}
@observable
bool _addCustomNode = false;
@@ -109,6 +123,9 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
@action
void setDisableBulletin(bool value) => _settingsStore.disableBulletin = value;
@action
void setUseBlinkProtection(bool value) => _settingsStore.useBlinkProtection = value;
@action
void toggleAddCustomNode() => _addCustomNode = !_addCustomNode;
@@ -11,7 +11,6 @@ import 'package:cake_wallet/store/settings_store.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/currency.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart';
@@ -34,13 +33,13 @@ abstract class AnonInvoicePageViewModelBase with Store {
description = '',
amount = '',
state = InitialExecutionState(),
selectedCurrency = walletTypeToCryptoCurrency(_wallet.type),
cryptoCurrency = walletTypeToCryptoCurrency(_wallet.type) {
selectedCurrency = _wallet.currency,
cryptoCurrency = _wallet.currency {
_getPreviousDonationLink();
_fetchLimits();
}
List<Currency> get currencies => [walletTypeToCryptoCurrency(_wallet.type), ...FiatCurrency.all];
List<Currency> get currencies => [_wallet.currency, ...FiatCurrency.all];
final AnonPayApi anonPayApi;
final String address;
final SettingsStore settingsStore;
@@ -85,7 +84,7 @@ abstract class AnonInvoicePageViewModelBase with Store {
if (currency is CryptoCurrency) {
cryptoCurrency = currency;
} else {
cryptoCurrency = walletTypeToCryptoCurrency(_wallet.type);
cryptoCurrency = _wallet.currency;
}
_fetchLimits();
@@ -118,7 +117,7 @@ abstract class AnonInvoicePageViewModelBase with Store {
email: receipientEmail,
name: receipientName,
fiatEquivalent:
selectedCurrency is FiatCurrency ? (selectedCurrency as FiatCurrency).raw : null,
selectedCurrency is FiatCurrency ? (selectedCurrency as FiatCurrency).raw : null,
));
_anonpayInvoiceInfoSource.add(result);
@@ -178,12 +177,12 @@ abstract class AnonInvoicePageViewModelBase with Store {
String get currentWalletName => _wallet.name;
@computed
String get qrImage => getQrImage(_wallet.type);
String get qrImage => getQrImage(_wallet.type, selectedChainId: _wallet.chainId);
@action
void reset() {
selectedCurrency = walletTypeToCryptoCurrency(_wallet.type);
cryptoCurrency = walletTypeToCryptoCurrency(_wallet.type);
selectedCurrency = _wallet.currency;
cryptoCurrency = _wallet.currency;
receipientEmail = '';
receipientName = '';
description = '';
+11 -13
View File
@@ -1,9 +1,7 @@
import 'package:cake_wallet/buy/buy_provider.dart';
import 'package:cake_wallet/buy/moonpay/moonpay_provider.dart';
import 'package:cake_wallet/buy/wyre/wyre_buy_provider.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/store/settings_store.dart';
@@ -20,12 +18,12 @@ part 'buy_view_model.g.dart';
class BuyViewModel = BuyViewModelBase with _$BuyViewModel;
abstract class BuyViewModelBase with Store {
BuyViewModelBase(this.ordersSource, this.ordersStore, this.settingsStore,
this.buyAmountViewModel, {required this.wallet})
: isRunning = false,
isDisabled = true,
isShowProviderButtons = false,
items = <BuyItem>[] {
BuyViewModelBase(this.ordersSource, this.ordersStore, this.settingsStore, this.buyAmountViewModel,
{required this.wallet})
: isRunning = false,
isDisabled = true,
isShowProviderButtons = false,
items = <BuyItem>[] {
_fetchBuyItems();
}
@@ -57,9 +55,9 @@ abstract class BuyViewModelBase with Store {
@computed
FiatCurrency get fiatCurrency => buyAmountViewModel.fiatCurrency;
CryptoCurrency get cryptoCurrency => walletTypeToCryptoCurrency(type);
CryptoCurrency get cryptoCurrency => wallet.currency;
Future <String> fetchUrl() async {
Future<String> fetchUrl() async {
String _url = '';
try {
@@ -95,8 +93,8 @@ abstract class BuyViewModelBase with Store {
_providerList.add(WyreBuyProvider(wallet: wallet));
}
items = _providerList.map((provider) =>
BuyItem(provider: provider, buyAmountViewModel: buyAmountViewModel))
items = _providerList
.map((provider) => BuyItem(provider: provider, buyAmountViewModel: buyAmountViewModel))
.toList();
}
}
}
@@ -7,9 +7,12 @@ import 'package:cake_wallet/entities/contact_record.dart';
import 'package:cake_wallet/entities/wallet_contact.dart';
import 'package:cake_wallet/entities/wallet_list_order_types.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/utils/mobx.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/erc20_token.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
@@ -44,7 +47,10 @@ abstract class ContactListViewModelBase with Store {
walletContacts.add(WalletContact(
address.address,
name,
walletTypeToCryptoCurrency(info.type),
getCryptoCurrencyForWalletListItem(
info.type,
),
walletType: info.type,
));
}
}
@@ -56,7 +62,10 @@ abstract class ContactListViewModelBase with Store {
walletContacts.add(WalletContact(
address,
name,
walletTypeToCryptoCurrency(info.type),
getCryptoCurrencyForWalletListItem(
info.type,
),
walletType: info.type,
));
} else {
addresses.forEach((address, label) {
@@ -67,10 +76,12 @@ abstract class ContactListViewModelBase with Store {
walletContacts.add(WalletContact(
address,
name,
walletTypeToCryptoCurrency(info.type,
isTestnet: info.network == null
? false
: info.network!.toLowerCase().contains("testnet")),
getCryptoCurrencyForWalletListItem(
info.type,
isTestnet:
info.network == null ? false : info.network!.toLowerCase().contains("testnet"),
),
walletType: info.type,
));
});
}
@@ -81,7 +92,10 @@ abstract class ContactListViewModelBase with Store {
key: [WalletType.monero, WalletType.wownero, WalletType.haven].contains(info.type)
? 0
: null),
walletTypeToCryptoCurrency(info.type),
getCryptoCurrencyForWalletListItem(
info.type,
),
walletType: info.type,
));
}
}
@@ -203,7 +217,6 @@ abstract class ContactListViewModelBase with Store {
await sortGroupByType();
break;
case FilterListOrderType.Custom:
default:
reorderAccordingToContactList();
break;
}
@@ -2,6 +2,7 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cake_wallet/entities/sort_balance_types.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cw_core/transaction_history.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/balance.dart';
@@ -20,8 +21,7 @@ part 'balance_view_model.g.dart';
class BalanceRecord {
const BalanceRecord(
{
required this.availableBalance,
{required this.availableBalance,
required this.additionalBalance,
required this.secondAvailableBalance,
required this.secondAdditionalBalance,
@@ -114,6 +114,9 @@ abstract class BalanceViewModelBase with Store {
wallet.type == WalletType.tron ||
wallet.type == WalletType.zano;
@computed
bool get isEVMCompatible => isEVMCompatibleChain(wallet.type);
@computed
bool get hasAccounts => wallet.type == WalletType.monero || wallet.type == WalletType.wownero;
@@ -125,7 +128,16 @@ abstract class BalanceViewModelBase with Store {
@computed
String get asset {
final typeFormatted = walletTypeToString(appStore.wallet!.type);
if (isEVMCompatibleChain(wallet.type)) {
final currentChain = evm!.getCurrentChain(wallet);
if (currentChain != null) {
return currentChain.name;
}
return walletTypeToString(wallet.type);
}
final typeFormatted = walletTypeToString(wallet.type);
switch (wallet.type) {
case WalletType.haven:
@@ -150,18 +162,15 @@ abstract class BalanceViewModelBase with Store {
@computed
String get availableBalanceLabel {
if (displayMode == BalanceDisplayMode.hiddenBalance) {
return S.current.show_balance;
}
else {
} else {
return S.current.xmr_available_balance;
}
}
@computed
String get additionalBalanceLabel {
switch (wallet.type) {
case WalletType.haven:
case WalletType.ethereum:
@@ -225,8 +234,10 @@ abstract class BalanceViewModelBase with Store {
fiatAdditionalBalance: isFiatDisabled ? '' : '${fiatCurrency.toString()} ●●●●●',
fiatAvailableBalance: isFiatDisabled ? '' : '${fiatCurrency.toString()} ●●●●●',
fiatFrozenBalance: isFiatDisabled ? '' : '',
fiatSecondAvailableBalance: isFiatDisabled ? '' : '${fiatCurrency.toString()} ●●●●●',
fiatSecondAdditionalBalance: isFiatDisabled ? '' : '${fiatCurrency.toString()} ●●●●●',
fiatSecondAvailableBalance:
isFiatDisabled ? '' : '${fiatCurrency.toString()} ●●●●●',
fiatSecondAdditionalBalance:
isFiatDisabled ? '' : '${fiatCurrency.toString()} ●●●●●',
asset: key,
formattedAssetTitle: _formatterAsset(key)));
}
@@ -395,7 +406,6 @@ abstract class BalanceViewModelBase with Store {
return balance;
}
@observable
bool isShowCard;
@@ -44,7 +44,6 @@ import 'package:cake_wallet/view_model/dashboard/transaction_list_item.dart';
import 'package:cake_wallet/view_model/settings/sync_mode.dart';
import 'package:cryptography/cryptography.dart';
import 'package:cw_core/balance.dart';
import 'package:cw_core/cake_hive.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/sync_status.dart';
import 'package:cw_core/transaction_history.dart';
@@ -63,6 +62,8 @@ import 'package:permission_handler/permission_handler.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cake_wallet/core/trade_monitor.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/evm/evm.dart';
part 'dashboard_view_model.g.dart';
@@ -336,6 +337,26 @@ abstract class DashboardViewModelBase with Store {
bool _isTransactionDisposerCallbackRunning = false;
@action
void _reloadTransactions() {
if (wallet.type == WalletType.monero || wallet.type == WalletType.wownero) {
return; // Monero/Wownero transactions are handled separately
}
transactions.clear();
transactions.addAll(
wallet.transactionHistory.transactions.values.map(
(transaction) => TransactionListItem(
transaction: transaction,
balanceViewModel: balanceViewModel,
settingsStore: appStore.settingsStore,
key: ValueKey('${wallet.type.name}_transaction_history_item_${transaction.id}_key'),
),
),
);
}
void _transactionDisposerCallback(int _) async {
// Simple check to prevent the callback from being called multiple times in the same frame
if (_isTransactionDisposerCallbackRunning) return;
@@ -443,7 +464,10 @@ abstract class DashboardViewModelBase with Store {
@computed
List<TradeListItem> get trades =>
tradesStore.trades.where((trade) => trade.trade.walletId == wallet.id).toList();
tradesStore.trades.where((trade) {
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
return trade.trade.walletId == wallet.id && isSameChain;
}).toList();
@computed
List<OrderListItem> get orders =>
@@ -566,6 +590,30 @@ abstract class DashboardViewModelBase with Store {
@computed
bool get showSilentPaymentsCard => hasSilentPayments && settingsStore.silentPaymentsCardDisplay;
@computed
bool get isEVMWallet => isEVMCompatibleChain(wallet.type);
@computed
List<ChainInfo> get availableChains {
if (!isEVMWallet) return [];
return evm!.getAllChains();
}
@computed
ChainInfo? get currentChain {
if (!isEVMWallet) return null;
return evm!.getCurrentChain(wallet);
}
@action
Future<void> selectChain(int chainId) async {
if (!isEVMWallet) return;
final node = appStore.settingsStore.getCurrentNode(wallet.type, chainId: chainId);
await evm!.selectChain(wallet, chainId, node: node);
}
final KeyService keyService;
final SharedPreferences sharedPreferences;
@@ -880,6 +928,8 @@ abstract class DashboardViewModelBase with Store {
ReactionDisposer? _transactionDisposer;
ReactionDisposer? _chainChangeDisposer;
@computed
bool get hasPowNodes => [WalletType.nano, WalletType.banano].contains(wallet.type);
@@ -925,7 +975,12 @@ abstract class DashboardViewModelBase with Store {
}
Future<void> reconnect() async {
final node = appStore.settingsStore.getCurrentNode(wallet.type);
int? chainId;
if (isEVMWallet) {
chainId = evm!.getSelectedChainId(wallet);
}
final node = appStore.settingsStore.getCurrentNode(wallet.type, chainId: chainId);
await wallet.connectToNode(node: node);
if (hasPowNodes) {
final powNode = settingsStore.getCurrentPowNode(wallet.type);
@@ -983,22 +1038,25 @@ abstract class DashboardViewModelBase with Store {
// subname = null;
subname = '';
transactions.clear();
transactions.addAll(
wallet.transactionHistory.transactions.values.map(
(transaction) => TransactionListItem(
transaction: transaction,
balanceViewModel: balanceViewModel,
settingsStore: appStore.settingsStore,
key: ValueKey('${wallet.type.name}_transaction_history_item_${transaction.id}_key'),
),
),
);
_reloadTransactions();
}
_transactionDisposer?.reaction.dispose();
if (isEVMCompatibleChain(wallet.type)) {
_chainChangeDisposer?.reaction.dispose();
_chainChangeDisposer = reaction((_) {
// Access selectedChainId through proxy to track chain changes
return evm!.getSelectedChainId(wallet);
}, (_) {
// When chain switches, reload transactions for the new chain
_reloadTransactions();
});
} else {
_chainChangeDisposer?.reaction.dispose();
_chainChangeDisposer = null;
}
_transactionDisposer = reaction((_) {
final length = appStore.wallet!.transactionHistory.transactions.length;
if (length == 0) {
@@ -1085,10 +1143,11 @@ abstract class DashboardViewModelBase with Store {
@action
void setBuiltinTor(bool value, BuildContext context) {
if (value) {
unawaited(showPopUp<bool>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
unawaited(
showPopUp<bool>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).tor_connection,
alertContent: S.of(context).tor_experimental,
buttonText: S.of(context).ok,
@@ -1101,13 +1160,25 @@ abstract class DashboardViewModelBase with Store {
settingsStore.currentBuiltinTor = value;
if (value) {
unawaited(ensureTorStarted(context: context).then((_) async {
if (settingsStore.currentBuiltinTor == false) return; // return when tor got disabled in the meantime;
await wallet.connectToNode(node: appStore.settingsStore.getCurrentNode(wallet.type));
if (settingsStore.currentBuiltinTor == false)
return; // return when tor got disabled in the meantime;
int? chainId;
if (isEVMWallet) {
chainId = evm!.getSelectedChainId(wallet);
}
await wallet.connectToNode(
node: appStore.settingsStore.getCurrentNode(wallet.type, chainId: chainId));
}));
} else {
unawaited(ensureTorStopped(context: context).then((_) async {
if (settingsStore.currentBuiltinTor == true) return; // return when tor got enabled in the meantime;
await wallet.connectToNode(node: appStore.settingsStore.getCurrentNode(wallet.type));
if (settingsStore.currentBuiltinTor == true)
return; // return when tor got enabled in the meantime;
int? chainId;
if (isEVMWallet) {
chainId = evm!.getSelectedChainId(wallet);
}
await wallet.connectToNode(
node: appStore.settingsStore.getCurrentNode(wallet.type, chainId: chainId));
}));
}
}
@@ -1211,7 +1282,7 @@ abstract class DashboardViewModelBase with Store {
if (tx.isReplaced == true) return ' (replaced)';
}
if (wallet.type == WalletType.ethereum && tx.evmSignatureName == 'approval')
if (wallet.chainId == 1 && tx.evmSignatureName == 'approval')
return ' (${tx.evmSignatureName})';
return '';
}
@@ -1,13 +1,10 @@
import 'dart:convert';
import 'package:cake_wallet/arbitrum/arbitrum.dart';
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/core/fiat_conversion_service.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cake_wallet/entities/erc20_token_info_moralis.dart';
import 'package:cake_wallet/entities/sort_balance_types.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/store/settings_store.dart';
@@ -33,6 +30,23 @@ abstract class HomeSettingsViewModelBase with Store {
isDeletingToken = false,
isValidatingContractAddress = false {
_updateTokensList();
// React to wallet changes
reaction((_) => _balanceViewModel.wallet, (_) {
_updateTokensList();
});
reaction((_) {
final wallet = _balanceViewModel.wallet;
if (isEVMCompatibleChain(wallet.type)) {
final selectedChainId = evm!.getSelectedChainId(wallet);
final erc20Currencies = evm!.getERC20Currencies(wallet);
return '${wallet.currency.title}_${selectedChainId}_${erc20Currencies.length}';
}
return null;
}, (_) async {
await Future.delayed(const Duration(milliseconds: 200));
_updateTokensList();
});
}
final SettingsStore _settingsStore;
@@ -76,21 +90,9 @@ abstract class HomeSettingsViewModelBase with Store {
}) async {
try {
isAddingToken = true;
if (_balanceViewModel.wallet.type == WalletType.ethereum) {
final erc20token = Erc20Token(
name: token.name,
symbol: token.title,
decimal: token.decimals,
contractAddress: contractAddress.toLowerCase(),
iconPath: token.iconPath,
isPotentialScam: token.isPotentialScam,
);
await ethereum!.addErc20Token(_balanceViewModel.wallet, erc20token);
}
if (_balanceViewModel.wallet.type == WalletType.polygon) {
final polygonToken = Erc20Token(
if (isEVMCompatibleChain(_balanceViewModel.wallet.type)) {
final evmToken = Erc20Token(
name: token.name,
symbol: token.title,
decimal: token.decimals,
@@ -98,30 +100,7 @@ abstract class HomeSettingsViewModelBase with Store {
iconPath: token.iconPath,
isPotentialScam: token.isPotentialScam,
);
await polygon!.addErc20Token(_balanceViewModel.wallet, polygonToken);
}
if (_balanceViewModel.wallet.type == WalletType.base) {
final baseToken = Erc20Token(
name: token.name,
symbol: token.title,
decimal: token.decimals,
contractAddress: contractAddress.toLowerCase(),
iconPath: token.iconPath,
isPotentialScam: token.isPotentialScam,
);
await base!.addErc20Token(_balanceViewModel.wallet, baseToken);
}
if (_balanceViewModel.wallet.type == WalletType.arbitrum) {
final arbitrumToken = Erc20Token(
name: token.name,
symbol: token.title,
decimal: token.decimals,
contractAddress: contractAddress.toLowerCase(),
iconPath: token.iconPath,
isPotentialScam: token.isPotentialScam,
);
await arbitrum!.addErc20Token(_balanceViewModel.wallet, arbitrumToken);
await evm!.addErc20Token(_balanceViewModel.wallet, evmToken);
}
if (_balanceViewModel.wallet.type == WalletType.solana) {
@@ -153,20 +132,8 @@ abstract class HomeSettingsViewModelBase with Store {
@action
bool checkIfTokenIsAlreadyAdded(String contractAddress) {
if (_balanceViewModel.wallet.type == WalletType.ethereum) {
return ethereum!.isTokenAlreadyAdded(_balanceViewModel.wallet, contractAddress);
}
if (_balanceViewModel.wallet.type == WalletType.polygon) {
return polygon!.isTokenAlreadyAdded(_balanceViewModel.wallet, contractAddress);
}
if (_balanceViewModel.wallet.type == WalletType.base) {
return base!.isTokenAlreadyAdded(_balanceViewModel.wallet, contractAddress);
}
if (_balanceViewModel.wallet.type == WalletType.arbitrum) {
return arbitrum!.isTokenAlreadyAdded(_balanceViewModel.wallet, contractAddress);
if (isEVMCompatibleChain(_balanceViewModel.wallet.type)) {
return evm!.isTokenAlreadyAdded(_balanceViewModel.wallet, contractAddress);
}
if (_balanceViewModel.wallet.type == WalletType.solana) {
@@ -188,20 +155,8 @@ abstract class HomeSettingsViewModelBase with Store {
Future<void> deleteToken(CryptoCurrency token) async {
try {
isDeletingToken = true;
if (_balanceViewModel.wallet.type == WalletType.ethereum) {
await ethereum!.deleteErc20Token(_balanceViewModel.wallet, token as Erc20Token);
}
if (_balanceViewModel.wallet.type == WalletType.polygon) {
await polygon!.deleteErc20Token(_balanceViewModel.wallet, token as Erc20Token);
}
if (_balanceViewModel.wallet.type == WalletType.base) {
await base!.deleteErc20Token(_balanceViewModel.wallet, token as Erc20Token);
}
if (_balanceViewModel.wallet.type == WalletType.arbitrum) {
await arbitrum!.deleteErc20Token(_balanceViewModel.wallet, token as Erc20Token);
if (isEVMCompatibleChain(_balanceViewModel.wallet.type)) {
await evm!.deleteErc20Token(_balanceViewModel.wallet, token as Erc20Token);
}
if (_balanceViewModel.wallet.type == WalletType.solana) {
@@ -237,7 +192,7 @@ abstract class HomeSettingsViewModelBase with Store {
bool isUnverifiedContract = await _isContractUnverified(
contractAddress,
chainId: getChainIdBasedOnWalletType(_balanceViewModel.wallet.type).toString(),
chainId: evm!.getSelectedChainId(_balanceViewModel.wallet).toString(),
);
final showWarningForContractAddress = isPotentialScamViaMoralis || isUnverifiedContract;
@@ -253,16 +208,10 @@ abstract class HomeSettingsViewModelBase with Store {
List<String> defaultTokenAddresses = [];
switch (_balanceViewModel.wallet.type) {
case WalletType.ethereum:
defaultTokenAddresses = ethereum!.getDefaultTokenContractAddresses();
break;
case WalletType.polygon:
defaultTokenAddresses = polygon!.getDefaultTokenContractAddresses();
break;
case WalletType.base:
defaultTokenAddresses = base!.getDefaultTokenContractAddresses();
break;
case WalletType.arbitrum:
defaultTokenAddresses = arbitrum!.getDefaultTokenContractAddresses();
defaultTokenAddresses = evm!.getDefaultTokenContractAddresses(_balanceViewModel.wallet);
break;
case WalletType.solana:
defaultTokenAddresses = solana!.getDefaultTokenContractAddresses();
@@ -323,9 +272,9 @@ abstract class HomeSettingsViewModelBase with Store {
}
// Tokens whose contract have not been verified are potentially risky tokens.
if (tokenInfo.verifiedContract == false) {
return true;
}
// if (tokenInfo.verifiedContract == false) {
// return true;
// }
// Tokens with a security score less than 40 are potentially risky, requiring caution when dealing with them.
if (tokenInfo.securityScore != null && tokenInfo.securityScore! < 40) {
@@ -391,20 +340,8 @@ abstract class HomeSettingsViewModelBase with Store {
}
Future<CryptoCurrency?> getToken(String contractAddress) async {
if (_balanceViewModel.wallet.type == WalletType.ethereum) {
return await ethereum!.getErc20Token(_balanceViewModel.wallet, contractAddress);
}
if (_balanceViewModel.wallet.type == WalletType.polygon) {
return await polygon!.getErc20Token(_balanceViewModel.wallet, contractAddress);
}
if (_balanceViewModel.wallet.type == WalletType.base) {
return await base!.getErc20Token(_balanceViewModel.wallet, contractAddress);
}
if (_balanceViewModel.wallet.type == WalletType.arbitrum) {
return await arbitrum!.getErc20Token(_balanceViewModel.wallet, contractAddress);
if (isEVMCompatibleChain(_balanceViewModel.wallet.type)) {
return await evm!.getErc20Token(_balanceViewModel.wallet, contractAddress);
}
if (_balanceViewModel.wallet.type == WalletType.solana) {
@@ -438,24 +375,9 @@ abstract class HomeSettingsViewModelBase with Store {
void changeTokenAvailability(CryptoCurrency token, bool value) async {
token.enabled = value;
if (_balanceViewModel.wallet.type == WalletType.ethereum) {
ethereum!.addErc20Token(_balanceViewModel.wallet, token as Erc20Token);
if (!value) ethereum!.removeTokenTransactionsInHistory(_balanceViewModel.wallet, token);
}
if (_balanceViewModel.wallet.type == WalletType.polygon) {
polygon!.addErc20Token(_balanceViewModel.wallet, token as Erc20Token);
if (!value) polygon!.removeTokenTransactionsInHistory(_balanceViewModel.wallet, token);
}
if (_balanceViewModel.wallet.type == WalletType.base) {
base!.addErc20Token(_balanceViewModel.wallet, token as Erc20Token);
if (!value) base!.removeTokenTransactionsInHistory(_balanceViewModel.wallet, token);
}
if (_balanceViewModel.wallet.type == WalletType.arbitrum) {
arbitrum!.addErc20Token(_balanceViewModel.wallet, token as Erc20Token);
if (!value) arbitrum!.removeTokenTransactionsInHistory(_balanceViewModel.wallet, token);
if (isEVMCompatibleChain(_balanceViewModel.wallet.type)) {
evm!.addErc20Token(_balanceViewModel.wallet, token as Erc20Token);
if (!value) evm!.removeTokenTransactionsInHistory(_balanceViewModel.wallet, token);
}
if (_balanceViewModel.wallet.type == WalletType.solana) {
@@ -495,32 +417,8 @@ abstract class HomeSettingsViewModelBase with Store {
tokens.clear();
if (_balanceViewModel.wallet.type == WalletType.ethereum) {
tokens.addAll(ethereum!
.getERC20Currencies(_balanceViewModel.wallet)
.where((element) => _matchesSearchText(element))
.toList()
..sort(_sortFunc));
}
if (_balanceViewModel.wallet.type == WalletType.polygon) {
tokens.addAll(polygon!
.getERC20Currencies(_balanceViewModel.wallet)
.where((element) => _matchesSearchText(element))
.toList()
..sort(_sortFunc));
}
if (_balanceViewModel.wallet.type == WalletType.base) {
tokens.addAll(base!
.getERC20Currencies(_balanceViewModel.wallet)
.where((element) => _matchesSearchText(element))
.toList()
..sort(_sortFunc));
}
if (_balanceViewModel.wallet.type == WalletType.arbitrum) {
tokens.addAll(arbitrum!
if (isEVMCompatibleChain(_balanceViewModel.wallet.type)) {
tokens.addAll(evm!
.getERC20Currencies(_balanceViewModel.wallet)
.where((element) => _matchesSearchText(element))
.toList()
@@ -586,20 +484,8 @@ abstract class HomeSettingsViewModelBase with Store {
return solana!.getTokenAddress(asset);
}
if (_balanceViewModel.wallet.type == WalletType.ethereum) {
return ethereum!.getTokenAddress(asset);
}
if (_balanceViewModel.wallet.type == WalletType.polygon) {
return polygon!.getTokenAddress(asset);
}
if (_balanceViewModel.wallet.type == WalletType.base) {
return base!.getTokenAddress(asset);
}
if (_balanceViewModel.wallet.type == WalletType.arbitrum) {
return arbitrum!.getTokenAddress(asset);
if (isEVMCompatibleChain(_balanceViewModel.wallet.type)) {
return evm!.getTokenAddress(asset);
}
if (_balanceViewModel.wallet.type == WalletType.zano) {
+27 -14
View File
@@ -6,6 +6,7 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/src/screens/wallet_connect/services/bottom_sheet_service.dart';
import 'package:cake_wallet/src/screens/wallet_connect/widgets/bottom_sheet/bottom_sheet_message_display_widget.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:mobx/mobx.dart';
@@ -23,7 +24,16 @@ abstract class NFTViewModelBase with Store {
: isLoading = false,
isImportNFTLoading = false,
nftAssetByWalletModels = ObservableList(),
solanaNftAssetModels = ObservableList();
solanaNftAssetModels = ObservableList() {
if (isEVMCompatibleChain(appStore.wallet!.type)) {
reaction((_) {
final wallet = appStore.wallet;
if (wallet != null) return wallet.chainId;
return null;
}, (_) => getNFTAssetByWallet());
}
}
final AppStore appStore;
final BottomSheetService bottomSheetService;
@@ -40,14 +50,14 @@ abstract class NFTViewModelBase with Store {
@action
Future<void> getNFTAssetByWallet() async {
final walletType = appStore.wallet!.type;
final wallet = appStore.wallet!;
if (!isNFTACtivatedChain(walletType)) return;
if (!isNFTACtivatedChain(wallet.type, wallet.chainId)) return;
final walletAddress = appStore.wallet!.walletInfo.address;
final walletAddress = wallet.walletInfo.address;
log('Fetching wallet NFTs for $walletAddress');
final chainName = getChainNameBasedOnWalletType(walletType);
final chainName = getChainNameBasedOnWalletType(wallet.type, chainId: wallet.chainId);
// the [chain] refers to the chain network that the nft is on
// the [format] refers to the number format type of the responses
// the [normalizedMetadata] field is a boolean that determines if
@@ -56,7 +66,7 @@ abstract class NFTViewModelBase with Store {
// the [excludeSpam] field is a boolean that determines if spam nfts be excluded from the response.
Uri uri;
if (walletType == WalletType.solana) {
if (wallet.type == WalletType.solana) {
uri = Uri.https(
'solana-gateway.moralis.io',
'/account/$chainName/$walletAddress/nft',
@@ -87,11 +97,10 @@ abstract class NFTViewModelBase with Store {
"X-API-Key": secrets.moralisApiKey,
},
);
final decodedResponse = jsonDecode(response.body) as Map<String, dynamic>;
final decodedResponse = jsonDecode(response.body);
if (walletType == WalletType.solana) {
if (wallet.type == WalletType.solana) {
final results = await Future.wait(
(decodedResponse as List<dynamic>).map(
(x) {
@@ -106,8 +115,7 @@ abstract class NFTViewModelBase with Store {
solanaNftAssetModels.addAll(results);
} else {
final result =
WalletNFTsResponseModel.fromJson(decodedResponse as Map<String, dynamic>).result ?? [];
final result = WalletNFTsResponseModel.fromJson(decodedResponse as Map<String, dynamic>).result ?? [];
nftAssetByWalletModels.clear();
@@ -139,7 +147,7 @@ abstract class NFTViewModelBase with Store {
"X-API-Key": secrets.moralisApiKey,
},
);
final decodedResponse = jsonDecode(response.body) as Map<String, dynamic>;
return SolanaNFTAssetModel.fromJson(decodedResponse);
@@ -147,7 +155,12 @@ abstract class NFTViewModelBase with Store {
@action
Future<void> importNFT(String tokenAddress, String? tokenId) async {
final chainName = getChainNameBasedOnWalletType(appStore.wallet!.type);
final walletType = appStore.wallet!.type;
int? chainId;
if (isEVMCompatibleChain(walletType)) {
chainId = evm!.getSelectedChainId(appStore.wallet!);
}
final chainName = getChainNameBasedOnWalletType(walletType, chainId: chainId);
// the [chain] refers to the chain network that the nft is on
// the [format] refers to the number format type of the responses
// the [normalizedMetadata] field is a boolean that determines if
@@ -179,7 +192,7 @@ abstract class NFTViewModelBase with Store {
"X-API-Key": secrets.moralisApiKey,
},
);
final decodedResponse = jsonDecode(response.body) as Map<String, dynamic>;
final nftAsset = NFTAssetModel.fromJson(decodedResponse);
@@ -1,12 +1,9 @@
import 'package:cake_wallet/arbitrum/arbitrum.dart';
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/decred/decred.dart';
import 'package:cake_wallet/entities/balance_display_mode.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/tron/tron.dart';
@@ -126,22 +123,8 @@ class TransactionListItem extends ActionListItem with Keyable {
CryptoCurrency? get assetOfTransaction {
try {
if (balanceViewModel.wallet.type == WalletType.ethereum) {
final asset = ethereum!.assetOfTransaction(balanceViewModel.wallet, transaction);
return asset;
}
if (balanceViewModel.wallet.type == WalletType.polygon) {
final asset = polygon!.assetOfTransaction(balanceViewModel.wallet, transaction);
return asset;
}
if (balanceViewModel.wallet.type == WalletType.base) {
final asset = base!.assetOfTransaction(balanceViewModel.wallet, transaction);
return asset;
}
if (balanceViewModel.wallet.type == WalletType.arbitrum) {
final asset = arbitrum!.assetOfTransaction(balanceViewModel.wallet, transaction);
if (isEVMCompatibleChain(balanceViewModel.wallet.type)) {
final asset = evm!.assetOfTransaction(balanceViewModel.wallet, transaction);
return asset;
}
@@ -184,32 +167,15 @@ class TransactionListItem extends ActionListItem with Keyable {
price: price);
break;
case WalletType.ethereum:
final asset = ethereum!.assetOfTransaction(balanceViewModel.wallet, transaction);
final price = balanceViewModel.fiatConvertationStore.prices[asset];
amount = calculateFiatAmountRaw(
cryptoAmount: ethereum!.formatterEthereumAmountToDouble(transaction: transaction),
price: price);
break;
case WalletType.polygon:
final asset = polygon!.assetOfTransaction(balanceViewModel.wallet, transaction);
final price = balanceViewModel.fiatConvertationStore.prices[asset];
amount = calculateFiatAmountRaw(
cryptoAmount: polygon!.formatterPolygonAmountToDouble(transaction: transaction),
price: price);
break;
case WalletType.base:
final asset = base!.assetOfTransaction(balanceViewModel.wallet, transaction);
final price = balanceViewModel.fiatConvertationStore.prices[asset];
amount = calculateFiatAmountRaw(
cryptoAmount: base!.formatterBaseAmountToDouble(transaction: transaction),
price: price);
break;
case WalletType.arbitrum:
final asset = arbitrum!.assetOfTransaction(balanceViewModel.wallet, transaction);
final asset = evm!.assetOfTransaction(balanceViewModel.wallet, transaction);
final price = balanceViewModel.fiatConvertationStore.prices[asset];
amount = calculateFiatAmountRaw(
cryptoAmount: arbitrum!.formatterArbitrumAmountToDouble(transaction: transaction),
price: price);
cryptoAmount: evm!.formatterEVMAmountToDouble(transaction: transaction),
price: price,
);
break;
case WalletType.nano:
amount = calculateFiatAmountRaw(
@@ -242,9 +208,10 @@ class TransactionListItem extends ActionListItem with Keyable {
}
final price = balanceViewModel.fiatConvertationStore.prices[asset];
amount = calculateFiatAmountRaw(
cryptoAmount: zano!.formatterIntAmountToDouble(amount: transaction.amount, currency: asset, forFee: false),
price: price);
break;
cryptoAmount: zano!.formatterIntAmountToDouble(
amount: transaction.amount, currency: asset, forFee: false),
price: price);
break;
case WalletType.decred:
amount = calculateFiatAmountRaw(
cryptoAmount: decred!.formatterDecredAmountToDouble(amount: transaction.amount),
@@ -19,7 +19,6 @@ import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/xoswap_exchange_provider.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/arbitrum/arbitrum.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_item.dart';
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
@@ -339,7 +338,9 @@ abstract class ExchangeTradeViewModelBase with Store {
wallet.currency == CryptoCurrency.baseEth && tradeFrom?.tag == CryptoCurrency.baseEth.tag;
bool _isArbitrumToken() =>
wallet.currency == CryptoCurrency.arbEth && tradeFrom?.tag == CryptoCurrency.arbEth.tag;
wallet.currency == CryptoCurrency.arbEth &&
(tradeFrom?.tag == CryptoCurrency.arbEth.tag ||
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
bool _isTronToken() =>
wallet.currency == CryptoCurrency.trx && tradeFrom?.tag == CryptoCurrency.trx.title;
@@ -485,5 +486,5 @@ abstract class ExchangeTradeViewModelBase with Store {
}
@computed
String get qrImage => getQrImage(wallet.type);
String get qrImage => getQrImage(wallet.type, selectedChainId: wallet.chainId);
}
@@ -27,7 +27,6 @@ import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/exolix_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/near_Intents_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/stealth_ex_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/swapsxyz_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/swaptrade_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/xoswap_exchange_provider.dart';
@@ -39,6 +38,7 @@ import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
import 'package:cake_wallet/store/dashboard/trades_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/store/templates/exchange_template_store.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/utils/token_utilities.dart';
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
@@ -186,6 +186,17 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
if (isElectrumWallet) {
bitcoin!.updateFeeRates(wallet);
}
reaction((_) => wallet.currency, (_) async {
// When currency changes (e.g., EVM chain switch), update currencies
await Future.delayed(const Duration(milliseconds: 100));
receiveCurrency = wallet.currency;
depositCurrency = wallet.currency;
// Only refresh ETH tokens for EVM wallets
if (isEVMCompatibleChain(wallet.type)) {
_injectUserEthTokensIntoCurrencyLists();
}
});
}
bool useSameWalletAddress(CryptoCurrency currency) =>
@@ -332,7 +343,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
@computed
TransactionPriority get transactionPriority {
final priority = _settingsStore.priority[wallet.type];
final priority = _settingsStore.getPriority(wallet.type, chainId: wallet.chainId);
if (priority == null) {
throw Exception('Unexpected type ${wallet.type.toString()}');
@@ -711,6 +722,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
isSendAll: isSendAllEnabled,
);
trade.walletId = wallet.id;
trade.chainId = wallet.chainId;
trade.fromWalletAddress = wallet.walletAddresses.address;
final canCreateTrade = await isCanCreateTrade(trade);
@@ -785,7 +797,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
WalletType.bitcoinCash,
WalletType.dogecoin,
].contains(wallet.type)) {
final priority = _settingsStore.priority[wallet.type]!;
final priority = _settingsStore.getPriority(wallet.type)!;
final amount = await bitcoin!.estimateFakeSendAllTxAmount(
wallet,
@@ -835,6 +847,12 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
}
void _initialPairBasedOnWallet() {
if (isEVMCompatibleChain(wallet.type)) {
depositCurrency = wallet.currency;
receiveCurrency = CryptoCurrency.xmr;
return;
}
switch (wallet.type) {
case WalletType.monero:
depositCurrency = CryptoCurrency.xmr;
@@ -3,8 +3,7 @@ import 'dart:io';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
import 'package:cake_wallet/wallet_type_utils.dart';
import 'package:cw_core/hardware/hardware_wallet_service.dart';
@@ -89,9 +88,8 @@ abstract class BitboxViewModelBase extends HardwareWalletViewModel with Store {
case WalletType.litecoin:
return bitcoin!.getBitboxHardwareWalletService(bitboxManager, false);
case WalletType.ethereum:
return ethereum!.getBitboxHardwareWalletService(bitboxManager);
case WalletType.polygon:
return polygon!.getBitboxHardwareWalletService(bitboxManager);
return evm!.getBitboxHardwareWalletService(bitboxManager);
default:
throw UnimplementedError();
}
@@ -104,9 +102,8 @@ abstract class BitboxViewModelBase extends HardwareWalletViewModel with Store {
case WalletType.litecoin:
return bitcoin!.setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
case WalletType.ethereum:
return ethereum!.setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
case WalletType.polygon:
return polygon!.setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
return evm!.setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
default:
throw Exception('Unexpected wallet type: ${wallet.type}');
}
@@ -3,11 +3,10 @@ import 'dart:io';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/main.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
@@ -187,9 +186,8 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
case WalletType.litecoin:
return bitcoin!.setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
case WalletType.ethereum:
return ethereum!.setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
case WalletType.polygon:
return polygon!.setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
return evm!.setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
default:
throw Exception('Unexpected wallet type: ${wallet.type}');
}
@@ -203,9 +201,8 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
case WalletType.litecoin:
return bitcoin!.getLedgerHardwareWalletService(connection, false);
case WalletType.ethereum:
return ethereum!.getLedgerHardwareWalletService(connection);
case WalletType.polygon:
return polygon!.getLedgerHardwareWalletService(connection);
return evm!.getLedgerHardwareWalletService(connection);
default:
throw UnimplementedError();
}
@@ -2,8 +2,7 @@ import 'dart:async';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/entities/hardware_wallet/hardware_wallet_device.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
import 'package:cw_core/hardware/hardware_wallet_service.dart';
import 'package:cw_core/wallet_base.dart';
@@ -57,9 +56,8 @@ abstract class TrezorViewModelBase extends HardwareWalletViewModel with Store {
case WalletType.litecoin:
return bitcoin!.getTrezorHardwareWalletService(trezorConnect, false);
case WalletType.ethereum:
return ethereum!.getTrezorHardwareWalletService(trezorConnect);
case WalletType.polygon:
return polygon!.getTrezorHardwareWalletService(trezorConnect);
return evm!.getTrezorHardwareWalletService(trezorConnect);
default:
throw UnimplementedError();
}
@@ -72,9 +70,8 @@ abstract class TrezorViewModelBase extends HardwareWalletViewModel with Store {
case WalletType.litecoin:
return bitcoin!.setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
case WalletType.ethereum:
return ethereum!.setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
case WalletType.polygon:
return polygon!.setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
return evm!.setHardwareWalletService(wallet, await getHardwareWalletService(wallet.type));
default:
throw Exception('Unexpected wallet type: ${wallet.type}');
}
@@ -2,7 +2,7 @@ import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/core/utilities.dart';
import 'package:cake_wallet/entities/calculate_fiat_amount.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
@@ -13,7 +13,6 @@ import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/parse_fixed.dart';
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:mobx/mobx.dart';
part 'deuro_view_model.g.dart';
@@ -25,8 +24,8 @@ abstract class DEuroViewModelBase with Store {
static BigInt get MIN_ACCRUED_INTEREST => BigInt.parse("1000000000000");
DEuroViewModelBase(this._appStore, this.balanceViewModel, this._settingsStore,
this._fiatConversationStore,
DEuroViewModelBase(
this._appStore, this.balanceViewModel, this._settingsStore, this._fiatConversationStore,
[this.hardwareWalletViewModel]) {
reloadInterestRate();
reloadSavingsUserData();
@@ -84,7 +83,7 @@ abstract class DEuroViewModelBase with Store {
@computed
String get savingsBalanceFormated =>
ethereum!.formatterEthereumAmountToDouble(amount: savingsBalance).toStringAsFixed(6);
evm!.formatterEVMAmountToDouble(amount: savingsBalance).toStringAsFixed(6);
@computed
String get fiatSavingsBalanceFormated => _getDEuroFiatAmount(savingsBalanceFormated);
@@ -100,7 +99,7 @@ abstract class DEuroViewModelBase with Store {
@computed
String get accruedInterestFormated =>
ethereum!.formatterEthereumAmountToDouble(amount: accruedInterest).toStringAsFixed(6);
evm!.formatterEVMAmountToDouble(amount: accruedInterest).toStringAsFixed(6);
@computed
String get fiatAccruedInterestFormated => _getDEuroFiatAmount(accruedInterestFormated);
@@ -128,15 +127,15 @@ abstract class DEuroViewModelBase with Store {
@action
Future<void> reloadSavingsUserData() async {
approvedTokens = await ethereum!.getDEuroSavingsApproved(_appStore.wallet!);
savingsBalance = await ethereum!.getDEuroSavingsBalance(_appStore.wallet!);
accruedInterest = await ethereum!.getDEuroAccruedInterest(_appStore.wallet!);
approvedTokens = await evm!.getDEuroSavingsApproved(_appStore.wallet!) ?? BigInt.zero;
savingsBalance = await evm!.getDEuroSavingsBalance(_appStore.wallet!) ?? BigInt.zero;
accruedInterest = await evm!.getDEuroAccruedInterest(_appStore.wallet!) ?? BigInt.zero;
isLoading = false;
}
@action
Future<void> reloadInterestRate() async {
final interestRateRaw = await ethereum!.getDEuroInterestRate(_appStore.wallet!);
final interestRateRaw = await evm!.getDEuroInterestRate(_appStore.wallet!) ?? BigInt.zero;
interestRateFormated = (interestRateRaw / BigInt.from(10000)).toString();
}
@@ -150,8 +149,12 @@ abstract class DEuroViewModelBase with Store {
}
try {
state = TransactionCommitting();
final priority = _appStore.settingsStore.priority[WalletType.ethereum]!;
approvalTransaction = await ethereum!.enableDEuroSaving(_appStore.wallet!, priority);
final priority = _appStore.settingsStore.getPriority(wallet.type, chainId: wallet.chainId)!;
final approval = await evm!.enableDEuroSaving(_appStore.wallet!, priority);
if (approval == null) {
throw Exception('DEuro saving not available');
}
approvalTransaction = approval;
state = InitialExecutionState();
} catch (e) {
state = FailureState(e.toString());
@@ -162,26 +165,57 @@ abstract class DEuroViewModelBase with Store {
Future<void> prepareSavingsEdit(String amountRaw, bool isAdding) async {
try {
state = TransactionCommitting();
final amount = parseFixed(amountRaw, 18);
final priority = _appStore.settingsStore.priority[WalletType.ethereum]!;
if (amountRaw.isEmpty || amountRaw.trim().isEmpty) {
throw Exception('Invalid amount: amount cannot be empty');
}
final amount = tryParseFixed(amountRaw, 18);
if (amount == BigInt.zero || amount == null) {
throw Exception('Invalid amount: amount cannot be zero');
}
final priority = _appStore.settingsStore.getPriority(wallet.type, chainId: wallet.chainId)!;
actionType = isAdding ? DEuroActionType.deposit : DEuroActionType.withdraw;
transaction = await (isAdding
? ethereum!.addDEuroSaving(_appStore.wallet!, amount, priority)
: ethereum!.removeDEuroSaving(_appStore.wallet!, amount, priority));
final tx = await (isAdding
? evm!.addDEuroSaving(_appStore.wallet!, amount, priority)
: evm!.removeDEuroSaving(_appStore.wallet!, amount, priority));
if (tx == null) {
throw Exception('DEuro saving not available');
}
transaction = tx;
state = InitialExecutionState();
} catch (e) {
state = FailureState(e.toString());
}
}
Future<void> prepareCollectInterest() => prepareSavingsEdit(accruedInterestFormated, false);
Future<void> prepareCollectInterest() async {
if (accruedInterest < MIN_ACCRUED_INTEREST) {
state = FailureState('Accrued interest is below minimum threshold');
return;
}
final formatted = accruedInterestFormated;
if (formatted.isEmpty || formatted == '0.000000') {
state = FailureState('Invalid accrued interest amount');
return;
}
await prepareSavingsEdit(formatted, false);
}
Future<void> prepareReinvestInterest() async {
try {
state = TransactionCommitting();
actionType = DEuroActionType.reinvest;
final priority = _appStore.settingsStore.priority[WalletType.ethereum]!;
transaction = await ethereum!.reinvestDEuroInterest(_appStore.wallet!, priority);
final priority = _appStore.settingsStore.getPriority(wallet.type, chainId: wallet.chainId)!;
final tx = await evm!.reinvestDEuroInterest(_appStore.wallet!, priority);
if (tx == null) {
throw Exception('DEuro saving not available');
}
transaction = tx;
state = InitialExecutionState();
} catch (e) {
state = FailureState(e.toString());
@@ -10,6 +10,8 @@ import 'package:cw_core/node.dart';
import 'package:cake_wallet/entities/node_list.dart';
import 'package:cake_wallet/entities/default_settings_migration.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
part 'node_list_view_model.g.dart';
@@ -24,16 +26,37 @@ abstract class NodeListViewModelBase with Store {
reaction((_) => _appStore.wallet, (WalletBase? _wallet) {
_bindNodes();
});
reaction((_) {
final wallet = _appStore.wallet;
if (wallet != null && isEVMCompatibleChain(wallet.type)) {
// Access selectedChainId to track changes
return evm!.getSelectedChainId(wallet);
}
return null;
}, (_) {
_bindNodes();
});
}
@computed
Node get currentNode {
final node = settingsStore.nodes[_appStore.wallet!.type];
final wallet = _appStore.wallet!;
final walletType = wallet.type;
if (node == null) {
throw Exception('No node for wallet type: ${_appStore.wallet!.type}');
int? chainId;
if (isEVMCompatibleChain(walletType)) {
chainId = evm!.getSelectedChainId(wallet);
}
if (isEVMCompatibleChain(walletType) && chainId != null) {
return settingsStore.getCurrentNode(walletType, chainId: chainId);
}
final node = settingsStore.nodes[walletType];
if (node == null) {
throw Exception('No node for wallet type: $walletType');
}
return node;
}
@@ -56,11 +79,27 @@ abstract class NodeListViewModelBase with Store {
Future<void> reset() async {
await resetToDefault(_nodeSource);
final wallet = _appStore.wallet!;
final walletType = wallet.type;
Node node;
if (_appStore.wallet!.type == WalletType.bitcoin && _appStore.wallet!.isTestnet) {
if (walletType == WalletType.bitcoin && wallet.isTestnet) {
node = getBitcoinTestnetDefaultElectrumServer(nodes: _nodeSource)!;
} else if (isEVMCompatibleChain(walletType)) {
final chainId = evm!.getSelectedChainId(wallet);
if (chainId != null) {
final nodeWalletType = evm!.getWalletTypeByChainId(chainId);
if (nodeWalletType != null) {
node = getDefaultNode(nodes: _nodeSource, type: nodeWalletType)!;
} else {
throw Exception(
'Cannot reset node for EVM wallet: wallet type not found for chainId: $chainId');
}
} else {
throw Exception('Cannot reset node for EVM wallet: chainId is null');
}
} else {
node = getDefaultNode(nodes: _nodeSource, type: _appStore.wallet!.type)!;
node = getDefaultNode(nodes: _nodeSource, type: walletType)!;
}
await setAsCurrent(node);
@@ -70,14 +109,54 @@ abstract class NodeListViewModelBase with Store {
Future<void> delete(Node node) async => node.delete();
@action
Future<void> setAsCurrent(Node node) async => settingsStore.nodes[_appStore.wallet!.type] = node;
Future<void> setAsCurrent(Node node) async {
final wallet = _appStore.wallet!;
final walletType = wallet.type;
if (isEVMCompatibleChain(walletType)) {
final chainId = evm!.getSelectedChainId(wallet);
if (chainId != null) {
final nodeWalletType = evm!.getWalletTypeByChainId(chainId);
if (nodeWalletType != null) {
settingsStore.nodes[nodeWalletType] = node;
return;
}
}
throw Exception('Cannot set node for EVM wallet: chainId or wallet type not found');
}
// For non-EVM wallets, use the wallet type directly
settingsStore.nodes[walletType] = node;
}
@action
void _bindNodes() {
nodes.clear();
final wallet = _appStore.wallet!;
final walletType = wallet.type;
// We filter nodes by the wallet type corresponding to current chainId for EVM wallets
if (isEVMCompatibleChain(walletType)) {
final chainId = evm!.getSelectedChainId(wallet);
if (chainId != null) {
final nodeWalletType = evm!.getWalletTypeByChainId(chainId);
if (nodeWalletType != null) {
_nodeSource.bindToList(
nodes,
filter: (val) => val.type == nodeWalletType,
initialFire: true,
);
return;
}
}
// If chainId is null or wallet type not found, show no nodes
return;
}
// For non-EVM wallets, use the wallet type directly
_nodeSource.bindToList(
nodes,
filter: (val) => val.type == _appStore.wallet!.type,
filter: (val) => val.type == walletType,
initialFire: true,
);
}
+189 -32
View File
@@ -1,6 +1,8 @@
import 'dart:async';
import 'package:cake_wallet/core/universal_address_detector.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_core/wallet_info.dart';
@@ -14,21 +16,47 @@ part 'payment_view_model.g.dart';
class PaymentViewModel = PaymentViewModelBase with _$PaymentViewModel;
abstract class PaymentViewModelBase with Store {
PaymentViewModelBase({
required this.appStore,
});
PaymentViewModelBase({required this.appStore});
final AppStore appStore;
@observable
WalletType? detectedWalletType;
@observable
AddressDetectionResult? _lastDetectionResult;
@computed
int? get detectedChainId {
if (detectedWalletType == null) return null;
if (!isEVMCompatibleChain(detectedWalletType!)) return null;
if (_lastDetectionResult?.chainId != null) {
return _lastDetectionResult!.chainId;
}
// If detected wallet type is EVM-compatible, get chainId from detected currency
if (_lastDetectionResult?.detectedCurrency != null) {
return getChainIdByCryptoCurrency(_lastDetectionResult!.detectedCurrency!);
}
return evm!.getChainIdByWalletType(detectedWalletType!);
}
@observable
bool isProcessing = false;
@computed
WalletType get currentWalletType => appStore.wallet!.type;
@computed
int? get currentChainId {
if (!isEVMCompatibleChain(currentWalletType)) return null;
return evm!.getSelectedChainId(appStore.wallet!);
}
/// Main entry point - detect address type and check compatibility
@action
Future<PaymentFlowResult> processAddress(String addressData) async {
@@ -39,18 +67,79 @@ abstract class PaymentViewModelBase with Store {
// Detect address type
final detectionResult = UniversalAddressDetector.detectAddress(addressData);
_lastDetectionResult = detectionResult;
detectedWalletType = detectionResult.detectedWalletType;
if (!detectionResult.isValid || detectedWalletType == null) {
return PaymentFlowResult.incompatible('Unable to detect address type');
}
if (!addressData.contains(':') && _isEVMAddress(detectionResult.address)) {
return PaymentFlowResult.evmNetworkSelection(detectionResult);
final currentWallet = appStore.wallet;
if (isEVMCompatibleChain(detectedWalletType!)) {
final isRawEvmInput = !addressData.contains(':') && _isEVMAddress(detectionResult.address);
// Check if the current wallet is also EVM
if (currentWallet != null && isEVMCompatibleChain(currentWallet.type)) {
final currentChainId = evm!.getSelectedChainId(currentWallet);
final detectedChainIdValue = this.detectedChainId;
if (detectedChainIdValue != null && currentChainId != null) {
// For raw EVM address input that only defaulted to chainId 1,
// always force the EVM ecosystem bottom sheet so the user
// can pick the actual network and token.
if (isRawEvmInput && detectedChainIdValue == 1) {
final allEVMWallets = await getEVMCompatibleWallets();
final currentWalletInfo = currentWallet.walletInfo;
final otherEVMWallets =
allEVMWallets.where((w) => w.name != currentWallet.name).toList();
return PaymentFlowResult.evmNetworkSelection(
detectionResult,
compatibleWallets: otherEVMWallets,
wallet: currentWalletInfo,
);
}
if (detectedChainIdValue == currentChainId) {
return PaymentFlowResult.currentWalletCompatible();
}
final allEVMWallets = await getEVMCompatibleWallets();
final currentWalletInfo = currentWallet.walletInfo;
final otherEVMWallets =
allEVMWallets.where((w) => w.name != currentWallet.name).toList();
return PaymentFlowResult.evmNetworkSelection(
detectionResult,
compatibleWallets: otherEVMWallets,
wallet: currentWalletInfo,
);
}
}
// If the current wallet is not EVM or the chainId comparison failed
// We proceed with other checks
if (!addressData.contains(':') && _isEVMAddress(detectionResult.address)) {
final allEVMWallets = await getEVMCompatibleWallets();
return PaymentFlowResult.evmNetworkSelection(
detectionResult,
compatibleWallets: allEVMWallets,
);
}
// For EVM URIs, show network selection
final allEVMWallets = await getEVMCompatibleWallets();
return PaymentFlowResult.evmNetworkSelection(
detectionResult,
compatibleWallets: allEVMWallets,
);
}
// Check if current wallet is compatible
final currentWallet = appStore.wallet;
if (currentWallet != null && currentWallet.type == detectedWalletType) {
return PaymentFlowResult.currentWalletCompatible();
}
@@ -73,6 +162,16 @@ abstract class PaymentViewModelBase with Store {
}
}
@action
Future<void> selectChain() async {
if (detectedWalletType == null) return;
final node =
appStore.settingsStore.getCurrentNode(detectedWalletType!, chainId: detectedChainId);
await evm!.selectChain(appStore.wallet!, detectedChainId!, node: node);
}
bool _isEVMAddress(String address) {
return RegExp(r'^0x[a-fA-F0-9]{40}$').hasMatch(address);
}
@@ -80,12 +179,18 @@ abstract class PaymentViewModelBase with Store {
Future<List<WalletInfo>> getWalletsByType(WalletType walletType) async {
return (await WalletInfo.getAll()).where((wallet) => wallet.type == walletType).toList();
}
Future<List<WalletInfo>> getEVMCompatibleWallets() async {
final allWallets = await WalletInfo.getAll();
return allWallets.where((wallet) => isEVMCompatibleChain(wallet.type)).toList();
}
}
class PaymentFlowResult {
final PaymentFlowType type;
final String? message;
final WalletInfo? wallet;
final int? chainId;
final List<WalletInfo> wallets;
final WalletType? walletType;
final AddressDetectionResult? addressDetectionResult;
@@ -94,6 +199,7 @@ class PaymentFlowResult {
required this.type,
this.message,
this.wallet,
this.chainId,
this.wallets = const [],
this.walletType,
this.addressDetectionResult,
@@ -105,14 +211,24 @@ class PaymentFlowResult {
AddressDetectionResult addressDetectionResult, {
List<WalletInfo>? compatibleWallets,
WalletInfo? wallet,
}) =>
PaymentFlowResult._(
type: PaymentFlowType.evmNetworkSelection,
addressDetectionResult: addressDetectionResult,
walletType: addressDetectionResult.detectedWalletType,
wallets: compatibleWallets ?? [],
wallet: wallet,
);
}) {
int? chainId = addressDetectionResult.chainId;
if (chainId == null && addressDetectionResult.detectedCurrency != null) {
chainId = getChainIdByCryptoCurrency(addressDetectionResult.detectedCurrency!);
}
if (chainId == null && addressDetectionResult.detectedWalletType != null) {
chainId = evm!.getChainIdByWalletType(addressDetectionResult.detectedWalletType!);
}
return PaymentFlowResult._(
type: PaymentFlowType.evmNetworkSelection,
addressDetectionResult: addressDetectionResult,
walletType: addressDetectionResult.detectedWalletType,
chainId: chainId,
wallets: compatibleWallets ?? [],
wallet: wallet,
);
}
/// Current wallet is compatible
factory PaymentFlowResult.currentWalletCompatible() =>
@@ -122,29 +238,66 @@ class PaymentFlowResult {
factory PaymentFlowResult.singleWallet(
WalletInfo wallet,
AddressDetectionResult addressDetectionResult,
) =>
PaymentFlowResult._(
type: PaymentFlowType.singleWallet,
wallet: wallet,
walletType: wallet.type,
addressDetectionResult: addressDetectionResult);
) {
int? chainId = addressDetectionResult.chainId;
if (chainId == null && addressDetectionResult.detectedCurrency != null) {
chainId = getChainIdByCryptoCurrency(addressDetectionResult.detectedCurrency!);
}
if (chainId == null) {
chainId = evm!.getChainIdByWalletType(wallet.type);
}
return PaymentFlowResult._(
type: PaymentFlowType.singleWallet,
wallet: wallet,
walletType: wallet.type,
chainId: chainId,
addressDetectionResult: addressDetectionResult,
);
}
/// Multiple compatible wallets available
factory PaymentFlowResult.multipleWallets(
List<WalletInfo> wallets, AddressDetectionResult addressDetectionResult) =>
PaymentFlowResult._(
type: PaymentFlowType.multipleWallets,
wallets: wallets,
walletType: wallets.first.type,
addressDetectionResult: addressDetectionResult);
List<WalletInfo> wallets,
AddressDetectionResult addressDetectionResult,
) {
int? chainId = addressDetectionResult.chainId;
if (chainId == null && addressDetectionResult.detectedCurrency != null) {
chainId = getChainIdByCryptoCurrency(addressDetectionResult.detectedCurrency!);
}
if (chainId == null) {
chainId = evm!.getChainIdByWalletType(wallets.first.type);
}
return PaymentFlowResult._(
type: PaymentFlowType.multipleWallets,
wallets: wallets,
walletType: wallets.first.type,
addressDetectionResult: addressDetectionResult,
chainId: chainId,
);
}
/// No compatible wallets available
factory PaymentFlowResult.noWallets(
WalletType walletType, AddressDetectionResult addressDetectionResult) =>
PaymentFlowResult._(
type: PaymentFlowType.noWallets,
walletType: walletType,
addressDetectionResult: addressDetectionResult);
WalletType walletType,
AddressDetectionResult addressDetectionResult,
) {
int? chainId = addressDetectionResult.chainId;
if (chainId == null && addressDetectionResult.detectedCurrency != null) {
chainId = getChainIdByCryptoCurrency(addressDetectionResult.detectedCurrency!);
}
if (chainId == null) {
chainId = evm!.getChainIdByWalletType(walletType);
}
return PaymentFlowResult._(
type: PaymentFlowType.noWallets,
walletType: walletType,
addressDetectionResult: addressDetectionResult,
chainId: chainId,
);
}
/// Error occurred
factory PaymentFlowResult.error(String message) =>
@@ -159,6 +312,10 @@ class PaymentFlowResult {
return addressDetectionResult?.detectedCurrency;
}
if (walletType != null) {
if (isEVMCompatibleChain(walletType!)) {
return walletTypeToCryptoCurrency(walletType!, chainId: chainId);
}
return walletTypeToCryptoCurrency(walletType!);
}
return null;
@@ -85,7 +85,9 @@ class WalletRestoreFromQRCode {
try {
return AddressResolver.extractAddressByType(
raw: rawString,
type: walletTypeToCryptoCurrency(type),
type: walletTypeToCryptoCurrency(
type,
),
requireSurroundingWhitespaces: false,
);
} catch (_) {
@@ -118,7 +120,11 @@ class WalletRestoreFromQRCode {
String formattedUri = '';
WalletType? walletType = _extractWalletType(code);
final prefix = code.startsWith('xpub') ? 'xpub' : code.startsWith('zpub') ? 'zpub' : '????';
final prefix = code.startsWith('xpub')
? 'xpub'
: code.startsWith('zpub')
? 'zpub'
: '????';
if (walletType == null) {
await _specifyWalletAssets(context, "Can't determine wallet type, please pick it manually");
walletType =
@@ -129,15 +135,13 @@ class WalletRestoreFromQRCode {
formattedUri = seedPhrase != null
? '$walletType:?seed=$seedPhrase'
: code.startsWith(prefix)
? '$walletType:?$prefix=$code'
: throw Exception('Failed to determine valid seed phrase');
: code.startsWith(prefix)
? '$walletType:?$prefix=$code'
: throw Exception('Failed to determine valid seed phrase');
} else {
final index = code.indexOf(':');
final query = code.substring(index + 1).replaceAll('?', '&');
formattedUri = code.startsWith(prefix)
? '$walletType:?$prefix=$code'
:'$walletType:?$query';
formattedUri = code.startsWith(prefix) ? '$walletType:?$prefix=$code' : '$walletType:?$query';
}
final uri = Uri.parse(formattedUri);
@@ -174,8 +178,7 @@ class WalletRestoreFromQRCode {
throw Exception('Unexpected restore mode: tx_payment_id is invalid');
}
if (credentials.containsKey("xpub") ||
credentials.containsKey("zpub")) {
if (credentials.containsKey("xpub") || credentials.containsKey("zpub")) {
return WalletRestoreMode.keys;
}
+30 -31
View File
@@ -1,13 +1,10 @@
import 'package:cake_wallet/arbitrum/arbitrum.dart';
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/decred/decred.dart';
import 'package:cake_wallet/dogecoin/dogecoin.dart';
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
import 'package:cw_core/crypto_currency.dart';
@@ -29,14 +26,15 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor
this.balanceViewModel,
) : _settingsStore = appStore.settingsStore,
super(appStore: appStore) {
if (wallet.type == WalletType.bitcoin &&
_settingsStore.priority[wallet.type] == bitcoinTransactionPriorityCustom) {
final priority = _settingsStore.getPriority(wallet.type, chainId: wallet.chainId);
if (wallet.type == WalletType.bitcoin && priority == bitcoinTransactionPriorityCustom) {
setTransactionPriority(bitcoinTransactionPriorityMedium);
}
final priority = _settingsStore.priority[wallet.type];
final priorities = priorityForWalletType(wallet.type);
if (!priorityForWalletType(wallet.type).contains(priority) && priorities.isNotEmpty) {
_settingsStore.priority[wallet.type] = priorities.first;
if (!priorities.contains(priority) && priorities.isNotEmpty) {
_settingsStore.setPriority(wallet.type, priorities.first, chainId: wallet.chainId);
}
}
@@ -49,13 +47,13 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor
final BalanceViewModel balanceViewModel;
TransactionPriority get transactionPriority {
final priority = _settingsStore.priority[wallet.type];
final priority = _settingsStore.getPriority(wallet.type, chainId: wallet.chainId);
if (priority == null) {
if (priority == null && hasFeesPriority) {
throw Exception('Unexpected type ${wallet.type}');
}
return priority;
return priority!;
}
int? getCustomPriorityIndex(List<TransactionPriority> priorities) {
@@ -76,6 +74,8 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor
}
bool get isLowFee {
if (wallet.chainId == 42161) return false;
switch (wallet.type) {
case WalletType.monero:
case WalletType.wownero:
@@ -87,13 +87,11 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor
case WalletType.litecoin:
return transactionPriority == bitcoin!.getLitecoinTransactionPrioritySlow();
case WalletType.ethereum:
return transactionPriority == ethereum!.getEthereumTransactionPrioritySlow();
case WalletType.polygon:
case WalletType.base:
return transactionPriority == evm!.getEVMTransactionPrioritySlow();
case WalletType.bitcoinCash:
return transactionPriority == bitcoinCash!.getBitcoinCashTransactionPrioritySlow();
case WalletType.polygon:
return transactionPriority == polygon!.getPolygonTransactionPrioritySlow();
case WalletType.base:
return transactionPriority == base!.getBaseTransactionPrioritySlow();
case WalletType.decred:
return transactionPriority == decred!.getDecredTransactionPrioritySlow();
case WalletType.dogecoin:
@@ -122,7 +120,8 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor
wallet.type != WalletType.banano &&
wallet.type != WalletType.solana &&
wallet.type != WalletType.tron &&
wallet.type != WalletType.arbitrum;
wallet.chainId !=
42161; // Wallet type is generic for all EVM chains, so we need to check the chainId
@computed
bool get isElectrumWallet =>
@@ -140,7 +139,7 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor
@action
void setTransactionPriority(TransactionPriority priority) =>
_settingsStore.priority[wallet.type] = priority;
_settingsStore.setPriority(wallet.type, priority, chainId: wallet.chainId);
bool showAlertForCustomFeeRate() {
if (wallet.type != WalletType.bitcoin || isLowFee) {
@@ -184,28 +183,28 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor
case WalletType.haven:
case WalletType.wownero:
case WalletType.zano:
_settingsStore.priority[wallet.type] = monero!.getMoneroTransactionPriorityAutomatic();
_settingsStore.setPriority(wallet.type, monero!.getMoneroTransactionPriorityAutomatic());
break;
case WalletType.bitcoin:
_settingsStore.priority[wallet.type] = bitcoin!.getBitcoinTransactionPriorityMedium();
_settingsStore.setPriority(wallet.type, bitcoin!.getBitcoinTransactionPriorityMedium());
break;
case WalletType.litecoin:
_settingsStore.priority[wallet.type] = bitcoin!.getLitecoinTransactionPriorityMedium();
_settingsStore.setPriority(wallet.type, bitcoin!.getLitecoinTransactionPriorityMedium());
break;
case WalletType.ethereum:
_settingsStore.priority[wallet.type] = ethereum!.getDefaultTransactionPriority();
case WalletType.polygon:
case WalletType.base:
_settingsStore.setPriority(
wallet.type,
evm!.getDefaultTransactionPriority(),
chainId: wallet.chainId,
);
break;
case WalletType.bitcoinCash:
_settingsStore.priority[wallet.type] = bitcoinCash!.getDefaultTransactionPriority();
_settingsStore.setPriority(wallet.type, bitcoinCash!.getDefaultTransactionPriority());
break;
case WalletType.dogecoin:
_settingsStore.priority[wallet.type] = dogecoin!.getDefaultTransactionPriority();
break;
case WalletType.polygon:
_settingsStore.priority[wallet.type] = polygon!.getDefaultTransactionPriority();
break;
case WalletType.base:
_settingsStore.priority[wallet.type] = base!.getDefaultTransactionPriority();
_settingsStore.setPriority(wallet.type, dogecoin!.getDefaultTransactionPriority());
break;
default:
break;
+14 -40
View File
@@ -1,7 +1,4 @@
import 'dart:math';
import 'package:cake_wallet/arbitrum/arbitrum.dart';
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/decred/decred.dart';
import 'package:cake_wallet/di.dart';
@@ -10,10 +7,9 @@ import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart';
import 'package:cake_wallet/entities/contact_base.dart';
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
import 'package:cake_wallet/entities/parsed_address.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/src/screens/send/widgets/extract_address_from_parsed.dart';
@@ -117,16 +113,10 @@ abstract class OutputBase with Store {
_amount = decred!.formatterStringDoubleToDecredAmount(_cryptoAmount);
break;
case WalletType.ethereum:
_amount = ethereum!.formatterEthereumParseAmount(_cryptoAmount);
break;
case WalletType.polygon:
_amount = polygon!.formatterPolygonParseAmount(_cryptoAmount);
break;
case WalletType.base:
_amount = base!.formatterBaseParseAmount(_cryptoAmount);
break;
case WalletType.arbitrum:
_amount = arbitrum!.formatterArbitrumParseAmount(_cryptoAmount);
_amount = evm!.formatterEVMParseAmount(_cryptoAmount);
break;
case WalletType.wownero:
_amount = wownero!.formatterWowneroParseAmount(amount: _cryptoAmount);
@@ -161,14 +151,15 @@ abstract class OutputBase with Store {
@action
Future<void> calculateEstimatedFee() async {
try {
final priority = _settingsStore.getPriority(_wallet.type, chainId: _wallet.chainId);
if (isEVMCompatibleChain(_wallet.type)) {
await _wallet.updateEstimatedFeesParams(_settingsStore.priority[_wallet.type]!);
await _wallet.updateEstimatedFeesParams(priority);
}
int fee = 0;
if (_settingsStore.priority[_wallet.type] != null) {
if (_settingsStore.getPriority(_wallet.type, chainId: _wallet.chainId) != null) {
fee = _wallet.calculateEstimatedFee(
_settingsStore.priority[_wallet.type]!,
_settingsStore.getPriority(_wallet.type, chainId: _wallet.chainId)!,
formattedCryptoAmount,
);
}
@@ -178,7 +169,7 @@ abstract class OutputBase with Store {
estimatedFee = monero!.formatterMoneroAmountToDouble(amount: fee).toString();
break;
case WalletType.bitcoin:
if (_settingsStore.priority[_wallet.type] ==
if (_settingsStore.getPriority(_wallet.type) ==
bitcoin!.getBitcoinTransactionPriorityCustom()) {
fee = bitcoin!.getEstimatedFeeWithFeeRate(
_wallet, _settingsStore.customBitcoinFeeRate, formattedCryptoAmount);
@@ -216,33 +207,16 @@ abstract class OutputBase with Store {
/// EVMs
case WalletType.ethereum:
String? fee = cryptoCurrencyHandler() == CryptoCurrency.eth
? ethereum!.getEthereumNativeEstimatedFee(_wallet)
: ethereum!.getEthereumERC20EstimatedFee(_wallet);
estimatedFee = formatFixed(BigInt.parse(fee ?? '0.0'), 18, fractionalDigits: 12);
break;
case WalletType.polygon:
String? fee = cryptoCurrencyHandler() == CryptoCurrency.maticpoly
? polygon!.getPolygonNativeEstimatedFee(_wallet)
: polygon!.getPolygonERC20EstimatedFee(_wallet);
estimatedFee = formatFixed(BigInt.parse(fee ?? '0.0'), 18, fractionalDigits: 12);
break;
case WalletType.base:
String? fee = cryptoCurrencyHandler() == CryptoCurrency.baseEth
? base!.getBaseNativeEstimatedFee(_wallet)
: base!.getBaseERC20EstimatedFee(_wallet);
estimatedFee = formatFixed(BigInt.parse(fee ?? '0.0'), 18, fractionalDigits: 12);
break;
case WalletType.arbitrum:
String? fee = cryptoCurrencyHandler() == CryptoCurrency.arbEth
? arbitrum!.getArbitrumNativeEstimatedFee(_wallet)
: arbitrum!.getArbitrumERC20EstimatedFee(_wallet);
final isNative = cryptoCurrencyHandler() == CryptoCurrency.eth ||
cryptoCurrencyHandler() == CryptoCurrency.maticpoly ||
cryptoCurrencyHandler() == CryptoCurrency.baseEth ||
cryptoCurrencyHandler() == CryptoCurrency.arbEth;
String? fee = isNative
? evm!.getEVMNativeEstimatedFee(_wallet)
: evm!.getEVMERC20EstimatedFee(_wallet);
estimatedFee = formatFixed(BigInt.parse(fee ?? '0.0'), 18, fractionalDigits: 12);
break;
@@ -1,3 +1,4 @@
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/view_model/send/template_view_model.dart';
import 'package:cw_core/crypto_currency.dart';
@@ -44,8 +45,12 @@ abstract class SendTemplateViewModelBase with Store {
recipients.remove(recipient);
}
AmountValidator get amountValidator =>
AmountValidator(currency: walletTypeToCryptoCurrency(_wallet.type));
AmountValidator get amountValidator => AmountValidator(
currency: walletTypeToCryptoCurrency(
_wallet.type,
chainId: _wallet.chainId,
),
);
AddressValidator get addressValidator =>
AddressValidator(type: _wallet.currency, isTestnet: _wallet.isTestnet);
@@ -54,12 +59,12 @@ abstract class SendTemplateViewModelBase with Store {
bool get hasMultiRecipient =>
_wallet.type != WalletType.haven &&
_wallet.type != WalletType.ethereum &&
_wallet.type != WalletType.polygon &&
_wallet.type != WalletType.base &&
_wallet.type != WalletType.arbitrum &&
_wallet.type != WalletType.solana &&
_wallet.type != WalletType.tron;
_wallet.type != WalletType.tron &&
_wallet.chainId != 1 &&
_wallet.chainId != 137 &&
_wallet.chainId != 8453 &&
_wallet.chainId != 42161;
@computed
CryptoCurrency get cryptoCurrency => _wallet.currency;
+66 -184
View File
@@ -1,6 +1,5 @@
import 'dart:async';
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/address_validator.dart';
import 'package:cake_wallet/core/amount_validator.dart';
@@ -20,7 +19,7 @@ import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/entities/template.dart';
import 'package:cake_wallet/entities/transaction_description.dart';
import 'package:cake_wallet/entities/wallet_contact.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/swapsxyz_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart';
@@ -28,8 +27,6 @@ import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/arbitrum/arbitrum.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/solana/solana.dart';
@@ -50,7 +47,6 @@ import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cake_wallet/zano/zano.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/erc20_token.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/exceptions.dart';
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_core/sync_status.dart';
@@ -73,7 +69,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
void onWalletChange(wallet) {
currencies = wallet.balance.keys.toList();
selectedCryptoCurrency = wallet.currency;
hasMultipleTokens = isEVMCompatibleChain(wallet.type) ||
hasMultipleTokens = isEVMWallet ||
wallet.type == WalletType.solana ||
wallet.type == WalletType.tron ||
wallet.type == WalletType.zano;
@@ -109,6 +105,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
appStore.wallet!.type == WalletType.solana ||
appStore.wallet!.type == WalletType.tron ||
appStore.wallet!.type == WalletType.zano,
selectedChainId = appStore.wallet!.chainId,
outputs = ObservableList<Output>(),
_settingsStore = appStore.settingsStore,
fiatFromSettings = appStore.settingsStore.fiatCurrency,
@@ -119,6 +116,20 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
unspentCoinsListViewModel.initialSetup().then((_) {
unspentCoinsListViewModel.resetUnspentCoinsInfoSelections();
});
reaction((_) {
if (isEVMCompatibleChain(wallet.type)) {
// Access currency which depends on selectedChainId, so MobX tracks the change
return wallet.currency;
}
return null;
}, (_) async {
// When chain changes, update currencies and selected currency
await Future.delayed(const Duration(milliseconds: 100));
currencies = wallet.balance.keys.toList();
selectedCryptoCurrency = wallet.currency;
updateSendingBalance();
});
}
PendingTransaction? _pendingApprovalTx;
@@ -137,6 +148,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
bool get isMwebEnabled => balanceViewModel.mwebEnabled;
bool get isEVMWallet => isEVMCompatibleChain(walletType);
@action
void setShowAddressBookPopup(bool value) {
_settingsStore.showAddressBookPopupEnabled = value;
@@ -237,7 +249,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
CryptoCurrency get currency => wallet.currency;
Validator<String> amountValidator(Output output) => AmountValidator(
currency: walletTypeToCryptoCurrency(wallet.type),
currency: wallet.currency,
minValue: isSendToSilentPayments(output)
?
// TODO: get from server
@@ -266,7 +278,12 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
coinTypeToSpendFrom == UnspentCoinType.nonMweb) {
return balanceViewModel.balances.values.first.availableBalance;
}
return wallet.balance[selectedCryptoCurrency]!.formattedFullAvailableBalance;
// Handle case where balance might not be available yet (e.g., during chain switch)
final balanceForCurrency = wallet.balance[selectedCryptoCurrency];
if (balanceForCurrency == null) {
return wallet.formatCryptoAmount('0');
}
return balanceForCurrency.formattedFullAvailableBalance;
}
@action
@@ -383,6 +400,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
@observable
bool hasMultipleTokens;
@observable
int? selectedChainId;
@computed
List<ContactRecord> get contactsToShow => contactListViewModel.contacts
.where((element) => element.type == selectedCryptoCurrency)
@@ -540,30 +560,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
(trade.requiresTokenApproval ?? false) && !_isPreparedTransfer;
if (requiresTokenApproval && tokenContract.isNotEmpty && requiredAmount > BigInt.zero) {
if (walletType == WalletType.ethereum) {
final priority = _settingsStore.priority[WalletType.ethereum]!;
_pendingApprovalTx = await buildApprovalIfNeeded(
spender: routerTo!, // if API provides a specific spender, use that instead
tokenContract: tokenContract,
requiredAmount: requiredAmount,
sourceTokenDecimals: trade.sourceTokenDecimals,
);
// Build the callData tx
pendingTransaction = await ethereum!.createRawCallDataTransaction(
wallet,
routerTo,
routerData,
routerValueWei,
priority,
);
_isSwapsXYZCallDataTx = true;
state = ExecutedSuccessfullyState();
return pendingTransaction; // do NOT fall back to regular flow
}
if (walletType == WalletType.polygon) {
final priority = _settingsStore.priority[WalletType.polygon]!;
if (isEVMWallet) {
final priority = _settingsStore.getPriority(walletType, chainId: selectedChainId);
_pendingApprovalTx = await buildApprovalIfNeeded(
spender: routerTo!,
tokenContract: tokenContract,
@@ -572,54 +570,15 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
);
// Build the callData tx
pendingTransaction = await polygon!.createRawCallDataTransaction(
pendingTransaction = await evm!.createRawCallDataTransaction(
wallet,
routerTo,
routerData,
routerValueWei,
priority,
);
_isSwapsXYZCallDataTx = true;
state = ExecutedSuccessfullyState();
return pendingTransaction; // do NOT fall back to regular flow
}
if (walletType == WalletType.base) {
final priority = _settingsStore.priority[WalletType.base]!;
_pendingApprovalTx = await buildApprovalIfNeeded(
spender: routerTo!,
tokenContract: tokenContract,
requiredAmount: requiredAmount,
sourceTokenDecimals: trade.sourceTokenDecimals,
);
// Build the callData tx
pendingTransaction = await base!.createRawCallDataTransaction(
wallet,
routerTo,
routerData,
routerValueWei,
priority,
);
_isSwapsXYZCallDataTx = true;
state = ExecutedSuccessfullyState();
return pendingTransaction; // do NOT fall back to regular flow
}
if (walletType == WalletType.arbitrum) {
_pendingApprovalTx = await buildApprovalIfNeeded(
spender: routerTo!,
tokenContract: tokenContract,
requiredAmount: requiredAmount,
sourceTokenDecimals: trade.sourceTokenDecimals,
);
// Build the callData tx
pendingTransaction = await arbitrum!.createRawCallDataTransaction(
wallet,
routerTo,
routerData,
routerValueWei,
useBlinkProtection: canSupportBlinkProtection(selectedChainId)
? _settingsStore.useBlinkProtection
: false,
);
_isSwapsXYZCallDataTx = true;
@@ -629,51 +588,17 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
}
// No approval needed (or prepared transfer): send exactly what backend prepared
if (walletType == WalletType.ethereum) {
final priority = _settingsStore.priority[WalletType.ethereum]!;
pendingTransaction = await ethereum!.createRawCallDataTransaction(
if (isEVMWallet) {
final priority = _settingsStore.getPriority(walletType, chainId: selectedChainId);
pendingTransaction = await evm!.createRawCallDataTransaction(
wallet,
routerTo!,
routerData,
routerValueWei,
priority,
);
_isSwapsXYZCallDataTx = true;
state = ExecutedSuccessfullyState();
return pendingTransaction;
}
if (walletType == WalletType.polygon) {
final priority = _settingsStore.priority[WalletType.polygon]!;
pendingTransaction = await polygon!.createRawCallDataTransaction(
wallet,
routerTo!,
routerData,
routerValueWei,
priority,
);
_isSwapsXYZCallDataTx = true;
state = ExecutedSuccessfullyState();
return pendingTransaction;
}
if (walletType == WalletType.base) {
final priority = _settingsStore.priority[WalletType.base]!;
pendingTransaction = await base!.createRawCallDataTransaction(
wallet,
routerTo!,
routerData,
routerValueWei,
priority,
);
_isSwapsXYZCallDataTx = true;
state = ExecutedSuccessfullyState();
return pendingTransaction;
}
if (walletType == WalletType.arbitrum) {
pendingTransaction = await arbitrum!.createRawCallDataTransaction(
wallet,
routerTo!,
routerData,
routerValueWei,
useBlinkProtection: canSupportBlinkProtection(selectedChainId)
? _settingsStore.useBlinkProtection
: false,
);
_isSwapsXYZCallDataTx = true;
state = ExecutedSuccessfullyState();
@@ -871,7 +796,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
}
Object _credentials([ExchangeProvider? provider]) {
final priority = _settingsStore.priority[wallet.type];
final priority = _settingsStore.getPriority(wallet.type, chainId: wallet.chainId);
if (priority == null &&
wallet.type != WalletType.nano &&
@@ -879,6 +804,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
wallet.type != WalletType.solana &&
wallet.type != WalletType.tron &&
wallet.type != WalletType.arbitrum) {
// Wallet type is generic for all EVM chains, so we need to check the chainId
throw Exception('Priority is null for wallet type: ${wallet.type}');
}
@@ -911,19 +837,19 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
.createWowneroTransactionCreationCredentials(outputs: outputs, priority: priority!);
case WalletType.ethereum:
return ethereum!.createEthereumTransactionCredentials(outputs,
priority: priority!, currency: selectedCryptoCurrency);
case WalletType.polygon:
case WalletType.base:
case WalletType.arbitrum:
return evm!.createEVMTransactionCredentials(
outputs,
priority: priority,
currency: selectedCryptoCurrency,
useBlinkProtection: canSupportBlinkProtection(selectedChainId)
? _settingsStore.useBlinkProtection
: false,
);
case WalletType.nano:
return nano!.createNanoTransactionCredentials(outputs);
case WalletType.polygon:
return polygon!.createPolygonTransactionCredentials(outputs,
priority: priority!, currency: selectedCryptoCurrency);
case WalletType.base:
return base!.createBaseTransactionCredentials(outputs,
priority: priority!, currency: selectedCryptoCurrency);
case WalletType.arbitrum:
return arbitrum!
.createArbitrumTransactionCredentials(outputs, currency: selectedCryptoCurrency);
case WalletType.solana:
return solana!
.createSolanaTransactionCredentials(outputs, currency: selectedCryptoCurrency);
@@ -1063,11 +989,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
return errorMessage;
}
if (walletType == WalletType.ethereum ||
walletType == WalletType.polygon ||
walletType == WalletType.base ||
walletType == WalletType.arbitrum ||
walletType == WalletType.haven) {
if (isEVMWallet || walletType == WalletType.haven) {
if (errorMessage.contains('gas required exceeds allowance')) {
return S.current.gas_exceeds_allowance;
}
@@ -1096,6 +1018,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
'''${S.current.overshot}: ${parsedErrorMessageResult.overshotEth} ${walletType == WalletType.polygon ? "POL" : "ETH"} (${parsedErrorMessageResult.overshotUsd} ${fiatFromSettings.name})''';
}
if (errorMessage.contains('max fee per gas less than block base fee')) {
return S.current.tx_retry_message;
}
return errorMessage;
}
@@ -1105,7 +1031,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
}
if (errorMessage.contains('Transaction expired')) {
return 'An error occurred while processing the transaction. Please retry the transaction';
return S.current.tx_retry_message;
}
}
@@ -1184,29 +1110,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
if (requiredAmount <= BigInt.zero) return null;
bool needsApproval = false;
if (walletType == WalletType.ethereum) {
needsApproval = await ethereum!.isApprovalRequired(
wallet,
tokenContract,
spender,
requiredAmount,
);
} else if (walletType == WalletType.polygon) {
needsApproval = await polygon!.isApprovalRequired(
wallet,
tokenContract,
spender,
requiredAmount,
);
} else if (walletType == WalletType.base) {
needsApproval = await base!.isApprovalRequired(
wallet,
tokenContract,
spender,
requiredAmount,
);
} else if (walletType == WalletType.arbitrum) {
needsApproval = await arbitrum!.isApprovalRequired(
if (isEVMWallet) {
needsApproval = await evm!.isApprovalRequired(
wallet,
tokenContract,
spender,
@@ -1227,39 +1132,16 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
),
);
if (walletType == WalletType.ethereum) {
final priority = _settingsStore.priority[WalletType.ethereum]!;
return await ethereum!.createTokenApproval(
if (isEVMWallet) {
final priority = _settingsStore.getPriority(walletType, chainId: selectedChainId);
return await evm!.createTokenApproval(
wallet,
requiredAmount,
spender,
erc20Token,
priority,
);
} else if (walletType == WalletType.polygon) {
final priority = _settingsStore.priority[WalletType.polygon]!;
return await polygon!.createTokenApproval(
wallet,
requiredAmount,
spender,
erc20Token,
priority,
);
} else if (walletType == WalletType.base) {
final priority = _settingsStore.priority[WalletType.base]!;
return await base!.createTokenApproval(
wallet,
requiredAmount,
spender,
erc20Token,
priority,
);
} else if (walletType == WalletType.arbitrum) {
return await arbitrum!.createTokenApproval(
wallet,
requiredAmount,
spender,
erc20Token,
useBlinkProtection:
canSupportBlinkProtection(selectedChainId) ? _settingsStore.useBlinkProtection : false,
);
}
@@ -15,27 +15,27 @@ import 'package:mobx/mobx.dart';
part 'other_settings_view_model.g.dart';
class OtherSettingsViewModel = OtherSettingsViewModelBase
with _$OtherSettingsViewModel;
class OtherSettingsViewModel = OtherSettingsViewModelBase with _$OtherSettingsViewModel;
abstract class OtherSettingsViewModelBase with Store {
OtherSettingsViewModelBase(this._settingsStore, this._wallet, this.sendViewModel)
: walletType = _wallet.type,
chainId = _wallet.chainId,
currentVersion = '' {
PackageInfo.fromPlatform().then(
(PackageInfo packageInfo) => currentVersion = packageInfo.version);
PackageInfo.fromPlatform()
.then((PackageInfo packageInfo) => currentVersion = packageInfo.version);
final priority = _settingsStore.priority[_wallet.type];
final priority = _settingsStore.getPriority(_wallet.type, chainId: _wallet.chainId);
final priorities = priorityForWalletType(_wallet.type);
if (!priorities.contains(priority) && priorities.isNotEmpty) {
_settingsStore.priority[_wallet.type] = priorities.first;
_settingsStore.setPriority(_wallet.type, priorities.first, chainId: _wallet.chainId);
}
}
final WalletType walletType;
final WalletBase<Balance, TransactionHistoryBase<TransactionInfo>,
TransactionInfo> _wallet;
final int? chainId;
final WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> _wallet;
@observable
String currentVersion;
@@ -45,7 +45,7 @@ abstract class OtherSettingsViewModelBase with Store {
@computed
TransactionPriority get transactionPriority {
final priority = _settingsStore.priority[walletType];
final priority = _settingsStore.getPriority(walletType, chainId: chainId);
if (priority == null) {
throw Exception('Unexpected type ${walletType.toString()}');
@@ -66,8 +66,12 @@ abstract class OtherSettingsViewModelBase with Store {
].contains(_wallet.hardwareWalletType);
@computed
bool get displayTransactionPriority =>
!(changeRepresentativeEnabled || [WalletType.solana, WalletType.tron].contains(_wallet.type));
bool get displayTransactionPriority => !(changeRepresentativeEnabled ||
[
WalletType.solana,
WalletType.tron,
WalletType.arbitrum,
].contains(_wallet.type));
String getDisplayPriority(dynamic priority) {
final _priority = priority as TransactionPriority;
@@ -95,22 +99,20 @@ abstract class OtherSettingsViewModelBase with Store {
WalletType.dogecoin,
].contains(_wallet.type)) {
final rate = bitcoin!.getFeeRate(_wallet, _priority);
return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate,
customRate: customValue);
return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate, customRate: customValue);
}
return priority.toString();
}
void onDisplayPrioritySelected(TransactionPriority priority) =>
_settingsStore.priority[walletType] = priority;
_settingsStore.setPriority(walletType, priority, chainId: chainId);
void onDisplayBitcoinPrioritySelected(
TransactionPriority priority, double customValue) {
void onDisplayBitcoinPrioritySelected(TransactionPriority priority, double customValue) {
if (_wallet.type == WalletType.bitcoin) {
_settingsStore.customBitcoinFeeRate = customValue.round();
}
_settingsStore.priority[_wallet.type] = priority;
_settingsStore.setPriority(_wallet.type, priority, chainId: _wallet.chainId);
}
@action
@@ -120,13 +122,12 @@ abstract class OtherSettingsViewModelBase with Store {
}
@computed
double get customBitcoinFeeRate =>
_settingsStore.customBitcoinFeeRate.toDouble();
double get customBitcoinFeeRate => _settingsStore.customBitcoinFeeRate.toDouble();
int? get customPriorityItemIndex {
final priorities = priorityForWalletType(walletType);
final customItem = priorities.firstWhereOrNull(
(element) => element == bitcoin!.getBitcoinTransactionPriorityCustom());
final customItem = priorities
.firstWhereOrNull((element) => element == bitcoin!.getBitcoinTransactionPriorityCustom());
return customItem != null ? priorities.indexOf(customItem) : null;
}
@@ -1,12 +1,10 @@
import 'package:cake_wallet/arbitrum/arbitrum.dart';
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cw_core/balance.dart';
import 'package:cw_core/transaction_history.dart';
@@ -89,6 +87,11 @@ abstract class PrivacySettingsViewModelBase with Store {
@computed
bool get useMempoolFeeAPI => _settingsStore.useMempoolFeeAPI;
@computed
bool get useBlinkProtection => _settingsStore.useBlinkProtection;
bool get canUseBlinkProtection => canSupportBlinkProtection(_wallet.chainId);
@computed
bool get lookupTwitter => _settingsStore.lookupsTwitter;
@@ -116,13 +119,13 @@ abstract class PrivacySettingsViewModelBase with Store {
@computed
bool get usePayjoin => _settingsStore.usePayjoin;
bool get canUseEtherscan => _wallet.type == WalletType.ethereum;
bool get canUseEtherscan => _wallet.chainId == 1;
bool get canUsePolygonScan => _wallet.type == WalletType.polygon;
bool get canUsePolygonScan => _wallet.chainId == 137;
bool get canUseBaseScan => _wallet.type == WalletType.base;
bool get canUseBaseScan => _wallet.chainId == 8453;
bool get canUseArbiScan => _wallet.type == WalletType.arbitrum;
bool get canUseArbiScan => _wallet.chainId == 42161;
bool get canUseTronGrid => _wallet.type == WalletType.tron;
@@ -180,19 +183,19 @@ abstract class PrivacySettingsViewModelBase with Store {
@action
void setUseEtherscan(bool value) {
_settingsStore.useEtherscan = value;
ethereum!.updateEtherscanUsageState(_wallet, value);
evm!.updateScanProviderUsageState(_wallet, value);
}
@action
void setUsePolygonScan(bool value) {
_settingsStore.usePolygonScan = value;
polygon!.updatePolygonScanUsageState(_wallet, value);
evm!.updateScanProviderUsageState(_wallet, value);
}
@action
void setUseBaseScan(bool value) {
_settingsStore.useBaseScan = value;
base!.updateBaseScanUsageState(_wallet, value);
evm!.updateScanProviderUsageState(_wallet, value);
}
@action
@@ -204,12 +207,15 @@ abstract class PrivacySettingsViewModelBase with Store {
@action
void setUseArbiScan(bool value) {
_settingsStore.useArbiScan = value;
arbitrum!.updateArbitrumScanUsageState(_wallet, value);
evm!.updateScanProviderUsageState(_wallet, value);
}
@action
void setUseMempoolFeeAPI(bool value) => _settingsStore.useMempoolFeeAPI = value;
@action
void setUseBlinkProtection(bool value) => _settingsStore.useBlinkProtection = value;
@action
void setUsePayjoin(bool value) {
_settingsStore.usePayjoin = value;
@@ -1,11 +1,12 @@
import 'package:cake_wallet/core/address_validator.dart';
import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
import 'package:cake_wallet/entities/transaction_description.dart';
@@ -68,20 +69,14 @@ abstract class TransactionDetailsViewModelBase with Store {
_addHavenListItems(tx, dateFormat);
break;
case WalletType.ethereum:
_addEthereumListItems(tx, dateFormat);
case WalletType.polygon:
case WalletType.base:
case WalletType.arbitrum:
_addEVMListItems(tx, dateFormat);
break;
case WalletType.nano:
_addNanoListItems(tx, dateFormat);
break;
case WalletType.polygon:
_addPolygonListItems(tx, dateFormat);
break;
case WalletType.base:
_addBaseListItems(tx, dateFormat);
break;
case WalletType.arbitrum:
_addArbitrumListItems(tx, dateFormat);
break;
case WalletType.solana:
_addSolanaListItems(tx, dateFormat);
break;
@@ -129,10 +124,10 @@ abstract class TransactionDetailsViewModelBase with Store {
items.add(
BlockExplorerListItem(
title: S.current.view_in_block_explorer,
value: _explorerDescription(type),
value: _explorerDescription(type, wallet.chainId),
onTap: () async {
try {
final uri = Uri.parse(_explorerUrl(type, tx.txHash));
final uri = Uri.parse(_explorerUrl(type, tx.txHash, wallet.chainId));
if (await canLaunchUrl(uri)) await launchUrl(uri, mode: LaunchMode.externalApplication);
} catch (e) {}
},
@@ -175,7 +170,12 @@ abstract class TransactionDetailsViewModelBase with Store {
@observable
bool canReplaceByFee;
String _explorerUrl(WalletType type, String txId) {
String _explorerUrl(WalletType type, String txId, int? chainId) {
if (chainId != null) {
final explorerUrl = evm!.getExplorerUrlForChainId(chainId);
if (explorerUrl != null) return '$explorerUrl/tx/${txId}';
}
switch (type) {
case WalletType.monero:
return 'https://monero.com/tx/${txId}';
@@ -189,12 +189,16 @@ abstract class TransactionDetailsViewModelBase with Store {
return 'https://explorer.havenprotocol.org/search?value=${txId}';
case WalletType.ethereum:
return 'https://etherscan.io/tx/${txId}';
case WalletType.base:
return 'https://basescan.org/tx/${txId}';
case WalletType.arbitrum:
return 'https://arbiscan.io/tx/${txId}';
case WalletType.polygon:
return 'https://polygonscan.com/tx/${txId}';
case WalletType.nano:
return 'https://nanexplorer.com/nano/block/${txId}';
case WalletType.banano:
return 'https://nanexplorer.com/banano/block/${txId}';
case WalletType.polygon:
return 'https://polygonscan.com/tx/${txId}';
case WalletType.solana:
return 'https://solscan.io/tx/${txId}';
case WalletType.tron:
@@ -207,16 +211,19 @@ abstract class TransactionDetailsViewModelBase with Store {
return 'https://${wallet.isTestnet ? "testnet" : "dcrdata"}.decred.org/tx/${txId.split(':')[0]}';
case WalletType.dogecoin:
return 'https://blockchair.com/dogecoin/transaction/${txId}';
case WalletType.base:
return 'https://basescan.org/tx/${txId}';
case WalletType.arbitrum:
return 'https://arbiscan.io/tx/${txId}';
case WalletType.none:
return '';
}
}
String _explorerDescription(WalletType type) {
String _explorerDescription(WalletType type, int? chainId) {
if (chainId != null) {
final explorerUrl = evm!.getExplorerUrlForChainId(chainId, showProtocol: false);
if (explorerUrl != null) {
return S.current.view_transaction_on + explorerUrl;
}
}
switch (type) {
case WalletType.monero:
return S.current.view_transaction_on + 'Monero.com';
@@ -445,7 +452,7 @@ abstract class TransactionDetailsViewModelBase with Store {
]);
}
void _addEthereumListItems(TransactionInfo tx, DateFormat dateFormat) {
void _addEVMListItems(TransactionInfo tx, DateFormat dateFormat) {
final _items = [
StandartListItem(
title: S.current.transaction_details_transaction_id,
@@ -539,156 +546,6 @@ abstract class TransactionDetailsViewModelBase with Store {
items.addAll(_items);
}
void _addPolygonListItems(TransactionInfo tx, DateFormat dateFormat) {
final _items = [
StandartListItem(
title: S.current.transaction_details_transaction_id,
value: tx.txHash,
key: ValueKey('standard_list_item_transaction_details_id_key'),
),
StandartListItem(
title: S.current.transaction_details_date,
value: dateFormat.format(tx.date),
key: ValueKey('standard_list_item_transaction_details_date_key'),
),
StandartListItem(
title: S.current.confirmations,
value: tx.confirmations.toString(),
key: ValueKey('standard_list_item_transaction_confirmations_key'),
),
StandartListItem(
title: S.current.transaction_details_height,
value: '${tx.height}',
key: ValueKey('standard_list_item_transaction_details_height_key'),
),
StandartListItem(
title: S.current.transaction_details_amount,
value: tx.amountFormatted(),
key: ValueKey('standard_list_item_transaction_details_amount_key'),
),
if (tx.feeFormatted()?.isNotEmpty ?? false)
StandartListItem(
title: S.current.transaction_details_fee,
value: tx.feeFormatted()!,
key: ValueKey('standard_list_item_transaction_details_fee_key'),
),
if (showRecipientAddress && tx.to != null && tx.direction == TransactionDirection.outgoing)
StandartListItem(
title: S.current.transaction_details_recipient_address,
value: tx.to!,
key: ValueKey('standard_list_item_transaction_details_recipient_address_key'),
),
if (tx.direction == TransactionDirection.incoming && tx.from != null)
StandartListItem(
title: S.current.transaction_details_source_address,
value: tx.from!,
key: ValueKey('standard_list_item_transaction_details_source_address_key'),
),
];
items.addAll(_items);
}
void _addBaseListItems(TransactionInfo tx, DateFormat dateFormat) {
final _items = [
StandartListItem(
title: S.current.transaction_details_transaction_id,
value: tx.txHash,
key: ValueKey('standard_list_item_transaction_details_id_key'),
),
StandartListItem(
title: S.current.transaction_details_date,
value: dateFormat.format(tx.date),
key: ValueKey('standard_list_item_transaction_details_date_key'),
),
StandartListItem(
title: S.current.confirmations,
value: tx.confirmations.toString(),
key: ValueKey('standard_list_item_transaction_confirmations_key'),
),
StandartListItem(
title: S.current.transaction_details_height,
value: '${tx.height}',
key: ValueKey('standard_list_item_transaction_details_height_key'),
),
StandartListItem(
title: S.current.transaction_details_amount,
value: tx.amountFormatted(),
key: ValueKey('standard_list_item_transaction_details_amount_key'),
),
if (tx.feeFormatted()?.isNotEmpty ?? false)
StandartListItem(
title: S.current.transaction_details_fee,
value: tx.feeFormatted()!,
key: ValueKey('standard_list_item_transaction_details_fee_key'),
),
if (showRecipientAddress && tx.to != null && tx.direction == TransactionDirection.outgoing)
StandartListItem(
title: S.current.transaction_details_recipient_address,
value: tx.to!,
key: ValueKey('standard_list_item_transaction_details_recipient_address_key'),
),
if (tx.direction == TransactionDirection.incoming && tx.from != null)
StandartListItem(
title: S.current.transaction_details_source_address,
value: tx.from!,
key: ValueKey('standard_list_item_transaction_details_source_address_key'),
),
];
items.addAll(_items);
}
void _addArbitrumListItems(TransactionInfo tx, DateFormat dateFormat) {
final _items = [
StandartListItem(
title: S.current.transaction_details_transaction_id,
value: tx.txHash,
key: ValueKey('standard_list_item_transaction_details_id_key'),
),
StandartListItem(
title: S.current.transaction_details_date,
value: dateFormat.format(tx.date),
key: ValueKey('standard_list_item_transaction_details_date_key'),
),
StandartListItem(
title: S.current.confirmations,
value: tx.confirmations.toString(),
key: ValueKey('standard_list_item_transaction_confirmations_key'),
),
StandartListItem(
title: S.current.transaction_details_height,
value: '${tx.height}',
key: ValueKey('standard_list_item_transaction_details_height_key'),
),
StandartListItem(
title: S.current.transaction_details_amount,
value: tx.amountFormatted(),
key: ValueKey('standard_list_item_transaction_details_amount_key'),
),
if (tx.feeFormatted()?.isNotEmpty ?? false)
StandartListItem(
title: S.current.transaction_details_fee,
value: tx.feeFormatted()!,
key: ValueKey('standard_list_item_transaction_details_fee_key'),
),
if (showRecipientAddress && tx.to != null && tx.direction == TransactionDirection.outgoing)
StandartListItem(
title: S.current.transaction_details_recipient_address,
value: tx.to!,
key: ValueKey('standard_list_item_transaction_details_recipient_address_key'),
),
if (tx.direction == TransactionDirection.incoming && tx.from != null)
StandartListItem(
title: S.current.transaction_details_source_address,
value: tx.from!,
key: ValueKey('standard_list_item_transaction_details_source_address_key'),
),
];
items.addAll(_items);
}
void _addSolanaListItems(TransactionInfo tx, DateFormat dateFormat) {
final _items = [
StandartListItem(
@@ -768,8 +625,7 @@ abstract class TransactionDetailsViewModelBase with Store {
StandardPickerListItem(
key: ValueKey('standard_picker_list_item_transaction_priorities_key'),
title: S.current.estimated_new_fee,
value: bitcoin!.formatterBitcoinAmountToString(amount: newFee) +
' ${walletTypeToCryptoCurrency(wallet.type)}',
value: bitcoin!.formatterBitcoinAmountToString(amount: newFee) + ' ${wallet.currency}',
items: priorityForWalletType(wallet.type),
customValue: settingsStore.customBitcoinFeeRate.toDouble(),
maxValue: maxCustomFeeRate,
@@ -1,7 +1,6 @@
import 'dart:developer' as dev;
import 'dart:core';
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/fiat_conversion_service.dart';
import 'package:cake_wallet/core/payment_uris.dart';
@@ -9,11 +8,10 @@ import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/arbitrum/arbitrum.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/reactions/wallet_utils.dart';
import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/decred/decred.dart';
@@ -47,7 +45,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
required this.yatStore,
required this.fiatConversionStore,
}) : _baseItems = <ListItem>[],
selectedCurrency = walletTypeToCryptoCurrency(appStore.wallet!.type),
selectedCurrency = appStore.wallet!.currency,
_cryptoNumberFormat = NumberFormat(_cryptoNumberPattern),
hasAccounts = [WalletType.monero, WalletType.wownero, WalletType.haven]
.contains(appStore.wallet!.type),
@@ -57,11 +55,14 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
_init();
}
@computed
int? get selectedChainId => wallet.chainId;
@override
void onWalletChange(wallet) {
_init();
selectedCurrency = walletTypeToCryptoCurrency(wallet.type);
selectedCurrency = wallet.currency;
hasAccounts = [WalletType.monero, WalletType.wownero, WalletType.haven].contains(wallet.type);
}
@@ -75,7 +76,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
double? _fiatRate;
String _rawAmount = '';
List<Currency> get currencies => [walletTypeToCryptoCurrency(wallet.type), ...FiatCurrency.all];
List<Currency> get currencies => [wallet.currency, ...FiatCurrency.all];
String get buttonTitle {
if (isElectrumWallet) {
@@ -114,6 +115,21 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
@computed
PaymentURI get uri {
if (isEVMCompatibleChain(wallet.type) && selectedChainId != null) {
switch (selectedChainId) {
case 1:
return EthereumURI(amount: amount, address: address.address);
case 137:
return PolygonURI(amount: amount, address: address.address);
case 8453:
return BaseURI(amount: amount, address: address.address);
case 42161:
return ArbitrumURI(amount: amount, address: address.address);
default:
return EthereumURI(amount: amount, address: address.address);
}
}
switch (wallet.type) {
case WalletType.monero:
return MoneroURI(amount: amount, address: address.address);
@@ -205,7 +221,9 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
address: address.address,
txCount: address.txCount,
balance: AmountConverter.amountIntToString(
walletTypeToCryptoCurrency(type), address.balance),
walletTypeToCryptoCurrency(type),
address.balance,
),
isChange: address.isChange,
);
});
@@ -221,7 +239,9 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
address: address.address,
txCount: address.txCount,
balance: AmountConverter.amountIntToString(
walletTypeToCryptoCurrency(type), address.balance),
walletTypeToCryptoCurrency(type),
address.balance,
),
isChange: address.isChange,
isOneTimeReceiveAddress: true,
);
@@ -238,7 +258,9 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
address: subaddress.address,
txCount: subaddress.txCount,
balance: AmountConverter.amountIntToString(
walletTypeToCryptoCurrency(type), subaddress.balance),
walletTypeToCryptoCurrency(type),
subaddress.balance,
),
isChange: subaddress.isChange);
});
@@ -257,26 +279,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
}
}
if (wallet.type == WalletType.ethereum) {
final primaryAddress = ethereum!.getAddress(wallet);
addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
}
if (wallet.type == WalletType.polygon) {
final primaryAddress = polygon!.getAddress(wallet);
addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
}
if (wallet.type == WalletType.base) {
final primaryAddress = base!.getAddress(wallet);
addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
}
if (wallet.type == WalletType.arbitrum) {
final primaryAddress = arbitrum!.getAddress(wallet);
if (isEVMCompatibleChain(wallet.type)) {
final primaryAddress = evm!.getAddress(wallet);
addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
}
@@ -415,17 +419,47 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
WalletType.dogecoin
].contains(wallet.type);
@computed
List<String> get walletImages {
List<String> getWalletImages(int? chainId) {
if (chainId != null) {
switch (chainId) {
case 1:
return [
'assets/images/eth_icon.svg',
'assets/images/usdc_icon.svg',
'assets/images/usdt_wallet_icon.svg',
'assets/images/deuro_icon.svg',
'assets/images/more_tokens.svg',
];
case 137:
return [
'assets/images/pol_icon.svg',
'assets/images/eth_pol_icon.svg',
'assets/images/usdc_icon.svg',
'assets/images/usdt_wallet_icon.svg',
'assets/images/more_tokens.svg',
];
case 8453:
return [
'assets/images/eth_icon.svg',
'assets/images/usdc_icon.svg',
'assets/images/more_tokens.svg',
];
case 42161:
return [
'assets/images/crypto/arbitrum.webp',
'assets/images/usdc_icon.svg',
'assets/images/more_tokens.svg',
];
default:
return [
'assets/images/eth_icon.svg',
'assets/images/usdc_icon.svg',
'assets/images/usdt_wallet_icon.svg',
];
}
}
switch (wallet.type) {
case WalletType.ethereum:
return [
'assets/images/eth_icon.svg',
'assets/images/usdc_icon.svg',
'assets/images/usdt_wallet_icon.svg',
'assets/images/deuro_icon.svg',
'assets/images/more_tokens.svg',
];
case WalletType.solana:
return [
'assets/images/sol_icon.svg',
@@ -433,14 +467,6 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
'assets/images/usdt_wallet_icon.svg',
'assets/images/more_tokens.svg',
];
case WalletType.polygon:
return [
'assets/images/pol_icon.svg',
'assets/images/eth_pol_icon.svg',
'assets/images/usdc_icon.svg',
'assets/images/usdt_wallet_icon.svg',
'assets/images/more_tokens.svg',
];
case WalletType.tron:
return [
'assets/images/trx_icon.svg',
@@ -453,28 +479,16 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
'assets/images/zano_icon.svg',
'assets/images/more_tokens.svg',
];
case WalletType.base:
return [
'assets/images/eth_icon.svg',
'assets/images/usdc_icon.svg',
'assets/images/more_tokens.svg',
];
case WalletType.arbitrum:
return [
'assets/images/crypto/arbitrum.webp',
'assets/images/usdc_icon.svg',
'assets/images/more_tokens.svg',
];
default:
return [];
}
}
@computed
String get qrImage => getQrImage(type);
String get qrImage => getQrImage(type, selectedChainId: selectedChainId);
@computed
String get monoImage => getChainMonoImage(type);
String get monoImage => getChainMonoImage(type, selectedChainId: selectedChainId);
@computed
bool get isBalanceAvailable => isElectrumWallet;
@@ -544,7 +558,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
selectedCurrency = currency;
if (currency is FiatCurrency && _settingsStore.fiatCurrency != currency) {
final cryptoCurrency = walletTypeToCryptoCurrency(wallet.type);
final cryptoCurrency = wallet.currency;
dev.log("Requesting Fiat rate for $cryptoCurrency-$currency");
FiatConversionService.fetchPrice(
@@ -575,7 +589,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
@action
void _convertAmountToCrypto() {
final cryptoCurrency = walletTypeToCryptoCurrency(wallet.type);
final cryptoCurrency = wallet.currency;
final fiatRate = _fiatRate ?? (fiatConversionStore.prices[cryptoCurrency] ?? 0.0);
if (fiatRate <= 0.0) {
@@ -1,10 +1,9 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/generate_wallet_password.dart';
import 'package:cake_wallet/core/wallet_creation_service.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/view_model/hardware_wallet/hardware_wallet_view_model.dart';
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
@@ -79,11 +78,11 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with
bitcoin!.createBitcoinHardwareWalletCredentials(name: name, accountData: selectedAccount!);
break;
case WalletType.ethereum:
credentials =
ethereum!.createEthereumHardwareWalletCredentials(name: name, hwAccountData: selectedAccount!);
break;
case WalletType.polygon:
credentials = polygon!.createPolygonHardwareWalletCredentials(name: name, hwAccountData: selectedAccount!);
credentials = evm!.createEVMHardwareWalletCredentials(
name: name,
hwAccountData: selectedAccount!,
);
break;
case WalletType.monero:
final password = walletPassword ?? generateWalletPassword();
+19 -4
View File
@@ -14,6 +14,8 @@ import 'package:flutter/foundation.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/decred/decred.dart';
import 'package:polyseed/polyseed.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
part 'wallet_keys_view_model.g.dart';
@@ -21,12 +23,12 @@ class WalletKeysViewModel = WalletKeysViewModelBase with _$WalletKeysViewModel;
abstract class WalletKeysViewModelBase with Store {
WalletKeysViewModelBase(this._appStore)
: title = '${walletTypeToString(_appStore.wallet!.type)} ${S.current.wallet_keys}',
_wallet = _appStore.wallet!,
: _wallet = _appStore.wallet!,
_walletName = _appStore.wallet!.type.name,
_restoreHeight = _appStore.wallet!.walletInfo.restoreHeight,
_restoreHeightByTransactions = 0,
items = ObservableList<StandartListItem>() {
items = ObservableList<StandartListItem>(),
_title = _getInitialTitle(_appStore.wallet!) {
_populateKeysItems();
reaction((_) => _appStore.wallet, (WalletBase? _wallet) {
@@ -49,11 +51,24 @@ abstract class WalletKeysViewModelBase with Store {
}
}
static String _getInitialTitle(WalletBase wallet) {
if (isEVMCompatibleChain(wallet.type)) {
final currentChain = evm!.getCurrentChain(wallet);
return '${currentChain?.name ?? walletTypeToString(wallet.type)} ${S.current.wallet_keys}';
}
return '${walletTypeToString(wallet.type)} ${S.current.wallet_keys}';
}
bool get isBitcoin => _wallet.type == WalletType.bitcoin;
final ObservableList<StandartListItem> items;
final String title;
@observable
String _title;
String get title => _title;
final WalletBase _wallet;
final String _walletName;
final AppStore _appStore;
+5 -29
View File
@@ -1,14 +1,11 @@
import 'package:cake_wallet/arbitrum/arbitrum.dart';
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/core/new_wallet_arguments.dart';
import 'package:cake_wallet/dogecoin/dogecoin.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/zano/zano.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/wallet_creation_service.dart';
@@ -21,10 +18,7 @@ import 'package:cake_wallet/view_model/wallet_creation_vm.dart';
import 'package:cake_wallet/decred/decred.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import '../polygon/polygon.dart';
import 'advanced_privacy_settings_view_model.dart';
part 'wallet_new_vm.g.dart';
@@ -51,8 +45,7 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
bool get hasLanguageSelector =>
[WalletType.monero, WalletType.haven, WalletType.wownero].contains(type);
bool get showLanguageSelector =>
newWalletArguments?.mnemonic == null && hasLanguageSelector;
bool get showLanguageSelector => newWalletArguments?.mnemonic == null && hasLanguageSelector;
bool get hasSeedType =>
newWalletArguments?.mnemonic == null &&
@@ -85,21 +78,10 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
mnemonic: newWalletArguments!.mnemonic,
);
case WalletType.ethereum:
return ethereum!.createEthereumNewWalletCredentials(
name: name,
password: walletPassword,
mnemonic: newWalletArguments!.mnemonic,
passphrase: passphrase,
);
case WalletType.polygon:
case WalletType.base:
return base!.createBaseNewWalletCredentials(
name: name,
password: walletPassword,
mnemonic: newWalletArguments!.mnemonic,
passphrase: passphrase,
);
case WalletType.arbitrum:
return arbitrum!.createArbitrumNewWalletCredentials(
return evm!.createEVMNewWalletCredentials(
name: name,
password: walletPassword,
mnemonic: newWalletArguments!.mnemonic,
@@ -127,13 +109,7 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
mnemonic: newWalletArguments!.mnemonic,
passphrase: passphrase,
);
case WalletType.polygon:
return polygon!.createPolygonNewWalletCredentials(
name: name,
password: walletPassword,
mnemonic: newWalletArguments!.mnemonic,
passphrase: passphrase,
);
case WalletType.solana:
return solana!.createSolanaNewWalletCredentials(
name: name,
+35 -65
View File
@@ -1,15 +1,13 @@
import 'package:cake_wallet/arbitrum/arbitrum.dart';
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/core/generate_wallet_password.dart';
import 'package:cake_wallet/core/wallet_creation_service.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/dogecoin/dogecoin.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/tron/tron.dart';
@@ -68,7 +66,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
}
walletCreationService.changeWalletType(type: type);
if (restoredWallet != null) {
if(restoredWallet!.restoreMode == WalletRestoreMode.seed) {
if (restoredWallet!.restoreMode == WalletRestoreMode.seed) {
seedSettingsViewModel.setPassphrase(restoredWallet!.passphrase);
}
}
@@ -78,18 +76,12 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
static const decredSeedMnemonicLength = 15;
late List<WalletRestoreMode> availableModes;
late final bool hasSeedLanguageSelector = [
WalletType.monero,
WalletType.haven,
WalletType.wownero
].contains(type);
late final bool hasSeedLanguageSelector =
[WalletType.monero, WalletType.haven, WalletType.wownero].contains(type);
late final bool hasBlockchainHeightSelector =
[WalletType.monero, WalletType.haven, WalletType.wownero].contains(type);
late final bool hasBlockchainHeightSelector = [
WalletType.monero,
WalletType.haven,
WalletType.wownero
].contains(type);
late final bool hasRestoreFromPrivateKey = [
WalletType.ethereum,
WalletType.polygon,
@@ -101,10 +93,8 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
WalletType.tron
].contains(type);
late final bool onlyViewKeyRestore = [
if (FeatureFlag.hasBitcoinViewOnly) WalletType.bitcoin,
WalletType.decred
].contains(type);
late final bool onlyViewKeyRestore =
[if (FeatureFlag.hasBitcoinViewOnly) WalletType.bitcoin, WalletType.decred].contains(type);
final RestoredWallet? restoredWallet;
final HardwareWalletType? hardwareWalletType;
@@ -131,7 +121,11 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
switch (type) {
case WalletType.monero:
return monero!.createMoneroRestoreWalletFromSeedCredentials(
name: name, height: height, mnemonic: seed, password: password, passphrase: passphrase??'');
name: name,
height: height,
mnemonic: seed,
password: password,
passphrase: passphrase ?? '');
case WalletType.bitcoin:
case WalletType.litecoin:
return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials(
@@ -142,13 +136,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
derivationType: derivationInfo!.derivationType!,
derivationPath: derivationInfo.derivationPath!,
);
case WalletType.ethereum:
return ethereum!.createEthereumRestoreWalletFromSeedCredentials(
name: name,
mnemonic: seed,
password: password,
passphrase: passphrase,
);
case WalletType.bitcoinCash:
return bitcoinCash!.createBitcoinCashRestoreWalletFromSeedCredentials(
name: name,
@@ -172,22 +160,11 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
derivationType: derivationInfo!.derivationType!,
passphrase: passphrase,
);
case WalletType.ethereum:
case WalletType.polygon:
return polygon!.createPolygonRestoreWalletFromSeedCredentials(
name: name,
mnemonic: seed,
password: password,
passphrase: passphrase,
);
case WalletType.base:
return base!.createBaseRestoreWalletFromSeedCredentials(
name: name,
mnemonic: seed,
password: password,
passphrase: passphrase,
);
case WalletType.arbitrum:
return arbitrum!.createArbitrumRestoreWalletFromSeedCredentials(
return evm!.createEVMRestoreWalletFromSeedCredentials(
name: name,
mnemonic: seed,
password: password,
@@ -212,7 +189,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
name: name,
mnemonic: seed,
password: password,
passphrase: passphrase??'',
passphrase: passphrase ?? '',
height: height,
);
case WalletType.zano:
@@ -220,14 +197,14 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
name: name,
password: password,
height: height,
passphrase: passphrase??'',
passphrase: passphrase ?? '',
mnemonic: seed,
);
case WalletType.decred:
return decred!.createDecredRestoreWalletFromSeedCredentials(
name: name,
mnemonic: seed,
password: password,
name: name,
mnemonic: seed,
password: password,
);
case WalletType.none:
case WalletType.haven:
@@ -271,13 +248,6 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
language: 'English',
);
case WalletType.ethereum:
return ethereum!.createEthereumRestoreWalletFromPrivateKey(
name: name,
privateKey: options['private_key'] as String,
password: password,
);
case WalletType.nano:
return nano!.createNanoRestoreWalletFromKeysCredentials(
name: name,
@@ -285,20 +255,11 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
seedKey: options['private_key'] as String,
derivationType: derivationInfo!.derivationType!,
);
case WalletType.ethereum:
case WalletType.polygon:
return polygon!.createPolygonRestoreWalletFromPrivateKey(
name: name,
password: password,
privateKey: options['private_key'] as String,
);
case WalletType.base:
return base!.createBaseRestoreWalletFromPrivateKey(
name: name,
password: password,
privateKey: options['private_key'] as String,
);
case WalletType.arbitrum:
return arbitrum!.createArbitrumRestoreWalletFromPrivateKey(
return evm!.createEVMRestoreWalletFromPrivateKey(
name: name,
password: password,
privateKey: options['private_key'] as String,
@@ -343,7 +304,16 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
var list = <DerivationInfo>[];
var walletType = credentials["walletType"] as WalletType;
var appStore = getIt.get<AppStore>();
var node = appStore.settingsStore.getCurrentNode(walletType);
int? chainId;
if (isEVMCompatibleChain(walletType)) {
if (appStore.wallet != null) {
chainId = evm!.getSelectedChainId(appStore.wallet!);
}
chainId ??= evm!.getChainIdByWalletType(walletType);
}
var node = appStore.settingsStore.getCurrentNode(walletType, chainId: chainId);
switch (walletType) {
case WalletType.bitcoin:
+12 -1
View File
@@ -6,6 +6,8 @@ import 'package:flutter/foundation.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:mobx/mobx.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
part 'wallet_seed_view_model.g.dart';
@@ -15,11 +17,20 @@ abstract class WalletSeedViewModelBase with Store {
WalletSeedViewModelBase(WalletBase wallet)
: name = wallet.name,
seed = wallet.seed!,
walletType = walletTypeToString(wallet.type),
walletType = _getWalletTypeName(wallet),
currentOptions = ObservableList<String>(),
verificationIndices = ObservableList<int>() {
setupSeedVerification();
}
static String _getWalletTypeName(WalletBase wallet) {
if (isEVMCompatibleChain(wallet.type)) {
final currentChain = evm!.getCurrentChain(wallet);
return currentChain?.name ?? walletTypeToString(wallet.type);
} else {
return walletTypeToString(wallet.type);
}
}
@observable
String name;
@@ -1,5 +1,6 @@
import 'dart:async';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/core/wallet_loading_service.dart';
import 'package:cw_core/wallet_info.dart';
@@ -29,8 +30,12 @@ abstract class WalletSwitcherViewModelBase with Store {
@action
Future<List<WalletInfo>> getWallets(WalletType? walletType) async {
final wiList = await WalletInfo.getAll();
if (walletType == null) {
return wiList;
if (walletType == null) return wiList;
// For EVM-compatible wallet types, show all EVM-compatible wallets
// This allows users to switch between any EVM wallet regardless of the specific chain
if (isEVMCompatibleChain(walletType)) {
return wiList.where((wallet) => isEVMCompatibleChain(wallet.type)).toList();
}
return wiList.where((wallet) => wallet.type == walletType).toList();