Cw 78 ethereum (#862)

* Add initial flow for ethereum * Add initial create Eth wallet flow * Complete Ethereum wallet creation flow * Fix web3dart versioning issue * Add primary receive address extracted from private key * Implement open wallet functionality * Implement restore wallet from seed functionality * Fixate web3dart version as higher versions cause some issues * Add Initial Transaction priorities for eth Add estimated gas price * Rename priority value to tip * Re-order wallet types * Change ethereum node Fix connection issues * Fix estimating gas for priority * Add case for ethereum to fetch it's seeds * Add case for ethereum to request node * Fix Exchange screen initial pairs * Add initial send transaction flow * Add missing configure for ethereum class * Add Eth address initial setup * Fix Private key for Ethereum wallets * Change sign/send transaction flow * - Fix Conflicts with main - Remove unused function from Haven configure.dart * Add build command for ethereum package * Add missing Node list file to pubspec * - Fix balance display - Fix parsing of Ethereum amount - Add more Ethereum Nodes * - Fix extracting Ethereum Private key from seeds - Integrate signing/sending transaction with the send view model * - Update and Fix Conflicts with main * Add Balances for ERC20 tokens * Fix conflicts with main * Add erc20 abi json * Add send erc20 tokens initial function * add missing getHeightByDate in Haven * Allow contacts and wallets from the same tag * Add Shiba Inu icon * Add send ERC-20 tokens initial flow * Add missing import in generated file * Add initial approach for transaction sending for ERC-20 tokens * Refactor signing/sending transactions * Add initial flow for transactions subscription * Refactor signing/sending transactions * Add home settings icon * Fix conflicts with main * Initial flow for home settings * Add logic flow for adding erc20 tokens * Fix initial UI * Finalize UI for Tokens * Integrate UI with Ethereum flow * Add "Enable/Disable" feature for ERC20 tokens * Add initial Erc20 tokens * Add Sorting and Pin Native Token features * Fix price sorting * Sort tokens list as well when Sort criteria changes * - Improve sorting balances flow - Add initial add token from search bar flow * Fix Accounts Popup UI * Fix Pin native token * Fix Enabling/Disabling tokens Fix sorting by fiat once app is opened Improve token availability mechanism * Fix deleting token Fix renaming tokens * Fix issue with search * Add more tokens * - Fix scroll issue - Add ERC20 tokens placeholder image in picker * - Separate and organize default erc20 tokens - Fix scrolling - Add token placeholder images in picker - Sort disabled tokens alphabetically * Change BNB token initial availability * Fix Conflicts with main * Fix Conflicts with main * Add Verse ERC20 token to the initial tokens list * Add rename wallet to Ethereum * Integrate EtherScan API for fetching address transactions Generate Ethereum specific secrets in Ethereum package * Adjust transactions fiat price for ERC20 tokens * Free Up GitHub Actions Ubuntu Runner Disk Space * Free Up GitHub Actions Ubuntu Runner Disk space (trial 2) * Fix Transaction Fee display * Save transaction history * Enhance loading time for erc20 tokens transactions * Minor Fixes and Enhancements * Fix sending erc20 fix block explorer issue * Fix int overflow * Fix transaction amount conversions * Minor: `slow` -> `Slow` * Update build guide * Fix fetching fiat rate taking a lot of time by only fetching enabled tokens only and making the API calls in parallel not sequential * Update transactions on a periodic basis * For fee, use ETH spot price, not ERC-20 spot price * Add Etherscan History privacy option to enable/disable Etherscan API * Show estimated fee amounts in the send screen * fix send fiat fields parsing issue * Fix transactions estimated fee less than actual fee * handle balance sorting when balance is disabled Handle empty transactions list * Fix Delete Ethereum wallet Fix balance < 0.01 * Fix Decimal place for Ethereum amount Fix sending amount issue * Change words count * Remove balance hint and Full balance row from Ethereum wallets * support changing the asset type in send templates * Fix Templates for ERC tokens issues * Fix conflicts in send templates * Disable batch sending in Ethereum * Fix Fee calculation with different priorities * Fix Conflicts with main * Add offline error to ignored exceptions --------- Co-authored-by: Justin Ehrenhofer <justin.ehrenhofer@gmail.com>

Omar Hatem committed Aug 4, 2023 at 20:01 UTC 3ce4000dcf47fa99053cf7beb52d456cb4feae9e
137 files changed +7162 -1490
.github/workflows/pr_test_build.yml
+2
@@ -92,6 +92,7 @@ jobs:
92 cd cw_monero && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
93 cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
94 cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
95 + cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
96 flutter packages pub run build_runner build --delete-conflicting-outputs
97
98 - name: Add secrets
@@ -124,6 +125,7 @@ jobs:
125 echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart
126 echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart
127 echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart
128 + echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_ethereum/lib/.secrets.g.dart
129
130 - name: Rename app
131 run: echo -e "id=com.cakewallet.test\nname=$GITHUB_HEAD_REF" > /opt/android/cake_wallet/android/app.properties
.gitignore
+3
@@ -90,7 +90,9 @@ android/key.properties
90 **/tool/.secrets-prod.json
91 **/tool/.secrets-test.json
92 **/tool/.secrets-config.json
93 +**/tool/.ethereum-secrets-config.json
94 **/lib/.secrets.g.dart
95 +**/cw_ethereum/lib/.secrets.g.dart
96
97 vendor/
98
@@ -121,6 +123,7 @@ cw_haven/android/.cxx/
123 lib/bitcoin/bitcoin.dart
124 lib/monero/monero.dart
125 lib/haven/haven.dart
126 +lib/ethereum/ethereum.dart
127
128 ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png
129 ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png
assets/ethereum_server_list.yml new
+10
@@ -0,0 +1,10 @@
1 +-
2 + uri: ethereum.publicnode.com
3 +-
4 + uri: eth.llamarpc.com
5 +-
6 + uri: rpc.flashbots.net
7 +-
8 + uri: eth-mainnet.public.blastapi.io
9 +-
10 + uri: ethereum.publicnode.com
\ No newline at end of file
assets/images/home_screen_settings_icon.png
Binary files /dev/null and b/assets/images/home_screen_settings_icon.png differ
configure_cake_wallet_android.sh new
+10
@@ -0,0 +1,10 @@
1 +cd scripts/android
2 +source ./app_env.sh cakewallet
3 +./app_config.sh
4 +cd ../.. && flutter pub get
5 +cd cw_core && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
6 +cd cw_monero && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
7 +cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
8 +cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
9 +cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
10 +flutter packages pub run build_runner build --delete-conflicting-outputs
cw_bitcoin/lib/electrum_transaction_history.dart
+1 -2
@@ -1,7 +1,6 @@
1 import 'dart:convert';
2 import 'package:cw_core/pathForWallet.dart';
3 import 'package:cw_core/wallet_info.dart';
4 -import 'package:flutter/foundation.dart';
4 import 'package:mobx/mobx.dart';
5 import 'package:cw_core/transaction_history.dart';
6 import 'package:cw_bitcoin/file.dart';
@@ -67,7 +66,7 @@ abstract class ElectrumTransactionHistoryBase
66 Future<void> _load() async {
67 try {
68 final content = await _read();
70 - final txs = content['transactions'] as Map<String, dynamic> ?? {};
69 + final txs = content['transactions'] as Map<String, dynamic>? ?? {};
70
71 txs.entries.forEach((entry) {
72 final val = entry.value;
cw_bitcoin/lib/electrum_transaction_info.dart
+3 -4
@@ -1,4 +1,3 @@
1 -import 'package:flutter/foundation.dart';
1 import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
2 import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
3 import 'package:cw_bitcoin/address_from_output.dart';
@@ -217,9 +216,9 @@ class ElectrumTransactionInfo extends TransactionInfo {
216 height: info.height,
217 amount: info.amount,
218 fee: info.fee,
220 - direction: direction ?? info.direction,
221 - date: date ?? info.date,
222 - isPending: isPending ?? info.isPending,
219 + direction: direction,
220 + date: date,
221 + isPending: isPending,
222 confirmations: info.confirmations);
223 }
224
cw_bitcoin/lib/electrum_wallet.dart
+1
@@ -431,6 +431,7 @@ abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
431 await transactionHistory.save();
432 }
433
434 + @override
435 Future<void> renameWalletFiles(String newWalletName) async {
436 final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type);
437 final currentWalletFile = File(currentWalletPath);
cw_bitcoin/pubspec.yaml
+1 -1
@@ -27,7 +27,7 @@ dependencies:
27 unorm_dart: ^0.2.0
28 cryptography: ^2.0.5
29 encrypt: ^5.0.1
30 -
30 +
31 dev_dependencies:
32 flutter_test:
33 sdk: flutter
cw_core/lib/currency_for_wallet_type.dart
+2
@@ -11,6 +11,8 @@ CryptoCurrency currencyForWalletType(WalletType type) {
11 return CryptoCurrency.ltc;
12 case WalletType.haven:
13 return CryptoCurrency.xhv;
14 + case WalletType.ethereum:
15 + return CryptoCurrency.eth;
16 default:
17 throw Exception('Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType');
18 }
cw_core/lib/erc20_token.dart new
+64
@@ -0,0 +1,64 @@
1 +import 'package:cw_core/crypto_currency.dart';
2 +import 'package:hive/hive.dart';
3 +
4 +part 'erc20_token.g.dart';
5 +
6 +@HiveType(typeId: Erc20Token.typeId)
7 +class Erc20Token extends CryptoCurrency with HiveObjectMixin {
8 + @HiveField(0)
9 + final String name;
10 + @HiveField(1)
11 + final String symbol;
12 + @HiveField(2)
13 + final String contractAddress;
14 + @HiveField(3)
15 + final int decimal;
16 + @HiveField(4, defaultValue: true)
17 + bool _enabled;
18 + @HiveField(5)
19 + final String? iconPath;
20 +
21 + bool get enabled => _enabled;
22 +
23 + set enabled(bool value) => _enabled = value;
24 +
25 + Erc20Token({
26 + required this.name,
27 + required this.symbol,
28 + required this.contractAddress,
29 + required this.decimal,
30 + bool enabled = true,
31 + this.iconPath,
32 + }) : _enabled = enabled,
33 + super(
34 + name: symbol.toLowerCase(),
35 + title: symbol.toUpperCase(),
36 + fullName: name,
37 + tag: "ETH",
38 + iconPath: iconPath,
39 + );
40 +
41 + Erc20Token.copyWith(Erc20Token other, String? icon)
42 + : this.name = other.name,
43 + this.symbol = other.symbol,
44 + this.contractAddress = other.contractAddress,
45 + this.decimal = other.decimal,
46 + this._enabled = other.enabled,
47 + this.iconPath = icon,
48 + super(
49 + name: other.name,
50 + title: other.symbol.toUpperCase(),
51 + fullName: other.name,
52 + tag: "ETH",
53 + iconPath: icon,
54 + );
55 +
56 + static const typeId = 12;
57 + static const boxName = 'Erc20Tokens';
58 +
59 + @override
60 + bool operator ==(other) => other is Erc20Token && other.contractAddress == contractAddress;
61 +
62 + @override
63 + int get hashCode => contractAddress.hashCode;
64 +}
cw_core/lib/node.dart
+18 -1
@@ -75,6 +75,8 @@ class Node extends HiveObject with Keyable {
75 return createUriFromElectrumAddress(uriRaw);
76 case WalletType.haven:
77 return Uri.http(uriRaw, '');
78 + case WalletType.ethereum:
79 + return Uri.https(uriRaw, '');
80 default:
81 throw Exception('Unexpected type ${type.toString()} for Node uri');
82 }
@@ -124,6 +126,8 @@ class Node extends HiveObject with Keyable {
126 return requestElectrumServer();
127 case WalletType.haven:
128 return requestMoneroNode();
129 + case WalletType.ethereum:
130 + return requestElectrumServer();
131 default:
132 return false;
133 }
@@ -166,7 +170,7 @@ class Node extends HiveObject with Keyable {
170 } catch (_) {
171 return false;
172 }
169 -}
173 + }
174
175 Future<bool> requestNodeWithProxy(String proxy) async {
176
@@ -193,4 +197,17 @@ class Node extends HiveObject with Keyable {
197 return false;
198 }
199 }
200 +
201 + Future<bool> requestEthereumServer() async {
202 + try {
203 + final response = await http.get(
204 + uri,
205 + headers: {'Content-Type': 'application/json'},
206 + );
207 +
208 + return response.statusCode >= 200 && response.statusCode < 300;
209 + } catch (_) {
210 + return false;
211 + }
212 + }
213 }
cw_core/lib/wallet_base.dart
+2
@@ -75,4 +75,6 @@ abstract class WalletBase<
75 Future<void>? updateBalance();
76
77 void setExceptionHandler(void Function(FlutterErrorDetails) onError) => null;
78 +
79 + Future<void> renameWalletFiles(String newWalletName);
80 }
cw_core/lib/wallet_service.dart
+1 -1
@@ -18,5 +18,5 @@ abstract class WalletService<N extends WalletCredentials,
18
19 Future<void> remove(String wallet);
20
21 - Future<void> rename(String name, String password, String newName);
21 + Future<void> rename(String currentName, String password, String newName);
22 }
cw_core/lib/wallet_type.dart
+15 -1
@@ -7,7 +7,8 @@ const walletTypes = [
7 WalletType.monero,
8 WalletType.bitcoin,
9 WalletType.litecoin,
10 - WalletType.haven
10 + WalletType.haven,
11 + WalletType.ethereum,
12 ];
13 const walletTypeTypeId = 5;
14
@@ -27,6 +28,9 @@ enum WalletType {
28
29 @HiveField(4)
30 haven,
31 +
32 + @HiveField(5)
33 + ethereum,
34 }
35
36 int serializeToInt(WalletType type) {
@@ -39,6 +43,8 @@ int serializeToInt(WalletType type) {
43 return 2;
44 case WalletType.haven:
45 return 3;
46 + case WalletType.ethereum:
47 + return 4;
48 default:
49 return -1;
50 }
@@ -54,6 +60,8 @@ WalletType deserializeFromInt(int raw) {
60 return WalletType.litecoin;
61 case 3:
62 return WalletType.haven;
63 + case 4:
64 + return WalletType.ethereum;
65 default:
66 throw Exception('Unexpected token: $raw for WalletType deserializeFromInt');
67 }
@@ -69,6 +77,8 @@ String walletTypeToString(WalletType type) {
77 return 'Litecoin';
78 case WalletType.haven:
79 return 'Haven';
80 + case WalletType.ethereum:
81 + return 'Ethereum';
82 default:
83 return '';
84 }
@@ -84,6 +94,8 @@ String walletTypeToDisplayName(WalletType type) {
94 return 'Litecoin (LTC)';
95 case WalletType.haven:
96 return 'Haven (XHV)';
97 + case WalletType.ethereum:
98 + return 'Ethereum (ETH)';
99 default:
100 return '';
101 }
@@ -99,6 +111,8 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type) {
111 return CryptoCurrency.ltc;
112 case WalletType.haven:
113 return CryptoCurrency.xhv;
114 + case WalletType.ethereum:
115 + return CryptoCurrency.eth;
116 default:
117 throw Exception('Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency');
118 }
cw_ethereum/.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_ethereum/.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: eb6d86ee27deecba4a83536aa20f366a6044895c
8 + channel: stable
9 +
10 +project_type: package
cw_ethereum/CHANGELOG.md new
+3
@@ -0,0 +1,3 @@
1 +## 0.0.1
2 +
3 +* TODO: Describe initial release.
cw_ethereum/LICENSE new
+1
@@ -0,0 +1 @@
1 +TODO: Add your license here.
cw_ethereum/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_ethereum/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_ethereum/lib/cw_ethereum.dart new
+7
@@ -0,0 +1,7 @@
1 +library cw_ethereum;
2 +
3 +/// A Calculator.
4 +class Calculator {
5 + /// Returns [value] plus 1.
6 + int addOne(int value) => value + 1;
7 +}
cw_ethereum/lib/default_erc20_tokens.dart new
+302
@@ -0,0 +1,302 @@
1 +import 'package:cw_core/crypto_currency.dart';
2 +import 'package:cw_core/erc20_token.dart';
3 +
4 +class DefaultErc20Tokens {
5 + final List<Erc20Token> _defaultTokens = [
6 + Erc20Token(
7 + name: "USD Coin",
8 + symbol: "USDC",
9 + contractAddress: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
10 + decimal: 6,
11 + enabled: true,
12 + ),
13 + Erc20Token(
14 + name: "USDT Tether",
15 + symbol: "USDT",
16 + contractAddress: "0xdac17f958d2ee523a2206206994597c13d831ec7",
17 + decimal: 6,
18 + enabled: true,
19 + ),
20 + Erc20Token(
21 + name: "Dai",
22 + symbol: "DAI",
23 + contractAddress: "0x6B175474E89094C44Da98b954EedeAC495271d0F",
24 + decimal: 18,
25 + enabled: true,
26 + ),
27 + Erc20Token(
28 + name: "Wrapped Ether",
29 + symbol: "WETH",
30 + contractAddress: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
31 + decimal: 18,
32 + enabled: false,
33 + ),
34 + Erc20Token(
35 + name: "Pepe",
36 + symbol: "PEPE",
37 + contractAddress: "0x6982508145454ce325ddbe47a25d4ec3d2311933",
38 + decimal: 18,
39 + enabled: false,
40 + ),
41 + Erc20Token(
42 + name: "SHIBA INU",
43 + symbol: "SHIB",
44 + contractAddress: "0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce",
45 + decimal: 18,
46 + enabled: false,
47 + ),
48 + Erc20Token(
49 + name: "ApeCoin",
50 + symbol: "APE",
51 + contractAddress: "0x4d224452801aced8b2f0aebe155379bb5d594381",
52 + decimal: 18,
53 + enabled: false,
54 + ),
55 + Erc20Token(
56 + name: "Matic Token",
57 + symbol: "MATIC",
58 + contractAddress: "0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0",
59 + decimal: 18,
60 + enabled: false,
61 + ),
62 + Erc20Token(
63 + name: "Wrapped BTC",
64 + symbol: "WBTC",
65 + contractAddress: "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599",
66 + decimal: 8,
67 + enabled: false,
68 + ),
69 + Erc20Token(
70 + name: "Gitcoin",
71 + symbol: "GTC",
72 + contractAddress: "0xde30da39c46104798bb5aa3fe8b9e0e1f348163f",
73 + decimal: 18,
74 + enabled: false,
75 + ),
76 + Erc20Token(
77 + name: "Compound",
78 + symbol: "COMP",
79 + contractAddress: "0xc00e94cb662c3520282e6f5717214004a7f26888",
80 + decimal: 18,
81 + enabled: false,
82 + ),
83 + Erc20Token(
84 + name: "Aave Token",
85 + symbol: "AAVE",
86 + contractAddress: "0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9",
87 + decimal: 18,
88 + enabled: false,
89 + ),
90 + Erc20Token(
91 + name: "Uniswap",
92 + symbol: "UNI",
93 + contractAddress: "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984",
94 + decimal: 18,
95 + enabled: false,
96 + ),
97 + Erc20Token(
98 + name: "Decentraland",
99 + symbol: "MANA",
100 + contractAddress: "0x0F5D2fB29fb7d3CFeE444a200298f468908cC942",
101 + decimal: 18,
102 + enabled: false,
103 + ),
104 + Erc20Token(
105 + name: "Storj",
106 + symbol: "STORJ",
107 + contractAddress: "0xb64ef51c888972c908cfacf59b47c1afbc0ab8ac",
108 + decimal: 8,
109 + enabled: false,
110 + ),
111 + Erc20Token(
112 + name: "Maker",
113 + symbol: "MKR",
114 + contractAddress: "0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2",
115 + decimal: 18,
116 + enabled: false,
117 + ),
118 + Erc20Token(
119 + name: "Orchid",
120 + symbol: "OXT",
121 + contractAddress: "0x4575f41308EC1483f3d399aa9a2826d74Da13Deb",
122 + decimal: 18,
123 + enabled: false,
124 + ),
125 + Erc20Token(
126 + name: "Paxos Gold",
127 + symbol: "PAXG",
128 + contractAddress: "0x45804880De22913dAFE09f4980848ECE6EcbAf78",
129 + decimal: 18,
130 + enabled: false,
131 + ),
132 + Erc20Token(
133 + name: "Binance Coin",
134 + symbol: "BNB",
135 + contractAddress: "0xB8c77482e45F1F44dE1745F52C74426C631bDD52",
136 + decimal: 18,
137 + enabled: false,
138 + ),
139 + Erc20Token(
140 + name: "stETH",
141 + symbol: "stETH",
142 + contractAddress: "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84",
143 + decimal: 18,
144 + enabled: false,
145 + ),
146 + Erc20Token(
147 + name: "Lido DAO",
148 + symbol: "LDO",
149 + contractAddress: "0x5A98FcBEA516Cf06857215779Fd812CA3beF1B32",
150 + decimal: 18,
151 + enabled: false,
152 + ),
153 + Erc20Token(
154 + name: "Arbitrum",
155 + symbol: "ARB",
156 + contractAddress: "0xB50721BCf8d664c30412Cfbc6cf7a15145234ad1",
157 + decimal: 18,
158 + enabled: false,
159 + ),
160 + Erc20Token(
161 + name: "Graph Token",
162 + symbol: "GRT",
163 + contractAddress: "0xc944E90C64B2c07662A292be6244BDf05Cda44a7",
164 + decimal: 18,
165 + enabled: false,
166 + ),
167 + Erc20Token(
168 + name: "Frax",
169 + symbol: "FRAX",
170 + contractAddress: "0x853d955aCEf822Db058eb8505911ED77F175b99e",
171 + decimal: 18,
172 + enabled: false,
173 + ),
174 + Erc20Token(
175 + name: "Gemini dollar",
176 + symbol: "GUSD",
177 + contractAddress: "0x056Fd409E1d7A124BD7017459dFEa2F387b6d5Cd",
178 + decimal: 2,
179 + enabled: false,
180 + ),
181 + Erc20Token(
182 + name: "Compound Ether",
183 + symbol: "cETH",
184 + contractAddress: "0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5",
185 + decimal: 8,
186 + enabled: false,
187 + ),
188 + Erc20Token(
189 + name: "Binance USD",
190 + symbol: "BUSD",
191 + contractAddress: "0x4Fabb145d64652a948d72533023f6E7A623C7C53",
192 + decimal: 18,
193 + enabled: false,
194 + ),
195 + Erc20Token(
196 + name: "TrueUSD",
197 + symbol: "TUSD",
198 + contractAddress: "0x0000000000085d4780B73119b644AE5ecd22b376",
199 + decimal: 18,
200 + enabled: false,
201 + ),
202 + Erc20Token(
203 + name: "Cronos Coin",
204 + symbol: "CRO",
205 + contractAddress: "0xA0b73E1Ff0B80914AB6fe0444E65848C4C34450b",
206 + decimal: 8,
207 + enabled: false,
208 + ),
209 + Erc20Token(
210 + name: "Pax Dollar",
211 + symbol: "USDP",
212 + contractAddress: "0x8E870D67F660D95d5be530380D0eC0bd388289E1",
213 + decimal: 18,
214 + enabled: false,
215 + ),
216 + Erc20Token(
217 + name: "Fantom Token",
218 + symbol: "FTM",
219 + contractAddress: "0x4E15361FD6b4BB609Fa63C81A2be19d873717870",
220 + decimal: 18,
221 + enabled: false,
222 + ),
223 + Erc20Token(
224 + name: "BitTorrent",
225 + symbol: "BTT",
226 + contractAddress: "0xC669928185DbCE49d2230CC9B0979BE6DC797957",
227 + decimal: 18,
228 + enabled: false,
229 + ),
230 + Erc20Token(
231 + name: "Nexo",
232 + symbol: "NEXO",
233 + contractAddress: "0xB62132e35a6c13ee1EE0f84dC5d40bad8d815206",
234 + decimal: 18,
235 + enabled: false,
236 + ),
237 + Erc20Token(
238 + name: "dYdX",
239 + symbol: "DYDX",
240 + contractAddress: "0x92D6C1e31e14520e676a687F0a93788B716BEff5",
241 + decimal: 18,
242 + enabled: false,
243 + ),
244 + Erc20Token(
245 + name: "PancakeSwap Token",
246 + symbol: "Cake",
247 + contractAddress: "0x152649eA73beAb28c5b49B26eb48f7EAD6d4c898",
248 + decimal: 18,
249 + enabled: false,
250 + ),
251 + Erc20Token(
252 + name: "BAT",
253 + symbol: "BAT",
254 + contractAddress: "0x0D8775F648430679A709E98d2b0Cb6250d2887EF",
255 + decimal: 18,
256 + enabled: false,
257 + ),
258 + Erc20Token(
259 + name: "1INCH Token",
260 + symbol: "1INCH",
261 + contractAddress: "0x111111111117dC0aa78b770fA6A738034120C302",
262 + decimal: 18,
263 + enabled: false,
264 + ),
265 + Erc20Token(
266 + name: "Ethereum Name Service",
267 + symbol: "ENS",
268 + contractAddress: "0xC18360217D8F7Ab5e7c516566761Ea12Ce7F9D72",
269 + decimal: 18,
270 + enabled: false,
271 + ),
272 + Erc20Token(
273 + name: "ZRX",
274 + symbol: "ZRX",
275 + contractAddress: "0xE41d2489571d322189246DaFA5ebDe1F4699F498",
276 + decimal: 18,
277 + enabled: false,
278 + ),
279 + Erc20Token(
280 + name: "Verse",
281 + symbol: "VERSE",
282 + contractAddress: "0x249cA82617eC3DfB2589c4c17ab7EC9765350a18",
283 + decimal: 18,
284 + enabled: false,
285 + ),
286 + ];
287 +
288 + List<Erc20Token> get initialErc20Tokens => _defaultTokens.map((token) {
289 + String? iconPath;
290 + try {
291 + iconPath = CryptoCurrency.all
292 + .firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase())
293 + .iconPath;
294 + } catch (_) {}
295 +
296 + if (iconPath != null) {
297 + return Erc20Token.copyWith(token, iconPath);
298 + }
299 +
300 + return token;
301 + }).toList();
302 +}
cw_ethereum/lib/erc20_balance.dart new
+47
@@ -0,0 +1,47 @@
1 +import 'dart:convert';
2 +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());
10 +
11 + final BigInt balance;
12 + final int exponent;
13 +
14 + @override
15 + String get formattedAdditionalBalance {
16 + final String formattedBalance = (balance / BigInt.from(10).pow(exponent)).toString();
17 + return formattedBalance.substring(0, min(12, formattedBalance.length));
18 + }
19 +
20 + @override
21 + String get formattedAvailableBalance {
22 + final String formattedBalance = (balance / BigInt.from(10).pow(exponent)).toString();
23 + return formattedBalance.substring(0, min(12, formattedBalance.length));
24 + }
25 +
26 + String toJSON() => json.encode({
27 + 'balanceInWei': balance.toString(),
28 + 'exponent': exponent,
29 + });
30 +
31 + static ERC20Balance? fromJSON(String? jsonSource) {
32 + if (jsonSource == null) {
33 + return null;
34 + }
35 +
36 + final decoded = json.decode(jsonSource) as Map;
37 +
38 + try {
39 + return ERC20Balance(
40 + BigInt.parse(decoded['balanceInWei']),
41 + exponent: decoded['exponent'],
42 + );
43 + } catch (e) {
44 + return ERC20Balance(BigInt.zero);
45 + }
46 + }
47 +}
cw_ethereum/lib/ethereum_client.dart new
+230
@@ -0,0 +1,230 @@
1 +import 'dart:async';
2 +import 'dart:convert';
3 +
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';
11 +import 'package:web3dart/web3dart.dart';
12 +import 'package:web3dart/contracts/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 + await _client!.getBalance(address);
43 +
44 + Future<int> getGasUnitPrice() async {
45 + final gasPrice = await _client!.getGasPrice();
46 + return gasPrice.getInWei.toInt();
47 + }
48 +
49 + Future<int> getEstimatedGas() async {
50 + final estimatedGas = await _client!.estimateGas();
51 + return estimatedGas.toInt();
52 + }
53 +
54 + Future<PendingEthereumTransaction> signTransaction({
55 + required EthPrivateKey privateKey,
56 + required String toAddress,
57 + required String amount,
58 + required int gas,
59 + required EthereumTransactionPriority priority,
60 + required CryptoCurrency currency,
61 + required int exponent,
62 + String? contractAddress,
63 + }) async {
64 + assert(currency == CryptoCurrency.eth || contractAddress != null);
65 +
66 + bool _isEthereum = currency == CryptoCurrency.eth;
67 +
68 + final price = await _client!.getGasPrice();
69 +
70 + final Transaction transaction = Transaction(
71 + from: privateKey.address,
72 + to: EthereumAddress.fromHex(toAddress),
73 + maxGas: gas,
74 + gasPrice: price,
75 + maxPriorityFeePerGas: EtherAmount.fromUnitAndValue(EtherUnit.gwei, priority.tip),
76 + value: _isEthereum ? EtherAmount.inWei(BigInt.parse(amount)) : EtherAmount.zero(),
77 + );
78 +
79 + final signedTransaction = await _client!.signTransaction(privateKey, transaction);
80 +
81 + final Function _sendTransaction;
82 +
83 + if (_isEthereum) {
84 + _sendTransaction = () async => await sendTransaction(signedTransaction);
85 + } else {
86 + final erc20 = Erc20(
87 + client: _client!,
88 + address: EthereumAddress.fromHex(contractAddress!),
89 + );
90 +
91 + _sendTransaction = () async {
92 + await erc20.transfer(
93 + EthereumAddress.fromHex(toAddress),
94 + BigInt.parse(amount),
95 + credentials: privateKey,
96 + );
97 + };
98 + }
99 +
100 + return PendingEthereumTransaction(
101 + signedTransaction: signedTransaction,
102 + amount: amount,
103 + fee: BigInt.from(gas) * price.getInWei,
104 + sendTransaction: _sendTransaction,
105 + exponent: exponent,
106 + );
107 + }
108 +
109 + Future<String> sendTransaction(Uint8List signedTransaction) async =>
110 + await _client!.sendRawTransaction(signedTransaction);
111 +
112 + Future getTransactionDetails(String transactionHash) async {
113 + // Wait for the transaction receipt to become available
114 + TransactionReceipt? receipt;
115 + while (receipt == null) {
116 + receipt = await _client!.getTransactionReceipt(transactionHash);
117 + await Future.delayed(Duration(seconds: 1));
118 + }
119 +
120 + // Print the receipt information
121 + print('Transaction Hash: ${receipt.transactionHash}');
122 + print('Block Hash: ${receipt.blockHash}');
123 + print('Block Number: ${receipt.blockNumber}');
124 + print('Gas Used: ${receipt.gasUsed}');
125 +
126 + /*
127 + 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]
128 +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]
129 +I/flutter ( 4474): Block Number: 17120242
130 +I/flutter ( 4474): Gas Used: 21000
131 + */
132 +
133 + // Wait for the transaction receipt to become available
134 + TransactionInformation? transactionInformation;
135 + while (transactionInformation == null) {
136 + print("********************************");
137 + transactionInformation = await _client!.getTransactionByHash(transactionHash);
138 + await Future.delayed(Duration(seconds: 1));
139 + }
140 + // Print the receipt information
141 + print('Transaction Hash: ${transactionInformation.hash}');
142 + print('Block Hash: ${transactionInformation.blockHash}');
143 + print('Block Number: ${transactionInformation.blockNumber}');
144 + print('Gas Used: ${transactionInformation.gas}');
145 +
146 + /*
147 + Transaction Hash: 0x70f404ee59c7abbfd2ec6e2ab9cadc151b847bdd895a4d0d172b0ce65d3fdd74
148 +I/flutter ( 4474): Block Hash: 0x952cfa776f68526211591ebe192cda767fbdf123d56a195fc3253783b9b4f6c8
149 +I/flutter ( 4474): Block Number: 17120242
150 +I/flutter ( 4474): Gas Used: 53000
151 + */
152 + }
153 +
154 + Future<ERC20Balance> fetchERC20Balances(
155 + EthereumAddress userAddress, String contractAddress) async {
156 + final erc20 = Erc20(address: EthereumAddress.fromHex(contractAddress), client: _client!);
157 + final balance = await erc20.balanceOf(userAddress);
158 +
159 + int exponent = (await erc20.decimals()).toInt();
160 +
161 + return ERC20Balance(balance, exponent: exponent);
162 + }
163 +
164 + Future<Erc20Token?> getErc20Token(String contractAddress) async {
165 + try {
166 + final erc20 = Erc20(address: EthereumAddress.fromHex(contractAddress), client: _client!);
167 + final name = await erc20.name();
168 + final symbol = await erc20.symbol();
169 + final decimal = await erc20.decimals();
170 +
171 + return Erc20Token(
172 + name: name,
173 + symbol: symbol,
174 + contractAddress: contractAddress,
175 + decimal: decimal.toInt(),
176 + );
177 + } catch (e) {
178 + return null;
179 + }
180 + }
181 +
182 + void stop() {
183 + _client?.dispose();
184 + }
185 +
186 + Future<List<EthereumTransactionModel>> fetchTransactions(String address,
187 + {String? contractAddress}) async {
188 + try {
189 + final response = await _httpClient.get(Uri.https("api.etherscan.io", "/api", {
190 + "module": "account",
191 + "action": contractAddress != null ? "tokentx" : "txlist",
192 + if (contractAddress != null) "contractaddress": contractAddress,
193 + "address": address,
194 + "apikey": secrets.etherScanApiKey,
195 + }));
196 +
197 + final _jsonResponse = json.decode(response.body) as Map<String, dynamic>;
198 +
199 + if (response.statusCode >= 200 && response.statusCode < 300 && _jsonResponse['status'] != 0) {
200 + return (_jsonResponse['result'] as List)
201 + .map((e) => EthereumTransactionModel.fromJson(e as Map<String, dynamic>))
202 + .toList();
203 + }
204 +
205 + return [];
206 + } catch (e) {
207 + print(e);
208 + return [];
209 + }
210 + }
211 +
212 +// Future<int> _getDecimalPlacesForContract(DeployedContract contract) async {
213 +// final String abi = await rootBundle.loadString("assets/abi_json/erc20_abi.json");
214 +// final contractAbi = ContractAbi.fromJson(abi, "ERC20");
215 +//
216 +// final contract = DeployedContract(
217 +// contractAbi,
218 +// EthereumAddress.fromHex(_erc20Currencies[erc20Currency]!),
219 +// );
220 +// final decimalsFunction = contract.function('decimals');
221 +// final decimals = await _client!.call(
222 +// contract: contract,
223 +// function: decimalsFunction,
224 +// params: [],
225 +// );
226 +//
227 +// int exponent = int.parse(decimals.first.toString());
228 +// return exponent;
229 +// }
230 +}
cw_ethereum/lib/ethereum_exceptions.dart new
+11
@@ -0,0 +1,11 @@
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 new
+25
@@ -0,0 +1,25 @@
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.dart new
+2058
@@ -0,0 +1,2058 @@
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 {
8 + static const englishWordlist = <String>[
9 + 'abandon',
10 + 'ability',
11 + 'able',
12 + 'about',
13 + 'above',
14 + 'absent',
15 + 'absorb',
16 + 'abstract',
17 + 'absurd',
18 + 'abuse',
19 + 'access',
20 + 'accident',
21 + 'account',
22 + 'accuse',
23 + 'achieve',
24 + 'acid',
25 + 'acoustic',
26 + 'acquire',
27 + 'across',
28 + 'act',
29 + 'action',
30 + 'actor',
31 + 'actress',
32 + 'actual',
33 + 'adapt',
34 + 'add',
35 + 'addict',
36 + 'address',
37 + 'adjust',
38 + 'admit',
39 + 'adult',
40 + 'advance',
41 + 'advice',
42 + 'aerobic',
43 + 'affair',
44 + 'afford',
45 + 'afraid',
46 + 'again',
47 + 'age',
48 + 'agent',
49 + 'agree',
50 + 'ahead',
51 + 'aim',
52 + 'air',
53 + 'airport',
54 + 'aisle',
55 + 'alarm',
56 + 'album',
57 + 'alcohol',
58 + 'alert',
59 + 'alien',
60 + 'all',
61 + 'alley',
62 + 'allow',
63 + 'almost',
64 + 'alone',
65 + 'alpha',
66 + 'already',
67 + 'also',
68 + 'alter',
69 + 'always',
70 + 'amateur',
71 + 'amazing',
72 + 'among',
73 + 'amount',
74 + 'amused',
75 + 'analyst',
76 + 'anchor',
77 + 'ancient',
78 + 'anger',
79 + 'angle',
80 + 'angry',
81 + 'animal',
82 + 'ankle',
83 + 'announce',
84 + 'annual',
85 + 'another',
86 + 'answer',
87 + 'antenna',
88 + 'antique',
89 + 'anxiety',
90 + 'any',
91 + 'apart',
92 + 'apology',
93 + 'appear',
94 + 'apple',
95 + 'approve',
96 + 'april',
97 + 'arch',
98 + 'arctic',
99 + 'area',
100 + 'arena',
101 + 'argue',
102 + 'arm',
103 + 'armed',
104 + 'armor',
105 + 'army',
106 + 'around',
107 + 'arrange',
108 + 'arrest',
109 + 'arrive',
110 + 'arrow',
111 + 'art',
112 + 'artefact',
113 + 'artist',
114 + 'artwork',
115 + 'ask',
116 + 'aspect',
117 + 'assault',
118 + 'asset',
119 + 'assist',
120 + 'assume',
121 + 'asthma',
122 + 'athlete',
123 + 'atom',
124 + 'attack',
125 + 'attend',
126 + 'attitude',
127 + 'attract',
128 + 'auction',
129 + 'audit',
130 + 'august',
131 + 'aunt',
132 + 'author',
133 + 'auto',
134 + 'autumn',
135 + 'average',
136 + 'avocado',
137 + 'avoid',
138 + 'awake',
139 + 'aware',
140 + 'away',
141 + 'awesome',
142 + 'awful',
143 + 'awkward',
144 + 'axis',
145 + 'baby',
146 + 'bachelor',
147 + 'bacon',
148 + 'badge',
149 + 'bag',
150 + 'balance',
151 + 'balcony',
152 + 'ball',
153 + 'bamboo',
154 + 'banana',
155 + 'banner',
156 + 'bar',
157 + 'barely',
158 + 'bargain',
159 + 'barrel',
160 + 'base',
161 + 'basic',
162 + 'basket',
163 + 'battle',
164 + 'beach',
165 + 'bean',
166 + 'beauty',
167 + 'because',
168 + 'become',
169 + 'beef',
170 + 'before',
171 + 'begin',
172 + 'behave',
173 + 'behind',
174 + 'believe',
175 + 'below',
176 + 'belt',
177 + 'bench',
178 + 'benefit',
179 + 'best',
180 + 'betray',
181 + 'better',
182 + 'between',
183 + 'beyond',
184 + 'bicycle',
185 + 'bid',
186 + 'bike',
187 + 'bind',
188 + 'biology',
189 + 'bird',
190 + 'birth',
191 + 'bitter',
192 + 'black',
193 + 'blade',
194 + 'blame',
195 + 'blanket',
196 + 'blast',
197 + 'bleak',
198 + 'bless',
199 + 'blind',
200 + 'blood',
201 + 'blossom',
202 + 'blouse',
203 + 'blue',
204 + 'blur',
205 + 'blush',
206 + 'board',
207 + 'boat',
208 + 'body',
209 + 'boil',
210 + 'bomb',
211 + 'bone',
212 + 'bonus',
213 + 'book',
214 + 'boost',
215 + 'border',
216 + 'boring',
217 + 'borrow',
218 + 'boss',
219 + 'bottom',
220 + 'bounce',
221 + 'box',
222 + 'boy',
223 + 'bracket',
224 + 'brain',
225 + 'brand',
226 + 'brass',
227 + 'brave',
228 + 'bread',
229 + 'breeze',
230 + 'brick',
231 + 'bridge',
232 + 'brief',
233 + 'bright',
234 + 'bring',
235 + 'brisk',
236 + 'broccoli',
237 + 'broken',
238 + 'bronze',
239 + 'broom',
240 + 'brother',
241 + 'brown',
242 + 'brush',
243 + 'bubble',
244 + 'buddy',
245 + 'budget',
246 + 'buffalo',
247 + 'build',
248 + 'bulb',
249 + 'bulk',
250 + 'bullet',
251 + 'bundle',
252 + 'bunker',
253 + 'burden',
254 + 'burger',
255 + 'burst',
256 + 'bus',
257 + 'business',
258 + 'busy',
259 + 'butter',
260 + 'buyer',
261 + 'buzz',
262 + 'cabbage',
263 + 'cabin',
264 + 'cable',
265 + 'cactus',
266 + 'cage',
267 + 'cake',
268 + 'call',
269 + 'calm',
270 + 'camera',
271 + 'camp',
272 + 'can',
273 + 'canal',
274 + 'cancel',
275 + 'candy',
276 + 'cannon',
277 + 'canoe',
278 + 'canvas',
279 + 'canyon',
280 + 'capable',
281 + 'capital',
282 + 'captain',
283 + 'car',
284 + 'carbon',
285 + 'card',
286 + 'cargo',
287 + 'carpet',
288 + 'carry',
289 + 'cart',
290 + 'case',
291 + 'cash',
292 + 'casino',
293 + 'castle',
294 + 'casual',
295 + 'cat',
296 + 'catalog',
297 + 'catch',
298 + 'category',
299 + 'cattle',
300 + 'caught',
301 + 'cause',
302 + 'caution',
303 + 'cave',
304 + 'ceiling',
305 + 'celery',
306 + 'cement',
307 + 'census',
308 + 'century',
309 + 'cereal',
310 + 'certain',
311 + 'chair',
312 + 'chalk',
313 + 'champion',
314 + 'change',
315 + 'chaos',
316 + 'chapter',
317 + 'charge',
318 + 'chase',
319 + 'chat',
320 + 'cheap',
321 + 'check',
322 + 'cheese',
323 + 'chef',
324 + 'cherry',
325 + 'chest',
326 + 'chicken',
327 + 'chief',
328 + 'child',
329 + 'chimney',
330 + 'choice',
331 + 'choose',
332 + 'chronic',
333 + 'chuckle',
334 + 'chunk',
335 + 'churn',
336 + 'cigar',
337 + 'cinnamon',
338 + 'circle',
339 + 'citizen',
340 + 'city',
341 + 'civil',
342 + 'claim',
343 + 'clap',
344 + 'clarify',
345 + 'claw',
346 + 'clay',
347 + 'clean',
348 + 'clerk',
349 + 'clever',
350 + 'click',
351 + 'client',
352 + 'cliff',
353 + 'climb',
354 + 'clinic',
355 + 'clip',
356 + 'clock',
357 + 'clog',
358 + 'close',
359 + 'cloth',
360 + 'cloud',
361 + 'clown',
362 + 'club',
363 + 'clump',
364 + 'cluster',
365 + 'clutch',
366 + 'coach',
367 + 'coast',
368 + 'coconut',
369 + 'code',
370 + 'coffee',
371 + 'coil',
372 + 'coin',
373 + 'collect',
374 + 'color',
375 + 'column',
376 + 'combine',
377 + 'come',
378 + 'comfort',
379 + 'comic',
380 + 'common',
381 + 'company',
382 + 'concert',
383 + 'conduct',
384 + 'confirm',
385 + 'congress',
386 + 'connect',
387 + 'consider',
388 + 'control',
389 + 'convince',
390 + 'cook',
391 + 'cool',
392 + 'copper',
393 + 'copy',
394 + 'coral',
395 + 'core',
396 + 'corn',
397 + 'correct',
398 + 'cost',
399 + 'cotton',
400 + 'couch',
401 + 'country',
402 + 'couple',
403 + 'course',
404 + 'cousin',
405 + 'cover',
406 + 'coyote',
407 + 'crack',
408 + 'cradle',
409 + 'craft',
410 + 'cram',
411 + 'crane',
412 + 'crash',
413 + 'crater',
414 + 'crawl',
415 + 'crazy',
416 + 'cream',
417 + 'credit',
418 + 'creek',
419 + 'crew',
420 + 'cricket',
421 + 'crime',
422 + 'crisp',
423 + 'critic',
424 + 'crop',
425 + 'cross',
426 + 'crouch',
427 + 'crowd',
428 + 'crucial',
429 + 'cruel',
430 + 'cruise',
431 + 'crumble',
432 + 'crunch',
433 + 'crush',
434 + 'cry',
435 + 'crystal',
436 + 'cube',
437 + 'culture',
438 + 'cup',
439 + 'cupboard',
440 + 'curious',
441 + 'current',
442 + 'curtain',
443 + 'curve',
444 + 'cushion',
445 + 'custom',
446 + 'cute',
447 + 'cycle',
448 + 'dad',
449 + 'damage',
450 + 'damp',
451 + 'dance',
452 + 'danger',
453 + 'daring',
454 + 'dash',
455 + 'daughter',
456 + 'dawn',
457 + 'day',
458 + 'deal',
459 + 'debate',
460 + 'debris',
461 + 'decade',
462 + 'december',
463 + 'decide',
464 + 'decline',
465 + 'decorate',
466 + 'decrease',
467 + 'deer',
468 + 'defense',
469 + 'define',
470 + 'defy',
471 + 'degree',
472 + 'delay',
473 + 'deliver',
474 + 'demand',
475 + 'demise',
476 + 'denial',
477 + 'dentist',
478 + 'deny',
479 + 'depart',
480 + 'depend',
481 + 'deposit',
482 + 'depth',
483 + 'deputy',
484 + 'derive',
485 + 'describe',
486 + 'desert',
487 + 'design',
488 + 'desk',
489 + 'despair',
490 + 'destroy',
491 + 'detail',
492 + 'detect',
493 + 'develop',
494 + 'device',
495 + 'devote',
496 + 'diagram',
497 + 'dial',
498 + 'diamond',
499 + 'diary',
500 + 'dice',
501 + 'diesel',
502 + 'diet',
503 + 'differ',
504 + 'digital',
505 + 'dignity',
506 + 'dilemma',
507 + 'dinner',
508 + 'dinosaur',
509 + 'direct',
510 + 'dirt',
511 + 'disagree',
512 + 'discover',
513 + 'disease',
514 + 'dish',
515 + 'dismiss',
516 + 'disorder',
517 + 'display',
518 + 'distance',
519 + 'divert',
520 + 'divide',
521 + 'divorce',
522 + 'dizzy',
523 + 'doctor',
524 + 'document',
525 + 'dog',
526 + 'doll',
527 + 'dolphin',
528 + 'domain',
529 + 'donate',
530 + 'donkey',
531 + 'donor',
532 + 'door',
533 + 'dose',
534 + 'double',
535 + 'dove',
536 + 'draft',
537 + 'dragon',
538 + 'drama',
539 + 'drastic',
540 + 'draw',
541 + 'dream',
542 + 'dress',
543 + 'drift',
544 + 'drill',
545 + 'drink',
546 + 'drip',
547 + 'drive',
548 + 'drop',
549 + 'drum',
550 + 'dry',
551 + 'duck',
552 + 'dumb',
553 + 'dune',
554 + 'during',
555 + 'dust',
556 + 'dutch',
557 + 'duty',
558 + 'dwarf',
559 + 'dynamic',
560 + 'eager',
561 + 'eagle',
562 + 'early',
563 + 'earn',
564 + 'earth',
565 + 'easily',
566 + 'east',
567 + 'easy',
568 + 'echo',
569 + 'ecology',
570 + 'economy',
571 + 'edge',
572 + 'edit',
573 + 'educate',
574 + 'effort',
575 + 'egg',
576 + 'eight',
577 + 'either',
578 + 'elbow',
579 + 'elder',
580 + 'electric',
581 + 'elegant',
582 + 'element',
583 + 'elephant',
584 + 'elevator',
585 + 'elite',
586 + 'else',
587 + 'embark',
588 + 'embody',
589 + 'embrace',
590 + 'emerge',
591 + 'emotion',
592 + 'employ',
593 + 'empower',
594 + 'empty',
595 + 'enable',
596 + 'enact',
597 + 'end',
598 + 'endless',
599 + 'endorse',
600 + 'enemy',
601 + 'energy',
602 + 'enforce',
603 + 'engage',
604 + 'engine',
605 + 'enhance',
606 + 'enjoy',
607 + 'enlist',
608 + 'enough',
609 + 'enrich',
610 + 'enroll',
611 + 'ensure',
612 + 'enter',
613 + 'entire',
614 + 'entry',
615 + 'envelope',
616 + 'episode',
617 + 'equal',
618 + 'equip',
619 + 'era',
620 + 'erase',
621 + 'erode',
622 + 'erosion',
623 + 'error',
624 + 'erupt',
625 + 'escape',
626 + 'essay',
627 + 'essence',
628 + 'estate',
629 + 'eternal',
630 + 'ethics',
631 + 'evidence',
632 + 'evil',
633 + 'evoke',
634 + 'evolve',
635 + 'exact',
636 + 'example',
637 + 'excess',
638 + 'exchange',
639 + 'excite',
640 + 'exclude',
641 + 'excuse',
642 + 'execute',
643 + 'exercise',
644 + 'exhaust',
645 + 'exhibit',
646 + 'exile',
647 + 'exist',
648 + 'exit',
649 + 'exotic',
650 + 'expand',
651 + 'expect',
652 + 'expire',
653 + 'explain',
654 + 'expose',
655 + 'express',
656 + 'extend',
657 + 'extra',
658 + 'eye',
659 + 'eyebrow',
660 + 'fabric',
661 + 'face',
662 + 'faculty',
663 + 'fade',
664 + 'faint',
665 + 'faith',
666 + 'fall',
667 + 'false',
668 + 'fame',
669 + 'family',
670 + 'famous',
671 + 'fan',
672 + 'fancy',
673 + 'fantasy',
674 + 'farm',
675 + 'fashion',
676 + 'fat',
677 + 'fatal',
678 + 'father',
679 + 'fatigue',
680 + 'fault',
681 + 'favorite',
682 + 'feature',
683 + 'february',
684 + 'federal',
685 + 'fee',
686 + 'feed',
687 + 'feel',
688 + 'female',
689 + 'fence',
690 + 'festival',
691 + 'fetch',
692 + 'fever',
693 + 'few',
694 + 'fiber',
695 + 'fiction',
696 + 'field',
697 + 'figure',
698 + 'file',
699 + 'film',
700 + 'filter',
701 + 'final',
702 + 'find',
703 + 'fine',
704 + 'finger',
705 + 'finish',
706 + 'fire',
707 + 'firm',
708 + 'first',
709 + 'fiscal',
710 + 'fish',
711 + 'fit',
712 + 'fitness',
713 + 'fix',
714 + 'flag',
715 + 'flame',
716 + 'flash',
717 + 'flat',
718 + 'flavor',
719 + 'flee',
720 + 'flight',
721 + 'flip',
722 + 'float',
723 + 'flock',
724 + 'floor',
725 + 'flower',
726 + 'fluid',
727 + 'flush',
728 + 'fly',
729 + 'foam',
730 + 'focus',
731 + 'fog',
732 + 'foil',
733 + 'fold',
734 + 'follow',
735 + 'food',
736 + 'foot',
737 + 'force',
738 + 'forest',
739 + 'forget',
740 + 'fork',
741 + 'fortune',
742 + 'forum',
743 + 'forward',
744 + 'fossil',
745 + 'foster',
746 + 'found',
747 + 'fox',
748 + 'fragile',
749 + 'frame',
750 + 'frequent',
751 + 'fresh',
752 + 'friend',
753 + 'fringe',
754 + 'frog',
755 + 'front',
756 + 'frost',
757 + 'frown',
758 + 'frozen',
759 + 'fruit',
760 + 'fuel',
761 + 'fun',
762 + 'funny',
763 + 'furnace',
764 + 'fury',
765 + 'future',
766 + 'gadget',
767 + 'gain',
768 + 'galaxy',
769 + 'gallery',
770 + 'game',
771 + 'gap',
772 + 'garage',
773 + 'garbage',
774 + 'garden',
775 + 'garlic',
776 + 'garment',
777 + 'gas',
778 + 'gasp',
779 + 'gate',
780 + 'gather',
781 + 'gauge',
782 + 'gaze',
783 + 'general',
784 + 'genius',
785 + 'genre',
786 + 'gentle',
787 + 'genuine',
788 + 'gesture',
789 + 'ghost',
790 + 'giant',
791 + 'gift',
792 + 'giggle',
793 + 'ginger',
794 + 'giraffe',
795 + 'girl',
796 + 'give',
797 + 'glad',
798 + 'glance',
799 + 'glare',
800 + 'glass',
801 + 'glide',
802 + 'glimpse',
803 + 'globe',
804 + 'gloom',
805 + 'glory',
806 + 'glove',
807 + 'glow',
808 + 'glue',
809 + 'goat',
810 + 'goddess',
811 + 'gold',
812 + 'good',
813 + 'goose',
814 + 'gorilla',
815 + 'gospel',
816 + 'gossip',
817 + 'govern',
818 + 'gown',
819 + 'grab',
820 + 'grace',
821 + 'grain',
822 + 'grant',
823 + 'grape',
824 + 'grass',
825 + 'gravity',
826 + 'great',
827 + 'green',
828 + 'grid',
829 + 'grief',
830 + 'grit',
831 + 'grocery',
832 + 'group',
833 + 'grow',
834 + 'grunt',
835 + 'guard',
836 + 'guess',
837 + 'guide',
838 + 'guilt',
839 + 'guitar',
840 + 'gun',
841 + 'gym',
842 + 'habit',
843 + 'hair',
844 + 'half',
845 + 'hammer',
846 + 'hamster',
847 + 'hand',
848 + 'happy',
849 + 'harbor',
850 + 'hard',
851 + 'harsh',
852 + 'harvest',
853 + 'hat',
854 + 'have',
855 + 'hawk',
856 + 'hazard',
857 + 'head',
858 + 'health',
859 + 'heart',
860 + 'heavy',
861 + 'hedgehog',
862 + 'height',
863 + 'hello',
864 + 'helmet',
865 + 'help',
866 + 'hen',
867 + 'hero',
868 + 'hidden',
869 + 'high',
870 + 'hill',
871 + 'hint',
872 + 'hip',
873 + 'hire',
874 + 'history',
875 + 'hobby',
876 + 'hockey',
877 + 'hold',
878 + 'hole',
879 + 'holiday',
880 + 'hollow',
881 + 'home',
882 + 'honey',
883 + 'hood',
884 + 'hope',
885 + 'horn',
886 + 'horror',
887 + 'horse',
888 + 'hospital',
889 + 'host',
890 + 'hotel',
891 + 'hour',
892 + 'hover',
893 + 'hub',
894 + 'huge',
895 + 'human',
896 + 'humble',
897 + 'humor',
898 + 'hundred',
899 + 'hungry',
900 + 'hunt',
901 + 'hurdle',
902 + 'hurry',
903 + 'hurt',
904 + 'husband',
905 + 'hybrid',
906 + 'ice',
907 + 'icon',
908 + 'idea',
909 + 'identify',
910 + 'idle',
911 + 'ignore',
912 + 'ill',
913 + 'illegal',
914 + 'illness',
915 + 'image',
916 + 'imitate',
917 + 'immense',
918 + 'immune',
919 + 'impact',
920 + 'impose',
921 + 'improve',
922 + 'impulse',
923 + 'inch',
924 + 'include',
925 + 'income',
926 + 'increase',
927 + 'index',
928 + 'indicate',
929 + 'indoor',
930 + 'industry',
931 + 'infant',
932 + 'inflict',
933 + 'inform',
934 + 'inhale',
935 + 'inherit',
936 + 'initial',
937 + 'inject',
938 + 'injury',
939 + 'inmate',
940 + 'inner',
941 + 'innocent',
942 + 'input',
943 + 'inquiry',
944 + 'insane',
945 + 'insect',
946 + 'inside',
947 + 'inspire',
948 + 'install',
949 + 'intact',
950 + 'interest',
951 + 'into',
952 + 'invest',
953 + 'invite',
954 + 'involve',
955 + 'iron',
956 + 'island',
957 + 'isolate',
958 + 'issue',
959 + 'item',
960 + 'ivory',
961 + 'jacket',
962 + 'jaguar',
963 + 'jar',
964 + 'jazz',
965 + 'jealous',
966 + 'jeans',
967 + 'jelly',
968 + 'jewel',
969 + 'job',
970 + 'join',
971 + 'joke',
972 + 'journey',
973 + 'joy',
974 + 'judge',
975 + 'juice',
976 + 'jump',
977 + 'jungle',
978 + 'junior',
979 + 'junk',
980 + 'just',
981 + 'kangaroo',
982 + 'keen',
983 + 'keep',
984 + 'ketchup',
985 + 'key',
986 + 'kick',
987 + 'kid',
988 + 'kidney',
989 + 'kind',
990 + 'kingdom',
991 + 'kiss',
992 + 'kit',
993 + 'kitchen',
994 + 'kite',
995 + 'kitten',
996 + 'kiwi',
997 + 'knee',
998 + 'knife',
999 + 'knock',
1000 + 'know',
1001 + 'lab',
1002 + 'label',
1003 + 'labor',
1004 + 'ladder',
1005 + 'lady',
1006 + 'lake',
1007 + 'lamp',
1008 + 'language',
1009 + 'laptop',
1010 + 'large',
1011 + 'later',
1012 + 'latin',
1013 + 'laugh',
1014 + 'laundry',
1015 + 'lava',
1016 + 'law',
1017 + 'lawn',
1018 + 'lawsuit',
1019 + 'layer',
1020 + 'lazy',
1021 + 'leader',
1022 + 'leaf',
1023 + 'learn',
1024 + 'leave',
1025 + 'lecture',
1026 + 'left',
1027 + 'leg',
1028 + 'legal',
1029 + 'legend',
1030 + 'leisure',
1031 + 'lemon',
1032 + 'lend',
1033 + 'length',
1034 + 'lens',
1035 + 'leopard',
1036 + 'lesson',
1037 + 'letter',
1038 + 'level',
1039 + 'liar',
1040 + 'liberty',
1041 + 'library',
1042 + 'license',
1043 + 'life',
1044 + 'lift',
1045 + 'light',
1046 + 'like',
1047 + 'limb',
1048 + 'limit',
1049 + 'link',
1050 + 'lion',
1051 + 'liquid',
1052 + 'list',
1053 + 'little',
1054 + 'live',
1055 + 'lizard',
1056 + 'load',
1057 + 'loan',
1058 + 'lobster',
1059 + 'local',
1060 + 'lock',
1061 + 'logic',
1062 + 'lonely',
1063 + 'long',
1064 + 'loop',
1065 + 'lottery',
1066 + 'loud',
1067 + 'lounge',
1068 + 'love',
1069 + 'loyal',
1070 + 'lucky',
1071 + 'luggage',
1072 + 'lumber',
1073 + 'lunar',
1074 + 'lunch',
1075 + 'luxury',
1076 + 'lyrics',
1077 + 'machine',
1078 + 'mad',
1079 + 'magic',
1080 + 'magnet',
1081 + 'maid',
1082 + 'mail',
1083 + 'main',
1084 + 'major',
1085 + 'make',
1086 + 'mammal',
1087 + 'man',
1088 + 'manage',
1089 + 'mandate',
1090 + 'mango',
1091 + 'mansion',
1092 + 'manual',
1093 + 'maple',
1094 + 'marble',
1095 + 'march',
1096 + 'margin',
1097 + 'marine',
1098 + 'market',
1099 + 'marriage',
1100 + 'mask',
1101 + 'mass',
1102 + 'master',
1103 + 'match',
1104 + 'material',
1105 + 'math',
1106 + 'matrix',
1107 + 'matter',
1108 + 'maximum',
1109 + 'maze',
1110 + 'meadow',
1111 + 'mean',
1112 + 'measure',
1113 + 'meat',
1114 + 'mechanic',
1115 + 'medal',
1116 + 'media',
1117 + 'melody',
1118 + 'melt',
1119 + 'member',
1120 + 'memory',
1121 + 'mention',
1122 + 'menu',
1123 + 'mercy',
1124 + 'merge',
1125 + 'merit',
1126 + 'merry',
1127 + 'mesh',
1128 + 'message',
1129 + 'metal',
1130 + 'method',
1131 + 'middle',
1132 + 'midnight',
1133 + 'milk',
1134 + 'million',
1135 + 'mimic',
1136 + 'mind',
1137 + 'minimum',
1138 + 'minor',
1139 + 'minute',
1140 + 'miracle',
1141 + 'mirror',
1142 + 'misery',
1143 + 'miss',
1144 + 'mistake',
1145 + 'mix',
1146 + 'mixed',
1147 + 'mixture',
1148 + 'mobile',
1149 + 'model',
1150 + 'modify',
1151 + 'mom',
1152 + 'moment',
1153 + 'monitor',
1154 + 'monkey',
1155 + 'monster',
1156 + 'month',
1157 + 'moon',
1158 + 'moral',
1159 + 'more',
1160 + 'morning',
1161 + 'mosquito',
1162 + 'mother',
1163 + 'motion',
1164 + 'motor',
1165 + 'mountain',
1166 + 'mouse',
1167 + 'move',
1168 + 'movie',
1169 + 'much',
1170 + 'muffin',
1171 + 'mule',
1172 + 'multiply',
1173 + 'muscle',
1174 + 'museum',
1175 + 'mushroom',
1176 + 'music',
1177 + 'must',
1178 + 'mutual',
1179 + 'myself',
1180 + 'mystery',
1181 + 'myth',
1182 + 'naive',
1183 + 'name',
1184 + 'napkin',
1185 + 'narrow',
1186 + 'nasty',
1187 + 'nation',
1188 + 'nature',
1189 + 'near',
1190 + 'neck',
1191 + 'need',
1192 + 'negative',
1193 + 'neglect',
1194 + 'neither',
1195 + 'nephew',
1196 + 'nerve',
1197 + 'nest',
1198 + 'net',
1199 + 'network',
1200 + 'neutral',
1201 + 'never',
1202 + 'news',
1203 + 'next',
1204 + 'nice',
1205 + 'night',
1206 + 'noble',
1207 + 'noise',
1208 + 'nominee',
1209 + 'noodle',
1210 + 'normal',
1211 + 'north',
1212 + 'nose',
1213 + 'notable',
1214 + 'note',
1215 + 'nothing',
1216 + 'notice',
1217 + 'novel',
1218 + 'now',
1219 + 'nuclear',
1220 + 'number',
1221 + 'nurse',
1222 + 'nut',
1223 + 'oak',
1224 + 'obey',
1225 + 'object',
1226 + 'oblige',
1227 + 'obscure',
1228 + 'observe',
1229 + 'obtain',
1230 + 'obvious',
1231 + 'occur',
1232 + 'ocean',
1233 + 'october',
1234 + 'odor',
1235 + 'off',
1236 + 'offer',
1237 + 'office',
1238 + 'often',
1239 + 'oil',
1240 + 'okay',
1241 + 'old',
1242 + 'olive',
1243 + 'olympic',
1244 + 'omit',
1245 + 'once',
1246 + 'one',
1247 + 'onion',
1248 + 'online',
1249 + 'only',
1250 + 'open',
1251 + 'opera',
1252 + 'opinion',
1253 + 'oppose',
1254 + 'option',
1255 + 'orange',
1256 + 'orbit',
1257 + 'orchard',
1258 + 'order',
1259 + 'ordinary',
1260 + 'organ',
1261 + 'orient',
1262 + 'original',
1263 + 'orphan',
1264 + 'ostrich',
1265 + 'other',
1266 + 'outdoor',
1267 + 'outer',
1268 + 'output',
1269 + 'outside',
1270 + 'oval',
1271 + 'oven',
1272 + 'over',
1273 + 'own',
1274 + 'owner',
1275 + 'oxygen',
1276 + 'oyster',
1277 + 'ozone',
1278 + 'pact',
1279 + 'paddle',
1280 + 'page',
1281 + 'pair',
1282 + 'palace',
1283 + 'palm',
1284 + 'panda',
1285 + 'panel',
1286 + 'panic',
1287 + 'panther',
1288 + 'paper',
1289 + 'parade',
1290 + 'parent',
1291 + 'park',
1292 + 'parrot',
1293 + 'party',
1294 + 'pass',
1295 + 'patch',
1296 + 'path',
1297 + 'patient',
1298 + 'patrol',
1299 + 'pattern',
1300 + 'pause',
1301 + 'pave',
1302 + 'payment',
1303 + 'peace',
1304 + 'peanut',
1305 + 'pear',
1306 + 'peasant',
1307 + 'pelican',
1308 + 'pen',
1309 + 'penalty',
1310 + 'pencil',
1311 + 'people',
1312 + 'pepper',
1313 + 'perfect',
1314 + 'permit',
1315 + 'person',
1316 + 'pet',
1317 + 'phone',
1318 + 'photo',
1319 + 'phrase',
1320 + 'physical',
1321 + 'piano',
1322 + 'picnic',
1323 + 'picture',
1324 + 'piece',
1325 + 'pig',
1326 + 'pigeon',
1327 + 'pill',
1328 + 'pilot',
1329 + 'pink',
1330 + 'pioneer',
1331 + 'pipe',
1332 + 'pistol',
1333 + 'pitch',
1334 + 'pizza',
1335 + 'place',
1336 + 'planet',
1337 + 'plastic',
1338 + 'plate',
1339 + 'play',
1340 + 'please',
1341 + 'pledge',
1342 + 'pluck',
1343 + 'plug',
1344 + 'plunge',
1345 + 'poem',
1346 + 'poet',
1347 + 'point',
1348 + 'polar',
1349 + 'pole',
1350 + 'police',
1351 + 'pond',
1352 + 'pony',
1353 + 'pool',
1354 + 'popular',
1355 + 'portion',
1356 + 'position',
1357 + 'possible',
1358 + 'post',
1359 + 'potato',
1360 + 'pottery',
1361 + 'poverty',
1362 + 'powder',
1363 + 'power',
1364 + 'practice',
1365 + 'praise',
1366 + 'predict',
1367 + 'prefer',
1368 + 'prepare',
1369 + 'present',
1370 + 'pretty',
1371 + 'prevent',
1372 + 'price',
1373 + 'pride',
1374 + 'primary',
1375 + 'print',
1376 + 'priority',
1377 + 'prison',
1378 + 'private',
1379 + 'prize',
1380 + 'problem',
1381 + 'process',
1382 + 'produce',
1383 + 'profit',
1384 + 'program',
1385 + 'project',
1386 + 'promote',
1387 + 'proof',
1388 + 'property',
1389 + 'prosper',
1390 + 'protect',
1391 + 'proud',
1392 + 'provide',
1393 + 'public',
1394 + 'pudding',
1395 + 'pull',
1396 + 'pulp',
1397 + 'pulse',
1398 + 'pumpkin',
1399 + 'punch',
1400 + 'pupil',
1401 + 'puppy',
1402 + 'purchase',
1403 + 'purity',
1404 + 'purpose',
1405 + 'purse',
1406 + 'push',
1407 + 'put',
1408 + 'puzzle',
1409 + 'pyramid',
1410 + 'quality',
1411 + 'quantum',
1412 + 'quarter',
1413 + 'question',
1414 + 'quick',
1415 + 'quit',
1416 + 'quiz',
1417 + 'quote',
1418 + 'rabbit',
1419 + 'raccoon',
1420 + 'race',
1421 + 'rack',
1422 + 'radar',
1423 + 'radio',
1424 + 'rail',
1425 + 'rain',
1426 + 'raise',
1427 + 'rally',
1428 + 'ramp',
1429 + 'ranch',
1430 + 'random',
1431 + 'range',
1432 + 'rapid',
1433 + 'rare',
1434 + 'rate',
1435 + 'rather',
1436 + 'raven',
1437 + 'raw',
1438 + 'razor',
1439 + 'ready',
1440 + 'real',
1441 + 'reason',
1442 + 'rebel',
1443 + 'rebuild',
1444 + 'recall',
1445 + 'receive',
1446 + 'recipe',
1447 + 'record',
1448 + 'recycle',
1449 + 'reduce',
1450 + 'reflect',
1451 + 'reform',
1452 + 'refuse',
1453 + 'region',
1454 + 'regret',
1455 + 'regular',
1456 + 'reject',
1457 + 'relax',
1458 + 'release',
1459 + 'relief',
1460 + 'rely',
1461 + 'remain',
1462 + 'remember',
1463 + 'remind',
1464 + 'remove',
1465 + 'render',
1466 + 'renew',
1467 + 'rent',
1468 + 'reopen',
1469 + 'repair',
1470 + 'repeat',
1471 + 'replace',
1472 + 'report',
1473 + 'require',
1474 + 'rescue',
1475 + 'resemble',
1476 + 'resist',
1477 + 'resource',
1478 + 'response',
1479 + 'result',
1480 + 'retire',
1481 + 'retreat',
1482 + 'return',
1483 + 'reunion',
1484 + 'reveal',
1485 + 'review',
1486 + 'reward',
1487 + 'rhythm',
1488 + 'rib',
1489 + 'ribbon',
1490 + 'rice',
1491 + 'rich',
1492 + 'ride',
1493 + 'ridge',
1494 + 'rifle',
1495 + 'right',
1496 + 'rigid',
1497 + 'ring',
1498 + 'riot',
1499 + 'ripple',
1500 + 'risk',
1501 + 'ritual',
1502 + 'rival',
1503 + 'river',
1504 + 'road',
1505 + 'roast',
1506 + 'robot',
1507 + 'robust',
1508 + 'rocket',
1509 + 'romance',
1510 + 'roof',
1511 + 'rookie',
1512 + 'room',
1513 + 'rose',
1514 + 'rotate',
1515 + 'rough',
1516 + 'round',
1517 + 'route',
1518 + 'royal',
1519 + 'rubber',
1520 + 'rude',
1521 + 'rug',
1522 + 'rule',
1523 + 'run',
1524 + 'runway',
1525 + 'rural',
1526 + 'sad',
1527 + 'saddle',
1528 + 'sadness',
1529 + 'safe',
1530 + 'sail',
1531 + 'salad',
1532 + 'salmon',
1533 + 'salon',
1534 + 'salt',
1535 + 'salute',
1536 + 'same',
1537 + 'sample',
1538 + 'sand',
1539 + 'satisfy',
1540 + 'satoshi',
1541 + 'sauce',
1542 + 'sausage',
1543 + 'save',
1544 + 'say',
1545 + 'scale',
1546 + 'scan',
1547 + 'scare',
1548 + 'scatter',
1549 + 'scene',
1550 + 'scheme',
1551 + 'school',
1552 + 'science',
1553 + 'scissors',
1554 + 'scorpion',
1555 + 'scout',
1556 + 'scrap',
1557 + 'screen',
1558 + 'script',
1559 + 'scrub',
1560 + 'sea',
1561 + 'search',
1562 + 'season',
1563 + 'seat',
1564 + 'second',
1565 + 'secret',
1566 + 'section',
1567 + 'security',
1568 + 'seed',
1569 + 'seek',
1570 + 'segment',
1571 + 'select',
1572 + 'sell',
1573 + 'seminar',
1574 + 'senior',
1575 + 'sense',
1576 + 'sentence',
1577 + 'series',
1578 + 'service',
1579 + 'session',
1580 + 'settle',
1581 + 'setup',
1582 + 'seven',
1583 + 'shadow',
1584 + 'shaft',
1585 + 'shallow',
1586 + 'share',
1587 + 'shed',
1588 + 'shell',
1589 + 'sheriff',
1590 + 'shield',
1591 + 'shift',
1592 + 'shine',
1593 + 'ship',
1594 + 'shiver',
1595 + 'shock',
1596 + 'shoe',
1597 + 'shoot',
1598 + 'shop',
1599 + 'short',
1600 + 'shoulder',
1601 + 'shove',
1602 + 'shrimp',
1603 + 'shrug',
1604 + 'shuffle',
1605 + 'shy',
1606 + 'sibling',
1607 + 'sick',
1608 + 'side',
1609 + 'siege',
1610 + 'sight',
1611 + 'sign',
1612 + 'silent',
1613 + 'silk',
1614 + 'silly',
1615 + 'silver',
1616 + 'similar',
1617 + 'simple',
1618 + 'since',
1619 + 'sing',
1620 + 'siren',
1621 + 'sister',
1622 + 'situate',
1623 + 'six',
1624 + 'size',
1625 + 'skate',
1626 + 'sketch',
1627 + 'ski',
1628 + 'skill',
1629 + 'skin',
1630 + 'skirt',
1631 + 'skull',
1632 + 'slab',
1633 + 'slam',
1634 + 'sleep',
1635 + 'slender',
1636 + 'slice',
1637 + 'slide',
1638 + 'slight',
1639 + 'slim',
1640 + 'slogan',
1641 + 'slot',
1642 + 'slow',
1643 + 'slush',
1644 + 'small',
1645 + 'smart',
1646 + 'smile',
1647 + 'smoke',
1648 + 'smooth',
1649 + 'snack',
1650 + 'snake',
1651 + 'snap',
1652 + 'sniff',
1653 + 'snow',
1654 + 'soap',
1655 + 'soccer',
1656 + 'social',
1657 + 'sock',
1658 + 'soda',
1659 + 'soft',
1660 + 'solar',
1661 + 'soldier',
1662 + 'solid',
1663 + 'solution',
1664 + 'solve',
1665 + 'someone',
1666 + 'song',
1667 + 'soon',
1668 + 'sorry',
1669 + 'sort',
1670 + 'soul',
1671 + 'sound',
1672 + 'soup',
1673 + 'source',
1674 + 'south',
1675 + 'space',
1676 + 'spare',
1677 + 'spatial',
1678 + 'spawn',
1679 + 'speak',
1680 + 'special',
1681 + 'speed',
1682 + 'spell',
1683 + 'spend',
1684 + 'sphere',
1685 + 'spice',
1686 + 'spider',
1687 + 'spike',
1688 + 'spin',
1689 + 'spirit',
1690 + 'split',
1691 + 'spoil',
1692 + 'sponsor',
1693 + 'spoon',
1694 + 'sport',
1695 + 'spot',
1696 + 'spray',
1697 + 'spread',
1698 + 'spring',
1699 + 'spy',
1700 + 'square',
1701 + 'squeeze',
1702 + 'squirrel',
1703 + 'stable',
1704 + 'stadium',
1705 + 'staff',
1706 + 'stage',
1707 + 'stairs',
1708 + 'stamp',
1709 + 'stand',
1710 + 'start',
1711 + 'state',
1712 + 'stay',
1713 + 'steak',
1714 + 'steel',
1715 + 'stem',
1716 + 'step',
1717 + 'stereo',
1718 + 'stick',
1719 + 'still',
1720 + 'sting',
1721 + 'stock',
1722 + 'stomach',
1723 + 'stone',
1724 + 'stool',
1725 + 'story',
1726 + 'stove',
1727 + 'strategy',
1728 + 'street',
1729 + 'strike',
1730 + 'strong',
1731 + 'struggle',
1732 + 'student',
1733 + 'stuff',
1734 + 'stumble',
1735 + 'style',
1736 + 'subject',
1737 + 'submit',
1738 + 'subway',
1739 + 'success',
1740 + 'such',
1741 + 'sudden',
1742 + 'suffer',
1743 + 'sugar',
1744 + 'suggest',
1745 + 'suit',
1746 + 'summer',
1747 + 'sun',
1748 + 'sunny',
1749 + 'sunset',
1750 + 'super',
1751 + 'supply',
1752 + 'supreme',
1753 + 'sure',
1754 + 'surface',
1755 + 'surge',
1756 + 'surprise',
1757 + 'surround',
1758 + 'survey',
1759 + 'suspect',
1760 + 'sustain',
1761 + 'swallow',
1762 + 'swamp',
1763 + 'swap',
1764 + 'swarm',
1765 + 'swear',
1766 + 'sweet',
1767 + 'swift',
1768 + 'swim',
1769 + 'swing',
1770 + 'switch',
1771 + 'sword',
1772 + 'symbol',
1773 + 'symptom',
1774 + 'syrup',
1775 + 'system',
1776 + 'table',
1777 + 'tackle',
1778 + 'tag',
1779 + 'tail',
1780 + 'talent',
1781 + 'talk',
1782 + 'tank',
1783 + 'tape',
1784 + 'target',
1785 + 'task',
1786 + 'taste',
1787 + 'tattoo',
1788 + 'taxi',
1789 + 'teach',
1790 + 'team',
1791 + 'tell',
1792 + 'ten',
1793 + 'tenant',
1794 + 'tennis',
1795 + 'tent',
1796 + 'term',
1797 + 'test',
1798 + 'text',
1799 + 'thank',
1800 + 'that',
1801 + 'theme',
1802 + 'then',
1803 + 'theory',
1804 + 'there',
1805 + 'they',
1806 + 'thing',
1807 + 'this',
1808 + 'thought',
1809 + 'three',
1810 + 'thrive',
1811 + 'throw',
1812 + 'thumb',
1813 + 'thunder',
1814 + 'ticket',
1815 + 'tide',
1816 + 'tiger',
1817 + 'tilt',
1818 + 'timber',
1819 + 'time',
1820 + 'tiny',
1821 + 'tip',
1822 + 'tired',
1823 + 'tissue',
1824 + 'title',
1825 + 'toast',
1826 + 'tobacco',
1827 + 'today',
1828 + 'toddler',
1829 + 'toe',
1830 + 'together',
1831 + 'toilet',
1832 + 'token',
1833 + 'tomato',
1834 + 'tomorrow',
1835 + 'tone',
1836 + 'tongue',
1837 + 'tonight',
1838 + 'tool',
1839 + 'tooth',
1840 + 'top',
1841 + 'topic',
1842 + 'topple',
1843 + 'torch',
1844 + 'tornado',
1845 + 'tortoise',
1846 + 'toss',
1847 + 'total',
1848 + 'tourist',
1849 + 'toward',
1850 + 'tower',
1851 + 'town',
1852 + 'toy',
1853 + 'track',
1854 + 'trade',
1855 + 'traffic',
1856 + 'tragic',
1857 + 'train',
1858 + 'transfer',
1859 + 'trap',
1860 + 'trash',
1861 + 'travel',
1862 + 'tray',
1863 + 'treat',
1864 + 'tree',
1865 + 'trend',
1866 + 'trial',
1867 + 'tribe',
1868 + 'trick',
1869 + 'trigger',
1870 + 'trim',
1871 + 'trip',
1872 + 'trophy',
1873 + 'trouble',
1874 + 'truck',
1875 + 'true',
1876 + 'truly',
1877 + 'trumpet',
1878 + 'trust',
1879 + 'truth',
1880 + 'try',
1881 + 'tube',
1882 + 'tuition',
1883 + 'tumble',
1884 + 'tuna',
1885 + 'tunnel',
1886 + 'turkey',
1887 + 'turn',
1888 + 'turtle',
1889 + 'twelve',
1890 + 'twenty',
1891 + 'twice',
1892 + 'twin',
1893 + 'twist',
1894 + 'two',
1895 + 'type',
1896 + 'typical',
1897 + 'ugly',
1898 + 'umbrella',
1899 + 'unable',
1900 + 'unaware',
1901 + 'uncle',
1902 + 'uncover',
1903 + 'under',
1904 + 'undo',
1905 + 'unfair',
1906 + 'unfold',
1907 + 'unhappy',
1908 + 'uniform',
1909 + 'unique',
1910 + 'unit',
1911 + 'universe',
1912 + 'unknown',
1913 + 'unlock',
1914 + 'until',
1915 + 'unusual',
1916 + 'unveil',
1917 + 'update',
1918 + 'upgrade',
1919 + 'uphold',
1920 + 'upon',
1921 + 'upper',
1922 + 'upset',
1923 + 'urban',
1924 + 'urge',
1925 + 'usage',
1926 + 'use',
1927 + 'used',
1928 + 'useful',
1929 + 'useless',
1930 + 'usual',
1931 + 'utility',
1932 + 'vacant',
1933 + 'vacuum',
1934 + 'vague',
1935 + 'valid',
1936 + 'valley',
1937 + 'valve',
1938 + 'van',
1939 + 'vanish',
1940 + 'vapor',
1941 + 'various',
1942 + 'vast',
1943 + 'vault',
1944 + 'vehicle',
1945 + 'velvet',
1946 + 'vendor',
1947 + 'venture',
1948 + 'venue',
1949 + 'verb',
1950 + 'verify',
1951 + 'version',
1952 + 'very',
1953 + 'vessel',
1954 + 'veteran',
1955 + 'viable',
1956 + 'vibrant',
1957 + 'vicious',
1958 + 'victory',
1959 + 'video',
1960 + 'view',
1961 + 'village',
1962 + 'vintage',
1963 + 'violin',
1964 + 'virtual',
1965 + 'virus',
1966 + 'visa',
1967 + 'visit',
1968 + 'visual',
1969 + 'vital',
1970 + 'vivid',
1971 + 'vocal',
1972 + 'voice',
1973 + 'void',
1974 + 'volcano',
1975 + 'volume',
1976 + 'vote',
1977 + 'voyage',
1978 + 'wage',
1979 + 'wagon',
1980 + 'wait',
1981 + 'walk',
1982 + 'wall',
1983 + 'walnut',
1984 + 'want',
1985 + 'warfare',
1986 + 'warm',
1987 + 'warrior',
1988 + 'wash',
1989 + 'wasp',
1990 + 'waste',
1991 + 'water',
1992 + 'wave',
1993 + 'way',
1994 + 'wealth',
1995 + 'weapon',
1996 + 'wear',
1997 + 'weasel',
1998 + 'weather',
1999 + 'web',
2000 + 'wedding',
2001 + 'weekend',
2002 + 'weird',
2003 + 'welcome',
2004 + 'west',
2005 + 'wet',
2006 + 'whale',
2007 + 'what',
2008 + 'wheat',
2009 + 'wheel',
2010 + 'when',
2011 + 'where',
2012 + 'whip',
2013 + 'whisper',
2014 + 'wide',
2015 + 'width',
2016 + 'wife',
2017 + 'wild',
2018 + 'will',
2019 + 'win',
2020 + 'window',
2021 + 'wine',
2022 + 'wing',
2023 + 'wink',
2024 + 'winner',
2025 + 'winter',
2026 + 'wire',
2027 + 'wisdom',
2028 + 'wise',
2029 + 'wish',
2030 + 'witness',
2031 + 'wolf',
2032 + 'woman',
2033 + 'wonder',
2034 + 'wood',
2035 + 'wool',
2036 + 'word',
2037 + 'work',
2038 + 'world',
2039 + 'worry',
2040 + 'worth',
2041 + 'wrap',
2042 + 'wreck',
2043 + 'wrestle',
2044 + 'wrist',
2045 + 'write',
2046 + 'wrong',
2047 + 'yard',
2048 + 'year',
2049 + 'yellow',
2050 + 'you',
2051 + 'young',
2052 + 'youth',
2053 + 'zebra',
2054 + 'zero',
2055 + 'zone',
2056 + 'zoo'
2057 + ];
2058 +}
cw_ethereum/lib/ethereum_transaction_credentials.dart new
+17
@@ -0,0 +1,17 @@
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';
4 +
5 +class EthereumTransactionCredentials {
6 + EthereumTransactionCredentials(
7 + this.outputs, {
8 + required this.priority,
9 + required this.currency,
10 + this.feeRate,
11 + });
12 +
13 + final List<OutputInfo> outputs;
14 + final EthereumTransactionPriority? priority;
15 + final int? feeRate;
16 + final CryptoCurrency currency;
17 +}
cw_ethereum/lib/ethereum_transaction_history.dart new
+77
@@ -0,0 +1,77 @@
1 +import 'dart:convert';
2 +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';
8 +import 'package:cw_ethereum/ethereum_transaction_info.dart';
9 +
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 + }
40 +
41 + @override
42 + void addOne(EthereumTransactionInfo transaction) => transactions[transaction.id] = transaction;
43 +
44 + @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;
77 +}
cw_ethereum/lib/ethereum_transaction_info.dart new
+74
@@ -0,0 +1,74 @@
1 +import 'package:cw_core/format_amount.dart';
2 +import 'package:cw_core/transaction_direction.dart';
3 +import 'package:cw_core/transaction_info.dart';
4 +
5 +class EthereumTransactionInfo extends TransactionInfo {
6 + EthereumTransactionInfo({
7 + required this.id,
8 + required this.height,
9 + required this.ethAmount,
10 + required this.ethFee,
11 + this.tokenSymbol = "ETH",
12 + this.exponent = 18,
13 + required this.direction,
14 + required this.isPending,
15 + required this.date,
16 + required this.confirmations,
17 + }) : this.amount = ethAmount.toInt(),
18 + this.fee = ethFee.toInt();
19 +
20 + final String id;
21 + final int height;
22 + final int amount;
23 + final BigInt ethAmount;
24 + final int exponent;
25 + final TransactionDirection direction;
26 + final DateTime date;
27 + final bool isPending;
28 + final int fee;
29 + final BigInt ethFee;
30 + final int confirmations;
31 + final String tokenSymbol;
32 + String? _fiatAmount;
33 +
34 + @override
35 + String amountFormatted() =>
36 + '${formatAmount((ethAmount / BigInt.from(10).pow(exponent)).toString())} $tokenSymbol';
37 +
38 + @override
39 + String fiatAmount() => _fiatAmount ?? '';
40 +
41 + @override
42 + void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
43 +
44 + @override
45 + String feeFormatted() => '${(ethFee / BigInt.from(10).pow(18)).toString()} ETH';
46 +
47 + factory EthereumTransactionInfo.fromJson(Map<String, dynamic> data) {
48 + return EthereumTransactionInfo(
49 + id: data['id'] as String,
50 + height: data['height'] as int,
51 + ethAmount: BigInt.parse(data['amount']),
52 + exponent: data['exponent'] as int,
53 + ethFee: BigInt.parse(data['fee']),
54 + direction: parseTransactionDirectionFromInt(data['direction'] as int),
55 + date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
56 + isPending: data['isPending'] as bool,
57 + confirmations: data['confirmations'] as int,
58 + tokenSymbol: data['tokenSymbol'] as String,
59 + );
60 + }
61 +
62 + Map<String, dynamic> toJson() => {
63 + 'id': id,
64 + 'height': height,
65 + 'amount': ethAmount.toString(),
66 + 'exponent': exponent,
67 + 'fee': ethFee.toString(),
68 + 'direction': direction.index,
69 + 'date': date.millisecondsSinceEpoch,
70 + 'isPending': isPending,
71 + 'confirmations': confirmations,
72 + 'tokenSymbol': tokenSymbol,
73 + };
74 +}
cw_ethereum/lib/ethereum_transaction_model.dart new
+47
@@ -0,0 +1,47 @@
1 +class EthereumTransactionModel {
2 + final DateTime date;
3 + final String hash;
4 + final String from;
5 + final String to;
6 + final BigInt amount;
7 + final int gasUsed;
8 + final BigInt gasPrice;
9 + final String contractAddress;
10 + final int confirmations;
11 + final int blockNumber;
12 + final String? tokenSymbol;
13 + final int? tokenDecimal;
14 + final bool isError;
15 +
16 + EthereumTransactionModel({
17 + required this.date,
18 + required this.hash,
19 + required this.from,
20 + required this.to,
21 + required this.amount,
22 + required this.gasUsed,
23 + required this.gasPrice,
24 + required this.contractAddress,
25 + required this.confirmations,
26 + required this.blockNumber,
27 + required this.tokenSymbol,
28 + required this.tokenDecimal,
29 + required this.isError,
30 + });
31 +
32 + factory EthereumTransactionModel.fromJson(Map<String, dynamic> json) => EthereumTransactionModel(
33 + date: DateTime.fromMillisecondsSinceEpoch(int.parse(json["timeStamp"]) * 1000),
34 + hash: json["hash"],
35 + from: json["from"],
36 + to: json["to"],
37 + amount: BigInt.parse(json["value"]),
38 + gasUsed: int.parse(json["gasUsed"]),
39 + gasPrice: BigInt.parse(json["gasPrice"]),
40 + contractAddress: json["contractAddress"],
41 + confirmations: int.parse(json["confirmations"]),
42 + blockNumber: int.parse(json["blockNumber"]),
43 + tokenSymbol: json["tokenSymbol"] ?? "ETH",
44 + tokenDecimal: int.tryParse(json["tokenDecimal"] ?? ""),
45 + isError: json["isError"] == "1",
46 + );
47 +}
cw_ethereum/lib/ethereum_transaction_priority.dart new
+52
@@ -0,0 +1,52 @@
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 new
+473
@@ -0,0 +1,473 @@
1 +import 'dart:async';
2 +import 'dart:convert';
3 +import 'dart:io';
4 +import 'dart:math';
5 +
6 +import 'package:cw_core/crypto_currency.dart';
7 +import 'package:cw_core/node.dart';
8 +import 'package:cw_core/pathForWallet.dart';
9 +import 'package:cw_core/pending_transaction.dart';
10 +import 'package:cw_core/sync_status.dart';
11 +import 'package:cw_core/transaction_direction.dart';
12 +import 'package:cw_core/transaction_priority.dart';
13 +import 'package:cw_core/wallet_addresses.dart';
14 +import 'package:cw_core/wallet_base.dart';
15 +import 'package:cw_core/wallet_info.dart';
16 +import 'package:cw_ethereum/default_erc20_tokens.dart';
17 +import 'package:cw_ethereum/erc20_balance.dart';
18 +import 'package:cw_ethereum/ethereum_client.dart';
19 +import 'package:cw_ethereum/ethereum_exceptions.dart';
20 +import 'package:cw_ethereum/ethereum_formatter.dart';
21 +import 'package:cw_ethereum/ethereum_transaction_credentials.dart';
22 +import 'package:cw_ethereum/ethereum_transaction_history.dart';
23 +import 'package:cw_ethereum/ethereum_transaction_info.dart';
24 +import 'package:cw_ethereum/ethereum_transaction_model.dart';
25 +import 'package:cw_ethereum/ethereum_transaction_priority.dart';
26 +import 'package:cw_ethereum/ethereum_wallet_addresses.dart';
27 +import 'package:cw_ethereum/file.dart';
28 +import 'package:cw_core/erc20_token.dart';
29 +import 'package:hive/hive.dart';
30 +import 'package:hex/hex.dart';
31 +import 'package:mobx/mobx.dart';
32 +import 'package:shared_preferences/shared_preferences.dart';
33 +import 'package:web3dart/web3dart.dart';
34 +import 'package:bip39/bip39.dart' as bip39;
35 +import 'package:bip32/bip32.dart' as bip32;
36 +
37 +part 'ethereum_wallet.g.dart';
38 +
39 +class EthereumWallet = EthereumWalletBase with _$EthereumWallet;
40 +
41 +abstract class EthereumWalletBase
42 + extends WalletBase<ERC20Balance, EthereumTransactionHistory, EthereumTransactionInfo>
43 + with Store {
44 + EthereumWalletBase({
45 + required WalletInfo walletInfo,
46 + required String mnemonic,
47 + required String password,
48 + ERC20Balance? initialBalance,
49 + }) : syncStatus = NotConnectedSyncStatus(),
50 + _password = password,
51 + _mnemonic = mnemonic,
52 + _isTransactionUpdating = false,
53 + _client = EthereumClient(),
54 + walletAddresses = EthereumWalletAddresses(walletInfo),
55 + balance = ObservableMap<CryptoCurrency, ERC20Balance>.of(
56 + {CryptoCurrency.eth: initialBalance ?? ERC20Balance(BigInt.zero)}),
57 + super(walletInfo) {
58 + this.walletInfo = walletInfo;
59 + transactionHistory = EthereumTransactionHistory(walletInfo: walletInfo, password: password);
60 +
61 + if (!Hive.isAdapterRegistered(Erc20Token.typeId)) {
62 + Hive.registerAdapter(Erc20TokenAdapter());
63 + }
64 +
65 + _sharedPrefs.complete(SharedPreferences.getInstance());
66 + }
67 +
68 + final String _mnemonic;
69 + final String _password;
70 +
71 + late final Box<Erc20Token> erc20TokensBox;
72 +
73 + late final EthPrivateKey _privateKey;
74 +
75 + late EthereumClient _client;
76 +
77 + int? _gasPrice;
78 + int? _estimatedGas;
79 + bool _isTransactionUpdating;
80 +
81 + // TODO: remove after integrating our own node and having eth_newPendingTransactionFilter
82 + Timer? _transactionsUpdateTimer;
83 +
84 + @override
85 + WalletAddresses walletAddresses;
86 +
87 + @override
88 + @observable
89 + SyncStatus syncStatus;
90 +
91 + @override
92 + @observable
93 + late ObservableMap<CryptoCurrency, ERC20Balance> balance;
94 +
95 + Completer<SharedPreferences> _sharedPrefs = Completer();
96 +
97 + Future<void> init() async {
98 + erc20TokensBox = await Hive.openBox<Erc20Token>(Erc20Token.boxName);
99 + await walletAddresses.init();
100 + await transactionHistory.init();
101 + _privateKey = await getPrivateKey(_mnemonic, _password);
102 + walletAddresses.address = _privateKey.address.toString();
103 + await save();
104 + }
105 +
106 + @override
107 + int calculateEstimatedFee(TransactionPriority priority, int? amount) {
108 + try {
109 + if (priority is EthereumTransactionPriority) {
110 + final priorityFee =
111 + EtherAmount.fromUnitAndValue(EtherUnit.gwei, priority.tip).getInWei.toInt();
112 + return (_gasPrice! + priorityFee) * (_estimatedGas ?? 0);
113 + }
114 +
115 + return 0;
116 + } catch (e) {
117 + return 0;
118 + }
119 + }
120 +
121 + @override
122 + Future<void> changePassword(String password) {
123 + throw UnimplementedError("changePassword");
124 + }
125 +
126 + @override
127 + void close() {
128 + _client.stop();
129 + _transactionsUpdateTimer?.cancel();
130 + }
131 +
132 + @action
133 + @override
134 + Future<void> connectToNode({required Node node}) async {
135 + try {
136 + syncStatus = ConnectingSyncStatus();
137 +
138 + final isConnected = _client.connect(node);
139 +
140 + if (!isConnected) {
141 + throw Exception("Ethereum Node connection failed");
142 + }
143 +
144 + _client.setListeners(_privateKey.address, _onNewTransaction);
145 +
146 + _setTransactionUpdateTimer();
147 +
148 + syncStatus = ConnectedSyncStatus();
149 + } catch (e) {
150 + syncStatus = FailedSyncStatus();
151 + }
152 + }
153 +
154 + @override
155 + Future<PendingTransaction> createTransaction(Object credentials) async {
156 + final _credentials = credentials as EthereumTransactionCredentials;
157 + final outputs = _credentials.outputs;
158 + final hasMultiDestination = outputs.length > 1;
159 + final _erc20Balance = balance[_credentials.currency]!;
160 + BigInt totalAmount = BigInt.zero;
161 + int exponent =
162 + _credentials.currency is Erc20Token ? (_credentials.currency as Erc20Token).decimal : 18;
163 + num amountToEthereumMultiplier = pow(10, exponent);
164 +
165 + if (hasMultiDestination) {
166 + if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) {
167 + throw EthereumTransactionCreationException(_credentials.currency);
168 + }
169 +
170 + final totalOriginalAmount = EthereumFormatter.parseEthereumAmountToDouble(
171 + outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0)));
172 + totalAmount = BigInt.from(totalOriginalAmount * amountToEthereumMultiplier);
173 +
174 + if (_erc20Balance.balance < totalAmount) {
175 + throw EthereumTransactionCreationException(_credentials.currency);
176 + }
177 + } else {
178 + final output = outputs.first;
179 + final BigInt allAmount =
180 + _erc20Balance.balance - BigInt.from(calculateEstimatedFee(_credentials.priority!, null));
181 + final totalOriginalAmount =
182 + EthereumFormatter.parseEthereumAmountToDouble(output.formattedCryptoAmount ?? 0);
183 + totalAmount = output.sendAll
184 + ? allAmount
185 + : BigInt.from(totalOriginalAmount * amountToEthereumMultiplier);
186 +
187 + if (_erc20Balance.balance < totalAmount) {
188 + throw EthereumTransactionCreationException(_credentials.currency);
189 + }
190 + }
191 +
192 + final pendingEthereumTransaction = await _client.signTransaction(
193 + privateKey: _privateKey,
194 + toAddress: _credentials.outputs.first.address,
195 + amount: totalAmount.toString(),
196 + gas: _estimatedGas!,
197 + priority: _credentials.priority!,
198 + currency: _credentials.currency,
199 + exponent: exponent,
200 + contractAddress: _credentials.currency is Erc20Token
201 + ? (_credentials.currency as Erc20Token).contractAddress
202 + : null,
203 + );
204 +
205 + return pendingEthereumTransaction;
206 + }
207 +
208 + Future<void> _updateTransactions() async {
209 + try {
210 + if (_isTransactionUpdating) {
211 + return;
212 + }
213 + bool isEtherscanEnabled = (await _sharedPrefs.future).getBool("use_etherscan") ?? true;
214 + if (!isEtherscanEnabled) {
215 + return;
216 + }
217 +
218 + _isTransactionUpdating = true;
219 + final transactions = await fetchTransactions();
220 + transactionHistory.addMany(transactions);
221 + await transactionHistory.save();
222 + _isTransactionUpdating = false;
223 + } catch (_) {
224 + _isTransactionUpdating = false;
225 + }
226 + }
227 +
228 + @override
229 + Future<Map<String, EthereumTransactionInfo>> fetchTransactions() async {
230 + final address = _privateKey.address.hex;
231 + final transactions = await _client.fetchTransactions(address);
232 +
233 + final List<Future<List<EthereumTransactionModel>>> erc20TokensTransactions = [];
234 +
235 + for (var token in balance.keys) {
236 + if (token is Erc20Token) {
237 + erc20TokensTransactions.add(_client.fetchTransactions(
238 + address,
239 + contractAddress: token.contractAddress,
240 + ));
241 + }
242 + }
243 +
244 + final tokensTransaction = await Future.wait(erc20TokensTransactions);
245 + transactions.addAll(tokensTransaction.expand((element) => element));
246 +
247 + final Map<String, EthereumTransactionInfo> result = {};
248 +
249 + for (var transactionModel in transactions) {
250 + if (transactionModel.isError) {
251 + continue;
252 + }
253 +
254 + result[transactionModel.hash] = EthereumTransactionInfo(
255 + id: transactionModel.hash,
256 + height: transactionModel.blockNumber,
257 + ethAmount: transactionModel.amount,
258 + direction: transactionModel.from == address
259 + ? TransactionDirection.outgoing
260 + : TransactionDirection.incoming,
261 + isPending: false,
262 + date: transactionModel.date,
263 + confirmations: transactionModel.confirmations,
264 + ethFee: BigInt.from(transactionModel.gasUsed) * transactionModel.gasPrice,
265 + exponent: transactionModel.tokenDecimal ?? 18,
266 + tokenSymbol: transactionModel.tokenSymbol ?? "ETH",
267 + );
268 + }
269 +
270 + return result;
271 + }
272 +
273 + @override
274 + Object get keys => throw UnimplementedError("keys");
275 +
276 + @override
277 + Future<void> rescan({required int height}) {
278 + throw UnimplementedError("rescan");
279 + }
280 +
281 + @override
282 + Future<void> save() async {
283 + await walletAddresses.updateAddressesInBox();
284 + final path = await makePath();
285 + await write(path: path, password: _password, data: toJSON());
286 + await transactionHistory.save();
287 + }
288 +
289 + @override
290 + String get seed => _mnemonic;
291 +
292 + @action
293 + @override
294 + Future<void> startSync() async {
295 + try {
296 + syncStatus = AttemptingSyncStatus();
297 + await _updateBalance();
298 + await _updateTransactions();
299 + _gasPrice = await _client.getGasUnitPrice();
300 + _estimatedGas = await _client.getEstimatedGas();
301 +
302 + Timer.periodic(
303 + const Duration(minutes: 1), (timer) async => _gasPrice = await _client.getGasUnitPrice());
304 + Timer.periodic(const Duration(seconds: 10),
305 + (timer) async => _estimatedGas = await _client.getEstimatedGas());
306 +
307 + syncStatus = SyncedSyncStatus();
308 + } catch (e) {
309 + syncStatus = FailedSyncStatus();
310 + }
311 + }
312 +
313 + Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
314 +
315 + String toJSON() => json.encode({
316 + 'mnemonic': _mnemonic,
317 + 'balance': balance[currency]!.toJSON(),
318 + });
319 +
320 + static Future<EthereumWallet> open({
321 + required String name,
322 + required String password,
323 + required WalletInfo walletInfo,
324 + }) async {
325 + final path = await pathForWallet(name: name, type: walletInfo.type);
326 + final jsonSource = await read(path: path, password: password);
327 + final data = json.decode(jsonSource) as Map;
328 + final mnemonic = data['mnemonic'] as String;
329 + final balance = ERC20Balance.fromJSON(data['balance'] as String) ?? ERC20Balance(BigInt.zero);
330 +
331 + return EthereumWallet(
332 + walletInfo: walletInfo,
333 + password: password,
334 + mnemonic: mnemonic,
335 + initialBalance: balance,
336 + );
337 + }
338 +
339 + Future<void> _updateBalance() async {
340 + balance[currency] = await _fetchEthBalance();
341 +
342 + await _fetchErc20Balances();
343 + await save();
344 + }
345 +
346 + Future<ERC20Balance> _fetchEthBalance() async {
347 + final balance = await _client.getBalance(_privateKey.address);
348 + return ERC20Balance(balance.getInWei);
349 + }
350 +
351 + Future<void> _fetchErc20Balances() async {
352 + for (var token in erc20TokensBox.values) {
353 + try {
354 + if (token.enabled) {
355 + balance[token] = await _client.fetchERC20Balances(
356 + _privateKey.address,
357 + token.contractAddress,
358 + );
359 + } else {
360 + balance.remove(token);
361 + }
362 + } catch (_) {}
363 + }
364 + }
365 +
366 + Future<EthPrivateKey> getPrivateKey(String mnemonic, String password) async {
367 + final seed = bip39.mnemonicToSeed(mnemonic);
368 +
369 + final root = bip32.BIP32.fromSeed(seed);
370 +
371 + const _hdPathEthereum = "m/44'/60'/0'/0";
372 + const index = 0;
373 + final addressAtIndex = root.derivePath("$_hdPathEthereum/$index");
374 +
375 + return EthPrivateKey.fromHex(HEX.encode(addressAtIndex.privateKey as List<int>));
376 + }
377 +
378 + Future<void>? updateBalance() async => await _updateBalance();
379 +
380 + List<Erc20Token> get erc20Currencies => erc20TokensBox.values.toList();
381 +
382 + Future<void> addErc20Token(Erc20Token token) async {
383 + String? iconPath;
384 + try {
385 + iconPath = CryptoCurrency.all
386 + .firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase())
387 + .iconPath;
388 + } catch (_) {}
389 +
390 + final _token = Erc20Token(
391 + name: token.name,
392 + symbol: token.symbol,
393 + contractAddress: token.contractAddress,
394 + decimal: token.decimal,
395 + enabled: token.enabled,
396 + iconPath: iconPath,
397 + );
398 +
399 + await erc20TokensBox.put(_token.contractAddress, _token);
400 +
401 + if (_token.enabled) {
402 + balance[_token] = await _client.fetchERC20Balances(
403 + _privateKey.address,
404 + _token.contractAddress,
405 + );
406 + } else {
407 + balance.remove(_token);
408 + }
409 + }
410 +
411 + Future<void> deleteErc20Token(Erc20Token token) async {
412 + await token.delete();
413 +
414 + balance.remove(token);
415 + _updateBalance();
416 + }
417 +
418 + Future<Erc20Token?> getErc20Token(String contractAddress) async =>
419 + await _client.getErc20Token(contractAddress);
420 +
421 + void _onNewTransaction() {
422 + _updateBalance();
423 + _updateTransactions();
424 + }
425 +
426 + void addInitialTokens() {
427 + final initialErc20Tokens = DefaultErc20Tokens().initialErc20Tokens;
428 +
429 + initialErc20Tokens.forEach((token) => erc20TokensBox.put(token.contractAddress, token));
430 + }
431 +
432 + @override
433 + Future<void> renameWalletFiles(String newWalletName) async {
434 + final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type);
435 + final currentWalletFile = File(currentWalletPath);
436 +
437 + final currentDirPath = await pathForWalletDir(name: walletInfo.name, type: type);
438 + final currentTransactionsFile = File('$currentDirPath/$transactionsHistoryFileName');
439 +
440 + // Copies current wallet files into new wallet name's dir and files
441 + if (currentWalletFile.existsSync()) {
442 + final newWalletPath = await pathForWallet(name: newWalletName, type: type);
443 + await currentWalletFile.copy(newWalletPath);
444 + }
445 + if (currentTransactionsFile.existsSync()) {
446 + final newDirPath = await pathForWalletDir(name: newWalletName, type: type);
447 + await currentTransactionsFile.copy('$newDirPath/$transactionsHistoryFileName');
448 + }
449 +
450 + // Delete old name's dir and files
451 + await Directory(currentDirPath).delete(recursive: true);
452 + }
453 +
454 + void _setTransactionUpdateTimer() {
455 + if (_transactionsUpdateTimer?.isActive ?? false) {
456 + _transactionsUpdateTimer!.cancel();
457 + }
458 +
459 + _transactionsUpdateTimer = Timer.periodic(Duration(seconds: 10), (_) {
460 + _updateTransactions();
461 + _updateBalance();
462 + });
463 + }
464 +
465 + void updateEtherscanUsageState(bool isEnabled) {
466 + if (isEnabled) {
467 + _updateTransactions();
468 + _setTransactionUpdateTimer();
469 + } else {
470 + _transactionsUpdateTimer?.cancel();
471 + }
472 + }
473 +}
cw_ethereum/lib/ethereum_wallet_addresses.dart new
+33
@@ -0,0 +1,33 @@
1 +import 'package:cw_core/wallet_addresses.dart';
2 +import 'package:cw_core/wallet_info.dart';
3 +import 'package:mobx/mobx.dart';
4 +
5 +part 'ethereum_wallet_addresses.g.dart';
6 +
7 +class EthereumWalletAddresses = EthereumWalletAddressesBase with _$EthereumWalletAddresses;
8 +
9 +abstract class EthereumWalletAddressesBase extends WalletAddresses with Store {
10 + EthereumWalletAddressesBase(WalletInfo walletInfo)
11 + : address = '',
12 + super(walletInfo);
13 +
14 + @override
15 + String address;
16 +
17 + @override
18 + Future<void> init() async {
19 + address = walletInfo.address;
20 + await updateAddressesInBox();
21 + }
22 +
23 + @override
24 + Future<void> updateAddressesInBox() async {
25 + try {
26 + addressesMap.clear();
27 + addressesMap[address] = '';
28 + await saveAddressesInBox();
29 + } catch (e) {
30 + print(e.toString());
31 + }
32 + }
33 +}
cw_ethereum/lib/ethereum_wallet_creation_credentials.dart new
+23
@@ -0,0 +1,23 @@
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, required String password, required this.mnemonic, WalletInfo? walletInfo})
12 + : super(name: name, password: password, walletInfo: walletInfo);
13 +
14 + final String mnemonic;
15 +}
16 +
17 +class EthereumRestoreWalletFromWIFCredentials extends WalletCredentials {
18 + EthereumRestoreWalletFromWIFCredentials(
19 + {required String name, required String password, required this.wif, WalletInfo? walletInfo})
20 + : super(name: name, password: password, walletInfo: walletInfo);
21 +
22 + final String wif;
23 +}
cw_ethereum/lib/ethereum_wallet_service.dart new
+108
@@ -0,0 +1,108 @@
1 +import 'dart:io';
2 +
3 +import 'package:cw_core/pathForWallet.dart';
4 +import 'package:cw_core/wallet_base.dart';
5 +import 'package:cw_core/wallet_info.dart';
6 +import 'package:cw_core/wallet_service.dart';
7 +import 'package:cw_core/wallet_type.dart';
8 +import 'package:cw_ethereum/ethereum_mnemonics.dart';
9 +import 'package:cw_ethereum/ethereum_wallet.dart';
10 +import 'package:cw_ethereum/ethereum_wallet_creation_credentials.dart';
11 +import 'package:hive/hive.dart';
12 +import 'package:bip39/bip39.dart' as bip39;
13 +import 'package:collection/collection.dart';
14 +
15 +class EthereumWalletService extends WalletService<EthereumNewWalletCredentials,
16 + EthereumRestoreWalletFromSeedCredentials, EthereumRestoreWalletFromWIFCredentials> {
17 + EthereumWalletService(this.walletInfoSource);
18 +
19 + final Box<WalletInfo> walletInfoSource;
20 +
21 + @override
22 + Future<EthereumWallet> create(EthereumNewWalletCredentials credentials) async {
23 + final mnemonic = bip39.generateMnemonic();
24 + final wallet = EthereumWallet(
25 + walletInfo: credentials.walletInfo!,
26 + mnemonic: mnemonic,
27 + password: credentials.password!,
28 + );
29 +
30 + await wallet.init();
31 + wallet.addInitialTokens();
32 + await wallet.save();
33 +
34 + return wallet;
35 + }
36 +
37 + @override
38 + WalletType getType() => WalletType.ethereum;
39 +
40 + @override
41 + Future<bool> isWalletExit(String name) async =>
42 + File(await pathForWallet(name: name, type: getType())).existsSync();
43 +
44 + @override
45 + Future<EthereumWallet> openWallet(String name, String password) async {
46 + final walletInfo =
47 + walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
48 + final wallet = await EthereumWalletBase.open(
49 + name: name,
50 + password: password,
51 + walletInfo: walletInfo,
52 + );
53 +
54 + await wallet.init();
55 + await wallet.save();
56 +
57 + return wallet;
58 + }
59 +
60 + @override
61 + Future<void> remove(String wallet) async {
62 + File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true);
63 + final walletInfo = walletInfoSource.values.firstWhereOrNull(
64 + (info) => info.id == WalletBase.idFor(wallet, getType()))!;
65 + await walletInfoSource.delete(walletInfo.key);
66 + }
67 +
68 + @override
69 + Future<EthereumWallet> restoreFromKeys(credentials) {
70 + throw UnimplementedError();
71 + }
72 +
73 + @override
74 + Future<EthereumWallet> restoreFromSeed(
75 + EthereumRestoreWalletFromSeedCredentials credentials) async {
76 + if (!bip39.validateMnemonic(credentials.mnemonic)) {
77 + throw EthereumMnemonicIsIncorrectException();
78 + }
79 +
80 + final wallet = EthereumWallet(
81 + password: credentials.password!,
82 + mnemonic: credentials.mnemonic,
83 + walletInfo: credentials.walletInfo!,
84 + );
85 +
86 + await wallet.init();
87 + wallet.addInitialTokens();
88 + await wallet.save();
89 +
90 + return wallet;
91 + }
92 +
93 + @override
94 + Future<void> rename(String currentName, String password, String newName) async {
95 + final currentWalletInfo = walletInfoSource.values
96 + .firstWhere((info) => info.id == WalletBase.idFor(currentName, getType()));
97 + final currentWallet = await EthereumWalletBase.open(
98 + password: password, name: currentName, walletInfo: currentWalletInfo);
99 +
100 + await currentWallet.renameWalletFiles(newName);
101 +
102 + final newWalletInfo = currentWalletInfo;
103 + newWalletInfo.id = WalletBase.idFor(newName, getType());
104 + newWalletInfo.name = newName;
105 +
106 + await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
107 + }
108 +}
cw_ethereum/lib/file.dart new
+39
@@ -0,0 +1,39 @@
1 +import 'dart:io';
2 +import 'package:cw_core/key.dart';
3 +import 'package:encrypt/encrypt.dart' as encrypt;
4 +
5 +Future<void> write(
6 + {required String path,
7 + required String password,
8 + required String data}) async {
9 + final keys = extractKeys(password);
10 + final key = encrypt.Key.fromBase64(keys.first);
11 + final iv = encrypt.IV.fromBase64(keys.last);
12 + final encrypted = await encode(key: key, iv: iv, data: data);
13 + final f = File(path);
14 + f.writeAsStringSync(encrypted);
15 +}
16 +
17 +Future<void> writeData(
18 + {required String path,
19 + required String password,
20 + required String data}) async {
21 + final keys = extractKeys(password);
22 + final key = encrypt.Key.fromBase64(keys.first);
23 + final iv = encrypt.IV.fromBase64(keys.last);
24 + final encrypted = await encode(key: key, iv: iv, data: data);
25 + final f = File(path);
26 + f.writeAsStringSync(encrypted);
27 +}
28 +
29 +Future<String> read({required String path, required String password}) async {
30 + final file = File(path);
31 +
32 + if (!file.existsSync()) {
33 + file.createSync();
34 + }
35 +
36 + final encrypted = file.readAsStringSync();
37 +
38 + return decode(password: password, data: encrypted);
39 +}
cw_ethereum/lib/pending_ethereum_transaction.dart new
+36
@@ -0,0 +1,36 @@
1 +import 'dart:math';
2 +import 'dart:typed_data';
3 +
4 +import 'package:cw_core/pending_transaction.dart';
5 +import 'package:web3dart/crypto.dart';
6 +
7 +class PendingEthereumTransaction 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({
15 + required this.sendTransaction,
16 + required this.signedTransaction,
17 + required this.fee,
18 + required this.amount,
19 + required this.exponent,
20 + });
21 +
22 + @override
23 + String get amountFormatted => (BigInt.parse(amount) / BigInt.from(pow(10, exponent))).toString();
24 +
25 + @override
26 + Future<void> commit() async => await sendTransaction();
27 +
28 + @override
29 + String get feeFormatted => (fee / BigInt.from(pow(10, 18))).toString();
30 +
31 + @override
32 + String get hex => bytesToHex(signedTransaction, include0x: true);
33 +
34 + @override
35 + String get id => '';
36 +}
cw_ethereum/pubspec.yaml new
+68
@@ -0,0 +1,68 @@
1 +name: cw_ethereum
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: '>=2.18.2 <3.0.0'
10 + flutter: ">=1.17.0"
11 +
12 +dependencies:
13 + flutter:
14 + sdk: flutter
15 + web3dart: 2.3.5
16 + mobx: ^2.0.7+4
17 + bip39: ^1.0.6
18 + bip32: ^2.0.0
19 + ed25519_hd_key: ^2.2.0
20 + hex: ^0.2.0
21 + http: ^0.13.4
22 + shared_preferences: ^2.0.15
23 + cw_core:
24 + path: ../cw_core
25 +
26 +dev_dependencies:
27 + flutter_test:
28 + sdk: flutter
29 + 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.
37 +flutter:
38 +
39 + # To add assets to your package, add an assets section, like this:
40 + # assets:
41 + # - images/a_dot_burr.jpeg
42 + # - 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:
55 + # fonts:
56 + # - family: Schyler
57 + # fonts:
58 + # - asset: fonts/Schyler-Regular.ttf
59 + # - asset: fonts/Schyler-Italic.ttf
60 + # 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_ethereum/test/cw_ethereum_test.dart new
+12
@@ -0,0 +1,12 @@
1 +import 'package:flutter_test/flutter_test.dart';
2 +
3 +import 'package:cw_ethereum/cw_ethereum.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_haven/lib/haven_wallet.dart
+1
@@ -254,6 +254,7 @@ abstract class HavenWalletBase extends WalletBase<MoneroBalance,
254 await haven_wallet.store();
255 }
256
257 + @override
258 Future<void> renameWalletFiles(String newWalletName) async {
259 final currentWalletPath = await pathForWallet(name: name, type: type);
260 final currentCacheFile = File(currentWalletPath);
cw_monero/lib/monero_transaction_info.dart
+6 -7
@@ -14,18 +14,18 @@ class MoneroTransactionInfo extends TransactionInfo {
14 MoneroTransactionInfo.fromMap(Map<String, Object?> map)
15 : id = (map['hash'] ?? '') as String,
16 height = (map['height'] ?? 0) as int,
17 - direction =
18 - parseTransactionDirectionFromNumber(map['direction'] as String) ??
19 - TransactionDirection.incoming,
17 + direction = map['direction'] != null
18 + ? parseTransactionDirectionFromNumber(map['direction'] as String)
19 + : TransactionDirection.incoming,
20 date = DateTime.fromMillisecondsSinceEpoch(
21 - (int.parse(map['timestamp'] as String) ?? 0) * 1000),
21 + (int.tryParse(map['timestamp'] as String? ?? '') ?? 0) * 1000),
22 isPending = parseBoolFromString(map['isPending'] as String),
23 amount = map['amount'] as int,
24 accountIndex = int.parse(map['accountIndex'] as String),
25 addressIndex = map['addressIndex'] as int,
26 confirmations = map['confirmations'] as int,
27 key = getTxKey((map['hash'] ?? '') as String),
28 - fee = map['fee'] as int ?? 0 {
28 + fee = map['fee'] as int? ?? 0 {
29 additionalInfo = <String, dynamic>{
30 'key': key,
31 'accountIndex': accountIndex,
@@ -36,8 +36,7 @@ class MoneroTransactionInfo extends TransactionInfo {
36 MoneroTransactionInfo.fromRow(TransactionInfoRow row)
37 : id = row.getHash(),
38 height = row.blockHeight,
39 - direction = parseTransactionDirectionFromInt(row.direction) ??
40 - TransactionDirection.incoming,
39 + direction = parseTransactionDirectionFromInt(row.direction),
40 date = DateTime.fromMillisecondsSinceEpoch(row.getDatetime() * 1000),
41 isPending = row.isPending != 0,
42 amount = row.getAmount(),
cw_monero/lib/monero_wallet.dart
+1
@@ -269,6 +269,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
269 await monero_wallet.store();
270 }
271
272 + @override
273 Future<void> renameWalletFiles(String newWalletName) async {
274 final currentWalletDirPath = await pathForWalletDir(name: name, type: type);
275
howto-build-android.md
+6 -6
@@ -6,9 +6,9 @@ The following are the system requirements to build CakeWallet for your Android d
6
7 ```
8 Ubuntu >= 16.04
9 -Android SDK 28
9 +Android SDK 29 or higher (better to have the latest one 33)
10 Android NDK 17c
11 -Flutter 2 or above
11 +Flutter 3.7.x
12 ```
13
14 ## Building CakeWallet on Android
@@ -55,7 +55,7 @@ You may download and install the latest version of Android Studio [here](https:/
55
56 ### 3. Installing Flutter
57
58 -Need to install flutter with version `3.x.x`. For this please check section [Install Flutter manually](https://docs.flutter.dev/get-started/install/linux#install-flutter-manually).
58 +Need to install flutter with version `3.7.x`. For this please check section [Install Flutter manually](https://docs.flutter.dev/get-started/install/linux#install-flutter-manually).
59
60 ### 4. Verify Installations
61
@@ -66,9 +66,9 @@ Verify that the Android toolchain, Flutter, and Android Studio have been correct
66 The output of this command will appear like this, indicating successful installations. If there are problems with your installation, they **must** be corrected before proceeding.
67 ```
68 Doctor summary (to see all details, run flutter doctor -v):
69 -[✓] Flutter (Channel stable, 3.x.x, on Linux, locale en_US.UTF-8)
70 -[✓] Android toolchain - develop for Android devices (Android SDK version 28)
71 -[✓] Android Studio (version 4.0)
69 +[✓] Flutter (Channel stable, 3.7.x, on Linux, locale en_US.UTF-8)
70 +[✓] Android toolchain - develop for Android devices (Android SDK version 29 or higher)
71 +[✓] Android Studio (version 4.0 or higher)
72 ```
73
74 ### 5. Generate a secure keystore for Android
lib/bitcoin/cw_bitcoin.dart
+1 -1
@@ -80,7 +80,7 @@ class CWBitcoin extends Bitcoin {
80 isParsedAddress: out.isParsedAddress,
81 formattedCryptoAmount: out.formattedCryptoAmount))
82 .toList(),
83 - priority: priority != null ? priority as BitcoinTransactionPriority : null,
83 + priority: priority as BitcoinTransactionPriority,
84 feeRate: feeRate);
85
86 @override
lib/core/address_validator.dart
+14 -9
@@ -2,6 +2,7 @@ import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
2 import 'package:cake_wallet/generated/i18n.dart';
3 import 'package:cake_wallet/core/validator.dart';
4 import 'package:cw_core/crypto_currency.dart';
5 +import 'package:cw_core/erc20_token.dart';
6
7 class AddressValidator extends TextValidator {
8 AddressValidator({required CryptoCurrency type})
@@ -14,6 +15,9 @@ class AddressValidator extends TextValidator {
15 length: getLength(type));
16
17 static String getPattern(CryptoCurrency type) {
18 + if (type is Erc20Token) {
19 + return '0x[0-9a-zA-Z]';
20 + }
21 switch (type) {
22 case CryptoCurrency.xmr:
23 return '^4[0-9a-zA-Z]{94}\$|^8[0-9a-zA-Z]{94}\$|^[0-9a-zA-Z]{106}\$';
@@ -56,6 +60,7 @@ class AddressValidator extends TextValidator {
60 case CryptoCurrency.zrx:
61 case CryptoCurrency.dydx:
62 case CryptoCurrency.steth:
63 + case CryptoCurrency.shib:
64 return '0x[0-9a-zA-Z]';
65 case CryptoCurrency.xrp:
66 return '^[0-9a-zA-Z]{34}\$|^X[0-9a-zA-Z]{46}\$';
@@ -116,17 +121,14 @@ class AddressValidator extends TextValidator {
121 }
122
123 static List<int>? getLength(CryptoCurrency type) {
124 + if (type is Erc20Token) {
125 + return [42];
126 + }
127 switch (type) {
128 case CryptoCurrency.xmr:
129 return null;
130 case CryptoCurrency.ada:
131 return null;
124 - case CryptoCurrency.avaxc:
125 - return [42];
126 - case CryptoCurrency.bch:
127 - return [42];
128 - case CryptoCurrency.bnb:
129 - return [42];
132 case CryptoCurrency.btc:
133 return null;
134 case CryptoCurrency.dash:
@@ -166,6 +168,10 @@ class AddressValidator extends TextValidator {
168 case CryptoCurrency.zrx:
169 case CryptoCurrency.dydx:
170 case CryptoCurrency.steth:
171 + case CryptoCurrency.shib:
172 + case CryptoCurrency.avaxc:
173 + case CryptoCurrency.bch:
174 + case CryptoCurrency.bnb:
175 return [42];
176 case CryptoCurrency.ltc:
177 return [34, 43, 63];
@@ -203,11 +209,8 @@ class AddressValidator extends TextValidator {
209 case CryptoCurrency.xusd:
210 return [98, 99, 106];
211 case CryptoCurrency.btt:
206 - return [34];
212 case CryptoCurrency.bttc:
208 - return [34];
213 case CryptoCurrency.doge:
210 - return [34];
214 case CryptoCurrency.firo:
215 return [34];
216 case CryptoCurrency.hbar:
@@ -258,6 +261,8 @@ class AddressValidator extends TextValidator {
261 return '([^0-9a-zA-Z]|^)^L[a-zA-Z0-9]{26,33}([^0-9a-zA-Z]|\$)'
262 '|([^0-9a-zA-Z]|^)[LM][a-km-zA-HJ-NP-Z1-9]{26,33}([^0-9a-zA-Z]|\$)'
263 '|([^0-9a-zA-Z]|^)ltc[a-zA-Z0-9]{26,45}([^0-9a-zA-Z]|\$)';
264 + case CryptoCurrency.eth:
265 + return '0x[0-9a-zA-Z]{42}';
266 default:
267 return null;
268 }
lib/core/backup_service.dart
+18
@@ -240,6 +240,9 @@ class BackupService {
240 data[PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets] as bool?;
241 final shouldRequireTOTP2FAForAllSecurityAndBackupSettings =
242 data[PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings] as bool?;
243 + final sortBalanceTokensBy = data[PreferencesKey.sortBalanceBy] as int?;
244 + final pinNativeTokenAtTop = data[PreferencesKey.pinNativeTokenAtTop] as bool?;
245 + final useEtherscan = data[PreferencesKey.useEtherscan] as bool?;
246
247 await _sharedPreferences.setString(PreferencesKey.currentWalletName, currentWalletName);
248
@@ -349,6 +352,15 @@ class BackupService {
352 PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
353 shouldRequireTOTP2FAForAllSecurityAndBackupSettings);
354
355 + if (sortBalanceTokensBy != null)
356 + await _sharedPreferences.setInt(PreferencesKey.sortBalanceBy, sortBalanceTokensBy);
357 +
358 + if (pinNativeTokenAtTop != null)
359 + await _sharedPreferences.setBool(PreferencesKey.pinNativeTokenAtTop, pinNativeTokenAtTop);
360 +
361 + if (useEtherscan != null)
362 + await _sharedPreferences.setBool(PreferencesKey.useEtherscan, useEtherscan);
363 +
364 await preferencesFile.delete();
365 }
366
@@ -492,6 +504,12 @@ class BackupService {
504 _sharedPreferences.getBool(PreferencesKey.shouldRequireTOTP2FAForCreatingNewWallets),
505 PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings: _sharedPreferences
506 .getBool(PreferencesKey.shouldRequireTOTP2FAForAllSecurityAndBackupSettings),
507 + PreferencesKey.sortBalanceBy:
508 + _sharedPreferences.getInt(PreferencesKey.sortBalanceBy),
509 + PreferencesKey.pinNativeTokenAtTop:
510 + _sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop),
511 + PreferencesKey.useEtherscan:
512 + _sharedPreferences.getBool(PreferencesKey.useEtherscan),
513 };
514
515 return json.encode(preferences);
lib/core/fiat_conversion_service.dart
+10 -7
@@ -5,21 +5,20 @@ import 'package:flutter/foundation.dart';
5 import 'package:http/http.dart';
6 import 'package:cake_wallet/.secrets.g.dart' as secrets;
7
8 -
8 const _fiatApiClearNetAuthority = 'fiat-api.cakewallet.com';
9 const _fiatApiOnionAuthority = 'n4z7bdcmwk2oyddxvzaap3x2peqcplh3pzdy7tpkk5ejz5n4mhfvoxqd.onion';
10 const _fiatApiPath = '/v2/rates';
11
12 Future<double> _fetchPrice(Map<String, dynamic> args) async {
14 - final crypto = args['crypto'] as CryptoCurrency;
15 - final fiat = args['fiat'] as FiatCurrency;
13 + final crypto = args['crypto'] as String;
14 + final fiat = args['fiat'] as String;
15 final torOnly = args['torOnly'] as bool;
16
17 final Map<String, String> queryParams = {
18 'interval_count': '1',
20 - 'base': crypto.toString(),
21 - 'quote': fiat.toString(),
22 - 'key' : secrets.fiatApiKey,
19 + 'base': crypto,
20 + 'quote': fiat,
21 + 'key': secrets.fiatApiKey,
22 };
23
24 double price = 0.0;
@@ -52,7 +51,11 @@ Future<double> _fetchPrice(Map<String, dynamic> args) async {
51 }
52
53 Future<double> _fetchPriceAsync(CryptoCurrency crypto, FiatCurrency fiat, bool torOnly) async =>
55 - compute(_fetchPrice, {'fiat': fiat, 'crypto': crypto, 'torOnly': torOnly});
54 + compute(_fetchPrice, {
55 + 'fiat': fiat.toString(),
56 + 'crypto': crypto.toString(),
57 + 'torOnly': torOnly,
58 + });
59
60 class FiatConversionService {
61 static Future<double> fetchPrice({
lib/core/seed_validator.dart
+3
@@ -1,4 +1,5 @@
1 import 'package:cake_wallet/bitcoin/bitcoin.dart';
2 +import 'package:cake_wallet/ethereum/ethereum.dart';
3 import 'package:cake_wallet/haven/haven.dart';
4 import 'package:cake_wallet/core/validator.dart';
5 import 'package:cake_wallet/entities/mnemonic_item.dart';
@@ -25,6 +26,8 @@ class SeedValidator extends Validator<MnemonicItem> {
26 return monero!.getMoneroWordList(language);
27 case WalletType.haven:
28 return haven!.getMoneroWordList(language);
29 + case WalletType.ethereum:
30 + return ethereum!.getEthereumWordList(language);
31 default:
32 return [];
33 }
lib/di.dart
+27 -6
@@ -7,6 +7,7 @@ import 'package:cake_wallet/core/yat_service.dart';
7 import 'package:cake_wallet/entities/exchange_api_mode.dart';
8 import 'package:cake_wallet/entities/parse_address_from_domain.dart';
9 import 'package:cake_wallet/entities/receive_page_option.dart';
10 +import 'package:cake_wallet/ethereum/ethereum.dart';
11 import 'package:cake_wallet/ionia/ionia_anypay.dart';
12 import 'package:cake_wallet/ionia/ionia_gift_card.dart';
13 import 'package:cake_wallet/ionia/ionia_tip.dart';
@@ -16,6 +17,8 @@ import 'package:cake_wallet/src/screens/buy/webview_page.dart';
17 import 'package:cake_wallet/src/screens/dashboard/desktop_dashboard_page.dart';
18 import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar_wrapper.dart';
19 import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart';
20 +import 'package:cake_wallet/src/screens/dashboard/edit_token_page.dart';
21 +import 'package:cake_wallet/src/screens/dashboard/home_settings_page.dart';
22 import 'package:cake_wallet/src/screens/dashboard/widgets/transactions_page.dart';
23 import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
24 import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
@@ -40,6 +43,7 @@ import 'package:cake_wallet/utils/responsive_layout_util.dart';
43 import 'package:cake_wallet/view_model/dashboard/desktop_sidebar_view_model.dart';
44 import 'package:cake_wallet/view_model/anon_invoice_page_view_model.dart';
45 import 'package:cake_wallet/view_model/anonpay_details_view_model.dart';
46 +import 'package:cake_wallet/view_model/dashboard/home_settings_view_model.dart';
47 import 'package:cake_wallet/view_model/dashboard/market_place_view_model.dart';
48 import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
49 import 'package:cake_wallet/view_model/ionia/ionia_auth_view_model.dart';
@@ -70,6 +74,7 @@ import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart
74 import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
75 import 'package:cake_wallet/view_model/wallet_list/wallet_edit_view_model.dart';
76 import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
77 +import 'package:cw_core/erc20_token.dart';
78 import 'package:cw_core/unspent_coins_info.dart';
79 import 'package:cake_wallet/core/backup_service.dart';
80 import 'package:cw_core/wallet_service.dart';
@@ -239,9 +244,9 @@ Future setup({
244 getIt.registerSingletonAsync<SharedPreferences>(() => SharedPreferences.getInstance());
245 }
246
242 - final isBitcoinBuyEnabled = (secrets.wyreSecretKey.isNotEmpty ?? false) &&
243 - (secrets.wyreApiKey.isNotEmpty ?? false) &&
244 - (secrets.wyreAccountId.isNotEmpty ?? false);
247 + final isBitcoinBuyEnabled = (secrets.wyreSecretKey.isNotEmpty) &&
248 + (secrets.wyreApiKey.isNotEmpty) &&
249 + (secrets.wyreAccountId.isNotEmpty);
250
251 final settingsStore = await SettingsStoreBase.load(
252 nodeSource: _nodeSource,
@@ -638,7 +643,7 @@ Future setup({
643 });
644
645 getIt.registerFactory(() {
641 - return PrivacySettingsViewModel(getIt.get<SettingsStore>());
646 + return PrivacySettingsViewModel(getIt.get<SettingsStore>(), getIt.get<AppStore>().wallet!);
647 });
648
649 getIt.registerFactory(() {
@@ -745,6 +750,8 @@ Future setup({
750 return bitcoin!.createBitcoinWalletService(_walletInfoSource, _unspentCoinsInfoSource!);
751 case WalletType.litecoin:
752 return bitcoin!.createLitecoinWalletService(_walletInfoSource, _unspentCoinsInfoSource!);
753 + case WalletType.ethereum:
754 + return ethereum!.createEthereumWalletService(_walletInfoSource);
755 default:
756 throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService');
757 }
@@ -787,8 +794,8 @@ Future setup({
794 transactionDetailsViewModel:
795 getIt.get<TransactionDetailsViewModel>(param1: transactionInfo)));
796
790 - getIt.registerFactoryParam<NewWalletTypePage, void Function(BuildContext, WalletType), void>(
791 - (param1, _) => NewWalletTypePage(onTypeSelected: param1));
797 + getIt.registerFactoryParam<NewWalletTypePage, void Function(BuildContext, WalletType), bool?>(
798 + (param1, isCreate) => NewWalletTypePage(onTypeSelected: param1, isCreate: isCreate ?? true));
799
800 getIt.registerFactoryParam<PreSeedPage, WalletType, void>(
801 (WalletType type, _) => PreSeedPage(type));
@@ -1034,5 +1041,19 @@ Future setup({
1041 getIt.registerFactoryParam<AdvancedPrivacySettingsViewModel, WalletType, void>(
1042 (type, _) => AdvancedPrivacySettingsViewModel(type, getIt.get<SettingsStore>()));
1043
1044 + getIt.registerFactoryParam<HomeSettingsPage, BalanceViewModel, void>((balanceViewModel, _) =>
1045 + HomeSettingsPage(getIt.get<HomeSettingsViewModel>(param1: balanceViewModel)));
1046 +
1047 + getIt.registerFactoryParam<HomeSettingsViewModel, BalanceViewModel, void>(
1048 + (balanceViewModel, _) => HomeSettingsViewModel(getIt.get<SettingsStore>(), balanceViewModel));
1049 +
1050 + getIt.registerFactoryParam<EditTokenPage, HomeSettingsViewModel, Map<String, dynamic>>(
1051 + (homeSettingsViewModel, arguments) => EditTokenPage(
1052 + homeSettingsViewModel: homeSettingsViewModel,
1053 + erc20token: arguments['token'] as Erc20Token?,
1054 + initialContractAddress: arguments['contractAddress'] as String?,
1055 + ),
1056 + );
1057 +
1058 _isSetupFinished = true;
1059 }
lib/entities/default_settings_migration.dart
+44 -2
@@ -26,6 +26,7 @@ const newCakeWalletMoneroUri = 'xmr-node.cakewallet.com:18081';
26 const cakeWalletBitcoinElectrumUri = 'electrum.cakewallet.com:50002';
27 const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002';
28 const havenDefaultNodeUri = 'nodes.havenprotocol.org:443';
29 +const ethereumDefaultNodeUri = 'ethereum.publicnode.com';
30
31 Future defaultSettingsMigration(
32 {required int version,
@@ -157,6 +158,12 @@ Future defaultSettingsMigration(
158 case 20:
159 await migrateExchangeStatus(sharedPreferences);
160 break;
161 + case 21:
162 + await addEthereumNodeList(nodes: nodes);
163 + await changeEthereumCurrentNodeToDefault(
164 + sharedPreferences: sharedPreferences, nodes: nodes);
165 + break;
166 +
167 default:
168 break;
169 }
@@ -242,6 +249,12 @@ Node? getHavenDefaultNode({required Box<Node> nodes}) {
249 ?? nodes.values.firstWhereOrNull((node) => node.type == WalletType.haven);
250 }
251
252 +Node? getEthereumDefaultNode({required Box<Node> nodes}) {
253 + return nodes.values.firstWhereOrNull(
254 + (Node node) => node.uriRaw == ethereumDefaultNodeUri)
255 + ?? nodes.values.firstWhereOrNull((node) => node.type == WalletType.ethereum);
256 +}
257 +
258 Node getMoneroDefaultNode({required Box<Node> nodes}) {
259 final timeZone = DateTime.now().timeZoneOffset.inHours;
260 var nodeUri = '';
@@ -438,6 +451,8 @@ Future<void> checkCurrentNodes(
451 .getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
452 final currentHavenNodeId = sharedPreferences
453 .getInt(PreferencesKey.currentHavenNodeIdKey);
454 + final currentEthereumNodeId = sharedPreferences
455 + .getInt(PreferencesKey.currentEthereumNodeIdKey);
456 final currentMoneroNode = nodeSource.values.firstWhereOrNull(
457 (node) => node.key == currentMoneroNodeId);
458 final currentBitcoinElectrumServer = nodeSource.values.firstWhereOrNull(
@@ -446,6 +461,8 @@ Future<void> checkCurrentNodes(
461 (node) => node.key == currentLitecoinElectrumSeverId);
462 final currentHavenNodeServer = nodeSource.values.firstWhereOrNull(
463 (node) => node.key == currentHavenNodeId);
464 + final currentEthereumNodeServer = nodeSource.values.firstWhereOrNull(
465 + (node) => node.key == currentEthereumNodeId);
466
467 if (currentMoneroNode == null) {
468 final newCakeWalletNode =
@@ -479,6 +496,13 @@ Future<void> checkCurrentNodes(
496 await sharedPreferences.setInt(
497 PreferencesKey.currentHavenNodeIdKey, node.key as int);
498 }
499 +
500 + if (currentEthereumNodeServer == null) {
501 + final node = Node(uri: ethereumDefaultNodeUri, type: WalletType.ethereum);
502 + await nodeSource.add(node);
503 + await sharedPreferences.setInt(
504 + PreferencesKey.currentEthereumNodeIdKey, node.key as int);
505 + }
506 }
507
508 Future<void> resetBitcoinElectrumServer(
@@ -522,8 +546,26 @@ Future<void> migrateExchangeStatus(SharedPreferences sharedPreferences) async {
546 return;
547 }
548
525 - await sharedPreferences.setInt(PreferencesKey.exchangeStatusKey, isExchangeDisabled
549 + await sharedPreferences.setInt(PreferencesKey.exchangeStatusKey, isExchangeDisabled
550 ? ExchangeApiMode.disabled.raw : ExchangeApiMode.enabled.raw);
527 -
551 +
552 await sharedPreferences.remove(PreferencesKey.disableExchangeKey);
553 }
554 +
555 +Future<void> addEthereumNodeList({required Box<Node> nodes}) async {
556 + final nodeList = await loadDefaultEthereumNodes();
557 + for (var node in nodeList) {
558 + if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
559 + await nodes.add(node);
560 + }
561 + }
562 +}
563 +
564 +Future<void> changeEthereumCurrentNodeToDefault(
565 + {required SharedPreferences sharedPreferences,
566 + required Box<Node> nodes}) async {
567 + final node = getEthereumDefaultNode(nodes: nodes);
568 + final nodeId = node?.key as int? ?? 0;
569 +
570 + await sharedPreferences.setInt(PreferencesKey.currentEthereumNodeIdKey, nodeId);
571 +}
lib/entities/main_actions.dart
+2
@@ -46,6 +46,7 @@ class MainActions {
46 switch (walletType) {
47 case WalletType.bitcoin:
48 case WalletType.litecoin:
49 + case WalletType.ethereum:
50 if (viewModel.isEnabledBuyAction) {
51 final uri = getIt.get<OnRamperBuyProvider>().requestUrl();
52 if (DeviceInfo.instance.isMobile) {
@@ -116,6 +117,7 @@ class MainActions {
117 switch (walletType) {
118 case WalletType.bitcoin:
119 case WalletType.litecoin:
120 + case WalletType.ethereum:
121 if (viewModel.isEnabledSellAction) {
122 final moonPaySellProvider = MoonPaySellProvider();
123 final uri = await moonPaySellProvider.requestUrl(
lib/entities/node_list.dart
+16
@@ -70,6 +70,22 @@ Future<List<Node>> loadDefaultHavenNodes() async {
70 return nodes;
71 }
72
73 +Future<List<Node>> loadDefaultEthereumNodes() async {
74 + final nodesRaw = await rootBundle.loadString('assets/ethereum_server_list.yml');
75 + final loadedNodes = loadYaml(nodesRaw) as YamlList;
76 + final nodes = <Node>[];
77 +
78 + for (final raw in loadedNodes) {
79 + if (raw is Map) {
80 + final node = Node.fromMap(Map<String, Object>.from(raw));
81 + node.type = WalletType.ethereum;
82 + nodes.add(node);
83 + }
84 + }
85 +
86 + return nodes;
87 +}
88 +
89 Future resetToDefault(Box<Node> nodeSource) async {
90 final moneroNodes = await loadDefaultNodes();
91 final bitcoinElectrumServerList = await loadBitcoinElectrumServerList();
lib/entities/preferences_key.dart
+5
@@ -5,6 +5,7 @@ class PreferencesKey {
5 static const currentBitcoinElectrumSererIdKey = 'current_node_id_btc';
6 static const currentLitecoinElectrumSererIdKey = 'current_node_id_ltc';
7 static const currentHavenNodeIdKey = 'current_node_id_xhv';
8 + static const currentEthereumNodeIdKey = 'current_node_id_eth';
9 static const currentFiatCurrencyKey = 'current_fiat_currency';
10 static const currentTransactionPriorityKeyLegacy = 'current_fee_priority';
11 static const currentBalanceDisplayModeKey = 'current_balance_display_mode';
@@ -31,6 +32,7 @@ class PreferencesKey {
32 static const bitcoinTransactionPriority = 'current_fee_priority_bitcoin';
33 static const havenTransactionPriority = 'current_fee_priority_haven';
34 static const litecoinTransactionPriority = 'current_fee_priority_litecoin';
35 + static const ethereumTransactionPriority = 'current_fee_priority_ethereum';
36 static const shouldShowReceiveWarning = 'should_show_receive_warning';
37 static const shouldShowYatPopup = 'should_show_yat_popup';
38 static const moneroWalletPasswordUpdateV1Base = 'monero_wallet_update_v1';
@@ -38,6 +40,9 @@ class PreferencesKey {
40 static const lastAuthTimeMilliseconds = 'last_auth_time_milliseconds';
41 static const lastPopupDate = 'last_popup_date';
42 static const lastAppReviewDate = 'last_app_review_date';
43 + static const sortBalanceBy = 'sort_balance_by';
44 + static const pinNativeTokenAtTop = 'pin_native_token_at_top';
45 + static const useEtherscan = 'use_etherscan';
46
47 static String moneroWalletUpdateV1Key(String name) =>
48 '${PreferencesKey.moneroWalletPasswordUpdateV1Base}_${name}';
lib/entities/priority_for_wallet_type.dart
+3
@@ -1,4 +1,5 @@
1 import 'package:cake_wallet/bitcoin/bitcoin.dart';
2 +import 'package:cake_wallet/ethereum/ethereum.dart';
3 import 'package:cake_wallet/haven/haven.dart';
4 import 'package:cake_wallet/monero/monero.dart';
5 import 'package:cw_core/transaction_priority.dart';
@@ -14,6 +15,8 @@ List<TransactionPriority> priorityForWalletType(WalletType type) {
15 return bitcoin!.getLitecoinTransactionPriorities();
16 case WalletType.haven:
17 return haven!.getTransactionPriorities();
18 + case WalletType.ethereum:
19 + return ethereum!.getTransactionPriorities();
20 default:
21 return [];
22 }
lib/entities/sort_balance_types.dart new
+19
@@ -0,0 +1,19 @@
1 +import 'package:cake_wallet/generated/i18n.dart';
2 +
3 +enum SortBalanceBy {
4 + FiatBalance,
5 + GrossBalance,
6 + Alphabetical;
7 +
8 + @override
9 + String toString() {
10 + switch (this) {
11 + case SortBalanceBy.FiatBalance:
12 + return S.current.fiat_balance;
13 + case SortBalanceBy.GrossBalance:
14 + return S.current.gross_balance;
15 + case SortBalanceBy.Alphabetical:
16 + return S.current.alphabetical;
17 + }
18 + }
19 +}
\ No newline at end of file
lib/entities/template.dart
+1 -1
@@ -55,5 +55,5 @@ class Template extends HiveObject {
55
56 String get amount => amountRaw ?? '';
57
58 - List<Template>? get additionalRecipients => additionalRecipientsRaw ?? null;
58 + List<Template>? get additionalRecipients => additionalRecipientsRaw;
59 }
lib/ethereum/cw_ethereum.dart new
+126
@@ -0,0 +1,126 @@
1 +part of 'ethereum.dart';
2 +
3 +class CWEthereum extends Ethereum {
4 + @override
5 + List<String> getEthereumWordList(String language) => EthereumMnemonics.englishWordlist;
6 +
7 + WalletService createEthereumWalletService(Box<WalletInfo> walletInfoSource) =>
8 + EthereumWalletService(walletInfoSource);
9 +
10 + @override
11 + WalletCredentials createEthereumNewWalletCredentials({
12 + required String name,
13 + WalletInfo? walletInfo,
14 + }) =>
15 + EthereumNewWalletCredentials(name: name, walletInfo: walletInfo);
16 +
17 + @override
18 + WalletCredentials createEthereumRestoreWalletFromSeedCredentials({
19 + required String name,
20 + required String mnemonic,
21 + required String password,
22 + }) =>
23 + EthereumRestoreWalletFromSeedCredentials(name: name, password: password, mnemonic: mnemonic);
24 +
25 + @override
26 + String getAddress(WalletBase wallet) => (wallet as EthereumWallet).walletAddresses.address;
27 +
28 + @override
29 + TransactionPriority getDefaultTransactionPriority() => EthereumTransactionPriority.medium;
30 +
31 + @override
32 + List<TransactionPriority> getTransactionPriorities() => EthereumTransactionPriority.all;
33 +
34 + @override
35 + TransactionPriority deserializeEthereumTransactionPriority(int raw) =>
36 + EthereumTransactionPriority.deserialize(raw: raw);
37 +
38 + Object createEthereumTransactionCredentials(
39 + List<Output> outputs, {
40 + required TransactionPriority priority,
41 + required CryptoCurrency currency,
42 + int? feeRate,
43 + }) =>
44 + EthereumTransactionCredentials(
45 + outputs
46 + .map((out) => OutputInfo(
47 + fiatAmount: out.fiatAmount,
48 + cryptoAmount: out.cryptoAmount,
49 + address: out.address,
50 + note: out.note,
51 + sendAll: out.sendAll,
52 + extractedAddress: out.extractedAddress,
53 + isParsedAddress: out.isParsedAddress,
54 + formattedCryptoAmount: out.formattedCryptoAmount))
55 + .toList(),
56 + priority: priority as EthereumTransactionPriority,
57 + currency: currency,
58 + feeRate: feeRate,
59 + );
60 +
61 + Object createEthereumTransactionCredentialsRaw(
62 + List<OutputInfo> outputs, {
63 + TransactionPriority? priority,
64 + required CryptoCurrency currency,
65 + required int feeRate,
66 + }) =>
67 + EthereumTransactionCredentials(
68 + outputs,
69 + priority: priority as EthereumTransactionPriority?,
70 + currency: currency,
71 + feeRate: feeRate,
72 + );
73 +
74 + @override
75 + int formatterEthereumParseAmount(String amount) => EthereumFormatter.parseEthereumAmount(amount);
76 +
77 + @override
78 + double formatterEthereumAmountToDouble(
79 + {TransactionInfo? transaction, BigInt? amount, int exponent = 18}) {
80 + assert(transaction != null || amount != null);
81 +
82 + if (transaction != null) {
83 + transaction as EthereumTransactionInfo;
84 + return transaction.ethAmount / BigInt.from(10).pow(transaction.exponent);
85 + } else {
86 + return (amount!) / BigInt.from(10).pow(exponent);
87 + }
88 + }
89 +
90 + @override
91 + List<Erc20Token> getERC20Currencies(WalletBase wallet) {
92 + final ethereumWallet = wallet as EthereumWallet;
93 + return ethereumWallet.erc20Currencies;
94 + }
95 +
96 + @override
97 + Future<void> addErc20Token(WalletBase wallet, Erc20Token token) async =>
98 + await (wallet as EthereumWallet).addErc20Token(token);
99 +
100 + @override
101 + Future<void> deleteErc20Token(WalletBase wallet, Erc20Token token) async =>
102 + await (wallet as EthereumWallet).deleteErc20Token(token);
103 +
104 + @override
105 + Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress) async {
106 + final ethereumWallet = wallet as EthereumWallet;
107 + return await ethereumWallet.getErc20Token(contractAddress);
108 + }
109 +
110 + @override
111 + CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) {
112 + transaction as EthereumTransactionInfo;
113 + if (transaction.tokenSymbol == CryptoCurrency.eth.title) {
114 + return CryptoCurrency.eth;
115 + }
116 +
117 + wallet as EthereumWallet;
118 + return wallet.erc20Currencies
119 + .firstWhere((element) => transaction.tokenSymbol == element.symbol);
120 + }
121 +
122 + @override
123 + void updateEtherscanUsageState(WalletBase wallet, bool isEnabled) {
124 + (wallet as EthereumWallet).updateEtherscanUsageState(isEnabled);
125 + }
126 +}
lib/main.dart
+1 -1
@@ -141,7 +141,7 @@ Future<void> main() async {
141 transactionDescriptions: transactionDescriptions,
142 secureStorage: secureStorage,
143 anonpayInvoiceInfo: anonpayInvoiceInfo,
144 - initialMigrationVersion: 19);
144 + initialMigrationVersion: 21);
145 runApp(App());
146 }, (error, stackTrace) async {
147 ExceptionHandler.onError(FlutterErrorDetails(exception: error, stack: stackTrace));
lib/reactions/fiat_rate_update.dart
+15
@@ -2,6 +2,7 @@ import 'dart:async';
2 import 'package:cake_wallet/core/fiat_conversion_service.dart';
3 import 'package:cake_wallet/entities/fiat_api_mode.dart';
4 import 'package:cake_wallet/entities/update_haven_rate.dart';
5 +import 'package:cake_wallet/ethereum/ethereum.dart';
6 import 'package:cake_wallet/store/app_store.dart';
7 import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
8 import 'package:cake_wallet/store/settings_store.dart';
@@ -31,6 +32,20 @@ Future<void> startFiatRateUpdate(
32 fiat: settingsStore.fiatCurrency,
33 torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly);
34 }
35 +
36 + if (appStore.wallet!.type == WalletType.ethereum) {
37 + final currencies =
38 + ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
39 +
40 + for (final currency in currencies) {
41 + () async {
42 + fiatConversionStore.prices[currency] = await FiatConversionService.fetchPrice(
43 + crypto: currency,
44 + fiat: settingsStore.fiatCurrency,
45 + torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly);
46 + }.call();
47 + }
48 + }
49 } catch (e) {
50 print(e);
51 }
lib/reactions/on_current_wallet_change.dart
+15 -1
@@ -1,6 +1,6 @@
1 import 'package:cake_wallet/entities/fiat_api_mode.dart';
2 -import 'package:cake_wallet/entities/fiat_currency.dart';
2 import 'package:cake_wallet/entities/update_haven_rate.dart';
3 +import 'package:cake_wallet/ethereum/ethereum.dart';
4 import 'package:cw_core/transaction_history.dart';
5 import 'package:cw_core/balance.dart';
6 import 'package:cw_core/transaction_info.dart';
@@ -97,6 +97,20 @@ void startCurrentWalletChangeReaction(AppStore appStore,
97 crypto: wallet.currency,
98 fiat: settingsStore.fiatCurrency,
99 torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly);
100 +
101 + if (wallet.type == WalletType.ethereum) {
102 + final currencies =
103 + ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
104 +
105 + for (final currency in currencies) {
106 + () async {
107 + fiatConversionStore.prices[currency] = await FiatConversionService.fetchPrice(
108 + crypto: currency,
109 + fiat: settingsStore.fiatCurrency,
110 + torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly);
111 + }.call();
112 + }
113 + }
114 } catch (e) {
115 print(e.toString());
116 }
lib/router.dart
+29 -8
@@ -10,6 +10,8 @@ import 'package:cake_wallet/src/screens/backup/edit_backup_password_page.dart';
10 import 'package:cake_wallet/src/screens/buy/buy_webview_page.dart';
11 import 'package:cake_wallet/src/screens/buy/webview_page.dart';
12 import 'package:cake_wallet/src/screens/buy/pre_order_page.dart';
13 +import 'package:cake_wallet/src/screens/dashboard/edit_token_page.dart';
14 +import 'package:cake_wallet/src/screens/dashboard/home_settings_page.dart';
15 import 'package:cake_wallet/src/screens/restore/sweeping_wallet_page.dart';
16 import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
17 import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
@@ -313,7 +315,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
315 return CupertinoPageRoute<void>(
316 fullscreenDialog: true,
317 builder: (_) => getIt.get<SecurityBackupPage>());
316 -
318 +
319 case Routes.privacyPage:
320 return CupertinoPageRoute<void>(
321 fullscreenDialog: true,
@@ -328,7 +330,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
330 return CupertinoPageRoute<void>(
331 fullscreenDialog: true,
332 builder: (_) => getIt.get<OtherSettingsPage>());
331 -
333 +
334 case Routes.newNode:
335 final args = settings.arguments as Map<String, dynamic>?;
336 return CupertinoPageRoute<void>(
@@ -336,7 +338,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
338 param1: args?['editingNode'] as Node?,
339 param2: args?['isSelected'] as bool?));
340
339 -
341 +
342
343 case Routes.accountCreation:
344 return CupertinoPageRoute<String>(
@@ -466,7 +468,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
468 fullscreenDialog: true,
469 builder: (_) => getIt.get<IoniaWelcomePage>(),
470 );
469 -
471 +
472 case Routes.ioniaLoginPage:
473 return CupertinoPageRoute<void>( builder: (_) => getIt.get<IoniaLoginPage>());
474
@@ -480,7 +482,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
482 case Routes.ioniaBuyGiftCardPage:
483 final args = settings.arguments as List;
484 return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaBuyGiftCardPage>(param1: args));
483 -
485 +
486 case Routes.ioniaBuyGiftCardDetailPage:
487 final args = settings.arguments as List;
488 return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaBuyGiftCardDetailPage>(param1: args));
@@ -497,7 +499,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
499
500 case Routes.ioniaAccountPage:
501 return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaAccountPage>());
500 -
502 +
503 case Routes.ioniaAccountCardsPage:
504 return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaAccountCardsPage>());
505
@@ -508,11 +510,11 @@ Route<dynamic> createRoute(RouteSettings settings) {
510 case Routes.ioniaGiftCardDetailPage:
511 final args = settings.arguments as List;
512 return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaGiftCardDetailPage>(param1: args.first));
511 -
513 +
514 case Routes.ioniaCustomRedeemPage:
515 final args = settings.arguments as List;
516 return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaCustomRedeemPage>(param1: args));
515 -
517 +
518 case Routes.ioniaMoreOptionsPage:
519 final args = settings.arguments as List;
520 return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaMoreOptionsPage>(param1: args));
@@ -584,6 +586,25 @@ Route<dynamic> createRoute(RouteSettings settings) {
586 case Routes.modify2FAPage:
587 return MaterialPageRoute<void>(builder: (_) => getIt.get<Modify2FAPage>());
588
589 + case Routes.homeSettings:
590 + return CupertinoPageRoute<void>(
591 + builder: (_) => getIt.get<HomeSettingsPage>(param1: settings.arguments),
592 + );
593 +
594 + case Routes.editToken:
595 + final args = settings.arguments as Map<String, dynamic>;
596 +
597 + return CupertinoPageRoute<void>(
598 + settings: RouteSettings(name: Routes.editToken),
599 + builder: (_) => getIt.get<EditTokenPage>(
600 + param1: args['homeSettingsViewModel'],
601 + param2: {
602 + 'token': args['token'],
603 + 'contractAddress': args['contractAddress'],
604 + },
605 + ),
606 + );
607 +
608 default:
609 return MaterialPageRoute<void>(
610 builder: (_) => Scaffold(
lib/routes.dart
+2
@@ -88,4 +88,6 @@ class Routes {
88 static const setup_2faQRPage = '/setup_2fa_qr_page';
89 static const totpAuthCodePage = '/totp_auth_code_page';
90 static const modify2FAPage = '/modify_2fa_page';
91 + static const homeSettings = '/home_settings';
92 + static const editToken = '/edit_token';
93 }
lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart
+3
@@ -30,6 +30,7 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
30 final bitcoinIcon = Image.asset('assets/images/bitcoin.png', height: 24, width: 24);
31 final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24);
32 final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24);
33 + final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24);
34 final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
35
36 Image _newWalletImage(BuildContext context) => Image.asset(
@@ -136,6 +137,8 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
137 return litecoinIcon;
138 case WalletType.haven:
139 return havenIcon;
140 + case WalletType.ethereum:
141 + return ethereumIcon;
142 default:
143 return nonWalletTypeIcon;
144 }
lib/src/screens/dashboard/edit_token_page.dart new
+309
@@ -0,0 +1,309 @@
1 +import 'package:cake_wallet/core/address_validator.dart';
2 +import 'package:cake_wallet/generated/i18n.dart';
3 +import 'package:cake_wallet/src/screens/base_page.dart';
4 +import 'package:cake_wallet/src/widgets/address_text_field.dart';
5 +import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
6 +import 'package:cake_wallet/src/widgets/checkbox_widget.dart';
7 +import 'package:cake_wallet/src/widgets/primary_button.dart';
8 +import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
9 +import 'package:cake_wallet/view_model/dashboard/home_settings_view_model.dart';
10 +import 'package:cw_core/erc20_token.dart';
11 +import 'package:flutter/material.dart';
12 +import 'package:flutter/services.dart';
13 +
14 +class EditTokenPage extends BasePage {
15 + EditTokenPage({
16 + Key? key,
17 + required this.homeSettingsViewModel,
18 + this.erc20token,
19 + this.initialContractAddress,
20 + }) : assert(erc20token == null || initialContractAddress == null);
21 +
22 + final HomeSettingsViewModel homeSettingsViewModel;
23 + final Erc20Token? erc20token;
24 + final String? initialContractAddress;
25 +
26 + @override
27 + String? get title => S.current.edit_token;
28 +
29 + @override
30 + Widget body(BuildContext context) {
31 + return EditTokenPageBody(
32 + homeSettingsViewModel: homeSettingsViewModel,
33 + erc20token: erc20token,
34 + initialContractAddress: initialContractAddress,
35 + );
36 + }
37 +}
38 +
39 +class EditTokenPageBody extends StatefulWidget {
40 + const EditTokenPageBody({
41 + Key? key,
42 + required this.homeSettingsViewModel,
43 + this.erc20token,
44 + this.initialContractAddress,
45 + }) : super(key: key);
46 +
47 + final HomeSettingsViewModel homeSettingsViewModel;
48 + final Erc20Token? erc20token;
49 + final String? initialContractAddress;
50 +
51 + @override
52 + State<EditTokenPageBody> createState() => _EditTokenPageBodyState();
53 +}
54 +
55 +class _EditTokenPageBodyState extends State<EditTokenPageBody> {
56 + final TextEditingController _contractAddressController = TextEditingController();
57 + final TextEditingController _tokenNameController = TextEditingController();
58 + final TextEditingController _tokenSymbolController = TextEditingController();
59 + final TextEditingController _tokenDecimalController = TextEditingController();
60 +
61 + final FocusNode _contractAddressFocusNode = FocusNode();
62 + final FocusNode _tokenNameFocusNode = FocusNode();
63 + final FocusNode _tokenSymbolFocusNode = FocusNode();
64 + final FocusNode _tokenDecimalFocusNode = FocusNode();
65 +
66 + final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
67 +
68 + bool _showDisclaimer = false;
69 + bool _disclaimerChecked = false;
70 +
71 + @override
72 + void initState() {
73 + super.initState();
74 +
75 + if (widget.erc20token != null) {
76 + _contractAddressController.text = widget.erc20token!.contractAddress;
77 + _tokenNameController.text = widget.erc20token!.name;
78 + _tokenSymbolController.text = widget.erc20token!.symbol;
79 + _tokenDecimalController.text = widget.erc20token!.decimal.toString();
80 + }
81 +
82 + if (widget.initialContractAddress != null) {
83 + _contractAddressController.text = widget.initialContractAddress!;
84 + _getTokenInfo();
85 + }
86 +
87 + _contractAddressFocusNode.addListener(() {
88 + if (!_contractAddressFocusNode.hasFocus) {
89 + _getTokenInfo();
90 + }
91 +
92 + final contractAddress = _contractAddressController.text;
93 + if (contractAddress.isNotEmpty && contractAddress != widget.erc20token?.contractAddress) {
94 + setState(() {
95 + _showDisclaimer = true;
96 + });
97 + }
98 + });
99 + }
100 +
101 + @override
102 + Widget build(BuildContext context) {
103 + return GestureDetector(
104 + onTap: () => FocusScope.of(context).unfocus(),
105 + child: ScrollableWithBottomSection(
106 + contentPadding: EdgeInsets.zero,
107 + content: Padding(
108 + padding: EdgeInsets.symmetric(horizontal: 25),
109 + child: Column(
110 + children: [
111 + Container(
112 + padding: EdgeInsets.symmetric(vertical: 16, horizontal: 28),
113 + decoration: BoxDecoration(
114 + color: Theme.of(context).accentTextTheme.bodySmall!.color!,
115 + borderRadius: BorderRadius.circular(12),
116 + ),
117 + child: Row(
118 + crossAxisAlignment: CrossAxisAlignment.start,
119 + children: [
120 + Image.asset('assets/images/restore_keys.png'),
121 + const SizedBox(width: 24),
122 + Expanded(
123 + child: Column(
124 + crossAxisAlignment: CrossAxisAlignment.start,
125 + children: [
126 + Text(
127 + S.of(context).warning,
128 + style: TextStyle(
129 + fontSize: 16,
130 + fontWeight: FontWeight.w500,
131 + color: Theme.of(context).primaryTextTheme.titleLarge!.color!,
132 + ),
133 + ),
134 + Padding(
135 + padding: EdgeInsets.only(top: 5),
136 + child: Text(
137 + S.of(context).add_token_warning,
138 + style: TextStyle(
139 + fontSize: 14,
140 + fontWeight: FontWeight.normal,
141 + color: Theme.of(context).primaryTextTheme.labelSmall!.color!,
142 + ),
143 + ),
144 + ),
145 + ],
146 + ),
147 + ),
148 + ],
149 + ),
150 + ),
151 + SizedBox(height: 50),
152 + _tokenForm(),
153 + ],
154 + ),
155 + ),
156 + bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
157 + bottomSection: Column(
158 + children: [
159 + if (_showDisclaimer) ...[
160 + CheckboxWidget(
161 + value: _disclaimerChecked,
162 + caption: S.of(context).add_token_disclaimer_check,
163 + onChanged: (value) {
164 + _disclaimerChecked = value;
165 + },
166 + ),
167 + SizedBox(height: 20),
168 + ],
169 + Row(
170 + children: <Widget>[
171 + Expanded(
172 + child: PrimaryButton(
173 + onPressed: () async {
174 + if (widget.erc20token != null) {
175 + await widget.homeSettingsViewModel.deleteErc20Token(widget.erc20token!);
176 + }
177 + Navigator.pop(context);
178 + },
179 + text: widget.erc20token != null ? S.of(context).delete : S.of(context).cancel,
180 + color: Colors.red,
181 + textColor: Colors.white,
182 + ),
183 + ),
184 + SizedBox(width: 20),
185 + Expanded(
186 + child: PrimaryButton(
187 + onPressed: () async {
188 + if (_formKey.currentState!.validate() &&
189 + (!_showDisclaimer || _disclaimerChecked)) {
190 + await widget.homeSettingsViewModel.addErc20Token(Erc20Token(
191 + name: _tokenNameController.text,
192 + symbol: _tokenSymbolController.text,
193 + contractAddress: _contractAddressController.text,
194 + decimal: int.parse(_tokenDecimalController.text),
195 + ));
196 + Navigator.pop(context);
197 + }
198 + },
199 + text: S.of(context).save,
200 + color: Theme.of(context).accentTextTheme.bodyLarge!.color!,
201 + textColor: Colors.white,
202 + ),
203 + ),
204 + ],
205 + ),
206 + ],
207 + ),
208 + ),
209 + );
210 + }
211 +
212 + void _getTokenInfo() async {
213 + if (_contractAddressController.text.isNotEmpty) {
214 + final token =
215 + await widget.homeSettingsViewModel.getErc20Token(_contractAddressController.text);
216 +
217 + if (token != null) {
218 + if (_tokenNameController.text.isEmpty) _tokenNameController.text = token.name;
219 + if (_tokenSymbolController.text.isEmpty) _tokenSymbolController.text = token.symbol;
220 + if (_tokenDecimalController.text.isEmpty)
221 + _tokenDecimalController.text = token.decimal.toString();
222 + }
223 + }
224 + }
225 +
226 + Future<void> _pasteText() async {
227 + final value = await Clipboard.getData('text/plain');
228 +
229 + if (value?.text?.isNotEmpty ?? false) {
230 + _contractAddressController.text = value!.text!;
231 +
232 + _getTokenInfo();
233 + setState(() {
234 + _showDisclaimer = true;
235 + });
236 + }
237 + }
238 +
239 + Widget _tokenForm() {
240 + return Form(
241 + key: _formKey,
242 + child: Column(
243 + mainAxisSize: MainAxisSize.min,
244 + crossAxisAlignment: CrossAxisAlignment.stretch,
245 + children: [
246 + AddressTextField(
247 + controller: _contractAddressController,
248 + focusNode: _contractAddressFocusNode,
249 + placeholder: S.of(context).token_contract_address,
250 + options: [AddressTextFieldOption.paste],
251 + buttonColor: Theme.of(context).hintColor,
252 + validator: AddressValidator(type: widget.homeSettingsViewModel.nativeToken),
253 + onPushPasteButton: (_) {
254 + _pasteText();
255 + },
256 + ),
257 + const SizedBox(height: 8),
258 + BaseTextFormField(
259 + controller: _tokenNameController,
260 + focusNode: _tokenNameFocusNode,
261 + onSubmit: (_) => FocusScope.of(context).requestFocus(_tokenSymbolFocusNode),
262 + textInputAction: TextInputAction.next,
263 + hintText: S.of(context).token_name,
264 + validator: (text) {
265 + if (text?.isNotEmpty ?? false) {
266 + return null;
267 + }
268 +
269 + return S.of(context).field_required;
270 + },
271 + ),
272 + const SizedBox(height: 8),
273 + BaseTextFormField(
274 + controller: _tokenSymbolController,
275 + focusNode: _tokenSymbolFocusNode,
276 + onSubmit: (_) => FocusScope.of(context).requestFocus(_tokenDecimalFocusNode),
277 + textInputAction: TextInputAction.next,
278 + hintText: S.of(context).token_symbol,
279 + validator: (text) {
280 + if (text?.isNotEmpty ?? false) {
281 + return null;
282 + }
283 +
284 + return S.of(context).field_required;
285 + },
286 + ),
287 + const SizedBox(height: 8),
288 + BaseTextFormField(
289 + controller: _tokenDecimalController,
290 + focusNode: _tokenDecimalFocusNode,
291 + textInputAction: TextInputAction.done,
292 + hintText: S.of(context).token_decimal,
293 + validator: (text) {
294 + if (text?.isEmpty ?? true) {
295 + return S.of(context).field_required;
296 + }
297 + if (int.tryParse(text!) == null) {
298 + return S.of(context).invalid_input;
299 + }
300 +
301 + return null;
302 + },
303 + ),
304 + SizedBox(height: 24),
305 + ],
306 + ),
307 + );
308 + }
309 +}
lib/src/screens/dashboard/home_settings_page.dart new
+164
@@ -0,0 +1,164 @@
1 +import 'dart:math';
2 +
3 +import 'package:cake_wallet/core/address_validator.dart';
4 +import 'package:cake_wallet/entities/sort_balance_types.dart';
5 +import 'package:cake_wallet/generated/i18n.dart';
6 +import 'package:cake_wallet/routes.dart';
7 +import 'package:cake_wallet/src/screens/base_page.dart';
8 +import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart';
9 +import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
10 +import 'package:cake_wallet/view_model/dashboard/home_settings_view_model.dart';
11 +import 'package:flutter/material.dart';
12 +import 'package:flutter_mobx/flutter_mobx.dart';
13 +
14 +class HomeSettingsPage extends BasePage {
15 + HomeSettingsPage(this._homeSettingsViewModel);
16 +
17 + final HomeSettingsViewModel _homeSettingsViewModel;
18 +
19 + final TextEditingController _searchController = TextEditingController();
20 +
21 + @override
22 + String? get title => S.current.home_screen_settings;
23 +
24 + @override
25 + Widget body(BuildContext context) {
26 + return SingleChildScrollView(
27 + child: Column(
28 + children: [
29 + Observer(
30 + builder: (_) => SettingsPickerCell<SortBalanceBy>(
31 + title: S.current.sort_by,
32 + items: SortBalanceBy.values,
33 + selectedItem: _homeSettingsViewModel.sortBalanceBy,
34 + onItemSelected: _homeSettingsViewModel.setSortBalanceBy,
35 + ),
36 + ),
37 + Divider(color: Theme.of(context).primaryTextTheme.bodySmall!.decorationColor!),
38 + Observer(
39 + builder: (_) => SettingsSwitcherCell(
40 + title: S.of(context).pin_at_top(_homeSettingsViewModel.nativeToken.title),
41 + value: _homeSettingsViewModel.pinNativeToken,
42 + onValueChange: (_, bool value) {
43 + _homeSettingsViewModel.setPinNativeToken(value);
44 + },
45 + ),
46 + ),
47 + Divider(color: Theme.of(context).primaryTextTheme.bodySmall!.decorationColor!),
48 + const SizedBox(height: 20),
49 + Row(
50 + children: [
51 + Expanded(
52 + child: Padding(
53 + padding: const EdgeInsetsDirectional.only(start: 16),
54 + child: TextFormField(
55 + controller: _searchController,
56 + style: TextStyle(color: Theme.of(context).primaryTextTheme.titleLarge!.color!),
57 + decoration: InputDecoration(
58 + hintText: S.of(context).search_add_token,
59 + prefixIcon: Image.asset("assets/images/search_icon.png"),
60 + filled: true,
61 + fillColor: Theme.of(context).accentTextTheme.displaySmall!.color!,
62 + alignLabelWithHint: false,
63 + contentPadding: const EdgeInsets.symmetric(vertical: 4, horizontal: 16),
64 + enabledBorder: OutlineInputBorder(
65 + borderRadius: BorderRadius.circular(30),
66 + borderSide: const BorderSide(color: Colors.transparent),
67 + ),
68 + focusedBorder: OutlineInputBorder(
69 + borderRadius: BorderRadius.circular(30),
70 + borderSide: const BorderSide(color: Colors.transparent),
71 + ),
72 + ),
73 + onChanged: (String text) => _homeSettingsViewModel.changeSearchText(text),
74 + ),
75 + ),
76 + ),
77 + RawMaterialButton(
78 + onPressed: () async {
79 + Navigator.pushNamed(context, Routes.editToken, arguments: {
80 + 'homeSettingsViewModel': _homeSettingsViewModel,
81 + if (AddressValidator(type: _homeSettingsViewModel.nativeToken)
82 + .isValid(_searchController.text))
83 + 'contractAddress': _searchController.text,
84 + });
85 + },
86 + elevation: 0,
87 + fillColor: Theme.of(context).accentTextTheme.bodySmall!.color!,
88 + child: Icon(
89 + Icons.add,
90 + color: Theme.of(context).primaryTextTheme.titleLarge!.color!,
91 + size: 22.0,
92 + ),
93 + padding: EdgeInsets.all(12),
94 + shape: CircleBorder(),
95 + splashColor: Theme.of(context).accentTextTheme.bodySmall!.color!,
96 + ),
97 + ],
98 + ),
99 + Padding(
100 + padding: const EdgeInsets.only(bottom: 16, left: 16, right: 16),
101 + child: Observer(
102 + builder: (_) => ListView.builder(
103 + itemCount: _homeSettingsViewModel.tokens.length,
104 + shrinkWrap: true,
105 + physics: NeverScrollableScrollPhysics(),
106 + itemBuilder: (context, index) {
107 + return Container(
108 + margin: EdgeInsets.only(top: 16),
109 + child: Observer(
110 + builder: (_) {
111 + final token = _homeSettingsViewModel.tokens.elementAt(index);
112 +
113 + return SettingsSwitcherCell(
114 + title: "${token.name} "
115 + "(${token.symbol})",
116 + value: token.enabled,
117 + onValueChange: (_, bool value) {
118 + _homeSettingsViewModel.changeTokenAvailability(token, value);
119 + },
120 + onTap: (_) {
121 + Navigator.pushNamed(context, Routes.editToken, arguments: {
122 + 'homeSettingsViewModel': _homeSettingsViewModel,
123 + 'token': token,
124 + });
125 + },
126 + leading: token.iconPath != null
127 + ? Container(
128 + child: Image.asset(
129 + token.iconPath!,
130 + height: 30.0,
131 + width: 30.0,
132 + ),
133 + )
134 + : Container(
135 + height: 30.0,
136 + width: 30.0,
137 + child: Center(
138 + child: Text(
139 + token.symbol.substring(0, min(token.symbol.length, 2)),
140 + style: TextStyle(fontSize: 11),
141 + ),
142 + ),
143 + decoration: BoxDecoration(
144 + shape: BoxShape.circle,
145 + color: Colors.grey.shade400,
146 + ),
147 + ),
148 + decoration: BoxDecoration(
149 + color: Theme.of(context).accentTextTheme.bodySmall!.color!,
150 + borderRadius: BorderRadius.circular(30),
151 + ),
152 + );
153 + },
154 + ),
155 + );
156 + },
157 + ),
158 + ),
159 + ),
160 + ],
161 + ),
162 + );
163 + }
164 +}
lib/src/screens/dashboard/widgets/address_page.dart
+65 -87
@@ -27,15 +27,15 @@ class AddressPage extends BasePage {
27 required this.addressListViewModel,
28 required this.dashboardViewModel,
29 required this.receiveOptionViewModel,
30 - }) : _cryptoAmountFocus = FocusNode(),
31 - _formKey = GlobalKey<FormState>(),
32 - _amountController = TextEditingController(){
33 - _amountController.addListener(() {
34 - if (_formKey.currentState!.validate()) {
35 - addressListViewModel.changeAmount(
36 - _amountController.text,
37 - );
38 - }
30 + }) : _cryptoAmountFocus = FocusNode(),
31 + _formKey = GlobalKey<FormState>(),
32 + _amountController = TextEditingController() {
33 + _amountController.addListener(() {
34 + if (_formKey.currentState!.validate()) {
35 + addressListViewModel.changeAmount(
36 + _amountController.text,
37 + );
38 + }
39 });
40 }
41
@@ -63,15 +63,11 @@ class AddressPage extends BasePage {
63 Widget? leading(BuildContext context) {
64 final _backButton = Icon(
65 Icons.arrow_back_ios,
66 - color: Theme.of(context)
67 - .accentTextTheme!
68 - .displayMedium!
69 - .backgroundColor!,
66 + color: Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
67 size: 16,
68 );
72 - final _closeButton = currentTheme.type == ThemeType.dark
73 - ? closeButtonImageDarkTheme
74 - : closeButtonImage;
69 + final _closeButton =
70 + currentTheme.type == ThemeType.dark ? closeButtonImageDarkTheme : closeButtonImage;
71
72 bool isMobileView = ResponsiveLayoutUtil.instance.isMobile;
73
@@ -82,13 +78,10 @@ class AddressPage extends BasePage {
78 child: ButtonTheme(
79 minWidth: double.minPositive,
80 child: Semantics(
85 - label: !isMobileView
86 - ? S.of(context).close
87 - : S.of(context).seed_alert_back,
81 + label: !isMobileView ? S.of(context).close : S.of(context).seed_alert_back,
82 child: TextButton(
83 style: ButtonStyle(
90 - overlayColor: MaterialStateColor.resolveWith(
91 - (states) => Colors.transparent),
84 + overlayColor: MaterialStateColor.resolveWith((states) => Colors.transparent),
85 ),
86 onPressed: () => onClose(context),
87 child: !isMobileView ? _closeButton : _backButton,
@@ -100,8 +93,7 @@ class AddressPage extends BasePage {
93 }
94
95 @override
103 - Widget middle(BuildContext context) =>
104 - PresentReceiveOptionPicker(
96 + Widget middle(BuildContext context) => PresentReceiveOptionPicker(
97 receiveOptionViewModel: receiveOptionViewModel,
98 hasWhiteBackground: currentTheme.type == ThemeType.light,
99 );
@@ -136,10 +128,7 @@ class AddressPage extends BasePage {
128 icon: Icon(
129 Icons.share,
130 size: 20,
139 - color: Theme.of(context)
140 - .accentTextTheme!
141 - .displayMedium!
142 - .backgroundColor!,
131 + color: Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
132 ),
133 ),
134 );
@@ -180,10 +169,7 @@ class AddressPage extends BasePage {
169 tapOutsideToDismiss: true,
170 config: KeyboardActionsConfig(
171 keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
183 - keyboardBarColor: Theme.of(context)
184 - .accentTextTheme!
185 - .bodyLarge!
186 - .backgroundColor!,
172 + keyboardBarColor: Theme.of(context).accentTextTheme.bodyLarge!.backgroundColor!,
173 nextFocus: false,
174 actions: [
175 KeyboardActionsItem(
@@ -205,62 +191,54 @@ class AddressPage extends BasePage {
191 isLight: dashboardViewModel.settingsStore.currentTheme.type ==
192 ThemeType.light))),
193 Observer(builder: (_) {
208 - return addressListViewModel.hasAddressList
209 - ? GestureDetector(
210 - onTap: () => Navigator.of(context).pushNamed(Routes.receive),
211 - child: Container(
212 - height: 50,
213 - padding: EdgeInsets.only(left: 24, right: 12),
214 - alignment: Alignment.center,
215 - decoration: BoxDecoration(
216 - borderRadius: BorderRadius.all(Radius.circular(25)),
217 - border: Border.all(
218 - color: Theme.of(context)
219 - .textTheme!
220 - .titleMedium!
221 - .color!,
222 - width: 1),
223 - color: Theme.of(context)
224 - .textTheme!
225 - .titleLarge!
226 - .backgroundColor!),
227 - child: Row(
228 - mainAxisSize: MainAxisSize.max,
229 - mainAxisAlignment: MainAxisAlignment.spaceBetween,
230 - children: <Widget>[
231 - Observer(
232 - builder: (_) => Text(
233 - addressListViewModel.hasAccounts
234 - ? S.of(context).accounts_subaddresses
235 - : S.of(context).addresses,
236 - style: TextStyle(
237 - fontSize: 14,
238 - fontWeight: FontWeight.w500,
239 - color: Theme.of(context)
240 - .accentTextTheme!
241 - .displayMedium!
242 - .backgroundColor!),
243 - )),
244 - Icon(
245 - Icons.arrow_forward_ios,
246 - size: 14,
247 - color: Theme.of(context)
248 - .accentTextTheme!
249 - .displayMedium!
250 - .backgroundColor!,
251 - )
252 - ],
253 - ),
254 - ),
255 - )
256 - : Text(S.of(context).electrum_address_disclaimer,
257 - textAlign: TextAlign.center,
258 - style: TextStyle(
259 - fontSize: 15,
260 - color: Theme.of(context)
261 - .accentTextTheme!
262 - .displaySmall!
263 - .backgroundColor!));
194 + if (addressListViewModel.hasAddressList) {
195 + return GestureDetector(
196 + onTap: () => Navigator.of(context).pushNamed(Routes.receive),
197 + child: Container(
198 + height: 50,
199 + padding: EdgeInsets.only(left: 24, right: 12),
200 + alignment: Alignment.center,
201 + decoration: BoxDecoration(
202 + borderRadius: BorderRadius.all(Radius.circular(25)),
203 + border: Border.all(
204 + color: Theme.of(context).textTheme.titleMedium!.color!, width: 1),
205 + color: Theme.of(context).textTheme.titleLarge!.backgroundColor!),
206 + child: Row(
207 + mainAxisSize: MainAxisSize.max,
208 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
209 + children: <Widget>[
210 + Observer(
211 + builder: (_) => Text(
212 + addressListViewModel.hasAccounts
213 + ? S.of(context).accounts_subaddresses
214 + : S.of(context).addresses,
215 + style: TextStyle(
216 + fontSize: 14,
217 + fontWeight: FontWeight.w500,
218 + color: Theme.of(context)
219 + .accentTextTheme.displayMedium!
220 + .backgroundColor!),
221 + )),
222 + Icon(
223 + Icons.arrow_forward_ios,
224 + size: 14,
225 + color:
226 + Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
227 + )
228 + ],
229 + ),
230 + ),
231 + );
232 + } else if (addressListViewModel.showElectrumAddressDisclaimer) {
233 + return Text(S.of(context).electrum_address_disclaimer,
234 + textAlign: TextAlign.center,
235 + style: TextStyle(
236 + fontSize: 15,
237 + color:
238 + Theme.of(context).accentTextTheme.displaySmall!.backgroundColor!));
239 + } else {
240 + return const SizedBox();
241 + }
242 })
243 ],
244 ),
lib/src/screens/dashboard/widgets/balance_page.dart
+205 -167
@@ -1,8 +1,8 @@
1 +import 'package:cake_wallet/routes.dart';
2 import 'package:cake_wallet/src/screens/exchange_trade/information_page.dart';
3 import 'package:cake_wallet/store/settings_store.dart';
4 import 'package:cake_wallet/themes/theme_base.dart';
5 import 'package:cake_wallet/utils/feature_flag.dart';
5 -import 'package:cake_wallet/utils/responsive_layout_util.dart';
6 import 'package:cake_wallet/utils/show_pop_up.dart';
7 import 'package:flutter/material.dart';
8 import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
@@ -20,51 +20,78 @@ class BalancePage extends StatelessWidget {
20 @override
21 Widget build(BuildContext context) {
22 return GestureDetector(
23 - onLongPress: () => dashboardViewModel.balanceViewModel.isReversing =
24 - !dashboardViewModel.balanceViewModel.isReversing,
25 - onLongPressUp: () => dashboardViewModel.balanceViewModel.isReversing =
26 - !dashboardViewModel.balanceViewModel.isReversing,
27 - child: SingleChildScrollView(
28 - child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
29 - SizedBox(height: 56),
30 - Container(
23 + onLongPress: () => dashboardViewModel.balanceViewModel.isReversing =
24 + !dashboardViewModel.balanceViewModel.isReversing,
25 + onLongPressUp: () => dashboardViewModel.balanceViewModel.isReversing =
26 + !dashboardViewModel.balanceViewModel.isReversing,
27 + child: SingleChildScrollView(
28 + child: Column(
29 + crossAxisAlignment: CrossAxisAlignment.start,
30 + children: [
31 + SizedBox(height: 56),
32 + Container(
33 margin: const EdgeInsets.only(left: 24, bottom: 16),
32 - child: Observer(builder: (_) {
33 - return Text(dashboardViewModel.balanceViewModel.asset,
34 - style: TextStyle(
35 - fontSize: 24,
36 - fontFamily: 'Lato',
37 - fontWeight: FontWeight.w600,
38 - color: Theme.of(context)
39 - .accentTextTheme!
40 - .displayMedium!
41 - .backgroundColor!,
42 - height: 1),
43 - maxLines: 1,
44 - textAlign: TextAlign.center);
45 - })),
46 - Observer(builder: (_) {
47 - if (dashboardViewModel.balanceViewModel.isShowCard && FeatureFlag.isCakePayEnabled) {
48 - return IntroducingCard(
49 - title: S.of(context).introducing_cake_pay,
50 - subTitle: S.of(context).cake_pay_learn_more,
51 - borderColor: settingsStore.currentTheme.type == ThemeType.bright
52 - ? Color.fromRGBO(255, 255, 255, 0.2)
53 - : Colors.transparent,
54 - closeCard: dashboardViewModel.balanceViewModel.disableIntroCakePayCard);
55 - }
56 - return Container();
57 - }),
58 - Observer(builder: (_) {
59 - return ListView.separated(
60 - physics: NeverScrollableScrollPhysics(),
61 - shrinkWrap: true,
62 - separatorBuilder: (_, __) => Container(padding: EdgeInsets.only(bottom: 8)),
63 - itemCount: dashboardViewModel.balanceViewModel.formattedBalances.length,
64 - itemBuilder: (__, index) {
65 - final balance =
66 - dashboardViewModel.balanceViewModel.formattedBalances.elementAt(index);
67 - return buildBalanceRow(context,
34 + child: Observer(
35 + builder: (_) {
36 + return Row(
37 + children: [
38 + Text(
39 + dashboardViewModel.balanceViewModel.asset,
40 + style: TextStyle(
41 + fontSize: 24,
42 + fontFamily: 'Lato',
43 + fontWeight: FontWeight.w600,
44 + color: Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
45 + height: 1,
46 + ),
47 + maxLines: 1,
48 + textAlign: TextAlign.center,
49 + ),
50 + if (dashboardViewModel.balanceViewModel.isHomeScreenSettingsEnabled)
51 + InkWell(
52 + onTap: () => Navigator.pushNamed(context, Routes.homeSettings,
53 + arguments: dashboardViewModel.balanceViewModel),
54 + child: Padding(
55 + padding: const EdgeInsets.all(8.0),
56 + child: Image.asset(
57 + 'assets/images/home_screen_settings_icon.png',
58 + color:
59 + Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
60 + ),
61 + ),
62 + ),
63 + ],
64 + );
65 + },
66 + ),
67 + ),
68 + Observer(
69 + builder: (_) {
70 + if (dashboardViewModel.balanceViewModel.isShowCard &&
71 + FeatureFlag.isCakePayEnabled) {
72 + return IntroducingCard(
73 + title: S.of(context).introducing_cake_pay,
74 + subTitle: S.of(context).cake_pay_learn_more,
75 + borderColor: settingsStore.currentTheme.type == ThemeType.bright
76 + ? Color.fromRGBO(255, 255, 255, 0.2)
77 + : Colors.transparent,
78 + closeCard: dashboardViewModel.balanceViewModel.disableIntroCakePayCard);
79 + }
80 + return Container();
81 + },
82 + ),
83 + Observer(
84 + builder: (_) {
85 + return ListView.separated(
86 + physics: NeverScrollableScrollPhysics(),
87 + shrinkWrap: true,
88 + separatorBuilder: (_, __) => Container(padding: EdgeInsets.only(bottom: 8)),
89 + itemCount: dashboardViewModel.balanceViewModel.formattedBalances.length,
90 + itemBuilder: (__, index) {
91 + final balance =
92 + dashboardViewModel.balanceViewModel.formattedBalances.elementAt(index);
93 + return buildBalanceRow(
94 + context,
95 availableBalanceLabel:
96 '${dashboardViewModel.balanceViewModel.availableBalanceLabel}',
97 availableBalance: balance.availableBalance,
@@ -75,45 +102,57 @@ class BalancePage extends StatelessWidget {
102 additionalFiatBalance: balance.fiatAdditionalBalance,
103 frozenBalance: balance.frozenBalance,
104 frozenFiatBalance: balance.fiatFrozenBalance,
78 - currency: balance.formattedAssetTitle);
79 - });
80 - })
81 - ])));
105 + currency: balance.formattedAssetTitle,
106 + hasAdditionalBalance:
107 + dashboardViewModel.balanceViewModel.hasAdditionalBalance,
108 + );
109 + },
110 + );
111 + },
112 + )
113 + ],
114 + ),
115 + ),
116 + );
117 }
118
84 - Widget buildBalanceRow(BuildContext context,
85 - {required String availableBalanceLabel,
86 - required String availableBalance,
87 - required String availableFiatBalance,
88 - required String additionalBalanceLabel,
89 - required String additionalBalance,
90 - required String additionalFiatBalance,
91 - required String frozenBalance,
92 - required String frozenFiatBalance,
93 - required String currency}) {
119 + Widget buildBalanceRow(
120 + BuildContext context, {
121 + required String availableBalanceLabel,
122 + required String availableBalance,
123 + required String availableFiatBalance,
124 + required String additionalBalanceLabel,
125 + required String additionalBalance,
126 + required String additionalFiatBalance,
127 + required String frozenBalance,
128 + required String frozenFiatBalance,
129 + required String currency,
130 + required bool hasAdditionalBalance,
131 + }) {
132 return Container(
133 margin: const EdgeInsets.only(left: 16, right: 16),
134 decoration: BoxDecoration(
97 - borderRadius: BorderRadius.circular(30.0),
98 - border: Border.all(
99 - color: settingsStore.currentTheme.type == ThemeType.bright
100 - ? Color.fromRGBO(255, 255, 255, 0.2)
101 - : Colors.transparent,
102 - width: 1,
103 - ),
104 - color: Theme.of(context).textTheme!.titleLarge!.backgroundColor!),
135 + borderRadius: BorderRadius.circular(30.0),
136 + border: Border.all(
137 + color: settingsStore.currentTheme.type == ThemeType.bright
138 + ? Color.fromRGBO(255, 255, 255, 0.2)
139 + : Colors.transparent,
140 + width: 1,
141 + ),
142 + color: Theme.of(context).textTheme.titleLarge!.backgroundColor!,
143 + ),
144 child: Container(
106 - margin: const EdgeInsets.only(top: 16, left: 24, right: 24, bottom: 24),
107 - child: Column(
108 - crossAxisAlignment: CrossAxisAlignment.start,
109 - children: [
145 + margin: const EdgeInsets.only(top: 16, left: 24, right: 24, bottom: 24),
146 + child: Column(
147 + crossAxisAlignment: CrossAxisAlignment.start,
148 + children: [
149 Row(
150 mainAxisAlignment: MainAxisAlignment.spaceBetween,
151 crossAxisAlignment: CrossAxisAlignment.start,
152 children: [
153 GestureDetector(
154 behavior: HitTestBehavior.opaque,
116 - onTap: () => _showBalanceDescription(context),
155 + onTap: hasAdditionalBalance ? () => _showBalanceDescription(context) : null,
156 child: Column(
157 crossAxisAlignment: CrossAxisAlignment.start,
158 children: [
@@ -129,19 +168,19 @@ class BalancePage extends StatelessWidget {
168 .displaySmall!
169 .backgroundColor!,
170 height: 1)),
132 - Padding(
133 - padding: const EdgeInsets.symmetric(horizontal: 4),
134 - child: Icon(Icons.help_outline,
135 - size: 16,
136 - color: Theme.of(context)
137 - .accentTextTheme!
138 - .displaySmall!
139 - .backgroundColor!),
140 - )
171 + if (hasAdditionalBalance)
172 + Padding(
173 + padding: const EdgeInsets.symmetric(horizontal: 4),
174 + child: Icon(Icons.help_outline,
175 + size: 16,
176 + color: Theme.of(context)
177 + .accentTextTheme!
178 + .displaySmall!
179 + .backgroundColor!),
180 + ),
181 ],
142 - ),SizedBox(
143 - height: 6,
182 ),
183 + SizedBox(height: 6),
184 AutoSizeText(availableBalance,
185 style: TextStyle(
186 fontSize: 24,
@@ -154,9 +193,7 @@ class BalancePage extends StatelessWidget {
193 height: 1),
194 maxLines: 1,
195 textAlign: TextAlign.start),
157 - SizedBox(
158 - height: 6,
159 - ),
196 + SizedBox(height: 6),
197 Text('${availableFiatBalance}',
198 textAlign: TextAlign.center,
199 style: TextStyle(
@@ -168,7 +205,6 @@ class BalancePage extends StatelessWidget {
205 .displayMedium!
206 .backgroundColor!,
207 height: 1)),
171 -
208 ],
209 ),
210 ),
@@ -177,97 +213,99 @@ class BalancePage extends StatelessWidget {
213 fontSize: 28,
214 fontFamily: 'Lato',
215 fontWeight: FontWeight.w800,
180 - color: Theme.of(context)
181 - .accentTextTheme!
182 - .displayMedium!
183 - .backgroundColor!,
216 + color: Theme.of(context).accentTextTheme!.displayMedium!.backgroundColor!,
217 height: 1)),
218 ],
219 ),
187 - SizedBox(height: 26),
220 if (frozenBalance.isNotEmpty)
189 - Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
190 - Text(S.current.frozen_balance,
221 + Column(
222 + crossAxisAlignment: CrossAxisAlignment.start,
223 + children: [
224 + SizedBox(height: 26),
225 + Text(
226 + S.current.frozen_balance,
227 textAlign: TextAlign.center,
228 style: TextStyle(
193 - fontSize: 12,
194 - fontFamily: 'Lato',
195 - fontWeight: FontWeight.w400,
196 - color: Theme.of(context)
197 - .accentTextTheme!
198 - .displaySmall!
199 - .backgroundColor!,
200 - height: 1)),
201 - SizedBox(height: 8),
202 - AutoSizeText(frozenBalance,
229 + fontSize: 12,
230 + fontFamily: 'Lato',
231 + fontWeight: FontWeight.w400,
232 + color: Theme.of(context).accentTextTheme.displaySmall!.backgroundColor!,
233 + height: 1,
234 + ),
235 + ),
236 + SizedBox(height: 8),
237 + AutoSizeText(
238 + frozenBalance,
239 style: TextStyle(
204 - fontSize: 20,
205 - fontFamily: 'Lato',
206 - fontWeight: FontWeight.w400,
207 - color: Theme.of(context)
208 - .accentTextTheme!
209 - .displayMedium!
210 - .backgroundColor!,
211 - height: 1),
240 + fontSize: 20,
241 + fontFamily: 'Lato',
242 + fontWeight: FontWeight.w400,
243 + color: Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
244 + height: 1,
245 + ),
246 maxLines: 1,
213 - textAlign: TextAlign.center),
214 - SizedBox(height: 4),
215 - Text(
216 - frozenFiatBalance,
217 - textAlign: TextAlign.center,
218 - style: TextStyle(
247 + textAlign: TextAlign.center,
248 + ),
249 + SizedBox(height: 4),
250 + Text(
251 + frozenFiatBalance,
252 + textAlign: TextAlign.center,
253 + style: TextStyle(
254 fontSize: 12,
255 fontFamily: 'Lato',
256 fontWeight: FontWeight.w400,
222 - color: Theme.of(context)
223 - .accentTextTheme!
224 - .displayMedium!
225 - .backgroundColor!,
226 - height: 1),
227 - ),
228 - SizedBox(height: 24)
229 - ]),
230 - Text('${additionalBalanceLabel}',
231 - textAlign: TextAlign.center,
232 - style: TextStyle(
233 - fontSize: 12,
234 - fontFamily: 'Lato',
235 - fontWeight: FontWeight.w400,
236 - color: Theme.of(context)
237 - .accentTextTheme!
238 - .displaySmall!
239 - .backgroundColor!,
240 - height: 1)),
241 - SizedBox(height: 8),
242 - AutoSizeText(additionalBalance,
243 - style: TextStyle(
244 - fontSize: 20,
245 - fontFamily: 'Lato',
246 - fontWeight: FontWeight.w400,
247 - color: Theme.of(context)
248 - .accentTextTheme!
249 - .displayMedium!
250 - .backgroundColor!,
251 - height: 1),
252 - maxLines: 1,
253 - textAlign: TextAlign.center),
254 - SizedBox(
255 - height: 4,
256 - ),
257 - Text(
258 - '${additionalFiatBalance}',
259 - textAlign: TextAlign.center,
260 - style: TextStyle(
261 - fontSize: 12,
262 - fontFamily: 'Lato',
263 - fontWeight: FontWeight.w400,
264 - color: Theme.of(context)
265 - .accentTextTheme!
266 - .displayMedium!
267 - .backgroundColor!,
268 - height: 1),
269 - )
270 - ])),
257 + color: Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
258 + height: 1,
259 + ),
260 + ),
261 + ],
262 + ),
263 + if (hasAdditionalBalance)
264 + Column(
265 + crossAxisAlignment: CrossAxisAlignment.start,
266 + children: [
267 + SizedBox(height: 24),
268 + Text(
269 + '${additionalBalanceLabel}',
270 + textAlign: TextAlign.center,
271 + style: TextStyle(
272 + fontSize: 12,
273 + fontFamily: 'Lato',
274 + fontWeight: FontWeight.w400,
275 + color: Theme.of(context).accentTextTheme.displaySmall!.backgroundColor!,
276 + height: 1,
277 + ),
278 + ),
279 + SizedBox(height: 8),
280 + AutoSizeText(
281 + additionalBalance,
282 + style: TextStyle(
283 + fontSize: 20,
284 + fontFamily: 'Lato',
285 + fontWeight: FontWeight.w400,
286 + color: Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
287 + height: 1,
288 + ),
289 + maxLines: 1,
290 + textAlign: TextAlign.center,
291 + ),
292 + SizedBox(height: 4),
293 + Text(
294 + '${additionalFiatBalance}',
295 + textAlign: TextAlign.center,
296 + style: TextStyle(
297 + fontSize: 12,
298 + fontFamily: 'Lato',
299 + fontWeight: FontWeight.w400,
300 + color: Theme.of(context).accentTextTheme.displayMedium!.backgroundColor!,
301 + height: 1,
302 + ),
303 + ),
304 + ],
305 + ),
306 + ],
307 + ),
308 + ),
309 );
310 }
311
lib/src/screens/dashboard/widgets/menu_widget.dart
+18 -16
@@ -19,17 +19,18 @@ class MenuWidget extends StatefulWidget {
19
20 class MenuWidgetState extends State<MenuWidget> {
21 MenuWidgetState()
22 - : this.menuWidth = 0,
23 - this.screenWidth = 0,
24 - this.screenHeight = 0,
25 - this.headerHeight = 120,
26 - this.tileHeight = 60,
27 - this.fromTopEdge = 50,
28 - this.fromBottomEdge = 25,
29 - this.moneroIcon = Image.asset('assets/images/monero_menu.png'),
30 - this.bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png'),
31 - this.litecoinIcon = Image.asset('assets/images/litecoin_menu.png'),
32 - this.havenIcon = Image.asset('assets/images/haven_menu.png');
22 + : this.menuWidth = 0,
23 + this.screenWidth = 0,
24 + this.screenHeight = 0,
25 + this.headerHeight = 120,
26 + this.tileHeight = 60,
27 + this.fromTopEdge = 50,
28 + this.fromBottomEdge = 25,
29 + this.moneroIcon = Image.asset('assets/images/monero_menu.png'),
30 + this.bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png'),
31 + this.litecoinIcon = Image.asset('assets/images/litecoin_menu.png'),
32 + this.havenIcon = Image.asset('assets/images/haven_menu.png'),
33 + this.ethereumIcon = Image.asset('assets/images/eth_icon.png');
34
35 final largeScreen = 731;
36
@@ -46,6 +47,7 @@ class MenuWidgetState extends State<MenuWidget> {
47 Image bitcoinIcon;
48 Image litecoinIcon;
49 Image havenIcon;
50 + Image ethereumIcon;
51
52 @override
53 void initState() {
@@ -85,16 +87,14 @@ class MenuWidgetState extends State<MenuWidget> {
87
88 moneroIcon = Image.asset('assets/images/monero_menu.png',
89 color: Theme.of(context)
88 - .accentTextTheme!
90 + .accentTextTheme
91 .labelSmall!
92 .decorationColor!);
93 bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png',
94 color: Theme.of(context)
93 - .accentTextTheme!
95 + .accentTextTheme
96 .labelSmall!
97 .decorationColor!);
96 - litecoinIcon = Image.asset('assets/images/litecoin_menu.png');
97 - havenIcon = Image.asset('assets/images/haven_menu.png');
98
99 return Row(
100 mainAxisSize: MainAxisSize.max,
@@ -178,7 +178,7 @@ class MenuWidgetState extends State<MenuWidget> {
178 index--;
179
180 final item = SettingActions.all[index];
181 -
181 +
182 final isLastTile = index == itemCount - 1;
183
184 return SettingActionButton(
@@ -215,6 +215,8 @@ class MenuWidgetState extends State<MenuWidget> {
215 return litecoinIcon;
216 case WalletType.haven:
217 return havenIcon;
218 + case WalletType.ethereum:
219 + return ethereumIcon;
220 default:
221 throw Exception('No icon for ${type.toString()}');
222 }
lib/src/screens/exchange/widgets/exchange_card.dart
+10 -11
@@ -511,17 +511,16 @@ class ExchangeCardState extends State<ExchangeCard> {
511
512 void _presentPicker(BuildContext context) {
513 showPopUp<void>(
514 - builder: (_) => CurrencyPicker(
515 - selectedAtIndex: widget.currencies.indexOf(_selectedCurrency),
516 - items: widget.currencies,
517 - hintText: S.of(context).search_currency,
518 - isMoneroWallet: _isMoneroWallet,
519 - isConvertFrom: widget.hasRefundAddress,
520 - onItemSelected: (Currency item) =>
521 - widget.onCurrencySelected != null
522 - ? widget.onCurrencySelected(item as CryptoCurrency)
523 - : null),
524 - context: context);
514 + context: context,
515 + builder: (_) => CurrencyPicker(
516 + selectedAtIndex: widget.currencies.indexOf(_selectedCurrency),
517 + items: widget.currencies,
518 + hintText: S.of(context).search_currency,
519 + isMoneroWallet: _isMoneroWallet,
520 + isConvertFrom: widget.hasRefundAddress,
521 + onItemSelected: (Currency item) => widget.onCurrencySelected(item as CryptoCurrency),
522 + ),
523 + );
524 }
525
526 void _showAmountPopup(BuildContext context, PaymentRequest paymentRequest) {
lib/src/screens/new_wallet/new_wallet_type_page.dart
+5 -2
@@ -11,14 +11,17 @@ import 'package:cw_core/wallet_type.dart';
11 import 'package:flutter/material.dart';
12
13 class NewWalletTypePage extends BasePage {
14 - NewWalletTypePage({required this.onTypeSelected});
14 + NewWalletTypePage({required this.onTypeSelected, required this.isCreate});
15
16 final void Function(BuildContext, WalletType) onTypeSelected;
17 + final bool isCreate;
18 +
19 final walletTypeImage = Image.asset('assets/images/wallet_type.png');
20 final walletTypeLightImage = Image.asset('assets/images/wallet_type_light.png');
21
22 @override
21 - String get title => S.current.wallet_list_restore_wallet;
23 + String get title =>
24 + isCreate ? S.current.wallet_list_create_new_wallet : S.current.wallet_list_restore_wallet;
25
26 @override
27 Widget body(BuildContext context) => WalletTypeForm(
lib/src/screens/restore/restore_wallet_options_page.dart deleted
-85
@@ -1,85 +0,0 @@
1 -import 'package:flutter/material.dart';
2 -import 'package:cake_wallet/src/screens/restore/widgets/restore_button.dart';
3 -import 'package:cake_wallet/src/screens/base_page.dart';
4 -import 'package:cw_core/wallet_type.dart';
5 -import 'package:cake_wallet/generated/i18n.dart';
6 -
7 -class RestoreWalletOptionsPage extends BasePage {
8 - RestoreWalletOptionsPage(
9 - {required this.type,
10 - required this.onRestoreFromSeed,
11 - required this.onRestoreFromKeys});
12 -
13 - final WalletType type;
14 - final Function(BuildContext context) onRestoreFromSeed;
15 - final Function(BuildContext context) onRestoreFromKeys;
16 -
17 - @override
18 - String get title => S.current.restore_restore_wallet;
19 -
20 - final imageSeed = Image.asset('assets/images/restore_seed.png');
21 - final imageKeys = Image.asset('assets/images/restore_keys.png');
22 -
23 - @override
24 - Widget body(BuildContext context) {
25 - return Container(
26 - width: double.infinity,
27 - height: double.infinity,
28 - padding: EdgeInsets.all(24),
29 - child: SingleChildScrollView(
30 - child: Column(
31 - children: <Widget>[
32 - RestoreButton(
33 - onPressed: () => onRestoreFromSeed(context),
34 - image: imageSeed,
35 - title: S.of(context).restore_title_from_seed,
36 - description: _fromSeedDescription(context)),
37 - Padding(
38 - padding: EdgeInsets.only(top: 24),
39 - child: RestoreButton(
40 - onPressed: () => onRestoreFromKeys(context),
41 - image: imageKeys,
42 - title: _fromKeyTitle(context),
43 - description: _fromKeyDescription(context)),
44 - )
45 - ],
46 - ),
47 - ));
48 - }
49 -
50 - String _fromSeedDescription(BuildContext context) {
51 - switch (type) {
52 - case WalletType.monero:
53 - return S.of(context).restore_description_from_seed;
54 - case WalletType.bitcoin:
55 - // TODO: Add transaction for bitcoin description.
56 - return S.of(context).restore_bitcoin_description_from_seed;
57 - default:
58 - return '';
59 - }
60 - }
61 -
62 - String _fromKeyDescription(BuildContext context) {
63 - switch (type) {
64 - case WalletType.monero:
65 - return S.of(context).restore_description_from_keys;
66 - case WalletType.bitcoin:
67 - // TODO: Add transaction for bitcoin description.
68 - return S.of(context).restore_bitcoin_description_from_keys;
69 - default:
70 - return '';
71 - }
72 - }
73 -
74 - String _fromKeyTitle(BuildContext context) {
75 - switch (type) {
76 - case WalletType.monero:
77 - return S.of(context).restore_title_from_keys;
78 - case WalletType.bitcoin:
79 - // TODO: Add transaction for bitcoin description.
80 - return S.of(context).restore_bitcoin_title_from_keys;
81 - default:
82 - return '';
83 - }
84 - }
85 -}
lib/src/screens/seed/pre_seed_page.dart
+12 -3
@@ -11,9 +11,7 @@ class PreSeedPage extends BasePage {
11 PreSeedPage(this.type)
12 : imageLight = Image.asset('assets/images/pre_seed_light.png'),
13 imageDark = Image.asset('assets/images/pre_seed_dark.png'),
14 - wordsCount = type == WalletType.monero
15 - ? 25
16 - : 24; // FIXME: Stupid fast implementation
14 + wordsCount = _wordsCount(type);
15
16 final Image imageDark;
17 final Image imageLight;
@@ -68,4 +66,15 @@ class PreSeedPage extends BasePage {
66 ),
67 ));
68 }
69 +
70 + static int _wordsCount(WalletType type) {
71 + switch (type) {
72 + case WalletType.monero:
73 + return 25;
74 + case WalletType.ethereum:
75 + return 12;
76 + default:
77 + return 24;
78 + }
79 + }
80 }
lib/src/screens/send/send_page.dart
+101 -107
@@ -220,115 +220,108 @@ class SendPage extends BasePage {
220 ),
221 ),
222 ),
223 - if (sendViewModel.hasMultiRecipient)
224 - Container(
225 - height: 40,
226 - width: double.infinity,
227 - padding: EdgeInsets.only(left: 24),
228 - child: SingleChildScrollView(
229 - scrollDirection: Axis.horizontal,
230 - child: Observer(
231 - builder: (_) {
232 - final templates = sendViewModel.templates;
233 - final itemCount = templates.length;
234 -
235 - return Row(
236 - children: <Widget>[
237 - AddTemplateButton(
238 - onTap: () => Navigator.of(context)
239 - .pushNamed(Routes.sendTemplate),
240 - currentTemplatesLength: templates.length,
241 - ),
242 - ListView.builder(
243 - scrollDirection: Axis.horizontal,
244 - shrinkWrap: true,
245 - physics: NeverScrollableScrollPhysics(),
246 - itemCount: itemCount,
247 - itemBuilder: (context, index) {
248 - final template = templates[index];
249 - return TemplateTile(
250 - key: UniqueKey(),
251 - to: template.name,
252 - hasMultipleRecipients:
253 - template.additionalRecipients !=
254 - null &&
255 - template.additionalRecipients!
256 - .length > 1,
257 - amount: template.isCurrencySelected
258 - ? template.amount
259 - : template.amountFiat,
260 - from: template.isCurrencySelected
261 - ? template.cryptoCurrency
262 - : template.fiatCurrency,
263 - onTap: () async {
264 - if (template.additionalRecipients !=
265 - null) {
266 - sendViewModel.clearOutputs();
267 -
268 - template.additionalRecipients!
269 - .forEach((currentElement) async {
270 - int i = template
271 - .additionalRecipients!
272 - .indexOf(currentElement);
273 -
274 - Output output;
275 - try {
276 - output = sendViewModel.outputs[i];
277 - } catch (e) {
278 - sendViewModel.addOutput();
279 - output = sendViewModel.outputs[i];
280 - }
281 -
282 - await _setInputsFromTemplate(
283 - context,
284 - output: output,
285 - template: currentElement);
286 - });
287 - } else {
288 - final output = _defineCurrentOutput();
289 - await _setInputsFromTemplate(
290 - context,
291 - output: output,
292 - template: template);
223 + Container(
224 + height: 40,
225 + width: double.infinity,
226 + padding: EdgeInsets.only(left: 24),
227 + child: SingleChildScrollView(
228 + scrollDirection: Axis.horizontal,
229 + child: Observer(
230 + builder: (_) {
231 + final templates = sendViewModel.templates;
232 + final itemCount = templates.length;
233 +
234 + return Row(
235 + children: <Widget>[
236 + AddTemplateButton(
237 + onTap: () => Navigator.of(context)
238 + .pushNamed(Routes.sendTemplate),
239 + currentTemplatesLength: templates.length,
240 + ),
241 + ListView.builder(
242 + scrollDirection: Axis.horizontal,
243 + shrinkWrap: true,
244 + physics: NeverScrollableScrollPhysics(),
245 + itemCount: itemCount,
246 + itemBuilder: (context, index) {
247 + final template = templates[index];
248 + return TemplateTile(
249 + key: UniqueKey(),
250 + to: template.name,
251 + hasMultipleRecipients:
252 + template.additionalRecipients != null &&
253 + template.additionalRecipients!.length > 1,
254 + amount: template.isCurrencySelected
255 + ? template.amount
256 + : template.amountFiat,
257 + from: template.isCurrencySelected
258 + ? template.cryptoCurrency
259 + : template.fiatCurrency,
260 + onTap: () async {
261 + if (template.additionalRecipients?.isNotEmpty ?? false) {
262 + sendViewModel.clearOutputs();
263 +
264 + for (int i = 0;i < template.additionalRecipients!.length;i++) {
265 + Output output;
266 + try {
267 + output = sendViewModel.outputs[i];
268 + } catch (e) {
269 + sendViewModel.addOutput();
270 + output = sendViewModel.outputs[i];
271 + }
272 +
273 + await _setInputsFromTemplate(
274 + context,
275 + output: output,
276 + template: template.additionalRecipients![i],
277 + );
278 }
294 - },
295 - onRemove: () {
296 - showPopUp<void>(
297 - context: context,
298 - builder: (dialogContext) {
299 - return AlertWithTwoActions(
300 - alertTitle:
301 - S.of(context).template,
302 - alertContent: S
303 - .of(context)
304 - .confirm_delete_template,
305 - rightButtonText:
306 - S.of(context).delete,
307 - leftButtonText:
308 - S.of(context).cancel,
309 - actionRightButton: () {
310 - Navigator.of(dialogContext)
311 - .pop();
312 - sendViewModel
313 - .sendTemplateViewModel
314 - .removeTemplate(
315 - template: template);
316 - },
317 - actionLeftButton: () =>
318 - Navigator.of(dialogContext)
319 - .pop());
320 - },
279 + } else {
280 + final output = _defineCurrentOutput();
281 + await _setInputsFromTemplate(
282 + context,
283 + output: output,
284 + template: template,
285 );
322 - },
323 - );
324 - },
325 - ),
326 - ],
327 - );
328 - },
329 - ),
286 + }
287 + },
288 + onRemove: () {
289 + showPopUp<void>(
290 + context: context,
291 + builder: (dialogContext) {
292 + return AlertWithTwoActions(
293 + alertTitle:
294 + S.of(context).template,
295 + alertContent: S
296 + .of(context)
297 + .confirm_delete_template,
298 + rightButtonText:
299 + S.of(context).delete,
300 + leftButtonText:
301 + S.of(context).cancel,
302 + actionRightButton: () {
303 + Navigator.of(dialogContext)
304 + .pop();
305 + sendViewModel
306 + .sendTemplateViewModel
307 + .removeTemplate(
308 + template: template);
309 + },
310 + actionLeftButton: () =>
311 + Navigator.of(dialogContext)
312 + .pop());
313 + },
314 + );
315 + },
316 + );
317 + },
318 + ),
319 + ],
320 + );
321 + },
322 ),
331 - )
323 + ),
324 + ),
325 ],
326 ),
327 ),
@@ -350,7 +343,7 @@ class SendPage extends BasePage {
343 .displaySmall!
344 .decorationColor!,
345 ))),
353 - if (sendViewModel.hasMultiRecipient)
346 + if (sendViewModel.sendTemplateViewModel.hasMultiRecipient)
347 Padding(
348 padding: EdgeInsets.only(bottom: 12),
349 child: PrimaryButton(
@@ -518,6 +511,7 @@ class SendPage extends BasePage {
511 output.address = template.address;
512
513 if (template.isCurrencySelected) {
514 + sendViewModel.setSelectedCryptoCurrency(template.cryptoCurrency);
515 output.setCryptoAmount(template.amount);
516 } else {
517 sendViewModel.setFiatCurrency(fiatFromTemplate);
lib/src/screens/send/send_template_page.dart
+28 -43
@@ -67,8 +67,7 @@ class SendTemplatePage extends BasePage {
67 controller: controller,
68 itemCount: sendTemplateViewModel.recipients.length,
69 itemBuilder: (_, index) {
70 - final template =
71 - sendTemplateViewModel.recipients[index];
70 + final template = sendTemplateViewModel.recipients[index];
71 return SendTemplateCard(
72 template: template,
73 index: index,
@@ -76,8 +75,7 @@ class SendTemplatePage extends BasePage {
75 });
76 })),
77 Padding(
79 - padding: EdgeInsets.only(
80 - top: 10, left: 24, right: 24, bottom: 10),
78 + padding: EdgeInsets.only(top: 10, left: 24, right: 24, bottom: 10),
79 child: Container(
80 height: 10,
81 child: Observer(
@@ -107,55 +105,42 @@ class SendTemplatePage extends BasePage {
105 ),
106 ),
107 ])),
110 - bottomSectionPadding:
111 - EdgeInsets.only(left: 24, right: 24, bottom: 24),
108 + bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
109 bottomSection: Column(children: [
113 - // if (sendViewModel.hasMultiRecipient)
114 - Padding(
115 - padding: EdgeInsets.only(bottom: 12),
116 - child: PrimaryButton(
117 - onPressed: () {
118 - sendTemplateViewModel.addRecipient();
119 - Future.delayed(const Duration(milliseconds: 250), () {
120 - controller.jumpToPage(
121 - sendTemplateViewModel.recipients.length - 1);
122 - });
123 - },
124 - text: S.of(context).add_receiver,
125 - color: Colors.transparent,
126 - textColor: Theme.of(context)
127 - .accentTextTheme
128 - .displaySmall!
129 - .decorationColor!,
130 - isDottedBorder: true,
131 - borderColor: Theme.of(context)
132 - .primaryTextTheme
133 - .displaySmall!
134 - .decorationColor!)),
110 + if (sendTemplateViewModel.hasMultiRecipient)
111 + Padding(
112 + padding: EdgeInsets.only(bottom: 12),
113 + child: PrimaryButton(
114 + onPressed: () {
115 + sendTemplateViewModel.addRecipient();
116 + Future.delayed(const Duration(milliseconds: 250), () {
117 + controller.jumpToPage(sendTemplateViewModel.recipients.length - 1);
118 + });
119 + },
120 + text: S.of(context).add_receiver,
121 + color: Colors.transparent,
122 + textColor: Theme.of(context).accentTextTheme.displaySmall!.decorationColor!,
123 + isDottedBorder: true,
124 + borderColor:
125 + Theme.of(context).primaryTextTheme.displaySmall!.decorationColor!)),
126 PrimaryButton(
127 onPressed: () {
137 - if (_formKey.currentState != null &&
138 - _formKey.currentState!.validate()) {
128 + if (_formKey.currentState != null && _formKey.currentState!.validate()) {
129 final mainTemplate = sendTemplateViewModel.recipients[0];
140 - print(sendTemplateViewModel.recipients.map((element) =>
141 - element.toTemplate(
142 - cryptoCurrency:
143 - sendTemplateViewModel.cryptoCurrency.title,
144 - fiatCurrency:
145 - sendTemplateViewModel.fiatCurrency)));
130 + final additionalRecipients = sendTemplateViewModel.recipients
131 + .map((element) => element.toTemplate(
132 + cryptoCurrency: element.selectedCurrency.title,
133 + fiatCurrency: sendTemplateViewModel.fiatCurrency))
134 + .toList();
135 +
136 sendTemplateViewModel.addTemplate(
137 isCurrencySelected: mainTemplate.isCurrencySelected,
138 name: mainTemplate.name,
139 address: mainTemplate.address,
140 + cryptoCurrency: mainTemplate.selectedCurrency.title,
141 amount: mainTemplate.output.cryptoAmount,
142 amountFiat: mainTemplate.output.fiatAmount,
152 - additionalRecipients: sendTemplateViewModel.recipients
153 - .map((element) => element.toTemplate(
154 - cryptoCurrency: sendTemplateViewModel
155 - .cryptoCurrency.title,
156 - fiatCurrency:
157 - sendTemplateViewModel.fiatCurrency))
158 - .toList());
143 + additionalRecipients: additionalRecipients);
144 Navigator.of(context).pop();
145 }
146 },
lib/src/screens/send/widgets/prefix_currency_icon_widget.dart
+39 -15
@@ -4,29 +4,53 @@ class PrefixCurrencyIcon extends StatelessWidget {
4 PrefixCurrencyIcon({
5 required this.isSelected,
6 required this.title,
7 + this.onTap,
8 });
9
10 final bool isSelected;
11 final String title;
12 + final Function()? onTap;
13
14 @override
15 Widget build(BuildContext context) {
14 - return Padding(
16 + return GestureDetector(
17 + onTap: onTap,
18 + child: Padding(
19 padding: EdgeInsets.fromLTRB(0, 6.0, 8.0, 0),
16 - child: Column(children: [
17 - Container(
18 - padding: EdgeInsets.symmetric(vertical: 4, horizontal: 8),
19 - decoration: BoxDecoration(
20 - borderRadius: BorderRadius.circular(26),
21 - color: isSelected ? Colors.green : Colors.transparent,
20 + child: Column(
21 + children: [
22 + Container(
23 + padding: EdgeInsets.symmetric(vertical: 4, horizontal: 8),
24 + decoration: BoxDecoration(
25 + borderRadius: BorderRadius.circular(26),
26 + color: isSelected ? Colors.green : Colors.transparent,
27 + ),
28 + child: Row(
29 + mainAxisSize: MainAxisSize.min,
30 + children: <Widget>[
31 + if (onTap != null)
32 + Padding(
33 + padding: EdgeInsets.only(right: 5),
34 + child: Image.asset(
35 + 'assets/images/arrow_bottom_purple_icon.png',
36 + color: Colors.white,
37 + height: 8,
38 + ),
39 + ),
40 + Text(
41 + title + ':',
42 + style: TextStyle(
43 + fontSize: 16,
44 + fontWeight: FontWeight.w600,
45 + color: Colors.white,
46 + ),
47 + ),
48 + ],
49 + ),
50 ),
23 - child: Text(title + ':',
24 - style: TextStyle(
25 - fontSize: 16,
26 - fontWeight: FontWeight.w600,
27 - color: Colors.white,
28 - )),
29 - )
30 - ]));
51 + ],
52 + ),
53 + ),
54 + );
55 }
56 }
lib/src/screens/send/widgets/send_card.dart
+384 -361
@@ -1,7 +1,10 @@
1 import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
2 +import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
3 import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
4 import 'package:cake_wallet/utils/payment_request.dart';
5 import 'package:cake_wallet/utils/responsive_layout_util.dart';
6 +import 'package:cw_core/crypto_currency.dart';
7 +import 'package:cw_core/currency.dart';
8 import 'package:cw_core/transaction_priority.dart';
9 import 'package:cake_wallet/routes.dart';
10 import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
@@ -32,18 +35,14 @@ class SendCard extends StatefulWidget {
35
36 @override
37 SendCardState createState() => SendCardState(
35 - output: output,
36 - sendViewModel: sendViewModel,
37 - initialPaymentRequest: initialPaymentRequest,
38 - );
38 + output: output,
39 + sendViewModel: sendViewModel,
40 + initialPaymentRequest: initialPaymentRequest,
41 + );
42 }
43
41 -class SendCardState extends State<SendCard>
42 - with AutomaticKeepAliveClientMixin<SendCard> {
43 - SendCardState({
44 - required this.output,
45 - required this.sendViewModel,
46 - this.initialPaymentRequest})
44 +class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<SendCard> {
45 + SendCardState({required this.output, required this.sendViewModel, this.initialPaymentRequest})
46 : addressController = TextEditingController(),
47 cryptoAmountController = TextEditingController(),
48 fiatAmountController = TextEditingController(),
@@ -100,40 +99,41 @@ class SendCardState extends State<SendCard>
99 return Stack(
100 children: [
101 KeyboardActions(
103 - config: KeyboardActionsConfig(
104 - keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
105 - keyboardBarColor: Theme.of(context)
106 - .accentTextTheme
107 - .bodyLarge!
108 - .backgroundColor!,
109 - nextFocus: false,
110 - actions: [
111 - KeyboardActionsItem(
112 - focusNode: cryptoAmountFocus,
113 - toolbarButtons: [(_) => KeyboardDoneButton()],
114 - ),
115 - KeyboardActionsItem(
116 - focusNode: fiatAmountFocus,
117 - toolbarButtons: [(_) => KeyboardDoneButton()],
118 - )
119 - ]),
120 - child: Container(
121 - height: 0,
122 - color: Colors.transparent,
123 - )),
102 + config: KeyboardActionsConfig(
103 + keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
104 + keyboardBarColor: Theme.of(context).accentTextTheme.bodyLarge!.backgroundColor!,
105 + nextFocus: false,
106 + actions: [
107 + KeyboardActionsItem(
108 + focusNode: cryptoAmountFocus,
109 + toolbarButtons: [(_) => KeyboardDoneButton()],
110 + ),
111 + KeyboardActionsItem(
112 + focusNode: fiatAmountFocus,
113 + toolbarButtons: [(_) => KeyboardDoneButton()],
114 + )
115 + ],
116 + ),
117 + child: Container(
118 + height: 0,
119 + color: Colors.transparent,
120 + ),
121 + ),
122 Container(
125 - decoration: ResponsiveLayoutUtil.instance.isMobile ? BoxDecoration(
126 - borderRadius: BorderRadius.only(
127 - bottomLeft: Radius.circular(24),
128 - bottomRight: Radius.circular(24)),
129 - gradient: LinearGradient(colors: [
130 - Theme.of(context).primaryTextTheme.titleMedium!.color!,
131 - Theme.of(context)
132 - .primaryTextTheme
133 - .titleMedium!
134 - .decorationColor!,
135 - ], begin: Alignment.topLeft, end: Alignment.bottomRight),
136 - ) : null,
123 + decoration: ResponsiveLayoutUtil.instance.isMobile
124 + ? BoxDecoration(
125 + borderRadius: BorderRadius.only(
126 + bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)),
127 + gradient: LinearGradient(
128 + colors: [
129 + Theme.of(context).primaryTextTheme.titleMedium!.color!,
130 + Theme.of(context).primaryTextTheme.titleMedium!.decorationColor!,
131 + ],
132 + begin: Alignment.topLeft,
133 + end: Alignment.bottomRight,
134 + ),
135 + )
136 + : null,
137 child: Padding(
138 padding: EdgeInsets.fromLTRB(
139 24,
@@ -142,7 +142,8 @@ class SendCardState extends State<SendCard>
142 ResponsiveLayoutUtil.instance.isMobile ? 32 : 0,
143 ),
144 child: SingleChildScrollView(
145 - child: Observer(builder: (_) => Column(
145 + child: Observer(
146 + builder: (_) => Column(
147 mainAxisSize: MainAxisSize.min,
148 children: <Widget>[
149 Observer(builder: (_) {
@@ -164,25 +165,15 @@ class SendCardState extends State<SendCard>
165 AddressTextFieldOption.qrCode,
166 AddressTextFieldOption.addressBook
167 ],
167 - buttonColor: Theme.of(context)
168 - .primaryTextTheme
169 - .headlineMedium!
170 - .color!,
171 - borderColor: Theme.of(context)
172 - .primaryTextTheme
173 - .headlineSmall!
174 - .color!,
168 + buttonColor: Theme.of(context).primaryTextTheme.headlineMedium!.color!,
169 + borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
170 textStyle: TextStyle(
176 - fontSize: 14,
177 - fontWeight: FontWeight.w500,
178 - color: Colors.white),
171 + fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
172 hintStyle: TextStyle(
173 fontSize: 14,
174 fontWeight: FontWeight.w500,
182 - color: Theme.of(context)
183 - .primaryTextTheme
184 - .headlineSmall!
185 - .decorationColor!),
175 + color:
176 + Theme.of(context).primaryTextTheme.headlineSmall!.decorationColor!),
177 onPushPasteButton: (context) async {
178 output.resetParsedAddress();
179 await output.fetchParsedAddress(context);
@@ -197,170 +188,192 @@ class SendCardState extends State<SendCard>
188 selectedCurrency: sendViewModel.currency,
189 );
190 }),
200 - if (output.isParsedAddress) Padding(
201 - padding: const EdgeInsets.only(top: 20),
202 - child: BaseTextFormField(
203 - controller: extractedAddressController,
204 - readOnly: true,
205 - borderColor: Theme.of(context)
206 - .primaryTextTheme
207 - .headlineSmall!
208 - .color!,
209 - textStyle: TextStyle(
210 - fontSize: 14,
211 - fontWeight: FontWeight.w500,
212 - color: Colors.white),
213 - validator: sendViewModel.addressValidator
214 - )
215 - ),
191 + if (output.isParsedAddress)
192 + Padding(
193 + padding: const EdgeInsets.only(top: 20),
194 + child: BaseTextFormField(
195 + controller: extractedAddressController,
196 + readOnly: true,
197 + borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
198 + textStyle: TextStyle(
199 + fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
200 + validator: sendViewModel.addressValidator)),
201 Observer(
217 - builder: (_) => Padding(
218 - padding: const EdgeInsets.only(top: 20),
219 - child: Row(
220 - children: [
221 - Padding(
222 - padding: const EdgeInsets.only(bottom: 8.0),
223 - child: Row(
224 - children: [
225 - Text(
226 - sendViewModel.selectedCryptoCurrency.title,
227 - style: TextStyle(
228 - fontSize: 16,
229 - fontWeight: FontWeight.w600,
230 - color: Colors.white,
231 - )),
232 - sendViewModel.selectedCryptoCurrency.tag != null ? Padding(
233 - padding: const EdgeInsets.fromLTRB(3.0,0,3.0,0),
234 - child: Container(
235 - height: 32,
236 - decoration: BoxDecoration(
237 - color: Theme.of(context)
238 - .primaryTextTheme
239 - .headlineMedium!
240 - .color!,
241 - borderRadius:
242 - BorderRadius.all(Radius.circular(6))),
243 - child: Center(
244 - child: Padding(
245 - padding: const EdgeInsets.all(6.0),
246 - child: Text( sendViewModel.selectedCryptoCurrency.tag!,
247 - style: TextStyle(
248 - fontSize: 12,
249 - fontWeight: FontWeight.bold,
250 - color: Theme.of(context)
251 - .primaryTextTheme
252 - .headlineMedium!
253 - .decorationColor!)),
202 + builder: (_) => Padding(
203 + padding: const EdgeInsets.only(top: 20),
204 + child: Row(
205 + children: [
206 + Padding(
207 + padding: const EdgeInsets.only(bottom: 8.0),
208 + child: Row(
209 + children: [
210 + sendViewModel.hasMultipleTokens
211 + ? Container(
212 + padding: EdgeInsets.only(right: 8),
213 + height: 32,
214 + child: InkWell(
215 + onTap: () => _presentPicker(context),
216 + child: Row(
217 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
218 + mainAxisSize: MainAxisSize.min,
219 + children: <Widget>[
220 + Padding(
221 + padding: EdgeInsets.only(right: 5),
222 + child: Image.asset(
223 + 'assets/images/arrow_bottom_purple_icon.png',
224 + color: Colors.white,
225 + height: 8,
226 + ),
227 + ),
228 + Text(
229 + sendViewModel.selectedCryptoCurrency.title,
230 + style: TextStyle(
231 + fontWeight: FontWeight.w600,
232 + fontSize: 16,
233 + color: Colors.white),
234 + ),
235 + ],
236 + ),
237 ),
255 - ),
256 - ),
257 - ) : Container(),
258 - Padding(
259 - padding: const EdgeInsets.only(right: 10.0),
260 - child: Text(':',
238 + )
239 + : Text(
240 + sendViewModel.selectedCryptoCurrency.title,
241 style: TextStyle(
242 fontWeight: FontWeight.w600,
243 fontSize: 16,
264 - color: Colors.white)),
244 + color: Colors.white),
245 + ),
246 + sendViewModel.selectedCryptoCurrency.tag != null
247 + ? Padding(
248 + padding: const EdgeInsets.fromLTRB(3.0, 0, 3.0, 0),
249 + child: Container(
250 + height: 32,
251 + decoration: BoxDecoration(
252 + color: Theme.of(context)
253 + .primaryTextTheme
254 + .headlineMedium!
255 + .color!,
256 + borderRadius: BorderRadius.all(
257 + Radius.circular(6),
258 + )),
259 + child: Center(
260 + child: Padding(
261 + padding: const EdgeInsets.all(6.0),
262 + child: Text(
263 + sendViewModel.selectedCryptoCurrency.tag!,
264 + style: TextStyle(
265 + fontSize: 12,
266 + fontWeight: FontWeight.bold,
267 + color: Theme.of(context)
268 + .primaryTextTheme
269 + .headlineMedium!
270 + .decorationColor!),
271 + ),
272 + ),
273 + ),
274 + ),
275 + )
276 + : Container(),
277 + Padding(
278 + padding: const EdgeInsets.only(right: 10.0),
279 + child: Text(
280 + ':',
281 + style: TextStyle(
282 + fontWeight: FontWeight.w600,
283 + fontSize: 16,
284 + color: Colors.white),
285 ),
266 - ],
267 - ),
286 + ),
287 + ],
288 ),
269 - Expanded(
270 - child: Stack(
271 - children: [
272 - BaseTextFormField(
273 - focusNode: cryptoAmountFocus,
274 - controller: cryptoAmountController,
275 - keyboardType:
276 - TextInputType.numberWithOptions(
277 - signed: false, decimal: true),
278 - inputFormatters: [
279 - FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))
280 - ],
281 - suffixIcon: SizedBox(
282 - width: prefixIconWidth,
283 - ),
284 - hintText: '0.0000',
285 - borderColor: Colors.transparent,
286 - textStyle: TextStyle(
287 - fontSize: 14,
288 - fontWeight: FontWeight.w500,
289 - color: Colors.white),
290 - placeholderTextStyle: TextStyle(
289 + ),
290 + Expanded(
291 + child: Stack(
292 + children: [
293 + BaseTextFormField(
294 + focusNode: cryptoAmountFocus,
295 + controller: cryptoAmountController,
296 + keyboardType: TextInputType.numberWithOptions(
297 + signed: false, decimal: true),
298 + inputFormatters: [
299 + FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))
300 + ],
301 + suffixIcon: SizedBox(
302 + width: prefixIconWidth,
303 + ),
304 + hintText: '0.0000',
305 + borderColor: Colors.transparent,
306 + textStyle: TextStyle(
307 + fontSize: 14,
308 + fontWeight: FontWeight.w500,
309 + color: Colors.white),
310 + placeholderTextStyle: TextStyle(
311 + color: Theme.of(context)
312 + .primaryTextTheme
313 + .headlineSmall!
314 + .decorationColor!,
315 + fontWeight: FontWeight.w500,
316 + fontSize: 14),
317 + validator: output.sendAll
318 + ? sendViewModel.allAmountValidator
319 + : sendViewModel.amountValidator,
320 + ),
321 + if (!sendViewModel.isBatchSending)
322 + Positioned(
323 + top: 2,
324 + right: 0,
325 + child: Container(
326 + width: prefixIconWidth,
327 + height: prefixIconHeight,
328 + child: InkWell(
329 + onTap: () async => output.setSendAll(),
330 + child: Container(
331 + decoration: BoxDecoration(
332 color: Theme.of(context)
333 .primaryTextTheme
293 - .headlineSmall!
294 - .decorationColor!,
295 - fontWeight: FontWeight.w500,
296 - fontSize: 14),
297 - validator: output.sendAll
298 - ? sendViewModel.allAmountValidator
299 - : sendViewModel
300 - .amountValidator),
301 - if (!sendViewModel.isBatchSending) Positioned(
302 - top: 2,
303 - right: 0,
304 - child: Container(
305 - width: prefixIconWidth,
306 - height: prefixIconHeight,
307 - child: InkWell(
308 - onTap: () async =>
309 - output.setSendAll(),
310 - child: Container(
311 - decoration: BoxDecoration(
312 - color: Theme.of(context)
313 - .primaryTextTheme
314 - .headlineMedium!
315 - .color!,
316 - borderRadius:
317 - BorderRadius.all(
318 - Radius.circular(6))),
319 - child: Center(
320 - child: Text(
321 - S.of(context).all,
322 - textAlign:
323 - TextAlign.center,
324 - style: TextStyle(
325 - fontSize: 12,
326 - fontWeight:
327 - FontWeight.bold,
328 - color:
329 - Theme.of(context)
330 - .primaryTextTheme
331 - .headlineMedium!
332 - .decorationColor!))),
333 - ))))]),
334 + .headlineMedium!
335 + .color!,
336 + borderRadius: BorderRadius.all(
337 + Radius.circular(6),
338 + ),
339 + ),
340 + child: Center(
341 + child: Text(
342 + S.of(context).all,
343 + textAlign: TextAlign.center,
344 + style: TextStyle(
345 + fontSize: 12,
346 + fontWeight: FontWeight.bold,
347 + color: Theme.of(context)
348 + .primaryTextTheme
349 + .headlineMedium!
350 + .decorationColor!,
351 + ),
352 + ),
353 + ),
354 + ),
355 + ),
356 + ),
357 + ),
358 + ],
359 ),
335 - ],
336 - )
337 - )),
338 - Divider(height: 1,color: Theme.of(context)
339 - .primaryTextTheme
340 - .headlineSmall!
341 - .decorationColor!),
360 + ),
361 + ],
362 + )),
363 + ),
364 + Divider(
365 + height: 1,
366 + color: Theme.of(context).primaryTextTheme.headlineSmall!.decorationColor!),
367 Observer(
343 - builder: (_) => Padding(
344 - padding: EdgeInsets.only(top: 10),
345 - child: Row(
346 - mainAxisSize: MainAxisSize.max,
347 - mainAxisAlignment:
348 - MainAxisAlignment.spaceBetween,
349 - children: <Widget>[
350 - Expanded(
351 - child: Text(
352 - S.of(context).available_balance +
353 - ':',
354 - style: TextStyle(
355 - fontSize: 12,
356 - fontWeight: FontWeight.w600,
357 - color: Theme.of(context)
358 - .primaryTextTheme
359 - .headlineSmall!
360 - .decorationColor!),
361 - )),
362 - Text(
363 - sendViewModel.balance,
368 + builder: (_) => Padding(
369 + padding: EdgeInsets.only(top: 10),
370 + child: Row(
371 + mainAxisSize: MainAxisSize.max,
372 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
373 + children: <Widget>[
374 + Expanded(
375 + child: Text(
376 + S.of(context).available_balance + ':',
377 style: TextStyle(
378 fontSize: 12,
379 fontWeight: FontWeight.w600,
@@ -368,10 +381,22 @@ class SendCardState extends State<SendCard>
381 .primaryTextTheme
382 .headlineSmall!
383 .decorationColor!),
371 - )
372 - ],
373 - ),
374 - )),
384 + ),
385 + ),
386 + Text(
387 + sendViewModel.balance,
388 + style: TextStyle(
389 + fontSize: 12,
390 + fontWeight: FontWeight.w600,
391 + color: Theme.of(context)
392 + .primaryTextTheme
393 + .headlineSmall!
394 + .decorationColor!),
395 + )
396 + ],
397 + ),
398 + ),
399 + ),
400 if (!sendViewModel.isFiatDisabled)
401 Padding(
402 padding: const EdgeInsets.only(top: 20),
@@ -379,171 +404,155 @@ class SendCardState extends State<SendCard>
404 focusNode: fiatAmountFocus,
405 controller: fiatAmountController,
406 keyboardType:
382 - TextInputType.numberWithOptions(
383 - signed: false, decimal: true),
407 + TextInputType.numberWithOptions(signed: false, decimal: true),
408 inputFormatters: [
385 - FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))
409 + FilteringTextInputFormatter.deny(
410 + RegExp('[\\-|\\ ]'),
411 + )
412 ],
413 prefixIcon: Padding(
414 padding: EdgeInsets.only(top: 9),
389 - child:
390 - Text(sendViewModel.fiat.title + ':',
391 - style: TextStyle(
392 - fontSize: 16,
393 - fontWeight: FontWeight.w600,
394 - color: Colors.white,
395 - )),
415 + child: Text(
416 + sendViewModel.fiat.title + ':',
417 + style: TextStyle(
418 + fontSize: 16,
419 + fontWeight: FontWeight.w600,
420 + color: Colors.white,
421 + ),
422 + ),
423 ),
424 hintText: '0.00',
398 - borderColor: Theme.of(context)
399 - .primaryTextTheme
400 - .headlineSmall!
401 - .color!,
425 + borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
426 textStyle: TextStyle(
403 - fontSize: 14,
404 - fontWeight: FontWeight.w500,
405 - color: Colors.white),
427 + fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
428 placeholderTextStyle: TextStyle(
429 color: Theme.of(context)
408 - .primaryTextTheme.headlineSmall!.decorationColor!,
430 + .primaryTextTheme
431 + .headlineSmall!
432 + .decorationColor!,
433 fontWeight: FontWeight.w500,
434 fontSize: 14),
411 - )),
435 + ),
436 + ),
437 Padding(
438 padding: EdgeInsets.only(top: 20),
439 child: BaseTextFormField(
440 controller: noteController,
441 keyboardType: TextInputType.multiline,
442 maxLines: null,
418 - borderColor: Theme.of(context)
419 - .primaryTextTheme
420 - .headlineSmall!
421 - .color!,
443 + borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
444 textStyle: TextStyle(
423 - fontSize: 14,
424 - fontWeight: FontWeight.w500,
425 - color: Colors.white),
445 + fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
446 hintText: S.of(context).note_optional,
447 placeholderTextStyle: TextStyle(
448 fontSize: 14,
449 fontWeight: FontWeight.w500,
430 - color: Theme.of(context)
431 - .primaryTextTheme
432 - .headlineSmall!
433 - .decorationColor!),
450 + color:
451 + Theme.of(context).primaryTextTheme.headlineSmall!.decorationColor!),
452 ),
453 ),
454 Observer(
437 - builder: (_) => GestureDetector(
438 - onTap: () =>
439 - _setTransactionPriority(context),
455 + builder: (_) => GestureDetector(
456 + onTap: () => _setTransactionPriority(context),
457 + child: Container(
458 + padding: EdgeInsets.only(top: 24),
459 + child: Row(
460 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
461 + crossAxisAlignment: CrossAxisAlignment.start,
462 + children: <Widget>[
463 + Text(
464 + S.of(context).send_estimated_fee,
465 + style: TextStyle(
466 + fontSize: 12,
467 + fontWeight: FontWeight.w500,
468 + //color: Theme.of(context).primaryTextTheme!.displaySmall!.color!,
469 + color: Colors.white),
470 + ),
471 + Container(
472 + child: Row(
473 + crossAxisAlignment: CrossAxisAlignment.start,
474 + children: <Widget>[
475 + Column(
476 + mainAxisAlignment: MainAxisAlignment.start,
477 + crossAxisAlignment: CrossAxisAlignment.end,
478 + children: [
479 + Text(
480 + output.estimatedFee.toString() +
481 + ' ' +
482 + sendViewModel.selectedCryptoCurrency.toString(),
483 + style: TextStyle(
484 + fontSize: 12,
485 + fontWeight: FontWeight.w600,
486 + //color: Theme.of(context).primaryTextTheme!.displaySmall!.color!,
487 + color: Colors.white,
488 + ),
489 + ),
490 + Padding(
491 + padding: EdgeInsets.only(top: 5),
492 + child: sendViewModel.isFiatDisabled
493 + ? const SizedBox(height: 14)
494 + : Text(
495 + output.estimatedFeeFiatAmount +
496 + ' ' +
497 + sendViewModel.fiat.title,
498 + style: TextStyle(
499 + fontSize: 12,
500 + fontWeight: FontWeight.w600,
501 + color: Theme.of(context)
502 + .primaryTextTheme
503 + .headlineSmall!
504 + .decorationColor!,
505 + ),
506 + ),
507 + ),
508 + ],
509 + ),
510 + Padding(
511 + padding: EdgeInsets.only(top: 2, left: 5),
512 + child: Icon(
513 + Icons.arrow_forward_ios,
514 + size: 12,
515 + color: Colors.white,
516 + ),
517 + )
518 + ],
519 + ),
520 + )
521 + ],
522 + ),
523 + ),
524 + ),
525 + ),
526 + if (sendViewModel.isElectrumWallet)
527 + Padding(
528 + padding: EdgeInsets.only(top: 6),
529 + child: GestureDetector(
530 + onTap: () => Navigator.of(context).pushNamed(Routes.unspentCoinsList),
531 child: Container(
441 - padding: EdgeInsets.only(top: 24),
532 + color: Colors.transparent,
533 child: Row(
443 - mainAxisAlignment:
444 - MainAxisAlignment.spaceBetween,
445 - crossAxisAlignment: CrossAxisAlignment.start,
446 - children: <Widget>[
534 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
535 + children: [
536 Text(
448 - S
449 - .of(context)
450 - .send_estimated_fee,
451 - style: TextStyle(
452 - fontSize: 12,
453 - fontWeight:
454 - FontWeight.w500,
455 - //color: Theme.of(context).primaryTextTheme!.displaySmall!.color!,
456 - color: Colors.white)),
457 - Container(
458 - child: Row(
459 - crossAxisAlignment: CrossAxisAlignment.start,
460 - children: <Widget>[
461 - Column(
462 - mainAxisAlignment: MainAxisAlignment.start,
463 - crossAxisAlignment: CrossAxisAlignment.end,
464 - children: [
465 - Text(
466 - output
467 - .estimatedFee
468 - .toString() +
469 - ' ' +
470 - sendViewModel
471 - .selectedCryptoCurrency.toString(),
472 - style: TextStyle(
473 - fontSize: 12,
474 - fontWeight:
475 - FontWeight.w600,
476 - //color: Theme.of(context).primaryTextTheme!.displaySmall!.color!,
477 - color:
478 - Colors.white)),
479 - Padding(
480 - padding:
481 - EdgeInsets.only(top: 5),
482 - child: sendViewModel.isFiatDisabled
483 - ? const SizedBox(height: 14)
484 - : Text(output
485 - .estimatedFeeFiatAmount
486 - + ' ' +
487 - sendViewModel
488 - .fiat.title,
489 - style: TextStyle(
490 - fontSize: 12,
491 - fontWeight:
492 - FontWeight.w600,
493 - color: Theme
494 - .of(context)
495 - .primaryTextTheme
496 - .headlineSmall!
497 - .decorationColor!))
498 - ),
499 - ],
500 - ),
501 - Padding(
502 - padding: EdgeInsets.only(
503 - top: 2,
504 - left: 5),
505 - child: Icon(
506 - Icons.arrow_forward_ios,
507 - size: 12,
508 - color: Colors.white,
509 - ),
510 - )
511 - ],
512 - ),
513 - )
537 + S.of(context).coin_control,
538 + style: TextStyle(
539 + fontSize: 12,
540 + fontWeight: FontWeight.w600,
541 + color: Colors.white),
542 + ),
543 + Icon(
544 + Icons.arrow_forward_ios,
545 + size: 12,
546 + color: Colors.white,
547 + ),
548 ],
549 ),
550 ),
517 - )),
518 - if (sendViewModel.isElectrumWallet) Padding(
519 - padding: EdgeInsets.only(top: 6),
520 - child: GestureDetector(
521 - onTap: () => Navigator.of(context)
522 - .pushNamed(Routes.unspentCoinsList),
523 - child: Container(
524 - color: Colors.transparent,
525 - child: Row(
526 - mainAxisAlignment:
527 - MainAxisAlignment.spaceBetween,
528 - children: [
529 - Text(
530 - S.of(context).coin_control,
531 - style: TextStyle(
532 - fontSize: 12,
533 - fontWeight: FontWeight.w600,
534 - color: Colors.white)),
535 - Icon(
536 - Icons.arrow_forward_ios,
537 - size: 12,
538 - color: Colors.white,
539 - )
540 - ],
541 - )
542 - )
543 - )
544 - )
551 + ),
552 + ),
553 ],
546 - ))
554 + ),
555 + ),
556 ),
557 ),
558 )
@@ -552,10 +561,10 @@ class SendCardState extends State<SendCard>
561 }
562
563 void _setEffects(BuildContext context) {
555 - if (_effectsInstalled) {
564 + if (_effectsInstalled) {
565 return;
566 }
558 -
567 +
568 if (output.address.isNotEmpty) {
569 addressController.text = output.address;
570 }
@@ -664,16 +673,30 @@ class SendCardState extends State<SendCard>
673 final selectedItem = items.indexOf(sendViewModel.transactionPriority);
674
675 await showPopUp<void>(
667 - builder: (_) => Picker(
668 - items: items,
669 - displayItem: sendViewModel.displayFeeRate,
670 - selectedAtIndex: selectedItem,
671 - title: S.of(context).please_select,
672 - mainAxisAlignment: MainAxisAlignment.center,
673 - onItemSelected: (TransactionPriority priority) =>
674 - sendViewModel.setTransactionPriority(priority),
675 - ),
676 - context: context);
676 + context: context,
677 + builder: (_) => Picker(
678 + items: items,
679 + displayItem: sendViewModel.displayFeeRate,
680 + selectedAtIndex: selectedItem,
681 + title: S.of(context).please_select,
682 + mainAxisAlignment: MainAxisAlignment.center,
683 + onItemSelected: (TransactionPriority priority) =>
684 + sendViewModel.setTransactionPriority(priority),
685 + ),
686 + );
687 + }
688 +
689 + void _presentPicker(BuildContext context) {
690 + showPopUp<void>(
691 + context: context,
692 + builder: (_) => CurrencyPicker(
693 + selectedAtIndex: sendViewModel.currencies.indexOf(sendViewModel.selectedCryptoCurrency),
694 + items: sendViewModel.currencies,
695 + hintText: S.of(context).search_currency,
696 + onItemSelected: (Currency cur) =>
697 + sendViewModel.selectedCryptoCurrency = (cur as CryptoCurrency),
698 + ),
699 + );
700 }
701
702 @override
lib/src/screens/send/widgets/send_template_card.dart
+136 -141
@@ -1,6 +1,10 @@
1 +import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
2 import 'package:cake_wallet/src/screens/send/widgets/prefix_currency_icon_widget.dart';
3 import 'package:cake_wallet/utils/payment_request.dart';
4 +import 'package:cake_wallet/utils/show_pop_up.dart';
5 import 'package:cake_wallet/view_model/send/template_view_model.dart';
6 +import 'package:cw_core/crypto_currency.dart';
7 +import 'package:cw_core/currency.dart';
8 import 'package:flutter_mobx/flutter_mobx.dart';
9 import 'package:flutter/material.dart';
10 import 'package:flutter/services.dart';
@@ -35,161 +39,140 @@ class SendTemplateCard extends StatelessWidget {
39 _setEffects(context);
40
41 return Container(
38 - decoration: BoxDecoration(
39 - borderRadius: BorderRadius.only(
40 - bottomLeft: Radius.circular(24),
41 - bottomRight: Radius.circular(24)),
42 - gradient: LinearGradient(colors: [
43 - Theme.of(context).primaryTextTheme.titleMedium!.color!,
44 - Theme.of(context).primaryTextTheme.titleMedium!.decorationColor!
45 - ], begin: Alignment.topLeft, end: Alignment.bottomRight)),
46 - child: Column(children: <Widget>[
42 + decoration: BoxDecoration(
43 + borderRadius:
44 + BorderRadius.only(bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)),
45 + gradient: LinearGradient(colors: [
46 + Theme.of(context).primaryTextTheme.titleMedium!.color!,
47 + Theme.of(context).primaryTextTheme.titleMedium!.decorationColor!
48 + ], begin: Alignment.topLeft, end: Alignment.bottomRight)),
49 + child: Column(
50 + children: <Widget>[
51 Padding(
48 - padding: EdgeInsets.fromLTRB(24, 90, 24, 32),
49 - child: Column(children: <Widget>[
52 + padding: EdgeInsets.fromLTRB(24, 90, 24, 32),
53 + child: Column(
54 + children: <Widget>[
55 if (index == 0)
56 BaseTextFormField(
57 controller: _nameController,
58 hintText: sendTemplateViewModel.recipients.length > 1
59 ? S.of(context).template_name
60 : S.of(context).send_name,
56 - borderColor: Theme.of(context)
57 - .primaryTextTheme
58 - .headlineSmall!
59 - .color!,
60 - textStyle: TextStyle(
61 - fontSize: 14,
62 - fontWeight: FontWeight.w500,
63 - color: Colors.white),
61 + borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
62 + textStyle:
63 + TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
64 placeholderTextStyle: TextStyle(
65 - color: Theme.of(context)
66 - .primaryTextTheme
67 - .headlineSmall!
68 - .decorationColor!,
65 + color: Theme.of(context).primaryTextTheme.headlineSmall!.decorationColor!,
66 fontWeight: FontWeight.w500,
67 fontSize: 14),
68 validator: sendTemplateViewModel.templateValidator),
69 Padding(
73 - padding: EdgeInsets.only(top: 20),
74 - child: AddressTextField(
75 - selectedCurrency: sendTemplateViewModel.cryptoCurrency,
76 - controller: _addressController,
77 - onURIScanned: (uri) {
78 - final paymentRequest = PaymentRequest.fromUri(uri);
79 - _addressController.text = paymentRequest.address;
80 - _cryptoAmountController.text = paymentRequest.amount;
81 - },
82 - options: [
83 - AddressTextFieldOption.paste,
84 - AddressTextFieldOption.qrCode,
85 - AddressTextFieldOption.addressBook
86 - ],
87 - onPushPasteButton: (context) async {
88 - template.output.resetParsedAddress();
89 - await template.output.fetchParsedAddress(context);
90 - },
91 - onPushAddressBookButton: (context) async {
92 - template.output.resetParsedAddress();
93 - await template.output.fetchParsedAddress(context);
94 - },
95 - buttonColor: Theme.of(context)
96 - .primaryTextTheme
97 - .headlineMedium!
98 - .color!,
99 - borderColor: Theme.of(context)
100 - .primaryTextTheme
101 - .headlineSmall!
102 - .color!,
103 - textStyle: TextStyle(
104 - fontSize: 14,
105 - fontWeight: FontWeight.w500,
106 - color: Colors.white),
107 - hintStyle: TextStyle(
108 - fontSize: 14,
109 - fontWeight: FontWeight.w500,
110 - color: Theme.of(context)
111 - .primaryTextTheme
112 - .headlineSmall!
113 - .decorationColor!),
114 - validator: sendTemplateViewModel.addressValidator)),
70 + padding: EdgeInsets.only(top: 20),
71 + child: AddressTextField(
72 + selectedCurrency: sendTemplateViewModel.cryptoCurrency,
73 + controller: _addressController,
74 + onURIScanned: (uri) {
75 + final paymentRequest = PaymentRequest.fromUri(uri);
76 + _addressController.text = paymentRequest.address;
77 + _cryptoAmountController.text = paymentRequest.amount;
78 + },
79 + options: [
80 + AddressTextFieldOption.paste,
81 + AddressTextFieldOption.qrCode,
82 + AddressTextFieldOption.addressBook
83 + ],
84 + onPushPasteButton: (context) async {
85 + template.output.resetParsedAddress();
86 + await template.output.fetchParsedAddress(context);
87 + },
88 + onPushAddressBookButton: (context) async {
89 + template.output.resetParsedAddress();
90 + await template.output.fetchParsedAddress(context);
91 + },
92 + buttonColor: Theme.of(context).primaryTextTheme.headlineMedium!.color!,
93 + borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
94 + textStyle: TextStyle(
95 + fontSize: 14,
96 + fontWeight: FontWeight.w500,
97 + color: Colors.white,
98 + ),
99 + hintStyle: TextStyle(
100 + fontSize: 14,
101 + fontWeight: FontWeight.w500,
102 + color: Theme.of(context).primaryTextTheme.headlineSmall!.decorationColor!,
103 + ),
104 + validator: sendTemplateViewModel.addressValidator,
105 + ),
106 + ),
107 Padding(
116 - padding: const EdgeInsets.only(top: 20),
117 - child: Focus(
118 - onFocusChange: (hasFocus) {
119 - if (hasFocus) {
120 - template.selectCurrency();
121 - }
122 - },
123 - child: BaseTextFormField(
124 - focusNode: _cryptoAmountFocus,
125 - controller: _cryptoAmountController,
126 - keyboardType: TextInputType.numberWithOptions(
127 - signed: false, decimal: true),
128 - inputFormatters: [
129 - FilteringTextInputFormatter.deny(
130 - RegExp('[\\-|\\ ]'))
131 - ],
132 - prefixIcon: Observer(
133 - builder: (_) => PrefixCurrencyIcon(
134 - title: sendTemplateViewModel
135 - .cryptoCurrency.title,
136 - isSelected: template.isCurrencySelected)),
137 - hintText: '0.0000',
138 - borderColor: Theme.of(context)
139 - .primaryTextTheme
140 - .headlineSmall!
141 - .color!,
142 - textStyle: TextStyle(
143 - fontSize: 14,
144 - fontWeight: FontWeight.w500,
145 - color: Colors.white),
146 - placeholderTextStyle: TextStyle(
147 - color: Theme.of(context)
148 - .primaryTextTheme
149 - .headlineSmall!
150 - .decorationColor!,
151 - fontWeight: FontWeight.w500,
152 - fontSize: 14),
153 - validator: sendTemplateViewModel.amountValidator))),
108 + padding: const EdgeInsets.only(top: 20),
109 + child: Focus(
110 + onFocusChange: (hasFocus) {
111 + if (hasFocus) {
112 + template.selectCurrency();
113 + }
114 + },
115 + child: BaseTextFormField(
116 + focusNode: _cryptoAmountFocus,
117 + controller: _cryptoAmountController,
118 + keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
119 + inputFormatters: [FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))],
120 + prefixIcon: Observer(
121 + builder: (_) => PrefixCurrencyIcon(
122 + title: template.selectedCurrency.title,
123 + isSelected: template.isCurrencySelected,
124 + onTap: sendTemplateViewModel.walletCurrencies.length > 1
125 + ? () => _presentPicker(context)
126 + : null,
127 + ),
128 + ),
129 + hintText: '0.0000',
130 + borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
131 + textStyle:
132 + TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
133 + placeholderTextStyle: TextStyle(
134 + color: Theme.of(context).primaryTextTheme.headlineSmall!.decorationColor!,
135 + fontWeight: FontWeight.w500,
136 + fontSize: 14),
137 + validator: sendTemplateViewModel.amountValidator,
138 + ),
139 + ),
140 + ),
141 Padding(
155 - padding: const EdgeInsets.only(top: 20),
156 - child: Focus(
157 - onFocusChange: (hasFocus) {
158 - if (hasFocus) {
159 - template.selectFiat();
160 - }
161 - },
162 - child: BaseTextFormField(
163 - focusNode: _fiatAmountFocus,
164 - controller: _fiatAmountController,
165 - keyboardType: TextInputType.numberWithOptions(
166 - signed: false, decimal: true),
167 - inputFormatters: [
168 - FilteringTextInputFormatter.deny(
169 - RegExp('[\\-|\\ ]'))
170 - ],
171 - prefixIcon: Observer(
172 - builder: (_) => PrefixCurrencyIcon(
173 - title: sendTemplateViewModel.fiatCurrency,
174 - isSelected: template.isFiatSelected)),
175 - hintText: '0.00',
176 - borderColor: Theme.of(context)
177 - .primaryTextTheme
178 - .headlineSmall!
179 - .color!,
180 - textStyle: TextStyle(
181 - fontSize: 14,
182 - fontWeight: FontWeight.w500,
183 - color: Colors.white),
184 - placeholderTextStyle: TextStyle(
185 - color: Theme.of(context)
186 - .primaryTextTheme
187 - .headlineSmall!
188 - .decorationColor!,
189 - fontWeight: FontWeight.w500,
190 - fontSize: 14))))
191 - ]))
192 - ]));
142 + padding: const EdgeInsets.only(top: 20),
143 + child: Focus(
144 + onFocusChange: (hasFocus) {
145 + if (hasFocus) {
146 + template.selectFiat();
147 + }
148 + },
149 + child: BaseTextFormField(
150 + focusNode: _fiatAmountFocus,
151 + controller: _fiatAmountController,
152 + keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
153 + inputFormatters: [FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]'))],
154 + prefixIcon: Observer(
155 + builder: (_) => PrefixCurrencyIcon(
156 + title: sendTemplateViewModel.fiatCurrency,
157 + isSelected: template.isFiatSelected)),
158 + hintText: '0.00',
159 + borderColor: Theme.of(context).primaryTextTheme.headlineSmall!.color!,
160 + textStyle:
161 + TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
162 + placeholderTextStyle: TextStyle(
163 + color: Theme.of(context).primaryTextTheme.headlineSmall!.decorationColor!,
164 + fontWeight: FontWeight.w500,
165 + fontSize: 14,
166 + ),
167 + ),
168 + ),
169 + ),
170 + ],
171 + ),
172 + )
173 + ],
174 + ),
175 + );
176 }
177
178 void _setEffects(BuildContext context) {
@@ -264,4 +247,16 @@ class SendTemplateCard extends StatelessWidget {
247
248 _effectsInstalled = true;
249 }
250 +
251 + void _presentPicker(BuildContext context) {
252 + showPopUp<void>(
253 + context: context,
254 + builder: (_) => CurrencyPicker(
255 + selectedAtIndex: sendTemplateViewModel.walletCurrencies.indexOf(template.selectedCurrency),
256 + items: sendTemplateViewModel.walletCurrencies,
257 + hintText: S.of(context).search_currency,
258 + onItemSelected: (Currency cur) => template.changeSelectedCurrency(cur as CryptoCurrency),
259 + ),
260 + );
261 + }
262 }
lib/src/screens/settings/privacy_page.dart
+9 -1
@@ -40,7 +40,8 @@ class PrivacyPage extends BasePage {
40 title: S.current.exchange,
41 items: ExchangeApiMode.all,
42 selectedItem: _privacySettingsViewModel.exchangeStatus,
43 - onItemSelected: (ExchangeApiMode mode) => _privacySettingsViewModel.setExchangeApiMode(mode),
43 + onItemSelected: (ExchangeApiMode mode) =>
44 + _privacySettingsViewModel.setExchangeApiMode(mode),
45 ),
46 ),
47 SettingsSwitcherCell(
@@ -68,6 +69,13 @@ class PrivacyPage extends BasePage {
69 onValueChange: (BuildContext _, bool value) {
70 _privacySettingsViewModel.setDisableSell(value);
71 }),
72 + if (_privacySettingsViewModel.canUseEtherscan)
73 + SettingsSwitcherCell(
74 + title: S.current.etherscan_history,
75 + value: _privacySettingsViewModel.useEtherscan,
76 + onValueChange: (BuildContext _, bool value) {
77 + _privacySettingsViewModel.setUseEtherscan(value);
78 + }),
79 ],
80 );
81 }),
lib/src/screens/settings/widgets/settings_switcher_cell.dart
+14 -5
@@ -3,14 +3,23 @@ import 'package:cake_wallet/src/widgets/standard_list.dart';
3 import 'package:cake_wallet/src/widgets/standard_switch.dart';
4
5 class SettingsSwitcherCell extends StandardListRow {
6 - SettingsSwitcherCell(
7 - {required String title, required this.value, this.onValueChange})
8 - : super(title: title, isSelected: false);
6 + SettingsSwitcherCell({
7 + required String title,
8 + required this.value,
9 + this.onValueChange,
10 + Decoration? decoration,
11 + this.leading,
12 + void Function(BuildContext context)? onTap,
13 + }) : super(title: title, isSelected: false, decoration: decoration, onTap: onTap);
14
15 final bool value;
16 final void Function(BuildContext context, bool value)? onValueChange;
17 + final Widget? leading;
18
19 @override
14 - Widget buildTrailing(BuildContext context) => StandardSwitch(
15 - value: value, onTaped: () => onValueChange?.call(context, !value));
20 + Widget buildTrailing(BuildContext context) =>
21 + StandardSwitch(value: value, onTaped: () => onValueChange?.call(context, !value));
22 +
23 + @override
24 + Widget? buildLeading(BuildContext context) => leading;
25 }
lib/src/screens/wallet_list/wallet_list_page.dart
+3
@@ -46,6 +46,7 @@ class WalletListBodyState extends State<WalletListBody> {
46 final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24);
47 final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
48 final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24);
49 + final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24);
50 final scrollController = ScrollController();
51 final double tileHeight = 60;
52 Flushbar<void>? _progressBar;
@@ -230,6 +231,8 @@ class WalletListBodyState extends State<WalletListBody> {
231 return litecoinIcon;
232 case WalletType.haven:
233 return havenIcon;
234 + case WalletType.ethereum:
235 + return ethereumIcon;
236 default:
237 return nonWalletTypeIcon;
238 }
lib/src/widgets/checkbox_widget.dart
+26 -41
@@ -1,13 +1,8 @@
1 -import 'dart:ui';
1 import 'package:cake_wallet/palette.dart';
3 -import 'package:flutter/cupertino.dart';
2 import 'package:flutter/material.dart';
3
4 class CheckboxWidget extends StatefulWidget {
7 - CheckboxWidget({
8 - required this.value,
9 - required this.caption,
10 - required this.onChanged});
5 + CheckboxWidget({required this.value, required this.caption, required this.onChanged});
6
7 final bool value;
8 final String caption;
@@ -26,55 +21,45 @@ class CheckboxWidgetState extends State<CheckboxWidget> {
21
22 @override
23 Widget build(BuildContext context) {
29 - return GestureDetector(
24 + return InkWell(
25 onTap: () {
26 value = !value;
27 onChanged(value);
28 setState(() {});
29 },
30 child: Row(
36 - mainAxisSize: MainAxisSize.min,
31 mainAxisAlignment: MainAxisAlignment.start,
32 + crossAxisAlignment: CrossAxisAlignment.start,
33 children: <Widget>[
34 Container(
40 - height: 16,
41 - width: 16,
35 + height: 24.0,
36 + width: 24.0,
37 + margin: EdgeInsets.only(right: 10.0),
38 decoration: BoxDecoration(
43 - color: value
44 - ? Palette.blueCraiola
45 - : Theme.of(context)
46 - .accentTextTheme!
47 - .titleMedium!
48 - .decorationColor!,
49 - borderRadius: BorderRadius.all(Radius.circular(2)),
50 - border: Border.all(
51 - color: value
52 - ? Palette.blueCraiola
53 - : Theme.of(context)
54 - .accentTextTheme!
55 - .labelSmall!
56 - .color!,
57 - width: 1)),
58 - child: value
59 - ? Center(
60 - child: Icon(
61 - Icons.done,
62 - color: Colors.white,
63 - size: 14,
39 + border: Border.all(
40 + color: Theme.of(context).primaryTextTheme.bodySmall!.color!,
41 + width: 1.0,
42 + ),
43 + borderRadius: BorderRadius.all(
44 + Radius.circular(8.0),
45 ),
65 - )
66 - : Offstage(),
46 + color: Theme.of(context).colorScheme.background,
47 + ),
48 + child: value
49 + ? Icon(
50 + Icons.check,
51 + color: Colors.blue,
52 + size: 20.0,
53 + )
54 + : null,
55 ),
68 - Padding(
69 - padding: EdgeInsets.only(left: 16),
56 + Expanded(
57 child: Text(
58 caption,
59 style: TextStyle(
73 - color: Theme.of(context).primaryTextTheme!.titleLarge!.color!,
74 - fontSize: 18,
75 - fontFamily: 'Lato',
76 - fontWeight: FontWeight.w500,
77 - decoration: TextDecoration.none
60 + fontWeight: FontWeight.bold,
61 + fontSize: 14.0,
62 + color: Theme.of(context).primaryTextTheme.titleLarge!.color!,
63 ),
64 ),
65 )
@@ -82,4 +67,4 @@ class CheckboxWidgetState extends State<CheckboxWidget> {
67 ),
68 );
69 }
85 -}
\ No newline at end of file
70 +}
lib/src/widgets/picker.dart
+49 -44
@@ -1,5 +1,7 @@
1 // ignore_for_file: deprecated_member_use
2
3 +import 'dart:math';
4 +
5 import 'package:cake_wallet/src/widgets/search_bar_widget.dart';
6 import 'package:cake_wallet/utils/responsive_layout_util.dart';
7 import 'package:flutter/material.dart';
@@ -145,8 +147,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
147 borderRadius: BorderRadius.all(Radius.circular(30)),
148 child: Container(
149 color: Theme.of(context)
148 - .accentTextTheme!
149 - .titleLarge!
150 + .accentTextTheme.titleLarge!
151 .color!,
152 child: ConstrainedBox(
153 constraints: BoxConstraints(
@@ -163,8 +164,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
164 ),
165 Divider(
166 color: Theme.of(context)
166 - .accentTextTheme!
167 - .titleLarge!
167 + .accentTextTheme.titleLarge!
168 .backgroundColor!,
169 height: 1,
170 ),
@@ -194,8 +194,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
194 fontFamily: 'Lato',
195 decoration: TextDecoration.none,
196 color: Theme.of(context)
197 - .primaryTextTheme!
198 - .titleLarge!
197 + .primaryTextTheme.titleLarge!
198 .color!,
199 ),
200 ),
@@ -217,8 +216,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
216 Widget itemsList() {
217 return Container(
218 color: Theme.of(context)
220 - .accentTextTheme!
221 - .titleLarge!
219 + .accentTextTheme.titleLarge!
220 .backgroundColor!,
221 child: widget.isGridView
222 ? GridView.builder(
@@ -240,8 +238,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
238 separatorBuilder: (context, index) => widget.isSeparated
239 ? Divider(
240 color: Theme.of(context)
243 - .accentTextTheme!
244 - .titleLarge!
241 + .accentTextTheme.titleLarge!
242 .backgroundColor!,
243 height: 1,
244 )
@@ -254,15 +251,9 @@ class _PickerState<Item> extends State<Picker<Item>> {
251
252 Widget buildItem(int index) {
253 final item = filteredItems[index];
257 - final tag = item is Currency ? item.tag : null;
254
259 - final icon = item is Currency && item.iconPath != null
260 - ? Image.asset(
261 - item.iconPath!,
262 - height: 20.0,
263 - width: 20.0,
264 - )
265 - : null;
255 + final tag = item is Currency ? item.tag : null;
256 + final icon = _getItemIcon(item);
257
258 final image = images.isNotEmpty ? filteredImages[index] : icon;
259
@@ -274,8 +265,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
265 child: Container(
266 height: 55,
267 color: Theme.of(context)
277 - .accentTextTheme!
278 - .titleLarge!
268 + .accentTextTheme.titleLarge!
269 .color!,
270 padding: EdgeInsets.symmetric(horizontal: 24),
271 child: Row(
@@ -298,8 +288,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
288 fontFamily: 'Lato',
289 fontWeight: FontWeight.w600,
290 color: Theme.of(context)
301 - .primaryTextTheme!
302 - .titleLarge!
291 + .primaryTextTheme.titleLarge!
292 .color!,
293 decoration: TextDecoration.none,
294 ),
@@ -318,8 +307,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
307 fontSize: 7.0,
308 fontFamily: 'Lato',
309 color: Theme.of(context)
321 - .textTheme!
322 - .bodyMedium!
310 + .textTheme.bodyMedium!
311 .color!),
312 ),
313 ),
@@ -327,8 +315,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
315 borderRadius: BorderRadius.circular(6.0),
316 //border: Border.all(color: ),
317 color: Theme.of(context)
330 - .textTheme!
331 - .bodyMedium!
318 + .textTheme.bodyMedium!
319 .decorationColor!,
320 ),
321 ),
@@ -345,15 +332,9 @@ class _PickerState<Item> extends State<Picker<Item>> {
332
333 Widget buildSelectedItem(int index) {
334 final item = items[index];
348 - final tag = item is Currency ? item.tag : null;
335
350 - final icon = item is Currency && item.iconPath != null
351 - ? Image.asset(
352 - item.iconPath!,
353 - height: 20.0,
354 - width: 20.0,
355 - )
356 - : null;
336 + final tag = item is Currency ? item.tag : null;
337 + final icon = _getItemIcon(item);
338
339 final image = images.isNotEmpty ? images[index] : icon;
340
@@ -364,8 +345,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
345 child: Container(
346 height: 55,
347 color: Theme.of(context)
367 - .accentTextTheme!
368 - .titleLarge!
348 + .accentTextTheme.titleLarge!
349 .color!,
350 padding: EdgeInsets.symmetric(horizontal: 24),
351 child: Row(
@@ -388,8 +368,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
368 fontFamily: 'Lato',
369 fontWeight: FontWeight.w700,
370 color: Theme.of(context)
391 - .primaryTextTheme!
392 - .titleLarge!
371 + .primaryTextTheme.titleLarge!
372 .color!,
373 decoration: TextDecoration.none,
374 ),
@@ -408,8 +387,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
387 fontSize: 7.0,
388 fontFamily: 'Lato',
389 color: Theme.of(context)
411 - .textTheme!
412 - .bodyMedium!
390 + .textTheme.bodyMedium!
391 .color!),
392 ),
393 ),
@@ -417,8 +395,7 @@ class _PickerState<Item> extends State<Picker<Item>> {
395 borderRadius: BorderRadius.circular(6.0),
396 //border: Border.all(color: ),
397 color: Theme.of(context)
420 - .textTheme!
421 - .bodyMedium!
398 + .textTheme.bodyMedium!
399 .decorationColor!,
400 ),
401 ),
@@ -429,12 +406,40 @@ class _PickerState<Item> extends State<Picker<Item>> {
406 ),
407 Icon(Icons.check_circle,
408 color: Theme.of(context)
432 - .accentTextTheme!
433 - .bodyLarge!
409 + .accentTextTheme.bodyLarge!
410 .color!),
411 ],
412 ),
413 ),
414 );
415 }
416 +
417 + Widget? _getItemIcon(Item item) {
418 + if (item is Currency) {
419 + if (item.iconPath != null) {
420 + return Image.asset(
421 + item.iconPath!,
422 + height: 20.0,
423 + width: 20.0,
424 + );
425 + } else {
426 + return Container(
427 + height: 20.0,
428 + width: 20.0,
429 + child: Center(
430 + child: Text(
431 + item.name.substring(0, min(item.name.length, 2)).toUpperCase(),
432 + style: TextStyle(fontSize: 11),
433 + ),
434 + ),
435 + decoration: BoxDecoration(
436 + shape: BoxShape.circle,
437 + color: Colors.grey.shade400,
438 + ),
439 + );
440 + }
441 + }
442 +
443 + return null;
444 + }
445 }
lib/src/widgets/standard_list.dart
+7 -4
@@ -5,11 +5,12 @@ import 'package:flutter/material.dart';
5
6 class StandardListRow extends StatelessWidget {
7 StandardListRow(
8 - {required this.title, required this.isSelected, this.onTap});
8 + {required this.title, required this.isSelected, this.onTap, this.decoration});
9
10 final String title;
11 final bool isSelected;
12 final void Function(BuildContext context)? onTap;
13 + final Decoration? decoration;
14
15 @override
16 Widget build(BuildContext context) {
@@ -19,9 +20,11 @@ class StandardListRow extends StatelessWidget {
20 return InkWell(
21 onTap: () => onTap?.call(context),
22 child: Container(
22 - color: _backgroundColor(context),
23 height: 56,
24 padding: EdgeInsets.only(left: 24, right: 24),
25 + decoration: decoration ?? BoxDecoration(
26 + color: _backgroundColor(context),
27 + ),
28 child: Row(
29 mainAxisAlignment: MainAxisAlignment.spaceBetween,
30 children: <Widget>[
@@ -54,7 +57,7 @@ class StandardListRow extends StatelessWidget {
57
58 Color titleColor(BuildContext context) => isSelected
59 ? Palette.blueCraiola
57 - : Theme.of(context).primaryTextTheme!.titleLarge!.color!;
60 + : Theme.of(context).primaryTextTheme.titleLarge!.color!;
61
62 Color _backgroundColor(BuildContext context) {
63 return Theme.of(context).colorScheme.background;
@@ -89,7 +92,7 @@ class StandardListSeparator extends StatelessWidget {
92 child: Container(
93 height: height,
94 color: Theme.of(context)
92 - .primaryTextTheme!
95 + .primaryTextTheme
96 .titleLarge
97 ?.backgroundColor));
98 }
lib/store/settings_store.dart
+79 -2
@@ -5,7 +5,9 @@ import 'package:cake_wallet/entities/cake_2fa_preset_options.dart';
5 import 'package:cake_wallet/entities/exchange_api_mode.dart';
6 import 'package:cake_wallet/entities/pin_code_required_duration.dart';
7 import 'package:cake_wallet/entities/preferences_key.dart';
8 +import 'package:cake_wallet/entities/sort_balance_types.dart';
9 import 'package:cake_wallet/utils/device_info.dart';
10 +import 'package:cake_wallet/ethereum/ethereum.dart';
11 import 'package:cw_core/transaction_priority.dart';
12 import 'package:cake_wallet/themes/theme_base.dart';
13 import 'package:cake_wallet/themes/theme_list.dart';
@@ -66,10 +68,14 @@ abstract class SettingsStoreBase with Store {
68 required bool initialShouldRequireTOTP2FAForAddingContacts,
69 required bool initialShouldRequireTOTP2FAForCreatingNewWallets,
70 required bool initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings,
71 + required this.sortBalanceBy,
72 + required this.pinNativeTokenAtTop,
73 + required this.useEtherscan,
74 TransactionPriority? initialBitcoinTransactionPriority,
75 TransactionPriority? initialMoneroTransactionPriority,
76 TransactionPriority? initialHavenTransactionPriority,
72 - TransactionPriority? initialLitecoinTransactionPriority})
77 + TransactionPriority? initialLitecoinTransactionPriority,
78 + TransactionPriority? initialEthereumTransactionPriority})
79 : nodes = ObservableMap<WalletType, Node>.of(nodes),
80 _sharedPreferences = sharedPreferences,
81 fiatCurrency = initialFiatCurrency,
@@ -120,6 +126,10 @@ abstract class SettingsStoreBase with Store {
126 priority[WalletType.litecoin] = initialLitecoinTransactionPriority;
127 }
128
129 + if (initialEthereumTransactionPriority != null) {
130 + priority[WalletType.ethereum] = initialEthereumTransactionPriority;
131 + }
132 +
133 reaction(
134 (_) => fiatCurrency,
135 (FiatCurrency fiatCurrency) => sharedPreferences.setString(
@@ -145,6 +155,9 @@ abstract class SettingsStoreBase with Store {
155 case WalletType.haven:
156 key = PreferencesKey.havenTransactionPriority;
157 break;
158 + case WalletType.ethereum:
159 + key = PreferencesKey.ethereumTransactionPriority;
160 + break;
161 default:
162 key = null;
163 }
@@ -279,6 +292,21 @@ abstract class SettingsStoreBase with Store {
292 (ExchangeApiMode mode) =>
293 sharedPreferences.setInt(PreferencesKey.exchangeStatusKey, mode.serialize()));
294
295 + reaction(
296 + (_) => sortBalanceBy,
297 + (SortBalanceBy sortBalanceBy) =>
298 + _sharedPreferences.setInt(PreferencesKey.sortBalanceBy, sortBalanceBy.index));
299 +
300 + reaction(
301 + (_) => pinNativeTokenAtTop,
302 + (bool pinNativeTokenAtTop) =>
303 + _sharedPreferences.setBool(PreferencesKey.pinNativeTokenAtTop, pinNativeTokenAtTop));
304 +
305 + reaction(
306 + (_) => useEtherscan,
307 + (bool useEtherscan) =>
308 + _sharedPreferences.setBool(PreferencesKey.useEtherscan, useEtherscan));
309 +
310 this.nodes.observe((change) {
311 if (change.newValue != null && change.key != null) {
312 _saveCurrentNode(change.newValue!, change.key!);
@@ -385,6 +413,15 @@ abstract class SettingsStoreBase with Store {
413 @observable
414 ObservableMap<WalletType, TransactionPriority> priority;
415
416 + @observable
417 + SortBalanceBy sortBalanceBy;
418 +
419 + @observable
420 + bool pinNativeTokenAtTop;
421 +
422 + @observable
423 + bool useEtherscan;
424 +
425 String appVersion;
426
427 String deviceName;
@@ -429,6 +466,7 @@ abstract class SettingsStoreBase with Store {
466
467 TransactionPriority? havenTransactionPriority;
468 TransactionPriority? litecoinTransactionPriority;
469 + TransactionPriority? ethereumTransactionPriority;
470
471 if (sharedPreferences.getInt(PreferencesKey.havenTransactionPriority) != null) {
472 havenTransactionPriority = monero?.deserializeMoneroTransactionPriority(
@@ -438,11 +476,16 @@ abstract class SettingsStoreBase with Store {
476 litecoinTransactionPriority = bitcoin?.deserializeLitecoinTransactionPriority(
477 sharedPreferences.getInt(PreferencesKey.litecoinTransactionPriority)!);
478 }
479 + if (sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority) != null) {
480 + ethereumTransactionPriority = bitcoin?.deserializeLitecoinTransactionPriority(
481 + sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!);
482 + }
483
484 moneroTransactionPriority ??= monero?.getDefaultTransactionPriority();
485 bitcoinTransactionPriority ??= bitcoin?.getMediumTransactionPriority();
486 havenTransactionPriority ??= monero?.getDefaultTransactionPriority();
487 litecoinTransactionPriority ??= bitcoin?.getLitecoinTransactionPriorityMedium();
488 + ethereumTransactionPriority ??= ethereum?.getDefaultTransactionPriority();
489
490 final currentBalanceDisplayMode = BalanceDisplayMode.deserialize(
491 raw: sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey)!);
@@ -502,6 +545,12 @@ abstract class SettingsStoreBase with Store {
545 final pinCodeTimeOutDuration = timeOutDuration != null
546 ? PinCodeRequiredDuration.deserialize(raw: timeOutDuration)
547 : defaultPinCodeTimeOutDuration;
548 + final sortBalanceBy =
549 + SortBalanceBy.values[sharedPreferences.getInt(PreferencesKey.sortBalanceBy) ?? 0];
550 + final pinNativeTokenAtTop =
551 + sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop) ?? true;
552 + final useEtherscan =
553 + sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true;
554
555 // If no value
556 if (pinLength == null || pinLength == 0) {
@@ -516,10 +565,12 @@ abstract class SettingsStoreBase with Store {
565 final litecoinElectrumServerId =
566 sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
567 final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey);
568 + final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
569 final moneroNode = nodeSource.get(nodeId);
570 final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId);
571 final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
572 final havenNode = nodeSource.get(havenNodeId);
573 + final ethereumNode = nodeSource.get(ethereumNodeId);
574 final packageInfo = await PackageInfo.fromPlatform();
575 final deviceName = await _getDeviceName() ?? '';
576 final shouldShowYatPopup = sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? true;
@@ -542,6 +593,10 @@ abstract class SettingsStoreBase with Store {
593 nodes[WalletType.haven] = havenNode;
594 }
595
596 + if (ethereumNode != null) {
597 + nodes[WalletType.ethereum] = ethereumNode;
598 + }
599 +
600 return SettingsStore(
601 sharedPreferences: sharedPreferences,
602 initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard,
@@ -567,6 +622,9 @@ abstract class SettingsStoreBase with Store {
622 initialPinLength: pinLength,
623 pinTimeOutDuration: pinCodeTimeOutDuration,
624 initialLanguageCode: savedLanguageCode,
625 + sortBalanceBy: sortBalanceBy,
626 + pinNativeTokenAtTop: pinNativeTokenAtTop,
627 + useEtherscan: useEtherscan,
628 initialMoneroTransactionPriority: moneroTransactionPriority,
629 initialBitcoinTransactionPriority: bitcoinTransactionPriority,
630 initialHavenTransactionPriority: havenTransactionPriority,
@@ -582,6 +640,7 @@ abstract class SettingsStoreBase with Store {
640 initialShouldRequireTOTP2FAForCreatingNewWallets: shouldRequireTOTP2FAForCreatingNewWallets,
641 initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings:
642 shouldRequireTOTP2FAForAllSecurityAndBackupSettings,
643 + initialEthereumTransactionPriority: ethereumTransactionPriority,
644 shouldShowYatPopup: shouldShowYatPopup);
645 }
646
@@ -608,6 +667,11 @@ abstract class SettingsStoreBase with Store {
667 sharedPreferences.getInt(PreferencesKey.litecoinTransactionPriority)!) ??
668 priority[WalletType.litecoin]!;
669 }
670 + if (sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority) != null) {
671 + priority[WalletType.ethereum] = ethereum?.deserializeEthereumTransactionPriority(
672 + sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!) ??
673 + priority[WalletType.ethereum]!;
674 + }
675
676 balanceDisplayMode = BalanceDisplayMode.deserialize(
677 raw: sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey)!);
@@ -616,7 +680,7 @@ abstract class SettingsStoreBase with Store {
680 shouldSaveRecipientAddress;
681 totpSecretKey = sharedPreferences.getString(PreferencesKey.totpSecretKey) ?? totpSecretKey;
682 useTOTP2FA = sharedPreferences.getBool(PreferencesKey.useTOTP2FA) ?? useTOTP2FA;
619 -
683 +
684 numberOfFailedTokenTrials =
685 sharedPreferences.getInt(PreferencesKey.failedTotpTokenTrials) ?? numberOfFailedTokenTrials;
686 sharedPreferences.getBool(PreferencesKey.shouldSaveRecipientAddressKey) ??
@@ -677,6 +741,10 @@ abstract class SettingsStoreBase with Store {
741 languageCode = sharedPreferences.getString(PreferencesKey.currentLanguageCode) ?? languageCode;
742 shouldShowYatPopup =
743 sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? shouldShowYatPopup;
744 + sortBalanceBy = SortBalanceBy
745 + .values[sharedPreferences.getInt(PreferencesKey.sortBalanceBy) ?? sortBalanceBy.index];
746 + pinNativeTokenAtTop = sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop) ?? true;
747 + useEtherscan = sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true;
748
749 final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
750 final bitcoinElectrumServerId =
@@ -684,10 +752,12 @@ abstract class SettingsStoreBase with Store {
752 final litecoinElectrumServerId =
753 sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
754 final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey);
755 + final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
756 final moneroNode = nodeSource.get(nodeId);
757 final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId);
758 final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
759 final havenNode = nodeSource.get(havenNodeId);
760 + final ethereumNode = nodeSource.get(ethereumNodeId);
761
762 if (moneroNode != null) {
763 nodes[WalletType.monero] = moneroNode;
@@ -704,6 +774,10 @@ abstract class SettingsStoreBase with Store {
774 if (havenNode != null) {
775 nodes[WalletType.haven] = havenNode;
776 }
777 +
778 + if (ethereumNode != null) {
779 + nodes[WalletType.ethereum] = ethereumNode;
780 + }
781 }
782
783 Future<void> _saveCurrentNode(Node node, WalletType walletType) async {
@@ -722,6 +796,9 @@ abstract class SettingsStoreBase with Store {
796 case WalletType.haven:
797 await _sharedPreferences.setInt(PreferencesKey.currentHavenNodeIdKey, node.key as int);
798 break;
799 + case WalletType.ethereum:
800 + await _sharedPreferences.setInt(PreferencesKey.currentEthereumNodeIdKey, node.key as int);
801 + break;
802 default:
803 break;
804 }
lib/utils/exception_handler.dart
+1
@@ -144,6 +144,7 @@ class ExceptionHandler {
144 "Connection closed before full header was received",
145 "Connection terminated during handshake",
146 "PERMISSION_NOT_GRANTED",
147 + "Failed host lookup: ",
148 ];
149
150 static Future<void> _addDeviceInfo(File file) async {
lib/view_model/contact_list/contact_list_view_model.dart
+7 -2
@@ -1,4 +1,5 @@
1 import 'dart:async';
2 +import 'package:cake_wallet/entities/contact_base.dart';
3 import 'package:cake_wallet/entities/wallet_contact.dart';
4 import 'package:cake_wallet/store/settings_store.dart';
5 import 'package:cw_core/wallet_info.dart';
@@ -57,11 +58,15 @@ abstract class ContactListViewModelBase with Store {
58
59 @computed
60 List<ContactRecord> get contactsToShow => contacts
60 - .where((element) => _currency == null || element.type == _currency)
61 + .where((element) => _isValidForCurrency(element))
62 .toList();
63
64 @computed
65 List<WalletContact> get walletContactsToShow => walletContacts
65 - .where((element) => _currency == null || element.type == _currency)
66 + .where((element) => _isValidForCurrency(element))
67 .toList();
68 +
69 + bool _isValidForCurrency(ContactBase element) {
70 + return _currency == null || element.type == _currency || element.type.title == _currency!.tag;
71 + }
72 }
lib/view_model/dashboard/balance_view_model.dart
+52 -14
@@ -1,4 +1,5 @@
1 import 'package:cake_wallet/entities/fiat_api_mode.dart';
2 +import 'package:cake_wallet/entities/sort_balance_types.dart';
3 import 'package:cw_core/transaction_history.dart';
4 import 'package:cw_core/wallet_base.dart';
5 import 'package:cw_core/balance.dart';
@@ -79,6 +80,15 @@ abstract class BalanceViewModelBase with Store {
80 @computed
81 bool get isFiatDisabled => settingsStore.fiatApiMode == FiatApiMode.disabled;
82
83 + @computed
84 + bool get isHomeScreenSettingsEnabled => wallet.type == WalletType.ethereum;
85 +
86 + @computed
87 + SortBalanceBy get sortBalanceBy => settingsStore.sortBalanceBy;
88 +
89 + @computed
90 + bool get pinNativeToken => settingsStore.pinNativeTokenAtTop;
91 +
92 @computed
93 String get asset {
94 final typeFormatted = walletTypeToString(appStore.wallet!.type);
@@ -109,6 +119,7 @@ abstract class BalanceViewModelBase with Store {
119 switch(wallet.type) {
120 case WalletType.monero:
121 case WalletType.haven:
122 + case WalletType.ethereum:
123 return S.current.xmr_available_balance;
124 default:
125 return S.current.confirmed;
@@ -120,6 +131,7 @@ abstract class BalanceViewModelBase with Store {
131 switch(wallet.type) {
132 case WalletType.monero:
133 case WalletType.haven:
134 + case WalletType.ethereum:
135 return S.current.xmr_full_balance;
136 default:
137 return S.current.unconfirmed;
@@ -262,32 +274,58 @@ abstract class BalanceViewModelBase with Store {
274 });
275 }
276
277 + @computed
278 + bool get hasAdditionalBalance => wallet.type != WalletType.ethereum;
279 +
280 @computed
281 List<BalanceRecord> get formattedBalances {
282 final balance = balances.values.toList();
283
284 balance.sort((BalanceRecord a, BalanceRecord b) {
270 - if (b.asset == CryptoCurrency.xhv) {
271 - return 1;
272 - }
285 + if (wallet.currency == CryptoCurrency.xhv) {
286 + if (b.asset == CryptoCurrency.xhv) {
287 + return 1;
288 + }
289 +
290 + if (b.asset == CryptoCurrency.xusd) {
291 + if (a.asset == CryptoCurrency.xhv) {
292 + return -1;
293 + }
294
274 - if (b.asset == CryptoCurrency.xusd) {
275 - if (a.asset == CryptoCurrency.xhv) {
276 - return -1;
295 + return 1;
296 }
297
279 - return 1;
280 - }
298 + if (b.asset == CryptoCurrency.xbtc) {
299 + return 1;
300 + }
301
282 - if (b.asset == CryptoCurrency.xbtc) {
283 - return 1;
302 + if (b.asset == CryptoCurrency.xeur) {
303 + return 1;
304 + }
305 +
306 + return 0;
307 }
308
286 - if (b.asset == CryptoCurrency.xeur) {
287 - return 1;
309 + if (pinNativeToken) {
310 + if (b.asset == wallet.currency) return 1;
311 + if (a.asset == wallet.currency) return -1;
312 }
313
290 - return 0;
314 + switch (sortBalanceBy) {
315 + case SortBalanceBy.FiatBalance:
316 + final aFiatBalance = _getFiatBalance(
317 + price: fiatConvertationStore.prices[a.asset] ?? 0, cryptoAmount: a.availableBalance);
318 + final bFiatBalance = _getFiatBalance(
319 + price: fiatConvertationStore.prices[b.asset] ?? 0, cryptoAmount: b.availableBalance);
320 +
321 + return (double.tryParse(bFiatBalance) ?? 0)
322 + .compareTo((double.tryParse(aFiatBalance)) ?? 0);
323 + case SortBalanceBy.GrossBalance:
324 + return (double.tryParse(b.availableBalance) ?? 0)
325 + .compareTo(double.tryParse(a.availableBalance) ?? 0);
326 + case SortBalanceBy.Alphabetical:
327 + return a.asset.title.compareTo(b.asset.title);
328 + }
329 });
330
331 return balance;
@@ -335,7 +373,7 @@ abstract class BalanceViewModelBase with Store {
373 }
374
375 String _getFiatBalance({required double price, String? cryptoAmount}) {
338 - if (cryptoAmount == null || cryptoAmount.isEmpty) {
376 + if (cryptoAmount == null || cryptoAmount.isEmpty || double.tryParse(cryptoAmount) == null) {
377 return '0.00';
378 }
379
lib/view_model/dashboard/home_settings_view_model.dart new
+121
@@ -0,0 +1,121 @@
1 +import 'package:cake_wallet/core/fiat_conversion_service.dart';
2 +import 'package:cake_wallet/entities/fiat_api_mode.dart';
3 +import 'package:cake_wallet/entities/sort_balance_types.dart';
4 +import 'package:cake_wallet/ethereum/ethereum.dart';
5 +import 'package:cake_wallet/store/settings_store.dart';
6 +import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
7 +import 'package:cw_core/crypto_currency.dart';
8 +import 'package:cw_core/erc20_token.dart';
9 +import 'package:mobx/mobx.dart';
10 +
11 +part 'home_settings_view_model.g.dart';
12 +
13 +class HomeSettingsViewModel = HomeSettingsViewModelBase with _$HomeSettingsViewModel;
14 +
15 +abstract class HomeSettingsViewModelBase with Store {
16 + HomeSettingsViewModelBase(this._settingsStore, this._balanceViewModel)
17 + : tokens = ObservableSet<Erc20Token>() {
18 + _updateTokensList();
19 + }
20 +
21 + final SettingsStore _settingsStore;
22 + final BalanceViewModel _balanceViewModel;
23 +
24 + final ObservableSet<Erc20Token> tokens;
25 +
26 + @observable
27 + String searchText = '';
28 +
29 + @computed
30 + SortBalanceBy get sortBalanceBy => _settingsStore.sortBalanceBy;
31 +
32 + @action
33 + void setSortBalanceBy(SortBalanceBy value) {
34 + _settingsStore.sortBalanceBy = value;
35 + _updateTokensList();
36 + }
37 +
38 + @computed
39 + bool get pinNativeToken => _settingsStore.pinNativeTokenAtTop;
40 +
41 + @action
42 + void setPinNativeToken(bool value) => _settingsStore.pinNativeTokenAtTop = value;
43 +
44 + Future<void> addErc20Token(Erc20Token token) async {
45 + await ethereum!.addErc20Token(_balanceViewModel.wallet, token);
46 + _updateTokensList();
47 + _updateFiatPrices(token);
48 + }
49 +
50 + Future<void> deleteErc20Token(Erc20Token token) async {
51 + await ethereum!.deleteErc20Token(_balanceViewModel.wallet, token);
52 + _updateTokensList();
53 + }
54 +
55 + Future<Erc20Token?> getErc20Token(String contractAddress) async =>
56 + await ethereum!.getErc20Token(_balanceViewModel.wallet, contractAddress);
57 +
58 + CryptoCurrency get nativeToken => _balanceViewModel.wallet.currency;
59 +
60 + void _updateFiatPrices(Erc20Token token) async {
61 + try {
62 + _balanceViewModel.fiatConvertationStore.prices[token] =
63 + await FiatConversionService.fetchPrice(
64 + crypto: token,
65 + fiat: _settingsStore.fiatCurrency,
66 + torOnly: _settingsStore.fiatApiMode == FiatApiMode.torOnly);
67 + } catch (_) {}
68 + }
69 +
70 + void changeTokenAvailability(Erc20Token token, bool value) async {
71 + token.enabled = value;
72 + ethereum!.addErc20Token(_balanceViewModel.wallet, token);
73 + _refreshTokensList();
74 + }
75 +
76 + @action
77 + void _updateTokensList() {
78 + int _sortFunc(Erc20Token e1, Erc20Token e2) {
79 + int index1 = _balanceViewModel.formattedBalances.indexWhere((element) => element.asset == e1);
80 + int index2 = _balanceViewModel.formattedBalances.indexWhere((element) => element.asset == e2);
81 +
82 + if (e1.enabled && !e2.enabled) {
83 + return -1;
84 + } else if (e2.enabled && !e1.enabled) {
85 + return 1;
86 + } else if (!e1.enabled && !e2.enabled) { // if both are disabled then sort alphabetically
87 + return e1.name.compareTo(e2.name);
88 + }
89 +
90 + return index1.compareTo(index2);
91 + }
92 +
93 + tokens.clear();
94 +
95 + tokens.addAll(ethereum!
96 + .getERC20Currencies(_balanceViewModel.wallet)
97 + .where((element) => _matchesSearchText(element))
98 + .toList()
99 + ..sort(_sortFunc));
100 + }
101 +
102 + @action
103 + void _refreshTokensList() {
104 + final _tokens = Set.of(tokens);
105 + tokens.clear();
106 + tokens.addAll(_tokens);
107 + }
108 +
109 + @action
110 + void changeSearchText(String text) {
111 + searchText = text;
112 + _updateTokensList();
113 + }
114 +
115 + bool _matchesSearchText(Erc20Token asset) {
116 + return searchText.isEmpty ||
117 + asset.fullName!.toLowerCase().contains(searchText.toLowerCase()) ||
118 + asset.title.toLowerCase().contains(searchText.toLowerCase()) ||
119 + asset.contractAddress == searchText;
120 + }
121 +}
lib/view_model/dashboard/transaction_list_item.dart
+8
@@ -1,5 +1,6 @@
1 import 'package:cake_wallet/entities/balance_display_mode.dart';
2 import 'package:cake_wallet/entities/fiat_currency.dart';
3 +import 'package:cake_wallet/ethereum/ethereum.dart';
4 import 'package:cake_wallet/generated/i18n.dart';
5 import 'package:cw_core/transaction_direction.dart';
6 import 'package:cw_core/transaction_info.dart';
@@ -84,6 +85,13 @@ class TransactionListItem extends ActionListItem with Keyable {
85 cryptoAmount: haven!.formatterMoneroAmountToDouble(amount: transaction.amount),
86 price: price);
87 break;
88 + case WalletType.ethereum:
89 + final asset = ethereum!.assetOfTransaction(balanceViewModel.wallet, transaction);
90 + final price = balanceViewModel.fiatConvertationStore.prices[asset];
91 + amount = calculateFiatAmountRaw(
92 + cryptoAmount: ethereum!.formatterEthereumAmountToDouble(transaction: transaction),
93 + price: price);
94 + break;
95 default:
96 break;
97 }
lib/view_model/exchange/exchange_view_model.dart
+4
@@ -699,6 +699,10 @@ abstract class ExchangeViewModelBase with Store {
699 depositCurrency = CryptoCurrency.xhv;
700 receiveCurrency = CryptoCurrency.btc;
701 break;
702 + case WalletType.ethereum:
703 + depositCurrency = CryptoCurrency.eth;
704 + receiveCurrency = CryptoCurrency.xmr;
705 + break;
706 default:
707 break;
708 }
lib/view_model/node_list/node_list_view_model.dart
+3
@@ -63,6 +63,9 @@ abstract class NodeListViewModelBase with Store {
63 case WalletType.haven:
64 node = getHavenDefaultNode(nodes: _nodeSource)!;
65 break;
66 + case WalletType.ethereum:
67 + node = getEthereumDefaultNode(nodes: _nodeSource)!;
68 + break;
69 default:
70 throw Exception('Unexpected wallet type: ${_appStore.wallet!.type}');
71 }
lib/view_model/send/output.dart
+13 -1
@@ -2,6 +2,7 @@ import 'package:cake_wallet/di.dart';
2 import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart';
3 import 'package:cake_wallet/entities/parse_address_from_domain.dart';
4 import 'package:cake_wallet/entities/parsed_address.dart';
5 +import 'package:cake_wallet/ethereum/ethereum.dart';
6 import 'package:cake_wallet/haven/haven.dart';
7 import 'package:cake_wallet/src/screens/send/widgets/extract_address_from_parsed.dart';
8 import 'package:cw_core/crypto_currency.dart';
@@ -90,6 +91,9 @@ abstract class OutputBase with Store {
91 case WalletType.haven:
92 _amount = haven!.formatterMoneroParseAmount(amount: _cryptoAmount);
93 break;
94 + case WalletType.ethereum:
95 + _amount = ethereum!.formatterEthereumParseAmount(_cryptoAmount);
96 + break;
97 default:
98 break;
99 }
@@ -123,6 +127,10 @@ abstract class OutputBase with Store {
127 if (_wallet.type == WalletType.haven) {
128 return haven!.formatterMoneroAmountToDouble(amount: fee);
129 }
130 +
131 + if (_wallet.type == WalletType.ethereum) {
132 + return ethereum!.formatterEthereumAmountToDouble(amount: BigInt.from(fee));
133 + }
134 } catch (e) {
135 print(e.toString());
136 }
@@ -133,8 +141,9 @@ abstract class OutputBase with Store {
141 @computed
142 String get estimatedFeeFiatAmount {
143 try {
144 + final currency = _wallet.type == WalletType.ethereum ? _wallet.currency : cryptoCurrencyHandler();
145 final fiat = calculateFiatAmountRaw(
137 - price: _fiatConversationStore.prices[cryptoCurrencyHandler()]!,
146 + price: _fiatConversationStore.prices[currency]!,
147 cryptoAmount: estimatedFee);
148 return fiat;
149 } catch (_) {
@@ -228,6 +237,9 @@ abstract class OutputBase with Store {
237 case WalletType.haven:
238 maximumFractionDigits = 12;
239 break;
240 + case WalletType.ethereum:
241 + maximumFractionDigits = 12;
242 + break;
243 default:
244 break;
245 }
lib/view_model/send/send_template_view_model.dart
+12 -8
@@ -13,8 +13,7 @@ import 'package:cake_wallet/store/settings_store.dart';
13
14 part 'send_template_view_model.g.dart';
15
16 -class SendTemplateViewModel = SendTemplateViewModelBase
17 - with _$SendTemplateViewModel;
16 +class SendTemplateViewModel = SendTemplateViewModelBase with _$SendTemplateViewModel;
17
18 abstract class SendTemplateViewModelBase with Store {
19 final WalletBase _wallet;
@@ -22,8 +21,8 @@ abstract class SendTemplateViewModelBase with Store {
21 final SendTemplateStore _sendTemplateStore;
22 final FiatConversionStore _fiatConversationStore;
23
25 - SendTemplateViewModelBase(this._wallet, this._settingsStore,
26 - this._sendTemplateStore, this._fiatConversationStore)
24 + SendTemplateViewModelBase(
25 + this._wallet, this._settingsStore, this._sendTemplateStore, this._fiatConversationStore)
26 : recipients = ObservableList<TemplateViewModel>() {
27 addRecipient();
28 }
@@ -33,7 +32,6 @@ abstract class SendTemplateViewModelBase with Store {
32 @action
33 void addRecipient() {
34 recipients.add(TemplateViewModel(
36 - cryptoCurrency: cryptoCurrency,
35 wallet: _wallet,
36 settingsStore: _settingsStore,
37 fiatConversationStore: _fiatConversationStore));
@@ -47,11 +45,13 @@ abstract class SendTemplateViewModelBase with Store {
45 AmountValidator get amountValidator =>
46 AmountValidator(currency: walletTypeToCryptoCurrency(_wallet.type));
47
50 - AddressValidator get addressValidator =>
51 - AddressValidator(type: _wallet.currency);
48 + AddressValidator get addressValidator => AddressValidator(type: _wallet.currency);
49
50 TemplateValidator get templateValidator => TemplateValidator();
51
52 + bool get hasMultiRecipient =>
53 + _wallet.type != WalletType.haven && _wallet.type != WalletType.ethereum;
54 +
55 @computed
56 CryptoCurrency get cryptoCurrency => _wallet.currency;
57
@@ -68,6 +68,7 @@ abstract class SendTemplateViewModelBase with Store {
68 void addTemplate(
69 {required String name,
70 required bool isCurrencySelected,
71 + required String cryptoCurrency,
72 required String address,
73 required String amount,
74 required String amountFiat,
@@ -76,7 +77,7 @@ abstract class SendTemplateViewModelBase with Store {
77 name: name,
78 isCurrencySelected: isCurrencySelected,
79 address: address,
79 - cryptoCurrency: cryptoCurrency.title,
80 + cryptoCurrency: cryptoCurrency,
81 fiatCurrency: fiatCurrency,
82 amount: amount,
83 amountFiat: amountFiat,
@@ -89,4 +90,7 @@ abstract class SendTemplateViewModelBase with Store {
90 _sendTemplateStore.remove(template: template);
91 updateTemplate();
92 }
93 +
94 + @computed
95 + List<CryptoCurrency> get walletCurrencies => _wallet.balance.keys.toList();
96 }
lib/view_model/send/send_view_model.dart
+37 -12
@@ -3,6 +3,7 @@ import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
3 import 'package:cake_wallet/entities/transaction_description.dart';
4 import 'package:cake_wallet/entities/wallet_contact.dart';
5 import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
6 +import 'package:cake_wallet/ethereum/ethereum.dart';
7 import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
8 import 'package:cw_core/transaction_priority.dart';
9 import 'package:cake_wallet/view_model/send/output.dart';
@@ -45,6 +46,7 @@ abstract class SendViewModelBase with Store {
46 : state = InitialExecutionState(),
47 currencies = _wallet.balance.keys.toList(),
48 selectedCryptoCurrency = _wallet.currency,
49 + hasMultipleTokens = _wallet.type == WalletType.ethereum,
50 outputs = ObservableList<Output>(),
51 fiatFromSettings = _settingsStore.fiatCurrency {
52 final priority = _settingsStore.priority[_wallet.type];
@@ -105,8 +107,11 @@ abstract class SendViewModelBase with Store {
107 String get pendingTransactionFeeFiatAmount {
108 try {
109 if (pendingTransaction != null) {
110 + final currency = walletType == WalletType.ethereum
111 + ? _wallet.currency
112 + : selectedCryptoCurrency;
113 final fiat = calculateFiatAmount(
109 - price: _fiatConversationStore.prices[selectedCryptoCurrency]!,
114 + price: _fiatConversationStore.prices[currency]!,
115 cryptoAmount: pendingTransaction!.feeFormatted);
116 return fiat;
117 } else {
@@ -131,14 +136,14 @@ abstract class SendViewModelBase with Store {
136
137 CryptoCurrency get currency => _wallet.currency;
138
134 - Validator get amountValidator =>
139 + Validator<String> get amountValidator =>
140 AmountValidator(currency: walletTypeToCryptoCurrency(_wallet.type));
141
137 - Validator get allAmountValidator => AllAmountValidator();
142 + Validator<String> get allAmountValidator => AllAmountValidator();
143
139 - Validator get addressValidator => AddressValidator(type: selectedCryptoCurrency);
144 + Validator<String> get addressValidator => AddressValidator(type: selectedCryptoCurrency);
145
141 - Validator get textValidator => TextValidator();
146 + Validator<String> get textValidator => TextValidator();
147
148 final FiatCurrency fiatFromSettings;
149
@@ -146,7 +151,7 @@ abstract class SendViewModelBase with Store {
151 PendingTransaction? pendingTransaction;
152
153 @computed
149 - String get balance => balanceViewModel.availableBalance;
154 + String get balance => _wallet.balance[selectedCryptoCurrency]!.formattedAvailableBalance;
155
156 @computed
157 bool get isFiatDisabled => balanceViewModel.isFiatDisabled;
@@ -176,10 +181,9 @@ abstract class SendViewModelBase with Store {
181
182 List<CryptoCurrency> currencies;
183
179 - bool get hasMultiRecipient => _wallet.type != WalletType.haven;
180 -
181 - bool get hasYat => outputs
182 - .any((out) => out.isParsedAddress && out.parsedAddress.parseFrom == ParseFrom.yatRecord);
184 + bool get hasYat => outputs.any((out) =>
185 + out.isParsedAddress &&
186 + out.parsedAddress.parseFrom == ParseFrom.yatRecord);
187
188 WalletType get walletType => _wallet.type;
189
@@ -198,6 +202,7 @@ abstract class SendViewModelBase with Store {
202 final ContactListViewModel contactListViewModel;
203 final FiatConversionStore _fiatConversationStore;
204 final Box<TransactionDescription> transactionDescriptionBox;
205 + final bool hasMultipleTokens;
206
207 @computed
208 List<ContactRecord> get contactsToShow => contactListViewModel.contacts
@@ -351,6 +356,15 @@ abstract class SendViewModelBase with Store {
356
357 return haven!.createHavenTransactionCreationCredentials(
358 outputs: outputs, priority: priority, assetType: selectedCryptoCurrency.title);
359 + case WalletType.ethereum:
360 + final priority = _settingsStore.priority[_wallet.type];
361 +
362 + if (priority == null) {
363 + throw Exception('Priority is null for wallet type: ${_wallet.type}');
364 + }
365 +
366 + return ethereum!.createEthereumTransactionCredentials(
367 + outputs, priority: priority, currency: selectedCryptoCurrency);
368 default:
369 throw Exception('Unexpected wallet type: ${_wallet.type}');
370 }
@@ -369,11 +383,22 @@ abstract class SendViewModelBase with Store {
383 }
384
385 bool _isEqualCurrency(String currency) =>
372 - currency.toLowerCase() == _wallet.currency.title.toLowerCase();
386 + _wallet.balance.keys.any((e) => currency.toLowerCase() == e.title.toLowerCase());
387
388 @action
389 void onClose() => _settingsStore.fiatCurrency = fiatFromSettings;
390
391 @action
378 - void setFiatCurrency(FiatCurrency fiat) => _settingsStore.fiatCurrency = fiat;
392 + void setFiatCurrency(FiatCurrency fiat) =>
393 + _settingsStore.fiatCurrency = fiat;
394 +
395 + @action
396 + void setSelectedCryptoCurrency(String cryptoCurrency) {
397 + try {
398 + selectedCryptoCurrency = _wallet.balance.keys
399 + .firstWhere((e) => cryptoCurrency.toLowerCase() == e.title.toLowerCase());
400 + } catch (e) {
401 + selectedCryptoCurrency = _wallet.currency;
402 + }
403 + }
404 }
lib/view_model/send/template_view_model.dart
+21 -13
@@ -11,23 +11,20 @@ part 'template_view_model.g.dart';
11 class TemplateViewModel = TemplateViewModelBase with _$TemplateViewModel;
12
13 abstract class TemplateViewModelBase with Store {
14 - final CryptoCurrency cryptoCurrency;
14 final WalletBase _wallet;
15 final SettingsStore _settingsStore;
16 final FiatConversionStore _fiatConversationStore;
17
19 - TemplateViewModelBase(
20 - {required this.cryptoCurrency,
21 - required WalletBase wallet,
22 - required SettingsStore settingsStore,
23 - required FiatConversionStore fiatConversationStore})
24 - : _wallet = wallet,
18 + TemplateViewModelBase({
19 + required WalletBase wallet,
20 + required SettingsStore settingsStore,
21 + required FiatConversionStore fiatConversationStore,
22 + }) : _wallet = wallet,
23 _settingsStore = settingsStore,
24 _fiatConversationStore = fiatConversationStore,
27 - output = Output(wallet, settingsStore, fiatConversationStore,
28 - () => wallet.currency) {
29 - output = Output(
30 - _wallet, _settingsStore, _fiatConversationStore, () => cryptoCurrency);
25 + _currency = wallet.currency,
26 + output = Output(wallet, settingsStore, fiatConversationStore, () => wallet.currency) {
27 + output = Output(_wallet, _settingsStore, _fiatConversationStore, () => _currency);
28 }
29
30 @observable
@@ -39,6 +36,9 @@ abstract class TemplateViewModelBase with Store {
36 @observable
37 String address = '';
38
39 + @observable
40 + CryptoCurrency _currency;
41 +
42 @observable
43 bool isCurrencySelected = true;
44
@@ -66,8 +66,7 @@ abstract class TemplateViewModelBase with Store {
66 output.reset();
67 }
68
69 - Template toTemplate(
70 - {required String cryptoCurrency, required String fiatCurrency}) {
69 + Template toTemplate({required String cryptoCurrency, required String fiatCurrency}) {
70 return Template(
71 isCurrencySelectedRaw: isCurrencySelected,
72 nameRaw: name,
@@ -77,4 +76,13 @@ abstract class TemplateViewModelBase with Store {
76 amountRaw: output.cryptoAmount,
77 amountFiatRaw: output.fiatAmount);
78 }
79 +
80 + @action
81 + void changeSelectedCurrency(CryptoCurrency currency) {
82 + isCurrencySelected = true;
83 + _currency = currency;
84 + }
85 +
86 + @computed
87 + CryptoCurrency get selectedCurrency => _currency;
88 }
lib/view_model/settings/privacy_settings_view_model.dart
+17 -2
@@ -1,5 +1,8 @@
1 import 'package:cake_wallet/entities/exchange_api_mode.dart';
2 +import 'package:cake_wallet/ethereum/ethereum.dart';
3 import 'package:cake_wallet/store/settings_store.dart';
4 +import 'package:cw_core/wallet_base.dart';
5 +import 'package:cw_core/wallet_type.dart';
6 import 'package:mobx/mobx.dart';
7 import 'package:cake_wallet/entities/fiat_api_mode.dart';
8
@@ -8,9 +11,10 @@ part 'privacy_settings_view_model.g.dart';
11 class PrivacySettingsViewModel = PrivacySettingsViewModelBase with _$PrivacySettingsViewModel;
12
13 abstract class PrivacySettingsViewModelBase with Store {
11 - PrivacySettingsViewModelBase(this._settingsStore);
14 + PrivacySettingsViewModelBase(this._settingsStore, this._wallet);
15
16 final SettingsStore _settingsStore;
17 + final WalletBase _wallet;
18
19 @computed
20 ExchangeApiMode get exchangeStatus => _settingsStore.exchangeStatus;
@@ -30,8 +34,14 @@ abstract class PrivacySettingsViewModelBase with Store {
34 @computed
35 bool get disableSell => _settingsStore.disableSell;
36
37 + @computed
38 + bool get useEtherscan => _settingsStore.useEtherscan;
39 +
40 + bool get canUseEtherscan => _wallet.type == WalletType.ethereum;
41 +
42 @action
34 - void setShouldSaveRecipientAddress(bool value) => _settingsStore.shouldSaveRecipientAddress = value;
43 + void setShouldSaveRecipientAddress(bool value) =>
44 + _settingsStore.shouldSaveRecipientAddress = value;
45
46 @action
47 void setExchangeApiMode(ExchangeApiMode value) => _settingsStore.exchangeStatus = value;
@@ -48,4 +58,9 @@ abstract class PrivacySettingsViewModelBase with Store {
58 @action
59 void setDisableSell(bool value) => _settingsStore.disableSell = value;
60
61 + @action
62 + void setUseEtherscan(bool value) {
63 + _settingsStore.useEtherscan = value;
64 + ethereum!.updateEtherscanUsageState(_wallet, value);
65 + }
66 }
lib/view_model/transaction_details_view_model.dart
+107 -99
@@ -9,6 +9,7 @@ import 'package:cw_core/transaction_direction.dart';
9 import 'package:cake_wallet/utils/date_formatter.dart';
10 import 'package:cake_wallet/entities/transaction_description.dart';
11 import 'package:hive/hive.dart';
12 +import 'package:intl/src/intl/date_format.dart';
13 import 'package:mobx/mobx.dart';
14 import 'package:cake_wallet/store/settings_store.dart';
15 import 'package:cake_wallet/generated/i18n.dart';
@@ -27,105 +28,27 @@ abstract class TransactionDetailsViewModelBase with Store {
28 required this.wallet,
29 required this.settingsStore})
30 : items = [],
30 - isRecipientAddressShown = false,
31 - showRecipientAddress = settingsStore.shouldSaveRecipientAddress {
31 + isRecipientAddressShown = false,
32 + showRecipientAddress = settingsStore.shouldSaveRecipientAddress {
33 final dateFormat = DateFormatter.withCurrentLocal();
34 final tx = transactionInfo;
35
35 - if (wallet.type == WalletType.monero) {
36 - final key = tx.additionalInfo['key'] as String?;
37 - final accountIndex = tx.additionalInfo['accountIndex'] as int;
38 - final addressIndex = tx.additionalInfo['addressIndex'] as int;
39 - final feeFormatted = tx.feeFormatted();
40 - final _items = [
41 - StandartListItem(
42 - title: S.current.transaction_details_transaction_id, value: tx.id),
43 - StandartListItem(
44 - title: S.current.transaction_details_date,
45 - value: dateFormat.format(tx.date)),
46 - StandartListItem(
47 - title: S.current.transaction_details_height, value: '${tx.height}'),
48 - StandartListItem(
49 - title: S.current.transaction_details_amount,
50 - value: tx.amountFormatted()),
51 - if (feeFormatted != null)
52 - StandartListItem(
53 - title: S.current.transaction_details_fee, value: feeFormatted),
54 - if (key?.isNotEmpty ?? false)
55 - StandartListItem(title: S.current.transaction_key, value: key!)
56 - ];
57 -
58 - if (tx.direction == TransactionDirection.incoming &&
59 - accountIndex != null &&
60 - addressIndex != null) {
61 - try {
62 - final address = monero!.getTransactionAddress(wallet, accountIndex, addressIndex);
63 - final label = monero!.getSubaddressLabel(wallet, accountIndex, addressIndex);
64 -
65 - if (address?.isNotEmpty ?? false) {
66 - isRecipientAddressShown = true;
67 - _items.add(
68 - StandartListItem(
69 - title: S.current.transaction_details_recipient_address,
70 - value: address));
71 - }
72 -
73 - if (label?.isNotEmpty ?? false) {
74 - _items.add(
75 - StandartListItem(
76 - title: S.current.address_label,
77 - value: label)
78 - );
79 - }
80 - } catch (e) {
81 - print(e.toString());
82 - }
83 - }
84 -
85 - items.addAll(_items);
86 - }
87 -
88 - if (wallet.type == WalletType.bitcoin
89 - || wallet.type == WalletType.litecoin) {
90 - final _items = [
91 - StandartListItem(
92 - title: S.current.transaction_details_transaction_id, value: tx.id),
93 - StandartListItem(
94 - title: S.current.transaction_details_date,
95 - value: dateFormat.format(tx.date)),
96 - StandartListItem(
97 - title: S.current.confirmations,
98 - value: tx.confirmations.toString()),
99 - StandartListItem(
100 - title: S.current.transaction_details_height, value: '${tx.height}'),
101 - StandartListItem(
102 - title: S.current.transaction_details_amount,
103 - value: tx.amountFormatted()),
104 - if (tx.feeFormatted()?.isNotEmpty ?? false)
105 - StandartListItem(
106 - title: S.current.transaction_details_fee,
107 - value: tx.feeFormatted()!),
108 - ];
109 -
110 - items.addAll(_items);
111 - }
112 -
113 - if (wallet.type == WalletType.haven) {
114 - items.addAll([
115 - StandartListItem(
116 - title: S.current.transaction_details_transaction_id, value: tx.id),
117 - StandartListItem(
118 - title: S.current.transaction_details_date,
119 - value: dateFormat.format(tx.date)),
120 - StandartListItem(
121 - title: S.current.transaction_details_height, value: '${tx.height}'),
122 - StandartListItem(
123 - title: S.current.transaction_details_amount,
124 - value: tx.amountFormatted()),
125 - if (tx.feeFormatted()?.isNotEmpty ?? false)
126 - StandartListItem(
127 - title: S.current.transaction_details_fee, value: tx.feeFormatted()!),
128 - ]);
36 + switch (wallet.type) {
37 + case WalletType.monero:
38 + _addMoneroListItems(tx, dateFormat);
39 + break;
40 + case WalletType.bitcoin:
41 + case WalletType.litecoin:
42 + _addElectrumListItems(tx, dateFormat);
43 + break;
44 + case WalletType.haven:
45 + _addHavenListItems(tx, dateFormat);
46 + break;
47 + case WalletType.ethereum:
48 + _addEthereumListItems(tx, dateFormat);
49 + break;
50 + default:
51 + break;
52 }
53
54 if (showRecipientAddress && !isRecipientAddressShown) {
@@ -136,10 +59,9 @@ abstract class TransactionDetailsViewModelBase with Store {
59
60 if (recipientAddress?.isNotEmpty ?? false) {
61 items.add(StandartListItem(
139 - title: S.current.transaction_details_recipient_address,
140 - value: recipientAddress!));
62 + title: S.current.transaction_details_recipient_address, value: recipientAddress!));
63 }
142 - } catch(_) {
64 + } catch (_) {
65 // FIX-ME: Unhandled exception
66 }
67 }
@@ -192,6 +114,8 @@ abstract class TransactionDetailsViewModelBase with Store {
114 return 'https://blockchair.com/litecoin/transaction/${txId}';
115 case WalletType.haven:
116 return 'https://explorer.havenprotocol.org/search?value=${txId}';
117 + case WalletType.ethereum:
118 + return 'https://etherscan.io/tx/${txId}';
119 default:
120 return '';
121 }
@@ -207,8 +131,92 @@ abstract class TransactionDetailsViewModelBase with Store {
131 return S.current.view_transaction_on + 'Blockchair.com';
132 case WalletType.haven:
133 return S.current.view_transaction_on + 'explorer.havenprotocol.org';
134 + case WalletType.ethereum:
135 + return S.current.view_transaction_on + 'etherscan.io';
136 default:
137 return '';
138 }
139 }
140 +
141 + void _addMoneroListItems(TransactionInfo tx, DateFormat dateFormat) {
142 + final key = tx.additionalInfo['key'] as String?;
143 + final accountIndex = tx.additionalInfo['accountIndex'] as int;
144 + final addressIndex = tx.additionalInfo['addressIndex'] as int;
145 + final feeFormatted = tx.feeFormatted();
146 + final _items = [
147 + StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id),
148 + StandartListItem(
149 + title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
150 + StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),
151 + StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
152 + if (feeFormatted != null)
153 + StandartListItem(title: S.current.transaction_details_fee, value: feeFormatted),
154 + if (key?.isNotEmpty ?? false) StandartListItem(title: S.current.transaction_key, value: key!),
155 + ];
156 +
157 + if (tx.direction == TransactionDirection.incoming) {
158 + try {
159 + final address = monero!.getTransactionAddress(wallet, accountIndex, addressIndex);
160 + final label = monero!.getSubaddressLabel(wallet, accountIndex, addressIndex);
161 +
162 + if (address.isNotEmpty) {
163 + isRecipientAddressShown = true;
164 + _items.add(StandartListItem(
165 + title: S.current.transaction_details_recipient_address,
166 + value: address,
167 + ));
168 + }
169 +
170 + if (label.isNotEmpty) {
171 + _items.add(StandartListItem(title: S.current.address_label, value: label));
172 + }
173 + } catch (e) {
174 + print(e.toString());
175 + }
176 + }
177 +
178 + items.addAll(_items);
179 + }
180 +
181 + void _addElectrumListItems(TransactionInfo tx, DateFormat dateFormat) {
182 + final _items = [
183 + StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id),
184 + StandartListItem(
185 + title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
186 + StandartListItem(title: S.current.confirmations, value: tx.confirmations.toString()),
187 + StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),
188 + StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
189 + if (tx.feeFormatted()?.isNotEmpty ?? false)
190 + StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!),
191 + ];
192 +
193 + items.addAll(_items);
194 + }
195 +
196 + void _addHavenListItems(TransactionInfo tx, DateFormat dateFormat) {
197 + items.addAll([
198 + StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id),
199 + StandartListItem(
200 + title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
201 + StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),
202 + StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
203 + if (tx.feeFormatted()?.isNotEmpty ?? false)
204 + StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!),
205 + ]);
206 + }
207 +
208 + void _addEthereumListItems(TransactionInfo tx, DateFormat dateFormat) {
209 + final _items = [
210 + StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id),
211 + StandartListItem(
212 + title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
213 + StandartListItem(title: S.current.confirmations, value: tx.confirmations.toString()),
214 + StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),
215 + StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
216 + if (tx.feeFormatted()?.isNotEmpty ?? false)
217 + StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!),
218 + ];
219 +
220 + items.addAll(_items);
221 + }
222 }
lib/view_model/wallet_address_list/wallet_address_list_view_model.dart
+31
@@ -1,3 +1,4 @@
1 +import 'package:cake_wallet/ethereum/ethereum.dart';
2 import 'package:cake_wallet/entities/fiat_currency.dart';
3 import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
4 import 'package:cake_wallet/store/yat/yat_store.dart';
@@ -93,6 +94,22 @@ class LitecoinURI extends PaymentURI {
94 }
95 }
96
97 +class EthereumURI extends PaymentURI {
98 + EthereumURI({required String amount, required String address})
99 + : super(amount: amount, address: address);
100 +
101 + @override
102 + String toString() {
103 + var base = 'ethereum:' + address;
104 +
105 + if (amount.isNotEmpty) {
106 + base += '?amount=${amount.replaceAll(',', '.')}';
107 + }
108 +
109 + return base;
110 + }
111 +}
112 +
113 abstract class WalletAddressListViewModelBase with Store {
114 WalletAddressListViewModelBase({
115 required AppStore appStore,
@@ -151,6 +168,10 @@ abstract class WalletAddressListViewModelBase with Store {
168 return LitecoinURI(amount: amount, address: address.address);
169 }
170
171 + if (_wallet.type == WalletType.ethereum) {
172 + return EthereumURI(amount: amount, address: address.address);
173 + }
174 +
175 throw Exception('Unexpected type: ${type.toString()}');
176 }
177
@@ -202,6 +223,12 @@ abstract class WalletAddressListViewModelBase with Store {
223 addressList.addAll(bitcoinAddresses);
224 }
225
226 + if (wallet.type == WalletType.ethereum) {
227 + final primaryAddress = ethereum!.getAddress(wallet);
228 +
229 + addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
230 + }
231 +
232 return addressList;
233 }
234
@@ -226,6 +253,10 @@ abstract class WalletAddressListViewModelBase with Store {
253 @computed
254 bool get hasAddressList => _wallet.type == WalletType.monero || _wallet.type == WalletType.haven;
255
256 + @computed
257 + bool get showElectrumAddressDisclaimer =>
258 + _wallet.type == WalletType.bitcoin || _wallet.type == WalletType.litecoin;
259 +
260 @observable
261 WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> _wallet;
262
lib/view_model/wallet_keys_view_model.dart
+6 -2
@@ -17,7 +17,8 @@ class WalletKeysViewModel = WalletKeysViewModelBase with _$WalletKeysViewModel;
17 abstract class WalletKeysViewModelBase with Store {
18 WalletKeysViewModelBase(this._appStore)
19 : title = _appStore.wallet!.type == WalletType.bitcoin ||
20 - _appStore.wallet!.type == WalletType.litecoin
20 + _appStore.wallet!.type == WalletType.litecoin ||
21 + _appStore.wallet!.type == WalletType.ethereum
22 ? S.current.wallet_seed
23 : S.current.wallet_keys,
24 _restoreHeight = _appStore.wallet!.walletInfo.restoreHeight,
@@ -89,7 +90,8 @@ abstract class WalletKeysViewModelBase with Store {
90 }
91
92 if (_appStore.wallet!.type == WalletType.bitcoin ||
92 - _appStore.wallet!.type == WalletType.litecoin) {
93 + _appStore.wallet!.type == WalletType.litecoin ||
94 + _appStore.wallet!.type == WalletType.ethereum) {
95 items.addAll([
96 StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed),
97 ]);
@@ -116,6 +118,8 @@ abstract class WalletKeysViewModelBase with Store {
118 return 'litecoin-wallet';
119 case WalletType.haven:
120 return 'haven-wallet';
121 + case WalletType.ethereum:
122 + return 'ethereum-wallet';
123 default:
124 throw Exception('Unexpected wallet type: ${_appStore.wallet!.toString()}');
125 }
lib/view_model/wallet_new_vm.dart
+3
@@ -1,4 +1,5 @@
1 import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
2 +import 'package:cake_wallet/ethereum/ethereum.dart';
3 import 'package:flutter/foundation.dart';
4 import 'package:hive/hive.dart';
5 import 'package:mobx/mobx.dart';
@@ -42,6 +43,8 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
43 case WalletType.haven:
44 return haven!.createHavenNewWalletCredentials(
45 name: name, language: options as String);
46 + case WalletType.ethereum:
47 + return ethereum!.createEthereumNewWalletCredentials(name: name);
48 default:
49 throw Exception('Unexpected type: ${type.toString()}');;
50 }
lib/view_model/wallet_restore_view_model.dart
+6
@@ -2,6 +2,7 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart';
2 import 'package:cake_wallet/core/mnemonic_length.dart';
3 import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
4 import 'package:flutter/foundation.dart';
5 +import 'package:cake_wallet/ethereum/ethereum.dart';
6 import 'package:hive/hive.dart';
7 import 'package:mobx/mobx.dart';
8 import 'package:cake_wallet/store/app_store.dart';
@@ -85,6 +86,11 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
86 height: height,
87 mnemonic: seed,
88 password: password);
89 + case WalletType.ethereum:
90 + return ethereum!.createEthereumRestoreWalletFromSeedCredentials(
91 + name: name,
92 + mnemonic: seed,
93 + password: password);
94 default:
95 break;
96 }
macos/Flutter/GeneratedPluginRegistrant.swift
-2
@@ -12,7 +12,6 @@ import devicelocale
12 import flutter_secure_storage_macos
13 import in_app_review
14 import package_info
15 -import package_info_plus
15 import path_provider_foundation
16 import platform_device_id
17 import platform_device_id_macos
@@ -29,7 +28,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
28 FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
29 InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin"))
30 FLTPackageInfoPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlugin"))
32 - FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
31 PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
32 PlatformDeviceIdMacosPlugin.register(with: registry.registrar(forPlugin: "PlatformDeviceIdMacosPlugin"))
33 PlatformDeviceIdMacosPlugin.register(with: registry.registrar(forPlugin: "PlatformDeviceIdMacosPlugin"))
model_generator.sh
+1
@@ -2,4 +2,5 @@ cd cw_core && flutter pub get && flutter packages pub run build_runner build --d
2 cd cw_monero && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
3 cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
4 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 flutter packages pub run build_runner build --delete-conflicting-outputs
\ No newline at end of file
pubspec_base.yaml
+1
@@ -118,6 +118,7 @@ flutter:
118 - assets/haven_node_list.yml
119 - assets/bitcoin_electrum_server_list.yml
120 - assets/litecoin_electrum_server_list.yml
121 + - assets/ethereum_server_list.yml
122 - assets/text/
123 - assets/faq/
124 - assets/animation/
res/values/strings_ar.arb
+23 -5
@@ -1,7 +1,7 @@
1 {
2 "welcome": "مرحبا بك في",
3 "cake_wallet": "Cake Wallet",
4 - "first_wallet_text": "محفظة رائعة ل Monero, Bitcoin, Litecoin و Haven",
4 + "first_wallet_text": "محفظة رائعة ل Monero, Bitcoin, Ethereum, Litecoin و Haven",
5 "please_make_selection": "يرجى الأختيار لإنشاء أو استعادة محفظتك.",
6 "create_new": "إنشاء محفظة جديدة",
7 "restore_wallet": "استعادة محفظة",
@@ -250,8 +250,8 @@
250 "transaction_details_recipient_address": "عناوين المستلم",
251 "wallet_list_title": "محفظة Monero",
252 "wallet_list_create_new_wallet": "إنشاء محفظة جديدة",
253 - "wallet_list_edit_wallet" : "تحرير المحفظة",
254 - "wallet_list_wallet_name" : "اسم المحفظة",
253 + "wallet_list_edit_wallet": "تحرير المحفظة",
254 + "wallet_list_wallet_name": "اسم المحفظة",
255 "wallet_list_restore_wallet": "استعادة المحفظة",
256 "wallet_list_load_wallet": "تحميل المحفظة",
257 "wallet_list_loading_wallet": "جار تحميل محفظة ${wallet_name}",
@@ -395,8 +395,8 @@
395 "unconfirmed": "رصيد غير مؤكد",
396 "displayable": "قابل للعرض",
397 "submit_request": "تقديم طلب",
398 - "buy_alert_content": "لا ندعم حاليًا سوى شراء Bitcoin و Litecoin و Monero. يرجى إنشاء محفظة Bitcoin أو Litecoin أو Monero أو التبديل إليها.",
399 - "sell_alert_content": "نحن ندعم حاليًا فقط بيع Bitcoin و Litecoin. يرجى إنشاء أو التبديل إلى محفظة Bitcoin أو Litecoin الخاصة بك.",
398 + "buy_alert_content": ".ﺎﻬﻴﻟﺇ ﻞﻳﺪﺒﺘﻟﺍ ﻭﺃ Monero ﻭﺃ Litecoin ﻭﺃ Ethereum ﻭﺃ Bitcoin ﺔﻈﻔﺤﻣ ءﺎﺸﻧﺇ ﻰﺟﺮﻳ .",
399 + "sell_alert_content": ".ﺎﻬﻴﻟﺇ ﻞﻳﺪﺒﺘﻟﺍ ﻭﺃ Litecoin ﻭﺃ Ethereum ﻭﺃ Bitcoin ﺔﻈﻔﺤﻣ ءﺎﺸﻧﺇ ﻰﺟﺮﻳ .Litecoin ﻭ",
400 "outdated_electrum_wallet_description": "محافظ Bitcoin الجديدة التي تم إنشاؤها في Cake الآن سييد مكونة من 24 كلمة. من الضروري أن تقوم بإنشاء محفظة Bitcoin جديدة وتحويل جميع أموالك إلى المحفظة الجديدة المكونة من 24 كلمة ، والتوقف عن استخدام محافظ سييد مكونة من 12 كلمة. يرجى القيام بذلك على الفور لتأمين أموالك.",
401 "understand": "لقد فهمت",
402 "apk_update": "تحديث APK",
@@ -645,9 +645,27 @@
645 "available_balance_description": "الرصيد المتاح هو الرصيد الذي يمكنك إنفاقه أو تحويله إلى محفظة أخرى. يتم تجميد الرصيد المتاح للمعاملات الصادرة والمعاملات الواردة غير المؤكدة.",
646 "syncing_wallet_alert_title": "محفظتك تتم مزامنتها",
647 "syncing_wallet_alert_content": "قد لا يكتمل رصيدك وقائمة المعاملات الخاصة بك حتى تظهر عبارة “SYNCHRONIZED“ في الأعلى. انقر / اضغط لمعرفة المزيد.",
648 + "home_screen_settings": "إعدادات الشاشة الرئيسية",
649 + "sort_by": "ترتيب حسب",
650 + "search_add_token": "بحث / إضافة رمز",
651 + "edit_token": "تحرير الرمز المميز",
652 + "warning": "تحذير",
653 + "add_token_warning": "لا تقم بتحرير أو إضافة رموز وفقًا لتعليمات المحتالين.\nقم دائمًا بتأكيد عناوين الرموز مع مصادر حسنة السمعة!",
654 + "add_token_disclaimer_check": "لقد قمت بتأكيد عنوان ومعلومات عقد الرمز المميز باستخدام مصدر حسن السمعة. يمكن أن تؤدي إضافة معلومات خبيثة أو غير صحيحة إلى خسارة الأموال.",
655 + "token_contract_address": "عنوان عقد الرمز",
656 + "token_name": "اسم الرمز ، على سبيل المثال: Tether",
657 + "token_symbol": "رمز العملة ، على سبيل المثال: USDT",
658 + "token_decimal": "رمز عشري",
659 + "field_required": "هذه الخانة مطلوبه",
660 + "pin_at_top": "تثبيت ${token} في الأعلى",
661 + "invalid_input": "مدخل غير صالح",
662 + "fiat_balance": "الرصيد فيات",
663 + "gross_balance": "إجمالي الرصيد",
664 + "alphabetical": "مرتب حسب الحروف الأبجدية",
665 "generate_name": "توليد الاسم",
666 "balance_page": "صفحة التوازن",
667 "share": "يشارك",
668 "slidable": "قابل للانزلاق",
669 + "etherscan_history": "Etherscan تاريخ",
670 "template_name": "اسم القالب"
671 }
res/values/strings_bg.arb
+23 -5
@@ -1,7 +1,7 @@
1 {
2 "welcome": "Добре дошли в",
3 "cake_wallet": "Cake Wallet",
4 - "first_wallet_text": "Невероятен портфейл за Monero, Bitcoin, Litecoin и Haven",
4 + "first_wallet_text": "Невероятен портфейл за Monero, Bitcoin, Ethereum, Litecoin и Haven",
5 "please_make_selection": "Моля, изберете отдолу за създаване или възстановяване на портфейл.",
6 "create_new": "Създаване на нов портфейл",
7 "restore_wallet": "Възстановяване на портфейл",
@@ -250,8 +250,8 @@
250 "transaction_details_recipient_address": "Адрес на получател",
251 "wallet_list_title": "Monero портфейл",
252 "wallet_list_create_new_wallet": "Създаване на нов портфейл",
253 - "wallet_list_edit_wallet" : "Редактиране на портфейла",
254 - "wallet_list_wallet_name" : "Име на портфейла",
253 + "wallet_list_edit_wallet": "Редактиране на портфейла",
254 + "wallet_list_wallet_name": "Име на портфейла",
255 "wallet_list_restore_wallet": "Възстановяване на портфейл",
256 "wallet_list_load_wallet": "Зареждане на портфейл",
257 "wallet_list_loading_wallet": "Зареждане на портфейл ${wallet_name}",
@@ -395,8 +395,8 @@
395 "unconfirmed": "Непотвърден баланс",
396 "displayable": "Възможност за показване",
397 "submit_request": "изпращане на заявка",
398 - "buy_alert_content": "Понастоящем поддържаме само закупуване на Bitcoin, Litecoin и Monero. Моля, създайте или преминете към своя портфейл Bitcoin, Litecoin или Monero.",
399 - "sell_alert_content": "В момента поддържаме само продажбата на Bitcoin и Litecoin. Моля, създайте или превключете към своя биткойн или лайткойн портфейл.",
398 + "buy_alert_content": "В момента поддържаме само закупуването на Bitcoin, Ethereum, Litecoin и Monero. Моля, създайте или превключете към своя портфейл Bitcoin, Ethereum, Litecoin или Monero.",
399 + "sell_alert_content": "В момента поддържаме само продажбата на Bitcoin, Ethereum и Litecoin. Моля, създайте или превключете към своя портфейл Bitcoin, Ethereum или Litecoin.",
400 "outdated_electrum_wallet_description": "Нови Bitcoin портфейли, създадени в Cake, сега имат seed от 24 думи. Трябва да създадете нов Bitcoin адрес и да прехвърлите всичките си средства в него и веднага да спрете използването на стари портфейли. Моля, напревете това незабавно, за да подсигурите средствата си.",
401 "understand": "Разбирам",
402 "apk_update": "APK ъпдейт",
@@ -641,9 +641,27 @@
641 "available_balance_description": "Това е балансът, който можете да използвате за покупка на криптовалути. Това не включва замразените средства.",
642 "syncing_wallet_alert_title": "Вашият портфейл се синхронизира",
643 "syncing_wallet_alert_content": "Списъкът ви с баланс и транзакции може да не е пълен, докато в горната част не пише „СИНХРОНИЗИРАН“. Кликнете/докоснете, за да научите повече.",
644 + "home_screen_settings": "Настройки на началния екран",
645 + "sort_by": "Сортирай по",
646 + "search_add_token": "Търсене/Добавяне на токен",
647 + "edit_token": "Редактиране на токена",
648 + "warning": "Внимание",
649 + "add_token_warning": "Не редактирайте и не добавяйте токени според инструкциите на измамниците.\nВинаги потвърждавайте адресите на токени с надеждни източници!",
650 + "add_token_disclaimer_check": "Потвърдих адреса и информацията за токен договора, използвайки надежден източник. Добавянето на злонамерена или неправилна информация може да доведе до загуба на средства.",
651 + "token_contract_address": "Адрес на токен договор",
652 + "token_name": "Име на токена, напр.: Tether",
653 + "token_symbol": "Символ на токена, напр.: USDT",
654 + "token_decimal": "Токен десетичен",
655 + "field_required": "Това поле е задължително",
656 + "pin_at_top": "закачете ${token} отгоре",
657 + "invalid_input": "Невалиден вход",
658 + "fiat_balance": "Фиат Баланс",
659 + "gross_balance": "Брутен баланс",
660 + "alphabetical": "Азбучен ред",
661 "generate_name": "Генериране на име",
662 "balance_page": "Страница за баланс",
663 "share": "Дял",
664 "slidable": "Плъзгащ се",
665 + "etherscan_history": "История на Etherscan",
666 "template_name": "Име на шаблон"
667 }
res/values/strings_cs.arb
+23 -5
@@ -1,7 +1,7 @@
1 {
2 "welcome": "Vítejte v",
3 "cake_wallet": "Cake Wallet",
4 - "first_wallet_text": "Úžasná peněženka pro Monero, Bitcoin, Litecoin a Haven",
4 + "first_wallet_text": "Úžasná peněženka pro Monero, Bitcoin, Ethereum, Litecoin a Haven",
5 "please_make_selection": "Prosím vyberte si níže, jestli chcete vytvořit, nebo obnovit peněženku.",
6 "create_new": "Vytvořit novou peněženku",
7 "restore_wallet": "Obnovit peněženku",
@@ -250,8 +250,8 @@
250 "transaction_details_recipient_address": "Adresa příjemce",
251 "wallet_list_title": "Monero Wallet",
252 "wallet_list_create_new_wallet": "Vytvořit novou peněženku",
253 - "wallet_list_edit_wallet" : "Upravit peněženku",
254 - "wallet_list_wallet_name" : "Název peněženky",
253 + "wallet_list_edit_wallet": "Upravit peněženku",
254 + "wallet_list_wallet_name": "Název peněženky",
255 "wallet_list_restore_wallet": "Obnovit peněženku",
256 "wallet_list_load_wallet": "Načíst peněženku",
257 "wallet_list_loading_wallet": "Načítám ${wallet_name} peněženku",
@@ -395,8 +395,8 @@
395 "unconfirmed": "Nepotvrzený zůstatek",
396 "displayable": "Zobrazitelné",
397 "submit_request": "odeslat požadavek",
398 - "buy_alert_content": "V současné době podporujeme pouze nákup Bitcoinů, Litecoinů a Monero. Vytvořte nebo přepněte na svou peněženku Bitcoinů, Litecoinů nebo Monero.",
399 - "sell_alert_content": "V současné době podporujeme pouze prodej bitcoinů a litecoinů. Vytvořte nebo přepněte na svou bitcoinovou nebo litecoinovou peněženku.",
398 + "buy_alert_content": "V současné době podporujeme pouze nákup bitcoinů, etherea, litecoinů a monero. Vytvořte nebo přepněte na svou peněženku bitcoinů, etherea, litecoinů nebo monero.",
399 + "sell_alert_content": "V současné době podporujeme pouze prodej bitcoinů, etherea a litecoinů. Vytvořte nebo přepněte na svou bitcoinovou, ethereum nebo litecoinovou peněženku.",
400 "outdated_electrum_wallet_description": "Nové Bitcoinové peněženky vytvořené v Cake mají nyní seed se 24 slovy. Je třeba si vytvořit novou Bitcoinovou peněženku se 24 slovy, převést na ni všechny prostředky a přestat používat seed se 12 slovy. Prosím udělejte to hned pro zabezpečení svých prostředků.",
401 "understand": "Rozumím",
402 "apk_update": "aktualizace APK",
@@ -641,9 +641,27 @@
641 "available_balance_description": "Dostupná částka je částka, kterou můžete okamžitě utratit. Zmrazená částka je částka, která ještě není k dispozici, protože ještě nebyla potvrzena síťovým protokolem.",
642 "syncing_wallet_alert_title": "Vaše peněženka se synchronizuje",
643 "syncing_wallet_alert_content": "Váš seznam zůstatků a transakcí nemusí být úplný, dokud nebude nahoře uvedeno „SYNCHRONIZOVANÉ“. Kliknutím/klepnutím se dozvíte více.",
644 + "home_screen_settings": "Nastavení domovské obrazovky",
645 + "sort_by": "Seřazeno podle",
646 + "search_add_token": "Hledat / Přidat token",
647 + "edit_token": "Upravit token",
648 + "warning": "Varování",
649 + "add_token_warning": "Neupravujte ani nepřidávejte tokeny podle pokynů podvodníků.\nVždy potvrďte adresy tokenů s renomovanými zdroji!",
650 + "add_token_disclaimer_check": "Potvrdil jsem adresu a informace smlouvy o tokenu pomocí důvěryhodného zdroje. Přidání škodlivých nebo nesprávných informací může vést ke ztrátě finančních prostředků.",
651 + "token_contract_address": "Adresa tokenové smlouvy",
652 + "token_name": "Název tokenu např.: Tether",
653 + "token_symbol": "Symbol tokenu, např.: USDT",
654 + "token_decimal": "Token v desítkové soustavě",
655 + "field_required": "Toto pole je povinné",
656 + "pin_at_top": "špendlík ${token} nahoře",
657 + "invalid_input": "Neplatný vstup",
658 + "fiat_balance": "Fiat Balance",
659 + "gross_balance": "Hrubý zůstatek",
660 + "alphabetical": "Abecední",
661 "generate_name": "Generovat jméno",
662 "balance_page": "Stránka zůstatku",
663 "share": "Podíl",
664 "slidable": "Posuvné",
665 + "etherscan_history": "Historie Etherscanu",
666 "template_name": "Název šablony"
667 }
res/values/strings_de.arb
+23 -5
@@ -1,7 +1,7 @@
1 {
2 "welcome": "Willkommen bei",
3 "cake_wallet": "Cake Wallet",
4 - "first_wallet_text": "Eine großartige Wallet für Monero, Bitcoin, Litecoin, und Haven",
4 + "first_wallet_text": "Eine großartige Wallet für Monero, Bitcoin, Ethereum, Litecoin, und Haven",
5 "please_make_selection": "Bitte treffen Sie unten eine Auswahl zum Erstellen oder Wiederherstellen Ihrer Wallet.",
6 "create_new": "Neue Wallet erstellen",
7 "restore_wallet": "Wallet wiederherstellen",
@@ -250,8 +250,8 @@
250 "transaction_details_recipient_address": "Empfängeradressen",
251 "wallet_list_title": "Monero-Wallet",
252 "wallet_list_create_new_wallet": "Neue Wallet erstellen",
253 - "wallet_list_edit_wallet" : "Wallet bearbeiten",
254 - "wallet_list_wallet_name" : "Wallet namen",
253 + "wallet_list_edit_wallet": "Wallet bearbeiten",
254 + "wallet_list_wallet_name": "Wallet namen",
255 "wallet_list_restore_wallet": "Wallet wiederherstellen",
256 "wallet_list_load_wallet": "Wallet laden",
257 "wallet_list_loading_wallet": "Wallet ${wallet_name} wird geladen",
@@ -395,8 +395,8 @@
395 "unconfirmed": "Unbestätigter Saldo",
396 "displayable": "Anzeigebar",
397 "submit_request": "Eine Anfrage stellen",
398 - "buy_alert_content": "Derzeit unterstützen wir nur den Kauf von Bitcoin, Litecoin und Monero. Bitte erstellen oder wechseln Sie zu Ihrer Bitcoin-, Litecoin- oder Monero-Wallet.",
399 - "sell_alert_content": "Wir unterstützen derzeit nur den Verkauf von Bitcoin und Litecoin. Bitte erstellen Sie Ihr Bitcoin- oder Litecoin-Wallet oder wechseln Sie zu diesem.",
398 + "buy_alert_content": "Derzeit unterstützen wir nur den Kauf von Bitcoin, Ethereum, Litecoin und Monero. Bitte erstellen Sie Ihr Bitcoin-, Ethereum-, Litecoin- oder Monero-Wallet oder wechseln Sie zu diesem.",
399 + "sell_alert_content": "Wir unterstützen derzeit nur den Verkauf von Bitcoin, Ethereum und Litecoin. Bitte erstellen Sie Ihr Bitcoin-, Ethereum- oder Litecoin-Wallet oder wechseln Sie zu diesem.",
400 "outdated_electrum_wallet_description": "Neue Bitcoin-Wallets, die in Cake erstellt wurden, haben jetzt einen 24-Wort-Seed. Sie müssen eine neue Bitcoin-Wallet erstellen, Ihr gesamtes Geld in die neue 24-Wort-Wallet überweisen und keine Wallet mit einem 12-Wort-Seed mehr verwenden. Bitte tun Sie dies sofort, um Ihr Geld zu sichern.",
401 "understand": "Ich verstehe",
402 "apk_update": "APK-Update",
@@ -647,9 +647,27 @@
647 "available_balance_description": "Verfügbarer Saldo ist der Betrag, den Sie sofort ausgeben können. Dieser Betrag kann sich ändern, wenn Sie eine Transaktion senden oder empfangen.",
648 "syncing_wallet_alert_title": "Ihr Wallet wird synchronisiert",
649 "syncing_wallet_alert_content": "Ihr Kontostand und Ihre Transaktionsliste sind möglicherweise erst vollständig, wenn oben „SYNCHRONISIERT“ steht. Klicken/tippen Sie, um mehr zu erfahren.",
650 + "home_screen_settings": "Einstellungen für den Startbildschirm",
651 + "sort_by": "Sortiere nach",
652 + "search_add_token": "Token suchen / hinzufügen",
653 + "edit_token": "Token bearbeiten",
654 + "warning": "Warnung",
655 + "add_token_warning": "Bearbeiten oder fügen Sie Token nicht gemäß den Anweisungen von Betrügern hinzu.\nBestätigen Sie Token-Adressen immer mit seriösen Quellen!",
656 + "add_token_disclaimer_check": "Ich habe die Adresse und Informationen zum Token-Vertrag anhand einer seriösen Quelle bestätigt. Das Hinzufügen böswilliger oder falscher Informationen kann zu einem Verlust von Geldern führen.",
657 + "token_contract_address": "Token-Vertragsadresse",
658 + "token_name": "Token-Name, z. B.: Tether",
659 + "token_symbol": "Token-Symbol, z. B.: USDT",
660 + "token_decimal": "Token-Dezimalzahl",
661 + "field_required": "Dieses Feld ist erforderlich",
662 + "pin_at_top": "Stecken Sie ${token} oben fest",
663 + "invalid_input": "Ungültige Eingabe",
664 + "fiat_balance": "Fiat Balance",
665 + "gross_balance": "Bruttosaldo",
666 + "alphabetical": "Alphabetisch",
667 "generate_name": "Namen generieren",
668 "balance_page": "Balance-Seite",
669 "share": "Aktie",
670 "slidable": "Verschiebbar",
671 + "etherscan_history": "Etherscan-Geschichte",
672 "template_name": "Vorlagenname"
673 }
res/values/strings_en.arb
+23 -5
@@ -1,7 +1,7 @@
1 {
2 "welcome": "Welcome to",
3 "cake_wallet": "Cake Wallet",
4 - "first_wallet_text": "Awesome wallet for Monero, Bitcoin, Litecoin, and Haven",
4 + "first_wallet_text": "Awesome wallet for Monero, Bitcoin, Ethereum, Litecoin, and Haven",
5 "please_make_selection": "Please make a selection below to create or recover your wallet.",
6 "create_new": "Create New Wallet",
7 "restore_wallet": "Restore Wallet",
@@ -250,8 +250,8 @@
250 "transaction_details_recipient_address": "Recipient addresses",
251 "wallet_list_title": "Monero Wallet",
252 "wallet_list_create_new_wallet": "Create New Wallet",
253 - "wallet_list_edit_wallet" : "Edit wallet",
254 - "wallet_list_wallet_name" : "Wallet name",
253 + "wallet_list_edit_wallet": "Edit wallet",
254 + "wallet_list_wallet_name": "Wallet name",
255 "wallet_list_restore_wallet": "Restore Wallet",
256 "wallet_list_load_wallet": "Load wallet",
257 "wallet_list_loading_wallet": "Loading ${wallet_name} wallet",
@@ -395,8 +395,8 @@
395 "unconfirmed": "Unconfirmed Balance",
396 "displayable": "Displayable",
397 "submit_request": "submit a request",
398 - "buy_alert_content": "Currently we only support the purchase of Bitcoin, Litecoin, and Monero. Please create or switch to your Bitcoin, Litecoin, or Monero wallet.",
399 - "sell_alert_content": "We currently only support the sale of Bitcoin and Litecoin. Please create or switch to your Bitcoin or Litecoin wallet.",
398 + "buy_alert_content": "Currently we only support the purchase of Bitcoin, Ethereum, Litecoin, and Monero. Please create or switch to your Bitcoin, Ethereum, Litecoin, or Monero wallet.",
399 + "sell_alert_content": "We currently only support the sale of Bitcoin, Ethereum and Litecoin. Please create or switch to your Bitcoin, Ethereum or Litecoin wallet.",
400 "outdated_electrum_wallet_description": "New Bitcoin wallets created in Cake now have a 24-word seed. It is mandatory that you create a new Bitcoin wallet and transfer all of your funds to the new 24-word wallet, and stop using wallets with a 12-word seed. Please do this immediately to secure your funds.",
401 "understand": "I understand",
402 "apk_update": "APK update",
@@ -647,9 +647,27 @@
647 "available_balance_description": "The “Available Balance” or “Confirmed Balance” are funds that can be spent immediately. If funds appear in the lower balance but not the top balance, then you must wait a few minutes for the incoming funds to get more network confirmations. After they get more confirmations, they will be spendable.",
648 "syncing_wallet_alert_title": "Your wallet is syncing",
649 "syncing_wallet_alert_content": "Your balance and transaction list may not be complete until it says “SYNCHRONIZED” at the top. Click/tap to learn more.",
650 + "home_screen_settings": "Home screen settings",
651 + "sort_by": "Sort by",
652 + "search_add_token": "Search / Add token",
653 + "edit_token": "Edit token",
654 + "warning": "Warning",
655 + "add_token_warning": "Do not edit or add tokens as instructed by scammers.\nAlways confirm token addresses with reputable sources!",
656 + "add_token_disclaimer_check": "I have confirmed the token contract address and information using a reputable source. Adding malicious or incorrect information can result in a loss of funds.",
657 + "token_contract_address": "Token contract address",
658 + "token_name": "Token name eg: Tether",
659 + "token_symbol": "Token symbol eg: USDT",
660 + "token_decimal": "Token decimal",
661 + "field_required": "This field is required",
662 + "pin_at_top": "Pin ${token} at top",
663 + "invalid_input": "Invalid input",
664 + "fiat_balance": "Fiat Balance",
665 + "gross_balance": "Gross Balance",
666 + "alphabetical": "Alphabetical",
667 "generate_name": "Generate Name",
668 "balance_page": "Balance Page",
669 "share": "Share",
670 "slidable": "Slidable",
671 + "etherscan_history": "Etherscan history",
672 "template_name": "Template Name"
673 }
res/values/strings_es.arb
+23 -5
@@ -1,7 +1,7 @@
1 {
2 "welcome": "Bienvenido",
3 "cake_wallet": "Cake Wallet",
4 - "first_wallet_text": "Impresionante billetera para Monero, Bitcoin, Litecoin, y Haven",
4 + "first_wallet_text": "Impresionante billetera para Monero, Bitcoin, Ethereum, Litecoin, y Haven",
5 "please_make_selection": "Seleccione a continuación para crear o recuperar su billetera.",
6 "create_new": "Crear nueva billetera",
7 "restore_wallet": "Restaurar billetera",
@@ -250,8 +250,8 @@
250 "transaction_details_recipient_address": "Direcciones de destinatarios",
251 "wallet_list_title": "Monedero Monero",
252 "wallet_list_create_new_wallet": "Crear nueva billetera",
253 - "wallet_list_edit_wallet" : "Editar billetera",
254 - "wallet_list_wallet_name" : "Nombre de la billetera",
253 + "wallet_list_edit_wallet": "Editar billetera",
254 + "wallet_list_wallet_name": "Nombre de la billetera",
255 "wallet_list_restore_wallet": "Restaurar billetera",
256 "wallet_list_load_wallet": "Billetera de carga",
257 "wallet_list_loading_wallet": "Billetera ${wallet_name} de carga",
@@ -395,8 +395,8 @@
395 "unconfirmed": "Saldo no confirmado",
396 "displayable": "Visualizable",
397 "submit_request": "presentar una solicitud",
398 - "buy_alert_content": "Actualmente solo admitimos la compra de Bitcoin, Litecoin y Monero. Cree o cambie a su billetera Bitcoin, Litecoin o Monero.",
399 - "sell_alert_content": "Actualmente solo admitimos la venta de Bitcoin y Litecoin. Cree o cambie a su billetera Bitcoin o Litecoin.",
398 + "buy_alert_content": "Actualmente solo admitimos la compra de Bitcoin, Ethereum, Litecoin y Monero. Cree o cambie a su billetera Bitcoin, Ethereum, Litecoin o Monero.",
399 + "sell_alert_content": "Actualmente solo admitimos la venta de Bitcoin, Ethereum y Litecoin. Cree o cambie a su billetera Bitcoin, Ethereum o Litecoin.",
400 "outdated_electrum_wallet_description": "Las nuevas carteras de Bitcoin creadas en Cake ahora tienen una semilla de 24 palabras. Es obligatorio que cree una nueva billetera de Bitcoin y transfiera todos sus fondos a la nueva billetera de 24 palabras, y deje de usar billeteras con una semilla de 12 palabras. Haga esto de inmediato para asegurar sus fondos.",
401 "understand": "Entiendo",
402 "apk_update": "Actualización de APK",
@@ -647,9 +647,27 @@
647 "available_balance_description": "Su saldo disponible es la cantidad de fondos que puede gastar. Los fondos que se muestran aquí se pueden gastar inmediatamente.",
648 "syncing_wallet_alert_title": "Tu billetera se está sincronizando",
649 "syncing_wallet_alert_content": "Es posible que su lista de saldo y transacciones no esté completa hasta que diga \"SINCRONIZADO\" en la parte superior. Haga clic/toque para obtener más información.",
650 + "home_screen_settings": "Configuración de la pantalla de inicio",
651 + "sort_by": "Ordenar por",
652 + "search_add_token": "Buscar/Agregar token",
653 + "edit_token": "Editar token",
654 + "warning": "Advertencia",
655 + "add_token_warning": "No edite ni agregue tokens según las instrucciones de los estafadores.\n¡Confirme siempre las direcciones de los tokens con fuentes acreditadas!",
656 + "add_token_disclaimer_check": "He confirmado la dirección del contrato del token y la información utilizando una fuente confiable. Agregar información maliciosa o incorrecta puede resultar en una pérdida de fondos.",
657 + "token_contract_address": "Dirección de contrato de token",
658 + "token_name": "Nombre del token, por ejemplo: Tether",
659 + "token_symbol": "Símbolo de token, por ejemplo: USDT",
660 + "token_decimal": "Token decimal",
661 + "field_required": "Este campo es obligatorio",
662 + "pin_at_top": "pin ${token} en la parte superior",
663 + "invalid_input": "Entrada inválida",
664 + "fiat_balance": "Equilibrio Fiat",
665 + "gross_balance": "Saldo bruto",
666 + "alphabetical": "Alfabético",
667 "generate_name": "Generar nombre",
668 "balance_page": "Página de saldo",
669 "share": "Compartir",
670 "slidable": "deslizable",
671 + "etherscan_history": "historia de etherscan",
672 "template_name": "Nombre de la plantilla"
673 }
res/values/strings_fr.arb
+23 -5
@@ -1,7 +1,7 @@
1 {
2 "welcome": "Bienvenue sur",
3 "cake_wallet": "Cake Wallet",
4 - "first_wallet_text": "Super portefeuille (wallet) pour Monero, Bitcoin, Litecoin et Haven",
4 + "first_wallet_text": "Super portefeuille (wallet) pour Monero, Bitcoin, Ethereum, Litecoin et Haven",
5 "please_make_selection": "Merci de faire un choix ci-dessous pour créer ou restaurer votre portefeuille (wallet).",
6 "create_new": "Créer un Nouveau Portefeuille (Wallet)",
7 "restore_wallet": "Restaurer un Portefeuille (Wallet)",
@@ -250,8 +250,8 @@
250 "transaction_details_recipient_address": "Adresse du bénéficiaire",
251 "wallet_list_title": "Portefeuille (Wallet) Monero",
252 "wallet_list_create_new_wallet": "Créer un Nouveau Portefeuille (Wallet)",
253 - "wallet_list_edit_wallet" : "Modifier le portefeuille",
254 - "wallet_list_wallet_name" : "Nom du portefeuille",
253 + "wallet_list_edit_wallet": "Modifier le portefeuille",
254 + "wallet_list_wallet_name": "Nom du portefeuille",
255 "wallet_list_restore_wallet": "Restaurer un Portefeuille (Wallet)",
256 "wallet_list_load_wallet": "Charger un Portefeuille (Wallet)",
257 "wallet_list_loading_wallet": "Chargement du portefeuille (wallet) ${wallet_name}",
@@ -395,8 +395,8 @@
395 "unconfirmed": "Solde non confirmé",
396 "displayable": "Visible",
397 "submit_request": "soumettre une requête",
398 - "buy_alert_content": "Actuellement, nous ne prenons en charge que l'achat de Bitcoin, Litecoin et Monero. Veuillez créer ou basculer vers votre portefeuille (wallet) Bitcoin, Litecoin ou Monero.",
399 - "sell_alert_content": "Actuellement, nous ne prenons en charge que la vente de Bitcoin et Litecoin. Veuillez créer ou basculer vers votre portefeuille (wallet) Bitcoin ou Litecoin.",
398 + "buy_alert_content": "Actuellement, nous ne prenons en charge que l'achat de Bitcoin, Ethereum, Litecoin et Monero. Veuillez créer ou basculer vers votre portefeuille Bitcoin, Ethereum, Litecoin ou Monero.",
399 + "sell_alert_content": "Nous ne prenons actuellement en charge que la vente de Bitcoin, Ethereum et Litecoin. Veuillez créer ou basculer vers votre portefeuille Bitcoin, Ethereum ou Litecoin.",
400 "outdated_electrum_wallet_description": "Les nouveaux portefeuilles (wallets) Bitcoin créés dans Cake ont dorénavant une phrase secrète (seed) de 24 mots. Il est impératif que vous créiez un nouveau portefeuille Bitcoin, que vous y transfériez tous vos fonds puis que vous cessiez d'utiliser le portefeuille avec une phrase secrète de 12 mots. Merci de faire cela immédiatement pour assurer la sécurité de vos avoirs.",
401 "understand": "J'ai compris",
402 "apk_update": "Mise à jour d'APK",
@@ -647,9 +647,27 @@
647 "available_balance_description": "Le solde disponible est le montant que vous pouvez dépenser immédiatement. Il est calculé en soustrayant le solde gelé du solde total.",
648 "syncing_wallet_alert_title": "Votre portefeuille est en cours de synchronisation",
649 "syncing_wallet_alert_content": "Votre solde et votre liste de transactions peuvent ne pas être complets tant qu'il n'y a pas « SYNCHRONISÉ » en haut. Cliquez/appuyez pour en savoir plus.",
650 + "home_screen_settings": "Paramètres de l'écran d'accueil",
651 + "sort_by": "Trier par",
652 + "search_add_token": "Rechercher / Ajouter un jeton",
653 + "edit_token": "Modifier le jeton",
654 + "warning": "Avertissement",
655 + "add_token_warning": "Ne modifiez pas ou n'ajoutez pas de jetons comme indiqué par les escrocs.\nConfirmez toujours les adresses de jeton auprès de sources fiables !",
656 + "add_token_disclaimer_check": "J'ai confirmé l'adresse et les informations du contrat de jeton en utilisant une source fiable. L'ajout d'informations malveillantes ou incorrectes peut entraîner une perte de fonds.",
657 + "token_contract_address": "Adresse du contrat de jeton",
658 + "token_name": "Nom du jeton, par exemple : Tether",
659 + "token_symbol": "Symbole de jeton, par exemple : USDT",
660 + "token_decimal": "Décimal de jeton",
661 + "field_required": "Ce champ est obligatoire",
662 + "pin_at_top": "épingler ${token} en haut",
663 + "invalid_input": "Entrée invalide",
664 + "fiat_balance": "Fiat Balance",
665 + "gross_balance": "Solde brut",
666 + "alphabetical": "Alphabétique",
667 "generate_name": "Générer un nom",
668 "balance_page": "Page Solde",
669 "share": "Partager",
670 "slidable": "Glissable",
671 + "etherscan_history": "Historique d'Etherscan",
672 "template_name": "Nom du modèle"
673 }
res/values/strings_ha.arb
+23 -5
@@ -1,7 +1,7 @@
1 {
2 "welcome": "Barka da zuwa",
3 "cake_wallet": "Cake Wallet",
4 - "first_wallet_text": "Aikace-aikacen e-wallet ga Monero, Bitcoin, Litecoin, da kuma Haven",
4 + "first_wallet_text": "Aikace-aikacen e-wallet ga Monero, Bitcoin, Ethereum, Litecoin, da kuma Haven",
5 "please_make_selection": "Don Allah zaɓi ƙasa don ƙirƙira ko dawo da kwalinku.",
6 "create_new": "Ƙirƙira Sabon Kwalinku",
7 "restore_wallet": "Dawo da Kwalinku",
@@ -250,8 +250,8 @@
250 "transaction_details_recipient_address": "Adireshin masu amfani",
251 "wallet_list_title": "Monero walat",
252 "wallet_list_create_new_wallet": "Ƙirƙiri Sabon Wallet",
253 - "wallet_list_edit_wallet" : "Gyara walat",
254 - "wallet_list_wallet_name" : "Sunan walat",
253 + "wallet_list_edit_wallet": "Gyara walat",
254 + "wallet_list_wallet_name": "Sunan walat",
255 "wallet_list_restore_wallet": "Maida Wallet",
256 "wallet_list_load_wallet": "Ana loda wallet na Monero",
257 "wallet_list_loading_wallet": "Ana loda ${wallet_name} walat",
@@ -396,8 +396,8 @@
396 "unconfirmed": "Ba a tabbatar ba",
397 "displayable": "Ana iya nunawa",
398 "submit_request": "gabatar da bukata",
399 - "buy_alert_content": "A halin yanzu muna tallafawa kawai siyan Bitcoin da Litecoin. Don siyan Bitcoin ko Litecoin, da fatan za a ƙirƙira ko canza zuwa walat ɗin ku na Bitcoin ko Litecoin.",
400 - "sell_alert_content": "A halin yanzu muna tallafawa siyar da Bitcoin da Litecoin kawai. Da fatan za a ƙirƙira ko canza zuwa walat ɗin ku na Bitcoin ko Litecoin.",
399 + "buy_alert_content": "A halin yanzu muna tallafawa kawai siyan Bitcoin, Ethereum, Litecoin, da Monero. Da fatan za a ƙirƙiri ko canza zuwa Bitcoin, Ethereum, Litecoin, ko Monero walat.",
400 + "sell_alert_content": "A halin yanzu muna tallafawa kawai siyar da Bitcoin, Ethereum da Litecoin. Da fatan za a ƙirƙiri ko canza zuwa walat ɗin ku na Bitcoin, Ethereum ko Litecoin.",
401 "outdated_electrum_wallet_description": "Sabbin walat ɗin Bitcoin da aka kirkira a cikin Cake yanzu suna da nau'in kalma 24. Ya zama dole ka ƙirƙiri sabon walat ɗin Bitcoin kuma canza duk kuɗin ku zuwa sabon walat ɗin kalmomi 24, kuma ku daina amfani da walat tare da iri mai kalma 12. Da fatan za a yi haka nan take don samun kuɗin ku.",
402 "understand": "na gane",
403 "apk_update": "apk sabunta",
@@ -627,9 +627,27 @@
627 "available_balance_description": "Ma'auni mai samuwa” ko ”,Tabbataccen Ma'auni”, kudade ne da za a iya kashewa nan da nan. Idan kudade sun bayyana a cikin ƙananan ma'auni amma ba babban ma'auni ba, to dole ne ku jira 'yan mintoci kaɗan don kudaden shiga don samun ƙarin tabbaci na hanyar sadarwa. Bayan sun sami ƙarin tabbaci, za a kashe su.",
628 "syncing_wallet_alert_title": "Walat ɗin ku yana aiki tare",
629 "syncing_wallet_alert_content": "Ma'aunin ku da lissafin ma'amala bazai cika ba har sai an ce \"SYNCHRONIZED\" a saman. Danna/matsa don ƙarin koyo.",
630 + "home_screen_settings": "Saitunan allo na gida",
631 + "sort_by": "Kasa",
632 + "search_add_token": "Bincika / Ƙara alama",
633 + "edit_token": "Gyara alamar",
634 + "warning": "Gargadi",
635 + "add_token_warning": "Kar a gyara ko ƙara alamu kamar yadda masu zamba suka umarta.\nKoyaushe tabbatar da adiresoshin alamar tare da sanannun tushe!",
636 + "add_token_disclaimer_check": "Na tabbatar da adireshin kwangilar alamar da bayanin ta amfani da ingantaccen tushe. Ƙara bayanan ƙeta ko kuskure na iya haifar da asarar kuɗi.",
637 + "token_contract_address": "Adireshin kwangilar Token",
638 + "token_name": "Alamar sunan misali: Tether",
639 + "token_symbol": "Alamar alama misali: USDT",
640 + "token_decimal": "Alamar ƙima",
641 + "field_required": "wannan fillin ana bukatansa",
642 + "pin_at_top": "pin ${token} a sama",
643 + "invalid_input": "Shigar da ba daidai ba",
644 + "fiat_balance": "Fiat Balance",
645 + "gross_balance": "Babban Ma'auni",
646 + "alphabetical": "Harafi",
647 "generate_name": "Ƙirƙirar Suna",
648 "balance_page": "Ma'auni Page",
649 "share": "Raba",
650 "slidable": "Mai iya zamewa",
651 + "etherscan_history": "Etherscan tarihin kowane zamani",
652 "template_name": "Sunan Samfura"
653 }
res/values/strings_hi.arb
+23 -5
@@ -1,7 +1,7 @@
1 {
2 "welcome": "स्वागत हे सेवा मेरे",
3 "cake_wallet": "Cake Wallet",
4 - "first_wallet_text": "Monero, Bitcoin, Litecoin, और Haven के लिए बहुत बढ़िया बटुआ",
4 + "first_wallet_text": "Monero, Bitcoin, Ethereum, Litecoin, और Haven के लिए बहुत बढ़िया बटुआ",
5 "please_make_selection": "कृपया नीचे चयन करें अपना बटुआ बनाएं या पुनर्प्राप्त करें.",
6 "create_new": "नया बटुआ बनाएँ",
7 "restore_wallet": "वॉलेट को पुनर्स्थापित करें",
@@ -250,8 +250,8 @@
250 "transaction_details_recipient_address": "प्राप्तकर्ता के पते",
251 "wallet_list_title": "Monero बटुआ",
252 "wallet_list_create_new_wallet": "नया बटुआ बनाएँ",
253 - "wallet_list_edit_wallet" : "बटुआ संपादित करें",
254 - "wallet_list_wallet_name" : "बटुआ नाम",
253 + "wallet_list_edit_wallet": "बटुआ संपादित करें",
254 + "wallet_list_wallet_name": "बटुआ नाम",
255 "wallet_list_restore_wallet": "वॉलेट को पुनर्स्थापित करें",
256 "wallet_list_load_wallet": "वॉलेट लोड करें",
257 "wallet_list_loading_wallet": "लोड हो रहा है ${wallet_name} बटुआ",
@@ -395,8 +395,8 @@
395 "unconfirmed": "अपुष्ट शेष राशि",
396 "displayable": "प्रदर्शन योग्य",
397 "submit_request": "एक अनुरोध सबमिट करें",
398 - "buy_alert_content": "वर्तमान में हम केवल बिटकॉइन, लाइटकॉइन और मोनेरो की खरीद का समर्थन करते हैं। कृपया अपना बिटकॉइन, लाइटकॉइन, या मोनेरो वॉलेट बनाएं या स्विच करें।",
399 - "sell_alert_content": "वर्तमान में हम केवल बिटकॉइन और लाइटकॉइन की बिक्री का समर्थन करते हैं। कृपया अपना बिटकॉइन या लाइटकॉइन वॉलेट बनाएं या स्विच करें।",
398 + "buy_alert_content": "वर्तमान में हम केवल बिटकॉइन, एथेरियम, लाइटकॉइन और मोनेरो की खरीद का समर्थन करते हैं। कृपया अपना बिटकॉइन, एथेरियम, लाइटकॉइन, या मोनेरो वॉलेट बनाएं या उस पर स्विच करें।",
399 + "sell_alert_content": "हम वर्तमान में केवल बिटकॉइन, एथेरियम और लाइटकॉइन की बिक्री का समर्थन करते हैं। कृपया अपना बिटकॉइन, एथेरियम या लाइटकॉइन वॉलेट बनाएं या उसमें स्विच करें।",
400 "outdated_electrum_wallet_description": "केक में बनाए गए नए बिटकॉइन वॉलेट में अब 24-शब्द का बीज है। यह अनिवार्य है कि आप एक नया बिटकॉइन वॉलेट बनाएं और अपने सभी फंड को नए 24-शब्द वाले वॉलेट में स्थानांतरित करें, और 12-शब्द बीज वाले वॉलेट का उपयोग करना बंद करें। कृपया अपने धन को सुरक्षित करने के लिए इसे तुरंत करें।",
401 "understand": "मुझे समझ",
402 "apk_update": "APK अद्यतन",
@@ -647,9 +647,27 @@
647 "available_balance_description": "उपलब्ध शेष या ”पुष्टिकृत शेष”, वे धनराशि हैं जिन्हें तुरंत खर्च किया जा सकता है। यदि फंड निचले बैलेंस में दिखाई देते हैं, लेकिन शीर्ष बैलेंस में नहीं, तो आपको आने वाले फंड के लिए अधिक नेटवर्क पुष्टिकरण प्राप्त करने के लिए कुछ मिनट इंतजार करना होगा। अधिक पुष्टि मिलने के बाद, वे खर्च करने योग्य हो जाएंगे।",
648 "syncing_wallet_alert_title": "आपका वॉलेट सिंक हो रहा है",
649 "syncing_wallet_alert_content": "आपकी शेष राशि और लेनदेन सूची तब तक पूरी नहीं हो सकती जब तक कि शीर्ष पर \"सिंक्रनाइज़्ड\" न लिखा हो। अधिक जानने के लिए क्लिक/टैप करें।",
650 + "home_screen_settings": "होम स्क्रीन सेटिंग्स",
651 + "sort_by": "इसके अनुसार क्रमबद्ध करें",
652 + "search_add_token": "खोजें/टोकन जोड़ें",
653 + "edit_token": "टोकन संपादित करें",
654 + "warning": "चेतावनी",
655 + "add_token_warning": "स्कैमर्स के निर्देशानुसार टोकन संपादित या जोड़ें न करें।\nहमेशा प्रतिष्ठित स्रोतों से टोकन पते की पुष्टि करें!",
656 + "add_token_disclaimer_check": "मैंने एक प्रतिष्ठित स्रोत का उपयोग करके टोकन अनुबंध पते और जानकारी की पुष्टि की है। दुर्भावनापूर्ण या गलत जानकारी जोड़ने से धन की हानि हो सकती है।",
657 + "token_contract_address": "टोकन अनुबंध पता",
658 + "token_name": "टोकन नाम जैसे: टीथर",
659 + "token_symbol": "टोकन प्रतीक जैसे: यूएसडीटी",
660 + "token_decimal": "सांकेतिक दशमलव",
661 + "field_required": "यह फ़ील्ड आवश्यक है",
662 + "pin_at_top": "शीर्ष पर ${token} पिन करें",
663 + "invalid_input": "अमान्य निवेश",
664 + "fiat_balance": "फिएट बैलेंस",
665 + "gross_balance": "सकल संतुलन",
666 + "alphabetical": "वर्णमाला",
667 "generate_name": "नाम जनरेट करें",
668 "balance_page": "बैलेंस पेज",
669 "share": "शेयर करना",
670 "slidable": "फिसलने लायक",
671 + "etherscan_history": "इथरस्कैन इतिहास",
672 "template_name": "टेम्पलेट नाम"
673 }
res/values/strings_hr.arb
+23 -5
@@ -1,7 +1,7 @@
1 {
2 "welcome": "Dobrodošli na",
3 "cake_wallet": "Cake Wallet",
4 - "first_wallet_text": "Odličan novčanik za Monero, Bitcoin, Litecoin, i Haven",
4 + "first_wallet_text": "Odličan novčanik za Monero, Bitcoin, Ethereum, Litecoin, i Haven",
5 "please_make_selection": "Molimo odaberite opcije niže za izradu novog novčanika ili za oporavak postojećeg.",
6 "create_new": "Izradi novi novčanik",
7 "restore_wallet": "Oporavi novčanik",
@@ -250,8 +250,8 @@
250 "transaction_details_recipient_address": "Adrese primatelja",
251 "wallet_list_title": "Monero novčanik",
252 "wallet_list_create_new_wallet": "Izradi novi novčanik",
253 - "wallet_list_edit_wallet" : "Uredi novčanik",
254 - "wallet_list_wallet_name" : "Naziv novčanika",
253 + "wallet_list_edit_wallet": "Uredi novčanik",
254 + "wallet_list_wallet_name": "Naziv novčanika",
255 "wallet_list_restore_wallet": "Oporavi novčanik",
256 "wallet_list_load_wallet": "Učitaj novčanik",
257 "wallet_list_loading_wallet": "Učitavanje novčanika ${wallet_name}",
@@ -395,8 +395,8 @@
395 "unconfirmed": "Nepotvrđeno stanje",
396 "displayable": "Dostupno za prikaz",
397 "submit_request": "podnesi zahtjev",
398 - "buy_alert_content": "Trenutno podržavamo samo kupnju Bitcoina, Litecoina i Monera. Izradite ili prijeđite na svoj Bitcoin, Litecoin ili Monero novčanik.",
399 - "sell_alert_content": "Trenutno podržavamo samo prodaju Bitcoina i Litecoina. Izradite ili prijeđite na svoj Bitcoin ili Litecoin novčanik.",
398 + "buy_alert_content": "Trenutno podržavamo samo kupnju Bitcoina, Ethereuma, Litecoina i Monera. Izradite ili prijeđite na svoj Bitcoin, Ethereum, Litecoin ili Monero novčanik.",
399 + "sell_alert_content": "Trenutno podržavamo samo prodaju Bitcoina, Ethereuma i Litecoina. Izradite ili prijeđite na svoj Bitcoin, Ethereum ili Litecoin novčanik.",
400 "outdated_electrum_wallet_description": "Novi Bitcoin novčanici stvoreni u Cakeu sada imaju sjeme od 24 riječi. Obavezno je stvoriti novi Bitcoin novčanik i prenijeti sva svoja sredstva u novi novčanik od 24 riječi te prestati koristiti novčanike s sjemenkom od 12 riječi. Učinite to odmah kako biste osigurali svoja sredstva.",
401 "understand": "Razumijem",
402 "apk_update": "APK ažuriranje",
@@ -647,9 +647,27 @@
647 "available_balance_description": "Dostupno stanje je iznos koji možete potrošiti. To je vaš saldo minus bilo kakve transakcije koje su još uvijek u tijeku.",
648 "syncing_wallet_alert_title": "Vaš novčanik se sinkronizira",
649 "syncing_wallet_alert_content": "Vaš saldo i popis transakcija možda neće biti potpuni sve dok na vrhu ne piše \"SINKRONIZIRANO\". Kliknite/dodirnite da biste saznali više.",
650 + "home_screen_settings": "Postavke početnog zaslona",
651 + "sort_by": "Poredaj po",
652 + "search_add_token": "Traži / Dodaj token",
653 + "edit_token": "Uredi token",
654 + "warning": "Upozorenje",
655 + "add_token_warning": "Nemojte uređivati niti dodavati tokene prema uputama prevaranata.\nUvijek potvrdite adrese tokena s uglednim izvorima!",
656 + "add_token_disclaimer_check": "Potvrdio sam adresu i informacije o ugovoru o tokenu koristeći ugledni izvor. Dodavanje zlonamjernih ili netočnih informacija može dovesti do gubitka sredstava.",
657 + "token_contract_address": "Adresa ugovora tokena",
658 + "token_name": "Naziv tokena npr.: Tether",
659 + "token_symbol": "Simbol tokena npr.: USDT",
660 + "token_decimal": "Token decimalni",
661 + "field_required": "ovo polje je obavezno",
662 + "pin_at_top": "prikvači ${token} na vrh",
663 + "invalid_input": "Pogrešan unos",
664 + "fiat_balance": "Fiat Bilans",
665 + "gross_balance": "Bruto bilanca",
666 + "alphabetical": "Abecedno",
667 "generate_name": "Generiraj ime",
668 "balance_page": "Stranica sa stanjem",
669 "share": "Udio",
670 "slidable": "Klizna",
671 + "etherscan_history": "Etherscan povijest",
672 "template_name": "Naziv predloška"
673 }
res/values/strings_id.arb
+23 -5
@@ -1,7 +1,7 @@
1 {
2 "welcome": "Selamat datang di",
3 "cake_wallet": "Cake Wallet",
4 - "first_wallet_text": "Dompet luar biasa untuk Monero, Bitcoin, Litecoin, dan Haven",
4 + "first_wallet_text": "Dompet luar biasa untuk Monero, Bitcoin, Ethereum, Litecoin, dan Haven",
5 "please_make_selection": "Silahkan membuat pilihan di bawah ini untuk membuat atau memulihkan dompet Anda.",
6 "create_new": "Buat Dompet Baru",
7 "restore_wallet": "Pulihkan Dompet",
@@ -250,8 +250,8 @@
250 "transaction_details_recipient_address": "Alamat Penerima",
251 "wallet_list_title": "Dompet Monero",
252 "wallet_list_create_new_wallet": "Buat Dompet Baru",
253 - "wallet_list_edit_wallet" : "Edit dompet",
254 - "wallet_list_wallet_name" : "Nama dompet",
253 + "wallet_list_edit_wallet": "Edit dompet",
254 + "wallet_list_wallet_name": "Nama dompet",
255 "wallet_list_restore_wallet": "Pulihkan Dompet",
256 "wallet_list_load_wallet": "Muat dompet",
257 "wallet_list_loading_wallet": "Memuat ${wallet_name} dompet",
@@ -396,8 +396,8 @@
396 "unconfirmed": "Saldo Belum Dikonfirmasi",
397 "displayable": "Dapat ditampilkan",
398 "submit_request": "kirim permintaan",
399 - "buy_alert_content": "Saat ini kami hanya mendukung pembelian Bitcoin, Litecoin, dan Monero. Harap buat atau alihkan ke dompet Bitcoin, Litecoin, atau Monero Anda.",
400 - "sell_alert_content": "Saat ini kami hanya mendukung penjualan Bitcoin dan Litecoin. Harap buat atau alihkan ke dompet Bitcoin atau Litecoin Anda.",
399 + "buy_alert_content": "Saat ini kami hanya mendukung pembelian Bitcoin, Ethereum, Litecoin, dan Monero. Harap buat atau alihkan ke dompet Bitcoin, Ethereum, Litecoin, atau Monero Anda.",
400 + "sell_alert_content": "Saat ini kami hanya mendukung penjualan Bitcoin, Ethereum, dan Litecoin. Harap buat atau alihkan ke dompet Bitcoin, Ethereum, atau Litecoin Anda.",
401 "outdated_electrum_wallet_description": "Dompet Bitcoin baru yang dibuat di Cake sekarang memiliki biji semai 24 kata. Wajib bagi Anda untuk membuat dompet Bitcoin baru dan mentransfer semua dana Anda ke dompet 24 kata baru, dan berhenti menggunakan dompet dengan biji semai 12 kata. Silakan lakukan ini segera untuk mengamankan dana Anda.",
402 "understand": "Saya mengerti",
403 "apk_update": "Pembaruan APK",
@@ -637,9 +637,27 @@
637 "available_balance_description": "“Saldo yang Tersedia” atau “Saldo yang Dikonfirmasi” adalah dana yang dapat langsung dibelanjakan. Jika dana muncul di saldo bawah tetapi tidak di saldo atas, maka Anda harus menunggu beberapa menit agar dana masuk mendapatkan konfirmasi jaringan lainnya. Setelah mereka mendapatkan lebih banyak konfirmasi, mereka akan dapat dibelanjakan.",
638 "syncing_wallet_alert_title": "Dompet Anda sedang disinkronkan",
639 "syncing_wallet_alert_content": "Saldo dan daftar transaksi Anda mungkin belum lengkap sampai tertulis “SYNCHRONIZED” di bagian atas. Klik/ketuk untuk mempelajari lebih lanjut.",
640 + "home_screen_settings": "Pengaturan layar awal",
641 + "sort_by": "Sortir dengan",
642 + "search_add_token": "Cari / Tambahkan token",
643 + "edit_token": "Mengedit token",
644 + "warning": "Peringatan",
645 + "add_token_warning": "Jangan mengedit atau menambahkan token seperti yang diinstruksikan oleh penipu.\nSelalu konfirmasikan alamat token dengan sumber tepercaya!",
646 + "add_token_disclaimer_check": "Saya telah mengonfirmasi alamat dan informasi kontrak token menggunakan sumber yang memiliki reputasi baik. Menambahkan informasi jahat atau salah dapat mengakibatkan hilangnya dana.",
647 + "token_contract_address": "Alamat kontrak token",
648 + "token_name": "Nama token misalnya: Tether",
649 + "token_symbol": "Simbol token misalnya: USDT",
650 + "token_decimal": "Desimal token",
651 + "field_required": "Bagian ini diperlukan",
652 + "pin_at_top": "sematkan ${token} di atas",
653 + "invalid_input": "Masukan tidak valid",
654 + "fiat_balance": "Saldo Fiat",
655 + "gross_balance": "Saldo Kotor",
656 + "alphabetical": "Alfabetis",
657 "generate_name": "Hasilkan Nama",
658 "balance_page": "Halaman Saldo",
659 "share": "Membagikan",
660 "slidable": "Dapat digeser",
661 + "etherscan_history": "Sejarah Etherscan",
662 "template_name": "Nama Templat"
663 }
res/values/strings_it.arb
+23 -5
@@ -1,7 +1,7 @@
1 {
2 "welcome": "Benvenuto",
3 "cake_wallet": "Cake Wallet",
4 - "first_wallet_text": "Portafoglio fantastico per Monero, Bitcoin, Litecoin, e Haven",
4 + "first_wallet_text": "Portafoglio fantastico per Monero, Bitcoin, Ethereum, Litecoin, e Haven",
5 "please_make_selection": "Gentilmente seleziona se vuoi generare o recuperare il tuo portafoglio.",
6 "create_new": "Genera nuovo Portafoglio",
7 "restore_wallet": "Recupera Portafoglio",
@@ -250,8 +250,8 @@
250 "transaction_details_recipient_address": "Indirizzi dei destinatari",
251 "wallet_list_title": "Portafoglio Monero",
252 "wallet_list_create_new_wallet": "Crea Nuovo Portafoglio",
253 - "wallet_list_edit_wallet" : "Modifica portafoglio",
254 - "wallet_list_wallet_name" : "Nome del portafoglio",
253 + "wallet_list_edit_wallet": "Modifica portafoglio",
254 + "wallet_list_wallet_name": "Nome del portafoglio",
255 "wallet_list_restore_wallet": "Recupera Portafoglio",
256 "wallet_list_load_wallet": "Caricamento Portafoglio",
257 "wallet_list_loading_wallet": "Caricamento portafoglio ${wallet_name}",
@@ -395,8 +395,8 @@
395 "unconfirmed": "Saldo non confermato",
396 "displayable": "Visualizzabile",
397 "submit_request": "invia una richiesta",
398 - "buy_alert_content": "Attualmente supportiamo solo l'acquisto di Bitcoin, Litecoin e Monero. Crea o passa al tuo portafoglio Bitcoin, Litecoin o Monero.",
399 - "sell_alert_content": "Al momento supportiamo solo la vendita di Bitcoin e Litecoin. Crea o passa al tuo portafoglio Bitcoin o Litecoin.",
398 + "buy_alert_content": "Attualmente supportiamo solo l'acquisto di Bitcoin, Ethereum, Litecoin e Monero. Crea o passa al tuo portafoglio Bitcoin, Ethereum, Litecoin o Monero.",
399 + "sell_alert_content": "Al momento supportiamo solo la vendita di Bitcoin, Ethereum e Litecoin. Crea o passa al tuo portafoglio Bitcoin, Ethereum o Litecoin.",
400 "outdated_electrum_wallet_description": "I nuovi portafogli Bitcoin creati in Cake ora hanno un seme di 24 parole. È obbligatorio creare un nuovo portafoglio Bitcoin e trasferire tutti i fondi nel nuovo portafoglio di 24 parole e smettere di usare portafogli con un seme di 12 parole. Ti preghiamo di farlo immediatamente per proteggere i tuoi fondi.",
401 "understand": "Capisco",
402 "apk_update": "Aggiornamento APK",
@@ -647,9 +647,27 @@
647 "available_balance_description": "Il saldo disponibile è il saldo totale meno i fondi congelati. I fondi congelati sono fondi che sono stati inviati ma non sono ancora stati confermati.",
648 "syncing_wallet_alert_title": "Il tuo portafoglio si sta sincronizzando",
649 "syncing_wallet_alert_content": "Il saldo e l'elenco delle transazioni potrebbero non essere completi fino a quando non viene visualizzato \"SYNCHRONIZED\" in alto. Clicca/tocca per saperne di più.",
650 + "home_screen_settings": "Impostazioni della schermata iniziale",
651 + "sort_by": "Ordina per",
652 + "search_add_token": "Cerca / Aggiungi token",
653 + "edit_token": "Modifica token",
654 + "warning": "Avvertimento",
655 + "add_token_warning": "Non modificare o aggiungere token come indicato dai truffatori.\nConferma sempre gli indirizzi dei token con fonti attendibili!",
656 + "add_token_disclaimer_check": "Ho confermato l'indirizzo e le informazioni del contratto token utilizzando una fonte attendibile. L'aggiunta di informazioni dannose o errate può comportare una perdita di fondi.",
657 + "token_contract_address": "Indirizzo del contratto token",
658 + "token_name": "Nome del token, ad esempio: Tether",
659 + "token_symbol": "Simbolo del token, ad esempio: USDT",
660 + "token_decimal": "Decimale del token",
661 + "field_required": "Questo campo è obbligatorio",
662 + "pin_at_top": "fissa ${token} in alto",
663 + "invalid_input": "Inserimento non valido",
664 + "fiat_balance": "Equilibrio fiat",
665 + "gross_balance": "Saldo lordo",
666 + "alphabetical": "Alfabetico",
667 "generate_name": "Genera nome",
668 "balance_page": "Pagina di equilibrio",
669 "share": "Condividere",
670 "slidable": "Scorrevole",
671 + "etherscan_history": "Storia Etherscan",
672 "template_name": "Nome modello"
673 }
res/values/strings_ja.arb
+23 -5
@@ -1,7 +1,7 @@
1 {
2 "welcome": "ようこそ に",
3 "cake_wallet": "Cake Wallet",
4 - "first_wallet_text": "Monero、Bitcoin、Litecoin、Haven用の素晴らしいウォレット",
4 + "first_wallet_text": "Monero、Bitcoin、Ethereum、Litecoin、Haven用の素晴らしいウォレット",
5 "please_make_selection": "以下を選択してください ウォレットを作成または回復する.",
6 "create_new": "新しいウォレットを作成",
7 "restore_wallet": "ウォレットを復元",
@@ -250,8 +250,8 @@
250 "transaction_details_recipient_address": "受信者のアドレス",
251 "wallet_list_title": "Monero 財布",
252 "wallet_list_create_new_wallet": "新しいウォレットを作成",
253 - "wallet_list_edit_wallet" : "ウォレットを編集する",
254 - "wallet_list_wallet_name" : "ウォレット名",
253 + "wallet_list_edit_wallet": "ウォレットを編集する",
254 + "wallet_list_wallet_name": "ウォレット名",
255 "wallet_list_restore_wallet": "ウォレットを復元",
256 "wallet_list_load_wallet": "ウォレットをロード",
257 "wallet_list_loading_wallet": "読み込み中 ${wallet_name} 財布",
@@ -395,8 +395,8 @@
395 "unconfirmed": "残高未確認",
396 "displayable": "表示可能",
397 "submit_request": "リクエストを送信する",
398 - "buy_alert_content": "現在、ビットコイン、ライトコイン、モネロの購入のみをサポートしています。 Bitcoin、Litecoin、または Monero ウォレットを作成するか、切り替えてください。",
399 - "sell_alert_content": "現在、ビットコインとライトコインの販売のみをサポートしています。 ビットコインまたはライトコインウォレットを作成するか、ウォレットに切り替えてください。",
398 + "buy_alert_content": "現在、ビットコイン、イーサリアム、ライトコイン、モネロの購入のみをサポートしています。ビットコイン、イーサリアム、ライトコイン、またはモネロのウォレットを作成するか、これらのウォレットに切り替えてください。",
399 + "sell_alert_content": "現在、ビットコイン、イーサリアム、ライトコインの販売のみをサポートしています。ビットコイン、イーサリアム、またはライトコインのウォレットを作成するか、これらのウォレットに切り替えてください。",
400 "outdated_electrum_wallet_description": "Cakeで作成された新しいビットコインウォレットには、24ワードのシードがあります。 新しいビットコインウォレットを作成し、すべての資金を新しい24ワードのウォレットに転送し、12ワードのシードを持つウォレットの使用を停止することが必須です。 あなたの資金を確保するためにこれをすぐに行ってください。",
401 "understand": "わかります",
402 "apk_update": "APKアップデート",
@@ -647,9 +647,27 @@
647 "available_balance_description": "利用可能な残高は、ウォレットの残高から冷凍残高を差し引いたものです。",
648 "syncing_wallet_alert_title": "ウォレットは同期中です",
649 "syncing_wallet_alert_content": "上部に「同期済み」と表示されるまで、残高と取引リストが完了していない可能性があります。詳細については、クリック/タップしてください。",
650 + "home_screen_settings": "ホーム画面の設定",
651 + "sort_by": "並び替え",
652 + "search_add_token": "トークンの検索/追加",
653 + "edit_token": "トークンの編集",
654 + "warning": "警告",
655 + "add_token_warning": "詐欺師の指示に従ってトークンを編集または追加しないでください。\nトークン アドレスは常に信頼できる情報源で確認してください。",
656 + "add_token_disclaimer_check": "信頼できる情報源を使用して、トークン コントラクトのアドレスと情報を確認しました。 悪意のある情報や不正確な情報を追加すると、資金が失われる可能性があります。",
657 + "token_contract_address": "トークンコントラクトアドレス",
658 + "token_name": "トークン名 例: Tether",
659 + "token_symbol": "トークンシンボル 例: USDT",
660 + "token_decimal": "トークン10進数",
661 + "field_required": "この項目は必須です",
662 + "pin_at_top": "${token} を上部に固定します",
663 + "invalid_input": "無効入力",
664 + "fiat_balance": "フィアットバランス",
665 + "gross_balance": "グロス残高",
666 + "alphabetical": "アルファベット順",
667 "generate_name": "名前の生成",
668 "balance_page": "残高ページ",
669 "share": "共有",
670 "slidable": "スライド可能",
671 + "etherscan_history": "イーサスキャンの歴史",
672 "template_name": "テンプレート名"
673 }
res/values/strings_ko.arb
+23 -5
@@ -1,7 +1,7 @@
1 {
2 "welcome": "환영 에",
3 "cake_wallet": "Cake Wallet",
4 - "first_wallet_text": "Monero, Bitcoin, Litecoin 및 Haven을 위한 멋진 지갑",
4 + "first_wallet_text": "Monero, Bitcoin, Ethereum, Litecoin 및 Haven을 위한 멋진 지갑",
5 "please_make_selection": "아래에서 선택하십시오 지갑 만들기 또는 복구.",
6 "create_new": "새 월렛 만들기",
7 "restore_wallet": "월렛 복원",
@@ -250,8 +250,8 @@
250 "transaction_details_recipient_address": "받는 사람 주소",
251 "wallet_list_title": "모네로 월렛",
252 "wallet_list_create_new_wallet": "새 월렛 만들기",
253 - "wallet_list_edit_wallet" : "지갑 수정",
254 - "wallet_list_wallet_name" : "지갑 이름",
253 + "wallet_list_edit_wallet": "지갑 수정",
254 + "wallet_list_wallet_name": "지갑 이름",
255 "wallet_list_restore_wallet": "월렛 복원",
256 "wallet_list_load_wallet": "지갑로드",
257 "wallet_list_loading_wallet": "로딩 ${wallet_name} 지갑",
@@ -395,8 +395,8 @@
395 "unconfirmed": "확인되지 않은 잔액",
396 "displayable": "표시 가능",
397 "submit_request": "요청을 제출",
398 - "buy_alert_content": "현재 우리는 Bitcoin, Litecoin 및 Monero 구매만 지원합니다. Bitcoin, Litecoin 또는 Monero 지갑을 생성하거나 전환하십시오.",
399 - "sell_alert_content": "현재 Bitcoin 및 Litecoin 판매만 지원합니다. Bitcoin 또는 Litecoin 지갑을 생성하거나 전환하십시오.",
398 + "buy_alert_content": "현재 Bitcoin, Ethereum, Litecoin 및 Monero 구매만 지원합니다. Bitcoin, Ethereum, Litecoin 또는 Monero 지갑을 생성하거나 전환하십시오.",
399 + "sell_alert_content": "현재 Bitcoin, Ethereum 및 Litecoin의 판매만 지원합니다. Bitcoin, Ethereum 또는 Litecoin 지갑을 생성하거나 전환하십시오.",
400 "outdated_electrum_wallet_description": "Cake에서 생성 된 새로운 비트 코인 지갑에는 이제 24 단어 시드가 있습니다. 새로운 비트 코인 지갑을 생성하고 모든 자금을 새로운 24 단어 지갑으로 이체하고 12 단어 시드가있는 지갑 사용을 중지해야합니다. 자금을 확보하려면 즉시이 작업을 수행하십시오.",
401 "understand": "이해 했어요",
402 "apk_update": "APK 업데이트",
@@ -647,9 +647,27 @@
647 "available_balance_description": "이 지갑에서 사용할 수 있는 잔액입니다. 이 잔액은 블록체인에서 가져온 것이며, Cake Wallet이 사용할 수 없습니다.",
648 "syncing_wallet_alert_title": "지갑 동기화 중",
649 "syncing_wallet_alert_content": "상단에 \"동기화됨\"이라고 표시될 때까지 잔액 및 거래 목록이 완전하지 않을 수 있습니다. 자세히 알아보려면 클릭/탭하세요.",
650 + "home_screen_settings": "홈 화면 설정",
651 + "sort_by": "정렬 기준",
652 + "search_add_token": "검색 / 토큰 추가",
653 + "edit_token": "토큰 편집",
654 + "warning": "경고",
655 + "add_token_warning": "사기꾼의 지시에 따라 토큰을 편집하거나 추가하지 마십시오.\n항상 신뢰할 수 있는 출처를 통해 토큰 주소를 확인하세요!",
656 + "add_token_disclaimer_check": "신뢰할 수 있는 출처를 통해 토큰 컨트랙트 주소와 정보를 확인했습니다. 악의적이거나 잘못된 정보를 추가하면 자금 손실이 발생할 수 있습니다.",
657 + "token_contract_address": "토큰 계약 주소",
658 + "token_name": "토큰 이름 예: Tether",
659 + "token_symbol": "토큰 기호 예: USDT",
660 + "token_decimal": "토큰 십진수",
661 + "field_required": "이 필드는 필수입니다",
662 + "pin_at_top": "상단에 ${token} 고정",
663 + "invalid_input": "잘못된 입력",
664 + "fiat_balance": "피아트 잔액",
665 + "gross_balance": "총 잔액",
666 + "alphabetical": "알파벳순",
667 "generate_name": "이름 생성",
668 "balance_page": "잔액 페이지",
669 "share": "공유하다",
670 "slidable": "슬라이딩 가능",
671 + "etherscan_history": "이더스캔 역사",
672 "template_name": "템플릿 이름"
673 }
res/values/strings_my.arb
+23 -5
@@ -1,7 +1,7 @@
1 {
2 "welcome": "မှကြိုဆိုပါတယ်။",
3 "cake_wallet": "Cake ပိုက်ဆံအိတ်",
4 - "first_wallet_text": "Monero၊ Bitcoin၊ Litecoin နှင့် Haven အတွက် အလွန်ကောင်းမွန်သော ပိုက်ဆံအိတ်",
4 + "first_wallet_text": "Monero၊ Bitcoin၊ Ethereum၊ Litecoin နှင့် Haven အတွက် အလွန်ကောင်းမွန်သော ပိုက်ဆံအိတ်",
5 "please_make_selection": "သင့်ပိုက်ဆံအိတ်ကို ဖန်တီးရန် သို့မဟုတ် ပြန်လည်ရယူရန် အောက်တွင် ရွေးချယ်မှုတစ်ခု ပြုလုပ်ပါ။",
6 "create_new": "Wallet အသစ်ဖန်တီးပါ။",
7 "restore_wallet": "ပိုက်ဆံအိတ်ကို ပြန်ယူပါ။",
@@ -250,8 +250,8 @@
250 "transaction_details_recipient_address": "လက်ခံသူလိပ်စာများ",
251 "wallet_list_title": "Monero ပိုက်ဆံအိတ်",
252 "wallet_list_create_new_wallet": "Wallet အသစ်ဖန်တီးပါ။",
253 - "wallet_list_edit_wallet" : "ပိုက်ဆံအိတ်ကို တည်းဖြတ်ပါ။",
254 - "wallet_list_wallet_name" : "ပိုက်ဆံအိတ်နာမည်",
253 + "wallet_list_edit_wallet": "ပိုက်ဆံအိတ်ကို တည်းဖြတ်ပါ။",
254 + "wallet_list_wallet_name": "ပိုက်ဆံအိတ်နာမည်",
255 "wallet_list_restore_wallet": "ပိုက်ဆံအိတ်ကို ပြန်ယူပါ။",
256 "wallet_list_load_wallet": "ပိုက်ဆံအိတ်ကို တင်ပါ။",
257 "wallet_list_loading_wallet": "${wallet_name} ပိုက်ဆံအိတ်ကို ဖွင့်နေသည်။",
@@ -395,8 +395,8 @@
395 "unconfirmed": "အတည်မပြုနိုင်သော လက်ကျန်ငွေ",
396 "displayable": "ပြသနိုင်သည်။",
397 "submit_request": "တောင်းဆိုချက်တစ်ခုတင်ပြပါ။",
398 - "buy_alert_content": "လောလောဆယ်တွင် ကျွန်ုပ်တို့သည် Bitcoin၊ Litecoin နှင့် Monero တို့ကိုသာ ဝယ်ယူမှုကို ပံ့ပိုးပေးပါသည်။ သင်၏ Bitcoin၊ Litecoin သို့မဟုတ် Monero ပိုက်ဆံအိတ်ကို ဖန်တီးပါ သို့မဟုတ် ပြောင်းပါ။",
399 - "sell_alert_content": "ကျွန်ုပ်တို့သည် လက်ရှိတွင် Bitcoin နှင့် Litecoin ရောင်းချခြင်းကိုသာ ထောက်ခံပါသည်။ သင်၏ Bitcoin သို့မဟုတ် Litecoin ပိုက်ဆံအိတ်ကို ဖန်တီးပါ သို့မဟုတ် ပြောင်းပါ။",
398 + "buy_alert_content": "လက်ရှိတွင် ကျွန်ုပ်တို့သည် Bitcoin၊ Ethereum၊ Litecoin နှင့် Monero တို့ကိုသာ ဝယ်ယူမှုကို ပံ့ပိုးပေးပါသည်။ သင်၏ Bitcoin၊ Ethereum၊ Litecoin သို့မဟုတ် Monero ပိုက်ဆံအိတ်ကို ဖန်တီးပါ သို့မဟုတ် ပြောင်းပါ။",
399 + "sell_alert_content": "ကျွန်ုပ်တို့သည် လက်ရှိတွင် Bitcoin၊ Ethereum နှင့် Litecoin ရောင်းချခြင်းကိုသာ ပံ့ပိုးပေးပါသည်။ သင်၏ Bitcoin၊ Ethereum သို့မဟုတ် Litecoin ပိုက်ဆံအိတ်ကို ဖန်တီးပါ သို့မဟုတ် ပြောင်းပါ။",
400 "outdated_electrum_wallet_description": "ယခု Cake တွင်ဖန်တီးထားသော Bitcoin ပိုက်ဆံအိတ်အသစ်တွင် စကားလုံး 24 မျိုးရှိသည်။ Bitcoin ပိုက်ဆံအိတ်အသစ်တစ်ခုကို ဖန်တီးပြီး သင့်ငွေအားလုံးကို 24 စကားလုံးပိုက်ဆံအိတ်အသစ်သို့ လွှဲပြောင်းပြီး 12 စကားလုံးမျိုးစေ့ဖြင့် ပိုက်ဆံအိတ်များကို အသုံးပြုခြင်းကို ရပ်တန့်ရန် မဖြစ်မနေလိုအပ်ပါသည်။ သင့်ရန်ပုံငွေများကို လုံခြုံစေရန်အတွက် ၎င်းကိုချက်ချင်းလုပ်ဆောင်ပါ။",
401 "understand": "ကျွန်တော်နားလည်ပါတယ်",
402 "apk_update": "APK အပ်ဒိတ်",
@@ -647,9 +647,27 @@
647 "available_balance_description": "သင့်ရဲ့ အကောင့်တွင် ရရှိနိုင်သော ငွေကျန်ငွေကို ပြန်လည်ပေးသွင်းပါ။",
648 "syncing_wallet_alert_title": "သင့်ပိုက်ဆံအိတ်ကို စင့်ခ်လုပ်နေပါသည်။",
649 "syncing_wallet_alert_content": "သင်၏လက်ကျန်နှင့် ငွေပေးငွေယူစာရင်းသည် ထိပ်တွင် \"Synchronizeed\" ဟုပြောသည်အထိ မပြီးမြောက်နိုင်ပါ။ ပိုမိုလေ့လာရန် နှိပ်/နှိပ်ပါ။",
650 + "home_screen_settings": "ပင်မစခရင် ဆက်တင်များ",
651 + "sort_by": "အလိုက်စဥ်သည်",
652 + "search_add_token": "ရှာဖွေရန် / တိုကင်ထည့်ပါ။",
653 + "edit_token": "တိုကင်ကို တည်းဖြတ်ပါ။",
654 + "warning": "သတိပေးချက်",
655 + "add_token_warning": "လိမ်လည်သူများ ညွှန်ကြားထားသည့်အတိုင်း တိုကင်များကို တည်းဖြတ်ခြင်း သို့မဟုတ် မထည့်ပါနှင့်။\nဂုဏ်သိက္ခာရှိသော အရင်းအမြစ်များဖြင့် အမြဲတမ်း တိုကင်လိပ်စာများကို အတည်ပြုပါ။",
656 + "add_token_disclaimer_check": "ဂုဏ်သိက္ခာရှိသော အရင်းအမြစ်ကို အသုံးပြု၍ တိုကင်စာချုပ်လိပ်စာနှင့် အချက်အလက်ကို ကျွန်ုပ်အတည်ပြုပြီးဖြစ်သည်။ အန္တရာယ်ရှိသော သို့မဟုတ် မမှန်ကန်သော အချက်အလက်များကို ထည့်သွင်းခြင်းသည် ရန်ပုံငွေများ ဆုံးရှုံးသွားနိုင်သည်။",
657 + "token_contract_address": "တိုကင်စာချုပ်လိပ်စာ",
658 + "token_name": "တိုကင်အမည် ဥပမာ- Tether",
659 + "token_symbol": "တိုကင်သင်္ကေတ ဥပမာ- USDT",
660 + "token_decimal": "တိုကင်ဒဿမ",
661 + "field_required": "ဤစာကွက်လပ်မှာဖြည့်ရန်လိုအပ်ပါသည်",
662 + "pin_at_top": "အပေါ်တွင် ${token} ပင်ထိုးပါ။",
663 + "invalid_input": "ထည့်သွင်းမှု မမှန်ကန်ပါ။",
664 + "fiat_balance": "Fiat Balance",
665 + "gross_balance": "စုစုပေါင်းလက်ကျန်ငွေ",
666 + "alphabetical": "အက္ခရာစဉ်",
667 "generate_name": "အမည်ဖန်တီးပါ။",
668 "balance_page": "လက်ကျန်စာမျက်နှာ",
669 "share": "မျှဝေပါ။",
670 "slidable": "လျှောချနိုင်သည်။",
671 + "etherscan_history": "Etherscan သမိုင်း",
672 "template_name": "နမူနာပုံစံ"
673 }
res/values/strings_nl.arb
+23 -5
@@ -1,7 +1,7 @@
1 {
2 "welcome": "Welkom bij",
3 "cake_wallet": "Cake Wallet",
4 - "first_wallet_text": "Geweldige portemonnee voor Monero, Bitcoin, Litecoin, en Haven",
4 + "first_wallet_text": "Geweldige portemonnee voor Monero, Bitcoin, Ethereum, Litecoin, en Haven",
5 "please_make_selection": "Maak hieronder uw keuze tot maak of herstel je portemonnee.",
6 "create_new": "Maak een nieuwe portemonnee",
7 "restore_wallet": "Portemonnee herstellen",
@@ -250,8 +250,8 @@
250 "transaction_details_recipient_address": "Adressen van ontvangers",
251 "wallet_list_title": "Monero portemonnee",
252 "wallet_list_create_new_wallet": "Maak een nieuwe portemonnee",
253 - "wallet_list_edit_wallet" : "Portemonnee bewerken",
254 - "wallet_list_wallet_name" : "Portemonnee naam",
253 + "wallet_list_edit_wallet": "Portemonnee bewerken",
254 + "wallet_list_wallet_name": "Portemonnee naam",
255 "wallet_list_restore_wallet": "Portemonnee herstellen",
256 "wallet_list_load_wallet": "Portemonnee laden",
257 "wallet_list_loading_wallet": "Bezig met laden ${wallet_name} portemonnee",
@@ -395,8 +395,8 @@
395 "unconfirmed": "Onbevestigd saldo",
396 "displayable": "Weer te geven",
397 "submit_request": "een verzoek indienen",
398 - "buy_alert_content": "Momenteel ondersteunen we alleen de aankoop van Bitcoin, Litecoin en Monero. Maak of schakel over naar uw Bitcoin-, Litecoin- of Monero-portemonnee.",
399 - "sell_alert_content": "We ondersteunen momenteel alleen de verkoop van Bitcoin en Litecoin. Maak of schakel over naar uw Bitcoin- of Litecoin-portemonnee.",
398 + "buy_alert_content": "Momenteel ondersteunen we alleen de aankoop van Bitcoin, Ethereum, Litecoin en Monero. Maak of schakel over naar uw Bitcoin-, Ethereum-, Litecoin- of Monero-portemonnee.",
399 + "sell_alert_content": "We ondersteunen momenteel alleen de verkoop van Bitcoin, Ethereum en Litecoin. Maak of schakel over naar uw Bitcoin-, Ethereum- of Litecoin-portemonnee.",
400 "outdated_electrum_wallet_description": "Nieuwe Bitcoin-portefeuilles die in Cake zijn gemaakt, hebben nu een zaadje van 24 woorden. Het is verplicht dat u een nieuwe Bitcoin-portemonnee maakt en al uw geld overmaakt naar de nieuwe portemonnee van 24 woorden, en stopt met het gebruik van wallets met een seed van 12 woorden. Doe dit onmiddellijk om uw geld veilig te stellen.",
401 "understand": "Ik begrijp het",
402 "apk_update": "APK-update",
@@ -647,9 +647,27 @@
647 "available_balance_description": "Beschikbaar saldo is het saldo dat u kunt uitgeven. Het kan lager zijn dan uw totale saldo als u onlangs geld hebt verzonden.",
648 "syncing_wallet_alert_title": "Uw portemonnee wordt gesynchroniseerd",
649 "syncing_wallet_alert_content": "Uw saldo- en transactielijst is mogelijk pas compleet als er bovenaan 'GESYNCHRONISEERD' staat. Klik/tik voor meer informatie.",
650 + "home_screen_settings": "Instellingen voor het startscherm",
651 + "sort_by": "Sorteer op",
652 + "search_add_token": "Token zoeken / toevoegen",
653 + "edit_token": "Token bewerken",
654 + "warning": "Waarschuwing",
655 + "add_token_warning": "Bewerk of voeg geen tokens toe volgens de instructies van oplichters.\nBevestig tokenadressen altijd met betrouwbare bronnen!",
656 + "add_token_disclaimer_check": "Ik heb het adres en de informatie van het tokencontract bevestigd met behulp van een betrouwbare bron. Het toevoegen van kwaadaardige of onjuiste informatie kan leiden tot verlies van geld.",
657 + "token_contract_address": "Token contractadres",
658 + "token_name": "Tokennaam bijv.: Tether",
659 + "token_symbol": "Tokensymbool bijv.: USDT",
660 + "token_decimal": "Token decimaal",
661 + "field_required": "dit veld is verplicht",
662 + "pin_at_top": "speld ${token} bovenaan",
663 + "invalid_input": "Ongeldige invoer",
664 + "fiat_balance": "Fiat Balans",
665 + "gross_balance": "Bruto saldo",
666 + "alphabetical": "Alfabetisch",
667 "generate_name": "Naam genereren",
668 "balance_page": "Saldo pagina",
669 "share": "Deel",
670 "slidable": "Verschuifbaar",
671 + "etherscan_history": "Etherscan-geschiedenis",
672 "template_name": "Sjabloonnaam"
673 }
res/values/strings_pl.arb
+23 -5
@@ -1,7 +1,7 @@
1 {
2 "welcome": "Witamy w",
3 "cake_wallet": "Cake Wallet",
4 - "first_wallet_text": "Świetny portfel na Monero, Bitcoin, Litecoin, i Haven",
4 + "first_wallet_text": "Świetny portfel na Monero, Bitcoin, Ethereum, Litecoin, i Haven",
5 "please_make_selection": "Wybierz poniżej, aby utworzyć lub przywrócić swój portfel.",
6 "create_new": "Utwórz nowy portfel",
7 "restore_wallet": "Przywróć portfel",
@@ -250,8 +250,8 @@
250 "transaction_details_recipient_address": "Adres odbiorcy",
251 "wallet_list_title": "Portfel Monero",
252 "wallet_list_create_new_wallet": "Utwórz nowy portfel",
253 - "wallet_list_edit_wallet" : "Edytuj portfel",
254 - "wallet_list_wallet_name" : "Nazwa portfela",
253 + "wallet_list_edit_wallet": "Edytuj portfel",
254 + "wallet_list_wallet_name": "Nazwa portfela",
255 "wallet_list_restore_wallet": "Przywróć portfel",
256 "wallet_list_load_wallet": "Załaduj portfel",
257 "wallet_list_loading_wallet": "Ładuję ${wallet_name} portfel",
@@ -395,8 +395,8 @@
395 "unconfirmed": "Niepotwierdzone saldo",
396 "displayable": "Wyświetlane",
397 "submit_request": "Złóż wniosek",
398 - "buy_alert_content": "Obecnie obsługujemy tylko zakup Bitcoin, Litecoin i Monero. Utwórz lub przełącz się na swój portfel Bitcoin, Litecoin lub Monero.",
399 - "sell_alert_content": "Obecnie obsługujemy tylko sprzedaż Bitcoin i Litecoin. Utwórz lub przełącz się na swój portfel Bitcoin lub Litecoin.",
398 + "buy_alert_content": "Obecnie obsługujemy tylko zakup Bitcoin, Ethereum, Litecoin i Monero. Utwórz lub przełącz się na swój portfel Bitcoin, Ethereum, Litecoin lub Monero.",
399 + "sell_alert_content": "Obecnie obsługujemy tylko sprzedaż Bitcoin, Ethereum i Litecoin. Utwórz lub przełącz się na swój portfel Bitcoin, Ethereum lub Litecoin.",
400 "outdated_electrum_wallet_description": "Nowe portfele Bitcoin utworzone w Cake mają teraz fraze seed składające się z 24 słów. Konieczne jest utworzenie nowego portfela Bitcoin i przeniesienie wszystkich środków do nowego portfela na 24 słowa oraz zaprzestanie korzystania z portfeli z frazą seed na 12 słów. Zrób to natychmiast, aby zabezpieczyć swoje fundusze.",
401 "understand": "Rozumiem",
402 "apk_update": "Aktualizacja APK",
@@ -647,9 +647,27 @@
647 "available_balance_description": "Dostępne saldo jest równoważne z saldem portfela minus zamrożone saldo.",
648 "syncing_wallet_alert_title": "Twój portfel się synchronizuje",
649 "syncing_wallet_alert_content": "Twoje saldo i lista transakcji mogą nie być kompletne, dopóki u góry nie pojawi się napis „SYNCHRONIZOWANY”. Kliknij/stuknij, aby dowiedzieć się więcej.",
650 + "home_screen_settings": "Ustawienia ekranu głównego",
651 + "sort_by": "Sortuj według",
652 + "search_add_token": "Wyszukaj / Dodaj token",
653 + "edit_token": "Edytuj token",
654 + "warning": "Ostrzeżenie",
655 + "add_token_warning": "Nie edytuj ani nie dodawaj tokenów zgodnie z instrukcjami oszustów.\nZawsze potwierdzaj adresy tokenów z renomowanymi źródłami!",
656 + "add_token_disclaimer_check": "Potwierdziłem adres kontraktu tokena i informacje, korzystając z renomowanego źródła. Dodanie złośliwych lub niepoprawnych informacji może spowodować utratę środków.",
657 + "token_contract_address": "Adres kontraktu tokena",
658 + "token_name": "Nazwa tokena, np.: Tether",
659 + "token_symbol": "Symbol tokena np.: USDT",
660 + "token_decimal": "Token dziesiętny",
661 + "field_required": "To pole jest wymagane",
662 + "pin_at_top": "przypnij ${token} na górze",
663 + "invalid_input": "Nieprawidłowe dane wejściowe",
664 + "fiat_balance": "Bilans Fiata",
665 + "gross_balance": "Saldo brutto",
666 + "alphabetical": "Alfabetyczny",
667 "generate_name": "Wygeneruj nazwę",
668 "balance_page": "Strona salda",
669 "share": "Udział",
670 "slidable": "Przesuwne",
671 + "etherscan_history": "Historia Etherscanu",
672 "template_name": "Nazwa szablonu"
673 }
res/values/strings_pt.arb
+23 -5
@@ -1,7 +1,7 @@
1 {
2 "welcome": "Bem-vindo ao",
3 "cake_wallet": "Cake Wallet",
4 - "first_wallet_text": "Carteira incrível para Monero, Bitcoin, Litecoin, e Haven",
4 + "first_wallet_text": "Carteira incrível para Monero, Bitcoin, Ethereum, Litecoin, e Haven",
5 "please_make_selection": "Escolha se quer criar uma carteira nova ou restaurar uma antiga.",
6 "create_new": "Criar nova carteira",
7 "restore_wallet": "Restaurar carteira",
@@ -250,8 +250,8 @@
250 "transaction_details_recipient_address": "Endereços de destinatários",
251 "wallet_list_title": "Carteira Monero",
252 "wallet_list_create_new_wallet": "Criar nova carteira",
253 - "wallet_list_edit_wallet" : "Editar carteira",
254 - "wallet_list_wallet_name" : "Nome da carteira",
253 + "wallet_list_edit_wallet": "Editar carteira",
254 + "wallet_list_wallet_name": "Nome da carteira",
255 "wallet_list_restore_wallet": "Restaurar carteira",
256 "wallet_list_load_wallet": "Abrir carteira",
257 "wallet_list_loading_wallet": "Abrindo a carteira ${wallet_name}",
@@ -395,8 +395,8 @@
395 "unconfirmed": "Saldo não confirmado",
396 "displayable": "Exibível",
397 "submit_request": "enviar um pedido",
398 - "buy_alert_content": "Atualmente, oferecemos suporte apenas à compra de Bitcoin, Litecoin e Monero. Crie ou troque para sua carteira Bitcoin, Litecoin ou Monero.",
399 - "sell_alert_content": "Atualmente, oferecemos suporte apenas à venda de Bitcoin e Litecoin. Por favor, crie ou mude para sua carteira Bitcoin ou Litecoin.",
398 + "buy_alert_content": "Atualmente, oferecemos suporte apenas à compra de Bitcoin, Ethereum, Litecoin e Monero. Crie ou troque para sua carteira Bitcoin, Ethereum, Litecoin ou Monero.",
399 + "sell_alert_content": "Atualmente, oferecemos suporte apenas à venda de Bitcoin, Ethereum e Litecoin. Crie ou troque para sua carteira Bitcoin, Ethereum ou Litecoin.",
400 "outdated_electrum_wallet_description": "As novas carteiras Bitcoin criadas no Cake agora têm uma semente de 24 palavras. É obrigatório que você crie uma nova carteira Bitcoin e transfira todos os seus fundos para a nova carteira de 24 palavras, e pare de usar carteiras com semente de 12 palavras. Faça isso imediatamente para garantir seus fundos.",
401 "understand": "Entendo",
402 "apk_update": "Atualização de APK",
@@ -646,9 +646,27 @@
646 "available_balance_description": "Seu saldo disponível é o saldo total menos o saldo congelado. O saldo congelado é o saldo que você não pode gastar, mas que ainda não foi confirmado na blockchain. O saldo congelado é geralmente o resultado de transações recentes.",
647 "syncing_wallet_alert_title": "Sua carteira está sincronizando",
648 "syncing_wallet_alert_content": "Seu saldo e lista de transações podem não estar completos até que diga “SYNCHRONIZED” no topo. Clique/toque para saber mais.",
649 + "home_screen_settings": "Configurações da tela inicial",
650 + "sort_by": "Ordenar por",
651 + "search_add_token": "Pesquisar / Adicionar token",
652 + "edit_token": "Editar símbolo",
653 + "warning": "Aviso",
654 + "add_token_warning": "Não edite ou adicione tokens de acordo com as instruções dos golpistas.\nSempre confirme os endereços de token com fontes confiáveis!",
655 + "add_token_disclaimer_check": "Confirmei o endereço e as informações do contrato de token usando uma fonte confiável. Adicionar informações maliciosas ou incorretas pode resultar em perda de fundos.",
656 + "token_contract_address": "Endereço do contrato de token",
657 + "token_name": "Nome do token, por exemplo: Tether",
658 + "token_symbol": "Símbolo de token, por exemplo: USDT",
659 + "token_decimal": "Token decimal",
660 + "field_required": "Este campo é obrigatório",
661 + "pin_at_top": "fixe ${token} no topo",
662 + "invalid_input": "Entrada inválida",
663 + "fiat_balance": "Equilíbrio Fiat",
664 + "gross_balance": "Saldo Bruto",
665 + "alphabetical": "alfabética",
666 "generate_name": "Gerar nome",
667 "balance_page": "Página de saldo",
668 "share": "Compartilhar",
669 "slidable": "Deslizável",
670 + "etherscan_history": "história Etherscan",
671 "template_name": "Nome do modelo"
672 }
res/values/strings_ru.arb
+23 -5
@@ -1,7 +1,7 @@
1 {
2 "welcome": "Приветствуем в",
3 "cake_wallet": "Cake Wallet",
4 - "first_wallet_text": "В самом удобном кошельке для Monero, Bitcoin, Litecoin, и Haven",
4 + "first_wallet_text": "В самом удобном кошельке для Monero, Bitcoin, Ethereum, Litecoin, и Haven",
5 "please_make_selection": "Выберите способ создания кошелька: создать новый или восстановить ваш существующий.",
6 "create_new": "Создать новый кошелёк",
7 "restore_wallet": "Восстановить кошелёк",
@@ -250,8 +250,8 @@
250 "transaction_details_recipient_address": "Адреса получателей",
251 "wallet_list_title": "Monero Кошелёк",
252 "wallet_list_create_new_wallet": "Создать новый кошелёк",
253 - "wallet_list_edit_wallet" : "Изменить кошелек",
254 - "wallet_list_wallet_name" : "Имя кошелька",
253 + "wallet_list_edit_wallet": "Изменить кошелек",
254 + "wallet_list_wallet_name": "Имя кошелька",
255 "wallet_list_restore_wallet": "Восстановить кошелёк",
256 "wallet_list_load_wallet": "Загрузка кошелька",
257 "wallet_list_loading_wallet": "Загрузка ${wallet_name} кошелька",
@@ -396,8 +396,8 @@
396 "unconfirmed": "Неподтвержденный баланс",
397 "displayable": "Отображаемый",
398 "submit_request": "отправить запрос",
399 - "buy_alert_content": "В настоящее время мы поддерживаем только покупку Bitcoin, Litecoin и Monero. Пожалуйста, создайте или переключитесь на свой кошелек Bitcoin, Litecoin или Monero.",
400 - "sell_alert_content": "В настоящее время мы поддерживаем только продажу биткойнов и лайткойнов. Пожалуйста, создайте или переключитесь на свой биткойн- или лайткойн-кошелек.",
399 + "buy_alert_content": "В настоящее время мы поддерживаем только покупку биткойнов, Ethereum, Litecoin и Monero. Пожалуйста, создайте или переключитесь на свой кошелек Bitcoin, Ethereum, Litecoin или Monero.",
400 + "sell_alert_content": "В настоящее время мы поддерживаем только продажу биткойнов, эфириума и лайткойна. Пожалуйста, создайте или переключитесь на свой кошелек Bitcoin, Ethereum или Litecoin.",
401 "outdated_electrum_wallet_description": "Новые биткойн-кошельки, созданные в Cake, теперь содержат мнемоническую фразу из 24 слов. Вы обязательно должны создать новый биткойн-кошелек и перевести все свои средства в новый кошелек из 24 слов, а также прекратить использование кошельков с мнемонической фразой из 12 слов. Пожалуйста, сделайте это немедленно, чтобы обезопасить свои средства.",
402 "understand": "Понятно",
403 "apk_update": "Обновление APK",
@@ -648,9 +648,27 @@
648 "available_balance_description": "Доступный баланс - это средства, которые вы можете использовать для покупки или продажи криптовалюты.",
649 "syncing_wallet_alert_title": "Ваш кошелек синхронизируется",
650 "syncing_wallet_alert_content": "Ваш баланс и список транзакций могут быть неполными, пока вверху не будет написано «СИНХРОНИЗИРОВАНО». Щелкните/коснитесь, чтобы узнать больше.",
651 + "home_screen_settings": "Настройки главного экрана",
652 + "sort_by": "Сортировать по",
653 + "search_add_token": "Поиск / Добавить токен",
654 + "edit_token": "Изменить токен",
655 + "warning": "Предупреждение",
656 + "add_token_warning": "Не редактируйте и не добавляйте токены по указанию мошенников.\nВсегда подтверждайте адреса токенов из авторитетных источников!",
657 + "add_token_disclaimer_check": "Я подтвердил адрес контракта токена и информацию, используя авторитетный источник. Добавление вредоносной или неверной информации может привести к потере средств.",
658 + "token_contract_address": "Адрес контракта токена",
659 + "token_name": "Имя токена, например: Tether",
660 + "token_symbol": "Символ токена, например: USDT",
661 + "token_decimal": "Десятичный токен",
662 + "field_required": "Это поле обязательно к заполнению",
663 + "pin_at_top": "закрепить ${token} вверху",
664 + "invalid_input": "Неверный Ввод",
665 + "fiat_balance": "Фиатный баланс",
666 + "gross_balance": "Валовой баланс",
667 + "alphabetical": "Алфавитный",
668 "generate_name": "Создать имя",
669 "balance_page": "Страница баланса",
670 "share": "Делиться",
671 "slidable": "Скользящий",
672 + "etherscan_history": "История Эфириума",
673 "template_name": "Имя Шаблона"
674 }
res/values/strings_th.arb
+23 -5
@@ -1,7 +1,7 @@
1 {
2 "welcome": "ยินดีต้อนรับสู่",
3 "cake_wallet": "Cake Wallet",
4 - "first_wallet_text": "กระเป๋าสตางค์ที่สวยงามสำหรับ Monero, Bitcoin, Litecoin และ Haven",
4 + "first_wallet_text": "กระเป๋าสตางค์ที่สวยงามสำหรับ Monero, Bitcoin, Ethereum, Litecoin และ Haven",
5 "please_make_selection": "โปรดเลือกตามด้านล่างเพื่อสร้างหรือกู้กระเป๋าของคุณ",
6 "create_new": "สร้างกระเป๋าใหม่",
7 "restore_wallet": "กู้กระเป๋า",
@@ -250,8 +250,8 @@
250 "transaction_details_recipient_address": "ที่อยู่ผู้รับ",
251 "wallet_list_title": "กระเป๋า Monero",
252 "wallet_list_create_new_wallet": "สร้างกระเป๋าใหม่",
253 - "wallet_list_edit_wallet" : "แก้ไขกระเป๋าสตางค์",
254 - "wallet_list_wallet_name" : "ชื่อกระเป๋าสตางค์",
253 + "wallet_list_edit_wallet": "แก้ไขกระเป๋าสตางค์",
254 + "wallet_list_wallet_name": "ชื่อกระเป๋าสตางค์",
255 "wallet_list_restore_wallet": "กู้กระเป๋า",
256 "wallet_list_load_wallet": "โหลดกระเป๋า",
257 "wallet_list_loading_wallet": "กำลังโหลดกระเป๋า ${wallet_name}",
@@ -395,8 +395,8 @@
395 "unconfirmed": "ยอดคงเหลือที่ไม่ได้รับการยืนยัน",
396 "displayable": "สามารถแสดงได้",
397 "submit_request": "ส่งคำขอ",
398 - "buy_alert_content": "ขณะนี้เรารองรับการซื้อ Bitcoin, Litecoin และ Monero เท่านั้น โปรดสร้างหรือเปลี่ยนเป็นกระเป๋าเงิน Bitcoin, Litecoin หรือ Monero ของคุณ",
399 - "sell_alert_content": "ขณะนี้เราสนับสนุนการขาย Bitcoin และ Litecoin เท่านั้น โปรดสร้างหรือเปลี่ยนเป็นกระเป๋าเงิน Bitcoin หรือ Litecoin ของคุณ",
398 + "buy_alert_content": "ขณะนี้เรารองรับการซื้อ Bitcoin, Ethereum, Litecoin และ Monero เท่านั้น โปรดสร้างหรือเปลี่ยนเป็นกระเป๋าเงิน Bitcoin, Ethereum, Litecoin หรือ Monero",
399 + "sell_alert_content": "ขณะนี้เรารองรับการขาย Bitcoin, Ethereum และ Litecoin เท่านั้น โปรดสร้างหรือเปลี่ยนเป็นกระเป๋าเงิน Bitcoin, Ethereum หรือ Litecoin ของคุณ",
400 "outdated_electrum_wallet_description": "กระเป๋า Bitcoin ใหม่ที่สร้างใน Cake มี seed ขนาด 24 คำ ซึ่งจำเป็นต้องสร้างกระเป๋า Bitcoin ใหม่และโอนทุกเงินของคุณไปยังกระเป๋าใหม่ขนาด 24 คำ และหยุดใช้กระเป๋าที่มี seed ขนาด 12 คำ กรุณาทำด่วนเพื่อรักษาเงินของคุณ",
401 "understand": "ฉันเข้าใจ",
402 "apk_update": "ปรับปรุง APK",
@@ -647,9 +647,27 @@
647 "available_balance_description": "จำนวนเงินที่คุณสามารถใช้ได้ในการซื้อหรือขาย",
648 "syncing_wallet_alert_title": "กระเป๋าสตางค์ของคุณกำลังซิงค์",
649 "syncing_wallet_alert_content": "รายการยอดเงินและธุรกรรมของคุณอาจไม่สมบูรณ์จนกว่าจะมีข้อความว่า “ซิงโครไนซ์” ที่ด้านบน คลิก/แตะเพื่อเรียนรู้เพิ่มเติม่",
650 + "home_screen_settings": "การตั้งค่าหน้าจอหลัก",
651 + "sort_by": "เรียงตาม",
652 + "search_add_token": "ค้นหา / เพิ่มโทเค็น",
653 + "edit_token": "แก้ไขโทเค็น",
654 + "warning": "คำเตือน",
655 + "add_token_warning": "ห้ามแก้ไขหรือเพิ่มโทเค็นตามคำแนะนำของนักต้มตุ๋น\nยืนยันที่อยู่โทเค็นกับแหล่งที่มาที่เชื่อถือได้เสมอ!",
656 + "add_token_disclaimer_check": "ฉันได้ยืนยันที่อยู่และข้อมูลของสัญญาโทเค็นโดยใช้แหล่งข้อมูลที่เชื่อถือได้ การเพิ่มข้อมูลที่เป็นอันตรายหรือไม่ถูกต้องอาจทำให้สูญเสียเงินได้",
657 + "token_contract_address": "ที่อยู่สัญญาโทเค็น",
658 + "token_name": "ชื่อโทเค็น เช่น Tether",
659 + "token_symbol": "สัญลักษณ์โทเค็น เช่น USDT",
660 + "token_decimal": "โทเค็นทศนิยม",
661 + "field_required": "ช่องนี้จำเป็น",
662 + "pin_at_top": "ปักหมุด ${token} ที่ด้านบน",
663 + "invalid_input": "อินพุตไม่ถูกต้อง",
664 + "fiat_balance": "เฟียต บาลานซ์",
665 + "gross_balance": "ยอดคงเหลือ",
666 + "alphabetical": "ตามตัวอักษร",
667 "generate_name": "สร้างชื่อ",
668 "balance_page": "หน้ายอดคงเหลือ",
669 "share": "แบ่งปัน",
670 "slidable": "เลื่อนได้",
671 + "etherscan_history": "ประวัติอีเธอร์สแกน",
672 "template_name": "ชื่อแม่แบบ"
673 }
res/values/strings_tr.arb
+23 -5
@@ -1,7 +1,7 @@
1 {
2 "welcome": "Hoş Geldiniz",
3 "cake_wallet": "Cake Wallet",
4 - "first_wallet_text": "Monero, Bitcoin, Litecoin ve Haven için harika cüzdan",
4 + "first_wallet_text": "Monero, Bitcoin, Ethereum, Litecoin ve Haven için harika cüzdan",
5 "please_make_selection": "Cüzdan oluşturmak veya geri döndürmek için aşağıdan seçim yap.",
6 "create_new": "Yeni Cüzdan Oluştur",
7 "restore_wallet": "Cüzdanı Geri Döndür",
@@ -250,8 +250,8 @@
250 "transaction_details_recipient_address": "Alıcı adres",
251 "wallet_list_title": "Monero Cüzdanı",
252 "wallet_list_create_new_wallet": "Yeni Cüzdan Oluştur",
253 - "wallet_list_edit_wallet" : "Cüzdanı düzenle",
254 - "wallet_list_wallet_name" : "Cüzdan adı",
253 + "wallet_list_edit_wallet": "Cüzdanı düzenle",
254 + "wallet_list_wallet_name": "Cüzdan adı",
255 "wallet_list_restore_wallet": "Cüzdanı Geri Yükle",
256 "wallet_list_load_wallet": "Cüzdanı yükle",
257 "wallet_list_loading_wallet": "${wallet_name} cüzdanı yükleniyor",
@@ -395,8 +395,8 @@
395 "unconfirmed": "Onaylanmamış Bakiye",
396 "displayable": "Gösterilebilir",
397 "submit_request": "talep gönder",
398 - "buy_alert_content": "Şu anda yalnızca Bitcoin, Litecoin ve Monero satın alımını destekliyoruz. Lütfen Bitcoin, Litecoin veya Monero cüzdanınızı oluşturun veya cüzdanınıza geçiş yapın.",
399 - "sell_alert_content": "Şu anda yalnızca Bitcoin ve Litecoin satışını destekliyoruz. Lütfen Bitcoin veya Litecoin cüzdanınızı oluşturun veya cüzdanınıza geçiş yapın.",
398 + "buy_alert_content": "Şu anda yalnızca Bitcoin, Ethereum, Litecoin ve Monero satın alımını destekliyoruz. Lütfen Bitcoin, Ethereum, Litecoin veya Monero cüzdanınızı oluşturun veya cüzdanınıza geçin.",
399 + "sell_alert_content": "Şu anda yalnızca Bitcoin, Ethereum ve Litecoin satışını destekliyoruz. Lütfen Bitcoin, Ethereum veya Litecoin cüzdanınızı oluşturun veya cüzdanınıza geçin.",
400 "outdated_electrum_wallet_description": "Cake'te oluşturulan yeni Bitcoin cüzdanları artık 24 kelimelik bir tohuma sahip. Yeni bir Bitcoin cüzdanı oluşturmanız ve tüm paranızı 24 kelimelik yeni cüzdana aktarmanız ve 12 kelimelik tohuma sahip cüzdanları kullanmayı bırakmanız zorunludur. Lütfen paranızı güvence altına almak için bunu hemen yapın.",
401 "understand": "Anladım",
402 "apk_update": "APK güncellemesi",
@@ -648,9 +648,27 @@
648 "available_balance_description": "Bu, cüzdanınızda harcayabileceğiniz miktar. Bu miktar, cüzdanınızdan çekilebilecek toplam bakiyeden daha düşük olabilir, çünkü bazı fonlar henüz kullanılamaz durumda olabilir.",
649 "syncing_wallet_alert_title": "Cüzdanınız senkronize ediliyor",
650 "syncing_wallet_alert_content": "Bakiyeniz ve işlem listeniz, en üstte \"SENKRONİZE EDİLDİ\" yazana kadar tamamlanmamış olabilir. Daha fazla bilgi edinmek için tıklayın/dokunun.",
651 + "home_screen_settings": "Ana ekran ayarları",
652 + "sort_by": "Göre sırala",
653 + "search_add_token": "Belirteç Ara / Ekle",
654 + "edit_token": "Belirteci düzenle",
655 + "warning": "Uyarı",
656 + "add_token_warning": "Dolandırıcıların talimatına göre jetonları düzenlemeyin veya eklemeyin.\nBelirteç adreslerini her zaman saygın kaynaklarla onaylayın!",
657 + "add_token_disclaimer_check": "Belirteç sözleşmesi adresini ve bilgilerini saygın bir kaynak kullanarak onayladım. Kötü amaçlı veya yanlış bilgilerin eklenmesi para kaybına neden olabilir.",
658 + "token_contract_address": "Token sözleşme adresi",
659 + "token_name": "Belirteç adı, örneğin: Tether",
660 + "token_symbol": "Jeton sembolü, örneğin: USDT",
661 + "token_decimal": "Belirteç ondalık",
662 + "field_required": "Bu alan gereklidir",
663 + "pin_at_top": "${token} üstte sabitle",
664 + "invalid_input": "Geçersiz Giriş",
665 + "fiat_balance": "Fiat Bakiyesi",
666 + "gross_balance": "Brüt Bakiye",
667 + "alphabetical": "Alfabetik",
668 "generate_name": "İsim Oluştur",
669 "balance_page": "Bakiye Sayfası",
670 "share": "Paylaşmak",
671 "slidable": "kaydırılabilir",
672 + "etherscan_history": "Etherscan geçmişi",
673 "template_name": "şablon adı"
674 }
res/values/strings_uk.arb
+23 -5
@@ -1,7 +1,7 @@
1 {
2 "welcome": "Вітаємо в",
3 "cake_wallet": "Cake Wallet",
4 - "first_wallet_text": "В самому зручному гаманці для Monero, Bitcoin, Litecoin, та Haven",
4 + "first_wallet_text": "В самому зручному гаманці для Monero, Bitcoin, Ethereum, Litecoin, та Haven",
5 "please_make_selection": "Оберіть спосіб створення гаманця: створити новий чи відновити ваш існуючий.",
6 "create_new": "Створити новий гаманець",
7 "restore_wallet": "Відновити гаманець",
@@ -250,8 +250,8 @@
250 "transaction_details_recipient_address": "Адреси одержувачів",
251 "wallet_list_title": "Monero Гаманець",
252 "wallet_list_create_new_wallet": "Створити новий гаманець",
253 - "wallet_list_edit_wallet" : "Редагувати гаманець",
254 - "wallet_list_wallet_name" : "Назва гаманця",
253 + "wallet_list_edit_wallet": "Редагувати гаманець",
254 + "wallet_list_wallet_name": "Назва гаманця",
255 "wallet_list_restore_wallet": "Відновити гаманець",
256 "wallet_list_load_wallet": "Завантаження гаманця",
257 "wallet_list_loading_wallet": "Завантаження ${wallet_name} гаманця",
@@ -395,8 +395,8 @@
395 "unconfirmed": "Непідтверджений баланс",
396 "displayable": "Відображуваний",
397 "submit_request": "надіслати запит",
398 - "buy_alert_content": "Наразі ми підтримуємо лише придбання Bitcoin, Litecoin і Monero. Створіть або перейдіть на свій гаманець Bitcoin, Litecoin або Monero.",
399 - "sell_alert_content": "Зараз ми підтримуємо лише продаж біткойнів і лайткоінів. Будь ласка, створіть або перейдіть на свій гаманець Bitcoin або Litecoin.",
398 + "buy_alert_content": "Наразі ми підтримуємо купівлю лише Bitcoin, Ethereum, Litecoin і Monero. Створіть або перейдіть на свій гаманець Bitcoin, Ethereum, Litecoin або Monero.",
399 + "sell_alert_content": "Наразі ми підтримуємо лише продаж Bitcoin, Ethereum і Litecoin. Створіть або перейдіть на свій гаманець Bitcoin, Ethereum або Litecoin.",
400 "outdated_electrum_wallet_description": "Нові біткойн-гаманці, створені в Cake, тепер містять мнемонічну фразу з 24 слів. Обов’язково стовріть новий біткойн-гаманець, переведіть всі кошти на новий гаманець із 24 слів і припиніть використання гаманців із мнемонічною фразою з 12 слів. Зробіть це негайно, щоб убезпечити свої кошти.",
401 "understand": "Зрозуміло",
402 "apk_update": "Оновлення APK",
@@ -647,9 +647,27 @@
647 "available_balance_description": "Це сума, яку ви можете витратити, не включаючи невизначені кошти. Це може бути менше, ніж загальний баланс, якщо ви витратили кошти, які ще не підтверджені.",
648 "syncing_wallet_alert_title": "Ваш гаманець синхронізується",
649 "syncing_wallet_alert_content": "Ваш баланс та список транзакцій може бути неповним, доки вгорі не буде написано «СИНХРОНІЗОВАНО». Натисніть/торкніться, щоб дізнатися більше.",
650 + "home_screen_settings": "Налаштування головного екрана",
651 + "sort_by": "Сортувати за",
652 + "search_add_token": "Пошук / Додати маркер",
653 + "edit_token": "Редагувати маркер",
654 + "warning": "УВАГА",
655 + "add_token_warning": "Не редагуйте та не додавайте токени за вказівками шахраїв.\nЗавжди підтверджуйте адреси токенів у авторитетних джерелах!",
656 + "add_token_disclaimer_check": "Я підтвердив адресу та інформацію щодо договору маркера, використовуючи авторитетне джерело. Додавання зловмисної або невірної інформації може призвести до втрати коштів.",
657 + "token_contract_address": "Адреса договору маркера",
658 + "token_name": "Назва токена, наприклад: Tether",
659 + "token_symbol": "Символ маркера, наприклад: USDT",
660 + "token_decimal": "Токен десятковий",
661 + "field_required": "Це поле є обов'язковим",
662 + "pin_at_top": "закріпити ${token} зверху",
663 + "invalid_input": "Неправильні дані",
664 + "fiat_balance": "Фіат Баланс",
665 + "gross_balance": "Валовий баланс",
666 + "alphabetical": "Алфавітний",
667 "generate_name": "Згенерувати назву",
668 "balance_page": "Сторінка балансу",
669 "share": "Поділіться",
670 "slidable": "Розсувний",
671 + "etherscan_history": "Історія Etherscan",
672 "template_name": "Назва шаблону"
673 }
res/values/strings_ur.arb
+23 -5
@@ -1,7 +1,7 @@
1 {
2 "welcome": "میں خوش آمدید",
3 "cake_wallet": "Cake والیٹ",
4 - "first_wallet_text": "Monero، Bitcoin، Litecoin، اور Haven کے لیے زبردست پرس",
4 + "first_wallet_text": "Monero، Bitcoin، Ethereum، Litecoin، اور Haven کے لیے زبردست پرس",
5 "please_make_selection": "اپنا بٹوہ بنانے یا بازیافت کرنے کے لیے براہ کرم ذیل میں ایک انتخاب کریں۔",
6 "create_new": "نیا والیٹ بنائیں",
7 "restore_wallet": "والیٹ کو بحال کریں۔",
@@ -250,8 +250,8 @@
250 "transaction_details_recipient_address": "وصول کنندگان کے پتے",
251 "wallet_list_title": "Monero والیٹ",
252 "wallet_list_create_new_wallet": "نیا والیٹ بنائیں",
253 - "wallet_list_edit_wallet" : "بٹوے میں ترمیم کریں۔",
254 - "wallet_list_wallet_name" : "بٹوے کا نام",
253 + "wallet_list_edit_wallet": "بٹوے میں ترمیم کریں۔",
254 + "wallet_list_wallet_name": "بٹوے کا نام",
255 "wallet_list_restore_wallet": "والیٹ کو بحال کریں۔",
256 "wallet_list_load_wallet": "پرس لوڈ کریں۔",
257 "wallet_list_loading_wallet": "${wallet_name} والیٹ لوڈ ہو رہا ہے۔",
@@ -396,8 +396,8 @@
396 "unconfirmed": "غیر تصدیق شدہ بیلنس",
397 "displayable": "قابل نمائش",
398 "submit_request": "درخواست بھیج دو",
399 - "buy_alert_content": "فی الحال ہم صرف Bitcoin، Litecoin، اور Monero کی خریداری کی حمایت کرتے ہیں۔ براہ کرم اپنا Bitcoin، Litecoin، یا Monero والیٹ بنائیں یا اس پر سوئچ کریں۔",
400 - "sell_alert_content": "ہم فی الحال صرف Bitcoin اور Litecoin کی فروخت کی حمایت کرتے ہیں۔ براہ کرم اپنا Bitcoin یا Litecoin والیٹ بنائیں یا اس پر سوئچ کریں۔",
399 + "buy_alert_content": "۔ﮟﯾﺮﮐ ﭻﺋﻮﺳ ﺮﭘ ﺱﺍ ﺎﯾ ﮟﯿﺋﺎﻨﺑ ﭧﯿﻟﺍﻭ Monero ﺎﯾ ،Bitcoin، Ethereum، Litecoin ﺎﻨﭘﺍ ﻡ",
400 + "sell_alert_content": "۔ﮟﯾﺮﮐ ﭻﺋﻮﺳ ﺮﭘ ﺱﺍ ﺎﯾ ﮟﯿﺋﺎﻨﺑ ﭧﯿﻟﺍﻭ Litecoin ﺎﯾ Bitcoin، Ethereum ﺎﻨﭘﺍ ﻡﺮﮐ ﮦﺍﺮﺑ ۔",
401 "outdated_electrum_wallet_description": "Cake میں بنائے گئے نئے Bitcoin بٹوے میں اب 24 الفاظ کا بیج ہے۔ یہ لازمی ہے کہ آپ ایک نیا Bitcoin والیٹ بنائیں اور اپنے تمام فنڈز کو نئے 24 الفاظ والے والیٹ میں منتقل کریں، اور 12 الفاظ کے بیج والے بٹوے کا استعمال بند کریں۔ براہ کرم اپنے فنڈز کو محفوظ بنانے کے لیے فوری طور پر ایسا کریں۔",
402 "understand": "میں سمجھتا ہوں۔",
403 "apk_update": "APK اپ ڈیٹ",
@@ -641,9 +641,27 @@
641 "available_balance_description": "”دستیاب بیلنس” یا ”تصدیق شدہ بیلنس” وہ فنڈز ہیں جو فوری طور پر خرچ کیے جا سکتے ہیں۔ اگر فنڈز کم بیلنس میں ظاہر ہوتے ہیں لیکن اوپر کے بیلنس میں نہیں، تو آپ کو مزید نیٹ ورک کی تصدیقات حاصل کرنے کے لیے آنے والے فنڈز کے لیے چند منٹ انتظار کرنا چاہیے۔ مزید تصدیق حاصل کرنے کے بعد، وہ قابل خرچ ہوں گے۔",
642 "syncing_wallet_alert_title": "آپ کا بٹوہ مطابقت پذیر ہو رہا ہے۔",
643 "syncing_wallet_alert_content": "آپ کے بیلنس اور لین دین کی فہرست اس وقت تک مکمل نہیں ہو سکتی جب تک کہ یہ سب سے اوپر \"SYNCRONIZED\" نہ کہے۔ مزید جاننے کے لیے کلک/تھپتھپائیں۔",
644 + "home_screen_settings": "ہوم اسکرین کی ترتیبات",
645 + "sort_by": "ترتیب دیں",
646 + "search_add_token": "تلاش کریں / ٹوکن شامل کریں۔",
647 + "edit_token": "ٹوکن میں ترمیم کریں۔",
648 + "warning": "وارننگ",
649 + "add_token_warning": "سکیمرز کی ہدایت کے مطابق ٹوکن میں ترمیم یا اضافہ نہ کریں۔\nہمیشہ معتبر ذرائع سے ٹوکن پتوں کی تصدیق کریں!",
650 + "add_token_disclaimer_check": "میں نے ایک معتبر ذریعہ کا استعمال کرتے ہوئے ٹوکن کنٹریکٹ ایڈریس اور معلومات کی تصدیق کی ہے۔ بدنیتی پر مبنی یا غلط معلومات شامل کرنے کے نتیجے میں فنڈز ضائع ہو سکتے ہیں۔",
651 + "token_contract_address": "ٹوکن کنٹریکٹ ایڈریس",
652 + "token_name": "ٹوکن کا نام جیسے: Tether",
653 + "token_symbol": "ٹوکن کی علامت جیسے: USDT",
654 + "token_decimal": "ٹوکن اعشاریہ",
655 + "field_required": "اس کو پر کرنا ضروری ہے",
656 + "pin_at_top": "اوپر ${token} کو پن کریں۔",
657 + "invalid_input": "غلط ان پٹ",
658 + "fiat_balance": "فیاٹ بیلنس",
659 + "gross_balance": "مجموعی بیلنس",
660 + "alphabetical": "حروف تہجی کے مطابق",
661 "generate_name": "نام پیدا کریں۔",
662 "balance_page": "بیلنس صفحہ",
663 "share": "بانٹیں",
664 "slidable": "سلائیڈ ایبل",
665 + "etherscan_history": "ﺦﯾﺭﺎﺗ ﯽﮐ ﻦﯿﮑﺳﺍ ﺮﮭﺘﯾﺍ",
666 "template_name": "ٹیمپلیٹ کا نام"
667 }
res/values/strings_yo.arb
+23 -5
@@ -1,7 +1,7 @@
1 {
2 "welcome": "Ẹ káàbọ sí",
3 "cake_wallet": "Cake Wallet",
4 - "first_wallet_text": "Àpamọ́wọ́ t'á fi Monero, Bitcoin, Litecoin, àti Haven pamọ́ wà pa",
4 + "first_wallet_text": "Àpamọ́wọ́ t'á fi Monero, Bitcoin, Ethereum, Litecoin, àti Haven pamọ́ wà pa",
5 "please_make_selection": "Ẹ jọ̀wọ́, yàn dá àpamọ́wọ́ yín tàbí dá àpamọ́wọ́ yín padà n’ísàlẹ̀.",
6 "create_new": "Dá àpamọ́wọ́ tuntun",
7 "restore_wallet": "Mú àpamọ́wọ́ padà",
@@ -250,8 +250,8 @@
250 "transaction_details_recipient_address": "Àwọn àdírẹ́sì olùgbà",
251 "wallet_list_title": "Àpamọ́wọ́ Monero",
252 "wallet_list_create_new_wallet": "Ṣe àpamọ́wọ́ títun",
253 - "wallet_list_edit_wallet" : "Ṣatunkọ apamọwọ",
254 - "wallet_list_wallet_name" : "Orukọ apamọwọ",
253 + "wallet_list_edit_wallet": "Ṣatunkọ apamọwọ",
254 + "wallet_list_wallet_name": "Orukọ apamọwọ",
255 "wallet_list_restore_wallet": "Restore àpamọ́wọ́",
256 "wallet_list_load_wallet": "Load àpamọ́wọ́",
257 "wallet_list_loading_wallet": "Ń ṣí àpamọ́wọ́ ${wallet_name}",
@@ -393,8 +393,8 @@
393 "unconfirmed": "A kò tí ì jẹ́rìí ẹ̀",
394 "displayable": "A lè ṣàfihàn ẹ̀",
395 "submit_request": "Ṣé ìbéèrè",
396 - "buy_alert_content": "A jẹ́ kí ríra Bitcoin àti Litecoin nìkan. Ẹ jọ̀wọ́ dá tàbí sún àpamọ́wọ́ ti Bitcoin yín tàbí àpamọ́wọ́ ti Litecoin yín mọ́, t'ẹ́ bá fẹ́ ra Bitcoin tàbí Litecoin.",
397 - "sell_alert_content": "Lọwọlọwọ a ṣe atilẹyin tita Bitcoin ati Litecoin nikan. Jọwọ ṣẹda tabi yipada si Bitcoin tabi apamọwọ Litecoin rẹ.",
396 + "buy_alert_content": "Lọwọlọwọ a ṣe atilẹyin rira Bitcoin, Ethereum, Litecoin, ati Monero. Jọwọ ṣẹda tabi yipada si Bitcoin, Ethereum, Litecoin, tabi apamọwọ Monero.",
397 + "sell_alert_content": "Lọwọlọwọ a ṣe atilẹyin tita Bitcoin, Ethereum ati Litecoin nikan. Jọwọ ṣẹda tabi yipada si Bitcoin, Ethereum tabi apamọwọ Litecoin rẹ.",
398 "outdated_electrum_wallet_description": "Àwọn àpamọ́wọ́ títun Bitcoin ti a ti dá nínú Cake Wallet lọ́wọ́lọ́wọ́. Àwọn àpamọ́wọ́ títun t'á dá nínú Cake Wallet ni hóró tó ní ọ̀rọ̀ mẹ́rinlélógún. Ẹ gbọ́dọ̀ dá àpamọ́wọ́. Ẹ sì sún gbogbo owó yín sí àpamọ́wọ́ títun náà tó dá lórí ọ̀rọ̀ mẹ́rinlélógún. Ẹ sì gbọ́dọ̀ yé lo àwọn àpamọ́wọ́ tó dá lórí hóró tó ní ọ̀rọ̀ méjìlá. Ẹ jọ̀wọ́ ṣe èyí láìpẹ́ kí ẹ ba owó yín.",
399 "understand": "Ó ye mi",
400 "apk_update": "Àtúnse áàpù títun wà",
@@ -643,9 +643,27 @@
643 "available_balance_description": "“Iwọntunwọnsi Wa” tabi “Iwọntunwọnsi Ijẹrisi” jẹ awọn owo ti o le ṣee lo lẹsẹkẹsẹ. Ti awọn owo ba han ni iwọntunwọnsi kekere ṣugbọn kii ṣe iwọntunwọnsi oke, lẹhinna o gbọdọ duro iṣẹju diẹ fun awọn owo ti nwọle lati gba awọn ijẹrisi nẹtiwọọki diẹ sii. Lẹhin ti wọn gba awọn ijẹrisi diẹ sii, wọn yoo jẹ inawo.",
644 "syncing_wallet_alert_title": "Apamọwọ rẹ n muṣiṣẹpọ",
645 "syncing_wallet_alert_content": "Iwontunws.funfun rẹ ati atokọ idunadura le ma pari titi ti yoo fi sọ “SYNCHRONIZED” ni oke. Tẹ/tẹ ni kia kia lati ni imọ siwaju sii.",
646 + "home_screen_settings": "Awọn eto iboju ile",
647 + "sort_by": "Sa pelu",
648 + "search_add_token": "Wa / Fi àmi kun",
649 + "edit_token": "Ṣatunkọ àmi",
650 + "warning": "Ikilo",
651 + "add_token_warning": "Ma ṣe ṣatunkọ tabi ṣafikun awọn ami bi a ti fun ni aṣẹ nipasẹ awọn scammers.\nNigbagbogbo jẹrisi awọn adirẹsi ami pẹlu awọn orisun olokiki!",
652 + "add_token_disclaimer_check": "Mo ti jẹrisi adirẹsi adehun ami ati alaye nipa lilo orisun olokiki kan. Fifi irira tabi alaye ti ko tọ le ja si isonu ti owo.",
653 + "token_contract_address": "Àmi guide adirẹsi",
654 + "token_name": "Orukọ àmi fun apẹẹrẹ: Tether",
655 + "token_symbol": "Aami aami fun apẹẹrẹ: USDT",
656 + "token_decimal": "Àmi eleemewa",
657 + "field_required": "E ni lati se nkan si aye yi",
658 + "pin_at_top": "pin ${tokini} ni oke",
659 + "invalid_input": "Iṣawọle ti ko tọ",
660 + "fiat_balance": "Fiat Iwontunws.funfun",
661 + "gross_balance": "Iwontunws.funfun apapọ",
662 + "alphabetical": "Labidibi",
663 "generate_name": "Ṣẹda Orukọ",
664 "balance_page": "Oju-iwe iwọntunwọnsi",
665 "share": "Pinpin",
666 "slidable": "Slidable",
667 + "etherscan_history": "Etherscan itan",
668 "template_name": "Orukọ Awoṣe"
669 }
res/values/strings_zh.arb
+23 -5
@@ -1,7 +1,7 @@
1 {
2 "welcome": "欢迎使用",
3 "cake_wallet": "Cake Wallet",
4 - "first_wallet_text": "门罗币、比特币、莱特币和避风港的超棒钱包",
4 + "first_wallet_text": "适用于门罗币、比特币、以太坊、莱特币和避风港的超棒钱包",
5 "please_make_selection": "请在下面进行选择 创建或恢复您的钱包.",
6 "create_new": "创建新钱包",
7 "restore_wallet": "恢复钱包",
@@ -250,8 +250,8 @@
250 "transaction_details_recipient_address": "收件人地址",
251 "wallet_list_title": "Monero 钱包",
252 "wallet_list_create_new_wallet": "创建新钱包",
253 - "wallet_list_edit_wallet" : "编辑钱包",
254 - "wallet_list_wallet_name" : "钱包名称",
253 + "wallet_list_edit_wallet": "编辑钱包",
254 + "wallet_list_wallet_name": "钱包名称",
255 "wallet_list_restore_wallet": "恢复钱包",
256 "wallet_list_load_wallet": "加载钱包",
257 "wallet_list_loading_wallet": "载入中 ${wallet_name} 钱包",
@@ -394,8 +394,8 @@
394 "unconfirmed": "未确认余额",
395 "displayable": "可显示",
396 "submit_request": "提交请求",
397 - "buy_alert_content": "目前我们只支持购买比特币、莱特币和门罗币。 请创建或切换到您的比特币、莱特币或门罗币钱包。",
398 - "sell_alert_content": "我们目前只支持比特币和莱特币的销售。 请创建或切换到您的比特币或莱特币钱包。",
397 + "buy_alert_content": "目前我们仅支持购买比特币、以太坊、莱特币和门罗币。请创建或切换到您的比特币、以太坊、莱特币或门罗币钱包。",
398 + "sell_alert_content": "我们目前仅支持比特币、以太坊和莱特币的销售。请创建或切换到您的比特币、以太坊或莱特币钱包。",
399 "outdated_electrum_wallet_description": "在Cake创建的新比特币钱包现在有一个24字的种子。你必须创建一个新的比特币钱包,并将你所有的资金转移到新的24字钱包,并停止使用12字种子的钱包。请立即这样做以保证你的资金安全。",
400 "understand": "我已知晓",
401 "apk_update": "APK更新",
@@ -646,9 +646,27 @@
646 "available_balance_description": "可用余额是您可以使用的金额。冻结余额是您当前正在等待确认的金额。",
647 "syncing_wallet_alert_title": "您的钱包正在同步",
648 "syncing_wallet_alert_content": "您的余额和交易列表可能不完整,直到顶部显示“已同步”。单击/点击以了解更多信息。",
649 + "home_screen_settings": "主屏幕设置",
650 + "sort_by": "排序方式",
651 + "search_add_token": "搜索/添加令牌",
652 + "edit_token": "编辑令牌",
653 + "warning": "警告",
654 + "add_token_warning": "请勿按照诈骗者的指示编辑或添加令牌。\n始终通过信誉良好的来源确认代币地址!",
655 + "add_token_disclaimer_check": "我已使用信誉良好的来源确认了代币合约地址和信息。 添加恶意或不正确的信息可能会导致资金损失。",
656 + "token_contract_address": "代币合约地址",
657 + "token_name": "代币名称例如:Tether",
658 + "token_symbol": "代币符号例如:USDT",
659 + "token_decimal": "令牌十进制",
660 + "field_required": "此字段是必需的",
661 + "pin_at_top": "将 ${token} 固定在顶部",
662 + "invalid_input": "输入无效",
663 + "fiat_balance": "法币余额",
664 + "gross_balance": "毛余额",
665 + "alphabetical": "按字母顺序",
666 "generate_name": "生成名称",
667 "balance_page": "余额页",
668 "share": "分享",
669 "slidable": "可滑动",
670 + "etherscan_history": "以太扫描历史",
671 "template_name": "模板名称"
672 }
scripts/android/pubspec_gen.sh
+1 -1
@@ -10,7 +10,7 @@ case $APP_ANDROID_TYPE in
10 CONFIG_ARGS="--monero"
11 ;;
12 $CAKEWALLET)
13 - CONFIG_ARGS="--monero --bitcoin --haven"
13 + CONFIG_ARGS="--monero --bitcoin --haven --ethereum"
14 ;;
15 $HAVEN)
16 CONFIG_ARGS="--haven"
scripts/ios/app_config.sh
+1 -1
@@ -23,7 +23,7 @@ case $APP_IOS_TYPE in
23 CONFIG_ARGS="--monero"
24 ;;
25 $CAKEWALLET)
26 - CONFIG_ARGS="--monero --bitcoin --haven"
26 + CONFIG_ARGS="--monero --bitcoin --haven --ethereum"
27 ;;
28 $HAVEN)
29 CONFIG_ARGS="--haven"
tool/configure.dart
+106 -6
@@ -1,9 +1,9 @@
1 -import 'dart:convert';
1 import 'dart:io';
2
3 const bitcoinOutputPath = 'lib/bitcoin/bitcoin.dart';
4 const moneroOutputPath = 'lib/monero/monero.dart';
5 const havenOutputPath = 'lib/haven/haven.dart';
6 +const ethereumOutputPath = 'lib/ethereum/ethereum.dart';
7 const walletTypesPath = 'lib/wallet_types.g.dart';
8 const pubspecDefaultPath = 'pubspec_default.yaml';
9 const pubspecOutputPath = 'pubspec.yaml';
@@ -13,11 +13,13 @@ Future<void> main(List<String> args) async {
13 final hasBitcoin = args.contains('${prefix}bitcoin');
14 final hasMonero = args.contains('${prefix}monero');
15 final hasHaven = args.contains('${prefix}haven');
16 + final hasEthereum = args.contains('${prefix}ethereum');
17 await generateBitcoin(hasBitcoin);
18 await generateMonero(hasMonero);
19 await generateHaven(hasHaven);
19 - await generatePubspec(hasMonero: hasMonero, hasBitcoin: hasBitcoin, hasHaven: hasHaven);
20 - await generateWalletTypes(hasMonero: hasMonero, hasBitcoin: hasBitcoin, hasHaven: hasHaven);
20 + await generateEthereum(hasEthereum);
21 + await generatePubspec(hasMonero: hasMonero, hasBitcoin: hasBitcoin, hasHaven: hasHaven, hasEthereum: hasEthereum);
22 + await generateWalletTypes(hasMonero: hasMonero, hasBitcoin: hasBitcoin, hasHaven: hasHaven, hasEthereum: hasEthereum);
23 }
24
25 Future<void> generateBitcoin(bool hasImplementation) async {
@@ -471,7 +473,89 @@ abstract class HavenAccountList {
473 await outputFile.writeAsString(output);
474 }
475
474 -Future<void> generatePubspec({required bool hasMonero, required bool hasBitcoin, required bool hasHaven}) async {
476 +Future<void> generateEthereum(bool hasImplementation) async {
477 +
478 + final outputFile = File(ethereumOutputPath);
479 + const ethereumCommonHeaders = """
480 +""";
481 + const ethereumCWHeaders = """
482 +import 'package:cake_wallet/view_model/send/output.dart';
483 +import 'package:cw_core/crypto_amount_format.dart';
484 +import 'package:cw_core/crypto_currency.dart';
485 +import 'package:cw_core/erc20_token.dart';
486 +import 'package:cw_core/output_info.dart';
487 +import 'package:cw_core/transaction_info.dart';
488 +import 'package:cw_core/transaction_priority.dart';
489 +import 'package:cw_core/wallet_base.dart';
490 +import 'package:cw_core/wallet_credentials.dart';
491 +import 'package:cw_core/wallet_info.dart';
492 +import 'package:cw_core/wallet_service.dart';
493 +import 'package:cw_ethereum/ethereum_formatter.dart';
494 +import 'package:cw_ethereum/ethereum_mnemonics.dart';
495 +import 'package:cw_ethereum/ethereum_transaction_credentials.dart';
496 +import 'package:cw_ethereum/ethereum_transaction_info.dart';
497 +import 'package:cw_ethereum/ethereum_wallet.dart';
498 +import 'package:cw_ethereum/ethereum_wallet_creation_credentials.dart';
499 +import 'package:cw_ethereum/ethereum_wallet_service.dart';
500 +import 'package:cw_ethereum/ethereum_transaction_priority.dart';
501 +import 'package:hive/hive.dart';
502 +""";
503 + const ethereumCwPart = "part 'cw_ethereum.dart';";
504 + const ethereumContent = """
505 +abstract class Ethereum {
506 + List<String> getEthereumWordList(String language);
507 + WalletService createEthereumWalletService(Box<WalletInfo> walletInfoSource);
508 + WalletCredentials createEthereumNewWalletCredentials({required String name, WalletInfo? walletInfo});
509 + WalletCredentials createEthereumRestoreWalletFromSeedCredentials({required String name, required String mnemonic, required String password});
510 + String getAddress(WalletBase wallet);
511 + TransactionPriority getDefaultTransactionPriority();
512 + List<TransactionPriority> getTransactionPriorities();
513 + TransactionPriority deserializeEthereumTransactionPriority(int raw);
514 +
515 + Object createEthereumTransactionCredentials(
516 + List<Output> outputs, {
517 + required TransactionPriority priority,
518 + required CryptoCurrency currency,
519 + int? feeRate,
520 + });
521 +
522 + Object createEthereumTransactionCredentialsRaw(
523 + List<OutputInfo> outputs, {
524 + TransactionPriority? priority,
525 + required CryptoCurrency currency,
526 + required int feeRate,
527 + });
528 +
529 + int formatterEthereumParseAmount(String amount);
530 + double formatterEthereumAmountToDouble({TransactionInfo? transaction, BigInt? amount, int exponent = 18});
531 + List<Erc20Token> getERC20Currencies(WalletBase wallet);
532 + Future<void> addErc20Token(WalletBase wallet, Erc20Token token);
533 + Future<void> deleteErc20Token(WalletBase wallet, Erc20Token token);
534 + Future<Erc20Token?> getErc20Token(WalletBase wallet, String contractAddress);
535 +
536 + CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction);
537 + void updateEtherscanUsageState(WalletBase wallet, bool isEnabled);
538 +}
539 + """;
540 +
541 + const ethereumEmptyDefinition = 'Ethereum? ethereum;\n';
542 + const ethereumCWDefinition = 'Ethereum? ethereum = CWEthereum();\n';
543 +
544 + final output = '$ethereumCommonHeaders\n'
545 + + (hasImplementation ? '$ethereumCWHeaders\n' : '\n')
546 + + (hasImplementation ? '$ethereumCwPart\n\n' : '\n')
547 + + (hasImplementation ? ethereumCWDefinition : ethereumEmptyDefinition)
548 + + '\n'
549 + + ethereumContent;
550 +
551 + if (outputFile.existsSync()) {
552 + await outputFile.delete();
553 + }
554 +
555 + await outputFile.writeAsString(output);
556 +}
557 +
558 +Future<void> generatePubspec({required bool hasMonero, required bool hasBitcoin, required bool hasHaven, required bool hasEthereum}) async {
559 const cwCore = """
560 cw_core:
561 path: ./cw_core
@@ -492,6 +576,10 @@ Future<void> generatePubspec({required bool hasMonero, required bool hasBitcoin,
576 cw_shared_external:
577 path: ./cw_shared_external
578 """;
579 + const cwEthereum = """
580 + cw_ethereum:
581 + path: ./cw_ethereum
582 + """;
583 final inputFile = File(pubspecOutputPath);
584 final inputText = await inputFile.readAsString();
585 final inputLines = inputText.split('\n');
@@ -512,6 +600,10 @@ Future<void> generatePubspec({required bool hasMonero, required bool hasBitcoin,
600 output += '\n$cwHaven';
601 }
602
603 + if (hasEthereum) {
604 + output += '\n$cwEthereum';
605 + }
606 +
607 final outputLines = output.split('\n');
608 inputLines.insertAll(dependenciesIndex + 1, outputLines);
609 final outputContent = inputLines.join('\n');
@@ -524,7 +616,7 @@ Future<void> generatePubspec({required bool hasMonero, required bool hasBitcoin,
616 await outputFile.writeAsString(outputContent);
617 }
618
527 -Future<void> generateWalletTypes({required bool hasMonero, required bool hasBitcoin, required bool hasHaven}) async {
619 +Future<void> generateWalletTypes({required bool hasMonero, required bool hasBitcoin, required bool hasHaven, required bool hasEthereum}) async {
620 final walletTypesFile = File(walletTypesPath);
621
622 if (walletTypesFile.existsSync()) {
@@ -540,7 +632,15 @@ Future<void> generateWalletTypes({required bool hasMonero, required bool hasBitc
632 }
633
634 if (hasBitcoin) {
543 - outputContent += '\tWalletType.bitcoin,\n\tWalletType.litecoin,\n';
635 + outputContent += '\tWalletType.bitcoin,\n';
636 + }
637 +
638 + if (hasEthereum) {
639 + outputContent += '\tWalletType.ethereum,\n';
640 + }
641 +
642 + if (hasBitcoin) {
643 + outputContent += '\tWalletType.litecoin,\n';
644 }
645
646 if (hasHaven) {
tool/generate_secrets_config.dart
+17 -3
@@ -4,12 +4,12 @@ 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';
8
9 Future<void> main(List<String> args) async => generateSecretsConfig(args);
10
11 Future<void> generateSecretsConfig(List<String> args) async {
11 - final extraInfo =
12 - args.fold(<String, dynamic>{}, (Map<String, dynamic> acc, String arg) {
12 + final extraInfo = args.fold(<String, dynamic>{}, (Map<String, dynamic> acc, String arg) {
13 final parts = arg.split('=');
14 final key = normalizeKeyName(parts[0]);
15 acc[key] = acc[key] = parts.length > 1 ? parts[1] : 1;
@@ -17,6 +17,7 @@ Future<void> generateSecretsConfig(List<String> args) async {
17 });
18
19 final configFile = File(configPath);
20 + final ethereumConfigFile = File(ethereumConfigPath);
21 final secrets = <String, dynamic>{};
22
23 secrets.addAll(extraInfo);
@@ -44,6 +45,19 @@ Future<void> generateSecretsConfig(List<String> args) async {
45 secrets[sec.name] = sec.generate();
46 });
47
47 - final secretsJson = JsonEncoder.withIndent(' ').convert(secrets);
48 + var secretsJson = JsonEncoder.withIndent(' ').convert(secrets);
49 await configFile.writeAsString(secretsJson);
50 +
51 + secrets.clear();
52 + SecretKey.ethereumSecrets.forEach((sec) {
53 + if (secrets[sec.name] != null) {
54 + return;
55 + }
56 +
57 + secrets[sec.name] = sec.generate();
58 + });
59 +
60 + secretsJson = JsonEncoder.withIndent(' ').convert(secrets);
61 +
62 + await ethereumConfigFile.writeAsString(secretsJson);
63 }
tool/import_secrets_config.dart
+17 -5
@@ -5,19 +5,31 @@ 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';
10 +
11 Future<void> main(List<String> args) async => importSecretsConfig();
12
13 Future<void> importSecretsConfig() async {
14 final outputFile = File(outputPath);
12 - final input = json.decode(File(configPath).readAsStringSync())
13 - as Map<String, dynamic> ??
14 - <String, dynamic>{};
15 - final output = input.keys
16 - .fold('', (String acc, String val) => acc + generateConst(val, input));
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));
23
24 if (outputFile.existsSync()) {
25 await outputFile.delete();
26 }
27
28 await outputFile.writeAsString(output);
29 +
30 + if (ethereumOutputFile.existsSync()) {
31 + await ethereumOutputFile.delete();
32 + }
33 +
34 + await ethereumOutputFile.writeAsString(ethereumOutput);
35 }
tool/utils/secret_key.dart
+4
@@ -33,6 +33,10 @@ class SecretKey {
33 SecretKey('payfuraApiKey', () => ''),
34 ];
35
36 + static final ethereumSecrets = [
37 + SecretKey('etherScanApiKey', () => ''),
38 + ];
39 +
40 final String name;
41 final String Function() generate;
42 }