Zano with passphrase (#1971)

* CW-685 Add passphrase restore for xmr/wow (#1552) * CW-685 Add passphrase restore for xmr/wow * add support for polyseed passphrase * disable 14 word seed passphrase (not supported in wownero-seed) fix: Getting grayed screen on latest passphrase build after having restored a 14-word wownero seed (+passphrase) and attempting to restore a XMR seed, legacy or otherwise. * fix pointer when restoring depracated wownero seed * Fix polyseed encryption * changes from review * remove unused code * add passphrase back to the screen add passphrase to qr code backup export * fix settings leaking through currencies on seed restore * fix monero.com builds, make passphrase a getter on WalletBase * add support for weird polyseed * store passphrase for weird polyseed * show encrypted seed only when passphrase is not empty * force set restore height * fix build issues * fix build errors * fix configure script * print -> printV * Update lib/view_model/wallet_keys_view_model.dart [skip ci] * Update lib/view_model/wallet_keys_view_model.dart [skip ci] * Update tool/configure.dart [skip ci] * Update lib/view_model/wallet_new_vm.dart * reuse existing passphrase field * remove unused passphrase field * make workflow run on pullrequests only [skip ci] [skip slack] --------- Co-authored-by: Konstantin Ullrich <konstantinullrich12@gmail.com> Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * Zano (#1793) * my experiments * Inital code for Zano integration * Added missing android log lib * added dummy wallet & some zano implementation * fixing api for zano * fixed zano build script * attempt tp fix namespace problem * added copy script for Zano files * changes for zano wallet * last updates * zano.dart test app * wallet recovery * added pending transfer, some cleanup of unused * some cleanup * send + receive qr code * last upd * updated build_zano.sh * updated zano ApiCalls, removed dummy * updated zano ApiCalls, removed dummy * added logging for get status/get info * restored old wallet.dart * restoring original versions of files * restoring original versions of files * restored get_height_by_date.dart, removed unnecessary calls for zano get height * restoring original versions * added multiple destinations, send all flag; some refactoring * logging * removed the duplicate * fixed syncing sync status, decimal division, safe null json parsing * some fixes after merge * added multibalance/asset support for zano (ui) * adding/removing from whitelist * transfers in different assets * transfers for multiple destinations and send all, some refactoring * whitelists, some refactoring * added different digits (decimal points) for formatters, some refactoring * open, create, restore wallet refactoring; whitelists * whitelists * getting and updating transaction list; restoring a wallet from QR code * several attempts to close wallet * some refactoring * added seed phrase * changed fields to BigInt, some fixes * modified build scripts for android * build scripts * restored accidently removed cw_haven.dart * inital ios integration(zano libs built) * update in script * latest changes * Applied a patch for iOS build (Boost and Zano scripts) * Removed zano.dart (script-generated) and some unnecessary files * Revert "Removed zano.dart (script-generated) and some unnecessary files" This reverts commit 367c86398ef2616006a2cbd2339ef2f30ed2800e. * Removed zano.dart (generated by scripts), some files restored to initial versions * added timer library * changed paths in build_zano.sh * build_zano.sh * edit_token_page.dart - removed flag skipZanoAddressValidation Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * lib/core/address_validator.dart - updated zano address regex lib/src/screens/dashboard/edit_token_page.dart - using AddressValidator().call * fix zano build issues on android * remove contrib/depends to save space * move async call to a synchronouse one * call sync call in isolate to make it async generate framework for iOS as well fix UR issues * zano changes from monero_c repo * update monero_c hash * fix invalid zano imports, add support for linux, speed up CI builds * update monero_c hash * bump monero_c commit (yes, again, I know) * fix wallet resttore, fix hardcoded IP * fix regex, don't throw error when opening wallet, fix tx history, fix async calls, move stuff to isolate * fix api calls in async transaction creation * update build scripts * fix some build issues * update dependencies * fix dependencies * update ci scripts * Improve multithread use of zano api * Fix build issue * fix zano node selection, move other zano calls to separate isolate * update moneroc hash WIP fixes for zano * update monero_c * fix monero.com builds * sync wallet after connecting * update monero_c * Fix windows builds * update monero_c * update monero_c * unshallow submodule * cherry pick CW-867 Wownero fixes (#1881) * fix wownero syntax error * remove print statements in zano * update zano node URL * [PATCH] Apply new CI script (https://github.com/cake-tech/cake_wallet/pull/1948) * drop env -i to fix cmake build errors on newer system * [skip ci] Revert "[PATCH] Apply new CI script (https://github.com/cake-tech/cake_wallet/pull/1948)" This reverts commit 5acb5bfe57c4b2aeb7aa40bd684a3319a68f98bc. * [run tests] [skip slack] Fix env in build * Dynamically detect number of cores used to build monero_c, since it appears that zano requires more memory to link (and it reliably fails for first couple builds due to OOM on CI/VM with memory constrains). Drop unshallowing of all modules [run tests] * Changes from review [run tests] * drop zano on linux (missing symbols) fix wownero on linux add aarch64-linux-gnu [run tests] * - remove duplicate entry in addToken() - use walletPassword in createZanoNewWalletCredentials - remove createZanoRestoreWalletFromKeysCredentials [run tests] * [skip ci] update dockerfile * fix parameter issue --------- Co-authored-by: leo <leonid.ivanov@gmail.com> Co-authored-by: cr.zoidberg <crypto.zoidberg@gmail.com> Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * - Add Zano Aliases - Enable simpleswap [skip ci] - Fix settings migration versions * push missing file [skip ci] * Zano pr with CW-685 passphrase (#1969) * my experiments * Inital code for Zano integration * Added missing android log lib * added dummy wallet & some zano implementation * fixing api for zano * fixed zano build script * attempt tp fix namespace problem * added copy script for Zano files * changes for zano wallet * last updates * zano.dart test app * wallet recovery * added pending transfer, some cleanup of unused * some cleanup * send + receive qr code * last upd * updated build_zano.sh * updated zano ApiCalls, removed dummy * updated zano ApiCalls, removed dummy * added logging for get status/get info * restored old wallet.dart * restoring original versions of files * restoring original versions of files * restored get_height_by_date.dart, removed unnecessary calls for zano get height * restoring original versions * added multiple destinations, send all flag; some refactoring * logging * removed the duplicate * fixed syncing sync status, decimal division, safe null json parsing * some fixes after merge * added multibalance/asset support for zano (ui) * adding/removing from whitelist * transfers in different assets * transfers for multiple destinations and send all, some refactoring * whitelists, some refactoring * added different digits (decimal points) for formatters, some refactoring * open, create, restore wallet refactoring; whitelists * whitelists * getting and updating transaction list; restoring a wallet from QR code * several attempts to close wallet * some refactoring * added seed phrase * CW-685 Add passphrase restore for xmr/wow * add support for polyseed passphrase * disable 14 word seed passphrase (not supported in wownero-seed) fix: Getting grayed screen on latest passphrase build after having restored a 14-word wownero seed (+passphrase) and attempting to restore a XMR seed, legacy or otherwise. * fix pointer when restoring depracated wownero seed * Fix polyseed encryption * changed fields to BigInt, some fixes * modified build scripts for android * build scripts * restored accidently removed cw_haven.dart * inital ios integration(zano libs built) * update in script * latest changes * changes from review * remove unused code * add passphrase back to the screen add passphrase to qr code backup export * fix settings leaking through currencies on seed restore * fix monero.com builds, make passphrase a getter on WalletBase * add support for weird polyseed * store passphrase for weird polyseed * show encrypted seed only when passphrase is not empty * force set restore height * Applied a patch for iOS build (Boost and Zano scripts) * Removed zano.dart (script-generated) and some unnecessary files * Revert "Removed zano.dart (script-generated) and some unnecessary files" This reverts commit 367c86398ef2616006a2cbd2339ef2f30ed2800e. * Removed zano.dart (generated by scripts), some files restored to initial versions * fix build issues * fix build errors * added timer library * changed paths in build_zano.sh * build_zano.sh * edit_token_page.dart - removed flag skipZanoAddressValidation Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * lib/core/address_validator.dart - updated zano address regex lib/src/screens/dashboard/edit_token_page.dart - using AddressValidator().call * fix zano build issues on android * remove contrib/depends to save space * move async call to a synchronouse one * call sync call in isolate to make it async generate framework for iOS as well fix UR issues * zano changes from monero_c repo * update monero_c hash * fix invalid zano imports, add support for linux, speed up CI builds * update monero_c hash * bump monero_c commit (yes, again, I know) * fix wallet resttore, fix hardcoded IP * fix regex, don't throw error when opening wallet, fix tx history, fix async calls, move stuff to isolate * fix api calls in async transaction creation * fix configure script * update build scripts * fix some build issues * update dependencies * fix dependencies * update ci scripts * Improve multithread use of zano api * Fix build issue * fix zano node selection, move other zano calls to separate isolate * update moneroc hash WIP fixes for zano * update monero_c * fix monero.com builds * sync wallet after connecting * update monero_c * Fix windows builds * update monero_c * print -> printV * update monero_c * unshallow submodule * cherry pick CW-867 Wownero fixes (#1881) * fix wownero syntax error * remove print statements in zano * update zano node URL * [PATCH] Apply new CI script (https://github.com/cake-tech/cake_wallet/pull/1948) * drop env -i to fix cmake build errors on newer system * Update lib/view_model/wallet_keys_view_model.dart [skip ci] * Update lib/view_model/wallet_keys_view_model.dart [skip ci] * Update tool/configure.dart [skip ci] * Update lib/view_model/wallet_new_vm.dart * [skip ci] Revert "[PATCH] Apply new CI script (https://github.com/cake-tech/cake_wallet/pull/1948)" This reverts commit 5acb5bfe57c4b2aeb7aa40bd684a3319a68f98bc. * [run tests] [skip slack] Fix env in build * Dynamically detect number of cores used to build monero_c, since it appears that zano requires more memory to link (and it reliably fails for first couple builds due to OOM on CI/VM with memory constrains). Drop unshallowing of all modules [run tests] * Changes from review [run tests] * drop zano on linux (missing symbols) fix wownero on linux add aarch64-linux-gnu [run tests] * - remove duplicate entry in addToken() - use walletPassword in createZanoNewWalletCredentials - remove createZanoRestoreWalletFromKeysCredentials [run tests] * [skip ci] update dockerfile * reuse existing passphrase field * add passphrase support for zano * Drop aarch64-linux-gnu for now. * fix passphrase display, fix gray screen * catch errors in polyseed encryption, encrypt only polyseed, fix coin in wownero * update monero_c update wownero to 0.11.3.0 * Show passphrase only when non-empty, fix passphrase being displayed as view key private. * fix NanoAccountListPage showing up instead of MoneroAccountListPage for wownero * build zano dependencies on android * fix parameter issue * minor merge leftover [skip ci] * minor cleanup [skip ci] * fix zano alias update eth url for ens lookup change $MAKE_JOB_COUNT to $NPROC * minor cleanup [skip ci] * fix zano alias * Disable passphrase for creation of xmr/wow/zano minor fixes * fix zano on iOS * - Fix get token data - Enable unavailable balance - Enable confirmations count - Adjust explorer link --------- Co-authored-by: leo <leonid.ivanov@gmail.com> Co-authored-by: Konstantin Ullrich <konstantinullrich12@gmail.com> Co-authored-by: cr.zoidberg <crypto.zoidberg@gmail.com> Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> --------- Co-authored-by: cyan <cyjan@mrcyjanek.net> Co-authored-by: Konstantin Ullrich <konstantinullrich12@gmail.com> Co-authored-by: leo <leonid.ivanov@gmail.com> Co-authored-by: cr.zoidberg <crypto.zoidberg@gmail.com>

Omar Hatem committed Jan 24, 2025 at 20:33 UTC df3a26dc15f145579bf8467dfa45a2cb7d4526a1
169 files changed +6113 -212
.github/workflows/pr_test_build_android.yml
+1 -1
@@ -1,6 +1,6 @@
1 name: Cake Wallet Android
2
3 -on: [push]
3 +on: [pull_request]
4
5 defaults:
6 run:
.github/workflows/pr_test_build_linux.yml
+1 -1
@@ -1,6 +1,6 @@
1 name: Cake Wallet Linux
2
3 -on: [push]
3 +on: [pull_request]
4
5 defaults:
6 run:
.gitignore
+4 -1
@@ -126,7 +126,7 @@ cw_shared_external/ios/External/
126 cw_haven/ios/External/
127 cw_haven/android/.externalNativeBuild/
128 cw_haven/android/.cxx/
129 -
129 +cw_zano/ios/External/
130 lib/bitcoin/bitcoin.dart
131 lib/monero/monero.dart
132 lib/haven/haven.dart
@@ -137,6 +137,7 @@ lib/polygon/polygon.dart
137 lib/solana/solana.dart
138 lib/tron/tron.dart
139 lib/wownero/wownero.dart
140 +lib/zano/zano.dart
141
142 ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png
143 ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png
@@ -179,3 +180,5 @@ scripts/monero_c
180 # iOS generated framework bin
181 ios/MoneroWallet.framework/MoneroWallet
182 ios/WowneroWallet.framework/WowneroWallet
183 +ios/ZanoWallet.framework/ZanoWallet
184 +*_libwallet2_api_c.dylib
analysis_options.yaml
+1
@@ -22,6 +22,7 @@ analyzer:
22 lib/solana/cw_solana.dart,
23 lib/tron/cw_tron.dart,
24 lib/wownero/cw_wownero.dart,
25 + lib/zano/cw_zano.dart,
26 ]
27 language:
28 strict-casts: true
android/app/src/main/AndroidManifestBase.xml
+3
@@ -89,6 +89,9 @@
89 <data android:scheme="wownero" />
90 <data android:scheme="wownero-wallet" />
91 <data android:scheme="wownero_wallet" />
92 + <data android:scheme="zano" />
93 + <data android:scheme="zano-wallet" />
94 + <data android:scheme="zano_wallet" />
95 </intent-filter>
96 <!-- nano-gpt link scheme -->
97 <intent-filter android:autoVerify="true">
android/app/src/main/jniLibs/arm64-v8a/libzano_libwallet2_api_c.so new
+1
@@ -0,0 +1 @@
1 +../../../../../../scripts/monero_c/release/zano/aarch64-linux-android_libwallet2_api_c.so
\ No newline at end of file
android/app/src/main/jniLibs/armeabi-v7a/libzano_libwallet2_api_c.so new
+1
@@ -0,0 +1 @@
1 +../../../../../../scripts/monero_c/release/zano/armv7a-linux-androideabi_libwallet2_api_c.so
\ No newline at end of file
android/app/src/main/jniLibs/x86_64/libzano_libwallet2_api_c.so new
+1
@@ -0,0 +1 @@
1 +../../../../../../scripts/monero_c/release/zano/x86_64-linux-android_libwallet2_api_c.so
\ No newline at end of file
assets/images/zano_icon.png
Binary files /dev/null and b/assets/images/zano_icon.png differ
assets/zano_node_list.yml new
+4
@@ -0,0 +1,4 @@
1 +-
2 + uri: 37.27.100.59:10500
3 + is_default: true
4 + useSSL: false
\ No newline at end of file
cw_bitcoin/pubspec.lock
+16
@@ -316,6 +316,14 @@ packages:
316 url: "https://pub.dev"
317 source: hosted
318 version: "0.7.10"
319 + decimal:
320 + dependency: transitive
321 + description:
322 + name: decimal
323 + sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21"
324 + url: "https://pub.dev"
325 + source: hosted
326 + version: "2.3.3"
327 encrypt:
328 dependency: transitive
329 description:
@@ -797,6 +805,14 @@ packages:
805 url: "https://pub.dev"
806 source: hosted
807 version: "3.2.2"
808 + rational:
809 + dependency: transitive
810 + description:
811 + name: rational
812 + sha256: cb808fb6f1a839e6fc5f7d8cb3b0a10e1db48b3be102de73938c627f0b636336
813 + url: "https://pub.dev"
814 + source: hosted
815 + version: "2.2.3"
816 rxdart:
817 dependency: "direct main"
818 description:
cw_core/lib/amount_converter.dart
+10
@@ -1,3 +1,5 @@
1 +import 'package:decimal/decimal.dart';
2 +import 'package:decimal/intl.dart';
3 import 'package:intl/intl.dart';
4 import 'package:cw_core/crypto_currency.dart';
5
@@ -43,6 +45,8 @@ class AmountConverter {
45 case CryptoCurrency.xnzd:
46 case CryptoCurrency.xusd:
47 return _moneroAmountToString(amount);
48 + case CryptoCurrency.zano:
49 + return _moneroAmountToStringUsingDecimals(amount);
50 default:
51 return '';
52 }
@@ -59,4 +63,10 @@ class AmountConverter {
63
64 static String _wowneroAmountToString(int amount) => _wowneroAmountFormat
65 .format(cryptoAmountToDouble(amount: amount, divider: _wowneroAmountDivider));
66 +
67 + static Decimal cryptoAmountToDecimal({required int amount, required int divider}) =>
68 + (Decimal.fromInt(amount) / Decimal.fromInt(divider)).toDecimal();
69 +
70 + static String _moneroAmountToStringUsingDecimals(int amount) => _moneroAmountFormat.format(
71 + DecimalIntl(cryptoAmountToDecimal(amount: amount, divider: _moneroAmountDivider)));
72 }
cw_core/lib/crypto_currency.dart
+3 -2
@@ -106,6 +106,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
106 CryptoCurrency.usdcTrc20,
107 CryptoCurrency.tbtc,
108 CryptoCurrency.wow,
109 + CryptoCurrency.zano,
110 CryptoCurrency.ton,
111 CryptoCurrency.flip
112 ];
@@ -226,8 +227,8 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
227 static const tbtc = CryptoCurrency(title: 'tBTC', fullName: 'Testnet Bitcoin', raw: 93, name: 'tbtc', iconPath: 'assets/images/tbtc.png', decimals: 8);
228 static const wow = CryptoCurrency(title: 'WOW', fullName: 'Wownero', raw: 94, name: 'wow', iconPath: 'assets/images/wownero_icon.png', decimals: 11);
229 static const ton = CryptoCurrency(title: 'TON', fullName: 'Toncoin', raw: 95, name: 'ton', iconPath: 'assets/images/ton_icon.png', decimals: 8);
229 -
230 - static const flip = CryptoCurrency(title: 'FLIP', tag: 'ETH', fullName: 'Chainflip', raw: 96, name: 'flip', iconPath: 'assets/images/flip_icon.png', decimals: 18);
230 + static const zano = CryptoCurrency(title: 'ZANO', tag: 'ZANO', fullName: 'Zano', raw: 96, name: 'zano', iconPath: 'assets/images/zano_icon.png', decimals: 12);
231 + static const flip = CryptoCurrency(title: 'FLIP', tag: 'ETH', fullName: 'Chainflip', raw: 97, name: 'flip', iconPath: 'assets/images/flip_icon.png', decimals: 18);
232
233 static final Map<int, CryptoCurrency> _rawCurrencyMap =
234 [...all, ...havenCurrencies].fold<Map<int, CryptoCurrency>>(<int, CryptoCurrency>{}, (acc, item) {
cw_core/lib/currency_for_wallet_type.dart
+3
@@ -30,8 +30,11 @@ CryptoCurrency currencyForWalletType(WalletType type, {bool? isTestnet}) {
30 return CryptoCurrency.trx;
31 case WalletType.wownero:
32 return CryptoCurrency.wow;
33 + case WalletType.zano:
34 + return CryptoCurrency.zano;
35 case WalletType.none:
36 throw Exception(
37 +
38 'Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType');
39 }
40 }
cw_core/lib/hive_type_ids.dart
+2 -1
@@ -19,4 +19,5 @@ const DERIVATION_INFO_TYPE_ID = 17;
19 const TRON_TOKEN_TYPE_ID = 18;
20 const HARDWARE_WALLET_TYPE_TYPE_ID = 19;
21 const MWEB_UTXO_TYPE_ID = 20;
22 -const HAVEN_SEED_STORE_TYPE_ID = 21;
\ No newline at end of file
22 +const HAVEN_SEED_STORE_TYPE_ID = 21;
23 +const ZANO_ASSET_TYPE_ID = 22;
cw_core/lib/monero_wallet_keys.dart
+3 -1
@@ -4,11 +4,13 @@ class MoneroWalletKeys {
4 required this.privateSpendKey,
5 required this.privateViewKey,
6 required this.publicSpendKey,
7 - required this.publicViewKey});
7 + required this.publicViewKey,
8 + required this.passphrase});
9
10 final String primaryAddress;
11 final String publicViewKey;
12 final String privateViewKey;
13 final String publicSpendKey;
14 final String privateSpendKey;
15 + final String passphrase;
16 }
\ No newline at end of file
cw_core/lib/node.dart
+12 -7
@@ -1,5 +1,4 @@
1 import 'dart:io';
2 -import 'dart:math';
2 import 'package:cw_core/keyable.dart';
3 import 'package:cw_core/utils/print_verbose.dart';
4 import 'dart:convert';
@@ -10,7 +9,6 @@ import 'package:cw_core/wallet_type.dart';
9 import 'package:http/io_client.dart' as ioc;
10 import 'dart:math' as math;
11 import 'package:convert/convert.dart';
13 -import 'package:crypto/crypto.dart' as crypto;
12
13 import 'package:crypto/crypto.dart';
14
@@ -105,7 +103,9 @@ class Node extends HiveObject with Keyable {
103 case WalletType.solana:
104 case WalletType.tron:
105 return Uri.parse(
108 - "http${isSSL ? "s" : ""}://$uriRaw${path!.startsWith("/") ? path : "/$path"}");
106 + "http${isSSL ? "s" : ""}://$uriRaw${path!.startsWith("/") ? path : "/$path"}");
107 + case WalletType.zano:
108 + return Uri.https(uriRaw, '');
109 case WalletType.none:
110 throw Exception('Unexpected type ${type.toString()} for Node uri');
111 }
@@ -166,6 +166,8 @@ class Node extends HiveObject with Keyable {
166 case WalletType.solana:
167 case WalletType.tron:
168 return requestElectrumServer();
169 + case WalletType.zano:
170 + return requestZanoNode();
171 case WalletType.none:
172 return false;
173 }
@@ -174,7 +176,11 @@ class Node extends HiveObject with Keyable {
176 }
177 }
178
177 - Future<bool> requestMoneroNode() async {
179 + Future<bool> requestZanoNode() async {
180 + return requestMoneroNode(methodName: "getinfo");
181 + }
182 +
183 + Future<bool> requestMoneroNode({String methodName = 'get_info'}) async {
184 if (useSocksProxy) {
185 return await requestNodeWithProxy();
186 }
@@ -182,8 +188,7 @@ class Node extends HiveObject with Keyable {
188
189 final path = '/json_rpc';
190 final rpcUri = isSSL ? Uri.https(uri.authority, path) : Uri.http(uri.authority, path);
185 - final body = {'jsonrpc': '2.0', 'id': '0', 'method': 'get_info'};
186 -
191 + final body = {'jsonrpc': '2.0', 'id': '0', 'method': methodName};
192
193 try {
194 final authenticatingClient = HttpClient();
@@ -226,7 +231,7 @@ class Node extends HiveObject with Keyable {
231 final oldUseSSL = useSSL;
232 useSSL = true;
233 try {
229 - final ret = await requestMoneroNode();
234 + final ret = await requestMoneroNode(methodName: methodName);
235 if (ret == true) {
236 await save();
237 return ret;
cw_core/lib/wallet_type.dart
+17 -1
@@ -16,6 +16,7 @@ const walletTypes = [
16 WalletType.polygon,
17 WalletType.solana,
18 WalletType.tron,
19 + WalletType.zano,
20 ];
21
22 @HiveType(typeId: WALLET_TYPE_TYPE_ID)
@@ -58,6 +59,10 @@ enum WalletType {
59
60 @HiveField(12)
61 wownero,
62 +
63 + @HiveField(13)
64 + zano,
65 +
66 }
67
68 int serializeToInt(WalletType type) {
@@ -86,6 +91,8 @@ int serializeToInt(WalletType type) {
91 return 10;
92 case WalletType.wownero:
93 return 11;
94 + case WalletType.zano:
95 + return 12;
96 case WalletType.none:
97 return -1;
98 }
@@ -117,8 +124,11 @@ WalletType deserializeFromInt(int raw) {
124 return WalletType.tron;
125 case 11:
126 return WalletType.wownero;
127 + case 12:
128 + return WalletType.zano;
129 default:
121 - throw Exception('Unexpected token: $raw for WalletType deserializeFromInt');
130 + throw Exception(
131 + 'Unexpected token: $raw for WalletType deserializeFromInt');
132 }
133 }
134
@@ -148,6 +158,8 @@ String walletTypeToString(WalletType type) {
158 return 'Tron';
159 case WalletType.wownero:
160 return 'Wownero';
161 + case WalletType.zano:
162 + return 'Zano';
163 case WalletType.none:
164 return '';
165 }
@@ -179,6 +191,8 @@ String walletTypeToDisplayName(WalletType type) {
191 return 'Tron (TRX)';
192 case WalletType.wownero:
193 return 'Wownero (WOW)';
194 + case WalletType.zano:
195 + return 'Zano (ZANO)';
196 case WalletType.none:
197 return '';
198 }
@@ -213,6 +227,8 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type, {bool isTestnet = fal
227 return CryptoCurrency.trx;
228 case WalletType.wownero:
229 return CryptoCurrency.wow;
230 + case WalletType.zano:
231 + return CryptoCurrency.zano;
232 case WalletType.none:
233 throw Exception(
234 'Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency');
cw_core/lib/zano_asset.dart new
+117
@@ -0,0 +1,117 @@
1 +import 'package:cw_core/crypto_currency.dart';
2 +import 'package:cw_core/hive_type_ids.dart';
3 +import 'package:hive/hive.dart';
4 +
5 +part 'zano_asset.g.dart';
6 +
7 +@HiveType(typeId: ZanoAsset.typeId)
8 +class ZanoAsset extends CryptoCurrency with HiveObjectMixin {
9 + @HiveField(0)
10 + final String fullName;
11 + @HiveField(1)
12 + final String ticker;
13 + @HiveField(2)
14 + final String assetId;
15 + @HiveField(3)
16 + final int decimalPoint;
17 + @HiveField(4, defaultValue: true)
18 + bool _enabled;
19 + @HiveField(5)
20 + final String? iconPath;
21 + // @HiveField(6)
22 + // final String? tag;
23 + @HiveField(6)
24 + final String owner;
25 + @HiveField(7)
26 + final String metaInfo;
27 + @HiveField(8)
28 + final BigInt currentSupply;
29 + @HiveField(9)
30 + final bool hiddenSupply;
31 + @HiveField(10)
32 + final BigInt totalMaxSupply;
33 + @HiveField(11)
34 + final bool isInGlobalWhitelist;
35 +
36 + bool get enabled => _enabled;
37 +
38 + set enabled(bool value) => _enabled = value;
39 +
40 + ZanoAsset({
41 + this.fullName = '',
42 + this.ticker = '',
43 + required this.assetId,
44 + this.decimalPoint = 12,
45 + bool enabled = true,
46 + this.iconPath,
47 + this.owner = defaultOwner,
48 + this.metaInfo = '',
49 + required this.currentSupply,
50 + this.hiddenSupply = false,
51 + required this.totalMaxSupply,
52 + this.isInGlobalWhitelist = false,
53 + }) : _enabled = enabled,
54 + super(
55 + name: fullName,
56 + title: ticker.toUpperCase(),
57 + fullName: fullName,
58 + tag: 'ZANO',
59 + iconPath: iconPath,
60 + decimals: decimalPoint,
61 + );
62 +
63 + ZanoAsset.copyWith(ZanoAsset other, {String? icon, String? assetId, bool enabled = true})
64 + : this.fullName = other.fullName,
65 + this.ticker = other.ticker,
66 + this.assetId = assetId ?? other.assetId,
67 + this.decimalPoint = other.decimalPoint,
68 + this._enabled = enabled && other.enabled,
69 + this.iconPath = icon,
70 + this.currentSupply = other.currentSupply,
71 + this.hiddenSupply = other.hiddenSupply,
72 + this.metaInfo = other.metaInfo,
73 + this.owner = other.owner,
74 + this.totalMaxSupply = other.totalMaxSupply,
75 + this.isInGlobalWhitelist = other.isInGlobalWhitelist,
76 + super(
77 + name: other.name,
78 + title: other.ticker.toUpperCase(),
79 + fullName: other.name,
80 + tag: 'ZANO',
81 + iconPath: icon,
82 + decimals: other.decimalPoint,
83 + enabled: enabled,
84 + );
85 +
86 + factory ZanoAsset.fromJson(Map<String, dynamic> json, {bool isInGlobalWhitelist = false}) => ZanoAsset(
87 + assetId: json['asset_id'] as String? ?? '',
88 + currentSupply: bigIntFromDynamic(json['current_supply']),
89 + decimalPoint: json['decimal_point'] as int? ?? 12,
90 + fullName: json['full_name'] as String? ?? '',
91 + hiddenSupply: json['hidden_supply'] as bool? ?? false,
92 + metaInfo: json['meta_info'] as String? ?? '',
93 + owner: json['owner'] as String? ?? '',
94 + ticker: json['ticker'] as String? ?? '',
95 + totalMaxSupply: bigIntFromDynamic(json['total_max_supply']),
96 + isInGlobalWhitelist: isInGlobalWhitelist,
97 + );
98 +
99 +
100 +
101 + static const typeId = ZANO_ASSET_TYPE_ID;
102 + static const zanoAssetsBoxName = 'zanoAssetsBox';
103 + static const defaultOwner = '0000000000000000000000000000000000000000000000000000000000000000';
104 +}
105 +
106 +BigInt bigIntFromDynamic(dynamic d) {
107 + if (d is int) {
108 + return BigInt.from(d);
109 + } else if (d is BigInt) {
110 + return d;
111 + } else if (d == null) {
112 + return BigInt.zero;
113 + } else {
114 + throw 'cannot cast value of type ${d.runtimeType} to BigInt';
115 + //return BigInt.zero;
116 + }
117 +}
\ No newline at end of file
cw_core/pubspec.lock
+16
@@ -207,6 +207,14 @@ packages:
207 url: "https://pub.dev"
208 source: hosted
209 version: "2.3.7"
210 + decimal:
211 + dependency: "direct main"
212 + description:
213 + name: decimal
214 + sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21"
215 + url: "https://pub.dev"
216 + source: hosted
217 + version: "2.3.3"
218 encrypt:
219 dependency: "direct main"
220 description:
@@ -577,6 +585,14 @@ packages:
585 url: "https://pub.dev"
586 source: hosted
587 version: "1.3.0"
588 + rational:
589 + dependency: transitive
590 + description:
591 + name: rational
592 + sha256: cb808fb6f1a839e6fc5f7d8cb3b0a10e1db48b3be102de73938c627f0b636336
593 + url: "https://pub.dev"
594 + source: hosted
595 + version: "2.2.3"
596 shelf:
597 dependency: transitive
598 description:
cw_core/pubspec.yaml
+1
@@ -19,6 +19,7 @@ dependencies:
19 flutter_mobx: ^2.0.6+1
20 intl: ^0.19.0
21 encrypt: ^5.0.1
22 + decimal: ^2.3.3
23 cake_backup:
24 git:
25 url: https://github.com/cake-tech/cake_backup.git
cw_haven/lib/haven_wallet.dart
+2 -1
@@ -78,7 +78,8 @@ abstract class HavenWalletBase
78 privateSpendKey: haven_wallet.getSecretSpendKey(),
79 privateViewKey: haven_wallet.getSecretViewKey(),
80 publicSpendKey: haven_wallet.getPublicSpendKey(),
81 - publicViewKey: haven_wallet.getPublicViewKey());
81 + publicViewKey: haven_wallet.getPublicViewKey(),
82 + passphrase: "");
83
84 haven_wallet.SyncListener? _listener;
85 ReactionDisposer? _onAccountChangeReaction;
cw_haven/pubspec.lock
+16
@@ -209,6 +209,14 @@ packages:
209 url: "https://pub.dev"
210 source: hosted
211 version: "2.2.4"
212 + decimal:
213 + dependency: transitive
214 + description:
215 + name: decimal
216 + sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21"
217 + url: "https://pub.dev"
218 + source: hosted
219 + version: "2.3.3"
220 encrypt:
221 dependency: transitive
222 description:
@@ -571,6 +579,14 @@ packages:
579 url: "https://pub.dev"
580 source: hosted
581 version: "1.3.0"
582 + rational:
583 + dependency: transitive
584 + description:
585 + name: rational
586 + sha256: cb808fb6f1a839e6fc5f7d8cb3b0a10e1db48b3be102de73938c627f0b636336
587 + url: "https://pub.dev"
588 + source: hosted
589 + version: "2.2.3"
590 shelf:
591 dependency: transitive
592 description:
cw_monero/lib/api/wallet.dart
+27 -7
@@ -8,6 +8,7 @@ import 'package:cw_monero/api/exceptions/setup_wallet_exception.dart';
8 import 'package:flutter/foundation.dart';
9 import 'package:monero/monero.dart' as monero;
10 import 'package:mutex/mutex.dart';
11 +import 'package:polyseed/polyseed.dart';
12
13 bool debugMonero = false;
14
@@ -36,19 +37,34 @@ String getSeed() {
37 // monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed);
38 final cakepolyseed =
39 monero.Wallet_getCacheAttribute(wptr!, key: "cakewallet.seed");
40 + final cakepassphrase = getPassphrase();
41 +
42 + final weirdPolyseed = monero.Wallet_getPolyseed(wptr!, passphrase: cakepassphrase);
43 + if (weirdPolyseed != "") return weirdPolyseed;
44 +
45 if (cakepolyseed != "") {
46 + if (cakepassphrase != "") {
47 + try {
48 + final lang = PolyseedLang.getByPhrase(cakepassphrase);
49 + final coin = PolyseedCoin.POLYSEED_MONERO;
50 + final ps = Polyseed.decode(cakepolyseed, lang, coin);
51 + final passphrase = getPassphrase();
52 + if (ps.isEncrypted || passphrase == "") return ps.encode(lang, coin);
53 + ps.crypt(getPassphrase());
54 + return ps.encode(lang, coin);
55 + } catch (e) {
56 + printV(e);
57 + }
58 + }
59 return cakepolyseed;
60 }
42 - final polyseed = monero.Wallet_getPolyseed(wptr!, passphrase: '');
43 - if (polyseed != "") {
44 - return polyseed;
45 - }
46 - final legacy = getSeedLegacy("English");
61 + final legacy = getSeedLegacy(null);
62 return legacy;
63 }
64
65 String getSeedLegacy(String? language) {
51 - var legacy = monero.Wallet_seed(wptr!, seedOffset: '');
66 + final cakepassphrase = getPassphrase();
67 + var legacy = monero.Wallet_seed(wptr!, seedOffset: cakepassphrase);
68 switch (language) {
69 case "Chinese (Traditional)": language = "Chinese (simplified)"; break;
70 case "Chinese (Simplified)": language = "Chinese (simplified)"; break;
@@ -58,7 +74,7 @@ String getSeedLegacy(String? language) {
74 }
75 if (monero.Wallet_status(wptr!) != 0) {
76 monero.Wallet_setSeedLanguage(wptr!, language: language ?? "English");
61 - legacy = monero.Wallet_seed(wptr!, seedOffset: '');
77 + legacy = monero.Wallet_seed(wptr!, seedOffset: cakepassphrase);
78 }
79 if (monero.Wallet_status(wptr!) != 0) {
80 final err = monero.Wallet_errorString(wptr!);
@@ -70,6 +86,10 @@ String getSeedLegacy(String? language) {
86 return legacy;
87 }
88
89 +String getPassphrase() {
90 + return monero.Wallet_getCacheAttribute(wptr!, key: "cakewallet.passphrase");
91 +}
92 +
93 Map<int, Map<int, Map<int, String>>> addressCache = {};
94
95 String getAddress({int accountIndex = 0, int addressIndex = 0}) {
cw_monero/lib/api/wallet_manager.dart
+55 -3
@@ -95,9 +95,10 @@ bool isWalletExistSync({required String path}) {
95 void restoreWalletFromSeedSync(
96 {required String path,
97 required String password,
98 + required String passphrase,
99 required String seed,
100 int nettype = 0,
100 - int restoreHeight = 0}) {
101 + int restoreHeight = 0}) async {
102 txhistory = null;
103 final newWptr = monero.WalletManager_recoveryWallet(
104 wmPtr,
@@ -105,7 +106,7 @@ void restoreWalletFromSeedSync(
106 password: password,
107 mnemonic: seed,
108 restoreHeight: restoreHeight,
108 - seedOffset: '',
109 + seedOffset: passphrase,
110 networkType: 0,
111 );
112
@@ -117,7 +118,13 @@ void restoreWalletFromSeedSync(
118 }
119 wptr = newWptr;
120
121 + setRefreshFromBlockHeight(height: restoreHeight);
122 +
123 + monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.passphrase", value: passphrase);
124 +
125 openedWalletsByPath[path] = wptr!;
126 +
127 + monero.Wallet_store(wptr!);
128 _lastOpenedWallet = path;
129 }
130
@@ -189,6 +196,48 @@ void restoreWalletFromKeysSync(
196 _lastOpenedWallet = path;
197 }
198
199 +
200 +// English only, because normalization.
201 +void restoreWalletFromPolyseedWithOffset(
202 + {required String path,
203 + required String password,
204 + required String seed,
205 + required String seedOffset,
206 + required String language,
207 + int nettype = 0}) {
208 +
209 + txhistory = null;
210 + final newWptr = monero.WalletManager_createWalletFromPolyseed(
211 + wmPtr,
212 + path: path,
213 + password: password,
214 + networkType: nettype,
215 + mnemonic: seed,
216 + seedOffset: seedOffset,
217 + newWallet: true, // safe to remove
218 + restoreHeight: 0,
219 + kdfRounds: 1,
220 + );
221 +
222 + final status = monero.Wallet_status(newWptr);
223 +
224 + if (status != 0) {
225 + final err = monero.Wallet_errorString(newWptr);
226 + printV("err: $err");
227 + throw WalletRestoreFromKeysException(message: err);
228 + }
229 +
230 + wptr = newWptr;
231 +
232 + monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed);
233 + monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.passphrase", value: seedOffset);
234 + monero.Wallet_store(wptr!);
235 + storeSync();
236 +
237 + openedWalletsByPath[path] = wptr!;
238 +}
239 +
240 +
241 void restoreWalletFromSpendKeySync(
242 {required String path,
243 required String password,
@@ -348,11 +397,12 @@ void _createWallet(Map<String, dynamic> args) {
397 void _restoreFromSeed(Map<String, dynamic> args) {
398 final path = args['path'] as String;
399 final password = args['password'] as String;
400 + final passphrase = args['passphrase'] as String;
401 final seed = args['seed'] as String;
402 final restoreHeight = args['restoreHeight'] as int;
403
404 restoreWalletFromSeedSync(
355 - path: path, password: password, seed: seed, restoreHeight: restoreHeight);
405 + path: path, password: password, passphrase: passphrase, seed: seed, restoreHeight: restoreHeight);
406 }
407
408 void _restoreFromKeys(Map<String, dynamic> args) {
@@ -420,12 +470,14 @@ Future<void> createWallet(
470 Future<void> restoreFromSeed(
471 {required String path,
472 required String password,
473 + required String passphrase,
474 required String seed,
475 int nettype = 0,
476 int restoreHeight = 0}) async =>
477 _restoreFromSeed({
478 'path': path,
479 'password': password,
480 + 'passphrase': passphrase,
481 'seed': seed,
482 'nettype': nettype,
483 'restoreHeight': restoreHeight
cw_monero/lib/monero_wallet.dart
+5 -1
@@ -122,13 +122,17 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
122 @override
123 String get password => _password;
124
125 + @override
126 + String get passphrase => monero_wallet.getPassphrase();
127 +
128 @override
129 MoneroWalletKeys get keys => MoneroWalletKeys(
130 primaryAddress: monero_wallet.getAddress(accountIndex: 0, addressIndex: 0),
131 privateSpendKey: monero_wallet.getSecretSpendKey(),
132 privateViewKey: monero_wallet.getSecretViewKey(),
133 publicSpendKey: monero_wallet.getPublicSpendKey(),
131 - publicViewKey: monero_wallet.getPublicViewKey());
134 + publicViewKey: monero_wallet.getPublicViewKey(),
135 + passphrase: monero_wallet.getPassphrase());
136
137 int? get restoreHeight =>
138 transactionHistory.transactions.values.firstOrNull?.height;
cw_monero/lib/monero_wallet_service.dart
+43 -6
@@ -11,6 +11,8 @@ import 'package:cw_core/wallet_credentials.dart';
11 import 'package:cw_core/wallet_info.dart';
12 import 'package:cw_core/wallet_service.dart';
13 import 'package:cw_core/wallet_type.dart';
14 +import 'package:cw_monero/api/exceptions/wallet_opening_exception.dart';
15 +import 'package:cw_core/get_height_by_date.dart';
16 import 'package:cw_monero/api/account_list.dart';
17 import 'package:cw_monero/api/wallet_manager.dart' as monero_wallet_manager;
18 import 'package:cw_monero/api/wallet_manager.dart';
@@ -42,10 +44,15 @@ class MoneroRestoreWalletFromHardwareCredentials extends WalletCredentials {
44
45 class MoneroRestoreWalletFromSeedCredentials extends WalletCredentials {
46 MoneroRestoreWalletFromSeedCredentials(
45 - {required String name, required this.mnemonic, int height = 0, String? password})
47 + {required String name,
48 + required this.mnemonic,
49 + required this.passphrase,
50 + int height = 0,
51 + String? password})
52 : super(name: name, password: password, height: height);
53
54 final String mnemonic;
55 + final String passphrase;
56 }
57
58 class MoneroWalletLoadingException implements Exception {
@@ -94,12 +101,14 @@ class MoneroWalletService extends WalletService<
101 final polyseed = Polyseed.create();
102 final lang = PolyseedLang.getByEnglishName(credentials.language);
103
104 + if (credentials.passphrase != null) polyseed.crypt(credentials.passphrase!);
105 +
106 final heightOverride =
107 getMoneroHeigthByDate(date: DateTime.now().subtract(Duration(days: 2)));
108
109 return _restoreFromPolyseed(
110 path, credentials.password!, polyseed, credentials.walletInfo!, lang,
102 - overrideHeight: heightOverride);
111 + overrideHeight: heightOverride, passphrase: credentials.passphrase);
112 }
113
114 await monero_wallet_manager.createWallet(
@@ -292,6 +301,7 @@ class MoneroWalletService extends WalletService<
301 await monero_wallet_manager.restoreFromSeed(
302 path: path,
303 password: credentials.password!,
304 + passphrase: credentials.passphrase,
305 seed: credentials.mnemonic,
306 restoreHeight: credentials.height!);
307 final wallet = MoneroWallet(
@@ -318,7 +328,8 @@ class MoneroWalletService extends WalletService<
328 Polyseed.decode(credentials.mnemonic, lang, polyseedCoin);
329
330 return _restoreFromPolyseed(
321 - path, credentials.password!, polyseed, credentials.walletInfo!, lang);
331 + path, credentials.password!, polyseed, credentials.walletInfo!, lang,
332 + passphrase: credentials.passphrase);
333 } catch (e) {
334 // TODO: Implement Exception for wallet list service.
335 printV('MoneroWalletsManager Error: $e');
@@ -326,9 +337,35 @@ class MoneroWalletService extends WalletService<
337 }
338 }
339
329 - Future<MoneroWallet> _restoreFromPolyseed(String path, String password, Polyseed polyseed,
330 - WalletInfo walletInfo, PolyseedLang lang,
331 - {PolyseedCoin coin = PolyseedCoin.POLYSEED_MONERO, int? overrideHeight}) async {
340 + Future<MoneroWallet> _restoreFromPolyseed(
341 + String path, String password, Polyseed polyseed, WalletInfo walletInfo, PolyseedLang lang,
342 + {PolyseedCoin coin = PolyseedCoin.POLYSEED_MONERO,
343 + int? overrideHeight,
344 + String? passphrase}) async {
345 +
346 + if (polyseed.isEncrypted == false &&
347 + (passphrase??'') != "") {
348 + // Fallback to the different passphrase offset method, when a passphrase
349 + // was provided but the polyseed is not encrypted.
350 + monero_wallet_manager.restoreWalletFromPolyseedWithOffset(
351 + path: path,
352 + password: password,
353 + seed: polyseed.encode(lang, coin),
354 + seedOffset: passphrase??'',
355 + language: "English");
356 +
357 + final wallet = MoneroWallet(
358 + walletInfo: walletInfo,
359 + unspentCoinsInfo: unspentCoinsInfoSource,
360 + password: password,
361 + );
362 + await wallet.init();
363 +
364 + return wallet;
365 + }
366 +
367 + if (polyseed.isEncrypted) polyseed.crypt(passphrase ?? '');
368 +
369 final height = overrideHeight ??
370 getMoneroHeigthByDate(date: DateTime.fromMillisecondsSinceEpoch(polyseed.birthday * 1000));
371 final spendKey = polyseed.generateKey(coin, 32).toHexString();
cw_monero/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux
+1 -1
@@ -1 +1 @@
1 -/Users/omarhatem/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/
\ No newline at end of file
1 +/home/parallels/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/
\ No newline at end of file
cw_monero/pubspec.lock
+18 -2
@@ -225,6 +225,14 @@ packages:
225 url: "https://pub.dev"
226 source: hosted
227 version: "0.7.10"
228 + decimal:
229 + dependency: transitive
230 + description:
231 + name: decimal
232 + sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21"
233 + url: "https://pub.dev"
234 + source: hosted
235 + version: "2.3.3"
236 encrypt:
237 dependency: "direct main"
238 description:
@@ -503,8 +511,8 @@ packages:
511 dependency: "direct main"
512 description:
513 path: "impls/monero.dart"
506 - ref: af5277f96073917185864d3596e82b67bee54e78
507 - resolved-ref: af5277f96073917185864d3596e82b67bee54e78
514 + ref: 9526921acb774b523a2e1d9ba9a7b389acfc6b70
515 + resolved-ref: 9526921acb774b523a2e1d9ba9a7b389acfc6b70
516 url: "https://github.com/mrcyjanek/monero_c"
517 source: git
518 version: "0.0.0"
@@ -660,6 +668,14 @@ packages:
668 url: "https://pub.dev"
669 source: hosted
670 version: "1.3.0"
671 + rational:
672 + dependency: transitive
673 + description:
674 + name: rational
675 + sha256: cb808fb6f1a839e6fc5f7d8cb3b0a10e1db48b3be102de73938c627f0b636336
676 + url: "https://pub.dev"
677 + source: hosted
678 + version: "2.2.3"
679 rxdart:
680 dependency: transitive
681 description:
cw_monero/pubspec.yaml
+1 -2
@@ -25,8 +25,7 @@ dependencies:
25 monero:
26 git:
27 url: https://github.com/mrcyjanek/monero_c
28 - ref: af5277f96073917185864d3596e82b67bee54e78
29 -# ref: 6eb571ea498ed7b854934785f00fabfd0dadf75b # monero_c hash
28 + ref: 9526921acb774b523a2e1d9ba9a7b389acfc6b70
29 path: impls/monero.dart
30 mutex: ^3.1.0
31 ledger_flutter_plus: ^1.4.1
cw_wownero/lib/api/transaction_history.dart
+8 -4
@@ -10,7 +10,7 @@ import 'package:cw_wownero/exceptions/wownero_transaction_creation_exception.dar
10 import 'package:ffi/ffi.dart';
11 import 'package:monero/wownero.dart' as wownero;
12 import 'package:monero/src/generated_bindings_wownero.g.dart' as wownero_gen;
13 -
13 +import 'package:mutex/mutex.dart';
14
15 String getTxKey(String txId) {
16 final ret = wownero.Wallet_getTxKey(wptr!, txid: txId);
@@ -18,6 +18,7 @@ String getTxKey(String txId) {
18 return ret;
19 }
20
21 +final txHistoryMutex = Mutex();
22 wownero.TransactionHistory? txhistory;
23
24 bool isRefreshingTx = false;
@@ -26,22 +27,25 @@ Future<void> refreshTransactions() async {
27 isRefreshingTx = true;
28 txhistory ??= wownero.Wallet_history(wptr!);
29 final ptr = txhistory!.address;
30 + await txHistoryMutex.acquire();
31 await Isolate.run(() {
32 wownero.TransactionHistory_refresh(Pointer.fromAddress(ptr));
33 });
34 + txHistoryMutex.release();
35 isRefreshingTx = false;
36 }
37
38 int countOfTransactions() => wownero.TransactionHistory_count(txhistory!);
39
37 -List<Transaction> getAllTransactions() {
40 +Future<List<Transaction>> getAllTransactions() async {
41 List<Transaction> dummyTxs = [];
42
43 + await txHistoryMutex.acquire();
44 txhistory ??= wownero.Wallet_history(wptr!);
41 - wownero.TransactionHistory_refresh(txhistory!);
45 int size = countOfTransactions();
46 final list = List.generate(size, (index) => Transaction(txInfo: wownero.TransactionHistory_transaction(txhistory!, index: index)));
44 -
47 + txHistoryMutex.release();
48 +
49 final accts = wownero.Wallet_numSubaddressAccounts(wptr!);
50 for (var i = 0; i < accts; i++) {
51 final fullBalance = wownero.Wallet_balance(wptr!, accountIndex: i);
cw_wownero/lib/api/wallet.dart
+28 -8
@@ -7,6 +7,7 @@ import 'package:cw_wownero/api/account_list.dart';
7 import 'package:cw_wownero/api/exceptions/setup_wallet_exception.dart';
8 import 'package:monero/wownero.dart' as wownero;
9 import 'package:mutex/mutex.dart';
10 +import 'package:polyseed/polyseed.dart';
11
12 int getSyncingHeight() {
13 // final height = wownero.WOWNERO_cw_WalletListener_height(getWlptr());
@@ -32,22 +33,37 @@ bool isNewTransactionExist() {
33 String getFilename() => wownero.Wallet_filename(wptr!);
34
35 String getSeed() {
35 - // wownero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed);
36 + // monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed);
37 final cakepolyseed =
38 wownero.Wallet_getCacheAttribute(wptr!, key: "cakewallet.seed");
39 + final cakepassphrase = getPassphrase();
40 +
41 + final weirdPolyseed = wownero.Wallet_getPolyseed(wptr!, passphrase: cakepassphrase);
42 + if (weirdPolyseed != "") return weirdPolyseed;
43 +
44 if (cakepolyseed != "") {
45 + if (cakepassphrase != "") {
46 + try {
47 + final lang = PolyseedLang.getByPhrase(cakepassphrase);
48 + final coin = PolyseedCoin.POLYSEED_WOWNERO;
49 + final ps = Polyseed.decode(cakepolyseed, lang, coin);
50 + final passphrase = getPassphrase();
51 + if (ps.isEncrypted || passphrase == "") return ps.encode(lang, coin);
52 + ps.crypt(passphrase);
53 + return ps.encode(lang, coin);
54 + } catch (e) {
55 + printV(e);
56 + }
57 + }
58 return cakepolyseed;
59 }
41 - final polyseed = wownero.Wallet_getPolyseed(wptr!, passphrase: '');
42 - if (polyseed != "") {
43 - return polyseed;
44 - }
60 final legacy = getSeedLegacy(null);
61 return legacy;
62 }
63
64 String getSeedLegacy(String? language) {
50 - var legacy = wownero.Wallet_seed(wptr!, seedOffset: '');
65 + final cakepassphrase = getPassphrase();
66 + var legacy = wownero.Wallet_seed(wptr!, seedOffset: cakepassphrase);
67 switch (language) {
68 case "Chinese (Traditional)": language = "Chinese (simplified)"; break;
69 case "Chinese (Simplified)": language = "Chinese (simplified)"; break;
@@ -57,7 +73,7 @@ String getSeedLegacy(String? language) {
73 }
74 if (wownero.Wallet_status(wptr!) != 0) {
75 wownero.Wallet_setSeedLanguage(wptr!, language: language ?? "English");
60 - legacy = wownero.Wallet_seed(wptr!, seedOffset: '');
76 + legacy = wownero.Wallet_seed(wptr!, seedOffset: cakepassphrase);
77 }
78 if (wownero.Wallet_status(wptr!) != 0) {
79 final err = wownero.Wallet_errorString(wptr!);
@@ -70,6 +86,10 @@ String getSeedLegacy(String? language) {
86 }
87 Map<int, Map<int, Map<int, String>>> addressCache = {};
88
89 +String getPassphrase() {
90 + return wownero.Wallet_getCacheAttribute(wptr!, key: "cakewallet.passphrase");
91 +}
92 +
93 String getAddress({int accountIndex = 0, int addressIndex = 1}) {
94 while (wownero.Wallet_numSubaddresses(wptr!, accountIndex: accountIndex)-1 < addressIndex) {
95 printV("adding subaddress");
@@ -333,4 +353,4 @@ String signMessage(String message, {String address = ""}) {
353
354 bool verifyMessage(String message, String address, String signature) {
355 return wownero.Wallet_verifySignedMessage(wptr!, message: message, address: address, signature: signature);
336 -}
\ No newline at end of file
356 +}
cw_wownero/lib/api/wallet_manager.dart
+57 -4
@@ -90,6 +90,7 @@ bool isWalletExistSync({required String path}) {
90 void restoreWalletFromSeedSync(
91 {required String path,
92 required String password,
93 + required String passphrase,
94 required String seed,
95 int nettype = 0,
96 int restoreHeight = 0}) {
@@ -102,10 +103,12 @@ void restoreWalletFromSeedSync(
103 language: seed, // I KNOW - this is supposed to be called seed
104 networkType: 0,
105 );
105 -
106 + final oldwptr = wptr;
107 + wptr = newWptr;
108 setRefreshFromBlockHeight(
109 height: wownero.WOWNERO_deprecated_14WordSeedHeight(seed: seed),
110 );
111 + wptr = oldwptr;
112 } else {
113 txhistory = null;
114 newWptr = wownero.WalletManager_recoveryWallet(
@@ -114,7 +117,7 @@ void restoreWalletFromSeedSync(
117 password: password,
118 mnemonic: seed,
119 restoreHeight: restoreHeight,
117 - seedOffset: '',
120 + seedOffset: passphrase,
121 networkType: 0,
122 );
123 }
@@ -127,8 +130,13 @@ void restoreWalletFromSeedSync(
130 }
131
132 wptr = newWptr;
130 -
133 +
134 + wownero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.passphrase", value: passphrase);
135 + wownero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed);
136 +
137 openedWalletsByPath[path] = wptr!;
138 +
139 + store();
140 }
141
142 void restoreWalletFromKeysSync(
@@ -196,6 +204,48 @@ void restoreWalletFromKeysSync(
204 openedWalletsByPath[path] = wptr!;
205 }
206
207 +
208 +
209 +// English only, because normalization.
210 +void restoreWalletFromPolyseedWithOffset(
211 + {required String path,
212 + required String password,
213 + required String seed,
214 + required String seedOffset,
215 + required String language,
216 + int nettype = 0}) {
217 +
218 + txhistory = null;
219 + final newWptr = wownero.WalletManager_createWalletFromPolyseed(
220 + wmPtr,
221 + path: path,
222 + password: password,
223 + networkType: nettype,
224 + mnemonic: seed,
225 + seedOffset: seedOffset,
226 + newWallet: true, // safe to remove
227 + restoreHeight: 0,
228 + kdfRounds: 1,
229 + );
230 +
231 + final status = wownero.Wallet_status(newWptr);
232 +
233 + if (status != 0) {
234 + final err = wownero.Wallet_errorString(newWptr);
235 + printV("err: $err");
236 + throw WalletRestoreFromKeysException(message: err);
237 + }
238 +
239 + wptr = newWptr;
240 +
241 + wownero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed);
242 + wownero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.passphrase", value: seedOffset);
243 +
244 + storeSync();
245 +
246 + openedWalletsByPath[path] = wptr!;
247 +}
248 +
249 void restoreWalletFromSpendKeySync(
250 {required String path,
251 required String password,
@@ -319,11 +369,12 @@ void _createWallet(Map<String, dynamic> args) {
369 void _restoreFromSeed(Map<String, dynamic> args) {
370 final path = args['path'] as String;
371 final password = args['password'] as String;
372 + final passphrase = args['passphrase'] as String;
373 final seed = args['seed'] as String;
374 final restoreHeight = args['restoreHeight'] as int;
375
376 restoreWalletFromSeedSync(
326 - path: path, password: password, seed: seed, restoreHeight: restoreHeight);
377 + path: path, password: password, passphrase: passphrase, seed: seed, restoreHeight: restoreHeight);
378 }
379
380 void _restoreFromKeys(Map<String, dynamic> args) {
@@ -391,12 +442,14 @@ Future<void> createWallet(
442 Future<void> restoreFromSeed(
443 {required String path,
444 required String password,
445 + required String passphrase,
446 required String seed,
447 int nettype = 0,
448 int restoreHeight = 0}) async =>
449 _restoreFromSeed({
450 'path': path,
451 'password': password,
452 + 'passphrase': passphrase,
453 'seed': seed,
454 'nettype': nettype,
455 'restoreHeight': restoreHeight
cw_wownero/lib/wownero_wallet.dart
+9 -5
@@ -117,6 +117,9 @@ abstract class WowneroWalletBase
117
118 String get password => _password;
119
120 + @override
121 + String get passphrase => wownero_wallet.getPassphrase();
122 +
123 String _password;
124
125 @override
@@ -125,7 +128,8 @@ abstract class WowneroWalletBase
128 privateSpendKey: wownero_wallet.getSecretSpendKey(),
129 privateViewKey: wownero_wallet.getSecretViewKey(),
130 publicSpendKey: wownero_wallet.getPublicSpendKey(),
128 - publicViewKey: wownero_wallet.getPublicViewKey());
131 + publicViewKey: wownero_wallet.getPublicViewKey(),
132 + passphrase: wownero_wallet.getPassphrase());
133
134 wownero_wallet.SyncListener? _listener;
135 ReactionDisposer? _onAccountChangeReaction;
@@ -557,7 +561,7 @@ abstract class WowneroWalletBase
561 @override
562 Future<Map<String, WowneroTransactionInfo>> fetchTransactions() async {
563 transaction_history.refreshTransactions();
560 - return _getAllTransactionsOfAccount(walletAddresses.account?.id)
564 + return (await _getAllTransactionsOfAccount(walletAddresses.account?.id))
565 .fold<Map<String, WowneroTransactionInfo>>(<String, WowneroTransactionInfo>{},
566 (Map<String, WowneroTransactionInfo> acc, WowneroTransactionInfo tx) {
567 acc[tx.id] = tx;
@@ -586,9 +590,9 @@ abstract class WowneroWalletBase
590 String getSubaddressLabel(int accountIndex, int addressIndex) =>
591 wownero_wallet.getSubaddressLabel(accountIndex, addressIndex);
592
589 - List<WowneroTransactionInfo> _getAllTransactionsOfAccount(int? accountIndex) =>
590 - transaction_history
591 - .getAllTransactions()
593 + Future<List<WowneroTransactionInfo>> _getAllTransactionsOfAccount(int? accountIndex) async =>
594 + (await transaction_history
595 + .getAllTransactions())
596 .map(
597 (row) => WowneroTransactionInfo(
598 row.hash,
cw_wownero/lib/wownero_wallet_service.dart
+33 -4
@@ -30,10 +30,11 @@ class WowneroNewWalletCredentials extends WalletCredentials {
30
31 class WowneroRestoreWalletFromSeedCredentials extends WalletCredentials {
32 WowneroRestoreWalletFromSeedCredentials(
33 - {required String name, required this.mnemonic, int height = 0, String? password})
33 + {required String name, required this.mnemonic, required this.passphrase, int height = 0, String? password})
34 : super(name: name, password: password, height: height);
35
36 final String mnemonic;
37 + final String passphrase;
38 }
39
40 class WowneroWalletLoadingException implements Exception {
@@ -83,12 +84,14 @@ class WowneroWalletService extends WalletService<
84 final polyseed = Polyseed.create();
85 final lang = PolyseedLang.getByEnglishName(credentials.language);
86
87 + if (credentials.passphrase != null) polyseed.crypt(credentials.passphrase!);
88 +
89 final heightOverride =
90 getWowneroHeightByDate(date: DateTime.now().subtract(Duration(days: 2)));
91
92 return _restoreFromPolyseed(
93 path, credentials.password!, polyseed, credentials.walletInfo!, lang,
91 - overrideHeight: heightOverride);
94 + overrideHeight: heightOverride, passphrase: credentials.passphrase);
95 }
96
97 await wownero_wallet_manager.createWallet(
@@ -266,6 +269,7 @@ class WowneroWalletService extends WalletService<
269 await wownero_wallet_manager.restoreFromSeed(
270 path: path,
271 password: credentials.password!,
272 + passphrase: credentials.passphrase,
273 seed: credentials.mnemonic,
274 restoreHeight: credentials.height!);
275 final wallet = WowneroWallet(
@@ -289,7 +293,7 @@ class WowneroWalletService extends WalletService<
293 final polyseed = Polyseed.decode(credentials.mnemonic, lang, polyseedCoin);
294
295 return _restoreFromPolyseed(
292 - path, credentials.password!, polyseed, credentials.walletInfo!, lang);
296 + path, credentials.password!, polyseed, credentials.walletInfo!, lang, passphrase: credentials.passphrase);
297 } catch (e) {
298 // TODO: Implement Exception for wallet list service.
299 printV('WowneroWalletsManager Error: $e');
@@ -299,7 +303,32 @@ class WowneroWalletService extends WalletService<
303
304 Future<WowneroWallet> _restoreFromPolyseed(
305 String path, String password, Polyseed polyseed, WalletInfo walletInfo, PolyseedLang lang,
302 - {PolyseedCoin coin = PolyseedCoin.POLYSEED_WOWNERO, int? overrideHeight}) async {
306 + {PolyseedCoin coin = PolyseedCoin.POLYSEED_WOWNERO, int? overrideHeight, String? passphrase}) async {
307 +
308 +
309 + if (polyseed.isEncrypted == false &&
310 + (passphrase??'') != "") {
311 + // Fallback to the different passphrase offset method, when a passphrase
312 + // was provided but the polyseed is not encrypted.
313 + wownero_wallet_manager.restoreWalletFromPolyseedWithOffset(
314 + path: path,
315 + password: password,
316 + seed: polyseed.encode(lang, coin),
317 + seedOffset: passphrase??'',
318 + language: "English");
319 +
320 + final wallet = WowneroWallet(
321 + walletInfo: walletInfo,
322 + unspentCoinsInfo: unspentCoinsInfoSource,
323 + password: password,
324 + );
325 + await wallet.init();
326 +
327 + return wallet;
328 + }
329 +
330 + if (polyseed.isEncrypted) polyseed.crypt(passphrase ?? '');
331 +
332 final height = overrideHeight ??
333 getWowneroHeightByDate(date: DateTime.fromMillisecondsSinceEpoch(polyseed.birthday * 1000));
334 final spendKey = polyseed.generateKey(coin, 32).toHexString();
cw_wownero/pubspec.lock
+18 -2
@@ -209,6 +209,14 @@ packages:
209 url: "https://pub.dev"
210 source: hosted
211 version: "2.2.4"
212 + decimal:
213 + dependency: transitive
214 + description:
215 + name: decimal
216 + sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21"
217 + url: "https://pub.dev"
218 + source: hosted
219 + version: "2.3.3"
220 encrypt:
221 dependency: "direct main"
222 description:
@@ -463,8 +471,8 @@ packages:
471 dependency: "direct main"
472 description:
473 path: "impls/monero.dart"
466 - ref: af5277f96073917185864d3596e82b67bee54e78
467 - resolved-ref: af5277f96073917185864d3596e82b67bee54e78
474 + ref: 9526921acb774b523a2e1d9ba9a7b389acfc6b70
475 + resolved-ref: 9526921acb774b523a2e1d9ba9a7b389acfc6b70
476 url: "https://github.com/mrcyjanek/monero_c"
477 source: git
478 version: "0.0.0"
@@ -612,6 +620,14 @@ packages:
620 url: "https://pub.dev"
621 source: hosted
622 version: "1.3.0"
623 + rational:
624 + dependency: transitive
625 + description:
626 + name: rational
627 + sha256: cb808fb6f1a839e6fc5f7d8cb3b0a10e1db48b3be102de73938c627f0b636336
628 + url: "https://pub.dev"
629 + source: hosted
630 + version: "2.2.3"
631 shelf:
632 dependency: transitive
633 description:
cw_wownero/pubspec.yaml
+1 -2
@@ -25,8 +25,7 @@ dependencies:
25 monero:
26 git:
27 url: https://github.com/mrcyjanek/monero_c
28 - ref: af5277f96073917185864d3596e82b67bee54e78
29 -# ref: 6eb571ea498ed7b854934785f00fabfd0dadf75b # monero_c hash
28 + ref: 9526921acb774b523a2e1d9ba9a7b389acfc6b70 # monero_c hash
29 path: impls/monero.dart
30 mutex: ^3.1.0
31
cw_zano/.gitignore new
+7
@@ -0,0 +1,7 @@
1 +.DS_Store
2 +.dart_tool/
3 +
4 +.packages
5 +.pub/
6 +
7 +build/
cw_zano/.metadata new
+10
@@ -0,0 +1,10 @@
1 +# This file tracks properties of this Flutter project.
2 +# Used by Flutter tool to assess capabilities and perform upgrades etc.
3 +#
4 +# This file should be version controlled and should not be manually edited.
5 +
6 +version:
7 + revision: 4d7946a68d26794349189cf21b3f68cc6fe61dcb
8 + channel: stable
9 +
10 +project_type: plugin
cw_zano/CHANGELOG.md new
+3
@@ -0,0 +1,3 @@
1 +## 0.0.1
2 +
3 +* TODO: Describe initial release.
cw_zano/LICENSE new
+1
@@ -0,0 +1 @@
1 +TODO: Add your license here.
cw_zano/README.md new
+15
@@ -0,0 +1,15 @@
1 +# cw_zano
2 +
3 +A new flutter plugin project.
4 +
5 +## Getting Started
6 +
7 +This project is a starting point for a Flutter
8 +[plug-in package](https://flutter.dev/developing-packages/),
9 +a specialized package that includes platform-specific implementation code for
10 +Android and/or iOS.
11 +
12 +For help getting started with Flutter, view our
13 +[online documentation](https://flutter.dev/docs), which offers tutorials,
14 +samples, guidance on mobile development, and a full API reference.
15 +
cw_zano/lib/api/consts.dart new
+6
@@ -0,0 +1,6 @@
1 +class Consts {
2 + static const errorWrongSeed = 'WRONG_SEED';
3 + static const errorAlreadyExists = 'ALREADY_EXISTS';
4 + static const errorWalletWrongId = 'WALLET_WRONG_ID';
5 + static const errorBusy = 'BUSY';
6 +}
\ No newline at end of file
cw_zano/lib/api/model/asset_id_params.dart new
+9
@@ -0,0 +1,9 @@
1 +class AssetIdParams {
2 + final String assetId;
3 +
4 + AssetIdParams({required this.assetId});
5 +
6 + Map<String, dynamic> toJson() => {
7 + 'asset_id': assetId,
8 + };
9 +}
\ No newline at end of file
cw_zano/lib/api/model/balance.dart new
+32
@@ -0,0 +1,32 @@
1 +import 'package:cw_core/zano_asset.dart';
2 +import 'package:cw_zano/model/zano_asset.dart';
3 +import 'package:cw_zano/zano_formatter.dart';
4 +
5 +class Balance {
6 + final ZanoAsset assetInfo;
7 + final BigInt awaitingIn;
8 + final BigInt awaitingOut;
9 + final BigInt total;
10 + final BigInt unlocked;
11 +
12 + Balance(
13 + {required this.assetInfo,
14 + required this.awaitingIn,
15 + required this.awaitingOut,
16 + required this.total,
17 + required this.unlocked});
18 +
19 + String get assetId => assetInfo.assetId;
20 +
21 + @override
22 + String toString() => '$assetInfo: $total/$unlocked';
23 +
24 + factory Balance.fromJson(Map<String, dynamic> json) => Balance(
25 + assetInfo:
26 + ZanoAsset.fromJson(json['asset_info'] as Map<String, dynamic>? ?? {}),
27 + awaitingIn: ZanoFormatter.bigIntFromDynamic(json['awaiting_in']),
28 + awaitingOut: ZanoFormatter.bigIntFromDynamic(json['awaiting_out']),
29 + total: ZanoFormatter.bigIntFromDynamic(json['total']),
30 + unlocked: ZanoFormatter.bigIntFromDynamic(json['unlocked']),
31 + );
32 +}
cw_zano/lib/api/model/create_wallet_result.dart new
+39
@@ -0,0 +1,39 @@
1 +import 'package:cw_zano/api/model/recent_history.dart';
2 +import 'package:cw_zano/api/model/wi.dart';
3 +
4 +class CreateWalletResult {
5 + final String name;
6 + final String pass;
7 + final RecentHistory recentHistory;
8 + final bool recovered;
9 + final String seed;
10 + final int walletFileSize;
11 + final int walletId;
12 + final int walletLocalBcSize;
13 + final Wi wi;
14 +
15 + CreateWalletResult(
16 + {required this.name,
17 + required this.pass,
18 + required this.recentHistory,
19 + required this.recovered,
20 + required this.seed,
21 + required this.walletFileSize,
22 + required this.walletId,
23 + required this.walletLocalBcSize,
24 + required this.wi});
25 +
26 + factory CreateWalletResult.fromJson(Map<String, dynamic> json) =>
27 + CreateWalletResult(
28 + name: json['name'] as String? ?? '',
29 + pass: json['pass'] as String? ?? '',
30 + recentHistory: RecentHistory.fromJson(
31 + json['recent_history'] as Map<String, dynamic>? ?? {}),
32 + recovered: json['recovered'] as bool? ?? false,
33 + seed: json['seed'] as String? ?? '',
34 + walletFileSize: json['wallet_file_size'] as int? ?? 0,
35 + walletId: json['wallet_id'] as int? ?? 0,
36 + walletLocalBcSize: json['wallet_local_bc_size'] as int? ?? 0,
37 + wi: Wi.fromJson(json['wi'] as Map<String, dynamic>? ?? {}),
38 + );
39 +}
cw_zano/lib/api/model/destination.dart new
+20
@@ -0,0 +1,20 @@
1 +class Destination {
2 + final BigInt amount; // transfered as string
3 + final String address;
4 + final String assetId;
5 +
6 + Destination(
7 + {required this.amount, required this.address, required this.assetId});
8 +
9 + factory Destination.fromJson(Map<String, dynamic> json) => Destination(
10 + amount: BigInt.parse(json['amount'] as String? ?? '0'),
11 + address: json['address'] as String? ?? '',
12 + assetId: json['asset_id'] as String? ?? '',
13 + );
14 +
15 + Map<String, dynamic> toJson() => {
16 + 'amount': amount.toString(),
17 + 'address': address,
18 + 'asset_id': assetId,
19 + };
20 +}
cw_zano/lib/api/model/employed_entries.dart new
+18
@@ -0,0 +1,18 @@
1 +import 'package:cw_zano/api/model/receive.dart';
2 +
3 +class EmployedEntries {
4 + final List<Receive> receive;
5 + final List<Receive> send;
6 +
7 + EmployedEntries({required this.receive, required this.send});
8 +
9 + factory EmployedEntries.fromJson(Map<String, dynamic> json) =>
10 + EmployedEntries(
11 + receive: json['receive'] == null ? [] : (json['receive'] as List<dynamic>)
12 + .map((e) => Receive.fromJson(e as Map<String, dynamic>))
13 + .toList(),
14 + send: json['spent'] == null ? [] : (json['spent'] as List<dynamic>)
15 + .map((e) => Receive.fromJson(e as Map<String, dynamic>))
16 + .toList(),
17 + );
18 +}
cw_zano/lib/api/model/get_address_info_result.dart new
+16
@@ -0,0 +1,16 @@
1 +class GetAddressInfoResult {
2 + final bool valid;
3 + final bool auditable;
4 + final bool paymentId;
5 + final bool wrap;
6 +
7 + GetAddressInfoResult(
8 + {required this.valid, required this.auditable, required this.paymentId, required this.wrap});
9 +
10 + factory GetAddressInfoResult.fromJson(Map<String, dynamic> json) => GetAddressInfoResult(
11 + valid: json['valid'] as bool? ?? false,
12 + auditable: json['auditable'] as bool? ?? false,
13 + paymentId: json['payment_id'] as bool? ?? false,
14 + wrap: json['wrap'] as bool? ?? false,
15 + );
16 +}
cw_zano/lib/api/model/get_recent_txs_and_info_params.dart new
+14
@@ -0,0 +1,14 @@
1 +class GetRecentTxsAndInfoParams {
2 + final int offset;
3 + final int count;
4 + final bool updateProvisionInfo;
5 +
6 + GetRecentTxsAndInfoParams({required this.offset, required this.count, this.updateProvisionInfo = true});
7 +
8 + Map<String, dynamic> toJson() => {
9 + 'offset': offset,
10 + 'count': count,
11 + 'update_provision_info': updateProvisionInfo,
12 + 'order': 'FROM_BEGIN_TO_END',
13 + };
14 +}
\ No newline at end of file
cw_zano/lib/api/model/get_recent_txs_and_info_result.dart new
+12
@@ -0,0 +1,12 @@
1 +import 'package:cw_zano/api/model/transfer.dart';
2 +
3 +class GetRecentTxsAndInfoResult {
4 + final List<Transfer> transfers;
5 + final int lastItemIndex;
6 + final int totalTransfers;
7 +
8 + GetRecentTxsAndInfoResult({required this.transfers, required this.lastItemIndex, required this.totalTransfers});
9 +
10 + GetRecentTxsAndInfoResult.empty(): this.transfers = [], this.lastItemIndex = 0, this.totalTransfers = 0;
11 +
12 +}
\ No newline at end of file
cw_zano/lib/api/model/get_wallet_info_result.dart new
+14
@@ -0,0 +1,14 @@
1 +import 'package:cw_zano/api/model/wi.dart';
2 +import 'package:cw_zano/api/model/wi_extended.dart';
3 +
4 +class GetWalletInfoResult {
5 + final Wi wi;
6 + final WiExtended wiExtended;
7 +
8 + GetWalletInfoResult({required this.wi, required this.wiExtended});
9 +
10 + factory GetWalletInfoResult.fromJson(Map<String, dynamic> json) => GetWalletInfoResult(
11 + wi: Wi.fromJson(json['wi'] as Map<String, dynamic>? ?? {}),
12 + wiExtended: WiExtended.fromJson(json['wi_extended'] as Map<String, dynamic>? ?? {}),
13 + );
14 +}
cw_zano/lib/api/model/get_wallet_status_result.dart new
+26
@@ -0,0 +1,26 @@
1 +class GetWalletStatusResult {
2 + final int currentDaemonHeight;
3 + final int currentWalletHeight;
4 + final bool isDaemonConnected;
5 + final bool isInLongRefresh;
6 + final int progress;
7 + final int walletState;
8 +
9 + GetWalletStatusResult(
10 + {required this.currentDaemonHeight,
11 + required this.currentWalletHeight,
12 + required this.isDaemonConnected,
13 + required this.isInLongRefresh,
14 + required this.progress,
15 + required this.walletState});
16 +
17 + factory GetWalletStatusResult.fromJson(Map<String, dynamic> json) =>
18 + GetWalletStatusResult(
19 + currentDaemonHeight: json['current_daemon_height'] as int? ?? 0,
20 + currentWalletHeight: json['current_wallet_height'] as int? ?? 0,
21 + isDaemonConnected: json['is_daemon_connected'] as bool? ?? false,
22 + isInLongRefresh: json['is_in_long_refresh'] as bool? ?? false,
23 + progress: json['progress'] as int? ?? 0,
24 + walletState: json['wallet_state'] as int? ?? 0,
25 + );
26 +}
cw_zano/lib/api/model/proxy_to_daemon_params.dart new
+13
@@ -0,0 +1,13 @@
1 +import 'dart:convert';
2 +
3 +class ProxyToDaemonParams {
4 + final String body;
5 + final String uri;
6 +
7 + ProxyToDaemonParams({required this.body, required this.uri});
8 +
9 + Map<String, dynamic> toJson() => {
10 + 'base64_body': base64Encode(utf8.encode(body)),
11 + 'uri': uri,
12 + };
13 +}
cw_zano/lib/api/model/proxy_to_daemon_result.dart new
+13
@@ -0,0 +1,13 @@
1 +import 'dart:convert';
2 +
3 +class ProxyToDaemonResult {
4 + final String body;
5 + final int responseCode;
6 +
7 + ProxyToDaemonResult({required this.body, required this.responseCode});
8 +
9 + factory ProxyToDaemonResult.fromJson(Map<String, dynamic> json) => ProxyToDaemonResult(
10 + body: utf8.decode(base64Decode(json['base64_body'] as String? ?? '')),
11 + responseCode: json['response_code'] as int? ?? 0,
12 + );
13 +}
cw_zano/lib/api/model/receive.dart new
+15
@@ -0,0 +1,15 @@
1 +import 'package:cw_zano/zano_formatter.dart';
2 +
3 +class Receive {
4 + final BigInt amount;
5 + final String assetId;
6 + final int index;
7 +
8 + Receive({required this.amount, required this.assetId, required this.index});
9 +
10 + factory Receive.fromJson(Map<String, dynamic> json) => Receive(
11 + amount: ZanoFormatter.bigIntFromDynamic(json['amount']),
12 + assetId: json['asset_id'] as String? ?? '',
13 + index: json['index'] as int? ?? 0,
14 + );
15 +}
cw_zano/lib/api/model/recent_history.dart new
+20
@@ -0,0 +1,20 @@
1 +import 'package:cw_zano/api/model/transfer.dart';
2 +
3 +class RecentHistory {
4 + final List<Transfer>? history;
5 + final int lastItemIndex;
6 + final int totalHistoryItems;
7 +
8 + RecentHistory(
9 + {required this.history,
10 + required this.lastItemIndex,
11 + required this.totalHistoryItems});
12 +
13 + factory RecentHistory.fromJson(Map<String, dynamic> json) => RecentHistory(
14 + history: json['history'] == null ? null : (json['history'] as List<dynamic>)
15 + .map((e) => Transfer.fromJson(e as Map<String, dynamic>))
16 + .toList(),
17 + lastItemIndex: json['last_item_index'] as int? ?? 0,
18 + totalHistoryItems: json['total_history_items'] as int? ?? 0,
19 + );
20 +}
cw_zano/lib/api/model/store_result.dart new
+9
@@ -0,0 +1,9 @@
1 +class StoreResult {
2 + final int walletFileSize;
3 +
4 + StoreResult({required this.walletFileSize});
5 +
6 + factory StoreResult.fromJson(Map<String, dynamic> json) => StoreResult(
7 + walletFileSize: json['wallet_file_size'] as int? ?? 0,
8 + );
9 +}
\ No newline at end of file
cw_zano/lib/api/model/subtransfer.dart new
+16
@@ -0,0 +1,16 @@
1 +import 'package:cw_zano/zano_formatter.dart';
2 +
3 +class Subtransfer {
4 + final BigInt amount;
5 + final String assetId;
6 + final bool isIncome;
7 +
8 + Subtransfer(
9 + {required this.amount, required this.assetId, required this.isIncome});
10 +
11 + factory Subtransfer.fromJson(Map<String, dynamic> json) => Subtransfer(
12 + amount: ZanoFormatter.bigIntFromDynamic(json['amount']),
13 + assetId: json['asset_id'] as String? ?? '',
14 + isIncome: json['is_income'] as bool? ?? false,
15 + );
16 +}
cw_zano/lib/api/model/transfer.dart new
+126
@@ -0,0 +1,126 @@
1 +import 'package:cw_core/zano_asset.dart';
2 +import 'package:cw_zano/api/model/employed_entries.dart';
3 +import 'package:cw_zano/api/model/subtransfer.dart';
4 +import 'package:collection/collection.dart';
5 +import 'package:cw_zano/model/zano_asset.dart';
6 +import 'package:cw_zano/model/zano_transaction_info.dart';
7 +import 'package:cw_zano/zano_formatter.dart';
8 +import 'package:cw_zano/zano_wallet.dart';
9 +import 'package:cw_zano/zano_wallet_api.dart';
10 +
11 +class Transfer {
12 + final String comment;
13 + final EmployedEntries employedEntries;
14 + final int fee;
15 + final int height;
16 + final bool isMining;
17 + final bool isMixing;
18 + final bool isService;
19 + final String paymentId;
20 + final List<String> remoteAddresses;
21 + final List<String> remoteAliases;
22 + final bool showSender;
23 + final List<Subtransfer> subtransfers;
24 + final int timestamp;
25 + final int transferInternalIndex;
26 + final int txBlobSize;
27 + final String txHash;
28 + final int txType;
29 + final int unlockTime;
30 +
31 + Transfer({
32 + required this.comment,
33 + required this.employedEntries,
34 + required this.fee,
35 + required this.height,
36 + required this.isMining,
37 + required this.isMixing,
38 + required this.isService,
39 + required this.paymentId,
40 + required this.remoteAddresses,
41 + required this.remoteAliases,
42 + required this.showSender,
43 + required this.subtransfers,
44 + required this.timestamp,
45 + required this.transferInternalIndex,
46 + required this.txBlobSize,
47 + required this.txHash,
48 + required this.txType,
49 + required this.unlockTime,
50 + });
51 +
52 + factory Transfer.fromJson(Map<String, dynamic> json) => Transfer(
53 + comment: json['comment'] as String? ?? '',
54 + employedEntries: EmployedEntries.fromJson(json['employed_entries'] as Map<String, dynamic>? ?? {}),
55 + fee: json['fee'] as int? ?? 0,
56 + height: json['height'] as int? ?? 0,
57 + isMining: json['is_mining'] as bool? ?? false,
58 + isMixing: json['is_mixing'] as bool? ?? false,
59 + isService: json['is_service'] as bool? ?? false,
60 + paymentId: json['payment_id'] as String? ?? '',
61 + remoteAddresses: json['remote_addresses'] == null ? [] : (json['remote_addresses'] as List<dynamic>).cast<String>(),
62 + remoteAliases: json['remote_aliases'] == null ? [] : (json['remote_aliases'] as List<dynamic>).cast<String>(),
63 + showSender: json['show_sender'] as bool? ?? false,
64 + subtransfers: (json['subtransfers'] as List<dynamic>? ?? []).map((e) => Subtransfer.fromJson(e as Map<String, dynamic>)).toList(),
65 + timestamp: json['timestamp'] as int? ?? 0,
66 + transferInternalIndex: json['transfer_internal_index'] == null
67 + ? 0
68 + : json['transfer_internal_index'] is double
69 + ? (json['transfer_internal_index'] as double).toInt()
70 + : json['transfer_internal_index'] as int,
71 + txBlobSize: json['tx_blob_size'] as int? ?? 0,
72 + txHash: json['tx_hash'] as String? ?? '',
73 + txType: json['tx_type'] as int? ?? 0,
74 + unlockTime: json['unlock_time'] as int? ?? 0,
75 + );
76 +
77 + static Map<String, ZanoTransactionInfo> makeMap(List<Transfer> transfers, Map<String, ZanoAsset> zanoAssets, int currentDaemonHeight) {
78 + return Map.fromIterable(
79 + transfers,
80 + key: (item) => (item as Transfer).txHash,
81 + value: (transfer) {
82 + transfer as Transfer;
83 + // Simple (only one subtransfer OR two subtransfers and the second is Zano, outgoing and amount equals to fee) or complex?
84 + Subtransfer? single = transfer.subtransfers.singleOrNull;
85 + if (transfer.subtransfers.length == 2) {
86 + final zano = transfer.subtransfers.firstWhereOrNull((element) => element.assetId == ZanoWalletBase.zanoAssetId);
87 + if (zano != null && !zano.isIncome && zano.amount == BigInt.from(transfer.fee)) {
88 + single = transfer.subtransfers.firstWhere((element) => element.assetId != ZanoWalletBase.zanoAssetId);
89 + }
90 + }
91 + bool isSimple = single != null;
92 + // TODO: for complex transactions we show zano or any other transaction, will fix it later
93 + if (!isSimple) {
94 + single =
95 + transfer.subtransfers.firstWhereOrNull((element) => element.assetId == ZanoWalletBase.zanoAssetId) ?? transfer.subtransfers.first;
96 + }
97 + if (single.assetId != ZanoWalletBase.zanoAssetId) {
98 + final asset = zanoAssets[single.assetId];
99 + if (asset == null) {
100 + ZanoWalletApi.error('unknown asset ${single.assetId}');
101 + }
102 + final ticker = asset == null ? '***' : asset.ticker;
103 + final decimalPoint = asset == null ? ZanoFormatter.defaultDecimalPoint : asset.decimalPoint;
104 + return ZanoTransactionInfo.fromTransfer(
105 + transfer,
106 + confirmations: currentDaemonHeight - transfer.height,
107 + isIncome: single.isIncome,
108 + assetId: single.assetId,
109 + amount: single.amount,
110 + tokenSymbol: isSimple ? ticker : '*${ticker}',
111 + decimalPoint: decimalPoint,
112 + );
113 + }
114 + final amount = single.isIncome ? single.amount : single.amount - BigInt.from(transfer.fee);
115 + return ZanoTransactionInfo.fromTransfer(
116 + transfer,
117 + confirmations: currentDaemonHeight - transfer.height,
118 + isIncome: single.isIncome,
119 + assetId: single.assetId,
120 + amount: amount,
121 + tokenSymbol: isSimple ? 'ZANO' : '*ZANO',
122 + );
123 + },
124 + );
125 + }
126 +}
cw_zano/lib/api/model/transfer_params.dart new
+41
@@ -0,0 +1,41 @@
1 +import 'package:cw_zano/api/model/destination.dart';
2 +
3 +class TransferParams {
4 + final List<Destination> destinations;
5 + final BigInt fee;
6 + final int mixin;
7 + final String paymentId;
8 + final String comment;
9 + final bool pushPayer;
10 + final bool hideReceiver;
11 +
12 + TransferParams({
13 + required this.destinations,
14 + required this.fee,
15 + required this.mixin,
16 + required this.paymentId,
17 + required this.comment,
18 + required this.pushPayer,
19 + required this.hideReceiver,
20 + });
21 +
22 + Map<String, dynamic> toJson() => {
23 + 'destinations': destinations,
24 + 'fee': fee.toInt(),
25 + 'mixin': mixin,
26 + 'payment_id': paymentId,
27 + 'comment': comment,
28 + 'push_payer': pushPayer,
29 + 'hide_receiver': hideReceiver,
30 + };
31 +
32 + factory TransferParams.fromJson(Map<String, dynamic> json) => TransferParams(
33 + destinations: (json['destinations'] as List<dynamic>?)?.map((e) => Destination.fromJson(e as Map<String, dynamic>)).toList() ?? [],
34 + fee: BigInt.from(json['fee'] as int? ?? 0),
35 + mixin: json['mixin'] as int? ?? 0,
36 + paymentId: json['payment_id'] as String? ?? '',
37 + comment: json['comment'] as String? ?? '',
38 + pushPayer: json['push_payer'] as bool? ?? false,
39 + hideReceiver: json['hide_receiver'] as bool? ?? false,
40 + );
41 +}
cw_zano/lib/api/model/transfer_result.dart new
+13
@@ -0,0 +1,13 @@
1 +class TransferResult {
2 + final String txHash;
3 + final int txSize;
4 + final String txUnsignedHex;
5 +
6 + TransferResult({required this.txHash, required this.txSize, required this.txUnsignedHex});
7 +
8 + factory TransferResult.fromJson(Map<String, dynamic> json) => TransferResult(
9 + txHash: json['tx_hash'] as String? ?? '',
10 + txSize: json['tx_size'] as int? ?? 0,
11 + txUnsignedHex: json['tx_unsigned_hex'] as String? ?? '',
12 + );
13 +}
cw_zano/lib/api/model/wi.dart new
+32
@@ -0,0 +1,32 @@
1 +import 'package:cw_zano/api/model/balance.dart';
2 +
3 +class Wi {
4 + final String address;
5 + final List<Balance> balances;
6 + final bool isAuditable;
7 + final bool isWatchOnly;
8 + final int minedTotal;
9 + final String path;
10 + final String viewSecKey;
11 +
12 + Wi(
13 + {required this.address,
14 + required this.balances,
15 + required this.isAuditable,
16 + required this.isWatchOnly,
17 + required this.minedTotal,
18 + required this.path,
19 + required this.viewSecKey});
20 +
21 + factory Wi.fromJson(Map<String, dynamic> json) => Wi(
22 + address: json['address'] as String? ?? '',
23 + balances: (json['balances'] as List<dynamic>? ?? [])
24 + .map((e) => Balance.fromJson(e as Map<String, dynamic>))
25 + .toList(),
26 + isAuditable: json['is_auditable'] as bool? ?? false,
27 + isWatchOnly: json['is_watch_only'] as bool? ?? false,
28 + minedTotal: json['mined_total'] as int? ?? 0,
29 + path: json['path'] as String? ?? '',
30 + viewSecKey: json['view_sec_key'] as String? ?? '',
31 + );
32 +}
cw_zano/lib/api/model/wi_extended.dart new
+17
@@ -0,0 +1,17 @@
1 +class WiExtended {
2 + final String seed;
3 + final String spendPrivateKey;
4 + final String spendPublicKey;
5 + final String viewPrivateKey;
6 + final String viewPublicKey;
7 +
8 + WiExtended({required this.seed, required this.spendPrivateKey, required this.spendPublicKey, required this.viewPrivateKey, required this.viewPublicKey});
9 +
10 + factory WiExtended.fromJson(Map<String, dynamic> json) => WiExtended(
11 + seed: json['seed'] as String? ?? '',
12 + spendPrivateKey: json['spend_private_key'] as String? ?? '',
13 + spendPublicKey: json['spend_public_key'] as String? ?? '',
14 + viewPrivateKey: json['view_private_key'] as String? ?? '',
15 + viewPublicKey: json['view_public_key'] as String? ?? '',
16 + );
17 +}
\ No newline at end of file
cw_zano/lib/mnemonics/english.dart new
+1630
@@ -0,0 +1,1630 @@
1 +class EnglishMnemonics {
2 + static const words = [
3 + "like",
4 + "just",
5 + "love",
6 + "know",
7 + "never",
8 + "want",
9 + "time",
10 + "out",
11 + "there",
12 + "make",
13 + "look",
14 + "eye",
15 + "down",
16 + "only",
17 + "think",
18 + "heart",
19 + "back",
20 + "then",
21 + "into",
22 + "about",
23 + "more",
24 + "away",
25 + "still",
26 + "them",
27 + "take",
28 + "thing",
29 + "even",
30 + "through",
31 + "long",
32 + "always",
33 + "world",
34 + "too",
35 + "friend",
36 + "tell",
37 + "try",
38 + "hand",
39 + "thought",
40 + "over",
41 + "here",
42 + "other",
43 + "need",
44 + "smile",
45 + "again",
46 + "much",
47 + "cry",
48 + "been",
49 + "night",
50 + "ever",
51 + "little",
52 + "said",
53 + "end",
54 + "some",
55 + "those",
56 + "around",
57 + "mind",
58 + "people",
59 + "girl",
60 + "leave",
61 + "dream",
62 + "left",
63 + "turn",
64 + "myself",
65 + "give",
66 + "nothing",
67 + "really",
68 + "off",
69 + "before",
70 + "something",
71 + "find",
72 + "walk",
73 + "wish",
74 + "good",
75 + "once",
76 + "place",
77 + "ask",
78 + "stop",
79 + "keep",
80 + "watch",
81 + "seem",
82 + "everything",
83 + "wait",
84 + "got",
85 + "yet",
86 + "made",
87 + "remember",
88 + "start",
89 + "alone",
90 + "run",
91 + "hope",
92 + "maybe",
93 + "believe",
94 + "body",
95 + "hate",
96 + "after",
97 + "close",
98 + "talk",
99 + "stand",
100 + "own",
101 + "each",
102 + "hurt",
103 + "help",
104 + "home",
105 + "god",
106 + "soul",
107 + "new",
108 + "many",
109 + "two",
110 + "inside",
111 + "should",
112 + "true",
113 + "first",
114 + "fear",
115 + "mean",
116 + "better",
117 + "play",
118 + "another",
119 + "gone",
120 + "change",
121 + "use",
122 + "wonder",
123 + "someone",
124 + "hair",
125 + "cold",
126 + "open",
127 + "best",
128 + "any",
129 + "behind",
130 + "happen",
131 + "water",
132 + "dark",
133 + "laugh",
134 + "stay",
135 + "forever",
136 + "name",
137 + "work",
138 + "show",
139 + "sky",
140 + "break",
141 + "came",
142 + "deep",
143 + "door",
144 + "put",
145 + "black",
146 + "together",
147 + "upon",
148 + "happy",
149 + "such",
150 + "great",
151 + "white",
152 + "matter",
153 + "fill",
154 + "past",
155 + "please",
156 + "burn",
157 + "cause",
158 + "enough",
159 + "touch",
160 + "moment",
161 + "soon",
162 + "voice",
163 + "scream",
164 + "anything",
165 + "stare",
166 + "sound",
167 + "red",
168 + "everyone",
169 + "hide",
170 + "kiss",
171 + "truth",
172 + "death",
173 + "beautiful",
174 + "mine",
175 + "blood",
176 + "broken",
177 + "very",
178 + "pass",
179 + "next",
180 + "forget",
181 + "tree",
182 + "wrong",
183 + "air",
184 + "mother",
185 + "understand",
186 + "lip",
187 + "hit",
188 + "wall",
189 + "memory",
190 + "sleep",
191 + "free",
192 + "high",
193 + "realize",
194 + "school",
195 + "might",
196 + "skin",
197 + "sweet",
198 + "perfect",
199 + "blue",
200 + "kill",
201 + "breath",
202 + "dance",
203 + "against",
204 + "fly",
205 + "between",
206 + "grow",
207 + "strong",
208 + "under",
209 + "listen",
210 + "bring",
211 + "sometimes",
212 + "speak",
213 + "pull",
214 + "person",
215 + "become",
216 + "family",
217 + "begin",
218 + "ground",
219 + "real",
220 + "small",
221 + "father",
222 + "sure",
223 + "feet",
224 + "rest",
225 + "young",
226 + "finally",
227 + "land",
228 + "across",
229 + "today",
230 + "different",
231 + "guy",
232 + "line",
233 + "fire",
234 + "reason",
235 + "reach",
236 + "second",
237 + "slowly",
238 + "write",
239 + "eat",
240 + "smell",
241 + "mouth",
242 + "step",
243 + "learn",
244 + "three",
245 + "floor",
246 + "promise",
247 + "breathe",
248 + "darkness",
249 + "push",
250 + "earth",
251 + "guess",
252 + "save",
253 + "song",
254 + "above",
255 + "along",
256 + "both",
257 + "color",
258 + "house",
259 + "almost",
260 + "sorry",
261 + "anymore",
262 + "brother",
263 + "okay",
264 + "dear",
265 + "game",
266 + "fade",
267 + "already",
268 + "apart",
269 + "warm",
270 + "beauty",
271 + "heard",
272 + "notice",
273 + "question",
274 + "shine",
275 + "began",
276 + "piece",
277 + "whole",
278 + "shadow",
279 + "secret",
280 + "street",
281 + "within",
282 + "finger",
283 + "point",
284 + "morning",
285 + "whisper",
286 + "child",
287 + "moon",
288 + "green",
289 + "story",
290 + "glass",
291 + "kid",
292 + "silence",
293 + "since",
294 + "soft",
295 + "yourself",
296 + "empty",
297 + "shall",
298 + "angel",
299 + "answer",
300 + "baby",
301 + "bright",
302 + "dad",
303 + "path",
304 + "worry",
305 + "hour",
306 + "drop",
307 + "follow",
308 + "power",
309 + "war",
310 + "half",
311 + "flow",
312 + "heaven",
313 + "act",
314 + "chance",
315 + "fact",
316 + "least",
317 + "tired",
318 + "children",
319 + "near",
320 + "quite",
321 + "afraid",
322 + "rise",
323 + "sea",
324 + "taste",
325 + "window",
326 + "cover",
327 + "nice",
328 + "trust",
329 + "lot",
330 + "sad",
331 + "cool",
332 + "force",
333 + "peace",
334 + "return",
335 + "blind",
336 + "easy",
337 + "ready",
338 + "roll",
339 + "rose",
340 + "drive",
341 + "held",
342 + "music",
343 + "beneath",
344 + "hang",
345 + "mom",
346 + "paint",
347 + "emotion",
348 + "quiet",
349 + "clear",
350 + "cloud",
351 + "few",
352 + "pretty",
353 + "bird",
354 + "outside",
355 + "paper",
356 + "picture",
357 + "front",
358 + "rock",
359 + "simple",
360 + "anyone",
361 + "meant",
362 + "reality",
363 + "road",
364 + "sense",
365 + "waste",
366 + "bit",
367 + "leaf",
368 + "thank",
369 + "happiness",
370 + "meet",
371 + "men",
372 + "smoke",
373 + "truly",
374 + "decide",
375 + "self",
376 + "age",
377 + "book",
378 + "form",
379 + "alive",
380 + "carry",
381 + "escape",
382 + "damn",
383 + "instead",
384 + "able",
385 + "ice",
386 + "minute",
387 + "throw",
388 + "catch",
389 + "leg",
390 + "ring",
391 + "course",
392 + "goodbye",
393 + "lead",
394 + "poem",
395 + "sick",
396 + "corner",
397 + "desire",
398 + "known",
399 + "problem",
400 + "remind",
401 + "shoulder",
402 + "suppose",
403 + "toward",
404 + "wave",
405 + "drink",
406 + "jump",
407 + "woman",
408 + "pretend",
409 + "sister",
410 + "week",
411 + "human",
412 + "joy",
413 + "crack",
414 + "grey",
415 + "pray",
416 + "surprise",
417 + "dry",
418 + "knee",
419 + "less",
420 + "search",
421 + "bleed",
422 + "caught",
423 + "clean",
424 + "embrace",
425 + "future",
426 + "king",
427 + "son",
428 + "sorrow",
429 + "chest",
430 + "hug",
431 + "remain",
432 + "sat",
433 + "worth",
434 + "blow",
435 + "daddy",
436 + "final",
437 + "parent",
438 + "tight",
439 + "also",
440 + "create",
441 + "lonely",
442 + "safe",
443 + "cross",
444 + "dress",
445 + "evil",
446 + "silent",
447 + "bone",
448 + "fate",
449 + "perhaps",
450 + "anger",
451 + "class",
452 + "scar",
453 + "snow",
454 + "tiny",
455 + "tonight",
456 + "continue",
457 + "control",
458 + "dog",
459 + "edge",
460 + "mirror",
461 + "month",
462 + "suddenly",
463 + "comfort",
464 + "given",
465 + "loud",
466 + "quickly",
467 + "gaze",
468 + "plan",
469 + "rush",
470 + "stone",
471 + "town",
472 + "battle",
473 + "ignore",
474 + "spirit",
475 + "stood",
476 + "stupid",
477 + "yours",
478 + "brown",
479 + "build",
480 + "dust",
481 + "hey",
482 + "kept",
483 + "pay",
484 + "phone",
485 + "twist",
486 + "although",
487 + "ball",
488 + "beyond",
489 + "hidden",
490 + "nose",
491 + "taken",
492 + "fail",
493 + "float",
494 + "pure",
495 + "somehow",
496 + "wash",
497 + "wrap",
498 + "angry",
499 + "cheek",
500 + "creature",
501 + "forgotten",
502 + "heat",
503 + "rip",
504 + "single",
505 + "space",
506 + "special",
507 + "weak",
508 + "whatever",
509 + "yell",
510 + "anyway",
511 + "blame",
512 + "job",
513 + "choose",
514 + "country",
515 + "curse",
516 + "drift",
517 + "echo",
518 + "figure",
519 + "grew",
520 + "laughter",
521 + "neck",
522 + "suffer",
523 + "worse",
524 + "yeah",
525 + "disappear",
526 + "foot",
527 + "forward",
528 + "knife",
529 + "mess",
530 + "somewhere",
531 + "stomach",
532 + "storm",
533 + "beg",
534 + "idea",
535 + "lift",
536 + "offer",
537 + "breeze",
538 + "field",
539 + "five",
540 + "often",
541 + "simply",
542 + "stuck",
543 + "win",
544 + "allow",
545 + "confuse",
546 + "enjoy",
547 + "except",
548 + "flower",
549 + "seek",
550 + "strength",
551 + "calm",
552 + "grin",
553 + "gun",
554 + "heavy",
555 + "hill",
556 + "large",
557 + "ocean",
558 + "shoe",
559 + "sigh",
560 + "straight",
561 + "summer",
562 + "tongue",
563 + "accept",
564 + "crazy",
565 + "everyday",
566 + "exist",
567 + "grass",
568 + "mistake",
569 + "sent",
570 + "shut",
571 + "surround",
572 + "table",
573 + "ache",
574 + "brain",
575 + "destroy",
576 + "heal",
577 + "nature",
578 + "shout",
579 + "sign",
580 + "stain",
581 + "choice",
582 + "doubt",
583 + "glance",
584 + "glow",
585 + "mountain",
586 + "queen",
587 + "stranger",
588 + "throat",
589 + "tomorrow",
590 + "city",
591 + "either",
592 + "fish",
593 + "flame",
594 + "rather",
595 + "shape",
596 + "spin",
597 + "spread",
598 + "ash",
599 + "distance",
600 + "finish",
601 + "image",
602 + "imagine",
603 + "important",
604 + "nobody",
605 + "shatter",
606 + "warmth",
607 + "became",
608 + "feed",
609 + "flesh",
610 + "funny",
611 + "lust",
612 + "shirt",
613 + "trouble",
614 + "yellow",
615 + "attention",
616 + "bare",
617 + "bite",
618 + "money",
619 + "protect",
620 + "amaze",
621 + "appear",
622 + "born",
623 + "choke",
624 + "completely",
625 + "daughter",
626 + "fresh",
627 + "friendship",
628 + "gentle",
629 + "probably",
630 + "six",
631 + "deserve",
632 + "expect",
633 + "grab",
634 + "middle",
635 + "nightmare",
636 + "river",
637 + "thousand",
638 + "weight",
639 + "worst",
640 + "wound",
641 + "barely",
642 + "bottle",
643 + "cream",
644 + "regret",
645 + "relationship",
646 + "stick",
647 + "test",
648 + "crush",
649 + "endless",
650 + "fault",
651 + "itself",
652 + "rule",
653 + "spill",
654 + "art",
655 + "circle",
656 + "join",
657 + "kick",
658 + "mask",
659 + "master",
660 + "passion",
661 + "quick",
662 + "raise",
663 + "smooth",
664 + "unless",
665 + "wander",
666 + "actually",
667 + "broke",
668 + "chair",
669 + "deal",
670 + "favorite",
671 + "gift",
672 + "note",
673 + "number",
674 + "sweat",
675 + "box",
676 + "chill",
677 + "clothes",
678 + "lady",
679 + "mark",
680 + "park",
681 + "poor",
682 + "sadness",
683 + "tie",
684 + "animal",
685 + "belong",
686 + "brush",
687 + "consume",
688 + "dawn",
689 + "forest",
690 + "innocent",
691 + "pen",
692 + "pride",
693 + "stream",
694 + "thick",
695 + "clay",
696 + "complete",
697 + "count",
698 + "draw",
699 + "faith",
700 + "press",
701 + "silver",
702 + "struggle",
703 + "surface",
704 + "taught",
705 + "teach",
706 + "wet",
707 + "bless",
708 + "chase",
709 + "climb",
710 + "enter",
711 + "letter",
712 + "melt",
713 + "metal",
714 + "movie",
715 + "stretch",
716 + "swing",
717 + "vision",
718 + "wife",
719 + "beside",
720 + "crash",
721 + "forgot",
722 + "guide",
723 + "haunt",
724 + "joke",
725 + "knock",
726 + "plant",
727 + "pour",
728 + "prove",
729 + "reveal",
730 + "steal",
731 + "stuff",
732 + "trip",
733 + "wood",
734 + "wrist",
735 + "bother",
736 + "bottom",
737 + "crawl",
738 + "crowd",
739 + "fix",
740 + "forgive",
741 + "frown",
742 + "grace",
743 + "loose",
744 + "lucky",
745 + "party",
746 + "release",
747 + "surely",
748 + "survive",
749 + "teacher",
750 + "gently",
751 + "grip",
752 + "speed",
753 + "suicide",
754 + "travel",
755 + "treat",
756 + "vein",
757 + "written",
758 + "cage",
759 + "chain",
760 + "conversation",
761 + "date",
762 + "enemy",
763 + "however",
764 + "interest",
765 + "million",
766 + "page",
767 + "pink",
768 + "proud",
769 + "sway",
770 + "themselves",
771 + "winter",
772 + "church",
773 + "cruel",
774 + "cup",
775 + "demon",
776 + "experience",
777 + "freedom",
778 + "pair",
779 + "pop",
780 + "purpose",
781 + "respect",
782 + "shoot",
783 + "softly",
784 + "state",
785 + "strange",
786 + "bar",
787 + "birth",
788 + "curl",
789 + "dirt",
790 + "excuse",
791 + "lord",
792 + "lovely",
793 + "monster",
794 + "order",
795 + "pack",
796 + "pants",
797 + "pool",
798 + "scene",
799 + "seven",
800 + "shame",
801 + "slide",
802 + "ugly",
803 + "among",
804 + "blade",
805 + "blonde",
806 + "closet",
807 + "creek",
808 + "deny",
809 + "drug",
810 + "eternity",
811 + "gain",
812 + "grade",
813 + "handle",
814 + "key",
815 + "linger",
816 + "pale",
817 + "prepare",
818 + "swallow",
819 + "swim",
820 + "tremble",
821 + "wheel",
822 + "won",
823 + "cast",
824 + "cigarette",
825 + "claim",
826 + "college",
827 + "direction",
828 + "dirty",
829 + "gather",
830 + "ghost",
831 + "hundred",
832 + "loss",
833 + "lung",
834 + "orange",
835 + "present",
836 + "swear",
837 + "swirl",
838 + "twice",
839 + "wild",
840 + "bitter",
841 + "blanket",
842 + "doctor",
843 + "everywhere",
844 + "flash",
845 + "grown",
846 + "knowledge",
847 + "numb",
848 + "pressure",
849 + "radio",
850 + "repeat",
851 + "ruin",
852 + "spend",
853 + "unknown",
854 + "buy",
855 + "clock",
856 + "devil",
857 + "early",
858 + "false",
859 + "fantasy",
860 + "pound",
861 + "precious",
862 + "refuse",
863 + "sheet",
864 + "teeth",
865 + "welcome",
866 + "add",
867 + "ahead",
868 + "block",
869 + "bury",
870 + "caress",
871 + "content",
872 + "depth",
873 + "despite",
874 + "distant",
875 + "marry",
876 + "purple",
877 + "threw",
878 + "whenever",
879 + "bomb",
880 + "dull",
881 + "easily",
882 + "grasp",
883 + "hospital",
884 + "innocence",
885 + "normal",
886 + "receive",
887 + "reply",
888 + "rhyme",
889 + "shade",
890 + "someday",
891 + "sword",
892 + "toe",
893 + "visit",
894 + "asleep",
895 + "bought",
896 + "center",
897 + "consider",
898 + "flat",
899 + "hero",
900 + "history",
901 + "ink",
902 + "insane",
903 + "muscle",
904 + "mystery",
905 + "pocket",
906 + "reflection",
907 + "shove",
908 + "silently",
909 + "smart",
910 + "soldier",
911 + "spot",
912 + "stress",
913 + "train",
914 + "type",
915 + "view",
916 + "whether",
917 + "bus",
918 + "energy",
919 + "explain",
920 + "holy",
921 + "hunger",
922 + "inch",
923 + "magic",
924 + "mix",
925 + "noise",
926 + "nowhere",
927 + "prayer",
928 + "presence",
929 + "shock",
930 + "snap",
931 + "spider",
932 + "study",
933 + "thunder",
934 + "trail",
935 + "admit",
936 + "agree",
937 + "bag",
938 + "bang",
939 + "bound",
940 + "butterfly",
941 + "cute",
942 + "exactly",
943 + "explode",
944 + "familiar",
945 + "fold",
946 + "further",
947 + "pierce",
948 + "reflect",
949 + "scent",
950 + "selfish",
951 + "sharp",
952 + "sink",
953 + "spring",
954 + "stumble",
955 + "universe",
956 + "weep",
957 + "women",
958 + "wonderful",
959 + "action",
960 + "ancient",
961 + "attempt",
962 + "avoid",
963 + "birthday",
964 + "branch",
965 + "chocolate",
966 + "core",
967 + "depress",
968 + "drunk",
969 + "especially",
970 + "focus",
971 + "fruit",
972 + "honest",
973 + "match",
974 + "palm",
975 + "perfectly",
976 + "pillow",
977 + "pity",
978 + "poison",
979 + "roar",
980 + "shift",
981 + "slightly",
982 + "thump",
983 + "truck",
984 + "tune",
985 + "twenty",
986 + "unable",
987 + "wipe",
988 + "wrote",
989 + "coat",
990 + "constant",
991 + "dinner",
992 + "drove",
993 + "egg",
994 + "eternal",
995 + "flight",
996 + "flood",
997 + "frame",
998 + "freak",
999 + "gasp",
1000 + "glad",
1001 + "hollow",
1002 + "motion",
1003 + "peer",
1004 + "plastic",
1005 + "root",
1006 + "screen",
1007 + "season",
1008 + "sting",
1009 + "strike",
1010 + "team",
1011 + "unlike",
1012 + "victim",
1013 + "volume",
1014 + "warn",
1015 + "weird",
1016 + "attack",
1017 + "await",
1018 + "awake",
1019 + "built",
1020 + "charm",
1021 + "crave",
1022 + "despair",
1023 + "fought",
1024 + "grant",
1025 + "grief",
1026 + "horse",
1027 + "limit",
1028 + "message",
1029 + "ripple",
1030 + "sanity",
1031 + "scatter",
1032 + "serve",
1033 + "split",
1034 + "string",
1035 + "trick",
1036 + "annoy",
1037 + "blur",
1038 + "boat",
1039 + "brave",
1040 + "clearly",
1041 + "cling",
1042 + "connect",
1043 + "fist",
1044 + "forth",
1045 + "imagination",
1046 + "iron",
1047 + "jock",
1048 + "judge",
1049 + "lesson",
1050 + "milk",
1051 + "misery",
1052 + "nail",
1053 + "naked",
1054 + "ourselves",
1055 + "poet",
1056 + "possible",
1057 + "princess",
1058 + "sail",
1059 + "size",
1060 + "snake",
1061 + "society",
1062 + "stroke",
1063 + "torture",
1064 + "toss",
1065 + "trace",
1066 + "wise",
1067 + "bloom",
1068 + "bullet",
1069 + "cell",
1070 + "check",
1071 + "cost",
1072 + "darling",
1073 + "during",
1074 + "footstep",
1075 + "fragile",
1076 + "hallway",
1077 + "hardly",
1078 + "horizon",
1079 + "invisible",
1080 + "journey",
1081 + "midnight",
1082 + "mud",
1083 + "nod",
1084 + "pause",
1085 + "relax",
1086 + "shiver",
1087 + "sudden",
1088 + "value",
1089 + "youth",
1090 + "abuse",
1091 + "admire",
1092 + "blink",
1093 + "breast",
1094 + "bruise",
1095 + "constantly",
1096 + "couple",
1097 + "creep",
1098 + "curve",
1099 + "difference",
1100 + "dumb",
1101 + "emptiness",
1102 + "gotta",
1103 + "honor",
1104 + "plain",
1105 + "planet",
1106 + "recall",
1107 + "rub",
1108 + "ship",
1109 + "slam",
1110 + "soar",
1111 + "somebody",
1112 + "tightly",
1113 + "weather",
1114 + "adore",
1115 + "approach",
1116 + "bond",
1117 + "bread",
1118 + "burst",
1119 + "candle",
1120 + "coffee",
1121 + "cousin",
1122 + "crime",
1123 + "desert",
1124 + "flutter",
1125 + "frozen",
1126 + "grand",
1127 + "heel",
1128 + "hello",
1129 + "language",
1130 + "level",
1131 + "movement",
1132 + "pleasure",
1133 + "powerful",
1134 + "random",
1135 + "rhythm",
1136 + "settle",
1137 + "silly",
1138 + "slap",
1139 + "sort",
1140 + "spoken",
1141 + "steel",
1142 + "threaten",
1143 + "tumble",
1144 + "upset",
1145 + "aside",
1146 + "awkward",
1147 + "bee",
1148 + "blank",
1149 + "board",
1150 + "button",
1151 + "card",
1152 + "carefully",
1153 + "complain",
1154 + "crap",
1155 + "deeply",
1156 + "discover",
1157 + "drag",
1158 + "dread",
1159 + "effort",
1160 + "entire",
1161 + "fairy",
1162 + "giant",
1163 + "gotten",
1164 + "greet",
1165 + "illusion",
1166 + "jeans",
1167 + "leap",
1168 + "liquid",
1169 + "march",
1170 + "mend",
1171 + "nervous",
1172 + "nine",
1173 + "replace",
1174 + "rope",
1175 + "spine",
1176 + "stole",
1177 + "terror",
1178 + "accident",
1179 + "apple",
1180 + "balance",
1181 + "boom",
1182 + "childhood",
1183 + "collect",
1184 + "demand",
1185 + "depression",
1186 + "eventually",
1187 + "faint",
1188 + "glare",
1189 + "goal",
1190 + "group",
1191 + "honey",
1192 + "kitchen",
1193 + "laid",
1194 + "limb",
1195 + "machine",
1196 + "mere",
1197 + "mold",
1198 + "murder",
1199 + "nerve",
1200 + "painful",
1201 + "poetry",
1202 + "prince",
1203 + "rabbit",
1204 + "shelter",
1205 + "shore",
1206 + "shower",
1207 + "soothe",
1208 + "stair",
1209 + "steady",
1210 + "sunlight",
1211 + "tangle",
1212 + "tease",
1213 + "treasure",
1214 + "uncle",
1215 + "begun",
1216 + "bliss",
1217 + "canvas",
1218 + "cheer",
1219 + "claw",
1220 + "clutch",
1221 + "commit",
1222 + "crimson",
1223 + "crystal",
1224 + "delight",
1225 + "doll",
1226 + "existence",
1227 + "express",
1228 + "fog",
1229 + "football",
1230 + "gay",
1231 + "goose",
1232 + "guard",
1233 + "hatred",
1234 + "illuminate",
1235 + "mass",
1236 + "math",
1237 + "mourn",
1238 + "rich",
1239 + "rough",
1240 + "skip",
1241 + "stir",
1242 + "student",
1243 + "style",
1244 + "support",
1245 + "thorn",
1246 + "tough",
1247 + "yard",
1248 + "yearn",
1249 + "yesterday",
1250 + "advice",
1251 + "appreciate",
1252 + "autumn",
1253 + "bank",
1254 + "beam",
1255 + "bowl",
1256 + "capture",
1257 + "carve",
1258 + "collapse",
1259 + "confusion",
1260 + "creation",
1261 + "dove",
1262 + "feather",
1263 + "girlfriend",
1264 + "glory",
1265 + "government",
1266 + "harsh",
1267 + "hop",
1268 + "inner",
1269 + "loser",
1270 + "moonlight",
1271 + "neighbor",
1272 + "neither",
1273 + "peach",
1274 + "pig",
1275 + "praise",
1276 + "screw",
1277 + "shield",
1278 + "shimmer",
1279 + "sneak",
1280 + "stab",
1281 + "subject",
1282 + "throughout",
1283 + "thrown",
1284 + "tower",
1285 + "twirl",
1286 + "wow",
1287 + "army",
1288 + "arrive",
1289 + "bathroom",
1290 + "bump",
1291 + "cease",
1292 + "cookie",
1293 + "couch",
1294 + "courage",
1295 + "dim",
1296 + "guilt",
1297 + "howl",
1298 + "hum",
1299 + "husband",
1300 + "insult",
1301 + "led",
1302 + "lunch",
1303 + "mock",
1304 + "mostly",
1305 + "natural",
1306 + "nearly",
1307 + "needle",
1308 + "nerd",
1309 + "peaceful",
1310 + "perfection",
1311 + "pile",
1312 + "price",
1313 + "remove",
1314 + "roam",
1315 + "sanctuary",
1316 + "serious",
1317 + "shiny",
1318 + "shook",
1319 + "sob",
1320 + "stolen",
1321 + "tap",
1322 + "vain",
1323 + "void",
1324 + "warrior",
1325 + "wrinkle",
1326 + "affection",
1327 + "apologize",
1328 + "blossom",
1329 + "bounce",
1330 + "bridge",
1331 + "cheap",
1332 + "crumble",
1333 + "decision",
1334 + "descend",
1335 + "desperately",
1336 + "dig",
1337 + "dot",
1338 + "flip",
1339 + "frighten",
1340 + "heartbeat",
1341 + "huge",
1342 + "lazy",
1343 + "lick",
1344 + "odd",
1345 + "opinion",
1346 + "process",
1347 + "puzzle",
1348 + "quietly",
1349 + "retreat",
1350 + "score",
1351 + "sentence",
1352 + "separate",
1353 + "situation",
1354 + "skill",
1355 + "soak",
1356 + "square",
1357 + "stray",
1358 + "taint",
1359 + "task",
1360 + "tide",
1361 + "underneath",
1362 + "veil",
1363 + "whistle",
1364 + "anywhere",
1365 + "bedroom",
1366 + "bid",
1367 + "bloody",
1368 + "burden",
1369 + "careful",
1370 + "compare",
1371 + "concern",
1372 + "curtain",
1373 + "decay",
1374 + "defeat",
1375 + "describe",
1376 + "double",
1377 + "dreamer",
1378 + "driver",
1379 + "dwell",
1380 + "evening",
1381 + "flare",
1382 + "flicker",
1383 + "grandma",
1384 + "guitar",
1385 + "harm",
1386 + "horrible",
1387 + "hungry",
1388 + "indeed",
1389 + "lace",
1390 + "melody",
1391 + "monkey",
1392 + "nation",
1393 + "object",
1394 + "obviously",
1395 + "rainbow",
1396 + "salt",
1397 + "scratch",
1398 + "shown",
1399 + "shy",
1400 + "stage",
1401 + "stun",
1402 + "third",
1403 + "tickle",
1404 + "useless",
1405 + "weakness",
1406 + "worship",
1407 + "worthless",
1408 + "afternoon",
1409 + "beard",
1410 + "boyfriend",
1411 + "bubble",
1412 + "busy",
1413 + "certain",
1414 + "chin",
1415 + "concrete",
1416 + "desk",
1417 + "diamond",
1418 + "doom",
1419 + "drawn",
1420 + "due",
1421 + "felicity",
1422 + "freeze",
1423 + "frost",
1424 + "garden",
1425 + "glide",
1426 + "harmony",
1427 + "hopefully",
1428 + "hunt",
1429 + "jealous",
1430 + "lightning",
1431 + "mama",
1432 + "mercy",
1433 + "peel",
1434 + "physical",
1435 + "position",
1436 + "pulse",
1437 + "punch",
1438 + "quit",
1439 + "rant",
1440 + "respond",
1441 + "salty",
1442 + "sane",
1443 + "satisfy",
1444 + "savior",
1445 + "sheep",
1446 + "slept",
1447 + "social",
1448 + "sport",
1449 + "tuck",
1450 + "utter",
1451 + "valley",
1452 + "wolf",
1453 + "aim",
1454 + "alas",
1455 + "alter",
1456 + "arrow",
1457 + "awaken",
1458 + "beaten",
1459 + "belief",
1460 + "brand",
1461 + "ceiling",
1462 + "cheese",
1463 + "clue",
1464 + "confidence",
1465 + "connection",
1466 + "daily",
1467 + "disguise",
1468 + "eager",
1469 + "erase",
1470 + "essence",
1471 + "everytime",
1472 + "expression",
1473 + "fan",
1474 + "flag",
1475 + "flirt",
1476 + "foul",
1477 + "fur",
1478 + "giggle",
1479 + "glorious",
1480 + "ignorance",
1481 + "law",
1482 + "lifeless",
1483 + "measure",
1484 + "mighty",
1485 + "muse",
1486 + "north",
1487 + "opposite",
1488 + "paradise",
1489 + "patience",
1490 + "patient",
1491 + "pencil",
1492 + "petal",
1493 + "plate",
1494 + "ponder",
1495 + "possibly",
1496 + "practice",
1497 + "slice",
1498 + "spell",
1499 + "stock",
1500 + "strife",
1501 + "strip",
1502 + "suffocate",
1503 + "suit",
1504 + "tender",
1505 + "tool",
1506 + "trade",
1507 + "velvet",
1508 + "verse",
1509 + "waist",
1510 + "witch",
1511 + "aunt",
1512 + "bench",
1513 + "bold",
1514 + "cap",
1515 + "certainly",
1516 + "click",
1517 + "companion",
1518 + "creator",
1519 + "dart",
1520 + "delicate",
1521 + "determine",
1522 + "dish",
1523 + "dragon",
1524 + "drama",
1525 + "drum",
1526 + "dude",
1527 + "everybody",
1528 + "feast",
1529 + "forehead",
1530 + "former",
1531 + "fright",
1532 + "fully",
1533 + "gas",
1534 + "hook",
1535 + "hurl",
1536 + "invite",
1537 + "juice",
1538 + "manage",
1539 + "moral",
1540 + "possess",
1541 + "raw",
1542 + "rebel",
1543 + "royal",
1544 + "scale",
1545 + "scary",
1546 + "several",
1547 + "slight",
1548 + "stubborn",
1549 + "swell",
1550 + "talent",
1551 + "tea",
1552 + "terrible",
1553 + "thread",
1554 + "torment",
1555 + "trickle",
1556 + "usually",
1557 + "vast",
1558 + "violence",
1559 + "weave",
1560 + "acid",
1561 + "agony",
1562 + "ashamed",
1563 + "awe",
1564 + "belly",
1565 + "blend",
1566 + "blush",
1567 + "character",
1568 + "cheat",
1569 + "common",
1570 + "company",
1571 + "coward",
1572 + "creak",
1573 + "danger",
1574 + "deadly",
1575 + "defense",
1576 + "define",
1577 + "depend",
1578 + "desperate",
1579 + "destination",
1580 + "dew",
1581 + "duck",
1582 + "dusty",
1583 + "embarrass",
1584 + "engine",
1585 + "example",
1586 + "explore",
1587 + "foe",
1588 + "freely",
1589 + "frustrate",
1590 + "generation",
1591 + "glove",
1592 + "guilty",
1593 + "health",
1594 + "hurry",
1595 + "idiot",
1596 + "impossible",
1597 + "inhale",
1598 + "jaw",
1599 + "kingdom",
1600 + "mention",
1601 + "mist",
1602 + "moan",
1603 + "mumble",
1604 + "mutter",
1605 + "observe",
1606 + "ode",
1607 + "pathetic",
1608 + "pattern",
1609 + "pie",
1610 + "prefer",
1611 + "puff",
1612 + "rape",
1613 + "rare",
1614 + "revenge",
1615 + "rude",
1616 + "scrape",
1617 + "spiral",
1618 + "squeeze",
1619 + "strain",
1620 + "sunset",
1621 + "suspend",
1622 + "sympathy",
1623 + "thigh",
1624 + "throne",
1625 + "total",
1626 + "unseen",
1627 + "weapon",
1628 + "weary"
1629 + ];
1630 +}
cw_zano/lib/model/pending_zano_transaction.dart new
+52
@@ -0,0 +1,52 @@
1 +import 'package:cw_core/pending_transaction.dart';
2 +import 'package:cw_zano/api/model/destination.dart';
3 +import 'package:cw_zano/api/model/transfer_result.dart';
4 +import 'package:cw_zano/zano_formatter.dart';
5 +import 'package:cw_zano/zano_wallet.dart';
6 +
7 +class PendingZanoTransaction with PendingTransaction {
8 + PendingZanoTransaction({
9 + required this.zanoWallet,
10 + required this.destinations,
11 + required this.fee,
12 + required this.comment,
13 + required this.assetId,
14 + required this.ticker,
15 + this.decimalPoint = ZanoFormatter.defaultDecimalPoint,
16 + required this.amount,
17 + });
18 +
19 + final ZanoWalletBase zanoWallet;
20 + final List<Destination> destinations;
21 + final BigInt fee;
22 + final String comment;
23 + final String assetId;
24 + final String ticker;
25 + final int decimalPoint;
26 + final BigInt amount;
27 +
28 + @override
29 + String get id => transferResult?.txHash ?? '';
30 +
31 + @override
32 + String get hex => '';
33 +
34 + @override
35 + String get amountFormatted => '${ZanoFormatter.bigIntAmountToString(amount, decimalPoint)} $ticker';
36 +
37 + @override
38 + String get feeFormatted => '${ZanoFormatter.bigIntAmountToString(fee)} ZANO';
39 +
40 + TransferResult? transferResult;
41 +
42 + @override
43 + Future<void> commit() async {
44 + await zanoWallet.transfer(destinations, fee, comment);
45 + zanoWallet.fetchTransactions();
46 + }
47 +
48 + @override
49 + Future<String?> commitUR() {
50 + throw UnimplementedError();
51 + }
52 +}
cw_zano/lib/model/zano_asset.dart
cw_zano/lib/model/zano_balance.dart new
+17
@@ -0,0 +1,17 @@
1 +import 'package:cw_core/balance.dart';
2 +import 'package:cw_zano/zano_formatter.dart';
3 +
4 +class ZanoBalance extends Balance {
5 + final BigInt total;
6 + final BigInt unlocked;
7 + final int decimalPoint;
8 + ZanoBalance({required this.total, required this.unlocked, this.decimalPoint = ZanoFormatter.defaultDecimalPoint}) : super(unlocked.isValidInt ? unlocked.toInt() : 0, (total - unlocked).isValidInt ? (total - unlocked).toInt() : 0);
9 +
10 + ZanoBalance.empty({this.decimalPoint = ZanoFormatter.defaultDecimalPoint}): total = BigInt.zero, unlocked = BigInt.zero, super(0, 0);
11 +
12 + @override
13 + String get formattedAdditionalBalance => ZanoFormatter.bigIntAmountToString(total - unlocked, decimalPoint);
14 +
15 + @override
16 + String get formattedAvailableBalance => ZanoFormatter.bigIntAmountToString(unlocked, decimalPoint);
17 +}
cw_zano/lib/model/zano_transaction_creation_exception.dart new
+8
@@ -0,0 +1,8 @@
1 +class ZanoTransactionCreationException implements Exception {
2 + ZanoTransactionCreationException(this.message);
3 +
4 + final String message;
5 +
6 + @override
7 + String toString() => message;
8 +}
\ No newline at end of file
cw_zano/lib/model/zano_transaction_credentials.dart new
+11
@@ -0,0 +1,11 @@
1 +import 'package:cw_core/crypto_currency.dart';
2 +import 'package:cw_core/monero_transaction_priority.dart';
3 +import 'package:cw_core/output_info.dart';
4 +
5 +class ZanoTransactionCredentials {
6 + ZanoTransactionCredentials({required this.outputs, required this.priority, required this.currency});
7 +
8 + final List<OutputInfo> outputs;
9 + final MoneroTransactionPriority priority;
10 + final CryptoCurrency currency;
11 +}
cw_zano/lib/model/zano_transaction_info.dart new
+76
@@ -0,0 +1,76 @@
1 +import 'package:cw_core/format_amount.dart';
2 +import 'package:cw_core/transaction_direction.dart';
3 +import 'package:cw_core/transaction_info.dart';
4 +import 'package:cw_zano/api/model/transfer.dart';
5 +import 'package:cw_zano/zano_formatter.dart';
6 +
7 +class ZanoTransactionInfo extends TransactionInfo {
8 + ZanoTransactionInfo({
9 + required this.id,
10 + required this.height,
11 + required this.direction,
12 + required this.date,
13 + required this.isPending,
14 + required this.zanoAmount,
15 + required this.fee,
16 + required this.confirmations,
17 + required this.tokenSymbol,
18 + required this.decimalPoint,
19 + required String assetId,
20 + }) : amount = zanoAmount.isValidInt ? zanoAmount.toInt() : 0 {
21 + additionalInfo['assetId'] = assetId;
22 + }
23 +
24 + ZanoTransactionInfo.fromTransfer(Transfer transfer,
25 + {required int confirmations,
26 + required bool isIncome,
27 + required String assetId,
28 + required BigInt amount,
29 + this.tokenSymbol = 'ZANO',
30 + this.decimalPoint = ZanoFormatter.defaultDecimalPoint})
31 + : id = transfer.txHash,
32 + height = transfer.height,
33 + direction = isIncome ? TransactionDirection.incoming : TransactionDirection.outgoing,
34 + date = DateTime.fromMillisecondsSinceEpoch(transfer.timestamp * 1000),
35 + zanoAmount = amount,
36 + amount = amount.isValidInt ? amount.toInt() : 0,
37 + fee = transfer.fee,
38 + confirmations = confirmations,
39 + isPending = false,
40 + recipientAddress = transfer.remoteAddresses.isNotEmpty ? transfer.remoteAddresses.first : '' {
41 + additionalInfo = <String, dynamic>{
42 + 'comment': transfer.comment,
43 + 'assetId': assetId,
44 + };
45 + }
46 + String get assetId => additionalInfo["assetId"] as String;
47 + set assetId(String newId) => additionalInfo["assetId"] = newId;
48 + final String id;
49 + final int height;
50 + final TransactionDirection direction;
51 + final DateTime date;
52 + final bool isPending;
53 + final BigInt zanoAmount;
54 + final int amount;
55 + final int fee;
56 + final int confirmations;
57 + final int decimalPoint;
58 + late String recipientAddress;
59 + final String tokenSymbol;
60 + String? _fiatAmount;
61 + String? key;
62 +
63 + @override
64 + String amountFormatted() => '${formatAmount(ZanoFormatter.bigIntAmountToString(zanoAmount, decimalPoint))} $tokenSymbol';
65 +
66 + @override
67 + String fiatAmount() => _fiatAmount ?? '';
68 +
69 + @override
70 + void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
71 +
72 + @override
73 + String feeFormatted() => '${formatAmount(ZanoFormatter.intAmountToString(fee))} $feeCurrency';
74 +
75 + String get feeCurrency => 'ZANO';
76 +}
cw_zano/lib/model/zano_wallet_keys.dart new
+12
@@ -0,0 +1,12 @@
1 +class ZanoWalletKeys {
2 + const ZanoWalletKeys(
3 + {required this.privateSpendKey,
4 + required this.privateViewKey,
5 + required this.publicSpendKey,
6 + required this.publicViewKey});
7 +
8 + final String publicViewKey;
9 + final String privateViewKey;
10 + final String publicSpendKey;
11 + final String privateSpendKey;
12 +}
\ No newline at end of file
cw_zano/lib/zano_formatter.dart new
+74
@@ -0,0 +1,74 @@
1 +import 'dart:math';
2 +
3 +import 'package:cw_zano/zano_wallet_api.dart';
4 +import 'package:decimal/decimal.dart';
5 +import 'package:decimal/intl.dart';
6 +import 'package:fluttertoast/fluttertoast.dart';
7 +import 'package:intl/intl.dart';
8 +
9 +class ZanoFormatter {
10 + static const defaultDecimalPoint = 12;
11 +
12 + //static final numberFormat = NumberFormat()
13 + // ..maximumFractionDigits = defaultDecimalPoint
14 + // ..minimumFractionDigits = 1;
15 +
16 + static Decimal _bigIntDivision({required BigInt amount, required BigInt divider}) {
17 + return (Decimal.fromBigInt(amount) / Decimal.fromBigInt(divider)).toDecimal();
18 + }
19 +
20 + static String intAmountToString(int amount, [int decimalPoint = defaultDecimalPoint]) {
21 + final numberFormat = NumberFormat()..maximumFractionDigits = decimalPoint
22 + ..minimumFractionDigits = 1;
23 + return numberFormat.format(
24 + DecimalIntl(
25 + _bigIntDivision(
26 + amount: BigInt.from(amount),
27 + divider: BigInt.from(pow(10, decimalPoint)),
28 + ),
29 + ),
30 + )
31 + .replaceAll(',', '');
32 + }
33 +
34 + static String bigIntAmountToString(BigInt amount, [int decimalPoint = defaultDecimalPoint]) {
35 + final numberFormat = NumberFormat()..maximumFractionDigits = decimalPoint
36 + ..minimumFractionDigits = 1;
37 + return numberFormat.format(
38 + DecimalIntl(
39 + _bigIntDivision(
40 + amount: amount,
41 + divider: BigInt.from(pow(10, decimalPoint)),
42 + ),
43 + ),
44 + )
45 + .replaceAll(',', '');
46 + }
47 +
48 + static double intAmountToDouble(int amount, [int decimalPoint = defaultDecimalPoint]) => _bigIntDivision(
49 + amount: BigInt.from(amount),
50 + divider: BigInt.from(pow(10, decimalPoint)),
51 + ).toDouble();
52 +
53 + static int parseAmount(String amount, [int decimalPoint = defaultDecimalPoint]) {
54 + final resultBigInt = (Decimal.parse(amount) * Decimal.fromBigInt(BigInt.from(10).pow(decimalPoint))).toBigInt();
55 + if (!resultBigInt.isValidInt) {
56 + Fluttertoast.showToast(msg: 'Cannot transfer $amount. Maximum is ${intAmountToString(resultBigInt.toInt(), decimalPoint)}.');
57 + }
58 + return resultBigInt.toInt();
59 + }
60 +
61 + static BigInt bigIntFromDynamic(dynamic d) {
62 + if (d is int) {
63 + return BigInt.from(d);
64 + } else if (d is BigInt) {
65 + return d;
66 + } else if (d == null) {
67 + return BigInt.zero;
68 + } else {
69 + ZanoWalletApi.error('cannot cast value of type ${d.runtimeType} to BigInt');
70 + throw 'cannot cast value of type ${d.runtimeType} to BigInt';
71 + //return BigInt.zero;
72 + }
73 + }
74 +}
cw_zano/lib/zano_transaction_history.dart new
+27
@@ -0,0 +1,27 @@
1 +import 'dart:core';
2 +import 'package:mobx/mobx.dart';
3 +import 'package:cw_core/transaction_history.dart';
4 +import 'package:cw_zano/model/zano_transaction_info.dart';
5 +
6 +part 'zano_transaction_history.g.dart';
7 +
8 +class ZanoTransactionHistory = ZanoTransactionHistoryBase
9 + with _$ZanoTransactionHistory;
10 +
11 +abstract class ZanoTransactionHistoryBase
12 + extends TransactionHistoryBase<ZanoTransactionInfo> with Store {
13 + ZanoTransactionHistoryBase() {
14 + transactions = ObservableMap<String, ZanoTransactionInfo>();
15 + }
16 +
17 + @override
18 + Future<void> save() async {}
19 +
20 + @override
21 + void addOne(ZanoTransactionInfo transaction) =>
22 + transactions[transaction.id] = transaction;
23 +
24 + @override
25 + void addMany(Map<String, ZanoTransactionInfo> transactions) =>
26 + this.transactions.addAll(transactions);
27 +}
cw_zano/lib/zano_utils.dart new
+17
@@ -0,0 +1,17 @@
1 +import 'dart:convert';
2 +
3 +import 'package:monero/zano.dart' as zano;
4 +import 'package:cw_zano/api/model/get_address_info_result.dart';
5 +
6 +class ZanoUtils {
7 + static bool validateAddress(String address) {
8 + try {
9 + final result = GetAddressInfoResult.fromJson(
10 + jsonDecode(zano.PlainWallet_getAddressInfo(address)) as Map<String, dynamic>,
11 + );
12 + return result.valid;
13 + } catch (err) {
14 + return false;
15 + }
16 + }
17 +}
cw_zano/lib/zano_wallet.dart new
+545
@@ -0,0 +1,545 @@
1 +import 'dart:async';
2 +import 'dart:io';
3 +import 'dart:math';
4 +
5 +import 'package:cw_core/cake_hive.dart';
6 +import 'package:cw_core/crypto_currency.dart';
7 +import 'package:cw_core/node.dart';
8 +import 'package:cw_core/pathForWallet.dart';
9 +import 'package:cw_core/pending_transaction.dart';
10 +import 'package:cw_core/sync_status.dart';
11 +import 'package:cw_core/transaction_priority.dart';
12 +import 'package:cw_core/utils/print_verbose.dart';
13 +import 'package:cw_core/wallet_base.dart';
14 +import 'package:cw_core/wallet_credentials.dart';
15 +import 'package:cw_core/wallet_info.dart';
16 +import 'package:cw_core/zano_asset.dart';
17 +import 'package:cw_zano/api/model/create_wallet_result.dart';
18 +import 'package:cw_zano/api/model/destination.dart';
19 +import 'package:cw_zano/api/model/get_recent_txs_and_info_result.dart';
20 +import 'package:cw_zano/api/model/get_wallet_status_result.dart';
21 +import 'package:cw_zano/api/model/transfer.dart';
22 +import 'package:cw_zano/model/pending_zano_transaction.dart';
23 +import 'package:cw_zano/model/zano_balance.dart';
24 +import 'package:cw_zano/model/zano_transaction_creation_exception.dart';
25 +import 'package:cw_zano/model/zano_transaction_credentials.dart';
26 +import 'package:cw_zano/model/zano_transaction_info.dart';
27 +import 'package:cw_zano/model/zano_wallet_keys.dart';
28 +import 'package:cw_zano/zano_formatter.dart';
29 +import 'package:cw_zano/zano_transaction_history.dart';
30 +import 'package:cw_zano/zano_wallet_addresses.dart';
31 +import 'package:cw_zano/zano_wallet_api.dart';
32 +import 'package:cw_zano/zano_wallet_exceptions.dart';
33 +import 'package:cw_zano/zano_wallet_service.dart';
34 +import 'package:cw_zano/api/model/balance.dart';
35 +
36 +import 'package:mobx/mobx.dart';
37 +
38 +part 'zano_wallet.g.dart';
39 +
40 +class ZanoWallet = ZanoWalletBase with _$ZanoWallet;
41 +
42 +abstract class ZanoWalletBase
43 + extends WalletBase<ZanoBalance, ZanoTransactionHistory, ZanoTransactionInfo>
44 + with Store, ZanoWalletApi {
45 + static const int _autoSaveIntervalSeconds = 30;
46 + static const int _pollIntervalMilliseconds = 2000;
47 + static const int _maxLoadAssetsRetries = 5;
48 +
49 + @override
50 + void setPassword(String password) {
51 + _password = password;
52 + super.setPassword(password);
53 + }
54 +
55 + String _password;
56 +
57 + @override
58 + String get password => _password;
59 +
60 + @override
61 + Future<String> signMessage(String message, {String? address = null}) {
62 + throw UnimplementedError();
63 + }
64 +
65 + @override
66 + Future<bool> verifyMessage(String message, String signature, {String? address = null}) {
67 + throw UnimplementedError();
68 + }
69 +
70 + @override
71 + ZanoWalletAddresses walletAddresses;
72 +
73 + @override
74 + @observable
75 + SyncStatus syncStatus;
76 +
77 + @override
78 + @observable
79 + ObservableMap<CryptoCurrency, ZanoBalance> balance;
80 +
81 + @override
82 + String seed = '';
83 +
84 + @override
85 + ZanoWalletKeys keys = ZanoWalletKeys(
86 + privateSpendKey: '', privateViewKey: '', publicSpendKey: '', publicViewKey: '');
87 +
88 + static const String zanoAssetId =
89 + 'd6329b5b1f7c0805b5c345f4957554002a2f557845f64d7645dae0e051a6498a';
90 +
91 + Map<String, ZanoAsset> zanoAssets = {};
92 +
93 + Timer? _updateSyncInfoTimer;
94 +
95 + int _lastKnownBlockHeight = 0;
96 + int _initialSyncHeight = 0;
97 + int currentDaemonHeight = 0;
98 + bool _isTransactionUpdating;
99 + bool _hasSyncAfterStartup;
100 + Timer? _autoSaveTimer;
101 +
102 + /// number of transactions in each request
103 + static final int _txChunkSize = (pow(2, 32) - 1).toInt();
104 +
105 + ZanoWalletBase(WalletInfo walletInfo, String password)
106 + : balance = ObservableMap.of({CryptoCurrency.zano: ZanoBalance.empty()}),
107 + _isTransactionUpdating = false,
108 + _hasSyncAfterStartup = false,
109 + walletAddresses = ZanoWalletAddresses(walletInfo),
110 + syncStatus = NotConnectedSyncStatus(),
111 + _password = password,
112 + super(walletInfo) {
113 + transactionHistory = ZanoTransactionHistory();
114 + if (!CakeHive.isAdapterRegistered(ZanoAsset.typeId)) {
115 + CakeHive.registerAdapter(ZanoAssetAdapter());
116 + }
117 + }
118 +
119 + @override
120 + int calculateEstimatedFee(TransactionPriority priority, [int? amount = null]) =>
121 + getCurrentTxFee(priority);
122 +
123 + @override
124 + Future<void> changePassword(String password) async {
125 + setPassword(password);
126 + }
127 +
128 + static Future<ZanoWallet> create({required WalletCredentials credentials}) async {
129 + final wallet = ZanoWallet(credentials.walletInfo!, credentials.password!);
130 + await wallet.initWallet();
131 + final path = await pathForWallet(name: credentials.name, type: credentials.walletInfo!.type);
132 + final createWalletResult = await wallet.createWallet(path, credentials.password!);
133 + await wallet.initWallet();
134 + await wallet.parseCreateWalletResult(createWalletResult);
135 + await wallet.init(createWalletResult.wi.address);
136 + return wallet;
137 + }
138 +
139 + static Future<ZanoWallet> restore(
140 + {required ZanoRestoreWalletFromSeedCredentials credentials}) async {
141 + final wallet = ZanoWallet(credentials.walletInfo!, credentials.password!);
142 + await wallet.initWallet();
143 + final path = await pathForWallet(name: credentials.name, type: credentials.walletInfo!.type);
144 + final createWalletResult = await wallet.restoreWalletFromSeed(
145 + path, credentials.password!, credentials.mnemonic, credentials.passphrase);
146 + await wallet.initWallet();
147 + await wallet.parseCreateWalletResult(createWalletResult);
148 + await wallet.init(createWalletResult.wi.address);
149 + return wallet;
150 + }
151 +
152 + static Future<ZanoWallet> open(
153 + {required String name, required String password, required WalletInfo walletInfo}) async {
154 + final path = await pathForWallet(name: name, type: walletInfo.type);
155 + final wallet = ZanoWallet(walletInfo, password);
156 + await wallet.initWallet();
157 + final createWalletResult = await wallet.loadWallet(path, password);
158 + await wallet.initWallet();
159 + await wallet.parseCreateWalletResult(createWalletResult);
160 + await wallet.init(createWalletResult.wi.address);
161 + return wallet;
162 + }
163 +
164 + Future<void> parseCreateWalletResult(CreateWalletResult result) async {
165 + hWallet = result.walletId;
166 + seed = result.seed;
167 + ZanoWalletApi.info('setting hWallet = ${result.walletId}');
168 + walletAddresses.address = result.wi.address;
169 + await loadAssets(result.wi.balances, maxRetries: _maxLoadAssetsRetries);
170 + for (final item in result.wi.balances) {
171 + if (item.assetInfo.assetId == zanoAssetId) {
172 + balance[CryptoCurrency.zano] = ZanoBalance(
173 + total: item.total,
174 + unlocked: item.unlocked,
175 + );
176 + }
177 + }
178 + if (result.recentHistory.history != null) {
179 + final transfers = result.recentHistory.history!;
180 + final transactions = Transfer.makeMap(transfers, zanoAssets, currentDaemonHeight);
181 + transactionHistory.addMany(transactions);
182 + await transactionHistory.save();
183 + }
184 + }
185 +
186 + @override
187 + Future<void> close({bool shouldCleanup = true}) async {
188 + closeWallet();
189 + _updateSyncInfoTimer?.cancel();
190 + _autoSaveTimer?.cancel();
191 + }
192 +
193 + @override
194 + Future<void> connectToNode({required Node node}) async {
195 + syncStatus = ConnectingSyncStatus();
196 + await setupNode(node.uriRaw);
197 + syncStatus = ConnectedSyncStatus();
198 + }
199 +
200 + @override
201 + Future<PendingTransaction> createTransaction(Object credentials) async {
202 + credentials as ZanoTransactionCredentials;
203 + final isZano = credentials.currency == CryptoCurrency.zano;
204 + final outputs = credentials.outputs;
205 + final hasMultiDestination = outputs.length > 1;
206 + final unlockedBalanceZano = balance[CryptoCurrency.zano]?.unlocked ?? BigInt.zero;
207 + final unlockedBalanceCurrency = balance[credentials.currency]?.unlocked ?? BigInt.zero;
208 + final fee = BigInt.from(calculateEstimatedFee(credentials.priority));
209 + late BigInt totalAmount;
210 + void checkForEnoughBalances() {
211 + if (isZano) {
212 + if (totalAmount + fee > unlockedBalanceZano) {
213 + throw ZanoTransactionCreationException(
214 + "You don't have enough coins (required: ${ZanoFormatter.bigIntAmountToString(totalAmount + fee)} ZANO, unlocked ${ZanoFormatter.bigIntAmountToString(unlockedBalanceZano)} ZANO).");
215 + }
216 + } else {
217 + if (fee > unlockedBalanceZano) {
218 + throw ZanoTransactionCreationException(
219 + "You don't have enough coins (required: ${ZanoFormatter.bigIntAmountToString(fee)} ZANO, unlocked ${ZanoFormatter.bigIntAmountToString(unlockedBalanceZano)} ZANO).");
220 + }
221 + if (totalAmount > unlockedBalanceCurrency) {
222 + throw ZanoTransactionCreationException(
223 + "You don't have enough coins (required: ${ZanoFormatter.bigIntAmountToString(totalAmount, credentials.currency.decimals)} ${credentials.currency.title}, unlocked ${ZanoFormatter.bigIntAmountToString(unlockedBalanceCurrency, credentials.currency.decimals)} ${credentials.currency.title}).");
224 + }
225 + }
226 + }
227 +
228 + final assetId = isZano ? zanoAssetId : (credentials.currency as ZanoAsset).assetId;
229 + late List<Destination> destinations;
230 + if (hasMultiDestination) {
231 + if (outputs.any((output) => output.sendAll || (output.formattedCryptoAmount ?? 0) <= 0)) {
232 + throw ZanoTransactionCreationException("You don't have enough coins.");
233 + }
234 + totalAmount = outputs.fold(
235 + BigInt.zero, (acc, value) => acc + BigInt.from(value.formattedCryptoAmount ?? 0));
236 + checkForEnoughBalances();
237 + destinations = outputs
238 + .map((output) => Destination(
239 + amount: BigInt.from(output.formattedCryptoAmount ?? 0),
240 + address: output.isParsedAddress ? output.extractedAddress! : output.address,
241 + assetId: assetId,
242 + ))
243 + .toList();
244 + } else {
245 + final output = outputs.first;
246 + if (output.sendAll) {
247 + if (isZano) {
248 + totalAmount = unlockedBalanceZano - fee;
249 + } else {
250 + totalAmount = unlockedBalanceCurrency;
251 + }
252 + } else {
253 + totalAmount = BigInt.from(output.formattedCryptoAmount!);
254 + }
255 + checkForEnoughBalances();
256 + destinations = [
257 + Destination(
258 + amount: totalAmount,
259 + address: output.isParsedAddress ? output.extractedAddress! : output.address,
260 + assetId: assetId,
261 + )
262 + ];
263 + }
264 + return PendingZanoTransaction(
265 + zanoWallet: this,
266 + destinations: destinations,
267 + fee: fee,
268 + comment: outputs.first.note ?? '',
269 + assetId: assetId,
270 + ticker: credentials.currency.title,
271 + decimalPoint: credentials.currency.decimals,
272 + amount: totalAmount,
273 + );
274 + }
275 +
276 + @override
277 + Future<Map<String, ZanoTransactionInfo>> fetchTransactions() async {
278 + try {
279 + final transfers = <Transfer>[];
280 + late GetRecentTxsAndInfoResult result;
281 + do {
282 + result = await getRecentTxsAndInfo(offset: 0, count: _txChunkSize);
283 + // _lastTxIndex += result.transfers.length;
284 + transfers.addAll(result.transfers);
285 + } while (result.lastItemIndex + 1 < result.totalTransfers);
286 + return Transfer.makeMap(transfers, zanoAssets, currentDaemonHeight);
287 + } catch (e) {
288 + ZanoWalletApi.error(e.toString());
289 + return {};
290 + }
291 + }
292 +
293 + Future<void> init(String address) async {
294 + await walletAddresses.init();
295 + await walletAddresses.updateAddress(address);
296 + await updateTransactions();
297 + _autoSaveTimer = Timer.periodic(Duration(seconds: _autoSaveIntervalSeconds), (_) async {
298 + await save();
299 + });
300 + }
301 +
302 + @override
303 + Future<void> renameWalletFiles(String newWalletName) async {
304 + final currentWalletPath = await pathForWallet(name: name, type: type);
305 + final currentCacheFile = File(currentWalletPath);
306 + final currentKeysFile = File('$currentWalletPath.keys');
307 + final currentAddressListFile = File('$currentWalletPath.address.txt');
308 +
309 + final newWalletPath = await pathForWallet(name: newWalletName, type: type);
310 +
311 + // Copies current wallet files into new wallet name's dir and files
312 + if (currentCacheFile.existsSync()) {
313 + await currentCacheFile.copy(newWalletPath);
314 + }
315 + if (currentKeysFile.existsSync()) {
316 + await currentKeysFile.copy('$newWalletPath.keys');
317 + }
318 + if (currentAddressListFile.existsSync()) {
319 + await currentAddressListFile.copy('$newWalletPath.address.txt');
320 + }
321 +
322 + // Delete old name's dir and files
323 + await Directory(currentWalletPath).delete(recursive: true);
324 + }
325 +
326 + @override
327 + Future<void> rescan({required int height}) => throw UnimplementedError();
328 +
329 + @override
330 + Future<void> save() async {
331 + try {
332 + await store();
333 + await walletAddresses.updateAddressesInBox();
334 + } catch (e) {
335 + ZanoWalletApi.error('Error while saving Zano wallet file ${e.toString()}');
336 + }
337 + }
338 +
339 + Future<void> loadAssets(List<Balance> balances, {int maxRetries = 1}) async {
340 + List<ZanoAsset> assets = [];
341 + int retryCount = 0;
342 +
343 + while (retryCount < maxRetries) {
344 + try {
345 + assets = await getAssetsWhitelist();
346 + break;
347 + } on ZanoWalletBusyException {
348 + if (retryCount < maxRetries - 1) {
349 + retryCount++;
350 + await Future.delayed(Duration(seconds: 1));
351 + } else {
352 + ZanoWalletApi.error('failed to load assets after $retryCount retries');
353 + break;
354 + }
355 + }
356 + }
357 + zanoAssets = {};
358 + for (final asset in assets) {
359 + final newAsset = ZanoAsset.copyWith(
360 + asset,
361 + enabled: balances.any((element) => element.assetId == asset.assetId),
362 + );
363 + zanoAssets.putIfAbsent(asset.assetId, () => newAsset);
364 + }
365 + }
366 +
367 + @override
368 + Future<void> startSync() async {
369 + try {
370 + syncStatus = AttemptingSyncStatus();
371 + _lastKnownBlockHeight = 0;
372 + _initialSyncHeight = 0;
373 + _updateSyncInfoTimer ??=
374 + Timer.periodic(Duration(milliseconds: _pollIntervalMilliseconds), (_) async {
375 + GetWalletStatusResult walletStatus;
376 + // ignoring get wallet status exception (in case of wrong wallet id)
377 + try {
378 + walletStatus = await getWalletStatus();
379 + } on ZanoWalletException {
380 + return;
381 + }
382 + currentDaemonHeight = walletStatus.currentDaemonHeight;
383 + _updateSyncProgress(walletStatus);
384 +
385 + // we can call getWalletInfo ONLY if getWalletStatus returns NOT is in long refresh and wallet state is 2 (ready)
386 + if (!walletStatus.isInLongRefresh && walletStatus.walletState == 2) {
387 + final walletInfo = await getWalletInfo();
388 + seed = walletInfo.wiExtended.seed;
389 + keys = ZanoWalletKeys(
390 + privateSpendKey: walletInfo.wiExtended.spendPrivateKey,
391 + privateViewKey: walletInfo.wiExtended.viewPrivateKey,
392 + publicSpendKey: walletInfo.wiExtended.spendPublicKey,
393 + publicViewKey: walletInfo.wiExtended.viewPublicKey,
394 + );
395 + loadAssets(walletInfo.wi.balances);
396 + // matching balances and whitelists
397 + // 1. show only balances available in whitelists
398 + // 2. set whitelists available in balances as 'enabled' ('disabled' by default)
399 + for (final b in walletInfo.wi.balances) {
400 + if (b.assetId == zanoAssetId) {
401 + balance[CryptoCurrency.zano] = ZanoBalance(total: b.total, unlocked: b.unlocked);
402 + } else {
403 + final asset = zanoAssets[b.assetId];
404 + if (asset == null) {
405 + ZanoWalletApi.error('balance for an unknown asset ${b.assetInfo.assetId}');
406 + continue;
407 + }
408 + if (balance.keys.any(
409 + (element) => element is ZanoAsset && element.assetId == b.assetInfo.assetId)) {
410 + balance[balance.keys.firstWhere((element) =>
411 + element is ZanoAsset && element.assetId == b.assetInfo.assetId)] =
412 + ZanoBalance(
413 + total: b.total, unlocked: b.unlocked, decimalPoint: asset.decimalPoint);
414 + } else {
415 + balance[asset] = ZanoBalance(
416 + total: b.total, unlocked: b.unlocked, decimalPoint: asset.decimalPoint);
417 + }
418 + }
419 + }
420 + await updateTransactions();
421 + // removing balances for assets missing in wallet info balances
422 + balance.removeWhere(
423 + (key, _) =>
424 + key != CryptoCurrency.zano &&
425 + !walletInfo.wi.balances
426 + .any((element) => element.assetId == (key as ZanoAsset).assetId),
427 + );
428 + }
429 + });
430 + } catch (e) {
431 + syncStatus = FailedSyncStatus();
432 + ZanoWalletApi.error(e.toString());
433 + }
434 + }
435 +
436 + @override
437 + Future<void>? updateBalance() => null;
438 +
439 + Future<void> updateTransactions() async {
440 + try {
441 + if (_isTransactionUpdating) {
442 + return;
443 + }
444 + _isTransactionUpdating = true;
445 + final transactions = await fetchTransactions();
446 + if (transactions.length == transactionHistory.transactions.length) {
447 + _isTransactionUpdating = false;
448 + return;
449 + }
450 + transactionHistory.clear();
451 + transactionHistory.addMany(transactions);
452 + await transactionHistory.save();
453 + _isTransactionUpdating = false;
454 + } catch (e) {
455 + printV("e: $e");
456 + ZanoWalletApi.error(e.toString());
457 + _isTransactionUpdating = false;
458 + }
459 + }
460 +
461 + Future<CryptoCurrency> addZanoAssetById(String assetId) async {
462 + if (zanoAssets.containsKey(assetId)) {
463 + throw ZanoWalletException('zano asset with id $assetId already added');
464 + }
465 + final assetDescriptor = await addAssetsWhitelist(assetId);
466 + if (assetDescriptor == null) {
467 + throw ZanoWalletException("there's no zano asset with id $assetId");
468 + }
469 + final asset = ZanoAsset.copyWith(
470 + assetDescriptor,
471 + assetId: assetId,
472 + enabled: true,
473 + );
474 + zanoAssets[asset.assetId] = asset;
475 + balance[asset] = ZanoBalance.empty(decimalPoint: asset.decimalPoint);
476 + return asset;
477 + }
478 +
479 + Future<void> changeZanoAssetAvailability(ZanoAsset asset) async {
480 + if (asset.enabled) {
481 + final assetDescriptor = await addAssetsWhitelist(asset.assetId);
482 + if (assetDescriptor == null) {
483 + ZanoWalletApi.error('Error adding zano asset');
484 + }
485 + } else {
486 + final result = await removeAssetsWhitelist(asset.assetId);
487 + if (result == false) {
488 + ZanoWalletApi.error('Error removing zano asset');
489 + }
490 + }
491 + }
492 +
493 + Future<void> deleteZanoAsset(ZanoAsset asset) async {
494 + final _ = await removeAssetsWhitelist(asset.assetId);
495 + }
496 +
497 + Future<ZanoAsset?> getZanoAsset(String assetId) async {
498 + return await getAssetInfo(assetId);
499 + }
500 +
501 + Future<void> _askForUpdateTransactionHistory() async => await updateTransactions();
502 +
503 + void _onNewBlock(int height, int blocksLeft, double ptc) async {
504 + try {
505 + if (blocksLeft < 1000) {
506 + await _askForUpdateTransactionHistory();
507 + syncStatus = SyncedSyncStatus();
508 +
509 + if (!_hasSyncAfterStartup) {
510 + _hasSyncAfterStartup = true;
511 + await save();
512 + }
513 + } else {
514 + syncStatus = SyncingSyncStatus(blocksLeft, ptc);
515 + }
516 + } catch (e) {
517 + ZanoWalletApi.error(e.toString());
518 + }
519 + }
520 +
521 + void _updateSyncProgress(GetWalletStatusResult walletStatus) {
522 + final syncHeight = walletStatus.currentWalletHeight;
523 + if (_initialSyncHeight <= 0) {
524 + _initialSyncHeight = syncHeight;
525 + }
526 + final bchHeight = walletStatus.currentDaemonHeight;
527 +
528 + if (_lastKnownBlockHeight == syncHeight) {
529 + return;
530 + }
531 +
532 + _lastKnownBlockHeight = syncHeight;
533 + final track = bchHeight - _initialSyncHeight;
534 + final diff = track - (bchHeight - syncHeight);
535 + final ptc = diff <= 0 ? 0.0 : diff / track;
536 + final left = bchHeight - syncHeight;
537 +
538 + if (syncHeight < 0 || left < 0) {
539 + return;
540 + }
541 +
542 + // 1. Actual new height; 2. Blocks left to finish; 3. Progress in percents;
543 + _onNewBlock.call(syncHeight, left, ptc);
544 + }
545 +}
cw_zano/lib/zano_wallet_addresses.dart new
+40
@@ -0,0 +1,40 @@
1 +import 'package:cw_core/wallet_addresses.dart';
2 +import 'package:cw_core/wallet_info.dart';
3 +import 'package:cw_zano/zano_wallet_api.dart';
4 +import 'package:mobx/mobx.dart';
5 +
6 +part 'zano_wallet_addresses.g.dart';
7 +
8 +class ZanoWalletAddresses = ZanoWalletAddressesBase with _$ZanoWalletAddresses;
9 +
10 +abstract class ZanoWalletAddressesBase extends WalletAddresses with Store {
11 + ZanoWalletAddressesBase(WalletInfo walletInfo)
12 + : address = '',
13 + super(walletInfo);
14 +
15 + @override
16 + @observable
17 + String address;
18 +
19 + @override
20 + Future<void> init() async {
21 + address = walletInfo.address;
22 + await updateAddressesInBox();
23 + }
24 +
25 + Future<void> updateAddress(String address) async {
26 + this.address = address;
27 + await updateAddressesInBox();
28 + }
29 +
30 + @override
31 + Future<void> updateAddressesInBox() async {
32 + try {
33 + addressesMap.clear();
34 + addressesMap[address] = '';
35 + await saveAddressesInBox();
36 + } catch (e) {
37 + ZanoWalletApi.error(e.toString());
38 + }
39 + }
40 +}
cw_zano/lib/zano_wallet_api.dart new
+467
@@ -0,0 +1,467 @@
1 +import 'dart:convert' as convert;
2 +import 'dart:ffi';
3 +import 'dart:isolate';
4 +
5 +import 'package:cw_core/transaction_priority.dart';
6 +import 'package:cw_core/utils/print_verbose.dart';
7 +import 'package:cw_core/zano_asset.dart';
8 +import 'package:cw_zano/api/consts.dart';
9 +import 'package:cw_zano/api/model/asset_id_params.dart';
10 +import 'package:cw_zano/api/model/create_wallet_result.dart';
11 +import 'package:cw_zano/api/model/destination.dart';
12 +import 'package:cw_zano/api/model/get_address_info_result.dart';
13 +import 'package:cw_zano/api/model/get_recent_txs_and_info_params.dart';
14 +import 'package:cw_zano/api/model/get_recent_txs_and_info_result.dart';
15 +import 'package:cw_zano/api/model/get_wallet_info_result.dart';
16 +import 'package:cw_zano/api/model/get_wallet_status_result.dart';
17 +import 'package:cw_zano/api/model/proxy_to_daemon_params.dart';
18 +import 'package:cw_zano/api/model/proxy_to_daemon_result.dart';
19 +import 'package:cw_zano/api/model/store_result.dart';
20 +import 'package:cw_zano/api/model/transfer.dart';
21 +import 'package:cw_zano/api/model/transfer_params.dart';
22 +import 'package:cw_zano/api/model/transfer_result.dart';
23 +import 'package:cw_zano/zano_wallet_exceptions.dart';
24 +import 'package:ffi/ffi.dart';
25 +import 'package:flutter/foundation.dart';
26 +import 'package:json_bigint/json_bigint.dart';
27 +import 'package:monero/zano.dart' as zano;
28 +import 'package:monero/src/generated_bindings_zano.g.dart' as zanoapi;
29 +
30 +mixin ZanoWalletApi {
31 + static const _maxReopenAttempts = 5;
32 + static const _logInfo = false;
33 + static const _logError = true;
34 + static const _logJson = false;
35 + static const int _zanoMixinValue = 10;
36 +
37 + int _hWallet = 0;
38 +
39 + int get hWallet => _hWallet;
40 +
41 + set hWallet(int value) {
42 + _hWallet = value;
43 + }
44 +
45 + int getCurrentTxFee(TransactionPriority priority) => zano.PlainWallet_getCurrentTxFee(priority.raw);
46 +
47 + void setPassword(String password) => zano.PlainWallet_resetWalletPassword(hWallet, password);
48 +
49 + void closeWallet([int? walletToClose]) async {
50 + info('close_wallet ${walletToClose ?? hWallet}');
51 + final result = await _closeWallet(walletToClose ?? hWallet);
52 + info('close_wallet result $result');
53 + }
54 +
55 + Future<bool> initWallet() async {
56 + // pathForWallet(name: , type: type)
57 + final result = zano.PlainWallet_init("", "", 0);
58 + printV(result);
59 + return result == "OK";
60 + }
61 +
62 + Future<bool> setupNode(String nodeUrl) async {
63 + await _setupNode(hWallet, nodeUrl);
64 + return true;
65 + }
66 +
67 + Future<GetWalletInfoResult> getWalletInfo() async {
68 + final json = await _getWalletInfo(hWallet);
69 + final result = GetWalletInfoResult.fromJson(jsonDecode(json));
70 + _json('get_wallet_info', json);
71 + info('get_wallet_info got ${result.wi.balances.length} balances: ${result.wi.balances} seed: ${_shorten(result.wiExtended.seed)}');
72 + return result;
73 + }
74 +
75 + Future<GetWalletStatusResult> getWalletStatus() async {
76 + final json = await _getWalletStatus(hWallet);
77 + if (json == Consts.errorWalletWrongId) {
78 + error('wrong wallet id');
79 + throw ZanoWalletException('Wrong wallet id');
80 + }
81 + final status = GetWalletStatusResult.fromJson(jsonDecode(json));
82 + _json('get_wallet_status', json);
83 + if (_logInfo)
84 + info(
85 + 'get_wallet_status connected: ${status.isDaemonConnected} in refresh: ${status.isInLongRefresh} progress: ${status.progress} wallet state: ${status.walletState} sync: ${status.currentWalletHeight}/${status.currentDaemonHeight} ${(status.currentWalletHeight/status.currentDaemonHeight*100).toStringAsFixed(2)}%');
86 + return status;
87 + }
88 +
89 + Future<String> invokeMethod(String methodName, Object params) async {
90 + final request = jsonEncode({
91 + "method": methodName,
92 + "params": params,
93 + });
94 + final invokeResult = await callSyncMethod('invoke', hWallet, request);
95 + try {
96 + jsonDecode(invokeResult);
97 + } catch (e) {
98 + if (invokeResult.contains(Consts.errorWalletWrongId)) throw ZanoWalletException('Wrong wallet id');
99 + error('exception in parsing json in invokeMethod: $invokeResult');
100 + rethrow;
101 + }
102 + return invokeResult;
103 + }
104 +
105 + Future<List<ZanoAsset>> getAssetsWhitelist() async {
106 + try {
107 + final json = await invokeMethod('assets_whitelist_get', '{}');
108 + _json('assets_whitelist_get', json);
109 + final map = jsonDecode(json) as Map<String, dynamic>?;
110 + _checkForErrors(map);
111 + List<ZanoAsset> assets(String type, bool isGlobalWhitelist) =>
112 + (map?['result']?[type] as List<dynamic>?)
113 + ?.map((e) => ZanoAsset.fromJson(e as Map<String, dynamic>, isInGlobalWhitelist: isGlobalWhitelist))
114 + .toList() ??
115 + [];
116 + final localWhitelist = assets('local_whitelist', false);
117 + final globalWhitelist = assets('global_whitelist', true);
118 + final ownAssets = assets('own_assets', false);
119 + if (_logInfo)
120 + info('assets_whitelist_get got local whitelist: ${localWhitelist.length} ($localWhitelist); '
121 + 'global whitelist: ${globalWhitelist.length} ($globalWhitelist); '
122 + 'own assets: ${ownAssets.length} ($ownAssets)');
123 + return [...globalWhitelist, ...localWhitelist, ...ownAssets];
124 + } catch (e) {
125 + error('assets_whitelist_get $e');
126 + return [];
127 + // rethrow;
128 + }
129 + }
130 +
131 + Future<ZanoAsset?> addAssetsWhitelist(String assetId) async {
132 + try {
133 + final json = await invokeMethod('assets_whitelist_add', AssetIdParams(assetId: assetId));
134 + _json('assets_whitelist_add $assetId', json);
135 + final map = jsonDecode(json) as Map<String, dynamic>?;
136 + _checkForErrors(map);
137 + if (map!['result']!['status']! == 'OK') {
138 + final assetDescriptor = ZanoAsset.fromJson(map['result']!['asset_descriptor']! as Map<String, dynamic>);
139 + info('assets_whitelist_add added ${assetDescriptor.fullName} ${assetDescriptor.ticker}');
140 + return assetDescriptor;
141 + } else {
142 + info('assets_whitelist_add status ${map['result']!['status']!}');
143 + return null;
144 + }
145 + } catch (e) {
146 + error('assets_whitelist_add $e');
147 + return null;
148 + }
149 + }
150 +
151 + Future<bool> removeAssetsWhitelist(String assetId) async {
152 + try {
153 + final json = await invokeMethod('assets_whitelist_remove', AssetIdParams(assetId: assetId));
154 + _json('assets_whitelist_remove $assetId', json);
155 + final map = jsonDecode(json) as Map<String, dynamic>?;
156 + _checkForErrors(map);
157 + info('assets_whitelist_remove status ${map!['result']!['status']!}');
158 + return (map['result']!['status']! == 'OK');
159 + } catch (e) {
160 + error('assets_whitelist_remove $e');
161 + return false;
162 + }
163 + }
164 +
165 + Future<ProxyToDaemonResult?> _proxyToDaemon(String uri, String body) async {
166 + final json = await invokeMethod('proxy_to_daemon', ProxyToDaemonParams(body: body, uri: uri));
167 + final map = jsonDecode(json) as Map<String, dynamic>?;
168 + _checkForErrors(map);
169 + return ProxyToDaemonResult.fromJson(map!['result'] as Map<String, dynamic>);
170 + }
171 +
172 + Future<ZanoAsset?> getAssetInfo(String assetId) async {
173 + final methodName = 'get_asset_info';
174 + final params = AssetIdParams(assetId: assetId);
175 + final result = await _proxyToDaemon('/json_rpc', '{"method": "$methodName","params": ${jsonEncode(params)}}');
176 + _json('$methodName $assetId', result?.body ?? '');
177 + if (result == null) {
178 + error('get_asset_info empty result');
179 + return null;
180 + }
181 + final map = jsonDecode(result.body) as Map<String, dynamic>?;
182 + if (map!['error'] != null) {
183 + info('get_asset_info $assetId error ${map['error']!['code']} ${map['error']!['message']}');
184 + return null;
185 + } else if (map['result']!['status']! == 'OK') {
186 + final assetDescriptor = ZanoAsset.fromJson(map['result']!['asset_descriptor']! as Map<String, dynamic>);
187 + info('get_asset_info $assetId ${assetDescriptor.fullName} ${assetDescriptor.ticker}');
188 + return assetDescriptor;
189 + } else {
190 + info('get_asset_info $assetId status ${map['result']!['status']!}');
191 + return null;
192 + }
193 + }
194 +
195 + Future<StoreResult?> store() async {
196 + try {
197 + final json = await invokeMethod('store', '{}');
198 + final map = jsonDecode(json) as Map<String, dynamic>?;
199 + _checkForErrors(map);
200 + return StoreResult.fromJson(map!['result'] as Map<String, dynamic>);
201 + } catch (e) {
202 + error('store $e');
203 + return null;
204 + }
205 + }
206 +
207 + Future<GetRecentTxsAndInfoResult> getRecentTxsAndInfo({required int offset, required int count}) async {
208 + info('get_recent_txs_and_info $offset $count');
209 + try {
210 + final json = await invokeMethod('get_recent_txs_and_info', GetRecentTxsAndInfoParams(offset: offset, count: count));
211 + _json('get_recent_txs_and_info', json);
212 + final map = jsonDecode(json) as Map<String, dynamic>?;
213 + _checkForErrors(map);
214 + final lastItemIndex = map?['result']?['last_item_index'] as int?;
215 + final totalTransfers = map?['result']?['total_transfers'] as int?;
216 + final transfers = map?['result']?['transfers'] as List<dynamic>?;
217 + if (transfers == null || lastItemIndex == null || totalTransfers == null) {
218 + error('get_recent_txs_and_info empty transfers');
219 + return GetRecentTxsAndInfoResult.empty();
220 + }
221 + info('get_recent_txs_and_info transfers.length: ${transfers.length}');
222 + return GetRecentTxsAndInfoResult(
223 + transfers: transfers.map((e) => Transfer.fromJson(e as Map<String, dynamic>)).toList(),
224 + lastItemIndex: lastItemIndex,
225 + totalTransfers: totalTransfers,
226 + );
227 + } catch (e) {
228 + error('get_recent_txs_and_info $e');
229 + return GetRecentTxsAndInfoResult.empty();
230 + }
231 + }
232 +
233 + GetAddressInfoResult getAddressInfo(String address) => GetAddressInfoResult.fromJson(
234 + jsonDecode(zano.PlainWallet_getAddressInfo(address)),
235 + );
236 +
237 + String _shorten(String s) => s.length > 10 ? '${s.substring(0, 4)}...${s.substring(s.length - 4)}' : s;
238 +
239 + Future<CreateWalletResult> createWallet(String path, String password) async {
240 + info('create_wallet path $path password ${_shorten(password)}');
241 + final json = zano.PlainWallet_generate(path, password);
242 + _json('create_wallet', json);
243 + final map = jsonDecode(json) as Map<String, dynamic>?;
244 + if (map?['error'] != null) {
245 + final code = map!['error']?['code'] ?? '';
246 + final message = map['error']?['message'] ?? '';
247 + throw ZanoWalletException('Error creating wallet file, $message ($code)');
248 + }
249 + if (map?['result'] == null) {
250 + throw ZanoWalletException('Error creating wallet file, empty response');
251 + }
252 + final result = CreateWalletResult.fromJson(map!['result'] as Map<String, dynamic>);
253 + info('create_wallet ${result.name} ${result.seed}');
254 + return result;
255 + }
256 +
257 + Future<CreateWalletResult> restoreWalletFromSeed(String path, String password, String seed, String? passphrase) async {
258 + info('restore_wallet path $path password ${_shorten(password)} seed ${_shorten(seed)}');
259 + final json = zano.PlainWallet_restore(seed, path, password, passphrase??'');
260 + _json('restore_wallet', json);
261 + final map = jsonDecode(json) as Map<String, dynamic>?;
262 + if (map?['error'] != null) {
263 + final code = map!['error']!['code'] ?? '';
264 + final message = map['error']!['message'] ?? '';
265 + if (code == Consts.errorWrongSeed) {
266 + throw RestoreFromSeedsException('Error restoring wallet, wrong seed');
267 + } else if (code == Consts.errorAlreadyExists) {
268 + throw RestoreFromSeedsException('Error restoring wallet, already exists');
269 + }
270 + throw RestoreFromSeedsException('Error restoring wallet, $message ($code)');
271 + }
272 + if (map?['result'] == null) {
273 + throw RestoreFromSeedsException('Error restoring wallet, empty response');
274 + }
275 + final result = CreateWalletResult.fromJson(map!['result'] as Map<String, dynamic>);
276 + info('restore_wallet ${result.name} ${result.wi.address}');
277 + return result;
278 + }
279 +
280 + Future<CreateWalletResult>loadWallet(String path, String password, [int attempt = 0]) async {
281 + info('load_wallet1 path $path password ${_shorten(password)}');
282 + final String json;
283 + try {
284 + json = zano.PlainWallet_open(path, password);
285 + } catch (e) {
286 + error('error in loadingWallet $e');
287 + rethrow;
288 + }
289 + info('load_wallet2: $json');
290 + final map = jsonDecode(json) as Map<String, dynamic>?;
291 + if (map?['error'] != null) {
292 + final code = map?['error']!['code'] ?? '';
293 + final message = map?['error']!['message'] ?? '';
294 + if (code == Consts.errorAlreadyExists && attempt <= _maxReopenAttempts) {
295 + // already connected to this wallet. closing and trying to reopen
296 + info('already connected. closing and reopen wallet (attempt $attempt)');
297 + closeWallet(attempt);
298 + await Future.delayed(const Duration(milliseconds: 500));
299 + return await loadWallet(path, password, attempt + 1);
300 + }
301 + throw ZanoWalletException('Error loading wallet, $message ($code)');
302 + }
303 + if (map?['result'] == null) {
304 + throw ZanoWalletException('Error loading wallet, empty response');
305 + }
306 + final result = CreateWalletResult.fromJson(map!['result'] as Map<String, dynamic>);
307 + info('load_wallet3 ${result.name} ${result.wi.address}');
308 + return result;
309 + }
310 +
311 + Future<TransferResult> transfer(List<Destination> destinations, BigInt fee, String comment) async {
312 + final params = TransferParams(
313 + destinations: destinations,
314 + fee: fee,
315 + mixin: _zanoMixinValue,
316 + paymentId: '',
317 + comment: comment,
318 + pushPayer: false,
319 + hideReceiver: true,
320 + );
321 + final json = await invokeMethod('transfer', params);
322 + _json('transfer', json);
323 + final map = jsonDecode(json);
324 + final resultMap = map as Map<String, dynamic>?;
325 + if (resultMap != null) {
326 + final transferResultMap = resultMap['result'] as Map<String, dynamic>?;
327 + if (transferResultMap != null) {
328 + final transferResult = TransferResult.fromJson(transferResultMap);
329 + info('transfer success hash ${transferResult.txHash}');
330 + return transferResult;
331 + } else {
332 + final errorCode = resultMap['error']?['code'];
333 + final code = errorCode is int ? errorCode.toString() : errorCode as String? ?? '';
334 + final message = resultMap['error']?['message'] as String? ?? '';
335 + error('transfer error $code $message');
336 + throw TransferException('Transfer error, $message ($code)');
337 + }
338 + }
339 + error('transfer error empty result');
340 + throw TransferException('Transfer error, empty result');
341 + }
342 +
343 + void _checkForErrors(Map<String, dynamic>? map) {
344 + if (map == null) {
345 + throw ZanoWalletException('Empty response');
346 + }
347 + final result = map['result'];
348 + if (result == null) {
349 + throw ZanoWalletException('Empty response');
350 + }
351 + if (result['error'] != null) {
352 + final code = result['error']!['code'] ?? '';
353 + final message = result['error']!['message'] ?? '';
354 + if (code == -1 && message == Consts.errorBusy) {
355 + throw ZanoWalletBusyException();
356 + }
357 + throw ZanoWalletException('Error, $message ($code)');
358 + }
359 + }
360 +
361 + /*Future<void> _writeLog(String method, String logMessage) async {
362 + final dir = await getDownloadsDirectory();
363 + final logFile = File('${dir!.path}/$method.txt');
364 + final date = DateTime.now();
365 + String twoDigits(int value) => value.toString().padLeft(2, '0');
366 + String removeCRandLF(String input) => input.replaceAll(RegExp('\r|\n'), '');
367 + await logFile.writeAsString('${twoDigits(date.hour)}:${twoDigits(date.minute)}:${twoDigits(date.second)} ${removeCRandLF(logMessage)}\n',
368 + mode: FileMode.append);
369 + }*/
370 +
371 + static void info(String s) => _logInfo ? debugPrint('[info] $s') : null;
372 + static void error(String s) => _logError ? debugPrint('[error] $s') : null;
373 + static void printWrapped(String text) => RegExp('.{1,800}').allMatches(text).map((m) => m.group(0)).forEach(print);
374 + static void _json(String methodName, String json) => _logJson ? printWrapped('$methodName $json') : null;
375 +
376 +}
377 +
378 +Future<String> callSyncMethod(String methodName, int hWallet, String params) async {
379 + final params_ = params.toNativeUtf8().address;
380 + final method_name_ = methodName.toNativeUtf8().address;
381 + final invokeResult = await Isolate.run(() async {
382 + final lib = zanoapi.ZanoC(DynamicLibrary.open(zano.libPath));
383 + final txid = lib.ZANO_PlainWallet_syncCall(
384 + Pointer.fromAddress(method_name_).cast(),
385 + hWallet,
386 + Pointer.fromAddress(params_).cast()
387 + );
388 + try {
389 + final strPtr = txid.cast<Utf8>();
390 + final str = strPtr.toDartString();
391 + lib.ZANO_free(strPtr.cast());
392 + return str;
393 + } catch (e) {
394 + return "";
395 + }
396 + });
397 + calloc.free(Pointer.fromAddress(method_name_));
398 + calloc.free(Pointer.fromAddress(params_));
399 + return invokeResult;
400 +}
401 +
402 +Map<String, dynamic> jsonDecode(String json) {
403 + try {
404 + return decodeJson(json.replaceAll("\\/", "/")) as Map<String, dynamic>;
405 + } catch (e) {
406 + return convert.jsonDecode(json) as Map<String, dynamic>;
407 + }
408 +}
409 +
410 +String jsonEncode(Object? object) {
411 + return convert.jsonEncode(object);
412 +}
413 +
414 +Future<String> _getWalletStatus(int hWallet) async {
415 + final jsonPtr = await Isolate.run(() async {
416 + final lib = zanoapi.ZanoC(DynamicLibrary.open(zano.libPath));
417 + final status = lib.ZANO_PlainWallet_getWalletStatus(
418 + hWallet,
419 + );
420 + return status.address;
421 + });
422 + String json = "";
423 + try {
424 + final strPtr = Pointer.fromAddress(jsonPtr).cast<Utf8>();
425 + final str = strPtr.toDartString();
426 + zano.ZANO_free(strPtr.cast());
427 + json = str;
428 + } catch (e) {
429 + json = "";
430 + }
431 + return json;
432 +}
433 +Future<String> _getWalletInfo(int hWallet) async {
434 + final jsonPtr = await Isolate.run(() async {
435 + final lib = zanoapi.ZanoC(DynamicLibrary.open(zano.libPath));
436 + final status = lib.ZANO_PlainWallet_getWalletInfo(
437 + hWallet,
438 + );
439 + return status.address;
440 + });
441 + String json = "";
442 + try {
443 + final strPtr = Pointer.fromAddress(jsonPtr).cast<Utf8>();
444 + final str = strPtr.toDartString();
445 + zano.ZANO_free(strPtr.cast());
446 + json = str;
447 + } catch (e) {
448 + json = "";
449 + }
450 + return json;
451 +}
452 +
453 +Future<String> _setupNode(int hWallet, String nodeUrl) async {
454 + final resp = await callSyncMethod("reset_connection_url", hWallet, nodeUrl);
455 + printV(resp);
456 + final resp2 = await callSyncMethod("run_wallet", hWallet, "");
457 + printV(resp2);
458 + return "OK";
459 +}
460 +
461 +Future<String> _closeWallet(int hWallet) async {
462 + final str = await Isolate.run(() async {
463 + return zano.PlainWallet_closeWallet(hWallet);
464 + });
465 + printV("Closing wallet: $str");
466 + return str;
467 +}
\ No newline at end of file
cw_zano/lib/zano_wallet_exceptions.dart new
+19
@@ -0,0 +1,19 @@
1 +class ZanoWalletException implements Exception {
2 + final String message;
3 +
4 + ZanoWalletException(this.message);
5 + @override
6 + String toString() => '${this.runtimeType} (message: $message)';
7 +}
8 +
9 +class RestoreFromSeedsException extends ZanoWalletException {
10 + RestoreFromSeedsException(String message) : super(message);
11 +}
12 +
13 +class TransferException extends ZanoWalletException {
14 + TransferException(String message): super(message);
15 +}
16 +
17 +class ZanoWalletBusyException extends ZanoWalletException {
18 + ZanoWalletBusyException(): super('');
19 +}
\ No newline at end of file
cw_zano/lib/zano_wallet_service.dart new
+123
@@ -0,0 +1,123 @@
1 +import 'dart:io';
2 +
3 +import 'package:collection/collection.dart';
4 +import 'package:cw_core/pathForWallet.dart';
5 +import 'package:cw_core/wallet_base.dart';
6 +import 'package:cw_core/wallet_credentials.dart';
7 +import 'package:cw_core/wallet_info.dart';
8 +import 'package:cw_core/wallet_service.dart';
9 +import 'package:cw_core/wallet_type.dart';
10 +import 'package:cw_zano/zano_wallet.dart';
11 +import 'package:cw_zano/zano_wallet_api.dart';
12 +import 'package:hive/hive.dart';
13 +import 'package:monero/zano.dart' as zano;
14 +
15 +class ZanoNewWalletCredentials extends WalletCredentials {
16 + ZanoNewWalletCredentials({required String name, String? password}) : super(name: name, password: password);
17 +}
18 +
19 +class ZanoRestoreWalletFromSeedCredentials extends WalletCredentials {
20 + ZanoRestoreWalletFromSeedCredentials({required String name, required String password, required String passphrase, required int height, required this.mnemonic})
21 + : super(name: name, password: password, passphrase: passphrase, height: height);
22 +
23 + final String mnemonic;
24 +}
25 +
26 +class ZanoRestoreWalletFromKeysCredentials extends WalletCredentials {
27 + ZanoRestoreWalletFromKeysCredentials(
28 + {required String name,
29 + required String password,
30 + required this.language,
31 + required this.address,
32 + required this.viewKey,
33 + required this.spendKey,
34 + required int height})
35 + : super(name: name, password: password, height: height);
36 +
37 + final String language;
38 + final String address;
39 + final String viewKey;
40 + final String spendKey;
41 +}
42 +
43 +class ZanoWalletService extends WalletService<ZanoNewWalletCredentials,
44 + ZanoRestoreWalletFromSeedCredentials, ZanoRestoreWalletFromKeysCredentials, ZanoNewWalletCredentials> {
45 + ZanoWalletService(this.walletInfoSource);
46 +
47 + final Box<WalletInfo> walletInfoSource;
48 +
49 + static bool walletFilesExist(String path) => !File(path).existsSync() && !File('$path.keys').existsSync();
50 +
51 + int hWallet = 0;
52 +
53 + @override
54 + WalletType getType() => WalletType.zano;
55 +
56 + @override
57 + Future<ZanoWallet> create(WalletCredentials credentials, {bool? isTestnet}) async {
58 + ZanoWalletApi.info('zanowallet service create isTestnet $isTestnet');
59 + return await ZanoWalletBase.create(credentials: credentials);
60 + }
61 +
62 + @override
63 + Future<bool> isWalletExit(String name) async {
64 + final path = await pathForWallet(name: name, type: getType());
65 + return zano.PlainWallet_isWalletExist(path);
66 + }
67 +
68 + @override
69 + Future<ZanoWallet> openWallet(String name, String password) async {
70 + final walletInfo = walletInfoSource.values.firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!;
71 + try {
72 + final wallet = await ZanoWalletBase.open(name: name, password: password, walletInfo: walletInfo);
73 + saveBackup(name);
74 + return wallet;
75 + } catch (e) {
76 + await restoreWalletFilesFromBackup(name);
77 + return await ZanoWalletBase.open(name: name, password: password, walletInfo: walletInfo);
78 + }
79 + }
80 +
81 + @override
82 + Future<void> remove(String wallet) async {
83 + final path = await pathForWalletDir(name: wallet, type: getType());
84 + final file = Directory(path);
85 + final isExist = file.existsSync();
86 +
87 + if (isExist) {
88 + await file.delete(recursive: true);
89 + }
90 +
91 + final walletInfo = walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(wallet, getType()));
92 + await walletInfoSource.delete(walletInfo.key);
93 + }
94 +
95 + @override
96 + Future<void> rename(String currentName, String password, String newName) async {
97 + final currentWalletInfo = walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(currentName, getType()));
98 + final currentWallet = ZanoWallet(currentWalletInfo, password);
99 +
100 + await currentWallet.renameWalletFiles(newName);
101 +
102 + final newWalletInfo = currentWalletInfo;
103 + newWalletInfo.id = WalletBase.idFor(newName, getType());
104 + newWalletInfo.name = newName;
105 +
106 + await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
107 + }
108 +
109 + @override
110 + Future<ZanoWallet> restoreFromKeys(ZanoRestoreWalletFromKeysCredentials credentials, {bool? isTestnet}) async {
111 + throw UnimplementedError();
112 + }
113 +
114 + @override
115 + Future<ZanoWallet> restoreFromSeed(ZanoRestoreWalletFromSeedCredentials credentials, {bool? isTestnet}) async {
116 + return ZanoWalletBase.restore(credentials: credentials);
117 + }
118 +
119 + @override
120 + Future<ZanoWallet> restoreFromHardwareWallet(ZanoNewWalletCredentials credentials) {
121 + throw UnimplementedError("Restoring a Zano wallet from a hardware wallet is not yet supported!");
122 + }
123 +}
cw_zano/pubspec.lock new
+827
@@ -0,0 +1,827 @@
1 +# Generated by pub
2 +# See https://dart.dev/tools/pub/glossary#lockfile
3 +packages:
4 + _fe_analyzer_shared:
5 + dependency: transitive
6 + description:
7 + name: _fe_analyzer_shared
8 + sha256: "4897882604d919befd350648c7f91926a9d5de99e67b455bf0917cc2362f4bb8"
9 + url: "https://pub.dev"
10 + source: hosted
11 + version: "47.0.0"
12 + analyzer:
13 + dependency: transitive
14 + description:
15 + name: analyzer
16 + sha256: "690e335554a8385bc9d787117d9eb52c0c03ee207a607e593de3c9d71b1cfe80"
17 + url: "https://pub.dev"
18 + source: hosted
19 + version: "4.7.0"
20 + args:
21 + dependency: transitive
22 + description:
23 + name: args
24 + sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6
25 + url: "https://pub.dev"
26 + source: hosted
27 + version: "2.6.0"
28 + asn1lib:
29 + dependency: transitive
30 + description:
31 + name: asn1lib
32 + sha256: "4bae5ae63e6d6dd17c4aac8086f3dec26c0236f6a0f03416c6c19d830c367cf5"
33 + url: "https://pub.dev"
34 + source: hosted
35 + version: "1.5.8"
36 + async:
37 + dependency: transitive
38 + description:
39 + name: async
40 + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
41 + url: "https://pub.dev"
42 + source: hosted
43 + version: "2.11.0"
44 + boolean_selector:
45 + dependency: transitive
46 + description:
47 + name: boolean_selector
48 + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
49 + url: "https://pub.dev"
50 + source: hosted
51 + version: "2.1.1"
52 + build:
53 + dependency: transitive
54 + description:
55 + name: build
56 + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0"
57 + url: "https://pub.dev"
58 + source: hosted
59 + version: "2.4.1"
60 + build_config:
61 + dependency: transitive
62 + description:
63 + name: build_config
64 + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1
65 + url: "https://pub.dev"
66 + source: hosted
67 + version: "1.1.1"
68 + build_daemon:
69 + dependency: transitive
70 + description:
71 + name: build_daemon
72 + sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9"
73 + url: "https://pub.dev"
74 + source: hosted
75 + version: "4.0.2"
76 + build_resolvers:
77 + dependency: "direct dev"
78 + description:
79 + name: build_resolvers
80 + sha256: "687cf90a3951affac1bd5f9ecb5e3e90b60487f3d9cdc359bb310f8876bb02a6"
81 + url: "https://pub.dev"
82 + source: hosted
83 + version: "2.0.10"
84 + build_runner:
85 + dependency: "direct dev"
86 + description:
87 + name: build_runner
88 + sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d"
89 + url: "https://pub.dev"
90 + source: hosted
91 + version: "2.4.13"
92 + build_runner_core:
93 + dependency: transitive
94 + description:
95 + name: build_runner_core
96 + sha256: "6d6ee4276b1c5f34f21fdf39425202712d2be82019983d52f351c94aafbc2c41"
97 + url: "https://pub.dev"
98 + source: hosted
99 + version: "7.2.10"
100 + built_collection:
101 + dependency: transitive
102 + description:
103 + name: built_collection
104 + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100"
105 + url: "https://pub.dev"
106 + source: hosted
107 + version: "5.1.1"
108 + built_value:
109 + dependency: transitive
110 + description:
111 + name: built_value
112 + sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb
113 + url: "https://pub.dev"
114 + source: hosted
115 + version: "8.9.2"
116 + cake_backup:
117 + dependency: transitive
118 + description:
119 + path: "."
120 + ref: main
121 + resolved-ref: "3aba867dcab6737f6707782f5db15d71f303db38"
122 + url: "https://github.com/cake-tech/cake_backup.git"
123 + source: git
124 + version: "1.0.0+1"
125 + characters:
126 + dependency: transitive
127 + description:
128 + name: characters
129 + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
130 + url: "https://pub.dev"
131 + source: hosted
132 + version: "1.3.0"
133 + checked_yaml:
134 + dependency: transitive
135 + description:
136 + name: checked_yaml
137 + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
138 + url: "https://pub.dev"
139 + source: hosted
140 + version: "2.0.3"
141 + clock:
142 + dependency: transitive
143 + description:
144 + name: clock
145 + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
146 + url: "https://pub.dev"
147 + source: hosted
148 + version: "1.1.1"
149 + code_builder:
150 + dependency: transitive
151 + description:
152 + name: code_builder
153 + sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e"
154 + url: "https://pub.dev"
155 + source: hosted
156 + version: "4.10.1"
157 + collection:
158 + dependency: transitive
159 + description:
160 + name: collection
161 + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
162 + url: "https://pub.dev"
163 + source: hosted
164 + version: "1.18.0"
165 + convert:
166 + dependency: transitive
167 + description:
168 + name: convert
169 + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
170 + url: "https://pub.dev"
171 + source: hosted
172 + version: "3.1.2"
173 + crypto:
174 + dependency: transitive
175 + description:
176 + name: crypto
177 + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
178 + url: "https://pub.dev"
179 + source: hosted
180 + version: "3.0.6"
181 + cryptography:
182 + dependency: transitive
183 + description:
184 + name: cryptography
185 + sha256: d146b76d33d94548cf035233fbc2f4338c1242fa119013bead807d033fc4ae05
186 + url: "https://pub.dev"
187 + source: hosted
188 + version: "2.7.0"
189 + cupertino_icons:
190 + dependency: transitive
191 + description:
192 + name: cupertino_icons
193 + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
194 + url: "https://pub.dev"
195 + source: hosted
196 + version: "1.0.8"
197 + cw_core:
198 + dependency: "direct main"
199 + description:
200 + path: "../cw_core"
201 + relative: true
202 + source: path
203 + version: "0.0.1"
204 + dart_style:
205 + dependency: transitive
206 + description:
207 + name: dart_style
208 + sha256: "7a03456c3490394c8e7665890333e91ae8a49be43542b616e414449ac358acd4"
209 + url: "https://pub.dev"
210 + source: hosted
211 + version: "2.2.4"
212 + decimal:
213 + dependency: "direct main"
214 + description:
215 + name: decimal
216 + sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21"
217 + url: "https://pub.dev"
218 + source: hosted
219 + version: "2.3.3"
220 + encrypt:
221 + dependency: transitive
222 + description:
223 + name: encrypt
224 + sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2"
225 + url: "https://pub.dev"
226 + source: hosted
227 + version: "5.0.3"
228 + fake_async:
229 + dependency: transitive
230 + description:
231 + name: fake_async
232 + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
233 + url: "https://pub.dev"
234 + source: hosted
235 + version: "1.3.1"
236 + ffi:
237 + dependency: "direct main"
238 + description:
239 + name: ffi
240 + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
241 + url: "https://pub.dev"
242 + source: hosted
243 + version: "2.1.3"
244 + file:
245 + dependency: transitive
246 + description:
247 + name: file
248 + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
249 + url: "https://pub.dev"
250 + source: hosted
251 + version: "7.0.1"
252 + fixnum:
253 + dependency: transitive
254 + description:
255 + name: fixnum
256 + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
257 + url: "https://pub.dev"
258 + source: hosted
259 + version: "1.1.1"
260 + flutter:
261 + dependency: "direct main"
262 + description: flutter
263 + source: sdk
264 + version: "0.0.0"
265 + flutter_mobx:
266 + dependency: "direct main"
267 + description:
268 + name: flutter_mobx
269 + sha256: "859fbf452fa9c2519d2700b125dd7fb14c508bbdd7fb65e26ca8ff6c92280e2e"
270 + url: "https://pub.dev"
271 + source: hosted
272 + version: "2.2.1+1"
273 + flutter_test:
274 + dependency: "direct dev"
275 + description: flutter
276 + source: sdk
277 + version: "0.0.0"
278 + flutter_web_plugins:
279 + dependency: transitive
280 + description: flutter
281 + source: sdk
282 + version: "0.0.0"
283 + fluttertoast:
284 + dependency: "direct main"
285 + description:
286 + name: fluttertoast
287 + sha256: "95f349437aeebe524ef7d6c9bde3e6b4772717cf46a0eb6a3ceaddc740b297cc"
288 + url: "https://pub.dev"
289 + source: hosted
290 + version: "8.2.8"
291 + frontend_server_client:
292 + dependency: transitive
293 + description:
294 + name: frontend_server_client
295 + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
296 + url: "https://pub.dev"
297 + source: hosted
298 + version: "4.0.0"
299 + glob:
300 + dependency: transitive
301 + description:
302 + name: glob
303 + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
304 + url: "https://pub.dev"
305 + source: hosted
306 + version: "2.1.2"
307 + graphs:
308 + dependency: transitive
309 + description:
310 + name: graphs
311 + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0"
312 + url: "https://pub.dev"
313 + source: hosted
314 + version: "2.3.2"
315 + hive:
316 + dependency: transitive
317 + description:
318 + name: hive
319 + sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941"
320 + url: "https://pub.dev"
321 + source: hosted
322 + version: "2.2.3"
323 + hive_generator:
324 + dependency: "direct dev"
325 + description:
326 + name: hive_generator
327 + sha256: "81fd20125cb2ce8fd23623d7744ffbaf653aae93706c9bd3bf7019ea0ace3938"
328 + url: "https://pub.dev"
329 + source: hosted
330 + version: "1.1.3"
331 + http:
332 + dependency: "direct main"
333 + description:
334 + name: http
335 + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
336 + url: "https://pub.dev"
337 + source: hosted
338 + version: "1.2.2"
339 + http_multi_server:
340 + dependency: transitive
341 + description:
342 + name: http_multi_server
343 + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b"
344 + url: "https://pub.dev"
345 + source: hosted
346 + version: "3.2.1"
347 + http_parser:
348 + dependency: transitive
349 + description:
350 + name: http_parser
351 + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
352 + url: "https://pub.dev"
353 + source: hosted
354 + version: "4.0.2"
355 + intl:
356 + dependency: "direct main"
357 + description:
358 + name: intl
359 + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
360 + url: "https://pub.dev"
361 + source: hosted
362 + version: "0.19.0"
363 + io:
364 + dependency: transitive
365 + description:
366 + name: io
367 + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
368 + url: "https://pub.dev"
369 + source: hosted
370 + version: "1.0.5"
371 + js:
372 + dependency: transitive
373 + description:
374 + name: js
375 + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
376 + url: "https://pub.dev"
377 + source: hosted
378 + version: "0.6.7"
379 + json_annotation:
380 + dependency: transitive
381 + description:
382 + name: json_annotation
383 + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
384 + url: "https://pub.dev"
385 + source: hosted
386 + version: "4.9.0"
387 + json_bigint:
388 + dependency: "direct main"
389 + description:
390 + name: json_bigint
391 + sha256: "9e613e731847ab2154d67160682adf104cbd9863741ec2f7abfcf6e77c70592f"
392 + url: "https://pub.dev"
393 + source: hosted
394 + version: "3.1.0"
395 + leak_tracker:
396 + dependency: transitive
397 + description:
398 + name: leak_tracker
399 + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
400 + url: "https://pub.dev"
401 + source: hosted
402 + version: "10.0.5"
403 + leak_tracker_flutter_testing:
404 + dependency: transitive
405 + description:
406 + name: leak_tracker_flutter_testing
407 + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
408 + url: "https://pub.dev"
409 + source: hosted
410 + version: "3.0.5"
411 + leak_tracker_testing:
412 + dependency: transitive
413 + description:
414 + name: leak_tracker_testing
415 + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
416 + url: "https://pub.dev"
417 + source: hosted
418 + version: "3.0.1"
419 + logging:
420 + dependency: transitive
421 + description:
422 + name: logging
423 + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
424 + url: "https://pub.dev"
425 + source: hosted
426 + version: "1.3.0"
427 + matcher:
428 + dependency: transitive
429 + description:
430 + name: matcher
431 + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
432 + url: "https://pub.dev"
433 + source: hosted
434 + version: "0.12.16+1"
435 + material_color_utilities:
436 + dependency: transitive
437 + description:
438 + name: material_color_utilities
439 + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
440 + url: "https://pub.dev"
441 + source: hosted
442 + version: "0.11.1"
443 + meta:
444 + dependency: transitive
445 + description:
446 + name: meta
447 + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
448 + url: "https://pub.dev"
449 + source: hosted
450 + version: "1.15.0"
451 + mime:
452 + dependency: transitive
453 + description:
454 + name: mime
455 + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
456 + url: "https://pub.dev"
457 + source: hosted
458 + version: "2.0.0"
459 + mobx:
460 + dependency: "direct main"
461 + description:
462 + name: mobx
463 + sha256: "1f01a429529ac55e5e80c0fcad62c60112fb91df3dec11a9113d71cf0c2e2c4c"
464 + url: "https://pub.dev"
465 + source: hosted
466 + version: "2.4.0"
467 + mobx_codegen:
468 + dependency: "direct dev"
469 + description:
470 + name: mobx_codegen
471 + sha256: d4beb9cea4b7b014321235f8fdc7c2193ee0fe1d1198e9da7403f8bc85c4407c
472 + url: "https://pub.dev"
473 + source: hosted
474 + version: "2.3.0"
475 + monero:
476 + dependency: "direct main"
477 + description:
478 + path: "impls/monero.dart"
479 + ref: "9526921acb774b523a2e1d9ba9a7b389acfc6b70"
480 + resolved-ref: "9526921acb774b523a2e1d9ba9a7b389acfc6b70"
481 + url: "https://github.com/mrcyjanek/monero_c"
482 + source: git
483 + version: "0.0.0"
484 + nested:
485 + dependency: transitive
486 + description:
487 + name: nested
488 + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
489 + url: "https://pub.dev"
490 + source: hosted
491 + version: "1.0.0"
492 + package_config:
493 + dependency: transitive
494 + description:
495 + name: package_config
496 + sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67"
497 + url: "https://pub.dev"
498 + source: hosted
499 + version: "2.1.1"
500 + path:
501 + dependency: transitive
502 + description:
503 + name: path
504 + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
505 + url: "https://pub.dev"
506 + source: hosted
507 + version: "1.9.0"
508 + path_provider:
509 + dependency: "direct main"
510 + description:
511 + name: path_provider
512 + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
513 + url: "https://pub.dev"
514 + source: hosted
515 + version: "2.1.5"
516 + path_provider_android:
517 + dependency: transitive
518 + description:
519 + name: path_provider_android
520 + sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2"
521 + url: "https://pub.dev"
522 + source: hosted
523 + version: "2.2.15"
524 + path_provider_foundation:
525 + dependency: transitive
526 + description:
527 + name: path_provider_foundation
528 + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942"
529 + url: "https://pub.dev"
530 + source: hosted
531 + version: "2.4.1"
532 + path_provider_linux:
533 + dependency: transitive
534 + description:
535 + name: path_provider_linux
536 + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
537 + url: "https://pub.dev"
538 + source: hosted
539 + version: "2.2.1"
540 + path_provider_platform_interface:
541 + dependency: transitive
542 + description:
543 + name: path_provider_platform_interface
544 + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
545 + url: "https://pub.dev"
546 + source: hosted
547 + version: "2.1.2"
548 + path_provider_windows:
549 + dependency: transitive
550 + description:
551 + name: path_provider_windows
552 + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
553 + url: "https://pub.dev"
554 + source: hosted
555 + version: "2.3.0"
556 + petitparser:
557 + dependency: transitive
558 + description:
559 + name: petitparser
560 + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
561 + url: "https://pub.dev"
562 + source: hosted
563 + version: "6.0.2"
564 + platform:
565 + dependency: transitive
566 + description:
567 + name: platform
568 + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
569 + url: "https://pub.dev"
570 + source: hosted
571 + version: "3.1.6"
572 + plugin_platform_interface:
573 + dependency: transitive
574 + description:
575 + name: plugin_platform_interface
576 + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
577 + url: "https://pub.dev"
578 + source: hosted
579 + version: "2.1.8"
580 + pointycastle:
581 + dependency: transitive
582 + description:
583 + name: pointycastle
584 + sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe"
585 + url: "https://pub.dev"
586 + source: hosted
587 + version: "3.9.1"
588 + pool:
589 + dependency: transitive
590 + description:
591 + name: pool
592 + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
593 + url: "https://pub.dev"
594 + source: hosted
595 + version: "1.5.1"
596 + provider:
597 + dependency: transitive
598 + description:
599 + name: provider
600 + sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
601 + url: "https://pub.dev"
602 + source: hosted
603 + version: "6.1.2"
604 + pub_semver:
605 + dependency: transitive
606 + description:
607 + name: pub_semver
608 + sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd"
609 + url: "https://pub.dev"
610 + source: hosted
611 + version: "2.1.5"
612 + pubspec_parse:
613 + dependency: transitive
614 + description:
615 + name: pubspec_parse
616 + sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8
617 + url: "https://pub.dev"
618 + source: hosted
619 + version: "1.3.0"
620 + rational:
621 + dependency: transitive
622 + description:
623 + name: rational
624 + sha256: cb808fb6f1a839e6fc5f7d8cb3b0a10e1db48b3be102de73938c627f0b636336
625 + url: "https://pub.dev"
626 + source: hosted
627 + version: "2.2.3"
628 + shelf:
629 + dependency: transitive
630 + description:
631 + name: shelf
632 + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
633 + url: "https://pub.dev"
634 + source: hosted
635 + version: "1.4.1"
636 + shelf_web_socket:
637 + dependency: transitive
638 + description:
639 + name: shelf_web_socket
640 + sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67
641 + url: "https://pub.dev"
642 + source: hosted
643 + version: "2.0.1"
644 + sky_engine:
645 + dependency: transitive
646 + description: flutter
647 + source: sdk
648 + version: "0.0.99"
649 + socks5_proxy:
650 + dependency: transitive
651 + description:
652 + name: socks5_proxy
653 + sha256: "616818a0ea1064a4823b53c9f7eaf8da64ed82dcd51ed71371c7e54751ed5053"
654 + url: "https://pub.dev"
655 + source: hosted
656 + version: "1.0.6"
657 + source_gen:
658 + dependency: transitive
659 + description:
660 + name: source_gen
661 + sha256: "2d79738b6bbf38a43920e2b8d189e9a3ce6cc201f4b8fc76be5e4fe377b1c38d"
662 + url: "https://pub.dev"
663 + source: hosted
664 + version: "1.2.6"
665 + source_helper:
666 + dependency: transitive
667 + description:
668 + name: source_helper
669 + sha256: "3b67aade1d52416149c633ba1bb36df44d97c6b51830c2198e934e3fca87ca1f"
670 + url: "https://pub.dev"
671 + source: hosted
672 + version: "1.3.3"
673 + source_span:
674 + dependency: transitive
675 + description:
676 + name: source_span
677 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
678 + url: "https://pub.dev"
679 + source: hosted
680 + version: "1.10.0"
681 + stack_trace:
682 + dependency: transitive
683 + description:
684 + name: stack_trace
685 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
686 + url: "https://pub.dev"
687 + source: hosted
688 + version: "1.11.1"
689 + stream_channel:
690 + dependency: transitive
691 + description:
692 + name: stream_channel
693 + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
694 + url: "https://pub.dev"
695 + source: hosted
696 + version: "2.1.2"
697 + stream_transform:
698 + dependency: transitive
699 + description:
700 + name: stream_transform
701 + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f"
702 + url: "https://pub.dev"
703 + source: hosted
704 + version: "2.1.0"
705 + string_scanner:
706 + dependency: transitive
707 + description:
708 + name: string_scanner
709 + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
710 + url: "https://pub.dev"
711 + source: hosted
712 + version: "1.2.0"
713 + term_glyph:
714 + dependency: transitive
715 + description:
716 + name: term_glyph
717 + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
718 + url: "https://pub.dev"
719 + source: hosted
720 + version: "1.2.1"
721 + test_api:
722 + dependency: transitive
723 + description:
724 + name: test_api
725 + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
726 + url: "https://pub.dev"
727 + source: hosted
728 + version: "0.7.2"
729 + timing:
730 + dependency: transitive
731 + description:
732 + name: timing
733 + sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
734 + url: "https://pub.dev"
735 + source: hosted
736 + version: "1.0.2"
737 + tuple:
738 + dependency: transitive
739 + description:
740 + name: tuple
741 + sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151
742 + url: "https://pub.dev"
743 + source: hosted
744 + version: "2.0.2"
745 + typed_data:
746 + dependency: transitive
747 + description:
748 + name: typed_data
749 + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
750 + url: "https://pub.dev"
751 + source: hosted
752 + version: "1.4.0"
753 + unorm_dart:
754 + dependency: transitive
755 + description:
756 + name: unorm_dart
757 + sha256: "23d8bf65605401a6a32cff99435fed66ef3dab3ddcad3454059165df46496a3b"
758 + url: "https://pub.dev"
759 + source: hosted
760 + version: "0.3.0"
761 + vector_math:
762 + dependency: transitive
763 + description:
764 + name: vector_math
765 + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
766 + url: "https://pub.dev"
767 + source: hosted
768 + version: "2.1.4"
769 + vm_service:
770 + dependency: transitive
771 + description:
772 + name: vm_service
773 + sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
774 + url: "https://pub.dev"
775 + source: hosted
776 + version: "14.2.4"
777 + watcher:
778 + dependency: "direct overridden"
779 + description:
780 + name: watcher
781 + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
782 + url: "https://pub.dev"
783 + source: hosted
784 + version: "1.1.0"
785 + web:
786 + dependency: transitive
787 + description:
788 + name: web
789 + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
790 + url: "https://pub.dev"
791 + source: hosted
792 + version: "1.1.0"
793 + web_socket:
794 + dependency: transitive
795 + description:
796 + name: web_socket
797 + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83"
798 + url: "https://pub.dev"
799 + source: hosted
800 + version: "0.1.6"
801 + web_socket_channel:
802 + dependency: transitive
803 + description:
804 + name: web_socket_channel
805 + sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f"
806 + url: "https://pub.dev"
807 + source: hosted
808 + version: "3.0.1"
809 + xdg_directories:
810 + dependency: transitive
811 + description:
812 + name: xdg_directories
813 + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
814 + url: "https://pub.dev"
815 + source: hosted
816 + version: "1.1.0"
817 + yaml:
818 + dependency: transitive
819 + description:
820 + name: yaml
821 + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
822 + url: "https://pub.dev"
823 + source: hosted
824 + version: "3.1.2"
825 +sdks:
826 + dart: ">=3.5.0 <4.0.0"
827 + flutter: ">=3.24.0"
cw_zano/pubspec.yaml new
+81
@@ -0,0 +1,81 @@
1 +name: cw_zano
2 +description: A new flutter plugin project.
3 +version: 0.0.1
4 +publish_to: none
5 +author: Cake Wallet
6 +homepage: https://cakewallet.com
7 +
8 +environment:
9 + sdk: ">=2.19.0 <3.0.0"
10 + flutter: ">=1.20.0"
11 +
12 +dependencies:
13 + flutter:
14 + sdk: flutter
15 + ffi: ^2.0.1
16 + http: ^1.1.0
17 + path_provider: ^2.0.11
18 + mobx: ^2.1.4
19 + flutter_mobx: ^2.0.6+1
20 + intl: ^0.19.0
21 + decimal: ^2.3.3
22 + cw_core:
23 + path: ../cw_core
24 + json_bigint: ^3.0.0
25 + fluttertoast: ^8.2.8
26 + monero:
27 + git:
28 + url: https://github.com/mrcyjanek/monero_c
29 + ref: 9526921acb774b523a2e1d9ba9a7b389acfc6b70 # monero_c hash
30 + path: impls/monero.dart
31 +dev_dependencies:
32 + flutter_test:
33 + sdk: flutter
34 + build_runner: ^2.4.7
35 + mobx_codegen: ^2.1.1
36 + build_resolvers: ^2.0.9
37 + hive_generator: ^1.1.3
38 +
39 +dependency_overrides:
40 + watcher: ^1.1.0
41 +
42 +
43 +# For information on the generic Dart part of this file, see the
44 +# following page: https://dart.dev/tools/pub/pubspec
45 +
46 +# The following section is specific to Flutter.
47 +flutter:
48 + # This section identifies this Flutter project as a plugin project.
49 + # The 'pluginClass' and Android 'package' identifiers should not ordinarily
50 + # be modified. They are used by the tooling to maintain consistency when
51 + # adding or updating assets for this project.
52 + # To add assets to your plugin package, add an assets section, like this:
53 + # assets:
54 + # - images/a_dot_burr.jpeg
55 + # - images/a_dot_ham.jpeg
56 + #
57 + # For details regarding assets in packages, see
58 + # https://flutter.dev/assets-and-images/#from-packages
59 + #
60 + # An image asset can refer to one or more resolution-specific "variants", see
61 + # https://flutter.dev/assets-and-images/#resolution-aware.
62 +
63 + # To add custom fonts to your plugin package, add a fonts section here,
64 + # in this "flutter" section. Each entry in this list should have a
65 + # "family" key with the font family name, and a "fonts" key with a
66 + # list giving the asset and other descriptors for the font. For
67 + # example:
68 + # fonts:
69 + # - family: Schyler
70 + # fonts:
71 + # - asset: fonts/Schyler-Regular.ttf
72 + # - asset: fonts/Schyler-Italic.ttf
73 + # style: italic
74 + # - family: Trajan Pro
75 + # fonts:
76 + # - asset: fonts/TrajanPro.ttf
77 + # - asset: fonts/TrajanPro_Bold.ttf
78 + # weight: 700
79 + #
80 + # For details regarding fonts in packages, see
81 + # https://flutter.dev/custom-fonts/#from-packages
ios/.gitignore
+1 -1
@@ -31,4 +31,4 @@ Runner/GeneratedPluginRegistrant.*
31 !default.pbxuser
32 !default.perspectivev3
33
34 -Mwebd.xcframework
\ No newline at end of file
34 +Mwebd.xcframework
ios/Runner.xcodeproj/project.pbxproj
+7
@@ -28,6 +28,7 @@
28 A3D5E17CC53DF13FA740DEFA /* RedeemSwap.swift in Resources */ = {isa = PBXBuildFile; fileRef = 9D2F2C9F2555316C95EE7EA3 /* RedeemSwap.swift */; settings = {ASSET_TAGS = (BreezSDK, ); }; };
29 B6C6E59403ACDE44724C12F4 /* ServiceConfig.swift in Resources */ = {isa = PBXBuildFile; fileRef = B3D5E78267F5F18D882FDC3B /* ServiceConfig.swift */; settings = {ASSET_TAGS = (BreezSDK, ); }; };
30 CE291CFE2C15DB9A00B9F709 /* WowneroWallet.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE291CFD2C15DB9A00B9F709 /* WowneroWallet.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
31 + CEA883682D43BE4500278CD3 /* ZanoWallet.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CEB6D62E2D43BB78002C6DBC /* ZanoWallet.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
32 CEAFE4A02C53926F009FF3AD /* libresolv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = C58D93382C00FAC6004BCF69 /* libresolv.tbd */; };
33 CFEFC24F82F78FE747DF1D22 /* LnurlPayInfo.swift in Resources */ = {isa = PBXBuildFile; fileRef = 58C22CBD8C22B9D6023D59F8 /* LnurlPayInfo.swift */; settings = {ASSET_TAGS = (BreezSDK, ); }; };
34 D0D7A0D4E13F31C4E02E235B /* ReceivePayment.swift in Resources */ = {isa = PBXBuildFile; fileRef = 91C524F800843E0A3F17E004 /* ReceivePayment.swift */; settings = {ASSET_TAGS = (BreezSDK, ); }; };
@@ -44,6 +45,7 @@
45 files = (
46 CE291CFE2C15DB9A00B9F709 /* WowneroWallet.framework in CopyFiles */,
47 0C50DFB92BF3CB56002B0EB3 /* MoneroWallet.framework in CopyFiles */,
48 + CEA883682D43BE4500278CD3 /* ZanoWallet.framework in CopyFiles */,
49 );
50 runOnlyForDeploymentPostprocessing = 0;
51 };
@@ -86,6 +88,7 @@
88 C58D93382C00FAC6004BCF69 /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = SDKROOT; };
89 CE291CFD2C15DB9A00B9F709 /* WowneroWallet.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = WowneroWallet.framework; sourceTree = "<group>"; };
90 CEAFE49D2C539250009FF3AD /* Mwebd.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Mwebd.xcframework; sourceTree = "<group>"; };
91 + CEB6D62E2D43BB78002C6DBC /* ZanoWallet.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = ZanoWallet.framework; sourceTree = "<group>"; };
92 D139E30AEB36740C21C00A9E /* 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>"; };
93 D7CD6B6020744E8FA471915D /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
94 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>"; };
@@ -161,6 +164,7 @@
164 children = (
165 CE291CFD2C15DB9A00B9F709 /* WowneroWallet.framework */,
166 0C50DFB82BF3CB56002B0EB3 /* MoneroWallet.framework */,
167 + CEB6D62E2D43BB78002C6DBC /* ZanoWallet.framework */,
168 0C44A7182518EF4A00B570ED /* CakeWallet */,
169 9740EEB11CF90186004384FC /* Flutter */,
170 97C146F01CF9000F007C117D /* Runner */,
@@ -495,6 +499,7 @@
499 FRAMEWORK_SEARCH_PATHS = (
500 "$(inherited)",
501 "$(PROJECT_DIR)/Flutter",
502 + "$(PROJECT_DIR)",
503 );
504 INFOPLIST_FILE = Runner/Info.plist;
505 IPHONEOS_DEPLOYMENT_TARGET = 12.0;
@@ -641,6 +646,7 @@
646 FRAMEWORK_SEARCH_PATHS = (
647 "$(inherited)",
648 "$(PROJECT_DIR)/Flutter",
649 + "$(PROJECT_DIR)",
650 );
651 INFOPLIST_FILE = Runner/Info.plist;
652 IPHONEOS_DEPLOYMENT_TARGET = 12.0;
@@ -679,6 +685,7 @@
685 FRAMEWORK_SEARCH_PATHS = (
686 "$(inherited)",
687 "$(PROJECT_DIR)/Flutter",
688 + "$(PROJECT_DIR)",
689 );
690 INFOPLIST_FILE = Runner/Info.plist;
691 IPHONEOS_DEPLOYMENT_TARGET = 12.0;
ios/ZanoWallet.framework/Info.plist
Binary files /dev/null and b/ios/ZanoWallet.framework/Info.plist differ
ios/ZanoWallet.framework/ZanoWallet
Binary files a/ios/ZanoWallet.framework/ZanoWallet and /dev/null differ
ios/monero_libwallet2_api_c.dylib
+1 -1
@@ -1 +1 @@
1 -../scripts/monero_c/release/monero/host-apple-ios_libwallet2_api_c.dylib
\ No newline at end of file
1 +../scripts/monero_c/release/monero/aarch64-apple-ios_libwallet2_api_c.dylib
\ No newline at end of file
ios/wownero_libwallet2_api_c.dylib
+1 -1
@@ -1 +1 @@
1 -../scripts/monero_c/release/wownero/host-apple-ios_libwallet2_api_c.dylib
\ No newline at end of file
1 +../scripts/monero_c/release/wownero/aarch64-apple-ios_libwallet2_api_c.dylib
\ No newline at end of file
ios/zano_libwallet2_api_c.dylib
+1 -1
@@ -1 +1 @@
1 -../scripts/monero_c/release/zano/host-apple-ios_libwallet2_api_c.dylib
\ No newline at end of file
1 +../scripts/monero_c/release/zano/aarch64-apple-ios_libwallet2_api_c.dylib
\ No newline at end of file
lib/core/address_validator.dart
+9 -2
@@ -2,9 +2,9 @@ import 'package:bitcoin_base/bitcoin_base.dart';
2 import 'package:cake_wallet/generated/i18n.dart';
3 import 'package:cake_wallet/core/validator.dart';
4 import 'package:cake_wallet/solana/solana.dart';
5 +import 'package:cake_wallet/zano/zano.dart';
6 import 'package:cw_core/crypto_currency.dart';
7 import 'package:cw_core/erc20_token.dart';
7 -
8 const BEFORE_REGEX = '(^|\\s)';
9 const AFTER_REGEX = '(\$|\\s)';
10
@@ -19,7 +19,9 @@ class AddressValidator extends TextValidator {
19 ? BitcoinNetwork.mainnet
20 : LitecoinNetwork.mainnet,
21 )
22 - : null,
22 + : type == CryptoCurrency.zano
23 + ? zano?.validateAddress
24 + : null,
25 pattern: getPattern(type),
26 length: getLength(type));
27
@@ -132,6 +134,8 @@ class AddressValidator extends TextValidator {
134 pattern = 'D([1-9a-km-zA-HJ-NP-Z]){33}';
135 case CryptoCurrency.btcln:
136 pattern = '(lnbc|LNBC)([0-9]{1,}[a-zA-Z0-9]+)';
137 + case CryptoCurrency.zano:
138 + pattern = r'([1-9A-HJ-NP-Za-km-z]{90,200})|(@[\w\d-.]+)';
139 default:
140 return '';
141 }
@@ -271,6 +275,7 @@ class AddressValidator extends TextValidator {
275 return [64];
276 case CryptoCurrency.btcln:
277 case CryptoCurrency.kaspa:
278 + case CryptoCurrency.zano:
279 default:
280 return null;
281 }
@@ -310,6 +315,8 @@ class AddressValidator extends TextValidator {
315 pattern = '[1-9A-HJ-NP-Za-km-z]+';
316 case CryptoCurrency.trx:
317 pattern = '(T|t)[1-9A-HJ-NP-Za-km-z]{33}';
318 + case CryptoCurrency.zano:
319 + pattern = '([1-9A-HJ-NP-Za-km-z]{90,200})|(@[\w\d-.]+)';
320 default:
321 if (type.tag == CryptoCurrency.eth.title) {
322 pattern = '0x[0-9a-zA-Z]{42}';
lib/core/amount_validator.dart
+2
@@ -76,6 +76,8 @@ class DecimalAmountValidator extends TextValidator {
76 return '^([0-9]+([.\,][0-9]{1,12})?|[.\,][0-9]{1,12})\$';
77 case CryptoCurrency.btc:
78 return '^([0-9]+([.\,][0-9]{1,8})?|[.\,][0-9]{1,8})\$';
79 + case CryptoCurrency.zano:
80 + return '^([0-9]+([.\,][0-9]{1,12})?|[.\,][0-9]{1,18})\$';
81 default:
82 return '^([0-9]+([.\,][0-9]{1,12})?|[.\,][0-9]{1,12})\$';
83 }
lib/core/seed_validator.dart
+5 -1
@@ -9,6 +9,7 @@ import 'package:cake_wallet/polygon/polygon.dart';
9 import 'package:cake_wallet/solana/solana.dart';
10 import 'package:cake_wallet/tron/tron.dart';
11 import 'package:cake_wallet/wownero/wownero.dart';
12 +import 'package:cake_wallet/zano/zano.dart';
13 import 'package:cake_wallet/utils/language_list.dart';
14 import 'package:cw_core/wallet_type.dart';
15
@@ -21,7 +22,8 @@ class SeedValidator extends Validator<MnemonicItem> {
22 final String language;
23 final List<String> _words;
24
24 - static List<String> getWordList({required WalletType type, required String language}) {
25 + static List<String> getWordList(
26 + {required WalletType type, required String language}) {
27 switch (type) {
28 case WalletType.bitcoin:
29 return getBitcoinWordList(language);
@@ -46,6 +48,8 @@ class SeedValidator extends Validator<MnemonicItem> {
48 return tron!.getTronWordList(language);
49 case WalletType.wownero:
50 return wownero!.getWowneroWordList(language);
51 + case WalletType.zano:
52 + return zano!.getWordList(language);
53 case WalletType.none:
54 return [];
55 }
lib/core/wallet_creation_service.dart
+1
@@ -89,6 +89,7 @@ class WalletCreationService {
89 case WalletType.haven:
90 case WalletType.nano:
91 case WalletType.banano:
92 + case WalletType.zano:
93 return false;
94 }
95 }
lib/di.dart
+4 -1
@@ -245,6 +245,7 @@ import 'package:cake_wallet/view_model/wallet_seed_view_model.dart';
245 import 'package:cake_wallet/view_model/wallet_unlock_loadable_view_model.dart';
246 import 'package:cake_wallet/view_model/wallet_unlock_verifiable_view_model.dart';
247 import 'package:cake_wallet/wownero/wownero.dart';
248 +import 'package:cake_wallet/zano/zano.dart';
249 import 'package:cw_core/crypto_currency.dart';
250 import 'package:cw_core/wallet_info.dart';
251 import 'package:cw_core/wallet_type.dart';
@@ -1087,6 +1088,8 @@ Future<void> setup({
1088 return tron!.createTronWalletService(_walletInfoSource, SettingsStoreBase.walletPasswordDirectInput);
1089 case WalletType.wownero:
1090 return wownero!.createWowneroWalletService(_walletInfoSource, _unspentCoinsInfoSource);
1091 + case WalletType.zano:
1092 + return zano!.createZanoWalletService(_walletInfoSource);
1093 case WalletType.none:
1094 throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService');
1095 }
@@ -1415,4 +1418,4 @@ Future<void> setup({
1418 getIt.registerFactory(() => SeedVerificationPage(getIt.get<WalletSeedViewModel>()));
1419
1420 _isSetupFinished = true;
1418 -}
1421 +}
\ No newline at end of file
lib/entities/default_settings_migration.dart
+60 -5
@@ -44,6 +44,7 @@ const solanaDefaultNodeUri = 'solana-mainnet.core.chainstack.com';
44 const tronDefaultNodeUri = 'api.trongrid.io';
45 const newCakeWalletBitcoinUri = 'btc-electrum.cakewallet.com:50002';
46 const wowneroDefaultNodeUri = 'node3.monerodevs.org:34568';
47 +const zanoDefaultNodeUri = '37.27.100.59:10500';
48 const moneroWorldNodeUri = '.moneroworld.com';
49
50 Future<void> defaultSettingsMigration(
@@ -260,7 +261,11 @@ Future<void> defaultSettingsMigration(
261 await removeMoneroWorld(sharedPreferences: sharedPreferences, nodes: nodes);
262 break;
263 case 41:
263 - _deselectExchangeProvider(sharedPreferences, "Quantex");
264 + _changeExchangeProviderAvailability(
265 + sharedPreferences,
266 + providerName: "Quantex",
267 + enabled: false,
268 + );
269 await _addSethNode(nodes, sharedPreferences);
270 await updateTronNodesWithNowNodes(sharedPreferences: sharedPreferences, nodes: nodes);
271 break;
@@ -269,8 +274,16 @@ Future<void> defaultSettingsMigration(
274 break;
275 case 43:
276 _fixNodesUseSSLFlag(nodes);
272 - _deselectExchangeProvider(sharedPreferences, "THORChain");
273 - _deselectExchangeProvider(sharedPreferences, "SimpleSwap");
277 + _changeExchangeProviderAvailability(
278 + sharedPreferences,
279 + providerName: "THORChain",
280 + enabled: false,
281 + );
282 + _changeExchangeProviderAvailability(
283 + sharedPreferences,
284 + providerName: "SimpleSwap",
285 + enabled: false,
286 + );
287 break;
288 case 44:
289 _fixNodesUseSSLFlag(nodes);
@@ -311,6 +324,7 @@ Future<void> defaultSettingsMigration(
324 type: WalletType.ethereum,
325 useSSL: true,
326 );
327 +
328 _changeDefaultNode(
329 nodes: nodes,
330 sharedPreferences: sharedPreferences,
@@ -373,6 +387,15 @@ Future<void> defaultSettingsMigration(
387 useSSL: true,
388 );
389 break;
390 + case 47:
391 + await addZanoNodeList(nodes: nodes);
392 + await changeZanoCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
393 + _changeExchangeProviderAvailability(
394 + sharedPreferences,
395 + providerName: "SimpleSwap",
396 + enabled: true,
397 + );
398 + break;
399 default:
400 break;
401 }
@@ -469,12 +492,13 @@ Future<void> updateWalletTypeNodesWithNewNode({
492 );
493 }
494
472 -void _deselectExchangeProvider(SharedPreferences sharedPreferences, String providerName) {
495 +void _changeExchangeProviderAvailability(SharedPreferences sharedPreferences,
496 + {required String providerName, required bool enabled}) {
497 final Map<String, dynamic> exchangeProvidersSelection =
498 json.decode(sharedPreferences.getString(PreferencesKey.exchangeProvidersSelection) ?? "{}")
499 as Map<String, dynamic>;
500
477 - exchangeProvidersSelection[providerName] = false;
501 + exchangeProvidersSelection[providerName] = enabled;
502
503 sharedPreferences.setString(
504 PreferencesKey.exchangeProvidersSelection,
@@ -703,6 +727,12 @@ Node? getBitcoinCashDefaultElectrumServer({required Box<Node> nodes}) {
727 nodes.values.firstWhereOrNull((node) => node.type == WalletType.bitcoinCash);
728 }
729
730 +Node? getZanoDefaultNode({required Box<Node> nodes}) {
731 + return nodes.values.firstWhereOrNull(
732 + (Node node) => node.uriRaw == zanoDefaultNodeUri)
733 + ?? nodes.values.firstWhereOrNull((node) => node.type == WalletType.zano);
734 +}
735 +
736 Node getMoneroDefaultNode({required Box<Node> nodes}) {
737 var nodeUri = newCakeWalletMoneroUri;
738
@@ -1199,6 +1229,7 @@ Future<void> checkCurrentNodes(
1229 final currentSolanaNodeId = sharedPreferences.getInt(PreferencesKey.currentSolanaNodeIdKey);
1230 final currentTronNodeId = sharedPreferences.getInt(PreferencesKey.currentTronNodeIdKey);
1231 final currentWowneroNodeId = sharedPreferences.getInt(PreferencesKey.currentWowneroNodeIdKey);
1232 + final currentZanoNodeId = sharedPreferences.getInt(PreferencesKey.currentZanoNodeIdKey);
1233 final currentMoneroNode =
1234 nodeSource.values.firstWhereOrNull((node) => node.key == currentMoneroNodeId);
1235 final currentBitcoinElectrumServer =
@@ -1223,6 +1254,8 @@ Future<void> checkCurrentNodes(
1254 nodeSource.values.firstWhereOrNull((node) => node.key == currentTronNodeId);
1255 final currentWowneroNodeServer =
1256 nodeSource.values.firstWhereOrNull((node) => node.key == currentWowneroNodeId);
1257 + final currentZanoNode = nodeSource.values.firstWhereOrNull((node) => node.key == currentZanoNodeId);
1258 +
1259 if (currentMoneroNode == null) {
1260 final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
1261 await nodeSource.add(newCakeWalletNode);
@@ -1306,6 +1339,12 @@ Future<void> checkCurrentNodes(
1339 await nodeSource.add(node);
1340 await sharedPreferences.setInt(PreferencesKey.currentWowneroNodeIdKey, node.key as int);
1341 }
1342 +
1343 + if (currentZanoNode == null) {
1344 + final node = Node(uri: zanoDefaultNodeUri, type: WalletType.zano);
1345 + await nodeSource.add(node);
1346 + await sharedPreferences.setInt(PreferencesKey.currentZanoNodeIdKey, node.key as int);
1347 + }
1348 }
1349
1350 Future<void> resetBitcoinElectrumServer(
@@ -1381,6 +1420,15 @@ Future<void> addWowneroNodeList({required Box<Node> nodes}) async {
1420 }
1421 }
1422
1423 +Future<void> addZanoNodeList({required Box<Node> nodes}) async {
1424 + final nodeList = await loadDefaultZanoNodes();
1425 + for (var node in nodeList) {
1426 + if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
1427 + await nodes.add(node);
1428 + }
1429 + }
1430 +}
1431 +
1432 Future<void> changeWowneroCurrentNodeToDefault(
1433 {required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
1434 final node = getWowneroDefaultNode(nodes: nodes);
@@ -1389,6 +1437,13 @@ Future<void> changeWowneroCurrentNodeToDefault(
1437 await sharedPreferences.setInt(PreferencesKey.currentWowneroNodeIdKey, nodeId);
1438 }
1439
1440 +Future<void> changeZanoCurrentNodeToDefault(
1441 + {required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
1442 + final node = getZanoDefaultNode(nodes: nodes);
1443 + final nodeId = node?.key as int? ?? 0;
1444 + await sharedPreferences.setInt(PreferencesKey.currentZanoNodeIdKey, nodeId);
1445 +}
1446 +
1447 Future<void> addNanoNodeList({required Box<Node> nodes}) async {
1448 final nodeList = await loadDefaultNanoNodes();
1449 for (var node in nodeList) {
lib/entities/ens_record.dart
+1 -1
@@ -20,7 +20,7 @@ class EnsRecord {
20 }
21
22 if (_client == null) {
23 - _client = Web3Client("https://ethereum.publicnode.com", Client());
23 + _client = Web3Client("https://ethereum-rpc.publicnode.com", Client());
24 }
25
26 try {
lib/entities/node_list.dart
+19 -1
@@ -200,6 +200,23 @@ Future<List<Node>> loadDefaultWowneroNodes() async {
200 return nodes;
201 }
202
203 +Future<List<Node>> loadDefaultZanoNodes() async {
204 + final nodesRaw = await rootBundle.loadString('assets/zano_node_list.yml');
205 + final loadedNodes = loadYaml(nodesRaw) as YamlList;
206 + final nodes = <Node>[];
207 +
208 + for (final raw in loadedNodes) {
209 + if (raw is Map) {
210 + final node = Node.fromMap(Map<String, Object>.from(raw));
211 +
212 + node.type = WalletType.zano;
213 + nodes.add(node);
214 + }
215 + }
216 +
217 + return nodes;
218 +}
219 +
220 Future<void> resetToDefault(Box<Node> nodeSource) async {
221 final moneroNodes = await loadDefaultNodes();
222 final bitcoinElectrumServerList = await loadBitcoinElectrumServerList();
@@ -211,6 +228,7 @@ Future<void> resetToDefault(Box<Node> nodeSource) async {
228 final polygonNodes = await loadDefaultPolygonNodes();
229 final solanaNodes = await loadDefaultSolanaNodes();
230 final tronNodes = await loadDefaultTronNodes();
231 + final zanoNodes = await loadDefaultZanoNodes();
232
233 final nodes = moneroNodes +
234 bitcoinElectrumServerList +
@@ -220,7 +238,7 @@ Future<void> resetToDefault(Box<Node> nodeSource) async {
238 bitcoinCashElectrumServerList +
239 nanoNodes +
240 polygonNodes +
223 - solanaNodes + tronNodes;
241 + solanaNodes + tronNodes + zanoNodes;
242
243 await nodeSource.clear();
244 await nodeSource.addAll(nodes);
lib/entities/parse_address_from_domain.dart
+11
@@ -6,6 +6,7 @@ import 'package:cake_wallet/entities/parsed_address.dart';
6 import 'package:cake_wallet/entities/unstoppable_domain_address.dart';
7 import 'package:cake_wallet/entities/emoji_string_extension.dart';
8 import 'package:cake_wallet/entities/wellknown_record.dart';
9 +import 'package:cake_wallet/entities/zano_alias.dart';
10 import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart';
11 import 'package:cake_wallet/mastodon/mastodon_api.dart';
12 import 'package:cake_wallet/nostr/nostr_api.dart';
@@ -137,6 +138,16 @@ class AddressResolver {
138 try {
139 // twitter handle example: @username
140 if (text.startsWith('@') && !text.substring(1).contains('@')) {
141 + if (currency == CryptoCurrency.zano && settingsStore.lookupsZanoAlias) {
142 + final formattedName = text.substring(1);
143 + final zanoAddress = await ZanoAlias.fetchZanoAliasAddress(formattedName);
144 + if (zanoAddress != null) {
145 + return ParsedAddress.zanoAddress(
146 + address: zanoAddress,
147 + name: text,
148 + );
149 + }
150 + }
151 if (settingsStore.lookupsTwitter) {
152 final formattedName = text.substring(1);
153 final twitterUser = await TwitterApi.lookupUserByName(userName: formattedName);
lib/entities/parsed_address.dart
+10 -1
@@ -13,7 +13,8 @@ enum ParseFrom {
13 mastodon,
14 nostr,
15 thorChain,
16 - wellKnown
16 + wellKnown,
17 + zanoAlias,
18 }
19
20 class ParsedAddress {
@@ -143,6 +144,14 @@ class ParsedAddress {
144 );
145 }
146
147 + factory ParsedAddress.zanoAddress({required String address, required String name}) {
148 + return ParsedAddress(
149 + addresses: [address],
150 + name: name,
151 + parseFrom: ParseFrom.zanoAlias,
152 + );
153 + }
154 +
155 factory ParsedAddress.fetchWellKnownAddress({required String address, required String name}) {
156 return ParsedAddress(
157 addresses: [address],
lib/entities/preferences_key.dart
+3
@@ -5,6 +5,7 @@ class PreferencesKey {
5 static const currentBitcoinElectrumSererIdKey = 'current_node_id_btc';
6 static const currentLitecoinElectrumSererIdKey = 'current_node_id_ltc';
7 static const currentHavenNodeIdKey = 'current_node_id_xhv';
8 + static const currentZanoNodeIdKey = 'current_node_id_zano';
9 static const currentEthereumNodeIdKey = 'current_node_id_eth';
10 static const currentPolygonNodeIdKey = 'current_node_id_matic';
11 static const currentNanoNodeIdKey = 'current_node_id_nano';
@@ -45,6 +46,7 @@ class PreferencesKey {
46 static const ethereumTransactionPriority = 'current_fee_priority_ethereum';
47 static const polygonTransactionPriority = 'current_fee_priority_polygon';
48 static const bitcoinCashTransactionPriority = 'current_fee_priority_bitcoin_cash';
49 + static const zanoTransactionPriority = 'current_fee_priority_zano';
50 static const wowneroTransactionPriority = 'current_fee_priority_wownero';
51 static const customBitcoinFeeRate = 'custom_electrum_fee_rate';
52 static const silentPaymentsCardDisplay = 'silentPaymentsCardDisplay';
@@ -71,6 +73,7 @@ class PreferencesKey {
73 static const defaultNanoRep = 'default_nano_representative';
74 static const defaultBananoRep = 'default_banano_representative';
75 static const lookupsTwitter = 'looks_up_twitter';
76 + static const lookupsZanoAlias = 'looks_up_zano_alias';
77 static const lookupsMastodon = 'looks_up_mastodon';
78 static const lookupsYatService = 'looks_up_yat';
79 static const lookupsUnstoppableDomains = 'looks_up_unstoppable_domain';
lib/entities/priority_for_wallet_type.dart
+3
@@ -5,6 +5,7 @@ import 'package:cake_wallet/haven/haven.dart';
5 import 'package:cake_wallet/monero/monero.dart';
6 import 'package:cake_wallet/polygon/polygon.dart';
7 import 'package:cake_wallet/wownero/wownero.dart';
8 +import 'package:cake_wallet/zano/zano.dart';
9 import 'package:cw_core/transaction_priority.dart';
10 import 'package:cw_core/wallet_type.dart';
11
@@ -32,6 +33,8 @@ List<TransactionPriority> priorityForWalletType(WalletType type) {
33 case WalletType.solana:
34 case WalletType.tron:
35 return [];
36 + case WalletType.zano:
37 + return zano!.getTransactionPriorities();
38 default:
39 return [];
40 }
lib/entities/provider_types.dart
+2
@@ -76,6 +76,7 @@ class ProvidersHelper {
76 ];
77 case WalletType.none:
78 case WalletType.haven:
79 + case WalletType.zano:
80 return [];
81 }
82 }
@@ -109,6 +110,7 @@ class ProvidersHelper {
110 case WalletType.none:
111 case WalletType.haven:
112 case WalletType.wownero:
113 + case WalletType.zano:
114 return [];
115 }
116 }
lib/entities/zano_alias.dart new
+28
@@ -0,0 +1,28 @@
1 +import 'dart:convert';
2 +
3 +import 'package:cw_core/utils/print_verbose.dart';
4 +import 'package:http/http.dart' as http;
5 +
6 +class ZanoAlias {
7 + static Future<String?> fetchZanoAliasAddress(String alias) async {
8 + try {
9 + final uri = Uri.parse("http://37.27.100.59:10500/json_rpc");
10 + final response = await http.post(
11 + uri,
12 + body: json.encode({
13 + "id": 0,
14 + "jsonrpc": "2.0",
15 + "method": "get_alias_details",
16 + "params": {"alias": alias}
17 + }),
18 + );
19 + final jsonParsed = json.decode(response.body) as Map<String, dynamic>;
20 +
21 + return jsonParsed['result']['alias_details']['address'] as String?;
22 + } catch (e) {
23 + printV('Zano Alias error: ${e.toString()}');
24 + }
25 +
26 + return null;
27 + }
28 +}
lib/main.dart
+1 -1
@@ -215,7 +215,7 @@ Future<void> initializeAppConfigs() async {
215 secureStorage: secureStorage,
216 anonpayInvoiceInfo: anonpayInvoiceInfo,
217 havenSeedStore: havenSeedStore,
218 - initialMigrationVersion: 46,
218 + initialMigrationVersion: 47,
219 );
220 }
221
lib/monero/cw_monero.dart
+4 -2
@@ -242,10 +242,11 @@ class CWMonero extends Monero {
242 WalletCredentials createMoneroRestoreWalletFromSeedCredentials(
243 {required String name,
244 required String password,
245 + required String passphrase,
246 required int height,
247 required String mnemonic}) =>
248 MoneroRestoreWalletFromSeedCredentials(
248 - name: name, password: password, height: height, mnemonic: mnemonic);
249 + name: name, password: password, passphrase: passphrase, height: height, mnemonic: mnemonic);
250
251 @override
252 WalletCredentials createMoneroNewWalletCredentials({
@@ -265,7 +266,8 @@ class CWMonero extends Monero {
266 'privateSpendKey': keys.privateSpendKey,
267 'privateViewKey': keys.privateViewKey,
268 'publicSpendKey': keys.publicSpendKey,
268 - 'publicViewKey': keys.publicViewKey
269 + 'publicViewKey': keys.publicViewKey,
270 + 'passphrase': keys.passphrase
271 };
272 }
273
lib/reactions/bip39_wallet_utils.dart
+1
@@ -15,6 +15,7 @@ bool isBIP39Wallet(WalletType walletType) {
15 case WalletType.monero:
16 case WalletType.wownero:
17 case WalletType.haven:
18 + case WalletType.zano:
19 case WalletType.none:
20 return false;
21 }
lib/reactions/on_current_node_change.dart
-1
@@ -1,6 +1,5 @@
1 import 'package:cw_core/utils/print_verbose.dart';
2 import 'package:mobx/mobx.dart';
3 -import 'package:cw_core/node.dart';
3 import 'package:cake_wallet/store/app_store.dart';
4
5 ReactionDisposer? _onCurrentNodeChangeReaction;
lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart
+5 -2
@@ -46,6 +46,7 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
46 final solanaIcon = Image.asset('assets/images/sol_icon.png', height: 24, width: 24);
47 final tronIcon = Image.asset('assets/images/trx_icon.png', height: 24, width: 24);
48 final wowneroIcon = Image.asset('assets/images/wownero_icon.png', height: 24, width: 24);
49 + final zanoIcon = Image.asset('assets/images/zano_icon.png', height: 24, width: 24);
50 final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
51
52 Image _newWalletImage(BuildContext context) => Image.asset(
@@ -158,6 +159,8 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
159 return bitcoinIcon;
160 case WalletType.monero:
161 return moneroIcon;
162 + case WalletType.wownero:
163 + return wowneroIcon;
164 case WalletType.litecoin:
165 return litecoinIcon;
166 case WalletType.haven:
@@ -176,8 +179,8 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
179 return solanaIcon;
180 case WalletType.tron:
181 return tronIcon;
179 - case WalletType.wownero:
180 - return wowneroIcon;
182 + case WalletType.zano:
183 + return zanoIcon;
184 default:
185 return nonWalletTypeIcon;
186 }
lib/src/screens/dashboard/edit_token_page.dart
+22 -5
@@ -2,6 +2,7 @@ import 'package:cake_wallet/core/address_validator.dart';
2 import 'package:cake_wallet/generated/i18n.dart';
3 import 'package:cake_wallet/src/screens/base_page.dart';
4 import 'package:cake_wallet/src/widgets/address_text_field.dart';
5 +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
6 import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
7 import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
8 import 'package:cake_wallet/src/widgets/checkbox_widget.dart';
@@ -12,6 +13,7 @@ import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
13 import 'package:cake_wallet/utils/show_pop_up.dart';
14 import 'package:cake_wallet/view_model/dashboard/home_settings_view_model.dart';
15 import 'package:cw_core/crypto_currency.dart';
16 +import 'package:cw_core/wallet_type.dart';
17 import 'package:flutter/material.dart';
18 import 'package:flutter/services.dart';
19 import 'package:flutter_mobx/flutter_mobx.dart';
@@ -242,7 +244,21 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
244 },
245 );
246 } else {
245 - await actionCall();
247 + try {
248 + await actionCall();
249 + } catch (e) {
250 + showPopUp<void>(
251 + context: context,
252 + builder: (dialogContext) {
253 + return AlertWithOneAction(
254 + alertTitle: "Unable to add token",
255 + alertContent: "$e",
256 + buttonText: S.of(context).ok,
257 + buttonAction: () => Navigator.of(context).pop(),
258 + );
259 + },
260 + );
261 + }
262 if (mounted) {
263 Navigator.pop(context);
264 }
@@ -269,11 +285,12 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
285 final token = await widget.homeSettingsViewModel.getToken(_contractAddressController.text);
286
287 if (token != null) {
272 - if (_tokenNameController.text.isEmpty) _tokenNameController.text = token.name;
273 - if (_tokenSymbolController.text.isEmpty) _tokenSymbolController.text = token.title;
288 + final isZano = widget.homeSettingsViewModel.walletType == WalletType.zano;
289 + if (_tokenNameController.text.isEmpty || isZano) _tokenNameController.text = token.name;
290 + if (_tokenSymbolController.text.isEmpty || isZano) _tokenSymbolController.text = token.title;
291 if (_tokenIconPathController.text.isEmpty)
292 _tokenIconPathController.text = token.iconPath ?? '';
276 - if (_tokenDecimalController.text.isEmpty)
293 + if (_tokenDecimalController.text.isEmpty || isZano)
294 _tokenDecimalController.text = token.decimals.toString();
295 }
296 }
@@ -305,7 +322,7 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
322 placeholder: S.of(context).token_contract_address,
323 options: [AddressTextFieldOption.paste],
324 buttonColor: Theme.of(context).hintColor,
308 - validator: AddressValidator(type: widget.homeSettingsViewModel.nativeToken),
325 + validator: widget.homeSettingsViewModel.walletType == WalletType.zano ? null : AddressValidator(type: widget.homeSettingsViewModel.nativeToken).call,
326 onPushPasteButton: (_) {
327 _pasteText();
328 },
lib/src/screens/dashboard/widgets/menu_widget.dart
+5 -1
@@ -37,7 +37,8 @@ class MenuWidgetState extends State<MenuWidget> {
37 this.polygonIcon = Image.asset('assets/images/matic_icon.png'),
38 this.solanaIcon = Image.asset('assets/images/sol_icon.png'),
39 this.tronIcon = Image.asset('assets/images/trx_icon.png'),
40 - this.wowneroIcon = Image.asset('assets/images/wownero_icon.png');
40 + this.wowneroIcon = Image.asset('assets/images/wownero_icon.png'),
41 + this.zanoIcon = Image.asset('assets/images/zano_icon.png');
42
43 final largeScreen = 731;
44
@@ -62,6 +63,7 @@ class MenuWidgetState extends State<MenuWidget> {
63 Image solanaIcon;
64 Image tronIcon;
65 Image wowneroIcon;
66 + Image zanoIcon;
67
68 @override
69 void initState() {
@@ -245,6 +247,8 @@ class MenuWidgetState extends State<MenuWidget> {
247 return tronIcon;
248 case WalletType.wownero:
249 return wowneroIcon;
250 + case WalletType.zano:
251 + return zanoIcon;
252 default:
253 throw Exception('No icon for ${type.toString()}');
254 }
lib/src/screens/new_wallet/advanced_privacy_settings_page.dart
+3 -3
@@ -202,7 +202,7 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
202 );
203 return Container();
204 }),
205 - if (widget.privacySettingsViewModel.hasPassphraseOption)
205 + if (widget.privacySettingsViewModel.hasPassphraseOption(widget.isFromRestore))
206 Padding(
207 padding: EdgeInsets.all(24),
208 child: Form(
@@ -219,7 +219,7 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
219 }),
220 child: Icon(
221 Icons.remove_red_eye,
222 - color: obscurePassphrase ? Colors.black54 : Colors.black26,
222 + // color: obscurePassphrase ? Colors.black54 : Colors.black26,
223 ),
224 ),
225 ),
@@ -241,7 +241,7 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
241 }),
242 child: Icon(
243 Icons.remove_red_eye,
244 - color: obscurePassphrase ? Colors.black54 : Colors.black26,
244 + // color: obscurePassphrase ? Colors.black54 : Colors.black26,
245 ),
246 ),
247 ),
lib/src/screens/receive/widgets/address_list.dart
+1
@@ -92,6 +92,7 @@ class _AddressListState extends State<AddressList> {
92 walletAddressListViewModel: widget.addressListViewModel,
93 trailingButtonTap: () async {
94 if (widget.addressListViewModel.type == WalletType.monero ||
95 + widget.addressListViewModel.type == WalletType.wownero ||
96 widget.addressListViewModel.type == WalletType.haven) {
97 await showPopUp<void>(
98 context: context, builder: (_) => getIt.get<MoneroAccountListPage>());
lib/src/screens/restore/wallet_restore_from_seed_form.dart
+7 -6
@@ -58,7 +58,7 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
58 repeatedPasswordTextEditingController = displayWalletPassword
59 ? TextEditingController()
60 : null,
61 - seedTypeController = TextEditingController();
61 + seedTypeController = TextEditingController();
62
63 final GlobalKey<SeedWidgetState> seedWidgetStateKey;
64 final GlobalKey<BlockchainHeightState> blockchainHeightKey;
@@ -75,7 +75,8 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
75
76 @override
77 void initState() {
78 - _setSeedType(widget.seedSettingsViewModel.moneroSeedType);
78 + // _setSeedType(widget.seedTypeViewModel.moneroSeedType);
79 + _setSeedType(MoneroSeedType.defaultSeedType);
80 _setLanguageLabel(language);
81
82 if (passwordTextEditingController != null) {
@@ -99,7 +100,7 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
100 }
101
102 @override
102 - void dispose() {
103 + void dispose() {
104 moneroSeedTypeReaction();
105
106 if (passwordListener != null) {
@@ -200,9 +201,9 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
201 items: _getItems(),
202 selectedAtIndex: isPolyseed
203 ? 1
203 - : seedTypeController.value.text.contains("14")
204 - ? 2
205 - : 0,
204 + : seedTypeController.value.text.contains("14") && widget.type == WalletType.wownero
205 + ? 2
206 + : 0,
207 mainAxisAlignment: MainAxisAlignment.start,
208 onItemSelected: _changeSeedType,
209 isSeparated: false,
lib/src/screens/restore/wallet_restore_page.dart
+1
@@ -274,6 +274,7 @@ class WalletRestorePage extends BasePage {
274 final seedWords = seedPhrase.split(' ');
275
276 if (seedWords.length == 14 && walletRestoreViewModel.type == WalletType.wownero) return true;
277 + if (seedWords.length == 26 && walletRestoreViewModel.type == WalletType.zano) return true;
278
279 if ((walletRestoreViewModel.type == WalletType.monero ||
280 walletRestoreViewModel.type == WalletType.wownero ||
lib/src/screens/send/widgets/extract_address_from_parsed.dart
+5
@@ -66,6 +66,11 @@ Future<String> extractAddressFromParsed(
66 content = S.of(context).extracted_address_content('${parsedAddress.name} (ThorChain)');
67 address = parsedAddress.addresses.first;
68 break;
69 + case ParseFrom.zanoAlias:
70 + title = S.of(context).address_detected;
71 + content = S.of(context).extracted_address_content('${parsedAddress.name} (Zano Alias)');
72 + address = parsedAddress.addresses.first;
73 + break;
74 case ParseFrom.yatRecord:
75 if (parsedAddress.name.isEmpty) {
76 title = S.of(context).yat_error;
lib/src/screens/settings/domain_lookups_page.dart
+4
@@ -49,6 +49,10 @@ class DomainLookupsPage extends BasePage {
49 title: '.well-known',
50 value: _privacySettingsViewModel.looksUpWellKnown,
51 onValueChange: (_, bool value) => _privacySettingsViewModel.setLookupsWellKnown(value)),
52 + SettingsSwitcherCell(
53 + title: 'Zano Aliases',
54 + value: _privacySettingsViewModel.lookupsZanoAlias,
55 + onValueChange: (_, bool value) => _privacySettingsViewModel.setLookupsZanoAlias(value)),
56
57 //if (!isHaven) it does not work correctly
58 ],
lib/src/screens/wallet_list/wallet_list_page.dart
+36
@@ -131,6 +131,7 @@ class WalletListBodyState extends State<WalletListBody> {
131 final solanaIcon = Image.asset('assets/images/sol_icon.png', height: 24, width: 24);
132 final tronIcon = Image.asset('assets/images/trx_icon.png', height: 24, width: 24);
133 final wowneroIcon = Image.asset('assets/images/wownero_icon.png', height: 24, width: 24);
134 + final zanoIcon = Image.asset('assets/images/zano_icon.png', height: 24, width: 24);
135 final scrollController = ScrollController();
136 final double tileHeight = 60;
137 Flushbar<void>? _progressBar;
@@ -401,6 +402,41 @@ class WalletListBodyState extends State<WalletListBody> {
402 );
403 }
404
405 + Image _imageFor({required WalletType type, bool? isTestnet}) {
406 + switch (type) {
407 + case WalletType.bitcoin:
408 + if (isTestnet == true) {
409 + return tBitcoinIcon;
410 + }
411 + return bitcoinIcon;
412 + case WalletType.monero:
413 + return moneroIcon;
414 + case WalletType.litecoin:
415 + return litecoinIcon;
416 + case WalletType.haven:
417 + return havenIcon;
418 + case WalletType.ethereum:
419 + return ethereumIcon;
420 + case WalletType.bitcoinCash:
421 + return bitcoinCashIcon;
422 + case WalletType.nano:
423 + case WalletType.banano:
424 + return nanoIcon;
425 + case WalletType.polygon:
426 + return polygonIcon;
427 + case WalletType.solana:
428 + return solanaIcon;
429 + case WalletType.tron:
430 + return tronIcon;
431 + case WalletType.wownero:
432 + return wowneroIcon;
433 + case WalletType.zano:
434 + return zanoIcon;
435 + case WalletType.none:
436 + return nonWalletTypeIcon;
437 + }
438 + }
439 +
440 Future<void> _loadWallet(WalletListItem wallet) async {
441 if (SettingsStoreBase.walletPasswordDirectInput) {
442 Navigator.of(context).pushNamed(Routes.walletUnlockLoadable,
lib/store/settings_store.dart
+51 -1
@@ -23,6 +23,10 @@ import 'package:cake_wallet/entities/seed_type.dart';
23 import 'package:cake_wallet/entities/sort_balance_types.dart';
24 import 'package:cake_wallet/entities/wallet_list_order_types.dart';
25 import 'package:cake_wallet/ethereum/ethereum.dart';
26 +import 'package:cake_wallet/wallet_type_utils.dart';
27 +import 'package:cake_wallet/wownero/wownero.dart';
28 +import 'package:cake_wallet/zano/zano.dart';
29 +import 'package:cw_core/transaction_priority.dart';
30 import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
31 import 'package:cake_wallet/monero/monero.dart';
32 import 'package:cake_wallet/polygon/polygon.dart';
@@ -110,6 +114,7 @@ abstract class SettingsStoreBase with Store {
114 required this.defaultNanoRep,
115 required this.defaultBananoRep,
116 required this.lookupsTwitter,
117 + required this.lookupsZanoAlias,
118 required this.lookupsMastodon,
119 required this.lookupsYatService,
120 required this.lookupsUnstoppableDomains,
@@ -132,6 +137,7 @@ abstract class SettingsStoreBase with Store {
137 TransactionPriority? initialEthereumTransactionPriority,
138 TransactionPriority? initialPolygonTransactionPriority,
139 TransactionPriority? initialBitcoinCashTransactionPriority,
140 + TransactionPriority? initialZanoTransactionPriority,
141 Country? initialCakePayCountry})
142 : nodes = ObservableMap<WalletType, Node>.of(nodes),
143 powNodes = ObservableMap<WalletType, Node>.of(powNodes),
@@ -215,6 +221,9 @@ abstract class SettingsStoreBase with Store {
221 priority[WalletType.bitcoinCash] = initialBitcoinCashTransactionPriority;
222 }
223
224 + if (initialZanoTransactionPriority != null) {
225 + priority[WalletType.zano] = initialZanoTransactionPriority;
226 + }
227 if (initialCakePayCountry != null) {
228 selectedCakePayCountry = initialCakePayCountry;
229 }
@@ -268,6 +277,9 @@ abstract class SettingsStoreBase with Store {
277 case WalletType.polygon:
278 key = PreferencesKey.polygonTransactionPriority;
279 break;
280 + case WalletType.zano:
281 + key = PreferencesKey.zanoTransactionPriority;
282 + break;
283 default:
284 key = null;
285 }
@@ -436,6 +448,10 @@ abstract class SettingsStoreBase with Store {
448 (_) => lookupsTwitter,
449 (bool looksUpTwitter) =>
450 _sharedPreferences.setBool(PreferencesKey.lookupsTwitter, looksUpTwitter));
451 + reaction(
452 + (_) => lookupsZanoAlias,
453 + (bool lookupsZanoAlias) =>
454 + _sharedPreferences.setBool(PreferencesKey.lookupsZanoAlias, lookupsZanoAlias));
455
456 reaction(
457 (_) => lookupsMastodon,
@@ -763,6 +779,9 @@ abstract class SettingsStoreBase with Store {
779 @observable
780 bool lookupsTwitter;
781
782 + @observable
783 + bool lookupsZanoAlias;
784 +
785 @observable
786 bool lookupsMastodon;
787
@@ -878,6 +897,7 @@ abstract class SettingsStoreBase with Store {
897 TransactionPriority? polygonTransactionPriority;
898 TransactionPriority? bitcoinCashTransactionPriority;
899 TransactionPriority? wowneroTransactionPriority;
900 + TransactionPriority? zanoTransactionPriority;
901
902 if (sharedPreferences.getInt(PreferencesKey.havenTransactionPriority) != null) {
903 havenTransactionPriority = monero?.deserializeMoneroTransactionPriority(
@@ -903,6 +923,10 @@ abstract class SettingsStoreBase with Store {
923 wowneroTransactionPriority = wownero?.deserializeWowneroTransactionPriority(
924 raw: sharedPreferences.getInt(PreferencesKey.wowneroTransactionPriority)!);
925 }
926 + if (sharedPreferences.getInt(PreferencesKey.zanoTransactionPriority) != null) {
927 + zanoTransactionPriority = monero?.deserializeMoneroTransactionPriority(
928 + raw: sharedPreferences.getInt(PreferencesKey.zanoTransactionPriority)!);
929 + }
930
931 moneroTransactionPriority ??= monero?.getDefaultTransactionPriority();
932 bitcoinTransactionPriority ??= bitcoin?.getMediumTransactionPriority();
@@ -912,6 +936,7 @@ abstract class SettingsStoreBase with Store {
936 bitcoinCashTransactionPriority ??= bitcoinCash?.getDefaultTransactionPriority();
937 wowneroTransactionPriority ??= wownero?.getDefaultTransactionPriority();
938 polygonTransactionPriority ??= polygon?.getDefaultTransactionPriority();
939 + zanoTransactionPriority ??= zano?.getDefaultTransactionPriority();
940
941 final currentBalanceDisplayMode = BalanceDisplayMode.deserialize(
942 raw: sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey)!);
@@ -969,6 +994,7 @@ abstract class SettingsStoreBase with Store {
994 final defaultNanoRep = sharedPreferences.getString(PreferencesKey.defaultNanoRep) ?? "";
995 final defaultBananoRep = sharedPreferences.getString(PreferencesKey.defaultBananoRep) ?? "";
996 final lookupsTwitter = sharedPreferences.getBool(PreferencesKey.lookupsTwitter) ?? true;
997 + final lookupsZanoAlias = sharedPreferences.getBool(PreferencesKey.lookupsZanoAlias) ?? true;
998 final lookupsMastodon = sharedPreferences.getBool(PreferencesKey.lookupsMastodon) ?? true;
999 final lookupsYatService = sharedPreferences.getBool(PreferencesKey.lookupsYatService) ?? true;
1000 final lookupsUnstoppableDomains =
@@ -1011,6 +1037,8 @@ abstract class SettingsStoreBase with Store {
1037 final solanaNodeId = sharedPreferences.getInt(PreferencesKey.currentSolanaNodeIdKey);
1038 final tronNodeId = sharedPreferences.getInt(PreferencesKey.currentTronNodeIdKey);
1039 final wowneroNodeId = sharedPreferences.getInt(PreferencesKey.currentWowneroNodeIdKey);
1040 + final zanoNodeId = sharedPreferences.getInt(PreferencesKey.currentZanoNodeIdKey);
1041 +
1042 final moneroNode = nodeSource.get(nodeId);
1043 final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId);
1044 final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
@@ -1023,6 +1051,7 @@ abstract class SettingsStoreBase with Store {
1051 final solanaNode = nodeSource.get(solanaNodeId);
1052 final tronNode = nodeSource.get(tronNodeId);
1053 final wowneroNode = nodeSource.get(wowneroNodeId);
1054 + final zanoNode = nodeSource.get(zanoNodeId);
1055 final packageInfo = await PackageInfo.fromPlatform();
1056 final deviceName = await _getDeviceName() ?? '';
1057 final shouldShowYatPopup = sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? true;
@@ -1104,6 +1133,10 @@ abstract class SettingsStoreBase with Store {
1133 nodes[WalletType.wownero] = wowneroNode;
1134 }
1135
1136 + if (zanoNode != null) {
1137 + nodes[WalletType.zano] = zanoNode;
1138 + }
1139 +
1140 final savedSyncMode = SyncMode.all.firstWhere((element) {
1141 return element.type.index == (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 0);
1142 });
@@ -1249,6 +1282,7 @@ abstract class SettingsStoreBase with Store {
1282 defaultNanoRep: defaultNanoRep,
1283 defaultBananoRep: defaultBananoRep,
1284 lookupsTwitter: lookupsTwitter,
1285 + lookupsZanoAlias: lookupsZanoAlias,
1286 lookupsMastodon: lookupsMastodon,
1287 lookupsYatService: lookupsYatService,
1288 lookupsUnstoppableDomains: lookupsUnstoppableDomains,
@@ -1265,6 +1299,7 @@ abstract class SettingsStoreBase with Store {
1299 hasEnabledMwebBefore: hasEnabledMwebBefore,
1300 initialMoneroTransactionPriority: moneroTransactionPriority,
1301 initialWowneroTransactionPriority: wowneroTransactionPriority,
1302 + initialZanoTransactionPriority: zanoTransactionPriority,
1303 initialBitcoinTransactionPriority: bitcoinTransactionPriority,
1304 initialHavenTransactionPriority: havenTransactionPriority,
1305 initialLitecoinTransactionPriority: litecoinTransactionPriority,
@@ -1339,6 +1374,10 @@ abstract class SettingsStoreBase with Store {
1374 priority[WalletType.bitcoinCash] = bitcoinCash!.deserializeBitcoinCashTransactionPriority(
1375 sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority)!);
1376 }
1377 + if (zano != null && sharedPreferences.getInt(PreferencesKey.zanoTransactionPriority) != null) {
1378 + priority[WalletType.zano] = zano!.deserializeMoneroTransactionPriority(
1379 + raw: sharedPreferences.getInt(PreferencesKey.zanoTransactionPriority)!);
1380 + }
1381
1382 final generateSubaddresses =
1383 sharedPreferences.getInt(PreferencesKey.autoGenerateSubaddressStatusKey);
@@ -1418,6 +1457,7 @@ abstract class SettingsStoreBase with Store {
1457 defaultNanoRep = sharedPreferences.getString(PreferencesKey.defaultNanoRep) ?? "";
1458 defaultBananoRep = sharedPreferences.getString(PreferencesKey.defaultBananoRep) ?? "";
1459 lookupsTwitter = sharedPreferences.getBool(PreferencesKey.lookupsTwitter) ?? true;
1460 + lookupsZanoAlias = sharedPreferences.getBool(PreferencesKey.lookupsZanoAlias) ?? true;
1461 lookupsMastodon = sharedPreferences.getBool(PreferencesKey.lookupsMastodon) ?? true;
1462 lookupsYatService = sharedPreferences.getBool(PreferencesKey.lookupsYatService) ?? true;
1463 lookupsUnstoppableDomains =
@@ -1448,6 +1488,7 @@ abstract class SettingsStoreBase with Store {
1488 final solanaNodeId = sharedPreferences.getInt(PreferencesKey.currentSolanaNodeIdKey);
1489 final tronNodeId = sharedPreferences.getInt(PreferencesKey.currentTronNodeIdKey);
1490 final wowneroNodeId = sharedPreferences.getInt(PreferencesKey.currentWowneroNodeIdKey);
1491 + final zanoNodeId = sharedPreferences.getInt(PreferencesKey.currentZanoNodeIdKey);
1492 final moneroNode = nodeSource.get(nodeId);
1493 final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId);
1494 final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
@@ -1455,10 +1496,12 @@ abstract class SettingsStoreBase with Store {
1496 final ethereumNode = nodeSource.get(ethereumNodeId);
1497 final polygonNode = nodeSource.get(polygonNodeId);
1498 final bitcoinCashNode = nodeSource.get(bitcoinCashElectrumServerId);
1458 - final nanoNode = nodeSource.get(nanoNodeId);
1499 + final nanoNode = nodeSource.get(nanoNodeId);
1500 final solanaNode = nodeSource.get(solanaNodeId);
1501 final tronNode = nodeSource.get(tronNodeId);
1502 final wowneroNode = nodeSource.get(wowneroNodeId);
1503 + final zanoNode = nodeSource.get(zanoNodeId);
1504 +
1505 if (moneroNode != null) {
1506 nodes[WalletType.monero] = moneroNode;
1507 }
@@ -1501,6 +1544,11 @@ abstract class SettingsStoreBase with Store {
1544
1545 if (wowneroNode != null) {
1546 nodes[WalletType.wownero] = wowneroNode;
1547 +
1548 + }
1549 +
1550 + if (zanoNode != null) {
1551 + nodes[WalletType.zano] = zanoNode;
1552 }
1553
1554 // MIGRATED:
@@ -1639,6 +1687,8 @@ abstract class SettingsStoreBase with Store {
1687 case WalletType.wownero:
1688 await _sharedPreferences.setInt(PreferencesKey.currentWowneroNodeIdKey, node.key as int);
1689 break;
1690 + case WalletType.zano:
1691 + await _sharedPreferences.setInt(PreferencesKey.currentZanoNodeIdKey, node.key as int);
1692 default:
1693 break;
1694 }
lib/view_model/advanced_privacy_settings_view_model.dart
+5 -2
@@ -54,11 +54,11 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
54 case WalletType.wownero:
55 case WalletType.none:
56 case WalletType.haven:
57 + case WalletType.zano:
58 return false;
59 }
60 }
61
61 -
62 bool get isMoneroSeedTypeOptionsEnabled => [
63 WalletType.monero,
64 WalletType.wownero,
@@ -71,13 +71,16 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
71
72 bool get isNanoSeedTypeOptionsEnabled => [WalletType.nano].contains(type);
73
74 - bool get hasPassphraseOption => [
74 + bool hasPassphraseOption(bool isRestore) => [
75 WalletType.bitcoin,
76 WalletType.litecoin,
77 WalletType.bitcoinCash,
78 WalletType.ethereum,
79 WalletType.polygon,
80 WalletType.tron,
81 + if (isRestore) WalletType.monero,
82 + if (isRestore) WalletType.wownero,
83 + if (isRestore) WalletType.zano,
84 ].contains(type);
85
86 @computed
lib/view_model/dashboard/balance_view_model.dart
+3 -1
@@ -111,7 +111,8 @@ abstract class BalanceViewModelBase with Store {
111 bool get isHomeScreenSettingsEnabled =>
112 isEVMCompatibleChain(wallet.type) ||
113 wallet.type == WalletType.solana ||
114 - wallet.type == WalletType.tron;
114 + wallet.type == WalletType.tron ||
115 + wallet.type == WalletType.zano;
116
117 @computed
118 bool get hasAccounts => wallet.type == WalletType.monero || wallet.type == WalletType.wownero;
@@ -306,6 +307,7 @@ abstract class BalanceViewModelBase with Store {
307 switch (type) {
308 case WalletType.monero:
309 case WalletType.wownero:
310 + case WalletType.zano:
311 return true;
312 default:
313 return false;
lib/view_model/dashboard/home_settings_view_model.dart
+29
@@ -13,6 +13,7 @@ import 'package:cake_wallet/solana/solana.dart';
13 import 'package:cake_wallet/store/settings_store.dart';
14 import 'package:cake_wallet/tron/tron.dart';
15 import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
16 +import 'package:cake_wallet/zano/zano.dart';
17 import 'package:cw_core/crypto_currency.dart';
18 import 'package:cw_core/erc20_token.dart';
19 import 'package:cw_core/utils/print_verbose.dart';
@@ -39,6 +40,8 @@ abstract class HomeSettingsViewModelBase with Store {
40
41 final ObservableSet<CryptoCurrency> tokens;
42
43 + WalletType get walletType => _balanceViewModel.wallet.type;
44 +
45 @observable
46 bool isAddingToken;
47
@@ -108,6 +111,10 @@ abstract class HomeSettingsViewModelBase with Store {
111 await tron!.addTronToken(_balanceViewModel.wallet, token, contractAddress);
112 }
113
114 + if (_balanceViewModel.wallet.type == WalletType.zano) {
115 + await zano!.addZanoAssetById(_balanceViewModel.wallet, contractAddress);
116 + }
117 +
118 _updateTokensList();
119 _updateFiatPrices(token);
120 } finally {
@@ -134,6 +141,9 @@ abstract class HomeSettingsViewModelBase with Store {
141 if (_balanceViewModel.wallet.type == WalletType.tron) {
142 await tron!.deleteTronToken(_balanceViewModel.wallet, token);
143 }
144 + if (_balanceViewModel.wallet.type == WalletType.zano) {
145 + await zano!.deleteZanoAsset(_balanceViewModel.wallet, token);
146 + }
147 _updateTokensList();
148 } finally {
149 isDeletingToken = false;
@@ -340,6 +350,10 @@ abstract class HomeSettingsViewModelBase with Store {
350 return await tron!.getTronToken(_balanceViewModel.wallet, contractAddress);
351 }
352
353 + if (_balanceViewModel.wallet.type == WalletType.zano) {
354 + return await zano!.getZanoAsset(_balanceViewModel.wallet, contractAddress);
355 + }
356 +
357 return null;
358 }
359
@@ -378,6 +392,10 @@ abstract class HomeSettingsViewModelBase with Store {
392 tron!.addTronToken(_balanceViewModel.wallet, token, address);
393 }
394
395 + if (_balanceViewModel.wallet.type == WalletType.zano) {
396 + await zano!.changeZanoAssetAvailability(_balanceViewModel.wallet, token);
397 + }
398 +
399 _refreshTokensList();
400 }
401
@@ -432,6 +450,13 @@ abstract class HomeSettingsViewModelBase with Store {
450 .toList()
451 ..sort(_sortFunc));
452 }
453 +
454 + if (_balanceViewModel.wallet.type == WalletType.zano) {
455 + tokens.addAll(zano!.getZanoAssets(_balanceViewModel.wallet)
456 + .where((element) => _matchesSearchText(element))
457 + .toList()
458 + ..sort(_sortFunc));
459 + }
460 }
461
462 @action
@@ -476,6 +501,10 @@ abstract class HomeSettingsViewModelBase with Store {
501 return polygon!.getTokenAddress(asset);
502 }
503
504 + if (_balanceViewModel.wallet.type == WalletType.zano) {
505 + return zano!.getZanoAssetAddress(asset);
506 + }
507 +
508 // We return null if it's neither Tron, Polygon, Ethereum or Solana wallet (which is actually impossible because we only display home settings for either of these three wallets).
509 return null;
510 }
lib/view_model/dashboard/transaction_list_item.dart
+9 -1
@@ -8,6 +8,7 @@ import 'package:cake_wallet/reactions/wallet_connect.dart';
8 import 'package:cake_wallet/solana/solana.dart';
9 import 'package:cake_wallet/tron/tron.dart';
10 import 'package:cake_wallet/wownero/wownero.dart';
11 +import 'package:cake_wallet/zano/zano.dart';
12 import 'package:cw_core/crypto_currency.dart';
13 import 'package:cw_core/transaction_direction.dart';
14 import 'package:cw_core/transaction_info.dart';
@@ -63,6 +64,7 @@ class TransactionListItem extends ActionListItem with Keyable {
64 switch (balanceViewModel.wallet.type) {
65 case WalletType.monero:
66 case WalletType.haven:
67 + case WalletType.zano:
68 if (transaction.confirmations >= 0 && transaction.confirmations < 10) {
69 return ' (${transaction.confirmations}/10)';
70 }
@@ -201,7 +203,6 @@ class TransactionListItem extends ActionListItem with Keyable {
203 price: price,
204 );
205 break;
204 -
206 case WalletType.tron:
207 final asset = tron!.assetOfTransaction(balanceViewModel.wallet, transaction);
208 final price = balanceViewModel.fiatConvertationStore.prices[asset];
@@ -211,6 +212,13 @@ class TransactionListItem extends ActionListItem with Keyable {
212 price: price,
213 );
214 break;
215 + case WalletType.zano:
216 + final asset = zano!.assetOfTransaction(balanceViewModel.wallet, transaction);
217 + final price = balanceViewModel.fiatConvertationStore.prices[asset];
218 + amount = calculateFiatAmountRaw(
219 + cryptoAmount: zano!.formatterIntAmountToDouble(amount: transaction.amount, currency: asset, forFee: false),
220 + price: price);
221 + break;
222 default:
223 break;
224 }
lib/view_model/exchange/exchange_view_model.dart
+6
@@ -311,6 +311,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
311 case WalletType.monero:
312 case WalletType.wownero:
313 case WalletType.haven:
314 + case WalletType.zano:
315 return transactionPriority == monero!.getMoneroTransactionPrioritySlow();
316 case WalletType.bitcoin:
317 return transactionPriority == bitcoin!.getBitcoinTransactionPrioritySlow();
@@ -750,6 +751,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
751 depositCurrency = CryptoCurrency.wow;
752 receiveCurrency = CryptoCurrency.xmr;
753 break;
754 + case WalletType.zano:
755 + depositCurrency = CryptoCurrency.zano;
756 + receiveCurrency = CryptoCurrency.xmr;
757 + break;
758 case WalletType.none:
759 break;
760 }
@@ -824,6 +829,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
829 case WalletType.monero:
830 case WalletType.haven:
831 case WalletType.wownero:
832 + case WalletType.zano:
833 _settingsStore.priority[wallet.type] = monero!.getMoneroTransactionPriorityAutomatic();
834 break;
835 case WalletType.bitcoin:
lib/view_model/node_list/node_create_or_edit_view_model.dart
+1
@@ -85,6 +85,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
85 case WalletType.litecoin:
86 case WalletType.bitcoinCash:
87 case WalletType.bitcoin:
88 + case WalletType.zano:
89 return false;
90 }
91 }
lib/view_model/node_list/node_list_view_model.dart
+3
@@ -88,6 +88,9 @@ abstract class NodeListViewModelBase with Store {
88 case WalletType.wownero:
89 node = getWowneroDefaultNode(nodes: _nodeSource);
90 break;
91 + case WalletType.zano:
92 + node = getZanoDefaultNode(nodes: _nodeSource)!;
93 + break;
94 default:
95 throw Exception('Unexpected wallet type: ${_appStore.wallet!.type}');
96 }
lib/view_model/restore/restore_from_qr_vm.dart
+11
@@ -9,6 +9,7 @@ import 'package:cake_wallet/view_model/restore/restore_mode.dart';
9 import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
10 import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
11 import 'package:cake_wallet/wownero/wownero.dart';
12 +import 'package:cake_wallet/zano/zano.dart';
13 import 'package:hive/hive.dart';
14 import 'package:mobx/mobx.dart';
15 import 'package:cake_wallet/monero/monero.dart';
@@ -110,6 +111,7 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
111 height: restoreWallet.height ?? 0,
112 mnemonic: restoreWallet.mnemonicSeed ?? '',
113 password: password,
114 + passphrase: restoreWallet.passphrase ?? '',
115 );
116 case WalletType.bitcoin:
117 case WalletType.litecoin:
@@ -180,6 +182,15 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
182 height: restoreWallet.height ?? 0,
183 mnemonic: restoreWallet.mnemonicSeed ?? '',
184 password: password,
185 + passphrase: restoreWallet.passphrase ?? '',
186 + );
187 + case WalletType.zano:
188 + return zano!.createZanoRestoreWalletFromSeedCredentials(
189 + name: name,
190 + password: password,
191 + height: height,
192 + mnemonic: restoreWallet.mnemonicSeed ?? '',
193 + passphrase: restoreWallet.passphrase ?? '',
194 );
195 default:
196 throw Exception('Unexpected type: ${type.toString()}');
lib/view_model/restore/wallet_restore_from_qr_code.dart
+3
@@ -41,6 +41,9 @@ class WalletRestoreFromQRCode {
41 'wownero': WalletType.wownero,
42 'wownero-wallet': WalletType.wownero,
43 'wownero_wallet': WalletType.wownero,
44 + 'zano': WalletType.zano,
45 + 'zano-wallet': WalletType.zano,
46 + 'zano_wallet': WalletType.zano,
47 };
48
49 static bool _containsAssetSpecifier(String code) => _extractWalletType(code) != null;
lib/view_model/send/output.dart
+11
@@ -10,6 +10,7 @@ import 'package:cake_wallet/solana/solana.dart';
10 import 'package:cake_wallet/src/screens/send/widgets/extract_address_from_parsed.dart';
11 import 'package:cake_wallet/tron/tron.dart';
12 import 'package:cake_wallet/wownero/wownero.dart';
13 +import 'package:cake_wallet/zano/zano.dart';
14 import 'package:cw_core/crypto_currency.dart';
15 import 'package:cw_core/utils/print_verbose.dart';
16 import 'package:flutter/material.dart';
@@ -112,6 +113,9 @@ abstract class OutputBase with Store {
113 case WalletType.wownero:
114 _amount = wownero!.formatterWowneroParseAmount(amount: _cryptoAmount);
115 break;
116 + case WalletType.zano:
117 + _amount = zano!.formatterParseAmount(amount: _cryptoAmount, currency: cryptoCurrencyHandler());
118 + break;
119 default:
120 break;
121 }
@@ -180,6 +184,10 @@ abstract class OutputBase with Store {
184 if (_wallet.type == WalletType.polygon) {
185 return polygon!.formatterPolygonAmountToDouble(amount: BigInt.from(fee));
186 }
187 +
188 + if (_wallet.type == WalletType.zano) {
189 + return zano!.formatterIntAmountToDouble(amount: fee, currency: cryptoCurrencyHandler(), forFee: true);
190 + }
191 } catch (e) {
192 printV(e.toString());
193 }
@@ -308,6 +316,9 @@ abstract class OutputBase with Store {
316 case WalletType.wownero:
317 maximumFractionDigits = 11;
318 break;
319 + case WalletType.zano:
320 + maximumFractionDigits = 12;
321 + break;
322 default:
323 break;
324 }
lib/view_model/send/send_view_model.dart
+8 -2
@@ -20,6 +20,7 @@ import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
20 import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
21 import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart';
22 import 'package:cake_wallet/wownero/wownero.dart';
23 +import 'package:cake_wallet/zano/zano.dart';
24 import 'package:cw_core/exceptions.dart';
25 import 'package:cw_core/transaction_info.dart';
26 import 'package:cw_core/transaction_priority.dart';
@@ -61,7 +62,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
62 selectedCryptoCurrency = wallet.currency;
63 hasMultipleTokens = isEVMCompatibleChain(wallet.type) ||
64 wallet.type == WalletType.solana ||
64 - wallet.type == WalletType.tron;
65 + wallet.type == WalletType.tron ||
66 + wallet.type == WalletType.zano;
67 }
68
69 UnspentCoinsListViewModel unspentCoinsListViewModel;
@@ -81,7 +83,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
83 selectedCryptoCurrency = appStore.wallet!.currency,
84 hasMultipleTokens = isEVMCompatibleChain(appStore.wallet!.type) ||
85 appStore.wallet!.type == WalletType.solana ||
84 - appStore.wallet!.type == WalletType.tron,
86 + appStore.wallet!.type == WalletType.tron ||
87 + appStore.wallet!.type == WalletType.zano,
88 outputs = ObservableList<Output>(),
89 _settingsStore = appStore.settingsStore,
90 fiatFromSettings = appStore.settingsStore.fiatCurrency,
@@ -576,6 +579,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
579 .createSolanaTransactionCredentials(outputs, currency: selectedCryptoCurrency);
580 case WalletType.tron:
581 return tron!.createTronTransactionCredentials(outputs, currency: selectedCryptoCurrency);
582 + case WalletType.zano:
583 + return zano!.createZanoTransactionCredentials(
584 + outputs: outputs, priority: priority!, currency: selectedCryptoCurrency);
585 default:
586 throw Exception('Unexpected wallet type: ${wallet.type}');
587 }
lib/view_model/settings/privacy_settings_view_model.dart
+6 -1
@@ -1,4 +1,3 @@
1 -import 'package:cake_wallet/bitcoin/bitcoin.dart';
1 import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
2 import 'package:cake_wallet/entities/exchange_api_mode.dart';
3 import 'package:cake_wallet/ethereum/ethereum.dart';
@@ -79,6 +78,9 @@ abstract class PrivacySettingsViewModelBase with Store {
78 @computed
79 bool get lookupTwitter => _settingsStore.lookupsTwitter;
80
81 + @computed
82 + bool get lookupsZanoAlias => _settingsStore.lookupsZanoAlias;
83 +
84 @computed
85 bool get looksUpMastodon => _settingsStore.lookupsMastodon;
86
@@ -127,6 +129,9 @@ abstract class PrivacySettingsViewModelBase with Store {
129 @action
130 void setLookupsTwitter(bool value) => _settingsStore.lookupsTwitter = value;
131
132 + @action
133 + void setLookupsZanoAlias(bool value) => _settingsStore.lookupsZanoAlias = value;
134 +
135 @action
136 void setLookupsMastodon(bool value) => _settingsStore.lookupsMastodon = value;
137
lib/view_model/transaction_details_view_model.dart
+23
@@ -81,6 +81,9 @@ abstract class TransactionDetailsViewModelBase with Store {
81 case WalletType.wownero:
82 _addWowneroListItems(tx, dateFormat);
83 break;
84 + case WalletType.zano:
85 + _addZanoListItems(tx, dateFormat);
86 + break;
87 default:
88 break;
89 }
@@ -181,6 +184,8 @@ abstract class TransactionDetailsViewModelBase with Store {
184 return 'https://tronscan.org/#/transaction/${txId}';
185 case WalletType.wownero:
186 return 'https://explore.wownero.com/tx/${txId}';
187 + case WalletType.zano:
188 + return 'https://explorer.zano.org/transaction/${txId}';
189 case WalletType.none:
190 return '';
191 }
@@ -211,6 +216,8 @@ abstract class TransactionDetailsViewModelBase with Store {
216 return S.current.view_transaction_on + 'tronscan.org';
217 case WalletType.wownero:
218 return S.current.view_transaction_on + 'Wownero.com';
219 + case WalletType.zano:
220 + return S.current.view_transaction_on + 'explorer.zano.org';
221 case WalletType.none:
222 return '';
223 }
@@ -776,4 +783,20 @@ abstract class TransactionDetailsViewModelBase with Store {
783
784 items.addAll(_items);
785 }
786 +
787 + void _addZanoListItems(TransactionInfo tx, DateFormat dateFormat) {
788 + final comment = tx.additionalInfo['comment'] as String?;
789 + items.addAll([
790 + StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id),
791 + StandartListItem(title: 'Asset ID', value: tx.additionalInfo['assetId'] as String? ?? "Unknown asset id"),
792 + StandartListItem(
793 + title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
794 + StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),
795 + StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
796 + if (tx.feeFormatted()?.isNotEmpty ?? false)
797 + StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!),
798 + if (comment != null)
799 + StandartListItem(title: S.current.transaction_details_title, value: comment),
800 + ]);
801 + }
802 }
lib/view_model/wallet_address_list/wallet_address_list_view_model.dart
+26 -1
@@ -17,6 +17,7 @@ import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
17 import 'package:cake_wallet/store/settings_store.dart';
18 import 'package:cake_wallet/store/yat/yat_store.dart';
19 import 'package:cake_wallet/tron/tron.dart';
20 +import 'package:cake_wallet/zano/zano.dart';
21 import 'package:cake_wallet/utils/list_item.dart';
22 import 'package:cake_wallet/view_model/wallet_address_list/wallet_account_list_header.dart';
23 import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_hidden_list_header.dart';
@@ -205,6 +206,23 @@ class WowneroURI extends PaymentURI {
206 }
207 }
208
209 +class ZanoURI extends PaymentURI {
210 + ZanoURI({required String amount, required String address})
211 + : super(amount: amount, address: address);
212 +
213 + @override
214 + String toString() {
215 + var base = 'zano:' + address;
216 +
217 + if (amount.isNotEmpty) {
218 + base += '?amount=${amount.replaceAll(',', '.')}';
219 + }
220 +
221 + return base;
222 + }
223 +}
224 +
225 +
226 abstract class WalletAddressListViewModelBase
227 extends WalletChangeListenerViewModel with Store {
228 WalletAddressListViewModelBase({
@@ -298,11 +316,12 @@ abstract class WalletAddressListViewModelBase
316 return TronURI(amount: amount, address: address.address);
317 case WalletType.wownero:
318 return WowneroURI(amount: amount, address: address.address);
319 + case WalletType.zano:
320 + return ZanoURI(amount: amount, address: address.address);
321 case WalletType.none:
322 throw Exception('Unexpected type: ${type.toString()}');
323 }
324 }
305 -
325 @computed
326 ObservableList<ListItem> get items => ObservableList<ListItem>()
327 ..addAll(_baseItems)
@@ -479,6 +498,12 @@ abstract class WalletAddressListViewModelBase
498 .contains((addressList[i] as WalletAddressListItem).address);
499 }
500
501 + if (wallet.type == WalletType.zano) {
502 + final primaryAddress = zano!.getAddress(wallet);
503 +
504 + addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
505 + }
506 +
507 if (searchText.isNotEmpty) {
508 return ObservableList.of(addressList.where((item) {
509 if (item is WalletAddressListItem) {
lib/view_model/wallet_keys_view_model.dart
+11 -1
@@ -6,11 +6,13 @@ import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.d
6 import 'package:cake_wallet/src/widgets/text_info_box.dart';
7 import 'package:cake_wallet/store/app_store.dart';
8 import 'package:cake_wallet/wownero/wownero.dart';
9 +import 'package:cake_wallet/zano/zano.dart';
10 import 'package:cw_core/transaction_direction.dart';
11 import 'package:cw_core/transaction_info.dart';
12 import 'package:cw_core/wallet_base.dart';
13 import 'package:cw_core/wallet_type.dart';
14 import 'package:cw_monero/monero_wallet.dart';
15 +import 'package:cw_wownero/wownero_wallet.dart';
16 import 'package:flutter/foundation.dart';
17 import 'package:mobx/mobx.dart';
18 import 'package:polyseed/polyseed.dart';
@@ -255,6 +257,12 @@ abstract class WalletKeysViewModelBase with Store {
257 ),
258 ]);
259 }
260 +
261 + if (_appStore.wallet!.type == WalletType.zano) {
262 + items.addAll([
263 + StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed!),
264 + ]);
265 + }
266 }
267
268 Future<int?> _currentHeight() async {
@@ -296,6 +304,8 @@ abstract class WalletKeysViewModelBase with Store {
304 return 'tron-wallet';
305 case WalletType.wownero:
306 return 'wownero-wallet';
307 + case WalletType.zano:
308 + return 'zano-wallet';
309 default:
310 throw Exception('Unexpected wallet type: ${_wallet.type.toString()}');
311 }
@@ -328,7 +338,7 @@ abstract class WalletKeysViewModelBase with Store {
338 return {
339 if (legacySeed.isNotEmpty) 'seed': legacySeed,
340 if (restoreHeightResult != null) ...{'height': restoreHeightResult},
331 - if (_wallet.passphrase != null) 'passphrase': _wallet.passphrase!
341 + if ((_wallet.passphrase ?? '') != '') 'passphrase': _wallet.passphrase!
342 };
343 }
344
lib/view_model/wallet_new_vm.dart
+14 -1
@@ -1,5 +1,13 @@
1 import 'package:cake_wallet/core/new_wallet_arguments.dart';
2 import 'package:cake_wallet/ethereum/ethereum.dart';
3 +import 'package:cake_wallet/zano/zano.dart';
4 +import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
5 +import 'package:cake_wallet/solana/solana.dart';
6 +import 'package:cake_wallet/tron/tron.dart';
7 +import 'package:cake_wallet/wownero/wownero.dart';
8 +import 'package:cake_wallet/zano/zano.dart';
9 +import 'package:hive/hive.dart';
10 +import 'package:mobx/mobx.dart';
11 import 'package:cake_wallet/bitcoin/bitcoin.dart';
12 import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
13 import 'package:cake_wallet/core/wallet_creation_service.dart';
@@ -152,9 +160,14 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
160 case WalletType.wownero:
161 return wownero!.createWowneroNewWalletCredentials(
162 name: name,
155 - password: walletPassword,
163 language: options!.first as String,
164 isPolyseed: options.last as bool,
165 + password: walletPassword,
166 + );
167 + case WalletType.zano:
168 + return zano!.createZanoNewWalletCredentials(
169 + name: name,
170 + password: walletPassword,
171 );
172 case WalletType.none:
173 throw Exception('Unexpected type: ${type.toString()}');
lib/view_model/wallet_restore_view_model.dart
+11 -1
@@ -15,6 +15,7 @@ import 'package:cake_wallet/view_model/restore/restore_mode.dart';
15 import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
16 import 'package:cake_wallet/view_model/wallet_creation_vm.dart';
17 import 'package:cake_wallet/wownero/wownero.dart';
18 +import 'package:cake_wallet/zano/zano.dart';
19 import 'package:cw_core/wallet_base.dart';
20 import 'package:cw_core/wallet_credentials.dart';
21 import 'package:cw_core/wallet_info.dart';
@@ -61,6 +62,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
62 case WalletType.bitcoin:
63 case WalletType.litecoin:
64 case WalletType.bitcoinCash:
65 + case WalletType.zano:
66 case WalletType.none:
67 availableModes = [WalletRestoreMode.seed];
68 break;
@@ -97,7 +99,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
99 switch (type) {
100 case WalletType.monero:
101 return monero!.createMoneroRestoreWalletFromSeedCredentials(
100 - name: name, height: height, mnemonic: seed, password: password);
102 + name: name, height: height, mnemonic: seed, password: password, passphrase: passphrase??'');
103 case WalletType.bitcoin:
104 case WalletType.litecoin:
105 return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials(
@@ -160,8 +162,16 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
162 name: name,
163 mnemonic: seed,
164 password: password,
165 + passphrase: passphrase??'',
166 height: height,
167 );
168 + case WalletType.zano:
169 + return zano!.createZanoRestoreWalletFromSeedCredentials(
170 + name: name,
171 + password: password,
172 + height: height,
173 + passphrase: passphrase??'',
174 + mnemonic: seed);
175 case WalletType.none:
176 break;
177 }
lib/wownero/cw_wownero.dart
+4 -2
@@ -222,10 +222,11 @@ class CWWownero extends Wownero {
222 WalletCredentials createWowneroRestoreWalletFromSeedCredentials(
223 {required String name,
224 required String password,
225 + required String passphrase,
226 required int height,
227 required String mnemonic}) =>
228 WowneroRestoreWalletFromSeedCredentials(
228 - name: name, password: password, height: height, mnemonic: mnemonic);
229 + name: name, password: password, passphrase: passphrase, height: height, mnemonic: mnemonic);
230
231 @override
232 WalletCredentials createWowneroNewWalletCredentials(
@@ -244,7 +245,8 @@ class CWWownero extends Wownero {
245 'privateSpendKey': keys.privateSpendKey,
246 'privateViewKey': keys.privateViewKey,
247 'publicSpendKey': keys.publicSpendKey,
247 - 'publicViewKey': keys.publicViewKey
248 + 'publicViewKey': keys.publicViewKey,
249 + 'passphrase': keys.passphrase
250 };
251 }
252
lib/zano/cw_zano.dart new
+134
@@ -0,0 +1,134 @@
1 +part of 'zano.dart';
2 +
3 +class CWZano extends Zano {
4 +
5 + List<ZanoAsset> getZanoAssets(WalletBase wallet) => (wallet as ZanoWallet).zanoAssets.values.toList();
6 +
7 + @override
8 + Future<CryptoCurrency> addZanoAssetById(WalletBase wallet, String assetId) async => await (wallet as ZanoWallet).addZanoAssetById(assetId);
9 +
10 + @override
11 + Future<void> changeZanoAssetAvailability(WalletBase wallet, CryptoCurrency token) async => await (wallet as ZanoWallet).changeZanoAssetAvailability(token as ZanoAsset);
12 +
13 + @override
14 + Future<void> deleteZanoAsset(WalletBase wallet, CryptoCurrency token) async => await (wallet as ZanoWallet).deleteZanoAsset(token as ZanoAsset);
15 +
16 + @override
17 + Future<ZanoAsset?> getZanoAsset(WalletBase wallet, String assetId) async {
18 + final zanoWallet = wallet as ZanoWallet;
19 + return await zanoWallet.getZanoAsset(assetId);
20 + }
21 +
22 + // @override
23 + // TransactionHistoryBase getTransactionHistory(Object wallet) {
24 + // final zanoWallet = wallet as ZanoWallet;
25 + // return zanoWallet.transactionHistory;
26 + // }
27 +
28 + @override
29 + TransactionPriority getDefaultTransactionPriority() {
30 + return MoneroTransactionPriority.automatic;
31 + }
32 +
33 + @override
34 + TransactionPriority deserializeMoneroTransactionPriority({required int raw}) {
35 + return MoneroTransactionPriority.deserialize(raw: raw);
36 + }
37 +
38 + @override
39 + List<TransactionPriority> getTransactionPriorities() {
40 + return MoneroTransactionPriority.all;
41 + }
42 +
43 + @override
44 + List<String> getWordList(String language) {
45 + assert(language.toLowerCase() == LanguageList.english.toLowerCase());
46 + return EnglishMnemonics.words;
47 + }
48 +
49 + @override
50 + WalletCredentials createZanoRestoreWalletFromSeedCredentials(
51 + {required String name, required String password, required int height, required String passphrase, required String mnemonic}) {
52 + return ZanoRestoreWalletFromSeedCredentials(name: name, password: password, passphrase: passphrase, height: height, mnemonic: mnemonic);
53 + }
54 +
55 + @override
56 + WalletCredentials createZanoNewWalletCredentials({required String name, required String? password}) {
57 + return ZanoNewWalletCredentials(name: name, password: password);
58 + }
59 +
60 + // @override
61 + // Map<String, String> getKeys(Object wallet) {
62 + // final zanoWallet = wallet as ZanoWallet;
63 + // final keys = zanoWallet.keys;
64 + // return <String, String>{
65 + // 'privateSpendKey': keys.privateSpendKey,
66 + // 'privateViewKey': keys.privateViewKey,
67 + // 'publicSpendKey': keys.publicSpendKey,
68 + // 'publicViewKey': keys.publicViewKey
69 + // };
70 + // }
71 +
72 + @override
73 + Object createZanoTransactionCredentials({required List<Output> outputs, required TransactionPriority priority, required CryptoCurrency currency}) {
74 + return ZanoTransactionCredentials(
75 + outputs: outputs
76 + .map((out) => OutputInfo(
77 + fiatAmount: out.fiatAmount,
78 + cryptoAmount: out.cryptoAmount,
79 + address: out.address,
80 + note: out.note,
81 + sendAll: out.sendAll,
82 + extractedAddress: out.extractedAddress,
83 + isParsedAddress: out.isParsedAddress,
84 + formattedCryptoAmount: out.formattedCryptoAmount))
85 + .toList(),
86 + priority: priority as MoneroTransactionPriority,
87 + currency: currency,
88 + );
89 + }
90 +
91 + @override
92 + double formatterIntAmountToDouble({required int amount, required CryptoCurrency currency, required bool forFee}) {
93 + // fee always counted in zano with default decimal points
94 + if (forFee) return ZanoFormatter.intAmountToDouble(amount);
95 + if (currency is ZanoAsset) return ZanoFormatter.intAmountToDouble(amount, currency.decimalPoint);
96 + return ZanoFormatter.intAmountToDouble(amount);
97 + }
98 +
99 + @override
100 + int formatterParseAmount({required String amount, required CryptoCurrency currency}) {
101 + if (currency is ZanoAsset) return ZanoFormatter.parseAmount(amount, currency.decimalPoint);
102 + return ZanoFormatter.parseAmount(amount);
103 + }
104 +
105 + // @override
106 + // int getTransactionInfoAccountId(TransactionInfo tx) {
107 + // final zanoTransactionInfo = tx as ZanoTransactionInfo;
108 + // return zanoTransactionInfo.accountIndex;
109 + // }
110 +
111 + @override
112 + WalletService createZanoWalletService(Box<WalletInfo> walletInfoSource) {
113 + return ZanoWalletService(walletInfoSource);
114 + }
115 +
116 + @override
117 + CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) {
118 + transaction as ZanoTransactionInfo;
119 + if (transaction.tokenSymbol == CryptoCurrency.zano.title) {
120 + return CryptoCurrency.zano;
121 + }
122 + wallet as ZanoWallet;
123 + final asset = wallet.zanoAssets.values.firstWhereOrNull((element) => element?.ticker == transaction.tokenSymbol);
124 + return asset ?? CryptoCurrency.zano;
125 + }
126 +
127 + String getZanoAssetAddress(CryptoCurrency asset) => (asset as ZanoAsset).assetId;
128 +
129 + @override
130 + String getAddress(WalletBase wallet) => (wallet as ZanoWallet).walletAddresses.address;
131 +
132 + @override
133 + bool validateAddress(String address) => ZanoUtils.validateAddress(address);
134 +}
linux/CMakeLists.txt
+11 -2
@@ -107,10 +107,19 @@ install(CODE "
107 set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
108 set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
109
110 -install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/monero_c/release/monero/x86_64-linux-gnu_libwallet2_api_c.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" RENAME "monero_libwallet2_api_c.so"
110 +if(CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64")
111 + set(LIB_TRIPLET "aarch64-linux-gnu")
112 +else()
113 + set(LIB_TRIPLET "x86_64-linux-gnu")
114 +endif()
115 +
116 +install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/monero_c/release/monero/${LIB_TRIPLET}_libwallet2_api_c.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" RENAME "monero_libwallet2_api_c.so"
117 + COMPONENT Runtime)
118 +
119 +install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/monero_c/release/wownero/${LIB_TRIPLET}_libwallet2_api_c.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" RENAME "wownero_libwallet2_api_c.so"
120 COMPONENT Runtime)
121
113 -install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/monero_c/release/wownero/x86_64-linux-gnu_libwallet2_api_c.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" RENAME "wownero_libwallet2_api_c.so"
122 +install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/monero_c/release/zano/${LIB_TRIPLET}_libwallet2_api_c.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" RENAME "zano_libwallet2_api_c.so"
123 COMPONENT Runtime)
124
125 install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
model_generator.sh
+1 -1
@@ -1,7 +1,7 @@
1 #!/bin/bash
2 set -x -e
3
4 -for cwcoin in cw_{core,evm,monero,bitcoin,haven,nano,bitcoin_cash,solana,tron,wownero}
4 +for cwcoin in cw_{core,evm,monero,bitcoin,haven,nano,bitcoin_cash,solana,tron,wownero,zano}
5 do
6 if [[ "x$1" == "xasync" ]];
7 then
pubspec_base.yaml
+1
@@ -181,6 +181,7 @@ flutter:
181 - assets/solana_node_list.yml
182 - assets/tron_node_list.yml
183 - assets/wownero_node_list.yml
184 + - assets/zano_node_list.yml
185 - assets/text/
186 - assets/faq/
187 - assets/animation/
res/values/strings_en.arb
+1
@@ -499,6 +499,7 @@
499 "overwrite_amount": "Overwrite amount",
500 "pairingInvalidEvent": "Pairing Invalid Event",
501 "passphrase": "Passphrase (Optional)",
502 + "passphrase_view_keys": "Passphrase",
503 "passphrases_doesnt_match": "Passphrases do not match, please try again",
504 "password": "Password",
505 "paste": "Paste",
run-android.sh
+1 -1
@@ -1,5 +1,5 @@
1 #!/bin/bash
2 -source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/universal_sed.sh"
2 +source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/functions.sh"
3 # Get the current git branch
4 get_current_branch() {
5 if git rev-parse --git-dir > /dev/null 2>&1; then
scripts/android/build_monero_all.sh
+4 -5
@@ -1,4 +1,5 @@
1 #!/bin/bash
2 +source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/functions.sh"
3
4 # Usage: env USE_DOCKER= ./build_all.sh
5
@@ -6,11 +7,9 @@ set -x -e
7
8 cd "$(dirname "$0")"
9
9 -NPROC="-j$(nproc)"
10 -
10 ../prepare_moneroc.sh
11
13 -for COIN in monero wownero;
12 +for COIN in monero wownero zano;
13 do
14 pushd ../monero_c
15 for target in {x86_64,aarch64}-linux-android armv7a-linux-androideabi
@@ -19,9 +18,9 @@ do
18 then
19 echo "file exist, not building monero_c for ${COIN}/$target.";
20 else
22 - env -i ./build_single.sh ${COIN} $target $NPROC
21 + ./build_single.sh ${COIN} $target -j$MAKE_JOB_COUNT
22 unxz -f ../monero_c/release/${COIN}/${target}_libwallet2_api_c.so.xz
23 fi
24 done
25 popd
27 -done
\ No newline at end of file
26 +done
scripts/android/copy_monero_deps.sh
+2
@@ -5,6 +5,7 @@ CW_DIR=${WORKDIR}/cake_wallet
5 CW_EXRTERNAL_DIR=${CW_DIR}/cw_shared_external/ios/External/android
6 CW_HAVEN_EXTERNAL_DIR=${CW_DIR}/cw_haven/ios/External/android
7 CW_MONERO_EXTERNAL_DIR=${CW_DIR}/cw_monero/ios/External/android
8 +CW_ZANO_EXTERNAL_DIR=${CW_DIR}/cw_zano/ios/External/android
9 for arch in "aarch" "aarch64" "i686" "x86_64"
10 do
11
@@ -40,5 +41,6 @@ done
41
42 mkdir -p ${CW_HAVEN_EXTERNAL_DIR}/include
43 mkdir -p ${CW_MONERO_EXTERNAL_DIR}/include
44 +mkdir -p ${CW_ZANO_EXTERNAL_DIR}/include
45
46 cp $CW_EXRTERNAL_DIR/x86/include/haven/wallet2_api.h ${CW_HAVEN_EXTERNAL_DIR}/include
scripts/android/finish_boost.sh
+2 -2
@@ -5,5 +5,5 @@ PREFIX=$2
5 BOOST_SRC_DIR=$3
6
7 cd $BOOST_SRC_DIR
8 -
9 -./b2 --build-type=minimal link=static runtime-link=static --with-chrono --with-date_time --with-filesystem --with-program_options --with-regex --with-serialization --with-system --with-thread --with-locale --build-dir=android --stagedir=android toolset=clang threading=multi threadapi=pthread target-os=android -sICONV_PATH=${PREFIX} -j$THREADS install
8 +echo "Building boost"
9 +./b2 --build-type=minimal link=static runtime-link=static --with-chrono --with-date_time --with-timer --with-filesystem --with-program_options --with-regex --with-serialization --with-system --with-thread --with-locale --with-log --build-dir=android --stagedir=android toolset=clang threading=multi threadapi=pthread target-os=android -sICONV_PATH=${PREFIX} -j$THREADS install
scripts/android/inject_app_details.sh
+1 -1
@@ -1,5 +1,5 @@
1 #!/bin/bash
2 -source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/universal_sed.sh"
2 +source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/functions.sh"
3 if [ -z "$APP_ANDROID_TYPE" ]; then
4 echo "Please set APP_ANDROID_TYPE"
5 exit 1
scripts/android/pubspec_gen.sh
+1 -1
@@ -10,7 +10,7 @@ case $APP_ANDROID_TYPE in
10 CONFIG_ARGS="--monero"
11 ;;
12 $CAKEWALLET)
13 - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero"
13 + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano"
14 if [ "$CW_WITH_HAVEN" = true ];then
15 CONFIG_ARGS="$CONFIG_ARGS --haven"
16 fi
scripts/functions.sh new
+33
@@ -0,0 +1,33 @@
1 +#!/bin/bash
2 +
3 +detect_sed() {
4 + if sed --version 2>/dev/null | grep -q "GNU"; then
5 + SED_TYPE="GNU"
6 + else
7 + SED_TYPE="BSD"
8 + fi
9 +}
10 +
11 +universal_sed() {
12 + local expression=$1
13 + local file=$2
14 +
15 + if [[ "$SED_TYPE" == "GNU" ]]; then
16 + sed -i "$expression" "$file"
17 + else
18 + sed -i '' "$expression" "$file"
19 + fi
20 +}
21 +
22 +detect_sed
23 +
24 +if [[ "$(uname)" == "Linux" ]];
25 +then
26 + export MAKE_JOB_COUNT="$(expr $(printf '%s\n%s' $(( $(grep MemTotal: /proc/meminfo | cut -d: -f2 | cut -dk -f1) * 4 / (1048576 * 9) )) $(nproc) | sort -n | head -n1) '|' 1)"
27 +elif [[ "$(uname)" == "Darwin" ]];
28 +then
29 + export MAKE_JOB_COUNT="(expr $(printf '%s\n%s' $(( $(sysctl -n hw.memsize) * 4 / (1073741824 * 9) )) $(sysctl -n hw.logicalcpu) | sort -n | head -n1) '|' 1)"
30 +else
31 + # Assume windows eh?
32 + export MAKE_JOB_COUNT="$(expr $(printf '%s\n%s' $(( $(grep MemTotal: /proc/meminfo | cut -d: -f2 | cut -dk -f1) * 4 / (1048576 * 9) )) $(nproc) | sort -n | head -n1) '|' 1)"
33 +fi
\ No newline at end of file
scripts/ios/app_config.sh
+2 -2
@@ -1,5 +1,5 @@
1 #!/bin/bash
2 -source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/universal_sed.sh"
2 +source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/functions.sh"
3 set -x -e
4 MONERO_COM="monero.com"
5 CAKEWALLET="cakewallet"
@@ -32,7 +32,7 @@ case $APP_IOS_TYPE in
32 CONFIG_ARGS="--monero"
33 ;;
34 $CAKEWALLET)
35 - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero"
35 + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano"
36 if [ "$CW_WITH_HAVEN" = true ];then
37 CONFIG_ARGS="$CONFIG_ARGS --haven"
38 fi
scripts/ios/build_boost.sh
+25 -6
@@ -1,23 +1,42 @@
1 -#!/bin/sh
1 +#!/bin/bash
2 +
3 +set -e
4
5 . ./config.sh
6
7 MIN_IOS_VERSION=10.0
8 BOOST_URL="https://github.com/cake-tech/Apple-Boost-BuildScript.git"
9 BOOST_DIR_PATH="${EXTERNAL_IOS_SOURCE_DIR}/Apple-Boost-BuildScript"
8 -BOOST_VERSION=1.72.0
10 +BOOST_VERSION=1.84.0
11 BOOST_LIBS="random regex graph random chrono thread filesystem system date_time locale serialization program_options"
12
13 echo "============================ Boost ============================"
14
15 echo "Cloning Apple-Boost-BuildScript from - $BOOST_URL"
14 -git clone -b build $BOOST_URL $BOOST_DIR_PATH
15 -cd $BOOST_DIR_PATH
16 +
17 +# Check if the directory already exists.
18 +if [ -d "$BOOST_DIR_PATH" ]; then
19 + echo "Boost directory already exists."
20 +else
21 + echo "Cloning Boost from $BOOST_URL"
22 + git clone -b build $BOOST_URL $BOOST_DIR_PATH
23 +fi
24 +
25 +# Verify if the repository was cloned successfully.
26 +if [ -d "$BOOST_DIR_PATH/.git" ]; then
27 + echo "Boost repository cloned successfully."
28 + cd $BOOST_DIR_PATH
29 + git checkout build
30 +else
31 + echo "Failed to clone Boost repository. Exiting."
32 + exit 1
33 +fi
34 +
35 ./boost.sh -ios \
36 --min-ios-version ${MIN_IOS_VERSION} \
37 --boost-libs "${BOOST_LIBS}" \
38 --boost-version ${BOOST_VERSION} \
39 --no-framework
40
22 -mv ${BOOST_DIR_PATH}/build/boost/${BOOST_VERSION}/ios/release/prefix/include/* $EXTERNAL_IOS_INCLUDE_DIR
23 -mv ${BOOST_DIR_PATH}/build/boost/${BOOST_VERSION}/ios/release/prefix/lib/* $EXTERNAL_IOS_LIB_DIR
\ No newline at end of file
41 +mv -f ${BOOST_DIR_PATH}/build/boost/${BOOST_VERSION}/ios/release/prefix/include/* $EXTERNAL_IOS_INCLUDE_DIR
42 +mv -f ${BOOST_DIR_PATH}/build/boost/${BOOST_VERSION}/ios/release/prefix/lib/* $EXTERNAL_IOS_LIB_DIR
\ No newline at end of file
scripts/ios/build_monero_all.sh
+6 -6
@@ -1,4 +1,5 @@
1 #!/bin/sh
2 +source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/functions.sh"
3
4 . ./config.sh
5 # ./install_missing_headers.sh
@@ -12,17 +13,16 @@ set -x -e
13
14 cd "$(dirname "$0")"
15
15 -NPROC="-j$(sysctl -n hw.logicalcpu)"
16 -
16 ../prepare_moneroc.sh
17
19 -for COIN in monero wownero;
18 +for COIN in monero wownero zano;
19 do
20 pushd ../monero_c
21 rm -rf external/ios/build
23 - ./build_single.sh ${COIN} host-apple-ios $NPROC
22 + ./build_single.sh ${COIN} aarch64-apple-ios -j$NPROC
23 popd
24 done
25
27 -unxz -f ../monero_c/release/monero/host-apple-ios_libwallet2_api_c.dylib.xz
28 -unxz -f ../monero_c/release/wownero/host-apple-ios_libwallet2_api_c.dylib.xz
26 +unxz -f ../monero_c/release/monero/aarch64-apple-ios_libwallet2_api_c.dylib.xz
27 +unxz -f ../monero_c/release/wownero/aarch64-apple-ios_libwallet2_api_c.dylib.xz
28 +unxz -f ../monero_c/release/zano/aarch64-apple-ios_libwallet2_api_c.dylib.xz
scripts/ios/build_zano.sh new
+106
@@ -0,0 +1,106 @@
1 +#!/bin/sh
2 +
3 +. ./config.sh
4 +
5 +ZANO_URL="https://github.com/hyle-team/zano.git"
6 +ZANO_DIR_PATH="${EXTERNAL_IOS_SOURCE_DIR}/zano"
7 +ZANO_VERSION=fde28efdc5d7efe8741dcb0e62ea0aebc805a373
8 +
9 +
10 +IOS_TOOLCHAIN_DIR_PATH="${EXTERNAL_IOS_SOURCE_DIR}/ios_toolchain"
11 +IOS_TOOLCHAIN_URL="https://github.com/leetal/ios-cmake.git"
12 +IOS_TOOLCHAIN_VERSION=06465b27698424cf4a04a5ca4904d50a3c966c45
13 +
14 +export NO_DEFAULT_PATH
15 +
16 +BUILD_TYPE=release
17 +PREFIX=${EXTERNAL_IOS_DIR}
18 +DEST_LIB_DIR=${EXTERNAL_IOS_LIB_DIR}/zano
19 +DEST_INCLUDE_DIR=${EXTERNAL_IOS_INCLUDE_DIR}/zano
20 +
21 +ZANO_MOBILE_IOS_BUILD_FOLDER_ARM64="${ZANO_DIR_PATH}/build"
22 +ZANO_MOBILE_IOS_INSTALL_FOLDER_ARM64="${ZANO_DIR_PATH}/install"
23 +
24 +echo "ZANO_URL: $ZANO_URL"
25 +echo "IOS_TOOLCHAIN_DIR_PATH: $IOS_TOOLCHAIN_DIR_PATH"
26 +echo "ZANO_MOBILE_IOS_BUILD_FOLDER_ARM64: $ZANO_MOBILE_IOS_BUILD_FOLDER_ARM64"
27 +echo "ZANO_MOBILE_IOS_INSTALL_FOLDER_ARM64: $ZANO_MOBILE_IOS_INSTALL_FOLDER_ARM64"
28 +echo "PREFIX: $PREFIX"
29 +echo "DEST_LIB_DIR: $DEST_LIB_DIR"
30 +echo "DEST_INCLUDE_DIR: $DEST_INCLUDE_DIR"
31 +echo "ZANO_DIR_PATH: $ZANO_DIR_PATH"
32 +
33 +echo "Cloning ios_toolchain from - $IOS_TOOLCHAIN_URL to - $IOS_TOOLCHAIN_DIR_PATH"
34 +git clone $IOS_TOOLCHAIN_URL $IOS_TOOLCHAIN_DIR_PATH
35 +cd $IOS_TOOLCHAIN_DIR_PATH
36 +git checkout $IOS_TOOLCHAIN_VERSION
37 +git submodule update --init --force
38 +cd ..
39 +
40 +echo "Cloning zano from - $ZANO_URL to - $ZANO_DIR_PATH"
41 +git clone $ZANO_URL $ZANO_DIR_PATH
42 +cd $ZANO_DIR_PATH
43 +git fetch origin
44 +if [ $? -ne 0 ]; then
45 + echo "Failed to perform command"
46 + exit 1
47 +fi
48 +git checkout $ZANO_VERSION
49 +if [ $? -ne 0 ]; then
50 + echo "Failed to perform command"
51 + exit 1
52 +fi
53 +git submodule update --init --force
54 +if [ $? -ne 0 ]; then
55 + echo "Failed to perform command"
56 + exit 1
57 +fi
58 +mkdir -p build
59 +cd ..
60 +
61 +
62 +export CMAKE_INCLUDE_PATH="${PREFIX}/include"
63 +export CMAKE_LIBRARY_PATH="${PREFIX}/lib"
64 +
65 +
66 +rm -rf ${ZANO_MOBILE_IOS_BUILD_FOLDER_ARM64} > /dev/null
67 +rm -rf ${ZANO_MOBILE_IOS_INSTALL_FOLDER_ARM64} > /dev/null
68 +
69 +echo "CMAKE_INCLUDE_PATH: $CMAKE_INCLUDE_PATH"
70 +echo "CMAKE_LIBRARY_PATH: $CMAKE_LIBRARY_PATH"
71 +echo "ROOT_DIR: $ROOT_DIR"
72 +
73 +
74 +cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE \
75 + -DCMAKE_TOOLCHAIN_FILE="${IOS_TOOLCHAIN_DIR_PATH}/ios.toolchain.cmake" \
76 + -DPLATFORM=OS64 \
77 + -S"${ZANO_DIR_PATH}" \
78 + -B"${ZANO_MOBILE_IOS_BUILD_FOLDER_ARM64}" \
79 + -GXcode \
80 + -DCAKEWALLET=TRUE \
81 + -DSKIP_BOOST_FATLIB_LIB=TRUE \
82 + -DCMAKE_SYSTEM_NAME=iOS \
83 + -DCMAKE_INSTALL_PREFIX="${ZANO_MOBILE_IOS_INSTALL_FOLDER_ARM64}" \
84 + -DCMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH=NO \
85 + -DCMAKE_CXX_FLAGS="-Wno-enum-constexpr-conversion" \
86 + -DDISABLE_TOR=TRUE
87 +
88 +# -DCMAKE_OSX_ARCHITECTURES="arm64"
89 +# -DCMAKE_IOS_INSTALL_COMBINED=YES
90 +
91 +if [ $? -ne 0 ]; then
92 + echo "Failed to perform command"
93 + exit 1
94 +fi
95 +
96 +cmake --build "${ZANO_MOBILE_IOS_BUILD_FOLDER_ARM64}" --config $BUILD_TYPE --target install -- -j 4
97 +if [ $? -ne 0 ]; then
98 + echo "Failed to perform command"
99 + exit 1
100 +fi
101 +
102 +mkdir -p $DEST_LIB_DIR
103 +mkdir -p $DEST_INCLUDE_DIR
104 +
105 +cp ${ZANO_MOBILE_IOS_INSTALL_FOLDER_ARM64}/lib/* $DEST_LIB_DIR
106 +cp ${ZANO_DIR_PATH}/src/wallet/plain_wallet_api.h $DEST_INCLUDE_DIR
scripts/ios/build_zano_all.sh new
+7
@@ -0,0 +1,7 @@
1 +#!/bin/sh
2 +
3 +. ./config.sh
4 +./install_missing_headers.sh
5 +./build_openssl.sh
6 +./build_boost.sh
7 +./build_zano.sh
scripts/ios/gen_framework.sh
+16
@@ -29,3 +29,19 @@ cd $FRWK_DIR # go to iOS framework dir
29 lipo -create $DYLIB_LINK_PATH -output WowneroWallet
30
31 echo "Generated ${FRWK_DIR}"
32 +
33 +# also generate for zano
34 +IOS_DIR="$(pwd)/../../ios"
35 +DYLIB_NAME="zano_libwallet2_api_c.dylib"
36 +DYLIB_LINK_PATH="${IOS_DIR}/${DYLIB_NAME}"
37 +FRWK_DIR="${IOS_DIR}/ZanoWallet.framework"
38 +
39 +if [ ! -f $DYLIB_LINK_PATH ]; then
40 + echo "Dylib is not found by the link: ${DYLIB_LINK_PATH}"
41 + exit 0
42 +fi
43 +
44 +cd $FRWK_DIR # go to iOS framework dir
45 +lipo -create $DYLIB_LINK_PATH -output ZanoWallet
46 +
47 +echo "Generated ${FRWK_DIR}"
scripts/ios/setup.sh
+12 -1
@@ -13,16 +13,21 @@ fi
13 libtool -static -o libboost.a ./libboost_*.a
14 libtool -static -o libhaven.a ./haven/*.a
15 libtool -static -o libmonero.a ./monero/*.a
16 +libtool -static -o libzano.a ./zano/*.a
17
18 CW_HAVEN_EXTERNAL_LIB=../../../../../cw_haven/ios/External/ios/lib
19 CW_HAVEN_EXTERNAL_INCLUDE=../../../../../cw_haven/ios/External/ios/include
20 CW_MONERO_EXTERNAL_LIB=../../../../../cw_monero/ios/External/ios/lib
21 CW_MONERO_EXTERNAL_INCLUDE=../../../../../cw_monero/ios/External/ios/include
22 +CW_ZANO_EXTERNAL_LIB=../../../../../cw_zano/ios/External/ios/lib
23 +CW_ZANO_EXTERNAL_INCLUDE=../../../../../cw_zano/ios/External/ios/include
24
25 mkdir -p $CW_HAVEN_EXTERNAL_INCLUDE
26 mkdir -p $CW_MONERO_EXTERNAL_INCLUDE
27 +mkdir -p $CW_ZANO_EXTERNAL_INCLUDE
28 mkdir -p $CW_HAVEN_EXTERNAL_LIB
29 mkdir -p $CW_MONERO_EXTERNAL_LIB
30 +mkdir -p $CW_ZANO_EXTERNAL_LIB
31
32 ln ./libboost.a ${CW_HAVEN_EXTERNAL_LIB}/libboost.a
33 ln ./libcrypto.a ${CW_HAVEN_EXTERNAL_LIB}/libcrypto.a
@@ -37,4 +42,10 @@ ln ./libssl.a ${CW_MONERO_EXTERNAL_LIB}/libssl.a
42 ln ./libsodium.a ${CW_MONERO_EXTERNAL_LIB}/libsodium.a
43 ln ./libunbound.a ${CW_MONERO_EXTERNAL_LIB}/libunbound.a
44 cp ./libmonero.a $CW_MONERO_EXTERNAL_LIB
40 -cp ../include/monero/* $CW_MONERO_EXTERNAL_INCLUDE
\ No newline at end of file
45 +cp ../include/monero/* $CW_MONERO_EXTERNAL_INCLUDE
46 +
47 +ln ./libboost.a ${CW_ZANO_EXTERNAL_LIB}/libboost.a
48 +ln ./libcrypto.a ${CW_ZANO_EXTERNAL_LIB}/libcrypto.a
49 +ln ./libssl.a ${CW_ZANO_EXTERNAL_LIB}/libssl.a
50 +cp ./libzano.a $CW_ZANO_EXTERNAL_LIB
51 +cp ../include/zano/* $CW_ZANO_EXTERNAL_INCLUDE
scripts/linux/.fvmrc new
+3
@@ -0,0 +1,3 @@
1 +{
2 + "flutter": "3.24.0"
3 +}
\ No newline at end of file
scripts/linux/.gitignore new
+3
@@ -0,0 +1,3 @@
1 +
2 +# FVM Version Cache
3 +.fvm/
\ No newline at end of file
scripts/linux/Dockerfile.linux
+2
@@ -53,6 +53,8 @@ RUN set -o xtrace \
53 && apt-get install -y udev qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils \
54 # for linux tests
55 && apt-get install -y xvfb network-manager ffmpeg x11-utils \
56 + # for aarch64-linux-gnu
57 + && apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu \
58 && rm -rf /var/lib/apt/lists/* \
59 && sh -c 'echo "en_US.UTF-8 UTF-8" > /etc/locale.gen' \
60 && locale-gen \
scripts/linux/app_config.sh
+1 -1
@@ -1,5 +1,5 @@
1 #!/bin/bash
2 -source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/universal_sed.sh"
2 +source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/functions.sh"
3 CAKEWALLET="cakewallet"
4 DIR=`pwd`
5
scripts/linux/build_monero_all.sh
+4 -5
@@ -1,23 +1,22 @@
1 #!/bin/bash
2 +source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/functions.sh"
3
4 set -x -e
5
6 cd "$(dirname "$0")"
7
7 -NPROC="-j$(nproc)"
8 -
8 ../prepare_moneroc.sh
9
11 -for COIN in monero wownero;
10 +for COIN in monero wownero zano;
11 do
12 pushd ../monero_c
14 - for target in x86_64-linux-gnu
13 + for target in x86_64-linux-gnu # aarch64-linux-gnu
14 do
15 if [[ -f "release/${COIN}/${target}_libwallet2_api_c.so" ]];
16 then
17 echo "file exist, not building monero_c for ${COIN}/$target.";
18 else
20 - ./build_single.sh ${COIN} $target $NPROC
19 + ./build_single.sh ${COIN} $target -j$MAKE_JOB_COUNT
20 unxz -f ../monero_c/release/${COIN}/${target}_libwallet2_api_c.so.xz
21 fi
22 done
scripts/macos/app_config.sh
+2 -2
@@ -1,5 +1,5 @@
1 #!/bin/bash
2 -source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/universal_sed.sh"
2 +source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/functions.sh"
3
4 MONERO_COM="monero.com"
5 CAKEWALLET="cakewallet"
@@ -36,7 +36,7 @@ case $APP_MACOS_TYPE in
36 $MONERO_COM)
37 CONFIG_ARGS="--monero";;
38 $CAKEWALLET)
39 - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero";; #--haven
39 + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano";; #--haven
40 esac
41
42 cp -rf pubspec_description.yaml pubspec.yaml
scripts/macos/build_monero_all.sh
+3 -15
@@ -22,11 +22,7 @@ then
22 popd
23 done
24 else
25 - if [[ "x$1" == "xuniversal" ]]; then
25 ARCHS=(x86_64 arm64)
27 - else
28 - ARCHS=$(uname -m)
29 - fi
26 for COIN in monero wownero;
27 do
28 MONERO_LIBS=""
@@ -34,24 +30,16 @@ else
30 for ARCH in "${ARCHS[@]}";
31 do
32 if [[ "$ARCH" == "arm64" ]]; then
37 - export HOMEBREW_PREFIX=/opt/homebrew
38 - HOST="aarch64-host-apple-darwin"
33 + HOST="aarch64-apple-darwin"
34 else
40 - export HOMEBREW_PREFIX=/usr/local
41 - HOST="${ARCH}-host-apple-darwin"
35 + HOST="x86_64-apple-darwin"
36 fi
37
38 MONERO_LIBS="$MONERO_LIBS -arch ${ARCH} ${MONEROC_RELEASE_DIR}/${HOST}_libwallet2_api_c.dylib"
39 WOWNERO_LIBS="$WOWNERO_LIBS -arch ${ARCH} ${WOWNEROC_RELEASE_DIR}/${HOST}_libwallet2_api_c.dylib"
40
47 - if [[ ! $(uname -m) == $ARCH ]]; then
48 - PRC="arch -${ARCH}"
49 - else
50 - PRC=""
51 - fi
52 -
41 pushd ../monero_c
54 - $PRC ./build_single.sh ${COIN} ${HOST} $NPROC
42 + ./build_single.sh ${COIN} ${HOST} $NPROC
43 unxz -f ./release/${COIN}/${HOST}_libwallet2_api_c.dylib.xz
44 popd
45 done
scripts/macos/gen_common.sh
+1 -1
@@ -1,5 +1,5 @@
1 #!/bin/sh
2 -source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/universal_sed.sh"
2 +source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/functions.sh"
3 gen_podspec() {
4 ARCH=$1
5 CW_PLUGIN_DIR="`pwd`/../../cw_monero/macos"
scripts/prepare_moneroc.sh
+9 -10
@@ -8,24 +8,23 @@ if [[ ! -d "monero_c/.git" ]];
8 then
9 git clone https://github.com/mrcyjanek/monero_c --branch master monero_c
10 cd monero_c
11 - git checkout af5277f96073917185864d3596e82b67bee54e78
11 + git checkout 9526921acb774b523a2e1d9ba9a7b389acfc6b70
12 git reset --hard
13 git submodule update --init --force --recursive
14 ./apply_patches.sh monero
15 ./apply_patches.sh wownero
16 + ./apply_patches.sh zano
17 else
18 cd monero_c
19 fi
20
20 -if [[ ! -f "monero/.patch-applied" ]];
21 -then
22 - ./apply_patches.sh monero
23 -fi
24 -
25 -if [[ ! -f "wownero/.patch-applied" ]];
26 -then
27 - ./apply_patches.sh wownero
28 -fi
21 +for coin in monero wownero zano;
22 +do
23 + if [[ ! -f "$coin/.patch-applied" ]];
24 + then
25 + ./apply_patches.sh $coin
26 + fi
27 +done
28 cd ..
29
30 echo "monero_c source prepared".
scripts/universal_sed.sh deleted
-22
@@ -1,22 +0,0 @@
1 -#!/bin/bash
2 -
3 -detect_sed() {
4 - if sed --version 2>/dev/null | grep -q "GNU"; then
5 - SED_TYPE="GNU"
6 - else
7 - SED_TYPE="BSD"
8 - fi
9 -}
10 -
11 -universal_sed() {
12 - local expression=$1
13 - local file=$2
14 -
15 - if [[ "$SED_TYPE" == "GNU" ]]; then
16 - sed -i "$expression" "$file"
17 - else
18 - sed -i '' "$expression" "$file"
19 - fi
20 -}
21 -
22 -detect_sed
scripts/windows/build_all.sh
+6 -5
@@ -1,4 +1,6 @@
1 +#!/bin/bash
2 set -x -e
3 +source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/functions.sh"
4
5 cd "$(dirname "$0")"
6
@@ -16,19 +18,18 @@ pushd ../monero_c
18 set +e
19 command -v sudo && export SUDO=sudo
20 set -e
19 - NPROC="-j$(nproc)"
21 if [[ ! "x$USE_DOCKER" == "x" ]];
22 then
23 for COIN in monero wownero;
24 do
24 - $SUDO docker run --platform linux/amd64 -v$HOME/.cache/ccache:/root/.ccache -v$PWD:$PWD -w $PWD --rm -it git.mrcyjanek.net/mrcyjanek/debian:buster bash -c "git config --global --add safe.directory '*'; apt update; apt install -y ccache gcc-mingw-w64-x86-64 g++-mingw-w64-x86-64 gperf libtinfo5; ./build_single.sh ${COIN} x86_64-w64-mingw32 $NPROC"
25 - # $SUDO docker run --platform linux/amd64 -v$HOME/.cache/ccache:/root/.ccache -v$PWD:$PWD -w $PWD --rm -it git.mrcyjanek.net/mrcyjanek/debian:buster bash -c "git config --global --add safe.directory '*'; apt update; apt install -y ccache gcc-mingw-w64-i686 g++-mingw-w64-i686 gperf libtinfo5; ./build_single.sh ${COIN} i686-w64-mingw32 $NPROC"
25 + $SUDO docker run --platform linux/amd64 -v$HOME/.cache/ccache:/root/.ccache -v$PWD:$PWD -w $PWD --rm -it git.mrcyjanek.net/mrcyjanek/debian:buster bash -c "git config --global --add safe.directory '*'; apt update; apt install -y ccache gcc-mingw-w64-x86-64 g++-mingw-w64-x86-64 gperf libtinfo5; ./build_single.sh ${COIN} x86_64-w64-mingw32 -j$MAKE_JOB_COUNT"
26 + # $SUDO docker run --platform linux/amd64 -v$HOME/.cache/ccache:/root/.ccache -v$PWD:$PWD -w $PWD --rm -it git.mrcyjanek.net/mrcyjanek/debian:buster bash -c "git config --global --add safe.directory '*'; apt update; apt install -y ccache gcc-mingw-w64-i686 g++-mingw-w64-i686 gperf libtinfo5; ./build_single.sh ${COIN} i686-w64-mingw32 -j$MAKE_JOB_COUNT"
27 done
28 else
29 for COIN in monero wownero;
30 do
30 - $SUDO ./build_single.sh ${COIN} x86_64-w64-mingw32 $NPROC
31 - # $SUDO ./build_single.sh ${COIN} i686-w64-mingw32 $NPROC
31 + $SUDO ./build_single.sh ${COIN} x86_64-w64-mingw32 -j$MAKE_JOB_COUNT
32 + # $SUDO ./build_single.sh ${COIN} i686-w64-mingw32 -j$MAKE_JOB_COUNT
33 done
34 fi
35 popd
tool/configure.dart
+91 -2
@@ -10,6 +10,7 @@ const polygonOutputPath = 'lib/polygon/polygon.dart';
10 const solanaOutputPath = 'lib/solana/solana.dart';
11 const tronOutputPath = 'lib/tron/tron.dart';
12 const wowneroOutputPath = 'lib/wownero/wownero.dart';
13 +const zanoOutputPath = 'lib/zano/zano.dart';
14 const walletTypesPath = 'lib/wallet_types.g.dart';
15 const secureStoragePath = 'lib/core/secure_storage.dart';
16 const pubspecDefaultPath = 'pubspec_default.yaml';
@@ -28,6 +29,7 @@ Future<void> main(List<String> args) async {
29 final hasSolana = args.contains('${prefix}solana');
30 final hasTron = args.contains('${prefix}tron');
31 final hasWownero = args.contains('${prefix}wownero');
32 + final hasZano = args.contains('${prefix}zano');
33 final excludeFlutterSecureStorage = args.contains('${prefix}excludeFlutterSecureStorage');
34
35 await generateBitcoin(hasBitcoin);
@@ -40,6 +42,7 @@ Future<void> main(List<String> args) async {
42 await generateSolana(hasSolana);
43 await generateTron(hasTron);
44 await generateWownero(hasWownero);
45 + await generateZano(hasZano);
46 // await generateBanano(hasEthereum);
47
48 await generatePubspec(
@@ -55,6 +58,7 @@ Future<void> main(List<String> args) async {
58 hasSolana: hasSolana,
59 hasTron: hasTron,
60 hasWownero: hasWownero,
61 + hasZano: hasZano,
62 );
63 await generateWalletTypes(
64 hasMonero: hasMonero,
@@ -68,6 +72,7 @@ Future<void> main(List<String> args) async {
72 hasSolana: hasSolana,
73 hasTron: hasTron,
74 hasWownero: hasWownero,
75 + hasZano: hasZano,
76 );
77 await injectSecureStorage(!excludeFlutterSecureStorage);
78 }
@@ -399,7 +404,7 @@ abstract class Monero {
404 required String password,
405 required String language,
406 required int height});
402 - WalletCredentials createMoneroRestoreWalletFromSeedCredentials({required String name, required String password, required int height, required String mnemonic});
407 + WalletCredentials createMoneroRestoreWalletFromSeedCredentials({required String name, required String password, required String passphrase, required int height, required String mnemonic});
408 WalletCredentials createMoneroRestoreWalletFromHardwareCredentials({required String name, required String password, required int height, required ledger.LedgerConnection ledgerConnection});
409 WalletCredentials createMoneroNewWalletCredentials({required String name, required String language, required bool isPolyseed, String? password});
410 Map<String, String> getKeys(Object wallet);
@@ -589,7 +594,7 @@ abstract class Wownero {
594 required String password,
595 required String language,
596 required int height});
592 - WalletCredentials createWowneroRestoreWalletFromSeedCredentials({required String name, required String password, required int height, required String mnemonic});
597 + WalletCredentials createWowneroRestoreWalletFromSeedCredentials({required String name, required String password, required String passphrase, required int height, required String mnemonic});
598 WalletCredentials createWowneroNewWalletCredentials({required String name, required String language, required bool isPolyseed, String? password});
599 Map<String, String> getKeys(Object wallet);
600 Object createWowneroTransactionCreationCredentials({required List<Output> outputs, required TransactionPriority priority});
@@ -1400,6 +1405,76 @@ abstract class Tron {
1405 await outputFile.writeAsString(output);
1406 }
1407
1408 +Future<void> generateZano(bool hasImplementation) async {
1409 + final outputFile = File(zanoOutputPath);
1410 + const zanoCommonHeaders = """
1411 +import 'package:cake_wallet/utils/language_list.dart';
1412 +import 'package:cake_wallet/view_model/send/output.dart';
1413 +import 'package:collection/collection.dart';
1414 +import 'package:cw_core/crypto_currency.dart';
1415 +import 'package:cw_core/monero_transaction_priority.dart';
1416 +import 'package:cw_core/output_info.dart';
1417 +import 'package:cw_core/transaction_history.dart';
1418 +import 'package:cw_core/transaction_info.dart';
1419 +import 'package:cw_core/transaction_priority.dart';
1420 +import 'package:cw_core/wallet_base.dart';
1421 +import 'package:cw_core/wallet_credentials.dart';
1422 +import 'package:cw_core/wallet_info.dart';
1423 +import 'package:cw_core/wallet_service.dart';
1424 +import 'package:cw_core/zano_asset.dart';
1425 +import 'package:hive/hive.dart';
1426 +""";
1427 + const zanoCWHeaders = """
1428 +import 'package:cw_zano/mnemonics/english.dart';
1429 +import 'package:cw_zano/model/zano_transaction_credentials.dart';
1430 +import 'package:cw_zano/model/zano_transaction_info.dart';
1431 +import 'package:cw_zano/zano_formatter.dart';
1432 +import 'package:cw_zano/zano_wallet.dart';
1433 +import 'package:cw_zano/zano_wallet_service.dart';
1434 +import 'package:cw_zano/zano_utils.dart';
1435 +""";
1436 + const zanoCwPart = "part 'cw_zano.dart';";
1437 + const zanoContent = """
1438 +abstract class Zano {
1439 + TransactionPriority getDefaultTransactionPriority();
1440 + TransactionPriority deserializeMoneroTransactionPriority({required int raw});
1441 + List<TransactionPriority> getTransactionPriorities();
1442 + List<String> getWordList(String language);
1443 +
1444 + WalletCredentials createZanoRestoreWalletFromSeedCredentials({required String name, required String password, required String passphrase, required int height, required String mnemonic});
1445 + WalletCredentials createZanoNewWalletCredentials({required String name, required String? password});
1446 + Object createZanoTransactionCredentials({required List<Output> outputs, required TransactionPriority priority, required CryptoCurrency currency});
1447 + double formatterIntAmountToDouble({required int amount, required CryptoCurrency currency, required bool forFee});
1448 + int formatterParseAmount({required String amount, required CryptoCurrency currency});
1449 + WalletService createZanoWalletService(Box<WalletInfo> walletInfoSource);
1450 + CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo tx);
1451 + List<ZanoAsset> getZanoAssets(WalletBase wallet);
1452 + String getZanoAssetAddress(CryptoCurrency asset);
1453 + Future<void> changeZanoAssetAvailability(WalletBase wallet, CryptoCurrency token);
1454 + Future<CryptoCurrency> addZanoAssetById(WalletBase wallet, String assetId);
1455 + Future<void> deleteZanoAsset(WalletBase wallet, CryptoCurrency token);
1456 + Future<CryptoCurrency?> getZanoAsset(WalletBase wallet, String contractAddress);
1457 + String getAddress(WalletBase wallet);
1458 + bool validateAddress(String address);
1459 +}
1460 +""";
1461 + const zanoEmptyDefinition = 'Zano? zano;\n';
1462 + const zanoCWDefinition = 'Zano? zano = CWZano();\n';
1463 +
1464 + final output = '$zanoCommonHeaders\n' +
1465 + (hasImplementation ? '$zanoCWHeaders\n' : '\n') +
1466 + (hasImplementation ? '$zanoCwPart\n\n' : '\n') +
1467 + (hasImplementation ? zanoCWDefinition : zanoEmptyDefinition) +
1468 + '\n' +
1469 + zanoContent;
1470 +
1471 + if (outputFile.existsSync()) {
1472 + await outputFile.delete();
1473 + }
1474 +
1475 + await outputFile.writeAsString(output);
1476 +}
1477 +
1478 Future<void> generatePubspec({
1479 required bool hasMonero,
1480 required bool hasBitcoin,
@@ -1413,6 +1488,7 @@ Future<void> generatePubspec({
1488 required bool hasSolana,
1489 required bool hasTron,
1490 required bool hasWownero,
1491 + required bool hasZano,
1492 }) async {
1493 const cwCore = """
1494 cw_core:
@@ -1477,6 +1553,10 @@ Future<void> generatePubspec({
1553 cw_wownero:
1554 path: ./cw_wownero
1555 """;
1556 + const cwZano = """
1557 + cw_zano:
1558 + path: ./cw_zano
1559 + """;
1560 final inputFile = File(pubspecOutputPath);
1561 final inputText = await inputFile.readAsString();
1562 final inputLines = inputText.split('\n');
@@ -1538,6 +1618,10 @@ Future<void> generatePubspec({
1618 output += '\n$cwWownero';
1619 }
1620
1621 + if (hasZano) {
1622 + output += '\n$cwZano';
1623 + }
1624 +
1625 final outputLines = output.split('\n');
1626 inputLines.insertAll(dependenciesIndex + 1, outputLines);
1627 final outputContent = inputLines.join('\n');
@@ -1562,6 +1646,7 @@ Future<void> generateWalletTypes({
1646 required bool hasSolana,
1647 required bool hasTron,
1648 required bool hasWownero,
1649 + required bool hasZano,
1650 }) async {
1651 final walletTypesFile = File(walletTypesPath);
1652
@@ -1609,6 +1694,10 @@ Future<void> generateWalletTypes({
1694 outputContent += '\tWalletType.nano,\n';
1695 }
1696
1697 + if (hasZano) {
1698 + outputContent += '\tWalletType.zano,\n';
1699 + }
1700 +
1701 if (hasBanano) {
1702 outputContent += '\tWalletType.banano,\n';
1703 }
tool/download_moneroc_prebuilds.dart
+1 -1
@@ -23,7 +23,7 @@ final List<String> triplets = [
23 // "host-apple-darwin", // not available on CI (yet)
24 // "x86_64-host-apple-darwin", // not available on CI (yet)
25 "aarch64-host-apple-darwin", // apple silicon macbooks (local builds)
26 - "host-apple-ios",
26 + "aarch64-apple-ios",
27 ];
28
29 Future<void> main() async {
windows/CMakeLists.txt
-2
@@ -87,8 +87,6 @@ install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/monero_c/release/monero/x8
87 install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/monero_c/release/wownero/x86_64-w64-mingw32_libwallet2_api_c.dll" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" RENAME "wownero_libwallet2_api_c.dll"
88 COMPONENT Runtime)
89
90 -install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/monero_c/release/monero/x86_64-w64-mingw32_libpolyseed.dll" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" RENAME "libpolyseed.dll"
91 - COMPONENT Runtime)
90
91 install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/monero_c/release/monero/x86_64-w64-mingw32_libssp-0.dll" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" RENAME "libssp-0.dll"
92 COMPONENT Runtime)