fixed syncing sync status, decimal division, safe null json parsing

This commit is contained in:
leo 2024-03-10 02:51:30 +00:00
parent 75f1f3f7cc
commit 23485a4bab
19 changed files with 232 additions and 151 deletions

View file

@ -1,5 +1,8 @@
import 'package:decimal/decimal.dart';
import 'package:decimal/intl.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/crypto_currency.dart';
import 'package:rational/rational.dart';
class AmountConverter { class AmountConverter {
static const _moneroAmountLength = 12; static const _moneroAmountLength = 12;
@ -97,7 +100,7 @@ class AmountConverter {
case CryptoCurrency.xusd: case CryptoCurrency.xusd:
return _moneroAmountToString(amount); return _moneroAmountToString(amount);
case CryptoCurrency.zano: case CryptoCurrency.zano:
return _moneroAmountToString(amount); return _moneroAmountToStringUsingDecimals(amount);
default: default:
return ''; return '';
} }
@ -106,9 +109,16 @@ class AmountConverter {
static double cryptoAmountToDouble({required num amount, required num divider}) => static double cryptoAmountToDouble({required num amount, required num divider}) =>
amount / divider; amount / divider;
static Decimal cryptoAmountToDecimal({required int amount, required int divider}) =>
(Decimal.fromInt(amount) / Decimal.fromInt(divider)).toDecimal();
static String _moneroAmountToString(int amount) => _moneroAmountFormat.format( static String _moneroAmountToString(int amount) => _moneroAmountFormat.format(
cryptoAmountToDouble(amount: amount, divider: _moneroAmountDivider)); cryptoAmountToDouble(amount: amount, divider: _moneroAmountDivider));
static String _moneroAmountToStringUsingDecimals(int amount) => _moneroAmountFormat.format(
DecimalIntl(cryptoAmountToDecimal(amount: amount, divider: _moneroAmountDivider)));
static double _moneroAmountToDouble(int amount) => static double _moneroAmountToDouble(int amount) =>
cryptoAmountToDouble(amount: amount, divider: _moneroAmountDivider); cryptoAmountToDouble(amount: amount, divider: _moneroAmountDivider);

View file

@ -177,6 +177,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.4" version: "2.2.4"
decimal:
dependency: "direct main"
description:
name: decimal
sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21"
url: "https://pub.dev"
source: hosted
version: "2.3.3"
encrypt: encrypt:
dependency: "direct main" dependency: "direct main"
description: description:
@ -507,6 +515,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.1" version: "1.2.1"
rational:
dependency: transitive
description:
name: rational
sha256: ba58e9e18df9abde280e8b10051e4bce85091e41e8e7e411b6cde2e738d357cf
url: "https://pub.dev"
source: hosted
version: "2.2.2"
shelf: shelf:
dependency: transitive dependency: transitive
description: description:

View file

@ -19,6 +19,7 @@ dependencies:
flutter_mobx: ^2.0.6+1 flutter_mobx: ^2.0.6+1
intl: ^0.18.0 intl: ^0.18.0
encrypt: ^5.0.1 encrypt: ^5.0.1
decimal: ^2.3.3
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View file

@ -21,14 +21,14 @@ class AssetInfo {
required this.totalMaxSupply}); required this.totalMaxSupply});
factory AssetInfo.fromJson(Map<String, dynamic> json) => AssetInfo( factory AssetInfo.fromJson(Map<String, dynamic> json) => AssetInfo(
assetId: json['asset_id'] as String, assetId: json['asset_id'] as String? ?? '',
currentSupply: json['current_supply'] as int, currentSupply: json['current_supply'] as int? ?? 0,
decimalPoint: json['decimal_point'] as int, decimalPoint: json['decimal_point'] as int? ?? 0,
fullName: json['full_name'] as String, fullName: json['full_name'] as String? ?? '',
hiddenSupply: json['hidden_supply'] as bool, hiddenSupply: json['hidden_supply'] as bool,
metaInfo: json['meta_info'] as String, metaInfo: json['meta_info'] as String? ?? '',
owner: json['owner'] as String, owner: json['owner'] as String? ?? '',
ticker: json['ticker'] as String, ticker: json['ticker'] as String? ?? '',
totalMaxSupply: json['total_max_supply'] as int, totalMaxSupply: json['total_max_supply'] as int? ?? 0,
); );
} }

View file

