What changed, and why it matters
This is a large feature commit that adds Zcash wallet support to Cake Wallet. It introduces a new cw_zcash package, integrates the Warp FFI library, adds transparent address rotation, auto-shielding, and exchange/CakePay support. The commit itself is a feature addition, not a disclosed security fix. There are some code-quality and potential operational-security concerns (hardcoded fee values, debug code left in release paths, unhandled exceptions swallowed, and a custom CRC32-based seed derivation for disposable addresses), but no direct vulnerability is demonstrated in the diff.
Treat this as a feature commit requiring normal security review rather than an emergency patch. Review the Zcash FFI integration, the custom T-address rotation seed-derivation scheme, hardcoded fee logic, and debug-mode data exposure before release. Ensure the warp_api native library and its build scripts are audited. Consider adding tests for address rotation, auto-shielding, and edge cases in balance/fee calculation.
Security signals we found
New wallet type added with FFI/native library integration (warp_api)
Hardcoded fee constants (10000 zatoshi) in transaction preparation and auto-shielding
Debug-mode serialization exposes wallet account/transaction data in human-readable JSON
Custom seed-offset scheme for transparent address rotation uses CRC32 and non-standard passphrase manipulation
Broad try/catch blocks swallow errors (sync, balance, transaction updates)
TODO marker for unimplemented encrypted database copy on first creation
No explicit security disclosure, CVE, or advisory referenced in commit or materials
Evidence from the diff
The commit adds a full Zcash wallet implementation using warp_api FFI. Key components: ZcashWallet/Service/Addresses/Balance/TransactionInfo, T-address rotation via seed offsetting with CRC32, auto-shielding of transparent+sapling funds, exchange provider integration, and build/CI wiring. Notable signals: hardcoded 10000 zatoshi fees, kDebugMode branches that serialize human-readable wallet state, broad exception catching that may hide failures, and a TODO-commented database encryption path. The custom T-address rotation scheme derives sub-wallets by appending :tgen:${CRC32(seed)} to the seed phrase, which is non-standard and not independently reviewed. No CVE, advisory, or vendor security disclosure is present in the supplied materials.
Changed components
cw_zcash (new package)cw_core (wallet type, currency, node, path, wallet_info, sqlite helpers)lib/exchange/providerslib/cake_paylib/view_model/send, dashboard, wallet_address_listAndroid/iOS/Linux build scripts and manifestsCI workflowsInspect captured patch +5664 / −284
diff --git a/.github/workflows/pr_test_build_android.yml b/.github/workflows/pr_test_build_android.yml
index 54b53cb..3743ed1 100644
--- a/.github/workflows/pr_test_build_android.yml
+++ b/.github/workflows/pr_test_build_android.yml
@@ -4,7 +4,6 @@ on:
pull_request:
jobs:
-
# -----------------------------------------
# PATH A: Internal PRs
internal-build:
diff --git a/.github/workflows/pr_test_build_linux.yml b/.github/workflows/pr_test_build_linux.yml
index c3e7ecd..67438ed 100644
--- a/.github/workflows/pr_test_build_linux.yml
+++ b/.github/workflows/pr_test_build_linux.yml
@@ -25,7 +25,7 @@ jobs:
steps:
- name: Fix github actions messing up $HOME...
- run: 'echo HOME=/root | sudo tee -a $GITHUB_ENV'
+ run: "echo HOME=/root | sudo tee -a $GITHUB_ENV"
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
@@ -94,6 +94,13 @@ jobs:
rm ./reown_flutter.tar.gz
popd
+ - name: Build Zcash Lib
+ run: |
+ set -x -e
+ pushd scripts/linux
+ ./build_zcash.sh
+ popd
+
- name: Execute Build and Setup Commands
run: |
pushd scripts/linux
@@ -108,7 +115,7 @@ jobs:
wget https://github.com/MrCyjaneK/monero_c/releases/download/v0.18.4.0-RC9/release-bundle.zip
unzip release-bundle.zip
rm release-bundle.zip
- unxz -fv release/*/*.xz
+ unxz -fv release/*/*.xz
popd
- name: Build Bitbox Flutter
@@ -138,7 +145,11 @@ jobs:
- name: Generate localization
run: |
dart run tool/generate_localization.dart
-
+ - name: Workaround zcash
+ run: |
+ mkdir -p scripts/zcash_lib/assets/
+ touch scripts/zcash_lib/assets/sapling-spend.params
+ touch scripts/zcash_lib/assets/sapling-output.params
- name: Build linux
run: |
flutter build linux --dart-define=hasDevOptions=true --release
@@ -154,15 +165,18 @@ jobs:
with:
path: ${{ github.workspace }}/build/linux/x64/release/cakewallet_linux.zip
name: cakewallet_linux
+ - name: Verify lint
+ run: |
+ exec env ABORT_ON_CHANGE=true ./scripts/lint.sh
- name: Prepare virtual desktop
if: ${{ contains(env.message, 'run tests') }}
run: |
- nohup Xvfb :99 -screen 0 720x1280x16 &
- echo DISPLAY=:99 | sudo tee -a $GITHUB_ENV
- dbus-daemon --system --fork
- nohup NetworkManager &
- nohup ffmpeg -framerate 60 -video_size 720x1280 -f x11grab -i :99 -c:v libx264 -c:a aac /opt/screen_grab.mkv &
+ nohup Xvfb :99 -screen 0 720x1280x16 &
+ echo DISPLAY=:99 | sudo tee -a $GITHUB_ENV
+ dbus-daemon --system --fork
+ nohup NetworkManager &
+ nohup ffmpeg -framerate 60 -video_size 720x1280 -f x11grab -i :99 -c:v libx264 -c:a aac /opt/screen_grab.mkv &
# Note for people adding tests:
# - Tests are ran on Linux, with some things being mocked out.
diff --git a/.github/workflows/reusable-build.yml b/.github/workflows/reusable-build.yml
index 08bf24d..7c21897 100644
--- a/.github/workflows/reusable-build.yml
+++ b/.github/workflows/reusable-build.yml
@@ -40,7 +40,7 @@ jobs:
steps:
- name: Fix github actions messing up $HOME...
- run: 'echo HOME=/root | sudo tee -a $GITHUB_ENV'
+ run: "echo HOME=/root | sudo tee -a $GITHUB_ENV"
- uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
@@ -112,7 +112,7 @@ jobs:
# cleaning
rm -rf reown_flutter
rm -f reown_flutter.tar.gz
-
+
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
mkdir reown_flutter
pushd reown_flutter
@@ -135,7 +135,7 @@ jobs:
wget https://github.com/MrCyjaneK/monero_c/releases/download/v0.18.4.0-RC9/release-bundle.zip
unzip release-bundle.zip
rm release-bundle.zip
- unxz -fv release/*/*.xz
+ unxz -fv release/*/*.xz
popd
- name: Build Bitbox Flutter
@@ -144,6 +144,12 @@ jobs:
pushd scripts
./build_bitbox_flutter.sh
popd
+ - name: Build Zcash Lib
+ run: |
+ set -x -e
+ pushd scripts/android
+ ./build_zcash.sh
+ popd
- name: Install Flutter dependencies
run: |
@@ -236,4 +242,4 @@ jobs:
cd build/app/outputs/flutter-apk
for i in arm64-v8a x86_64; do
../../../../scripts/android/check_16kb_align.sh app-$i-release.apk
- done
\ No newline at end of file
+ done
diff --git a/.gitignore b/.gitignore
index 5b17c01..e9d8156 100644
--- a/.gitignore
+++ b/.gitignore
@@ -144,6 +144,7 @@ lib/dogecoin/dogecoin.dart
lib/base/base.dart
lib/arbitrum/arbitrum.dart
lib/evm/evm.dart
+lib/zcash/zcash.dart
ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
ios/Runner/Assets.xcassets/AppIcon.appiconset/*.png
@@ -151,7 +152,7 @@ ios/Runner/Assets.xcassets/AppIcon.appiconset/*.png
ios/Runner/Info.plist
android/app/src/main/res/mipmap-*
android/app/src/main/res/drawable/ic_launcher.png
-!android/app/src/main/res/drawable/launch_background.xml
+!android/app/src/main/res/drawable/launch_background.xml
!android/app/src/main/res/values/styles.xml
assets/images/app_logo.png
@@ -215,4 +216,8 @@ flatpak-build/
**/linux/flutter/generated_plugins.cmake
scripts/torch_dart/
-fiat-check-output.txt
\ No newline at end of file
+fiat-check-output.txt
+scripts/zcash_lib
+scripts/android/yttrium
+scripts/bitbox_flutter
+scripts/reown_flutter
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index cdd040e..0000000
--- a/.gitmodules
+++ /dev/null
@@ -1,6 +0,0 @@
-[submodule "inject.dart"]
- path = inject.dart
- url = https://github.com/google/inject.dart
-[submodule ".vendor/inject.dart"]
- path = .vendor/inject.dart
- url = https://github.com/google/inject.dart
diff --git a/android/app/src/main/AndroidManifestBase.xml b/android/app/src/main/AndroidManifestBase.xml
index 6b3b2cb..d2ffeef 100644
--- a/android/app/src/main/AndroidManifestBase.xml
+++ b/android/app/src/main/AndroidManifestBase.xml
@@ -39,7 +39,7 @@
android:versionName="__versionName__"
android:requestLegacyExternalStorage="true"
android:extractNativeLibs="true">
- <service
+ <service
android:name="androidx.work.impl.foreground.SystemForegroundService"
android:foregroundServiceType="dataSync"
android:exported="false" />
@@ -112,6 +112,8 @@
<data android:scheme="arbitrum" />
<data android:scheme="arbitrum-wallet" />
<data android:scheme="arbitrum_wallet" />
+ <data android:scheme="zcash" />
+ <data android:scheme="zcash-wallet" />
</intent-filter>
<!-- nano-gpt link scheme -->
<intent-filter android:autoVerify="true">
@@ -139,7 +141,7 @@
android:resource="@xml/provider_paths" />
</provider>
</application>
-
+
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
diff --git a/assets/images/crypto/zcash.webp b/assets/images/crypto/zcash.webp
new file mode 100644
index 0000000..08f2ec3
Binary files /dev/null and b/assets/images/crypto/zcash.webp differ
diff --git a/assets/images/tx_shield.svg b/assets/images/tx_shield.svg
new file mode 100644
index 0000000..bf0073e
--- /dev/null
+++ b/assets/images/tx_shield.svg
@@ -0,0 +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
diff --git a/assets/images/zec_icon.png b/assets/images/zec_icon.png
index 095ad2c..009e174 100644
Binary files a/assets/images/zec_icon.png and b/assets/images/zec_icon.png differ
diff --git a/assets/images/zec_icon_qr.svg b/assets/images/zec_icon_qr.svg
new file mode 100644
index 0000000..54ceab8
--- /dev/null
+++ b/assets/images/zec_icon_qr.svg
@@ -0,0 +1,5 @@
+<svg width="45" height="45" viewBox="0 0 45 45" fill="none" xmlns="http://www.w3.org/2000/svg">
+<rect x="1" y="1" width="43" height="43" rx="6" fill="#E4A50B"/>
+<rect x="1" y="1" width="43" height="43" rx="6" stroke="black" stroke-width="2"/>
+<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"/>
+</svg>
diff --git a/assets/text/Release_Notes.txt b/assets/text/Release_Notes.txt
index 6772685..7858a57 100644
--- a/assets/text/Release_Notes.txt
+++ b/assets/text/Release_Notes.txt
@@ -1 +1,3 @@
+Add Zcash wallet
+Generic enhancements
Bug fixes
\ No newline at end of file
diff --git a/assets/zcash_node_list.yml b/assets/zcash_node_list.yml
new file mode 100644
index 0000000..88db5c5
--- /dev/null
+++ b/assets/zcash_node_list.yml
@@ -0,0 +1,12 @@
+- uri: zec.rocks:443
+ is_default: false
+ useSSL: true
+ isEnabledForAutoSwitching: true
+- uri: zec-node.cakewallet.com:443
+ is_default: true
+ useSSL: true
+ isEnabledForAutoSwitching: true
+- uri: 2c4whzg26j6hgjh22rxynj3oig4gm22ga7x74weclujywxye23v3u5id.onion:9067
+ useSSL: false
+- uri: bvp2l442g5ogma7rywzahm7eqyhpp3g26n3gvvxeioqn5csio2ir6myd.onion:9067
+ useSSL: false
diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock
index c4b45c7..90dbdf6 100644
--- a/cw_bitcoin/pubspec.lock
+++ b/cw_bitcoin/pubspec.lock
@@ -604,8 +604,8 @@ packages:
dependency: "direct main"
description:
path: "packages/ledger-bitcoin"
- ref: dc42621f55702d0732681afc2f90bc0047f26633
- resolved-ref: dc42621f55702d0732681afc2f90bc0047f26633
+ ref: "75bba8632c529debf30ee2b1858530009fa6440b"
+ resolved-ref: "75bba8632c529debf30ee2b1858530009fa6440b"
url: "https://github.com/cake-tech/ledger-flutter-plus-plugins"
source: git
version: "0.0.3"
diff --git a/cw_core/lib/crypto_currency.dart b/cw_core/lib/crypto_currency.dart
index b581e80..82451f2 100644
--- a/cw_core/lib/crypto_currency.dart
+++ b/cw_core/lib/crypto_currency.dart
@@ -62,7 +62,6 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
CryptoCurrency.hbar,
CryptoCurrency.sc,
CryptoCurrency.usdcsol,
- CryptoCurrency.zaddr,
CryptoCurrency.zec,
CryptoCurrency.zen,
CryptoCurrency.xvg,
@@ -140,6 +139,10 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
xusd,
];
+ // well technically not currencies but since we had them a long time ago and don't want to break
+ // the app for users who had contacts saved for them
+ static const zcashCurrencies = [ zec, zaddr, tzec ];
+
// title, tag (if applicable), fullName (if unique), raw, name, iconPath
static const xmr = CryptoCurrency(title: 'XMR', fullName: 'Monero', raw: 0, name: 'xmr', iconPath: 'assets/images/crypto/monero.webp', decimals: 12);
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
static const usdc = CryptoCurrency(title: 'USDC', tag: 'ETH', fullName: 'USD Coin', raw: 40, name: 'usdc', iconPath: 'assets/images/crypto/usdc.webp', decimals: 6);
static const usdcsol = CryptoCurrency(title: 'USDC', tag: 'SOL', fullName: 'USDC Coin', raw: 41, name: 'usdcsol', iconPath: 'assets/images/crypto/usdc.webp', decimals: 6);
static const zaddr = CryptoCurrency(title: 'ZZEC', tag: 'ZEC', fullName: 'Shielded Zcash', raw: 42, name: 'zaddr', iconPath: 'assets/images/zec_icon.png', decimals: 8);
- static const zec = CryptoCurrency(title: 'TZEC', tag: 'ZEC', fullName: 'Transparent Zcash', raw: 43, name: 'zec', iconPath: 'assets/images/zec_icon.png', decimals: 8);
+ static const tzec = CryptoCurrency(title: 'tZEC', tag: 'ZEC', fullName: 'Transparent Zcash', raw: 43, name: 'zec', iconPath: 'assets/images/zec_icon.png', decimals: 8);
static const zen = CryptoCurrency(title: 'ZEN', fullName: 'Horizen', raw: 44, name: 'zen', iconPath: 'assets/images/zen_icon.png', decimals: 8);
static const xvg = CryptoCurrency(title: 'XVG', fullName: 'Verge', raw: 45, name: 'xvg', iconPath: 'assets/images/xvg_icon.png', decimals: 8);
@@ -250,23 +253,24 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
static const cbbtc = CryptoCurrency(title: 'CBBTC', tag: 'ETH', fullName: 'Coinbase Wrapped BTC', raw: 103, name: 'cbbtc', iconPath: 'assets/images/cbbtc_icon.png', decimals: 8);
static const baseEth = CryptoCurrency(title: 'ETH', tag: 'BASE', fullName: 'Ethereum', raw: 104, name: 'baseth', iconPath: 'assets/images/crypto/base_icon.webp', decimals: 18);
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);
- static const arbEth = CryptoCurrency(title: 'ETH', tag: 'ARB', fullName: 'Ethereum (Arbitrum One)', raw: 106, name: 'arbeth', iconPath: 'assets/images/crypto/ethereum.webp', decimals: 18);
- static const usdcArb = CryptoCurrency(title: 'USDC', tag: 'ARB', fullName: 'USDC Coin', raw: 107, name: 'usdcarb', iconPath: 'assets/images/crypto/usdc.webp', decimals: 6);
+ static const arbEth = CryptoCurrency(title: 'ETH', tag: 'ARB', fullName: 'Ethereum (Arbitrum One)', raw: 106, name: 'arbeth', iconPath: 'assets/images/crypto/ethereum.webp', decimals: 18);
+ static const zec = CryptoCurrency(title: 'ZEC', fullName: 'Zcash', raw: 107, name: 'zec', iconPath: 'assets/images/zec_icon.png', decimals: 8);
+ static const usdcArb = CryptoCurrency(title: 'USDC', tag: 'ARB', fullName: 'USDC Coin', raw: 108, name: 'usdcarb', iconPath: 'assets/images/crypto/usdc.webp', decimals: 6);
static final Map<int, CryptoCurrency> _rawCurrencyMap =
- [...all, ...havenCurrencies].fold<Map<int, CryptoCurrency>>(<int, CryptoCurrency>{}, (acc, item) {
+ [...all, ...havenCurrencies, ...zcashCurrencies].fold<Map<int, CryptoCurrency>>(<int, CryptoCurrency>{}, (acc, item) {
acc.addAll({item.raw: item});
return acc;
});
static final Map<String, CryptoCurrency> _nameCurrencyMap =
- [...all, ...havenCurrencies].fold<Map<String, CryptoCurrency>>(<String, CryptoCurrency>{}, (acc, item) {
+ [...all, ...havenCurrencies, ...zcashCurrencies].fold<Map<String, CryptoCurrency>>(<String, CryptoCurrency>{}, (acc, item) {
acc.addAll({item.name: item});
return acc;
});
static final Map<String, CryptoCurrency> _fullNameCurrencyMap =
- [...all, ...havenCurrencies].fold<Map<String, CryptoCurrency>>(<String, CryptoCurrency>{}, (acc, item) {
+ [...all, ...havenCurrencies, ...zcashCurrencies].fold<Map<String, CryptoCurrency>>(<String, CryptoCurrency>{}, (acc, item) {
if(item.fullName != null){
acc.addAll({item.fullName!.toLowerCase(): item});
}
diff --git a/cw_core/lib/currency_for_wallet_type.dart b/cw_core/lib/currency_for_wallet_type.dart
index e09abed..8928bfd 100644
--- a/cw_core/lib/currency_for_wallet_type.dart
+++ b/cw_core/lib/currency_for_wallet_type.dart
@@ -44,6 +44,8 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type, {bool isTestnet = fal
return CryptoCurrency.dcr;
case WalletType.dogecoin:
return CryptoCurrency.doge;
+ case WalletType.zcash:
+ return CryptoCurrency.zec;
case WalletType.none:
throw Exception(
'Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency');
diff --git a/cw_core/lib/db/sqlite.dart b/cw_core/lib/db/sqlite.dart
index f4e2350..6c875e5 100644
--- a/cw_core/lib/db/sqlite.dart
+++ b/cw_core/lib/db/sqlite.dart
@@ -115,16 +115,26 @@ Future<Map<String, dynamic>> dumpDb() async {
}
}
-Future<List<String>> _getTableNames() async {
+Future<List<String>> _getTableNames(Database db) async {
final tableNames = await db.rawQuery('SELECT name FROM sqlite_master WHERE type = "table"');
return tableNames.map((e) => (e["name"]).toString()).toList();
}
Future<Map<String, dynamic>> _dumpDb() async {
- final tableNames = await _getTableNames();
+ final tableNames = await _getTableNames(db);
final ret = <String, dynamic>{};
for (final tableName in tableNames) {
ret[tableName] = await db.query(tableName);
}
return ret;
}
+
+Future<Map<String, dynamic>> dumpCustomDb(String path) async {
+ final db = await openDatabase(path);
+ final tableNames = await _getTableNames(db);
+ final ret = <String, dynamic>{};
+ for (final tableName in tableNames) {
+ ret[tableName] = await db.query(tableName);
+ }
+ return ret;
+}
\ No newline at end of file
diff --git a/cw_core/lib/node.dart b/cw_core/lib/node.dart
index a8bd39a..67ef15c 100644
--- a/cw_core/lib/node.dart
+++ b/cw_core/lib/node.dart
@@ -38,6 +38,20 @@ class Node extends HiveObject with Keyable {
}
}
+ @override
+ String toString() {
+ return """Node(
+ uriRaw: $uriRaw,
+ path: $path,
+ login: $login,
+ password: $password,
+ useSSL: $useSSL,
+ trusted: $trusted,
+ socksProxyAddress: $socksProxyAddress,
+ isEnabledForAutoSwitching: $isEnabledForAutoSwitching,
+ })""";
+ }
+
Node.fromMap(Map<String, Object?> map)
: uriRaw = map['uri'] as String? ?? '',
path = map['path'] as String? ?? '',
@@ -94,6 +108,7 @@ class Node extends HiveObject with Keyable {
Uri get uri {
switch (type) {
case WalletType.monero:
+ case WalletType.zcash:
case WalletType.haven:
case WalletType.wownero:
return Uri.http(uriRaw, '');
@@ -176,6 +191,7 @@ class Node extends HiveObject with Keyable {
case WalletType.solana:
case WalletType.tron:
case WalletType.dogecoin:
+ case WalletType.zcash:
return requestElectrumServer();
case WalletType.zano:
return requestZanoNode();
diff --git a/cw_core/lib/pathForWallet.dart b/cw_core/lib/pathForWallet.dart
index 9aa7219..cb5a4fe 100644
--- a/cw_core/lib/pathForWallet.dart
+++ b/cw_core/lib/pathForWallet.dart
@@ -1,13 +1,23 @@
import 'dart:io';
import 'package:cw_core/root_dir.dart';
import 'package:cw_core/wallet_type.dart';
-import 'package:path_provider/path_provider.dart';
-Future<String> pathForWalletDir({required String name, required WalletType type}) async {
+Future<String> pathForWalletTypeDir({required WalletType type}) async {
final root = await getAppDir();
final prefix = walletTypeToString(type).toLowerCase();
final walletsDir = Directory('${root.path}/wallets');
- final walletDire = Directory('${walletsDir.path}/$prefix/$name');
+ final walletDir = Directory('${walletsDir.path}/$prefix');
+
+ if (!walletDir.existsSync()) {
+ walletDir.createSync(recursive: true);
+ }
+
+ return walletDir.path;
+}
+
+Future<String> pathForWalletDir({required String name, required WalletType type}) async {
+ final typeRoot = await pathForWalletTypeDir(type: type);
+ final walletDire = Directory('${typeRoot}/$name');
if (!walletDire.existsSync()) {
walletDire.createSync(recursive: true);
diff --git a/cw_core/lib/wallet_info.dart b/cw_core/lib/wallet_info.dart
index 2fd1801..a8d6c18 100644
--- a/cw_core/lib/wallet_info.dart
+++ b/cw_core/lib/wallet_info.dart
@@ -332,6 +332,7 @@ class WalletInfo {
this.hashedWalletIdentifier,
this.isNonSeedWallet,
this.sortOrder,
+ this.addressPageType,
) : _yatLastUsedAddressController = StreamController<String>.broadcast();
factory WalletInfo.external({
@@ -374,6 +375,7 @@ class WalletInfo {
hashedWalletIdentifier,
isNonSeedWallet ?? false,
sortOrder ?? 0,
+ null,
);
}
@@ -546,6 +548,7 @@ class WalletInfo {
"hashedWalletIdentifier": hashedWalletIdentifier,
"isNonSeedWallet": isNonSeedWallet ? 1 : 0,
"sortOrder": sortOrder,
+ "addressPageType": addressPageType,
};
factory WalletInfo.fromJson(Map<String, dynamic> json) {
@@ -569,6 +572,7 @@ class WalletInfo {
json['hashedWalletIdentifier'] as String?,
(json['isNonSeedWallet'] as int) == 1,
json['sortOrder'] as int? ?? 0,
+ json['addressPageType'] as String? ?? null,
);
}
diff --git a/cw_core/lib/wallet_info_legacy.dart b/cw_core/lib/wallet_info_legacy.dart
index 502edeb..08207f2 100644
--- a/cw_core/lib/wallet_info_legacy.dart
+++ b/cw_core/lib/wallet_info_legacy.dart
@@ -270,6 +270,7 @@ class WalletInfo extends HiveObject {
hashedWalletIdentifier,
isNonSeedWallet,
0,
+ addressPageType,
);
final wiId = await walletInfo.save();
for (final address in usedAddresses ?? <String>[]) {
diff --git a/cw_core/lib/wallet_type.dart b/cw_core/lib/wallet_type.dart
index 1f8af52..0cb974c 100644
--- a/cw_core/lib/wallet_type.dart
+++ b/cw_core/lib/wallet_type.dart
@@ -21,6 +21,7 @@ const walletTypes = [
WalletType.dogecoin,
WalletType.base,
WalletType.arbitrum,
+ WalletType.zcash,
];
@HiveType(typeId: WALLET_TYPE_TYPE_ID)
@@ -78,6 +79,9 @@ enum WalletType {
@HiveField(17)
arbitrum,
+
+ @HiveField(18)
+ zcash,
}
int serializeToInt(WalletType type) {
@@ -116,6 +120,8 @@ int serializeToInt(WalletType type) {
return 15;
case WalletType.arbitrum:
return 16;
+ case WalletType.zcash:
+ return 17;
case WalletType.none:
return -1;
}
@@ -157,6 +163,8 @@ WalletType deserializeFromInt(int raw) {
return WalletType.base;
case 16:
return WalletType.arbitrum;
+ case 17:
+ return WalletType.zcash;
default:
throw Exception('Unexpected token: $raw for WalletType deserializeFromInt');
}
@@ -198,6 +206,8 @@ String walletTypeToString(WalletType type) {
return 'Base';
case WalletType.arbitrum:
return 'Arbitrum';
+ case WalletType.zcash:
+ return 'Zcash';
case WalletType.none:
return '';
}
@@ -239,6 +249,8 @@ String walletTypeToDisplayName(WalletType type) {
return 'Base (BASE)';
case WalletType.arbitrum:
return 'Arbitrum (ARB)';
+ case WalletType.zcash:
+ return 'Zcash (ZEC)';
case WalletType.none:
return '';
}
@@ -281,6 +293,8 @@ WalletType? cryptoCurrencyToWalletType(CryptoCurrency type) {
return WalletType.decred;
case CryptoCurrency.doge:
return WalletType.dogecoin;
+ case CryptoCurrency.zec:
+ return WalletType.zcash;
default:
return null;
}
diff --git a/cw_decred/pubspec.yaml b/cw_decred/pubspec.yaml
index 989831a..781b38c 100644
--- a/cw_decred/pubspec.yaml
+++ b/cw_decred/pubspec.yaml
@@ -6,10 +6,9 @@ author: Cake Wallet
homepage: https://cakewallet.com
environment:
- sdk: '>=3.2.0-0 <4.0.0'
+ sdk: ">=3.2.0-0 <4.0.0"
flutter: ">=3.19.0"
-
dependencies:
flutter:
sdk: flutter
diff --git a/cw_zcash/.gitignore b/cw_zcash/.gitignore
new file mode 100644
index 0000000..3cceda5
--- /dev/null
+++ b/cw_zcash/.gitignore
@@ -0,0 +1,7 @@
+# https://dart.dev/guides/libraries/private-files
+# Created by `dart pub`
+.dart_tool/
+
+# Avoid committing pubspec.lock for library packages; see
+# https://dart.dev/guides/libraries/private-files#pubspeclock.
+pubspec.lock
diff --git a/cw_zcash/CHANGELOG.md b/cw_zcash/CHANGELOG.md
new file mode 100644
index 0000000..effe43c
--- /dev/null
+++ b/cw_zcash/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 1.0.0
+
+- Initial version.
diff --git a/cw_zcash/README.md b/cw_zcash/README.md
new file mode 100644
index 0000000..b6754b0
--- /dev/null
+++ b/cw_zcash/README.md
@@ -0,0 +1 @@
+# cw_zcash
diff --git a/cw_zcash/analysis_options.yaml b/cw_zcash/analysis_options.yaml
new file mode 100644
index 0000000..f43bd48
--- /dev/null
+++ b/cw_zcash/analysis_options.yaml
@@ -0,0 +1,31 @@
+analyzer:
+ errors:
+ implementation_imports: ignore
+ must_be_immutable: ignore
+ overridden_fields: ignore
+ exclude:
+ - "**.g.dart"
+ - "external/**"
+
+linter:
+ rules:
+ unawaited_futures: true
+ avoid_print: false
+ parameter_assignments: true
+ prefer_const_declarations: true
+ prefer_final_fields: true
+ prefer_final_in_for_each: true
+ prefer_final_locals: true
+ prefer_final_parameters: true
+ avoid_void_async: true
+ require_trailing_commas: true
+ sort_child_properties_last: true
+ sort_constructors_first: true
+ sort_unnamed_constructors_first: true
+ unnecessary_async: true
+ unnecessary_await_in_return: true
+ unnecessary_string_interpolations: true
+ always_declare_return_types: true
+
+formatter:
+ page_width: 100
diff --git a/cw_zcash/lib/cw_zcash.dart b/cw_zcash/lib/cw_zcash.dart
new file mode 100644
index 0000000..c312ddb
--- /dev/null
+++ b/cw_zcash/lib/cw_zcash.dart
@@ -0,0 +1,15 @@
+/// Support for doing something awesome.
+///
+/// More dartdocs go here.
+library;
+
+export 'src/zcash_balance.dart';
+export 'src/zcash_mnemonic_is_incorrect_exception.dart';
+export 'src/zcash_transaction_info.dart';
+export 'src/zcash_transactions.dart';
+export 'src/zcash_wallet.dart';
+export 'src/zcash_wallet_service.dart';
+export 'src/models.dart';
+export 'src/zcash_transaction_credentials.dart';
+export 'src/pending_zcash_transaction.dart';
+export 'src/zcash_receive_page_options.dart';
diff --git a/cw_zcash/lib/src/models.dart b/cw_zcash/lib/src/models.dart
new file mode 100644
index 0000000..0dba32a
--- /dev/null
+++ b/cw_zcash/lib/src/models.dart
@@ -0,0 +1,41 @@
+import 'package:cw_core/wallet_credentials.dart';
+
+class ZcashNewWalletCredentials extends WalletCredentials {
+ ZcashNewWalletCredentials({
+ required final String name,
+ final String? password,
+ required final String? passphrase,
+ final String? mnemonic,
+ final int? seedPhraseLength,
+ }) : super(
+ name: name,
+ password: password,
+ passphrase: passphrase,
+ seedPhraseLength: seedPhraseLength,
+ ) {
+ this.mnemonic = mnemonic;
+ }
+
+ String? mnemonic;
+}
+
+class ZcashFromSeedWalletCredentials extends WalletCredentials {
+ ZcashFromSeedWalletCredentials({
+ required final String name,
+ final String? password,
+ required final String? passphrase,
+ required this.seed,
+ required super.height,
+ }) : super(name: name, password: password, passphrase: passphrase);
+ final String? seed;
+}
+
+class ZcashFromKeysWalletCredentials extends WalletCredentials {
+ ZcashFromKeysWalletCredentials({
+ required final String name,
+ final String? password,
+ required final String? passphrase,
+ required this.privateKey,
+ }) : super(name: name, password: password, passphrase: passphrase);
+ final String? privateKey;
+}
diff --git a/cw_zcash/lib/src/pending_zcash_transaction.dart b/cw_zcash/lib/src/pending_zcash_transaction.dart
new file mode 100644
index 0000000..28be246
--- /dev/null
+++ b/cw_zcash/lib/src/pending_zcash_transaction.dart
@@ -0,0 +1,103 @@
+import 'dart:convert';
+import 'dart:math';
+
+import 'package:cw_core/output_info.dart';
+import 'package:cw_core/pending_transaction.dart';
+import 'package:cw_zcash/cw_zcash.dart';
+import 'package:cw_core/currency_for_wallet_type.dart';
+import 'package:cw_core/wallet_type.dart';
+import 'package:cw_zcash/src/zcash_taddress_rotation.dart';
+import 'package:warp_api/data_fb_generated.dart';
+import 'package:warp_api/warp_api.dart';
+
+class PendingZcashTransaction with PendingTransaction {
+ PendingZcashTransaction({
+ required this.zcashWallet,
+ required this.credentials,
+ required this.txPlan,
+ required this.fee,
+ required this.availableBalance,
+ });
+
+ final ZcashWallet zcashWallet;
+ final ZcashTransactionCredentials credentials;
+ final String txPlan;
+ String? _txId;
+ final int availableBalance;
+
+ @override
+ String get id => _txId ?? '';
+
+ @override
+ String get hex => '';
+
+ @override
+ String get amountFormatted {
+ return walletTypeToCryptoCurrency(WalletType.zcash).formatAmount(BigInt.from(totalAmount));
+ }
+
+ int get totalAmount {
+ final isAll = credentials.outputs.fold<bool>(false, (final a, final b) => a || (b.sendAll));
+ if (isAll) {
+ return availableBalance;
+ }
+ return credentials.outputs.fold<int>(
+ 0,
+ (final a, final b) => a + (b.formattedCryptoAmount ?? 0),
+ );
+ }
+
+ @override
+ String get feeFormatted =>
+ '$feeFormattedValue ${walletTypeToCryptoCurrency(WalletType.zcash).title}';
+
+ @override
+ late String feeFormattedValue = walletTypeToCryptoCurrency(
+ WalletType.zcash,
+ ).formatAmount(BigInt.from(fee));
+
+ int fee;
+
+ @override
+ Future<void> commit() async {
+ _txId = await ZcashWalletService.runInDbMutex(
+ () => WarpApi.signAndBroadcast(ZcashWalletBase.coin, zcashWallet.accountId, txPlan),
+ );
+ ZcashWalletBase.temporarySentTx[zcashWallet.accountId] ??= [];
+ ZcashWalletBase.temporarySentTx[zcashWallet.accountId]?.add(
+ ShieldedTx(
+ base64.decode(
+ ZcashTaddressRotation.flatBuffersPack(
+ ShieldedTxT(
+ id: Random().nextInt(pow(2, 32).toInt()),
+ txId: _txId,
+ height: 0,
+ timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000,
+ value: -totalAmount,
+ ).pack,
+ ),
+ ),
+ ),
+ );
+ await ZcashTransactionInfo.addCachedDestinationAddress(
+ _txId ?? '',
+ credentials.outputs.reduce((final o1, final o2) {
+ return OutputInfo(
+ address: o1.address + "," + o2.address,
+ sendAll: false,
+ isParsedAddress: false,
+ );
+ }).address,
+ );
+ await zcashWallet.updateTransactions();
+ await zcashWallet.updateBalance();
+ }
+
+ @override
+ Future<Map<String, String>> commitUR() {
+ throw UnimplementedError('UR not supported for Zcash');
+ }
+
+ @override
+ bool shouldCommitUR() => false;
+}
diff --git a/cw_zcash/lib/src/util/crc32.dart b/cw_zcash/lib/src/util/crc32.dart
new file mode 100644
index 0000000..c5fc239
--- /dev/null
+++ b/cw_zcash/lib/src/util/crc32.dart
@@ -0,0 +1,296 @@
+/// CRC32
+/// Copyright (C) 2012, Kai Sellgren
+/// Licensed under the MIT License.
+/// http://www.opensource.org/licenses/mit-license.php
+/// source: https://raw.githubusercontent.com/kaisellgren/CRC32/refs/heads/master/lib/crc32.dart
+// ignore_for_file: parameter_assignments
+
+import 'dart:convert';
+
+/// Computes Cyclic Redundancy Check values.
+class CRC32 {
+ /// Computes a CRC32 value for the given input.
+ ///
+ /// The return value is an unsigned integer.
+ ///
+ /// You may optionally specify the beginning CRC value.
+ static int compute(dynamic input, [int crc = 0]) {
+ if (input == null) throw new ArgumentError.notNull('input');
+ if (input is String) input = utf8.encode(input);
+
+ crc = crc ^ (0xffffffff);
+
+ for (final byte in input) {
+ if (!(byte >= -128 && byte <= 255))
+ throw new FormatException("Invalid value in input: $byte");
+
+ final x = CRC32._table[(crc ^ byte) & 0xff];
+ crc = (crc & 0xffffffff) >> 8; // crc >>> 8 (32-bit unsigned integer)
+ crc ^= x;
+ }
+
+ crc = crc ^ (0xffffffff);
+
+ // Turns the signed integer into an unsigned integer.
+ return crc & 0xffffffff;
+ }
+
+ static const _table = const [
+ 0x00000000,
+ 0x77073096,
+ 0xEE0E612C,
+ 0x990951BA,
+ 0x076DC419,
+ 0x706AF48F,
+ 0xE963A535,
+ 0x9E6495A3,
+ 0x0EDB8832,
+ 0x79DCB8A4,
+ 0xE0D5E91E,
+ 0x97D2D988,
+ 0x09B64C2B,
+ 0x7EB17CBD,
+ 0xE7B82D07,
+ 0x90BF1D91,
+ 0x1DB71064,
+ 0x6AB020F2,
+ 0xF3B97148,
+ 0x84BE41DE,
+ 0x1ADAD47D,
+ 0x6DDDE4EB,
+ 0xF4D4B551,
+ 0x83D385C7,
+ 0x136C9856,
+ 0x646BA8C0,
+ 0xFD62F97A,
+ 0x8A65C9EC,
+ 0x14015C4F,
+ 0x63066CD9,
+ 0xFA0F3D63,
+ 0x8D080DF5,
+ 0x3B6E20C8,
+ 0x4C69105E,
+ 0xD56041E4,
+ 0xA2677172,
+ 0x3C03E4D1,
+ 0x4B04D447,
+ 0xD20D85FD,
+ 0xA50AB56B,
+ 0x35B5A8FA,
+ 0x42B2986C,
+ 0xDBBBC9D6,
+ 0xACBCF940,
+ 0x32D86CE3,
+ 0x45DF5C75,
+ 0xDCD60DCF,
+ 0xABD13D59,
+ 0x26D930AC,
+ 0x51DE003A,
+ 0xC8D75180,
+ 0xBFD06116,
+ 0x21B4F4B5,
+ 0x56B3C423,
+ 0xCFBA9599,
+ 0xB8BDA50F,
+ 0x2802B89E,
+ 0x5F058808,
+ 0xC60CD9B2,
+ 0xB10BE924,
+ 0x2F6F7C87,
+ 0x58684C11,
+ 0xC1611DAB,
+ 0xB6662D3D,
+ 0x76DC4190,
+ 0x01DB7106,
+ 0x98D220BC,
+ 0xEFD5102A,
+ 0x71B18589,
+ 0x06B6B51F,
+ 0x9FBFE4A5,
+ 0xE8B8D433,
+ 0x7807C9A2,
+ 0x0F00F934,
+ 0x9609A88E,
+ 0xE10E9818,
+ 0x7F6A0DBB,
+ 0x086D3D2D,
+ 0x91646C97,
+ 0xE6635C01,
+ 0x6B6B51F4,
+ 0x1C6C6162,
+ 0x856530D8,
+ 0xF262004E,
+ 0x6C0695ED,
+ 0x1B01A57B,
+ 0x8208F4C1,
+ 0xF50FC457,
+ 0x65B0D9C6,
+ 0x12B7E950,
+ 0x8BBEB8EA,
+ 0xFCB9887C,
+ 0x62DD1DDF,
+ 0x15DA2D49,
+ 0x8CD37CF3,
+ 0xFBD44C65,
+ 0x4DB26158,
+ 0x3AB551CE,
+ 0xA3BC0074,
+ 0xD4BB30E2,
+ 0x4ADFA541,
+ 0x3DD895D7,
+ 0xA4D1C46D,
+ 0xD3D6F4FB,
+ 0x4369E96A,
+ 0x346ED9FC,
+ 0xAD678846,
+ 0xDA60B8D0,
+ 0x44042D73,
+ 0x33031DE5,
+ 0xAA0A4C5F,
+ 0xDD0D7CC9,
+ 0x5005713C,
+ 0x270241AA,
+ 0xBE0B1010,
+ 0xC90C2086,
+ 0x5768B525,
+ 0x206F85B3,
+ 0xB966D409,
+ 0xCE61E49F,
+ 0x5EDEF90E,
+ 0x29D9C998,
+ 0xB0D09822,
+ 0xC7D7A8B4,
+ 0x59B33D17,
+ 0x2EB40D81,
+ 0xB7BD5C3B,
+ 0xC0BA6CAD,
+ 0xEDB88320,
+ 0x9ABFB3B6,
+ 0x03B6E20C,
+ 0x74B1D29A,
+ 0xEAD54739,
+ 0x9DD277AF,
+ 0x04DB2615,
+ 0x73DC1683,
+ 0xE3630B12,
+ 0x94643B84,
+ 0x0D6D6A3E,
+ 0x7A6A5AA8,
+ 0xE40ECF0B,
+ 0x9309FF9D,
+ 0x0A00AE27,
+ 0x7D079EB1,
+ 0xF00F9344,
+ 0x8708A3D2,
+ 0x1E01F268,
+ 0x6906C2FE,
+ 0xF762575D,
+ 0x806567CB,
+ 0x196C3671,
+ 0x6E6B06E7,
+ 0xFED41B76,
+ 0x89D32BE0,
+ 0x10DA7A5A,
+ 0x67DD4ACC,
+ 0xF9B9DF6F,
+ 0x8EBEEFF9,
+ 0x17B7BE43,
+ 0x60B08ED5,
+ 0xD6D6A3E8,
+ 0xA1D1937E,
+ 0x38D8C2C4,
+ 0x4FDFF252,
+ 0xD1BB67F1,
+ 0xA6BC5767,
+ 0x3FB506DD,
+ 0x48B2364B,
+ 0xD80D2BDA,
+ 0xAF0A1B4C,
+ 0x36034AF6,
+ 0x41047A60,
+ 0xDF60EFC3,
+ 0xA867DF55,
+ 0x316E8EEF,
+ 0x4669BE79,
+ 0xCB61B38C,
+ 0xBC66831A,
+ 0x256FD2A0,
+ 0x5268E236,
+ 0xCC0C7795,
+ 0xBB0B4703,
+ 0x220216B9,
+ 0x5505262F,
+ 0xC5BA3BBE,
+ 0xB2BD0B28,
+ 0x2BB45A92,
+ 0x5CB36A04,
+ 0xC2D7FFA7,
+ 0xB5D0CF31,
+ 0x2CD99E8B,
+ 0x5BDEAE1D,
+ 0x9B64C2B0,
+ 0xEC63F226,
+ 0x756AA39C,
+ 0x026D930A,
+ 0x9C0906A9,
+ 0xEB0E363F,
+ 0x72076785,
+ 0x05005713,
+ 0x95BF4A82,
+ 0xE2B87A14,
+ 0x7BB12BAE,
+ 0x0CB61B38,
+ 0x92D28E9B,
+ 0xE5D5BE0D,
+ 0x7CDCEFB7,
+ 0x0BDBDF21,
+ 0x86D3D2D4,
+ 0xF1D4E242,
+ 0x68DDB3F8,
+ 0x1FDA836E,
+ 0x81BE16CD,
+ 0xF6B9265B,
+ 0x6FB077E1,
+ 0x18B74777,
+ 0x88085AE6,
+ 0xFF0F6A70,
+ 0x66063BCA,
+ 0x11010B5C,
+ 0x8F659EFF,
+ 0xF862AE69,
+ 0x616BFFD3,
+ 0x166CCF45,
+ 0xA00AE278,
+ 0xD70DD2EE,
+ 0x4E048354,
+ 0x3903B3C2,
+ 0xA7672661,
+ 0xD06016F7,
+ 0x4969474D,
+ 0x3E6E77DB,
+ 0xAED16A4A,
+ 0xD9D65ADC,
+ 0x40DF0B66,
+ 0x37D83BF0,
+ 0xA9BCAE53,
+ 0xDEBB9EC5,
+ 0x47B2CF7F,
+ 0x30B5FFE9,
+ 0xBDBDF21C,
+ 0xCABAC28A,
+ 0x53B39330,
+ 0x24B4A3A6,
+ 0xBAD03605,
+ 0xCDD70693,
+ 0x54DE5729,
+ 0x23D967BF,
+ 0xB3667A2E,
+ 0xC4614AB8,
+ 0x5D681B02,
+ 0x2A6F2B94,
+ 0xB40BBE37,
+ 0xC30C8EA1,
+ 0x5A05DF1B,
+ 0x2D02EF8D,
+ ];
+}
diff --git a/cw_zcash/lib/src/zcash_balance.dart b/cw_zcash/lib/src/zcash_balance.dart
new file mode 100644
index 0000000..5247ce9
--- /dev/null
+++ b/cw_zcash/lib/src/zcash_balance.dart
@@ -0,0 +1,30 @@
+import 'package:cw_core/balance.dart';
+import 'package:cw_core/crypto_currency.dart';
+
+class ZcashBalance extends Balance {
+ ZcashBalance({required this.confirmed, required this.unconfirmed, required this.frozen})
+ : super(confirmed, unconfirmed);
+
+ factory ZcashBalance.zero() => ZcashBalance(confirmed: 0, unconfirmed: 0, frozen: 0);
+
+ final int confirmed;
+ final int unconfirmed;
+ final int frozen;
+
+ @override
+ String get formattedAvailableBalance {
+ return CryptoCurrency.zec.formatAmount(BigInt.from(confirmed));
+ }
+
+ @override
+ String get formattedAdditionalBalance {
+ if (unconfirmed == 0) return '0.0';
+ return CryptoCurrency.zec.formatAmount(BigInt.from(unconfirmed));
+ }
+
+ @override
+ String get formattedUnAvailableBalance {
+ if (frozen == 0) return '';
+ return CryptoCurrency.zec.formatAmount(BigInt.from(frozen));
+ }
+}
diff --git a/cw_zcash/lib/src/zcash_mnemonic_is_incorrect_exception.dart b/cw_zcash/lib/src/zcash_mnemonic_is_incorrect_exception.dart
new file mode 100644
index 0000000..4986837
--- /dev/null
+++ b/cw_zcash/lib/src/zcash_mnemonic_is_incorrect_exception.dart
@@ -0,0 +1,5 @@
+class ZcashMnemonicIsIncorrectException implements Exception {
+ @override
+ String toString() =>
+ 'Zcash mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.';
+}
diff --git a/cw_zcash/lib/src/zcash_receive_page_options.dart b/cw_zcash/lib/src/zcash_receive_page_options.dart
new file mode 100644
index 0000000..400ade3
--- /dev/null
+++ b/cw_zcash/lib/src/zcash_receive_page_options.dart
@@ -0,0 +1,70 @@
+import 'package:cw_core/receive_page_option.dart';
+import 'package:cw_core/utils/print_verbose.dart';
+
+enum ZcashAddressType {
+ transparent,
+ transparentRotated,
+ shieldedSapling,
+ shieldedOrchard,
+ unifiedType,
+}
+
+class ZcashReceivePageOption implements ReceivePageOption {
+ const ZcashReceivePageOption._(this.type);
+
+ factory ZcashReceivePageOption.fromType(final ZcashAddressType type) {
+ switch (type) {
+ case ZcashAddressType.transparent:
+ return transparent;
+ case ZcashAddressType.transparentRotated:
+ return transparentRotated;
+ case ZcashAddressType.shieldedSapling:
+ return shieldedSapling;
+ case ZcashAddressType.shieldedOrchard:
+ return shieldedOrchard;
+ case ZcashAddressType.unifiedType:
+ return unified;
+ }
+ }
+ static const transparent = ZcashReceivePageOption._(ZcashAddressType.transparent);
+ static const transparentRotated = ZcashReceivePageOption._(ZcashAddressType.transparentRotated);
+ static const shieldedSapling = ZcashReceivePageOption._(ZcashAddressType.shieldedSapling);
+ static const shieldedOrchard = ZcashReceivePageOption._(ZcashAddressType.shieldedOrchard);
+ static const unified = ZcashReceivePageOption._(ZcashAddressType.unifiedType);
+
+ final ZcashAddressType type;
+
+ String get value => switch (type) {
+ ZcashAddressType.transparent => "Transparent (Static)",
+ ZcashAddressType.transparentRotated => "Transparent (Disposable)",
+ ZcashAddressType.shieldedSapling => "Shielded (Sapling)",
+ ZcashAddressType.shieldedOrchard => "Shielded (Orchard)",
+ ZcashAddressType.unifiedType => "Unified",
+ };
+
+ String toString() {
+ return value;
+ }
+
+ static const all = [
+ ZcashReceivePageOption.shieldedOrchard,
+ ZcashReceivePageOption.shieldedSapling,
+ ZcashReceivePageOption.unified,
+ ZcashReceivePageOption.transparentRotated,
+ ZcashReceivePageOption.transparent,
+ ];
+
+ ZcashAddressType toType() {
+ return type;
+ }
+
+ static ZcashAddressType typeFromString(final String str) {
+ for (int i = 0; i < ZcashAddressType.values.length; i++) {
+ if (str == ZcashAddressType.values[i].toString()) {
+ return ZcashAddressType.values[i];
+ }
+ }
+ printV("Not found for: $str");
+ return ZcashAddressType.shieldedOrchard;
+ }
+}
diff --git a/cw_zcash/lib/src/zcash_taddress_rotation.dart b/cw_zcash/lib/src/zcash_taddress_rotation.dart
new file mode 100644
index 0000000..faa8673
--- /dev/null
+++ b/cw_zcash/lib/src/zcash_taddress_rotation.dart
@@ -0,0 +1,421 @@
+// create offset wallets for T address rotation.
+// Each address is considered one time use, if it receives less than 15000 zatoshi balance will be essentially burned.
+//
+// This class takes address index as constructor, and will create 10 addresses by default, if any of the addresses gets used
+// it will be moved out of the pool
+//
+// T addresses are generated by creating new wallets with an extra seed word (or appending to "passphrase") the following
+// :tgen:${CRC32(seed)}
+// It is highly unlikely for users to create a wallet with that specific last word and add index to it
+// We could leverage other fields such as native account index alone, but users may want to set that in future
+// This solution is rather simple and if standard would get estabilished for this purpose it can be easily
+// turned off and replaced with proper solution.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:cw_core/pathForWallet.dart';
+import 'package:cw_core/utils/print_verbose.dart';
+import 'package:cw_core/wallet_type.dart';
+import 'package:cw_zcash/src/util/crc32.dart';
+import 'package:cw_zcash/src/zcash_wallet.dart';
+import 'package:cw_zcash/src/zcash_wallet_service.dart';
+import 'package:flutter/foundation.dart';
+import 'package:warp_api/data_fb_generated.dart';
+import 'package:warp_api/warp_api.dart';
+import 'package:flat_buffers/flat_buffers.dart' as fb;
+import 'package:path/path.dart' as p;
+
+class ZcashTaddressRotation {
+ ZcashTaddressRotation(this.accountIndex);
+ final int accountIndex;
+
+ static bool _isStarted = false;
+
+ static const coin = ZcashWalletBase.coin;
+ static String seedForOffset(final String seed) {
+ final seedWords = seed.split(" ");
+ if ([12, 24].contains(seedWords.length)) {
+ seedWords.add("");
+ }
+ final lastI = seedWords.length - 1;
+ final crc = CRC32.compute(seed);
+ seedWords[lastI] += ":tgen:${crc}";
+ return seedWords.join(" ");
+ }
+
+ static bool isSeedForWallet(final String mainWallet, final String subWallet) {
+ return seedForOffset(mainWallet.trim()) == subWallet;
+ }
+
+ static Map<int, List<Account>> rotationAccounts = {};
+ static Map<int, List<Account>> rotationAccountsUsable = {};
+ static Map<int, List<ShieldedTx>> shieldedAccountsTx = {};
+ static Future<void> init() async {
+ printV("Deserializing previous state");
+ if (_isStarted) {
+ return;
+ }
+ _isStarted = true;
+ final pfw = await pathForWalletTypeDir(type: WalletType.zcash);
+ try {
+ final f = File(p.join(pfw, "zec-taddr.json"));
+ if (!f.existsSync()) {
+ f.writeAsBytesSync(serialize());
+ }
+ deserialize(f.readAsBytesSync());
+ } catch (e) {
+ printV("Failed to deserialize T address DB for zcash, it doesn't really matter: $e");
+ }
+
+ unawaited(
+ (() async {
+ await Future.delayed(Duration(seconds: 5));
+ return _jobRunner();
+ })(),
+ );
+ }
+
+ static Future<void> serializeToFile() async {
+ final pfw = await pathForWalletTypeDir(type: WalletType.zcash);
+ final f = File(p.join(pfw, "zec-taddr.json"));
+ f.writeAsBytesSync(serialize());
+ }
+
+ static Uint8List serialize() {
+ final data = {
+ "rotationAccounts": rotationAccounts.map(
+ (final k, final v) => MapEntry(
+ k.toRadixString(16),
+ v.map((final a) => flatBuffersPack(a.unpack().pack)).toList(),
+ ),
+ ),
+ if (kDebugMode)
+ "_rotationAccounts": rotationAccounts.map(
+ (final k, final v) =>
+ MapEntry(k.toRadixString(16), v.map((final a) => a.toString()).toList()),
+ ),
+ "rotationAccountsUsable": rotationAccountsUsable.map(
+ (final k, final v) => MapEntry(
+ k.toRadixString(16),
+ v.map((final a) => flatBuffersPack(a.unpack().pack)).toList(),
+ ),
+ ),
+ if (kDebugMode)
+ "_rotationAccountsUsable": rotationAccountsUsable.map(
+ (final k, final v) =>
+ MapEntry(k.toRadixString(16), v.map((final a) => a.toString()).toList()),
+ ),
+ "shieldedAccountsTx": shieldedAccountsTx.map(
+ (final k, final v) => MapEntry(
+ k.toRadixString(16),
+ v.map((final a) => flatBuffersPack(a.unpack().pack)).toList(),
+ ),
+ ),
+ if (kDebugMode)
+ "_shieldedAccountsTx": shieldedAccountsTx.map(
+ (final k, final v) =>
+ MapEntry(k.toRadixString(16), v.map((final a) => a.toString()).toList()),
+ ),
+ };
+
+ if (kDebugMode) {
+ return utf8.encode(JsonEncoder.withIndent(" ").convert(data));
+ }
+ return utf8.encode(jsonEncode(data));
+ }
+
+ static String flatBuffersPack(final int Function(fb.Builder fbBuilder) pack) {
+ final fbBuilder = fb.Builder();
+ final offset = pack(fbBuilder);
+ fbBuilder.finish(offset);
+ return base64.encode(fbBuilder.buffer);
+ }
+
+ static void deserialize(final Uint8List bytes) {
+ // todo: replace keys with sums and reverse in serialize
+ // final List<String> seeds = [];
+ // for (int i = 0; i < accounts.length; i++) {
+ // final acc = accounts[i];
+ // final backup = WarpApi.getBackup(coin, acc.id);
+ // await WarpApi.transparentSync(coin, acc.id, syncHeight);
+ // seeds.add(backup.seed!);
+ // }
+ try {
+ final Map<dynamic, dynamic> data = jsonDecode(utf8.decode(bytes));
+ rotationAccounts = (data["rotationAccounts"] as Map<String, dynamic>).map(
+ (final k, final v) => MapEntry(
+ int.parse(k, radix: 16),
+ (v as List).map((final a) => Account(atob(a))).toList(),
+ ),
+ );
+ rotationAccountsUsable = (data["rotationAccountsUsable"] as Map<String, dynamic>).map(
+ (final k, final v) => MapEntry(
+ int.parse(k, radix: 16),
+ (v as List).map((final a) => Account(atob(a))).toList(),
+ ),
+ );
+ shieldedAccountsTx = (data["shieldedAccountsTx"] as Map<String, dynamic>).map(
+ (final k, final v) => MapEntry(
+ int.parse(k, radix: 16),
+ (v as List).map((final a) => ShieldedTx(atob(a))).toList(),
+ ),
+ );
+ } catch (e) {
+ rethrow;
+ }
+ }
+
+ static Uint8List atob(final String value) =>
+ Uint8List.fromList(List<int>.from(base64.decode(value)));
+
+ static Future<void> createAndSweepTAddresses() async {
+ int chainHeight = 0;
+ try {
+ chainHeight = await WarpApi.getLatestHeight(coin);
+ } catch (e) {
+ printV("Error getting latest height: $e");
+ }
+ final dbHeight = WarpApi.getDbHeight(coin);
+ final height = dbHeight.unpack();
+ final syncHeight = height.height;
+ if (chainHeight != syncHeight) {
+ printV(
+ "Waiting for sync to finish: chainHeight(${chainHeight}) != syncHeight(${syncHeight})",
+ );
+ return;
+ }
+ final Map<int, List<ShieldedTx>> newShieldedAccountsTx = {};
+ final accounts = WarpApi.getAccountList(coin);
+ if (accounts.isEmpty) return;
+ final Map<int, String> seeds = {};
+ for (int i = 0; i < accounts.length; i++) {
+ final acc = accounts[i];
+ final backup = WarpApi.getBackup(coin, acc.id);
+ await WarpApi.transparentSync(coin, acc.id, syncHeight);
+ seeds[acc.id] = backup.seed!;
+ }
+ for (int i = 0; i < accounts.length; i++) {
+ final seed = seeds[accounts[i].id]!;
+ if ([12, 13, 24, 25].contains(seed.split(" ").length)) {
+ if (seed.split(" ").last.contains(":tgen:")) continue;
+ rotationAccounts[accounts[i].id] = [];
+ }
+ }
+ final raKeys = rotationAccounts.keys.toList();
+ seeds.removeWhere((_, final val) => raKeys.contains(val));
+ printV("raKeys: ${raKeys.length}");
+ for (int i = 0; i < raKeys.length; i++) {
+ final accs = accounts.where((final a) {
+ final seed = WarpApi.getBackup(coin, a.id).seed;
+ return isSeedForWallet(seeds[raKeys[i]]!, seed!);
+ }).toList();
+ rotationAccounts[raKeys[i]]!.addAll(accs);
+ final acc = accountForSeed(seeds[raKeys[i]]!)!;
+ final txs = WarpApi.getTxsSync(coin, acc.id);
+ newShieldedAccountsTx[acc.id] ??= [];
+ newShieldedAccountsTx[acc.id]!.addAll(txs);
+ }
+ rotationAccountsUsable = rotationAccounts.map((final k, final v) => MapEntry(k, v.toList()));
+ printV("rotationAccounts: ${rotationAccounts.length}");
+
+ for (int i = 0; i < raKeys.length; i++) {
+ final acc = accountForSeed(seeds[raKeys[i]]!)!;
+ newShieldedAccountsTx[acc.id] = [];
+ for (int j = 0; j < (rotationAccounts[raKeys[i]]?.length ?? 0); j++) {
+ final txs = WarpApi.getTxsSync(coin, rotationAccounts[raKeys[i]]![j].id);
+ rotationAccounts[raKeys[i]]![j];
+ newShieldedAccountsTx[acc.id]!.addAll(txs);
+ }
+ rotationAccountsUsable[raKeys[i]]!.removeWhere((final a) {
+ final txs = WarpApi.getTxsSync(coin, a.id);
+ return txs.isNotEmpty;
+ });
+ }
+ printV("rotationAccountsUsable: ${rotationAccountsUsable.length}");
+
+ bool didAddNewAccount = false;
+ for (int i = 0; i < raKeys.length; i++) {
+ if (rotationAccountsUsable[raKeys[i]]!.length < 5) {
+ final seed = seedForOffset(seeds[raKeys[i]]!);
+ final name = CRC32.compute(raKeys[i].toString()).toString();
+ final id = await ZcashWalletService.runInDbMutex(
+ () => WarpApi.newAccount(coin, name, seed, rotationAccounts[raKeys[i]]!.length),
+ );
+ printV("new id: $id / $seed");
+ printV("${rotationAccounts[raKeys[i]]}");
+ printV(raKeys[i]);
+ rotationAccounts[raKeys[i]]!.forEach((final a) {
+ final b = WarpApi.getBackup(coin, a.id);
+ printV("${a.id} / ${b.seed}");
+ });
+ didAddNewAccount = true;
+ }
+ }
+ printV("didAddNewAccount: ${didAddNewAccount}");
+ if (didAddNewAccount) {
+ return createAndSweepTAddresses();
+ }
+
+ for (var i = 0; i < raKeys.length; i++) {
+ final accs = rotationAccounts[raKeys[i]]!;
+ inner:
+ for (var j = 0; j < accs.length; j++) {
+ int bal = 0;
+ try {
+ bal += await WarpApi.getTBalance(coin, accs[j].id);
+ } catch (e) {
+ printV("getTBalance: $e");
+ return;
+ }
+ if (bal < 30000) continue inner;
+ final to = accountForSeed(seeds[raKeys[i]]!)!;
+ // final toAddress = WarpApi.getTAddr(coin, to.id);
+ // final fromSeed = WarpApi.getBackup(coin, to.id);
+ // final txId = await ZcashWalletService.runInDbMutex(
+ // () => WarpApi.sweepTransparentSeed(
+ // to.coin,
+ // to.id,
+ // chainHeight,
+ // fromSeed.seed!,
+ // 7,
+ // toAddress,
+ // 0,
+ // 1,
+ // FeeT(fee: 10000, minFee: 0, maxFee: 0, scheme: 0),
+ // ),
+ // );
+ final recipientBuilder = RecipientObjectBuilder(
+ address: WarpApi.getAddress(coin, to.id, 4),
+ pools: 4,
+ feeIncluded: true,
+ amount: bal,
+ );
+
+ final recipient = Recipient(recipientBuilder.toBytes());
+ final fee = FeeT(fee: 10000, minFee: 0, maxFee: 0, scheme: 0);
+ final txPlan = await ZcashWalletService.runInDbMutex(
+ () => WarpApi.prepareTx(
+ coin,
+ accs[j].id,
+ [recipient],
+ 3, // pools: (Transparent + Sapling)
+ 1,
+ 0, // anchorOffset
+ fee,
+ ),
+ );
+ final _txId = await ZcashWalletService.runInDbMutex(
+ () => WarpApi.signAndBroadcast(ZcashWalletBase.coin, accs[j].id, txPlan),
+ );
+ await ZcashWalletService.addShieldedTx(_txId);
+ printV("shielded: $_txId");
+
+ await Future.delayed(Duration(seconds: 120)); // let it be
+ return createAndSweepTAddresses();
+ }
+ }
+ final nsatKeys = newShieldedAccountsTx.keys.toList();
+ for (int i = 0; i < nsatKeys.length; i++) {
+ inner:
+ for (int j = 0; j < newShieldedAccountsTx[nsatKeys[i]]!.length; j++) {
+ final tx = newShieldedAccountsTx[nsatKeys[i]]?[j];
+ if (tx == null) {
+ continue inner;
+ }
+ final _txId = tx.txId;
+ if (_txId == null) {
+ continue inner;
+ }
+ if (tx.value < 0) {
+ await ZcashWalletService.addShieldedTx(_txId);
+ // continue inner;
+ }
+ if (ZcashWalletService.autoshieldTx.contains(_txId)) {
+ continue inner;
+ }
+ }
+ }
+ shieldedAccountsTx = newShieldedAccountsTx.map((final k, final v) => MapEntry(k, v.toList()));
+ await serializeToFile();
+ return;
+ }
+
+ static ShieldedTx reverseAmount(final ShieldedTx t) {
+ final tt = t.unpack();
+ tt.value = -tt.value;
+ return ShieldedTx(base64.decode(flatBuffersPack(tt.pack)));
+ }
+
+ static Account? accountForSeed(final String seed) {
+ final accounts = WarpApi.getAccountList(coin);
+ if (accounts.isEmpty) return null;
+ for (int i = 0; i < accounts.length; i++) {
+ final acc = accounts[i];
+ final backup = WarpApi.getBackup(coin, acc.id);
+ if (backup.seed == seed) {
+ return acc;
+ }
+ }
+ return null;
+ }
+
+ static Future<void> _jobRunner() async {
+ for (;;) {
+ try {
+ await Future.delayed(Duration(seconds: 5));
+ await createAndSweepTAddresses();
+ } catch (e, s) {
+ printV(e);
+ s.toString().split("\n").forEach(printV);
+ rethrow;
+ } finally {
+ await Future.delayed(Duration(seconds: 30));
+ }
+ }
+ }
+
+ static String? addressForAccount(final int accountId) {
+ final acc = rotationAccountsUsable[accountId]?.firstOrNull;
+ if (acc == null) {
+ return null;
+ }
+ return WarpApi.getTAddr(coin, acc.id);
+ }
+
+ static List<String>? allAddressesForAccount(final int accountId) {
+ final acc = rotationAccounts[accountId];
+ if (acc == null) {
+ printV("Nothing found");
+ return null;
+ }
+ for (int i = 0; i < acc.length; i++) {
+ final b = WarpApi.getBackup(coin, acc[i].id);
+ printV("$i. ${b.seed?.split(" ").last}, ${b.index}, ${WarpApi.getTAddr(coin, acc[i].id)}");
+ }
+ return acc.map((final a) => WarpApi.getTAddr(coin, a.id)).toList();
+ }
+
+ static List<String>? allUsedAddressesForAccount(final int accountId) {
+ final seed = WarpApi.getBackup(coin, accountId).seed!;
+ final acc = rotationAccounts[seed]?.toList();
+ if (acc == null) {
+ printV("Nothing found");
+ return null;
+ }
+ acc.removeWhere((final a1) {
+ for (int i = 0; i < (rotationAccountsUsable[seed]?.length ?? 0); i++) {
+ if (rotationAccountsUsable[seed]?[i].id == a1.id) {
+ return true;
+ }
+ }
+ return false;
+ });
+ for (int i = 0; i < acc.length; i++) {
+ final b = WarpApi.getBackup(coin, acc[i].id);
+ printV("$i. ${b.seed?.split(" ").last}, ${b.index}, ${WarpApi.getTAddr(coin, acc[i].id)}");
+ }
+ return acc.map((final a) => WarpApi.getTAddr(coin, a.id)).toList();
+ }
+}
diff --git a/cw_zcash/lib/src/zcash_transaction_credentials.dart b/cw_zcash/lib/src/zcash_transaction_credentials.dart
new file mode 100644
index 0000000..7ec3e5a
--- /dev/null
+++ b/cw_zcash/lib/src/zcash_transaction_credentials.dart
@@ -0,0 +1,15 @@
+import 'package:cw_core/crypto_currency.dart';
+import 'package:cw_core/monero_transaction_priority.dart';
+import 'package:cw_core/output_info.dart';
+
+class ZcashTransactionCredentials {
+ ZcashTransactionCredentials({
+ required this.outputs,
+ required this.priority,
+ required this.currency,
+ });
+
+ final List<OutputInfo> outputs;
+ final MoneroTransactionPriority priority;
+ final CryptoCurrency currency;
+}
diff --git a/cw_zcash/lib/src/zcash_transaction_info.dart b/cw_zcash/lib/src/zcash_transaction_info.dart
new file mode 100644
index 0000000..62c9ad8
--- /dev/null
+++ b/cw_zcash/lib/src/zcash_transaction_info.dart
@@ -0,0 +1,106 @@
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:cw_core/currency_for_wallet_type.dart';
+import 'package:cw_core/pathForWallet.dart';
+import 'package:cw_core/transaction_direction.dart';
+import 'package:cw_core/transaction_info.dart';
+import 'package:cw_core/format_amount.dart';
+import 'package:cw_core/utils/print_verbose.dart';
+import 'package:cw_core/wallet_type.dart';
+import 'package:cw_zcash/cw_zcash.dart';
+import 'package:path/path.dart' as p;
+
+class ZcashTransactionInfo extends TransactionInfo {
+ ZcashTransactionInfo({
+ required final String id,
+ required final int amount,
+ required final int fee,
+ required final TransactionDirection direction,
+ required final bool isPending,
+ required final DateTime date,
+ required final int height,
+ required final int confirmations,
+ required final String to,
+ final String? memo,
+ }) {
+ this.id = id;
+ this.amount = amount;
+ this.fee = fee;
+ this.height = height;
+ this.direction = direction;
+ this.date = date;
+ this.isPending = isPending;
+ this.confirmations = confirmations;
+ this.to = getCachedDestinationAddress(id);
+ if (memo != null && memo.isNotEmpty) {
+ additionalInfo['memo'] = memo;
+ }
+ additionalInfo['autoShield'] = false;
+ // note: this won't work yet, fee is not in zcash_lib metadata,
+ // leaving here so it can start working automagically in future
+ if (amount == fee && to.startsWith("u")) {
+ additionalInfo['autoShield'] = true;
+ }
+ // remove below when above starts working
+ additionalInfo['autoShield'] = ZcashWalletService.autoshieldTx.contains(txHash);
+ // --- == === cut here === == ---
+ if (additionalInfo['autoShield'] == true) {
+ additionalInfo['memo'] ??= '';
+ additionalInfo['memo'] += '\This is an auto-shielding transaction. Enjoy default privacy!';
+ additionalInfo['memo'] = additionalInfo['memo'].trim();
+ }
+ }
+
+ String? _fiatAmount;
+
+ @override
+ String amountFormatted() =>
+ '${walletTypeToCryptoCurrency(WalletType.zcash).formatAmount(BigInt.from(amount))} ${walletTypeToCryptoCurrency(WalletType.zcash).title}';
+
+ @override
+ String? feeFormatted() {
+ if (fee == null || fee == 0) return null;
+ return '${walletTypeToCryptoCurrency(WalletType.zcash).formatAmount(BigInt.from(fee!))} ${walletTypeToCryptoCurrency(WalletType.zcash).title}';
+ }
+
+ @override
+ String fiatAmount() => _fiatAmount ?? '';
+
+ @override
+ void changeFiatAmount(final String amount) => _fiatAmount = formatAmount(amount);
+
+ String? get memo => additionalInfo['memo'] as String?;
+
+ static final Map<String, String> _destinationAddressMap = {};
+
+ static String? getCachedDestinationAddress(final String txId) {
+ printV("$txId -> ${_destinationAddressMap.keys.join(",")}");
+ return _destinationAddressMap[txId] ??
+ _destinationAddressMap['"$txId"'] ??
+ _destinationAddressMap[txId.replaceAll('"', '')];
+ }
+
+ static Future<void> addCachedDestinationAddress(final String txId, final String address) async {
+ _destinationAddressMap[txId] = address;
+ final pfwt = await pathForWalletTypeDir(type: WalletType.zcash);
+ final f = File(p.join(pfwt, "sent-tx-map.json"));
+ f.writeAsStringSync(json.encode(_destinationAddressMap));
+ }
+
+ static Future<void> init() async {
+ try {
+ final pfwt = await pathForWalletTypeDir(type: WalletType.zcash);
+ final f = File(p.join(pfwt, "sent-tx-map.json"));
+ if (!f.existsSync()) {
+ f.writeAsStringSync('{}');
+ }
+ final tmpMap = json.decode(f.readAsStringSync());
+ tmpMap.forEach((final k, final v) {
+ _destinationAddressMap[k.toString()] = v.toString();
+ });
+ } catch (e, s) {
+ printV("failed to deserialize: $e, $s");
+ }
+ }
+}
diff --git a/cw_zcash/lib/src/zcash_transactions.dart b/cw_zcash/lib/src/zcash_transactions.dart
new file mode 100644
index 0000000..afb0743
--- /dev/null
+++ b/cw_zcash/lib/src/zcash_transactions.dart
@@ -0,0 +1,24 @@
+import 'package:mobx/mobx.dart';
+import 'package:cw_core/transaction_info.dart';
+import 'package:cw_core/transaction_history.dart';
+
+class ZcashTransactionHistory extends TransactionHistoryBase<TransactionInfo> {
+ ZcashTransactionHistory() {
+ transactions = ObservableMap<String, TransactionInfo>();
+ }
+
+ @override
+ void addMany(final Map<String, TransactionInfo> txs) {
+ transactions.addAll(txs);
+ }
+
+ @override
+ void addOne(final TransactionInfo txs) {
+ transactions[txs.txHash] = txs;
+ }
+
+ @override
+ Future<void> save() async {
+ // No need to save anything, backend is taking care of that
+ }
+}
diff --git a/cw_zcash/lib/src/zcash_wallet.dart b/cw_zcash/lib/src/zcash_wallet.dart
new file mode 100644
index 0000000..b94eb83
--- /dev/null
+++ b/cw_zcash/lib/src/zcash_wallet.dart
@@ -0,0 +1,976 @@
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:cw_core/crypto_currency.dart';
+import 'package:cw_core/monero_transaction_priority.dart';
+import 'package:cw_core/node.dart';
+import 'package:cw_core/pathForWallet.dart';
+import 'package:cw_core/pending_transaction.dart';
+import 'package:cw_core/sync_status.dart';
+import 'package:cw_core/transaction_direction.dart';
+import 'package:cw_core/transaction_priority.dart';
+import 'package:cw_core/utils/print_verbose.dart';
+import 'package:cw_core/wallet_addresses.dart';
+import 'package:cw_core/wallet_base.dart';
+import 'package:cw_core/wallet_credentials.dart';
+import 'package:cw_core/wallet_info.dart';
+import 'package:cw_core/wallet_type.dart';
+import 'package:bip39/bip39.dart' as bip39;
+import 'package:cw_zcash/cw_zcash.dart';
+import 'package:cw_zcash/src/util/crc32.dart';
+import 'package:cw_zcash/src/zcash_taddress_rotation.dart';
+import 'package:cw_zcash/src/zcash_wallet_addresses.dart';
+import 'package:mobx/mobx.dart';
+import 'package:warp_api/warp_api.dart';
+import 'package:warp_api/data_fb_generated.dart';
+import 'package:flutter/services.dart';
+import 'package:mutex/mutex.dart';
+import 'package:path/path.dart' as p;
+
+part 'zcash_wallet.g.dart';
+
+class ZcashWallet = ZcashWalletBase with _$ZcashWallet;
+
+abstract class ZcashWalletBase
+ extends WalletBase<ZcashBalance, ZcashTransactionHistory, ZcashTransactionInfo>
+ with Store {
+ ZcashWalletBase(super.walletInfo, super.derivationInfo, {required this.accountId}) {
+ transactionHistory = ZcashTransactionHistory();
+ }
+
+ final int accountId;
+ static const int coin = 0; // Zcash mainnet coin ID in warp_api
+ @override
+ @observable
+ SyncStatus syncStatus = NotConnectedSyncStatus();
+
+ Timer? _syncStatusTimer;
+ Timer? _periodicSyncTimer;
+ int _initialSyncHeight = 0;
+ int _lastKnownBlockHeight = 0;
+
+ @override
+ ObservableMap<CryptoCurrency, ZcashBalance> balance = ObservableMap.of({
+ CryptoCurrency.zec: ZcashBalance(confirmed: 0, unconfirmed: 0, frozen: 0),
+ });
+
+ static int internalCalculateEstimatedFee(final TransactionPriority priority, final int? amount) {
+ const baseFee = 10000;
+ switch (priority) {
+ case MoneroTransactionPriority.slow:
+ case MoneroTransactionPriority.automatic:
+ return baseFee;
+ case MoneroTransactionPriority.medium:
+ return baseFee * 2;
+ case MoneroTransactionPriority.fast:
+ return baseFee * 4;
+ case MoneroTransactionPriority.fastest:
+ return baseFee * 10;
+ }
+ ;
+ return internalCalculateEstimatedFee(MoneroTransactionPriority.automatic, amount);
+ }
+
+ @override
+ int calculateEstimatedFee(final TransactionPriority priority, final int? amount) {
+ return internalCalculateEstimatedFee(priority, amount);
+ }
+
+ @override
+ Future<void> changePassword(final String password) async {
+ // throw UnimplementedError();
+ }
+
+ static bool isNodeWorking = true;
+
+ @override
+ Future<bool> checkNodeHealth() {
+ return Future.value(isNodeWorking);
+ }
+
+ @override
+ Future<void> close({final bool shouldCleanup = false}) async {
+ _stopSyncStatusUpdates();
+ _stopPeriodicSync();
+ await ZcashWalletService.runInDbMutex(() async => WarpApi.cancelSync());
+ }
+
+ @override
+ @action
+ Future<void> connectToNode({required final Node node}) async {
+ printV("connecting to node: ${node.uriRaw}");
+ syncStatus = ConnectingSyncStatus();
+ try {
+ String lwdUrl = node.uriRaw;
+ if (!lwdUrl.startsWith('http://') && !lwdUrl.startsWith('https://')) {
+ final protocol = node.useSSL == true ? 'https://' : 'http://';
+ lwdUrl = '$protocol$lwdUrl';
+ }
+ printV("Setting LWD URL to: $lwdUrl");
+ WarpApi.updateLWD(coin, lwdUrl);
+ syncStatus = ConnectedSyncStatus();
+ try {
+ await updateBalance();
+ await updateTransactions();
+ } catch (e) {
+ printV("Error updating balance/transactions after connect: $e");
+ }
+ } catch (e) {
+ printV("Connection error: $e");
+ syncStatus = FailedSyncStatus(error: e.toString());
+ rethrow;
+ }
+ }
+
+ @override
+ Future<PendingTransaction> createTransaction(final Object credentials) async {
+ final creds = credentials as ZcashTransactionCredentials;
+ await updateBalance();
+
+ final zcashBalance = balance[CryptoCurrency.zec];
+ final availableBalance = zcashBalance?.confirmed ?? 0;
+
+ final recipients = <Recipient>[];
+ int totalAmount = 0;
+
+ for (final output in creds.outputs) {
+ int amount;
+ if (output.sendAll) {
+ amount = availableBalance;
+ } else {
+ amount = output.formattedCryptoAmount ?? 0;
+
+ if (amount == 0 && output.cryptoAmount != null && output.cryptoAmount!.isNotEmpty) {
+ try {
+ final parsedAmount = CryptoCurrency.zec.parseAmount(
+ output.cryptoAmount!.replaceAll(',', '.'),
+ );
+ amount = parsedAmount.toInt();
+ printV("Parsed amount from cryptoAmount '${output.cryptoAmount}': $amount");
+ } catch (e) {
+ printV("Failed to parse cryptoAmount '${output.cryptoAmount}': $e");
+ }
+ }
+
+ if (amount <= 0) {
+ throw Exception(
+ 'Invalid amount for output. Amount: ${output.cryptoAmount}, Formatted: ${output.formattedCryptoAmount}',
+ );
+ }
+ }
+
+ totalAmount += amount;
+
+ var address = (output.isParsedAddress ? output.extractedAddress! : output.address).trim();
+
+ if (address.isEmpty) {
+ throw Exception('Empty address for output');
+ }
+
+ final paymentUri = WarpApi.decodePaymentURI(coin, address);
+ String memo = output.note ?? '';
+ if (paymentUri != null && paymentUri.address != null) {
+ address = paymentUri.address!;
+ if (memo.isEmpty && paymentUri.memo != null) {
+ memo = paymentUri.memo!;
+ }
+ }
+
+ if (!WarpApi.validAddress(coin, address)) {
+ throw Exception('Invalid Zcash address: $address');
+ }
+
+ int recipientPools = 7;
+
+ if (address.startsWith('t1') || address.startsWith('t3')) {
+ recipientPools = 1; // Transparent only
+ } else if (address.startsWith('zs')) {
+ recipientPools = 2; // Sapling only
+ }
+ // For unified addresses (u1...) and other types, use 7 (all pools)
+
+ final builder = RecipientObjectBuilder(
+ address: address,
+ pools: recipientPools,
+ amount: amount,
+ feeIncluded: output.sendAll,
+ replyTo: false,
+ memo: memo.isNotEmpty ? memo : null,
+ );
+
+ recipients.add(Recipient(builder.toBytes()));
+ }
+
+ if (totalAmount > availableBalance) {
+ throw Exception('Insufficient balance');
+ }
+
+ final fee = FeeT(
+ fee: internalCalculateEstimatedFee(creds.priority, null),
+ minFee: 0,
+ maxFee: 0,
+ scheme: 0, // Fixed fee scheme
+ );
+
+ // pools parameter: bitmask for which pools to use for sending
+ // 1=Transparent, 2=Sapling, 4=Orchard, 7=All pools
+ // Using 7 (all pools) allows spending from any pool type
+ final txPlan = await ZcashWalletService.runInDbMutex(
+ () => WarpApi.prepareTx(
+ coin,
+ accountId,
+ recipients,
+ 7, // pools: All pools (Transparent + Sapling + Orchard) - allows spending from any pool
+ 1, // senderUAType: 0 = unified address
+ 0, // anchorOffset
+ fee,
+ ),
+ );
+
+ return PendingZcashTransaction(
+ zcashWallet: this as ZcashWallet,
+ credentials: creds,
+ txPlan: txPlan,
+ fee: internalCalculateEstimatedFee(creds.priority, null),
+ availableBalance: availableBalance,
+ );
+ }
+
+ static const _dispPhrase = "Received to disposable address";
+ Future<List<ShieldedTx>> getShieldTxForUi() async {
+ final tx = (ZcashTaddressRotation.shieldedAccountsTx[accountId] ?? <ShieldedTx>[])
+ .map((final v) {
+ final unpacked = v.unpack();
+ unpacked.memo ??= "";
+ unpacked.memo = "${unpacked.memo}\n$_dispPhrase".trim();
+ final List<int> buff = base64.decode(
+ ZcashTaddressRotation.flatBuffersPack(unpacked.pack),
+ );
+ return ShieldedTx(buff);
+ })
+ .where((final t) => t.value > 0);
+
+ return tx.toList();
+ }
+
+ static Map<int, List<ShieldedTx>> temporarySentTx = {};
+
+ static String txChecksumKey(final ShieldedTx tx) {
+ final direction = tx.value > 0 ? TransactionDirection.incoming : TransactionDirection.outgoing;
+ return 'tx${direction}_${tx.id}_${tx.timestamp}_${CRC32.compute(tx.toString())}';
+ }
+
+ @override
+ Future<Map<String, ZcashTransactionInfo>> fetchTransactions() async {
+ await ZcashWalletService.loadShieldTxs();
+ final txs = (await ZcashWalletService.runInDbMutex(
+ () => WarpApi.getTxs(coin, accountId),
+ )).toList();
+ // ShieldedTx{id: 26, txId: 4d1be06ce2c2debec8d98ce4e9434c8aac27c980488b459017d423fdcab37f93, height: 3195705, shortTxId: 4d1be06c, timestamp: 1767730944, name: null, value: 1000000, address: null, memo: , messages: MemoVec{memos: null}}
+
+ final shieldTx = await getShieldTxForUi();
+
+ txs.addAll(shieldTx);
+ final txIds = txs.map((final tx) => tx.txId!.replaceAll('"', '')).toSet();
+ temporarySentTx[accountId]?.removeWhere(
+ (final ttx) => txIds.contains(ttx.txId!.replaceAll('"', '')),
+ );
+ txs.addAll(temporarySentTx[accountId] ?? []);
+
+ txs.sort((final a, final b) => a.height.compareTo(b.height));
+ final Map<String, ZcashTransactionInfo> result = {};
+ int currentHeight = 0;
+ try {
+ currentHeight = await WarpApi.getLatestHeight(coin);
+ } catch (e) {
+ printV("Error getting latest height: $e");
+ }
+
+ for (final tx in txs) {
+ final direction = tx.value > 0
+ ? TransactionDirection.incoming
+ : TransactionDirection.outgoing;
+
+ final confirmations = tx.height > 0 && currentHeight > 0 ? currentHeight - tx.height + 1 : 0;
+
+ final txChecksum = txChecksumKey(tx);
+ final txId = tx.txId ?? tx.shortTxId ?? txChecksum;
+
+ final txInfo = ZcashTransactionInfo(
+ id: txId.trim().replaceAll('"', ''),
+ amount: tx.value.abs(),
+ fee: 0,
+ direction: direction,
+ isPending: tx.height == 0,
+ date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000),
+ height: tx.height,
+ confirmations: confirmations,
+ to: tx.address ?? '',
+ memo: tx.memo,
+ );
+ // if (txInfo.additionalInfo['autoShield'] == true) {
+ // continue;
+ // }
+ result[txChecksum] = txInfo;
+ }
+
+ return result;
+ }
+
+ @override
+ Object get keys => {};
+
+ @override
+ String get password => _password!;
+
+ @override
+ Future<void> renameWalletFiles(final String newWalletName) async {
+ final currentWalletPath = await pathForWallet(name: name, type: type);
+ final currentCacheFile = File(currentWalletPath);
+ final newWalletPath = await pathForWallet(name: newWalletName, type: type);
+ if (currentCacheFile.existsSync()) {
+ await currentCacheFile.copy(newWalletPath);
+ }
+ Directory(currentWalletPath).deleteSync(recursive: true);
+ }
+
+ @override
+ bool get hasRescan => true;
+
+ @override
+ @action
+ Future<void> rescan({required final int height}) async {
+ try {
+ syncStatus = StartingScanSyncStatus(height);
+ printV("rescanning from: $height");
+ await ZcashWalletService.runInDbMutex(() async => WarpApi.rescanFrom(coin, height));
+ await startSync();
+ } catch (e) {
+ printV("Rescan error: $e");
+ syncStatus = FailedSyncStatus(error: e.toString());
+ rethrow;
+ }
+ }
+
+ bool _isTransactionUpdating = false;
+
+ Future<void> updateTransactions() async {
+ try {
+ if (_isTransactionUpdating) {
+ return;
+ }
+
+ _isTransactionUpdating = true;
+ final transactions = await fetchTransactions();
+
+ final currentIds = transactionHistory.transactions.keys.toSet();
+ final newIds = transactions.keys.toSet();
+
+ currentIds
+ .difference(newIds)
+ .forEach((final id) => transactionHistory.transactions.remove(id));
+
+ transactions.forEach((final key, final tx) {
+ transactionHistory.transactions[key] = tx;
+ });
+ await transactionHistory.save();
+ _isTransactionUpdating = false;
+ } catch (e, stackTrace) {
+ printV("Update transactions error: $e");
+ printV("Stack trace: $stackTrace");
+ _isTransactionUpdating = false;
+ }
+ }
+
+ Future<void> updateTransactionsHistory() => updateTransactions();
+
+ @override
+ Future<void> save() async {}
+
+ Future<void> init() async {
+ try {
+ await walletAddresses.init();
+
+ await updateBalance();
+ await updateTransactions();
+ } catch (e) {
+ printV("Wallet init error: $e");
+ }
+ }
+
+ @override
+ String? get seed {
+ try {
+ final backup = WarpApi.getBackup(coin, accountId);
+ final seed = backup.seed!.split(" ");
+ if ([13, 25].contains(seed.length)) {
+ seed.removeLast();
+ }
+ return seed.join(" ").trim();
+ } catch (e) {
+ return null;
+ }
+ }
+
+ @override
+ String? get passphrase {
+ try {
+ final backup = WarpApi.getBackup(coin, accountId);
+ final seed = backup.seed!.split(" ");
+ if ([13, 25].contains(seed.length)) {
+ final passphrase = seed.removeLast();
+ return passphrase;
+ }
+ return null;
+ } catch (e) {
+ return null;
+ }
+ }
+
+ @override
+ Future<String> signMessage(final String message, {final String? address = null}) {
+ throw UnimplementedError();
+ }
+
+ @override
+ @action
+ Future<void> startSync() async {
+ try {
+ syncStatus = AttemptingSyncStatus();
+
+ _initialSyncHeight = 0;
+ _lastKnownBlockHeight = 0;
+
+ _startSyncStatusUpdates();
+
+ syncStatus = SyncronizingSyncStatus();
+
+ unawaited(
+ _runWarpSync().catchError((final e) {
+ isNodeWorking = false;
+ printV("WarpSync error in startSync: $e");
+ syncStatus = FailedSyncStatus(error: e.toString());
+ _stopSyncStatusUpdates();
+ }),
+ );
+ } catch (e) {
+ isNodeWorking = false;
+ printV("Sync error: $e");
+ syncStatus = FailedSyncStatus(error: e.toString());
+ _stopSyncStatusUpdates();
+ rethrow;
+ }
+ }
+
+ static Mutex warpSyncMutex = Mutex();
+
+ static Future<void> initialSyncCheck() async {
+ final zcashDir = await pathForWalletTypeDir(type: WalletType.zcash);
+ final zcashInitialSync = File(p.join(zcashDir, ".initial-sync-marker"));
+ if (!zcashInitialSync.existsSync()) {
+ int chainHeight = 3000000; // fallback if node is offline
+ try {
+ chainHeight = await WarpApi.getLatestHeight(coin);
+ } catch (e) {
+ printV("Error getting latest height: $e");
+ }
+ await ZcashWalletService.runInDbMutex(
+ () async => await WarpApi.rescanFrom(coin, chainHeight - 150000),
+ );
+ zcashInitialSync.writeAsBytesSync([0x00]);
+ zcashInitialSync.writeAsStringSync(chainHeight.toString(), mode: FileMode.writeOnlyAppend);
+ }
+ }
+
+ @action
+ Future<void> _runWarpSync() async {
+ Timer? _t;
+ try {
+ await warpSyncMutex.acquire();
+ await initialSyncCheck();
+ isNodeWorking = true;
+ printV("Starting warpSync for coin $coin, account $accountId");
+ int? initialQueue = null;
+ void _cancelSyncIfShould(final Timer t) {
+ initialQueue ??= ZcashWalletService.dbMutexQueue + 2;
+ if (ZcashWalletService.dbMutexQueue <= initialQueue!) {
+ initialQueue = ZcashWalletService.dbMutexQueue;
+ return;
+ }
+ printV(
+ "Canceling sync! (ZcashWalletService.dbMutexQueue: ${ZcashWalletService.dbMutexQueue} > initialQueue: ${initialQueue})",
+ );
+ WarpApi.cancelSync();
+ t.cancel();
+ _t = null;
+ }
+
+ unawaited(
+ Future.delayed(Duration(seconds: 2)).then((_) {
+ _t = Timer.periodic(Duration(milliseconds: 100), _cancelSyncIfShould);
+ }),
+ );
+ final result = await ZcashWalletService.runInDbMutex(
+ () => WarpApi.warpSync(coin, accountId, true, 0, 1000000, 0),
+ );
+ printV("warpSync completed with result: $result");
+
+ await _updateSyncStatus();
+ } catch (e) {
+ syncStatus = FailedSyncStatus(error: e.toString());
+ unawaited(Future.delayed(Duration(seconds: 1)).then((_) => unawaited(_runWarpSync())));
+ _stopSyncStatusUpdates();
+ } finally {
+ isNodeWorking = false;
+ warpSyncMutex.release();
+ _t?.cancel();
+ }
+ }
+
+ void _startSyncStatusUpdates() {
+ _stopSyncStatusUpdates();
+ _updateSyncStatus();
+ _syncStatusTimer = Timer.periodic(const Duration(milliseconds: 5000), (_) {
+ _updateSyncStatus().catchError((final e) {
+ printV("Error in sync status update timer: $e");
+ });
+ });
+ }
+
+ void _stopSyncStatusUpdates() {
+ _syncStatusTimer?.cancel();
+ _syncStatusTimer = null;
+ }
+
+ void _startPeriodicSync() {
+ printV("_startPeriodicSync");
+ _stopPeriodicSync();
+ _periodicSyncTimer = Timer.periodic(const Duration(seconds: 5), (_) async {
+ try {
+ final chainHeight = await WarpApi.getLatestHeight(coin);
+ final dbHeight = WarpApi.getDbHeight(coin);
+ final height = dbHeight.unpack();
+ final syncHeight = height.height;
+
+ if (syncHeight < chainHeight) {
+ printV("Periodic sync: chainHeight=$chainHeight, syncHeight=$syncHeight, starting sync");
+ await _runWarpSync();
+ } else {
+ await updateBalance();
+ await updateTransactions();
+ }
+ } catch (e) {
+ printV("Periodic sync error: $e");
+ }
+ });
+ }
+
+ void _stopPeriodicSync() {
+ _periodicSyncTimer?.cancel();
+ _periodicSyncTimer = null;
+ }
+
+ @action
+ Future<void> _updateSyncStatus() async {
+ try {
+ final dbHeight = WarpApi.getDbHeight(coin);
+ final height = dbHeight.unpack();
+ final syncHeight = height.height;
+
+ final chainHeight = await WarpApi.getLatestHeight(coin);
+
+ if (_initialSyncHeight <= 0 && syncHeight > 0) {
+ _initialSyncHeight = syncHeight;
+ printV("Initialized sync height to: $_initialSyncHeight");
+ if (syncHeight - 10 > dbHeight.height) {}
+ }
+
+ if (chainHeight <= 0) {
+ if (syncStatus is! ConnectedSyncStatus && syncStatus is! ConnectingSyncStatus) {
+ syncStatus = ConnectedSyncStatus();
+ }
+ try {
+ await updateBalance();
+ await updateTransactions();
+ } catch (e) {
+ printV("Error updating balance/transactions: $e");
+ }
+ return;
+ }
+
+ if (syncHeight <= 0) {
+ if (syncStatus is! ConnectedSyncStatus &&
+ syncStatus is! ConnectingSyncStatus &&
+ syncStatus is! AttemptingSyncStatus) {
+ syncStatus = ConnectedSyncStatus();
+ }
+ try {
+ await updateBalance();
+ await updateTransactions();
+ } catch (e) {
+ printV("Error updating balance/transactions: $e");
+ }
+ return;
+ }
+
+ if (syncHeight >= chainHeight && syncHeight > 0) {
+ syncStatus = SyncedSyncStatus();
+ _stopSyncStatusUpdates();
+ await updateBalance();
+ await updateTransactions();
+ _startPeriodicSync();
+ return;
+ }
+
+ if (_lastKnownBlockHeight != syncHeight) {
+ _lastKnownBlockHeight = syncHeight;
+ }
+
+ if (syncHeight < 0 || chainHeight < syncHeight) {
+ return;
+ }
+
+ final blocksLeft = chainHeight - syncHeight;
+ if (blocksLeft <= 0) {
+ syncStatus = SyncedSyncStatus();
+ _stopSyncStatusUpdates();
+ await updateBalance();
+ await updateTransactions();
+ _startPeriodicSync();
+ return;
+ }
+
+ double ptc = 0.0;
+ if (_initialSyncHeight > 0) {
+ final track = chainHeight - _initialSyncHeight;
+ final diff = track > 0 ? track - (chainHeight - syncHeight) : 0;
+ ptc = track > 0 && diff >= 0 ? diff / track : 0.0;
+ } else {
+ ptc = syncHeight / chainHeight;
+ }
+
+ syncStatus = SyncingSyncStatus(blocksLeft, ptc.clamp(0.0, 1.0));
+
+ await updateBalance();
+ await updateTransactions();
+ } catch (e) {
+ printV("Sync status update error: $e");
+ }
+ }
+
+ String getDiversifiedAddress(final int uaType, {final DateTime? time}) {
+ try {
+ final timestamp = (time ?? DateTime.now()).millisecondsSinceEpoch ~/ 1000;
+ return WarpApi.getDiversifiedAddress(coin, accountId, uaType, timestamp);
+ } catch (e) {
+ printV("Error getting diversified address: $e");
+ return "";
+ }
+ }
+
+ static final autoShieldMutex = Mutex();
+ Future<void> _autoShield() async {
+ try {
+ await autoShieldMutex.acquire();
+ await _$autoShield();
+ } catch (e) {
+ printV("shielding failed: $e");
+ await Future.delayed(Duration(seconds: 30));
+ } finally {
+ autoShieldMutex.release();
+ }
+ }
+
+ Future<void> _$autoShield() async {
+ final chainHeight = await WarpApi.getLatestHeight(coin);
+ final dbHeight = WarpApi.getDbHeight(coin);
+ final height = dbHeight.unpack();
+ final syncHeight = height.height;
+ if (chainHeight != syncHeight) {
+ printV("Not autoshielding: chainHeight(${chainHeight}) != syncHeight(${syncHeight})");
+ return;
+ }
+ final bpConfirmed = WarpApi.getPoolBalances(coin, accountId, 0, false);
+ if (bpConfirmed.transparent + bpConfirmed.sapling <= 20000) {
+ return;
+ }
+
+ final recipientBuilder = RecipientObjectBuilder(
+ address: (walletAddresses as ZcashWalletAddresses).orchardAddress,
+ pools: 4,
+ feeIncluded: true,
+ amount: bpConfirmed.transparent + bpConfirmed.sapling,
+ );
+
+ final recipient = Recipient(recipientBuilder.toBytes());
+ final fee = FeeT(fee: 10000, minFee: 0, maxFee: 0, scheme: 0);
+ final txPlan = await ZcashWalletService.runInDbMutex(
+ () => WarpApi.prepareTx(
+ coin,
+ accountId,
+ [recipient],
+ 3, // pools: (Transparent + Sapling)
+ 1,
+ 0, // anchorOffset
+ fee,
+ ),
+ );
+ final _txId = await ZcashWalletService.runInDbMutex(
+ () => WarpApi.signAndBroadcast(ZcashWalletBase.coin, accountId, txPlan),
+ );
+ await ZcashWalletService.addShieldedTx(_txId);
+ printV("shielded: $_txId");
+ await updateTransactions();
+ await updateBalance();
+ await Future.delayed(Duration(seconds: 75 * 5)); // do not re-try doing that
+ }
+
+ @override
+ @action
+ Future<void> updateBalance() async {
+ try {
+ final poolBalances = WarpApi.getPoolBalances(coin, accountId, 0, true);
+ final balances = poolBalances.unpack();
+ // final notes = WarpApi.getNotesSync(coin, accountId);
+ // int frozenBalance = 0;
+ // for (final note in notes) {
+ // if (note.excluded) {
+ // frozenBalance += note.value;
+ // }
+ // }
+ final total = balances.orchard + balances.sapling + balances.transparent;
+ final spendable = total - balances.transparent;
+
+ final confirmedPoolBalances = WarpApi.getPoolBalances(coin, accountId, 3, true);
+ final confirmedBalances = confirmedPoolBalances.unpack();
+ final confirmedTotal =
+ confirmedBalances.orchard + confirmedBalances.sapling + confirmedBalances.transparent;
+
+ int knownOutPending = 0;
+ ZcashWalletBase.temporarySentTx[accountId]?.forEach((final sTx) {
+ knownOutPending += sTx.value; // it's negative
+ });
+ final confirmedSpendable = confirmedTotal - balances.transparent + knownOutPending;
+
+ unawaited(_autoShield());
+
+ balance[CryptoCurrency.zec] = ZcashBalance(
+ confirmed: confirmedSpendable,
+ unconfirmed: spendable - confirmedSpendable,
+ frozen: 0,
+ );
+ } catch (e, stackTrace) {
+ printV("Balance update error: $e");
+ printV("Stack trace: $stackTrace");
+ }
+ }
+
+ @override
+ Future<bool> verifyMessage(
+ final String message,
+ final String signature, {
+ final String? address = null,
+ }) {
+ throw UnimplementedError();
+ }
+
+ @observable
+ late WalletAddresses walletAddresses = ZcashWalletAddresses(accountId, walletInfo);
+
+ static Future<ZcashWallet> create(final WalletCredentials credentials) async {
+ await _init();
+ final newWalletCredentials = credentials as ZcashNewWalletCredentials;
+
+ String mnemonic;
+ if (newWalletCredentials.mnemonic != null && newWalletCredentials.mnemonic!.isNotEmpty) {
+ mnemonic = newWalletCredentials.mnemonic!;
+ } else {
+ final strength = (newWalletCredentials.seedPhraseLength == 24) ? 256 : 128;
+ mnemonic = bip39.generateMnemonic(strength: strength);
+ }
+
+ final accountId = await _restoreZcashWalletFromSeed(
+ name: credentials.name,
+ seed: mnemonic,
+ passphrase: newWalletCredentials.passphrase,
+ );
+ await _saveAccountId(credentials.name, accountId);
+ final wallet = await open(
+ name: credentials.name,
+ password: credentials.password!,
+ walletInfo: credentials.walletInfo!,
+ );
+ await wallet.walletAddresses.saveAddressesInBox();
+ return wallet;
+ }
+
+ static Future<ZcashWallet> restore(final WalletCredentials credentials) async {
+ await _init();
+ final fromSeedCredentials = credentials as ZcashFromSeedWalletCredentials;
+ final String? seed = fromSeedCredentials.seed;
+ if (seed == null || seed.isEmpty) {
+ throw Exception('Seed phrase is required for wallet restoration');
+ }
+
+ final accountId = await _restoreZcashWalletFromSeed(
+ name: credentials.name,
+ seed: seed,
+ passphrase: fromSeedCredentials.passphrase,
+ );
+ await _saveAccountId(credentials.name, accountId);
+ final wallet = await open(
+ name: credentials.name,
+ password: credentials.password!,
+ walletInfo: credentials.walletInfo!,
+ );
+ await wallet.walletAddresses.saveAddressesInBox();
+ printV("height: ${credentials.height}");
+ if (credentials.height != null) {
+ final zcashDir = await pathForWalletTypeDir(type: WalletType.zcash);
+ final zcashInitialSync = File(p.join(zcashDir, ".initial-sync-marker"));
+ zcashInitialSync.writeAsBytesSync([0x00]);
+ zcashInitialSync.writeAsStringSync(
+ credentials.height.toString(),
+ mode: FileMode.writeOnlyAppend,
+ );
+ unawaited(
+ Future.delayed(Duration(seconds: 8)).then(
+ (_) => ZcashWalletService.runInDbMutex(
+ () async => await WarpApi.rescanFrom(coin, credentials.height ?? 0),
+ ),
+ ),
+ );
+ }
+ return wallet;
+ }
+
+ static Future<ZcashWallet> open({
+ required final String name,
+ required final String password,
+ required final WalletInfo walletInfo,
+ }) async {
+ await _init();
+ if (password.isNotEmpty) {
+ WarpApi.setDbPasswd(coin, password);
+ }
+ final accountId = await getZcashAccountIdForName(name);
+ if (accountId == null) {
+ throw Exception("Wallet account not found for name: $name");
+ }
+ final wallet = ZcashWallet(
+ walletInfo,
+ await walletInfo.getDerivationInfo(),
+ accountId: accountId,
+ );
+ await wallet.walletAddresses.init();
+ return wallet;
+ }
+
+ static Future<int> _restoreZcashWalletFromSeed({
+ required final String name,
+ required String seed,
+ required String? passphrase,
+ }) async {
+ if (passphrase?.isNotEmpty == true) {
+ passphrase = passphrase!.replaceAll(" ", "_");
+ seed = "${seed} ${passphrase}";
+ }
+ final accountId = await ZcashWalletService.runInDbMutex(
+ () => WarpApi.newAccount(coin, name, seed, 0),
+ );
+ return accountId;
+ }
+
+ static Future<int?> getZcashAccountIdForName(final String name) async {
+ final wPath = await pathForWallet(name: name, type: _type);
+ final f = File(wPath);
+ if (!f.existsSync()) {
+ final accounts = WarpApi.getAccountList(coin);
+ for (final account in accounts) {
+ if (account.name == name) {
+ return account.id;
+ }
+ }
+ return null;
+ }
+ final content = f.readAsStringSync();
+ return int.tryParse(content.trim());
+ }
+
+ static Future<void> _saveAccountId(final String name, final int accountId) async {
+ final wPath = await pathForWallet(name: name, type: _type);
+ final f = File(wPath);
+ f.writeAsStringSync(accountId.toString());
+ }
+
+ static WalletType get _type => WalletType.zcash;
+
+ static Future<String> getDbDataPath() async {
+ final pathForWalletType = await pathForWalletTypeDir(type: _type);
+ final dbDataPath = "${pathForWalletType}/zec.db";
+ if (!Directory(pathForWalletType).existsSync()) {
+ Directory(pathForWalletType).createSync(recursive: true);
+ }
+ return dbDataPath;
+ }
+
+ static Future<String> getTorDir() async {
+ final pathForWalletType = await pathForWalletTypeDir(type: _type);
+ final torPath = "${pathForWalletType}/tor";
+ if (!Directory(torPath).existsSync()) {
+ Directory(torPath).createSync(recursive: true);
+ }
+ return torPath;
+ }
+
+ static Future<String> getFsBlockCacheDir() async {
+ final pathForWalletType = await pathForWalletTypeDir(type: _type);
+ final fsBlockCacheDir = "${pathForWalletType}/blockCache";
+ if (!Directory(pathForWalletType).existsSync()) {
+ Directory(pathForWalletType).createSync(recursive: true);
+ }
+ return fsBlockCacheDir;
+ }
+
+ static String? dbDataPath;
+ static bool _initialized = false;
+
+ static void unlockDatabase(final String password) {
+ _password = password;
+ }
+
+ static String? _password;
+ static Future<void> _init() async {
+ if (_initialized) return;
+ dbDataPath = await getDbDataPath();
+ printV("WarpApi.initWallet");
+ if (_password == null) {
+ throw Exception("Zcash wallet locked! Please contact support");
+ }
+ if (!File(dbDataPath!).existsSync()) {
+ //TODO(mrcyjanek): copy-encrypt
+ }
+ // coin+1 = ycash
+ WarpApi.setDbPasswd(coin, '');
+ WarpApi.setDbPasswd(coin + 1, '');
+ WarpApi.initWallet(coin, dbDataPath!);
+ WarpApi.initWallet(coin + 1, dbDataPath!);
+ try {
+ WarpApi.migrateData(coin);
+ WarpApi.migrateData(coin + 1);
+ } catch (e) {
+ printV("zec init failed: $e");
+ } // do not fail on network exception
+ final spend = await rootBundle.load('scripts/zcash_lib/assets/sapling-spend.params');
+ final output = await rootBundle.load('scripts/zcash_lib/assets/sapling-output.params');
+ WarpApi.initProver(spend.buffer.asUint8List(), output.buffer.asUint8List());
+ await ZcashTaddressRotation.init();
+ await ZcashTransactionInfo.init();
+ _initialized = true;
+ }
+
+ static Future<int> getHeightByDate(final DateTime date) {
+ return WarpApi.getBlockHeightByTime(coin, date);
+ }
+}
diff --git a/cw_zcash/lib/src/zcash_wallet_addresses.dart b/cw_zcash/lib/src/zcash_wallet_addresses.dart
new file mode 100644
index 0000000..e05ee2e
--- /dev/null
+++ b/cw_zcash/lib/src/zcash_wallet_addresses.dart
@@ -0,0 +1,223 @@
+import 'package:cw_core/utils/print_verbose.dart';
+import 'package:cw_core/wallet_addresses.dart';
+import 'package:cw_core/wallet_info.dart';
+import 'package:cw_zcash/cw_zcash.dart';
+import 'package:cw_zcash/src/zcash_taddress_rotation.dart';
+import 'package:mobx/mobx.dart';
+import 'package:warp_api/warp_api.dart';
+
+part 'zcash_wallet_addresses.g.dart';
+
+class ZcashWalletAddresses = ZcashWalletAddressesBase with _$ZcashWalletAddresses;
+
+abstract class ZcashWalletAddressesBase extends WalletAddresses with Store {
+ ZcashWalletAddressesBase(this.accountId, final WalletInfo walletInfo) : super(walletInfo) {
+ addressPageType = ZcashReceivePageOption.typeFromString(walletInfo.addressPageType ?? "");
+ }
+
+ final int accountId;
+
+ static const int transparentType = 0;
+ static const int shieldedSaplingType = 1;
+ static const int shieldedOrchardType = 2;
+ static const int unifiedType = 3;
+
+ String? _transparentAddress;
+ String? _saplingAddress;
+ String? _orchardAddress;
+ String? _unifiedAddress;
+
+ @override
+ @computed
+ String get latestAddress {
+ switch (addressPageType) {
+ case ZcashAddressType.transparent:
+ return transparentAddress;
+ case ZcashAddressType.transparentRotated:
+ // just to display something, after the wallet is synced the address
+ // will start rotating, and since we can't rotate address until the wallet syncs
+ // we can't know if some T address already got used, so we would still default to
+ // the same address.
+ // So.. we can
+ // 1) display placeholder text (looks ugly)
+ // 2) display static address
+ // 3) display first address from rotated pool which is also static until synced
+ // 2 seems like an obvious winner simply because it offers little to no benefit over 3
+ // and is noticably less complex in implementation
+ return transparentAddressRotated ?? transparentAddress;
+ case ZcashAddressType.shieldedSapling:
+ return saplingAddress;
+ case ZcashAddressType.shieldedOrchard:
+ return orchardAddress;
+ case ZcashAddressType.unifiedType:
+ if (_unifiedAddress == null) {
+ _unifiedAddress = WarpApi.getAddress(ZcashWalletBase.coin, accountId, 7);
+ }
+ return _unifiedAddress ?? "";
+ }
+ }
+
+ @observable
+ ZcashAddressType? _addressPageType;
+
+ @computed
+ ZcashAddressType get addressPageType => _addressPageType ?? ZcashAddressType.shieldedOrchard;
+
+ @computed
+ set addressPageType(final ZcashAddressType newZat) {
+ _addressPageType = newZat;
+ init();
+ address = latestAddress;
+ }
+
+ @action
+ Future<void> setAddressType(final ZcashAddressType type) async {
+ addressPageType = ZcashReceivePageOption.typeFromString(type.toString());
+ walletInfo.addressPageType = type.toString();
+ await walletInfo.save();
+ }
+
+ String get transparentAddress {
+ try {
+ if (_transparentAddress == null) {
+ _transparentAddress = WarpApi.getTAddr(ZcashWalletBase.coin, accountId);
+ }
+ return _transparentAddress ?? "";
+ } catch (e) {
+ return "";
+ }
+ }
+
+ @computed
+ String? get transparentAddressRotated {
+ try {
+ return ZcashTaddressRotation.addressForAccount(accountId);
+ } catch (e) {
+ return null;
+ }
+ }
+
+ String get saplingAddress {
+ try {
+ if (_saplingAddress == null) {
+ _saplingAddress = WarpApi.getAddress(ZcashWalletBase.coin, accountId, 2);
+ }
+ return _saplingAddress ?? "";
+ } catch (e) {
+ return "";
+ }
+ }
+
+ String get orchardAddress {
+ try {
+ if (_orchardAddress == null) {
+ _orchardAddress = WarpApi.getAddress(ZcashWalletBase.coin, accountId, 4);
+ }
+ return _orchardAddress ?? "";
+ } catch (e) {
+ return "";
+ }
+ }
+
+ @override
+ String get addressForExchange => transparentAddressRotated ?? transparentAddress;
+
+ @override
+ bool containsAddress(final String address) {
+ return this.address == address || addressesMap.values.contains(address);
+ }
+
+ static int get coin => ZcashWalletBase.coin;
+
+ @override
+ Future<void> init() async {
+ addressesMap = await walletInfo.getAddresses();
+ addressInfos = await walletInfo.getAddressInfos();
+ usedAddresses = await walletInfo.getUsedAddresses();
+ manualAddresses = await walletInfo.getManualAddresses();
+ hiddenAddresses = await walletInfo.getHiddenAddresses();
+
+ addressesMap = {"a": "b"};
+
+ await ZcashTaddressRotation.init();
+ printV(ZcashTaddressRotation.rotationAccounts.keys);
+ int accountIndex = 0;
+ addressInfos = {
+ 0:
+ ZcashTaddressRotation.allAddressesForAccount(accountId)?.map((final v) {
+ return WalletInfoAddressInfo(
+ walletInfoId: walletInfo.internalId,
+ mapKey: ++accountIndex,
+ accountIndex: 0,
+ address: v,
+ label: "",
+ );
+ }).toList() ??
+ [],
+ };
+ hiddenAddresses = ZcashTaddressRotation.allUsedAddressesForAccount(accountId)?.toSet() ?? {};
+
+ addressInfos[0]?.removeWhere((final test) => hiddenAddresses.contains(test.address));
+ if (_addressPageType == ZcashAddressType.transparentRotated) {
+ final addr = ZcashTaddressRotation.addressForAccount(accountId);
+ if (addr != null) {
+ address = addr;
+ }
+ }
+ await saveAddressesInBox();
+ }
+
+ @override
+ @observable
+ late String _address = latestAddress;
+
+ String get address {
+ if (addressPageType == ZcashAddressType.transparentRotated) {
+ return transparentAddressRotated ?? transparentAddress;
+ }
+ return _address;
+ }
+
+ void set address(final String _$address) => _address = _$address;
+
+ @override
+ String get primaryAddress => address;
+
+ @override
+ Future<void> saveAddressesInBox() async {
+ try {
+ await walletInfo.setAddresses(addressesMap);
+ await walletInfo.setAddressInfos(addressInfos);
+ await walletInfo.setUsedAddresses(usedAddresses.toList());
+ await walletInfo.setHiddenAddresses(hiddenAddresses.toList());
+ await walletInfo.setManualAddresses(manualAddresses.toList());
+ } catch (e) {
+ printV("Error saving addresses: $e");
+ }
+ }
+
+ @override
+ Future<void> updateAddressesInBox() async {
+ _transparentAddress = null;
+ _saplingAddress = null;
+ _orchardAddress = null;
+ _unifiedAddress = null;
+ }
+
+ @override
+ Map<String, String> get usableAddressesMap => addressesMap;
+
+ @override
+ Map<String, String> get usableAllAddressesMap => allAddressesMap;
+
+ List<WalletInfoAddressInfo> getAddressInfos() {
+ if (addressPageType != ZcashAddressType.transparentRotated) {
+ return [];
+ }
+ final List<WalletInfoAddressInfo> allInfos = [];
+ for (final entry in addressInfos.entries) {
+ allInfos.addAll(entry.value);
+ }
+ return allInfos;
+ }
+}
diff --git a/cw_zcash/lib/src/zcash_wallet_service.dart b/cw_zcash/lib/src/zcash_wallet_service.dart
new file mode 100644
index 0000000..ac6fab5
--- /dev/null
+++ b/cw_zcash/lib/src/zcash_wallet_service.dart
@@ -0,0 +1,181 @@
+import 'dart:io';
+
+import 'package:bip39/bip39.dart' as bip39;
+import 'package:cw_core/pathForWallet.dart';
+import 'package:cw_core/utils/print_verbose.dart';
+import 'package:cw_core/wallet_base.dart';
+import 'package:cw_core/wallet_credentials.dart';
+import 'package:cw_core/wallet_info.dart';
+import 'package:cw_core/wallet_service.dart';
+import 'package:cw_core/wallet_type.dart';
+import 'package:cw_zcash/cw_zcash.dart';
+import 'package:mutex/mutex.dart';
+import 'package:path/path.dart' as p;
+
+class ZcashWalletService
+ extends
+ WalletService<
+ ZcashNewWalletCredentials,
+ ZcashFromSeedWalletCredentials,
+ ZcashFromKeysWalletCredentials,
+ ZcashNewWalletCredentials
+ > {
+ ZcashWalletService();
+
+ static Mutex dbMutex = Mutex();
+ static int dbMutexQueue = 0;
+ static Future<T> runInDbMutex<T>(final Future<T> Function() call) async {
+ try {
+ printV("dbMutexQueue: ${dbMutexQueue++}");
+ await dbMutex.acquire();
+ return await call();
+ } finally {
+ dbMutex.release();
+ dbMutexQueue--;
+ }
+ }
+
+ static Set<String> autoshieldTx = {};
+
+ static Future<void> addShieldedTx(final String txId) async {
+ final pathForWalletType = await pathForWalletTypeDir(type: type);
+ final shieldedListFile = p.join(pathForWalletType, "autoshield_list.txt");
+ autoshieldTx.add(txId.replaceAll(RegExp(r'[^a-zA-Z0-9 ]'), ''));
+ final shieldedList = File(shieldedListFile);
+ shieldedList.writeAsStringSync(autoshieldTx.join("\n"));
+ }
+
+ static Future<void> loadShieldTxs() async {
+ try {
+ final pathForWalletType = await pathForWalletTypeDir(type: type);
+ final shieldedListFile = p.join(pathForWalletType, "autoshield_list.txt");
+ final shieldedList = File(shieldedListFile);
+ if (!shieldedList.existsSync()) {
+ return;
+ }
+ autoshieldTx = shieldedList.readAsLinesSync().toSet();
+ } catch (e) {
+ printV("loadShieldTxs failed: $e");
+ }
+ }
+
+ static bool walletFilesExist(final String path) =>
+ !File(path).existsSync() && !File('$path.keys').existsSync();
+
+ @override
+ WalletType getType() => type;
+
+ static WalletType get type => WalletType.zcash;
+
+ @override
+ Future<ZcashWallet> create(final WalletCredentials credentials, {final bool? isTestnet}) {
+ return ZcashWalletBase.create(credentials);
+ }
+
+ @override
+ Future<bool> isWalletExit(final String name) async {
+ final path = await pathForWallet(name: name, type: getType());
+ return File(path).existsSync();
+ }
+
+ @override
+ Future<ZcashWallet> openWallet(final String name, final String password) async {
+ await loadShieldTxs();
+ final walletInfo = await WalletInfo.get(name, getType());
+ if (walletInfo == null) {
+ throw Exception('Wallet not found');
+ }
+ try {
+ final wallet = await ZcashWalletBase.open(
+ name: name,
+ password: password,
+ walletInfo: walletInfo,
+ );
+ await wallet.init();
+ await saveBackup(name);
+ return wallet;
+ } catch (e) {
+ await restoreWalletFilesFromBackup(name);
+ final wallet = await ZcashWalletBase.open(
+ name: name,
+ password: password,
+ walletInfo: walletInfo,
+ );
+ await wallet.init();
+ return wallet;
+ }
+ }
+
+ @override
+ Future<void> remove(final String wallet) async {
+ final path = await pathForWalletDir(name: wallet, type: getType());
+ final file = Directory(path);
+ final isExist = file.existsSync();
+
+ if (isExist) {
+ await file.delete(recursive: true);
+ }
+
+ final walletInfo = await WalletInfo.get(wallet, getType());
+ if (walletInfo == null) {
+ throw Exception('Wallet not found');
+ }
+ await WalletInfo.delete(walletInfo);
+ }
+
+ @override
+ Future<void> rename(final String currentName, final String password, final String newName) async {
+ final currentWalletInfo = await WalletInfo.get(currentName, getType());
+ if (currentWalletInfo == null) {
+ throw Exception('Wallet not found');
+ }
+ final accountId = await ZcashWalletBase.getZcashAccountIdForName(currentName);
+ if (accountId == null) {
+ throw Exception('Wallet account not found for name: $currentName');
+ }
+ final currentWallet = ZcashWallet(
+ currentWalletInfo,
+ await currentWalletInfo.getDerivationInfo(),
+ accountId: accountId,
+ );
+
+ await currentWallet.renameWalletFiles(newName);
+
+ final newWalletInfo = currentWalletInfo;
+ newWalletInfo.id = WalletBase.idFor(newName, getType());
+ newWalletInfo.name = newName;
+
+ await newWalletInfo.save();
+ }
+
+ @override
+ Future<ZcashWallet> restoreFromKeys(
+ final ZcashFromKeysWalletCredentials credentials, {
+ final bool? isTestnet,
+ }) async {
+ throw UnimplementedError();
+ }
+
+ @override
+ Future<ZcashWallet> restoreFromSeed(
+ final ZcashFromSeedWalletCredentials credentials, {
+ final bool? isTestnet,
+ }) async {
+ if (credentials.seed == null || credentials.seed!.isEmpty) {
+ throw ZcashMnemonicIsIncorrectException();
+ }
+
+ if (!bip39.validateMnemonic(credentials.seed!)) {
+ throw ZcashMnemonicIsIncorrectException();
+ }
+
+ return ZcashWalletBase.restore(credentials);
+ }
+
+ @override
+ Future<ZcashWallet> restoreFromHardwareWallet(final ZcashNewWalletCredentials credentials) {
+ throw UnimplementedError(
+ "Restoring a Zcash wallet from a hardware wallet is not yet supported!",
+ );
+ }
+}
diff --git a/cw_zcash/pubspec.yaml b/cw_zcash/pubspec.yaml
new file mode 100644
index 0000000..70314f7
--- /dev/null
+++ b/cw_zcash/pubspec.yaml
@@ -0,0 +1,29 @@
+name: cw_zcash
+description: Zcash library for cake wallet
+version: 1.0.0
+publish_to: none
+
+environment:
+ sdk: ^3.8.0
+
+dependencies:
+ flutter:
+ sdk: flutter
+ warp_api:
+ path: ../scripts/zcash_lib/packages/warp_api_ffi
+ cw_core:
+ path: ../cw_core
+ mobx: ^2.5.0
+ bip39: ^1.0.6
+ mutex: ^3.1.0
+
+ path: any
+ flat_buffers: any
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+ build_runner: ^2.4.15
+ build_resolvers: ^2.4.4
+ mobx_codegen: ^2.0.7
+ hive_generator: ^2.0.1
+ ffigen: ^16.1.0
diff --git a/integration_test/components/common_test_flows.dart b/integration_test/components/common_test_flows.dart
index 333a659..ba42109 100644
--- a/integration_test/components/common_test_flows.dart
+++ b/integration_test/components/common_test_flows.dart
@@ -370,6 +370,8 @@ class CommonTestFlows {
return secrets.decredTestWalletSeeds;
case WalletType.dogecoin:
return secrets.dogeTestWalletSeeds;
+ case WalletType.zcash:
+ return secrets.zcashTestWalletSeeds;
case WalletType.none:
case WalletType.haven:
case WalletType.banano:
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 557e7c3..513aca5 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -139,6 +139,8 @@ PODS:
- Flutter
- wakelock_plus (0.0.1):
- Flutter
+ - warp_api (0.0.1):
+ - Flutter
- YttriumWrapper (0.8.35)
DEPENDENCIES:
@@ -179,6 +181,7 @@ DEPENDENCIES:
- universal_ble (from `.symlinks/plugins/universal_ble/darwin`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
+ - warp_api (from `.symlinks/plugins/warp_api/ios`)
SPEC REPOS:
https://github.com/CocoaPods/Specs.git:
@@ -264,6 +267,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/url_launcher_ios/ios"
wakelock_plus:
:path: ".symlinks/plugins/wakelock_plus/ios"
+ warp_api:
+ :path: ".symlinks/plugins/warp_api/ios"
SPEC CHECKSUMS:
bitbox_flutter: 506f80b961ddf646b0d80cef9f6eadaab96d91b0
@@ -309,6 +314,7 @@ SPEC CHECKSUMS:
universal_ble: cf52a7b3fd2e7c14d6d7262e9fdadb72ab6b88a6
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
wakelock_plus: 76957ab028e12bfa4e66813c99e46637f367fc7e
+ warp_api: 4a60cc00440d5eb56ae4dbf75f89a34ed90ddf1b
YttriumWrapper: 31e937fe9fbe0f1314d2ca6be9ce9b379a059966
PODFILE CHECKSUM: 5296465b1c6d14d506230356756826012f65d97a
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index c4818da..0899da3 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -25,6 +25,7 @@
A1B4A70C9CFA13AB71662216 /* LnurlPay.swift in Resources */ = {isa = PBXBuildFile; fileRef = 7D3364C03978A8A74B6D586E /* LnurlPay.swift */; settings = {ASSET_TAGS = (BreezSDK, ); }; };
A3D5E17CC53DF13FA740DEFA /* RedeemSwap.swift in Resources */ = {isa = PBXBuildFile; fileRef = 9D2F2C9F2555316C95EE7EA3 /* RedeemSwap.swift */; settings = {ASSET_TAGS = (BreezSDK, ); }; };
B6C6E59403ACDE44724C12F4 /* ServiceConfig.swift in Resources */ = {isa = PBXBuildFile; fileRef = B3D5E78267F5F18D882FDC3B /* ServiceConfig.swift */; settings = {ASSET_TAGS = (BreezSDK, ); }; };
+ CE3D5B792F142305004D767D /* WarpApiFFI.xcframework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE3D5B782F142305004D767D /* WarpApiFFI.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
CE918BF82D533ECE007F186E /* MoneroWallet.xcframework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE918BF72D533ECE007F186E /* MoneroWallet.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
CE918BFA2D533ED4007F186E /* WowneroWallet.xcframework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE918BF92D533ED4007F186E /* WowneroWallet.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
CE918BFC2D533ED8007F186E /* ZanoWallet.xcframework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE918BFB2D533ED8007F186E /* ZanoWallet.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
@@ -46,6 +47,7 @@
CE918BF82D533ECE007F186E /* MoneroWallet.xcframework in CopyFiles */,
CE918BFA2D533ED4007F186E /* WowneroWallet.xcframework in CopyFiles */,
CE918BFC2D533ED8007F186E /* ZanoWallet.xcframework in CopyFiles */,
+ CE3D5B792F142305004D767D /* WarpApiFFI.xcframework in CopyFiles */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -85,9 +87,11 @@
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>"; };
B478EFCC3C0760ED97FFE561 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
C58D93382C00FAC6004BCF69 /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = SDKROOT; };
+ CE3D5B782F142305004D767D /* WarpApiFFI.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = WarpApiFFI.xcframework; path = ../scripts/zcash_lib/WarpApiFFI.xcframework; sourceTree = "<group>"; };
CE918BF72D533ECE007F186E /* MoneroWallet.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = MoneroWallet.xcframework; sourceTree = "<group>"; };
CE918BF92D533ED4007F186E /* WowneroWallet.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = WowneroWallet.xcframework; sourceTree = "<group>"; };
CE918BFB2D533ED8007F186E /* ZanoWallet.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = ZanoWallet.xcframework; sourceTree = "<group>"; };
+ CEFC07A12EF2EEEB0012DB29 /* WarpApiFFI.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = WarpApiFFI.xcframework; path = ../../zcash_lib/WarpApiFFI.xcframework; sourceTree = "<group>"; };
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>"; };
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>"; };
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 @@
CE918BFB2D533ED8007F186E /* ZanoWallet.xcframework */,
CE918BF92D533ED4007F186E /* WowneroWallet.xcframework */,
CE918BF72D533ECE007F186E /* MoneroWallet.xcframework */,
+ CEFC07A12EF2EEEB0012DB29 /* WarpApiFFI.xcframework */,
C58D93382C00FAC6004BCF69 /* libresolv.tbd */,
0C9986A3251A932F00D566FD /* CryptoSwift.framework */,
B478EFCC3C0760ED97FFE561 /* Pods_Runner.framework */,
@@ -163,6 +168,7 @@
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
+ CE3D5B782F142305004D767D /* WarpApiFFI.xcframework */,
0C44A7182518EF4A00B570ED /* CakeWallet */,
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
diff --git a/ios/Runner/InfoBase.plist b/ios/Runner/InfoBase.plist
index 59e3bb9..3486cc4 100644
--- a/ios/Runner/InfoBase.plist
+++ b/ios/Runner/InfoBase.plist
@@ -342,6 +342,26 @@
<string>arbitrum-wallet</string>
</array>
</dict>
+ <dict>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ <key>CFBundleURLName</key>
+ <string>zcash</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>zcash</string>
+ </array>
+ </dict>
+ <dict>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ <key>CFBundleURLName</key>
+ <string>zcash-wallet</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>zcash-wallet</string>
+ </array>
+ </dict>
</array>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
diff --git a/lib/anonpay/anonpay_api.dart b/lib/anonpay/anonpay_api.dart
index 6f401ae..02877e0 100644
--- a/lib/anonpay/anonpay_api.dart
+++ b/lib/anonpay/anonpay_api.dart
@@ -35,7 +35,7 @@ class AnonPayApi {
clearnetUri: Uri.https(clearNetAuthority, "$anonPayStatus/$id"),
onionUri: Uri.https(onionApiAuthority, "$anonPayStatus/$id"),
);
-
+
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final status = responseJSON['Status'] as String;
final fiatAmount = responseJSON['Fiat_Amount'] as double?;
diff --git a/lib/buy/robinhood/robinhood_buy_provider.dart b/lib/buy/robinhood/robinhood_buy_provider.dart
index 57ce1ad..48b46c7 100644
--- a/lib/buy/robinhood/robinhood_buy_provider.dart
+++ b/lib/buy/robinhood/robinhood_buy_provider.dart
@@ -122,6 +122,7 @@ class RobinhoodBuyProvider extends BuyProvider {
case WalletType.banano:
case WalletType.wownero:
case WalletType.zano:
+ case WalletType.zcash:
case WalletType.decred:
throw Exception("Wallet Type ${wallet.type.name} is not available for Robinhood");
}
diff --git a/lib/cake_pay/src/models/cake_pay_order.dart b/lib/cake_pay/src/models/cake_pay_order.dart
index 0f70c50..d4cb37c 100644
--- a/lib/cake_pay/src/models/cake_pay_order.dart
+++ b/lib/cake_pay/src/models/cake_pay_order.dart
@@ -1,4 +1,4 @@
-enum CakePayPaymentMethod { BTC, BTC_LN, XMR, LTC, LTC_MWEB }
+enum CakePayPaymentMethod { BTC, BTC_LN, XMR, LTC, LTC_MWEB, ZEC }
extension CakePayPaymentMethodLabel on CakePayPaymentMethod {
String get label => switch (this) {
@@ -7,6 +7,7 @@ extension CakePayPaymentMethodLabel on CakePayPaymentMethod {
CakePayPaymentMethod.XMR => 'Monero',
CakePayPaymentMethod.LTC => 'Litecoin',
CakePayPaymentMethod.LTC_MWEB => 'MWEB',
+ CakePayPaymentMethod.ZEC => 'Zcash',
};
}
@@ -63,7 +64,9 @@ class CakePayOrder {
CakePayPaymentMethod.XMR => order.paymentData.xmr,
CakePayPaymentMethod.LTC => order.paymentData.ltc,
CakePayPaymentMethod.LTC_MWEB => order.paymentData.ltc_mweb,
- _ => null
+ CakePayPaymentMethod.ZEC => order.paymentData.zec,
+ // TODO: Handle this case.
+ CakePayPaymentMethod.BTC_LN => throw UnimplementedError(),
};
if (data == null) return null;
@@ -87,6 +90,7 @@ class CakePayOrder {
CakePayPaymentMethod.XMR => 'XMR',
CakePayPaymentMethod.LTC => 'LTC',
CakePayPaymentMethod.LTC_MWEB => 'LTC',
+ CakePayPaymentMethod.ZEC => 'ZEC',
};
}
}
@@ -129,6 +133,7 @@ class PaymentData {
final CryptoPaymentData? xmr;
final CryptoPaymentData? ltc;
final CryptoPaymentData? ltc_mweb;
+ final CryptoPaymentData? zec;
final DateTime invoiceTime;
final DateTime expirationTime;
final int? commission;
@@ -139,6 +144,7 @@ class PaymentData {
required this.xmr,
required this.ltc,
required this.ltc_mweb,
+ required this.zec,
required this.invoiceTime,
required this.expirationTime,
required this.commission,
@@ -151,6 +157,7 @@ class PaymentData {
xmr: map['XMR'] != null ? CryptoPaymentData.fromMap(map['XMR'] as Map<String, dynamic>) : null,
ltc: map['LTC'] != null ? CryptoPaymentData.fromMap(map['LTC'] as Map<String, dynamic>) : null,
ltc_mweb: map['LTC_MWEB'] != null ? CryptoPaymentData.fromMap(map['LTC_MWEB'] as Map<String, dynamic>) : null,
+ zec: map['ZEC_CHAIN'] != null ? CryptoPaymentData.fromMap(map['ZEC_CHAIN'] as Map<String, dynamic>) : null,
invoiceTime: DateTime.fromMillisecondsSinceEpoch(map['invoice_time'] as int),
expirationTime: DateTime.fromMillisecondsSinceEpoch(map['expiration_time'] as int),
commission: map['commission'] as int?,
diff --git a/lib/core/address_validator.dart b/lib/core/address_validator.dart
index 62c62fa..70b7dc9 100644
--- a/lib/core/address_validator.dart
+++ b/lib/core/address_validator.dart
@@ -135,10 +135,15 @@ class AddressValidator extends TextValidator {
'|[13][a-km-zA-HJ-NP-Z1-9]{25,34}';
case CryptoCurrency.hbar:
pattern = '[0-9a-zA-Z.]+';
- case CryptoCurrency.zaddr:
- pattern = 'zs[0-9a-zA-Z]{75}';
case CryptoCurrency.zec:
- pattern = 't1[0-9a-zA-Z]{33}|t3[0-9a-zA-Z]{33}';
+ pattern =
+ '(?:'
+ 't1[0-9A-Za-z]{33}'
+ '|t3[0-9A-Za-z]{33}'
+ '|zs[a-z0-9]{76}'
+ '|zxviews[a-z0-9]{278}'
+ '|u1[a-z0-9]{1,300}'
+ ')';
case CryptoCurrency.dcr:
pattern = '(D|T|S)[ksecS]([0-9a-zA-Z])+';
case CryptoCurrency.rvn:
@@ -282,8 +287,6 @@ class AddressValidator extends TextValidator {
return [34];
case CryptoCurrency.zen:
return [35];
- case CryptoCurrency.zaddr:
- return null;
case CryptoCurrency.zec:
return null;
case CryptoCurrency.kmd:
@@ -352,6 +355,15 @@ class AddressValidator extends TextValidator {
pattern = '(T|t)[1-9A-HJ-NP-Za-km-z]{33}';
case CryptoCurrency.zano:
pattern = '([1-9A-HJ-NP-Za-km-z]{90,200})|(@[\w\d.-]+)';
+ case CryptoCurrency.zec:
+ pattern =
+ '(?:'
+ 't1[0-9A-Za-z]{33}'
+ '|t3[0-9A-Za-z]{33}'
+ '|zs[a-z0-9]{76}'
+ '|zxviews[a-z0-9]{278}'
+ '|u1[a-z0-9]{1,300}'
+ ')';
default:
if (type.tag == CryptoCurrency.eth.title) {
pattern = '0x[0-9a-zA-Z]+';
diff --git a/lib/core/payment_uris.dart b/lib/core/payment_uris.dart
index 38f4f6d..74bd737 100644
--- a/lib/core/payment_uris.dart
+++ b/lib/core/payment_uris.dart
@@ -253,6 +253,21 @@ class DogeURI extends PaymentURI {
}
}
+class ZcashURI extends PaymentURI {
+ ZcashURI({required super.amount, required super.address});
+
+ @override
+ String toString() {
+ var base = 'zcash:$address';
+
+ if (amount.isNotEmpty) {
+ base += '?amount=${amount.replaceAll(',', '.')}';
+ }
+
+ return base;
+ }
+}
+
class ERC681URI extends PaymentURI {
final int chainId;
final String? contractAddress;
diff --git a/lib/core/seed_validator.dart b/lib/core/seed_validator.dart
index dae50a2..893ebfa 100644
--- a/lib/core/seed_validator.dart
+++ b/lib/core/seed_validator.dart
@@ -10,6 +10,7 @@ import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cake_wallet/zano/zano.dart';
import 'package:cake_wallet/decred/decred.dart';
import 'package:cake_wallet/utils/language_list.dart';
+import 'package:cake_wallet/zcash/zcash.dart';
import 'package:cw_core/wallet_type.dart';
class SeedValidator extends Validator<MnemonicItem> {
@@ -52,6 +53,8 @@ class SeedValidator extends Validator<MnemonicItem> {
return zano!.getWordList(language);
case WalletType.decred:
return decred!.getDecredWordList();
+ case WalletType.zcash:
+ return zcash!.getZcashWordList(language);
case WalletType.none:
case WalletType.haven:
return [];
diff --git a/lib/core/wallet_creation_service.dart b/lib/core/wallet_creation_service.dart
index 51f1b28..69e9245 100644
--- a/lib/core/wallet_creation_service.dart
+++ b/lib/core/wallet_creation_service.dart
@@ -84,6 +84,7 @@ class WalletCreationService {
case WalletType.tron:
case WalletType.dogecoin:
case WalletType.nano:
+ case WalletType.zcash:
return true;
case WalletType.monero:
case WalletType.wownero:
diff --git a/lib/di.dart b/lib/di.dart
index 31703b4..a26af47 100644
--- a/lib/di.dart
+++ b/lib/di.dart
@@ -62,6 +62,7 @@ import 'package:cake_wallet/view_model/link_view_model.dart';
import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart';
import 'package:cake_wallet/view_model/start_tor_view_model.dart';
+import 'package:cake_wallet/zcash/zcash.dart';
import 'package:cw_core/receive_page_option.dart';
import 'package:cake_wallet/entities/wallet_edit_page_arguments.dart';
import 'package:cake_wallet/entities/wallet_manager.dart';
@@ -1109,7 +1110,7 @@ Future<void> setup({
getIt.registerFactory<MoonPayProvider>(() => MoonPayProvider(
appStore: getIt.get<AppStore>(),
wallet: getIt.get<AppStore>().wallet!,
- isTestEnvironment: kDebugMode,
+ isTestEnvironment: kDebugMode || kProfileMode,
));
getIt.registerFactory<OnRamperBuyProvider>(() => OnRamperBuyProvider(
@@ -1224,6 +1225,8 @@ Future<void> setup({
return decred!.createDecredWalletService(_unspentCoinsInfoSource);
case WalletType.haven:
return HavenWalletService();
+ case WalletType.zcash:
+ return zcash!.createZcashWalletService(SettingsStoreBase.walletPasswordDirectInput);
case WalletType.none:
throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService');
}
diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart
index 9e57fd8..c176ebe 100644
--- a/lib/entities/default_settings_migration.dart
+++ b/lib/entities/default_settings_migration.dart
@@ -52,6 +52,7 @@ const decredDefaultUri = "default-spv-nodes";
const dogecoinDefaultNodeUri = 'dogecoin.stackwallet.com:50022';
const baseDefaultNodeUri = 'base.nownodes.io';
const arbitrumDefaultNodeUri = 'arbitrum.nownodes.io';
+const zcashDefaultNodeUri = 'zec-node.cakewallet.com:443';
Future<void> defaultSettingsMigration(
{required int version,
@@ -559,7 +560,14 @@ Future<void> defaultSettingsMigration(
case 54:
await _backupWowneroSeeds(havenSeedStore);
break;
-
+ case 55:
+ await addWalletNodeList(nodes: nodes, type: WalletType.zcash);
+ await _changeDefaultNode(
+ nodes: nodes,
+ sharedPreferences: sharedPreferences,
+ type: WalletType.zcash,
+ currentNodePreferenceKey: PreferencesKey.currentZcashNodeIdKey,
+ );
default:
break;
}
@@ -672,6 +680,8 @@ String _getDefaultNodeUri(WalletType type) {
return baseDefaultNodeUri;
case WalletType.arbitrum:
return arbitrumDefaultNodeUri;
+ case WalletType.zcash:
+ return zcashDefaultNodeUri;
case WalletType.banano:
case WalletType.none:
return '';
@@ -1092,6 +1102,7 @@ Future<void> checkCurrentNodes(
final currentTronNodeId = sharedPreferences.getInt(PreferencesKey.currentTronNodeIdKey);
final currentWowneroNodeId = sharedPreferences.getInt(PreferencesKey.currentWowneroNodeIdKey);
final currentZanoNodeId = sharedPreferences.getInt(PreferencesKey.currentZanoNodeIdKey);
+ final currentZcashNodeId = sharedPreferences.getInt(PreferencesKey.currentZcashNodeIdKey);
final currentMoneroNode =
nodeSource.values.firstWhereOrNull((node) => node.key == currentMoneroNodeId);
final currentBitcoinElectrumServer =
@@ -1126,6 +1137,8 @@ Future<void> checkCurrentNodes(
nodeSource.values.firstWhereOrNull((node) => node.key == currentWowneroNodeId);
final currentZanoNode =
nodeSource.values.firstWhereOrNull((node) => node.key == currentZanoNodeId);
+ final currentZcashNode =
+ nodeSource.values.firstWhereOrNull((node) => node.key == currentZcashNodeId);
if (currentMoneroNode == null) {
final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
@@ -1240,6 +1253,12 @@ Future<void> checkCurrentNodes(
await nodeSource.add(node);
await sharedPreferences.setInt(PreferencesKey.currentDecredNodeIdKey, node.key as int);
}
+
+ if (currentZcashNode == null) {
+ final node = Node(uri: zcashDefaultNodeUri, type: WalletType.zcash, useSSL: true);
+ await nodeSource.add(node);
+ await sharedPreferences.setInt(PreferencesKey.currentZcashNodeIdKey, node.key as int);
+ }
}
Future<void> resetBitcoinElectrumServer(
diff --git a/lib/entities/hash_wallet_identifier.dart b/lib/entities/hash_wallet_identifier.dart
index 175063e..204faec 100644
--- a/lib/entities/hash_wallet_identifier.dart
+++ b/lib/entities/hash_wallet_identifier.dart
@@ -2,6 +2,7 @@ import 'dart:convert';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cw_core/wallet_base.dart';
+import 'package:flutter/foundation.dart';
import 'package:hashlib/hashlib.dart';
String createHashedWalletIdentifier(WalletBase wallet) {
@@ -9,8 +10,10 @@ String createHashedWalletIdentifier(WalletBase wallet) {
final salt = secrets.walletGroupSalt;
final combined = '$salt.$hashContent';
-
final bytes = utf8.encode(combined);
final digest = sha256.convert(bytes);
+ if (kDebugMode) {
+ return "$combined;$digest";
+ }
return digest.toString();
}
diff --git a/lib/entities/node_list.dart b/lib/entities/node_list.dart
index 1b7a1e0..68a99e0 100644
--- a/lib/entities/node_list.dart
+++ b/lib/entities/node_list.dart
@@ -56,6 +56,9 @@ Future<List<Node>> loadDefaultNodes(WalletType type) async {
case WalletType.arbitrum:
path = 'assets/arbitrum_node_list.yml';
break;
+ case WalletType.zcash:
+ path = 'assets/zcash_node_list.yml';
+ break;
case WalletType.banano:
case WalletType.none:
path = '';
@@ -109,6 +112,7 @@ Future<void> resetToDefault(Box<Node> nodeSource) async {
final dogecoinElectrumServerList = await loadDefaultNodes(WalletType.dogecoin);
final baseNodes = await loadDefaultNodes(WalletType.base);
final arbitrumNodes = await loadDefaultNodes(WalletType.arbitrum);
+ final zcashNodes = await loadDefaultNodes(WalletType.zcash);
final nodes = moneroNodes +
bitcoinElectrumServerList +
@@ -124,7 +128,8 @@ Future<void> resetToDefault(Box<Node> nodeSource) async {
decredNodes +
dogecoinElectrumServerList +
baseNodes +
- arbitrumNodes;
+ arbitrumNodes +
+ zcashNodes;
await nodeSource.clear();
await nodeSource.addAll(nodes);
diff --git a/lib/entities/parse_address_from_domain.dart b/lib/entities/parse_address_from_domain.dart
index 59683ba..451a4f4 100644
--- a/lib/entities/parse_address_from_domain.dart
+++ b/lib/entities/parse_address_from_domain.dart
@@ -175,7 +175,7 @@ class AddressResolver {
var addressPattern = AddressValidator.getAddressFromStringPattern(type);
if (addressPattern == null) {
- throw Exception('Unexpected token: $type for getAddressFromStringPattern');
+ return null;
}
if (requireSurroundingWhitespaces)
diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart
index 8109c4e..8cc6be1 100644
--- a/lib/entities/preferences_key.dart
+++ b/lib/entities/preferences_key.dart
@@ -22,6 +22,7 @@ class PreferencesKey {
static const currentSolanaNodeIdKey = 'current_node_id_sol';
static const currentTronNodeIdKey = 'current_node_id_trx';
static const currentWowneroNodeIdKey = 'current_node_id_wow';
+ static const currentZcashNodeIdKey = 'current_node_id_zec';
static const currentTransactionPriorityKeyLegacy = 'current_fee_priority';
static const currentBalanceDisplayModeKey = 'current_balance_display_mode';
static const shouldSaveRecipientAddressKey = 'save_recipient_address';
diff --git a/lib/entities/priority_for_wallet_type.dart b/lib/entities/priority_for_wallet_type.dart
index d346e34..a7ae6fc 100644
--- a/lib/entities/priority_for_wallet_type.dart
+++ b/lib/entities/priority_for_wallet_type.dart
@@ -6,6 +6,7 @@ import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cake_wallet/zano/zano.dart';
import 'package:cake_wallet/decred/decred.dart';
+import 'package:cake_wallet/zcash/zcash.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/wallet_type.dart';
@@ -37,6 +38,8 @@ List<TransactionPriority> priorityForWalletType(WalletType type) {
return zano!.getTransactionPriorities();
case WalletType.decred:
return decred!.getTransactionPriorities();
+ case WalletType.zcash:
+ return zcash!.getTransactionPriorities();
case WalletType.none:
case WalletType.haven:
return [];
diff --git a/lib/exchange/provider/chainflip_exchange_provider.dart b/lib/exchange/provider/chainflip_exchange_provider.dart
index 8e2413c..2fa44a4 100644
--- a/lib/exchange/provider/chainflip_exchange_provider.dart
+++ b/lib/exchange/provider/chainflip_exchange_provider.dart
@@ -16,10 +16,8 @@ import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cake_wallet/utils/exchange_provider_logger.dart';
class ChainflipExchangeProvider extends ExchangeProvider {
- ChainflipExchangeProvider({required this.tradesStore})
- : super(pairList: supportedPairs(_notSupported));
+ ChainflipExchangeProvider({required this.tradesStore});
- static final List<CryptoCurrency> _notSupported = [];
static final List<CryptoCurrency> _supported = [
CryptoCurrency.btc,
CryptoCurrency.eth,
diff --git a/lib/exchange/provider/changenow_exchange_provider.dart b/lib/exchange/provider/changenow_exchange_provider.dart
index 31ab6d5..24f1ec8 100644
--- a/lib/exchange/provider/changenow_exchange_provider.dart
+++ b/lib/exchange/provider/changenow_exchange_provider.dart
@@ -21,13 +21,8 @@ import 'package:cake_wallet/utils/exchange_provider_logger.dart';
class ChangeNowExchangeProvider extends ExchangeProvider {
ChangeNowExchangeProvider({required SettingsStore settingsStore})
: _settingsStore = settingsStore,
- _lastUsedRateId = '',
- super(pairList: supportedPairs(_notSupported));
+ _lastUsedRateId = '';
- static const List<CryptoCurrency> _notSupported = [
- CryptoCurrency.zaddr,
- CryptoCurrency.xhv,
- ];
static final apiKey =
isMoneroOnly ? secrets.changeNowMoneroApiKey : secrets.changeNowCakeWalletApiKey;
diff --git a/lib/exchange/provider/exchange_provider.dart b/lib/exchange/provider/exchange_provider.dart
index 45c61d7..2f2bfaf 100644
--- a/lib/exchange/provider/exchange_provider.dart
+++ b/lib/exchange/provider/exchange_provider.dart
@@ -6,12 +6,10 @@ import 'package:cake_wallet/exchange/trade_request.dart';
import 'package:cw_core/crypto_currency.dart';
abstract class ExchangeProvider {
- ExchangeProvider({required this.pairList});
+ ExchangeProvider();
String get title;
- List<ExchangePair> pairList;
-
ExchangeProviderDescription get description;
bool get isAvailable;
diff --git a/lib/exchange/provider/exolix_exchange_provider.dart b/lib/exchange/provider/exolix_exchange_provider.dart
index 6065b05..b7e8df1 100644
--- a/lib/exchange/provider/exolix_exchange_provider.dart
+++ b/lib/exchange/provider/exolix_exchange_provider.dart
@@ -16,33 +16,13 @@ import 'package:cw_core/utils/print_verbose.dart';
import 'package:cake_wallet/utils/exchange_provider_logger.dart';
class ExolixExchangeProvider extends ExchangeProvider {
- ExolixExchangeProvider() : super(pairList: supportedPairs(_notSupported));
+ ExolixExchangeProvider();
static final apiKey = isMoneroOnly ? secrets.exolixMoneroApiKey : secrets.exolixCakeWalletApiKey;
static const apiBaseUrl = 'exolix.com';
static const transactionsPath = '/api/v2/transactions';
static const ratePath = '/api/v2/rate';
- static const List<CryptoCurrency> _notSupported = [
- CryptoCurrency.usdt,
- CryptoCurrency.xhv,
- CryptoCurrency.btt,
- CryptoCurrency.firo,
- CryptoCurrency.zaddr,
- CryptoCurrency.xvg,
- CryptoCurrency.kmd,
- CryptoCurrency.paxg,
- CryptoCurrency.rune,
- CryptoCurrency.scrt,
- CryptoCurrency.btcln,
- CryptoCurrency.cro,
- CryptoCurrency.ftm,
- CryptoCurrency.frax,
- CryptoCurrency.gusd,
- CryptoCurrency.gtc,
- CryptoCurrency.weth,
- ];
-
@override
String get title => 'Exolix';
diff --git a/lib/exchange/provider/letsexchange_exchange_provider.dart b/lib/exchange/provider/letsexchange_exchange_provider.dart
index b2915ee..b579123 100644
--- a/lib/exchange/provider/letsexchange_exchange_provider.dart
+++ b/lib/exchange/provider/letsexchange_exchange_provider.dart
@@ -16,9 +16,7 @@ import 'package:cw_core/utils/print_verbose.dart';
import 'package:cake_wallet/utils/exchange_provider_logger.dart';
class LetsExchangeExchangeProvider extends ExchangeProvider {
- LetsExchangeExchangeProvider() : super(pairList: supportedPairs(_notSupported));
-
- static const List<CryptoCurrency> _notSupported = [];
+ LetsExchangeExchangeProvider();
static const apiKey = secrets.letsExchangeBearerToken;
static const _baseUrl = 'api.letsexchange.io';
diff --git a/lib/exchange/provider/near_Intents_exchange_provider.dart b/lib/exchange/provider/near_Intents_exchange_provider.dart
index 71a4005..b36a420 100644
--- a/lib/exchange/provider/near_Intents_exchange_provider.dart
+++ b/lib/exchange/provider/near_Intents_exchange_provider.dart
@@ -17,10 +17,7 @@ import 'package:cw_core/utils/print_verbose.dart';
import 'package:cake_wallet/utils/exchange_provider_logger.dart';
class NearIntentsExchangeProvider extends ExchangeProvider {
- NearIntentsExchangeProvider()
- : super(pairList: supportedPairs(_notSupported));
-
- static const List<CryptoCurrency> _notSupported = [];
+ NearIntentsExchangeProvider();
static const apiKey = secrets.nearIntentsBearerToken;
static const _baseUrl = '1click.chaindefuser.com';
@@ -44,7 +41,6 @@ class NearIntentsExchangeProvider extends ExchangeProvider {
// UTXO
'LTC': 'ltc1qhdwz74m3wuuhppv2mckagqk9e2e49z5j4kucnv',
'BTC': 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080',
- 'ZZEC': 't1Zcash4hPS2bq8rTQkN9bKpZV1X4W3pZxK',
'DOGE': 'D9t7rGQ9mE3hJ2z1w8pGQxkGmKjYwYc8pQ',
'BCH': 'qpm2qsznhks23z7629mms6s4cwef74vcwvy22gdx6a',
@@ -63,6 +59,7 @@ class NearIntentsExchangeProvider extends ExchangeProvider {
'TON': 'UQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c',
'XLM': 'GA3D5O2W7YQZJQ3H4Y5QW6N7V8X9Z0A1B2C3D4E5F6G7H8I9J0',
'ADA': 'addr1vyc33hdv5vag52f3d8h0qsngu52vm27x28zkzru333jma9gaxd38v',
+ 'ZEC': 't1VdfFUbyTT7ZSdeEaHuwB7veGD1NXoUhGS',
};
String getNearDummyAddress(CryptoCurrency currency) {
@@ -430,8 +427,7 @@ class NearIntentsExchangeProvider extends ExchangeProvider {
try {
final uri = Uri.https(_baseUrl, '$_versionPath$_tokenPath');
- final response =
- await ProxyWrapper().get(clearnetUri: uri, headers: _headers);
+ final response = await ProxyWrapper().get(clearnetUri: uri, headers: _headers);
if (response.statusCode != 200) return [];
final data = json.decode(response.body) as List<dynamic>;
@@ -532,14 +528,6 @@ class NearIntentsExchangeProvider extends ExchangeProvider {
};
}
- String _normalizeTitleToNearSymbol(CryptoCurrency currency) {
- final title = currency.title.toUpperCase();
- return switch (title) {
- 'TZEC' => 'ZEC',
- _ => title,
- };
- }
-
String? _normalizeTagToNearBlockchain(String? tag) {
return switch (tag) {
'TRX' => 'tron',
@@ -559,7 +547,7 @@ class NearIntentsExchangeProvider extends ExchangeProvider {
Token? currencyToNearAssetId(CryptoCurrency currency, List<Token> supported) {
if (supported.isEmpty) return null;
- final symbol = _normalizeTitleToNearSymbol(currency).toUpperCase();
+ final symbol = currency.title.toUpperCase();
final blockchain = _normalizeTagToNearBlockchain(currency.tag);
// Native asset (no contract)
diff --git a/lib/exchange/provider/sideshift_exchange_provider.dart b/lib/exchange/provider/sideshift_exchange_provider.dart
index 629597b..3408422 100644
--- a/lib/exchange/provider/sideshift_exchange_provider.dart
+++ b/lib/exchange/provider/sideshift_exchange_provider.dart
@@ -16,23 +16,7 @@ import 'package:cw_core/utils/print_verbose.dart';
import 'package:cake_wallet/utils/exchange_provider_logger.dart';
class SideShiftExchangeProvider extends ExchangeProvider {
- SideShiftExchangeProvider() : super(pairList: supportedPairs(_notSupported));
-
- static const List<CryptoCurrency> _notSupported = [
- CryptoCurrency.xhv,
- CryptoCurrency.dcr,
- CryptoCurrency.kmd,
- CryptoCurrency.oxt,
- CryptoCurrency.pivx,
- CryptoCurrency.rune,
- CryptoCurrency.rvn,
- CryptoCurrency.scrt,
- CryptoCurrency.stx,
- CryptoCurrency.bttc,
- CryptoCurrency.usdt,
- CryptoCurrency.eos,
- CryptoCurrency.xmr,
- ];
+ SideShiftExchangeProvider();
static const affiliateId = secrets.sideShiftAffiliateId;
static const apiBaseUrl = 'https://sideshift.ai/api';
diff --git a/lib/exchange/provider/simpleswap_exchange_provider.dart b/lib/exchange/provider/simpleswap_exchange_provider.dart
index 1118e7b..3d14c69 100644
--- a/lib/exchange/provider/simpleswap_exchange_provider.dart
+++ b/lib/exchange/provider/simpleswap_exchange_provider.dart
@@ -16,12 +16,8 @@ import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/utils/exchange_provider_logger.dart';
class SimpleSwapExchangeProvider extends ExchangeProvider {
- SimpleSwapExchangeProvider() : super(pairList: supportedPairs(_notSupported));
+ SimpleSwapExchangeProvider();
- static const List<CryptoCurrency> _notSupported = [
- CryptoCurrency.zaddr,
- CryptoCurrency.xhv,
- ];
static final apiKey =
DeviceInfo.instance.isMobile ? secrets.simpleSwapApiKey : secrets.simpleSwapApiKeyDesktop;
@@ -347,8 +343,6 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
static String _normalizeCurrency(CryptoCurrency currency) {
switch (currency) {
- case CryptoCurrency.zaddr:
- return 'zec';
case CryptoCurrency.zec:
return 'zec';
case CryptoCurrency.bnb:
diff --git a/lib/exchange/provider/stealth_ex_exchange_provider.dart b/lib/exchange/provider/stealth_ex_exchange_provider.dart
index 1b9e6a4..a10affa 100644
--- a/lib/exchange/provider/stealth_ex_exchange_provider.dart
+++ b/lib/exchange/provider/stealth_ex_exchange_provider.dart
@@ -15,9 +15,7 @@ import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/utils/exchange_provider_logger.dart';
class StealthExExchangeProvider extends ExchangeProvider {
- StealthExExchangeProvider() : super(pairList: supportedPairs(_notSupported));
-
- static const List<CryptoCurrency> _notSupported = [];
+ StealthExExchangeProvider();
static final apiKey = secrets.stealthExBearerToken;
static final _additionalFeePercent = double.tryParse(secrets.stealthExAdditionalFeePercent);
diff --git a/lib/exchange/provider/swapsxyz_exchange_provider.dart b/lib/exchange/provider/swapsxyz_exchange_provider.dart
index 4b399d6..b93b236 100644
--- a/lib/exchange/provider/swapsxyz_exchange_provider.dart
+++ b/lib/exchange/provider/swapsxyz_exchange_provider.dart
@@ -15,9 +15,7 @@ import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cw_core/crypto_currency.dart';
class SwapsXyzExchangeProvider extends ExchangeProvider {
- SwapsXyzExchangeProvider() : super(pairList: supportedPairs(_notSupported));
-
- static const List<CryptoCurrency> _notSupported = [];
+ SwapsXyzExchangeProvider();
static final _apiKey = secrets.swapsXyzApiKey;
static const _baseUrl = 'api-v2.swaps.xyz';
diff --git a/lib/exchange/provider/swaptrade_exchange_provider.dart b/lib/exchange/provider/swaptrade_exchange_provider.dart
index 539c026..ff37165 100644
--- a/lib/exchange/provider/swaptrade_exchange_provider.dart
+++ b/lib/exchange/provider/swaptrade_exchange_provider.dart
@@ -16,25 +16,7 @@ import 'package:cw_core/utils/print_verbose.dart';
import 'package:cake_wallet/utils/exchange_provider_logger.dart';
class SwapTradeExchangeProvider extends ExchangeProvider {
- SwapTradeExchangeProvider() : super(pairList: supportedPairs(_notSupported));
-
- static final List<CryptoCurrency> _notSupported = [
- ...(CryptoCurrency.all
- .where((element) => ![
- CryptoCurrency.btc,
- CryptoCurrency.sol,
- CryptoCurrency.eth,
- CryptoCurrency.ltc,
- CryptoCurrency.ada,
- CryptoCurrency.bch,
- CryptoCurrency.usdterc20,
- CryptoCurrency.usdttrc20,
- CryptoCurrency.bnb,
- CryptoCurrency.xmr,
- CryptoCurrency.zec,
- ].contains(element))
- .toList())
- ];
+ SwapTradeExchangeProvider();
static final markup = secrets.swapTradeExchangeMarkup;
@@ -220,8 +202,11 @@ class SwapTradeExchangeProvider extends ExchangeProvider {
final responseBody = json.decode(response.body) as Map<String, dynamic>;
if (response.statusCode == 400 || responseBody["success"] == false) {
- final error = responseBody['errors'][0]['msg'] as String;
-
+ final List<dynamic> errorsList = responseBody['errors'] as List? ?? [];
+ final error = errorsList.isNotEmpty
+ ? (errorsList[0]['msg'] as String?) ?? responseBody.toString()
+ : responseBody.toString();
+
ExchangeProviderLogger.logError(
provider: description,
function: 'createTrade',
diff --git a/lib/exchange/provider/thorchain_exchange.provider.dart b/lib/exchange/provider/thorchain_exchange.provider.dart
index 023b021..69b11ad 100644
--- a/lib/exchange/provider/thorchain_exchange.provider.dart
+++ b/lib/exchange/provider/thorchain_exchange.provider.dart
@@ -14,26 +14,7 @@ import 'package:hive/hive.dart';
import 'package:cake_wallet/utils/exchange_provider_logger.dart';
class ThorChainExchangeProvider extends ExchangeProvider {
- ThorChainExchangeProvider({required this.tradesStore})
- : super(pairList: supportedPairs(_notSupported));
-
- static final List<CryptoCurrency> _notSupported = [
- ...(CryptoCurrency.all
- .where((element) => ![
- CryptoCurrency.btc,
- // CryptoCurrency.eth,
- CryptoCurrency.ltc,
- CryptoCurrency.bch,
- CryptoCurrency.usdtbsc,
- // CryptoCurrency.aave,
- // CryptoCurrency.dai,
- // CryptoCurrency.gusd,
- // CryptoCurrency.usdc,
- // CryptoCurrency.usdterc20,
- // CryptoCurrency.wbtc, // TODO: temporarily commented until https://github.com/cake-tech/cake_wallet/pull/1436 is merged
- ].contains(element))
- .toList())
- ];
+ ThorChainExchangeProvider({required this.tradesStore});
static final isRefundAddressSupported = [CryptoCurrency.eth];
diff --git a/lib/exchange/provider/trocador_exchange_provider.dart b/lib/exchange/provider/trocador_exchange_provider.dart
index 1b27d75..a8a13b3 100644
--- a/lib/exchange/provider/trocador_exchange_provider.dart
+++ b/lib/exchange/provider/trocador_exchange_provider.dart
@@ -17,8 +17,7 @@ import 'package:cake_wallet/utils/exchange_provider_logger.dart';
class TrocadorExchangeProvider extends ExchangeProvider {
TrocadorExchangeProvider({this.useTorOnly = false, this.providerStates = const {}})
: _lastUsedRateId = '',
- _provider = [],
- super(pairList: supportedPairs(_notSupported));
+ _provider = [];
bool useTorOnly;
Map<String, bool> providerStates;
@@ -48,11 +47,6 @@ class TrocadorExchangeProvider extends ExchangeProvider {
'SwapSpace',
];
- static const List<CryptoCurrency> _notSupported = [
- CryptoCurrency.stx,
- CryptoCurrency.zaddr,
- ];
-
static final apiKey = isMoneroOnly ? secrets.trocadorMoneroApiKey : secrets.trocadorApiKey;
static const clearNetAuthority = 'api.trocador.app';
static const onionApiAuthority = clearNetAuthority;
diff --git a/lib/exchange/provider/xoswap_exchange_provider.dart b/lib/exchange/provider/xoswap_exchange_provider.dart
index d05ed4c..80559a7 100644
--- a/lib/exchange/provider/xoswap_exchange_provider.dart
+++ b/lib/exchange/provider/xoswap_exchange_provider.dart
@@ -13,9 +13,7 @@ import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
import 'package:cake_wallet/utils/exchange_provider_logger.dart';
class XOSwapExchangeProvider extends ExchangeProvider {
- XOSwapExchangeProvider() : super(pairList: supportedPairs(_notSupported));
-
- static const List<CryptoCurrency> _notSupported = [];
+ XOSwapExchangeProvider();
static const _apiAuthority = 'exchange.exodus.io';
static const _apiPath = '/v3';
diff --git a/lib/main.dart b/lib/main.dart
index 6d96e67..f30f09e 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -34,10 +34,12 @@ import 'package:cake_wallet/utils/exception_handler.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/view_model/link_view_model.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
+import 'package:cake_wallet/zcash/zcash.dart';
import 'package:cw_core/address_info.dart';
import 'package:cw_core/cake_hive.dart';
import 'package:cw_core/erc20_token.dart';
import 'package:cw_core/hive_type_ids.dart';
+import 'package:cw_core/key.dart';
import 'package:cw_core/mweb_utxo.dart';
import 'package:cw_core/node.dart';
import 'package:cw_core/payjoin_session.dart';
@@ -119,6 +121,12 @@ Future<void> runAppWithZone({Key? topLevelKey}) async {
if (FeatureFlag.hasDevOptions) {
ProxyWrapper.logger = MemoryProxyLogger();
}
+ var zcashPassword = await secureStorageShared.read(key: "com.cakewallet.cw_zcash/zec.db");
+ if (zcashPassword == null || zcashPassword.isEmpty) {
+ zcashPassword = generateKey().substring(0,32);
+ secureStorageShared.write(key: "com.cakewallet.cw_zcash/zec.db", value: zcashPassword);
+ }
+ zcash?.unlockDatabase(zcashPassword);
// Basically when we're running a test
if (topLevelKey != null) {
@@ -266,7 +274,7 @@ Future<void> initializeAppConfigs({bool loadWallet = true}) async {
payjoinSessionSource: payjoinSessionSource,
anonpayInvoiceInfo: anonpayInvoiceInfo,
havenSeedStore: havenSeedStore,
- initialMigrationVersion: 54,
+ initialMigrationVersion: 55,
);
}
diff --git a/lib/reactions/on_wallet_sync_status_change.dart b/lib/reactions/on_wallet_sync_status_change.dart
index 6d31cb2..9380755 100644
--- a/lib/reactions/on_wallet_sync_status_change.dart
+++ b/lib/reactions/on_wallet_sync_status_change.dart
@@ -1,5 +1,7 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/store/settings_store.dart';
+import 'package:cake_wallet/di.dart';
+import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:mobx/mobx.dart';
diff --git a/lib/reactions/wallet_utils.dart b/lib/reactions/wallet_utils.dart
index 2b48a49..9dff92f 100644
--- a/lib/reactions/wallet_utils.dart
+++ b/lib/reactions/wallet_utils.dart
@@ -15,6 +15,7 @@ bool isBIP39Wallet(WalletType walletType) {
case WalletType.banano:
case WalletType.monero:
case WalletType.dogecoin:
+ case WalletType.zcash:
return true;
case WalletType.wownero:
case WalletType.haven:
diff --git a/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart b/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart
index 99cecc5..4ab6e20 100644
--- a/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart
+++ b/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart
@@ -17,6 +17,7 @@ import 'package:cake_wallet/wallet_type_utils.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
+import 'package:flutter_svg/svg.dart';
class DesktopWalletSelectionDropDown extends StatefulWidget {
final WalletListViewModel walletListViewModel;
@@ -48,6 +49,7 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
final zanoIcon = Image.asset('assets/images/crypto/zano.webp', height: 24, width: 24);
final decredIcon = Image.asset('assets/images/crypto/decred.webp', height: 24, width: 24);
final dogeIcon = Image.asset('assets/images/crypto/dogecoin.webp', height: 24, width: 24);
+ final zcashIcon = Image.asset('assets/images/crypto/zcash.webp', height: 24, width: 24);
final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
Image _newWalletImage(BuildContext context) => Image.asset(
@@ -153,7 +155,7 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
});
}
- Image _imageFor({required WalletType type, bool? isTestnet}) {
+ Widget _imageFor({required WalletType type, bool? isTestnet}) {
switch (type) {
case WalletType.bitcoin:
if (isTestnet == true) return tBitcoinIcon;
@@ -190,6 +192,8 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
return baseIcon;
case WalletType.arbitrum:
return arbitrumIcon;
+ case WalletType.zcash:
+ return zcashIcon;
case WalletType.none:
return nonWalletTypeIcon;
}
diff --git a/lib/src/screens/dashboard/desktop_widgets/dropdown_item_widget.dart b/lib/src/screens/dashboard/desktop_widgets/dropdown_item_widget.dart
index 9ceedc7..1783919 100644
--- a/lib/src/screens/dashboard/desktop_widgets/dropdown_item_widget.dart
+++ b/lib/src/screens/dashboard/desktop_widgets/dropdown_item_widget.dart
@@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
class DropDownItemWidget extends StatelessWidget {
const DropDownItemWidget({super.key, required this.title, required this.image});
final double tileHeight = 60;
- final Image image;
+ final Widget image;
final String title;
@override
diff --git a/lib/src/screens/dashboard/pages/address_page.dart b/lib/src/screens/dashboard/pages/address_page.dart
index 358b13e..561210b 100644
--- a/lib/src/screens/dashboard/pages/address_page.dart
+++ b/lib/src/screens/dashboard/pages/address_page.dart
@@ -6,6 +6,7 @@ import 'package:cake_wallet/anonpay/anonpay_donation_link_info.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
+import 'package:cake_wallet/zcash/zcash.dart';
import 'package:cw_core/receive_page_option.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/present_receive_option_picker.dart';
import 'package:cake_wallet/src/widgets/gradient_background.dart';
@@ -14,6 +15,7 @@ import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/share_util.dart';
import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
+import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
@@ -269,6 +271,10 @@ class AddressPage extends BasePage {
addressListViewModel.setAddressType(bitcoin!.getOptionToType(option));
return;
}
+ if (dashboardViewModel.type == WalletType.zcash) {
+ addressListViewModel.setAddressType(zcash!.getOptionToType(option));
+ return;
+ }
switch (option) {
case ReceivePageOption.anonPayInvoice:
@@ -313,6 +319,10 @@ class AddressPage extends BasePage {
addressListViewModel.type == WalletType.litecoin) {
addressListViewModel.setAddressType(bitcoin!.getBitcoinAddressType(option));
}
+ if (addressListViewModel.type == WalletType.zcash) {
+ printV("help me i'll kms if that wont work: ${zcash!.getZcashAddressType(option)}");
+ addressListViewModel.setAddressType(zcash!.getZcashAddressType(option));
+ }
}
});
diff --git a/lib/src/screens/dashboard/pages/transactions_page.dart b/lib/src/screens/dashboard/pages/transactions_page.dart
index 85ffbcb..8b8d153 100644
--- a/lib/src/screens/dashboard/pages/transactions_page.dart
+++ b/lib/src/screens/dashboard/pages/transactions_page.dart
@@ -104,12 +104,12 @@ class TransactionsPage extends StatelessWidget {
tags.add("MWEB");
}
}
-
return Observer(
builder: (_) => TransactionRow(
key: item.key,
onTap: () => Navigator.of(context)
.pushNamed(Routes.transactionDetails, arguments: transaction),
+ isShield: transaction.additionalInfo['autoShield'] == true,
direction: transaction.direction,
formattedDate: DateFormat('HH:mm').format(transaction.date),
formattedAmount: item.formattedCryptoAmount,
diff --git a/lib/src/screens/dashboard/widgets/menu_widget.dart b/lib/src/screens/dashboard/widgets/menu_widget.dart
index 9c67c2a..d163194 100644
--- a/lib/src/screens/dashboard/widgets/menu_widget.dart
+++ b/lib/src/screens/dashboard/widgets/menu_widget.dart
@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
+import 'package:flutter_svg/svg.dart';
class MenuWidget extends StatefulWidget {
MenuWidget(this.dashboardViewModel, Key? key);
@@ -40,7 +41,8 @@ class MenuWidgetState extends State<MenuWidget> {
this.wowneroIcon = Image.asset('assets/images/crypto/wownero.webp'),
this.zanoIcon = Image.asset('assets/images/crypto/zano.webp'),
this.decredIcon = Image.asset('assets/images/crypto/decred.webp'),
- this.dogecoinIcon = Image.asset('assets/images/crypto/dogecoin.webp');
+ this.dogecoinIcon = Image.asset('assets/images/crypto/dogecoin.webp'),
+ this.zcashIcon = Image.asset('assets/images/crypto/zcash.webp');
final largeScreen = 731;
@@ -70,6 +72,7 @@ class MenuWidgetState extends State<MenuWidget> {
Image zanoIcon;
Image decredIcon;
Image dogecoinIcon;
+ Image zcashIcon;
@override
void initState() {
@@ -229,7 +232,7 @@ class MenuWidgetState extends State<MenuWidget> {
);
}
- Image _iconFor({required WalletType type}) {
+ Widget _iconFor({required WalletType type}) {
switch (type) {
case WalletType.monero:
return moneroIcon;
@@ -265,6 +268,8 @@ class MenuWidgetState extends State<MenuWidget> {
return decredIcon;
case WalletType.dogecoin:
return dogecoinIcon;
+ case WalletType.zcash:
+ return zcashIcon;
default:
throw Exception('No icon for ${type.toString()}');
}
diff --git a/lib/src/screens/dashboard/widgets/transaction_raw.dart b/lib/src/screens/dashboard/widgets/transaction_raw.dart
index ed8ccd3..7abb0f4 100644
--- a/lib/src/screens/dashboard/widgets/transaction_raw.dart
+++ b/lib/src/screens/dashboard/widgets/transaction_raw.dart
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:cw_core/transaction_direction.dart';
+import 'package:flutter_svg/svg.dart';
class TransactionRow extends StatelessWidget {
TransactionRow({
@@ -10,6 +11,7 @@ class TransactionRow extends StatelessWidget {
required this.tags,
required this.title,
required this.onTap,
+ this.isShield = false,
super.key,
});
@@ -20,6 +22,7 @@ class TransactionRow extends StatelessWidget {
final String formattedFiatAmount;
final String title;
final List<String> tags;
+ final bool isShield;
@override
Widget build(BuildContext context) {
@@ -39,7 +42,11 @@ class TransactionRow extends StatelessWidget {
shape: BoxShape.circle,
color: Theme.of(context).colorScheme.surfaceContainerHighest,
),
- child: Image.asset(direction == TransactionDirection.incoming
+ child: (isShield) ? Padding(
+ padding: const EdgeInsets.all(8.0),
+ child: SvgPicture.asset('assets/images/tx_shield.svg'),
+ ) :
+ Image.asset(direction == TransactionDirection.incoming
? 'assets/images/down_arrow.png'
: 'assets/images/up_arrow.png'),
),
diff --git a/lib/src/screens/restore/wallet_restore_from_seed_form.dart b/lib/src/screens/restore/wallet_restore_from_seed_form.dart
index 426ea66..166a46c 100644
--- a/lib/src/screens/restore/wallet_restore_from_seed_form.dart
+++ b/lib/src/screens/restore/wallet_restore_from_seed_form.dart
@@ -286,7 +286,7 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
'wallet_restore_from_seed_blockheight_textfield_key',
),
onHeightOrDateEntered: widget.onHeightOrDateEntered,
- hasDatePicker: [WalletType.monero, WalletType.wownero].contains(
+ hasDatePicker: [WalletType.monero, WalletType.wownero, WalletType.zcash].contains(
widget.type,
),
walletType: widget.type,
diff --git a/lib/src/screens/restore/wallet_restore_page.dart b/lib/src/screens/restore/wallet_restore_page.dart
index d673319..14e2d5a 100644
--- a/lib/src/screens/restore/wallet_restore_page.dart
+++ b/lib/src/screens/restore/wallet_restore_page.dart
@@ -146,7 +146,7 @@ class WalletRestorePage extends BasePage {
bottom: MediaQuery.of(bottomSheetContext).viewInsets.bottom,
),
child: AddPassphraseBottomSheet(
- titleText: S.of(context).add_passphrase,
+ titleText: S.of(bottomSheetContext).add_passphrase,
onRestoreButtonPressed: (passphrase) async {
await _onPassphraseBottomSheetRestoreButtonPressed(
passphrase,
diff --git a/lib/src/screens/settings/other_settings_page.dart b/lib/src/screens/settings/other_settings_page.dart
index 1c39d8f..850f566 100644
--- a/lib/src/screens/settings/other_settings_page.dart
+++ b/lib/src/screens/settings/other_settings_page.dart
@@ -10,11 +10,16 @@ import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.da
import 'package:cake_wallet/src/screens/settings/widgets/settings_version_cell.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart';
+import 'package:cw_core/node.dart';
+import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_core/db/sqlite.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
+import 'package:get_it/get_it.dart';
+import 'package:hive/hive.dart';
+import 'package:path/path.dart' as p;
class OtherSettingsPage extends BasePage {
OtherSettingsPage(this._otherSettingsViewModel) {
diff --git a/lib/src/widgets/blockchain_height_widget.dart b/lib/src/widgets/blockchain_height_widget.dart
index a835048..6473891 100644
--- a/lib/src/widgets/blockchain_height_widget.dart
+++ b/lib/src/widgets/blockchain_height_widget.dart
@@ -2,6 +2,7 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/src/widgets/standard_switch.dart';
import 'package:cake_wallet/utils/date_picker.dart';
import 'package:cake_wallet/wownero/wownero.dart';
+import 'package:cake_wallet/zcash/zcash.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
@@ -189,10 +190,12 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
height = decred!.heightByDate(date);
} else if (widget.walletType == WalletType.monero) {
height = monero!.getHeightByDate(date: date);
- } else {
- assert(widget.walletType == WalletType.wownero,
- "unknown currency in BlockchainHeightWidget");
+ } else if (widget.walletType == WalletType.wownero){
height = wownero!.getHeightByDate(date: date);
+ } else if (widget.walletType == WalletType.zcash) {
+ height = await zcash!.getHeightByDate(date);
+ } else {
+ throw Exception("unknown currency in BlockchainHeightWidget");
}
}
if (mounted) {
diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart
index bac343c..0a57047 100644
--- a/lib/store/settings_store.dart
+++ b/lib/store/settings_store.dart
@@ -964,6 +964,10 @@ abstract class SettingsStoreBase with Store {
if (node == null) {
throw Exception('No node found for wallet type: ${walletType.toString()}');
}
+ if (node.type != walletType) {
+ throw Exception("Node is not valid for $walletType (found: ${node.type}");
+ }
+
return node;
}
@@ -1202,6 +1206,7 @@ abstract class SettingsStoreBase with Store {
final tronNodeId = sharedPreferences.getInt(PreferencesKey.currentTronNodeIdKey);
final wowneroNodeId = sharedPreferences.getInt(PreferencesKey.currentWowneroNodeIdKey);
final zanoNodeId = sharedPreferences.getInt(PreferencesKey.currentZanoNodeIdKey);
+ final zcashNodeId = sharedPreferences.getInt(PreferencesKey.currentZcashNodeIdKey);
final decredNodeId = sharedPreferences.getInt(PreferencesKey.currentDecredNodeIdKey);
final dogecoinNodeId = sharedPreferences.getInt(PreferencesKey.currentDogecoinNodeIdKey);
@@ -1236,6 +1241,8 @@ abstract class SettingsStoreBase with Store {
nodeSource.values.firstWhereOrNull((e) => e.uriRaw == wowneroDefaultNodeUri);
final zanoNode = nodeSource.get(zanoNodeId) ??
nodeSource.values.firstWhereOrNull((e) => e.uriRaw == zanoDefaultNodeUri);
+ final zcashNode = nodeSource.get(zcashNodeId) ??
+ nodeSource.values.firstWhereOrNull((e) => e.uriRaw == zcashDefaultNodeUri);
final dogecoinNode = nodeSource.get(dogecoinNodeId) ??
nodeSource.values.firstWhereOrNull((e) => e.uriRaw == dogecoinDefaultNodeUri);
@@ -1330,6 +1337,10 @@ abstract class SettingsStoreBase with Store {
nodes[WalletType.zano] = zanoNode;
}
+ if (zcashNode != null) {
+ nodes[WalletType.zcash] = zcashNode;
+ }
+
if (decredNode != null) {
nodes[WalletType.decred] = decredNode;
}
@@ -1738,6 +1749,7 @@ abstract class SettingsStoreBase with Store {
final tronNodeId = sharedPreferences.getInt(PreferencesKey.currentTronNodeIdKey);
final wowneroNodeId = sharedPreferences.getInt(PreferencesKey.currentWowneroNodeIdKey);
final zanoNodeId = sharedPreferences.getInt(PreferencesKey.currentZanoNodeIdKey);
+ final zcashNodeId = sharedPreferences.getInt(PreferencesKey.currentZcashNodeIdKey);
final decredNodeId = sharedPreferences.getInt(PreferencesKey.currentDecredNodeIdKey);
final dogecoinNodeId = sharedPreferences.getInt(PreferencesKey.currentDogecoinNodeIdKey);
final moneroNode = nodeSource.get(nodeId);
@@ -1754,6 +1766,7 @@ abstract class SettingsStoreBase with Store {
final tronNode = nodeSource.get(tronNodeId);
final wowneroNode = nodeSource.get(wowneroNodeId);
final zanoNode = nodeSource.get(zanoNodeId);
+ final zcashNode = nodeSource.get(zcashNodeId);
final decredNode = nodeSource.get(decredNodeId);
final dogecoinNode = nodeSource.get(dogecoinNodeId);
@@ -1813,6 +1826,10 @@ abstract class SettingsStoreBase with Store {
nodes[WalletType.zano] = zanoNode;
}
+ if (zcashNode != null) {
+ nodes[WalletType.zcash] = zcashNode;
+ }
+
if (decredNode != null) {
nodes[WalletType.decred] = decredNode;
}
@@ -1969,8 +1986,13 @@ abstract class SettingsStoreBase with Store {
case WalletType.dogecoin:
await _sharedPreferences.setInt(PreferencesKey.currentDogecoinNodeIdKey, node.key as int);
break;
- default:
+ case WalletType.zcash:
+ await _sharedPreferences.setInt(PreferencesKey.currentZcashNodeIdKey, node.key as int);
break;
+ case WalletType.none:
+ throw UnimplementedError();
+ case WalletType.banano:
+ throw UnimplementedError();
}
nodes[walletType] = node;
diff --git a/lib/utils/exception_handler.dart b/lib/utils/exception_handler.dart
index 67ead27..1b32746 100644
--- a/lib/utils/exception_handler.dart
+++ b/lib/utils/exception_handler.dart
@@ -22,7 +22,7 @@ import 'package:shared_preferences/shared_preferences.dart';
class ExceptionHandler {
static bool _hasError = false;
static const _coolDownDurationInDays =
- bool.fromEnvironment('hasDevOptions', defaultValue: kDebugMode) ? 0 : 7;
+ bool.fromEnvironment('hasDevOptions', defaultValue: kDebugMode || kProfileMode) ? 0 : 7;
static File? _file;
static Future<void> _saveException(String? error, StackTrace? stackTrace,
@@ -118,7 +118,7 @@ class ExceptionHandler {
if (kDebugMode || kProfileMode) {
FlutterError.presentError(errorDetails);
- printV(errorDetails.toString());
+ errorDetails.toString().split("\n").forEach(printV);
return;
}
@@ -198,6 +198,7 @@ class ExceptionHandler {
'Exception: 6e00',
'Exception: 6985',
'Exception: 5515',
+ 'transport error'
];
static bool isLedgerError(Object exception) =>
diff --git a/lib/utils/feature_flag.dart b/lib/utils/feature_flag.dart
index 81329cb..340eb0d 100644
--- a/lib/utils/feature_flag.dart
+++ b/lib/utils/feature_flag.dart
@@ -9,8 +9,8 @@ class FeatureFlag {
static const bool isExolixEnabled = true;
static const bool isBackgroundSyncEnabled = true;
static bool get isInAppTorEnabled => CakeTor.instance is! CakeTorDisabled;
- static const int verificationWordsCount = kDebugMode ? 0 : 2;
- static const bool hasDevOptions = bool.fromEnvironment('hasDevOptions', defaultValue: kDebugMode);
+ static const int verificationWordsCount = kDebugMode || kProfileMode ? 0 : 2;
+ static const bool hasDevOptions = bool.fromEnvironment('hasDevOptions', defaultValue: kDebugMode || kProfileMode);
static const bool hasBitcoinViewOnly = true;
static const bool customBackgroundEnabled = false;
static const bool duressPinEnabled = true;
diff --git a/lib/utils/qr_util.dart b/lib/utils/qr_util.dart
index 36ef209..0762395 100644
--- a/lib/utils/qr_util.dart
+++ b/lib/utils/qr_util.dart
@@ -41,6 +41,8 @@ String getQrImage(WalletType type, {int? selectedChainId}) {
return 'assets/images/dcr_chain_qr.svg';
case WalletType.dogecoin:
return 'assets/images/doge_chain_qr.svg';
+ case WalletType.zcash:
+ return 'assets/images/zec_icon_qr.svg';
case WalletType.banano:
case WalletType.haven:
case WalletType.none:
diff --git a/lib/view_model/advanced_privacy_settings_view_model.dart b/lib/view_model/advanced_privacy_settings_view_model.dart
index c946d2e..6a491f9 100644
--- a/lib/view_model/advanced_privacy_settings_view_model.dart
+++ b/lib/view_model/advanced_privacy_settings_view_model.dart
@@ -57,6 +57,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
case WalletType.arbitrum:
case WalletType.solana:
case WalletType.tron:
+ case WalletType.zcash:
return true;
case WalletType.bitcoin:
@@ -103,6 +104,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
WalletType.wownero,
WalletType.zano,
WalletType.dogecoin,
+ WalletType.zcash,
].contains(type);
@computed
diff --git a/lib/view_model/cake_pay/cake_pay_buy_card_view_model.dart b/lib/view_model/cake_pay/cake_pay_buy_card_view_model.dart
index 2f4ccc5..8148e64 100644
--- a/lib/view_model/cake_pay/cake_pay_buy_card_view_model.dart
+++ b/lib/view_model/cake_pay/cake_pay_buy_card_view_model.dart
@@ -115,6 +115,8 @@ abstract class CakePayBuyCardViewModelBase with Store {
];
case WalletType.monero:
return [CakePayPaymentMethod.XMR];
+ case WalletType.zcash:
+ return [CakePayPaymentMethod.ZEC];
default:
return const [];
}
@@ -139,11 +141,14 @@ abstract class CakePayBuyCardViewModelBase with Store {
@action
Future<void> createOrder() async {
- if (walletType != WalletType.bitcoin &&
- walletType != WalletType.monero &&
- walletType != WalletType.litecoin) {
+ if (![
+ WalletType.bitcoin,
+ WalletType.monero,
+ WalletType.litecoin,
+ WalletType.zcash,
+ ].contains(walletType)) {
sendViewModel.state =
- FailureState('Unsupported wallet type, please use Bitcoin, Monero, or Litecoin.');
+ FailureState('Unsupported wallet type, please use Bitcoin, Monero, Litecoin or Zcash.');
}
try {
order = await _cakePayService.createOrder(
diff --git a/lib/view_model/dashboard/balance_view_model.dart b/lib/view_model/dashboard/balance_view_model.dart
index 88b651e..3698791 100644
--- a/lib/view_model/dashboard/balance_view_model.dart
+++ b/lib/view_model/dashboard/balance_view_model.dart
@@ -320,6 +320,7 @@ abstract class BalanceViewModelBase with Store {
case WalletType.wownero:
case WalletType.zano:
case WalletType.decred:
+ case WalletType.zcash:
return true;
default:
return false;
diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart
index 671c20f..d01c39f 100644
--- a/lib/view_model/dashboard/dashboard_view_model.dart
+++ b/lib/view_model/dashboard/dashboard_view_model.dart
@@ -957,6 +957,7 @@ abstract class DashboardViewModelBase with Store {
return true;
case WalletType.zano:
case WalletType.haven:
+ case WalletType.zcash:
case WalletType.none:
return false;
}
@@ -1284,6 +1285,7 @@ abstract class DashboardViewModelBase with Store {
if (wallet.chainId == 1 && tx.evmSignatureName == 'approval')
return ' (${tx.evmSignatureName})';
+
return '';
}
diff --git a/lib/view_model/dashboard/home_settings_view_model.dart b/lib/view_model/dashboard/home_settings_view_model.dart
index 0207336..dc6d54d 100644
--- a/lib/view_model/dashboard/home_settings_view_model.dart
+++ b/lib/view_model/dashboard/home_settings_view_model.dart
@@ -231,6 +231,7 @@ abstract class HomeSettingsViewModelBase with Store {
case WalletType.bitcoinCash:
case WalletType.decred:
case WalletType.dogecoin:
+ case WalletType.zcash:
return false;
}
diff --git a/lib/view_model/dashboard/receive_option_view_model.dart b/lib/view_model/dashboard/receive_option_view_model.dart
index 8aab273..c3d4f20 100644
--- a/lib/view_model/dashboard/receive_option_view_model.dart
+++ b/lib/view_model/dashboard/receive_option_view_model.dart
@@ -1,4 +1,5 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
+import 'package:cake_wallet/zcash/zcash.dart';
import 'package:cw_core/receive_page_option.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
@@ -16,7 +17,9 @@ abstract class ReceiveOptionViewModelBase with Store {
? bitcoin!.getSelectedAddressType(_wallet)
: (_wallet.type == WalletType.decred && _wallet.isTestnet)
? ReceivePageOption.testnet
- : ReceivePageOption.mainnet),
+ : _wallet.type == WalletType.zcash
+ ? zcash!.getSelectedAddressType(_wallet)
+ : ReceivePageOption.mainnet),
_options = [] {
final walletType = _wallet.type;
switch (walletType) {
@@ -46,6 +49,12 @@ abstract class ReceiveOptionViewModelBase with Store {
_options = ReceivePageOptions;
}
break;
+ case WalletType.zcash:
+ _options = [
+ ...zcash!.getZcashReceivePageOptions(_wallet),
+ ...ReceivePageOptions.where((element) => element != ReceivePageOption.mainnet)
+ ];
+ break;
default:
_options = ReceivePageOptions;
}
diff --git a/lib/view_model/dashboard/transaction_list_item.dart b/lib/view_model/dashboard/transaction_list_item.dart
index da86968..8aef7a8 100644
--- a/lib/view_model/dashboard/transaction_list_item.dart
+++ b/lib/view_model/dashboard/transaction_list_item.dart
@@ -9,6 +9,7 @@ import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cake_wallet/zano/zano.dart';
+import 'package:cake_wallet/zcash/zcash.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_info.dart';
@@ -52,6 +53,9 @@ class TransactionListItem extends ActionListItem with Keyable {
}
String get formattedTitle {
+ if (transaction.additionalInfo['autoShield'] == true) {
+ return "Autoshield";
+ }
if (transaction.direction == TransactionDirection.incoming) {
return S.current.received;
}
@@ -217,6 +221,11 @@ class TransactionListItem extends ActionListItem with Keyable {
cryptoAmount: decred!.formatterDecredAmountToDouble(amount: transaction.amount),
price: price);
break;
+ case WalletType.zcash:
+ amount = calculateFiatAmountRaw(
+ cryptoAmount: zcash!.formatterZcashAmountToDouble(amount: BigInt.from(transaction.amount)),
+ price: price);
+
case WalletType.none:
case WalletType.banano:
case WalletType.haven:
diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart
index 58b4915..ac6ba2d 100644
--- a/lib/view_model/exchange/exchange_view_model.dart
+++ b/lib/view_model/exchange/exchange_view_model.dart
@@ -922,6 +922,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
depositCurrency = CryptoCurrency.dcr;
receiveCurrency = CryptoCurrency.xmr;
break;
+ case WalletType.zcash:
+ depositCurrency = CryptoCurrency.zec;
+ receiveCurrency = CryptoCurrency.xmr;
+ break;
case WalletType.none:
break;
}
diff --git a/lib/view_model/node_list/node_create_or_edit_view_model.dart b/lib/view_model/node_list/node_create_or_edit_view_model.dart
index dffa690..c833b37 100644
--- a/lib/view_model/node_list/node_create_or_edit_view_model.dart
+++ b/lib/view_model/node_list/node_create_or_edit_view_model.dart
@@ -97,6 +97,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
case WalletType.dogecoin:
case WalletType.zano:
case WalletType.decred:
+ case WalletType.zcash:
return false;
}
}
diff --git a/lib/view_model/send/fees_view_model.dart b/lib/view_model/send/fees_view_model.dart
index 588bdc5..6bda339 100644
--- a/lib/view_model/send/fees_view_model.dart
+++ b/lib/view_model/send/fees_view_model.dart
@@ -5,6 +5,7 @@ import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
import 'package:cake_wallet/evm/evm.dart';
import 'package:cake_wallet/monero/monero.dart';
+import 'package:cake_wallet/zcash/zcash.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
import 'package:cw_core/crypto_currency.dart';
@@ -102,6 +103,7 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor
case WalletType.solana:
case WalletType.tron:
case WalletType.arbitrum:
+ case WalletType.zcash:
return false;
}
}
@@ -185,6 +187,9 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor
case WalletType.zano:
_settingsStore.setPriority(wallet.type, monero!.getMoneroTransactionPriorityAutomatic());
break;
+ case WalletType.zcash:
+ _settingsStore.setPriority[wallet.type] = zcash!.getZcashTransactionPriorityAutomatic();
+ break;
case WalletType.bitcoin:
_settingsStore.setPriority(wallet.type, bitcoin!.getBitcoinTransactionPriorityMedium());
break;
diff --git a/lib/view_model/send/output.dart b/lib/view_model/send/output.dart
index 2330115..16864d4 100644
--- a/lib/view_model/send/output.dart
+++ b/lib/view_model/send/output.dart
@@ -18,6 +18,7 @@ import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cake_wallet/zano/zano.dart';
+import 'package:cake_wallet/zcash/zcash.dart';
import 'package:cw_core/balance.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/format_fixed.dart';
@@ -125,6 +126,9 @@ abstract class OutputBase with Store {
_amount = zano!
.formatterParseAmount(amount: _cryptoAmount, currency: cryptoCurrencyHandler());
break;
+ case WalletType.zcash:
+ _amount = zcash!.formatterZcashParseAmount(_cryptoAmount);
+ break;
case WalletType.none:
case WalletType.haven:
case WalletType.nano:
@@ -204,6 +208,10 @@ abstract class OutputBase with Store {
estimatedFee = tron!.getTronTRC20EstimatedFee(_wallet).toString();
}
break;
+
+ case WalletType.zcash:
+ estimatedFee = zcash!.formatterZcashAmountToDouble(amount: BigInt.from(fee)).toString();
+ break;
/// EVMs
case WalletType.ethereum:
diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart
index 55a604b..a469758 100644
--- a/lib/view_model/send/send_view_model.dart
+++ b/lib/view_model/send/send_view_model.dart
@@ -45,6 +45,7 @@ import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart';
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cake_wallet/zano/zano.dart';
+import 'package:cake_wallet/zcash/zcash.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/erc20_token.dart';
import 'package:cw_core/exceptions.dart';
@@ -803,8 +804,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
wallet.type != WalletType.banano &&
wallet.type != WalletType.solana &&
wallet.type != WalletType.tron &&
- wallet.type != WalletType.arbitrum) {
- // Wallet type is generic for all EVM chains, so we need to check the chainId
+ wallet.type != WalletType.arbitrum &&
+ wallet.type != WalletType.zcash) {
throw Exception('Priority is null for wallet type: ${wallet.type}');
}
@@ -861,6 +862,12 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
case WalletType.decred:
this.coinTypeToSpendFrom = UnspentCoinType.any;
return decred!.createDecredTransactionCredentials(outputs, priority!);
+ case WalletType.zcash:
+ return zcash!.createZcashTransactionCredentials(
+ outputs,
+ currency: selectedCryptoCurrency,
+ // priority: priority,
+ );
default:
throw Exception('Unexpected wallet type: ${wallet.type}');
}
diff --git a/lib/view_model/settings/other_settings_view_model.dart b/lib/view_model/settings/other_settings_view_model.dart
index 3977d4f..39c23f3 100644
--- a/lib/view_model/settings/other_settings_view_model.dart
+++ b/lib/view_model/settings/other_settings_view_model.dart
@@ -1,5 +1,6 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
+import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/utils/package_info.dart';
import 'package:cake_wallet/view_model/send/send_view_model.dart';
diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart
index 489057f..c5e4943 100644
--- a/lib/view_model/transaction_details_view_model.dart
+++ b/lib/view_model/transaction_details_view_model.dart
@@ -95,6 +95,8 @@ abstract class TransactionDetailsViewModelBase with Store {
case WalletType.dogecoin:
_addDogecoinListItems(tx, dateFormat);
break;
+ case WalletType.zcash:
+ _addZcashListItems(tx, dateFormat);
case WalletType.none:
case WalletType.banano:
break;
@@ -211,7 +213,8 @@ abstract class TransactionDetailsViewModelBase with Store {
return 'https://${wallet.isTestnet ? "testnet" : "dcrdata"}.decred.org/tx/${txId.split(':')[0]}';
case WalletType.dogecoin:
return 'https://blockchair.com/dogecoin/transaction/${txId}';
-
+ case WalletType.zcash:
+ return 'https://blockchair.com/zcash/transaction/${txId}';
case WalletType.none:
return '';
}
@@ -257,6 +260,8 @@ abstract class TransactionDetailsViewModelBase with Store {
return S.current.view_transaction_on + 'basescan.org';
case WalletType.arbitrum:
return S.current.view_transaction_on + 'arbiscan.io';
+ case WalletType.zcash:
+ return S.current.view_transaction_on + 'blockchair.com';
case WalletType.none:
return '';
}
@@ -755,6 +760,57 @@ abstract class TransactionDetailsViewModelBase with Store {
items.addAll(_items);
}
+ void _addZcashListItems(TransactionInfo tx, DateFormat dateFormat) {
+ final memo = tx.additionalInfo['memo'] as String?;
+
+ final _items = [
+ StandartListItem(
+ title: S.current.transaction_details_transaction_id,
+ value: tx.txHash,
+ key: ValueKey('standard_list_item_transaction_details_id_key'),
+ ),
+ if (tx.to != null)
+ StandartListItem(
+ title: S.current.transaction_details_recipient_address,
+ value: tx.to!,
+ key: ValueKey('standard_list_item_transaction_details_recipient_address_key'),
+ ),
+ StandartListItem(
+ title: S.current.transaction_details_date,
+ value: dateFormat.format(tx.date),
+ key: ValueKey('standard_list_item_transaction_details_date_key'),
+ ),
+ StandartListItem(
+ title: S.current.confirmations,
+ value: tx.confirmations.toString(),
+ key: ValueKey('standard_list_item_transaction_confirmations_key'),
+ ),
+ StandartListItem(
+ title: S.current.transaction_details_height,
+ value: '${tx.height ?? 0}',
+ key: ValueKey('standard_list_item_transaction_details_height_key'),
+ ),
+ StandartListItem(
+ title: S.current.transaction_details_amount,
+ value: tx.amountFormatted(),
+ key: ValueKey('standard_list_item_transaction_details_amount_key'),
+ ),
+ if (tx.feeFormatted() != null && tx.feeFormatted()!.isNotEmpty)
+ StandartListItem(
+ title: S.current.transaction_details_fee,
+ value: tx.feeFormatted()!,
+ key: ValueKey('standard_list_item_transaction_details_fee_key'),
+ ),
+ if (memo != null && memo.isNotEmpty)
+ StandartListItem(
+ title: S.current.memo,
+ value: memo,
+ key: ValueKey('standard_list_item_transaction_details_memo_key'),
+ ),
+ ];
+ items.addAll(_items);
+ }
+
void _addDogecoinListItems(TransactionInfo tx, DateFormat dateFormat) {
final _items = [
StandartListItem(
diff --git a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart
index 1ceb984..1ad9c92 100644
--- a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart
+++ b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart
@@ -28,6 +28,7 @@ import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_hidden
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_header.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
import 'package:cake_wallet/wownero/wownero.dart';
+import 'package:cake_wallet/zcash/zcash.dart';
import 'package:cw_core/amount_converter.dart';
import 'package:cw_core/currency.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
@@ -165,6 +166,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
return BaseURI(amount: amount, address: address.address);
case WalletType.arbitrum:
return ArbitrumURI(amount: amount, address: address.address);
+ case WalletType.zcash:
+ return ZcashURI(amount: amount, address: address.address);
case WalletType.none:
throw Exception('Unexpected type: ${type.toString()}');
}
@@ -313,6 +316,14 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
});
}
+ if (wallet.type == WalletType.zcash) {
+ final addrInfos = zcash!.getAddressInfos(wallet);
+ addrInfos.forEach((info) {
+ addressList.add(
+ new WalletAddressListItem(isPrimary: false, address: info.address, name: info.label));
+ });
+ }
+
for (var i = 0; i < addressList.length; i++) {
if (!(addressList[i] is WalletAddressListItem)) continue;
(addressList[i] as WalletAddressListItem).isHidden = wallet.walletAddresses.hiddenAddresses
@@ -409,6 +420,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
WalletType.litecoin,
WalletType.decred,
WalletType.dogecoin,
+ WalletType.zcash,
].contains(wallet.type);
@computed
@@ -528,6 +540,9 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
if ([WalletType.bitcoin, WalletType.litecoin].contains(wallet.type)) {
await bitcoin!.setAddressType(wallet, option);
}
+ if (wallet.type == WalletType.zcash) {
+ await zcash!.setAddressType(wallet, option);
+ }
}
void _init() {
diff --git a/lib/view_model/wallet_keys_view_model.dart b/lib/view_model/wallet_keys_view_model.dart
index a09a9c5..c8257d8 100644
--- a/lib/view_model/wallet_keys_view_model.dart
+++ b/lib/view_model/wallet_keys_view_model.dart
@@ -144,6 +144,7 @@ abstract class WalletKeysViewModelBase with Store {
case WalletType.base:
case WalletType.arbitrum:
case WalletType.solana:
+ case WalletType.zcash:
case WalletType.tron:
items.addAll([
if (_wallet.privateKey != null)
@@ -285,6 +286,8 @@ abstract class WalletKeysViewModelBase with Store {
return 'decred-wallet';
case WalletType.dogecoin:
return 'dogecoin-wallet';
+ case WalletType.zcash:
+ return 'zcash-wallet';
default:
throw Exception('Unexpected wallet type: ${_wallet.type.toString()}');
}
diff --git a/lib/view_model/wallet_new_vm.dart b/lib/view_model/wallet_new_vm.dart
index 26ac356..8e07721 100644
--- a/lib/view_model/wallet_new_vm.dart
+++ b/lib/view_model/wallet_new_vm.dart
@@ -6,6 +6,7 @@ import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/wownero/wownero.dart';
+import 'package:cake_wallet/zcash/zcash.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/wallet_creation_service.dart';
@@ -138,6 +139,12 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
password: walletPassword,
passphrase: passphrase,
);
+ case WalletType.zcash:
+ return zcash!.createZcashNewWalletCredentials(
+ name: name,
+ password: walletPassword,
+ passphrase: passphrase,
+ );
case WalletType.decred:
return decred!.createDecredNewWalletCredentials(name: name);
case WalletType.none:
diff --git a/lib/view_model/wallet_restore_view_model.dart b/lib/view_model/wallet_restore_view_model.dart
index 0227b89..d7e668f 100644
--- a/lib/view_model/wallet_restore_view_model.dart
+++ b/lib/view_model/wallet_restore_view_model.dart
@@ -19,6 +19,7 @@ import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
import 'package:cake_wallet/view_model/wallet_creation_vm.dart';
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cake_wallet/zano/zano.dart';
+import 'package:cake_wallet/zcash/zcash.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart';
@@ -59,10 +60,12 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
break;
case WalletType.bitcoinCash:
case WalletType.zano:
- case WalletType.none:
+ case WalletType.zcash:
case WalletType.dogecoin:
availableModes = [WalletRestoreMode.seed];
break;
+ case WalletType.none:
+ availableModes = [];
}
walletCreationService.changeWalletType(type: type);
if (restoredWallet != null) {
@@ -79,9 +82,13 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
late final bool hasSeedLanguageSelector =
[WalletType.monero, WalletType.haven, WalletType.wownero].contains(type);
- late final bool hasBlockchainHeightSelector =
- [WalletType.monero, WalletType.haven, WalletType.wownero].contains(type);
-
+ late final bool hasBlockchainHeightSelector = [
+ WalletType.monero,
+ WalletType.haven,
+ WalletType.wownero,
+ WalletType.zcash
+ ].contains(type);
+
late final bool hasRestoreFromPrivateKey = [
WalletType.ethereum,
WalletType.polygon,
@@ -202,9 +209,17 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
);
case WalletType.decred:
return decred!.createDecredRestoreWalletFromSeedCredentials(
+ name: name,
+ mnemonic: seed,
+ password: password,
+ );
+ case WalletType.zcash:
+ return zcash!.createZcashRestoreWalletFromSeedCredentials(
name: name,
mnemonic: seed,
password: password,
+ passphrase: passphrase,
+ height: height,
);
case WalletType.none:
case WalletType.haven:
diff --git a/lib/view_model/wallet_seed_view_model.dart b/lib/view_model/wallet_seed_view_model.dart
index 3cf05bf..70813c3 100644
--- a/lib/view_model/wallet_seed_view_model.dart
+++ b/lib/view_model/wallet_seed_view_model.dart
@@ -84,7 +84,7 @@ abstract class WalletSeedViewModelBase with Store {
bool shouldPerformVerification() {
bool isCI = bool.fromEnvironment('CI_BUILD', defaultValue: false);
- bool isDebug = kDebugMode;
+ bool isDebug = kDebugMode || kProfileMode;
if (isDebug && !isCI) {
printV("Skipping verification in debug mode - $isDebug (and when it's not in CI - $isCI).");
diff --git a/lib/zcash/cw_zcash.dart b/lib/zcash/cw_zcash.dart
new file mode 100644
index 0000000..4d082a1
--- /dev/null
+++ b/lib/zcash/cw_zcash.dart
@@ -0,0 +1,2263 @@
+part of 'zcash.dart';
+
+class CWZcash extends Zcash {
+ @override
+ WalletCredentials createZcashNewWalletCredentials(
+ {required String name,
+ WalletInfo? walletInfo,
+ String? password,
+ String? mnemonic,
+ required String? passphrase}) {
+ return ZcashNewWalletCredentials(
+ name: name,
+ passphrase: passphrase,
+ password: password,
+ mnemonic: mnemonic,
+ );
+ }
+
+ @override
+ WalletCredentials createZcashRestoreWalletFromPrivateKey(
+ {required String name,
+ required String privateKey,
+ required String password}) {
+ throw UnimplementedError();
+ }
+
+ @override
+ WalletCredentials createZcashRestoreWalletFromSeedCredentials(
+ {required String name,
+ required String mnemonic,
+ required String password,
+ String? passphrase,
+ required int? height}) {
+ return ZcashFromSeedWalletCredentials(
+ name: name, seed: mnemonic, passphrase: passphrase, password: password, height: height);
+ }
+
+ @override
+ Object createZcashTransactionCredentials(List<Output> outputs,
+ {required CryptoCurrency currency,
+ int? feeRate,
+ TransactionPriority? priority}) {
+ final txPriority = (priority ?? getZcashTransactionPriorityAutomatic())
+ as MoneroTransactionPriority;
+ return ZcashTransactionCredentials(
+ outputs: outputs
+ .map((out) => OutputInfo(
+ fiatAmount: out.fiatAmount,
+ cryptoAmount: out.cryptoAmount,
+ address: out.address,
+ note: out.note,
+ sendAll: out.sendAll,
+ extractedAddress: out.extractedAddress,
+ isParsedAddress: out.isParsedAddress,
+ formattedCryptoAmount: out.formattedCryptoAmount))
+ .toList(),
+ priority: txPriority,
+ currency: currency,
+ );
+ }
+
+ @override
+ Object createZcashTransactionCredentialsRaw(List<OutputInfo> outputs,
+ {required CryptoCurrency currency, required int feeRate}) {
+ return ZcashTransactionCredentials(
+ outputs: outputs,
+ priority:
+ getZcashTransactionPriorityAutomatic() as MoneroTransactionPriority,
+ currency: currency,
+ );
+ }
+
+ @override
+ WalletService<WalletCredentials, WalletCredentials, WalletCredentials,
+ WalletCredentials> createZcashWalletService(bool isDirect) {
+ return ZcashWalletService();
+ }
+
+ @override
+ double formatterZcashAmountToDouble({TransactionInfo? transaction, BigInt? amount}) {
+ return cryptoAmountToDouble(amount: amount?.toInt()??0, divider: 1e8);
+ }
+
+ @override
+ int formatterZcashParseAmount(String amount) {
+ return CryptoCurrency.zec.parseAmount(amount).toInt();
+ }
+
+ @override
+ String formatterZcashAmountToString({required int amount}) {
+ return CryptoCurrency.zec.formatAmount(BigInt.from(amount));
+ }
+
+ @override
+ String getAddress(
+ WalletBase<Balance, TransactionHistoryBase<TransactionInfo>,
+ TransactionInfo>
+ wallet) {
+ final zcashWallet = wallet as ZcashWallet;
+ return zcashWallet.walletAddresses.address;
+ }
+
+ @override
+ String getPrivateKey(
+ WalletBase<Balance, TransactionHistoryBase<TransactionInfo>,
+ TransactionInfo>
+ wallet) {
+ final zcashWallet = wallet as ZcashWallet;
+ final seed = zcashWallet.seed;
+ return seed ?? '';
+ }
+
+ @override
+ String getPublicKey(
+ WalletBase<Balance, TransactionHistoryBase<TransactionInfo>,
+ TransactionInfo>
+ wallet) {
+ return getAddress(wallet);
+ }
+
+ @override
+ Map<String, String> getKeys(Object wallet) {
+ final zcashWallet = wallet as ZcashWallet;
+ final seed = zcashWallet.seed;
+ final address = zcashWallet.walletAddresses.address;
+
+ return <String, String>{
+ if (seed != null) 'seed': seed,
+ 'primaryAddress': address,
+ };
+ }
+
+ @override
+ List<String> getZcashWordList(String language) {
+ return wordList;
+ }
+
+ @override
+ List<WalletInfoAddressInfo> getAddressInfos(Object wallet) {
+ final zcashWallet = wallet as ZcashWallet;
+ return (zcashWallet.walletAddresses as ZcashWalletAddresses)
+ .getAddressInfos();
+ }
+
+ @override
+ TransactionPriority getDefaultTransactionPriority() {
+ return MoneroTransactionPriority.automatic;
+ }
+
+ @override
+ TransactionPriority getZcashTransactionPriorityAutomatic() {
+ return MoneroTransactionPriority.automatic;
+ }
+
+ @override
+ TransactionPriority deserializeZcashTransactionPriority({required int raw}) {
+ return MoneroTransactionPriority.deserialize(raw: raw);
+ }
+
+ @override
+ List<TransactionPriority> getTransactionPriorities() {
+ return MoneroTransactionPriority.all;
+ }
+
+ @override
+ List<ReceivePageOption> getZcashReceivePageOptions(Object wallet) {
+ // final zcashWallet = wallet as ZcashWallet;
+ return ZcashReceivePageOption.all;
+ }
+
+ @override
+ ReceivePageOption getSelectedAddressType(Object wallet) {
+ final zcashWallet = wallet as ZcashWallet;
+ final t = (zcashWallet.walletAddresses as ZcashWalletAddresses).walletInfo.addressPageType??"";
+ return ZcashReceivePageOption.fromType(ZcashReceivePageOption.typeFromString(t));
+ }
+
+ @override
+ ZcashAddressType getZcashAddressType(ReceivePageOption option) {
+ switch (option) {
+ case ZcashReceivePageOption.unified:
+ return ZcashAddressType.unifiedType;
+ case ZcashReceivePageOption.transparent:
+ return ZcashAddressType.transparent;
+ case ZcashReceivePageOption.shieldedSapling:
+ return ZcashAddressType.shieldedSapling;
+ case ZcashReceivePageOption.shieldedOrchard:
+ return ZcashAddressType.shieldedOrchard;
+ default:
+ throw Exception("Unknown ReceivePageOption!");
+ }
+ }
+ Future<void> setAddressType(Object wallet, dynamic option) async {
+ final zcashWallet = wallet as ZcashWallet;
+ await (zcashWallet.walletAddresses as ZcashWalletAddresses).setAddressType(option as ZcashAddressType);
+ }
+
+ @override
+ ZcashAddressType getOptionToType(ReceivePageOption option) {
+ return (option as ZcashReceivePageOption).toType();
+ }
+
+ @override
+ void unlockDatabase(String password) {
+ return ZcashWalletBase.unlockDatabase(password);
+ }
+
+ @override
+ Future<int> getHeightByDate(DateTime date) {
+ return ZcashWalletBase.getHeightByDate(date);
+ }
+}
+
+const wordList = [
+ 'abandon',
+ 'ability',
+ 'able',
+ 'about',
+ 'above',
+ 'absent',
+ 'absorb',
+ 'abstract',
+ 'absurd',
+ 'abuse',
+ 'access',
+ 'accident',
+ 'account',
+ 'accuse',
+ 'achieve',
+ 'acid',
+ 'acoustic',
+ 'acquire',
+ 'across',
+ 'act',
+ 'action',
+ 'actor',
+ 'actress',
+ 'actual',
+ 'adapt',
+ 'add',
+ 'addict',
+ 'address',
+ 'adjust',
+ 'admit',
+ 'adult',
+ 'advance',
+ 'advice',
+ 'aerobic',
+ 'affair',
+ 'afford',
+ 'afraid',
+ 'again',
+ 'age',
+ 'agent',
+ 'agree',
+ 'ahead',
+ 'aim',
+ 'air',
+ 'airport',
+ 'aisle',
+ 'alarm',
+ 'album',
+ 'alcohol',
+ 'alert',
+ 'alien',
+ 'all',
+ 'alley',
+ 'allow',
+ 'almost',
+ 'alone',
+ 'alpha',
+ 'already',
+ 'also',
+ 'alter',
+ 'always',
+ 'amateur',
+ 'amazing',
+ 'among',
+ 'amount',
+ 'amused',
+ 'analyst',
+ 'anchor',
+ 'ancient',
+ 'anger',
+ 'angle',
+ 'angry',
+ 'animal',
+ 'ankle',
+ 'announce',
+ 'annual',
+ 'another',
+ 'answer',
+ 'antenna',
+ 'antique',
+ 'anxiety',
+ 'any',
+ 'apart',
+ 'apology',
+ 'appear',
+ 'apple',
+ 'approve',
+ 'april',
+ 'arch',
+ 'arctic',
+ 'area',
+ 'arena',
+ 'argue',
+ 'arm',
+ 'armed',
+ 'armor',
+ 'army',
+ 'around',
+ 'arrange',
+ 'arrest',
+ 'arrive',
+ 'arrow',
+ 'art',
+ 'artefact',
+ 'artist',
+ 'artwork',
+ 'ask',
+ 'aspect',
+ 'assault',
+ 'asset',
+ 'assist',
+ 'assume',
+ 'asthma',
+ 'athlete',
+ 'atom',
+ 'attack',
+ 'attend',
+ 'attitude',
+ 'attract',
+ 'auction',
+ 'audit',
+ 'august',
+ 'aunt',
+ 'author',
+ 'auto',
+ 'autumn',
+ 'average',
+ 'avocado',
+ 'avoid',
+ 'awake',
+ 'aware',
+ 'away',
+ 'awesome',
+ 'awful',
+ 'awkward',
+ 'axis',
+ 'baby',
+ 'bachelor',
+ 'bacon',
+ 'badge',
+ 'bag',
+ 'balance',
+ 'balcony',
+ 'ball',
+ 'bamboo',
+ 'banana',
+ 'banner',
+ 'bar',
+ 'barely',
+ 'bargain',
+ 'barrel',
+ 'base',
+ 'basic',
+ 'basket',
+ 'battle',
+ 'beach',
+ 'bean',
+ 'beauty',
+ 'because',
+ 'become',
+ 'beef',
+ 'before',
+ 'begin',
+ 'behave',
+ 'behind',
+ 'believe',
+ 'below',
+ 'belt',
+ 'bench',
+ 'benefit',
+ 'best',
+ 'betray',
+ 'better',
+ 'between',
+ 'beyond',
+ 'bicycle',
+ 'bid',
+ 'bike',
+ 'bind',
+ 'biology',
+ 'bird',
+ 'birth',
+ 'bitter',
+ 'black',
+ 'blade',
+ 'blame',
+ 'blanket',
+ 'blast',
+ 'bleak',
+ 'bless',
+ 'blind',
+ 'blood',
+ 'blossom',
+ 'blouse',
+ 'blue',
+ 'blur',
+ 'blush',
+ 'board',
+ 'boat',
+ 'body',
+ 'boil',
+ 'bomb',
+ 'bone',
+ 'bonus',
+ 'book',
+ 'boost',
+ 'border',
+ 'boring',
+ 'borrow',
+ 'boss',
+ 'bottom',
+ 'bounce',
+ 'box',
+ 'boy',
+ 'bracket',
+ 'brain',
+ 'brand',
+ 'brass',
+ 'brave',
+ 'bread',
+ 'breeze',
+ 'brick',
+ 'bridge',
+ 'brief',
+ 'bright',
+ 'bring',
+ 'brisk',
+ 'broccoli',
+ 'broken',
+ 'bronze',
+ 'broom',
+ 'brother',
+ 'brown',
+ 'brush',
+ 'bubble',
+ 'buddy',
+ 'budget',
+ 'buffalo',
+ 'build',
+ 'bulb',
+ 'bulk',
+ 'bullet',
+ 'bundle',
+ 'bunker',
+ 'burden',
+ 'burger',
+ 'burst',
+ 'bus',
+ 'business',
+ 'busy',
+ 'butter',
+ 'buyer',
+ 'buzz',
+ 'cabbage',
+ 'cabin',
+ 'cable',
+ 'cactus',
+ 'cage',
+ 'cake',
+ 'call',
+ 'calm',
+ 'camera',
+ 'camp',
+ 'can',
+ 'canal',
+ 'cancel',
+ 'candy',
+ 'cannon',
+ 'canoe',
+ 'canvas',
+ 'canyon',
+ 'capable',
+ 'capital',
+ 'captain',
+ 'car',
+ 'carbon',
+ 'card',
+ 'cargo',
+ 'carpet',
+ 'carry',
+ 'cart',
+ 'case',
+ 'cash',
+ 'casino',
+ 'castle',
+ 'casual',
+ 'cat',
+ 'catalog',
+ 'catch',
+ 'category',
+ 'cattle',
+ 'caught',
+ 'cause',
+ 'caution',
+ 'cave',
+ 'ceiling',
+ 'celery',
+ 'cement',
+ 'census',
+ 'century',
+ 'cereal',
+ 'certain',
+ 'chair',
+ 'chalk',
+ 'champion',
+ 'change',
+ 'chaos',
+ 'chapter',
+ 'charge',
+ 'chase',
+ 'chat',
+ 'cheap',
+ 'check',
+ 'cheese',
+ 'chef',
+ 'cherry',
+ 'chest',
+ 'chicken',
+ 'chief',
+ 'child',
+ 'chimney',
+ 'choice',
+ 'choose',
+ 'chronic',
+ 'chuckle',
+ 'chunk',
+ 'churn',
+ 'cigar',
+ 'cinnamon',
+ 'circle',
+ 'citizen',
+ 'city',
+ 'civil',
+ 'claim',
+ 'clap',
+ 'clarify',
+ 'claw',
+ 'clay',
+ 'clean',
+ 'clerk',
+ 'clever',
+ 'click',
+ 'client',
+ 'cliff',
+ 'climb',
+ 'clinic',
+ 'clip',
+ 'clock',
+ 'clog',
+ 'close',
+ 'cloth',
+ 'cloud',
+ 'clown',
+ 'club',
+ 'clump',
+ 'cluster',
+ 'clutch',
+ 'coach',
+ 'coast',
+ 'coconut',
+ 'code',
+ 'coffee',
+ 'coil',
+ 'coin',
+ 'collect',
+ 'color',
+ 'column',
+ 'combine',
+ 'come',
+ 'comfort',
+ 'comic',
+ 'common',
+ 'company',
+ 'concert',
+ 'conduct',
+ 'confirm',
+ 'congress',
+ 'connect',
+ 'consider',
+ 'control',
+ 'convince',
+ 'cook',
+ 'cool',
+ 'copper',
+ 'copy',
+ 'coral',
+ 'core',
+ 'corn',
+ 'correct',
+ 'cost',
+ 'cotton',
+ 'couch',
+ 'country',
+ 'couple',
+ 'course',
+ 'cousin',
+ 'cover',
+ 'coyote',
+ 'crack',
+ 'cradle',
+ 'craft',
+ 'cram',
+ 'crane',
+ 'crash',
+ 'crater',
+ 'crawl',
+ 'crazy',
+ 'cream',
+ 'credit',
+ 'creek',
+ 'crew',
+ 'cricket',
+ 'crime',
+ 'crisp',
+ 'critic',
+ 'crop',
+ 'cross',
+ 'crouch',
+ 'crowd',
+ 'crucial',
+ 'cruel',
+ 'cruise',
+ 'crumble',
+ 'crunch',
+ 'crush',
+ 'cry',
+ 'crystal',
+ 'cube',
+ 'culture',
+ 'cup',
+ 'cupboard',
+ 'curious',
+ 'current',
+ 'curtain',
+ 'curve',
+ 'cushion',
+ 'custom',
+ 'cute',
+ 'cycle',
+ 'dad',
+ 'damage',
+ 'damp',
+ 'dance',
+ 'danger',
+ 'daring',
+ 'dash',
+ 'daughter',
+ 'dawn',
+ 'day',
+ 'deal',
+ 'debate',
+ 'debris',
+ 'decade',
+ 'december',
+ 'decide',
+ 'decline',
+ 'decorate',
+ 'decrease',
+ 'deer',
+ 'defense',
+ 'define',
+ 'defy',
+ 'degree',
+ 'delay',
+ 'deliver',
+ 'demand',
+ 'demise',
+ 'denial',
+ 'dentist',
+ 'deny',
+ 'depart',
+ 'depend',
+ 'deposit',
+ 'depth',
+ 'deputy',
+ 'derive',
+ 'describe',
+ 'desert',
+ 'design',
+ 'desk',
+ 'despair',
+ 'destroy',
+ 'detail',
+ 'detect',
+ 'develop',
+ 'device',
+ 'devote',
+ 'diagram',
+ 'dial',
+ 'diamond',
+ 'diary',
+ 'dice',
+ 'diesel',
+ 'diet',
+ 'differ',
+ 'digital',
+ 'dignity',
+ 'dilemma',
+ 'dinner',
+ 'dinosaur',
+ 'direct',
+ 'dirt',
+ 'disagree',
+ 'discover',
+ 'disease',
+ 'dish',
+ 'dismiss',
+ 'disorder',
+ 'display',
+ 'distance',
+ 'divert',
+ 'divide',
+ 'divorce',
+ 'dizzy',
+ 'doctor',
+ 'document',
+ 'dog',
+ 'doll',
+ 'dolphin',
+ 'domain',
+ 'donate',
+ 'donkey',
+ 'donor',
+ 'door',
+ 'dose',
+ 'double',
+ 'dove',
+ 'draft',
+ 'dragon',
+ 'drama',
+ 'drastic',
+ 'draw',
+ 'dream',
+ 'dress',
+ 'drift',
+ 'drill',
+ 'drink',
+ 'drip',
+ 'drive',
+ 'drop',
+ 'drum',
+ 'dry',
+ 'duck',
+ 'dumb',
+ 'dune',
+ 'during',
+ 'dust',
+ 'dutch',
+ 'duty',
+ 'dwarf',
+ 'dynamic',
+ 'eager',
+ 'eagle',
+ 'early',
+ 'earn',
+ 'earth',
+ 'easily',
+ 'east',
+ 'easy',
+ 'echo',
+ 'ecology',
+ 'economy',
+ 'edge',
+ 'edit',
+ 'educate',
+ 'effort',
+ 'egg',
+ 'eight',
+ 'either',
+ 'elbow',
+ 'elder',
+ 'electric',
+ 'elegant',
+ 'element',
+ 'elephant',
+ 'elevator',
+ 'elite',
+ 'else',
+ 'embark',
+ 'embody',
+ 'embrace',
+ 'emerge',
+ 'emotion',
+ 'employ',
+ 'empower',
+ 'empty',
+ 'enable',
+ 'enact',
+ 'end',
+ 'endless',
+ 'endorse',
+ 'enemy',
+ 'energy',
+ 'enforce',
+ 'engage',
+ 'engine',
+ 'enhance',
+ 'enjoy',
+ 'enlist',
+ 'enough',
+ 'enrich',
+ 'enroll',
+ 'ensure',
+ 'enter',
+ 'entire',
+ 'entry',
+ 'envelope',
+ 'episode',
+ 'equal',
+ 'equip',
+ 'era',
+ 'erase',
+ 'erode',
+ 'erosion',
+ 'error',
+ 'erupt',
+ 'escape',
+ 'essay',
+ 'essence',
+ 'estate',
+ 'eternal',
+ 'ethics',
+ 'evidence',
+ 'evil',
+ 'evoke',
+ 'evolve',
+ 'exact',
+ 'example',
+ 'excess',
+ 'exchange',
+ 'excite',
+ 'exclude',
+ 'excuse',
+ 'execute',
+ 'exercise',
+ 'exhaust',
+ 'exhibit',
+ 'exile',
+ 'exist',
+ 'exit',
+ 'exotic',
+ 'expand',
+ 'expect',
+ 'expire',
+ 'explain',
+ 'expose',
+ 'express',
+ 'extend',
+ 'extra',
+ 'eye',
+ 'eyebrow',
+ 'fabric',
+ 'face',
+ 'faculty',
+ 'fade',
+ 'faint',
+ 'faith',
+ 'fall',
+ 'false',
+ 'fame',
+ 'family',
+ 'famous',
+ 'fan',
+ 'fancy',
+ 'fantasy',
+ 'farm',
+ 'fashion',
+ 'fat',
+ 'fatal',
+ 'father',
+ 'fatigue',
+ 'fault',
+ 'favorite',
+ 'feature',
+ 'february',
+ 'federal',
+ 'fee',
+ 'feed',
+ 'feel',
+ 'female',
+ 'fence',
+ 'festival',
+ 'fetch',
+ 'fever',
+ 'few',
+ 'fiber',
+ 'fiction',
+ 'field',
+ 'figure',
+ 'file',
+ 'film',
+ 'filter',
+ 'final',
+ 'find',
+ 'fine',
+ 'finger',
+ 'finish',
+ 'fire',
+ 'firm',
+ 'first',
+ 'fiscal',
+ 'fish',
+ 'fit',
+ 'fitness',
+ 'fix',
+ 'flag',
+ 'flame',
+ 'flash',
+ 'flat',
+ 'flavor',
+ 'flee',
+ 'flight',
+ 'flip',
+ 'float',
+ 'flock',
+ 'floor',
+ 'flower',
+ 'fluid',
+ 'flush',
+ 'fly',
+ 'foam',
+ 'focus',
+ 'fog',
+ 'foil',
+ 'fold',
+ 'follow',
+ 'food',
+ 'foot',
+ 'force',
+ 'forest',
+ 'forget',
+ 'fork',
+ 'fortune',
+ 'forum',
+ 'forward',
+ 'fossil',
+ 'foster',
+ 'found',
+ 'fox',
+ 'fragile',
+ 'frame',
+ 'frequent',
+ 'fresh',
+ 'friend',
+ 'fringe',
+ 'frog',
+ 'front',
+ 'frost',
+ 'frown',
+ 'frozen',
+ 'fruit',
+ 'fuel',
+ 'fun',
+ 'funny',
+ 'furnace',
+ 'fury',
+ 'future',
+ 'gadget',
+ 'gain',
+ 'galaxy',
+ 'gallery',
+ 'game',
+ 'gap',
+ 'garage',
+ 'garbage',
+ 'garden',
+ 'garlic',
+ 'garment',
+ 'gas',
+ 'gasp',
+ 'gate',
+ 'gather',
+ 'gauge',
+ 'gaze',
+ 'general',
+ 'genius',
+ 'genre',
+ 'gentle',
+ 'genuine',
+ 'gesture',
+ 'ghost',
+ 'giant',
+ 'gift',
+ 'giggle',
+ 'ginger',
+ 'giraffe',
+ 'girl',
+ 'give',
+ 'glad',
+ 'glance',
+ 'glare',
+ 'glass',
+ 'glide',
+ 'glimpse',
+ 'globe',
+ 'gloom',
+ 'glory',
+ 'glove',
+ 'glow',
+ 'glue',
+ 'goat',
+ 'goddess',
+ 'gold',
+ 'good',
+ 'goose',
+ 'gorilla',
+ 'gospel',
+ 'gossip',
+ 'govern',
+ 'gown',
+ 'grab',
+ 'grace',
+ 'grain',
+ 'grant',
+ 'grape',
+ 'grass',
+ 'gravity',
+ 'great',
+ 'green',
+ 'grid',
+ 'grief',
+ 'grit',
+ 'grocery',
+ 'group',
+ 'grow',
+ 'grunt',
+ 'guard',
+ 'guess',
+ 'guide',
+ 'guilt',
+ 'guitar',
+ 'gun',
+ 'gym',
+ 'habit',
+ 'hair',
+ 'half',
+ 'hammer',
+ 'hamster',
+ 'hand',
+ 'happy',
+ 'harbor',
+ 'hard',
+ 'harsh',
+ 'harvest',
+ 'hat',
+ 'have',
+ 'hawk',
+ 'hazard',
+ 'head',
+ 'health',
+ 'heart',
+ 'heavy',
+ 'hedgehog',
+ 'height',
+ 'hello',
+ 'helmet',
+ 'help',
+ 'hen',
+ 'hero',
+ 'hidden',
+ 'high',
+ 'hill',
+ 'hint',
+ 'hip',
+ 'hire',
+ 'history',
+ 'hobby',
+ 'hockey',
+ 'hold',
+ 'hole',
+ 'holiday',
+ 'hollow',
+ 'home',
+ 'honey',
+ 'hood',
+ 'hope',
+ 'horn',
+ 'horror',
+ 'horse',
+ 'hospital',
+ 'host',
+ 'hotel',
+ 'hour',
+ 'hover',
+ 'hub',
+ 'huge',
+ 'human',
+ 'humble',
+ 'humor',
+ 'hundred',
+ 'hungry',
+ 'hunt',
+ 'hurdle',
+ 'hurry',
+ 'hurt',
+ 'husband',
+ 'hybrid',
+ 'ice',
+ 'icon',
+ 'idea',
+ 'identify',
+ 'idle',
+ 'ignore',
+ 'ill',
+ 'illegal',
+ 'illness',
+ 'image',
+ 'imitate',
+ 'immense',
+ 'immune',
+ 'impact',
+ 'impose',
+ 'improve',
+ 'impulse',
+ 'inch',
+ 'include',
+ 'income',
+ 'increase',
+ 'index',
+ 'indicate',
+ 'indoor',
+ 'industry',
+ 'infant',
+ 'inflict',
+ 'inform',
+ 'inhale',
+ 'inherit',
+ 'initial',
+ 'inject',
+ 'injury',
+ 'inmate',
+ 'inner',
+ 'innocent',
+ 'input',
+ 'inquiry',
+ 'insane',
+ 'insect',
+ 'inside',
+ 'inspire',
+ 'install',
+ 'intact',
+ 'interest',
+ 'into',
+ 'invest',
+ 'invite',
+ 'involve',
+ 'iron',
+ 'island',
+ 'isolate',
+ 'issue',
+ 'item',
+ 'ivory',
+ 'jacket',
+ 'jaguar',
+ 'jar',
+ 'jazz',
+ 'jealous',
+ 'jeans',
+ 'jelly',
+ 'jewel',
+ 'job',
+ 'join',
+ 'joke',
+ 'journey',
+ 'joy',
+ 'judge',
+ 'juice',
+ 'jump',
+ 'jungle',
+ 'junior',
+ 'junk',
+ 'just',
+ 'kangaroo',
+ 'keen',
+ 'keep',
+ 'ketchup',
+ 'key',
+ 'kick',
+ 'kid',
+ 'kidney',
+ 'kind',
+ 'kingdom',
+ 'kiss',
+ 'kit',
+ 'kitchen',
+ 'kite',
+ 'kitten',
+ 'kiwi',
+ 'knee',
+ 'knife',
+ 'knock',
+ 'know',
+ 'lab',
+ 'label',
+ 'labor',
+ 'ladder',
+ 'lady',
+ 'lake',
+ 'lamp',
+ 'language',
+ 'laptop',
+ 'large',
+ 'later',
+ 'latin',
+ 'laugh',
+ 'laundry',
+ 'lava',
+ 'law',
+ 'lawn',
+ 'lawsuit',
+ 'layer',
+ 'lazy',
+ 'leader',
+ 'leaf',
+ 'learn',
+ 'leave',
+ 'lecture',
+ 'left',
+ 'leg',
+ 'legal',
+ 'legend',
+ 'leisure',
+ 'lemon',
+ 'lend',
+ 'length',
+ 'lens',
+ 'leopard',
+ 'lesson',
+ 'letter',
+ 'level',
+ 'liar',
+ 'liberty',
+ 'library',
+ 'license',
+ 'life',
+ 'lift',
+ 'light',
+ 'like',
+ 'limb',
+ 'limit',
+ 'link',
+ 'lion',
+ 'liquid',
+ 'list',
+ 'little',
+ 'live',
+ 'lizard',
+ 'load',
+ 'loan',
+ 'lobster',
+ 'local',
+ 'lock',
+ 'logic',
+ 'lonely',
+ 'long',
+ 'loop',
+ 'lottery',
+ 'loud',
+ 'lounge',
+ 'love',
+ 'loyal',
+ 'lucky',
+ 'luggage',
+ 'lumber',
+ 'lunar',
+ 'lunch',
+ 'luxury',
+ 'lyrics',
+ 'machine',
+ 'mad',
+ 'magic',
+ 'magnet',
+ 'maid',
+ 'mail',
+ 'main',
+ 'major',
+ 'make',
+ 'mammal',
+ 'man',
+ 'manage',
+ 'mandate',
+ 'mango',
+ 'mansion',
+ 'manual',
+ 'maple',
+ 'marble',
+ 'march',
+ 'margin',
+ 'marine',
+ 'market',
+ 'marriage',
+ 'mask',
+ 'mass',
+ 'master',
+ 'match',
+ 'material',
+ 'math',
+ 'matrix',
+ 'matter',
+ 'maximum',
+ 'maze',
+ 'meadow',
+ 'mean',
+ 'measure',
+ 'meat',
+ 'mechanic',
+ 'medal',
+ 'media',
+ 'melody',
+ 'melt',
+ 'member',
+ 'memory',
+ 'mention',
+ 'menu',
+ 'mercy',
+ 'merge',
+ 'merit',
+ 'merry',
+ 'mesh',
+ 'message',
+ 'metal',
+ 'method',
+ 'middle',
+ 'midnight',
+ 'milk',
+ 'million',
+ 'mimic',
+ 'mind',
+ 'minimum',
+ 'minor',
+ 'minute',
+ 'miracle',
+ 'mirror',
+ 'misery',
+ 'miss',
+ 'mistake',
+ 'mix',
+ 'mixed',
+ 'mixture',
+ 'mobile',
+ 'model',
+ 'modify',
+ 'mom',
+ 'moment',
+ 'monitor',
+ 'monkey',
+ 'monster',
+ 'month',
+ 'moon',
+ 'moral',
+ 'more',
+ 'morning',
+ 'mosquito',
+ 'mother',
+ 'motion',
+ 'motor',
+ 'mountain',
+ 'mouse',
+ 'move',
+ 'movie',
+ 'much',
+ 'muffin',
+ 'mule',
+ 'multiply',
+ 'muscle',
+ 'museum',
+ 'mushroom',
+ 'music',
+ 'must',
+ 'mutual',
+ 'myself',
+ 'mystery',
+ 'myth',
+ 'naive',
+ 'name',
+ 'napkin',
+ 'narrow',
+ 'nasty',
+ 'nation',
+ 'nature',
+ 'near',
+ 'neck',
+ 'need',
+ 'negative',
+ 'neglect',
+ 'neither',
+ 'nephew',
+ 'nerve',
+ 'nest',
+ 'net',
+ 'network',
+ 'neutral',
+ 'never',
+ 'news',
+ 'next',
+ 'nice',
+ 'night',
+ 'noble',
+ 'noise',
+ 'nominee',
+ 'noodle',
+ 'normal',
+ 'north',
+ 'nose',
+ 'notable',
+ 'note',
+ 'nothing',
+ 'notice',
+ 'novel',
+ 'now',
+ 'nuclear',
+ 'number',
+ 'nurse',
+ 'nut',
+ 'oak',
+ 'obey',
+ 'object',
+ 'oblige',
+ 'obscure',
+ 'observe',
+ 'obtain',
+ 'obvious',
+ 'occur',
+ 'ocean',
+ 'october',
+ 'odor',
+ 'off',
+ 'offer',
+ 'office',
+ 'often',
+ 'oil',
+ 'okay',
+ 'old',
+ 'olive',
+ 'olympic',
+ 'omit',
+ 'once',
+ 'one',
+ 'onion',
+ 'online',
+ 'only',
+ 'open',
+ 'opera',
+ 'opinion',
+ 'oppose',
+ 'option',
+ 'orange',
+ 'orbit',
+ 'orchard',
+ 'order',
+ 'ordinary',
+ 'organ',
+ 'orient',
+ 'original',
+ 'orphan',
+ 'ostrich',
+ 'other',
+ 'outdoor',
+ 'outer',
+ 'output',
+ 'outside',
+ 'oval',
+ 'oven',
+ 'over',
+ 'own',
+ 'owner',
+ 'oxygen',
+ 'oyster',
+ 'ozone',
+ 'pact',
+ 'paddle',
+ 'page',
+ 'pair',
+ 'palace',
+ 'palm',
+ 'panda',
+ 'panel',
+ 'panic',
+ 'panther',
+ 'paper',
+ 'parade',
+ 'parent',
+ 'park',
+ 'parrot',
+ 'party',
+ 'pass',
+ 'patch',
+ 'path',
+ 'patient',
+ 'patrol',
+ 'pattern',
+ 'pause',
+ 'pave',
+ 'payment',
+ 'peace',
+ 'peanut',
+ 'pear',
+ 'peasant',
+ 'pelican',
+ 'pen',
+ 'penalty',
+ 'pencil',
+ 'people',
+ 'pepper',
+ 'perfect',
+ 'permit',
+ 'person',
+ 'pet',
+ 'phone',
+ 'photo',
+ 'phrase',
+ 'physical',
+ 'piano',
+ 'picnic',
+ 'picture',
+ 'piece',
+ 'pig',
+ 'pigeon',
+ 'pill',
+ 'pilot',
+ 'pink',
+ 'pioneer',
+ 'pipe',
+ 'pistol',
+ 'pitch',
+ 'pizza',
+ 'place',
+ 'planet',
+ 'plastic',
+ 'plate',
+ 'play',
+ 'please',
+ 'pledge',
+ 'pluck',
+ 'plug',
+ 'plunge',
+ 'poem',
+ 'poet',
+ 'point',
+ 'polar',
+ 'pole',
+ 'police',
+ 'pond',
+ 'pony',
+ 'pool',
+ 'popular',
+ 'portion',
+ 'position',
+ 'possible',
+ 'post',
+ 'potato',
+ 'pottery',
+ 'poverty',
+ 'powder',
+ 'power',
+ 'practice',
+ 'praise',
+ 'predict',
+ 'prefer',
+ 'prepare',
+ 'present',
+ 'pretty',
+ 'prevent',
+ 'price',
+ 'pride',
+ 'primary',
+ 'print',
+ 'priority',
+ 'prison',
+ 'private',
+ 'prize',
+ 'problem',
+ 'process',
+ 'produce',
+ 'profit',
+ 'program',
+ 'project',
+ 'promote',
+ 'proof',
+ 'property',
+ 'prosper',
+ 'protect',
+ 'proud',
+ 'provide',
+ 'public',
+ 'pudding',
+ 'pull',
+ 'pulp',
+ 'pulse',
+ 'pumpkin',
+ 'punch',
+ 'pupil',
+ 'puppy',
+ 'purchase',
+ 'purity',
+ 'purpose',
+ 'purse',
+ 'push',
+ 'put',
+ 'puzzle',
+ 'pyramid',
+ 'quality',
+ 'quantum',
+ 'quarter',
+ 'question',
+ 'quick',
+ 'quit',
+ 'quiz',
+ 'quote',
+ 'rabbit',
+ 'raccoon',
+ 'race',
+ 'rack',
+ 'radar',
+ 'radio',
+ 'rail',
+ 'rain',
+ 'raise',
+ 'rally',
+ 'ramp',
+ 'ranch',
+ 'random',
+ 'range',
+ 'rapid',
+ 'rare',
+ 'rate',
+ 'rather',
+ 'raven',
+ 'raw',
+ 'razor',
+ 'ready',
+ 'real',
+ 'reason',
+ 'rebel',
+ 'rebuild',
+ 'recall',
+ 'receive',
+ 'recipe',
+ 'record',
+ 'recycle',
+ 'reduce',
+ 'reflect',
+ 'reform',
+ 'refuse',
+ 'region',
+ 'regret',
+ 'regular',
+ 'reject',
+ 'relax',
+ 'release',
+ 'relief',
+ 'rely',
+ 'remain',
+ 'remember',
+ 'remind',
+ 'remove',
+ 'render',
+ 'renew',
+ 'rent',
+ 'reopen',
+ 'repair',
+ 'repeat',
+ 'replace',
+ 'report',
+ 'require',
+ 'rescue',
+ 'resemble',
+ 'resist',
+ 'resource',
+ 'response',
+ 'result',
+ 'retire',
+ 'retreat',
+ 'return',
+ 'reunion',
+ 'reveal',
+ 'review',
+ 'reward',
+ 'rhythm',
+ 'rib',
+ 'ribbon',
+ 'rice',
+ 'rich',
+ 'ride',
+ 'ridge',
+ 'rifle',
+ 'right',
+ 'rigid',
+ 'ring',
+ 'riot',
+ 'ripple',
+ 'risk',
+ 'ritual',
+ 'rival',
+ 'river',
+ 'road',
+ 'roast',
+ 'robot',
+ 'robust',
+ 'rocket',
+ 'romance',
+ 'roof',
+ 'rookie',
+ 'room',
+ 'rose',
+ 'rotate',
+ 'rough',
+ 'round',
+ 'route',
+ 'royal',
+ 'rubber',
+ 'rude',
+ 'rug',
+ 'rule',
+ 'run',
+ 'runway',
+ 'rural',
+ 'sad',
+ 'saddle',
+ 'sadness',
+ 'safe',
+ 'sail',
+ 'salad',
+ 'salmon',
+ 'salon',
+ 'salt',
+ 'salute',
+ 'same',
+ 'sample',
+ 'sand',
+ 'satisfy',
+ 'satoshi',
+ 'sauce',
+ 'sausage',
+ 'save',
+ 'say',
+ 'scale',
+ 'scan',
+ 'scare',
+ 'scatter',
+ 'scene',
+ 'scheme',
+ 'school',
+ 'science',
+ 'scissors',
+ 'scorpion',
+ 'scout',
+ 'scrap',
+ 'screen',
+ 'script',
+ 'scrub',
+ 'sea',
+ 'search',
+ 'season',
+ 'seat',
+ 'second',
+ 'secret',
+ 'section',
+ 'security',
+ 'seed',
+ 'seek',
+ 'segment',
+ 'select',
+ 'sell',
+ 'seminar',
+ 'senior',
+ 'sense',
+ 'sentence',
+ 'series',
+ 'service',
+ 'session',
+ 'settle',
+ 'setup',
+ 'seven',
+ 'shadow',
+ 'shaft',
+ 'shallow',
+ 'share',
+ 'shed',
+ 'shell',
+ 'sheriff',
+ 'shield',
+ 'shift',
+ 'shine',
+ 'ship',
+ 'shiver',
+ 'shock',
+ 'shoe',
+ 'shoot',
+ 'shop',
+ 'short',
+ 'shoulder',
+ 'shove',
+ 'shrimp',
+ 'shrug',
+ 'shuffle',
+ 'shy',
+ 'sibling',
+ 'sick',
+ 'side',
+ 'siege',
+ 'sight',
+ 'sign',
+ 'silent',
+ 'silk',
+ 'silly',
+ 'silver',
+ 'similar',
+ 'simple',
+ 'since',
+ 'sing',
+ 'siren',
+ 'sister',
+ 'situate',
+ 'six',
+ 'size',
+ 'skate',
+ 'sketch',
+ 'ski',
+ 'skill',
+ 'skin',
+ 'skirt',
+ 'skull',
+ 'slab',
+ 'slam',
+ 'sleep',
+ 'slender',
+ 'slice',
+ 'slide',
+ 'slight',
+ 'slim',
+ 'slogan',
+ 'slot',
+ 'slow',
+ 'slush',
+ 'small',
+ 'smart',
+ 'smile',
+ 'smoke',
+ 'smooth',
+ 'snack',
+ 'snake',
+ 'snap',
+ 'sniff',
+ 'snow',
+ 'soap',
+ 'soccer',
+ 'social',
+ 'sock',
+ 'soda',
+ 'soft',
+ 'solar',
+ 'soldier',
+ 'solid',
+ 'solution',
+ 'solve',
+ 'someone',
+ 'song',
+ 'soon',
+ 'sorry',
+ 'sort',
+ 'soul',
+ 'sound',
+ 'soup',
+ 'source',
+ 'south',
+ 'space',
+ 'spare',
+ 'spatial',
+ 'spawn',
+ 'speak',
+ 'special',
+ 'speed',
+ 'spell',
+ 'spend',
+ 'sphere',
+ 'spice',
+ 'spider',
+ 'spike',
+ 'spin',
+ 'spirit',
+ 'split',
+ 'spoil',
+ 'sponsor',
+ 'spoon',
+ 'sport',
+ 'spot',
+ 'spray',
+ 'spread',
+ 'spring',
+ 'spy',
+ 'square',
+ 'squeeze',
+ 'squirrel',
+ 'stable',
+ 'stadium',
+ 'staff',
+ 'stage',
+ 'stairs',
+ 'stamp',
+ 'stand',
+ 'start',
+ 'state',
+ 'stay',
+ 'steak',
+ 'steel',
+ 'stem',
+ 'step',
+ 'stereo',
+ 'stick',
+ 'still',
+ 'sting',
+ 'stock',
+ 'stomach',
+ 'stone',
+ 'stool',
+ 'story',
+ 'stove',
+ 'strategy',
+ 'street',
+ 'strike',
+ 'strong',
+ 'struggle',
+ 'student',
+ 'stuff',
+ 'stumble',
+ 'style',
+ 'subject',
+ 'submit',
+ 'subway',
+ 'success',
+ 'such',
+ 'sudden',
+ 'suffer',
+ 'sugar',
+ 'suggest',
+ 'suit',
+ 'summer',
+ 'sun',
+ 'sunny',
+ 'sunset',
+ 'super',
+ 'supply',
+ 'supreme',
+ 'sure',
+ 'surface',
+ 'surge',
+ 'surprise',
+ 'surround',
+ 'survey',
+ 'suspect',
+ 'sustain',
+ 'swallow',
+ 'swamp',
+ 'swap',
+ 'swarm',
+ 'swear',
+ 'sweet',
+ 'swift',
+ 'swim',
+ 'swing',
+ 'switch',
+ 'sword',
+ 'symbol',
+ 'symptom',
+ 'syrup',
+ 'system',
+ 'table',
+ 'tackle',
+ 'tag',
+ 'tail',
+ 'talent',
+ 'talk',
+ 'tank',
+ 'tape',
+ 'target',
+ 'task',
+ 'taste',
+ 'tattoo',
+ 'taxi',
+ 'teach',
+ 'team',
+ 'tell',
+ 'ten',
+ 'tenant',
+ 'tennis',
+ 'tent',
+ 'term',
+ 'test',
+ 'text',
+ 'thank',
+ 'that',
+ 'theme',
+ 'then',
+ 'theory',
+ 'there',
+ 'they',
+ 'thing',
+ 'this',
+ 'thought',
+ 'three',
+ 'thrive',
+ 'throw',
+ 'thumb',
+ 'thunder',
+ 'ticket',
+ 'tide',
+ 'tiger',
+ 'tilt',
+ 'timber',
+ 'time',
+ 'tiny',
+ 'tip',
+ 'tired',
+ 'tissue',
+ 'title',
+ 'toast',
+ 'tobacco',
+ 'today',
+ 'toddler',
+ 'toe',
+ 'together',
+ 'toilet',
+ 'token',
+ 'tomato',
+ 'tomorrow',
+ 'tone',
+ 'tongue',
+ 'tonight',
+ 'tool',
+ 'tooth',
+ 'top',
+ 'topic',
+ 'topple',
+ 'torch',
+ 'tornado',
+ 'tortoise',
+ 'toss',
+ 'total',
+ 'tourist',
+ 'toward',
+ 'tower',
+ 'town',
+ 'toy',
+ 'track',
+ 'trade',
+ 'traffic',
+ 'tragic',
+ 'train',
+ 'transfer',
+ 'trap',
+ 'trash',
+ 'travel',
+ 'tray',
+ 'treat',
+ 'tree',
+ 'trend',
+ 'trial',
+ 'tribe',
+ 'trick',
+ 'trigger',
+ 'trim',
+ 'trip',
+ 'trophy',
+ 'trouble',
+ 'truck',
+ 'true',
+ 'truly',
+ 'trumpet',
+ 'trust',
+ 'truth',
+ 'try',
+ 'tube',
+ 'tuition',
+ 'tumble',
+ 'tuna',
+ 'tunnel',
+ 'turkey',
+ 'turn',
+ 'turtle',
+ 'twelve',
+ 'twenty',
+ 'twice',
+ 'twin',
+ 'twist',
+ 'two',
+ 'type',
+ 'typical',
+ 'ugly',
+ 'umbrella',
+ 'unable',
+ 'unaware',
+ 'uncle',
+ 'uncover',
+ 'under',
+ 'undo',
+ 'unfair',
+ 'unfold',
+ 'unhappy',
+ 'uniform',
+ 'unique',
+ 'unit',
+ 'universe',
+ 'unknown',
+ 'unlock',
+ 'until',
+ 'unusual',
+ 'unveil',
+ 'update',
+ 'upgrade',
+ 'uphold',
+ 'upon',
+ 'upper',
+ 'upset',
+ 'urban',
+ 'urge',
+ 'usage',
+ 'use',
+ 'used',
+ 'useful',
+ 'useless',
+ 'usual',
+ 'utility',
+ 'vacant',
+ 'vacuum',
+ 'vague',
+ 'valid',
+ 'valley',
+ 'valve',
+ 'van',
+ 'vanish',
+ 'vapor',
+ 'various',
+ 'vast',
+ 'vault',
+ 'vehicle',
+ 'velvet',
+ 'vendor',
+ 'venture',
+ 'venue',
+ 'verb',
+ 'verify',
+ 'version',
+ 'very',
+ 'vessel',
+ 'veteran',
+ 'viable',
+ 'vibrant',
+ 'vicious',
+ 'victory',
+ 'video',
+ 'view',
+ 'village',
+ 'vintage',
+ 'violin',
+ 'virtual',
+ 'virus',
+ 'visa',
+ 'visit',
+ 'visual',
+ 'vital',
+ 'vivid',
+ 'vocal',
+ 'voice',
+ 'void',
+ 'volcano',
+ 'volume',
+ 'vote',
+ 'voyage',
+ 'wage',
+ 'wagon',
+ 'wait',
+ 'walk',
+ 'wall',
+ 'walnut',
+ 'want',
+ 'warfare',
+ 'warm',
+ 'warrior',
+ 'wash',
+ 'wasp',
+ 'waste',
+ 'water',
+ 'wave',
+ 'way',
+ 'wealth',
+ 'weapon',
+ 'wear',
+ 'weasel',
+ 'weather',
+ 'web',
+ 'wedding',
+ 'weekend',
+ 'weird',
+ 'welcome',
+ 'west',
+ 'wet',
+ 'whale',
+ 'what',
+ 'wheat',
+ 'wheel',
+ 'when',
+ 'where',
+ 'whip',
+ 'whisper',
+ 'wide',
+ 'width',
+ 'wife',
+ 'wild',
+ 'will',
+ 'win',
+ 'window',
+ 'wine',
+ 'wing',
+ 'wink',
+ 'winner',
+ 'winter',
+ 'wire',
+ 'wisdom',
+ 'wise',
+ 'wish',
+ 'witness',
+ 'wolf',
+ 'woman',
+ 'wonder',
+ 'wood',
+ 'wool',
+ 'word',
+ 'work',
+ 'world',
+ 'worry',
+ 'worth',
+ 'wrap',
+ 'wreck',
+ 'wrestle',
+ 'wrist',
+ 'write',
+ 'wrong',
+ 'yard',
+ 'year',
+ 'yellow',
+ 'you',
+ 'young',
+ 'youth',
+ 'zebra',
+ 'zero',
+ 'zone',
+ 'zoo'
+];
diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt
index b551d62..2356c84 100644
--- a/linux/CMakeLists.txt
+++ b/linux/CMakeLists.txt
@@ -113,6 +113,9 @@ else()
set(LIB_TRIPLET "x86_64-linux-gnu")
endif()
+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"
+ COMPONENT Runtime)
+
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"
COMPONENT Runtime)
diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj
index f1b1fda..e29989c 100644
--- a/macos/Runner.xcodeproj/project.pbxproj
+++ b/macos/Runner.xcodeproj/project.pbxproj
@@ -28,6 +28,7 @@
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
83FF6BF911B29965D3589BE7 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BD06F8F6E3797AC031038136 /* Pods_Runner.framework */; };
9F565D5929954F53009A75FB /* secRandom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F565D5729954F53009A75FB /* secRandom.swift */; };
+ CE46BF642F15C020000A6140 /* WarpApiFFI.xcframework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE46BF632F15C020000A6140 /* WarpApiFFI.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
CE75FC4A2C147EBA00CCC46E /* wownero_libwallet2_api_c.dylib in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE75FC492C147EBA00CCC46E /* wownero_libwallet2_api_c.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
CED5DBE42BE59BBF0065028F /* monero_libwallet2_api_c.dylib in CopyFiles */ = {isa = PBXBuildFile; fileRef = CED5DBE32BE59BBF0065028F /* monero_libwallet2_api_c.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
/* End PBXBuildFile section */
@@ -59,6 +60,7 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
+ CE46BF642F15C020000A6140 /* WarpApiFFI.xcframework in CopyFiles */,
CE75FC4A2C147EBA00CCC46E /* wownero_libwallet2_api_c.dylib in CopyFiles */,
CED5DBE42BE59BBF0065028F /* monero_libwallet2_api_c.dylib in CopyFiles */,
);
@@ -90,6 +92,7 @@
9F1E7BFB2BF2D27500C28C9A /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
9F565D5729954F53009A75FB /* secRandom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = secRandom.swift; path = CakeWallet/secRandom.swift; sourceTree = "<group>"; };
BD06F8F6E3797AC031038136 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ CE46BF632F15C020000A6140 /* WarpApiFFI.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = WarpApiFFI.xcframework; path = ../scripts/zcash_lib/WarpApiFFI.xcframework; sourceTree = "<group>"; };
CE75FC492C147EBA00CCC46E /* wownero_libwallet2_api_c.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = wownero_libwallet2_api_c.dylib; sourceTree = "<group>"; };
CED5DBE32BE59BBF0065028F /* monero_libwallet2_api_c.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = monero_libwallet2_api_c.dylib; sourceTree = "<group>"; };
/* End PBXFileReference section */
@@ -120,6 +123,7 @@
33CC10E42044A3C60003C045 = {
isa = PBXGroup;
children = (
+ CE46BF632F15C020000A6140 /* WarpApiFFI.xcframework */,
CE75FC492C147EBA00CCC46E /* wownero_libwallet2_api_c.dylib */,
CED5DBE32BE59BBF0065028F /* monero_libwallet2_api_c.dylib */,
9F565D5729954F53009A75FB /* secRandom.swift */,
diff --git a/model_generator.sh b/model_generator.sh
index e5efb06..20de89b 100755
--- a/model_generator.sh
+++ b/model_generator.sh
@@ -1,7 +1,7 @@
#!/bin/bash
set -x -e
-for cwcoin in cw_{core,evm,monero,bitcoin,nano,bitcoin_cash,solana,tron,wownero,zano,decred,dogecoin}
+for cwcoin in cw_{core,evm,monero,bitcoin,nano,bitcoin_cash,solana,tron,wownero,zano,decred,dogecoin,zcash}
do
if [[ "x$1" == "xasync" ]];
then
diff --git a/pubspec_base.yaml b/pubspec_base.yaml
index 4552b00..f5ecd6e 100644
--- a/pubspec_base.yaml
+++ b/pubspec_base.yaml
@@ -241,9 +241,12 @@ flutter:
- assets/dogecoin_electrum_server_list.yml
- assets/base_node_list.yml
- assets/arbitrum_node_list.yml
+ - assets/zcash_node_list.yml
- assets/text/
- assets/faq/
- assets/animation/
+ - scripts/zcash_lib/assets/sapling-spend.params
+ - scripts/zcash_lib/assets/sapling-output.params
fonts:
- family: Lato
diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb
index 8923ad4..4381285 100644
--- a/res/values/strings_cs.arb
+++ b/res/values/strings_cs.arb
@@ -515,7 +515,7 @@
"matrix_green_dark_theme": "Tmavé téma Matrix Green",
"max_amount": "Max: ${value}",
"max_value": "Max: ${value} ${currency}",
- "memo": "Memo:",
+ "memo": "Memo",
"message": "Zpráva",
"message_verified": "Zpráva byla úspěšně ověřena",
"messages": "Zprávy",
@@ -1172,4 +1172,4 @@
"youCanGoBackToYourDapp": "Nyní se můžete vrátit do svého dappu",
"your": "Vaše",
"yy": "YY"
-}
\ No newline at end of file
+}
diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb
index eacc316..8f0d7eb 100644
--- a/res/values/strings_de.arb
+++ b/res/values/strings_de.arb
@@ -515,7 +515,7 @@
"matrix_green_dark_theme": "Matrix Green Dark Theme",
"max_amount": "Max: ${value}",
"max_value": "Max: ${value} ${currency}",
- "memo": "Memo:",
+ "memo": "Memo",
"message": "Nachricht",
"message_verified": "Die Nachricht wurde erfolgreich überprüft",
"messages": "Nachrichten",
diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb
index 6ae55d7..fdc515e 100644
--- a/res/values/strings_en.arb
+++ b/res/values/strings_en.arb
@@ -515,7 +515,7 @@
"matrix_green_dark_theme": "Matrix Green Dark Theme",
"max_amount": "Max: ${value}",
"max_value": "Max: ${value} ${currency}",
- "memo": "Memo:",
+ "memo": "Memo",
"message": "Message",
"message_verified": "The message was successfully verified",
"messages": "Messages",
@@ -1173,4 +1173,4 @@
"youCanGoBackToYourDapp": "You can go back to your dApp now",
"your": "Your",
"yy": "YY"
-}
\ No newline at end of file
+}
diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb
index 738e956..d0a4437 100644
--- a/res/values/strings_es.arb
+++ b/res/values/strings_es.arb
@@ -515,7 +515,7 @@
"matrix_green_dark_theme": "Tema oscuro Matrix Green",
"max_amount": "Máx: ${value}",
"max_value": "Máx: ${value} ${currency}",
- "memo": "Memo:",
+ "memo": "Memo",
"message": "Mensaje",
"message_verified": "El mensaje se verificó con éxito",
"messages": "Mensajes",
@@ -1174,4 +1174,4 @@
"youCanGoBackToYourDapp": "Ahora puedes volver a tu dApp",
"your": "Tu",
"yy": "YY"
-}
\ No newline at end of file
+}
diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb
index 5c90824..4601874 100644
--- a/res/values/strings_ha.arb
+++ b/res/values/strings_ha.arb
@@ -515,7 +515,7 @@
"matrix_green_dark_theme": "Matrix Green Dark Jigo",
"max_amount": "Max: ${value}",
"max_value": "Max: ${value} ${currency}",
- "memo": "Memo:",
+ "memo": "Memo",
"message": "Sako",
"message_verified": "An yi nasarar tabbatar da sakon",
"messages": "Manegiya",
@@ -1175,4 +1175,4 @@
"youCanGoBackToYourDapp": "Kuna iya komawa zuwa DPP ɗinku yanzu",
"your": "Naku",
"yy": "YY"
-}
\ No newline at end of file
+}
diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb
index 5ea9c5e..a27d7e2 100644
--- a/res/values/strings_hr.arb
+++ b/res/values/strings_hr.arb
@@ -515,7 +515,7 @@
"matrix_green_dark_theme": "Matrix Green Dark Theme",
"max_amount": "Maksimum: ${value}",
"max_value": "Maks.: ${value} ${currency}",
- "memo": "Memo:",
+ "memo": "Memo",
"message": "Poruka",
"message_verified": "Poruka je uspješno provjerena",
"messages": "Poruke",
@@ -1172,4 +1172,4 @@
"youCanGoBackToYourDapp": "Sada se možete vratiti na svoj dapp",
"your": "Vaš",
"yy": "GG"
-}
\ No newline at end of file
+}
diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb
index 39b070d..ca333a1 100644
--- a/res/values/strings_id.arb
+++ b/res/values/strings_id.arb
@@ -515,7 +515,7 @@
"matrix_green_dark_theme": "Tema Matrix Green Dark",
"max_amount": "Max: ${value}",
"max_value": "Max: ${value} ${currency}",
- "memo": "Memo:",
+ "memo": "Memo",
"message": "Pesan",
"message_verified": "Pesan itu berhasil diverifikasi",
"messages": "Pesan",
@@ -1175,4 +1175,4 @@
"youCanGoBackToYourDapp": "Anda dapat kembali ke dapp Anda sekarang",
"your": "Milikmu",
"yy": "YY"
-}
\ No newline at end of file
+}
diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb
index 93a1dd9..f37f4fa 100644
--- a/res/values/strings_it.arb
+++ b/res/values/strings_it.arb
@@ -515,7 +515,7 @@
"matrix_green_dark_theme": "Tema Matrix verde scuro",
"max_amount": "Max: ${value}",
"max_value": "Max: ${value} ${currency}",
- "memo": "Memo:",
+ "memo": "Memo",
"message": "Messaggio",
"message_verified": "Il messaggio è stato verificato con successo",
"messages": "Messaggi",
@@ -1175,4 +1175,4 @@
"youCanGoBackToYourDapp": "Puoi tornare al tuo DApp ora",
"your": "Tuo",
"yy": "YY"
-}
\ No newline at end of file
+}
diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb
index 813e493..ac50d86 100644
--- a/res/values/strings_nl.arb
+++ b/res/values/strings_nl.arb
@@ -515,7 +515,7 @@
"matrix_green_dark_theme": "Matrix groen donker thema",
"max_amount": "Max: ${value}",
"max_value": "Max: ${value} ${currency}",
- "memo": "Memo:",
+ "memo": "Memo",
"message": "Bericht",
"message_verified": "Het bericht is succesvol geverifieerd",
"messages": "Berichten",
@@ -1173,4 +1173,4 @@
"youCanGoBackToYourDapp": "U kunt nu terug naar uw DApp gaan",
"your": "Jouw",
"yy": "JJ"
-}
\ No newline at end of file
+}
diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb
index 14bbad0..c9cdf5f 100644
--- a/res/values/strings_tl.arb
+++ b/res/values/strings_tl.arb
@@ -515,7 +515,7 @@
"matrix_green_dark_theme": "Matrix Green Dark Theme",
"max_amount": "Max: ${value}",
"max_value": "Max: ${value} ${currency}",
- "memo": "Memo:",
+ "memo": "Memo",
"message": "Mensahe",
"message_verified": "Ang mensahe ay matagumpay na na -verify",
"messages": "Mga mensahe",
@@ -1172,4 +1172,4 @@
"youCanGoBackToYourDapp": "Maaari kang bumalik sa iyong dapp ngayon",
"your": "IYONG",
"yy": "YY"
-}
\ No newline at end of file
+}
diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb
index 7ffb8a2..c9f8680 100644
--- a/res/values/strings_tr.arb
+++ b/res/values/strings_tr.arb
@@ -515,7 +515,7 @@
"matrix_green_dark_theme": "Matrix Yeşil Koyu Tema",
"max_amount": "Maks: ${value}",
"max_value": "En fazla: ${value} ${currency}",
- "memo": "Memo:",
+ "memo": "Memo",
"message": "İleti",
"message_verified": "Mesaj başarıyla doğrulandı",
"messages": "Mesaj",
@@ -1172,4 +1172,4 @@
"youCanGoBackToYourDapp": "Şimdi Dapp'ınıza geri dönebilirsin",
"your": "Senin",
"yy": "YY"
-}
\ No newline at end of file
+}
diff --git a/scripts/android/app_env.sh b/scripts/android/app_env.sh
index 5e7eaef..c739ffa 100644
--- a/scripts/android/app_env.sh
+++ b/scripts/android/app_env.sh
@@ -21,8 +21,8 @@ MONERO_COM_PACKAGE="com.monero.app"
MONERO_COM_SCHEME="monero.com"
CAKEWALLET_NAME="Cake Wallet"
-CAKEWALLET_VERSION="5.6.5"
-CAKEWALLET_BUILD_NUMBER=4296
+CAKEWALLET_VERSION="5.7.0"
+CAKEWALLET_BUILD_NUMBER=4298
CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet"
CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet"
CAKEWALLET_SCHEME="cakewallet"
diff --git a/scripts/android/build_zcash.sh b/scripts/android/build_zcash.sh
new file mode 100755
index 0000000..eca4693
--- /dev/null
+++ b/scripts/android/build_zcash.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+set -x -e
+cd "$(dirname "$0")"
+
+../prepare_zcash.sh
+
+pushd ../zcash_lib
+ ./build-scripts/build-android.sh
+popd
diff --git a/scripts/android/pubspec_gen.sh b/scripts/android/pubspec_gen.sh
index 1d4dcad..f95f635 100755
--- a/scripts/android/pubspec_gen.sh
+++ b/scripts/android/pubspec_gen.sh
@@ -10,7 +10,7 @@ case $APP_ANDROID_TYPE in
CONFIG_ARGS="--monero"
;;
$CAKEWALLET)
- CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred --dogecoin --base --arbitrum"
+ CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred --dogecoin --base --zcash --arbitrum"
;;
esac
diff --git a/scripts/build_bitbox_flutter.sh b/scripts/build_bitbox_flutter.sh
index 00693ab..7d790a5 100755
--- a/scripts/build_bitbox_flutter.sh
+++ b/scripts/build_bitbox_flutter.sh
@@ -1,9 +1,17 @@
#!/bin/bash
+set -x -e
-git clone https://github.com/konstantinullrich/bitbox_flutter
-cd bitbox_flutter
+if [[ ! -d "bitbox_flutter" ]];
+then
+ git clone https://github.com/konstantinullrich/bitbox_flutter
+fi
+cd bitbox_flutter
+git fetch -a
+git reset --hard
git checkout 5a6e6dd388ef64003f86094af80d5453518b601d
+git reset --hard
+
bash ./build_bindings.sh --dont-install
FILE=api.aar
diff --git a/scripts/ios/app_config.sh b/scripts/ios/app_config.sh
index 9a8ee84..8991921 100755
--- a/scripts/ios/app_config.sh
+++ b/scripts/ios/app_config.sh
@@ -31,7 +31,7 @@ case $APP_IOS_TYPE in
;;
$CAKEWALLET)
- CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred --dogecoin --base --arbitrum"
+ CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred --dogecoin --base --zcash --arbitrum"
;;
esac
diff --git a/scripts/ios/app_env.sh b/scripts/ios/app_env.sh
index b3b2d8d..36cb36a 100644
--- a/scripts/ios/app_env.sh
+++ b/scripts/ios/app_env.sh
@@ -17,8 +17,8 @@ MONERO_COM_BUILD_NUMBER=153
MONERO_COM_BUNDLE_ID="com.cakewallet.monero"
CAKEWALLET_NAME="Cake Wallet"
-CAKEWALLET_VERSION="5.6.5"
-CAKEWALLET_BUILD_NUMBER=360
+CAKEWALLET_VERSION="5.7.0"
+CAKEWALLET_BUILD_NUMBER=363
CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"
diff --git a/scripts/ios/build_zcash.sh b/scripts/ios/build_zcash.sh
new file mode 100755
index 0000000..96a8e49
--- /dev/null
+++ b/scripts/ios/build_zcash.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+set -x -e
+cd "$(dirname "$0")"
+
+../prepare_zcash.sh
+
+pushd ../zcash_lib
+ ./build-scripts/mac/build-mac.sh
+popd
diff --git a/scripts/lint.sh b/scripts/lint.sh
new file mode 100755
index 0000000..c4b1b4e
--- /dev/null
+++ b/scripts/lint.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+set -x -e
+cd $(dirname $0)
+cd ..
+
+for i in cw_zcash;
+do
+ dart fix --apply $i/
+ dart format --line-length=100 $i/
+ if [[ ! "x$ABORT_ON_CHANGE" == "x" ]];
+ then
+ if [[ ! -z "$(git status --porcelain -- $(find $i))" ]];
+ then
+ echo "Please run scripts/lint.sh ($i has changes)"
+ exit 1
+ fi
+ fi
+done
diff --git a/scripts/linux/build_zcash.sh b/scripts/linux/build_zcash.sh
new file mode 100755
index 0000000..07f85f0
--- /dev/null
+++ b/scripts/linux/build_zcash.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+set -x -e
+cd "$(dirname "$0")"
+
+../prepare_zcash.sh
+
+pushd ../zcash_lib
+ ./build-scripts/build-linux.sh
+popd
diff --git a/scripts/macos/app_config.sh b/scripts/macos/app_config.sh
index 589b5ec..21e8ada 100755
--- a/scripts/macos/app_config.sh
+++ b/scripts/macos/app_config.sh
@@ -36,7 +36,7 @@ case $APP_MACOS_TYPE in
$MONERO_COM)
CONFIG_ARGS="--monero";;
$CAKEWALLET)
- CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --dogecoin --base --arbitrum";;
+ CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --dogecoin --base --zcash --arbitrum";;
esac
cp -rf pubspec_description.yaml pubspec.yaml
diff --git a/scripts/macos/app_env.sh b/scripts/macos/app_env.sh
index 5916e06..d1c41b6 100755
--- a/scripts/macos/app_env.sh
+++ b/scripts/macos/app_env.sh
@@ -16,13 +16,13 @@ if [ -n "$1" ]; then
fi
MONERO_COM_NAME="Monero.com"
-MONERO_COM_VERSION="5.6.5"
-MONERO_COM_BUILD_NUMBER=73
+MONERO_COM_VERSION="5.6.6"
+MONERO_COM_BUILD_NUMBER=74
MONERO_COM_BUNDLE_ID="com.cakewallet.monero"
CAKEWALLET_NAME="Cake Wallet"
-CAKEWALLET_VERSION="5.6.5"
-CAKEWALLET_BUILD_NUMBER=140
+CAKEWALLET_VERSION="5.7.0"
+CAKEWALLET_BUILD_NUMBER=142
CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"
if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then
diff --git a/scripts/macos/build_zcash.sh b/scripts/macos/build_zcash.sh
new file mode 100755
index 0000000..96a8e49
--- /dev/null
+++ b/scripts/macos/build_zcash.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+set -x -e
+cd "$(dirname "$0")"
+
+../prepare_zcash.sh
+
+pushd ../zcash_lib
+ ./build-scripts/mac/build-mac.sh
+popd
diff --git a/scripts/prepare_torch.sh b/scripts/prepare_torch.sh
index d918051..d8ecbf2 100755
--- a/scripts/prepare_torch.sh
+++ b/scripts/prepare_torch.sh
@@ -7,7 +7,7 @@ cd "$(dirname "$0")"
# - .github/workflows/pr_test_build_linux.yml
# https://github.com/MrCyjaneK/torch_dart/releases/download/v1.0.17/torch_dart-v1.0.17.tar.gz
-HASH=85130c85ad80f4596896f5fa4dc1a160cf0f0487
+HASH=d52b9ebcc2e3677520e51bd8a54d771c353587b8
if [[ ! -d "torch_dart/.git" ]];
then
diff --git a/scripts/prepare_zcash.sh b/scripts/prepare_zcash.sh
new file mode 100755
index 0000000..5abc321
--- /dev/null
+++ b/scripts/prepare_zcash.sh
@@ -0,0 +1,28 @@
+#!/bin/bash
+set -x -e
+cd "$(dirname "$0")"
+
+HASH=647231e2e6d93c1d6c16d4019cd3a11a3aaa9504
+
+if [[ ! -d "zcash_lib/.git" ]];
+then
+ rm -rf zcash_lib
+ git clone https://github.com/MrCyjaneK/zwallet.git zcash_lib
+ cd zcash_lib
+else
+ cd zcash_lib
+ git fetch -a
+fi
+
+
+git reset --hard
+git checkout $HASH
+git reset --hard
+git submodule update --init --force --recursive
+
+# in go I could nicely replace => the heck out of it
+# sadly rust is not go
+find . -name Cargo.toml -exec sed -i.bak -E '
+s|rusqlite[[:space:]]*=[[:space:]]*\{[^}]*\}|rusqlite = { version = "0.29.0", features = ["bundled", "modern_sqlite", "backup"] }|g
+' {} +
+# find . -name Cargo.toml -exec sed -i.bak -E 's|r2d2_sqlite[[:space:]]*=[[:space:]]*([^\n]*)|r2d2_sqlite = "0.32.0"|g' {} +
diff --git a/tool/configure.dart b/tool/configure.dart
index f8d40a9..3781483 100644
--- a/tool/configure.dart
+++ b/tool/configure.dart
@@ -11,6 +11,7 @@ const zanoOutputPath = 'lib/zano/zano.dart';
const decredOutputPath = 'lib/decred/decred.dart';
const dogecoinOutputPath = 'lib/dogecoin/dogecoin.dart';
const evmOutputPath = 'lib/evm/evm.dart';
+const zcashOutputPath = 'lib/zcash/zcash.dart';
const walletTypesPath = 'lib/wallet_types.g.dart';
const secureStoragePath = 'lib/core/secure_storage.dart';
const pubspecDefaultPath = 'pubspec_default.yaml';
@@ -33,6 +34,7 @@ Future<void> main(List<String> args) async {
final hasDogecoin = args.contains('${prefix}dogecoin');
final hasBase = args.contains('${prefix}base');
final hasArbitrum = args.contains('${prefix}arbitrum');
+ final hasZcash = args.contains('${prefix}zcash');
final hasEVM = hasEthereum || hasPolygon || hasBase || hasArbitrum;
final excludeFlutterSecureStorage = args.contains('${prefix}excludeFlutterSecureStorage');
@@ -48,6 +50,7 @@ Future<void> main(List<String> args) async {
await generateDecred(hasDecred);
await generateDogecoin(hasDogecoin);
await generateEVM(hasEVM);
+ await generateZcash(hasZcash);
await generatePubspec(
hasMonero: hasMonero,
@@ -66,6 +69,7 @@ Future<void> main(List<String> args) async {
hasDogecoin: hasDogecoin,
hasBase: hasBase,
hasArbitrum: hasArbitrum,
+ hasZcash: hasZcash,
);
await generateWalletTypes(
hasMonero: hasMonero,
@@ -83,6 +87,7 @@ Future<void> main(List<String> args) async {
hasDogecoin: hasDogecoin,
hasBase: hasBase,
hasArbitrum: hasArbitrum,
+ hasZcash: hasZcash,
);
await injectSecureStorage(!excludeFlutterSecureStorage);
}
@@ -149,7 +154,7 @@ import 'package:mobx/mobx.dart';
""";
const bitcoinCwPart = "part 'cw_bitcoin.dart';";
const bitcoinContent = """
-
+
class ElectrumSubAddress {
ElectrumSubAddress({
required this.id,
@@ -396,7 +401,7 @@ abstract class MoneroWalletDetails {
abstract class Monero {
MoneroAccountList getAccountList(Object wallet);
-
+
MoneroSubaddressList getSubaddressList(Object wallet);
TransactionHistoryBase getTransactionHistory(Object wallet);
@@ -414,7 +419,7 @@ abstract class Monero {
TransactionPriority deserializeMoneroTransactionPriority({required int raw});
List<TransactionPriority> getTransactionPriorities();
List<String> getMoneroWordList(String language);
-
+
List<Unspent> getUnspents(Object wallet);
Future<void> updateUnspents(Object wallet);
@@ -603,7 +608,7 @@ abstract class WowneroWalletDetails {
abstract class Wownero {
WowneroAccountList getAccountList(Object wallet);
-
+
WowneroSubaddressList getSubaddressList(Object wallet);
TransactionHistoryBase getTransactionHistory(Object wallet);
@@ -621,7 +626,7 @@ abstract class Wownero {
TransactionPriority deserializeWowneroTransactionPriority({required int raw});
List<TransactionPriority> getTransactionPriorities();
ListWhy this scored 32/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.