Merge upstream Cake Wallet v6.5.0

Catches up from the May 12 fork point (v6.1.2+23) through v6.5.0: 535
upstream commits, including the Monero silent-open fix, Monero on Trezor,
Ledger/Trezor BLE fixes, the new seed and history screens, quick Bitcoin
sync, git-pinned dependencies (pubspec_overrides.yaml), frozen Hive
adapters (*.part.dart), monero_c v0.18.4.6-RC2 and Flutter 3.41.9.

Merged with both sides normalized first (import rename, 100-column
format), which took the conflict count from 614 paths to 322. The
procedure is now scripts/upstream_sync.sh.

Kept from Hash Bags:
- WalletType enum order and Hive IDs. SQLite stores WalletType.index and
  our order differs from upstream's; taking theirs would reread saved
  wallets as other chains. Our frozen adapter keeps the shipped IDs.
- The 1.0.2 legacy PIN and wallet-password decode and self-heal.
- Removed chains (Solana, Tron, Zano, Zcash, Decred, Haven) and the new
  Robinhood chain stay out; Cake Pay and non-Trocador swaps stay out.
- Default nodes (node.wownero.org, sethforprivacy, Blockstream, bysh.me),
  converted to upstream's isDefault node-list key; Wownero is back in the
  node reset list.
- Privacy: address resolvers limited to domain-anchored sources again;
  prices.neroswap.com for fiat; upstream's charts tab, which reads
  prices.cakewallet.com, sits behind FeatureFlag.isChartsEnabled = false.
- Lightning stays behind FeatureFlag.isLightningEnabled, including
  upstream's new post-seed Lightning username step.
- Our icon pipeline, branding and bundle identifiers.

