CW-438 add nano (#1015)

* 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 [skip ci] * - 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 [skip ci] * 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 [skip ci] * 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` [skip-ci] * initial changes * more base config stuff * config changes * successfully builds! * save * successfully add nano wallet * save * seed generation * receive screen + node screen working * tx history working and fiat fixes * balance working * derivation updates * nano-unfinished * sends working * remove fees from send screen, send and receive transactions working * fixes + auto receive incoming txs * fix for scanning QR codes * save * update translations * fixes * more fixes * more strings * small fix * fix github actions workflow * potential fix * potential fix * ci/cd fix * change rep working * seed generation fixes * fixes * save * change rep screen functional * save * banano changes * fixes, start adding ui for PoW * pow node changes * update translations * fix * account changing barely working * save * disable account generation * small fix * save * UI work * save * fixes after merge main * fixes * remove monero stuff, work on derivation ui * lots of fixes + finish up seed derivation * last minute fixes * node related fixes * more fixes * small fix * more fixes * fixes * pretty big refactor for pow, still some bugs * finally works! * get transactions after send * fix * merge conflict fixes * save * fix pow node showing up twice * done * initial changes * small fix * more merge fixes * fixes * more fixes * fix * save * fix manage pow nodes setting appearing on other wallets * fix contact bug * fixes * fiat fixes * save * save * save * save * updates * cleanup * restore fix * fixes * remove deprecated alert * fix * small fix * remove outdated warning * electrum restore fixes * fixes * fixes * fix * derivation fixes * nano fixes pt.1 * nano fixes pt.2 * bip39 fixes * pownode refactor * nodes pages fixes * observer fix * ssl fix * remove old references * remove unused imports * code cleanup * small fix * small potential fix * save * undo all bitcoin related changes * remove dead code * review fixes * more fixes * fix * fix * review fix * small fix * nano derivation and nanoutil fixes * exchange nano fix * nano review fixes pt.1 * nano fixes pt.2 * nano fixes pt.3 * remove old imports + stop using dynamic in di * nanoutil fixes * add nano.dart to gitignore, configure fixes * review fixes, getnanowalletservice removed * fix settings screen, add changeRep to configure.dart, other minor fixes * remove manage_pow_nodes_page, key derivation edge case handled * remove old refs * more small fixes * Generic Enhancements/Minor fixes * review fixes * hopefully final fixes * review fixes * node connection fixes --------- Co-authored-by: OmarHatem <omarh.ismail1@gmail.com> Co-authored-by: Justin Ehrenhofer <justin.ehrenhofer@gmail.com> Co-authored-by: fossephate <fosse@book.local>

Matthew Fosse committed Oct 4, 2023 at 21:09 UTC 4c60b178be958d21cb1fd3fa3ee48137a487310e
134 files changed +8165 -1049
.github/workflows/pr_test_build.yml
+1
@@ -92,6 +92,7 @@ jobs:
92 cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
93 cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
94 cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
95 + cd cw_nano && 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
.gitignore
+1
@@ -124,6 +124,7 @@ lib/bitcoin/bitcoin.dart
124 lib/monero/monero.dart
125 lib/haven/haven.dart
126 lib/ethereum/ethereum.dart
127 +lib/nano/nano.dart
128
129 ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png
130 ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png
assets/nano_node_list.yml new
+6
@@ -0,0 +1,6 @@
1 +-
2 + uri: rpc.nano.to
3 + useSSL: true
4 + is_default: true
5 +-
6 + uri: node.perish.co:9076
\ No newline at end of file
assets/nano_pow_node_list.yml new
+9
@@ -0,0 +1,9 @@
1 +-
2 + uri: rpc.nano.to
3 + useSSL: true
4 + is_default: true
5 +-
6 + uri: workers.perish.co
7 +-
8 + uri: worker.nanoriver.cc
9 + useSSL: true
\ No newline at end of file
configure_cake_wallet_android.sh
+1
@@ -7,4 +7,5 @@ cd cw_monero && flutter pub get && flutter packages pub run build_runner build -
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 +cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
11 flutter packages pub run build_runner build --delete-conflicting-outputs
cw_bitcoin/lib/bitcoin_mnemonic.dart
+1 -1
@@ -2297,4 +2297,4 @@ final englishWordlist = <String>[
2297 'zero',
2298 'zone',
2299 'zoo'
2300 -];
2300 +];
\ No newline at end of file
cw_bitcoin/lib/bitcoin_wallet.dart
+1 -1
@@ -89,4 +89,4 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
89 initialRegularAddressIndex: snp.regularAddressIndex,
90 initialChangeAddressIndex: snp.changeAddressIndex);
91 }
92 -}
92 +}
\ No newline at end of file
cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart
+1 -1
@@ -20,4 +20,4 @@ class BitcoinRestoreWalletFromWIFCredentials extends WalletCredentials {
20 : super(name: name, password: password, walletInfo: walletInfo);
21
22 final String wif;
23 -}
23 +}
\ No newline at end of file
cw_bitcoin/lib/bitcoin_wallet_service.dart
+1 -1
@@ -100,4 +100,4 @@ class BitcoinWalletService extends WalletService<
100 await wallet.init();
101 return wallet;
102 }
103 -}
103 +}
\ No newline at end of file
cw_bitcoin/lib/electrum_wallet_snapshot.dart
+1 -1
@@ -56,4 +56,4 @@ class ElectrumWallletSnapshot {
56 regularAddressIndex: regularAddressIndex,
57 changeAddressIndex: changeAddressIndex);
58 }
59 -}
59 +}
\ No newline at end of file
cw_core/lib/crypto_currency.dart
+3 -1
@@ -90,6 +90,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
90 CryptoCurrency.zrx,
91 CryptoCurrency.dydx,
92 CryptoCurrency.steth,
93 + CryptoCurrency.banano,
94 ];
95
96 static const havenCurrencies = [
@@ -119,7 +120,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
120 static const eos = CryptoCurrency(title: 'EOS', fullName: 'EOS', raw: 7, name: 'eos', iconPath: 'assets/images/eos_icon.png');
121 static const eth = CryptoCurrency(title: 'ETH', fullName: 'Ethereum', raw: 8, name: 'eth', iconPath: 'assets/images/eth_icon.png');
122 static const ltc = CryptoCurrency(title: 'LTC', fullName: 'Litecoin', raw: 9, name: 'ltc', iconPath: 'assets/images/litecoin-ltc_icon.png');
122 - static const nano = CryptoCurrency(title: 'NANO', raw: 10, name: 'nano', iconPath: 'assets/images/nano.png');
123 + static const nano = CryptoCurrency(title: 'XNO', raw: 10, fullName: 'Nano', name: 'xno', iconPath: 'assets/images/nano_icon.png');
124 static const trx = CryptoCurrency(title: 'TRX', fullName: 'TRON', raw: 11, name: 'trx', iconPath: 'assets/images/trx_icon.png');
125 static const usdt = CryptoCurrency(title: 'USDT', tag: 'OMNI', fullName: 'USDT Tether', raw: 12, name: 'usdt', iconPath: 'assets/images/usdt_icon.png');
126 static const usdterc20 = CryptoCurrency(title: 'USDT', tag: 'ETH', fullName: 'USDT Tether', raw: 13, name: 'usdterc20', iconPath: 'assets/images/usdterc20_icon.png');
@@ -198,6 +199,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
199 static const zrx = CryptoCurrency(title: 'ZRX', tag: 'ETH', fullName: '0x Protocol', raw: 83, name: 'zrx', iconPath: 'assets/images/zrx_icon.png');
200 static const dydx = CryptoCurrency(title: 'DYDX', tag: 'ETH', fullName: 'dYdX', raw: 84, name: 'dydx', iconPath: 'assets/images/dydx_icon.png');
201 static const steth = CryptoCurrency(title: 'STETH', tag: 'ETH', fullName: 'Lido Staked Ethereum', raw: 85, name: 'steth', iconPath: 'assets/images/steth_icon.png');
202 + static const banano = CryptoCurrency(title: 'BAN', raw: 86, name: 'banano', iconPath: 'assets/images/nano_icon.png');
203
204
205 static final Map<int, CryptoCurrency> _rawCurrencyMap =
cw_core/lib/currency_for_wallet_type.dart
+4
@@ -13,6 +13,10 @@ CryptoCurrency currencyForWalletType(WalletType type) {
13 return CryptoCurrency.xhv;
14 case WalletType.ethereum:
15 return CryptoCurrency.eth;
16 + case WalletType.nano:
17 + return CryptoCurrency.nano;
18 + case WalletType.banano:
19 + return CryptoCurrency.banano;
20 default:
21 throw Exception('Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType');
22 }
cw_core/lib/hive_type_ids.dart
+4 -1
@@ -1,4 +1,4 @@
1 -const CONTACT_TYPE_ID = 0;
1 +const CONTACT_TYPE_ID = 0;
2 const NODE_TYPE_ID = 1;
3 const TRANSACTION_TYPE_ID = 2;
4 const TRADE_TYPE_ID = 3;
@@ -11,3 +11,6 @@ const UNSPENT_COINS_INFO_TYPE_ID = 9;
11 const ANONPAY_INVOICE_INFO_TYPE_ID = 10;
12 const ADDRESS_INFO_TYPE_ID = 11;
13 const ERC20_TOKEN_TYPE_ID = 12;
14 +const NANO_ACCOUNT_TYPE_ID = 13;
15 +const POW_NODE_TYPE_ID = 14;
16 +const DERIVATION_TYPE_TYPE_ID = 15;
\ No newline at end of file
cw_core/lib/nano_account.dart new
+23
@@ -0,0 +1,23 @@
1 +import 'package:cw_core/hive_type_ids.dart';
2 +import 'package:hive/hive.dart';
3 +
4 +part 'nano_account.g.dart';
5 +
6 +@HiveType(typeId: NanoAccount.typeId)
7 +class NanoAccount extends HiveObject {
8 + NanoAccount({required this.label, required this.id, this.balance, this.isSelected = false});
9 +
10 + static const typeId = NANO_ACCOUNT_TYPE_ID;
11 +
12 + @HiveField(0)
13 + String label;
14 +
15 + @HiveField(1)
16 + final int id;
17 +
18 + @HiveField(2)
19 + bool isSelected;
20 +
21 + @HiveField(3)
22 + String? balance;
23 +}
cw_core/lib/nano_account_info_response.dart new
+23
@@ -0,0 +1,23 @@
1 +class AccountInfoResponse {
2 + String frontier;
3 + int confirmationHeight;
4 + String balance;
5 + String representative;
6 + String? address;
7 +
8 + AccountInfoResponse({
9 + required this.frontier,
10 + required this.balance,
11 + required this.representative,
12 + required this.confirmationHeight,
13 + });
14 +
15 + factory AccountInfoResponse.fromJson(Map<String, dynamic> json) {
16 + return AccountInfoResponse(
17 + frontier: json['frontier'] as String,
18 + representative: json['representative'] as String,
19 + balance: json['balance'] as String,
20 + confirmationHeight: int.parse(json['confirmation_height'] as String),
21 + );
22 + }
23 +}
cw_core/lib/node.dart
+54 -30
@@ -9,19 +9,19 @@ import 'package:http/io_client.dart' as ioc;
9
10 part 'node.g.dart';
11
12 -Uri createUriFromElectrumAddress(String address) =>
13 - Uri.tryParse('tcp://$address')!;
12 +Uri createUriFromElectrumAddress(String address) => Uri.tryParse('tcp://$address')!;
13
14 @HiveType(typeId: Node.typeId)
15 class Node extends HiveObject with Keyable {
17 - Node(
18 - {this.login,
19 - this.password,
20 - this.useSSL,
21 - this.trusted = false,
22 - this.socksProxyAddress,
23 - String? uri,
24 - WalletType? type,}) {
16 + Node({
17 + this.login,
18 + this.password,
19 + this.useSSL,
20 + this.trusted = false,
21 + this.socksProxyAddress,
22 + String? uri,
23 + WalletType? type,
24 + }) {
25 if (uri != null) {
26 uriRaw = uri;
27 }
@@ -78,6 +78,13 @@ class Node extends HiveObject with Keyable {
78 return Uri.http(uriRaw, '');
79 case WalletType.ethereum:
80 return Uri.https(uriRaw, '');
81 + case WalletType.nano:
82 + case WalletType.banano:
83 + if (isSSL) {
84 + return Uri.https(uriRaw, '');
85 + } else {
86 + return Uri.http(uriRaw, '');
87 + }
88 default:
89 throw Exception('Unexpected type ${type.toString()} for Node uri');
90 }
@@ -86,13 +93,13 @@ class Node extends HiveObject with Keyable {
93 @override
94 bool operator ==(other) =>
95 other is Node &&
89 - (other.uriRaw == uriRaw &&
90 - other.login == login &&
91 - other.password == password &&
92 - other.typeRaw == typeRaw &&
93 - other.useSSL == useSSL &&
94 - other.trusted == trusted &&
95 - other.socksProxyAddress == socksProxyAddress);
96 + (other.uriRaw == uriRaw &&
97 + other.login == login &&
98 + other.password == password &&
99 + other.typeRaw == typeRaw &&
100 + other.useSSL == useSSL &&
101 + other.trusted == trusted &&
102 + other.socksProxyAddress == socksProxyAddress);
103
104 @override
105 int get hashCode =>
@@ -120,7 +127,9 @@ class Node extends HiveObject with Keyable {
127 try {
128 switch (type) {
129 case WalletType.monero:
123 - return useSocksProxy ? requestNodeWithProxy(socksProxyAddress ?? '') : requestMoneroNode();
130 + return useSocksProxy
131 + ? requestNodeWithProxy(socksProxyAddress ?? '')
132 + : requestMoneroNode();
133 case WalletType.bitcoin:
134 return requestElectrumServer();
135 case WalletType.litecoin:
@@ -129,6 +138,9 @@ class Node extends HiveObject with Keyable {
138 return requestMoneroNode();
139 case WalletType.ethereum:
140 return requestElectrumServer();
141 + case WalletType.nano:
142 + case WalletType.banano:
143 + return requestNanoNode();
144 default:
145 return false;
146 }
@@ -141,27 +153,23 @@ class Node extends HiveObject with Keyable {
153 final path = '/json_rpc';
154 final rpcUri = isSSL ? Uri.https(uri.authority, path) : Uri.http(uri.authority, path);
155 final realm = 'monero-rpc';
144 - final body = {
145 - 'jsonrpc': '2.0',
146 - 'id': '0',
147 - 'method': 'get_info'
148 - };
156 + final body = {'jsonrpc': '2.0', 'id': '0', 'method': 'get_info'};
157
158 try {
159 final authenticatingClient = HttpClient();
160
161 authenticatingClient.addCredentials(
154 - rpcUri,
155 - realm,
156 - HttpClientDigestCredentials(login ?? '', password ?? ''),
162 + rpcUri,
163 + realm,
164 + HttpClientDigestCredentials(login ?? '', password ?? ''),
165 );
166
167 final http.Client client = ioc.IOClient(authenticatingClient);
168
169 final response = await client.post(
162 - rpcUri,
163 - headers: {'Content-Type': 'application/json'},
164 - body: json.encode(body),
170 + rpcUri,
171 + headers: {'Content-Type': 'application/json'},
172 + body: json.encode(body),
173 );
174
175 client.close();
@@ -173,8 +181,24 @@ class Node extends HiveObject with Keyable {
181 }
182 }
183
176 - Future<bool> requestNodeWithProxy(String proxy) async {
184 + Future<bool> requestNanoNode() async {
185 + http.Response response = await http.post(
186 + uri,
187 + headers: {'Content-type': 'application/json'},
188 + body: json.encode(
189 + {
190 + "action": "block_count",
191 + },
192 + ),
193 + );
194 + if (response.statusCode == 200) {
195 + return true;
196 + } else {
197 + return false;
198 + }
199 + }
200
201 + Future<bool> requestNodeWithProxy(String proxy) async {
202 if (proxy.isEmpty || !proxy.contains(':')) {
203 return false;
204 }
cw_core/lib/wallet_base.dart
+4 -3
@@ -13,9 +13,7 @@ import 'package:cw_core/sync_status.dart';
13 import 'package:cw_core/node.dart';
14 import 'package:cw_core/wallet_type.dart';
15
16 -abstract class WalletBase<
17 - BalanceType extends Balance,
18 - HistoryType extends TransactionHistoryBase,
16 +abstract class WalletBase<BalanceType extends Balance, HistoryType extends TransactionHistoryBase,
17 TransactionType extends TransactionInfo> {
18 WalletBase(this.walletInfo);
19
@@ -58,6 +56,9 @@ abstract class WalletBase<
56
57 Future<void> connectToNode({required Node node});
58
59 + // there is a default definition here because only coins with a pow node (nano based) need to override this
60 + Future<void> connectToPowNode({required Node node}) async {}
61 +
62 Future<void> startSync();
63
64 Future<PendingTransaction> createTransaction(Object credentials);
cw_core/lib/wallet_credentials.dart
+6 -1
@@ -5,10 +5,15 @@ abstract class WalletCredentials {
5 required this.name,
6 this.height,
7 this.walletInfo,
8 - this.password});
8 + this.password,
9 + this.derivationType,
10 + this.derivationPath,
11 + });
12
13 final String name;
14 final int? height;
15 String? password;
16 + DerivationType? derivationType;
17 + String? derivationPath;
18 WalletInfo? walletInfo;
19 }
cw_core/lib/wallet_info.dart
+89 -20
@@ -6,29 +6,92 @@ import 'package:hive/hive.dart';
6
7 part 'wallet_info.g.dart';
8
9 +@HiveType(typeId: DERIVATION_TYPE_TYPE_ID)
10 +enum DerivationType {
11 + @HiveField(0)
12 + unknown,
13 + @HiveField(1)
14 + def, // default is a reserved word
15 + @HiveField(2)
16 + nano,
17 + @HiveField(3)
18 + bip39,
19 + @HiveField(4)
20 + electrum1,
21 + @HiveField(5)
22 + electrum2,
23 +}
24 +
25 +class DerivationInfo {
26 + DerivationInfo({
27 + required this.derivationType,
28 + this.derivationPath,
29 + this.balance = "",
30 + this.address = "",
31 + this.height = 0,
32 + this.script_type,
33 + this.description,
34 + });
35 +
36 + String balance;
37 + String address;
38 + int height;
39 + final DerivationType derivationType;
40 + final String? derivationPath;
41 + final String? script_type;
42 + final String? description;
43 +}
44 +
45 @HiveType(typeId: WalletInfo.typeId)
46 class WalletInfo extends HiveObject {
11 - WalletInfo(this.id, this.name, this.type, this.isRecovery, this.restoreHeight,
12 - this.timestamp, this.dirPath, this.path, this.address, this.yatEid,
13 - this.yatLastUsedAddressRaw, this.showIntroCakePayCard)
47 + WalletInfo(
48 + this.id,
49 + this.name,
50 + this.type,
51 + this.isRecovery,
52 + this.restoreHeight,
53 + this.timestamp,
54 + this.dirPath,
55 + this.path,
56 + this.address,
57 + this.yatEid,
58 + this.yatLastUsedAddressRaw,
59 + this.showIntroCakePayCard,
60 + this.derivationType,
61 + this.derivationPath)
62 : _yatLastUsedAddressController = StreamController<String>.broadcast();
63
16 - factory WalletInfo.external(
17 - {required String id,
18 - required String name,
19 - required WalletType type,
20 - required bool isRecovery,
21 - required int restoreHeight,
22 - required DateTime date,
23 - required String dirPath,
24 - required String path,
25 - required String address,
26 - bool? showIntroCakePayCard,
27 - String yatEid ='',
28 - String yatLastUsedAddressRaw = ''}) {
29 - return WalletInfo(id, name, type, isRecovery, restoreHeight,
30 - date.millisecondsSinceEpoch, dirPath, path, address,
31 - yatEid, yatLastUsedAddressRaw, showIntroCakePayCard);
64 + factory WalletInfo.external({
65 + required String id,
66 + required String name,
67 + required WalletType type,
68 + required bool isRecovery,
69 + required int restoreHeight,
70 + required DateTime date,
71 + required String dirPath,
72 + required String path,
73 + required String address,
74 + bool? showIntroCakePayCard,
75 + String yatEid = '',
76 + String yatLastUsedAddressRaw = '',
77 + DerivationType? derivationType,
78 + String? derivationPath,
79 + }) {
80 + return WalletInfo(
81 + id,
82 + name,
83 + type,
84 + isRecovery,
85 + restoreHeight,
86 + date.millisecondsSinceEpoch,
87 + dirPath,
88 + path,
89 + address,
90 + yatEid,
91 + yatLastUsedAddressRaw,
92 + showIntroCakePayCard,
93 + derivationType,
94 + derivationPath);
95 }
96
97 static const typeId = WALLET_INFO_TYPE_ID;
@@ -79,6 +142,12 @@ class WalletInfo extends HiveObject {
142 @HiveField(15)
143 List<String>? usedAddresses;
144
145 + @HiveField(16)
146 + DerivationType? derivationType;
147 +
148 + @HiveField(17)
149 + String? derivationPath;
150 +
151 String get yatLastUsedAddress => yatLastUsedAddressRaw ?? '';
152
153 set yatLastUsedAddress(String address) {
@@ -89,7 +158,7 @@ class WalletInfo extends HiveObject {
158 String get yatEmojiId => yatEid ?? '';
159
160 bool get isShowIntroCakePayCard {
92 - if(showIntroCakePayCard == null) {
161 + if (showIntroCakePayCard == null) {
162 return type != WalletType.haven;
163 }
164 return showIntroCakePayCard!;
cw_core/lib/wallet_service.dart
+4 -2
@@ -1,9 +1,11 @@
1 +import 'package:cw_core/node.dart';
2 import 'package:cw_core/wallet_base.dart';
3 import 'package:cw_core/wallet_credentials.dart';
4 +import 'package:cw_core/wallet_info.dart';
5 import 'package:cw_core/wallet_type.dart';
6
5 -abstract class WalletService<N extends WalletCredentials,
6 - RFS extends WalletCredentials, RFK extends WalletCredentials> {
7 +abstract class WalletService<N extends WalletCredentials, RFS extends WalletCredentials,
8 + RFK extends WalletCredentials> {
9 WalletType getType();
10
11 Future<WalletBase> create(N credentials);
cw_core/lib/wallet_type.dart
+28
@@ -10,6 +10,8 @@ const walletTypes = [
10 WalletType.litecoin,
11 WalletType.haven,
12 WalletType.ethereum,
13 + WalletType.nano,
14 + WalletType.banano,
15 ];
16
17 @HiveType(typeId: WALLET_TYPE_TYPE_ID)
@@ -31,6 +33,12 @@ enum WalletType {
33
34 @HiveField(5)
35 ethereum,
36 +
37 + @HiveField(6)
38 + nano,
39 +
40 + @HiveField(7)
41 + banano,
42 }
43
44 int serializeToInt(WalletType type) {
@@ -45,6 +53,10 @@ int serializeToInt(WalletType type) {
53 return 3;
54 case WalletType.ethereum:
55 return 4;
56 + case WalletType.nano:
57 + return 5;
58 + case WalletType.banano:
59 + return 6;
60 default:
61 return -1;
62 }
@@ -62,6 +74,10 @@ WalletType deserializeFromInt(int raw) {
74 return WalletType.haven;
75 case 4:
76 return WalletType.ethereum;
77 + case 5:
78 + return WalletType.nano;
79 + case 6:
80 + return WalletType.banano;
81 default:
82 throw Exception('Unexpected token: $raw for WalletType deserializeFromInt');
83 }
@@ -79,6 +95,10 @@ String walletTypeToString(WalletType type) {
95 return 'Haven';
96 case WalletType.ethereum:
97 return 'Ethereum';
98 + case WalletType.nano:
99 + return 'Nano';
100 + case WalletType.banano:
101 + return 'Banano';
102 default:
103 return '';
104 }
@@ -96,6 +116,10 @@ String walletTypeToDisplayName(WalletType type) {
116 return 'Haven (XHV)';
117 case WalletType.ethereum:
118 return 'Ethereum (ETH)';
119 + case WalletType.nano:
120 + return 'Nano (XNO)';
121 + case WalletType.banano:
122 + return 'Banano (BAN)';
123 default:
124 return '';
125 }
@@ -113,6 +137,10 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type) {
137 return CryptoCurrency.xhv;
138 case WalletType.ethereum:
139 return CryptoCurrency.eth;
140 + case WalletType.nano:
141 + return CryptoCurrency.nano;
142 + case WalletType.banano:
143 + return CryptoCurrency.banano;
144 default:
145 throw Exception('Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency');
146 }
cw_monero/ios/Classes/monero_api.cpp
+1 -2
@@ -234,7 +234,6 @@ extern "C"
234 }
235
236 void setUnlocked(bool unlocked);
237 -
237 };
238
239 Monero::Coins *m_coins;
@@ -568,7 +567,7 @@ extern "C"
567 _preferred_inputs.insert(std::string(*preferred_inputs));
568 preferred_inputs++;
569 }
571 -
570 +
571 auto priority = static_cast<Monero::PendingTransaction::Priority>(priority_raw);
572 std::string _payment_id;
573 Monero::PendingTransaction *transaction;
cw_monero/lib/monero_wallet_service.dart
+1 -1
@@ -57,7 +57,7 @@ class MoneroWalletService extends WalletService<
57
58 final Box<WalletInfo> walletInfoSource;
59 final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
60 -
60 +
61 static bool walletFilesExist(String path) =>
62 !File(path).existsSync() && !File('$path.keys').existsSync();
63
cw_nano/lib/banano_balance.dart new
+20
@@ -0,0 +1,20 @@
1 +import 'package:cw_core/balance.dart';
2 +import 'package:cw_nano/nano_util.dart';
3 +
4 +class BananoBalance extends Balance {
5 + final BigInt currentBalance;
6 + final BigInt receivableBalance;
7 +
8 + BananoBalance({required this.currentBalance, required this.receivableBalance}) : super(0, 0) {
9 + }
10 +
11 + @override
12 + String get formattedAvailableBalance {
13 + return NanoUtil.getRawAsUsableString(currentBalance.toString(), NanoUtil.rawPerBanano);
14 + }
15 +
16 + @override
17 + String get formattedAdditionalBalance {
18 + return NanoUtil.getRawAsUsableString(receivableBalance.toString(), NanoUtil.rawPerBanano);
19 + }
20 +}
cw_nano/lib/cw_nano.dart new
+7
@@ -0,0 +1,7 @@
1 +library cw_nano;
2 +
3 +/// A Calculator.
4 +class Calculator {
5 + /// Returns [value] plus 1.
6 + int addOne(int value) => value + 1;
7 +}
cw_nano/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_nano/lib/nano_account_list.dart new
+69
@@ -0,0 +1,69 @@
1 +import 'package:cw_core/cake_hive.dart';
2 +import 'package:cw_core/nano_account.dart';
3 +import 'package:mobx/mobx.dart';
4 +import 'package:hive/hive.dart';
5 +
6 +part 'nano_account_list.g.dart';
7 +
8 +class NanoAccountList = NanoAccountListBase with _$NanoAccountList;
9 +
10 +abstract class NanoAccountListBase with Store {
11 + NanoAccountListBase(this.address)
12 + : accounts = ObservableList<NanoAccount>(),
13 + _isRefreshing = false,
14 + _isUpdating = false {
15 + refresh();
16 + }
17 +
18 + @observable
19 + ObservableList<NanoAccount> accounts;
20 + bool _isRefreshing;
21 + bool _isUpdating;
22 +
23 + String address;
24 +
25 + Future<void> update(String? address) async {
26 + if (_isUpdating) {
27 + return;
28 + }
29 +
30 + try {
31 + _isUpdating = true;
32 +
33 + final accounts = await getAll(address: address ?? this.address);
34 +
35 + if (accounts.isNotEmpty) {
36 + this.accounts.clear();
37 + this.accounts.addAll(accounts);
38 + }
39 +
40 + _isUpdating = false;
41 + } catch (e) {
42 + _isUpdating = false;
43 + rethrow;
44 + }
45 + }
46 +
47 + Future<List<NanoAccount>> getAll({String? address}) async {
48 + final box = await CakeHive.openBox<NanoAccount>(address ?? this.address);
49 +
50 + // get all accounts in box:
51 + return box.values.toList();
52 + }
53 +
54 + Future<void> addAccount({required String label}) async {
55 + final box = await CakeHive.openBox<NanoAccount>(address);
56 + final account = NanoAccount(id: box.length, label: label, balance: "0.00", isSelected: false);
57 + await box.add(account);
58 + await account.save();
59 + }
60 +
61 + Future<void> setLabelAccount({required int accountIndex, required String label}) async {
62 + final box = await CakeHive.openBox<NanoAccount>(address);
63 + final account = box.getAt(accountIndex);
64 + account!.label = label;
65 + await account.save();
66 + }
67 +
68 + void refresh() {}
69 +}
cw_nano/lib/nano_balance.dart new
+34
@@ -0,0 +1,34 @@
1 +import 'package:cw_core/balance.dart';
2 +import 'package:cw_nano/nano_util.dart';
3 +
4 +BigInt stringAmountToBigInt(String amount) {
5 + return BigInt.parse(NanoUtil.getAmountAsRaw(amount, NanoUtil.rawPerNano));
6 +}
7 +
8 +class NanoBalance extends Balance {
9 + final BigInt currentBalance;
10 + final BigInt receivableBalance;
11 + late String formattedCurrentBalance;
12 + late String formattedReceivableBalance;
13 +
14 + NanoBalance({required this.currentBalance, required this.receivableBalance}) : super(0, 0) {
15 + this.formattedCurrentBalance = "";
16 + this.formattedReceivableBalance = "";
17 + }
18 +
19 + NanoBalance.fromString(
20 + {required this.formattedCurrentBalance, required this.formattedReceivableBalance})
21 + : currentBalance = stringAmountToBigInt(formattedCurrentBalance),
22 + receivableBalance = stringAmountToBigInt(formattedReceivableBalance),
23 + super(0, 0);
24 +
25 + @override
26 + String get formattedAvailableBalance {
27 + return NanoUtil.getRawAsUsableString(currentBalance.toString(), NanoUtil.rawPerNano);
28 + }
29 +
30 + @override
31 + String get formattedAdditionalBalance {
32 + return NanoUtil.getRawAsUsableString(receivableBalance.toString(), NanoUtil.rawPerNano);
33 + }
34 +}
cw_nano/lib/nano_client.dart new
+424
@@ -0,0 +1,424 @@
1 +import 'dart:async';
2 +import 'dart:convert';
3 +
4 +import 'package:cw_core/nano_account_info_response.dart';
5 +import 'package:cw_nano/nano_balance.dart';
6 +import 'package:cw_nano/nano_transaction_model.dart';
7 +import 'package:cw_nano/nano_util.dart';
8 +import 'package:http/http.dart' as http;
9 +import 'package:nanodart/nanodart.dart';
10 +import 'package:cw_core/node.dart';
11 +
12 +class NanoClient {
13 + static const String DEFAULT_REPRESENTATIVE =
14 + "nano_38713x95zyjsqzx6nm1dsom1jmm668owkeb9913ax6nfgj15az3nu8xkx579";
15 +
16 + Node? _node;
17 + Node? _powNode;
18 +
19 + bool connect(Node node) {
20 + try {
21 + _node = node;
22 + return true;
23 + } catch (e) {
24 + return false;
25 + }
26 + }
27 +
28 + bool connectPow(Node node) {
29 + try {
30 + _powNode = node;
31 + return true;
32 + } catch (e) {
33 + return false;
34 + }
35 + }
36 +
37 + Future<NanoBalance> getBalance(String address) async {
38 + final response = await http.post(
39 + _node!.uri,
40 + headers: {"Content-Type": "application/json"},
41 + body: jsonEncode(
42 + {
43 + "action": "account_balance",
44 + "account": address,
45 + },
46 + ),
47 + );
48 + final data = await jsonDecode(response.body);
49 + final String currentBalance = data["balance"] as String;
50 + final String receivableBalance = data["receivable"] as String;
51 + final BigInt cur = BigInt.parse(currentBalance);
52 + final BigInt rec = BigInt.parse(receivableBalance);
53 + return NanoBalance(currentBalance: cur, receivableBalance: rec);
54 + }
55 +
56 + Future<AccountInfoResponse?> getAccountInfo(String address) async {
57 + try {
58 + final response = await http.post(
59 + _node!.uri,
60 + headers: {"Content-Type": "application/json"},
61 + body: jsonEncode(
62 + {
63 + "action": "account_info",
64 + "representative": "true",
65 + "account": address,
66 + },
67 + ),
68 + );
69 + final data = await jsonDecode(response.body);
70 + return AccountInfoResponse.fromJson(data as Map<String, dynamic>);
71 + } catch (e) {
72 + print("error while getting account info");
73 + return null;
74 + }
75 + }
76 +
77 + Future<String> changeRep({
78 + required String privateKey,
79 + required String repAddress,
80 + required String ourAddress,
81 + }) async {
82 + try {
83 + AccountInfoResponse? accountInfo = await getAccountInfo(ourAddress);
84 +
85 + if (accountInfo == null) {
86 + throw Exception("error while getting account info");
87 + }
88 +
89 + // construct the change block:
90 + Map<String, String> changeBlock = {
91 + "type": "state",
92 + "account": ourAddress,
93 + "previous": accountInfo.frontier,
94 + "representative": repAddress,
95 + "balance": accountInfo.balance,
96 + "link": "0000000000000000000000000000000000000000000000000000000000000000",
97 + "link_as_account": "nano_1111111111111111111111111111111111111111111111111111hifc8npp",
98 + };
99 +
100 + // sign the change block:
101 + final String hash = NanoBlocks.computeStateHash(
102 + NanoAccountType.NANO,
103 + changeBlock["account"]!,
104 + changeBlock["previous"]!,
105 + changeBlock["representative"]!,
106 + BigInt.parse(changeBlock["balance"]!),
107 + changeBlock["link"]!,
108 + );
109 + final String signature = NanoSignatures.signBlock(hash, privateKey);
110 +
111 + // get PoW for the send block:
112 + final String work = await requestWork(accountInfo.frontier);
113 +
114 + changeBlock["signature"] = signature;
115 + changeBlock["work"] = work;
116 +
117 + return await processBlock(changeBlock, "change");
118 + } catch (e) {
119 + throw Exception("error while changing representative");
120 + }
121 + }
122 +
123 + Future<String> requestWork(String hash) async {
124 + final response = await http.post(
125 + _powNode!.uri,
126 + headers: {'Content-type': 'application/json'},
127 + body: json.encode(
128 + {
129 + "action": "work_generate",
130 + "hash": hash,
131 + },
132 + ),
133 + );
134 + if (response.statusCode == 200) {
135 + final Map<String, dynamic> decoded = json.decode(response.body) as Map<String, dynamic>;
136 + if (decoded.containsKey("error")) {
137 + throw Exception("Received error ${decoded["error"]}");
138 + }
139 + return decoded["work"] as String;
140 + } else {
141 + throw Exception("Received work error ${response.body}");
142 + }
143 + }
144 +
145 + Future<String> send({
146 + required String privateKey,
147 + required String amountRaw,
148 + required String destinationAddress,
149 + }) async {
150 + final Map<String, String> sendBlock = await constructSendBlock(
151 + privateKey: privateKey,
152 + amountRaw: amountRaw,
153 + destinationAddress: destinationAddress,
154 + );
155 +
156 + return await processBlock(sendBlock, "send");
157 + }
158 +
159 + Future<String> processBlock(Map<String, String> block, String subtype) async {
160 + final headers = {"Content-Type": "application/json"};
161 + final processBody = jsonEncode({
162 + "action": "process",
163 + "json_block": "true",
164 + "subtype": subtype,
165 + "block": block,
166 + });
167 +
168 + final processResponse = await http.post(
169 + _node!.uri,
170 + headers: headers,
171 + body: processBody,
172 + );
173 +
174 + final Map<String, dynamic> decoded = json.decode(processResponse.body) as Map<String, dynamic>;
175 + if (decoded.containsKey("error")) {
176 + throw Exception("Received error ${decoded["error"]}");
177 + }
178 +
179 + // return the hash of the transaction:
180 + return decoded["hash"].toString();
181 + }
182 +
183 + Future<Map<String, String>> constructSendBlock({
184 + required String privateKey,
185 + required String amountRaw,
186 + required String destinationAddress,
187 + BigInt? balanceAfterTx,
188 + String? previousHash,
189 + }) async {
190 + try {
191 + // our address:
192 + final String publicAddress = NanoUtil.privateKeyToAddress(privateKey);
193 +
194 + // first get the current account balance:
195 + if (balanceAfterTx == null) {
196 + final BigInt currentBalance = (await getBalance(publicAddress)).currentBalance;
197 + final BigInt txAmount = BigInt.parse(amountRaw);
198 + balanceAfterTx = currentBalance - txAmount;
199 + }
200 +
201 + // get the account info (we need the frontier and representative):
202 + AccountInfoResponse? infoResponse = await getAccountInfo(publicAddress);
203 + if (infoResponse == null) {
204 + throw Exception(
205 + "error while getting account info! (we probably don't have an open account yet)");
206 + }
207 +
208 + String frontier = infoResponse.frontier;
209 + // override if provided:
210 + if (previousHash != null) {
211 + frontier = previousHash;
212 + }
213 + final String representative = infoResponse.representative;
214 + // link = destination address:
215 + final String link = NanoAccounts.extractPublicKey(destinationAddress);
216 + final String linkAsAccount = destinationAddress;
217 +
218 + // construct the send block:
219 + Map<String, String> sendBlock = {
220 + "type": "state",
221 + "account": publicAddress,
222 + "previous": frontier,
223 + "representative": representative,
224 + "balance": balanceAfterTx.toString(),
225 + "link": link,
226 + };
227 +
228 + // sign the send block:
229 + final String hash = NanoBlocks.computeStateHash(
230 + NanoAccountType.NANO,
231 + sendBlock["account"]!,
232 + sendBlock["previous"]!,
233 + sendBlock["representative"]!,
234 + BigInt.parse(sendBlock["balance"]!),
235 + sendBlock["link"]!,
236 + );
237 + final String signature = NanoSignatures.signBlock(hash, privateKey);
238 +
239 + // get PoW for the send block:
240 + final String work = await requestWork(frontier);
241 +
242 + sendBlock["link_as_account"] = linkAsAccount;
243 + sendBlock["signature"] = signature;
244 + sendBlock["work"] = work;
245 +
246 + // ready to post send block:
247 + return sendBlock;
248 + } catch (e) {
249 + print(e);
250 + rethrow;
251 + }
252 + }
253 +
254 + Future<void> receiveBlock({
255 + required String blockHash,
256 + required String source,
257 + required String amountRaw,
258 + required String destinationAddress,
259 + required String privateKey,
260 + }) async {
261 + bool openBlock = false;
262 +
263 + final headers = {
264 + "Content-Type": "application/json",
265 + };
266 +
267 + // first check if the account is open:
268 + // get the account info (we need the frontier and representative):
269 + AccountInfoResponse? infoData = await getAccountInfo(destinationAddress);
270 + String? frontier;
271 + String? representative;
272 +
273 + if (infoData == null) {
274 + // account is not open yet, we need to create an open block:
275 + openBlock = true;
276 + // we don't have a representative set yet:
277 + representative = DEFAULT_REPRESENTATIVE;
278 + // we don't have a frontier yet:
279 + frontier = "0000000000000000000000000000000000000000000000000000000000000000";
280 + } else {
281 + frontier = infoData.frontier;
282 + representative = infoData.representative;
283 + }
284 +
285 + // first get the account balance:
286 + final BigInt currentBalance = (await getBalance(destinationAddress)).currentBalance;
287 + final BigInt txAmount = BigInt.parse(amountRaw);
288 + final BigInt balanceAfterTx = currentBalance + txAmount;
289 +
290 + // link = send block hash:
291 + final String link = blockHash;
292 + // this "linkAsAccount" is meaningless:
293 + final String linkAsAccount = NanoAccounts.createAccount(NanoAccountType.NANO, blockHash);
294 +
295 + // construct the receive block:
296 + Map<String, String> receiveBlock = {
297 + "type": "state",
298 + "account": destinationAddress,
299 + "previous": frontier,
300 + "representative": representative,
301 + "balance": balanceAfterTx.toString(),
302 + "link": link,
303 + "link_as_account": linkAsAccount,
304 + };
305 +
306 + // sign the receive block:
307 + final String hash = NanoBlocks.computeStateHash(
308 + NanoAccountType.NANO,
309 + receiveBlock["account"]!,
310 + receiveBlock["previous"]!,
311 + receiveBlock["representative"]!,
312 + BigInt.parse(receiveBlock["balance"]!),
313 + receiveBlock["link"]!,
314 + );
315 + final String signature = NanoSignatures.signBlock(hash, privateKey);
316 +
317 + // get PoW for the receive block:
318 + String? work;
319 + if (openBlock) {
320 + work = await requestWork(NanoAccounts.extractPublicKey(destinationAddress));
321 + } else {
322 + work = await requestWork(frontier);
323 + }
324 + receiveBlock["link_as_account"] = linkAsAccount;
325 + receiveBlock["signature"] = signature;
326 + receiveBlock["work"] = work;
327 +
328 + // process the receive block:
329 +
330 + final processBody = jsonEncode({
331 + "action": "process",
332 + "json_block": "true",
333 + "subtype": "receive",
334 + "block": receiveBlock,
335 + });
336 + final processResponse = await http.post(
337 + _node!.uri,
338 + headers: headers,
339 + body: processBody,
340 + );
341 +
342 + final Map<String, dynamic> decoded = json.decode(processResponse.body) as Map<String, dynamic>;
343 + if (decoded.containsKey("error")) {
344 + throw Exception("Received error ${decoded["error"]}");
345 + }
346 + }
347 +
348 + // returns the number of blocks received:
349 + Future<int> confirmAllReceivable({
350 + required String destinationAddress,
351 + required String privateKey,
352 + }) async {
353 + final receivableResponse = await http.post(_node!.uri,
354 + headers: {"Content-Type": "application/json"},
355 + body: jsonEncode({
356 + "action": "receivable",
357 + "account": destinationAddress,
358 + "count": "-1",
359 + "source": true,
360 + }));
361 +
362 + final receivableData = await jsonDecode(receivableResponse.body);
363 + if (receivableData["blocks"] == "" || receivableData["blocks"] == null) {
364 + return 0;
365 + }
366 +
367 + dynamic blocks;
368 + if (receivableData["blocks"] is List<dynamic>) {
369 + var listBlocks = receivableData["blocks"] as List<dynamic>;
370 + if (listBlocks.isEmpty) {
371 + return 0;
372 + }
373 + blocks = {for (var block in listBlocks) block['hash']: block};
374 + } else {
375 + blocks = receivableData["blocks"] as Map<String, dynamic>;
376 + }
377 +
378 + blocks = blocks as Map<String, dynamic>;
379 +
380 + // confirm all receivable blocks:
381 + for (final blockHash in blocks.keys) {
382 + final block = blocks[blockHash];
383 + final String amountRaw = block["amount"] as String;
384 + final String source = block["source"] as String;
385 + await receiveBlock(
386 + blockHash: blockHash,
387 + source: source,
388 + amountRaw: amountRaw,
389 + privateKey: privateKey,
390 + destinationAddress: destinationAddress,
391 + );
392 + // a bit of a hack:
393 + await Future<void>.delayed(const Duration(seconds: 2));
394 + }
395 +
396 + return blocks.keys.length;
397 + }
398 +
399 + void stop() {}
400 +
401 + Future<List<NanoTransactionModel>> fetchTransactions(String address) async {
402 + try {
403 + final response = await http.post(_node!.uri,
404 + headers: {"Content-Type": "application/json"},
405 + body: jsonEncode({
406 + "action": "account_history",
407 + "account": address,
408 + "count": "250", // TODO: pick a number
409 + // "raw": true,
410 + }));
411 + final data = await jsonDecode(response.body);
412 + final transactions = data["history"] is List ? data["history"] as List<dynamic> : [];
413 +
414 + // Map the transactions list to NanoTransactionModel using the factory
415 + // reversed so that the DateTime is correct when local_timestamp is absent
416 + return transactions.reversed
417 + .map<NanoTransactionModel>((transaction) => NanoTransactionModel.fromJson(transaction))
418 + .toList();
419 + } catch (e) {
420 + print(e);
421 + return [];
422 + }
423 + }
424 +}
cw_nano/lib/nano_mnemonic.dart new
+2088
@@ -0,0 +1,2088 @@
1 +import 'package:bip39/bip39.dart' as bip39;
2 +import 'package:nanodart/nanodart.dart';
3 +
4 +class NanoMnemonicIsIncorrectException implements Exception {
5 + @override
6 + String toString() =>
7 + 'Nano mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.';
8 +}
9 +
10 +class NanoMnemomics {
11 + /// Converts a nano seed to a 24-word mnemonic word list
12 + static List<String> seedToMnemonic(String seed) {
13 + if (!NanoSeeds.isValidSeed(seed)) {
14 + throw Exception('Invalid Seed');
15 + }
16 + String words = bip39.entropyToMnemonic(seed);
17 + return words.split(' ');
18 + }
19 +
20 + /// Convert a 24-word mnemonic word list to a nano seed
21 + static String mnemonicListToSeed(List<String> words) {
22 + if (words.length != 24) {
23 + throw Exception('Expected a 24-word list, got a ${words.length} list');
24 + }
25 + return bip39.mnemonicToEntropy(words.join(' ')).toUpperCase();
26 + }
27 +
28 + /// Validate a mnemonic word list
29 + static bool validateMnemonic(List<String> words) {
30 + return bip39.validateMnemonic(words.join(' '));
31 + }
32 +
33 + /// Validate a specific menmonic word
34 + static bool isValidWord(String word) {
35 + return WORDLIST.contains(word);
36 + }
37 +
38 + static const WORDLIST = [
39 + "abandon",
40 + "ability",
41 + "able",
42 + "about",
43 + "above",
44 + "absent",
45 + "absorb",
46 + "abstract",
47 + "absurd",
48 + "abuse",
49 + "access",
50 + "accident",
51 + "account",
52 + "accuse",
53 + "achieve",
54 + "acid",
55 + "acoustic",
56 + "acquire",
57 + "across",
58 + "act",
59 + "action",
60 + "actor",
61 + "actress",
62 + "actual",
63 + "adapt",
64 + "add",
65 + "addict",
66 + "address",
67 + "adjust",
68 + "admit",
69 + "adult",
70 + "advance",
71 + "advice",
72 + "aerobic",
73 + "affair",
74 + "afford",
75 + "afraid",
76 + "again",
77 + "age",
78 + "agent",
79 + "agree",
80 + "ahead",
81 + "aim",
82 + "air",
83 + "airport",
84 + "aisle",
85 + "alarm",
86 + "album",
87 + "alcohol",
88 + "alert",
89 + "alien",
90 + "all",
91 + "alley",
92 + "allow",
93 + "almost",
94 + "alone",
95 + "alpha",
96 + "already",
97 + "also",
98 + "alter",
99 + "always",
100 + "amateur",
101 + "amazing",
102 + "among",
103 + "amount",
104 + "amused",
105 + "analyst",
106 + "anchor",
107 + "ancient",
108 + "anger",
109 + "angle",
110 + "angry",
111 + "animal",
112 + "ankle",
113 + "announce",
114 + "annual",
115 + "another",
116 + "answer",
117 + "antenna",
118 + "antique",
119 + "anxiety",
120 + "any",
121 + "apart",
122 + "apology",
123 + "appear",
124 + "apple",
125 + "approve",
126 + "april",
127 + "arch",
128 + "arctic",
129 + "area",
130 + "arena",
131 + "argue",
132 + "arm",
133 + "armed",
134 + "armor",
135 + "army",
136 + "around",
137 + "arrange",
138 + "arrest",
139 + "arrive",
140 + "arrow",
141 + "art",
142 + "artefact",
143 + "artist",
144 + "artwork",
145 + "ask",
146 + "aspect",
147 + "assault",
148 + "asset",
149 + "assist",
150 + "assume",
151 + "asthma",
152 + "athlete",
153 + "atom",
154 + "attack",
155 + "attend",
156 + "attitude",
157 + "attract",
158 + "auction",
159 + "audit",
160 + "august",
161 + "aunt",
162 + "author",
163 + "auto",
164 + "autumn",
165 + "average",
166 + "avocado",
167 + "avoid",
168 + "awake",
169 + "aware",
170 + "away",
171 + "awesome",
172 + "awful",
173 + "awkward",
174 + "axis",
175 + "baby",
176 + "bachelor",
177 + "bacon",
178 + "badge",
179 + "bag",
180 + "balance",
181 + "balcony",
182 + "ball",
183 + "bamboo",
184 + "banana",
185 + "banner",
186 + "bar",
187 + "barely",
188 + "bargain",
189 + "barrel",
190 + "base",
191 + "basic",
192 + "basket",
193 + "battle",
194 + "beach",
195 + "bean",
196 + "beauty",
197 + "because",
198 + "become",
199 + "beef",
200 + "before",
201 + "begin",
202 + "behave",
203 + "behind",
204 + "believe",
205 + "below",
206 + "belt",
207 + "bench",
208 + "benefit",
209 + "best",
210 + "betray",
211 + "better",
212 + "between",
213 + "beyond",
214 + "bicycle",
215 + "bid",
216 + "bike",
217 + "bind",
218 + "biology",
219 + "bird",
220 + "birth",
221 + "bitter",
222 + "black",
223 + "blade",
224 + "blame",
225 + "blanket",
226 + "blast",
227 + "bleak",
228 + "bless",
229 + "blind",
230 + "blood",
231 + "blossom",
232 + "blouse",
233 + "blue",
234 + "blur",
235 + "blush",
236 + "board",
237 + "boat",
238 + "body",
239 + "boil",
240 + "bomb",
241 + "bone",
242 + "bonus",
243 + "book",
244 + "boost",
245 + "border",
246 + "boring",
247 + "borrow",
248 + "boss",
249 + "bottom",
250 + "bounce",
251 + "box",
252 + "boy",
253 + "bracket",
254 + "brain",
255 + "brand",
256 + "brass",
257 + "brave",
258 + "bread",
259 + "breeze",
260 + "brick",
261 + "bridge",
262 + "brief",
263 + "bright",
264 + "bring",
265 + "brisk",
266 + "broccoli",
267 + "broken",
268 + "bronze",
269 + "broom",
270 + "brother",
271 + "brown",
272 + "brush",
273 + "bubble",
274 + "buddy",
275 + "budget",
276 + "buffalo",
277 + "build",
278 + "bulb",
279 + "bulk",
280 + "bullet",
281 + "bundle",
282 + "bunker",
283 + "burden",
284 + "burger",
285 + "burst",
286 + "bus",
287 + "business",
288 + "busy",
289 + "butter",
290 + "buyer",
291 + "buzz",
292 + "cabbage",
293 + "cabin",
294 + "cable",
295 + "cactus",
296 + "cage",
297 + "cake",
298 + "call",
299 + "calm",
300 + "camera",
301 + "camp",
302 + "can",
303 + "canal",
304 + "cancel",
305 + "candy",
306 + "cannon",
307 + "canoe",
308 + "canvas",
309 + "canyon",
310 + "capable",
311 + "capital",
312 + "captain",
313 + "car",
314 + "carbon",
315 + "card",
316 + "cargo",
317 + "carpet",
318 + "carry",
319 + "cart",
320 + "case",
321 + "cash",
322 + "casino",
323 + "castle",
324 + "casual",
325 + "cat",
326 + "catalog",
327 + "catch",
328 + "category",
329 + "cattle",
330 + "caught",
331 + "cause",
332 + "caution",
333 + "cave",
334 + "ceiling",
335 + "celery",
336 + "cement",
337 + "census",
338 + "century",
339 + "cereal",
340 + "certain",
341 + "chair",
342 + "chalk",
343 + "champion",
344 + "change",
345 + "chaos",
346 + "chapter",
347 + "charge",
348 + "chase",
349 + "chat",
350 + "cheap",
351 + "check",
352 + "cheese",
353 + "chef",
354 + "cherry",
355 + "chest",
356 + "chicken",
357 + "chief",
358 + "child",
359 + "chimney",
360 + "choice",
361 + "choose",
362 + "chronic",
363 + "chuckle",
364 + "chunk",
365 + "churn",
366 + "cigar",
367 + "cinnamon",
368 + "circle",
369 + "citizen",
370 + "city",
371 + "civil",
372 + "claim",
373 + "clap",
374 + "clarify",
375 + "claw",
376 + "clay",
377 + "clean",
378 + "clerk",
379 + "clever",
380 + "click",
381 + "client",
382 + "cliff",
383 + "climb",
384 + "clinic",
385 + "clip",
386 + "clock",
387 + "clog",
388 + "close",
389 + "cloth",
390 + "cloud",
391 + "clown",
392 + "club",
393 + "clump",
394 + "cluster",
395 + "clutch",
396 + "coach",
397 + "coast",
398 + "coconut",
399 + "code",
400 + "coffee",
401 + "coil",
402 + "coin",
403 + "collect",
404 + "color",
405 + "column",
406 + "combine",
407 + "come",
408 + "comfort",
409 + "comic",
410 + "common",
411 + "company",
412 + "concert",
413 + "conduct",
414 + "confirm",
415 + "congress",
416 + "connect",
417 + "consider",
418 + "control",
419 + "convince",
420 + "cook",
421 + "cool",
422 + "copper",
423 + "copy",
424 + "coral",
425 + "core",
426 + "corn",
427 + "correct",
428 + "cost",
429 + "cotton",
430 + "couch",
431 + "country",
432 + "couple",
433 + "course",
434 + "cousin",
435 + "cover",
436 + "coyote",
437 + "crack",
438 + "cradle",
439 + "craft",
440 + "cram",
441 + "crane",
442 + "crash",
443 + "crater",
444 + "crawl",
445 + "crazy",
446 + "cream",
447 + "credit",
448 + "creek",
449 + "crew",
450 + "cricket",
451 + "crime",
452 + "crisp",
453 + "critic",
454 + "crop",
455 + "cross",
456 + "crouch",
457 + "crowd",
458 + "crucial",
459 + "cruel",
460 + "cruise",
461 + "crumble",
462 + "crunch",
463 + "crush",
464 + "cry",
465 + "crystal",
466 + "cube",
467 + "culture",
468 + "cup",
469 + "cupboard",
470 + "curious",
471 + "current",
472 + "curtain",
473 + "curve",
474 + "cushion",
475 + "custom",
476 + "cute",
477 + "cycle",
478 + "dad",
479 + "damage",
480 + "damp",
481 + "dance",
482 + "danger",
483 + "daring",
484 + "dash",
485 + "daughter",
486 + "dawn",
487 + "day",
488 + "deal",
489 + "debate",
490 + "debris",
491 + "decade",
492 + "december",
493 + "decide",
494 + "decline",
495 + "decorate",
496 + "decrease",
497 + "deer",
498 + "defense",
499 + "define",
500 + "defy",
501 + "degree",
502 + "delay",
503 + "deliver",
504 + "demand",
505 + "demise",
506 + "denial",
507 + "dentist",
508 + "deny",
509 + "depart",
510 + "depend",
511 + "deposit",
512 + "depth",
513 + "deputy",
514 + "derive",
515 + "describe",
516 + "desert",
517 + "design",
518 + "desk",
519 + "despair",
520 + "destroy",
521 + "detail",
522 + "detect",
523 + "develop",
524 + "device",
525 + "devote",
526 + "diagram",
527 + "dial",
528 + "diamond",
529 + "diary",
530 + "dice",
531 + "diesel",
532 + "diet",
533 + "differ",
534 + "digital",
535 + "dignity",
536 + "dilemma",
537 + "dinner",
538 + "dinosaur",
539 + "direct",
540 + "dirt",
541 + "disagree",
542 + "discover",
543 + "disease",
544 + "dish",
545 + "dismiss",
546 + "disorder",
547 + "display",
548 + "distance",
549 + "divert",
550 + "divide",
551 + "divorce",
552 + "dizzy",
553 + "doctor",
554 + "document",
555 + "dog",
556 + "doll",
557 + "dolphin",
558 + "domain",
559 + "donate",
560 + "donkey",
561 + "donor",
562 + "door",
563 + "dose",
564 + "double",
565 + "dove",
566 + "draft",
567 + "dragon",
568 + "drama",
569 + "drastic",
570 + "draw",
571 + "dream",
572 + "dress",
573 + "drift",
574 + "drill",
575 + "drink",
576 + "drip",
577 + "drive",
578 + "drop",
579 + "drum",
580 + "dry",
581 + "duck",
582 + "dumb",
583 + "dune",
584 + "during",
585 + "dust",
586 + "dutch",
587 + "duty",
588 + "dwarf",
589 + "dynamic",
590 + "eager",
591 + "eagle",
592 + "early",
593 + "earn",
594 + "earth",
595 + "easily",
596 + "east",
597 + "easy",
598 + "echo",
599 + "ecology",
600 + "economy",
601 + "edge",
602 + "edit",
603 + "educate",
604 + "effort",
605 + "egg",
606 + "eight",
607 + "either",
608 + "elbow",
609 + "elder",
610 + "electric",
611 + "elegant",
612 + "element",
613 + "elephant",
614 + "elevator",
615 + "elite",
616 + "else",
617 + "embark",
618 + "embody",
619 + "embrace",
620 + "emerge",
621 + "emotion",
622 + "employ",
623 + "empower",
624 + "empty",
625 + "enable",
626 + "enact",
627 + "end",
628 + "endless",
629 + "endorse",
630 + "enemy",
631 + "energy",
632 + "enforce",
633 + "engage",
634 + "engine",
635 + "enhance",
636 + "enjoy",
637 + "enlist",
638 + "enough",
639 + "enrich",
640 + "enroll",
641 + "ensure",
642 + "enter",
643 + "entire",
644 + "entry",
645 + "envelope",
646 + "episode",
647 + "equal",
648 + "equip",
649 + "era",
650 + "erase",
651 + "erode",
652 + "erosion",
653 + "error",
654 + "erupt",
655 + "escape",
656 + "essay",
657 + "essence",
658 + "estate",
659 + "eternal",
660 + "ethics",
661 + "evidence",
662 + "evil",
663 + "evoke",
664 + "evolve",
665 + "exact",
666 + "example",
667 + "excess",
668 + "exchange",
669 + "excite",
670 + "exclude",
671 + "excuse",
672 + "execute",
673 + "exercise",
674 + "exhaust",
675 + "exhibit",
676 + "exile",
677 + "exist",
678 + "exit",
679 + "exotic",
680 + "expand",
681 + "expect",
682 + "expire",
683 + "explain",
684 + "expose",
685 + "express",
686 + "extend",
687 + "extra",
688 + "eye",
689 + "eyebrow",
690 + "fabric",
691 + "face",
692 + "faculty",
693 + "fade",
694 + "faint",
695 + "faith",
696 + "fall",
697 + "false",
698 + "fame",
699 + "family",
700 + "famous",
701 + "fan",
702 + "fancy",
703 + "fantasy",
704 + "farm",
705 + "fashion",
706 + "fat",
707 + "fatal",
708 + "father",
709 + "fatigue",
710 + "fault",
711 + "favorite",
712 + "feature",
713 + "february",
714 + "federal",
715 + "fee",
716 + "feed",
717 + "feel",
718 + "female",
719 + "fence",
720 + "festival",
721 + "fetch",
722 + "fever",
723 + "few",
724 + "fiber",
725 + "fiction",
726 + "field",
727 + "figure",
728 + "file",
729 + "film",
730 + "filter",
731 + "final",
732 + "find",
733 + "fine",
734 + "finger",
735 + "finish",
736 + "fire",
737 + "firm",
738 + "first",
739 + "fiscal",
740 + "fish",
741 + "fit",
742 + "fitness",
743 + "fix",
744 + "flag",
745 + "flame",
746 + "flash",
747 + "flat",
748 + "flavor",
749 + "flee",
750 + "flight",
751 + "flip",
752 + "float",
753 + "flock",
754 + "floor",
755 + "flower",
756 + "fluid",
757 + "flush",
758 + "fly",
759 + "foam",
760 + "focus",
761 + "fog",
762 + "foil",
763 + "fold",
764 + "follow",
765 + "food",
766 + "foot",
767 + "force",
768 + "forest",
769 + "forget",
770 + "fork",
771 + "fortune",
772 + "forum",
773 + "forward",
774 + "fossil",
775 + "foster",
776 + "found",
777 + "fox",
778 + "fragile",
779 + "frame",
780 + "frequent",
781 + "fresh",
782 + "friend",
783 + "fringe",
784 + "frog",
785 + "front",
786 + "frost",
787 + "frown",
788 + "frozen",
789 + "fruit",
790 + "fuel",
791 + "fun",
792 + "funny",
793 + "furnace",
794 + "fury",
795 + "future",
796 + "gadget",
797 + "gain",
798 + "galaxy",
799 + "gallery",
800 + "game",
801 + "gap",
802 + "garage",
803 + "garbage",
804 + "garden",
805 + "garlic",
806 + "garment",
807 + "gas",
808 + "gasp",
809 + "gate",
810 + "gather",
811 + "gauge",
812 + "gaze",
813 + "general",
814 + "genius",
815 + "genre",
816 + "gentle",
817 + "genuine",
818 + "gesture",
819 + "ghost",
820 + "giant",
821 + "gift",
822 + "giggle",
823 + "ginger",
824 + "giraffe",
825 + "girl",
826 + "give",
827 + "glad",
828 + "glance",
829 + "glare",
830 + "glass",
831 + "glide",
832 + "glimpse",
833 + "globe",
834 + "gloom",
835 + "glory",
836 + "glove",
837 + "glow",
838 + "glue",
839 + "goat",
840 + "goddess",
841 + "gold",
842 + "good",
843 + "goose",
844 + "gorilla",
845 + "gospel",
846 + "gossip",
847 + "govern",
848 + "gown",
849 + "grab",
850 + "grace",
851 + "grain",
852 + "grant",
853 + "grape",
854 + "grass",
855 + "gravity",
856 + "great",
857 + "green",
858 + "grid",
859 + "grief",
860 + "grit",
861 + "grocery",
862 + "group",
863 + "grow",
864 + "grunt",
865 + "guard",
866 + "guess",
867 + "guide",
868 + "guilt",
869 + "guitar",
870 + "gun",
871 + "gym",
872 + "habit",
873 + "hair",
874 + "half",
875 + "hammer",
876 + "hamster",
877 + "hand",
878 + "happy",
879 + "harbor",
880 + "hard",
881 + "harsh",
882 + "harvest",
883 + "hat",
884 + "have",
885 + "hawk",
886 + "hazard",
887 + "head",
888 + "health",
889 + "heart",
890 + "heavy",
891 + "hedgehog",
892 + "height",
893 + "hello",
894 + "helmet",
895 + "help",
896 + "hen",
897 + "hero",
898 + "hidden",
899 + "high",
900 + "hill",
901 + "hint",
902 + "hip",
903 + "hire",
904 + "history",
905 + "hobby",
906 + "hockey",
907 + "hold",
908 + "hole",
909 + "holiday",
910 + "hollow",
911 + "home",
912 + "honey",
913 + "hood",
914 + "hope",
915 + "horn",
916 + "horror",
917 + "horse",
918 + "hospital",
919 + "host",
920 + "hotel",
921 + "hour",
922 + "hover",
923 + "hub",
924 + "huge",
925 + "human",
926 + "humble",
927 + "humor",
928 + "hundred",
929 + "hungry",
930 + "hunt",
931 + "hurdle",
932 + "hurry",
933 + "hurt",
934 + "husband",
935 + "hybrid",
936 + "ice",
937 + "icon",
938 + "idea",
939 + "identify",
940 + "idle",
941 + "ignore",
942 + "ill",
943 + "illegal",
944 + "illness",
945 + "image",
946 + "imitate",
947 + "immense",
948 + "immune",
949 + "impact",
950 + "impose",
951 + "improve",
952 + "impulse",
953 + "inch",
954 + "include",
955 + "income",
956 + "increase",
957 + "index",
958 + "indicate",
959 + "indoor",
960 + "industry",
961 + "infant",
962 + "inflict",
963 + "inform",
964 + "inhale",
965 + "inherit",
966 + "initial",
967 + "inject",
968 + "injury",
969 + "inmate",
970 + "inner",
971 + "innocent",
972 + "input",
973 + "inquiry",
974 + "insane",
975 + "insect",
976 + "inside",
977 + "inspire",
978 + "install",
979 + "intact",
980 + "interest",
981 + "into",
982 + "invest",
983 + "invite",
984 + "involve",
985 + "iron",
986 + "island",
987 + "isolate",
988 + "issue",
989 + "item",
990 + "ivory",
991 + "jacket",
992 + "jaguar",
993 + "jar",
994 + "jazz",
995 + "jealous",
996 + "jeans",
997 + "jelly",
998 + "jewel",
999 + "job",
1000 + "join",
1001 + "joke",
1002 + "journey",
1003 + "joy",
1004 + "judge",
1005 + "juice",
1006 + "jump",
1007 + "jungle",
1008 + "junior",
1009 + "junk",
1010 + "just",
1011 + "kangaroo",
1012 + "keen",
1013 + "keep",
1014 + "ketchup",
1015 + "key",
1016 + "kick",
1017 + "kid",
1018 + "kidney",
1019 + "kind",
1020 + "kingdom",
1021 + "kiss",
1022 + "kit",
1023 + "kitchen",
1024 + "kite",
1025 + "kitten",
1026 + "kiwi",
1027 + "knee",
1028 + "knife",
1029 + "knock",
1030 + "know",
1031 + "lab",
1032 + "label",
1033 + "labor",
1034 + "ladder",
1035 + "lady",
1036 + "lake",
1037 + "lamp",
1038 + "language",
1039 + "laptop",
1040 + "large",
1041 + "later",
1042 + "latin",
1043 + "laugh",
1044 + "laundry",
1045 + "lava",
1046 + "law",
1047 + "lawn",
1048 + "lawsuit",
1049 + "layer",
1050 + "lazy",
1051 + "leader",
1052 + "leaf",
1053 + "learn",
1054 + "leave",
1055 + "lecture",
1056 + "left",
1057 + "leg",
1058 + "legal",
1059 + "legend",
1060 + "leisure",
1061 + "lemon",
1062 + "lend",
1063 + "length",
1064 + "lens",
1065 + "leopard",
1066 + "lesson",
1067 + "letter",
1068 + "level",
1069 + "liar",
1070 + "liberty",
1071 + "library",
1072 + "license",
1073 + "life",
1074 + "lift",
1075 + "light",
1076 + "like",
1077 + "limb",
1078 + "limit",
1079 + "link",
1080 + "lion",
1081 + "liquid",
1082 + "list",
1083 + "little",
1084 + "live",
1085 + "lizard",
1086 + "load",
1087 + "loan",
1088 + "lobster",
1089 + "local",
1090 + "lock",
1091 + "logic",
1092 + "lonely",
1093 + "long",
1094 + "loop",
1095 + "lottery",
1096 + "loud",
1097 + "lounge",
1098 + "love",
1099 + "loyal",
1100 + "lucky",
1101 + "luggage",
1102 + "lumber",
1103 + "lunar",
1104 + "lunch",
1105 + "luxury",
1106 + "lyrics",
1107 + "machine",
1108 + "mad",
1109 + "magic",
1110 + "magnet",
1111 + "maid",
1112 + "mail",
1113 + "main",
1114 + "major",
1115 + "make",
1116 + "mammal",
1117 + "man",
1118 + "manage",
1119 + "mandate",
1120 + "mango",
1121 + "mansion",
1122 + "manual",
1123 + "maple",
1124 + "marble",
1125 + "march",
1126 + "margin",
1127 + "marine",
1128 + "market",
1129 + "marriage",
1130 + "mask",
1131 + "mass",
1132 + "master",
1133 + "match",
1134 + "material",
1135 + "math",
1136 + "matrix",
1137 + "matter",
1138 + "maximum",
1139 + "maze",
1140 + "meadow",
1141 + "mean",
1142 + "measure",
1143 + "meat",
1144 + "mechanic",
1145 + "medal",
1146 + "media",
1147 + "melody",
1148 + "melt",
1149 + "member",
1150 + "memory",
1151 + "mention",
1152 + "menu",
1153 + "mercy",
1154 + "merge",
1155 + "merit",
1156 + "merry",
1157 + "mesh",
1158 + "message",
1159 + "metal",
1160 + "method",
1161 + "middle",
1162 + "midnight",
1163 + "milk",
1164 + "million",
1165 + "mimic",
1166 + "mind",
1167 + "minimum",
1168 + "minor",
1169 + "minute",
1170 + "miracle",
1171 + "mirror",
1172 + "misery",
1173 + "miss",
1174 + "mistake",
1175 + "mix",
1176 + "mixed",
1177 + "mixture",
1178 + "mobile",
1179 + "model",
1180 + "modify",
1181 + "mom",
1182 + "moment",
1183 + "monitor",
1184 + "monkey",
1185 + "monster",
1186 + "month",
1187 + "moon",
1188 + "moral",
1189 + "more",
1190 + "morning",
1191 + "mosquito",
1192 + "mother",
1193 + "motion",
1194 + "motor",
1195 + "mountain",
1196 + "mouse",
1197 + "move",
1198 + "movie",
1199 + "much",
1200 + "muffin",
1201 + "mule",
1202 + "multiply",
1203 + "muscle",
1204 + "museum",
1205 + "mushroom",
1206 + "music",
1207 + "must",
1208 + "mutual",
1209 + "myself",
1210 + "mystery",
1211 + "myth",
1212 + "naive",
1213 + "name",
1214 + "napkin",
1215 + "narrow",
1216 + "nasty",
1217 + "nation",
1218 + "nature",
1219 + "near",
1220 + "neck",
1221 + "need",
1222 + "negative",
1223 + "neglect",
1224 + "neither",
1225 + "nephew",
1226 + "nerve",
1227 + "nest",
1228 + "net",
1229 + "network",
1230 + "neutral",
1231 + "never",
1232 + "news",
1233 + "next",
1234 + "nice",
1235 + "night",
1236 + "noble",
1237 + "noise",
1238 + "nominee",
1239 + "noodle",
1240 + "normal",
1241 + "north",
1242 + "nose",
1243 + "notable",
1244 + "note",
1245 + "nothing",
1246 + "notice",
1247 + "novel",
1248 + "now",
1249 + "nuclear",
1250 + "number",
1251 + "nurse",
1252 + "nut",
1253 + "oak",
1254 + "obey",
1255 + "object",
1256 + "oblige",
1257 + "obscure",
1258 + "observe",
1259 + "obtain",
1260 + "obvious",
1261 + "occur",
1262 + "ocean",
1263 + "october",
1264 + "odor",
1265 + "off",
1266 + "offer",
1267 + "office",
1268 + "often",
1269 + "oil",
1270 + "okay",
1271 + "old",
1272 + "olive",
1273 + "olympic",
1274 + "omit",
1275 + "once",
1276 + "one",
1277 + "onion",
1278 + "online",
1279 + "only",
1280 + "open",
1281 + "opera",
1282 + "opinion",
1283 + "oppose",
1284 + "option",
1285 + "orange",
1286 + "orbit",
1287 + "orchard",
1288 + "order",
1289 + "ordinary",
1290 + "organ",
1291 + "orient",
1292 + "original",
1293 + "orphan",
1294 + "ostrich",
1295 + "other",
1296 + "outdoor",
1297 + "outer",
1298 + "output",
1299 + "outside",
1300 + "oval",
1301 + "oven",
1302 + "over",
1303 + "own",
1304 + "owner",
1305 + "oxygen",
1306 + "oyster",
1307 + "ozone",
1308 + "pact",
1309 + "paddle",
1310 + "page",
1311 + "pair",
1312 + "palace",
1313 + "palm",
1314 + "panda",
1315 + "panel",
1316 + "panic",
1317 + "panther",
1318 + "paper",
1319 + "parade",
1320 + "parent",
1321 + "park",
1322 + "parrot",
1323 + "party",
1324 + "pass",
1325 + "patch",
1326 + "path",
1327 + "patient",
1328 + "patrol",
1329 + "pattern",
1330 + "pause",
1331 + "pave",
1332 + "payment",
1333 + "peace",
1334 + "peanut",
1335 + "pear",
1336 + "peasant",
1337 + "pelican",
1338 + "pen",
1339 + "penalty",
1340 + "pencil",
1341 + "people",
1342 + "pepper",
1343 + "perfect",
1344 + "permit",
1345 + "person",
1346 + "pet",
1347 + "phone",
1348 + "photo",
1349 + "phrase",
1350 + "physical",
1351 + "piano",
1352 + "picnic",
1353 + "picture",
1354 + "piece",
1355 + "pig",
1356 + "pigeon",
1357 + "pill",
1358 + "pilot",
1359 + "pink",
1360 + "pioneer",
1361 + "pipe",
1362 + "pistol",
1363 + "pitch",
1364 + "pizza",
1365 + "place",
1366 + "planet",
1367 + "plastic",
1368 + "plate",
1369 + "play",
1370 + "please",
1371 + "pledge",
1372 + "pluck",
1373 + "plug",
1374 + "plunge",
1375 + "poem",
1376 + "poet",
1377 + "point",
1378 + "polar",
1379 + "pole",
1380 + "police",
1381 + "pond",
1382 + "pony",
1383 + "pool",
1384 + "popular",
1385 + "portion",
1386 + "position",
1387 + "possible",
1388 + "post",
1389 + "potato",
1390 + "pottery",
1391 + "poverty",
1392 + "powder",
1393 + "power",
1394 + "practice",
1395 + "praise",
1396 + "predict",
1397 + "prefer",
1398 + "prepare",
1399 + "present",
1400 + "pretty",
1401 + "prevent",
1402 + "price",
1403 + "pride",
1404 + "primary",
1405 + "print",
1406 + "priority",
1407 + "prison",
1408 + "private",
1409 + "prize",
1410 + "problem",
1411 + "process",
1412 + "produce",
1413 + "profit",
1414 + "program",
1415 + "project",
1416 + "promote",
1417 + "proof",
1418 + "property",
1419 + "prosper",
1420 + "protect",
1421 + "proud",
1422 + "provide",
1423 + "public",
1424 + "pudding",
1425 + "pull",
1426 + "pulp",
1427 + "pulse",
1428 + "pumpkin",
1429 + "punch",
1430 + "pupil",
1431 + "puppy",
1432 + "purchase",
1433 + "purity",
1434 + "purpose",
1435 + "purse",
1436 + "push",
1437 + "put",
1438 + "puzzle",
1439 + "pyramid",
1440 + "quality",
1441 + "quantum",
1442 + "quarter",
1443 + "question",
1444 + "quick",
1445 + "quit",
1446 + "quiz",
1447 + "quote",
1448 + "rabbit",
1449 + "raccoon",
1450 + "race",
1451 + "rack",
1452 + "radar",
1453 + "radio",
1454 + "rail",
1455 + "rain",
1456 + "raise",
1457 + "rally",
1458 + "ramp",
1459 + "ranch",
1460 + "random",
1461 + "range",
1462 + "rapid",
1463 + "rare",
1464 + "rate",
1465 + "rather",
1466 + "raven",
1467 + "raw",
1468 + "razor",
1469 + "ready",
1470 + "real",
1471 + "reason",
1472 + "rebel",
1473 + "rebuild",
1474 + "recall",
1475 + "receive",
1476 + "recipe",
1477 + "record",
1478 + "recycle",
1479 + "reduce",
1480 + "reflect",
1481 + "reform",
1482 + "refuse",
1483 + "region",
1484 + "regret",
1485 + "regular",
1486 + "reject",
1487 + "relax",
1488 + "release",
1489 + "relief",
1490 + "rely",
1491 + "remain",
1492 + "remember",
1493 + "remind",
1494 + "remove",
1495 + "render",
1496 + "renew",
1497 + "rent",
1498 + "reopen",
1499 + "repair",
1500 + "repeat",
1501 + "replace",
1502 + "report",
1503 + "require",
1504 + "rescue",
1505 + "resemble",
1506 + "resist",
1507 + "resource",
1508 + "response",
1509 + "result",
1510 + "retire",
1511 + "retreat",
1512 + "return",
1513 + "reunion",
1514 + "reveal",
1515 + "review",
1516 + "reward",
1517 + "rhythm",
1518 + "rib",
1519 + "ribbon",
1520 + "rice",
1521 + "rich",
1522 + "ride",
1523 + "ridge",
1524 + "rifle",
1525 + "right",
1526 + "rigid",
1527 + "ring",
1528 + "riot",
1529 + "ripple",
1530 + "risk",
1531 + "ritual",
1532 + "rival",
1533 + "river",
1534 + "road",
1535 + "roast",
1536 + "robot",
1537 + "robust",
1538 + "rocket",
1539 + "romance",
1540 + "roof",
1541 + "rookie",
1542 + "room",
1543 + "rose",
1544 + "rotate",
1545 + "rough",
1546 + "round",
1547 + "route",
1548 + "royal",
1549 + "rubber",
1550 + "rude",
1551 + "rug",
1552 + "rule",
1553 + "run",
1554 + "runway",
1555 + "rural",
1556 + "sad",
1557 + "saddle",
1558 + "sadness",
1559 + "safe",
1560 + "sail",
1561 + "salad",
1562 + "salmon",
1563 + "salon",
1564 + "salt",
1565 + "salute",
1566 + "same",
1567 + "sample",
1568 + "sand",
1569 + "satisfy",
1570 + "satoshi",
1571 + "sauce",
1572 + "sausage",
1573 + "save",
1574 + "say",
1575 + "scale",
1576 + "scan",
1577 + "scare",
1578 + "scatter",
1579 + "scene",
1580 + "scheme",
1581 + "school",
1582 + "science",
1583 + "scissors",
1584 + "scorpion",
1585 + "scout",
1586 + "scrap",
1587 + "screen",
1588 + "script",
1589 + "scrub",
1590 + "sea",
1591 + "search",
1592 + "season",
1593 + "seat",
1594 + "second",
1595 + "secret",
1596 + "section",
1597 + "security",
1598 + "seed",
1599 + "seek",
1600 + "segment",
1601 + "select",
1602 + "sell",
1603 + "seminar",
1604 + "senior",
1605 + "sense",
1606 + "sentence",
1607 + "series",
1608 + "service",
1609 + "session",
1610 + "settle",
1611 + "setup",
1612 + "seven",
1613 + "shadow",
1614 + "shaft",
1615 + "shallow",
1616 + "share",
1617 + "shed",
1618 + "shell",
1619 + "sheriff",
1620 + "shield",
1621 + "shift",
1622 + "shine",
1623 + "ship",
1624 + "shiver",
1625 + "shock",
1626 + "shoe",
1627 + "shoot",
1628 + "shop",
1629 + "short",
1630 + "shoulder",
1631 + "shove",
1632 + "shrimp",
1633 + "shrug",
1634 + "shuffle",
1635 + "shy",
1636 + "sibling",
1637 + "sick",
1638 + "side",
1639 + "siege",
1640 + "sight",
1641 + "sign",
1642 + "silent",
1643 + "silk",
1644 + "silly",
1645 + "silver",
1646 + "similar",
1647 + "simple",
1648 + "since",
1649 + "sing",
1650 + "siren",
1651 + "sister",
1652 + "situate",
1653 + "six",
1654 + "size",
1655 + "skate",
1656 + "sketch",
1657 + "ski",
1658 + "skill",
1659 + "skin",
1660 + "skirt",
1661 + "skull",
1662 + "slab",
1663 + "slam",
1664 + "sleep",
1665 + "slender",
1666 + "slice",
1667 + "slide",
1668 + "slight",
1669 + "slim",
1670 + "slogan",
1671 + "slot",
1672 + "slow",
1673 + "slush",
1674 + "small",
1675 + "smart",
1676 + "smile",
1677 + "smoke",
1678 + "smooth",
1679 + "snack",
1680 + "snake",
1681 + "snap",
1682 + "sniff",
1683 + "snow",
1684 + "soap",
1685 + "soccer",
1686 + "social",
1687 + "sock",
1688 + "soda",
1689 + "soft",
1690 + "solar",
1691 + "soldier",
1692 + "solid",
1693 + "solution",
1694 + "solve",
1695 + "someone",
1696 + "song",
1697 + "soon",
1698 + "sorry",
1699 + "sort",
1700 + "soul",
1701 + "sound",
1702 + "soup",
1703 + "source",
1704 + "south",
1705 + "space",
1706 + "spare",
1707 + "spatial",
1708 + "spawn",
1709 + "speak",
1710 + "special",
1711 + "speed",
1712 + "spell",
1713 + "spend",
1714 + "sphere",
1715 + "spice",
1716 + "spider",
1717 + "spike",
1718 + "spin",
1719 + "spirit",
1720 + "split",
1721 + "spoil",
1722 + "sponsor",
1723 + "spoon",
1724 + "sport",
1725 + "spot",
1726 + "spray",
1727 + "spread",
1728 + "spring",
1729 + "spy",
1730 + "square",
1731 + "squeeze",
1732 + "squirrel",
1733 + "stable",
1734 + "stadium",
1735 + "staff",
1736 + "stage",
1737 + "stairs",
1738 + "stamp",
1739 + "stand",
1740 + "start",
1741 + "state",
1742 + "stay",
1743 + "steak",
1744 + "steel",
1745 + "stem",
1746 + "step",
1747 + "stereo",
1748 + "stick",
1749 + "still",
1750 + "sting",
1751 + "stock",
1752 + "stomach",
1753 + "stone",
1754 + "stool",
1755 + "story",
1756 + "stove",
1757 + "strategy",
1758 + "street",
1759 + "strike",
1760 + "strong",
1761 + "struggle",
1762 + "student",
1763 + "stuff",
1764 + "stumble",
1765 + "style",
1766 + "subject",
1767 + "submit",
1768 + "subway",
1769 + "success",
1770 + "such",
1771 + "sudden",
1772 + "suffer",
1773 + "sugar",
1774 + "suggest",
1775 + "suit",
1776 + "summer",
1777 + "sun",
1778 + "sunny",
1779 + "sunset",
1780 + "super",
1781 + "supply",
1782 + "supreme",
1783 + "sure",
1784 + "surface",
1785 + "surge",
1786 + "surprise",
1787 + "surround",
1788 + "survey",
1789 + "suspect",
1790 + "sustain",
1791 + "swallow",
1792 + "swamp",
1793 + "swap",
1794 + "swarm",
1795 + "swear",
1796 + "sweet",
1797 + "swift",
1798 + "swim",
1799 + "swing",
1800 + "switch",
1801 + "sword",
1802 + "symbol",
1803 + "symptom",
1804 + "syrup",
1805 + "system",
1806 + "table",
1807 + "tackle",
1808 + "tag",
1809 + "tail",
1810 + "talent",
1811 + "talk",
1812 + "tank",
1813 + "tape",
1814 + "target",
1815 + "task",
1816 + "taste",
1817 + "tattoo",
1818 + "taxi",
1819 + "teach",
1820 + "team",
1821 + "tell",
1822 + "ten",
1823 + "tenant",
1824 + "tennis",
1825 + "tent",
1826 + "term",
1827 + "test",
1828 + "text",
1829 + "thank",
1830 + "that",
1831 + "theme",
1832 + "then",
1833 + "theory",
1834 + "there",
1835 + "they",
1836 + "thing",
1837 + "this",
1838 + "thought",
1839 + "three",
1840 + "thrive",
1841 + "throw",
1842 + "thumb",
1843 + "thunder",
1844 + "ticket",
1845 + "tide",
1846 + "tiger",
1847 + "tilt",
1848 + "timber",
1849 + "time",
1850 + "tiny",
1851 + "tip",
1852 + "tired",
1853 + "tissue",
1854 + "title",
1855 + "toast",
1856 + "tobacco",
1857 + "today",
1858 + "toddler",
1859 + "toe",
1860 + "together",
1861 + "toilet",
1862 + "token",
1863 + "tomato",
1864 + "tomorrow",
1865 + "tone",
1866 + "tongue",
1867 + "tonight",
1868 + "tool",
1869 + "tooth",
1870 + "top",
1871 + "topic",
1872 + "topple",
1873 + "torch",
1874 + "tornado",
1875 + "tortoise",
1876 + "toss",
1877 + "total",
1878 + "tourist",
1879 + "toward",
1880 + "tower",
1881 + "town",
1882 + "toy",
1883 + "track",
1884 + "trade",
1885 + "traffic",
1886 + "tragic",
1887 + "train",
1888 + "transfer",
1889 + "trap",
1890 + "trash",
1891 + "travel",
1892 + "tray",
1893 + "treat",
1894 + "tree",
1895 + "trend",
1896 + "trial",
1897 + "tribe",
1898 + "trick",
1899 + "trigger",
1900 + "trim",
1901 + "trip",
1902 + "trophy",
1903 + "trouble",
1904 + "truck",
1905 + "true",
1906 + "truly",
1907 + "trumpet",
1908 + "trust",
1909 + "truth",
1910 + "try",
1911 + "tube",
1912 + "tuition",
1913 + "tumble",
1914 + "tuna",
1915 + "tunnel",
1916 + "turkey",
1917 + "turn",
1918 + "turtle",
1919 + "twelve",
1920 + "twenty",
1921 + "twice",
1922 + "twin",
1923 + "twist",
1924 + "two",
1925 + "type",
1926 + "typical",
1927 + "ugly",
1928 + "umbrella",
1929 + "unable",
1930 + "unaware",
1931 + "uncle",
1932 + "uncover",
1933 + "under",
1934 + "undo",
1935 + "unfair",
1936 + "unfold",
1937 + "unhappy",
1938 + "uniform",
1939 + "unique",
1940 + "unit",
1941 + "universe",
1942 + "unknown",
1943 + "unlock",
1944 + "until",
1945 + "unusual",
1946 + "unveil",
1947 + "update",
1948 + "upgrade",
1949 + "uphold",
1950 + "upon",
1951 + "upper",
1952 + "upset",
1953 + "urban",
1954 + "urge",
1955 + "usage",
1956 + "use",
1957 + "used",
1958 + "useful",
1959 + "useless",
1960 + "usual",
1961 + "utility",
1962 + "vacant",
1963 + "vacuum",
1964 + "vague",
1965 + "valid",
1966 + "valley",
1967 + "valve",
1968 + "van",
1969 + "vanish",
1970 + "vapor",
1971 + "various",
1972 + "vast",
1973 + "vault",
1974 + "vehicle",
1975 + "velvet",
1976 + "vendor",
1977 + "venture",
1978 + "venue",
1979 + "verb",
1980 + "verify",
1981 + "version",
1982 + "very",
1983 + "vessel",
1984 + "veteran",
1985 + "viable",
1986 + "vibrant",
1987 + "vicious",
1988 + "victory",
1989 + "video",
1990 + "view",
1991 + "village",
1992 + "vintage",
1993 + "violin",
1994 + "virtual",
1995 + "virus",
1996 + "visa",
1997 + "visit",
1998 + "visual",
1999 + "vital",
2000 + "vivid",
2001 + "vocal",
2002 + "voice",
2003 + "void",
2004 + "volcano",
2005 + "volume",
2006 + "vote",
2007 + "voyage",
2008 + "wage",
2009 + "wagon",
2010 + "wait",
2011 + "walk",
2012 + "wall",
2013 + "walnut",
2014 + "want",
2015 + "warfare",
2016 + "warm",
2017 + "warrior",
2018 + "wash",
2019 + "wasp",
2020 + "waste",
2021 + "water",
2022 + "wave",
2023 + "way",
2024 + "wealth",
2025 + "weapon",
2026 + "wear",
2027 + "weasel",
2028 + "weather",
2029 + "web",
2030 + "wedding",
2031 + "weekend",
2032 + "weird",
2033 + "welcome",
2034 + "west",
2035 + "wet",
2036 + "whale",
2037 + "what",
2038 + "wheat",
2039 + "wheel",
2040 + "when",
2041 + "where",
2042 + "whip",
2043 + "whisper",
2044 + "wide",
2045 + "width",
2046 + "wife",
2047 + "wild",
2048 + "will",
2049 + "win",
2050 + "window",
2051 + "wine",
2052 + "wing",
2053 + "wink",
2054 + "winner",
2055 + "winter",
2056 + "wire",
2057 + "wisdom",
2058 + "wise",
2059 + "wish",
2060 + "witness",
2061 + "wolf",
2062 + "woman",
2063 + "wonder",
2064 + "wood",
2065 + "wool",
2066 + "word",
2067 + "work",
2068 + "world",
2069 + "worry",
2070 + "worth",
2071 + "wrap",
2072 + "wreck",
2073 + "wrestle",
2074 + "wrist",
2075 + "write",
2076 + "wrong",
2077 + "yard",
2078 + "year",
2079 + "yellow",
2080 + "you",
2081 + "young",
2082 + "youth",
2083 + "zebra",
2084 + "zero",
2085 + "zone",
2086 + "zoo"
2087 + ];
2088 +}
cw_nano/lib/nano_transaction_credentials.dart new
+7
@@ -0,0 +1,7 @@
1 +import 'package:cw_core/output_info.dart';
2 +
3 +class NanoTransactionCredentials {
4 + NanoTransactionCredentials(this.outputs);
5 +
6 + final List<OutputInfo> outputs;
7 +}
cw_nano/lib/nano_transaction_history.dart new
+72
@@ -0,0 +1,72 @@
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_nano/file.dart';
6 +import 'package:mobx/mobx.dart';
7 +import 'package:cw_core/transaction_history.dart';
8 +import 'package:cw_nano/nano_transaction_info.dart';
9 +
10 +part 'nano_transaction_history.g.dart';
11 +const transactionsHistoryFileName = 'transactions.json';
12 +
13 +class NanoTransactionHistory = NanoTransactionHistoryBase with _$NanoTransactionHistory;
14 +
15 +abstract class NanoTransactionHistoryBase
16 + extends TransactionHistoryBase<NanoTransactionInfo> with Store {
17 + NanoTransactionHistoryBase({required this.walletInfo, required String password})
18 + : _password = password {
19 + transactions = ObservableMap<String, NanoTransactionInfo>();
20 + }
21 +
22 + final WalletInfo walletInfo;
23 + String _password;
24 +
25 + Future<void> init() async => await _load();
26 +
27 + @override
28 + Future<void> save() async {
29 + try {
30 + final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
31 + final path = '$dirPath/$transactionsHistoryFileName';
32 + final data = json.encode({'transactions': transactions});
33 + await writeData(path: path, password: _password, data: data);
34 + } catch (e) {
35 + print('Error while save nano transaction history: ${e.toString()}');
36 + }
37 + }
38 +
39 + @override
40 + void addOne(NanoTransactionInfo transaction) => transactions[transaction.id] = transaction;
41 +
42 + @override
43 + void addMany(Map<String, NanoTransactionInfo> transactions) =>
44 + this.transactions.addAll(transactions);
45 +
46 + Future<Map<String, dynamic>> _read() async {
47 + final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
48 + final path = '$dirPath/$transactionsHistoryFileName';
49 + final content = await read(path: path, password: _password);
50 + return json.decode(content) as Map<String, dynamic>;
51 + }
52 +
53 + Future<void> _load() async {
54 + try {
55 + final content = await _read();
56 + final txs = content['transactions'] as Map<String, dynamic>? ?? {};
57 +
58 + txs.entries.forEach((entry) {
59 + final val = entry.value;
60 +
61 + if (val is Map<String, dynamic>) {
62 + final tx = NanoTransactionInfo.fromJson(val);
63 + _update(tx);
64 + }
65 + });
66 + } catch (e) {
67 + print(e);
68 + }
69 + }
70 +
71 + void _update(NanoTransactionInfo transaction) => transactions[transaction.id] = transaction;
72 +}
cw_nano/lib/nano_transaction_info.dart new
+70
@@ -0,0 +1,70 @@
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 +import 'package:cw_nano/nano_util.dart';
5 +
6 +class NanoTransactionInfo extends TransactionInfo {
7 + NanoTransactionInfo({
8 + required this.id,
9 + required this.height,
10 + required this.amountRaw,
11 + this.tokenSymbol = "XNO",
12 + required this.direction,
13 + required this.confirmed,
14 + required this.date,
15 + required this.confirmations,
16 + }) : this.amount = amountRaw.toInt();
17 +
18 + final String id;
19 + final int height;
20 + final int amount;
21 + final BigInt amountRaw;
22 + final TransactionDirection direction;
23 + final DateTime date;
24 + final bool confirmed;
25 + final int confirmations;
26 + final String tokenSymbol;
27 + String? _fiatAmount;
28 +
29 + bool get isPending => !this.confirmed;
30 +
31 + @override
32 + String amountFormatted() {
33 + final String amt = NanoUtil.getRawAsUsableString(amountRaw.toString(), NanoUtil.rawPerNano);
34 + final String acc = NanoUtil.getRawAccuracy(amountRaw.toString(), NanoUtil.rawPerNano);
35 + return "$acc$amt $tokenSymbol";
36 + }
37 +
38 + @override
39 + String fiatAmount() => _fiatAmount ?? '';
40 +
41 + @override
42 + void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
43 +
44 + @override
45 + String feeFormatted() => "0 XNO";
46 +
47 + factory NanoTransactionInfo.fromJson(Map<String, dynamic> data) {
48 + return NanoTransactionInfo(
49 + id: data['id'] as String,
50 + height: data['height'] as int,
51 + amountRaw: BigInt.parse(data['amountRaw'] as String),
52 + direction: parseTransactionDirectionFromInt(data['direction'] as int),
53 + date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
54 + confirmed: data['confirmed'] as bool,
55 + confirmations: data['confirmations'] as int,
56 + tokenSymbol: data['tokenSymbol'] as String,
57 + );
58 + }
59 +
60 + Map<String, dynamic> toJson() => {
61 + 'id': id,
62 + 'height': height,
63 + 'amountRaw': amountRaw.toString(),
64 + 'direction': direction.index,
65 + 'date': date.millisecondsSinceEpoch,
66 + 'confirmed': confirmed,
67 + 'confirmations': confirmations,
68 + 'tokenSymbol': tokenSymbol,
69 + };
70 +}
cw_nano/lib/nano_transaction_model.dart new
+39
@@ -0,0 +1,39 @@
1 +class NanoTransactionModel {
2 + final DateTime? date;
3 + final String hash;
4 + final bool confirmed;
5 + final String account;
6 + final BigInt amount;
7 + final int height;
8 + final String type;
9 +
10 + NanoTransactionModel({
11 + this.date,
12 + required this.hash,
13 + required this.height,
14 + required this.amount,
15 + required this.confirmed,
16 + required this.type,
17 + required this.account,
18 + });
19 +
20 + factory NanoTransactionModel.fromJson(dynamic json) {
21 + DateTime? localTimestamp;
22 + try {
23 + localTimestamp = DateTime.fromMillisecondsSinceEpoch(
24 + int.parse(json["local_timestamp"] as String) * 1000);
25 + } catch (e) {
26 + localTimestamp = DateTime.now();
27 + }
28 +
29 + return NanoTransactionModel(
30 + date: localTimestamp,
31 + hash: json["hash"] as String,
32 + height: int.parse(json["height"] as String),
33 + type: json["type"] as String,
34 + amount: BigInt.parse(json["amount"] as String),
35 + account: json["account"] as String,
36 + confirmed: (json["confirmed"] as String) == "true",
37 + );
38 + }
39 +}
cw_nano/lib/nano_util.dart new
+193
@@ -0,0 +1,193 @@
1 +import 'dart:convert';
2 +import 'dart:typed_data';
3 +
4 +import 'package:convert/convert.dart';
5 +import "package:ed25519_hd_key/ed25519_hd_key.dart";
6 +import 'package:libcrypto/libcrypto.dart';
7 +import 'package:nanodart/nanodart.dart';
8 +import 'package:decimal/decimal.dart';
9 +
10 +class NanoUtil {
11 + // standard:
12 + static String seedToPrivate(String seed, int index) {
13 + return NanoKeys.seedToPrivate(seed, index);
14 + }
15 +
16 + static String seedToAddress(String seed, int index) {
17 + return NanoAccounts.createAccount(
18 + NanoAccountType.NANO, privateKeyToPublic(seedToPrivate(seed, index)));
19 + }
20 +
21 + static String seedToMnemonic(String seed) {
22 + return NanoMnemomics.seedToMnemonic(seed).join(" ");
23 + }
24 +
25 + static Future<String> mnemonicToSeed(String mnemonic) async {
26 + return NanoMnemomics.mnemonicListToSeed(mnemonic.split(' '));
27 + }
28 +
29 + static String privateKeyToPublic(String privateKey) {
30 + // return NanoHelpers.byteToHex(Ed25519Blake2b.getPubkey(NanoHelpers.hexToBytes(privateKey))!);
31 + return NanoKeys.createPublicKey(privateKey);
32 + }
33 +
34 + static String addressToPublicKey(String publicAddress) {
35 + return NanoAccounts.extractPublicKey(publicAddress);
36 + }
37 +
38 + // universal:
39 + static String privateKeyToAddress(String privateKey) {
40 + return NanoAccounts.createAccount(NanoAccountType.NANO, privateKeyToPublic(privateKey));
41 + }
42 +
43 + static String publicKeyToAddress(String publicKey) {
44 + return NanoAccounts.createAccount(NanoAccountType.NANO, publicKey);
45 + }
46 +
47 + // standard + hd:
48 + static bool isValidSeed(String seed) {
49 + // Ensure seed is 64 or 128 characters long
50 + if (seed == null || (seed.length != 64 && seed.length != 128)) {
51 + return false;
52 + }
53 + // Ensure seed only contains hex characters, 0-9;A-F
54 + return NanoHelpers.isHexString(seed);
55 + }
56 +
57 + // // hd:
58 + static Future<String> hdMnemonicListToSeed(List<String> words) async {
59 + // if (words.length != 24) {
60 + // throw Exception('Expected a 24-word list, got a ${words.length} list');
61 + // }
62 + final Uint8List salt = Uint8List.fromList(utf8.encode('mnemonic'));
63 + final Pbkdf2 hasher = Pbkdf2(iterations: 2048);
64 + final String seed = await hasher.sha512(words.join(' '), salt);
65 + return seed;
66 + }
67 +
68 + static Future<String> hdSeedToPrivate(String seed, int index) async {
69 + List<int> seedBytes = hex.decode(seed);
70 + KeyData data = await ED25519_HD_KEY.derivePath("m/44'/165'/$index'", seedBytes);
71 + return hex.encode(data.key);
72 + }
73 +
74 + static Future<String> hdSeedToAddress(String seed, int index) async {
75 + return NanoAccounts.createAccount(
76 + NanoAccountType.NANO, privateKeyToPublic(await hdSeedToPrivate(seed, index)));
77 + }
78 +
79 + static Future<String> uniSeedToAddress(String seed, int index, String type) {
80 + if (type == "standard") {
81 + return Future<String>.value(seedToAddress(seed, index));
82 + } else if (type == "hd") {
83 + return hdSeedToAddress(seed, index);
84 + } else {
85 + throw Exception('Unknown seed type');
86 + }
87 + }
88 +
89 + static Future<String> uniSeedToPrivate(String seed, int index, String type) {
90 + if (type == "standard") {
91 + return Future<String>.value(seedToPrivate(seed, index));
92 + } else if (type == "hd") {
93 + return hdSeedToPrivate(seed, index);
94 + } else {
95 + throw Exception('Unknown seed type');
96 + }
97 + }
98 +
99 + static bool isValidBip39Seed(String seed) {
100 + // Ensure seed is 128 characters long
101 + if (seed.length != 128) {
102 + return false;
103 + }
104 + // Ensure seed only contains hex characters, 0-9;A-F
105 + return NanoHelpers.isHexString(seed);
106 + }
107 +
108 + // number util:
109 +
110 + static const int maxDecimalDigits = 6; // Max digits after decimal
111 + static BigInt rawPerNano = BigInt.parse("1000000000000000000000000000000");
112 + static BigInt rawPerNyano = BigInt.parse("1000000000000000000000000");
113 + static BigInt rawPerBanano = BigInt.parse("100000000000000000000000000000");
114 + static BigInt rawPerXMR = BigInt.parse("1000000000000");
115 + static BigInt convertXMRtoNano = BigInt.parse("1000000000000000000");
116 + // static BigInt convertXMRtoNano = BigInt.parse("1000000000000000000000000000");
117 +
118 + /// Convert raw to ban and return as BigDecimal
119 + ///
120 + /// @param raw 100000000000000000000000000000
121 + /// @return Decimal value 1.000000000000000000000000000000
122 + ///
123 + static Decimal getRawAsDecimal(String? raw, BigInt? rawPerCur) {
124 + rawPerCur ??= rawPerNano;
125 + final Decimal amount = Decimal.parse(raw.toString());
126 + final Decimal result = (amount / Decimal.parse(rawPerCur.toString())).toDecimal();
127 + return result;
128 + }
129 +
130 + static String truncateDecimal(Decimal input, {int digits = maxDecimalDigits}) {
131 + Decimal bigger = input.shift(digits);
132 + bigger = bigger.floor(); // chop off the decimal: 1.059 -> 1.05
133 + bigger = bigger.shift(-digits);
134 + return bigger.toString();
135 + }
136 +
137 + /// Return raw as a NANO amount.
138 + ///
139 + /// @param raw 100000000000000000000000000000
140 + /// @returns 1
141 + ///
142 + static String getRawAsUsableString(String? raw, BigInt rawPerCur) {
143 + final String res =
144 + truncateDecimal(getRawAsDecimal(raw, rawPerCur), digits: maxDecimalDigits + 9);
145 +
146 + if (raw == null || raw == "0" || raw == "00000000000000000000000000000000") {
147 + return "0";
148 + }
149 +
150 + if (!res.contains(".")) {
151 + return res;
152 + }
153 +
154 + final String numAmount = res.split(".")[0];
155 + String decAmount = res.split(".")[1];
156 +
157 + // truncate:
158 + if (decAmount.length > maxDecimalDigits) {
159 + decAmount = decAmount.substring(0, maxDecimalDigits);
160 + // remove trailing zeros:
161 + decAmount = decAmount.replaceAllMapped(RegExp(r'0+$'), (Match match) => '');
162 + if (decAmount.isEmpty) {
163 + return numAmount;
164 + }
165 + }
166 +
167 + return "$numAmount.$decAmount";
168 + }
169 +
170 + static String getRawAccuracy(String? raw, BigInt rawPerCur) {
171 + final String rawString = getRawAsUsableString(raw, rawPerCur);
172 + final String rawDecimalString = getRawAsDecimal(raw, rawPerCur).toString();
173 +
174 + if (raw == null || raw.isEmpty || raw == "0") {
175 + return "";
176 + }
177 +
178 + if (rawString != rawDecimalString) {
179 + return "~";
180 + }
181 + return "";
182 + }
183 +
184 + /// Return readable string amount as raw string
185 + /// @param amount 1.01
186 + /// @returns 101000000000000000000000000000
187 + ///
188 + static String getAmountAsRaw(String amount, BigInt rawPerCur) {
189 + final Decimal asDecimal = Decimal.parse(amount);
190 + final Decimal rawDecimal = Decimal.parse(rawPerCur.toString());
191 + return (asDecimal * rawDecimal).toString();
192 + }
193 +}
cw_nano/lib/nano_wallet.dart new
+437
@@ -0,0 +1,437 @@
1 +import 'dart:convert';
2 +import 'dart:io';
3 +
4 +import 'package:cw_core/cake_hive.dart';
5 +import 'package:cw_core/crypto_currency.dart';
6 +import 'package:cw_core/nano_account_info_response.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_info.dart';
14 +import 'package:cw_nano/file.dart';
15 +import 'package:cw_core/nano_account.dart';
16 +import 'package:cw_nano/nano_balance.dart';
17 +import 'package:cw_nano/nano_client.dart';
18 +import 'package:cw_nano/nano_transaction_credentials.dart';
19 +import 'package:cw_nano/nano_transaction_history.dart';
20 +import 'package:cw_nano/nano_transaction_info.dart';
21 +import 'package:cw_nano/nano_util.dart';
22 +import 'package:cw_nano/nano_wallet_keys.dart';
23 +import 'package:cw_nano/pending_nano_transaction.dart';
24 +import 'package:mobx/mobx.dart';
25 +import 'dart:async';
26 +import 'package:cw_nano/nano_wallet_addresses.dart';
27 +import 'package:cw_core/wallet_base.dart';
28 +import 'package:nanodart/nanodart.dart';
29 +import 'package:bip39/bip39.dart' as bip39;
30 +
31 +part 'nano_wallet.g.dart';
32 +
33 +class NanoWallet = NanoWalletBase with _$NanoWallet;
34 +
35 +abstract class NanoWalletBase
36 + extends WalletBase<NanoBalance, NanoTransactionHistory, NanoTransactionInfo> with Store {
37 + NanoWalletBase({
38 + required WalletInfo walletInfo,
39 + required String mnemonic,
40 + required String password,
41 + NanoBalance? initialBalance,
42 + }) : syncStatus = NotConnectedSyncStatus(),
43 + _password = password,
44 + _mnemonic = mnemonic,
45 + _derivationType = walletInfo.derivationType!,
46 + _isTransactionUpdating = false,
47 + _client = NanoClient(),
48 + walletAddresses = NanoWalletAddresses(walletInfo),
49 + balance = ObservableMap<CryptoCurrency, NanoBalance>.of({
50 + CryptoCurrency.nano: initialBalance ??
51 + NanoBalance(currentBalance: BigInt.zero, receivableBalance: BigInt.zero)
52 + }),
53 + super(walletInfo) {
54 + this.walletInfo = walletInfo;
55 + transactionHistory = NanoTransactionHistory(walletInfo: walletInfo, password: password);
56 + if (!CakeHive.isAdapterRegistered(NanoAccount.typeId)) {
57 + CakeHive.registerAdapter(NanoAccountAdapter());
58 + }
59 + }
60 +
61 + final String _mnemonic;
62 + final String _password;
63 + final DerivationType _derivationType;
64 +
65 + String? _privateKey;
66 + String? _publicAddress;
67 + String? _seedKey;
68 +
69 + String? _representativeAddress;
70 + Timer? _receiveTimer;
71 +
72 + late final NanoClient _client;
73 + bool _isTransactionUpdating;
74 +
75 + @override
76 + NanoWalletAddresses walletAddresses;
77 +
78 + @override
79 + @observable
80 + SyncStatus syncStatus;
81 +
82 + @override
83 + @observable
84 + late ObservableMap<CryptoCurrency, NanoBalance> balance;
85 +
86 + // initialize the different forms of private / public key we'll need:
87 + Future<void> init() async {
88 + final String type = (_derivationType == DerivationType.nano) ? "standard" : "hd";
89 +
90 + // our "mnemonic" is actually a seedkey:
91 + if (!_mnemonic.contains(' ')) {
92 + _seedKey = _mnemonic;
93 + }
94 +
95 + if (_seedKey == null) {
96 + if (_derivationType == DerivationType.nano) {
97 + _seedKey = bip39.mnemonicToEntropy(_mnemonic).toUpperCase();
98 + } else {
99 + _seedKey = await NanoUtil.hdMnemonicListToSeed(_mnemonic.split(' '));
100 + }
101 + }
102 + _privateKey = await NanoUtil.uniSeedToPrivate(_seedKey!, 0, type);
103 + _publicAddress = await NanoUtil.uniSeedToAddress(_seedKey!, 0, type);
104 + this.walletInfo.address = _publicAddress!;
105 +
106 + await walletAddresses.init();
107 + await transactionHistory.init();
108 + await save();
109 + }
110 +
111 + @override
112 + int calculateEstimatedFee(TransactionPriority priority, int? amount) {
113 + return 0; // always 0 :)
114 + }
115 +
116 + @override
117 + Future<void> changePassword(String password) {
118 + throw UnimplementedError("changePassword");
119 + }
120 +
121 + @override
122 + void close() {
123 + _client.stop();
124 + }
125 +
126 + @action
127 + @override
128 + Future<void> connectToNode({required Node node}) async {
129 + try {
130 + syncStatus = ConnectingSyncStatus();
131 + final isConnected = _client.connect(node);
132 + if (!isConnected) {
133 + throw Exception("Nano Node connection failed");
134 + }
135 +
136 + try {
137 + await _updateBalance();
138 + await _updateRep();
139 + await _receiveAll();
140 + } catch (e) {
141 + print(e);
142 + }
143 +
144 + syncStatus = ConnectedSyncStatus();
145 + } catch (e) {
146 + print(e);
147 + syncStatus = FailedSyncStatus();
148 + }
149 + }
150 +
151 + @override
152 + Future<void> connectToPowNode({required Node node}) async {
153 + _client.connectPow(node);
154 + }
155 +
156 + @override
157 + Future<PendingTransaction> createTransaction(Object credentials) async {
158 + credentials = credentials as NanoTransactionCredentials;
159 +
160 + BigInt runningAmount = BigInt.zero;
161 + await _updateBalance();
162 + BigInt runningBalance = balance[currency]?.currentBalance ?? BigInt.zero;
163 +
164 + final List<Map<String, String>> blocks = [];
165 + String? previousHash;
166 +
167 + for (var txOut in credentials.outputs) {
168 + late BigInt amt;
169 + if (txOut.sendAll) {
170 + amt = balance[currency]?.currentBalance ?? BigInt.zero;
171 + } else {
172 + amt = BigInt.tryParse(
173 + NanoUtil.getAmountAsRaw(txOut.cryptoAmount ?? "0", NanoUtil.rawPerNano)) ??
174 + BigInt.zero;
175 + }
176 +
177 + if (balance[currency]?.currentBalance != null && amt > balance[currency]!.currentBalance) {
178 + throw Exception("Trying to send more than entire balance!");
179 + }
180 +
181 + runningBalance = runningBalance - amt;
182 +
183 + final block = await _client.constructSendBlock(
184 + amountRaw: amt.toString(),
185 + destinationAddress: txOut.extractedAddress ?? txOut.address,
186 + privateKey: _privateKey!,
187 + balanceAfterTx: runningBalance,
188 + previousHash: previousHash,
189 + );
190 + previousHash = NanoBlocks.computeStateHash(
191 + NanoAccountType.NANO,
192 + block["account"]!,
193 + block["previous"]!,
194 + block["representative"]!,
195 + BigInt.parse(block["balance"]!),
196 + block["link"]!,
197 + );
198 +
199 + blocks.add(block);
200 + runningAmount += amt;
201 + }
202 +
203 + try {
204 + if (runningAmount > balance[currency]!.currentBalance || runningBalance < BigInt.zero) {
205 + throw Exception(("Trying to send more than entire balance!"));
206 + }
207 + } catch (e) {
208 + rethrow;
209 + }
210 +
211 + return PendingNanoTransaction(
212 + amount: runningAmount,
213 + id: "",
214 + nanoClient: _client,
215 + blocks: blocks,
216 + );
217 + }
218 +
219 + Future<void> _receiveAll() async {
220 + await _updateBalance();
221 + int blocksReceived = await this._client.confirmAllReceivable(
222 + destinationAddress: _publicAddress!,
223 + privateKey: _privateKey!,
224 + );
225 +
226 + if (blocksReceived > 0) {
227 + await Future<void>.delayed(Duration(seconds: 3));
228 + _updateBalance();
229 + updateTransactions();
230 + }
231 + }
232 +
233 + Future<void> updateTransactions() async {
234 + try {
235 + if (_isTransactionUpdating) {
236 + return;
237 + }
238 +
239 + _isTransactionUpdating = true;
240 + final transactions = await fetchTransactions();
241 + transactionHistory.addMany(transactions);
242 + await transactionHistory.save();
243 + _isTransactionUpdating = false;
244 + } catch (_) {
245 + _isTransactionUpdating = false;
246 + }
247 + }
248 +
249 + @override
250 + Future<Map<String, NanoTransactionInfo>> fetchTransactions() async {
251 + String address = _publicAddress!;
252 +
253 + final transactions = await _client.fetchTransactions(address);
254 +
255 + final Map<String, NanoTransactionInfo> result = {};
256 +
257 + for (var transactionModel in transactions) {
258 + result[transactionModel.hash] = NanoTransactionInfo(
259 + id: transactionModel.hash,
260 + amountRaw: transactionModel.amount,
261 + height: transactionModel.height,
262 + direction: transactionModel.type == "send"
263 + ? TransactionDirection.outgoing
264 + : TransactionDirection.incoming,
265 + confirmed: transactionModel.confirmed,
266 + date: transactionModel.date ?? DateTime.now(),
267 + confirmations: transactionModel.confirmed ? 1 : 0,
268 + );
269 + }
270 +
271 + return result;
272 + }
273 +
274 + @override
275 + NanoWalletKeys get keys {
276 + return NanoWalletKeys(seedKey: _seedKey!);
277 + }
278 +
279 + @override
280 + String? get privateKey => _seedKey!;
281 +
282 + @override
283 + Future<void> rescan({required int height}) async {
284 + updateTransactions();
285 + _updateBalance();
286 + return;
287 + }
288 +
289 + @override
290 + Future<void> save() async {
291 + await walletAddresses.updateAddressesInBox();
292 + final path = await makePath();
293 + await write(path: path, password: _password, data: toJSON());
294 + await transactionHistory.save();
295 + }
296 +
297 + @override
298 + String get seed => _mnemonic;
299 +
300 + String get representative => _representativeAddress ?? "";
301 +
302 + @action
303 + @override
304 + Future<void> startSync() async {
305 + try {
306 + syncStatus = AttemptingSyncStatus();
307 + await _updateBalance();
308 + await updateTransactions();
309 +
310 + _receiveTimer?.cancel();
311 + _receiveTimer = Timer.periodic(const Duration(seconds: 15), (timer) async {
312 + // get our balance:
313 + await _updateBalance();
314 + // if we have anything to receive, process it:
315 + if (balance[currency]!.receivableBalance > BigInt.zero) {
316 + await _receiveAll();
317 + }
318 + });
319 +
320 + syncStatus = SyncedSyncStatus();
321 + } catch (e) {
322 + print(e);
323 + syncStatus = FailedSyncStatus();
324 + rethrow;
325 + }
326 + }
327 +
328 + Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
329 +
330 + String toJSON() => json.encode({
331 + 'seedKey': _seedKey,
332 + 'mnemonic': _mnemonic,
333 + 'currentBalance': balance[currency]?.currentBalance.toString() ?? "0",
334 + 'receivableBalance': balance[currency]?.receivableBalance.toString() ?? "0",
335 + 'derivationType': _derivationType.toString()
336 + });
337 +
338 + static Future<NanoWallet> open({
339 + required String name,
340 + required String password,
341 + required WalletInfo walletInfo,
342 + }) async {
343 + final path = await pathForWallet(name: name, type: walletInfo.type);
344 + final jsonSource = await read(path: path, password: password);
345 +
346 + final data = json.decode(jsonSource) as Map;
347 + final mnemonic = data['mnemonic'] as String;
348 + final balance = NanoBalance.fromString(
349 + formattedCurrentBalance: data['currentBalance'] as String? ?? "0",
350 + formattedReceivableBalance: data['receivableBalance'] as String? ?? "0");
351 +
352 + DerivationType derivationType = DerivationType.bip39;
353 + if (data['derivationType'] == "DerivationType.nano") {
354 + derivationType = DerivationType.nano;
355 + }
356 +
357 + walletInfo.derivationType = derivationType;
358 +
359 + return NanoWallet(
360 + walletInfo: walletInfo,
361 + password: password,
362 + mnemonic: mnemonic,
363 + initialBalance: balance,
364 + );
365 + // init() should always be run after this!
366 + }
367 +
368 + Future<void> _updateBalance() async {
369 + try {
370 + balance[currency] = await _client.getBalance(_publicAddress!);
371 + } catch (e) {
372 + print("Failed to get balance $e");
373 + }
374 + await save();
375 + }
376 +
377 + Future<void> _updateRep() async {
378 + try {
379 + AccountInfoResponse accountInfo = (await _client.getAccountInfo(_publicAddress!))!;
380 + _representativeAddress = accountInfo.representative;
381 + } catch (e) {
382 + // account not found:
383 + _representativeAddress = NanoClient.DEFAULT_REPRESENTATIVE;
384 + throw Exception("Failed to get representative address $e");
385 + }
386 + }
387 +
388 + Future<void> regenerateAddress() async {
389 + final String type = (_derivationType == DerivationType.nano) ? "standard" : "hd";
390 + _privateKey =
391 + await NanoUtil.uniSeedToPrivate(_seedKey!, this.walletAddresses.account!.id, type);
392 + _publicAddress =
393 + await NanoUtil.uniSeedToAddress(_seedKey!, this.walletAddresses.account!.id, type);
394 +
395 + this.walletInfo.address = _publicAddress!;
396 + this.walletAddresses.address = _publicAddress!;
397 + }
398 +
399 + Future<void> changeRep(String address) async {
400 + try {
401 + final String hash = await _client.changeRep(
402 + privateKey: _privateKey!,
403 + repAddress: address,
404 + ourAddress: _publicAddress!,
405 + );
406 + if (hash.isNotEmpty) {
407 + _representativeAddress = address;
408 + }
409 + } catch (e) {
410 + throw Exception("Failed to change representative address $e");
411 + }
412 + }
413 +
414 + Future<void>? updateBalance() async => await _updateBalance();
415 +
416 + @override
417 + Future<void> renameWalletFiles(String newWalletName) async {
418 + final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type);
419 + final currentWalletFile = File(currentWalletPath);
420 +
421 + final currentDirPath = await pathForWalletDir(name: walletInfo.name, type: type);
422 + final currentTransactionsFile = File('$currentDirPath/$transactionsHistoryFileName');
423 +
424 + // Copies current wallet files into new wallet name's dir and files
425 + if (currentWalletFile.existsSync()) {
426 + final newWalletPath = await pathForWallet(name: newWalletName, type: type);
427 + await currentWalletFile.copy(newWalletPath);
428 + }
429 + if (currentTransactionsFile.existsSync()) {
430 + final newDirPath = await pathForWalletDir(name: newWalletName, type: type);
431 + await currentTransactionsFile.copy('$newDirPath/$transactionsHistoryFileName');
432 + }
433 +
434 + // Delete old name's dir and files
435 + await Directory(currentDirPath).delete(recursive: true);
436 + }
437 +}
cw_nano/lib/nano_wallet_addresses.dart new
+50
@@ -0,0 +1,50 @@
1 +import 'package:cw_core/cake_hive.dart';
2 +import 'package:cw_core/wallet_addresses.dart';
3 +import 'package:cw_core/wallet_info.dart';
4 +import 'package:cw_core/nano_account.dart';
5 +import 'package:cw_nano/nano_account_list.dart';
6 +import 'package:mobx/mobx.dart';
7 +
8 +part 'nano_wallet_addresses.g.dart';
9 +
10 +class NanoWalletAddresses = NanoWalletAddressesBase with _$NanoWalletAddresses;
11 +
12 +abstract class NanoWalletAddressesBase extends WalletAddresses with Store {
13 + NanoWalletAddressesBase(WalletInfo walletInfo)
14 + : accountList = NanoAccountList(walletInfo.address),
15 + address = '',
16 + super(walletInfo);
17 + @override
18 + @observable
19 + String address;
20 +
21 + @observable
22 + NanoAccount? account;
23 +
24 + NanoAccountList accountList;
25 +
26 + @override
27 + Future<void> init() async {
28 + var box = await CakeHive.openBox<NanoAccount>(walletInfo.address);
29 + try {
30 + box.getAt(0);
31 + } catch (e) {
32 + box.add(NanoAccount(id: 0, label: "Primary Account", balance: "0.00"));
33 + }
34 +
35 + await accountList.update(walletInfo.address);
36 + account = accountList.accounts.first;
37 + address = walletInfo.address;
38 + }
39 +
40 + @override
41 + Future<void> updateAddressesInBox() async {
42 + try {
43 + addressesMap.clear();
44 + addressesMap[address] = '';
45 + await saveAddressesInBox();
46 + } catch (e) {
47 + print(e.toString());
48 + }
49 + }
50 +}
cw_nano/lib/nano_wallet_creation_credentials.dart new
+41
@@ -0,0 +1,41 @@
1 +import 'package:cw_core/wallet_credentials.dart';
2 +import 'package:cw_core/wallet_info.dart';
3 +
4 +class NanoNewWalletCredentials extends WalletCredentials {
5 + NanoNewWalletCredentials({required String name, String? password})
6 + : super(name: name, password: password);
7 +}
8 +
9 +class NanoRestoreWalletFromSeedCredentials extends WalletCredentials {
10 + NanoRestoreWalletFromSeedCredentials({
11 + required String name,
12 + required this.mnemonic,
13 + int height = 0,
14 + String? password,
15 + DerivationType? derivationType,
16 + }) : super(
17 + name: name,
18 + password: password,
19 + height: height,
20 + derivationType: derivationType,
21 + );
22 +
23 + final String mnemonic;
24 +}
25 +
26 +class NanoWalletLoadingException implements Exception {
27 + @override
28 + String toString() => 'Failure to load the wallet.';
29 +}
30 +
31 +class NanoRestoreWalletFromKeysCredentials extends WalletCredentials {
32 + NanoRestoreWalletFromKeysCredentials({
33 + required String name,
34 + required String password,
35 + required this.seedKey,
36 + this.derivationType,
37 + }) : super(name: name, password: password);
38 +
39 + final String seedKey;
40 + final DerivationType? derivationType;
41 +}
\ No newline at end of file
cw_nano/lib/nano_wallet_keys.dart new
+5
@@ -0,0 +1,5 @@
1 +class NanoWalletKeys {
2 + const NanoWalletKeys({required this.seedKey});
3 +
4 + final String seedKey;
5 +}
cw_nano/lib/nano_wallet_service.dart new
+163
@@ -0,0 +1,163 @@
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_nano/nano_mnemonic.dart' as nm;
9 +import 'package:cw_nano/nano_util.dart';
10 +import 'package:cw_nano/nano_wallet.dart';
11 +import 'package:cw_nano/nano_wallet_creation_credentials.dart';
12 +import 'package:hive/hive.dart';
13 +import 'package:bip39/bip39.dart' as bip39;
14 +import 'package:nanodart/nanodart.dart';
15 +
16 +class NanoWalletService extends WalletService<NanoNewWalletCredentials,
17 + NanoRestoreWalletFromSeedCredentials, NanoRestoreWalletFromKeysCredentials> {
18 + NanoWalletService(this.walletInfoSource);
19 +
20 + final Box<WalletInfo> walletInfoSource;
21 +
22 + static bool walletFilesExist(String path) =>
23 + !File(path).existsSync() && !File('$path.keys').existsSync();
24 +
25 + @override
26 + WalletType getType() => WalletType.nano;
27 +
28 + @override
29 + Future<WalletBase> create(NanoNewWalletCredentials credentials) async {
30 + // nano standard:
31 + DerivationType derivationType = DerivationType.nano;
32 + String seedKey = NanoSeeds.generateSeed();
33 + String mnemonic = NanoUtil.seedToMnemonic(seedKey);
34 +
35 + credentials.walletInfo!.derivationType = derivationType;
36 +
37 + final wallet = NanoWallet(
38 + walletInfo: credentials.walletInfo!,
39 + mnemonic: mnemonic,
40 + password: credentials.password!,
41 + );
42 + wallet.init();
43 + return wallet;
44 + }
45 +
46 + @override
47 + Future<void> remove(String wallet) async {
48 + final path = await pathForWalletDir(name: wallet, type: getType());
49 + final file = Directory(path);
50 + final isExist = file.existsSync();
51 +
52 + if (isExist) {
53 + await file.delete(recursive: true);
54 + }
55 +
56 + final walletInfo = walletInfoSource.values
57 + .firstWhere((info) => info.id == WalletBase.idFor(wallet, getType()));
58 + await walletInfoSource.delete(walletInfo.key);
59 + }
60 +
61 + @override
62 + Future<void> rename(String currentName, String password, String newName) async {
63 + final currentWalletInfo = walletInfoSource.values
64 + .firstWhere((info) => info.id == WalletBase.idFor(currentName, getType()));
65 +
66 + String randomWords =
67 + (List<String>.from(nm.NanoMnemomics.WORDLIST)..shuffle()).take(24).join(' ');
68 + final currentWallet =
69 + NanoWallet(walletInfo: currentWalletInfo, password: password, mnemonic: randomWords);
70 +
71 + await currentWallet.renameWalletFiles(newName);
72 +
73 + final newWalletInfo = currentWalletInfo;
74 + newWalletInfo.id = WalletBase.idFor(newName, getType());
75 + newWalletInfo.name = newName;
76 +
77 + await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
78 + }
79 +
80 + @override
81 + Future<NanoWallet> restoreFromKeys(NanoRestoreWalletFromKeysCredentials credentials) async {
82 + if (credentials.seedKey.contains(' ')) {
83 + throw Exception("Invalid key!");
84 + } else {
85 + if (credentials.seedKey.length != 64 && credentials.seedKey.length != 128) {
86 + throw Exception("Invalid key length!");
87 + }
88 + }
89 +
90 + DerivationType derivationType = credentials.derivationType ?? DerivationType.nano;
91 + credentials.walletInfo!.derivationType = derivationType;
92 +
93 + String? mnemonic;
94 +
95 + // we can't derive the mnemonic from the key in all cases, only if it's a "nano" seed
96 + if (credentials.seedKey.length == 64) {
97 + try {
98 + mnemonic = NanoUtil.seedToMnemonic(credentials.seedKey);
99 + } catch (e) {
100 + throw Exception("Wasn't a valid nano style seed!");
101 + }
102 + }
103 +
104 + final wallet = await NanoWallet(
105 + password: credentials.password!,
106 + mnemonic: mnemonic ?? credentials.seedKey,
107 + walletInfo: credentials.walletInfo!,
108 + );
109 + await wallet.init();
110 + await wallet.save();
111 + return wallet;
112 + }
113 +
114 + @override
115 + Future<NanoWallet> restoreFromSeed(NanoRestoreWalletFromSeedCredentials credentials) async {
116 + if (credentials.mnemonic.contains(' ')) {
117 + if (!bip39.validateMnemonic(credentials.mnemonic)) {
118 + throw nm.NanoMnemonicIsIncorrectException();
119 + }
120 +
121 + if (!NanoMnemomics.validateMnemonic(credentials.mnemonic.split(' '))) {
122 + throw nm.NanoMnemonicIsIncorrectException();
123 + }
124 + } else {
125 + if (credentials.mnemonic.length != 64 && credentials.mnemonic.length != 128) {
126 + throw Exception("Invalid seed length");
127 + }
128 + }
129 +
130 + DerivationType derivationType = credentials.derivationType ?? DerivationType.nano;
131 +
132 + credentials.walletInfo!.derivationType = derivationType;
133 +
134 + final wallet = await NanoWallet(
135 + password: credentials.password!,
136 + mnemonic: credentials.mnemonic,
137 + walletInfo: credentials.walletInfo!,
138 + );
139 +
140 + await wallet.init();
141 + await wallet.save();
142 + return wallet;
143 + }
144 +
145 + @override
146 + Future<bool> isWalletExit(String name) async =>
147 + File(await pathForWallet(name: name, type: getType())).existsSync();
148 +
149 + @override
150 + Future<NanoWallet> openWallet(String name, String password) async {
151 + final walletInfo =
152 + walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
153 + final wallet = await NanoWalletBase.open(
154 + name: name,
155 + password: password,
156 + walletInfo: walletInfo,
157 + );
158 +
159 + await wallet.init();
160 + await wallet.save();
161 + return wallet;
162 + }
163 +}
cw_nano/lib/pending_nano_transaction.dart new
+40
@@ -0,0 +1,40 @@
1 +import 'package:cw_core/pending_transaction.dart';
2 +import 'package:cw_nano/nano_client.dart';
3 +import 'package:cw_nano/nano_util.dart';
4 +
5 +class PendingNanoTransaction with PendingTransaction {
6 + PendingNanoTransaction({
7 + required this.nanoClient,
8 + required this.amount,
9 + required this.id,
10 + required this.blocks,
11 + });
12 +
13 + final NanoClient nanoClient;
14 + final BigInt amount;
15 + final String id;
16 + final List<Map<String, String>> blocks;
17 + String hex = "unused";
18 +
19 + @override
20 + String get amountFormatted {
21 + final String amt = NanoUtil.getRawAsUsableString(amount.toString(), NanoUtil.rawPerNano);
22 + return amt;
23 + }
24 +
25 + String get accurateAmountFormatted {
26 + final String amt = NanoUtil.getRawAsUsableString(amount.toString(), NanoUtil.rawPerNano);
27 + final String acc = NanoUtil.getRawAccuracy(amount.toString(), NanoUtil.rawPerNano);
28 + return "$acc$amt";
29 + }
30 +
31 + @override
32 + String get feeFormatted => "0";
33 +
34 + @override
35 + Future<void> commit() async {
36 + for (var block in blocks) {
37 + await nanoClient.processBlock(block, "send");
38 + }
39 + }
40 +}
cw_nano/pubspec.lock new
+756
@@ -0,0 +1,756 @@
1 +# Generated by pub
2 +# See https://dart.dev/tools/pub/glossary#lockfile
3 +packages:
4 + _fe_analyzer_shared:
5 + dependency: transitive
6 + description:
7 + name: _fe_analyzer_shared
8 + sha256: "4897882604d919befd350648c7f91926a9d5de99e67b455bf0917cc2362f4bb8"
9 + url: "https://pub.dev"
10 + source: hosted
11 + version: "47.0.0"
12 + analyzer:
13 + dependency: transitive
14 + description:
15 + name: analyzer
16 + sha256: "690e335554a8385bc9d787117d9eb52c0c03ee207a607e593de3c9d71b1cfe80"
17 + url: "https://pub.dev"
18 + source: hosted
19 + version: "4.7.0"
20 + args:
21 + dependency: transitive
22 + description:
23 + name: args
24 + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
25 + url: "https://pub.dev"
26 + source: hosted
27 + version: "2.4.2"
28 + asn1lib:
29 + dependency: transitive
30 + description:
31 + name: asn1lib
32 + sha256: b74e3842a52c61f8819a1ec8444b4de5419b41a7465e69d4aa681445377398b0
33 + url: "https://pub.dev"
34 + source: hosted
35 + version: "1.4.1"
36 + async:
37 + dependency: transitive
38 + description:
39 + name: async
40 + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
41 + url: "https://pub.dev"
42 + source: hosted
43 + version: "2.11.0"
44 + bip32:
45 + dependency: "direct main"
46 + description:
47 + name: bip32
48 + sha256: "54787cd7a111e9d37394aabbf53d1fc5e2e0e0af2cd01c459147a97c0e3f8a97"
49 + url: "https://pub.dev"
50 + source: hosted
51 + version: "2.0.0"
52 + bip39:
53 + dependency: "direct main"
54 + description:
55 + name: bip39
56 + sha256: de1ee27ebe7d96b84bb3a04a4132a0a3007dcdd5ad27dd14aa87a29d97c45edc
57 + url: "https://pub.dev"
58 + source: hosted
59 + version: "1.0.6"
60 + boolean_selector:
61 + dependency: transitive
62 + description:
63 + name: boolean_selector
64 + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
65 + url: "https://pub.dev"
66 + source: hosted
67 + version: "2.1.1"
68 + bs58check:
69 + dependency: transitive
70 + description:
71 + name: bs58check
72 + sha256: c4a164d42b25c2f6bc88a8beccb9fc7d01440f3c60ba23663a20a70faf484ea9
73 + url: "https://pub.dev"
74 + source: hosted
75 + version: "1.0.2"
76 + build:
77 + dependency: transitive
78 + description:
79 + name: build
80 + sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777"
81 + url: "https://pub.dev"
82 + source: hosted
83 + version: "2.3.1"
84 + build_config:
85 + dependency: transitive
86 + description:
87 + name: build_config
88 + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1
89 + url: "https://pub.dev"
90 + source: hosted
91 + version: "1.1.1"
92 + build_daemon:
93 + dependency: transitive
94 + description:
95 + name: build_daemon
96 + sha256: "757153e5d9cd88253cb13f28c2fb55a537dc31fefd98137549895b5beb7c6169"
97 + url: "https://pub.dev"
98 + source: hosted
99 + version: "3.1.1"
100 + build_resolvers:
101 + dependency: transitive
102 + description:
103 + name: build_resolvers
104 + sha256: "687cf90a3951affac1bd5f9ecb5e3e90b60487f3d9cdc359bb310f8876bb02a6"
105 + url: "https://pub.dev"
106 + source: hosted
107 + version: "2.0.10"
108 + build_runner:
109 + dependency: "direct dev"
110 + description:
111 + name: build_runner
112 + sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727
113 + url: "https://pub.dev"
114 + source: hosted
115 + version: "2.3.3"
116 + build_runner_core:
117 + dependency: transitive
118 + description:
119 + name: build_runner_core
120 + sha256: "0671ad4162ed510b70d0eb4ad6354c249f8429cab4ae7a4cec86bbc2886eb76e"
121 + url: "https://pub.dev"
122 + source: hosted
123 + version: "7.2.7+1"
124 + built_collection:
125 + dependency: transitive
126 + description:
127 + name: built_collection
128 + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100"
129 + url: "https://pub.dev"
130 + source: hosted
131 + version: "5.1.1"
132 + built_value:
133 + dependency: transitive
134 + description:
135 + name: built_value
136 + sha256: "598a2a682e2a7a90f08ba39c0aaa9374c5112340f0a2e275f61b59389543d166"
137 + url: "https://pub.dev"
138 + source: hosted
139 + version: "8.6.1"
140 + characters:
141 + dependency: transitive
142 + description:
143 + name: characters
144 + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
145 + url: "https://pub.dev"
146 + source: hosted
147 + version: "1.3.0"
148 + checked_yaml:
149 + dependency: transitive
150 + description:
151 + name: checked_yaml
152 + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
153 + url: "https://pub.dev"
154 + source: hosted
155 + version: "2.0.3"
156 + clock:
157 + dependency: transitive
158 + description:
159 + name: clock
160 + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
161 + url: "https://pub.dev"
162 + source: hosted
163 + version: "1.1.1"
164 + code_builder:
165 + dependency: transitive
166 + description:
167 + name: code_builder
168 + sha256: "4ad01d6e56db961d29661561effde45e519939fdaeb46c351275b182eac70189"
169 + url: "https://pub.dev"
170 + source: hosted
171 + version: "4.5.0"
172 + collection:
173 + dependency: transitive
174 + description:
175 + name: collection
176 + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
177 + url: "https://pub.dev"
178 + source: hosted
179 + version: "1.17.1"
180 + convert:
181 + dependency: transitive
182 + description:
183 + name: convert
184 + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
185 + url: "https://pub.dev"
186 + source: hosted
187 + version: "3.1.1"
188 + crypto:
189 + dependency: transitive
190 + description:
191 + name: crypto
192 + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
193 + url: "https://pub.dev"
194 + source: hosted
195 + version: "3.0.3"
196 + cw_core:
197 + dependency: "direct main"
198 + description:
199 + path: "../cw_core"
200 + relative: true
201 + source: path
202 + version: "0.0.1"
203 + dart_style:
204 + dependency: transitive
205 + description:
206 + name: dart_style
207 + sha256: "7a03456c3490394c8e7665890333e91ae8a49be43542b616e414449ac358acd4"
208 + url: "https://pub.dev"
209 + source: hosted
210 + version: "2.2.4"
211 + decimal:
212 + dependency: "direct main"
213 + description:
214 + name: decimal
215 + sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21"
216 + url: "https://pub.dev"
217 + source: hosted
218 + version: "2.3.3"
219 + ed25519_hd_key:
220 + dependency: "direct main"
221 + description:
222 + name: ed25519_hd_key
223 + sha256: "326608234e986ea826a5db4cf4cd6826058d860875a3fff7926c0725fe1a604d"
224 + url: "https://pub.dev"
225 + source: hosted
226 + version: "2.2.0"
227 + encrypt:
228 + dependency: transitive
229 + description:
230 + name: encrypt
231 + sha256: "4fd4e4fdc21b9d7d4141823e1e6515cd94e7b8d84749504c232999fba25d9bbb"
232 + url: "https://pub.dev"
233 + source: hosted
234 + version: "5.0.1"
235 + fake_async:
236 + dependency: transitive
237 + description:
238 + name: fake_async
239 + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
240 + url: "https://pub.dev"
241 + source: hosted
242 + version: "1.3.1"
243 + ffi:
244 + dependency: transitive
245 + description:
246 + name: ffi
247 + sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99
248 + url: "https://pub.dev"
249 + source: hosted
250 + version: "2.0.2"
251 + file:
252 + dependency: transitive
253 + description:
254 + name: file
255 + sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
256 + url: "https://pub.dev"
257 + source: hosted
258 + version: "6.1.4"
259 + fixnum:
260 + dependency: transitive
261 + description:
262 + name: fixnum
263 + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
264 + url: "https://pub.dev"
265 + source: hosted
266 + version: "1.1.0"
267 + fixnum_nanodart:
268 + dependency: transitive
269 + description:
270 + name: fixnum_nanodart
271 + sha256: "4b0132d11ecddc0d2ca64b6d7dee6726db432ed02cac1349d7532a08be5c54fc"
272 + url: "https://pub.dev"
273 + source: hosted
274 + version: "1.0.0"
275 + flutter:
276 + dependency: "direct main"
277 + description: flutter
278 + source: sdk
279 + version: "0.0.0"
280 + flutter_mobx:
281 + dependency: transitive
282 + description:
283 + name: flutter_mobx
284 + sha256: "0da4add0016387a7bf309a0d0c41d36c6b3ae25ed7a176409267f166509e723e"
285 + url: "https://pub.dev"
286 + source: hosted
287 + version: "2.0.6+5"
288 + flutter_test:
289 + dependency: "direct dev"
290 + description: flutter
291 + source: sdk
292 + version: "0.0.0"
293 + frontend_server_client:
294 + dependency: transitive
295 + description:
296 + name: frontend_server_client
297 + sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612"
298 + url: "https://pub.dev"
299 + source: hosted
300 + version: "3.2.0"
301 + glob:
302 + dependency: transitive
303 + description:
304 + name: glob
305 + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
306 + url: "https://pub.dev"
307 + source: hosted
308 + version: "2.1.2"
309 + graphs:
310 + dependency: transitive
311 + description:
312 + name: graphs
313 + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19
314 + url: "https://pub.dev"
315 + source: hosted
316 + version: "2.3.1"
317 + hex:
318 + dependency: "direct main"
319 + description:
320 + name: hex
321 + sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a"
322 + url: "https://pub.dev"
323 + source: hosted
324 + version: "0.2.0"
325 + hive:
326 + dependency: transitive
327 + description:
328 + name: hive
329 + sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941"
330 + url: "https://pub.dev"
331 + source: hosted
332 + version: "2.2.3"
333 + hive_generator:
334 + dependency: "direct dev"
335 + description:
336 + name: hive_generator
337 + sha256: "81fd20125cb2ce8fd23623d7744ffbaf653aae93706c9bd3bf7019ea0ace3938"
338 + url: "https://pub.dev"
339 + source: hosted
340 + version: "1.1.3"
341 + http:
342 + dependency: "direct main"
343 + description:
344 + name: http
345 + sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
346 + url: "https://pub.dev"
347 + source: hosted
348 + version: "1.1.0"
349 + http_multi_server:
350 + dependency: transitive
351 + description:
352 + name: http_multi_server
353 + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b"
354 + url: "https://pub.dev"
355 + source: hosted
356 + version: "3.2.1"
357 + http_parser:
358 + dependency: transitive
359 + description:
360 + name: http_parser
361 + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
362 + url: "https://pub.dev"
363 + source: hosted
364 + version: "4.0.2"
365 + intl:
366 + dependency: transitive
367 + description:
368 + name: intl
369 + sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
370 + url: "https://pub.dev"
371 + source: hosted
372 + version: "0.18.1"
373 + io:
374 + dependency: transitive
375 + description:
376 + name: io
377 + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e"
378 + url: "https://pub.dev"
379 + source: hosted
380 + version: "1.0.4"
381 + js:
382 + dependency: transitive
383 + description:
384 + name: js
385 + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
386 + url: "https://pub.dev"
387 + source: hosted
388 + version: "0.6.7"
389 + json_annotation:
390 + dependency: transitive
391 + description:
392 + name: json_annotation
393 + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467
394 + url: "https://pub.dev"
395 + source: hosted
396 + version: "4.8.1"
397 + libcrypto:
398 + dependency: "direct main"
399 + description:
400 + name: libcrypto
401 + sha256: "18a97db8d88147b0b60d2755f29b5e4944181c4c1a9f52bd1ecbea1b0a5aab03"
402 + url: "https://pub.dev"
403 + source: hosted
404 + version: "0.2.2"
405 + logging:
406 + dependency: transitive
407 + description:
408 + name: logging
409 + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
410 + url: "https://pub.dev"
411 + source: hosted
412 + version: "1.2.0"
413 + matcher:
414 + dependency: transitive
415 + description:
416 + name: matcher
417 + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
418 + url: "https://pub.dev"
419 + source: hosted
420 + version: "0.12.15"
421 + material_color_utilities:
422 + dependency: transitive
423 + description:
424 + name: material_color_utilities
425 + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
426 + url: "https://pub.dev"
427 + source: hosted
428 + version: "0.2.0"
429 + meta:
430 + dependency: transitive
431 + description:
432 + name: meta
433 + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
434 + url: "https://pub.dev"
435 + source: hosted
436 + version: "1.9.1"
437 + mime:
438 + dependency: transitive
439 + description:
440 + name: mime
441 + sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e
442 + url: "https://pub.dev"
443 + source: hosted
444 + version: "1.0.4"
445 + mobx:
446 + dependency: "direct main"
447 + description:
448 + name: mobx
449 + sha256: "0afcf88b3ee9d6819890bf16c11a727fc8c62cf736fda8e5d3b9b4eace4e62ea"
450 + url: "https://pub.dev"
451 + source: hosted
452 + version: "2.2.0"
453 + mobx_codegen:
454 + dependency: "direct dev"
455 + description:
456 + name: mobx_codegen
457 + sha256: d4beb9cea4b7b014321235f8fdc7c2193ee0fe1d1198e9da7403f8bc85c4407c
458 + url: "https://pub.dev"
459 + source: hosted
460 + version: "2.3.0"
461 + nanodart:
462 + dependency: "direct main"
463 + description:
464 + name: nanodart
465 + sha256: "4b2f42d60307b54e8cf384d6193a567d07f8efd773858c0d5948246153c13282"
466 + url: "https://pub.dev"
467 + source: hosted
468 + version: "2.0.0"
469 + package_config:
470 + dependency: transitive
471 + description:
472 + name: package_config
473 + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd"
474 + url: "https://pub.dev"
475 + source: hosted
476 + version: "2.1.0"
477 + path:
478 + dependency: transitive
479 + description:
480 + name: path
481 + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
482 + url: "https://pub.dev"
483 + source: hosted
484 + version: "1.8.3"
485 + path_provider:
486 + dependency: transitive
487 + description:
488 + name: path_provider
489 + sha256: "3087813781ab814e4157b172f1a11c46be20179fcc9bea043e0fba36bc0acaa2"
490 + url: "https://pub.dev"
491 + source: hosted
492 + version: "2.0.15"
493 + path_provider_android:
494 + dependency: transitive
495 + description:
496 + name: path_provider_android
497 + sha256: "2cec049d282c7f13c594b4a73976b0b4f2d7a1838a6dd5aaf7bd9719196bee86"
498 + url: "https://pub.dev"
499 + source: hosted
500 + version: "2.0.27"
501 + path_provider_foundation:
502 + dependency: transitive
503 + description:
504 + name: path_provider_foundation
505 + sha256: "916731ccbdce44d545414dd9961f26ba5fbaa74bcbb55237d8e65a623a8c7297"
506 + url: "https://pub.dev"
507 + source: hosted
508 + version: "2.2.4"
509 + path_provider_linux:
510 + dependency: transitive
511 + description:
512 + name: path_provider_linux
513 + sha256: ffbb8cc9ed2c9ec0e4b7a541e56fd79b138e8f47d2fb86815f15358a349b3b57
514 + url: "https://pub.dev"
515 + source: hosted
516 + version: "2.1.11"
517 + path_provider_platform_interface:
518 + dependency: transitive
519 + description:
520 + name: path_provider_platform_interface
521 + sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec"
522 + url: "https://pub.dev"
523 + source: hosted
524 + version: "2.0.6"
525 + path_provider_windows:
526 + dependency: transitive
527 + description:
528 + name: path_provider_windows
529 + sha256: "1cb68ba4cd3a795033de62ba1b7b4564dace301f952de6bfb3cd91b202b6ee96"
530 + url: "https://pub.dev"
531 + source: hosted
532 + version: "2.1.7"
533 + pinenacl:
534 + dependency: transitive
535 + description:
536 + name: pinenacl
537 + sha256: e5fb0bce1717b7f136f35ee98b5c02b3e6383211f8a77ca882fa7812232a07b9
538 + url: "https://pub.dev"
539 + source: hosted
540 + version: "0.3.4"
541 + platform:
542 + dependency: transitive
543 + description:
544 + name: platform
545 + sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76"
546 + url: "https://pub.dev"
547 + source: hosted
548 + version: "3.1.0"
549 + plugin_platform_interface:
550 + dependency: transitive
551 + description:
552 + name: plugin_platform_interface
553 + sha256: "43798d895c929056255600343db8f049921cbec94d31ec87f1dc5c16c01935dd"
554 + url: "https://pub.dev"
555 + source: hosted
556 + version: "2.1.5"
557 + pointycastle:
558 + dependency: transitive
559 + description:
560 + name: pointycastle
561 + sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c"
562 + url: "https://pub.dev"
563 + source: hosted
564 + version: "3.7.3"
565 + pool:
566 + dependency: transitive
567 + description:
568 + name: pool
569 + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
570 + url: "https://pub.dev"
571 + source: hosted
572 + version: "1.5.1"
573 + pub_semver:
574 + dependency: transitive
575 + description:
576 + name: pub_semver
577 + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
578 + url: "https://pub.dev"
579 + source: hosted
580 + version: "2.1.4"
581 + pubspec_parse:
582 + dependency: transitive
583 + description:
584 + name: pubspec_parse
585 + sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367
586 + url: "https://pub.dev"
587 + source: hosted
588 + version: "1.2.3"
589 + rational:
590 + dependency: transitive
591 + description:
592 + name: rational
593 + sha256: ba58e9e18df9abde280e8b10051e4bce85091e41e8e7e411b6cde2e738d357cf
594 + url: "https://pub.dev"
595 + source: hosted
596 + version: "2.2.2"
597 + shelf:
598 + dependency: transitive
599 + description:
600 + name: shelf
601 + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
602 + url: "https://pub.dev"
603 + source: hosted
604 + version: "1.4.1"
605 + shelf_web_socket:
606 + dependency: transitive
607 + description:
608 + name: shelf_web_socket
609 + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1"
610 + url: "https://pub.dev"
611 + source: hosted
612 + version: "1.0.4"
613 + sky_engine:
614 + dependency: transitive
615 + description: flutter
616 + source: sdk
617 + version: "0.0.99"
618 + source_gen:
619 + dependency: transitive
620 + description:
621 + name: source_gen
622 + sha256: "2d79738b6bbf38a43920e2b8d189e9a3ce6cc201f4b8fc76be5e4fe377b1c38d"
623 + url: "https://pub.dev"
624 + source: hosted
625 + version: "1.2.6"
626 + source_helper:
627 + dependency: transitive
628 + description:
629 + name: source_helper
630 + sha256: "3b67aade1d52416149c633ba1bb36df44d97c6b51830c2198e934e3fca87ca1f"
631 + url: "https://pub.dev"
632 + source: hosted
633 + version: "1.3.3"
634 + source_span:
635 + dependency: transitive
636 + description:
637 + name: source_span
638 + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
639 + url: "https://pub.dev"
640 + source: hosted
641 + version: "1.9.1"
642 + stack_trace:
643 + dependency: transitive
644 + description:
645 + name: stack_trace
646 + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
647 + url: "https://pub.dev"
648 + source: hosted
649 + version: "1.11.0"
650 + stream_channel:
651 + dependency: transitive
652 + description:
653 + name: stream_channel
654 + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
655 + url: "https://pub.dev"
656 + source: hosted
657 + version: "2.1.1"
658 + stream_transform:
659 + dependency: transitive
660 + description:
661 + name: stream_transform
662 + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f"
663 + url: "https://pub.dev"
664 + source: hosted
665 + version: "2.1.0"
666 + string_scanner:
667 + dependency: transitive
668 + description:
669 + name: string_scanner
670 + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
671 + url: "https://pub.dev"
672 + source: hosted
673 + version: "1.2.0"
674 + term_glyph:
675 + dependency: transitive
676 + description:
677 + name: term_glyph
678 + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
679 + url: "https://pub.dev"
680 + source: hosted
681 + version: "1.2.1"
682 + test_api:
683 + dependency: transitive
684 + description:
685 + name: test_api
686 + sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
687 + url: "https://pub.dev"
688 + source: hosted
689 + version: "0.5.1"
690 + timing:
691 + dependency: transitive
692 + description:
693 + name: timing
694 + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32"
695 + url: "https://pub.dev"
696 + source: hosted
697 + version: "1.0.1"
698 + typed_data:
699 + dependency: transitive
700 + description:
701 + name: typed_data
702 + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
703 + url: "https://pub.dev"
704 + source: hosted
705 + version: "1.3.2"
706 + vector_math:
707 + dependency: transitive
708 + description:
709 + name: vector_math
710 + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
711 + url: "https://pub.dev"
712 + source: hosted
713 + version: "2.1.4"
714 + watcher:
715 + dependency: transitive
716 + description:
717 + name: watcher
718 + sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0"
719 + url: "https://pub.dev"
720 + source: hosted
721 + version: "1.0.2"
722 + web_socket_channel:
723 + dependency: transitive
724 + description:
725 + name: web_socket_channel
726 + sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
727 + url: "https://pub.dev"
728 + source: hosted
729 + version: "2.4.0"
730 + win32:
731 + dependency: transitive
732 + description:
733 + name: win32
734 + sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c"
735 + url: "https://pub.dev"
736 + source: hosted
737 + version: "4.1.4"
738 + xdg_directories:
739 + dependency: transitive
740 + description:
741 + name: xdg_directories
742 + sha256: e0b1147eec179d3911f1f19b59206448f78195ca1d20514134e10641b7d7fbff
743 + url: "https://pub.dev"
744 + source: hosted
745 + version: "1.0.1"
746 + yaml:
747 + dependency: transitive
748 + description:
749 + name: yaml
750 + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
751 + url: "https://pub.dev"
752 + source: hosted
753 + version: "3.1.2"
754 +sdks:
755 + dart: ">=3.0.0 <4.0.0"
756 + flutter: ">=3.3.0"
cw_nano/pubspec.yaml new
+69
@@ -0,0 +1,69 @@
1 +name: cw_nano
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 + mobx: ^2.0.7+4
16 + bip39: ^1.0.6
17 + bip32: ^2.0.0
18 + nanodart: ^2.0.0
19 + decimal: ^2.3.3
20 + libcrypto: ^0.2.2
21 + ed25519_hd_key: ^2.2.0
22 + hex: ^0.2.0
23 + http: ^1.1.0
24 + cw_core:
25 + path: ../cw_core
26 +
27 +dev_dependencies:
28 + flutter_test:
29 + sdk: flutter
30 + build_runner: ^2.1.11
31 + mobx_codegen: ^2.0.7
32 + hive_generator: ^1.1.3
33 +
34 +# For information on the generic Dart part of this file, see the
35 +# following page: https://dart.dev/tools/pub/pubspec
36 +
37 +# The following section is specific to Flutter packages.
38 +flutter:
39 +
40 + # To add assets to your package, add an assets section, like this:
41 + # assets:
42 + # - images/a_dot_burr.jpeg
43 + # - images/a_dot_ham.jpeg
44 + #
45 + # For details regarding assets in packages, see
46 + # https://flutter.dev/assets-and-images/#from-packages
47 + #
48 + # An image asset can refer to one or more resolution-specific "variants", see
49 + # https://flutter.dev/assets-and-images/#resolution-aware
50 +
51 + # To add custom fonts to your package, add a fonts section here,
52 + # in this "flutter" section. Each entry in this list should have a
53 + # "family" key with the font family name, and a "fonts" key with a
54 + # list giving the asset and other descriptors for the font. For
55 + # example:
56 + # fonts:
57 + # - family: Schyler
58 + # fonts:
59 + # - asset: fonts/Schyler-Regular.ttf
60 + # - asset: fonts/Schyler-Italic.ttf
61 + # style: italic
62 + # - family: Trajan Pro
63 + # fonts:
64 + # - asset: fonts/TrajanPro.ttf
65 + # - asset: fonts/TrajanPro_Bold.ttf
66 + # weight: 700
67 + #
68 + # For details regarding fonts in packages, see
69 + # https://flutter.dev/custom-fonts/#from-packages
cw_nano/test/cw_nano_test.dart new
+12
@@ -0,0 +1,12 @@
1 +import 'package:flutter_test/flutter_test.dart';
2 +
3 +import 'package:cw_nano/cw_nano.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 +}
lib/bitcoin/cw_bitcoin.dart
+1 -1
@@ -161,4 +161,4 @@ class CWBitcoin extends Bitcoin {
161 @override
162 TransactionPriority getLitecoinTransactionPrioritySlow()
163 => LitecoinTransactionPriority.slow;
164 -}
164 +}
\ No newline at end of file
lib/buy/onramper/onramper_buy_provider.dart
+2
@@ -27,6 +27,8 @@ class OnRamperBuyProvider {
27 return "LTC_LITECOIN";
28 case CryptoCurrency.xmr:
29 return "XMR_MONERO";
30 + case CryptoCurrency.nano:
31 + return "XNO_NANO";
32 default:
33 return _wallet.currency.title;
34 }
lib/core/address_validator.dart
+4
@@ -28,6 +28,8 @@ class AddressValidator extends TextValidator {
28 return '^3[0-9a-zA-Z]{32}\$|^3[0-9a-zA-Z]{33}\$|^bc1[0-9a-zA-Z]{59}\$';
29 case CryptoCurrency.nano:
30 return '[0-9a-zA-Z_]';
31 + case CryptoCurrency.banano:
32 + return '[0-9a-zA-Z_]';
33 case CryptoCurrency.usdc:
34 case CryptoCurrency.usdcpoly:
35 case CryptoCurrency.ape:
@@ -177,6 +179,8 @@ class AddressValidator extends TextValidator {
179 return [34, 43, 63];
180 case CryptoCurrency.nano:
181 return [64, 65];
182 + case CryptoCurrency.banano:
183 + return [64, 65];
184 case CryptoCurrency.sc:
185 return [76];
186 case CryptoCurrency.sol:
lib/core/seed_validator.dart
+4
@@ -5,6 +5,7 @@ import 'package:cake_wallet/core/validator.dart';
5 import 'package:cake_wallet/entities/mnemonic_item.dart';
6 import 'package:cw_core/wallet_type.dart';
7 import 'package:cake_wallet/monero/monero.dart';
8 +import 'package:cake_wallet/nano/nano.dart';
9 import 'package:cake_wallet/utils/language_list.dart';
10
11 class SeedValidator extends Validator<MnemonicItem> {
@@ -28,6 +29,9 @@ class SeedValidator extends Validator<MnemonicItem> {
29 return haven!.getMoneroWordList(language);
30 case WalletType.ethereum:
31 return ethereum!.getEthereumWordList(language);
32 + case WalletType.nano:
33 + case WalletType.banano:
34 + return nano!.getNanoWordList(language);
35 default:
36 return [];
37 }
lib/core/wallet_creation_service.dart
+12 -25
@@ -39,15 +39,11 @@ class WalletCreationService {
39
40 bool exists(String name) {
41 final walletName = name.toLowerCase();
42 - return walletInfoSource
43 - .values
44 - .any((walletInfo) => walletInfo.name.toLowerCase() == walletName);
42 + return walletInfoSource.values.any((walletInfo) => walletInfo.name.toLowerCase() == walletName);
43 }
44
45 bool typeExists(WalletType type) {
48 - return walletInfoSource
49 - .values
50 - .any((walletInfo) => walletInfo.type == type);
46 + return walletInfoSource.values.any((walletInfo) => walletInfo.type == type);
47 }
48
49 void checkIfExists(String name) {
@@ -60,15 +56,12 @@ class WalletCreationService {
56 checkIfExists(credentials.name);
57 final password = generateWalletPassword();
58 credentials.password = password;
63 - await keyService.saveWalletPassword(
64 - password: password, walletName: credentials.name);
65 - final wallet = await _service!.create(credentials);
59 + await keyService.saveWalletPassword(password: password, walletName: credentials.name);
60 + final wallet = await _service!.create(credentials);
61
62 if (wallet.type == WalletType.monero) {
68 - await sharedPreferences
69 - .setBool(
70 - PreferencesKey.moneroWalletUpdateV1Key(wallet.name),
71 - _isNewMoneroWalletPasswordUpdated);
63 + await sharedPreferences.setBool(
64 + PreferencesKey.moneroWalletUpdateV1Key(wallet.name), _isNewMoneroWalletPasswordUpdated);
65 }
66
67 return wallet;
@@ -78,15 +71,12 @@ class WalletCreationService {
71 checkIfExists(credentials.name);
72 final password = generateWalletPassword();
73 credentials.password = password;
81 - await keyService.saveWalletPassword(
82 - password: password, walletName: credentials.name);
74 + await keyService.saveWalletPassword(password: password, walletName: credentials.name);
75 final wallet = await _service!.restoreFromKeys(credentials);
76
77 if (wallet.type == WalletType.monero) {
86 - await sharedPreferences
87 - .setBool(
88 - PreferencesKey.moneroWalletUpdateV1Key(wallet.name),
89 - _isNewMoneroWalletPasswordUpdated);
78 + await sharedPreferences.setBool(
79 + PreferencesKey.moneroWalletUpdateV1Key(wallet.name), _isNewMoneroWalletPasswordUpdated);
80 }
81
82 return wallet;
@@ -96,15 +86,12 @@ class WalletCreationService {
86 checkIfExists(credentials.name);
87 final password = generateWalletPassword();
88 credentials.password = password;
99 - await keyService.saveWalletPassword(
100 - password: password, walletName: credentials.name);
89 + await keyService.saveWalletPassword(password: password, walletName: credentials.name);
90 final wallet = await _service!.restoreFromSeed(credentials);
91
92 if (wallet.type == WalletType.monero) {
104 - await sharedPreferences
105 - .setBool(
106 - PreferencesKey.moneroWalletUpdateV1Key(wallet.name),
107 - _isNewMoneroWalletPasswordUpdated);
93 + await sharedPreferences.setBool(
94 + PreferencesKey.moneroWalletUpdateV1Key(wallet.name), _isNewMoneroWalletPasswordUpdated);
95 }
96
97 return wallet;
lib/di.dart
+82 -12
@@ -13,6 +13,7 @@ import 'package:cake_wallet/entities/exchange_api_mode.dart';
13 import 'package:cake_wallet/entities/parse_address_from_domain.dart';
14 import 'package:cake_wallet/entities/receive_page_option.dart';
15 import 'package:cake_wallet/ethereum/ethereum.dart';
16 +import 'package:cake_wallet/nano/nano.dart';
17 import 'package:cake_wallet/ionia/ionia_anypay.dart';
18 import 'package:cake_wallet/ionia/ionia_gift_card.dart';
19 import 'package:cake_wallet/ionia/ionia_tip.dart';
@@ -26,8 +27,13 @@ import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_wallet
27 import 'package:cake_wallet/src/screens/dashboard/edit_token_page.dart';
28 import 'package:cake_wallet/src/screens/dashboard/home_settings_page.dart';
29 import 'package:cake_wallet/src/screens/dashboard/widgets/transactions_page.dart';
30 +import 'package:cake_wallet/src/screens/nano/nano_change_rep_page.dart';
31 +import 'package:cake_wallet/src/screens/nano_accounts/nano_account_edit_or_create_page.dart';
32 +import 'package:cake_wallet/src/screens/nano_accounts/nano_account_list_page.dart';
33 +import 'package:cake_wallet/src/screens/nodes/pow_node_create_or_edit_page.dart';
34 import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
35 import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
36 +import 'package:cake_wallet/src/screens/restore/wallet_restore_choose_derivation.dart';
37 import 'package:cake_wallet/src/screens/settings/display_settings_page.dart';
38 import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
39 import 'package:cake_wallet/src/screens/settings/other_settings_page.dart';
@@ -73,6 +79,9 @@ import 'package:cake_wallet/src/screens/dashboard/widgets/balance_page.dart';
79 import 'package:cake_wallet/view_model/ionia/ionia_account_view_model.dart';
80 import 'package:cake_wallet/view_model/ionia/ionia_gift_cards_list_view_model.dart';
81 import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dart';
82 +import 'package:cake_wallet/view_model/nano_account_list/nano_account_edit_or_create_view_model.dart';
83 +import 'package:cake_wallet/view_model/nano_account_list/nano_account_list_view_model.dart';
84 +import 'package:cake_wallet/view_model/node_list/pow_node_list_view_model.dart';
85 import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart';
86 import 'package:cake_wallet/view_model/restore/restore_from_qr_vm.dart';
87 import 'package:cake_wallet/view_model/settings/display_settings_view_model.dart';
@@ -83,7 +92,9 @@ import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart
92 import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
93 import 'package:cake_wallet/view_model/wallet_list/wallet_edit_view_model.dart';
94 import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
95 +import 'package:cake_wallet/view_model/wallet_restore_choose_derivation_view_model.dart';
96 import 'package:cw_core/erc20_token.dart';
97 +import 'package:cw_core/nano_account.dart';
98 import 'package:cw_core/unspent_coins_info.dart';
99 import 'package:cake_wallet/core/backup_service.dart';
100 import 'package:cw_core/wallet_service.dart';
@@ -206,6 +217,7 @@ import 'package:cake_wallet/src/screens/receive/fullscreen_qr_page.dart';
217 import 'package:cake_wallet/core/wallet_loading_service.dart';
218 import 'package:cw_core/crypto_currency.dart';
219 import 'package:cake_wallet/entities/qr_view_data.dart';
220 +import 'package:cake_wallet/nano/nano.dart' as nanoNano;
221
222 import 'core/totp_request_details.dart';
223
@@ -214,6 +226,7 @@ final getIt = GetIt.instance;
226 var _isSetupFinished = false;
227 late Box<WalletInfo> _walletInfoSource;
228 late Box<Node> _nodeSource;
229 +late Box<Node> _powNodeSource;
230 late Box<Contact> _contactSource;
231 late Box<Trade> _tradesSource;
232 late Box<Template> _templates;
@@ -226,6 +239,7 @@ late Box<AnonpayInvoiceInfo> _anonpayInvoiceInfoSource;
239 Future<void> setup({
240 required Box<WalletInfo> walletInfoSource,
241 required Box<Node> nodeSource,
242 + required Box<Node> powNodeSource,
243 required Box<Contact> contactSource,
244 required Box<Trade> tradesSource,
245 required Box<Template> templates,
@@ -237,6 +251,7 @@ Future<void> setup({
251 }) async {
252 _walletInfoSource = walletInfoSource;
253 _nodeSource = nodeSource;
254 + _powNodeSource = powNodeSource;
255 _contactSource = contactSource;
256 _tradesSource = tradesSource;
257 _templates = templates;
@@ -259,6 +274,7 @@ Future<void> setup({
274
275 final settingsStore = await SettingsStoreBase.load(
276 nodeSource: _nodeSource,
277 + powNodeSource: _powNodeSource,
278 isBitcoinBuyEnabled: isBitcoinBuyEnabled,
279 // Enforce darkTheme on platforms other than mobile till the design for other themes is completed
280 initialTheme: ResponsiveLayoutUtil.instance.isMobile && DeviceInfo.instance.isMobile
@@ -271,6 +287,7 @@ Future<void> setup({
287 }
288
289 getIt.registerFactory<Box<Node>>(() => _nodeSource);
290 + getIt.registerFactory<Box<Node>>(() => _powNodeSource, instanceName: Node.boxName + "pow");
291
292 getIt.registerSingleton<FlutterSecureStorage>(FlutterSecureStorage());
293 getIt.registerSingleton(AuthenticationStore());
@@ -401,7 +418,7 @@ Future<void> setup({
418 }
419 if (appStore.wallet != null) {
420 authStore.allowed();
404 -
421 +
422 if (appStore.wallet!.type == WalletType.ethereum) {
423 getIt.get<Web3WalletService>().init();
424 }
@@ -449,7 +466,7 @@ Future<void> setup({
466 }, instanceName: 'login');
467
468 getIt.registerSingleton<BottomSheetService>(BottomSheetServiceImpl());
452 -
469 +
470 final appStore = getIt.get<AppStore>();
471
472 getIt.registerLazySingleton<WalletConnectKeyService>(() => KeyServiceImpl(appStore.wallet!));
@@ -612,20 +629,30 @@ Future<void> setup({
629 editingWallet: editingWallet);
630 });
631
615 - getIt.registerFactory(() {
632 + getIt.registerFactory<NanoAccountListViewModel>(() {
633 final wallet = getIt.get<AppStore>().wallet!;
634 + if (wallet.type == WalletType.nano || wallet.type == WalletType.banano) {
635 + return NanoAccountListViewModel(wallet);
636 + }
637 + throw Exception(
638 + 'Unexpected wallet type: ${wallet.type} for generate Nano/Banano AccountListViewModel');
639 + });
640
641 + getIt.registerFactory<MoneroAccountListViewModel>(() {
642 + final wallet = getIt.get<AppStore>().wallet!;
643 if (wallet.type == WalletType.monero || wallet.type == WalletType.haven) {
644 return MoneroAccountListViewModel(wallet);
645 }
621 -
646 throw Exception(
623 - 'Unexpected wallet type: ${wallet.type} for generate MoneroAccountListViewModel');
647 + 'Unexpected wallet type: ${wallet.type} for generate Nano/Monero AccountListViewModel');
648 });
649
650 getIt.registerFactory(
651 () => MoneroAccountListPage(accountListViewModel: getIt.get<MoneroAccountListViewModel>()));
652
653 + getIt.registerFactory(
654 + () => NanoAccountListPage(accountListViewModel: getIt.get<NanoAccountListViewModel>()));
655 +
656 /*getIt.registerFactory(() {
657 final wallet = getIt.get<AppStore>().wallet;
658
@@ -653,6 +680,18 @@ Future<void> setup({
680 moneroAccountCreationViewModel:
681 getIt.get<MoneroAccountEditOrCreateViewModel>(param1: account)));
682
683 + getIt.registerFactoryParam<NanoAccountEditOrCreateViewModel, NanoAccount?, void>(
684 + (NanoAccount? account, _) =>
685 + NanoAccountEditOrCreateViewModel(nano!.getAccountList(getIt.get<AppStore>().wallet!),
686 + // banano?.getAccountList(getIt.get<AppStore>().wallet!),
687 + wallet: getIt.get<AppStore>().wallet!,
688 + accountListItem: account));
689 +
690 + getIt.registerFactoryParam<NanoAccountEditOrCreatePage, NanoAccount?, void>(
691 + (NanoAccount? account, _) => NanoAccountEditOrCreatePage(
692 + nanoAccountCreationViewModel:
693 + getIt.get<NanoAccountEditOrCreateViewModel>(param1: account)));
694 +
695 getIt.registerFactory(() {
696 return DisplaySettingsViewModel(getIt.get<SettingsStore>());
697 });
@@ -696,6 +735,11 @@ Future<void> setup({
735 return NodeListViewModel(_nodeSource, appStore);
736 });
737
738 + getIt.registerFactory(() {
739 + final appStore = getIt.get<AppStore>();
740 + return PowNodeListViewModel(_powNodeSource, appStore);
741 + });
742 +
743 getIt.registerFactory(
744 () => ConnectionSyncPage(getIt.get<DashboardViewModel>(), getIt.get<Web3WalletService>()),
745 );
@@ -709,13 +753,23 @@ Future<void> setup({
753
754 getIt.registerFactory(() => OtherSettingsPage(getIt.get<OtherSettingsViewModel>()));
755
712 - getIt.registerFactoryParam<NodeCreateOrEditViewModel, WalletType?, void>((WalletType? type, _) =>
713 - NodeCreateOrEditViewModel(
714 - _nodeSource, type ?? getIt.get<AppStore>().wallet!.type, getIt.get<SettingsStore>()));
756 + getIt.registerFactory(() => NanoChangeRepPage(getIt.get<AppStore>().wallet!));
757 +
758 + getIt.registerFactoryParam<NodeCreateOrEditViewModel, WalletType?, bool?>(
759 + (WalletType? type, bool? isPow) => NodeCreateOrEditViewModel(
760 + (isPow ?? false) ? _powNodeSource : _nodeSource,
761 + type ?? getIt.get<AppStore>().wallet!.type,
762 + getIt.get<SettingsStore>()));
763
764 getIt.registerFactoryParam<NodeCreateOrEditPage, Node?, bool?>(
765 (Node? editingNode, bool? isSelected) => NodeCreateOrEditPage(
718 - nodeCreateOrEditViewModel: getIt.get<NodeCreateOrEditViewModel>(),
766 + nodeCreateOrEditViewModel: getIt.get<NodeCreateOrEditViewModel>(param2: false),
767 + editingNode: editingNode,
768 + isSelected: isSelected));
769 +
770 + getIt.registerFactoryParam<PowNodeCreateOrEditPage, Node?, bool?>(
771 + (Node? editingNode, bool? isSelected) => PowNodeCreateOrEditPage(
772 + nodeCreateOrEditViewModel: getIt.get<NodeCreateOrEditViewModel>(param2: true),
773 editingNode: editingNode,
774 isSelected: isSelected));
775
@@ -771,6 +825,8 @@ Future<void> setup({
825 return bitcoin!.createLitecoinWalletService(_walletInfoSource, _unspentCoinsInfoSource);
826 case WalletType.ethereum:
827 return ethereum!.createEthereumWalletService(_walletInfoSource);
828 + case WalletType.nano:
829 + return nano!.createNanoWalletService(_walletInfoSource);
830 default:
831 throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService');
832 }
@@ -798,6 +854,15 @@ Future<void> setup({
854 getIt.registerFactoryParam<WalletRestorePage, WalletType, void>(
855 (type, _) => WalletRestorePage(getIt.get<WalletRestoreViewModel>(param1: type)));
856
857 + getIt.registerFactoryParam<WalletRestoreChooseDerivationViewModel, List<DerivationInfo>, void>(
858 + (derivations, _) => WalletRestoreChooseDerivationViewModel(derivationInfos: derivations));
859 +
860 + getIt.registerFactoryParam<WalletRestoreChooseDerivationPage, List<DerivationInfo>, void>(
861 + (credentials, _) =>
862 + WalletRestoreChooseDerivationPage(getIt.get<WalletRestoreChooseDerivationViewModel>(
863 + param1: credentials,
864 + )));
865 +
866 getIt.registerFactoryParam<TransactionDetailsViewModel, TransactionInfo, void>(
867 (TransactionInfo transactionInfo, _) {
868 final wallet = getIt.get<AppStore>().wallet!;
@@ -911,8 +976,8 @@ Future<void> setup({
976
977 getIt.registerFactory(() => YatService());
978
914 - getIt.registerFactory(() => AddressResolver(
915 - yatService: getIt.get<YatService>(), wallet: getIt.get<AppStore>().wallet!));
979 + getIt.registerFactory(() =>
980 + AddressResolver(yatService: getIt.get<YatService>(), wallet: getIt.get<AppStore>().wallet!));
981
982 getIt.registerFactoryParam<FullscreenQRPage, QrViewData, void>(
983 (QrViewData viewData, _) => FullscreenQRPage(qrViewData: viewData));
@@ -1077,7 +1142,12 @@ Future<void> setup({
1142 ),
1143 );
1144
1080 - getIt.registerFactory<ManageNodesPage>(() => ManageNodesPage(getIt.get<NodeListViewModel>()));
1145 + getIt.registerFactoryParam<ManageNodesPage, bool, void>((bool isPow, _) {
1146 + if (isPow) {
1147 + return ManageNodesPage(isPow, powNodeListViewModel: getIt.get<PowNodeListViewModel>());
1148 + }
1149 + return ManageNodesPage(isPow, nodeListViewModel: getIt.get<NodeListViewModel>());
1150 + });
1151
1152 _isSetupFinished = true;
1153 }
lib/entities/default_settings_migration.dart
+165 -136
@@ -26,12 +26,15 @@ const cakeWalletBitcoinElectrumUri = 'electrum.cakewallet.com:50002';
26 const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002';
27 const havenDefaultNodeUri = 'nodes.havenprotocol.org:443';
28 const ethereumDefaultNodeUri = 'ethereum.publicnode.com';
29 +const nanoDefaultNodeUri = 'rpc.nano.to';
30 +const nanoDefaultPowNodeUri = 'rpc.nano.to';
31
32 Future<void> defaultSettingsMigration(
33 {required int version,
34 required SharedPreferences sharedPreferences,
35 required FlutterSecureStorage secureStorage,
36 required Box<Node> nodes,
37 + required Box<Node> powNodes,
38 required Box<WalletInfo> walletInfoSource,
39 required Box<Trade> tradeSource,
40 required Box<Contact> contactSource}) async {
@@ -40,41 +43,36 @@ Future<void> defaultSettingsMigration(
43 }
44
45 // check current nodes for nullability regardless of the version
43 - await checkCurrentNodes(nodes, sharedPreferences);
46 + await checkCurrentNodes(nodes, powNodes, sharedPreferences);
47
45 - await _validateWalletInfoBoxData(walletInfoSource);
48 + final isNewInstall =
49 + sharedPreferences.getInt(PreferencesKey.currentDefaultSettingsMigrationVersion) == null;
50
47 - final isNewInstall = sharedPreferences
48 - .getInt(PreferencesKey.currentDefaultSettingsMigrationVersion) == null;
51 + await _validateWalletInfoBoxData(walletInfoSource);
52
50 - await sharedPreferences.setBool(
51 - PreferencesKey.isNewInstall, isNewInstall);
53 + await sharedPreferences.setBool(PreferencesKey.isNewInstall, isNewInstall);
54
55 + final currentVersion =
56 + sharedPreferences.getInt(PreferencesKey.currentDefaultSettingsMigrationVersion) ?? 0;
57
54 - final currentVersion = sharedPreferences
55 - .getInt(PreferencesKey.currentDefaultSettingsMigrationVersion) ??
56 - 0;
58 if (currentVersion >= version) {
59 return;
60 }
61
62 final migrationVersionsLength = version - currentVersion;
62 - final migrationVersions = List<int>.generate(
63 - migrationVersionsLength, (i) => currentVersion + (i + 1));
63 + final migrationVersions =
64 + List<int>.generate(migrationVersionsLength, (i) => currentVersion + (i + 1));
65
66 await Future.forEach(migrationVersions, (int version) async {
67 try {
68 switch (version) {
69 case 1:
70 await sharedPreferences.setString(
70 - PreferencesKey.currentFiatCurrencyKey,
71 - FiatCurrency.usd.toString());
72 - await sharedPreferences.setInt(
73 - PreferencesKey.currentTransactionPriorityKeyLegacy,
71 + PreferencesKey.currentFiatCurrencyKey, FiatCurrency.usd.toString());
72 + await sharedPreferences.setInt(PreferencesKey.currentTransactionPriorityKeyLegacy,
73 monero!.getDefaultTransactionPriority().raw);
74 await sharedPreferences.setInt(
76 - PreferencesKey.currentBalanceDisplayModeKey,
77 - BalanceDisplayMode.availableBalance.raw);
75 + PreferencesKey.currentBalanceDisplayModeKey, BalanceDisplayMode.availableBalance.raw);
76 await sharedPreferences.setBool('save_recipient_address', true);
77 await resetToDefault(nodes);
78 await changeMoneroCurrentNodeToDefault(
@@ -83,14 +81,12 @@ Future<void> defaultSettingsMigration(
81 sharedPreferences: sharedPreferences, nodes: nodes);
82 await changeLitecoinCurrentElectrumServerToDefault(
83 sharedPreferences: sharedPreferences, nodes: nodes);
86 - await changeHavenCurrentNodeToDefault(
87 - sharedPreferences: sharedPreferences, nodes: nodes);
84 + await changeHavenCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
85
86 break;
87 case 2:
88 await replaceNodesMigration(nodes: nodes);
92 - await replaceDefaultNode(
93 - sharedPreferences: sharedPreferences, nodes: nodes);
89 + await replaceDefaultNode(sharedPreferences: sharedPreferences, nodes: nodes);
90
91 break;
92 case 3:
@@ -124,7 +120,7 @@ Future<void> defaultSettingsMigration(
120 break;
121
122 case 12:
127 - await checkCurrentNodes(nodes, sharedPreferences);
123 + await checkCurrentNodes(nodes, powNodes, sharedPreferences);
124 break;
125
126 case 13:
@@ -135,14 +131,13 @@ Future<void> defaultSettingsMigration(
131 await addLitecoinElectrumServerList(nodes: nodes);
132 await changeLitecoinCurrentElectrumServerToDefault(
133 sharedPreferences: sharedPreferences, nodes: nodes);
138 - await checkCurrentNodes(nodes, sharedPreferences);
134 + await checkCurrentNodes(nodes, powNodes, sharedPreferences);
135 break;
136
137 case 16:
138 await addHavenNodeList(nodes: nodes);
143 - await changeHavenCurrentNodeToDefault(
144 - sharedPreferences: sharedPreferences, nodes: nodes);
145 - await checkCurrentNodes(nodes, sharedPreferences);
139 + await changeHavenCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
140 + await checkCurrentNodes(nodes, powNodes, sharedPreferences);
141 break;
142
143 case 17:
@@ -164,6 +159,13 @@ Future<void> defaultSettingsMigration(
159 await changeEthereumCurrentNodeToDefault(
160 sharedPreferences: sharedPreferences, nodes: nodes);
161 break;
162 + case 22:
163 + await addNanoNodeList(nodes: nodes);
164 + await addNanoPowNodeList(nodes: nodes);
165 + await changeNanoCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
166 + await changeNanoCurrentPowNodeToDefault(
167 + sharedPreferences: sharedPreferences, nodes: powNodes);
168 + break;
169
170 default:
171 break;
@@ -176,8 +178,7 @@ Future<void> defaultSettingsMigration(
178 }
179 });
180
179 - await sharedPreferences.setInt(
180 - PreferencesKey.currentDefaultSettingsMigrationVersion, version);
181 + await sharedPreferences.setInt(PreferencesKey.currentDefaultSettingsMigrationVersion, version);
182 }
183
184 Future<void> _validateWalletInfoBoxData(Box<WalletInfo> walletInfoSource) async {
@@ -247,8 +248,8 @@ Future<void> validateBitcoinSavedTransactionPriority(SharedPreferences sharedPre
248 final int? savedBitcoinPriority =
249 sharedPreferences.getInt(PreferencesKey.bitcoinTransactionPriority);
250 if (!bitcoin!.getTransactionPriorities().any((element) => element.raw == savedBitcoinPriority)) {
250 - await sharedPreferences.setInt(
251 - PreferencesKey.bitcoinTransactionPriority, bitcoin!.getMediumTransactionPriority().serialize());
251 + await sharedPreferences.setInt(PreferencesKey.bitcoinTransactionPriority,
252 + bitcoin!.getMediumTransactionPriority().serialize());
253 }
254 }
255
@@ -265,10 +266,9 @@ Future<void> replaceNodesMigration({required Box<Node> nodes}) async {
266 final replaceNodes = <String, Node>{
267 'eu-node.cakewallet.io:18081':
268 Node(uri: 'xmr-node-eu.cakewallet.com:18081', type: WalletType.monero),
268 - 'node.cakewallet.io:18081': Node(
269 - uri: 'xmr-node-usa-east.cakewallet.com:18081', type: WalletType.monero),
270 - 'node.xmr.ru:13666':
271 - Node(uri: 'node.monero.net:18081', type: WalletType.monero)
269 + 'node.cakewallet.io:18081':
270 + Node(uri: 'xmr-node-usa-east.cakewallet.com:18081', type: WalletType.monero),
271 + 'node.xmr.ru:13666': Node(uri: 'node.monero.net:18081', type: WalletType.monero)
272 };
273
274 nodes.values.forEach((Node node) async {
@@ -284,8 +284,7 @@ Future<void> replaceNodesMigration({required Box<Node> nodes}) async {
284 }
285
286 Future<void> changeMoneroCurrentNodeToDefault(
287 - {required SharedPreferences sharedPreferences,
288 - required Box<Node> nodes}) async {
287 + {required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
288 final node = getMoneroDefaultNode(nodes: nodes);
289 final nodeId = node.key as int? ?? 0; // 0 - England
290
@@ -293,27 +292,35 @@ Future<void> changeMoneroCurrentNodeToDefault(
292 }
293
294 Node? getBitcoinDefaultElectrumServer({required Box<Node> nodes}) {
296 - return nodes.values.firstWhereOrNull(
297 - (Node node) => node.uriRaw == cakeWalletBitcoinElectrumUri)
298 - ?? nodes.values.firstWhereOrNull((node) => node.type == WalletType.bitcoin);
295 + return nodes.values
296 + .firstWhereOrNull((Node node) => node.uriRaw == cakeWalletBitcoinElectrumUri) ??
297 + nodes.values.firstWhereOrNull((node) => node.type == WalletType.bitcoin);
298 }
299
300 Node? getLitecoinDefaultElectrumServer({required Box<Node> nodes}) {
302 - return nodes.values.firstWhereOrNull(
303 - (Node node) => node.uriRaw == cakeWalletLitecoinElectrumUri)
304 - ?? nodes.values.firstWhereOrNull((node) => node.type == WalletType.litecoin);
301 + return nodes.values
302 + .firstWhereOrNull((Node node) => node.uriRaw == cakeWalletLitecoinElectrumUri) ??
303 + nodes.values.firstWhereOrNull((node) => node.type == WalletType.litecoin);
304 }
305
306 Node? getHavenDefaultNode({required Box<Node> nodes}) {
308 - return nodes.values.firstWhereOrNull(
309 - (Node node) => node.uriRaw == havenDefaultNodeUri)
310 - ?? nodes.values.firstWhereOrNull((node) => node.type == WalletType.haven);
307 + return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == havenDefaultNodeUri) ??
308 + nodes.values.firstWhereOrNull((node) => node.type == WalletType.haven);
309 }
310
311 Node? getEthereumDefaultNode({required Box<Node> nodes}) {
314 - return nodes.values.firstWhereOrNull(
315 - (Node node) => node.uriRaw == ethereumDefaultNodeUri)
316 - ?? nodes.values.firstWhereOrNull((node) => node.type == WalletType.ethereum);
312 + return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == ethereumDefaultNodeUri) ??
313 + nodes.values.firstWhereOrNull((node) => node.type == WalletType.ethereum);
314 +}
315 +
316 +Node? getNanoDefaultNode({required Box<Node> nodes}) {
317 + return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == nanoDefaultNodeUri) ??
318 + nodes.values.firstWhereOrNull((node) => node.type == WalletType.nano);
319 +}
320 +
321 +Node? getNanoDefaultPowNode({required Box<Node> nodes}) {
322 + return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == nanoDefaultPowNodeUri) ??
323 + nodes.values.firstWhereOrNull((node) => (node.type == WalletType.nano));
324 }
325
326 Node getMoneroDefaultNode({required Box<Node> nodes}) {
@@ -329,16 +336,14 @@ Node getMoneroDefaultNode({required Box<Node> nodes}) {
336 }
337
338 try {
332 - return nodes.values
333 - .firstWhere((Node node) => node.uriRaw == nodeUri);
334 - } catch(_) {
339 + return nodes.values.firstWhere((Node node) => node.uriRaw == nodeUri);
340 + } catch (_) {
341 return nodes.values.first;
342 }
343 }
344
345 Future<void> changeBitcoinCurrentElectrumServerToDefault(
340 - {required SharedPreferences sharedPreferences,
341 - required Box<Node> nodes}) async {
346 + {required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
347 final server = getBitcoinDefaultElectrumServer(nodes: nodes);
348 final serverId = server?.key as int? ?? 0;
349
@@ -346,8 +351,7 @@ Future<void> changeBitcoinCurrentElectrumServerToDefault(
351 }
352
353 Future<void> changeLitecoinCurrentElectrumServerToDefault(
349 - {required SharedPreferences sharedPreferences,
350 - required Box<Node> nodes}) async {
354 + {required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
355 final server = getLitecoinDefaultElectrumServer(nodes: nodes);
356 final serverId = server?.key as int? ?? 0;
357
@@ -355,8 +359,7 @@ Future<void> changeLitecoinCurrentElectrumServerToDefault(
359 }
360
361 Future<void> changeHavenCurrentNodeToDefault(
358 - {required SharedPreferences sharedPreferences,
359 - required Box<Node> nodes}) async {
362 + {required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
363 final node = getHavenDefaultNode(nodes: nodes);
364 final nodeId = node?.key as int? ?? 0;
365
@@ -364,25 +367,21 @@ Future<void> changeHavenCurrentNodeToDefault(
367 }
368
369 Future<void> replaceDefaultNode(
367 - {required SharedPreferences sharedPreferences,
368 - required Box<Node> nodes}) async {
370 + {required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
371 const nodesForReplace = <String>[
372 'xmr-node-uk.cakewallet.com:18081',
373 'eu-node.cakewallet.io:18081',
374 'node.cakewallet.io:18081'
375 ];
376 final currentNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
375 - final currentNode =
376 - nodes.values.firstWhereOrNull((Node node) => node.key == currentNodeId);
377 - final needToReplace =
378 - currentNode == null ? true : nodesForReplace.contains(currentNode.uriRaw);
377 + final currentNode = nodes.values.firstWhereOrNull((Node node) => node.key == currentNodeId);
378 + final needToReplace = currentNode == null ? true : nodesForReplace.contains(currentNode.uriRaw);
379
380 if (!needToReplace) {
381 return;
382 }
383
384 - await changeMoneroCurrentNodeToDefault(
385 - sharedPreferences: sharedPreferences, nodes: nodes);
384 + await changeMoneroCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
385 }
386
387 Future<void> updateNodeTypes({required Box<Node> nodes}) async {
@@ -421,14 +420,11 @@ Future<void> addHavenNodeList({required Box<Node> nodes}) async {
420 }
421 }
422
424 -Future<void> addAddressesForMoneroWallets(
425 - Box<WalletInfo> walletInfoSource) async {
426 - final moneroWalletsInfo =
427 - walletInfoSource.values.where((info) => info.type == WalletType.monero);
423 +Future<void> addAddressesForMoneroWallets(Box<WalletInfo> walletInfoSource) async {
424 + final moneroWalletsInfo = walletInfoSource.values.where((info) => info.type == WalletType.monero);
425 moneroWalletsInfo.forEach((info) async {
426 try {
430 - final walletPath =
431 - await pathForWallet(name: info.name, type: WalletType.monero);
427 + final walletPath = await pathForWallet(name: info.name, type: WalletType.monero);
428 final addressFilePath = '$walletPath.address.txt';
429 final addressFile = File(addressFilePath);
430
@@ -449,8 +445,7 @@ Future<void> updateDisplayModes(SharedPreferences sharedPreferences) async {
445 final currentBalanceDisplayMode =
446 sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey) ?? -1;
447 final balanceDisplayMode = currentBalanceDisplayMode < 2 ? 3 : 2;
452 - await sharedPreferences.setInt(
453 - PreferencesKey.currentBalanceDisplayModeKey, balanceDisplayMode);
448 + await sharedPreferences.setInt(PreferencesKey.currentBalanceDisplayModeKey, balanceDisplayMode);
449 }
450
451 Future<void> generateBackupPassword(FlutterSecureStorage secureStorage) async {
@@ -464,10 +459,9 @@ Future<void> generateBackupPassword(FlutterSecureStorage secureStorage) async {
459 await secureStorage.write(key: key, value: password);
460 }
461
467 -Future<void> changeTransactionPriorityAndFeeRateKeys(
468 - SharedPreferences sharedPreferences) async {
469 - final legacyTransactionPriority = sharedPreferences
470 - .getInt(PreferencesKey.currentTransactionPriorityKeyLegacy)!;
462 +Future<void> changeTransactionPriorityAndFeeRateKeys(SharedPreferences sharedPreferences) async {
463 + final legacyTransactionPriority =
464 + sharedPreferences.getInt(PreferencesKey.currentTransactionPriorityKeyLegacy)!;
465 await sharedPreferences.setInt(
466 PreferencesKey.moneroTransactionPriority, legacyTransactionPriority);
467 await sharedPreferences.setInt(PreferencesKey.bitcoinTransactionPriority,
@@ -477,10 +471,8 @@ Future<void> changeTransactionPriorityAndFeeRateKeys(
471 Future<void> changeDefaultMoneroNode(
472 Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
473 const cakeWalletMoneroNodeUriPattern = '.cakewallet.com';
480 - final currentMoneroNodeId =
481 - sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
482 - final currentMoneroNode =
483 - nodeSource.values.firstWhere((node) => node.key == currentMoneroNodeId);
474 + final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
475 + final currentMoneroNode = nodeSource.values.firstWhere((node) => node.key == currentMoneroNodeId);
476 final needToReplaceCurrentMoneroNode =
477 currentMoneroNode.uri.toString().contains(cakeWalletMoneroNodeUriPattern);
478
@@ -491,78 +483,87 @@ Future<void> changeDefaultMoneroNode(
483 }
484 });
485
494 - final newCakeWalletNode =
495 - Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
486 + final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
487
488 await nodeSource.add(newCakeWalletNode);
489
490 if (needToReplaceCurrentMoneroNode) {
500 - await sharedPreferences.setInt(
501 - PreferencesKey.currentNodeIdKey, newCakeWalletNode.key as int);
491 + await sharedPreferences.setInt(PreferencesKey.currentNodeIdKey, newCakeWalletNode.key as int);
492 }
493 }
494
495 Future<void> checkCurrentNodes(
506 - Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
507 - final currentMoneroNodeId =
508 - sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
496 + Box<Node> nodeSource, Box<Node> powNodeSource, SharedPreferences sharedPreferences) async {
497 + final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
498 final currentBitcoinElectrumSeverId =
499 sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
511 - final currentLitecoinElectrumSeverId = sharedPreferences
512 - .getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
513 - final currentHavenNodeId = sharedPreferences
514 - .getInt(PreferencesKey.currentHavenNodeIdKey);
515 - final currentEthereumNodeId = sharedPreferences
516 - .getInt(PreferencesKey.currentEthereumNodeIdKey);
517 - final currentMoneroNode = nodeSource.values.firstWhereOrNull(
518 - (node) => node.key == currentMoneroNodeId);
519 - final currentBitcoinElectrumServer = nodeSource.values.firstWhereOrNull(
520 - (node) => node.key == currentBitcoinElectrumSeverId);
521 - final currentLitecoinElectrumServer = nodeSource.values.firstWhereOrNull(
522 - (node) => node.key == currentLitecoinElectrumSeverId);
523 - final currentHavenNodeServer = nodeSource.values.firstWhereOrNull(
524 - (node) => node.key == currentHavenNodeId);
525 - final currentEthereumNodeServer = nodeSource.values.firstWhereOrNull(
526 - (node) => node.key == currentEthereumNodeId);
500 + final currentLitecoinElectrumSeverId =
501 + sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
502 + final currentHavenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey);
503 + final currentEthereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
504 + final currentNanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
505 + final currentNanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey);
506 + final currentMoneroNode =
507 + nodeSource.values.firstWhereOrNull((node) => node.key == currentMoneroNodeId);
508 + final currentBitcoinElectrumServer =
509 + nodeSource.values.firstWhereOrNull((node) => node.key == currentBitcoinElectrumSeverId);
510 + final currentLitecoinElectrumServer =
511 + nodeSource.values.firstWhereOrNull((node) => node.key == currentLitecoinElectrumSeverId);
512 + final currentHavenNodeServer =
513 + nodeSource.values.firstWhereOrNull((node) => node.key == currentHavenNodeId);
514 + final currentEthereumNodeServer =
515 + nodeSource.values.firstWhereOrNull((node) => node.key == currentEthereumNodeId);
516 + final currentNanoNodeServer =
517 + nodeSource.values.firstWhereOrNull((node) => node.key == currentNanoNodeId);
518 + final currentNanoPowNodeServer =
519 + powNodeSource.values.firstWhereOrNull((node) => node.key == currentNanoPowNodeId);
520
521 if (currentMoneroNode == null) {
529 - final newCakeWalletNode =
530 - Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
522 + final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
523 await nodeSource.add(newCakeWalletNode);
532 - await sharedPreferences.setInt(
533 - PreferencesKey.currentNodeIdKey, newCakeWalletNode.key as int);
524 + await sharedPreferences.setInt(PreferencesKey.currentNodeIdKey, newCakeWalletNode.key as int);
525 }
526
527 if (currentBitcoinElectrumServer == null) {
537 - final cakeWalletElectrum =
538 - Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin);
528 + final cakeWalletElectrum = Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin);
529 await nodeSource.add(cakeWalletElectrum);
530 await sharedPreferences.setInt(
541 - PreferencesKey.currentBitcoinElectrumSererIdKey,
542 - cakeWalletElectrum.key as int);
531 + PreferencesKey.currentBitcoinElectrumSererIdKey, cakeWalletElectrum.key as int);
532 }
533
534 if (currentLitecoinElectrumServer == null) {
546 - final cakeWalletElectrum =
547 - Node(uri: cakeWalletLitecoinElectrumUri, type: WalletType.litecoin);
535 + final cakeWalletElectrum = Node(uri: cakeWalletLitecoinElectrumUri, type: WalletType.litecoin);
536 await nodeSource.add(cakeWalletElectrum);
537 await sharedPreferences.setInt(
550 - PreferencesKey.currentLitecoinElectrumSererIdKey,
551 - cakeWalletElectrum.key as int);
538 + PreferencesKey.currentLitecoinElectrumSererIdKey, cakeWalletElectrum.key as int);
539 }
540
541 if (currentHavenNodeServer == null) {
542 final node = Node(uri: havenDefaultNodeUri, type: WalletType.haven);
543 await nodeSource.add(node);
557 - await sharedPreferences.setInt(
558 - PreferencesKey.currentHavenNodeIdKey, node.key as int);
544 + await sharedPreferences.setInt(PreferencesKey.currentHavenNodeIdKey, node.key as int);
545 }
546
547 if (currentEthereumNodeServer == null) {
548 final node = Node(uri: ethereumDefaultNodeUri, type: WalletType.ethereum);
549 await nodeSource.add(node);
564 - await sharedPreferences.setInt(
565 - PreferencesKey.currentEthereumNodeIdKey, node.key as int);
550 + await sharedPreferences.setInt(PreferencesKey.currentEthereumNodeIdKey, node.key as int);
551 + }
552 +
553 + if (currentNanoNodeServer == null) {
554 + final node = Node(uri: nanoDefaultNodeUri, useSSL: true, type: WalletType.nano);
555 + await nodeSource.add(node);
556 + await sharedPreferences.setInt(PreferencesKey.currentNanoNodeIdKey, node.key as int);
557 + }
558 +
559 + if (currentNanoPowNodeServer == null) {
560 + Node? node = powNodeSource.values
561 + .firstWhereOrNull((node) => node.uri.toString() == nanoDefaultPowNodeUri);
562 + if (node == null) {
563 + node = Node(uri: nanoDefaultPowNodeUri, useSSL: true, type: WalletType.nano);
564 + await powNodeSource.add(node);
565 + }
566 + await sharedPreferences.setInt(PreferencesKey.currentNanoPowNodeIdKey, node.key as int);
567 }
568 }
569
@@ -570,31 +571,27 @@ Future<void> resetBitcoinElectrumServer(
571 Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
572 final currentElectrumSeverId =
573 sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
573 - final oldElectrumServer = nodeSource.values.firstWhereOrNull(
574 - (node) => node.uri.toString().contains('electrumx.cakewallet.com'));
575 - var cakeWalletNode = nodeSource.values.firstWhereOrNull(
576 - (node) => node.uriRaw.toString() == cakeWalletBitcoinElectrumUri);
574 + final oldElectrumServer = nodeSource.values
575 + .firstWhereOrNull((node) => node.uri.toString().contains('electrumx.cakewallet.com'));
576 + var cakeWalletNode = nodeSource.values
577 + .firstWhereOrNull((node) => node.uriRaw.toString() == cakeWalletBitcoinElectrumUri);
578
579 if (cakeWalletNode == null) {
579 - cakeWalletNode =
580 - Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin);
580 + cakeWalletNode = Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin);
581 await nodeSource.add(cakeWalletNode);
582 }
583
584 if (currentElectrumSeverId == oldElectrumServer?.key) {
585 await sharedPreferences.setInt(
586 - PreferencesKey.currentBitcoinElectrumSererIdKey,
587 - cakeWalletNode.key as int);
586 + PreferencesKey.currentBitcoinElectrumSererIdKey, cakeWalletNode.key as int);
587 }
588
589 await oldElectrumServer?.delete();
590 }
591
593 -Future<void> changeDefaultHavenNode(
594 - Box<Node> nodeSource) async {
592 +Future<void> changeDefaultHavenNode(Box<Node> nodeSource) async {
593 const previousHavenDefaultNodeUri = 'vault.havenprotocol.org:443';
596 - final havenNodes = nodeSource.values.where(
597 - (node) => node.uriRaw == previousHavenDefaultNodeUri);
594 + final havenNodes = nodeSource.values.where((node) => node.uriRaw == previousHavenDefaultNodeUri);
595 havenNodes.forEach((node) async {
596 node.uriRaw = havenDefaultNodeUri;
597 await node.save();
@@ -607,8 +604,8 @@ Future<void> migrateExchangeStatus(SharedPreferences sharedPreferences) async {
604 return;
605 }
606
610 - await sharedPreferences.setInt(PreferencesKey.exchangeStatusKey, isExchangeDisabled
611 - ? ExchangeApiMode.disabled.raw : ExchangeApiMode.enabled.raw);
607 + await sharedPreferences.setInt(PreferencesKey.exchangeStatusKey,
608 + isExchangeDisabled ? ExchangeApiMode.disabled.raw : ExchangeApiMode.enabled.raw);
609
610 await sharedPreferences.remove(PreferencesKey.disableExchangeKey);
611 }
@@ -623,10 +620,42 @@ Future<void> addEthereumNodeList({required Box<Node> nodes}) async {
620 }
621
622 Future<void> changeEthereumCurrentNodeToDefault(
626 - {required SharedPreferences sharedPreferences,
627 - required Box<Node> nodes}) async {
623 + {required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
624 final node = getEthereumDefaultNode(nodes: nodes);
625 final nodeId = node?.key as int? ?? 0;
626
627 await sharedPreferences.setInt(PreferencesKey.currentEthereumNodeIdKey, nodeId);
628 }
629 +
630 +Future<void> addNanoNodeList({required Box<Node> nodes}) async {
631 + final nodeList = await loadDefaultNanoNodes();
632 + for (var node in nodeList) {
633 + if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
634 + await nodes.add(node);
635 + }
636 + }
637 +}
638 +
639 +Future<void> addNanoPowNodeList({required Box<Node> nodes}) async {
640 + final nodeList = await loadDefaultNanoPowNodes();
641 + for (var node in nodeList) {
642 + if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
643 + await nodes.add(node);
644 + }
645 + }
646 +}
647 +
648 +Future<void> changeNanoCurrentNodeToDefault(
649 + {required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
650 + final node = getNanoDefaultNode(nodes: nodes);
651 + final nodeId = node?.key as int? ?? 0;
652 +
653 + await sharedPreferences.setInt(PreferencesKey.currentNanoNodeIdKey, nodeId);
654 +}
655 +
656 +Future<void> changeNanoCurrentPowNodeToDefault(
657 + {required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
658 + final node = getNanoDefaultPowNode(nodes: nodes);
659 + final nodeId = node?.key as int? ?? 0;
660 + await sharedPreferences.setInt(PreferencesKey.currentNanoPowNodeIdKey, nodeId);
661 +}
lib/entities/main_actions.dart
+4 -2
@@ -52,6 +52,8 @@ class MainActions {
52 case WalletType.bitcoin:
53 case WalletType.litecoin:
54 case WalletType.ethereum:
55 + case WalletType.nano:
56 + case WalletType.banano:
57 switch (defaultBuyProvider) {
58 case BuyProviderType.AskEachTime:
59 Navigator.pushNamed(context, Routes.buy);
@@ -73,7 +75,7 @@ class MainActions {
75 builder: (BuildContext context) {
76 return AlertWithOneAction(
77 alertTitle: S.of(context).buy,
76 - alertContent: S.of(context).buy_alert_content,
78 + alertContent: S.of(context).unsupported_asset,
79 buttonText: S.of(context).ok,
80 buttonAction: () => Navigator.of(context).pop());
81 });
@@ -143,7 +145,7 @@ class MainActions {
145 builder: (BuildContext context) {
146 return AlertWithOneAction(
147 alertTitle: S.of(context).sell,
146 - alertContent: S.of(context).sell_alert_content,
148 + alertContent: S.of(context).unsupported_asset,
149 buttonText: S.of(context).ok,
150 buttonAction: () => Navigator.of(context).pop());
151 },
lib/entities/node_list.dart
+50 -9
@@ -21,13 +21,12 @@ Future<List<Node>> loadDefaultNodes() async {
21 }
22
23 Future<List<Node>> loadBitcoinElectrumServerList() async {
24 - final serverListRaw =
25 - await rootBundle.loadString('assets/bitcoin_electrum_server_list.yml');
24 + final serverListRaw = await rootBundle.loadString('assets/bitcoin_electrum_server_list.yml');
25 final loadedServerList = loadYaml(serverListRaw) as YamlList;
26 final serverList = <Node>[];
27
28 for (final raw in loadedServerList) {
30 - if (raw is Map) {
29 + if (raw is Map) {
30 final node = Node.fromMap(Map<String, Object>.from(raw));
31 node.type = WalletType.bitcoin;
32 serverList.add(node);
@@ -38,8 +37,7 @@ Future<List<Node>> loadBitcoinElectrumServerList() async {
37 }
38
39 Future<List<Node>> loadLitecoinElectrumServerList() async {
41 - final serverListRaw =
42 - await rootBundle.loadString('assets/litecoin_electrum_server_list.yml');
40 + final serverListRaw = await rootBundle.loadString('assets/litecoin_electrum_server_list.yml');
41 final loadedServerList = loadYaml(serverListRaw) as YamlList;
42 final serverList = <Node>[];
43
@@ -66,7 +64,7 @@ Future<List<Node>> loadDefaultHavenNodes() async {
64 nodes.add(node);
65 }
66 }
69 -
67 +
68 return nodes;
69 }
70
@@ -86,17 +84,60 @@ Future<List<Node>> loadDefaultEthereumNodes() async {
84 return nodes;
85 }
86
87 +Future<List<Node>> loadDefaultNanoNodes() async {
88 + final nodesRaw = await rootBundle.loadString('assets/nano_node_list.yml');
89 + final loadedNodes = loadYaml(nodesRaw) as YamlList;
90 + final nodes = <Node>[];
91 +
92 + for (final raw in loadedNodes) {
93 + if (raw is Map) {
94 + final node = Node.fromMap(Map<String, Object>.from(raw));
95 + node.type = WalletType.nano;
96 + nodes.add(node);
97 + }
98 + }
99 +
100 + return nodes;
101 +}
102 +
103 +Future<List<Node>> loadDefaultNanoPowNodes() async {
104 + final powNodesRaw = await rootBundle.loadString('assets/nano_pow_node_list.yml');
105 + final loadedPowNodes = loadYaml(powNodesRaw) as YamlList;
106 + final nodes = <Node>[];
107 +
108 + for (final raw in loadedPowNodes) {
109 + if (raw is Map) {
110 + final node = Node.fromMap(Map<String, Object>.from(raw));
111 + node.type = WalletType.nano;
112 + nodes.add(node);
113 + }
114 + }
115 +
116 + return nodes;
117 +}
118 +
119 Future resetToDefault(Box<Node> nodeSource) async {
120 final moneroNodes = await loadDefaultNodes();
121 final bitcoinElectrumServerList = await loadBitcoinElectrumServerList();
122 final litecoinElectrumServerList = await loadLitecoinElectrumServerList();
123 final havenNodes = await loadDefaultHavenNodes();
94 - final nodes =
95 - moneroNodes +
124 + final ethereumNodes = await loadDefaultEthereumNodes();
125 + final nanoNodes = await loadDefaultNanoNodes();
126 +
127 + final nodes = moneroNodes +
128 bitcoinElectrumServerList +
129 litecoinElectrumServerList +
98 - havenNodes;
130 + havenNodes +
131 + ethereumNodes +
132 + nanoNodes;
133
134 await nodeSource.clear();
135 await nodeSource.addAll(nodes);
136 }
137 +
138 +Future resetPowToDefault(Box<Node> powNodeSource) async {
139 + final nanoPowNodes = await loadDefaultNanoPowNodes();
140 + final nodes = nanoPowNodes;
141 + await powNodeSource.clear();
142 + await powNodeSource.addAll(nodes);
143 +}
\ No newline at end of file
lib/entities/preferences_key.dart
+4
@@ -6,6 +6,10 @@ class PreferencesKey {
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 currentNanoNodeIdKey = 'current_node_id_nano';
10 + static const currentNanoPowNodeIdKey = 'current_node_id_nano_pow';
11 + static const currentBananoNodeIdKey = 'current_node_id_banano';
12 + static const currentBananoPowNodeIdKey = 'current_node_id_banano_pow';
13 static const currentFiatCurrencyKey = 'current_fiat_currency';
14 static const currentTransactionPriorityKeyLegacy = 'current_fee_priority';
15 static const currentBalanceDisplayModeKey = 'current_balance_display_mode';
lib/entities/priority_for_wallet_type.dart
+4 -1
@@ -17,8 +17,11 @@ List<TransactionPriority> priorityForWalletType(WalletType type) {
17 return haven!.getTransactionPriorities();
18 case WalletType.ethereum:
19 return ethereum!.getTransactionPriorities();
20 + // no such thing for nano/banano:
21 + case WalletType.nano:
22 + case WalletType.banano:
23 + return [];
24 default:
25 return [];
26 }
27 }
24 -
lib/main.dart
+12 -3
@@ -97,6 +97,10 @@ Future<void> initializeAppConfigs() async {
97 CakeHive.registerAdapter(WalletInfoAdapter());
98 }
99
100 + if (!Hive.isAdapterRegistered(DERIVATION_TYPE_TYPE_ID)) {
101 + CakeHive.registerAdapter(DerivationTypeAdapter());
102 + }
103 +
104 if (!CakeHive.isAdapterRegistered(WALLET_TYPE_TYPE_ID)) {
105 CakeHive.registerAdapter(WalletTypeAdapter());
106 }
@@ -128,6 +132,7 @@ Future<void> initializeAppConfigs() async {
132 final ordersBoxKey = await getEncryptionKey(secureStorage: secureStorage, forKey: Order.boxKey);
133 final contacts = await CakeHive.openBox<Contact>(Contact.boxName);
134 final nodes = await CakeHive.openBox<Node>(Node.boxName);
135 + final powNodes = await CakeHive.openBox<Node>(Node.boxName + "pow");// must be different from Node.boxName
136 final transactionDescriptions = await CakeHive.openBox<TransactionDescription>(
137 TransactionDescription.boxName,
138 encryptionKey: transactionDescriptionsBoxKey);
@@ -142,6 +147,7 @@ Future<void> initializeAppConfigs() async {
147 await initialSetup(
148 sharedPreferences: await SharedPreferences.getInstance(),
149 nodes: nodes,
150 + powNodes: powNodes,
151 walletInfoSource: walletInfoSource,
152 contactSource: contacts,
153 tradesSource: trades,
@@ -153,12 +159,13 @@ Future<void> initializeAppConfigs() async {
159 transactionDescriptions: transactionDescriptions,
160 secureStorage: secureStorage,
161 anonpayInvoiceInfo: anonpayInvoiceInfo,
156 - initialMigrationVersion: 21);
157 -}
162 + initialMigrationVersion: 22);
163 + }
164
165 Future<void> initialSetup(
166 {required SharedPreferences sharedPreferences,
167 required Box<Node> nodes,
168 + required Box<Node> powNodes,
169 required Box<WalletInfo> walletInfoSource,
170 required Box<Contact> contactSource,
171 required Box<Trade> tradesSource,
@@ -179,10 +186,12 @@ Future<void> initialSetup(
186 walletInfoSource: walletInfoSource,
187 contactSource: contactSource,
188 tradeSource: tradesSource,
182 - nodes: nodes);
189 + nodes: nodes,
190 + powNodes: powNodes);
191 await setup(
192 walletInfoSource: walletInfoSource,
193 nodeSource: nodes,
194 + powNodeSource: powNodes,
195 contactSource: contactSource,
196 tradesSource: tradesSource,
197 templates: templates,
lib/nano/cw_nano.dart new
+499
@@ -0,0 +1,499 @@
1 +part of 'nano.dart';
2 +
3 +class CWNanoAccountList extends NanoAccountList {
4 + CWNanoAccountList(this._wallet);
5 + final Object _wallet;
6 +
7 + @override
8 + @computed
9 + ObservableList<NanoAccount> get accounts {
10 + final nanoWallet = _wallet as NanoWallet;
11 + final accounts = nanoWallet.walletAddresses.accountList.accounts
12 + .map((acc) => NanoAccount(id: acc.id, label: acc.label, balance: acc.balance))
13 + .toList();
14 + return ObservableList<NanoAccount>.of(accounts);
15 + }
16 +
17 + @override
18 + void update(Object wallet) {
19 + final nanoWallet = wallet as NanoWallet;
20 + nanoWallet.walletAddresses.accountList.update(null);
21 + }
22 +
23 + @override
24 + void refresh(Object wallet) {
25 + final nanoWallet = wallet as NanoWallet;
26 + nanoWallet.walletAddresses.accountList.refresh();
27 + }
28 +
29 + @override
30 + Future<List<NanoAccount>> getAll(Object wallet) async {
31 + final nanoWallet = wallet as NanoWallet;
32 + return (await nanoWallet.walletAddresses.accountList.getAll())
33 + .map((acc) => NanoAccount(id: acc.id, label: acc.label, balance: acc.balance))
34 + .toList();
35 + }
36 +
37 + @override
38 + Future<void> addAccount(Object wallet, {required String label}) async {
39 + final nanoWallet = wallet as NanoWallet;
40 + await nanoWallet.walletAddresses.accountList.addAccount(label: label);
41 + }
42 +
43 + @override
44 + Future<void> setLabelAccount(Object wallet,
45 + {required int accountIndex, required String label}) async {
46 + final nanoWallet = wallet as NanoWallet;
47 + await nanoWallet.walletAddresses.accountList
48 + .setLabelAccount(accountIndex: accountIndex, label: label);
49 + }
50 +}
51 +
52 +class CWNano extends Nano {
53 + @override
54 + NanoAccountList getAccountList(Object wallet) {
55 + return CWNanoAccountList(wallet);
56 + }
57 +
58 + @override
59 + Account getCurrentAccount(Object wallet) {
60 + final nanoWallet = wallet as NanoWallet;
61 + final acc = nanoWallet.walletAddresses.account;
62 + return Account(id: acc!.id, label: acc.label, balance: acc.balance);
63 + }
64 +
65 + @override
66 + void setCurrentAccount(Object wallet, int id, String label, String? balance) {
67 + final nanoWallet = wallet as NanoWallet;
68 + nanoWallet.walletAddresses.account = NanoAccount(id: id, label: label, balance: balance);
69 + nanoWallet.regenerateAddress();
70 + }
71 +
72 + @override
73 + List<String> getNanoWordList(String language) {
74 + return NanoMnemomics.WORDLIST;
75 + }
76 +
77 + @override
78 + WalletService createNanoWalletService(Box<WalletInfo> walletInfoSource) {
79 + return NanoWalletService(walletInfoSource);
80 + }
81 +
82 + @override
83 + Map<String, String> getKeys(Object wallet) {
84 + final nanoWallet = wallet as NanoWallet;
85 + final keys = nanoWallet.keys;
86 + return <String, String>{
87 + "seedKey": keys.seedKey,
88 + };
89 + }
90 +
91 + @override
92 + WalletCredentials createNanoNewWalletCredentials({
93 + required String name,
94 + String? password,
95 + }) =>
96 + NanoNewWalletCredentials(
97 + name: name,
98 + password: password,
99 + );
100 +
101 + @override
102 + WalletCredentials createNanoRestoreWalletFromSeedCredentials({
103 + required String name,
104 + required String password,
105 + required String mnemonic,
106 + DerivationType? derivationType,
107 + }) {
108 + if (derivationType == null) {
109 + // figure out the derivation type as best we can, otherwise set it to "unknown"
110 + if (mnemonic.split(" ").length == 12) {
111 + derivationType = DerivationType.bip39;
112 + } else {
113 + derivationType = DerivationType.unknown;
114 + }
115 + }
116 +
117 + return NanoRestoreWalletFromSeedCredentials(
118 + name: name,
119 + password: password,
120 + mnemonic: mnemonic,
121 + derivationType: derivationType,
122 + );
123 + }
124 +
125 + @override
126 + WalletCredentials createNanoRestoreWalletFromKeysCredentials({
127 + required String name,
128 + required String password,
129 + required String seedKey,
130 + DerivationType? derivationType,
131 + }) {
132 + if (derivationType == null) {
133 + // figure out the derivation type as best we can, otherwise set it to "unknown"
134 + if (seedKey.length == 64) {
135 + derivationType = DerivationType.nano;
136 + } else {
137 + derivationType = DerivationType.unknown;
138 + }
139 + }
140 +
141 + return NanoRestoreWalletFromKeysCredentials(
142 + name: name,
143 + password: password,
144 + seedKey: seedKey,
145 + derivationType: derivationType,
146 + );
147 + }
148 +
149 + @override
150 + Object createNanoTransactionCredentials(List<Output> outputs) {
151 + return NanoTransactionCredentials(
152 + outputs
153 + .map((out) => OutputInfo(
154 + fiatAmount: out.fiatAmount,
155 + cryptoAmount: out.cryptoAmount,
156 + address: out.address,
157 + note: out.note,
158 + sendAll: out.sendAll,
159 + extractedAddress: out.extractedAddress,
160 + isParsedAddress: out.isParsedAddress,
161 + formattedCryptoAmount: out.formattedCryptoAmount,
162 + ))
163 + .toList(),
164 + );
165 + }
166 +
167 + @override
168 + Future<void> changeRep(Object wallet, String address) async {
169 + return (wallet as NanoWallet).changeRep(address);
170 + }
171 +
172 + @override
173 + Future<void> updateTransactions(Object wallet) async {
174 + return (wallet as NanoWallet).updateTransactions();
175 + }
176 +
177 + @override
178 + BigInt getTransactionAmountRaw(TransactionInfo transactionInfo) {
179 + return (transactionInfo as NanoTransactionInfo).amountRaw;
180 + }
181 +}
182 +
183 +class CWNanoUtil extends NanoUtil {
184 + // standard:
185 + @override
186 + String seedToPrivate(String seed, int index) {
187 + return ND.NanoKeys.seedToPrivate(seed, index);
188 + }
189 +
190 + @override
191 + String seedToAddress(String seed, int index) {
192 + return ND.NanoAccounts.createAccount(
193 + ND.NanoAccountType.NANO, privateKeyToPublic(seedToPrivate(seed, index)));
194 + }
195 +
196 + @override
197 + String seedToMnemonic(String seed) {
198 + return NanoMnemomics.seedToMnemonic(seed).join(" ");
199 + }
200 +
201 + @override
202 + Future<String> mnemonicToSeed(String mnemonic) async {
203 + return NanoMnemomics.mnemonicListToSeed(mnemonic.split(' '));
204 + }
205 +
206 + @override
207 + String privateKeyToPublic(String privateKey) {
208 + // return NanoHelpers.byteToHex(Ed25519Blake2b.getPubkey(NanoHelpers.hexToBytes(privateKey))!);
209 + return ND.NanoKeys.createPublicKey(privateKey);
210 + }
211 +
212 + @override
213 + String addressToPublicKey(String publicAddress) {
214 + return ND.NanoAccounts.extractPublicKey(publicAddress);
215 + }
216 +
217 + // universal:
218 + @override
219 + String privateKeyToAddress(String privateKey) {
220 + return ND.NanoAccounts.createAccount(ND.NanoAccountType.NANO, privateKeyToPublic(privateKey));
221 + }
222 +
223 + @override
224 + String publicKeyToAddress(String publicKey) {
225 + return ND.NanoAccounts.createAccount(ND.NanoAccountType.NANO, publicKey);
226 + }
227 +
228 + // standard + hd:
229 + @override
230 + bool isValidSeed(String seed) {
231 + // Ensure seed is 64 or 128 characters long
232 + if (seed.length != 64 && seed.length != 128) {
233 + return false;
234 + }
235 + // Ensure seed only contains hex characters, 0-9;A-F
236 + return ND.NanoHelpers.isHexString(seed);
237 + }
238 +
239 + // hd:
240 + @override
241 + Future<String> hdMnemonicListToSeed(List<String> words) async {
242 + // if (words.length != 24) {
243 + // throw Exception('Expected a 24-word list, got a ${words.length} list');
244 + // }
245 + final Uint8List salt = Uint8List.fromList(utf8.encode('mnemonic'));
246 + final Pbkdf2 hasher = Pbkdf2(iterations: 2048);
247 + final String seed = await hasher.sha512(words.join(' '), salt);
248 + return seed;
249 + }
250 +
251 + @override
252 + Future<String> hdSeedToPrivate(String seed, int index) async {
253 + List<int> seedBytes = hex.decode(seed);
254 + KeyData data = await ED25519_HD_KEY.derivePath("m/44'/165'/$index'", seedBytes);
255 + return hex.encode(data.key);
256 + }
257 +
258 + @override
259 + Future<String> hdSeedToAddress(String seed, int index) async {
260 + return ND.NanoAccounts.createAccount(
261 + ND.NanoAccountType.NANO, privateKeyToPublic(await hdSeedToPrivate(seed, index)));
262 + }
263 +
264 + @override
265 + Future<String> uniSeedToAddress(String seed, int index, String type) {
266 + if (type == "standard") {
267 + return Future<String>.value(seedToAddress(seed, index));
268 + } else if (type == "hd") {
269 + return hdSeedToAddress(seed, index);
270 + } else {
271 + throw Exception('Unknown seed type');
272 + }
273 + }
274 +
275 + @override
276 + Future<String> uniSeedToPrivate(String seed, int index, String type) {
277 + if (type == "standard") {
278 + return Future<String>.value(seedToPrivate(seed, index));
279 + } else if (type == "hd") {
280 + return hdSeedToPrivate(seed, index);
281 + } else {
282 + throw Exception('Unknown seed type');
283 + }
284 + }
285 +
286 + @override
287 + bool isValidBip39Seed(String seed) {
288 + // Ensure seed is 128 characters long
289 + if (seed.length != 128) {
290 + return false;
291 + }
292 + // Ensure seed only contains hex characters, 0-9;A-F
293 + return ND.NanoHelpers.isHexString(seed);
294 + }
295 +
296 + // number util:
297 +
298 + static const int maxDecimalDigits = 6; // Max digits after decimal
299 + BigInt rawPerNano = BigInt.parse("1000000000000000000000000000000");
300 + BigInt rawPerNyano = BigInt.parse("1000000000000000000000000");
301 + BigInt rawPerBanano = BigInt.parse("100000000000000000000000000000");
302 + BigInt rawPerXMR = BigInt.parse("1000000000000");
303 + BigInt convertXMRtoNano = BigInt.parse("1000000000000000000");
304 + // static BigInt convertXMRtoNano = BigInt.parse("1000000000000000000000000000");
305 +
306 + /// Convert raw to ban and return as BigDecimal
307 + ///
308 + /// @param raw 100000000000000000000000000000
309 + /// @return Decimal value 1.000000000000000000000000000000
310 + ///
311 + @override
312 + Decimal getRawAsDecimal(String? raw, BigInt? rawPerCur) {
313 + rawPerCur ??= rawPerNano;
314 + final Decimal amount = Decimal.parse(raw.toString());
315 + final Decimal result = (amount / Decimal.parse(rawPerCur.toString())).toDecimal();
316 + return result;
317 + }
318 +
319 + @override
320 + String truncateDecimal(Decimal input, {int digits = maxDecimalDigits}) {
321 + Decimal bigger = input.shift(digits);
322 + bigger = bigger.floor(); // chop off the decimal: 1.059 -> 1.05
323 + bigger = bigger.shift(-digits);
324 + return bigger.toString();
325 + }
326 +
327 + /// Return raw as a NANO amount.
328 + ///
329 + /// @param raw 100000000000000000000000000000
330 + /// @returns 1
331 + ///
332 + @override
333 + String getRawAsUsableString(String? raw, BigInt rawPerCur) {
334 + final String res =
335 + truncateDecimal(getRawAsDecimal(raw, rawPerCur), digits: maxDecimalDigits + 9);
336 +
337 + if (raw == null || raw == "0" || raw == "00000000000000000000000000000000") {
338 + return "0";
339 + }
340 +
341 + if (!res.contains(".")) {
342 + return res;
343 + }
344 +
345 + final String numAmount = res.split(".")[0];
346 + String decAmount = res.split(".")[1];
347 +
348 + // truncate:
349 + if (decAmount.length > maxDecimalDigits) {
350 + decAmount = decAmount.substring(0, maxDecimalDigits);
351 + // remove trailing zeros:
352 + decAmount = decAmount.replaceAllMapped(RegExp(r'0+$'), (Match match) => '');
353 + if (decAmount.isEmpty) {
354 + return numAmount;
355 + }
356 + }
357 +
358 + return "$numAmount.$decAmount";
359 + }
360 +
361 + @override
362 + String getRawAccuracy(String? raw, BigInt rawPerCur) {
363 + final String rawString = getRawAsUsableString(raw, rawPerCur);
364 + final String rawDecimalString = getRawAsDecimal(raw, rawPerCur).toString();
365 +
366 + if (raw == null || raw.isEmpty || raw == "0") {
367 + return "";
368 + }
369 +
370 + if (rawString != rawDecimalString) {
371 + return "~";
372 + }
373 + return "";
374 + }
375 +
376 + /// Return readable string amount as raw string
377 + /// @param amount 1.01
378 + /// @returns 101000000000000000000000000000
379 + ///
380 + @override
381 + String getAmountAsRaw(String amount, BigInt rawPerCur) {
382 + final Decimal asDecimal = Decimal.parse(amount);
383 + final Decimal rawDecimal = Decimal.parse(rawPerCur.toString());
384 + return (asDecimal * rawDecimal).toString();
385 + }
386 +
387 + @override
388 + Future<AccountInfoResponse?> getInfoFromSeedOrMnemonic(
389 + DerivationType derivationType, {
390 + String? seedKey,
391 + String? mnemonic,
392 + required Node node,
393 + }) async {
394 + NanoClient nanoClient = NanoClient();
395 + nanoClient.connect(node);
396 + late String publicAddress;
397 +
398 + if (seedKey != null) {
399 + if (derivationType == DerivationType.bip39) {
400 + publicAddress = await hdSeedToAddress(seedKey, 0);
401 + } else if (derivationType == DerivationType.nano) {
402 + publicAddress = await seedToAddress(seedKey, 0);
403 + }
404 + }
405 +
406 + if (derivationType == DerivationType.bip39) {
407 + if (mnemonic != null) {
408 + seedKey = await hdMnemonicListToSeed(mnemonic.split(' '));
409 + publicAddress = await hdSeedToAddress(seedKey, 0);
410 + }
411 + }
412 +
413 + if (derivationType == DerivationType.nano) {
414 + if (mnemonic != null) {
415 + seedKey = await mnemonicToSeed(mnemonic);
416 + publicAddress = await seedToAddress(seedKey, 0);
417 + }
418 + }
419 +
420 + AccountInfoResponse? accountInfo = await nanoClient.getAccountInfo(publicAddress);
421 + if (accountInfo == null) {
422 + accountInfo = AccountInfoResponse(frontier: "", balance: "0", representative: "", confirmationHeight: 0);
423 + }
424 + accountInfo.address = publicAddress;
425 + return accountInfo;
426 + }
427 +
428 + @override
429 + Future<List<DerivationType>> compareDerivationMethods({
430 + String? mnemonic,
431 + String? privateKey,
432 + required Node node,
433 + }) async {
434 + String? seedKey = privateKey;
435 +
436 + if (mnemonic?.split(' ').length == 12) {
437 + return [DerivationType.bip39];
438 + }
439 + if (seedKey?.length == 128) {
440 + return [DerivationType.bip39];
441 + } else if (seedKey?.length == 64) {
442 + return [DerivationType.nano];
443 + }
444 +
445 + late String publicAddressStandard;
446 + late String publicAddressBip39;
447 +
448 + try {
449 + NanoClient nanoClient = NanoClient();
450 + nanoClient.connect(node);
451 +
452 + if (mnemonic != null) {
453 + seedKey = await hdMnemonicListToSeed(mnemonic.split(' '));
454 + publicAddressBip39 = await hdSeedToAddress(seedKey, 0);
455 +
456 + seedKey = await mnemonicToSeed(mnemonic);
457 + publicAddressStandard = await seedToAddress(seedKey, 0);
458 + } else if (seedKey != null) {
459 + try {
460 + publicAddressBip39 = await hdSeedToAddress(seedKey, 0);
461 + } catch (e) {
462 + return [DerivationType.nano];
463 + }
464 + try {
465 + publicAddressStandard = await seedToAddress(seedKey, 0);
466 + } catch (e) {
467 + return [DerivationType.bip39];
468 + }
469 + }
470 +
471 + // check if account has a history:
472 + AccountInfoResponse? bip39Info;
473 + AccountInfoResponse? standardInfo;
474 +
475 + try {
476 + bip39Info = await nanoClient.getAccountInfo(publicAddressBip39);
477 + } catch (e) {
478 + bip39Info = null;
479 + }
480 + try {
481 + standardInfo = await nanoClient.getAccountInfo(publicAddressStandard);
482 + } catch (e) {
483 + standardInfo = null;
484 + }
485 +
486 + // one of these is *probably* null:
487 + if (bip39Info == null && standardInfo != null) {
488 + return [DerivationType.nano];
489 + } else if (standardInfo == null && bip39Info != null) {
490 + return [DerivationType.bip39];
491 + }
492 +
493 + // we don't know for sure:
494 + return [DerivationType.nano, DerivationType.bip39];
495 + } catch (e) {
496 + return [DerivationType.unknown];
497 + }
498 + }
499 +}
lib/reactions/on_current_node_change.dart
+7
@@ -13,4 +13,11 @@ void startOnCurrentNodeChangeReaction(AppStore appStore) {
13 print(e.toString());
14 }
15 });
16 + appStore.settingsStore.powNodes.observe((change) async {
17 + try {
18 + await appStore.wallet!.connectToPowNode(node: change.newValue!);
19 + } catch (e) {
20 + print(e.toString());
21 + }
22 + });
23 }
lib/reactions/on_current_wallet_change.dart
+8 -1
@@ -58,16 +58,23 @@ void startCurrentWalletChangeReaction(
58 }
59
60 final node = settingsStore.getCurrentNode(wallet.type);
61 +
62 startWalletSyncStatusChangeReaction(wallet, fiatConversionStore);
63 startCheckConnectionReaction(wallet, settingsStore);
64 await getIt.get<SharedPreferences>().setString(PreferencesKey.currentWalletName, wallet.name);
65 await getIt
66 .get<SharedPreferences>()
67 .setInt(PreferencesKey.currentWalletType, serializeToInt(wallet.type));
68 +
69 if (wallet.type == WalletType.monero) {
70 _setAutoGenerateSubaddressStatus(wallet, settingsStore);
71 }
72 +
73 await wallet.connectToNode(node: node);
74 + if (wallet.type == WalletType.nano || wallet.type == WalletType.banano) {
75 + final powNode = settingsStore.getCurrentPowNode(wallet.type);
76 + await wallet.connectToPowNode(node: powNode);
77 + }
78
79 if (wallet.type == WalletType.haven) {
80 await updateHavenRate(fiatConversionStore);
@@ -101,7 +108,7 @@ void startCurrentWalletChangeReaction(
108
109 if (wallet.type == WalletType.ethereum) {
110 final currencies =
104 - ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
111 + ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
112
113 for (final currency in currencies) {
114 () async {
lib/router.dart
+125 -141
@@ -12,11 +12,15 @@ import 'package:cake_wallet/src/screens/buy/buy_webview_page.dart';
12 import 'package:cake_wallet/src/screens/buy/webview_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/nano/nano_change_rep_page.dart';
16 +import 'package:cake_wallet/src/screens/nano_accounts/nano_account_edit_or_create_page.dart';
17 +import 'package:cake_wallet/src/screens/nodes/pow_node_create_or_edit_page.dart';
18 import 'package:cake_wallet/src/screens/restore/sweeping_wallet_page.dart';
19 import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
20 import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
21 import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_dashboard_actions.dart';
22 import 'package:cake_wallet/src/screens/dashboard/widgets/transactions_page.dart';
23 +import 'package:cake_wallet/src/screens/restore/wallet_restore_choose_derivation.dart';
24 import 'package:cake_wallet/src/screens/settings/desktop_settings/desktop_settings_page.dart';
25 import 'package:cake_wallet/src/screens/settings/display_settings_page.dart';
26 import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
@@ -51,6 +55,8 @@ import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dar
55 import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
56 import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart';
57 import 'package:cake_wallet/wallet_type_utils.dart';
58 +import 'package:cw_core/nano_account.dart';
59 +import 'package:cw_core/wallet_info.dart';
60 import 'package:flutter/cupertino.dart';
61 import 'package:flutter/material.dart';
62 import 'package:cake_wallet/routes.dart';
@@ -111,36 +117,34 @@ Route<dynamic> createRoute(RouteSettings settings) {
117
118 case Routes.newWalletFromWelcome:
119 return CupertinoPageRoute<void>(
114 - builder: (_) => getIt.get<SetupPinCodePage>(
115 - param1: (PinCodeState<PinCodeWidget> context, dynamic _) {
116 - if (availableWalletTypes.length == 1) {
117 - Navigator.of(context.context).pushNamed(Routes.newWallet, arguments: availableWalletTypes.first);
118 - } else {
119 - Navigator.of(context.context).pushNamed(Routes.newWalletType);
120 - }
121 - }),
120 + builder: (_) =>
121 + getIt.get<SetupPinCodePage>(param1: (PinCodeState<PinCodeWidget> context, dynamic _) {
122 + if (availableWalletTypes.length == 1) {
123 + Navigator.of(context.context)
124 + .pushNamed(Routes.newWallet, arguments: availableWalletTypes.first);
125 + } else {
126 + Navigator.of(context.context).pushNamed(Routes.newWalletType);
127 + }
128 + }),
129 fullscreenDialog: true);
130
131 case Routes.newWalletType:
132 return CupertinoPageRoute<void>(
133 builder: (_) => getIt.get<NewWalletTypePage>(
134 param1: (BuildContext context, WalletType type) =>
128 - Navigator.of(context)
129 - .pushNamed(Routes.newWallet, arguments: type)));
135 + Navigator.of(context).pushNamed(Routes.newWallet, arguments: type)));
136
137 case Routes.newWallet:
138 final type = settings.arguments as WalletType;
139 final walletNewVM = getIt.get<WalletNewVM>(param1: type);
140
135 - return CupertinoPageRoute<void>(
136 - builder: (_) => NewWalletPage(walletNewVM));
141 + return CupertinoPageRoute<void>(builder: (_) => NewWalletPage(walletNewVM));
142
143 case Routes.setupPin:
144 Function(PinCodeState<PinCodeWidget>, String)? callback;
145
146 if (settings.arguments is Function(PinCodeState<PinCodeWidget>, String)) {
142 - callback =
143 - settings.arguments as Function(PinCodeState<PinCodeWidget>, String);
147 + callback = settings.arguments as Function(PinCodeState<PinCodeWidget>, String);
148 }
149
150 return CupertinoPageRoute<void>(
@@ -150,8 +154,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
154 return CupertinoPageRoute<void>(
155 builder: (_) => getIt.get<NewWalletTypePage>(
156 param1: (BuildContext context, WalletType type) =>
153 - Navigator.of(context)
154 - .pushNamed(Routes.restoreWallet, arguments: type),
157 + Navigator.of(context).pushNamed(Routes.restoreWallet, arguments: type),
158 param2: false));
159
160 case Routes.restoreOptions:
@@ -166,66 +169,62 @@ Route<dynamic> createRoute(RouteSettings settings) {
169 if (isNewInstall) {
170 return CupertinoPageRoute<void>(
171 builder: (_) => getIt.get<SetupPinCodePage>(
169 - param1: (PinCodeState<PinCodeWidget> context, dynamic _) {
172 + param1: (PinCodeState<PinCodeWidget> context, dynamic _) {
173 if (isSingleCoin) {
174 return Navigator.of(context.context)
175 .pushNamed(Routes.restoreWallet, arguments: availableWalletTypes.first);
176 }
177
175 - return Navigator.pushNamed(
176 - context.context, Routes.restoreWalletType);
178 + return Navigator.pushNamed(context.context, Routes.restoreWalletType);
179 }),
180 fullscreenDialog: true);
181 } else if (isSingleCoin) {
182 return MaterialPageRoute<void>(
181 - builder: (_) => getIt.get<WalletRestorePage>(
182 - param1: availableWalletTypes.first
183 - ));
183 + builder: (_) => getIt.get<WalletRestorePage>(param1: availableWalletTypes.first));
184 } else {
185 return CupertinoPageRoute<void>(
186 builder: (_) => getIt.get<NewWalletTypePage>(
187 param1: (BuildContext context, WalletType type) =>
188 - Navigator.of(context)
189 - .pushNamed(Routes.restoreWallet, arguments: type),
188 + Navigator.of(context).pushNamed(Routes.restoreWallet, arguments: type),
189 param2: false));
190 }
191
192 case Routes.seed:
193 return MaterialPageRoute<void>(
194 fullscreenDialog: true,
196 - builder: (_) =>
197 - getIt.get<WalletSeedPage>(param1: settings.arguments as bool));
195 + builder: (_) => getIt.get<WalletSeedPage>(param1: settings.arguments as bool));
196
197 case Routes.restoreWallet:
198 return MaterialPageRoute<void>(
201 - builder: (_) => getIt.get<WalletRestorePage>(
202 - param1: settings.arguments as WalletType));
199 + builder: (_) => getIt.get<WalletRestorePage>(param1: settings.arguments as WalletType));
200 +
201 + case Routes.restoreWalletChooseDerivation:
202 + return MaterialPageRoute<void>(
203 + builder: (_) => getIt.get<WalletRestoreChooseDerivationPage>(
204 + param1: settings.arguments as List<DerivationInfo>));
205
206 case Routes.sweepingWalletPage:
205 - return CupertinoPageRoute<void>(
206 - builder: (_) => getIt.get<SweepingWalletPage>());
207 + return CupertinoPageRoute<void>(builder: (_) => getIt.get<SweepingWalletPage>());
208
209 case Routes.dashboard:
210 return CupertinoPageRoute<void>(
210 - settings: settings,
211 - builder: (_) => getIt.get<DashboardPage>());
211 + settings: settings, builder: (_) => getIt.get<DashboardPage>());
212
213 case Routes.send:
214 final initialPaymentRequest = settings.arguments as PaymentRequest?;
215
216 return CupertinoPageRoute<void>(
217 - fullscreenDialog: true, builder: (_) => getIt.get<SendPage>(
218 - param1: initialPaymentRequest,
219 - ));
217 + fullscreenDialog: true,
218 + builder: (_) => getIt.get<SendPage>(
219 + param1: initialPaymentRequest,
220 + ));
221
222 case Routes.sendTemplate:
223 return CupertinoPageRoute<void>(
223 - fullscreenDialog: true,
224 - builder: (_) => getIt.get<SendTemplatePage>());
224 + fullscreenDialog: true, builder: (_) => getIt.get<SendTemplatePage>());
225
226 case Routes.receive:
227 - return CupertinoPageRoute<void>(
228 - builder: (_) => getIt.get<ReceivePage>());
227 + return CupertinoPageRoute<void>(builder: (_) => getIt.get<ReceivePage>());
228
229 case Routes.addressPage:
230 return CupertinoPageRoute<void>(
@@ -234,20 +233,21 @@ Route<dynamic> createRoute(RouteSettings settings) {
233 case Routes.transactionDetails:
234 return CupertinoPageRoute<void>(
235 fullscreenDialog: true,
237 - builder: (_) => getIt.get<TransactionDetailsPage>(
238 - param1: settings.arguments as TransactionInfo));
236 + builder: (_) =>
237 + getIt.get<TransactionDetailsPage>(param1: settings.arguments as TransactionInfo));
238
239 case Routes.newSubaddress:
240 return CupertinoPageRoute<void>(
242 - builder: (_) =>
243 - getIt.get<AddressEditOrCreatePage>(param1: settings.arguments));
241 + builder: (_) => getIt.get<AddressEditOrCreatePage>(param1: settings.arguments));
242
243 case Routes.disclaimer:
244 return CupertinoPageRoute<void>(builder: (_) => DisclaimerPage());
245
246 case Routes.readDisclaimer:
249 - return CupertinoPageRoute<void>(
250 - builder: (_) => DisclaimerPage(isReadOnly: true));
247 + return CupertinoPageRoute<void>(builder: (_) => DisclaimerPage(isReadOnly: true));
248 +
249 + case Routes.changeRep:
250 + return CupertinoPageRoute<void>(builder: (_) => getIt.get<NanoChangeRepPage>());
251
252 case Routes.seedLanguage:
253 final args = settings.arguments as List<dynamic>;
@@ -256,8 +256,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
256
257 return CupertinoPageRoute<void>(builder: (_) {
258 return SeedLanguage(
259 - onConfirm: (context, lang) => Navigator.of(context)
260 - .popAndPushNamed(redirectRoute, arguments: [type, lang]));
259 + onConfirm: (context, lang) =>
260 + Navigator.of(context).popAndPushNamed(redirectRoute, arguments: [type, lang]));
261 });
262
263 case Routes.walletList:
@@ -267,15 +267,13 @@ Route<dynamic> createRoute(RouteSettings settings) {
267 case Routes.walletEdit:
268 return MaterialPageRoute<void>(
269 fullscreenDialog: true,
270 - builder: (_) => getIt.get<WalletEditPage>(
271 - param1: settings.arguments as List<dynamic>));
270 + builder: (_) => getIt.get<WalletEditPage>(param1: settings.arguments as List<dynamic>));
271
272 case Routes.auth:
273 return MaterialPageRoute<void>(
274 fullscreenDialog: true,
275 builder: (_) => getIt.get<AuthPage>(
277 - param1: settings.arguments as OnAuthenticationFinished,
278 - param2: true));
276 + param1: settings.arguments as OnAuthenticationFinished, param2: true));
277
278 case Routes.totpAuthCodePage:
279 final args = settings.arguments as TotpAuthArgumentsModel;
@@ -290,9 +288,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
288 return CupertinoPageRoute<void>(
289 builder: (context) => WillPopScope(
290 child: getIt.get<AuthPage>(instanceName: 'login'),
293 - onWillPop: () async =>
294 - // FIX-ME: Additional check does it works correctly
295 - (await SystemChannels.platform.invokeMethod<bool>('SystemNavigator.pop') ??
291 + onWillPop: () async =>
292 + // FIX-ME: Additional check does it works correctly
293 + (await SystemChannels.platform.invokeMethod<bool>('SystemNavigator.pop') ??
294 false),
295 ),
296 fullscreenDialog: true);
@@ -302,53 +300,54 @@ Route<dynamic> createRoute(RouteSettings settings) {
300 fullscreenDialog: true,
301 builder: (_) => WillPopScope(
302 child: getIt.get<AuthPage>(
305 - param1: settings.arguments as OnAuthenticationFinished,
306 - param2: false),
303 + param1: settings.arguments as OnAuthenticationFinished, param2: false),
304 onWillPop: () async => false));
305
306 case Routes.connectionSync:
307 return CupertinoPageRoute<void>(
311 - fullscreenDialog: true,
312 - builder: (_) => getIt.get<ConnectionSyncPage>());
308 + fullscreenDialog: true, builder: (_) => getIt.get<ConnectionSyncPage>());
309
310 case Routes.securityBackupPage:
311 return CupertinoPageRoute<void>(
316 - fullscreenDialog: true,
317 - builder: (_) => getIt.get<SecurityBackupPage>());
312 + fullscreenDialog: true, builder: (_) => getIt.get<SecurityBackupPage>());
313
319 - case Routes.privacyPage:
314 + case Routes.privacyPage:
315 return CupertinoPageRoute<void>(
321 - fullscreenDialog: true,
322 - builder: (_) => getIt.get<PrivacyPage>());
316 + fullscreenDialog: true, builder: (_) => getIt.get<PrivacyPage>());
317
324 - case Routes.displaySettingsPage:
318 + case Routes.displaySettingsPage:
319 return CupertinoPageRoute<void>(
326 - fullscreenDialog: true,
327 - builder: (_) => getIt.get<DisplaySettingsPage>());
320 + fullscreenDialog: true, builder: (_) => getIt.get<DisplaySettingsPage>());
321
322 case Routes.otherSettingsPage:
323 return CupertinoPageRoute<void>(
331 - fullscreenDialog: true,
332 - builder: (_) => getIt.get<OtherSettingsPage>());
324 + fullscreenDialog: true, builder: (_) => getIt.get<OtherSettingsPage>());
325
326 case Routes.newNode:
327 final args = settings.arguments as Map<String, dynamic>?;
328 return CupertinoPageRoute<void>(
329 builder: (_) => getIt.get<NodeCreateOrEditPage>(
338 - param1: args?['editingNode'] as Node?,
339 - param2: args?['isSelected'] as bool?));
340 -
330 + param1: args?['editingNode'] as Node?, param2: args?['isSelected'] as bool?));
331
332 + case Routes.newPowNode:
333 + final args = settings.arguments as Map<String, dynamic>?;
334 + return CupertinoPageRoute<void>(
335 + builder: (_) => getIt.get<PowNodeCreateOrEditPage>(
336 + param1: args?['editingNode'] as Node?, param2: args?['isSelected'] as bool?));
337
338 case Routes.accountCreation:
339 return CupertinoPageRoute<String>(
340 builder: (_) => getIt.get<MoneroAccountEditOrCreatePage>(
341 param1: settings.arguments as AccountListItem?));
342
343 + case Routes.nanoAccountCreation:
344 + return CupertinoPageRoute<String>(
345 + builder: (_) =>
346 + getIt.get<NanoAccountEditOrCreatePage>(param1: settings.arguments as NanoAccount?));
347 +
348 case Routes.addressBook:
349 return MaterialPageRoute<void>(
350 - fullscreenDialog: true,
351 - builder: (_) => getIt.get<ContactListPage>());
350 + fullscreenDialog: true, builder: (_) => getIt.get<ContactListPage>());
351
352 case Routes.pickerAddressBook:
353 final selectedCurrency = settings.arguments as CryptoCurrency?;
@@ -357,31 +356,26 @@ Route<dynamic> createRoute(RouteSettings settings) {
356
357 case Routes.addressBookAddContact:
358 return CupertinoPageRoute<void>(
360 - builder: (_) => getIt.get<ContactPage>(
361 - param1: settings.arguments as ContactRecord?));
359 + builder: (_) => getIt.get<ContactPage>(param1: settings.arguments as ContactRecord?));
360
361 case Routes.showKeys:
362 return MaterialPageRoute<void>(
363 builder: (_) => getIt.get<WalletKeysPage>(), fullscreenDialog: true);
364
365 case Routes.exchangeTrade:
368 - return CupertinoPageRoute<void>(
369 - builder: (_) => getIt.get<ExchangeTradePage>());
366 + return CupertinoPageRoute<void>(builder: (_) => getIt.get<ExchangeTradePage>());
367
368 case Routes.exchangeConfirm:
372 - return MaterialPageRoute<void>(
373 - builder: (_) => getIt.get<ExchangeConfirmPage>());
369 + return MaterialPageRoute<void>(builder: (_) => getIt.get<ExchangeConfirmPage>());
370
371 case Routes.tradeDetails:
372 return MaterialPageRoute<void>(
373 fullscreenDialog: true,
378 - builder: (_) =>
379 - getIt.get<TradeDetailsPage>(param1: settings.arguments as Trade));
374 + builder: (_) => getIt.get<TradeDetailsPage>(param1: settings.arguments as Trade));
375
376 case Routes.orderDetails:
377 return MaterialPageRoute<void>(
383 - builder: (_) =>
384 - getIt.get<OrderDetailsPage>(param1: settings.arguments as Order));
378 + builder: (_) => getIt.get<OrderDetailsPage>(param1: settings.arguments as Order));
379
380 case Routes.buy:
381 return MaterialPageRoute<void>(builder: (_) => getIt.get<BuyOptionsPage>());
@@ -390,18 +384,14 @@ Route<dynamic> createRoute(RouteSettings settings) {
384 final args = settings.arguments as List;
385
386 return MaterialPageRoute<void>(
393 - fullscreenDialog: true,
394 - builder: (_) =>
395 - getIt.get<BuyWebViewPage>(param1: args));
387 + fullscreenDialog: true, builder: (_) => getIt.get<BuyWebViewPage>(param1: args));
388
389 case Routes.exchange:
390 return CupertinoPageRoute<void>(
399 - fullscreenDialog: true,
400 - builder: (_) => getIt.get<ExchangePage>());
391 + fullscreenDialog: true, builder: (_) => getIt.get<ExchangePage>());
392
393 case Routes.exchangeTemplate:
403 - return CupertinoPageRoute<void>(
404 - builder: (_) => getIt.get<ExchangeTemplatePage>());
394 + return CupertinoPageRoute<void>(builder: (_) => getIt.get<ExchangeTemplatePage>());
395
396 case Routes.rescan:
397 return MaterialPageRoute<void>(builder: (_) => getIt.get<RescanPage>());
@@ -411,51 +401,41 @@ Route<dynamic> createRoute(RouteSettings settings) {
401
402 case Routes.preSeed:
403 return MaterialPageRoute<void>(
414 - builder: (_) =>
415 - getIt.get<PreSeedPage>(param1: settings.arguments as WalletType));
404 + builder: (_) => getIt.get<PreSeedPage>(param1: settings.arguments as WalletType));
405
406 case Routes.backup:
407 return CupertinoPageRoute<void>(
408 fullscreenDialog: true, builder: (_) => getIt.get<BackupPage>());
409
410 case Routes.editBackupPassword:
422 - return CupertinoPageRoute<void>(
423 - builder: (_) => getIt.get<EditBackupPasswordPage>());
411 + return CupertinoPageRoute<void>(builder: (_) => getIt.get<EditBackupPasswordPage>());
412
413 case Routes.restoreFromBackup:
414 return CupertinoPageRoute<void>(
427 - fullscreenDialog: true,
428 - builder: (_) => getIt.get<RestoreFromBackupPage>());
415 + fullscreenDialog: true, builder: (_) => getIt.get<RestoreFromBackupPage>());
416
417 case Routes.support:
431 - return CupertinoPageRoute<void>(
432 - builder: (_) => getIt.get<SupportPage>());
418 + return CupertinoPageRoute<void>(builder: (_) => getIt.get<SupportPage>());
419
420 case Routes.supportLiveChat:
435 - return CupertinoPageRoute<void>(
436 - builder: (_) => getIt.get<SupportChatPage>());
421 + return CupertinoPageRoute<void>(builder: (_) => getIt.get<SupportChatPage>());
422
423 case Routes.supportOtherLinks:
424 return CupertinoPageRoute<void>(
440 - fullscreenDialog: true,
441 - builder: (_) => getIt.get<SupportOtherLinksPage>());
425 + fullscreenDialog: true, builder: (_) => getIt.get<SupportOtherLinksPage>());
426
427 case Routes.unspentCoinsList:
444 - return MaterialPageRoute<void>(
445 - builder: (_) => getIt.get<UnspentCoinsListPage>());
428 + return MaterialPageRoute<void>(builder: (_) => getIt.get<UnspentCoinsListPage>());
429
430 case Routes.unspentCoinsDetails:
431 final args = settings.arguments as List;
432
433 return MaterialPageRoute<void>(
451 - builder: (_) =>
452 - getIt.get<UnspentCoinsDetailsPage>(
453 - param1: args));
434 + builder: (_) => getIt.get<UnspentCoinsDetailsPage>(param1: args));
435
436 case Routes.fullscreenQR:
437 return MaterialPageRoute<void>(
457 - builder: (_) =>
458 - getIt.get<FullscreenQRPage>(
438 + builder: (_) => getIt.get<FullscreenQRPage>(
439 param1: settings.arguments as QrViewData,
440 ));
441
@@ -466,26 +446,27 @@ Route<dynamic> createRoute(RouteSettings settings) {
446 );
447
448 case Routes.ioniaLoginPage:
469 - return CupertinoPageRoute<void>( builder: (_) => getIt.get<IoniaLoginPage>());
449 + return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaLoginPage>());
450
451 case Routes.ioniaCreateAccountPage:
472 - return CupertinoPageRoute<void>( builder: (_) => getIt.get<IoniaCreateAccountPage>());
452 + return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaCreateAccountPage>());
453
454 case Routes.ioniaManageCardsPage:
475 - return CupertinoPageRoute<void>(
476 - builder: (_) => getIt.get<IoniaManageCardsPage>());
455 + return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaManageCardsPage>());
456
457 case Routes.ioniaBuyGiftCardPage:
458 final args = settings.arguments as List;
480 - return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaBuyGiftCardPage>(param1: args));
459 + return CupertinoPageRoute<void>(
460 + builder: (_) => getIt.get<IoniaBuyGiftCardPage>(param1: args));
461
462 case Routes.ioniaBuyGiftCardDetailPage:
463 final args = settings.arguments as List;
484 - return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaBuyGiftCardDetailPage>(param1: args));
464 + return CupertinoPageRoute<void>(
465 + builder: (_) => getIt.get<IoniaBuyGiftCardDetailPage>(param1: args));
466
467 case Routes.ioniaVerifyIoniaOtpPage:
468 final args = settings.arguments as List;
488 - return CupertinoPageRoute<void>(builder: (_) =>getIt.get<IoniaVerifyIoniaOtp>(param1: args));
469 + return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaVerifyIoniaOtp>(param1: args));
470
471 case Routes.ioniaDebitCardPage:
472 return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaDebitCardPage>());
@@ -501,57 +482,60 @@ Route<dynamic> createRoute(RouteSettings settings) {
482
483 case Routes.ioniaCustomTipPage:
484 final args = settings.arguments as List;
504 - return CupertinoPageRoute<void>(builder: (_) =>getIt.get<IoniaCustomTipPage>(param1: args));
485 + return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaCustomTipPage>(param1: args));
486
487 case Routes.ioniaGiftCardDetailPage:
488 final args = settings.arguments as List;
508 - return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaGiftCardDetailPage>(param1: args.first));
489 + return CupertinoPageRoute<void>(
490 + builder: (_) => getIt.get<IoniaGiftCardDetailPage>(param1: args.first));
491
492 case Routes.ioniaCustomRedeemPage:
493 final args = settings.arguments as List;
512 - return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaCustomRedeemPage>(param1: args));
494 + return CupertinoPageRoute<void>(
495 + builder: (_) => getIt.get<IoniaCustomRedeemPage>(param1: args));
496
497 case Routes.ioniaMoreOptionsPage:
498 final args = settings.arguments as List;
516 - return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaMoreOptionsPage>(param1: args));
499 + return CupertinoPageRoute<void>(
500 + builder: (_) => getIt.get<IoniaMoreOptionsPage>(param1: args));
501
502 case Routes.ioniaPaymentStatusPage:
503 final args = settings.arguments as List;
504 final paymentInfo = args.first as IoniaAnyPayPaymentInfo;
505 final commitedInfo = args[1] as AnyPayPaymentCommittedInfo;
522 - return CupertinoPageRoute<void>(builder: (_) => getIt.get<IoniaPaymentStatusPage>(
523 - param1: paymentInfo,
524 - param2: commitedInfo));
506 + return CupertinoPageRoute<void>(
507 + builder: (_) =>
508 + getIt.get<IoniaPaymentStatusPage>(param1: paymentInfo, param2: commitedInfo));
509
510 case Routes.webViewPage:
511 final args = settings.arguments as List;
512 final title = args.first as String;
513 final url = args[1] as Uri;
530 - return CupertinoPageRoute<void>(builder: (_) => getIt.get<WebViewPage>(
531 - param1: title,
532 - param2: url));
514 + return CupertinoPageRoute<void>(
515 + builder: (_) => getIt.get<WebViewPage>(param1: title, param2: url));
516
517 case Routes.advancedPrivacySettings:
518 final type = settings.arguments as WalletType;
519
520 return CupertinoPageRoute<void>(
521 builder: (_) => AdvancedPrivacySettingsPage(
539 - getIt.get<AdvancedPrivacySettingsViewModel>(param1: type),
540 - getIt.get<NodeCreateOrEditViewModel>(param1: type),
541 - ));
522 + getIt.get<AdvancedPrivacySettingsViewModel>(param1: type),
523 + getIt.get<NodeCreateOrEditViewModel>(param1: type, param2: false),
524 + ));
525
526 case Routes.anonPayInvoicePage:
527 final args = settings.arguments as List;
545 - return CupertinoPageRoute<void>(
546 - builder: (_) => getIt.get<AnonPayInvoicePage>(param1: args));
528 + return CupertinoPageRoute<void>(builder: (_) => getIt.get<AnonPayInvoicePage>(param1: args));
529
530 case Routes.anonPayReceivePage:
549 - final anonInvoiceViewData = settings.arguments as AnonpayInfoBase;
550 - return CupertinoPageRoute<void>(builder: (_) => getIt.get<AnonPayReceivePage>(param1: anonInvoiceViewData));
531 + final anonInvoiceViewData = settings.arguments as AnonpayInfoBase;
532 + return CupertinoPageRoute<void>(
533 + builder: (_) => getIt.get<AnonPayReceivePage>(param1: anonInvoiceViewData));
534
535 case Routes.anonPayDetailsPage:
536 final anonInvoiceViewData = settings.arguments as AnonpayInvoiceInfo;
554 - return CupertinoPageRoute<void>(builder: (_) => getIt.get<AnonpayDetailsPage>(param1: anonInvoiceViewData));
537 + return CupertinoPageRoute<void>(
538 + builder: (_) => getIt.get<AnonpayDetailsPage>(param1: anonInvoiceViewData));
539
540 case Routes.desktop_actions:
541 return PageRouteBuilder(
@@ -560,12 +544,10 @@ Route<dynamic> createRoute(RouteSettings settings) {
544 );
545
546 case Routes.desktop_settings_page:
563 - return CupertinoPageRoute<void>(
564 - builder: (_) => DesktopSettingsPage());
547 + return CupertinoPageRoute<void>(builder: (_) => DesktopSettingsPage());
548
549 case Routes.empty_no_route:
567 - return MaterialPageRoute<void>(
568 - builder: (_) => SizedBox.shrink());
550 + return MaterialPageRoute<void>(builder: (_) => SizedBox.shrink());
551
552 case Routes.transactionsPage:
553 return CupertinoPageRoute<void>(
@@ -602,12 +584,14 @@ Route<dynamic> createRoute(RouteSettings settings) {
584 );
585
586 case Routes.manageNodes:
605 - return MaterialPageRoute<void>(builder: (_) => getIt.get<ManageNodesPage>());
587 + return MaterialPageRoute<void>(builder: (_) => getIt.get<ManageNodesPage>(param1: false));
588 +
589 + case Routes.managePowNodes:
590 + return MaterialPageRoute<void>(builder: (_) => getIt.get<ManageNodesPage>(param1: true));
591
592 default:
593 return MaterialPageRoute<void>(
594 builder: (_) => Scaffold(
610 - body: Center(
611 - child: Text(S.current.router_no_route(settings.name ?? 'No route')))));
595 + body: Center(child: Text(S.current.router_no_route(settings.name ?? 'No route')))));
596 }
597 }
lib/routes.dart
+6
@@ -6,6 +6,7 @@ class Routes {
6 static const seed = '/seed';
7 static const restoreOptions = '/restore_options';
8 static const restoreWalletFromSeedKeys = '/restore_wallet_from_seeds_keys';
9 + static const restoreWalletChooseDerivation = '/restore_wallet_choose_derivation';
10 static const dashboard = '/dashboard';
11 static const send = '/send';
12 static const transactionDetails = '/transaction_info';
@@ -14,13 +15,16 @@ class Routes {
15 static const walletEdit = '/walletEdit';
16 static const disclaimer = '/disclaimer';
17 static const readDisclaimer = '/read_disclaimer';
18 + static const changeRep = '/change_representative';
19 static const seedLanguage = '/seed_language';
20 static const walletList = '/view_model.wallet_list';
21 static const auth = '/auth';
22 static const newNode = '/new_node_list';
23 + static const newPowNode = '/new_pow_node_list';
24 static const login = '/login';
25 static const splash = '/splash';
26 static const accountCreation = '/account_new';
27 + static const nanoAccountCreation = '/nano_account_new';
28 static const addressBook = '/address_book';
29 static const pickerAddressBook = '/picker_address_book';
30 static const addressBookAddContact = '/address_book_add_contact';
@@ -92,4 +96,6 @@ class Routes {
96 static const homeSettings = '/home_settings';
97 static const editToken = '/edit_token';
98 static const manageNodes = '/manage_nodes';
99 + static const managePowNodes = '/manage_pow_nodes';
100 +
101 }
lib/src/screens/dashboard/dashboard_page.dart
-23
@@ -295,29 +295,6 @@ class _DashboardPageView extends BasePage {
295 );
296 _isEffectsInstalled = true;
297
298 - autorun(
299 - (_) async {
300 - if (!dashboardViewModel.isOutdatedElectrumWallet) {
301 - return;
302 - }
303 -
304 - await Future<void>.delayed(Duration(seconds: 1));
305 - if (context.mounted) {
306 - await showPopUp<void>(
307 - context: context,
308 - builder: (BuildContext context) {
309 - return AlertWithOneAction(
310 - alertTitle: S.of(context).pre_seed_title,
311 - alertContent: S.of(context).outdated_electrum_wallet_description,
312 - buttonText: S.of(context).understand,
313 - buttonAction: () => Navigator.of(context).pop(),
314 - );
315 - },
316 - );
317 - }
318 - },
319 - );
320 -
298 _showReleaseNotesPopup(context);
299
300 var needToPresentYat = false;
lib/src/screens/dashboard/desktop_dashboard_page.dart
-17
@@ -78,23 +78,6 @@ class DesktopDashboardPage extends StatelessWidget {
78 }
79 _isEffectsInstalled = true;
80
81 - autorun((_) async {
82 - if (!dashboardViewModel.isOutdatedElectrumWallet) {
83 - return;
84 - }
85 -
86 - await Future<void>.delayed(Duration(seconds: 1));
87 - await showPopUp<void>(
88 - context: context,
89 - builder: (BuildContext context) {
90 - return AlertWithOneAction(
91 - alertTitle: S.of(context).pre_seed_title,
92 - alertContent: S.of(context).outdated_electrum_wallet_description,
93 - buttonText: S.of(context).understand,
94 - buttonAction: () => Navigator.of(context).pop());
95 - });
96 - });
97 -
81 var needToPresentYat = false;
82 var isInactive = false;
83
lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart
+6
@@ -33,6 +33,8 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
33 final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24);
34 final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24);
35 final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24);
36 + final nanoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24);
37 + final bananoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24);
38 final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
39
40 Image _newWalletImage(BuildContext context) => Image.asset(
@@ -141,6 +143,10 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
143 return havenIcon;
144 case WalletType.ethereum:
145 return ethereumIcon;
146 + case WalletType.nano:
147 + return nanoIcon;
148 + case WalletType.banano:
149 + return bananoIcon;
150 default:
151 return nonWalletTypeIcon;
152 }
lib/src/screens/dashboard/widgets/address_page.dart
-25
@@ -120,31 +120,6 @@ class AddressPage extends BasePage {
120 Widget body(BuildContext context) {
121 _setEffects(context);
122
123 - autorun((_) async {
124 - if (!dashboardViewModel.isOutdatedElectrumWallet ||
125 - !dashboardViewModel.settingsStore.shouldShowReceiveWarning) {
126 - return;
127 - }
128 -
129 - await Future<void>.delayed(Duration(seconds: 1));
130 - if (context.mounted) {
131 - await showPopUp<void>(
132 - context: context,
133 - builder: (BuildContext context) {
134 - return AlertWithTwoActions(
135 - alertTitle: S.of(context).pre_seed_title,
136 - alertContent: S.of(context).outdated_electrum_wallet_receive_warning,
137 - leftButtonText: S.of(context).understand,
138 - actionLeftButton: () => Navigator.of(context).pop(),
139 - rightButtonText: S.of(context).do_not_show_me,
140 - actionRightButton: () {
141 - dashboardViewModel.settingsStore.setShouldShowReceiveWarning(false);
142 - Navigator.of(context).pop();
143 - });
144 - });
145 - }
146 - });
147 -
123 return KeyboardActions(
124 autoScroll: false,
125 disableScroll: true,
lib/src/screens/dashboard/widgets/menu_widget.dart
+22 -12
@@ -18,18 +18,21 @@ class MenuWidget extends StatefulWidget {
18
19 class MenuWidgetState extends State<MenuWidget> {
20 MenuWidgetState()
21 - : this.menuWidth = 0,
22 - this.screenWidth = 0,
23 - this.screenHeight = 0,
24 - this.headerHeight = 120,
25 - this.tileHeight = 60,
26 - this.fromTopEdge = 50,
27 - this.fromBottomEdge = 25,
28 - this.moneroIcon = Image.asset('assets/images/monero_menu.png'),
29 - this.bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png'),
30 - this.litecoinIcon = Image.asset('assets/images/litecoin_menu.png'),
31 - this.havenIcon = Image.asset('assets/images/haven_menu.png'),
32 - this.ethereumIcon = Image.asset('assets/images/eth_icon.png');
21 + : this.menuWidth = 0,
22 + this.screenWidth = 0,
23 + this.screenHeight = 0,
24 + this.headerHeight = 120,
25 + this.tileHeight = 60,
26 + this.fromTopEdge = 50,
27 + this.fromBottomEdge = 25,
28 + this.moneroIcon = Image.asset('assets/images/monero_menu.png'),
29 + this.bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png'),
30 + this.litecoinIcon = Image.asset('assets/images/litecoin_menu.png'),
31 + this.havenIcon = Image.asset('assets/images/haven_menu.png'),
32 + this.ethereumIcon = Image.asset('assets/images/eth_icon.png'),
33 + this.nanoIcon = Image.asset('assets/images/nano_icon.png'),
34 + this.bananoIcon = Image.asset('assets/images/nano_icon.png');
35 +
36
37 final largeScreen = 731;
38
@@ -47,6 +50,9 @@ class MenuWidgetState extends State<MenuWidget> {
50 Image litecoinIcon;
51 Image havenIcon;
52 Image ethereumIcon;
53 + Image nanoIcon;
54 + Image bananoIcon;
55 +
56
57 @override
58 void initState() {
@@ -206,6 +212,10 @@ class MenuWidgetState extends State<MenuWidget> {
212 return havenIcon;
213 case WalletType.ethereum:
214 return ethereumIcon;
215 + case WalletType.nano:
216 + return nanoIcon;
217 + case WalletType.banano:
218 + return bananoIcon;
219 default:
220 throw Exception('No icon for ${type.toString()}');
221 }
lib/src/screens/nano/nano_change_rep_page.dart new
+103
@@ -0,0 +1,103 @@
1 +import 'package:cake_wallet/core/address_validator.dart';
2 +import 'package:cake_wallet/nano/nano.dart';
3 +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
4 +import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
5 +import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
6 +import 'package:cake_wallet/utils/show_pop_up.dart';
7 +import 'package:cw_core/crypto_currency.dart';
8 +import 'package:cw_core/wallet_base.dart';
9 +import 'package:cw_nano/nano_wallet.dart';
10 +import 'package:flutter/material.dart';
11 +import 'package:flutter_mobx/flutter_mobx.dart';
12 +import 'package:cake_wallet/generated/i18n.dart';
13 +import 'package:cake_wallet/src/widgets/primary_button.dart';
14 +import 'package:cake_wallet/src/screens/base_page.dart';
15 +import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
16 +
17 +class NanoChangeRepPage extends BasePage {
18 + NanoChangeRepPage(WalletBase wallet)
19 + : _wallet = wallet,
20 + _addressController = TextEditingController() {
21 + _addressController.text = (wallet as NanoWallet).representative;
22 + }
23 +
24 + final TextEditingController _addressController;
25 + final WalletBase _wallet;
26 +
27 + @override
28 + String get title => S.current.change_rep;
29 +
30 + @override
31 + Widget body(BuildContext context) {
32 + return Container(
33 + padding: EdgeInsets.only(left: 24, right: 24),
34 + child: ScrollableWithBottomSection(
35 + contentPadding: EdgeInsets.only(bottom: 24.0),
36 + content: Container(
37 + child: Column(
38 + children: <Widget>[
39 + Row(
40 + children: <Widget>[
41 + Expanded(
42 + child: BaseTextFormField(
43 + controller: _addressController,
44 + hintText: S.of(context).node_address,
45 + validator: AddressValidator(type: CryptoCurrency.nano),
46 + ),
47 + )
48 + ],
49 + ),
50 + ],
51 + ),
52 + ),
53 + bottomSectionPadding: EdgeInsets.only(bottom: 24),
54 + bottomSection: Observer(
55 + builder: (_) => Row(
56 + mainAxisAlignment: MainAxisAlignment.center,
57 + children: <Widget>[
58 + Flexible(
59 + child: Container(
60 + padding: EdgeInsets.only(right: 8.0),
61 + child: LoadingPrimaryButton(
62 + onPressed: () async {
63 + final confirmed = await showPopUp<bool>(
64 + context: context,
65 + builder: (BuildContext context) {
66 + return AlertWithTwoActions(
67 + alertTitle: S.of(context).change_rep,
68 + alertContent: S.of(context).change_rep_message,
69 + rightButtonText: S.of(context).change,
70 + leftButtonText: S.of(context).cancel,
71 + actionRightButton: () => Navigator.pop(context, true),
72 + actionLeftButton: () => Navigator.pop(context, false));
73 + }) ??
74 + false;
75 +
76 + if (confirmed) {
77 + try {
78 + await nano!.changeRep(_wallet, _addressController.text);
79 + Navigator.of(context).pop();
80 + } catch (e) {
81 + await showPopUp<void>(
82 + context: context,
83 + builder: (BuildContext context) {
84 + return AlertWithOneAction(
85 + alertTitle: S.of(context).error,
86 + alertContent: e.toString(),
87 + buttonText: S.of(context).ok,
88 + buttonAction: () => Navigator.pop(context));
89 + });
90 + throw e;
91 + }
92 + }
93 + },
94 + text: S.of(context).change,
95 + color: Theme.of(context).primaryColor,
96 + textColor: Colors.white,
97 + ),
98 + )),
99 + ],
100 + )),
101 + ));
102 + }
103 +}
lib/src/screens/nano_accounts/nano_account_edit_or_create_page.dart new
+66
@@ -0,0 +1,66 @@
1 +import 'package:cake_wallet/core/execution_state.dart';
2 +import 'package:cake_wallet/view_model/nano_account_list/nano_account_edit_or_create_view_model.dart';
3 +import 'package:flutter/material.dart';
4 +import 'package:flutter/cupertino.dart';
5 +import 'package:flutter_mobx/flutter_mobx.dart';
6 +import 'package:cake_wallet/generated/i18n.dart';
7 +import 'package:cake_wallet/core/monero_account_label_validator.dart';
8 +import 'package:cake_wallet/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart';
9 +import 'package:cake_wallet/src/widgets/primary_button.dart';
10 +import 'package:cake_wallet/src/screens/base_page.dart';
11 +import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
12 +
13 +class NanoAccountEditOrCreatePage extends BasePage {
14 + NanoAccountEditOrCreatePage({required this.nanoAccountCreationViewModel})
15 + : _formKey = GlobalKey<FormState>(),
16 + _textController = TextEditingController() {
17 + _textController.addListener(() => nanoAccountCreationViewModel.label = _textController.text);
18 + _textController.text = nanoAccountCreationViewModel.label;
19 + }
20 +
21 + final NanoAccountEditOrCreateViewModel nanoAccountCreationViewModel;
22 +
23 + @override
24 + String get title => S.current.account;
25 +
26 + final GlobalKey<FormState> _formKey;
27 + final TextEditingController _textController;
28 +
29 + @override
30 + Widget body(BuildContext context) => Form(
31 + key: _formKey,
32 + child: Container(
33 + padding: EdgeInsets.all(24.0),
34 + child: Column(
35 + children: <Widget>[
36 + Expanded(
37 + child: Center(
38 + child: BaseTextFormField(
39 + controller: _textController,
40 + hintText: S.of(context).account,
41 + validator: MoneroLabelValidator(),
42 + ))),
43 + Observer(
44 + builder: (_) => LoadingPrimaryButton(
45 + onPressed: () async {
46 + if (_formKey.currentState != null && !_formKey.currentState!.validate()) {
47 + return;
48 + }
49 +
50 + await nanoAccountCreationViewModel.save();
51 +
52 + Navigator.of(context).pop(_textController.text);
53 + },
54 + text: nanoAccountCreationViewModel.isEdit
55 + ? S.of(context).rename
56 + : S.of(context).add,
57 + color: Theme.of(context).primaryColor,
58 + textColor: Colors.white,
59 + isLoading: nanoAccountCreationViewModel.state is IsExecutingState,
60 + isDisabled: nanoAccountCreationViewModel.label?.isEmpty ?? true,
61 + ))
62 + ],
63 + ),
64 + ),
65 + );
66 +}
lib/src/screens/nano_accounts/nano_account_list_page.dart new
+92
@@ -0,0 +1,92 @@
1 +import 'package:cake_wallet/src/widgets/picker_inner_wrapper_widget.dart';
2 +import 'package:cake_wallet/src/widgets/section_divider.dart';
3 +import 'package:cake_wallet/view_model/nano_account_list/nano_account_list_view_model.dart';
4 +import 'package:flutter/material.dart';
5 +import 'package:flutter_mobx/flutter_mobx.dart';
6 +import 'package:cake_wallet/routes.dart';
7 +import 'package:cake_wallet/generated/i18n.dart';
8 +import 'package:cake_wallet/src/screens/monero_accounts/widgets/account_tile.dart';
9 +
10 +class NanoAccountListPage extends StatelessWidget {
11 + NanoAccountListPage({required this.accountListViewModel});
12 +
13 + final NanoAccountListViewModel accountListViewModel;
14 + final ScrollController controller = ScrollController();
15 +
16 + @override
17 + Widget build(BuildContext context) {
18 + double itemHeight = 80;
19 + double buttonHeight = 62;
20 +
21 + return Observer(builder: (_) {
22 + final accounts = accountListViewModel.accounts;
23 + return PickerInnerWrapperWidget(
24 + title: S.of(context).choose_account,
25 + itemsHeight: (itemHeight * accounts.length) + buttonHeight,
26 + children: [
27 + Expanded(
28 + child: Scrollbar(
29 + controller: controller,
30 + child: ListView.separated(
31 + padding: EdgeInsets.zero,
32 + controller: controller,
33 + separatorBuilder: (context, index) => const VerticalSectionDivider(),
34 + itemCount: accounts.length,
35 + itemBuilder: (context, index) {
36 + final account = accounts[index];
37 +
38 + return AccountTile(
39 + isCurrent: account.isSelected,
40 + accountName: account.label,
41 + accountBalance: account.balance ?? '0.00',
42 + currency: accountListViewModel.currency.toString(),
43 + onTap: () {
44 + if (account.isSelected) {
45 + return;
46 + }
47 +
48 + accountListViewModel.select(account);
49 + Navigator.of(context).pop();
50 + },
51 + onEdit: () async => await Navigator.of(context)
52 + .pushNamed(Routes.nanoAccountCreation, arguments: account));
53 + },
54 + ),
55 + )),
56 + GestureDetector(
57 + onTap: () async => await Navigator.of(context).pushNamed(Routes.nanoAccountCreation),
58 + child: Container(
59 + height: buttonHeight,
60 + color: Theme.of(context).cardColor,
61 + padding: EdgeInsets.symmetric(horizontal: 24),
62 + child: Center(
63 + child: Row(
64 + mainAxisSize: MainAxisSize.min,
65 + children: <Widget>[
66 + Icon(
67 + Icons.add,
68 + color: Colors.white,
69 + ),
70 + Padding(
71 + padding: EdgeInsets.only(left: 5),
72 + child: Text(
73 + S.of(context).create_new_account,
74 + style: TextStyle(
75 + fontSize: 15,
76 + fontWeight: FontWeight.w600,
77 + fontFamily: 'Lato',
78 + color: Colors.white,
79 + decoration: TextDecoration.none,
80 + ),
81 + ),
82 + )
83 + ],
84 + ),
85 + ),
86 + ),
87 + )
88 + ],
89 + );
90 + });
91 + }
92 +}
lib/src/screens/nano_accounts/widgets/account_tile.dart new
+96
@@ -0,0 +1,96 @@
1 +import 'package:cake_wallet/themes/extensions/account_list_theme.dart';
2 +import 'package:flutter/material.dart';
3 +import 'package:flutter_slidable/flutter_slidable.dart';
4 +import 'package:cake_wallet/generated/i18n.dart';
5 +
6 +class AccountTile extends StatelessWidget {
7 + AccountTile(
8 + {required this.isCurrent,
9 + required this.accountName,
10 + this.accountBalance,
11 + required this.currency,
12 + required this.onTap,
13 + required this.onEdit});
14 +
15 + final bool isCurrent;
16 + final String accountName;
17 + final String? accountBalance;
18 + final String currency;
19 + final Function() onTap;
20 + final Function() onEdit;
21 +
22 + @override
23 + Widget build(BuildContext context) {
24 + final color = isCurrent
25 + ? Theme.of(context).extension<AccountListTheme>()!.currentAccountBackgroundColor
26 + : Theme.of(context).extension<AccountListTheme>()!.tilesBackgroundColor;
27 + final textColor = isCurrent
28 + ? Theme.of(context).extension<AccountListTheme>()!.currentAccountTextColor
29 + : Theme.of(context).extension<AccountListTheme>()!.tilesTextColor;
30 +
31 + final Widget cell = GestureDetector(
32 + onTap: onTap,
33 + child: Container(
34 + height: 77,
35 + width: double.infinity,
36 + padding: EdgeInsets.only(left: 24, right: 24),
37 + color: color,
38 + child: Wrap(
39 + direction: Axis.horizontal,
40 + alignment: WrapAlignment.spaceBetween,
41 + runAlignment: WrapAlignment.center,
42 + crossAxisAlignment: WrapCrossAlignment.center,
43 + children: [
44 + Container(
45 + child: Text(
46 + accountName,
47 + style: TextStyle(
48 + fontSize: 18,
49 + fontWeight: FontWeight.w600,
50 + fontFamily: 'Lato',
51 + color: textColor,
52 + decoration: TextDecoration.none,
53 + ),
54 + ),
55 + ),
56 + if (accountBalance != null)
57 + Container(
58 + child: Text(
59 + '${accountBalance.toString()} $currency',
60 + textAlign: TextAlign.end,
61 + style: TextStyle(
62 + fontSize: 15,
63 + fontWeight: FontWeight.w600,
64 + fontFamily: 'Lato',
65 + color: Theme.of(context).textTheme.headlineMedium!.color!,
66 + decoration: TextDecoration.none,
67 + ),
68 + ),
69 + ),
70 + ],
71 + ),
72 + ),
73 + );
74 +
75 + // return cell;
76 + return Slidable(
77 + key: Key(accountName),
78 + child: cell,
79 + endActionPane: _actionPane(context)
80 + );
81 + }
82 +
83 + ActionPane _actionPane(BuildContext context) => ActionPane(
84 + motion: const ScrollMotion(),
85 + extentRatio: 0.3,
86 + children: [
87 + SlidableAction(
88 + onPressed: (_) => onEdit.call(),
89 + backgroundColor: Colors.blue,
90 + foregroundColor: Colors.white,
91 + icon: Icons.edit,
92 + label: S.of(context).edit,
93 + ),
94 + ],
95 + );
96 +}
lib/src/screens/nodes/pow_node_create_or_edit_page.dart new
+196
@@ -0,0 +1,196 @@
1 +import 'package:cake_wallet/core/execution_state.dart';
2 +import 'package:cake_wallet/palette.dart';
3 +import 'package:cake_wallet/src/screens/nodes/widgets/node_form.dart';
4 +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
5 +import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
6 +import 'package:cake_wallet/utils/show_pop_up.dart';
7 +import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
8 +import 'package:cw_core/node.dart';
9 +import 'package:flutter/material.dart';
10 +import 'package:flutter_mobx/flutter_mobx.dart';
11 +import 'package:mobx/mobx.dart';
12 +import 'package:cake_wallet/generated/i18n.dart';
13 +import 'package:cake_wallet/src/widgets/primary_button.dart';
14 +import 'package:cake_wallet/src/screens/base_page.dart';
15 +import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
16 +
17 +class PowNodeCreateOrEditPage extends BasePage {
18 + PowNodeCreateOrEditPage({required this.nodeCreateOrEditViewModel,this.editingNode, this.isSelected})
19 + : _formKey = GlobalKey<FormState>(),
20 + _addressController = TextEditingController(),
21 + _portController = TextEditingController(),
22 + _loginController = TextEditingController(),
23 + _passwordController = TextEditingController() {
24 + reaction((_) => nodeCreateOrEditViewModel.address, (String address) {
25 + if (address != _addressController.text) {
26 + _addressController.text = address;
27 + }
28 + });
29 +
30 + reaction((_) => nodeCreateOrEditViewModel.port, (String port) {
31 + if (port != _portController.text) {
32 + _portController.text = port;
33 + }
34 + });
35 +
36 + if (nodeCreateOrEditViewModel.hasAuthCredentials) {
37 + reaction((_) => nodeCreateOrEditViewModel.login, (String login) {
38 + if (login != _loginController.text) {
39 + _loginController.text = login;
40 + }
41 + });
42 +
43 + reaction((_) => nodeCreateOrEditViewModel.password, (String password) {
44 + if (password != _passwordController.text) {
45 + _passwordController.text = password;
46 + }
47 + });
48 + }
49 +
50 + _addressController.addListener(
51 + () => nodeCreateOrEditViewModel.address = _addressController.text);
52 + _portController.addListener(
53 + () => nodeCreateOrEditViewModel.port = _portController.text);
54 + _loginController.addListener(
55 + () => nodeCreateOrEditViewModel.login = _loginController.text);
56 + _passwordController.addListener(
57 + () => nodeCreateOrEditViewModel.password = _passwordController.text);
58 + }
59 +
60 + final GlobalKey<FormState> _formKey;
61 + final TextEditingController _addressController;
62 + final TextEditingController _portController;
63 + final TextEditingController _loginController;
64 + final TextEditingController _passwordController;
65 +
66 + @override
67 + String get title => editingNode != null ? S.current.edit_node : S.current.node_new;
68 +
69 + @override
70 + Widget trailing(BuildContext context) => IconButton(
71 + onPressed: () async {
72 + await nodeCreateOrEditViewModel.scanQRCodeForNewNode();
73 + },
74 + splashColor: Colors.transparent,
75 + highlightColor: Colors.transparent,
76 + hoverColor: Colors.transparent,
77 + icon: Image.asset(
78 + 'assets/images/qr_code_icon.png',
79 + ),
80 + );
81 +
82 + final NodeCreateOrEditViewModel nodeCreateOrEditViewModel;
83 + final Node? editingNode;
84 + final bool? isSelected;
85 +
86 + @override
87 + Widget body(BuildContext context) {
88 +
89 + reaction((_) => nodeCreateOrEditViewModel.connectionState,
90 + (ExecutionState state) {
91 + if (state is ExecutedSuccessfullyState) {
92 + WidgetsBinding.instance.addPostFrameCallback((_) {
93 + showPopUp<void>(
94 + context: context,
95 + builder: (BuildContext context) =>
96 + AlertWithOneAction(
97 + alertTitle: S.of(context).new_node_testing,
98 + alertContent: state.payload as bool
99 + ? S.of(context).node_connection_successful
100 + : S.of(context).node_connection_failed,
101 + buttonText: S.of(context).ok,
102 + buttonAction: () => Navigator.of(context).pop()));
103 + });
104 + }
105 +
106 + if (state is FailureState) {
107 + WidgetsBinding.instance.addPostFrameCallback((_) {
108 + showPopUp<void>(
109 + context: context,
110 + builder: (BuildContext context) {
111 + return AlertWithOneAction(
112 + alertTitle: S.of(context).error,
113 + alertContent: state.error,
114 + buttonText: S.of(context).ok,
115 + buttonAction: () => Navigator.of(context).pop());
116 + });
117 + });
118 + }
119 + });
120 +
121 + return Container(
122 + padding: EdgeInsets.only(left: 24, right: 24),
123 + child: ScrollableWithBottomSection(
124 + contentPadding: EdgeInsets.only(bottom: 24.0),
125 + content: NodeForm(
126 + formKey: _formKey,
127 + nodeViewModel: nodeCreateOrEditViewModel,
128 + editingNode: editingNode,
129 + ),
130 + bottomSectionPadding: EdgeInsets.only(bottom: 24),
131 + bottomSection: Observer(
132 + builder: (_) => Row(
133 + mainAxisAlignment: MainAxisAlignment.center,
134 + children: <Widget>[
135 + Flexible(
136 + child: Container(
137 + padding: EdgeInsets.only(right: 8.0),
138 + child: LoadingPrimaryButton(
139 + onPressed: () async {
140 + final confirmed = await showPopUp<bool>(
141 + context: context,
142 + builder: (BuildContext context) {
143 + return AlertWithTwoActions(
144 + alertTitle:
145 + S.of(context).remove_node,
146 + alertContent: S
147 + .of(context)
148 + .remove_node_message,
149 + rightButtonText:
150 + S.of(context).remove,
151 + leftButtonText:
152 + S.of(context).cancel,
153 + actionRightButton: () =>
154 + Navigator.pop(context, true),
155 + actionLeftButton: () =>
156 + Navigator.pop(context, false));
157 + }) ??
158 + false;
159 +
160 + if (confirmed) {
161 + await editingNode!.delete();
162 + Navigator.of(context).pop();
163 + }
164 + },
165 + text: S.of(context).delete,
166 + isDisabled: !nodeCreateOrEditViewModel.isReady ||
167 + (isSelected ?? false),
168 + color: Palette.red,
169 + textColor: Colors.white),
170 + )),
171 + Flexible(
172 + child: Container(
173 + padding: EdgeInsets.only(left: 8.0),
174 + child: PrimaryButton(
175 + onPressed: () async {
176 + if (_formKey.currentState != null && !_formKey.currentState!.validate()) {
177 + return;
178 + }
179 +
180 + await nodeCreateOrEditViewModel.save(
181 + editingNode: editingNode, saveAsCurrent: isSelected ?? false);
182 + Navigator.of(context).pop();
183 + },
184 + text: S.of(context).save,
185 + color: Theme.of(context).primaryColor,
186 + textColor: Colors.white,
187 + isDisabled: (!nodeCreateOrEditViewModel.isReady)||
188 + (nodeCreateOrEditViewModel
189 + .connectionState is IsExecutingState),
190 + ),
191 + )),
192 + ],
193 + )),
194 + ));
195 + }
196 +}
\ No newline at end of file
lib/src/screens/nodes/widgets/node_list_row.dart
+4 -2
@@ -11,10 +11,12 @@ class NodeListRow extends StandardListRow {
11 {required String title,
12 required this.node,
13 required void Function(BuildContext context) onTap,
14 - required bool isSelected})
14 + required bool isSelected,
15 + required this.isPow})
16 : super(title: title, onTap: onTap, isSelected: isSelected);
17
18 final Node node;
19 + final bool isPow;
20
21 @override
22 Widget buildLeading(BuildContext context) {
@@ -33,7 +35,7 @@ class NodeListRow extends StandardListRow {
35 @override
36 Widget buildTrailing(BuildContext context) {
37 return GestureDetector(
36 - onTap: () => Navigator.of(context).pushNamed(Routes.newNode,
38 + onTap: () => Navigator.of(context).pushNamed(isPow ? Routes.newPowNode : Routes.newNode,
39 arguments: {'editingNode': node, 'isSelected': isSelected}),
40 child: Container(
41 padding: EdgeInsets.all(10),
lib/src/screens/receive/receive_page.dart
+18 -6
@@ -1,7 +1,9 @@
1 +import 'package:cake_wallet/src/screens/nano_accounts/nano_account_list_page.dart';
2 +import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
3 +import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
4 import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
5 import 'package:cake_wallet/themes/extensions/receive_page_theme.dart';
6 import 'package:cake_wallet/src/widgets/gradient_background.dart';
4 -import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
7 import 'package:cake_wallet/src/widgets/section_divider.dart';
8 import 'package:cake_wallet/themes/theme_base.dart';
9 import 'package:cake_wallet/utils/share_util.dart';
@@ -22,7 +24,6 @@ import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_i
24 import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
25 import 'package:cake_wallet/src/screens/receive/widgets/qr_widget.dart';
26 import 'package:keyboard_actions/keyboard_actions.dart';
25 -import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
27
28 class ReceivePage extends BasePage {
29 ReceivePage({required this.addressListViewModel})
@@ -99,7 +100,9 @@ class ReceivePage extends BasePage {
100 @override
101 Widget body(BuildContext context) {
102 return (addressListViewModel.type == WalletType.monero ||
102 - addressListViewModel.type == WalletType.haven)
103 + addressListViewModel.type == WalletType.haven ||
104 + addressListViewModel.type == WalletType.nano ||
105 + addressListViewModel.type == WalletType.banano)
106 ? KeyboardActions(
107 config: KeyboardActionsConfig(
108 keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
@@ -137,9 +140,18 @@ class ReceivePage extends BasePage {
140
141 if (item is WalletAccountListHeader) {
142 cell = HeaderTile(
140 - onTap: () async => await showPopUp<void>(
141 - context: context,
142 - builder: (_) => getIt.get<MoneroAccountListPage>()),
143 + onTap: () async {
144 + if (addressListViewModel.type == WalletType.monero ||
145 + addressListViewModel.type == WalletType.haven) {
146 + await showPopUp<void>(
147 + context: context,
148 + builder: (_) => getIt.get<MoneroAccountListPage>());
149 + } else {
150 + await showPopUp<void>(
151 + context: context,
152 + builder: (_) => getIt.get<NanoAccountListPage>());
153 + }
154 + },
155 title: S.of(context).accounts,
156 icon: Icon(
157 Icons.arrow_forward_ios,
lib/src/screens/restore/wallet_restore_choose_derivation.dart new
+129
@@ -0,0 +1,129 @@
1 +import 'package:cake_wallet/di.dart';
2 +import 'package:cake_wallet/generated/i18n.dart';
3 +import 'package:cake_wallet/store/settings_store.dart';
4 +import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
5 +import 'package:cake_wallet/themes/theme_base.dart';
6 +import 'package:cake_wallet/view_model/wallet_restore_choose_derivation_view_model.dart';
7 +import 'package:cw_core/wallet_info.dart';
8 +import 'package:flutter/material.dart';
9 +import 'package:flutter_mobx/flutter_mobx.dart';
10 +import 'package:cake_wallet/src/screens/base_page.dart';
11 +
12 +class WalletRestoreChooseDerivationPage extends BasePage {
13 + WalletRestoreChooseDerivationPage(this.walletRestoreChooseDerivationViewModel) {}
14 +
15 + @override
16 + Widget middle(BuildContext context) => Observer(
17 + builder: (_) => Text(
18 + S.current.choose_derivation,
19 + style: TextStyle(
20 + fontSize: 18.0,
21 + fontWeight: FontWeight.bold,
22 + fontFamily: 'Lato',
23 + color: titleColor(context)),
24 + ));
25 +
26 + final WalletRestoreChooseDerivationViewModel walletRestoreChooseDerivationViewModel;
27 + DerivationType derivationType = DerivationType.unknown;
28 +
29 + @override
30 + Widget body(BuildContext context) {
31 + return Observer(
32 + builder: (_) => FutureBuilder<List<DerivationInfo>>(
33 + future: walletRestoreChooseDerivationViewModel.derivations,
34 + builder: (context, snapshot) {
35 + if (snapshot.connectionState == ConnectionState.waiting) {
36 + return Center(
37 + child: CircularProgressIndicator(),
38 + );
39 + } else if (snapshot.hasError) {
40 + return Center(child: Text('Error: ${snapshot.error}'));
41 + } else if (!snapshot.hasData || snapshot.data!.isEmpty) {
42 + return Center(child: Text('Error! No derivations available!'));
43 + } else {
44 + return ListView.separated(
45 + shrinkWrap: true,
46 + separatorBuilder: (_, __) => SizedBox(),
47 + itemCount: snapshot.data!.length,
48 + itemBuilder: (__, index) {
49 + final derivation = snapshot.data![index];
50 + return Card(
51 + margin: const EdgeInsets.all(8),
52 + elevation: 3,
53 + shape: RoundedRectangleBorder(
54 + borderRadius: BorderRadius.circular(15),
55 + ),
56 + child: InkWell(
57 + borderRadius: BorderRadius.circular(15),
58 + onTap: () async {
59 + Navigator.pop(context, derivation);
60 + },
61 + child: ListTile(
62 + contentPadding: EdgeInsets.all(16),
63 + title: Center(
64 + child: Text(
65 + "${derivation.description ?? derivation.derivationType.toString().split('.').last}",
66 + style: Theme.of(context).primaryTextTheme.labelMedium!.copyWith(
67 + fontSize: 18,
68 + fontWeight: FontWeight.w800,
69 + color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
70 + ),
71 + ),
72 + ),
73 + subtitle: Column(
74 + crossAxisAlignment: CrossAxisAlignment.start,
75 + children: [
76 + if (derivation.derivationPath != null)
77 + Text(
78 + derivation.derivationPath!,
79 + style: Theme.of(context).primaryTextTheme.labelMedium!.copyWith(
80 + fontSize: 14,
81 + fontWeight: FontWeight.w500,
82 + color: Theme.of(context)
83 + .extension<CakeTextTheme>()!
84 + .secondaryTextColor,
85 + ),
86 + ),
87 + Text(
88 + derivation.address,
89 + style: Theme.of(context).primaryTextTheme.labelMedium!.copyWith(
90 + fontSize: 16,
91 + fontWeight: FontWeight.w500,
92 + color: Theme.of(context)
93 + .extension<CakeTextTheme>()!
94 + .secondaryTextColor,
95 + ),
96 + ),
97 + Text(
98 + "${S.current.confirmed}: ${derivation.balance}",
99 + style: Theme.of(context).primaryTextTheme.labelMedium!.copyWith(
100 + fontSize: 16,
101 + fontWeight: FontWeight.w500,
102 + color: Theme.of(context)
103 + .extension<CakeTextTheme>()!
104 + .secondaryTextColor,
105 + ),
106 + ),
107 + Text(
108 + "${S.current.transactions}: ${derivation.height}",
109 + style: Theme.of(context).primaryTextTheme.labelMedium!.copyWith(
110 + fontSize: 16,
111 + fontWeight: FontWeight.w500,
112 + color: Theme.of(context)
113 + .extension<CakeTextTheme>()!
114 + .secondaryTextColor,
115 + ),
116 + ),
117 + ],
118 + ),
119 + ),
120 + ),
121 + );
122 + },
123 + );
124 + }
125 + },
126 + ),
127 + );
128 + }
129 +}
lib/src/screens/restore/wallet_restore_from_keys_form.dart
+4 -1
@@ -13,6 +13,7 @@ import 'package:flutter/services.dart';
13 class WalletRestoreFromKeysFrom extends StatefulWidget {
14 WalletRestoreFromKeysFrom({
15 required this.walletRestoreViewModel,
16 + required this.onPrivateKeyChange,
17 required this.displayPrivateKeyField,
18 required this.onHeightOrDateEntered,
19 Key? key,
@@ -20,6 +21,7 @@ class WalletRestoreFromKeysFrom extends StatefulWidget {
21
22 final Function(bool) onHeightOrDateEntered;
23 final WalletRestoreViewModel walletRestoreViewModel;
24 + final void Function(String)? onPrivateKeyChange;
25 final bool displayPrivateKeyField;
26
27 @override
@@ -54,6 +56,7 @@ class WalletRestoreFromKeysFromState extends State<WalletRestoreFromKeysFrom> {
56 if (privateKeyController.text.isNotEmpty) {
57 widget.onHeightOrDateEntered(true);
58 }
59 + widget.onPrivateKeyChange?.call(privateKeyController.text);
60 });
61 }
62
@@ -62,8 +65,8 @@ class WalletRestoreFromKeysFromState extends State<WalletRestoreFromKeysFrom> {
65 nameController.dispose();
66 addressController.dispose();
67 viewKeyController.dispose();
65 - spendKeyController.dispose();
68 privateKeyController.dispose();
69 + spendKeyController.dispose();
70 super.dispose();
71 }
72
lib/src/screens/restore/wallet_restore_page.dart
+151 -19
@@ -1,6 +1,12 @@
1 +import 'package:cake_wallet/di.dart';
2 +import 'package:cake_wallet/nano/nano.dart';
3 +import 'package:cake_wallet/routes.dart';
4 +import 'package:cake_wallet/store/app_store.dart';
5 import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
6 import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
7 import 'package:cake_wallet/utils/responsive_layout_util.dart';
8 +import 'package:cw_core/nano_account_info_response.dart';
9 +import 'package:cw_core/wallet_info.dart';
10 import 'package:cw_core/wallet_type.dart';
11 import 'package:flutter/material.dart';
12 import 'package:keyboard_actions/keyboard_actions.dart';
@@ -69,6 +75,12 @@ class WalletRestorePage extends BasePage {
75 _pages.add(WalletRestoreFromKeysFrom(
76 key: walletRestoreFromKeysFormKey,
77 walletRestoreViewModel: walletRestoreViewModel,
78 + onPrivateKeyChange: (String seed) {
79 + if (walletRestoreViewModel.type == WalletType.nano ||
80 + walletRestoreViewModel.type == WalletType.banano) {
81 + walletRestoreViewModel.isButtonEnabled = _isValidSeedKey();
82 + }
83 + },
84 displayPrivateKeyField: walletRestoreViewModel.hasRestoreFromPrivateKey,
85 onHeightOrDateEntered: (value) => walletRestoreViewModel.isButtonEnabled = value));
86 break;
@@ -97,6 +109,8 @@ class WalletRestorePage extends BasePage {
109 final GlobalKey<WalletRestoreFromSeedFormState> walletRestoreFromSeedFormKey;
110 final GlobalKey<WalletRestoreFromKeysFromState> walletRestoreFromKeysFormKey;
111 final FocusNode _blockHeightFocusNode;
112 + DerivationType derivationType = DerivationType.unknown;
113 + String? derivationPath = null;
114
115 @override
116 Widget body(BuildContext context) {
@@ -185,7 +199,9 @@ class WalletRestorePage extends BasePage {
199 child: Observer(
200 builder: (context) {
201 return LoadingPrimaryButton(
188 - onPressed: _confirmForm,
202 + onPressed: () async {
203 + await _confirmForm(context);
204 + },
205 text: S.of(context).restore_recover,
206 color: Theme.of(context)
207 .extension<WalletListTheme>()!
@@ -217,18 +233,34 @@ class WalletRestorePage extends BasePage {
233 return false;
234 }
235
220 - if ((walletRestoreViewModel.type == WalletType.bitcoin ||
221 - walletRestoreViewModel.type == WalletType.litecoin) &&
236 + if ((walletRestoreViewModel.type == WalletType.litecoin) &&
237 (seedWords.length != WalletRestoreViewModelBase.electrumSeedMnemonicLength &&
238 seedWords.length != WalletRestoreViewModelBase.electrumShortSeedMnemonicLength)) {
239 return false;
240 }
241
242 + // bip39:
243 + const validSeedLengths = [12, 18, 24];
244 + if (walletRestoreViewModel.type == WalletType.bitcoin &&
245 + !(validSeedLengths.contains(seedWords.length))) {
246 + return false;
247 + }
248 +
249 final words =
250 walletRestoreFromSeedFormKey.currentState!.seedWidgetStateKey.currentState!.words.toSet();
251 return seedWords.toSet().difference(words).toSet().isEmpty;
252 }
253
254 + bool _isValidSeedKey() {
255 + final seedKey = walletRestoreFromKeysFormKey.currentState!.privateKeyController.text;
256 +
257 + if (seedKey.length != 64 && seedKey.length != 128) {
258 + return false;
259 + }
260 +
261 + return true;
262 + }
263 +
264 Map<String, dynamic> _credentials() {
265 final credentials = <String, dynamic>{};
266
@@ -243,10 +275,12 @@ class WalletRestorePage extends BasePage {
275
276 credentials['name'] =
277 walletRestoreFromSeedFormKey.currentState!.nameTextEditingController.text;
246 - } else {
278 + } else if (walletRestoreViewModel.mode == WalletRestoreMode.keys) {
279 if (walletRestoreViewModel.hasRestoreFromPrivateKey) {
280 credentials['private_key'] =
281 walletRestoreFromKeysFormKey.currentState!.privateKeyController.text;
282 + credentials['name'] =
283 + walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text;
284 } else {
285 credentials['address'] = walletRestoreFromKeysFormKey.currentState!.addressController.text;
286 credentials['viewKey'] = walletRestoreFromKeysFormKey.currentState!.viewKeyController.text;
@@ -254,31 +288,81 @@ class WalletRestorePage extends BasePage {
288 walletRestoreFromKeysFormKey.currentState!.spendKeyController.text;
289 credentials['height'] =
290 walletRestoreFromKeysFormKey.currentState!.blockchainHeightKey.currentState!.height;
291 + credentials['name'] =
292 + walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text;
293 }
258 -
259 - credentials['name'] =
260 - walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text;
294 }
295
296 + credentials['derivationType'] = this.derivationType;
297 + credentials['derivationPath'] = this.derivationPath;
298 + credentials['walletType'] = walletRestoreViewModel.type;
299 return credentials;
300 }
301
266 - void _confirmForm() {
302 + Future<List<DerivationInfo>> getDerivationInfo(dynamic credentials) async {
303 + var list = <DerivationInfo>[];
304 + var walletType = credentials["walletType"] as WalletType;
305 + var appStore = getIt.get<AppStore>();
306 + var node = appStore.settingsStore.getCurrentNode(walletType);
307 +
308 + switch (walletType) {
309 + case WalletType.nano:
310 + String? mnemonic = credentials['seed'] as String?;
311 + String? seedKey = credentials['private_key'] as String?;
312 + AccountInfoResponse? bip39Info = await nanoUtil!.getInfoFromSeedOrMnemonic(
313 + DerivationType.bip39,
314 + mnemonic: mnemonic,
315 + seedKey: seedKey,
316 + node: node);
317 + AccountInfoResponse? standardInfo = await nanoUtil!.getInfoFromSeedOrMnemonic(
318 + DerivationType.nano,
319 + mnemonic: mnemonic,
320 + seedKey: seedKey,
321 + node: node,
322 + );
323 +
324 + if (standardInfo?.balance != null) {
325 + list.add(DerivationInfo(
326 + derivationType: DerivationType.nano,
327 + balance: nanoUtil!.getRawAsUsableString(standardInfo!.balance, nanoUtil!.rawPerNano),
328 + address: standardInfo.address!,
329 + height: standardInfo.confirmationHeight,
330 + ));
331 + }
332 +
333 + if (bip39Info?.balance != null) {
334 + list.add(DerivationInfo(
335 + derivationType: DerivationType.bip39,
336 + balance: nanoUtil!.getRawAsUsableString(bip39Info!.balance, nanoUtil!.rawPerNano),
337 + address: bip39Info.address!,
338 + height: bip39Info.confirmationHeight,
339 + ));
340 + }
341 + break;
342 + default:
343 + break;
344 + }
345 + return list;
346 + }
347 +
348 + Future<void> _confirmForm(BuildContext context) async {
349 // Dismissing all visible keyboard to provide context for navigation
350 FocusManager.instance.primaryFocus?.unfocus();
269 - final formContext = walletRestoreViewModel.mode == WalletRestoreMode.seed
270 - ? walletRestoreFromSeedFormKey.currentContext
271 - : walletRestoreFromKeysFormKey.currentContext;
351
273 - final formKey = walletRestoreViewModel.mode == WalletRestoreMode.seed
274 - ? walletRestoreFromSeedFormKey.currentState!.formKey
275 - : walletRestoreFromKeysFormKey.currentState!.formKey;
276 -
277 - final name = walletRestoreViewModel.mode == WalletRestoreMode.seed
278 - ? walletRestoreFromSeedFormKey.currentState!.nameTextEditingController.value.text
279 - : walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.value.text;
352 + late BuildContext? formContext;
353 + late GlobalKey<FormState>? formKey;
354 + late String name;
355 + if (walletRestoreViewModel.mode == WalletRestoreMode.seed) {
356 + formContext = walletRestoreFromSeedFormKey.currentContext;
357 + formKey = walletRestoreFromSeedFormKey.currentState!.formKey;
358 + name = walletRestoreFromSeedFormKey.currentState!.nameTextEditingController.value.text;
359 + } else if (walletRestoreViewModel.mode == WalletRestoreMode.keys) {
360 + formContext = walletRestoreFromKeysFormKey.currentContext;
361 + formKey = walletRestoreFromKeysFormKey.currentState!.formKey;
362 + name = walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.value.text;
363 + }
364
281 - if (!formKey.currentState!.validate()) {
365 + if (!formKey!.currentState!.validate()) {
366 return;
367 }
368
@@ -287,6 +371,54 @@ class WalletRestorePage extends BasePage {
371 return;
372 }
373
374 + walletRestoreViewModel.state = IsExecutingState();
375 +
376 + List<DerivationType> derivationTypes =
377 + await walletRestoreViewModel.getDerivationTypes(_credentials());
378 +
379 + if (derivationTypes[0] == DerivationType.unknown || derivationTypes.length > 1) {
380 + // push screen to choose the derivation type:
381 + List<DerivationInfo> derivations = await getDerivationInfo(_credentials());
382 +
383 + int derivationsWithHistory = 0;
384 + int derivationWithHistoryIndex = 0;
385 + for (int i = 0; i < derivations.length; i++) {
386 + if (derivations[i].height > 0) {
387 + derivationsWithHistory++;
388 + derivationWithHistoryIndex = i;
389 + }
390 + }
391 +
392 + DerivationInfo? derivationInfo;
393 +
394 + if (derivationsWithHistory > 1) {
395 + derivationInfo = await Navigator.of(context).pushNamed(Routes.restoreWalletChooseDerivation,
396 + arguments: derivations) as DerivationInfo?;
397 + } else if (derivationsWithHistory == 1) {
398 + derivationInfo = derivations[derivationWithHistoryIndex];
399 + } else if (derivationsWithHistory == 0) {
400 + // default derivation:
401 + derivationInfo = DerivationInfo(
402 + derivationType: derivationTypes[0],
403 + derivationPath: "m/0'/1",
404 + height: 0,
405 + );
406 + }
407 +
408 + if (derivationInfo == null) {
409 + walletRestoreViewModel.state = InitialExecutionState();
410 + return;
411 + }
412 + this.derivationType = derivationInfo.derivationType;
413 + this.derivationPath = derivationInfo.derivationPath;
414 + } else {
415 + // electrum derivation:
416 + this.derivationType = derivationTypes[0];
417 + this.derivationPath = "m/0'/1";
418 + }
419 +
420 + walletRestoreViewModel.state = InitialExecutionState();
421 +
422 walletRestoreViewModel.create(options: _credentials());
423 }
424
lib/src/screens/send/widgets/confirm_sending_alert.dart
+231 -245
@@ -8,26 +8,26 @@ import 'package:cake_wallet/src/widgets/cake_scrollbar.dart';
8 import 'package:flutter/scheduler.dart';
9
10 class ConfirmSendingAlert extends BaseAlertDialog {
11 - ConfirmSendingAlert({
12 - required this.alertTitle,
13 - this.paymentId,
14 - this.paymentIdValue,
15 - required this.amount,
16 - required this.amountValue,
17 - required this.fiatAmountValue,
18 - required this.fee,
19 - required this.feeValue,
20 - required this.feeFiatAmount,
21 - required this.outputs,
22 - required this.leftButtonText,
23 - required this.rightButtonText,
24 - required this.actionLeftButton,
25 - required this.actionRightButton,
26 - this.alertBarrierDismissible = true,
27 - this.alertLeftActionButtonTextColor,
28 - this.alertRightActionButtonTextColor,
29 - this.alertLeftActionButtonColor,
30 - this.alertRightActionButtonColor});
11 + ConfirmSendingAlert(
12 + {required this.alertTitle,
13 + this.paymentId,
14 + this.paymentIdValue,
15 + required this.amount,
16 + required this.amountValue,
17 + required this.fiatAmountValue,
18 + required this.fee,
19 + required this.feeValue,
20 + required this.feeFiatAmount,
21 + required this.outputs,
22 + required this.leftButtonText,
23 + required this.rightButtonText,
24 + required this.actionLeftButton,
25 + required this.actionRightButton,
26 + this.alertBarrierDismissible = true,
27 + this.alertLeftActionButtonTextColor,
28 + this.alertRightActionButtonTextColor,
29 + this.alertLeftActionButtonColor,
30 + this.alertRightActionButtonColor});
31
32 final String alertTitle;
33 final String? paymentId;
@@ -92,21 +92,20 @@ class ConfirmSendingAlert extends BaseAlertDialog {
92 fee: fee,
93 feeValue: feeValue,
94 feeFiatAmount: feeFiatAmount,
95 - outputs: outputs
96 - );
95 + outputs: outputs);
96 }
97
98 class ConfirmSendingAlertContent extends StatefulWidget {
100 - ConfirmSendingAlertContent({
101 - this.paymentId,
102 - this.paymentIdValue,
103 - required this.amount,
104 - required this.amountValue,
105 - required this.fiatAmountValue,
106 - required this.fee,
107 - required this.feeValue,
108 - required this.feeFiatAmount,
109 - required this.outputs});
99 + ConfirmSendingAlertContent(
100 + {this.paymentId,
101 + this.paymentIdValue,
102 + required this.amount,
103 + required this.amountValue,
104 + required this.fiatAmountValue,
105 + required this.fee,
106 + required this.feeValue,
107 + required this.feeFiatAmount,
108 + required this.outputs});
109
110 final String? paymentId;
111 final String? paymentIdValue;
@@ -120,29 +119,28 @@ class ConfirmSendingAlertContent extends StatefulWidget {
119
120 @override
121 ConfirmSendingAlertContentState createState() => ConfirmSendingAlertContentState(
123 - paymentId: paymentId,
124 - paymentIdValue: paymentIdValue,
125 - amount: amount,
126 - amountValue: amountValue,
127 - fiatAmountValue: fiatAmountValue,
128 - fee: fee,
129 - feeValue: feeValue,
130 - feeFiatAmount: feeFiatAmount,
131 - outputs: outputs
132 - );
122 + paymentId: paymentId,
123 + paymentIdValue: paymentIdValue,
124 + amount: amount,
125 + amountValue: amountValue,
126 + fiatAmountValue: fiatAmountValue,
127 + fee: fee,
128 + feeValue: feeValue,
129 + feeFiatAmount: feeFiatAmount,
130 + outputs: outputs);
131 }
132
133 class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent> {
136 - ConfirmSendingAlertContentState({
137 - this.paymentId,
138 - this.paymentIdValue,
139 - required this.amount,
140 - required this.amountValue,
141 - required this.fiatAmountValue,
142 - required this.fee,
143 - required this.feeValue,
144 - required this.feeFiatAmount,
145 - required this.outputs})
134 + ConfirmSendingAlertContentState(
135 + {this.paymentId,
136 + this.paymentIdValue,
137 + required this.amount,
138 + required this.amountValue,
139 + required this.fiatAmountValue,
140 + required this.fee,
141 + required this.feeValue,
142 + required this.feeFiatAmount,
143 + required this.outputs})
144 : recipientTitle = '' {
145 recipientTitle = outputs.length > 1
146 ? S.current.transaction_details_recipient_address
@@ -170,8 +168,9 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
168 Widget build(BuildContext context) {
169 controller.addListener(() {
170 fromTop = controller.hasClients
173 - ? (controller.offset / controller.position.maxScrollExtent *
174 - (backgroundHeight - thumbHeight))
171 + ? (controller.offset /
172 + controller.position.maxScrollExtent *
173 + (backgroundHeight - thumbHeight))
174 : 0;
175 setState(() {});
176 });
@@ -182,94 +181,92 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
181 });
182 });
183
185 - return Stack(
186 - alignment: Alignment.center,
187 - clipBehavior: Clip.none,
188 - children: [
189 - Container(
190 - height: 200,
191 - child: SingleChildScrollView(
192 - controller: controller,
193 - child: Column(
194 - children: <Widget>[
195 - if (paymentIdValue != null && paymentId != null)
196 - Padding(
197 - padding: EdgeInsets.only(bottom: 32),
198 - child: Row(
199 - mainAxisSize: MainAxisSize.max,
200 - mainAxisAlignment: MainAxisAlignment.spaceBetween,
201 - crossAxisAlignment: CrossAxisAlignment.start,
202 - children: <Widget>[
203 - Text(
204 - paymentId!,
205 - style: TextStyle(
206 - fontSize: 16,
207 - fontWeight: FontWeight.normal,
208 - fontFamily: 'Lato',
209 - color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
210 - decoration: TextDecoration.none,
211 - ),
184 + return Stack(alignment: Alignment.center, clipBehavior: Clip.none, children: [
185 + Container(
186 + height: 200,
187 + child: SingleChildScrollView(
188 + controller: controller,
189 + child: Column(
190 + children: <Widget>[
191 + if (paymentIdValue != null && paymentId != null)
192 + Padding(
193 + padding: EdgeInsets.only(bottom: 32),
194 + child: Row(
195 + mainAxisSize: MainAxisSize.max,
196 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
197 + crossAxisAlignment: CrossAxisAlignment.start,
198 + children: <Widget>[
199 + Text(
200 + paymentId!,
201 + style: TextStyle(
202 + fontSize: 16,
203 + fontWeight: FontWeight.normal,
204 + fontFamily: 'Lato',
205 + color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
206 + decoration: TextDecoration.none,
207 ),
213 - Column(
214 - crossAxisAlignment: CrossAxisAlignment.end,
215 - children: [
216 - Text(
217 - paymentIdValue!,
218 - style: TextStyle(
219 - fontSize: 18,
220 - fontWeight: FontWeight.w600,
221 - fontFamily: 'Lato',
222 - color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
223 - decoration: TextDecoration.none,
224 - ),
208 + ),
209 + Column(
210 + crossAxisAlignment: CrossAxisAlignment.end,
211 + children: [
212 + Text(
213 + paymentIdValue!,
214 + style: TextStyle(
215 + fontSize: 18,
216 + fontWeight: FontWeight.w600,
217 + fontFamily: 'Lato',
218 + color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
219 + decoration: TextDecoration.none,
220 ),
226 - ],
227 - )
228 - ],
221 + ),
222 + ],
223 + )
224 + ],
225 + ),
226 + ),
227 + Row(
228 + mainAxisSize: MainAxisSize.max,
229 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
230 + crossAxisAlignment: CrossAxisAlignment.start,
231 + children: <Widget>[
232 + Text(
233 + amount,
234 + style: TextStyle(
235 + fontSize: 16,
236 + fontWeight: FontWeight.normal,
237 + fontFamily: 'Lato',
238 + color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
239 + decoration: TextDecoration.none,
240 ),
241 ),
231 - Row(
232 - mainAxisSize: MainAxisSize.max,
233 - mainAxisAlignment: MainAxisAlignment.spaceBetween,
234 - crossAxisAlignment: CrossAxisAlignment.start,
235 - children: <Widget>[
236 - Text(
237 - amount,
238 - style: TextStyle(
239 - fontSize: 16,
240 - fontWeight: FontWeight.normal,
241 - fontFamily: 'Lato',
242 - color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
243 - decoration: TextDecoration.none,
242 + Column(
243 + crossAxisAlignment: CrossAxisAlignment.end,
244 + children: [
245 + Text(
246 + amountValue,
247 + style: TextStyle(
248 + fontSize: 18,
249 + fontWeight: FontWeight.w600,
250 + fontFamily: 'Lato',
251 + color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
252 + decoration: TextDecoration.none,
253 + ),
254 ),
245 - ),
246 - Column(
247 - crossAxisAlignment: CrossAxisAlignment.end,
248 - children: [
249 - Text(
250 - amountValue,
251 - style: TextStyle(
252 - fontSize: 18,
253 - fontWeight: FontWeight.w600,
254 - fontFamily: 'Lato',
255 - color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
256 - decoration: TextDecoration.none,
257 - ),
255 + Text(
256 + fiatAmountValue,
257 + style: TextStyle(
258 + fontSize: 12,
259 + fontWeight: FontWeight.w600,
260 + fontFamily: 'Lato',
261 + color: PaletteDark.pigeonBlue,
262 + decoration: TextDecoration.none,
263 ),
259 - Text(
260 - fiatAmountValue,
261 - style: TextStyle(
262 - fontSize: 12,
263 - fontWeight: FontWeight.w600,
264 - fontFamily: 'Lato',
265 - color: PaletteDark.pigeonBlue,
266 - decoration: TextDecoration.none,
267 - ),
268 - )
269 - ],
270 - )
271 - ],
272 - ),
264 + )
265 + ],
266 + )
267 + ],
268 + ),
269 + if (feeValue.isNotEmpty && feeValue != "0")
270 Padding(
271 padding: EdgeInsets.only(top: 16),
272 child: Row(
@@ -313,103 +310,97 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
310 ],
311 )
312 ],
316 - )
317 - ),
318 - Padding(
319 - padding: EdgeInsets.only(top: 16),
320 - child: Column(
321 - children: [
322 - Text(
323 - '$recipientTitle:',
324 - style: TextStyle(
325 - fontSize: 16,
326 - fontWeight: FontWeight.normal,
327 - fontFamily: 'Lato',
328 - color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
329 - decoration: TextDecoration.none,
330 - ),
313 + )),
314 + Padding(
315 + padding: EdgeInsets.only(top: 16),
316 + child: Column(
317 + children: [
318 + Text(
319 + '$recipientTitle:',
320 + style: TextStyle(
321 + fontSize: 16,
322 + fontWeight: FontWeight.normal,
323 + fontFamily: 'Lato',
324 + color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
325 + decoration: TextDecoration.none,
326 ),
332 - outputs.length > 1
333 - ? ListView.builder(
334 - padding: EdgeInsets.only(top: 0),
335 - shrinkWrap: true,
336 - physics: NeverScrollableScrollPhysics(),
337 - itemCount: outputs.length,
338 - itemBuilder: (context, index) {
339 - final item = outputs[index];
340 - final _address = item.isParsedAddress
341 - ? item.extractedAddress
342 - : item.address;
343 - final _amount =
344 - item.cryptoAmount.replaceAll(',', '.');
327 + ),
328 + outputs.length > 1
329 + ? ListView.builder(
330 + padding: EdgeInsets.only(top: 0),
331 + shrinkWrap: true,
332 + physics: NeverScrollableScrollPhysics(),
333 + itemCount: outputs.length,
334 + itemBuilder: (context, index) {
335 + final item = outputs[index];
336 + final _address =
337 + item.isParsedAddress ? item.extractedAddress : item.address;
338 + final _amount = item.cryptoAmount.replaceAll(',', '.');
339
346 - return Column(
347 - children: [
348 - if (item.isParsedAddress) Padding(
349 - padding: EdgeInsets.only(top: 8),
350 - child: Text(
351 - item.parsedAddress.name,
352 - textAlign: TextAlign.center,
353 - style: TextStyle(
354 - fontSize: 14,
355 - fontWeight: FontWeight.w600,
356 - fontFamily: 'Lato',
357 - color: PaletteDark.pigeonBlue,
358 - decoration: TextDecoration.none,
359 - ),
360 - )
361 - ),
362 - Padding(
363 - padding: EdgeInsets.only(top: 8),
364 - child: Text(
365 - _address,
366 - style: TextStyle(
367 - fontSize: 10,
368 - fontWeight: FontWeight.w600,
369 - fontFamily: 'Lato',
370 - color: PaletteDark.pigeonBlue,
371 - decoration: TextDecoration.none,
372 - ),
373 - )
374 - ),
375 - Padding(
376 - padding: EdgeInsets.only(top: 8),
377 - child: Row(
378 - mainAxisSize: MainAxisSize.max,
379 - mainAxisAlignment: MainAxisAlignment.end,
380 - children: [
381 - Text(
382 - _amount,
340 + return Column(
341 + children: [
342 + if (item.isParsedAddress)
343 + Padding(
344 + padding: EdgeInsets.only(top: 8),
345 + child: Text(
346 + item.parsedAddress.name,
347 + textAlign: TextAlign.center,
348 style: TextStyle(
384 - fontSize: 10,
349 + fontSize: 14,
350 fontWeight: FontWeight.w600,
351 fontFamily: 'Lato',
352 color: PaletteDark.pigeonBlue,
353 decoration: TextDecoration.none,
354 ),
390 - )
391 - ],
392 - )
393 - )
394 - ],
395 - );
396 - })
397 - : Column(
398 - children: [
399 - if (outputs.first.isParsedAddress) Padding(
400 - padding: EdgeInsets.only(top: 8),
401 - child: Text(
402 - outputs.first.parsedAddress.name,
403 - textAlign: TextAlign.center,
404 - style: TextStyle(
405 - fontSize: 14,
406 - fontWeight: FontWeight.w600,
407 - fontFamily: 'Lato',
408 - color: PaletteDark.pigeonBlue,
409 - decoration: TextDecoration.none,
410 - ),
411 - )
412 - ),
355 + )),
356 + Padding(
357 + padding: EdgeInsets.only(top: 8),
358 + child: Text(
359 + _address,
360 + style: TextStyle(
361 + fontSize: 10,
362 + fontWeight: FontWeight.w600,
363 + fontFamily: 'Lato',
364 + color: PaletteDark.pigeonBlue,
365 + decoration: TextDecoration.none,
366 + ),
367 + )),
368 + Padding(
369 + padding: EdgeInsets.only(top: 8),
370 + child: Row(
371 + mainAxisSize: MainAxisSize.max,
372 + mainAxisAlignment: MainAxisAlignment.end,
373 + children: [
374 + Text(
375 + _amount,
376 + style: TextStyle(
377 + fontSize: 10,
378 + fontWeight: FontWeight.w600,
379 + fontFamily: 'Lato',
380 + color: PaletteDark.pigeonBlue,
381 + decoration: TextDecoration.none,
382 + ),
383 + )
384 + ],
385 + ))
386 + ],
387 + );
388 + })
389 + : Column(children: [
390 + if (outputs.first.isParsedAddress)
391 + Padding(
392 + padding: EdgeInsets.only(top: 8),
393 + child: Text(
394 + outputs.first.parsedAddress.name,
395 + textAlign: TextAlign.center,
396 + style: TextStyle(
397 + fontSize: 14,
398 + fontWeight: FontWeight.w600,
399 + fontFamily: 'Lato',
400 + color: PaletteDark.pigeonBlue,
401 + decoration: TextDecoration.none,
402 + ),
403 + )),
404 Padding(
405 padding: EdgeInsets.only(top: 8),
406 child: Text(
@@ -423,24 +414,19 @@ class ConfirmSendingAlertContentState extends State<ConfirmSendingAlertContent>
414 color: PaletteDark.pigeonBlue,
415 decoration: TextDecoration.none,
416 ),
426 - )
427 - ),
428 - ]
429 - )
430 - ],
431 - ),
432 - )
433 - ],
434 - )
435 - )
436 - ),
437 - if (showScrollbar) CakeScrollbar(
438 - backgroundHeight: backgroundHeight,
439 - thumbHeight: thumbHeight,
440 - fromTop: fromTop,
441 - rightOffset: -15
442 - )
443 - ]
444 - );
417 + )),
418 + ])
419 + ],
420 + ),
421 + )
422 + ],
423 + ))),
424 + if (showScrollbar)
425 + CakeScrollbar(
426 + backgroundHeight: backgroundHeight,
427 + thumbHeight: thumbHeight,
428 + fromTop: fromTop,
429 + rightOffset: -15)
430 + ]);
431 }
446 -}
\ No newline at end of file
432 +}
lib/src/screens/send/widgets/send_card.dart
+98 -74
@@ -167,8 +167,10 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
167 AddressTextFieldOption.qrCode,
168 AddressTextFieldOption.addressBook
169 ],
170 - buttonColor: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
171 - borderColor: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
170 + buttonColor:
171 + Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
172 + borderColor:
173 + Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
174 textStyle: TextStyle(
175 fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
176 hintStyle: TextStyle(
@@ -196,7 +198,9 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
198 child: BaseTextFormField(
199 controller: extractedAddressController,
200 readOnly: true,
199 - borderColor: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
201 + borderColor: Theme.of(context)
202 + .extension<SendPageTheme>()!
203 + .textFieldBorderColor,
204 textStyle: TextStyle(
205 fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
206 validator: sendViewModel.addressValidator)),
@@ -251,7 +255,9 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
255 child: Container(
256 height: 32,
257 decoration: BoxDecoration(
254 - color: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
258 + color: Theme.of(context)
259 + .extension<SendPageTheme>()!
260 + .textFieldButtonColor,
261 borderRadius: BorderRadius.all(
262 Radius.circular(6),
263 )),
@@ -263,7 +269,9 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
269 style: TextStyle(
270 fontSize: 12,
271 fontWeight: FontWeight.bold,
266 - color: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonIconColor),
272 + color: Theme.of(context)
273 + .extension<SendPageTheme>()!
274 + .textFieldButtonIconColor),
275 ),
276 ),
277 ),
@@ -304,7 +312,9 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
312 fontWeight: FontWeight.w500,
313 color: Colors.white),
314 placeholderTextStyle: TextStyle(
307 - color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor,
315 + color: Theme.of(context)
316 + .extension<SendPageTheme>()!
317 + .textFieldHintColor,
318 fontWeight: FontWeight.w500,
319 fontSize: 14),
320 validator: output.sendAll
@@ -322,7 +332,9 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
332 onTap: () async => output.setSendAll(),
333 child: Container(
334 decoration: BoxDecoration(
325 - color: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonColor,
335 + color: Theme.of(context)
336 + .extension<SendPageTheme>()!
337 + .textFieldButtonColor,
338 borderRadius: BorderRadius.all(
339 Radius.circular(6),
340 ),
@@ -334,7 +346,9 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
346 style: TextStyle(
347 fontSize: 12,
348 fontWeight: FontWeight.bold,
337 - color: Theme.of(context).extension<SendPageTheme>()!.textFieldButtonIconColor,
349 + color: Theme.of(context)
350 + .extension<SendPageTheme>()!
351 + .textFieldButtonIconColor,
352 ),
353 ),
354 ),
@@ -364,7 +378,9 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
378 style: TextStyle(
379 fontSize: 12,
380 fontWeight: FontWeight.w600,
367 - color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor),
381 + color: Theme.of(context)
382 + .extension<SendPageTheme>()!
383 + .textFieldHintColor),
384 ),
385 ),
386 Text(
@@ -372,7 +388,9 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
388 style: TextStyle(
389 fontSize: 12,
390 fontWeight: FontWeight.w600,
375 - color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor),
391 + color: Theme.of(context)
392 + .extension<SendPageTheme>()!
393 + .textFieldHintColor),
394 )
395 ],
396 ),
@@ -403,11 +421,13 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
421 ),
422 ),
423 hintText: '0.00',
406 - borderColor: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
424 + borderColor:
425 + Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
426 textStyle: TextStyle(
427 fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
428 placeholderTextStyle: TextStyle(
410 - color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor,
429 + color:
430 + Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor,
431 fontWeight: FontWeight.w500,
432 fontSize: 14),
433 ),
@@ -418,7 +438,8 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
438 controller: noteController,
439 keyboardType: TextInputType.multiline,
440 maxLines: null,
421 - borderColor: Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
441 + borderColor:
442 + Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
443 textStyle: TextStyle(
444 fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
445 hintText: S.of(context).note_optional,
@@ -429,73 +450,76 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
450 Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor),
451 ),
452 ),
432 - Observer(
433 - builder: (_) => GestureDetector(
434 - onTap: () => _setTransactionPriority(context),
435 - child: Container(
436 - padding: EdgeInsets.only(top: 24),
437 - child: Row(
438 - mainAxisAlignment: MainAxisAlignment.spaceBetween,
439 - crossAxisAlignment: CrossAxisAlignment.start,
440 - children: <Widget>[
441 - Text(
442 - S.of(context).send_estimated_fee,
443 - style: TextStyle(
444 - fontSize: 12,
445 - fontWeight: FontWeight.w500,
446 - color: Colors.white),
447 - ),
448 - Container(
449 - child: Row(
450 - crossAxisAlignment: CrossAxisAlignment.start,
451 - children: <Widget>[
452 - Column(
453 - mainAxisAlignment: MainAxisAlignment.start,
454 - crossAxisAlignment: CrossAxisAlignment.end,
455 - children: [
456 - Text(
457 - output.estimatedFee.toString() +
458 - ' ' +
459 - sendViewModel.selectedCryptoCurrency.toString(),
460 - style: TextStyle(
461 - fontSize: 12,
462 - fontWeight: FontWeight.w600,
463 - color: Colors.white,
453 + if (sendViewModel.hasFees)
454 + Observer(
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: Colors.white),
469 + ),
470 + Container(
471 + child: Row(
472 + crossAxisAlignment: CrossAxisAlignment.start,
473 + children: <Widget>[
474 + Column(
475 + mainAxisAlignment: MainAxisAlignment.start,
476 + crossAxisAlignment: CrossAxisAlignment.end,
477 + children: [
478 + Text(
479 + output.estimatedFee.toString() +
480 + ' ' +
481 + sendViewModel.selectedCryptoCurrency.toString(),
482 + style: TextStyle(
483 + fontSize: 12,
484 + fontWeight: FontWeight.w600,
485 + color: Colors.white,
486 + ),
487 ),
465 - ),
466 - Padding(
467 - padding: EdgeInsets.only(top: 5),
468 - child: sendViewModel.isFiatDisabled
469 - ? const SizedBox(height: 14)
470 - : Text(
471 - output.estimatedFeeFiatAmount +
472 - ' ' +
473 - sendViewModel.fiat.title,
474 - style: TextStyle(
475 - fontSize: 12,
476 - fontWeight: FontWeight.w600,
477 - color: Theme.of(context).extension<SendPageTheme>()!.textFieldHintColor,
488 + Padding(
489 + padding: EdgeInsets.only(top: 5),
490 + child: sendViewModel.isFiatDisabled
491 + ? const SizedBox(height: 14)
492 + : Text(
493 + output.estimatedFeeFiatAmount +
494 + ' ' +
495 + sendViewModel.fiat.title,
496 + style: TextStyle(
497 + fontSize: 12,
498 + fontWeight: FontWeight.w600,
499 + color: Theme.of(context)
500 + .extension<SendPageTheme>()!
501 + .textFieldHintColor,
502 + ),
503 ),
479 - ),
480 - ),
481 - ],
482 - ),
483 - Padding(
484 - padding: EdgeInsets.only(top: 2, left: 5),
485 - child: Icon(
486 - Icons.arrow_forward_ios,
487 - size: 12,
488 - color: Colors.white,
504 + ),
505 + ],
506 ),
490 - )
491 - ],
492 - ),
493 - )
494 - ],
507 + Padding(
508 + padding: EdgeInsets.only(top: 2, left: 5),
509 + child: Icon(
510 + Icons.arrow_forward_ios,
511 + size: 12,
512 + color: Colors.white,
513 + ),
514 + )
515 + ],
516 + ),
517 + )
518 + ],
519 + ),
520 ),
521 ),
522 ),
498 - ),
523 if (sendViewModel.hasCoinControl)
524 Padding(
525 padding: EdgeInsets.only(top: 6),
lib/src/screens/settings/connection_sync_page.dart
+15
@@ -70,6 +70,21 @@ class ConnectionSyncPage extends BasePage {
70 handler: (context) => Navigator.of(context).pushNamed(Routes.manageNodes),
71 ),
72 const StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
73 + Observer(
74 + builder: (context) {
75 + if (!dashboardViewModel.hasPowNodes) return const SizedBox();
76 +
77 + return Column(
78 + children: [
79 + SettingsCellWithArrow(
80 + title: S.current.manage_pow_nodes,
81 + handler: (context) => Navigator.of(context).pushNamed(Routes.managePowNodes),
82 + ),
83 + const StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
84 + ],
85 + );
86 + },
87 + ),
88 if (dashboardViewModel.wallet.type == WalletType.ethereum) ...[
89 WalletConnectTile(
90 onTap: () async {
lib/src/screens/settings/manage_nodes_page.dart
+24 -7
@@ -6,13 +6,17 @@ import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
6 import 'package:cake_wallet/src/widgets/standard_list.dart';
7 import 'package:cake_wallet/utils/show_pop_up.dart';
8 import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart';
9 +import 'package:cake_wallet/view_model/node_list/pow_node_list_view_model.dart';
10 import 'package:flutter/material.dart';
11 import 'package:flutter_mobx/flutter_mobx.dart';
12
13 class ManageNodesPage extends BasePage {
13 - ManageNodesPage(this.nodeListViewModel);
14 + ManageNodesPage(this.isPow, {this.nodeListViewModel, this.powNodeListViewModel})
15 + : assert((isPow && powNodeListViewModel != null) || (!isPow && nodeListViewModel != null));
16
15 - final NodeListViewModel nodeListViewModel;
17 + final NodeListViewModel? nodeListViewModel;
18 + final PowNodeListViewModel? powNodeListViewModel;
19 + final bool isPow;
20
21 @override
22 String get title => S.current.manage_nodes;
@@ -34,7 +38,8 @@ class ManageNodesPage extends BasePage {
38 SizedBox(height: 20),
39 Observer(
40 builder: (BuildContext context) {
37 - int itemsCount = nodeListViewModel.nodes.length;
41 + int itemsCount =
42 + nodeListViewModel?.nodes.length ?? powNodeListViewModel!.nodes.length;
43 return Flexible(
44 child: SectionStandardList(
45 sectionCount: 1,
@@ -43,12 +48,19 @@ class ManageNodesPage extends BasePage {
48 itemBuilder: (_, index) {
49 return Observer(
50 builder: (context) {
46 - final node = nodeListViewModel.nodes[index];
47 - final isSelected = node.keyIndex == nodeListViewModel.currentNode.keyIndex;
51 + final node =
52 + nodeListViewModel?.nodes[index] ?? powNodeListViewModel!.nodes[index];
53 + late bool isSelected;
54 + if (isPow) {
55 + isSelected = node.keyIndex == powNodeListViewModel!.currentNode.keyIndex;
56 + } else {
57 + isSelected = node.keyIndex == nodeListViewModel!.currentNode.keyIndex;
58 + }
59 final nodeListRow = NodeListRow(
60 title: node.uriRaw,
61 node: node,
62 isSelected: isSelected,
63 + isPow: false,
64 onTap: (_) async {
65 if (isSelected) {
66 return;
@@ -59,12 +71,17 @@ class ManageNodesPage extends BasePage {
71 builder: (BuildContext context) {
72 return AlertWithTwoActions(
73 alertTitle: S.of(context).change_current_node_title,
62 - alertContent: nodeListViewModel.getAlertContent(node.uriRaw),
74 + alertContent: nodeListViewModel?.getAlertContent(node.uriRaw) ??
75 + powNodeListViewModel!.getAlertContent(node.uriRaw),
76 leftButtonText: S.of(context).cancel,
77 rightButtonText: S.of(context).change,
78 actionLeftButton: () => Navigator.of(context).pop(),
79 actionRightButton: () async {
67 - await nodeListViewModel.setAsCurrent(node);
80 + if (isPow) {
81 + await powNodeListViewModel!.setAsCurrent(node);
82 + } else {
83 + await nodeListViewModel!.setAsCurrent(node);
84 + }
85 Navigator.of(context).pop();
86 },
87 );
lib/src/screens/settings/other_settings_page.dart
+15 -8
@@ -28,13 +28,20 @@ class OtherSettingsPage extends BasePage {
28 padding: EdgeInsets.only(top: 10),
29 child: Column(
30 children: [
31 - SettingsPickerCell(
32 - title: S.current.settings_fee_priority,
33 - items: priorityForWalletType(_otherSettingsViewModel.walletType),
34 - displayItem: _otherSettingsViewModel.getDisplayPriority,
35 - selectedItem: _otherSettingsViewModel.transactionPriority,
36 - onItemSelected: _otherSettingsViewModel.onDisplayPrioritySelected,
37 - ),
31 + if (!_otherSettingsViewModel.changeRepresentativeEnabled)
32 + SettingsPickerCell(
33 + title: S.current.settings_fee_priority,
34 + items: priorityForWalletType(_otherSettingsViewModel.walletType),
35 + displayItem: _otherSettingsViewModel.getDisplayPriority,
36 + selectedItem: _otherSettingsViewModel.transactionPriority,
37 + onItemSelected: _otherSettingsViewModel.onDisplayPrioritySelected,
38 + ),
39 + if (_otherSettingsViewModel.changeRepresentativeEnabled)
40 + SettingsCellWithArrow(
41 + title: S.current.change_rep,
42 + handler: (BuildContext context) =>
43 + Navigator.of(context).pushNamed(Routes.changeRep),
44 + ),
45 SettingsPickerCell(
46 title: S.current.default_buy_provider,
47 items: BuyProviderType.values,
@@ -50,7 +57,7 @@ class OtherSettingsPage extends BasePage {
57 StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
58 Spacer(),
59 SettingsVersionCell(
53 - title: S.of(context).version(_otherSettingsViewModel.currentVersion))
60 + title: S.of(context).version(_otherSettingsViewModel.currentVersion)),
61 ],
62 ),
63 );
lib/src/screens/wallet_list/wallet_list_page.dart
+3
@@ -48,6 +48,7 @@ class WalletListBodyState extends State<WalletListBody> {
48 final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
49 final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24);
50 final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24);
51 + final nanoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24);
52 final scrollController = ScrollController();
53 final double tileHeight = 60;
54 Flushbar<void>? _progressBar;
@@ -242,6 +243,8 @@ class WalletListBodyState extends State<WalletListBody> {
243 return havenIcon;
244 case WalletType.ethereum:
245 return ethereumIcon;
246 + case WalletType.nano:
247 + return nanoIcon;
248 default:
249 return nonWalletTypeIcon;
250 }
lib/src/screens/welcome/welcome_page.dart
+1 -1
@@ -36,7 +36,7 @@ class WelcomePage extends BasePage {
36 return S.of(context).haven_app_wallet_text;
37 }
38
39 - return S.of(context).first_wallet_text;
39 + return S.of(context).new_first_wallet_text;
40 }
41
42 @override
lib/store/settings_store.dart
+55
@@ -64,6 +64,7 @@ abstract class SettingsStoreBase with Store {
64 required this.appVersion,
65 required this.deviceName,
66 required Map<WalletType, Node> nodes,
67 + required Map<WalletType, Node> powNodes,
68 required this.shouldShowYatPopup,
69 required this.isBitcoinBuyEnabled,
70 required this.actionlistDisplayMode,
@@ -86,6 +87,7 @@ abstract class SettingsStoreBase with Store {
87 TransactionPriority? initialLitecoinTransactionPriority,
88 TransactionPriority? initialEthereumTransactionPriority})
89 : nodes = ObservableMap<WalletType, Node>.of(nodes),
90 + powNodes = ObservableMap<WalletType, Node>.of(powNodes),
91 _sharedPreferences = sharedPreferences,
92 _backgroundTasks = backgroundTasks,
93 fiatCurrency = initialFiatCurrency,
@@ -347,6 +349,12 @@ abstract class SettingsStoreBase with Store {
349 _saveCurrentNode(change.newValue!, change.key!);
350 }
351 });
352 +
353 + this.powNodes.observe((change) {
354 + if (change.newValue != null && change.key != null) {
355 + _saveCurrentPowNode(change.newValue!, change.key!);
356 + }
357 + });
358 }
359
360 static const defaultPinLength = 4;
@@ -473,6 +481,7 @@ abstract class SettingsStoreBase with Store {
481 final BackgroundTasks _backgroundTasks;
482
483 ObservableMap<WalletType, Node> nodes;
484 + ObservableMap<WalletType, Node> powNodes;
485
486 Node getCurrentNode(WalletType walletType) {
487 final node = nodes[walletType];
@@ -484,6 +493,16 @@ abstract class SettingsStoreBase with Store {
493 return node;
494 }
495
496 + Node getCurrentPowNode(WalletType walletType) {
497 + final node = powNodes[walletType];
498 +
499 + if (node == null) {
500 + throw Exception('No pow node found for wallet type: ${walletType.toString()}');
501 + }
502 +
503 + return node;
504 + }
505 +
506 bool isBitcoinBuyEnabled;
507
508 bool get shouldShowReceiveWarning =>
@@ -494,6 +513,7 @@ abstract class SettingsStoreBase with Store {
513
514 static Future<SettingsStore> load(
515 {required Box<Node> nodeSource,
516 + required Box<Node> powNodeSource,
517 required bool isBitcoinBuyEnabled,
518 FiatCurrency initialFiatCurrency = FiatCurrency.usd,
519 BalanceDisplayMode initialBalanceDisplayMode = BalanceDisplayMode.availableBalance,
@@ -611,11 +631,15 @@ abstract class SettingsStoreBase with Store {
631 sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
632 final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey);
633 final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
634 + final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
635 + final nanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey);
636 final moneroNode = nodeSource.get(nodeId);
637 final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId);
638 final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
639 final havenNode = nodeSource.get(havenNodeId);
640 final ethereumNode = nodeSource.get(ethereumNodeId);
641 + final nanoNode = nodeSource.get(nanoNodeId);
642 + final nanoPowNode = powNodeSource.get(nanoPowNodeId);
643 final packageInfo = await PackageInfo.fromPlatform();
644 final deviceName = await _getDeviceName() ?? '';
645 final shouldShowYatPopup = sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? true;
@@ -626,6 +650,7 @@ abstract class SettingsStoreBase with Store {
650 ? AutoGenerateSubaddressStatus.deserialize(raw: generateSubaddresses)
651 : defaultAutoGenerateSubaddressStatus;
652 final nodes = <WalletType, Node>{};
653 + final powNodes = <WalletType, Node>{};
654
655 if (moneroNode != null) {
656 nodes[WalletType.monero] = moneroNode;
@@ -647,6 +672,13 @@ abstract class SettingsStoreBase with Store {
672 nodes[WalletType.ethereum] = ethereumNode;
673 }
674
675 + if (nanoNode != null) {
676 + nodes[WalletType.nano] = nanoNode;
677 + }
678 + if (nanoPowNode != null) {
679 + powNodes[WalletType.nano] = nanoPowNode;
680 + }
681 +
682 final savedSyncMode = SyncMode.all.firstWhere((element) {
683 return element.type.index == (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 1);
684 });
@@ -656,6 +688,7 @@ abstract class SettingsStoreBase with Store {
688 sharedPreferences: sharedPreferences,
689 initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard,
690 nodes: nodes,
691 + powNodes: powNodes,
692 appVersion: packageInfo.version,
693 deviceName: deviceName,
694 isBitcoinBuyEnabled: isBitcoinBuyEnabled,
@@ -818,11 +851,14 @@ abstract class SettingsStoreBase with Store {
851 sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey);
852 final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey);
853 final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey);
854 + final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
855 + final nanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
856 final moneroNode = nodeSource.get(nodeId);
857 final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId);
858 final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
859 final havenNode = nodeSource.get(havenNodeId);
860 final ethereumNode = nodeSource.get(ethereumNodeId);
861 + final nanoNode = nodeSource.get(nanoNodeId);
862
863 if (moneroNode != null) {
864 nodes[WalletType.monero] = moneroNode;
@@ -843,6 +879,10 @@ abstract class SettingsStoreBase with Store {
879 if (ethereumNode != null) {
880 nodes[WalletType.ethereum] = ethereumNode;
881 }
882 +
883 + if (nanoNode != null) {
884 + nodes[WalletType.nano] = nanoNode;
885 + }
886 }
887
888 Future<void> _saveCurrentNode(Node node, WalletType walletType) async {
@@ -864,6 +904,9 @@ abstract class SettingsStoreBase with Store {
904 case WalletType.ethereum:
905 await _sharedPreferences.setInt(PreferencesKey.currentEthereumNodeIdKey, node.key as int);
906 break;
907 + case WalletType.nano:
908 + await _sharedPreferences.setInt(PreferencesKey.currentNanoNodeIdKey, node.key as int);
909 + break;
910 default:
911 break;
912 }
@@ -871,6 +914,18 @@ abstract class SettingsStoreBase with Store {
914 nodes[walletType] = node;
915 }
916
917 + Future<void> _saveCurrentPowNode(Node node, WalletType walletType) async {
918 + switch (walletType) {
919 + case WalletType.nano:
920 + await _sharedPreferences.setInt(PreferencesKey.currentNanoPowNodeIdKey, node.key as int);
921 + break;
922 + default:
923 + break;
924 + }
925 +
926 + powNodes[walletType] = node;
927 + }
928 +
929 static Future<String?> _getDeviceName() async {
930 String? deviceName = '';
931 final deviceInfoPlugin = DeviceInfoPlugin();
lib/utils/payment_request.dart
+12 -3
@@ -1,3 +1,5 @@
1 +import 'package:cake_wallet/nano/nano.dart';
2 +
3 class PaymentRequest {
4 PaymentRequest(this.address, this.amount, this.note, this.scheme);
5
@@ -10,11 +12,18 @@ class PaymentRequest {
12 if (uri != null) {
13 address = uri.path;
14 amount = uri.queryParameters['tx_amount'] ?? uri.queryParameters['amount'] ?? "";
13 - note = uri.queryParameters['tx_description']
14 - ?? uri.queryParameters['message'] ?? "";
15 + note = uri.queryParameters['tx_description'] ?? uri.queryParameters['message'] ?? "";
16 scheme = uri.scheme;
17 }
18
19 + if (nano != null) {
20 + if (address.contains("nano")) {
21 + amount = nanoUtil!.getRawAsUsableString(amount, nanoUtil!.rawPerNano);
22 + } else if (address.contains("ban")) {
23 + amount = nanoUtil!.getRawAsUsableString(amount, nanoUtil!.rawPerBanano);
24 + }
25 + }
26 +
27 return PaymentRequest(address, amount, note, scheme);
28 }
29
@@ -22,4 +31,4 @@ class PaymentRequest {
31 final String amount;
32 final String note;
33 final String scheme;
25 -}
\ No newline at end of file
34 +}
lib/view_model/contact_list/contact_list_view_model.dart
+13 -10
@@ -15,12 +15,11 @@ import 'package:collection/collection.dart';
15
16 part 'contact_list_view_model.g.dart';
17
18 -class ContactListViewModel = ContactListViewModelBase
19 - with _$ContactListViewModel;
18 +class ContactListViewModel = ContactListViewModelBase with _$ContactListViewModel;
19
20 abstract class ContactListViewModelBase with Store {
22 - ContactListViewModelBase(this.contactSource, this.walletInfoSource,
23 - this._currency, this.settingsStore)
21 + ContactListViewModelBase(
22 + this.contactSource, this.walletInfoSource, this._currency, this.settingsStore)
23 : contacts = ObservableList<ContactRecord>(),
24 walletContacts = [],
25 isAutoGenerateEnabled =
@@ -48,6 +47,12 @@ abstract class ContactListViewModelBase with Store {
47 walletTypeToCryptoCurrency(info.type),
48 ));
49 });
50 + } else if (info.address != null) {
51 + walletContacts.add(WalletContact(
52 + info.address,
53 + info.name,
54 + walletTypeToCryptoCurrency(info.type),
55 + ));
56 }
57 });
58
@@ -78,14 +83,12 @@ abstract class ContactListViewModelBase with Store {
83 Future<void> delete(ContactRecord contact) async => contact.original.delete();
84
85 @computed
81 - List<ContactRecord> get contactsToShow => contacts
82 - .where((element) => _isValidForCurrency(element))
83 - .toList();
86 + List<ContactRecord> get contactsToShow =>
87 + contacts.where((element) => _isValidForCurrency(element)).toList();
88
89 @computed
86 - List<WalletContact> get walletContactsToShow => walletContacts
87 - .where((element) => _isValidForCurrency(element))
88 - .toList();
90 + List<WalletContact> get walletContactsToShow =>
91 + walletContacts.where((element) => _isValidForCurrency(element)).toList();
92
93 bool _isValidForCurrency(ContactBase element) {
94 return _currency == null || element.type == _currency || element.type.title == _currency!.tag;
lib/view_model/dashboard/dashboard_view_model.dart
+74 -70
@@ -47,74 +47,71 @@ abstract class DashboardViewModelBase with Store {
47 required this.yatStore,
48 required this.ordersStore,
49 required this.anonpayTransactionsStore})
50 - : isOutdatedElectrumWallet = false,
51 - hasSellAction = false,
52 - hasBuyAction = false,
53 - hasExchangeAction = false,
54 - isShowFirstYatIntroduction = false,
55 - isShowSecondYatIntroduction = false,
56 - isShowThirdYatIntroduction = false,
57 - filterItems = {
58 - S.current.transactions: [
59 - FilterItem(
60 - value: () => transactionFilterStore.displayAll,
61 - caption: S.current.all_transactions,
62 - onChanged: transactionFilterStore.toggleAll),
63 - FilterItem(
64 - value: () => transactionFilterStore.displayIncoming,
65 - caption: S.current.incoming,
66 - onChanged: transactionFilterStore.toggleIncoming),
67 - FilterItem(
68 - value: () => transactionFilterStore.displayOutgoing,
69 - caption: S.current.outgoing,
70 - onChanged: transactionFilterStore.toggleOutgoing),
71 - // FilterItem(
72 - // value: () => false,
73 - // caption: S.current.transactions_by_date,
74 - // onChanged: null),
75 - ],
76 - S.current.trades: [
77 - FilterItem(
78 - value: () => tradeFilterStore.displayAllTrades,
79 - caption: S.current.all_trades,
80 - onChanged: () =>
81 - tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.all)),
82 - FilterItem(
83 - value: () => tradeFilterStore.displayChangeNow,
84 - caption: ExchangeProviderDescription.changeNow.title,
85 - onChanged: () =>
86 - tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.changeNow)),
87 - FilterItem(
88 - value: () => tradeFilterStore.displaySideShift,
89 - caption: ExchangeProviderDescription.sideShift.title,
90 - onChanged: () =>
91 - tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.sideShift)),
92 - FilterItem(
93 - value: () => tradeFilterStore.displaySimpleSwap,
94 - caption: ExchangeProviderDescription.simpleSwap.title,
95 - onChanged: () =>
96 - tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.simpleSwap)),
97 - FilterItem(
98 - value: () => tradeFilterStore.displayTrocador,
99 - caption: ExchangeProviderDescription.trocador.title,
100 - onChanged: () =>
101 - tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.trocador)),
102 - FilterItem(
103 - value: () => tradeFilterStore.displayExolix,
104 - caption: ExchangeProviderDescription.exolix.title,
105 - onChanged: () =>
106 - tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.exolix)),
107 - ]
108 - },
109 - subname = '',
110 - name = appStore.wallet!.name,
111 - type = appStore.wallet!.type,
112 - transactions = ObservableList<TransactionListItem>(),
113 - wallet = appStore.wallet! {
50 + : hasSellAction = false,
51 + hasBuyAction = false,
52 + hasExchangeAction = false,
53 + isShowFirstYatIntroduction = false,
54 + isShowSecondYatIntroduction = false,
55 + isShowThirdYatIntroduction = false,
56 + filterItems = {
57 + S.current.transactions: [
58 + FilterItem(
59 + value: () => transactionFilterStore.displayAll,
60 + caption: S.current.all_transactions,
61 + onChanged: transactionFilterStore.toggleAll),
62 + FilterItem(
63 + value: () => transactionFilterStore.displayIncoming,
64 + caption: S.current.incoming,
65 + onChanged:transactionFilterStore.toggleIncoming),
66 + FilterItem(
67 + value: () => transactionFilterStore.displayOutgoing,
68 + caption: S.current.outgoing,
69 + onChanged: transactionFilterStore.toggleOutgoing),
70 + // FilterItem(
71 + // value: () => false,
72 + // caption: S.current.transactions_by_date,
73 + // onChanged: null),
74 + ],
75 + S.current.trades: [
76 + FilterItem(
77 + value: () => tradeFilterStore.displayAllTrades,
78 + caption: S.current.all_trades,
79 + onChanged: () => tradeFilterStore
80 + .toggleDisplayExchange(ExchangeProviderDescription.all)),
81 + FilterItem(
82 + value: () => tradeFilterStore.displayChangeNow,
83 + caption: ExchangeProviderDescription.changeNow.title,
84 + onChanged: () => tradeFilterStore
85 + .toggleDisplayExchange(ExchangeProviderDescription.changeNow)),
86 + FilterItem(
87 + value: () => tradeFilterStore.displaySideShift,
88 + caption: ExchangeProviderDescription.sideShift.title,
89 + onChanged: () => tradeFilterStore
90 + .toggleDisplayExchange(ExchangeProviderDescription.sideShift)),
91 + FilterItem(
92 + value: () => tradeFilterStore.displaySimpleSwap,
93 + caption: ExchangeProviderDescription.simpleSwap.title,
94 + onChanged: () => tradeFilterStore
95 + .toggleDisplayExchange(ExchangeProviderDescription.simpleSwap)),
96 + FilterItem(
97 + value: () => tradeFilterStore.displayTrocador,
98 + caption: ExchangeProviderDescription.trocador.title,
99 + onChanged: () => tradeFilterStore
100 + .toggleDisplayExchange(ExchangeProviderDescription.trocador)),
101 + FilterItem(
102 + value: () => tradeFilterStore.displayExolix,
103 + caption: ExchangeProviderDescription.exolix.title,
104 + onChanged: () => tradeFilterStore
105 + .toggleDisplayExchange(ExchangeProviderDescription.exolix)),
106 + ]
107 + },
108 + subname = '',
109 + name = appStore.wallet!.name,
110 + type = appStore.wallet!.type,
111 + transactions = ObservableList<TransactionListItem>(),
112 + wallet = appStore.wallet! {
113 name = wallet.name;
114 type = wallet.type;
116 - isOutdatedElectrumWallet =
117 - wallet.type == WalletType.bitcoin && wallet.seed!.split(' ').length < 24;
115 isShowFirstYatIntroduction = false;
116 isShowSecondYatIntroduction = false;
117 isShowThirdYatIntroduction = false;
@@ -151,6 +148,11 @@ abstract class DashboardViewModelBase with Store {
148 settingsStore: appStore.settingsStore)));
149 }
150
151 + // TODO: nano sub-account generation is disabled:
152 + // if (_wallet.type == WalletType.nano || _wallet.type == WalletType.banano) {
153 + // subname = nano!.getCurrentAccount(_wallet).label;
154 + // }
155 +
156 reaction((_) => appStore.wallet, _onWalletChange);
157
158 connectMapToListWithTransform(
@@ -315,12 +317,16 @@ abstract class DashboardViewModelBase with Store {
317
318 ReactionDisposer? _onMoneroBalanceChangeReaction;
319
318 - @observable
319 - bool isOutdatedElectrumWallet;
320 + @computed
321 + bool get hasPowNodes => wallet.type == WalletType.nano || wallet.type == WalletType.banano;
322
323 Future<void> reconnect() async {
324 final node = appStore.settingsStore.getCurrentNode(wallet.type);
325 await wallet.connectToNode(node: node);
326 + if (hasPowNodes) {
327 + final powNode = settingsStore.getCurrentPowNode(wallet.type);
328 + await wallet.connectToPowNode(node: powNode);
329 + }
330 }
331
332 @action
@@ -333,8 +339,6 @@ abstract class DashboardViewModelBase with Store {
339 this.wallet = wallet;
340 type = wallet.type;
341 name = wallet.name;
336 - isOutdatedElectrumWallet =
337 - wallet.type == WalletType.bitcoin && wallet.seed!.split(' ').length < 24;
342 updateActions();
343
344 if (wallet.type == WalletType.monero) {
lib/view_model/dashboard/transaction_list_item.dart
+28 -16
@@ -2,6 +2,7 @@ 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:cake_wallet/nano/nano.dart';
6 import 'package:cw_core/transaction_direction.dart';
7 import 'package:cw_core/transaction_info.dart';
8 import 'package:cake_wallet/store/settings_store.dart';
@@ -16,9 +17,7 @@ import 'package:cw_core/wallet_type.dart';
17
18 class TransactionListItem extends ActionListItem with Keyable {
19 TransactionListItem(
19 - {required this.transaction,
20 - required this.balanceViewModel,
21 - required this.settingsStore});
20 + {required this.transaction, required this.balanceViewModel, required this.settingsStore});
21
22 final TransactionInfo transaction;
23 final BalanceViewModel balanceViewModel;
@@ -34,10 +33,9 @@ class TransactionListItem extends ActionListItem with Keyable {
33 dynamic get keyIndex => transaction.id;
34
35 String get formattedCryptoAmount {
37 - return displayMode == BalanceDisplayMode.hiddenBalance
38 - ? '---'
39 - : transaction.amountFormatted();
36 + return displayMode == BalanceDisplayMode.hiddenBalance ? '---' : transaction.amountFormatted();
37 }
38 +
39 String get formattedTitle {
40 if (transaction.direction == TransactionDirection.incoming) {
41 return S.current.received;
@@ -57,33 +55,47 @@ class TransactionListItem extends ActionListItem with Keyable {
55 if (transaction.direction == TransactionDirection.incoming) {
56 if (balanceViewModel.wallet.type == WalletType.monero ||
57 balanceViewModel.wallet.type == WalletType.haven) {
60 - return formattedPendingStatus;
61 - }
58 + return formattedPendingStatus;
59 }
63 - return transaction.isPending ? S.current.pending : '';
60 }
61 + return transaction.isPending ? S.current.pending : '';
62 + }
63
64 String get formattedFiatAmount {
65 var amount = '';
66
69 - switch(balanceViewModel.wallet.type) {
67 + switch (balanceViewModel.wallet.type) {
68 case WalletType.monero:
69 amount = calculateFiatAmountRaw(
72 - cryptoAmount: monero!.formatterMoneroAmountToDouble(amount: transaction.amount),
73 - price: price);
70 + cryptoAmount: monero!.formatterMoneroAmountToDouble(amount: transaction.amount),
71 + price: price);
72 break;
73 case WalletType.bitcoin:
74 case WalletType.litecoin:
75 amount = calculateFiatAmountRaw(
78 - cryptoAmount: bitcoin!.formatterBitcoinAmountToDouble(amount: transaction.amount),
79 - price: price);
76 + cryptoAmount: bitcoin!.formatterBitcoinAmountToDouble(amount: transaction.amount),
77 + price: price);
78 break;
79 case WalletType.haven:
80 final asset = haven!.assetOfTransaction(transaction);
81 final price = balanceViewModel.fiatConvertationStore.prices[asset];
82 amount = calculateFiatAmountRaw(
85 - cryptoAmount: haven!.formatterMoneroAmountToDouble(amount: transaction.amount),
86 - price: price);
83 + cryptoAmount: haven!.formatterMoneroAmountToDouble(amount: transaction.amount),
84 + price: price);
85 + break;
86 + case WalletType.ethereum:
87 + final asset = ethereum!.assetOfTransaction(balanceViewModel.wallet, transaction);
88 + final price = balanceViewModel.fiatConvertationStore.prices[asset];
89 + amount = calculateFiatAmountRaw(
90 + cryptoAmount: ethereum!.formatterEthereumAmountToDouble(transaction: transaction),
91 + price: price);
92 + break;
93 + case WalletType.nano:
94 + amount = calculateFiatAmountRaw(
95 + cryptoAmount: nanoUtil!
96 + .getRawAsDecimal(nano!.getTransactionAmountRaw(transaction).toString(), nanoUtil!.rawPerNano)
97 + .toDouble(),
98 + price: price);
99 break;
100 case WalletType.ethereum:
101 final asset = ethereum!.assetOfTransaction(balanceViewModel.wallet, transaction);
lib/view_model/exchange/exchange_view_model.dart
+6 -3
@@ -86,13 +86,12 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
86 super(appStore: appStore) {
87 _useTorOnly = _settingsStore.exchangeStatus == ExchangeApiMode.torOnly;
88 _setProviders();
89 - const excludeDepositCurrencies = [CryptoCurrency.btt, CryptoCurrency.nano];
89 + const excludeDepositCurrencies = [CryptoCurrency.btt];
90 const excludeReceiveCurrencies = [
91 CryptoCurrency.xlm,
92 CryptoCurrency.xrp,
93 CryptoCurrency.bnb,
94 - CryptoCurrency.btt,
95 - CryptoCurrency.nano
94 + CryptoCurrency.btt
95 ];
96 _initialPairBasedOnWallet();
97
@@ -703,6 +702,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
702 depositCurrency = CryptoCurrency.eth;
703 receiveCurrency = CryptoCurrency.xmr;
704 break;
705 + case WalletType.nano:
706 + depositCurrency = CryptoCurrency.nano;
707 + receiveCurrency = CryptoCurrency.xmr;
708 + break;
709 default:
710 break;
711 }
lib/view_model/nano_account_list/nano_account_edit_or_create_view_model.dart new
+58
@@ -0,0 +1,58 @@
1 +import 'package:cake_wallet/nano/nano.dart';
2 +import 'package:cw_core/nano_account.dart';
3 +import 'package:cw_core/wallet_base.dart';
4 +import 'package:cw_core/wallet_type.dart';
5 +
6 +import 'package:mobx/mobx.dart';
7 +import 'package:cake_wallet/core/execution_state.dart';
8 +
9 +part 'nano_account_edit_or_create_view_model.g.dart';
10 +
11 +class NanoAccountEditOrCreateViewModel = NanoAccountEditOrCreateViewModelBase
12 + with _$NanoAccountEditOrCreateViewModel;
13 +
14 +abstract class NanoAccountEditOrCreateViewModelBase with Store {
15 + NanoAccountEditOrCreateViewModelBase(this._nanoAccountList,
16 + {required WalletBase wallet, NanoAccount? accountListItem})
17 + : state = InitialExecutionState(),
18 + isEdit = accountListItem != null,
19 + label = accountListItem?.label ?? '',
20 + _accountListItem = accountListItem,
21 + _wallet = wallet;
22 +
23 + final bool isEdit;
24 +
25 + @observable
26 + ExecutionState state;
27 +
28 + @observable
29 + String label;
30 +
31 + final NanoAccountList _nanoAccountList;
32 + final NanoAccount? _accountListItem;
33 + final WalletBase _wallet;
34 +
35 + Future<void> save() async {
36 + if (_wallet.type == WalletType.nano) {
37 + await saveNano();
38 + }
39 + }
40 +
41 + Future<void> saveNano() async {
42 + try {
43 + state = IsExecutingState();
44 +
45 + if (_accountListItem != null) {
46 + await _nanoAccountList.setLabelAccount(_wallet,
47 + accountIndex: _accountListItem!.id, label: label);
48 + } else {
49 + await _nanoAccountList.addAccount(_wallet, label: label);
50 + }
51 +
52 + await _wallet.save();
53 + state = ExecutedSuccessfullyState();
54 + } catch (e) {
55 + state = FailureState(e.toString());
56 + }
57 + }
58 +}
lib/view_model/nano_account_list/nano_account_list_view_model.dart new
+54
@@ -0,0 +1,54 @@
1 +import 'package:cake_wallet/nano/nano.dart';
2 +import 'package:cw_core/crypto_currency.dart';
3 +import 'package:cw_core/wallet_type.dart';
4 +import 'package:cw_core/nano_account.dart';
5 +import 'package:mobx/mobx.dart';
6 +import 'package:cw_core/wallet_base.dart';
7 +
8 +part 'nano_account_list_view_model.g.dart';
9 +
10 +class NanoAccountListViewModel = NanoAccountListViewModelBase with _$NanoAccountListViewModel;
11 +
12 +abstract class NanoAccountListViewModelBase with Store {
13 + NanoAccountListViewModelBase(this._wallet) : scrollOffsetFromTop = 0;
14 +
15 + @observable
16 + double scrollOffsetFromTop;
17 +
18 + @action
19 + void setScrollOffsetFromTop(double scrollOffsetFromTop) {
20 + this.scrollOffsetFromTop = scrollOffsetFromTop;
21 + }
22 +
23 + CryptoCurrency get currency => _wallet.currency;
24 +
25 + @computed
26 + List<NanoAccount> get accounts {
27 + if (_wallet.type == WalletType.nano) {
28 + return nano!
29 + .getAccountList(_wallet)
30 + .accounts
31 + .map((acc) => NanoAccount(
32 + label: acc.label,
33 + id: acc.id,
34 + isSelected: acc.id == nano?.getCurrentAccount(_wallet).id,
35 + ))
36 + .toList();
37 + }
38 +
39 + throw Exception('Unexpected wallet type: ${_wallet.type}');
40 + }
41 +
42 + final WalletBase _wallet;
43 +
44 + void select(NanoAccount item) {
45 + if (_wallet.type == WalletType.nano) {
46 + nano!.setCurrentAccount(
47 + _wallet,
48 + item.id,
49 + item.label,
50 + item.balance,
51 + );
52 + }
53 + }
54 +}
lib/view_model/node_list/node_list_view_model.dart
+3
@@ -66,6 +66,9 @@ abstract class NodeListViewModelBase with Store {
66 case WalletType.ethereum:
67 node = getEthereumDefaultNode(nodes: _nodeSource)!;
68 break;
69 + case WalletType.nano:
70 + node = getNanoDefaultNode(nodes: _nodeSource)!;
71 + break;
72 default:
73 throw Exception('Unexpected wallet type: ${_appStore.wallet!.type}');
74 }
lib/view_model/node_list/pow_node_list_view_model.dart new
+79
@@ -0,0 +1,79 @@
1 +import 'package:cake_wallet/generated/i18n.dart';
2 +import 'package:cake_wallet/store/app_store.dart';
3 +import 'package:cake_wallet/utils/mobx.dart';
4 +import 'package:hive/hive.dart';
5 +import 'package:mobx/mobx.dart';
6 +import 'package:cw_core/wallet_base.dart';
7 +import 'package:cake_wallet/store/settings_store.dart';
8 +import 'package:cw_core/node.dart';
9 +import 'package:cake_wallet/entities/node_list.dart';
10 +import 'package:cake_wallet/entities/default_settings_migration.dart';
11 +import 'package:cw_core/wallet_type.dart';
12 +
13 +part 'pow_node_list_view_model.g.dart';
14 +
15 +class PowNodeListViewModel = PowNodeListViewModelBase with _$PowNodeListViewModel;
16 +
17 +abstract class PowNodeListViewModelBase with Store {
18 + PowNodeListViewModelBase(this._nodeSource, this._appStore)
19 + : nodes = ObservableList<Node>(),
20 + settingsStore = _appStore.settingsStore {
21 + _bindNodes();
22 +
23 + reaction((_) => _appStore.wallet, (WalletBase? _wallet) {
24 + _bindNodes();
25 + });
26 + }
27 +
28 + @computed
29 + Node get currentNode {
30 + final node = settingsStore.powNodes[_appStore.wallet!.type];
31 +
32 + if (node == null) {
33 + throw Exception('No node for wallet type: ${_appStore.wallet!.type}');
34 + }
35 +
36 + return node;
37 + }
38 +
39 + String getAlertContent(String uri) =>
40 + S.current.change_current_node(uri) +
41 + '${uri.endsWith('.onion') || uri.contains('.onion:') ? '\n' + S.current.orbot_running_alert : ''}';
42 +
43 + final ObservableList<Node> nodes;
44 + final SettingsStore settingsStore;
45 + final Box<Node> _nodeSource;
46 + final AppStore _appStore;
47 +
48 + Future<void> reset() async {
49 + await resetPowToDefault(_nodeSource);
50 +
51 + Node node;
52 +
53 + switch (_appStore.wallet!.type) {
54 + case WalletType.nano:
55 + node = getNanoDefaultPowNode(nodes: _nodeSource)!;
56 + break;
57 + default:
58 + throw Exception('Unexpected wallet type: ${_appStore.wallet!.type}');
59 + }
60 +
61 + await setAsCurrent(node);
62 + }
63 +
64 + @action
65 + Future<void> delete(Node node) async => node.delete();
66 +
67 + Future<void> setAsCurrent(Node node) async =>
68 + settingsStore.powNodes[_appStore.wallet!.type] = node;
69 +
70 + @action
71 + void _bindNodes() {
72 + nodes.clear();
73 + _nodeSource.bindToList(
74 + nodes,
75 + filter: (val) => val.type == _appStore.wallet!.type,
76 + initialFire: true,
77 + );
78 + }
79 +}
lib/view_model/send/send_view_model.dart
+26 -13
@@ -1,10 +1,13 @@
1 -import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
2 -import 'package:cake_wallet/entities/contact_record.dart';
1 +import 'package:cake_wallet/di.dart';
2 import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
3 import 'package:cake_wallet/entities/transaction_description.dart';
4 +import 'package:cake_wallet/ethereum/ethereum.dart';
5 +import 'package:cake_wallet/nano/nano.dart';
6 +import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
7 +import 'package:cake_wallet/entities/contact_record.dart';
8 import 'package:cake_wallet/entities/wallet_contact.dart';
9 +import 'package:cake_wallet/store/app_store.dart';
10 import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
7 -import 'package:cake_wallet/ethereum/ethereum.dart';
11 import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
12 import 'package:cw_core/transaction_priority.dart';
13 import 'package:cake_wallet/view_model/send/output.dart';
@@ -16,7 +19,6 @@ import 'package:cake_wallet/core/address_validator.dart';
19 import 'package:cake_wallet/core/amount_validator.dart';
20 import 'package:cw_core/pending_transaction.dart';
21 import 'package:cake_wallet/core/validator.dart';
19 -import 'package:cake_wallet/store/app_store.dart';
22 import 'package:cake_wallet/core/execution_state.dart';
23 import 'package:cake_wallet/monero/monero.dart';
24 import 'package:cw_core/sync_status.dart';
@@ -62,7 +64,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
64 final priority = _settingsStore.priority[wallet.type];
65 final priorities = priorityForWalletType(wallet.type);
66
65 - if (!priorityForWalletType(wallet.type).contains(priority)) {
67 + if (!priorityForWalletType(wallet.type).contains(priority) && priorities.isNotEmpty) {
68 _settingsStore.priority[wallet.type] = priorities.first;
69 }
70
@@ -99,15 +101,15 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
101
102 @computed
103 String get pendingTransactionFiatAmount {
104 + if (pendingTransaction == null) {
105 + return '0.00';
106 + }
107 +
108 try {
103 - if (pendingTransaction != null) {
104 - final fiat = calculateFiatAmount(
105 - price: _fiatConversationStore.prices[selectedCryptoCurrency]!,
106 - cryptoAmount: pendingTransaction!.amountFormatted);
107 - return fiat;
108 - } else {
109 - return '0.00';
110 - }
109 + final fiat = calculateFiatAmount(
110 + price: _fiatConversationStore.prices[selectedCryptoCurrency]!,
111 + cryptoAmount: pendingTransaction!.amountFormatted);
112 + return fiat;
113 } catch (_) {
114 return '0.00';
115 }
@@ -191,6 +193,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
193 bool get isElectrumWallet =>
194 wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin;
195
196 + @computed
197 + bool get hasFees => wallet.type != WalletType.nano && wallet.type != WalletType.banano;
198 +
199 @observable
200 CryptoCurrency selectedCryptoCurrency;
201
@@ -316,6 +321,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
321 state = TransactionCommitting();
322 await pendingTransaction!.commit();
323
324 + if (walletType == WalletType.nano) {
325 + nano!.updateTransactions(wallet);
326 + }
327 +
328 if (pendingTransaction!.id.isNotEmpty) {
329 _settingsStore.shouldSaveRecipientAddress
330 ? await transactionDescriptionBox.add(TransactionDescription(
@@ -380,6 +389,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
389
390 return ethereum!.createEthereumTransactionCredentials(outputs,
391 priority: priority, currency: selectedCryptoCurrency);
392 + case WalletType.nano:
393 + return nano!.createNanoTransactionCredentials(
394 + outputs,
395 + );
396 default:
397 throw Exception('Unexpected wallet type: ${wallet.type}');
398 }
lib/view_model/settings/other_settings_view_model.dart
+9 -1
@@ -25,7 +25,7 @@ abstract class OtherSettingsViewModelBase with Store {
25 final priority = _settingsStore.priority[_wallet.type];
26 final priorities = priorityForWalletType(_wallet.type);
27
28 - if (!priorities.contains(priority)) {
28 + if (!priorities.contains(priority) && priorities.isNotEmpty) {
29 _settingsStore.priority[_wallet.type] = priorities.first;
30 }
31 }
@@ -50,6 +50,14 @@ abstract class OtherSettingsViewModelBase with Store {
50 }
51
52 @computed
53 + bool get changeRepresentativeEnabled {
54 + if (_wallet.type == WalletType.nano || _wallet.type == WalletType.banano) {
55 + return true;
56 + }
57 +
58 + return false;
59 + }
60 +
61 BuyProviderType get buyProviderType { return _settingsStore.defaultBuyProvider; }
62
63 String getDisplayPriority(dynamic priority) {
lib/view_model/transaction_details_view_model.dart
+25
@@ -47,6 +47,9 @@ abstract class TransactionDetailsViewModelBase with Store {
47 case WalletType.ethereum:
48 _addEthereumListItems(tx, dateFormat);
49 break;
50 + case WalletType.nano:
51 + _addNanoListItems(tx, dateFormat);
52 + break;
53 default:
54 break;
55 }
@@ -116,6 +119,10 @@ abstract class TransactionDetailsViewModelBase with Store {
119 return 'https://explorer.havenprotocol.org/search?value=${txId}';
120 case WalletType.ethereum:
121 return 'https://etherscan.io/tx/${txId}';
122 + case WalletType.nano:
123 + return 'https://nanolooker.com/block/${txId}';
124 + case WalletType.banano:
125 + return 'https://bananolooker.com/block/${txId}';
126 default:
127 return '';
128 }
@@ -133,6 +140,10 @@ abstract class TransactionDetailsViewModelBase with Store {
140 return S.current.view_transaction_on + 'explorer.havenprotocol.org';
141 case WalletType.ethereum:
142 return S.current.view_transaction_on + 'etherscan.io';
143 + case WalletType.nano:
144 + return S.current.view_transaction_on + 'nanolooker.com';
145 + case WalletType.banano:
146 + return S.current.view_transaction_on + 'bananolooker.com';
147 default:
148 return '';
149 }
@@ -221,4 +232,18 @@ abstract class TransactionDetailsViewModelBase with Store {
232
233 items.addAll(_items);
234 }
235 +
236 +
237 + void _addNanoListItems(TransactionInfo tx, DateFormat dateFormat) {
238 + final _items = [
239 + StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id),
240 + StandartListItem(
241 + title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
242 + StandartListItem(title: S.current.confirmations, value: (tx.confirmations > 0).toString()),
243 + StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),
244 + StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
245 + ];
246 +
247 + items.addAll(_items);
248 + }
249 }
lib/view_model/wallet_address_list/wallet_address_list_view_model.dart
+32 -3
@@ -107,6 +107,22 @@ class EthereumURI extends PaymentURI {
107 }
108 }
109
110 +class NanoURI extends PaymentURI {
111 + NanoURI({required String amount, required String address})
112 + : super(amount: amount, address: address);
113 +
114 + @override
115 + String toString() {
116 + var base = 'nano:' + address;
117 +
118 + if (amount.isNotEmpty) {
119 + base += '?amount=${amount.replaceAll(',', '.')}';
120 + }
121 +
122 + return base;
123 + }
124 +}
125 +
126 abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewModel with Store {
127 WalletAddressListViewModelBase({
128 required AppStore appStore,
@@ -176,6 +192,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
192 return EthereumURI(amount: amount, address: address.address);
193 }
194
195 + if (wallet.type == WalletType.nano) {
196 + return NanoURI(amount: amount, address: address.address);
197 + }
198 +
199 throw Exception('Unexpected type: ${type.toString()}');
200 }
201
@@ -252,7 +272,11 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
272 }
273
274 @computed
255 - bool get hasAddressList => wallet.type == WalletType.monero || wallet.type == WalletType.haven;
275 + bool get hasAddressList =>
276 + wallet.type == WalletType.monero ||
277 + wallet.type == WalletType.haven;/* ||
278 + wallet.type == WalletType.nano ||
279 + wallet.type == WalletType.banano;*/// TODO: nano accounts are disabled for now
280
281 @computed
282 bool get showElectrumAddressDisclaimer =>
@@ -269,11 +293,16 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
293 void _init() {
294 _baseItems = [];
295
272 - if (wallet.type == WalletType.monero || wallet.type == WalletType.haven) {
296 + if (wallet.type == WalletType.monero ||
297 + wallet.type == WalletType.haven /*||
298 + wallet.type == WalletType.nano ||
299 + wallet.type == WalletType.banano*/) {
300 _baseItems.add(WalletAccountListHeader());
301 }
302
276 - _baseItems.add(WalletAddressListHeader());
303 + if (wallet.type != WalletType.nano && wallet.type != WalletType.banano) {
304 + _baseItems.add(WalletAddressListHeader());
305 + }
306 }
307
308 @action
lib/view_model/wallet_creation_vm.dart
+24 -21
@@ -35,18 +35,16 @@ abstract class WalletCreationVMBase with Store {
35 final Box<WalletInfo> _walletInfoSource;
36 final AppStore _appStore;
37
38 - bool nameExists(String name)
39 - => walletCreationService.exists(name);
38 + bool nameExists(String name) => walletCreationService.exists(name);
39
41 - bool typeExists(WalletType type)
42 - => walletCreationService.typeExists(type);
40 + bool typeExists(WalletType type) => walletCreationService.typeExists(type);
41
42 Future<void> create({dynamic options, RestoredWallet? restoreWallet}) async {
43 final type = restoreWallet?.type ?? this.type;
44 try {
45 state = IsExecutingState();
46 if (name.isEmpty) {
49 - name = await generateName();
47 + name = await generateName();
48 }
49
50 walletCreationService.checkIfExists(name);
@@ -55,17 +53,21 @@ abstract class WalletCreationVMBase with Store {
53 final credentials = restoreWallet != null
54 ? getCredentialsFromRestoredWallet(options, restoreWallet)
55 : getCredentials(options);
56 +
57 final walletInfo = WalletInfo.external(
59 - id: WalletBase.idFor(name, type),
60 - name: name,
61 - type: type,
62 - isRecovery: isRecovery,
63 - restoreHeight: credentials.height ?? 0,
64 - date: DateTime.now(),
65 - path: path,
66 - dirPath: dirPath,
67 - address: '',
68 - showIntroCakePayCard: (!walletCreationService.typeExists(type)) && type != WalletType.haven);
58 + id: WalletBase.idFor(name, type),
59 + name: name,
60 + type: type,
61 + isRecovery: isRecovery,
62 + restoreHeight: credentials.height ?? 0,
63 + date: DateTime.now(),
64 + path: path,
65 + dirPath: dirPath,
66 + address: '',
67 + showIntroCakePayCard: (!walletCreationService.typeExists(type)) && type != WalletType.haven,
68 + derivationPath: credentials.derivationPath,
69 + derivationType: credentials.derivationType,
70 + );
71 credentials.walletInfo = walletInfo;
72 final wallet = restoreWallet != null
73 ? await processFromRestoredWallet(credentials, restoreWallet)
@@ -80,15 +82,16 @@ abstract class WalletCreationVMBase with Store {
82 state = FailureState(e.toString());
83 }
84 }
83 - WalletCredentials getCredentials(dynamic options) =>
84 - throw UnimplementedError();
85
86 - Future<WalletBase> process(WalletCredentials credentials) =>
87 - throw UnimplementedError();
86 + WalletCredentials getCredentials(dynamic options) => throw UnimplementedError();
87 +
88 + Future<WalletBase> process(WalletCredentials credentials) => throw UnimplementedError();
89
89 - WalletCredentials getCredentialsFromRestoredWallet(dynamic options, RestoredWallet restoreWallet) =>
90 + WalletCredentials getCredentialsFromRestoredWallet(
91 + dynamic options, RestoredWallet restoreWallet) =>
92 throw UnimplementedError();
93
92 - Future<WalletBase> processFromRestoredWallet(WalletCredentials credentials, RestoredWallet restoreWallet) =>
94 + Future<WalletBase> processFromRestoredWallet(
95 + WalletCredentials credentials, RestoredWallet restoreWallet) =>
96 throw UnimplementedError();
97 }
lib/view_model/wallet_keys_view_model.dart
+21
@@ -1,3 +1,4 @@
1 +import 'package:cake_wallet/nano/nano.dart';
2 import 'package:cake_wallet/store/app_store.dart';
3 import 'package:cw_core/transaction_direction.dart';
4 import 'package:cw_core/transaction_info.dart';
@@ -104,6 +105,22 @@ abstract class WalletKeysViewModelBase with Store {
105 StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed!),
106 ]);
107 }
108 +
109 + if (_appStore.wallet!.type == WalletType.nano || _appStore.wallet!.type == WalletType.banano) {
110 +
111 + // we don't necessarily have the seed phrase for nano / banano:
112 + if (_appStore.wallet!.seed != null) {
113 + items.addAll([
114 + StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed!),
115 + ]);
116 + }
117 +
118 + // we always have the hex version of the seed:
119 + items.addAll([
120 + if (_appStore.wallet!.privateKey != null)
121 + StandartListItem(title: S.current.spend_key_private, value: _appStore.wallet!.privateKey!),
122 + ]);
123 + }
124 }
125
126 Future<int?> _currentHeight() async {
@@ -128,6 +145,10 @@ abstract class WalletKeysViewModelBase with Store {
145 return 'haven-wallet';
146 case WalletType.ethereum:
147 return 'ethereum-wallet';
148 + case WalletType.nano:
149 + return 'nano-wallet';
150 + case WalletType.banano:
151 + return 'banano-wallet';
152 default:
153 throw Exception('Unexpected wallet type: ${_appStore.wallet!.toString()}');
154 }
lib/view_model/wallet_new_vm.dart
+3
@@ -4,6 +4,7 @@ import 'package:flutter/foundation.dart';
4 import 'package:hive/hive.dart';
5 import 'package:mobx/mobx.dart';
6 import 'package:cake_wallet/monero/monero.dart';
7 +import 'package:cake_wallet/nano/nano.dart';
8 import 'package:cake_wallet/store/app_store.dart';
9 import 'package:cw_core/wallet_base.dart';
10 import 'package:cake_wallet/core/wallet_creation_service.dart';
@@ -45,6 +46,8 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
46 name: name, language: options as String);
47 case WalletType.ethereum:
48 return ethereum!.createEthereumNewWalletCredentials(name: name);
49 + case WalletType.nano:
50 + return nano!.createNanoNewWalletCredentials(name: name);
51 default:
52 throw Exception('Unexpected type: ${type.toString()}');;
53 }
lib/view_model/wallet_restore_choose_derivation_view_model.dart new
+23
@@ -0,0 +1,23 @@
1 +import 'package:cw_core/wallet_info.dart';
2 +import 'package:mobx/mobx.dart';
3 +import 'package:cake_wallet/view_model/restore/restore_mode.dart';
4 +
5 +part 'wallet_restore_choose_derivation_view_model.g.dart';
6 +
7 +class WalletRestoreChooseDerivationViewModel = WalletRestoreChooseDerivationViewModelBase
8 + with _$WalletRestoreChooseDerivationViewModel;
9 +
10 +abstract class WalletRestoreChooseDerivationViewModelBase with Store {
11 + WalletRestoreChooseDerivationViewModelBase({required this.derivationInfos})
12 + : mode = WalletRestoreMode.seed {}
13 +
14 + @observable
15 + List<DerivationInfo> derivationInfos;
16 +
17 + Future<List<DerivationInfo>> get derivations async {
18 + return derivationInfos;
19 + }
20 +
21 + @observable
22 + WalletRestoreMode mode;
23 +}
lib/view_model/wallet_restore_view_model.dart
+84 -41
@@ -1,4 +1,6 @@
1 import 'package:cake_wallet/bitcoin/bitcoin.dart';
2 +import 'package:cake_wallet/di.dart';
3 +import 'package:cake_wallet/nano/nano.dart';
4 import 'package:cake_wallet/ethereum/ethereum.dart';
5 import 'package:hive/hive.dart';
6 import 'package:mobx/mobx.dart';
@@ -16,25 +18,34 @@ import 'package:cake_wallet/view_model/restore/restore_mode.dart';
18
19 part 'wallet_restore_view_model.g.dart';
20
19 -class WalletRestoreViewModel = WalletRestoreViewModelBase
20 - with _$WalletRestoreViewModel;
21 +class WalletRestoreViewModel = WalletRestoreViewModelBase with _$WalletRestoreViewModel;
22
23 abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
24 WalletRestoreViewModelBase(AppStore appStore, WalletCreationService walletCreationService,
25 Box<WalletInfo> walletInfoSource,
26 {required WalletType type})
26 - : availableModes =
27 - (type == WalletType.monero || type == WalletType.haven || type == WalletType.ethereum)
28 - ? WalletRestoreMode.values
29 - : [WalletRestoreMode.seed],
30 - hasSeedLanguageSelector = type == WalletType.monero || type == WalletType.haven,
27 + : hasSeedLanguageSelector = type == WalletType.monero || type == WalletType.haven,
28 hasBlockchainHeightLanguageSelector = type == WalletType.monero || type == WalletType.haven,
32 - hasRestoreFromPrivateKey = type == WalletType.ethereum,
29 + hasRestoreFromPrivateKey =
30 + type == WalletType.ethereum || type == WalletType.nano || type == WalletType.banano,
31 isButtonEnabled = false,
32 mode = WalletRestoreMode.seed,
33 super(appStore, walletInfoSource, walletCreationService, type: type, isRecovery: true) {
36 - isButtonEnabled =
37 - !hasSeedLanguageSelector && !hasBlockchainHeightLanguageSelector;
34 + switch (type) {
35 + case WalletType.monero:
36 + case WalletType.haven:
37 + case WalletType.ethereum:
38 + availableModes = WalletRestoreMode.values;
39 + break;
40 + case WalletType.nano:
41 + case WalletType.banano:
42 + availableModes = [WalletRestoreMode.seed, WalletRestoreMode.keys];
43 + break;
44 + default:
45 + availableModes = [WalletRestoreMode.seed];
46 + break;
47 + }
48 + isButtonEnabled = !hasSeedLanguageSelector && !hasBlockchainHeightLanguageSelector;
49 walletCreationService.changeWalletType(type: type);
50 }
51
@@ -42,7 +53,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
53 static const electrumSeedMnemonicLength = 24;
54 static const electrumShortSeedMnemonicLength = 12;
55
45 - final List<WalletRestoreMode> availableModes;
56 + late List<WalletRestoreMode> availableModes;
57 final bool hasSeedLanguageSelector;
58 final bool hasBlockchainHeightLanguageSelector;
59 final bool hasRestoreFromPrivateKey;
@@ -58,38 +69,37 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
69 final password = generateWalletPassword();
70 final height = options['height'] as int? ?? 0;
71 name = options['name'] as String;
72 + DerivationType? derivationType = options["derivationType"] as DerivationType?;
73 + String? derivationPath = options["derivationPath"] as String?;
74
75 if (mode == WalletRestoreMode.seed) {
76 final seed = options['seed'] as String;
64 -
77 switch (type) {
78 case WalletType.monero:
79 return monero!.createMoneroRestoreWalletFromSeedCredentials(
68 - name: name,
69 - height: height,
70 - mnemonic: seed,
71 - password: password);
80 + name: name, height: height, mnemonic: seed, password: password);
81 case WalletType.bitcoin:
82 return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials(
83 name: name,
84 mnemonic: seed,
76 - password: password);
85 + password: password,
86 + );
87 case WalletType.litecoin:
88 return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials(
79 - name: name,
80 - mnemonic: seed,
81 - password: password);
89 + name: name, mnemonic: seed, password: password);
90 case WalletType.haven:
91 return haven!.createHavenRestoreWalletFromSeedCredentials(
84 - name: name,
85 - height: height,
86 - mnemonic: seed,
87 - password: password);
92 + name: name, height: height, mnemonic: seed, password: password);
93 case WalletType.ethereum:
94 return ethereum!.createEthereumRestoreWalletFromSeedCredentials(
90 - name: name,
91 - mnemonic: seed,
92 - password: password);
95 + name: name, mnemonic: seed, password: password);
96 + case WalletType.nano:
97 + return nano!.createNanoRestoreWalletFromSeedCredentials(
98 + name: name,
99 + mnemonic: seed,
100 + password: password,
101 + derivationType: derivationType,
102 + );
103 default:
104 break;
105 }
@@ -100,40 +110,73 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
110 final spendKey = options['spendKey'] as String?;
111 final address = options['address'] as String?;
112
103 - if (type == WalletType.monero) {
104 - return monero!.createMoneroRestoreWalletFromKeysCredentials(
113 + switch (type) {
114 + case WalletType.monero:
115 + return monero!.createMoneroRestoreWalletFromKeysCredentials(
116 name: name,
117 height: height,
118 spendKey: spendKey!,
119 viewKey: viewKey!,
120 address: address!,
121 password: password,
111 - language: 'English');
112 - }
122 + language: 'English',
123 + );
124
114 - if (type == WalletType.haven) {
115 - return haven!.createHavenRestoreWalletFromKeysCredentials(
125 + case WalletType.haven:
126 + return haven!.createHavenRestoreWalletFromKeysCredentials(
127 name: name,
128 height: height,
129 spendKey: spendKey!,
130 viewKey: viewKey!,
131 address: address!,
132 password: password,
122 - language: 'English');
123 - }
133 + language: 'English',
134 + );
135
125 - if (type == WalletType.ethereum) {
126 - return ethereum!.createEthereumRestoreWalletFromPrivateKey(
127 - name: name,
128 - privateKey: options['private_key'] as String,
129 - password: password,
130 - );
136 + case WalletType.ethereum:
137 + return ethereum!.createEthereumRestoreWalletFromPrivateKey(
138 + name: name,
139 + privateKey: options['private_key'] as String,
140 + password: password,
141 + );
142 +
143 + case WalletType.nano:
144 + return nano!.createNanoRestoreWalletFromKeysCredentials(
145 + name: name,
146 + password: password,
147 + seedKey: options['private_key'] as String,
148 + derivationType: options["derivationType"] as DerivationType,
149 + );
150 + default:
151 + break;
152 }
153 }
154
155 throw Exception('Unexpected type: ${type.toString()}');
156 }
157
158 + Future<List<DerivationType>> getDerivationTypes(dynamic options) async {
159 + final seedKey = options['private_key'] as String?;
160 + final mnemonic = options['seed'] as String?;
161 + WalletType walletType = options['walletType'] as WalletType;
162 + var appStore = getIt.get<AppStore>();
163 + var node = appStore.settingsStore.getCurrentNode(walletType);
164 +
165 + switch (type) {
166 + case WalletType.nano:
167 + return nanoUtil!.compareDerivationMethods(
168 + mnemonic: mnemonic,
169 + privateKey: seedKey,
170 + node: node,
171 + );
172 + default:
173 + break;
174 + }
175 +
176 + // throw Exception('Unexpected type: ${type.toString()}');
177 + return [DerivationType.def];
178 + }
179 +
180 @override
181 Future<WalletBase> process(WalletCredentials credentials) async {
182 if (mode == WalletRestoreMode.keys) {
model_generator.sh
+1
@@ -3,4 +3,5 @@ cd cw_monero && flutter pub get && flutter packages pub run build_runner build -
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 +cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..
7 flutter packages pub run build_runner build --delete-conflicting-outputs
\ No newline at end of file
pubspec_base.yaml
+2
@@ -129,6 +129,8 @@ flutter:
129 - assets/bitcoin_electrum_server_list.yml
130 - assets/litecoin_electrum_server_list.yml
131 - assets/ethereum_server_list.yml
132 + - assets/nano_node_list.yml
133 + - assets/nano_pow_node_list.yml
134 - assets/text/
135 - assets/faq/
136 - assets/animation/
res/values/strings_ar.arb
+7 -1
@@ -673,14 +673,20 @@
673 "matrix_green_dark_theme": "موضوع ماتريكس الأخضر الداكن",
674 "monero_light_theme": " ضوء مونيرو",
675 "etherscan_history": "Etherscan تاريخ",
676 - "manage_nodes": "ﺪﻘﻌﻟﺍ ﺓﺭﺍﺩﺇ",
676 "template_name": "اسم القالب",
677 + "change_rep": "ﺏﻭﺪﻨﻣ ﺮﻴﻴﻐﺗ",
678 + "change_rep_message": "؟ﻦﻴﻠﺜﻤﻤﻟﺍ ﺮﻴﻴﻐﺗ ﺪﻳﺮﺗ ﻚﻧﺃ ﺪﻛﺄﺘﻣ ﺖﻧﺃ ﻞﻫ",
679 + "manage_nodes": "ﺪﻘﻌﻟﺍ ﺓﺭﺍﺩﺇ",
680 + "unsupported_asset": ".ﻡﻮﻋﺪﻣ ﻞﺻﺃ ﻉﻮﻧ ﻦﻣ ﺔﻈﻔﺤﻣ ﻰﻟﺇ ﻞﻳﺪﺒﺘﻟﺍ ﻭﺃ ءﺎﺸﻧﺇ ﻰﺟﺮﻳ .ﻞﺻﻷﺍ ﺍﺬﻬﻟ ءﺍﺮﺟﻹﺍ ﺍﺬﻫ ﻢﻋﺪﻧ ﻻ ﻦﺤﻧ",
681 + "manage_pow_nodes": "PoW ﻁﺎﻘﻧ ﺓﺭﺍﺩﺇ",
682 "support_title_live_chat": "الدعم المباشر",
683 "support_description_live_chat": "حرة وسريعة! ممثلو الدعم المدربين متاحون للمساعدة",
684 "support_title_guides": "أدلة محفظة كعكة",
685 "support_description_guides": "توثيق ودعم القضايا المشتركة",
686 "support_title_other_links": "روابط دعم أخرى",
687 "support_description_other_links": "انضم إلى مجتمعاتنا أو تصل إلينا شركائنا من خلال أساليب أخرى",
688 + "choose_derivation": "اختر اشتقاق المحفظة",
689 + "new_first_wallet_text": "حافظ بسهولة على أمان العملة المشفرة",
690 "select_destination": ".ﻲﻃﺎﻴﺘﺣﻻﺍ ﺦﺴﻨﻟﺍ ﻒﻠﻣ ﺔﻬﺟﻭ ﺪﻳﺪﺤﺗ ءﺎﺟﺮﻟﺍ",
691 "auto_generate_subaddresses": "تلقائي توليد subddresses",
692 "save_to_downloads": "ﺕﻼﻳﺰﻨﺘﻟﺍ ﻲﻓ ﻆﻔﺣ",
res/values/strings_bg.arb
+7 -1
@@ -670,14 +670,20 @@
670 "matrix_green_dark_theme": "Зелена тъмна тема Matrix",
671 "monero_light_theme": "Лека тема Monero",
672 "etherscan_history": "История на Etherscan",
673 - "manage_nodes": "Управление на възли",
673 "template_name": "Име на шаблон",
674 + "change_rep": "Смяна на представител",
675 + "change_rep_message": "Сигурни ли сте, че искате да смените представителите?",
676 + "manage_nodes": "Управление на възли",
677 + "unsupported_asset": "Не поддържаме това действие за този актив. Моля, създайте или преминете към портфейл от поддържан тип актив.",
678 + "manage_pow_nodes": "Управление на PoW възли",
679 "support_title_live_chat": "Подкрепа на живо",
680 "support_description_live_chat": "Безплатно и бързо! Обучени представители на подкрепата са на разположение за подпомагане",
681 "support_title_guides": "Ръководства за портфейл за торта",
682 "support_description_guides": "Документация и подкрепа за общи проблеми",
683 "support_title_other_links": "Други връзки за поддръжка",
684 "support_description_other_links": "Присъединете се към нашите общности или се свържете с нас нашите партньори чрез други методи",
685 + "choose_derivation": "Изберете производно на портфейла",
686 + "new_first_wallet_text": "Лесно пазете криптовалутата си в безопасност",
687 "select_destination": "Моля, изберете дестинация за архивния файл.",
688 "save_to_downloads": "Запазване в Изтегляния",
689 "select_buy_provider_notice": "Изберете доставчик на покупка по -горе. Можете да пропуснете този екран, като зададете вашия доставчик по подразбиране по подразбиране в настройките на приложението.",
res/values/strings_cs.arb
+6
@@ -672,12 +672,18 @@
672 "manage_nodes": "Spravovat uzly",
673 "etherscan_history": "Historie Etherscanu",
674 "template_name": "Název šablony",
675 + "change_rep": "Změna zástupce",
676 + "change_rep_message": "Jste si jisti, že chcete změnit zástupce?",
677 + "unsupported_asset": "Tuto akci u tohoto díla nepodporujeme. Vytvořte nebo přepněte na peněženku podporovaného typu aktiv.",
678 + "manage_pow_nodes": "Správa uzlů PoW",
679 "support_title_live_chat": "Živá podpora",
680 "support_description_live_chat": "Zdarma a rychle! K dispozici jsou zástupci vyškolených podpůrných podpory",
681 "support_title_guides": "Průvodce peněženkami dortu",
682 "support_description_guides": "Dokumentace a podpora běžných otázek",
683 "support_title_other_links": "Další odkazy na podporu",
684 "support_description_other_links": "Připojte se k našim komunitám nebo se k nám oslovte další metody",
685 + "choose_derivation": "Vyberte derivaci peněženky",
686 + "new_first_wallet_text": "Snadno udržujte svou kryptoměnu v bezpečí",
687 "select_destination": "Vyberte cíl pro záložní soubor.",
688 "save_to_downloads": "Uložit do Stažených souborů",
689 "select_buy_provider_notice": "Vyberte výše uvedeného poskytovatele nákupu. Tuto obrazovku můžete přeskočit nastavením výchozího poskytovatele nákupu v nastavení aplikace.",
res/values/strings_de.arb
+7 -1
@@ -634,6 +634,7 @@
634 "setup_totp_recommended": "TOTP einrichten (empfohlen)",
635 "disable_buy": "Kaufaktion deaktivieren",
636 "disable_sell": "Verkaufsaktion deaktivieren",
637 + "cake_2fa_preset": "Kuchen 2FA-Voreinstellung",
638 "monero_dark_theme": "Dunkles Monero-Thema",
639 "bitcoin_dark_theme": "Dunkles Bitcoin-Thema",
640 "bitcoin_light_theme": "Bitcoin Light-Thema",
@@ -641,7 +642,6 @@
642 "matrix_green_dark_theme": "Matrix Green Dark Theme",
643 "monero_light_theme": "Monero Light-Thema",
644 "auto_generate_subaddresses": "Unteradressen automatisch generieren",
644 - "cake_2fa_preset": "Cake 2FA-Voreinstellung",
645 "narrow": "Eng",
646 "normal": "Normal",
647 "aggressive": "Übereifrig",
@@ -680,12 +680,18 @@
680 "manage_nodes": "Knoten verwalten",
681 "etherscan_history": "Etherscan-Geschichte",
682 "template_name": "Vorlagenname",
683 + "change_rep": "Change-Beauftragter",
684 + "change_rep_message": "Sind Sie sicher, dass Sie den Vertreter wechseln möchten?",
685 + "unsupported_asset": "Wir unterstützen diese Aktion für dieses Asset nicht. Bitte erstellen Sie eine Wallet eines unterstützten Asset-Typs oder wechseln Sie zu einer Wallet.",
686 + "manage_pow_nodes": "PoW-Knoten verwalten",
687 "support_title_live_chat": "Live Support",
688 "support_description_live_chat": "Kostenlos und schnell! Ausgebildete Mitarbeiter stehen zur Unterstützung bereit, um zu helfen",
689 "support_title_guides": "Cake Wallet Guides",
690 "support_description_guides": "Dokumentation und Hilfe für bekannte Probleme",
691 "support_title_other_links": "Andere Support-Links",
692 "support_description_other_links": "Treten Sie unseren Communities bei oder erreichen Sie uns oder unsere Partner über andere Methoden",
693 + "choose_derivation": "Wählen Sie Brieftaschenableitung",
694 + "new_first_wallet_text": "Bewahren Sie Ihre Kryptowährung einfach sicher auf",
695 "select_destination": "Bitte wählen Sie das Ziel für die Sicherungsdatei aus.",
696 "save_to_downloads": "Unter „Downloads“ speichern",
697 "select_buy_provider_notice": "Wählen Sie oben einen Anbieter kaufen. Sie können diese Seite überspringen, indem Sie Ihren Standard-Kaufanbieter in den App-Einstellungen festlegen.",
res/values/strings_en.arb
+7 -1
@@ -635,13 +635,13 @@
635 "setup_totp_recommended": "Set up TOTP (Recommended)",
636 "disable_buy": "Disable buy action",
637 "disable_sell": "Disable sell action",
638 + "cake_2fa_preset": "Cake 2FA Preset",
639 "monero_dark_theme": "Monero Dark Theme",
640 "bitcoin_dark_theme": "Bitcoin Dark Theme",
641 "bitcoin_light_theme": "Bitcoin Light Theme",
642 "high_contrast_theme": "High Contrast Theme",
643 "matrix_green_dark_theme": "Matrix Green Dark Theme",
644 "monero_light_theme": "Monero Light Theme",
644 - "cake_2fa_preset": "Cake 2FA Preset",
645 "narrow": "Narrow",
646 "normal": "Normal",
647 "aggressive": "Aggressive",
@@ -680,12 +680,18 @@
680 "manage_nodes": "Manage nodes",
681 "etherscan_history": "Etherscan history",
682 "template_name": "Template Name",
683 + "change_rep": "Change Representative",
684 + "change_rep_message": "Are you sure that you want to change representatives?",
685 + "unsupported_asset": "We don't support this action for this asset. Please create or switch to a wallet of a supported asset type.",
686 + "manage_pow_nodes": "Manage PoW nodes",
687 "support_title_live_chat": "Live support",
688 "support_description_live_chat": "Free and fast! Trained support representatives are available to assist",
689 "support_title_guides": "Cake Wallet guides",
690 "support_description_guides": "Documentation and support for common issues",
691 "support_title_other_links": "Other support links",
692 "support_description_other_links": "Join our communities or reach us our our partners through other methods",
693 + "choose_derivation": "Choose Wallet Derivation",
694 + "new_first_wallet_text": "Keep your crypto safe, piece of cake",
695 "select_destination": "Please select destination for the backup file.",
696 "save_to_downloads": "Save to Downloads",
697 "select_buy_provider_notice": "Select a buy provider above. You can skip this screen by setting your default buy provider in app settings.",
res/values/strings_es.arb
+7 -1
@@ -634,6 +634,7 @@
634 "setup_totp_recommended": "Configurar TOTP (Recomendado)",
635 "disable_buy": "Desactivar acción de compra",
636 "disable_sell": "Desactivar acción de venta",
637 + "cake_2fa_preset": "Pastel 2FA preestablecido",
638 "monero_dark_theme": "Tema oscuro de Monero",
639 "bitcoin_dark_theme": "Tema oscuro de Bitcoin",
640 "bitcoin_light_theme": "Tema de la luz de Bitcoin",
@@ -641,7 +642,6 @@
642 "matrix_green_dark_theme": "Matrix verde oscuro tema",
643 "monero_light_theme": "Tema ligero de Monero",
644 "auto_generate_subaddresses": "Generar subdirecciones automáticamente",
644 - "cake_2fa_preset": "Pastel 2FA preestablecido",
645 "narrow": "Angosto",
646 "normal": "Normal",
647 "aggressive": "Demasiado entusiasta",
@@ -680,12 +680,18 @@
680 "manage_nodes": "Administrar nodos",
681 "etherscan_history": "historia de etherscan",
682 "template_name": "Nombre de la plantilla",
683 + "change_rep": "Representante de cambio",
684 + "change_rep_message": "¿Estás seguro de que quieres cambiar de representante?",
685 + "unsupported_asset": "No admitimos esta acción para este activo. Cree o cambie a una billetera de un tipo de activo admitido.",
686 + "manage_pow_nodes": "Administrar nodos PoW",
687 "support_title_live_chat": "Soporte vital",
688 "support_description_live_chat": "¡GRATIS y RÁPIDO! Los representantes de apoyo capacitado están disponibles para ayudar",
689 "support_title_guides": "Guías de billetera para pastel",
690 "support_description_guides": "Documentación y apoyo para problemas comunes",
691 "support_title_other_links": "Otros enlaces de soporte",
692 "support_description_other_links": "Únase a nuestras comunidades o comuníquese con nosotros nuestros socios a través de otros métodos",
693 + "choose_derivation": "Elija la derivación de la billetera",
694 + "new_first_wallet_text": "Mantenga fácilmente su criptomoneda segura",
695 "select_destination": "Seleccione el destino del archivo de copia de seguridad.",
696 "save_to_downloads": "Guardar en Descargas",
697 "select_buy_provider_notice": "Seleccione un proveedor de compra arriba. Puede omitir esta pantalla configurando su proveedor de compra predeterminado en la configuración de la aplicación.",
res/values/strings_fr.arb
+7 -1
@@ -634,6 +634,7 @@
634 "setup_totp_recommended": "Configurer TOTP (recommandé)",
635 "disable_buy": "Désactiver l'action d'achat",
636 "disable_sell": "Désactiver l'action de vente",
637 + "cake_2fa_preset": "Gâteau 2FA prédéfini",
638 "monero_dark_theme": "Thème sombre Monero",
639 "bitcoin_dark_theme": "Thème sombre Bitcoin",
640 "bitcoin_light_theme": "Thème léger Bitcoin",
@@ -641,7 +642,6 @@
642 "matrix_green_dark_theme": "Thème Matrix Green Dark",
643 "monero_light_theme": "Thème de lumière Monero",
644 "auto_generate_subaddresses": "Générer automatiquement des sous-adresses",
644 - "cake_2fa_preset": "Gâteau 2FA prédéfini",
645 "narrow": "Étroit",
646 "normal": "Normal",
647 "aggressive": "Trop zélé",
@@ -680,12 +680,18 @@
680 "manage_nodes": "Gérer les nœuds",
681 "etherscan_history": "Historique d'Etherscan",
682 "template_name": "Nom du modèle",
683 + "change_rep": "Changer de représentant",
684 + "change_rep_message": "Êtes-vous sûr de vouloir changer de représentant ?",
685 + "unsupported_asset": "Nous ne prenons pas en charge cette action pour cet élément. Veuillez créer ou passer à un portefeuille d'un type d'actif pris en charge.",
686 + "manage_pow_nodes": "Gérer les nœuds PoW",
687 "support_title_live_chat": "Support en direct",
688 "support_description_live_chat": "GRATUIT ET RAPIDE! Des représentants de soutien formé sont disponibles pour aider",
689 "support_title_guides": "Guides de portefeuille à gâteau",
690 "support_description_guides": "Documentation et soutien aux problèmes communs",
691 "support_title_other_links": "Autres liens d'assistance",
692 "support_description_other_links": "Rejoignez nos communautés ou contactez-nous nos partenaires à travers d'autres méthodes",
693 + "choose_derivation": "Choisissez la dérivation du portefeuille",
694 + "new_first_wallet_text": "Gardez facilement votre crypto-monnaie en sécurité",
695 "select_destination": "Veuillez sélectionner la destination du fichier de sauvegarde.",
696 "save_to_downloads": "Enregistrer dans les téléchargements",
697 "select_buy_provider_notice": "Sélectionnez un fournisseur d'achat ci-dessus. Vous pouvez ignorer cet écran en définissant votre fournisseur d'achat par défaut dans les paramètres de l'application.",
res/values/strings_ha.arb
+8 -2
@@ -612,6 +612,7 @@
612 "prevent_screenshots": "Fada lambobi da jarrabobi na kayan lambobi",
613 "disable_buy": "Kashe alama",
614 "disable_sell": "Kashe karbuwa",
615 + "cake_2fa_preset": "Cake 2FA saiti",
616 "monero_dark_theme": "Monero Dark Jigo",
617 "bitcoin_dark_theme": "Bitcoin Dark Jigo",
618 "bitcoin_light_theme": "Jigon Hasken Bitcoin",
@@ -619,7 +620,6 @@
620 "matrix_green_dark_theme": "Matrix Green Dark Jigo",
621 "monero_light_theme": "Jigon Hasken Monero",
622 "auto_generate_subaddresses": "Saɓaƙa subaddresses ta kai tsaye",
622 - "cake_2fa_preset": "Cake 2FA saiti",
623 "narrow": "kunkuntar",
624 "normal": "Na al'ada",
625 "aggressive": "Mai tsananin kishi",
@@ -656,14 +656,20 @@
656 "share": "Raba",
657 "slidable": "Mai iya zamewa",
658 "etherscan_history": "Etherscan tarihin kowane zamani",
659 - "manage_nodes": "Sarrafa nodes",
659 "template_name": "Sunan Samfura",
660 + "change_rep": "Canza Wakili",
661 + "change_rep_message": "Shin kun tabbata kuna son canza wakilai?",
662 + "manage_nodes": "Sarrafa nodes",
663 + "unsupported_asset": "Ba mu goyi bayan wannan aikin don wannan kadara. Da fatan za a ƙirƙira ko canza zuwa walat na nau'in kadara mai tallafi.",
664 + "manage_pow_nodes": "Sarrafa PoW Nodes",
665 "support_title_live_chat": "Tallafi na Live",
666 "support_description_live_chat": "Kyauta da sauri! An horar da wakilan tallafi na tallafi don taimakawa",
667 "support_title_guides": "Jagorar Cake",
668 "support_description_guides": "Tallafi da tallafi don batutuwa na yau da kullun",
669 "support_title_other_links": "Sauran hanyoyin tallafi",
670 "support_description_other_links": "Kasance tare da al'ummominmu ko kuma ka kai mu abokanmu ta hanyar wasu hanyoyi",
671 + "choose_derivation": "Zaɓi walatawa",
672 + "new_first_wallet_text": "A sauƙaƙe kiyaye kuzarin ku",
673 "select_destination": "Da fatan za a zaɓi wurin da za a yi wa madadin fayil ɗin.",
674 "save_to_downloads": "Ajiye zuwa Zazzagewa",
675 "select_buy_provider_notice": "Zaɓi mai ba da kyauta a sama. Zaka iya tsallake wannan allon ta hanyar saita mai ba da isasshen busasshen mai ba da isasshen busasshiyar saiti.",
res/values/strings_hi.arb
+7 -1
@@ -634,6 +634,7 @@
634 "setup_totp_recommended": "टीओटीपी सेट अप करें (अनुशंसित)",
635 "disable_buy": "खरीद कार्रवाई अक्षम करें",
636 "disable_sell": "बेचने की कार्रवाई अक्षम करें",
637 + "cake_2fa_preset": "केक 2एफए प्रीसेट",
638 "monero_dark_theme": "मोनेरो डार्क थीम",
639 "bitcoin_dark_theme": "बिटकॉइन डार्क थीम",
640 "bitcoin_light_theme": "बिटकॉइन लाइट थीम",
@@ -641,7 +642,6 @@
642 "matrix_green_dark_theme": "मैट्रिक्स ग्रीन डार्क थीम",
643 "monero_light_theme": "मोनेरो लाइट थीम",
644 "auto_generate_subaddresses": "स्वचालित रूप से उप-पते उत्पन्न करें",
644 - "cake_2fa_preset": "केक 2एफए प्रीसेट",
645 "narrow": "सँकरा",
646 "normal": "सामान्य",
647 "aggressive": "ज्यादा",
@@ -680,12 +680,18 @@
680 "manage_nodes": "नोड्स प्रबंधित करें",
681 "etherscan_history": "इथरस्कैन इतिहास",
682 "template_name": "टेम्पलेट नाम",
683 + "change_rep": "प्रतिनिधि बदलें",
684 + "change_rep_message": "क्या आप वाकई प्रतिनिधियों को बदलना चाहते हैं?",
685 + "unsupported_asset": "हम इस संपत्ति के लिए इस कार्रवाई का समर्थन नहीं करते हैं. कृपया समर्थित परिसंपत्ति प्रकार का वॉलेट बनाएं या उस पर स्विच करें।",
686 + "manage_pow_nodes": "PoW नोड्स प्रबंधित करें",
687 "support_title_live_chat": "लाइव सहायता",
688 "support_description_live_chat": "मुक्त और तेजी से! प्रशिक्षित सहायता प्रतिनिधि सहायता के लिए उपलब्ध हैं",
689 "support_title_guides": "केक वॉलेट गाइड",
690 "support_description_guides": "सामान्य मुद्दों के लिए प्रलेखन और समर्थन",
691 "support_title_other_links": "अन्य समर्थन लिंक",
692 "support_description_other_links": "हमारे समुदायों में शामिल हों या अन्य तरीकों के माध्यम से हमारे साथी तक पहुंचें",
693 + "choose_derivation": "वॉलेट व्युत्पत्ति चुनें",
694 + "new_first_wallet_text": "आसानी से अपनी क्रिप्टोक्यूरेंसी को सुरक्षित रखें",
695 "select_destination": "कृपया बैकअप फ़ाइल के लिए गंतव्य का चयन करें।",
696 "save_to_downloads": "डाउनलोड में सहेजें",
697 "select_buy_provider_notice": "ऊपर एक खरीद प्रदाता का चयन करें। आप इस स्क्रीन को ऐप सेटिंग्स में अपना डिफ़ॉल्ट बाय प्रदाता सेट करके छोड़ सकते हैं।",
res/values/strings_hr.arb
+6 -2
@@ -634,6 +634,7 @@
634 "setup_totp_recommended": "Postavite TOTP (preporučeno)",
635 "disable_buy": "Onemogući kupnju",
636 "disable_sell": "Onemogući akciju prodaje",
637 + "cake_2fa_preset": "Cake 2FA Preset",
638 "monero_dark_theme": "Monero tamna tema",
639 "bitcoin_dark_theme": "Bitcoin Tamna tema",
640 "bitcoin_light_theme": "Bitcoin Light Theme",
@@ -641,7 +642,6 @@
642 "matrix_green_dark_theme": "Matrix Green Dark Theme",
643 "monero_light_theme": "Monero lagana tema",
644 "auto_generate_subaddresses": "Automatski generirajte podadrese",
644 - "cake_2fa_preset": "Cake 2FA Preset",
645 "narrow": "Usko",
646 "normal": "Normalno",
647 "aggressive": "Preterano",
@@ -680,12 +680,16 @@
680 "manage_nodes": "Upravljanje čvorovima",
681 "etherscan_history": "Etherscan povijest",
682 "template_name": "Naziv predloška",
683 + "change_rep_message": "Jeste li sigurni da želite promijeniti predstavnika?",
684 + "unsupported_asset": "Ne podržavamo ovu radnju za ovaj materijal. Izradite ili prijeđite na novčanik podržane vrste sredstava.",
685 + "manage_pow_nodes": "Upravljanje PoW čvorovima",
686 "support_title_live_chat": "Podrška uživo",
684 - "support_description_live_chat": "Besplatno i brzo! Obučeni predstavnici podrške dostupni su za pomoć",
687 "support_title_guides": "Vodiči za torte",
688 "support_description_guides": "Dokumentacija i podrška za uobičajena pitanja",
689 "support_title_other_links": "Ostale veze za podršku",
690 "support_description_other_links": "Pridružite se našim zajednicama ili nam dosegnu naše partnere drugim metodama",
691 + "choose_derivation": "Odaberite izvedbu novčanika",
692 + "new_first_wallet_text": "Jednostavno čuvajte svoju kripto valutu",
693 "select_destination": "Odaberite odredište za datoteku sigurnosne kopije.",
694 "save_to_downloads": "Spremi u Preuzimanja",
695 "select_buy_provider_notice": "Odaberite gornji davatelj kupnje. Ovaj zaslon možete preskočiti postavljanjem zadanog davatelja usluga kupnje u postavkama aplikacija.",
res/values/strings_id.arb
+6
@@ -668,12 +668,18 @@
668 "manage_nodes": "Kelola node",
669 "etherscan_history": "Sejarah Etherscan",
670 "template_name": "Nama Templat",
671 + "change_rep": "Ubah Perwakilan",
672 + "change_rep_message": "Apakah Anda yakin ingin mengubah perwakilan?",
673 + "unsupported_asset": "Kami tidak mendukung tindakan ini untuk aset ini. Harap buat atau alihkan ke dompet dari jenis aset yang didukung.",
674 + "manage_pow_nodes": "Kelola Node PoW",
675 "support_title_live_chat": "Dukungan langsung",
676 "support_description_live_chat": "Gratis dan Cepat! Perwakilan dukungan terlatih tersedia untuk membantu",
677 "support_title_guides": "Panduan Dompet Kue",
678 "support_description_guides": "Dokumentasi dan dukungan untuk masalah umum",
679 "support_title_other_links": "Tautan dukungan lainnya",
680 "support_description_other_links": "Bergabunglah dengan komunitas kami atau hubungi kami mitra kami melalui metode lain",
681 + "choose_derivation": "Pilih dompet dompet",
682 + "new_first_wallet_text": "Dengan mudah menjaga cryptocurrency Anda aman",
683 "select_destination": "Silakan pilih tujuan untuk file cadangan.",
684 "save_to_downloads": "Simpan ke Unduhan",
685 "select_buy_provider_notice": "Pilih penyedia beli di atas. Anda dapat melewatkan layar ini dengan mengatur penyedia pembelian default Anda di pengaturan aplikasi.",
res/values/strings_it.arb
+6
@@ -680,12 +680,18 @@
680 "manage_nodes": "Gestisci i nodi",
681 "etherscan_history": "Storia Etherscan",
682 "template_name": "Nome modello",
683 + "change_rep": "Cambia rappresentante",
684 + "change_rep_message": "Sei sicuro di voler cambiare rappresentante?",
685 + "unsupported_asset": "Non supportiamo questa azione per questa risorsa. Crea o passa a un portafoglio di un tipo di asset supportato.",
686 + "manage_pow_nodes": "Gestisci i nodi PoW",
687 "support_title_live_chat": "Supporto dal vivo",
688 "support_description_live_chat": "Gratuito e veloce! I rappresentanti di supporto qualificati sono disponibili per assistere",
689 "support_title_guides": "Guide del portafoglio per torte",
690 "support_description_guides": "Documentazione e supporto per problemi comuni",
691 "support_title_other_links": "Altri collegamenti di supporto",
692 "support_description_other_links": "Unisciti alle nostre comunità o raggiungici i nostri partner attraverso altri metodi",
693 + "choose_derivation": "Scegli la derivazione del portafoglio",
694 + "new_first_wallet_text": "Mantieni facilmente la tua criptovaluta al sicuro",
695 "select_destination": "Seleziona la destinazione per il file di backup.",
696 "save_to_downloads": "Salva in Download",
697 "select_buy_provider_notice": "Seleziona un fornitore di acquisto sopra. È possibile saltare questa schermata impostando il provider di acquisto predefinito nelle impostazioni dell'app.",
res/values/strings_ja.arb
+6
@@ -679,12 +679,18 @@
679 "manage_nodes": "ノードの管理",
680 "etherscan_history": "イーサスキャンの歴史",
681 "template_name": "テンプレート名",
682 + "change_rep": "代表者の変更",
683 + "change_rep_message": "代表者を変更してもよろしいですか?",
684 + "unsupported_asset": "このアセットに対するこのアクションはサポートされていません。サポートされているアセットタイプのウォレットを作成するか、ウォレットに切り替えてください。",
685 + "manage_pow_nodes": "PoWノードの管理",
686 "support_title_live_chat": "ライブサポート",
687 "support_description_live_chat": "無料で速い!訓練されたサポート担当者が支援することができます",
688 "support_title_guides": "ケーキウォレットガイド",
689 "support_description_guides": "一般的な問題のドキュメントとサポート",
690 "support_title_other_links": "その他のサポートリンク",
691 "support_description_other_links": "私たちのコミュニティに参加するか、他の方法を通して私たちのパートナーに連絡してください",
692 + "choose_derivation": "ウォレット派生を選択します",
693 + "new_first_wallet_text": "暗号通貨を簡単に安全に保ちます",
694 "select_destination": "バックアップファイルの保存先を選択してください。",
695 "auto_generate_subaddresses": "Autoはサブアドレスを生成します",
696 "save_to_downloads": "ダウンロードに保存",
res/values/strings_ko.arb
+4
@@ -679,12 +679,16 @@
679 "manage_nodes": "노드 관리",
680 "etherscan_history": "이더스캔 역사",
681 "template_name": "템플릿 이름",
682 + "unsupported_asset": "이 저작물에 대해 이 작업을 지원하지 않습니다. 지원되는 자산 유형의 지갑을 생성하거나 전환하십시오.",
683 + "manage_pow_nodes": "PoW 노드 관리",
684 "support_title_live_chat": "실시간 지원",
685 "support_description_live_chat": "자유롭고 빠릅니다! 훈련 된 지원 담당자가 지원할 수 있습니다",
686 "support_title_guides": "케이크 지갑 가이드",
687 "support_description_guides": "일반적인 문제에 대한 문서화 및 지원",
688 "support_title_other_links": "다른 지원 링크",
689 "support_description_other_links": "다른 방법을 통해 커뮤니티에 가입하거나 파트너에게 연락하십시오.",
690 + "choose_derivation": "지갑 파생을 선택하십시오",
691 + "new_first_wallet_text": "cryptocurrency를 쉽게 안전하게 유지하십시오",
692 "select_destination": "백업 파일의 대상을 선택하십시오.",
693 "auto_generate_subaddresses": "자동 생성 서브 아드 드레스",
694 "save_to_downloads": "다운로드에 저장",
res/values/strings_my.arb
+6
@@ -677,12 +677,18 @@
677 "manage_nodes": "ဆုံမှတ်များကို စီမံပါ။",
678 "etherscan_history": "Etherscan သမိုင်း",
679 "template_name": "နမူနာပုံစံ",
680 + "change_rep": "ကိုယ်စားလှယ်ပြောင်းပါ။",
681 + "change_rep_message": "ကိုယ်စားလှယ်ပြောင်းလိုသည်မှာ သေချာပါသလား။",
682 + "unsupported_asset": "ဤပိုင်ဆိုင်မှုအတွက် ဤလုပ်ဆောင်ချက်ကို ကျွန်ုပ်တို့ မပံ့ပိုးပါ။ ကျေးဇူးပြု၍ ပံ့ပိုးပေးထားသော ပိုင်ဆိုင်မှုအမျိုးအစား၏ ပိုက်ဆံအိတ်ကို ဖန်တီးပါ သို့မဟုတ် ပြောင်းပါ။",
683 + "manage_pow_nodes": "PoW Nodes ကို စီမံပါ။",
684 "support_title_live_chat": "တိုက်ရိုက်ပံ့ပိုးမှု",
685 "support_description_live_chat": "အခမဲ့နှင့်အစာရှောင်ခြင်း! လေ့ကျင့်ထားသောထောက်ခံသူကိုယ်စားလှယ်များသည်ကူညီနိုင်သည်",
686 "support_title_guides": "ကိတ်မုန့်ပိုက်ဆံအိတ်လမ်းညွှန်များ",
687 "support_description_guides": "ဘုံပြ issues နာများအတွက်စာရွက်စာတမ်းများနှင့်ထောက်ခံမှု",
688 "support_title_other_links": "အခြားအထောက်အပံ့လင့်များ",
689 "support_description_other_links": "ကျွန်ုပ်တို့၏လူမှုအသိုင်းအဝိုင်းများသို့ 0 င်ရောက်ပါ",
690 + "choose_derivation": "ပိုက်ဆံအိတ်ကိုရွေးချယ်ပါ",
691 + "new_first_wallet_text": "သင့်ရဲ့ cryptocurrencrencres ကိုအလွယ်တကူလုံခြုံစွာထားရှိပါ",
692 "select_destination": "အရန်ဖိုင်အတွက် ဦးတည်ရာကို ရွေးပါ။",
693 "auto_generate_subaddresses": "အော်တို Generate Subaddresses",
694 "save_to_downloads": "ဒေါင်းလုဒ်များထံ သိမ်းဆည်းပါ။",
res/values/strings_nl.arb
+6
@@ -680,12 +680,18 @@
680 "manage_nodes": "Beheer knooppunten",
681 "etherscan_history": "Etherscan-geschiedenis",
682 "template_name": "Sjabloonnaam",
683 + "change_rep": "Vertegenwoordiger wijzigen",
684 + "change_rep_message": "Weet u zeker dat u van vertegenwoordiger wilt veranderen?",
685 + "unsupported_asset": "We ondersteunen deze actie niet voor dit item. Maak of schakel over naar een portemonnee van een ondersteund activatype.",
686 + "manage_pow_nodes": "PoW-knooppunten beheren",
687 "support_title_live_chat": "Live ondersteuning",
688 "support_description_live_chat": "Gratis en snel! Getrainde ondersteuningsvertegenwoordigers zijn beschikbaar om te helpen",
689 "support_title_guides": "Cake -portemonnee gidsen",
690 "support_description_guides": "Documentatie en ondersteuning voor gemeenschappelijke problemen",
691 "support_title_other_links": "Andere ondersteuningslinks",
692 "support_description_other_links": "Word lid van onze gemeenschappen of bereik ons ​​onze partners via andere methoden",
693 + "choose_derivation": "Kies portemonnee -afleiding",
694 + "new_first_wallet_text": "Houd uw cryptocurrency gemakkelijk veilig",
695 "select_destination": "Selecteer de bestemming voor het back-upbestand.",
696 "save_to_downloads": "Opslaan in downloads",
697 "select_buy_provider_notice": "Selecteer hierboven een koopprovider. U kunt dit scherm overslaan door uw standaard kopenprovider in te stellen in app -instellingen.",
res/values/strings_pl.arb
+6
@@ -680,12 +680,18 @@
680 "manage_nodes": "Zarządzaj węzłami",
681 "etherscan_history": "Historia Etherscanu",
682 "template_name": "Nazwa szablonu",
683 + "change_rep": "Zmień przedstawiciela",
684 + "change_rep_message": "Czy na pewno chcesz zmienić przedstawiciela?",
685 + "unsupported_asset": "Nie obsługujemy tego działania w przypadku tego zasobu. Utwórz lub przełącz się na portfel obsługiwanego typu aktywów.",
686 + "manage_pow_nodes": "Zarządzaj węzłami PoW",
687 "support_title_live_chat": "Wsparcie na żywo",
688 "support_description_live_chat": "Darmowe i szybkie! Do pomocy są dostępni przeszkoleni przedstawiciele wsparcia",
689 "support_title_guides": "Przewodniki portfela ciasta",
690 "support_description_guides": "Dokumentacja i wsparcie dla typowych problemów",
691 "support_title_other_links": "Inne linki wsparcia",
692 "support_description_other_links": "Dołącz do naszych społeczności lub skontaktuj się z nami naszymi partnerami za pomocą innych metod",
693 + "choose_derivation": "Wybierz wyprowadzenie portfela",
694 + "new_first_wallet_text": "Łatwo zapewnić bezpieczeństwo kryptowalut",
695 "select_destination": "Wybierz miejsce docelowe dla pliku kopii zapasowej.",
696 "save_to_downloads": "Zapisz w Pobranych",
697 "select_buy_provider_notice": "Wybierz powyższe dostawcę zakupu. Możesz pominąć ten ekran, ustawiając domyślnego dostawcę zakupu w ustawieniach aplikacji.",
res/values/strings_pt.arb
+6
@@ -679,12 +679,18 @@
679 "manage_nodes": "Gerenciar nós",
680 "etherscan_history": "história Etherscan",
681 "template_name": "Nome do modelo",
682 + "change_rep": "Alterar representante",
683 + "change_rep_message": "Tem certeza de que deseja alterar os representantes?",
684 + "unsupported_asset": "Não oferecemos suporte a esta ação para este recurso. Crie ou mude para uma carteira de um tipo de ativo compatível.",
685 + "manage_pow_nodes": "Gerenciar nós PoW",
686 "support_title_live_chat": "Apoio ao vivo",
687 "support_description_live_chat": "Livre e rápido! Representantes de suporte treinado estão disponíveis para ajudar",
688 "support_title_guides": "Guias da carteira de bolo",
689 "support_description_guides": "Documentação e suporte para problemas comuns",
690 "support_title_other_links": "Outros links de suporte",
691 "support_description_other_links": "Junte -se às nossas comunidades ou chegue a nós nossos parceiros por meio de outros métodos",
692 + "choose_derivation": "Escolha a derivação da carteira",
693 + "new_first_wallet_text": "Mantenha sua criptomoeda facilmente segura",
694 "select_destination": "Selecione o destino para o arquivo de backup.",
695 "save_to_downloads": "Salvar em Downloads",
696 "select_buy_provider_notice": "Selecione um provedor de compra acima. Você pode pular esta tela definindo seu provedor de compra padrão nas configurações de aplicativos.",
res/values/strings_ru.arb
+6
@@ -679,12 +679,18 @@
679 "manage_nodes": "Управление узлами",
680 "etherscan_history": "История Эфириума",
681 "template_name": "Имя Шаблона",
682 + "change_rep": "Изменить представителя",
683 + "change_rep_message": "Вы уверены, что хотите сменить представителя?",
684 + "unsupported_asset": "Мы не поддерживаем это действие для этого объекта. Пожалуйста, создайте или переключитесь на кошелек поддерживаемого типа активов.",
685 + "manage_pow_nodes": "Управление узлами PoW",
686 "support_title_live_chat": "Живая поддержка",
687 "support_description_live_chat": "Бесплатно и быстро! Обученные представители поддержки доступны для оказания помощи",
688 "support_title_guides": "Корт -гиды",
689 "support_description_guides": "Документация и поддержка общих вопросов",
690 "support_title_other_links": "Другие ссылки на поддержку",
691 "support_description_other_links": "Присоединяйтесь к нашим сообществам или охватите нас наших партнеров с помощью других методов",
692 + "choose_derivation": "Выберите вывод кошелька",
693 + "new_first_wallet_text": "Легко сохранить свою криптовалюту в безопасности",
694 "select_destination": "Пожалуйста, выберите место для файла резервной копии.",
695 "auto_generate_subaddresses": "Авто генерируйте Subaddresses",
696 "save_to_downloads": "Сохранить в загрузках",
res/values/strings_th.arb
+6
@@ -677,12 +677,18 @@
677 "manage_nodes": "จัดการโหนด",
678 "etherscan_history": "ประวัติอีเธอร์สแกน",
679 "template_name": "ชื่อแม่แบบ",
680 + "change_rep": "เปลี่ยนผู้แทน",
681 + "change_rep_message": "คุณแน่ใจหรือไม่ว่าต้องการเปลี่ยนตัวแทน",
682 + "unsupported_asset": "เราไม่สนับสนุนการกระทำนี้สำหรับเนื้อหานี้ โปรดสร้างหรือเปลี่ยนเป็นกระเป๋าเงินประเภทสินทรัพย์ที่รองรับ",
683 + "manage_pow_nodes": "จัดการโหนด PoW",
684 "support_title_live_chat": "การสนับสนุนสด",
685 "support_description_live_chat": "ฟรีและรวดเร็ว! ตัวแทนฝ่ายสนับสนุนที่ผ่านการฝึกอบรมพร้อมให้ความช่วยเหลือ",
686 "support_title_guides": "คู่มือกระเป๋าเงินเค้ก",
687 "support_description_guides": "เอกสารและการสนับสนุนสำหรับปัญหาทั่วไป",
688 "support_title_other_links": "ลิงค์สนับสนุนอื่น ๆ",
689 "support_description_other_links": "เข้าร่วมชุมชนของเราหรือเข้าถึงเราพันธมิตรของเราผ่านวิธีการอื่น ๆ",
690 + "choose_derivation": "เลือก Wallet Derivation",
691 + "new_first_wallet_text": "ทำให้สกุลเงินดิจิตอลของคุณปลอดภัยได้อย่างง่ายดาย",
692 "select_destination": "โปรดเลือกปลายทางสำหรับไฟล์สำรอง",
693 "auto_generate_subaddresses": "Auto สร้าง subaddresses",
694 "save_to_downloads": "บันทึกลงดาวน์โหลด",
res/values/strings_tr.arb
+6
@@ -678,12 +678,18 @@
678 "manage_nodes": "Düğümleri yönet",
679 "etherscan_history": "Etherscan geçmişi",
680 "template_name": "şablon adı",
681 + "change_rep": "Temsilciyi Değiştir",
682 + "change_rep_message": "Temsilcileri değiştirmek istediğinizden emin misiniz?",
683 + "unsupported_asset": "Bu öğe için bu eylemi desteklemiyoruz. Lütfen desteklenen bir varlık türünde bir cüzdan oluşturun veya cüzdana geçiş yapın.",
684 + "manage_pow_nodes": "PoW Düğümlerini Yönet",
685 "support_title_live_chat": "Canlı destek",
686 "support_description_live_chat": "Ücretsiz ve hızlı! Eğitimli destek temsilcileri yardımcı olmak için mevcuttur",
687 "support_title_guides": "Kek Cüzdan Kılavuzları",
688 "support_description_guides": "Ortak sorunlara belge ve destek",
689 "support_title_other_links": "Diğer destek bağlantıları",
690 "support_description_other_links": "Topluluklarımıza katılın veya ortaklarımıza diğer yöntemlerle bize ulaşın",
691 + "choose_derivation": "Cüzdan türevini seçin",
692 + "new_first_wallet_text": "Kripto para biriminizi kolayca güvende tutun",
693 "select_destination": "Lütfen yedekleme dosyası için hedef seçin.",
694 "save_to_downloads": "İndirilenlere Kaydet",
695 "select_buy_provider_notice": "Yukarıda bir satın alma sağlayıcısı seçin. App ayarlarında varsayılan satın alma sağlayıcınızı ayarlayarak bu ekranı atlayabilirsiniz.",
res/values/strings_uk.arb
+6
@@ -680,12 +680,18 @@
680 "manage_nodes": "Керуйте вузлами",
681 "etherscan_history": "Історія Etherscan",
682 "template_name": "Назва шаблону",
683 + "change_rep": "Зміна представника",
684 + "change_rep_message": "Ви впевнені, що хочете змінити представника?",
685 + "unsupported_asset": "Ми не підтримуємо цю дію для цього ресурсу. Створіть або перейдіть на гаманець підтримуваного типу активів.",
686 + "manage_pow_nodes": "Керуйте вузлами PoW",
687 "support_title_live_chat": "Жива підтримка",
688 "support_description_live_chat": "Безкоштовно і швидко! Навчені представники підтримки доступні для надання допомоги",
689 "support_title_guides": "Поклики для гаманців тортів",
690 "support_description_guides": "Документація та підтримка загальних питань",
691 "support_title_other_links": "Інші посилання на підтримку",
692 "support_description_other_links": "Приєднуйтесь до наших спільнот або досягайте нас нашими партнерами іншими методами",
693 + "choose_derivation": "Виберіть деривацію гаманця",
694 + "new_first_wallet_text": "Легко зберігайте свою криптовалюту в безпеці",
695 "select_destination": "Виберіть місце призначення для файлу резервної копії.",
696 "save_to_downloads": "Зберегти до завантажень",
697 "select_buy_provider_notice": "Виберіть постачальника купівлі вище. Ви можете пропустити цей екран, встановивши свого постачальника купівлі за замовчуванням у налаштуваннях додатків.",
res/values/strings_ur.arb
+6
@@ -671,12 +671,18 @@
671 "manage_nodes": "۔ﮟﯾﺮﮐ ﻢﻈﻧ ﺎﮐ ﺱﮈﻮﻧ",
672 "etherscan_history": "ﺦﯾﺭﺎﺗ ﯽﮐ ﻦﯿﮑﺳﺍ ﺮﮭﺘﯾﺍ",
673 "template_name": "ٹیمپلیٹ کا نام",
674 + "change_rep": "۔ﮟﯾﺮﮐ ﻞﯾﺪﺒﺗ ﮦﺪﻨﺋﺎﻤﻧ",
675 + "change_rep_message": "؟ﮟﯿﮨ ﮯﺘﮨﺎﭼ ﺎﻧﺮﮐ ﻞﯾﺪﺒﺗ ﻮﮐ ﮞﻭﺪﻨﺋﺎﻤﻧ ﯽﻌﻗﺍﻭ ﭖﺁ ﺎﯿﮐ",
676 + "unsupported_asset": "۔ﮟﯾﺮﮐ ﭻﺋﻮﺳ ﺮﭘ ﺱﺍ ﺎﯾ ﮟﯿﺋﺎﻨﺑ ﺱﺮﭘ ﺎﮐ ﻢﺴﻗ ﯽﮐ ﮧﺛﺎﺛﺍ ﮧﺘﻓﺎﯾ ﻥﻭﺎﻌﺗ ﻡﺮﮐ ﮦﺍﺮﺑ ۔ﮟﯿﮨ ﮯﺗﺮﮐ ﮟﯿﮩﻧ ﺖﯾﺎﻤﺣ ﯽﮐ ﯽﺋﺍﻭﺭﺭﺎﮐ ﺱﺍ ﮯﯿﻟ ﮯﮐ ﮧﺛﺎﺛﺍ ﺱﺍ ﻢﮨ",
677 + "manage_pow_nodes": "PoW ۔ﮟﯾﺮﮐ ﻢﻈﻧ ﺎﮐ ﺱﮈﻮﻧ",
678 "support_title_live_chat": "براہ راست مدد",
679 "support_description_live_chat": "مفت اور تیز! تربیت یافتہ معاون نمائندے مدد کے لئے دستیاب ہیں",
680 "support_title_guides": "کیک پرس گائڈز",
681 "support_description_guides": "عام مسائل کے لئے دستاویزات اور مدد",
682 "support_title_other_links": "دوسرے سپورٹ لنکس",
683 "support_description_other_links": "ہماری برادریوں میں شامل ہوں یا دوسرے طریقوں سے ہمارے شراکت داروں تک پہنچیں",
684 + "choose_derivation": "پرس سے ماخوذ منتخب کریں",
685 + "new_first_wallet_text": "آسانی سے اپنے cryptocurrency محفوظ رکھیں",
686 "select_destination": "۔ﮟﯾﺮﮐ ﺏﺎﺨﺘﻧﺍ ﺎﮐ ﻝﺰﻨﻣ ﮯﯿﻟ ﮯﮐ ﻞﺋﺎﻓ ﭖﺍ ﮏﯿﺑ ﻡﺮﮐ ﮦﺍﺮﺑ",
687 "auto_generate_subaddresses": "آٹو سب ایڈریس تیار کرتا ہے",
688 "save_to_downloads": "۔ﮟﯾﺮﮐ ﻅﻮﻔﺤﻣ ﮟﯿﻣ ﺯﮈﻮﻟ ﻥﺅﺍﮈ",
res/values/strings_yo.arb
+6
@@ -667,12 +667,18 @@
667 "manage_nodes": "Ṣakoso awọn apa",
668 "etherscan_history": "Etherscan itan",
669 "template_name": "Orukọ Awoṣe",
670 + "change_rep": "Yi Aṣoju",
671 + "change_rep_message": "Ṣe o da ọ loju pe o fẹ yi awọn aṣoju pada?",
672 + "unsupported_asset": "A ko ṣe atilẹyin iṣẹ yii fun dukia yii. Jọwọ ṣẹda tabi yipada si apamọwọ iru dukia atilẹyin.",
673 + "manage_pow_nodes": "Ṣakoso awọn Nodes PoW",
674 "support_title_live_chat": "Atilẹyin ifiwe",
675 "support_description_live_chat": "Free ati sare! Ti oṣiṣẹ awọn aṣoju wa lati ṣe iranlọwọ",
676 "support_title_guides": "Akara oyinbo Awọn Itọsọna Awọki oyinbo",
677 "support_description_guides": "Iwe ati atilẹyin fun awọn ọran ti o wọpọ",
678 "support_title_other_links": "Awọn ọna asopọ atilẹyin miiran",
679 "support_description_other_links": "Darapọ mọ awọn agbegbe wa tabi de wa awọn alabaṣepọ wa nipasẹ awọn ọna miiran",
680 + "choose_derivation": "Yan awọn apamọwọ apamọwọ",
681 + "new_first_wallet_text": "Ni rọọrun jẹ ki o jẹ ki o jẹ ki o jẹ ki a mu",
682 "monero_dark_theme": "Monero Dudu Akori",
683 "bitcoin_dark_theme": "Bitcoin Dark Akori",
684 "bitcoin_light_theme": "Bitcoin Light Akori",
res/values/strings_zh.arb
+6
@@ -672,12 +672,18 @@
672 "manage_nodes": "管理节点",
673 "etherscan_history": "以太扫描历史",
674 "template_name": "模板名称",
675 + "change_rep": "变革代表",
676 + "change_rep_message": "您确定要更换代表吗?",
677 + "unsupported_asset": "我们不支持针对该资产采取此操作。请创建或切换到支持的资产类型的钱包。",
678 + "manage_pow_nodes": "管理 PoW 节点",
679 "support_title_live_chat": "实时支持",
680 "support_description_live_chat": "免费快速!训练有素的支持代表可以协助",
681 "support_title_guides": "蛋糕钱包指南",
682 "support_description_guides": "对常见问题的文档和支持",
683 "support_title_other_links": "其他支持链接",
684 "support_description_other_links": "加入我们的社区或通过其他方法与我们联系我们的合作伙伴",
685 + "choose_derivation": "选择钱包推导",
686 + "new_first_wallet_text": "轻松确保您的加密货币安全",
687 "monero_dark_theme": "门罗币深色主题",
688 "bitcoin_dark_theme": "比特币黑暗主题",
689 "bitcoin_light_theme": "比特币浅色主题",
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 --ethereum"
13 + CONFIG_ARGS="--monero --bitcoin --haven --ethereum --nano"
14 ;;
15 $HAVEN)
16 CONFIG_ARGS="--haven"
scripts/ios/app_config.sh
+1 -1
@@ -28,7 +28,7 @@ case $APP_IOS_TYPE in
28 CONFIG_ARGS="--monero"
29 ;;
30 $CAKEWALLET)
31 - CONFIG_ARGS="--monero --bitcoin --haven --ethereum"
31 + CONFIG_ARGS="--monero --bitcoin --haven --ethereum --nano"
32 ;;
33 $HAVEN)
34 CONFIG_ARGS="--haven"
scripts/macos/app_config.sh
+1 -1
@@ -23,7 +23,7 @@ CONFIG_ARGS=""
23
24 case $APP_MACOS_TYPE in
25 $CAKEWALLET)
26 - CONFIG_ARGS="--monero --bitcoin --ethereum";; #--haven
26 + CONFIG_ARGS="--monero --bitcoin --ethereum --nano";; #--haven
27 esac
28
29 cp -rf pubspec_description.yaml pubspec.yaml
tool/append_translation.dart
+1 -1
@@ -19,4 +19,4 @@ void main(List<String> args) async {
19
20 appendStringToArbFile(fileName, name, translation);
21 }
22 -}
22 +}
\ No newline at end of file
tool/configure.dart
+207 -16
@@ -4,6 +4,7 @@ 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 nanoOutputPath = 'lib/nano/nano.dart';
8 const walletTypesPath = 'lib/wallet_types.g.dart';
9 const pubspecDefaultPath = 'pubspec_default.yaml';
10 const pubspecOutputPath = 'pubspec.yaml';
@@ -14,14 +15,32 @@ Future<void> main(List<String> args) async {
15 final hasMonero = args.contains('${prefix}monero');
16 final hasHaven = args.contains('${prefix}haven');
17 final hasEthereum = args.contains('${prefix}ethereum');
18 + final hasNano = args.contains('${prefix}nano');
19 + final hasBanano = args.contains('${prefix}banano');
20 +
21 await generateBitcoin(hasBitcoin);
22 await generateMonero(hasMonero);
23 await generateHaven(hasHaven);
24 await generateEthereum(hasEthereum);
25 + await generateNano(hasNano);
26 + // await generateBanano(hasEthereum);
27 +
28 await generatePubspec(
22 - hasMonero: hasMonero, hasBitcoin: hasBitcoin, hasHaven: hasHaven, hasEthereum: hasEthereum);
29 + hasMonero: hasMonero,
30 + hasBitcoin: hasBitcoin,
31 + hasHaven: hasHaven,
32 + hasEthereum: hasEthereum,
33 + hasNano: hasNano,
34 + hasBanano: hasBanano,
35 + );
36 await generateWalletTypes(
24 - hasMonero: hasMonero, hasBitcoin: hasBitcoin, hasHaven: hasHaven, hasEthereum: hasEthereum);
37 + hasMonero: hasMonero,
38 + hasBitcoin: hasBitcoin,
39 + hasHaven: hasHaven,
40 + hasEthereum: hasEthereum,
41 + hasNano: hasNano,
42 + hasBanano: hasBanano,
43 + );
44 }
45
46 Future<void> generateBitcoin(bool hasImplementation) async {
@@ -549,11 +568,156 @@ abstract class Ethereum {
568 await outputFile.writeAsString(output);
569 }
570
552 -Future<void> generatePubspec(
553 - {required bool hasMonero,
554 - required bool hasBitcoin,
555 - required bool hasHaven,
556 - required bool hasEthereum}) async {
571 +Future<void> generateNano(bool hasImplementation) async {
572 + final outputFile = File(nanoOutputPath);
573 + const nanoCommonHeaders = """
574 +import 'package:cw_core/cake_hive.dart';
575 +import 'package:cw_core/nano_account.dart';
576 +import 'package:cw_core/account.dart';
577 +import 'package:cw_core/node.dart';
578 +import 'package:cw_core/wallet_credentials.dart';
579 +import 'package:cw_core/wallet_info.dart';
580 +import 'package:cw_core/transaction_info.dart';
581 +import 'package:cw_core/transaction_history.dart';
582 +import 'package:cw_core/wallet_service.dart';
583 +import 'package:cw_core/output_info.dart';
584 +import 'package:cw_core/nano_account_info_response.dart';
585 +import 'package:mobx/mobx.dart';
586 +import 'package:hive/hive.dart';
587 +import 'package:cake_wallet/view_model/send/output.dart';
588 +""";
589 + const nanoCWHeaders = """
590 +import 'package:cw_nano/nano_client.dart';
591 +import 'package:cw_nano/nano_mnemonic.dart';
592 +import 'package:cw_nano/nano_wallet.dart';
593 +import 'package:cw_nano/nano_wallet_service.dart';
594 +import 'package:cw_nano/nano_transaction_info.dart';
595 +import 'package:cw_nano/nano_transaction_credentials.dart';
596 +import 'package:cw_nano/nano_wallet_creation_credentials.dart';
597 +// needed for nano_util:
598 +import 'dart:convert';
599 +import 'dart:typed_data';
600 +import 'package:convert/convert.dart';
601 +import "package:ed25519_hd_key/ed25519_hd_key.dart";
602 +import 'package:libcrypto/libcrypto.dart';
603 +import 'package:nanodart/nanodart.dart' as ND;
604 +import 'package:decimal/decimal.dart';
605 +""";
606 + const nanoCwPart = "part 'cw_nano.dart';";
607 + const nanoContent = """
608 +abstract class Nano {
609 + NanoAccountList getAccountList(Object wallet);
610 +
611 + Account getCurrentAccount(Object wallet);
612 +
613 + void setCurrentAccount(Object wallet, int id, String label, String? balance);
614 +
615 + WalletService createNanoWalletService(Box<WalletInfo> walletInfoSource);
616 +
617 + WalletCredentials createNanoNewWalletCredentials({
618 + required String name,
619 + String password,
620 + });
621 +
622 + WalletCredentials createNanoRestoreWalletFromSeedCredentials({
623 + required String name,
624 + required String password,
625 + required String mnemonic,
626 + DerivationType? derivationType,
627 + });
628 +
629 + WalletCredentials createNanoRestoreWalletFromKeysCredentials({
630 + required String name,
631 + required String password,
632 + required String seedKey,
633 + DerivationType? derivationType,
634 + });
635 +
636 + List<String> getNanoWordList(String language);
637 + Map<String, String> getKeys(Object wallet);
638 + Object createNanoTransactionCredentials(List<Output> outputs);
639 + Future<void> changeRep(Object wallet, String address);
640 + Future<void> updateTransactions(Object wallet);
641 + BigInt getTransactionAmountRaw(TransactionInfo transactionInfo);
642 +}
643 +
644 +abstract class NanoAccountList {
645 + ObservableList<NanoAccount> get accounts;
646 + void update(Object wallet);
647 + void refresh(Object wallet);
648 + Future<List<NanoAccount>> getAll(Object wallet);
649 + Future<void> addAccount(Object wallet, {required String label});
650 + Future<void> setLabelAccount(Object wallet, {required int accountIndex, required String label});
651 +}
652 +
653 +abstract class NanoUtil {
654 + String seedToPrivate(String seed, int index);
655 + String seedToAddress(String seed, int index);
656 + String seedToMnemonic(String seed);
657 + Future<String> mnemonicToSeed(String mnemonic);
658 + String privateKeyToPublic(String privateKey);
659 + String addressToPublicKey(String publicAddress);
660 + String privateKeyToAddress(String privateKey);
661 + String publicKeyToAddress(String publicKey);
662 + bool isValidSeed(String seed);
663 + Future<String> hdMnemonicListToSeed(List<String> words);
664 + Future<String> hdSeedToPrivate(String seed, int index);
665 + Future<String> hdSeedToAddress(String seed, int index);
666 + Future<String> uniSeedToAddress(String seed, int index, String type);
667 + Future<String> uniSeedToPrivate(String seed, int index, String type);
668 + bool isValidBip39Seed(String seed);
669 + static const int maxDecimalDigits = 6; // Max digits after decimal
670 + BigInt rawPerNano = BigInt.parse("1000000000000000000000000000000");
671 + BigInt rawPerNyano = BigInt.parse("1000000000000000000000000");
672 + BigInt rawPerBanano = BigInt.parse("100000000000000000000000000000");
673 + BigInt rawPerXMR = BigInt.parse("1000000000000");
674 + BigInt convertXMRtoNano = BigInt.parse("1000000000000000000");
675 + Decimal getRawAsDecimal(String? raw, BigInt? rawPerCur);
676 + String truncateDecimal(Decimal input, {int digits = maxDecimalDigits});
677 + String getRawAsUsableString(String? raw, BigInt rawPerCur);
678 + String getRawAccuracy(String? raw, BigInt rawPerCur);
679 + String getAmountAsRaw(String amount, BigInt rawPerCur);
680 +
681 + // derivationInfo:
682 + Future<AccountInfoResponse?> getInfoFromSeedOrMnemonic(
683 + DerivationType derivationType, {
684 + String? seedKey,
685 + String? mnemonic,
686 + required Node node,
687 + });
688 + Future<List<DerivationType>> compareDerivationMethods({
689 + String? mnemonic,
690 + String? privateKey,
691 + required Node node,
692 + });
693 +}
694 + """;
695 +
696 + const nanoEmptyDefinition = 'Nano? nano;\nNanoUtil? nanoUtil = CWNanoUtil();\n';
697 + const nanoCWDefinition = 'Nano? nano = CWNano();\nNanoUtil? nanoUtil = CWNanoUtil();\n';
698 +
699 + final output = '$nanoCommonHeaders\n' +
700 + (hasImplementation ? '$nanoCWHeaders\n' : '\n') +
701 + (hasImplementation ? '$nanoCwPart\n\n' : '\n') +
702 + (hasImplementation ? nanoCWDefinition : nanoEmptyDefinition) +
703 + '\n' +
704 + nanoContent;
705 +
706 + if (outputFile.existsSync()) {
707 + await outputFile.delete();
708 + }
709 +
710 + await outputFile.writeAsString(output);
711 +}
712 +
713 +Future<void> generatePubspec({
714 + required bool hasMonero,
715 + required bool hasBitcoin,
716 + required bool hasHaven,
717 + required bool hasEthereum,
718 + required bool hasNano,
719 + required bool hasBanano,
720 +}) async {
721 const cwCore = """
722 cw_core:
723 path: ./cw_core
@@ -578,6 +742,14 @@ Future<void> generatePubspec(
742 cw_ethereum:
743 path: ./cw_ethereum
744 """;
745 + const cwNano = """
746 + cw_nano:
747 + path: ./cw_nano
748 + """;
749 + const cwBanano = """
750 + cw_banano:
751 + path: ./cw_banano
752 + """;
753 final inputFile = File(pubspecOutputPath);
754 final inputText = await inputFile.readAsString();
755 final inputLines = inputText.split('\n');
@@ -592,16 +764,24 @@ Future<void> generatePubspec(
764 output += '\n$cwBitcoin';
765 }
766
767 + if (hasEthereum) {
768 + output += '\n$cwEthereum';
769 + }
770 +
771 + if (hasNano) {
772 + output += '\n$cwNano';
773 + }
774 +
775 + if (hasBanano) {
776 + output += '\n$cwBanano';
777 + }
778 +
779 if (hasHaven && !hasMonero) {
780 output += '\n$cwSharedExternal\n$cwHaven';
781 } else if (hasHaven) {
782 output += '\n$cwHaven';
783 }
784
601 - if (hasEthereum) {
602 - output += '\n$cwEthereum';
603 - }
604 -
785 final outputLines = output.split('\n');
786 inputLines.insertAll(dependenciesIndex + 1, outputLines);
787 final outputContent = inputLines.join('\n');
@@ -614,11 +794,14 @@ Future<void> generatePubspec(
794 await outputFile.writeAsString(outputContent);
795 }
796
617 -Future<void> generateWalletTypes(
618 - {required bool hasMonero,
619 - required bool hasBitcoin,
620 - required bool hasHaven,
621 - required bool hasEthereum}) async {
797 +Future<void> generateWalletTypes({
798 + required bool hasMonero,
799 + required bool hasBitcoin,
800 + required bool hasHaven,
801 + required bool hasEthereum,
802 + required bool hasNano,
803 + required bool hasBanano,
804 +}) async {
805 final walletTypesFile = File(walletTypesPath);
806
807 if (walletTypesFile.existsSync()) {
@@ -645,6 +828,14 @@ Future<void> generateWalletTypes(
828 outputContent += '\tWalletType.litecoin,\n';
829 }
830
831 + if (hasNano) {
832 + outputContent += '\tWalletType.nano,\n';
833 + }
834 +
835 + if (hasBanano) {
836 + outputContent += '\tWalletType.banano,\n';
837 + }
838 +
839 if (hasHaven) {
840 outputContent += '\tWalletType.haven,\n';
841 }