@ -16,10 +16,10 @@ class Balance {
factory Balance.fromJson(Map<String, dynamic> json) => Balance( factory Balance.fromJson(Map<String, dynamic> json) => Balance(
assetInfo: assetInfo:
AssetInfo.fromJson(json['asset_info'] as Map<String, dynamic>), AssetInfo.fromJson(json['asset_info'] as Map<String, dynamic>? ?? {}),
awaitingIn: json['awaiting_in'] as int, awaitingIn: json['awaiting_in'] as int? ?? 0,
awaitingOut: json['awaiting_out'] as int, awaitingOut: json['awaiting_out'] as int? ?? 0,
total: json['total'] as int, total: json['total'] as int? ?? 0,
unlocked: json['unlocked'] as int, unlocked: json['unlocked'] as int? ?? 0,
); );
} }

View file

@ -25,15 +25,15 @@ class CreateWalletResult {
factory CreateWalletResult.fromJson(Map<String, dynamic> json) => factory CreateWalletResult.fromJson(Map<String, dynamic> json) =>
CreateWalletResult( CreateWalletResult(
name: json['name'] as String, name: json['name'] as String? ?? '',
pass: json['pass'] as String, pass: json['pass'] as String? ?? '',
recentHistory: RecentHistory.fromJson( recentHistory: RecentHistory.fromJson(
json['recent_history'] as Map<String, dynamic>), json['recent_history'] as Map<String, dynamic>? ?? {}),
recovered: json['recovered'] as bool, recovered: json['recovered'] as bool? ?? false,
seed: json['seed'] as String, seed: json['seed'] as String? ?? '',
walletFileSize: json['wallet_file_size'] as int, walletFileSize: json['wallet_file_size'] as int? ?? 0,
walletId: json['wallet_id'] as int, walletId: json['wallet_id'] as int? ?? 0,
walletLocalBcSize: json['wallet_local_bc_size'] as int, walletLocalBcSize: json['wallet_local_bc_size'] as int? ?? 0,
wi: Wi.fromJson(json['wi'] as Map<String, dynamic>), wi: Wi.fromJson(json['wi'] as Map<String, dynamic>? ?? {}),
); );
} }

View file

@ -7,14 +7,14 @@ class Destination {
{required this.amount, required this.address, required this.assetId}); {required this.amount, required this.address, required this.assetId});
factory Destination.fromJson(Map<String, dynamic> json) => Destination( factory Destination.fromJson(Map<String, dynamic> json) => Destination(
amount: int.parse(json['amount'] as String), amount: int.parse(json['amount'] as String? ?? '0'),
address: json['address'] as String, address: json['address'] as String? ?? '',
assetId: json['asset_id'] as String, assetId: json['asset_id'] as String? ?? '',
); );
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
"amount": amount.toString(), 'amount': amount.toString(),
"address": address, 'address': address,
"asset_id": assetId, 'asset_id': assetId,
}; };
} }

View file

@ -8,9 +8,9 @@ class GetAddressInfoResult {
{required this.valid, required this.auditable, required this.paymentId, required this.wrap}); {required this.valid, required this.auditable, required this.paymentId, required this.wrap});
factory GetAddressInfoResult.fromJson(Map<String, dynamic> json) => GetAddressInfoResult( factory GetAddressInfoResult.fromJson(Map<String, dynamic> json) => GetAddressInfoResult(
valid: json['valid'] as bool, valid: json['valid'] as bool? ?? false,
auditable: json['auditable'] as bool, auditable: json['auditable'] as bool? ?? false,
paymentId: json['payment_id'] as bool, paymentId: json['payment_id'] as bool? ?? false,
wrap: json['wrap'] as bool, wrap: json['wrap'] as bool? ?? false,
); );
} }

View file

