Add Base Wallet (#2558)

* chore: Create Base package template

* chore: Update configuration scripts and dependencies to include 'base' coin support

* chore: Update pubspec.yaml, adjust SDK constraints, and add new dependencies

* feat: Add support for base wallet type and cryptocurrency in related configurations

* feat: Implement BaseWallet and DefaultBaseTokens classes for managing base cryptocurrency transactions

* feat: Add BaseTransactionHistory and BaseTransactionInfo classes for handling base cryptocurrency transaction data

* feat: Implement BaseClient, BaseWalletService, and BaseMnemonicIsIncorrectException

* feat: Setup base proxy for main package and adjust token class name

* feat: Setup initial base node configuration

* feat: Integrate support for base wallet type across various functions and services

* feat: Add base support to exchange and outputs

* feat: Add base wallet type integration to wallet creation and node management

* feat: Add base support to transaction details and transaction list item

* feat: Implement BaseURI class and add base support across wallet creation and keys view models

* feat: Add support for BaseScan preference in settings store

* feat: Integrate Base support into various workflows and documentation

* feat: Addingbase config to windows and secret key support

* feat: Add base support to settings and address handling

* chore: Move walletTypeToCurrency to another file and adjust across codebase

* feat: Add 'basescan_history' string localization

* feat: Remove Cryptocurrency.base, it doesn't have a base currency

* feat: Set eth as native currency

* feat: Enhance Base support across various sending, exchange, transaction details, fees, dashboard and balance

* feat: Integrate Base wallet support in advanced privacy settings and home settings view models

* feat: Add Base wallet support with icon and integration across providers, default settings migration, preferences, wallet connect, settings and token utilities

* feat: Update currency handling to use baseEth for Base wallet integration across various components

* fix: Update default token symbol to ETH in Base wallet transaction model

* refactor: Change default currency references from BASE to ETH across client, transaction info, and wallet components

* feat: Introduce normalizedIconPath for CryptoCurrency to standardize icon retrieval across wallet components

* fix: Include baseEth as a native token in EVMChainClient for currency handling

* chore: Update base_icon.png

* feat: Add more nodes to base_node_list.yml

* refactor: Update wallet icon retrieval to use normalizedIconPath

* chore: Remove unused cases and cleanup impl.

* feat: Add base proxy generation to configure

* refactor: Update base proxy and generation code in configure.dart

* feat: Add missing wallet and mono images

* fix: Wrong image path for pr and add missing base qr image

* feat: Enhance EVMTransactionErrorFeesHandler to parse new insufficient funds error format and calculate transaction fees in ETH and USD

* feat: Add gas price safety buffer for Base chain transactions to improve fee estimation accuracy, also fixes send all not calculating correctly

* Update model_generator.sh [skip ci]

* Update assets/base_node_list.yml [skip ci]

* Update cw_base/lib/default_base_erc20_tokens.dart [skip ci]

* Review fixes

* fixes

---------

Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>
This commit is contained in:
David Adegoke
2025-10-11 04:24:32 +03:00
committed by GitHub
co-authored by OmarHatem
parent 025fc9154c
commit 2a74317aff
139 changed files with 1824 additions and 193 deletions
@@ -163,6 +163,7 @@ jobs:
echo "const litecoinTestWalletSeeds = '${{ secrets.LITECOIN_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
echo "const bitcoinCashTestWalletSeeds = '${{ secrets.BITCOIN_CASH_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
echo "const polygonTestWalletSeeds = '${{ secrets.POLYGON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
echo "const baseTestWalletSeeds = '${{ secrets.BASE_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
echo "const solanaTestWalletSeeds = '${{ secrets.SOLANA_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
echo "const solanaTestWalletSeeds2 = '${{ secrets.SOLANA_TEST_WALLET_SEEDS2 }}';" >> lib/.secrets.g.dart
echo "const tronTestWalletSeeds = '${{ secrets.TRON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
@@ -176,6 +177,7 @@ jobs:
echo "const litecoinTestWalletReceiveAddress = '${{ secrets.LITECOIN_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
echo "const bitcoinCashTestWalletReceiveAddress = '${{ secrets.BITCOIN_CASH_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
echo "const polygonTestWalletReceiveAddress = '${{ secrets.POLYGON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
echo "const baseTestWalletReceiveAddress = '${{ secrets.BASE_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
echo "const solanaTestWalletReceiveAddress = '${{ secrets.SOLANA_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
echo "const tronTestWalletReceiveAddress = '${{ secrets.TRON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
echo "const nanoTestWalletReceiveAddress = '${{ secrets.NANO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
@@ -20,6 +20,7 @@ jobs:
"cw_mweb"
"cw_nano"
"cw_polygon"
"cw_base"
"cw_solana"
"cw_tron"
"cw_wownero"
@@ -157,6 +157,7 @@ jobs:
echo "const litecoinTestWalletSeeds = '${{ secrets.LITECOIN_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
echo "const bitcoinCashTestWalletSeeds = '${{ secrets.BITCOIN_CASH_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
echo "const polygonTestWalletSeeds = '${{ secrets.POLYGON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
echo "const baseTestWalletSeeds = '${{ secrets.BASE_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
echo "const solanaTestWalletSeeds = '${{ secrets.SOLANA_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
echo "const tronTestWalletSeeds = '${{ secrets.TRON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
echo "const nanoTestWalletSeeds = '${{ secrets.NANO_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
@@ -168,6 +169,7 @@ jobs:
echo "const litecoinTestWalletReceiveAddress = '${{ secrets.LITECOIN_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
echo "const bitcoinCashTestWalletReceiveAddress = '${{ secrets.BITCOIN_CASH_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
echo "const polygonTestWalletReceiveAddress = '${{ secrets.POLYGON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
echo "const baseTestWalletReceiveAddress = '${{ secrets.BASE_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
echo "const solanaTestWalletReceiveAddress = '${{ secrets.SOLANA_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
echo "const tronTestWalletReceiveAddress = '${{ secrets.TRON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
echo "const nanoTestWalletReceiveAddress = '${{ secrets.NANO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
@@ -150,6 +150,7 @@ jobs:
echo "const litecoinTestWalletSeeds = '${{ secrets.LITECOIN_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
echo "const bitcoinCashTestWalletSeeds = '${{ secrets.BITCOIN_CASH_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
echo "const polygonTestWalletSeeds = '${{ secrets.POLYGON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
echo "const baseTestWalletSeeds = '${{ secrets.BASE_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
echo "const solanaTestWalletSeeds = '${{ secrets.SOLANA_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
echo "const tronTestWalletSeeds = '${{ secrets.TRON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
echo "const nanoTestWalletSeeds = '${{ secrets.NANO_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
@@ -162,6 +163,7 @@ jobs:
echo "const litecoinTestWalletReceiveAddress = '${{ secrets.LITECOIN_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
echo "const bitcoinCashTestWalletReceiveAddress = '${{ secrets.BITCOIN_CASH_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
echo "const polygonTestWalletReceiveAddress = '${{ secrets.POLYGON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
echo "const baseTestWalletReceiveAddress = '${{ secrets.BASE_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
echo "const solanaTestWalletReceiveAddress = '${{ secrets.SOLANA_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
echo "const tronTestWalletReceiveAddress = '${{ secrets.TRON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
echo "const nanoTestWalletReceiveAddress = '${{ secrets.NANO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
+1
View File
@@ -141,6 +141,7 @@ lib/wownero/wownero.dart
lib/zano/zano.dart
lib/decred/decred.dart
lib/dogecoin/dogecoin.dart
lib/base/base.dart
ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@2x.png
ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@2x~ipad.png
+1
View File
@@ -25,6 +25,7 @@ Cake Wallet includes support for several cryptocurrencies, including:
* Litecoin (LTC)
* Bitcoin Cash (BCH)
* Polygon (POL)
* Base (BASE)
* Solana (SOL)
* Tron (TRX)
* Nano (XNO)
+1
View File
@@ -23,6 +23,7 @@ analyzer:
lib/tron/cw_tron.dart,
lib/wownero/cw_wownero.dart,
lib/zano/cw_zano.dart,
lib/base/cw_base.dart,
]
language:
strict-casts: true
@@ -106,6 +106,9 @@
<data android:scheme="dogecoin" />
<data android:scheme="dogecoin-wallet" />
<data android:scheme="dogecoin_wallet" />
<data android:scheme="base" />
<data android:scheme="base-wallet" />
<data android:scheme="base_wallet" />
</intent-filter>
<!-- nano-gpt link scheme -->
<intent-filter android:autoVerify="true">
+15
View File
@@ -0,0 +1,15 @@
-
uri: base.nownodes.io
is_default: true
useSSL: true
isEnabledForAutoSwitching: true
-
uri: base.llamarpc.com
useSSL: true
-
uri: base-rpc.publicnode.com
useSSL: true
isEnabledForAutoSwitching: true
-
uri: 1rpc.io/base
useSSL: true
+5
View File
@@ -0,0 +1,5 @@
<svg width="45" height="45" viewBox="0 0 45 45" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="1" y="1" width="43" height="43" rx="6.03125" fill="white"/>
<rect x="1" y="1" width="43" height="43" rx="6.03125" stroke="black" stroke-width="2"/>
<path d="M22.4774 35.4375C29.635 35.4375 35.4375 29.6451 35.4375 22.5C35.4375 15.3548 29.635 9.5625 22.4774 9.5625C15.6866 9.5625 10.1158 14.7762 9.5625 21.4125H26.6927V23.5874H9.5625C10.1158 30.2238 15.6866 35.4375 22.4774 35.4375Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 509 B

+3
View File
@@ -0,0 +1,3 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.9748 30.4C23.9416 30.4 30.4 23.9528 30.4 16C30.4 8.04709 23.9416 1.60001 15.9748 1.60001C8.41642 1.60001 2.2158 7.40309 1.59998 14.7895H20.6666V17.2104H1.59998C2.2158 24.5969 8.41642 30.4 15.9748 30.4Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 334 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

