diff --git a/.gitignore b/.gitignore index 970241189f..c431a7f603 100644 --- a/.gitignore +++ b/.gitignore @@ -126,7 +126,7 @@ cw_shared_external/ios/External/ cw_haven/ios/External/ cw_haven/android/.externalNativeBuild/ cw_haven/android/.cxx/ - +cw_zano/ios/External/ lib/bitcoin/bitcoin.dart lib/monero/monero.dart lib/haven/haven.dart @@ -137,6 +137,7 @@ lib/polygon/polygon.dart lib/solana/solana.dart lib/tron/tron.dart lib/wownero/wownero.dart +lib/zano/zano.dart ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png @@ -179,3 +180,5 @@ scripts/monero_c # iOS generated framework bin ios/MoneroWallet.framework/MoneroWallet ios/WowneroWallet.framework/WowneroWallet +ios/ZanoWallet.framework/ZanoWallet +*_libwallet2_api_c.dylib diff --git a/analysis_options.yaml b/analysis_options.yaml index be68a4f26d..bd35233ba5 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -22,6 +22,7 @@ analyzer: lib/solana/cw_solana.dart, lib/tron/cw_tron.dart, lib/wownero/cw_wownero.dart, + lib/zano/cw_zano.dart, ] language: strict-casts: true diff --git a/android/app/src/main/AndroidManifestBase.xml b/android/app/src/main/AndroidManifestBase.xml index a92f493df3..5a1824a17e 100644 --- a/android/app/src/main/AndroidManifestBase.xml +++ b/android/app/src/main/AndroidManifestBase.xml @@ -89,6 +89,9 @@ + + + diff --git a/android/app/src/main/jniLibs/arm64-v8a/libzano_libwallet2_api_c.so b/android/app/src/main/jniLibs/arm64-v8a/libzano_libwallet2_api_c.so new file mode 120000 index 0000000000..49ddd0f47b --- /dev/null +++ b/android/app/src/main/jniLibs/arm64-v8a/libzano_libwallet2_api_c.so @@ -0,0 +1 @@ +../../../../../../scripts/monero_c/release/zano/aarch64-linux-android_libwallet2_api_c.so \ No newline at end of file diff --git a/android/app/src/main/jniLibs/armeabi-v7a/libzano_libwallet2_api_c.so b/android/app/src/main/jniLibs/armeabi-v7a/libzano_libwallet2_api_c.so new file mode 120000 index 0000000000..43f9b98b2f --- /dev/null +++ b/android/app/src/main/jniLibs/armeabi-v7a/libzano_libwallet2_api_c.so @@ -0,0 +1 @@ +../../../../../../scripts/monero_c/release/zano/armv7a-linux-androideabi_libwallet2_api_c.so \ No newline at end of file diff --git a/android/app/src/main/jniLibs/x86_64/libzano_libwallet2_api_c.so b/android/app/src/main/jniLibs/x86_64/libzano_libwallet2_api_c.so new file mode 120000 index 0000000000..8c37d73c27 --- /dev/null +++ b/android/app/src/main/jniLibs/x86_64/libzano_libwallet2_api_c.so @@ -0,0 +1 @@ +../../../../../../scripts/monero_c/release/zano/x86_64-linux-android_libwallet2_api_c.so \ No newline at end of file diff --git a/assets/images/zano_icon.png b/assets/images/zano_icon.png new file mode 100644 index 0000000000..ff3cb023c0 Binary files /dev/null and b/assets/images/zano_icon.png differ diff --git a/assets/zano_node_list.yml b/assets/zano_node_list.yml new file mode 100644 index 0000000000..9ee602d82b --- /dev/null +++ b/assets/zano_node_list.yml @@ -0,0 +1,4 @@ +- + uri: 37.27.100.59:10500 + is_default: true + useSSL: false \ No newline at end of file diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index c65f056bbf..6a8e7d5c4a 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -316,6 +316,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.10" + decimal: + dependency: transitive + description: + name: decimal + sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21" + url: "https://pub.dev" + source: hosted + version: "2.3.3" encrypt: dependency: transitive description: @@ -797,6 +805,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.2" + rational: + dependency: transitive + description: + name: rational + sha256: cb808fb6f1a839e6fc5f7d8cb3b0a10e1db48b3be102de73938c627f0b636336 + url: "https://pub.dev" + source: hosted + version: "2.2.3" rxdart: dependency: "direct main" description: diff --git a/cw_core/lib/amount_converter.dart b/cw_core/lib/amount_converter.dart index 1c5456b07d..71d0cef429 100644 --- a/cw_core/lib/amount_converter.dart +++ b/cw_core/lib/amount_converter.dart @@ -1,3 +1,5 @@ +import 'package:decimal/decimal.dart'; +import 'package:decimal/intl.dart'; import 'package:intl/intl.dart'; import 'package:cw_core/crypto_currency.dart'; @@ -43,6 +45,8 @@ class AmountConverter { case CryptoCurrency.xnzd: case CryptoCurrency.xusd: return _moneroAmountToString(amount); + case CryptoCurrency.zano: + return _moneroAmountToStringUsingDecimals(amount); default: return ''; } @@ -59,4 +63,10 @@ class AmountConverter { static String _wowneroAmountToString(int amount) => _wowneroAmountFormat .format(cryptoAmountToDouble(amount: amount, divider: _wowneroAmountDivider)); + + static Decimal cryptoAmountToDecimal({required int amount, required int divider}) => + (Decimal.fromInt(amount) / Decimal.fromInt(divider)).toDecimal(); + + static String _moneroAmountToStringUsingDecimals(int amount) => _moneroAmountFormat.format( + DecimalIntl(cryptoAmountToDecimal(amount: amount, divider: _moneroAmountDivider))); } diff --git a/cw_core/lib/crypto_currency.dart b/cw_core/lib/crypto_currency.dart index 0280bb45af..970f2ebf60 100644 --- a/cw_core/lib/crypto_currency.dart +++ b/cw_core/lib/crypto_currency.dart @@ -106,6 +106,7 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen CryptoCurrency.usdcTrc20, CryptoCurrency.tbtc, CryptoCurrency.wow, + CryptoCurrency.zano, CryptoCurrency.ton, ]; @@ -225,7 +226,7 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen static const tbtc = CryptoCurrency(title: 'tBTC', fullName: 'Testnet Bitcoin', raw: 93, name: 'tbtc', iconPath: 'assets/images/tbtc.png', decimals: 8); static const wow = CryptoCurrency(title: 'WOW', fullName: 'Wownero', raw: 94, name: 'wow', iconPath: 'assets/images/wownero_icon.png', decimals: 11); static const ton = CryptoCurrency(title: 'TON', fullName: 'Toncoin', raw: 95, name: 'ton', iconPath: 'assets/images/ton_icon.png', decimals: 8); - + static const zano = CryptoCurrency(title: 'ZANO', tag: 'ZANO', fullName: 'Zano', raw: 96, name: 'zano', iconPath: 'assets/images/zano_icon.png', decimals: 12); static final Map _rawCurrencyMap = [...all, ...havenCurrencies].fold>({}, (acc, item) { diff --git a/cw_core/lib/currency_for_wallet_type.dart b/cw_core/lib/currency_for_wallet_type.dart index af6037a3b4..bd1c224a3e 100644 --- a/cw_core/lib/currency_for_wallet_type.dart +++ b/cw_core/lib/currency_for_wallet_type.dart @@ -30,8 +30,11 @@ CryptoCurrency currencyForWalletType(WalletType type, {bool? isTestnet}) { return CryptoCurrency.trx; case WalletType.wownero: return CryptoCurrency.wow; + case WalletType.zano: + return CryptoCurrency.zano; case WalletType.none: throw Exception( + 'Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType'); } } diff --git a/cw_core/lib/hive_type_ids.dart b/cw_core/lib/hive_type_ids.dart index 0899ac665e..ff0f531691 100644 --- a/cw_core/lib/hive_type_ids.dart +++ b/cw_core/lib/hive_type_ids.dart @@ -19,4 +19,5 @@ const DERIVATION_INFO_TYPE_ID = 17; const TRON_TOKEN_TYPE_ID = 18; const HARDWARE_WALLET_TYPE_TYPE_ID = 19; const MWEB_UTXO_TYPE_ID = 20; -const HAVEN_SEED_STORE_TYPE_ID = 21; \ No newline at end of file +const HAVEN_SEED_STORE_TYPE_ID = 21; +const ZANO_ASSET_TYPE_ID = 22; diff --git a/cw_core/lib/node.dart b/cw_core/lib/node.dart index 7b32940432..780e7143ff 100644 --- a/cw_core/lib/node.dart +++ b/cw_core/lib/node.dart @@ -1,5 +1,4 @@ import 'dart:io'; -import 'dart:math'; import 'package:cw_core/keyable.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'dart:convert'; @@ -10,7 +9,6 @@ import 'package:cw_core/wallet_type.dart'; import 'package:http/io_client.dart' as ioc; import 'dart:math' as math; import 'package:convert/convert.dart'; -import 'package:crypto/crypto.dart' as crypto; import 'package:crypto/crypto.dart'; @@ -105,7 +103,9 @@ class Node extends HiveObject with Keyable { case WalletType.solana: case WalletType.tron: return Uri.parse( - "http${isSSL ? "s" : ""}://$uriRaw${path!.startsWith("/") ? path : "/$path"}"); + "http${isSSL ? "s" : ""}://$uriRaw${path!.startsWith("/") ? path : "/$path"}"); + case WalletType.zano: + return Uri.https(uriRaw, ''); case WalletType.none: throw Exception('Unexpected type ${type.toString()} for Node uri'); } @@ -166,6 +166,8 @@ class Node extends HiveObject with Keyable { case WalletType.solana: case WalletType.tron: return requestElectrumServer(); + case WalletType.zano: + return requestZanoNode(); case WalletType.none: return false; } @@ -174,7 +176,11 @@ class Node extends HiveObject with Keyable { } } - Future requestMoneroNode() async { + Future requestZanoNode() async { + return requestMoneroNode(methodName: "getinfo"); + } + + Future requestMoneroNode({String methodName = 'get_info'}) async { if (useSocksProxy) { return await requestNodeWithProxy(); } @@ -182,8 +188,7 @@ class Node extends HiveObject with Keyable { final path = '/json_rpc'; final rpcUri = isSSL ? Uri.https(uri.authority, path) : Uri.http(uri.authority, path); - final body = {'jsonrpc': '2.0', 'id': '0', 'method': 'get_info'}; - + final body = {'jsonrpc': '2.0', 'id': '0', 'method': methodName}; try { final authenticatingClient = HttpClient(); @@ -226,7 +231,7 @@ class Node extends HiveObject with Keyable { final oldUseSSL = useSSL; useSSL = true; try { - final ret = await requestMoneroNode(); + final ret = await requestMoneroNode(methodName: methodName); if (ret == true) { await save(); return ret; diff --git a/cw_core/lib/wallet_type.dart b/cw_core/lib/wallet_type.dart index e3957b4e7a..79d2b002df 100644 --- a/cw_core/lib/wallet_type.dart +++ b/cw_core/lib/wallet_type.dart @@ -16,6 +16,7 @@ const walletTypes = [ WalletType.polygon, WalletType.solana, WalletType.tron, + WalletType.zano, ]; @HiveType(typeId: WALLET_TYPE_TYPE_ID) @@ -58,6 +59,10 @@ enum WalletType { @HiveField(12) wownero, + + @HiveField(13) + zano, + } int serializeToInt(WalletType type) { @@ -86,6 +91,8 @@ int serializeToInt(WalletType type) { return 10; case WalletType.wownero: return 11; + case WalletType.zano: + return 12; case WalletType.none: return -1; } @@ -117,8 +124,11 @@ WalletType deserializeFromInt(int raw) { return WalletType.tron; case 11: return WalletType.wownero; + case 12: + return WalletType.zano; default: - throw Exception('Unexpected token: $raw for WalletType deserializeFromInt'); + throw Exception( + 'Unexpected token: $raw for WalletType deserializeFromInt'); } } @@ -148,6 +158,8 @@ String walletTypeToString(WalletType type) { return 'Tron'; case WalletType.wownero: return 'Wownero'; + case WalletType.zano: + return 'Zano'; case WalletType.none: return ''; } @@ -179,6 +191,8 @@ String walletTypeToDisplayName(WalletType type) { return 'Tron (TRX)'; case WalletType.wownero: return 'Wownero (WOW)'; + case WalletType.zano: + return 'Zano (ZANO)'; case WalletType.none: return ''; } @@ -213,6 +227,8 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type, {bool isTestnet = fal return CryptoCurrency.trx; case WalletType.wownero: return CryptoCurrency.wow; + case WalletType.zano: + return CryptoCurrency.zano; case WalletType.none: throw Exception( 'Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency'); diff --git a/cw_core/lib/zano_asset.dart b/cw_core/lib/zano_asset.dart new file mode 100644 index 0000000000..edafb4de57 --- /dev/null +++ b/cw_core/lib/zano_asset.dart @@ -0,0 +1,117 @@ +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/hive_type_ids.dart'; +import 'package:hive/hive.dart'; + +part 'zano_asset.g.dart'; + +@HiveType(typeId: ZanoAsset.typeId) +class ZanoAsset extends CryptoCurrency with HiveObjectMixin { + @HiveField(0) + final String fullName; + @HiveField(1) + final String ticker; + @HiveField(2) + final String assetId; + @HiveField(3) + final int decimalPoint; + @HiveField(4, defaultValue: true) + bool _enabled; + @HiveField(5) + final String? iconPath; + // @HiveField(6) + // final String? tag; + @HiveField(6) + final String owner; + @HiveField(7) + final String metaInfo; + @HiveField(8) + final BigInt currentSupply; + @HiveField(9) + final bool hiddenSupply; + @HiveField(10) + final BigInt totalMaxSupply; + @HiveField(11) + final bool isInGlobalWhitelist; + + bool get enabled => _enabled; + + set enabled(bool value) => _enabled = value; + + ZanoAsset({ + this.fullName = '', + this.ticker = '', + required this.assetId, + this.decimalPoint = 12, + bool enabled = true, + this.iconPath, + this.owner = defaultOwner, + this.metaInfo = '', + required this.currentSupply, + this.hiddenSupply = false, + required this.totalMaxSupply, + this.isInGlobalWhitelist = false, + }) : _enabled = enabled, + super( + name: fullName, + title: ticker.toUpperCase(), + fullName: fullName, + tag: 'ZANO', + iconPath: iconPath, + decimals: decimalPoint, + ); + + ZanoAsset.copyWith(ZanoAsset other, {String? icon, String? assetId, bool enabled = true}) + : this.fullName = other.fullName, + this.ticker = other.ticker, + this.assetId = assetId ?? other.assetId, + this.decimalPoint = other.decimalPoint, + this._enabled = enabled && other.enabled, + this.iconPath = icon, + this.currentSupply = other.currentSupply, + this.hiddenSupply = other.hiddenSupply, + this.metaInfo = other.metaInfo, + this.owner = other.owner, + this.totalMaxSupply = other.totalMaxSupply, + this.isInGlobalWhitelist = other.isInGlobalWhitelist, + super( + name: other.name, + title: other.ticker.toUpperCase(), + fullName: other.name, + tag: 'ZANO', + iconPath: icon, + decimals: other.decimalPoint, + enabled: enabled, + ); + + factory ZanoAsset.fromJson(Map json, {bool isInGlobalWhitelist = false}) => ZanoAsset( + assetId: json['asset_id'] as String? ?? '', + currentSupply: bigIntFromDynamic(json['current_supply']), + decimalPoint: json['decimal_point'] as int? ?? 12, + fullName: json['full_name'] as String? ?? '', + hiddenSupply: json['hidden_supply'] as bool? ?? false, + metaInfo: json['meta_info'] as String? ?? '', + owner: json['owner'] as String? ?? '', + ticker: json['ticker'] as String? ?? '', + totalMaxSupply: bigIntFromDynamic(json['total_max_supply']), + isInGlobalWhitelist: isInGlobalWhitelist, + ); + + + + static const typeId = ZANO_ASSET_TYPE_ID; + static const zanoAssetsBoxName = 'zanoAssetsBox'; + static const defaultOwner = '0000000000000000000000000000000000000000000000000000000000000000'; +} + +BigInt bigIntFromDynamic(dynamic d) { + if (d is int) { + return BigInt.from(d); + } else if (d is BigInt) { + return d; + } else if (d == null) { + return BigInt.zero; + } else { + throw 'cannot cast value of type ${d.runtimeType} to BigInt'; + //return BigInt.zero; + } +} \ No newline at end of file diff --git a/cw_core/pubspec.lock b/cw_core/pubspec.lock index c12839a19d..f023dc153a 100644 --- a/cw_core/pubspec.lock +++ b/cw_core/pubspec.lock @@ -207,6 +207,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.7" + decimal: + dependency: "direct main" + description: + name: decimal + sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21" + url: "https://pub.dev" + source: hosted + version: "2.3.3" encrypt: dependency: "direct main" description: @@ -577,6 +585,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + rational: + dependency: transitive + description: + name: rational + sha256: cb808fb6f1a839e6fc5f7d8cb3b0a10e1db48b3be102de73938c627f0b636336 + url: "https://pub.dev" + source: hosted + version: "2.2.3" shelf: dependency: transitive description: diff --git a/cw_core/pubspec.yaml b/cw_core/pubspec.yaml index 19eda51e0c..d594408007 100644 --- a/cw_core/pubspec.yaml +++ b/cw_core/pubspec.yaml @@ -19,6 +19,7 @@ dependencies: flutter_mobx: ^2.0.6+1 intl: ^0.19.0 encrypt: ^5.0.1 + decimal: ^2.3.3 cake_backup: git: url: https://github.com/cake-tech/cake_backup.git diff --git a/cw_haven/pubspec.lock b/cw_haven/pubspec.lock index b6cae9f397..da5a11b890 100644 --- a/cw_haven/pubspec.lock +++ b/cw_haven/pubspec.lock @@ -209,6 +209,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.4" + decimal: + dependency: transitive + description: + name: decimal + sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21" + url: "https://pub.dev" + source: hosted + version: "2.3.3" encrypt: dependency: transitive description: @@ -571,6 +579,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + rational: + dependency: transitive + description: + name: rational + sha256: cb808fb6f1a839e6fc5f7d8cb3b0a10e1db48b3be102de73938c627f0b636336 + url: "https://pub.dev" + source: hosted + version: "2.2.3" shelf: dependency: transitive description: diff --git a/cw_monero/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux b/cw_monero/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux index 17553f81e5..1306eaecd2 120000 --- a/cw_monero/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux +++ b/cw_monero/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux @@ -1 +1 @@ -/Users/omarhatem/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/ \ No newline at end of file +/home/parallels/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/ \ No newline at end of file diff --git a/cw_monero/pubspec.lock b/cw_monero/pubspec.lock index 24be1c0dd0..30a71a702f 100644 --- a/cw_monero/pubspec.lock +++ b/cw_monero/pubspec.lock @@ -225,6 +225,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.10" + decimal: + dependency: transitive + description: + name: decimal + sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21" + url: "https://pub.dev" + source: hosted + version: "2.3.3" encrypt: dependency: "direct main" description: @@ -503,8 +511,8 @@ packages: dependency: "direct main" description: path: "impls/monero.dart" - ref: af5277f96073917185864d3596e82b67bee54e78 - resolved-ref: af5277f96073917185864d3596e82b67bee54e78 + ref: c3dd64bdee37d361a2c1252d127fb575936e43e6 + resolved-ref: c3dd64bdee37d361a2c1252d127fb575936e43e6 url: "https://github.com/mrcyjanek/monero_c" source: git version: "0.0.0" @@ -660,6 +668,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + rational: + dependency: transitive + description: + name: rational + sha256: cb808fb6f1a839e6fc5f7d8cb3b0a10e1db48b3be102de73938c627f0b636336 + url: "https://pub.dev" + source: hosted + version: "2.2.3" rxdart: dependency: transitive description: diff --git a/cw_monero/pubspec.yaml b/cw_monero/pubspec.yaml index 61caf93da5..25b0fd6d3e 100644 --- a/cw_monero/pubspec.yaml +++ b/cw_monero/pubspec.yaml @@ -25,8 +25,7 @@ dependencies: monero: git: url: https://github.com/mrcyjanek/monero_c - ref: af5277f96073917185864d3596e82b67bee54e78 -# ref: 6eb571ea498ed7b854934785f00fabfd0dadf75b # monero_c hash + ref: c3dd64bdee37d361a2c1252d127fb575936e43e6 path: impls/monero.dart mutex: ^3.1.0 ledger_flutter_plus: ^1.4.1 diff --git a/cw_wownero/lib/api/transaction_history.dart b/cw_wownero/lib/api/transaction_history.dart index 3cebdd8112..10bfb535e1 100644 --- a/cw_wownero/lib/api/transaction_history.dart +++ b/cw_wownero/lib/api/transaction_history.dart @@ -10,7 +10,7 @@ import 'package:cw_wownero/exceptions/wownero_transaction_creation_exception.dar import 'package:ffi/ffi.dart'; import 'package:monero/wownero.dart' as wownero; import 'package:monero/src/generated_bindings_wownero.g.dart' as wownero_gen; - +import 'package:mutex/mutex.dart'; String getTxKey(String txId) { final ret = wownero.Wallet_getTxKey(wptr!, txid: txId); @@ -18,6 +18,7 @@ String getTxKey(String txId) { return ret; } +final txHistoryMutex = Mutex(); wownero.TransactionHistory? txhistory; bool isRefreshingTx = false; @@ -26,22 +27,25 @@ Future refreshTransactions() async { isRefreshingTx = true; txhistory ??= wownero.Wallet_history(wptr!); final ptr = txhistory!.address; + await txHistoryMutex.acquire(); await Isolate.run(() { wownero.TransactionHistory_refresh(Pointer.fromAddress(ptr)); }); + txHistoryMutex.release(); isRefreshingTx = false; } int countOfTransactions() => wownero.TransactionHistory_count(txhistory!); -List getAllTransactions() { +Future> getAllTransactions() async { List dummyTxs = []; + await txHistoryMutex.acquire(); txhistory ??= wownero.Wallet_history(wptr!); - wownero.TransactionHistory_refresh(txhistory!); int size = countOfTransactions(); final list = List.generate(size, (index) => Transaction(txInfo: wownero.TransactionHistory_transaction(txhistory!, index: index))); - + txHistoryMutex.release(); + final accts = wownero.Wallet_numSubaddressAccounts(wptr!); for (var i = 0; i < accts; i++) { final fullBalance = wownero.Wallet_balance(wptr!, accountIndex: i); diff --git a/cw_wownero/lib/api/wallet_manager.dart b/cw_wownero/lib/api/wallet_manager.dart index ed0191446f..5f19341a17 100644 --- a/cw_wownero/lib/api/wallet_manager.dart +++ b/cw_wownero/lib/api/wallet_manager.dart @@ -130,6 +130,7 @@ void restoreWalletFromSeedSync( } wptr = newWptr; + wownero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed); wownero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.passphrase", value: passphrase); openedWalletsByPath[path] = wptr!; diff --git a/cw_wownero/lib/wownero_wallet.dart b/cw_wownero/lib/wownero_wallet.dart index f3d1c2a84e..9438414468 100644 --- a/cw_wownero/lib/wownero_wallet.dart +++ b/cw_wownero/lib/wownero_wallet.dart @@ -560,7 +560,7 @@ abstract class WowneroWalletBase @override Future> fetchTransactions() async { transaction_history.refreshTransactions(); - return _getAllTransactionsOfAccount(walletAddresses.account?.id) + return (await _getAllTransactionsOfAccount(walletAddresses.account?.id)) .fold>({}, (Map acc, WowneroTransactionInfo tx) { acc[tx.id] = tx; @@ -589,9 +589,9 @@ abstract class WowneroWalletBase String getSubaddressLabel(int accountIndex, int addressIndex) => wownero_wallet.getSubaddressLabel(accountIndex, addressIndex); - List _getAllTransactionsOfAccount(int? accountIndex) => - transaction_history - .getAllTransactions() + Future> _getAllTransactionsOfAccount(int? accountIndex) async => + (await transaction_history + .getAllTransactions()) .map( (row) => WowneroTransactionInfo( row.hash, diff --git a/cw_wownero/pubspec.lock b/cw_wownero/pubspec.lock index 1e16fa089f..a64404a74d 100644 --- a/cw_wownero/pubspec.lock +++ b/cw_wownero/pubspec.lock @@ -209,6 +209,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.4" + decimal: + dependency: transitive + description: + name: decimal + sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21" + url: "https://pub.dev" + source: hosted + version: "2.3.3" encrypt: dependency: "direct main" description: @@ -463,8 +471,8 @@ packages: dependency: "direct main" description: path: "impls/monero.dart" - ref: af5277f96073917185864d3596e82b67bee54e78 - resolved-ref: af5277f96073917185864d3596e82b67bee54e78 + ref: c3dd64bdee37d361a2c1252d127fb575936e43e6 + resolved-ref: c3dd64bdee37d361a2c1252d127fb575936e43e6 url: "https://github.com/mrcyjanek/monero_c" source: git version: "0.0.0" @@ -612,6 +620,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + rational: + dependency: transitive + description: + name: rational + sha256: cb808fb6f1a839e6fc5f7d8cb3b0a10e1db48b3be102de73938c627f0b636336 + url: "https://pub.dev" + source: hosted + version: "2.2.3" shelf: dependency: transitive description: diff --git a/cw_wownero/pubspec.yaml b/cw_wownero/pubspec.yaml index a92f530f68..e03706a79b 100644 --- a/cw_wownero/pubspec.yaml +++ b/cw_wownero/pubspec.yaml @@ -25,8 +25,7 @@ dependencies: monero: git: url: https://github.com/mrcyjanek/monero_c - ref: af5277f96073917185864d3596e82b67bee54e78 -# ref: 6eb571ea498ed7b854934785f00fabfd0dadf75b # monero_c hash + ref: c3dd64bdee37d361a2c1252d127fb575936e43e6 # monero_c hash path: impls/monero.dart mutex: ^3.1.0 diff --git a/cw_zano/.gitignore b/cw_zano/.gitignore new file mode 100644 index 0000000000..e9dc58d3d6 --- /dev/null +++ b/cw_zano/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +.dart_tool/ + +.packages +.pub/ + +build/ diff --git a/cw_zano/.metadata b/cw_zano/.metadata new file mode 100644 index 0000000000..cb1a29e7c8 --- /dev/null +++ b/cw_zano/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 4d7946a68d26794349189cf21b3f68cc6fe61dcb + channel: stable + +project_type: plugin diff --git a/cw_zano/CHANGELOG.md b/cw_zano/CHANGELOG.md new file mode 100644 index 0000000000..41cc7d8192 --- /dev/null +++ b/cw_zano/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: Describe initial release. diff --git a/cw_zano/LICENSE b/cw_zano/LICENSE new file mode 100644 index 0000000000..ba75c69f7f --- /dev/null +++ b/cw_zano/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/cw_zano/README.md b/cw_zano/README.md new file mode 100644 index 0000000000..4a297e8e38 --- /dev/null +++ b/cw_zano/README.md @@ -0,0 +1,15 @@ +# cw_zano + +A new flutter plugin project. + +## Getting Started + +This project is a starting point for a Flutter +[plug-in package](https://flutter.dev/developing-packages/), +a specialized package that includes platform-specific implementation code for +Android and/or iOS. + +For help getting started with Flutter, view our +[online documentation](https://flutter.dev/docs), which offers tutorials, +samples, guidance on mobile development, and a full API reference. + diff --git a/cw_zano/lib/api/consts.dart b/cw_zano/lib/api/consts.dart new file mode 100644 index 0000000000..80002b8802 --- /dev/null +++ b/cw_zano/lib/api/consts.dart @@ -0,0 +1,6 @@ +class Consts { + static const errorWrongSeed = 'WRONG_SEED'; + static const errorAlreadyExists = 'ALREADY_EXISTS'; + static const errorWalletWrongId = 'WALLET_WRONG_ID'; + static const errorBusy = 'BUSY'; +} \ No newline at end of file diff --git a/cw_zano/lib/api/model/asset_id_params.dart b/cw_zano/lib/api/model/asset_id_params.dart new file mode 100644 index 0000000000..3856f5f4ec --- /dev/null +++ b/cw_zano/lib/api/model/asset_id_params.dart @@ -0,0 +1,9 @@ +class AssetIdParams { + final String assetId; + + AssetIdParams({required this.assetId}); + + Map toJson() => { + 'asset_id': assetId, + }; +} \ No newline at end of file diff --git a/cw_zano/lib/api/model/balance.dart b/cw_zano/lib/api/model/balance.dart new file mode 100644 index 0000000000..b796338fbb --- /dev/null +++ b/cw_zano/lib/api/model/balance.dart @@ -0,0 +1,32 @@ +import 'package:cw_core/zano_asset.dart'; +import 'package:cw_zano/model/zano_asset.dart'; +import 'package:cw_zano/zano_formatter.dart'; + +class Balance { + final ZanoAsset assetInfo; + final BigInt awaitingIn; + final BigInt awaitingOut; + final BigInt total; + final BigInt unlocked; + + Balance( + {required this.assetInfo, + required this.awaitingIn, + required this.awaitingOut, + required this.total, + required this.unlocked}); + + String get assetId => assetInfo.assetId; + + @override + String toString() => '$assetInfo: $total/$unlocked'; + + factory Balance.fromJson(Map json) => Balance( + assetInfo: + ZanoAsset.fromJson(json['asset_info'] as Map? ?? {}), + awaitingIn: ZanoFormatter.bigIntFromDynamic(json['awaiting_in']), + awaitingOut: ZanoFormatter.bigIntFromDynamic(json['awaiting_out']), + total: ZanoFormatter.bigIntFromDynamic(json['total']), + unlocked: ZanoFormatter.bigIntFromDynamic(json['unlocked']), + ); +} diff --git a/cw_zano/lib/api/model/create_wallet_result.dart b/cw_zano/lib/api/model/create_wallet_result.dart new file mode 100644 index 0000000000..91b6fc00b3 --- /dev/null +++ b/cw_zano/lib/api/model/create_wallet_result.dart @@ -0,0 +1,39 @@ +import 'package:cw_zano/api/model/recent_history.dart'; +import 'package:cw_zano/api/model/wi.dart'; + +class CreateWalletResult { + final String name; + final String pass; + final RecentHistory recentHistory; + final bool recovered; + final String seed; + final int walletFileSize; + final int walletId; + final int walletLocalBcSize; + final Wi wi; + + CreateWalletResult( + {required this.name, + required this.pass, + required this.recentHistory, + required this.recovered, + required this.seed, + required this.walletFileSize, + required this.walletId, + required this.walletLocalBcSize, + required this.wi}); + + factory CreateWalletResult.fromJson(Map json) => + CreateWalletResult( + name: json['name'] as String? ?? '', + pass: json['pass'] as String? ?? '', + recentHistory: RecentHistory.fromJson( + json['recent_history'] as Map? ?? {}), + recovered: json['recovered'] as bool? ?? false, + seed: json['seed'] as String? ?? '', + walletFileSize: json['wallet_file_size'] as int? ?? 0, + walletId: json['wallet_id'] as int? ?? 0, + walletLocalBcSize: json['wallet_local_bc_size'] as int? ?? 0, + wi: Wi.fromJson(json['wi'] as Map? ?? {}), + ); +} diff --git a/cw_zano/lib/api/model/destination.dart b/cw_zano/lib/api/model/destination.dart new file mode 100644 index 0000000000..3db4f6652a --- /dev/null +++ b/cw_zano/lib/api/model/destination.dart @@ -0,0 +1,20 @@ +class Destination { + final BigInt amount; // transfered as string + final String address; + final String assetId; + + Destination( + {required this.amount, required this.address, required this.assetId}); + + factory Destination.fromJson(Map json) => Destination( + amount: BigInt.parse(json['amount'] as String? ?? '0'), + address: json['address'] as String? ?? '', + assetId: json['asset_id'] as String? ?? '', + ); + + Map toJson() => { + 'amount': amount.toString(), + 'address': address, + 'asset_id': assetId, + }; +} diff --git a/cw_zano/lib/api/model/employed_entries.dart b/cw_zano/lib/api/model/employed_entries.dart new file mode 100644 index 0000000000..59e5fe34df --- /dev/null +++ b/cw_zano/lib/api/model/employed_entries.dart @@ -0,0 +1,18 @@ +import 'package:cw_zano/api/model/receive.dart'; + +class EmployedEntries { + final List receive; + final List send; + + EmployedEntries({required this.receive, required this.send}); + + factory EmployedEntries.fromJson(Map json) => + EmployedEntries( + receive: json['receive'] == null ? [] : (json['receive'] as List) + .map((e) => Receive.fromJson(e as Map)) + .toList(), + send: json['spent'] == null ? [] : (json['spent'] as List) + .map((e) => Receive.fromJson(e as Map)) + .toList(), + ); +} diff --git a/cw_zano/lib/api/model/get_address_info_result.dart b/cw_zano/lib/api/model/get_address_info_result.dart new file mode 100644 index 0000000000..e8399adb1e --- /dev/null +++ b/cw_zano/lib/api/model/get_address_info_result.dart @@ -0,0 +1,16 @@ +class GetAddressInfoResult { + final bool valid; + final bool auditable; + final bool paymentId; + final bool wrap; + + GetAddressInfoResult( + {required this.valid, required this.auditable, required this.paymentId, required this.wrap}); + + factory GetAddressInfoResult.fromJson(Map json) => GetAddressInfoResult( + valid: json['valid'] as bool? ?? false, + auditable: json['auditable'] as bool? ?? false, + paymentId: json['payment_id'] as bool? ?? false, + wrap: json['wrap'] as bool? ?? false, + ); +} diff --git a/cw_zano/lib/api/model/get_recent_txs_and_info_params.dart b/cw_zano/lib/api/model/get_recent_txs_and_info_params.dart new file mode 100644 index 0000000000..1ad9fc1557 --- /dev/null +++ b/cw_zano/lib/api/model/get_recent_txs_and_info_params.dart @@ -0,0 +1,14 @@ +class GetRecentTxsAndInfoParams { + final int offset; + final int count; + final bool updateProvisionInfo; + + GetRecentTxsAndInfoParams({required this.offset, required this.count, this.updateProvisionInfo = true}); + + Map toJson() => { + 'offset': offset, + 'count': count, + 'update_provision_info': updateProvisionInfo, + 'order': 'FROM_BEGIN_TO_END', + }; +} \ No newline at end of file diff --git a/cw_zano/lib/api/model/get_recent_txs_and_info_result.dart b/cw_zano/lib/api/model/get_recent_txs_and_info_result.dart new file mode 100644 index 0000000000..6b725490cc --- /dev/null +++ b/cw_zano/lib/api/model/get_recent_txs_and_info_result.dart @@ -0,0 +1,12 @@ +import 'package:cw_zano/api/model/transfer.dart'; + +class GetRecentTxsAndInfoResult { + final List transfers; + final int lastItemIndex; + final int totalTransfers; + + GetRecentTxsAndInfoResult({required this.transfers, required this.lastItemIndex, required this.totalTransfers}); + + GetRecentTxsAndInfoResult.empty(): this.transfers = [], this.lastItemIndex = 0, this.totalTransfers = 0; + +} \ No newline at end of file diff --git a/cw_zano/lib/api/model/get_wallet_info_result.dart b/cw_zano/lib/api/model/get_wallet_info_result.dart new file mode 100644 index 0000000000..e14d19375d --- /dev/null +++ b/cw_zano/lib/api/model/get_wallet_info_result.dart @@ -0,0 +1,14 @@ +import 'package:cw_zano/api/model/wi.dart'; +import 'package:cw_zano/api/model/wi_extended.dart'; + +class GetWalletInfoResult { + final Wi wi; + final WiExtended wiExtended; + + GetWalletInfoResult({required this.wi, required this.wiExtended}); + + factory GetWalletInfoResult.fromJson(Map json) => GetWalletInfoResult( + wi: Wi.fromJson(json['wi'] as Map? ?? {}), + wiExtended: WiExtended.fromJson(json['wi_extended'] as Map? ?? {}), + ); +} diff --git a/cw_zano/lib/api/model/get_wallet_status_result.dart b/cw_zano/lib/api/model/get_wallet_status_result.dart new file mode 100644 index 0000000000..da11c4c93e --- /dev/null +++ b/cw_zano/lib/api/model/get_wallet_status_result.dart @@ -0,0 +1,26 @@ +class GetWalletStatusResult { + final int currentDaemonHeight; + final int currentWalletHeight; + final bool isDaemonConnected; + final bool isInLongRefresh; + final int progress; + final int walletState; + + GetWalletStatusResult( + {required this.currentDaemonHeight, + required this.currentWalletHeight, + required this.isDaemonConnected, + required this.isInLongRefresh, + required this.progress, + required this.walletState}); + + factory GetWalletStatusResult.fromJson(Map json) => + GetWalletStatusResult( + currentDaemonHeight: json['current_daemon_height'] as int? ?? 0, + currentWalletHeight: json['current_wallet_height'] as int? ?? 0, + isDaemonConnected: json['is_daemon_connected'] as bool? ?? false, + isInLongRefresh: json['is_in_long_refresh'] as bool? ?? false, + progress: json['progress'] as int? ?? 0, + walletState: json['wallet_state'] as int? ?? 0, + ); +} diff --git a/cw_zano/lib/api/model/proxy_to_daemon_params.dart b/cw_zano/lib/api/model/proxy_to_daemon_params.dart new file mode 100644 index 0000000000..328187cfa1 --- /dev/null +++ b/cw_zano/lib/api/model/proxy_to_daemon_params.dart @@ -0,0 +1,13 @@ +import 'dart:convert'; + +class ProxyToDaemonParams { + final String body; + final String uri; + + ProxyToDaemonParams({required this.body, required this.uri}); + + Map toJson() => { + 'base64_body': base64Encode(utf8.encode(body)), + 'uri': uri, + }; +} diff --git a/cw_zano/lib/api/model/proxy_to_daemon_result.dart b/cw_zano/lib/api/model/proxy_to_daemon_result.dart new file mode 100644 index 0000000000..bf8da7c8d1 --- /dev/null +++ b/cw_zano/lib/api/model/proxy_to_daemon_result.dart @@ -0,0 +1,13 @@ +import 'dart:convert'; + +class ProxyToDaemonResult { + final String body; + final int responseCode; + + ProxyToDaemonResult({required this.body, required this.responseCode}); + + factory ProxyToDaemonResult.fromJson(Map json) => ProxyToDaemonResult( + body: utf8.decode(base64Decode(json['base64_body'] as String? ?? '')), + responseCode: json['response_code'] as int? ?? 0, + ); +} diff --git a/cw_zano/lib/api/model/receive.dart b/cw_zano/lib/api/model/receive.dart new file mode 100644 index 0000000000..6364bf1818 --- /dev/null +++ b/cw_zano/lib/api/model/receive.dart @@ -0,0 +1,15 @@ +import 'package:cw_zano/zano_formatter.dart'; + +class Receive { + final BigInt amount; + final String assetId; + final int index; + + Receive({required this.amount, required this.assetId, required this.index}); + + factory Receive.fromJson(Map json) => Receive( + amount: ZanoFormatter.bigIntFromDynamic(json['amount']), + assetId: json['asset_id'] as String? ?? '', + index: json['index'] as int? ?? 0, + ); +} diff --git a/cw_zano/lib/api/model/recent_history.dart b/cw_zano/lib/api/model/recent_history.dart new file mode 100644 index 0000000000..6591f426d4 --- /dev/null +++ b/cw_zano/lib/api/model/recent_history.dart @@ -0,0 +1,20 @@ +import 'package:cw_zano/api/model/transfer.dart'; + +class RecentHistory { + final List? history; + final int lastItemIndex; + final int totalHistoryItems; + + RecentHistory( + {required this.history, + required this.lastItemIndex, + required this.totalHistoryItems}); + + factory RecentHistory.fromJson(Map json) => RecentHistory( + history: json['history'] == null ? null : (json['history'] as List) + .map((e) => Transfer.fromJson(e as Map)) + .toList(), + lastItemIndex: json['last_item_index'] as int? ?? 0, + totalHistoryItems: json['total_history_items'] as int? ?? 0, + ); +} diff --git a/cw_zano/lib/api/model/store_result.dart b/cw_zano/lib/api/model/store_result.dart new file mode 100644 index 0000000000..0ff6625c19 --- /dev/null +++ b/cw_zano/lib/api/model/store_result.dart @@ -0,0 +1,9 @@ +class StoreResult { + final int walletFileSize; + + StoreResult({required this.walletFileSize}); + + factory StoreResult.fromJson(Map json) => StoreResult( + walletFileSize: json['wallet_file_size'] as int? ?? 0, + ); +} \ No newline at end of file diff --git a/cw_zano/lib/api/model/subtransfer.dart b/cw_zano/lib/api/model/subtransfer.dart new file mode 100644 index 0000000000..d92f1407a3 --- /dev/null +++ b/cw_zano/lib/api/model/subtransfer.dart @@ -0,0 +1,16 @@ +import 'package:cw_zano/zano_formatter.dart'; + +class Subtransfer { + final BigInt amount; + final String assetId; + final bool isIncome; + + Subtransfer( + {required this.amount, required this.assetId, required this.isIncome}); + + factory Subtransfer.fromJson(Map json) => Subtransfer( + amount: ZanoFormatter.bigIntFromDynamic(json['amount']), + assetId: json['asset_id'] as String? ?? '', + isIncome: json['is_income'] as bool? ?? false, + ); +} diff --git a/cw_zano/lib/api/model/transfer.dart b/cw_zano/lib/api/model/transfer.dart new file mode 100644 index 0000000000..1d46ca8b34 --- /dev/null +++ b/cw_zano/lib/api/model/transfer.dart @@ -0,0 +1,126 @@ +import 'package:cw_core/zano_asset.dart'; +import 'package:cw_zano/api/model/employed_entries.dart'; +import 'package:cw_zano/api/model/subtransfer.dart'; +import 'package:collection/collection.dart'; +import 'package:cw_zano/model/zano_asset.dart'; +import 'package:cw_zano/model/zano_transaction_info.dart'; +import 'package:cw_zano/zano_formatter.dart'; +import 'package:cw_zano/zano_wallet.dart'; +import 'package:cw_zano/zano_wallet_api.dart'; + +class Transfer { + final String comment; + final EmployedEntries employedEntries; + final int fee; + final int height; + final bool isMining; + final bool isMixing; + final bool isService; + final String paymentId; + final List remoteAddresses; + final List remoteAliases; + final bool showSender; + final List subtransfers; + final int timestamp; + final int transferInternalIndex; + final int txBlobSize; + final String txHash; + final int txType; + final int unlockTime; + + Transfer({ + required this.comment, + required this.employedEntries, + required this.fee, + required this.height, + required this.isMining, + required this.isMixing, + required this.isService, + required this.paymentId, + required this.remoteAddresses, + required this.remoteAliases, + required this.showSender, + required this.subtransfers, + required this.timestamp, + required this.transferInternalIndex, + required this.txBlobSize, + required this.txHash, + required this.txType, + required this.unlockTime, + }); + + factory Transfer.fromJson(Map json) => Transfer( + comment: json['comment'] as String? ?? '', + employedEntries: EmployedEntries.fromJson(json['employed_entries'] as Map? ?? {}), + fee: json['fee'] as int? ?? 0, + height: json['height'] as int? ?? 0, + isMining: json['is_mining'] as bool? ?? false, + isMixing: json['is_mixing'] as bool? ?? false, + isService: json['is_service'] as bool? ?? false, + paymentId: json['payment_id'] as String? ?? '', + remoteAddresses: json['remote_addresses'] == null ? [] : (json['remote_addresses'] as List).cast(), + remoteAliases: json['remote_aliases'] == null ? [] : (json['remote_aliases'] as List).cast(), + showSender: json['show_sender'] as bool? ?? false, + subtransfers: (json['subtransfers'] as List? ?? []).map((e) => Subtransfer.fromJson(e as Map)).toList(), + timestamp: json['timestamp'] as int? ?? 0, + transferInternalIndex: json['transfer_internal_index'] == null + ? 0 + : json['transfer_internal_index'] is double + ? (json['transfer_internal_index'] as double).toInt() + : json['transfer_internal_index'] as int, + txBlobSize: json['tx_blob_size'] as int? ?? 0, + txHash: json['tx_hash'] as String? ?? '', + txType: json['tx_type'] as int? ?? 0, + unlockTime: json['unlock_time'] as int? ?? 0, + ); + + static Map makeMap(List transfers, Map zanoAssets, int currentDaemonHeight) { + return Map.fromIterable( + transfers, + key: (item) => (item as Transfer).txHash, + value: (transfer) { + transfer as Transfer; + // Simple (only one subtransfer OR two subtransfers and the second is Zano, outgoing and amount equals to fee) or complex? + Subtransfer? single = transfer.subtransfers.singleOrNull; + if (transfer.subtransfers.length == 2) { + final zano = transfer.subtransfers.firstWhereOrNull((element) => element.assetId == ZanoWalletBase.zanoAssetId); + if (zano != null && !zano.isIncome && zano.amount == BigInt.from(transfer.fee)) { + single = transfer.subtransfers.firstWhere((element) => element.assetId != ZanoWalletBase.zanoAssetId); + } + } + bool isSimple = single != null; + // TODO: for complex transactions we show zano or any other transaction, will fix it later + if (!isSimple) { + single = + transfer.subtransfers.firstWhereOrNull((element) => element.assetId == ZanoWalletBase.zanoAssetId) ?? transfer.subtransfers.first; + } + if (single.assetId != ZanoWalletBase.zanoAssetId) { + final asset = zanoAssets[single.assetId]; + if (asset == null) { + ZanoWalletApi.error('unknown asset ${single.assetId}'); + } + final ticker = asset == null ? '***' : asset.ticker; + final decimalPoint = asset == null ? ZanoFormatter.defaultDecimalPoint : asset.decimalPoint; + return ZanoTransactionInfo.fromTransfer( + transfer, + confirmations: currentDaemonHeight - transfer.height, + isIncome: single.isIncome, + assetId: single.assetId, + amount: single.amount, + tokenSymbol: isSimple ? ticker : '*${ticker}', + decimalPoint: decimalPoint, + ); + } + final amount = single.isIncome ? single.amount : single.amount - BigInt.from(transfer.fee); + return ZanoTransactionInfo.fromTransfer( + transfer, + confirmations: currentDaemonHeight - transfer.height, + isIncome: single.isIncome, + assetId: single.assetId, + amount: amount, + tokenSymbol: isSimple ? 'ZANO' : '*ZANO', + ); + }, + ); + } +} diff --git a/cw_zano/lib/api/model/transfer_params.dart b/cw_zano/lib/api/model/transfer_params.dart new file mode 100644 index 0000000000..586d5ddb8a --- /dev/null +++ b/cw_zano/lib/api/model/transfer_params.dart @@ -0,0 +1,41 @@ +import 'package:cw_zano/api/model/destination.dart'; + +class TransferParams { + final List destinations; + final BigInt fee; + final int mixin; + final String paymentId; + final String comment; + final bool pushPayer; + final bool hideReceiver; + + TransferParams({ + required this.destinations, + required this.fee, + required this.mixin, + required this.paymentId, + required this.comment, + required this.pushPayer, + required this.hideReceiver, + }); + + Map toJson() => { + 'destinations': destinations, + 'fee': fee.toInt(), + 'mixin': mixin, + 'payment_id': paymentId, + 'comment': comment, + 'push_payer': pushPayer, + 'hide_receiver': hideReceiver, + }; + + factory TransferParams.fromJson(Map json) => TransferParams( + destinations: (json['destinations'] as List?)?.map((e) => Destination.fromJson(e as Map)).toList() ?? [], + fee: BigInt.from(json['fee'] as int? ?? 0), + mixin: json['mixin'] as int? ?? 0, + paymentId: json['payment_id'] as String? ?? '', + comment: json['comment'] as String? ?? '', + pushPayer: json['push_payer'] as bool? ?? false, + hideReceiver: json['hide_receiver'] as bool? ?? false, + ); +} diff --git a/cw_zano/lib/api/model/transfer_result.dart b/cw_zano/lib/api/model/transfer_result.dart new file mode 100644 index 0000000000..e0259fecc8 --- /dev/null +++ b/cw_zano/lib/api/model/transfer_result.dart @@ -0,0 +1,13 @@ +class TransferResult { + final String txHash; + final int txSize; + final String txUnsignedHex; + + TransferResult({required this.txHash, required this.txSize, required this.txUnsignedHex}); + + factory TransferResult.fromJson(Map json) => TransferResult( + txHash: json['tx_hash'] as String? ?? '', + txSize: json['tx_size'] as int? ?? 0, + txUnsignedHex: json['tx_unsigned_hex'] as String? ?? '', + ); +} diff --git a/cw_zano/lib/api/model/wi.dart b/cw_zano/lib/api/model/wi.dart new file mode 100644 index 0000000000..0375cdf96b --- /dev/null +++ b/cw_zano/lib/api/model/wi.dart @@ -0,0 +1,32 @@ +import 'package:cw_zano/api/model/balance.dart'; + +class Wi { + final String address; + final List balances; + final bool isAuditable; + final bool isWatchOnly; + final int minedTotal; + final String path; + final String viewSecKey; + + Wi( + {required this.address, + required this.balances, + required this.isAuditable, + required this.isWatchOnly, + required this.minedTotal, + required this.path, + required this.viewSecKey}); + + factory Wi.fromJson(Map json) => Wi( + address: json['address'] as String? ?? '', + balances: (json['balances'] as List? ?? []) + .map((e) => Balance.fromJson(e as Map)) + .toList(), + isAuditable: json['is_auditable'] as bool? ?? false, + isWatchOnly: json['is_watch_only'] as bool? ?? false, + minedTotal: json['mined_total'] as int? ?? 0, + path: json['path'] as String? ?? '', + viewSecKey: json['view_sec_key'] as String? ?? '', + ); +} diff --git a/cw_zano/lib/api/model/wi_extended.dart b/cw_zano/lib/api/model/wi_extended.dart new file mode 100644 index 0000000000..ab7e8efbda --- /dev/null +++ b/cw_zano/lib/api/model/wi_extended.dart @@ -0,0 +1,17 @@ +class WiExtended { + final String seed; + final String spendPrivateKey; + final String spendPublicKey; + final String viewPrivateKey; + final String viewPublicKey; + + WiExtended({required this.seed, required this.spendPrivateKey, required this.spendPublicKey, required this.viewPrivateKey, required this.viewPublicKey}); + + factory WiExtended.fromJson(Map json) => WiExtended( + seed: json['seed'] as String? ?? '', + spendPrivateKey: json['spend_private_key'] as String? ?? '', + spendPublicKey: json['spend_public_key'] as String? ?? '', + viewPrivateKey: json['view_private_key'] as String? ?? '', + viewPublicKey: json['view_public_key'] as String? ?? '', + ); +} \ No newline at end of file diff --git a/cw_zano/lib/mnemonics/english.dart b/cw_zano/lib/mnemonics/english.dart new file mode 100644 index 0000000000..9749f974be --- /dev/null +++ b/cw_zano/lib/mnemonics/english.dart @@ -0,0 +1,1630 @@ +class EnglishMnemonics { + static const words = [ + "like", + "just", + "love", + "know", + "never", + "want", + "time", + "out", + "there", + "make", + "look", + "eye", + "down", + "only", + "think", + "heart", + "back", + "then", + "into", + "about", + "more", + "away", + "still", + "them", + "take", + "thing", + "even", + "through", + "long", + "always", + "world", + "too", + "friend", + "tell", + "try", + "hand", + "thought", + "over", + "here", + "other", + "need", + "smile", + "again", + "much", + "cry", + "been", + "night", + "ever", + "little", + "said", + "end", + "some", + "those", + "around", + "mind", + "people", + "girl", + "leave", + "dream", + "left", + "turn", + "myself", + "give", + "nothing", + "really", + "off", + "before", + "something", + "find", + "walk", + "wish", + "good", + "once", + "place", + "ask", + "stop", + "keep", + "watch", + "seem", + "everything", + "wait", + "got", + "yet", + "made", + "remember", + "start", + "alone", + "run", + "hope", + "maybe", + "believe", + "body", + "hate", + "after", + "close", + "talk", + "stand", + "own", + "each", + "hurt", + "help", + "home", + "god", + "soul", + "new", + "many", + "two", + "inside", + "should", + "true", + "first", + "fear", + "mean", + "better", + "play", + "another", + "gone", + "change", + "use", + "wonder", + "someone", + "hair", + "cold", + "open", + "best", + "any", + "behind", + "happen", + "water", + "dark", + "laugh", + "stay", + "forever", + "name", + "work", + "show", + "sky", + "break", + "came", + "deep", + "door", + "put", + "black", + "together", + "upon", + "happy", + "such", + "great", + "white", + "matter", + "fill", + "past", + "please", + "burn", + "cause", + "enough", + "touch", + "moment", + "soon", + "voice", + "scream", + "anything", + "stare", + "sound", + "red", + "everyone", + "hide", + "kiss", + "truth", + "death", + "beautiful", + "mine", + "blood", + "broken", + "very", + "pass", + "next", + "forget", + "tree", + "wrong", + "air", + "mother", + "understand", + "lip", + "hit", + "wall", + "memory", + "sleep", + "free", + "high", + "realize", + "school", + "might", + "skin", + "sweet", + "perfect", + "blue", + "kill", + "breath", + "dance", + "against", + "fly", + "between", + "grow", + "strong", + "under", + "listen", + "bring", + "sometimes", + "speak", + "pull", + "person", + "become", + "family", + "begin", + "ground", + "real", + "small", + "father", + "sure", + "feet", + "rest", + "young", + "finally", + "land", + "across", + "today", + "different", + "guy", + "line", + "fire", + "reason", + "reach", + "second", + "slowly", + "write", + "eat", + "smell", + "mouth", + "step", + "learn", + "three", + "floor", + "promise", + "breathe", + "darkness", + "push", + "earth", + "guess", + "save", + "song", + "above", + "along", + "both", + "color", + "house", + "almost", + "sorry", + "anymore", + "brother", + "okay", + "dear", + "game", + "fade", + "already", + "apart", + "warm", + "beauty", + "heard", + "notice", + "question", + "shine", + "began", + "piece", + "whole", + "shadow", + "secret", + "street", + "within", + "finger", + "point", + "morning", + "whisper", + "child", + "moon", + "green", + "story", + "glass", + "kid", + "silence", + "since", + "soft", + "yourself", + "empty", + "shall", + "angel", + "answer", + "baby", + "bright", + "dad", + "path", + "worry", + "hour", + "drop", + "follow", + "power", + "war", + "half", + "flow", + "heaven", + "act", + "chance", + "fact", + "least", + "tired", + "children", + "near", + "quite", + "afraid", + "rise", + "sea", + "taste", + "window", + "cover", + "nice", + "trust", + "lot", + "sad", + "cool", + "force", + "peace", + "return", + "blind", + "easy", + "ready", + "roll", + "rose", + "drive", + "held", + "music", + "beneath", + "hang", + "mom", + "paint", + "emotion", + "quiet", + "clear", + "cloud", + "few", + "pretty", + "bird", + "outside", + "paper", + "picture", + "front", + "rock", + "simple", + "anyone", + "meant", + "reality", + "road", + "sense", + "waste", + "bit", + "leaf", + "thank", + "happiness", + "meet", + "men", + "smoke", + "truly", + "decide", + "self", + "age", + "book", + "form", + "alive", + "carry", + "escape", + "damn", + "instead", + "able", + "ice", + "minute", + "throw", + "catch", + "leg", + "ring", + "course", + "goodbye", + "lead", + "poem", + "sick", + "corner", + "desire", + "known", + "problem", + "remind", + "shoulder", + "suppose", + "toward", + "wave", + "drink", + "jump", + "woman", + "pretend", + "sister", + "week", + "human", + "joy", + "crack", + "grey", + "pray", + "surprise", + "dry", + "knee", + "less", + "search", + "bleed", + "caught", + "clean", + "embrace", + "future", + "king", + "son", + "sorrow", + "chest", + "hug", + "remain", + "sat", + "worth", + "blow", + "daddy", + "final", + "parent", + "tight", + "also", + "create", + "lonely", + "safe", + "cross", + "dress", + "evil", + "silent", + "bone", + "fate", + "perhaps", + "anger", + "class", + "scar", + "snow", + "tiny", + "tonight", + "continue", + "control", + "dog", + "edge", + "mirror", + "month", + "suddenly", + "comfort", + "given", + "loud", + "quickly", + "gaze", + "plan", + "rush", + "stone", + "town", + "battle", + "ignore", + "spirit", + "stood", + "stupid", + "yours", + "brown", + "build", + "dust", + "hey", + "kept", + "pay", + "phone", + "twist", + "although", + "ball", + "beyond", + "hidden", + "nose", + "taken", + "fail", + "float", + "pure", + "somehow", + "wash", + "wrap", + "angry", + "cheek", + "creature", + "forgotten", + "heat", + "rip", + "single", + "space", + "special", + "weak", + "whatever", + "yell", + "anyway", + "blame", + "job", + "choose", + "country", + "curse", + "drift", + "echo", + "figure", + "grew", + "laughter", + "neck", + "suffer", + "worse", + "yeah", + "disappear", + "foot", + "forward", + "knife", + "mess", + "somewhere", + "stomach", + "storm", + "beg", + "idea", + "lift", + "offer", + "breeze", + "field", + "five", + "often", + "simply", + "stuck", + "win", + "allow", + "confuse", + "enjoy", + "except", + "flower", + "seek", + "strength", + "calm", + "grin", + "gun", + "heavy", + "hill", + "large", + "ocean", + "shoe", + "sigh", + "straight", + "summer", + "tongue", + "accept", + "crazy", + "everyday", + "exist", + "grass", + "mistake", + "sent", + "shut", + "surround", + "table", + "ache", + "brain", + "destroy", + "heal", + "nature", + "shout", + "sign", + "stain", + "choice", + "doubt", + "glance", + "glow", + "mountain", + "queen", + "stranger", + "throat", + "tomorrow", + "city", + "either", + "fish", + "flame", + "rather", + "shape", + "spin", + "spread", + "ash", + "distance", + "finish", + "image", + "imagine", + "important", + "nobody", + "shatter", + "warmth", + "became", + "feed", + "flesh", + "funny", + "lust", + "shirt", + "trouble", + "yellow", + "attention", + "bare", + "bite", + "money", + "protect", + "amaze", + "appear", + "born", + "choke", + "completely", + "daughter", + "fresh", + "friendship", + "gentle", + "probably", + "six", + "deserve", + "expect", + "grab", + "middle", + "nightmare", + "river", + "thousand", + "weight", + "worst", + "wound", + "barely", + "bottle", + "cream", + "regret", + "relationship", + "stick", + "test", + "crush", + "endless", + "fault", + "itself", + "rule", + "spill", + "art", + "circle", + "join", + "kick", + "mask", + "master", + "passion", + "quick", + "raise", + "smooth", + "unless", + "wander", + "actually", + "broke", + "chair", + "deal", + "favorite", + "gift", + "note", + "number", + "sweat", + "box", + "chill", + "clothes", + "lady", + "mark", + "park", + "poor", + "sadness", + "tie", + "animal", + "belong", + "brush", + "consume", + "dawn", + "forest", + "innocent", + "pen", + "pride", + "stream", + "thick", + "clay", + "complete", + "count", + "draw", + "faith", + "press", + "silver", + "struggle", + "surface", + "taught", + "teach", + "wet", + "bless", + "chase", + "climb", + "enter", + "letter", + "melt", + "metal", + "movie", + "stretch", + "swing", + "vision", + "wife", + "beside", + "crash", + "forgot", + "guide", + "haunt", + "joke", + "knock", + "plant", + "pour", + "prove", + "reveal", + "steal", + "stuff", + "trip", + "wood", + "wrist", + "bother", + "bottom", + "crawl", + "crowd", + "fix", + "forgive", + "frown", + "grace", + "loose", + "lucky", + "party", + "release", + "surely", + "survive", + "teacher", + "gently", + "grip", + "speed", + "suicide", + "travel", + "treat", + "vein", + "written", + "cage", + "chain", + "conversation", + "date", + "enemy", + "however", + "interest", + "million", + "page", + "pink", + "proud", + "sway", + "themselves", + "winter", + "church", + "cruel", + "cup", + "demon", + "experience", + "freedom", + "pair", + "pop", + "purpose", + "respect", + "shoot", + "softly", + "state", + "strange", + "bar", + "birth", + "curl", + "dirt", + "excuse", + "lord", + "lovely", + "monster", + "order", + "pack", + "pants", + "pool", + "scene", + "seven", + "shame", + "slide", + "ugly", + "among", + "blade", + "blonde", + "closet", + "creek", + "deny", + "drug", + "eternity", + "gain", + "grade", + "handle", + "key", + "linger", + "pale", + "prepare", + "swallow", + "swim", + "tremble", + "wheel", + "won", + "cast", + "cigarette", + "claim", + "college", + "direction", + "dirty", + "gather", + "ghost", + "hundred", + "loss", + "lung", + "orange", + "present", + "swear", + "swirl", + "twice", + "wild", + "bitter", + "blanket", + "doctor", + "everywhere", + "flash", + "grown", + "knowledge", + "numb", + "pressure", + "radio", + "repeat", + "ruin", + "spend", + "unknown", + "buy", + "clock", + "devil", + "early", + "false", + "fantasy", + "pound", + "precious", + "refuse", + "sheet", + "teeth", + "welcome", + "add", + "ahead", + "block", + "bury", + "caress", + "content", + "depth", + "despite", + "distant", + "marry", + "purple", + "threw", + "whenever", + "bomb", + "dull", + "easily", + "grasp", + "hospital", + "innocence", + "normal", + "receive", + "reply", + "rhyme", + "shade", + "someday", + "sword", + "toe", + "visit", + "asleep", + "bought", + "center", + "consider", + "flat", + "hero", + "history", + "ink", + "insane", + "muscle", + "mystery", + "pocket", + "reflection", + "shove", + "silently", + "smart", + "soldier", + "spot", + "stress", + "train", + "type", + "view", + "whether", + "bus", + "energy", + "explain", + "holy", + "hunger", + "inch", + "magic", + "mix", + "noise", + "nowhere", + "prayer", + "presence", + "shock", + "snap", + "spider", + "study", + "thunder", + "trail", + "admit", + "agree", + "bag", + "bang", + "bound", + "butterfly", + "cute", + "exactly", + "explode", + "familiar", + "fold", + "further", + "pierce", + "reflect", + "scent", + "selfish", + "sharp", + "sink", + "spring", + "stumble", + "universe", + "weep", + "women", + "wonderful", + "action", + "ancient", + "attempt", + "avoid", + "birthday", + "branch", + "chocolate", + "core", + "depress", + "drunk", + "especially", + "focus", + "fruit", + "honest", + "match", + "palm", + "perfectly", + "pillow", + "pity", + "poison", + "roar", + "shift", + "slightly", + "thump", + "truck", + "tune", + "twenty", + "unable", + "wipe", + "wrote", + "coat", + "constant", + "dinner", + "drove", + "egg", + "eternal", + "flight", + "flood", + "frame", + "freak", + "gasp", + "glad", + "hollow", + "motion", + "peer", + "plastic", + "root", + "screen", + "season", + "sting", + "strike", + "team", + "unlike", + "victim", + "volume", + "warn", + "weird", + "attack", + "await", + "awake", + "built", + "charm", + "crave", + "despair", + "fought", + "grant", + "grief", + "horse", + "limit", + "message", + "ripple", + "sanity", + "scatter", + "serve", + "split", + "string", + "trick", + "annoy", + "blur", + "boat", + "brave", + "clearly", + "cling", + "connect", + "fist", + "forth", + "imagination", + "iron", + "jock", + "judge", + "lesson", + "milk", + "misery", + "nail", + "naked", + "ourselves", + "poet", + "possible", + "princess", + "sail", + "size", + "snake", + "society", + "stroke", + "torture", + "toss", + "trace", + "wise", + "bloom", + "bullet", + "cell", + "check", + "cost", + "darling", + "during", + "footstep", + "fragile", + "hallway", + "hardly", + "horizon", + "invisible", + "journey", + "midnight", + "mud", + "nod", + "pause", + "relax", + "shiver", + "sudden", + "value", + "youth", + "abuse", + "admire", + "blink", + "breast", + "bruise", + "constantly", + "couple", + "creep", + "curve", + "difference", + "dumb", + "emptiness", + "gotta", + "honor", + "plain", + "planet", + "recall", + "rub", + "ship", + "slam", + "soar", + "somebody", + "tightly", + "weather", + "adore", + "approach", + "bond", + "bread", + "burst", + "candle", + "coffee", + "cousin", + "crime", + "desert", + "flutter", + "frozen", + "grand", + "heel", + "hello", + "language", + "level", + "movement", + "pleasure", + "powerful", + "random", + "rhythm", + "settle", + "silly", + "slap", + "sort", + "spoken", + "steel", + "threaten", + "tumble", + "upset", + "aside", + "awkward", + "bee", + "blank", + "board", + "button", + "card", + "carefully", + "complain", + "crap", + "deeply", + "discover", + "drag", + "dread", + "effort", + "entire", + "fairy", + "giant", + "gotten", + "greet", + "illusion", + "jeans", + "leap", + "liquid", + "march", + "mend", + "nervous", + "nine", + "replace", + "rope", + "spine", + "stole", + "terror", + "accident", + "apple", + "balance", + "boom", + "childhood", + "collect", + "demand", + "depression", + "eventually", + "faint", + "glare", + "goal", + "group", + "honey", + "kitchen", + "laid", + "limb", + "machine", + "mere", + "mold", + "murder", + "nerve", + "painful", + "poetry", + "prince", + "rabbit", + "shelter", + "shore", + "shower", + "soothe", + "stair", + "steady", + "sunlight", + "tangle", + "tease", + "treasure", + "uncle", + "begun", + "bliss", + "canvas", + "cheer", + "claw", + "clutch", + "commit", + "crimson", + "crystal", + "delight", + "doll", + "existence", + "express", + "fog", + "football", + "gay", + "goose", + "guard", + "hatred", + "illuminate", + "mass", + "math", + "mourn", + "rich", + "rough", + "skip", + "stir", + "student", + "style", + "support", + "thorn", + "tough", + "yard", + "yearn", + "yesterday", + "advice", + "appreciate", + "autumn", + "bank", + "beam", + "bowl", + "capture", + "carve", + "collapse", + "confusion", + "creation", + "dove", + "feather", + "girlfriend", + "glory", + "government", + "harsh", + "hop", + "inner", + "loser", + "moonlight", + "neighbor", + "neither", + "peach", + "pig", + "praise", + "screw", + "shield", + "shimmer", + "sneak", + "stab", + "subject", + "throughout", + "thrown", + "tower", + "twirl", + "wow", + "army", + "arrive", + "bathroom", + "bump", + "cease", + "cookie", + "couch", + "courage", + "dim", + "guilt", + "howl", + "hum", + "husband", + "insult", + "led", + "lunch", + "mock", + "mostly", + "natural", + "nearly", + "needle", + "nerd", + "peaceful", + "perfection", + "pile", + "price", + "remove", + "roam", + "sanctuary", + "serious", + "shiny", + "shook", + "sob", + "stolen", + "tap", + "vain", + "void", + "warrior", + "wrinkle", + "affection", + "apologize", + "blossom", + "bounce", + "bridge", + "cheap", + "crumble", + "decision", + "descend", + "desperately", + "dig", + "dot", + "flip", + "frighten", + "heartbeat", + "huge", + "lazy", + "lick", + "odd", + "opinion", + "process", + "puzzle", + "quietly", + "retreat", + "score", + "sentence", + "separate", + "situation", + "skill", + "soak", + "square", + "stray", + "taint", + "task", + "tide", + "underneath", + "veil", + "whistle", + "anywhere", + "bedroom", + "bid", + "bloody", + "burden", + "careful", + "compare", + "concern", + "curtain", + "decay", + "defeat", + "describe", + "double", + "dreamer", + "driver", + "dwell", + "evening", + "flare", + "flicker", + "grandma", + "guitar", + "harm", + "horrible", + "hungry", + "indeed", + "lace", + "melody", + "monkey", + "nation", + "object", + "obviously", + "rainbow", + "salt", + "scratch", + "shown", + "shy", + "stage", + "stun", + "third", + "tickle", + "useless", + "weakness", + "worship", + "worthless", + "afternoon", + "beard", + "boyfriend", + "bubble", + "busy", + "certain", + "chin", + "concrete", + "desk", + "diamond", + "doom", + "drawn", + "due", + "felicity", + "freeze", + "frost", + "garden", + "glide", + "harmony", + "hopefully", + "hunt", + "jealous", + "lightning", + "mama", + "mercy", + "peel", + "physical", + "position", + "pulse", + "punch", + "quit", + "rant", + "respond", + "salty", + "sane", + "satisfy", + "savior", + "sheep", + "slept", + "social", + "sport", + "tuck", + "utter", + "valley", + "wolf", + "aim", + "alas", + "alter", + "arrow", + "awaken", + "beaten", + "belief", + "brand", + "ceiling", + "cheese", + "clue", + "confidence", + "connection", + "daily", + "disguise", + "eager", + "erase", + "essence", + "everytime", + "expression", + "fan", + "flag", + "flirt", + "foul", + "fur", + "giggle", + "glorious", + "ignorance", + "law", + "lifeless", + "measure", + "mighty", + "muse", + "north", + "opposite", + "paradise", + "patience", + "patient", + "pencil", + "petal", + "plate", + "ponder", + "possibly", + "practice", + "slice", + "spell", + "stock", + "strife", + "strip", + "suffocate", + "suit", + "tender", + "tool", + "trade", + "velvet", + "verse", + "waist", + "witch", + "aunt", + "bench", + "bold", + "cap", + "certainly", + "click", + "companion", + "creator", + "dart", + "delicate", + "determine", + "dish", + "dragon", + "drama", + "drum", + "dude", + "everybody", + "feast", + "forehead", + "former", + "fright", + "fully", + "gas", + "hook", + "hurl", + "invite", + "juice", + "manage", + "moral", + "possess", + "raw", + "rebel", + "royal", + "scale", + "scary", + "several", + "slight", + "stubborn", + "swell", + "talent", + "tea", + "terrible", + "thread", + "torment", + "trickle", + "usually", + "vast", + "violence", + "weave", + "acid", + "agony", + "ashamed", + "awe", + "belly", + "blend", + "blush", + "character", + "cheat", + "common", + "company", + "coward", + "creak", + "danger", + "deadly", + "defense", + "define", + "depend", + "desperate", + "destination", + "dew", + "duck", + "dusty", + "embarrass", + "engine", + "example", + "explore", + "foe", + "freely", + "frustrate", + "generation", + "glove", + "guilty", + "health", + "hurry", + "idiot", + "impossible", + "inhale", + "jaw", + "kingdom", + "mention", + "mist", + "moan", + "mumble", + "mutter", + "observe", + "ode", + "pathetic", + "pattern", + "pie", + "prefer", + "puff", + "rape", + "rare", + "revenge", + "rude", + "scrape", + "spiral", + "squeeze", + "strain", + "sunset", + "suspend", + "sympathy", + "thigh", + "throne", + "total", + "unseen", + "weapon", + "weary" + ]; +} diff --git a/cw_zano/lib/model/pending_zano_transaction.dart b/cw_zano/lib/model/pending_zano_transaction.dart new file mode 100644 index 0000000000..001ec2cd1d --- /dev/null +++ b/cw_zano/lib/model/pending_zano_transaction.dart @@ -0,0 +1,52 @@ +import 'package:cw_core/pending_transaction.dart'; +import 'package:cw_zano/api/model/destination.dart'; +import 'package:cw_zano/api/model/transfer_result.dart'; +import 'package:cw_zano/zano_formatter.dart'; +import 'package:cw_zano/zano_wallet.dart'; + +class PendingZanoTransaction with PendingTransaction { + PendingZanoTransaction({ + required this.zanoWallet, + required this.destinations, + required this.fee, + required this.comment, + required this.assetId, + required this.ticker, + this.decimalPoint = ZanoFormatter.defaultDecimalPoint, + required this.amount, + }); + + final ZanoWalletBase zanoWallet; + final List destinations; + final BigInt fee; + final String comment; + final String assetId; + final String ticker; + final int decimalPoint; + final BigInt amount; + + @override + String get id => transferResult?.txHash ?? ''; + + @override + String get hex => ''; + + @override + String get amountFormatted => '${ZanoFormatter.bigIntAmountToString(amount, decimalPoint)} $ticker'; + + @override + String get feeFormatted => '${ZanoFormatter.bigIntAmountToString(fee)} ZANO'; + + TransferResult? transferResult; + + @override + Future commit() async { + await zanoWallet.transfer(destinations, fee, comment); + zanoWallet.fetchTransactions(); + } + + @override + Future commitUR() { + throw UnimplementedError(); + } +} diff --git a/cw_zano/lib/model/zano_asset.dart b/cw_zano/lib/model/zano_asset.dart new file mode 100644 index 0000000000..e69de29bb2 diff --git a/cw_zano/lib/model/zano_balance.dart b/cw_zano/lib/model/zano_balance.dart new file mode 100644 index 0000000000..882c0e11bf --- /dev/null +++ b/cw_zano/lib/model/zano_balance.dart @@ -0,0 +1,17 @@ +import 'package:cw_core/balance.dart'; +import 'package:cw_zano/zano_formatter.dart'; + +class ZanoBalance extends Balance { + final BigInt total; + final BigInt unlocked; + final int decimalPoint; + ZanoBalance({required this.total, required this.unlocked, this.decimalPoint = ZanoFormatter.defaultDecimalPoint}) : super(unlocked.isValidInt ? unlocked.toInt() : 0, (total - unlocked).isValidInt ? (total - unlocked).toInt() : 0); + + ZanoBalance.empty({this.decimalPoint = ZanoFormatter.defaultDecimalPoint}): total = BigInt.zero, unlocked = BigInt.zero, super(0, 0); + + @override + String get formattedAdditionalBalance => ZanoFormatter.bigIntAmountToString(total - unlocked, decimalPoint); + + @override + String get formattedAvailableBalance => ZanoFormatter.bigIntAmountToString(unlocked, decimalPoint); +} diff --git a/cw_zano/lib/model/zano_transaction_creation_exception.dart b/cw_zano/lib/model/zano_transaction_creation_exception.dart new file mode 100644 index 0000000000..74a5f77c65 --- /dev/null +++ b/cw_zano/lib/model/zano_transaction_creation_exception.dart @@ -0,0 +1,8 @@ +class ZanoTransactionCreationException implements Exception { + ZanoTransactionCreationException(this.message); + + final String message; + + @override + String toString() => message; +} \ No newline at end of file diff --git a/cw_zano/lib/model/zano_transaction_credentials.dart b/cw_zano/lib/model/zano_transaction_credentials.dart new file mode 100644 index 0000000000..dbbfe53c6c --- /dev/null +++ b/cw_zano/lib/model/zano_transaction_credentials.dart @@ -0,0 +1,11 @@ +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/monero_transaction_priority.dart'; +import 'package:cw_core/output_info.dart'; + +class ZanoTransactionCredentials { + ZanoTransactionCredentials({required this.outputs, required this.priority, required this.currency}); + + final List outputs; + final MoneroTransactionPriority priority; + final CryptoCurrency currency; +} diff --git a/cw_zano/lib/model/zano_transaction_info.dart b/cw_zano/lib/model/zano_transaction_info.dart new file mode 100644 index 0000000000..1e44da674f --- /dev/null +++ b/cw_zano/lib/model/zano_transaction_info.dart @@ -0,0 +1,76 @@ +import 'package:cw_core/format_amount.dart'; +import 'package:cw_core/transaction_direction.dart'; +import 'package:cw_core/transaction_info.dart'; +import 'package:cw_zano/api/model/transfer.dart'; +import 'package:cw_zano/zano_formatter.dart'; + +class ZanoTransactionInfo extends TransactionInfo { + ZanoTransactionInfo({ + required this.id, + required this.height, + required this.direction, + required this.date, + required this.isPending, + required this.zanoAmount, + required this.fee, + required this.confirmations, + required this.tokenSymbol, + required this.decimalPoint, + required String assetId, + }) : amount = zanoAmount.isValidInt ? zanoAmount.toInt() : 0 { + additionalInfo['assetId'] = assetId; + } + + ZanoTransactionInfo.fromTransfer(Transfer transfer, + {required int confirmations, + required bool isIncome, + required String assetId, + required BigInt amount, + this.tokenSymbol = 'ZANO', + this.decimalPoint = ZanoFormatter.defaultDecimalPoint}) + : id = transfer.txHash, + height = transfer.height, + direction = isIncome ? TransactionDirection.incoming : TransactionDirection.outgoing, + date = DateTime.fromMillisecondsSinceEpoch(transfer.timestamp * 1000), + zanoAmount = amount, + amount = amount.isValidInt ? amount.toInt() : 0, + fee = transfer.fee, + confirmations = confirmations, + isPending = false, + recipientAddress = transfer.remoteAddresses.isNotEmpty ? transfer.remoteAddresses.first : '' { + additionalInfo = { + 'comment': transfer.comment, + 'assetId': assetId, + }; + } + String get assetId => additionalInfo["assetId"] as String; + set assetId(String newId) => additionalInfo["assetId"] = newId; + final String id; + final int height; + final TransactionDirection direction; + final DateTime date; + final bool isPending; + final BigInt zanoAmount; + final int amount; + final int fee; + final int confirmations; + final int decimalPoint; + late String recipientAddress; + final String tokenSymbol; + String? _fiatAmount; + String? key; + + @override + String amountFormatted() => '${formatAmount(ZanoFormatter.bigIntAmountToString(zanoAmount, decimalPoint))} $tokenSymbol'; + + @override + String fiatAmount() => _fiatAmount ?? ''; + + @override + void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount); + + @override + String feeFormatted() => '${formatAmount(ZanoFormatter.intAmountToString(fee))} $feeCurrency'; + + String get feeCurrency => 'ZANO'; +} diff --git a/cw_zano/lib/model/zano_wallet_keys.dart b/cw_zano/lib/model/zano_wallet_keys.dart new file mode 100644 index 0000000000..5a224633bb --- /dev/null +++ b/cw_zano/lib/model/zano_wallet_keys.dart @@ -0,0 +1,12 @@ +class ZanoWalletKeys { + const ZanoWalletKeys( + {required this.privateSpendKey, + required this.privateViewKey, + required this.publicSpendKey, + required this.publicViewKey}); + + final String publicViewKey; + final String privateViewKey; + final String publicSpendKey; + final String privateSpendKey; +} \ No newline at end of file diff --git a/cw_zano/lib/zano_formatter.dart b/cw_zano/lib/zano_formatter.dart new file mode 100644 index 0000000000..ffc5d20f32 --- /dev/null +++ b/cw_zano/lib/zano_formatter.dart @@ -0,0 +1,74 @@ +import 'dart:math'; + +import 'package:cw_zano/zano_wallet_api.dart'; +import 'package:decimal/decimal.dart'; +import 'package:decimal/intl.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:intl/intl.dart'; + +class ZanoFormatter { + static const defaultDecimalPoint = 12; + + //static final numberFormat = NumberFormat() + // ..maximumFractionDigits = defaultDecimalPoint + // ..minimumFractionDigits = 1; + + static Decimal _bigIntDivision({required BigInt amount, required BigInt divider}) { + return (Decimal.fromBigInt(amount) / Decimal.fromBigInt(divider)).toDecimal(); + } + + static String intAmountToString(int amount, [int decimalPoint = defaultDecimalPoint]) { + final numberFormat = NumberFormat()..maximumFractionDigits = decimalPoint + ..minimumFractionDigits = 1; + return numberFormat.format( + DecimalIntl( + _bigIntDivision( + amount: BigInt.from(amount), + divider: BigInt.from(pow(10, decimalPoint)), + ), + ), + ) + .replaceAll(',', ''); + } + + static String bigIntAmountToString(BigInt amount, [int decimalPoint = defaultDecimalPoint]) { + final numberFormat = NumberFormat()..maximumFractionDigits = decimalPoint + ..minimumFractionDigits = 1; + return numberFormat.format( + DecimalIntl( + _bigIntDivision( + amount: amount, + divider: BigInt.from(pow(10, decimalPoint)), + ), + ), + ) + .replaceAll(',', ''); + } + + static double intAmountToDouble(int amount, [int decimalPoint = defaultDecimalPoint]) => _bigIntDivision( + amount: BigInt.from(amount), + divider: BigInt.from(pow(10, decimalPoint)), + ).toDouble(); + + static int parseAmount(String amount, [int decimalPoint = defaultDecimalPoint]) { + final resultBigInt = (Decimal.parse(amount) * Decimal.fromBigInt(BigInt.from(10).pow(decimalPoint))).toBigInt(); + if (!resultBigInt.isValidInt) { + Fluttertoast.showToast(msg: 'Cannot transfer $amount. Maximum is ${intAmountToString(resultBigInt.toInt(), decimalPoint)}.'); + } + return resultBigInt.toInt(); + } + + static BigInt bigIntFromDynamic(dynamic d) { + if (d is int) { + return BigInt.from(d); + } else if (d is BigInt) { + return d; + } else if (d == null) { + return BigInt.zero; + } else { + ZanoWalletApi.error('cannot cast value of type ${d.runtimeType} to BigInt'); + throw 'cannot cast value of type ${d.runtimeType} to BigInt'; + //return BigInt.zero; + } + } +} diff --git a/cw_zano/lib/zano_transaction_history.dart b/cw_zano/lib/zano_transaction_history.dart new file mode 100644 index 0000000000..4c6a2d5541 --- /dev/null +++ b/cw_zano/lib/zano_transaction_history.dart @@ -0,0 +1,27 @@ +import 'dart:core'; +import 'package:mobx/mobx.dart'; +import 'package:cw_core/transaction_history.dart'; +import 'package:cw_zano/model/zano_transaction_info.dart'; + +part 'zano_transaction_history.g.dart'; + +class ZanoTransactionHistory = ZanoTransactionHistoryBase + with _$ZanoTransactionHistory; + +abstract class ZanoTransactionHistoryBase + extends TransactionHistoryBase with Store { + ZanoTransactionHistoryBase() { + transactions = ObservableMap(); + } + + @override + Future save() async {} + + @override + void addOne(ZanoTransactionInfo transaction) => + transactions[transaction.id] = transaction; + + @override + void addMany(Map transactions) => + this.transactions.addAll(transactions); +} diff --git a/cw_zano/lib/zano_utils.dart b/cw_zano/lib/zano_utils.dart new file mode 100644 index 0000000000..f39f204629 --- /dev/null +++ b/cw_zano/lib/zano_utils.dart @@ -0,0 +1,17 @@ +import 'dart:convert'; + +import 'package:monero/zano.dart' as zano; +import 'package:cw_zano/api/model/get_address_info_result.dart'; + +class ZanoUtils { + static bool validateAddress(String address) { + try { + final result = GetAddressInfoResult.fromJson( + jsonDecode(zano.PlainWallet_getAddressInfo(address)) as Map, + ); + return result.valid; + } catch (err) { + return false; + } + } +} diff --git a/cw_zano/lib/zano_wallet.dart b/cw_zano/lib/zano_wallet.dart new file mode 100644 index 0000000000..b628c45208 --- /dev/null +++ b/cw_zano/lib/zano_wallet.dart @@ -0,0 +1,534 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:math'; + +import 'package:cw_core/cake_hive.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/node.dart'; +import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/pending_transaction.dart'; +import 'package:cw_core/sync_status.dart'; +import 'package:cw_core/transaction_priority.dart'; +import 'package:cw_core/utils/print_verbose.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_credentials.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/zano_asset.dart'; +import 'package:cw_zano/api/model/create_wallet_result.dart'; +import 'package:cw_zano/api/model/destination.dart'; +import 'package:cw_zano/api/model/get_recent_txs_and_info_result.dart'; +import 'package:cw_zano/api/model/get_wallet_info_result.dart'; +import 'package:cw_zano/api/model/get_wallet_status_result.dart'; +import 'package:cw_zano/api/model/transfer.dart'; +import 'package:cw_zano/model/pending_zano_transaction.dart'; +import 'package:cw_zano/model/zano_asset.dart'; +import 'package:cw_zano/model/zano_balance.dart'; +import 'package:cw_zano/model/zano_transaction_creation_exception.dart'; +import 'package:cw_zano/model/zano_transaction_credentials.dart'; +import 'package:cw_zano/model/zano_transaction_info.dart'; +import 'package:cw_zano/model/zano_wallet_keys.dart'; +import 'package:cw_zano/zano_formatter.dart'; +import 'package:cw_zano/zano_transaction_history.dart'; +import 'package:cw_zano/zano_wallet_addresses.dart'; +import 'package:cw_zano/zano_wallet_api.dart'; +import 'package:cw_zano/zano_wallet_exceptions.dart'; +import 'package:cw_zano/zano_wallet_service.dart'; +import 'package:cw_zano/api/model/balance.dart'; + +import 'package:flutter/material.dart'; +import 'package:mobx/mobx.dart'; + +part 'zano_wallet.g.dart'; + +class ZanoWallet = ZanoWalletBase with _$ZanoWallet; + +abstract class ZanoWalletBase extends WalletBase with Store, ZanoWalletApi { + static const int _autoSaveIntervalSeconds = 30; + static const int _pollIntervalMilliseconds = 2000; + static const int _maxLoadAssetsRetries = 5; + + @override + void setPassword(String password) { + _password = password; + super.setPassword(password); + } + String _password; + @override + String get password => _password; + + @override + Future signMessage(String message, {String? address = null}) { + throw UnimplementedError(); + } + + @override + Future verifyMessage(String message, String signature, {String? address = null}) { + throw UnimplementedError(); + } + + @override + ZanoWalletAddresses walletAddresses; + + @override + @observable + SyncStatus syncStatus; + + @override + @observable + ObservableMap balance; + + @override + String seed = ''; + + @override + ZanoWalletKeys keys = ZanoWalletKeys(privateSpendKey: '', privateViewKey: '', publicSpendKey: '', publicViewKey: ''); + + static const String zanoAssetId = 'd6329b5b1f7c0805b5c345f4957554002a2f557845f64d7645dae0e051a6498a'; + + Map zanoAssets = {}; + + Timer? _updateSyncInfoTimer; + + int _lastKnownBlockHeight = 0; + int _initialSyncHeight = 0; + int currentDaemonHeight = 0; + bool _isTransactionUpdating; + bool _hasSyncAfterStartup; + Timer? _autoSaveTimer; + + /// index of last transaction fetched + final int _lastTxIndex = 0; + /// number of transactions in each request + static final int _txChunkSize = (pow(2, 32)-1).toInt(); + + ZanoWalletBase(WalletInfo walletInfo, String password) + : balance = ObservableMap.of({CryptoCurrency.zano: ZanoBalance.empty()}), + _isTransactionUpdating = false, + _hasSyncAfterStartup = false, + walletAddresses = ZanoWalletAddresses(walletInfo), + syncStatus = NotConnectedSyncStatus(), + _password = password, + super(walletInfo) { + transactionHistory = ZanoTransactionHistory(); + if (!CakeHive.isAdapterRegistered(ZanoAsset.typeId)) { + CakeHive.registerAdapter(ZanoAssetAdapter()); + } + } + + @override + int calculateEstimatedFee(TransactionPriority priority, [int? amount = null]) => getCurrentTxFee(priority); + + @override + Future changePassword(String password) async { + setPassword(password); + } + + static Future create({required WalletCredentials credentials}) async { + final wallet = ZanoWallet(credentials.walletInfo!, credentials.password!); + await wallet.initWallet(); + final path = await pathForWallet(name: credentials.name, type: credentials.walletInfo!.type); + final createWalletResult = await wallet.createWallet(path, credentials.password!); + await wallet.initWallet(); + await wallet.parseCreateWalletResult(createWalletResult); + await wallet.init(createWalletResult.wi.address); + return wallet; + } + + static Future restore({required ZanoRestoreWalletFromSeedCredentials credentials}) async { + final wallet = ZanoWallet(credentials.walletInfo!, credentials.password!); await wallet.initWallet(); + await wallet.initWallet(); + final path = await pathForWallet(name: credentials.name, type: credentials.walletInfo!.type); + final createWalletResult = await wallet.restoreWalletFromSeed(path, credentials.password!, credentials.mnemonic); + await wallet.initWallet(); + await wallet.parseCreateWalletResult(createWalletResult); + await wallet.init(createWalletResult.wi.address); + return wallet; + } + + static Future open({required String name, required String password, required WalletInfo walletInfo}) async { + final path = await pathForWallet(name: name, type: walletInfo.type); + final wallet = ZanoWallet(walletInfo, password); + await wallet.initWallet(); + final createWalletResult = await wallet.loadWallet(path, password); + await wallet.initWallet(); + await wallet.parseCreateWalletResult(createWalletResult); + await wallet.init(createWalletResult.wi.address); + return wallet; + } + + Future parseCreateWalletResult(CreateWalletResult result) async { + hWallet = result.walletId; + seed = result.seed; + ZanoWalletApi.info('setting hWallet = ${result.walletId}'); + walletAddresses.address = result.wi.address; + await loadAssets(result.wi.balances, maxRetries: _maxLoadAssetsRetries); + for (final item in result.wi.balances) { + if (item.assetInfo.assetId == zanoAssetId) { + balance[CryptoCurrency.zano] = ZanoBalance( + total: item.total, + unlocked: item.unlocked, + ); + } + } + if (result.recentHistory.history != null) { + final transfers = result.recentHistory.history!; + final transactions = Transfer.makeMap(transfers, zanoAssets, currentDaemonHeight); + transactionHistory.addMany(transactions); + await transactionHistory.save(); + } + } + + @override + Future close({bool shouldCleanup = true}) async { + closeWallet(); + _updateSyncInfoTimer?.cancel(); + _autoSaveTimer?.cancel(); + } + + @override + Future connectToNode({required Node node}) async { + syncStatus = ConnectingSyncStatus(); + await setupNode(node.uriRaw); + syncStatus = ConnectedSyncStatus(); + } + + @override + Future createTransaction(Object credentials) async { + credentials as ZanoTransactionCredentials; + final isZano = credentials.currency == CryptoCurrency.zano; + final outputs = credentials.outputs; + final hasMultiDestination = outputs.length > 1; + final unlockedBalanceZano = balance[CryptoCurrency.zano]?.unlocked ?? BigInt.zero; + final unlockedBalanceCurrency = balance[credentials.currency]?.unlocked ?? BigInt.zero; + final fee = BigInt.from(calculateEstimatedFee(credentials.priority)); + late BigInt totalAmount; + void checkForEnoughBalances() { + if (isZano) { + if (totalAmount + fee > unlockedBalanceZano) { + throw ZanoTransactionCreationException( + "You don't have enough coins (required: ${ZanoFormatter.bigIntAmountToString(totalAmount + fee)} ZANO, unlocked ${ZanoFormatter.bigIntAmountToString(unlockedBalanceZano)} ZANO)."); + } + } else { + if (fee > unlockedBalanceZano) { + throw ZanoTransactionCreationException( + "You don't have enough coins (required: ${ZanoFormatter.bigIntAmountToString(fee)} ZANO, unlocked ${ZanoFormatter.bigIntAmountToString(unlockedBalanceZano)} ZANO)."); + } + if (totalAmount > unlockedBalanceCurrency) { + throw ZanoTransactionCreationException( + "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})."); + } + } + } + + final assetId = isZano ? zanoAssetId : (credentials.currency as ZanoAsset).assetId; + late List destinations; + if (hasMultiDestination) { + if (outputs.any((output) => output.sendAll || (output.formattedCryptoAmount ?? 0) <= 0)) { + throw ZanoTransactionCreationException("You don't have enough coins."); + } + totalAmount = outputs.fold(BigInt.zero, (acc, value) => acc + BigInt.from(value.formattedCryptoAmount ?? 0)); + checkForEnoughBalances(); + destinations = outputs + .map((output) => Destination( + amount: BigInt.from(output.formattedCryptoAmount ?? 0), + address: output.isParsedAddress ? output.extractedAddress! : output.address, + assetId: assetId, + )) + .toList(); + } else { + final output = outputs.first; + if (output.sendAll) { + if (isZano) { + totalAmount = unlockedBalanceZano - fee; + } else { + totalAmount = unlockedBalanceCurrency; + } + } else { + totalAmount = BigInt.from(output.formattedCryptoAmount!); + } + checkForEnoughBalances(); + destinations = [ + Destination( + amount: totalAmount, + address: output.isParsedAddress ? output.extractedAddress! : output.address, + assetId: assetId, + ) + ]; + } + return PendingZanoTransaction( + zanoWallet: this, + destinations: destinations, + fee: fee, + comment: outputs.first.note ?? '', + assetId: assetId, + ticker: credentials.currency.title, + decimalPoint: credentials.currency.decimals, + amount: totalAmount, + ); + } + + @override + Future> fetchTransactions() async { + try { + final transfers = []; + late GetRecentTxsAndInfoResult result; + do { + result = await getRecentTxsAndInfo(offset: 0, count: _txChunkSize); + // _lastTxIndex += result.transfers.length; + transfers.addAll(result.transfers); + } while (result.lastItemIndex + 1 < result.totalTransfers); + return Transfer.makeMap(transfers, zanoAssets, currentDaemonHeight); + } catch (e) { + ZanoWalletApi.error(e.toString()); + return {}; + } + } + + Future init(String address) async { + await walletAddresses.init(); + await walletAddresses.updateAddress(address); + await updateTransactions(); + _autoSaveTimer = Timer.periodic(Duration(seconds: _autoSaveIntervalSeconds), (_) async { + await save(); + }); + } + + @override + Future renameWalletFiles(String newWalletName) async { + final currentWalletPath = await pathForWallet(name: name, type: type); + final currentCacheFile = File(currentWalletPath); + final currentKeysFile = File('$currentWalletPath.keys'); + final currentAddressListFile = File('$currentWalletPath.address.txt'); + + final newWalletPath = await pathForWallet(name: newWalletName, type: type); + + // Copies current wallet files into new wallet name's dir and files + if (currentCacheFile.existsSync()) { + await currentCacheFile.copy(newWalletPath); + } + if (currentKeysFile.existsSync()) { + await currentKeysFile.copy('$newWalletPath.keys'); + } + if (currentAddressListFile.existsSync()) { + await currentAddressListFile.copy('$newWalletPath.address.txt'); + } + + // Delete old name's dir and files + await Directory(currentWalletPath).delete(recursive: true); + } + + @override + Future rescan({required int height}) => throw UnimplementedError(); + + @override + Future save() async { + try { + await store(); + await walletAddresses.updateAddressesInBox(); + } catch (e) { + ZanoWalletApi.error('Error while saving Zano wallet file ${e.toString()}'); + } + } + + Future loadAssets(List balances, {int maxRetries = 1}) async { + List assets = []; + int retryCount = 0; + + while (retryCount < maxRetries) { + try { + assets = await getAssetsWhitelist(); + break; + } on ZanoWalletBusyException { + if (retryCount < maxRetries - 1) { + retryCount++; + await Future.delayed(Duration(seconds: 1)); + } else { + ZanoWalletApi.error('failed to load assets after $retryCount retries'); + break; + } + } + } + zanoAssets = {}; + for (final asset in assets) { + final newAsset = ZanoAsset.copyWith(asset, + icon: _getIconPath(asset.title), enabled: balances.any((element) => element.assetId == asset.assetId)); + zanoAssets.putIfAbsent(asset.assetId, () => newAsset); + } + } + + @override + Future startSync() async { + try { + syncStatus = AttemptingSyncStatus(); + _lastKnownBlockHeight = 0; + _initialSyncHeight = 0; + _updateSyncInfoTimer ??= Timer.periodic(Duration(milliseconds: _pollIntervalMilliseconds), (_) async { + GetWalletStatusResult walletStatus; + // ignoring get wallet status exception (in case of wrong wallet id) + try { + walletStatus = await getWalletStatus(); + } on ZanoWalletException { + return; + } + currentDaemonHeight = walletStatus.currentDaemonHeight; + _updateSyncProgress(walletStatus); + + // we can call getWalletInfo ONLY if getWalletStatus returns NOT is in long refresh and wallet state is 2 (ready) + if (!walletStatus.isInLongRefresh && walletStatus.walletState == 2) { + final walletInfo = await getWalletInfo(); + seed = walletInfo.wiExtended.seed; + keys = ZanoWalletKeys( + privateSpendKey: walletInfo.wiExtended.spendPrivateKey, + privateViewKey: walletInfo.wiExtended.viewPrivateKey, + publicSpendKey: walletInfo.wiExtended.spendPublicKey, + publicViewKey: walletInfo.wiExtended.viewPublicKey, + ); + loadAssets(walletInfo.wi.balances); + // matching balances and whitelists + // 1. show only balances available in whitelists + // 2. set whitelists available in balances as 'enabled' ('disabled' by default) + for (final b in walletInfo.wi.balances) { + if (b.assetId == zanoAssetId) { + balance[CryptoCurrency.zano] = ZanoBalance(total: b.total, unlocked: b.unlocked); + } else { + final asset = zanoAssets[b.assetId]; + if (asset == null) { + ZanoWalletApi.error('balance for an unknown asset ${b.assetInfo.assetId}'); + continue; + } + if (balance.keys.any((element) => element is ZanoAsset && element.assetId == b.assetInfo.assetId)) { + balance[balance.keys.firstWhere((element) => element is ZanoAsset && element.assetId == b.assetInfo.assetId)] = + ZanoBalance(total: b.total, unlocked: b.unlocked, decimalPoint: asset.decimalPoint); + } else { + balance[asset] = ZanoBalance(total: b.total, unlocked: b.unlocked, decimalPoint: asset.decimalPoint); + } + } + } + await updateTransactions(); + // removing balances for assets missing in wallet info balances + balance.removeWhere( + (key, _) => key != CryptoCurrency.zano && !walletInfo.wi.balances.any((element) => element.assetId == (key as ZanoAsset).assetId), + ); + } + }); + } catch (e) { + syncStatus = FailedSyncStatus(); + ZanoWalletApi.error(e.toString()); + //rethrow; // TODO: we don't need to propagate exception here + } + } + + @override + Future? updateBalance() => null; + + Future updateTransactions() async { + try { + if (_isTransactionUpdating) { + return; + } + _isTransactionUpdating = true; + final transactions = await fetchTransactions(); + if (transactions.length == transactionHistory.transactions.length) { + _isTransactionUpdating = false; + return; + } + transactionHistory.clear(); + transactionHistory.addMany(transactions); + await transactionHistory.save(); + _isTransactionUpdating = false; + } catch (e) { + printV("e: $e"); + ZanoWalletApi.error(e.toString()); + _isTransactionUpdating = false; + } + } + + Future addZanoAssetById(String assetId) async { + if (zanoAssets.containsKey(assetId)) { + throw ZanoWalletException('zano asset with id $assetId already added'); + } + final assetDescriptor = await addAssetsWhitelist(assetId); + if (assetDescriptor == null) { + throw ZanoWalletException("there's no zano asset with id $assetId"); + } + final asset = ZanoAsset.copyWith(assetDescriptor, icon: _getIconPath(assetDescriptor.title), assetId: assetId, enabled: true); + zanoAssets[asset.assetId] = asset; + balance[asset] = ZanoBalance.empty(decimalPoint: asset.decimalPoint); + return asset; + } + + String? _getIconPath(String title) { + try { + return CryptoCurrency.all.firstWhere((element) => element.title.toUpperCase() == title.toUpperCase()).iconPath; + } catch (_) {} + return null; + } + + Future changeZanoAssetAvailability(ZanoAsset asset) async { + if (asset.enabled) { + final assetDescriptor = await addAssetsWhitelist(asset.assetId); + if (assetDescriptor == null) { + ZanoWalletApi.error('Error adding zano asset'); + } + } else { + final result = await removeAssetsWhitelist(asset.assetId); + if (result == false) { + ZanoWalletApi.error('Error removing zano asset'); + } + } + } + + Future deleteZanoAsset(ZanoAsset asset) async { + final _ = await removeAssetsWhitelist(asset.assetId); + } + + Future getZanoAsset(String assetId) async { + return await getAssetInfo(assetId); + } + + Future _askForUpdateTransactionHistory() async => await updateTransactions(); + + void _onNewBlock(int height, int blocksLeft, double ptc) async { + try { + if (blocksLeft < 1000) { + await _askForUpdateTransactionHistory(); + syncStatus = SyncedSyncStatus(); + + if (!_hasSyncAfterStartup) { + _hasSyncAfterStartup = true; + await save(); + } + } else { + syncStatus = SyncingSyncStatus(blocksLeft, ptc); + } + } catch (e) { + ZanoWalletApi.error(e.toString()); + } + } + + void _updateSyncProgress(GetWalletStatusResult walletStatus) { + final syncHeight = walletStatus.currentWalletHeight; + if (_initialSyncHeight <= 0) { + _initialSyncHeight = syncHeight; + } + final bchHeight = walletStatus.currentDaemonHeight; + + if (_lastKnownBlockHeight == syncHeight) { + return; + } + + _lastKnownBlockHeight = syncHeight; + final track = bchHeight - _initialSyncHeight; + final diff = track - (bchHeight - syncHeight); + final ptc = diff <= 0 ? 0.0 : diff / track; + final left = bchHeight - syncHeight; + + if (syncHeight < 0 || left < 0) { + return; + } + + // 1. Actual new height; 2. Blocks left to finish; 3. Progress in percents; + _onNewBlock.call(syncHeight, left, ptc); + } + +} diff --git a/cw_zano/lib/zano_wallet_addresses.dart b/cw_zano/lib/zano_wallet_addresses.dart new file mode 100644 index 0000000000..e485e85253 --- /dev/null +++ b/cw_zano/lib/zano_wallet_addresses.dart @@ -0,0 +1,40 @@ +import 'package:cw_core/wallet_addresses.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_zano/zano_wallet_api.dart'; +import 'package:mobx/mobx.dart'; + +part 'zano_wallet_addresses.g.dart'; + +class ZanoWalletAddresses = ZanoWalletAddressesBase with _$ZanoWalletAddresses; + +abstract class ZanoWalletAddressesBase extends WalletAddresses with Store { + ZanoWalletAddressesBase(WalletInfo walletInfo) + : address = '', + super(walletInfo); + + @override + @observable + String address; + + @override + Future init() async { + address = walletInfo.address; + await updateAddressesInBox(); + } + + Future updateAddress(String address) async { + this.address = address; + await updateAddressesInBox(); + } + + @override + Future updateAddressesInBox() async { + try { + addressesMap.clear(); + addressesMap[address] = ''; + await saveAddressesInBox(); + } catch (e) { + ZanoWalletApi.error(e.toString()); + } + } +} diff --git a/cw_zano/lib/zano_wallet_api.dart b/cw_zano/lib/zano_wallet_api.dart new file mode 100644 index 0000000000..4c6a270a04 --- /dev/null +++ b/cw_zano/lib/zano_wallet_api.dart @@ -0,0 +1,472 @@ +import 'dart:convert' as convert; +import 'dart:ffi'; +import 'dart:isolate'; + +import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/transaction_priority.dart'; +import 'package:cw_core/utils/print_verbose.dart'; +import 'package:cw_core/zano_asset.dart'; +import 'package:cw_zano/api/consts.dart'; +import 'package:cw_zano/api/model/asset_id_params.dart'; +import 'package:cw_zano/api/model/create_wallet_result.dart'; +import 'package:cw_zano/api/model/destination.dart'; +import 'package:cw_zano/api/model/get_address_info_result.dart'; +import 'package:cw_zano/api/model/get_recent_txs_and_info_params.dart'; +import 'package:cw_zano/api/model/get_recent_txs_and_info_result.dart'; +import 'package:cw_zano/api/model/get_wallet_info_result.dart'; +import 'package:cw_zano/api/model/get_wallet_status_result.dart'; +import 'package:cw_zano/api/model/proxy_to_daemon_params.dart'; +import 'package:cw_zano/api/model/proxy_to_daemon_result.dart'; +import 'package:cw_zano/api/model/store_result.dart'; +import 'package:cw_zano/api/model/transfer.dart'; +import 'package:cw_zano/api/model/transfer_params.dart'; +import 'package:cw_zano/api/model/transfer_result.dart'; +import 'package:cw_zano/model/zano_asset.dart'; +import 'package:cw_zano/zano_wallet_exceptions.dart'; +import 'package:ffi/ffi.dart'; +import 'package:flutter/foundation.dart'; +import 'package:json_bigint/json_bigint.dart'; +import 'package:monero/zano.dart' as zano; +import 'package:monero/src/generated_bindings_zano.g.dart' as zanoapi; + +mixin ZanoWalletApi { + static const _statusDelivered = 'delivered'; + static const _maxInvokeAttempts = 10; + static const _maxReopenAttempts = 5; + static const _logInfo = false; + static const _logError = true; + static const _logJson = false; + static const int _zanoMixinValue = 10; + + int _hWallet = 0; + + int get hWallet => _hWallet; + + set hWallet(int value) { + _hWallet = value; + } + + int getCurrentTxFee(TransactionPriority priority) => zano.PlainWallet_getCurrentTxFee(priority.raw); + + void setPassword(String password) => zano.PlainWallet_resetWalletPassword(hWallet, password); + + void closeWallet([int? walletToClose]) async { + info('close_wallet ${walletToClose ?? hWallet}'); + final result = await _closeWallet(walletToClose ?? hWallet); + info('close_wallet result $result'); + } + + Future initWallet() async { + // pathForWallet(name: , type: type) + final result = zano.PlainWallet_init("", "", 0); + printV(result); + return result == "OK"; + } + + Future setupNode(String nodeUrl) async { + await _setupNode(hWallet, nodeUrl); + return true; + } + + Future getWalletInfo() async { + final json = await _getWalletInfo(hWallet); + final result = GetWalletInfoResult.fromJson(jsonDecode(json) as Map); + _json('get_wallet_info', json); + info('get_wallet_info got ${result.wi.balances.length} balances: ${result.wi.balances} seed: ${_shorten(result.wiExtended.seed)}'); + return result; + } + + Future getWalletStatus() async { + final json = await _getWalletStatus(hWallet); + if (json == Consts.errorWalletWrongId) { + error('wrong wallet id'); + throw ZanoWalletException('Wrong wallet id'); + } + final status = GetWalletStatusResult.fromJson(jsonDecode(json) as Map); + _json('get_wallet_status', json); + if (_logInfo) + info( + '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)}%'); + return status; + } + + Future invokeMethod(String methodName, Object params) async { + final request = jsonEncode({ + "method": methodName, + "params": params, + }); + final invokeResult = await callSyncMethod('invoke', hWallet, request); + Map map; + try { + map = jsonDecode(invokeResult); + } catch (e) { + if (invokeResult.contains(Consts.errorWalletWrongId)) throw ZanoWalletException('Wrong wallet id'); + error('exception in parsing json in invokeMethod: $invokeResult'); + rethrow; + } + return invokeResult; + } + + Future> getAssetsWhitelist() async { + try { + final json = await invokeMethod('assets_whitelist_get', '{}'); + _json('assets_whitelist_get', json); + final map = jsonDecode(json) as Map?; + _checkForErrors(map); + List assets(String type, bool isGlobalWhitelist) => + (map?['result']?[type] as List?) + ?.map((e) => ZanoAsset.fromJson(e as Map, isInGlobalWhitelist: isGlobalWhitelist)) + .toList() ?? + []; + final localWhitelist = assets('local_whitelist', false); + final globalWhitelist = assets('global_whitelist', true); + final ownAssets = assets('own_assets', false); + if (_logInfo) + info('assets_whitelist_get got local whitelist: ${localWhitelist.length} ($localWhitelist); ' + 'global whitelist: ${globalWhitelist.length} ($globalWhitelist); ' + 'own assets: ${ownAssets.length} ($ownAssets)'); + return [...globalWhitelist, ...localWhitelist, ...ownAssets]; + } catch (e) { + error('assets_whitelist_get $e'); + return []; + // rethrow; + } + } + + Future addAssetsWhitelist(String assetId) async { + try { + final json = await invokeMethod('assets_whitelist_add', AssetIdParams(assetId: assetId)); + _json('assets_whitelist_add $assetId', json); + final map = jsonDecode(json) as Map?; + _checkForErrors(map); + if (map!['result']!['status']! == 'OK') { + final assetDescriptor = ZanoAsset.fromJson(map['result']!['asset_descriptor']! as Map); + info('assets_whitelist_add added ${assetDescriptor.fullName} ${assetDescriptor.ticker}'); + return assetDescriptor; + } else { + info('assets_whitelist_add status ${map['result']!['status']!}'); + return null; + } + } catch (e) { + error('assets_whitelist_add $e'); + return null; + } + } + + Future removeAssetsWhitelist(String assetId) async { + try { + final json = await invokeMethod('assets_whitelist_remove', AssetIdParams(assetId: assetId)); + _json('assets_whitelist_remove $assetId', json); + final map = jsonDecode(json) as Map?; + _checkForErrors(map); + info('assets_whitelist_remove status ${map!['result']!['status']!}'); + return (map['result']!['status']! == 'OK'); + } catch (e) { + error('assets_whitelist_remove $e'); + return false; + } + } + + Future _proxyToDaemon(String uri, String body) async { + final json = await invokeMethod('proxy_to_daemon', ProxyToDaemonParams(body: body, uri: uri)); + final map = jsonDecode(json) as Map?; + _checkForErrors(map); + return ProxyToDaemonResult.fromJson(map!['result']['result'] as Map); + } + + Future getAssetInfo(String assetId) async { + final methodName = 'get_asset_info'; + final params = AssetIdParams(assetId: assetId); + final result = await _proxyToDaemon('/json_rpc', '{"method": "$methodName","params": ${jsonEncode(params)}}'); + _json('$methodName $assetId', result?.body ?? ''); + if (result == null) { + error('get_asset_info empty result'); + return null; + } + final map = jsonDecode(result.body) as Map?; + if (map!['error'] != null) { + info('get_asset_info $assetId error ${map['error']!['code']} ${map['error']!['message']}'); + return null; + } else if (map['result']!['status']! == 'OK') { + final assetDescriptor = ZanoAsset.fromJson(map['result']!['asset_descriptor']! as Map); + info('get_asset_info $assetId ${assetDescriptor.fullName} ${assetDescriptor.ticker}'); + return assetDescriptor; + } else { + info('get_asset_info $assetId status ${map['result']!['status']!}'); + return null; + } + } + + Future store() async { + try { + final json = await invokeMethod('store', '{}'); + final map = jsonDecode(json) as Map?; + _checkForErrors(map); + return StoreResult.fromJson(map!['result'] as Map); + } catch (e) { + error('store $e'); + return null; + } + } + + Future getRecentTxsAndInfo({required int offset, required int count}) async { + info('get_recent_txs_and_info $offset $count'); + try { + final json = await invokeMethod('get_recent_txs_and_info', GetRecentTxsAndInfoParams(offset: offset, count: count)); + _json('get_recent_txs_and_info', json); + final map = jsonDecode(json) as Map?; + _checkForErrors(map); + final lastItemIndex = map?['result']?['last_item_index'] as int?; + final totalTransfers = map?['result']?['total_transfers'] as int?; + final transfers = map?['result']?['transfers'] as List?; + if (transfers == null || lastItemIndex == null || totalTransfers == null) { + error('get_recent_txs_and_info empty transfers'); + return GetRecentTxsAndInfoResult.empty(); + } + info('get_recent_txs_and_info transfers.length: ${transfers.length}'); + return GetRecentTxsAndInfoResult( + transfers: transfers.map((e) => Transfer.fromJson(e as Map)).toList(), + lastItemIndex: lastItemIndex, + totalTransfers: totalTransfers, + ); + } catch (e) { + error('get_recent_txs_and_info $e'); + return GetRecentTxsAndInfoResult.empty(); + } + } + + GetAddressInfoResult getAddressInfo(String address) => GetAddressInfoResult.fromJson( + jsonDecode(zano.PlainWallet_getAddressInfo(address)) as Map, + ); + + String _shorten(String s) => s.length > 10 ? '${s.substring(0, 4)}...${s.substring(s.length - 4)}' : s; + + Future createWallet(String path, String password) async { + info('create_wallet path $path password ${_shorten(password)}'); + final json = zano.PlainWallet_generate(path, password); + _json('create_wallet', json); + final map = jsonDecode(json) as Map?; + if (map?['error'] != null) { + final code = map!['error']?['code'] ?? ''; + final message = map['error']?['message'] ?? ''; + throw ZanoWalletException('Error creating wallet file, $message ($code)'); + } + if (map?['result'] == null) { + throw ZanoWalletException('Error creating wallet file, empty response'); + } + final result = CreateWalletResult.fromJson(map!['result'] as Map); + info('create_wallet ${result.name} ${result.seed}'); + return result; + } + + Future restoreWalletFromSeed(String path, String password, String seed) async { + info('restore_wallet path $path password ${_shorten(password)} seed ${_shorten(seed)}'); + final json = zano.PlainWallet_restore(seed, path, password, ""); + _json('restore_wallet', json); + final map = jsonDecode(json) as Map?; + if (map?['error'] != null) { + final code = map!['error']!['code'] ?? ''; + final message = map['error']!['message'] ?? ''; + if (code == Consts.errorWrongSeed) { + throw RestoreFromKeysException('Error restoring wallet, wrong seed'); + } else if (code == Consts.errorAlreadyExists) { + throw RestoreFromKeysException('Error restoring wallet, already exists'); + } + throw RestoreFromKeysException('Error restoring wallet, $message ($code)'); + } + if (map?['result'] == null) { + throw RestoreFromKeysException('Error restoring wallet, empty response'); + } + final result = CreateWalletResult.fromJson(map!['result'] as Map); + info('restore_wallet ${result.name} ${result.wi.address}'); + return result; + } + + FutureloadWallet(String path, String password, [int attempt = 0]) async { + info('load_wallet1 path $path password ${_shorten(password)}'); + final String json; + try { + json = zano.PlainWallet_open(path, password); + } catch (e) { + error('error in loadingWallet $e'); + rethrow; + } + info('load_wallet2: $json'); + final map = jsonDecode(json) as Map?; + if (map?['error'] != null) { + final code = map?['error']!['code'] ?? ''; + final message = map?['error']!['message'] ?? ''; + if (code == Consts.errorAlreadyExists && attempt <= _maxReopenAttempts) { + // already connected to this wallet. closing and trying to reopen + info('already connected. closing and reopen wallet (attempt $attempt)'); + closeWallet(attempt); + await Future.delayed(const Duration(milliseconds: 500)); + return await loadWallet(path, password, attempt + 1); + } + throw ZanoWalletException('Error loading wallet, $message ($code)'); + } + if (map?['result'] == null) { + throw ZanoWalletException('Error loading wallet, empty response'); + } + final result = CreateWalletResult.fromJson(map!['result'] as Map); + info('load_wallet3 ${result.name} ${result.wi.address}'); + return result; + } + + Future transfer(List destinations, BigInt fee, String comment) async { + final params = TransferParams( + destinations: destinations, + fee: fee, + mixin: _zanoMixinValue, + paymentId: '', + comment: comment, + pushPayer: false, + hideReceiver: true, + ); + final json = await invokeMethod('transfer', params); + _json('transfer', json); + final map = jsonDecode(json); + final resultMap = map as Map?; + if (resultMap != null) { + final transferResultMap = resultMap['result'] as Map?; + if (transferResultMap != null) { + final transferResult = TransferResult.fromJson(transferResultMap); + info('transfer success hash ${transferResult.txHash}'); + return transferResult; + } else { + final errorCode = resultMap['error']?['code']; + final code = errorCode is int ? errorCode.toString() : errorCode as String? ?? ''; + final message = resultMap['error']?['message'] as String? ?? ''; + error('transfer error $code $message'); + throw TransferException('Transfer error, $message ($code)'); + } + } + error('transfer error empty result'); + throw TransferException('Transfer error, empty result'); + } + + void _checkForErrors(Map? map) { + if (map == null) { + throw ZanoWalletException('Empty response'); + } + final result = map['result']; + if (result == null) { + throw ZanoWalletException('Empty response'); + } + if (result['error'] != null) { + final code = result['error']!['code'] ?? ''; + final message = result['error']!['message'] ?? ''; + if (code == -1 && message == Consts.errorBusy) { + throw ZanoWalletBusyException(); + } + throw ZanoWalletException('Error, $message ($code)'); + } + } + + /*Future _writeLog(String method, String logMessage) async { + final dir = await getDownloadsDirectory(); + final logFile = File('${dir!.path}/$method.txt'); + final date = DateTime.now(); + String twoDigits(int value) => value.toString().padLeft(2, '0'); + String removeCRandLF(String input) => input.replaceAll(RegExp('\r|\n'), ''); + await logFile.writeAsString('${twoDigits(date.hour)}:${twoDigits(date.minute)}:${twoDigits(date.second)} ${removeCRandLF(logMessage)}\n', + mode: FileMode.append); + }*/ + + static void info(String s) => _logInfo ? debugPrint('[info] $s') : null; + static void error(String s) => _logError ? debugPrint('[error] $s') : null; + static void printWrapped(String text) => RegExp('.{1,800}').allMatches(text).map((m) => m.group(0)).forEach(print); + static void _json(String methodName, String json) => _logJson ? printWrapped('$methodName $json') : null; + +} + +Future callSyncMethod(String methodName, int hWallet, String params) async { + final params_ = params.toNativeUtf8().address; + final method_name_ = methodName.toNativeUtf8().address; + final invokeResult = await Isolate.run(() async { + final lib = zanoapi.ZanoC(DynamicLibrary.open(zano.libPath)); + final txid = lib.ZANO_PlainWallet_syncCall( + Pointer.fromAddress(method_name_).cast(), + hWallet, + Pointer.fromAddress(params_).cast() + ); + try { + final strPtr = txid.cast(); + final str = strPtr.toDartString(); + lib.ZANO_free(strPtr.cast()); + return str; + } catch (e) { + return ""; + } + }); + calloc.free(Pointer.fromAddress(method_name_)); + calloc.free(Pointer.fromAddress(params_)); + return invokeResult; +} + +Map jsonDecode(String json) { + try { + return decodeJson(json.replaceAll("\\/", "/")) as Map; + } catch (e) { + return convert.jsonDecode(json) as Map; + } +} + +String jsonEncode(Object? object) { + return convert.jsonEncode(object); +} + +Future _getWalletStatus(int hWallet) async { + final jsonPtr = await Isolate.run(() async { + final lib = zanoapi.ZanoC(DynamicLibrary.open(zano.libPath)); + final status = lib.ZANO_PlainWallet_getWalletStatus( + hWallet, + ); + return status.address; + }); + String json = ""; + try { + final strPtr = Pointer.fromAddress(jsonPtr).cast(); + final str = strPtr.toDartString(); + zano.ZANO_free(strPtr.cast()); + json = str; + } catch (e) { + json = ""; + } + return json; +} +Future _getWalletInfo(int hWallet) async { + final jsonPtr = await Isolate.run(() async { + final lib = zanoapi.ZanoC(DynamicLibrary.open(zano.libPath)); + final status = lib.ZANO_PlainWallet_getWalletInfo( + hWallet, + ); + return status.address; + }); + String json = ""; + try { + final strPtr = Pointer.fromAddress(jsonPtr).cast(); + final str = strPtr.toDartString(); + zano.ZANO_free(strPtr.cast()); + json = str; + } catch (e) { + json = ""; + } + return json; +} + +Future _setupNode(int hWallet, String nodeUrl) async { + final resp = await callSyncMethod("reset_connection_url", hWallet, nodeUrl); + printV(resp); + final resp2 = await callSyncMethod("run_wallet", hWallet, ""); + printV(resp2); + return "OK"; +} + +Future _closeWallet(int hWallet) async { + final str = await Isolate.run(() async { + return zano.PlainWallet_closeWallet(hWallet); + }); + printV("Closing wallet: $str"); + return str; +} \ No newline at end of file diff --git a/cw_zano/lib/zano_wallet_exceptions.dart b/cw_zano/lib/zano_wallet_exceptions.dart new file mode 100644 index 0000000000..57767ef90b --- /dev/null +++ b/cw_zano/lib/zano_wallet_exceptions.dart @@ -0,0 +1,19 @@ +class ZanoWalletException implements Exception { + final String message; + + ZanoWalletException(this.message); + @override + String toString() => '${this.runtimeType} (message: $message)'; +} + +class RestoreFromKeysException extends ZanoWalletException { + RestoreFromKeysException(String message) : super(message); +} + +class TransferException extends ZanoWalletException { + TransferException(String message): super(message); +} + +class ZanoWalletBusyException extends ZanoWalletException { + ZanoWalletBusyException(): super(''); +} \ No newline at end of file diff --git a/cw_zano/lib/zano_wallet_service.dart b/cw_zano/lib/zano_wallet_service.dart new file mode 100644 index 0000000000..b175d6bf2c --- /dev/null +++ b/cw_zano/lib/zano_wallet_service.dart @@ -0,0 +1,123 @@ +import 'dart:io'; + +import 'package:collection/collection.dart'; +import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_credentials.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_service.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:cw_zano/zano_wallet.dart'; +import 'package:cw_zano/zano_wallet_api.dart'; +import 'package:hive/hive.dart'; +import 'package:monero/zano.dart' as zano; + +class ZanoNewWalletCredentials extends WalletCredentials { + ZanoNewWalletCredentials({required String name, String? password}) : super(name: name, password: password); +} + +class ZanoRestoreWalletFromSeedCredentials extends WalletCredentials { + ZanoRestoreWalletFromSeedCredentials({required String name, required String password, required int height, required this.mnemonic}) + : super(name: name, password: password, height: height); + + final String mnemonic; +} + +class ZanoRestoreWalletFromKeysCredentials extends WalletCredentials { + ZanoRestoreWalletFromKeysCredentials( + {required String name, + required String password, + required this.language, + required this.address, + required this.viewKey, + required this.spendKey, + required int height}) + : super(name: name, password: password, height: height); + + final String language; + final String address; + final String viewKey; + final String spendKey; +} + +class ZanoWalletService extends WalletService { + ZanoWalletService(this.walletInfoSource); + + final Box walletInfoSource; + + static bool walletFilesExist(String path) => !File(path).existsSync() && !File('$path.keys').existsSync(); + + int hWallet = 0; + + @override + WalletType getType() => WalletType.zano; + + @override + Future create(WalletCredentials credentials, {bool? isTestnet}) async { + ZanoWalletApi.info('zanowallet service create isTestnet $isTestnet'); + return await ZanoWalletBase.create(credentials: credentials); + } + + @override + Future isWalletExit(String name) async { + final path = await pathForWallet(name: name, type: getType()); + return zano.PlainWallet_isWalletExist(path); + } + + @override + Future openWallet(String name, String password) async { + final walletInfo = walletInfoSource.values.firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!; + try { + final wallet = await ZanoWalletBase.open(name: name, password: password, walletInfo: walletInfo); + saveBackup(name); + return wallet; + } catch (e) { + await restoreWalletFilesFromBackup(name); + return await ZanoWalletBase.open(name: name, password: password, walletInfo: walletInfo); + } + } + + @override + Future remove(String wallet) async { + final path = await pathForWalletDir(name: wallet, type: getType()); + final file = Directory(path); + final isExist = file.existsSync(); + + if (isExist) { + await file.delete(recursive: true); + } + + final walletInfo = walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(wallet, getType())); + await walletInfoSource.delete(walletInfo.key); + } + + @override + Future rename(String currentName, String password, String newName) async { + final currentWalletInfo = walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(currentName, getType())); + final currentWallet = ZanoWallet(currentWalletInfo, password); + + await currentWallet.renameWalletFiles(newName); + + final newWalletInfo = currentWalletInfo; + newWalletInfo.id = WalletBase.idFor(newName, getType()); + newWalletInfo.name = newName; + + await walletInfoSource.put(currentWalletInfo.key, newWalletInfo); + } + + @override + Future restoreFromKeys(ZanoRestoreWalletFromKeysCredentials credentials, {bool? isTestnet}) async { + throw UnimplementedError(); + } + + @override + Future restoreFromSeed(ZanoRestoreWalletFromSeedCredentials credentials, {bool? isTestnet}) async { + return ZanoWalletBase.restore(credentials: credentials); + } + + @override + Future restoreFromHardwareWallet(ZanoNewWalletCredentials credentials) { + throw UnimplementedError("Restoring a Zano wallet from a hardware wallet is not yet supported!"); + } +} diff --git a/cw_zano/pubspec.lock b/cw_zano/pubspec.lock new file mode 100644 index 0000000000..089069042a --- /dev/null +++ b/cw_zano/pubspec.lock @@ -0,0 +1,827 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: "4897882604d919befd350648c7f91926a9d5de99e67b455bf0917cc2362f4bb8" + url: "https://pub.dev" + source: hosted + version: "47.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "690e335554a8385bc9d787117d9eb52c0c03ee207a607e593de3c9d71b1cfe80" + url: "https://pub.dev" + source: hosted + version: "4.7.0" + args: + dependency: transitive + description: + name: args + sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 + url: "https://pub.dev" + source: hosted + version: "2.6.0" + asn1lib: + dependency: transitive + description: + name: asn1lib + sha256: "4bae5ae63e6d6dd17c4aac8086f3dec26c0236f6a0f03416c6c19d830c367cf5" + url: "https://pub.dev" + source: hosted + version: "1.5.8" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + build: + dependency: transitive + description: + name: build + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + build_config: + dependency: transitive + description: + name: build_config + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + url: "https://pub.dev" + source: hosted + version: "1.1.1" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + build_resolvers: + dependency: "direct dev" + description: + name: build_resolvers + sha256: "687cf90a3951affac1bd5f9ecb5e3e90b60487f3d9cdc359bb310f8876bb02a6" + url: "https://pub.dev" + source: hosted + version: "2.0.10" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" + url: "https://pub.dev" + source: hosted + version: "2.4.13" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: "6d6ee4276b1c5f34f21fdf39425202712d2be82019983d52f351c94aafbc2c41" + url: "https://pub.dev" + source: hosted + version: "7.2.10" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb + url: "https://pub.dev" + source: hosted + version: "8.9.2" + cake_backup: + dependency: transitive + description: + path: "." + ref: main + resolved-ref: "3aba867dcab6737f6707782f5db15d71f303db38" + url: "https://github.com/cake-tech/cake_backup.git" + source: git + version: "1.0.0+1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev" + source: hosted + version: "2.0.3" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" + url: "https://pub.dev" + source: hosted + version: "4.10.1" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + cryptography: + dependency: transitive + description: + name: cryptography + sha256: d146b76d33d94548cf035233fbc2f4338c1242fa119013bead807d033fc4ae05 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + cupertino_icons: + dependency: transitive + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.dev" + source: hosted + version: "1.0.8" + cw_core: + dependency: "direct main" + description: + path: "../cw_core" + relative: true + source: path + version: "0.0.1" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "7a03456c3490394c8e7665890333e91ae8a49be43542b616e414449ac358acd4" + url: "https://pub.dev" + source: hosted + version: "2.2.4" + decimal: + dependency: "direct main" + description: + name: decimal + sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21" + url: "https://pub.dev" + source: hosted + version: "2.3.3" + encrypt: + dependency: transitive + description: + name: encrypt + sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2" + url: "https://pub.dev" + source: hosted + version: "5.0.3" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + ffi: + dependency: "direct main" + description: + name: ffi + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_mobx: + dependency: "direct main" + description: + name: flutter_mobx + sha256: "859fbf452fa9c2519d2700b125dd7fb14c508bbdd7fb65e26ca8ff6c92280e2e" + url: "https://pub.dev" + source: hosted + version: "2.2.1+1" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + fluttertoast: + dependency: "direct main" + description: + name: fluttertoast + sha256: "95f349437aeebe524ef7d6c9bde3e6b4772717cf46a0eb6a3ceaddc740b297cc" + url: "https://pub.dev" + source: hosted + version: "8.2.8" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + hive: + dependency: transitive + description: + name: hive + sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + hive_generator: + dependency: "direct dev" + description: + name: hive_generator + sha256: "81fd20125cb2ce8fd23623d7744ffbaf653aae93706c9bd3bf7019ea0ace3938" + url: "https://pub.dev" + source: hosted + version: "1.1.3" + http: + dependency: "direct main" + description: + name: http + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 + url: "https://pub.dev" + source: hosted + version: "1.2.2" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + intl: + dependency: "direct main" + description: + name: intl + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + url: "https://pub.dev" + source: hosted + version: "0.19.0" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.dev" + source: hosted + version: "1.0.5" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + json_bigint: + dependency: "direct main" + description: + name: json_bigint + sha256: "9e613e731847ab2154d67160682adf104cbd9863741ec2f7abfcf6e77c70592f" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + url: "https://pub.dev" + source: hosted + version: "10.0.5" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + url: "https://pub.dev" + source: hosted + version: "3.0.5" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" + source: hosted + version: "0.12.16+1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + url: "https://pub.dev" + source: hosted + version: "1.15.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + mobx: + dependency: "direct main" + description: + name: mobx + sha256: "1f01a429529ac55e5e80c0fcad62c60112fb91df3dec11a9113d71cf0c2e2c4c" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + mobx_codegen: + dependency: "direct dev" + description: + name: mobx_codegen + sha256: d4beb9cea4b7b014321235f8fdc7c2193ee0fe1d1198e9da7403f8bc85c4407c + url: "https://pub.dev" + source: hosted + version: "2.3.0" + monero: + dependency: "direct main" + description: + path: "impls/monero.dart" + ref: c3dd64bdee37d361a2c1252d127fb575936e43e6 + resolved-ref: c3dd64bdee37d361a2c1252d127fb575936e43e6 + url: "https://github.com/mrcyjanek/monero_c" + source: git + version: "0.0.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + package_config: + dependency: transitive + description: + name: package_config + sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + path: + dependency: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" + url: "https://pub.dev" + source: hosted + version: "2.2.15" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + url: "https://pub.dev" + source: hosted + version: "6.0.2" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe" + url: "https://pub.dev" + source: hosted + version: "3.9.1" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + provider: + dependency: transitive + description: + name: provider + sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c + url: "https://pub.dev" + source: hosted + version: "6.1.2" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + rational: + dependency: transitive + description: + name: rational + sha256: cb808fb6f1a839e6fc5f7d8cb3b0a10e1db48b3be102de73938c627f0b636336 + url: "https://pub.dev" + source: hosted + version: "2.2.3" + shelf: + dependency: transitive + description: + name: shelf + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + url: "https://pub.dev" + source: hosted + version: "1.4.1" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + socks5_proxy: + dependency: transitive + description: + name: socks5_proxy + sha256: "616818a0ea1064a4823b53c9f7eaf8da64ed82dcd51ed71371c7e54751ed5053" + url: "https://pub.dev" + source: hosted + version: "1.0.6" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "2d79738b6bbf38a43920e2b8d189e9a3ce6cc201f4b8fc76be5e4fe377b1c38d" + url: "https://pub.dev" + source: hosted + version: "1.2.6" + source_helper: + dependency: transitive + description: + name: source_helper + sha256: "3b67aade1d52416149c633ba1bb36df44d97c6b51830c2198e934e3fca87ca1f" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + url: "https://pub.dev" + source: hosted + version: "0.7.2" + timing: + dependency: transitive + description: + name: timing + sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + tuple: + dependency: transitive + description: + name: tuple + sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151 + url: "https://pub.dev" + source: hosted + version: "2.0.2" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + unorm_dart: + dependency: transitive + description: + name: unorm_dart + sha256: "23d8bf65605401a6a32cff99435fed66ef3dab3ddcad3454059165df46496a3b" + url: "https://pub.dev" + source: hosted + version: "0.3.0" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc + url: "https://pub.dev" + source: hosted + version: "14.2.4" + watcher: + dependency: "direct overridden" + description: + name: watcher + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + url: "https://pub.dev" + source: hosted + version: "0.1.6" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" +sdks: + dart: ">=3.5.0 <4.0.0" + flutter: ">=3.24.0" diff --git a/cw_zano/pubspec.yaml b/cw_zano/pubspec.yaml new file mode 100644 index 0000000000..5f61f517fa --- /dev/null +++ b/cw_zano/pubspec.yaml @@ -0,0 +1,81 @@ +name: cw_zano +description: A new flutter plugin project. +version: 0.0.1 +publish_to: none +author: Cake Wallet +homepage: https://cakewallet.com + +environment: + sdk: ">=2.19.0 <3.0.0" + flutter: ">=1.20.0" + +dependencies: + flutter: + sdk: flutter + ffi: ^2.0.1 + http: ^1.1.0 + path_provider: ^2.0.11 + mobx: ^2.1.4 + flutter_mobx: ^2.0.6+1 + intl: ^0.19.0 + decimal: ^2.3.3 + cw_core: + path: ../cw_core + json_bigint: ^3.0.0 + fluttertoast: ^8.2.8 + monero: + git: + url: https://github.com/mrcyjanek/monero_c + ref: c3dd64bdee37d361a2c1252d127fb575936e43e6 # monero_c hash + path: impls/monero.dart +dev_dependencies: + flutter_test: + sdk: flutter + build_runner: ^2.4.7 + mobx_codegen: ^2.1.1 + build_resolvers: ^2.0.9 + hive_generator: ^1.1.3 + +dependency_overrides: + watcher: ^1.1.0 + + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + # This section identifies this Flutter project as a plugin project. + # The 'pluginClass' and Android 'package' identifiers should not ordinarily + # be modified. They are used by the tooling to maintain consistency when + # adding or updating assets for this project. + # To add assets to your plugin package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # To add custom fonts to your plugin package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/ios/.gitignore b/ios/.gitignore index 8ded86f140..4f85acc8dc 100644 --- a/ios/.gitignore +++ b/ios/.gitignore @@ -31,4 +31,4 @@ Runner/GeneratedPluginRegistrant.* !default.pbxuser !default.perspectivev3 -Mwebd.xcframework \ No newline at end of file +Mwebd.xcframework diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 9e2a8507a9..0323b38e80 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -3,38 +3,6 @@ PODS: - Flutter - ReachabilitySwift - CryptoSwift (1.8.3) - - cw_haven (0.0.1): - - cw_haven/Boost (= 0.0.1) - - cw_haven/Haven (= 0.0.1) - - cw_haven/OpenSSL (= 0.0.1) - - cw_haven/Sodium (= 0.0.1) - - cw_shared_external - - Flutter - - cw_haven/Boost (0.0.1): - - cw_shared_external - - Flutter - - cw_haven/Haven (0.0.1): - - cw_shared_external - - Flutter - - cw_haven/OpenSSL (0.0.1): - - cw_shared_external - - Flutter - - cw_haven/Sodium (0.0.1): - - cw_shared_external - - Flutter - - cw_mweb (0.0.1): - - Flutter - - cw_shared_external (0.0.1): - - cw_shared_external/Boost (= 0.0.1) - - cw_shared_external/OpenSSL (= 0.0.1) - - cw_shared_external/Sodium (= 0.0.1) - - Flutter - - cw_shared_external/Boost (0.0.1): - - Flutter - - cw_shared_external/OpenSSL (0.0.1): - - Flutter - - cw_shared_external/Sodium (0.0.1): - - Flutter - device_display_brightness (0.0.1): - Flutter - device_info_plus (0.0.1): @@ -117,8 +85,6 @@ PODS: - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS - - sp_scanner (0.0.1): - - Flutter - SwiftyGif (5.4.5) - Toast (4.1.1) - uni_links (0.0.1): @@ -136,9 +102,6 @@ PODS: DEPENDENCIES: - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) - CryptoSwift - - cw_haven (from `.symlinks/plugins/cw_haven/ios`) - - cw_mweb (from `.symlinks/plugins/cw_mweb/ios`) - - cw_shared_external (from `.symlinks/plugins/cw_shared_external/ios`) - device_display_brightness (from `.symlinks/plugins/device_display_brightness/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - devicelocale (from `.symlinks/plugins/devicelocale/ios`) @@ -158,7 +121,6 @@ DEPENDENCIES: - sensitive_clipboard (from `.symlinks/plugins/sensitive_clipboard/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - - sp_scanner (from `.symlinks/plugins/sp_scanner/ios`) - uni_links (from `.symlinks/plugins/uni_links/ios`) - universal_ble (from `.symlinks/plugins/universal_ble/darwin`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) @@ -179,12 +141,6 @@ SPEC REPOS: EXTERNAL SOURCES: connectivity_plus: :path: ".symlinks/plugins/connectivity_plus/ios" - cw_haven: - :path: ".symlinks/plugins/cw_haven/ios" - cw_mweb: - :path: ".symlinks/plugins/cw_mweb/ios" - cw_shared_external: - :path: ".symlinks/plugins/cw_shared_external/ios" device_display_brightness: :path: ".symlinks/plugins/device_display_brightness/ios" device_info_plus: @@ -223,8 +179,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/share_plus/ios" shared_preferences_foundation: :path: ".symlinks/plugins/shared_preferences_foundation/darwin" - sp_scanner: - :path: ".symlinks/plugins/sp_scanner/ios" uni_links: :path: ".symlinks/plugins/uni_links/ios" universal_ble: @@ -239,9 +193,6 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d CryptoSwift: 967f37cea5a3294d9cce358f78861652155be483 - cw_haven: b3e54e1fbe7b8e6fda57a93206bc38f8e89b898a - cw_mweb: 87af74f9659fed0c1a2cbfb44413f1070e79e3ae - cw_shared_external: 2972d872b8917603478117c9957dfca611845a92 device_display_brightness: 1510e72c567a1f6ce6ffe393dcd9afd1426034f7 device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6 devicelocale: 35ba84dc7f45f527c3001535d8c8d104edd5d926 @@ -266,7 +217,6 @@ SPEC CHECKSUMS: sensitive_clipboard: d4866e5d176581536c27bb1618642ee83adca986 share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 - sp_scanner: eaa617fa827396b967116b7f1f43549ca62e9a12 SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e uni_links: d97da20c7701486ba192624d99bffaaffcfc298a @@ -277,4 +227,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: e448f662d4c41f0c0b1ccbb78afd57dbf895a597 -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/ios/ZanoWallet.framework/Info.plist b/ios/ZanoWallet.framework/Info.plist new file mode 100644 index 0000000000..c430c096e5 Binary files /dev/null and b/ios/ZanoWallet.framework/Info.plist differ diff --git a/ios/ZanoWallet.framework/ZanoWallet b/ios/ZanoWallet.framework/ZanoWallet deleted file mode 100644 index 701878274a..0000000000 Binary files a/ios/ZanoWallet.framework/ZanoWallet and /dev/null differ diff --git a/ios/monero_libwallet2_api_c.dylib b/ios/monero_libwallet2_api_c.dylib index 4ad2b0213e..528ecd4a39 120000 --- a/ios/monero_libwallet2_api_c.dylib +++ b/ios/monero_libwallet2_api_c.dylib @@ -1 +1 @@ -../scripts/monero_c/release/monero/host-apple-ios_libwallet2_api_c.dylib \ No newline at end of file +../scripts/monero_c/release/monero/aarch64-apple-ios_libwallet2_api_c.dylib \ No newline at end of file diff --git a/ios/wownero_libwallet2_api_c.dylib b/ios/wownero_libwallet2_api_c.dylib index 9baa13cc83..35e4e98e92 120000 --- a/ios/wownero_libwallet2_api_c.dylib +++ b/ios/wownero_libwallet2_api_c.dylib @@ -1 +1 @@ -../scripts/monero_c/release/wownero/host-apple-ios_libwallet2_api_c.dylib \ No newline at end of file +../scripts/monero_c/release/wownero/aarch64-apple-ios_libwallet2_api_c.dylib \ No newline at end of file diff --git a/ios/zano_libwallet2_api_c.dylib b/ios/zano_libwallet2_api_c.dylib index ed324a208c..767071f7da 120000 --- a/ios/zano_libwallet2_api_c.dylib +++ b/ios/zano_libwallet2_api_c.dylib @@ -1 +1 @@ -../scripts/monero_c/release/zano/host-apple-ios_libwallet2_api_c.dylib \ No newline at end of file +../scripts/monero_c/release/zano/aarch64-apple-ios_libwallet2_api_c.dylib \ No newline at end of file diff --git a/lib/core/address_validator.dart b/lib/core/address_validator.dart index dbb6f95413..68039bdfbf 100644 --- a/lib/core/address_validator.dart +++ b/lib/core/address_validator.dart @@ -2,9 +2,9 @@ import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/core/validator.dart'; import 'package:cake_wallet/solana/solana.dart'; +import 'package:cake_wallet/zano/zano.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/erc20_token.dart'; - const BEFORE_REGEX = '(^|\\s)'; const AFTER_REGEX = '(\$|\\s)'; @@ -19,7 +19,9 @@ class AddressValidator extends TextValidator { ? BitcoinNetwork.mainnet : LitecoinNetwork.mainnet, ) - : null, + : type == CryptoCurrency.zano + ? zano?.validateAddress + : null, pattern: getPattern(type), length: getLength(type)); @@ -132,6 +134,8 @@ class AddressValidator extends TextValidator { pattern = 'D([1-9a-km-zA-HJ-NP-Z]){33}'; case CryptoCurrency.btcln: pattern = '(lnbc|LNBC)([0-9]{1,}[a-zA-Z0-9]+)'; + case CryptoCurrency.zano: + pattern = r'([1-9A-HJ-NP-Za-km-z]{90,200})|(@[\w\d-.]+)'; default: return ''; } @@ -271,6 +275,7 @@ class AddressValidator extends TextValidator { return [64]; case CryptoCurrency.btcln: case CryptoCurrency.kaspa: + case CryptoCurrency.zano: default: return null; } @@ -310,6 +315,8 @@ class AddressValidator extends TextValidator { pattern = '[1-9A-HJ-NP-Za-km-z]+'; case CryptoCurrency.trx: pattern = '(T|t)[1-9A-HJ-NP-Za-km-z]{33}'; + case CryptoCurrency.zano: + pattern = '([1-9A-HJ-NP-Za-km-z]{90,200})|(@[\w\d-.]+)'; default: if (type.tag == CryptoCurrency.eth.title) { pattern = '0x[0-9a-zA-Z]{42}'; diff --git a/lib/core/amount_validator.dart b/lib/core/amount_validator.dart index 38983dfb20..f85df483f7 100644 --- a/lib/core/amount_validator.dart +++ b/lib/core/amount_validator.dart @@ -76,6 +76,8 @@ class DecimalAmountValidator extends TextValidator { return '^([0-9]+([.\,][0-9]{1,12})?|[.\,][0-9]{1,12})\$'; case CryptoCurrency.btc: return '^([0-9]+([.\,][0-9]{1,8})?|[.\,][0-9]{1,8})\$'; + case CryptoCurrency.zano: + return '^([0-9]+([.\,][0-9]{1,12})?|[.\,][0-9]{1,18})\$'; default: return '^([0-9]+([.\,][0-9]{1,12})?|[.\,][0-9]{1,12})\$'; } diff --git a/lib/core/seed_validator.dart b/lib/core/seed_validator.dart index 2d2a0c6ee7..d963fb5239 100644 --- a/lib/core/seed_validator.dart +++ b/lib/core/seed_validator.dart @@ -9,6 +9,7 @@ import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/solana/solana.dart'; import 'package:cake_wallet/tron/tron.dart'; import 'package:cake_wallet/wownero/wownero.dart'; +import 'package:cake_wallet/zano/zano.dart'; import 'package:cake_wallet/utils/language_list.dart'; import 'package:cw_core/wallet_type.dart'; @@ -21,7 +22,8 @@ class SeedValidator extends Validator { final String language; final List _words; - static List getWordList({required WalletType type, required String language}) { + static List getWordList( + {required WalletType type, required String language}) { switch (type) { case WalletType.bitcoin: return getBitcoinWordList(language); @@ -46,6 +48,8 @@ class SeedValidator extends Validator { return tron!.getTronWordList(language); case WalletType.wownero: return wownero!.getWowneroWordList(language); + case WalletType.zano: + return zano!.getWordList(language); case WalletType.none: return []; } diff --git a/lib/core/wallet_creation_service.dart b/lib/core/wallet_creation_service.dart index 3ee630b33b..3fb4b5b1da 100644 --- a/lib/core/wallet_creation_service.dart +++ b/lib/core/wallet_creation_service.dart @@ -89,6 +89,7 @@ class WalletCreationService { case WalletType.haven: case WalletType.nano: case WalletType.banano: + case WalletType.zano: return false; } } diff --git a/lib/di.dart b/lib/di.dart index 4458f8ebd3..9f22563045 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -245,6 +245,7 @@ import 'package:cake_wallet/view_model/wallet_seed_view_model.dart'; import 'package:cake_wallet/view_model/wallet_unlock_loadable_view_model.dart'; import 'package:cake_wallet/view_model/wallet_unlock_verifiable_view_model.dart'; import 'package:cake_wallet/wownero/wownero.dart'; +import 'package:cake_wallet/zano/zano.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; @@ -1087,6 +1088,8 @@ Future setup({ return tron!.createTronWalletService(_walletInfoSource, SettingsStoreBase.walletPasswordDirectInput); case WalletType.wownero: return wownero!.createWowneroWalletService(_walletInfoSource, _unspentCoinsInfoSource); + case WalletType.zano: + return zano!.createZanoWalletService(_walletInfoSource); case WalletType.none: throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService'); } @@ -1415,4 +1418,4 @@ Future setup({ getIt.registerFactory(() => SeedVerificationPage(getIt.get())); _isSetupFinished = true; -} +} \ No newline at end of file diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index 049bfa15c2..cc8add7c06 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -44,6 +44,7 @@ const solanaDefaultNodeUri = 'solana-mainnet.core.chainstack.com'; const tronDefaultNodeUri = 'api.trongrid.io'; const newCakeWalletBitcoinUri = 'btc-electrum.cakewallet.com:50002'; const wowneroDefaultNodeUri = 'node3.monerodevs.org:34568'; +const zanoDefaultNodeUri = '37.27.100.59:10500'; const moneroWorldNodeUri = '.moneroworld.com'; Future defaultSettingsMigration( @@ -305,12 +306,15 @@ Future defaultSettingsMigration( type: WalletType.polygon, useSSL: true, ); + case 46: updateWalletTypeNodesWithNewNode( newNodeUri: 'eth.nownodes.io', nodes: nodes, type: WalletType.ethereum, useSSL: true, ); + case 47: + _changeDefaultNode( nodes: nodes, sharedPreferences: sharedPreferences, @@ -333,7 +337,7 @@ Future defaultSettingsMigration( oldUri: ['rpc.ankr.com'], ); break; - case 46: + case 48: await _fixNodesUseSSLFlag(nodes); await updateWalletTypeNodesWithNewNode( newNodeUri: 'litecoin.stackwallet.com:20063', @@ -373,6 +377,10 @@ Future defaultSettingsMigration( useSSL: true, ); break; + case 49: + await addZanoNodeList(nodes: nodes); + await changeZanoCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes); + break; default: break; } @@ -703,6 +711,12 @@ Node? getBitcoinCashDefaultElectrumServer({required Box nodes}) { nodes.values.firstWhereOrNull((node) => node.type == WalletType.bitcoinCash); } +Node? getZanoDefaultNode({required Box nodes}) { + return nodes.values.firstWhereOrNull( + (Node node) => node.uriRaw == zanoDefaultNodeUri) + ?? nodes.values.firstWhereOrNull((node) => node.type == WalletType.zano); +} + Node getMoneroDefaultNode({required Box nodes}) { var nodeUri = newCakeWalletMoneroUri; @@ -1199,6 +1213,7 @@ Future checkCurrentNodes( final currentSolanaNodeId = sharedPreferences.getInt(PreferencesKey.currentSolanaNodeIdKey); final currentTronNodeId = sharedPreferences.getInt(PreferencesKey.currentTronNodeIdKey); final currentWowneroNodeId = sharedPreferences.getInt(PreferencesKey.currentWowneroNodeIdKey); + final currentZanoNodeId = sharedPreferences.getInt(PreferencesKey.currentZanoNodeIdKey); final currentMoneroNode = nodeSource.values.firstWhereOrNull((node) => node.key == currentMoneroNodeId); final currentBitcoinElectrumServer = @@ -1223,6 +1238,8 @@ Future checkCurrentNodes( nodeSource.values.firstWhereOrNull((node) => node.key == currentTronNodeId); final currentWowneroNodeServer = nodeSource.values.firstWhereOrNull((node) => node.key == currentWowneroNodeId); + final currentZanoNode = nodeSource.values.firstWhereOrNull((node) => node.key == currentZanoNodeId); + if (currentMoneroNode == null) { final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero); await nodeSource.add(newCakeWalletNode); @@ -1306,6 +1323,12 @@ Future checkCurrentNodes( await nodeSource.add(node); await sharedPreferences.setInt(PreferencesKey.currentWowneroNodeIdKey, node.key as int); } + + if (currentZanoNode == null) { + final node = Node(uri: zanoDefaultNodeUri, type: WalletType.zano); + await nodeSource.add(node); + await sharedPreferences.setInt(PreferencesKey.currentZanoNodeIdKey, node.key as int); + } } Future resetBitcoinElectrumServer( @@ -1381,6 +1404,15 @@ Future addWowneroNodeList({required Box nodes}) async { } } +Future addZanoNodeList({required Box nodes}) async { + final nodeList = await loadDefaultZanoNodes(); + for (var node in nodeList) { + if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) { + await nodes.add(node); + } + } +} + Future changeWowneroCurrentNodeToDefault( {required SharedPreferences sharedPreferences, required Box nodes}) async { final node = getWowneroDefaultNode(nodes: nodes); @@ -1389,6 +1421,13 @@ Future changeWowneroCurrentNodeToDefault( await sharedPreferences.setInt(PreferencesKey.currentWowneroNodeIdKey, nodeId); } +Future changeZanoCurrentNodeToDefault( + {required SharedPreferences sharedPreferences, required Box nodes}) async { + final node = getZanoDefaultNode(nodes: nodes); + final nodeId = node?.key as int? ?? 0; + await sharedPreferences.setInt(PreferencesKey.currentZanoNodeIdKey, nodeId); +} + Future addNanoNodeList({required Box nodes}) async { final nodeList = await loadDefaultNanoNodes(); for (var node in nodeList) { diff --git a/lib/entities/node_list.dart b/lib/entities/node_list.dart index 85e37a7bc8..5147aa614a 100644 --- a/lib/entities/node_list.dart +++ b/lib/entities/node_list.dart @@ -200,6 +200,23 @@ Future> loadDefaultWowneroNodes() async { return nodes; } +Future> loadDefaultZanoNodes() async { + final nodesRaw = await rootBundle.loadString('assets/zano_node_list.yml'); + final loadedNodes = loadYaml(nodesRaw) as YamlList; + final nodes = []; + + for (final raw in loadedNodes) { + if (raw is Map) { + final node = Node.fromMap(Map.from(raw)); + + node.type = WalletType.zano; + nodes.add(node); + } + } + + return nodes; +} + Future resetToDefault(Box nodeSource) async { final moneroNodes = await loadDefaultNodes(); final bitcoinElectrumServerList = await loadBitcoinElectrumServerList(); @@ -211,6 +228,7 @@ Future resetToDefault(Box nodeSource) async { final polygonNodes = await loadDefaultPolygonNodes(); final solanaNodes = await loadDefaultSolanaNodes(); final tronNodes = await loadDefaultTronNodes(); + final zanoNodes = await loadDefaultZanoNodes(); final nodes = moneroNodes + bitcoinElectrumServerList + @@ -220,7 +238,7 @@ Future resetToDefault(Box nodeSource) async { bitcoinCashElectrumServerList + nanoNodes + polygonNodes + - solanaNodes + tronNodes; + solanaNodes + tronNodes + zanoNodes; await nodeSource.clear(); await nodeSource.addAll(nodes); diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index 4955690e2e..c93994e83e 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -5,6 +5,7 @@ class PreferencesKey { static const currentBitcoinElectrumSererIdKey = 'current_node_id_btc'; static const currentLitecoinElectrumSererIdKey = 'current_node_id_ltc'; static const currentHavenNodeIdKey = 'current_node_id_xhv'; + static const currentZanoNodeIdKey = 'current_node_id_zano'; static const currentEthereumNodeIdKey = 'current_node_id_eth'; static const currentPolygonNodeIdKey = 'current_node_id_matic'; static const currentNanoNodeIdKey = 'current_node_id_nano'; @@ -45,6 +46,7 @@ class PreferencesKey { static const ethereumTransactionPriority = 'current_fee_priority_ethereum'; static const polygonTransactionPriority = 'current_fee_priority_polygon'; static const bitcoinCashTransactionPriority = 'current_fee_priority_bitcoin_cash'; + static const zanoTransactionPriority = 'current_fee_priority_zano'; static const wowneroTransactionPriority = 'current_fee_priority_wownero'; static const customBitcoinFeeRate = 'custom_electrum_fee_rate'; static const silentPaymentsCardDisplay = 'silentPaymentsCardDisplay'; diff --git a/lib/entities/priority_for_wallet_type.dart b/lib/entities/priority_for_wallet_type.dart index 5342874940..bbd98d17db 100644 --- a/lib/entities/priority_for_wallet_type.dart +++ b/lib/entities/priority_for_wallet_type.dart @@ -5,6 +5,7 @@ import 'package:cake_wallet/haven/haven.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/wownero/wownero.dart'; +import 'package:cake_wallet/zano/zano.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/wallet_type.dart'; @@ -32,6 +33,8 @@ List priorityForWalletType(WalletType type) { case WalletType.solana: case WalletType.tron: return []; + case WalletType.zano: + return zano!.getTransactionPriorities(); default: return []; } diff --git a/lib/entities/provider_types.dart b/lib/entities/provider_types.dart index 42ec74c124..c65ac267ba 100644 --- a/lib/entities/provider_types.dart +++ b/lib/entities/provider_types.dart @@ -76,6 +76,7 @@ class ProvidersHelper { ]; case WalletType.none: case WalletType.haven: + case WalletType.zano: return []; } } @@ -109,6 +110,7 @@ class ProvidersHelper { case WalletType.none: case WalletType.haven: case WalletType.wownero: + case WalletType.zano: return []; } } diff --git a/lib/main.dart b/lib/main.dart index fd25a1e9ca..4faf64099a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -215,7 +215,7 @@ Future initializeAppConfigs() async { secureStorage: secureStorage, anonpayInvoiceInfo: anonpayInvoiceInfo, havenSeedStore: havenSeedStore, - initialMigrationVersion: 46, + initialMigrationVersion: 49, ); } diff --git a/lib/reactions/bip39_wallet_utils.dart b/lib/reactions/bip39_wallet_utils.dart index a31fec91f2..8b99331ce3 100644 --- a/lib/reactions/bip39_wallet_utils.dart +++ b/lib/reactions/bip39_wallet_utils.dart @@ -15,6 +15,7 @@ bool isBIP39Wallet(WalletType walletType) { case WalletType.monero: case WalletType.wownero: case WalletType.haven: + case WalletType.zano: case WalletType.none: return false; } diff --git a/lib/reactions/on_current_node_change.dart b/lib/reactions/on_current_node_change.dart index 730fba6749..4b49d05ad0 100644 --- a/lib/reactions/on_current_node_change.dart +++ b/lib/reactions/on_current_node_change.dart @@ -1,6 +1,5 @@ import 'package:cw_core/utils/print_verbose.dart'; import 'package:mobx/mobx.dart'; -import 'package:cw_core/node.dart'; import 'package:cake_wallet/store/app_store.dart'; ReactionDisposer? _onCurrentNodeChangeReaction; diff --git a/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart b/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart index e5f38010d0..6804467f76 100644 --- a/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart +++ b/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart @@ -46,6 +46,7 @@ class _DesktopWalletSelectionDropDownState extends State Image.asset( @@ -158,6 +159,8 @@ class _DesktopWalletSelectionDropDownState extends State { }, ); } else { - await actionCall(); + try { + await actionCall(); + } catch (e) { + showPopUp( + context: context, + builder: (dialogContext) { + return AlertWithOneAction( + alertTitle: "Unable to add token", + alertContent: "$e", + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop(), + ); + }, + ); + } if (mounted) { Navigator.pop(context); } @@ -269,11 +286,12 @@ class _EditTokenPageBodyState extends State { final token = await widget.homeSettingsViewModel.getToken(_contractAddressController.text); if (token != null) { - if (_tokenNameController.text.isEmpty) _tokenNameController.text = token.name; - if (_tokenSymbolController.text.isEmpty) _tokenSymbolController.text = token.title; + final isZano = widget.homeSettingsViewModel.walletType == WalletType.zano; + if (_tokenNameController.text.isEmpty || isZano) _tokenNameController.text = token.name; + if (_tokenSymbolController.text.isEmpty || isZano) _tokenSymbolController.text = token.title; if (_tokenIconPathController.text.isEmpty) _tokenIconPathController.text = token.iconPath ?? ''; - if (_tokenDecimalController.text.isEmpty) + if (_tokenDecimalController.text.isEmpty || isZano) _tokenDecimalController.text = token.decimals.toString(); } } @@ -305,7 +323,7 @@ class _EditTokenPageBodyState extends State { placeholder: S.of(context).token_contract_address, options: [AddressTextFieldOption.paste], buttonColor: Theme.of(context).hintColor, - validator: AddressValidator(type: widget.homeSettingsViewModel.nativeToken), + validator: widget.homeSettingsViewModel.walletType == WalletType.zano ? null : AddressValidator(type: widget.homeSettingsViewModel.nativeToken).call, onPushPasteButton: (_) { _pasteText(); }, diff --git a/lib/src/screens/dashboard/widgets/menu_widget.dart b/lib/src/screens/dashboard/widgets/menu_widget.dart index b49a085846..7474e96761 100644 --- a/lib/src/screens/dashboard/widgets/menu_widget.dart +++ b/lib/src/screens/dashboard/widgets/menu_widget.dart @@ -37,7 +37,8 @@ class MenuWidgetState extends State { this.polygonIcon = Image.asset('assets/images/matic_icon.png'), this.solanaIcon = Image.asset('assets/images/sol_icon.png'), this.tronIcon = Image.asset('assets/images/trx_icon.png'), - this.wowneroIcon = Image.asset('assets/images/wownero_icon.png'); + this.wowneroIcon = Image.asset('assets/images/wownero_icon.png'), + this.zanoIcon = Image.asset('assets/images/zano_icon.png'); final largeScreen = 731; @@ -62,6 +63,7 @@ class MenuWidgetState extends State { Image solanaIcon; Image tronIcon; Image wowneroIcon; + Image zanoIcon; @override void initState() { @@ -245,6 +247,8 @@ class MenuWidgetState extends State { return tronIcon; case WalletType.wownero: return wowneroIcon; + case WalletType.zano: + return zanoIcon; default: throw Exception('No icon for ${type.toString()}'); } diff --git a/lib/src/screens/restore/wallet_restore_page.dart b/lib/src/screens/restore/wallet_restore_page.dart index 6215e26c35..beedee220b 100644 --- a/lib/src/screens/restore/wallet_restore_page.dart +++ b/lib/src/screens/restore/wallet_restore_page.dart @@ -274,6 +274,7 @@ class WalletRestorePage extends BasePage { final seedWords = seedPhrase.split(' '); if (seedWords.length == 14 && walletRestoreViewModel.type == WalletType.wownero) return true; + if (seedWords.length == 26 && walletRestoreViewModel.type == WalletType.zano) return true; if ((walletRestoreViewModel.type == WalletType.monero || walletRestoreViewModel.type == WalletType.wownero || diff --git a/lib/src/screens/wallet_list/wallet_list_page.dart b/lib/src/screens/wallet_list/wallet_list_page.dart index 9bf924f61d..bd15d2f605 100644 --- a/lib/src/screens/wallet_list/wallet_list_page.dart +++ b/lib/src/screens/wallet_list/wallet_list_page.dart @@ -131,6 +131,7 @@ class WalletListBodyState extends State { final solanaIcon = Image.asset('assets/images/sol_icon.png', height: 24, width: 24); final tronIcon = Image.asset('assets/images/trx_icon.png', height: 24, width: 24); final wowneroIcon = Image.asset('assets/images/wownero_icon.png', height: 24, width: 24); + final zanoIcon = Image.asset('assets/images/zano_icon.png', height: 24, width: 24); final scrollController = ScrollController(); final double tileHeight = 60; Flushbar? _progressBar; @@ -401,6 +402,41 @@ class WalletListBodyState extends State { ); } + Image _imageFor({required WalletType type, bool? isTestnet}) { + switch (type) { + case WalletType.bitcoin: + if (isTestnet == true) { + return tBitcoinIcon; + } + return bitcoinIcon; + case WalletType.monero: + return moneroIcon; + case WalletType.litecoin: + return litecoinIcon; + case WalletType.haven: + return havenIcon; + case WalletType.ethereum: + return ethereumIcon; + case WalletType.bitcoinCash: + return bitcoinCashIcon; + case WalletType.nano: + case WalletType.banano: + return nanoIcon; + case WalletType.polygon: + return polygonIcon; + case WalletType.solana: + return solanaIcon; + case WalletType.tron: + return tronIcon; + case WalletType.wownero: + return wowneroIcon; + case WalletType.zano: + return zanoIcon; + case WalletType.none: + return nonWalletTypeIcon; + } + } + Future _loadWallet(WalletListItem wallet) async { if (SettingsStoreBase.walletPasswordDirectInput) { Navigator.of(context).pushNamed(Routes.walletUnlockLoadable, diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 318be637e5..89ebb98e4e 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -23,6 +23,10 @@ import 'package:cake_wallet/entities/seed_type.dart'; import 'package:cake_wallet/entities/sort_balance_types.dart'; import 'package:cake_wallet/entities/wallet_list_order_types.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; +import 'package:cake_wallet/wallet_type_utils.dart'; +import 'package:cake_wallet/wownero/wownero.dart'; +import 'package:cake_wallet/zano/zano.dart'; +import 'package:cw_core/transaction_priority.dart'; import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/polygon/polygon.dart'; @@ -132,6 +136,7 @@ abstract class SettingsStoreBase with Store { TransactionPriority? initialEthereumTransactionPriority, TransactionPriority? initialPolygonTransactionPriority, TransactionPriority? initialBitcoinCashTransactionPriority, + TransactionPriority? initialZanoTransactionPriority, Country? initialCakePayCountry}) : nodes = ObservableMap.of(nodes), powNodes = ObservableMap.of(powNodes), @@ -215,6 +220,9 @@ abstract class SettingsStoreBase with Store { priority[WalletType.bitcoinCash] = initialBitcoinCashTransactionPriority; } + if (initialZanoTransactionPriority != null) { + priority[WalletType.zano] = initialZanoTransactionPriority; + } if (initialCakePayCountry != null) { selectedCakePayCountry = initialCakePayCountry; } @@ -268,6 +276,9 @@ abstract class SettingsStoreBase with Store { case WalletType.polygon: key = PreferencesKey.polygonTransactionPriority; break; + case WalletType.zano: + key = PreferencesKey.zanoTransactionPriority; + break; default: key = null; } @@ -878,6 +889,7 @@ abstract class SettingsStoreBase with Store { TransactionPriority? polygonTransactionPriority; TransactionPriority? bitcoinCashTransactionPriority; TransactionPriority? wowneroTransactionPriority; + TransactionPriority? zanoTransactionPriority; if (sharedPreferences.getInt(PreferencesKey.havenTransactionPriority) != null) { havenTransactionPriority = monero?.deserializeMoneroTransactionPriority( @@ -903,6 +915,10 @@ abstract class SettingsStoreBase with Store { wowneroTransactionPriority = wownero?.deserializeWowneroTransactionPriority( raw: sharedPreferences.getInt(PreferencesKey.wowneroTransactionPriority)!); } + if (sharedPreferences.getInt(PreferencesKey.zanoTransactionPriority) != null) { + zanoTransactionPriority = monero?.deserializeMoneroTransactionPriority( + raw: sharedPreferences.getInt(PreferencesKey.zanoTransactionPriority)!); + } moneroTransactionPriority ??= monero?.getDefaultTransactionPriority(); bitcoinTransactionPriority ??= bitcoin?.getMediumTransactionPriority(); @@ -912,6 +928,7 @@ abstract class SettingsStoreBase with Store { bitcoinCashTransactionPriority ??= bitcoinCash?.getDefaultTransactionPriority(); wowneroTransactionPriority ??= wownero?.getDefaultTransactionPriority(); polygonTransactionPriority ??= polygon?.getDefaultTransactionPriority(); + zanoTransactionPriority ??= zano?.getDefaultTransactionPriority(); final currentBalanceDisplayMode = BalanceDisplayMode.deserialize( raw: sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey)!); @@ -1011,6 +1028,8 @@ abstract class SettingsStoreBase with Store { final solanaNodeId = sharedPreferences.getInt(PreferencesKey.currentSolanaNodeIdKey); final tronNodeId = sharedPreferences.getInt(PreferencesKey.currentTronNodeIdKey); final wowneroNodeId = sharedPreferences.getInt(PreferencesKey.currentWowneroNodeIdKey); + final zanoNodeId = sharedPreferences.getInt(PreferencesKey.currentZanoNodeIdKey); + final moneroNode = nodeSource.get(nodeId); final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId); final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId); @@ -1023,6 +1042,7 @@ abstract class SettingsStoreBase with Store { final solanaNode = nodeSource.get(solanaNodeId); final tronNode = nodeSource.get(tronNodeId); final wowneroNode = nodeSource.get(wowneroNodeId); + final zanoNode = nodeSource.get(zanoNodeId); final packageInfo = await PackageInfo.fromPlatform(); final deviceName = await _getDeviceName() ?? ''; final shouldShowYatPopup = sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? true; @@ -1104,6 +1124,10 @@ abstract class SettingsStoreBase with Store { nodes[WalletType.wownero] = wowneroNode; } + if (zanoNode != null) { + nodes[WalletType.zano] = zanoNode; + } + final savedSyncMode = SyncMode.all.firstWhere((element) { return element.type.index == (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 0); }); @@ -1265,6 +1289,7 @@ abstract class SettingsStoreBase with Store { hasEnabledMwebBefore: hasEnabledMwebBefore, initialMoneroTransactionPriority: moneroTransactionPriority, initialWowneroTransactionPriority: wowneroTransactionPriority, + initialZanoTransactionPriority: zanoTransactionPriority, initialBitcoinTransactionPriority: bitcoinTransactionPriority, initialHavenTransactionPriority: havenTransactionPriority, initialLitecoinTransactionPriority: litecoinTransactionPriority, @@ -1339,6 +1364,10 @@ abstract class SettingsStoreBase with Store { priority[WalletType.bitcoinCash] = bitcoinCash!.deserializeBitcoinCashTransactionPriority( sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority)!); } + if (zano != null && sharedPreferences.getInt(PreferencesKey.zanoTransactionPriority) != null) { + priority[WalletType.zano] = zano!.deserializeMoneroTransactionPriority( + raw: sharedPreferences.getInt(PreferencesKey.zanoTransactionPriority)!); + } final generateSubaddresses = sharedPreferences.getInt(PreferencesKey.autoGenerateSubaddressStatusKey); @@ -1448,6 +1477,7 @@ abstract class SettingsStoreBase with Store { final solanaNodeId = sharedPreferences.getInt(PreferencesKey.currentSolanaNodeIdKey); final tronNodeId = sharedPreferences.getInt(PreferencesKey.currentTronNodeIdKey); final wowneroNodeId = sharedPreferences.getInt(PreferencesKey.currentWowneroNodeIdKey); + final zanoNodeId = sharedPreferences.getInt(PreferencesKey.currentZanoNodeIdKey); final moneroNode = nodeSource.get(nodeId); final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId); final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId); @@ -1455,10 +1485,12 @@ abstract class SettingsStoreBase with Store { final ethereumNode = nodeSource.get(ethereumNodeId); final polygonNode = nodeSource.get(polygonNodeId); final bitcoinCashNode = nodeSource.get(bitcoinCashElectrumServerId); - final nanoNode = nodeSource.get(nanoNodeId); + final nanoNode = nodeSource.get(nanoNodeId); final solanaNode = nodeSource.get(solanaNodeId); final tronNode = nodeSource.get(tronNodeId); final wowneroNode = nodeSource.get(wowneroNodeId); + final zanoNode = nodeSource.get(zanoNodeId); + if (moneroNode != null) { nodes[WalletType.monero] = moneroNode; } @@ -1501,6 +1533,11 @@ abstract class SettingsStoreBase with Store { if (wowneroNode != null) { nodes[WalletType.wownero] = wowneroNode; + + } + + if (zanoNode != null) { + nodes[WalletType.zano] = zanoNode; } // MIGRATED: @@ -1639,6 +1676,8 @@ abstract class SettingsStoreBase with Store { case WalletType.wownero: await _sharedPreferences.setInt(PreferencesKey.currentWowneroNodeIdKey, node.key as int); break; + case WalletType.zano: + await _sharedPreferences.setInt(PreferencesKey.currentZanoNodeIdKey, node.key as int); default: break; } diff --git a/lib/view_model/advanced_privacy_settings_view_model.dart b/lib/view_model/advanced_privacy_settings_view_model.dart index 3bc16c3f21..29d122d540 100644 --- a/lib/view_model/advanced_privacy_settings_view_model.dart +++ b/lib/view_model/advanced_privacy_settings_view_model.dart @@ -54,6 +54,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store { case WalletType.wownero: case WalletType.none: case WalletType.haven: + case WalletType.zano: return false; } } diff --git a/lib/view_model/dashboard/balance_view_model.dart b/lib/view_model/dashboard/balance_view_model.dart index 5ca11e2bba..f06f8c7d55 100644 --- a/lib/view_model/dashboard/balance_view_model.dart +++ b/lib/view_model/dashboard/balance_view_model.dart @@ -111,7 +111,8 @@ abstract class BalanceViewModelBase with Store { bool get isHomeScreenSettingsEnabled => isEVMCompatibleChain(wallet.type) || wallet.type == WalletType.solana || - wallet.type == WalletType.tron; + wallet.type == WalletType.tron || + wallet.type == WalletType.zano; @computed bool get hasAccounts => wallet.type == WalletType.monero || wallet.type == WalletType.wownero; diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index 4ab171a15d..d0e1408124 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -566,6 +566,7 @@ abstract class DashboardViewModelBase with Store { case WalletType.wownero: return true; case WalletType.haven: + case WalletType.zano: case WalletType.none: return false; } diff --git a/lib/view_model/dashboard/home_settings_view_model.dart b/lib/view_model/dashboard/home_settings_view_model.dart index 0c3a611eb2..04d52a9185 100644 --- a/lib/view_model/dashboard/home_settings_view_model.dart +++ b/lib/view_model/dashboard/home_settings_view_model.dart @@ -13,6 +13,7 @@ import 'package:cake_wallet/solana/solana.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/tron/tron.dart'; import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart'; +import 'package:cake_wallet/zano/zano.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/erc20_token.dart'; import 'package:cw_core/utils/print_verbose.dart'; @@ -39,6 +40,8 @@ abstract class HomeSettingsViewModelBase with Store { final ObservableSet tokens; + WalletType get walletType => _balanceViewModel.wallet.type; + @observable bool isAddingToken; @@ -108,6 +111,10 @@ abstract class HomeSettingsViewModelBase with Store { await tron!.addTronToken(_balanceViewModel.wallet, token, contractAddress); } + if (_balanceViewModel.wallet.type == WalletType.zano) { + await zano!.addZanoAssetById(_balanceViewModel.wallet, contractAddress); + } + _updateTokensList(); _updateFiatPrices(token); } finally { @@ -134,6 +141,9 @@ abstract class HomeSettingsViewModelBase with Store { if (_balanceViewModel.wallet.type == WalletType.tron) { await tron!.deleteTronToken(_balanceViewModel.wallet, token); } + if (_balanceViewModel.wallet.type == WalletType.zano) { + await zano!.deleteZanoAsset(_balanceViewModel.wallet, token); + } _updateTokensList(); } finally { isDeletingToken = false; @@ -340,6 +350,10 @@ abstract class HomeSettingsViewModelBase with Store { return await tron!.getTronToken(_balanceViewModel.wallet, contractAddress); } + if (_balanceViewModel.wallet.type == WalletType.zano) { + return await zano!.getZanoAsset(_balanceViewModel.wallet, contractAddress); + } + return null; } @@ -378,6 +392,10 @@ abstract class HomeSettingsViewModelBase with Store { tron!.addTronToken(_balanceViewModel.wallet, token, address); } + if (_balanceViewModel.wallet.type == WalletType.zano) { + await zano!.changeZanoAssetAvailability(_balanceViewModel.wallet, token); + } + _refreshTokensList(); } @@ -432,6 +450,13 @@ abstract class HomeSettingsViewModelBase with Store { .toList() ..sort(_sortFunc)); } + + if (_balanceViewModel.wallet.type == WalletType.zano) { + tokens.addAll(zano!.getZanoAssets(_balanceViewModel.wallet) + .where((element) => _matchesSearchText(element)) + .toList() + ..sort(_sortFunc)); + } } @action @@ -476,6 +501,10 @@ abstract class HomeSettingsViewModelBase with Store { return polygon!.getTokenAddress(asset); } + if (_balanceViewModel.wallet.type == WalletType.zano) { + return zano!.getZanoAssetAddress(asset); + } + // 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). return null; } diff --git a/lib/view_model/dashboard/transaction_list_item.dart b/lib/view_model/dashboard/transaction_list_item.dart index d9b361fd24..479a2b9454 100644 --- a/lib/view_model/dashboard/transaction_list_item.dart +++ b/lib/view_model/dashboard/transaction_list_item.dart @@ -8,6 +8,7 @@ import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/solana/solana.dart'; import 'package:cake_wallet/tron/tron.dart'; import 'package:cake_wallet/wownero/wownero.dart'; +import 'package:cake_wallet/zano/zano.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_info.dart'; @@ -201,7 +202,6 @@ class TransactionListItem extends ActionListItem with Keyable { price: price, ); break; - case WalletType.tron: final asset = tron!.assetOfTransaction(balanceViewModel.wallet, transaction); final price = balanceViewModel.fiatConvertationStore.prices[asset]; @@ -211,6 +211,13 @@ class TransactionListItem extends ActionListItem with Keyable { price: price, ); break; + case WalletType.zano: + final asset = zano!.assetOfTransaction(balanceViewModel.wallet, transaction); + final price = balanceViewModel.fiatConvertationStore.prices[asset]; + amount = calculateFiatAmountRaw( + cryptoAmount: zano!.formatterIntAmountToDouble(amount: transaction.amount, currency: asset, forFee: false), + price: price); + break; default: break; } diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index 63e1db6bca..f69a697e54 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -308,6 +308,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with case WalletType.monero: case WalletType.wownero: case WalletType.haven: + case WalletType.zano: return transactionPriority == monero!.getMoneroTransactionPrioritySlow(); case WalletType.bitcoin: return transactionPriority == bitcoin!.getBitcoinTransactionPrioritySlow(); @@ -747,6 +748,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with depositCurrency = CryptoCurrency.wow; receiveCurrency = CryptoCurrency.xmr; break; + case WalletType.zano: + depositCurrency = CryptoCurrency.zano; + receiveCurrency = CryptoCurrency.xmr; + break; case WalletType.none: break; } @@ -821,6 +826,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with case WalletType.monero: case WalletType.haven: case WalletType.wownero: + case WalletType.zano: _settingsStore.priority[wallet.type] = monero!.getMoneroTransactionPriorityAutomatic(); break; case WalletType.bitcoin: diff --git a/lib/view_model/node_list/node_create_or_edit_view_model.dart b/lib/view_model/node_list/node_create_or_edit_view_model.dart index 8b3c70c5e6..74deb066bb 100644 --- a/lib/view_model/node_list/node_create_or_edit_view_model.dart +++ b/lib/view_model/node_list/node_create_or_edit_view_model.dart @@ -85,6 +85,7 @@ abstract class NodeCreateOrEditViewModelBase with Store { case WalletType.litecoin: case WalletType.bitcoinCash: case WalletType.bitcoin: + case WalletType.zano: return false; } } diff --git a/lib/view_model/node_list/node_list_view_model.dart b/lib/view_model/node_list/node_list_view_model.dart index 2721fd7b36..71e77eb126 100644 --- a/lib/view_model/node_list/node_list_view_model.dart +++ b/lib/view_model/node_list/node_list_view_model.dart @@ -88,6 +88,9 @@ abstract class NodeListViewModelBase with Store { case WalletType.wownero: node = getWowneroDefaultNode(nodes: _nodeSource); break; + case WalletType.zano: + node = getZanoDefaultNode(nodes: _nodeSource)!; + break; default: throw Exception('Unexpected wallet type: ${_appStore.wallet!.type}'); } diff --git a/lib/view_model/restore/restore_from_qr_vm.dart b/lib/view_model/restore/restore_from_qr_vm.dart index 7fd9312710..1e30bebda8 100644 --- a/lib/view_model/restore/restore_from_qr_vm.dart +++ b/lib/view_model/restore/restore_from_qr_vm.dart @@ -9,6 +9,7 @@ import 'package:cake_wallet/view_model/restore/restore_mode.dart'; import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; import 'package:cake_wallet/view_model/seed_settings_view_model.dart'; import 'package:cake_wallet/wownero/wownero.dart'; +import 'package:cake_wallet/zano/zano.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/monero/monero.dart'; @@ -183,6 +184,9 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store password: password, passphrase: restoreWallet.passphrase ?? '', ); + case WalletType.zano: + return zano!.createZanoRestoreWalletFromSeedCredentials( + name: name, password: password, height: height, mnemonic: restoreWallet.mnemonicSeed ?? ''); default: throw Exception('Unexpected type: ${type.toString()}'); } diff --git a/lib/view_model/restore/wallet_restore_from_qr_code.dart b/lib/view_model/restore/wallet_restore_from_qr_code.dart index c1a19ea573..9fde8386b5 100644 --- a/lib/view_model/restore/wallet_restore_from_qr_code.dart +++ b/lib/view_model/restore/wallet_restore_from_qr_code.dart @@ -41,6 +41,9 @@ class WalletRestoreFromQRCode { 'wownero': WalletType.wownero, 'wownero-wallet': WalletType.wownero, 'wownero_wallet': WalletType.wownero, + 'zano': WalletType.zano, + 'zano-wallet': WalletType.zano, + 'zano_wallet': WalletType.zano, }; static bool _containsAssetSpecifier(String code) => _extractWalletType(code) != null; diff --git a/lib/view_model/send/output.dart b/lib/view_model/send/output.dart index f977ef003f..121ffa693f 100644 --- a/lib/view_model/send/output.dart +++ b/lib/view_model/send/output.dart @@ -10,6 +10,7 @@ import 'package:cake_wallet/solana/solana.dart'; import 'package:cake_wallet/src/screens/send/widgets/extract_address_from_parsed.dart'; import 'package:cake_wallet/tron/tron.dart'; import 'package:cake_wallet/wownero/wownero.dart'; +import 'package:cake_wallet/zano/zano.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:flutter/material.dart'; @@ -112,6 +113,9 @@ abstract class OutputBase with Store { case WalletType.wownero: _amount = wownero!.formatterWowneroParseAmount(amount: _cryptoAmount); break; + case WalletType.zano: + _amount = zano!.formatterParseAmount(amount: _cryptoAmount, currency: cryptoCurrencyHandler()); + break; default: break; } @@ -180,6 +184,10 @@ abstract class OutputBase with Store { if (_wallet.type == WalletType.polygon) { return polygon!.formatterPolygonAmountToDouble(amount: BigInt.from(fee)); } + + if (_wallet.type == WalletType.zano) { + return zano!.formatterIntAmountToDouble(amount: fee, currency: cryptoCurrencyHandler(), forFee: true); + } } catch (e) { printV(e.toString()); } @@ -308,6 +316,9 @@ abstract class OutputBase with Store { case WalletType.wownero: maximumFractionDigits = 11; break; + case WalletType.zano: + maximumFractionDigits = 12; + break; default: break; } diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index cafe89cb13..53c52aa1e7 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -20,6 +20,7 @@ import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart'; import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart'; import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart'; import 'package:cake_wallet/wownero/wownero.dart'; +import 'package:cake_wallet/zano/zano.dart'; import 'package:cw_core/exceptions.dart'; import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/transaction_priority.dart'; @@ -61,7 +62,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor selectedCryptoCurrency = wallet.currency; hasMultipleTokens = isEVMCompatibleChain(wallet.type) || wallet.type == WalletType.solana || - wallet.type == WalletType.tron; + wallet.type == WalletType.tron || + wallet.type == WalletType.zano; } UnspentCoinsListViewModel unspentCoinsListViewModel; @@ -81,7 +83,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor selectedCryptoCurrency = appStore.wallet!.currency, hasMultipleTokens = isEVMCompatibleChain(appStore.wallet!.type) || appStore.wallet!.type == WalletType.solana || - appStore.wallet!.type == WalletType.tron, + appStore.wallet!.type == WalletType.tron || + appStore.wallet!.type == WalletType.zano, outputs = ObservableList(), _settingsStore = appStore.settingsStore, fiatFromSettings = appStore.settingsStore.fiatCurrency, @@ -576,6 +579,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor .createSolanaTransactionCredentials(outputs, currency: selectedCryptoCurrency); case WalletType.tron: return tron!.createTronTransactionCredentials(outputs, currency: selectedCryptoCurrency); + case WalletType.zano: + return zano!.createZanoTransactionCredentials( + outputs: outputs, priority: priority!, currency: selectedCryptoCurrency); default: throw Exception('Unexpected wallet type: ${wallet.type}'); } diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart index 9ec542361a..549a601fdf 100644 --- a/lib/view_model/transaction_details_view_model.dart +++ b/lib/view_model/transaction_details_view_model.dart @@ -81,6 +81,9 @@ abstract class TransactionDetailsViewModelBase with Store { case WalletType.wownero: _addWowneroListItems(tx, dateFormat); break; + case WalletType.zano: + _addZanoListItems(tx, dateFormat); + break; default: break; } @@ -181,6 +184,8 @@ abstract class TransactionDetailsViewModelBase with Store { return 'https://tronscan.org/#/transaction/${txId}'; case WalletType.wownero: return 'https://explore.wownero.com/tx/${txId}'; + case WalletType.zano: + return 'https://testnet-explorer.zano.org/transaction/${txId}'; case WalletType.none: return ''; } @@ -211,6 +216,8 @@ abstract class TransactionDetailsViewModelBase with Store { return S.current.view_transaction_on + 'tronscan.org'; case WalletType.wownero: return S.current.view_transaction_on + 'Wownero.com'; + case WalletType.zano: + return S.current.view_transaction_on + 'explorer.zano.org'; case WalletType.none: return ''; } @@ -776,4 +783,20 @@ abstract class TransactionDetailsViewModelBase with Store { items.addAll(_items); } + + void _addZanoListItems(TransactionInfo tx, DateFormat dateFormat) { + final comment = tx.additionalInfo['comment'] as String?; + items.addAll([ + StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id), + StandartListItem(title: 'Asset ID', value: tx.additionalInfo['assetId'] as String? ?? "Unknown asset id"), + StandartListItem( + title: S.current.transaction_details_date, value: dateFormat.format(tx.date)), + StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'), + StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()), + if (tx.feeFormatted()?.isNotEmpty ?? false) + StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!), + if (comment != null) + StandartListItem(title: S.current.transaction_details_title, value: comment), + ]); + } } diff --git a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart index 3e399266a6..773d8335cc 100644 --- a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart @@ -17,6 +17,7 @@ import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/store/yat/yat_store.dart'; import 'package:cake_wallet/tron/tron.dart'; +import 'package:cake_wallet/zano/zano.dart'; import 'package:cake_wallet/utils/list_item.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_account_list_header.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_hidden_list_header.dart'; @@ -205,6 +206,23 @@ class WowneroURI extends PaymentURI { } } +class ZanoURI extends PaymentURI { + ZanoURI({required String amount, required String address}) + : super(amount: amount, address: address); + + @override + String toString() { + var base = 'zano:' + address; + + if (amount.isNotEmpty) { + base += '?amount=${amount.replaceAll(',', '.')}'; + } + + return base; + } +} + + abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewModel with Store { WalletAddressListViewModelBase({ @@ -298,11 +316,12 @@ abstract class WalletAddressListViewModelBase return TronURI(amount: amount, address: address.address); case WalletType.wownero: return WowneroURI(amount: amount, address: address.address); + case WalletType.zano: + return ZanoURI(amount: amount, address: address.address); case WalletType.none: throw Exception('Unexpected type: ${type.toString()}'); } } - @computed ObservableList get items => ObservableList() ..addAll(_baseItems) @@ -479,6 +498,12 @@ abstract class WalletAddressListViewModelBase .contains((addressList[i] as WalletAddressListItem).address); } + if (wallet.type == WalletType.zano) { + final primaryAddress = zano!.getAddress(wallet); + + addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress)); + } + if (searchText.isNotEmpty) { return ObservableList.of(addressList.where((item) { if (item is WalletAddressListItem) { diff --git a/lib/view_model/wallet_keys_view_model.dart b/lib/view_model/wallet_keys_view_model.dart index 5158b64f36..2d1aa83dab 100644 --- a/lib/view_model/wallet_keys_view_model.dart +++ b/lib/view_model/wallet_keys_view_model.dart @@ -5,6 +5,7 @@ import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart'; import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/wownero/wownero.dart'; +import 'package:cake_wallet/zano/zano.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/wallet_base.dart'; @@ -290,6 +291,12 @@ abstract class WalletKeysViewModelBase with Store { ), ]); } + + if (_appStore.wallet!.type == WalletType.zano) { + items.addAll([ + StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed!), + ]); + } } Future _currentHeight() async { @@ -331,6 +338,8 @@ abstract class WalletKeysViewModelBase with Store { return 'tron-wallet'; case WalletType.wownero: return 'wownero-wallet'; + case WalletType.zano: + return 'zano-wallet'; default: throw Exception('Unexpected wallet type: ${_appStore.wallet!.type.toString()}'); } diff --git a/lib/view_model/wallet_new_vm.dart b/lib/view_model/wallet_new_vm.dart index be30811d95..9b29f55ffd 100644 --- a/lib/view_model/wallet_new_vm.dart +++ b/lib/view_model/wallet_new_vm.dart @@ -1,5 +1,13 @@ import 'package:cake_wallet/core/new_wallet_arguments.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; +import 'package:cake_wallet/zano/zano.dart'; +import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; +import 'package:cake_wallet/solana/solana.dart'; +import 'package:cake_wallet/tron/tron.dart'; +import 'package:cake_wallet/wownero/wownero.dart'; +import 'package:cake_wallet/zano/zano.dart'; +import 'package:hive/hive.dart'; +import 'package:mobx/mobx.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/core/wallet_creation_service.dart'; @@ -152,9 +160,14 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { case WalletType.wownero: return wownero!.createWowneroNewWalletCredentials( name: name, - password: walletPassword, language: options!.first as String, isPolyseed: options.last as bool, + password: walletPassword, + ); + case WalletType.zano: + return zano!.createZanoNewWalletCredentials( + name: name, + password: walletPassword, ); case WalletType.none: throw Exception('Unexpected type: ${type.toString()}'); diff --git a/lib/view_model/wallet_restore_view_model.dart b/lib/view_model/wallet_restore_view_model.dart index 2f9793a7d3..ac016906d7 100644 --- a/lib/view_model/wallet_restore_view_model.dart +++ b/lib/view_model/wallet_restore_view_model.dart @@ -15,6 +15,7 @@ import 'package:cake_wallet/view_model/restore/restore_mode.dart'; import 'package:cake_wallet/view_model/seed_settings_view_model.dart'; import 'package:cake_wallet/view_model/wallet_creation_vm.dart'; import 'package:cake_wallet/wownero/wownero.dart'; +import 'package:cake_wallet/zano/zano.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; @@ -61,6 +62,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { case WalletType.bitcoin: case WalletType.litecoin: case WalletType.bitcoinCash: + case WalletType.zano: case WalletType.none: availableModes = [WalletRestoreMode.seed]; break; @@ -163,6 +165,12 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { passphrase: passphrase??'', height: height, ); + case WalletType.zano: + return zano!.createZanoRestoreWalletFromSeedCredentials( + name: name, + password: password, + height: height, + mnemonic: seed); case WalletType.none: break; } diff --git a/lib/zano/cw_zano.dart b/lib/zano/cw_zano.dart new file mode 100644 index 0000000000..2581f123ec --- /dev/null +++ b/lib/zano/cw_zano.dart @@ -0,0 +1,134 @@ +part of 'zano.dart'; + +class CWZano extends Zano { + + List getZanoAssets(WalletBase wallet) => (wallet as ZanoWallet).zanoAssets.values.toList(); + + @override + Future addZanoAssetById(WalletBase wallet, String assetId) async => await (wallet as ZanoWallet).addZanoAssetById(assetId); + + @override + Future changeZanoAssetAvailability(WalletBase wallet, CryptoCurrency token) async => await (wallet as ZanoWallet).changeZanoAssetAvailability(token as ZanoAsset); + + @override + Future deleteZanoAsset(WalletBase wallet, CryptoCurrency token) async => await (wallet as ZanoWallet).deleteZanoAsset(token as ZanoAsset); + + @override + Future getZanoAsset(WalletBase wallet, String assetId) async { + final zanoWallet = wallet as ZanoWallet; + return await zanoWallet.getZanoAsset(assetId); + } + + // @override + // TransactionHistoryBase getTransactionHistory(Object wallet) { + // final zanoWallet = wallet as ZanoWallet; + // return zanoWallet.transactionHistory; + // } + + @override + TransactionPriority getDefaultTransactionPriority() { + return MoneroTransactionPriority.automatic; + } + + @override + TransactionPriority deserializeMoneroTransactionPriority({required int raw}) { + return MoneroTransactionPriority.deserialize(raw: raw); + } + + @override + List getTransactionPriorities() { + return MoneroTransactionPriority.all; + } + + @override + List getWordList(String language) { + assert(language.toLowerCase() == LanguageList.english.toLowerCase()); + return EnglishMnemonics.words; + } + + @override + WalletCredentials createZanoRestoreWalletFromSeedCredentials( + {required String name, required String password, required int height, required String mnemonic}) { + return ZanoRestoreWalletFromSeedCredentials(name: name, password: password, height: height, mnemonic: mnemonic); + } + + @override + WalletCredentials createZanoNewWalletCredentials({required String name, required String? password}) { + return ZanoNewWalletCredentials(name: name, password: password); + } + + // @override + // Map getKeys(Object wallet) { + // final zanoWallet = wallet as ZanoWallet; + // final keys = zanoWallet.keys; + // return { + // 'privateSpendKey': keys.privateSpendKey, + // 'privateViewKey': keys.privateViewKey, + // 'publicSpendKey': keys.publicSpendKey, + // 'publicViewKey': keys.publicViewKey + // }; + // } + + @override + Object createZanoTransactionCredentials({required List outputs, required TransactionPriority priority, required CryptoCurrency currency}) { + return ZanoTransactionCredentials( + outputs: outputs + .map((out) => OutputInfo( + fiatAmount: out.fiatAmount, + cryptoAmount: out.cryptoAmount, + address: out.address, + note: out.note, + sendAll: out.sendAll, + extractedAddress: out.extractedAddress, + isParsedAddress: out.isParsedAddress, + formattedCryptoAmount: out.formattedCryptoAmount)) + .toList(), + priority: priority as MoneroTransactionPriority, + currency: currency, + ); + } + + @override + double formatterIntAmountToDouble({required int amount, required CryptoCurrency currency, required bool forFee}) { + // fee always counted in zano with default decimal points + if (forFee) return ZanoFormatter.intAmountToDouble(amount); + if (currency is ZanoAsset) return ZanoFormatter.intAmountToDouble(amount, currency.decimalPoint); + return ZanoFormatter.intAmountToDouble(amount); + } + + @override + int formatterParseAmount({required String amount, required CryptoCurrency currency}) { + if (currency is ZanoAsset) return ZanoFormatter.parseAmount(amount, currency.decimalPoint); + return ZanoFormatter.parseAmount(amount); + } + + // @override + // int getTransactionInfoAccountId(TransactionInfo tx) { + // final zanoTransactionInfo = tx as ZanoTransactionInfo; + // return zanoTransactionInfo.accountIndex; + // } + + @override + WalletService createZanoWalletService(Box walletInfoSource) { + return ZanoWalletService(walletInfoSource); + } + + @override + CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) { + transaction as ZanoTransactionInfo; + if (transaction.tokenSymbol == CryptoCurrency.zano.title) { + return CryptoCurrency.zano; + } + wallet as ZanoWallet; + final asset = wallet.zanoAssets.values.firstWhereOrNull((element) => element?.ticker == transaction.tokenSymbol); + return asset ?? CryptoCurrency.zano; + } + + String getZanoAssetAddress(CryptoCurrency asset) => (asset as ZanoAsset).assetId; + + @override + String getAddress(WalletBase wallet) => (wallet as ZanoWallet).walletAddresses.address; + + @override + bool validateAddress(String address) => ZanoUtils.validateAddress(address); +} diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt index bfce34c341..0b6f32fd64 100644 --- a/linux/CMakeLists.txt +++ b/linux/CMakeLists.txt @@ -107,10 +107,19 @@ install(CODE " set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") -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" +if(CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64") + set(LIB_TRIPLET "aarch64-linux-gnu") +else() + set(LIB_TRIPLET "x86_64-linux-gnu") +endif() + +install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/monero_c/release/monero/${LIB_TRIPLET}_libwallet2_api_c.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" RENAME "monero_libwallet2_api_c.so" + COMPONENT Runtime) + +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" COMPONENT Runtime) -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" +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" COMPONENT Runtime) install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 01b9228945..0c9fa9deec 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -8,7 +8,6 @@ #include #include -#include #include void fl_register_plugins(FlPluginRegistry* registry) { @@ -18,9 +17,6 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) flutter_local_authentication_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterLocalAuthenticationPlugin"); flutter_local_authentication_plugin_register_with_registrar(flutter_local_authentication_registrar); - g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = - fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); - flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index f52be7481a..614f09c76c 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -5,7 +5,6 @@ list(APPEND FLUTTER_PLUGIN_LIST devicelocale flutter_local_authentication - flutter_secure_storage_linux url_launcher_linux ) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 42b9fa84cf..17de3145bd 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -12,7 +12,6 @@ import devicelocale import fast_scanner import flutter_inappwebview_macos import flutter_local_authentication -import flutter_secure_storage_macos import in_app_review import package_info_plus import path_provider_foundation @@ -30,7 +29,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin")) InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin")) FlutterLocalAuthenticationPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalAuthenticationPlugin")) - FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) diff --git a/model_generator.sh b/model_generator.sh index 1443b0fc98..c43f60a31e 100755 --- a/model_generator.sh +++ b/model_generator.sh @@ -1,7 +1,7 @@ #!/bin/bash set -x -e -for cwcoin in cw_{core,evm,monero,bitcoin,haven,nano,bitcoin_cash,solana,tron,wownero} +for cwcoin in cw_{core,evm,monero,bitcoin,haven,nano,bitcoin_cash,solana,tron,wownero,zano} do if [[ "x$1" == "xasync" ]]; then diff --git a/pubspec_base.yaml b/pubspec_base.yaml index 450977a40b..3b6112fceb 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -181,6 +181,7 @@ flutter: - assets/solana_node_list.yml - assets/tron_node_list.yml - assets/wownero_node_list.yml + - assets/zano_node_list.yml - assets/text/ - assets/faq/ - assets/animation/ diff --git a/run-android.sh b/run-android.sh index cb0c340382..feaf0d91d1 100755 --- a/run-android.sh +++ b/run-android.sh @@ -1,5 +1,5 @@ #!/bin/bash -source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/universal_sed.sh" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/functions.sh" # Get the current git branch get_current_branch() { if git rev-parse --git-dir > /dev/null 2>&1; then diff --git a/scripts/android/build_monero_all.sh b/scripts/android/build_monero_all.sh index 71a6b62282..1c28d1cd12 100755 --- a/scripts/android/build_monero_all.sh +++ b/scripts/android/build_monero_all.sh @@ -1,4 +1,5 @@ #!/bin/bash +source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/functions.sh" # Usage: env USE_DOCKER= ./build_all.sh @@ -6,8 +7,6 @@ set -x -e cd "$(dirname "$0")" -NPROC="-j$(nproc)" - ../prepare_moneroc.sh for COIN in monero wownero; @@ -19,9 +18,9 @@ do then echo "file exist, not building monero_c for ${COIN}/$target."; else - env -i ./build_single.sh ${COIN} $target $NPROC + ./build_single.sh ${COIN} $target -j$MAKE_JOB_COUNT unxz -f ../monero_c/release/${COIN}/${target}_libwallet2_api_c.so.xz fi done popd -done \ No newline at end of file +done diff --git a/scripts/android/copy_monero_deps.sh b/scripts/android/copy_monero_deps.sh index ed8181e6be..be621b683c 100755 --- a/scripts/android/copy_monero_deps.sh +++ b/scripts/android/copy_monero_deps.sh @@ -5,6 +5,7 @@ CW_DIR=${WORKDIR}/cake_wallet CW_EXRTERNAL_DIR=${CW_DIR}/cw_shared_external/ios/External/android CW_HAVEN_EXTERNAL_DIR=${CW_DIR}/cw_haven/ios/External/android CW_MONERO_EXTERNAL_DIR=${CW_DIR}/cw_monero/ios/External/android +CW_ZANO_EXTERNAL_DIR=${CW_DIR}/cw_zano/ios/External/android for arch in "aarch" "aarch64" "i686" "x86_64" do @@ -40,5 +41,6 @@ done mkdir -p ${CW_HAVEN_EXTERNAL_DIR}/include mkdir -p ${CW_MONERO_EXTERNAL_DIR}/include +mkdir -p ${CW_ZANO_EXTERNAL_DIR}/include cp $CW_EXRTERNAL_DIR/x86/include/haven/wallet2_api.h ${CW_HAVEN_EXTERNAL_DIR}/include diff --git a/scripts/android/finish_boost.sh b/scripts/android/finish_boost.sh index 3cf656c555..d96de2d72e 100755 --- a/scripts/android/finish_boost.sh +++ b/scripts/android/finish_boost.sh @@ -5,5 +5,5 @@ PREFIX=$2 BOOST_SRC_DIR=$3 cd $BOOST_SRC_DIR - -./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 +echo "Building boost" +./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 diff --git a/scripts/android/inject_app_details.sh b/scripts/android/inject_app_details.sh index 7b0d747982..7f0858b18d 100755 --- a/scripts/android/inject_app_details.sh +++ b/scripts/android/inject_app_details.sh @@ -1,5 +1,5 @@ #!/bin/bash -source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/universal_sed.sh" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/functions.sh" if [ -z "$APP_ANDROID_TYPE" ]; then echo "Please set APP_ANDROID_TYPE" exit 1 diff --git a/scripts/android/pubspec_gen.sh b/scripts/android/pubspec_gen.sh index febc4f9e9e..5d6a247228 100755 --- a/scripts/android/pubspec_gen.sh +++ b/scripts/android/pubspec_gen.sh @@ -10,7 +10,7 @@ case $APP_ANDROID_TYPE in CONFIG_ARGS="--monero" ;; $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero" + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano" if [ "$CW_WITH_HAVEN" = true ];then CONFIG_ARGS="$CONFIG_ARGS --haven" fi diff --git a/scripts/functions.sh b/scripts/functions.sh new file mode 100644 index 0000000000..3b613da228 --- /dev/null +++ b/scripts/functions.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +detect_sed() { + if sed --version 2>/dev/null | grep -q "GNU"; then + SED_TYPE="GNU" + else + SED_TYPE="BSD" + fi +} + +universal_sed() { + local expression=$1 + local file=$2 + + if [[ "$SED_TYPE" == "GNU" ]]; then + sed -i "$expression" "$file" + else + sed -i '' "$expression" "$file" + fi +} + +detect_sed + +if [[ "$(uname)" == "Linux" ]]; +then + 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)" +elif [[ "$(uname)" == "Darwin" ]]; +then + 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)" +else + # Assume windows eh? + 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)" +fi \ No newline at end of file diff --git a/scripts/ios/app_config.sh b/scripts/ios/app_config.sh index 396ccd7f0c..35bbe7b74b 100755 --- a/scripts/ios/app_config.sh +++ b/scripts/ios/app_config.sh @@ -1,5 +1,5 @@ #!/bin/bash -source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/universal_sed.sh" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/functions.sh" set -x -e MONERO_COM="monero.com" CAKEWALLET="cakewallet" @@ -32,7 +32,7 @@ case $APP_IOS_TYPE in CONFIG_ARGS="--monero" ;; $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero" + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano" if [ "$CW_WITH_HAVEN" = true ];then CONFIG_ARGS="$CONFIG_ARGS --haven" fi diff --git a/scripts/ios/build_boost.sh b/scripts/ios/build_boost.sh index e2dc291ee8..11dcead3e7 100755 --- a/scripts/ios/build_boost.sh +++ b/scripts/ios/build_boost.sh @@ -1,23 +1,42 @@ -#!/bin/sh +#!/bin/bash + +set -e . ./config.sh MIN_IOS_VERSION=10.0 BOOST_URL="https://github.com/cake-tech/Apple-Boost-BuildScript.git" BOOST_DIR_PATH="${EXTERNAL_IOS_SOURCE_DIR}/Apple-Boost-BuildScript" -BOOST_VERSION=1.72.0 +BOOST_VERSION=1.84.0 BOOST_LIBS="random regex graph random chrono thread filesystem system date_time locale serialization program_options" echo "============================ Boost ============================" echo "Cloning Apple-Boost-BuildScript from - $BOOST_URL" -git clone -b build $BOOST_URL $BOOST_DIR_PATH -cd $BOOST_DIR_PATH + +# Check if the directory already exists. +if [ -d "$BOOST_DIR_PATH" ]; then + echo "Boost directory already exists." +else + echo "Cloning Boost from $BOOST_URL" + git clone -b build $BOOST_URL $BOOST_DIR_PATH +fi + +# Verify if the repository was cloned successfully. +if [ -d "$BOOST_DIR_PATH/.git" ]; then + echo "Boost repository cloned successfully." + cd $BOOST_DIR_PATH + git checkout build +else + echo "Failed to clone Boost repository. Exiting." + exit 1 +fi + ./boost.sh -ios \ --min-ios-version ${MIN_IOS_VERSION} \ --boost-libs "${BOOST_LIBS}" \ --boost-version ${BOOST_VERSION} \ --no-framework -mv ${BOOST_DIR_PATH}/build/boost/${BOOST_VERSION}/ios/release/prefix/include/* $EXTERNAL_IOS_INCLUDE_DIR -mv ${BOOST_DIR_PATH}/build/boost/${BOOST_VERSION}/ios/release/prefix/lib/* $EXTERNAL_IOS_LIB_DIR \ No newline at end of file +mv -f ${BOOST_DIR_PATH}/build/boost/${BOOST_VERSION}/ios/release/prefix/include/* $EXTERNAL_IOS_INCLUDE_DIR +mv -f ${BOOST_DIR_PATH}/build/boost/${BOOST_VERSION}/ios/release/prefix/lib/* $EXTERNAL_IOS_LIB_DIR \ No newline at end of file diff --git a/scripts/ios/build_monero_all.sh b/scripts/ios/build_monero_all.sh index a5993d1811..79a3c61f8e 100755 --- a/scripts/ios/build_monero_all.sh +++ b/scripts/ios/build_monero_all.sh @@ -1,4 +1,5 @@ #!/bin/sh +source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/functions.sh" . ./config.sh # ./install_missing_headers.sh @@ -12,17 +13,16 @@ set -x -e cd "$(dirname "$0")" -NPROC="-j$(sysctl -n hw.logicalcpu)" - ../prepare_moneroc.sh -for COIN in monero wownero; +for COIN in monero wownero zano; do pushd ../monero_c rm -rf external/ios/build - ./build_single.sh ${COIN} host-apple-ios $NPROC + ./build_single.sh ${COIN} aarch64-apple-ios -j$MAKE_JOB_COUNT popd done -unxz -f ../monero_c/release/monero/host-apple-ios_libwallet2_api_c.dylib.xz -unxz -f ../monero_c/release/wownero/host-apple-ios_libwallet2_api_c.dylib.xz +unxz -f ../monero_c/release/monero/aarch64-apple-ios_libwallet2_api_c.dylib.xz +unxz -f ../monero_c/release/wownero/aarch64-apple-ios_libwallet2_api_c.dylib.xz +unxz -f ../monero_c/release/zano/aarch64-apple-ios_libwallet2_api_c.dylib.xz diff --git a/scripts/ios/build_zano.sh b/scripts/ios/build_zano.sh new file mode 100755 index 0000000000..d37ed226dd --- /dev/null +++ b/scripts/ios/build_zano.sh @@ -0,0 +1,106 @@ +#!/bin/sh + +. ./config.sh + +ZANO_URL="https://github.com/hyle-team/zano.git" +ZANO_DIR_PATH="${EXTERNAL_IOS_SOURCE_DIR}/zano" +ZANO_VERSION=fde28efdc5d7efe8741dcb0e62ea0aebc805a373 + + +IOS_TOOLCHAIN_DIR_PATH="${EXTERNAL_IOS_SOURCE_DIR}/ios_toolchain" +IOS_TOOLCHAIN_URL="https://github.com/leetal/ios-cmake.git" +IOS_TOOLCHAIN_VERSION=06465b27698424cf4a04a5ca4904d50a3c966c45 + +export NO_DEFAULT_PATH + +BUILD_TYPE=release +PREFIX=${EXTERNAL_IOS_DIR} +DEST_LIB_DIR=${EXTERNAL_IOS_LIB_DIR}/zano +DEST_INCLUDE_DIR=${EXTERNAL_IOS_INCLUDE_DIR}/zano + +ZANO_MOBILE_IOS_BUILD_FOLDER_ARM64="${ZANO_DIR_PATH}/build" +ZANO_MOBILE_IOS_INSTALL_FOLDER_ARM64="${ZANO_DIR_PATH}/install" + +echo "ZANO_URL: $ZANO_URL" +echo "IOS_TOOLCHAIN_DIR_PATH: $IOS_TOOLCHAIN_DIR_PATH" +echo "ZANO_MOBILE_IOS_BUILD_FOLDER_ARM64: $ZANO_MOBILE_IOS_BUILD_FOLDER_ARM64" +echo "ZANO_MOBILE_IOS_INSTALL_FOLDER_ARM64: $ZANO_MOBILE_IOS_INSTALL_FOLDER_ARM64" +echo "PREFIX: $PREFIX" +echo "DEST_LIB_DIR: $DEST_LIB_DIR" +echo "DEST_INCLUDE_DIR: $DEST_INCLUDE_DIR" +echo "ZANO_DIR_PATH: $ZANO_DIR_PATH" + +echo "Cloning ios_toolchain from - $IOS_TOOLCHAIN_URL to - $IOS_TOOLCHAIN_DIR_PATH" +git clone $IOS_TOOLCHAIN_URL $IOS_TOOLCHAIN_DIR_PATH +cd $IOS_TOOLCHAIN_DIR_PATH +git checkout $IOS_TOOLCHAIN_VERSION +git submodule update --init --force +cd .. + +echo "Cloning zano from - $ZANO_URL to - $ZANO_DIR_PATH" +git clone $ZANO_URL $ZANO_DIR_PATH +cd $ZANO_DIR_PATH +git fetch origin +if [ $? -ne 0 ]; then + echo "Failed to perform command" + exit 1 +fi +git checkout $ZANO_VERSION +if [ $? -ne 0 ]; then + echo "Failed to perform command" + exit 1 +fi +git submodule update --init --force +if [ $? -ne 0 ]; then + echo "Failed to perform command" + exit 1 +fi +mkdir -p build +cd .. + + +export CMAKE_INCLUDE_PATH="${PREFIX}/include" +export CMAKE_LIBRARY_PATH="${PREFIX}/lib" + + +rm -rf ${ZANO_MOBILE_IOS_BUILD_FOLDER_ARM64} > /dev/null +rm -rf ${ZANO_MOBILE_IOS_INSTALL_FOLDER_ARM64} > /dev/null + +echo "CMAKE_INCLUDE_PATH: $CMAKE_INCLUDE_PATH" +echo "CMAKE_LIBRARY_PATH: $CMAKE_LIBRARY_PATH" +echo "ROOT_DIR: $ROOT_DIR" + + +cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE \ + -DCMAKE_TOOLCHAIN_FILE="${IOS_TOOLCHAIN_DIR_PATH}/ios.toolchain.cmake" \ + -DPLATFORM=OS64 \ + -S"${ZANO_DIR_PATH}" \ + -B"${ZANO_MOBILE_IOS_BUILD_FOLDER_ARM64}" \ + -GXcode \ + -DCAKEWALLET=TRUE \ + -DSKIP_BOOST_FATLIB_LIB=TRUE \ + -DCMAKE_SYSTEM_NAME=iOS \ + -DCMAKE_INSTALL_PREFIX="${ZANO_MOBILE_IOS_INSTALL_FOLDER_ARM64}" \ + -DCMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH=NO \ + -DCMAKE_CXX_FLAGS="-Wno-enum-constexpr-conversion" \ + -DDISABLE_TOR=TRUE + +# -DCMAKE_OSX_ARCHITECTURES="arm64" +# -DCMAKE_IOS_INSTALL_COMBINED=YES + +if [ $? -ne 0 ]; then + echo "Failed to perform command" + exit 1 +fi + +cmake --build "${ZANO_MOBILE_IOS_BUILD_FOLDER_ARM64}" --config $BUILD_TYPE --target install -- -j 4 +if [ $? -ne 0 ]; then + echo "Failed to perform command" + exit 1 +fi + +mkdir -p $DEST_LIB_DIR +mkdir -p $DEST_INCLUDE_DIR + +cp ${ZANO_MOBILE_IOS_INSTALL_FOLDER_ARM64}/lib/* $DEST_LIB_DIR +cp ${ZANO_DIR_PATH}/src/wallet/plain_wallet_api.h $DEST_INCLUDE_DIR diff --git a/scripts/ios/build_zano_all.sh b/scripts/ios/build_zano_all.sh new file mode 100755 index 0000000000..2e5ef81d55 --- /dev/null +++ b/scripts/ios/build_zano_all.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +. ./config.sh +./install_missing_headers.sh +./build_openssl.sh +./build_boost.sh +./build_zano.sh diff --git a/scripts/ios/gen_framework.sh b/scripts/ios/gen_framework.sh index 5c9bcd2281..e9cc448017 100755 --- a/scripts/ios/gen_framework.sh +++ b/scripts/ios/gen_framework.sh @@ -29,3 +29,19 @@ cd $FRWK_DIR # go to iOS framework dir lipo -create $DYLIB_LINK_PATH -output WowneroWallet echo "Generated ${FRWK_DIR}" + +# also generate for zano +IOS_DIR="$(pwd)/../../ios" +DYLIB_NAME="zano_libwallet2_api_c.dylib" +DYLIB_LINK_PATH="${IOS_DIR}/${DYLIB_NAME}" +FRWK_DIR="${IOS_DIR}/ZanoWallet.framework" + +if [ ! -f $DYLIB_LINK_PATH ]; then + echo "Dylib is not found by the link: ${DYLIB_LINK_PATH}" + exit 0 +fi + +cd $FRWK_DIR # go to iOS framework dir +lipo -create $DYLIB_LINK_PATH -output ZanoWallet + +echo "Generated ${FRWK_DIR}" diff --git a/scripts/ios/setup.sh b/scripts/ios/setup.sh index abe8435aec..b17b3718bf 100755 --- a/scripts/ios/setup.sh +++ b/scripts/ios/setup.sh @@ -13,16 +13,21 @@ fi libtool -static -o libboost.a ./libboost_*.a libtool -static -o libhaven.a ./haven/*.a libtool -static -o libmonero.a ./monero/*.a +libtool -static -o libzano.a ./zano/*.a CW_HAVEN_EXTERNAL_LIB=../../../../../cw_haven/ios/External/ios/lib CW_HAVEN_EXTERNAL_INCLUDE=../../../../../cw_haven/ios/External/ios/include CW_MONERO_EXTERNAL_LIB=../../../../../cw_monero/ios/External/ios/lib CW_MONERO_EXTERNAL_INCLUDE=../../../../../cw_monero/ios/External/ios/include +CW_ZANO_EXTERNAL_LIB=../../../../../cw_zano/ios/External/ios/lib +CW_ZANO_EXTERNAL_INCLUDE=../../../../../cw_zano/ios/External/ios/include mkdir -p $CW_HAVEN_EXTERNAL_INCLUDE mkdir -p $CW_MONERO_EXTERNAL_INCLUDE +mkdir -p $CW_ZANO_EXTERNAL_INCLUDE mkdir -p $CW_HAVEN_EXTERNAL_LIB mkdir -p $CW_MONERO_EXTERNAL_LIB +mkdir -p $CW_ZANO_EXTERNAL_LIB ln ./libboost.a ${CW_HAVEN_EXTERNAL_LIB}/libboost.a ln ./libcrypto.a ${CW_HAVEN_EXTERNAL_LIB}/libcrypto.a @@ -37,4 +42,10 @@ ln ./libssl.a ${CW_MONERO_EXTERNAL_LIB}/libssl.a ln ./libsodium.a ${CW_MONERO_EXTERNAL_LIB}/libsodium.a ln ./libunbound.a ${CW_MONERO_EXTERNAL_LIB}/libunbound.a cp ./libmonero.a $CW_MONERO_EXTERNAL_LIB -cp ../include/monero/* $CW_MONERO_EXTERNAL_INCLUDE \ No newline at end of file +cp ../include/monero/* $CW_MONERO_EXTERNAL_INCLUDE + +ln ./libboost.a ${CW_ZANO_EXTERNAL_LIB}/libboost.a +ln ./libcrypto.a ${CW_ZANO_EXTERNAL_LIB}/libcrypto.a +ln ./libssl.a ${CW_ZANO_EXTERNAL_LIB}/libssl.a +cp ./libzano.a $CW_ZANO_EXTERNAL_LIB +cp ../include/zano/* $CW_ZANO_EXTERNAL_INCLUDE diff --git a/scripts/linux/.fvmrc b/scripts/linux/.fvmrc new file mode 100644 index 0000000000..262e5e8378 --- /dev/null +++ b/scripts/linux/.fvmrc @@ -0,0 +1,3 @@ +{ + "flutter": "3.24.0" +} \ No newline at end of file diff --git a/scripts/linux/.gitignore b/scripts/linux/.gitignore new file mode 100644 index 0000000000..9e366fe3b7 --- /dev/null +++ b/scripts/linux/.gitignore @@ -0,0 +1,3 @@ + +# FVM Version Cache +.fvm/ \ No newline at end of file diff --git a/scripts/linux/Dockerfile.linux b/scripts/linux/Dockerfile.linux index c8f4d3bdef..b41873cf55 100644 --- a/scripts/linux/Dockerfile.linux +++ b/scripts/linux/Dockerfile.linux @@ -53,6 +53,8 @@ RUN set -o xtrace \ && apt-get install -y udev qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils \ # for linux tests && apt-get install -y xvfb network-manager ffmpeg x11-utils \ + # for aarch64-linux-gnu + && apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu \ && rm -rf /var/lib/apt/lists/* \ && sh -c 'echo "en_US.UTF-8 UTF-8" > /etc/locale.gen' \ && locale-gen \ diff --git a/scripts/linux/app_config.sh b/scripts/linux/app_config.sh index 0fc41bd0f9..5d9d8597bb 100755 --- a/scripts/linux/app_config.sh +++ b/scripts/linux/app_config.sh @@ -1,5 +1,5 @@ #!/bin/bash -source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/universal_sed.sh" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/functions.sh" CAKEWALLET="cakewallet" DIR=`pwd` diff --git a/scripts/linux/build_monero_all.sh b/scripts/linux/build_monero_all.sh index 558423219a..7c6bab56a1 100755 --- a/scripts/linux/build_monero_all.sh +++ b/scripts/linux/build_monero_all.sh @@ -1,23 +1,22 @@ #!/bin/bash +source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/functions.sh" set -x -e cd "$(dirname "$0")" -NPROC="-j$(nproc)" - ../prepare_moneroc.sh -for COIN in monero wownero; +for COIN in monero wownero zano; do pushd ../monero_c - for target in x86_64-linux-gnu + for target in x86_64-linux-gnu aarch64-linux-gnu do if [[ -f "release/${COIN}/${target}_libwallet2_api_c.so" ]]; then echo "file exist, not building monero_c for ${COIN}/$target."; else - ./build_single.sh ${COIN} $target $NPROC + ./build_single.sh ${COIN} $target -j$MAKE_JOB_COUNT unxz -f ../monero_c/release/${COIN}/${target}_libwallet2_api_c.so.xz fi done diff --git a/scripts/macos/app_config.sh b/scripts/macos/app_config.sh index 452205dd97..e909bb9a23 100755 --- a/scripts/macos/app_config.sh +++ b/scripts/macos/app_config.sh @@ -1,5 +1,5 @@ #!/bin/bash -source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/universal_sed.sh" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/functions.sh" MONERO_COM="monero.com" CAKEWALLET="cakewallet" @@ -36,7 +36,7 @@ case $APP_MACOS_TYPE in $MONERO_COM) CONFIG_ARGS="--monero";; $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero";; #--haven + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano";; #--haven esac cp -rf pubspec_description.yaml pubspec.yaml diff --git a/scripts/macos/build_monero_all.sh b/scripts/macos/build_monero_all.sh index edc8efe813..09b08c620e 100755 --- a/scripts/macos/build_monero_all.sh +++ b/scripts/macos/build_monero_all.sh @@ -22,11 +22,7 @@ then popd done else - if [[ "x$1" == "xuniversal" ]]; then ARCHS=(x86_64 arm64) - else - ARCHS=$(uname -m) - fi for COIN in monero wownero; do MONERO_LIBS="" @@ -34,24 +30,16 @@ else for ARCH in "${ARCHS[@]}"; do if [[ "$ARCH" == "arm64" ]]; then - export HOMEBREW_PREFIX=/opt/homebrew - HOST="aarch64-host-apple-darwin" + HOST="aarch64-apple-darwin" else - export HOMEBREW_PREFIX=/usr/local - HOST="${ARCH}-host-apple-darwin" + HOST="x86_64-apple-darwin" fi MONERO_LIBS="$MONERO_LIBS -arch ${ARCH} ${MONEROC_RELEASE_DIR}/${HOST}_libwallet2_api_c.dylib" WOWNERO_LIBS="$WOWNERO_LIBS -arch ${ARCH} ${WOWNEROC_RELEASE_DIR}/${HOST}_libwallet2_api_c.dylib" - if [[ ! $(uname -m) == $ARCH ]]; then - PRC="arch -${ARCH}" - else - PRC="" - fi - pushd ../monero_c - $PRC ./build_single.sh ${COIN} ${HOST} $NPROC + ./build_single.sh ${COIN} ${HOST} $NPROC unxz -f ./release/${COIN}/${HOST}_libwallet2_api_c.dylib.xz popd done diff --git a/scripts/macos/gen_common.sh b/scripts/macos/gen_common.sh index d1d40edc94..72ff638b6a 100755 --- a/scripts/macos/gen_common.sh +++ b/scripts/macos/gen_common.sh @@ -1,5 +1,5 @@ #!/bin/sh -source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/universal_sed.sh" +source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/functions.sh" gen_podspec() { ARCH=$1 CW_PLUGIN_DIR="`pwd`/../../cw_monero/macos" diff --git a/scripts/prepare_moneroc.sh b/scripts/prepare_moneroc.sh index c0de33f6f1..78776ae580 100755 --- a/scripts/prepare_moneroc.sh +++ b/scripts/prepare_moneroc.sh @@ -8,24 +8,23 @@ if [[ ! -d "monero_c/.git" ]]; then git clone https://github.com/mrcyjanek/monero_c --branch master monero_c cd monero_c - git checkout af5277f96073917185864d3596e82b67bee54e78 + git checkout c3dd64bdee37d361a2c1252d127fb575936e43e6 git reset --hard git submodule update --init --force --recursive ./apply_patches.sh monero ./apply_patches.sh wownero + ./apply_patches.sh zano else cd monero_c fi -if [[ ! -f "monero/.patch-applied" ]]; -then - ./apply_patches.sh monero -fi - -if [[ ! -f "wownero/.patch-applied" ]]; -then - ./apply_patches.sh wownero -fi +for coin in monero wownero zano; +do + if [[ ! -f "$coin/.patch-applied" ]]; + then + ./apply_patches.sh $coin + fi +done cd .. echo "monero_c source prepared". diff --git a/scripts/universal_sed.sh b/scripts/universal_sed.sh deleted file mode 100644 index d8a95684e6..0000000000 --- a/scripts/universal_sed.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash - -detect_sed() { - if sed --version 2>/dev/null | grep -q "GNU"; then - SED_TYPE="GNU" - else - SED_TYPE="BSD" - fi -} - -universal_sed() { - local expression=$1 - local file=$2 - - if [[ "$SED_TYPE" == "GNU" ]]; then - sed -i "$expression" "$file" - else - sed -i '' "$expression" "$file" - fi -} - -detect_sed diff --git a/scripts/windows/build_all.sh b/scripts/windows/build_all.sh index e77f6edb57..90a60dac71 100755 --- a/scripts/windows/build_all.sh +++ b/scripts/windows/build_all.sh @@ -1,4 +1,6 @@ +#!/bin/bash set -x -e +source "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/functions.sh" cd "$(dirname "$0")" @@ -16,19 +18,18 @@ pushd ../monero_c set +e command -v sudo && export SUDO=sudo set -e - NPROC="-j$(nproc)" if [[ ! "x$USE_DOCKER" == "x" ]]; then for COIN in monero wownero; do - $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" - # $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" + $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" + # $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" done else for COIN in monero wownero; do - $SUDO ./build_single.sh ${COIN} x86_64-w64-mingw32 $NPROC - # $SUDO ./build_single.sh ${COIN} i686-w64-mingw32 $NPROC + $SUDO ./build_single.sh ${COIN} x86_64-w64-mingw32 -j$MAKE_JOB_COUNT + # $SUDO ./build_single.sh ${COIN} i686-w64-mingw32 -j$MAKE_JOB_COUNT done fi popd diff --git a/tool/configure.dart b/tool/configure.dart index 4ed4fa9fc8..18fdccc5b7 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -10,6 +10,7 @@ const polygonOutputPath = 'lib/polygon/polygon.dart'; const solanaOutputPath = 'lib/solana/solana.dart'; const tronOutputPath = 'lib/tron/tron.dart'; const wowneroOutputPath = 'lib/wownero/wownero.dart'; +const zanoOutputPath = 'lib/zano/zano.dart'; const walletTypesPath = 'lib/wallet_types.g.dart'; const secureStoragePath = 'lib/core/secure_storage.dart'; const pubspecDefaultPath = 'pubspec_default.yaml'; @@ -28,6 +29,7 @@ Future main(List args) async { final hasSolana = args.contains('${prefix}solana'); final hasTron = args.contains('${prefix}tron'); final hasWownero = args.contains('${prefix}wownero'); + final hasZano = args.contains('${prefix}zano'); final excludeFlutterSecureStorage = args.contains('${prefix}excludeFlutterSecureStorage'); await generateBitcoin(hasBitcoin); @@ -40,6 +42,7 @@ Future main(List args) async { await generateSolana(hasSolana); await generateTron(hasTron); await generateWownero(hasWownero); + await generateZano(hasZano); // await generateBanano(hasEthereum); await generatePubspec( @@ -55,6 +58,7 @@ Future main(List args) async { hasSolana: hasSolana, hasTron: hasTron, hasWownero: hasWownero, + hasZano: hasZano, ); await generateWalletTypes( hasMonero: hasMonero, @@ -68,6 +72,7 @@ Future main(List args) async { hasSolana: hasSolana, hasTron: hasTron, hasWownero: hasWownero, + hasZano: hasZano, ); await injectSecureStorage(!excludeFlutterSecureStorage); } @@ -1400,6 +1405,76 @@ abstract class Tron { await outputFile.writeAsString(output); } +Future generateZano(bool hasImplementation) async { + final outputFile = File(zanoOutputPath); + const zanoCommonHeaders = """ +import 'package:cake_wallet/utils/language_list.dart'; +import 'package:cake_wallet/view_model/send/output.dart'; +import 'package:collection/collection.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/monero_transaction_priority.dart'; +import 'package:cw_core/output_info.dart'; +import 'package:cw_core/transaction_history.dart'; +import 'package:cw_core/transaction_info.dart'; +import 'package:cw_core/transaction_priority.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_credentials.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_service.dart'; +import 'package:cw_core/zano_asset.dart'; +import 'package:hive/hive.dart'; +"""; + const zanoCWHeaders = """ +import 'package:cw_zano/mnemonics/english.dart'; +import 'package:cw_zano/model/zano_transaction_credentials.dart'; +import 'package:cw_zano/model/zano_transaction_info.dart'; +import 'package:cw_zano/zano_formatter.dart'; +import 'package:cw_zano/zano_wallet.dart'; +import 'package:cw_zano/zano_wallet_service.dart'; +import 'package:cw_zano/zano_utils.dart'; +"""; + const zanoCwPart = "part 'cw_zano.dart';"; + const zanoContent = """ +abstract class Zano { + TransactionPriority getDefaultTransactionPriority(); + TransactionPriority deserializeMoneroTransactionPriority({required int raw}); + List getTransactionPriorities(); + List getWordList(String language); + + WalletCredentials createZanoRestoreWalletFromSeedCredentials({required String name, required String password, required int height, required String mnemonic}); + WalletCredentials createZanoNewWalletCredentials({required String name, required String? password}); + Object createZanoTransactionCredentials({required List outputs, required TransactionPriority priority, required CryptoCurrency currency}); + double formatterIntAmountToDouble({required int amount, required CryptoCurrency currency, required bool forFee}); + int formatterParseAmount({required String amount, required CryptoCurrency currency}); + WalletService createZanoWalletService(Box walletInfoSource); + CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo tx); + List getZanoAssets(WalletBase wallet); + String getZanoAssetAddress(CryptoCurrency asset); + Future changeZanoAssetAvailability(WalletBase wallet, CryptoCurrency token); + Future addZanoAssetById(WalletBase wallet, String assetId); + Future deleteZanoAsset(WalletBase wallet, CryptoCurrency token); + Future getZanoAsset(WalletBase wallet, String contractAddress); + String getAddress(WalletBase wallet); + bool validateAddress(String address); +} +"""; + const zanoEmptyDefinition = 'Zano? zano;\n'; + const zanoCWDefinition = 'Zano? zano = CWZano();\n'; + + final output = '$zanoCommonHeaders\n' + + (hasImplementation ? '$zanoCWHeaders\n' : '\n') + + (hasImplementation ? '$zanoCwPart\n\n' : '\n') + + (hasImplementation ? zanoCWDefinition : zanoEmptyDefinition) + + '\n' + + zanoContent; + + if (outputFile.existsSync()) { + await outputFile.delete(); + } + + await outputFile.writeAsString(output); +} + Future generatePubspec({ required bool hasMonero, required bool hasBitcoin, @@ -1413,6 +1488,7 @@ Future generatePubspec({ required bool hasSolana, required bool hasTron, required bool hasWownero, + required bool hasZano, }) async { const cwCore = """ cw_core: @@ -1477,6 +1553,10 @@ Future generatePubspec({ cw_wownero: path: ./cw_wownero """; + const cwZano = """ + cw_zano: + path: ./cw_zano + """; final inputFile = File(pubspecOutputPath); final inputText = await inputFile.readAsString(); final inputLines = inputText.split('\n'); @@ -1538,6 +1618,10 @@ Future generatePubspec({ output += '\n$cwWownero'; } + if (hasZano) { + output += '\n$cwZano'; + } + final outputLines = output.split('\n'); inputLines.insertAll(dependenciesIndex + 1, outputLines); final outputContent = inputLines.join('\n'); @@ -1562,6 +1646,7 @@ Future generateWalletTypes({ required bool hasSolana, required bool hasTron, required bool hasWownero, + required bool hasZano, }) async { final walletTypesFile = File(walletTypesPath); @@ -1621,6 +1706,10 @@ Future generateWalletTypes({ outputContent += '\tWalletType.haven,\n'; } + if (hasZano) { + outputContent += '\tWalletType.zano,\n'; + } + outputContent += '];\n'; await walletTypesFile.writeAsString(outputContent); } diff --git a/tool/download_moneroc_prebuilds.dart b/tool/download_moneroc_prebuilds.dart index 5169ea6870..22384e84a2 100644 --- a/tool/download_moneroc_prebuilds.dart +++ b/tool/download_moneroc_prebuilds.dart @@ -23,7 +23,7 @@ final List triplets = [ // "host-apple-darwin", // not available on CI (yet) // "x86_64-host-apple-darwin", // not available on CI (yet) "aarch64-host-apple-darwin", // apple silicon macbooks (local builds) - "host-apple-ios", + "aarch64-apple-ios", ]; Future main() async { diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 09bc8cfb6f..231aaaf196 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -87,8 +87,6 @@ install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/monero_c/release/monero/x8 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" COMPONENT Runtime) -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" - COMPONENT Runtime) 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" COMPONENT Runtime) diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 4deae34205..3eaee80ac6 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include @@ -22,8 +21,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi")); FlutterLocalAuthenticationPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterLocalAuthenticationPluginCApi")); - FlutterSecureStorageWindowsPluginRegisterWithRegistrar( - registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); PermissionHandlerWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); SharePlusWindowsPluginCApiRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index e0f2c11c04..dfb48b9609 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -6,7 +6,6 @@ list(APPEND FLUTTER_PLUGIN_LIST connectivity_plus flutter_inappwebview_windows flutter_local_authentication - flutter_secure_storage_windows permission_handler_windows share_plus universal_ble