From 42731fcdcb22abf9491b4ff86903dcdaf7cc6684 Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 3 Apr 2024 15:14:53 +0000 Subject: [PATCH] open, create, restore wallet refactoring; whitelists --- cw_zano/lib/api/api_calls.dart | 5 +- cw_zano/lib/api/consts.dart | 1 + .../exceptions/already_exists_exception.dart | 6 - cw_zano/lib/api/exceptions/api_exception.dart | 9 - .../exceptions/create_wallet_exception.dart | 7 - .../restore_from_seed_exception.dart | 5 - .../api/exceptions/transfer_exception.dart | 5 - .../wallet_restore_from_keys_exception.dart | 5 - .../api/exceptions/wrong_seed_exception.dart | 6 - cw_zano/lib/api/model/balance.dart | 4 +- cw_zano/lib/api/utf8.dart | 25 ++ cw_zano/lib/api/{structs => }/utf8_box.dart | 0 cw_zano/lib/default_zano_assets.dart | 33 --- .../lib/model/pending_zano_transaction.dart | 34 +-- cw_zano/lib/model/zano_asset.dart | 12 +- cw_zano/lib/zano_wallet.dart | 191 +++++++++----- cw_zano/lib/zano_wallet_addresses.dart | 54 +--- cw_zano/lib/zano_wallet_api.dart | 245 ++++++++++++++---- cw_zano/lib/zano_wallet_exceptions.dart | 15 ++ cw_zano/lib/zano_wallet_service.dart | 177 ++----------- lib/core/address_validator.dart | 6 +- .../dashboard/home_settings_view_model.dart | 14 - lib/view_model/send/send_view_model.dart | 9 +- lib/zano/cw_zano.dart | 4 +- 24 files changed, 394 insertions(+), 478 deletions(-) delete mode 100644 cw_zano/lib/api/exceptions/already_exists_exception.dart delete mode 100644 cw_zano/lib/api/exceptions/api_exception.dart delete mode 100644 cw_zano/lib/api/exceptions/create_wallet_exception.dart delete mode 100644 cw_zano/lib/api/exceptions/restore_from_seed_exception.dart delete mode 100644 cw_zano/lib/api/exceptions/transfer_exception.dart delete mode 100644 cw_zano/lib/api/exceptions/wallet_restore_from_keys_exception.dart delete mode 100644 cw_zano/lib/api/exceptions/wrong_seed_exception.dart create mode 100644 cw_zano/lib/api/utf8.dart rename cw_zano/lib/api/{structs => }/utf8_box.dart (100%) delete mode 100644 cw_zano/lib/default_zano_assets.dart create mode 100644 cw_zano/lib/zano_wallet_exceptions.dart diff --git a/cw_zano/lib/api/api_calls.dart b/cw_zano/lib/api/api_calls.dart index 428f5afd0..23a76b6e1 100644 --- a/cw_zano/lib/api/api_calls.dart +++ b/cw_zano/lib/api/api_calls.dart @@ -1,6 +1,7 @@ import 'dart:ffi'; -import 'package:cw_zano/api/structs/utf8_box.dart'; +import 'package:cw_zano/api/utf8.dart'; +import 'package:cw_zano/api/utf8_box.dart'; import 'package:cw_zano/api/zano_api.dart'; import 'package:ffi/ffi.dart'; @@ -55,7 +56,7 @@ typedef _stringFunction = Pointer Function(); class ApiCalls { static String _convertUTF8ToString({required Pointer pointer}) { - final str = pointer.toDartString(); + final str = pointer.toDartStringAllowingMalformed(); calloc.free(pointer); return str; } diff --git a/cw_zano/lib/api/consts.dart b/cw_zano/lib/api/consts.dart index 06c3b314b..f3c64f648 100644 --- a/cw_zano/lib/api/consts.dart +++ b/cw_zano/lib/api/consts.dart @@ -1,4 +1,5 @@ class Consts { static const errorWrongSeed = 'WRONG_SEED'; static const errorAlreadyExists = 'ALREADY_EXISTS'; + static const errorWalletWrongId = 'WALLET_WRONG_ID'; } \ No newline at end of file diff --git a/cw_zano/lib/api/exceptions/already_exists_exception.dart b/cw_zano/lib/api/exceptions/already_exists_exception.dart deleted file mode 100644 index a6d00fef9..000000000 --- a/cw_zano/lib/api/exceptions/already_exists_exception.dart +++ /dev/null @@ -1,6 +0,0 @@ -import 'package:cw_zano/api/consts.dart'; -import 'package:cw_zano/api/exceptions/api_exception.dart'; - -class AlreadyExistsException extends ApiException { - AlreadyExistsException(String message): super(Consts.errorAlreadyExists, message); -} \ No newline at end of file diff --git a/cw_zano/lib/api/exceptions/api_exception.dart b/cw_zano/lib/api/exceptions/api_exception.dart deleted file mode 100644 index e9923942b..000000000 --- a/cw_zano/lib/api/exceptions/api_exception.dart +++ /dev/null @@ -1,9 +0,0 @@ -class ApiException implements Exception { - final String code; - final String message; - - ApiException(this.code, this.message); - - @override - String toString() => '${this.runtimeType}(code: $code, message: $message)'; -} \ No newline at end of file diff --git a/cw_zano/lib/api/exceptions/create_wallet_exception.dart b/cw_zano/lib/api/exceptions/create_wallet_exception.dart deleted file mode 100644 index 199f743a0..000000000 --- a/cw_zano/lib/api/exceptions/create_wallet_exception.dart +++ /dev/null @@ -1,7 +0,0 @@ -class CreateWalletException implements Exception { - final String message; - - CreateWalletException(this.message): super(); - @override - String toString() => '${this.runtimeType}(message: $message)'; -} \ No newline at end of file diff --git a/cw_zano/lib/api/exceptions/restore_from_seed_exception.dart b/cw_zano/lib/api/exceptions/restore_from_seed_exception.dart deleted file mode 100644 index 3319cdaf1..000000000 --- a/cw_zano/lib/api/exceptions/restore_from_seed_exception.dart +++ /dev/null @@ -1,5 +0,0 @@ -import 'package:cw_zano/api/exceptions/api_exception.dart'; - -class RestoreFromSeedException extends ApiException { - RestoreFromSeedException(String code, String message): super(code, message); -} \ No newline at end of file diff --git a/cw_zano/lib/api/exceptions/transfer_exception.dart b/cw_zano/lib/api/exceptions/transfer_exception.dart deleted file mode 100644 index d263ff646..000000000 --- a/cw_zano/lib/api/exceptions/transfer_exception.dart +++ /dev/null @@ -1,5 +0,0 @@ -import 'package:cw_zano/api/exceptions/api_exception.dart'; - -class TransferException extends ApiException { - TransferException(String code, String message): super(code, message); -} diff --git a/cw_zano/lib/api/exceptions/wallet_restore_from_keys_exception.dart b/cw_zano/lib/api/exceptions/wallet_restore_from_keys_exception.dart deleted file mode 100644 index c6b6c6ef7..000000000 --- a/cw_zano/lib/api/exceptions/wallet_restore_from_keys_exception.dart +++ /dev/null @@ -1,5 +0,0 @@ -class WalletRestoreFromKeysException implements Exception { - WalletRestoreFromKeysException({required this.message}); - - final String message; -} \ No newline at end of file diff --git a/cw_zano/lib/api/exceptions/wrong_seed_exception.dart b/cw_zano/lib/api/exceptions/wrong_seed_exception.dart deleted file mode 100644 index 624fc24b9..000000000 --- a/cw_zano/lib/api/exceptions/wrong_seed_exception.dart +++ /dev/null @@ -1,6 +0,0 @@ -import 'package:cw_zano/api/consts.dart'; -import 'package:cw_zano/api/exceptions/api_exception.dart'; - -class WrongSeedException extends ApiException { - WrongSeedException(String message): super(Consts.errorWrongSeed, message); -} \ No newline at end of file diff --git a/cw_zano/lib/api/model/balance.dart b/cw_zano/lib/api/model/balance.dart index 4b40a2f4b..0cafc5759 100644 --- a/cw_zano/lib/api/model/balance.dart +++ b/cw_zano/lib/api/model/balance.dart @@ -1,5 +1,3 @@ -import 'package:cw_core/amount_converter.dart'; -import 'package:cw_core/crypto_currency.dart'; import 'package:cw_zano/model/zano_asset.dart'; class Balance { @@ -17,7 +15,7 @@ class Balance { required this.unlocked}); @override - String toString() => '$assetInfo: ${AmountConverter.amountIntToString(CryptoCurrency.zano, total)}/${AmountConverter.amountIntToString(CryptoCurrency.zano, unlocked)}'; + String toString() => '$assetInfo: $total/$unlocked'; factory Balance.fromJson(Map json) => Balance( assetInfo: diff --git a/cw_zano/lib/api/utf8.dart b/cw_zano/lib/api/utf8.dart new file mode 100644 index 000000000..fced763cc --- /dev/null +++ b/cw_zano/lib/api/utf8.dart @@ -0,0 +1,25 @@ +import 'dart:convert'; +import 'dart:ffi'; + +import 'package:ffi/ffi.dart'; + +extension Utf8Pointer on Pointer { + String toDartStringAllowingMalformed({int? length}) { + //_ensureNotNullptr('toDartString'); + final codeUnits = cast(); + if (length != null) { + RangeError.checkNotNegative(length, 'length'); + } else { + length = _length(codeUnits); + } + return utf8.decode(codeUnits.asTypedList(length), allowMalformed: true); + } + + static int _length(Pointer codeUnits) { + var length = 0; + while (codeUnits[length] != 0) { + length++; + } + return length; + } +} \ No newline at end of file diff --git a/cw_zano/lib/api/structs/utf8_box.dart b/cw_zano/lib/api/utf8_box.dart similarity index 100% rename from cw_zano/lib/api/structs/utf8_box.dart rename to cw_zano/lib/api/utf8_box.dart diff --git a/cw_zano/lib/default_zano_assets.dart b/cw_zano/lib/default_zano_assets.dart deleted file mode 100644 index 98f3e58b9..000000000 --- a/cw_zano/lib/default_zano_assets.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'package:cw_core/crypto_currency.dart'; -import 'package:cw_zano/model/zano_asset.dart'; - -class DefaultZanoAssets { - final List _defaultAssets = [ - ZanoAsset( - decimalPoint: 12, - fullName: 'Confidential token', - assetId: 'cc4e69455e63f4a581257382191de6856c2156630b3fba0db4bdd73ffcfb36b6', - owner: '32911fabcf90b9731a152d2a3a75fcbb0a46c78e2f502678bae44c3d6823b4ce', - ticker: 'CT', - enabled: false, - ), - ZanoAsset( - decimalPoint: 12, - fullName: '새로운경제', - assetId: 'bb9590162509f956ff79851fb1bc0ced6646f5d5ba7eae847a9f21c92c39437c', - owner: '32911fabcf90b9731a152d2a3a75fcbb0a46c78e2f502678bae44c3d6823b4ce', - ticker: '새로운경제', - enabled: false, - ), - ]; - - List get initialZanoAssets => _defaultAssets.map( - (asset) { - String? iconPath; - try { - iconPath = CryptoCurrency.all.firstWhere((element) => element.title.toUpperCase() == asset.title.toUpperCase()).iconPath; - } catch (_) {} - return ZanoAsset.copyWith(asset, iconPath, 'ZANO'); - }, - ).toList(); -} diff --git a/cw_zano/lib/model/pending_zano_transaction.dart b/cw_zano/lib/model/pending_zano_transaction.dart index 98f5ec9cc..c63ade6f9 100644 --- a/cw_zano/lib/model/pending_zano_transaction.dart +++ b/cw_zano/lib/model/pending_zano_transaction.dart @@ -1,9 +1,5 @@ -import 'dart:convert'; - import 'package:cw_core/pending_transaction.dart'; -import 'package:cw_zano/api/exceptions/transfer_exception.dart'; import 'package:cw_zano/api/model/destination.dart'; -import 'package:cw_zano/api/model/transfer_params.dart'; import 'package:cw_zano/api/model/transfer_result.dart'; import 'package:cw_zano/zano_formatter.dart'; import 'package:cw_zano/zano_wallet.dart'; @@ -45,33 +41,7 @@ class PendingZanoTransaction with PendingTransaction { @override Future commit() async { - final params = TransferParams( - destinations: destinations, - fee: fee, - mixin: zanoMixinValue, - paymentId: '', - comment: comment, - pushPayer: false, - hideReceiver: true, - ); - final result = await zanoWallet.invokeMethod('transfer', params); - final map = jsonDecode(result); - final resultMap = map['result'] as Map?; - if (resultMap != null) { - final transferResultMap = resultMap['result'] as Map?; - if (transferResultMap != null) { - transferResult = TransferResult.fromJson(transferResultMap); - print('transfer success hash ${transferResult!.txHash}'); - await zanoWallet.fetchTransactions(); - } else { - final errorCode = resultMap['error']['code']; - final code = errorCode is int ? errorCode.toString() : errorCode as String? ?? ''; - final message = resultMap['error']['message'] as String? ?? ''; - print('transfer error $code $message'); - throw TransferException(code, message); - } - } + await zanoWallet.transfer(destinations, fee, comment); + await zanoWallet.fetchTransactions(); } - - } diff --git a/cw_zano/lib/model/zano_asset.dart b/cw_zano/lib/model/zano_asset.dart index 416d45234..c47612c1d 100644 --- a/cw_zano/lib/model/zano_asset.dart +++ b/cw_zano/lib/model/zano_asset.dart @@ -41,7 +41,7 @@ class ZanoAsset extends CryptoCurrency with HiveObjectMixin { this.ticker = '', required this.assetId, this.decimalPoint = ZanoFormatter.defaultDecimalPoint, - bool enabled = true, + bool enabled = false, this.iconPath, this.tag, this.owner = defaultOwner, @@ -59,16 +59,6 @@ class ZanoAsset extends CryptoCurrency with HiveObjectMixin { decimals: decimalPoint, ); - // ZanoAsset.copyWithCurrency(CryptoCurrency other, String? icon, String? tag, String? assetId, {bool enabled = false}): - // ZanoAsset(assetId: assetId, ); - // // this.fullName = other.fullName ?? other.title, - // // this.ticker = other.title, - // // this.decimalPoint = other.decimals, - // // this.assetId = assetId, - // // this.iconPath = icon, - // // this.tag = tag, - // // this._enabled = enabled; - ZanoAsset.copyWith(ZanoAsset other, String? icon, String? tag, {String? assetId, bool enabled = false}) : this.fullName = other.fullName, this.ticker = other.ticker, diff --git a/cw_zano/lib/zano_wallet.dart b/cw_zano/lib/zano_wallet.dart index f17ea8d6b..a3ed43f51 100644 --- a/cw_zano/lib/zano_wallet.dart +++ b/cw_zano/lib/zano_wallet.dart @@ -10,32 +10,30 @@ 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/wallet_base.dart'; +import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; -import 'package:cw_zano/api/api_calls.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_wallet_status_result.dart'; import 'package:cw_zano/api/model/transfer.dart'; -import 'package:cw_zano/model/zano_wallet_keys.dart'; -import 'package:cw_zano/model/zano_transaction_creation_exception.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/zano_formatter.dart'; +import 'package:cw_zano/model/zano_transaction_creation_exception.dart'; import 'package:cw_zano/model/zano_transaction_credentials.dart'; -import 'package:cw_zano/zano_transaction_history.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_service.dart'; import 'package:flutter/material.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; -import 'default_zano_assets.dart'; - part 'zano_wallet.g.dart'; -const int zanoMixinValue = 10; - class ZanoWallet = ZanoWalletBase with _$ZanoWallet; abstract class ZanoWalletBase extends WalletBase with Store, ZanoWalletApi { @@ -62,6 +60,7 @@ abstract class ZanoWalletBase extends WalletBase zanoAssetsBox; List get zanoAssets => zanoAssetsBox.values.toList(); + // final Map zanoAssets = {}; //zano_wallet.SyncListener? _listener; // ReactionDisposer? _onAccountChangeReaction; @@ -103,6 +102,66 @@ abstract class ZanoWalletBase extends WalletBase create({required WalletCredentials credentials}) async { + final wallet = ZanoWallet(credentials.walletInfo!); + await wallet.connectToNode(node: Node()); + final path = await pathForWallet(name: credentials.name, type: credentials.walletInfo!.type); + final createWalletResult = await wallet.createWallet(path, credentials.password!); + _parseCreateWalletResult(createWalletResult, wallet); + //await wallet.store(); // TODO: unnecessary here? + await wallet.init(createWalletResult.wi.address); + return wallet; + } + + static Future restore({required ZanoRestoreWalletFromSeedCredentials credentials}) async { + final wallet = ZanoWallet(credentials.walletInfo!); + await wallet.connectToNode(node: Node()); + final path = await pathForWallet(name: credentials.name, type: credentials.walletInfo!.type); + final createWalletResult = await wallet.restoreWalletFromSeed(path, credentials.password!, credentials.mnemonic); + _parseCreateWalletResult(createWalletResult, wallet); + //await wallet.store(); // TODO: unnecessary here? + 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); + await wallet.connectToNode(node: Node()); + final createWalletResult = await wallet.loadWallet(path, password); + _parseCreateWalletResult(createWalletResult, wallet); + //await wallet.store(); // TODO: unnecessary here? + await wallet.init(createWalletResult.wi.address); + return wallet; + } + + static void _parseCreateWalletResult(CreateWalletResult result, ZanoWallet wallet) { + wallet.hWallet = result.walletId; + wallet.walletAddresses.address = result.wi.address; + for (final item in result.wi.balances) { + if (item.assetInfo.ticker == 'ZANO') { + wallet.balance[CryptoCurrency.zano] = ZanoBalance( + total: item.total, + unlocked: item.unlocked, + decimalPoint: ZanoFormatter.defaultDecimalPoint, + ); + } else { + for (final asset in wallet.balance.keys) { + if (asset is ZanoAsset && asset.assetId == item.assetInfo.assetId) { + wallet.balance[asset] = ZanoBalance( + total: item.total, + unlocked: item.unlocked, + decimalPoint: asset.decimalPoint, + ); + } + } + } + } + if (result.recentHistory.history != null) { + wallet.transfers = result.recentHistory.history!; + } + } + @override void close() { closeWallet(); @@ -221,6 +280,20 @@ abstract class ZanoWalletBase extends WalletBase result = {}; + for (final item in transfers) { + if (item.subtransfers.first.assetId == zanoAssetId) { + result[item.txHash] = ZanoTransactionInfo.fromTransfer(item, 'ZANO', ZanoFormatter.defaultDecimalPoint); + } else { + if (!zanoAssets.containsKey(item.subtransfers.first.assetId)) { + print('no such asset ${item.subtransfers.first.assetId}'); + } else { + final asset = zanoAssets[item.subtransfers.first.assetId]!; + result[item.txHash] = ZanoTransactionInfo.fromTransfer(item, asset.ticker, asset.decimalPoint); + } + } + } + return result;*/ } catch (e) { print(e); return {}; @@ -237,21 +310,11 @@ abstract class ZanoWalletBase extends WalletBase await save()); } - String loadWallet(String path, String password) { - print('load_wallet path $path password $password'); - final result = ApiCalls.loadWallet(path: path, password: password); - print('load_wallet result $result'); - return result; - } - @override Future renameWalletFiles(String newWalletName) async { final currentWalletPath = await pathForWallet(name: name, type: type); @@ -277,6 +340,8 @@ abstract class ZanoWalletBase extends WalletBase rescan({required int height}) => throw UnimplementedError(); + /*@override Future rescan({required int height}) async { walletInfo.restoreHeight = height; walletInfo.isRecovery = true; @@ -287,25 +352,18 @@ abstract class ZanoWalletBase extends WalletBase save() async { try { - await walletAddresses.updateAddressesInBox(); - await backupWalletFiles(name); await store(); + await walletAddresses.updateAddressesInBox(); } catch (e) { print('Error while saving Zano wallet file ${e.toString()}'); } } - Future setAsRecovered() async { - walletInfo.isRecovery = false; - await walletInfo.save(); - } - - bool _calledOnce = false; int _counter = 0; @override @@ -320,11 +378,12 @@ abstract class ZanoWalletBase extends WalletBase !whitelists.any((element) => element.assetId == key)); + + // matching balances and whitelists + // 1. show only balances available in whitelists + // 2. set whitelists available in balances as 'enabled' ('disabled' by default) for (final item in walletInfo.wi.balances) { if (item.assetInfo.ticker == 'ZANO') { balance[CryptoCurrency.zano] = ZanoBalance(total: item.total, unlocked: item.unlocked, decimalPoint: ZanoFormatter.defaultDecimalPoint); } else { - for (final asset in balance.keys) { - if (asset is ZanoAsset && asset.assetId == item.assetInfo.assetId) { - balance[asset] = ZanoBalance(total: item.total, unlocked: item.unlocked, decimalPoint: asset.decimalPoint); - } + final asset = zanoAssetsBox.get(item.assetInfo.assetId); + if (asset == null) { + debugPrint('balance for an unknown asset ${item.assetInfo.assetId}'); + continue; } + if (balance.keys.any((element) => element is ZanoAsset && element.assetId == item.assetInfo.assetId)) { + balance[balance.keys.firstWhere((element) => element is ZanoAsset && element.assetId == item.assetInfo.assetId)] = + ZanoBalance(total: item.total, unlocked: item.unlocked, decimalPoint: asset.decimalPoint); + } else { + balance[asset] = ZanoBalance(total: item.total, unlocked: item.unlocked, decimalPoint: asset.decimalPoint); + } + //balance[asset] = ZanoBalance(total: item.total, unlocked: item.unlocked, decimalPoint: asset.decimalPoint); + asset.enabled = true; } } + // removing balances for assets missing in wallet info balances (in case they were removed for some reason) + balance.removeWhere( + (key, _) => + key != CryptoCurrency.zano && !walletInfo.wi.balances.any((element) => element.assetInfo.assetId == (key as ZanoAsset).assetId), + ); - //await getAssetsWhitelist(); - if (!_calledOnce) { - //await addAssetsWhitelist('00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff'); - //await removeAssetsWhitelist('cc4e69455e63f4a581257382191de6856c2156630b3fba0db4bdd73ffcfb36b6'); - //await removeAssetsWhitelist('bb9590162509f956ff79851fb1bc0ced6646f5d5ba7eae847a9f21c92c39437c'); - //await removeAssetsWhitelist(''); - _calledOnce = true; - } else { - await getAssetsWhitelist(); - } - // if (++_counter >= 10) { - // await getAssetsWhitelist(); - // _counter = 0; - // } + //if (_counter++ % 10 == 0) await _askForUpdateTransactionHistory(); } }); } catch (e) { @@ -387,14 +457,6 @@ abstract class ZanoWalletBase extends WalletBase addZanoAssetById(String assetId) async { if (zanoAssetsBox.containsKey(assetId)) { throw 'zano asset with id $assetId already added'; @@ -407,8 +469,9 @@ abstract class ZanoWalletBase extends WalletBase element.title.toUpperCase() == assetDescriptor.title.toUpperCase()).iconPath; } catch (_) {} + // TODO: copywith two times. was it intended final asset = ZanoAsset.copyWith(assetDescriptor, iconPath, 'ZANO', assetId: assetId, enabled: true); - await zanoAssetsBox.put(asset.assetId, ZanoAsset.copyWith(asset, iconPath, 'ZANO')); + zanoAssetsBox.put(asset.assetId, ZanoAsset.copyWith(asset, iconPath, 'ZANO')); balance[asset] = ZanoBalance(total: 0, unlocked: 0, decimalPoint: asset.decimalPoint); return asset; } @@ -418,7 +481,7 @@ abstract class ZanoWalletBase extends WalletBase element.title.toUpperCase() == asset.title.toUpperCase()).iconPath; } catch (_) {} - await zanoAssetsBox.put(asset.assetId, ZanoAsset.copyWith(asset, iconPath, 'ZANO')); + zanoAssetsBox.put(asset.assetId, ZanoAsset.copyWith(asset, iconPath, 'ZANO')); if (asset.enabled) { final assetDescriptor = await addAssetsWhitelist(asset.assetId); if (assetDescriptor == null) { @@ -460,12 +523,8 @@ abstract class ZanoWalletBase extends WalletBase with Store {*/ abstract class ZanoWalletAddressesBase extends WalletAddresses with Store { ZanoWalletAddressesBase(WalletInfo walletInfo) : address = '', @@ -16,24 +15,10 @@ abstract class ZanoWalletAddressesBase extends WalletAddresses with Store { @observable String address; - // @override - /**@observable - Account? account;*/ - - /**@observable - Subaddress? subaddress;*/ - - /**ZanoSubaddressList subaddressList;*/ - - /**ZanoAccountList accountList;*/ - @override Future init() async { - /*accountList.update(); - account = accountList.accounts.first;*/ - /**updateSubaddressList(accountIndex: account?.id ?? 0);*/ - //address = walletInfo.address; - //await updateAddressesInBox(); + address = walletInfo.address; + await updateAddressesInBox(); } Future updateAddress(String address) async { @@ -44,46 +29,11 @@ abstract class ZanoWalletAddressesBase extends WalletAddresses with Store { @override Future updateAddressesInBox() async { try { - /**final _subaddressList = ZanoSubaddressList();*/ - addressesMap.clear(); addressesMap[address] = ''; await saveAddressesInBox(); - - /*accountList.accounts.forEach((account) { - _subaddressList.update(accountIndex: account.id); - _subaddressList.subaddresses.forEach((subaddress) { - addressesMap[subaddress.address] = subaddress.label; - }); - }); - - await saveAddressesInBox();*/ } catch (e) { print(e.toString()); } } - - // bool validate() { - // accountList.update(); - // final accountListLength = accountList.accounts.length ?? 0; - - // if (accountListLength <= 0) { - // return false; - // } - - // /**subaddressList.update(accountIndex: accountList.accounts.first.id); - // final subaddressListLength = subaddressList.subaddresses.length ?? 0; - - // if (subaddressListLength <= 0) { - // return false; - // }*/ - - // return true; - // } - - /*void updateSubaddressList({required int accountIndex}) { - subaddressList.update(accountIndex: accountIndex); - subaddress = subaddressList.subaddresses.first; - address = subaddress!.address; - }*/ } diff --git a/cw_zano/lib/zano_wallet_api.dart b/cw_zano/lib/zano_wallet_api.dart index ec4d50246..af26c6958 100644 --- a/cw_zano/lib/zano_wallet_api.dart +++ b/cw_zano/lib/zano_wallet_api.dart @@ -1,8 +1,12 @@ import 'dart:convert'; +import 'dart:io'; import 'package:cw_core/transaction_priority.dart'; import 'package:cw_zano/api/api_calls.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_wallet_info_result.dart'; @@ -10,18 +14,25 @@ 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/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:flutter/foundation.dart'; +import 'package:path_provider/path_provider.dart'; import 'api/model/store_result.dart'; -enum _LogType { none, simple, json } +//enum _LogType { none, simple, json } mixin ZanoWalletApi { static const _defaultNodeUri = '195.201.107.230:33336'; static const _statusDelivered = 'delivered'; static const _maxAttempts = 10; - static const _logType = _LogType.simple; + //static const _logType = _LogType.json; + static const _logInfo = true; + static const _logJson = false; + static const int _zanoMixinValue = 10; int _hWallet = 0; @@ -37,43 +48,46 @@ mixin ZanoWalletApi { void closeWallet() => ApiCalls.closeWallet(hWallet: hWallet); - Future setupNode() async => ApiCalls.setupNode( - address: _defaultNodeUri, - login: '', - password: '', - useSSL: false, - isLightWallet: false, - ); - - GetWalletInfoResult getWalletInfo() { - final json = ApiCalls.getWalletInfo(hWallet); - final result = GetWalletInfoResult.fromJson(jsonDecode(json) as Map); - switch (_logType) { - case _LogType.json: - debugPrint('get_wallet_info $json'); - break; - case _LogType.simple: - debugPrint('get_wallet_info got ${result.wi.balances.length} balances: ${result.wi.balances}'); - } - + Future setupNode() async { + debugPrint('[info] init $_defaultNodeUri'); + final result = ApiCalls.setupNode( + address: _defaultNodeUri, + login: '', + password: '', + useSSL: false, + isLightWallet: false, + ); + debugPrint('[info] init result $result'); return result; } - GetWalletStatusResult getWalletStatus() { + Future getWalletInfo() async { + final json = ApiCalls.getWalletInfo(hWallet); + final result = GetWalletInfoResult.fromJson(jsonDecode(json) as Map); + if (_logJson) debugPrint('get_wallet_info $json'); + await _writeLog('get_wallet_info', 'get_wallet_info result $json'); + if (_logInfo) + debugPrint('[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 = ApiCalls.getWalletStatus(hWallet: hWallet); - final status = GetWalletStatusResult.fromJson(jsonDecode(json) as Map); - switch (_logType) { - case _LogType.json: - debugPrint('get_wallet_status $json'); - break; - case _LogType.simple: - debugPrint( - 'get_wallet_status connected: ${status.isDaemonConnected} in refresh: ${status.isInLongRefresh} wallet state: ${status.walletState}'); + if (json == Consts.errorWalletWrongId) { + print('wrong wallet id'); + throw ZanoWalletException('Wrong wallet id'); } + final status = GetWalletStatusResult.fromJson(jsonDecode(json) as Map); + if (_logJson) debugPrint('get_wallet_status $json'); + await _writeLog('get_wallet_status', 'get_wallet_status result $json'); + if (_logInfo) + debugPrint( + '[info] get_wallet_status connected: ${status.isDaemonConnected} in refresh: ${status.isInLongRefresh} progress: ${status.progress} wallet state: ${status.walletState}'); return status; } Future invokeMethod(String methodName, Object params) async { + await _writeLog(methodName, 'invoke method $methodName params: ${jsonEncode(params)} hWallet: $hWallet'); var invokeResult = ApiCalls.asyncCall(methodName: 'invoke', hWallet: hWallet, params: '{"method": "$methodName","params": ${jsonEncode(params)}}'); var map = jsonDecode(invokeResult) as Map; @@ -85,17 +99,19 @@ mixin ZanoWalletApi { final result = ApiCalls.tryPullResult(jobId); map = jsonDecode(result) as Map; if (map['status'] != null && map['status'] == _statusDelivered && map['result'] != null) { + await _writeLog(methodName, 'invoke method $methodName result $result'); return result; } } while (++attempts < _maxAttempts); } + await _writeLog(methodName, 'invoke method $methodName result: $invokeResult'); return invokeResult; } Future> getAssetsWhitelist() async { try { final json = await invokeMethod('assets_whitelist_get', '{}'); - /*if (_logType == _LogType.json)*/ debugPrint('assets_whitelist_get $json'); + if (_logJson) debugPrint('assets_whitelist_get $json'); final map = jsonDecode(json) as Map?; _checkForErrors(map); List assets(String type) => @@ -103,13 +119,13 @@ mixin ZanoWalletApi { final localWhitelist = assets('local_whitelist'); final globalWhitelist = assets('global_whitelist'); final ownAssets = assets('own_assets'); - if (_logType == _LogType.simple) - print('assets_whitelist_get got local whitelist: ${localWhitelist.length} ($localWhitelist); ' + if (_logInfo) + print('[info] assets_whitelist_get got local whitelist: ${localWhitelist.length} ($localWhitelist); ' 'global whitelist: ${globalWhitelist.length} ($globalWhitelist); ' 'own assets: ${ownAssets.length} ($ownAssets)'); return [...localWhitelist, ...globalWhitelist, ...ownAssets]; } catch (e) { - print(e.toString()); + print('[error] assets_whitelist_get $e'); return []; } } @@ -117,19 +133,19 @@ mixin ZanoWalletApi { Future addAssetsWhitelist(String assetId) async { try { final json = await invokeMethod('assets_whitelist_add', AssetIdParams(assetId: assetId)); - if (_logType == _LogType.json) print('assets_whitelist_add $assetId $json'); + if (_logJson) print('assets_whitelist_add $assetId $json'); final map = jsonDecode(json) as Map?; _checkForErrors(map); if (map!['result']!['result']!['status']! == 'OK') { final assetDescriptor = ZanoAsset.fromJson(map['result']!['result']!['asset_descriptor']! as Map); - if (_logType == _LogType.simple) print('assets_whitelist_add added ${assetDescriptor.fullName} ${assetDescriptor.ticker}'); + if (_logInfo) print('[info] assets_whitelist_add added ${assetDescriptor.fullName} ${assetDescriptor.ticker}'); return assetDescriptor; } else { - if (_logType == _LogType.simple) print('assets_whitelist_add status ${map['result']!['result']!['status']!}'); + if (_logInfo) print('[info] assets_whitelist_add status ${map['result']!['result']!['status']!}'); return null; } } catch (e) { - print(e.toString()); + print('[error] assets_whitelist_add $e'); return null; } } @@ -137,13 +153,13 @@ mixin ZanoWalletApi { Future removeAssetsWhitelist(String assetId) async { try { final json = await invokeMethod('assets_whitelist_remove', AssetIdParams(assetId: assetId)); - if (_logType == _LogType.json) print('assets_whitelist_remove $assetId $json'); + if (_logJson) print('assets_whitelist_remove $assetId $json'); final map = jsonDecode(json) as Map?; _checkForErrors(map); - if (_logType == _LogType.simple) print('assets_whitelist_remove status ${map!['result']!['result']!['status']!}'); + if (_logInfo) print('[info] assets_whitelist_remove status ${map!['result']!['result']!['status']!}'); return (map!['result']!['result']!['status']! == 'OK'); } catch (e) { - print(e.toString()); + print('[error] assets_whitelist_remove $e'); return false; } } @@ -159,21 +175,21 @@ mixin ZanoWalletApi { final methodName = 'get_asset_info'; final params = AssetIdParams(assetId: assetId); final result = await _proxyToDaemon('/json_rpc', '{"method": "$methodName","params": ${jsonEncode(params)}}'); - if (_logType == _LogType.json) print('$methodName $assetId ${result?.body}'); + if (_logJson) print('$methodName $assetId ${result?.body}'); if (result == null) { debugPrint('get_asset_info empty result'); return null; } final map = jsonDecode(result.body) as Map?; if (map!['error'] != null) { - if (_logType == _LogType.simple) print('get_asset_info $assetId error ${map['error']!['code']} ${map['error']!['message']}'); + if (_logInfo) print('[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); - if (_logType == _LogType.simple) print('get_asset_info $assetId ${assetDescriptor.fullName} ${assetDescriptor.ticker}'); + if (_logInfo) print('[info] get_asset_info $assetId ${assetDescriptor.fullName} ${assetDescriptor.ticker}'); return assetDescriptor; } else { - if (_logType == _LogType.simple) print('get_asset_info $assetId status ${map['result']!['status']!}'); + if (_logInfo) print('[info] get_asset_info $assetId status ${map['result']!['status']!}'); return null; } } @@ -185,7 +201,7 @@ mixin ZanoWalletApi { _checkForErrors(map); return StoreResult.fromJson(map!['result']['result'] as Map); } catch (e) { - print(e); + print('[error] store $e'); return null; } } @@ -193,17 +209,18 @@ mixin ZanoWalletApi { Future> getRecentTxsAndInfo() async { try { final json = await invokeMethod('get_recent_txs_and_info', GetRecentTxsAndInfoParams(offset: 0, count: 30)); - //debugPrint('get_recent_txs_and_info $json'); + if (_logJson) debugPrint('get_recent_txs_and_info $json'); final map = jsonDecode(json) as Map?; _checkForErrors(map); final transfers = map?['result']?['result']?['transfers'] as List?; if (transfers == null) { - print('get_recent_txs_and_info empty transfers'); + if (_logInfo) print('[info] get_recent_txs_and_info empty transfers'); return []; } + if (_logInfo) print('[info] get_recent_txs_and_info transfers: ${transfers.length}'); return transfers.map((e) => Transfer.fromJson(e as Map)).toList(); } catch (e) { - print(e); + print('[error] get_recent_txs_and_info $e'); return []; } } @@ -212,20 +229,140 @@ mixin ZanoWalletApi { jsonDecode(ApiCalls.getAddressInfo(address: 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 { + if (_logInfo) debugPrint('[info] create_wallet path $path password ${_shorten(password)}'); + await _writeLog('create_wallet', 'create_wallet path $path password ${_shorten(password)}'); + final json = ApiCalls.createWallet(path: path, password: password); + if (_logJson) debugPrint('create_wallet $json'); + await _writeLog('create_wallet', 'create_wallet result $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); + if (_logInfo) debugPrint('[info] create_wallet ${result.name} ${result.seed}'); + return result; + } + + Future restoreWalletFromSeed(String path, String password, String seed) async { + if (_logInfo) debugPrint('[info] restore_wallet path $path password ${_shorten(password)} seed ${_shorten(seed)}'); + await _writeLog('restore_wallet', 'restore_wallet path $path password ${_shorten(password)} seed ${_shorten(seed)}'); + final json = ApiCalls.restoreWalletFromSeed(path: path, password: password, seed: seed); + if (_logJson) debugPrint('restore_wallet $json'); + await _writeLog('restore_wallet', 'restore_wallet result $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); + if (_logInfo) debugPrint('[info] restore_wallet ${result.name} ${result.wi.address}'); + return result; + } + + Future loadWallet(String path, String password, [bool secondAttempt = false]) async { + if (_logInfo) debugPrint('[info] load_wallet path $path password ${_shorten(password)}'); + await _writeLog('load_wallet', 'load_wallet path $path password ${_shorten(password)}'); + final json = ApiCalls.loadWallet(path: path, password: password); + if (_logJson) debugPrint('load_wallet $json'); + await _writeLog('load_wallet', 'load_wallet result $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 && !secondAttempt) { + // TODO: that's not the best solution! + // already connected to this wallet. closing and attempting to reopen + debugPrint('already connected. closing and reopen wallet'); + closeWallet(); + await Future.delayed(const Duration(milliseconds: 500)); + return await loadWallet(path, password, true); + } + 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); + if (_logInfo) debugPrint('[info] load_wallet ${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); + if (_logJson) debugPrint('transfer $json'); + final map = jsonDecode(json); + final resultMap = map['result'] as Map?; + if (resultMap != null) { + final transferResultMap = resultMap['result'] as Map?; + if (transferResultMap != null) { + final transferResult = TransferResult.fromJson(transferResultMap); + debugPrint('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? ?? ''; + debugPrint('transfer error $code $message'); + throw TransferException('Transfer error, $message ($code)'); + } + } + debugPrint('transfer error empty result'); + throw TransferException('Transfer error, empty result'); + } + void _checkForErrors(Map? map) { if (map == null) { - throw 'empty response'; + throw ZanoWalletException('Empty response'); } - final result = map['result']; if (result == null) { - throw 'empty response'; + throw ZanoWalletException('Empty response'); } - if (result['error'] != null) { final code = result['error']!['code'] ?? ''; final message = result['error']!['message'] ?? ''; - throw 'error $code $message'; + 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); + RegExp regExp = RegExp(r'"fee":\s*(\d+(?:\.\d+)?)'); + final matches = regExp.allMatches(logMessage); + if (matches.isNotEmpty) { + await logFile.writeAsString(' ' + matches.map((element) => '${element.group(0)}').join(', ') + '\n', mode: FileMode.append); } } } diff --git a/cw_zano/lib/zano_wallet_exceptions.dart b/cw_zano/lib/zano_wallet_exceptions.dart new file mode 100644 index 000000000..4e82cb2aa --- /dev/null +++ b/cw_zano/lib/zano_wallet_exceptions.dart @@ -0,0 +1,15 @@ +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); +} \ No newline at end of file diff --git a/cw_zano/lib/zano_wallet_service.dart b/cw_zano/lib/zano_wallet_service.dart index b74511db4..68d2772a5 100644 --- a/cw_zano/lib/zano_wallet_service.dart +++ b/cw_zano/lib/zano_wallet_service.dart @@ -1,9 +1,6 @@ -import 'dart:convert'; import 'dart:io'; import 'package:collection/collection.dart'; -import 'package:cw_core/crypto_currency.dart'; -import 'package:cw_core/node.dart'; import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_credentials.dart'; @@ -11,17 +8,7 @@ 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/api/api_calls.dart'; -import 'package:cw_zano/api/consts.dart'; -import 'package:cw_zano/api/exceptions/already_exists_exception.dart'; -import 'package:cw_zano/api/exceptions/create_wallet_exception.dart'; -import 'package:cw_zano/api/exceptions/restore_from_seed_exception.dart'; -import 'package:cw_zano/api/exceptions/wrong_seed_exception.dart'; -import 'package:cw_zano/api/model/create_wallet_result.dart'; -import 'package:cw_zano/model/zano_asset.dart'; -import 'package:cw_zano/model/zano_balance.dart'; -import 'package:cw_zano/zano_formatter.dart'; import 'package:cw_zano/zano_wallet.dart'; -import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; class ZanoNewWalletCredentials extends WalletCredentials { @@ -37,7 +24,13 @@ class ZanoRestoreWalletFromSeedCredentials extends WalletCredentials { 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}) + {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; @@ -61,102 +54,25 @@ class ZanoWalletService extends WalletService create(WalletCredentials credentials, {bool? isTestnet}) async { print('zanowallet service create isTestnet $isTestnet'); // TODO: remove - try { - final wallet = ZanoWallet(credentials.walletInfo!); - await wallet.connectToNode(node: Node()); - final path = await pathForWallet(name: credentials.name, type: getType()); - final json = ApiCalls.createWallet(language: '', path: path, password: credentials.password!); - final map = jsonDecode(json) as Map; - _checkForCreateWalletError(map); - final createWalletResult = CreateWalletResult.fromJson(map['result'] as Map); - _parseCreateWalletResult(createWalletResult, wallet); - await wallet.store(); - await wallet.init(createWalletResult.wi.address); - wallet.addInitialAssets(); - return wallet; - } catch (e) { - // TODO: Implement Exception for wallet list service. - print('ZanoWalletsManager Error: ${e.toString()}'); - rethrow; - } + return await ZanoWalletBase.create(credentials: credentials); } @override Future isWalletExit(String name) async { - try { - final path = await pathForWallet(name: name, type: getType()); - return ApiCalls.isWalletExist(path: path); - } catch (e) { - // TODO: Implement Exception for wallet list service. - print('ZanoWalletsManager Error: $e'); - rethrow; - } + final path = await pathForWallet(name: name, type: getType()); + return ApiCalls.isWalletExist(path: path); } @override Future openWallet(String name, String password) async { + final walletInfo = walletInfoSource.values.firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!; try { - final path = await pathForWallet(name: name, type: getType()); - - if (walletFilesExist(path)) { - await repairOldAndroidWallet(name); - } - - final walletInfo = walletInfoSource.values.firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!; - final wallet = ZanoWallet(walletInfo); - await wallet.connectToNode(node: Node()); - final json = wallet.loadWallet(path, password); - debugPrint('load wallet result $json'); - final map = jsonDecode(json) as Map; - _checkForCreateWalletError(map); - final createWalletResult = CreateWalletResult.fromJson(map['result'] as Map); - _parseCreateWalletResult(createWalletResult, wallet); - await wallet.store(); - await wallet.init(createWalletResult.wi.address); + final wallet = await ZanoWalletBase.open(name: name, password: password, walletInfo: walletInfo); + saveBackup(name); return wallet; } catch (e) { - rethrow; - // TODO: uncomment after merge - //await restoreWalletFilesFromBackup(name); - } - } - - void _checkForCreateWalletError(Map map) { - if (map['error'] != null) { - final code = map['error']!['code'] ?? ''; - final message = map['error']!['message'] ?? ''; - throw CreateWalletException('Error creating/loading wallet $code $message'); - } - if (map['result'] == null) { - throw CreateWalletException('Error creating/loading wallet, empty response'); - } - } - - void _parseCreateWalletResult(CreateWalletResult result, ZanoWallet wallet) { - hWallet = result.walletId; - wallet.hWallet = hWallet; - wallet.walletAddresses.address = result.wi.address; - for (final item in result.wi.balances) { - if (item.assetInfo.ticker == 'ZANO') { - wallet.balance[CryptoCurrency.zano] = ZanoBalance( - total: item.total, - unlocked: item.unlocked, - decimalPoint: ZanoFormatter.defaultDecimalPoint, - ); - } else { - for (final asset in wallet.balance.keys) { - if (asset is ZanoAsset && asset.assetId == item.assetInfo.assetId) { - wallet.balance[asset] = ZanoBalance( - total: item.total, - unlocked: item.unlocked, - decimalPoint: asset.decimalPoint, - ); - } - } - } - } - if (result.recentHistory.history != null) { - wallet.transfers = result.recentHistory.history!; + await restoreWalletFilesFromBackup(name); + return await ZanoWalletBase.open(name: name, password: password, walletInfo: walletInfo); } } @@ -190,70 +106,11 @@ class ZanoWalletService extends WalletService restoreFromKeys(ZanoRestoreWalletFromKeysCredentials credentials, {bool? isTestnet}) async { - throw UnimplementedError('Restore from keys not implemented'); + throw UnimplementedError(); } @override Future restoreFromSeed(ZanoRestoreWalletFromSeedCredentials credentials, {bool? isTestnet}) async { - try { - final wallet = ZanoWallet(credentials.walletInfo!); - await wallet.connectToNode(node: Node()); - final path = await pathForWallet(name: credentials.name, type: getType()); - final json = ApiCalls.restoreWalletFromSeed(path: path, password: credentials.password!, seed: credentials.mnemonic); - final map = jsonDecode(json) as Map; - if (map['result'] != null) { - final createWalletResult = CreateWalletResult.fromJson(map['result'] as Map); - _parseCreateWalletResult(createWalletResult, wallet); - await wallet.store(); - await wallet.init(createWalletResult.wi.address); - wallet.addInitialAssets(); - return wallet; - } else if (map['error'] != null) { - final code = map['error']['code'] as String; - final message = map['error']['message'] as String; - if (code == Consts.errorWrongSeed) { - throw WrongSeedException(message); - } else if (code == Consts.errorAlreadyExists) { - throw AlreadyExistsException(message); - } - throw RestoreFromSeedException(code, message); - } - throw RestoreFromSeedException('', ''); - } catch (e) { - // TODO: Implement Exception for wallet list service. - print('ZanoWalletsManager Error: $e'); - rethrow; - } - } - - Future repairOldAndroidWallet(String name) async { - try { - if (!Platform.isAndroid) { - return; - } - - final oldAndroidWalletDirPath = await outdatedAndroidPathForWalletDir(name: name); - final dir = Directory(oldAndroidWalletDirPath); - - if (!dir.existsSync()) { - return; - } - - final newWalletDirPath = await pathForWalletDir(name: name, type: getType()); - - dir.listSync().forEach((f) { - final file = File(f.path); - final name = f.path.split('/').last; - final newPath = newWalletDirPath + '/$name'; - final newFile = File(newPath); - - if (!newFile.existsSync()) { - newFile.createSync(); - } - newFile.writeAsBytesSync(file.readAsBytesSync()); - }); - } catch (e) { - print(e.toString()); - } + return ZanoWalletBase.restore(credentials: credentials); } } diff --git a/lib/core/address_validator.dart b/lib/core/address_validator.dart index 9407f58f3..5554fbaa0 100644 --- a/lib/core/address_validator.dart +++ b/lib/core/address_validator.dart @@ -15,10 +15,10 @@ class AddressValidator extends TextValidator { : type == CryptoCurrency.zano && !skipZanoAddressValidation ? ZanoUtils.validateAddress : null, - pattern: getPattern(type), + pattern: getPattern(type, skipZanoAddressValidation), length: getLength(type)); - static String getPattern(CryptoCurrency type) { + static String getPattern(CryptoCurrency type, bool skipZanoAddressValidation) { if (type is Erc20Token) { return '0x[0-9a-zA-Z]'; } @@ -126,7 +126,7 @@ class AddressValidator extends TextValidator { case CryptoCurrency.btcln: return '^(lnbc|LNBC)([0-9]{1,}[a-zA-Z0-9]+)'; case CryptoCurrency.zano: - return r'$.^'; // always false, we use additional validation then + return skipZanoAddressValidation ? '[0-9a-zA-Z]' : r'$.^'; // always false, we use additional validation then default: return '[0-9a-zA-Z]'; } diff --git a/lib/view_model/dashboard/home_settings_view_model.dart b/lib/view_model/dashboard/home_settings_view_model.dart index 0dcae8d81..fe61ddc8e 100644 --- a/lib/view_model/dashboard/home_settings_view_model.dart +++ b/lib/view_model/dashboard/home_settings_view_model.dart @@ -47,20 +47,6 @@ abstract class HomeSettingsViewModelBase with Store { @action void setPinNativeToken(bool value) => _settingsStore.pinNativeTokenAtTop = value; - Future addAsset(String assetId) async { - if (_balanceViewModel.wallet.type == WalletType.zano) { - try { - final asset = await zano!.addZanoAssetById(_balanceViewModel.wallet, assetId); - _updateTokensList(); - _updateFiatPrices(asset); - return true; - } catch (e) { - return false; - } - } - return false; - } - Future addToken(CryptoCurrency token) async { if (_balanceViewModel.wallet.type == WalletType.ethereum) { await ethereum!.addErc20Token(_balanceViewModel.wallet, token); diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 66c84ed1d..dd49fbee8 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -168,7 +168,14 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor PendingTransaction? pendingTransaction; @computed - String get balance => wallet.balance[selectedCryptoCurrency]!.formattedAvailableBalance; + String get balance { + try { + return wallet.balance[selectedCryptoCurrency]!.formattedAvailableBalance; + } catch (e) { + print(e); + return 'err'; + } + } @computed bool get isFiatDisabled => balanceViewModel.isFiatDisabled; diff --git a/lib/zano/cw_zano.dart b/lib/zano/cw_zano.dart index 524155bac..bc0773f53 100644 --- a/lib/zano/cw_zano.dart +++ b/lib/zano/cw_zano.dart @@ -81,7 +81,7 @@ class CWZano extends Zano { List getZanoAssets(WalletBase wallet) { wallet as ZanoWallet; - return wallet.zanoAssets; + return wallet.zanoAssets.values.toList(); } @override @@ -216,7 +216,7 @@ class CWZano extends Zano { return CryptoCurrency.zano; } wallet as ZanoWallet; - return wallet.zanoAssets.firstWhere((element) => element.ticker == transaction.tokenSymbol); + return wallet.zanoAssets.values.firstWhere((element) => element.ticker == transaction.tokenSymbol); } String getZanoAssetAddress(CryptoCurrency asset) => (asset as ZanoAsset).assetId;