Ywallet zcash (#2810)

* feat(zcash): initial commit - initial work on cw_zcash - use single .db for everything zcash to speed up sync and scanning - implement silent autoshielding of non-orchard funds - use FFI for everything to fetch the data - add support for passphrase by adding extra word to seed phrase - enforce lints on CI for cw_zcash directory - use correct zcash name, and call transparent zcash tZEC - minor cleanups outsite of cw_zcash - gitmodules, gitignore - db debug - fix broken addressPageType in WalletInfo - enable debug options in kDebugMode and kProfileMode * feat: T address rotation (and cache) wip: minor fixes * wip: shield tx list * fix fiat amounts fix derrivation of rotation account addresses screen for zcash block height fix T address shields improvements Amount in disposable T shields UI improvements show pending outgoing tx in tx history * fix null check on passphrase page fix tx showing as pending rename auto shield txs fixed send all to include fee use rotating address in addressForExchange wrap all calls of WarpApi.getLatestHeight in try catch to prevent leaking errors other minor fixes from notion * wallet group debug cache sent tx address wallet T address rotation fixes automatically pick the fee for auto-shield * fix T address cache deserialization fix autoshield icon fix autoshield text getCachedDestinationAddress fix send address cache * address comments from review * wip: iOS fix * fix swap * Allow cakepay payment from zcash wallet * fix: ios (I'm stupid) fix: reorder addresses to make orchard default fix: send all balance * 5.6.6 bump fix: ios apple review add transport error to ignored exceptions * fix: leaving dust behind when sweeping all * Change wallets order * fix: reflect balance of pending out txs fix: restore height support * linux support (disabled) * fix: date picker for zcash on restore screen fix: build script for linux match WarpApi.warpSync args with upstream * fix: linter script * ensure all exchange providers support ZEC (#2802) * ensure all exchange providers support ZEC * remove zaddr and tzec currencies and refactor providers * Update lib/exchange/provider/near_Intents_exchange_provider.dart * add back old zcash currencies for backward compatibility, thanks Czarek * minor fix [skip ci] --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * Update android/app/src/main/AndroidManifestBase.xml [skip ci] * Update ios/Runner/InfoBase.plist [skip ci] * fix: remove unused code fix: macos fix: disable on linux and macos * update build numbers [skip ci] * Add zcash to birdpay --------- Co-authored-by: Czarek Nakamoto <cyjan@mrcyjanek.net> Co-authored-by: Serhii <borodenko.sv@gmail.com>

Omar Hatem committed Jan 16, 2026 at 03:16 UTC ef9b0fbae680ee14fbb2acbb4b7797e156daace6
141 files changed +5664 -284
.github/workflows/pr_test_build_android.yml
-1
@@ -4,7 +4,6 @@ on:
4 pull_request:
5
6 jobs:
7 -
7 # -----------------------------------------
8 # PATH A: Internal PRs
9 internal-build:
.github/workflows/pr_test_build_linux.yml
+22 -8
@@ -25,7 +25,7 @@ jobs:
25
26 steps:
27 - name: Fix github actions messing up $HOME...
28 - run: 'echo HOME=/root | sudo tee -a $GITHUB_ENV'
28 + run: "echo HOME=/root | sudo tee -a $GITHUB_ENV"
29 - uses: actions/checkout@v4
30 with:
31 ref: ${{ github.event.pull_request.head.sha }}
@@ -94,6 +94,13 @@ jobs:
94 rm ./reown_flutter.tar.gz
95 popd
96
97 + - name: Build Zcash Lib
98 + run: |
99 + set -x -e
100 + pushd scripts/linux
101 + ./build_zcash.sh
102 + popd
103 +
104 - name: Execute Build and Setup Commands
105 run: |
106 pushd scripts/linux
@@ -108,7 +115,7 @@ jobs:
115 wget https://github.com/MrCyjaneK/monero_c/releases/download/v0.18.4.0-RC9/release-bundle.zip
116 unzip release-bundle.zip
117 rm release-bundle.zip
111 - unxz -fv release/*/*.xz
118 + unxz -fv release/*/*.xz
119 popd
120
121 - name: Build Bitbox Flutter
@@ -138,7 +145,11 @@ jobs:
145 - name: Generate localization
146 run: |
147 dart run tool/generate_localization.dart
141 -
148 + - name: Workaround zcash
149 + run: |
150 + mkdir -p scripts/zcash_lib/assets/
151 + touch scripts/zcash_lib/assets/sapling-spend.params
152 + touch scripts/zcash_lib/assets/sapling-output.params
153 - name: Build linux
154 run: |
155 flutter build linux --dart-define=hasDevOptions=true --release
@@ -154,15 +165,18 @@ jobs:
165 with:
166 path: ${{ github.workspace }}/build/linux/x64/release/cakewallet_linux.zip
167 name: cakewallet_linux
168 + - name: Verify lint
169 + run: |
170 + exec env ABORT_ON_CHANGE=true ./scripts/lint.sh
171
172 - name: Prepare virtual desktop
173 if: ${{ contains(env.message, 'run tests') }}
174 run: |
161 - nohup Xvfb :99 -screen 0 720x1280x16 &
162 - echo DISPLAY=:99 | sudo tee -a $GITHUB_ENV
163 - dbus-daemon --system --fork
164 - nohup NetworkManager &
165 - nohup ffmpeg -framerate 60 -video_size 720x1280 -f x11grab -i :99 -c:v libx264 -c:a aac /opt/screen_grab.mkv &
175 + nohup Xvfb :99 -screen 0 720x1280x16 &
176 + echo DISPLAY=:99 | sudo tee -a $GITHUB_ENV
177 + dbus-daemon --system --fork
178 + nohup NetworkManager &
179 + nohup ffmpeg -framerate 60 -video_size 720x1280 -f x11grab -i :99 -c:v libx264 -c:a aac /opt/screen_grab.mkv &
180
181 # Note for people adding tests:
182 # - Tests are ran on Linux, with some things being mocked out.
.github/workflows/reusable-build.yml
+10 -4
@@ -40,7 +40,7 @@ jobs:
40
41 steps:
42 - name: Fix github actions messing up $HOME...
43 - run: 'echo HOME=/root | sudo tee -a $GITHUB_ENV'
43 + run: "echo HOME=/root | sudo tee -a $GITHUB_ENV"
44 - uses: actions/checkout@v4
45 with:
46 ref: ${{ inputs.ref }}
@@ -112,7 +112,7 @@ jobs:
112 # cleaning
113 rm -rf reown_flutter
114 rm -f reown_flutter.tar.gz
115 -
115 +
116 wget https://github.com/cake-tech/reown_flutter/releases/download/v0.0.4/reown_flutter-v0.0.4.tar.gz -O reown_flutter.tar.gz
117 mkdir reown_flutter
118 pushd reown_flutter
@@ -135,7 +135,7 @@ jobs:
135 wget https://github.com/MrCyjaneK/monero_c/releases/download/v0.18.4.0-RC9/release-bundle.zip
136 unzip release-bundle.zip
137 rm release-bundle.zip
138 - unxz -fv release/*/*.xz
138 + unxz -fv release/*/*.xz
139 popd
140
141 - name: Build Bitbox Flutter
@@ -144,6 +144,12 @@ jobs:
144 pushd scripts
145 ./build_bitbox_flutter.sh
146 popd
147 + - name: Build Zcash Lib
148 + run: |
149 + set -x -e
150 + pushd scripts/android
151 + ./build_zcash.sh
152 + popd
153
154 - name: Install Flutter dependencies
155 run: |
@@ -236,4 +242,4 @@ jobs:
242 cd build/app/outputs/flutter-apk
243 for i in arm64-v8a x86_64; do
244 ../../../../scripts/android/check_16kb_align.sh app-$i-release.apk
239 - done
\ No newline at end of file
245 + done
.gitignore
+7 -2
@@ -144,6 +144,7 @@ lib/dogecoin/dogecoin.dart
144 lib/base/base.dart
145 lib/arbitrum/arbitrum.dart
146 lib/evm/evm.dart
147 +lib/zcash/zcash.dart
148
149 ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
150 ios/Runner/Assets.xcassets/AppIcon.appiconset/*.png
@@ -151,7 +152,7 @@ ios/Runner/Assets.xcassets/AppIcon.appiconset/*.png
152 ios/Runner/Info.plist
153 android/app/src/main/res/mipmap-*
154 android/app/src/main/res/drawable/ic_launcher.png
154 -!android/app/src/main/res/drawable/launch_background.xml
155 +!android/app/src/main/res/drawable/launch_background.xml
156 !android/app/src/main/res/values/styles.xml
157 assets/images/app_logo.png
158
@@ -215,4 +216,8 @@ flatpak-build/
216 **/linux/flutter/generated_plugins.cmake
217
218 scripts/torch_dart/
218 -fiat-check-output.txt
\ No newline at end of file
219 +fiat-check-output.txt
220 +scripts/zcash_lib
221 +scripts/android/yttrium
222 +scripts/bitbox_flutter
223 +scripts/reown_flutter
.gitmodules deleted
-6
@@ -1,6 +0,0 @@
1 -[submodule "inject.dart"]
2 - path = inject.dart
3 - url = https://github.com/google/inject.dart
4 -[submodule ".vendor/inject.dart"]
5 - path = .vendor/inject.dart
6 - url = https://github.com/google/inject.dart
android/app/src/main/AndroidManifestBase.xml
+4 -2
@@ -39,7 +39,7 @@
39 android:versionName="__versionName__"
40 android:requestLegacyExternalStorage="true"
41 android:extractNativeLibs="true">
42 - <service
42 + <service
43 android:name="androidx.work.impl.foreground.SystemForegroundService"
44 android:foregroundServiceType="dataSync"
45 android:exported="false" />
@@ -112,6 +112,8 @@
112 <data android:scheme="arbitrum" />
113 <data android:scheme="arbitrum-wallet" />
114 <data android:scheme="arbitrum_wallet" />
115 + <data android:scheme="zcash" />
116 + <data android:scheme="zcash-wallet" />
117 </intent-filter>
118 <!-- nano-gpt link scheme -->
119 <intent-filter android:autoVerify="true">
@@ -139,7 +141,7 @@
141 android:resource="@xml/provider_paths" />
142 </provider>
143 </application>
142 -
144 +
145 <queries>
146 <intent>
147 <action android:name="android.intent.action.VIEW" />
assets/images/crypto/zcash.webp
Binary files /dev/null and b/assets/images/crypto/zcash.webp differ
assets/images/tx_shield.svg new
+1
@@ -0,0 +1 @@
1 +<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e3e3e3"><path d="M480-80q-139-35-229.5-159.5T160-516v-244l320-120 320 120v244q0 152-90.5 276.5T480-80Zm0-84q104-33 172-132t68-220v-189l-240-90-240 90v189q0 121 68 220t172 132Zm0-316Z"/></svg>
\ No newline at end of file
assets/images/zec_icon.png
Binary files a/assets/images/zec_icon.png and b/assets/images/zec_icon.png differ
assets/images/zec_icon_qr.svg new
+5
@@ -0,0 +1,5 @@
1 +<svg width="45" height="45" viewBox="0 0 45 45" fill="none" xmlns="http://www.w3.org/2000/svg">
2 +<rect x="1" y="1" width="43" height="43" rx="6" fill="#E4A50B"/>
3 +<rect x="1" y="1" width="43" height="43" rx="6" stroke="black" stroke-width="2"/>
4 +<path d="M30.9445 14.7287V11.1141H24.4639V7.13086H20.4807V11.1141H14V15.9084H24.0425L14 29.5436V33.1582H20.4807V37.1309H24.4639V33.1582H30.9445V28.3639H20.8915L30.9445 14.7287Z" fill="white"/>
5 +</svg>
assets/text/Release_Notes.txt
+2
@@ -1 +1,3 @@
1 +Add Zcash wallet
2 +Generic enhancements
3 Bug fixes
\ No newline at end of file
assets/zcash_node_list.yml new
+12
@@ -0,0 +1,12 @@
1 +- uri: zec.rocks:443
2 + is_default: false
3 + useSSL: true
4 + isEnabledForAutoSwitching: true
5 +- uri: zec-node.cakewallet.com:443
6 + is_default: true
7 + useSSL: true
8 + isEnabledForAutoSwitching: true
9 +- uri: 2c4whzg26j6hgjh22rxynj3oig4gm22ga7x74weclujywxye23v3u5id.onion:9067
10 + useSSL: false
11 +- uri: bvp2l442g5ogma7rywzahm7eqyhpp3g26n3gvvxeioqn5csio2ir6myd.onion:9067
12 + useSSL: false
cw_bitcoin/pubspec.lock
+2 -2
@@ -604,8 +604,8 @@ packages:
604 dependency: "direct main"
605 description:
606 path: "packages/ledger-bitcoin"
607 - ref: dc42621f55702d0732681afc2f90bc0047f26633
608 - resolved-ref: dc42621f55702d0732681afc2f90bc0047f26633
607 + ref: "75bba8632c529debf30ee2b1858530009fa6440b"
608 + resolved-ref: "75bba8632c529debf30ee2b1858530009fa6440b"
609 url: "https://github.com/cake-tech/ledger-flutter-plus-plugins"
610 source: git
611 version: "0.0.3"
cw_core/lib/crypto_currency.dart
+11 -7
@@ -62,7 +62,6 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
62 CryptoCurrency.hbar,
63 CryptoCurrency.sc,
64 CryptoCurrency.usdcsol,
65 - CryptoCurrency.zaddr,
65 CryptoCurrency.zec,
66 CryptoCurrency.zen,
67 CryptoCurrency.xvg,
@@ -140,6 +139,10 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
139 xusd,
140 ];
141
142 + // well technically not currencies but since we had them a long time ago and don't want to break
143 + // the app for users who had contacts saved for them
144 + static const zcashCurrencies = [ zec, zaddr, tzec ];
145 +
146 // title, tag (if applicable), fullName (if unique), raw, name, iconPath
147 static const xmr = CryptoCurrency(title: 'XMR', fullName: 'Monero', raw: 0, name: 'xmr', iconPath: 'assets/images/crypto/monero.webp', decimals: 12);
148 static const ada = CryptoCurrency(title: 'ADA', fullName: 'Cardano', raw: 1, name: 'ada', iconPath: 'assets/images/ada_icon.png', decimals: 6);
@@ -186,7 +189,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
189 static const usdc = CryptoCurrency(title: 'USDC', tag: 'ETH', fullName: 'USD Coin', raw: 40, name: 'usdc', iconPath: 'assets/images/crypto/usdc.webp', decimals: 6);
190 static const usdcsol = CryptoCurrency(title: 'USDC', tag: 'SOL', fullName: 'USDC Coin', raw: 41, name: 'usdcsol', iconPath: 'assets/images/crypto/usdc.webp', decimals: 6);
191 static const zaddr = CryptoCurrency(title: 'ZZEC', tag: 'ZEC', fullName: 'Shielded Zcash', raw: 42, name: 'zaddr', iconPath: 'assets/images/zec_icon.png', decimals: 8);
189 - static const zec = CryptoCurrency(title: 'TZEC', tag: 'ZEC', fullName: 'Transparent Zcash', raw: 43, name: 'zec', iconPath: 'assets/images/zec_icon.png', decimals: 8);
192 + static const tzec = CryptoCurrency(title: 'tZEC', tag: 'ZEC', fullName: 'Transparent Zcash', raw: 43, name: 'zec', iconPath: 'assets/images/zec_icon.png', decimals: 8);
193 static const zen = CryptoCurrency(title: 'ZEN', fullName: 'Horizen', raw: 44, name: 'zen', iconPath: 'assets/images/zen_icon.png', decimals: 8);
194 static const xvg = CryptoCurrency(title: 'XVG', fullName: 'Verge', raw: 45, name: 'xvg', iconPath: 'assets/images/xvg_icon.png', decimals: 8);
195
@@ -250,23 +253,24 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
253 static const cbbtc = CryptoCurrency(title: 'CBBTC', tag: 'ETH', fullName: 'Coinbase Wrapped BTC', raw: 103, name: 'cbbtc', iconPath: 'assets/images/cbbtc_icon.png', decimals: 8);
254 static const baseEth = CryptoCurrency(title: 'ETH', tag: 'BASE', fullName: 'Ethereum', raw: 104, name: 'baseth', iconPath: 'assets/images/crypto/base_icon.webp', decimals: 18);
255 static const usde = CryptoCurrency(title: 'USDE', tag: 'BASE', fullName: 'Ethena USDE', raw: 105, name: 'usde', iconPath: 'assets/images/crypto/ethena-usde-logo.png', decimals: 18);
253 - static const arbEth = CryptoCurrency(title: 'ETH', tag: 'ARB', fullName: 'Ethereum (Arbitrum One)', raw: 106, name: 'arbeth', iconPath: 'assets/images/crypto/ethereum.webp', decimals: 18);
254 - static const usdcArb = CryptoCurrency(title: 'USDC', tag: 'ARB', fullName: 'USDC Coin', raw: 107, name: 'usdcarb', iconPath: 'assets/images/crypto/usdc.webp', decimals: 6);
256 + static const arbEth = CryptoCurrency(title: 'ETH', tag: 'ARB', fullName: 'Ethereum (Arbitrum One)', raw: 106, name: 'arbeth', iconPath: 'assets/images/crypto/ethereum.webp', decimals: 18);
257 + static const zec = CryptoCurrency(title: 'ZEC', fullName: 'Zcash', raw: 107, name: 'zec', iconPath: 'assets/images/zec_icon.png', decimals: 8);
258 + static const usdcArb = CryptoCurrency(title: 'USDC', tag: 'ARB', fullName: 'USDC Coin', raw: 108, name: 'usdcarb', iconPath: 'assets/images/crypto/usdc.webp', decimals: 6);
259
260 static final Map<int, CryptoCurrency> _rawCurrencyMap =
257 - [...all, ...havenCurrencies].fold<Map<int, CryptoCurrency>>(<int, CryptoCurrency>{}, (acc, item) {
261 + [...all, ...havenCurrencies, ...zcashCurrencies].fold<Map<int, CryptoCurrency>>(<int, CryptoCurrency>{}, (acc, item) {
262 acc.addAll({item.raw: item});
263 return acc;
264 });
265
266 static final Map<String, CryptoCurrency> _nameCurrencyMap =
263 - [...all, ...havenCurrencies].fold<Map<String, CryptoCurrency>>(<String, CryptoCurrency>{}, (acc, item) {
267 + [...all, ...havenCurrencies, ...zcashCurrencies].fold<Map<String, CryptoCurrency>>(<String, CryptoCurrency>{}, (acc, item) {
268 acc.addAll({item.name: item});
269 return acc;
270 });
271
272 static final Map<String, CryptoCurrency> _fullNameCurrencyMap =
269 - [...all, ...havenCurrencies].fold<Map<String, CryptoCurrency>>(<String, CryptoCurrency>{}, (acc, item) {
273 + [...all, ...havenCurrencies, ...zcashCurrencies].fold<Map<String, CryptoCurrency>>(<String, CryptoCurrency>{}, (acc, item) {
274 if(item.fullName != null){
275 acc.addAll({item.fullName!.toLowerCase(): item});
276 }
cw_core/lib/currency_for_wallet_type.dart
+2
@@ -44,6 +44,8 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type, {bool isTestnet = fal
44 return CryptoCurrency.dcr;
45 case WalletType.dogecoin:
46 return CryptoCurrency.doge;
47 + case WalletType.zcash:
48 + return CryptoCurrency.zec;
49 case WalletType.none:
50 throw Exception(
51 'Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency');
cw_core/lib/db/sqlite.dart
+12 -2
@@ -115,16 +115,26 @@ Future<Map<String, dynamic>> dumpDb() async {
115 }
116 }
117
118 -Future<List<String>> _getTableNames() async {
118 +Future<List<String>> _getTableNames(Database db) async {
119 final tableNames = await db.rawQuery('SELECT name FROM sqlite_master WHERE type = "table"');
120 return tableNames.map((e) => (e["name"]).toString()).toList();
121 }
122
123 Future<Map<String, dynamic>> _dumpDb() async {
124 - final tableNames = await _getTableNames();
124 + final tableNames = await _getTableNames(db);
125 final ret = <String, dynamic>{};
126 for (final tableName in tableNames) {
127 ret[tableName] = await db.query(tableName);
128 }
129 return ret;
130 }
131 +
132 +Future<Map<String, dynamic>> dumpCustomDb(String path) async {
133 + final db = await openDatabase(path);
134 + final tableNames = await _getTableNames(db);
135 + final ret = <String, dynamic>{};
136 + for (final tableName in tableNames) {
137 + ret[tableName] = await db.query(tableName);
138 + }
139 + return ret;
140 +}
\ No newline at end of file
cw_core/lib/node.dart
+16
@@ -38,6 +38,20 @@ class Node extends HiveObject with Keyable {
38 }
39 }
40
41 + @override
42 + String toString() {
43 + return """Node(
44 + uriRaw: $uriRaw,
45 + path: $path,
46 + login: $login,
47 + password: $password,
48 + useSSL: $useSSL,
49 + trusted: $trusted,
50 + socksProxyAddress: $socksProxyAddress,
51 + isEnabledForAutoSwitching: $isEnabledForAutoSwitching,
52 + })""";
53 + }
54 +
55 Node.fromMap(Map<String, Object?> map)
56 : uriRaw = map['uri'] as String? ?? '',
57 path = map['path'] as String? ?? '',
@@ -94,6 +108,7 @@ class Node extends HiveObject with Keyable {
108 Uri get uri {
109 switch (type) {
110 case WalletType.monero:
111 + case WalletType.zcash:
112 case WalletType.haven:
113 case WalletType.wownero:
114 return Uri.http(uriRaw, '');
@@ -176,6 +191,7 @@ class Node extends HiveObject with Keyable {
191 case WalletType.solana:
192 case WalletType.tron:
193 case WalletType.dogecoin:
194 + case WalletType.zcash:
195 return requestElectrumServer();
196 case WalletType.zano:
197 return requestZanoNode();
cw_core/lib/pathForWallet.dart
+13 -3
@@ -1,13 +1,23 @@
1 import 'dart:io';
2 import 'package:cw_core/root_dir.dart';
3 import 'package:cw_core/wallet_type.dart';
4 -import 'package:path_provider/path_provider.dart';
4
6 -Future<String> pathForWalletDir({required String name, required WalletType type}) async {
5 +Future<String> pathForWalletTypeDir({required WalletType type}) async {
6 final root = await getAppDir();
7 final prefix = walletTypeToString(type).toLowerCase();
8 final walletsDir = Directory('${root.path}/wallets');
10 - final walletDire = Directory('${walletsDir.path}/$prefix/$name');
9 + final walletDir = Directory('${walletsDir.path}/$prefix');
10 +
11 + if (!walletDir.existsSync()) {
12 + walletDir.createSync(recursive: true);
13 + }
14 +
15 + return walletDir.path;
16 +}
17 +
18 +Future<String> pathForWalletDir({required String name, required WalletType type}) async {
19 + final typeRoot = await pathForWalletTypeDir(type: type);
20 + final walletDire = Directory('${typeRoot}/$name');
21
22 if (!walletDire.existsSync()) {
23 walletDire.createSync(recursive: true);
cw_core/lib/wallet_info.dart
+4
@@ -332,6 +332,7 @@ class WalletInfo {
332 this.hashedWalletIdentifier,
333 this.isNonSeedWallet,
334 this.sortOrder,
335 + this.addressPageType,
336 ) : _yatLastUsedAddressController = StreamController<String>.broadcast();
337
338 factory WalletInfo.external({
@@ -374,6 +375,7 @@ class WalletInfo {
375 hashedWalletIdentifier,
376 isNonSeedWallet ?? false,
377 sortOrder ?? 0,
378 + null,
379 );
380 }
381
@@ -546,6 +548,7 @@ class WalletInfo {
548 "hashedWalletIdentifier": hashedWalletIdentifier,
549 "isNonSeedWallet": isNonSeedWallet ? 1 : 0,
550 "sortOrder": sortOrder,
551 + "addressPageType": addressPageType,
552 };
553
554 factory WalletInfo.fromJson(Map<String, dynamic> json) {
@@ -569,6 +572,7 @@ class WalletInfo {
572 json['hashedWalletIdentifier'] as String?,
573 (json['isNonSeedWallet'] as int) == 1,
574 json['sortOrder'] as int? ?? 0,
575 + json['addressPageType'] as String? ?? null,
576 );
577 }
578
cw_core/lib/wallet_info_legacy.dart
+1
@@ -270,6 +270,7 @@ class WalletInfo extends HiveObject {
270 hashedWalletIdentifier,
271 isNonSeedWallet,
272 0,
273 + addressPageType,
274 );
275 final wiId = await walletInfo.save();
276 for (final address in usedAddresses ?? <String>[]) {
cw_core/lib/wallet_type.dart
+14
@@ -21,6 +21,7 @@ const walletTypes = [
21 WalletType.dogecoin,
22 WalletType.base,
23 WalletType.arbitrum,
24 + WalletType.zcash,
25 ];
26
27 @HiveType(typeId: WALLET_TYPE_TYPE_ID)
@@ -78,6 +79,9 @@ enum WalletType {
79
80 @HiveField(17)
81 arbitrum,
82 +
83 + @HiveField(18)
84 + zcash,
85 }
86
87 int serializeToInt(WalletType type) {
@@ -116,6 +120,8 @@ int serializeToInt(WalletType type) {
120 return 15;
121 case WalletType.arbitrum:
122 return 16;
123 + case WalletType.zcash:
124 + return 17;
125 case WalletType.none:
126 return -1;
127 }
@@ -157,6 +163,8 @@ WalletType deserializeFromInt(int raw) {
163 return WalletType.base;
164 case 16:
165 return WalletType.arbitrum;
166 + case 17:
167 + return WalletType.zcash;
168 default:
169 throw Exception('Unexpected token: $raw for WalletType deserializeFromInt');
170 }
@@ -198,6 +206,8 @@ String walletTypeToString(WalletType type) {
206 return 'Base';
207 case WalletType.arbitrum:
208 return 'Arbitrum';
209 + case WalletType.zcash:
210 + return 'Zcash';
211 case WalletType.none:
212 return '';
213 }
@@ -239,6 +249,8 @@ String walletTypeToDisplayName(WalletType type) {
249 return 'Base (BASE)';
250 case WalletType.arbitrum:
251 return 'Arbitrum (ARB)';
252 + case WalletType.zcash:
253 + return 'Zcash (ZEC)';
254 case WalletType.none:
255 return '';
256 }
@@ -281,6 +293,8 @@ WalletType? cryptoCurrencyToWalletType(CryptoCurrency type) {
293 return WalletType.decred;
294 case CryptoCurrency.doge:
295 return WalletType.dogecoin;
296 + case CryptoCurrency.zec:
297 + return WalletType.zcash;
298 default:
299 return null;
300 }
cw_decred/pubspec.yaml
+1 -2
@@ -6,10 +6,9 @@ author: Cake Wallet
6 homepage: https://cakewallet.com
7
8 environment:
9 - sdk: '>=3.2.0-0 <4.0.0'
9 + sdk: ">=3.2.0-0 <4.0.0"
10 flutter: ">=3.19.0"
11
12 -
12 dependencies:
13 flutter:
14 sdk: flutter
cw_zcash/.gitignore new
+7
@@ -0,0 +1,7 @@
1 +# https://dart.dev/guides/libraries/private-files
2 +# Created by `dart pub`
3 +.dart_tool/
4 +
5 +# Avoid committing pubspec.lock for library packages; see
6 +# https://dart.dev/guides/libraries/private-files#pubspeclock.
7 +pubspec.lock
cw_zcash/CHANGELOG.md new
+3
@@ -0,0 +1,3 @@
1 +## 1.0.0
2 +
3 +- Initial version.
cw_zcash/README.md new
+1
@@ -0,0 +1 @@
1 +# cw_zcash
cw_zcash/analysis_options.yaml new
+31
@@ -0,0 +1,31 @@
1 +analyzer:
2 + errors:
3 + implementation_imports: ignore
4 + must_be_immutable: ignore
5 + overridden_fields: ignore
6 + exclude:
7 + - "**.g.dart"
8 + - "external/**"
9 +
10 +linter:
11 + rules:
12 + unawaited_futures: true
13 + avoid_print: false
14 + parameter_assignments: true
15 + prefer_const_declarations: true
16 + prefer_final_fields: true
17 + prefer_final_in_for_each: true
18 + prefer_final_locals: true
19 + prefer_final_parameters: true
20 + avoid_void_async: true
21 + require_trailing_commas: true
22 + sort_child_properties_last: true
23 + sort_constructors_first: true
24 + sort_unnamed_constructors_first: true
25 + unnecessary_async: true
26 + unnecessary_await_in_return: true
27 + unnecessary_string_interpolations: true
28 + always_declare_return_types: true
29 +
30 +formatter:
31 + page_width: 100
cw_zcash/lib/cw_zcash.dart new
+15
@@ -0,0 +1,15 @@
1 +/// Support for doing something awesome.
2 +///
3 +/// More dartdocs go here.
4 +library;
5 +
6 +export 'src/zcash_balance.dart';
7 +export 'src/zcash_mnemonic_is_incorrect_exception.dart';
8 +export 'src/zcash_transaction_info.dart';
9 +export 'src/zcash_transactions.dart';
10 +export 'src/zcash_wallet.dart';
11 +export 'src/zcash_wallet_service.dart';
12 +export 'src/models.dart';
13 +export 'src/zcash_transaction_credentials.dart';
14 +export 'src/pending_zcash_transaction.dart';
15 +export 'src/zcash_receive_page_options.dart';
cw_zcash/lib/src/models.dart new
+41
@@ -0,0 +1,41 @@
1 +import 'package:cw_core/wallet_credentials.dart';
2 +
3 +class ZcashNewWalletCredentials extends WalletCredentials {
4 + ZcashNewWalletCredentials({
5 + required final String name,
6 + final String? password,
7 + required final String? passphrase,
8 + final String? mnemonic,
9 + final int? seedPhraseLength,
10 + }) : super(
11 + name: name,
12 + password: password,
13 + passphrase: passphrase,
14 + seedPhraseLength: seedPhraseLength,
15 + ) {
16 + this.mnemonic = mnemonic;
17 + }
18 +
19 + String? mnemonic;
20 +}
21 +
22 +class ZcashFromSeedWalletCredentials extends WalletCredentials {
23 + ZcashFromSeedWalletCredentials({
24 + required final String name,
25 + final String? password,
26 + required final String? passphrase,
27 + required this.seed,
28 + required super.height,
29 + }) : super(name: name, password: password, passphrase: passphrase);
30 + final String? seed;
31 +}
32 +
33 +class ZcashFromKeysWalletCredentials extends WalletCredentials {
34 + ZcashFromKeysWalletCredentials({
35 + required final String name,
36 + final String? password,
37 + required final String? passphrase,
38 + required this.privateKey,
39 + }) : super(name: name, password: password, passphrase: passphrase);
40 + final String? privateKey;
41 +}
cw_zcash/lib/src/pending_zcash_transaction.dart new
+103
@@ -0,0 +1,103 @@
1 +import 'dart:convert';
2 +import 'dart:math';
3 +
4 +import 'package:cw_core/output_info.dart';
5 +import 'package:cw_core/pending_transaction.dart';
6 +import 'package:cw_zcash/cw_zcash.dart';
7 +import 'package:cw_core/currency_for_wallet_type.dart';
8 +import 'package:cw_core/wallet_type.dart';
9 +import 'package:cw_zcash/src/zcash_taddress_rotation.dart';
10 +import 'package:warp_api/data_fb_generated.dart';
11 +import 'package:warp_api/warp_api.dart';
12 +
13 +class PendingZcashTransaction with PendingTransaction {
14 + PendingZcashTransaction({
15 + required this.zcashWallet,
16 + required this.credentials,
17 + required this.txPlan,
18 + required this.fee,
19 + required this.availableBalance,
20 + });
21 +
22 + final ZcashWallet zcashWallet;
23 + final ZcashTransactionCredentials credentials;
24 + final String txPlan;
25 + String? _txId;
26 + final int availableBalance;
27 +
28 + @override
29 + String get id => _txId ?? '';
30 +
31 + @override
32 + String get hex => '';
33 +
34 + @override
35 + String get amountFormatted {
36 + return walletTypeToCryptoCurrency(WalletType.zcash).formatAmount(BigInt.from(totalAmount));
37 + }
38 +
39 + int get totalAmount {
40 + final isAll = credentials.outputs.fold<bool>(false, (final a, final b) => a || (b.sendAll));
41 + if (isAll) {
42 + return availableBalance;
43 + }
44 + return credentials.outputs.fold<int>(
45 + 0,
46 + (final a, final b) => a + (b.formattedCryptoAmount ?? 0),
47 + );
48 + }
49 +
50 + @override
51 + String get feeFormatted =>
52 + '$feeFormattedValue ${walletTypeToCryptoCurrency(WalletType.zcash).title}';
53 +
54 + @override
55 + late String feeFormattedValue = walletTypeToCryptoCurrency(
56 + WalletType.zcash,
57 + ).formatAmount(BigInt.from(fee));
58 +
59 + int fee;
60 +
61 + @override
62 + Future<void> commit() async {
63 + _txId = await ZcashWalletService.runInDbMutex(
64 + () => WarpApi.signAndBroadcast(ZcashWalletBase.coin, zcashWallet.accountId, txPlan),
65 + );
66 + ZcashWalletBase.temporarySentTx[zcashWallet.accountId] ??= [];
67 + ZcashWalletBase.temporarySentTx[zcashWallet.accountId]?.add(
68 + ShieldedTx(
69 + base64.decode(
70 + ZcashTaddressRotation.flatBuffersPack(
71 + ShieldedTxT(
72 + id: Random().nextInt(pow(2, 32).toInt()),
73 + txId: _txId,
74 + height: 0,
75 + timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000,
76 + value: -totalAmount,
77 + ).pack,
78 + ),
79 + ),
80 + ),
81 + );
82 + await ZcashTransactionInfo.addCachedDestinationAddress(
83 + _txId ?? '',
84 + credentials.outputs.reduce((final o1, final o2) {
85 + return OutputInfo(
86 + address: o1.address + "," + o2.address,
87 + sendAll: false,
88 + isParsedAddress: false,
89 + );
90 + }).address,
91 + );
92 + await zcashWallet.updateTransactions();
93 + await zcashWallet.updateBalance();
94 + }
95 +
96 + @override
97 + Future<Map<String, String>> commitUR() {
98 + throw UnimplementedError('UR not supported for Zcash');
99 + }
100 +
101 + @override
102 + bool shouldCommitUR() => false;
103 +}
cw_zcash/lib/src/util/crc32.dart new
+296
@@ -0,0 +1,296 @@
1 +/// CRC32
2 +/// Copyright (C) 2012, Kai Sellgren
3 +/// Licensed under the MIT License.
4 +/// http://www.opensource.org/licenses/mit-license.php
5 +/// source: https://raw.githubusercontent.com/kaisellgren/CRC32/refs/heads/master/lib/crc32.dart
6 +// ignore_for_file: parameter_assignments
7 +
8 +import 'dart:convert';
9 +
10 +/// Computes Cyclic Redundancy Check values.
11 +class CRC32 {
12 + /// Computes a CRC32 value for the given input.
13 + ///
14 + /// The return value is an unsigned integer.
15 + ///
16 + /// You may optionally specify the beginning CRC value.
17 + static int compute(dynamic input, [int crc = 0]) {
18 + if (input == null) throw new ArgumentError.notNull('input');
19 + if (input is String) input = utf8.encode(input);
20 +
21 + crc = crc ^ (0xffffffff);
22 +
23 + for (final byte in input) {
24 + if (!(byte >= -128 && byte <= 255))
25 + throw new FormatException("Invalid value in input: $byte");
26 +
27 + final x = CRC32._table[(crc ^ byte) & 0xff];
28 + crc = (crc & 0xffffffff) >> 8; // crc >>> 8 (32-bit unsigned integer)
29 + crc ^= x;
30 + }
31 +
32 + crc = crc ^ (0xffffffff);
33 +
34 + // Turns the signed integer into an unsigned integer.
35 + return crc & 0xffffffff;
36 + }
37 +
38 + static const _table = const [
39 + 0x00000000,
40 + 0x77073096,
41 + 0xEE0E612C,
42 + 0x990951BA,
43 + 0x076DC419,
44 + 0x706AF48F,
45 + 0xE963A535,
46 + 0x9E6495A3,
47 + 0x0EDB8832,
48 + 0x79DCB8A4,
49 + 0xE0D5E91E,
50 + 0x97D2D988,
51 + 0x09B64C2B,
52 + 0x7EB17CBD,
53 + 0xE7B82D07,
54 + 0x90BF1D91,
55 + 0x1DB71064,
56 + 0x6AB020F2,
57 + 0xF3B97148,
58 + 0x84BE41DE,
59 + 0x1ADAD47D,
60 + 0x6DDDE4EB,
61 + 0xF4D4B551,
62 + 0x83D385C7,
63 + 0x136C9856,
64 + 0x646BA8C0,
65 + 0xFD62F97A,
66 + 0x8A65C9EC,
67 + 0x14015C4F,
68 + 0x63066CD9,
69 + 0xFA0F3D63,
70 + 0x8D080DF5,
71 + 0x3B6E20C8,
72 + 0x4C69105E,
73 + 0xD56041E4,
74 + 0xA2677172,
75 + 0x3C03E4D1,
76 + 0x4B04D447,
77 + 0xD20D85FD,
78 + 0xA50AB56B,
79 + 0x35B5A8FA,
80 + 0x42B2986C,
81 + 0xDBBBC9D6,
82 + 0xACBCF940,
83 + 0x32D86CE3,
84 + 0x45DF5C75,
85 + 0xDCD60DCF,
86 + 0xABD13D59,
87 + 0x26D930AC,
88 + 0x51DE003A,
89 + 0xC8D75180,
90 + 0xBFD06116,
91 + 0x21B4F4B5,
92 + 0x56B3C423,
93 + 0xCFBA9599,
94 + 0xB8BDA50F,
95 + 0x2802B89E,
96 + 0x5F058808,
97 + 0xC60CD9B2,
98 + 0xB10BE924,
99 + 0x2F6F7C87,
100 + 0x58684C11,
101 + 0xC1611DAB,
102 + 0xB6662D3D,
103 + 0x76DC4190,
104 + 0x01DB7106,
105 + 0x98D220BC,
106 + 0xEFD5102A,
107 + 0x71B18589,
108 + 0x06B6B51F,
109 + 0x9FBFE4A5,
110 + 0xE8B8D433,
111 + 0x7807C9A2,
112 + 0x0F00F934,
113 + 0x9609A88E,
114 + 0xE10E9818,
115 + 0x7F6A0DBB,
116 + 0x086D3D2D,
117 + 0x91646C97,
118 + 0xE6635C01,
119 + 0x6B6B51F4,
120 + 0x1C6C6162,
121 + 0x856530D8,
122 + 0xF262004E,
123 + 0x6C0695ED,
124 + 0x1B01A57B,
125 + 0x8208F4C1,
126 + 0xF50FC457,
127 + 0x65B0D9C6,
128 + 0x12B7E950,
129 + 0x8BBEB8EA,
130 + 0xFCB9887C,
131 + 0x62DD1DDF,
132 + 0x15DA2D49,
133 + 0x8CD37CF3,
134 + 0xFBD44C65,
135 + 0x4DB26158,
136 + 0x3AB551CE,
137 + 0xA3BC0074,
138 + 0xD4BB30E2,
139 + 0x4ADFA541,
140 + 0x3DD895D7,
141 + 0xA4D1C46D,
142 + 0xD3D6F4FB,
143 + 0x4369E96A,
144 + 0x346ED9FC,
145 + 0xAD678846,
146 + 0xDA60B8D0,
147 + 0x44042D73,
148 + 0x33031DE5,
149 + 0xAA0A4C5F,
150 + 0xDD0D7CC9,
151 + 0x5005713C,
152 + 0x270241AA,
153 + 0xBE0B1010,
154 + 0xC90C2086,
155 + 0x5768B525,
156 + 0x206F85B3,
157 + 0xB966D409,
158 + 0xCE61E49F,
159 + 0x5EDEF90E,
160 + 0x29D9C998,
161 + 0xB0D09822,
162 + 0xC7D7A8B4,
163 + 0x59B33D17,
164 + 0x2EB40D81,
165 + 0xB7BD5C3B,
166 + 0xC0BA6CAD,
167 + 0xEDB88320,
168 + 0x9ABFB3B6,
169 + 0x03B6E20C,
170 + 0x74B1D29A,
171 + 0xEAD54739,
172 + 0x9DD277AF,
173 + 0x04DB2615,
174 + 0x73DC1683,
175 + 0xE3630B12,
176 + 0x94643B84,
177 + 0x0D6D6A3E,
178 + 0x7A6A5AA8,
179 + 0xE40ECF0B,
180 + 0x9309FF9D,
181 + 0x0A00AE27,
182 + 0x7D079EB1,
183 + 0xF00F9344,
184 + 0x8708A3D2,
185 + 0x1E01F268,
186 + 0x6906C2FE,
187 + 0xF762575D,
188 + 0x806567CB,
189 + 0x196C3671,
190 + 0x6E6B06E7,
191 + 0xFED41B76,
192 + 0x89D32BE0,
193 + 0x10DA7A5A,
194 + 0x67DD4ACC,
195 + 0xF9B9DF6F,
196 + 0x8EBEEFF9,
197 + 0x17B7BE43,
198 + 0x60B08ED5,
199 + 0xD6D6A3E8,
200 + 0xA1D1937E,
201 + 0x38D8C2C4,
202 + 0x4FDFF252,
203 + 0xD1BB67F1,
204 + 0xA6BC5767,
205 + 0x3FB506DD,
206 + 0x48B2364B,
207 + 0xD80D2BDA,
208 + 0xAF0A1B4C,
209 + 0x36034AF6,
210 + 0x41047A60,
211 + 0xDF60EFC3,
212 + 0xA867DF55,
213 + 0x316E8EEF,
214 + 0x4669BE79,
215 + 0xCB61B38C,
216 + 0xBC66831A,
217 + 0x256FD2A0,
218 + 0x5268E236,
219 + 0xCC0C7795,
220 + 0xBB0B4703,
221 + 0x220216B9,
222 + 0x5505262F,
223 + 0xC5BA3BBE,
224 + 0xB2BD0B28,
225 + 0x2BB45A92,
226 + 0x5CB36A04,
227 + 0xC2D7FFA7,
228 + 0xB5D0CF31,
229 + 0x2CD99E8B,
230 + 0x5BDEAE1D,
231 + 0x9B64C2B0,
232 + 0xEC63F226,
233 + 0x756AA39C,
234 + 0x026D930A,
235 + 0x9C0906A9,
236 + 0xEB0E363F,
237 + 0x72076785,
238 + 0x05005713,
239 + 0x95BF4A82,
240 + 0xE2B87A14,
241 + 0x7BB12BAE,
242 + 0x0CB61B38,
243 + 0x92D28E9B,
244 + 0xE5D5BE0D,
245 + 0x7CDCEFB7,
246 + 0x0BDBDF21,
247 + 0x86D3D2D4,
248 + 0xF1D4E242,
249 + 0x68DDB3F8,
250 + 0x1FDA836E,
251 + 0x81BE16CD,
252 + 0xF6B9265B,
253 + 0x6FB077E1,
254 + 0x18B74777,
255 + 0x88085AE6,
256 + 0xFF0F6A70,
257 + 0x66063BCA,
258 + 0x11010B5C,
259 + 0x8F659EFF,
260 + 0xF862AE69,
261 + 0x616BFFD3,
262 + 0x166CCF45,
263 + 0xA00AE278,
264 + 0xD70DD2EE,
265 + 0x4E048354,
266 + 0x3903B3C2,
267 + 0xA7672661,
268 + 0xD06016F7,
269 + 0x4969474D,
270 + 0x3E6E77DB,
271 + 0xAED16A4A,
272 + 0xD9D65ADC,
273 + 0x40DF0B66,
274 + 0x37D83BF0,
275 + 0xA9BCAE53,
276 + 0xDEBB9EC5,
277 + 0x47B2CF7F,
278 + 0x30B5FFE9,
279 + 0xBDBDF21C,
280 + 0xCABAC28A,
281 + 0x53B39330,
282 + 0x24B4A3A6,
283 + 0xBAD03605,
284 + 0xCDD70693,
285 + 0x54DE5729,
286 + 0x23D967BF,
287 + 0xB3667A2E,
288 + 0xC4614AB8,
289 + 0x5D681B02,
290 + 0x2A6F2B94,
291 + 0xB40BBE37,
292 + 0xC30C8EA1,
293 + 0x5A05DF1B,
294 + 0x2D02EF8D,
295 + ];
296 +}
cw_zcash/lib/src/zcash_balance.dart new
+30
@@ -0,0 +1,30 @@
1 +import 'package:cw_core/balance.dart';
2 +import 'package:cw_core/crypto_currency.dart';
3 +
4 +class ZcashBalance extends Balance {
5 + ZcashBalance({required this.confirmed, required this.unconfirmed, required this.frozen})
6 + : super(confirmed, unconfirmed);
7 +
8 + factory ZcashBalance.zero() => ZcashBalance(confirmed: 0, unconfirmed: 0, frozen: 0);
9 +
10 + final int confirmed;
11 + final int unconfirmed;
12 + final int frozen;
13 +
14 + @override
15 + String get formattedAvailableBalance {
16 + return CryptoCurrency.zec.formatAmount(BigInt.from(confirmed));
17 + }
18 +
19 + @override
20 + String get formattedAdditionalBalance {
21 + if (unconfirmed == 0) return '0.0';
22 + return CryptoCurrency.zec.formatAmount(BigInt.from(unconfirmed));
23 + }
24 +
25 + @override
26 + String get formattedUnAvailableBalance {
27 + if (frozen == 0) return '';
28 + return CryptoCurrency.zec.formatAmount(BigInt.from(frozen));
29 + }
30 +}
cw_zcash/lib/src/zcash_mnemonic_is_incorrect_exception.dart new
+5
@@ -0,0 +1,5 @@
1 +class ZcashMnemonicIsIncorrectException implements Exception {
2 + @override
3 + String toString() =>
4 + 'Zcash mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.';
5 +}
cw_zcash/lib/src/zcash_receive_page_options.dart new
+70
@@ -0,0 +1,70 @@
1 +import 'package:cw_core/receive_page_option.dart';
2 +import 'package:cw_core/utils/print_verbose.dart';
3 +
4 +enum ZcashAddressType {
5 + transparent,
6 + transparentRotated,
7 + shieldedSapling,
8 + shieldedOrchard,
9 + unifiedType,
10 +}
11 +
12 +class ZcashReceivePageOption implements ReceivePageOption {
13 + const ZcashReceivePageOption._(this.type);
14 +
15 + factory ZcashReceivePageOption.fromType(final ZcashAddressType type) {
16 + switch (type) {
17 + case ZcashAddressType.transparent:
18 + return transparent;
19 + case ZcashAddressType.transparentRotated:
20 + return transparentRotated;
21 + case ZcashAddressType.shieldedSapling:
22 + return shieldedSapling;
23 + case ZcashAddressType.shieldedOrchard:
24 + return shieldedOrchard;
25 + case ZcashAddressType.unifiedType:
26 + return unified;
27 + }
28 + }
29 + static const transparent = ZcashReceivePageOption._(ZcashAddressType.transparent);
30 + static const transparentRotated = ZcashReceivePageOption._(ZcashAddressType.transparentRotated);
31 + static const shieldedSapling = ZcashReceivePageOption._(ZcashAddressType.shieldedSapling);
32 + static const shieldedOrchard = ZcashReceivePageOption._(ZcashAddressType.shieldedOrchard);
33 + static const unified = ZcashReceivePageOption._(ZcashAddressType.unifiedType);
34 +
35 + final ZcashAddressType type;
36 +
37 + String get value => switch (type) {
38 + ZcashAddressType.transparent => "Transparent (Static)",
39 + ZcashAddressType.transparentRotated => "Transparent (Disposable)",
40 + ZcashAddressType.shieldedSapling => "Shielded (Sapling)",
41 + ZcashAddressType.shieldedOrchard => "Shielded (Orchard)",
42 + ZcashAddressType.unifiedType => "Unified",
43 + };
44 +
45 + String toString() {
46 + return value;
47 + }
48 +
49 + static const all = [
50 + ZcashReceivePageOption.shieldedOrchard,
51 + ZcashReceivePageOption.shieldedSapling,
52 + ZcashReceivePageOption.unified,
53 + ZcashReceivePageOption.transparentRotated,
54 + ZcashReceivePageOption.transparent,
55 + ];
56 +
57 + ZcashAddressType toType() {
58 + return type;
59 + }
60 +
61 + static ZcashAddressType typeFromString(final String str) {
62 + for (int i = 0; i < ZcashAddressType.values.length; i++) {
63 + if (str == ZcashAddressType.values[i].toString()) {
64 + return ZcashAddressType.values[i];
65 + }
66 + }
67 + printV("Not found for: $str");
68 + return ZcashAddressType.shieldedOrchard;
69 + }
70 +}
cw_zcash/lib/src/zcash_taddress_rotation.dart new
+421
@@ -0,0 +1,421 @@
1 +// create offset wallets for T address rotation.
2 +// Each address is considered one time use, if it receives less than 15000 zatoshi balance will be essentially burned.
3 +//
4 +// This class takes address index as constructor, and will create 10 addresses by default, if any of the addresses gets used
5 +// it will be moved out of the pool
6 +//
7 +// T addresses are generated by creating new wallets with an extra seed word (or appending to "passphrase") the following
8 +// :tgen:${CRC32(seed)}
9 +// It is highly unlikely for users to create a wallet with that specific last word and add index to it
10 +// We could leverage other fields such as native account index alone, but users may want to set that in future
11 +// This solution is rather simple and if standard would get estabilished for this purpose it can be easily
12 +// turned off and replaced with proper solution.
13 +
14 +import 'dart:async';
15 +import 'dart:convert';
16 +import 'dart:io';
17 +
18 +import 'package:cw_core/pathForWallet.dart';
19 +import 'package:cw_core/utils/print_verbose.dart';
20 +import 'package:cw_core/wallet_type.dart';
21 +import 'package:cw_zcash/src/util/crc32.dart';
22 +import 'package:cw_zcash/src/zcash_wallet.dart';
23 +import 'package:cw_zcash/src/zcash_wallet_service.dart';
24 +import 'package:flutter/foundation.dart';
25 +import 'package:warp_api/data_fb_generated.dart';
26 +import 'package:warp_api/warp_api.dart';
27 +import 'package:flat_buffers/flat_buffers.dart' as fb;
28 +import 'package:path/path.dart' as p;
29 +
30 +class ZcashTaddressRotation {
31 + ZcashTaddressRotation(this.accountIndex);
32 + final int accountIndex;
33 +
34 + static bool _isStarted = false;
35 +
36 + static const coin = ZcashWalletBase.coin;
37 + static String seedForOffset(final String seed) {
38 + final seedWords = seed.split(" ");
39 + if ([12, 24].contains(seedWords.length)) {
40 + seedWords.add("");
41 + }
42 + final lastI = seedWords.length - 1;
43 + final crc = CRC32.compute(seed);
44 + seedWords[lastI] += ":tgen:${crc}";
45 + return seedWords.join(" ");
46 + }
47 +
48 + static bool isSeedForWallet(final String mainWallet, final String subWallet) {
49 + return seedForOffset(mainWallet.trim()) == subWallet;
50 + }
51 +
52 + static Map<int, List<Account>> rotationAccounts = {};
53 + static Map<int, List<Account>> rotationAccountsUsable = {};
54 + static Map<int, List<ShieldedTx>> shieldedAccountsTx = {};
55 + static Future<void> init() async {
56 + printV("Deserializing previous state");
57 + if (_isStarted) {
58 + return;
59 + }
60 + _isStarted = true;
61 + final pfw = await pathForWalletTypeDir(type: WalletType.zcash);
62 + try {
63 + final f = File(p.join(pfw, "zec-taddr.json"));
64 + if (!f.existsSync()) {
65 + f.writeAsBytesSync(serialize());
66 + }
67 + deserialize(f.readAsBytesSync());
68 + } catch (e) {
69 + printV("Failed to deserialize T address DB for zcash, it doesn't really matter: $e");
70 + }
71 +
72 + unawaited(
73 + (() async {
74 + await Future.delayed(Duration(seconds: 5));
75 + return _jobRunner();
76 + })(),
77 + );
78 + }
79 +
80 + static Future<void> serializeToFile() async {
81 + final pfw = await pathForWalletTypeDir(type: WalletType.zcash);
82 + final f = File(p.join(pfw, "zec-taddr.json"));
83 + f.writeAsBytesSync(serialize());
84 + }
85 +
86 + static Uint8List serialize() {
87 + final data = {
88 + "rotationAccounts": rotationAccounts.map(
89 + (final k, final v) => MapEntry(
90 + k.toRadixString(16),
91 + v.map((final a) => flatBuffersPack(a.unpack().pack)).toList(),
92 + ),
93 + ),
94 + if (kDebugMode)
95 + "_rotationAccounts": rotationAccounts.map(
96 + (final k, final v) =>
97 + MapEntry(k.toRadixString(16), v.map((final a) => a.toString()).toList()),
98 + ),
99 + "rotationAccountsUsable": rotationAccountsUsable.map(
100 + (final k, final v) => MapEntry(
101 + k.toRadixString(16),
102 + v.map((final a) => flatBuffersPack(a.unpack().pack)).toList(),
103 + ),
104 + ),
105 + if (kDebugMode)
106 + "_rotationAccountsUsable": rotationAccountsUsable.map(
107 + (final k, final v) =>
108 + MapEntry(k.toRadixString(16), v.map((final a) => a.toString()).toList()),
109 + ),
110 + "shieldedAccountsTx": shieldedAccountsTx.map(
111 + (final k, final v) => MapEntry(
112 + k.toRadixString(16),
113 + v.map((final a) => flatBuffersPack(a.unpack().pack)).toList(),
114 + ),
115 + ),
116 + if (kDebugMode)
117 + "_shieldedAccountsTx": shieldedAccountsTx.map(
118 + (final k, final v) =>
119 + MapEntry(k.toRadixString(16), v.map((final a) => a.toString()).toList()),
120 + ),
121 + };
122 +
123 + if (kDebugMode) {
124 + return utf8.encode(JsonEncoder.withIndent(" ").convert(data));
125 + }
126 + return utf8.encode(jsonEncode(data));
127 + }
128 +
129 + static String flatBuffersPack(final int Function(fb.Builder fbBuilder) pack) {
130 + final fbBuilder = fb.Builder();
131 + final offset = pack(fbBuilder);
132 + fbBuilder.finish(offset);
133 + return base64.encode(fbBuilder.buffer);
134 + }
135 +
136 + static void deserialize(final Uint8List bytes) {
137 + // todo: replace keys with sums and reverse in serialize
138 + // final List<String> seeds = [];
139 + // for (int i = 0; i < accounts.length; i++) {
140 + // final acc = accounts[i];
141 + // final backup = WarpApi.getBackup(coin, acc.id);
142 + // await WarpApi.transparentSync(coin, acc.id, syncHeight);
143 + // seeds.add(backup.seed!);
144 + // }
145 + try {
146 + final Map<dynamic, dynamic> data = jsonDecode(utf8.decode(bytes));
147 + rotationAccounts = (data["rotationAccounts"] as Map<String, dynamic>).map(
148 + (final k, final v) => MapEntry(
149 + int.parse(k, radix: 16),
150 + (v as List).map((final a) => Account(atob(a))).toList(),
151 + ),
152 + );
153 + rotationAccountsUsable = (data["rotationAccountsUsable"] as Map<String, dynamic>).map(
154 + (final k, final v) => MapEntry(
155 + int.parse(k, radix: 16),
156 + (v as List).map((final a) => Account(atob(a))).toList(),
157 + ),
158 + );
159 + shieldedAccountsTx = (data["shieldedAccountsTx"] as Map<String, dynamic>).map(
160 + (final k, final v) => MapEntry(
161 + int.parse(k, radix: 16),
162 + (v as List).map((final a) => ShieldedTx(atob(a))).toList(),
163 + ),
164 + );
165 + } catch (e) {
166 + rethrow;
167 + }
168 + }
169 +
170 + static Uint8List atob(final String value) =>
171 + Uint8List.fromList(List<int>.from(base64.decode(value)));
172 +
173 + static Future<void> createAndSweepTAddresses() async {
174 + int chainHeight = 0;
175 + try {
176 + chainHeight = await WarpApi.getLatestHeight(coin);
177 + } catch (e) {
178 + printV("Error getting latest height: $e");
179 + }
180 + final dbHeight = WarpApi.getDbHeight(coin);
181 + final height = dbHeight.unpack();
182 + final syncHeight = height.height;
183 + if (chainHeight != syncHeight) {
184 + printV(
185 + "Waiting for sync to finish: chainHeight(${chainHeight}) != syncHeight(${syncHeight})",
186 + );
187 + return;
188 + }
189 + final Map<int, List<ShieldedTx>> newShieldedAccountsTx = {};
190 + final accounts = WarpApi.getAccountList(coin);
191 + if (accounts.isEmpty) return;
192 + final Map<int, String> seeds = {};
193 + for (int i = 0; i < accounts.length; i++) {
194 + final acc = accounts[i];
195 + final backup = WarpApi.getBackup(coin, acc.id);
196 + await WarpApi.transparentSync(coin, acc.id, syncHeight);
197 + seeds[acc.id] = backup.seed!;
198 + }
199 + for (int i = 0; i < accounts.length; i++) {
200 + final seed = seeds[accounts[i].id]!;
201 + if ([12, 13, 24, 25].contains(seed.split(" ").length)) {
202 + if (seed.split(" ").last.contains(":tgen:")) continue;
203 + rotationAccounts[accounts[i].id] = [];
204 + }
205 + }
206 + final raKeys = rotationAccounts.keys.toList();
207 + seeds.removeWhere((_, final val) => raKeys.contains(val));
208 + printV("raKeys: ${raKeys.length}");
209 + for (int i = 0; i < raKeys.length; i++) {
210 + final accs = accounts.where((final a) {
211 + final seed = WarpApi.getBackup(coin, a.id).seed;
212 + return isSeedForWallet(seeds[raKeys[i]]!, seed!);
213 + }).toList();
214 + rotationAccounts[raKeys[i]]!.addAll(accs);
215 + final acc = accountForSeed(seeds[raKeys[i]]!)!;
216 + final txs = WarpApi.getTxsSync(coin, acc.id);
217 + newShieldedAccountsTx[acc.id] ??= [];
218 + newShieldedAccountsTx[acc.id]!.addAll(txs);
219 + }
220 + rotationAccountsUsable = rotationAccounts.map((final k, final v) => MapEntry(k, v.toList()));
221 + printV("rotationAccounts: ${rotationAccounts.length}");
222 +
223 + for (int i = 0; i < raKeys.length; i++) {
224 + final acc = accountForSeed(seeds[raKeys[i]]!)!;
225 + newShieldedAccountsTx[acc.id] = [];
226 + for (int j = 0; j < (rotationAccounts[raKeys[i]]?.length ?? 0); j++) {
227 + final txs = WarpApi.getTxsSync(coin, rotationAccounts[raKeys[i]]![j].id);
228 + rotationAccounts[raKeys[i]]![j];
229 + newShieldedAccountsTx[acc.id]!.addAll(txs);
230 + }
231 + rotationAccountsUsable[raKeys[i]]!.removeWhere((final a) {
232 + final txs = WarpApi.getTxsSync(coin, a.id);
233 + return txs.isNotEmpty;
234 + });
235 + }
236 + printV("rotationAccountsUsable: ${rotationAccountsUsable.length}");
237 +
238 + bool didAddNewAccount = false;
239 + for (int i = 0; i < raKeys.length; i++) {
240 + if (rotationAccountsUsable[raKeys[i]]!.length < 5) {
241 + final seed = seedForOffset(seeds[raKeys[i]]!);
242 + final name = CRC32.compute(raKeys[i].toString()).toString();
243 + final id = await ZcashWalletService.runInDbMutex(
244 + () => WarpApi.newAccount(coin, name, seed, rotationAccounts[raKeys[i]]!.length),
245 + );
246 + printV("new id: $id / $seed");
247 + printV("${rotationAccounts[raKeys[i]]}");
248 + printV(raKeys[i]);
249 + rotationAccounts[raKeys[i]]!.forEach((final a) {
250 + final b = WarpApi.getBackup(coin, a.id);
251 + printV("${a.id} / ${b.seed}");
252 + });
253 + didAddNewAccount = true;
254 + }
255 + }
256 + printV("didAddNewAccount: ${didAddNewAccount}");
257 + if (didAddNewAccount) {
258 + return createAndSweepTAddresses();
259 + }
260 +
261 + for (var i = 0; i < raKeys.length; i++) {
262 + final accs = rotationAccounts[raKeys[i]]!;
263 + inner:
264 + for (var j = 0; j < accs.length; j++) {
265 + int bal = 0;
266 + try {
267 + bal += await WarpApi.getTBalance(coin, accs[j].id);
268 + } catch (e) {
269 + printV("getTBalance: $e");
270 + return;
271 + }
272 + if (bal < 30000) continue inner;
273 + final to = accountForSeed(seeds[raKeys[i]]!)!;
274 + // final toAddress = WarpApi.getTAddr(coin, to.id);
275 + // final fromSeed = WarpApi.getBackup(coin, to.id);
276 + // final txId = await ZcashWalletService.runInDbMutex(
277 + // () => WarpApi.sweepTransparentSeed(
278 + // to.coin,
279 + // to.id,
280 + // chainHeight,
281 + // fromSeed.seed!,
282 + // 7,
283 + // toAddress,
284 + // 0,
285 + // 1,
286 + // FeeT(fee: 10000, minFee: 0, maxFee: 0, scheme: 0),
287 + // ),
288 + // );
289 + final recipientBuilder = RecipientObjectBuilder(
290 + address: WarpApi.getAddress(coin, to.id, 4),
291 + pools: 4,
292 + feeIncluded: true,
293 + amount: bal,
294 + );
295 +
296 + final recipient = Recipient(recipientBuilder.toBytes());
297 + final fee = FeeT(fee: 10000, minFee: 0, maxFee: 0, scheme: 0);
298 + final txPlan = await ZcashWalletService.runInDbMutex(
299 + () => WarpApi.prepareTx(
300 + coin,
301 + accs[j].id,
302 + [recipient],
303 + 3, // pools: (Transparent + Sapling)
304 + 1,
305 + 0, // anchorOffset
306 + fee,
307 + ),
308 + );
309 + final _txId = await ZcashWalletService.runInDbMutex(
310 + () => WarpApi.signAndBroadcast(ZcashWalletBase.coin, accs[j].id, txPlan),
311 + );
312 + await ZcashWalletService.addShieldedTx(_txId);
313 + printV("shielded: $_txId");
314 +
315 + await Future.delayed(Duration(seconds: 120)); // let it be
316 + return createAndSweepTAddresses();
317 + }
318 + }
319 + final nsatKeys = newShieldedAccountsTx.keys.toList();
320 + for (int i = 0; i < nsatKeys.length; i++) {
321 + inner:
322 + for (int j = 0; j < newShieldedAccountsTx[nsatKeys[i]]!.length; j++) {
323 + final tx = newShieldedAccountsTx[nsatKeys[i]]?[j];
324 + if (tx == null) {
325 + continue inner;
326 + }
327 + final _txId = tx.txId;
328 + if (_txId == null) {
329 + continue inner;
330 + }
331 + if (tx.value < 0) {
332 + await ZcashWalletService.addShieldedTx(_txId);
333 + // continue inner;
334 + }
335 + if (ZcashWalletService.autoshieldTx.contains(_txId)) {
336 + continue inner;
337 + }
338 + }
339 + }
340 + shieldedAccountsTx = newShieldedAccountsTx.map((final k, final v) => MapEntry(k, v.toList()));
341 + await serializeToFile();
342 + return;
343 + }
344 +
345 + static ShieldedTx reverseAmount(final ShieldedTx t) {
346 + final tt = t.unpack();
347 + tt.value = -tt.value;
348 + return ShieldedTx(base64.decode(flatBuffersPack(tt.pack)));
349 + }
350 +
351 + static Account? accountForSeed(final String seed) {
352 + final accounts = WarpApi.getAccountList(coin);
353 + if (accounts.isEmpty) return null;
354 + for (int i = 0; i < accounts.length; i++) {
355 + final acc = accounts[i];
356 + final backup = WarpApi.getBackup(coin, acc.id);
357 + if (backup.seed == seed) {
358 + return acc;
359 + }
360 + }
361 + return null;
362 + }
363 +
364 + static Future<void> _jobRunner() async {
365 + for (;;) {
366 + try {
367 + await Future.delayed(Duration(seconds: 5));
368 + await createAndSweepTAddresses();
369 + } catch (e, s) {
370 + printV(e);
371 + s.toString().split("\n").forEach(printV);
372 + rethrow;
373 + } finally {
374 + await Future.delayed(Duration(seconds: 30));
375 + }
376 + }
377 + }
378 +
379 + static String? addressForAccount(final int accountId) {
380 + final acc = rotationAccountsUsable[accountId]?.firstOrNull;
381 + if (acc == null) {
382 + return null;
383 + }
384 + return WarpApi.getTAddr(coin, acc.id);
385 + }
386 +
387 + static List<String>? allAddressesForAccount(final int accountId) {
388 + final acc = rotationAccounts[accountId];
389 + if (acc == null) {
390 + printV("Nothing found");
391 + return null;
392 + }
393 + for (int i = 0; i < acc.length; i++) {
394 + final b = WarpApi.getBackup(coin, acc[i].id);
395 + printV("$i. ${b.seed?.split(" ").last}, ${b.index}, ${WarpApi.getTAddr(coin, acc[i].id)}");
396 + }
397 + return acc.map((final a) => WarpApi.getTAddr(coin, a.id)).toList();
398 + }
399 +
400 + static List<String>? allUsedAddressesForAccount(final int accountId) {
401 + final seed = WarpApi.getBackup(coin, accountId).seed!;
402 + final acc = rotationAccounts[seed]?.toList();
403 + if (acc == null) {
404 + printV("Nothing found");
405 + return null;
406 + }
407 + acc.removeWhere((final a1) {
408 + for (int i = 0; i < (rotationAccountsUsable[seed]?.length ?? 0); i++) {
409 + if (rotationAccountsUsable[seed]?[i].id == a1.id) {
410 + return true;
411 + }
412 + }
413 + return false;
414 + });
415 + for (int i = 0; i < acc.length; i++) {
416 + final b = WarpApi.getBackup(coin, acc[i].id);
417 + printV("$i. ${b.seed?.split(" ").last}, ${b.index}, ${WarpApi.getTAddr(coin, acc[i].id)}");
418 + }
419 + return acc.map((final a) => WarpApi.getTAddr(coin, a.id)).toList();
420 + }
421 +}
cw_zcash/lib/src/zcash_transaction_credentials.dart new
+15
@@ -0,0 +1,15 @@
1 +import 'package:cw_core/crypto_currency.dart';
2 +import 'package:cw_core/monero_transaction_priority.dart';
3 +import 'package:cw_core/output_info.dart';
4 +
5 +class ZcashTransactionCredentials {
6 + ZcashTransactionCredentials({
7 + required this.outputs,
8 + required this.priority,
9 + required this.currency,
10 + });
11 +
12 + final List<OutputInfo> outputs;
13 + final MoneroTransactionPriority priority;
14 + final CryptoCurrency currency;
15 +}
cw_zcash/lib/src/zcash_transaction_info.dart new
+106
@@ -0,0 +1,106 @@
1 +import 'dart:convert';
2 +import 'dart:io';
3 +
4 +import 'package:cw_core/currency_for_wallet_type.dart';
5 +import 'package:cw_core/pathForWallet.dart';
6 +import 'package:cw_core/transaction_direction.dart';
7 +import 'package:cw_core/transaction_info.dart';
8 +import 'package:cw_core/format_amount.dart';
9 +import 'package:cw_core/utils/print_verbose.dart';
10 +import 'package:cw_core/wallet_type.dart';
11 +import 'package:cw_zcash/cw_zcash.dart';
12 +import 'package:path/path.dart' as p;
13 +
14 +class ZcashTransactionInfo extends TransactionInfo {
15 + ZcashTransactionInfo({
16 + required final String id,
17 + required final int amount,
18 + required final int fee,
19 + required final TransactionDirection direction,
20 + required final bool isPending,
21 + required final DateTime date,
22 + required final int height,
23 + required final int confirmations,
24 + required final String to,
25 + final String? memo,
26 + }) {
27 + this.id = id;
28 + this.amount = amount;
29 + this.fee = fee;
30 + this.height = height;
31 + this.direction = direction;
32 + this.date = date;
33 + this.isPending = isPending;
34 + this.confirmations = confirmations;
35 + this.to = getCachedDestinationAddress(id);
36 + if (memo != null && memo.isNotEmpty) {
37 + additionalInfo['memo'] = memo;
38 + }
39 + additionalInfo['autoShield'] = false;
40 + // note: this won't work yet, fee is not in zcash_lib metadata,
41 + // leaving here so it can start working automagically in future
42 + if (amount == fee && to.startsWith("u")) {
43 + additionalInfo['autoShield'] = true;
44 + }
45 + // remove below when above starts working
46 + additionalInfo['autoShield'] = ZcashWalletService.autoshieldTx.contains(txHash);
47 + // --- == === cut here === == ---
48 + if (additionalInfo['autoShield'] == true) {
49 + additionalInfo['memo'] ??= '';
50 + additionalInfo['memo'] += '\This is an auto-shielding transaction. Enjoy default privacy!';
51 + additionalInfo['memo'] = additionalInfo['memo'].trim();
52 + }
53 + }
54 +
55 + String? _fiatAmount;
56 +
57 + @override
58 + String amountFormatted() =>
59 + '${walletTypeToCryptoCurrency(WalletType.zcash).formatAmount(BigInt.from(amount))} ${walletTypeToCryptoCurrency(WalletType.zcash).title}';
60 +
61 + @override
62 + String? feeFormatted() {
63 + if (fee == null || fee == 0) return null;
64 + return '${walletTypeToCryptoCurrency(WalletType.zcash).formatAmount(BigInt.from(fee!))} ${walletTypeToCryptoCurrency(WalletType.zcash).title}';
65 + }
66 +
67 + @override
68 + String fiatAmount() => _fiatAmount ?? '';
69 +
70 + @override
71 + void changeFiatAmount(final String amount) => _fiatAmount = formatAmount(amount);
72 +
73 + String? get memo => additionalInfo['memo'] as String?;
74 +
75 + static final Map<String, String> _destinationAddressMap = {};
76 +
77 + static String? getCachedDestinationAddress(final String txId) {
78 + printV("$txId -> ${_destinationAddressMap.keys.join(",")}");
79 + return _destinationAddressMap[txId] ??
80 + _destinationAddressMap['"$txId"'] ??
81 + _destinationAddressMap[txId.replaceAll('"', '')];
82 + }
83 +
84 + static Future<void> addCachedDestinationAddress(final String txId, final String address) async {
85 + _destinationAddressMap[txId] = address;
86 + final pfwt = await pathForWalletTypeDir(type: WalletType.zcash);
87 + final f = File(p.join(pfwt, "sent-tx-map.json"));
88 + f.writeAsStringSync(json.encode(_destinationAddressMap));
89 + }
90 +
91 + static Future<void> init() async {
92 + try {
93 + final pfwt = await pathForWalletTypeDir(type: WalletType.zcash);
94 + final f = File(p.join(pfwt, "sent-tx-map.json"));
95 + if (!f.existsSync()) {
96 + f.writeAsStringSync('{}');
97 + }
98 + final tmpMap = json.decode(f.readAsStringSync());
99 + tmpMap.forEach((final k, final v) {
100 + _destinationAddressMap[k.toString()] = v.toString();
101 + });
102 + } catch (e, s) {
103 + printV("failed to deserialize: $e, $s");
104 + }
105 + }
106 +}
cw_zcash/lib/src/zcash_transactions.dart new
+24
@@ -0,0 +1,24 @@
1 +import 'package:mobx/mobx.dart';
2 +import 'package:cw_core/transaction_info.dart';
3 +import 'package:cw_core/transaction_history.dart';
4 +
5 +class ZcashTransactionHistory extends TransactionHistoryBase<TransactionInfo> {
6 + ZcashTransactionHistory() {
7 + transactions = ObservableMap<String, TransactionInfo>();
8 + }
9 +
10 + @override
11 + void addMany(final Map<String, TransactionInfo> txs) {
12 + transactions.addAll(txs);
13 + }
14 +
15 + @override
16 + void addOne(final TransactionInfo txs) {
17 + transactions[txs.txHash] = txs;
18 + }
19 +
20 + @override
21 + Future<void> save() async {
22 + // No need to save anything, backend is taking care of that
23 + }
24 +}
cw_zcash/lib/src/zcash_wallet.dart new
+976
@@ -0,0 +1,976 @@
1 +import 'dart:async';
2 +import 'dart:convert';
3 +import 'dart:io';
4 +
5 +import 'package:cw_core/crypto_currency.dart';
6 +import 'package:cw_core/monero_transaction_priority.dart';
7 +import 'package:cw_core/node.dart';
8 +import 'package:cw_core/pathForWallet.dart';
9 +import 'package:cw_core/pending_transaction.dart';
10 +import 'package:cw_core/sync_status.dart';
11 +import 'package:cw_core/transaction_direction.dart';
12 +import 'package:cw_core/transaction_priority.dart';
13 +import 'package:cw_core/utils/print_verbose.dart';
14 +import 'package:cw_core/wallet_addresses.dart';
15 +import 'package:cw_core/wallet_base.dart';
16 +import 'package:cw_core/wallet_credentials.dart';
17 +import 'package:cw_core/wallet_info.dart';
18 +import 'package:cw_core/wallet_type.dart';
19 +import 'package:bip39/bip39.dart' as bip39;
20 +import 'package:cw_zcash/cw_zcash.dart';
21 +import 'package:cw_zcash/src/util/crc32.dart';
22 +import 'package:cw_zcash/src/zcash_taddress_rotation.dart';
23 +import 'package:cw_zcash/src/zcash_wallet_addresses.dart';
24 +import 'package:mobx/mobx.dart';
25 +import 'package:warp_api/warp_api.dart';
26 +import 'package:warp_api/data_fb_generated.dart';
27 +import 'package:flutter/services.dart';
28 +import 'package:mutex/mutex.dart';
29 +import 'package:path/path.dart' as p;
30 +
31 +part 'zcash_wallet.g.dart';
32 +
33 +class ZcashWallet = ZcashWalletBase with _$ZcashWallet;
34 +
35 +abstract class ZcashWalletBase
36 + extends WalletBase<ZcashBalance, ZcashTransactionHistory, ZcashTransactionInfo>
37 + with Store {
38 + ZcashWalletBase(super.walletInfo, super.derivationInfo, {required this.accountId}) {
39 + transactionHistory = ZcashTransactionHistory();
40 + }
41 +
42 + final int accountId;
43 + static const int coin = 0; // Zcash mainnet coin ID in warp_api
44 + @override
45 + @observable
46 + SyncStatus syncStatus = NotConnectedSyncStatus();
47 +
48 + Timer? _syncStatusTimer;
49 + Timer? _periodicSyncTimer;
50 + int _initialSyncHeight = 0;
51 + int _lastKnownBlockHeight = 0;
52 +
53 + @override
54 + ObservableMap<CryptoCurrency, ZcashBalance> balance = ObservableMap.of({
55 + CryptoCurrency.zec: ZcashBalance(confirmed: 0, unconfirmed: 0, frozen: 0),
56 + });
57 +
58 + static int internalCalculateEstimatedFee(final TransactionPriority priority, final int? amount) {
59 + const baseFee = 10000;
60 + switch (priority) {
61 + case MoneroTransactionPriority.slow:
62 + case MoneroTransactionPriority.automatic:
63 + return baseFee;
64 + case MoneroTransactionPriority.medium:
65 + return baseFee * 2;
66 + case MoneroTransactionPriority.fast:
67 + return baseFee * 4;
68 + case MoneroTransactionPriority.fastest:
69 + return baseFee * 10;
70 + }
71 + ;
72 + return internalCalculateEstimatedFee(MoneroTransactionPriority.automatic, amount);
73 + }
74 +
75 + @override
76 + int calculateEstimatedFee(final TransactionPriority priority, final int? amount) {
77 + return internalCalculateEstimatedFee(priority, amount);
78 + }
79 +
80 + @override
81 + Future<void> changePassword(final String password) async {
82 + // throw UnimplementedError();
83 + }
84 +
85 + static bool isNodeWorking = true;
86 +
87 + @override
88 + Future<bool> checkNodeHealth() {
89 + return Future.value(isNodeWorking);
90 + }
91 +
92 + @override
93 + Future<void> close({final bool shouldCleanup = false}) async {
94 + _stopSyncStatusUpdates();
95 + _stopPeriodicSync();
96 + await ZcashWalletService.runInDbMutex(() async => WarpApi.cancelSync());
97 + }
98 +
99 + @override
100 + @action
101 + Future<void> connectToNode({required final Node node}) async {
102 + printV("connecting to node: ${node.uriRaw}");
103 + syncStatus = ConnectingSyncStatus();
104 + try {
105 + String lwdUrl = node.uriRaw;
106 + if (!lwdUrl.startsWith('http://') && !lwdUrl.startsWith('https://')) {
107 + final protocol = node.useSSL == true ? 'https://' : 'http://';
108 + lwdUrl = '$protocol$lwdUrl';
109 + }
110 + printV("Setting LWD URL to: $lwdUrl");
111 + WarpApi.updateLWD(coin, lwdUrl);
112 + syncStatus = ConnectedSyncStatus();
113 + try {
114 + await updateBalance();
115 + await updateTransactions();
116 + } catch (e) {
117 + printV("Error updating balance/transactions after connect: $e");
118 + }
119 + } catch (e) {
120 + printV("Connection error: $e");
121 + syncStatus = FailedSyncStatus(error: e.toString());
122 + rethrow;
123 + }
124 + }
125 +
126 + @override
127 + Future<PendingTransaction> createTransaction(final Object credentials) async {
128 + final creds = credentials as ZcashTransactionCredentials;
129 + await updateBalance();
130 +
131 + final zcashBalance = balance[CryptoCurrency.zec];
132 + final availableBalance = zcashBalance?.confirmed ?? 0;
133 +
134 + final recipients = <Recipient>[];
135 + int totalAmount = 0;
136 +
137 + for (final output in creds.outputs) {
138 + int amount;
139 + if (output.sendAll) {
140 + amount = availableBalance;
141 + } else {
142 + amount = output.formattedCryptoAmount ?? 0;
143 +
144 + if (amount == 0 && output.cryptoAmount != null && output.cryptoAmount!.isNotEmpty) {
145 + try {
146 + final parsedAmount = CryptoCurrency.zec.parseAmount(
147 + output.cryptoAmount!.replaceAll(',', '.'),
148 + );
149 + amount = parsedAmount.toInt();
150 + printV("Parsed amount from cryptoAmount '${output.cryptoAmount}': $amount");
151 + } catch (e) {
152 + printV("Failed to parse cryptoAmount '${output.cryptoAmount}': $e");
153 + }
154 + }
155 +
156 + if (amount <= 0) {
157 + throw Exception(
158 + 'Invalid amount for output. Amount: ${output.cryptoAmount}, Formatted: ${output.formattedCryptoAmount}',
159 + );
160 + }
161 + }
162 +
163 + totalAmount += amount;
164 +
165 + var address = (output.isParsedAddress ? output.extractedAddress! : output.address).trim();
166 +
167 + if (address.isEmpty) {
168 + throw Exception('Empty address for output');
169 + }
170 +
171 + final paymentUri = WarpApi.decodePaymentURI(coin, address);
172 + String memo = output.note ?? '';
173 + if (paymentUri != null && paymentUri.address != null) {
174 + address = paymentUri.address!;
175 + if (memo.isEmpty && paymentUri.memo != null) {
176 + memo = paymentUri.memo!;
177 + }
178 + }
179 +
180 + if (!WarpApi.validAddress(coin, address)) {
181 + throw Exception('Invalid Zcash address: $address');
182 + }
183 +
184 + int recipientPools = 7;
185 +
186 + if (address.startsWith('t1') || address.startsWith('t3')) {
187 + recipientPools = 1; // Transparent only
188 + } else if (address.startsWith('zs')) {
189 + recipientPools = 2; // Sapling only
190 + }
191 + // For unified addresses (u1...) and other types, use 7 (all pools)
192 +
193 + final builder = RecipientObjectBuilder(
194 + address: address,
195 + pools: recipientPools,
196 + amount: amount,
197 + feeIncluded: output.sendAll,
198 + replyTo: false,
199 + memo: memo.isNotEmpty ? memo : null,
200 + );
201 +
202 + recipients.add(Recipient(builder.toBytes()));
203 + }
204 +
205 + if (totalAmount > availableBalance) {
206 + throw Exception('Insufficient balance');
207 + }
208 +
209 + final fee = FeeT(
210 + fee: internalCalculateEstimatedFee(creds.priority, null),
211 + minFee: 0,
212 + maxFee: 0,
213 + scheme: 0, // Fixed fee scheme
214 + );
215 +
216 + // pools parameter: bitmask for which pools to use for sending
217 + // 1=Transparent, 2=Sapling, 4=Orchard, 7=All pools
218 + // Using 7 (all pools) allows spending from any pool type
219 + final txPlan = await ZcashWalletService.runInDbMutex(
220 + () => WarpApi.prepareTx(
221 + coin,
222 + accountId,
223 + recipients,
224 + 7, // pools: All pools (Transparent + Sapling + Orchard) - allows spending from any pool
225 + 1, // senderUAType: 0 = unified address
226 + 0, // anchorOffset
227 + fee,
228 + ),
229 + );
230 +
231 + return PendingZcashTransaction(
232 + zcashWallet: this as ZcashWallet,
233 + credentials: creds,
234 + txPlan: txPlan,
235 + fee: internalCalculateEstimatedFee(creds.priority, null),
236 + availableBalance: availableBalance,
237 + );
238 + }
239 +
240 + static const _dispPhrase = "Received to disposable address";
241 + Future<List<ShieldedTx>> getShieldTxForUi() async {
242 + final tx = (ZcashTaddressRotation.shieldedAccountsTx[accountId] ?? <ShieldedTx>[])
243 + .map((final v) {
244 + final unpacked = v.unpack();
245 + unpacked.memo ??= "";
246 + unpacked.memo = "${unpacked.memo}\n$_dispPhrase".trim();
247 + final List<int> buff = base64.decode(
248 + ZcashTaddressRotation.flatBuffersPack(unpacked.pack),
249 + );
250 + return ShieldedTx(buff);
251 + })
252 + .where((final t) => t.value > 0);
253 +
254 + return tx.toList();
255 + }
256 +
257 + static Map<int, List<ShieldedTx>> temporarySentTx = {};
258 +
259 + static String txChecksumKey(final ShieldedTx tx) {
260 + final direction = tx.value > 0 ? TransactionDirection.incoming : TransactionDirection.outgoing;
261 + return 'tx${direction}_${tx.id}_${tx.timestamp}_${CRC32.compute(tx.toString())}';
262 + }
263 +
264 + @override
265 + Future<Map<String, ZcashTransactionInfo>> fetchTransactions() async {
266 + await ZcashWalletService.loadShieldTxs();
267 + final txs = (await ZcashWalletService.runInDbMutex(
268 + () => WarpApi.getTxs(coin, accountId),
269 + )).toList();
270 + // ShieldedTx{id: 26, txId: 4d1be06ce2c2debec8d98ce4e9434c8aac27c980488b459017d423fdcab37f93, height: 3195705, shortTxId: 4d1be06c, timestamp: 1767730944, name: null, value: 1000000, address: null, memo: , messages: MemoVec{memos: null}}
271 +
272 + final shieldTx = await getShieldTxForUi();
273 +
274 + txs.addAll(shieldTx);
275 + final txIds = txs.map((final tx) => tx.txId!.replaceAll('"', '')).toSet();
276 + temporarySentTx[accountId]?.removeWhere(
277 + (final ttx) => txIds.contains(ttx.txId!.replaceAll('"', '')),
278 + );
279 + txs.addAll(temporarySentTx[accountId] ?? []);
280 +
281 + txs.sort((final a, final b) => a.height.compareTo(b.height));
282 + final Map<String, ZcashTransactionInfo> result = {};
283 + int currentHeight = 0;
284 + try {
285 + currentHeight = await WarpApi.getLatestHeight(coin);
286 + } catch (e) {
287 + printV("Error getting latest height: $e");
288 + }
289 +
290 + for (final tx in txs) {
291 + final direction = tx.value > 0
292 + ? TransactionDirection.incoming
293 + : TransactionDirection.outgoing;
294 +
295 + final confirmations = tx.height > 0 && currentHeight > 0 ? currentHeight - tx.height + 1 : 0;
296 +
297 + final txChecksum = txChecksumKey(tx);
298 + final txId = tx.txId ?? tx.shortTxId ?? txChecksum;
299 +
300 + final txInfo = ZcashTransactionInfo(
301 + id: txId.trim().replaceAll('"', ''),
302 + amount: tx.value.abs(),
303 + fee: 0,
304 + direction: direction,
305 + isPending: tx.height == 0,
306 + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000),
307 + height: tx.height,
308 + confirmations: confirmations,
309 + to: tx.address ?? '',
310 + memo: tx.memo,
311 + );
312 + // if (txInfo.additionalInfo['autoShield'] == true) {
313 + // continue;
314 + // }
315 + result[txChecksum] = txInfo;
316 + }
317 +
318 + return result;
319 + }
320 +
321 + @override
322 + Object get keys => {};
323 +
324 + @override
325 + String get password => _password!;
326 +
327 + @override
328 + Future<void> renameWalletFiles(final String newWalletName) async {
329 + final currentWalletPath = await pathForWallet(name: name, type: type);
330 + final currentCacheFile = File(currentWalletPath);
331 + final newWalletPath = await pathForWallet(name: newWalletName, type: type);
332 + if (currentCacheFile.existsSync()) {
333 + await currentCacheFile.copy(newWalletPath);
334 + }
335 + Directory(currentWalletPath).deleteSync(recursive: true);
336 + }
337 +
338 + @override
339 + bool get hasRescan => true;
340 +
341 + @override
342 + @action
343 + Future<void> rescan({required final int height}) async {
344 + try {
345 + syncStatus = StartingScanSyncStatus(height);
346 + printV("rescanning from: $height");
347 + await ZcashWalletService.runInDbMutex(() async => WarpApi.rescanFrom(coin, height));
348 + await startSync();
349 + } catch (e) {
350 + printV("Rescan error: $e");
351 + syncStatus = FailedSyncStatus(error: e.toString());
352 + rethrow;
353 + }
354 + }
355 +
356 + bool _isTransactionUpdating = false;
357 +
358 + Future<void> updateTransactions() async {
359 + try {
360 + if (_isTransactionUpdating) {
361 + return;
362 + }
363 +
364 + _isTransactionUpdating = true;
365 + final transactions = await fetchTransactions();
366 +
367 + final currentIds = transactionHistory.transactions.keys.toSet();
368 + final newIds = transactions.keys.toSet();
369 +
370 + currentIds
371 + .difference(newIds)
372 + .forEach((final id) => transactionHistory.transactions.remove(id));
373 +
374 + transactions.forEach((final key, final tx) {
375 + transactionHistory.transactions[key] = tx;
376 + });
377 + await transactionHistory.save();
378 + _isTransactionUpdating = false;
379 + } catch (e, stackTrace) {
380 + printV("Update transactions error: $e");
381 + printV("Stack trace: $stackTrace");
382 + _isTransactionUpdating = false;
383 + }
384 + }
385 +
386 + Future<void> updateTransactionsHistory() => updateTransactions();
387 +
388 + @override
389 + Future<void> save() async {}
390 +
391 + Future<void> init() async {
392 + try {
393 + await walletAddresses.init();
394 +
395 + await updateBalance();
396 + await updateTransactions();
397 + } catch (e) {
398 + printV("Wallet init error: $e");
399 + }
400 + }
401 +
402 + @override
403 + String? get seed {
404 + try {
405 + final backup = WarpApi.getBackup(coin, accountId);
406 + final seed = backup.seed!.split(" ");
407 + if ([13, 25].contains(seed.length)) {
408 + seed.removeLast();
409 + }
410 + return seed.join(" ").trim();
411 + } catch (e) {
412 + return null;
413 + }
414 + }
415 +
416 + @override
417 + String? get passphrase {
418 + try {
419 + final backup = WarpApi.getBackup(coin, accountId);
420 + final seed = backup.seed!.split(" ");
421 + if ([13, 25].contains(seed.length)) {
422 + final passphrase = seed.removeLast();
423 + return passphrase;
424 + }
425 + return null;
426 + } catch (e) {
427 + return null;
428 + }
429 + }
430 +
431 + @override
432 + Future<String> signMessage(final String message, {final String? address = null}) {
433 + throw UnimplementedError();
434 + }
435 +
436 + @override
437 + @action
438 + Future<void> startSync() async {
439 + try {
440 + syncStatus = AttemptingSyncStatus();
441 +
442 + _initialSyncHeight = 0;
443 + _lastKnownBlockHeight = 0;
444 +
445 + _startSyncStatusUpdates();
446 +
447 + syncStatus = SyncronizingSyncStatus();
448 +
449 + unawaited(
450 + _runWarpSync().catchError((final e) {
451 + isNodeWorking = false;
452 + printV("WarpSync error in startSync: $e");
453 + syncStatus = FailedSyncStatus(error: e.toString());
454 + _stopSyncStatusUpdates();
455 + }),
456 + );
457 + } catch (e) {
458 + isNodeWorking = false;
459 + printV("Sync error: $e");
460 + syncStatus = FailedSyncStatus(error: e.toString());
461 + _stopSyncStatusUpdates();
462 + rethrow;
463 + }
464 + }
465 +
466 + static Mutex warpSyncMutex = Mutex();
467 +
468 + static Future<void> initialSyncCheck() async {
469 + final zcashDir = await pathForWalletTypeDir(type: WalletType.zcash);
470 + final zcashInitialSync = File(p.join(zcashDir, ".initial-sync-marker"));
471 + if (!zcashInitialSync.existsSync()) {
472 + int chainHeight = 3000000; // fallback if node is offline
473 + try {
474 + chainHeight = await WarpApi.getLatestHeight(coin);
475 + } catch (e) {
476 + printV("Error getting latest height: $e");
477 + }
478 + await ZcashWalletService.runInDbMutex(
479 + () async => await WarpApi.rescanFrom(coin, chainHeight - 150000),
480 + );
481 + zcashInitialSync.writeAsBytesSync([0x00]);
482 + zcashInitialSync.writeAsStringSync(chainHeight.toString(), mode: FileMode.writeOnlyAppend);
483 + }
484 + }
485 +
486 + @action
487 + Future<void> _runWarpSync() async {
488 + Timer? _t;
489 + try {
490 + await warpSyncMutex.acquire();
491 + await initialSyncCheck();
492 + isNodeWorking = true;
493 + printV("Starting warpSync for coin $coin, account $accountId");
494 + int? initialQueue = null;
495 + void _cancelSyncIfShould(final Timer t) {
496 + initialQueue ??= ZcashWalletService.dbMutexQueue + 2;
497 + if (ZcashWalletService.dbMutexQueue <= initialQueue!) {
498 + initialQueue = ZcashWalletService.dbMutexQueue;
499 + return;
500 + }
501 + printV(
502 + "Canceling sync! (ZcashWalletService.dbMutexQueue: ${ZcashWalletService.dbMutexQueue} > initialQueue: ${initialQueue})",
503 + );
504 + WarpApi.cancelSync();
505 + t.cancel();
506 + _t = null;
507 + }
508 +
509 + unawaited(
510 + Future.delayed(Duration(seconds: 2)).then((_) {
511 + _t = Timer.periodic(Duration(milliseconds: 100), _cancelSyncIfShould);
512 + }),
513 + );
514 + final result = await ZcashWalletService.runInDbMutex(
515 + () => WarpApi.warpSync(coin, accountId, true, 0, 1000000, 0),
516 + );
517 + printV("warpSync completed with result: $result");
518 +
519 + await _updateSyncStatus();
520 + } catch (e) {
521 + syncStatus = FailedSyncStatus(error: e.toString());
522 + unawaited(Future.delayed(Duration(seconds: 1)).then((_) => unawaited(_runWarpSync())));
523 + _stopSyncStatusUpdates();
524 + } finally {
525 + isNodeWorking = false;
526 + warpSyncMutex.release();
527 + _t?.cancel();
528 + }
529 + }
530 +
531 + void _startSyncStatusUpdates() {
532 + _stopSyncStatusUpdates();
533 + _updateSyncStatus();
534 + _syncStatusTimer = Timer.periodic(const Duration(milliseconds: 5000), (_) {
535 + _updateSyncStatus().catchError((final e) {
536 + printV("Error in sync status update timer: $e");
537 + });
538 + });
539 + }
540 +
541 + void _stopSyncStatusUpdates() {
542 + _syncStatusTimer?.cancel();
543 + _syncStatusTimer = null;
544 + }
545 +
546 + void _startPeriodicSync() {
547 + printV("_startPeriodicSync");
548 + _stopPeriodicSync();
549 + _periodicSyncTimer = Timer.periodic(const Duration(seconds: 5), (_) async {
550 + try {
551 + final chainHeight = await WarpApi.getLatestHeight(coin);
552 + final dbHeight = WarpApi.getDbHeight(coin);
553 + final height = dbHeight.unpack();
554 + final syncHeight = height.height;
555 +
556 + if (syncHeight < chainHeight) {
557 + printV("Periodic sync: chainHeight=$chainHeight, syncHeight=$syncHeight, starting sync");
558 + await _runWarpSync();
559 + } else {
560 + await updateBalance();
561 + await updateTransactions();
562 + }
563 + } catch (e) {
564 + printV("Periodic sync error: $e");
565 + }
566 + });
567 + }
568 +
569 + void _stopPeriodicSync() {
570 + _periodicSyncTimer?.cancel();
571 + _periodicSyncTimer = null;
572 + }
573 +
574 + @action
575 + Future<void> _updateSyncStatus() async {
576 + try {
577 + final dbHeight = WarpApi.getDbHeight(coin);
578 + final height = dbHeight.unpack();
579 + final syncHeight = height.height;
580 +
581 + final chainHeight = await WarpApi.getLatestHeight(coin);
582 +
583 + if (_initialSyncHeight <= 0 && syncHeight > 0) {
584 + _initialSyncHeight = syncHeight;
585 + printV("Initialized sync height to: $_initialSyncHeight");
586 + if (syncHeight - 10 > dbHeight.height) {}
587 + }
588 +
589 + if (chainHeight <= 0) {
590 + if (syncStatus is! ConnectedSyncStatus && syncStatus is! ConnectingSyncStatus) {
591 + syncStatus = ConnectedSyncStatus();
592 + }
593 + try {
594 + await updateBalance();
595 + await updateTransactions();
596 + } catch (e) {
597 + printV("Error updating balance/transactions: $e");
598 + }
599 + return;
600 + }
601 +
602 + if (syncHeight <= 0) {
603 + if (syncStatus is! ConnectedSyncStatus &&
604 + syncStatus is! ConnectingSyncStatus &&
605 + syncStatus is! AttemptingSyncStatus) {
606 + syncStatus = ConnectedSyncStatus();
607 + }
608 + try {
609 + await updateBalance();
610 + await updateTransactions();
611 + } catch (e) {
612 + printV("Error updating balance/transactions: $e");
613 + }
614 + return;
615 + }
616 +
617 + if (syncHeight >= chainHeight && syncHeight > 0) {
618 + syncStatus = SyncedSyncStatus();
619 + _stopSyncStatusUpdates();
620 + await updateBalance();
621 + await updateTransactions();
622 + _startPeriodicSync();
623 + return;
624 + }
625 +
626 + if (_lastKnownBlockHeight != syncHeight) {
627 + _lastKnownBlockHeight = syncHeight;
628 + }
629 +
630 + if (syncHeight < 0 || chainHeight < syncHeight) {
631 + return;
632 + }
633 +
634 + final blocksLeft = chainHeight - syncHeight;
635 + if (blocksLeft <= 0) {
636 + syncStatus = SyncedSyncStatus();
637 + _stopSyncStatusUpdates();
638 + await updateBalance();
639 + await updateTransactions();
640 + _startPeriodicSync();
641 + return;
642 + }
643 +
644 + double ptc = 0.0;
645 + if (_initialSyncHeight > 0) {
646 + final track = chainHeight - _initialSyncHeight;
647 + final diff = track > 0 ? track - (chainHeight - syncHeight) : 0;
648 + ptc = track > 0 && diff >= 0 ? diff / track : 0.0;
649 + } else {
650 + ptc = syncHeight / chainHeight;
651 + }
652 +
653 + syncStatus = SyncingSyncStatus(blocksLeft, ptc.clamp(0.0, 1.0));
654 +
655 + await updateBalance();
656 + await updateTransactions();
657 + } catch (e) {
658 + printV("Sync status update error: $e");
659 + }
660 + }
661 +
662 + String getDiversifiedAddress(final int uaType, {final DateTime? time}) {
663 + try {
664 + final timestamp = (time ?? DateTime.now()).millisecondsSinceEpoch ~/ 1000;
665 + return WarpApi.getDiversifiedAddress(coin, accountId, uaType, timestamp);
666 + } catch (e) {
667 + printV("Error getting diversified address: $e");
668 + return "";
669 + }
670 + }
671 +
672 + static final autoShieldMutex = Mutex();
673 + Future<void> _autoShield() async {
674 + try {
675 + await autoShieldMutex.acquire();
676 + await _$autoShield();
677 + } catch (e) {
678 + printV("shielding failed: $e");
679 + await Future.delayed(Duration(seconds: 30));
680 + } finally {
681 + autoShieldMutex.release();
682 + }
683 + }
684 +
685 + Future<void> _$autoShield() async {
686 + final chainHeight = await WarpApi.getLatestHeight(coin);
687 + final dbHeight = WarpApi.getDbHeight(coin);
688 + final height = dbHeight.unpack();
689 + final syncHeight = height.height;
690 + if (chainHeight != syncHeight) {
691 + printV("Not autoshielding: chainHeight(${chainHeight}) != syncHeight(${syncHeight})");
692 + return;
693 + }
694 + final bpConfirmed = WarpApi.getPoolBalances(coin, accountId, 0, false);
695 + if (bpConfirmed.transparent + bpConfirmed.sapling <= 20000) {
696 + return;
697 + }
698 +
699 + final recipientBuilder = RecipientObjectBuilder(
700 + address: (walletAddresses as ZcashWalletAddresses).orchardAddress,
701 + pools: 4,
702 + feeIncluded: true,
703 + amount: bpConfirmed.transparent + bpConfirmed.sapling,
704 + );
705 +
706 + final recipient = Recipient(recipientBuilder.toBytes());
707 + final fee = FeeT(fee: 10000, minFee: 0, maxFee: 0, scheme: 0);
708 + final txPlan = await ZcashWalletService.runInDbMutex(
709 + () => WarpApi.prepareTx(
710 + coin,
711 + accountId,
712 + [recipient],
713 + 3, // pools: (Transparent + Sapling)
714 + 1,
715 + 0, // anchorOffset
716 + fee,
717 + ),
718 + );
719 + final _txId = await ZcashWalletService.runInDbMutex(
720 + () => WarpApi.signAndBroadcast(ZcashWalletBase.coin, accountId, txPlan),
721 + );
722 + await ZcashWalletService.addShieldedTx(_txId);
723 + printV("shielded: $_txId");
724 + await updateTransactions();
725 + await updateBalance();
726 + await Future.delayed(Duration(seconds: 75 * 5)); // do not re-try doing that
727 + }
728 +
729 + @override
730 + @action
731 + Future<void> updateBalance() async {
732 + try {
733 + final poolBalances = WarpApi.getPoolBalances(coin, accountId, 0, true);
734 + final balances = poolBalances.unpack();
735 + // final notes = WarpApi.getNotesSync(coin, accountId);
736 + // int frozenBalance = 0;
737 + // for (final note in notes) {
738 + // if (note.excluded) {
739 + // frozenBalance += note.value;
740 + // }
741 + // }
742 + final total = balances.orchard + balances.sapling + balances.transparent;
743 + final spendable = total - balances.transparent;
744 +
745 + final confirmedPoolBalances = WarpApi.getPoolBalances(coin, accountId, 3, true);
746 + final confirmedBalances = confirmedPoolBalances.unpack();
747 + final confirmedTotal =
748 + confirmedBalances.orchard + confirmedBalances.sapling + confirmedBalances.transparent;
749 +
750 + int knownOutPending = 0;
751 + ZcashWalletBase.temporarySentTx[accountId]?.forEach((final sTx) {
752 + knownOutPending += sTx.value; // it's negative
753 + });
754 + final confirmedSpendable = confirmedTotal - balances.transparent + knownOutPending;
755 +
756 + unawaited(_autoShield());
757 +
758 + balance[CryptoCurrency.zec] = ZcashBalance(
759 + confirmed: confirmedSpendable,
760 + unconfirmed: spendable - confirmedSpendable,
761 + frozen: 0,
762 + );
763 + } catch (e, stackTrace) {
764 + printV("Balance update error: $e");
765 + printV("Stack trace: $stackTrace");
766 + }
767 + }
768 +
769 + @override
770 + Future<bool> verifyMessage(
771 + final String message,
772 + final String signature, {
773 + final String? address = null,
774 + }) {
775 + throw UnimplementedError();
776 + }
777 +
778 + @observable
779 + late WalletAddresses walletAddresses = ZcashWalletAddresses(accountId, walletInfo);
780 +
781 + static Future<ZcashWallet> create(final WalletCredentials credentials) async {
782 + await _init();
783 + final newWalletCredentials = credentials as ZcashNewWalletCredentials;
784 +
785 + String mnemonic;
786 + if (newWalletCredentials.mnemonic != null && newWalletCredentials.mnemonic!.isNotEmpty) {
787 + mnemonic = newWalletCredentials.mnemonic!;
788 + } else {
789 + final strength = (newWalletCredentials.seedPhraseLength == 24) ? 256 : 128;
790 + mnemonic = bip39.generateMnemonic(strength: strength);
791 + }
792 +
793 + final accountId = await _restoreZcashWalletFromSeed(
794 + name: credentials.name,
795 + seed: mnemonic,
796 + passphrase: newWalletCredentials.passphrase,
797 + );
798 + await _saveAccountId(credentials.name, accountId);
799 + final wallet = await open(
800 + name: credentials.name,
801 + password: credentials.password!,
802 + walletInfo: credentials.walletInfo!,
803 + );
804 + await wallet.walletAddresses.saveAddressesInBox();
805 + return wallet;
806 + }
807 +
808 + static Future<ZcashWallet> restore(final WalletCredentials credentials) async {
809 + await _init();
810 + final fromSeedCredentials = credentials as ZcashFromSeedWalletCredentials;
811 + final String? seed = fromSeedCredentials.seed;
812 + if (seed == null || seed.isEmpty) {
813 + throw Exception('Seed phrase is required for wallet restoration');
814 + }
815 +
816 + final accountId = await _restoreZcashWalletFromSeed(
817 + name: credentials.name,
818 + seed: seed,
819 + passphrase: fromSeedCredentials.passphrase,
820 + );
821 + await _saveAccountId(credentials.name, accountId);
822 + final wallet = await open(
823 + name: credentials.name,
824 + password: credentials.password!,
825 + walletInfo: credentials.walletInfo!,
826 + );
827 + await wallet.walletAddresses.saveAddressesInBox();
828 + printV("height: ${credentials.height}");
829 + if (credentials.height != null) {
830 + final zcashDir = await pathForWalletTypeDir(type: WalletType.zcash);
831 + final zcashInitialSync = File(p.join(zcashDir, ".initial-sync-marker"));
832 + zcashInitialSync.writeAsBytesSync([0x00]);
833 + zcashInitialSync.writeAsStringSync(
834 + credentials.height.toString(),
835 + mode: FileMode.writeOnlyAppend,
836 + );
837 + unawaited(
838 + Future.delayed(Duration(seconds: 8)).then(
839 + (_) => ZcashWalletService.runInDbMutex(
840 + () async => await WarpApi.rescanFrom(coin, credentials.height ?? 0),
841 + ),
842 + ),
843 + );
844 + }
845 + return wallet;
846 + }
847 +
848 + static Future<ZcashWallet> open({
849 + required final String name,
850 + required final String password,
851 + required final WalletInfo walletInfo,
852 + }) async {
853 + await _init();
854 + if (password.isNotEmpty) {
855 + WarpApi.setDbPasswd(coin, password);
856 + }
857 + final accountId = await getZcashAccountIdForName(name);
858 + if (accountId == null) {
859 + throw Exception("Wallet account not found for name: $name");
860 + }
861 + final wallet = ZcashWallet(
862 + walletInfo,
863 + await walletInfo.getDerivationInfo(),
864 + accountId: accountId,
865 + );
866 + await wallet.walletAddresses.init();
867 + return wallet;
868 + }
869 +
870 + static Future<int> _restoreZcashWalletFromSeed({
871 + required final String name,
872 + required String seed,
873 + required String? passphrase,
874 + }) async {
875 + if (passphrase?.isNotEmpty == true) {
876 + passphrase = passphrase!.replaceAll(" ", "_");
877 + seed = "${seed} ${passphrase}";
878 + }
879 + final accountId = await ZcashWalletService.runInDbMutex(
880 + () => WarpApi.newAccount(coin, name, seed, 0),
881 + );
882 + return accountId;
883 + }
884 +
885 + static Future<int?> getZcashAccountIdForName(final String name) async {
886 + final wPath = await pathForWallet(name: name, type: _type);
887 + final f = File(wPath);
888 + if (!f.existsSync()) {
889 + final accounts = WarpApi.getAccountList(coin);
890 + for (final account in accounts) {
891 + if (account.name == name) {
892 + return account.id;
893 + }
894 + }
895 + return null;
896 + }
897 + final content = f.readAsStringSync();
898 + return int.tryParse(content.trim());
899 + }
900 +
901 + static Future<void> _saveAccountId(final String name, final int accountId) async {
902 + final wPath = await pathForWallet(name: name, type: _type);
903 + final f = File(wPath);
904 + f.writeAsStringSync(accountId.toString());
905 + }
906 +
907 + static WalletType get _type => WalletType.zcash;
908 +
909 + static Future<String> getDbDataPath() async {
910 + final pathForWalletType = await pathForWalletTypeDir(type: _type);
911 + final dbDataPath = "${pathForWalletType}/zec.db";
912 + if (!Directory(pathForWalletType).existsSync()) {
913 + Directory(pathForWalletType).createSync(recursive: true);
914 + }
915 + return dbDataPath;
916 + }
917 +
918 + static Future<String> getTorDir() async {
919 + final pathForWalletType = await pathForWalletTypeDir(type: _type);
920 + final torPath = "${pathForWalletType}/tor";
921 + if (!Directory(torPath).existsSync()) {
922 + Directory(torPath).createSync(recursive: true);
923 + }
924 + return torPath;
925 + }
926 +
927 + static Future<String> getFsBlockCacheDir() async {
928 + final pathForWalletType = await pathForWalletTypeDir(type: _type);
929 + final fsBlockCacheDir = "${pathForWalletType}/blockCache";
930 + if (!Directory(pathForWalletType).existsSync()) {
931 + Directory(pathForWalletType).createSync(recursive: true);
932 + }
933 + return fsBlockCacheDir;
934 + }
935 +
936 + static String? dbDataPath;
937 + static bool _initialized = false;
938 +
939 + static void unlockDatabase(final String password) {
940 + _password = password;
941 + }
942 +
943 + static String? _password;
944 + static Future<void> _init() async {
945 + if (_initialized) return;
946 + dbDataPath = await getDbDataPath();
947 + printV("WarpApi.initWallet");
948 + if (_password == null) {
949 + throw Exception("Zcash wallet locked! Please contact support");
950 + }
951 + if (!File(dbDataPath!).existsSync()) {
952 + //TODO(mrcyjanek): copy-encrypt
953 + }
954 + // coin+1 = ycash
955 + WarpApi.setDbPasswd(coin, '');
956 + WarpApi.setDbPasswd(coin + 1, '');
957 + WarpApi.initWallet(coin, dbDataPath!);
958 + WarpApi.initWallet(coin + 1, dbDataPath!);
959 + try {
960 + WarpApi.migrateData(coin);
961 + WarpApi.migrateData(coin + 1);
962 + } catch (e) {
963 + printV("zec init failed: $e");
964 + } // do not fail on network exception
965 + final spend = await rootBundle.load('scripts/zcash_lib/assets/sapling-spend.params');
966 + final output = await rootBundle.load('scripts/zcash_lib/assets/sapling-output.params');
967 + WarpApi.initProver(spend.buffer.asUint8List(), output.buffer.asUint8List());
968 + await ZcashTaddressRotation.init();
969 + await ZcashTransactionInfo.init();
970 + _initialized = true;
971 + }
972 +
973 + static Future<int> getHeightByDate(final DateTime date) {
974 + return WarpApi.getBlockHeightByTime(coin, date);
975 + }
976 +}
cw_zcash/lib/src/zcash_wallet_addresses.dart new
+223
@@ -0,0 +1,223 @@
1 +import 'package:cw_core/utils/print_verbose.dart';
2 +import 'package:cw_core/wallet_addresses.dart';
3 +import 'package:cw_core/wallet_info.dart';
4 +import 'package:cw_zcash/cw_zcash.dart';
5 +import 'package:cw_zcash/src/zcash_taddress_rotation.dart';
6 +import 'package:mobx/mobx.dart';
7 +import 'package:warp_api/warp_api.dart';
8 +
9 +part 'zcash_wallet_addresses.g.dart';
10 +
11 +class ZcashWalletAddresses = ZcashWalletAddressesBase with _$ZcashWalletAddresses;
12 +
13 +abstract class ZcashWalletAddressesBase extends WalletAddresses with Store {
14 + ZcashWalletAddressesBase(this.accountId, final WalletInfo walletInfo) : super(walletInfo) {
15 + addressPageType = ZcashReceivePageOption.typeFromString(walletInfo.addressPageType ?? "");
16 + }
17 +
18 + final int accountId;
19 +
20 + static const int transparentType = 0;
21 + static const int shieldedSaplingType = 1;
22 + static const int shieldedOrchardType = 2;
23 + static const int unifiedType = 3;
24 +
25 + String? _transparentAddress;
26 + String? _saplingAddress;
27 + String? _orchardAddress;
28 + String? _unifiedAddress;
29 +
30 + @override
31 + @computed
32 + String get latestAddress {
33 + switch (addressPageType) {
34 + case ZcashAddressType.transparent:
35 + return transparentAddress;
36 + case ZcashAddressType.transparentRotated:
37 + // just to display something, after the wallet is synced the address
38 + // will start rotating, and since we can't rotate address until the wallet syncs
39 + // we can't know if some T address already got used, so we would still default to
40 + // the same address.
41 + // So.. we can
42 + // 1) display placeholder text (looks ugly)
43 + // 2) display static address
44 + // 3) display first address from rotated pool which is also static until synced
45 + // 2 seems like an obvious winner simply because it offers little to no benefit over 3
46 + // and is noticably less complex in implementation
47 + return transparentAddressRotated ?? transparentAddress;
48 + case ZcashAddressType.shieldedSapling:
49 + return saplingAddress;
50 + case ZcashAddressType.shieldedOrchard:
51 + return orchardAddress;
52 + case ZcashAddressType.unifiedType:
53 + if (_unifiedAddress == null) {
54 + _unifiedAddress = WarpApi.getAddress(ZcashWalletBase.coin, accountId, 7);
55 + }
56 + return _unifiedAddress ?? "";
57 + }
58 + }
59 +
60 + @observable
61 + ZcashAddressType? _addressPageType;
62 +
63 + @computed
64 + ZcashAddressType get addressPageType => _addressPageType ?? ZcashAddressType.shieldedOrchard;
65 +
66 + @computed
67 + set addressPageType(final ZcashAddressType newZat) {
68 + _addressPageType = newZat;
69 + init();
70 + address = latestAddress;
71 + }
72 +
73 + @action
74 + Future<void> setAddressType(final ZcashAddressType type) async {
75 + addressPageType = ZcashReceivePageOption.typeFromString(type.toString());
76 + walletInfo.addressPageType = type.toString();
77 + await walletInfo.save();
78 + }
79 +
80 + String get transparentAddress {
81 + try {
82 + if (_transparentAddress == null) {
83 + _transparentAddress = WarpApi.getTAddr(ZcashWalletBase.coin, accountId);
84 + }
85 + return _transparentAddress ?? "";
86 + } catch (e) {
87 + return "";
88 + }
89 + }
90 +
91 + @computed
92 + String? get transparentAddressRotated {
93 + try {
94 + return ZcashTaddressRotation.addressForAccount(accountId);
95 + } catch (e) {
96 + return null;
97 + }
98 + }
99 +
100 + String get saplingAddress {
101 + try {
102 + if (_saplingAddress == null) {
103 + _saplingAddress = WarpApi.getAddress(ZcashWalletBase.coin, accountId, 2);
104 + }
105 + return _saplingAddress ?? "";
106 + } catch (e) {
107 + return "";
108 + }
109 + }
110 +
111 + String get orchardAddress {
112 + try {
113 + if (_orchardAddress == null) {
114 + _orchardAddress = WarpApi.getAddress(ZcashWalletBase.coin, accountId, 4);
115 + }
116 + return _orchardAddress ?? "";
117 + } catch (e) {
118 + return "";
119 + }
120 + }
121 +
122 + @override
123 + String get addressForExchange => transparentAddressRotated ?? transparentAddress;
124 +
125 + @override
126 + bool containsAddress(final String address) {
127 + return this.address == address || addressesMap.values.contains(address);
128 + }
129 +
130 + static int get coin => ZcashWalletBase.coin;
131 +
132 + @override
133 + Future<void> init() async {
134 + addressesMap = await walletInfo.getAddresses();
135 + addressInfos = await walletInfo.getAddressInfos();
136 + usedAddresses = await walletInfo.getUsedAddresses();
137 + manualAddresses = await walletInfo.getManualAddresses();
138 + hiddenAddresses = await walletInfo.getHiddenAddresses();
139 +
140 + addressesMap = {"a": "b"};
141 +
142 + await ZcashTaddressRotation.init();
143 + printV(ZcashTaddressRotation.rotationAccounts.keys);
144 + int accountIndex = 0;
145 + addressInfos = {
146 + 0:
147 + ZcashTaddressRotation.allAddressesForAccount(accountId)?.map((final v) {
148 + return WalletInfoAddressInfo(
149 + walletInfoId: walletInfo.internalId,
150 + mapKey: ++accountIndex,
151 + accountIndex: 0,
152 + address: v,
153 + label: "",
154 + );
155 + }).toList() ??
156 + [],
157 + };
158 + hiddenAddresses = ZcashTaddressRotation.allUsedAddressesForAccount(accountId)?.toSet() ?? {};
159 +
160 + addressInfos[0]?.removeWhere((final test) => hiddenAddresses.contains(test.address));
161 + if (_addressPageType == ZcashAddressType.transparentRotated) {
162 + final addr = ZcashTaddressRotation.addressForAccount(accountId);
163 + if (addr != null) {
164 + address = addr;
165 + }
166 + }
167 + await saveAddressesInBox();
168 + }
169 +
170 + @override
171 + @observable
172 + late String _address = latestAddress;
173 +
174 + String get address {
175 + if (addressPageType == ZcashAddressType.transparentRotated) {
176 + return transparentAddressRotated ?? transparentAddress;
177 + }
178 + return _address;
179 + }
180 +
181 + void set address(final String _$address) => _address = _$address;
182 +
183 + @override
184 + String get primaryAddress => address;
185 +
186 + @override
187 + Future<void> saveAddressesInBox() async {
188 + try {
189 + await walletInfo.setAddresses(addressesMap);
190 + await walletInfo.setAddressInfos(addressInfos);
191 + await walletInfo.setUsedAddresses(usedAddresses.toList());
192 + await walletInfo.setHiddenAddresses(hiddenAddresses.toList());
193 + await walletInfo.setManualAddresses(manualAddresses.toList());
194 + } catch (e) {
195 + printV("Error saving addresses: $e");
196 + }
197 + }
198 +
199 + @override
200 + Future<void> updateAddressesInBox() async {
201 + _transparentAddress = null;
202 + _saplingAddress = null;
203 + _orchardAddress = null;
204 + _unifiedAddress = null;
205 + }
206 +
207 + @override
208 + Map<String, String> get usableAddressesMap => addressesMap;
209 +
210 + @override
211 + Map<String, String> get usableAllAddressesMap => allAddressesMap;
212 +
213 + List<WalletInfoAddressInfo> getAddressInfos() {
214 + if (addressPageType != ZcashAddressType.transparentRotated) {
215 + return [];
216 + }
217 + final List<WalletInfoAddressInfo> allInfos = [];
218 + for (final entry in addressInfos.entries) {
219 + allInfos.addAll(entry.value);
220 + }
221 + return allInfos;
222 + }
223 +}
cw_zcash/lib/src/zcash_wallet_service.dart new
+181
@@ -0,0 +1,181 @@
1 +import 'dart:io';
2 +
3 +import 'package:bip39/bip39.dart' as bip39;
4 +import 'package:cw_core/pathForWallet.dart';
5 +import 'package:cw_core/utils/print_verbose.dart';
6 +import 'package:cw_core/wallet_base.dart';
7 +import 'package:cw_core/wallet_credentials.dart';
8 +import 'package:cw_core/wallet_info.dart';
9 +import 'package:cw_core/wallet_service.dart';
10 +import 'package:cw_core/wallet_type.dart';
11 +import 'package:cw_zcash/cw_zcash.dart';
12 +import 'package:mutex/mutex.dart';
13 +import 'package:path/path.dart' as p;
14 +
15 +class ZcashWalletService
16 + extends
17 + WalletService<
18 + ZcashNewWalletCredentials,
19 + ZcashFromSeedWalletCredentials,
20 + ZcashFromKeysWalletCredentials,
21 + ZcashNewWalletCredentials
22 + > {
23 + ZcashWalletService();
24 +
25 + static Mutex dbMutex = Mutex();
26 + static int dbMutexQueue = 0;
27 + static Future<T> runInDbMutex<T>(final Future<T> Function() call) async {
28 + try {
29 + printV("dbMutexQueue: ${dbMutexQueue++}");
30 + await dbMutex.acquire();
31 + return await call();
32 + } finally {
33 + dbMutex.release();
34 + dbMutexQueue--;
35 + }
36 + }
37 +
38 + static Set<String> autoshieldTx = {};
39 +
40 + static Future<void> addShieldedTx(final String txId) async {
41 + final pathForWalletType = await pathForWalletTypeDir(type: type);
42 + final shieldedListFile = p.join(pathForWalletType, "autoshield_list.txt");
43 + autoshieldTx.add(txId.replaceAll(RegExp(r'[^a-zA-Z0-9 ]'), ''));
44 + final shieldedList = File(shieldedListFile);
45 + shieldedList.writeAsStringSync(autoshieldTx.join("\n"));
46 + }
47 +
48 + static Future<void> loadShieldTxs() async {
49 + try {
50 + final pathForWalletType = await pathForWalletTypeDir(type: type);
51 + final shieldedListFile = p.join(pathForWalletType, "autoshield_list.txt");
52 + final shieldedList = File(shieldedListFile);
53 + if (!shieldedList.existsSync()) {
54 + return;
55 + }
56 + autoshieldTx = shieldedList.readAsLinesSync().toSet();
57 + } catch (e) {
58 + printV("loadShieldTxs failed: $e");
59 + }
60 + }
61 +
62 + static bool walletFilesExist(final String path) =>
63 + !File(path).existsSync() && !File('$path.keys').existsSync();
64 +
65 + @override
66 + WalletType getType() => type;
67 +
68 + static WalletType get type => WalletType.zcash;
69 +
70 + @override
71 + Future<ZcashWallet> create(final WalletCredentials credentials, {final bool? isTestnet}) {
72 + return ZcashWalletBase.create(credentials);
73 + }
74 +
75 + @override
76 + Future<bool> isWalletExit(final String name) async {
77 + final path = await pathForWallet(name: name, type: getType());
78 + return File(path).existsSync();
79 + }
80 +
81 + @override
82 + Future<ZcashWallet> openWallet(final String name, final String password) async {
83 + await loadShieldTxs();
84 + final walletInfo = await WalletInfo.get(name, getType());
85 + if (walletInfo == null) {
86 + throw Exception('Wallet not found');
87 + }
88 + try {
89 + final wallet = await ZcashWalletBase.open(
90 + name: name,
91 + password: password,
92 + walletInfo: walletInfo,
93 + );
94 + await wallet.init();
95 + await saveBackup(name);
96 + return wallet;
97 + } catch (e) {
98 + await restoreWalletFilesFromBackup(name);
99 + final wallet = await ZcashWalletBase.open(
100 + name: name,
101 + password: password,
102 + walletInfo: walletInfo,
103 + );
104 + await wallet.init();
105 + return wallet;
106 + }
107 + }
108 +
109 + @override
110 + Future<void> remove(final String wallet) async {
111 + final path = await pathForWalletDir(name: wallet, type: getType());
112 + final file = Directory(path);
113 + final isExist = file.existsSync();
114 +
115 + if (isExist) {
116 + await file.delete(recursive: true);
117 + }
118 +
119 + final walletInfo = await WalletInfo.get(wallet, getType());
120 + if (walletInfo == null) {
121 + throw Exception('Wallet not found');
122 + }
123 + await WalletInfo.delete(walletInfo);
124 + }
125 +
126 + @override
127 + Future<void> rename(final String currentName, final String password, final String newName) async {
128 + final currentWalletInfo = await WalletInfo.get(currentName, getType());
129 + if (currentWalletInfo == null) {
130 + throw Exception('Wallet not found');
131 + }
132 + final accountId = await ZcashWalletBase.getZcashAccountIdForName(currentName);
133 + if (accountId == null) {
134 + throw Exception('Wallet account not found for name: $currentName');
135 + }
136 + final currentWallet = ZcashWallet(
137 + currentWalletInfo,
138 + await currentWalletInfo.getDerivationInfo(),
139 + accountId: accountId,
140 + );
141 +
142 + await currentWallet.renameWalletFiles(newName);
143 +
144 + final newWalletInfo = currentWalletInfo;
145 + newWalletInfo.id = WalletBase.idFor(newName, getType());
146 + newWalletInfo.name = newName;
147 +
148 + await newWalletInfo.save();
149 + }
150 +
151 + @override
152 + Future<ZcashWallet> restoreFromKeys(
153 + final ZcashFromKeysWalletCredentials credentials, {
154 + final bool? isTestnet,
155 + }) async {
156 + throw UnimplementedError();
157 + }
158 +
159 + @override
160 + Future<ZcashWallet> restoreFromSeed(
161 + final ZcashFromSeedWalletCredentials credentials, {
162 + final bool? isTestnet,
163 + }) async {
164 + if (credentials.seed == null || credentials.seed!.isEmpty) {
165 + throw ZcashMnemonicIsIncorrectException();
166 + }
167 +
168 + if (!bip39.validateMnemonic(credentials.seed!)) {
169 + throw ZcashMnemonicIsIncorrectException();
170 + }
171 +
172 + return ZcashWalletBase.restore(credentials);
173 + }
174 +
175 + @override
176 + Future<ZcashWallet> restoreFromHardwareWallet(final ZcashNewWalletCredentials credentials) {
177 + throw UnimplementedError(
178 + "Restoring a Zcash wallet from a hardware wallet is not yet supported!",
179 + );
180 + }
181 +}
cw_zcash/pubspec.yaml new
+29
@@ -0,0 +1,29 @@
1 +name: cw_zcash
2 +description: Zcash library for cake wallet
3 +version: 1.0.0
4 +publish_to: none
5 +
6 +environment:
7 + sdk: ^3.8.0
8 +
9 +dependencies:
10 + flutter:
11 + sdk: flutter
12 + warp_api:
13 + path: ../scripts/zcash_lib/packages/warp_api_ffi
14 + cw_core:
15 + path: ../cw_core
16 + mobx: ^2.5.0
17 + bip39: ^1.0.6
18 + mutex: ^3.1.0
19 +
20 + path: any
21 + flat_buffers: any
22 +dev_dependencies:
23 + flutter_test:
24 + sdk: flutter
25 + build_runner: ^2.4.15
26 + build_resolvers: ^2.4.4
27 + mobx_codegen: ^2.0.7
28 + hive_generator: ^2.0.1
29 + ffigen: ^16.1.0
integration_test/components/common_test_flows.dart
+2
@@ -370,6 +370,8 @@ class CommonTestFlows {
370 return secrets.decredTestWalletSeeds;
371 case WalletType.dogecoin:
372 return secrets.dogeTestWalletSeeds;
373 + case WalletType.zcash:
374 + return secrets.zcashTestWalletSeeds;
375 case WalletType.none:
376 case WalletType.haven:
377 case WalletType.banano:
ios/Podfile.lock
+6
@@ -139,6 +139,8 @@ PODS:
139 - Flutter
140 - wakelock_plus (0.0.1):
141 - Flutter
142 + - warp_api (0.0.1):
143 + - Flutter
144 - YttriumWrapper (0.8.35)
145
146 DEPENDENCIES:
@@ -179,6 +181,7 @@ DEPENDENCIES:
181 - universal_ble (from `.symlinks/plugins/universal_ble/darwin`)
182 - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
183 - wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
184 + - warp_api (from `.symlinks/plugins/warp_api/ios`)
185
186 SPEC REPOS:
187 https://github.com/CocoaPods/Specs.git:
@@ -264,6 +267,8 @@ EXTERNAL SOURCES:
267 :path: ".symlinks/plugins/url_launcher_ios/ios"
268 wakelock_plus:
269 :path: ".symlinks/plugins/wakelock_plus/ios"
270 + warp_api:
271 + :path: ".symlinks/plugins/warp_api/ios"
272
273 SPEC CHECKSUMS:
274 bitbox_flutter: 506f80b961ddf646b0d80cef9f6eadaab96d91b0
@@ -309,6 +314,7 @@ SPEC CHECKSUMS:
314 universal_ble: cf52a7b3fd2e7c14d6d7262e9fdadb72ab6b88a6
315 url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
316 wakelock_plus: 76957ab028e12bfa4e66813c99e46637f367fc7e
317 + warp_api: 4a60cc00440d5eb56ae4dbf75f89a34ed90ddf1b
318 YttriumWrapper: 31e937fe9fbe0f1314d2ca6be9ce9b379a059966
319
320 PODFILE CHECKSUM: 5296465b1c6d14d506230356756826012f65d97a
ios/Runner.xcodeproj/project.pbxproj
+6
@@ -25,6 +25,7 @@
25 A1B4A70C9CFA13AB71662216 /* LnurlPay.swift in Resources */ = {isa = PBXBuildFile; fileRef = 7D3364C03978A8A74B6D586E /* LnurlPay.swift */; settings = {ASSET_TAGS = (BreezSDK, ); }; };
26 A3D5E17CC53DF13FA740DEFA /* RedeemSwap.swift in Resources */ = {isa = PBXBuildFile; fileRef = 9D2F2C9F2555316C95EE7EA3 /* RedeemSwap.swift */; settings = {ASSET_TAGS = (BreezSDK, ); }; };
27 B6C6E59403ACDE44724C12F4 /* ServiceConfig.swift in Resources */ = {isa = PBXBuildFile; fileRef = B3D5E78267F5F18D882FDC3B /* ServiceConfig.swift */; settings = {ASSET_TAGS = (BreezSDK, ); }; };
28 + CE3D5B792F142305004D767D /* WarpApiFFI.xcframework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE3D5B782F142305004D767D /* WarpApiFFI.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
29 CE918BF82D533ECE007F186E /* MoneroWallet.xcframework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE918BF72D533ECE007F186E /* MoneroWallet.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
30 CE918BFA2D533ED4007F186E /* WowneroWallet.xcframework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE918BF92D533ED4007F186E /* WowneroWallet.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
31 CE918BFC2D533ED8007F186E /* ZanoWallet.xcframework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE918BFB2D533ED8007F186E /* ZanoWallet.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
@@ -46,6 +47,7 @@
47 CE918BF82D533ECE007F186E /* MoneroWallet.xcframework in CopyFiles */,
48 CE918BFA2D533ED4007F186E /* WowneroWallet.xcframework in CopyFiles */,
49 CE918BFC2D533ED8007F186E /* ZanoWallet.xcframework in CopyFiles */,
50 + CE3D5B792F142305004D767D /* WarpApiFFI.xcframework in CopyFiles */,
51 );
52 runOnlyForDeploymentPostprocessing = 0;
53 };
@@ -85,9 +87,11 @@
87 B3D5E78267F5F18D882FDC3B /* ServiceConfig.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ServiceConfig.swift; path = "../.symlinks/plugins/breez_sdk/ios/bindings-swift/Sources/BreezSDK/ServiceConfig.swift"; sourceTree = "<group>"; };
88 B478EFCC3C0760ED97FFE561 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
89 C58D93382C00FAC6004BCF69 /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = SDKROOT; };
90 + CE3D5B782F142305004D767D /* WarpApiFFI.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = WarpApiFFI.xcframework; path = ../scripts/zcash_lib/WarpApiFFI.xcframework; sourceTree = "<group>"; };
91 CE918BF72D533ECE007F186E /* MoneroWallet.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = MoneroWallet.xcframework; sourceTree = "<group>"; };
92 CE918BF92D533ED4007F186E /* WowneroWallet.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = WowneroWallet.xcframework; sourceTree = "<group>"; };
93 CE918BFB2D533ED8007F186E /* ZanoWallet.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = ZanoWallet.xcframework; sourceTree = "<group>"; };
94 + CEFC07A12EF2EEEB0012DB29 /* WarpApiFFI.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = WarpApiFFI.xcframework; path = ../../zcash_lib/WarpApiFFI.xcframework; sourceTree = "<group>"; };
95 D67E45DEB6E9449D981B35E0 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
96 DCEA540E3586164FB47AD13E /* LnurlPayInvoice.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LnurlPayInvoice.swift; path = "../.symlinks/plugins/breez_sdk/ios/bindings-swift/Sources/BreezSDK/Task/LnurlPayInvoice.swift"; sourceTree = "<group>"; };
97 F2F2EF79B5A1520AF76225B9 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
@@ -113,6 +117,7 @@
117 CE918BFB2D533ED8007F186E /* ZanoWallet.xcframework */,
118 CE918BF92D533ED4007F186E /* WowneroWallet.xcframework */,
119 CE918BF72D533ECE007F186E /* MoneroWallet.xcframework */,
120 + CEFC07A12EF2EEEB0012DB29 /* WarpApiFFI.xcframework */,
121 C58D93382C00FAC6004BCF69 /* libresolv.tbd */,
122 0C9986A3251A932F00D566FD /* CryptoSwift.framework */,
123 B478EFCC3C0760ED97FFE561 /* Pods_Runner.framework */,
@@ -163,6 +168,7 @@
168 97C146E51CF9000F007C117D = {
169 isa = PBXGroup;
170 children = (
171 + CE3D5B782F142305004D767D /* WarpApiFFI.xcframework */,
172 0C44A7182518EF4A00B570ED /* CakeWallet */,
173 9740EEB11CF90186004384FC /* Flutter */,
174 97C146F01CF9000F007C117D /* Runner */,
ios/Runner/InfoBase.plist
+20
@@ -342,6 +342,26 @@
342 <string>arbitrum-wallet</string>
343 </array>
344 </dict>
345 + <dict>
346 + <key>CFBundleTypeRole</key>
347 + <string>Viewer</string>
348 + <key>CFBundleURLName</key>
349 + <string>zcash</string>
350 + <key>CFBundleURLSchemes</key>
351 + <array>
352 + <string>zcash</string>
353 + </array>
354 + </dict>
355 + <dict>
356 + <key>CFBundleTypeRole</key>
357 + <string>Viewer</string>
358 + <key>CFBundleURLName</key>
359 + <string>zcash-wallet</string>
360 + <key>CFBundleURLSchemes</key>
361 + <array>
362 + <string>zcash-wallet</string>
363 + </array>
364 + </dict>
365 </array>
366 <key>CFBundleVersion</key>
367 <string>$(CURRENT_PROJECT_VERSION)</string>
lib/anonpay/anonpay_api.dart
+1 -1
@@ -35,7 +35,7 @@ class AnonPayApi {
35 clearnetUri: Uri.https(clearNetAuthority, "$anonPayStatus/$id"),
36 onionUri: Uri.https(onionApiAuthority, "$anonPayStatus/$id"),
37 );
38 -
38 +
39 final responseJSON = json.decode(response.body) as Map<String, dynamic>;
40 final status = responseJSON['Status'] as String;
41 final fiatAmount = responseJSON['Fiat_Amount'] as double?;
lib/buy/robinhood/robinhood_buy_provider.dart
+1
@@ -122,6 +122,7 @@ class RobinhoodBuyProvider extends BuyProvider {
122 case WalletType.banano:
123 case WalletType.wownero:
124 case WalletType.zano:
125 + case WalletType.zcash:
126 case WalletType.decred:
127 throw Exception("Wallet Type ${wallet.type.name} is not available for Robinhood");
128 }
lib/cake_pay/src/models/cake_pay_order.dart
+9 -2
@@ -1,4 +1,4 @@
1 -enum CakePayPaymentMethod { BTC, BTC_LN, XMR, LTC, LTC_MWEB }
1 +enum CakePayPaymentMethod { BTC, BTC_LN, XMR, LTC, LTC_MWEB, ZEC }
2
3 extension CakePayPaymentMethodLabel on CakePayPaymentMethod {
4 String get label => switch (this) {
@@ -7,6 +7,7 @@ extension CakePayPaymentMethodLabel on CakePayPaymentMethod {
7 CakePayPaymentMethod.XMR => 'Monero',
8 CakePayPaymentMethod.LTC => 'Litecoin',
9 CakePayPaymentMethod.LTC_MWEB => 'MWEB',
10 + CakePayPaymentMethod.ZEC => 'Zcash',
11 };
12 }
13
@@ -63,7 +64,9 @@ class CakePayOrder {
64 CakePayPaymentMethod.XMR => order.paymentData.xmr,
65 CakePayPaymentMethod.LTC => order.paymentData.ltc,
66 CakePayPaymentMethod.LTC_MWEB => order.paymentData.ltc_mweb,
66 - _ => null
67 + CakePayPaymentMethod.ZEC => order.paymentData.zec,
68 + // TODO: Handle this case.
69 + CakePayPaymentMethod.BTC_LN => throw UnimplementedError(),
70 };
71
72 if (data == null) return null;
@@ -87,6 +90,7 @@ class CakePayOrder {
90 CakePayPaymentMethod.XMR => 'XMR',
91 CakePayPaymentMethod.LTC => 'LTC',
92 CakePayPaymentMethod.LTC_MWEB => 'LTC',
93 + CakePayPaymentMethod.ZEC => 'ZEC',
94 };
95 }
96 }
@@ -129,6 +133,7 @@ class PaymentData {
133 final CryptoPaymentData? xmr;
134 final CryptoPaymentData? ltc;
135 final CryptoPaymentData? ltc_mweb;
136 + final CryptoPaymentData? zec;
137 final DateTime invoiceTime;
138 final DateTime expirationTime;
139 final int? commission;
@@ -139,6 +144,7 @@ class PaymentData {
144 required this.xmr,
145 required this.ltc,
146 required this.ltc_mweb,
147 + required this.zec,
148 required this.invoiceTime,
149 required this.expirationTime,
150 required this.commission,
@@ -151,6 +157,7 @@ class PaymentData {
157 xmr: map['XMR'] != null ? CryptoPaymentData.fromMap(map['XMR'] as Map<String, dynamic>) : null,
158 ltc: map['LTC'] != null ? CryptoPaymentData.fromMap(map['LTC'] as Map<String, dynamic>) : null,
159 ltc_mweb: map['LTC_MWEB'] != null ? CryptoPaymentData.fromMap(map['LTC_MWEB'] as Map<String, dynamic>) : null,
160 + zec: map['ZEC_CHAIN'] != null ? CryptoPaymentData.fromMap(map['ZEC_CHAIN'] as Map<String, dynamic>) : null,
161 invoiceTime: DateTime.fromMillisecondsSinceEpoch(map['invoice_time'] as int),
162 expirationTime: DateTime.fromMillisecondsSinceEpoch(map['expiration_time'] as int),
163 commission: map['commission'] as int?,
lib/core/address_validator.dart
+17 -5
@@ -135,10 +135,15 @@ class AddressValidator extends TextValidator {
135 '|[13][a-km-zA-HJ-NP-Z1-9]{25,34}';
136 case CryptoCurrency.hbar:
137 pattern = '[0-9a-zA-Z.]+';
138 - case CryptoCurrency.zaddr:
139 - pattern = 'zs[0-9a-zA-Z]{75}';
138 case CryptoCurrency.zec:
141 - pattern = 't1[0-9a-zA-Z]{33}|t3[0-9a-zA-Z]{33}';
139 + pattern =
140 + '(?:'
141 + 't1[0-9A-Za-z]{33}'
142 + '|t3[0-9A-Za-z]{33}'
143 + '|zs[a-z0-9]{76}'
144 + '|zxviews[a-z0-9]{278}'
145 + '|u1[a-z0-9]{1,300}'
146 + ')';
147 case CryptoCurrency.dcr:
148 pattern = '(D|T|S)[ksecS]([0-9a-zA-Z])+';
149 case CryptoCurrency.rvn:
@@ -282,8 +287,6 @@ class AddressValidator extends TextValidator {
287 return [34];
288 case CryptoCurrency.zen:
289 return [35];
285 - case CryptoCurrency.zaddr:
286 - return null;
290 case CryptoCurrency.zec:
291 return null;
292 case CryptoCurrency.kmd:
@@ -352,6 +355,15 @@ class AddressValidator extends TextValidator {
355 pattern = '(T|t)[1-9A-HJ-NP-Za-km-z]{33}';
356 case CryptoCurrency.zano:
357 pattern = '([1-9A-HJ-NP-Za-km-z]{90,200})|(@[\w\d.-]+)';
358 + case CryptoCurrency.zec:
359 + pattern =
360 + '(?:'
361 + 't1[0-9A-Za-z]{33}'
362 + '|t3[0-9A-Za-z]{33}'
363 + '|zs[a-z0-9]{76}'
364 + '|zxviews[a-z0-9]{278}'
365 + '|u1[a-z0-9]{1,300}'
366 + ')';
367 default:
368 if (type.tag == CryptoCurrency.eth.title) {
369 pattern = '0x[0-9a-zA-Z]+';
lib/core/payment_uris.dart
+15
@@ -253,6 +253,21 @@ class DogeURI extends PaymentURI {
253 }
254 }
255
256 +class ZcashURI extends PaymentURI {
257 + ZcashURI({required super.amount, required super.address});
258 +
259 + @override
260 + String toString() {
261 + var base = 'zcash:$address';
262 +
263 + if (amount.isNotEmpty) {
264 + base += '?amount=${amount.replaceAll(',', '.')}';
265 + }
266 +
267 + return base;
268 + }
269 +}
270 +
271 class ERC681URI extends PaymentURI {
272 final int chainId;
273 final String? contractAddress;
lib/core/seed_validator.dart
+3
@@ -10,6 +10,7 @@ import 'package:cake_wallet/wownero/wownero.dart';
10 import 'package:cake_wallet/zano/zano.dart';
11 import 'package:cake_wallet/decred/decred.dart';
12 import 'package:cake_wallet/utils/language_list.dart';
13 +import 'package:cake_wallet/zcash/zcash.dart';
14 import 'package:cw_core/wallet_type.dart';
15
16 class SeedValidator extends Validator<MnemonicItem> {
@@ -52,6 +53,8 @@ class SeedValidator extends Validator<MnemonicItem> {
53 return zano!.getWordList(language);
54 case WalletType.decred:
55 return decred!.getDecredWordList();
56 + case WalletType.zcash:
57 + return zcash!.getZcashWordList(language);
58 case WalletType.none:
59 case WalletType.haven:
60 return [];
lib/core/wallet_creation_service.dart
+1
@@ -84,6 +84,7 @@ class WalletCreationService {
84 case WalletType.tron:
85 case WalletType.dogecoin:
86 case WalletType.nano:
87 + case WalletType.zcash:
88 return true;
89 case WalletType.monero:
90 case WalletType.wownero:
lib/di.dart
+4 -1
@@ -62,6 +62,7 @@ import 'package:cake_wallet/view_model/link_view_model.dart';
62 import 'package:cake_wallet/tron/tron.dart';
63 import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart';
64 import 'package:cake_wallet/view_model/start_tor_view_model.dart';
65 +import 'package:cake_wallet/zcash/zcash.dart';
66 import 'package:cw_core/receive_page_option.dart';
67 import 'package:cake_wallet/entities/wallet_edit_page_arguments.dart';
68 import 'package:cake_wallet/entities/wallet_manager.dart';
@@ -1109,7 +1110,7 @@ Future<void> setup({
1110 getIt.registerFactory<MoonPayProvider>(() => MoonPayProvider(
1111 appStore: getIt.get<AppStore>(),
1112 wallet: getIt.get<AppStore>().wallet!,
1112 - isTestEnvironment: kDebugMode,
1113 + isTestEnvironment: kDebugMode || kProfileMode,
1114 ));
1115
1116 getIt.registerFactory<OnRamperBuyProvider>(() => OnRamperBuyProvider(
@@ -1224,6 +1225,8 @@ Future<void> setup({
1225 return decred!.createDecredWalletService(_unspentCoinsInfoSource);
1226 case WalletType.haven:
1227 return HavenWalletService();
1228 + case WalletType.zcash:
1229 + return zcash!.createZcashWalletService(SettingsStoreBase.walletPasswordDirectInput);
1230 case WalletType.none:
1231 throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService');
1232 }
lib/entities/default_settings_migration.dart
+20 -1
@@ -52,6 +52,7 @@ const decredDefaultUri = "default-spv-nodes";
52 const dogecoinDefaultNodeUri = 'dogecoin.stackwallet.com:50022';
53 const baseDefaultNodeUri = 'base.nownodes.io';
54 const arbitrumDefaultNodeUri = 'arbitrum.nownodes.io';
55 +const zcashDefaultNodeUri = 'zec-node.cakewallet.com:443';
56
57 Future<void> defaultSettingsMigration(
58 {required int version,
@@ -559,7 +560,14 @@ Future<void> defaultSettingsMigration(
560 case 54:
561 await _backupWowneroSeeds(havenSeedStore);
562 break;
562 -
563 + case 55:
564 + await addWalletNodeList(nodes: nodes, type: WalletType.zcash);
565 + await _changeDefaultNode(
566 + nodes: nodes,
567 + sharedPreferences: sharedPreferences,
568 + type: WalletType.zcash,
569 + currentNodePreferenceKey: PreferencesKey.currentZcashNodeIdKey,
570 + );
571 default:
572 break;
573 }
@@ -672,6 +680,8 @@ String _getDefaultNodeUri(WalletType type) {
680 return baseDefaultNodeUri;
681 case WalletType.arbitrum:
682 return arbitrumDefaultNodeUri;
683 + case WalletType.zcash:
684 + return zcashDefaultNodeUri;
685 case WalletType.banano:
686 case WalletType.none:
687 return '';
@@ -1092,6 +1102,7 @@ Future<void> checkCurrentNodes(
1102 final currentTronNodeId = sharedPreferences.getInt(PreferencesKey.currentTronNodeIdKey);
1103 final currentWowneroNodeId = sharedPreferences.getInt(PreferencesKey.currentWowneroNodeIdKey);
1104 final currentZanoNodeId = sharedPreferences.getInt(PreferencesKey.currentZanoNodeIdKey);
1105 + final currentZcashNodeId = sharedPreferences.getInt(PreferencesKey.currentZcashNodeIdKey);
1106 final currentMoneroNode =
1107 nodeSource.values.firstWhereOrNull((node) => node.key == currentMoneroNodeId);
1108 final currentBitcoinElectrumServer =
@@ -1126,6 +1137,8 @@ Future<void> checkCurrentNodes(
1137 nodeSource.values.firstWhereOrNull((node) => node.key == currentWowneroNodeId);
1138 final currentZanoNode =
1139 nodeSource.values.firstWhereOrNull((node) => node.key == currentZanoNodeId);
1140 + final currentZcashNode =
1141 + nodeSource.values.firstWhereOrNull((node) => node.key == currentZcashNodeId);
1142
1143 if (currentMoneroNode == null) {
1144 final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
@@ -1240,6 +1253,12 @@ Future<void> checkCurrentNodes(
1253 await nodeSource.add(node);
1254 await sharedPreferences.setInt(PreferencesKey.currentDecredNodeIdKey, node.key as int);
1255 }
1256 +
1257 + if (currentZcashNode == null) {
1258 + final node = Node(uri: zcashDefaultNodeUri, type: WalletType.zcash, useSSL: true);
1259 + await nodeSource.add(node);
1260 + await sharedPreferences.setInt(PreferencesKey.currentZcashNodeIdKey, node.key as int);
1261 + }
1262 }
1263
1264 Future<void> resetBitcoinElectrumServer(
lib/entities/hash_wallet_identifier.dart
+4 -1
@@ -2,6 +2,7 @@ import 'dart:convert';
2
3 import 'package:cake_wallet/.secrets.g.dart' as secrets;
4 import 'package:cw_core/wallet_base.dart';
5 +import 'package:flutter/foundation.dart';
6 import 'package:hashlib/hashlib.dart';
7
8 String createHashedWalletIdentifier(WalletBase wallet) {
@@ -9,8 +10,10 @@ String createHashedWalletIdentifier(WalletBase wallet) {
10
11 final salt = secrets.walletGroupSalt;
12 final combined = '$salt.$hashContent';
12 -
13 final bytes = utf8.encode(combined);
14 final digest = sha256.convert(bytes);
15 + if (kDebugMode) {
16 + return "$combined;$digest";
17 + }
18 return digest.toString();
19 }
lib/entities/node_list.dart
+6 -1
@@ -56,6 +56,9 @@ Future<List<Node>> loadDefaultNodes(WalletType type) async {
56 case WalletType.arbitrum:
57 path = 'assets/arbitrum_node_list.yml';
58 break;
59 + case WalletType.zcash:
60 + path = 'assets/zcash_node_list.yml';
61 + break;
62 case WalletType.banano:
63 case WalletType.none:
64 path = '';
@@ -109,6 +112,7 @@ Future<void> resetToDefault(Box<Node> nodeSource) async {
112 final dogecoinElectrumServerList = await loadDefaultNodes(WalletType.dogecoin);
113 final baseNodes = await loadDefaultNodes(WalletType.base);
114 final arbitrumNodes = await loadDefaultNodes(WalletType.arbitrum);
115 + final zcashNodes = await loadDefaultNodes(WalletType.zcash);
116
117 final nodes = moneroNodes +
118 bitcoinElectrumServerList +
@@ -124,7 +128,8 @@ Future<void> resetToDefault(Box<Node> nodeSource) async {
128 decredNodes +
129 dogecoinElectrumServerList +
130 baseNodes +
127 - arbitrumNodes;
131 + arbitrumNodes +
132 + zcashNodes;
133
134 await nodeSource.clear();
135 await nodeSource.addAll(nodes);
lib/entities/parse_address_from_domain.dart
+1 -1
@@ -175,7 +175,7 @@ class AddressResolver {
175 var addressPattern = AddressValidator.getAddressFromStringPattern(type);
176
177 if (addressPattern == null) {
178 - throw Exception('Unexpected token: $type for getAddressFromStringPattern');
178 + return null;
179 }
180
181 if (requireSurroundingWhitespaces)
lib/entities/preferences_key.dart
+1
@@ -22,6 +22,7 @@ class PreferencesKey {
22 static const currentSolanaNodeIdKey = 'current_node_id_sol';
23 static const currentTronNodeIdKey = 'current_node_id_trx';
24 static const currentWowneroNodeIdKey = 'current_node_id_wow';
25 + static const currentZcashNodeIdKey = 'current_node_id_zec';
26 static const currentTransactionPriorityKeyLegacy = 'current_fee_priority';
27 static const currentBalanceDisplayModeKey = 'current_balance_display_mode';
28 static const shouldSaveRecipientAddressKey = 'save_recipient_address';
lib/entities/priority_for_wallet_type.dart
+3
@@ -6,6 +6,7 @@ import 'package:cake_wallet/monero/monero.dart';
6 import 'package:cake_wallet/wownero/wownero.dart';
7 import 'package:cake_wallet/zano/zano.dart';
8 import 'package:cake_wallet/decred/decred.dart';
9 +import 'package:cake_wallet/zcash/zcash.dart';
10 import 'package:cw_core/transaction_priority.dart';
11 import 'package:cw_core/wallet_type.dart';
12
@@ -37,6 +38,8 @@ List<TransactionPriority> priorityForWalletType(WalletType type) {
38 return zano!.getTransactionPriorities();
39 case WalletType.decred:
40 return decred!.getTransactionPriorities();
41 + case WalletType.zcash:
42 + return zcash!.getTransactionPriorities();
43 case WalletType.none:
44 case WalletType.haven:
45 return [];
lib/exchange/provider/chainflip_exchange_provider.dart
+1 -3
@@ -16,10 +16,8 @@ import 'package:cw_core/utils/proxy_wrapper.dart';
16 import 'package:cake_wallet/utils/exchange_provider_logger.dart';
17
18 class ChainflipExchangeProvider extends ExchangeProvider {
19 - ChainflipExchangeProvider({required this.tradesStore})
20 - : super(pairList: supportedPairs(_notSupported));
19 + ChainflipExchangeProvider({required this.tradesStore});
20
22 - static final List<CryptoCurrency> _notSupported = [];
21 static final List<CryptoCurrency> _supported = [
22 CryptoCurrency.btc,
23 CryptoCurrency.eth,
lib/exchange/provider/changenow_exchange_provider.dart
+1 -6
@@ -21,13 +21,8 @@ import 'package:cake_wallet/utils/exchange_provider_logger.dart';
21 class ChangeNowExchangeProvider extends ExchangeProvider {
22 ChangeNowExchangeProvider({required SettingsStore settingsStore})
23 : _settingsStore = settingsStore,
24 - _lastUsedRateId = '',
25 - super(pairList: supportedPairs(_notSupported));
24 + _lastUsedRateId = '';
25
27 - static const List<CryptoCurrency> _notSupported = [
28 - CryptoCurrency.zaddr,
29 - CryptoCurrency.xhv,
30 - ];
26
27 static final apiKey =
28 isMoneroOnly ? secrets.changeNowMoneroApiKey : secrets.changeNowCakeWalletApiKey;
lib/exchange/provider/exchange_provider.dart
+1 -3
@@ -6,12 +6,10 @@ import 'package:cake_wallet/exchange/trade_request.dart';
6 import 'package:cw_core/crypto_currency.dart';
7
8 abstract class ExchangeProvider {
9 - ExchangeProvider({required this.pairList});
9 + ExchangeProvider();
10
11 String get title;
12
13 - List<ExchangePair> pairList;
14 -
13 ExchangeProviderDescription get description;
14
15 bool get isAvailable;
lib/exchange/provider/exolix_exchange_provider.dart
+1 -21
@@ -16,33 +16,13 @@ import 'package:cw_core/utils/print_verbose.dart';
16 import 'package:cake_wallet/utils/exchange_provider_logger.dart';
17
18 class ExolixExchangeProvider extends ExchangeProvider {
19 - ExolixExchangeProvider() : super(pairList: supportedPairs(_notSupported));
19 + ExolixExchangeProvider();
20
21 static final apiKey = isMoneroOnly ? secrets.exolixMoneroApiKey : secrets.exolixCakeWalletApiKey;
22 static const apiBaseUrl = 'exolix.com';
23 static const transactionsPath = '/api/v2/transactions';
24 static const ratePath = '/api/v2/rate';
25
26 - static const List<CryptoCurrency> _notSupported = [
27 - CryptoCurrency.usdt,
28 - CryptoCurrency.xhv,
29 - CryptoCurrency.btt,
30 - CryptoCurrency.firo,
31 - CryptoCurrency.zaddr,
32 - CryptoCurrency.xvg,
33 - CryptoCurrency.kmd,
34 - CryptoCurrency.paxg,
35 - CryptoCurrency.rune,
36 - CryptoCurrency.scrt,
37 - CryptoCurrency.btcln,
38 - CryptoCurrency.cro,
39 - CryptoCurrency.ftm,
40 - CryptoCurrency.frax,
41 - CryptoCurrency.gusd,
42 - CryptoCurrency.gtc,
43 - CryptoCurrency.weth,
44 - ];
45 -
26 @override
27 String get title => 'Exolix';
28
lib/exchange/provider/letsexchange_exchange_provider.dart
+1 -3
@@ -16,9 +16,7 @@ import 'package:cw_core/utils/print_verbose.dart';
16 import 'package:cake_wallet/utils/exchange_provider_logger.dart';
17
18 class LetsExchangeExchangeProvider extends ExchangeProvider {
19 - LetsExchangeExchangeProvider() : super(pairList: supportedPairs(_notSupported));
20 -
21 - static const List<CryptoCurrency> _notSupported = [];
19 + LetsExchangeExchangeProvider();
20
21 static const apiKey = secrets.letsExchangeBearerToken;
22 static const _baseUrl = 'api.letsexchange.io';
lib/exchange/provider/near_Intents_exchange_provider.dart
+4 -16
@@ -17,10 +17,7 @@ import 'package:cw_core/utils/print_verbose.dart';
17 import 'package:cake_wallet/utils/exchange_provider_logger.dart';
18
19 class NearIntentsExchangeProvider extends ExchangeProvider {
20 - NearIntentsExchangeProvider()
21 - : super(pairList: supportedPairs(_notSupported));
22 -
23 - static const List<CryptoCurrency> _notSupported = [];
20 + NearIntentsExchangeProvider();
21
22 static const apiKey = secrets.nearIntentsBearerToken;
23 static const _baseUrl = '1click.chaindefuser.com';
@@ -44,7 +41,6 @@ class NearIntentsExchangeProvider extends ExchangeProvider {
41 // UTXO
42 'LTC': 'ltc1qhdwz74m3wuuhppv2mckagqk9e2e49z5j4kucnv',
43 'BTC': 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080',
47 - 'ZZEC': 't1Zcash4hPS2bq8rTQkN9bKpZV1X4W3pZxK',
44 'DOGE': 'D9t7rGQ9mE3hJ2z1w8pGQxkGmKjYwYc8pQ',
45 'BCH': 'qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a',
46
@@ -63,6 +59,7 @@ class NearIntentsExchangeProvider extends ExchangeProvider {
59 'TON': 'UQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c',
60 'XLM': 'GA3D5O2W7YQZJQ3H4Y5QW6N7V8X9Z0A1B2C3D4E5F6G7H8I9J0',
61 'ADA': 'addr1vyc33hdv5vag52f3d8h0qsngu52vm27x28zkzru333jma9gaxd38v',
62 + 'ZEC': 't1VdfFUbyTT7ZSdeEaHuwB7veGD1NXoUhGS',
63 };
64
65 String getNearDummyAddress(CryptoCurrency currency) {
@@ -430,8 +427,7 @@ class NearIntentsExchangeProvider extends ExchangeProvider {
427 try {
428 final uri = Uri.https(_baseUrl, '$_versionPath$_tokenPath');
429
433 - final response =
434 - await ProxyWrapper().get(clearnetUri: uri, headers: _headers);
430 + final response = await ProxyWrapper().get(clearnetUri: uri, headers: _headers);
431 if (response.statusCode != 200) return [];
432
433 final data = json.decode(response.body) as List<dynamic>;
@@ -532,14 +528,6 @@ class NearIntentsExchangeProvider extends ExchangeProvider {
528 };
529 }
530
535 - String _normalizeTitleToNearSymbol(CryptoCurrency currency) {
536 - final title = currency.title.toUpperCase();
537 - return switch (title) {
538 - 'TZEC' => 'ZEC',
539 - _ => title,
540 - };
541 - }
542 -
531 String? _normalizeTagToNearBlockchain(String? tag) {
532 return switch (tag) {
533 'TRX' => 'tron',
@@ -559,7 +547,7 @@ class NearIntentsExchangeProvider extends ExchangeProvider {
547 Token? currencyToNearAssetId(CryptoCurrency currency, List<Token> supported) {
548 if (supported.isEmpty) return null;
549
562 - final symbol = _normalizeTitleToNearSymbol(currency).toUpperCase();
550 + final symbol = currency.title.toUpperCase();
551 final blockchain = _normalizeTagToNearBlockchain(currency.tag);
552
553 // Native asset (no contract)
lib/exchange/provider/sideshift_exchange_provider.dart
+1 -17
@@ -16,23 +16,7 @@ import 'package:cw_core/utils/print_verbose.dart';
16 import 'package:cake_wallet/utils/exchange_provider_logger.dart';
17
18 class SideShiftExchangeProvider extends ExchangeProvider {
19 - SideShiftExchangeProvider() : super(pairList: supportedPairs(_notSupported));
20 -
21 - static const List<CryptoCurrency> _notSupported = [
22 - CryptoCurrency.xhv,
23 - CryptoCurrency.dcr,
24 - CryptoCurrency.kmd,
25 - CryptoCurrency.oxt,
26 - CryptoCurrency.pivx,
27 - CryptoCurrency.rune,
28 - CryptoCurrency.rvn,
29 - CryptoCurrency.scrt,
30 - CryptoCurrency.stx,
31 - CryptoCurrency.bttc,
32 - CryptoCurrency.usdt,
33 - CryptoCurrency.eos,
34 - CryptoCurrency.xmr,
35 - ];
19 + SideShiftExchangeProvider();
20
21 static const affiliateId = secrets.sideShiftAffiliateId;
22 static const apiBaseUrl = 'https://sideshift.ai/api';
lib/exchange/provider/simpleswap_exchange_provider.dart
+1 -7
@@ -16,12 +16,8 @@ import 'package:cw_core/crypto_currency.dart';
16 import 'package:cake_wallet/utils/exchange_provider_logger.dart';
17
18 class SimpleSwapExchangeProvider extends ExchangeProvider {
19 - SimpleSwapExchangeProvider() : super(pairList: supportedPairs(_notSupported));
19 + SimpleSwapExchangeProvider();
20
21 - static const List<CryptoCurrency> _notSupported = [
22 - CryptoCurrency.zaddr,
23 - CryptoCurrency.xhv,
24 - ];
21
22 static final apiKey =
23 DeviceInfo.instance.isMobile ? secrets.simpleSwapApiKey : secrets.simpleSwapApiKeyDesktop;
@@ -347,8 +343,6 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
343
344 static String _normalizeCurrency(CryptoCurrency currency) {
345 switch (currency) {
350 - case CryptoCurrency.zaddr:
351 - return 'zec';
346 case CryptoCurrency.zec:
347 return 'zec';
348 case CryptoCurrency.bnb:
lib/exchange/provider/stealth_ex_exchange_provider.dart
+1 -3
@@ -15,9 +15,7 @@ import 'package:cw_core/crypto_currency.dart';
15 import 'package:cake_wallet/utils/exchange_provider_logger.dart';
16
17 class StealthExExchangeProvider extends ExchangeProvider {
18 - StealthExExchangeProvider() : super(pairList: supportedPairs(_notSupported));
19 -
20 - static const List<CryptoCurrency> _notSupported = [];
18 + StealthExExchangeProvider();
19
20 static final apiKey = secrets.stealthExBearerToken;
21 static final _additionalFeePercent = double.tryParse(secrets.stealthExAdditionalFeePercent);
lib/exchange/provider/swapsxyz_exchange_provider.dart
+1 -3
@@ -15,9 +15,7 @@ import 'package:cw_core/utils/proxy_wrapper.dart';
15 import 'package:cw_core/crypto_currency.dart';
16
17 class SwapsXyzExchangeProvider extends ExchangeProvider {
18 - SwapsXyzExchangeProvider() : super(pairList: supportedPairs(_notSupported));
19 -
20 - static const List<CryptoCurrency> _notSupported = [];
18 + SwapsXyzExchangeProvider();
19
20 static final _apiKey = secrets.swapsXyzApiKey;
21 static const _baseUrl = 'api-v2.swaps.xyz';
lib/exchange/provider/swaptrade_exchange_provider.dart
+6 -21
@@ -16,25 +16,7 @@ import 'package:cw_core/utils/print_verbose.dart';
16 import 'package:cake_wallet/utils/exchange_provider_logger.dart';
17
18 class SwapTradeExchangeProvider extends ExchangeProvider {
19 - SwapTradeExchangeProvider() : super(pairList: supportedPairs(_notSupported));
20 -
21 - static final List<CryptoCurrency> _notSupported = [
22 - ...(CryptoCurrency.all
23 - .where((element) => ![
24 - CryptoCurrency.btc,
25 - CryptoCurrency.sol,
26 - CryptoCurrency.eth,
27 - CryptoCurrency.ltc,
28 - CryptoCurrency.ada,
29 - CryptoCurrency.bch,
30 - CryptoCurrency.usdterc20,
31 - CryptoCurrency.usdttrc20,
32 - CryptoCurrency.bnb,
33 - CryptoCurrency.xmr,
34 - CryptoCurrency.zec,
35 - ].contains(element))
36 - .toList())
37 - ];
19 + SwapTradeExchangeProvider();
20
21 static final markup = secrets.swapTradeExchangeMarkup;
22
@@ -220,8 +202,11 @@ class SwapTradeExchangeProvider extends ExchangeProvider {
202 final responseBody = json.decode(response.body) as Map<String, dynamic>;
203
204 if (response.statusCode == 400 || responseBody["success"] == false) {
223 - final error = responseBody['errors'][0]['msg'] as String;
224 -
205 + final List<dynamic> errorsList = responseBody['errors'] as List? ?? [];
206 + final error = errorsList.isNotEmpty
207 + ? (errorsList[0]['msg'] as String?) ?? responseBody.toString()
208 + : responseBody.toString();
209 +
210 ExchangeProviderLogger.logError(
211 provider: description,
212 function: 'createTrade',
lib/exchange/provider/thorchain_exchange.provider.dart
+1 -20
@@ -14,26 +14,7 @@ import 'package:hive/hive.dart';
14 import 'package:cake_wallet/utils/exchange_provider_logger.dart';
15
16 class ThorChainExchangeProvider extends ExchangeProvider {
17 - ThorChainExchangeProvider({required this.tradesStore})
18 - : super(pairList: supportedPairs(_notSupported));
19 -
20 - static final List<CryptoCurrency> _notSupported = [
21 - ...(CryptoCurrency.all
22 - .where((element) => ![
23 - CryptoCurrency.btc,
24 - // CryptoCurrency.eth,
25 - CryptoCurrency.ltc,
26 - CryptoCurrency.bch,
27 - CryptoCurrency.usdtbsc,
28 - // CryptoCurrency.aave,
29 - // CryptoCurrency.dai,
30 - // CryptoCurrency.gusd,
31 - // CryptoCurrency.usdc,
32 - // CryptoCurrency.usdterc20,
33 - // CryptoCurrency.wbtc, // TODO: temporarily commented until https://github.com/cake-tech/cake_wallet/pull/1436 is merged
34 - ].contains(element))
35 - .toList())
36 - ];
17 + ThorChainExchangeProvider({required this.tradesStore});
18
19 static final isRefundAddressSupported = [CryptoCurrency.eth];
20
lib/exchange/provider/trocador_exchange_provider.dart
+1 -7
@@ -17,8 +17,7 @@ import 'package:cake_wallet/utils/exchange_provider_logger.dart';
17 class TrocadorExchangeProvider extends ExchangeProvider {
18 TrocadorExchangeProvider({this.useTorOnly = false, this.providerStates = const {}})
19 : _lastUsedRateId = '',
20 - _provider = [],
21 - super(pairList: supportedPairs(_notSupported));
20 + _provider = [];
21
22 bool useTorOnly;
23 Map<String, bool> providerStates;
@@ -48,11 +47,6 @@ class TrocadorExchangeProvider extends ExchangeProvider {
47 'SwapSpace',
48 ];
49
51 - static const List<CryptoCurrency> _notSupported = [
52 - CryptoCurrency.stx,
53 - CryptoCurrency.zaddr,
54 - ];
55 -
50 static final apiKey = isMoneroOnly ? secrets.trocadorMoneroApiKey : secrets.trocadorApiKey;
51 static const clearNetAuthority = 'api.trocador.app';
52 static const onionApiAuthority = clearNetAuthority;
lib/exchange/provider/xoswap_exchange_provider.dart
+1 -3
@@ -13,9 +13,7 @@ import 'package:cw_core/utils/print_verbose.dart';
13 import 'package:cw_core/utils/proxy_wrapper.dart';
14 import 'package:cake_wallet/utils/exchange_provider_logger.dart';
15 class XOSwapExchangeProvider extends ExchangeProvider {
16 - XOSwapExchangeProvider() : super(pairList: supportedPairs(_notSupported));
17 -
18 - static const List<CryptoCurrency> _notSupported = [];
16 + XOSwapExchangeProvider();
17
18 static const _apiAuthority = 'exchange.exodus.io';
19 static const _apiPath = '/v3';
lib/main.dart
+9 -1
@@ -34,10 +34,12 @@ import 'package:cake_wallet/utils/exception_handler.dart';
34 import 'package:cake_wallet/utils/feature_flag.dart';
35 import 'package:cake_wallet/view_model/link_view_model.dart';
36 import 'package:cake_wallet/utils/responsive_layout_util.dart';
37 +import 'package:cake_wallet/zcash/zcash.dart';
38 import 'package:cw_core/address_info.dart';
39 import 'package:cw_core/cake_hive.dart';
40 import 'package:cw_core/erc20_token.dart';
41 import 'package:cw_core/hive_type_ids.dart';
42 +import 'package:cw_core/key.dart';
43 import 'package:cw_core/mweb_utxo.dart';
44 import 'package:cw_core/node.dart';
45 import 'package:cw_core/payjoin_session.dart';
@@ -119,6 +121,12 @@ Future<void> runAppWithZone({Key? topLevelKey}) async {
121 if (FeatureFlag.hasDevOptions) {
122 ProxyWrapper.logger = MemoryProxyLogger();
123 }
124 + var zcashPassword = await secureStorageShared.read(key: "com.cakewallet.cw_zcash/zec.db");
125 + if (zcashPassword == null || zcashPassword.isEmpty) {
126 + zcashPassword = generateKey().substring(0,32);
127 + secureStorageShared.write(key: "com.cakewallet.cw_zcash/zec.db", value: zcashPassword);
128 + }
129 + zcash?.unlockDatabase(zcashPassword);
130
131 // Basically when we're running a test
132 if (topLevelKey != null) {
@@ -266,7 +274,7 @@ Future<void> initializeAppConfigs({bool loadWallet = true}) async {
274 payjoinSessionSource: payjoinSessionSource,
275 anonpayInvoiceInfo: anonpayInvoiceInfo,
276 havenSeedStore: havenSeedStore,
269 - initialMigrationVersion: 54,
277 + initialMigrationVersion: 55,
278 );
279 }
280
lib/reactions/on_wallet_sync_status_change.dart
+2
@@ -1,5 +1,7 @@
1 import 'package:cake_wallet/bitcoin/bitcoin.dart';
2 import 'package:cake_wallet/store/settings_store.dart';
3 +import 'package:cake_wallet/di.dart';
4 +import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
5 import 'package:cw_core/utils/print_verbose.dart';
6 import 'package:cw_core/wallet_type.dart';
7 import 'package:mobx/mobx.dart';
lib/reactions/wallet_utils.dart
+1
@@ -15,6 +15,7 @@ bool isBIP39Wallet(WalletType walletType) {
15 case WalletType.banano:
16 case WalletType.monero:
17 case WalletType.dogecoin:
18 + case WalletType.zcash:
19 return true;
20 case WalletType.wownero:
21 case WalletType.haven:
lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart
+5 -1
@@ -17,6 +17,7 @@ import 'package:cake_wallet/wallet_type_utils.dart';
17 import 'package:cw_core/wallet_type.dart';
18 import 'package:flutter/material.dart';
19 import 'package:flutter_mobx/flutter_mobx.dart';
20 +import 'package:flutter_svg/svg.dart';
21
22 class DesktopWalletSelectionDropDown extends StatefulWidget {
23 final WalletListViewModel walletListViewModel;
@@ -48,6 +49,7 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
49 final zanoIcon = Image.asset('assets/images/crypto/zano.webp', height: 24, width: 24);
50 final decredIcon = Image.asset('assets/images/crypto/decred.webp', height: 24, width: 24);
51 final dogeIcon = Image.asset('assets/images/crypto/dogecoin.webp', height: 24, width: 24);
52 + final zcashIcon = Image.asset('assets/images/crypto/zcash.webp', height: 24, width: 24);
53 final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
54
55 Image _newWalletImage(BuildContext context) => Image.asset(
@@ -153,7 +155,7 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
155 });
156 }
157
156 - Image _imageFor({required WalletType type, bool? isTestnet}) {
158 + Widget _imageFor({required WalletType type, bool? isTestnet}) {
159 switch (type) {
160 case WalletType.bitcoin:
161 if (isTestnet == true) return tBitcoinIcon;
@@ -190,6 +192,8 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
192 return baseIcon;
193 case WalletType.arbitrum:
194 return arbitrumIcon;
195 + case WalletType.zcash:
196 + return zcashIcon;
197 case WalletType.none:
198 return nonWalletTypeIcon;
199 }
lib/src/screens/dashboard/desktop_widgets/dropdown_item_widget.dart
+1 -1
@@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
3 class DropDownItemWidget extends StatelessWidget {
4 const DropDownItemWidget({super.key, required this.title, required this.image});
5 final double tileHeight = 60;
6 - final Image image;
6 + final Widget image;
7 final String title;
8
9 @override
lib/src/screens/dashboard/pages/address_page.dart
+10
@@ -6,6 +6,7 @@ import 'package:cake_wallet/anonpay/anonpay_donation_link_info.dart';
6 import 'package:cake_wallet/entities/preferences_key.dart';
7 import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
8 import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
9 +import 'package:cake_wallet/zcash/zcash.dart';
10 import 'package:cw_core/receive_page_option.dart';
11 import 'package:cake_wallet/src/screens/dashboard/widgets/present_receive_option_picker.dart';
12 import 'package:cake_wallet/src/widgets/gradient_background.dart';
@@ -14,6 +15,7 @@ import 'package:cake_wallet/utils/responsive_layout_util.dart';
15 import 'package:cake_wallet/utils/share_util.dart';
16 import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
17 import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
18 +import 'package:cw_core/utils/print_verbose.dart';
19 import 'package:cw_core/wallet_type.dart';
20 import 'package:flutter/material.dart';
21 import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
@@ -269,6 +271,10 @@ class AddressPage extends BasePage {
271 addressListViewModel.setAddressType(bitcoin!.getOptionToType(option));
272 return;
273 }
274 + if (dashboardViewModel.type == WalletType.zcash) {
275 + addressListViewModel.setAddressType(zcash!.getOptionToType(option));
276 + return;
277 + }
278
279 switch (option) {
280 case ReceivePageOption.anonPayInvoice:
@@ -313,6 +319,10 @@ class AddressPage extends BasePage {
319 addressListViewModel.type == WalletType.litecoin) {
320 addressListViewModel.setAddressType(bitcoin!.getBitcoinAddressType(option));
321 }
322 + if (addressListViewModel.type == WalletType.zcash) {
323 + printV("help me i'll kms if that wont work: ${zcash!.getZcashAddressType(option)}");
324 + addressListViewModel.setAddressType(zcash!.getZcashAddressType(option));
325 + }
326 }
327 });
328
lib/src/screens/dashboard/pages/transactions_page.dart
+1 -1
@@ -104,12 +104,12 @@ class TransactionsPage extends StatelessWidget {
104 tags.add("MWEB");
105 }
106 }
107 -
107 return Observer(
108 builder: (_) => TransactionRow(
109 key: item.key,
110 onTap: () => Navigator.of(context)
111 .pushNamed(Routes.transactionDetails, arguments: transaction),
112 + isShield: transaction.additionalInfo['autoShield'] == true,
113 direction: transaction.direction,
114 formattedDate: DateFormat('HH:mm').format(transaction.date),
115 formattedAmount: item.formattedCryptoAmount,
lib/src/screens/dashboard/widgets/menu_widget.dart
+7 -2
@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
5 import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
6 import 'package:cw_core/wallet_type.dart';
7 import 'package:flutter_mobx/flutter_mobx.dart';
8 +import 'package:flutter_svg/svg.dart';
9
10 class MenuWidget extends StatefulWidget {
11 MenuWidget(this.dashboardViewModel, Key? key);
@@ -40,7 +41,8 @@ class MenuWidgetState extends State<MenuWidget> {
41 this.wowneroIcon = Image.asset('assets/images/crypto/wownero.webp'),
42 this.zanoIcon = Image.asset('assets/images/crypto/zano.webp'),
43 this.decredIcon = Image.asset('assets/images/crypto/decred.webp'),
43 - this.dogecoinIcon = Image.asset('assets/images/crypto/dogecoin.webp');
44 + this.dogecoinIcon = Image.asset('assets/images/crypto/dogecoin.webp'),
45 + this.zcashIcon = Image.asset('assets/images/crypto/zcash.webp');
46
47 final largeScreen = 731;
48
@@ -70,6 +72,7 @@ class MenuWidgetState extends State<MenuWidget> {
72 Image zanoIcon;
73 Image decredIcon;
74 Image dogecoinIcon;
75 + Image zcashIcon;
76
77 @override
78 void initState() {
@@ -229,7 +232,7 @@ class MenuWidgetState extends State<MenuWidget> {
232 );
233 }
234
232 - Image _iconFor({required WalletType type}) {
235 + Widget _iconFor({required WalletType type}) {
236 switch (type) {
237 case WalletType.monero:
238 return moneroIcon;
@@ -265,6 +268,8 @@ class MenuWidgetState extends State<MenuWidget> {
268 return decredIcon;
269 case WalletType.dogecoin:
270 return dogecoinIcon;
271 + case WalletType.zcash:
272 + return zcashIcon;
273 default:
274 throw Exception('No icon for ${type.toString()}');
275 }
lib/src/screens/dashboard/widgets/transaction_raw.dart
+8 -1
@@ -1,5 +1,6 @@
1 import 'package:flutter/material.dart';
2 import 'package:cw_core/transaction_direction.dart';
3 +import 'package:flutter_svg/svg.dart';
4
5 class TransactionRow extends StatelessWidget {
6 TransactionRow({
@@ -10,6 +11,7 @@ class TransactionRow extends StatelessWidget {
11 required this.tags,
12 required this.title,
13 required this.onTap,
14 + this.isShield = false,
15 super.key,
16 });
17
@@ -20,6 +22,7 @@ class TransactionRow extends StatelessWidget {
22 final String formattedFiatAmount;
23 final String title;
24 final List<String> tags;
25 + final bool isShield;
26
27 @override
28 Widget build(BuildContext context) {
@@ -39,7 +42,11 @@ class TransactionRow extends StatelessWidget {
42 shape: BoxShape.circle,
43 color: Theme.of(context).colorScheme.surfaceContainerHighest,
44 ),
42 - child: Image.asset(direction == TransactionDirection.incoming
45 + child: (isShield) ? Padding(
46 + padding: const EdgeInsets.all(8.0),
47 + child: SvgPicture.asset('assets/images/tx_shield.svg'),
48 + ) :
49 + Image.asset(direction == TransactionDirection.incoming
50 ? 'assets/images/down_arrow.png'
51 : 'assets/images/up_arrow.png'),
52 ),
lib/src/screens/restore/wallet_restore_from_seed_form.dart
+1 -1
@@ -286,7 +286,7 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
286 'wallet_restore_from_seed_blockheight_textfield_key',
287 ),
288 onHeightOrDateEntered: widget.onHeightOrDateEntered,
289 - hasDatePicker: [WalletType.monero, WalletType.wownero].contains(
289 + hasDatePicker: [WalletType.monero, WalletType.wownero, WalletType.zcash].contains(
290 widget.type,
291 ),
292 walletType: widget.type,
lib/src/screens/restore/wallet_restore_page.dart
+1 -1
@@ -146,7 +146,7 @@ class WalletRestorePage extends BasePage {
146 bottom: MediaQuery.of(bottomSheetContext).viewInsets.bottom,
147 ),
148 child: AddPassphraseBottomSheet(
149 - titleText: S.of(context).add_passphrase,
149 + titleText: S.of(bottomSheetContext).add_passphrase,
150 onRestoreButtonPressed: (passphrase) async {
151 await _onPassphraseBottomSheetRestoreButtonPressed(
152 passphrase,
lib/src/screens/settings/other_settings_page.dart
+5
@@ -10,11 +10,16 @@ import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.da
10 import 'package:cake_wallet/src/screens/settings/widgets/settings_version_cell.dart';
11 import 'package:cake_wallet/utils/feature_flag.dart';
12 import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart';
13 +import 'package:cw_core/node.dart';
14 +import 'package:cw_core/pathForWallet.dart';
15 import 'package:cw_core/wallet_info.dart';
16 import 'package:cw_core/wallet_type.dart';
17 import 'package:cw_core/db/sqlite.dart';
18 import 'package:flutter/material.dart';
19 import 'package:flutter_mobx/flutter_mobx.dart';
20 +import 'package:get_it/get_it.dart';
21 +import 'package:hive/hive.dart';
22 +import 'package:path/path.dart' as p;
23
24 class OtherSettingsPage extends BasePage {
25 OtherSettingsPage(this._otherSettingsViewModel) {
lib/src/widgets/blockchain_height_widget.dart
+6 -3
@@ -2,6 +2,7 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart';
2 import 'package:cake_wallet/src/widgets/standard_switch.dart';
3 import 'package:cake_wallet/utils/date_picker.dart';
4 import 'package:cake_wallet/wownero/wownero.dart';
5 +import 'package:cake_wallet/zcash/zcash.dart';
6 import 'package:cw_core/wallet_type.dart';
7 import 'package:flutter/material.dart';
8 import 'package:intl/intl.dart';
@@ -189,10 +190,12 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
190 height = decred!.heightByDate(date);
191 } else if (widget.walletType == WalletType.monero) {
192 height = monero!.getHeightByDate(date: date);
192 - } else {
193 - assert(widget.walletType == WalletType.wownero,
194 - "unknown currency in BlockchainHeightWidget");
193 + } else if (widget.walletType == WalletType.wownero){
194 height = wownero!.getHeightByDate(date: date);
195 + } else if (widget.walletType == WalletType.zcash) {
196 + height = await zcash!.getHeightByDate(date);
197 + } else {
198 + throw Exception("unknown currency in BlockchainHeightWidget");
199 }
200 }
201 if (mounted) {
lib/store/settings_store.dart
+23 -1
@@ -964,6 +964,10 @@ abstract class SettingsStoreBase with Store {
964 if (node == null) {
965 throw Exception('No node found for wallet type: ${walletType.toString()}');
966 }
967 + if (node.type != walletType) {
968 + throw Exception("Node is not valid for $walletType (found: ${node.type}");
969 + }
970 +
971 return node;
972 }
973
@@ -1202,6 +1206,7 @@ abstract class SettingsStoreBase with Store {
1206 final tronNodeId = sharedPreferences.getInt(PreferencesKey.currentTronNodeIdKey);
1207 final wowneroNodeId = sharedPreferences.getInt(PreferencesKey.currentWowneroNodeIdKey);
1208 final zanoNodeId = sharedPreferences.getInt(PreferencesKey.currentZanoNodeIdKey);
1209 + final zcashNodeId = sharedPreferences.getInt(PreferencesKey.currentZcashNodeIdKey);
1210 final decredNodeId = sharedPreferences.getInt(PreferencesKey.currentDecredNodeIdKey);
1211 final dogecoinNodeId = sharedPreferences.getInt(PreferencesKey.currentDogecoinNodeIdKey);
1212
@@ -1236,6 +1241,8 @@ abstract class SettingsStoreBase with Store {
1241 nodeSource.values.firstWhereOrNull((e) => e.uriRaw == wowneroDefaultNodeUri);
1242 final zanoNode = nodeSource.get(zanoNodeId) ??
1243 nodeSource.values.firstWhereOrNull((e) => e.uriRaw == zanoDefaultNodeUri);
1244 + final zcashNode = nodeSource.get(zcashNodeId) ??
1245 + nodeSource.values.firstWhereOrNull((e) => e.uriRaw == zcashDefaultNodeUri);
1246 final dogecoinNode = nodeSource.get(dogecoinNodeId) ??
1247 nodeSource.values.firstWhereOrNull((e) => e.uriRaw == dogecoinDefaultNodeUri);
1248
@@ -1330,6 +1337,10 @@ abstract class SettingsStoreBase with Store {
1337 nodes[WalletType.zano] = zanoNode;
1338 }
1339
1340 + if (zcashNode != null) {
1341 + nodes[WalletType.zcash] = zcashNode;
1342 + }
1343 +
1344 if (decredNode != null) {
1345 nodes[WalletType.decred] = decredNode;
1346 }
@@ -1738,6 +1749,7 @@ abstract class SettingsStoreBase with Store {
1749 final tronNodeId = sharedPreferences.getInt(PreferencesKey.currentTronNodeIdKey);
1750 final wowneroNodeId = sharedPreferences.getInt(PreferencesKey.currentWowneroNodeIdKey);
1751 final zanoNodeId = sharedPreferences.getInt(PreferencesKey.currentZanoNodeIdKey);
1752 + final zcashNodeId = sharedPreferences.getInt(PreferencesKey.currentZcashNodeIdKey);
1753 final decredNodeId = sharedPreferences.getInt(PreferencesKey.currentDecredNodeIdKey);
1754 final dogecoinNodeId = sharedPreferences.getInt(PreferencesKey.currentDogecoinNodeIdKey);
1755 final moneroNode = nodeSource.get(nodeId);
@@ -1754,6 +1766,7 @@ abstract class SettingsStoreBase with Store {
1766 final tronNode = nodeSource.get(tronNodeId);
1767 final wowneroNode = nodeSource.get(wowneroNodeId);
1768 final zanoNode = nodeSource.get(zanoNodeId);
1769 + final zcashNode = nodeSource.get(zcashNodeId);
1770 final decredNode = nodeSource.get(decredNodeId);
1771 final dogecoinNode = nodeSource.get(dogecoinNodeId);
1772
@@ -1813,6 +1826,10 @@ abstract class SettingsStoreBase with Store {
1826 nodes[WalletType.zano] = zanoNode;
1827 }
1828
1829 + if (zcashNode != null) {
1830 + nodes[WalletType.zcash] = zcashNode;
1831 + }
1832 +
1833 if (decredNode != null) {
1834 nodes[WalletType.decred] = decredNode;
1835 }
@@ -1969,8 +1986,13 @@ abstract class SettingsStoreBase with Store {
1986 case WalletType.dogecoin:
1987 await _sharedPreferences.setInt(PreferencesKey.currentDogecoinNodeIdKey, node.key as int);
1988 break;
1972 - default:
1989 + case WalletType.zcash:
1990 + await _sharedPreferences.setInt(PreferencesKey.currentZcashNodeIdKey, node.key as int);
1991 break;
1992 + case WalletType.none:
1993 + throw UnimplementedError();
1994 + case WalletType.banano:
1995 + throw UnimplementedError();
1996 }
1997
1998 nodes[walletType] = node;
lib/utils/exception_handler.dart
+3 -2
@@ -22,7 +22,7 @@ import 'package:shared_preferences/shared_preferences.dart';
22 class ExceptionHandler {
23 static bool _hasError = false;
24 static const _coolDownDurationInDays =
25 - bool.fromEnvironment('hasDevOptions', defaultValue: kDebugMode) ? 0 : 7;
25 + bool.fromEnvironment('hasDevOptions', defaultValue: kDebugMode || kProfileMode) ? 0 : 7;
26 static File? _file;
27
28 static Future<void> _saveException(String? error, StackTrace? stackTrace,
@@ -118,7 +118,7 @@ class ExceptionHandler {
118
119 if (kDebugMode || kProfileMode) {
120 FlutterError.presentError(errorDetails);
121 - printV(errorDetails.toString());
121 + errorDetails.toString().split("\n").forEach(printV);
122 return;
123 }
124
@@ -198,6 +198,7 @@ class ExceptionHandler {
198 'Exception: 6e00',
199 'Exception: 6985',
200 'Exception: 5515',
201 + 'transport error'
202 ];
203
204 static bool isLedgerError(Object exception) =>
lib/utils/feature_flag.dart
+2 -2
@@ -9,8 +9,8 @@ class FeatureFlag {
9 static const bool isExolixEnabled = true;
10 static const bool isBackgroundSyncEnabled = true;
11 static bool get isInAppTorEnabled => CakeTor.instance is! CakeTorDisabled;
12 - static const int verificationWordsCount = kDebugMode ? 0 : 2;
13 - static const bool hasDevOptions = bool.fromEnvironment('hasDevOptions', defaultValue: kDebugMode);
12 + static const int verificationWordsCount = kDebugMode || kProfileMode ? 0 : 2;
13 + static const bool hasDevOptions = bool.fromEnvironment('hasDevOptions', defaultValue: kDebugMode || kProfileMode);
14 static const bool hasBitcoinViewOnly = true;
15 static const bool customBackgroundEnabled = false;
16 static const bool duressPinEnabled = true;
lib/utils/qr_util.dart
+2
@@ -41,6 +41,8 @@ String getQrImage(WalletType type, {int? selectedChainId}) {
41 return 'assets/images/dcr_chain_qr.svg';
42 case WalletType.dogecoin:
43 return 'assets/images/doge_chain_qr.svg';
44 + case WalletType.zcash:
45 + return 'assets/images/zec_icon_qr.svg';
46 case WalletType.banano:
47 case WalletType.haven:
48 case WalletType.none:
lib/view_model/advanced_privacy_settings_view_model.dart
+2
@@ -57,6 +57,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
57 case WalletType.arbitrum:
58 case WalletType.solana:
59 case WalletType.tron:
60 + case WalletType.zcash:
61 return true;
62
63 case WalletType.bitcoin:
@@ -103,6 +104,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
104 WalletType.wownero,
105 WalletType.zano,
106 WalletType.dogecoin,
107 + WalletType.zcash,
108 ].contains(type);
109
110 @computed
lib/view_model/cake_pay/cake_pay_buy_card_view_model.dart
+9 -4
@@ -115,6 +115,8 @@ abstract class CakePayBuyCardViewModelBase with Store {
115 ];
116 case WalletType.monero:
117 return [CakePayPaymentMethod.XMR];
118 + case WalletType.zcash:
119 + return [CakePayPaymentMethod.ZEC];
120 default:
121 return const [];
122 }
@@ -139,11 +141,14 @@ abstract class CakePayBuyCardViewModelBase with Store {
141
142 @action
143 Future<void> createOrder() async {
142 - if (walletType != WalletType.bitcoin &&
143 - walletType != WalletType.monero &&
144 - walletType != WalletType.litecoin) {
144 + if (![
145 + WalletType.bitcoin,
146 + WalletType.monero,
147 + WalletType.litecoin,
148 + WalletType.zcash,
149 + ].contains(walletType)) {
150 sendViewModel.state =
146 - FailureState('Unsupported wallet type, please use Bitcoin, Monero, or Litecoin.');
151 + FailureState('Unsupported wallet type, please use Bitcoin, Monero, Litecoin or Zcash.');
152 }
153 try {
154 order = await _cakePayService.createOrder(
lib/view_model/dashboard/balance_view_model.dart
+1
@@ -320,6 +320,7 @@ abstract class BalanceViewModelBase with Store {
320 case WalletType.wownero:
321 case WalletType.zano:
322 case WalletType.decred:
323 + case WalletType.zcash:
324 return true;
325 default:
326 return false;
lib/view_model/dashboard/dashboard_view_model.dart
+2
@@ -957,6 +957,7 @@ abstract class DashboardViewModelBase with Store {
957 return true;
958 case WalletType.zano:
959 case WalletType.haven:
960 + case WalletType.zcash:
961 case WalletType.none:
962 return false;
963 }
@@ -1284,6 +1285,7 @@ abstract class DashboardViewModelBase with Store {
1285
1286 if (wallet.chainId == 1 && tx.evmSignatureName == 'approval')
1287 return ' (${tx.evmSignatureName})';
1288 +
1289 return '';
1290 }
1291
lib/view_model/dashboard/home_settings_view_model.dart
+1
@@ -231,6 +231,7 @@ abstract class HomeSettingsViewModelBase with Store {
231 case WalletType.bitcoinCash:
232 case WalletType.decred:
233 case WalletType.dogecoin:
234 + case WalletType.zcash:
235 return false;
236 }
237
lib/view_model/dashboard/receive_option_view_model.dart
+10 -1
@@ -1,4 +1,5 @@
1 import 'package:cake_wallet/bitcoin/bitcoin.dart';
2 +import 'package:cake_wallet/zcash/zcash.dart';
3 import 'package:cw_core/receive_page_option.dart';
4 import 'package:cw_core/wallet_base.dart';
5 import 'package:cw_core/wallet_type.dart';
@@ -16,7 +17,9 @@ abstract class ReceiveOptionViewModelBase with Store {
17 ? bitcoin!.getSelectedAddressType(_wallet)
18 : (_wallet.type == WalletType.decred && _wallet.isTestnet)
19 ? ReceivePageOption.testnet
19 - : ReceivePageOption.mainnet),
20 + : _wallet.type == WalletType.zcash
21 + ? zcash!.getSelectedAddressType(_wallet)
22 + : ReceivePageOption.mainnet),
23 _options = [] {
24 final walletType = _wallet.type;
25 switch (walletType) {
@@ -46,6 +49,12 @@ abstract class ReceiveOptionViewModelBase with Store {
49 _options = ReceivePageOptions;
50 }
51 break;
52 + case WalletType.zcash:
53 + _options = [
54 + ...zcash!.getZcashReceivePageOptions(_wallet),
55 + ...ReceivePageOptions.where((element) => element != ReceivePageOption.mainnet)
56 + ];
57 + break;
58 default:
59 _options = ReceivePageOptions;
60 }
lib/view_model/dashboard/transaction_list_item.dart
+9
@@ -9,6 +9,7 @@ import 'package:cake_wallet/solana/solana.dart';
9 import 'package:cake_wallet/tron/tron.dart';
10 import 'package:cake_wallet/wownero/wownero.dart';
11 import 'package:cake_wallet/zano/zano.dart';
12 +import 'package:cake_wallet/zcash/zcash.dart';
13 import 'package:cw_core/crypto_currency.dart';
14 import 'package:cw_core/transaction_direction.dart';
15 import 'package:cw_core/transaction_info.dart';
@@ -52,6 +53,9 @@ class TransactionListItem extends ActionListItem with Keyable {
53 }
54
55 String get formattedTitle {
56 + if (transaction.additionalInfo['autoShield'] == true) {
57 + return "Autoshield";
58 + }
59 if (transaction.direction == TransactionDirection.incoming) {
60 return S.current.received;
61 }
@@ -217,6 +221,11 @@ class TransactionListItem extends ActionListItem with Keyable {
221 cryptoAmount: decred!.formatterDecredAmountToDouble(amount: transaction.amount),
222 price: price);
223 break;
224 + case WalletType.zcash:
225 + amount = calculateFiatAmountRaw(
226 + cryptoAmount: zcash!.formatterZcashAmountToDouble(amount: BigInt.from(transaction.amount)),
227 + price: price);
228 +
229 case WalletType.none:
230 case WalletType.banano:
231 case WalletType.haven:
lib/view_model/exchange/exchange_view_model.dart
+4
@@ -922,6 +922,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
922 depositCurrency = CryptoCurrency.dcr;
923 receiveCurrency = CryptoCurrency.xmr;
924 break;
925 + case WalletType.zcash:
926 + depositCurrency = CryptoCurrency.zec;
927 + receiveCurrency = CryptoCurrency.xmr;
928 + break;
929 case WalletType.none:
930 break;
931 }
lib/view_model/node_list/node_create_or_edit_view_model.dart
+1
@@ -97,6 +97,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
97 case WalletType.dogecoin:
98 case WalletType.zano:
99 case WalletType.decred:
100 + case WalletType.zcash:
101 return false;
102 }
103 }
lib/view_model/send/fees_view_model.dart
+5
@@ -5,6 +5,7 @@ import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
5 import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
6 import 'package:cake_wallet/evm/evm.dart';
7 import 'package:cake_wallet/monero/monero.dart';
8 +import 'package:cake_wallet/zcash/zcash.dart';
9 import 'package:cake_wallet/store/app_store.dart';
10 import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
11 import 'package:cw_core/crypto_currency.dart';
@@ -102,6 +103,7 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor
103 case WalletType.solana:
104 case WalletType.tron:
105 case WalletType.arbitrum:
106 + case WalletType.zcash:
107 return false;
108 }
109 }
@@ -185,6 +187,9 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor
187 case WalletType.zano:
188 _settingsStore.setPriority(wallet.type, monero!.getMoneroTransactionPriorityAutomatic());
189 break;
190 + case WalletType.zcash:
191 + _settingsStore.setPriority[wallet.type] = zcash!.getZcashTransactionPriorityAutomatic();
192 + break;
193 case WalletType.bitcoin:
194 _settingsStore.setPriority(wallet.type, bitcoin!.getBitcoinTransactionPriorityMedium());
195 break;
lib/view_model/send/output.dart
+8
@@ -18,6 +18,7 @@ import 'package:cake_wallet/store/settings_store.dart';
18 import 'package:cake_wallet/tron/tron.dart';
19 import 'package:cake_wallet/wownero/wownero.dart';
20 import 'package:cake_wallet/zano/zano.dart';
21 +import 'package:cake_wallet/zcash/zcash.dart';
22 import 'package:cw_core/balance.dart';
23 import 'package:cw_core/crypto_currency.dart';
24 import 'package:cw_core/format_fixed.dart';
@@ -125,6 +126,9 @@ abstract class OutputBase with Store {
126 _amount = zano!
127 .formatterParseAmount(amount: _cryptoAmount, currency: cryptoCurrencyHandler());
128 break;
129 + case WalletType.zcash:
130 + _amount = zcash!.formatterZcashParseAmount(_cryptoAmount);
131 + break;
132 case WalletType.none:
133 case WalletType.haven:
134 case WalletType.nano:
@@ -204,6 +208,10 @@ abstract class OutputBase with Store {
208 estimatedFee = tron!.getTronTRC20EstimatedFee(_wallet).toString();
209 }
210 break;
211 +
212 + case WalletType.zcash:
213 + estimatedFee = zcash!.formatterZcashAmountToDouble(amount: BigInt.from(fee)).toString();
214 + break;
215
216 /// EVMs
217 case WalletType.ethereum:
lib/view_model/send/send_view_model.dart
+9 -2
@@ -45,6 +45,7 @@ import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
45 import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart';
46 import 'package:cake_wallet/wownero/wownero.dart';
47 import 'package:cake_wallet/zano/zano.dart';
48 +import 'package:cake_wallet/zcash/zcash.dart';
49 import 'package:cw_core/crypto_currency.dart';
50 import 'package:cw_core/erc20_token.dart';
51 import 'package:cw_core/exceptions.dart';
@@ -803,8 +804,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
804 wallet.type != WalletType.banano &&
805 wallet.type != WalletType.solana &&
806 wallet.type != WalletType.tron &&
806 - wallet.type != WalletType.arbitrum) {
807 - // Wallet type is generic for all EVM chains, so we need to check the chainId
807 + wallet.type != WalletType.arbitrum &&
808 + wallet.type != WalletType.zcash) {
809 throw Exception('Priority is null for wallet type: ${wallet.type}');
810 }
811
@@ -861,6 +862,12 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
862 case WalletType.decred:
863 this.coinTypeToSpendFrom = UnspentCoinType.any;
864 return decred!.createDecredTransactionCredentials(outputs, priority!);
865 + case WalletType.zcash:
866 + return zcash!.createZcashTransactionCredentials(
867 + outputs,
868 + currency: selectedCryptoCurrency,
869 + // priority: priority,
870 + );
871 default:
872 throw Exception('Unexpected wallet type: ${wallet.type}');
873 }
lib/view_model/settings/other_settings_view_model.dart
+1
@@ -1,5 +1,6 @@
1 import 'package:cake_wallet/bitcoin/bitcoin.dart';
2 import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
3 +import 'package:cake_wallet/monero/monero.dart';
4 import 'package:cake_wallet/store/settings_store.dart';
5 import 'package:cake_wallet/utils/package_info.dart';
6 import 'package:cake_wallet/view_model/send/send_view_model.dart';
lib/view_model/transaction_details_view_model.dart
+57 -1
@@ -95,6 +95,8 @@ abstract class TransactionDetailsViewModelBase with Store {
95 case WalletType.dogecoin:
96 _addDogecoinListItems(tx, dateFormat);
97 break;
98 + case WalletType.zcash:
99 + _addZcashListItems(tx, dateFormat);
100 case WalletType.none:
101 case WalletType.banano:
102 break;
@@ -211,7 +213,8 @@ abstract class TransactionDetailsViewModelBase with Store {
213 return 'https://${wallet.isTestnet ? "testnet" : "dcrdata"}.decred.org/tx/${txId.split(':')[0]}';
214 case WalletType.dogecoin:
215 return 'https://blockchair.com/dogecoin/transaction/${txId}';
214 -
216 + case WalletType.zcash:
217 + return 'https://blockchair.com/zcash/transaction/${txId}';
218 case WalletType.none:
219 return '';
220 }
@@ -257,6 +260,8 @@ abstract class TransactionDetailsViewModelBase with Store {
260 return S.current.view_transaction_on + 'basescan.org';
261 case WalletType.arbitrum:
262 return S.current.view_transaction_on + 'arbiscan.io';
263 + case WalletType.zcash:
264 + return S.current.view_transaction_on + 'blockchair.com';
265 case WalletType.none:
266 return '';
267 }
@@ -755,6 +760,57 @@ abstract class TransactionDetailsViewModelBase with Store {
760 items.addAll(_items);
761 }
762
763 + void _addZcashListItems(TransactionInfo tx, DateFormat dateFormat) {
764 + final memo = tx.additionalInfo['memo'] as String?;
765 +
766 + final _items = [
767 + StandartListItem(
768 + title: S.current.transaction_details_transaction_id,
769 + value: tx.txHash,
770 + key: ValueKey('standard_list_item_transaction_details_id_key'),
771 + ),
772 + if (tx.to != null)
773 + StandartListItem(
774 + title: S.current.transaction_details_recipient_address,
775 + value: tx.to!,
776 + key: ValueKey('standard_list_item_transaction_details_recipient_address_key'),
777 + ),
778 + StandartListItem(
779 + title: S.current.transaction_details_date,
780 + value: dateFormat.format(tx.date),
781 + key: ValueKey('standard_list_item_transaction_details_date_key'),
782 + ),
783 + StandartListItem(
784 + title: S.current.confirmations,
785 + value: tx.confirmations.toString(),
786 + key: ValueKey('standard_list_item_transaction_confirmations_key'),
787 + ),
788 + StandartListItem(
789 + title: S.current.transaction_details_height,
790 + value: '${tx.height ?? 0}',
791 + key: ValueKey('standard_list_item_transaction_details_height_key'),
792 + ),
793 + StandartListItem(
794 + title: S.current.transaction_details_amount,
795 + value: tx.amountFormatted(),
796 + key: ValueKey('standard_list_item_transaction_details_amount_key'),
797 + ),
798 + if (tx.feeFormatted() != null && tx.feeFormatted()!.isNotEmpty)
799 + StandartListItem(
800 + title: S.current.transaction_details_fee,
801 + value: tx.feeFormatted()!,
802 + key: ValueKey('standard_list_item_transaction_details_fee_key'),
803 + ),
804 + if (memo != null && memo.isNotEmpty)
805 + StandartListItem(
806 + title: S.current.memo,
807 + value: memo,
808 + key: ValueKey('standard_list_item_transaction_details_memo_key'),
809 + ),
810 + ];
811 + items.addAll(_items);
812 + }
813 +
814 void _addDogecoinListItems(TransactionInfo tx, DateFormat dateFormat) {
815 final _items = [
816 StandartListItem(
lib/view_model/wallet_address_list/wallet_address_list_view_model.dart
+15
@@ -28,6 +28,7 @@ import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_hidden
28 import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_header.dart';
29 import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
30 import 'package:cake_wallet/wownero/wownero.dart';
31 +import 'package:cake_wallet/zcash/zcash.dart';
32 import 'package:cw_core/amount_converter.dart';
33 import 'package:cw_core/currency.dart';
34 import 'package:cw_core/currency_for_wallet_type.dart';
@@ -165,6 +166,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
166 return BaseURI(amount: amount, address: address.address);
167 case WalletType.arbitrum:
168 return ArbitrumURI(amount: amount, address: address.address);
169 + case WalletType.zcash:
170 + return ZcashURI(amount: amount, address: address.address);
171 case WalletType.none:
172 throw Exception('Unexpected type: ${type.toString()}');
173 }
@@ -313,6 +316,14 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
316 });
317 }
318
319 + if (wallet.type == WalletType.zcash) {
320 + final addrInfos = zcash!.getAddressInfos(wallet);
321 + addrInfos.forEach((info) {
322 + addressList.add(
323 + new WalletAddressListItem(isPrimary: false, address: info.address, name: info.label));
324 + });
325 + }
326 +
327 for (var i = 0; i < addressList.length; i++) {
328 if (!(addressList[i] is WalletAddressListItem)) continue;
329 (addressList[i] as WalletAddressListItem).isHidden = wallet.walletAddresses.hiddenAddresses
@@ -409,6 +420,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
420 WalletType.litecoin,
421 WalletType.decred,
422 WalletType.dogecoin,
423 + WalletType.zcash,
424 ].contains(wallet.type);
425
426 @computed
@@ -528,6 +540,9 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
540 if ([WalletType.bitcoin, WalletType.litecoin].contains(wallet.type)) {
541 await bitcoin!.setAddressType(wallet, option);
542 }
543 + if (wallet.type == WalletType.zcash) {
544 + await zcash!.setAddressType(wallet, option);
545 + }
546 }
547
548 void _init() {
lib/view_model/wallet_keys_view_model.dart
+3
@@ -144,6 +144,7 @@ abstract class WalletKeysViewModelBase with Store {
144 case WalletType.base:
145 case WalletType.arbitrum:
146 case WalletType.solana:
147 + case WalletType.zcash:
148 case WalletType.tron:
149 items.addAll([
150 if (_wallet.privateKey != null)
@@ -285,6 +286,8 @@ abstract class WalletKeysViewModelBase with Store {
286 return 'decred-wallet';
287 case WalletType.dogecoin:
288 return 'dogecoin-wallet';
289 + case WalletType.zcash:
290 + return 'zcash-wallet';
291 default:
292 throw Exception('Unexpected wallet type: ${_wallet.type.toString()}');
293 }
lib/view_model/wallet_new_vm.dart
+7
@@ -6,6 +6,7 @@ import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
6 import 'package:cake_wallet/solana/solana.dart';
7 import 'package:cake_wallet/tron/tron.dart';
8 import 'package:cake_wallet/wownero/wownero.dart';
9 +import 'package:cake_wallet/zcash/zcash.dart';
10 import 'package:mobx/mobx.dart';
11 import 'package:cake_wallet/bitcoin/bitcoin.dart';
12 import 'package:cake_wallet/core/wallet_creation_service.dart';
@@ -138,6 +139,12 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
139 password: walletPassword,
140 passphrase: passphrase,
141 );
142 + case WalletType.zcash:
143 + return zcash!.createZcashNewWalletCredentials(
144 + name: name,
145 + password: walletPassword,
146 + passphrase: passphrase,
147 + );
148 case WalletType.decred:
149 return decred!.createDecredNewWalletCredentials(name: name);
150 case WalletType.none:
lib/view_model/wallet_restore_view_model.dart
+19 -4
@@ -19,6 +19,7 @@ import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
19 import 'package:cake_wallet/view_model/wallet_creation_vm.dart';
20 import 'package:cake_wallet/wownero/wownero.dart';
21 import 'package:cake_wallet/zano/zano.dart';
22 +import 'package:cake_wallet/zcash/zcash.dart';
23 import 'package:cw_core/wallet_base.dart';
24 import 'package:cw_core/wallet_credentials.dart';
25 import 'package:cw_core/wallet_info.dart';
@@ -59,10 +60,12 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
60 break;
61 case WalletType.bitcoinCash:
62 case WalletType.zano:
62 - case WalletType.none:
63 + case WalletType.zcash:
64 case WalletType.dogecoin:
65 availableModes = [WalletRestoreMode.seed];
66 break;
67 + case WalletType.none:
68 + availableModes = [];
69 }
70 walletCreationService.changeWalletType(type: type);
71 if (restoredWallet != null) {
@@ -79,9 +82,13 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
82 late final bool hasSeedLanguageSelector =
83 [WalletType.monero, WalletType.haven, WalletType.wownero].contains(type);
84
82 - late final bool hasBlockchainHeightSelector =
83 - [WalletType.monero, WalletType.haven, WalletType.wownero].contains(type);
84 -
85 + late final bool hasBlockchainHeightSelector = [
86 + WalletType.monero,
87 + WalletType.haven,
88 + WalletType.wownero,
89 + WalletType.zcash
90 + ].contains(type);
91 +
92 late final bool hasRestoreFromPrivateKey = [
93 WalletType.ethereum,
94 WalletType.polygon,
@@ -202,9 +209,17 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
209 );
210 case WalletType.decred:
211 return decred!.createDecredRestoreWalletFromSeedCredentials(
212 + name: name,
213 + mnemonic: seed,
214 + password: password,
215 + );
216 + case WalletType.zcash:
217 + return zcash!.createZcashRestoreWalletFromSeedCredentials(
218 name: name,
219 mnemonic: seed,
220 password: password,
221 + passphrase: passphrase,
222 + height: height,
223 );
224 case WalletType.none:
225 case WalletType.haven:
lib/view_model/wallet_seed_view_model.dart
+1 -1
@@ -84,7 +84,7 @@ abstract class WalletSeedViewModelBase with Store {
84
85 bool shouldPerformVerification() {
86 bool isCI = bool.fromEnvironment('CI_BUILD', defaultValue: false);
87 - bool isDebug = kDebugMode;
87 + bool isDebug = kDebugMode || kProfileMode;
88
89 if (isDebug && !isCI) {
90 printV("Skipping verification in debug mode - $isDebug (and when it's not in CI - $isCI).");
lib/zcash/cw_zcash.dart new
+2263
@@ -0,0 +1,2263 @@
1 +part of 'zcash.dart';
2 +
3 +class CWZcash extends Zcash {
4 + @override
5 + WalletCredentials createZcashNewWalletCredentials(
6 + {required String name,
7 + WalletInfo? walletInfo,
8 + String? password,
9 + String? mnemonic,
10 + required String? passphrase}) {
11 + return ZcashNewWalletCredentials(
12 + name: name,
13 + passphrase: passphrase,
14 + password: password,
15 + mnemonic: mnemonic,
16 + );
17 + }
18 +
19 + @override
20 + WalletCredentials createZcashRestoreWalletFromPrivateKey(
21 + {required String name,
22 + required String privateKey,
23 + required String password}) {
24 + throw UnimplementedError();
25 + }
26 +
27 + @override
28 + WalletCredentials createZcashRestoreWalletFromSeedCredentials(
29 + {required String name,
30 + required String mnemonic,
31 + required String password,
32 + String? passphrase,
33 + required int? height}) {
34 + return ZcashFromSeedWalletCredentials(
35 + name: name, seed: mnemonic, passphrase: passphrase, password: password, height: height);
36 + }
37 +
38 + @override
39 + Object createZcashTransactionCredentials(List<Output> outputs,
40 + {required CryptoCurrency currency,
41 + int? feeRate,
42 + TransactionPriority? priority}) {
43 + final txPriority = (priority ?? getZcashTransactionPriorityAutomatic())
44 + as MoneroTransactionPriority;
45 + return ZcashTransactionCredentials(
46 + outputs: outputs
47 + .map((out) => OutputInfo(
48 + fiatAmount: out.fiatAmount,
49 + cryptoAmount: out.cryptoAmount,
50 + address: out.address,
51 + note: out.note,
52 + sendAll: out.sendAll,
53 + extractedAddress: out.extractedAddress,
54 + isParsedAddress: out.isParsedAddress,
55 + formattedCryptoAmount: out.formattedCryptoAmount))
56 + .toList(),
57 + priority: txPriority,
58 + currency: currency,
59 + );
60 + }
61 +
62 + @override
63 + Object createZcashTransactionCredentialsRaw(List<OutputInfo> outputs,
64 + {required CryptoCurrency currency, required int feeRate}) {
65 + return ZcashTransactionCredentials(
66 + outputs: outputs,
67 + priority:
68 + getZcashTransactionPriorityAutomatic() as MoneroTransactionPriority,
69 + currency: currency,
70 + );
71 + }
72 +
73 + @override
74 + WalletService<WalletCredentials, WalletCredentials, WalletCredentials,
75 + WalletCredentials> createZcashWalletService(bool isDirect) {
76 + return ZcashWalletService();
77 + }
78 +
79 + @override
80 + double formatterZcashAmountToDouble({TransactionInfo? transaction, BigInt? amount}) {
81 + return cryptoAmountToDouble(amount: amount?.toInt()??0, divider: 1e8);
82 + }
83 +
84 + @override
85 + int formatterZcashParseAmount(String amount) {
86 + return CryptoCurrency.zec.parseAmount(amount).toInt();
87 + }
88 +
89 + @override
90 + String formatterZcashAmountToString({required int amount}) {
91 + return CryptoCurrency.zec.formatAmount(BigInt.from(amount));
92 + }
93 +
94 + @override
95 + String getAddress(
96 + WalletBase<Balance, TransactionHistoryBase<TransactionInfo>,
97 + TransactionInfo>
98 + wallet) {
99 + final zcashWallet = wallet as ZcashWallet;
100 + return zcashWallet.walletAddresses.address;
101 + }
102 +
103 + @override
104 + String getPrivateKey(
105 + WalletBase<Balance, TransactionHistoryBase<TransactionInfo>,
106 + TransactionInfo>
107 + wallet) {
108 + final zcashWallet = wallet as ZcashWallet;
109 + final seed = zcashWallet.seed;
110 + return seed ?? '';
111 + }
112 +
113 + @override
114 + String getPublicKey(
115 + WalletBase<Balance, TransactionHistoryBase<TransactionInfo>,
116 + TransactionInfo>
117 + wallet) {
118 + return getAddress(wallet);
119 + }
120 +
121 + @override
122 + Map<String, String> getKeys(Object wallet) {
123 + final zcashWallet = wallet as ZcashWallet;
124 + final seed = zcashWallet.seed;
125 + final address = zcashWallet.walletAddresses.address;
126 +
127 + return <String, String>{
128 + if (seed != null) 'seed': seed,
129 + 'primaryAddress': address,
130 + };
131 + }
132 +
133 + @override
134 + List<String> getZcashWordList(String language) {
135 + return wordList;
136 + }
137 +
138 + @override
139 + List<WalletInfoAddressInfo> getAddressInfos(Object wallet) {
140 + final zcashWallet = wallet as ZcashWallet;
141 + return (zcashWallet.walletAddresses as ZcashWalletAddresses)
142 + .getAddressInfos();
143 + }
144 +
145 + @override
146 + TransactionPriority getDefaultTransactionPriority() {
147 + return MoneroTransactionPriority.automatic;
148 + }
149 +
150 + @override
151 + TransactionPriority getZcashTransactionPriorityAutomatic() {
152 + return MoneroTransactionPriority.automatic;
153 + }
154 +
155 + @override
156 + TransactionPriority deserializeZcashTransactionPriority({required int raw}) {
157 + return MoneroTransactionPriority.deserialize(raw: raw);
158 + }
159 +
160 + @override
161 + List<TransactionPriority> getTransactionPriorities() {
162 + return MoneroTransactionPriority.all;
163 + }
164 +
165 + @override
166 + List<ReceivePageOption> getZcashReceivePageOptions(Object wallet) {
167 + // final zcashWallet = wallet as ZcashWallet;
168 + return ZcashReceivePageOption.all;
169 + }
170 +
171 + @override
172 + ReceivePageOption getSelectedAddressType(Object wallet) {
173 + final zcashWallet = wallet as ZcashWallet;
174 + final t = (zcashWallet.walletAddresses as ZcashWalletAddresses).walletInfo.addressPageType??"";
175 + return ZcashReceivePageOption.fromType(ZcashReceivePageOption.typeFromString(t));
176 + }
177 +
178 + @override
179 + ZcashAddressType getZcashAddressType(ReceivePageOption option) {
180 + switch (option) {
181 + case ZcashReceivePageOption.unified:
182 + return ZcashAddressType.unifiedType;
183 + case ZcashReceivePageOption.transparent:
184 + return ZcashAddressType.transparent;
185 + case ZcashReceivePageOption.shieldedSapling:
186 + return ZcashAddressType.shieldedSapling;
187 + case ZcashReceivePageOption.shieldedOrchard:
188 + return ZcashAddressType.shieldedOrchard;
189 + default:
190 + throw Exception("Unknown ReceivePageOption!");
191 + }
192 + }
193 + Future<void> setAddressType(Object wallet, dynamic option) async {
194 + final zcashWallet = wallet as ZcashWallet;
195 + await (zcashWallet.walletAddresses as ZcashWalletAddresses).setAddressType(option as ZcashAddressType);
196 + }
197 +
198 + @override
199 + ZcashAddressType getOptionToType(ReceivePageOption option) {
200 + return (option as ZcashReceivePageOption).toType();
201 + }
202 +
203 + @override
204 + void unlockDatabase(String password) {
205 + return ZcashWalletBase.unlockDatabase(password);
206 + }
207 +
208 + @override
209 + Future<int> getHeightByDate(DateTime date) {
210 + return ZcashWalletBase.getHeightByDate(date);
211 + }
212 +}
213 +
214 +const wordList = [
215 + 'abandon',
216 + 'ability',
217 + 'able',
218 + 'about',
219 + 'above',
220 + 'absent',
221 + 'absorb',
222 + 'abstract',
223 + 'absurd',
224 + 'abuse',
225 + 'access',
226 + 'accident',
227 + 'account',
228 + 'accuse',
229 + 'achieve',
230 + 'acid',
231 + 'acoustic',
232 + 'acquire',
233 + 'across',
234 + 'act',
235 + 'action',
236 + 'actor',
237 + 'actress',
238 + 'actual',
239 + 'adapt',
240 + 'add',
241 + 'addict',
242 + 'address',
243 + 'adjust',
244 + 'admit',
245 + 'adult',
246 + 'advance',
247 + 'advice',
248 + 'aerobic',
249 + 'affair',
250 + 'afford',
251 + 'afraid',
252 + 'again',
253 + 'age',
254 + 'agent',
255 + 'agree',
256 + 'ahead',
257 + 'aim',
258 + 'air',
259 + 'airport',
260 + 'aisle',
261 + 'alarm',
262 + 'album',
263 + 'alcohol',
264 + 'alert',
265 + 'alien',
266 + 'all',
267 + 'alley',
268 + 'allow',
269 + 'almost',
270 + 'alone',
271 + 'alpha',
272 + 'already',
273 + 'also',
274 + 'alter',
275 + 'always',
276 + 'amateur',
277 + 'amazing',
278 + 'among',
279 + 'amount',
280 + 'amused',
281 + 'analyst',
282 + 'anchor',
283 + 'ancient',
284 + 'anger',
285 + 'angle',
286 + 'angry',
287 + 'animal',
288 + 'ankle',
289 + 'announce',
290 + 'annual',
291 + 'another',
292 + 'answer',
293 + 'antenna',
294 + 'antique',
295 + 'anxiety',
296 + 'any',
297 + 'apart',
298 + 'apology',
299 + 'appear',
300 + 'apple',
301 + 'approve',
302 + 'april',
303 + 'arch',
304 + 'arctic',
305 + 'area',
306 + 'arena',
307 + 'argue',
308 + 'arm',
309 + 'armed',
310 + 'armor',
311 + 'army',
312 + 'around',
313 + 'arrange',
314 + 'arrest',
315 + 'arrive',
316 + 'arrow',
317 + 'art',
318 + 'artefact',
319 + 'artist',
320 + 'artwork',
321 + 'ask',
322 + 'aspect',
323 + 'assault',
324 + 'asset',
325 + 'assist',
326 + 'assume',
327 + 'asthma',
328 + 'athlete',
329 + 'atom',
330 + 'attack',
331 + 'attend',
332 + 'attitude',
333 + 'attract',
334 + 'auction',
335 + 'audit',
336 + 'august',
337 + 'aunt',
338 + 'author',
339 + 'auto',
340 + 'autumn',
341 + 'average',
342 + 'avocado',
343 + 'avoid',
344 + 'awake',
345 + 'aware',
346 + 'away',
347 + 'awesome',
348 + 'awful',
349 + 'awkward',
350 + 'axis',
351 + 'baby',
352 + 'bachelor',
353 + 'bacon',
354 + 'badge',
355 + 'bag',
356 + 'balance',
357 + 'balcony',
358 + 'ball',
359 + 'bamboo',
360 + 'banana',
361 + 'banner',
362 + 'bar',
363 + 'barely',
364 + 'bargain',
365 + 'barrel',
366 + 'base',
367 + 'basic',
368 + 'basket',
369 + 'battle',
370 + 'beach',
371 + 'bean',
372 + 'beauty',
373 + 'because',
374 + 'become',
375 + 'beef',
376 + 'before',
377 + 'begin',
378 + 'behave',
379 + 'behind',
380 + 'believe',
381 + 'below',
382 + 'belt',
383 + 'bench',
384 + 'benefit',
385 + 'best',
386 + 'betray',
387 + 'better',
388 + 'between',
389 + 'beyond',
390 + 'bicycle',
391 + 'bid',
392 + 'bike',
393 + 'bind',
394 + 'biology',
395 + 'bird',
396 + 'birth',
397 + 'bitter',
398 + 'black',
399 + 'blade',
400 + 'blame',
401 + 'blanket',
402 + 'blast',
403 + 'bleak',
404 + 'bless',
405 + 'blind',
406 + 'blood',
407 + 'blossom',
408 + 'blouse',
409 + 'blue',
410 + 'blur',
411 + 'blush',
412 + 'board',
413 + 'boat',
414 + 'body',
415 + 'boil',
416 + 'bomb',
417 + 'bone',
418 + 'bonus',
419 + 'book',
420 + 'boost',
421 + 'border',
422 + 'boring',
423 + 'borrow',
424 + 'boss',
425 + 'bottom',
426 + 'bounce',
427 + 'box',
428 + 'boy',
429 + 'bracket',
430 + 'brain',
431 + 'brand',
432 + 'brass',
433 + 'brave',
434 + 'bread',
435 + 'breeze',
436 + 'brick',
437 + 'bridge',
438 + 'brief',
439 + 'bright',
440 + 'bring',
441 + 'brisk',
442 + 'broccoli',
443 + 'broken',
444 + 'bronze',
445 + 'broom',
446 + 'brother',
447 + 'brown',
448 + 'brush',
449 + 'bubble',
450 + 'buddy',
451 + 'budget',
452 + 'buffalo',
453 + 'build',
454 + 'bulb',
455 + 'bulk',
456 + 'bullet',
457 + 'bundle',
458 + 'bunker',
459 + 'burden',
460 + 'burger',
461 + 'burst',
462 + 'bus',
463 + 'business',
464 + 'busy',
465 + 'butter',
466 + 'buyer',
467 + 'buzz',
468 + 'cabbage',
469 + 'cabin',
470 + 'cable',
471 + 'cactus',
472 + 'cage',
473 + 'cake',
474 + 'call',
475 + 'calm',
476 + 'camera',
477 + 'camp',
478 + 'can',
479 + 'canal',
480 + 'cancel',
481 + 'candy',
482 + 'cannon',
483 + 'canoe',
484 + 'canvas',
485 + 'canyon',
486 + 'capable',
487 + 'capital',
488 + 'captain',
489 + 'car',
490 + 'carbon',
491 + 'card',
492 + 'cargo',
493 + 'carpet',
494 + 'carry',
495 + 'cart',
496 + 'case',
497 + 'cash',
498 + 'casino',
499 + 'castle',
500 + 'casual',
501 + 'cat',
502 + 'catalog',
503 + 'catch',
504 + 'category',
505 + 'cattle',
506 + 'caught',
507 + 'cause',
508 + 'caution',
509 + 'cave',
510 + 'ceiling',
511 + 'celery',
512 + 'cement',
513 + 'census',
514 + 'century',
515 + 'cereal',
516 + 'certain',
517 + 'chair',
518 + 'chalk',
519 + 'champion',
520 + 'change',
521 + 'chaos',
522 + 'chapter',
523 + 'charge',
524 + 'chase',
525 + 'chat',
526 + 'cheap',
527 + 'check',
528 + 'cheese',
529 + 'chef',
530 + 'cherry',
531 + 'chest',
532 + 'chicken',
533 + 'chief',
534 + 'child',
535 + 'chimney',
536 + 'choice',
537 + 'choose',
538 + 'chronic',
539 + 'chuckle',
540 + 'chunk',
541 + 'churn',
542 + 'cigar',
543 + 'cinnamon',
544 + 'circle',
545 + 'citizen',
546 + 'city',
547 + 'civil',
548 + 'claim',
549 + 'clap',
550 + 'clarify',
551 + 'claw',
552 + 'clay',
553 + 'clean',
554 + 'clerk',
555 + 'clever',
556 + 'click',
557 + 'client',
558 + 'cliff',
559 + 'climb',
560 + 'clinic',
561 + 'clip',
562 + 'clock',
563 + 'clog',
564 + 'close',
565 + 'cloth',
566 + 'cloud',
567 + 'clown',
568 + 'club',
569 + 'clump',
570 + 'cluster',
571 + 'clutch',
572 + 'coach',
573 + 'coast',
574 + 'coconut',
575 + 'code',
576 + 'coffee',
577 + 'coil',
578 + 'coin',
579 + 'collect',
580 + 'color',
581 + 'column',
582 + 'combine',
583 + 'come',
584 + 'comfort',
585 + 'comic',
586 + 'common',
587 + 'company',
588 + 'concert',
589 + 'conduct',
590 + 'confirm',
591 + 'congress',
592 + 'connect',
593 + 'consider',
594 + 'control',
595 + 'convince',
596 + 'cook',
597 + 'cool',
598 + 'copper',
599 + 'copy',
600 + 'coral',
601 + 'core',
602 + 'corn',
603 + 'correct',
604 + 'cost',
605 + 'cotton',
606 + 'couch',
607 + 'country',
608 + 'couple',
609 + 'course',
610 + 'cousin',
611 + 'cover',
612 + 'coyote',
613 + 'crack',
614 + 'cradle',
615 + 'craft',
616 + 'cram',
617 + 'crane',
618 + 'crash',
619 + 'crater',
620 + 'crawl',
621 + 'crazy',
622 + 'cream',
623 + 'credit',
624 + 'creek',
625 + 'crew',
626 + 'cricket',
627 + 'crime',
628 + 'crisp',
629 + 'critic',
630 + 'crop',
631 + 'cross',
632 + 'crouch',
633 + 'crowd',
634 + 'crucial',
635 + 'cruel',
636 + 'cruise',
637 + 'crumble',
638 + 'crunch',
639 + 'crush',
640 + 'cry',
641 + 'crystal',
642 + 'cube',
643 + 'culture',
644 + 'cup',
645 + 'cupboard',
646 + 'curious',
647 + 'current',
648 + 'curtain',
649 + 'curve',
650 + 'cushion',
651 + 'custom',
652 + 'cute',
653 + 'cycle',
654 + 'dad',
655 + 'damage',
656 + 'damp',
657 + 'dance',
658 + 'danger',
659 + 'daring',
660 + 'dash',
661 + 'daughter',
662 + 'dawn',
663 + 'day',
664 + 'deal',
665 + 'debate',
666 + 'debris',
667 + 'decade',
668 + 'december',
669 + 'decide',
670 + 'decline',
671 + 'decorate',
672 + 'decrease',
673 + 'deer',
674 + 'defense',
675 + 'define',
676 + 'defy',
677 + 'degree',
678 + 'delay',
679 + 'deliver',
680 + 'demand',
681 + 'demise',
682 + 'denial',
683 + 'dentist',
684 + 'deny',
685 + 'depart',
686 + 'depend',
687 + 'deposit',
688 + 'depth',
689 + 'deputy',
690 + 'derive',
691 + 'describe',
692 + 'desert',
693 + 'design',
694 + 'desk',
695 + 'despair',
696 + 'destroy',
697 + 'detail',
698 + 'detect',
699 + 'develop',
700 + 'device',
701 + 'devote',
702 + 'diagram',
703 + 'dial',
704 + 'diamond',
705 + 'diary',
706 + 'dice',
707 + 'diesel',
708 + 'diet',
709 + 'differ',
710 + 'digital',
711 + 'dignity',
712 + 'dilemma',
713 + 'dinner',
714 + 'dinosaur',
715 + 'direct',
716 + 'dirt',
717 + 'disagree',
718 + 'discover',
719 + 'disease',
720 + 'dish',
721 + 'dismiss',
722 + 'disorder',
723 + 'display',
724 + 'distance',
725 + 'divert',
726 + 'divide',
727 + 'divorce',
728 + 'dizzy',
729 + 'doctor',
730 + 'document',
731 + 'dog',
732 + 'doll',
733 + 'dolphin',
734 + 'domain',
735 + 'donate',
736 + 'donkey',
737 + 'donor',
738 + 'door',
739 + 'dose',
740 + 'double',
741 + 'dove',
742 + 'draft',
743 + 'dragon',
744 + 'drama',
745 + 'drastic',
746 + 'draw',
747 + 'dream',
748 + 'dress',
749 + 'drift',
750 + 'drill',
751 + 'drink',
752 + 'drip',
753 + 'drive',
754 + 'drop',
755 + 'drum',
756 + 'dry',
757 + 'duck',
758 + 'dumb',
759 + 'dune',
760 + 'during',
761 + 'dust',
762 + 'dutch',
763 + 'duty',
764 + 'dwarf',
765 + 'dynamic',
766 + 'eager',
767 + 'eagle',
768 + 'early',
769 + 'earn',
770 + 'earth',
771 + 'easily',
772 + 'east',
773 + 'easy',
774 + 'echo',
775 + 'ecology',
776 + 'economy',
777 + 'edge',
778 + 'edit',
779 + 'educate',
780 + 'effort',
781 + 'egg',
782 + 'eight',
783 + 'either',
784 + 'elbow',
785 + 'elder',
786 + 'electric',
787 + 'elegant',
788 + 'element',
789 + 'elephant',
790 + 'elevator',
791 + 'elite',
792 + 'else',
793 + 'embark',
794 + 'embody',
795 + 'embrace',
796 + 'emerge',
797 + 'emotion',
798 + 'employ',
799 + 'empower',
800 + 'empty',
801 + 'enable',
802 + 'enact',
803 + 'end',
804 + 'endless',
805 + 'endorse',
806 + 'enemy',
807 + 'energy',
808 + 'enforce',
809 + 'engage',
810 + 'engine',
811 + 'enhance',
812 + 'enjoy',
813 + 'enlist',
814 + 'enough',
815 + 'enrich',
816 + 'enroll',
817 + 'ensure',
818 + 'enter',
819 + 'entire',
820 + 'entry',
821 + 'envelope',
822 + 'episode',
823 + 'equal',
824 + 'equip',
825 + 'era',
826 + 'erase',
827 + 'erode',
828 + 'erosion',
829 + 'error',
830 + 'erupt',
831 + 'escape',
832 + 'essay',
833 + 'essence',
834 + 'estate',
835 + 'eternal',
836 + 'ethics',
837 + 'evidence',
838 + 'evil',
839 + 'evoke',
840 + 'evolve',
841 + 'exact',
842 + 'example',
843 + 'excess',
844 + 'exchange',
845 + 'excite',
846 + 'exclude',
847 + 'excuse',
848 + 'execute',
849 + 'exercise',
850 + 'exhaust',
851 + 'exhibit',
852 + 'exile',
853 + 'exist',
854 + 'exit',
855 + 'exotic',
856 + 'expand',
857 + 'expect',
858 + 'expire',
859 + 'explain',
860 + 'expose',
861 + 'express',
862 + 'extend',
863 + 'extra',
864 + 'eye',
865 + 'eyebrow',
866 + 'fabric',
867 + 'face',
868 + 'faculty',
869 + 'fade',
870 + 'faint',
871 + 'faith',
872 + 'fall',
873 + 'false',
874 + 'fame',
875 + 'family',
876 + 'famous',
877 + 'fan',
878 + 'fancy',
879 + 'fantasy',
880 + 'farm',
881 + 'fashion',
882 + 'fat',
883 + 'fatal',
884 + 'father',
885 + 'fatigue',
886 + 'fault',
887 + 'favorite',
888 + 'feature',
889 + 'february',
890 + 'federal',
891 + 'fee',
892 + 'feed',
893 + 'feel',
894 + 'female',
895 + 'fence',
896 + 'festival',
897 + 'fetch',
898 + 'fever',
899 + 'few',
900 + 'fiber',
901 + 'fiction',
902 + 'field',
903 + 'figure',
904 + 'file',
905 + 'film',
906 + 'filter',
907 + 'final',
908 + 'find',
909 + 'fine',
910 + 'finger',
911 + 'finish',
912 + 'fire',
913 + 'firm',
914 + 'first',
915 + 'fiscal',
916 + 'fish',
917 + 'fit',
918 + 'fitness',
919 + 'fix',
920 + 'flag',
921 + 'flame',
922 + 'flash',
923 + 'flat',
924 + 'flavor',
925 + 'flee',
926 + 'flight',
927 + 'flip',
928 + 'float',
929 + 'flock',
930 + 'floor',
931 + 'flower',
932 + 'fluid',
933 + 'flush',
934 + 'fly',
935 + 'foam',
936 + 'focus',
937 + 'fog',
938 + 'foil',
939 + 'fold',
940 + 'follow',
941 + 'food',
942 + 'foot',
943 + 'force',
944 + 'forest',
945 + 'forget',
946 + 'fork',
947 + 'fortune',
948 + 'forum',
949 + 'forward',
950 + 'fossil',
951 + 'foster',
952 + 'found',
953 + 'fox',
954 + 'fragile',
955 + 'frame',
956 + 'frequent',
957 + 'fresh',
958 + 'friend',
959 + 'fringe',
960 + 'frog',
961 + 'front',
962 + 'frost',
963 + 'frown',
964 + 'frozen',
965 + 'fruit',
966 + 'fuel',
967 + 'fun',
968 + 'funny',
969 + 'furnace',
970 + 'fury',
971 + 'future',
972 + 'gadget',
973 + 'gain',
974 + 'galaxy',
975 + 'gallery',
976 + 'game',
977 + 'gap',
978 + 'garage',
979 + 'garbage',
980 + 'garden',
981 + 'garlic',
982 + 'garment',
983 + 'gas',
984 + 'gasp',
985 + 'gate',
986 + 'gather',
987 + 'gauge',
988 + 'gaze',
989 + 'general',
990 + 'genius',
991 + 'genre',
992 + 'gentle',
993 + 'genuine',
994 + 'gesture',
995 + 'ghost',
996 + 'giant',
997 + 'gift',
998 + 'giggle',
999 + 'ginger',
1000 + 'giraffe',
1001 + 'girl',
1002 + 'give',
1003 + 'glad',
1004 + 'glance',
1005 + 'glare',
1006 + 'glass',
1007 + 'glide',
1008 + 'glimpse',
1009 + 'globe',
1010 + 'gloom',
1011 + 'glory',
1012 + 'glove',
1013 + 'glow',
1014 + 'glue',
1015 + 'goat',
1016 + 'goddess',
1017 + 'gold',
1018 + 'good',
1019 + 'goose',
1020 + 'gorilla',
1021 + 'gospel',
1022 + 'gossip',
1023 + 'govern',
1024 + 'gown',
1025 + 'grab',
1026 + 'grace',
1027 + 'grain',
1028 + 'grant',
1029 + 'grape',
1030 + 'grass',
1031 + 'gravity',
1032 + 'great',
1033 + 'green',
1034 + 'grid',
1035 + 'grief',
1036 + 'grit',
1037 + 'grocery',
1038 + 'group',
1039 + 'grow',
1040 + 'grunt',
1041 + 'guard',
1042 + 'guess',
1043 + 'guide',
1044 + 'guilt',
1045 + 'guitar',
1046 + 'gun',
1047 + 'gym',
1048 + 'habit',
1049 + 'hair',
1050 + 'half',
1051 + 'hammer',
1052 + 'hamster',
1053 + 'hand',
1054 + 'happy',
1055 + 'harbor',
1056 + 'hard',
1057 + 'harsh',
1058 + 'harvest',
1059 + 'hat',
1060 + 'have',
1061 + 'hawk',
1062 + 'hazard',
1063 + 'head',
1064 + 'health',
1065 + 'heart',
1066 + 'heavy',
1067 + 'hedgehog',
1068 + 'height',
1069 + 'hello',
1070 + 'helmet',
1071 + 'help',
1072 + 'hen',
1073 + 'hero',
1074 + 'hidden',
1075 + 'high',
1076 + 'hill',
1077 + 'hint',
1078 + 'hip',
1079 + 'hire',
1080 + 'history',
1081 + 'hobby',
1082 + 'hockey',
1083 + 'hold',
1084 + 'hole',
1085 + 'holiday',
1086 + 'hollow',
1087 + 'home',
1088 + 'honey',
1089 + 'hood',
1090 + 'hope',
1091 + 'horn',
1092 + 'horror',
1093 + 'horse',
1094 + 'hospital',
1095 + 'host',
1096 + 'hotel',
1097 + 'hour',
1098 + 'hover',
1099 + 'hub',
1100 + 'huge',
1101 + 'human',
1102 + 'humble',
1103 + 'humor',
1104 + 'hundred',
1105 + 'hungry',
1106 + 'hunt',
1107 + 'hurdle',
1108 + 'hurry',
1109 + 'hurt',
1110 + 'husband',
1111 + 'hybrid',
1112 + 'ice',
1113 + 'icon',
1114 + 'idea',
1115 + 'identify',
1116 + 'idle',
1117 + 'ignore',
1118 + 'ill',
1119 + 'illegal',
1120 + 'illness',
1121 + 'image',
1122 + 'imitate',
1123 + 'immense',
1124 + 'immune',
1125 + 'impact',
1126 + 'impose',
1127 + 'improve',
1128 + 'impulse',
1129 + 'inch',
1130 + 'include',
1131 + 'income',
1132 + 'increase',
1133 + 'index',
1134 + 'indicate',
1135 + 'indoor',
1136 + 'industry',
1137 + 'infant',
1138 + 'inflict',
1139 + 'inform',
1140 + 'inhale',
1141 + 'inherit',
1142 + 'initial',
1143 + 'inject',
1144 + 'injury',
1145 + 'inmate',
1146 + 'inner',
1147 + 'innocent',
1148 + 'input',
1149 + 'inquiry',
1150 + 'insane',
1151 + 'insect',
1152 + 'inside',
1153 + 'inspire',
1154 + 'install',
1155 + 'intact',
1156 + 'interest',
1157 + 'into',
1158 + 'invest',
1159 + 'invite',
1160 + 'involve',
1161 + 'iron',
1162 + 'island',
1163 + 'isolate',
1164 + 'issue',
1165 + 'item',
1166 + 'ivory',
1167 + 'jacket',
1168 + 'jaguar',
1169 + 'jar',
1170 + 'jazz',
1171 + 'jealous',
1172 + 'jeans',
1173 + 'jelly',
1174 + 'jewel',
1175 + 'job',
1176 + 'join',
1177 + 'joke',
1178 + 'journey',
1179 + 'joy',
1180 + 'judge',
1181 + 'juice',
1182 + 'jump',
1183 + 'jungle',
1184 + 'junior',
1185 + 'junk',
1186 + 'just',
1187 + 'kangaroo',
1188 + 'keen',
1189 + 'keep',
1190 + 'ketchup',
1191 + 'key',
1192 + 'kick',
1193 + 'kid',
1194 + 'kidney',
1195 + 'kind',
1196 + 'kingdom',
1197 + 'kiss',
1198 + 'kit',
1199 + 'kitchen',
1200 + 'kite',
1201 + 'kitten',
1202 + 'kiwi',
1203 + 'knee',
1204 + 'knife',
1205 + 'knock',
1206 + 'know',
1207 + 'lab',
1208 + 'label',
1209 + 'labor',
1210 + 'ladder',
1211 + 'lady',
1212 + 'lake',
1213 + 'lamp',
1214 + 'language',
1215 + 'laptop',
1216 + 'large',
1217 + 'later',
1218 + 'latin',
1219 + 'laugh',
1220 + 'laundry',
1221 + 'lava',
1222 + 'law',
1223 + 'lawn',
1224 + 'lawsuit',
1225 + 'layer',
1226 + 'lazy',
1227 + 'leader',
1228 + 'leaf',
1229 + 'learn',
1230 + 'leave',
1231 + 'lecture',
1232 + 'left',
1233 + 'leg',
1234 + 'legal',
1235 + 'legend',
1236 + 'leisure',
1237 + 'lemon',
1238 + 'lend',
1239 + 'length',
1240 + 'lens',
1241 + 'leopard',
1242 + 'lesson',
1243 + 'letter',
1244 + 'level',
1245 + 'liar',
1246 + 'liberty',
1247 + 'library',
1248 + 'license',
1249 + 'life',
1250 + 'lift',
1251 + 'light',
1252 + 'like',
1253 + 'limb',
1254 + 'limit',
1255 + 'link',
1256 + 'lion',
1257 + 'liquid',
1258 + 'list',
1259 + 'little',
1260 + 'live',
1261 + 'lizard',
1262 + 'load',
1263 + 'loan',
1264 + 'lobster',
1265 + 'local',
1266 + 'lock',
1267 + 'logic',
1268 + 'lonely',
1269 + 'long',
1270 + 'loop',
1271 + 'lottery',
1272 + 'loud',
1273 + 'lounge',
1274 + 'love',
1275 + 'loyal',
1276 + 'lucky',
1277 + 'luggage',
1278 + 'lumber',
1279 + 'lunar',
1280 + 'lunch',
1281 + 'luxury',
1282 + 'lyrics',
1283 + 'machine',
1284 + 'mad',
1285 + 'magic',
1286 + 'magnet',
1287 + 'maid',
1288 + 'mail',
1289 + 'main',
1290 + 'major',
1291 + 'make',
1292 + 'mammal',
1293 + 'man',
1294 + 'manage',
1295 + 'mandate',
1296 + 'mango',
1297 + 'mansion',
1298 + 'manual',
1299 + 'maple',
1300 + 'marble',
1301 + 'march',
1302 + 'margin',
1303 + 'marine',
1304 + 'market',
1305 + 'marriage',
1306 + 'mask',
1307 + 'mass',
1308 + 'master',
1309 + 'match',
1310 + 'material',
1311 + 'math',
1312 + 'matrix',
1313 + 'matter',
1314 + 'maximum',
1315 + 'maze',
1316 + 'meadow',
1317 + 'mean',
1318 + 'measure',
1319 + 'meat',
1320 + 'mechanic',
1321 + 'medal',
1322 + 'media',
1323 + 'melody',
1324 + 'melt',
1325 + 'member',
1326 + 'memory',
1327 + 'mention',
1328 + 'menu',
1329 + 'mercy',
1330 + 'merge',
1331 + 'merit',
1332 + 'merry',
1333 + 'mesh',
1334 + 'message',
1335 + 'metal',
1336 + 'method',
1337 + 'middle',
1338 + 'midnight',
1339 + 'milk',
1340 + 'million',
1341 + 'mimic',
1342 + 'mind',
1343 + 'minimum',
1344 + 'minor',
1345 + 'minute',
1346 + 'miracle',
1347 + 'mirror',
1348 + 'misery',
1349 + 'miss',
1350 + 'mistake',
1351 + 'mix',
1352 + 'mixed',
1353 + 'mixture',
1354 + 'mobile',
1355 + 'model',
1356 + 'modify',
1357 + 'mom',
1358 + 'moment',
1359 + 'monitor',
1360 + 'monkey',
1361 + 'monster',
1362 + 'month',
1363 + 'moon',
1364 + 'moral',
1365 + 'more',
1366 + 'morning',
1367 + 'mosquito',
1368 + 'mother',
1369 + 'motion',
1370 + 'motor',
1371 + 'mountain',
1372 + 'mouse',
1373 + 'move',
1374 + 'movie',
1375 + 'much',
1376 + 'muffin',
1377 + 'mule',
1378 + 'multiply',
1379 + 'muscle',
1380 + 'museum',
1381 + 'mushroom',
1382 + 'music',
1383 + 'must',
1384 + 'mutual',
1385 + 'myself',
1386 + 'mystery',
1387 + 'myth',
1388 + 'naive',
1389 + 'name',
1390 + 'napkin',
1391 + 'narrow',
1392 + 'nasty',
1393 + 'nation',
1394 + 'nature',
1395 + 'near',
1396 + 'neck',
1397 + 'need',
1398 + 'negative',
1399 + 'neglect',
1400 + 'neither',
1401 + 'nephew',
1402 + 'nerve',
1403 + 'nest',
1404 + 'net',
1405 + 'network',
1406 + 'neutral',
1407 + 'never',
1408 + 'news',
1409 + 'next',
1410 + 'nice',
1411 + 'night',
1412 + 'noble',
1413 + 'noise',
1414 + 'nominee',
1415 + 'noodle',
1416 + 'normal',
1417 + 'north',
1418 + 'nose',
1419 + 'notable',
1420 + 'note',
1421 + 'nothing',
1422 + 'notice',
1423 + 'novel',
1424 + 'now',
1425 + 'nuclear',
1426 + 'number',
1427 + 'nurse',
1428 + 'nut',
1429 + 'oak',
1430 + 'obey',
1431 + 'object',
1432 + 'oblige',
1433 + 'obscure',
1434 + 'observe',
1435 + 'obtain',
1436 + 'obvious',
1437 + 'occur',
1438 + 'ocean',
1439 + 'october',
1440 + 'odor',
1441 + 'off',
1442 + 'offer',
1443 + 'office',
1444 + 'often',
1445 + 'oil',
1446 + 'okay',
1447 + 'old',
1448 + 'olive',
1449 + 'olympic',
1450 + 'omit',
1451 + 'once',
1452 + 'one',
1453 + 'onion',
1454 + 'online',
1455 + 'only',
1456 + 'open',
1457 + 'opera',
1458 + 'opinion',
1459 + 'oppose',
1460 + 'option',
1461 + 'orange',
1462 + 'orbit',
1463 + 'orchard',
1464 + 'order',
1465 + 'ordinary',
1466 + 'organ',
1467 + 'orient',
1468 + 'original',
1469 + 'orphan',
1470 + 'ostrich',
1471 + 'other',
1472 + 'outdoor',
1473 + 'outer',
1474 + 'output',
1475 + 'outside',
1476 + 'oval',
1477 + 'oven',
1478 + 'over',
1479 + 'own',
1480 + 'owner',
1481 + 'oxygen',
1482 + 'oyster',
1483 + 'ozone',
1484 + 'pact',
1485 + 'paddle',
1486 + 'page',
1487 + 'pair',
1488 + 'palace',
1489 + 'palm',
1490 + 'panda',
1491 + 'panel',
1492 + 'panic',
1493 + 'panther',
1494 + 'paper',
1495 + 'parade',
1496 + 'parent',
1497 + 'park',
1498 + 'parrot',
1499 + 'party',
1500 + 'pass',
1501 + 'patch',
1502 + 'path',
1503 + 'patient',
1504 + 'patrol',
1505 + 'pattern',
1506 + 'pause',
1507 + 'pave',
1508 + 'payment',
1509 + 'peace',
1510 + 'peanut',
1511 + 'pear',
1512 + 'peasant',
1513 + 'pelican',
1514 + 'pen',
1515 + 'penalty',
1516 + 'pencil',
1517 + 'people',
1518 + 'pepper',
1519 + 'perfect',
1520 + 'permit',
1521 + 'person',
1522 + 'pet',
1523 + 'phone',
1524 + 'photo',
1525 + 'phrase',
1526 + 'physical',
1527 + 'piano',
1528 + 'picnic',
1529 + 'picture',
1530 + 'piece',
1531 + 'pig',
1532 + 'pigeon',
1533 + 'pill',
1534 + 'pilot',
1535 + 'pink',
1536 + 'pioneer',
1537 + 'pipe',
1538 + 'pistol',
1539 + 'pitch',
1540 + 'pizza',
1541 + 'place',
1542 + 'planet',
1543 + 'plastic',
1544 + 'plate',
1545 + 'play',
1546 + 'please',
1547 + 'pledge',
1548 + 'pluck',
1549 + 'plug',
1550 + 'plunge',
1551 + 'poem',
1552 + 'poet',
1553 + 'point',
1554 + 'polar',
1555 + 'pole',
1556 + 'police',
1557 + 'pond',
1558 + 'pony',
1559 + 'pool',
1560 + 'popular',
1561 + 'portion',
1562 + 'position',
1563 + 'possible',
1564 + 'post',
1565 + 'potato',
1566 + 'pottery',
1567 + 'poverty',
1568 + 'powder',
1569 + 'power',
1570 + 'practice',
1571 + 'praise',
1572 + 'predict',
1573 + 'prefer',
1574 + 'prepare',
1575 + 'present',
1576 + 'pretty',
1577 + 'prevent',
1578 + 'price',
1579 + 'pride',
1580 + 'primary',
1581 + 'print',
1582 + 'priority',
1583 + 'prison',
1584 + 'private',
1585 + 'prize',
1586 + 'problem',
1587 + 'process',
1588 + 'produce',
1589 + 'profit',
1590 + 'program',
1591 + 'project',
1592 + 'promote',
1593 + 'proof',
1594 + 'property',
1595 + 'prosper',
1596 + 'protect',
1597 + 'proud',
1598 + 'provide',
1599 + 'public',
1600 + 'pudding',
1601 + 'pull',
1602 + 'pulp',
1603 + 'pulse',
1604 + 'pumpkin',
1605 + 'punch',
1606 + 'pupil',
1607 + 'puppy',
1608 + 'purchase',
1609 + 'purity',
1610 + 'purpose',
1611 + 'purse',
1612 + 'push',
1613 + 'put',
1614 + 'puzzle',
1615 + 'pyramid',
1616 + 'quality',
1617 + 'quantum',
1618 + 'quarter',
1619 + 'question',
1620 + 'quick',
1621 + 'quit',
1622 + 'quiz',
1623 + 'quote',
1624 + 'rabbit',
1625 + 'raccoon',
1626 + 'race',
1627 + 'rack',
1628 + 'radar',
1629 + 'radio',
1630 + 'rail',
1631 + 'rain',
1632 + 'raise',
1633 + 'rally',
1634 + 'ramp',
1635 + 'ranch',
1636 + 'random',
1637 + 'range',
1638 + 'rapid',
1639 + 'rare',
1640 + 'rate',
1641 + 'rather',
1642 + 'raven',
1643 + 'raw',
1644 + 'razor',
1645 + 'ready',
1646 + 'real',
1647 + 'reason',
1648 + 'rebel',
1649 + 'rebuild',
1650 + 'recall',
1651 + 'receive',
1652 + 'recipe',
1653 + 'record',
1654 + 'recycle',
1655 + 'reduce',
1656 + 'reflect',
1657 + 'reform',
1658 + 'refuse',
1659 + 'region',
1660 + 'regret',
1661 + 'regular',
1662 + 'reject',
1663 + 'relax',
1664 + 'release',
1665 + 'relief',
1666 + 'rely',
1667 + 'remain',
1668 + 'remember',
1669 + 'remind',
1670 + 'remove',
1671 + 'render',
1672 + 'renew',
1673 + 'rent',
1674 + 'reopen',
1675 + 'repair',
1676 + 'repeat',
1677 + 'replace',
1678 + 'report',
1679 + 'require',
1680 + 'rescue',
1681 + 'resemble',
1682 + 'resist',
1683 + 'resource',
1684 + 'response',
1685 + 'result',
1686 + 'retire',
1687 + 'retreat',
1688 + 'return',
1689 + 'reunion',
1690 + 'reveal',
1691 + 'review',
1692 + 'reward',
1693 + 'rhythm',
1694 + 'rib',
1695 + 'ribbon',
1696 + 'rice',
1697 + 'rich',
1698 + 'ride',
1699 + 'ridge',
1700 + 'rifle',
1701 + 'right',
1702 + 'rigid',
1703 + 'ring',
1704 + 'riot',
1705 + 'ripple',
1706 + 'risk',
1707 + 'ritual',
1708 + 'rival',
1709 + 'river',
1710 + 'road',
1711 + 'roast',
1712 + 'robot',
1713 + 'robust',
1714 + 'rocket',
1715 + 'romance',
1716 + 'roof',
1717 + 'rookie',
1718 + 'room',
1719 + 'rose',
1720 + 'rotate',
1721 + 'rough',
1722 + 'round',
1723 + 'route',
1724 + 'royal',
1725 + 'rubber',
1726 + 'rude',
1727 + 'rug',
1728 + 'rule',
1729 + 'run',
1730 + 'runway',
1731 + 'rural',
1732 + 'sad',
1733 + 'saddle',
1734 + 'sadness',
1735 + 'safe',
1736 + 'sail',
1737 + 'salad',
1738 + 'salmon',
1739 + 'salon',
1740 + 'salt',
1741 + 'salute',
1742 + 'same',
1743 + 'sample',
1744 + 'sand',
1745 + 'satisfy',
1746 + 'satoshi',
1747 + 'sauce',
1748 + 'sausage',
1749 + 'save',
1750 + 'say',
1751 + 'scale',
1752 + 'scan',
1753 + 'scare',
1754 + 'scatter',
1755 + 'scene',
1756 + 'scheme',
1757 + 'school',
1758 + 'science',
1759 + 'scissors',
1760 + 'scorpion',
1761 + 'scout',
1762 + 'scrap',
1763 + 'screen',
1764 + 'script',
1765 + 'scrub',
1766 + 'sea',
1767 + 'search',
1768 + 'season',
1769 + 'seat',
1770 + 'second',
1771 + 'secret',
1772 + 'section',
1773 + 'security',
1774 + 'seed',
1775 + 'seek',
1776 + 'segment',
1777 + 'select',
1778 + 'sell',
1779 + 'seminar',
1780 + 'senior',
1781 + 'sense',
1782 + 'sentence',
1783 + 'series',
1784 + 'service',
1785 + 'session',
1786 + 'settle',
1787 + 'setup',
1788 + 'seven',
1789 + 'shadow',
1790 + 'shaft',
1791 + 'shallow',
1792 + 'share',
1793 + 'shed',
1794 + 'shell',
1795 + 'sheriff',
1796 + 'shield',
1797 + 'shift',
1798 + 'shine',
1799 + 'ship',
1800 + 'shiver',
1801 + 'shock',
1802 + 'shoe',
1803 + 'shoot',
1804 + 'shop',
1805 + 'short',
1806 + 'shoulder',
1807 + 'shove',
1808 + 'shrimp',
1809 + 'shrug',
1810 + 'shuffle',
1811 + 'shy',
1812 + 'sibling',
1813 + 'sick',
1814 + 'side',
1815 + 'siege',
1816 + 'sight',
1817 + 'sign',
1818 + 'silent',
1819 + 'silk',
1820 + 'silly',
1821 + 'silver',
1822 + 'similar',
1823 + 'simple',
1824 + 'since',
1825 + 'sing',
1826 + 'siren',
1827 + 'sister',
1828 + 'situate',
1829 + 'six',
1830 + 'size',
1831 + 'skate',
1832 + 'sketch',
1833 + 'ski',
1834 + 'skill',
1835 + 'skin',
1836 + 'skirt',
1837 + 'skull',
1838 + 'slab',
1839 + 'slam',
1840 + 'sleep',
1841 + 'slender',
1842 + 'slice',
1843 + 'slide',
1844 + 'slight',
1845 + 'slim',
1846 + 'slogan',
1847 + 'slot',
1848 + 'slow',
1849 + 'slush',
1850 + 'small',
1851 + 'smart',
1852 + 'smile',
1853 + 'smoke',
1854 + 'smooth',
1855 + 'snack',
1856 + 'snake',
1857 + 'snap',
1858 + 'sniff',
1859 + 'snow',
1860 + 'soap',
1861 + 'soccer',
1862 + 'social',
1863 + 'sock',
1864 + 'soda',
1865 + 'soft',
1866 + 'solar',
1867 + 'soldier',
1868 + 'solid',
1869 + 'solution',
1870 + 'solve',
1871 + 'someone',
1872 + 'song',
1873 + 'soon',
1874 + 'sorry',
1875 + 'sort',
1876 + 'soul',
1877 + 'sound',
1878 + 'soup',
1879 + 'source',
1880 + 'south',
1881 + 'space',
1882 + 'spare',
1883 + 'spatial',
1884 + 'spawn',
1885 + 'speak',
1886 + 'special',
1887 + 'speed',
1888 + 'spell',
1889 + 'spend',
1890 + 'sphere',
1891 + 'spice',
1892 + 'spider',
1893 + 'spike',
1894 + 'spin',
1895 + 'spirit',
1896 + 'split',
1897 + 'spoil',
1898 + 'sponsor',
1899 + 'spoon',
1900 + 'sport',
1901 + 'spot',
1902 + 'spray',
1903 + 'spread',
1904 + 'spring',
1905 + 'spy',
1906 + 'square',
1907 + 'squeeze',
1908 + 'squirrel',
1909 + 'stable',
1910 + 'stadium',
1911 + 'staff',
1912 + 'stage',
1913 + 'stairs',
1914 + 'stamp',
1915 + 'stand',
1916 + 'start',
1917 + 'state',
1918 + 'stay',
1919 + 'steak',
1920 + 'steel',
1921 + 'stem',
1922 + 'step',
1923 + 'stereo',
1924 + 'stick',
1925 + 'still',
1926 + 'sting',
1927 + 'stock',
1928 + 'stomach',
1929 + 'stone',
1930 + 'stool',
1931 + 'story',
1932 + 'stove',
1933 + 'strategy',
1934 + 'street',
1935 + 'strike',
1936 + 'strong',
1937 + 'struggle',
1938 + 'student',
1939 + 'stuff',
1940 + 'stumble',
1941 + 'style',
1942 + 'subject',
1943 + 'submit',
1944 + 'subway',
1945 + 'success',
1946 + 'such',
1947 + 'sudden',
1948 + 'suffer',
1949 + 'sugar',
1950 + 'suggest',
1951 + 'suit',
1952 + 'summer',
1953 + 'sun',
1954 + 'sunny',
1955 + 'sunset',
1956 + 'super',
1957 + 'supply',
1958 + 'supreme',
1959 + 'sure',
1960 + 'surface',
1961 + 'surge',
1962 + 'surprise',
1963 + 'surround',
1964 + 'survey',
1965 + 'suspect',
1966 + 'sustain',
1967 + 'swallow',
1968 + 'swamp',
1969 + 'swap',
1970 + 'swarm',
1971 + 'swear',
1972 + 'sweet',
1973 + 'swift',
1974 + 'swim',
1975 + 'swing',
1976 + 'switch',
1977 + 'sword',
1978 + 'symbol',
1979 + 'symptom',
1980 + 'syrup',
1981 + 'system',
1982 + 'table',
1983 + 'tackle',
1984 + 'tag',
1985 + 'tail',
1986 + 'talent',
1987 + 'talk',
1988 + 'tank',
1989 + 'tape',
1990 + 'target',
1991 + 'task',
1992 + 'taste',
1993 + 'tattoo',
1994 + 'taxi',
1995 + 'teach',
1996 + 'team',
1997 + 'tell',
1998 + 'ten',
1999 + 'tenant',
2000 + 'tennis',
2001 + 'tent',
2002 + 'term',
2003 + 'test',
2004 + 'text',
2005 + 'thank',
2006 + 'that',
2007 + 'theme',
2008 + 'then',
2009 + 'theory',
2010 + 'there',
2011 + 'they',
2012 + 'thing',
2013 + 'this',
2014 + 'thought',
2015 + 'three',
2016 + 'thrive',
2017 + 'throw',
2018 + 'thumb',
2019 + 'thunder',
2020 + 'ticket',
2021 + 'tide',
2022 + 'tiger',
2023 + 'tilt',
2024 + 'timber',
2025 + 'time',
2026 + 'tiny',
2027 + 'tip',
2028 + 'tired',
2029 + 'tissue',
2030 + 'title',
2031 + 'toast',
2032 + 'tobacco',
2033 + 'today',
2034 + 'toddler',
2035 + 'toe',
2036 + 'together',
2037 + 'toilet',
2038 + 'token',
2039 + 'tomato',
2040 + 'tomorrow',
2041 + 'tone',
2042 + 'tongue',
2043 + 'tonight',
2044 + 'tool',
2045 + 'tooth',
2046 + 'top',
2047 + 'topic',
2048 + 'topple',
2049 + 'torch',
2050 + 'tornado',
2051 + 'tortoise',
2052 + 'toss',
2053 + 'total',
2054 + 'tourist',
2055 + 'toward',
2056 + 'tower',
2057 + 'town',
2058 + 'toy',
2059 + 'track',
2060 + 'trade',
2061 + 'traffic',
2062 + 'tragic',
2063 + 'train',
2064 + 'transfer',
2065 + 'trap',
2066 + 'trash',
2067 + 'travel',
2068 + 'tray',
2069 + 'treat',
2070 + 'tree',
2071 + 'trend',
2072 + 'trial',
2073 + 'tribe',
2074 + 'trick',
2075 + 'trigger',
2076 + 'trim',
2077 + 'trip',
2078 + 'trophy',
2079 + 'trouble',
2080 + 'truck',
2081 + 'true',
2082 + 'truly',
2083 + 'trumpet',
2084 + 'trust',
2085 + 'truth',
2086 + 'try',
2087 + 'tube',
2088 + 'tuition',
2089 + 'tumble',
2090 + 'tuna',
2091 + 'tunnel',
2092 + 'turkey',
2093 + 'turn',
2094 + 'turtle',
2095 + 'twelve',
2096 + 'twenty',
2097 + 'twice',
2098 + 'twin',
2099 + 'twist',
2100 + 'two',
2101 + 'type',
2102 + 'typical',
2103 + 'ugly',
2104 + 'umbrella',
2105 + 'unable',
2106 + 'unaware',
2107 + 'uncle',
2108 + 'uncover',
2109 + 'under',
2110 + 'undo',
2111 + 'unfair',
2112 + 'unfold',
2113 + 'unhappy',
2114 + 'uniform',
2115 + 'unique',
2116 + 'unit',
2117 + 'universe',
2118 + 'unknown',
2119 + 'unlock',
2120 + 'until',
2121 + 'unusual',
2122 + 'unveil',
2123 + 'update',
2124 + 'upgrade',
2125 + 'uphold',
2126 + 'upon',
2127 + 'upper',
2128 + 'upset',
2129 + 'urban',
2130 + 'urge',
2131 + 'usage',
2132 + 'use',
2133 + 'used',
2134 + 'useful',
2135 + 'useless',
2136 + 'usual',
2137 + 'utility',
2138 + 'vacant',
2139 + 'vacuum',
2140 + 'vague',
2141 + 'valid',
2142 + 'valley',
2143 + 'valve',
2144 + 'van',
2145 + 'vanish',
2146 + 'vapor',
2147 + 'various',
2148 + 'vast',
2149 + 'vault',
2150 + 'vehicle',
2151 + 'velvet',
2152 + 'vendor',
2153 + 'venture',
2154 + 'venue',
2155 + 'verb',
2156 + 'verify',
2157 + 'version',
2158 + 'very',
2159 + 'vessel',
2160 + 'veteran',
2161 + 'viable',
2162 + 'vibrant',
2163 + 'vicious',
2164 + 'victory',
2165 + 'video',
2166 + 'view',
2167 + 'village',
2168 + 'vintage',
2169 + 'violin',
2170 + 'virtual',
2171 + 'virus',
2172 + 'visa',
2173 + 'visit',
2174 + 'visual',
2175 + 'vital',
2176 + 'vivid',
2177 + 'vocal',
2178 + 'voice',
2179 + 'void',
2180 + 'volcano',
2181 + 'volume',
2182 + 'vote',
2183 + 'voyage',
2184 + 'wage',
2185 + 'wagon',
2186 + 'wait',
2187 + 'walk',
2188 + 'wall',
2189 + 'walnut',
2190 + 'want',
2191 + 'warfare',
2192 + 'warm',
2193 + 'warrior',
2194 + 'wash',
2195 + 'wasp',
2196 + 'waste',
2197 + 'water',
2198 + 'wave',
2199 + 'way',
2200 + 'wealth',
2201 + 'weapon',
2202 + 'wear',
2203 + 'weasel',
2204 + 'weather',
2205 + 'web',
2206 + 'wedding',
2207 + 'weekend',
2208 + 'weird',
2209 + 'welcome',
2210 + 'west',
2211 + 'wet',
2212 + 'whale',
2213 + 'what',
2214 + 'wheat',
2215 + 'wheel',
2216 + 'when',
2217 + 'where',
2218 + 'whip',
2219 + 'whisper',
2220 + 'wide',
2221 + 'width',
2222 + 'wife',
2223 + 'wild',
2224 + 'will',
2225 + 'win',
2226 + 'window',
2227 + 'wine',
2228 + 'wing',
2229 + 'wink',
2230 + 'winner',
2231 + 'winter',
2232 + 'wire',
2233 + 'wisdom',
2234 + 'wise',
2235 + 'wish',
2236 + 'witness',
2237 + 'wolf',
2238 + 'woman',
2239 + 'wonder',
2240 + 'wood',
2241 + 'wool',
2242 + 'word',
2243 + 'work',
2244 + 'world',
2245 + 'worry',
2246 + 'worth',
2247 + 'wrap',
2248 + 'wreck',
2249 + 'wrestle',
2250 + 'wrist',
2251 + 'write',
2252 + 'wrong',
2253 + 'yard',
2254 + 'year',
2255 + 'yellow',
2256 + 'you',
2257 + 'young',
2258 + 'youth',
2259 + 'zebra',
2260 + 'zero',
2261 + 'zone',
2262 + 'zoo'
2263 +];
linux/CMakeLists.txt
+3
@@ -113,6 +113,9 @@ else()
113 set(LIB_TRIPLET "x86_64-linux-gnu")
114 endif()
115
116 +install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/zcash_lib/target/release/libwarp_api_ffi.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" RENAME "libwarp_api_ffi.so"
117 + COMPONENT Runtime)
118 +
119 install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/monero_c/release/monero/${LIB_TRIPLET}_libwallet2_api_c.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" RENAME "monero_libwallet2_api_c.so"
120 COMPONENT Runtime)
121
macos/Runner.xcodeproj/project.pbxproj
+4
@@ -28,6 +28,7 @@
28 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
29 83FF6BF911B29965D3589BE7 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BD06F8F6E3797AC031038136 /* Pods_Runner.framework */; };
30 9F565D5929954F53009A75FB /* secRandom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F565D5729954F53009A75FB /* secRandom.swift */; };
31 + CE46BF642F15C020000A6140 /* WarpApiFFI.xcframework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE46BF632F15C020000A6140 /* WarpApiFFI.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
32 CE75FC4A2C147EBA00CCC46E /* wownero_libwallet2_api_c.dylib in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE75FC492C147EBA00CCC46E /* wownero_libwallet2_api_c.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
33 CED5DBE42BE59BBF0065028F /* monero_libwallet2_api_c.dylib in CopyFiles */ = {isa = PBXBuildFile; fileRef = CED5DBE32BE59BBF0065028F /* monero_libwallet2_api_c.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
34 /* End PBXBuildFile section */
@@ -59,6 +60,7 @@
60 dstPath = "";
61 dstSubfolderSpec = 10;
62 files = (
63 + CE46BF642F15C020000A6140 /* WarpApiFFI.xcframework in CopyFiles */,
64 CE75FC4A2C147EBA00CCC46E /* wownero_libwallet2_api_c.dylib in CopyFiles */,
65 CED5DBE42BE59BBF0065028F /* monero_libwallet2_api_c.dylib in CopyFiles */,
66 );
@@ -90,6 +92,7 @@
92 9F1E7BFB2BF2D27500C28C9A /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
93 9F565D5729954F53009A75FB /* secRandom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = secRandom.swift; path = CakeWallet/secRandom.swift; sourceTree = "<group>"; };
94 BD06F8F6E3797AC031038136 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
95 + CE46BF632F15C020000A6140 /* WarpApiFFI.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = WarpApiFFI.xcframework; path = ../scripts/zcash_lib/WarpApiFFI.xcframework; sourceTree = "<group>"; };
96 CE75FC492C147EBA00CCC46E /* wownero_libwallet2_api_c.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = wownero_libwallet2_api_c.dylib; sourceTree = "<group>"; };
97 CED5DBE32BE59BBF0065028F /* monero_libwallet2_api_c.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = monero_libwallet2_api_c.dylib; sourceTree = "<group>"; };
98 /* End PBXFileReference section */
@@ -120,6 +123,7 @@
123 33CC10E42044A3C60003C045 = {
124 isa = PBXGroup;
125 children = (
126 + CE46BF632F15C020000A6140 /* WarpApiFFI.xcframework */,
127 CE75FC492C147EBA00CCC46E /* wownero_libwallet2_api_c.dylib */,
128 CED5DBE32BE59BBF0065028F /* monero_libwallet2_api_c.dylib */,
129 9F565D5729954F53009A75FB /* secRandom.swift */,
model_generator.sh
+1 -1
@@ -1,7 +1,7 @@
1 #!/bin/bash
2 set -x -e
3
4 -for cwcoin in cw_{core,evm,monero,bitcoin,nano,bitcoin_cash,solana,tron,wownero,zano,decred,dogecoin}
4 +for cwcoin in cw_{core,evm,monero,bitcoin,nano,bitcoin_cash,solana,tron,wownero,zano,decred,dogecoin,zcash}
5 do
6 if [[ "x$1" == "xasync" ]];
7 then
pubspec_base.yaml
+3
@@ -241,9 +241,12 @@ flutter:
241 - assets/dogecoin_electrum_server_list.yml
242 - assets/base_node_list.yml
243 - assets/arbitrum_node_list.yml
244 + - assets/zcash_node_list.yml
245 - assets/text/
246 - assets/faq/
247 - assets/animation/
248 + - scripts/zcash_lib/assets/sapling-spend.params
249 + - scripts/zcash_lib/assets/sapling-output.params
250
251 fonts:
252 - family: Lato
res/values/strings_cs.arb
+2 -2
@@ -515,7 +515,7 @@
515 "matrix_green_dark_theme": "Tmavé téma Matrix Green",
516 "max_amount": "Max: ${value}",
517 "max_value": "Max: ${value} ${currency}",
518 - "memo": "Memo:",
518 + "memo": "Memo",
519 "message": "Zpráva",
520 "message_verified": "Zpráva byla úspěšně ověřena",
521 "messages": "Zprávy",
@@ -1172,4 +1172,4 @@
1172 "youCanGoBackToYourDapp": "Nyní se můžete vrátit do svého dappu",
1173 "your": "Vaše",
1174 "yy": "YY"
1175 -}
\ No newline at end of file
1175 +}
res/values/strings_de.arb
+1 -1
@@ -515,7 +515,7 @@
515 "matrix_green_dark_theme": "Matrix Green Dark Theme",
516 "max_amount": "Max: ${value}",
517 "max_value": "Max: ${value} ${currency}",
518 - "memo": "Memo:",
518 + "memo": "Memo",
519 "message": "Nachricht",
520 "message_verified": "Die Nachricht wurde erfolgreich überprüft",
521 "messages": "Nachrichten",
res/values/strings_en.arb
+2 -2
@@ -515,7 +515,7 @@
515 "matrix_green_dark_theme": "Matrix Green Dark Theme",
516 "max_amount": "Max: ${value}",
517 "max_value": "Max: ${value} ${currency}",
518 - "memo": "Memo:",
518 + "memo": "Memo",
519 "message": "Message",
520 "message_verified": "The message was successfully verified",
521 "messages": "Messages",
@@ -1173,4 +1173,4 @@
1173 "youCanGoBackToYourDapp": "You can go back to your dApp now",
1174 "your": "Your",
1175 "yy": "YY"
1176 -}
\ No newline at end of file
1176 +}
res/values/strings_es.arb
+2 -2
@@ -515,7 +515,7 @@
515 "matrix_green_dark_theme": "Tema oscuro Matrix Green",
516 "max_amount": "Máx: ${value}",
517 "max_value": "Máx: ${value} ${currency}",
518 - "memo": "Memo:",
518 + "memo": "Memo",
519 "message": "Mensaje",
520 "message_verified": "El mensaje se verificó con éxito",
521 "messages": "Mensajes",
@@ -1174,4 +1174,4 @@
1174 "youCanGoBackToYourDapp": "Ahora puedes volver a tu dApp",
1175 "your": "Tu",
1176 "yy": "YY"
1177 -}
\ No newline at end of file
1177 +}
res/values/strings_ha.arb
+2 -2
@@ -515,7 +515,7 @@
515 "matrix_green_dark_theme": "Matrix Green Dark Jigo",
516 "max_amount": "Max: ${value}",
517 "max_value": "Max: ${value} ${currency}",
518 - "memo": "Memo:",
518 + "memo": "Memo",
519 "message": "Sako",
520 "message_verified": "An yi nasarar tabbatar da sakon",
521 "messages": "Manegiya",
@@ -1175,4 +1175,4 @@
1175 "youCanGoBackToYourDapp": "Kuna iya komawa zuwa DPP ɗinku yanzu",
1176 "your": "Naku",
1177 "yy": "YY"
1178 -}
\ No newline at end of file
1178 +}
res/values/strings_hr.arb
+2 -2
@@ -515,7 +515,7 @@
515 "matrix_green_dark_theme": "Matrix Green Dark Theme",
516 "max_amount": "Maksimum: ${value}",
517 "max_value": "Maks.: ${value} ${currency}",
518 - "memo": "Memo:",
518 + "memo": "Memo",
519 "message": "Poruka",
520 "message_verified": "Poruka je uspješno provjerena",
521 "messages": "Poruke",
@@ -1172,4 +1172,4 @@
1172 "youCanGoBackToYourDapp": "Sada se možete vratiti na svoj dapp",
1173 "your": "Vaš",
1174 "yy": "GG"
1175 -}
\ No newline at end of file
1175 +}
res/values/strings_id.arb
+2 -2
@@ -515,7 +515,7 @@
515 "matrix_green_dark_theme": "Tema Matrix Green Dark",
516 "max_amount": "Max: ${value}",
517 "max_value": "Max: ${value} ${currency}",
518 - "memo": "Memo:",
518 + "memo": "Memo",
519 "message": "Pesan",
520 "message_verified": "Pesan itu berhasil diverifikasi",
521 "messages": "Pesan",
@@ -1175,4 +1175,4 @@
1175 "youCanGoBackToYourDapp": "Anda dapat kembali ke dapp Anda sekarang",
1176 "your": "Milikmu",
1177 "yy": "YY"
1178 -}
\ No newline at end of file
1178 +}
res/values/strings_it.arb
+2 -2
@@ -515,7 +515,7 @@
515 "matrix_green_dark_theme": "Tema Matrix verde scuro",
516 "max_amount": "Max: ${value}",
517 "max_value": "Max: ${value} ${currency}",
518 - "memo": "Memo:",
518 + "memo": "Memo",
519 "message": "Messaggio",
520 "message_verified": "Il messaggio è stato verificato con successo",
521 "messages": "Messaggi",
@@ -1175,4 +1175,4 @@
1175 "youCanGoBackToYourDapp": "Puoi tornare al tuo DApp ora",
1176 "your": "Tuo",
1177 "yy": "YY"
1178 -}
\ No newline at end of file
1178 +}
res/values/strings_nl.arb
+2 -2
@@ -515,7 +515,7 @@
515 "matrix_green_dark_theme": "Matrix groen donker thema",
516 "max_amount": "Max: ${value}",
517 "max_value": "Max: ${value} ${currency}",
518 - "memo": "Memo:",
518 + "memo": "Memo",
519 "message": "Bericht",
520 "message_verified": "Het bericht is succesvol geverifieerd",
521 "messages": "Berichten",
@@ -1173,4 +1173,4 @@
1173 "youCanGoBackToYourDapp": "U kunt nu terug naar uw DApp gaan",
1174 "your": "Jouw",
1175 "yy": "JJ"
1176 -}
\ No newline at end of file
1176 +}
res/values/strings_tl.arb
+2 -2
@@ -515,7 +515,7 @@
515 "matrix_green_dark_theme": "Matrix Green Dark Theme",
516 "max_amount": "Max: ${value}",
517 "max_value": "Max: ${value} ${currency}",
518 - "memo": "Memo:",
518 + "memo": "Memo",
519 "message": "Mensahe",
520 "message_verified": "Ang mensahe ay matagumpay na na -verify",
521 "messages": "Mga mensahe",
@@ -1172,4 +1172,4 @@
1172 "youCanGoBackToYourDapp": "Maaari kang bumalik sa iyong dapp ngayon",
1173 "your": "IYONG",
1174 "yy": "YY"
1175 -}
\ No newline at end of file
1175 +}
res/values/strings_tr.arb
+2 -2
@@ -515,7 +515,7 @@
515 "matrix_green_dark_theme": "Matrix Yeşil Koyu Tema",
516 "max_amount": "Maks: ${value}",
517 "max_value": "En fazla: ${value} ${currency}",
518 - "memo": "Memo:",
518 + "memo": "Memo",
519 "message": "İleti",
520 "message_verified": "Mesaj başarıyla doğrulandı",
521 "messages": "Mesaj",
@@ -1172,4 +1172,4 @@
1172 "youCanGoBackToYourDapp": "Şimdi Dapp'ınıza geri dönebilirsin",
1173 "your": "Senin",
1174 "yy": "YY"
1175 -}
\ No newline at end of file
1175 +}
scripts/android/app_env.sh
+2 -2
@@ -21,8 +21,8 @@ MONERO_COM_PACKAGE="com.monero.app"
21 MONERO_COM_SCHEME="monero.com"
22
23 CAKEWALLET_NAME="Cake Wallet"
24 -CAKEWALLET_VERSION="5.6.5"
25 -CAKEWALLET_BUILD_NUMBER=4296
24 +CAKEWALLET_VERSION="5.7.0"
25 +CAKEWALLET_BUILD_NUMBER=4298
26 CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet"
27 CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet"
28 CAKEWALLET_SCHEME="cakewallet"
scripts/android/build_zcash.sh new
+9
@@ -0,0 +1,9 @@
1 +#!/bin/bash
2 +set -x -e
3 +cd "$(dirname "$0")"
4 +
5 +../prepare_zcash.sh
6 +
7 +pushd ../zcash_lib
8 + ./build-scripts/build-android.sh
9 +popd
scripts/android/pubspec_gen.sh
+1 -1
@@ -10,7 +10,7 @@ case $APP_ANDROID_TYPE in
10 CONFIG_ARGS="--monero"
11 ;;
12 $CAKEWALLET)
13 - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred --dogecoin --base --arbitrum"
13 + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred --dogecoin --base --zcash --arbitrum"
14 ;;
15 esac
16
scripts/build_bitbox_flutter.sh
+10 -2
@@ -1,9 +1,17 @@
1 #!/bin/bash
2 +set -x -e
3
3 -git clone https://github.com/konstantinullrich/bitbox_flutter
4 -cd bitbox_flutter
4 +if [[ ! -d "bitbox_flutter" ]];
5 +then
6 + git clone https://github.com/konstantinullrich/bitbox_flutter
7 +fi
8
9 +cd bitbox_flutter
10 +git fetch -a
11 +git reset --hard
12 git checkout 5a6e6dd388ef64003f86094af80d5453518b601d
13 +git reset --hard
14 +
15 bash ./build_bindings.sh --dont-install
16
17 FILE=api.aar
scripts/ios/app_config.sh
+1 -1
@@ -31,7 +31,7 @@ case $APP_IOS_TYPE in
31 ;;
32
33 $CAKEWALLET)
34 - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred --dogecoin --base --arbitrum"
34 + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred --dogecoin --base --zcash --arbitrum"
35 ;;
36 esac
37
scripts/ios/app_env.sh
+2 -2
@@ -17,8 +17,8 @@ MONERO_COM_BUILD_NUMBER=153
17 MONERO_COM_BUNDLE_ID="com.cakewallet.monero"
18
19 CAKEWALLET_NAME="Cake Wallet"
20 -CAKEWALLET_VERSION="5.6.5"
21 -CAKEWALLET_BUILD_NUMBER=360
20 +CAKEWALLET_VERSION="5.7.0"
21 +CAKEWALLET_BUILD_NUMBER=363
22 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"
23
24
scripts/ios/build_zcash.sh new
+9
@@ -0,0 +1,9 @@
1 +#!/bin/bash
2 +set -x -e
3 +cd "$(dirname "$0")"
4 +
5 +../prepare_zcash.sh
6 +
7 +pushd ../zcash_lib
8 + ./build-scripts/mac/build-mac.sh
9 +popd
scripts/lint.sh new
+18
@@ -0,0 +1,18 @@
1 +#!/bin/bash
2 +set -x -e
3 +cd $(dirname $0)
4 +cd ..
5 +
6 +for i in cw_zcash;
7 +do
8 + dart fix --apply $i/
9 + dart format --line-length=100 $i/
10 + if [[ ! "x$ABORT_ON_CHANGE" == "x" ]];
11 + then
12 + if [[ ! -z "$(git status --porcelain -- $(find $i))" ]];
13 + then
14 + echo "Please run scripts/lint.sh ($i has changes)"
15 + exit 1
16 + fi
17 + fi
18 +done
scripts/linux/build_zcash.sh new
+9
@@ -0,0 +1,9 @@
1 +#!/bin/bash
2 +set -x -e
3 +cd "$(dirname "$0")"
4 +
5 +../prepare_zcash.sh
6 +
7 +pushd ../zcash_lib
8 + ./build-scripts/build-linux.sh
9 +popd
scripts/macos/app_config.sh
+1 -1
@@ -36,7 +36,7 @@ case $APP_MACOS_TYPE in
36 $MONERO_COM)
37 CONFIG_ARGS="--monero";;
38 $CAKEWALLET)
39 - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --dogecoin --base --arbitrum";;
39 + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --dogecoin --base --zcash --arbitrum";;
40 esac
41
42 cp -rf pubspec_description.yaml pubspec.yaml
scripts/macos/app_env.sh
+4 -4
@@ -16,13 +16,13 @@ if [ -n "$1" ]; then
16 fi
17
18 MONERO_COM_NAME="Monero.com"
19 -MONERO_COM_VERSION="5.6.5"
20 -MONERO_COM_BUILD_NUMBER=73
19 +MONERO_COM_VERSION="5.6.6"
20 +MONERO_COM_BUILD_NUMBER=74
21 MONERO_COM_BUNDLE_ID="com.cakewallet.monero"
22
23 CAKEWALLET_NAME="Cake Wallet"
24 -CAKEWALLET_VERSION="5.6.5"
25 -CAKEWALLET_BUILD_NUMBER=140
24 +CAKEWALLET_VERSION="5.7.0"
25 +CAKEWALLET_BUILD_NUMBER=142
26 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"
27
28 if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then
scripts/macos/build_zcash.sh new
+9
@@ -0,0 +1,9 @@
1 +#!/bin/bash
2 +set -x -e
3 +cd "$(dirname "$0")"
4 +
5 +../prepare_zcash.sh
6 +
7 +pushd ../zcash_lib
8 + ./build-scripts/mac/build-mac.sh
9 +popd
scripts/prepare_torch.sh
+1 -1
@@ -7,7 +7,7 @@ cd "$(dirname "$0")"
7 # - .github/workflows/pr_test_build_linux.yml
8 # https://github.com/MrCyjaneK/torch_dart/releases/download/v1.0.17/torch_dart-v1.0.17.tar.gz
9
10 -HASH=85130c85ad80f4596896f5fa4dc1a160cf0f0487
10 +HASH=d52b9ebcc2e3677520e51bd8a54d771c353587b8
11
12 if [[ ! -d "torch_dart/.git" ]];
13 then
scripts/prepare_zcash.sh new
+28
@@ -0,0 +1,28 @@
1 +#!/bin/bash
2 +set -x -e
3 +cd "$(dirname "$0")"
4 +
5 +HASH=647231e2e6d93c1d6c16d4019cd3a11a3aaa9504
6 +
7 +if [[ ! -d "zcash_lib/.git" ]];
8 +then
9 + rm -rf zcash_lib
10 + git clone https://github.com/MrCyjaneK/zwallet.git zcash_lib
11 + cd zcash_lib
12 +else
13 + cd zcash_lib
14 + git fetch -a
15 +fi
16 +
17 +
18 +git reset --hard
19 +git checkout $HASH
20 +git reset --hard
21 +git submodule update --init --force --recursive
22 +
23 +# in go I could nicely replace => the heck out of it
24 +# sadly rust is not go
25 +find . -name Cargo.toml -exec sed -i.bak -E '
26 +s|rusqlite[[:space:]]*=[[:space:]]*\{[^}]*\}|rusqlite = { version = "0.29.0", features = ["bundled", "modern_sqlite", "backup"] }|g
27 +' {} +
28 +# find . -name Cargo.toml -exec sed -i.bak -E 's|r2d2_sqlite[[:space:]]*=[[:space:]]*([^\n]*)|r2d2_sqlite = "0.32.0"|g' {} +
tool/configure.dart
+156 -33
@@ -11,6 +11,7 @@ const zanoOutputPath = 'lib/zano/zano.dart';
11 const decredOutputPath = 'lib/decred/decred.dart';
12 const dogecoinOutputPath = 'lib/dogecoin/dogecoin.dart';
13 const evmOutputPath = 'lib/evm/evm.dart';
14 +const zcashOutputPath = 'lib/zcash/zcash.dart';
15 const walletTypesPath = 'lib/wallet_types.g.dart';
16 const secureStoragePath = 'lib/core/secure_storage.dart';
17 const pubspecDefaultPath = 'pubspec_default.yaml';
@@ -33,6 +34,7 @@ Future<void> main(List<String> args) async {
34 final hasDogecoin = args.contains('${prefix}dogecoin');
35 final hasBase = args.contains('${prefix}base');
36 final hasArbitrum = args.contains('${prefix}arbitrum');
37 + final hasZcash = args.contains('${prefix}zcash');
38 final hasEVM = hasEthereum || hasPolygon || hasBase || hasArbitrum;
39 final excludeFlutterSecureStorage = args.contains('${prefix}excludeFlutterSecureStorage');
40
@@ -48,6 +50,7 @@ Future<void> main(List<String> args) async {
50 await generateDecred(hasDecred);
51 await generateDogecoin(hasDogecoin);
52 await generateEVM(hasEVM);
53 + await generateZcash(hasZcash);
54
55 await generatePubspec(
56 hasMonero: hasMonero,
@@ -66,6 +69,7 @@ Future<void> main(List<String> args) async {
69 hasDogecoin: hasDogecoin,
70 hasBase: hasBase,
71 hasArbitrum: hasArbitrum,
72 + hasZcash: hasZcash,
73 );
74 await generateWalletTypes(
75 hasMonero: hasMonero,
@@ -83,6 +87,7 @@ Future<void> main(List<String> args) async {
87 hasDogecoin: hasDogecoin,
88 hasBase: hasBase,
89 hasArbitrum: hasArbitrum,
90 + hasZcash: hasZcash,
91 );
92 await injectSecureStorage(!excludeFlutterSecureStorage);
93 }
@@ -149,7 +154,7 @@ import 'package:mobx/mobx.dart';
154 """;
155 const bitcoinCwPart = "part 'cw_bitcoin.dart';";
156 const bitcoinContent = """
152 -
157 +
158 class ElectrumSubAddress {
159 ElectrumSubAddress({
160 required this.id,
@@ -396,7 +401,7 @@ abstract class MoneroWalletDetails {
401
402 abstract class Monero {
403 MoneroAccountList getAccountList(Object wallet);
399 -
404 +
405 MoneroSubaddressList getSubaddressList(Object wallet);
406
407 TransactionHistoryBase getTransactionHistory(Object wallet);
@@ -414,7 +419,7 @@ abstract class Monero {
419 TransactionPriority deserializeMoneroTransactionPriority({required int raw});
420 List<TransactionPriority> getTransactionPriorities();
421 List<String> getMoneroWordList(String language);
417 -
422 +
423 List<Unspent> getUnspents(Object wallet);
424 Future<void> updateUnspents(Object wallet);
425
@@ -603,7 +608,7 @@ abstract class WowneroWalletDetails {
608
609 abstract class Wownero {
610 WowneroAccountList getAccountList(Object wallet);
606 -
611 +
612 WowneroSubaddressList getSubaddressList(Object wallet);
613
614 TransactionHistoryBase getTransactionHistory(Object wallet);
@@ -621,7 +626,7 @@ abstract class Wownero {
626 TransactionPriority deserializeWowneroTransactionPriority({required int raw});
627 List<TransactionPriority> getTransactionPriorities();
628 List<String> getWowneroWordList(String language);
624 -
629 +
630 List<Unspent> getUnspents(Object wallet);
631 Future<void> updateUnspents(Object wallet);
632
@@ -729,18 +734,21 @@ abstract class BitcoinCash {
734 TransactionPriority getDefaultTransactionPriority();
735
736 List<TransactionPriority> getTransactionPriorities();
732 -
737 +
738 TransactionPriority getBitcoinCashTransactionPrioritySlow();
739 }
740 """;
741
742 const bitcoinCashEmptyDefinition = 'BitcoinCash? bitcoinCash;\n';
738 - const bitcoinCashCWDefinition = 'BitcoinCash? bitcoinCash = CWBitcoinCash();\n';
743 + const bitcoinCashCWDefinition =
744 + 'BitcoinCash? bitcoinCash = CWBitcoinCash();\n';
745
746 final output = '$bitcoinCashCommonHeaders\n' +
747 (hasImplementation ? '$bitcoinCashCWHeaders\n' : '\n') +
748 (hasImplementation ? '$bitcoinCashCwPart\n\n' : '\n') +
743 - (hasImplementation ? bitcoinCashCWDefinition : bitcoinCashEmptyDefinition) +
749 + (hasImplementation
750 + ? bitcoinCashCWDefinition
751 + : bitcoinCashEmptyDefinition) +
752 '\n' +
753 bitcoinCashContent;
754
@@ -806,7 +814,7 @@ abstract class Nano {
814 WalletInfo? walletInfo,
815 String? passphrase,
816 });
809 -
817 +
818 WalletCredentials createNanoRestoreWalletFromSeedCredentials({
819 required String name,
820 required String password,
@@ -875,7 +883,8 @@ abstract class NanoUtil {
883 """;
884
885 const nanoEmptyDefinition = 'Nano? nano;\nNanoUtil? nanoUtil;\n';
878 - const nanoCWDefinition = 'Nano? nano = CWNano();\nNanoUtil? nanoUtil = CWNanoUtil();\n';
886 + const nanoCWDefinition =
887 + 'Nano? nano = CWNano();\nNanoUtil? nanoUtil = CWNanoUtil();\n';
888
889 final output = '$nanoCommonHeaders\n' +
890 (hasImplementation ? '$nanoCWHeaders\n' : '\n') +
@@ -1023,7 +1032,7 @@ abstract class Tron {
1032 Future<void> addTronToken(WalletBase wallet, CryptoCurrency token, String contractAddress);
1033 Future<void> deleteTronToken(WalletBase wallet, CryptoCurrency token);
1034 Future<CryptoCurrency?> getTronToken(WalletBase wallet, String contractAddress);
1026 -
1035 +
1036 double getTransactionAmountRaw(TransactionInfo transactionInfo);
1037 CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction);
1038 String getTokenAddress(CryptoCurrency asset);
@@ -1031,7 +1040,7 @@ abstract class Tron {
1040
1041 String? getTronNativeEstimatedFee(WalletBase wallet);
1042 String? getTronTRC20EstimatedFee(WalletBase wallet);
1034 -
1043 +
1044 void updateTronGridUsageState(WalletBase wallet, bool isEnabled);
1045 List<String> getDefaultTokenContractAddresses();
1046 bool isTokenAlreadyAdded(WalletBase wallet, String contractAddress);
@@ -1498,6 +1507,104 @@ class ChainInfo {
1507 await outputFile.writeAsString(output);
1508 }
1509
1510 +Future<void> generateZcash(bool hasImplementation) async {
1511 + final outputFile = File(zcashOutputPath);
1512 + const zcashCommonHeaders = """
1513 +import 'package:cake_wallet/view_model/send/output.dart';
1514 +import 'package:cw_core/balance.dart';
1515 +import 'package:cw_core/crypto_amount_format.dart';
1516 +import 'package:cw_core/crypto_currency.dart';
1517 +import 'package:cw_core/output_info.dart';
1518 +import 'package:cw_core/transaction_history.dart';
1519 +import 'package:cw_core/transaction_info.dart';
1520 +import 'package:cw_core/transaction_priority.dart';
1521 +import 'package:cw_core/monero_transaction_priority.dart';
1522 +import 'package:cw_core/wallet_base.dart';
1523 +import 'package:cw_core/wallet_credentials.dart';
1524 +import 'package:cw_core/wallet_info.dart';
1525 +import 'package:cw_core/wallet_service.dart';
1526 +import 'package:cw_core/receive_page_option.dart';
1527 +
1528 +""";
1529 + const zcashCWHeaders = """
1530 +import 'package:cw_zcash/cw_zcash.dart';
1531 +import 'package:cw_zcash/src/zcash_wallet_addresses.dart';
1532 +
1533 +""";
1534 + const zcashCwPart = "part 'cw_zcash.dart';";
1535 + const zcashContent = """
1536 +abstract class Zcash {
1537 + List<String> getZcashWordList(String language);
1538 + WalletService createZcashWalletService(bool isDirect);
1539 + WalletCredentials createZcashNewWalletCredentials(
1540 + {required String name,
1541 + WalletInfo? walletInfo,
1542 + String? password,
1543 + String? mnemonic,
1544 + required String? passphrase});
1545 + WalletCredentials createZcashRestoreWalletFromSeedCredentials(
1546 + {required String name,
1547 + required String mnemonic,
1548 + required String password,
1549 + String? passphrase,
1550 + required int? height});
1551 + WalletCredentials createZcashRestoreWalletFromPrivateKey(
1552 + {required String name, required String privateKey, required String password});
1553 + String getAddress(WalletBase wallet);
1554 + String getPrivateKey(WalletBase wallet);
1555 + String getPublicKey(WalletBase wallet);
1556 + Map<String, String> getKeys(Object wallet);
1557 +
1558 + Object createZcashTransactionCredentials(
1559 + List<Output> outputs, {
1560 + required CryptoCurrency currency,
1561 + int? feeRate,
1562 + });
1563 +
1564 + Object createZcashTransactionCredentialsRaw(
1565 + List<OutputInfo> outputs, {
1566 + required CryptoCurrency currency,
1567 + required int feeRate,
1568 + });
1569 +
1570 + int formatterZcashParseAmount(String amount);
1571 + double formatterZcashAmountToDouble({TransactionInfo? transaction, BigInt? amount});
1572 + String formatterZcashAmountToString({required int amount});
1573 +
1574 + List<WalletInfoAddressInfo> getAddressInfos(Object wallet);
1575 +
1576 + TransactionPriority getDefaultTransactionPriority();
1577 + TransactionPriority getZcashTransactionPriorityAutomatic();
1578 + TransactionPriority deserializeZcashTransactionPriority({required int raw});
1579 + List<TransactionPriority> getTransactionPriorities();
1580 + List<ReceivePageOption> getZcashReceivePageOptions(Object wallet);
1581 + ReceivePageOption getSelectedAddressType(Object wallet);
1582 + ZcashAddressType getZcashAddressType(ReceivePageOption option);
1583 + Future<void> setAddressType(Object wallet, dynamic option);
1584 + ZcashAddressType getOptionToType(ReceivePageOption option);
1585 + void unlockDatabase(String password);
1586 + Future<int> getHeightByDate(DateTime date);
1587 +}
1588 +
1589 + """;
1590 +
1591 + const zcashEmptyDefinition = 'Zcash? zcash;\n';
1592 + const zcashCWDefinition = 'Zcash? zcash = CWZcash();\n';
1593 +
1594 + final output = '$zcashCommonHeaders\n' +
1595 + (hasImplementation ? '$zcashCWHeaders\n' : '\n') +
1596 + (hasImplementation ? '$zcashCwPart\n\n' : '\n') +
1597 + (hasImplementation ? zcashCWDefinition : zcashEmptyDefinition) +
1598 + '\n' +
1599 + zcashContent;
1600 +
1601 + if (outputFile.existsSync()) {
1602 + await outputFile.delete();
1603 + }
1604 +
1605 + await outputFile.writeAsString(output);
1606 +}
1607 +
1608 Future<void> generatePubspec({
1609 required bool hasMonero,
1610 required bool hasBitcoin,
@@ -1515,6 +1622,7 @@ Future<void> generatePubspec({
1622 required bool hasDogecoin,
1623 required bool hasBase,
1624 required bool hasArbitrum,
1625 + required bool hasZcash,
1626 }) async {
1627 const cwCore = """
1628 cw_core:
@@ -1575,12 +1683,18 @@ Future<void> generatePubspec({
1683 cw_dogecoin:
1684 path: ./cw_dogecoin
1685 """;
1686 + const cwZcash = """
1687 + cw_zcash:
1688 + path: ./cw_zcash
1689 + """;
1690 +
1691 final inputFile = File(pubspecOutputPath);
1692 final inputText = await inputFile.readAsString();
1693 final inputLines = inputText.split('\n');
1694 final dependenciesIndex = inputLines.indexWhere((line) => Platform.isWindows
1695 // On Windows it could contains `\r` (Carriage Return). It could be fixed in newer dart versions.
1583 - ? line.toLowerCase() == 'dependencies:\r' || line.toLowerCase() == 'dependencies:'
1696 + ? line.toLowerCase() == 'dependencies:\r' ||
1697 + line.toLowerCase() == 'dependencies:'
1698 : line.toLowerCase() == 'dependencies:');
1699 var output = cwCore;
1700
@@ -1636,6 +1750,10 @@ Future<void> generatePubspec({
1750 output += '\n$cwDogecoin';
1751 }
1752
1753 + if (hasZcash) {
1754 + output += '\n$cwZcash';
1755 + }
1756 +
1757 final outputLines = output.split('\n');
1758 inputLines.insertAll(dependenciesIndex + 1, outputLines);
1759 final outputContent = inputLines.join('\n');
@@ -1664,6 +1782,7 @@ Future<void> generateWalletTypes({
1782 required bool hasDogecoin,
1783 required bool hasBase,
1784 required bool hasArbitrum,
1785 + required bool hasZcash,
1786 }) async {
1787 final walletTypesFile = File(walletTypesPath);
1788
@@ -1687,42 +1806,50 @@ Future<void> generateWalletTypes({
1806 outputContent += '\tWalletType.ethereum,\n';
1807 }
1808
1690 - if (hasBitcoin) {
1691 - outputContent += '\tWalletType.litecoin,\n';
1809 + if (hasSolana) {
1810 + outputContent += '\tWalletType.solana,\n';
1811 + }
1812 +
1813 + if (hasTron) {
1814 + outputContent += '\tWalletType.tron,\n';
1815 }
1816
1817 if (hasDogecoin) {
1818 outputContent += '\tWalletType.dogecoin,\n';
1819 }
1820
1698 - if (hasBase) {
1699 - outputContent += '\tWalletType.base,\n';
1821 + if (hasBitcoinCash) {
1822 + outputContent += '\tWalletType.bitcoinCash,\n';
1823 }
1824
1702 - if (hasArbitrum) {
1703 - outputContent += '\tWalletType.arbitrum,\n';
1825 + if (hasZcash) {
1826 + outputContent += '\tWalletType.zcash,\n';
1827 }
1828
1706 - if (hasBitcoinCash) {
1707 - outputContent += '\tWalletType.bitcoinCash,\n';
1829 + if (hasBitcoin) {
1830 + outputContent += '\tWalletType.litecoin,\n';
1831 }
1832
1710 - if (hasPolygon) {
1711 - outputContent += '\tWalletType.polygon,\n';
1833 + if (hasBase) {
1834 + outputContent += '\tWalletType.base,\n';
1835 }
1836
1714 - if (hasSolana) {
1715 - outputContent += '\tWalletType.solana,\n';
1837 + if (hasArbitrum) {
1838 + outputContent += '\tWalletType.arbitrum,\n';
1839 }
1840
1718 - if (hasTron) {
1719 - outputContent += '\tWalletType.tron,\n';
1841 + if (hasPolygon) {
1842 + outputContent += '\tWalletType.polygon,\n';
1843 }
1844
1845 if (hasNano) {
1846 outputContent += '\tWalletType.nano,\n';
1847 }
1848
1849 + if (hasDecred) {
1850 + outputContent += '\tWalletType.decred,\n';
1851 + }
1852 +
1853 if (hasZano) {
1854 outputContent += '\tWalletType.zano,\n';
1855 }
@@ -1731,10 +1858,6 @@ Future<void> generateWalletTypes({
1858 outputContent += '\tWalletType.banano,\n';
1859 }
1860
1734 - if (hasDecred) {
1735 - outputContent += '\tWalletType.decred,\n';
1736 - }
1737 -
1861 // if (hasWownero) {
1862 // outputContent += '\tWalletType.wownero,\n';
1863 // }
@@ -1769,7 +1892,7 @@ class DefaultSecureStorage extends SecureStorage {
1892 iOptions: IOSOptions(accessibility: KeychainAccessibility.first_unlock),
1893 aOptions: AndroidOptions(encryptedSharedPreferences: true),
1894 ));
1772 -
1895 +
1896 final FlutterSecureStorage _secureStorage;
1897
1898 @override
@@ -1787,7 +1910,7 @@ class DefaultSecureStorage extends SecureStorage {
1910
1911 @override
1912 Future<void> delete({required String key}) async => _secureStorage.delete(key: key);
1790 -
1913 +
1914 @override
1915 Future<void> deleteAll() async => _secureStorage.deleteAll();
1916
tool/fiat_api_check.dart