dev
dart 430 lines 14.5 KB
Raw
1 import 'dart:ffi';
2 import 'dart:io';
3 import 'package:cw_core/exceptions.dart' show WalletDeprecationException;
4 import 'package:cw_core/monero_wallet_utils.dart';
5 import 'package:cw_core/pathForWallet.dart';
6 import 'package:cw_core/unspent_coins_info.dart';
7 import 'package:cw_core/utils/print_verbose.dart';
8 import 'package:cw_core/wallet_base.dart';
9 import 'package:cw_core/wallet_credentials.dart';
10 import 'package:cw_core/wallet_info.dart';
11 import 'package:cw_core/wallet_service.dart';
12 import 'package:cw_core/wallet_type.dart';
13 import 'package:cw_core/get_height_by_date.dart';
14 import 'package:cw_wownero/api/account_list.dart';
15 import 'package:cw_wownero/api/exceptions/wallet_opening_exception.dart';
16 import 'package:cw_wownero/api/wallet_manager.dart' as wownero_wallet_manager;
17 import 'package:cw_wownero/api/wallet_manager.dart';
18 import 'package:cw_wownero/wownero_wallet.dart';
19 import 'package:flutter/widgets.dart';
20 import 'package:hive/hive.dart';
21 import 'package:polyseed/polyseed.dart';
22 import 'package:monero/wownero.dart' as wownero;
23
24 class WowneroNewWalletCredentials extends WalletCredentials {
25 WowneroNewWalletCredentials(
26 {required String name,
27 required this.language,
28 required this.isPolyseed,
29 this.passphrase,
30 String? password})
31 : super(name: name, password: password);
32
33 final String language;
34 final bool isPolyseed;
35 final String? passphrase;
36 }
37
38 class WowneroRestoreWalletFromSeedCredentials extends WalletCredentials {
39 WowneroRestoreWalletFromSeedCredentials(
40 {required String name,
41 required this.mnemonic,
42 required this.passphrase,
43 int height = 0,
44 String? password})
45 : super(name: name, password: password, height: height);
46
47 final String mnemonic;
48 final String passphrase;
49 }
50
51 class WowneroWalletLoadingException implements Exception {
52 @override
53 String toString() => 'Failure to load the wallet.';
54 }
55
56 class WowneroRestoreWalletFromKeysCredentials extends WalletCredentials {
57 WowneroRestoreWalletFromKeysCredentials(
58 {required String name,
59 required String password,
60 required this.language,
61 required this.address,
62 required this.viewKey,
63 required this.spendKey,
64 int height = 0})
65 : super(name: name, password: password, height: height);
66
67 final String language;
68 final String address;
69 final String viewKey;
70 final String spendKey;
71 }
72
73 class WowneroWalletService extends WalletService<
74 WowneroNewWalletCredentials,
75 WowneroRestoreWalletFromSeedCredentials,
76 WowneroRestoreWalletFromKeysCredentials,
77 WowneroNewWalletCredentials> {
78 WowneroWalletService(this.unspentCoinsInfoSource);
79
80 final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
81
82 static bool walletFilesExist(String path) =>
83 !File(path).existsSync() && !File('$path.keys').existsSync();
84
85 @override
86 WalletType getType() => WalletType.wownero;
87
88 @override
89 Future<WowneroWallet> create(WowneroNewWalletCredentials credentials, {bool? isTestnet}) async {
90 try {
91 final path = await pathForWallet(name: credentials.name, type: getType());
92
93 if (credentials.isPolyseed) {
94 final polyseed = Polyseed.create();
95 final lang = PolyseedLang.getByEnglishName(credentials.language);
96
97 if (credentials.passphrase != null) polyseed.crypt(credentials.passphrase!);
98
99 final heightOverride =
100 getWowneroHeightByDate(date: DateTime.now().subtract(Duration(days: 2)));
101
102 return _restoreFromPolyseed(
103 path, credentials.password!, polyseed, credentials.walletInfo!, lang,
104 overrideHeight: heightOverride, passphrase: credentials.passphrase);
105 }
106
107 await wownero_wallet_manager.createWallet(
108 path: path,
109 password: credentials.password!,
110 language: credentials.language,
111 passphrase: credentials.passphrase ?? '');
112 final wallet = WowneroWallet(
113 walletInfo: credentials.walletInfo!,
114 derivationInfo: await credentials.walletInfo!.getDerivationInfo(),
115 unspentCoinsInfo: unspentCoinsInfoSource,
116 password: credentials.password!);
117 await wallet.init();
118
119 return wallet;
120 } catch (e) {
121 // TODO: Implement Exception for wallet list service.
122 printV('WowneroWalletsManager Error: ${e.toString()}');
123 rethrow;
124 }
125 }
126
127 @override
128 Future<bool> isWalletExit(String name) async {
129 try {
130 final path = await pathForWallet(name: name, type: getType());
131 return wownero_wallet_manager.isWalletExist(path: path);
132 } catch (e) {
133 // TODO: Implement Exception for wallet list service.
134 printV('WowneroWalletsManager Error: $e');
135 rethrow;
136 }
137 }
138
139 @override
140 Future<WowneroWallet> openWallet(String name, String password) async {
141 WowneroWallet? wallet;
142 try {
143 final path = await pathForWallet(name: name, type: getType());
144
145 if (walletFilesExist(path)) {
146 await repairOldAndroidWallet(name);
147 }
148
149 await wownero_wallet_manager.openWalletAsync({'path': path, 'password': password});
150 final walletInfo = await WalletInfo.get(name, getType());
151 if (walletInfo == null) {
152 throw Exception('Wallet not found');
153 }
154
155 wallet = WowneroWallet(
156 walletInfo: walletInfo,
157 derivationInfo: await walletInfo.getDerivationInfo(),
158 unspentCoinsInfo: unspentCoinsInfoSource,
159 password: password);
160 throw WalletDeprecationException(seed: wallet.seed, curr: wallet.currency);
161
162 final isValid = wallet.walletAddresses.validate();
163
164 if (!isValid) {
165 await restoreOrResetWalletFiles(name);
166 wallet.close(shouldCleanup: false);
167 return openWallet(name, password);
168 }
169
170 await wallet.init();
171 return wallet;
172 } catch (e, s) {
173 rethrow;
174 // TODO: Implement Exception for wallet list service.
175
176 final bool isBadAlloc = e.toString().contains('bad_alloc') ||
177 (e is WalletOpeningException &&
178 (e.message == 'std::bad_alloc' || e.message.contains('bad_alloc')));
179
180 final bool doesNotCorrespond = e.toString().contains('does not correspond') ||
181 (e is WalletOpeningException && e.message.contains('does not correspond'));
182
183 final bool isMissingCacheFilesIOS = e.toString().contains('basic_string') ||
184 (e is WalletOpeningException && e.message.contains('basic_string'));
185
186 final bool isMissingCacheFilesAndroid = e.toString().contains('input_stream') ||
187 e.toString().contains('input stream error') ||
188 (e is WalletOpeningException &&
189 (e.message.contains('input_stream') || e.message.contains('input stream error')));
190
191 final bool invalidSignature = e.toString().contains('invalid signature') ||
192 (e is WalletOpeningException && e.message.contains('invalid signature'));
193
194 if (!isBadAlloc &&
195 !doesNotCorrespond &&
196 !isMissingCacheFilesIOS &&
197 !isMissingCacheFilesAndroid &&
198 !invalidSignature &&
199 wallet != null &&
200 wallet.onError != null) {
201 wallet.onError!(FlutterErrorDetails(exception: e, stack: s));
202 }
203
204 await restoreOrResetWalletFiles(name);
205 return openWallet(name, password);
206 }
207 }
208
209 @override
210 Future<void> remove(String wallet) async {
211 final path = await pathForWalletDir(name: wallet, type: getType());
212 if (openedWalletsByPath["$path/$wallet"] != null) {
213 // NOTE: this is realistically only required on windows.
214 printV("closing wallet");
215 final wmaddr = wmPtr.address;
216 final waddr = openedWalletsByPath["$path/$wallet"]!.address;
217 // await Isolate.run(() {
218 wownero.WalletManager_closeWallet(
219 Pointer.fromAddress(wmaddr), Pointer.fromAddress(waddr), false);
220 // });
221 openedWalletsByPath.remove("$path/$wallet");
222 printV("wallet closed");
223 }
224
225 final file = Directory(path);
226 final isExist = file.existsSync();
227
228 if (isExist) {
229 await file.delete(recursive: true);
230 }
231
232 final walletInfo = await WalletInfo.get(wallet, getType());
233 if (walletInfo == null) {
234 throw Exception('Wallet not found');
235 }
236 await WalletInfo.delete(walletInfo);
237 }
238
239 @override
240 Future<void> rename(String currentName, String password, String newName) async {
241 final currentWalletInfo = await WalletInfo.get(currentName, getType());
242 if (currentWalletInfo == null) {
243 throw Exception('Wallet not found');
244 }
245 final currentWallet = WowneroWallet(
246 walletInfo: currentWalletInfo,
247 derivationInfo: await currentWalletInfo.getDerivationInfo(),
248 unspentCoinsInfo: unspentCoinsInfoSource,
249 password: password);
250
251 await currentWallet.renameWalletFiles(newName);
252
253 final newWalletInfo = currentWalletInfo;
254 newWalletInfo.id = WalletBase.idFor(newName, getType());
255 newWalletInfo.name = newName;
256
257 await newWalletInfo.save();
258 }
259
260 @override
261 Future<WowneroWallet> restoreFromKeys(WowneroRestoreWalletFromKeysCredentials credentials,
262 {bool? isTestnet}) async {
263 try {
264 final path = await pathForWallet(name: credentials.name, type: getType());
265 await wownero_wallet_manager.restoreFromKeys(
266 path: path,
267 password: credentials.password!,
268 language: credentials.language,
269 restoreHeight: credentials.height!,
270 address: credentials.address,
271 viewKey: credentials.viewKey,
272 spendKey: credentials.spendKey);
273 final wallet = WowneroWallet(
274 walletInfo: credentials.walletInfo!,
275 derivationInfo: await credentials.walletInfo!.getDerivationInfo(),
276 unspentCoinsInfo: unspentCoinsInfoSource,
277 password: credentials.password!);
278 await wallet.init();
279
280 return wallet;
281 } catch (e) {
282 // TODO: Implement Exception for wallet list service.
283 printV('WowneroWalletsManager Error: $e');
284 rethrow;
285 }
286 }
287
288 @override
289 Future<WowneroWallet> restoreFromHardwareWallet(WowneroNewWalletCredentials credentials) {
290 throw UnimplementedError(
291 "Restoring a Wownero wallet from a hardware wallet is not yet supported!");
292 }
293
294 @override
295 Future<WowneroWallet> restoreFromSeed(WowneroRestoreWalletFromSeedCredentials credentials,
296 {bool? isTestnet}) async {
297 // Restore from Polyseed
298 if (Polyseed.isValidSeed(credentials.mnemonic)) {
299 return restoreFromPolyseed(credentials);
300 }
301
302 try {
303 final path = await pathForWallet(name: credentials.name, type: getType());
304 await wownero_wallet_manager.restoreFromSeed(
305 path: path,
306 password: credentials.password!,
307 passphrase: credentials.passphrase,
308 seed: credentials.mnemonic,
309 restoreHeight: credentials.height!);
310 final wallet = WowneroWallet(
311 walletInfo: credentials.walletInfo!,
312 derivationInfo: await credentials.walletInfo!.getDerivationInfo(),
313 unspentCoinsInfo: unspentCoinsInfoSource,
314 password: credentials.password!);
315 await wallet.init();
316
317 return wallet;
318 } catch (e) {
319 // TODO: Implement Exception for wallet list service.
320 printV('WowneroWalletsManager Error: $e');
321 rethrow;
322 }
323 }
324
325 Future<WowneroWallet> restoreFromPolyseed(
326 WowneroRestoreWalletFromSeedCredentials credentials) async {
327 try {
328 final path = await pathForWallet(name: credentials.name, type: getType());
329 final polyseedCoin = PolyseedCoin.POLYSEED_WOWNERO;
330 final lang = PolyseedLang.getByPhrase(credentials.mnemonic);
331 final polyseed = Polyseed.decode(credentials.mnemonic, lang, polyseedCoin);
332
333 return _restoreFromPolyseed(
334 path, credentials.password!, polyseed, credentials.walletInfo!, lang,
335 passphrase: credentials.passphrase);
336 } catch (e) {
337 // TODO: Implement Exception for wallet list service.
338 printV('WowneroWalletsManager Error: $e');
339 rethrow;
340 }
341 }
342
343 Future<WowneroWallet> _restoreFromPolyseed(
344 String path, String password, Polyseed polyseed, WalletInfo walletInfo, PolyseedLang lang,
345 {PolyseedCoin coin = PolyseedCoin.POLYSEED_WOWNERO,
346 int? overrideHeight,
347 String? passphrase}) async {
348 if (polyseed.isEncrypted == false && (passphrase ?? '') != "") {
349 // Fallback to the different passphrase offset method, when a passphrase
350 // was provided but the polyseed is not encrypted.
351 wownero_wallet_manager.restoreWalletFromPolyseedWithOffset(
352 path: path,
353 password: password,
354 seed: polyseed.encode(lang, coin),
355 seedOffset: passphrase ?? '',
356 language: "English");
357
358 final wallet = WowneroWallet(
359 walletInfo: walletInfo,
360 derivationInfo: await walletInfo.getDerivationInfo(),
361 unspentCoinsInfo: unspentCoinsInfoSource,
362 password: password,
363 );
364 await wallet.init();
365
366 return wallet;
367 }
368
369 if (polyseed.isEncrypted) polyseed.crypt(passphrase ?? '');
370
371 final height = overrideHeight ??
372 getWowneroHeightByDate(date: DateTime.fromMillisecondsSinceEpoch(polyseed.birthday * 1000));
373 final spendKey = polyseed.generateKey(coin, 32).toHexString();
374 final seed = polyseed.encode(lang, coin);
375
376 walletInfo.isRecovery = true;
377 walletInfo.restoreHeight = height;
378
379 await wownero_wallet_manager.restoreFromSpendKey(
380 path: path,
381 password: password,
382 seed: seed,
383 language: lang.nameEnglish,
384 restoreHeight: height,
385 spendKey: spendKey);
386
387 wownero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed);
388 wownero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.passphrase", value: passphrase ?? '');
389
390 final wallet = WowneroWallet(
391 walletInfo: walletInfo,
392 derivationInfo: await walletInfo.getDerivationInfo(),
393 unspentCoinsInfo: unspentCoinsInfoSource,
394 password: password);
395 await wallet.init();
396
397 return wallet;
398 }
399
400 Future<void> repairOldAndroidWallet(String name) async {
401 try {
402 if (!Platform.isAndroid) {
403 return;
404 }
405
406 final oldAndroidWalletDirPath = await outdatedAndroidPathForWalletDir(name: name);
407 final dir = Directory(oldAndroidWalletDirPath);
408
409 if (!dir.existsSync()) {
410 return;
411 }
412
413 final newWalletDirPath = await pathForWalletDir(name: name, type: getType());
414
415 dir.listSync().forEach((f) {
416 final file = File(f.path);
417 final name = f.path.split('/').last;
418 final newPath = newWalletDirPath + '/$name';
419 final newFile = File(newPath);
420
421 if (!newFile.existsSync()) {
422 newFile.createSync();
423 }
424 newFile.writeAsBytesSync(file.readAsBytesSync());
425 });
426 } catch (e) {
427 printV(e.toString());
428 }
429 }
430 }