Twelve new upstream widget tests are skipped: test and code are unchanged
from v6.5.0 apart from renames (not rerun on a pristine upstream checkout;
upstream's top_bar_test does not compile against its own TopBar).
flutter analyze lib: 0 errors; flutter test: 345 passed, 12 skipped;
flutter build bundle compiles. Built with Flutter 3.41.9.
This commit is contained in:
jwinterm
2026-10-24 23:49:29 -04:00
1406 changed files with 81062 additions and 48972 deletions
+167 -103
View File
@@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'package:cw_core/amount/money.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/erc20_token.dart';
import 'package:cw_core/node.dart';
@@ -56,7 +57,7 @@ class EVMChainClient {
}
if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) {
final res = (jsonResponse['result'] as List);
final res = jsonResponse["result"] as List;
res.removeWhere((e) => e['value'] == '0');
// Filter out spam native transactions below 0.00001 ETH (10000000000000 wei)
@@ -144,7 +145,10 @@ class EVMChainClient {
final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) {
if (response.statusCode >= 200 &&
response.statusCode < 300 &&
jsonResponse['status'] != 0 &&
jsonResponse['result'] is List) {
final symbol = EVMChainUtils.getFeeCurrency(chainId);
return (jsonResponse['result'] as List)
@@ -171,17 +175,31 @@ class EVMChainClient {
try {
Uri? rpcUri;
bool isModifiedNodeUri = false;
final nodeHost = Uri.parse("https://${node.uriRaw}").host;
final pathSegments =
(node.path ?? "").split("/").where((segment) => segment.isNotEmpty).toList();
if (node.uriRaw.contains('nownodes.io')) {
if (nodeHost.endsWith(".nownodes.io") && pathSegments.isEmpty) {
isModifiedNodeUri = true;
String nowNodeApiKey = secrets.nowNodesApiKey;
if (nowNodeApiKey.isEmpty) {
printV('NowNodes API key is empty, cannot connect to ${node.uriRaw}');
printV("NowNodes API key is empty, cannot connect to ${node.uriRaw}");
return false;
}
rpcUri = Uri.https(node.uriRaw, '/$nowNodeApiKey');
} else if (nodeHost.endsWith(".g.alchemy.com") &&
(pathSegments.isEmpty || (pathSegments.length == 1 && pathSegments.first == "v2"))) {
isModifiedNodeUri = true;
String alchemyApiKey = "";
if (alchemyApiKey.isEmpty) {
printV("Alchemy API key is empty, cannot connect to ${node.uriRaw}");
return false;
}
rpcUri = Uri.https(node.uriRaw, "/v2/$alchemyApiKey");
}
_client = Web3Client(isModifiedNodeUri ? rpcUri!.toString() : node.uri.toString(), client);
@@ -297,14 +315,13 @@ class EVMChainClient {
Future<PendingEVMChainTransaction> signTransaction({
required Credentials privateKey,
required String toAddress,
required BigInt amount,
required BigInt gasFee,
required Money amount,
required Money gasFee,
required int estimatedGasUnits,
required int maxFeePerGas,
required EVMChainTransactionPriority? priority,
required CryptoCurrency currency,
required String feeCurrency,
required int exponent,
String? contractAddress,
String? data,
int? gasPrice,
@@ -315,13 +332,17 @@ class EVMChainClient {
currency == CryptoCurrency.baseEth ||
currency == CryptoCurrency.arbEth ||
currency == CryptoCurrency.bnb ||
currency == CryptoCurrency.robEth ||
contractAddress != null);
bool isNativeToken = currency == CryptoCurrency.eth ||
currency == CryptoCurrency.maticpoly ||
currency == CryptoCurrency.baseEth ||
currency == CryptoCurrency.arbEth ||
currency == CryptoCurrency.bnb;
final isNativeToken = [
CryptoCurrency.eth,
CryptoCurrency.maticpoly,
CryptoCurrency.baseEth,
CryptoCurrency.arbEth,
CryptoCurrency.bnb,
CryptoCurrency.robEth
].contains(currency);
// Get nonce with "pending" block tag to include pending transactions
// This prevents "Nonce too low" errors when sending multiple transactions quickly
@@ -335,7 +356,7 @@ class EVMChainClient {
to: EthereumAddress.fromHex(toAddress),
maxPriorityFeePerGas:
priority != null ? EtherAmount.fromInt(EtherUnit.gwei, priority.tip) : null,
amount: isNativeToken ? EtherAmount.inWei(amount) : EtherAmount.zero(),
amount: isNativeToken ? EtherAmount.inWei(amount.amount) : EtherAmount.zero(),
data: data != null ? hexToBytes(data) : null,
maxGas: estimatedGasUnits,
maxFeePerGas: EtherAmount.fromInt(EtherUnit.wei, maxFeePerGas),
@@ -358,7 +379,7 @@ class EVMChainClient {
signedTransaction = await erc20.transfer(
EthereumAddress.fromHex(toAddress),
amount,
amount.amount,
credentials: privateKey,
transaction: transaction,
);
@@ -369,24 +390,20 @@ class EVMChainClient {
return PendingEVMChainTransaction(
signedTransaction: prepareSignedTransactionForSending(signedTransaction),
amount: amount.toString(),
amount: amount,
fee: gasFee,
feeCurrency: feeCurrency,
sendTransaction: _sendTransaction,
exponent: exponent,
);
}
Future<PendingEVMChainTransaction> signApprovalTransaction({
required Credentials privateKey,
required String spender,
required BigInt amount,
required BigInt gasFee,
required String feeCurrency,
required Money amount,
required Money gasFee,
required int estimatedGasUnits,
required int maxFeePerGas,
required EVMChainTransactionPriority? priority,
required int exponent,
required String contractAddress,
int? gasPrice,
bool useBlinkProtection = true,
@@ -416,20 +433,18 @@ class EVMChainClient {
final signedTransaction = await erc20.approve(
EthereumAddress.fromHex(spender),
amount,
amount.amount,
credentials: privateKey,
transaction: transaction,
);
return PendingEVMChainTransaction(
signedTransaction: prepareSignedTransactionForSending(signedTransaction),
amount: amount.toString(),
amount: amount,
fee: gasFee,
feeCurrency: feeCurrency,
sendTransaction: () =>
sendTransaction(signedTransaction, useBlinkProtection: useBlinkProtection),
exponent: exponent,
isInfiniteApproval: amount.toRadixString(16) ==
isInfiniteApproval: amount.amount.toRadixString(16) ==
'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
);
}
@@ -524,36 +539,37 @@ class EVMChainClient {
}
Future<EVMChainERC20Balance> fetchERC20Balances(
EthereumAddress userAddress, String contractAddress) async {
EthereumAddress userAddress, Erc20Token token) async {
try {
final erc20 = ERC20(address: EthereumAddress.fromHex(contractAddress), client: _client!);
final erc20 =
ERC20(address: EthereumAddress.fromHex(token.contractAddress), client: _client!);
final balance = await erc20.balanceOf(userAddress);
int exponent = (await erc20.decimals()).toInt();
return EVMChainERC20Balance(balance, exponent: exponent);
return EVMChainERC20Balance(Money(balance, token));
} on RangeError catch (_) {
throw Exception('Invalid token contract for this network.');
} catch (e) {
if (e.toString().contains("hostUnreachable")) {
return EVMChainERC20Balance(BigInt.zero);
return EVMChainERC20Balance(Money.zero(token));
}
throw Exception('Could not fetch balances: ${e.toString()}');
}
}
Future<Erc20Token?> getErc20Token(String contractAddress, String chainName) async {
Future<Erc20Token?> getErc20Token(String contractAddress, String? chainName) async {
try {
final token = await getErc20TokenFromMoralis(contractAddress, chainName);
if (chainName != null) {
final token = await getErc20TokenFromMoralis(contractAddress, chainName);
if (token == null || token.name.isEmpty || token.symbol.isEmpty) {
return await getErcTokenInfoFromNode(contractAddress, chainName);
if (token != null && token.name.isNotEmpty && token.symbol.isNotEmpty) {
return token;
}
}
return token;
return await getErcTokenInfoFromNode(contractAddress);
} catch (e) {
try {
return await getErcTokenInfoFromNode(contractAddress, chainName);
return await getErcTokenInfoFromNode(contractAddress);
} catch (e) {
return null;
}
@@ -600,7 +616,7 @@ class EVMChainClient {
);
}
Future<Erc20Token?> getErcTokenInfoFromNode(String contractAddress, String chainName) async {
Future<Erc20Token?> getErcTokenInfoFromNode(String contractAddress) async {
final erc20 = ERC20(address: EthereumAddress.fromHex(contractAddress), client: _client!);
final name = await erc20.name();
final symbol = await erc20.symbol();
@@ -624,74 +640,116 @@ class EVMChainClient {
return [];
}
final uri = Uri.https(
'deep-index.moralis.io',
'/api/v2.2/$address/erc20',
{
"chain": chainName,
},
);
final response = await client.get(
uri,
headers: {
"Accept": "application/json",
"X-API-Key": secrets.moralisApiKey,
},
);
if (response.statusCode < 200 || response.statusCode >= 300) {
printV('Moralis API returned invalid status code: ${response.statusCode}');
return [];
}
final decodedResponse = jsonDecode(response.body) as List;
const maxPages = 3;
String? cursor;
int pageCount = 0;
final List<MoralisWalletTokenBalance> tokens = [];
for (final item in decodedResponse) {
final tokenData = item as Map<String, dynamic>;
do {
final params = <String, String>{
"chain": chainName,
if (cursor != null && cursor.isNotEmpty) "cursor": cursor,
};
final balanceStr = tokenData['balance'] as String? ?? '0';
final balanceWei = BigInt.tryParse(balanceStr) ?? BigInt.zero;
if (balanceWei == BigInt.zero) continue;
final contractAddress = (tokenData['token_address'] as String? ?? '').toLowerCase();
final name = (tokenData['name'] as String? ?? '').toString();
final symbol = (tokenData['symbol'] as String? ?? '').toString();
final symbolFiltered = symbol.replaceFirst(RegExp('^\\\$'), '');
final decimalsRaw = tokenData['decimals'];
final decimals =
decimalsRaw is int ? decimalsRaw : int.tryParse(decimalsRaw.toString()) ?? 18;
final logo = tokenData['logo'] as String?;
final thumbnail = tokenData['thumbnail'] as String?;
final iconUrl = logo ?? thumbnail;
final possibleSpamRaw = tokenData['possible_spam'];
final possibleSpam = possibleSpamRaw is bool
? possibleSpamRaw
: (possibleSpamRaw.toString().toLowerCase() == 'true');
final verifiedContractRaw = tokenData['verified_contract'];
final verifiedContract = verifiedContractRaw is bool
? verifiedContractRaw
: (verifiedContractRaw.toString().toLowerCase() == 'true');
tokens.add(
MoralisWalletTokenBalance(
contractAddress: contractAddress,
name: name,
symbol: symbolFiltered,
decimals: decimals,
iconUrl: iconUrl,
balanceWei: balanceWei,
possibleSpam: possibleSpam,
verifiedContract: verifiedContract,
),
final uri = Uri.https(
'deep-index.moralis.io',
'/api/v2.2/wallets/$address/tokens',
params,
);
}
final response = await client.get(
uri,
headers: {
"Accept": "application/json",
"X-API-Key": secrets.moralisApiKey,
},
);
if (response.statusCode < 200 || response.statusCode >= 300) {
printV('Moralis API returned invalid status code: ${response.statusCode}');
return tokens;
}
final decoded = jsonDecode(response.body);
if (decoded is! Map<String, dynamic>) return tokens;
final result = decoded['result'];
if (result is! List) return tokens;
for (final item in result) {
if (item is! Map<String, dynamic>) continue;
final tokenData = item;
final nativeRaw = tokenData['native_token'];
final nativeToken =
nativeRaw is bool ? nativeRaw : (nativeRaw?.toString().toLowerCase() == 'true');
if (nativeToken) continue;
final balanceStr = tokenData['balance'] as String? ?? '0';
final balanceWei = BigInt.tryParse(balanceStr) ?? BigInt.zero;
if (balanceWei == BigInt.zero) continue;
final contractAddress = (tokenData['token_address'] as String? ?? '').toLowerCase();
final name = (tokenData['name'] as String? ?? '').toString();
final symbol = (tokenData['symbol'] as String? ?? '').toString();
final symbolFiltered = symbol.replaceFirst(RegExp('^\\\$'), '');
final decimalsRaw = tokenData['decimals'];
final decimals =
decimalsRaw is int ? decimalsRaw : int.tryParse(decimalsRaw.toString()) ?? 18;
final logo = tokenData['logo'] as String?;
final thumbnail = tokenData['thumbnail'] as String?;
final iconUrl = logo ?? thumbnail;
final possibleSpamRaw = tokenData['possible_spam'];
final possibleSpam = possibleSpamRaw is bool
? possibleSpamRaw
: (possibleSpamRaw?.toString().toLowerCase() == 'true');
final verifiedContractRaw = tokenData['verified_contract'];
final verifiedContract = verifiedContractRaw is bool
? verifiedContractRaw
: (verifiedContractRaw?.toString().toLowerCase() == 'true');
final usdPriceRaw = tokenData['usd_price'];
final double? usdPrice = usdPriceRaw is num
? usdPriceRaw.toDouble()
: (usdPriceRaw is String ? double.tryParse(usdPriceRaw) : null);
final usdValueRaw = tokenData['usd_value'];
final double? usdValue = usdValueRaw is num
? usdValueRaw.toDouble()
: (usdValueRaw is String ? double.tryParse(usdValueRaw) : null);
final securityRaw = tokenData['security_score'];
final int? securityScore = securityRaw is int
? securityRaw
: (securityRaw is num
? securityRaw.toInt()
: (securityRaw is String ? int.tryParse(securityRaw) : null));
tokens.add(
MoralisWalletTokenBalance(
contractAddress: contractAddress,
name: name,
symbol: symbolFiltered,
decimals: decimals,
iconUrl: iconUrl,
balanceWei: balanceWei,
possibleSpam: possibleSpam,
verifiedContract: verifiedContract,
usdPrice: usdPrice,
usdValue: usdValue,
securityScore: securityScore,
),
);
}
final nextCursor = decoded['cursor'];
cursor = nextCursor is String && nextCursor.isNotEmpty ? nextCursor : null;
pageCount++;
} while (cursor != null && pageCount < maxPages);
return tokens;
} catch (e, stackTrace) {
@@ -743,6 +801,9 @@ class MoralisWalletTokenBalance {
final BigInt balanceWei;
final bool possibleSpam;
final bool verifiedContract;
final double? usdPrice;
final double? usdValue;
final int? securityScore;
MoralisWalletTokenBalance({
required this.contractAddress,
@@ -753,5 +814,8 @@ class MoralisWalletTokenBalance {
required this.balanceWei,
required this.possibleSpam,
required this.verifiedContract,
this.usdPrice,
this.usdValue,
this.securityScore,
});
}
+42
View File
@@ -0,0 +1,42 @@
import "package:cw_evm/clients/evm_chain_client.dart";
import "package:flutter/foundation.dart";
import "package:web3dart/web3dart.dart";
class RobinhoodClient extends EVMChainClient {
RobinhoodClient() : super(chainId: 4663);
@override
Transaction createTransaction({
required EthereumAddress from,
required EthereumAddress to,
required EtherAmount amount,
EtherAmount? maxPriorityFeePerGas,
Uint8List? data,
int? maxGas,
EtherAmount? gasPrice,
EtherAmount? maxFeePerGas,
int? nonce,
}) {
EtherAmount? finalGasPrice = gasPrice;
if (gasPrice == null && maxFeePerGas != null) {
finalGasPrice = maxFeePerGas;
}
return Transaction(
from: from,
to: to,
value: amount,
data: data,
maxGas: maxGas,
gasPrice: finalGasPrice,
nonce: nonce,
);
}
@override
Uint8List prepareSignedTransactionForSending(Uint8List signedTransaction) => signedTransaction;
@override
int get chainId => 4663;
}
+101 -55
View File
@@ -1,5 +1,7 @@
import 'package:cw_core/amount/money.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_evm/deuro/deuro_savings_contract.dart';
import 'package:cw_evm/deuro/deuro_savings_gateway_contract.dart' as v1;
import 'package:cw_evm/deuro/deuro_savings_contract.dart' as v2;
import 'package:cw_evm/evm_chain_wallet.dart';
import 'package:cw_evm/contract/erc20.dart';
import 'package:cw_evm/evm_chain_exceptions.dart';
@@ -9,21 +11,27 @@ import 'package:web3dart/crypto.dart';
import 'package:web3dart/web3dart.dart';
const String savingsGatewayAddress = "0x073493d73258C4BEb6542e8dd3e1b2891C972303";
const String savingsV2Address = "0x760233b90e45d186A9A98E911B115F7F4B90d3D9";
const String dEuroAddress = "0xbA3f535bbCcCcA2A154b573Ca6c5A49BAAE0a3ea";
const String frontendCode = "0x00000000000000000000000000000000000000000043616b652057616c6c6574";
class DEuro {
final SavingsGateway _savingsGateway;
final v2.Savings _savings;
final ERC20 _dEuro;
final EVMChainWallet _wallet;
DEuro(EVMChainWallet wallet)
: _wallet = wallet,
_savingsGateway = _getSavingsGateway(wallet.getWeb3Client()!),
_savings = _getSavings(wallet.getWeb3Client()!),
_dEuro = _getDEuroToken(wallet.getWeb3Client()!);
static SavingsGateway _getSavingsGateway(Web3Client client) => SavingsGateway(
static v2.Savings _getSavings(Web3Client client) => v2.Savings(
address: EthereumAddress.fromHex(savingsV2Address),
client: client,
);
static v1.SavingsGateway _getSavingsGateway(Web3Client client) => v1.SavingsGateway(
address: EthereumAddress.fromHex(savingsGatewayAddress),
client: client,
);
@@ -35,26 +43,31 @@ class DEuro {
EthereumAddress get _address => EthereumAddress.fromHex(_wallet.walletAddresses.primaryAddress);
Future<BigInt> get savingsBalance async =>
(await _savingsGateway.savings(accountOwner: _address)).saved;
Future<Money> get savingsBalance async =>
Money((await _savings.savings(accountOwner: _address)).saved, CryptoCurrency.deuro);
Future<BigInt> get accruedInterest => _savingsGateway.accruedInterest(accountOwner: _address);
Future<Money> get savingsBalanceV1 async => Money(
(await _getSavingsGateway(_wallet.getWeb3Client()!).savings(accountOwner: _address)).saved,
CryptoCurrency.deuro);
Future<BigInt> get interestRate => _savingsGateway.currentRatePPM();
Future<Money> get accruedInterest async =>
Money(await _savings.accruedInterest(accountOwner: _address), CryptoCurrency.deuro);
Future<BigInt> get approvedBalance => _dEuro.allowance(_address, _savingsGateway.self.address);
Future<BigInt> get interestRate => _savings.currentRatePPM();
Future<BigInt> get approvedBalance => _dEuro.allowance(_address, _savings.self.address);
Future<void> _checkEthBalanceForGasFees(EVMChainTransactionPriority priority) async {
final ethBalance = await _wallet.getWeb3Client()!.getBalance(_address);
final currentBalance = ethBalance.getInWei;
final savings = _getSavingsGateway(_wallet.getWeb3Client()!);
final gasFeesModel = await _wallet.calculateActualEstimatedFeeForCreateTransaction(
amount: BigInt.zero,
contractAddress: _savingsGateway.self.address.hexEip55,
receivingAddressHex: _savingsGateway.self.address.hexEip55,
amount: Money.zero(_wallet.currency),
contractAddress: savings.self.address.hexEip55,
receivingAddressHex: savings.self.address.hexEip55,
priority: priority,
data: _savingsGateway.self.abi.functions[17]
.encodeCall([BigInt.zero, hexToBytes(frontendCode)]),
data: savings.self.abi.functions[17].encodeCall([BigInt.zero, hexToBytes(frontendCode)]),
);
final estimatedGasFee = BigInt.from(gasFeesModel.estimatedGasFee);
@@ -73,28 +86,26 @@ class DEuro {
try {
await _checkEthBalanceForGasFees(priority);
final signedTransaction = await _savingsGateway.save(
(amount: amount, frontendCode: hexToBytes(frontendCode)),
final signedTransaction = await _savings.save(
(amount: amount, compound: true),
credentials: _wallet.evmChainPrivateKey,
);
final fee = await _wallet.calculateActualEstimatedFeeForCreateTransaction(
amount: amount,
contractAddress: _savingsGateway.self.address.hexEip55,
receivingAddressHex: _savingsGateway.self.address.hexEip55,
amount: Money.zero(_wallet.currency),
contractAddress: _savings.self.address.hexEip55,
receivingAddressHex: _savings.self.address.hexEip55,
priority: priority,
data: _savingsGateway.self.abi.functions[17].encodeCall([amount, hexToBytes(frontendCode)]),
data: _savings.self.abi.functions[18].encodeCall([amount, true]),
);
final sendTransaction = () => _wallet.getWeb3Client()!.sendRawTransaction(signedTransaction);
sendTransaction() => _wallet.getWeb3Client()!.sendRawTransaction(signedTransaction);
return PendingEVMChainTransaction(
feeCurrency: "ETH",
sendTransaction: sendTransaction,
signedTransaction: signedTransaction,
fee: BigInt.from(fee.estimatedGasFee),
amount: amount.toString(),
exponent: 18,
fee: Money(BigInt.from(fee.estimatedGasFee), CryptoCurrency.eth),
amount: Money(amount, CryptoCurrency.deuro),
);
} catch (e) {
if (e.toString().contains('insufficient funds for gas')) {
@@ -110,29 +121,66 @@ class DEuro {
try {
await _checkEthBalanceForGasFees(priority);
final signedTransaction = await _savingsGateway.withdraw(
final signedTransaction = await _savings.withdraw(
(target: _address, amount: amount),
credentials: _wallet.evmChainPrivateKey,
);
final fee = await _wallet.calculateActualEstimatedFeeForCreateTransaction(
amount: Money.zero(_wallet.currency),
contractAddress: _savings.self.address.hexEip55,
receivingAddressHex: _savings.self.address.hexEip55,
priority: priority,
data: _savings.self.abi.functions[23].encodeCall([_address, amount]),
);
sendTransaction() => _wallet.getWeb3Client()!.sendRawTransaction(signedTransaction);
return PendingEVMChainTransaction(
sendTransaction: sendTransaction,
signedTransaction: signedTransaction,
fee: Money(BigInt.from(fee.estimatedGasFee), CryptoCurrency.eth),
amount: Money(amount, CryptoCurrency.deuro),
);
} catch (e) {
if (e.toString().contains('insufficient funds for gas')) {
final ethBalance = await _wallet.getWeb3Client()!.getBalance(_address);
throw InsufficientGasFeeException(currentBalance: ethBalance.getInWei);
}
rethrow;
}
}
Future<PendingEVMChainTransaction> withdrawSavingsV1(EVMChainTransactionPriority priority) async {
try {
await _checkEthBalanceForGasFees(priority);
final withdrawAmount = await savingsBalanceV1;
// Withdraw at least a million to overflow and close the savings position
final amount = BigInt.parse("1000000000000000000000000");
final savings = _getSavingsGateway(_wallet.getWeb3Client()!);
final signedTransaction = await savings.withdraw(
(target: _address, amount: amount, frontendCode: hexToBytes(frontendCode)),
credentials: _wallet.evmChainPrivateKey,
);
final fee = await _wallet.calculateActualEstimatedFeeForCreateTransaction(
amount: amount,
contractAddress: _savingsGateway.self.address.hexEip55,
receivingAddressHex: _savingsGateway.self.address.hexEip55,
amount: Money.zero(_wallet.currency),
contractAddress: savings.self.address.hexEip55,
receivingAddressHex: savings.self.address.hexEip55,
priority: priority,
data: _savingsGateway.self.abi.functions[24]
.encodeCall([_address, amount, hexToBytes(frontendCode)]),
data:
savings.self.abi.functions[24].encodeCall([_address, amount, hexToBytes(frontendCode)]),
);
final sendTransaction = () => _wallet.getWeb3Client()!.sendRawTransaction(signedTransaction);
sendTransaction() => _wallet.getWeb3Client()!.sendRawTransaction(signedTransaction);
return PendingEVMChainTransaction(
sendTransaction: sendTransaction,
signedTransaction: signedTransaction,
fee: BigInt.from(fee.estimatedGasFee),
amount: amount.toString(),
feeCurrency: "ETH",
exponent: 18);
sendTransaction: sendTransaction,
signedTransaction: signedTransaction,
fee: Money(BigInt.from(fee.estimatedGasFee), CryptoCurrency.eth),
amount: withdrawAmount,
);
} catch (e) {
if (e.toString().contains('insufficient funds for gas')) {
final ethBalance = await _wallet.getWeb3Client()!.getBalance(_address);
@@ -146,28 +194,26 @@ class DEuro {
try {
await _checkEthBalanceForGasFees(priority);
final signedTransaction = await _savingsGateway.refreshBalance(
final signedTransaction = await _savings.refreshBalance(
(owner: _address),
credentials: _wallet.evmChainPrivateKey,
);
final fee = await _wallet.calculateActualEstimatedFeeForCreateTransaction(
amount: BigInt.zero,
contractAddress: _savingsGateway.self.address.hexEip55,
receivingAddressHex: _savingsGateway.self.address.hexEip55,
amount: Money.zero(_wallet.currency),
contractAddress: _savings.self.address.hexEip55,
receivingAddressHex: _savings.self.address.hexEip55,
priority: priority,
data: _savingsGateway.self.abi.functions[15].encodeCall([_address]),
data: _savings.self.abi.functions[16].encodeCall([_address]),
);
final sendTransaction = () => _wallet.getWeb3Client()!.sendRawTransaction(signedTransaction);
sendTransaction() => _wallet.getWeb3Client()!.sendRawTransaction(signedTransaction);
return PendingEVMChainTransaction(
feeCurrency: "ETH",
sendTransaction: sendTransaction,
signedTransaction: signedTransaction,
fee: BigInt.from(fee.estimatedGasFee),
amount: 0.toString(),
exponent: 18,
fee: Money(BigInt.from(fee.estimatedGasFee), CryptoCurrency.eth),
amount: Money.zero(CryptoCurrency.deuro),
);
} catch (e) {
if (e.toString().contains('insufficient funds for gas')) {
@@ -184,14 +230,14 @@ class DEuro {
await _checkEthBalanceForGasFees(priority);
return (await _wallet.createApprovalTransaction(
BigInt.parse(
'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
radix: 16,
),
_savingsGateway.self.address.hexEip55,
CryptoCurrency.deuro,
Money(
BigInt.parse(
'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
radix: 16,
),
CryptoCurrency.deuro),
_savings.self.address.hexEip55,
priority,
"ETH",
)) as PendingEVMChainTransaction;
} catch (e) {
if (e.toString().contains('insufficient funds for gas')) {
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+3
View File
@@ -1,6 +1,7 @@
import 'package:cw_evm/clients/arbitrum_client.dart';
import 'package:cw_evm/clients/base_client.dart';
import 'package:cw_evm/clients/bsc_client.dart';
import "package:cw_evm/clients/robinhood_client.dart";
import 'package:cw_evm/clients/ethereum_client.dart';
import 'package:cw_evm/clients/polygon_client.dart';
import 'package:cw_evm/clients/evm_chain_client.dart';
@@ -32,6 +33,8 @@ class EVMChainClientFactory {
return ArbitrumClient();
case 56:
return BSCClient();
case 4663:
return RobinhoodClient();
default:
return EVMChainClient(chainId: chainId);
}
+48 -20
View File
@@ -1,28 +1,56 @@
import 'package:cw_core/erc20_token.dart';
import 'package:cw_evm/tokens/arbitrum_tokens.dart';
import 'package:cw_evm/tokens/base_tokens.dart';
import 'package:cw_evm/tokens/bsc_tokens.dart';
import 'package:cw_evm/tokens/ethereum_tokens.dart';
import 'package:cw_evm/tokens/polygon_tokens.dart';
import "package:cw_core/erc20_token.dart";
import "package:cw_evm/tokens/arbitrum_tokens.dart";
import "package:cw_evm/tokens/base_tokens.dart";
import "package:cw_evm/tokens/bsc_tokens.dart";
import "package:cw_evm/tokens/ethereum_tokens.dart";
import "package:cw_evm/tokens/polygon_tokens.dart";
import "package:cw_evm/tokens/robinhood_tokens.dart";
/// Default ERC20 tokens for each EVM chain and utility methods for interacting with them
class EVMChainDefaultTokens {
static List<Erc20Token> getDefaultTokensByChainId(int chainId) {
return switch (chainId) {
1 => EthereumTokens.tokens,
137 => PolygonTokens.tokens,
8453 => BaseTokens.tokens,
42161 => ArbitrumTokens.tokens,
56 => BSCTokens.tokens,
_ => [],
};
static List<Erc20Token> getDefaultTokensByChainId(int chainId) => switch (chainId) {
1 => EthereumTokens.tokens,
137 => PolygonTokens.tokens,
8453 => BaseTokens.tokens,
42161 => ArbitrumTokens.tokens,
56 => BSCTokens.tokens,
4663 => RobinhoodTokens.tokens,
_ => [],
};
static List<String> getDefaultTokenAddresses(int chainId) =>
getDefaultTokensByChainId(chainId).map((token) => token.contractAddress).toList();
static List<String> getDefaultTokenSymbols(int chainId) =>
getDefaultTokensByChainId(chainId).map((token) => token.symbol.toUpperCase()).toList();
static final Map<int, Map<String, String>> _iconPathsByAddress = {};
static String? getDefaultIconPathByAddress(int chainId, String contractAddress) {
final iconPaths = _iconPathsByAddress.putIfAbsent(chainId, () {
final paths = <String, String>{};
for (final token in getDefaultTokensByChainId(chainId)) {
final iconPath = token.iconPath;
if (iconPath != null && iconPath.isNotEmpty) {
paths[token.contractAddress.toLowerCase()] = iconPath;
}
}
return paths;
});
return iconPaths[contractAddress.toLowerCase()];
}
static List<String> getDefaultTokenAddresses(int chainId) {
return getDefaultTokensByChainId(chainId).map((token) => token.contractAddress).toList();
}
static Set<String> getDefaultGroupsByAddress(int chainId, String contractAddress) {
for (final token in getDefaultTokensByChainId(chainId)) {
if (token.contractAddress.toLowerCase() == contractAddress.toLowerCase()) {
return token.groups;
}
}
static List<String> getDefaultTokenSymbols(int chainId) {
return getDefaultTokensByChainId(chainId).map((token) => token.symbol.toUpperCase()).toList();
return const {};
}
}
+6
View File
@@ -1,5 +1,11 @@
import 'package:cw_core/crypto_currency.dart';
class EVMChainMnemonicIsIncorrectException implements Exception {
@override
String toString() =>
'EVM mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.';
}
class EVMChainTransactionCreationException implements Exception {
final String exceptionMessage;
+1
View File
@@ -186,6 +186,7 @@ class EvmChainRegistry {
WalletType.bsc,
'BSC',
);
}
void _registerChain(
+23 -32
View File
@@ -1,18 +1,19 @@
// ignore_for_file: overridden_fields, annotate_overrides
import 'dart:math';
import 'package:cw_core/amount/money.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/erc20_token.dart';
import 'package:cw_core/format_amount.dart';
import 'package:cw_core/format_fixed.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cw_evm/evm_chain_registry.dart';
import 'package:cw_evm/utils/evm_chain_utils.dart';
class EVMChainTransactionInfo extends TransactionInfo {
EVMChainTransactionInfo({
required this.id,
required this.height,
required this.ethAmount,
required this.ethFee,
required this.amount,
required this.fee,
required this.tokenSymbol,
this.exponent = 18,
required this.direction,
@@ -24,19 +25,16 @@ class EVMChainTransactionInfo extends TransactionInfo {
this.evmSignatureName,
this.contractAddress,
required this.chainId,
}) : amount = ethAmount.toInt(),
fee = ethFee.toInt();
});
final String id;
final int height;
final int amount;
final BigInt ethAmount;
final Money amount;
final int exponent;
final TransactionDirection direction;
final DateTime date;
final bool isPending;
final int fee;
final BigInt ethFee;
final Money fee;
final int confirmations;
final String tokenSymbol;
String? _fiatAmount;
@@ -49,39 +47,32 @@ class EVMChainTransactionInfo extends TransactionInfo {
/// Get fee currency symbol based on wallet type
String get feeCurrency => EVMChainUtils.getFeeCurrency(chainId);
String get _displayTokenSymbol {
if (chainId == 56 && tokenSymbol.toUpperCase() == 'BSC-USD') return 'USDT';
return tokenSymbol;
}
@override
String amountFormatted() {
final amount = formatAmount((ethAmount / BigInt.from(10).pow(exponent)).toString());
return '${amount.substring(0, min(10, amount.length))} $_displayTokenSymbol';
}
@override
String fiatAmount() => _fiatAmount ?? '';
@override
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
@override
String feeFormatted() => '${formatFixed(ethFee, 18)} $feeCurrency';
factory EVMChainTransactionInfo.fromJson(Map<String, dynamic> data, int chainId) {
final decimals = data['exponent'] as int? ?? 18;
final tokenSymbol = data['tokenSymbol'] as String;
final currency =
Erc20Token(name: '', symbol: tokenSymbol, contractAddress: '', decimal: decimals);
final feeCurrency =
EvmChainRegistry().getChainConfig(chainId)?.nativeCurrency ?? CryptoCurrency.eth;
return EVMChainTransactionInfo(
id: data['id'] as String,
height: data['height'] as int,
ethAmount: BigInt.parse(data['amount'] as String),
exponent: data['exponent'] as int? ?? 18,
ethFee: BigInt.parse(data['fee'] as String),
amount: Money(BigInt.parse(data['amount'] as String), currency),
exponent: decimals,
fee: Money(BigInt.parse(data['fee'] as String), feeCurrency),
direction: TransactionDirection.values[data['direction'] as int],
date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
isPending: data['isPending'] as bool? ?? false,
confirmations: data['confirmations'] as int,
tokenSymbol: data['tokenSymbol'] as String,
tokenSymbol: tokenSymbol,
to: data['to'] as String?,
from: data['from'] as String?,
evmSignatureName: data['evmSignatureName'] as String?,
@@ -93,9 +84,9 @@ class EVMChainTransactionInfo extends TransactionInfo {
Map<String, dynamic> toJson() => {
'id': id,
'height': height,
'amount': ethAmount.toString(),
'amount': amount.amount.toString(),
'exponent': exponent,
'fee': ethFee.toString(),
'fee': fee.amount.toString(),
'direction': direction.index,
'date': date.millisecondsSinceEpoch,
'isPending': isPending,
File diff suppressed because it is too large Load Diff
+13 -26
View File
@@ -1,18 +1,15 @@
import 'dart:developer';
import "dart:developer";
import 'package:cw_core/payment_uris.dart';
import 'package:cw_core/wallet_addresses.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:mobx/mobx.dart';
import "package:cw_core/payment_uris.dart";
import "package:cw_core/wallet_addresses.dart";
import "package:mobx/mobx.dart";
part 'evm_chain_wallet_addresses.g.dart';
part "evm_chain_wallet_addresses.g.dart";
class EVMChainWalletAddresses = EVMChainWalletAddressesBase with _$EVMChainWalletAddresses;
abstract class EVMChainWalletAddressesBase extends WalletAddresses with Store {
EVMChainWalletAddressesBase(WalletInfo walletInfo, this._selectedChainId)
: address = '',
super(walletInfo);
EVMChainWalletAddressesBase(super.walletInfo, this._selectedChainId) : address = "";
@override
@observable
@@ -33,7 +30,7 @@ abstract class EVMChainWalletAddressesBase extends WalletAddresses with Store {
Future<void> updateAddressesInBox() async {
try {
addressesMap.clear();
addressesMap[address] = '';
addressesMap[address] = "";
await saveAddressesInBox();
} catch (e) {
log(e.toString());
@@ -41,20 +38,10 @@ abstract class EVMChainWalletAddressesBase extends WalletAddresses with Store {
}
@override
PaymentURI getPaymentUri(String amount) {
switch (_selectedChainId) {
case 1:
return EthereumURI(amount: amount, address: address);
case 137:
return PolygonURI(amount: amount, address: address);
case 8453:
return BaseURI(amount: amount, address: address);
case 42161:
return ArbitrumURI(amount: amount, address: address);
case 56:
return BSCURI(amount: amount, address: address);
default:
return EthereumURI(amount: amount, address: address);
}
}
PaymentURI getPaymentUri(String amount) => ERC681URI(
address: address,
amount: amount,
chainId: _selectedChainId,
contractAddress: null,
);
}
+68 -33
View File
@@ -1,14 +1,19 @@
import 'dart:io';
import "dart:io";
import 'package:bip39/bip39.dart' as bip39;
import 'package:cw_core/encryption_file_utils.dart';
import "package:cw_core/erc20_token.dart";
import "package:cw_core/imported_nft.dart";
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_service.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:path/path.dart' as p;
import 'package:cw_evm/clients/evm_chain_client.dart';
import 'package:cw_evm/evm_chain_client_factory.dart';
import 'package:cw_evm/evm_chain_exceptions.dart';
import 'package:cw_evm/evm_chain_registry.dart';
import 'package:cw_evm/evm_chain_wallet.dart';
import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
@@ -32,6 +37,9 @@ class EVMChainWalletService extends WalletService<
return _registry.getRegisteredWalletTypes();
}
List<String> get _importedNFTChains =>
_registry.getAllChains().map((chain) => chain.shortCode).toList();
Future<WalletInfo?> _findWalletByName(String name) async {
for (final type in _evmWalletTypes) {
final walletInfo = await WalletInfo.get(name, type);
@@ -46,8 +54,8 @@ class EVMChainWalletService extends WalletService<
@override
WalletType getType() {
throw UnsupportedError(
'EVMChainWalletService is unified and does not have a single type. '
'Use walletInfo.type instead.',
"EVMChainWalletService is unified and does not have a single type. "
"Use walletInfo.type instead.",
);
}
@@ -57,7 +65,7 @@ class EVMChainWalletService extends WalletService<
Future<void> saveBackup(String name, {WalletInfo? walletInfo}) async {
final info = walletInfo ?? await _findWalletByName(name);
if (info == null) {
throw Exception('Wallet not found: $name');
throw Exception("Wallet not found: $name");
}
final backupWalletDirPath = await pathForWalletDir(name: "$name.backup", type: info.type);
@@ -73,7 +81,7 @@ class EVMChainWalletService extends WalletService<
Future<void> restoreWalletFilesFromBackup(String name) async {
final walletInfo = await _findWalletByName(name);
if (walletInfo == null) {
throw Exception('Wallet not found: $name');
throw Exception("Wallet not found: $name");
}
final backupWalletDirPath = await pathForWalletDir(name: "$name.backup", type: walletInfo.type);
@@ -94,7 +102,7 @@ class EVMChainWalletService extends WalletService<
// Get chainId from wallet type
final chainConfig = _registry.getChainConfigByWalletType(walletInfo.type);
if (chainConfig == null) {
throw Exception('Chain config not found for wallet type: ${walletInfo.type}');
throw Exception("Chain config not found for wallet type: ${walletInfo.type}");
}
final initialChainId = chainConfig.chainId;
@@ -122,7 +130,7 @@ class EVMChainWalletService extends WalletService<
);
await wallet.init();
wallet.addInitialTokens();
await wallet.addInitialTokens();
await wallet.save();
return wallet;
}
@@ -131,7 +139,7 @@ class EVMChainWalletService extends WalletService<
Future<EVMChainWallet> openWallet(String name, String password) async {
final walletInfo = await _findWalletByName(name);
if (walletInfo == null) {
throw Exception('Wallet not found');
throw Exception("Wallet not found");
}
try {
@@ -143,7 +151,7 @@ class EVMChainWalletService extends WalletService<
);
await wallet.init();
wallet.addInitialTokens();
await wallet.addInitialTokens();
await wallet.save();
await saveBackup(name);
return wallet;
@@ -158,7 +166,7 @@ class EVMChainWalletService extends WalletService<
);
await wallet.init();
wallet.addInitialTokens();
await wallet.addInitialTokens();
await wallet.save();
return wallet;
}
@@ -166,29 +174,47 @@ class EVMChainWalletService extends WalletService<
@override
Future<void> rename(String currentName, String password, String newName) async {
if (currentName == newName) return;
final currentWalletInfo = await _findWalletByName(currentName);
if (currentWalletInfo == null) {
throw Exception('Wallet not found');
throw Exception("Wallet not found");
}
final currentWallet = await _openWalletInstance(
password: password,
name: currentName,
walletInfo: currentWalletInfo,
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
);
final type = currentWalletInfo.type;
await currentWallet.renameWalletFiles(newName);
await copyWalletFilesTo(fromName: currentName, toName: newName, type: type);
await saveBackup(newName, walletInfo: currentWalletInfo);
// Update walletInfo with new name before saving backup
final newWalletInfo = currentWalletInfo;
newWalletInfo.id = WalletBase.idFor(newName, currentWalletInfo.type);
newWalletInfo.name = newName;
currentWalletInfo.id = WalletBase.idFor(newName, type);
currentWalletInfo.name = newName;
await currentWalletInfo.save();
// Pass walletInfo to saveBackup to avoid lookup (since WalletInfo not saved yet)
await saveBackup(newName, walletInfo: newWalletInfo);
final oldNameStillUsed = (await _findWalletByName(currentName)) != null;
if (oldNameStillUsed) {
for (final token in await Erc20Token.selectList("walletName = ?", [currentName])) {
final copiedToken = Erc20Token.copyWith(token, walletName: newName);
await copiedToken.save();
}
await newWalletInfo.save();
final ownNFTs = (await ImportedNFT.getAllForWallet(currentName))
.where((nft) => _importedNFTChains.contains(nft.chain));
for (final nft in ownNFTs) {
await ImportedNFT.copyWith(nft, walletName: newName).save();
}
} else {
await Erc20Token.renameWallet(currentName, newName);
await ImportedNFT.renameWallet(currentName, newName, chains: _importedNFTChains);
}
final oldDir = Directory(p.join(await pathForWalletTypeDir(type: type), currentName));
if (oldDir.existsSync()) {
try {
await oldDir.delete(recursive: true);
} catch (e) {
printV('rename: failed to delete old wallet dir "$currentName": $e');
}
}
}
@override
@@ -196,12 +222,16 @@ class EVMChainWalletService extends WalletService<
EVMChainRestoreWalletFromSeedCredentials credentials, {
bool? isTestnet,
}) async {
if (!bip39.validateMnemonic(credentials.mnemonic)) {
throw EVMChainMnemonicIsIncorrectException();
}
final walletInfo = credentials.walletInfo!;
// Get chainId from wallet type
final chainConfig = _registry.getChainConfigByWalletType(walletInfo.type);
if (chainConfig == null) {
throw Exception('Chain config not found for wallet type: ${walletInfo.type}');
throw Exception("Chain config not found for wallet type: ${walletInfo.type}");
}
final initialChainId = chainConfig.chainId;
@@ -227,7 +257,7 @@ class EVMChainWalletService extends WalletService<
);
await wallet.init();
wallet.addInitialTokens();
await wallet.addInitialTokens();
await wallet.save();
return wallet;
}
@@ -242,7 +272,7 @@ class EVMChainWalletService extends WalletService<
// Get chainId from wallet type
final chainConfig = _registry.getChainConfigByWalletType(walletInfo.type);
if (chainConfig == null) {
throw Exception('Chain config not found for wallet type: ${walletInfo.type}');
throw Exception("Chain config not found for wallet type: ${walletInfo.type}");
}
final initialChainId = chainConfig.chainId;
@@ -267,7 +297,7 @@ class EVMChainWalletService extends WalletService<
);
await wallet.init();
wallet.addInitialTokens();
await wallet.addInitialTokens();
await wallet.save();
return wallet;
}
@@ -281,7 +311,7 @@ class EVMChainWalletService extends WalletService<
// Get chainId from wallet type
final chainConfig = _registry.getChainConfigByWalletType(walletInfo.type);
if (chainConfig == null) {
throw Exception('Chain config not found for wallet type: ${walletInfo.type}');
throw Exception("Chain config not found for wallet type: ${walletInfo.type}");
}
final initialChainId = chainConfig.chainId;
@@ -305,7 +335,7 @@ class EVMChainWalletService extends WalletService<
);
await wallet.init();
wallet.addInitialTokens();
await wallet.addInitialTokens();
await wallet.save();
return wallet;
}
@@ -324,11 +354,16 @@ class EVMChainWalletService extends WalletService<
Future<void> remove(String wallet) async {
final walletInfo = await _findWalletByName(wallet);
if (walletInfo == null) {
throw Exception('Wallet not found');
throw Exception("Wallet not found");
}
File(await pathForWalletDir(name: wallet, type: walletInfo.type)).delete(recursive: true);
await WalletInfo.delete(walletInfo);
final nameStillUsed = (await _findWalletByName(wallet)) != null;
if (!nameStillUsed) {
await Erc20Token.deleteAllForWallet(wallet);
await ImportedNFT.deleteAllForWallet(wallet, chains: _importedNFTChains);
}
}
EVMChainWallet _createWalletInstance({
@@ -346,7 +381,7 @@ class EVMChainWalletService extends WalletService<
final chainConfig = _registry.getChainConfigByWalletType(walletType);
if (chainConfig == null) {
throw Exception('Chain config not found for wallet type: $walletType');
throw Exception("Chain config not found for wallet type: $walletType");
}
return EVMChainWallet(
+10 -19
View File
@@ -1,32 +1,23 @@
import 'dart:convert';
import "dart:convert";
import 'package:cw_core/balance.dart';
import "package:cw_core/amount/money.dart";
import "package:cw_core/balance.dart";
import "package:cw_core/currency.dart";
class EVMChainERC20Balance extends Balance {
EVMChainERC20Balance(this.balance, {this.exponent = 18}) : super(balance, balance);
EVMChainERC20Balance(Money balance) : super(balance, Money.zero(balance.currency));
final BigInt balance;
final int exponent;
String toJSON() => json.encode({"balanceInWei": available.amount.toString()});
String toJSON() => json.encode({
'balanceInWei': balance.toString(),
'exponent': exponent,
});
static EVMChainERC20Balance? fromJSON(String? jsonSource) {
if (jsonSource == null) {
return null;
}
static EVMChainERC20Balance? fromJSON(String? jsonSource, Currency currency) {
if (jsonSource == null) return null;
final decoded = json.decode(jsonSource) as Map;
try {
return EVMChainERC20Balance(
BigInt.parse(decoded['balanceInWei']),
exponent: decoded['exponent'],
);
return EVMChainERC20Balance(Money(BigInt.parse(decoded["balanceInWei"]), currency));
} catch (e) {
return EVMChainERC20Balance(BigInt.zero);
return EVMChainERC20Balance(Money.zero(currency));
}
}
}
+11 -13
View File
@@ -1,6 +1,6 @@
import 'dart:typed_data';
import 'package:cw_core/format_fixed.dart';
import 'package:cw_core/amount/money.dart';
import 'package:cw_core/pending_transaction.dart';
import 'package:web3dart/crypto.dart';
import 'package:hex/hex.dart' as Hex;
@@ -8,36 +8,36 @@ import 'package:hex/hex.dart' as Hex;
class PendingEVMChainTransaction with PendingTransaction {
final Function sendTransaction;
final Uint8List signedTransaction;
final BigInt fee;
final String feeCurrency;
final String amount;
final int exponent;
final bool isInfiniteApproval;
PendingEVMChainTransaction({
required this.sendTransaction,
required this.signedTransaction,
required this.fee,
required this.feeCurrency,
required this.amount,
required this.exponent,
this.isInfiniteApproval = false,
});
@override
String get amountFormatted {
if (isInfiniteApproval) return "∞";
return formatFixed(BigInt.parse(amount), exponent);
return amount.toString();
}
@override
final Money amount;
@override
final Money fee;
@override
Future<void> commit() async => await sendTransaction();
@override
String get feeFormatted => "$feeFormattedValue $feeCurrency";
String get feeFormatted => fee.toStringWithSymbol(fractionalDigits: 10);
@override
String get feeFormattedValue => formatFixed(fee, 18, fractionalDigits: 10);
String get feeFormattedValue => fee.toStringWithPrecision(fractionalDigits: 10);
@override
String get hex => bytesToHex(signedTransaction, include0x: true);
@@ -61,7 +61,5 @@ class PendingEVMChainTransaction with PendingTransaction {
}
@override
Future<Map<String, String>> commitUR() {
throw UnimplementedError();
}
Future<Map<String, String>> commitUR() => throw UnimplementedError();
}
+409
View File
@@ -0,0 +1,409 @@
import "package:cw_core/crypto_currency.dart";
import "package:cw_core/currency_groups.dart";
import "package:cw_core/erc20_token.dart";
class RobinhoodTokens {
static List<Erc20Token> get tokens {
final tokens = [
Erc20Token(
name: "Global Dollar",
symbol: "USDG",
contractAddress: "0x5fc5360d0400a0fd4f2af552add042d716f1d168",
decimal: 6,
enabled: true,
iconPath: "assets/images/usdg_icon.png",
groups: const {CurrencyGroups.stablecoin},
),
Erc20Token(
name: "Ethena USDe",
symbol: "USDe",
contractAddress: "0x5d3a1ff2b6bab83b63cd9ad0787074081a52ef34",
decimal: 18,
enabled: true,
groups: const {CurrencyGroups.stablecoin},
),
Erc20Token(
name: "WETH",
symbol: "WETH",
contractAddress: "0x0bd7d308f8e1639fab988df18a8011f41eacad73",
decimal: 18,
enabled: false,
),
Erc20Token(
name: "Apple",
symbol: "AAPL",
contractAddress: "0xaf3d76f1834a1d425780943c99ea8a608f8a93f9",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/apple.webp",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "AMD",
symbol: "AMD",
contractAddress: "0x86923f96303d656e4aa86d9d42d1e57ad2023fdc",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/amd.png",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "Amazon",
symbol: "AMZN",
contractAddress: "0x12f190a9f9d7d37a250758b26824b97ce941bf54",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/amznx.webp",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "ASML Holding NV",
symbol: "ASML",
contractAddress: "0x47f93d52cbec7c6d2cfc080e154002370a60daea",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/asml.png",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "Broadcom",
symbol: "AVGO",
contractAddress: "0x156e175dd063a8ce274c50654ef40e0032b3fbcf",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/avgox.webp",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "Alibaba",
symbol: "BABA",
contractAddress: "0xad25ac6c84d497db898fa1e8387bf6af3532a1c4",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/baba.png",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "Coinbase",
symbol: "COIN",
contractAddress: "0x6330d8c3178a418788df01a47479c0ce7ccf450b",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/coinx.webp",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "Costco",
symbol: "COST",
contractAddress: "0x4ea005168d7f09a7a0ba9d1def21a479950e44c2",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/cost.png",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "Circle Internet Group",
symbol: "CRCL",
contractAddress: "0xdf0992e440dd0be65bd8439b609d6d4366bf1cb5",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/crclx.webp",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "Datadog",
symbol: "DDOG",
contractAddress: "0x27c99fbde9d0d2aa4f4bfb4943f237843ddf6958",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/ddog.png",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "Dell",
symbol: "DELL",
contractAddress: "0x941ae714ec6d8130c7b75d67160ca08f1e7d11dd",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/dell.png",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "Ford Motor",
symbol: "F",
contractAddress: "0x25c288e6d899b9bc30160965ad9644c67e73be0c",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/f.png",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "GameStop",
symbol: "GME",
contractAddress: "0x1b0e319c6a659f002271b69db8a7df2f911c153e",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/gmex.webp",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "Alphabet Class A",
symbol: "GOOGL",
contractAddress: "0x2e0847e8910a9732eb3fb1bb4b70a580adad4fe3",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/googlx.webp",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "Intel",
symbol: "INTC",
contractAddress: "0xc72b96e0e48ecd4dc75e1e45396e26300bc39681",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/intcx.webp",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "Intuit",
symbol: "INTU",
contractAddress: "0x56d23bee5f41a7120170b0c603dae30128e460e9",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/intu.png",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "Eli Lilly",
symbol: "LLY",
contractAddress: "0x8005d266423c7ea827372c9c864491e5786600ea",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/llyx.webp",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "Lululemon",
symbol: "LULU",
contractAddress: "0x4e62068525ab11fe768e29dfd00ef909b9803016",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/lulu.png",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "Meta Platforms",
symbol: "META",
contractAddress: "0xc0d6457c16cc70d6790dd43521c899c87ce02f35",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/metax.webp",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "Marvell Technology",
symbol: "MRVL",
contractAddress: "0x62fd0668e10d8b72339be2dcf7643001688ff13b",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/mrvlx.webp",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "Microsoft",
symbol: "MSFT",
contractAddress: "0xe93237c50d904957cf27e7b1133b510c669c2e74",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/msftx.webp",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "Strategy Inc.",
symbol: "MSTR",
contractAddress: "0xec262a75e413fafd0df80480274532c79d42da09",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/mstrx.webp",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "Micron Technology",
symbol: "MU",
contractAddress: "0xff080c8ce2e5feadaca0da81314ae59d232d4afd",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/mu.png",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "Netflix",
symbol: "NFLX",
contractAddress: "0xe0444ef8bf4ed74f74fd73686e2ddf4c1c5591e8",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/nflxx.webp",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "Nu",
symbol: "NU",
contractAddress: "0x408c14038a04f7bd235329e26d2bf569ee20e250",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/nu.png",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "NVIDIA",
symbol: "NVDA",
contractAddress: "0xd0601ce157db5bdc3162bbac2a2c8af5320d9eec",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/nvdax.webp",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "Oracle",
symbol: "ORCL",
contractAddress: "0xb0992820e760d836549ba69bc7598b4af75dee03",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/orclx.webp",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "Palantir Technologies",
symbol: "PLTR",
contractAddress: "0x894e1ec2d74ffe5aef8dc8a9e84686accb964f2a",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/pltrx.webp",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "Qualcomm",
symbol: "QCOM",
contractAddress: "0x0f17206447090e464c277571124dd2688e48aea9",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/qcom.png",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "Invesco QQQ",
symbol: "QQQ",
contractAddress: "0xd5f3879160bc7c32ebb4dc785f8a4f505888de68",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/qqqx.webp",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "Rivian Automotive",
symbol: "RIVN",
contractAddress: "0xb1bf26c1d20ff267a4f93550d1e0d06ac40a114b",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/rivn.png",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "Shopify",
symbol: "SHOP",
contractAddress: "0xf53f66751b1eff985311b693531e3290f600c410",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/shop.png",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "SoFi Technologies",
symbol: "SOFI",
contractAddress: "0x98e75885157c80992a8d41b696d8c9c6fb30a926",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/sofi.png",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "SPDR S&P 500 ETF Trust",
symbol: "SPY",
contractAddress: "0x117cc2133c37b721f49de2a7a74833232b3b4c0c",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/spyx.webp",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "Tesla",
symbol: "TSLA",
contractAddress: "0x322f0929c4625ed5bad873c95208d54e1c003b2d",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/tslax.webp",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "Taiwan Semiconductor Manufacturing",
symbol: "TSM",
contractAddress: "0x58ffe4a942d3885baa22d7520691f611ef09e7aa",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/tsm.png",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "Take-Two Interactive Software",
symbol: "TTWO",
contractAddress: "0x5e81213613b6b86eab4c6c50d718d34359459786",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/ttwo.png",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "UPS",
symbol: "UPS",
contractAddress: "0xf23250dac154d05bb671cb0d0ebef3c635c79ce2",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/ups.png",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "Workday",
symbol: "WDAY",
contractAddress: "0x82da4646242e1d962e96e932269dc644c94a9caa",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/wday.png",
groups: const {CurrencyGroups.tokenizedStock},
),
Erc20Token(
name: "ExxonMobil Holdings Corporation",
symbol: "XOM",
contractAddress: "0xf9b46d3d1b22199d4d1025a9cedb540a33f1a2d5",
decimal: 18,
enabled: false,
iconPath: "assets/images/stocks/xomx.webp",
groups: const {CurrencyGroups.tokenizedStock},
),
];
return tokens.map((token) {
String? iconPath;
if (token.iconPath?.isEmpty ?? true) {
try {
iconPath = CryptoCurrency.all
.firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase())
.iconPath;
} catch (_) {}
} else {
iconPath = token.iconPath;
}
return Erc20Token.copyWith(token, icon: iconPath, tag: "ROB");
}).toList();
}
}
+3 -4
View File
@@ -1,3 +1,4 @@
import 'package:cw_core/amount/money.dart';
import 'package:cw_core/erc20_token.dart';
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_evm/contract/erc20.dart';
@@ -80,11 +81,9 @@ class USDT0Service {
);
final pendingApproval = await wallet.createApprovalTransaction(
maxUint,
Money(maxUint, token),
adapter,
token,
priority,
EVMChainUtils.getFeeCurrency(wallet.selectedChainId),
useBlinkProtection: useBlinkProtection,
);
await pendingApproval.commit();
@@ -116,7 +115,7 @@ class USDT0Service {
return wallet.createCallDataTransaction(
oftAddress,
encoded.dataHex,
encoded.valueWei,
Money(encoded.valueWei, token),
priority,
token.contractAddress,
amount,
-34
View File
@@ -11,40 +11,6 @@ class EVMChainFormatter {
}
}
/// Parse EVM chain amount to BigInt to avoid integer overflow for large amounts
/// [decimals] defaults to 18 for native ETH/POL, but should be set to token decimals for ERC20 tokens
static BigInt parseEVMChainAmountToBigInt(String amount, {int decimals = evmDecimals}) {
try {
String cleanAmount = amount.replaceAll(',', '.');
bool isNegative = cleanAmount.startsWith('-');
if (isNegative) {
cleanAmount = cleanAmount.substring(1);
}
final parts = cleanAmount.split('.');
String whole = parts[0].isEmpty ? '0' : parts[0];
String fraction = parts.length > 1 ? parts[1] : '';
// 3. Strict Truncation Logic
// If fraction is longer than decimals, strictly cut it off.
// If shorter, pad it with zeros.
if (fraction.length > decimals) {
fraction = fraction.substring(0, decimals);
} else {
fraction = fraction.padRight(decimals, '0');
}
final wholeBigInt = BigInt.parse(whole);
final fractionBigInt = BigInt.parse(fraction);
final multiplier = BigInt.from(10).pow(decimals);
return (wholeBigInt * multiplier) + fractionBigInt;
} catch (_) {
return BigInt.zero;
}
}
static String truncateDecimals(String amount, int decimals) {
final parts = amount.split(".");
+80 -93
View File
@@ -1,26 +1,24 @@
import 'package:cw_core/erc20_token.dart';
import 'package:cw_evm/evm_chain_transaction_priority.dart';
import 'package:web3dart/web3dart.dart' show EtherAmount, EtherUnit;
import "package:cw_evm/evm_chain_transaction_priority.dart";
import "package:web3dart/web3dart.dart" show EtherAmount, EtherUnit;
/// Utility class for chain-specific EVM chain operations
class EVMChainUtils {
static int getTotalPriorityFee(EVMChainTransactionPriority priority, int chainId) {
return switch (chainId) {
1 => _ethereumPriorityFee(priority),
137 => _polygonPriorityFee(priority),
8453 => _basePriorityFee(priority),
56 => _ethereumPriorityFee(priority),
42161 => 0, // Arbitrum doesn't use priority fees
_ => _ethereumPriorityFee(priority),
};
}
static int getTotalPriorityFee(EVMChainTransactionPriority priority, int chainId) =>
switch (chainId) {
1 => _ethereumPriorityFee(priority),
137 => _polygonPriorityFee(priority),
8453 => _basePriorityFee(priority),
56 => _ethereumPriorityFee(priority),
42161 => 0, // Arbitrum doesn't use priority fees
4663 => 0, // Robinhood Chain (Arbitrum Orbit) doesn't use priority fees
_ => _ethereumPriorityFee(priority),
};
static bool hasPriorityFee(int chainId) {
return switch (chainId) {
42161 => false, // Arbitrum doesn't use priority fees
_ => true,
};
}
static bool hasPriorityFee(int chainId) => switch (chainId) {
42161 => false, // Arbitrum doesn't use priority fees
4663 => false, // Robinhood Chain (Arbitrum Orbit) doesn't use priority fees
_ => true,
};
static int computeBufferedMaxFeePerGasWei({
required int? gasBaseFee,
@@ -38,78 +36,68 @@ class EVMChainUtils {
return gasPrice + priorityFeeWei;
}
static String getErc20TokensBoxName(String walletName, int chainId) {
final sanitizedName = walletName.replaceAll(" ", "_");
return switch (chainId) {
1 => "${sanitizedName}_${Erc20Token.ethereumBoxName}",
137 => "${sanitizedName}_${Erc20Token.polygonBoxName}",
8453 => "${sanitizedName}_${Erc20Token.baseBoxName}",
42161 => "${sanitizedName}_${Erc20Token.arbitrumBoxName}",
56 => "${sanitizedName}_${Erc20Token.bscBoxName}",
_ => "${sanitizedName}_${Erc20Token.ethereumBoxName}",
};
}
static String getTransactionHistoryFileName(int chainId) {
return switch (chainId) {
1 => 'transactions.json', // Ethereum
137 => 'polygon_transactions.json',
8453 => 'base_transactions.json',
42161 => 'arbitrum_transactions.json',
56 => 'bsc_transactions.json',
_ => 'transactions_$chainId.json', // Generic format for other chains
};
}
static String getTransactionHistoryFileName(int chainId) => switch (chainId) {
1 => "transactions.json", // Ethereum
137 => "polygon_transactions.json",
8453 => "base_transactions.json",
42161 => "arbitrum_transactions.json",
56 => "bsc_transactions.json",
4663 => "robinhood_transactions.json",
_ => "transactions_$chainId.json", // Generic format for other chains
};
/// Get scan provider preference key for a wallet type
static String getScanProviderPreferenceKey(int chainId) {
return switch (chainId) {
1 => 'use_etherscan',
137 => 'use_polygonscan',
8453 => 'use_basescan',
42161 => 'use_arbiscan',
56 => 'use_bscscan',
_ => 'use_etherscan',
};
}
static String getScanProviderPreferenceKey(int chainId) => switch (chainId) {
1 => "use_etherscan",
137 => "use_polygonscan",
8453 => "use_base_scan",
42161 => "use_arbitrum_scan",
56 => "use_bscscan",
4663 => "use_robinhood_scan",
_ => "use_etherscan",
};
static String getDefaultTokenTag(int chainId) {
return switch (chainId) {
1 => 'ETH',
137 => 'POL',
8453 => 'BASE',
42161 => 'ARB',
56 => 'BSC',
_ => 'ETH',
};
}
static String getDefaultTokenTag(int chainId) => switch (chainId) {
1 => "ETH",
137 => "POL",
8453 => "BASE",
42161 => "ARB",
56 => "BSC",
4663 => "ROB",
_ => "ETH",
};
static String getFeeCurrency(int chainId) {
return switch (chainId) {
1 => 'ETH',
137 => 'POL',
8453 => 'ETH',
42161 => 'ETH',
56 => 'BNB',
_ => 'ETH',
};
}
static String getFeeCurrency(int chainId) => switch (chainId) {
1 => "ETH",
137 => "POL",
8453 => "ETH",
42161 => "ETH",
56 => "BNB",
4663 => "ETH",
_ => "ETH",
};
static String getDefaultTokenSymbol(int chainId) {
return switch (chainId) {
1 => 'ETH',
137 => 'POL',
8453 => 'BASE',
42161 => 'ARBITRUM',
56 => 'BSC',
_ => 'ETH',
};
}
static String getDefaultTokenSymbol(int chainId) => switch (chainId) {
1 => "ETH",
137 => "POL",
8453 => "BASE",
42161 => "ARBITRUM",
56 => "BSC",
4663 => "ETH",
_ => "ETH",
};
static int _ethereumPriorityFee(EVMChainTransactionPriority priority) {
return EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
}
static String? getMoralisChainName(int chainId) => switch (chainId) {
1 => "eth",
137 => "polygon",
8453 => "base",
42161 => "arbitrum",
56 => "bsc",
_ => null,
};
static int _ethereumPriorityFee(EVMChainTransactionPriority priority) =>
EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
// Polygon priority fee calculation (minimum 25 gwei + additional based on priority)
static int _polygonPriorityFee(EVMChainTransactionPriority priority) {
@@ -127,12 +115,11 @@ class EVMChainUtils {
return minPriorityFeeWei + additionalPriorityFee;
}
static int _basePriorityFee(EVMChainTransactionPriority priority) {
return switch (priority) {
EVMChainTransactionPriority.fast => EtherAmount.fromInt(EtherUnit.mwei, 5).getInWei.toInt(),
EVMChainTransactionPriority.medium => EtherAmount.fromInt(EtherUnit.mwei, 3).getInWei.toInt(),
EVMChainTransactionPriority.slow => EtherAmount.fromInt(EtherUnit.mwei, 1).getInWei.toInt(),
_ => EtherAmount.fromInt(EtherUnit.mwei, 1).getInWei.toInt(),
};
}
static int _basePriorityFee(EVMChainTransactionPriority priority) => switch (priority) {
EVMChainTransactionPriority.fast => EtherAmount.fromInt(EtherUnit.mwei, 5).getInWei.toInt(),
EVMChainTransactionPriority.medium =>
EtherAmount.fromInt(EtherUnit.mwei, 3).getInWei.toInt(),
EVMChainTransactionPriority.slow => EtherAmount.fromInt(EtherUnit.mwei, 1).getInWei.toInt(),
_ => EtherAmount.fromInt(EtherUnit.mwei, 1).getInWei.toInt(),
};
}