@ -43,27 +43,27 @@ class History {
}); });
factory History.fromJson(Map<String, dynamic> json) => History( factory History.fromJson(Map<String, dynamic> json) => History(
comment: json['comment'] as String, comment: json['comment'] as String? ?? '',
employedEntries: EmployedEntries.fromJson( employedEntries: EmployedEntries.fromJson(
json['employed_entries'] as Map<String, dynamic>), json['employed_entries'] as Map<String, dynamic>? ?? {}),
fee: json['fee'] as int, fee: json['fee'] as int? ?? 0,
height: json['height'] as int, height: json['height'] as int? ?? 0,
isMining: json['is_mining'] as bool, isMining: json['is_mining'] as bool? ?? false,
isMixing: json['is_mixing'] as bool, isMixing: json['is_mixing'] as bool? ?? false,
isService: json['is_service'] as bool, isService: json['is_service'] as bool? ?? false,
paymentId: json['payment_id'] as String, paymentId: json['payment_id'] as String? ?? '',
remoteAddresses: json['remote_addresses'] == null ? [] : remoteAddresses: json['remote_addresses'] == null ? [] :
(json['remote_addresses'] as List<dynamic>).cast<String>(), (json['remote_addresses'] as List<dynamic>).cast<String>(),
remoteAliases: json['remote_aliases'] == null ? [] : (json['remote_aliases'] as List<dynamic>).cast<String>(), remoteAliases: json['remote_aliases'] == null ? [] : (json['remote_aliases'] as List<dynamic>).cast<String>(),
showSender: json['show_sender'] as bool, showSender: json['show_sender'] as bool? ?? false,
subtransfers: (json['subtransfers'] as List<dynamic>) subtransfers: (json['subtransfers'] as List<dynamic>? ?? [])
.map((e) => Subtransfer.fromJson(e as Map<String, dynamic>)) .map((e) => Subtransfer.fromJson(e as Map<String, dynamic>))
.toList(), .toList(),
timestamp: json['timestamp'] as int, timestamp: json['timestamp'] as int? ?? 0,
transferInternalIndex: json['transfer_internal_index'] is double ? (json['transfer_internal_index'] as double).toInt() : json['transfer_internal_index'] as int, 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, txBlobSize: json['tx_blob_size'] as int? ?? 0,
txHash: json['tx_hash'] as String, txHash: json['tx_hash'] as String? ?? '',
txType: json['tx_type'] as int, txType: json['tx_type'] as int? ?? 0,
unlockTime: json['unlock_time'] as int, unlockTime: json['unlock_time'] as int? ?? 0,
); );
} }

View file

@ -6,8 +6,8 @@ class Receive {
Receive({required this.amount, required this.assetId, required this.index}); Receive({required this.amount, required this.assetId, required this.index});
factory Receive.fromJson(Map<String, dynamic> json) => Receive( factory Receive.fromJson(Map<String, dynamic> json) => Receive(
amount: json['amount'] as int, amount: json['amount'] as int? ?? 0,
assetId: json['asset_id'] as String, assetId: json['asset_id'] as String? ?? '',
index: json['index'] as int, index: json['index'] as int? ?? 0,
); );
} }

View file

@ -14,7 +14,7 @@ class RecentHistory {
history: json['history'] == null ? null : (json['history'] as List<dynamic>) history: json['history'] == null ? null : (json['history'] as List<dynamic>)
.map((e) => History.fromJson(e as Map<String, dynamic>)) .map((e) => History.fromJson(e as Map<String, dynamic>))
.toList(), .toList(),
lastItemIndex: json['last_item_index'] as int, lastItemIndex: json['last_item_index'] as int? ?? 0,
totalHistoryItems: json['total_history_items'] as int, totalHistoryItems: json['total_history_items'] as int? ?? 0,
); );
} }

View file

@ -4,6 +4,6 @@ class StoreResult {
StoreResult({required this.walletFileSize}); StoreResult({required this.walletFileSize});
factory StoreResult.fromJson(Map<String, dynamic> json) => StoreResult( factory StoreResult.fromJson(Map<String, dynamic> json) => StoreResult(
walletFileSize: json['wallet_file_size'] as int, walletFileSize: json['wallet_file_size'] as int? ?? 0,
); );
} }

View file

@ -7,8 +7,8 @@ class Subtransfer {
{required this.amount, required this.assetId, required this.isIncome}); {required this.amount, required this.assetId, required this.isIncome});
factory Subtransfer.fromJson(Map<String, dynamic> json) => Subtransfer( factory Subtransfer.fromJson(Map<String, dynamic> json) => Subtransfer(
amount: json['amount'] as int, amount: json['amount'] as int? ?? 0,
assetId: json['asset_id'] as String, assetId: json['asset_id'] as String? ?? '',
isIncome: json['is_income'] as bool, isIncome: json['is_income'] as bool? ?? false,
); );
} }

View file

@ -20,13 +20,13 @@ class TransferParams {
}); });
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
"destinations": destinations, 'destinations': destinations,
"fee": fee, 'fee': fee,
"mixin": mixin, 'mixin': mixin,
"payment_id": paymentId, 'payment_id': paymentId,
"comment": comment, 'comment': comment,
"push_payer": pushPayer, 'push_payer': pushPayer,
"hide_receiver": hideReceiver, 'hide_receiver': hideReceiver,
}; };
factory TransferParams.fromJson(Map<String, dynamic> json) => TransferParams( factory TransferParams.fromJson(Map<String, dynamic> json) => TransferParams(
@ -35,7 +35,7 @@ class TransferParams {
mixin: json['mixin'] as int? ?? 0, mixin: json['mixin'] as int? ?? 0,
paymentId: json['payment_id'] as String? ?? '', paymentId: json['payment_id'] as String? ?? '',
comment: json['comment'] as String? ?? '', comment: json['comment'] as String? ?? '',
pushPayer: json["push_payer"] as bool? ?? false, pushPayer: json['push_payer'] as bool? ?? false,
hideReceiver: json["hide_receiver"] as bool? ?? false, hideReceiver: json['hide_receiver'] as bool? ?? false,
); );
} }

