dev
dart 438 lines 12.7 KB
Raw
1 import 'dart:ffi';
2 import 'dart:io';
3 import 'dart:isolate';
4
5 import 'package:cw_core/utils/print_verbose.dart';
6 import 'package:cw_monero/api/account_list.dart';
7 import 'package:cw_monero/api/exceptions/wallet_creation_exception.dart';
8 import 'package:cw_monero/api/exceptions/wallet_opening_exception.dart';
9 import 'package:cw_monero/api/exceptions/wallet_restore_from_keys_exception.dart';
10 import 'package:cw_monero/api/exceptions/wallet_restore_from_seed_exception.dart';
11 import 'package:cw_monero/api/transaction_history.dart';
12 import 'package:cw_monero/api/wallet.dart';
13 import 'package:cw_monero/ledger.dart';
14 import 'package:flutter/foundation.dart';
15 import 'package:monero/src/monero.dart';
16 import 'package:monero/src/wallet2.dart';
17 import 'package:monero/monero.dart' as monero;
18
19 class MoneroCException implements Exception {
20 final String message;
21
22 MoneroCException(this.message);
23
24 @override
25 String toString() => message;
26 }
27
28 void checkIfMoneroCIsFine() {
29 final checksum = MoneroWalletChecksum();
30 final cppCsCpp = checksum.checksum_wallet2_api_c_cpp();
31 final cppCsH = checksum.checksum_wallet2_api_c_h();
32 final cppCsExp = checksum.checksum_wallet2_api_c_exp();
33
34 final dartCsCpp = monero.wallet2_api_c_cpp_sha256;
35 final dartCsH = monero.wallet2_api_c_h_sha256;
36 final dartCsExp = monero.wallet2_api_c_exp_sha256;
37
38 if (cppCsCpp != dartCsCpp) {
39 throw MoneroCException(
40 "monero_c and monero.dart cpp wrapper code mismatch.\nLogic errors can occur.\nRefusing to run in release mode.\ncpp: '$cppCsCpp'\ndart: '$dartCsCpp'");
41 }
42
43 if (cppCsH != dartCsH) {
44 throw MoneroCException(
45 "monero_c and monero.dart cpp wrapper header mismatch.\nLogic errors can occur.\nRefusing to run in release mode.\ncpp: '$cppCsH'\ndart: '$dartCsH'");
46 }
47
48 if (cppCsExp != dartCsExp && (Platform.isIOS || Platform.isMacOS)) {
49 throw MoneroCException(
50 "monero_c and monero.dart wrapper export list mismatch.\nLogic errors can occur.\nRefusing to run in release mode.\ncpp: '$cppCsExp'\ndart: '$dartCsExp'");
51 }
52 }
53
54 Wallet2WalletManager? _wmPtr;
55 Wallet2WalletManager wmPtr = (() {
56 try {
57 // Problems with the wallet? Crashes? Lags? this will print all calls to xmr
58 // codebase, so it will be easier to debug what happens. At least easier
59 // than plugging gdb in. Especially on windows/android.
60 monero.printStarts = false;
61 if (kDebugMode && debugMonero) {
62 MoneroWalletManagerFactory().setLogLevel(4);
63 }
64 _wmPtr ??= MoneroWalletManagerFactory().getWalletManager();
65 if (kDebugMode && debugMonero) {
66 MoneroWalletManagerFactory().setLogLevel(4);
67 }
68 printV("ptr: $_wmPtr");
69 } catch (e) {
70 printV(e);
71 rethrow;
72 }
73 return _wmPtr!;
74 })();
75
76 Wallet2Wallet createWalletPointer() {
77 final newWptr = wmPtr.createWallet(path: "", password: "", language: "", networkType: 0);
78 return newWptr;
79 }
80
81 void createWallet(
82 {required String path,
83 required String password,
84 required String language,
85 required String passphrase,
86 int nettype = 0}) {
87 txhistory = null;
88 language = getSeedLanguage(language)!;
89 final newW =
90 wmPtr.createWallet(path: path, password: password, language: language, networkType: 0);
91
92 int status = newW.status();
93 if (status != 0) {
94 throw WalletCreationException(message: newW.errorString());
95 }
96 newW.store(path: path);
97 setupBackgroundSync(password, newW);
98 newW.store(path: path);
99
100 currentWallet = newW;
101 currentWallet!.setCacheAttribute(key: "cakewallet.passphrase", value: passphrase);
102 currentWallet!.store(path: path);
103 openedWalletsByPath[path] = currentWallet!;
104 _lastOpenedWallet = path;
105 }
106
107 bool isWalletExist({required String path}) {
108 return wmPtr.walletExists(path);
109 }
110
111 void restoreWalletFromSeedSync(
112 {required String path,
113 required String password,
114 required String passphrase,
115 required String seed,
116 int nettype = 0,
117 int restoreHeight = 0}) {
118 txhistory = null;
119 final newW = wmPtr.recoveryWallet(
120 path: path,
121 password: password,
122 mnemonic: seed,
123 restoreHeight: restoreHeight,
124 seedOffset: passphrase,
125 networkType: 0,
126 );
127
128 final status = newW.status();
129
130 if (status != 0) {
131 final error = newW.errorString();
132 if (error.contains('word list failed verification')) {
133 throw WalletRestoreFromSeedException(
134 message:
135 "Seed verification failed, please make sure you entered the correct seed with the correct words order",
136 );
137 }
138 throw WalletRestoreFromSeedException(message: error);
139 }
140 currentWallet = newW;
141
142 setRefreshFromBlockHeight(height: restoreHeight);
143 setupBackgroundSync(password, newW);
144
145 currentWallet!.setCacheAttribute(key: "cakewallet.passphrase", value: passphrase);
146
147 openedWalletsByPath[path] = currentWallet!;
148
149 currentWallet!.store(path: path);
150 _lastOpenedWallet = path;
151 }
152
153 void storePassphrase({required String path, required String passphrase}) {
154 currentWallet!.setCacheAttribute(key: "cakewallet.passphrase", value: passphrase);
155 currentWallet!.store(path: path);
156 }
157
158 void restoreWalletFromKeys(
159 {required String path,
160 required String password,
161 required String language,
162 required String address,
163 required String viewKey,
164 required String spendKey,
165 int nettype = 0,
166 int restoreHeight = 0}) {
167 txhistory = null;
168 var newW = (spendKey != "")
169 ? wmPtr.createDeterministicWalletFromSpendKey(
170 path: path,
171 password: password,
172 language: language,
173 spendKeyString: spendKey,
174 newWallet: true,
175 // TODO(mrcyjanek): safe to remove
176 restoreHeight: restoreHeight)
177 : wmPtr.createWalletFromKeys(
178 path: path,
179 password: password,
180 restoreHeight: restoreHeight,
181 addressString: address,
182 viewKeyString: viewKey,
183 spendKeyString: spendKey,
184 nettype: 0,
185 );
186
187 int status = newW.status();
188 if (status != 0) {
189 throw WalletRestoreFromKeysException(message: newW.errorString());
190 }
191 newW.store(path: path);
192
193 // CW-712 - Try to restore deterministic wallet first, if the view key doesn't
194 // match the view key provided
195 if (spendKey != "") {
196 final viewKeyRestored = newW.secretViewKey();
197 if (viewKey != viewKeyRestored && viewKey != "") {
198 wmPtr.closeWallet(newW, false);
199 File(path).deleteSync();
200 File(path + ".keys").deleteSync();
201 newW = wmPtr.createWalletFromKeys(
202 path: path,
203 password: password,
204 restoreHeight: restoreHeight,
205 addressString: address,
206 viewKeyString: viewKey,
207 spendKeyString: spendKey,
208 nettype: 0,
209 );
210 int status = newW.status();
211 if (status != 0) {
212 throw WalletRestoreFromKeysException(message: newW.errorString());
213 }
214 newW.store(path: path);
215
216 setupBackgroundSync(password, newW);
217 }
218 }
219
220 currentWallet = newW;
221
222 openedWalletsByPath[path] = currentWallet!;
223 _lastOpenedWallet = path;
224 }
225
226 // English only, because normalization.
227 void restoreWalletFromPolyseedWithOffset(
228 {required String path,
229 required String password,
230 required String seed,
231 required String seedOffset,
232 required String language,
233 int nettype = 0}) {
234 txhistory = null;
235 final newW = wmPtr.createWalletFromPolyseed(
236 path: path,
237 password: password,
238 networkType: nettype,
239 mnemonic: seed,
240 seedOffset: seedOffset,
241 newWallet: true, // safe to remove
242 restoreHeight: 0,
243 kdfRounds: 1,
244 );
245
246 int status = newW.status();
247
248 if (status != 0) {
249 final err = newW.errorString();
250 printV("err: $err");
251 throw WalletRestoreFromKeysException(message: err);
252 }
253
254 currentWallet = newW;
255
256 currentWallet!.setCacheAttribute(key: "cakewallet.seed", value: seed);
257 currentWallet!.setCacheAttribute(key: "cakewallet.passphrase", value: seedOffset);
258 currentWallet!.store(path: path);
259
260 setupBackgroundSync(password, currentWallet!);
261 storeSync();
262
263 openedWalletsByPath[path] = currentWallet!;
264 }
265
266 void restoreWalletFromSpendKeySync(
267 {required String path,
268 required String password,
269 required String seed,
270 required String language,
271 required String spendKey,
272 int nettype = 0,
273 int restoreHeight = 0}) {
274 // txhistory = null;
275 // wptr = monero.WalletManager_createWalletFromKeys(
276 // wmPtr,
277 // path: path,
278 // password: password,
279 // restoreHeight: restoreHeight,
280 // addressString: '',
281 // spendKeyString: spendKey,
282 // viewKeyString: '',
283 // nettype: 0,
284 // );
285
286 txhistory = null;
287 final newW = wmPtr.createDeterministicWalletFromSpendKey(
288 path: path,
289 password: password,
290 language: language,
291 spendKeyString: spendKey,
292 newWallet: true, // TODO(mrcyjanek): safe to remove
293 restoreHeight: restoreHeight,
294 );
295
296 int status = newW.status();
297
298 if (status != 0) {
299 final err = newW.errorString();
300 printV("err: $err");
301 throw WalletRestoreFromKeysException(message: err);
302 }
303
304 currentWallet = newW;
305
306 currentWallet!.setCacheAttribute(key: "cakewallet.seed", value: seed);
307
308 storeSync();
309
310 setupBackgroundSync(password, currentWallet!);
311
312 openedWalletsByPath[path] = currentWallet!;
313 _lastOpenedWallet = path;
314 }
315
316 String _lastOpenedWallet = "";
317
318 Future<void> restoreWalletFromHardwareWallet(
319 {required String path,
320 required String password,
321 required String deviceName,
322 int nettype = 0,
323 int restoreHeight = 0}) async {
324 txhistory = null;
325 final wmPtr = MoneroWalletManagerFactory().getWalletManager().ffiAddress();
326 final newWptrAddr = await Isolate.run(() {
327 return monero.WalletManager_createWalletFromDevice(Pointer.fromAddress(wmPtr),
328 path: path, password: password, restoreHeight: restoreHeight, deviceName: deviceName)
329 .address;
330 });
331 final newW = MoneroWallet(Pointer.fromAddress(newWptrAddr));
332
333 final status = newW.status();
334
335 if (status != 0) {
336 final error = newW.errorString();
337 throw WalletRestoreFromSeedException(message: error);
338 }
339
340 currentWallet = newW;
341 currentWallet!.store(path: path);
342 _lastOpenedWallet = path;
343 openedWalletsByPath[path] = currentWallet!;
344 }
345
346 Map<String, Wallet2Wallet> openedWalletsByPath = {};
347
348 Future<void> loadWallet({required String path, required String password, int nettype = 0}) async {
349 if (openedWalletsByPath[path] != null) {
350 txhistory = null;
351 currentWallet = openedWalletsByPath[path]!;
352 return;
353 }
354 if (currentWallet == null || path != _lastOpenedWallet) {
355 if (currentWallet != null) {
356 final addr = currentWallet!.ffiAddress();
357 Isolate.run(() {
358 monero.Wallet_store(Pointer.fromAddress(addr));
359 });
360 }
361 txhistory = null;
362
363 /// Get the device type
364 /// 0: Software Wallet
365 /// 1: Ledger
366 /// 2: Trezor
367 var deviceType = 0;
368
369 if (Platform.isAndroid || Platform.isIOS) {
370 deviceType = wmPtr.queryWalletDevice(
371 keysFileName: "$path.keys",
372 password: password,
373 kdfRounds: 1,
374 );
375 final status = wmPtr.errorString();
376 if (status != "") {
377 printV("loadWallet:" + status);
378 // This is most likely closeWallet call leaking error. This is fine.
379 if (status.contains("failed to save file")) {
380 printV("loadWallet: error leaked: $status");
381 deviceType = 0;
382 } else {
383 throw WalletOpeningException(message: status);
384 }
385 }
386 } else {
387 deviceType = 0;
388 }
389
390 if (deviceType == 1) {
391 if (gLedger == null) {
392 throw Exception("Tried to open a ledger wallet with no ledger connected");
393 }
394 enableLedgerExchange(gLedger!);
395 }
396
397 final addr = wmPtr.ffiAddress();
398 final newWptrAddr = await Isolate.run(() {
399 return monero.WalletManager_openWallet(Pointer.fromAddress(addr),
400 path: path, password: password)
401 .address;
402 });
403
404 final newW = MoneroWallet(Pointer.fromAddress(newWptrAddr));
405
406 int status = newW.status();
407 if (status != 0) {
408 final err = newW.errorString();
409 printV("loadWallet:" + err);
410 throw WalletOpeningException(message: err);
411 }
412 if (deviceType == 0) {
413 setupBackgroundSync(password, newW);
414 }
415
416 currentWallet = newW;
417 _lastOpenedWallet = path;
418 openedWalletsByPath[path] = currentWallet!;
419 }
420 }
421
422 void setupBackgroundSync(String password, Wallet2Wallet wallet) {
423 if (isViewOnlyBySpendKey(wallet)) {
424 return;
425 }
426 wallet.setupBackgroundSync(
427 backgroundSyncType: 2, walletPassword: password, backgroundCachePassword: '');
428 if (wallet.status() != 0) {
429 // We simply ignore the error.
430 printV("setupBackgroundSync: ${wallet.errorString()}");
431 }
432 }
433
434 Future<void> openWallet({required String path, required String password, int nettype = 0}) async =>
435 loadWallet(path: path, password: password, nettype: nettype);
436
437 bool isViewOnlyBySpendKey(Wallet2Wallet? wallet) =>
438 int.tryParse((wallet ?? currentWallet!).secretSpendKey()) == 0;