dev
dart 194 lines 5.84 KB
Raw
1 import 'dart:io';
2
3 import 'package:bip39/bip39.dart' as bip39;
4 import 'package:cw_core/pathForWallet.dart';
5 import 'package:cw_core/utils/print_verbose.dart';
6 import 'package:cw_core/wallet_base.dart';
7 import 'package:cw_core/wallet_credentials.dart';
8 import 'package:cw_core/wallet_info.dart';
9 import 'package:cw_core/wallet_service.dart';
10 import 'package:cw_core/wallet_type.dart';
11 import 'package:cw_zcash/cw_zcash.dart';
12 import 'package:cw_zcash/src/migrate.dart';
13 import 'package:mutex/mutex.dart';
14 import 'package:path/path.dart' as p;
15
16 class ZcashWalletService
17 extends
18 WalletService<
19 ZcashNewWalletCredentials,
20 ZcashFromSeedWalletCredentials,
21 ZcashFromKeysWalletCredentials,
22 ZcashNewWalletCredentials
23 > {
24 ZcashWalletService();
25
26 static Mutex dbMutex = Mutex();
27 static int dbMutexQueue = 0;
28 static Future<T> runInDbMutex<T>(final Future<T> Function() call) async {
29 dbMutexQueue++;
30 await dbMutex.acquire();
31 try {
32 return await call();
33 } finally {
34 dbMutex.release();
35 dbMutexQueue--;
36 }
37 }
38
39 static Set<String> autoshieldTx = {};
40
41 static String normalizeTxId(final String txId) =>
42 txId.replaceAll(RegExp(r'[^a-fA-F0-9]'), '').toLowerCase();
43
44 static bool isAutoshieldTx(final String txHash) => autoshieldTx.contains(normalizeTxId(txHash));
45
46 static Future<void> addShieldedTx(final String txId) async {
47 final pathForWalletType = await pathForWalletTypeDir(type: type);
48 final shieldedListFile = p.join(pathForWalletType, "autoshield_list.v2.txt");
49 autoshieldTx.add(normalizeTxId(txId));
50 final shieldedList = File(shieldedListFile);
51 shieldedList.writeAsStringSync(autoshieldTx.join("\n"));
52 }
53
54 static Future<void> loadShieldTxs() async {
55 try {
56 final pathForWalletType = await pathForWalletTypeDir(type: type);
57 final shieldedListFile = p.join(pathForWalletType, "autoshield_list.v2.txt");
58 final shieldedList = File(shieldedListFile);
59 if (!shieldedList.existsSync()) {
60 return;
61 }
62 autoshieldTx = shieldedList
63 .readAsLinesSync()
64 .map(normalizeTxId)
65 .where((final line) => line.isNotEmpty)
66 .toSet();
67 } catch (e) {
68 printV("loadShieldTxs failed: $e");
69 }
70 }
71
72 static bool walletFilesExist(final String path) =>
73 !File(path).existsSync() && !File('$path.keys').existsSync();
74
75 @override
76 WalletType getType() => type;
77
78 static WalletType get type => WalletType.zcash;
79
80 @override
81 Future<ZcashWallet> create(final WalletCredentials credentials, {final bool? isTestnet}) {
82 return ZcashWalletBase.create(credentials);
83 }
84
85 @override
86 Future<bool> isWalletExit(final String name) async {
87 final oldPath = (await pathForWallet(name: name, type: getType()));
88 final path = (await pathForWallet(name: name, type: getType())) + ".v2";
89 return File(path).existsSync() || File(oldPath).existsSync();
90 }
91
92 @override
93 Future<ZcashWallet> openWallet(final String name, final String password) async {
94 final walletInfo = await WalletInfo.get(name, getType());
95 if (walletInfo == null) {
96 throw Exception('Wallet not found');
97 }
98 await ZcashWalletBase.$init(network: ZcashWalletBase.networkFor(walletInfo));
99 if (await isWalletExit(name)) {
100 final path = (await pathForWallet(name: name, type: getType())) + ".v2";
101 if (!File(path).existsSync()) {
102 await migrateOldSqliteToZkool2(walletName: name);
103 }
104 }
105
106 await loadShieldTxs();
107 try {
108 final wallet = await ZcashWalletBase.open(
109 name: name,
110 password: password,
111 walletInfo: walletInfo,
112 );
113 await wallet.init();
114 await saveBackup(name);
115 return wallet;
116 } catch (e) {
117 await restoreWalletFilesFromBackup(name);
118 final wallet = await ZcashWalletBase.open(
119 name: name,
120 password: password,
121 walletInfo: walletInfo,
122 );
123 await wallet.init();
124 return wallet;
125 }
126 }
127
128 @override
129 Future<void> remove(final String wallet) async {
130 final path = (await pathForWalletDir(name: wallet, type: getType()));
131 final file = Directory(path);
132 final isExist = file.existsSync();
133
134 if (isExist) {
135 await file.delete(recursive: true);
136 }
137
138 final walletInfo = await WalletInfo.get(wallet, getType());
139 if (walletInfo == null) {
140 throw Exception('Wallet not found');
141 }
142 await WalletInfo.delete(walletInfo);
143 }
144
145 @override
146 Future<void> rename(final String currentName, final String password, final String newName) async {
147 final currentWalletInfo = await WalletInfo.get(currentName, getType());
148 if (currentWalletInfo == null) {
149 throw Exception('Wallet not found');
150 }
151 if (!await isWalletExit(currentName)) {
152 throw Exception('Wallet not found');
153 }
154
155 await ZcashWalletBase.renameWalletFilesForName(fromName: currentName, toName: newName);
156
157 final newWalletInfo = currentWalletInfo;
158 newWalletInfo.id = WalletBase.idFor(newName, getType());
159 newWalletInfo.name = newName;
160
161 await newWalletInfo.save();
162 }
163
164 @override
165 Future<ZcashWallet> restoreFromKeys(
166 final ZcashFromKeysWalletCredentials credentials, {
167 final bool? isTestnet,
168 }) async {
169 return ZcashWalletBase.restoreKeys(credentials);
170 }
171
172 @override
173 Future<ZcashWallet> restoreFromSeed(
174 final ZcashFromSeedWalletCredentials credentials, {
175 final bool? isTestnet,
176 }) async {
177 if (credentials.seed == null || credentials.seed!.isEmpty) {
178 throw Exception("Seed is missing");
179 }
180
181 if (!bip39.validateMnemonic(credentials.seed!)) {
182 throw Exception("Seed is not valid bip39");
183 }
184
185 return ZcashWalletBase.restore(credentials);
186 }
187
188 @override
189 Future<ZcashWallet> restoreFromHardwareWallet(final ZcashNewWalletCredentials credentials) {
190 throw UnimplementedError(
191 "Restoring a Zcash wallet from a hardware wallet is not yet supported!",
192 );
193 }
194 }