dev
dart 386 lines 11.1 KB
Raw
1 import 'dart:async';
2 import 'dart:ffi';
3 import 'dart:isolate';
4
5 import 'package:cw_core/utils/print_verbose.dart';
6 import 'package:cw_wownero/api/account_list.dart';
7 import 'package:cw_wownero/api/exceptions/setup_wallet_exception.dart';
8 import 'package:monero/wownero.dart' as wownero;
9 import 'package:mutex/mutex.dart';
10 import 'package:polyseed/polyseed.dart';
11
12 int getSyncingHeight() {
13 // final height = wownero.WOWNERO_cw_WalletListener_height(getWlptr());
14 final h2 = wownero.Wallet_blockChainHeight(wptr!);
15 // printV("height: $height / $h2");
16 return h2;
17 }
18
19 bool isNeededToRefresh() {
20 // final ret = wownero.WOWNERO_cw_WalletListener_isNeedToRefresh(getWlptr());
21 // wownero.WOWNERO_cw_WalletListener_resetNeedToRefresh(getWlptr());
22 return true;
23 }
24
25 bool isNewTransactionExist() {
26 // final ret =
27 // wownero.WOWNERO_cw_WalletListener_isNewTransactionExist(getWlptr());
28 // wownero.WOWNERO_cw_WalletListener_resetIsNewTransactionExist(getWlptr());
29 // NOTE: I don't know why wownero is being funky, but
30 return true;
31 }
32
33 String getFilename() => wownero.Wallet_filename(wptr!);
34
35 String getSeed() {
36 // monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed);
37 final cakepolyseed = wownero.Wallet_getCacheAttribute(wptr!, key: "cakewallet.seed");
38 final cakepassphrase = getPassphrase();
39
40 final weirdPolyseed = wownero.Wallet_getPolyseed(wptr!, passphrase: cakepassphrase);
41 if (weirdPolyseed != "") return weirdPolyseed;
42
43 if (cakepolyseed != "") {
44 if (cakepassphrase != "") {
45 try {
46 final lang = PolyseedLang.getByPhrase(cakepolyseed);
47 final coin = PolyseedCoin.POLYSEED_WOWNERO;
48 final ps = Polyseed.decode(cakepolyseed, lang, coin);
49 final passphrase = getPassphrase();
50 if (ps.isEncrypted || passphrase == "") return ps.encode(lang, coin);
51 ps.crypt(passphrase);
52 return ps.encode(lang, coin);
53 } catch (e) {
54 printV(e);
55 }
56 }
57 return cakepolyseed;
58 }
59 final legacy = getSeedLegacy(null);
60 return legacy;
61 }
62
63 String getSeedLegacy(String? language) {
64 final cakepassphrase = getPassphrase();
65 var legacy = wownero.Wallet_seed(wptr!, seedOffset: cakepassphrase);
66 switch (language) {
67 case "Chinese (Traditional)":
68 language = "Chinese (simplified)";
69 break;
70 case "Chinese (Simplified)":
71 language = "Chinese (simplified)";
72 break;
73 case "Korean":
74 language = "English";
75 break;
76 case "Czech":
77 language = "English";
78 break;
79 case "Japanese":
80 language = "English";
81 break;
82 }
83 if (wownero.Wallet_status(wptr!) != 0) {
84 wownero.Wallet_setSeedLanguage(wptr!, language: language ?? "English");
85 legacy = wownero.Wallet_seed(wptr!, seedOffset: cakepassphrase);
86 }
87 if (wownero.Wallet_status(wptr!) != 0) {
88 final err = wownero.Wallet_errorString(wptr!);
89 if (legacy.isNotEmpty) {
90 return "$err\n\n$legacy";
91 }
92 return err;
93 }
94 return legacy;
95 }
96
97 Map<int, Map<int, Map<int, String>>> addressCache = {};
98
99 String getPassphrase() {
100 return wownero.Wallet_getCacheAttribute(wptr!, key: "cakewallet.passphrase");
101 }
102
103 String getAddress({int accountIndex = 0, int addressIndex = 1}) {
104 while (wownero.Wallet_numSubaddresses(wptr!, accountIndex: accountIndex) - 1 < addressIndex) {
105 printV("adding subaddress");
106 wownero.Wallet_addSubaddress(wptr!, accountIndex: accountIndex);
107 }
108 addressCache[wptr!.address] ??= {};
109 addressCache[wptr!.address]![accountIndex] ??= {};
110 addressCache[wptr!.address]![accountIndex]![addressIndex] ??=
111 wownero.Wallet_address(wptr!, accountIndex: accountIndex, addressIndex: addressIndex);
112 return addressCache[wptr!.address]![accountIndex]![addressIndex]!;
113 }
114
115 int getFullBalance({int accountIndex = 0}) {
116 if (wptr == null) return 0;
117 return wownero.Wallet_balance(wptr!, accountIndex: accountIndex);
118 }
119
120 int getUnlockedBalance({int accountIndex = 0}) {
121 if (wptr == null) return 0;
122 return wownero.Wallet_unlockedBalance(wptr!, accountIndex: accountIndex);
123 }
124
125 int getCurrentHeight() {
126 if (wptr == null) return 0;
127 return wownero.Wallet_blockChainHeight(wptr!);
128 }
129
130 int cachedNodeHeight = 0;
131 int getNodeHeightSync() {
132 if (wptr == null) return 0;
133 (() async {
134 final wptrAddress = wptr!.address;
135 cachedNodeHeight = await Isolate.run(() async {
136 return wownero.Wallet_daemonBlockChainHeight(Pointer.fromAddress(wptrAddress));
137 });
138 })();
139 return cachedNodeHeight;
140 }
141
142 bool isConnectedSync() {
143 if (wptr == null) return false;
144 return wownero.Wallet_connected(wptr!) != 0;
145 }
146
147 Future<bool> setupNodeSync(
148 {required String address,
149 String? login,
150 String? password,
151 bool useSSL = false,
152 bool isLightWallet = false,
153 String? socksProxyAddress}) async {
154 printV('''
155 {
156 wptr!,
157 daemonAddress: $address,
158 useSsl: $useSSL,
159 proxyAddress: $socksProxyAddress ?? '',
160 daemonUsername: $login ?? '',
161 daemonPassword: $password ?? ''
162 }
163 ''');
164 final addr = wptr!.address;
165 await Isolate.run(() {
166 wownero.Wallet_init(Pointer.fromAddress(addr),
167 daemonAddress: address,
168 useSsl: useSSL,
169 proxyAddress: socksProxyAddress ?? '',
170 daemonUsername: login ?? '',
171 daemonPassword: password ?? '');
172 });
173 // wownero.Wallet_init3(wptr!, argv0: '', defaultLogBaseName: 'wowneroc', console: true);
174
175 final status = wownero.Wallet_status(wptr!);
176
177 if (status != 0) {
178 final error = wownero.Wallet_errorString(wptr!);
179 printV("error: $error");
180 throw SetupWalletException(message: error);
181 }
182
183 return status == 0;
184 }
185
186 void startRefreshSync() {
187 // wownero.Wallet_refreshAsync(wptr!);
188 wownero.Wallet_startRefresh(wptr!);
189 }
190
191 Future<bool> connectToNode() async {
192 return true;
193 }
194
195 void setRefreshFromBlockHeight({required int height}) =>
196 wownero.Wallet_setRefreshFromBlockHeight(wptr!, refresh_from_block_height: height);
197
198 void setRecoveringFromSeed({required bool isRecovery}) =>
199 wownero.Wallet_setRecoveringFromSeed(wptr!, recoveringFromSeed: isRecovery);
200
201 final storeMutex = Mutex();
202
203 int lastStorePointer = 0;
204 int lastStoreHeight = 0;
205 void storeSync() async {
206 final addr = wptr!.address;
207 final synchronized = await Isolate.run(() {
208 return wownero.Wallet_synchronized(Pointer.fromAddress(addr));
209 });
210 if (lastStorePointer == wptr!.address &&
211 lastStoreHeight + 5000 < wownero.Wallet_blockChainHeight(wptr!) &&
212 !synchronized) {
213 return;
214 }
215 lastStorePointer = wptr!.address;
216 lastStoreHeight = wownero.Wallet_blockChainHeight(wptr!);
217 await storeMutex.acquire();
218 Isolate.run(() {
219 wownero.Wallet_store(Pointer.fromAddress(addr));
220 });
221 storeMutex.release();
222 }
223
224 void setPasswordSync(String password) {
225 wownero.Wallet_setPassword(wptr!, password: password);
226
227 final status = wownero.Wallet_status(wptr!);
228 if (status != 0) {
229 throw Exception(wownero.Wallet_errorString(wptr!));
230 }
231 }
232
233 void closeCurrentWallet() {
234 wownero.Wallet_stop(wptr!);
235 }
236
237 String getSecretViewKey() => wownero.Wallet_secretViewKey(wptr!);
238
239 String getPublicViewKey() => wownero.Wallet_publicViewKey(wptr!);
240
241 String getSecretSpendKey() => wownero.Wallet_secretSpendKey(wptr!);
242
243 String getPublicSpendKey() => wownero.Wallet_publicSpendKey(wptr!);
244
245 class SyncListener {
246 SyncListener(this.onNewBlock, this.onNewTransaction)
247 : _cachedBlockchainHeight = 0,
248 _lastKnownBlockHeight = 0,
249 _initialSyncHeight = 0;
250
251 void Function(int, int, double) onNewBlock;
252 void Function() onNewTransaction;
253
254 Timer? _updateSyncInfoTimer;
255 int _cachedBlockchainHeight;
256 int _lastKnownBlockHeight;
257 int _initialSyncHeight;
258
259 Future<int> getNodeHeightOrUpdate(int baseHeight) async {
260 if (_cachedBlockchainHeight < baseHeight || _cachedBlockchainHeight == 0) {
261 _cachedBlockchainHeight = await getNodeHeight();
262 }
263
264 return _cachedBlockchainHeight;
265 }
266
267 void start() {
268 _cachedBlockchainHeight = 0;
269 _lastKnownBlockHeight = 0;
270 _initialSyncHeight = 0;
271 _updateSyncInfoTimer ??= Timer.periodic(Duration(milliseconds: 1200), (_) async {
272 if (isNewTransactionExist()) {
273 onNewTransaction();
274 }
275
276 var syncHeight = getSyncingHeight();
277
278 if (syncHeight <= 0) {
279 syncHeight = getCurrentHeight();
280 }
281
282 if (_initialSyncHeight <= 0) {
283 _initialSyncHeight = syncHeight;
284 }
285
286 final bchHeight = await getNodeHeightOrUpdate(syncHeight);
287
288 if (_lastKnownBlockHeight == syncHeight) {
289 return;
290 }
291
292 _lastKnownBlockHeight = syncHeight;
293 final track = bchHeight - _initialSyncHeight;
294 final diff = track - (bchHeight - syncHeight);
295 final ptc = diff <= 0 ? 0.0 : diff / track;
296 final left = bchHeight - syncHeight;
297
298 if (syncHeight < 0 || left < 0) {
299 return;
300 }
301
302 // 1. Actual new height; 2. Blocks left to finish; 3. Progress in percents;
303 onNewBlock.call(syncHeight, left, ptc);
304 });
305 }
306
307 void stop() => _updateSyncInfoTimer?.cancel();
308 }
309
310 SyncListener setListeners(
311 void Function(int, int, double) onNewBlock, void Function() onNewTransaction) {
312 final listener = SyncListener(onNewBlock, onNewTransaction);
313 // setListenerNative();
314 return listener;
315 }
316
317 void onStartup() {}
318
319 void _storeSync(Object _) => storeSync();
320
321 Future<bool> _setupNodeSync(Map<String, Object?> args) async {
322 final address = args['address'] as String;
323 final login = (args['login'] ?? '') as String;
324 final password = (args['password'] ?? '') as String;
325 final useSSL = args['useSSL'] as bool;
326 final isLightWallet = args['isLightWallet'] as bool;
327 final socksProxyAddress = (args['socksProxyAddress'] ?? '') as String;
328
329 return setupNodeSync(
330 address: address,
331 login: login,
332 password: password,
333 useSSL: useSSL,
334 isLightWallet: isLightWallet,
335 socksProxyAddress: socksProxyAddress);
336 }
337
338 bool _isConnected(Object _) => isConnectedSync();
339
340 int _getNodeHeight(Object _) => getNodeHeightSync();
341
342 void startRefresh() => startRefreshSync();
343
344 Future<void> setupNode(
345 {required String address,
346 String? login,
347 String? password,
348 bool useSSL = false,
349 String? socksProxyAddress,
350 bool isLightWallet = false}) async =>
351 _setupNodeSync({
352 'address': address,
353 'login': login,
354 'password': password,
355 'useSSL': useSSL,
356 'isLightWallet': isLightWallet,
357 'socksProxyAddress': socksProxyAddress
358 });
359
360 Future<void> store() async => _storeSync(0);
361
362 Future<bool> isConnected() async => _isConnected(0);
363
364 Future<int> getNodeHeight() async => _getNodeHeight(0);
365
366 void rescanBlockchainAsync() => wownero.Wallet_rescanBlockchainAsync(wptr!);
367
368 String getSubaddressLabel(int accountIndex, int addressIndex) {
369 return wownero.Wallet_getSubaddressLabel(wptr!,
370 accountIndex: accountIndex, addressIndex: addressIndex);
371 }
372
373 Future setTrustedDaemon(bool trusted) async => wownero.Wallet_setTrustedDaemon(wptr!, arg: trusted);
374
375 Future<bool> trustedDaemon() async => wownero.Wallet_trustedDaemon(wptr!);
376
377 String signMessage(String message, {String address = ""}) {
378 return wownero.Wallet_signMessage(wptr!, message: message, address: address);
379 }
380
381 bool verifyMessage(String message, String address, String signature) {
382 return wownero.Wallet_verifySignedMessage(wptr!,
383 message: message, address: address, signature: signature);
384 }
385
386 Map<String, List<int>> debugCallLength() => wownero.debugCallLength;