From 92c2c5e043b60fc437ea9c749230c76b0fbdab9e Mon Sep 17 00:00:00 2001 From: cyan Date: Mon, 27 Jul 2026 10:20:40 +0200 Subject: [PATCH 1/3] 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 --- cw_core/lib/wallet_info.dart | 7 +- cw_core/lib/wallet_info_legacy.dart | 3 +- cw_custom_lints/pubspec.lock | 429 ++++++++++++++++++ cw_zcash/lib/cw_zcash.dart | 2 + cw_zcash/lib/src/models.dart | 6 + .../lib/src/pending_zcash_transaction.dart | 1 + cw_zcash/lib/src/zcash_mempool.dart | 23 +- cw_zcash/lib/src/zcash_network.dart | 49 ++ .../lib/src/zcash_receive_page_options.dart | 37 +- cw_zcash/lib/src/zcash_taddress_rotation.dart | 29 +- cw_zcash/lib/src/zcash_transaction_info.dart | 2 + cw_zcash/lib/src/zcash_wallet.dart | 342 +++++++++++--- cw_zcash/lib/src/zcash_wallet_addresses.dart | 24 +- cw_zcash/lib/src/zcash_wallet_service.dart | 10 +- cw_zcash/lib/src/zkool_compat.dart | 8 + cw_zcash/lib/src/zkooltx.dart | 29 +- cw_zcash/pubspec.yaml | 2 +- ios/Podfile | 2 +- ios/Podfile.lock | 67 +-- ios/Runner.xcodeproj/project.pbxproj | 12 +- lib/core/address_validator.dart | 15 +- .../receive_address_type_selector.dart | 3 + lib/router.dart | 6 + .../advanced_privacy_settings_page.dart | 28 ++ .../screens/new_wallet/new_wallet_page.dart | 2 + .../screens/restore/wallet_restore_page.dart | 4 +- .../dashboard/dashboard_view_model.dart | 14 +- .../dashboard/receive_option_view_model.dart | 21 +- .../dashboard/transaction_list_item.dart | 3 + .../send/send_template_view_model.dart | 7 +- lib/view_model/send/send_view_model.dart | 12 +- .../transaction_details_view_model.dart | 3 + lib/view_model/wallet_creation_vm.dart | 9 + lib/view_model/wallet_new_vm.dart | 1 + lib/view_model/wallet_restore_view_model.dart | 1 + lib/zcash/cw_zcash.dart | 31 +- lib/zcash/zcash_network_type.dart | 17 + tool/configure.dart | 6 +- 38 files changed, 1064 insertions(+), 203 deletions(-) create mode 100644 cw_custom_lints/pubspec.lock create mode 100644 cw_zcash/lib/src/zcash_network.dart create mode 100644 lib/zcash/zcash_network_type.dart diff --git a/cw_core/lib/wallet_info.dart b/cw_core/lib/wallet_info.dart index aa4b258c2..585ce3b3a 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 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 save() async { diff --git a/cw_core/lib/wallet_info_legacy.dart b/cw_core/lib/wallet_info_legacy.dart index 35a710ee9..2dadbf2ab 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 ?? []) { 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 000000000..cd831e497 --- /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 92af28582..6df1d9b37 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 e6886b189..d6bae3c1f 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 3f6762d3c..3aba2404b 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 33ba40fd7..97bdf87c5 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? _subscription; + void removeTx(final String txId) { + _txsById.remove(ZcashWalletService.normalizeTxId(txId)); + } + + void removeKnownTxs(final Iterable txIds) { + for (final txId in txIds) { + removeTx(txId); + } + } + List 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 000000000..d69254414 --- /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 8a9d05766..31c03ca00 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 allOptionsFor({final bool ironwood = false}) => [ + shieldedOrchard(ironwood: ironwood), + shieldedSapling, + unified, + transparentRotated, + transparent, + ]; + + static List 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 8e8940395..cbbb0b0e5 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 9dd09bdda..46494111b 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 e1258e5e3..cc6df83e4 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 _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.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() @@ -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 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 updateTransactions() async { + if (_isTransactionUpdating) { + _transactionUpdateQueued = true; + return; + } + + _isTransactionUpdating = true; try { - if (_isTransactionUpdating) { - return; - } + do { + _transactionUpdateQueued = false; + final transactions = await fetchTransactions(); - _isTransactionUpdating = true; - final transactions = await fetchTransactions(); + final currentIds = transactionHistory.transactions.keys.toSet(); + final newIds = transactions.keys.toSet(); - final currentIds = transactionHistory.transactions.keys.toSet(); - final newIds = transactions.keys.toSet(); + currentIds + .difference(newIds) + .forEach((final id) => transactionHistory.transactions.remove(id)); - currentIds - .difference(newIds) - .forEach((final id) => transactionHistory.transactions.remove(id)); - - transactions.forEach((final key, final tx) { - transactionHistory.transactions[key] = tx; - }); - await transactionHistory.save(); - _isTransactionUpdating = false; + 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 _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 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 _refreshBalance({required final bool runAutoShield}) async { + Future _ironwoodMigrate() async { + if (_lastIronwoodMigrateAt != null && + _lastIronwoodMigrateAt!.isAfter(DateTime.now().subtract(const Duration(seconds: 75)))) { + return; + } try { + await ironwoodMigrateMutex.acquire(); + await _$ironwoodMigrate(); + } catch (e, s) { + printV("ironwood migration failed: $e"); + s.toString().split("\n").forEach(printV); + } finally { + ironwoodMigrateMutex.release(); + } + } + + Future _$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 _updateIronwoodActive() async { + bool? active; + try { + active = await runWithCoin( + accountId: accountId, + func: (final coin) => zkool_network.isIronwoodActive(c: coin), + ); + } catch (e) { + printV("isIronwoodActive: $e"); + } + + 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"); + } + } + + if (active == null) { + return; + } + + ironwoodActive = active; + runInAction(() => walletAddresses.setIronwoodActive(active!)); + printV("ironwoodActive=$active (account $accountId)"); + } + + Future _refreshBalance({ + required final bool runAutoShield, + final bool runIronwoodMigrate = true, + }) async { + try { + await _updateIronwoodActive(); final bal = await runWithCoin( accountId: accountId, - func: (coin) => zkool_sync.balance(c: coin), + func: (final coin) async => zkool_sync.balance(c: coin), ); - // 0 - transparent - // 1 - sapling - // 2 - orchard - final confirmedTotal = bal.field0.reduce((final a, final b) => a + b); - - // int knownOutPending = 0; - // ZcashWalletBase.temporarySentTx[accountId]?.forEach((final sTx) { - // knownOutPending += sTx.value; // it's negative - // }); - final confirmedSpendable = confirmedTotal - bal.field0[0]; + // 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 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 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 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 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 birthHeightForNetwork(final ZcashNetwork network) async { + if (network != ZcashNetwork.mainnet) { + return 1; + } + return ZcashHeight.getBlockHeightByTime(DateTime.now()); + } + + static Future 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 $init() async { - if (_initialized) return; - _initialized = true; - printV(r".$init()"); - await zkool_frb.RustLib.init(); + static Future $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 42bd30423..295f2b096 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 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 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 4f4604722..660724e44 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 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 c13ff292b..60c296ee1 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 16fdc6fbf..6c7c64b3c 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 _listFromJson(final dynamic raw, final T Function(Map) 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 84e0d27e6..eeed076a5 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 cf24cb605..60e07e6b4 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 295e3b96a..be45d608d 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 6b469cb1c..e9d4c224f 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 e423dda2b..e5905bb82 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 ab4850caa..d628573e7 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 @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 ], )), ); + }); } } diff --git a/lib/router.dart b/lib/router.dart index 40772c8b1..a130b2667 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 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 createRoute(RouteSettings settings) { isChildWallet: isChildWallet, useTestnet: useTestnet, toggleUseTestnet: toggleTestnet, + zcashNetwork: zcashNetwork, + setZcashNetwork: setZcashNetwork, advancedPrivacySettingsViewModel: getIt.get(param1: type), nodeViewModel: getIt.get(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 383e0d161..76683bb3b 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(); final _passphraseFormKey = GlobalKey(); 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( + 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 887652999..f6a4fb25a 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 { "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 c8f795c5f..de2157bee 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 17ffd334a..e16b0af3a 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 a86890fc7..9ddbe4342 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( + (_) => (_wallet.walletAddresses as ZcashWalletAddresses).ironwoodActive, + (_) => _syncZcashShieldedLabel(), + ); + } + } final WalletBase _wallet; @@ -27,10 +35,21 @@ abstract class ReceiveOptionViewModelBase with Store { @observable ReceivePageOption selectedReceiveOption; + @computed List 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 e2554b225..9a7dfa655 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 75a99bb4d..f2fca86b7 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 ae0f56a6f..7e7cec7cb 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 get allAmountValidator => AllAmountValidator(); - Validator get addressValidator => - AddressValidator(type: selectedCryptoCurrency, isTestnet: wallet.isTestnet); + Validator get addressValidator => AddressValidator( + type: selectedCryptoCurrency, + isTestnet: wallet.isTestnet, + network: wallet.walletInfo.network, + ); Validator get textValidator => TextValidator(); diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart index e83feb90c..df9c72721 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 321a331ae..eeb2bd476 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 cb2f3ed7f..f7fa7c7c7 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 1f9557170..467ad065a 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 1284aa335..9dff31bb0 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 000000000..4508711d5 --- /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 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 1628abbcb..348f1c970 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); From fe3a702e52b278ce0e87c78ec55b38fe3bf9eebc Mon Sep 17 00:00:00 2001 From: cyan Date: Mon, 27 Jul 2026 11:08:23 +0200 Subject: [PATCH 2/3] fix: zkool2 (#3445) --- cw_zcash/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cw_zcash/pubspec.yaml b/cw_zcash/pubspec.yaml index eeed076a5..40fdd5019 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: 97d18db966c061a536fa9056efb3756f4b9aa89d + ref: 6329d4aacb4568ee80ff84baba5ea93b07ae1013 path_provider: any sqflite_common_ffi: any From a612135046cc8731d31f20cad63b3960b9bb8ee8 Mon Sep 17 00:00:00 2001 From: Omar Date: Mon, 27 Jul 2026 13:20:52 +0200 Subject: [PATCH 3/3] update build numbers fix cw_zcash import --- ios/Podfile.lock | 8 +------- lib/view_model/dashboard/receive_option_view_model.dart | 3 +-- lib/zcash/cw_zcash.dart | 5 +++++ scripts/android/app_env.sh | 8 ++++---- scripts/ios/app_env.sh | 8 ++++---- tool/configure.dart | 2 ++ 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index be45d608d..b7e67acbf 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -54,8 +54,6 @@ 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) @@ -167,7 +165,6 @@ 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`) @@ -234,8 +231,6 @@ 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: @@ -311,7 +306,6 @@ 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 @@ -349,4 +343,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: b08d7f4c8d9c64f5648599a30caea878b0f7616a -COCOAPODS: 1.17.0 +COCOAPODS: 1.16.2 diff --git a/lib/view_model/dashboard/receive_option_view_model.dart b/lib/view_model/dashboard/receive_option_view_model.dart index 9ddbe4342..0f3afeff9 100644 --- a/lib/view_model/dashboard/receive_option_view_model.dart +++ b/lib/view_model/dashboard/receive_option_view_model.dart @@ -3,7 +3,6 @@ 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'; @@ -22,7 +21,7 @@ abstract class ReceiveOptionViewModelBase with Store { : ReceivePageOption.mainnet) { if (_wallet.type == WalletType.zcash) { reaction( - (_) => (_wallet.walletAddresses as ZcashWalletAddresses).ironwoodActive, + (_) => zcash!.ironwoodActive(_wallet.walletAddresses), (_) => _syncZcashShieldedLabel(), ); } diff --git a/lib/zcash/cw_zcash.dart b/lib/zcash/cw_zcash.dart index 9dff31bb0..76ecb5ca6 100644 --- a/lib/zcash/cw_zcash.dart +++ b/lib/zcash/cw_zcash.dart @@ -208,6 +208,11 @@ class CWZcash extends Zcash { @override Future rescanInternalChange(WalletBase wallet) async {} + + @override + bool ironwoodActive(WalletAddresses walletAddresses) { + return (walletAddresses as ZcashWalletAddresses).ironwoodActive; + } } const wordList = [ diff --git a/scripts/android/app_env.sh b/scripts/android/app_env.sh index e3a035a38..3e7784b97 100644 --- a/scripts/android/app_env.sh +++ b/scripts/android/app_env.sh @@ -14,15 +14,15 @@ TYPES=($MONERO_COM $CAKEWALLET) APP_ANDROID_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="6.3.2" -MONERO_COM_BUILD_NUMBER=4163 +MONERO_COM_VERSION="6.4.0" +MONERO_COM_BUILD_NUMBER=4164 MONERO_COM_BUNDLE_ID="com.monero.app" MONERO_COM_PACKAGE="com.monero.app" MONERO_COM_SCHEME="monero.com" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="6.3.2" -CAKEWALLET_BUILD_NUMBER=4418 +CAKEWALLET_VERSION="6.4.0" +CAKEWALLET_BUILD_NUMBER=4419 CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet" CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet" CAKEWALLET_SCHEME="cakewallet" diff --git a/scripts/ios/app_env.sh b/scripts/ios/app_env.sh index 2771e5dcc..1c9dd1464 100644 --- a/scripts/ios/app_env.sh +++ b/scripts/ios/app_env.sh @@ -12,13 +12,13 @@ TYPES=($MONERO_COM $CAKEWALLET) APP_IOS_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="6.3.2" -MONERO_COM_BUILD_NUMBER=169 +MONERO_COM_VERSION="6.4.0" +MONERO_COM_BUILD_NUMBER=170 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="6.3.2" -CAKEWALLET_BUILD_NUMBER=429 +CAKEWALLET_VERSION="6.4.0" +CAKEWALLET_BUILD_NUMBER=433 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" diff --git a/tool/configure.dart b/tool/configure.dart index 348f1c970..6621fc21a 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -1704,6 +1704,7 @@ import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/receive_page_option.dart'; +import 'package:cw_core/wallet_addresses.dart'; """; const zcashCWHeaders = """ @@ -1768,6 +1769,7 @@ abstract class Zcash { Future getHeightByDate(DateTime date); bool showMissingFundsCard(WalletBase wallet); Future rescanInternalChange(WalletBase wallet); + bool ironwoodActive(WalletAddresses walletAddresses); } """;