mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-17 05:41:29 +02:00
Ywallet zcash (#2810)
* feat(zcash): initial commit - initial work on cw_zcash - use single .db for everything zcash to speed up sync and scanning - implement silent autoshielding of non-orchard funds - use FFI for everything to fetch the data - add support for passphrase by adding extra word to seed phrase - enforce lints on CI for cw_zcash directory - use correct zcash name, and call transparent zcash tZEC - minor cleanups outsite of cw_zcash - gitmodules, gitignore - db debug - fix broken addressPageType in WalletInfo - enable debug options in kDebugMode and kProfileMode * feat: T address rotation (and cache) wip: minor fixes * wip: shield tx list * fix fiat amounts fix derrivation of rotation account addresses screen for zcash block height fix T address shields improvements Amount in disposable T shields UI improvements show pending outgoing tx in tx history * fix null check on passphrase page fix tx showing as pending rename auto shield txs fixed send all to include fee use rotating address in addressForExchange wrap all calls of WarpApi.getLatestHeight in try catch to prevent leaking errors other minor fixes from notion * wallet group debug cache sent tx address wallet T address rotation fixes automatically pick the fee for auto-shield * fix T address cache deserialization fix autoshield icon fix autoshield text getCachedDestinationAddress fix send address cache * address comments from review * wip: iOS fix * fix swap * Allow cakepay payment from zcash wallet * fix: ios (I'm stupid) fix: reorder addresses to make orchard default fix: send all balance * 5.6.6 bump fix: ios apple review add transport error to ignored exceptions * fix: leaving dust behind when sweeping all * Change wallets order * fix: reflect balance of pending out txs fix: restore height support * linux support (disabled) * fix: date picker for zcash on restore screen fix: build script for linux match WarpApi.warpSync args with upstream * fix: linter script * ensure all exchange providers support ZEC (#2802) * ensure all exchange providers support ZEC * remove zaddr and tzec currencies and refactor providers * Update lib/exchange/provider/near_Intents_exchange_provider.dart * add back old zcash currencies for backward compatibility, thanks Czarek * minor fix [skip ci] --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * Update android/app/src/main/AndroidManifestBase.xml [skip ci] * Update ios/Runner/InfoBase.plist [skip ci] * fix: remove unused code fix: macos fix: disable on linux and macos * update build numbers [skip ci] * Add zcash to birdpay --------- Co-authored-by: Czarek Nakamoto <cyjan@mrcyjanek.net> Co-authored-by: Serhii <borodenko.sv@gmail.com>
This commit is contained in:
co-authored by
Czarek Nakamoto
Serhii
parent
2a77d359f1
commit
ef9b0fbae6
@@ -4,7 +4,6 @@ on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
|
||||
# -----------------------------------------
|
||||
# PATH A: Internal PRs
|
||||
internal-build:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
done
|
||||
|
||||
+7
-2
@@ -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
|
||||
fiat-check-output.txt
|
||||
scripts/zcash_lib
|
||||
scripts/android/yttrium
|
||||
scripts/bitbox_flutter
|
||||
scripts/reown_flutter
|
||||
|
||||
@@ -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
|
||||
@@ -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" />
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.0 KiB |
@@ -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>
|
||||
|
After Width: | Height: | Size: 290 B |
Binary file not shown.
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 32 KiB |
@@ -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>
|
||||
|
After Width: | Height: | Size: 443 B |
@@ -1 +1,3 @@
|
||||
Add Zcash wallet
|
||||
Generic enhancements
|
||||
Bug fixes
|
||||
@@ -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
|
||||
@@ -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"
|
||||
|
||||
@@ -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});
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -270,6 +270,7 @@ class WalletInfo extends HiveObject {
|
||||
hashedWalletIdentifier,
|
||||
isNonSeedWallet,
|
||||
0,
|
||||
addressPageType,
|
||||
);
|
||||
final wiId = await walletInfo.save();
|
||||
for (final address in usedAddresses ?? <String>[]) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
## 1.0.0
|
||||
|
||||
- Initial version.
|
||||
@@ -0,0 +1 @@
|
||||
# cw_zcash
|
||||
@@ -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
|
||||
@@ -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';
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,
|
||||
];
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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.';
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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!",
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 */,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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?;
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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?,
|
||||
|
||||
@@ -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]+';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 [];
|
||||
|
||||
@@ -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:
|
||||
|
||||
+4
-1
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 [];
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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];
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
+9
-1
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()}');
|
||||
}
|
||||
|
||||
@@ -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'),
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) =>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 '';
|
||||
}
|
||||
|
||||
|
||||
@@ -231,6 +231,7 @@ abstract class HomeSettingsViewModelBase with Store {
|
||||
case WalletType.bitcoinCash:
|
||||
case WalletType.decred:
|
||||
case WalletType.dogecoin:
|
||||
case WalletType.zcash:
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -97,6 +97,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
|
||||
case WalletType.dogecoin:
|
||||
case WalletType.zano:
|
||||
case WalletType.decred:
|
||||
case WalletType.zcash:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user