What changed, and why it matters
This commit adds support for Zcash's Ironwood network upgrade to Cake Wallet. It changes how wallet balances, transactions, and addresses are handled when Ironwood activates, and it lets advanced users pick mainnet, testnet, or regtest for Zcash. The changes are mostly functional upgrades and bug fixes rather than a clear security patch, but they touch sensitive areas like balance calculation, transaction construction, and address validation. Because the commit is large and partially hardcodes dev-network settings (for example, a regtest node at 10.0.2.2), there is some risk of misconfiguration or incorrect balance reporting, though no direct exploit is visible in the diff.
Treat this as a significant feature upgrade, not a confirmed security fix. Review the hardcoded regtest endpoint and disabled address validation for dev networks to ensure they cannot be triggered in production builds. Audit the new balance and migration logic against zkool2's Rust behavior to confirm funds cannot be incorrectly classified or locked. Verify the zkool2 dependency bump does not introduce breaking or unsafe behavior. Run integration tests on mainnet, testnet, and regtest Zcash wallets for balance accuracy, send/receive, and migration display.
Security signals we found
Hardcoded regtest lightwalletd endpoint 10.0.2.2:9067 with no TLS (ZcashNetwork.defaultNodeUri/useSsl)
Address regex validation disabled for Zcash dev networks (AddressValidator returns null pattern)
Balance/availability logic changed based on ironwoodActive flag, with Orchard funds marked unavailable after Ironwood activation
New migration path invokes zkool_migrate.stepMigration() and adjusts transaction display/direction for migration transactions
Mempool tx removal and pending-outgoing amount tracking modified, affecting what transactions/balances are shown
Dependency zkool2 git ref bumped; full Rust-side behavior not visible in this diff
iOS deployment target raised and Google Sign-In dependencies removed (build/privacy surface change)
Evidence from the diff
The patch upgrades the Zcash wallet layer (cw_zcash) for NU6.3/Ironwood. Key changes: adds ZcashNetwork enum and persists network in WalletInfo; initializes the Rust zkool library per-network and uses separate DB files; adds ironwoodActive state, receive-page label updates, and Orchard→Ironwood migration logic via zkool_migrate.stepMigration(); adjusts balance math so Orchard notes become unavailable once Ironwood is active; updates payment preparation to use srcPools=15 and an ironwood pool mask when Ironwood is active; fixes mempool handling to track block heights and remove known txs; adds pending-outgoing amount tracking to improve displayed amounts; hardcodes regtest defaults including 10.0.2.2:9067 and birth height 1; disables Zcash address regex validation for dev networks; and bumps the zkool2 dependency. iOS deployment target is raised to 15.0 and Google Sign-In pods are removed. No explicit security bug or CVE is described in the commit message.
Changed components
cw_zcash Zcash wallet implementationWalletInfo serialization/network fieldZcash address validation (lib/core/address_validator.dart)Zcash receive page and address type selectionZcash send flow and transaction preparationBalance refresh and Orchard→Ironwood migrationMempool serviceiOS build configuration and CocoaPods dependencieszkool2 Rust dependencyInspect captured patch +1067 / −206
diff --git a/cw_core/lib/wallet_info.dart b/cw_core/lib/wallet_info.dart
index aa4b258..585ce3b 100644
--- a/cw_core/lib/wallet_info.dart
+++ b/cw_core/lib/wallet_info.dart
@@ -586,11 +586,12 @@ class WalletInfo {
"addressPageType": addressPageType,
"receiveInfoboxDismissed": receiveInfoboxDismissed ? 1 : 0,
"showCombinedBalance": showCombinedBalance ? 1 : 0,
- "favoriteTokenAddress": favoriteTokenAddress
+ "favoriteTokenAddress": favoriteTokenAddress,
+ "network": network,
};
factory WalletInfo.fromJson(Map<String, dynamic> json) {
- return WalletInfo(
+ final info = WalletInfo(
json[selfIdColumn] as int,
json['id'] as String,
json['name'] as String,
@@ -616,6 +617,8 @@ class WalletInfo {
json['receiveInfoboxDismissed'] != 0,
json["showCombinedBalance"] != 0,
json["favoriteTokenAddress"] as String? ?? null);
+ info.network = json['network'] as String?;
+ return info;
}
Future<int> save() async {
diff --git a/cw_core/lib/wallet_info_legacy.dart b/cw_core/lib/wallet_info_legacy.dart
index 35a710e..2dadbf2 100644
--- a/cw_core/lib/wallet_info_legacy.dart
+++ b/cw_core/lib/wallet_info_legacy.dart
@@ -274,7 +274,8 @@ class WalletInfo extends HiveObject {
addressPageType,
false,
true,
- null);
+ null)
+ ..network = network;
final wiId = await walletInfo.save();
for (final address in usedAddresses ?? <String>[]) {
await newWi.WalletInfoAddress.insert(wiId, newWi.WalletInfoAddressType.used, address);
diff --git a/cw_custom_lints/pubspec.lock b/cw_custom_lints/pubspec.lock
new file mode 100644
index 0000000..cd831e4
--- /dev/null
+++ b/cw_custom_lints/pubspec.lock
@@ -0,0 +1,429 @@
+# Generated by pub
+# See https://dart.dev/tools/pub/glossary#lockfile
+packages:
+ _fe_analyzer_shared:
+ dependency: transitive
+ description:
+ name: _fe_analyzer_shared
+ sha256: cd6add6f846f35fb79f3c315296703c1a24f3cfd7f4739d91a74961c1c7e9f1b
+ url: "https://pub.dev"
+ source: hosted
+ version: "100.0.0"
+ analysis_server_plugin:
+ dependency: "direct main"
+ description:
+ name: analysis_server_plugin
+ sha256: c4021359e7a673ecd9c6db5f11fea12fabb55e0ba96fa08c78920ae81936f1d1
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.3.15"
+ analyzer:
+ dependency: "direct main"
+ description:
+ name: analyzer
+ sha256: "6ba98576948803398b69e3a444df24eacdbe12ed699c7014e120ea38552debbf"
+ url: "https://pub.dev"
+ source: hosted
+ version: "13.0.0"
+ analyzer_plugin:
+ dependency: "direct main"
+ description:
+ name: analyzer_plugin
+ sha256: "9a81dc36f7f71890d7cf83960a3a7df1e6c9a24035f98eb848e91ad48b1d21c6"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.14.9"
+ analyzer_testing:
+ dependency: "direct dev"
+ description:
+ name: analyzer_testing
+ sha256: da27ae9bd4bf3ce4ebab6295cf922ca21a2b14ea8fcacc859342aca796a48a2d
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.2.6"
+ args:
+ dependency: transitive
+ description:
+ name: args
+ sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.7.0"
+ async:
+ dependency: transitive
+ description:
+ name: async
+ sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.13.1"
+ boolean_selector:
+ dependency: transitive
+ description:
+ name: boolean_selector
+ sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.2"
+ cli_config:
+ dependency: transitive
+ description:
+ name: cli_config
+ sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.2.0"
+ collection:
+ dependency: transitive
+ description:
+ name: collection
+ sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.19.1"
+ convert:
+ dependency: transitive
+ description:
+ name: convert
+ sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.2"
+ coverage:
+ dependency: transitive
+ description:
+ name: coverage
+ sha256: "956a3de0725ca232ad353565a8290d3357592bf4250f6f298a185e2d949c5d3d"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.15.1"
+ crypto:
+ dependency: transitive
+ description:
+ name: crypto
+ sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.7"
+ dart_style:
+ dependency: transitive
+ description:
+ name: dart_style
+ sha256: "59d53ef8eaed9d288ed9767618e2b31c4fa0383a127db59d5eb2e737a7638a60"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.9"
+ file:
+ dependency: transitive
+ description:
+ name: file
+ sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
+ url: "https://pub.dev"
+ source: hosted
+ version: "7.0.1"
+ frontend_server_client:
+ dependency: transitive
+ description:
+ name: frontend_server_client
+ sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.0.0"
+ glob:
+ dependency: transitive
+ description:
+ name: glob
+ sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.3"
+ http_multi_server:
+ dependency: transitive
+ description:
+ name: http_multi_server
+ sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.2.2"
+ http_parser:
+ dependency: transitive
+ description:
+ name: http_parser
+ sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.1.2"
+ io:
+ dependency: transitive
+ description:
+ name: io
+ sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.5"
+ logging:
+ dependency: transitive
+ description:
+ name: logging
+ sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.0"
+ matcher:
+ dependency: transitive
+ description:
+ name: matcher
+ sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.12.20"
+ meta:
+ dependency: transitive
+ description:
+ name: meta
+ sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.19.0"
+ mime:
+ dependency: transitive
+ description:
+ name: mime
+ sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.0"
+ node_preamble:
+ dependency: transitive
+ description:
+ name: node_preamble
+ sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.2"
+ package_config:
+ dependency: transitive
+ description:
+ name: package_config
+ sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.0"
+ path:
+ dependency: transitive
+ description:
+ name: path
+ sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.9.1"
+ pool:
+ dependency: transitive
+ description:
+ name: pool
+ sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.5.2"
+ pub_semver:
+ dependency: transitive
+ description:
+ name: pub_semver
+ sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.0"
+ shelf:
+ dependency: transitive
+ description:
+ name: shelf
+ sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.4.2"
+ shelf_packages_handler:
+ dependency: transitive
+ description:
+ name: shelf_packages_handler
+ sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.2"
+ shelf_static:
+ dependency: transitive
+ description:
+ name: shelf_static
+ sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.3"
+ shelf_web_socket:
+ dependency: transitive
+ description:
+ name: shelf_web_socket
+ sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.0"
+ source_map_stack_trace:
+ dependency: transitive
+ description:
+ name: source_map_stack_trace
+ sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.2"
+ source_maps:
+ dependency: transitive
+ description:
+ name: source_maps
+ sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.10.13"
+ source_span:
+ dependency: transitive
+ description:
+ name: source_span
+ sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.10.2"
+ stack_trace:
+ dependency: transitive
+ description:
+ name: stack_trace
+ sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.12.1"
+ stream_channel:
+ dependency: transitive
+ description:
+ name: stream_channel
+ sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.4"
+ string_scanner:
+ dependency: transitive
+ description:
+ name: string_scanner
+ sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.4.1"
+ term_glyph:
+ dependency: transitive
+ description:
+ name: term_glyph
+ sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.2"
+ test:
+ dependency: "direct dev"
+ description:
+ name: test
+ sha256: "0d5ba5602ec3baa28c8ce365e1efc5575969c765f45c554a3e167dc7945b9c30"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.31.2"
+ test_api:
+ dependency: transitive
+ description:
+ name: test_api
+ sha256: "475610b2aa23c19687cce2961e44b0cc57cafe220f67c2b80201231b2a07fbe7"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.7.13"
+ test_core:
+ dependency: transitive
+ description:
+ name: test_core
+ sha256: a39c204a4fc7a7ccb04a2b985e359fda3cc37e45e0b8ac61c3fb1a05aa832132
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.6.19"
+ test_reflective_loader:
+ dependency: "direct dev"
+ description:
+ name: test_reflective_loader
+ sha256: d828d5ca15179aaac2aaf8f510cf0a52ec28e0031681b044ec5e581a4b8002e7
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.4.0"
+ typed_data:
+ dependency: transitive
+ description:
+ name: typed_data
+ sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.4.0"
+ vm_service:
+ dependency: transitive
+ description:
+ name: vm_service
+ sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360"
+ url: "https://pub.dev"
+ source: hosted
+ version: "15.2.0"
+ watcher:
+ dependency: transitive
+ description:
+ name: watcher
+ sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.1"
+ web:
+ dependency: transitive
+ description:
+ name: web
+ sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.1"
+ web_socket:
+ dependency: transitive
+ description:
+ name: web_socket
+ sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.1"
+ web_socket_channel:
+ dependency: transitive
+ description:
+ name: web_socket_channel
+ sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.0.3"
+ webkit_inspection_protocol:
+ dependency: transitive
+ description:
+ name: webkit_inspection_protocol
+ sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.1"
+ yaml:
+ dependency: transitive
+ description:
+ name: yaml
+ sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.3"
+ yaml_edit:
+ dependency: transitive
+ description:
+ name: yaml_edit
+ sha256: "07c9e63ba42519745182b88ca12264a7ba2484d8239958778dfe4d44fe760488"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.4"
+sdks:
+ dart: ">=3.11.0 <4.0.0"
diff --git a/cw_zcash/lib/cw_zcash.dart b/cw_zcash/lib/cw_zcash.dart
index 92af285..6df1d9b 100644
--- a/cw_zcash/lib/cw_zcash.dart
+++ b/cw_zcash/lib/cw_zcash.dart
@@ -11,4 +11,6 @@ export 'src/zcash_wallet_service.dart';
export 'src/models.dart';
export 'src/zcash_transaction_credentials.dart';
export 'src/pending_zcash_transaction.dart';
+export 'src/zcash_network.dart';
export 'src/zcash_receive_page_options.dart';
+export 'src/zcash_wallet_addresses.dart';
diff --git a/cw_zcash/lib/src/models.dart b/cw_zcash/lib/src/models.dart
index e6886b1..d6bae3c 100644
--- a/cw_zcash/lib/src/models.dart
+++ b/cw_zcash/lib/src/models.dart
@@ -7,6 +7,7 @@ class ZcashNewWalletCredentials extends WalletCredentials {
required final String? passphrase,
final String? mnemonic,
final int? seedPhraseLength,
+ this.network = 0,
}) : super(
name: name,
password: password,
@@ -17,6 +18,7 @@ class ZcashNewWalletCredentials extends WalletCredentials {
}
String? mnemonic;
+ int network;
}
class ZcashFromSeedWalletCredentials extends WalletCredentials {
@@ -26,8 +28,10 @@ class ZcashFromSeedWalletCredentials extends WalletCredentials {
required final String? passphrase,
required this.seed,
required super.height,
+ this.network = 0,
}) : super(name: name, password: password, passphrase: passphrase);
final String? seed;
+ int network;
}
class ZcashFromKeysWalletCredentials extends WalletCredentials {
@@ -36,6 +40,8 @@ class ZcashFromKeysWalletCredentials extends WalletCredentials {
final String? password,
required final int? height,
required this.privateKey,
+ this.network = 0,
}) : super(name: name, password: password, height: height);
final String? privateKey;
+ int network;
}
diff --git a/cw_zcash/lib/src/pending_zcash_transaction.dart b/cw_zcash/lib/src/pending_zcash_transaction.dart
index 3f6762d..3aba240 100644
--- a/cw_zcash/lib/src/pending_zcash_transaction.dart
+++ b/cw_zcash/lib/src/pending_zcash_transaction.dart
@@ -63,6 +63,7 @@ class PendingZcashTransaction with PendingTransaction {
throw TransactionCommitFailed(errorMessage: result);
}
_txId = txId;
+ zcashWallet.rememberPendingOutgoingAmount(txId, amount);
},
);
await zcashWallet.updateTransactions();
diff --git a/cw_zcash/lib/src/zcash_mempool.dart b/cw_zcash/lib/src/zcash_mempool.dart
index 33ba40f..97bdf87 100644
--- a/cw_zcash/lib/src/zcash_mempool.dart
+++ b/cw_zcash/lib/src/zcash_mempool.dart
@@ -21,8 +21,19 @@ class ZcashMempoolService {
bool _loopRunning = false;
String? _nodeUrl;
+ int? _lastBlockHeight;
StreamSubscription<zkool_mempool.MempoolMsg>? _subscription;
+ void removeTx(final String txId) {
+ _txsById.remove(ZcashWalletService.normalizeTxId(txId));
+ }
+
+ void removeKnownTxs(final Iterable<String> txIds) {
+ for (final txId in txIds) {
+ removeTx(txId);
+ }
+ }
+
List<zkool_mempool.MempoolTx> txsForAccount(final int accountId) => _txsById.values
.where(
(final tx) =>
@@ -49,6 +60,7 @@ class ZcashMempoolService {
await _subscription?.cancel();
_subscription = null;
_txsById.clear();
+ _lastBlockHeight = null;
try {
await _mempool?.cancel();
} catch (e) {
@@ -69,8 +81,8 @@ class ZcashMempoolService {
return;
}
switch (msg) {
- case zkool_mempool.MempoolMsg_BlockHeight():
- _onBlockMined();
+ case zkool_mempool.MempoolMsg_BlockHeight(:final field0):
+ _onBlockHeight(field0);
case zkool_mempool.MempoolMsg_TxId(:final field0):
_onMempoolTx(field0);
}
@@ -97,7 +109,12 @@ class ZcashMempoolService {
}
}
- void _onBlockMined() {
+ void _onBlockHeight(final int height) {
+ final heightChanged = _lastBlockHeight != null && height > _lastBlockHeight!;
+ _lastBlockHeight = height;
+ if (!heightChanged) {
+ return;
+ }
final affected = _allAffectedAccounts();
_txsById.clear();
onAccountsUpdated?.call(affected);
diff --git a/cw_zcash/lib/src/zcash_network.dart b/cw_zcash/lib/src/zcash_network.dart
new file mode 100644
index 0000000..d692544
--- /dev/null
+++ b/cw_zcash/lib/src/zcash_network.dart
@@ -0,0 +1,49 @@
+enum ZcashNetwork {
+ mainnet,
+ testnet,
+ regtest;
+
+ /// NU6.3 / Ironwood activation height on zkool regtest (see zkool2/misc/zebra.toml).
+ static const int regtestNu63Height = 250;
+
+ String get value => name;
+
+ String get label => switch (this) {
+ ZcashNetwork.mainnet => 'Mainnet',
+ ZcashNetwork.testnet => 'Testnet',
+ ZcashNetwork.regtest => 'Regtest',
+ };
+
+ String get dbFileName => switch (this) {
+ ZcashNetwork.mainnet => 'zec.v2.db',
+ ZcashNetwork.testnet => 'zec.testnet.v2.db',
+ ZcashNetwork.regtest => 'zec.regtest.v2.db',
+ };
+
+ /// Default lightwalletd endpoint for dev networks.
+ String get defaultNodeUri => switch (this) {
+ ZcashNetwork.mainnet => 'zec.rocks:443',
+ ZcashNetwork.testnet => 'testnet.lightwalletd.com:9067',
+ ZcashNetwork.regtest => '10.0.2.2:9067', // 10.0.2.2 - AVD host bridge
+ };
+
+ bool get useSsl => this == ZcashNetwork.mainnet;
+
+ static ZcashNetwork fromName(final String? name) => switch (name) {
+ 'testnet' => ZcashNetwork.testnet,
+ 'regtest' => ZcashNetwork.regtest,
+ _ => ZcashNetwork.mainnet,
+ };
+
+ static ZcashNetwork fromIndex(final int index) => switch (index) {
+ 1 => ZcashNetwork.testnet,
+ 2 => ZcashNetwork.regtest,
+ _ => ZcashNetwork.mainnet,
+ };
+
+ int get networkIndex => switch (this) {
+ ZcashNetwork.testnet => 1,
+ ZcashNetwork.regtest => 2,
+ ZcashNetwork.mainnet => 0,
+ };
+}
diff --git a/cw_zcash/lib/src/zcash_receive_page_options.dart b/cw_zcash/lib/src/zcash_receive_page_options.dart
index 8a9d057..31c03ca 100644
--- a/cw_zcash/lib/src/zcash_receive_page_options.dart
+++ b/cw_zcash/lib/src/zcash_receive_page_options.dart
@@ -19,7 +19,7 @@ class ZcashReceivePageOption implements ReceivePageOption {
case ZcashAddressType.shieldedSapling:
return shieldedSapling;
case ZcashAddressType.shieldedOrchard:
- return shieldedOrchard;
+ return shieldedOrchard();
case ZcashAddressType.unifiedType:
return unified;
}
@@ -57,13 +57,18 @@ class ZcashReceivePageOption implements ReceivePageOption {
description: "Sapling",
iconPath: "assets/new-ui/address-type-picker-icons/zec/sapling.svg",
);
- static const shieldedOrchard = ZcashReceivePageOption._(
- ZcashAddressType.shieldedOrchard,
- "Shielded",
- description: "Default (Orchard)",
- iconPath: "assets/new-ui/address-type-picker-icons/zec/shielded.svg",
- isCommon: true,
- );
+ static const _shieldedOrchardIcon =
+ "assets/new-ui/address-type-picker-icons/zec/shielded.svg";
+
+ static ZcashReceivePageOption shieldedOrchard({final bool ironwood = false}) {
+ return ZcashReceivePageOption._(
+ ZcashAddressType.shieldedOrchard,
+ "Shielded",
+ description: ironwood ? "Default (Ironwood)" : "Default (Orchard)",
+ iconPath: _shieldedOrchardIcon,
+ isCommon: true,
+ );
+ }
static const unified = ZcashReceivePageOption._(
ZcashAddressType.unifiedType,
"Unified",
@@ -78,13 +83,15 @@ class ZcashReceivePageOption implements ReceivePageOption {
return value;
}
- static const all = [
- ZcashReceivePageOption.shieldedOrchard,
- ZcashReceivePageOption.shieldedSapling,
- ZcashReceivePageOption.unified,
- ZcashReceivePageOption.transparentRotated,
- ZcashReceivePageOption.transparent,
- ];
+ static List<ZcashReceivePageOption> allOptionsFor({final bool ironwood = false}) => [
+ shieldedOrchard(ironwood: ironwood),
+ shieldedSapling,
+ unified,
+ transparentRotated,
+ transparent,
+ ];
+
+ static List<ZcashReceivePageOption> get allOptions => allOptionsFor();
ZcashAddressType toType() {
return type;
diff --git a/cw_zcash/lib/src/zcash_taddress_rotation.dart b/cw_zcash/lib/src/zcash_taddress_rotation.dart
index 8e89403..cbbb0b0 100644
--- a/cw_zcash/lib/src/zcash_taddress_rotation.dart
+++ b/cw_zcash/lib/src/zcash_taddress_rotation.dart
@@ -34,6 +34,7 @@ class ZcashTaddressRotation {
static bool _isStarted = false;
static zkool_coin.Coin get c => ZcashWalletBase.c;
static const int _sweepThreshold = 30000;
+ static const int _minSpendableNote = 5000;
static const int _lookahead = 5;
// Matches transparentLimit in ZcashWalletBase._oneshotSync.
static const int _transparentSyncLimit = 100;
@@ -407,18 +408,42 @@ class ZcashTaddressRotation {
final result = await ZcashWalletBase.runWithCoin(
accountId: rotationAccount,
func: (final coin) async {
+ final height = await zkool_network.getCurrentHeight(c: coin);
+ final notes = await zkool_account.listNotes(c: coin);
+ var amount = BigInt.zero;
+ for (final note in notes) {
+ if (note.pool != NotePool.transparent.index || note.locked) {
+ continue;
+ }
+ if (note.value < BigInt.from(_minSpendableNote)) {
+ continue;
+ }
+ if (note.height > height) {
+ continue;
+ }
+ amount += note.value;
+ }
+ if (amount < BigInt.from(_sweepThreshold)) {
+ throw Exception('rotation sweep: insufficient spendable transparent notes');
+ }
+ final ironwood = await zkool_network.isIronwoodActive(c: coin);
final tx = await zkool_pay.prepare(
recipients: [
- zkool_paydart.Recipient(assetBase: zecBase, address: toAddress, amount: transparentBal),
+ zkool_paydart.Recipient(
+ assetBase: zecBase,
+ address: toAddress,
+ amount: amount,
+ pools: ironwood ? ironwoodPoolMask : null,
+ ),
],
options: zkool_pay.PaymentOptions(
srcPools: 1,
recipientPaysFee: true,
smartTransparent: false,
+ mode: 0,
),
c: coin,
);
- final height = await zkool_network.getCurrentHeight(c: coin);
final signTx = await zkool_pay.signTransaction(pczt: tx, c: coin);
final txBytes = await zkool_pay.extractTransaction(package: signTx);
return zkool_pay.broadcastTransaction(height: height, txBytes: txBytes, c: coin);
diff --git a/cw_zcash/lib/src/zcash_transaction_info.dart b/cw_zcash/lib/src/zcash_transaction_info.dart
index 9dd09bd..4649411 100644
--- a/cw_zcash/lib/src/zcash_transaction_info.dart
+++ b/cw_zcash/lib/src/zcash_transaction_info.dart
@@ -20,6 +20,7 @@ class ZcashTransactionInfo extends TransactionInfo {
final TxType? txType,
final bool isRotationReceive = false,
final bool isShieldAction = false,
+ final bool isIronwoodMigration = false,
}) {
this.id = id;
this.amount = amount;
@@ -38,6 +39,7 @@ class ZcashTransactionInfo extends TransactionInfo {
}
additionalInfo['isRotationReceive'] = isRotationReceive;
additionalInfo['isAutoShield'] = isShieldAction || ZcashWalletService.isAutoshieldTx(txHash);
+ additionalInfo['isIronwoodMigration'] = isIronwoodMigration;
if (additionalInfo['isAutoShield'] == true) {
additionalInfo['memo'] ??= '';
diff --git a/cw_zcash/lib/src/zcash_wallet.dart b/cw_zcash/lib/src/zcash_wallet.dart
index e1258e5..cc6df83 100644
--- a/cw_zcash/lib/src/zcash_wallet.dart
+++ b/cw_zcash/lib/src/zcash_wallet.dart
@@ -23,6 +23,7 @@ import 'package:cw_zcash/src/util/crc32.dart';
import 'package:cw_zcash/src/zcash_mempool.dart';
import 'package:cw_zcash/src/zcash_taddress_rotation.dart';
import 'package:cw_zcash/src/zcash_wallet_addresses.dart';
+import 'package:cw_zcash/src/zcash_network.dart';
import 'package:cw_zcash/src/zkool_compat.dart';
import 'package:cw_zcash/src/zkooltx.dart';
import 'package:mobx/mobx.dart';
@@ -32,6 +33,7 @@ import 'package:zkool/src/rust/api/coin.dart' as zkool_coin;
import 'package:zkool/src/rust/api/mempool.dart' as zkool_mempool;
import 'package:zkool/src/rust/api/sync.dart' as zkool_sync;
import 'package:zkool/src/rust/api/pay.dart' as zkool_pay;
+import 'package:zkool/src/rust/api/migrate.dart' as zkool_migrate;
import 'package:zkool/src/rust/api/network.dart' as zkool_network;
import 'package:zkool/src/rust/pay.dart' as zkool_paydart;
import 'package:zkool/src/rust/frb_generated.dart' as zkool_frb;
@@ -61,6 +63,15 @@ abstract class ZcashWalletBase
int accountId;
+ final Map<String, BigInt> _pendingOutgoingAmounts = {};
+
+ void rememberPendingOutgoingAmount(final String txId, final Money amount) {
+ if (amount.isZero) {
+ return;
+ }
+ _pendingOutgoingAmounts[ZcashWalletService.normalizeTxId(txId)] = amount.amount;
+ }
+
@override
@observable
SyncStatus syncStatus = NotConnectedSyncStatus();
@@ -147,6 +158,7 @@ abstract class ZcashWalletBase
c = c.setLwd(url: lwdUrl, serverType: 0);
syncStatus = ConnectedSyncStatus();
unawaited(ZcashMempoolService.instance.ensureRunning(c));
+ unawaited(_updateIronwoodActive());
_ensureSyncLoopRunning();
unawaited(_refreshSyncStatus());
unawaited(_oneshotSync());
@@ -463,12 +475,14 @@ abstract class ZcashWalletBase
return await runWithCoin(
accountId: accountId,
func: (coin) async {
+ final ironwood = await zkool_network.isIronwoodActive(c: coin);
final txPlan = await zkool_pay.prepare(
recipients: recipients,
options: zkool_pay.PaymentOptions(
- srcPools: 7,
+ srcPools: ironwood ? 15 : 7,
recipientPaysFee: receipientPaysFee,
smartTransparent: false,
+ mode: 0,
),
c: coin,
);
@@ -502,18 +516,57 @@ abstract class ZcashWalletBase
static const _dispPhrase = "Received to disposable address";
+ bool _hasExternalOutputs(final ZkoolTx tx) =>
+ tx.outputsWithAddress.any((final o) => !_addressBelongsToWallet(o.address));
+
+ bool _isIronwoodMigrationTx(final ZkoolTx tx) {
+ // zkool classifies migration as selfTransfer with fee-sized net value.
+ if (tx.type != TxType.selfTransfer) {
+ return false;
+ }
+ final orchardSpent = tx.orchardSpent;
+ final spentFromOrchard = orchardSpent > BigInt.zero ||
+ tx.spendPools.contains(NotePool.orchard.index);
+ if (!spentFromOrchard) {
+ return false;
+ }
+ if (tx.ironwoodReceived > BigInt.zero ||
+ tx.notePools.contains(NotePool.ironwood.index)) {
+ return true;
+ }
+ // Orchard → Orchard split step (SD notes created, all outputs are ours).
+ if (tx.orchardReceived > BigInt.zero && !_hasExternalOutputs(tx)) {
+ return true;
+ }
+ // Orchard → Ironwood before the IW note is attached to tx details.
+ return orchardSpent > tx.value;
+ }
+
+ BigInt _migrationDisplayAmount(final ZkoolTx tx) {
+ if (tx.ironwoodReceived > BigInt.zero) {
+ return tx.ironwoodReceived;
+ }
+ if (tx.orchardReceived > BigInt.zero) {
+ return tx.orchardReceived;
+ }
+ return tx.orchardSpent;
+ }
+
ZcashTransactionInfo _zcashInfoFromMempoolTx(
final zkool_mempool.MempoolTx tx,
final int accountId,
) {
final accountNotes = tx.notes.where((final n) => n.account == accountId);
+ final txHash = ZcashWalletService.normalizeTxId(tx.txid);
+ final pendingAmount = _pendingOutgoingAmounts[txHash];
final netValue = accountNotes.fold<BigInt>(
BigInt.zero,
(final sum, final note) => sum + BigInt.from(note.value),
);
- final direction = netValue >= BigInt.zero
- ? TransactionDirection.incoming
- : TransactionDirection.outgoing;
+ final direction = pendingAmount != null || netValue < BigInt.zero
+ ? TransactionDirection.outgoing
+ : TransactionDirection.incoming;
+ final displayAmount = pendingAmount ?? netValue.abs();
final memo = accountNotes
.map((final n) => n.memo)
.whereType<String>()
@@ -525,8 +578,8 @@ abstract class ZcashWalletBase
);
final info = ZcashTransactionInfo(
- id: ZcashWalletService.normalizeTxId(tx.txid),
- amount: Money(netValue.abs(), currency),
+ id: txHash,
+ amount: Money(displayAmount, currency),
fee: Money.zero(currency),
direction: direction,
isPending: true,
@@ -555,11 +608,15 @@ abstract class ZcashWalletBase
? currentHeight - tx.height + 1
: 0;
final memo = extraMemo != null ? "${tx.memo ?? ''}\n$extraMemo".trim() : tx.memo;
- final direction = directionOverride ?? tx.direction;
+ final isMigration = directionOverride == null && _isIronwoodMigrationTx(tx);
+ final direction = directionOverride ??
+ (isMigration ? TransactionDirection.outgoing : tx.direction);
final recipientAddresses = _recipientAddresses(_paymentOutputAddresses(tx), direction);
+ final amount = amountOverride ??
+ (isMigration ? _migrationDisplayAmount(tx) : tx.value);
final info = ZcashTransactionInfo(
id: tx.txHash,
- amount: Money(amountOverride ?? tx.value, currency),
+ amount: Money(amount, currency),
fee: Money.zero(currency),
direction: direction,
isPending: tx.height == 0,
@@ -571,6 +628,7 @@ abstract class ZcashWalletBase
txType: tx.type,
isRotationReceive: isRotationReceive,
isShieldAction: isShieldAction,
+ isIronwoodMigration: isMigration,
);
if (recipientAddresses.isNotEmpty) {
info.outputAddresses = recipientAddresses;
@@ -627,6 +685,9 @@ abstract class ZcashWalletBase
'tx_$txHash$suffix';
static int _txDisplayPriority(final ZcashTransactionInfo info) {
+ if (info.additionalInfo['isIronwoodMigration'] == true) {
+ return 4;
+ }
if (info.additionalInfo['isAutoShield'] == true) {
return 3;
}
@@ -650,7 +711,8 @@ abstract class ZcashWalletBase
return;
}
if (infoPriority == existingPriority &&
- info.additionalInfo['isAutoShield'] == true &&
+ (info.additionalInfo['isAutoShield'] == true ||
+ info.additionalInfo['isIronwoodMigration'] == true) &&
info.direction == TransactionDirection.outgoing &&
existing.direction == TransactionDirection.incoming) {
byHash[hash] = info;
@@ -813,6 +875,10 @@ abstract class ZcashWalletBase
final Map<String, ZcashTransactionInfo> splitEntries = {};
for (final tx in txs) {
+ _pendingOutgoingAmounts.remove(ZcashWalletService.normalizeTxId(tx.txHash));
+ if (tx.height > 0) {
+ ZcashMempoolService.instance.removeTx(tx.txHash);
+ }
final isShield = _isShieldActionTx(tx, rotationSweepHashes: rotationSweepHashes);
if (_shouldSplitAutoshieldTx(tx, isShield: isShield)) {
byHash.remove(tx.txHash);
@@ -834,10 +900,15 @@ abstract class ZcashWalletBase
_offerTx(byHash, _zcashInfoFromZkoolTx(tx, currentHeight, isShieldAction: isShield));
}
- final knownHashes = {for (final tx in txs) tx.txHash, ...byHash.keys};
+ final knownHashes = {
+ for (final tx in txs) ZcashWalletService.normalizeTxId(tx.txHash),
+ for (final hash in byHash.keys) ZcashWalletService.normalizeTxId(hash),
+ };
for (final mempoolTx in ZcashMempoolService.instance.txsForAccount(accountId)) {
final hash = ZcashWalletService.normalizeTxId(mempoolTx.txid);
if (knownHashes.contains(hash)) {
+ ZcashMempoolService.instance.removeTx(hash);
+ _pendingOutgoingAmounts.remove(hash);
continue;
}
final info = _zcashInfoFromMempoolTx(mempoolTx, accountId);
@@ -998,31 +1069,36 @@ abstract class ZcashWalletBase
}
bool _isTransactionUpdating = false;
+ bool _transactionUpdateQueued = false;
Future<void> updateTransactions() async {
- try {
- if (_isTransactionUpdating) {
- return;
- }
-
- _isTransactionUpdating = true;
- final transactions = await fetchTransactions();
-
- final currentIds = transactionHistory.transactions.keys.toSet();
- final newIds = transactions.keys.toSet();
-
- currentIds
- .difference(newIds)
- .forEach((final id) => transactionHistory.transactions.remove(id));
+ if (_isTransactionUpdating) {
+ _transactionUpdateQueued = true;
+ return;
+ }
- transactions.forEach((final key, final tx) {
- transactionHistory.transactions[key] = tx;
- });
- await transactionHistory.save();
- _isTransactionUpdating = false;
+ _isTransactionUpdating = true;
+ try {
+ do {
+ _transactionUpdateQueued = false;
+ final transactions = await fetchTransactions();
+
+ final currentIds = transactionHistory.transactions.keys.toSet();
+ final newIds = transactions.keys.toSet();
+
+ currentIds
+ .difference(newIds)
+ .forEach((final id) => transactionHistory.transactions.remove(id));
+
+ transactions.forEach((final key, final tx) {
+ transactionHistory.transactions[key] = tx;
+ });
+ await transactionHistory.save();
+ } while (_transactionUpdateQueued);
} catch (e, stackTrace) {
printV("Update transactions error: $e");
printV("Stack trace: $stackTrace");
+ } finally {
_isTransactionUpdating = false;
}
}
@@ -1081,6 +1157,8 @@ abstract class ZcashWalletBase
static final autoShieldMutex = Mutex();
static DateTime? _lastAutoShieldAt;
+ static final ironwoodMigrateMutex = Mutex();
+ static DateTime? _lastIronwoodMigrateAt;
Future<void> _autoShield() async {
if (_lastAutoShieldAt != null &&
_lastAutoShieldAt!.isAfter(DateTime.now().subtract(const Duration(seconds: 75)))) {
@@ -1105,35 +1183,37 @@ abstract class ZcashWalletBase
accountId: accountId,
func: (coin) async {
final _notes = await zkool_account.listNotes(c: coin);
- final List<zkool_account.TxNote> txNotes = [];
+ BigInt sweepable = BigInt.zero;
for (int i = 0; i < _notes.length; i++) {
final note = _notes[i];
+ if (note.pool < 0 || note.pool >= NotePool.values.length) {
+ continue;
+ }
final noteType = NotePool.values[note.pool];
- if ([NotePool.sapling, NotePool.transparent].contains(noteType)) {
- txNotes.add(note);
+ if (noteType == NotePool.transparent || noteType == NotePool.sapling) {
+ sweepable += note.value;
}
}
- final sweepable = txNotes.isEmpty
- ? BigInt.from(0)
- : txNotes.map((final txn) => txn.value).reduce((final a, final b) => a + b);
-
if (sweepable <= BigInt.from(_autoShieldMinSweep)) {
return null;
}
+ final ironwood = await zkool_network.isIronwoodActive(c: coin);
final txPlan = await zkool_pay.prepare(
recipients: [
zkool_paydart.Recipient(
assetBase: zecBase,
address: walletAddresses.orchardAddress!,
amount: sweepable,
+ pools: ironwood ? ironwoodPoolMask : null,
),
],
options: zkool_pay.PaymentOptions(
srcPools: 3,
recipientPaysFee: true,
smartTransparent: false,
+ mode: 0,
),
c: coin,
);
@@ -1156,34 +1236,134 @@ abstract class ZcashWalletBase
_lastAutoShieldAt = DateTime.now();
printV("shielded: $txId");
await updateTransactions();
- await _refreshBalance(runAutoShield: false);
+ await _refreshBalance(runAutoShield: false, runIronwoodMigrate: false);
}
- Future<void> _refreshBalance({required final bool runAutoShield}) async {
+ Future<void> _ironwoodMigrate() async {
+ if (_lastIronwoodMigrateAt != null &&
+ _lastIronwoodMigrateAt!.isAfter(DateTime.now().subtract(const Duration(seconds: 75)))) {
+ return;
+ }
try {
- final bal = await runWithCoin(
+ await ironwoodMigrateMutex.acquire();
+ await _$ironwoodMigrate();
+ } catch (e, s) {
+ printV("ironwood migration failed: $e");
+ s.toString().split("\n").forEach(printV);
+ } finally {
+ ironwoodMigrateMutex.release();
+ }
+ }
+
+ Future<void> _$ironwoodMigrate() async {
+ if (syncStatus is! SyncedSyncStatus) {
+ return;
+ }
+ final event = await runWithCoin(
+ accountId: accountId,
+ func: (coin) async {
+ if (!await zkool_network.isIronwoodActive(c: coin)) {
+ return null;
+ }
+ final bal = await zkool_sync.balance(c: coin);
+ if (bal.field0.length <= 2 || bal.field0[2] <= BigInt.zero) {
+ return null;
+ }
+ return zkool_migrate.stepMigration(c: coin);
+ },
+ );
+ if (event == null) {
+ return;
+ }
+ switch (event) {
+ case zkool_migrate.MigrationEvent_Complete():
+ case zkool_migrate.MigrationEvent_NothingToDo():
+ return;
+ case zkool_migrate.MigrationEvent_SplitComplete(:final fee):
+ printV("ironwood split step complete, fee: $fee");
+ case zkool_migrate.MigrationEvent_MigrateComplete(:final fee):
+ printV("ironwood migrate step complete, fee: $fee");
+ case zkool_migrate.MigrationEvent_Error(:final message):
+ printV("ironwood migration error: $message");
+ return;
+ }
+
+ _lastIronwoodMigrateAt = DateTime.now();
+ await updateTransactions();
+ await _refreshBalance(runAutoShield: false, runIronwoodMigrate: false);
+ }
+
+ Future<void> _updateIronwoodActive() async {
+ bool? active;
+ try {
+ active = await runWithCoin(
accountId: accountId,
- func: (coin) => zkool_sync.balance(c: coin),
+ func: (final coin) => zkool_network.isIronwoodActive(c: coin),
);
+ } catch (e) {
+ printV("isIronwoodActive: $e");
+ }
- // 0 - transparent
- // 1 - sapling
- // 2 - orchard
- final confirmedTotal = bal.field0.reduce((final a, final b) => a + b);
+ if (active == null && networkFor(walletInfo) == ZcashNetwork.regtest) {
+ try {
+ final height = await runWithCoin(
+ accountId: accountId,
+ func: (final coin) => zkool_network.getCurrentHeight(c: coin),
+ );
+ active = height >= ZcashNetwork.regtestNu63Height;
+ printV("regtest ironwood inferred from height $height: $active");
+ } catch (e) {
+ printV("regtest height check failed: $e");
+ }
+ }
- // int knownOutPending = 0;
- // ZcashWalletBase.temporarySentTx[accountId]?.forEach((final sTx) {
- // knownOutPending += sTx.value; // it's negative
- // });
- final confirmedSpendable = confirmedTotal - bal.field0[0];
+ if (active == null) {
+ return;
+ }
+
+ ironwoodActive = active;
+ runInAction(() => walletAddresses.setIronwoodActive(active!));
+ printV("ironwoodActive=$active (account $accountId)");
+ }
+
+ Future<void> _refreshBalance({
+ required final bool runAutoShield,
+ final bool runIronwoodMigrate = true,
+ }) async {
+ try {
+ await _updateIronwoodActive();
+ final bal = await runWithCoin(
+ accountId: accountId,
+ func: (final coin) async => zkool_sync.balance(c: coin),
+ );
+
+ // 0 - transparent, 1 - sapling, 2 - orchard, 3 - ironwood
+ final transparent = bal.field0[0];
+ final sapling = bal.field0.length > 1 ? bal.field0[1] : BigInt.zero;
+ final orchard = bal.field0.length > 2 ? bal.field0[2] : BigInt.zero;
+ final ironwood = bal.field0.length > 3 ? bal.field0[3] : BigInt.zero;
if (runAutoShield) {
await _autoShield();
}
+ if (runIronwoodMigrate) {
+ await _ironwoodMigrate();
+ }
+
+ // After NU6.3, Orchard notes are migrated to Ironwood - show them as unconfirmed.
+ final BigInt availableAmount;
+ final BigInt unavailableAmount;
+ if (ironwoodActive == true && orchard > BigInt.zero) {
+ availableAmount = sapling + ironwood;
+ unavailableAmount = transparent + orchard;
+ } else {
+ availableAmount = sapling + orchard + ironwood;
+ unavailableAmount = transparent;
+ }
balance[CryptoCurrency.zec] = ZcashBalance(
- Money(confirmedSpendable, currency),
- Money(confirmedTotal - confirmedSpendable, currency),
+ Money(availableAmount, currency),
+ Money(unavailableAmount, currency),
frozen: Money.zero(currency),
);
} catch (e, stackTrace) {
@@ -1211,7 +1391,9 @@ abstract class ZcashWalletBase
late ZcashWalletAddresses walletAddresses = ZcashWalletAddresses(accountId, walletInfo);
static Future<ZcashWallet> create(final WalletCredentials credentials) async {
- await $init();
+ final network = networkForCredentials(credentials);
+ await $init(network: network);
+ credentials.walletInfo?.network = network.value;
final newWalletCredentials = credentials as ZcashNewWalletCredentials;
String mnemonic;
@@ -1222,7 +1404,7 @@ abstract class ZcashWalletBase
mnemonic = bip39.generateMnemonic(strength: strength);
}
- final birthHeight = await ZcashHeight.getBlockHeightByTime(DateTime.now());
+ final birthHeight = await birthHeightForNetwork(network);
final accountId = await restoreZcashWalletFromSeed(
name: credentials.name,
@@ -1241,7 +1423,9 @@ abstract class ZcashWalletBase
}
static Future<ZcashWallet> restore(final WalletCredentials credentials) async {
- await $init();
+ final network = networkForCredentials(credentials);
+ await $init(network: network);
+ credentials.walletInfo?.network = network.value;
final fromSeedCredentials = credentials as ZcashFromSeedWalletCredentials;
final String? seed = fromSeedCredentials.seed;
if (seed == null || seed.isEmpty) {
@@ -1265,7 +1449,9 @@ abstract class ZcashWalletBase
}
static Future<ZcashWallet> restoreKeys(final WalletCredentials credentials) async {
- await $init();
+ final network = networkForCredentials(credentials);
+ await $init(network: network);
+ credentials.walletInfo?.network = network.value;
final fromKeysCredentials = credentials as ZcashFromKeysWalletCredentials;
final String? keys = fromKeysCredentials.privateKey;
if (keys == null || keys.isEmpty) {
@@ -1299,7 +1485,8 @@ abstract class ZcashWalletBase
required final String password,
required final WalletInfo walletInfo,
}) async {
- await $init();
+ final network = networkFor(walletInfo);
+ await $init(network: network);
// if (password.isNotEmpty) {
// setDbPasswd(coin, password);
// }
@@ -1379,9 +1566,32 @@ abstract class ZcashWalletBase
static WalletType get _type => WalletType.zcash;
- static Future<String> getDbDataPath() async {
+ static ZcashNetwork networkFor(final WalletInfo? walletInfo) =>
+ ZcashNetwork.fromName(walletInfo?.network ?? ZcashNetwork.mainnet.value);
+
+ static ZcashNetwork networkForCredentials(final WalletCredentials credentials) {
+ if (credentials is ZcashNewWalletCredentials) {
+ return ZcashNetwork.fromIndex(credentials.network);
+ }
+ if (credentials is ZcashFromSeedWalletCredentials) {
+ return ZcashNetwork.fromIndex(credentials.network);
+ }
+ if (credentials is ZcashFromKeysWalletCredentials) {
+ return ZcashNetwork.fromIndex(credentials.network);
+ }
+ return ZcashNetwork.mainnet;
+ }
+
+ static Future<int> birthHeightForNetwork(final ZcashNetwork network) async {
+ if (network != ZcashNetwork.mainnet) {
+ return 1;
+ }
+ return ZcashHeight.getBlockHeightByTime(DateTime.now());
+ }
+
+ static Future<String> getDbDataPath({final ZcashNetwork network = ZcashNetwork.mainnet}) async {
final pathForWalletType = await pathForWalletTypeDir(type: _type);
- final dbDataPath = "${pathForWalletType}/zec.v2.db";
+ final dbDataPath = "${pathForWalletType}/${network.dbFileName}";
if (!Directory(pathForWalletType).existsSync()) {
Directory(pathForWalletType).createSync(recursive: true);
}
@@ -1398,6 +1608,8 @@ abstract class ZcashWalletBase
}
static bool _initialized = false;
+ static bool _rustInitialized = false;
+ static ZcashNetwork? _activeNetwork;
static void unlockDatabase(final String password) {
_password = password;
@@ -1406,22 +1618,25 @@ abstract class ZcashWalletBase
static var c = zkool_coin.Coin();
static String? _password;
- static Future<void> $init() async {
- if (_initialized) return;
- _initialized = true;
- printV(r".$init()");
- await zkool_frb.RustLib.init();
+ static Future<void> $init({final ZcashNetwork network = ZcashNetwork.mainnet}) async {
+ if (!_rustInitialized) {
+ await zkool_frb.RustLib.init();
+ _rustInitialized = true;
+ }
+ if (_initialized && _activeNetwork == network) {
+ return;
+ }
+ printV(r".$init($network)");
ZcashMempoolService.instance.onAccountsUpdated = (final accountIds) {
for (final accountId in accountIds) {
unawaited(refreshWalletForAccount(accountId));
}
};
- final dbFile = File(await getDbDataPath());
+ final dbFile = File(await getDbDataPath(network: network));
final ywalletDbFile = File(await getDbDataPathLegacyYwallet());
await zkool_network.initDatadir(directory: dbFile.parent.path);
- // c = await c.openDatabase(dbFilepath: dbFile.path, password: 'cw_zcash_migration');
c = await c.openDatabase(dbFilepath: dbFile.path, password: null);
- printV("initWallet");
+ printV("initWallet: ${dbFile.path}");
if (_password == null) {
throw Exception("Zcash wallet locked! Please contact support");
}
@@ -1432,6 +1647,7 @@ abstract class ZcashWalletBase
//TODO(mrcyjanek): migrate to zkool
}
+ _activeNetwork = network;
_initialized = true;
}
diff --git a/cw_zcash/lib/src/zcash_wallet_addresses.dart b/cw_zcash/lib/src/zcash_wallet_addresses.dart
index 42bd304..295f2b0 100644
--- a/cw_zcash/lib/src/zcash_wallet_addresses.dart
+++ b/cw_zcash/lib/src/zcash_wallet_addresses.dart
@@ -39,8 +39,24 @@ abstract class ZcashWalletAddressesBase extends WalletAddresses with Store {
@observable
String? unifiedAddress;
+ @observable
+ bool ironwoodActive = false;
+
+ @action
+ void setIronwoodActive(final bool active) {
+ ironwoodActive = active;
+ }
+
@override
@computed
+ List<ReceivePageOption> get receivePageOptions {
+ return [
+ ...ZcashReceivePageOption.allOptionsFor(ironwood: ironwoodActive),
+ ...ReceivePageOptions.where((final element) => element != ReceivePageOption.mainnet),
+ ];
+ }
+
+ @override
String get latestAddress {
switch (addressPageType) {
case ZcashAddressType.transparent:
@@ -267,14 +283,6 @@ abstract class ZcashWalletAddressesBase extends WalletAddresses with Store {
return addressInfos[0] ?? [];
}
- @override
- List<ReceivePageOption> get receivePageOptions {
- return [
- ...ZcashReceivePageOption.all,
- ...ReceivePageOptions.where((final element) => element != ReceivePageOption.mainnet),
- ];
- }
-
@override
PaymentURI getPaymentUri(final String amount) => ZcashURI(amount: amount, address: address);
}
diff --git a/cw_zcash/lib/src/zcash_wallet_service.dart b/cw_zcash/lib/src/zcash_wallet_service.dart
index 4f46047..660724e 100644
--- a/cw_zcash/lib/src/zcash_wallet_service.dart
+++ b/cw_zcash/lib/src/zcash_wallet_service.dart
@@ -91,7 +91,11 @@ class ZcashWalletService
@override
Future<ZcashWallet> openWallet(final String name, final String password) async {
- await ZcashWalletBase.$init();
+ final walletInfo = await WalletInfo.get(name, getType());
+ if (walletInfo == null) {
+ throw Exception('Wallet not found');
+ }
+ await ZcashWalletBase.$init(network: ZcashWalletBase.networkFor(walletInfo));
if (await isWalletExit(name)) {
final path = (await pathForWallet(name: name, type: getType())) + ".v2";
if (!File(path).existsSync()) {
@@ -100,10 +104,6 @@ class ZcashWalletService
}
await loadShieldTxs();
- final walletInfo = await WalletInfo.get(name, getType());
- if (walletInfo == null) {
- throw Exception('Wallet not found');
- }
try {
final wallet = await ZcashWalletBase.open(
name: name,
diff --git a/cw_zcash/lib/src/zkool_compat.dart b/cw_zcash/lib/src/zkool_compat.dart
index c13ff29..60c296e 100644
--- a/cw_zcash/lib/src/zkool_compat.dart
+++ b/cw_zcash/lib/src/zkool_compat.dart
@@ -1,3 +1,11 @@
import 'dart:typed_data';
final zecBase = Uint8List(32);
+
+/// Whether NU6.3 (Ironwood) is active at the chain tip. Updated on balance refresh.
+bool ironwoodActive = false;
+
+/// Pool bitmask for Ironwood (pool index 3). Orchard and Ironwood share the same address.
+const ironwoodPoolMask = 8;
+
+int? get shieldedRecipientPools => ironwoodActive ? ironwoodPoolMask : null;
diff --git a/cw_zcash/lib/src/zkooltx.dart b/cw_zcash/lib/src/zkooltx.dart
index 16fdc6f..6c7c64b 100644
--- a/cw_zcash/lib/src/zkooltx.dart
+++ b/cw_zcash/lib/src/zkooltx.dart
@@ -17,7 +17,7 @@ enum TxType {
transparentSelfTransfer, // 12
}
-enum NotePool { transparent, sapling, orchard, unknown }
+enum NotePool { transparent, sapling, orchard, ironwood }
class ZkoolTx {
ZkoolTx(final zkool_account.Tx tx, final zkool_account.TxAccount txAccount)
@@ -72,6 +72,22 @@ class ZkoolTx {
.fold(BigInt.zero, (final a, final o) => a + o.value);
}
+ BigInt get ironwoodReceived {
+ final fromNotes = _txAccount.notes
+ .where((final n) => n.pool == NotePool.ironwood.index)
+ .fold(BigInt.zero, (final a, final n) => a + n.value);
+ if (fromNotes > BigInt.zero) {
+ return fromNotes;
+ }
+ return _txAccount.outputs
+ .where((final o) => o.pool == NotePool.ironwood.index)
+ .fold(BigInt.zero, (final a, final o) => a + o.value);
+ }
+
+ BigInt get orchardSpent => _txAccount.spends
+ .where((final s) => s.pool == NotePool.orchard.index)
+ .fold(BigInt.zero, (final a, final s) => a + s.value);
+
String get txHash {
final reversed = Uint8List.fromList(_txAccount.txid.reversed.toList());
final txId = uint8ListToHex(reversed);
@@ -222,6 +238,13 @@ class ZkoolTx {
}
static Uint8List _txidFromJson(final dynamic raw) {
+ return _bytesFromJson(raw, fieldName: "txid");
+ }
+
+ static Uint8List _bytesFromJson(final dynamic raw, {required final String fieldName}) {
+ if (raw == null) {
+ return Uint8List(0);
+ }
if (raw is Uint8List) {
return raw;
}
@@ -231,7 +254,7 @@ class ZkoolTx {
if (raw is String) {
return base64.decode(raw);
}
- throw FormatException("Invalid txid in ZkoolTx json: $raw");
+ throw FormatException("Invalid $fieldName in ZkoolTx json: $raw");
}
static List<T> _listFromJson<T>(final dynamic raw, final T Function(Map<String, dynamic>) parse) {
@@ -257,6 +280,7 @@ class ZkoolTx {
tpe: txJson["tpe"] == null ? null : _asInt(txJson["tpe"]),
zsaValue: _asInt(txJson["zsaValue"]),
assetDisplay: txJson["assetDisplay"] as String? ?? "",
+ isUserMemo: txJson["isUserMemo"] as bool? ?? false,
),
zkool_account.TxAccount(
id: _asInt(txAccountJson["id"]),
@@ -304,6 +328,7 @@ class ZkoolTx {
pool: _asInt(a["pool"]),
output: a["output"] == null ? null : _asInt(a["output"]),
memo: a["memo"] as String?,
+ memoBytes: _bytesFromJson(a["memoBytes"], fieldName: "memoBytes"),
),
),
),
diff --git a/cw_zcash/pubspec.yaml b/cw_zcash/pubspec.yaml
index 84e0d27..eeed076 100644
--- a/cw_zcash/pubspec.yaml
+++ b/cw_zcash/pubspec.yaml
@@ -21,7 +21,7 @@ dependencies:
# path: ../../zkool2
git:
url: https://github.com/cake-tech/zkool2.git
- ref: dbd3dde8d8485428326a58e55660b7404666dd7c
+ ref: 97d18db966c061a536fa9056efb3756f4b9aa89d
path_provider: any
sqflite_common_ffi: any
diff --git a/ios/Podfile b/ios/Podfile
index cf24cb6..60e07e6 100644
--- a/ios/Podfile
+++ b/ios/Podfile
@@ -43,7 +43,7 @@ post_install do |installer|
flutter_additional_ios_build_settings(target)
target.build_configurations.each do |config|
- config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
+ config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0'
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
'$(inherited)',
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 295e3b9..be45d60 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -1,14 +1,4 @@
PODS:
- - AppAuth (1.7.6):
- - AppAuth/Core (= 1.7.6)
- - AppAuth/ExternalUserAgent (= 1.7.6)
- - AppAuth/Core (1.7.6)
- - AppAuth/ExternalUserAgent (1.7.6):
- - AppAuth/Core
- - AppCheckCore (11.2.0):
- - GoogleUtilities/Environment (~> 8.0)
- - GoogleUtilities/UserDefaults (~> 8.0)
- - PromisesObjC (~> 2.4)
- bitbox_flutter (0.0.1):
- Flutter
- breez_sdk_spark_flutter (0.14.0):
@@ -64,6 +54,8 @@ PODS:
- DKImagePickerController/PhotoGallery
- Flutter
- Flutter (1.0.0)
+ - flutter_contacts (0.0.1):
+ - Flutter
- flutter_inappwebview_ios (0.0.1):
- Flutter
- flutter_inappwebview_ios/Core (= 0.0.1)
@@ -83,34 +75,6 @@ PODS:
- Flutter
- fluttertoast (0.0.2):
- Flutter
- - google_sign_in_ios (0.0.1):
- - AppAuth (>= 1.7.4)
- - Flutter
- - FlutterMacOS
- - GoogleSignIn (~> 8.0)
- - GTMSessionFetcher (>= 3.4.0)
- - GoogleSignIn (8.0.0):
- - AppAuth (< 2.0, >= 1.7.3)
- - AppCheckCore (~> 11.0)
- - GTMAppAuth (< 5.0, >= 4.1.1)
- - GTMSessionFetcher/Core (~> 3.3)
- - GoogleUtilities/Environment (8.1.0):
- - GoogleUtilities/Privacy
- - GoogleUtilities/Logger (8.1.0):
- - GoogleUtilities/Environment
- - GoogleUtilities/Privacy
- - GoogleUtilities/Privacy (8.1.0)
- - GoogleUtilities/UserDefaults (8.1.0):
- - GoogleUtilities/Logger
- - GoogleUtilities/Privacy
- - GTMAppAuth (4.1.1):
- - AppAuth/Core (~> 1.7)
- - GTMSessionFetcher/Core (< 4.0, >= 3.3)
- - GTMSessionFetcher (3.5.0):
- - GTMSessionFetcher/Full (= 3.5.0)
- - GTMSessionFetcher/Core (3.5.0)
- - GTMSessionFetcher/Full (3.5.0):
- - GTMSessionFetcher/Core
- image_picker_ios (0.0.1):
- Flutter
- in_app_review (2.0.0):
@@ -126,7 +90,6 @@ PODS:
- payjoin_flutter (0.23.0)
- permission_handler_apple (9.4.8):
- Flutter
- - PromisesObjC (2.4.0)
- quick_actions_ios (0.0.1):
- Flutter
- reown_yttrium (0.0.1):
@@ -204,6 +167,7 @@ DEPENDENCIES:
- fast_scanner (from `.symlinks/plugins/fast_scanner/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`)
+ - flutter_contacts (from `.symlinks/plugins/flutter_contacts/ios`)
- flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
- flutter_local_authentication (from `.symlinks/plugins/flutter_local_authentication/ios`)
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
@@ -211,7 +175,6 @@ DEPENDENCIES:
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
- - google_sign_in_ios (from `.symlinks/plugins/google_sign_in_ios/darwin`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- in_app_review (from `.symlinks/plugins/in_app_review/ios`)
- integration_test (from `.symlinks/plugins/integration_test/ios`)
@@ -237,17 +200,10 @@ DEPENDENCIES:
SPEC REPOS:
https://github.com/CocoaPods/Specs.git:
- - AppAuth
- - AppCheckCore
- CryptoSwift
- DKImagePickerController
- DKPhotoGallery
- - GoogleSignIn
- - GoogleUtilities
- - GTMAppAuth
- - GTMSessionFetcher
- OrderedSet
- - PromisesObjC
- SDWebImage
- sqlite3
- SwiftyGif
@@ -278,6 +234,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/file_picker/ios"
Flutter:
:path: Flutter
+ flutter_contacts:
+ :path: ".symlinks/plugins/flutter_contacts/ios"
flutter_inappwebview_ios:
:path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
flutter_local_authentication:
@@ -292,8 +250,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/flutter_secure_storage/ios"
fluttertoast:
:path: ".symlinks/plugins/fluttertoast/ios"
- google_sign_in_ios:
- :path: ".symlinks/plugins/google_sign_in_ios/darwin"
image_picker_ios:
:path: ".symlinks/plugins/image_picker_ios/ios"
in_app_review:
@@ -340,8 +296,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/wakelock_plus/ios"
SPEC CHECKSUMS:
- AppAuth: d4f13a8fe0baf391b2108511793e4b479691fb73
- AppCheckCore: cc8fd0a3a230ddd401f326489c99990b013f0c4f
bitbox_flutter: 9505732798041c413152669751beeaecc5fe400f
breez_sdk_spark_flutter: ed3b6709b8ce9b40309d8728640eade5b19e8a0d
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
@@ -357,6 +311,7 @@ SPEC CHECKSUMS:
fast_scanner: 2cb1ad3e69e645e9980fb4961396ce5804caa3e3
file_picker: 9b3292d7c8bc68c8a7bf8eb78f730e49c8efc517
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
+ flutter_contacts: 2ec1d6b62ca2869c3bbb3871bd74d4e3ef157135
flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
flutter_local_authentication: 989278c681612f1ee0e36019e149137f114b9d7f
flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb
@@ -364,11 +319,6 @@ SPEC CHECKSUMS:
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
flutter_secure_storage: 2c2ff13db9e0a5647389bff88b0ecac56e3f3418
fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1
- google_sign_in_ios: b48bb9af78576358a168361173155596c845f0b9
- GoogleSignIn: ce8c89bb9b37fb624b92e7514cc67335d1e277e4
- GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
- GTMAppAuth: f69bd07d68cd3b766125f7e072c45d7340dea0de
- GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
in_app_review: 7dd1ea365263f834b8464673f9df72c80c17c937
integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
@@ -377,7 +327,6 @@ SPEC CHECKSUMS:
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
payjoin_flutter: d9d4c8aa16bd5dfedb9b21d0edc8199e0187d96e
permission_handler_apple: 92d754bbaa7361d436db2d6c3c1c2a0fdcec462e
- PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
quick_actions_ios: 500fcc11711d9f646739093395c4ae8eec25f779
reown_yttrium: cee334ade64725b1d83f7b34c706a6aae2696d58
rive_common: dd421daaf9ae69f0125aa761dd96abd278399952
@@ -398,6 +347,6 @@ SPEC CHECKSUMS:
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
YttriumWrapper: 31e937fe9fbe0f1314d2ca6be9ce9b379a059966
-PODFILE CHECKSUM: 5296465b1c6d14d506230356756826012f65d97a
+PODFILE CHECKSUM: b08d7f4c8d9c64f5648599a30caea878b0f7616a
-COCOAPODS: 1.16.2
+COCOAPODS: 1.17.0
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index 6b469cb..e9d4c22 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -479,7 +479,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -509,7 +509,7 @@
"$(PROJECT_DIR)",
);
INFOPLIST_FILE = Runner/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -578,7 +578,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -628,7 +628,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -660,7 +660,7 @@
"$(PROJECT_DIR)",
);
INFOPLIST_FILE = Runner/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -704,7 +704,7 @@
"$(PROJECT_DIR)",
);
INFOPLIST_FILE = Runner/Info.plist;
- IPHONEOS_DEPLOYMENT_TARGET = 13.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
diff --git a/lib/core/address_validator.dart b/lib/core/address_validator.dart
index e423dda..e5905bb 100644
--- a/lib/core/address_validator.dart
+++ b/lib/core/address_validator.dart
@@ -3,6 +3,7 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/core/validator.dart';
import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/zano/zano.dart';
+import 'package:cake_wallet/zcash/zcash_network_type.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/erc20_token.dart';
@@ -10,8 +11,11 @@ const BEFORE_REGEX = '(^|\\s)';
const AFTER_REGEX = '(\$|\\s)';
class AddressValidator extends TextValidator {
- AddressValidator({required CryptoCurrency type, bool isTestnet = false})
- : super(
+ AddressValidator({
+ required CryptoCurrency type,
+ bool isTestnet = false,
+ String? network,
+ }) : super(
errorMessage: S.current.error_text_address,
useAdditionalValidation: [CryptoCurrency.btc, CryptoCurrency.ltc].contains(type)
? (String txt) {
@@ -32,7 +36,7 @@ class AddressValidator extends TextValidator {
: type == CryptoCurrency.zano
? zano?.validateAddress
: null,
- pattern: getPattern(type, isTestnet: isTestnet),
+ pattern: getPattern(type, isTestnet: isTestnet, network: network),
length: getLength(type),
);
@@ -61,7 +65,7 @@ class AddressValidator extends TextValidator {
CryptoCurrency.btcln,
];
- static String getPattern(CryptoCurrency type, {bool isTestnet = false}) {
+ static String? getPattern(CryptoCurrency type, {bool isTestnet = false, String? network}) {
var pattern = "";
if (type is Erc20Token) {
pattern = '0x[0-9a-zA-Z]+';
@@ -170,6 +174,9 @@ class AddressValidator extends TextValidator {
case CryptoCurrency.hbar:
pattern = '[0-9a-zA-Z.]+';
case CryptoCurrency.zec:
+ if (ZcashNetworkType.isDevNetwork(network)) {
+ return null;
+ }
pattern = '(?:'
't1[0-9A-Za-z]{33}'
'|t3[0-9A-Za-z]{33}'
diff --git a/lib/new-ui/widgets/receive_page/receive_address_type_selector.dart b/lib/new-ui/widgets/receive_page/receive_address_type_selector.dart
index ab4850c..d628573 100644
--- a/lib/new-ui/widgets/receive_page/receive_address_type_selector.dart
+++ b/lib/new-ui/widgets/receive_page/receive_address_type_selector.dart
@@ -6,6 +6,7 @@ import 'package:cake_wallet/src/widgets/section_divider.dart';
import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
import 'package:cw_core/receive_page_option.dart';
import 'package:flutter/material.dart';
+import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
@@ -34,6 +35,7 @@ class _ReceiveAddressTypeSelectorState extends State<ReceiveAddressTypeSelector>
@override
Widget build(BuildContext context) {
+ return Observer(builder: (final _) {
final commonOptions = widget.receiveOptionViewModel.options
.where((element) =>
element.isCommon ||
@@ -223,6 +225,7 @@ class _ReceiveAddressTypeSelectorState extends State<ReceiveAddressTypeSelector>
],
)),
);
+ });
}
}
diff --git a/lib/router.dart b/lib/router.dart
index 40772c8..a130b26 100644
--- a/lib/router.dart
+++ b/lib/router.dart
@@ -162,6 +162,7 @@ import 'package:cw_core/unspent_coin_type.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
+import 'package:cake_wallet/zcash/zcash_network_type.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -822,6 +823,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
final isChildWallet = args['isChildWallet'] as bool? ?? false;
final useTestnet = args['useTestnet'] as bool;
final toggleTestnet = args['toggleTestnet'] as Function(bool? val);
+ final zcashNetwork = args['zcashNetwork'] as int? ?? ZcashNetworkType.mainnet;
+ final setZcashNetwork =
+ args['setZcashNetwork'] as void Function(int network)? ?? (_) {};
final restoredWallet = args['restoredWallet'] as RestoredWallet?;
final viewModelParam = {'type': type, 'isPow': false};
@@ -832,6 +836,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
isChildWallet: isChildWallet,
useTestnet: useTestnet,
toggleUseTestnet: toggleTestnet,
+ zcashNetwork: zcashNetwork,
+ setZcashNetwork: setZcashNetwork,
advancedPrivacySettingsViewModel:
getIt.get<AdvancedPrivacySettingsViewModel>(param1: type),
nodeViewModel: getIt.get<NodeCreateOrEditViewModel>(param1: viewModelParam),
diff --git a/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart b/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart
index 383e0d1..76683bb 100644
--- a/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart
+++ b/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart
@@ -14,11 +14,13 @@ import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
+import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart';
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
import 'package:cake_wallet/view_model/settings/choices_list_item.dart';
import 'package:cw_core/wallet_type.dart';
+import 'package:cake_wallet/zcash/zcash_network_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@@ -28,6 +30,8 @@ class AdvancedPrivacySettingsPage extends BasePage {
required this.isChildWallet,
required this.useTestnet,
required this.toggleUseTestnet,
+ required this.zcashNetwork,
+ required this.setZcashNetwork,
required this.advancedPrivacySettingsViewModel,
required this.nodeViewModel,
required this.seedSettingsViewModel,
@@ -44,6 +48,8 @@ class AdvancedPrivacySettingsPage extends BasePage {
final bool isChildWallet;
final bool useTestnet;
final Function(bool? val) toggleUseTestnet;
+ final int zcashNetwork;
+ final void Function(int network) setZcashNetwork;
@override
Widget body(BuildContext context) => _AdvancedPrivacySettingsBody(
@@ -51,6 +57,8 @@ class AdvancedPrivacySettingsPage extends BasePage {
isChildWallet,
useTestnet,
toggleUseTestnet,
+ zcashNetwork,
+ setZcashNetwork,
advancedPrivacySettingsViewModel,
nodeViewModel,
seedSettingsViewModel,
@@ -63,6 +71,8 @@ class _AdvancedPrivacySettingsBody extends StatefulWidget {
this.isChildWallet,
this.useTestnet,
this.toggleUseTestnet,
+ this.zcashNetwork,
+ this.setZcashNetwork,
this.privacySettingsViewModel,
this.nodeViewModel,
this.seedTypeViewModel,
@@ -76,6 +86,8 @@ class _AdvancedPrivacySettingsBody extends StatefulWidget {
final bool isChildWallet;
final bool useTestnet;
final Function(bool? val) toggleUseTestnet;
+ final int zcashNetwork;
+ final void Function(int network) setZcashNetwork;
@override
_AdvancedPrivacySettingsBodyState createState() => _AdvancedPrivacySettingsBodyState();
@@ -87,6 +99,7 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
final _formKey = GlobalKey<NodeFormState>();
final _passphraseFormKey = GlobalKey<FormState>();
bool? testnetValue;
+ int? zcashNetworkValue;
bool obscurePassphrase = true;
@@ -112,6 +125,7 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
if (testnetValue == null && widget.useTestnet) {
testnetValue = widget.useTestnet;
}
+ zcashNetworkValue ??= widget.zcashNetwork;
return Container(
padding: EdgeInsets.only(top: 24),
@@ -281,6 +295,20 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
],
);
}),
+ if (FeatureFlag.hasDevOptions &&
+ widget.privacySettingsViewModel.type == WalletType.zcash)
+ SettingsChoicesCell(
+ ChoicesListItem<int>(
+ title: 'Zcash network',
+ items: ZcashNetworkType.values,
+ selectedItem: zcashNetworkValue ?? ZcashNetworkType.mainnet,
+ displayItem: ZcashNetworkType.label,
+ onItemSelected: (final network) {
+ setState(() => zcashNetworkValue = network);
+ widget.setZcashNetwork(network);
+ },
+ ),
+ ),
if (widget.privacySettingsViewModel.type == WalletType.bitcoin ||
widget.privacySettingsViewModel.type == WalletType.decred)
Builder(builder: (_) {
diff --git a/lib/src/screens/new_wallet/new_wallet_page.dart b/lib/src/screens/new_wallet/new_wallet_page.dart
index 8876529..f6a4fb2 100644
--- a/lib/src/screens/new_wallet/new_wallet_page.dart
+++ b/lib/src/screens/new_wallet/new_wallet_page.dart
@@ -316,6 +316,8 @@ class _WalletNameFormState extends State<WalletNameForm> {
"type": _walletNewVM.type,
"useTestnet": _walletNewVM.useTestnet,
"toggleTestnet": _walletNewVM.toggleUseTestnet,
+ "zcashNetwork": _walletNewVM.zcashNetwork,
+ "setZcashNetwork": _walletNewVM.setZcashNetwork,
"isChildWallet": widget.isChildWallet,
});
},
diff --git a/lib/src/screens/restore/wallet_restore_page.dart b/lib/src/screens/restore/wallet_restore_page.dart
index c8f795c..de2157b 100644
--- a/lib/src/screens/restore/wallet_restore_page.dart
+++ b/lib/src/screens/restore/wallet_restore_page.dart
@@ -121,7 +121,9 @@ class WalletRestorePage extends BasePage {
'isFromRestore': true,
'type': walletRestoreViewModel.type,
'useTestnet': walletRestoreViewModel.useTestnet,
- 'toggleTestnet': walletRestoreViewModel.toggleUseTestnet
+ 'toggleTestnet': walletRestoreViewModel.toggleUseTestnet,
+ 'zcashNetwork': walletRestoreViewModel.zcashNetwork,
+ 'setZcashNetwork': walletRestoreViewModel.setZcashNetwork,
},
);
},
diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart
index 17ffd33..e16b0af 100644
--- a/lib/view_model/dashboard/dashboard_view_model.dart
+++ b/lib/view_model/dashboard/dashboard_view_model.dart
@@ -538,10 +538,16 @@ abstract class DashboardViewModelBase with Store {
.map((item) => _txIdentityString(item.transaction.txHash, item.transaction.direction))
.toSet();
- transactions.removeWhere(
- (item) => newKeys
- .contains(_txIdentityString(item.transaction.txHash, item.transaction.direction)),
- );
+ transactions.removeWhere((item) {
+ if (wallet.type == WalletType.zcash) {
+ return newTransactions.any(
+ (n) => n.transaction.txHash == item.transaction.txHash,
+ );
+ }
+ return newKeys.contains(
+ _txIdentityString(item.transaction.txHash, item.transaction.direction),
+ );
+ });
transactions.addAll(newTransactions);
// transactions.clear();
diff --git a/lib/view_model/dashboard/receive_option_view_model.dart b/lib/view_model/dashboard/receive_option_view_model.dart
index a86890f..9ddbe43 100644
--- a/lib/view_model/dashboard/receive_option_view_model.dart
+++ b/lib/view_model/dashboard/receive_option_view_model.dart
@@ -3,6 +3,7 @@ import 'package:cake_wallet/zcash/zcash.dart';
import 'package:cw_core/receive_page_option.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
+import 'package:cw_zcash/cw_zcash.dart';
import 'package:mobx/mobx.dart';
part 'receive_option_view_model.g.dart';
@@ -18,7 +19,14 @@ abstract class ReceiveOptionViewModelBase with Store {
? ReceivePageOption.testnet
: _wallet.type == WalletType.zcash
? zcash!.getSelectedAddressType(_wallet)
- : ReceivePageOption.mainnet);
+ : ReceivePageOption.mainnet) {
+ if (_wallet.type == WalletType.zcash) {
+ reaction<bool>(
+ (_) => (_wallet.walletAddresses as ZcashWalletAddresses).ironwoodActive,
+ (_) => _syncZcashShieldedLabel(),
+ );
+ }
+ }
final WalletBase _wallet;
@@ -27,10 +35,21 @@ abstract class ReceiveOptionViewModelBase with Store {
@observable
ReceivePageOption selectedReceiveOption;
+ @computed
List<ReceivePageOption> get options => _wallet.walletAddresses.receivePageOptions;
String get walletTypeString => walletTypeToString(_wallet.type);
@action
void selectReceiveOption(ReceivePageOption option) => selectedReceiveOption = option;
+
+ @action
+ void _syncZcashShieldedLabel() {
+ for (final option in options) {
+ if (option.value == selectedReceiveOption.value) {
+ selectedReceiveOption = option;
+ return;
+ }
+ }
+ }
}
diff --git a/lib/view_model/dashboard/transaction_list_item.dart b/lib/view_model/dashboard/transaction_list_item.dart
index e2554b2..9a7dfa6 100644
--- a/lib/view_model/dashboard/transaction_list_item.dart
+++ b/lib/view_model/dashboard/transaction_list_item.dart
@@ -60,6 +60,9 @@ class TransactionListItem extends ActionListItem with Keyable {
return 'Transaction has missing data';
}
+ if (transaction.additionalInfo['isIronwoodMigration'] == true) {
+ return 'Migration';
+ }
if (transaction.additionalInfo['isAutoShield'] == true) {
if (transaction.isPending) {
final status = formattedStatus;
diff --git a/lib/view_model/send/send_template_view_model.dart b/lib/view_model/send/send_template_view_model.dart
index 75a99bb..f2fca86 100644
--- a/lib/view_model/send/send_template_view_model.dart
+++ b/lib/view_model/send/send_template_view_model.dart
@@ -48,8 +48,11 @@ abstract class SendTemplateViewModelBase with Store {
amountParsingProxy: _appStore.amountParsingProxy,
);
- AddressValidator get addressValidator =>
- AddressValidator(type: _wallet.currency, isTestnet: _wallet.isTestnet);
+ AddressValidator get addressValidator => AddressValidator(
+ type: _wallet.currency,
+ isTestnet: _wallet.isTestnet,
+ network: _wallet.walletInfo.network,
+ );
TemplateValidator get templateValidator => TemplateValidator();
diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart
index ae0f56a..7e7cec7 100644
--- a/lib/view_model/send/send_view_model.dart
+++ b/lib/view_model/send/send_view_model.dart
@@ -190,7 +190,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
// Check if the address is valid for the current currency (except for Zano, which can use handles as addresses)
if (selectedCryptoCurrency != CryptoCurrency.zano) {
- final isValidAddress = AddressValidator(type: selectedCryptoCurrency).isValid(query);
+ final isValidAddress = AddressValidator(
+ type: selectedCryptoCurrency,
+ network: wallet.walletInfo.network,
+ ).isValid(query);
if (isValidAddress) return null;
}
@@ -317,8 +320,11 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
Validator<String> get allAmountValidator => AllAmountValidator();
- Validator<String> get addressValidator =>
- AddressValidator(type: selectedCryptoCurrency, isTestnet: wallet.isTestnet);
+ Validator<String> get addressValidator => AddressValidator(
+ type: selectedCryptoCurrency,
+ isTestnet: wallet.isTestnet,
+ network: wallet.walletInfo.network,
+ );
Validator<String> get textValidator => TextValidator();
diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart
index e83feb9..df9c727 100644
--- a/lib/view_model/transaction_details_view_model.dart
+++ b/lib/view_model/transaction_details_view_model.dart
@@ -428,6 +428,9 @@ abstract class TransactionDetailsViewModelBase with Store {
}
String get formattedTitle {
+ if (transactionInfo.additionalInfo['isIronwoodMigration'] == true) {
+ return 'Migration';
+ }
if (transactionInfo.additionalInfo['isAutoShield'] == true) {
return S.current.shielding;
}
diff --git a/lib/view_model/wallet_creation_vm.dart b/lib/view_model/wallet_creation_vm.dart
index 321a331..eeb2bd4 100644
--- a/lib/view_model/wallet_creation_vm.dart
+++ b/lib/view_model/wallet_creation_vm.dart
@@ -17,6 +17,7 @@ import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
+import 'package:cake_wallet/zcash/zcash_network_type.dart';
import 'package:mobx/mobx.dart';
import 'package:polyseed/polyseed.dart';
@@ -33,9 +34,13 @@ abstract class WalletCreationVMBase with Store {
@observable
bool _useTestnet = false;
+ int _zcashNetwork = ZcashNetworkType.mainnet;
+
@computed
bool get useTestnet => _useTestnet;
+ int get zcashNetwork => _zcashNetwork;
+
@observable
String name;
@@ -252,4 +257,8 @@ abstract class WalletCreationVMBase with Store {
void toggleUseTestnet(bool? value) {
_useTestnet = value ?? !_useTestnet;
}
+
+ void setZcashNetwork(int network) {
+ _zcashNetwork = network;
+ }
}
diff --git a/lib/view_model/wallet_new_vm.dart b/lib/view_model/wallet_new_vm.dart
index cb2f3ed..f7fa7c7 100644
--- a/lib/view_model/wallet_new_vm.dart
+++ b/lib/view_model/wallet_new_vm.dart
@@ -146,6 +146,7 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
password: walletPassword,
mnemonic: newWalletArguments!.mnemonic,
passphrase: passphrase,
+ network: zcashNetwork,
);
case WalletType.decred:
return decred!.createDecredNewWalletCredentials(name: name);
diff --git a/lib/view_model/wallet_restore_view_model.dart b/lib/view_model/wallet_restore_view_model.dart
index 1f95571..467ad06 100644
--- a/lib/view_model/wallet_restore_view_model.dart
+++ b/lib/view_model/wallet_restore_view_model.dart
@@ -224,6 +224,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
password: password,
passphrase: passphrase,
height: height,
+ network: zcashNetwork,
);
case WalletType.none:
case WalletType.haven:
diff --git a/lib/zcash/cw_zcash.dart b/lib/zcash/cw_zcash.dart
index 1284aa3..9dff31b 100644
--- a/lib/zcash/cw_zcash.dart
+++ b/lib/zcash/cw_zcash.dart
@@ -7,12 +7,14 @@ class CWZcash extends Zcash {
WalletInfo? walletInfo,
String? password,
String? mnemonic,
- required String? passphrase}) {
+ required String? passphrase,
+ int network = 0}) {
return ZcashNewWalletCredentials(
name: name,
passphrase: passphrase,
password: password,
mnemonic: mnemonic,
+ network: network,
);
}
@@ -36,9 +38,10 @@ class CWZcash extends Zcash {
required String mnemonic,
required String password,
String? passphrase,
- required int? height}) {
+ required int? height,
+ int network = 0}) {
return ZcashFromSeedWalletCredentials(
- name: name, seed: mnemonic, passphrase: passphrase, password: password, height: height);
+ name: name, seed: mnemonic, passphrase: passphrase, password: password, height: height, network: network);
}
@override
@@ -161,9 +164,12 @@ class CWZcash extends Zcash {
@override
ReceivePageOption getSelectedAddressType(Object wallet) {
final zcashWallet = wallet as ZcashWallet;
- final t =
- (zcashWallet.walletAddresses as ZcashWalletAddresses).walletInfo.addressPageType ?? "";
- return ZcashReceivePageOption.fromType(ZcashReceivePageOption.typeFromString(t));
+ final addresses = zcashWallet.walletAddresses as ZcashWalletAddresses;
+ final type = ZcashReceivePageOption.typeFromString(addresses.walletInfo.addressPageType ?? "");
+ if (type == ZcashAddressType.shieldedOrchard) {
+ return ZcashReceivePageOption.shieldedOrchard(ironwood: addresses.ironwoodActive);
+ }
+ return ZcashReceivePageOption.fromType(type);
}
bool hasSelectedTransparentAddress(Object wallet) {
@@ -172,18 +178,7 @@ class CWZcash extends Zcash {
@override
dynamic getZcashAddressType(ReceivePageOption option) {
- switch (option) {
- case ZcashReceivePageOption.unified:
- return ZcashAddressType.unifiedType;
- case ZcashReceivePageOption.transparent:
- return ZcashAddressType.transparent;
- case ZcashReceivePageOption.shieldedSapling:
- return ZcashAddressType.shieldedSapling;
- case ZcashReceivePageOption.shieldedOrchard:
- return ZcashAddressType.shieldedOrchard;
- default:
- throw Exception("Unknown ReceivePageOption!");
- }
+ return (option as ZcashReceivePageOption).toType();
}
@override
diff --git a/lib/zcash/zcash_network_type.dart b/lib/zcash/zcash_network_type.dart
new file mode 100644
index 0000000..4508711
--- /dev/null
+++ b/lib/zcash/zcash_network_type.dart
@@ -0,0 +1,17 @@
+/// Zcash network indices passed from lib/ into cw_zcash.
+abstract final class ZcashNetworkType {
+ static const int mainnet = 0;
+ static const int testnet = 1;
+ static const int regtest = 2;
+
+ static const List<int> values = [mainnet, testnet, regtest];
+
+ static String label(final int network) => switch (network) {
+ testnet => 'Testnet',
+ regtest => 'Regtest',
+ _ => 'Mainnet',
+ };
+
+ static bool isDevNetwork(final String? network) =>
+ network?.toLowerCase() == 'testnet' || network?.toLowerCase() == 'regtest';
+}
diff --git a/tool/configure.dart b/tool/configure.dart
index 1628abb..348f1c9 100644
--- a/tool/configure.dart
+++ b/tool/configure.dart
@@ -1721,13 +1721,15 @@ abstract class Zcash {
WalletInfo? walletInfo,
String? password,
String? mnemonic,
- required String? passphrase});
+ required String? passphrase,
+ int network = 0});
WalletCredentials createZcashRestoreWalletFromSeedCredentials(
{required String name,
required String mnemonic,
required String password,
String? passphrase,
- required int? height});
+ required int? height,
+ int network = 0});
WalletCredentials createZcashRestoreWalletFromPrivateKey(
{required String name, required String privateKey, required String password, required int height});
String getAddress(WalletBase wallet);
Why this scored 35/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.