CW-551-Refactor-EVM-Chains (#1256)

* feat: Create central package for EVM chains * chore: Cleanup pubspec and add core evm dependencies * feat: Replicated core evm chain files, time to start fixing the issues * feat: Setup evm central package to handle all evm chains * feat: Link up Polygon and Ethereum wallets to the centra evm package, fix bugs and issues, and optimze for better performance * feat: Setup and adjust configs to reflect new evm configurations * Remove unneeded file * fix: Changes done while re-reviewing entire structure and refactor * fix: Add evm chain wallet path to imports in configure file * feat: Adjust implementation of parent class, remove unneeded files, remove windows, linux and mac directories, restructure the evm child classes * fix: Make EVMChainWallet a central abstract class and adjust accordingly * fix: Adjust transaction info, restructure EVMWalletChain to be an abstract, adjust external facing interfaces for polygon and ethereum, adjust configuration for ethereum and polygon in configure file * fix: Testing issues * fix: Add localization for nft tile and details page texts and add dashes for null responses * fix: merge conflicts * Minor fixes for building Monero.com --------- Co-authored-by: OmarHatem <omarh.ismail1@gmail.com>

Adegoke David committed Jan 30, 2024 at 19:01 UTC 7410daacff6f35e12e079ab45cc1c15d2453c600
90 files changed +1755 -2021
.github/workflows/pr_test_build.yml
+4 -6
@@ -105,21 +105,19 @@ jobs:
105 run: |
106 cd /opt/android/cake_wallet
107 cd cw_core && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
108 + cd cw_evm && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
109 cd cw_monero && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
110 cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
111 cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
111 - cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
112 cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
113 cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
114 - cd cw_polygon && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
114 flutter packages pub run build_runner build --delete-conflicting-outputs
115
116 - name: Add secrets
117 run: |
118 cd /opt/android/cake_wallet
119 touch lib/.secrets.g.dart
121 - touch cw_ethereum/lib/.secrets.g.dart
122 - touch cw_polygon/lib/.secrets.g.dart
120 + touch cw_evm/lib/.secrets.g.dart
121 echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart
122 echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart
123 echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart
@@ -146,14 +144,14 @@ jobs:
144 echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart
145 echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart
146 echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart
149 - echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_ethereum/lib/.secrets.g.dart
147 + echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
148 echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart
149 echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart
150 echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart
151 echo "const robinhoodCIdApiSecret = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart
152 echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart
153 echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart
156 - echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_ethereum/lib/.secrets.g.dart
154 + echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
155
156 - name: Rename app
157 run: echo -e "id=com.cakewallet.test\nname=${{ env.BRANCH_NAME }}" > /opt/android/cake_wallet/android/app.properties
.gitignore
+2 -1
@@ -90,9 +90,10 @@ android/key.properties
90 **/tool/.secrets-prod.json
91 **/tool/.secrets-test.json
92 **/tool/.secrets-config.json
93 +**/tool/.evm-secrets-config.json
94 **/tool/.ethereum-secrets-config.json
95 **/lib/.secrets.g.dart
95 -**/cw_ethereum/lib/.secrets.g.dart
96 +**/cw_evm/lib/.secrets.g.dart
97
98 vendor/
99
configure_cake_wallet.sh
+1 -2
@@ -24,11 +24,10 @@ source ./app_env.sh cakewallet
24 cd ../.. && flutter pub get
25 flutter packages pub run tool/generate_localization.dart
26 cd cw_core && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
27 +cd cw_evm && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
28 cd cw_monero && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
29 cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
30 cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
30 -cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
31 cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
32 cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
33 -cd cw_polygon && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
33 flutter packages pub run build_runner build --delete-conflicting-outputs
cw_ethereum/lib/default_ethereum_erc20_tokens.dart
+1 -1
@@ -1,7 +1,7 @@
1 import 'package:cw_core/crypto_currency.dart';
2 import 'package:cw_core/erc20_token.dart';
3
4 -class DefaultErc20Tokens {
4 +class DefaultEthereumErc20Tokens {
5 final List<Erc20Token> _defaultTokens = [
6 Erc20Token(
7 name: "USD Coin",
cw_ethereum/lib/ethereum_client.dart
+15 -245
@@ -1,229 +1,22 @@
1 -import 'dart:async';
1 import 'dart:convert';
2 +import 'dart:developer';
3 +import 'dart:typed_data';
4
4 -import 'package:cw_core/crypto_currency.dart';
5 -import 'package:cw_ethereum/erc20_balance.dart';
6 -import 'package:cw_core/erc20_token.dart';
7 -import 'package:cw_ethereum/ethereum_transaction_model.dart';
8 -import 'package:cw_ethereum/pending_ethereum_transaction.dart';
9 -import 'package:flutter/services.dart';
10 -import 'package:http/http.dart';
5 +import 'package:cw_evm/evm_chain_client.dart';
6 +import 'package:cw_evm/.secrets.g.dart' as secrets;
7 +import 'package:cw_evm/evm_chain_transaction_model.dart';
8 import 'package:web3dart/web3dart.dart';
12 -import 'package:erc20/erc20.dart';
13 -import 'package:cw_core/node.dart';
14 -import 'package:cw_ethereum/ethereum_transaction_priority.dart';
15 -import 'package:cw_ethereum/.secrets.g.dart' as secrets;
16 -
17 -class EthereumClient {
18 - final httpClient = Client();
19 - Web3Client? _client;
20 -
21 - bool connect(Node node) {
22 - try {
23 - _client = Web3Client(node.uri.toString(), httpClient);
24 -
25 - return true;
26 - } catch (e) {
27 - return false;
28 - }
29 - }
30 -
31 - void setListeners(EthereumAddress userAddress, Function() onNewTransaction) async {
32 - // _client?.pendingTransactions().listen((transactionHash) async {
33 - // final transaction = await _client!.getTransactionByHash(transactionHash);
34 - //
35 - // if (transaction.from.hex == userAddress || transaction.to?.hex == userAddress) {
36 - // onNewTransaction();
37 - // }
38 - // });
39 - }
40 -
41 - Future<EtherAmount> getBalance(EthereumAddress address) async {
42 - try {
43 - return await _client!.getBalance(address);
44 - } catch (_) {
45 - return EtherAmount.zero();
46 - }
47 - }
48 -
49 - Future<int> getGasUnitPrice() async {
50 - try {
51 - final gasPrice = await _client!.getGasPrice();
52 - return gasPrice.getInWei.toInt();
53 - } catch (_) {
54 - return 0;
55 - }
56 - }
57 -
58 - Future<int> getEstimatedGas() async {
59 - try {
60 - final estimatedGas = await _client!.estimateGas();
61 - return estimatedGas.toInt();
62 - } catch (_) {
63 - return 0;
64 - }
65 - }
66 -
67 - Future<PendingEthereumTransaction> signTransaction({
68 - required EthPrivateKey privateKey,
69 - required String toAddress,
70 - required String amount,
71 - required int gas,
72 - required EthereumTransactionPriority priority,
73 - required CryptoCurrency currency,
74 - required int exponent,
75 - String? contractAddress,
76 - }) async {
77 - assert(currency == CryptoCurrency.eth ||
78 - currency == CryptoCurrency.maticpoly ||
79 - contractAddress != null);
80 -
81 - bool _isEVMCompatibleChain =
82 - currency == CryptoCurrency.eth || currency == CryptoCurrency.maticpoly;
83 -
84 - final price = _client!.getGasPrice();
85 -
86 - final Transaction transaction = createTransaction(
87 - from: privateKey.address,
88 - to: EthereumAddress.fromHex(toAddress),
89 - maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip),
90 - amount: _isEVMCompatibleChain ? EtherAmount.inWei(BigInt.parse(amount)) : EtherAmount.zero(),
91 - );
92 -
93 - final signedTransaction =
94 - await _client!.signTransaction(privateKey, transaction, chainId: chainId);
95 -
96 - final Function _sendTransaction;
97 -
98 - if (_isEVMCompatibleChain) {
99 - _sendTransaction = () async => await sendTransaction(signedTransaction);
100 - } else {
101 - final erc20 = ERC20(
102 - client: _client!,
103 - address: EthereumAddress.fromHex(contractAddress!),
104 - chainId: chainId,
105 - );
106 -
107 - _sendTransaction = () async {
108 - await erc20.transfer(
109 - EthereumAddress.fromHex(toAddress),
110 - BigInt.parse(amount),
111 - credentials: privateKey,
112 - transaction: transaction,
113 - );
114 - };
115 - }
116 -
117 - return PendingEthereumTransaction(
118 - signedTransaction: signedTransaction,
119 - amount: amount,
120 - fee: BigInt.from(gas) * (await price).getInWei,
121 - sendTransaction: _sendTransaction,
122 - exponent: exponent,
123 - );
124 - }
9
10 +class EthereumClient extends EVMChainClient {
11 + @override
12 int get chainId => 1;
13
128 - Transaction createTransaction({
129 - required EthereumAddress from,
130 - required EthereumAddress to,
131 - required EtherAmount amount,
132 - EtherAmount? maxPriorityFeePerGas,
133 - }) {
134 - return Transaction(
135 - from: from,
136 - to: to,
137 - maxPriorityFeePerGas: maxPriorityFeePerGas,
138 - value: amount,
139 - );
140 - }
141 -
142 - Future<String> sendTransaction(Uint8List signedTransaction) async =>
143 - await _client!.sendRawTransaction(prepareSignedTransactionForSending(signedTransaction));
144 -
14 + @override
15 Uint8List prepareSignedTransactionForSending(Uint8List signedTransaction) =>
16 prependTransactionType(0x02, signedTransaction);
17
148 - Future getTransactionDetails(String transactionHash) async {
149 - // Wait for the transaction receipt to become available
150 - TransactionReceipt? receipt;
151 - while (receipt == null) {
152 - receipt = await _client!.getTransactionReceipt(transactionHash);
153 - await Future.delayed(Duration(seconds: 1));
154 - }
155 -
156 - // Print the receipt information
157 - print('Transaction Hash: ${receipt.transactionHash}');
158 - print('Block Hash: ${receipt.blockHash}');
159 - print('Block Number: ${receipt.blockNumber}');
160 - print('Gas Used: ${receipt.gasUsed}');
161 -
162 - /*
163 - Transaction Hash: [112, 244, 4, 238, 89, 199, 171, 191, 210, 236, 110, 42, 185, 202, 220, 21, 27, 132, 123, 221, 137, 90, 77, 13, 23, 43, 12, 230, 93, 63, 221, 116]
164 -I/flutter ( 4474): Block Hash: [149, 44, 250, 119, 111, 104, 82, 98, 17, 89, 30, 190, 25, 44, 218, 118, 127, 189, 241, 35, 213, 106, 25, 95, 195, 37, 55, 131, 185, 180, 246, 200]
165 -I/flutter ( 4474): Block Number: 17120242
166 -I/flutter ( 4474): Gas Used: 21000
167 - */
168 -
169 - // Wait for the transaction receipt to become available
170 - TransactionInformation? transactionInformation;
171 - while (transactionInformation == null) {
172 - print("********************************");
173 - transactionInformation = await _client!.getTransactionByHash(transactionHash);
174 - await Future.delayed(Duration(seconds: 1));
175 - }
176 - // Print the receipt information
177 - print('Transaction Hash: ${transactionInformation.hash}');
178 - print('Block Hash: ${transactionInformation.blockHash}');
179 - print('Block Number: ${transactionInformation.blockNumber}');
180 - print('Gas Used: ${transactionInformation.gas}');
181 -
182 - /*
183 - Transaction Hash: 0x70f404ee59c7abbfd2ec6e2ab9cadc151b847bdd895a4d0d172b0ce65d3fdd74
184 -I/flutter ( 4474): Block Hash: 0x952cfa776f68526211591ebe192cda767fbdf123d56a195fc3253783b9b4f6c8
185 -I/flutter ( 4474): Block Number: 17120242
186 -I/flutter ( 4474): Gas Used: 53000
187 - */
188 - }
189 -
190 - Future<ERC20Balance> fetchERC20Balances(
191 - EthereumAddress userAddress, String contractAddress) async {
192 - final erc20 = ERC20(address: EthereumAddress.fromHex(contractAddress), client: _client!);
193 - try {
194 - final balance = await erc20.balanceOf(userAddress);
195 -
196 - int exponent = (await erc20.decimals()).toInt();
197 -
198 - return ERC20Balance(balance, exponent: exponent);
199 - } catch (_) {
200 - return ERC20Balance(BigInt.zero);
201 - }
202 - }
203 -
204 - Future<Erc20Token?> getErc20Token(String contractAddress) async {
205 - try {
206 - final erc20 = ERC20(address: EthereumAddress.fromHex(contractAddress), client: _client!);
207 - final name = await erc20.name();
208 - final symbol = await erc20.symbol();
209 - final decimal = await erc20.decimals();
210 -
211 - return Erc20Token(
212 - name: name,
213 - symbol: symbol,
214 - contractAddress: contractAddress,
215 - decimal: decimal.toInt(),
216 - );
217 - } catch (e) {
218 - return null;
219 - }
220 - }
221 -
222 - void stop() {
223 - _client?.dispose();
224 - }
225 -
226 - Future<List<EthereumTransactionModel>> fetchTransactions(String address,
18 + @override
19 + Future<List<EVMChainTransactionModel>> fetchTransactions(String address,
20 {String? contractAddress}) async {
21 try {
22 final response = await httpClient.get(Uri.https("api.etherscan.io", "/api", {
@@ -234,41 +27,18 @@ I/flutter ( 4474): Gas Used: 53000
27 "apikey": secrets.etherScanApiKey,
28 }));
29
237 - final _jsonResponse = json.decode(response.body) as Map<String, dynamic>;
30 + final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
31
239 - if (response.statusCode >= 200 && response.statusCode < 300 && _jsonResponse['status'] != 0) {
240 - return (_jsonResponse['result'] as List)
241 - .map((e) => EthereumTransactionModel.fromJson(e as Map<String, dynamic>))
32 + if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) {
33 + return (jsonResponse['result'] as List)
34 + .map((e) => EVMChainTransactionModel.fromJson(e as Map<String, dynamic>, 'ETH'))
35 .toList();
36 }
37
38 return [];
39 } catch (e) {
247 - print(e);
40 + log(e.toString());
41 return [];
42 }
43 }
251 -
252 - Web3Client? getWeb3Client() {
253 - return _client;
254 - }
255 -
256 -// Future<int> _getDecimalPlacesForContract(DeployedContract contract) async {
257 -// final String abi = await rootBundle.loadString("assets/abi_json/erc20_abi.json");
258 -// final contractAbi = ContractAbi.fromJson(abi, "ERC20");
259 -//
260 -// final contract = DeployedContract(
261 -// contractAbi,
262 -// EthereumAddress.fromHex(_erc20Currencies[erc20Currency]!),
263 -// );
264 -// final decimalsFunction = contract.function('decimals');
265 -// final decimals = await _client!.call(
266 -// contract: contract,
267 -// function: decimalsFunction,
268 -// params: [],
269 -// );
270 -//
271 -// int exponent = int.parse(decimals.first.toString());
272 -// return exponent;
273 -// }
44 }
cw_ethereum/lib/ethereum_exceptions.dart deleted
-11
@@ -1,11 +0,0 @@
1 -import 'package:cw_core/crypto_currency.dart';
2 -
3 -class EthereumTransactionCreationException implements Exception {
4 - final String exceptionMessage;
5 -
6 - EthereumTransactionCreationException(CryptoCurrency currency) :
7 - this.exceptionMessage = 'Wrong balance. Not enough ${currency.title} on your balance.';
8 -
9 - @override
10 - String toString() => exceptionMessage;
11 -}
cw_ethereum/lib/ethereum_formatter.dart deleted
-25
@@ -1,25 +0,0 @@
1 -import 'package:intl/intl.dart';
2 -
3 -const ethereumAmountLength = 12;
4 -const ethereumAmountDivider = 1000000000000;
5 -final ethereumAmountFormat = NumberFormat()
6 - ..maximumFractionDigits = ethereumAmountLength
7 - ..minimumFractionDigits = 1;
8 -
9 -class EthereumFormatter {
10 - static int parseEthereumAmount(String amount) {
11 - try {
12 - return (double.parse(amount) * ethereumAmountDivider).round();
13 - } catch (_) {
14 - return 0;
15 - }
16 - }
17 -
18 - static double parseEthereumAmountToDouble(int amount) {
19 - try {
20 - return amount / ethereumAmountDivider;
21 - } catch (_) {
22 - return 0;
23 - }
24 - }
25 -}
cw_ethereum/lib/ethereum_mnemonics_exception.dart new
+5
@@ -0,0 +1,5 @@
1 +class EthereumMnemonicIsIncorrectException implements Exception {
2 + @override
3 + String toString() =>
4 + 'Ethereum mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.';
5 +}
cw_ethereum/lib/ethereum_transaction_history.dart
+10 -69
@@ -1,77 +1,18 @@
1 -import 'dart:convert';
1 import 'dart:core';
3 -import 'package:cw_core/pathForWallet.dart';
4 -import 'package:cw_core/wallet_info.dart';
5 -import 'package:cw_ethereum/file.dart';
6 -import 'package:mobx/mobx.dart';
7 -import 'package:cw_core/transaction_history.dart';
2 import 'package:cw_ethereum/ethereum_transaction_info.dart';
3 +import 'package:cw_evm/evm_chain_transaction_history.dart';
4 +import 'package:cw_evm/evm_chain_transaction_info.dart';
5
10 -part 'ethereum_transaction_history.g.dart';
11 -
12 -const transactionsHistoryFileName = 'transactions.json';
13 -
14 -class EthereumTransactionHistory = EthereumTransactionHistoryBase with _$EthereumTransactionHistory;
15 -
16 -abstract class EthereumTransactionHistoryBase
17 - extends TransactionHistoryBase<EthereumTransactionInfo> with Store {
18 - EthereumTransactionHistoryBase({required this.walletInfo, required String password})
19 - : _password = password {
20 - transactions = ObservableMap<String, EthereumTransactionInfo>();
21 - }
22 -
23 - final WalletInfo walletInfo;
24 - String _password;
25 -
26 - Future<void> init() async => await _load();
27 -
28 - @override
29 - Future<void> save() async {
30 - try {
31 - final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
32 - final path = '$dirPath/$transactionsHistoryFileName';
33 - final data = json.encode({'transactions': transactions});
34 - await writeData(path: path, password: _password, data: data);
35 - } catch (e, s) {
36 - print('Error while save ethereum transaction history: ${e.toString()}');
37 - print(s);
38 - }
39 - }
6 +class EthereumTransactionHistory extends EVMChainTransactionHistory {
7 + EthereumTransactionHistory({
8 + required super.walletInfo,
9 + required super.password,
10 + });
11
12 @override
42 - void addOne(EthereumTransactionInfo transaction) => transactions[transaction.id] = transaction;
13 + String getTransactionHistoryFileName() => 'transactions.json';
14
15 @override
45 - void addMany(Map<String, EthereumTransactionInfo> transactions) =>
46 - this.transactions.addAll(transactions);
47 -
48 - Future<Map<String, dynamic>> _read() async {
49 - final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
50 - final path = '$dirPath/$transactionsHistoryFileName';
51 - final content = await read(path: path, password: _password);
52 - if (content.isEmpty) {
53 - return {};
54 - }
55 - return json.decode(content) as Map<String, dynamic>;
56 - }
57 -
58 - Future<void> _load() async {
59 - try {
60 - final content = await _read();
61 - final txs = content['transactions'] as Map<String, dynamic>? ?? {};
62 -
63 - txs.entries.forEach((entry) {
64 - final val = entry.value;
65 -
66 - if (val is Map<String, dynamic>) {
67 - final tx = EthereumTransactionInfo.fromJson(val);
68 - _update(tx);
69 - }
70 - });
71 - } catch (e) {
72 - print(e);
73 - }
74 - }
75 -
76 - void _update(EthereumTransactionInfo transaction) => transactions[transaction.id] = transaction;
16 + EVMChainTransactionInfo getTransactionInfo(Map<String, dynamic> val) =>
17 + EthereumTransactionInfo.fromJson(val);
18 }
cw_ethereum/lib/ethereum_transaction_info.dart
+18 -64
@@ -1,57 +1,21 @@
1 -import 'dart:math';
2 -
3 -import 'package:cw_core/format_amount.dart';
1 import 'package:cw_core/transaction_direction.dart';
5 -import 'package:cw_core/transaction_info.dart';
2 +import 'package:cw_evm/evm_chain_transaction_info.dart';
3
7 -class EthereumTransactionInfo extends TransactionInfo {
4 +class EthereumTransactionInfo extends EVMChainTransactionInfo {
5 EthereumTransactionInfo({
9 - required this.id,
10 - required this.height,
11 - required this.ethAmount,
12 - required this.ethFee,
13 - this.tokenSymbol = "ETH",
14 - this.exponent = 18,
15 - required this.direction,
16 - required this.isPending,
17 - required this.date,
18 - required this.confirmations,
19 - required this.to,
20 - }) : this.amount = ethAmount.toInt(),
21 - this.fee = ethFee.toInt();
22 -
23 - final String id;
24 - final int height;
25 - final int amount;
26 - final BigInt ethAmount;
27 - final int exponent;
28 - final TransactionDirection direction;
29 - final DateTime date;
30 - final bool isPending;
31 - final int fee;
32 - final BigInt ethFee;
33 - final int confirmations;
34 - final String tokenSymbol;
35 - String? _fiatAmount;
36 - final String? to;
37 -
38 - @override
39 - String amountFormatted() {
40 - final amount = formatAmount((ethAmount / BigInt.from(10).pow(exponent)).toString());
41 - return '${amount.substring(0, min(10, amount.length))} $tokenSymbol';
42 - }
43 -
44 - @override
45 - String fiatAmount() => _fiatAmount ?? '';
46 -
47 - @override
48 - void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
49 -
50 - @override
51 - String feeFormatted() {
52 - final amount = (ethFee / BigInt.from(10).pow(18)).toString();
53 - return '${amount.substring(0, min(10, amount.length))} ETH';
54 - }
6 + required super.id,
7 + required super.height,
8 + required super.ethAmount,
9 + required super.ethFee,
10 + required super.tokenSymbol,
11 + required super.direction,
12 + required super.isPending,
13 + required super.date,
14 + required super.confirmations,
15 + required super.to,
16 + required super.from,
17 + super.exponent,
18 + });
19
20 factory EthereumTransactionInfo.fromJson(Map<String, dynamic> data) {
21 return EthereumTransactionInfo(
@@ -66,20 +30,10 @@ class EthereumTransactionInfo extends TransactionInfo {
30 confirmations: data['confirmations'] as int,
31 tokenSymbol: data['tokenSymbol'] as String,
32 to: data['to'],
33 + from: data['from'],
34 );
35 }
36
72 - Map<String, dynamic> toJson() => {
73 - 'id': id,
74 - 'height': height,
75 - 'amount': ethAmount.toString(),
76 - 'exponent': exponent,
77 - 'fee': ethFee.toString(),
78 - 'direction': direction.index,
79 - 'date': date.millisecondsSinceEpoch,
80 - 'isPending': isPending,
81 - 'confirmations': confirmations,
82 - 'tokenSymbol': tokenSymbol,
83 - 'to': to,
84 - };
37 + @override
38 + String get feeCurrency => 'ETH';
39 }
cw_ethereum/lib/ethereum_transaction_priority.dart deleted
-52
@@ -1,52 +0,0 @@
1 -import 'package:cw_core/transaction_priority.dart';
2 -
3 -class EthereumTransactionPriority extends TransactionPriority {
4 - final int tip;
5 -
6 - const EthereumTransactionPriority({required String title, required int raw, required this.tip})
7 - : super(title: title, raw: raw);
8 -
9 - static const List<EthereumTransactionPriority> all = [fast, medium, slow];
10 - static const EthereumTransactionPriority slow =
11 - EthereumTransactionPriority(title: 'slow', raw: 0, tip: 1);
12 - static const EthereumTransactionPriority medium =
13 - EthereumTransactionPriority(title: 'Medium', raw: 1, tip: 2);
14 - static const EthereumTransactionPriority fast =
15 - EthereumTransactionPriority(title: 'Fast', raw: 2, tip: 4);
16 -
17 - static EthereumTransactionPriority deserialize({required int raw}) {
18 - switch (raw) {
19 - case 0:
20 - return slow;
21 - case 1:
22 - return medium;
23 - case 2:
24 - return fast;
25 - default:
26 - throw Exception('Unexpected token: $raw for EthereumTransactionPriority deserialize');
27 - }
28 - }
29 -
30 - String get units => 'gas';
31 -
32 - @override
33 - String toString() {
34 - var label = '';
35 -
36 - switch (this) {
37 - case EthereumTransactionPriority.slow:
38 - label = 'Slow';
39 - break;
40 - case EthereumTransactionPriority.medium:
41 - label = 'Medium';
42 - break;
43 - case EthereumTransactionPriority.fast:
44 - label = 'Fast';
45 - break;
46 - default:
47 - break;
48 - }
49 -
50 - return label;
51 - }
52 -}
cw_ethereum/lib/ethereum_wallet.dart
+72 -477
@@ -1,126 +1,58 @@
1 -import 'dart:async';
1 import 'dart:convert';
3 -import 'dart:io';
4 -import 'dart:math';
2
6 -import 'package:cw_core/crypto_currency.dart';
3 import 'package:cw_core/cake_hive.dart';
8 -import 'package:cw_core/node.dart';
4 +import 'package:cw_core/crypto_currency.dart';
5 +import 'package:cw_core/erc20_token.dart';
6 import 'package:cw_core/pathForWallet.dart';
10 -import 'package:cw_core/pending_transaction.dart';
11 -import 'package:cw_core/sync_status.dart';
7 import 'package:cw_core/transaction_direction.dart';
13 -import 'package:cw_core/transaction_priority.dart';
14 -import 'package:cw_core/wallet_addresses.dart';
15 -import 'package:cw_core/wallet_base.dart';
8 import 'package:cw_core/wallet_info.dart';
9 import 'package:cw_ethereum/default_ethereum_erc20_tokens.dart';
18 -import 'package:cw_ethereum/erc20_balance.dart';
10 import 'package:cw_ethereum/ethereum_client.dart';
20 -import 'package:cw_ethereum/ethereum_exceptions.dart';
21 -import 'package:cw_ethereum/ethereum_formatter.dart';
22 -import 'package:cw_ethereum/ethereum_transaction_credentials.dart';
11 import 'package:cw_ethereum/ethereum_transaction_history.dart';
12 import 'package:cw_ethereum/ethereum_transaction_info.dart';
25 -import 'package:cw_ethereum/ethereum_transaction_model.dart';
26 -import 'package:cw_ethereum/ethereum_transaction_priority.dart';
27 -import 'package:cw_ethereum/ethereum_wallet_addresses.dart';
28 -import 'package:cw_ethereum/file.dart';
29 -import 'package:cw_core/erc20_token.dart';
30 -import 'package:hive/hive.dart';
31 -import 'package:hex/hex.dart';
32 -import 'package:mobx/mobx.dart';
33 -import 'package:shared_preferences/shared_preferences.dart';
34 -import 'package:web3dart/crypto.dart';
35 -import 'package:web3dart/web3dart.dart';
36 -import 'package:bip39/bip39.dart' as bip39;
37 -import 'package:bip32/bip32.dart' as bip32;
38 -
39 -part 'ethereum_wallet.g.dart';
13 +import 'package:cw_evm/evm_chain_transaction_history.dart';
14 +import 'package:cw_evm/evm_chain_transaction_info.dart';
15 +import 'package:cw_evm/evm_chain_transaction_model.dart';
16 +import 'package:cw_evm/evm_chain_wallet.dart';
17 +import 'package:cw_evm/evm_erc20_balance.dart';
18 +import 'package:cw_evm/file.dart';
19 +
20 +class EthereumWallet extends EVMChainWallet {
21 + EthereumWallet({
22 + required super.client,
23 + required super.password,
24 + required super.walletInfo,
25 + super.mnemonic,
26 + super.initialBalance,
27 + super.privateKey,
28 + }) : super(nativeCurrency: CryptoCurrency.eth);
29
41 -class EthereumWallet = EthereumWalletBase with _$EthereumWallet;
42 -
43 -abstract class EthereumWalletBase
44 - extends WalletBase<ERC20Balance, EthereumTransactionHistory, EthereumTransactionInfo>
45 - with Store {
46 - EthereumWalletBase({
47 - required WalletInfo walletInfo,
48 - String? mnemonic,
49 - String? privateKey,
50 - required String password,
51 - ERC20Balance? initialBalance,
52 - }) : syncStatus = NotConnectedSyncStatus(),
53 - _password = password,
54 - _mnemonic = mnemonic,
55 - _hexPrivateKey = privateKey,
56 - _isTransactionUpdating = false,
57 - _client = EthereumClient(),
58 - walletAddresses = EthereumWalletAddresses(walletInfo),
59 - balance = ObservableMap<CryptoCurrency, ERC20Balance>.of(
60 - {CryptoCurrency.eth: initialBalance ?? ERC20Balance(BigInt.zero)}),
61 - super(walletInfo) {
62 - this.walletInfo = walletInfo;
63 - transactionHistory = EthereumTransactionHistory(walletInfo: walletInfo, password: password);
30 + @override
31 + void addInitialTokens() {
32 + final initialErc20Tokens = DefaultEthereumErc20Tokens().initialErc20Tokens;
33
65 - if (!CakeHive.isAdapterRegistered(Erc20Token.typeId)) {
66 - CakeHive.registerAdapter(Erc20TokenAdapter());
34 + for (var token in initialErc20Tokens) {
35 + evmChainErc20TokensBox.put(token.contractAddress, token);
36 }
68 -
69 - _sharedPrefs.complete(SharedPreferences.getInstance());
37 }
38
72 - final String? _mnemonic;
73 - final String? _hexPrivateKey;
74 - final String _password;
75 -
76 - late final Box<Erc20Token> erc20TokensBox;
77 -
78 - late final Box<Erc20Token> ethereumErc20TokensBox;
79 -
80 - late final EthPrivateKey _ethPrivateKey;
81 -
82 - EthPrivateKey get ethPrivateKey => _ethPrivateKey;
83 -
84 - late EthereumClient _client;
85 -
86 - int? _gasPrice;
87 - int? _estimatedGas;
88 - bool _isTransactionUpdating;
89 -
90 - // TODO: remove after integrating our own node and having eth_newPendingTransactionFilter
91 - Timer? _transactionsUpdateTimer;
92 -
93 - @override
94 - WalletAddresses walletAddresses;
95 -
39 @override
97 - @observable
98 - SyncStatus syncStatus;
40 + Future<bool> checkIfScanProviderIsEnabled() async {
41 + bool isEtherscanEnabled = (await sharedPrefs.future).getBool("use_etherscan") ?? true;
42 + return isEtherscanEnabled;
43 + }
44
45 @override
101 - @observable
102 - late ObservableMap<CryptoCurrency, ERC20Balance> balance;
103 -
104 - Completer<SharedPreferences> _sharedPrefs = Completer();
105 -
106 - Future<void> init() async {
46 + Future<void> initErc20TokensBox() async {
47 + // This is for ethereum wallets,
48 + // Other wallets would override and initialize their respective boxes with their boxNames.
49 await movePreviousErc20BoxConfigsToNewBox();
108 -
109 - await walletAddresses.init();
110 - await transactionHistory.init();
111 - _ethPrivateKey = await getPrivateKey(
112 - mnemonic: _mnemonic,
113 - privateKey: _hexPrivateKey,
114 - password: _password,
115 - );
116 - walletAddresses.address = _ethPrivateKey.address.toString();
117 - await save();
50 }
51
52 /// Majorly for backward compatibility for previous configs that have been set.
53 Future<void> movePreviousErc20BoxConfigsToNewBox() async {
54 // Opens a box specific to this wallet
123 - ethereumErc20TokensBox = await CakeHive.openBox<Erc20Token>(
55 + evmChainErc20TokensBox = await CakeHive.openBox<Erc20Token>(
56 "${walletInfo.name.replaceAll(" ", "_")}_${Erc20Token.ethereumBoxName}");
57
58 //Open the previous token configs box
@@ -130,7 +62,7 @@ abstract class EthereumWalletBase
62 if (erc20TokensBox.isEmpty) {
63 // If it's empty, but the new wallet specific box is also empty,
64 // we load the initial tokens to the new box.
133 - if (ethereumErc20TokensBox.isEmpty) addInitialTokens();
65 + if (evmChainErc20TokensBox.isEmpty) addInitialTokens();
66 return;
67 }
68
@@ -141,250 +73,61 @@ abstract class EthereumWalletBase
73 await erc20TokensBox.deleteFromDisk();
74
75 // Add all the previous tokens with configs to the new box
144 - ethereumErc20TokensBox.addAll(allValues);
145 - }
146 -
147 - @override
148 - int calculateEstimatedFee(TransactionPriority priority, int? amount) {
149 - try {
150 - if (priority is EthereumTransactionPriority) {
151 - final priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
152 - return (_gasPrice! + priorityFee) * (_estimatedGas ?? 0);
153 - }
154 -
155 - return 0;
156 - } catch (e) {
157 - return 0;
158 - }
159 - }
160 -
161 - @override
162 - Future<void> changePassword(String password) {
163 - throw UnimplementedError("changePassword");
164 - }
165 -
166 - @override
167 - void close() {
168 - _client.stop();
169 - _transactionsUpdateTimer?.cancel();
170 - }
171 -
172 - @action
173 - @override
174 - Future<void> connectToNode({required Node node}) async {
175 - try {
176 - syncStatus = ConnectingSyncStatus();
177 -
178 - final isConnected = _client.connect(node);
179 -
180 - if (!isConnected) {
181 - throw Exception("Ethereum Node connection failed");
182 - }
183 -
184 - _client.setListeners(_ethPrivateKey.address, _onNewTransaction);
185 -
186 - _setTransactionUpdateTimer();
187 -
188 - syncStatus = ConnectedSyncStatus();
189 - } catch (e) {
190 - syncStatus = FailedSyncStatus();
191 - }
192 - }
193 -
194 - @override
195 - Future<PendingTransaction> createTransaction(Object credentials) async {
196 - final _credentials = credentials as EthereumTransactionCredentials;
197 - final outputs = _credentials.outputs;
198 - final hasMultiDestination = outputs.length > 1;
199 -
200 - final CryptoCurrency transactionCurrency =
201 - balance.keys.firstWhere((element) => element.title == _credentials.currency.title);
202 -
203 - final _erc20Balance = balance[transactionCurrency]!;
204 - BigInt totalAmount = BigInt.zero;
205 - int exponent = transactionCurrency is Erc20Token ? transactionCurrency.decimal : 18;
206 - num amountToEthereumMultiplier = pow(10, exponent);
207 -
208 - // so far this can not be made with Ethereum as Ethereum does not support multiple recipients
209 - if (hasMultiDestination) {
210 - if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) {
211 - throw EthereumTransactionCreationException(transactionCurrency);
212 - }
213 -
214 - final totalOriginalAmount = EthereumFormatter.parseEthereumAmountToDouble(
215 - outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0)));
216 - totalAmount = BigInt.from(totalOriginalAmount * amountToEthereumMultiplier);
217 -
218 - if (_erc20Balance.balance < totalAmount) {
219 - throw EthereumTransactionCreationException(transactionCurrency);
220 - }
221 - } else {
222 - final output = outputs.first;
223 - // since the fees are taken from Ethereum
224 - // then no need to subtract the fees from the amount if send all
225 - final BigInt allAmount;
226 - if (transactionCurrency is Erc20Token) {
227 - allAmount = _erc20Balance.balance;
228 - } else {
229 - allAmount = _erc20Balance.balance -
230 - BigInt.from(calculateEstimatedFee(_credentials.priority!, null));
231 - }
232 - final totalOriginalAmount =
233 - EthereumFormatter.parseEthereumAmountToDouble(output.formattedCryptoAmount ?? 0);
234 - totalAmount = output.sendAll
235 - ? allAmount
236 - : BigInt.from(totalOriginalAmount * amountToEthereumMultiplier);
237 -
238 - if (_erc20Balance.balance < totalAmount) {
239 - throw EthereumTransactionCreationException(transactionCurrency);
240 - }
241 - }
242 -
243 - final pendingEthereumTransaction = await _client.signTransaction(
244 - privateKey: _ethPrivateKey,
245 - toAddress: _credentials.outputs.first.isParsedAddress
246 - ? _credentials.outputs.first.extractedAddress!
247 - : _credentials.outputs.first.address,
248 - amount: totalAmount.toString(),
249 - gas: _estimatedGas!,
250 - priority: _credentials.priority!,
251 - currency: transactionCurrency,
252 - exponent: exponent,
253 - contractAddress:
254 - transactionCurrency is Erc20Token ? transactionCurrency.contractAddress : null,
76 + evmChainErc20TokensBox.addAll(allValues);
77 + }
78 +
79 + @override
80 + EVMChainTransactionInfo getTransactionInfo(
81 + EVMChainTransactionModel transactionModel, String address) {
82 + final model = EthereumTransactionInfo(
83 + id: transactionModel.hash,
84 + height: transactionModel.blockNumber,
85 + ethAmount: transactionModel.amount,
86 + direction: transactionModel.from == address
87 + ? TransactionDirection.outgoing
88 + : TransactionDirection.incoming,
89 + isPending: false,
90 + date: transactionModel.date,
91 + confirmations: transactionModel.confirmations,
92 + ethFee: BigInt.from(transactionModel.gasUsed) * transactionModel.gasPrice,
93 + exponent: transactionModel.tokenDecimal ?? 18,
94 + tokenSymbol: transactionModel.tokenSymbol ?? "ETH",
95 + to: transactionModel.to,
96 + from: transactionModel.from,
97 );
256 -
257 - return pendingEthereumTransaction;
258 - }
259 -
260 - Future<void> _updateTransactions() async {
261 - try {
262 - if (_isTransactionUpdating) {
263 - return;
264 - }
265 - bool isEtherscanEnabled = (await _sharedPrefs.future).getBool("use_etherscan") ?? true;
266 - if (!isEtherscanEnabled) {
267 - return;
268 - }
269 -
270 - _isTransactionUpdating = true;
271 - final transactions = await fetchTransactions();
272 - transactionHistory.addMany(transactions);
273 - await transactionHistory.save();
274 - _isTransactionUpdating = false;
275 - } catch (_) {
276 - _isTransactionUpdating = false;
277 - }
98 + return model;
99 }
100
101 @override
281 - Future<Map<String, EthereumTransactionInfo>> fetchTransactions() async {
282 - final address = _ethPrivateKey.address.hex;
283 - final transactions = await _client.fetchTransactions(address);
284 -
285 - final List<Future<List<EthereumTransactionModel>>> erc20TokensTransactions = [];
286 -
287 - for (var token in balance.keys) {
288 - if (token is Erc20Token) {
289 - erc20TokensTransactions.add(_client.fetchTransactions(
290 - address,
291 - contractAddress: token.contractAddress,
292 - ));
293 - }
294 - }
295 -
296 - final tokensTransaction = await Future.wait(erc20TokensTransactions);
297 - transactions.addAll(tokensTransaction.expand((element) => element));
298 -
299 - final Map<String, EthereumTransactionInfo> result = {};
300 -
301 - for (var transactionModel in transactions) {
302 - if (transactionModel.isError) {
303 - continue;
304 - }
305 -
306 - result[transactionModel.hash] = EthereumTransactionInfo(
307 - id: transactionModel.hash,
308 - height: transactionModel.blockNumber,
309 - ethAmount: transactionModel.amount,
310 - direction: transactionModel.from == address
311 - ? TransactionDirection.outgoing
312 - : TransactionDirection.incoming,
313 - isPending: false,
314 - date: transactionModel.date,
315 - confirmations: transactionModel.confirmations,
316 - ethFee: BigInt.from(transactionModel.gasUsed) * transactionModel.gasPrice,
317 - exponent: transactionModel.tokenDecimal ?? 18,
318 - tokenSymbol: transactionModel.tokenSymbol ?? "ETH",
319 - to: transactionModel.to,
320 - );
321 - }
322 -
323 - return result;
324 - }
325 -
326 - @override
327 - Object get keys => throw UnimplementedError("keys");
102 + String getTransactionHistoryFileName() => 'transactions.json';
103
104 @override
330 - Future<void> rescan({required int height}) {
331 - throw UnimplementedError("rescan");
332 - }
333 -
334 - @override
335 - Future<void> save() async {
336 - await walletAddresses.updateAddressesInBox();
337 - final path = await makePath();
338 - await write(path: path, password: _password, data: toJSON());
339 - await transactionHistory.save();
105 + Erc20Token createNewErc20TokenObject(Erc20Token token, String? iconPath) {
106 + return Erc20Token(
107 + name: token.name,
108 + symbol: token.symbol,
109 + contractAddress: token.contractAddress,
110 + decimal: token.decimal,
111 + enabled: token.enabled,
112 + tag: token.tag ?? "ETH",
113 + iconPath: iconPath,
114 + );
115 }
116
117 @override
343 - String? get seed => _mnemonic;
344 -
345 - @override
346 - String get privateKey => HEX.encode(_ethPrivateKey.privateKey);
347 -
348 - @action
349 - @override
350 - Future<void> startSync() async {
351 - try {
352 - syncStatus = AttemptingSyncStatus();
353 - await _updateBalance();
354 - await _updateTransactions();
355 - _gasPrice = await _client.getGasUnitPrice();
356 - _estimatedGas = await _client.getEstimatedGas();
357 -
358 - Timer.periodic(
359 - const Duration(minutes: 1), (timer) async => _gasPrice = await _client.getGasUnitPrice());
360 - Timer.periodic(const Duration(seconds: 10),
361 - (timer) async => _estimatedGas = await _client.getEstimatedGas());
362 -
363 - syncStatus = SyncedSyncStatus();
364 - } catch (e) {
365 - syncStatus = FailedSyncStatus();
366 - }
118 + EVMChainTransactionHistory setUpTransactionHistory(WalletInfo walletInfo, String password) {
119 + return EthereumTransactionHistory(walletInfo: walletInfo, password: password);
120 }
121
369 - Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
370 -
371 - String toJSON() => json.encode({
372 - 'mnemonic': _mnemonic,
373 - 'private_key': privateKey,
374 - 'balance': balance[currency]!.toJSON(),
375 - });
376 -
377 - static Future<EthereumWallet> open({
378 - required String name,
379 - required String password,
380 - required WalletInfo walletInfo,
381 - }) async {
122 + static Future<EthereumWallet> open(
123 + {required String name, required String password, required WalletInfo walletInfo}) async {
124 final path = await pathForWallet(name: name, type: walletInfo.type);
125 final jsonSource = await read(path: path, password: password);
126 final data = json.decode(jsonSource) as Map;
127 final mnemonic = data['mnemonic'] as String?;
128 final privateKey = data['private_key'] as String?;
387 - final balance = ERC20Balance.fromJSON(data['balance'] as String) ?? ERC20Balance(BigInt.zero);
129 + final balance = EVMChainERC20Balance.fromJSON(data['balance'] as String) ??
130 + EVMChainERC20Balance(BigInt.zero);
131
132 return EthereumWallet(
133 walletInfo: walletInfo,
@@ -392,155 +135,7 @@ abstract class EthereumWalletBase
135 mnemonic: mnemonic,
136 privateKey: privateKey,
137 initialBalance: balance,
138 + client: EthereumClient(),
139 );
140 }
397 -
398 - Future<void> _updateBalance() async {
399 - balance[currency] = await _fetchEthBalance();
400 -
401 - await _fetchErc20Balances();
402 - await save();
403 - }
404 -
405 - Future<ERC20Balance> _fetchEthBalance() async {
406 - final balance = await _client.getBalance(_ethPrivateKey.address);
407 - return ERC20Balance(balance.getInWei);
408 - }
409 -
410 - Future<void> _fetchErc20Balances() async {
411 - for (var token in ethereumErc20TokensBox.values) {
412 - try {
413 - if (token.enabled) {
414 - balance[token] = await _client.fetchERC20Balances(
415 - _ethPrivateKey.address,
416 - token.contractAddress,
417 - );
418 - } else {
419 - balance.remove(token);
420 - }
421 - } catch (_) {}
422 - }
423 - }
424 -
425 - Future<EthPrivateKey> getPrivateKey(
426 - {String? mnemonic, String? privateKey, required String password}) async {
427 - assert(mnemonic != null || privateKey != null);
428 -
429 - if (privateKey != null) {
430 - return EthPrivateKey.fromHex(privateKey);
431 - }
432 -
433 - final seed = bip39.mnemonicToSeed(mnemonic!);
434 -
435 - final root = bip32.BIP32.fromSeed(seed);
436 -
437 - const _hdPathEthereum = "m/44'/60'/0'/0";
438 - const index = 0;
439 - final addressAtIndex = root.derivePath("$_hdPathEthereum/$index");
440 -
441 - return EthPrivateKey.fromHex(HEX.encode(addressAtIndex.privateKey as List<int>));
442 - }
443 -
444 - Future<void>? updateBalance() async => await _updateBalance();
445 -
446 - List<Erc20Token> get erc20Currencies => ethereumErc20TokensBox.values.toList();
447 -
448 - Future<void> addErc20Token(Erc20Token token) async {
449 - String? iconPath;
450 - try {
451 - iconPath = CryptoCurrency.all
452 - .firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase())
453 - .iconPath;
454 - } catch (_) {}
455 -
456 - final _token = Erc20Token(
457 - name: token.name,
458 - symbol: token.symbol,
459 - contractAddress: token.contractAddress,
460 - decimal: token.decimal,
461 - enabled: token.enabled,
462 - tag: token.tag ?? "ETH",
463 - iconPath: iconPath,
464 - );
465 -
466 - await ethereumErc20TokensBox.put(_token.contractAddress, _token);
467 -
468 - if (_token.enabled) {
469 - balance[_token] = await _client.fetchERC20Balances(
470 - _ethPrivateKey.address,
471 - _token.contractAddress,
472 - );
473 - } else {
474 - balance.remove(_token);
475 - }
476 - }
477 -
478 - Future<void> deleteErc20Token(Erc20Token token) async {
479 - await token.delete();
480 -
481 - balance.remove(token);
482 - _updateBalance();
483 - }
484 -
485 - Future<Erc20Token?> getErc20Token(String contractAddress) async =>
486 - await _client.getErc20Token(contractAddress);
487 -
488 - void _onNewTransaction() {
489 - _updateBalance();
490 - _updateTransactions();
491 - }
492 -
493 - void addInitialTokens() {
494 - final initialErc20Tokens = DefaultErc20Tokens().initialErc20Tokens;
495 -
496 - initialErc20Tokens.forEach((token) => ethereumErc20TokensBox.put(token.contractAddress, token));
497 - }
498 -
499 - @override
500 - Future<void> renameWalletFiles(String newWalletName) async {
501 - final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type);
502 - final currentWalletFile = File(currentWalletPath);
503 -
504 - final currentDirPath = await pathForWalletDir(name: walletInfo.name, type: type);
505 - final currentTransactionsFile = File('$currentDirPath/$transactionsHistoryFileName');
506 -
507 - // Copies current wallet files into new wallet name's dir and files
508 - if (currentWalletFile.existsSync()) {
509 - final newWalletPath = await pathForWallet(name: newWalletName, type: type);
510 - await currentWalletFile.copy(newWalletPath);
511 - }
512 - if (currentTransactionsFile.existsSync()) {
513 - final newDirPath = await pathForWalletDir(name: newWalletName, type: type);
514 - await currentTransactionsFile.copy('$newDirPath/$transactionsHistoryFileName');
515 - }
516 -
517 - // Delete old name's dir and files
518 - await Directory(currentDirPath).delete(recursive: true);
519 - }
520 -
521 - void _setTransactionUpdateTimer() {
522 - if (_transactionsUpdateTimer?.isActive ?? false) {
523 - _transactionsUpdateTimer!.cancel();
524 - }
525 -
526 - _transactionsUpdateTimer = Timer.periodic(const Duration(seconds: 10), (_) {
527 - _updateTransactions();
528 - _updateBalance();
529 - });
530 - }
531 -
532 - void updateEtherscanUsageState(bool isEnabled) {
533 - if (isEnabled) {
534 - _updateTransactions();
535 - _setTransactionUpdateTimer();
536 - } else {
537 - _transactionsUpdateTimer?.cancel();
538 - }
539 - }
540 -
541 - @override
542 - String signMessage(String message, {String? address}) =>
543 - bytesToHex(_ethPrivateKey.signPersonalMessageToUint8List(ascii.encode(message)));
544 -
545 - Web3Client? getWeb3Client() => _client.getWeb3Client();
141 }
cw_ethereum/lib/ethereum_wallet_creation_credentials.dart deleted
-29
@@ -1,29 +0,0 @@
1 -import 'package:cw_core/wallet_credentials.dart';
2 -import 'package:cw_core/wallet_info.dart';
3 -
4 -class EthereumNewWalletCredentials extends WalletCredentials {
5 - EthereumNewWalletCredentials({required String name, WalletInfo? walletInfo})
6 - : super(name: name, walletInfo: walletInfo);
7 -}
8 -
9 -class EthereumRestoreWalletFromSeedCredentials extends WalletCredentials {
10 - EthereumRestoreWalletFromSeedCredentials(
11 - {required String name,
12 - required String password,
13 - required this.mnemonic,
14 - WalletInfo? walletInfo})
15 - : super(name: name, password: password, walletInfo: walletInfo);
16 -
17 - final String mnemonic;
18 -}
19 -
20 -class EthereumRestoreWalletFromPrivateKey extends WalletCredentials {
21 - EthereumRestoreWalletFromPrivateKey(
22 - {required String name,
23 - required String password,
24 - required this.privateKey,
25 - WalletInfo? walletInfo})
26 - : super(name: name, password: password, walletInfo: walletInfo);
27 -
28 - final String privateKey;
29 -}
cw_ethereum/lib/ethereum_wallet_service.dart
+31 -45
@@ -1,32 +1,31 @@
1 -import 'dart:io';
2 -
3 -import 'package:cw_core/pathForWallet.dart';
1 import 'package:cw_core/wallet_base.dart';
5 -import 'package:cw_core/wallet_info.dart';
6 -import 'package:cw_core/wallet_service.dart';
2 import 'package:cw_core/wallet_type.dart';
8 -import 'package:cw_ethereum/ethereum_mnemonics.dart';
3 +import 'package:cw_ethereum/ethereum_client.dart';
4 +import 'package:cw_ethereum/ethereum_mnemonics_exception.dart';
5 import 'package:cw_ethereum/ethereum_wallet.dart';
10 -import 'package:cw_ethereum/ethereum_wallet_creation_credentials.dart';
11 -import 'package:hive/hive.dart';
6 +import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
7 +import 'package:cw_evm/evm_chain_wallet_service.dart';
8 import 'package:bip39/bip39.dart' as bip39;
13 -import 'package:collection/collection.dart';
9
15 -class EthereumWalletService extends WalletService<EthereumNewWalletCredentials,
16 - EthereumRestoreWalletFromSeedCredentials, EthereumRestoreWalletFromPrivateKey> {
17 - EthereumWalletService(this.walletInfoSource);
10 +class EthereumWalletService extends EVMChainWalletService<EthereumWallet> {
11 + EthereumWalletService(super.walletInfoSource, {required this.client});
12
19 - final Box<WalletInfo> walletInfoSource;
13 + late EthereumClient client;
14
15 @override
22 - Future<EthereumWallet> create(EthereumNewWalletCredentials credentials) async {
16 + WalletType getType() => WalletType.ethereum;
17 +
18 + @override
19 + Future<EthereumWallet> create(EVMChainNewWalletCredentials credentials) async {
20 final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
21
22 final mnemonic = bip39.generateMnemonic(strength: strength);
23 +
24 final wallet = EthereumWallet(
25 walletInfo: credentials.walletInfo!,
26 mnemonic: mnemonic,
27 password: credentials.password!,
28 + client: client,
29 );
30
31 await wallet.init();
@@ -36,18 +35,11 @@ class EthereumWalletService extends WalletService<EthereumNewWalletCredentials,
35 return wallet;
36 }
37
39 - @override
40 - WalletType getType() => WalletType.ethereum;
41 -
42 - @override
43 - Future<bool> isWalletExit(String name) async =>
44 - File(await pathForWallet(name: name, type: getType())).existsSync();
45 -
38 @override
39 Future<EthereumWallet> openWallet(String name, String password) async {
40 final walletInfo =
41 walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
50 - final wallet = await EthereumWalletBase.open(
42 + final wallet = await EthereumWallet.open(
43 name: name,
44 password: password,
45 walletInfo: walletInfo,
@@ -60,19 +52,28 @@ class EthereumWalletService extends WalletService<EthereumNewWalletCredentials,
52 }
53
54 @override
63 - Future<void> remove(String wallet) async {
64 - File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true);
65 - final walletInfo = walletInfoSource.values
66 - .firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!;
67 - await walletInfoSource.delete(walletInfo.key);
55 + Future<void> rename(String currentName, String password, String newName) async {
56 + final currentWalletInfo = walletInfoSource.values
57 + .firstWhere((info) => info.id == WalletBase.idFor(currentName, getType()));
58 + final currentWallet = await EthereumWallet.open(
59 + password: password, name: currentName, walletInfo: currentWalletInfo);
60 +
61 + await currentWallet.renameWalletFiles(newName);
62 +
63 + final newWalletInfo = currentWalletInfo;
64 + newWalletInfo.id = WalletBase.idFor(newName, getType());
65 + newWalletInfo.name = newName;
66 +
67 + await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
68 }
69
70 @override
71 - Future<EthereumWallet> restoreFromKeys(EthereumRestoreWalletFromPrivateKey credentials) async {
71 + Future<EthereumWallet> restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials) async {
72 final wallet = EthereumWallet(
73 password: credentials.password!,
74 privateKey: credentials.privateKey,
75 walletInfo: credentials.walletInfo!,
76 + client: client,
77 );
78
79 await wallet.init();
@@ -84,7 +85,7 @@ class EthereumWalletService extends WalletService<EthereumNewWalletCredentials,
85
86 @override
87 Future<EthereumWallet> restoreFromSeed(
87 - EthereumRestoreWalletFromSeedCredentials credentials) async {
88 + EVMChainRestoreWalletFromSeedCredentials credentials) async {
89 if (!bip39.validateMnemonic(credentials.mnemonic)) {
90 throw EthereumMnemonicIsIncorrectException();
91 }
@@ -93,6 +94,7 @@ class EthereumWalletService extends WalletService<EthereumNewWalletCredentials,
94 password: credentials.password!,
95 mnemonic: credentials.mnemonic,
96 walletInfo: credentials.walletInfo!,
97 + client: client,
98 );
99
100 await wallet.init();
@@ -101,20 +103,4 @@ class EthereumWalletService extends WalletService<EthereumNewWalletCredentials,
103
104 return wallet;
105 }
104 -
105 - @override
106 - Future<void> rename(String currentName, String password, String newName) async {
107 - final currentWalletInfo = walletInfoSource.values
108 - .firstWhere((info) => info.id == WalletBase.idFor(currentName, getType()));
109 - final currentWallet = await EthereumWalletBase.open(
110 - password: password, name: currentName, walletInfo: currentWalletInfo);
111 -
112 - await currentWallet.renameWalletFiles(newName);
113 -
114 - final newWalletInfo = currentWalletInfo;
115 - newWalletInfo.id = WalletBase.idFor(newName, getType());
116 - newWalletInfo.name = newName;
117 -
118 - await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
119 - }
106 }
cw_ethereum/pubspec.yaml
+3 -36
@@ -13,56 +13,23 @@ dependencies:
13 flutter:
14 sdk: flutter
15 web3dart: ^2.7.1
16 - erc20: ^1.0.1
17 - mobx: ^2.0.7+4
18 - bip39: ^1.0.6
19 - bip32: ^2.0.0
20 - hex: ^0.2.0
21 - http: ^1.1.0
22 - shared_preferences: ^2.0.15
16 cw_core:
17 path: ../cw_core
18 + cw_evm:
19 + path: ../cw_evm
20 + hive: ^2.2.3
21
22 dev_dependencies:
23 flutter_test:
24 sdk: flutter
25 build_runner: ^2.1.11
30 - mobx_codegen: ^2.0.7
31 - hive_generator: ^1.1.3
32 -
33 -# For information on the generic Dart part of this file, see the
34 -# following page: https://dart.dev/tools/pub/pubspec
35 -
36 -# The following section is specific to Flutter packages.
26 flutter:
38 -
39 - # To add assets to your package, add an assets section, like this:
27 # assets:
28 # - images/a_dot_burr.jpeg
29 # - images/a_dot_ham.jpeg
43 - #
44 - # For details regarding assets in packages, see
45 - # https://flutter.dev/assets-and-images/#from-packages
46 - #
47 - # An image asset can refer to one or more resolution-specific "variants", see
48 - # https://flutter.dev/assets-and-images/#resolution-aware
49 -
50 - # To add custom fonts to your package, add a fonts section here,
51 - # in this "flutter" section. Each entry in this list should have a
52 - # "family" key with the font family name, and a "fonts" key with a
53 - # list giving the asset and other descriptors for the font. For
54 - # example:
30 # fonts:
31 # - family: Schyler
32 # fonts:
33 # - asset: fonts/Schyler-Regular.ttf
34 # - asset: fonts/Schyler-Italic.ttf
35 # style: italic
61 - # - family: Trajan Pro
62 - # fonts:
63 - # - asset: fonts/TrajanPro.ttf
64 - # - asset: fonts/TrajanPro_Bold.ttf
65 - # weight: 700
66 - #
67 - # For details regarding fonts in packages, see
68 - # https://flutter.dev/custom-fonts/#from-packages
cw_evm/.gitignore new
+30
@@ -0,0 +1,30 @@
1 +# Miscellaneous
2 +*.class
3 +*.log
4 +*.pyc
5 +*.swp
6 +.DS_Store
7 +.atom/
8 +.buildlog/
9 +.history
10 +.svn/
11 +migrate_working_dir/
12 +
13 +# IntelliJ related
14 +*.iml
15 +*.ipr
16 +*.iws
17 +.idea/
18 +
19 +# The .vscode folder contains launch configuration and tasks you configure in
20 +# VS Code which you may wish to be included in version control, so this line
21 +# is commented out by default.
22 +#.vscode/
23 +
24 +# Flutter/Dart/Pub related
25 +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
26 +/pubspec.lock
27 +**/doc/api/
28 +.dart_tool/
29 +.packages
30 +build/
cw_evm/.metadata new
+10
@@ -0,0 +1,10 @@
1 +# This file tracks properties of this Flutter project.
2 +# Used by Flutter tool to assess capabilities and perform upgrades etc.
3 +#
4 +# This file should be version controlled and should not be manually edited.
5 +
6 +version:
7 + revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
8 + channel: stable
9 +
10 +project_type: package
cw_evm/CHANGELOG.md new
+3
@@ -0,0 +1,3 @@
1 +## 0.0.1
2 +
3 +* TODO: Describe initial release.
cw_evm/LICENSE new
+1
@@ -0,0 +1 @@
1 +TODO: Add your license here.
cw_evm/README.md new
+39
@@ -0,0 +1,39 @@
1 +<!--
2 +This README describes the package. If you publish this package to pub.dev,
3 +this README's contents appear on the landing page for your package.
4 +
5 +For information about how to write a good package README, see the guide for
6 +[writing package pages](https://dart.dev/guides/libraries/writing-package-pages).
7 +
8 +For general information about developing packages, see the Dart guide for
9 +[creating packages](https://dart.dev/guides/libraries/create-library-packages)
10 +and the Flutter guide for
11 +[developing packages and plugins](https://flutter.dev/developing-packages).
12 +-->
13 +
14 +TODO: Put a short description of the package here that helps potential users
15 +know whether this package might be useful for them.
16 +
17 +## Features
18 +
19 +TODO: List what your package can do. Maybe include images, gifs, or videos.
20 +
21 +## Getting started
22 +
23 +TODO: List prerequisites and provide or point to information on how to
24 +start using the package.
25 +
26 +## Usage
27 +
28 +TODO: Include short and useful examples for package users. Add longer examples
29 +to `/example` folder.
30 +
31 +```dart
32 +const like = 'sample';
33 +```
34 +
35 +## Additional information
36 +
37 +TODO: Tell users more about the package: where to find more information, how to
38 +contribute to the package, how to file issues, what response they can expect
39 +from the package authors, and more.
cw_evm/analysis_options.yaml new
+4
@@ -0,0 +1,4 @@
1 +include: package:flutter_lints/flutter.yaml
2 +
3 +# Additional information about this file can be found at
4 +# https://dart.dev/guides/language/analysis-options
cw_evm/lib/cw_evm.dart new
+7
@@ -0,0 +1,7 @@
1 +library cw_evm;
2 +
3 +/// A Calculator.
4 +class Calculator {
5 + /// Returns [value] plus 1.
6 + int addOne(int value) => value + 1;
7 +}
cw_evm/lib/evm_chain_client.dart new
+251
@@ -0,0 +1,251 @@
1 +import 'dart:async';
2 +import 'dart:developer';
3 +
4 +import 'package:cw_core/node.dart';
5 +import 'package:cw_core/erc20_token.dart';
6 +import 'package:cw_core/crypto_currency.dart';
7 +
8 +import 'package:cw_evm/evm_erc20_balance.dart';
9 +import 'package:cw_evm/evm_chain_transaction_model.dart';
10 +import 'package:cw_evm/pending_evm_chain_transaction.dart';
11 +import 'package:cw_evm/evm_chain_transaction_priority.dart';
12 +import 'package:flutter/services.dart';
13 +
14 +import 'package:http/http.dart';
15 +import 'package:erc20/erc20.dart';
16 +import 'package:web3dart/web3dart.dart';
17 +
18 +abstract class EVMChainClient {
19 + final httpClient = Client();
20 + Web3Client? _client;
21 +
22 + //! To be overridden by all child classes
23 +
24 + int get chainId;
25 +
26 + Future<List<EVMChainTransactionModel>> fetchTransactions(String address,
27 + {String? contractAddress});
28 +
29 + Uint8List prepareSignedTransactionForSending(Uint8List signedTransaction);
30 +
31 + //! Common methods across all child classes
32 +
33 + bool connect(Node node) {
34 + try {
35 + _client = Web3Client(node.uri.toString(), httpClient);
36 +
37 + return true;
38 + } catch (e) {
39 + return false;
40 + }
41 + }
42 +
43 + void setListeners(EthereumAddress userAddress, Function() onNewTransaction) async {
44 + // _client?.pendingTransactions().listen((transactionHash) async {
45 + // final transaction = await _client!.getTransactionByHash(transactionHash);
46 + //
47 + // if (transaction.from.hex == userAddress || transaction.to?.hex == userAddress) {
48 + // onNewTransaction();
49 + // }
50 + // });
51 + }
52 +
53 + Future<EtherAmount> getBalance(EthereumAddress address) async {
54 + try {
55 + return await _client!.getBalance(address);
56 + } catch (_) {
57 + return EtherAmount.zero();
58 + }
59 + }
60 +
61 + Future<int> getGasUnitPrice() async {
62 + try {
63 + final gasPrice = await _client!.getGasPrice();
64 + return gasPrice.getInWei.toInt();
65 + } catch (_) {
66 + return 0;
67 + }
68 + }
69 +
70 + Future<int> getEstimatedGas() async {
71 + try {
72 + final estimatedGas = await _client!.estimateGas();
73 + return estimatedGas.toInt();
74 + } catch (_) {
75 + return 0;
76 + }
77 + }
78 +
79 + Future<PendingEVMChainTransaction> signTransaction({
80 + required EthPrivateKey privateKey,
81 + required String toAddress,
82 + required String amount,
83 + required int gas,
84 + required EVMChainTransactionPriority priority,
85 + required CryptoCurrency currency,
86 + required int exponent,
87 + String? contractAddress,
88 + }) async {
89 + assert(currency == CryptoCurrency.eth ||
90 + currency == CryptoCurrency.maticpoly ||
91 + contractAddress != null);
92 +
93 + bool isEVMCompatibleChain =
94 + currency == CryptoCurrency.eth || currency == CryptoCurrency.maticpoly;
95 +
96 + final price = _client!.getGasPrice();
97 +
98 + final Transaction transaction = createTransaction(
99 + from: privateKey.address,
100 + to: EthereumAddress.fromHex(toAddress),
101 + maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip),
102 + amount: isEVMCompatibleChain ? EtherAmount.inWei(BigInt.parse(amount)) : EtherAmount.zero(),
103 + );
104 +
105 + final signedTransaction =
106 + await _client!.signTransaction(privateKey, transaction, chainId: chainId);
107 +
108 + final Function _sendTransaction;
109 +
110 + if (isEVMCompatibleChain) {
111 + _sendTransaction = () async => await sendTransaction(signedTransaction);
112 + } else {
113 + final erc20 = ERC20(
114 + client: _client!,
115 + address: EthereumAddress.fromHex(contractAddress!),
116 + chainId: chainId,
117 + );
118 +
119 + _sendTransaction = () async {
120 + await erc20.transfer(
121 + EthereumAddress.fromHex(toAddress),
122 + BigInt.parse(amount),
123 + credentials: privateKey,
124 + transaction: transaction,
125 + );
126 + };
127 + }
128 +
129 + return PendingEVMChainTransaction(
130 + signedTransaction: signedTransaction,
131 + amount: amount,
132 + fee: BigInt.from(gas) * (await price).getInWei,
133 + sendTransaction: _sendTransaction,
134 + exponent: exponent,
135 + );
136 + }
137 +
138 + Transaction createTransaction({
139 + required EthereumAddress from,
140 + required EthereumAddress to,
141 + required EtherAmount amount,
142 + EtherAmount? maxPriorityFeePerGas,
143 + }) {
144 + return Transaction(
145 + from: from,
146 + to: to,
147 + maxPriorityFeePerGas: maxPriorityFeePerGas,
148 + value: amount,
149 + );
150 + }
151 +
152 + Future<String> sendTransaction(Uint8List signedTransaction) async =>
153 + await _client!.sendRawTransaction(prepareSignedTransactionForSending(signedTransaction));
154 +
155 + Future getTransactionDetails(String transactionHash) async {
156 + // Wait for the transaction receipt to become available
157 + TransactionReceipt? receipt;
158 + while (receipt == null) {
159 + receipt = await _client!.getTransactionReceipt(transactionHash);
160 + await Future.delayed(const Duration(seconds: 1));
161 + }
162 +
163 + // Print the receipt information
164 + log('Transaction Hash: ${receipt.transactionHash}');
165 + log('Block Hash: ${receipt.blockHash}');
166 + log('Block Number: ${receipt.blockNumber}');
167 + log('Gas Used: ${receipt.gasUsed}');
168 +
169 + /*
170 + Transaction Hash: [112, 244, 4, 238, 89, 199, 171, 191, 210, 236, 110, 42, 185, 202, 220, 21, 27, 132, 123, 221, 137, 90, 77, 13, 23, 43, 12, 230, 93, 63, 221, 116]
171 + I/flutter ( 4474): Block Hash: [149, 44, 250, 119, 111, 104, 82, 98, 17, 89, 30, 190, 25, 44, 218, 118, 127, 189, 241, 35, 213, 106, 25, 95, 195, 37, 55, 131, 185, 180, 246, 200]
172 + I/flutter ( 4474): Block Number: 17120242
173 + I/flutter ( 4474): Gas Used: 21000
174 + */
175 +
176 + // Wait for the transaction receipt to become available
177 + TransactionInformation? transactionInformation;
178 + while (transactionInformation == null) {
179 + log("********************************");
180 + transactionInformation = await _client!.getTransactionByHash(transactionHash);
181 + await Future.delayed(const Duration(seconds: 1));
182 + }
183 + // Print the receipt information
184 + log('Transaction Hash: ${transactionInformation.hash}');
185 + log('Block Hash: ${transactionInformation.blockHash}');
186 + log('Block Number: ${transactionInformation.blockNumber}');
187 + log('Gas Used: ${transactionInformation.gas}');
188 +
189 + /*
190 + Transaction Hash: 0x70f404ee59c7abbfd2ec6e2ab9cadc151b847bdd895a4d0d172b0ce65d3fdd74
191 + I/flutter ( 4474): Block Hash: 0x952cfa776f68526211591ebe192cda767fbdf123d56a195fc3253783b9b4f6c8
192 + I/flutter ( 4474): Block Number: 17120242
193 + I/flutter ( 4474): Gas Used: 53000
194 + */
195 + }
196 +
197 + Future<EVMChainERC20Balance> fetchERC20Balances(
198 + EthereumAddress userAddress, String contractAddress) async {
199 + final erc20 = ERC20(address: EthereumAddress.fromHex(contractAddress), client: _client!);
200 + final balance = await erc20.balanceOf(userAddress);
201 +
202 + int exponent = (await erc20.decimals()).toInt();
203 +
204 + return EVMChainERC20Balance(balance, exponent: exponent);
205 + }
206 +
207 + Future<Erc20Token?> getErc20Token(String contractAddress) async {
208 + try {
209 + final erc20 = ERC20(address: EthereumAddress.fromHex(contractAddress), client: _client!);
210 + final name = await erc20.name();
211 + final symbol = await erc20.symbol();
212 + final decimal = await erc20.decimals();
213 +
214 + return Erc20Token(
215 + name: name,
216 + symbol: symbol,
217 + contractAddress: contractAddress,
218 + decimal: decimal.toInt(),
219 + );
220 + } catch (e) {
221 + return null;
222 + }
223 + }
224 +
225 + void stop() {
226 + _client?.dispose();
227 + }
228 +
229 + Web3Client? getWeb3Client() {
230 + return _client;
231 + }
232 +
233 +// Future<int> _getDecimalPlacesForContract(DeployedContract contract) async {
234 +// final String abi = await rootBundle.loadString("assets/abi_json/erc20_abi.json");
235 +// final contractAbi = ContractAbi.fromJson(abi, "ERC20");
236 +//
237 +// final contract = DeployedContract(
238 +// contractAbi,
239 +// EthereumAddress.fromHex(_erc20Currencies[erc20Currency]!),
240 +// );
241 +// final decimalsFunction = contract.function('decimals');
242 +// final decimals = await _client!.call(
243 +// contract: contract,
244 +// function: decimalsFunction,
245 +// params: [],
246 +// );
247 +//
248 +// int exponent = int.parse(decimals.first.toString());
249 +// return exponent;
250 +// }
251 +}
cw_evm/lib/evm_chain_exceptions.dart new
+11
@@ -0,0 +1,11 @@
1 +import 'package:cw_core/crypto_currency.dart';
2 +
3 +class EVMChainTransactionCreationException implements Exception {
4 + final String exceptionMessage;
5 +
6 + EVMChainTransactionCreationException(CryptoCurrency currency)
7 + : exceptionMessage = 'Wrong balance. Not enough ${currency.title} on your balance.';
8 +
9 + @override
10 + String toString() => exceptionMessage;
11 +}
cw_evm/lib/evm_chain_formatter.dart new
+25
@@ -0,0 +1,25 @@
1 +import 'package:intl/intl.dart';
2 +
3 +const evmChainAmountLength = 12;
4 +const evmChainAmountDivider = 1000000000000;
5 +final evmChainAmountFormat = NumberFormat()
6 + ..maximumFractionDigits = evmChainAmountLength
7 + ..minimumFractionDigits = 1;
8 +
9 +class EVMChainFormatter {
10 + static int parseEVMChainAmount(String amount) {
11 + try {
12 + return (double.parse(amount) * evmChainAmountDivider).round();
13 + } catch (_) {
14 + return 0;
15 + }
16 + }
17 +
18 + static double parseEVMChainAmountToDouble(int amount) {
19 + try {
20 + return amount / evmChainAmountDivider;
21 + } catch (_) {
22 + return 0;
23 + }
24 + }
25 +}
cw_evm/lib/evm_chain_mnemonics.dart renamed
+1 -7
@@ -1,10 +1,4 @@
1 -class EthereumMnemonicIsIncorrectException implements Exception {
2 - @override
3 - String toString() =>
4 - 'Ethereum mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.';
5 -}
6 -
7 -class EthereumMnemonics {
1 +class EVMChainMnemonics {
2 static const englishWordlist = <String>[
3 'abandon',
4 'ability',
cw_evm/lib/evm_chain_transaction_credentials.dart renamed
+4 -4
@@ -1,9 +1,9 @@
1 import 'package:cw_core/crypto_currency.dart';
2 import 'package:cw_core/output_info.dart';
3 -import 'package:cw_ethereum/ethereum_transaction_priority.dart';
3 +import 'package:cw_evm/evm_chain_transaction_priority.dart';
4
5 -class EthereumTransactionCredentials {
6 - EthereumTransactionCredentials(
5 +class EVMChainTransactionCredentials {
6 + EVMChainTransactionCredentials(
7 this.outputs, {
8 required this.priority,
9 required this.currency,
@@ -11,7 +11,7 @@ class EthereumTransactionCredentials {
11 });
12
13 final List<OutputInfo> outputs;
14 - final EthereumTransactionPriority? priority;
14 + final EVMChainTransactionPriority? priority;
15 final int? feeRate;
16 final CryptoCurrency currency;
17 }
cw_evm/lib/evm_chain_transaction_history.dart new
+88
@@ -0,0 +1,88 @@
1 +import 'dart:convert';
2 +import 'dart:core';
3 +import 'dart:developer';
4 +import 'package:cw_core/pathForWallet.dart';
5 +import 'package:cw_core/wallet_info.dart';
6 +import 'package:cw_evm/evm_chain_transaction_info.dart';
7 +import 'package:cw_evm/file.dart';
8 +import 'package:mobx/mobx.dart';
9 +import 'package:cw_core/transaction_history.dart';
10 +
11 +part 'evm_chain_transaction_history.g.dart';
12 +
13 +abstract class EVMChainTransactionHistory = EVMChainTransactionHistoryBase
14 + with _$EVMChainTransactionHistory;
15 +
16 +abstract class EVMChainTransactionHistoryBase
17 + extends TransactionHistoryBase<EVMChainTransactionInfo> with Store {
18 + EVMChainTransactionHistoryBase({required this.walletInfo, required String password})
19 + : _password = password {
20 + transactions = ObservableMap<String, EVMChainTransactionInfo>();
21 + }
22 +
23 + String _password;
24 +
25 + final WalletInfo walletInfo;
26 +
27 + //! Method to be overridden by all child classes
28 +
29 + String getTransactionHistoryFileName();
30 +
31 + EVMChainTransactionInfo getTransactionInfo(Map<String, dynamic> val);
32 +
33 + //! Common methods across all child classes
34 +
35 + Future<void> init() async => await _load();
36 +
37 + @override
38 + Future<void> save() async {
39 + final transactionsHistoryFileNameForWallet = getTransactionHistoryFileName();
40 + try {
41 + final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
42 + String path = '$dirPath/$transactionsHistoryFileNameForWallet';
43 + final data = json.encode({'transactions': transactions});
44 + await writeData(path: path, password: _password, data: data);
45 + } catch (e, s) {
46 + log('Error while saving ${walletInfo.type.name} transaction history: ${e.toString()}');
47 + log(s.toString());
48 + }
49 + }
50 +
51 + @override
52 + void addOne(EVMChainTransactionInfo transaction) => transactions[transaction.id] = transaction;
53 +
54 + @override
55 + void addMany(Map<String, EVMChainTransactionInfo> transactions) =>
56 + this.transactions.addAll(transactions);
57 +
58 + Future<Map<String, dynamic>> _read() async {
59 + final transactionsHistoryFileNameForWallet = getTransactionHistoryFileName();
60 + final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
61 + String path = '$dirPath/$transactionsHistoryFileNameForWallet';
62 + final content = await read(path: path, password: _password);
63 + if (content.isEmpty) {
64 + return {};
65 + }
66 + return json.decode(content) as Map<String, dynamic>;
67 + }
68 +
69 + Future<void> _load() async {
70 + try {
71 + final content = await _read();
72 + final txs = content['transactions'] as Map<String, dynamic>? ?? {};
73 +
74 + for (var entry in txs.entries) {
75 + final val = entry.value;
76 +
77 + if (val is Map<String, dynamic>) {
78 + final tx = getTransactionInfo(val);
79 + _update(tx);
80 + }
81 + }
82 + } catch (e) {
83 + log(e.toString());
84 + }
85 + }
86 +
87 + void _update(EVMChainTransactionInfo transaction) => transactions[transaction.id] = transaction;
88 +}
cw_evm/lib/evm_chain_transaction_info.dart new
+77
@@ -0,0 +1,77 @@
1 +// ignore_for_file: overridden_fields, annotate_overrides
2 +
3 +import 'dart:math';
4 +
5 +import 'package:cw_core/format_amount.dart';
6 +import 'package:cw_core/transaction_direction.dart';
7 +import 'package:cw_core/transaction_info.dart';
8 +
9 +abstract class EVMChainTransactionInfo extends TransactionInfo {
10 + EVMChainTransactionInfo({
11 + required this.id,
12 + required this.height,
13 + required this.ethAmount,
14 + required this.ethFee,
15 + required this.tokenSymbol,
16 + this.exponent = 18,
17 + required this.direction,
18 + required this.isPending,
19 + required this.date,
20 + required this.confirmations,
21 + required this.to,
22 + required this.from,
23 + }) : amount = ethAmount.toInt(),
24 + fee = ethFee.toInt();
25 +
26 + final String id;
27 + final int height;
28 + final int amount;
29 + final BigInt ethAmount;
30 + final int exponent;
31 + final TransactionDirection direction;
32 + final DateTime date;
33 + final bool isPending;
34 + final int fee;
35 + final BigInt ethFee;
36 + final int confirmations;
37 + final String tokenSymbol;
38 + String? _fiatAmount;
39 + final String? to;
40 + final String? from;
41 +
42 + //! Getter to be overridden in child classes
43 + String get feeCurrency;
44 +
45 + @override
46 + String amountFormatted() {
47 + final amount = formatAmount((ethAmount / BigInt.from(10).pow(exponent)).toString());
48 + return '${amount.substring(0, min(10, amount.length))} $tokenSymbol';
49 + }
50 +
51 + @override
52 + String fiatAmount() => _fiatAmount ?? '';
53 +
54 + @override
55 + void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
56 +
57 + @override
58 + String feeFormatted() {
59 + final amount = (ethFee / BigInt.from(10).pow(18)).toString();
60 + return '${amount.substring(0, min(10, amount.length))} $feeCurrency';
61 + }
62 +
63 + Map<String, dynamic> toJson() => {
64 + 'id': id,
65 + 'height': height,
66 + 'amount': ethAmount.toString(),
67 + 'exponent': exponent,
68 + 'fee': ethFee.toString(),
69 + 'direction': direction.index,
70 + 'date': date.millisecondsSinceEpoch,
71 + 'isPending': isPending,
72 + 'confirmations': confirmations,
73 + 'tokenSymbol': tokenSymbol,
74 + 'to': to,
75 + 'from': from,
76 + };
77 +}
cw_evm/lib/evm_chain_transaction_model.dart renamed
+5 -5
@@ -1,5 +1,4 @@
1 -//! Model used for in parsing transactions fetched using etherscan
2 -class EthereumTransactionModel {
1 +class EVMChainTransactionModel {
2 final DateTime date;
3 final String hash;
4 final String from;
@@ -14,7 +13,7 @@ class EthereumTransactionModel {
13 final int? tokenDecimal;
14 final bool isError;
15
17 - EthereumTransactionModel({
16 + EVMChainTransactionModel({
17 required this.date,
18 required this.hash,
19 required this.from,
@@ -30,7 +29,8 @@ class EthereumTransactionModel {
29 required this.isError,
30 });
31
33 - factory EthereumTransactionModel.fromJson(Map<String, dynamic> json) => EthereumTransactionModel(
32 + factory EVMChainTransactionModel.fromJson(Map<String, dynamic> json, String defaultSymbol) =>
33 + EVMChainTransactionModel(
34 date: DateTime.fromMillisecondsSinceEpoch(int.parse(json["timeStamp"]) * 1000),
35 hash: json["hash"],
36 from: json["from"],
@@ -41,7 +41,7 @@ class EthereumTransactionModel {
41 contractAddress: json["contractAddress"],
42 confirmations: int.parse(json["confirmations"]),
43 blockNumber: int.parse(json["blockNumber"]),
44 - tokenSymbol: json["tokenSymbol"] ?? "ETH",
44 + tokenSymbol: json["tokenSymbol"] ?? defaultSymbol,
45 tokenDecimal: int.tryParse(json["tokenDecimal"] ?? ""),
46 isError: json["isError"] == "1",
47 );
cw_evm/lib/evm_chain_transaction_priority.dart new
+52
@@ -0,0 +1,52 @@
1 +import 'package:cw_core/transaction_priority.dart';
2 +
3 +class EVMChainTransactionPriority extends TransactionPriority {
4 + final int tip;
5 +
6 + const EVMChainTransactionPriority({required String title, required int raw, required this.tip})
7 + : super(title: title, raw: raw);
8 +
9 + static const List<EVMChainTransactionPriority> all = [fast, medium, slow];
10 + static const EVMChainTransactionPriority slow =
11 + EVMChainTransactionPriority(title: 'slow', raw: 0, tip: 1);
12 + static const EVMChainTransactionPriority medium =
13 + EVMChainTransactionPriority(title: 'Medium', raw: 1, tip: 2);
14 + static const EVMChainTransactionPriority fast =
15 + EVMChainTransactionPriority(title: 'Fast', raw: 2, tip: 4);
16 +
17 + static EVMChainTransactionPriority deserialize({required int raw}) {
18 + switch (raw) {
19 + case 0:
20 + return slow;
21 + case 1:
22 + return medium;
23 + case 2:
24 + return fast;
25 + default:
26 + throw Exception('Unexpected token: $raw for EVMChainTransactionPriority deserialize');
27 + }
28 + }
29 +
30 + String get units => 'gas';
31 +
32 + @override
33 + String toString() {
34 + var label = '';
35 +
36 + switch (this) {
37 + case EVMChainTransactionPriority.slow:
38 + label = 'Slow';
39 + break;
40 + case EVMChainTransactionPriority.medium:
41 + label = 'Medium';
42 + break;
43 + case EVMChainTransactionPriority.fast:
44 + label = 'Fast';
45 + break;
46 + default:
47 + break;
48 + }
49 +
50 + return label;
51 + }
52 +}
cw_evm/lib/evm_chain_wallet.dart new
+512
@@ -0,0 +1,512 @@
1 +import 'dart:async';
2 +import 'dart:convert';
3 +import 'dart:io';
4 +import 'dart:math';
5 +
6 +import 'package:bip32/bip32.dart' as bip32;
7 +import 'package:bip39/bip39.dart' as bip39;
8 +import 'package:cw_core/cake_hive.dart';
9 +import 'package:cw_core/crypto_currency.dart';
10 +import 'package:cw_core/erc20_token.dart';
11 +import 'package:cw_core/node.dart';
12 +import 'package:cw_core/pathForWallet.dart';
13 +import 'package:cw_core/pending_transaction.dart';
14 +import 'package:cw_core/sync_status.dart';
15 +import 'package:cw_core/transaction_priority.dart';
16 +import 'package:cw_core/wallet_addresses.dart';
17 +import 'package:cw_core/wallet_base.dart';
18 +import 'package:cw_core/wallet_info.dart';
19 +import 'package:cw_core/wallet_type.dart';
20 +import 'package:cw_evm/evm_chain_client.dart';
21 +import 'package:cw_evm/evm_chain_exceptions.dart';
22 +import 'package:cw_evm/evm_chain_formatter.dart';
23 +import 'package:cw_evm/evm_chain_transaction_credentials.dart';
24 +import 'package:cw_evm/evm_chain_transaction_history.dart';
25 +import 'package:cw_evm/evm_chain_transaction_model.dart';
26 +import 'package:cw_evm/evm_chain_transaction_priority.dart';
27 +import 'package:cw_evm/evm_chain_wallet_addresses.dart';
28 +import 'package:cw_evm/file.dart';
29 +import 'package:hex/hex.dart';
30 +import 'package:hive/hive.dart';
31 +import 'package:mobx/mobx.dart';
32 +import 'package:shared_preferences/shared_preferences.dart';
33 +import 'package:web3dart/crypto.dart';
34 +import 'package:web3dart/web3dart.dart';
35 +
36 +import 'evm_chain_transaction_info.dart';
37 +import 'evm_erc20_balance.dart';
38 +
39 +part 'evm_chain_wallet.g.dart';
40 +
41 +abstract class EVMChainWallet = EVMChainWalletBase with _$EVMChainWallet;
42 +
43 +abstract class EVMChainWalletBase
44 + extends WalletBase<EVMChainERC20Balance, EVMChainTransactionHistory, EVMChainTransactionInfo>
45 + with Store {
46 + EVMChainWalletBase({
47 + required WalletInfo walletInfo,
48 + required EVMChainClient client,
49 + required CryptoCurrency nativeCurrency,
50 + String? mnemonic,
51 + String? privateKey,
52 + required String password,
53 + EVMChainERC20Balance? initialBalance,
54 + }) : syncStatus = const NotConnectedSyncStatus(),
55 + _password = password,
56 + _mnemonic = mnemonic,
57 + _hexPrivateKey = privateKey,
58 + _isTransactionUpdating = false,
59 + _client = client,
60 + walletAddresses = EVMChainWalletAddresses(walletInfo),
61 + balance = ObservableMap<CryptoCurrency, EVMChainERC20Balance>.of(
62 + {
63 + // Not sure of this yet, will it work? will it not?
64 + nativeCurrency: initialBalance ?? EVMChainERC20Balance(BigInt.zero),
65 + },
66 + ),
67 + super(walletInfo) {
68 + this.walletInfo = walletInfo;
69 + transactionHistory = setUpTransactionHistory(walletInfo, password);
70 +
71 + if (!CakeHive.isAdapterRegistered(Erc20Token.typeId)) {
72 + CakeHive.registerAdapter(Erc20TokenAdapter());
73 + }
74 +
75 + sharedPrefs.complete(SharedPreferences.getInstance());
76 + }
77 +
78 + final String? _mnemonic;
79 + final String? _hexPrivateKey;
80 + final String _password;
81 +
82 + late final Box<Erc20Token> erc20TokensBox;
83 +
84 + late final Box<Erc20Token> evmChainErc20TokensBox;
85 +
86 + late final EthPrivateKey _evmChainPrivateKey;
87 +
88 + EthPrivateKey get evmChainPrivateKey => _evmChainPrivateKey;
89 +
90 + late EVMChainClient _client;
91 +
92 + int? _gasPrice;
93 + int? _estimatedGas;
94 + bool _isTransactionUpdating;
95 +
96 + // TODO: remove after integrating our own node and having eth_newPendingTransactionFilter
97 + Timer? _transactionsUpdateTimer;
98 +
99 + @override
100 + WalletAddresses walletAddresses;
101 +
102 + @override
103 + @observable
104 + SyncStatus syncStatus;
105 +
106 + @override
107 + @observable
108 + late ObservableMap<CryptoCurrency, EVMChainERC20Balance> balance;
109 +
110 + Completer<SharedPreferences> sharedPrefs = Completer();
111 +
112 + //! Methods to be overridden by every child
113 +
114 + void addInitialTokens();
115 +
116 + // Future<EVMChainWallet> open({
117 + // required String name,
118 + // required String password,
119 + // required WalletInfo walletInfo,
120 + // });
121 +
122 + Future<void> initErc20TokensBox();
123 +
124 + String getTransactionHistoryFileName();
125 +
126 + Future<bool> checkIfScanProviderIsEnabled();
127 +
128 + EVMChainTransactionInfo getTransactionInfo(
129 + EVMChainTransactionModel transactionModel, String address);
130 +
131 + Erc20Token createNewErc20TokenObject(Erc20Token token, String? iconPath);
132 +
133 + EVMChainTransactionHistory setUpTransactionHistory(WalletInfo walletInfo, String password);
134 +
135 + //! Common Methods across child classes
136 +
137 + String idFor(String name, WalletType type) => '${walletTypeToString(type).toLowerCase()}_$name';
138 +
139 + Future<void> init() async {
140 + await initErc20TokensBox();
141 +
142 + await walletAddresses.init();
143 + await transactionHistory.init();
144 + _evmChainPrivateKey = await getPrivateKey(
145 + mnemonic: _mnemonic,
146 + privateKey: _hexPrivateKey,
147 + password: _password,
148 + );
149 + walletAddresses.address = _evmChainPrivateKey.address.toString();
150 + await save();
151 + }
152 +
153 + @override
154 + int calculateEstimatedFee(TransactionPriority priority, int? amount) {
155 + try {
156 + if (priority is EVMChainTransactionPriority) {
157 + final priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
158 + return (_gasPrice! + priorityFee) * (_estimatedGas ?? 0);
159 + }
160 +
161 + return 0;
162 + } catch (e) {
163 + return 0;
164 + }
165 + }
166 +
167 + @override
168 + Future<void> changePassword(String password) {
169 + throw UnimplementedError("changePassword");
170 + }
171 +
172 + @override
173 + void close() {
174 + _client.stop();
175 + _transactionsUpdateTimer?.cancel();
176 + }
177 +
178 + @action
179 + @override
180 + Future<void> connectToNode({required Node node}) async {
181 + try {
182 + syncStatus = ConnectingSyncStatus();
183 +
184 + final isConnected = _client.connect(node);
185 +
186 + if (!isConnected) {
187 + throw Exception("${walletInfo.type.name.toUpperCase()} Node connection failed");
188 + }
189 +
190 + _client.setListeners(_evmChainPrivateKey.address, _onNewTransaction);
191 +
192 + _setTransactionUpdateTimer();
193 +
194 + syncStatus = ConnectedSyncStatus();
195 + } catch (e) {
196 + syncStatus = FailedSyncStatus();
197 + }
198 + }
199 +
200 + @action
201 + @override
202 + Future<void> startSync() async {
203 + try {
204 + syncStatus = AttemptingSyncStatus();
205 + await _updateBalance();
206 + await _updateTransactions();
207 + _gasPrice = await _client.getGasUnitPrice();
208 + _estimatedGas = await _client.getEstimatedGas();
209 +
210 + Timer.periodic(
211 + const Duration(minutes: 1), (timer) async => _gasPrice = await _client.getGasUnitPrice());
212 + Timer.periodic(const Duration(seconds: 10),
213 + (timer) async => _estimatedGas = await _client.getEstimatedGas());
214 +
215 + syncStatus = SyncedSyncStatus();
216 + } catch (e) {
217 + syncStatus = FailedSyncStatus();
218 + }
219 + }
220 +
221 + @override
222 + Future<PendingTransaction> createTransaction(Object credentials) async {
223 + final _credentials = credentials as EVMChainTransactionCredentials;
224 + final outputs = _credentials.outputs;
225 + final hasMultiDestination = outputs.length > 1;
226 +
227 + final CryptoCurrency transactionCurrency =
228 + balance.keys.firstWhere((element) => element.title == _credentials.currency.title);
229 +
230 + final _erc20Balance = balance[transactionCurrency]!;
231 + BigInt totalAmount = BigInt.zero;
232 + int exponent = transactionCurrency is Erc20Token ? transactionCurrency.decimal : 18;
233 + num amountToEVMChainMultiplier = pow(10, exponent);
234 +
235 + // so far this can not be made with Ethereum as Ethereum does not support multiple recipients
236 + if (hasMultiDestination) {
237 + if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) {
238 + throw EVMChainTransactionCreationException(transactionCurrency);
239 + }
240 +
241 + final totalOriginalAmount = EVMChainFormatter.parseEVMChainAmountToDouble(
242 + outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0)));
243 + totalAmount = BigInt.from(totalOriginalAmount * amountToEVMChainMultiplier);
244 +
245 + if (_erc20Balance.balance < totalAmount) {
246 + throw EVMChainTransactionCreationException(transactionCurrency);
247 + }
248 + } else {
249 + final output = outputs.first;
250 + // since the fees are taken from Ethereum
251 + // then no need to subtract the fees from the amount if send all
252 + final BigInt allAmount;
253 + if (transactionCurrency is Erc20Token) {
254 + allAmount = _erc20Balance.balance;
255 + } else {
256 + allAmount = _erc20Balance.balance -
257 + BigInt.from(calculateEstimatedFee(_credentials.priority!, null));
258 + }
259 + final totalOriginalAmount =
260 + EVMChainFormatter.parseEVMChainAmountToDouble(output.formattedCryptoAmount ?? 0);
261 + totalAmount = output.sendAll
262 + ? allAmount
263 + : BigInt.from(totalOriginalAmount * amountToEVMChainMultiplier);
264 +
265 + if (_erc20Balance.balance < totalAmount) {
266 + throw EVMChainTransactionCreationException(transactionCurrency);
267 + }
268 + }
269 +
270 + final pendingEVMChainTransaction = await _client.signTransaction(
271 + privateKey: _evmChainPrivateKey,
272 + toAddress: _credentials.outputs.first.isParsedAddress
273 + ? _credentials.outputs.first.extractedAddress!
274 + : _credentials.outputs.first.address,
275 + amount: totalAmount.toString(),
276 + gas: _estimatedGas!,
277 + priority: _credentials.priority!,
278 + currency: transactionCurrency,
279 + exponent: exponent,
280 + contractAddress:
281 + transactionCurrency is Erc20Token ? transactionCurrency.contractAddress : null,
282 + );
283 +
284 + return pendingEVMChainTransaction;
285 + }
286 +
287 + Future<void> _updateTransactions() async {
288 + try {
289 + if (_isTransactionUpdating) {
290 + return;
291 + }
292 +
293 + final isProviderEnabled = await checkIfScanProviderIsEnabled();
294 +
295 + if (!isProviderEnabled) {
296 + return;
297 + }
298 +
299 + _isTransactionUpdating = true;
300 + final transactions = await fetchTransactions();
301 + transactionHistory.addMany(transactions);
302 + await transactionHistory.save();
303 + _isTransactionUpdating = false;
304 + } catch (_) {
305 + _isTransactionUpdating = false;
306 + }
307 + }
308 +
309 + @override
310 + Future<Map<String, EVMChainTransactionInfo>> fetchTransactions() async {
311 + final address = _evmChainPrivateKey.address.hex;
312 + final transactions = await _client.fetchTransactions(address);
313 +
314 + final List<Future<List<EVMChainTransactionModel>>> erc20TokensTransactions = [];
315 +
316 + for (var token in balance.keys) {
317 + if (token is Erc20Token) {
318 + erc20TokensTransactions.add(_client.fetchTransactions(
319 + address,
320 + contractAddress: token.contractAddress,
321 + ));
322 + }
323 + }
324 +
325 + final tokensTransaction = await Future.wait(erc20TokensTransactions);
326 + transactions.addAll(tokensTransaction.expand((element) => element));
327 +
328 + final Map<String, EVMChainTransactionInfo> result = {};
329 +
330 + for (var transactionModel in transactions) {
331 + if (transactionModel.isError) {
332 + continue;
333 + }
334 +
335 + result[transactionModel.hash] = getTransactionInfo(transactionModel, address);
336 + }
337 +
338 + return result;
339 + }
340 +
341 + @override
342 + Object get keys => throw UnimplementedError("keys");
343 +
344 + @override
345 + Future<void> rescan({required int height}) {
346 + throw UnimplementedError("rescan");
347 + }
348 +
349 + @override
350 + Future<void> save() async {
351 + await walletAddresses.updateAddressesInBox();
352 + final path = await makePath();
353 + await write(path: path, password: _password, data: toJSON());
354 + await transactionHistory.save();
355 + }
356 +
357 + @override
358 + String? get seed => _mnemonic;
359 +
360 + @override
361 + String get privateKey => HEX.encode(_evmChainPrivateKey.privateKey);
362 +
363 + Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
364 +
365 + String toJSON() => json.encode({
366 + 'mnemonic': _mnemonic,
367 + 'private_key': privateKey,
368 + 'balance': balance[currency]!.toJSON(),
369 + });
370 +
371 + Future<void> _updateBalance() async {
372 + balance[currency] = await _fetchEVMChainBalance();
373 +
374 + await _fetchErc20Balances();
375 + await save();
376 + }
377 +
378 + Future<EVMChainERC20Balance> _fetchEVMChainBalance() async {
379 + final balance = await _client.getBalance(_evmChainPrivateKey.address);
380 + return EVMChainERC20Balance(balance.getInWei);
381 + }
382 +
383 + Future<void> _fetchErc20Balances() async {
384 + for (var token in evmChainErc20TokensBox.values) {
385 + try {
386 + if (token.enabled) {
387 + balance[token] = await _client.fetchERC20Balances(
388 + _evmChainPrivateKey.address,
389 + token.contractAddress,
390 + );
391 + } else {
392 + balance.remove(token);
393 + }
394 + } catch (_) {}
395 + }
396 + }
397 +
398 + Future<EthPrivateKey> getPrivateKey(
399 + {String? mnemonic, String? privateKey, required String password}) async {
400 + assert(mnemonic != null || privateKey != null);
401 +
402 + if (privateKey != null) {
403 + return EthPrivateKey.fromHex(privateKey);
404 + }
405 +
406 + final seed = bip39.mnemonicToSeed(mnemonic!);
407 +
408 + final root = bip32.BIP32.fromSeed(seed);
409 +
410 + const hdPathEVMChain = "m/44'/60'/0'/0";
411 + const index = 0;
412 + final addressAtIndex = root.derivePath("$hdPathEVMChain/$index");
413 +
414 + return EthPrivateKey.fromHex(HEX.encode(addressAtIndex.privateKey as List<int>));
415 + }
416 +
417 + Future<void>? updateBalance() async => await _updateBalance();
418 +
419 + List<Erc20Token> get erc20Currencies => evmChainErc20TokensBox.values.toList();
420 +
421 + Future<void> addErc20Token(Erc20Token token) async {
422 + String? iconPath;
423 + try {
424 + iconPath = CryptoCurrency.all
425 + .firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase())
426 + .iconPath;
427 + } catch (_) {}
428 +
429 + final newToken = createNewErc20TokenObject(token, iconPath);
430 +
431 + await evmChainErc20TokensBox.put(newToken.contractAddress, newToken);
432 +
433 + if (newToken.enabled) {
434 + balance[newToken] = await _client.fetchERC20Balances(
435 + _evmChainPrivateKey.address,
436 + newToken.contractAddress,
437 + );
438 + } else {
439 + balance.remove(newToken);
440 + }
441 + }
442 +
443 + Future<void> deleteErc20Token(Erc20Token token) async {
444 + await token.delete();
445 +
446 + balance.remove(token);
447 + _updateBalance();
448 + }
449 +
450 + Future<Erc20Token?> getErc20Token(String contractAddress) async =>
451 + await _client.getErc20Token(contractAddress);
452 +
453 + void _onNewTransaction() {
454 + _updateBalance();
455 + _updateTransactions();
456 + }
457 +
458 + @override
459 + Future<void> renameWalletFiles(String newWalletName) async {
460 + final transactionHistoryFileNameForWallet = getTransactionHistoryFileName();
461 +
462 + final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type);
463 + final currentWalletFile = File(currentWalletPath);
464 +
465 + final currentDirPath = await pathForWalletDir(name: walletInfo.name, type: type);
466 + final currentTransactionsFile = File('$currentDirPath/$transactionHistoryFileNameForWallet');
467 +
468 + // Copies current wallet files into new wallet name's dir and files
469 + if (currentWalletFile.existsSync()) {
470 + final newWalletPath = await pathForWallet(name: newWalletName, type: type);
471 + await currentWalletFile.copy(newWalletPath);
472 + }
473 + if (currentTransactionsFile.existsSync()) {
474 + final newDirPath = await pathForWalletDir(name: newWalletName, type: type);
475 + await currentTransactionsFile.copy('$newDirPath/$transactionHistoryFileNameForWallet');
476 + }
477 +
478 + // Delete old name's dir and files
479 + await Directory(currentDirPath).delete(recursive: true);
480 + }
481 +
482 + void _setTransactionUpdateTimer() {
483 + if (_transactionsUpdateTimer?.isActive ?? false) {
484 + _transactionsUpdateTimer!.cancel();
485 + }
486 +
487 + _transactionsUpdateTimer = Timer.periodic(const Duration(seconds: 10), (_) {
488 + _updateTransactions();
489 + _updateBalance();
490 + });
491 + }
492 +
493 + /// Scan Providers:
494 + ///
495 + /// EtherScan for Ethereum.
496 + ///
497 + /// PolygonScan for Polygon.
498 + void updateScanProviderUsageState(bool isEnabled) {
499 + if (isEnabled) {
500 + _updateTransactions();
501 + _setTransactionUpdateTimer();
502 + } else {
503 + _transactionsUpdateTimer?.cancel();
504 + }
505 + }
506 +
507 + @override
508 + String signMessage(String message, {String? address}) =>
509 + bytesToHex(_evmChainPrivateKey.signPersonalMessageToUint8List(ascii.encode(message)));
510 +
511 + Web3Client? getWeb3Client() => _client.getWeb3Client();
512 +}
cw_evm/lib/evm_chain_wallet_addresses.dart renamed
+7 -5
@@ -1,13 +1,15 @@
1 +import 'dart:developer';
2 +
3 import 'package:cw_core/wallet_addresses.dart';
4 import 'package:cw_core/wallet_info.dart';
5 import 'package:mobx/mobx.dart';
6
5 -part 'ethereum_wallet_addresses.g.dart';
7 +part 'evm_chain_wallet_addresses.g.dart';
8
7 -class EthereumWalletAddresses = EthereumWalletAddressesBase with _$EthereumWalletAddresses;
9 +class EVMChainWalletAddresses = EVMChainWalletAddressesBase with _$EVMChainWalletAddresses;
10
9 -abstract class EthereumWalletAddressesBase extends WalletAddresses with Store {
10 - EthereumWalletAddressesBase(WalletInfo walletInfo)
11 +abstract class EVMChainWalletAddressesBase extends WalletAddresses with Store {
12 + EVMChainWalletAddressesBase(WalletInfo walletInfo)
13 : address = '',
14 super(walletInfo);
15
@@ -27,7 +29,7 @@ abstract class EthereumWalletAddressesBase extends WalletAddresses with Store {
29 addressesMap[address] = '';
30 await saveAddressesInBox();
31 } catch (e) {
30 - print(e.toString());
32 + log(e.toString());
33 }
34 }
35 }
cw_evm/lib/evm_chain_wallet_creation_credentials.dart new
+29
@@ -0,0 +1,29 @@
1 +import 'package:cw_core/wallet_credentials.dart';
2 +import 'package:cw_core/wallet_info.dart';
3 +
4 +class EVMChainNewWalletCredentials extends WalletCredentials {
5 + EVMChainNewWalletCredentials({required String name, WalletInfo? walletInfo})
6 + : super(name: name, walletInfo: walletInfo);
7 +}
8 +
9 +class EVMChainRestoreWalletFromSeedCredentials extends WalletCredentials {
10 + EVMChainRestoreWalletFromSeedCredentials({
11 + required String name,
12 + required String password,
13 + required this.mnemonic,
14 + WalletInfo? walletInfo,
15 + }) : super(name: name, password: password, walletInfo: walletInfo);
16 +
17 + final String mnemonic;
18 +}
19 +
20 +class EVMChainRestoreWalletFromPrivateKey extends WalletCredentials {
21 + EVMChainRestoreWalletFromPrivateKey({
22 + required String name,
23 + required String password,
24 + required this.privateKey,
25 + WalletInfo? walletInfo,
26 + }) : super(name: name, password: password, walletInfo: walletInfo);
27 +
28 + final String privateKey;
29 +}
cw_evm/lib/evm_chain_wallet_service.dart new
+50
@@ -0,0 +1,50 @@
1 +import 'dart:io';
2 +
3 +import 'package:collection/collection.dart';
4 +import 'package:cw_core/pathForWallet.dart';
5 +import 'package:cw_core/wallet_base.dart';
6 +import 'package:cw_core/wallet_info.dart';
7 +import 'package:cw_core/wallet_service.dart';
8 +import 'package:cw_core/wallet_type.dart';
9 +import 'package:cw_evm/evm_chain_wallet.dart';
10 +import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
11 +import 'package:hive/hive.dart';
12 +
13 +abstract class EVMChainWalletService<T extends EVMChainWallet> extends WalletService<
14 + EVMChainNewWalletCredentials,
15 + EVMChainRestoreWalletFromSeedCredentials,
16 + EVMChainRestoreWalletFromPrivateKey> {
17 + EVMChainWalletService(this.walletInfoSource);
18 +
19 + final Box<WalletInfo> walletInfoSource;
20 +
21 + @override
22 + WalletType getType();
23 +
24 + @override
25 + Future<T> create(EVMChainNewWalletCredentials credentials);
26 +
27 + @override
28 + Future<T> openWallet(String name, String password);
29 +
30 + @override
31 + Future<void> rename(String currentName, String password, String newName);
32 +
33 + @override
34 + Future<T> restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials);
35 +
36 + @override
37 + Future<T> restoreFromSeed(EVMChainRestoreWalletFromSeedCredentials credentials);
38 +
39 + @override
40 + Future<bool> isWalletExit(String name) async =>
41 + File(await pathForWallet(name: name, type: getType())).existsSync();
42 +
43 + @override
44 + Future<void> remove(String wallet) async {
45 + File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true);
46 + final walletInfo = walletInfoSource.values
47 + .firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!;
48 + await walletInfoSource.delete(walletInfo.key);
49 + }
50 +}
cw_evm/lib/evm_erc20_balance.dart renamed
+6 -7
@@ -3,10 +3,9 @@ import 'dart:math';
3
4 import 'package:cw_core/balance.dart';
5
6 -class ERC20Balance extends Balance {
7 - ERC20Balance(this.balance, {this.exponent = 18})
8 - : super(balance.toInt(),
9 - balance.toInt());
6 +class EVMChainERC20Balance extends Balance {
7 + EVMChainERC20Balance(this.balance, {this.exponent = 18})
8 + : super(balance.toInt(), balance.toInt());
9
10 final BigInt balance;
11 final int exponent;
@@ -28,7 +27,7 @@ class ERC20Balance extends Balance {
27 'exponent': exponent,
28 });
29
31 - static ERC20Balance? fromJSON(String? jsonSource) {
30 + static EVMChainERC20Balance? fromJSON(String? jsonSource) {
31 if (jsonSource == null) {
32 return null;
33 }
@@ -36,12 +35,12 @@ class ERC20Balance extends Balance {
35 final decoded = json.decode(jsonSource) as Map;
36
37 try {
39 - return ERC20Balance(
38 + return EVMChainERC20Balance(
39 BigInt.parse(decoded['balanceInWei']),
40 exponent: decoded['exponent'],
41 );
42 } catch (e) {
44 - return ERC20Balance(BigInt.zero);
43 + return EVMChainERC20Balance(BigInt.zero);
44 }
45 }
46 }
cw_evm/lib/file.dart renamed
cw_evm/lib/pending_evm_chain_transaction.dart renamed
+2 -2
@@ -4,14 +4,14 @@ import 'dart:typed_data';
4 import 'package:cw_core/pending_transaction.dart';
5 import 'package:web3dart/crypto.dart';
6
7 -class PendingEthereumTransaction with PendingTransaction {
7 +class PendingEVMChainTransaction with PendingTransaction {
8 final Function sendTransaction;
9 final Uint8List signedTransaction;
10 final BigInt fee;
11 final String amount;
12 final int exponent;
13
14 - PendingEthereumTransaction({
14 + PendingEVMChainTransaction({
15 required this.sendTransaction,
16 required this.signedTransaction,
17 required this.fee,
cw_evm/pubspec.yaml new
+45
@@ -0,0 +1,45 @@
1 +name: cw_evm
2 +description: A new Flutter package project.
3 +version: 0.0.1
4 +publish_to: none
5 +author: Cake Wallet
6 +homepage: https://cakewallet.com
7 +
8 +environment:
9 + sdk: '>=3.0.6 <4.0.0'
10 + flutter: ">=1.17.0"
11 +
12 +dependencies:
13 + flutter:
14 + sdk: flutter
15 + web3dart: ^2.7.1
16 + erc20: ^1.0.1
17 + bip39: ^1.0.6
18 + bip32: ^2.0.0
19 + hex: ^0.2.0
20 + http: ^1.1.0
21 + hive: ^2.2.3
22 + collection: ^1.17.1
23 + shared_preferences: ^2.0.15
24 + cw_core:
25 + path: ../cw_core
26 +
27 +dev_dependencies:
28 + flutter_test:
29 + sdk: flutter
30 + build_runner: ^2.1.11
31 + mobx_codegen: ^2.0.7
32 + hive_generator: ^1.1.3
33 + flutter_lints: ^2.0.0
34 +
35 +flutter:
36 + # assets:
37 + # - images/a_dot_burr.jpeg
38 + # - images/a_dot_ham.jpeg
39 + #
40 + # fonts:
41 + # - family: Schyler
42 + # fonts:
43 + # - asset: fonts/Schyler-Regular.ttf
44 + # - asset: fonts/Schyler-Italic.ttf
45 + # style: italic
cw_evm/test/cw_evm_test.dart new
+12
@@ -0,0 +1,12 @@
1 +import 'package:flutter_test/flutter_test.dart';
2 +
3 +import 'package:cw_evm/cw_evm.dart';
4 +
5 +void main() {
6 + test('adds one to input values', () {
7 + final calculator = Calculator();
8 + expect(calculator.addOne(2), 3);
9 + expect(calculator.addOne(-7), -6);
10 + expect(calculator.addOne(0), 1);
11 + });
12 +}
cw_polygon/lib/pending_polygon_transaction.dart deleted
-19
@@ -1,19 +0,0 @@
1 -import 'dart:typed_data';
2 -
3 -import 'package:cw_ethereum/pending_ethereum_transaction.dart';
4 -
5 -class PendingPolygonTransaction extends PendingEthereumTransaction {
6 - PendingPolygonTransaction({
7 - required Function sendTransaction,
8 - required Uint8List signedTransaction,
9 - required BigInt fee,
10 - required String amount,
11 - required int exponent,
12 - }) : super(
13 - amount: amount,
14 - sendTransaction: sendTransaction,
15 - signedTransaction: signedTransaction,
16 - fee: fee,
17 - exponent: exponent,
18 - );
19 -}
cw_polygon/lib/polygon_client.dart
+8 -6
@@ -1,12 +1,12 @@
1 import 'dart:convert';
2
3 -import 'package:cw_ethereum/ethereum_client.dart';
4 -import 'package:cw_polygon/polygon_transaction_model.dart';
5 -import 'package:cw_ethereum/.secrets.g.dart' as secrets;
3 +import 'package:cw_evm/evm_chain_client.dart';
4 +import 'package:cw_evm/.secrets.g.dart' as secrets;
5 +import 'package:cw_evm/evm_chain_transaction_model.dart';
6 import 'package:flutter/foundation.dart';
7 import 'package:web3dart/web3dart.dart';
8
9 -class PolygonClient extends EthereumClient {
9 +class PolygonClient extends EVMChainClient {
10 @override
11 Transaction createTransaction({
12 required EthereumAddress from,
@@ -28,7 +28,7 @@ class PolygonClient extends EthereumClient {
28 int get chainId => 137;
29
30 @override
31 - Future<List<PolygonTransactionModel>> fetchTransactions(String address,
31 + Future<List<EVMChainTransactionModel>> fetchTransactions(String address,
32 {String? contractAddress}) async {
33 try {
34 final response = await httpClient.get(Uri.https("api.polygonscan.com", "/api", {
@@ -43,7 +43,9 @@ class PolygonClient extends EthereumClient {
43
44 if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) {
45 return (jsonResponse['result'] as List)
46 - .map((e) => PolygonTransactionModel.fromJson(e as Map<String, dynamic>))
46 + .map(
47 + (e) => EVMChainTransactionModel.fromJson(e as Map<String, dynamic>, 'MATIC'),
48 + )
49 .toList();
50 }
51
cw_polygon/lib/polygon_exceptions.dart deleted
-6
@@ -1,6 +0,0 @@
1 -import 'package:cw_core/crypto_currency.dart';
2 -import 'package:cw_ethereum/ethereum_exceptions.dart';
3 -
4 -class PolygonTransactionCreationException extends EthereumTransactionCreationException {
5 - PolygonTransactionCreationException(CryptoCurrency currency) : super(currency);
6 -}
cw_polygon/lib/polygon_formatter.dart deleted
-25
@@ -1,25 +0,0 @@
1 -import 'package:intl/intl.dart';
2 -
3 -const polygonAmountLength = 12;
4 -const polygonAmountDivider = 1000000000000;
5 -final polygonAmountFormat = NumberFormat()
6 - ..maximumFractionDigits = polygonAmountLength
7 - ..minimumFractionDigits = 1;
8 -
9 -class PolygonFormatter {
10 - static int parsePolygonAmount(String amount) {
11 - try {
12 - return (double.parse(amount) * polygonAmountDivider).round();
13 - } catch (_) {
14 - return 0;
15 - }
16 - }
17 -
18 - static double parsePolygonAmountToDouble(int amount) {
19 - try {
20 - return amount / polygonAmountDivider;
21 - } catch (_) {
22 - return 0;
23 - }
24 - }
25 -}
cw_polygon/lib/polygon_transaction_credentials.dart deleted
-18
@@ -1,18 +0,0 @@
1 -import 'package:cw_core/crypto_currency.dart';
2 -import 'package:cw_core/output_info.dart';
3 -import 'package:cw_ethereum/ethereum_transaction_credentials.dart';
4 -import 'package:cw_polygon/polygon_transaction_priority.dart';
5 -
6 -class PolygonTransactionCredentials extends EthereumTransactionCredentials {
7 - PolygonTransactionCredentials(
8 - List<OutputInfo> outputs, {
9 - required PolygonTransactionPriority? priority,
10 - required CryptoCurrency currency,
11 - final int? feeRate,
12 - }) : super(
13 - outputs,
14 - currency: currency,
15 - priority: priority,
16 - feeRate: feeRate,
17 - );
18 -}
cw_polygon/lib/polygon_transaction_history.dart
+11 -69
@@ -1,77 +1,19 @@
1 -import 'dart:convert';
1 import 'dart:core';
3 -import 'package:cw_core/pathForWallet.dart';
4 -import 'package:cw_core/wallet_info.dart';
5 -import 'package:cw_ethereum/file.dart';
6 -import 'package:cw_polygon/polygon_transaction_info.dart';
7 -import 'package:mobx/mobx.dart';
8 -import 'package:cw_core/transaction_history.dart';
9 -
10 -part 'polygon_transaction_history.g.dart';
11 -
12 -const transactionsHistoryFileName = 'polygon_transactions.json';
13 -
14 -class PolygonTransactionHistory = PolygonTransactionHistoryBase with _$PolygonTransactionHistory;
2
16 -abstract class PolygonTransactionHistoryBase extends TransactionHistoryBase<PolygonTransactionInfo>
17 - with Store {
18 - PolygonTransactionHistoryBase({required this.walletInfo, required String password})
19 - : _password = password {
20 - transactions = ObservableMap<String, PolygonTransactionInfo>();
21 - }
22 -
23 - final WalletInfo walletInfo;
24 - String _password;
3 +import 'package:cw_evm/evm_chain_transaction_history.dart';
4 +import 'package:cw_evm/evm_chain_transaction_info.dart';
5 +import 'package:cw_polygon/polygon_transaction_info.dart';
6
26 - Future<void> init() async => await _load();
7 +class PolygonTransactionHistory extends EVMChainTransactionHistory {
8 + PolygonTransactionHistory({
9 + required super.walletInfo,
10 + required super.password,
11 + });
12
13 @override
29 - Future<void> save() async {
30 - try {
31 - final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
32 - final path = '$dirPath/$transactionsHistoryFileName';
33 - final data = json.encode({'transactions': transactions});
34 - await writeData(path: path, password: _password, data: data);
35 - } catch (e, s) {
36 - print('Error while saving polygon transaction history: ${e.toString()}');
37 - print(s);
38 - }
39 - }
14 + String getTransactionHistoryFileName() => 'polygon_transactions.json';
15
16 @override
42 - void addOne(PolygonTransactionInfo transaction) => transactions[transaction.id] = transaction;
43 -
44 - @override
45 - void addMany(Map<String, PolygonTransactionInfo> transactions) =>
46 - this.transactions.addAll(transactions);
47 -
48 - Future<Map<String, dynamic>> _read() async {
49 - final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
50 - final path = '$dirPath/$transactionsHistoryFileName';
51 - final content = await read(path: path, password: _password);
52 - if (content.isEmpty) {
53 - return {};
54 - }
55 - return json.decode(content) as Map<String, dynamic>;
56 - }
57 -
58 - Future<void> _load() async {
59 - try {
60 - final content = await _read();
61 - final txs = content['transactions'] as Map<String, dynamic>? ?? {};
62 -
63 - txs.entries.forEach((entry) {
64 - final val = entry.value;
65 -
66 - if (val is Map<String, dynamic>) {
67 - final tx = PolygonTransactionInfo.fromJson(val);
68 - _update(tx);
69 - }
70 - });
71 - } catch (e) {
72 - print(e);
73 - }
74 - }
75 -
76 - void _update(PolygonTransactionInfo transaction) => transactions[transaction.id] = transaction;
17 + EVMChainTransactionInfo getTransactionInfo(Map<String, dynamic> val) =>
18 + PolygonTransactionInfo.fromJson(val);
19 }
cw_polygon/lib/polygon_transaction_info.dart
+17 -27
@@ -1,32 +1,21 @@
1 import 'package:cw_core/transaction_direction.dart';
2 -import 'package:cw_ethereum/ethereum_transaction_info.dart';
2 +import 'package:cw_evm/evm_chain_transaction_info.dart';
3
4 -class PolygonTransactionInfo extends EthereumTransactionInfo {
4 +class PolygonTransactionInfo extends EVMChainTransactionInfo {
5 PolygonTransactionInfo({
6 - required String id,
7 - required int height,
8 - required BigInt ethAmount,
9 - int exponent = 18,
10 - required TransactionDirection direction,
11 - required DateTime date,
12 - required bool isPending,
13 - required BigInt ethFee,
14 - required int confirmations,
15 - String tokenSymbol = "MATIC",
16 - required String? to,
17 - }) : super(
18 - confirmations: confirmations,
19 - id: id,
20 - height: height,
21 - ethAmount: ethAmount,
22 - exponent: exponent,
23 - direction: direction,
24 - date: date,
25 - isPending: isPending,
26 - ethFee: ethFee,
27 - to: to,
28 - tokenSymbol: tokenSymbol,
29 - );
6 + required super.id,
7 + required super.height,
8 + required super.ethAmount,
9 + required super.ethFee,
10 + required super.tokenSymbol,
11 + required super.direction,
12 + required super.isPending,
13 + required super.date,
14 + required super.confirmations,
15 + required super.to,
16 + required super.from,
17 + super.exponent,
18 + });
19
20 factory PolygonTransactionInfo.fromJson(Map<String, dynamic> data) {
21 return PolygonTransactionInfo(
@@ -41,9 +30,10 @@ class PolygonTransactionInfo extends EthereumTransactionInfo {
30 confirmations: data['confirmations'] as int,
31 tokenSymbol: data['tokenSymbol'] as String,
32 to: data['to'],
33 + from: data['from'],
34 );
35 }
36
37 @override
48 - String feeFormatted() => '${(ethFee / BigInt.from(10).pow(18)).toString()} MATIC';
38 + String get feeCurrency => 'MATIC';
39 }
cw_polygon/lib/polygon_transaction_model.dart deleted
-49
@@ -1,49 +0,0 @@
1 -import 'package:cw_ethereum/ethereum_transaction_model.dart';
2 -
3 -class PolygonTransactionModel extends EthereumTransactionModel {
4 - PolygonTransactionModel({
5 - required DateTime date,
6 - required String hash,
7 - required String from,
8 - required String to,
9 - required BigInt amount,
10 - required int gasUsed,
11 - required BigInt gasPrice,
12 - required String contractAddress,
13 - required int confirmations,
14 - required int blockNumber,
15 - required String? tokenSymbol,
16 - required int? tokenDecimal,
17 - required bool isError,
18 - }) : super(
19 - amount: amount,
20 - date: date,
21 - hash: hash,
22 - from: from,
23 - to: to,
24 - gasPrice: gasPrice,
25 - gasUsed: gasUsed,
26 - confirmations: confirmations,
27 - contractAddress: contractAddress,
28 - blockNumber: blockNumber,
29 - tokenDecimal: tokenDecimal,
30 - tokenSymbol: tokenSymbol,
31 - isError: isError,
32 - );
33 -
34 - factory PolygonTransactionModel.fromJson(Map<String, dynamic> json) => PolygonTransactionModel(
35 - date: DateTime.fromMillisecondsSinceEpoch(int.parse(json["timeStamp"]) * 1000),
36 - hash: json["hash"],
37 - from: json["from"],
38 - to: json["to"],
39 - amount: BigInt.parse(json["value"]),
40 - gasUsed: int.parse(json["gasUsed"]),
41 - gasPrice: BigInt.parse(json["gasPrice"]),
42 - contractAddress: json["contractAddress"],
43 - confirmations: int.parse(json["confirmations"]),
44 - blockNumber: int.parse(json["blockNumber"]),
45 - tokenSymbol: json["tokenSymbol"] ?? "MATIC",
46 - tokenDecimal: int.tryParse(json["tokenDecimal"] ?? ""),
47 - isError: json["isError"] == "1",
48 - );
49 -}
cw_polygon/lib/polygon_transaction_priority.dart deleted
-51
@@ -1,51 +0,0 @@
1 -import 'package:cw_ethereum/ethereum_transaction_priority.dart';
2 -
3 -class PolygonTransactionPriority extends EthereumTransactionPriority {
4 - const PolygonTransactionPriority({required String title, required int raw, required int tip})
5 - : super(title: title, raw: raw, tip: tip);
6 -
7 - static const List<PolygonTransactionPriority> all = [fast, medium, slow];
8 - static const PolygonTransactionPriority slow =
9 - PolygonTransactionPriority(title: 'slow', raw: 0, tip: 1);
10 - static const PolygonTransactionPriority medium =
11 - PolygonTransactionPriority(title: 'Medium', raw: 1, tip: 2);
12 - static const PolygonTransactionPriority fast =
13 - PolygonTransactionPriority(title: 'Fast', raw: 2, tip: 4);
14 -
15 - static PolygonTransactionPriority deserialize({required int raw}) {
16 - switch (raw) {
17 - case 0:
18 - return slow;
19 - case 1:
20 - return medium;
21 - case 2:
22 - return fast;
23 - default:
24 - throw Exception('Unexpected token: $raw for PolygonTransactionPriority deserialize');
25 - }
26 - }
27 -
28 - @override
29 - String get units => 'gas';
30 -
31 - @override
32 - String toString() {
33 - var label = '';
34 -
35 - switch (this) {
36 - case PolygonTransactionPriority.slow:
37 - label = 'Slow';
38 - break;
39 - case PolygonTransactionPriority.medium:
40 - label = 'Medium';
41 - break;
42 - case PolygonTransactionPriority.fast:
43 - label = 'Fast';
44 - break;
45 - default:
46 - break;
47 - }
48 -
49 - return label;
50 - }
51 -}
cw_polygon/lib/polygon_wallet.dart
+63 -471
@@ -1,366 +1,109 @@
1 -import 'dart:async';
1 import 'dart:convert';
3 -import 'dart:io';
4 -import 'dart:math';
2
3 import 'package:cw_core/crypto_currency.dart';
4 import 'package:cw_core/cake_hive.dart';
8 -import 'package:cw_core/node.dart';
5 +import 'package:cw_core/erc20_token.dart';
6 import 'package:cw_core/pathForWallet.dart';
10 -import 'package:cw_core/pending_transaction.dart';
11 -import 'package:cw_core/sync_status.dart';
7 import 'package:cw_core/transaction_direction.dart';
13 -import 'package:cw_core/transaction_priority.dart';
14 -import 'package:cw_core/wallet_addresses.dart';
15 -import 'package:cw_core/wallet_base.dart';
8 import 'package:cw_core/wallet_info.dart';
17 -import 'package:cw_ethereum/erc20_balance.dart';
18 -import 'package:cw_ethereum/ethereum_formatter.dart';
19 -import 'package:cw_ethereum/file.dart';
20 -import 'package:cw_core/erc20_token.dart';
9 +import 'package:cw_evm/evm_chain_transaction_history.dart';
10 +import 'package:cw_evm/evm_chain_transaction_info.dart';
11 +import 'package:cw_evm/evm_chain_transaction_model.dart';
12 +import 'package:cw_evm/evm_chain_wallet.dart';
13 +import 'package:cw_evm/evm_erc20_balance.dart';
14 +import 'package:cw_evm/file.dart';
15 import 'package:cw_polygon/default_polygon_erc20_tokens.dart';
16 +import 'package:cw_polygon/polygon_transaction_info.dart';
17 import 'package:cw_polygon/polygon_client.dart';
23 -import 'package:cw_polygon/polygon_exceptions.dart';
24 -import 'package:cw_polygon/polygon_formatter.dart';
25 -import 'package:cw_polygon/polygon_transaction_credentials.dart';
18 import 'package:cw_polygon/polygon_transaction_history.dart';
27 -import 'package:cw_polygon/polygon_transaction_info.dart';
28 -import 'package:cw_polygon/polygon_transaction_model.dart';
29 -import 'package:cw_polygon/polygon_transaction_priority.dart';
30 -import 'package:cw_polygon/polygon_wallet_addresses.dart';
31 -import 'package:hive/hive.dart';
32 -import 'package:hex/hex.dart';
33 -import 'package:mobx/mobx.dart';
34 -import 'package:shared_preferences/shared_preferences.dart';
35 -import 'package:web3dart/crypto.dart';
36 -import 'package:web3dart/web3dart.dart';
37 -import 'package:bip39/bip39.dart' as bip39;
38 -import 'package:bip32/bip32.dart' as bip32;
39 -
40 -part 'polygon_wallet.g.dart';
41 -
42 -class PolygonWallet = PolygonWalletBase with _$PolygonWallet;
43 -
44 -abstract class PolygonWalletBase
45 - extends WalletBase<ERC20Balance, PolygonTransactionHistory, PolygonTransactionInfo> with Store {
46 - PolygonWalletBase({
47 - required WalletInfo walletInfo,
48 - String? mnemonic,
49 - String? privateKey,
50 - required String password,
51 - ERC20Balance? initialBalance,
52 - }) : syncStatus = const NotConnectedSyncStatus(),
53 - _password = password,
54 - _mnemonic = mnemonic,
55 - _hexPrivateKey = privateKey,
56 - _isTransactionUpdating = false,
57 - _client = PolygonClient(),
58 - walletAddresses = PolygonWalletAddresses(walletInfo),
59 - balance = ObservableMap<CryptoCurrency, ERC20Balance>.of(
60 - {CryptoCurrency.maticpoly: initialBalance ?? ERC20Balance(BigInt.zero)}),
61 - super(walletInfo) {
62 - this.walletInfo = walletInfo;
63 - transactionHistory = PolygonTransactionHistory(walletInfo: walletInfo, password: password);
64 -
65 - if (!CakeHive.isAdapterRegistered(Erc20Token.typeId)) {
66 - CakeHive.registerAdapter(Erc20TokenAdapter());
67 - }
68 -
69 - _sharedPrefs.complete(SharedPreferences.getInstance());
70 - }
71 -
72 - final String? _mnemonic;
73 - final String? _hexPrivateKey;
74 - final String _password;
75 -
76 - late final Box<Erc20Token> polygonErc20TokensBox;
77 -
78 - late final EthPrivateKey _polygonPrivateKey;
79 -
80 - late final PolygonClient _client;
81 -
82 - EthPrivateKey get polygonPrivateKey => _polygonPrivateKey;
83 -
84 - int? _gasPrice;
85 - int? _estimatedGas;
86 - bool _isTransactionUpdating;
87 -
88 - // TODO: remove after integrating our own node and having eth_newPendingTransactionFilter
89 - Timer? _transactionsUpdateTimer;
90 -
91 - @override
92 - WalletAddresses walletAddresses;
19
94 - @override
95 - @observable
96 - SyncStatus syncStatus;
20 +class PolygonWallet extends EVMChainWallet {
21 + PolygonWallet({
22 + required super.walletInfo,
23 + required super.password,
24 + super.mnemonic,
25 + super.initialBalance,
26 + super.privateKey,
27 + required super.client,
28 + }) : super(nativeCurrency: CryptoCurrency.maticpoly);
29
30 @override
99 - @observable
100 - late ObservableMap<CryptoCurrency, ERC20Balance> balance;
101 -
102 - final Completer<SharedPreferences> _sharedPrefs = Completer();
103 -
104 - Future<void> init() async {
31 + Future<void> initErc20TokensBox() async {
32 final boxName = "${walletInfo.name.replaceAll(" ", "_")}_ ${Erc20Token.polygonBoxName}";
33 if (await CakeHive.boxExists(boxName)) {
107 - polygonErc20TokensBox = await CakeHive.openBox<Erc20Token>(boxName);
34 + evmChainErc20TokensBox = await CakeHive.openBox<Erc20Token>(boxName);
35 } else {
109 - polygonErc20TokensBox = await CakeHive.openBox<Erc20Token>(boxName.replaceAll(" ", ""));
36 + evmChainErc20TokensBox = await CakeHive.openBox<Erc20Token>(boxName.replaceAll(" ", ""));
37 }
111 - await walletAddresses.init();
112 - await transactionHistory.init();
113 - _polygonPrivateKey = await getPrivateKey(
114 - mnemonic: _mnemonic,
115 - privateKey: _hexPrivateKey,
116 - password: _password,
117 - );
118 - walletAddresses.address = _polygonPrivateKey.address.toString();
119 - await save();
38 }
39
40 @override
123 - int calculateEstimatedFee(TransactionPriority priority, int? amount) {
124 - try {
125 - if (priority is PolygonTransactionPriority) {
126 - final priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
127 - return (_gasPrice! + priorityFee) * (_estimatedGas ?? 0);
128 - }
41 + void addInitialTokens() {
42 + final initialErc20Tokens = DefaultPolygonErc20Tokens().initialPolygonErc20Tokens;
43
130 - return 0;
131 - } catch (e) {
132 - return 0;
44 + for (var token in initialErc20Tokens) {
45 + evmChainErc20TokensBox.put(token.contractAddress, token);
46 }
47 }
48
49 @override
137 - Future<void> changePassword(String password) {
138 - throw UnimplementedError("changePassword");
50 + Future<bool> checkIfScanProviderIsEnabled() async {
51 + bool isPolygonScanEnabled = (await sharedPrefs.future).getBool("use_polygonscan") ?? true;
52 + return isPolygonScanEnabled;
53 }
54
55 @override
142 - void close() {
143 - _client.stop();
144 - _transactionsUpdateTimer?.cancel();
145 - }
56 + String getTransactionHistoryFileName() => 'polygon_transactions.json';
57
147 - @action
58 @override
149 - Future<void> connectToNode({required Node node}) async {
150 - try {
151 - syncStatus = ConnectingSyncStatus();
152 -
153 - final isConnected = _client.connect(node);
154 -
155 - if (!isConnected) {
156 - throw Exception("Polygon Node connection failed");
157 - }
158 -
159 - _client.setListeners(_polygonPrivateKey.address, _onNewTransaction);
160 -
161 - _setTransactionUpdateTimer();
162 -
163 - syncStatus = ConnectedSyncStatus();
164 - } catch (e) {
165 - syncStatus = FailedSyncStatus();
166 - }
167 - }
168 -
169 - @override
170 - Future<PendingTransaction> createTransaction(Object credentials) async {
171 - final credentials0 = credentials as PolygonTransactionCredentials;
172 - final outputs = credentials0.outputs;
173 - final hasMultiDestination = outputs.length > 1;
174 -
175 - final CryptoCurrency transactionCurrency =
176 - balance.keys.firstWhere((element) => element.title == credentials0.currency.title);
177 -
178 - final erc20Balance = balance[transactionCurrency]!;
179 - BigInt totalAmount = BigInt.zero;
180 - int exponent = transactionCurrency is Erc20Token ? transactionCurrency.decimal : 18;
181 - num amountToPolygonMultiplier = pow(10, exponent);
182 -
183 - // so far this can not be made with Polygon as Polygon does not support multiple recipients
184 - if (hasMultiDestination) {
185 - if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) {
186 - throw PolygonTransactionCreationException(transactionCurrency);
187 - }
188 -
189 - final totalOriginalAmount = PolygonFormatter.parsePolygonAmountToDouble(
190 - outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0)));
191 - totalAmount = BigInt.from(totalOriginalAmount * amountToPolygonMultiplier);
192 -
193 - if (erc20Balance.balance < totalAmount) {
194 - throw PolygonTransactionCreationException(transactionCurrency);
195 - }
196 - } else {
197 - final output = outputs.first;
198 - // since the fees are taken from Ethereum
199 - // then no need to subtract the fees from the amount if send all
200 - final BigInt allAmount;
201 - if (transactionCurrency is Erc20Token) {
202 - allAmount = erc20Balance.balance;
203 - } else {
204 - allAmount =
205 - erc20Balance.balance - BigInt.from(calculateEstimatedFee(credentials0.priority!, null));
206 - }
207 - final totalOriginalAmount =
208 - EthereumFormatter.parseEthereumAmountToDouble(output.formattedCryptoAmount ?? 0);
209 - totalAmount =
210 - output.sendAll ? allAmount : BigInt.from(totalOriginalAmount * amountToPolygonMultiplier);
211 -
212 - if (erc20Balance.balance < totalAmount) {
213 - throw PolygonTransactionCreationException(transactionCurrency);
214 - }
215 - }
216 -
217 - final pendingPolygonTransaction = await _client.signTransaction(
218 - privateKey: _polygonPrivateKey,
219 - toAddress: credentials0.outputs.first.isParsedAddress
220 - ? credentials0.outputs.first.extractedAddress!
221 - : credentials0.outputs.first.address,
222 - amount: totalAmount.toString(),
223 - gas: _estimatedGas!,
224 - priority: credentials0.priority!,
225 - currency: transactionCurrency,
226 - exponent: exponent,
227 - contractAddress:
228 - transactionCurrency is Erc20Token ? transactionCurrency.contractAddress : null,
59 + Erc20Token createNewErc20TokenObject(Erc20Token token, String? iconPath) {
60 + return Erc20Token(
61 + name: token.name,
62 + symbol: token.symbol,
63 + contractAddress: token.contractAddress,
64 + decimal: token.decimal,
65 + enabled: token.enabled,
66 + tag: token.tag ?? "MATIC",
67 + iconPath: iconPath,
68 );
230 -
231 - return pendingPolygonTransaction;
232 - }
233 -
234 - Future<void> _updateTransactions() async {
235 - try {
236 - if (_isTransactionUpdating) {
237 - return;
238 - }
239 - bool isPolygonScanEnabled = (await _sharedPrefs.future).getBool("use_polygonscan") ?? true;
240 - if (!isPolygonScanEnabled) {
241 - return;
242 - }
243 -
244 - _isTransactionUpdating = true;
245 - final transactions = await fetchTransactions();
246 - transactionHistory.addMany(transactions);
247 - await transactionHistory.save();
248 - _isTransactionUpdating = false;
249 - } catch (_) {
250 - _isTransactionUpdating = false;
251 - }
69 }
70
71 @override
255 - Future<Map<String, PolygonTransactionInfo>> fetchTransactions() async {
256 - final address = _polygonPrivateKey.address.hex;
257 - final transactions = await _client.fetchTransactions(address);
258 -
259 - final List<Future<List<PolygonTransactionModel>>> polygonErc20TokensTransactions = [];
260 -
261 - for (var token in balance.keys) {
262 - if (token is Erc20Token) {
263 - polygonErc20TokensTransactions.add(
264 - _client.fetchTransactions(
265 - address,
266 - contractAddress: token.contractAddress,
267 - ),
268 - );
269 - }
270 - }
271 -
272 - final tokensTransaction = await Future.wait(polygonErc20TokensTransactions);
273 - transactions.addAll(tokensTransaction.expand((element) => element));
274 -
275 - final Map<String, PolygonTransactionInfo> result = {};
276 -
277 - for (var transactionModel in transactions) {
278 - if (transactionModel.isError) {
279 - continue;
280 - }
281 -
282 - result[transactionModel.hash] = PolygonTransactionInfo(
283 - id: transactionModel.hash,
284 - height: transactionModel.blockNumber,
285 - ethAmount: transactionModel.amount,
286 - direction: transactionModel.from == address
287 - ? TransactionDirection.outgoing
288 - : TransactionDirection.incoming,
289 - isPending: false,
290 - date: transactionModel.date,
291 - confirmations: transactionModel.confirmations,
292 - ethFee: BigInt.from(transactionModel.gasUsed) * transactionModel.gasPrice,
293 - exponent: transactionModel.tokenDecimal ?? 18,
294 - tokenSymbol: transactionModel.tokenSymbol ?? "MATIC",
295 - to: transactionModel.to,
296 - );
297 - }
298 -
299 - return result;
300 - }
301 -
302 - @override
303 - Object get keys => throw UnimplementedError("keys");
304 -
305 - @override
306 - Future<void> rescan({required int height}) {
307 - throw UnimplementedError("rescan");
72 + EVMChainTransactionInfo getTransactionInfo(
73 + EVMChainTransactionModel transactionModel, String address) {
74 + final model = PolygonTransactionInfo(
75 + id: transactionModel.hash,
76 + height: transactionModel.blockNumber,
77 + ethAmount: transactionModel.amount,
78 + direction: transactionModel.from == address
79 + ? TransactionDirection.outgoing
80 + : TransactionDirection.incoming,
81 + isPending: false,
82 + date: transactionModel.date,
83 + confirmations: transactionModel.confirmations,
84 + ethFee: BigInt.from(transactionModel.gasUsed) * transactionModel.gasPrice,
85 + exponent: transactionModel.tokenDecimal ?? 18,
86 + tokenSymbol: transactionModel.tokenSymbol ?? "MATIC",
87 + to: transactionModel.to,
88 + from: transactionModel.from,
89 + );
90 + return model;
91 }
92
93 @override
311 - Future<void> save() async {
312 - await walletAddresses.updateAddressesInBox();
313 - final path = await makePath();
314 - await write(path: path, password: _password, data: toJSON());
315 - await transactionHistory.save();
94 + EVMChainTransactionHistory setUpTransactionHistory(WalletInfo walletInfo, String password) {
95 + return PolygonTransactionHistory(walletInfo: walletInfo, password: password);
96 }
97
318 - @override
319 - String? get seed => _mnemonic;
320 -
321 - @override
322 - String get privateKey => HEX.encode(_polygonPrivateKey.privateKey);
323 -
324 - @action
325 - @override
326 - Future<void> startSync() async {
327 - try {
328 - syncStatus = AttemptingSyncStatus();
329 - await _updateBalance();
330 - await _updateTransactions();
331 - _gasPrice = await _client.getGasUnitPrice();
332 - _estimatedGas = await _client.getEstimatedGas();
333 -
334 - Timer.periodic(
335 - const Duration(minutes: 1), (timer) async => _gasPrice = await _client.getGasUnitPrice());
336 - Timer.periodic(const Duration(seconds: 10),
337 - (timer) async => _estimatedGas = await _client.getEstimatedGas());
338 -
339 - syncStatus = SyncedSyncStatus();
340 - } catch (e) {
341 - syncStatus = FailedSyncStatus();
342 - }
343 - }
344 -
345 - Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
346 -
347 - String toJSON() => json.encode({
348 - 'mnemonic': _mnemonic,
349 - 'private_key': privateKey,
350 - 'balance': balance[currency]!.toJSON(),
351 - });
352 -
353 - static Future<PolygonWallet> open({
354 - required String name,
355 - required String password,
356 - required WalletInfo walletInfo,
357 - }) async {
98 + static Future<PolygonWallet> open(
99 + {required String name, required String password, required WalletInfo walletInfo}) async {
100 final path = await pathForWallet(name: name, type: walletInfo.type);
101 final jsonSource = await read(path: path, password: password);
102 final data = json.decode(jsonSource) as Map;
103 final mnemonic = data['mnemonic'] as String?;
104 final privateKey = data['private_key'] as String?;
363 - final balance = ERC20Balance.fromJSON(data['balance'] as String) ?? ERC20Balance(BigInt.zero);
105 + final balance = EVMChainERC20Balance.fromJSON(data['balance'] as String) ??
106 + EVMChainERC20Balance(BigInt.zero);
107
108 return PolygonWallet(
109 walletInfo: walletInfo,
@@ -368,158 +111,7 @@ abstract class PolygonWalletBase
111 mnemonic: mnemonic,
112 privateKey: privateKey,
113 initialBalance: balance,
114 + client: PolygonClient(),
115 );
116 }
373 -
374 - Future<void> _updateBalance() async {
375 - balance[currency] = await _fetchMaticBalance();
376 -
377 - await _fetchErc20Balances();
378 - await save();
379 - }
380 -
381 - Future<ERC20Balance> _fetchMaticBalance() async {
382 - final balance = await _client.getBalance(_polygonPrivateKey.address);
383 - return ERC20Balance(balance.getInWei);
384 - }
385 -
386 - Future<void> _fetchErc20Balances() async {
387 - for (var token in polygonErc20TokensBox.values) {
388 - try {
389 - if (token.enabled) {
390 - balance[token] = await _client.fetchERC20Balances(
391 - _polygonPrivateKey.address,
392 - token.contractAddress,
393 - );
394 - } else {
395 - balance.remove(token);
396 - }
397 - } catch (_) {}
398 - }
399 - }
400 -
401 - Future<EthPrivateKey> getPrivateKey(
402 - {String? mnemonic, String? privateKey, required String password}) async {
403 - assert(mnemonic != null || privateKey != null);
404 -
405 - if (privateKey != null) {
406 - return EthPrivateKey.fromHex(privateKey);
407 - }
408 -
409 - final seed = bip39.mnemonicToSeed(mnemonic!);
410 -
411 - final root = bip32.BIP32.fromSeed(seed);
412 -
413 - const hdPathPolygon = "m/44'/60'/0'/0";
414 - const index = 0;
415 - final addressAtIndex = root.derivePath("$hdPathPolygon/$index");
416 -
417 - return EthPrivateKey.fromHex(HEX.encode(addressAtIndex.privateKey as List<int>));
418 - }
419 -
420 - @override
421 - Future<void>? updateBalance() async => await _updateBalance();
422 -
423 - List<Erc20Token> get erc20Currencies => polygonErc20TokensBox.values.toList();
424 -
425 - Future<void> addErc20Token(Erc20Token token) async {
426 - String? iconPath;
427 - try {
428 - iconPath = CryptoCurrency.all
429 - .firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase())
430 - .iconPath;
431 - } catch (_) {}
432 -
433 - final token0 = Erc20Token(
434 - name: token.name,
435 - symbol: token.symbol,
436 - contractAddress: token.contractAddress,
437 - decimal: token.decimal,
438 - enabled: token.enabled,
439 - tag: token.tag ?? "POLY",
440 - iconPath: iconPath,
441 - );
442 -
443 - await polygonErc20TokensBox.put(token0.contractAddress, token0);
444 -
445 - if (token0.enabled) {
446 - balance[token0] = await _client.fetchERC20Balances(
447 - _polygonPrivateKey.address,
448 - token0.contractAddress,
449 - );
450 - } else {
451 - balance.remove(token0);
452 - }
453 - }
454 -
455 - Future<void> deleteErc20Token(Erc20Token token) async {
456 - await token.delete();
457 -
458 - balance.remove(token);
459 - _updateBalance();
460 - }
461 -
462 - Future<Erc20Token?> getErc20Token(String contractAddress) async =>
463 - await _client.getErc20Token(contractAddress);
464 -
465 - void _onNewTransaction() {
466 - _updateBalance();
467 - _updateTransactions();
468 - }
469 -
470 - void addInitialTokens() {
471 - final initialErc20Tokens = DefaultPolygonErc20Tokens().initialPolygonErc20Tokens;
472 -
473 - for (var token in initialErc20Tokens) {
474 - polygonErc20TokensBox.put(token.contractAddress, token);
475 - }
476 - }
477 -
478 - @override
479 - Future<void> renameWalletFiles(String newWalletName) async {
480 - final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type);
481 - final currentWalletFile = File(currentWalletPath);
482 -
483 - final currentDirPath = await pathForWalletDir(name: walletInfo.name, type: type);
484 - final currentTransactionsFile = File('$currentDirPath/$transactionsHistoryFileName');
485 -
486 - // Copies current wallet files into new wallet name's dir and files
487 - if (currentWalletFile.existsSync()) {
488 - final newWalletPath = await pathForWallet(name: newWalletName, type: type);
489 - await currentWalletFile.copy(newWalletPath);
490 - }
491 - if (currentTransactionsFile.existsSync()) {
492 - final newDirPath = await pathForWalletDir(name: newWalletName, type: type);
493 - await currentTransactionsFile.copy('$newDirPath/$transactionsHistoryFileName');
494 - }
495 -
496 - // Delete old name's dir and files
497 - await Directory(currentDirPath).delete(recursive: true);
498 - }
499 -
500 - void _setTransactionUpdateTimer() {
501 - if (_transactionsUpdateTimer?.isActive ?? false) {
502 - _transactionsUpdateTimer!.cancel();
503 - }
504 -
505 - _transactionsUpdateTimer = Timer.periodic(const Duration(seconds: 10), (_) {
506 - _updateTransactions();
507 - _updateBalance();
508 - });
509 - }
510 -
511 - void updatePolygonScanUsageState(bool isEnabled) {
512 - if (isEnabled) {
513 - _updateTransactions();
514 - _setTransactionUpdateTimer();
515 - } else {
516 - _transactionsUpdateTimer?.cancel();
517 - }
518 - }
519 -
520 - @override
521 - String signMessage(String message, {String? address}) =>
522 - bytesToHex(_polygonPrivateKey.signPersonalMessageToUint8List(ascii.encode(message)));
523 -
524 - Web3Client? getWeb3Client() => _client.getWeb3Client();
117 }
cw_polygon/lib/polygon_wallet_addresses.dart deleted
-5
@@ -1,5 +0,0 @@
1 -import 'package:cw_ethereum/ethereum_wallet_addresses.dart';
2 -
3 -class PolygonWalletAddresses extends EthereumWalletAddresses {
4 - PolygonWalletAddresses(super.walletInfo);
5 -}
cw_polygon/lib/polygon_wallet_creation_credentials.dart deleted
-28
@@ -1,28 +0,0 @@
1 -import 'package:cw_core/wallet_credentials.dart';
2 -import 'package:cw_core/wallet_info.dart';
3 -class PolygonNewWalletCredentials extends WalletCredentials {
4 - PolygonNewWalletCredentials({required String name, WalletInfo? walletInfo})
5 - : super(name: name, walletInfo: walletInfo);
6 -}
7 -
8 -class PolygonRestoreWalletFromSeedCredentials extends WalletCredentials {
9 - PolygonRestoreWalletFromSeedCredentials(
10 - {required String name,
11 - required String password,
12 - required this.mnemonic,
13 - WalletInfo? walletInfo})
14 - : super(name: name, password: password, walletInfo: walletInfo);
15 -
16 - final String mnemonic;
17 -}
18 -
19 -class PolygonRestoreWalletFromPrivateKey extends WalletCredentials {
20 - PolygonRestoreWalletFromPrivateKey(
21 - {required String name,
22 - required String password,
23 - required this.privateKey,
24 - WalletInfo? walletInfo})
25 - : super(name: name, password: password, walletInfo: walletInfo);
26 -
27 - final String privateKey;
28 -}
cw_polygon/lib/polygon_wallet_service.dart
+25 -34
@@ -1,32 +1,34 @@
1 -import 'dart:io';
2 -
3 -import 'package:cw_core/pathForWallet.dart';
1 +import 'package:bip39/bip39.dart' as bip39;
2 import 'package:cw_core/wallet_base.dart';
5 -import 'package:cw_core/wallet_info.dart';
6 -import 'package:cw_core/wallet_service.dart';
3 import 'package:cw_core/wallet_type.dart';
8 -import 'package:cw_ethereum/ethereum_mnemonics.dart';
4 +import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
5 +import 'package:cw_evm/evm_chain_wallet_service.dart';
6 +import 'package:cw_polygon/polygon_client.dart';
7 +import 'package:cw_polygon/polygon_mnemonics_exception.dart';
8 import 'package:cw_polygon/polygon_wallet.dart';
10 -import 'package:bip39/bip39.dart' as bip39;
11 -import 'package:hive/hive.dart';
12 -import 'polygon_wallet_creation_credentials.dart';
13 -import 'package:collection/collection.dart';
9
15 -class PolygonWalletService extends WalletService<PolygonNewWalletCredentials,
16 - PolygonRestoreWalletFromSeedCredentials, PolygonRestoreWalletFromPrivateKey> {
17 - PolygonWalletService(this.walletInfoSource);
10 +class PolygonWalletService extends EVMChainWalletService<PolygonWallet> {
11 + PolygonWalletService(
12 + super.walletInfoSource, {
13 + required this.client,
14 + });
15
19 - final Box<WalletInfo> walletInfoSource;
16 + late PolygonClient client;
17
18 @override
22 - Future<PolygonWallet> create(PolygonNewWalletCredentials credentials) async {
19 + WalletType getType() => WalletType.polygon;
20 +
21 + @override
22 + Future<PolygonWallet> create(EVMChainNewWalletCredentials credentials) async {
23 final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
24
25 final mnemonic = bip39.generateMnemonic(strength: strength);
26 +
27 final wallet = PolygonWallet(
28 walletInfo: credentials.walletInfo!,
29 mnemonic: mnemonic,
30 password: credentials.password!,
31 + client: client,
32 );
33
34 await wallet.init();
@@ -36,18 +38,11 @@ class PolygonWalletService extends WalletService<PolygonNewWalletCredentials,
38 return wallet;
39 }
40
39 - @override
40 - WalletType getType() => WalletType.polygon;
41 -
42 - @override
43 - Future<bool> isWalletExit(String name) async =>
44 - File(await pathForWallet(name: name, type: getType())).existsSync();
45 -
41 @override
42 Future<PolygonWallet> openWallet(String name, String password) async {
43 final walletInfo =
44 walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
50 - final wallet = await PolygonWalletBase.open(
45 + final wallet = await PolygonWallet.open(
46 name: name,
47 password: password,
48 walletInfo: walletInfo,
@@ -60,19 +55,13 @@ class PolygonWalletService extends WalletService<PolygonNewWalletCredentials,
55 }
56
57 @override
63 - Future<void> remove(String wallet) async {
64 - File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true);
65 - final walletInfo = walletInfoSource.values
66 - .firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!;
67 - await walletInfoSource.delete(walletInfo.key);
68 - }
58 + Future<PolygonWallet> restoreFromKeys(EVMChainRestoreWalletFromPrivateKey credentials) async {
59
70 - @override
71 - Future<PolygonWallet> restoreFromKeys(PolygonRestoreWalletFromPrivateKey credentials) async {
60 final wallet = PolygonWallet(
61 password: credentials.password!,
62 privateKey: credentials.privateKey,
63 walletInfo: credentials.walletInfo!,
64 + client: client,
65 );
66
67 await wallet.init();
@@ -83,15 +72,17 @@ class PolygonWalletService extends WalletService<PolygonNewWalletCredentials,
72 }
73
74 @override
86 - Future<PolygonWallet> restoreFromSeed(PolygonRestoreWalletFromSeedCredentials credentials) async {
75 + Future<PolygonWallet> restoreFromSeed(
76 + EVMChainRestoreWalletFromSeedCredentials credentials) async {
77 if (!bip39.validateMnemonic(credentials.mnemonic)) {
88 - throw EthereumMnemonicIsIncorrectException();
78 + throw PolygonMnemonicIsIncorrectException();
79 }
80
81 final wallet = PolygonWallet(
82 password: credentials.password!,
83 mnemonic: credentials.mnemonic,
84 walletInfo: credentials.walletInfo!,
85 + client: client,
86 );
87
88 await wallet.init();
@@ -105,7 +96,7 @@ class PolygonWalletService extends WalletService<PolygonNewWalletCredentials,
96 Future<void> rename(String currentName, String password, String newName) async {
97 final currentWalletInfo = walletInfoSource.values
98 .firstWhere((info) => info.id == WalletBase.idFor(currentName, getType()));
108 - final currentWallet = await PolygonWalletBase.open(
99 + final currentWallet = await PolygonWallet.open(
100 password: password, name: currentName, walletInfo: currentWalletInfo);
101
102 await currentWallet.renameWalletFiles(newName);
cw_polygon/pubspec.yaml
+4 -9
@@ -16,15 +16,12 @@ dependencies:
16 path: ../cw_core
17 cw_ethereum:
18 path: ../cw_ethereum
19 - mobx: ^2.0.7+4
20 - intl: ^0.18.0
21 - bip39: ^1.0.6
19 + cw_evm:
20 + path: ../cw_evm
21 + web3dart: ^2.7.1
22 hive: ^2.2.3
23 + bip39: ^1.0.6
24 collection: ^1.17.1
24 - web3dart: ^2.7.1
25 - bip32: ^2.0.0
26 - hex: ^0.2.0
27 - shared_preferences: ^2.0.15
25
26
27 dev_dependencies:
@@ -32,8 +29,6 @@ dev_dependencies:
29 sdk: flutter
30 flutter_lints: ^2.0.0
31 build_runner: ^2.1.11
35 - mobx_codegen: ^2.0.7
36 - hive_generator: ^1.1.3
32
33 # For information on the generic Dart part of this file, see the
34 # following page: https://dart.dev/tools/pub/pubspec
lib/ethereum/cw_ethereum.dart
+19 -19
@@ -2,17 +2,17 @@ part of 'ethereum.dart';
2
3 class CWEthereum extends Ethereum {
4 @override
5 - List<String> getEthereumWordList(String language) => EthereumMnemonics.englishWordlist;
5 + List<String> getEthereumWordList(String language) => EVMChainMnemonics.englishWordlist;
6
7 WalletService createEthereumWalletService(Box<WalletInfo> walletInfoSource) =>
8 - EthereumWalletService(walletInfoSource);
8 + EthereumWalletService(walletInfoSource, client: EthereumClient());
9
10 @override
11 WalletCredentials createEthereumNewWalletCredentials({
12 required String name,
13 WalletInfo? walletInfo,
14 }) =>
15 - EthereumNewWalletCredentials(name: name, walletInfo: walletInfo);
15 + EVMChainNewWalletCredentials(name: name, walletInfo: walletInfo);
16
17 @override
18 WalletCredentials createEthereumRestoreWalletFromSeedCredentials({
@@ -20,7 +20,7 @@ class CWEthereum extends Ethereum {
20 required String mnemonic,
21 required String password,
22 }) =>
23 - EthereumRestoreWalletFromSeedCredentials(name: name, password: password, mnemonic: mnemonic);
23 + EVMChainRestoreWalletFromSeedCredentials(name: name, password: password, mnemonic: mnemonic);
24
25 @override
26 WalletCredentials createEthereumRestoreWalletFromPrivateKey({
@@ -28,37 +28,37 @@ class CWEthereum extends Ethereum {
28 required String privateKey,
29 required String password,
30 }) =>
31 - EthereumRestoreWalletFromPrivateKey(name: name, password: password, privateKey: privateKey);
31 + EVMChainRestoreWalletFromPrivateKey(name: name, password: password, privateKey: privateKey);
32
33 @override
34 String getAddress(WalletBase wallet) => (wallet as EthereumWallet).walletAddresses.address;
35
36 @override
37 String getPrivateKey(WalletBase wallet) {
38 - final privateKeyHolder = (wallet as EthereumWallet).ethPrivateKey;
38 + final privateKeyHolder = (wallet as EthereumWallet).evmChainPrivateKey;
39 String stringKey = bytesToHex(privateKeyHolder.privateKey);
40 return stringKey;
41 }
42
43 @override
44 String getPublicKey(WalletBase wallet) {
45 - final privateKeyInUnitInt = (wallet as EthereumWallet).ethPrivateKey;
45 + final privateKeyInUnitInt = (wallet as EthereumWallet).evmChainPrivateKey;
46 final publicKey = privateKeyInUnitInt.address.hex;
47 return publicKey;
48 }
49
50 @override
51 - TransactionPriority getDefaultTransactionPriority() => EthereumTransactionPriority.medium;
51 + TransactionPriority getDefaultTransactionPriority() => EVMChainTransactionPriority.medium;
52
53 @override
54 - TransactionPriority getEthereumTransactionPrioritySlow() => EthereumTransactionPriority.slow;
54 + TransactionPriority getEthereumTransactionPrioritySlow() => EVMChainTransactionPriority.slow;
55
56 @override
57 - List<TransactionPriority> getTransactionPriorities() => EthereumTransactionPriority.all;
57 + List<TransactionPriority> getTransactionPriorities() => EVMChainTransactionPriority.all;
58
59 @override
60 TransactionPriority deserializeEthereumTransactionPriority(int raw) =>
61 - EthereumTransactionPriority.deserialize(raw: raw);
61 + EVMChainTransactionPriority.deserialize(raw: raw);
62
63 Object createEthereumTransactionCredentials(
64 List<Output> outputs, {
@@ -66,7 +66,7 @@ class CWEthereum extends Ethereum {
66 required CryptoCurrency currency,
67 int? feeRate,
68 }) =>
69 - EthereumTransactionCredentials(
69 + EVMChainTransactionCredentials(
70 outputs
71 .map((out) => OutputInfo(
72 fiatAmount: out.fiatAmount,
@@ -78,7 +78,7 @@ class CWEthereum extends Ethereum {
78 isParsedAddress: out.isParsedAddress,
79 formattedCryptoAmount: out.formattedCryptoAmount))
80 .toList(),
81 - priority: priority as EthereumTransactionPriority,
81 + priority: priority as EVMChainTransactionPriority,
82 currency: currency,
83 feeRate: feeRate,
84 );
@@ -89,15 +89,15 @@ class CWEthereum extends Ethereum {
89 required CryptoCurrency currency,
90 required int feeRate,
91 }) =>
92 - EthereumTransactionCredentials(
92 + EVMChainTransactionCredentials(
93 outputs,
94 - priority: priority as EthereumTransactionPriority?,
94 + priority: priority as EVMChainTransactionPriority?,
95 currency: currency,
96 feeRate: feeRate,
97 );
98
99 @override
100 - int formatterEthereumParseAmount(String amount) => EthereumFormatter.parseEthereumAmount(amount);
100 + int formatterEthereumParseAmount(String amount) => EVMChainFormatter.parseEVMChainAmount(amount);
101
102 @override
103 double formatterEthereumAmountToDouble(
@@ -105,7 +105,7 @@ class CWEthereum extends Ethereum {
105 assert(transaction != null || amount != null);
106
107 if (transaction != null) {
108 - transaction as EthereumTransactionInfo;
108 + transaction as EVMChainTransactionInfo;
109 return transaction.ethAmount / BigInt.from(10).pow(transaction.exponent);
110 } else {
111 return (amount!) / BigInt.from(10).pow(exponent);
@@ -134,7 +134,7 @@ class CWEthereum extends Ethereum {
134
135 @override
136 CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) {
137 - transaction as EthereumTransactionInfo;
137 + transaction as EVMChainTransactionInfo;
138 if (transaction.tokenSymbol == CryptoCurrency.eth.title) {
139 return CryptoCurrency.eth;
140 }
@@ -146,7 +146,7 @@ class CWEthereum extends Ethereum {
146
147 @override
148 void updateEtherscanUsageState(WalletBase wallet, bool isEnabled) {
149 - (wallet as EthereumWallet).updateEtherscanUsageState(isEnabled);
149 + (wallet as EthereumWallet).updateScanProviderUsageState(isEnabled);
150 }
151
152 @override
lib/polygon/cw_polygon.dart
+19 -19
@@ -2,17 +2,17 @@ part of 'polygon.dart';
2
3 class CWPolygon extends Polygon {
4 @override
5 - List<String> getPolygonWordList(String language) => EthereumMnemonics.englishWordlist;
5 + List<String> getPolygonWordList(String language) => EVMChainMnemonics.englishWordlist;
6
7 WalletService createPolygonWalletService(Box<WalletInfo> walletInfoSource) =>
8 - PolygonWalletService(walletInfoSource);
8 + PolygonWalletService(walletInfoSource, client: PolygonClient());
9
10 @override
11 WalletCredentials createPolygonNewWalletCredentials({
12 required String name,
13 WalletInfo? walletInfo,
14 }) =>
15 - PolygonNewWalletCredentials(name: name, walletInfo: walletInfo);
15 + EVMChainNewWalletCredentials(name: name, walletInfo: walletInfo);
16
17 @override
18 WalletCredentials createPolygonRestoreWalletFromSeedCredentials({
@@ -20,7 +20,7 @@ class CWPolygon extends Polygon {
20 required String mnemonic,
21 required String password,
22 }) =>
23 - PolygonRestoreWalletFromSeedCredentials(name: name, password: password, mnemonic: mnemonic);
23 + EVMChainRestoreWalletFromSeedCredentials(name: name, password: password, mnemonic: mnemonic);
24
25 @override
26 WalletCredentials createPolygonRestoreWalletFromPrivateKey({
@@ -28,37 +28,37 @@ class CWPolygon extends Polygon {
28 required String privateKey,
29 required String password,
30 }) =>
31 - PolygonRestoreWalletFromPrivateKey(name: name, password: password, privateKey: privateKey);
31 + EVMChainRestoreWalletFromPrivateKey(name: name, password: password, privateKey: privateKey);
32
33 @override
34 String getAddress(WalletBase wallet) => (wallet as PolygonWallet).walletAddresses.address;
35
36 @override
37 String getPrivateKey(WalletBase wallet) {
38 - final privateKeyHolder = (wallet as PolygonWallet).polygonPrivateKey;
38 + final privateKeyHolder = (wallet as PolygonWallet).evmChainPrivateKey;
39 String stringKey = bytesToHex(privateKeyHolder.privateKey);
40 return stringKey;
41 }
42
43 @override
44 String getPublicKey(WalletBase wallet) {
45 - final privateKeyInUnitInt = (wallet as PolygonWallet).polygonPrivateKey;
45 + final privateKeyInUnitInt = (wallet as PolygonWallet).evmChainPrivateKey;
46 final publicKey = privateKeyInUnitInt.address.hex;
47 return publicKey;
48 }
49
50 @override
51 - TransactionPriority getDefaultTransactionPriority() => PolygonTransactionPriority.medium;
51 + TransactionPriority getDefaultTransactionPriority() => EVMChainTransactionPriority.medium;
52
53 @override
54 - TransactionPriority getPolygonTransactionPrioritySlow() => PolygonTransactionPriority.slow;
54 + TransactionPriority getPolygonTransactionPrioritySlow() => EVMChainTransactionPriority.slow;
55
56 @override
57 - List<TransactionPriority> getTransactionPriorities() => PolygonTransactionPriority.all;
57 + List<TransactionPriority> getTransactionPriorities() => EVMChainTransactionPriority.all;
58
59 @override
60 TransactionPriority deserializePolygonTransactionPriority(int raw) =>
61 - PolygonTransactionPriority.deserialize(raw: raw);
61 + EVMChainTransactionPriority.deserialize(raw: raw);
62
63 Object createPolygonTransactionCredentials(
64 List<Output> outputs, {
@@ -66,7 +66,7 @@ class CWPolygon extends Polygon {
66 required CryptoCurrency currency,
67 int? feeRate,
68 }) =>
69 - PolygonTransactionCredentials(
69 + EVMChainTransactionCredentials(
70 outputs
71 .map((out) => OutputInfo(
72 fiatAmount: out.fiatAmount,
@@ -78,7 +78,7 @@ class CWPolygon extends Polygon {
78 isParsedAddress: out.isParsedAddress,
79 formattedCryptoAmount: out.formattedCryptoAmount))
80 .toList(),
81 - priority: priority as PolygonTransactionPriority,
81 + priority: priority as EVMChainTransactionPriority,
82 currency: currency,
83 feeRate: feeRate,
84 );
@@ -89,15 +89,15 @@ class CWPolygon extends Polygon {
89 required CryptoCurrency currency,
90 required int feeRate,
91 }) =>
92 - PolygonTransactionCredentials(
92 + EVMChainTransactionCredentials(
93 outputs,
94 - priority: priority as PolygonTransactionPriority?,
94 + priority: priority as EVMChainTransactionPriority?,
95 currency: currency,
96 feeRate: feeRate,
97 );
98
99 @override
100 - int formatterPolygonParseAmount(String amount) => PolygonFormatter.parsePolygonAmount(amount);
100 + int formatterPolygonParseAmount(String amount) => EVMChainFormatter.parseEVMChainAmount(amount);
101
102 @override
103 double formatterPolygonAmountToDouble(
@@ -105,7 +105,7 @@ class CWPolygon extends Polygon {
105 assert(transaction != null || amount != null);
106
107 if (transaction != null) {
108 - transaction as PolygonTransactionInfo;
108 + transaction as EVMChainTransactionInfo;
109 return transaction.ethAmount / BigInt.from(10).pow(transaction.exponent);
110 } else {
111 return (amount!) / BigInt.from(10).pow(exponent);
@@ -134,7 +134,7 @@ class CWPolygon extends Polygon {
134
135 @override
136 CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) {
137 - transaction as PolygonTransactionInfo;
137 + transaction as EVMChainTransactionInfo;
138 if (transaction.tokenSymbol == CryptoCurrency.maticpoly.title) {
139 return CryptoCurrency.maticpoly;
140 }
@@ -146,7 +146,7 @@ class CWPolygon extends Polygon {
146
147 @override
148 void updatePolygonScanUsageState(WalletBase wallet, bool isEnabled) {
149 - (wallet as PolygonWallet).updatePolygonScanUsageState(isEnabled);
149 + (wallet as PolygonWallet).updateScanProviderUsageState(isEnabled);
150 }
151
152 @override
lib/src/screens/dashboard/pages/nft_details_page.dart
+7 -7
@@ -101,26 +101,26 @@ class NFTDetailsPage extends BasePage {
101 SizedBox(height: 16),
102 _NFTSingleInfoTile(
103 infoType: S.current.name,
104 - infoValue: nftAsset.normalizedMetadata?.name ?? '',
104 + infoValue: nftAsset.normalizedMetadata?.name ?? '---',
105 ),
106
107 if (nftAsset.normalizedMetadata?.description != null) ...[
108 SizedBox(height: 16),
109 _NFTSingleInfoTile(
110 - infoType: 'Description',
111 - infoValue: nftAsset.normalizedMetadata?.description ?? '',
110 + infoType: S.current.description,
111 + infoValue: nftAsset.normalizedMetadata?.description ?? '---',
112 ),
113 ],
114
115 SizedBox(height: 16),
116 _NFTSingleInfoTile(
117 - infoType: 'Contract Name',
118 - infoValue: nftAsset.name ?? '',
117 + infoType: S.current.contractName,
118 + infoValue: nftAsset.name ?? '---',
119 ),
120 SizedBox(height: 8),
121 _NFTSingleInfoTile(
122 - infoType: 'Contract Symbol',
123 - infoValue: nftAsset.symbol ?? '',
122 + infoType: S.current.contractSymbol,
123 + infoValue: nftAsset.symbol ?? '---',
124 ),
125 ],
126 ),
lib/src/screens/dashboard/widgets/nft_tile_widget.dart
+9 -25
@@ -13,22 +13,15 @@ class NFTTileWidget extends StatelessWidget {
13 @override
14 Widget build(BuildContext context) {
15 return InkWell(
16 - onTap: () => Navigator.pushNamed(context, Routes.nftDetailsPage,
17 - arguments: nftAsset),
16 + onTap: () => Navigator.pushNamed(context, Routes.nftDetailsPage, arguments: nftAsset),
17 child: Container(
18 width: double.infinity,
19 margin: const EdgeInsets.only(left: 16, right: 16),
20 decoration: BoxDecoration(
21 borderRadius: BorderRadius.circular(30.0),
22 border: Border.all(
24 - color: Theme.of(context)
25 - .extension<BalancePageTheme>()!
26 - .cardBorderColor,
27 - width: 1,
28 - ),
29 - color: Theme.of(context)
30 - .extension<SyncIndicatorTheme>()!
31 - .syncedBackgroundColor,
23 + color: Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor, width: 1),
24 + color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
25 ),
26 child: Row(
27 children: [
@@ -40,14 +33,10 @@ class NFTTileWidget extends StatelessWidget {
33 decoration: BoxDecoration(
34 borderRadius: BorderRadius.circular(16.0),
35 border: Border.all(
43 - color: Theme.of(context)
44 - .extension<BalancePageTheme>()!
45 - .cardBorderColor,
36 + color: Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor,
37 width: 1,
38 ),
48 - color: Theme.of(context)
49 - .extension<SyncIndicatorTheme>()!
50 - .syncedBackgroundColor,
39 + color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
40 ),
41 child: NFTImageWidget(
42 imageUrl: nftAsset.normalizedMetadata?.imageUrl,
@@ -59,27 +48,23 @@ class NFTTileWidget extends StatelessWidget {
48 crossAxisAlignment: CrossAxisAlignment.start,
49 children: [
50 Text(
62 - '${nftAsset.name ?? ''} - ${nftAsset.symbol ?? ''}',
51 + '${nftAsset.name ?? '---'} - ${nftAsset.symbol ?? '---'}',
52 style: TextStyle(
53 fontSize: 12,
54 fontFamily: 'Lato',
55 fontWeight: FontWeight.w400,
67 - color: Theme.of(context)
68 - .extension<BalancePageTheme>()!
69 - .labelTextColor,
56 + color: Theme.of(context).extension<BalancePageTheme>()!.labelTextColor,
57 height: 1,
58 ),
59 ),
60 SizedBox(height: 8),
61 Text(
75 - nftAsset.normalizedMetadata?.name ?? nftAsset.name ?? "",
62 + nftAsset.normalizedMetadata?.name ?? nftAsset.name ?? "---",
63 style: TextStyle(
64 fontSize: 20,
65 fontFamily: 'Lato',
66 fontWeight: FontWeight.w900,
80 - color: Theme.of(context)
81 - .extension<BalancePageTheme>()!
82 - .assetTitleColor,
67 + color: Theme.of(context).extension<BalancePageTheme>()!.assetTitleColor,
68 height: 1,
69 ),
70 ),
@@ -92,4 +77,3 @@ class NFTTileWidget extends StatelessWidget {
77 );
78 }
79 }
95 -
lib/view_model/transaction_details_view_model.dart
+5 -1
@@ -239,6 +239,8 @@ abstract class TransactionDetailsViewModelBase with Store {
239 StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!),
240 if (showRecipientAddress && tx.to != null)
241 StandartListItem(title: S.current.transaction_details_recipient_address, value: tx.to!),
242 + if (tx.direction == TransactionDirection.incoming && tx.from != null)
243 + StandartListItem(title: S.current.transaction_details_source_address, value: tx.from!),
244 ];
245
246 items.addAll(_items);
@@ -271,8 +273,10 @@ abstract class TransactionDetailsViewModelBase with Store {
273 StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
274 if (tx.feeFormatted()?.isNotEmpty ?? false)
275 StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!),
274 - if (showRecipientAddress && tx.to != null)
276 + if (showRecipientAddress && tx.to != null && tx.direction == TransactionDirection.outgoing)
277 StandartListItem(title: S.current.transaction_details_recipient_address, value: tx.to!),
278 + if (tx.direction == TransactionDirection.incoming && tx.from != null)
279 + StandartListItem(title: S.current.transaction_details_source_address, value: tx.from!),
280 ];
281
282 items.addAll(_items);
model_generator.sh
+1 -2
@@ -1,9 +1,8 @@
1 cd cw_core && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
2 +cd cw_evm && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
3 cd cw_monero && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
4 cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
5 cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
5 -cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
6 cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
7 cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
8 -cd cw_polygon && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
8 flutter packages pub run build_runner build --delete-conflicting-outputs
\ No newline at end of file
res/values/strings_ar.arb
+5 -2
@@ -764,9 +764,12 @@
764 "confirmed_tx": "مؤكد",
765 "transaction_details_source_address": "عنوان المصدر",
766 "pause_wallet_creation": ".ﺎﻴًﻟﺎﺣ ﺎﺘًﻗﺆﻣ ﺔﻔﻗﻮﺘﻣ Haven Wallet ءﺎﺸﻧﺇ ﻰﻠﻋ ﺓﺭﺪﻘﻟﺍ",
767 + "contractName": "ﺪﻘﻌﻟﺍ ﻢﺳﺍ",
768 + "contractSymbol": "ﺪﻘﻌﻟﺍ ﺰﻣﺭ",
769 + "description": "ﻒﺻﻭ",
770 "camera_consent": ".ﻞﻴﺻﺎﻔﺘﻟﺍ ﻰﻠﻋ ﻝﻮﺼﺤﻠﻟ ﻢﻬﺑ ﺔﺻﺎﺨﻟﺍ ﺔﻴﺻﻮﺼﺨﻟﺍ ﺔﺳﺎﻴﺳ ﻦﻣ ﻖﻘﺤﺘﻟﺍ ﻰﺟﺮﻳ .${provider} ﻝﻮﻠ",
768 - "no_relays": " ﺕﻼﺣﺮﻤﻟﺍ ﻻ",
769 - "choose_relay": " ﻡﺍﺪﺨﺘﺳﻼﻟ ﻊﺑﺎﺘﺘﻟﺍ ﺭﺎﻴﺘﺧﺍ ءﺎﺟﺮﻟﺍ",
771 + "no_relays": "ﺕﻼﺣﺮﻤﻟﺍ ﻻ",
772 + "choose_relay": "ﻡﺍﺪﺨﺘﺳﻼﻟ ﻊﺑﺎﺘﺘﻟﺍ ﺭﺎﻴﺘﺧﺍ ءﺎﺟﺮﻟﺍ",
773 "no_relays_message": ".ﻪﺑ ﺹﺎﺨﻟﺍ Nostr ﻞﺠﺳ ﻰﻟﺇ ﺕﻼﺣﺮﻤﻟﺍ ﺔﻓﺎﺿﻹ ﻢﻠﺘﺴﻤﻟﺍ ﺩﺎﺷﺭﺇ ﻰﺟﺮﻳ .ﺕﻼﺣﺮﻣ ﻱﺃ ﻰﻠﻋ ﻱﻮﺘﺤﻳ ﻻ",
774 "no_relay_on_domain": ".ﻡﺍﺪﺨﺘﺳﻼﻟ ﻊﺑﺎﺘﺘﻟﺍ ﺭﺎﻴﺘﺧﺍ ءﺎﺟﺮﻟﺍ .ﺡﺎﺘﻣ ﺮﻴﻏ ﻞﻴﺣﺮﺘﻟﺍ ﻥﺃ ﻭﺃ ﻡﺪﺨﺘﺴﻤﻟﺍ ﻝﺎﺠﻤﻟ ﻞﻴﺣﺮﺗ ﺪ"
775 }
res/values/strings_bg.arb
+3
@@ -760,6 +760,9 @@
760 "confirmed_tx": "Потвърдено",
761 "transaction_details_source_address": "Адрес на източника",
762 "pause_wallet_creation": "Възможността за създаване на Haven Wallet в момента е на пауза.",
763 + "contractName": "Име на договора",
764 + "contractSymbol": "Договор Символ",
765 + "description": "Описание",
766 "camera_consent": "Вашият фотоапарат ще бъде използван за заснемане на изображение с цел идентификация от ${provider}. Моля, проверете тяхната политика за поверителност за подробности.",
767 "no_relays": "Без релета",
768 "choose_relay": "Моля, изберете реле, което да използвате",
res/values/strings_cs.arb
+3
@@ -760,6 +760,9 @@
760 "confirmed_tx": "Potvrzeno",
761 "transaction_details_source_address": "Zdrojová adresa",
762 "pause_wallet_creation": "Možnost vytvářet Haven Wallet je momentálně pozastavena.",
763 + "contractName": "Název smlouvy",
764 + "contractSymbol": "Symbol smlouvy",
765 + "description": "Popis",
766 "camera_consent": "Váš fotoaparát použije k pořízení snímku pro účely identifikace ${provider}. Podrobnosti najdete v jejich Zásadách ochrany osobních údajů.",
767 "no_relays": "Žádná relé",
768 "choose_relay": "Vyberte relé, které chcete použít",
res/values/strings_de.arb
+3
@@ -768,6 +768,9 @@
768 "confirmed_tx": "Bestätigt",
769 "transaction_details_source_address": "Quelladresse",
770 "pause_wallet_creation": "Die Möglichkeit, Haven Wallet zu erstellen, ist derzeit pausiert.",
771 + "contractName": "Vertragsname",
772 + "contractSymbol": "Vertragssymbol",
773 + "description": "Beschreibung",
774 "camera_consent": "Mit Ihrer Kamera wird bis zum ${provider} ein Bild zur Identifizierung aufgenommen. Weitere Informationen finden Sie in deren Datenschutzbestimmungen.",
775 "no_relays": "Keine Relais",
776 "choose_relay": "Bitte wählen Sie ein zu verwendendes Relais aus",
res/values/strings_en.arb
+3
@@ -769,6 +769,9 @@
769 "confirmed_tx": "Confirmed",
770 "transaction_details_source_address": "Source address",
771 "pause_wallet_creation": "Ability to create Haven Wallet is currently paused.",
772 + "contractName": "Contract Name",
773 + "contractSymbol": "Contract Symbol",
774 + "description": "Description",
775 "camera_consent": "Your camera will be used to capture an image for identification purposes by ${provider}. Please check their Privacy Policy for details.",
776 "no_relays": "No relays",
777 "choose_relay": "Please choose a relay to use",
res/values/strings_es.arb
+3
@@ -768,6 +768,9 @@
768 "confirmed_tx": "Confirmado",
769 "transaction_details_source_address": "Dirección de la fuente",
770 "pause_wallet_creation": "La capacidad para crear Haven Wallet está actualmente pausada.",
771 + "contractName": "Nombre del contrato",
772 + "contractSymbol": "Símbolo de contrato",
773 + "description": "Descripción",
774 "camera_consent": "Su cámara será utilizada para capturar una imagen con fines de identificación por ${provider}. Consulte su Política de privacidad para obtener más detalles.",
775 "no_relays": "Sin relevos",
776 "choose_relay": "Por favor elija un relé para usar",
res/values/strings_fr.arb
+3
@@ -768,6 +768,9 @@
768 "confirmed_tx": "Confirmé",
769 "transaction_details_source_address": "Adresse source",
770 "pause_wallet_creation": "La possibilité de créer Haven Wallet est actuellement suspendue.",
771 + "contractName": "Nom du contrat",
772 + "contractSymbol": "Symbole du contrat",
773 + "description": "Description",
774 "camera_consent": "Votre appareil photo sera utilisé pour capturer une image à des fins d'identification par ${provider}. Veuillez consulter leur politique de confidentialité pour plus de détails.",
775 "no_relays": "Pas de relais",
776 "choose_relay": "Veuillez choisir un relais à utiliser",
res/values/strings_ha.arb
+3
@@ -750,6 +750,9 @@
750 "confirmed_tx": "Tabbatar",
751 "transaction_details_source_address": "Adireshin Incord",
752 "pause_wallet_creation": "A halin yanzu an dakatar da ikon ƙirƙirar Haven Wallet.",
753 + "contractName": "Sunan Kwangila",
754 + "contractSymbol": "Alamar Kwangila",
755 + "description": "Bayani",
756 "camera_consent": "Za a yi amfani da kyamarar ku don ɗaukar hoto don dalilai na tantancewa ta ${provider}. Da fatan za a duba Manufar Sirri don cikakkun bayanai.",
757 "no_relays": "Babu relays",
758 "choose_relay": "Da fatan za a zaɓi gudun ba da sanda don amfani",
res/values/strings_hi.arb
+3
@@ -768,6 +768,9 @@
768 "confirmed_tx": "की पुष्टि",
769 "transaction_details_source_address": "स्रोत पता",
770 "pause_wallet_creation": "हेवन वॉलेट बनाने की क्षमता फिलहाल रुकी हुई है।",
771 + "contractName": "अनुबंध का नाम",
772 + "contractSymbol": "अनुबंध चिह्न",
773 + "description": "विवरण",
774 "camera_consent": "आपके कैमरे का उपयोग ${provider} द्वारा पहचान उद्देश्यों के लिए एक छवि कैप्चर करने के लिए किया जाएगा। विवरण के लिए कृपया उनकी गोपनीयता नीति जांचें।",
775 "no_relays": "कोई रिले नहीं",
776 "choose_relay": "कृपया उपयोग करने के लिए एक रिले चुनें",
res/values/strings_hr.arb
+3
@@ -766,6 +766,9 @@
766 "confirmed_tx": "Potvrđen",
767 "transaction_details_source_address": "Adresa izvora",
768 "pause_wallet_creation": "Mogućnost stvaranja novčanika Haven trenutno je pauzirana.",
769 + "contractName": "Naziv ugovora",
770 + "contractSymbol": "Simbol ugovora",
771 + "description": "Opis",
772 "camera_consent": "Vaš će fotoaparat koristiti za snimanje slike u svrhu identifikacije od strane ${provider}. Pojedinosti potražite u njihovoj politici privatnosti.",
773 "no_relays": "Nema releja",
774 "choose_relay": "Odaberite relej za korištenje",
res/values/strings_id.arb
+3
@@ -756,6 +756,9 @@
756 "confirmed_tx": "Dikonfirmasi",
757 "transaction_details_source_address": "Alamat sumber",
758 "pause_wallet_creation": "Kemampuan untuk membuat Haven Wallet saat ini dijeda.",
759 + "contractName": "Nama Kontrak",
760 + "contractSymbol": "Simbol Kontrak",
761 + "description": "Keterangan",
762 "camera_consent": "Kamera Anda akan digunakan untuk mengambil gambar untuk tujuan identifikasi oleh ${provider}. Silakan periksa Kebijakan Privasi mereka untuk detailnya.",
763 "no_relays": "Tidak ada relay",
764 "choose_relay": "Silakan pilih relai yang akan digunakan",
res/values/strings_it.arb
+3
@@ -768,6 +768,9 @@
768 "confirmed_tx": "Confermato",
769 "transaction_details_source_address": "Indirizzo di partenza",
770 "pause_wallet_creation": "La possibilità di creare Haven Wallet è attualmente sospesa.",
771 + "contractName": "Nome del contratto",
772 + "contractSymbol": "Simbolo del contratto",
773 + "description": "Descrizione",
774 "camera_consent": "La tua fotocamera verrà utilizzata per acquisire un'immagine a scopo identificativo da ${provider}. Si prega di controllare la loro Informativa sulla privacy per i dettagli.",
775 "no_relays": "Nessun relè",
776 "choose_relay": "Scegli un relè da utilizzare",
res/values/strings_ja.arb
+3
@@ -768,6 +768,9 @@
768 "confirmed_tx": "確認済み",
769 "transaction_details_source_address": "ソースアドレス",
770 "pause_wallet_creation": "Haven Wallet を作成する機能は現在一時停止されています。",
771 + "contractName": "契約名",
772 + "contractSymbol": "契約記号",
773 + "description": "説明",
774 "camera_consent": "あなたのカメラは、${provider}_ までに識別目的で画像を撮影するために使用されます。詳細については、プライバシー ポリシーをご確認ください。",
775 "no_relays": "リレーなし",
776 "choose_relay": "使用するリレーを選択してください",
res/values/strings_ko.arb
+3
@@ -766,6 +766,9 @@
766 "confirmed_tx": "확인",
767 "transaction_details_source_address": "소스 주소",
768 "pause_wallet_creation": "Haven Wallet 생성 기능이 현재 일시 중지되었습니다.",
769 + "contractName": "계약명",
770 + "contractSymbol": "계약 기호",
771 + "description": "설명",
772 "camera_consent": "귀하의 카메라는 ${provider}의 식별 목적으로 이미지를 캡처하는 데 사용됩니다. 자세한 내용은 해당 개인정보 보호정책을 확인하세요.",
773 "no_relays": "릴레이 없음",
774 "choose_relay": "사용할 릴레이를 선택해주세요",
res/values/strings_my.arb
+3
@@ -766,6 +766,9 @@
766 "confirmed_tx": "အတည်ပြုသည်",
767 "transaction_details_source_address": "အရင်းအမြစ်လိပ်စာ",
768 "pause_wallet_creation": "Haven Wallet ဖန်တီးနိုင်မှုကို လောလောဆယ် ခေတ္တရပ်ထားသည်။",
769 + "contractName": "စာချုပ်အမည်",
770 + "contractSymbol": "စာချုပ်သင်္ကေတ",
771 + "description": "ဖော်ပြချက်",
772 "camera_consent": "မှတ်ပုံတင်ခြင်းရည်ရွယ်ချက်များအတွက် ${provider} တွင် သင့်ကင်မရာကို အသုံးပြုပါမည်။ အသေးစိတ်အတွက် ၎င်းတို့၏ ကိုယ်ရေးကိုယ်တာမူဝါဒကို စစ်ဆေးပါ။",
773 "no_relays": "Relay မရှိပါ။",
774 "choose_relay": "အသုံးပြုရန် relay ကိုရွေးချယ်ပါ။",
res/values/strings_nl.arb
+3
@@ -768,6 +768,9 @@
768 "confirmed_tx": "Bevestigd",
769 "transaction_details_source_address": "Bron adres",
770 "pause_wallet_creation": "De mogelijkheid om Haven Wallet te maken is momenteel onderbroken.",
771 + "contractName": "Contractnaam",
772 + "contractSymbol": "Contractsymbool",
773 + "description": "Beschrijving",
774 "camera_consent": "Uw camera wordt gebruikt om vóór ${provider} een beeld vast te leggen voor identificatiedoeleinden. Raadpleeg hun privacybeleid voor meer informatie.",
775 "no_relays": "Geen relais",
776 "choose_relay": "Kies een relais dat u wilt gebruiken",
res/values/strings_pl.arb
+3
@@ -768,6 +768,9 @@
768 "confirmed_tx": "Potwierdzony",
769 "transaction_details_source_address": "Adres źródłowy",
770 "pause_wallet_creation": "Możliwość utworzenia Portfela Haven jest obecnie wstrzymana.",
771 + "contractName": "Nazwa umowy",
772 + "contractSymbol": "Symbol kontraktu",
773 + "description": "Opis",
774 "camera_consent": "Twój aparat zostanie użyty do przechwycenia obrazu w celach identyfikacyjnych przez ${provider}. Aby uzyskać szczegółowe informacje, sprawdź ich Politykę prywatności.",
775 "no_relays": "Żadnych przekaźników",
776 "choose_relay": "Wybierz przekaźnik, którego chcesz użyć",
res/values/strings_pt.arb
+3
@@ -767,6 +767,9 @@
767 "confirmed_tx": "Confirmado",
768 "transaction_details_source_address": "Endereço de Origem",
769 "pause_wallet_creation": "A capacidade de criar a Haven Wallet está atualmente pausada.",
770 + "contractName": "Nome do contrato",
771 + "contractSymbol": "Símbolo do Contrato",
772 + "description": "Descrição",
773 "camera_consent": "Sua câmera será usada para capturar uma imagem para fins de identificação por ${provider}. Por favor, verifique a Política de Privacidade para obter detalhes.",
774 "no_relays": "Sem relés",
775 "choose_relay": "Escolha um relé para usar",
res/values/strings_ru.arb
+3
@@ -768,6 +768,9 @@
768 "confirmed_tx": "Подтвержденный",
769 "transaction_details_source_address": "Адрес источника",
770 "pause_wallet_creation": "Возможность создания Haven Wallet в настоящее время приостановлена.",
771 + "contractName": "Название контракта",
772 + "contractSymbol": "Символ контракта",
773 + "description": "Описание",
774 "camera_consent": "Ваша камера будет использоваться для захвата изображения в целях идентификации ${provider}. Пожалуйста, ознакомьтесь с их Политикой конфиденциальности для получения подробной информации.",
775 "no_relays": "Нет реле",
776 "choose_relay": "Пожалуйста, выберите реле для использования",
res/values/strings_th.arb
+3
@@ -766,6 +766,9 @@
766 "confirmed_tx": "ซึ่งยืนยันแล้ว",
767 "transaction_details_source_address": "ที่อยู่แหล่งกำเนิด",
768 "pause_wallet_creation": "ขณะนี้ความสามารถในการสร้าง Haven Wallet ถูกหยุดชั่วคราว",
769 + "contractName": "ชื่อสัญญา",
770 + "contractSymbol": "สัญลักษณ์สัญญา",
771 + "description": "คำอธิบาย",
772 "camera_consent": "กล้องของคุณจะถูกนำมาใช้เพื่อจับภาพเพื่อวัตถุประสงค์ในการระบุตัวตนภายใน ${provider} โปรดตรวจสอบนโยบายความเป็นส่วนตัวเพื่อดูรายละเอียด",
773 "no_relays": "ไม่มีรีเลย์",
774 "choose_relay": "กรุณาเลือกรีเลย์ที่จะใช้",
res/values/strings_tl.arb
+3
@@ -762,6 +762,9 @@
762 "confirmed_tx": "Nakumpirma",
763 "transaction_details_source_address": "SOURCE ADDRESS",
764 "pause_wallet_creation": "Kasalukuyang naka-pause ang kakayahang gumawa ng Haven Wallet.",
765 + "contractName": "Pangalan ng Kontrata",
766 + "contractSymbol": "Simbolo ng Kontrata",
767 + "description": "Paglalarawan",
768 "camera_consent": "Gagamitin ang iyong camera upang kumuha ng larawan para sa mga layunin ng pagkakakilanlan sa pamamagitan ng ${provider}. Pakisuri ang kanilang Patakaran sa Privacy para sa mga detalye.",
769 "no_relays": "Walang mga relay",
770 "choose_relay": "Mangyaring pumili ng relay na gagamitin",
res/values/strings_tr.arb
+3
@@ -766,6 +766,9 @@
766 "confirmed_tx": "Onaylanmış",
767 "transaction_details_source_address": "Kaynak adresi",
768 "pause_wallet_creation": "Haven Cüzdanı oluşturma yeteneği şu anda duraklatıldı.",
769 + "contractName": "Sözleşme Adı",
770 + "contractSymbol": "Sözleşme Sembolü",
771 + "description": "Tanım",
772 "camera_consent": "Kameranız ${provider} tarihine kadar tanımlama amacıyla bir görüntü yakalamak için kullanılacaktır. Ayrıntılar için lütfen Gizlilik Politikalarını kontrol edin.",
773 "no_relays": "Röle yok",
774 "choose_relay": "Lütfen kullanmak için bir röle seçin",
res/values/strings_uk.arb
+3
@@ -768,6 +768,9 @@
768 "confirmed_tx": "Підтверджений",
769 "transaction_details_source_address": "Адреса джерела",
770 "pause_wallet_creation": "Можливість створення гаманця Haven зараз призупинено.",
771 + "contractName": "Назва контракту",
772 + "contractSymbol": "Контракт символ",
773 + "description": "опис",
774 "camera_consent": "Ваша камера використовуватиметься для зйомки зображення з метою ідентифікації ${provider}. Будь ласка, ознайомтеся з їхньою політикою конфіденційності, щоб дізнатися більше.",
775 "no_relays": "Без реле",
776 "choose_relay": "Будь ласка, виберіть реле для використання",
res/values/strings_ur.arb
+5 -2
@@ -760,9 +760,12 @@
760 "confirmed_tx": "تصدیق",
761 "transaction_details_source_address": "ماخذ ایڈریس",
762 "pause_wallet_creation": "Haven Wallet ۔ﮯﮨ ﻑﻮﻗﻮﻣ ﻝﺎﺤﻟﺍ ﯽﻓ ﺖﯿﻠﮨﺍ ﯽﮐ ﮯﻧﺎﻨﺑ",
763 + "contractName": "ﻡﺎﻧ ﺎﮐ ﮦﺪﮨﺎﻌﻣ",
764 + "contractSymbol": "ﺖﻣﻼﻋ ﯽﮐ ﮦﺪﮨﺎﻌﻣ",
765 + "description": "ﻞﯿﺼﻔﺗ",
766 "camera_consent": "۔ﮟﯿﮭﮑﯾﺩ ﯽﺴﯿﻟﺎﭘ ﯽﺴﯾﻮﯿﺋﺍﺮﭘ ﯽﮐ ﻥﺍ ﻡﺮﮐ ﮦﺍﺮﺑ ﮯﯿﻟ ﮯﮐ ﺕﻼ${provider}ﯿﺼﻔﺗ ۔ﺎﮔ ﮯﺋﺎﺟ ﺎﯿﮐ ﻝﺎﻤﻌﺘﺳﺍ ﮯﯿﻟ",
764 - "no_relays": " ۔ﮟﯿﮩﻧ ﮯﻠﯾﺭ ﯽﺋﻮﮐ",
765 - "choose_relay": " ۔ﮟﯾﺮﮐ ﺏﺎﺨﺘﻧﺍ ﺎﮐ ﮯﻠﯾﺭ ﮯﯿﻟ ﮯﮐ ﮯﻧﺮﮐ ﻝﺎﻤﻌﺘﺳﺍ ﻡﺮﮐ ﮦﺍﺮﺑ",
767 + "no_relays": "۔ﮟﯿﮩﻧ ﮯﻠﯾﺭ ﯽﺋﻮﮐ",
768 + "choose_relay": "۔ﮟﯾﺮﮐ ﺏﺎﺨﺘﻧﺍ ﺎﮐ ﮯﻠﯾﺭ ﮯﯿﻟ ﮯﮐ ﮯﻧﺮﮐ ﻝﺎﻤﻌﺘﺳﺍ ﻡﺮﮐ ﮦﺍﺮﺑ",
769 "no_relays_message": "۔ﮟﯾﺮﮐ ﻞﻣﺎﺷ ﮯﻠﯾﺭ ﮟﯿﻣ ﮈﺭﺎﮑﯾﺭ ﺮﭩﺳﻮﻧ ﮯﻨﭘﺍ ﮦﻭ ﮧﮐ ﮟﯾﺩ ﺖﯾﺍﺪﮨ ﻮﮐ ﮦﺪﻨﻨﮐ ﻝﻮﺻﻭ ﻡﺮﮐ ﮦﺍﺮﺑ ۔",
770 "no_relay_on_domain": "۔ﮟﯾﺮﮐ ﺏﺎﺨﺘﻧﺍ ﺎﮐ ﮯﻠﯾﺭ ﮯﯿﻟ ﮯﮐ ﮯﻧﺮﮐ ﻝﺎﻤﻌﺘﺳﺍ ﻡﺮﮐ ﮦﺍﺮﺑ ۔ﮯﮨ ﮟﯿﮩﻧ ﺏﺎﯿﺘﺳﺩ ﮯﻠﯾﺭ ﺎﯾ ﮯﮨ ﮟ"
771 }
res/values/strings_yo.arb
+3
@@ -762,6 +762,9 @@
762 "confirmed_tx": "Jẹrisi",
763 "transaction_details_source_address": "Adirẹsi orisun",
764 "pause_wallet_creation": "Agbara lati ṣẹda Haven Wallet ti wa ni idaduro lọwọlọwọ.",
765 + "contractName": "Orukọ adehun",
766 + "contractSymbol": "Aami adehun",
767 + "description": "Apejuwe",
768 "camera_consent": "Kamẹra rẹ yoo ṣee lo lati ya aworan kan fun awọn idi idanimọ nipasẹ ${provider}. Jọwọ ṣayẹwo Ilana Aṣiri wọn fun awọn alaye.",
769 "no_relays": "Ko si relays",
770 "choose_relay": "Jọwọ yan yii lati lo",
res/values/strings_zh.arb
+3
@@ -767,6 +767,9 @@
767 "confirmed_tx": "确认的",
768 "transaction_details_source_address": "源地址",
769 "pause_wallet_creation": "创建 Haven 钱包的功能当前已暂停。",
770 + "contractName": "合约名称",
771 + "contractSymbol": "合约符号",
772 + "description": "描述",
773 "camera_consent": "${provider} 将使用您的相机拍摄图像以供识别之用。请查看他们的隐私政策了解详情。",
774 "no_relays": "无继电器",
775 "choose_relay": "请选择要使用的继电器",
tool/configure.dart
+36 -18
@@ -525,19 +525,24 @@ import 'package:cw_core/wallet_base.dart';
525 import 'package:cw_core/wallet_credentials.dart';
526 import 'package:cw_core/wallet_info.dart';
527 import 'package:cw_core/wallet_service.dart';
528 -import 'package:eth_sig_util/util/utils.dart';
529 -import 'package:hive/hive.dart';
530 -import 'package:web3dart/web3dart.dart';
528 +
529 """;
530 const ethereumCWHeaders = """
533 -import 'package:cw_ethereum/ethereum_formatter.dart';
534 -import 'package:cw_ethereum/ethereum_mnemonics.dart';
535 -import 'package:cw_ethereum/ethereum_transaction_credentials.dart';
536 -import 'package:cw_ethereum/ethereum_transaction_info.dart';
531 +import 'package:cw_evm/evm_chain_formatter.dart';
532 +import 'package:cw_evm/evm_chain_mnemonics.dart';
533 +import 'package:cw_evm/evm_chain_transaction_credentials.dart';
534 +import 'package:cw_evm/evm_chain_transaction_info.dart';
535 +import 'package:cw_evm/evm_chain_transaction_priority.dart';
536 +import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
537 +
538 +import 'package:cw_ethereum/ethereum_client.dart';
539 import 'package:cw_ethereum/ethereum_wallet.dart';
538 -import 'package:cw_ethereum/ethereum_wallet_creation_credentials.dart';
540 import 'package:cw_ethereum/ethereum_wallet_service.dart';
540 -import 'package:cw_ethereum/ethereum_transaction_priority.dart';
541 +
542 +import 'package:eth_sig_util/util/utils.dart';
543 +import 'package:hive/hive.dart';
544 +import 'package:web3dart/web3dart.dart';
545 +
546 """;
547 const ethereumCwPart = "part 'cw_ethereum.dart';";
548 const ethereumContent = """
@@ -612,19 +617,24 @@ import 'package:cw_core/wallet_base.dart';
617 import 'package:cw_core/wallet_credentials.dart';
618 import 'package:cw_core/wallet_info.dart';
619 import 'package:cw_core/wallet_service.dart';
615 -import 'package:eth_sig_util/util/utils.dart';
616 -import 'package:hive/hive.dart';
617 -import 'package:web3dart/web3dart.dart';
620 +
621 """;
622 const polygonCWHeaders = """
620 -import 'package:cw_polygon/polygon_formatter.dart';
621 -import 'package:cw_polygon/polygon_transaction_credentials.dart';
622 -import 'package:cw_polygon/polygon_transaction_info.dart';
623 +import 'package:cw_evm/evm_chain_formatter.dart';
624 +import 'package:cw_evm/evm_chain_mnemonics.dart';
625 +import 'package:cw_evm/evm_chain_transaction_info.dart';
626 +import 'package:cw_evm/evm_chain_transaction_priority.dart';
627 +import 'package:cw_evm/evm_chain_transaction_credentials.dart';
628 +import 'package:cw_evm/evm_chain_wallet_creation_credentials.dart';
629 +
630 +import 'package:cw_polygon/polygon_client.dart';
631 import 'package:cw_polygon/polygon_wallet.dart';
624 -import 'package:cw_polygon/polygon_wallet_creation_credentials.dart';
632 import 'package:cw_polygon/polygon_wallet_service.dart';
626 -import 'package:cw_polygon/polygon_transaction_priority.dart';
627 -import 'package:cw_ethereum/ethereum_mnemonics.dart';
633 +
634 +import 'package:hive/hive.dart';
635 +import 'package:web3dart/web3dart.dart';
636 +import 'package:eth_sig_util/util/utils.dart';
637 +
638 """;
639 const polygonCwPart = "part 'cw_polygon.dart';";
640 const polygonContent = """
@@ -924,6 +934,10 @@ Future<void> generatePubspec(
934 cw_polygon:
935 path: ./cw_polygon
936 """;
937 + const cwEVM = """
938 + cw_evm:
939 + path: ./cw_evm
940 + """;
941 final inputFile = File(pubspecOutputPath);
942 final inputText = await inputFile.readAsString();
943 final inputLines = inputText.split('\n');
@@ -964,6 +978,10 @@ Future<void> generatePubspec(
978 output += '\n$cwHaven';
979 }
980
981 + if (hasEthereum || hasPolygon) {
982 + output += '\n$cwEVM';
983 + }
984 +
985 final outputLines = output.split('\n');
986 inputLines.insertAll(dependenciesIndex + 1, outputLines);
987 final outputContent = inputLines.join('\n');
tool/generate_secrets_config.dart
+4 -4
@@ -4,7 +4,7 @@ import 'utils/secret_key.dart';
4 import 'utils/utils.dart';
5
6 const configPath = 'tool/.secrets-config.json';
7 -const ethereumConfigPath = 'tool/.ethereum-secrets-config.json';
7 +const evmChainsConfigPath = 'tool/.evm-secrets-config.json';
8
9 Future<void> main(List<String> args) async => generateSecretsConfig(args);
10
@@ -17,7 +17,7 @@ Future<void> generateSecretsConfig(List<String> args) async {
17 });
18
19 final configFile = File(configPath);
20 - final ethereumConfigFile = File(ethereumConfigPath);
20 + final evmChainsConfigFile = File(evmChainsConfigPath);
21 final secrets = <String, dynamic>{};
22
23 secrets.addAll(extraInfo);
@@ -49,7 +49,7 @@ Future<void> generateSecretsConfig(List<String> args) async {
49 await configFile.writeAsString(secretsJson);
50
51 secrets.clear();
52 - SecretKey.ethereumSecrets.forEach((sec) {
52 + SecretKey.evmChainsSecrets.forEach((sec) {
53 if (secrets[sec.name] != null) {
54 return;
55 }
@@ -59,5 +59,5 @@ Future<void> generateSecretsConfig(List<String> args) async {
59
60 secretsJson = JsonEncoder.withIndent(' ').convert(secrets);
61
62 - await ethereumConfigFile.writeAsString(secretsJson);
62 + await evmChainsConfigFile.writeAsString(secretsJson);
63 }
tool/import_secrets_config.dart
+10 -10
@@ -5,8 +5,8 @@ import 'utils/utils.dart';
5 const configPath = 'tool/.secrets-config.json';
6 const outputPath = 'lib/.secrets.g.dart';
7
8 -const ethereumConfigPath = 'tool/.ethereum-secrets-config.json';
9 -const ethereumOutputPath = 'cw_ethereum/lib/.secrets.g.dart';
8 +const evmChainsConfigPath = 'tool/.evm-secrets-config.json';
9 +const evmChainsOutputPath = 'cw_evm/lib/.secrets.g.dart';
10
11 Future<void> main(List<String> args) async => importSecretsConfig();
12
@@ -15,11 +15,11 @@ Future<void> importSecretsConfig() async {
15 final input = json.decode(File(configPath).readAsStringSync()) as Map<String, dynamic>;
16 final output = input.keys.fold('', (String acc, String val) => acc + generateConst(val, input));
17
18 - final ethereumOutputFile = File(ethereumOutputPath);
19 - final ethereumInput =
20 - json.decode(File(ethereumConfigPath).readAsStringSync()) as Map<String, dynamic>;
21 - final ethereumOutput = ethereumInput.keys
22 - .fold('', (String acc, String val) => acc + generateConst(val, ethereumInput));
18 + final evmChainsOutputFile = File(evmChainsOutputPath);
19 + final evmChainsInput =
20 + json.decode(File(evmChainsConfigPath).readAsStringSync()) as Map<String, dynamic>;
21 + final evmChainsOutput = evmChainsInput.keys
22 + .fold('', (String acc, String val) => acc + generateConst(val, evmChainsInput));
23
24 if (outputFile.existsSync()) {
25 await outputFile.delete();
@@ -27,9 +27,9 @@ Future<void> importSecretsConfig() async {
27
28 await outputFile.writeAsString(output);
29
30 - if (ethereumOutputFile.existsSync()) {
31 - await ethereumOutputFile.delete();
30 + if (evmChainsOutputFile.existsSync()) {
31 + await evmChainsOutputFile.delete();
32 }
33
34 - await ethereumOutputFile.writeAsString(ethereumOutput);
34 + await evmChainsOutputFile.writeAsString(evmChainsOutput);
35 }
tool/utils/secret_key.dart
+2 -2
@@ -36,10 +36,10 @@ class SecretKey {
36 SecretKey('robinhoodApplicationId', () => ''),
37 SecretKey('robinhoodCIdApiSecret', () => ''),
38 SecretKey('walletConnectProjectId', () => ''),
39 - SecretKey('moralisApiKey', () => '')
39 + SecretKey('moralisApiKey', () => ''),
40 ];
41
42 - static final ethereumSecrets = [
42 + static final evmChainsSecrets = [
43 SecretKey('etherScanApiKey', () => ''),
44 SecretKey('polygonScanApiKey', () => ''),
45 ];