From 5699230ba1c869bf801e2581a91f6a7636834e5e Mon Sep 17 00:00:00 2001 From: leo Date: Thu, 14 Mar 2024 06:28:29 +0000 Subject: [PATCH] added multibalance/asset support for zano (ui) --- cw_core/lib/hive_type_ids.dart | 1 + cw_core/lib/wallet_type.dart | 4 +- cw_zano/lib/default_zano_assets.dart | 29 + cw_zano/lib/zano_asset.dart | 64 +++ cw_zano/lib/zano_wallet.dart | 521 ++++++++++-------- cw_zano/lib/zano_wallet_service.dart | 2 + .../screens/dashboard/edit_token_page.dart | 12 +- .../dashboard/balance_view_model.dart | 2 +- .../dashboard/home_settings_view_model.dart | 28 + lib/view_model/send/output.dart | 8 +- lib/zano/cw_zano.dart | 21 + lib/zano/zano.dart | 7 + 12 files changed, 447 insertions(+), 252 deletions(-) create mode 100644 cw_zano/lib/default_zano_assets.dart create mode 100644 cw_zano/lib/zano_asset.dart diff --git a/cw_core/lib/hive_type_ids.dart b/cw_core/lib/hive_type_ids.dart index 3fa2eb647..478068e77 100644 --- a/cw_core/lib/hive_type_ids.dart +++ b/cw_core/lib/hive_type_ids.dart @@ -15,3 +15,4 @@ const NANO_ACCOUNT_TYPE_ID = 13; const POW_NODE_TYPE_ID = 14; const DERIVATION_TYPE_TYPE_ID = 15; const SPL_TOKEN_TYPE_ID = 16; +const ZANO_ASSET_TYPE_ID = 17; diff --git a/cw_core/lib/wallet_type.dart b/cw_core/lib/wallet_type.dart index 507b93c0e..b93ab7788 100644 --- a/cw_core/lib/wallet_type.dart +++ b/cw_core/lib/wallet_type.dart @@ -184,8 +184,6 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type) { return CryptoCurrency.ltc; case WalletType.haven: return CryptoCurrency.xhv; - case WalletType.zano: - return CryptoCurrency.zano; case WalletType.ethereum: return CryptoCurrency.eth; case WalletType.bitcoinCash: @@ -198,6 +196,8 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type) { return CryptoCurrency.maticpoly; case WalletType.solana: return CryptoCurrency.sol; + case WalletType.zano: + return CryptoCurrency.zano; default: throw Exception( 'Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency'); diff --git a/cw_zano/lib/default_zano_assets.dart b/cw_zano/lib/default_zano_assets.dart new file mode 100644 index 000000000..7dfe1ec64 --- /dev/null +++ b/cw_zano/lib/default_zano_assets.dart @@ -0,0 +1,29 @@ +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_zano/zano_asset.dart'; + +class DefaultZanoAssets { + final List _defaultAssets = [ + ZanoAsset( + assetId: 'd6329b5b1f7c0805b5c345f4957554002a2f557845f64d7645dae0e051a6498a', + decimal: 12, + name: 'Zano', + symbol: 'ZANO', + ), + ZanoAsset( + assetId: '123', + decimal: 12, + name: 'Test Coin', + symbol: 'TC', + ), + ]; + + List get initialZanoAssets => _defaultAssets.map( + (token) { + String? iconPath; + if (CryptoCurrency.all.any((element) => element.title.toUpperCase() == token.symbol.toUpperCase())) { + iconPath = CryptoCurrency.all.singleWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase()).iconPath; + } + return ZanoAsset.copyWith(token, iconPath, 'ZANO'); + }, + ).toList(); +} diff --git a/cw_zano/lib/zano_asset.dart b/cw_zano/lib/zano_asset.dart new file mode 100644 index 000000000..6ea86a075 --- /dev/null +++ b/cw_zano/lib/zano_asset.dart @@ -0,0 +1,64 @@ +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 name; + @HiveField(1) + final String symbol; + @HiveField(2) + final String assetId; + @HiveField(3) + final int decimal; + @HiveField(4, defaultValue: true) + bool _enabled; + @HiveField(5) + final String? iconPath; + @HiveField(6) + final String? tag; + + bool get enabled => _enabled; + + set enabled(bool value) => _enabled = value; + + ZanoAsset({ + required this.name, + required this.symbol, + required this.assetId, + required this.decimal, + bool enabled = true, + this.iconPath, + this.tag, + }) : _enabled = enabled, + super( + name: symbol.toLowerCase(), + title: symbol.toUpperCase(), + fullName: name, + tag: tag, + iconPath: iconPath, + decimals: decimal); + + ZanoAsset.copyWith(ZanoAsset other, String? icon, String? tag) + : this.name = other.name, + this.symbol = other.symbol, + this.assetId = other.assetId, + this.decimal = other.decimal, + this._enabled = other.enabled, + this.tag = tag, + this.iconPath = icon, + super( + name: other.name, + title: other.symbol.toUpperCase(), + fullName: other.name, + tag: tag, + iconPath: icon, + decimals: other.decimal, + ); + + static const typeId = ZANO_ASSET_TYPE_ID; + static const zanoAssetsBoxName = 'zanoAssets'; +} \ No newline at end of file diff --git a/cw_zano/lib/zano_wallet.dart b/cw_zano/lib/zano_wallet.dart index 2baf1aa8b..a90e4b1f2 100644 --- a/cw_zano/lib/zano_wallet.dart +++ b/cw_zano/lib/zano_wallet.dart @@ -4,6 +4,7 @@ import 'dart:ffi'; import 'dart:io'; import 'dart:math'; +import 'package:cw_core/cake_hive.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/monero_amount_format.dart'; import 'package:cw_core/monero_wallet_utils.dart'; @@ -25,6 +26,7 @@ import 'package:cw_zano/api/model/zano_wallet_keys.dart'; import 'package:cw_zano/api/zano_api.dart'; import 'package:cw_zano/exceptions/zano_transaction_creation_exception.dart'; import 'package:cw_zano/pending_zano_transaction.dart'; +import 'package:cw_zano/zano_asset.dart'; import 'package:cw_zano/zano_balance.dart'; import 'package:cw_zano/zano_transaction_credentials.dart'; import 'package:cw_zano/zano_transaction_history.dart'; @@ -32,49 +34,30 @@ import 'package:cw_zano/zano_transaction_info.dart'; import 'package:cw_zano/zano_wallet_addresses.dart'; import 'package:ffi/ffi.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 moneroBlockSize = 1000; -class ZanoWallet = ZanoWalletBase with _$ZanoWallet; - -typedef _load_wallet = Pointer Function( - Pointer, Pointer, Int8); -typedef _LoadWallet = Pointer Function(Pointer, Pointer, int); - const int zanoMixin = 10; -abstract class ZanoWalletBase - extends WalletBase - with Store { - ZanoWalletBase(WalletInfo walletInfo) - : balance = ObservableMap.of( - {CryptoCurrency.zano: ZanoBalance(total: 0, unlocked: 0)}), - _isTransactionUpdating = false, - _hasSyncAfterStartup = false, - walletAddresses = ZanoWalletAddresses(walletInfo), - syncStatus = NotConnectedSyncStatus(), - super(walletInfo) { - transactionHistory = ZanoTransactionHistory(); - // _onAccountChangeReaction = - // reaction((_) => walletAddresses.account, (Account? account) { - // if (account == null) { - // return; - // } - // balance.addAll(getZanoBalance(accountIndex: account.id)); - // /**walletAddresses.updateSubaddressList(accountIndex: account.id);*/ - // }); - } +typedef _load_wallet = Pointer Function(Pointer, Pointer, Int8); +typedef _LoadWallet = Pointer Function(Pointer, Pointer, int); - List history = []; - String defaultAsssetId = ''; +class ZanoWallet = ZanoWalletBase with _$ZanoWallet; +abstract class ZanoWalletBase extends WalletBase with Store { static const int _autoSaveInterval = 30; + static const _statusDelivered = 'delivered'; static const _maxAttempts = 10; + List history = []; + String defaultAsssetId = ''; @override ZanoWalletAddresses walletAddresses; @@ -90,44 +73,59 @@ abstract class ZanoWalletBase String seed = ''; @override - ZanoWalletKeys keys = ZanoWalletKeys( - privateSpendKey: '', - privateViewKey: '', - publicSpendKey: '', - publicViewKey: ''); + ZanoWalletKeys keys = ZanoWalletKeys(privateSpendKey: '', privateViewKey: '', publicSpendKey: '', publicViewKey: ''); + + late final Box zanoAssetsBox; + List get zanoAssets => zanoAssetsBox.values.toList(); //zano_wallet.SyncListener? _listener; // ReactionDisposer? _onAccountChangeReaction; Timer? _updateSyncInfoTimer; + int _cachedBlockchainHeight = 0; int _lastKnownBlockHeight = 0; int _initialSyncHeight = 0; bool _isTransactionUpdating; bool _hasSyncAfterStartup; Timer? _autoSaveTimer; - int _hWallet = 0; + ZanoWalletBase(WalletInfo walletInfo) + : balance = ObservableMap.of({CryptoCurrency.zano: ZanoBalance(total: 0, unlocked: 0)}), + _isTransactionUpdating = false, + _hasSyncAfterStartup = false, + walletAddresses = ZanoWalletAddresses(walletInfo), + syncStatus = NotConnectedSyncStatus(), + super(walletInfo) { + transactionHistory = ZanoTransactionHistory(); + if (!CakeHive.isAdapterRegistered(ZanoAsset.typeId)) { + CakeHive.registerAdapter(ZanoAssetAdapter()); + } + // _onAccountChangeReaction = + // reaction((_) => walletAddresses.account, (Account? account) { + // if (account == null) { + // return; + // } + // balance.addAll(getZanoBalance(accountIndex: account.id)); + // /**walletAddresses.updateSubaddressList(accountIndex: account.id);*/ + // }); + } + int get hWallet => _hWallet; set hWallet(int value) { _hWallet = value; } - Future init(String address) async { - await walletAddresses.init(); - await walletAddresses.updateAddress(address); - - ///balance.addAll(getZanoBalance(/**accountIndex: walletAddresses.account?.id ?? 0*/)); - //_setListeners(); - await updateTransactions(); - - _autoSaveTimer = Timer.periodic( - Duration(seconds: _autoSaveInterval), (_) async => await save()); + @override + int calculateEstimatedFee(TransactionPriority priority, [int? amount = null]) { + return ApiCalls.getCurrentTxFee(priority: priority.raw); } @override - Future? updateBalance() => null; + Future changePassword(String password) async { + ApiCalls.setPassword(hWallet: hWallet, password: password); + } @override void close() { @@ -159,72 +157,6 @@ abstract class ZanoWalletBase } } - 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); - } - - @override - Future startSync() async { - try { - syncStatus = AttemptingSyncStatus(); - _cachedBlockchainHeight = 0; - _lastKnownBlockHeight = 0; - _initialSyncHeight = 0; - _updateSyncInfoTimer ??= - Timer.periodic(Duration(milliseconds: 1200), (_) async { - /**if (isNewTransactionExist()) { - onNewTransaction?.call(); - }*/ - - final walletStatus = getWalletStatus(); - _updateSyncProgress(walletStatus); - // You 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 = getWalletInfo(); - seed = walletInfo.wiExtended.seed; - keys = ZanoWalletKeys( - privateSpendKey: walletInfo.wiExtended.spendPrivateKey, - privateViewKey: walletInfo.wiExtended.viewPrivateKey, - publicSpendKey: walletInfo.wiExtended.spendPublicKey, - publicViewKey: walletInfo.wiExtended.viewPublicKey, - ); - - final _balance = walletInfo.wi.balances.first; - defaultAsssetId = _balance.assetInfo.assetId; - balance = ObservableMap.of({ - CryptoCurrency.zano: - ZanoBalance(total: _balance.total, unlocked: _balance.unlocked) - }); - } - }); - } catch (e) { - syncStatus = FailedSyncStatus(); - print(e); - rethrow; - } - } - @override Future createTransaction(Object credentials) async { final creds = credentials as ZanoTransactionCredentials; @@ -234,12 +166,10 @@ abstract class ZanoWalletBase final fee = calculateEstimatedFee(creds.priority); late List destinations; if (hasMultiDestination) { - if (outputs.any((output) => - output.sendAll || (output.formattedCryptoAmount ?? 0) <= 0)) { + if (outputs.any((output) => output.sendAll || (output.formattedCryptoAmount ?? 0) <= 0)) { throw ZanoTransactionCreationException("You don't have enough coins."); } - final int totalAmount = outputs.fold( - 0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0)); + final int totalAmount = outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0)); if (totalAmount + fee > unlockedBalance) { throw ZanoTransactionCreationException( "You don't have enough coins (required: ${moneroAmountToString(amount: totalAmount + fee)}, unlocked ${moneroAmountToString(amount: unlockedBalance)})."); @@ -247,9 +177,7 @@ abstract class ZanoWalletBase destinations = outputs .map((output) => Destination( amount: output.formattedCryptoAmount ?? 0, - address: output.isParsedAddress - ? output.extractedAddress! - : output.address, + address: output.isParsedAddress ? output.extractedAddress! : output.address, assetId: defaultAsssetId, )) .toList(); @@ -268,16 +196,13 @@ abstract class ZanoWalletBase destinations = [ Destination( amount: amount, - address: output.isParsedAddress - ? output.extractedAddress! - : output.address, + address: output.isParsedAddress ? output.extractedAddress! : output.address, assetId: defaultAsssetId, ) ]; } destinations.forEach((destination) { - debugPrint( - 'destination ${destination.address} ${destination.amount} ${destination.assetId}'); + debugPrint('destination ${destination.address} ${destination.amount} ${destination.assetId}'); }); return PendingZanoTransaction( zanoWallet: this, @@ -288,34 +213,72 @@ abstract class ZanoWalletBase } @override - int calculateEstimatedFee(TransactionPriority priority, - [int? amount = null]) { - return ApiCalls.getCurrentTxFee(priority: priority.raw); - } - - @override - Future save() async { + Future> fetchTransactions() async { try { - await walletAddresses.updateAddressesInBox(); - await backupWalletFiles(name); - await store(); + await _refreshTransactions(); + return history.map((history) => ZanoTransactionInfo.fromHistory(history)).fold>( + {}, + (Map acc, ZanoTransactionInfo tx) { + acc[tx.id] = tx; + return acc; + }, + ); } catch (e) { - print('Error while saving Zano wallet file ${e.toString()}'); + print(e); + return {}; } } - Future store() async { - try { - final json = await invokeMethod('store', '{}'); - final map = jsonDecode(json) as Map; - if (map['result'] == null || map['result']['result'] == null) { - throw 'store empty response'; - } - final _ = - StoreResult.fromJson(map['result']['result'] as Map); - } catch (e) { - print(e.toString()); + GetWalletInfoResult getWalletInfo() { + final json = ApiCalls.getWalletInfo(hWallet); + print('wallet info $json'); // TODO: remove + final result = GetWalletInfoResult.fromJson(jsonDecode(json) as Map); + return result; + } + + GetWalletStatusResult getWalletStatus() { + final json = ApiCalls.getWalletStatus(hWallet: hWallet); + print('wallet status $json'); // TODO: remove + final status = GetWalletStatusResult.fromJson(jsonDecode(json) as Map); + return status; + } + + Future init(String address) async { + _initZanoAssetsBox(); + await walletAddresses.init(); + await walletAddresses.updateAddress(address); + + ///balance.addAll(getZanoBalance(/**accountIndex: walletAddresses.account?.id ?? 0*/)); + //_setListeners(); + await updateTransactions(); + + _autoSaveTimer = Timer.periodic(Duration(seconds: _autoSaveInterval), (_) async => await save()); + } + + Future invokeMethod(String methodName, Object params) async { + var invokeResult = + ApiCalls.asyncCall(methodName: 'invoke', hWallet: hWallet, params: '{"method": "$methodName","params": ${jsonEncode(params)}}'); + var map = jsonDecode(invokeResult) as Map; + int attempts = 0; + if (map['job_id'] != null) { + final jobId = map['job_id'] as int; + do { + await Future.delayed(Duration(milliseconds: attempts < 2 ? 100 : 500)); + final result = ApiCalls.tryPullResult(jobId); + map = jsonDecode(result) as Map; + if (map['status'] != null && map['status'] == _statusDelivered && map['result'] != null) { + return result; + } + } while (++attempts < _maxAttempts); } + return invokeResult; + } + + 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 @@ -342,16 +305,6 @@ abstract class ZanoWalletBase await Directory(currentWalletPath).delete(recursive: true); } - @override - Future changePassword(String password) async { - ApiCalls.setPassword(hWallet: hWallet, password: password); - } - - Future setAsRecovered() async { - walletInfo.isRecovery = false; - await walletInfo.save(); - } - @override Future rescan({required int height}) async { walletInfo.restoreHeight = height; @@ -366,67 +319,81 @@ abstract class ZanoWalletBase await walletInfo.save(); } - Future _refreshTransactions() async { + @override + Future save() async { try { - final result = await invokeMethod('get_recent_txs_and_info', - GetRecentTxsAndInfoParams(offset: 0, count: 30)); - final map = jsonDecode(result) as Map?; - if (map == null) { - print('get_recent_txs_and_info empty response'); - return; - } + await walletAddresses.updateAddressesInBox(); + await backupWalletFiles(name); + await store(); + } catch (e) { + print('Error while saving Zano wallet file ${e.toString()}'); + } + } - final resultData = map['result']; - if (resultData == null) { - print('get_recent_txs_and_info empty response'); - return; - } + Future setAsRecovered() async { + walletInfo.isRecovery = false; + await walletInfo.save(); + } - if (resultData['error'] != null) { - print('get_recent_txs_and_info error ${resultData['error']}'); - return; - } + @override + Future startSync() async { + try { + syncStatus = AttemptingSyncStatus(); + _cachedBlockchainHeight = 0; + _lastKnownBlockHeight = 0; + _initialSyncHeight = 0; + _updateSyncInfoTimer ??= Timer.periodic(Duration(milliseconds: 1200), (_) async { + /*if (isNewTransactionExist()) { + onNewTransaction?.call(); + }*/ - final transfers = resultData['result']?['transfers'] as List?; - if (transfers == null) { - print('get_recent_txs_and_info empty transfers'); - return; - } + final walletStatus = getWalletStatus(); + _updateSyncProgress(walletStatus); + // You 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 = getWalletInfo(); + seed = walletInfo.wiExtended.seed; + keys = ZanoWalletKeys( + privateSpendKey: walletInfo.wiExtended.spendPrivateKey, + privateViewKey: walletInfo.wiExtended.viewPrivateKey, + publicSpendKey: walletInfo.wiExtended.spendPublicKey, + publicViewKey: walletInfo.wiExtended.viewPublicKey, + ); - history = transfers - .map((e) => History.fromJson(e as Map)) - .toList(); + final _balance = walletInfo.wi.balances.first; + defaultAsssetId = _balance.assetInfo.assetId; + balance[CryptoCurrency.zano] = ZanoBalance(total: _balance.total, unlocked: _balance.unlocked); + //balance = ObservableMap.of({CryptoCurrency.zano: ZanoBalance(total: _balance.total, unlocked: _balance.unlocked)}); + } + }); + } catch (e) { + syncStatus = FailedSyncStatus(); + print(e); + rethrow; + } + } + + Future store() async { + try { + final json = await invokeMethod('store', '{}'); + final map = jsonDecode(json) as Map; + if (map['result'] == null || map['result']['result'] == null) { + throw 'store empty response'; + } + final _ = StoreResult.fromJson(map['result']['result'] as Map); } catch (e) { print(e.toString()); } } @override - Future> fetchTransactions() async { - try { - await _refreshTransactions(); - return history - .map( - (history) => ZanoTransactionInfo.fromHistory(history)) - .fold>( - {}, - (Map acc, ZanoTransactionInfo tx) { - acc[tx.id] = tx; - return acc; - }, - ); - } catch (e) { - print(e); - return {}; - } - } + Future? updateBalance() => null; Future updateTransactions() async { try { if (_isTransactionUpdating) { return; } - _isTransactionUpdating = true; final transactions = await fetchTransactions(); transactionHistory.addMany(transactions); @@ -438,6 +405,65 @@ abstract class ZanoWalletBase } } + Future _initZanoAssetsBox() async { + final boxName = "${walletInfo.name.replaceAll(" ", "_")}_${ZanoAsset.zanoAssetsBoxName}"; + if (await CakeHive.boxExists(boxName)) { + zanoAssetsBox = await CakeHive.openBox(boxName); + } else { + zanoAssetsBox = await CakeHive.openBox(boxName.replaceAll(" ", "")); + } + } + + void addInitialAssets() { + final initialZanoAssets = DefaultZanoAssets().initialZanoAssets; + + for (var token in initialZanoAssets) { + zanoAssetsBox.put(token.assetId, token); + } + } + + ZanoAsset createNewZanoAssetObject(ZanoAsset asset, String? iconPath) { + return ZanoAsset( + name: asset.name, + symbol: asset.symbol, + assetId: asset.assetId, + decimal: asset.decimal, + enabled: asset.enabled, + tag: asset.tag ?? "ZANO", + iconPath: iconPath, + ); + } + + Future addZanoAsset(ZanoAsset asset) async { + String? iconPath; + try { + iconPath = CryptoCurrency.all + .firstWhere((element) => element.title.toUpperCase() == asset.title.toUpperCase()) + .iconPath; + } catch (_) {} + + final newAsset = createNewZanoAssetObject(asset, iconPath); + + await zanoAssetsBox.put(newAsset.assetId, newAsset); + + if (asset.enabled) { + balance[asset] = ZanoBalance(total: 0, unlocked: 0); + } else { + balance.remove(asset); + } + } + + Future deleteZanoAsset(ZanoAsset token) async { + await token.delete(); + + balance.remove(token); + //_updateBalance(); + } + + Future getZanoAsset(String assetId) async { + return ZanoAsset(assetId: assetId, decimal: 12, name: 'Not implemented', symbol: 'NI'); + } + // List _getAllTransactions(dynamic _) => // zano_transaction_history // .getAllTransations() @@ -450,12 +476,10 @@ abstract class ZanoWalletBase // } void _askForUpdateBalance() { - debugPrint( - 'askForUpdateBalance'); // TODO: remove, also remove this method completely + debugPrint('askForUpdateBalance'); // TODO: remove, also remove this method completely } - Future _askForUpdateTransactionHistory() async => - await updateTransactions(); + Future _askForUpdateTransactionHistory() async => await updateTransactions(); void _onNewBlock(int height, int blocksLeft, double ptc) async { try { @@ -497,49 +521,60 @@ abstract class ZanoWalletBase } } - 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; - } + Future _refreshTransactions() async { + try { + final result = await invokeMethod('get_recent_txs_and_info', GetRecentTxsAndInfoParams(offset: 0, count: 30)); + final map = jsonDecode(result) as Map?; + if (map == null) { + print('get_recent_txs_and_info empty response'); + return; + } - Future invokeMethod(String methodName, Object params) async { - var invokeResult = ApiCalls.asyncCall( - methodName: 'invoke', - hWallet: hWallet, - params: '{"method": "$methodName","params": ${jsonEncode(params)}}'); - var map = jsonDecode(invokeResult) as Map; - int attempts = 0; - if (map['job_id'] != null) { - final jobId = map['job_id'] as int; - do { - await Future.delayed(Duration(milliseconds: attempts < 2 ? 100 : 500)); - final result = ApiCalls.tryPullResult(jobId); - map = jsonDecode(result) as Map; - if (map['status'] != null && - map['status'] == _statusDelivered && - map['result'] != null) { - return result; - } - } while (++attempts < _maxAttempts); + final resultData = map['result']; + if (resultData == null) { + print('get_recent_txs_and_info empty response'); + return; + } + + if (resultData['error'] != null) { + print('get_recent_txs_and_info error ${resultData['error']}'); + return; + } + + final transfers = resultData['result']?['transfers'] as List?; + if (transfers == null) { + print('get_recent_txs_and_info empty transfers'); + return; + } + + history = transfers.map((e) => History.fromJson(e as Map)).toList(); + } catch (e) { + print(e.toString()); } - return invokeResult; } - GetWalletInfoResult getWalletInfo() { - final json = ApiCalls.getWalletInfo(hWallet); - print('wallet info $json'); // TODO: remove - final result = - GetWalletInfoResult.fromJson(jsonDecode(json) as Map); - return result; - } + void _updateSyncProgress(GetWalletStatusResult walletStatus) { + final syncHeight = walletStatus.currentWalletHeight; + if (_initialSyncHeight <= 0) { + _initialSyncHeight = syncHeight; + } + final bchHeight = walletStatus.currentDaemonHeight; - GetWalletStatusResult getWalletStatus() { - final json = ApiCalls.getWalletStatus(hWallet: hWallet); - print('wallet status $json'); // TODO: remove - final status = GetWalletStatusResult.fromJson( - jsonDecode(json) as Map); - return status; + 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_service.dart b/cw_zano/lib/zano_wallet_service.dart index b3fe00751..12dc16407 100644 --- a/cw_zano/lib/zano_wallet_service.dart +++ b/cw_zano/lib/zano_wallet_service.dart @@ -71,6 +71,7 @@ class ZanoWalletService extends WalletService { onPressed: () async { if (_formKey.currentState!.validate() && (!_showDisclaimer || _disclaimerChecked)) { - await widget.homeSettingsViewModel.addToken(Erc20Token( + // TODO: fix it!!! + await widget.homeSettingsViewModel.addToken(ZanoAsset( name: _tokenNameController.text, symbol: _tokenSymbolController.text, - contractAddress: _contractAddressController.text, + assetId: _contractAddressController.text, decimal: int.parse(_tokenDecimalController.text), )); + // await widget.homeSettingsViewModel.addToken(Erc20Token( + // name: _tokenNameController.text, + // symbol: _tokenSymbolController.text, + // contractAddress: _contractAddressController.text, + // decimal: int.parse(_tokenDecimalController.text), + // )); if (context.mounted) { Navigator.pop(context); } diff --git a/lib/view_model/dashboard/balance_view_model.dart b/lib/view_model/dashboard/balance_view_model.dart index eee53516e..e22e41ca7 100644 --- a/lib/view_model/dashboard/balance_view_model.dart +++ b/lib/view_model/dashboard/balance_view_model.dart @@ -80,7 +80,7 @@ abstract class BalanceViewModelBase with Store { @computed bool get isHomeScreenSettingsEnabled => - isEVMCompatibleChain(wallet.type) || wallet.type == WalletType.solana; + isEVMCompatibleChain(wallet.type) || wallet.type == WalletType.solana || wallet.type == WalletType.zano; @computed bool get hasAccounts => wallet.type == WalletType.monero; diff --git a/lib/view_model/dashboard/home_settings_view_model.dart b/lib/view_model/dashboard/home_settings_view_model.dart index 6d31a5af8..e880dd703 100644 --- a/lib/view_model/dashboard/home_settings_view_model.dart +++ b/lib/view_model/dashboard/home_settings_view_model.dart @@ -6,6 +6,7 @@ import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/solana/solana.dart'; import 'package:cake_wallet/store/settings_store.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/wallet_type.dart'; @@ -57,6 +58,10 @@ abstract class HomeSettingsViewModelBase with Store { await solana!.addSPLToken(_balanceViewModel.wallet, token); } + if (_balanceViewModel.wallet.type == WalletType.zano) { + await zano!.addZanoAsset(_balanceViewModel.wallet, token); + } + _updateTokensList(); _updateFiatPrices(token); } @@ -74,6 +79,10 @@ abstract class HomeSettingsViewModelBase with Store { await solana!.deleteSPLToken(_balanceViewModel.wallet, token); } + if (_balanceViewModel.wallet.type == WalletType.zano) { + await zano!.deleteZanoAsset(_balanceViewModel.wallet, token); + } + _updateTokensList(); } @@ -90,6 +99,10 @@ abstract class HomeSettingsViewModelBase with Store { return await solana!.getSPLToken(_balanceViewModel.wallet, contractAddress); } + if (_balanceViewModel.wallet.type == WalletType.zano) { + return await zano!.getZanoAsset(_balanceViewModel.wallet, contractAddress); + } + return null; } @@ -120,6 +133,10 @@ abstract class HomeSettingsViewModelBase with Store { solana!.addSPLToken(_balanceViewModel.wallet, token); } + if (_balanceViewModel.wallet.type == WalletType.zano) { + await zano!.addZanoAsset(_balanceViewModel.wallet, token); + } + _refreshTokensList(); } @@ -166,6 +183,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 @@ -206,6 +230,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 Polygin, 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/send/output.dart b/lib/view_model/send/output.dart index e287125f2..3966ca31a 100644 --- a/lib/view_model/send/output.dart +++ b/lib/view_model/send/output.dart @@ -135,10 +135,6 @@ abstract class OutputBase with Store { return haven!.formatterMoneroAmountToDouble(amount: fee); } - if (_wallet.type == WalletType.zano) { - return zano!.formatterMoneroAmountToDouble(amount: fee); - } - if (_wallet.type == WalletType.ethereum) { return ethereum!.formatterEthereumAmountToDouble(amount: BigInt.from(fee)); } @@ -146,6 +142,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!.formatterMoneroAmountToDouble(amount: fee); + } } catch (e) { print(e.toString()); } diff --git a/lib/zano/cw_zano.dart b/lib/zano/cw_zano.dart index 058326771..5ceaa41bc 100644 --- a/lib/zano/cw_zano.dart +++ b/lib/zano/cw_zano.dart @@ -79,6 +79,25 @@ class CWZano extends Zano { return CWZanoAccountList(wallet); }*/ + List getZanoAssets(WalletBase wallet) { + final zanoWallet = wallet as ZanoWallet; + return zanoWallet.zanoAssets; + } + + @override + Future addZanoAsset(WalletBase wallet, CryptoCurrency token) async => + await (wallet as ZanoWallet).addZanoAsset(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 mintAddress) async { + final zanoWallet = wallet as ZanoWallet; + return await zanoWallet.getZanoAsset(mintAddress); + } + @override TransactionHistoryBase getTransactionHistory(Object wallet) { final zanoWallet = wallet as ZanoWallet; @@ -214,6 +233,8 @@ class CWZano extends Zano { return asset; } + String getZanoAssetAddress(CryptoCurrency asset) => (asset as ZanoAsset).assetId; + // @override // List getAssetRate() => // getRate().map((rate) => AssetRate(rate.getAssetType(), rate.getRate())).toList(); diff --git a/lib/zano/zano.dart b/lib/zano/zano.dart index 05a4cf000..634d63a88 100644 --- a/lib/zano/zano.dart +++ b/lib/zano/zano.dart @@ -1,4 +1,6 @@ import 'package:cake_wallet/utils/language_list.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_zano/zano_asset.dart'; import 'package:cw_zano/zano_transaction_credentials.dart'; import 'package:mobx/mobx.dart'; import 'package:flutter/foundation.dart'; @@ -118,6 +120,11 @@ abstract class Zano { int getTransactionInfoAccountId(TransactionInfo tx); WalletService createZanoWalletService(Box walletInfoSource); CryptoCurrency assetOfTransaction(TransactionInfo tx); + List getZanoAssets(WalletBase wallet); + String getZanoAssetAddress(CryptoCurrency asset); + Future addZanoAsset(WalletBase wallet, CryptoCurrency token); + Future deleteZanoAsset(WalletBase wallet, CryptoCurrency token); + Future getZanoAsset(WalletBase wallet, String contractAddress); // List getAssetRate(); }