View file

@ -8,10 +8,10 @@ class WiExtended {
WiExtended({required this.seed, required this.spendPrivateKey, required this.spendPublicKey, required this.viewPrivateKey, required this.viewPublicKey}); WiExtended({required this.seed, required this.spendPrivateKey, required this.spendPublicKey, required this.viewPrivateKey, required this.viewPublicKey});
factory WiExtended.fromJson(Map<String, dynamic> json) => WiExtended( factory WiExtended.fromJson(Map<String, dynamic> json) => WiExtended(
seed: json["seed"] as String? ?? '', seed: json['seed'] as String? ?? '',
spendPrivateKey: json["spend_private_key"] as String? ?? '', spendPrivateKey: json['spend_private_key'] as String? ?? '',
spendPublicKey: json["spend_public_key"] as String? ?? '', spendPublicKey: json['spend_public_key'] as String? ?? '',
viewPrivateKey: json["view_private_key"] as String? ?? '', viewPrivateKey: json['view_private_key'] as String? ?? '',
viewPublicKey: json["view_public_key"] as String? ?? '', viewPublicKey: json['view_public_key'] as String? ?? '',
); );
} }

View file

@ -2,5 +2,5 @@ import 'dart:ffi';
import 'dart:io'; import 'dart:io';
final DynamicLibrary zanoApi = Platform.isAndroid final DynamicLibrary zanoApi = Platform.isAndroid
? DynamicLibrary.open("libcw_zano.so") ? DynamicLibrary.open('libcw_zano.so')
: DynamicLibrary.open("cw_zano.framework/cw_zano"); : DynamicLibrary.open('cw_zano.framework/cw_zano');

View file

