Files
hash-wallet/lib/evm/cw_evm.dart
T
75f3fe735b Introduce Money class and refactor currency handling (#3157)
* feat: add `Money` class for precise currency handling and amount parsing

- Introduced `Money` class for handling money values with fixed-point precision.
- Added parsing and comparison methods to `CryptoCurrency` and `FiatCurrency` interfaces.
- Implemented `smartAmountSanitizer` for format normalization.

Includes extensive unit tests for validation.

* refactor: optimize Money class with new factories and string formatting improvements

* refactor: unify balance handling with `Money` refactor

* refactor: update `ElectrumBalance` and transaction logic to use `Money` for currency precision

* refactor: standardize balance handling with `Money` across `Monero`, `Wownero`, and `Zano` wallets

* refactor: migrate EVM balance and transaction handling to `Money` for consistent currency precision

* refactor: migrate Decred balance and transaction handling to `Money` for consistent currency precision

* refactor: migrate nano balance and transaction handling to `Money` for consistent currency precision

* refactor: migrate Solana balance and transaction logic to `Money` for precise currency handling

* refactor: migrate Tron balance and transaction handling to `Money` for consistent currency precision

* refactor: migrate Zcash balance and transaction handling to `Money` for consistent currency precision

* refactor: adjust lib to handle money

* refactor: remove legacy Decred amount formatting and migrate to `Money`

* refactor: migrate pending transaction amount and fee to `Money`

* refactor: migrate dEuro savings and transaction logic to `Money` for improved precision and consistency

* refactor: migrate Bitcoin and lightning transaction logic to `Money` for consistent currency precision

* refactor: migrate Wownero, EVM, Zano, and Zcash transaction logic to `Money` for consistent and precise currency handling

* refactor: standardize balance handling across wallets, migrate to `Money.zero` and refine `Balance` class for consistency

* refactor: migrate Solana transaction and wallet logic to `Money` for consistent and precise currency handling

* refactor: improve fiat balance and formatting for consistency on dashboard

* fix: fiat input mode switching and standardize crypto amount display logic

* fix: cw_evm generator

* refactor: migrate exchange amount handling to `Money` for improved precision and consistency

* test: remove redundant and commented-out tests, tidy test cases, and apply minor formatting adjustments

* fix using Money wrapper in BridgeViewModel and USDT0Service

* refactor: streamline `AmountConverter` and `LightningWallet`, add `cw_core` tests to GitHub workflow, and remove unused/deprecated logic

* refactor: enhance `changeReceiveAmount` with `isCanonical` support, improve null handling, and refine amount parsing logic

* minor fix

* try/catch the lightning log file error

* feat: add copy-to-clipboard functionality for transaction amounts, fix typo in trailing widget method name (#3219)

* Cw 1234 disallow sp and mweb addresses as refund for swaps (#3187)

* disallow SP and MWEB for swaps

* Simplify address selection in addressForExchange

Refactor addressForExchange method to simplify address selection logic.

* validate refund and receive address types

* localize address validation messages

* minor ui fix [skip ci]

* minor fix [skip ci]

* Revert "localize address validation messages"

* fix translations

---------

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

* refactor: replace manual amount sanitization with `sanitized()` extension across the codebase, add unit tests for `AmountSanitizer`

* fix: correct `sanitized()` test expectations for decimal handling in `amount_sanitizer_test.dart`

* refactor: fee display logic and append currency symbol in `send_confirm_sheet.dart`

* refactor: simplify `ZcashBalance` class by replacing custom fields with inherited properties, update references accordingly

* refactor: replace hardcoded USDT contract address with `DefaultTronTokens` lookup, improve maintainability

* refactor: replace `currency` with `inputAmount.currency` in Solana transaction signing methods to avoid inconsistency

* fix: handle null `fee` in `electrum_transaction_info.dart` to prevent potential runtime errors

* refactor: replace raw fee and amount handling with `Money` object for improved consistency and maintainability

* fix: limit fiat balance display to 2 decimal places in `balance_view_model.dart`

* refactor: replace raw `amount` and `fee` types with `Money` in Solana transaction methods for consistency and precision

* refactor: replace raw `amount` and `fee` handling with `Money` and improve balance calculation consistency across view models

* refactor: replace raw balance strings with `Money` and simplify `UnconfirmedBalanceModal` widget logic

* refactor: replace `print` with `printV` for better logging, add equality and hashCode overrides to `ERC20Currency` for object comparison

* feat: prepare deuro savings migration

* feat: add V1 savings migration support and improve formatting consistency in DEuro view model

* feat: add V1 savings withdrawal support and update associated UI components and logic

* refactor: replace `Image.asset` with `CakeImageWidget`, update balance string handling, and streamline address validation logic

* refactor: use `Money.tryParse` with fallback to `Money.zero` in `NanoBalance.fromRawString` for safer balance parsing

* refactor: replace `ERC20Currency` with `Erc20Token` for consistency and remove unused `evm_erc20_currency.dart` file

* refactor: add computed `hasDepositAmount` for deposit null-checks, optimize balance handling in `zano_wallet`, update Breez SDK to v0.14.0, and implement equality/hashCode for `Currency`

* refactor: rename `amountSats` to `amount` in `PrepareLnurlPayRequest` for consistency

* revert: downgrade Breez SDK to v0.11.0 in `pubspec.yaml` and update relevant references in `pubspec.lock`

* fix: use `cryptoAmount.copyWith` for currency consistency and replace `Image.asset` with `CakeImageWidget` in swap address modal

* fix: ensure currency consistency by using `cryptoAmount.copyWith` in Solana and EVM wallet transactions

* refactor: simplify CSV row generation logic by removing `_splitAmountCurrency` and streamlining amount/fee handling

* fix: LateInitializationError Field 'sdk' has not been initialized

* fix: use `Money.tryParse` for safer parsing of deposit and receive amounts

* chore: remove debug print statement

* refactor: update transaction processing to consistently use `Money` for amounts and fees

* refactor: improve currency and amount handling, simplify logic, and enhance code readability in Send and Swap pages

* refactor: integrate `DecimalInputFormatter` for consistent decimal input across Send and Swap pages, simplify input handling logic

* fix: "fiatMode" per output on sendPage

* refactor: make transaction_details_height applicable more readable

* fix: use correct base unit for deposit

* refactor: use Money in estimateFakeSendAllTxAmount method

* fix decred frozen balance
minor fixes

* fix decred frozen balance
minor fixes

* fix: automatically switch to lightning if lnurl or invoice detected

* fix: getBolt11Amount test cases

* fix: standardise crypto amount formatting and display

* fix: use base unit for Tron transaction fee parsing
* refactor: hardcode fractional digits to 8 in `AmountParsingProxy` display methods
* refactor: ensure consistent 8-decimal precision for fee formatting in `PendingTransaction`
* fix: use `AmountParsingProxy` for available balance display in exchange view model
* fix: improve amount formatting and layout in swap and send confirmation sheets
* chore: remove redundant decimal truncation in balance view model and send confirmation sheet

* fix: allow for editing text field if it is filled with "ALL"

* fix: allow for editing text field if it is filled with "ALL"

* fix: update currency detection logic for lightning mode

* avoid potential infinite loop [skip ci]

* fix: getDisplayCryptoAmount

* fix: update estimated fee calculation

* chore: simplify fee calculation logic [skip-ci]

* fix value display in hidden mode

* fix stale state in send confirm sheet

* fix decimals for mweb

* fix available balance display for sol/trx on swap

* restore proper hint text

* fix swap receive amount calculation for xno

---------

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
Co-authored-by: Serhii <17529954+serhii-bor@users.noreply.github.com>
Co-authored-by: Robert Malikowski <malikowskirobert@gmail.com>
2026-06-30 02:45:44 +03:00

757 lines
22 KiB
Dart

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,
}) =>
EVMChainTransactionCredentials(
outputs
.map((out) => OutputInfo(
fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmountMoney,
address: out.address,
note: out.note,
sendAll: out.sendAll,
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
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
TransactionInfo getTransactionInfo({
required String id,
required int height,
required Money amount,
required Money fee,
required String tokenSymbol,
int exponent = 18,
required TransactionDirection direction,
required bool isPending,
required DateTime date,
required int confirmations,
String? to,
String? from,
String? evmSignatureName,
String? contractAddress,
required int chainId,
}) =>
EVMChainTransactionInfo(
id: id,
height: height,
amount: amount,
fee: fee,
tokenSymbol: tokenSymbol,
direction: direction,
isPending: isPending,
date: date,
confirmations: confirmations,
to: to,
from: from,
chainId: chainId);
@override
int formatterEVMParseAmount(String amount) => EVMChainFormatter.parseEVMChainAmount(amount);
@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
Future<bool?> getTransactionReceipt(WalletBase wallet, String txHash) async {
final client = getWeb3Client(wallet);
if (client == null) return null;
try {
final receipt = await client.getTransactionReceipt(txHash);
if (receipt == null) return null;
return receipt.status;
} catch (_) {
return null;
}
}
@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<BigInt?> getAllowance(WalletBase wallet, String tokenContract, String spender) =>
(wallet as EVMChainWallet).getAllowance(tokenContract, spender);
@override
Future<PendingTransaction> createTokenApproval(
WalletBase wallet,
Money amount,
String spender,
TransactionPriority? priority, {
bool useBlinkProtection = true,
}) {
final evmWallet = wallet as EVMChainWallet;
return evmWallet.createApprovalTransaction(
amount,
spender,
priority as EVMChainTransactionPriority?,
useBlinkProtection: useBlinkProtection,
);
}
@override
Future<PendingTransaction> createRawCallDataTransaction(
WalletBase wallet,
String to,
String dataHex,
Money valueWei,
TransactionPriority? priority, {
bool useBlinkProtection = true,
String? sourceTokenAddress,
BigInt? sourceTokenAmount,
}) =>
(wallet as EVMChainWallet).createCallDataTransaction(
to,
dataHex,
valueWei,
priority as EVMChainTransactionPriority?,
sourceTokenAddress,
sourceTokenAmount,
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<Erc20Token> getDefaultTokensByChainId(int chainId) =>
EVMChainDefaultTokens.getDefaultTokensByChainId(chainId);
@override
List<String> getDefaultTokenContractAddresses(WalletBase wallet) {
final chainId = getSelectedChainId(wallet);
if (chainId == null) return [];
return EVMChainDefaultTokens.getDefaultTokenAddresses(chainId);
}
@override
List<String> getDefaultTokenSymbols(WalletBase wallet) {
final chainId = getSelectedChainId(wallet);
if (chainId == null) return [];
return EVMChainDefaultTokens.getDefaultTokenSymbols(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<Money>? getDEuroSavingsBalance(WalletBase wallet) {
if (wallet.chainId == 1 && wallet is EVMChainWallet) {
return DEuro(wallet).savingsBalance;
}
return null;
}
@override
Future<Money>? getDEuroSavingsV1Balance(WalletBase wallet) {
if (wallet.chainId == 1 && wallet is EVMChainWallet) {
return DEuro(wallet).savingsBalanceV1;
}
return null;
}
@override
Future<Money>? 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>? withdrawDEuroSavingV1(
WalletBase wallet, TransactionPriority priority) {
if (wallet.chainId == 1 && wallet is EVMChainWallet) {
return DEuro(wallet).withdrawSavingsV1(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,
currency: config.nativeCurrency,
))
.toList();
}
@override
ChainInfo? getChainInfoByChainId(int chainId) {
final config = _registry.getChainConfig(chainId);
if (config == null) return null;
return ChainInfo(
chainId: config.chainId,
name: config.name,
shortCode: config.shortCode,
currency: config.nativeCurrency,
);
}
@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,
currency: config.nativeCurrency,
);
}
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);
@override
bool isUSDT0Token(WalletBase wallet, CryptoCurrency token) {
if (token is! Erc20Token) return false;
final chainId = getSelectedChainId(wallet);
if (chainId == null) return false;
return USDT0Config.isUSDT0Token(token, chainId);
}
@override
List<ChainInfo> getUSDT0DestinationChains(WalletBase wallet) {
final currentChainId = getSelectedChainId(wallet);
if (currentChainId == null) return [];
final result = <ChainInfo>[];
for (final config in _registry.getAllChains()) {
if (USDT0Config.isChainSupported(config.chainId) && config.chainId != currentChainId) {
result.add(ChainInfo(
chainId: config.chainId,
name: config.name,
shortCode: config.shortCode,
currency: config.nativeCurrency,
));
}
}
return result;
}
@override
Future<BridgeQuote> quoteUSDT0Transfer({
required WalletBase wallet,
required int sourceChainId,
required int destinationChainId,
required BigInt amount,
required String recipientAddress,
}) async {
final evmWallet = wallet as EVMChainWallet;
final client = evmWallet.getWeb3Client();
if (client == null) {
throw StateError('Wallet not connected');
}
final quote = await USDT0Service.quoteCrossChainTransfer(
client: client,
sourceChainId: sourceChainId,
destinationChainId: destinationChainId,
amount: amount,
recipientAddress: recipientAddress,
);
return BridgeQuote(
nativeFee: quote.nativeFee,
lzTokenFee: quote.lzTokenFee,
);
}
@override
Future<PendingTransaction> executeUSDT0Transfer({
required WalletBase wallet,
required CryptoCurrency token,
required int sourceChainId,
required int destinationChainId,
required BigInt amount,
required String recipientAddress,
required BridgeQuote quote,
required TransactionPriority priority,
bool useBlinkProtection = true,
}) {
final evmWallet = wallet as EVMChainWallet;
final tokenErc20 = token as Erc20Token;
return USDT0Service.executeCrossChainTransfer(
wallet: evmWallet,
sourceChainId: sourceChainId,
destinationChainId: destinationChainId,
amount: amount,
recipientAddress: recipientAddress,
quote: USDT0Quote(nativeFee: quote.nativeFee, lzTokenFee: quote.lzTokenFee),
token: tokenErc20,
priority: priority as EVMChainTransactionPriority,
useBlinkProtection: useBlinkProtection,
);
}
Future<EvmWalletConnectFeeQuote?> getWCBufferedFeeQuote(
WalletBase wallet,
TransactionPriority priority,
) async {
if (wallet is! EVMChainWallet) return null;
final data = await wallet.getWCBufferedFeeQuote(priority);
if (data == null) return null;
return EvmWalletConnectFeeQuote(
maxFeePerGasWei: data.maxFeePerGasWei,
maxPriorityFeePerGasWei: data.maxPriorityFeePerGasWei,
latestBaseFeeWei: data.latestBaseFeeWei,
);
}
Future<({double usdValue, bool hasValidFiatPrice})> _getTokenUsdValueAndFiatCheck(
Erc20Token token,
BigInt balanceWei,
) async {
try {
final settingsStore = getIt.get<SettingsStore>();
final torOnly = settingsStore.fiatApiMode == FiatApiMode.torOnly;
final price = await FiatConversionService.fetchPrice(
crypto: token,
fiat: FiatCurrency.usd,
torOnly: torOnly,
);
final hasValidFiatPrice = price > 0;
final decimals = token.decimal;
final balance = balanceWei.toDouble() / math.pow(10, decimals);
final usdValue = balance * price;
return (usdValue: usdValue, hasValidFiatPrice: hasValidFiatPrice);
} catch (e) {
return (usdValue: 0.0, hasValidFiatPrice: false);
}
}
static const _minTokenUsdValue = 0.1;
@override
Future<void> discoverAndAddWalletTokens(WalletBase wallet) async {
if (wallet is! EVMChainWallet) return;
try {
final result = await wallet.discoverTokensFromMoralis();
if (result.newTokens.isEmpty) return;
final List<Future<void>> tokenChecks = [];
for (final item in result.newTokens) {
tokenChecks.add((() async {
final token = item.token;
final isPropertiesSuspicious = wallet.isTokenPropertiesSuspicious(token);
final fiatResult = await _getTokenUsdValueAndFiatCheck(
token,
item.balanceWei,
);
final isSpam = isPropertiesSuspicious || !fiatResult.hasValidFiatPrice;
token.isPotentialScam = isSpam;
token.enabled = (fiatResult.usdValue >= _minTokenUsdValue) && !isSpam;
await wallet.addErc20Token(token);
})());
}
await Future.wait(tokenChecks);
} catch (_) {}
}
}