dev
dart 410 lines 14.3 KB
Raw
1 import 'package:cake_wallet/bitcoin/bitcoin.dart';
2 import 'package:cake_wallet/generated/i18n.dart';
3 import 'package:cake_wallet/monero/monero.dart';
4 import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
5 import 'package:cake_wallet/store/app_store.dart';
6 import 'package:cake_wallet/wownero/wownero.dart';
7 import 'package:cake_wallet/zano/zano.dart';
8 import 'package:cake_wallet/zcash/zcash.dart';
9 import 'package:cw_core/transaction_direction.dart';
10 import 'package:cw_core/transaction_info.dart';
11 import 'package:cw_core/wallet_base.dart';
12 import 'package:cw_core/wallet_info.dart';
13 import 'package:cw_core/wallet_type.dart';
14 import 'package:cw_monero/monero_wallet.dart';
15 import 'package:flutter/foundation.dart';
16 import 'package:mobx/mobx.dart';
17 import 'package:cake_wallet/decred/decred.dart';
18 import 'package:polyseed/polyseed.dart';
19 import 'package:cake_wallet/evm/evm.dart';
20 import 'package:cake_wallet/reactions/wallet_connect.dart';
21
22 part 'wallet_keys_view_model.g.dart';
23
24 class WalletKeysViewModel = WalletKeysViewModelBase with _$WalletKeysViewModel;
25
26 abstract class WalletKeysViewModelBase with Store {
27 WalletKeysViewModelBase(this._appStore)
28 : _wallet = _appStore.wallet!,
29 _walletName = _appStore.wallet!.type.name,
30 _restoreHeight = _appStore.wallet!.walletInfo.restoreHeight,
31 _restoreHeightByTransactions = 0,
32 items = ObservableList<StandartListItem>(),
33 silentPaymentItems = ObservableList<StandartListItem>(),
34 _title = _getInitialTitle(_appStore.wallet!) {
35 _populateKeysItems();
36
37 reaction((_) => _appStore.wallet, (WalletBase? _wallet) {
38 _populateKeysItems();
39 });
40
41 if (_wallet.type == WalletType.monero ||
42 _wallet.type == WalletType.haven ||
43 _wallet.type == WalletType.wownero) {
44 final accountTransactions = _getWalletTransactions(_wallet);
45 if (accountTransactions.isNotEmpty) {
46 final incomingAccountTransactions =
47 accountTransactions.where((tx) => tx.direction == TransactionDirection.incoming);
48 if (incomingAccountTransactions.isNotEmpty) {
49 incomingAccountTransactions.toList().sort((a, b) => a.date.compareTo(b.date));
50 _restoreHeightByTransactions =
51 _getRestoreHeightByTransactions(_wallet.type, incomingAccountTransactions.first.date);
52 }
53 }
54 }
55 }
56
57 static String _getInitialTitle(WalletBase wallet) {
58 final baseName = walletTypeToString(wallet.type);
59 final keysLabel = S.current.wallet_keys;
60
61 final hwSuffix =
62 wallet.isHardwareWallet ? ' (${_hardwareWalletTypeLabel(wallet.hardwareWalletType!)})' : '';
63
64 return '$baseName $keysLabel $hwSuffix';
65 }
66
67 static String _hardwareWalletTypeLabel(HardwareWalletType type) {
68 switch (type) {
69 case HardwareWalletType.ledger:
70 return 'Ledger';
71 case HardwareWalletType.bitbox:
72 return 'BitBox';
73 case HardwareWalletType.trezor:
74 return 'Trezor';
75 case HardwareWalletType.cupcake:
76 return 'Cupcake';
77 case HardwareWalletType.coldcard:
78 return 'Coldcard';
79 case HardwareWalletType.seedsigner:
80 return 'SeedSigner';
81 case HardwareWalletType.keystone:
82 return 'Keystone';
83 }
84 }
85
86 bool get isBitcoin => _wallet.type == WalletType.bitcoin;
87
88 // this is incomplete, needs legacy seed toggle for XMR
89 bool get shouldShowHeightBox => [WalletType.bitcoin, WalletType.zcash].contains(_wallet.type);
90 final ObservableList<StandartListItem> items;
91 final ObservableList<StandartListItem> silentPaymentItems;
92
93 @observable
94 String _title;
95
96 String get title => _title;
97
98 final WalletBase _wallet;
99 final String _walletName;
100 final AppStore _appStore;
101 final int _restoreHeight;
102
103 int _restoreHeightByTransactions;
104
105 AppStore get appStore => _appStore;
106
107 String get seed => _wallet.seed != null ? _wallet.seed! : '';
108
109 bool get isLegacySeedOnly =>
110 [WalletType.monero, WalletType.wownero].contains(_wallet.type) &&
111 _wallet.seed != null &&
112 !(Polyseed.isValidSeed(_wallet.seed!) || _wallet.seed!.split(' ').length == 12);
113
114 String get legacySeed {
115 if ((_wallet.type == WalletType.monero || _wallet.type == WalletType.wownero) &&
116 _wallet.seed != null &&
117 (Polyseed.isValidSeed(_wallet.seed!) || _wallet.seed!.split(' ').length == 12)) {
118 final langName = PolyseedLang.getByPhrase(_wallet.seed!).nameEnglish;
119
120 if (_wallet.type == WalletType.monero) {
121 return (_wallet as MoneroWalletBase).seedLegacy(langName);
122 } else if (_wallet.type == WalletType.wownero) {
123 return wownero!.getLegacySeed(_wallet, langName);
124 }
125 }
126 return '';
127 }
128
129 String get legacyRestoreHeight {
130 if (_wallet.type == WalletType.monero) {
131 return monero!.getRestoreHeight(_wallet)?.toString() ?? '';
132 }
133 if (_wallet.type == WalletType.wownero) {
134 return wownero!.getRestoreHeight(_wallet)?.toString() ?? '';
135 }
136 return '';
137 }
138
139 @observable
140 bool obscurePassphrase = true;
141
142 String get passphrase {
143 return _wallet.passphrase ?? '';
144 }
145
146 /// The Regex split the words based on any whitespace character.
147 ///
148 /// Either standard ASCII space (U+0020) or the full-width space character (U+3000) used by the Japanese.
149 List<String> get seedSplit => seed.isNotEmpty ? seed.split(RegExp(r'\s+')) : [];
150
151 List<String> get legacySeedSplit => legacySeed.isNotEmpty ? legacySeed.split(RegExp(r'\s+')) : [];
152
153 void _populateKeysItems() {
154 items.clear();
155 silentPaymentItems.clear();
156
157 Map<String, String>? keys;
158
159 switch (_wallet.type) {
160 case WalletType.monero:
161 keys = monero!.getKeys(_wallet);
162 break;
163 case WalletType.wownero:
164 keys = wownero!.getKeys(_wallet);
165 break;
166 case WalletType.zano:
167 keys = zano!.getKeys(_wallet);
168 break;
169 case WalletType.zcash:
170 keys = zcash!.getKeys(_wallet);
171 break;
172 case WalletType.ethereum:
173 case WalletType.polygon:
174 case WalletType.base:
175 case WalletType.arbitrum:
176 case WalletType.bsc:
177 case WalletType.solana:
178 case WalletType.tron:
179 items.addAll([
180 if (_wallet.privateKey != null)
181 StandartListItem(
182 key: ValueKey('${_walletName}_wallet_private_key_item_key'),
183 title: S.current.private_key,
184 value: _wallet.privateKey!,
185 ),
186 ]);
187 break;
188 case WalletType.nano:
189 case WalletType.banano:
190 // we always have the hex version of the seed and private key:
191 items.addAll([
192 if (_wallet.hexSeed != null)
193 StandartListItem(
194 key: ValueKey('${_walletName}_wallet_hex_seed_key'),
195 title: S.current.seed_hex_form,
196 value: _wallet.hexSeed!,
197 ),
198 if (_wallet.privateKey != null)
199 StandartListItem(
200 key: ValueKey('${_walletName}_wallet_private_key_item_key'),
201 title: S.current.private_key,
202 value: _wallet.privateKey!,
203 ),
204 ]);
205 break;
206 case WalletType.decred:
207 final pubkey = decred!.pubkey(_appStore.wallet!);
208 items.addAll([
209 StandartListItem(title: S.current.view_key_public, value: pubkey),
210 ]);
211 break;
212 case WalletType.bitcoin:
213 case WalletType.litecoin:
214 case WalletType.bitcoinCash:
215 case WalletType.dogecoin:
216 if (_wallet.type == WalletType.bitcoin) {
217 keys = bitcoin!.getSilentPaymentKeys(_appStore.wallet!);
218 }
219
220 final electrumKeys = bitcoin!.getWalletKeys(_appStore.wallet!);
221
222 items.addAll([
223 if ((electrumKeys['masterFingerprint'] ?? '').isNotEmpty)
224 StandartListItem(
225 title: "Master fingerprint",
226 value: electrumKeys['masterFingerprint']!,
227 ),
228 if ((electrumKeys['wif'] ?? '').isNotEmpty)
229 StandartListItem(title: "WIF", value: electrumKeys['wif']!),
230 if ((electrumKeys['privateKey'] ?? '').isNotEmpty)
231 StandartListItem(title: S.current.private_key, value: electrumKeys['privateKey']!),
232 if (electrumKeys['publicKey'] != null)
233 StandartListItem(title: S.current.public_key, value: electrumKeys['publicKey']!),
234 if (electrumKeys['xpub'] != null)
235 StandartListItem(title: "xPub", value: electrumKeys['xpub']!),
236 ]);
237 break;
238 case WalletType.none:
239 case WalletType.haven:
240 break;
241 }
242
243 if (keys != null) {
244 final keysList = _wallet.type == WalletType.bitcoin ? silentPaymentItems : items;
245 keysList.addAll([
246 if ((keys['primaryAddress'] ?? '').isNotEmpty)
247 StandartListItem(
248 key: ValueKey('${_walletName}_wallet_primary_address_item_key'),
249 title: S.current.primary_address,
250 value: keys['primaryAddress']!),
251 if ((keys['publicSpendKey'] ?? '').isNotEmpty)
252 StandartListItem(
253 key: ValueKey('${_walletName}_wallet_public_spend_key_item_key'),
254 title: S.current.spend_key_public,
255 value: keys['publicSpendKey']!,
256 ),
257 if ((keys['privateSpendKey'] ?? '').isNotEmpty)
258 StandartListItem(
259 key: ValueKey('${_walletName}_wallet_private_spend_key_item_key'),
260 title: S.current.spend_key_private,
261 value: keys['privateSpendKey']!,
262 ),
263 if ((keys['publicViewKey'] ?? '').isNotEmpty)
264 StandartListItem(
265 key: ValueKey('${_walletName}_wallet_public_view_key_item_key'),
266 title: S.current.view_key_public,
267 value: keys['publicViewKey']!,
268 ),
269 if ((keys['privateViewKey'] ?? '').isNotEmpty)
270 StandartListItem(
271 key: ValueKey('${_walletName}_wallet_private_view_key_item_key'),
272 title: S.current.view_key_private,
273 value: keys['privateViewKey']!,
274 ),
275 if ((keys['tsk'] ?? '').isNotEmpty)
276 StandartListItem(
277 key: ValueKey('${_walletName}_wallet_transparent_secret_key_item_key'),
278 title: S.current.transparent_secret_key,
279 value: keys['tsk']!,
280 ),
281 if ((keys['uvk'] ?? '').isNotEmpty)
282 StandartListItem(
283 key: ValueKey('${_walletName}_wallet_unified_view_key_item_key'),
284 title: S.current.unified_view_key,
285 value: keys['uvk']!,
286 ),
287 ]);
288 }
289 }
290
291 Future<int?> _currentHeight() async {
292 if (_wallet.type == WalletType.monero) {
293 return await monero!.getCurrentHeight();
294 }
295 if (_wallet.type == WalletType.wownero) {
296 return await wownero!.getCurrentHeight();
297 }
298 return null;
299 }
300
301 String get _scheme {
302 switch (_wallet.type) {
303 case WalletType.monero:
304 return 'monero-wallet';
305 case WalletType.bitcoin:
306 return 'bitcoin-wallet';
307 case WalletType.litecoin:
308 return 'litecoin-wallet';
309 case WalletType.haven:
310 return 'haven-wallet';
311 case WalletType.ethereum:
312 return 'ethereum-wallet';
313 case WalletType.bitcoinCash:
314 return 'bitcoincash-wallet';
315 case WalletType.nano:
316 return 'nano-wallet';
317 case WalletType.banano:
318 return 'banano-wallet';
319 case WalletType.polygon:
320 return 'polygon-wallet';
321 case WalletType.base:
322 return 'base-wallet';
323 case WalletType.arbitrum:
324 return 'arbitrum-wallet';
325 case WalletType.bsc:
326 return 'bsc-wallet';
327 case WalletType.solana:
328 return 'solana-wallet';
329 case WalletType.tron:
330 return 'tron-wallet';
331 case WalletType.wownero:
332 return 'wownero-wallet';
333 case WalletType.zano:
334 return 'zano-wallet';
335 case WalletType.decred:
336 return 'decred-wallet';
337 case WalletType.dogecoin:
338 return 'dogecoin-wallet';
339 case WalletType.zcash:
340 return 'zcash-wallet';
341 case WalletType.none:
342 throw Exception('Unexpected wallet type: ${_wallet.type.toString()} for wallet keys');
343 }
344 }
345
346 Future<String?> get restoreHeight async {
347 if (_wallet.type == WalletType.monero) {
348 return monero!.getRestoreHeight(_wallet)?.toString();
349 }
350 if (_wallet.type == WalletType.wownero) {
351 return wownero!.getRestoreHeight(_wallet)?.toString();
352 }
353 if (_wallet.type == WalletType.zcash) {
354 return zcash!.getKeys(_wallet)["restoreHeight"]?.toString();
355 }
356 if (_restoreHeightByTransactions != 0)
357 return getRoundedRestoreHeight(_restoreHeightByTransactions);
358 if (_restoreHeight != 0) return _restoreHeight.toString();
359
360 final currentHeight = await _currentHeight();
361 if (currentHeight == null) return null;
362
363 return getRoundedRestoreHeight(currentHeight);
364 }
365
366 Future<Map<String, String>> get _queryParams async {
367 final restoreHeightResult = await restoreHeight;
368 return {
369 if (_wallet.seed != null) 'seed': _wallet.seed!,
370 if (_wallet.seed == null && _wallet.hexSeed != null) 'hexSeed': _wallet.hexSeed!,
371 if (_wallet.seed == null && _wallet.privateKey != null) 'private_key': _wallet.privateKey!,
372 if (restoreHeightResult != null) ...{'height': restoreHeightResult},
373 if (_wallet.passphrase != null) 'passphrase': _wallet.passphrase!
374 };
375 }
376
377 Future<Map<String, String>> get _queryParamsForLegacy async {
378 final restoreHeightResult = await restoreHeight;
379 return {
380 if (legacySeed.isNotEmpty) 'seed': legacySeed,
381 if (restoreHeightResult != null) ...{'height': restoreHeightResult},
382 if ((_wallet.passphrase ?? '') != '') 'passphrase': _wallet.passphrase!
383 };
384 }
385
386 Future<Uri> getUrl(bool isLegacySeed) async => Uri(
387 scheme: _scheme,
388 queryParameters: isLegacySeed ? await _queryParamsForLegacy : await _queryParams,
389 );
390
391 List<TransactionInfo> _getWalletTransactions(WalletBase wallet) {
392 if (wallet.type == WalletType.monero) {
393 return monero!.getTransactionHistory(wallet).transactions.values.toList();
394 } else if (wallet.type == WalletType.wownero) {
395 return wownero!.getTransactionHistory(wallet).transactions.values.toList();
396 }
397 return [];
398 }
399
400 int _getRestoreHeightByTransactions(WalletType type, DateTime date) {
401 if (type == WalletType.monero) {
402 return monero!.getHeightByDate(date: date);
403 } else if (type == WalletType.wownero) {
404 return wownero!.getHeightByDate(date: date);
405 }
406 return 0;
407 }
408
409 String getRoundedRestoreHeight(int height) => ((height / 1000).floor() * 1000).toString();
410 }