@ -1,4 +1,6 @@
import 'package:cw_core/amount_converter.dart';
import 'package:cw_core/balance.dart'; import 'package:cw_core/balance.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/monero_amount_format.dart'; import 'package:cw_core/monero_amount_format.dart';
class ZanoBalance extends Balance { class ZanoBalance extends Balance {
@ -7,10 +9,10 @@ class ZanoBalance extends Balance {
ZanoBalance({required this.total, required this.unlocked}): super(unlocked, total-unlocked); ZanoBalance({required this.total, required this.unlocked}): super(unlocked, total-unlocked);
@override @override
String get formattedAdditionalBalance => moneroAmountToString(amount: total-unlocked); String get formattedAdditionalBalance => AmountConverter.amountIntToString(CryptoCurrency.zano, total-unlocked);
@override @override
String get formattedAvailableBalance => moneroAmountToString(amount: unlocked); String get formattedAvailableBalance => AmountConverter.amountIntToString(CryptoCurrency.zano, unlocked);
@override @override
String get formattedFrozenBalance => ''; String get formattedFrozenBalance => '';

View file

@ -40,28 +40,32 @@ const moneroBlockSize = 1000;
class ZanoWallet = ZanoWalletBase with _$ZanoWallet; class ZanoWallet = ZanoWalletBase with _$ZanoWallet;
typedef _load_wallet = Pointer<Utf8> Function(Pointer<Utf8>, Pointer<Utf8>, Int8); typedef _load_wallet = Pointer<Utf8> Function(
Pointer<Utf8>, Pointer<Utf8>, Int8);
typedef _LoadWallet = Pointer<Utf8> Function(Pointer<Utf8>, Pointer<Utf8>, int); typedef _LoadWallet = Pointer<Utf8> Function(Pointer<Utf8>, Pointer<Utf8>, int);
const int zanoMixin = 10; const int zanoMixin = 10;
abstract class ZanoWalletBase extends WalletBase<ZanoBalance, ZanoTransactionHistory, ZanoTransactionInfo> with Store { abstract class ZanoWalletBase
extends WalletBase<ZanoBalance, ZanoTransactionHistory, ZanoTransactionInfo>
with Store {
ZanoWalletBase(WalletInfo walletInfo) ZanoWalletBase(WalletInfo walletInfo)
: balance = ObservableMap.of({CryptoCurrency.zano: ZanoBalance(total: 0, unlocked: 0)}), : balance = ObservableMap.of(
{CryptoCurrency.zano: ZanoBalance(total: 0, unlocked: 0)}),
_isTransactionUpdating = false, _isTransactionUpdating = false,
_hasSyncAfterStartup = false, _hasSyncAfterStartup = false,
walletAddresses = ZanoWalletAddresses(walletInfo), walletAddresses = ZanoWalletAddresses(walletInfo),
syncStatus = NotConnectedSyncStatus(), syncStatus = NotConnectedSyncStatus(),
super(walletInfo) { super(walletInfo) {
transactionHistory = ZanoTransactionHistory(); transactionHistory = ZanoTransactionHistory();
/*_onAccountChangeReaction = // _onAccountChangeReaction =
reaction((_) => walletAddresses.account, (Account? account) { // reaction((_) => walletAddresses.account, (Account? account) {
if (account == null) { // if (account == null) {
return; // return;
} // }
balance.addAll(getZanoBalance(accountIndex: account.id)); // balance.addAll(getZanoBalance(accountIndex: account.id));
/**walletAddresses.updateSubaddressList(accountIndex: account.id);*/ // /**walletAddresses.updateSubaddressList(accountIndex: account.id);*/
});*/ // });
} }
List<History> history = []; List<History> history = [];
@ -86,10 +90,14 @@ abstract class ZanoWalletBase extends WalletBase<ZanoBalance, ZanoTransactionHis
String seed = ''; String seed = '';
@override @override
ZanoWalletKeys keys = ZanoWalletKeys(privateSpendKey: '', privateViewKey: '', publicSpendKey: '', publicViewKey: ''); ZanoWalletKeys keys = ZanoWalletKeys(
privateSpendKey: '',
privateViewKey: '',
publicSpendKey: '',
publicViewKey: '');
//zano_wallet.SyncListener? _listener; //zano_wallet.SyncListener? _listener;
/**ReactionDisposer? _onAccountChangeReaction;*/ // ReactionDisposer? _onAccountChangeReaction;
Timer? _updateSyncInfoTimer; Timer? _updateSyncInfoTimer;
int _cachedBlockchainHeight = 0; int _cachedBlockchainHeight = 0;
int _lastKnownBlockHeight = 0; int _lastKnownBlockHeight = 0;
@ -114,7 +122,8 @@ abstract class ZanoWalletBase extends WalletBase<ZanoBalance, ZanoTransactionHis
//_setListeners(); //_setListeners();
await updateTransactions(); await updateTransactions();
_autoSaveTimer = Timer.periodic(Duration(seconds: _autoSaveInterval), (_) async => await save()); _autoSaveTimer = Timer.periodic(
Duration(seconds: _autoSaveInterval), (_) async => await save());
} }
@override @override
@ -122,9 +131,10 @@ abstract class ZanoWalletBase extends WalletBase<ZanoBalance, ZanoTransactionHis
@override @override
void close() { void close() {
ApiCalls.closeWallet(hWallet: hWallet);
_updateSyncInfoTimer?.cancel(); _updateSyncInfoTimer?.cancel();
//_listener?.stop(); //_listener?.stop();
/**_onAccountChangeReaction?.reaction.dispose();*/ // _onAccountChangeReaction?.reaction.dispose();
_autoSaveTimer?.cancel(); _autoSaveTimer?.cancel();
} }
@ -133,9 +143,9 @@ abstract class ZanoWalletBase extends WalletBase<ZanoBalance, ZanoTransactionHis
try { try {
syncStatus = ConnectingSyncStatus(); syncStatus = ConnectingSyncStatus();
await ApiCalls.setupNode( await ApiCalls.setupNode(
address: "195.201.107.230:33336", // node.uriRaw, address: '195.201.107.230:33336', // node.uriRaw,
login: "", // node.login, login: '', // node.login,
password: "", // node.password, password: '', // node.password,
useSSL: false, // node.useSSL ?? false, useSSL: false, // node.useSSL ?? false,
isLightWallet: false, // FIXME: hardcoded value isLightWallet: false, // FIXME: hardcoded value
/*socksProxyAddress: node.socksProxyAddress*/ /*socksProxyAddress: node.socksProxyAddress*/
@ -149,41 +159,12 @@ abstract class ZanoWalletBase extends WalletBase<ZanoBalance, ZanoTransactionHis
} }
} }
@override void _updateSyncProgress(GetWalletStatusResult walletStatus) {
Future<void> startSync() async { final syncHeight = walletStatus.currentWalletHeight;
try {
syncStatus = AttemptingSyncStatus();
_cachedBlockchainHeight = 0;
_lastKnownBlockHeight = 0;
_initialSyncHeight = 0;
_updateSyncInfoTimer ??= Timer.periodic(Duration(milliseconds: 1200), (_) async {
/**if (isNewTransactionExist()) {
onNewTransaction?.call();
}*/
GetWalletStatusResult status = getWalletStatus();
// You can call getWalletInfo ONLY if getWalletStatus returns NOT is in long refresh and wallet state is 2 (ready)
if (!status.isInLongRefresh && status.walletState == 2) {
final syncHeight = status.currentWalletHeight;
GetWalletInfoResult result = getWalletInfo();
seed = result.wiExtended.seed;
keys = ZanoWalletKeys(
privateSpendKey: result.wiExtended.spendPrivateKey,
privateViewKey: result.wiExtended.viewPrivateKey,
publicSpendKey: result.wiExtended.spendPublicKey,
publicViewKey: result.wiExtended.viewPublicKey,
);
final _balance = result.wi.balances.first;
defaultAsssetId = _balance.assetInfo.assetId;
balance = ObservableMap.of({CryptoCurrency.zano: ZanoBalance(total: _balance.total, unlocked: _balance.unlocked)});
if (_initialSyncHeight <= 0) { if (_initialSyncHeight <= 0) {
_initialSyncHeight = syncHeight; _initialSyncHeight = syncHeight;
} }
final bchHeight = walletStatus.currentDaemonHeight;
final bchHeight = status.currentDaemonHeight;
if (_lastKnownBlockHeight == syncHeight) { if (_lastKnownBlockHeight == syncHeight) {
return; return;
@ -202,6 +183,40 @@ abstract class ZanoWalletBase extends WalletBase<ZanoBalance, ZanoTransactionHis
// 1. Actual new height; 2. Blocks left to finish; 3. Progress in percents; // 1. Actual new height; 2. Blocks left to finish; 3. Progress in percents;
_onNewBlock.call(syncHeight, left, ptc); _onNewBlock.call(syncHeight, left, ptc);
} }
@override
Future<void> 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) { } catch (e) {
syncStatus = FailedSyncStatus(); syncStatus = FailedSyncStatus();
@ -219,10 +234,12 @@ abstract class ZanoWalletBase extends WalletBase<ZanoBalance, ZanoTransactionHis
final fee = calculateEstimatedFee(creds.priority); final fee = calculateEstimatedFee(creds.priority);
late List<Destination> destinations; late List<Destination> destinations;
if (hasMultiDestination) { 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."); 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) { if (totalAmount + fee > unlockedBalance) {
throw ZanoTransactionCreationException( throw ZanoTransactionCreationException(
"You don't have enough coins (required: ${moneroAmountToString(amount: totalAmount + fee)}, unlocked ${moneroAmountToString(amount: unlockedBalance)})."); "You don't have enough coins (required: ${moneroAmountToString(amount: totalAmount + fee)}, unlocked ${moneroAmountToString(amount: unlockedBalance)}).");
@ -230,7 +247,9 @@ abstract class ZanoWalletBase extends WalletBase<ZanoBalance, ZanoTransactionHis
destinations = outputs destinations = outputs
.map((output) => Destination( .map((output) => Destination(
amount: output.formattedCryptoAmount ?? 0, amount: output.formattedCryptoAmount ?? 0,
address: output.isParsedAddress ? output.extractedAddress! : output.address, address: output.isParsedAddress
? output.extractedAddress!
: output.address,
assetId: defaultAsssetId, assetId: defaultAsssetId,
)) ))
.toList(); .toList();
@ -249,13 +268,16 @@ abstract class ZanoWalletBase extends WalletBase<ZanoBalance, ZanoTransactionHis
destinations = [ destinations = [
Destination( Destination(
amount: amount, amount: amount,
address: output.isParsedAddress ? output.extractedAddress! : output.address, address: output.isParsedAddress
? output.extractedAddress!
: output.address,
assetId: defaultAsssetId, assetId: defaultAsssetId,
) )
]; ];
} }
destinations.forEach((destination) { destinations.forEach((destination) {
debugPrint('destination ${destination.address} ${destination.amount} ${destination.assetId}'); debugPrint(
'destination ${destination.address} ${destination.amount} ${destination.assetId}');
}); });
return PendingZanoTransaction( return PendingZanoTransaction(
zanoWallet: this, zanoWallet: this,
@ -266,7 +288,8 @@ abstract class ZanoWalletBase extends WalletBase<ZanoBalance, ZanoTransactionHis
} }
@override @override
int calculateEstimatedFee(TransactionPriority priority, [int? amount = null]) { int calculateEstimatedFee(TransactionPriority priority,
[int? amount = null]) {
return ApiCalls.getCurrentTxFee(priority: priority.raw); return ApiCalls.getCurrentTxFee(priority: priority.raw);
} }
@ -288,7 +311,8 @@ abstract class ZanoWalletBase extends WalletBase<ZanoBalance, ZanoTransactionHis
if (map['result'] == null || map['result']['result'] == null) { if (map['result'] == null || map['result']['result'] == null) {
throw 'store empty response'; throw 'store empty response';
} }
final _ = StoreResult.fromJson(map['result']['result'] as Map<String, dynamic>); final _ =
StoreResult.fromJson(map['result']['result'] as Map<String, dynamic>);
} catch (e) { } catch (e) {
print(e.toString()); print(e.toString());
} }
@ -344,7 +368,8 @@ abstract class ZanoWalletBase extends WalletBase<ZanoBalance, ZanoTransactionHis
Future<void> _refreshTransactions() async { Future<void> _refreshTransactions() async {
try { try {
final result = await invokeMethod('get_recent_txs_and_info', GetRecentTxsAndInfoParams(offset: 0, count: 30)); final result = await invokeMethod('get_recent_txs_and_info',
GetRecentTxsAndInfoParams(offset: 0, count: 30));
final map = jsonDecode(result) as Map<String, dynamic>?; final map = jsonDecode(result) as Map<String, dynamic>?;
if (map == null) { if (map == null) {
print('get_recent_txs_and_info empty response'); print('get_recent_txs_and_info empty response');
@ -368,7 +393,9 @@ abstract class ZanoWalletBase extends WalletBase<ZanoBalance, ZanoTransactionHis
return; return;
} }
history = transfers.map((e) => History.fromJson(e as Map<String, dynamic>)).toList(); history = transfers
.map((e) => History.fromJson(e as Map<String, dynamic>))
.toList();
} catch (e) { } catch (e) {
print(e.toString()); print(e.toString());
} }
@ -378,7 +405,10 @@ abstract class ZanoWalletBase extends WalletBase<ZanoBalance, ZanoTransactionHis
Future<Map<String, ZanoTransactionInfo>> fetchTransactions() async { Future<Map<String, ZanoTransactionInfo>> fetchTransactions() async {
try { try {
await _refreshTransactions(); await _refreshTransactions();
return history.map<ZanoTransactionInfo>((history) => ZanoTransactionInfo.fromHistory(history)).fold<Map<String, ZanoTransactionInfo>>( return history
.map<ZanoTransactionInfo>(
(history) => ZanoTransactionInfo.fromHistory(history))
.fold<Map<String, ZanoTransactionInfo>>(
<String, ZanoTransactionInfo>{}, <String, ZanoTransactionInfo>{},
(Map<String, ZanoTransactionInfo> acc, ZanoTransactionInfo tx) { (Map<String, ZanoTransactionInfo> acc, ZanoTransactionInfo tx) {
acc[tx.id] = tx; acc[tx.id] = tx;
@ -420,10 +450,12 @@ abstract class ZanoWalletBase extends WalletBase<ZanoBalance, ZanoTransactionHis
// } // }
void _askForUpdateBalance() { void _askForUpdateBalance() {
debugPrint('askForUpdateBalance'); // TODO: remove, also remove this method completely debugPrint(
'askForUpdateBalance'); // TODO: remove, also remove this method completely
} }
Future<void> _askForUpdateTransactionHistory() async => await updateTransactions(); Future<void> _askForUpdateTransactionHistory() async =>
await updateTransactions();
void _onNewBlock(int height, int blocksLeft, double ptc) async { void _onNewBlock(int height, int blocksLeft, double ptc) async {
try { try {
@ -473,7 +505,10 @@ abstract class ZanoWalletBase extends WalletBase<ZanoBalance, ZanoTransactionHis
} }
Future<String> invokeMethod(String methodName, Object params) async { Future<String> invokeMethod(String methodName, Object params) async {
var invokeResult = ApiCalls.asyncCall(methodName: 'invoke', hWallet: hWallet, params: '{"method": "$methodName","params": ${jsonEncode(params)}}'); var invokeResult = ApiCalls.asyncCall(
methodName: 'invoke',
hWallet: hWallet,
params: '{"method": "$methodName","params": ${jsonEncode(params)}}');
var map = jsonDecode(invokeResult) as Map<String, dynamic>; var map = jsonDecode(invokeResult) as Map<String, dynamic>;
int attempts = 0; int attempts = 0;
if (map['job_id'] != null) { if (map['job_id'] != null) {
@ -482,7 +517,9 @@ abstract class ZanoWalletBase extends WalletBase<ZanoBalance, ZanoTransactionHis
await Future.delayed(Duration(milliseconds: attempts < 2 ? 100 : 500)); await Future.delayed(Duration(milliseconds: attempts < 2 ? 100 : 500));
final result = ApiCalls.tryPullResult(jobId); final result = ApiCalls.tryPullResult(jobId);
map = jsonDecode(result) as Map<String, dynamic>; map = jsonDecode(result) as Map<String, dynamic>;
if (map['status'] != null && map['status'] == _statusDelivered && map['result'] != null) { if (map['status'] != null &&
map['status'] == _statusDelivered &&
map['result'] != null) {
return result; return result;
} }
} while (++attempts < _maxAttempts); } while (++attempts < _maxAttempts);
@ -493,14 +530,16 @@ abstract class ZanoWalletBase extends WalletBase<ZanoBalance, ZanoTransactionHis
GetWalletInfoResult getWalletInfo() { GetWalletInfoResult getWalletInfo() {
final json = ApiCalls.getWalletInfo(hWallet); final json = ApiCalls.getWalletInfo(hWallet);
print('wallet info $json'); // TODO: remove print('wallet info $json'); // TODO: remove
final result = GetWalletInfoResult.fromJson(jsonDecode(json) as Map<String, dynamic>); final result =
GetWalletInfoResult.fromJson(jsonDecode(json) as Map<String, dynamic>);
return result; return result;
} }
GetWalletStatusResult getWalletStatus() { GetWalletStatusResult getWalletStatus() {
final json = ApiCalls.getWalletStatus(hWallet: hWallet); final json = ApiCalls.getWalletStatus(hWallet: hWallet);
print('wallet status $json'); // TODO: remove print('wallet status $json'); // TODO: remove
final status = GetWalletStatusResult.fromJson(jsonDecode(json) as Map<String, dynamic>); final status = GetWalletStatusResult.fromJson(
jsonDecode(json) as Map<String, dynamic>);
return status; return status;
} }
} }

View file

@ -63,9 +63,9 @@ class ZanoWalletService extends WalletService<ZanoNewWalletCredentials, ZanoRest
final wallet = ZanoWallet(credentials.walletInfo!); final wallet = ZanoWallet(credentials.walletInfo!);
await wallet.connectToNode(node: Node()); await wallet.connectToNode(node: Node());
final path = await pathForWallet(name: credentials.name, type: getType()); final path = await pathForWallet(name: credentials.name, type: getType());
final result = ApiCalls.createWallet(language: "", path: path, password: credentials.password!); final result = ApiCalls.createWallet(language: '', path: path, password: credentials.password!);
final map = json.decode(result) as Map<String, dynamic>; final map = json.decode(result) as Map<String, dynamic>;
if (map['result'] == null) throw CreateWalletException(''); _checkForCreateWalletError(map);
final createWalletResult = CreateWalletResult.fromJson(map['result'] as Map<String, dynamic>); final createWalletResult = CreateWalletResult.fromJson(map['result'] as Map<String, dynamic>);
_parseCreateWalletResult(createWalletResult, wallet); _parseCreateWalletResult(createWalletResult, wallet);
await wallet.store(); await wallet.store();
@ -103,9 +103,9 @@ class ZanoWalletService extends WalletService<ZanoNewWalletCredentials, ZanoRest
final wallet = ZanoWallet(walletInfo); final wallet = ZanoWallet(walletInfo);
await wallet.connectToNode(node: Node()); await wallet.connectToNode(node: Node());
final result = wallet.loadWallet(path, password); final result = wallet.loadWallet(path, password);
print("load wallet result $result"); print('load wallet result $result');
final map = json.decode(result) as Map<String, dynamic>; final map = json.decode(result) as Map<String, dynamic>;
if (map['result'] == null) throw CreateWalletException(''); _checkForCreateWalletError(map);
final createWalletResult = CreateWalletResult.fromJson(map['result'] as Map<String, dynamic>); final createWalletResult = CreateWalletResult.fromJson(map['result'] as Map<String, dynamic>);
_parseCreateWalletResult(createWalletResult, wallet); _parseCreateWalletResult(createWalletResult, wallet);
await wallet.store(); await wallet.store();
@ -113,6 +113,19 @@ class ZanoWalletService extends WalletService<ZanoNewWalletCredentials, ZanoRest
return wallet; return wallet;
} catch (e) { } catch (e) {
rethrow; rethrow;
// TODO: uncomment after merge
//await restoreWalletFilesFromBackup(name);
}
}
void _checkForCreateWalletError(Map<String, dynamic> 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');
} }
} }
@ -158,7 +171,7 @@ class ZanoWalletService extends WalletService<ZanoNewWalletCredentials, ZanoRest
@override @override
Future<ZanoWallet> restoreFromKeys(ZanoRestoreWalletFromKeysCredentials credentials) async { Future<ZanoWallet> restoreFromKeys(ZanoRestoreWalletFromKeysCredentials credentials) async {
throw UnimplementedError("Restore from keys not implemented"); throw UnimplementedError('Restore from keys not implemented');
} }
@override @override