+2 -2
View File
@@ -1,5 +1,5 @@
@echo off
set cw_win_app_config=--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --dogecoin
set cw_win_app_config=--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --dogecoin --base
set cw_root=%cd%
set cw_archive_name=Cake Wallet.zip
set cw_archive_path=%cw_root%\%cw_archive_name%
@@ -24,7 +24,7 @@ IF NOT EXIST "%secrets_file_path%" (
) ELSE (echo === Using previously/already generated secrets file: %secrets_file_path% ===)
echo === Generating mobx models ===
for /d %%i in (cw_core cw_monero cw_bitcoin cw_ethereum cw_evm cw_polygon cw_nano cw_bitcoin_cash cw_solana cw_tron .) do (
for /d %%i in (cw_core cw_monero cw_bitcoin cw_ethereum cw_evm cw_polygon cw_nano cw_bitcoin_cash cw_solana cw_tron cw_base.) do (
cd %%i
call flutter pub get > nul
call dart run build_runner build --delete-conflicting-outputs > nul
+31
View File
@@ -0,0 +1,31 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
build/
+10
View File
@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "fcf2c11572af6f390246c056bc905eca609533a0"
channel: "[user-branch]"
project_type: package
+3
View File
@@ -0,0 +1,3 @@
## 0.0.1
* TODO: Describe initial release.
+1
View File
@@ -0,0 +1 @@
TODO: Add your license here.
+39
View File
@@ -0,0 +1,39 @@
<!--
This README describes the package. If you publish this package to pub.dev,
this README's contents appear on the landing page for your package.
For information about how to write a good package README, see the guide for
[writing package pages](https://dart.dev/tools/pub/writing-package-pages).
For general information about developing packages, see the Dart guide for
[creating packages](https://dart.dev/guides/libraries/create-packages)
and the Flutter guide for
[developing packages and plugins](https://flutter.dev/to/develop-packages).
-->
TODO: Put a short description of the package here that helps potential users
know whether this package might be useful for them.
## Features
TODO: List what your package can do. Maybe include images, gifs, or videos.
## Getting started
TODO: List prerequisites and provide or point to information on how to
start using the package.
## Usage
TODO: Include short and useful examples for package users. Add longer examples
to `/example` folder.
```dart
const like = 'sample';
```
## Additional information
TODO: Tell users more about the package: where to find more information, how to
contribute to the package, how to file issues, what response they can expect
from the package authors, and more.
+4
View File
@@ -0,0 +1,4 @@
include: package:flutter_lints/flutter.yaml
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
+103
View File
@@ -0,0 +1,103 @@
import 'dart:convert';
import 'package:cw_evm/evm_chain_client.dart';
import 'package:cw_evm/.secrets.g.dart' as secrets;
import 'package:cw_evm/evm_chain_transaction_model.dart';
import 'package:flutter/foundation.dart';
import 'package:web3dart/web3dart.dart';
class BaseClient extends EVMChainClient {
@override
Transaction createTransaction({
required EthereumAddress from,
required EthereumAddress to,
required EtherAmount amount,
EtherAmount? maxPriorityFeePerGas,
Uint8List? data,
int? maxGas,
EtherAmount? gasPrice,
EtherAmount? maxFeePerGas,
}) {
EtherAmount? finalGasPrice = gasPrice;
if (gasPrice == null && maxFeePerGas != null) {
// If we have EIP-1559 parameters but no legacy gasPrice, then use maxFeePerGas as gasPrice
finalGasPrice = maxFeePerGas;
}
return Transaction(
from: from,
to: to,
value: amount,
data: data,
maxGas: maxGas,
gasPrice: finalGasPrice,
// maxFeePerGas: maxFeePerGas,
// maxPriorityFeePerGas: maxPriorityFeePerGas,
);
}
@override
Uint8List prepareSignedTransactionForSending(Uint8List signedTransaction) => signedTransaction;
@override
int get chainId => 8453;
@override
Future<List<EVMChainTransactionModel>> fetchTransactions(String address,
{String? contractAddress}) async {
try {
final response = await client.get(Uri.https("api.etherscan.io", "/v2/api", {
"chainid": "$chainId",
"module": "account",
"action": contractAddress != null ? "tokentx" : "txlist",
if (contractAddress != null) "contractaddress": contractAddress,
"address": address,
"apikey": secrets.etherScanApiKey,
}));
final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) {
final res = (jsonResponse['result'] as List);
res.removeWhere((e) => e['value'] == '0');
return res
.map(
(e) => EVMChainTransactionModel.fromJson(e as Map<String, dynamic>, 'ETH'),
)
.toList();
}
return [];
} catch (e) {
return [];
}
}
@override
Future<List<EVMChainTransactionModel>> fetchInternalTransactions(String address) async {
try {
final response = await client.get(Uri.https("api.etherscan.io", "/v2/api", {
"chainid": "$chainId",
"module": "account",
"action": "txlistinternal",
"address": address,
"apikey": secrets.etherScanApiKey,
}));
final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) {
return (jsonResponse['result'] as List)
.map((e) => EVMChainTransactionModel.fromJson(e as Map<String, dynamic>, 'ETH'))
.toList();
}
return [];
} catch (_) {
return [];
}
}
}
@@ -0,0 +1,5 @@
class BaseMnemonicIsIncorrectException implements Exception {
@override
String toString() =>
'Base mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.';
}
+20
View File
@@ -0,0 +1,20 @@
import 'dart:core';
import 'package:cw_evm/evm_chain_transaction_history.dart';
import 'package:cw_evm/evm_chain_transaction_info.dart';
import 'package:cw_base/base_transaction_info.dart';
class BaseTransactionHistory extends EVMChainTransactionHistory {
BaseTransactionHistory({
required super.walletInfo,
required super.password,
required super.encryptionFileUtils,
});
@override
String getTransactionHistoryFileName() => 'base_transactions.json';
@override
EVMChainTransactionInfo getTransactionInfo(Map<String, dynamic> val) =>
BaseTransactionInfo.fromJson(val);
}
+41
View File
@@ -0,0 +1,41 @@
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_evm/evm_chain_transaction_info.dart';
class BaseTransactionInfo extends EVMChainTransactionInfo {
BaseTransactionInfo({
required super.id,
required super.height,
required super.ethAmount,
required super.ethFee,
required super.tokenSymbol,
required super.direction,
required super.isPending,
required super.date,
required super.confirmations,
required super.to,
required super.from,
super.contractAddress,
super.exponent,
});
factory BaseTransactionInfo.fromJson(Map<String, dynamic> data) {
return BaseTransactionInfo(
id: data['id'] as String,
height: data['height'] as int,
ethAmount: BigInt.parse(data['amount']),
exponent: data['exponent'] as int,
ethFee: BigInt.parse(data['fee']),
direction: parseTransactionDirectionFromInt(data['direction'] as int),
date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
isPending: data['isPending'] as bool,
confirmations: data['confirmations'] as int,
tokenSymbol: data['tokenSymbol'] as String,
to: data['to'],
from: data['from'],
contractAddress: data['contractAddress'],
);
}
@override
String get feeCurrency => 'ETH';
}
+165
View File
@@ -0,0 +1,165 @@
import 'dart:convert';
import 'package:cw_base/base_client.dart';
import 'package:cw_base/base_transaction_history.dart';
import 'package:cw_base/base_transaction_info.dart';
import 'package:cw_base/default_base_erc20_tokens.dart';
import 'package:cw_core/cake_hive.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/encryption_file_utils.dart';
import 'package:cw_core/erc20_token.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_keys_file.dart';
import 'package:cw_evm/evm_chain_transaction_history.dart';
import 'package:cw_evm/evm_chain_transaction_info.dart';
import 'package:cw_evm/evm_chain_transaction_model.dart';
import 'package:cw_evm/evm_chain_wallet.dart';
import 'package:cw_evm/evm_erc20_balance.dart';
class BaseWallet extends EVMChainWallet {
BaseWallet({
required super.walletInfo,
required super.password,
super.mnemonic,
super.initialBalance,
super.privateKey,
required super.client,
required super.encryptionFileUtils,
super.passphrase,
}) : super(nativeCurrency: CryptoCurrency.baseEth);
@override
Future<void> initErc20TokensBox() async {
final boxName = "${walletInfo.name.replaceAll(" ", "_")}_ ${Erc20Token.baseBoxName}";
evmChainErc20TokensBox = await CakeHive.openBox<Erc20Token>(boxName);
}
@override
void addInitialTokens([bool isMigration = false]) {
final initialErc20Tokens = DefaultBaseErc20Tokens().initialBaseErc20Tokens;
for (final token in initialErc20Tokens) {
if (evmChainErc20TokensBox.containsKey(token.contractAddress)) {
final existingToken = evmChainErc20TokensBox.get(token.contractAddress);
if (existingToken?.tag != token.tag) {
evmChainErc20TokensBox.put(token.contractAddress, token);
}
} else {
if (isMigration) token.enabled = false;
evmChainErc20TokensBox.put(token.contractAddress, token);
}
}
}
@override
List<String> get getDefaultTokenContractAddresses =>
DefaultBaseErc20Tokens().initialBaseErc20Tokens.map((e) => e.contractAddress).toList();
@override
Future<bool> checkIfScanProviderIsEnabled() async {
return (await sharedPrefs.future).getBool("use_basescan") ?? true;
}
@override
String getTransactionHistoryFileName() => 'base_transactions.json';
@override
Erc20Token createNewErc20TokenObject(Erc20Token token, String? iconPath) {
return Erc20Token(
name: token.name,
symbol: token.symbol,
contractAddress: token.contractAddress,
decimal: token.decimal,
enabled: token.enabled,
tag: token.tag ?? 'ETH',
iconPath: iconPath,
isPotentialScam: token.isPotentialScam,
);
}
@override
EVMChainTransactionInfo getTransactionInfo(
EVMChainTransactionModel transactionModel, String address) {
final model = BaseTransactionInfo(
id: transactionModel.hash,
height: transactionModel.blockNumber,
ethAmount: transactionModel.amount,
direction: transactionModel.from == address
? TransactionDirection.outgoing
: TransactionDirection.incoming,
isPending: false,
date: transactionModel.date,
confirmations: transactionModel.confirmations,
ethFee: BigInt.from(transactionModel.gasUsed) * transactionModel.gasPrice,
exponent: transactionModel.tokenDecimal ?? 18,
tokenSymbol: transactionModel.tokenSymbol ?? "ETH",
to: transactionModel.to,
from: transactionModel.from,
contractAddress: transactionModel.contractAddress,
);
return model;
}
@override
EVMChainTransactionHistory setUpTransactionHistory(
WalletInfo walletInfo, String password, EncryptionFileUtils encryptionFileUtils) {
return BaseTransactionHistory(
walletInfo: walletInfo,
password: password,
encryptionFileUtils: encryptionFileUtils,
);
}
static Future<BaseWallet> open({
required String name,
required String password,
required WalletInfo walletInfo,
required EncryptionFileUtils encryptionFileUtils,
}) async {
final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
final path = await pathForWallet(name: name, type: walletInfo.type);
Map<String, dynamic>? data;
try {
final jsonSource = await encryptionFileUtils.read(path: path, password: password);
data = json.decode(jsonSource) as Map<String, dynamic>;
} catch (e) {
if (!hasKeysFile) rethrow;
}
final balance = EVMChainERC20Balance.fromJSON(data?['balance'] as String?) ??
EVMChainERC20Balance(BigInt.zero);
final WalletKeysData keysData;
// Migrate wallet from the old scheme to then new .keys file scheme
if (!hasKeysFile) {
final mnemonic = data!['mnemonic'] as String?;
final privateKey = data['private_key'] as String?;
final passphrase = data['passphrase'] as String?;
keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey, passphrase: passphrase);
} else {
keysData = await WalletKeysFile.readKeysFile(
name,
walletInfo.type,
password,
encryptionFileUtils,
);
}
return BaseWallet(
walletInfo: walletInfo,
password: password,
mnemonic: keysData.mnemonic,
privateKey: keysData.privateKey,
passphrase: keysData.passphrase,
initialBalance: balance,
client: BaseClient(),
encryptionFileUtils: encryptionFileUtils,
);
}
}
+163
View File
@@ -0,0 +1,163 @@
import 'package:bip39/bip39.dart' as bip39;
import 'package:cw_core/encryption_file_utils.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
import 'package:cw_evm/evm_chain_wallet_service.dart';
import 'package:cw_base/base_wallet.dart';
import 'package:cw_base/base_client.dart';
import 'package:cw_base/base_mnemonics_exception.dart';
class BaseWalletService extends EVMChainWalletService<BaseWallet> {
BaseWalletService(
super.walletInfoSource,
super.isDirect, {
required this.client,
});
late BaseClient client;
@override
WalletType getType() => WalletType.base;
@override
Future<BaseWallet> create(EVMChainNewWalletCredentials credentials, {bool? isTestnet}) async {
final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
final mnemonic = credentials.mnemonic ?? bip39.generateMnemonic(strength: strength);
final wallet = BaseWallet(
walletInfo: credentials.walletInfo!,
mnemonic: mnemonic,
password: credentials.password!,
passphrase: credentials.passphrase,
client: client,
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
);
await wallet.init();
wallet.addInitialTokens();
await wallet.save();
return wallet;
}
@override
Future<BaseWallet> openWallet(String name, String password) async {
final walletInfo =
walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
try {
final wallet = await BaseWallet.open(
name: name,
password: password,
walletInfo: walletInfo,
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
);
await wallet.init();
wallet.addInitialTokens(true);
await wallet.save();
saveBackup(name);
return wallet;
} catch (_) {
await restoreWalletFilesFromBackup(name);
final wallet = await BaseWallet.open(
name: name,
password: password,
walletInfo: walletInfo,
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
);
await wallet.init();
wallet.addInitialTokens(true);
await wallet.save();
return wallet;
}
}
@override
Future<BaseWallet> restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials,
{bool? isTestnet}) async {
final wallet = BaseWallet(
password: credentials.password!,
privateKey: credentials.privateKey,
walletInfo: credentials.walletInfo!,
client: client,
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
);
await wallet.init();
wallet.addInitialTokens();
await wallet.save();
return wallet;
}
@override
Future<BaseWallet> restoreFromHardwareWallet(
EVMChainRestoreWalletFromHardware credentials) async {
credentials.walletInfo!.derivationInfo = DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/44'/60'/${credentials.hwAccountData.accountIndex}'/0/0");
credentials.walletInfo!.hardwareWalletType = credentials.hardwareWalletType;
credentials.walletInfo!.address = credentials.hwAccountData.address;
final wallet = BaseWallet(
walletInfo: credentials.walletInfo!,
password: credentials.password!,
client: client,
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
);
await wallet.init();
wallet.addInitialTokens();
await wallet.save();
return wallet;
}
@override
Future<BaseWallet> restoreFromSeed(EVMChainRestoreWalletFromSeedCredentials credentials,
{bool? isTestnet}) async {
if (!bip39.validateMnemonic(credentials.mnemonic)) {
throw BaseMnemonicIsIncorrectException();
}
final wallet = BaseWallet(
password: credentials.password!,
mnemonic: credentials.mnemonic,
walletInfo: credentials.walletInfo!,
passphrase: credentials.passphrase,
client: client,
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
);
await wallet.init();
wallet.addInitialTokens();
await wallet.save();
return wallet;
}
@override
Future<void> rename(String currentName, String password, String newName) async {
final currentWalletInfo = walletInfoSource.values
.firstWhere((info) => info.id == WalletBase.idFor(currentName, getType()));
final currentWallet = await BaseWallet.open(
password: password,
name: currentName,
walletInfo: currentWalletInfo,
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
);
await currentWallet.renameWalletFiles(newName);
await saveBackup(newName);
final newWalletInfo = currentWalletInfo;
newWalletInfo.id = WalletBase.idFor(newName, getType());
newWalletInfo.name = newName;
await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
}
}
+5
View File
@@ -0,0 +1,5 @@
/// A Calculator.
class Calculator {
/// Returns [value] plus 1.
int addOne(int value) => value + 1;
}
@@ -0,0 +1,67 @@
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/erc20_token.dart';
class DefaultBaseErc20Tokens {
final List<Erc20Token> _defaultTokens = [
Erc20Token(
name: "USD Coin",
symbol: "USDC",
contractAddress: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
decimal: 6,
enabled: true,
),
Erc20Token(
name: "USDe",
symbol: "USDe",
contractAddress: "0x5d3a1Ff2b6BAb83b63cd9AD0787074081a52ef34",
decimal: 18,
enabled: true,
),
Erc20Token(
name: "Dai",
symbol: "DAI",
contractAddress: "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb",
decimal: 18,
enabled: true,
),
Erc20Token(
name: "Bridged Tether USD",
symbol: "USDT",
contractAddress: "0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2",
decimal: 6,
enabled: true,
),
Erc20Token(
name: "Wrapped Ether",
symbol: "WETH",
contractAddress: "0x4200000000000000000000000000000000000006",
decimal: 18,
enabled: false,
),
Erc20Token(
name: "Wrapped BTC",
symbol: "WBTC",
contractAddress: "0x0555E30da8f98308EdB960aa94C0Db47230d2B9c",
decimal: 8,
enabled: false,
),
Erc20Token(
name: "SPX6900",
symbol: "SPX",
contractAddress: "0x50dA645f148798F68EF2d7dB7C1CB22A6819bb2C",
decimal: 8,
enabled: false,
),
];
List<Erc20Token> get initialBaseErc20Tokens => _defaultTokens.map((token) {
String? iconPath;
try {
iconPath = CryptoCurrency.all
.firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase())
.iconPath;
} catch (_) {}
return Erc20Token.copyWith(token, iconPath, 'BASE');
}).toList();
}
+73
View File
@@ -0,0 +1,73 @@
name: cw_base
description: "Base package for Cake Wallet"
version: 0.0.1
publish_to: none
homepage: https://cakewallet.com
environment:
sdk: '>=3.0.6 <4.0.0'
flutter: ">=1.17.0"
dependencies:
flutter:
sdk: flutter
cw_core:
path: ../cw_core
cw_ethereum:
path: ../cw_ethereum
cw_evm:
path: ../cw_evm
web3dart: ^2.7.1
hive: ^2.2.3
bip39: ^1.0.6
collection: ^1.17.1
dependency_overrides:
web3dart:
git:
url: https://github.com/cake-tech/web3dart.git
ref: cake
watcher: ^1.1.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
build_runner: ^2.4.15
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# To add assets to your package, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
#
# For details regarding assets in packages, see
# https://flutter.dev/to/asset-from-package
#
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images
# To add custom fonts to your package, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts in packages, see
# https://flutter.dev/to/font-from-package
+12
View File
@@ -0,0 +1,12 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:cw_base/cw_base.dart';
void main() {
test('adds one to input values', () {
final calculator = Calculator();
expect(calculator.addOne(2), 3);
expect(calculator.addOne(-7), -6);
expect(calculator.addOne(0), 1);
});
}
@@ -5,6 +5,7 @@ import 'package:cw_bitcoin/address_from_output.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
import 'package:cw_bitcoin/bitcoin_unspent.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/format_amount.dart';
+2
View File
@@ -115,6 +115,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
CryptoCurrency.flip,
CryptoCurrency.ndeps,
CryptoCurrency.deps,
CryptoCurrency.baseEth,
];
static const havenCurrencies = [
@@ -241,6 +242,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
static const deps = CryptoCurrency(title: 'DEPS', tag: 'ETH', fullName: 'Decentralized Euro Protocol Share', raw: 101, name: 'deps', iconPath: 'assets/images/deps_icon.png', decimals: 18);
static const kbtc = CryptoCurrency(title: 'KBTC', tag: 'ETH', fullName: 'Kraken Wrapped Bitcoin', raw: 102, name: 'kbtc', iconPath: 'assets/images/kbtc_icon.png', decimals: 8);
static const cbbtc = CryptoCurrency(title: 'CBBTC', tag: 'ETH', fullName: 'Coinbase Wrapped BTC', raw: 103, name: 'cbbtc', iconPath: 'assets/images/cbbtc_icon.png', decimals: 8);
static const baseEth = CryptoCurrency(title: 'ETH', tag: 'BASE', fullName: 'Ethereum', raw: 104, name: 'baseth', iconPath: 'assets/images/eth_icon.png', decimals: 18);
static final Map<int, CryptoCurrency> _rawCurrencyMap =
[...all, ...havenCurrencies].fold<Map<int, CryptoCurrency>>(<int, CryptoCurrency>{}, (acc, item) {
+13 -37
View File
@@ -1,21 +1,23 @@
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/wallet_type.dart';
CryptoCurrency currencyForWalletType(WalletType type, {bool? isTestnet}) {
CryptoCurrency walletTypeToCryptoCurrency(WalletType type, {bool isTestnet = false}) {
switch (type) {
case WalletType.monero:
return CryptoCurrency.xmr;
case WalletType.bitcoin:
if (isTestnet == true) {
if (isTestnet) {
return CryptoCurrency.tbtc;
}
return CryptoCurrency.btc;
case WalletType.monero:
return CryptoCurrency.xmr;
case WalletType.litecoin:
return CryptoCurrency.ltc;
case WalletType.haven:
return CryptoCurrency.xhv;
case WalletType.ethereum:
return CryptoCurrency.eth;
case WalletType.base:
return CryptoCurrency.baseEth;
case WalletType.bitcoinCash:
return CryptoCurrency.bch;
case WalletType.nano:
@@ -38,41 +40,15 @@ CryptoCurrency currencyForWalletType(WalletType type, {bool? isTestnet}) {
return CryptoCurrency.doge;
case WalletType.none:
throw Exception(
'Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType');
'Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency');
}
}
WalletType? walletTypeForCurrency(CryptoCurrency currency) {
switch (currency) {
case CryptoCurrency.btc:
return WalletType.bitcoin;
case CryptoCurrency.xmr:
return WalletType.monero;
case CryptoCurrency.ltc:
return WalletType.litecoin;
case CryptoCurrency.xhv:
return WalletType.haven;
case CryptoCurrency.eth:
return WalletType.ethereum;
case CryptoCurrency.bch:
return WalletType.bitcoinCash;
case CryptoCurrency.nano:
return WalletType.nano;
case CryptoCurrency.banano:
return WalletType.banano;
case CryptoCurrency.maticpoly:
return WalletType.polygon;
case CryptoCurrency.sol:
return WalletType.solana;
case CryptoCurrency.trx:
return WalletType.tron;
case CryptoCurrency.wow:
return WalletType.wownero;
case CryptoCurrency.zano:
return WalletType.zano;
case CryptoCurrency.dcr:
return WalletType.decred;
default:
return null;
extension CryptoCurrencyForWalletType on CryptoCurrency {
String? get normalizedIconPath {
if (this == CryptoCurrency.baseEth) {
return 'assets/images/base_icon.png';
}
return iconPath;
}
}
+1
View File
@@ -70,6 +70,7 @@ class Erc20Token extends CryptoCurrency with HiveObjectMixin {
static const boxName = 'Erc20Tokens';
static const ethereumBoxName = 'EthereumErc20Tokens';
static const polygonBoxName = 'PolygonErc20Tokens';
static const baseBoxName = 'BaseErc20Tokens';
@override
bool operator ==(other) =>
+2
View File
@@ -106,6 +106,7 @@ class Node extends HiveObject with Keyable {
case WalletType.banano:
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.base:
case WalletType.solana:
case WalletType.tron:
case WalletType.zano:
@@ -169,6 +170,7 @@ class Node extends HiveObject with Keyable {
case WalletType.bitcoinCash:
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.base:
case WalletType.solana:
case WalletType.tron:
case WalletType.dogecoin:
+1 -1
View File
@@ -24,7 +24,7 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
WalletType get type => walletInfo.type;
CryptoCurrency get currency => currencyForWalletType(type, isTestnet: isTestnet);
CryptoCurrency get currency => walletTypeToCryptoCurrency(type, isTestnet: isTestnet);
String get id => walletInfo.id;
+16 -44
View File
@@ -19,6 +19,7 @@ const walletTypes = [
WalletType.zano,
WalletType.decred,
WalletType.dogecoin,
WalletType.base,
];
@HiveType(typeId: WALLET_TYPE_TYPE_ID)
@@ -69,7 +70,10 @@ enum WalletType {
decred,
@HiveField(15)
dogecoin
dogecoin,
@HiveField(16)
base,
}
int serializeToInt(WalletType type) {
@@ -104,6 +108,8 @@ int serializeToInt(WalletType type) {
return 13;
case WalletType.dogecoin:
return 14;
case WalletType.base:
return 15;
case WalletType.none:
return -1;
}
@@ -141,9 +147,10 @@ WalletType deserializeFromInt(int raw) {
return WalletType.decred;
case 14:
return WalletType.dogecoin;
case 15:
return WalletType.base;
default:
throw Exception(
'Unexpected token: $raw for WalletType deserializeFromInt');
throw Exception('Unexpected token: $raw for WalletType deserializeFromInt');
}
}
@@ -179,6 +186,8 @@ String walletTypeToString(WalletType type) {
return 'Decred';
case WalletType.dogecoin:
return 'Dogecoin';
case WalletType.base:
return 'Base';
case WalletType.none:
return '';
}
@@ -216,52 +225,13 @@ String walletTypeToDisplayName(WalletType type) {
return 'Decred (DCR)';
case WalletType.dogecoin:
return 'Dogecoin (DOGE)';
case WalletType.base:
return 'Base (BASE)';
case WalletType.none:
return '';
}
}
CryptoCurrency walletTypeToCryptoCurrency(WalletType type, {bool isTestnet = false}) {
switch (type) {
case WalletType.monero:
return CryptoCurrency.xmr;
case WalletType.bitcoin:
if (isTestnet) {
return CryptoCurrency.tbtc;
}
return CryptoCurrency.btc;
case WalletType.litecoin:
return CryptoCurrency.ltc;
case WalletType.haven:
return CryptoCurrency.xhv;
case WalletType.ethereum:
return CryptoCurrency.eth;
case WalletType.bitcoinCash:
return CryptoCurrency.bch;
case WalletType.nano:
return CryptoCurrency.nano;
case WalletType.banano:
return CryptoCurrency.banano;
case WalletType.polygon:
return CryptoCurrency.maticpoly;
case WalletType.solana:
return CryptoCurrency.sol;
case WalletType.tron:
return CryptoCurrency.trx;
case WalletType.wownero:
return CryptoCurrency.wow;
case WalletType.zano:
return CryptoCurrency.zano;
case WalletType.decred:
return CryptoCurrency.dcr;
case WalletType.dogecoin:
return CryptoCurrency.doge;
case WalletType.none:
throw Exception(
'Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency');
}
}
WalletType? cryptoCurrencyToWalletType(CryptoCurrency type) {
switch (type) {
case CryptoCurrency.xmr:
@@ -294,6 +264,8 @@ WalletType? cryptoCurrencyToWalletType(CryptoCurrency type) {
return WalletType.decred;
case CryptoCurrency.doge:
return WalletType.dogecoin;
case CryptoCurrency.baseEth:
return WalletType.base;
default:
return null;
}
+1
View File
@@ -1,3 +1,4 @@
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/format_amount.dart';
+12 -12
View File
@@ -39,15 +39,14 @@ abstract class EVMChainClient {
Uri? rpcUri;
bool isModifiedNodeUri = false;
if (node.uriRaw == 'eth.nownodes.io' || node.uriRaw == 'matic.nownodes.io') {
if (node.uriRaw.contains('nownodes.io')) {
isModifiedNodeUri = true;
String nowNodeApiKey = secrets.nowNodesApiKey;
rpcUri = Uri.https(node.uriRaw, '/$nowNodeApiKey');
}
_client =
Web3Client(isModifiedNodeUri ? rpcUri!.toString() : node.uri.toString(), client);
_client = Web3Client(isModifiedNodeUri ? rpcUri!.toString() : node.uri.toString(), client);
return true;
} catch (e) {
@@ -127,10 +126,11 @@ abstract class EVMChainClient {
final gasEstimate = await _client!.estimateGas(
sender: senderAddress,
to: EthereumAddress.fromHex(contractAddress),
data: data ?? transfer.encodeCall([
toAddress,
value.getInWei,
]),
data: data ??
transfer.encodeCall([
toAddress,
value.getInWei,
]),
);
return gasEstimate.toInt();
@@ -171,10 +171,10 @@ abstract class EVMChainClient {
int? gasPrice,
}) async {
assert(currency == CryptoCurrency.eth ||
currency == CryptoCurrency.maticpoly ||
currency == CryptoCurrency.maticpoly || currency == CryptoCurrency.baseEth ||
contractAddress != null);
bool isNativeToken = currency == CryptoCurrency.eth || currency == CryptoCurrency.maticpoly;
bool isNativeToken = currency == CryptoCurrency.eth || currency == CryptoCurrency.maticpoly || currency == CryptoCurrency.baseEth;
final Transaction transaction = createTransaction(
from: privateKey.address,
@@ -233,7 +233,6 @@ abstract class EVMChainClient {
required String contractAddress,
int? gasPrice,
}) async {
final Transaction transaction = createTransaction(
from: privateKey.address,
to: EthereumAddress.fromHex(contractAddress),
@@ -264,7 +263,8 @@ abstract class EVMChainClient {
feeCurrency: feeCurrency,
sendTransaction: () => sendTransaction(signedTransaction),
exponent: exponent,
isInfiniteApproval: amount.toRadixString(16) == 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
isInfiniteApproval: amount.toRadixString(16) ==
'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff',
);
}
@@ -342,7 +342,7 @@ abstract class EVMChainClient {
final erc20 = ERC20(address: EthereumAddress.fromHex(contractAddress), client: _client!);
final balance = await erc20.balanceOf(userAddress);
int exponent = (await erc20.decimals()).toInt();
int exponent = (await erc20.decimals()).toInt();
return EVMChainERC20Balance(balance, exponent: exponent);
} on RangeError catch (_) {
+45 -17
View File
@@ -171,25 +171,29 @@ abstract class EVMChainWalletBase
try {
// Check native balance
await _client.getBalance(_evmChainPrivateKey.address, throwOnError: true);
// Check USDC token balance
String usdcContractAddress;
if (_client.chainId == 1) {
// Ethereum mainnet
usdcContractAddress = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48";
} else if (_client.chainId == 137) {
// Polygon mainnet
usdcContractAddress = "0x2791bca1f2de4661ed88a30c99a7a9449aa84174";
} else {
// If we are not on Ethereum or Polygon, we skip ERC20 token check
return true;
switch (_client.chainId) {
case 1:
usdcContractAddress = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48";
break;
case 137:
usdcContractAddress = "0x2791bca1f2de4661ed88a30c99a7a9449aa84174";
break;
case 8453:
usdcContractAddress = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913";
break;
default:
return true;
}
await _client.fetchERC20Balances(
_evmChainPrivateKey.address,
usdcContractAddress,
);
return true;
} catch (e) {
return false;
@@ -542,6 +546,32 @@ abstract class EVMChainWalletBase
maxFeePerGasForTransaction = gasFeesModel.maxFeePerGas;
if (output.sendAll && transactionCurrency is! Erc20Token) {
if (_client.chainId == 8453) {
// Add a safety buffer for Base chain to account for gas price fluctuations
// Use 1% buffer or minimum 1000 wei (0.000001 ETH), whichever is higher
final gasBufferPercent =
estimatedFeesForTransaction * BigInt.from(101) ~/ BigInt.from(100);
final gasBufferMin = estimatedFeesForTransaction + BigInt.from(1000);
final gasBuffer = gasBufferPercent > gasBufferMin ? gasBufferPercent : gasBufferMin;
totalAmount = (currencyBalance.balance - gasBuffer);
// Re-estimate gas with the correct amount to get more accurate gas estimation
final refinedGasFeesModel = await calculateActualEstimatedFeeForCreateTransaction(
amount: totalAmount,
receivingAddressHex: toAddress,
priority: _credentials.priority!,
contractAddress: contractAddress,
);
// Use the higher of the two gas estimations to be safe
final refinedGasFee = BigInt.from(refinedGasFeesModel.estimatedGasFee);
estimatedFeesForTransaction = refinedGasFee > gasBuffer ? refinedGasFee : gasBuffer;
estimatedGasUnitsForTransaction = refinedGasFeesModel.estimatedGasUnits;
maxFeePerGasForTransaction = refinedGasFeesModel.maxFeePerGas;
}
// Final amount calculation with the higher gas estimation
totalAmount = (currencyBalance.balance - estimatedFeesForTransaction);
}
@@ -569,11 +599,7 @@ abstract class EVMChainWalletBase
gasFee: estimatedFeesForTransaction,
priority: _credentials.priority!,
currency: transactionCurrency,
feeCurrency: switch (_client.chainId) {
1 => "ETH",
137 => "POL",
_ => ""
},
feeCurrency: switch (_client.chainId) { 1 => "ETH", 137 => "POL", 8453 => "ETH", _ => "ETH" },
maxFeePerGas: maxFeePerGasForTransaction,
exponent: exponent,
contractAddress:
@@ -890,6 +916,8 @@ abstract class EVMChainWalletBase
/// EtherScan for Ethereum.
///
/// PolygonScan for Polygon.
///
/// BaseScan for Base.
void updateScanProviderUsageState(bool isEnabled) {
if (isEnabled) {
_updateTransactions();
@@ -3,6 +3,7 @@ import 'dart:io';
import 'package:cake_wallet/entities/seed_type.dart';
import 'package:cake_wallet/reactions/wallet_utils.dart';
import 'package:cake_wallet/wallet_types.g.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
@@ -109,7 +110,7 @@ class CommonTestFlows {
void confirmAllAvailableWalletTypeIconsDisplayCorrectly() {
for (var walletType in availableWalletTypes) {
final imageUrl = walletTypeToCryptoCurrency(walletType).iconPath;
final imageUrl = walletTypeToCryptoCurrency(walletType).normalizedIconPath;
final walletIconFinder = find.image(
Image.asset(
@@ -353,6 +354,8 @@ class CommonTestFlows {
return secrets.polygonTestWalletSeeds;
case WalletType.solana:
return secrets.solanaTestWalletSeeds;
case WalletType.base:
return secrets.baseTestWalletSeeds;
case WalletType.tron:
return secrets.tronTestWalletSeeds;
case WalletType.nano:
@@ -387,6 +390,8 @@ class CommonTestFlows {
return secrets.bitcoinCashTestWalletReceiveAddress;
case WalletType.polygon:
return secrets.polygonTestWalletReceiveAddress;
case WalletType.base:
return secrets.baseTestWalletReceiveAddress;
case WalletType.solana:
return secrets.solanaTestWalletReceiveAddress;
case WalletType.tron:
+20
View File
@@ -302,6 +302,26 @@
<string>dogecoin-wallet</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>base</string>
<key>CFBundleURLSchemes</key>
<array>
<string>base</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>CFBundleURLName</key>
<string>base-wallet</string>
<key>CFBundleURLSchemes</key>
<array>
<string>base-wallet</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
+238
View File
@@ -0,0 +1,238 @@
part of 'base.dart';
class CWBase extends Base {
@override
List<String> getBaseWordList(String language) => EVMChainMnemonics.englishWordlist;
WalletService createBaseWalletService(Box<WalletInfo> walletInfoSource, bool isDirect) =>
BaseWalletService(walletInfoSource, isDirect, client: BaseClient());
@override
WalletCredentials createBaseNewWalletCredentials({
required String name,
String? mnemonic,
WalletInfo? walletInfo,
String? password,
String? passphrase,
}) =>
EVMChainNewWalletCredentials(
name: name,
walletInfo: walletInfo,
password: password,
mnemonic: mnemonic,
passphrase: passphrase,
);
@override
WalletCredentials createBaseRestoreWalletFromSeedCredentials({
required String name,
required String mnemonic,
required String password,
String? passphrase,
}) =>
EVMChainRestoreWalletFromSeedCredentials(
name: name,
password: password,
mnemonic: mnemonic,
passphrase: passphrase,
);
@override
WalletCredentials createBaseRestoreWalletFromPrivateKey({
required String name,
required String privateKey,
required String password,
}) =>
EVMChainRestoreWalletFromPrivateKey(name: name, password: password, privateKey: privateKey);
@override
WalletCredentials createBaseHardwareWalletCredentials({
required String name,
required HardwareAccountData hwAccountData,
WalletInfo? walletInfo,
}) =>
EVMChainRestoreWalletFromHardware(
name: name,
hwAccountData: hwAccountData,
walletInfo: walletInfo,
);
@override
String getAddress(WalletBase wallet) => (wallet as BaseWallet).walletAddresses.address;
@override
String getPrivateKey(WalletBase wallet) {
final privateKeyHolder = (wallet as BaseWallet).evmChainPrivateKey;
if (privateKeyHolder is EthPrivateKey) return bytesToHex(privateKeyHolder.privateKey);
return "";
}
@override
String getPublicKey(WalletBase wallet) {
final privateKeyInUnitInt = (wallet as BaseWallet).evmChainPrivateKey;
return privateKeyInUnitInt.address.hex;
}
@override
TransactionPriority getDefaultTransactionPriority() => EVMChainTransactionPriority.medium;
@override
TransactionPriority getBaseTransactionPrioritySlow() => EVMChainTransactionPriority.slow;
@override
List<TransactionPriority> getTransactionPriorities() => EVMChainTransactionPriority.all;
@override
TransactionPriority deserializeBaseTransactionPriority(int raw) =>
EVMChainTransactionPriority.deserialize(raw: raw);
Object createBaseTransactionCredentials(
List<Output> outputs, {
required TransactionPriority priority,
required CryptoCurrency currency,
int? feeRate,
}) =>
EVMChainTransactionCredentials(
outputs
.map(
(out) => OutputInfo(
fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmount,
address: out.address,
note: out.note,
sendAll: out.sendAll,
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
formattedCryptoAmount: out.formattedCryptoAmount,
),
)
.toList(),
priority: priority as EVMChainTransactionPriority,
currency: currency,
feeRate: feeRate,
);
Object createBaseTransactionCredentialsRaw(
List<OutputInfo> outputs, {
TransactionPriority? priority,
required CryptoCurrency currency,
required int feeRate,
}) =>
EVMChainTransactionCredentials(
outputs,
priority: priority as EVMChainTransactionPriority?,
currency: currency,
feeRate: feeRate,
);
@override
int formatterBaseParseAmount(String amount) => EVMChainFormatter.parseEVMChainAmount(amount);
@override
double formatterBaseAmountToDouble({
TransactionInfo? transaction,
BigInt? amount,
int exponent = 18,
}) {
assert(transaction != null || amount != null);
if (transaction != null) {
transaction as EVMChainTransactionInfo;
return transaction.ethAmount / BigInt.from(10).pow(transaction.exponent);
} else {
return (amount!) / BigInt.from(10).pow(exponent);
}
}
@override
List<Erc20Token> getERC20Currencies(WalletBase wallet) => (wallet as BaseWallet).erc20Currencies;
@override
Future<void> addErc20Token(WalletBase wallet, CryptoCurrency token) =>
(wallet as BaseWallet).addErc20Token(token as Erc20Token);
@override
Future<void> deleteErc20Token(WalletBase wallet, CryptoCurrency token) =>
(wallet as BaseWallet).deleteErc20Token(token as Erc20Token);
@override
Future<void> removeTokenTransactionsInHistory(WalletBase wallet, CryptoCurrency token) =>
(wallet as BaseWallet).removeTokenTransactionsInHistory(token as Erc20Token);
@override
Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress) =>
(wallet as BaseWallet).getErc20Token(contractAddress, 'base');
@override
CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) {
transaction as EVMChainTransactionInfo;
if (transaction.tokenSymbol == CryptoCurrency.baseEth.title ||
transaction.tokenSymbol == "BASE") {
return CryptoCurrency.baseEth;
}
wallet as BaseWallet;
return wallet.erc20Currencies.firstWhere(
(element) =>
transaction.contractAddress?.toLowerCase() == element.contractAddress?.toLowerCase(),
);
}
@override
void updateBaseScanUsageState(WalletBase wallet, bool isEnabled) =>
(wallet as BaseWallet).updateScanProviderUsageState(isEnabled);
@override
Web3Client? getWeb3Client(WalletBase wallet) => (wallet as BaseWallet).getWeb3Client();
@override
String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress;
@override
Future<PendingTransaction> createTokenApproval(
WalletBase wallet,
BigInt amount,
String spender,
CryptoCurrency token,
TransactionPriority priority,
) =>
(wallet as EVMChainWallet).createApprovalTransaction(
amount,
spender,
token,
priority as EVMChainTransactionPriority,
"BASE",
);
@override
void setHardwareWalletService(WalletBase wallet, HardwareWalletService service) {
if (service is EVMChainLedgerService) {
((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedgerConnection(
service.ledgerConnection, wallet.walletInfo.derivationInfo?.derivationPath);
} else if (service is EVMChainBitboxService) {
((wallet as EVMChainWallet).evmChainPrivateKey as EvmBitboxCredentials)
.setBitbox(service.manager, wallet.walletInfo.derivationInfo?.derivationPath);
}
}
@override
HardwareWalletService getLedgerHardwareWalletService(ledger.LedgerConnection connection) =>
EVMChainLedgerService(connection);
@override
HardwareWalletService getBitboxHardwareWalletService(bitbox.BitboxManager manager) =>
EVMChainBitboxService(manager, chainId: 8453);
@override
List<String> getDefaultTokenContractAddresses() =>
DefaultBaseErc20Tokens().initialBaseErc20Tokens.map((e) => e.contractAddress).toList();
@override
bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress) {
final baseWallet = wallet as BaseWallet;
return baseWallet.erc20Currencies.any(
(element) => element.contractAddress.toLowerCase() == contractAddress.toLowerCase(),
);
}
}
+1
View File
@@ -135,6 +135,7 @@ class DFXBuyProvider extends BuyProvider {
switch (wallet.type) {
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.base:
case WalletType.solana:
case WalletType.tron:
return wallet.signMessage(message);
+2
View File
@@ -218,6 +218,7 @@ class KryptonimBuyProvider extends BuyProvider {
String _normalizeBlockChain(CryptoCurrency cur) {
String? blockchain = switch (cur.tag) {
'ETH' => 'Ethereum',
'BASE' => 'Base',
'POL' => 'Polygon',
'AVAXC' => 'Avalanche',
'SOL' => 'Solana',
@@ -229,6 +230,7 @@ class KryptonimBuyProvider extends BuyProvider {
CryptoCurrency.btc => 'Bitcoin',
CryptoCurrency.ltc => 'Litecoin',
CryptoCurrency.eth => 'Ethereum',
CryptoCurrency.baseEth => 'Base',
CryptoCurrency.maticpoly => 'Matic',
_ => null,
};
+1
View File
@@ -17,6 +17,7 @@ import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/themes/core/material_base_theme.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/wallet_base.dart';
@@ -105,6 +105,7 @@ class RobinhoodBuyProvider extends BuyProvider {
switch (wallet.type) {
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.base:
case WalletType.solana:
case WalletType.tron:
case WalletType.dogecoin:
+1
View File
@@ -4,6 +4,7 @@ import 'package:cake_wallet/order/order_source_description.dart';
import 'package:cake_wallet/buy/pairs_utils.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cake_wallet/buy/buy_amount.dart';
import 'package:cake_wallet/buy/buy_provider.dart';
+15
View File
@@ -86,6 +86,21 @@ class EthereumURI extends PaymentURI {
}
}
class BaseURI extends PaymentURI {
BaseURI({required super.amount, required super.address});
@override
String toString() {
var base = 'base:$address';
if (amount.isNotEmpty) {
base += '?amount=${amount.replaceAll(',', '.')}';
}
return base;
}
}
class BitcoinCashURI extends PaymentURI {
BitcoinCashURI({required super.amount, required super.address});
+3
View File
@@ -1,3 +1,4 @@
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/validator.dart';
import 'package:cake_wallet/entities/mnemonic_item.dart';
@@ -52,6 +53,8 @@ class SeedValidator extends Validator<MnemonicItem> {
return zano!.getWordList(language);
case WalletType.decred:
return decred!.getDecredWordList();
case WalletType.base:
return base!.getBaseWordList(language);
case WalletType.none:
case WalletType.haven:
return [];
+1
View File
@@ -80,6 +80,7 @@ class WalletCreationService {
case WalletType.bitcoinCash:
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.base:
case WalletType.solana:
case WalletType.tron:
case WalletType.dogecoin:
+3
View File
@@ -5,6 +5,7 @@ import 'package:cake_wallet/anonpay/anonpay_api.dart';
import 'package:cake_wallet/anonpay/anonpay_info_base.dart';
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
import 'package:cake_wallet/anypay/anypay_api.dart';
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/buy/dfx/dfx_buy_provider.dart';
@@ -1241,6 +1242,8 @@ Future<void> setup({
return zano!.createZanoWalletService(_walletInfoSource);
case WalletType.decred:
return decred!.createDecredWalletService(_walletInfoSource, _unspentCoinsInfoSource);
case WalletType.base:
return base!.createBaseWalletService(_walletInfoSource, SettingsStoreBase.walletPasswordDirectInput);
case WalletType.haven:
return HavenWalletService(_walletInfoSource);
case WalletType.none:
@@ -47,6 +47,7 @@ const zanoDefaultNodeUri = '37.27.100.59:10500';
const moneroWorldNodeUri = '.moneroworld.com';
const decredDefaultUri = "default-spv-nodes";
const dogecoinDefaultNodeUri = 'dogecoin.stackwallet.com:50022';
const baseDefaultNodeUri = 'base.nownodes.io';
Future<void> defaultSettingsMigration(
{required int version,
@@ -534,6 +535,15 @@ Future<void> defaultSettingsMigration(
currentNodePreferenceKey: PreferencesKey.currentDogecoinNodeIdKey,
);
break;
case 52:
await addWalletNodeList(nodes: nodes, type: WalletType.base);
await _changeDefaultNode(
nodes: nodes,
sharedPreferences: sharedPreferences,
type: WalletType.base,
currentNodePreferenceKey: PreferencesKey.currentBaseNodeIdKey,
);
break;
default:
break;
}
@@ -642,6 +652,8 @@ String _getDefaultNodeUri(WalletType type) {
return decredDefaultUri;
case WalletType.dogecoin:
return dogecoinDefaultNodeUri;
case WalletType.base:
return baseDefaultNodeUri;
case WalletType.banano:
case WalletType.none:
return '';
@@ -1041,6 +1053,7 @@ Future<void> checkCurrentNodes(
final currentHavenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey);
final currentEthereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
final currentPolygonNodeId = sharedPreferences.getInt(PreferencesKey.currentPolygonNodeIdKey);
final currentBaseNodeId = sharedPreferences.getInt(PreferencesKey.currentBaseNodeIdKey);
final currentNanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
final currentNanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey);
final currentDecredNodeId = sharedPreferences.getInt(PreferencesKey.currentDecredNodeIdKey);
@@ -1064,6 +1077,8 @@ Future<void> checkCurrentNodes(
nodeSource.values.firstWhereOrNull((node) => node.key == currentEthereumNodeId);
final currentPolygonNodeServer =
nodeSource.values.firstWhereOrNull((node) => node.key == currentPolygonNodeId);
final currentBaseNodeServer =
nodeSource.values.firstWhereOrNull((node) => node.key == currentBaseNodeId);
final currentNanoNodeServer =
nodeSource.values.firstWhereOrNull((node) => node.key == currentNanoNodeId);
final currentDecredNodeServer =
@@ -1155,6 +1170,12 @@ Future<void> checkCurrentNodes(
await sharedPreferences.setInt(PreferencesKey.currentPolygonNodeIdKey, node.key as int);
}
if (currentBaseNodeServer == null) {
final node = Node(uri: baseDefaultNodeUri, type: WalletType.base);
await nodeSource.add(node);
await sharedPreferences.setInt(PreferencesKey.currentBaseNodeIdKey, node.key as int);
}
if (currentSolanaNodeServer == null) {
final node = Node(uri: solanaDefaultNodeUri, type: WalletType.solana);
await nodeSource.add(node);
+6
View File
@@ -1,3 +1,4 @@
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
@@ -21,6 +22,10 @@ class EnsRecord {
_client = polygon!.getWeb3Client(wallet);
}
if (wallet != null && wallet.type == WalletType.base) {
_client = base!.getWeb3Client(wallet);
}
if (_client == null) {
late final client = ProxyWrapper().getHttpIOClient();
@@ -42,6 +47,7 @@ class EnsRecord {
return await ens.withName(name).getCoinAddress(CoinType.XHV);
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.base:
default:
return (await ens.withName(name).getAddress()).hex;
}
@@ -32,6 +32,50 @@ class EVMTransactionErrorFeesHandler {
String errorMessage,
double assetPriceUsd,
) {
// Pattern: "insufficient funds for gas * price + value: have 4728796358953246 want 4728796842182575"
RegExp insufficientFundsRegExp = RegExp(r'have (\d+) want (\d+)');
Match? insufficientFundsMatch = insufficientFundsRegExp.firstMatch(errorMessage);
if (insufficientFundsMatch != null) {
try {
// Extract the numerical strings from the new format
String balanceStr = insufficientFundsMatch.group(1)!;
String requiredStr = insufficientFundsMatch.group(2)!;
// Parse the numerical strings to BigInt
BigInt balanceWei = BigInt.parse(balanceStr);
BigInt requiredWei = BigInt.parse(requiredStr);
// Calculate overshot (how much more is needed)
BigInt overshotWei = requiredWei - balanceWei;
// The transaction cost is the required amount
BigInt txCostWei = requiredWei;
// Convert wei to ETH (1 ETH = 1e18 wei)
double balanceEth = balanceWei.toDouble() / 1e18;
double txCostEth = txCostWei.toDouble() / 1e18;
double overshotEth = overshotWei.toDouble() / 1e18;
// Calculate the USD values
double balanceUsd = balanceEth * assetPriceUsd;
double txCostUsd = txCostEth * assetPriceUsd;
double overshotUsd = overshotEth * assetPriceUsd;
return EVMTransactionErrorFeesHandler(
balanceWei: balanceWei.toString(),
balanceEth: balanceEth.toString().safeSubString(0, 12),
balanceUsd: balanceUsd.toString().safeSubString(0, 4),
txCostWei: txCostWei.toString(),
txCostEth: txCostEth.toString().safeSubString(0, 12),
txCostUsd: txCostUsd.toString().safeSubString(0, 4),
overshotWei: overshotWei.toString(),
overshotEth: overshotEth.toString().safeSubString(0, 12),
overshotUsd: overshotUsd.toString().safeSubString(0, 4),
);
} catch (e) {}
}
// Define Regular Expressions to extract the numerical values
RegExp balanceRegExp = RegExp(r'balance (\d+)');
RegExp txCostRegExp = RegExp(r'tx cost (\d+)');
@@ -44,40 +88,44 @@ class EVMTransactionErrorFeesHandler {
// Check if all required values are found
if (balanceMatch != null && txCostMatch != null && overshotMatch != null) {
// Extract the numerical strings
String balanceStr = balanceMatch.group(1)!;
String txCostStr = txCostMatch.group(1)!;
String overshotStr = overshotMatch.group(1)!;
try {
// Extract the numerical strings
String balanceStr = balanceMatch.group(1)!;
String txCostStr = txCostMatch.group(1)!;
String overshotStr = overshotMatch.group(1)!;
// Parse the numerical strings to BigInt
BigInt balanceWei = BigInt.parse(balanceStr);
BigInt txCostWei = BigInt.parse(txCostStr);
BigInt overshotWei = BigInt.parse(overshotStr);
// Parse the numerical strings to BigInt
BigInt balanceWei = BigInt.parse(balanceStr);
BigInt txCostWei = BigInt.parse(txCostStr);
BigInt overshotWei = BigInt.parse(overshotStr);
// Convert wei to ETH (1 ETH = 1e18 wei)
double balanceEth = balanceWei.toDouble() / 1e18;
double txCostEth = txCostWei.toDouble() / 1e18;
double overshotEth = overshotWei.toDouble() / 1e18;
// Convert wei to ETH (1 ETH = 1e18 wei)
double balanceEth = balanceWei.toDouble() / 1e18;
double txCostEth = txCostWei.toDouble() / 1e18;
double overshotEth = overshotWei.toDouble() / 1e18;
// Calculate the USD values
double balanceUsd = balanceEth * assetPriceUsd;
double txCostUsd = txCostEth * assetPriceUsd;
double overshotUsd = overshotEth * assetPriceUsd;
// Calculate the USD values
double balanceUsd = balanceEth * assetPriceUsd;
double txCostUsd = txCostEth * assetPriceUsd;
double overshotUsd = overshotEth * assetPriceUsd;
return EVMTransactionErrorFeesHandler(
balanceWei: balanceWei.toString(),
balanceEth: balanceEth.toString().safeSubString(0, 12),
balanceUsd: balanceUsd.toString().safeSubString(0, 4),
txCostWei: txCostWei.toString(),
txCostEth: txCostEth.toString().safeSubString(0, 12),
txCostUsd: txCostUsd.toString().safeSubString(0, 4),
overshotWei: overshotWei.toString(),
overshotEth: overshotEth.toString().safeSubString(0, 12),
overshotUsd: overshotUsd.toString().safeSubString(0, 4),
);
} else {
// If any value is missing, return an error message
return EVMTransactionErrorFeesHandler(error: 'Could not parse the error message.');
return EVMTransactionErrorFeesHandler(
balanceWei: balanceWei.toString(),
balanceEth: balanceEth.toString().safeSubString(0, 12),
balanceUsd: balanceUsd.toString().safeSubString(0, 4),
txCostWei: txCostWei.toString(),
txCostEth: txCostEth.toString().safeSubString(0, 12),
txCostUsd: txCostUsd.toString().safeSubString(0, 4),
overshotWei: overshotWei.toString(),
overshotEth: overshotEth.toString().safeSubString(0, 12),
overshotUsd: overshotUsd.toString().safeSubString(0, 4),
);
} catch (e) {
// If parsing fails, continue to error case
}
}
// If both parsing attempts fail, return an error message
return EVMTransactionErrorFeesHandler(error: 'Could not parse the error message.');
}
}
+6 -1
View File
@@ -49,6 +49,9 @@ Future<List<Node>> loadDefaultNodes(WalletType type) async {
case WalletType.dogecoin:
path = 'assets/dogecoin_electrum_server_list.yml';
break;
case WalletType.base:
path = 'assets/base_node_list.yml';
break;
case WalletType.banano:
case WalletType.none:
path = '';
@@ -100,6 +103,7 @@ Future<void> resetToDefault(Box<Node> nodeSource) async {
final decredNodes = await loadDefaultNodes(WalletType.decred);
final zanoNodes = await loadDefaultNodes(WalletType.zano);
final dogecoinElectrumServerList = await loadDefaultNodes(WalletType.dogecoin);
final baseNodes = await loadDefaultNodes(WalletType.base);
final nodes = moneroNodes +
bitcoinElectrumServerList +
@@ -113,7 +117,8 @@ Future<void> resetToDefault(Box<Node> nodeSource) async {
tronNodes +
zanoNodes +
decredNodes +
dogecoinElectrumServerList;
dogecoinElectrumServerList +
baseNodes;
await nodeSource.clear();
await nodeSource.addAll(nodes);
@@ -114,6 +114,7 @@ class AddressResolver {
"podcast",
"pog",
"polygon",
"base",
"press",
"privacy",
"pro",
+3
View File
@@ -8,6 +8,7 @@ class PreferencesKey {
static const currentZanoNodeIdKey = 'current_node_id_zano';
static const currentEthereumNodeIdKey = 'current_node_id_eth';
static const currentPolygonNodeIdKey = 'current_node_id_matic';
static const currentBaseNodeIdKey = 'current_node_id_base';
static const currentNanoNodeIdKey = 'current_node_id_nano';
static const currentNanoPowNodeIdKey = 'current_node_id_nano_pow';
static const currentDecredNodeIdKey = 'current_node_id_decred';
@@ -52,6 +53,7 @@ class PreferencesKey {
static const litecoinTransactionPriority = 'current_fee_priority_litecoin';
static const ethereumTransactionPriority = 'current_fee_priority_ethereum';
static const polygonTransactionPriority = 'current_fee_priority_polygon';
static const baseTransactionPriority = 'current_fee_priority_base';
static const bitcoinCashTransactionPriority = 'current_fee_priority_bitcoin_cash';
static const zanoTransactionPriority = 'current_fee_priority_zano';
static const wowneroTransactionPriority = 'current_fee_priority_wownero';
@@ -77,6 +79,7 @@ class PreferencesKey {
static const pinNativeTokenAtTop = 'pin_native_token_at_top';
static const useEtherscan = 'use_etherscan';
static const usePolygonScan = 'use_polygonscan';
static const useBaseScan = 'use_base_scan';
static const useTronGrid = 'use_trongrid';
static const useMempoolFeeAPI = 'use_mempool_fee_api';
static const defaultNanoRep = 'default_nano_representative';
@@ -1,3 +1,4 @@
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/dogecoin/dogecoin.dart';
@@ -28,6 +29,8 @@ List<TransactionPriority> priorityForWalletType(WalletType type) {
return dogecoin!.getTransactionPriorities();
case WalletType.polygon:
return polygon!.getTransactionPriorities();
case WalletType.base:
return base!.getTransactionPriorities();
// no such thing for nano/banano/solana/tron:
case WalletType.nano:
case WalletType.banano:
+6
View File
@@ -1,4 +1,5 @@
import 'dart:async';
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/core/fiat_conversion_service.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
@@ -44,6 +45,11 @@ Future<void> startFiatRateUpdate(
polygon!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
}
if (appStore.wallet!.type == WalletType.base) {
currencies =
base!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
}
if (appStore.wallet!.type == WalletType.solana) {
currencies =
solana!.getSPLTokenCurrencies(appStore.wallet!).where((element) => element.enabled);
@@ -1,3 +1,4 @@
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
@@ -130,6 +131,10 @@ void startCurrentWalletChangeReaction(
currencies =
polygon!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
}
if (wallet.type == WalletType.base) {
currencies =
base!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
}
if (wallet.type == WalletType.solana) {
currencies =
solana!.getSPLTokenCurrencies(appStore.wallet!).where((element) => element.enabled);
+12 -2
View File
@@ -8,6 +8,7 @@ bool isEVMCompatibleChain(WalletType walletType) {
switch (walletType) {
case WalletType.polygon:
case WalletType.ethereum:
case WalletType.base:
return true;
default:
return false;
@@ -18,6 +19,7 @@ bool isNFTACtivatedChain(WalletType walletType) {
switch (walletType) {
case WalletType.polygon:
case WalletType.ethereum:
case WalletType.base:
case WalletType.solana:
return true;
default:
@@ -29,6 +31,7 @@ bool isWalletConnectCompatibleChain(WalletType walletType) {
switch (walletType) {
case WalletType.polygon:
case WalletType.ethereum:
case WalletType.base:
case WalletType.solana:
return true;
default:
@@ -42,6 +45,8 @@ String getChainNameSpaceAndIdBasedOnWalletType(WalletType walletType) {
return EVMChainId.ethereum.chain();
case WalletType.polygon:
return EVMChainId.polygon.chain();
case WalletType.base:
return EVMChainId.base.chain();
case WalletType.solana:
return SolanaChainId.mainnet.chain();
default:
@@ -52,8 +57,8 @@ String getChainNameSpaceAndIdBasedOnWalletType(WalletType walletType) {
List<String> getChainSupportedMethodsOnWalletType(WalletType walletType) {
switch (walletType) {
case WalletType.ethereum:
return EVMSupportedMethods.values.map((e) => e.name).toList();
case WalletType.polygon:
case WalletType.base:
return EVMSupportedMethods.values.map((e) => e.name).toList();
case WalletType.solana:
return SolanaSupportedMethods.values.map((e) => e.name).toList();
@@ -66,7 +71,8 @@ int getChainIdBasedOnWalletType(WalletType walletType) {
switch (walletType) {
case WalletType.polygon:
return 137;
case WalletType.base:
return 8453;
// For now, we return eth chain Id as the default, we'll modify as we add more wallets
case WalletType.ethereum:
default:
@@ -80,6 +86,8 @@ String getChainNameBasedOnWalletType(WalletType walletType) {
return 'eth';
case WalletType.polygon:
return 'polygon';
case WalletType.base:
return 'base';
case WalletType.solana:
return 'mainnet';
default:
@@ -93,6 +101,8 @@ String getTokenNameBasedOnWalletType(WalletType walletType) {
return 'ETH';
case WalletType.polygon:
return 'MATIC';
case WalletType.base:
return 'BASE';
case WalletType.solana:
return 'SOL';
default:
+2
View File
@@ -4,6 +4,7 @@ bool isBIP39Wallet(WalletType walletType) {
switch (walletType) {
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.base:
case WalletType.solana:
case WalletType.tron:
case WalletType.bitcoin:
@@ -41,6 +42,7 @@ bool hasTokens(WalletType walletType) {
case WalletType.solana:
case WalletType.tron:
case WalletType.zano:
case WalletType.base:
return true;
default:
return false;
@@ -10,6 +10,7 @@ import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/wallet_hardware_restore_view_model.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@@ -148,7 +149,7 @@ class _SelectHardwareWalletAccountFormState extends State<SelectHardwareWalletAc
padding: EdgeInsets.only(top: 10),
child: SelectButton(
image: Image.asset(
walletTypeToCryptoCurrency(_walletHardwareRestoreVM.type).iconPath ??
walletTypeToCryptoCurrency(_walletHardwareRestoreVM.type).normalizedIconPath ??
'',
height: 24,
width: 24,
@@ -38,6 +38,7 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
final ethereumIcon = Image.asset('assets/images/crypto/ethereum.png', height: 24, width: 24);
final polygonIcon = Image.asset('assets/images/crypto/polygon.png', height: 24, width: 24);
final bitcoinCashIcon = Image.asset('assets/images/crypto/bitcoin-cash.png', height: 24, width: 24);
final baseIcon = Image.asset('assets/images/base_icon.png', height: 24, width: 24);
final nanoIcon = Image.asset('assets/images/crypto/nano.png', height: 24, width: 24);
final bananoIcon = Image.asset('assets/images/crypto/nano.png', height: 24, width: 24);
final solanaIcon = Image.asset('assets/images/crypto/solana.png', height: 24, width: 24);
@@ -184,6 +185,8 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
return decredIcon;
case WalletType.dogecoin:
return dogeIcon;
case WalletType.base:
return baseIcon;
case WalletType.none:
return nonWalletTypeIcon;
}
@@ -33,6 +33,7 @@ class MenuWidgetState extends State<MenuWidget> {
this.bananoIcon = Image.asset('assets/images/crypto/nano.png'),
this.bitcoinCashIcon = Image.asset('assets/images/crypto/bitcoin-cash.png'),
this.polygonIcon = Image.asset('assets/images/crypto/polygon.png'),
this.baseIcon = Image.asset('assets/images/base_icon.png'),
this.solanaIcon = Image.asset('assets/images/crypto/solana.png'),
this.tronIcon = Image.asset('assets/images/crypto/tron.png'),
this.wowneroIcon = Image.asset('assets/images/crypto/wownero.png'),
@@ -60,6 +61,7 @@ class MenuWidgetState extends State<MenuWidget> {
Image nanoIcon;
Image bananoIcon;
Image polygonIcon;
Image baseIcon;
Image solanaIcon;
Image tronIcon;
Image wowneroIcon;
@@ -247,6 +249,8 @@ class MenuWidgetState extends State<MenuWidget> {
return polygonIcon;
case WalletType.solana:
return solanaIcon;
case WalletType.base:
return baseIcon;
case WalletType.tron:
return tronIcon;
case WalletType.wownero:
@@ -2,6 +2,7 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/address_text_field.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -1,5 +1,6 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/address_text_field.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
@@ -15,6 +15,7 @@ import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/new_wallet_type_view_model.dart';
import 'package:cake_wallet/wallet_types.g.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/hardware/device_connection_type.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
@@ -142,9 +143,9 @@ class WalletTypeFormState extends State<WalletTypeForm> {
child: SelectButton(
key: ValueKey('new_wallet_type_${type.name}_button_key'),
image: Image.asset(
walletTypeToCryptoCurrency(type).iconPath ?? '',
height: 28,
width: 28,
walletTypeToCryptoCurrency(type).normalizedIconPath ?? '',
height: 24,
width: 24,
),
text: walletTypeToDisplayName(type),
showTrailingIcon: false,
@@ -7,7 +7,7 @@ import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/themes/core/material_base_theme.dart';
import 'package:cake_wallet/view_model/wallet_groups_display_view_model.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@@ -95,7 +95,7 @@ class WalletGroupsDisplayBody extends StatelessWidget {
isSelected:
walletGroupsDisplayViewModel.selectedSingleWallet == wallet,
leadingWidget: Image.asset(
walletTypeToCryptoCurrency(wallet.type).iconPath!,
walletTypeToCryptoCurrency(wallet.type).normalizedIconPath!,
width: 32,
height: 32,
),
@@ -1,4 +1,4 @@
import 'package:cw_core/wallet_type.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
@@ -99,7 +99,6 @@ class GroupedWalletExpansionTile extends StatelessWidget {
final currentColor = item.isCurrent
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.surface;
final walletTypeToCrypto = walletTypeToCryptoCurrency(item.type);
return ListTile(
contentPadding: EdgeInsets.zero,
key: ValueKey(item.name),
@@ -124,7 +123,7 @@ class GroupedWalletExpansionTile extends StatelessWidget {
: SizedBox(width: 7),
SizedBox(width: 24),
Image.asset(
walletTypeToCrypto.iconPath!,
walletTypeToCryptoCurrency(item.type).normalizedIconPath!,
width: 32,
height: 32,
),
@@ -118,6 +118,14 @@ class PrivacyPage extends BasePage {
_privacySettingsViewModel.setUsePolygonScan(value);
},
),
if (_privacySettingsViewModel.canUseBaseScan)
SettingsSwitcherCell(
title: S.current.basescan_history,
value: _privacySettingsViewModel.useBaseScan,
onValueChange: (BuildContext _, bool value) {
_privacySettingsViewModel.setUseBaseScan(value);
},
),
if (_privacySettingsViewModel.canUseTronGrid)
SettingsSwitcherCell(
title: S.current.trongrid_history,
@@ -1,6 +1,7 @@
enum EVMChainId {
ethereum,
polygon,
base,
goerli,
mumbai,
arbitrum,
@@ -17,6 +18,9 @@ extension EVMChainIdX on EVMChainId {
case EVMChainId.polygon:
name = '137';
break;
case EVMChainId.base:
name = '8453';
break;
case EVMChainId.goerli:
name = '5';
break;
@@ -1,3 +1,4 @@
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
@@ -23,6 +24,8 @@ class KeyServiceImpl implements WalletConnectKeyService {
return ethereum!.getPrivateKey(wallet);
case WalletType.polygon:
return polygon!.getPrivateKey(wallet);
case WalletType.base:
return base!.getPrivateKey(wallet);
case WalletType.solana:
return solana!.getPrivateKey(wallet);
default:
@@ -36,6 +39,8 @@ class KeyServiceImpl implements WalletConnectKeyService {
return ethereum!.getPublicKey(wallet);
case WalletType.polygon:
return polygon!.getPublicKey(wallet);
case WalletType.base:
return base!.getPublicKey(wallet);
case WalletType.solana:
return solana!.getPublicKey(wallet);
default:
@@ -51,6 +56,7 @@ class KeyServiceImpl implements WalletConnectKeyService {
'eip155:1',
'eip155:5',
'eip155:137',
'eip155:8453',
'eip155:42161',
'eip155:80001',
],
@@ -25,6 +25,7 @@ import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
import 'package:cake_wallet/wallet_type_utils.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
@@ -1,5 +1,6 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/utils/payment_request.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
@@ -9,6 +9,7 @@ import 'package:cake_wallet/src/widgets/bottom_sheet/swap_details_bottom_sheet.d
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/utils/address_formatter.dart';
import 'package:cake_wallet/utils/debounce.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/crypto_amount_format.dart';
import 'package:flutter/material.dart';
@@ -136,7 +137,7 @@ class SwapConfirmationContentState extends State<SwapConfirmationContent> {
@override
Widget build(BuildContext context) {
final detectedCurrencyName = walletTypeToCryptoCurrency(widget.paymentFlowResult.walletType!);
final detectedCurrency = walletTypeToCryptoCurrency(widget.paymentFlowResult.walletType!);
return Form(
key: _formKey,
@@ -159,7 +160,7 @@ class SwapConfirmationContentState extends State<SwapConfirmationContent> {
const SizedBox(width: 12),
CakeImageWidget(
imageUrl:
walletTypeToCryptoCurrency(widget.paymentFlowResult.walletType!).iconPath!,
detectedCurrency.iconPath!,
width: 32,
height: 32,
),
@@ -168,7 +169,7 @@ class SwapConfirmationContentState extends State<SwapConfirmationContent> {
const SizedBox(height: 16),
SwapConfirmationTextfield(
key: ValueKey('swap_confirmation_bottomsheet_amount_textfield_key'),
hintText: 'Amount (${detectedCurrencyName})',
hintText: 'Amount (${detectedCurrency})',
focusNode: _amountFocus,
controller: _amountController,
keyboardType: TextInputType.numberWithOptions(decimal: true, signed: false),
@@ -226,7 +227,7 @@ class SwapConfirmationContentState extends State<SwapConfirmationContent> {
min != null
? Text(
key: ValueKey('min_limit_text_key'),
S.of(context).min_value(min, detectedCurrencyName.toString()),
S.of(context).min_value(min, detectedCurrency.toString()),
style: Theme.of(context).textTheme.bodySmall!.copyWith(
fontSize: 10,
height: 1.2,
@@ -238,7 +239,7 @@ class SwapConfirmationContentState extends State<SwapConfirmationContent> {
max != null
? Text(
key: ValueKey('max_limit_text_key'),
S.of(context).max_value(max, detectedCurrencyName.toString()),
S.of(context).max_value(max, detectedCurrency.toString()),
style: Theme.of(context).textTheme.bodySmall!.copyWith(
fontSize: 10,
height: 1.2,
@@ -1,4 +1,5 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
@@ -94,7 +95,7 @@ class _WalletSwitcherContent extends StatelessWidget {
child: Row(
children: [
Image.asset(
walletTypeToCryptoCurrency(wallet.type).iconPath!,
walletTypeToCryptoCurrency(wallet.type).normalizedIconPath!,
width: 32,
height: 32,
),
+49
View File
@@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/utilities.dart';
import 'package:cake_wallet/decred/decred.dart';
@@ -108,6 +109,7 @@ abstract class SettingsStoreBase with Store {
required this.sortBalanceBy,
required this.pinNativeTokenAtTop,
required this.useEtherscan,
required this.useBaseScan,
required this.usePolygonScan,
required this.useTronGrid,
required this.useMempoolFeeAPI,
@@ -139,6 +141,7 @@ abstract class SettingsStoreBase with Store {
TransactionPriority? initialLitecoinTransactionPriority,
TransactionPriority? initialEthereumTransactionPriority,
TransactionPriority? initialPolygonTransactionPriority,
TransactionPriority? initialBaseTransactionPriority,
TransactionPriority? initialBitcoinCashTransactionPriority,
TransactionPriority? initialZanoTransactionPriority,
TransactionPriority? initialDecredTransactionPriority,
@@ -224,6 +227,10 @@ abstract class SettingsStoreBase with Store {
priority[WalletType.polygon] = initialPolygonTransactionPriority;
}
if (initialBaseTransactionPriority != null) {
priority[WalletType.base] = initialBaseTransactionPriority;
}
if (initialBitcoinCashTransactionPriority != null) {
priority[WalletType.bitcoinCash] = initialBitcoinCashTransactionPriority;
}
@@ -291,6 +298,9 @@ abstract class SettingsStoreBase with Store {
case WalletType.polygon:
key = PreferencesKey.polygonTransactionPriority;
break;
case WalletType.base:
key = PreferencesKey.baseTransactionPriority;
break;
case WalletType.zano:
key = PreferencesKey.zanoTransactionPriority;
break;
@@ -453,6 +463,11 @@ abstract class SettingsStoreBase with Store {
(bool usePolygonScan) =>
_sharedPreferences.setBool(PreferencesKey.usePolygonScan, usePolygonScan));
reaction(
(_) => useBaseScan,
(bool useBaseScan) =>
_sharedPreferences.setBool(PreferencesKey.useBaseScan, useBaseScan));
reaction((_) => useTronGrid,
(bool useTronGrid) => _sharedPreferences.setBool(PreferencesKey.useTronGrid, useTronGrid));
@@ -808,6 +823,9 @@ abstract class SettingsStoreBase with Store {
@observable
bool usePolygonScan;
@observable
bool useBaseScan;
@observable
bool useTronGrid;
@@ -949,6 +967,7 @@ abstract class SettingsStoreBase with Store {
TransactionPriority? litecoinTransactionPriority;
TransactionPriority? ethereumTransactionPriority;
TransactionPriority? polygonTransactionPriority;
TransactionPriority? baseTransactionPriority;
TransactionPriority? bitcoinCashTransactionPriority;
TransactionPriority? wowneroTransactionPriority;
TransactionPriority? zanoTransactionPriority;
@@ -970,6 +989,10 @@ abstract class SettingsStoreBase with Store {
polygonTransactionPriority = polygon?.deserializePolygonTransactionPriority(
sharedPreferences.getInt(PreferencesKey.polygonTransactionPriority)!);
}
if (sharedPreferences.getInt(PreferencesKey.baseTransactionPriority) != null) {
baseTransactionPriority = base?.deserializeBaseTransactionPriority(
sharedPreferences.getInt(PreferencesKey.baseTransactionPriority)!);
}
if (sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority) != null) {
bitcoinCashTransactionPriority = bitcoinCash?.deserializeBitcoinCashTransactionPriority(
sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority)!);
@@ -996,6 +1019,7 @@ abstract class SettingsStoreBase with Store {
wowneroTransactionPriority ??= wownero?.getDefaultTransactionPriority();
decredTransactionPriority ??= decred?.getDecredTransactionPriorityMedium();
polygonTransactionPriority ??= polygon?.getDefaultTransactionPriority();
baseTransactionPriority ??= base?.getDefaultTransactionPriority();
zanoTransactionPriority ??= zano?.getDefaultTransactionPriority();
final currentBalanceDisplayMode = BalanceDisplayMode.deserialize(
@@ -1042,6 +1066,7 @@ abstract class SettingsStoreBase with Store {
: defaultSeedPhraseLength;
final useEtherscan = sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true;
final usePolygonScan = sharedPreferences.getBool(PreferencesKey.usePolygonScan) ?? true;
final useBaseScan = sharedPreferences.getBool(PreferencesKey.useBaseScan) ?? true;
final useTronGrid = sharedPreferences.getBool(PreferencesKey.useTronGrid) ?? true;
final useMempoolFeeAPI = sharedPreferences.getBool(PreferencesKey.useMempoolFeeAPI) ?? true;
final defaultNanoRep = sharedPreferences.getString(PreferencesKey.defaultNanoRep) ?? "";
@@ -1087,6 +1112,7 @@ abstract class SettingsStoreBase with Store {
sharedPreferences.getInt(PreferencesKey.currentBitcoinCashNodeIdKey);
final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
final polygonNodeId = sharedPreferences.getInt(PreferencesKey.currentPolygonNodeIdKey);
final baseNodeId = sharedPreferences.getInt(PreferencesKey.currentBaseNodeIdKey);
final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
final nanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey);
final solanaNodeId = sharedPreferences.getInt(PreferencesKey.currentSolanaNodeIdKey);
@@ -1107,6 +1133,8 @@ abstract class SettingsStoreBase with Store {
nodeSource.values.firstWhereOrNull((e) => e.uriRaw == ethereumDefaultNodeUri);
final polygonNode = nodeSource.get(polygonNodeId) ??
nodeSource.values.firstWhereOrNull((e) => e.uriRaw == polygonDefaultNodeUri);
final baseNode = nodeSource.get(baseNodeId) ??
nodeSource.values.firstWhereOrNull((e) => e.uriRaw == baseDefaultNodeUri);
final bitcoinCashElectrumServer = nodeSource.get(bitcoinCashElectrumServerId) ??
nodeSource.values.firstWhereOrNull((e) => e.uriRaw == cakeWalletBitcoinCashDefaultNodeUri);
final nanoNode = nodeSource.get(nanoNodeId) ??
@@ -1180,6 +1208,10 @@ abstract class SettingsStoreBase with Store {
nodes[WalletType.polygon] = polygonNode;
}
if (baseNode != null) {
nodes[WalletType.base] = baseNode;
}
if (bitcoinCashElectrumServer != null) {
nodes[WalletType.bitcoinCash] = bitcoinCashElectrumServer;
}
@@ -1358,6 +1390,7 @@ abstract class SettingsStoreBase with Store {
pinNativeTokenAtTop: pinNativeTokenAtTop,
useEtherscan: useEtherscan,
usePolygonScan: usePolygonScan,
useBaseScan: useBaseScan,
useTronGrid: useTronGrid,
useMempoolFeeAPI: useMempoolFeeAPI,
defaultNanoRep: defaultNanoRep,
@@ -1404,6 +1437,7 @@ abstract class SettingsStoreBase with Store {
shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
initialEthereumTransactionPriority: ethereumTransactionPriority,
initialPolygonTransactionPriority: polygonTransactionPriority,
initialBaseTransactionPriority: baseTransactionPriority,
initialSyncMode: savedSyncMode,
initialSyncAll: savedSyncAll,
shouldShowYatPopup: shouldShowYatPopup,
@@ -1455,6 +1489,11 @@ abstract class SettingsStoreBase with Store {
priority[WalletType.polygon] = polygon!.deserializePolygonTransactionPriority(
sharedPreferences.getInt(PreferencesKey.polygonTransactionPriority)!);
}
if (base != null &&
sharedPreferences.getInt(PreferencesKey.baseTransactionPriority) != null) {
priority[WalletType.base] = base!.deserializeBaseTransactionPriority(
sharedPreferences.getInt(PreferencesKey.baseTransactionPriority)!);
}
if (bitcoinCash != null &&
sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority) != null) {
priority[WalletType.bitcoinCash] = bitcoinCash!.deserializeBitcoinCashTransactionPriority(
@@ -1548,6 +1587,7 @@ abstract class SettingsStoreBase with Store {
pinNativeTokenAtTop = sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop) ?? true;
useEtherscan = sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true;
usePolygonScan = sharedPreferences.getBool(PreferencesKey.usePolygonScan) ?? true;
useBaseScan = sharedPreferences.getBool(PreferencesKey.useBaseScan) ?? true;
useTronGrid = sharedPreferences.getBool(PreferencesKey.useTronGrid) ?? true;
useMempoolFeeAPI = sharedPreferences.getBool(PreferencesKey.useMempoolFeeAPI) ?? true;
defaultNanoRep = sharedPreferences.getString(PreferencesKey.defaultNanoRep) ?? "";
@@ -1578,6 +1618,7 @@ abstract class SettingsStoreBase with Store {
final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey);
final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
final polygonNodeId = sharedPreferences.getInt(PreferencesKey.currentPolygonNodeIdKey);
final baseNodeId = sharedPreferences.getInt(PreferencesKey.currentBaseNodeIdKey);
final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
final solanaNodeId = sharedPreferences.getInt(PreferencesKey.currentSolanaNodeIdKey);
final tronNodeId = sharedPreferences.getInt(PreferencesKey.currentTronNodeIdKey);
@@ -1591,6 +1632,7 @@ abstract class SettingsStoreBase with Store {
final havenNode = nodeSource.get(havenNodeId);
final ethereumNode = nodeSource.get(ethereumNodeId);
final polygonNode = nodeSource.get(polygonNodeId);
final baseNode = nodeSource.get(baseNodeId);
final bitcoinCashNode = nodeSource.get(bitcoinCashElectrumServerId);
final nanoNode = nodeSource.get(nanoNodeId);
final solanaNode = nodeSource.get(solanaNodeId);
@@ -1624,6 +1666,10 @@ abstract class SettingsStoreBase with Store {
nodes[WalletType.polygon] = polygonNode;
}
if (baseNode != null) {
nodes[WalletType.base] = baseNode;
}
if (bitcoinCashNode != null) {
nodes[WalletType.bitcoinCash] = bitcoinCashNode;
}
@@ -1784,6 +1830,9 @@ abstract class SettingsStoreBase with Store {
case WalletType.polygon:
await _sharedPreferences.setInt(PreferencesKey.currentPolygonNodeIdKey, node.key as int);
break;
case WalletType.base:
await _sharedPreferences.setInt(PreferencesKey.currentBaseNodeIdKey, node.key as int);
break;
case WalletType.solana:
await _sharedPreferences.setInt(PreferencesKey.currentSolanaNodeIdKey, node.key as int);
break;
+3 -1
View File
@@ -28,9 +28,11 @@ String getQrImage(WalletType type) {
return 'assets/images/dcr_chain_qr.svg';
case WalletType.dogecoin:
return 'assets/images/doge_chain_qr.svg';
case WalletType.base:
return 'assets/images/base_chain_QR.svg';
case WalletType.banano:
case WalletType.haven:
case WalletType.none:
return 'assets/images/qr-cake.svg';
return 'assets/images/qr-cake.png';
}
}
+5 -2
View File
@@ -1,3 +1,4 @@
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cw_core/cake_hive.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/erc20_token.dart';
@@ -13,14 +14,14 @@ class TokenUtilities {
Box<WalletInfo> walletInfoSource,
) async {
final evmWallets = walletInfoSource.values.where(
(w) => w.type == WalletType.ethereum || w.type == WalletType.polygon,
(w) => isEVMCompatibleChain(w.type),
);
final seen = <String>{};
final unique = <Erc20Token>[];
for (final wallet in evmWallets) {
final chain = wallet.type == WalletType.ethereum ? 'ETH' : 'POL';
final chain = getTokenNameBasedOnWalletType(wallet.type);
final box = await _openEvmTokensBoxFor(wallet);
for (final t in box.values.where((t) => t.enabled)) {
@@ -88,6 +89,7 @@ class TokenUtilities {
switch (walletType) {
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.base:
final tokens = await loadAllUniqueEvmTokens(walletInfoSource);
for (final t in tokens) {
if (t.contractAddress.toLowerCase() == lower) return t;
@@ -117,6 +119,7 @@ class TokenUtilities {
final boxName = switch (walletInfo.type) {
WalletType.ethereum => '${walletKey}_${Erc20Token.ethereumBoxName}',
WalletType.polygon => '${walletKey}_${Erc20Token.polygonBoxName}',
WalletType.base => '${walletKey}_${Erc20Token.baseBoxName}',
_ => '${walletKey}_${Erc20Token.ethereumBoxName}',
};
@@ -39,6 +39,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
case WalletType.bitcoinCash:
case WalletType.dogecoin:
case WalletType.polygon:
case WalletType.base:
case WalletType.solana:
case WalletType.tron:
return true;
@@ -79,6 +80,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
WalletType.bitcoinCash,
WalletType.ethereum,
WalletType.polygon,
WalletType.base,
WalletType.tron,
WalletType.solana,
WalletType.monero,
@@ -5,6 +5,7 @@ import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/utils/qr_util.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/receive_page_option.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cw_core/crypto_currency.dart';
+1
View File
@@ -3,6 +3,7 @@ import 'package:cake_wallet/buy/moonpay/moonpay_provider.dart';
import 'package:cake_wallet/buy/wyre/wyre_buy_provider.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/store/settings_store.dart';
@@ -10,6 +10,7 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/utils/mobx.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:hive/hive.dart';
@@ -166,6 +166,7 @@ abstract class BalanceViewModelBase with Store {
case WalletType.haven:
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.base:
case WalletType.solana:
case WalletType.tron:
return S.current.xmr_full_balance;
@@ -873,6 +873,7 @@ abstract class DashboardViewModelBase with Store {
case WalletType.bitcoinCash:
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.base:
case WalletType.solana:
case WalletType.nano:
case WalletType.banano:
@@ -1,5 +1,6 @@
import 'dart:convert';
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/core/fiat_conversion_service.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cake_wallet/entities/erc20_token_info_moralis.dart';
@@ -99,6 +100,18 @@ abstract class HomeSettingsViewModelBase with Store {
await polygon!.addErc20Token(_balanceViewModel.wallet, polygonToken);
}
if (_balanceViewModel.wallet.type == WalletType.base) {
final baseToken = Erc20Token(
name: token.name,
symbol: token.title,
decimal: token.decimals,
contractAddress: contractAddress.toLowerCase(),
iconPath: token.iconPath,
isPotentialScam: token.isPotentialScam,
);
await base!.addErc20Token(_balanceViewModel.wallet, baseToken);
}
if (_balanceViewModel.wallet.type == WalletType.solana) {
final splToken = token.copyWith(enabled: true);
await solana!.addSPLToken(
@@ -136,6 +149,10 @@ abstract class HomeSettingsViewModelBase with Store {
return polygon!.isTokenAlreadyAdded(_balanceViewModel.wallet, contractAddress);
}
if (_balanceViewModel.wallet.type == WalletType.base) {
return base!.isTokenAlreadyAdded(_balanceViewModel.wallet, contractAddress);
}
if (_balanceViewModel.wallet.type == WalletType.solana) {
return solana!.isTokenAlreadyAdded(_balanceViewModel.wallet, contractAddress);
}
@@ -163,6 +180,10 @@ abstract class HomeSettingsViewModelBase with Store {
await polygon!.deleteErc20Token(_balanceViewModel.wallet, token as Erc20Token);
}
if (_balanceViewModel.wallet.type == WalletType.base) {
await base!.deleteErc20Token(_balanceViewModel.wallet, token as Erc20Token);
}
if (_balanceViewModel.wallet.type == WalletType.solana) {
await solana!.deleteSPLToken(_balanceViewModel.wallet, token);
}
@@ -189,16 +210,14 @@ abstract class HomeSettingsViewModelBase with Store {
return false;
}
bool isEthereum = _balanceViewModel.wallet.type == WalletType.ethereum;
bool isPotentialScamViaMoralis = await _isPotentialScamTokenViaMoralis(
contractAddress,
isEthereum ? 'eth' : 'polygon',
getChainNameBasedOnWalletType(_balanceViewModel.wallet.type),
);
bool isUnverifiedContract = await _isContractUnverified(
contractAddress,
isEthereum: isEthereum,
chainId: getChainIdBasedOnWalletType(_balanceViewModel.wallet.type).toString(),
);
final showWarningForContractAddress = isPotentialScamViaMoralis || isUnverifiedContract;
@@ -219,6 +238,9 @@ abstract class HomeSettingsViewModelBase with Store {
case WalletType.polygon:
defaultTokenAddresses = polygon!.getDefaultTokenContractAddresses();
break;
case WalletType.base:
defaultTokenAddresses = base!.getDefaultTokenContractAddresses();
break;
case WalletType.solana:
defaultTokenAddresses = solana!.getDefaultTokenContractAddresses();
break;
@@ -306,13 +328,13 @@ abstract class HomeSettingsViewModelBase with Store {
Future<bool> _isContractUnverified(
String contractAddress, {
required bool isEthereum,
required String chainId,
}) async {
final uri = Uri.https(
"api.etherscan.io",
"/v2/api",
{
"chainid": isEthereum ? "1" : "137",
"chainid": chainId,
"module": "contract",
"action": "getsourcecode",
"address": contractAddress,
@@ -354,6 +376,10 @@ abstract class HomeSettingsViewModelBase with Store {
return await polygon!.getErc20Token(_balanceViewModel.wallet, contractAddress);
}
if (_balanceViewModel.wallet.type == WalletType.base) {
return await base!.getErc20Token(_balanceViewModel.wallet, contractAddress);
}
if (_balanceViewModel.wallet.type == WalletType.solana) {
return await solana!.getSPLToken(_balanceViewModel.wallet, contractAddress);
}
@@ -395,6 +421,11 @@ abstract class HomeSettingsViewModelBase with Store {
if (!value) polygon!.removeTokenTransactionsInHistory(_balanceViewModel.wallet, token);
}
if (_balanceViewModel.wallet.type == WalletType.base) {
base!.addErc20Token(_balanceViewModel.wallet, token as Erc20Token);
if (!value) base!.removeTokenTransactionsInHistory(_balanceViewModel.wallet, token);
}
if (_balanceViewModel.wallet.type == WalletType.solana) {
final address = solana!.getTokenAddress(token);
solana!.addSPLToken(_balanceViewModel.wallet, token, address);
@@ -448,6 +479,14 @@ abstract class HomeSettingsViewModelBase with Store {
..sort(_sortFunc));
}
if (_balanceViewModel.wallet.type == WalletType.base) {
tokens.addAll(base!
.getERC20Currencies(_balanceViewModel.wallet)
.where((element) => _matchesSearchText(element))
.toList()
..sort(_sortFunc));
}
if (_balanceViewModel.wallet.type == WalletType.solana) {
tokens.addAll(solana!
.getSPLTokenCurrencies(_balanceViewModel.wallet)
@@ -489,7 +528,7 @@ abstract class HomeSettingsViewModelBase with Store {
bool _matchesSearchText(CryptoCurrency asset) {
final address = getTokenAddressBasedOnWallet(asset);
// The homes settings would only be displayed for either of Tron, Ethereum, Polygon or Solana Wallets.
// The homes settings would only be displayed for either of Tron, Ethereum, Polygon, Base or Solana Wallets.
if (address == null) return false;
return searchText.isEmpty ||
@@ -515,11 +554,15 @@ abstract class HomeSettingsViewModelBase with Store {
return polygon!.getTokenAddress(asset);
}
if (_balanceViewModel.wallet.type == WalletType.base) {
return base!.getTokenAddress(asset);
}
if (_balanceViewModel.wallet.type == WalletType.zano) {
return zano!.getZanoAssetAddress(asset);
}
// We return null if it's neither Tron, Polygon, Ethereum or Solana wallet (which is actually impossible because we only display home settings for either of these three wallets).
// We return null if it's neither Tron, Polygon, Base, Ethereum or Solana wallet (which is actually impossible because we only display home settings for either of these four wallets).
return null;
}
}
@@ -1,3 +1,4 @@
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/decred/decred.dart';
import 'package:cake_wallet/entities/balance_display_mode.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
@@ -134,6 +135,11 @@ class TransactionListItem extends ActionListItem with Keyable {
return asset;
}
if (balanceViewModel.wallet.type == WalletType.base) {
final asset = base!.assetOfTransaction(balanceViewModel.wallet, transaction);
return asset;
}
if (balanceViewModel.wallet.type == WalletType.solana) {
final asset = solana!.assetOfTransaction(balanceViewModel.wallet, transaction);
return asset;
@@ -186,6 +192,13 @@ class TransactionListItem extends ActionListItem with Keyable {
cryptoAmount: polygon!.formatterPolygonAmountToDouble(transaction: transaction),
price: price);
break;
case WalletType.base:
final asset = base!.assetOfTransaction(balanceViewModel.wallet, transaction);
final price = balanceViewModel.fiatConvertationStore.prices[asset];
amount = calculateFiatAmountRaw(
cryptoAmount: base!.formatterBaseAmountToDouble(transaction: transaction),
price: price);
break;
case WalletType.nano:
amount = calculateFiatAmountRaw(
cryptoAmount: double.parse(nanoUtil!.getRawAsUsableString(
@@ -17,6 +17,7 @@ import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/xoswap_exchange_provider.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_item.dart';
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
import 'package:cake_wallet/store/dashboard/trades_store.dart';
@@ -306,6 +307,9 @@ abstract class ExchangeTradeViewModelBase with Store {
wallet.currency == CryptoCurrency.maticpoly &&
tradeFrom?.tag == CryptoCurrency.maticpoly.tag;
bool _isBaseToken() =>
wallet.currency == CryptoCurrency.baseEth && tradeFrom?.tag == CryptoCurrency.baseEth.title;
bool _isTronToken() =>
wallet.currency == CryptoCurrency.trx && tradeFrom?.tag == CryptoCurrency.trx.title;
@@ -317,7 +321,8 @@ abstract class ExchangeTradeViewModelBase with Store {
_isEthToken() ||
_isPolygonToken() ||
_isSplToken() ||
_isTronToken();
_isTronToken() ||
_isBaseToken();
}
PaymentURI? get paymentUri {
@@ -343,6 +348,8 @@ abstract class ExchangeTradeViewModelBase with Store {
// TODO: Expand ERC681URI support to Polygon(modify decoding flow for QRs, pay anything, and deep link handling)
case WalletType.polygon:
return PolygonURI(amount: amount, address: inputAddress);
case WalletType.base:
return BaseURI(amount: amount, address: inputAddress);
case WalletType.solana:
return SolanaURI(amount: amount, address: inputAddress);
case WalletType.tron:
@@ -377,7 +384,7 @@ abstract class ExchangeTradeViewModelBase with Store {
contractAddress: null,
);
} else {
if (wallet.type == WalletType.polygon || wallet.type == WalletType.ethereum) {
if (isEVMCompatibleChain(wallet.type)) {
final erc20Token = TokenUtilities.findErc20Token(currency, wallet);
if (erc20Token != null) {
@@ -878,6 +878,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
depositCurrency = CryptoCurrency.maticpoly;
receiveCurrency = CryptoCurrency.xmr;
break;
case WalletType.base:
depositCurrency = CryptoCurrency.baseEth;
receiveCurrency = CryptoCurrency.xmr;
break;
case WalletType.solana:
depositCurrency = CryptoCurrency.sol;
receiveCurrency = CryptoCurrency.xmr;
@@ -80,6 +80,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
switch (_walletType) {
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.base:
case WalletType.solana:
case WalletType.banano:
case WalletType.nano:
@@ -3,11 +3,13 @@ import 'dart:convert';
import 'package:cake_wallet/core/seed_validator.dart';
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
import 'package:cake_wallet/entities/qr_scanner.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/restore/restore_mode.dart';
import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/cupertino.dart';
import 'package:cake_wallet/generated/i18n.dart';
@@ -29,6 +31,7 @@ class WalletRestoreFromQRCode {
'litecoin_wallet': WalletType.litecoin,
'ethereum-wallet': WalletType.ethereum,
'polygon-wallet': WalletType.polygon,
'base-wallet': WalletType.base,
'nano-wallet': WalletType.nano,
'nano_wallet': WalletType.nano,
'bitcoincash': WalletType.bitcoinCash,
@@ -206,15 +209,7 @@ class WalletRestoreFromQRCode {
: throw Exception('Unexpected restore mode: spend_key or view_key is invalid');
}
if (type == WalletType.ethereum && credentials.containsKey('private_key')) {
final privateKey = credentials['private_key'] as String;
if (privateKey.isEmpty) {
throw Exception('Unexpected restore mode: private_key');
}
return WalletRestoreMode.keys;
}
if (type == WalletType.polygon && credentials.containsKey('private_key')) {
if (isEVMCompatibleChain(type) && credentials.containsKey('private_key')) {
final privateKey = credentials['private_key'] as String;
if (privateKey.isEmpty) {
throw Exception('Unexpected restore mode: private_key');
+6
View File
@@ -1,3 +1,4 @@
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/decred/decred.dart';
import 'package:cake_wallet/dogecoin/dogecoin.dart';
@@ -90,6 +91,8 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor
return transactionPriority == bitcoinCash!.getBitcoinCashTransactionPrioritySlow();
case WalletType.polygon:
return transactionPriority == polygon!.getPolygonTransactionPrioritySlow();
case WalletType.base:
return transactionPriority == base!.getBaseTransactionPrioritySlow();
case WalletType.decred:
return transactionPriority == decred!.getDecredTransactionPrioritySlow();
case WalletType.dogecoin:
@@ -198,6 +201,9 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor
case WalletType.polygon:
_settingsStore.priority[wallet.type] = polygon!.getDefaultTransactionPriority();
break;
case WalletType.base:
_settingsStore.priority[wallet.type] = base!.getDefaultTransactionPriority();
break;
default:
break;
}
+9
View File
@@ -1,3 +1,4 @@
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/decred/decred.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart';
@@ -114,6 +115,9 @@ abstract class OutputBase with Store {
case WalletType.polygon:
_amount = polygon!.formatterPolygonParseAmount(_cryptoAmount);
break;
case WalletType.base:
_amount = base!.formatterBaseParseAmount(_cryptoAmount);
break;
case WalletType.wownero:
_amount = wownero!.formatterWowneroParseAmount(amount: _cryptoAmount);
break;
@@ -196,6 +200,10 @@ abstract class OutputBase with Store {
return polygon!.formatterPolygonAmountToDouble(amount: BigInt.from(fee));
}
if (_wallet.type == WalletType.base) {
return base!.formatterBaseAmountToDouble(amount: BigInt.from(fee));
}
if (_wallet.type == WalletType.zano) {
return zano!.formatterIntAmountToDouble(
amount: fee, currency: cryptoCurrencyHandler(), forFee: true);
@@ -328,6 +336,7 @@ abstract class OutputBase with Store {
case WalletType.monero:
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.base:
case WalletType.solana:
case WalletType.tron:
case WalletType.haven:
@@ -1,6 +1,7 @@
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/view_model/send/template_view_model.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/entities/template.dart';
@@ -55,6 +56,7 @@ abstract class SendTemplateViewModelBase with Store {
_wallet.type != WalletType.haven &&
_wallet.type != WalletType.ethereum &&
_wallet.type != WalletType.polygon &&
_wallet.type != WalletType.base &&
_wallet.type != WalletType.solana &&
_wallet.type != WalletType.tron;
+7
View File
@@ -1,5 +1,6 @@
import 'dart:async';
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/address_validator.dart';
import 'package:cake_wallet/core/amount_validator.dart';
@@ -45,6 +46,7 @@ import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_mod
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cake_wallet/zano/zano.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/exceptions.dart';
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_core/sync_status.dart';
@@ -215,6 +217,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
switch (type) {
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.base:
case WalletType.tron:
case WalletType.solana:
return wallet.currency;
@@ -708,6 +711,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
case WalletType.polygon:
return polygon!.createPolygonTransactionCredentials(outputs,
priority: priority!, currency: selectedCryptoCurrency);
case WalletType.base:
return base!.createBaseTransactionCredentials(outputs,
priority: priority!, currency: selectedCryptoCurrency);
case WalletType.solana:
return solana!
.createSolanaTransactionCredentials(outputs, currency: selectedCryptoCurrency);
@@ -833,6 +839,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
}
if (walletType == WalletType.ethereum ||
walletType == WalletType.polygon ||
walletType == WalletType.base ||
walletType == WalletType.haven) {
if (errorMessage.contains('gas required exceeds allowance')) {
return S.current.gas_exceeds_allowance;
@@ -1,3 +1,4 @@
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart';
@@ -75,6 +76,9 @@ abstract class PrivacySettingsViewModelBase with Store {
@computed
bool get usePolygonScan => _settingsStore.usePolygonScan;
@computed
bool get useBaseScan => _settingsStore.useBaseScan;
@computed
bool get useTronGrid => _settingsStore.useTronGrid;
@@ -112,6 +116,8 @@ abstract class PrivacySettingsViewModelBase with Store {
bool get canUsePolygonScan => _wallet.type == WalletType.polygon;
bool get canUseBaseScan => _wallet.type == WalletType.base;
bool get canUseTronGrid => _wallet.type == WalletType.tron;
bool get canUseMempoolFeeAPI => _wallet.type == WalletType.bitcoin;
@@ -177,6 +183,12 @@ abstract class PrivacySettingsViewModelBase with Store {
polygon!.updatePolygonScanUsageState(_wallet, value);
}
@action
void setUseBaseScan(bool value) {
_settingsStore.useBaseScan = value;
base!.updateBaseScanUsageState(_wallet, value);
}
@action
void setUseTronGrid(bool value) {
_settingsStore.useTronGrid = value;
@@ -1,5 +1,6 @@
import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/transaction_info.dart';
@@ -73,6 +74,9 @@ abstract class TransactionDetailsViewModelBase with Store {
case WalletType.polygon:
_addPolygonListItems(tx, dateFormat);
break;
case WalletType.base:
_addBaseListItems(tx, dateFormat);
break;
case WalletType.solana:
_addSolanaListItems(tx, dateFormat);
break;
@@ -198,6 +202,8 @@ abstract class TransactionDetailsViewModelBase with Store {
return 'https://${wallet.isTestnet ? "testnet" : "dcrdata"}.decred.org/tx/${txId.split(':')[0]}';
case WalletType.dogecoin:
return 'https://blockchair.com/dogecoin/transaction/${txId}';
case WalletType.base:
return 'https://basescan.org/tx/${txId}';
case WalletType.none:
return '';
}
@@ -233,6 +239,8 @@ abstract class TransactionDetailsViewModelBase with Store {
return S.current.view_transaction_on + 'explorer.zano.org';
case WalletType.decred:
return S.current.view_transaction_on + 'dcrdata.decred.org';
case WalletType.base:
return S.current.view_transaction_on + 'basescan.org';
case WalletType.none:
return '';
}
@@ -548,6 +556,56 @@ abstract class TransactionDetailsViewModelBase with Store {
items.addAll(_items);
}
void _addBaseListItems(TransactionInfo tx, DateFormat dateFormat) {
final _items = [
StandartListItem(
title: S.current.transaction_details_transaction_id,
value: tx.txHash,
key: ValueKey('standard_list_item_transaction_details_id_key'),
),
StandartListItem(
title: S.current.transaction_details_date,
value: dateFormat.format(tx.date),
key: ValueKey('standard_list_item_transaction_details_date_key'),
),
StandartListItem(
title: S.current.confirmations,
value: tx.confirmations.toString(),
key: ValueKey('standard_list_item_transaction_confirmations_key'),
),
StandartListItem(
title: S.current.transaction_details_height,
value: '${tx.height}',
key: ValueKey('standard_list_item_transaction_details_height_key'),
),
StandartListItem(
title: S.current.transaction_details_amount,
value: tx.amountFormatted(),
key: ValueKey('standard_list_item_transaction_details_amount_key'),
),
if (tx.feeFormatted()?.isNotEmpty ?? false)
StandartListItem(
title: S.current.transaction_details_fee,
value: tx.feeFormatted()!,
key: ValueKey('standard_list_item_transaction_details_fee_key'),
),
if (showRecipientAddress && tx.to != null && tx.direction == TransactionDirection.outgoing)
StandartListItem(
title: S.current.transaction_details_recipient_address,
value: tx.to!,
key: ValueKey('standard_list_item_transaction_details_recipient_address_key'),
),
if (tx.direction == TransactionDirection.incoming && tx.from != null)
StandartListItem(
title: S.current.transaction_details_source_address,
value: tx.from!,
key: ValueKey('standard_list_item_transaction_details_source_address_key'),
),
];
items.addAll(_items);
}
void _addSolanaListItems(TransactionInfo tx, DateFormat dateFormat) {
final _items = [
StandartListItem(
@@ -1,6 +1,7 @@
import 'dart:developer' as dev;
import 'dart:core';
import 'package:cake_wallet/base/base.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/fiat_conversion_service.dart';
import 'package:cake_wallet/core/payment_uris.dart';
@@ -30,6 +31,7 @@ import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_i
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cw_core/amount_converter.dart';
import 'package:cw_core/currency.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:intl/intl.dart';
import 'package:mobx/mobx.dart';
@@ -38,7 +40,6 @@ part 'wallet_address_list_view_model.g.dart';
class WalletAddressListViewModel = WalletAddressListViewModelBase with _$WalletAddressListViewModel;
abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewModel with Store {
WalletAddressListViewModelBase({
required AppStore appStore,
@@ -143,6 +144,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
return DecredURI(amount: amount, address: address.address);
case WalletType.dogecoin:
return DogeURI(amount: amount, address: address.address);
case WalletType.base:
return BaseURI(amount: amount, address: address.address);
case WalletType.none:
throw Exception('Unexpected type: ${type.toString()}');
}
@@ -263,6 +266,12 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
}
if (wallet.type == WalletType.base) {
final primaryAddress = base!.getAddress(wallet);
addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
}
if (wallet.type == WalletType.solana) {
final primaryAddress = solana!.getAddress(wallet);
@@ -435,7 +444,12 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
'assets/images/zano_icon.svg',
'assets/images/more_tokens.svg',
];
case WalletType.base:
return [
'assets/images/eth_icon.svg',
'assets/images/usdc_icon.svg',
'assets/images/more_tokens.svg',
];
default:
return [];
}
@@ -457,6 +471,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
return 'assets/images/trx_chain_mono.svg';
case WalletType.zano:
return 'assets/images/zano_chain_mono.svg';
case WalletType.base:
return 'assets/images/base_chain_mono.svg';
default:
return 'assets/images/eth_chain_mono.svg';
}
@@ -126,6 +126,7 @@ abstract class WalletKeysViewModelBase with Store {
break;
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.base:
case WalletType.solana:
case WalletType.tron:
items.addAll([
@@ -252,6 +253,8 @@ abstract class WalletKeysViewModelBase with Store {
return 'banano-wallet';
case WalletType.polygon:
return 'polygon-wallet';
case WalletType.base:
return 'base-wallet';
case WalletType.solana:
return 'solana-wallet';
case WalletType.tron:

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