zec ironwood (#3425)
* initial * wip * ironwood detection, migration * dev: zec address validation on rt * fix: ironwood spend * ironwood regressions * fix: tx history * bump: zkool2 * fix: tx amounts on pending fix: ironwood pending balance fix: orchard->ironwood migration fix: orchard->any pre ironwood txs fix: ironwood -> any txs fix: WrongSpendAuthorizingKey fix: tx history fix: autoshield to ironwood fix: zkool to latest version break: my sleep schedule :sweating: :worksonmymachine: * thx fable * downgrade frb to 2.11.1 * fix: tx amounts in history * Migating... / migration label for O->I txs
cyan committed
Jul 27, 2026 at 10:20 UTC
92c2c5e043b60fc437ea9c749230c76b0fbdab9e
38 files changed
+1067
-206
cw_core/lib/wallet_info.dart
+5
-2
@@ -586,11 +586,12 @@ class WalletInfo {
586
"addressPageType": addressPageType,
587
"receiveInfoboxDismissed": receiveInfoboxDismissed ? 1 : 0,
588
"showCombinedBalance": showCombinedBalance ? 1 : 0,
589
- "favoriteTokenAddress": favoriteTokenAddress
589
+ "favoriteTokenAddress": favoriteTokenAddress,
590
+ "network": network,
591
};
592
593
factory WalletInfo.fromJson(Map<String, dynamic> json) {
593
- return WalletInfo(
594
+ final info = WalletInfo(
595
json[selfIdColumn] as int,
596
json['id'] as String,
597
json['name'] as String,
@@ -616,6 +617,8 @@ class WalletInfo {
617
json['receiveInfoboxDismissed'] != 0,
618
json["showCombinedBalance"] != 0,
619
json["favoriteTokenAddress"] as String? ?? null);
620
+ info.network = json['network'] as String?;
621
+ return info;
622
}
623
624
Future<int> save() async {
cw_core/lib/wallet_info_legacy.dart
+2
-1
@@ -274,7 +274,8 @@ class WalletInfo extends HiveObject {
274
addressPageType,
275
false,
276
true,
277
- null);
277
+ null)
278
+ ..network = network;
279
final wiId = await walletInfo.save();
280
for (final address in usedAddresses ?? <String>[]) {
281
await newWi.WalletInfoAddress.insert(wiId, newWi.WalletInfoAddressType.used, address);
cw_custom_lints/pubspec.lock
new
+429
@@ -0,0 +1,429 @@
1
+# Generated by pub
2
+# See https://dart.dev/tools/pub/glossary#lockfile
3
+packages:
4
+ _fe_analyzer_shared:
5
+ dependency: transitive
6
+ description:
7
+ name: _fe_analyzer_shared
8
+ sha256: cd6add6f846f35fb79f3c315296703c1a24f3cfd7f4739d91a74961c1c7e9f1b
9
+ url: "https://pub.dev"
10
+ source: hosted
11
+ version: "100.0.0"
12
+ analysis_server_plugin:
13
+ dependency: "direct main"
14
+ description:
15
+ name: analysis_server_plugin
16
+ sha256: c4021359e7a673ecd9c6db5f11fea12fabb55e0ba96fa08c78920ae81936f1d1
17
+ url: "https://pub.dev"
18
+ source: hosted
19
+ version: "0.3.15"
20
+ analyzer:
21
+ dependency: "direct main"
22
+ description:
23
+ name: analyzer
24
+ sha256: "6ba98576948803398b69e3a444df24eacdbe12ed699c7014e120ea38552debbf"
25
+ url: "https://pub.dev"
26
+ source: hosted
27
+ version: "13.0.0"
28
+ analyzer_plugin:
29
+ dependency: "direct main"
30
+ description:
31
+ name: analyzer_plugin
32
+ sha256: "9a81dc36f7f71890d7cf83960a3a7df1e6c9a24035f98eb848e91ad48b1d21c6"
33
+ url: "https://pub.dev"
34
+ source: hosted
35
+ version: "0.14.9"
36
+ analyzer_testing:
37
+ dependency: "direct dev"
38
+ description:
39
+ name: analyzer_testing
40
+ sha256: da27ae9bd4bf3ce4ebab6295cf922ca21a2b14ea8fcacc859342aca796a48a2d
41
+ url: "https://pub.dev"
42
+ source: hosted
43
+ version: "0.2.6"
44
+ args:
45
+ dependency: transitive
46
+ description:
47
+ name: args
48
+ sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
49
+ url: "https://pub.dev"
50
+ source: hosted
51
+ version: "2.7.0"
52
+ async:
53
+ dependency: transitive
54
+ description:
55
+ name: async
56
+ sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
57
+ url: "https://pub.dev"
58
+ source: hosted
59
+ version: "2.13.1"
60
+ boolean_selector:
61
+ dependency: transitive
62
+ description:
63
+ name: boolean_selector
64
+ sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
65
+ url: "https://pub.dev"
66
+ source: hosted
67
+ version: "2.1.2"
68
+ cli_config:
69
+ dependency: transitive
70
+ description:
71
+ name: cli_config
72
+ sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec
73
+ url: "https://pub.dev"
74
+ source: hosted
75
+ version: "0.2.0"
76
+ collection:
77
+ dependency: transitive
78
+ description:
79
+ name: collection
80
+ sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
81
+ url: "https://pub.dev"
82
+ source: hosted
83
+ version: "1.19.1"
84
+ convert:
85
+ dependency: transitive
86
+ description:
87
+ name: convert
88
+ sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
89
+ url: "https://pub.dev"
90
+ source: hosted
91
+ version: "3.1.2"
92
+ coverage:
93
+ dependency: transitive
94
+ description:
95
+ name: coverage
96
+ sha256: "956a3de0725ca232ad353565a8290d3357592bf4250f6f298a185e2d949c5d3d"
97
+ url: "https://pub.dev"
98
+ source: hosted
99
+ version: "1.15.1"
100
+ crypto:
101
+ dependency: transitive
102
+ description:
103
+ name: crypto
104
+ sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
105
+ url: "https://pub.dev"
106
+ source: hosted
107
+ version: "3.0.7"
108
+ dart_style:
109
+ dependency: transitive
110
+ description:
111
+ name: dart_style
112
+ sha256: "59d53ef8eaed9d288ed9767618e2b31c4fa0383a127db59d5eb2e737a7638a60"
113
+ url: "https://pub.dev"
114
+ source: hosted
115
+ version: "3.1.9"
116
+ file:
117
+ dependency: transitive
118
+ description:
119
+ name: file
120
+ sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
121
+ url: "https://pub.dev"
122
+ source: hosted
123
+ version: "7.0.1"
124
+ frontend_server_client:
125
+ dependency: transitive
126
+ description:
127
+ name: frontend_server_client
128
+ sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
129
+ url: "https://pub.dev"
130
+ source: hosted
131
+ version: "4.0.0"
132
+ glob:
133
+ dependency: transitive
134
+ description:
135
+ name: glob
136
+ sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
137
+ url: "https://pub.dev"
138
+ source: hosted
139
+ version: "2.1.3"
140
+ http_multi_server:
141
+ dependency: transitive
142
+ description:
143
+ name: http_multi_server
144
+ sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
145
+ url: "https://pub.dev"
146
+ source: hosted
147
+ version: "3.2.2"
148
+ http_parser:
149
+ dependency: transitive
150
+ description:
151
+ name: http_parser
152
+ sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
153
+ url: "https://pub.dev"
154
+ source: hosted
155
+ version: "4.1.2"
156
+ io:
157
+ dependency: transitive
158
+ description:
159
+ name: io
160
+ sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
161
+ url: "https://pub.dev"
162
+ source: hosted
163
+ version: "1.0.5"
164
+ logging:
165
+ dependency: transitive
166
+ description:
167
+ name: logging
168
+ sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
169
+ url: "https://pub.dev"
170
+ source: hosted
171
+ version: "1.3.0"
172
+ matcher:
173
+ dependency: transitive
174
+ description:
175
+ name: matcher
176
+ sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd"
177
+ url: "https://pub.dev"
178
+ source: hosted
179
+ version: "0.12.20"
180
+ meta:
181
+ dependency: transitive
182
+ description:
183
+ name: meta
184
+ sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9"
185
+ url: "https://pub.dev"
186
+ source: hosted
187
+ version: "1.19.0"
188
+ mime:
189
+ dependency: transitive
190
+ description:
191
+ name: mime
192
+ sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
193
+ url: "https://pub.dev"
194
+ source: hosted
195
+ version: "2.0.0"
196
+ node_preamble:
197
+ dependency: transitive
198
+ description:
199
+ name: node_preamble
200
+ sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
201
+ url: "https://pub.dev"
202
+ source: hosted
203
+ version: "2.0.2"
204
+ package_config:
205
+ dependency: transitive
206
+ description:
207
+ name: package_config
208
+ sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
209
+ url: "https://pub.dev"
210
+ source: hosted
211
+ version: "2.2.0"
212
+ path:
213
+ dependency: transitive
214
+ description:
215
+ name: path
216
+ sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
217
+ url: "https://pub.dev"
218
+ source: hosted
219
+ version: "1.9.1"
220
+ pool:
221
+ dependency: transitive
222
+ description:
223
+ name: pool
224
+ sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d"
225
+ url: "https://pub.dev"
226
+ source: hosted
227
+ version: "1.5.2"
228
+ pub_semver:
229
+ dependency: transitive
230
+ description:
231
+ name: pub_semver
232
+ sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
233
+ url: "https://pub.dev"
234
+ source: hosted
235
+ version: "2.2.0"
236
+ shelf:
237
+ dependency: transitive
238
+ description:
239
+ name: shelf
240
+ sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
241
+ url: "https://pub.dev"
242
+ source: hosted
243
+ version: "1.4.2"
244
+ shelf_packages_handler:
245
+ dependency: transitive
246
+ description:
247
+ name: shelf_packages_handler
248
+ sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
249
+ url: "https://pub.dev"
250
+ source: hosted
251
+ version: "3.0.2"
252
+ shelf_static:
253
+ dependency: transitive
254
+ description:
255
+ name: shelf_static
256
+ sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3
257
+ url: "https://pub.dev"
258
+ source: hosted
259
+ version: "1.1.3"
260
+ shelf_web_socket:
261
+ dependency: transitive
262
+ description:
263
+ name: shelf_web_socket
264
+ sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
265
+ url: "https://pub.dev"
266
+ source: hosted
267
+ version: "3.0.0"
268
+ source_map_stack_trace:
269
+ dependency: transitive
270
+ description:
271
+ name: source_map_stack_trace
272
+ sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b
273
+ url: "https://pub.dev"
274
+ source: hosted
275
+ version: "2.1.2"
276
+ source_maps:
277
+ dependency: transitive
278
+ description:
279
+ name: source_maps
280
+ sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812"
281
+ url: "https://pub.dev"
282
+ source: hosted
283
+ version: "0.10.13"
284
+ source_span:
285
+ dependency: transitive
286
+ description:
287
+ name: source_span
288
+ sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
289
+ url: "https://pub.dev"
290
+ source: hosted
291
+ version: "1.10.2"
292
+ stack_trace:
293
+ dependency: transitive
294
+ description:
295
+ name: stack_trace
296
+ sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
297
+ url: "https://pub.dev"
298
+ source: hosted
299
+ version: "1.12.1"
300
+ stream_channel:
301
+ dependency: transitive
302
+ description:
303
+ name: stream_channel
304
+ sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
305
+ url: "https://pub.dev"
306
+ source: hosted
307
+ version: "2.1.4"
308
+ string_scanner:
309
+ dependency: transitive
310
+ description:
311
+ name: string_scanner
312
+ sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
313
+ url: "https://pub.dev"
314
+ source: hosted
315
+ version: "1.4.1"
316
+ term_glyph:
317
+ dependency: transitive
318
+ description:
319
+ name: term_glyph
320
+ sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
321
+ url: "https://pub.dev"
322
+ source: hosted
323
+ version: "1.2.2"
324
+ test:
325
+ dependency: "direct dev"
326
+ description:
327
+ name: test
328
+ sha256: "0d5ba5602ec3baa28c8ce365e1efc5575969c765f45c554a3e167dc7945b9c30"
329
+ url: "https://pub.dev"
330
+ source: hosted
331
+ version: "1.31.2"
332
+ test_api:
333
+ dependency: transitive
334
+ description:
335
+ name: test_api
336
+ sha256: "475610b2aa23c19687cce2961e44b0cc57cafe220f67c2b80201231b2a07fbe7"
337
+ url: "https://pub.dev"
338
+ source: hosted
339
+ version: "0.7.13"
340
+ test_core:
341
+ dependency: transitive
342
+ description:
343
+ name: test_core
344
+ sha256: a39c204a4fc7a7ccb04a2b985e359fda3cc37e45e0b8ac61c3fb1a05aa832132
345
+ url: "https://pub.dev"
346
+ source: hosted
347
+ version: "0.6.19"
348
+ test_reflective_loader:
349
+ dependency: "direct dev"
350
+ description:
351
+ name: test_reflective_loader
352
+ sha256: d828d5ca15179aaac2aaf8f510cf0a52ec28e0031681b044ec5e581a4b8002e7
353
+ url: "https://pub.dev"
354
+ source: hosted
355
+ version: "0.4.0"
356
+ typed_data:
357
+ dependency: transitive
358
+ description:
359
+ name: typed_data
360
+ sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
361
+ url: "https://pub.dev"
362
+ source: hosted
363
+ version: "1.4.0"
364
+ vm_service:
365
+ dependency: transitive
366
+ description:
367
+ name: vm_service
368
+ sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360"
369
+ url: "https://pub.dev"
370
+ source: hosted
371
+ version: "15.2.0"
372
+ watcher:
373
+ dependency: transitive
374
+ description:
375
+ name: watcher
376
+ sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635"
377
+ url: "https://pub.dev"
378
+ source: hosted
379
+ version: "1.2.1"
380
+ web:
381
+ dependency: transitive
382
+ description:
383
+ name: web
384
+ sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
385
+ url: "https://pub.dev"
386
+ source: hosted
387
+ version: "1.1.1"
388
+ web_socket:
389
+ dependency: transitive
390
+ description:
391
+ name: web_socket
392
+ sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
393
+ url: "https://pub.dev"
394
+ source: hosted
395
+ version: "1.0.1"
396
+ web_socket_channel:
397
+ dependency: transitive
398
+ description:
399
+ name: web_socket_channel
400
+ sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
401
+ url: "https://pub.dev"
402
+ source: hosted
403
+ version: "3.0.3"
404
+ webkit_inspection_protocol:
405
+ dependency: transitive
406
+ description:
407
+ name: webkit_inspection_protocol
408
+ sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572"
409
+ url: "https://pub.dev"
410
+ source: hosted
411
+ version: "1.2.1"
412
+ yaml:
413
+ dependency: transitive
414
+ description:
415
+ name: yaml
416
+ sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
417
+ url: "https://pub.dev"
418
+ source: hosted
419
+ version: "3.1.3"
420
+ yaml_edit:
421
+ dependency: transitive
422
+ description:
423
+ name: yaml_edit
424
+ sha256: "07c9e63ba42519745182b88ca12264a7ba2484d8239958778dfe4d44fe760488"
425
+ url: "https://pub.dev"
426
+ source: hosted
427
+ version: "2.2.4"
428
+sdks:
429
+ dart: ">=3.11.0 <4.0.0"
cw_zcash/lib/cw_zcash.dart
+2
@@ -11,4 +11,6 @@ export 'src/zcash_wallet_service.dart';
11
export 'src/models.dart';
12
export 'src/zcash_transaction_credentials.dart';
13
export 'src/pending_zcash_transaction.dart';
14
+export 'src/zcash_network.dart';
15
export 'src/zcash_receive_page_options.dart';
16
+export 'src/zcash_wallet_addresses.dart';
cw_zcash/lib/src/models.dart
+6
@@ -7,6 +7,7 @@ class ZcashNewWalletCredentials extends WalletCredentials {
7
required final String? passphrase,
8
final String? mnemonic,
9
final int? seedPhraseLength,
10
+ this.network = 0,
11
}) : super(
12
name: name,
13
password: password,
@@ -17,6 +18,7 @@ class ZcashNewWalletCredentials extends WalletCredentials {
18
}
19
20
String? mnemonic;
21
+ int network;
22
}
23
24
class ZcashFromSeedWalletCredentials extends WalletCredentials {
@@ -26,8 +28,10 @@ class ZcashFromSeedWalletCredentials extends WalletCredentials {
28
required final String? passphrase,
29
required this.seed,
30
required super.height,
31
+ this.network = 0,
32
}) : super(name: name, password: password, passphrase: passphrase);
33
final String? seed;
34
+ int network;
35
}
36
37
class ZcashFromKeysWalletCredentials extends WalletCredentials {
@@ -36,6 +40,8 @@ class ZcashFromKeysWalletCredentials extends WalletCredentials {
40
final String? password,
41
required final int? height,
42
required this.privateKey,
43
+ this.network = 0,
44
}) : super(name: name, password: password, height: height);
45
final String? privateKey;
46
+ int network;
47
}
cw_zcash/lib/src/pending_zcash_transaction.dart
+1
@@ -63,6 +63,7 @@ class PendingZcashTransaction with PendingTransaction {
63
throw TransactionCommitFailed(errorMessage: result);
64
}
65
_txId = txId;
66
+ zcashWallet.rememberPendingOutgoingAmount(txId, amount);
67
},
68
);
69
await zcashWallet.updateTransactions();
cw_zcash/lib/src/zcash_mempool.dart
+20
-3
@@ -21,8 +21,19 @@ class ZcashMempoolService {
21
22
bool _loopRunning = false;
23
String? _nodeUrl;
24
+ int? _lastBlockHeight;
25
StreamSubscription<zkool_mempool.MempoolMsg>? _subscription;
26
27
+ void removeTx(final String txId) {
28
+ _txsById.remove(ZcashWalletService.normalizeTxId(txId));
29
+ }
30
+
31
+ void removeKnownTxs(final Iterable<String> txIds) {
32
+ for (final txId in txIds) {
33
+ removeTx(txId);
34
+ }
35
+ }
36
+
37
List<zkool_mempool.MempoolTx> txsForAccount(final int accountId) => _txsById.values
38
.where(
39
(final tx) =>
@@ -49,6 +60,7 @@ class ZcashMempoolService {
60
await _subscription?.cancel();
61
_subscription = null;
62
_txsById.clear();
63
+ _lastBlockHeight = null;
64
try {
65
await _mempool?.cancel();
66
} catch (e) {
@@ -69,8 +81,8 @@ class ZcashMempoolService {
81
return;
82
}
83
switch (msg) {
72
- case zkool_mempool.MempoolMsg_BlockHeight():
73
- _onBlockMined();
84
+ case zkool_mempool.MempoolMsg_BlockHeight(:final field0):
85
+ _onBlockHeight(field0);
86
case zkool_mempool.MempoolMsg_TxId(:final field0):
87
_onMempoolTx(field0);
88
}
@@ -97,7 +109,12 @@ class ZcashMempoolService {
109
}
110
}
111
100
- void _onBlockMined() {
112
+ void _onBlockHeight(final int height) {
113
+ final heightChanged = _lastBlockHeight != null && height > _lastBlockHeight!;
114
+ _lastBlockHeight = height;
115
+ if (!heightChanged) {
116
+ return;
117
+ }
118
final affected = _allAffectedAccounts();
119
_txsById.clear();
120
onAccountsUpdated?.call(affected);
cw_zcash/lib/src/zcash_network.dart
new
+49
@@ -0,0 +1,49 @@
1
+enum ZcashNetwork {
2
+ mainnet,
3
+ testnet,
4
+ regtest;
5
+
6
+ /// NU6.3 / Ironwood activation height on zkool regtest (see zkool2/misc/zebra.toml).
7
+ static const int regtestNu63Height = 250;
8
+
9
+ String get value => name;
10
+
11
+ String get label => switch (this) {
12
+ ZcashNetwork.mainnet => 'Mainnet',
13
+ ZcashNetwork.testnet => 'Testnet',
14
+ ZcashNetwork.regtest => 'Regtest',
15
+ };
16
+
17
+ String get dbFileName => switch (this) {
18
+ ZcashNetwork.mainnet => 'zec.v2.db',
19
+ ZcashNetwork.testnet => 'zec.testnet.v2.db',
20
+ ZcashNetwork.regtest => 'zec.regtest.v2.db',
21
+ };
22
+
23
+ /// Default lightwalletd endpoint for dev networks.
24
+ String get defaultNodeUri => switch (this) {
25
+ ZcashNetwork.mainnet => 'zec.rocks:443',
26
+ ZcashNetwork.testnet => 'testnet.lightwalletd.com:9067',
27
+ ZcashNetwork.regtest => '10.0.2.2:9067', // 10.0.2.2 - AVD host bridge
28
+ };
29
+
30
+ bool get useSsl => this == ZcashNetwork.mainnet;
31
+
32
+ static ZcashNetwork fromName(final String? name) => switch (name) {
33
+ 'testnet' => ZcashNetwork.testnet,
34
+ 'regtest' => ZcashNetwork.regtest,
35
+ _ => ZcashNetwork.mainnet,
36
+ };
37
+
38
+ static ZcashNetwork fromIndex(final int index) => switch (index) {
39
+ 1 => ZcashNetwork.testnet,
40
+ 2 => ZcashNetwork.regtest,
41
+ _ => ZcashNetwork.mainnet,
42
+ };
43
+
44
+ int get networkIndex => switch (this) {
45
+ ZcashNetwork.testnet => 1,
46
+ ZcashNetwork.regtest => 2,
47
+ ZcashNetwork.mainnet => 0,
48
+ };
49
+}
cw_zcash/lib/src/zcash_receive_page_options.dart
+22
-15
@@ -19,7 +19,7 @@ class ZcashReceivePageOption implements ReceivePageOption {
19
case ZcashAddressType.shieldedSapling:
20
return shieldedSapling;
21
case ZcashAddressType.shieldedOrchard:
22
- return shieldedOrchard;
22
+ return shieldedOrchard();
23
case ZcashAddressType.unifiedType:
24
return unified;
25
}
@@ -57,13 +57,18 @@ class ZcashReceivePageOption implements ReceivePageOption {
57
description: "Sapling",
58
iconPath: "assets/new-ui/address-type-picker-icons/zec/sapling.svg",
59
);
60
- static const shieldedOrchard = ZcashReceivePageOption._(
61
- ZcashAddressType.shieldedOrchard,
62
- "Shielded",
63
- description: "Default (Orchard)",
64
- iconPath: "assets/new-ui/address-type-picker-icons/zec/shielded.svg",
65
- isCommon: true,
66
- );
60
+ static const _shieldedOrchardIcon =
61
+ "assets/new-ui/address-type-picker-icons/zec/shielded.svg";
62
+
63
+ static ZcashReceivePageOption shieldedOrchard({final bool ironwood = false}) {
64
+ return ZcashReceivePageOption._(
65
+ ZcashAddressType.shieldedOrchard,
66
+ "Shielded",
67
+ description: ironwood ? "Default (Ironwood)" : "Default (Orchard)",
68
+ iconPath: _shieldedOrchardIcon,
69
+ isCommon: true,
70
+ );
71
+ }
72
static const unified = ZcashReceivePageOption._(
73
ZcashAddressType.unifiedType,
74
"Unified",
@@ -78,13 +83,15 @@ class ZcashReceivePageOption implements ReceivePageOption {
83
return value;
84
}
85
81
- static const all = [
82
- ZcashReceivePageOption.shieldedOrchard,
83
- ZcashReceivePageOption.shieldedSapling,
84
- ZcashReceivePageOption.unified,
85
- ZcashReceivePageOption.transparentRotated,
86
- ZcashReceivePageOption.transparent,
87
- ];
86
+ static List<ZcashReceivePageOption> allOptionsFor({final bool ironwood = false}) => [
87
+ shieldedOrchard(ironwood: ironwood),
88
+ shieldedSapling,
89
+ unified,
90
+ transparentRotated,
91
+ transparent,
92
+ ];
93
+
94
+ static List<ZcashReceivePageOption> get allOptions => allOptionsFor();
95
96
ZcashAddressType toType() {
97
return type;
cw_zcash/lib/src/zcash_taddress_rotation.dart
+27
-2
@@ -34,6 +34,7 @@ class ZcashTaddressRotation {
34
static bool _isStarted = false;
35
static zkool_coin.Coin get c => ZcashWalletBase.c;
36
static const int _sweepThreshold = 30000;
37
+ static const int _minSpendableNote = 5000;
38
static const int _lookahead = 5;
39
// Matches transparentLimit in ZcashWalletBase._oneshotSync.
40
static const int _transparentSyncLimit = 100;
@@ -407,18 +408,42 @@ class ZcashTaddressRotation {
408
final result = await ZcashWalletBase.runWithCoin(
409
accountId: rotationAccount,
410
func: (final coin) async {
411
+ final height = await zkool_network.getCurrentHeight(c: coin);
412
+ final notes = await zkool_account.listNotes(c: coin);
413
+ var amount = BigInt.zero;
414
+ for (final note in notes) {
415
+ if (note.pool != NotePool.transparent.index || note.locked) {
416
+ continue;
417
+ }
418
+ if (note.value < BigInt.from(_minSpendableNote)) {
419
+ continue;
420
+ }
421
+ if (note.height > height) {
422
+ continue;
423
+ }
424
+ amount += note.value;
425
+ }
426
+ if (amount < BigInt.from(_sweepThreshold)) {
427
+ throw Exception('rotation sweep: insufficient spendable transparent notes');
428
+ }
429
+ final ironwood = await zkool_network.isIronwoodActive(c: coin);
430
final tx = await zkool_pay.prepare(
431
recipients: [
412
- zkool_paydart.Recipient(assetBase: zecBase, address: toAddress, amount: transparentBal),
432
+ zkool_paydart.Recipient(
433
+ assetBase: zecBase,
434
+ address: toAddress,
435
+ amount: amount,
436
+ pools: ironwood ? ironwoodPoolMask : null,
437
+ ),
438
],
439
options: zkool_pay.PaymentOptions(
440
srcPools: 1,
441
recipientPaysFee: true,
442
smartTransparent: false,
443
+ mode: 0,
444
),
445
c: coin,
446
);
421
- final height = await zkool_network.getCurrentHeight(c: coin);
447
final signTx = await zkool_pay.signTransaction(pczt: tx, c: coin);
448
final txBytes = await zkool_pay.extractTransaction(package: signTx);
449
return zkool_pay.broadcastTransaction(height: height, txBytes: txBytes, c: coin);
cw_zcash/lib/src/zcash_transaction_info.dart
+2
@@ -20,6 +20,7 @@ class ZcashTransactionInfo extends TransactionInfo {
20
final TxType? txType,
21
final bool isRotationReceive = false,
22
final bool isShieldAction = false,
23
+ final bool isIronwoodMigration = false,
24
}) {
25
this.id = id;
26
this.amount = amount;
@@ -38,6 +39,7 @@ class ZcashTransactionInfo extends TransactionInfo {
39
}
40
additionalInfo['isRotationReceive'] = isRotationReceive;
41
additionalInfo['isAutoShield'] = isShieldAction || ZcashWalletService.isAutoshieldTx(txHash);
42
+ additionalInfo['isIronwoodMigration'] = isIronwoodMigration;
43
44
if (additionalInfo['isAutoShield'] == true) {
45
additionalInfo['memo'] ??= '';
cw_zcash/lib/src/zcash_wallet.dart
+282
-66
@@ -23,6 +23,7 @@ import 'package:cw_zcash/src/util/crc32.dart';
23
import 'package:cw_zcash/src/zcash_mempool.dart';
24
import 'package:cw_zcash/src/zcash_taddress_rotation.dart';
25
import 'package:cw_zcash/src/zcash_wallet_addresses.dart';
26
+import 'package:cw_zcash/src/zcash_network.dart';
27
import 'package:cw_zcash/src/zkool_compat.dart';
28
import 'package:cw_zcash/src/zkooltx.dart';
29
import 'package:mobx/mobx.dart';
@@ -32,6 +33,7 @@ import 'package:zkool/src/rust/api/coin.dart' as zkool_coin;
33
import 'package:zkool/src/rust/api/mempool.dart' as zkool_mempool;
34
import 'package:zkool/src/rust/api/sync.dart' as zkool_sync;
35
import 'package:zkool/src/rust/api/pay.dart' as zkool_pay;
36
+import 'package:zkool/src/rust/api/migrate.dart' as zkool_migrate;
37
import 'package:zkool/src/rust/api/network.dart' as zkool_network;
38
import 'package:zkool/src/rust/pay.dart' as zkool_paydart;
39
import 'package:zkool/src/rust/frb_generated.dart' as zkool_frb;
@@ -61,6 +63,15 @@ abstract class ZcashWalletBase
63
64
int accountId;
65
66
+ final Map<String, BigInt> _pendingOutgoingAmounts = {};
67
+
68
+ void rememberPendingOutgoingAmount(final String txId, final Money amount) {
69
+ if (amount.isZero) {
70
+ return;
71
+ }
72
+ _pendingOutgoingAmounts[ZcashWalletService.normalizeTxId(txId)] = amount.amount;
73
+ }
74
+
75
@override
76
@observable
77
SyncStatus syncStatus = NotConnectedSyncStatus();
@@ -147,6 +158,7 @@ abstract class ZcashWalletBase
158
c = c.setLwd(url: lwdUrl, serverType: 0);
159
syncStatus = ConnectedSyncStatus();
160
unawaited(ZcashMempoolService.instance.ensureRunning(c));
161
+ unawaited(_updateIronwoodActive());
162
_ensureSyncLoopRunning();
163
unawaited(_refreshSyncStatus());
164
unawaited(_oneshotSync());
@@ -463,12 +475,14 @@ abstract class ZcashWalletBase
475
return await runWithCoin(
476
accountId: accountId,
477
func: (coin) async {
478
+ final ironwood = await zkool_network.isIronwoodActive(c: coin);
479
final txPlan = await zkool_pay.prepare(
480
recipients: recipients,
481
options: zkool_pay.PaymentOptions(
469
- srcPools: 7,
482
+ srcPools: ironwood ? 15 : 7,
483
recipientPaysFee: receipientPaysFee,
484
smartTransparent: false,
485
+ mode: 0,
486
),
487
c: coin,
488
);
@@ -502,18 +516,57 @@ abstract class ZcashWalletBase
516
517
static const _dispPhrase = "Received to disposable address";
518
519
+ bool _hasExternalOutputs(final ZkoolTx tx) =>
520
+ tx.outputsWithAddress.any((final o) => !_addressBelongsToWallet(o.address));
521
+
522
+ bool _isIronwoodMigrationTx(final ZkoolTx tx) {
523
+ // zkool classifies migration as selfTransfer with fee-sized net value.
524
+ if (tx.type != TxType.selfTransfer) {
525
+ return false;
526
+ }
527
+ final orchardSpent = tx.orchardSpent;
528
+ final spentFromOrchard = orchardSpent > BigInt.zero ||
529
+ tx.spendPools.contains(NotePool.orchard.index);
530
+ if (!spentFromOrchard) {
531
+ return false;
532
+ }
533
+ if (tx.ironwoodReceived > BigInt.zero ||
534
+ tx.notePools.contains(NotePool.ironwood.index)) {
535
+ return true;
536
+ }
537
+ // Orchard → Orchard split step (SD notes created, all outputs are ours).
538
+ if (tx.orchardReceived > BigInt.zero && !_hasExternalOutputs(tx)) {
539
+ return true;
540
+ }
541
+ // Orchard → Ironwood before the IW note is attached to tx details.
542
+ return orchardSpent > tx.value;
543
+ }
544
+
545
+ BigInt _migrationDisplayAmount(final ZkoolTx tx) {
546
+ if (tx.ironwoodReceived > BigInt.zero) {
547
+ return tx.ironwoodReceived;
548
+ }
549
+ if (tx.orchardReceived > BigInt.zero) {
550
+ return tx.orchardReceived;
551
+ }
552
+ return tx.orchardSpent;
553
+ }
554
+
555
ZcashTransactionInfo _zcashInfoFromMempoolTx(
556
final zkool_mempool.MempoolTx tx,
557
final int accountId,
558
) {
559
final accountNotes = tx.notes.where((final n) => n.account == accountId);
560
+ final txHash = ZcashWalletService.normalizeTxId(tx.txid);
561
+ final pendingAmount = _pendingOutgoingAmounts[txHash];
562
final netValue = accountNotes.fold<BigInt>(
563
BigInt.zero,
564
(final sum, final note) => sum + BigInt.from(note.value),
565
);
514
- final direction = netValue >= BigInt.zero
515
- ? TransactionDirection.incoming
516
- : TransactionDirection.outgoing;
566
+ final direction = pendingAmount != null || netValue < BigInt.zero
567
+ ? TransactionDirection.outgoing
568
+ : TransactionDirection.incoming;
569
+ final displayAmount = pendingAmount ?? netValue.abs();
570
final memo = accountNotes
571
.map((final n) => n.memo)
572
.whereType<String>()
@@ -525,8 +578,8 @@ abstract class ZcashWalletBase
578
);
579
580
final info = ZcashTransactionInfo(
528
- id: ZcashWalletService.normalizeTxId(tx.txid),
529
- amount: Money(netValue.abs(), currency),
581
+ id: txHash,
582
+ amount: Money(displayAmount, currency),
583
fee: Money.zero(currency),
584
direction: direction,
585
isPending: true,
@@ -555,11 +608,15 @@ abstract class ZcashWalletBase
608
? currentHeight - tx.height + 1
609
: 0;
610
final memo = extraMemo != null ? "${tx.memo ?? ''}\n$extraMemo".trim() : tx.memo;
558
- final direction = directionOverride ?? tx.direction;
611
+ final isMigration = directionOverride == null && _isIronwoodMigrationTx(tx);
612
+ final direction = directionOverride ??
613
+ (isMigration ? TransactionDirection.outgoing : tx.direction);
614
final recipientAddresses = _recipientAddresses(_paymentOutputAddresses(tx), direction);
615
+ final amount = amountOverride ??
616
+ (isMigration ? _migrationDisplayAmount(tx) : tx.value);
617
final info = ZcashTransactionInfo(
618
id: tx.txHash,
562
- amount: Money(amountOverride ?? tx.value, currency),
619
+ amount: Money(amount, currency),
620
fee: Money.zero(currency),
621
direction: direction,
622
isPending: tx.height == 0,
@@ -571,6 +628,7 @@ abstract class ZcashWalletBase
628
txType: tx.type,
629
isRotationReceive: isRotationReceive,
630
isShieldAction: isShieldAction,
631
+ isIronwoodMigration: isMigration,
632
);
633
if (recipientAddresses.isNotEmpty) {
634
info.outputAddresses = recipientAddresses;
@@ -627,6 +685,9 @@ abstract class ZcashWalletBase
685
'tx_$txHash$suffix';
686
687
static int _txDisplayPriority(final ZcashTransactionInfo info) {
688
+ if (info.additionalInfo['isIronwoodMigration'] == true) {
689
+ return 4;
690
+ }
691
if (info.additionalInfo['isAutoShield'] == true) {
692
return 3;
693
}
@@ -650,7 +711,8 @@ abstract class ZcashWalletBase
711
return;
712
}
713
if (infoPriority == existingPriority &&
653
- info.additionalInfo['isAutoShield'] == true &&
714
+ (info.additionalInfo['isAutoShield'] == true ||
715
+ info.additionalInfo['isIronwoodMigration'] == true) &&
716
info.direction == TransactionDirection.outgoing &&
717
existing.direction == TransactionDirection.incoming) {
718
byHash[hash] = info;
@@ -813,6 +875,10 @@ abstract class ZcashWalletBase
875
876
final Map<String, ZcashTransactionInfo> splitEntries = {};
877
for (final tx in txs) {
878
+ _pendingOutgoingAmounts.remove(ZcashWalletService.normalizeTxId(tx.txHash));
879
+ if (tx.height > 0) {
880
+ ZcashMempoolService.instance.removeTx(tx.txHash);
881
+ }
882
final isShield = _isShieldActionTx(tx, rotationSweepHashes: rotationSweepHashes);
883
if (_shouldSplitAutoshieldTx(tx, isShield: isShield)) {
884
byHash.remove(tx.txHash);
@@ -834,10 +900,15 @@ abstract class ZcashWalletBase
900
_offerTx(byHash, _zcashInfoFromZkoolTx(tx, currentHeight, isShieldAction: isShield));
901
}
902
837
- final knownHashes = {for (final tx in txs) tx.txHash, ...byHash.keys};
903
+ final knownHashes = {
904
+ for (final tx in txs) ZcashWalletService.normalizeTxId(tx.txHash),
905
+ for (final hash in byHash.keys) ZcashWalletService.normalizeTxId(hash),
906
+ };
907
for (final mempoolTx in ZcashMempoolService.instance.txsForAccount(accountId)) {
908
final hash = ZcashWalletService.normalizeTxId(mempoolTx.txid);
909
if (knownHashes.contains(hash)) {
910
+ ZcashMempoolService.instance.removeTx(hash);
911
+ _pendingOutgoingAmounts.remove(hash);
912
continue;
913
}
914
final info = _zcashInfoFromMempoolTx(mempoolTx, accountId);
@@ -998,31 +1069,36 @@ abstract class ZcashWalletBase
1069
}
1070
1071
bool _isTransactionUpdating = false;
1072
+ bool _transactionUpdateQueued = false;
1073
1074
Future<void> updateTransactions() async {
1003
- try {
1004
- if (_isTransactionUpdating) {
1005
- return;
1006
- }
1007
-
1008
- _isTransactionUpdating = true;
1009
- final transactions = await fetchTransactions();
1010
-
1011
- final currentIds = transactionHistory.transactions.keys.toSet();
1012
- final newIds = transactions.keys.toSet();
1013
-
1014
- currentIds
1015
- .difference(newIds)
1016
- .forEach((final id) => transactionHistory.transactions.remove(id));
1075
+ if (_isTransactionUpdating) {
1076
+ _transactionUpdateQueued = true;
1077
+ return;
1078
+ }
1079
1018
- transactions.forEach((final key, final tx) {
1019
- transactionHistory.transactions[key] = tx;
1020
- });
1021
- await transactionHistory.save();
1022
- _isTransactionUpdating = false;
1080
+ _isTransactionUpdating = true;
1081
+ try {
1082
+ do {
1083
+ _transactionUpdateQueued = false;
1084
+ final transactions = await fetchTransactions();
1085
+
1086
+ final currentIds = transactionHistory.transactions.keys.toSet();
1087
+ final newIds = transactions.keys.toSet();
1088
+
1089
+ currentIds
1090
+ .difference(newIds)
1091
+ .forEach((final id) => transactionHistory.transactions.remove(id));
1092
+
1093
+ transactions.forEach((final key, final tx) {
1094
+ transactionHistory.transactions[key] = tx;
1095
+ });
1096
+ await transactionHistory.save();
1097
+ } while (_transactionUpdateQueued);
1098
} catch (e, stackTrace) {
1099
printV("Update transactions error: $e");
1100
printV("Stack trace: $stackTrace");
1101
+ } finally {
1102
_isTransactionUpdating = false;
1103
}
1104
}
@@ -1081,6 +1157,8 @@ abstract class ZcashWalletBase
1157
1158
static final autoShieldMutex = Mutex();
1159
static DateTime? _lastAutoShieldAt;
1160
+ static final ironwoodMigrateMutex = Mutex();
1161
+ static DateTime? _lastIronwoodMigrateAt;
1162
Future<void> _autoShield() async {
1163
if (_lastAutoShieldAt != null &&
1164
_lastAutoShieldAt!.isAfter(DateTime.now().subtract(const Duration(seconds: 75)))) {
@@ -1105,35 +1183,37 @@ abstract class ZcashWalletBase
1183
accountId: accountId,
1184
func: (coin) async {
1185
final _notes = await zkool_account.listNotes(c: coin);
1108
- final List<zkool_account.TxNote> txNotes = [];
1186
+ BigInt sweepable = BigInt.zero;
1187
for (int i = 0; i < _notes.length; i++) {
1188
final note = _notes[i];
1189
+ if (note.pool < 0 || note.pool >= NotePool.values.length) {
1190
+ continue;
1191
+ }
1192
final noteType = NotePool.values[note.pool];
1112
- if ([NotePool.sapling, NotePool.transparent].contains(noteType)) {
1113
- txNotes.add(note);
1193
+ if (noteType == NotePool.transparent || noteType == NotePool.sapling) {
1194
+ sweepable += note.value;
1195
}
1196
}
1197
1117
- final sweepable = txNotes.isEmpty
1118
- ? BigInt.from(0)
1119
- : txNotes.map((final txn) => txn.value).reduce((final a, final b) => a + b);
1120
-
1198
if (sweepable <= BigInt.from(_autoShieldMinSweep)) {
1199
return null;
1200
}
1201
1202
+ final ironwood = await zkool_network.isIronwoodActive(c: coin);
1203
final txPlan = await zkool_pay.prepare(
1204
recipients: [
1205
zkool_paydart.Recipient(
1206
assetBase: zecBase,
1207
address: walletAddresses.orchardAddress!,
1208
amount: sweepable,
1209
+ pools: ironwood ? ironwoodPoolMask : null,
1210
),
1211
],
1212
options: zkool_pay.PaymentOptions(
1213
srcPools: 3,
1214
recipientPaysFee: true,
1215
smartTransparent: false,
1216
+ mode: 0,
1217
),
1218
c: coin,
1219
);
@@ -1156,34 +1236,134 @@ abstract class ZcashWalletBase
1236
_lastAutoShieldAt = DateTime.now();
1237
printV("shielded: $txId");
1238
await updateTransactions();
1159
- await _refreshBalance(runAutoShield: false);
1239
+ await _refreshBalance(runAutoShield: false, runIronwoodMigrate: false);
1240
}
1241
1162
- Future<void> _refreshBalance({required final bool runAutoShield}) async {
1242
+ Future<void> _ironwoodMigrate() async {
1243
+ if (_lastIronwoodMigrateAt != null &&
1244
+ _lastIronwoodMigrateAt!.isAfter(DateTime.now().subtract(const Duration(seconds: 75)))) {
1245
+ return;
1246
+ }
1247
try {
1164
- final bal = await runWithCoin(
1248
+ await ironwoodMigrateMutex.acquire();
1249
+ await _$ironwoodMigrate();
1250
+ } catch (e, s) {
1251
+ printV("ironwood migration failed: $e");
1252
+ s.toString().split("\n").forEach(printV);
1253
+ } finally {
1254
+ ironwoodMigrateMutex.release();
1255
+ }
1256
+ }
1257
+
1258
+ Future<void> _$ironwoodMigrate() async {
1259
+ if (syncStatus is! SyncedSyncStatus) {
1260
+ return;
1261
+ }
1262
+ final event = await runWithCoin(
1263
+ accountId: accountId,
1264
+ func: (coin) async {
1265
+ if (!await zkool_network.isIronwoodActive(c: coin)) {
1266
+ return null;
1267
+ }
1268
+ final bal = await zkool_sync.balance(c: coin);
1269
+ if (bal.field0.length <= 2 || bal.field0[2] <= BigInt.zero) {
1270
+ return null;
1271
+ }
1272
+ return zkool_migrate.stepMigration(c: coin);
1273
+ },
1274
+ );
1275
+ if (event == null) {
1276
+ return;
1277
+ }
1278
+ switch (event) {
1279
+ case zkool_migrate.MigrationEvent_Complete():
1280
+ case zkool_migrate.MigrationEvent_NothingToDo():
1281
+ return;
1282
+ case zkool_migrate.MigrationEvent_SplitComplete(:final fee):
1283
+ printV("ironwood split step complete, fee: $fee");
1284
+ case zkool_migrate.MigrationEvent_MigrateComplete(:final fee):
1285
+ printV("ironwood migrate step complete, fee: $fee");
1286
+ case zkool_migrate.MigrationEvent_Error(:final message):
1287
+ printV("ironwood migration error: $message");
1288
+ return;
1289
+ }
1290
+
1291
+ _lastIronwoodMigrateAt = DateTime.now();
1292
+ await updateTransactions();
1293
+ await _refreshBalance(runAutoShield: false, runIronwoodMigrate: false);
1294
+ }
1295
+
1296
+ Future<void> _updateIronwoodActive() async {
1297
+ bool? active;
1298
+ try {
1299
+ active = await runWithCoin(
1300
accountId: accountId,
1166
- func: (coin) => zkool_sync.balance(c: coin),
1301
+ func: (final coin) => zkool_network.isIronwoodActive(c: coin),
1302
);
1303
+ } catch (e) {
1304
+ printV("isIronwoodActive: $e");
1305
+ }
1306
1169
- // 0 - transparent
1170
- // 1 - sapling
1171
- // 2 - orchard
1172
- final confirmedTotal = bal.field0.reduce((final a, final b) => a + b);
1307
+ if (active == null && networkFor(walletInfo) == ZcashNetwork.regtest) {
1308
+ try {
1309
+ final height = await runWithCoin(
1310
+ accountId: accountId,
1311
+ func: (final coin) => zkool_network.getCurrentHeight(c: coin),
1312
+ );
1313
+ active = height >= ZcashNetwork.regtestNu63Height;
1314
+ printV("regtest ironwood inferred from height $height: $active");
1315
+ } catch (e) {
1316
+ printV("regtest height check failed: $e");
1317
+ }
1318
+ }
1319
1174
- // int knownOutPending = 0;
1175
- // ZcashWalletBase.temporarySentTx[accountId]?.forEach((final sTx) {
1176
- // knownOutPending += sTx.value; // it's negative
1177
- // });
1178
- final confirmedSpendable = confirmedTotal - bal.field0[0];
1320
+ if (active == null) {
1321
+ return;
1322
+ }
1323
+
1324
+ ironwoodActive = active;
1325
+ runInAction(() => walletAddresses.setIronwoodActive(active!));
1326
+ printV("ironwoodActive=$active (account $accountId)");
1327
+ }
1328
+
1329
+ Future<void> _refreshBalance({
1330
+ required final bool runAutoShield,
1331
+ final bool runIronwoodMigrate = true,
1332
+ }) async {
1333
+ try {
1334
+ await _updateIronwoodActive();
1335
+ final bal = await runWithCoin(
1336
+ accountId: accountId,
1337
+ func: (final coin) async => zkool_sync.balance(c: coin),
1338
+ );
1339
+
1340
+ // 0 - transparent, 1 - sapling, 2 - orchard, 3 - ironwood
1341
+ final transparent = bal.field0[0];
1342
+ final sapling = bal.field0.length > 1 ? bal.field0[1] : BigInt.zero;
1343
+ final orchard = bal.field0.length > 2 ? bal.field0[2] : BigInt.zero;
1344
+ final ironwood = bal.field0.length > 3 ? bal.field0[3] : BigInt.zero;
1345
1346
if (runAutoShield) {
1347
await _autoShield();
1348
}
1349
+ if (runIronwoodMigrate) {
1350
+ await _ironwoodMigrate();
1351
+ }
1352
+
1353
+ // After NU6.3, Orchard notes are migrated to Ironwood - show them as unconfirmed.
1354
+ final BigInt availableAmount;
1355
+ final BigInt unavailableAmount;
1356
+ if (ironwoodActive == true && orchard > BigInt.zero) {
1357
+ availableAmount = sapling + ironwood;
1358
+ unavailableAmount = transparent + orchard;
1359
+ } else {
1360
+ availableAmount = sapling + orchard + ironwood;
1361
+ unavailableAmount = transparent;
1362
+ }
1363
1364
balance[CryptoCurrency.zec] = ZcashBalance(
1185
- Money(confirmedSpendable, currency),
1186
- Money(confirmedTotal - confirmedSpendable, currency),
1365
+ Money(availableAmount, currency),
1366
+ Money(unavailableAmount, currency),
1367
frozen: Money.zero(currency),
1368
);
1369
} catch (e, stackTrace) {
@@ -1211,7 +1391,9 @@ abstract class ZcashWalletBase
1391
late ZcashWalletAddresses walletAddresses = ZcashWalletAddresses(accountId, walletInfo);
1392
1393
static Future<ZcashWallet> create(final WalletCredentials credentials) async {
1214
- await $init();
1394
+ final network = networkForCredentials(credentials);
1395
+ await $init(network: network);
1396
+ credentials.walletInfo?.network = network.value;
1397
final newWalletCredentials = credentials as ZcashNewWalletCredentials;
1398
1399
String mnemonic;
@@ -1222,7 +1404,7 @@ abstract class ZcashWalletBase
1404
mnemonic = bip39.generateMnemonic(strength: strength);
1405
}
1406
1225
- final birthHeight = await ZcashHeight.getBlockHeightByTime(DateTime.now());
1407
+ final birthHeight = await birthHeightForNetwork(network);
1408
1409
final accountId = await restoreZcashWalletFromSeed(
1410
name: credentials.name,
@@ -1241,7 +1423,9 @@ abstract class ZcashWalletBase
1423
}
1424
1425
static Future<ZcashWallet> restore(final WalletCredentials credentials) async {
1244
- await $init();
1426
+ final network = networkForCredentials(credentials);
1427
+ await $init(network: network);
1428
+ credentials.walletInfo?.network = network.value;
1429
final fromSeedCredentials = credentials as ZcashFromSeedWalletCredentials;
1430
final String? seed = fromSeedCredentials.seed;
1431
if (seed == null || seed.isEmpty) {
@@ -1265,7 +1449,9 @@ abstract class ZcashWalletBase
1449
}
1450
1451
static Future<ZcashWallet> restoreKeys(final WalletCredentials credentials) async {
1268
- await $init();
1452
+ final network = networkForCredentials(credentials);
1453
+ await $init(network: network);
1454
+ credentials.walletInfo?.network = network.value;
1455
final fromKeysCredentials = credentials as ZcashFromKeysWalletCredentials;
1456
final String? keys = fromKeysCredentials.privateKey;
1457
if (keys == null || keys.isEmpty) {
@@ -1299,7 +1485,8 @@ abstract class ZcashWalletBase
1485
required final String password,
1486
required final WalletInfo walletInfo,
1487
}) async {
1302
- await $init();
1488
+ final network = networkFor(walletInfo);
1489
+ await $init(network: network);
1490
// if (password.isNotEmpty) {
1491
// setDbPasswd(coin, password);
1492
// }
@@ -1379,9 +1566,32 @@ abstract class ZcashWalletBase
1566
1567
static WalletType get _type => WalletType.zcash;
1568
1382
- static Future<String> getDbDataPath() async {
1569
+ static ZcashNetwork networkFor(final WalletInfo? walletInfo) =>
1570
+ ZcashNetwork.fromName(walletInfo?.network ?? ZcashNetwork.mainnet.value);
1571
+
1572
+ static ZcashNetwork networkForCredentials(final WalletCredentials credentials) {
1573
+ if (credentials is ZcashNewWalletCredentials) {
1574
+ return ZcashNetwork.fromIndex(credentials.network);
1575
+ }
1576
+ if (credentials is ZcashFromSeedWalletCredentials) {
1577
+ return ZcashNetwork.fromIndex(credentials.network);
1578
+ }
1579
+ if (credentials is ZcashFromKeysWalletCredentials) {
1580
+ return ZcashNetwork.fromIndex(credentials.network);
1581
+ }
1582
+ return ZcashNetwork.mainnet;
1583
+ }
1584
+
1585
+ static Future<int> birthHeightForNetwork(final ZcashNetwork network) async {
1586
+ if (network != ZcashNetwork.mainnet) {
1587
+ return 1;
1588
+ }
1589
+ return ZcashHeight.getBlockHeightByTime(DateTime.now());
1590
+ }
1591
+
1592
+ static Future<String> getDbDataPath({final ZcashNetwork network = ZcashNetwork.mainnet}) async {
1593
final pathForWalletType = await pathForWalletTypeDir(type: _type);
1384
- final dbDataPath = "${pathForWalletType}/zec.v2.db";
1594
+ final dbDataPath = "${pathForWalletType}/${network.dbFileName}";
1595
if (!Directory(pathForWalletType).existsSync()) {
1596
Directory(pathForWalletType).createSync(recursive: true);
1597
}
@@ -1398,6 +1608,8 @@ abstract class ZcashWalletBase
1608
}
1609
1610
static bool _initialized = false;
1611
+ static bool _rustInitialized = false;
1612
+ static ZcashNetwork? _activeNetwork;
1613
1614
static void unlockDatabase(final String password) {
1615
_password = password;
@@ -1406,22 +1618,25 @@ abstract class ZcashWalletBase
1618
static var c = zkool_coin.Coin();
1619
1620
static String? _password;
1409
- static Future<void> $init() async {
1410
- if (_initialized) return;
1411
- _initialized = true;
1412
- printV(r".$init()");
1413
- await zkool_frb.RustLib.init();
1621
+ static Future<void> $init({final ZcashNetwork network = ZcashNetwork.mainnet}) async {
1622
+ if (!_rustInitialized) {
1623
+ await zkool_frb.RustLib.init();
1624
+ _rustInitialized = true;
1625
+ }
1626
+ if (_initialized && _activeNetwork == network) {
1627
+ return;
1628
+ }
1629
+ printV(r".$init($network)");
1630
ZcashMempoolService.instance.onAccountsUpdated = (final accountIds) {
1631
for (final accountId in accountIds) {
1632
unawaited(refreshWalletForAccount(accountId));
1633
}
1634
};
1419
- final dbFile = File(await getDbDataPath());
1635
+ final dbFile = File(await getDbDataPath(network: network));
1636
final ywalletDbFile = File(await getDbDataPathLegacyYwallet());
1637
await zkool_network.initDatadir(directory: dbFile.parent.path);
1422
- // c = await c.openDatabase(dbFilepath: dbFile.path, password: 'cw_zcash_migration');
1638
c = await c.openDatabase(dbFilepath: dbFile.path, password: null);
1424
- printV("initWallet");
1639
+ printV("initWallet: ${dbFile.path}");
1640
if (_password == null) {
1641
throw Exception("Zcash wallet locked! Please contact support");
1642
}
@@ -1432,6 +1647,7 @@ abstract class ZcashWalletBase
1647
//TODO(mrcyjanek): migrate to zkool
1648
}
1649
1650
+ _activeNetwork = network;
1651
_initialized = true;
1652
}
1653
cw_zcash/lib/src/zcash_wallet_addresses.dart
+16
-8
@@ -39,8 +39,24 @@ abstract class ZcashWalletAddressesBase extends WalletAddresses with Store {
39
@observable
40
String? unifiedAddress;
41
42
+ @observable
43
+ bool ironwoodActive = false;
44
+
45
+ @action
46
+ void setIronwoodActive(final bool active) {
47
+ ironwoodActive = active;
48
+ }
49
+
50
@override
51
@computed
52
+ List<ReceivePageOption> get receivePageOptions {
53
+ return [
54
+ ...ZcashReceivePageOption.allOptionsFor(ironwood: ironwoodActive),
55
+ ...ReceivePageOptions.where((final element) => element != ReceivePageOption.mainnet),
56
+ ];
57
+ }
58
+
59
+ @override
60
String get latestAddress {
61
switch (addressPageType) {
62
case ZcashAddressType.transparent:
@@ -267,14 +283,6 @@ abstract class ZcashWalletAddressesBase extends WalletAddresses with Store {
283
return addressInfos[0] ?? [];
284
}
285
270
- @override
271
- List<ReceivePageOption> get receivePageOptions {
272
- return [
273
- ...ZcashReceivePageOption.all,
274
- ...ReceivePageOptions.where((final element) => element != ReceivePageOption.mainnet),
275
- ];
276
- }
277
-
286
@override
287
PaymentURI getPaymentUri(final String amount) => ZcashURI(amount: amount, address: address);
288
}
cw_zcash/lib/src/zcash_wallet_service.dart
+5
-5
@@ -91,7 +91,11 @@ class ZcashWalletService
91
92
@override
93
Future<ZcashWallet> openWallet(final String name, final String password) async {
94
- await ZcashWalletBase.$init();
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()) {
@@ -100,10 +104,6 @@ class ZcashWalletService
104
}
105
106
await loadShieldTxs();
103
- final walletInfo = await WalletInfo.get(name, getType());
104
- if (walletInfo == null) {
105
- throw Exception('Wallet not found');
106
- }
107
try {
108
final wallet = await ZcashWalletBase.open(
109
name: name,
cw_zcash/lib/src/zkool_compat.dart
+8
@@ -1,3 +1,11 @@
1
import 'dart:typed_data';
2
3
final zecBase = Uint8List(32);
4
+
5
+/// Whether NU6.3 (Ironwood) is active at the chain tip. Updated on balance refresh.
6
+bool ironwoodActive = false;
7
+
8
+/// Pool bitmask for Ironwood (pool index 3). Orchard and Ironwood share the same address.
9
+const ironwoodPoolMask = 8;
10
+
11
+int? get shieldedRecipientPools => ironwoodActive ? ironwoodPoolMask : null;
cw_zcash/lib/src/zkooltx.dart
+27
-2
@@ -17,7 +17,7 @@ enum TxType {
17
transparentSelfTransfer, // 12
18
}
19
20
-enum NotePool { transparent, sapling, orchard, unknown }
20
+enum NotePool { transparent, sapling, orchard, ironwood }
21
22
class ZkoolTx {
23
ZkoolTx(final zkool_account.Tx tx, final zkool_account.TxAccount txAccount)
@@ -72,6 +72,22 @@ class ZkoolTx {
72
.fold(BigInt.zero, (final a, final o) => a + o.value);
73
}
74
75
+ BigInt get ironwoodReceived {
76
+ final fromNotes = _txAccount.notes
77
+ .where((final n) => n.pool == NotePool.ironwood.index)
78
+ .fold(BigInt.zero, (final a, final n) => a + n.value);
79
+ if (fromNotes > BigInt.zero) {
80
+ return fromNotes;
81
+ }
82
+ return _txAccount.outputs
83
+ .where((final o) => o.pool == NotePool.ironwood.index)
84
+ .fold(BigInt.zero, (final a, final o) => a + o.value);
85
+ }
86
+
87
+ BigInt get orchardSpent => _txAccount.spends
88
+ .where((final s) => s.pool == NotePool.orchard.index)
89
+ .fold(BigInt.zero, (final a, final s) => a + s.value);
90
+
91
String get txHash {
92
final reversed = Uint8List.fromList(_txAccount.txid.reversed.toList());
93
final txId = uint8ListToHex(reversed);
@@ -222,6 +238,13 @@ class ZkoolTx {
238
}
239
240
static Uint8List _txidFromJson(final dynamic raw) {
241
+ return _bytesFromJson(raw, fieldName: "txid");
242
+ }
243
+
244
+ static Uint8List _bytesFromJson(final dynamic raw, {required final String fieldName}) {
245
+ if (raw == null) {
246
+ return Uint8List(0);
247
+ }
248
if (raw is Uint8List) {
249
return raw;
250
}
@@ -231,7 +254,7 @@ class ZkoolTx {
254
if (raw is String) {
255
return base64.decode(raw);
256
}
234
- throw FormatException("Invalid txid in ZkoolTx json: $raw");
257
+ throw FormatException("Invalid $fieldName in ZkoolTx json: $raw");
258
}
259
260
static List<T> _listFromJson<T>(final dynamic raw, final T Function(Map<String, dynamic>) parse) {
@@ -257,6 +280,7 @@ class ZkoolTx {
280
tpe: txJson["tpe"] == null ? null : _asInt(txJson["tpe"]),
281
zsaValue: _asInt(txJson["zsaValue"]),
282
assetDisplay: txJson["assetDisplay"] as String? ?? "",
283
+ isUserMemo: txJson["isUserMemo"] as bool? ?? false,
284
),
285
zkool_account.TxAccount(
286
id: _asInt(txAccountJson["id"]),
@@ -304,6 +328,7 @@ class ZkoolTx {
328
pool: _asInt(a["pool"]),
329
output: a["output"] == null ? null : _asInt(a["output"]),
330
memo: a["memo"] as String?,
331
+ memoBytes: _bytesFromJson(a["memoBytes"], fieldName: "memoBytes"),
332
),
333
),
334
),
cw_zcash/pubspec.yaml
+1
-1
@@ -21,7 +21,7 @@ dependencies:
21
# path: ../../zkool2
22
git:
23
url: https://github.com/cake-tech/zkool2.git
24
- ref: dbd3dde8d8485428326a58e55660b7404666dd7c
24
+ ref: 97d18db966c061a536fa9056efb3756f4b9aa89d
25
26
path_provider: any
27
sqflite_common_ffi: any
ios/Podfile
+1
-1
@@ -43,7 +43,7 @@ post_install do |installer|
43
flutter_additional_ios_build_settings(target)
44
45
target.build_configurations.each do |config|
46
- config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
46
+ config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0'
47
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
48
'$(inherited)',
49
ios/Podfile.lock
+8
-59
@@ -1,14 +1,4 @@
1
PODS:
2
- - AppAuth (1.7.6):
3
- - AppAuth/Core (= 1.7.6)
4
- - AppAuth/ExternalUserAgent (= 1.7.6)
5
- - AppAuth/Core (1.7.6)
6
- - AppAuth/ExternalUserAgent (1.7.6):
7
- - AppAuth/Core
8
- - AppCheckCore (11.2.0):
9
- - GoogleUtilities/Environment (~> 8.0)
10
- - GoogleUtilities/UserDefaults (~> 8.0)
11
- - PromisesObjC (~> 2.4)
2
- bitbox_flutter (0.0.1):
3
- Flutter
4
- breez_sdk_spark_flutter (0.14.0):
@@ -64,6 +54,8 @@ PODS:
54
- DKImagePickerController/PhotoGallery
55
- Flutter
56
- Flutter (1.0.0)
57
+ - flutter_contacts (0.0.1):
58
+ - Flutter
59
- flutter_inappwebview_ios (0.0.1):
60
- Flutter
61
- flutter_inappwebview_ios/Core (= 0.0.1)
@@ -83,34 +75,6 @@ PODS:
75
- Flutter
76
- fluttertoast (0.0.2):
77
- Flutter
86
- - google_sign_in_ios (0.0.1):
87
- - AppAuth (>= 1.7.4)
88
- - Flutter
89
- - FlutterMacOS
90
- - GoogleSignIn (~> 8.0)
91
- - GTMSessionFetcher (>= 3.4.0)
92
- - GoogleSignIn (8.0.0):
93
- - AppAuth (< 2.0, >= 1.7.3)
94
- - AppCheckCore (~> 11.0)
95
- - GTMAppAuth (< 5.0, >= 4.1.1)
96
- - GTMSessionFetcher/Core (~> 3.3)
97
- - GoogleUtilities/Environment (8.1.0):
98
- - GoogleUtilities/Privacy
99
- - GoogleUtilities/Logger (8.1.0):
100
- - GoogleUtilities/Environment
101
- - GoogleUtilities/Privacy
102
- - GoogleUtilities/Privacy (8.1.0)
103
- - GoogleUtilities/UserDefaults (8.1.0):
104
- - GoogleUtilities/Logger
105
- - GoogleUtilities/Privacy
106
- - GTMAppAuth (4.1.1):
107
- - AppAuth/Core (~> 1.7)
108
- - GTMSessionFetcher/Core (< 4.0, >= 3.3)
109
- - GTMSessionFetcher (3.5.0):
110
- - GTMSessionFetcher/Full (= 3.5.0)
111
- - GTMSessionFetcher/Core (3.5.0)
112
- - GTMSessionFetcher/Full (3.5.0):
113
- - GTMSessionFetcher/Core
78
- image_picker_ios (0.0.1):
79
- Flutter
80
- in_app_review (2.0.0):
@@ -126,7 +90,6 @@ PODS:
90
- payjoin_flutter (0.23.0)
91
- permission_handler_apple (9.4.8):
92
- Flutter
129
- - PromisesObjC (2.4.0)
93
- quick_actions_ios (0.0.1):
94
- Flutter
95
- reown_yttrium (0.0.1):
@@ -204,6 +167,7 @@ DEPENDENCIES:
167
- fast_scanner (from `.symlinks/plugins/fast_scanner/ios`)
168
- file_picker (from `.symlinks/plugins/file_picker/ios`)
169
- Flutter (from `Flutter`)
170
+ - flutter_contacts (from `.symlinks/plugins/flutter_contacts/ios`)
171
- flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
172
- flutter_local_authentication (from `.symlinks/plugins/flutter_local_authentication/ios`)
173
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
@@ -211,7 +175,6 @@ DEPENDENCIES:
175
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
176
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
177
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
214
- - google_sign_in_ios (from `.symlinks/plugins/google_sign_in_ios/darwin`)
178
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
179
- in_app_review (from `.symlinks/plugins/in_app_review/ios`)
180
- integration_test (from `.symlinks/plugins/integration_test/ios`)
@@ -237,17 +200,10 @@ DEPENDENCIES:
200
201
SPEC REPOS:
202
https://github.com/CocoaPods/Specs.git:
240
- - AppAuth
241
- - AppCheckCore
203
- CryptoSwift
204
- DKImagePickerController
205
- DKPhotoGallery
245
- - GoogleSignIn
246
- - GoogleUtilities
247
- - GTMAppAuth
248
- - GTMSessionFetcher
206
- OrderedSet
250
- - PromisesObjC
207
- SDWebImage
208
- sqlite3
209
- SwiftyGif
@@ -278,6 +234,8 @@ EXTERNAL SOURCES:
234
:path: ".symlinks/plugins/file_picker/ios"
235
Flutter:
236
:path: Flutter
237
+ flutter_contacts:
238
+ :path: ".symlinks/plugins/flutter_contacts/ios"
239
flutter_inappwebview_ios:
240
:path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
241
flutter_local_authentication:
@@ -292,8 +250,6 @@ EXTERNAL SOURCES:
250
:path: ".symlinks/plugins/flutter_secure_storage/ios"
251
fluttertoast:
252
:path: ".symlinks/plugins/fluttertoast/ios"
295
- google_sign_in_ios:
296
- :path: ".symlinks/plugins/google_sign_in_ios/darwin"
253
image_picker_ios:
254
:path: ".symlinks/plugins/image_picker_ios/ios"
255
in_app_review:
@@ -340,8 +296,6 @@ EXTERNAL SOURCES:
296
:path: ".symlinks/plugins/wakelock_plus/ios"
297
298
SPEC CHECKSUMS:
343
- AppAuth: d4f13a8fe0baf391b2108511793e4b479691fb73
344
- AppCheckCore: cc8fd0a3a230ddd401f326489c99990b013f0c4f
299
bitbox_flutter: 9505732798041c413152669751beeaecc5fe400f
300
breez_sdk_spark_flutter: ed3b6709b8ce9b40309d8728640eade5b19e8a0d
301
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
@@ -357,6 +311,7 @@ SPEC CHECKSUMS:
311
fast_scanner: 2cb1ad3e69e645e9980fb4961396ce5804caa3e3
312
file_picker: 9b3292d7c8bc68c8a7bf8eb78f730e49c8efc517
313
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
314
+ flutter_contacts: 2ec1d6b62ca2869c3bbb3871bd74d4e3ef157135
315
flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
316
flutter_local_authentication: 989278c681612f1ee0e36019e149137f114b9d7f
317
flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb
@@ -364,11 +319,6 @@ SPEC CHECKSUMS:
319
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
320
flutter_secure_storage: 2c2ff13db9e0a5647389bff88b0ecac56e3f3418
321
fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1
367
- google_sign_in_ios: b48bb9af78576358a168361173155596c845f0b9
368
- GoogleSignIn: ce8c89bb9b37fb624b92e7514cc67335d1e277e4
369
- GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
370
- GTMAppAuth: f69bd07d68cd3b766125f7e072c45d7340dea0de
371
- GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6
322
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
323
in_app_review: 7dd1ea365263f834b8464673f9df72c80c17c937
324
integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
@@ -377,7 +327,6 @@ SPEC CHECKSUMS:
327
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
328
payjoin_flutter: d9d4c8aa16bd5dfedb9b21d0edc8199e0187d96e
329
permission_handler_apple: 92d754bbaa7361d436db2d6c3c1c2a0fdcec462e
380
- PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
330
quick_actions_ios: 500fcc11711d9f646739093395c4ae8eec25f779
331
reown_yttrium: cee334ade64725b1d83f7b34c706a6aae2696d58
332
rive_common: dd421daaf9ae69f0125aa761dd96abd278399952
@@ -398,6 +347,6 @@ SPEC CHECKSUMS:
347
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
348
YttriumWrapper: 31e937fe9fbe0f1314d2ca6be9ce9b379a059966
349
401
-PODFILE CHECKSUM: 5296465b1c6d14d506230356756826012f65d97a
350
+PODFILE CHECKSUM: b08d7f4c8d9c64f5648599a30caea878b0f7616a
351
403
-COCOAPODS: 1.16.2
352
+COCOAPODS: 1.17.0
ios/Runner.xcodeproj/project.pbxproj
+6
-6
@@ -479,7 +479,7 @@
479
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
480
GCC_WARN_UNUSED_FUNCTION = YES;
481
GCC_WARN_UNUSED_VARIABLE = YES;
482
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
482
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
483
MTL_ENABLE_DEBUG_INFO = NO;
484
SDKROOT = iphoneos;
485
SUPPORTED_PLATFORMS = iphoneos;
@@ -509,7 +509,7 @@
509
"$(PROJECT_DIR)",
510
);
511
INFOPLIST_FILE = Runner/Info.plist;
512
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
512
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
513
LD_RUNPATH_SEARCH_PATHS = (
514
"$(inherited)",
515
"@executable_path/Frameworks",
@@ -578,7 +578,7 @@
578
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
579
GCC_WARN_UNUSED_FUNCTION = YES;
580
GCC_WARN_UNUSED_VARIABLE = YES;
581
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
581
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
582
MTL_ENABLE_DEBUG_INFO = YES;
583
ONLY_ACTIVE_ARCH = YES;
584
SDKROOT = iphoneos;
@@ -628,7 +628,7 @@
628
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
629
GCC_WARN_UNUSED_FUNCTION = YES;
630
GCC_WARN_UNUSED_VARIABLE = YES;
631
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
631
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
632
MTL_ENABLE_DEBUG_INFO = NO;
633
SDKROOT = iphoneos;
634
SUPPORTED_PLATFORMS = iphoneos;
@@ -660,7 +660,7 @@
660
"$(PROJECT_DIR)",
661
);
662
INFOPLIST_FILE = Runner/Info.plist;
663
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
663
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
664
LD_RUNPATH_SEARCH_PATHS = (
665
"$(inherited)",
666
"@executable_path/Frameworks",
@@ -704,7 +704,7 @@
704
"$(PROJECT_DIR)",
705
);
706
INFOPLIST_FILE = Runner/Info.plist;
707
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
707
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
708
LD_RUNPATH_SEARCH_PATHS = (
709
"$(inherited)",
710
"@executable_path/Frameworks",
lib/core/address_validator.dart
+11
-4
@@ -3,6 +3,7 @@ import 'package:cake_wallet/generated/i18n.dart';
3
import 'package:cake_wallet/core/validator.dart';
4
import 'package:cake_wallet/solana/solana.dart';
5
import 'package:cake_wallet/zano/zano.dart';
6
+import 'package:cake_wallet/zcash/zcash_network_type.dart';
7
import 'package:cw_core/crypto_currency.dart';
8
import 'package:cw_core/erc20_token.dart';
9
@@ -10,8 +11,11 @@ const BEFORE_REGEX = '(^|\\s)';
11
const AFTER_REGEX = '(\$|\\s)';
12
13
class AddressValidator extends TextValidator {
13
- AddressValidator({required CryptoCurrency type, bool isTestnet = false})
14
- : super(
14
+ AddressValidator({
15
+ required CryptoCurrency type,
16
+ bool isTestnet = false,
17
+ String? network,
18
+ }) : super(
19
errorMessage: S.current.error_text_address,
20
useAdditionalValidation: [CryptoCurrency.btc, CryptoCurrency.ltc].contains(type)
21
? (String txt) {
@@ -32,7 +36,7 @@ class AddressValidator extends TextValidator {
36
: type == CryptoCurrency.zano
37
? zano?.validateAddress
38
: null,
35
- pattern: getPattern(type, isTestnet: isTestnet),
39
+ pattern: getPattern(type, isTestnet: isTestnet, network: network),
40
length: getLength(type),
41
);
42
@@ -61,7 +65,7 @@ class AddressValidator extends TextValidator {
65
CryptoCurrency.btcln,
66
];
67
64
- static String getPattern(CryptoCurrency type, {bool isTestnet = false}) {
68
+ static String? getPattern(CryptoCurrency type, {bool isTestnet = false, String? network}) {
69
var pattern = "";
70
if (type is Erc20Token) {
71
pattern = '0x[0-9a-zA-Z]+';
@@ -170,6 +174,9 @@ class AddressValidator extends TextValidator {
174
case CryptoCurrency.hbar:
175
pattern = '[0-9a-zA-Z.]+';
176
case CryptoCurrency.zec:
177
+ if (ZcashNetworkType.isDevNetwork(network)) {
178
+ return null;
179
+ }
180
pattern = '(?:'
181
't1[0-9A-Za-z]{33}'
182
'|t3[0-9A-Za-z]{33}'
lib/new-ui/widgets/receive_page/receive_address_type_selector.dart
+3
@@ -6,6 +6,7 @@ import 'package:cake_wallet/src/widgets/section_divider.dart';
6
import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
7
import 'package:cw_core/receive_page_option.dart';
8
import 'package:flutter/material.dart';
9
+import 'package:flutter_mobx/flutter_mobx.dart';
10
import 'package:flutter_svg/flutter_svg.dart';
11
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
12
@@ -34,6 +35,7 @@ class _ReceiveAddressTypeSelectorState extends State<ReceiveAddressTypeSelector>
35
36
@override
37
Widget build(BuildContext context) {
38
+ return Observer(builder: (final _) {
39
final commonOptions = widget.receiveOptionViewModel.options
40
.where((element) =>
41
element.isCommon ||
@@ -223,6 +225,7 @@ class _ReceiveAddressTypeSelectorState extends State<ReceiveAddressTypeSelector>
225
],
226
)),
227
);
228
+ });
229
}
230
}
231
lib/router.dart
+6
@@ -162,6 +162,7 @@ import 'package:cw_core/unspent_coin_type.dart';
162
import 'package:cw_core/utils/print_verbose.dart';
163
import 'package:cw_core/wallet_info.dart';
164
import 'package:cw_core/wallet_type.dart';
165
+import 'package:cake_wallet/zcash/zcash_network_type.dart';
166
import 'package:flutter/cupertino.dart';
167
import 'package:flutter/material.dart';
168
import 'package:flutter/services.dart';
@@ -822,6 +823,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
823
final isChildWallet = args['isChildWallet'] as bool? ?? false;
824
final useTestnet = args['useTestnet'] as bool;
825
final toggleTestnet = args['toggleTestnet'] as Function(bool? val);
826
+ final zcashNetwork = args['zcashNetwork'] as int? ?? ZcashNetworkType.mainnet;
827
+ final setZcashNetwork =
828
+ args['setZcashNetwork'] as void Function(int network)? ?? (_) {};
829
final restoredWallet = args['restoredWallet'] as RestoredWallet?;
830
831
final viewModelParam = {'type': type, 'isPow': false};
@@ -832,6 +836,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
836
isChildWallet: isChildWallet,
837
useTestnet: useTestnet,
838
toggleUseTestnet: toggleTestnet,
839
+ zcashNetwork: zcashNetwork,
840
+ setZcashNetwork: setZcashNetwork,
841
advancedPrivacySettingsViewModel:
842
getIt.get<AdvancedPrivacySettingsViewModel>(param1: type),
843
nodeViewModel: getIt.get<NodeCreateOrEditViewModel>(param1: viewModelParam),
lib/src/screens/new_wallet/advanced_privacy_settings_page.dart
+28
@@ -14,11 +14,13 @@ import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
14
import 'package:cake_wallet/src/widgets/primary_button.dart';
15
import 'package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart';
16
import 'package:cake_wallet/utils/show_pop_up.dart';
17
+import 'package:cake_wallet/utils/feature_flag.dart';
18
import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart';
19
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
20
import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
21
import 'package:cake_wallet/view_model/settings/choices_list_item.dart';
22
import 'package:cw_core/wallet_type.dart';
23
+import 'package:cake_wallet/zcash/zcash_network_type.dart';
24
import 'package:flutter/material.dart';
25
import 'package:flutter_mobx/flutter_mobx.dart';
26
@@ -28,6 +30,8 @@ class AdvancedPrivacySettingsPage extends BasePage {
30
required this.isChildWallet,
31
required this.useTestnet,
32
required this.toggleUseTestnet,
33
+ required this.zcashNetwork,
34
+ required this.setZcashNetwork,
35
required this.advancedPrivacySettingsViewModel,
36
required this.nodeViewModel,
37
required this.seedSettingsViewModel,
@@ -44,6 +48,8 @@ class AdvancedPrivacySettingsPage extends BasePage {
48
final bool isChildWallet;
49
final bool useTestnet;
50
final Function(bool? val) toggleUseTestnet;
51
+ final int zcashNetwork;
52
+ final void Function(int network) setZcashNetwork;
53
54
@override
55
Widget body(BuildContext context) => _AdvancedPrivacySettingsBody(
@@ -51,6 +57,8 @@ class AdvancedPrivacySettingsPage extends BasePage {
57
isChildWallet,
58
useTestnet,
59
toggleUseTestnet,
60
+ zcashNetwork,
61
+ setZcashNetwork,
62
advancedPrivacySettingsViewModel,
63
nodeViewModel,
64
seedSettingsViewModel,
@@ -63,6 +71,8 @@ class _AdvancedPrivacySettingsBody extends StatefulWidget {
71
this.isChildWallet,
72
this.useTestnet,
73
this.toggleUseTestnet,
74
+ this.zcashNetwork,
75
+ this.setZcashNetwork,
76
this.privacySettingsViewModel,
77
this.nodeViewModel,
78
this.seedTypeViewModel,
@@ -76,6 +86,8 @@ class _AdvancedPrivacySettingsBody extends StatefulWidget {
86
final bool isChildWallet;
87
final bool useTestnet;
88
final Function(bool? val) toggleUseTestnet;
89
+ final int zcashNetwork;
90
+ final void Function(int network) setZcashNetwork;
91
92
@override
93
_AdvancedPrivacySettingsBodyState createState() => _AdvancedPrivacySettingsBodyState();
@@ -87,6 +99,7 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
99
final _formKey = GlobalKey<NodeFormState>();
100
final _passphraseFormKey = GlobalKey<FormState>();
101
bool? testnetValue;
102
+ int? zcashNetworkValue;
103
104
bool obscurePassphrase = true;
105
@@ -112,6 +125,7 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
125
if (testnetValue == null && widget.useTestnet) {
126
testnetValue = widget.useTestnet;
127
}
128
+ zcashNetworkValue ??= widget.zcashNetwork;
129
130
return Container(
131
padding: EdgeInsets.only(top: 24),
@@ -281,6 +295,20 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
295
],
296
);
297
}),
298
+ if (FeatureFlag.hasDevOptions &&
299
+ widget.privacySettingsViewModel.type == WalletType.zcash)
300
+ SettingsChoicesCell(
301
+ ChoicesListItem<int>(
302
+ title: 'Zcash network',
303
+ items: ZcashNetworkType.values,
304
+ selectedItem: zcashNetworkValue ?? ZcashNetworkType.mainnet,
305
+ displayItem: ZcashNetworkType.label,
306
+ onItemSelected: (final network) {
307
+ setState(() => zcashNetworkValue = network);
308
+ widget.setZcashNetwork(network);
309
+ },
310
+ ),
311
+ ),
312
if (widget.privacySettingsViewModel.type == WalletType.bitcoin ||
313
widget.privacySettingsViewModel.type == WalletType.decred)
314
Builder(builder: (_) {
lib/src/screens/new_wallet/new_wallet_page.dart
+2
@@ -316,6 +316,8 @@ class _WalletNameFormState extends State<WalletNameForm> {
316
"type": _walletNewVM.type,
317
"useTestnet": _walletNewVM.useTestnet,
318
"toggleTestnet": _walletNewVM.toggleUseTestnet,
319
+ "zcashNetwork": _walletNewVM.zcashNetwork,
320
+ "setZcashNetwork": _walletNewVM.setZcashNetwork,
321
"isChildWallet": widget.isChildWallet,
322
});
323
},
lib/src/screens/restore/wallet_restore_page.dart
+3
-1
@@ -121,7 +121,9 @@ class WalletRestorePage extends BasePage {
121
'isFromRestore': true,
122
'type': walletRestoreViewModel.type,
123
'useTestnet': walletRestoreViewModel.useTestnet,
124
- 'toggleTestnet': walletRestoreViewModel.toggleUseTestnet
124
+ 'toggleTestnet': walletRestoreViewModel.toggleUseTestnet,
125
+ 'zcashNetwork': walletRestoreViewModel.zcashNetwork,
126
+ 'setZcashNetwork': walletRestoreViewModel.setZcashNetwork,
127
},
128
);
129
},
lib/view_model/dashboard/dashboard_view_model.dart
+10
-4
@@ -538,10 +538,16 @@ abstract class DashboardViewModelBase with Store {
538
.map((item) => _txIdentityString(item.transaction.txHash, item.transaction.direction))
539
.toSet();
540
541
- transactions.removeWhere(
542
- (item) => newKeys
543
- .contains(_txIdentityString(item.transaction.txHash, item.transaction.direction)),
544
- );
541
+ transactions.removeWhere((item) {
542
+ if (wallet.type == WalletType.zcash) {
543
+ return newTransactions.any(
544
+ (n) => n.transaction.txHash == item.transaction.txHash,
545
+ );
546
+ }
547
+ return newKeys.contains(
548
+ _txIdentityString(item.transaction.txHash, item.transaction.direction),
549
+ );
550
+ });
551
552
transactions.addAll(newTransactions);
553
// transactions.clear();
lib/view_model/dashboard/receive_option_view_model.dart
+20
-1
@@ -3,6 +3,7 @@ import 'package:cake_wallet/zcash/zcash.dart';
3
import 'package:cw_core/receive_page_option.dart';
4
import 'package:cw_core/wallet_base.dart';
5
import 'package:cw_core/wallet_type.dart';
6
+import 'package:cw_zcash/cw_zcash.dart';
7
import 'package:mobx/mobx.dart';
8
9
part 'receive_option_view_model.g.dart';
@@ -18,7 +19,14 @@ abstract class ReceiveOptionViewModelBase with Store {
19
? ReceivePageOption.testnet
20
: _wallet.type == WalletType.zcash
21
? zcash!.getSelectedAddressType(_wallet)
21
- : ReceivePageOption.mainnet);
22
+ : ReceivePageOption.mainnet) {
23
+ if (_wallet.type == WalletType.zcash) {
24
+ reaction<bool>(
25
+ (_) => (_wallet.walletAddresses as ZcashWalletAddresses).ironwoodActive,
26
+ (_) => _syncZcashShieldedLabel(),
27
+ );
28
+ }
29
+ }
30
31
final WalletBase _wallet;
32
@@ -27,10 +35,21 @@ abstract class ReceiveOptionViewModelBase with Store {
35
@observable
36
ReceivePageOption selectedReceiveOption;
37
38
+ @computed
39
List<ReceivePageOption> get options => _wallet.walletAddresses.receivePageOptions;
40
41
String get walletTypeString => walletTypeToString(_wallet.type);
42
43
@action
44
void selectReceiveOption(ReceivePageOption option) => selectedReceiveOption = option;
45
+
46
+ @action
47
+ void _syncZcashShieldedLabel() {
48
+ for (final option in options) {
49
+ if (option.value == selectedReceiveOption.value) {
50
+ selectedReceiveOption = option;
51
+ return;
52
+ }
53
+ }
54
+ }
55
}
lib/view_model/dashboard/transaction_list_item.dart
+3
@@ -60,6 +60,9 @@ class TransactionListItem extends ActionListItem with Keyable {
60
return 'Transaction has missing data';
61
}
62
63
+ if (transaction.additionalInfo['isIronwoodMigration'] == true) {
64
+ return 'Migration';
65
+ }
66
if (transaction.additionalInfo['isAutoShield'] == true) {
67
if (transaction.isPending) {
68
final status = formattedStatus;
lib/view_model/send/send_template_view_model.dart
+5
-2
@@ -48,8 +48,11 @@ abstract class SendTemplateViewModelBase with Store {
48
amountParsingProxy: _appStore.amountParsingProxy,
49
);
50
51
- AddressValidator get addressValidator =>
52
- AddressValidator(type: _wallet.currency, isTestnet: _wallet.isTestnet);
51
+ AddressValidator get addressValidator => AddressValidator(
52
+ type: _wallet.currency,
53
+ isTestnet: _wallet.isTestnet,
54
+ network: _wallet.walletInfo.network,
55
+ );
56
57
TemplateValidator get templateValidator => TemplateValidator();
58
lib/view_model/send/send_view_model.dart
+9
-3
@@ -190,7 +190,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
190
191
// Check if the address is valid for the current currency (except for Zano, which can use handles as addresses)
192
if (selectedCryptoCurrency != CryptoCurrency.zano) {
193
- final isValidAddress = AddressValidator(type: selectedCryptoCurrency).isValid(query);
193
+ final isValidAddress = AddressValidator(
194
+ type: selectedCryptoCurrency,
195
+ network: wallet.walletInfo.network,
196
+ ).isValid(query);
197
if (isValidAddress) return null;
198
}
199
@@ -317,8 +320,11 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
320
321
Validator<String> get allAmountValidator => AllAmountValidator();
322
320
- Validator<String> get addressValidator =>
321
- AddressValidator(type: selectedCryptoCurrency, isTestnet: wallet.isTestnet);
323
+ Validator<String> get addressValidator => AddressValidator(
324
+ type: selectedCryptoCurrency,
325
+ isTestnet: wallet.isTestnet,
326
+ network: wallet.walletInfo.network,
327
+ );
328
329
Validator<String> get textValidator => TextValidator();
330
lib/view_model/transaction_details_view_model.dart
+3
@@ -428,6 +428,9 @@ abstract class TransactionDetailsViewModelBase with Store {
428
}
429
430
String get formattedTitle {
431
+ if (transactionInfo.additionalInfo['isIronwoodMigration'] == true) {
432
+ return 'Migration';
433
+ }
434
if (transactionInfo.additionalInfo['isAutoShield'] == true) {
435
return S.current.shielding;
436
}
lib/view_model/wallet_creation_vm.dart
+9
@@ -17,6 +17,7 @@ import 'package:cw_core/wallet_base.dart';
17
import 'package:cw_core/wallet_credentials.dart';
18
import 'package:cw_core/wallet_info.dart';
19
import 'package:cw_core/wallet_type.dart';
20
+import 'package:cake_wallet/zcash/zcash_network_type.dart';
21
import 'package:mobx/mobx.dart';
22
import 'package:polyseed/polyseed.dart';
23
@@ -33,9 +34,13 @@ abstract class WalletCreationVMBase with Store {
34
@observable
35
bool _useTestnet = false;
36
37
+ int _zcashNetwork = ZcashNetworkType.mainnet;
38
+
39
@computed
40
bool get useTestnet => _useTestnet;
41
42
+ int get zcashNetwork => _zcashNetwork;
43
+
44
@observable
45
String name;
46
@@ -252,4 +257,8 @@ abstract class WalletCreationVMBase with Store {
257
void toggleUseTestnet(bool? value) {
258
_useTestnet = value ?? !_useTestnet;
259
}
260
+
261
+ void setZcashNetwork(int network) {
262
+ _zcashNetwork = network;
263
+ }
264
}
lib/view_model/wallet_new_vm.dart
+1
@@ -146,6 +146,7 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
146
password: walletPassword,
147
mnemonic: newWalletArguments!.mnemonic,
148
passphrase: passphrase,
149
+ network: zcashNetwork,
150
);
151
case WalletType.decred:
152
return decred!.createDecredNewWalletCredentials(name: name);
lib/view_model/wallet_restore_view_model.dart
+1
@@ -224,6 +224,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
224
password: password,
225
passphrase: passphrase,
226
height: height,
227
+ network: zcashNetwork,
228
);
229
case WalletType.none:
230
case WalletType.haven:
lib/zcash/cw_zcash.dart
+13
-18
@@ -7,12 +7,14 @@ class CWZcash extends Zcash {
7
WalletInfo? walletInfo,
8
String? password,
9
String? mnemonic,
10
- required String? passphrase}) {
10
+ required String? passphrase,
11
+ int network = 0}) {
12
return ZcashNewWalletCredentials(
13
name: name,
14
passphrase: passphrase,
15
password: password,
16
mnemonic: mnemonic,
17
+ network: network,
18
);
19
}
20
@@ -36,9 +38,10 @@ class CWZcash extends Zcash {
38
required String mnemonic,
39
required String password,
40
String? passphrase,
39
- required int? height}) {
41
+ required int? height,
42
+ int network = 0}) {
43
return ZcashFromSeedWalletCredentials(
41
- name: name, seed: mnemonic, passphrase: passphrase, password: password, height: height);
44
+ name: name, seed: mnemonic, passphrase: passphrase, password: password, height: height, network: network);
45
}
46
47
@override
@@ -161,9 +164,12 @@ class CWZcash extends Zcash {
164
@override
165
ReceivePageOption getSelectedAddressType(Object wallet) {
166
final zcashWallet = wallet as ZcashWallet;
164
- final t =
165
- (zcashWallet.walletAddresses as ZcashWalletAddresses).walletInfo.addressPageType ?? "";
166
- return ZcashReceivePageOption.fromType(ZcashReceivePageOption.typeFromString(t));
167
+ final addresses = zcashWallet.walletAddresses as ZcashWalletAddresses;
168
+ final type = ZcashReceivePageOption.typeFromString(addresses.walletInfo.addressPageType ?? "");
169
+ if (type == ZcashAddressType.shieldedOrchard) {
170
+ return ZcashReceivePageOption.shieldedOrchard(ironwood: addresses.ironwoodActive);
171
+ }
172
+ return ZcashReceivePageOption.fromType(type);
173
}
174
175
bool hasSelectedTransparentAddress(Object wallet) {
@@ -172,18 +178,7 @@ class CWZcash extends Zcash {
178
179
@override
180
dynamic getZcashAddressType(ReceivePageOption option) {
175
- switch (option) {
176
- case ZcashReceivePageOption.unified:
177
- return ZcashAddressType.unifiedType;
178
- case ZcashReceivePageOption.transparent:
179
- return ZcashAddressType.transparent;
180
- case ZcashReceivePageOption.shieldedSapling:
181
- return ZcashAddressType.shieldedSapling;
182
- case ZcashReceivePageOption.shieldedOrchard:
183
- return ZcashAddressType.shieldedOrchard;
184
- default:
185
- throw Exception("Unknown ReceivePageOption!");
186
- }
181
+ return (option as ZcashReceivePageOption).toType();
182
}
183
184
@override
lib/zcash/zcash_network_type.dart
new
+17
@@ -0,0 +1,17 @@
1
+/// Zcash network indices passed from lib/ into cw_zcash.
2
+abstract final class ZcashNetworkType {
3
+ static const int mainnet = 0;
4
+ static const int testnet = 1;
5
+ static const int regtest = 2;
6
+
7
+ static const List<int> values = [mainnet, testnet, regtest];
8
+
9
+ static String label(final int network) => switch (network) {
10
+ testnet => 'Testnet',
11
+ regtest => 'Regtest',
12
+ _ => 'Mainnet',
13
+ };
14
+
15
+ static bool isDevNetwork(final String? network) =>
16
+ network?.toLowerCase() == 'testnet' || network?.toLowerCase() == 'regtest';
17
+}
tool/configure.dart
+4
-2
@@ -1721,13 +1721,15 @@ abstract class Zcash {
1721
WalletInfo? walletInfo,
1722
String? password,
1723
String? mnemonic,
1724
- required String? passphrase});
1724
+ required String? passphrase,
1725
+ int network = 0});
1726
WalletCredentials createZcashRestoreWalletFromSeedCredentials(
1727
{required String name,
1728
required String mnemonic,
1729
required String password,
1730
String? passphrase,
1730
- required int? height});
1731
+ required int? height,
1732
+ int network = 0});
1733
WalletCredentials createZcashRestoreWalletFromPrivateKey(
1734
{required String name, required String privateKey, required String password, required int height});
1735
String getAddress(WalletBase wallet);