changed fields to BigInt, some fixes

This commit is contained in:
leo 2024-08-07 12:32:47 +00:00
parent e7b29c0a56
commit 02bfe643d9
26 changed files with 262 additions and 455 deletions

View file

@ -1,5 +1,6 @@
import 'dart:ffi'; import 'dart:ffi';
import 'package:cw_zano/api/utf8.dart';
import 'package:cw_zano/api/utf8_box.dart'; import 'package:cw_zano/api/utf8_box.dart';
import 'package:cw_zano/api/zano_api.dart'; import 'package:cw_zano/api/zano_api.dart';
import 'package:ffi/ffi.dart'; import 'package:ffi/ffi.dart';
@ -52,12 +53,13 @@ typedef _SetPassword = Pointer<Utf8> Function(int hWallet, Pointer<Utf8> passwor
// char* get_connectivity_status() // char* get_connectivity_status()
// char* get_version() // char* get_version()
// get_opened_wallets()
typedef _stringFunction = Pointer<Utf8> Function(); typedef _stringFunction = Pointer<Utf8> Function();
class ApiCalls { class ApiCalls {
static String _convertUTF8ToString({required Pointer<Utf8> pointer}) { static String _convertUTF8ToString({required Pointer<Utf8> pointer}) {
final str = pointer.toDartString(); //final str = pointer.toDartString();
//final str = pointer.toDartStringAllowingMalformed(); final str = pointer.toDartStringAllowingMalformed();
calloc.free(pointer); calloc.free(pointer);
return str; return str;
} }
@ -181,8 +183,11 @@ class ApiCalls {
static int getCurrentTxFee({required int priority}) => _getCurrentTxFeeNative(priority); static int getCurrentTxFee({required int priority}) => _getCurrentTxFeeNative(priority);
static final _getConnectivityStatusNative = zanoApi.lookup<NativeFunction<_stringFunction>>('get_connectivity_status').asFunction<_stringFunction>(); static final _getConnectivityStatusNative = zanoApi.lookup<NativeFunction<_stringFunction>>('get_connectivity_status').asFunction<_stringFunction>();
static String getConnectivityStatus() => _performApiCall(() => _getConnectivityStatusNative()); static String getConnectivityStatus() => _performApiCall(() => _getConnectivityStatusNative());
static final _getOpenedWalletsNative = zanoApi.lookup<NativeFunction<_stringFunction>>('get_opened_wallets').asFunction<_stringFunction>();
static String getOpenedWallets() => _performApiCall(() => _getOpenedWalletsNative());
static final _getAddressInfoNative = zanoApi.lookup<NativeFunction<_GetAddressInfo>>('get_address_info').asFunction<_GetAddressInfo>(); static final _getAddressInfoNative = zanoApi.lookup<NativeFunction<_GetAddressInfo>>('get_address_info').asFunction<_GetAddressInfo>();

View file

@ -2,4 +2,5 @@ class Consts {
static const errorWrongSeed = 'WRONG_SEED'; static const errorWrongSeed = 'WRONG_SEED';
static const errorAlreadyExists = 'ALREADY_EXISTS'; static const errorAlreadyExists = 'ALREADY_EXISTS';
static const errorWalletWrongId = 'WALLET_WRONG_ID'; static const errorWalletWrongId = 'WALLET_WRONG_ID';
static const errorBusy = 'BUSY';
} }

View file

@ -1,11 +1,12 @@
import 'package:cw_zano/model/zano_asset.dart'; import 'package:cw_zano/model/zano_asset.dart';
import 'package:cw_zano/zano_formatter.dart';
class Balance { class Balance {
final ZanoAsset assetInfo; final ZanoAsset assetInfo;
final int awaitingIn; final BigInt awaitingIn;
final int awaitingOut; final BigInt awaitingOut;
final int total; final BigInt total;
final int unlocked; final BigInt unlocked;
Balance( Balance(
{required this.assetInfo, {required this.assetInfo,
@ -22,9 +23,9 @@ class Balance {
factory Balance.fromJson(Map<String, dynamic> json) => Balance( factory Balance.fromJson(Map<String, dynamic> json) => Balance(
assetInfo: assetInfo:
ZanoAsset.fromJson(json['asset_info'] as Map<String, dynamic>? ?? {}), ZanoAsset.fromJson(json['asset_info'] as Map<String, dynamic>? ?? {}),
awaitingIn: json['awaiting_in'] as int? ?? 0, awaitingIn: ZanoFormatter.bigIntFromDynamic(json['awaiting_in']),
awaitingOut: json['awaiting_out'] as int? ?? 0, awaitingOut: ZanoFormatter.bigIntFromDynamic(json['awaiting_out']),
total: json['total'] as int? ?? 0, total: ZanoFormatter.bigIntFromDynamic(json['total']),
unlocked: json['unlocked'] as int? ?? 0, unlocked: ZanoFormatter.bigIntFromDynamic(json['unlocked']),
); );
} }

View file

@ -1,12 +1,14 @@
import 'package:cw_zano/zano_formatter.dart';
class Receive { class Receive {
final int amount; final BigInt amount;
final String assetId; final String assetId;
final int index; final int index;
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? ?? 0, amount: ZanoFormatter.bigIntFromDynamic(json['amount']),
assetId: json['asset_id'] as String? ?? '', assetId: json['asset_id'] as String? ?? '',
index: json['index'] as int? ?? 0, index: json['index'] as int? ?? 0,
); );

View file

@ -1,5 +1,7 @@
import 'package:cw_zano/zano_formatter.dart';
class Subtransfer { class Subtransfer {
final int amount; final BigInt amount;
final String assetId; final String assetId;
final bool isIncome; final bool isIncome;
@ -7,7 +9,7 @@ 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? ?? 0, amount: ZanoFormatter.bigIntFromDynamic(json['amount']),
assetId: json['asset_id'] as String? ?? '', assetId: json['asset_id'] as String? ?? '',
isIncome: json['is_income'] as bool? ?? false, isIncome: json['is_income'] as bool? ?? false,
); );

View file

@ -3,7 +3,9 @@ import 'package:cw_zano/api/model/subtransfer.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:cw_zano/model/zano_asset.dart'; import 'package:cw_zano/model/zano_asset.dart';
import 'package:cw_zano/model/zano_transaction_info.dart'; import 'package:cw_zano/model/zano_transaction_info.dart';
import 'package:cw_zano/zano_formatter.dart';
import 'package:cw_zano/zano_wallet.dart'; import 'package:cw_zano/zano_wallet.dart';
import 'package:cw_zano/zano_wallet_api.dart';
class Transfer { class Transfer {
final String comment; final String comment;
@ -71,8 +73,8 @@ class Transfer {
unlockTime: json['unlock_time'] as int? ?? 0, unlockTime: json['unlock_time'] as int? ?? 0,
); );
static Map<String, ZanoTransactionInfo> makeMap(List<Transfer> transfers, Map<String, ZanoAsset> zanoAssets, int currentDaemonHeight) => static Map<String, ZanoTransactionInfo> makeMap(List<Transfer> transfers, Map<String, ZanoAsset> zanoAssets, int currentDaemonHeight) {
Map.fromIterable( return Map.fromIterable(
transfers, transfers,
key: (item) => (item as Transfer).txHash, key: (item) => (item as Transfer).txHash,
value: (transfer) { value: (transfer) {
@ -81,7 +83,7 @@ class Transfer {
Subtransfer? single = transfer.subtransfers.singleOrNull; Subtransfer? single = transfer.subtransfers.singleOrNull;
if (transfer.subtransfers.length == 2) { if (transfer.subtransfers.length == 2) {
final zano = transfer.subtransfers.firstWhereOrNull((element) => element.assetId == ZanoWalletBase.zanoAssetId); final zano = transfer.subtransfers.firstWhereOrNull((element) => element.assetId == ZanoWalletBase.zanoAssetId);
if (zano != null && !zano.isIncome && zano.amount == transfer.fee) { if (zano != null && !zano.isIncome && zano.amount == BigInt.from(transfer.fee)) {
single = transfer.subtransfers.firstWhere((element) => element.assetId != ZanoWalletBase.zanoAssetId); single = transfer.subtransfers.firstWhere((element) => element.assetId != ZanoWalletBase.zanoAssetId);
} }
} }
@ -93,18 +95,22 @@ class Transfer {
} }
if (single.assetId != ZanoWalletBase.zanoAssetId) { if (single.assetId != ZanoWalletBase.zanoAssetId) {
final asset = zanoAssets[single.assetId]; final asset = zanoAssets[single.assetId];
if (asset != null) if (asset == null) {
return ZanoTransactionInfo.fromTransfer( ZanoWalletApi.error('unknown asset ${single.assetId}');
transfer, }
confirmations: currentDaemonHeight - transfer.height, final ticker = asset == null ? '***' : asset.ticker;
isIncome: single.isIncome, final decimalPoint = asset == null ? ZanoFormatter.defaultDecimalPoint : asset.decimalPoint;
assetId: single.assetId, return ZanoTransactionInfo.fromTransfer(
amount: single.amount, transfer,
tokenSymbol: isSimple ? asset.ticker : '*${asset.ticker}', confirmations: currentDaemonHeight - transfer.height,
decimalPoint: asset.decimalPoint, isIncome: single.isIncome,
); assetId: single.assetId,
amount: single.amount,
tokenSymbol: isSimple ? ticker : '*${ticker}',
decimalPoint: decimalPoint,
);
} }
final amount = single.isIncome ? single.amount : single.amount - transfer.fee; final amount = single.isIncome ? single.amount : single.amount - BigInt.from(transfer.fee);
return ZanoTransactionInfo.fromTransfer( return ZanoTransactionInfo.fromTransfer(
transfer, transfer,
confirmations: currentDaemonHeight - transfer.height, confirmations: currentDaemonHeight - transfer.height,
@ -115,4 +121,5 @@ class Transfer {
); );
}, },
); );
}
} }

View file

@ -26,11 +26,11 @@ class ZanoAsset extends CryptoCurrency with HiveObjectMixin {
@HiveField(7) @HiveField(7)
final String metaInfo; final String metaInfo;
@HiveField(8) @HiveField(8)
final int currentSupply; final BigInt currentSupply;
@HiveField(9) @HiveField(9)
final bool hiddenSupply; final bool hiddenSupply;
@HiveField(10) @HiveField(10)
final int totalMaxSupply; final BigInt totalMaxSupply;
@HiveField(11) @HiveField(11)
final bool isInGlobalWhitelist; final bool isInGlobalWhitelist;
@ -47,11 +47,11 @@ class ZanoAsset extends CryptoCurrency with HiveObjectMixin {
this.iconPath, this.iconPath,
this.owner = defaultOwner, this.owner = defaultOwner,
this.metaInfo = '', this.metaInfo = '',
this.currentSupply = 0, required this.currentSupply,
this.hiddenSupply = false, this.hiddenSupply = false,
this.totalMaxSupply = 0, required this.totalMaxSupply,
this.isInGlobalWhitelist = false, this.isInGlobalWhitelist = false,
}) : _enabled = enabled, }) : _enabled = enabled,
super( super(
name: fullName, name: fullName,
title: ticker.toUpperCase(), title: ticker.toUpperCase(),
@ -86,17 +86,19 @@ class ZanoAsset extends CryptoCurrency with HiveObjectMixin {
factory ZanoAsset.fromJson(Map<String, dynamic> json, {bool isInGlobalWhitelist = false}) => ZanoAsset( factory ZanoAsset.fromJson(Map<String, dynamic> json, {bool isInGlobalWhitelist = false}) => ZanoAsset(
assetId: json['asset_id'] as String? ?? '', assetId: json['asset_id'] as String? ?? '',
currentSupply: json['current_supply'] as int? ?? 0, currentSupply: ZanoFormatter.bigIntFromDynamic(json['current_supply']),
decimalPoint: json['decimal_point'] as int? ?? ZanoFormatter.defaultDecimalPoint, decimalPoint: json['decimal_point'] as int? ?? ZanoFormatter.defaultDecimalPoint,
fullName: json['full_name'] as String? ?? '', fullName: json['full_name'] as String? ?? '',
hiddenSupply: json['hidden_supply'] as bool? ?? false, hiddenSupply: json['hidden_supply'] as bool? ?? false,
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? ?? 0, totalMaxSupply: ZanoFormatter.bigIntFromDynamic(json['total_max_supply']),
isInGlobalWhitelist: isInGlobalWhitelist, isInGlobalWhitelist: isInGlobalWhitelist,
); );
static const typeId = ZANO_ASSET_TYPE_ID; static const typeId = ZANO_ASSET_TYPE_ID;
static const zanoAssetsBoxName = 'zanoAssetsBox'; static const zanoAssetsBoxName = 'zanoAssetsBox';
static const defaultOwner = '0000000000000000000000000000000000000000000000000000000000000000'; static const defaultOwner = '0000000000000000000000000000000000000000000000000000000000000000';

View file

@ -2,16 +2,16 @@ import 'package:cw_core/balance.dart';
import 'package:cw_zano/zano_formatter.dart'; import 'package:cw_zano/zano_formatter.dart';
class ZanoBalance extends Balance { class ZanoBalance extends Balance {
final int total; final BigInt total;
final int unlocked; final BigInt unlocked;
final int decimalPoint; final int decimalPoint;
ZanoBalance({required this.total, required this.unlocked, this.decimalPoint = ZanoFormatter.defaultDecimalPoint}) : super(unlocked, total - unlocked); ZanoBalance({required this.total, required this.unlocked, this.decimalPoint = ZanoFormatter.defaultDecimalPoint}) : super(unlocked.isValidInt ? unlocked.toInt() : 0, (total - unlocked).isValidInt ? (total - unlocked).toInt() : 0);
ZanoBalance.empty({this.decimalPoint = ZanoFormatter.defaultDecimalPoint}): total = 0, unlocked = 0, super(0, 0); ZanoBalance.empty({this.decimalPoint = ZanoFormatter.defaultDecimalPoint}): total = BigInt.zero, unlocked = BigInt.zero, super(0, 0);
@override @override
String get formattedAdditionalBalance => ZanoFormatter.intAmountToString(total - unlocked, decimalPoint); String get formattedAdditionalBalance => ZanoFormatter.bigIntAmountToString(total - unlocked, decimalPoint);
@override @override
String get formattedAvailableBalance => ZanoFormatter.intAmountToString(unlocked, decimalPoint); String get formattedAvailableBalance => ZanoFormatter.bigIntAmountToString(unlocked, decimalPoint);
} }

View file

@ -11,26 +11,27 @@ class ZanoTransactionInfo extends TransactionInfo {
required this.direction, required this.direction,
required this.date, required this.date,
required this.isPending, required this.isPending,
required this.amount, required this.zanoAmount,
required this.fee, required this.fee,
required this.assetId, required this.assetId,
required this.confirmations, required this.confirmations,
required this.tokenSymbol, required this.tokenSymbol,
required this.decimalPoint, required this.decimalPoint,
}); }) : amount = zanoAmount.isValidInt ? zanoAmount.toInt() : 0;
ZanoTransactionInfo.fromTransfer(Transfer transfer, ZanoTransactionInfo.fromTransfer(Transfer transfer,
{required int confirmations, {required int confirmations,
required bool isIncome, required bool isIncome,
required String assetId, required String assetId,
required int amount, required BigInt amount,
this.tokenSymbol = 'ZANO', this.tokenSymbol = 'ZANO',
this.decimalPoint = ZanoFormatter.defaultDecimalPoint}) this.decimalPoint = ZanoFormatter.defaultDecimalPoint})
: id = transfer.txHash, : id = transfer.txHash,
height = transfer.height, height = transfer.height,
direction = isIncome ? TransactionDirection.incoming : TransactionDirection.outgoing, direction = isIncome ? TransactionDirection.incoming : TransactionDirection.outgoing,
date = DateTime.fromMillisecondsSinceEpoch(transfer.timestamp * 1000), date = DateTime.fromMillisecondsSinceEpoch(transfer.timestamp * 1000),
amount = amount, zanoAmount = amount,
amount = amount.isValidInt ? amount.toInt() : 0,
fee = transfer.fee, fee = transfer.fee,
assetId = assetId, assetId = assetId,
confirmations = confirmations, confirmations = confirmations,
@ -46,6 +47,7 @@ class ZanoTransactionInfo extends TransactionInfo {
final TransactionDirection direction; final TransactionDirection direction;
final DateTime date; final DateTime date;
final bool isPending; final bool isPending;
final BigInt zanoAmount;
final int amount; final int amount;
final int fee; final int fee;
final int confirmations; final int confirmations;
@ -57,7 +59,7 @@ class ZanoTransactionInfo extends TransactionInfo {
String? key; String? key;
@override @override
String amountFormatted() => '${formatAmount(ZanoFormatter.intAmountToString(amount, decimalPoint))} $tokenSymbol'; String amountFormatted() => '${formatAmount(ZanoFormatter.bigIntAmountToString(zanoAmount, decimalPoint))} $tokenSymbol';
@override @override
String fiatAmount() => _fiatAmount ?? ''; String fiatAmount() => _fiatAmount ?? '';

View file

@ -1,21 +1,26 @@
import 'dart:math'; import 'dart:math';
import 'package:cw_zano/zano_wallet_api.dart';
import 'package:decimal/decimal.dart'; import 'package:decimal/decimal.dart';
import 'package:decimal/intl.dart'; import 'package:decimal/intl.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
class ZanoFormatter { class ZanoFormatter {
static const defaultDecimalPoint = 12; static const defaultDecimalPoint = 12;
static final numberFormat = NumberFormat() //static final numberFormat = NumberFormat()
..maximumFractionDigits = defaultDecimalPoint // ..maximumFractionDigits = defaultDecimalPoint
..minimumFractionDigits = 1; // ..minimumFractionDigits = 1;
static Decimal _bigIntDivision({required BigInt amount, required BigInt divider}) => static Decimal _bigIntDivision({required BigInt amount, required BigInt divider}) {
(Decimal.fromBigInt(amount) / Decimal.fromBigInt(divider)).toDecimal(); return (Decimal.fromBigInt(amount) / Decimal.fromBigInt(divider)).toDecimal();
}
static String intAmountToString(int amount, [int decimalPoint = defaultDecimalPoint]) => numberFormat static String intAmountToString(int amount, [int decimalPoint = defaultDecimalPoint]) {
.format( final numberFormat = NumberFormat()..maximumFractionDigits = decimalPoint
..minimumFractionDigits = 1;
return numberFormat.format(
DecimalIntl( DecimalIntl(
_bigIntDivision( _bigIntDivision(
amount: BigInt.from(amount), amount: BigInt.from(amount),
@ -24,8 +29,12 @@ class ZanoFormatter {
), ),
) )
.replaceAll(',', ''); .replaceAll(',', '');
static String bigIntAmountToString(BigInt amount, [int decimalPoint = defaultDecimalPoint]) => numberFormat }
.format(
static String bigIntAmountToString(BigInt amount, [int decimalPoint = defaultDecimalPoint]) {
final numberFormat = NumberFormat()..maximumFractionDigits = decimalPoint
..minimumFractionDigits = 1;
return numberFormat.format(
DecimalIntl( DecimalIntl(
_bigIntDivision( _bigIntDivision(
amount: amount, amount: amount,
@ -34,12 +43,32 @@ class ZanoFormatter {
), ),
) )
.replaceAll(',', ''); .replaceAll(',', '');
}
static double intAmountToDouble(int amount, [int decimalPoint = defaultDecimalPoint]) => _bigIntDivision( static double intAmountToDouble(int amount, [int decimalPoint = defaultDecimalPoint]) => _bigIntDivision(
amount: BigInt.from(amount), amount: BigInt.from(amount),
divider: BigInt.from(pow(10, decimalPoint)), divider: BigInt.from(pow(10, decimalPoint)),
).toDouble(); ).toDouble();
static int parseAmount(String amount, [int decimalPoint = defaultDecimalPoint]) => static int parseAmount(String amount, [int decimalPoint = defaultDecimalPoint]) {
(Decimal.parse(amount) * Decimal.fromBigInt(BigInt.from(10).pow(decimalPoint))).toBigInt().toInt(); final resultBigInt = (Decimal.parse(amount) * Decimal.fromBigInt(BigInt.from(10).pow(decimalPoint))).toBigInt();
if (!resultBigInt.isValidInt) {
Fluttertoast.showToast(msg: 'Cannot transfer $amount. Maximum is ${intAmountToString(resultBigInt.toInt(), decimalPoint)}.');
}
return resultBigInt.toInt();
}
static BigInt bigIntFromDynamic(dynamic d) {
if (d is int) {
return BigInt.from(d);
} else if (d is BigInt) {
return d;
} else if (d == null) {
return BigInt.zero;
} else {
ZanoWalletApi.error('cannot cast value of type ${d.runtimeType} to BigInt');
throw 'cannot cast value of type ${d.runtimeType} to BigInt';
//return BigInt.zero;
}
}
} }

View file

@ -14,6 +14,7 @@ import 'package:cw_core/wallet_info.dart';
import 'package:cw_zano/api/model/create_wallet_result.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/destination.dart';
import 'package:cw_zano/api/model/get_recent_txs_and_info_result.dart'; import 'package:cw_zano/api/model/get_recent_txs_and_info_result.dart';
import 'package:cw_zano/api/model/get_wallet_info_result.dart';
import 'package:cw_zano/api/model/get_wallet_status_result.dart'; import 'package:cw_zano/api/model/get_wallet_status_result.dart';
import 'package:cw_zano/api/model/transfer.dart'; import 'package:cw_zano/api/model/transfer.dart';
import 'package:cw_zano/model/pending_zano_transaction.dart'; import 'package:cw_zano/model/pending_zano_transaction.dart';
@ -29,6 +30,8 @@ import 'package:cw_zano/zano_wallet_addresses.dart';
import 'package:cw_zano/zano_wallet_api.dart'; import 'package:cw_zano/zano_wallet_api.dart';
import 'package:cw_zano/zano_wallet_exceptions.dart'; import 'package:cw_zano/zano_wallet_exceptions.dart';
import 'package:cw_zano/zano_wallet_service.dart'; import 'package:cw_zano/zano_wallet_service.dart';
import 'package:cw_zano/api/model/balance.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
@ -39,6 +42,7 @@ class ZanoWallet = ZanoWalletBase with _$ZanoWallet;
abstract class ZanoWalletBase extends WalletBase<ZanoBalance, ZanoTransactionHistory, ZanoTransactionInfo> with Store, ZanoWalletApi { abstract class ZanoWalletBase extends WalletBase<ZanoBalance, ZanoTransactionHistory, ZanoTransactionInfo> with Store, ZanoWalletApi {
static const int _autoSaveIntervalSeconds = 30; static const int _autoSaveIntervalSeconds = 30;
static const int _pollIntervalMilliseconds = 2000; static const int _pollIntervalMilliseconds = 2000;
static const int _maxLoadAssetsRetries = 5;
@override @override
ZanoWalletAddresses walletAddresses; ZanoWalletAddresses walletAddresses;
@ -101,7 +105,7 @@ abstract class ZanoWalletBase extends WalletBase<ZanoBalance, ZanoTransactionHis
await wallet.connectToNode(node: Node()); await wallet.connectToNode(node: Node());
final path = await pathForWallet(name: credentials.name, type: credentials.walletInfo!.type); final path = await pathForWallet(name: credentials.name, type: credentials.walletInfo!.type);
final createWalletResult = await wallet.createWallet(path, credentials.password!); final createWalletResult = await wallet.createWallet(path, credentials.password!);
await _parseCreateWalletResult(createWalletResult, wallet); await wallet.parseCreateWalletResult(createWalletResult);
await wallet.init(createWalletResult.wi.address); await wallet.init(createWalletResult.wi.address);
return wallet; return wallet;
} }
@ -111,7 +115,7 @@ abstract class ZanoWalletBase extends WalletBase<ZanoBalance, ZanoTransactionHis
await wallet.connectToNode(node: Node()); await wallet.connectToNode(node: Node());
final path = await pathForWallet(name: credentials.name, type: credentials.walletInfo!.type); final path = await pathForWallet(name: credentials.name, type: credentials.walletInfo!.type);
final createWalletResult = await wallet.restoreWalletFromSeed(path, credentials.password!, credentials.mnemonic); final createWalletResult = await wallet.restoreWalletFromSeed(path, credentials.password!, credentials.mnemonic);
await _parseCreateWalletResult(createWalletResult, wallet); await wallet.parseCreateWalletResult(createWalletResult);
await wallet.init(createWalletResult.wi.address); await wallet.init(createWalletResult.wi.address);
return wallet; return wallet;
} }
@ -121,19 +125,20 @@ abstract class ZanoWalletBase extends WalletBase<ZanoBalance, ZanoTransactionHis
final wallet = ZanoWallet(walletInfo); final wallet = ZanoWallet(walletInfo);
await wallet.connectToNode(node: Node()); await wallet.connectToNode(node: Node());
final createWalletResult = await wallet.loadWallet(path, password); final createWalletResult = await wallet.loadWallet(path, password);
await _parseCreateWalletResult(createWalletResult, wallet); await wallet.parseCreateWalletResult(createWalletResult);
await wallet.init(createWalletResult.wi.address); await wallet.init(createWalletResult.wi.address);
return wallet; return wallet;
} }
static Future<void> _parseCreateWalletResult(CreateWalletResult result, ZanoWallet wallet) async { Future<void> parseCreateWalletResult(CreateWalletResult result) async {
wallet.hWallet = result.walletId; hWallet = result.walletId;
wallet.seed = result.seed; seed = result.seed;
ZanoWalletApi.info('setting hWallet = ${result.walletId}'); ZanoWalletApi.info('setting hWallet = ${result.walletId}');
wallet.walletAddresses.address = result.wi.address; walletAddresses.address = result.wi.address;
await loadAssets(result.wi.balances, maxRetries: _maxLoadAssetsRetries);
for (final item in result.wi.balances) { for (final item in result.wi.balances) {
if (item.assetInfo.assetId == zanoAssetId) { if (item.assetInfo.assetId == zanoAssetId) {
wallet.balance[CryptoCurrency.zano] = ZanoBalance( balance[CryptoCurrency.zano] = ZanoBalance(
total: item.total, total: item.total,
unlocked: item.unlocked, unlocked: item.unlocked,
); );
@ -141,9 +146,9 @@ abstract class ZanoWalletBase extends WalletBase<ZanoBalance, ZanoTransactionHis
} }
if (result.recentHistory.history != null) { if (result.recentHistory.history != null) {
final transfers = result.recentHistory.history!; final transfers = result.recentHistory.history!;
final transactions = Transfer.makeMap(transfers, wallet.zanoAssets, wallet.currentDaemonHeight); final transactions = Transfer.makeMap(transfers, zanoAssets, currentDaemonHeight);
wallet.transactionHistory.addMany(transactions); transactionHistory.addMany(transactions);
await wallet.transactionHistory.save(); await transactionHistory.save();
} }
} }
@ -167,8 +172,8 @@ abstract class ZanoWalletBase extends WalletBase<ZanoBalance, ZanoTransactionHis
final isZano = credentials.currency == CryptoCurrency.zano; final isZano = credentials.currency == CryptoCurrency.zano;
final outputs = credentials.outputs; final outputs = credentials.outputs;
final hasMultiDestination = outputs.length > 1; final hasMultiDestination = outputs.length > 1;
final unlockedBalanceZano = BigInt.from(balance[CryptoCurrency.zano]?.unlocked ?? 0); final unlockedBalanceZano = balance[CryptoCurrency.zano]?.unlocked ?? BigInt.zero;
final unlockedBalanceCurrency = BigInt.from(balance[credentials.currency]?.unlocked ?? 0); final unlockedBalanceCurrency = balance[credentials.currency]?.unlocked ?? BigInt.zero;
final fee = BigInt.from(calculateEstimatedFee(credentials.priority)); final fee = BigInt.from(calculateEstimatedFee(credentials.priority));
late BigInt totalAmount; late BigInt totalAmount;
void checkForEnoughBalances() { void checkForEnoughBalances() {
@ -184,7 +189,7 @@ abstract class ZanoWalletBase extends WalletBase<ZanoBalance, ZanoTransactionHis
} }
if (totalAmount > unlockedBalanceCurrency) { if (totalAmount > unlockedBalanceCurrency) {
throw ZanoTransactionCreationException( throw ZanoTransactionCreationException(
"You don't have enough coins (required: ${ZanoFormatter.bigIntAmountToString(totalAmount)} ${credentials.currency.title}, unlocked ${ZanoFormatter.bigIntAmountToString(unlockedBalanceZano)} ${credentials.currency.title})."); "You don't have enough coins (required: ${ZanoFormatter.bigIntAmountToString(totalAmount, credentials.currency.decimals)} ${credentials.currency.title}, unlocked ${ZanoFormatter.bigIntAmountToString(unlockedBalanceCurrency, credentials.currency.decimals)} ${credentials.currency.title}).");
} }
} }
} }
@ -241,18 +246,14 @@ abstract class ZanoWalletBase extends WalletBase<ZanoBalance, ZanoTransactionHis
try { try {
final transfers = <Transfer>[]; final transfers = <Transfer>[];
late GetRecentTxsAndInfoResult result; late GetRecentTxsAndInfoResult result;
bool first = true;
do { do {
result = await getRecentTxsAndInfo(offset: _lastTxIndex, count: _txChunkSize); result = await getRecentTxsAndInfo(offset: _lastTxIndex, count: _txChunkSize);
// TODO: that's for debug purposes
if (first && result.transfers.isEmpty) break;
first = false;
_lastTxIndex += result.transfers.length; _lastTxIndex += result.transfers.length;
transfers.addAll(result.transfers); transfers.addAll(result.transfers);
} while (result.lastItemIndex + 1 < result.totalTransfers); } while (result.lastItemIndex + 1 < result.totalTransfers);
return Transfer.makeMap(transfers, zanoAssets, currentDaemonHeight); return Transfer.makeMap(transfers, zanoAssets, currentDaemonHeight);
} catch (e) { } catch (e) {
print(e); ZanoWalletApi.error(e.toString());
return {}; return {};
} }
} }
@ -297,7 +298,33 @@ abstract class ZanoWalletBase extends WalletBase<ZanoBalance, ZanoTransactionHis
await store(); await store();
await walletAddresses.updateAddressesInBox(); await walletAddresses.updateAddressesInBox();
} catch (e) { } catch (e) {
print('Error while saving Zano wallet file ${e.toString()}'); ZanoWalletApi.error('Error while saving Zano wallet file ${e.toString()}');
}
}
Future<void> loadAssets(List<Balance> balances, {int maxRetries = 1}) async {
List<ZanoAsset> assets = [];
int retryCount = 0;
while (retryCount < maxRetries) {
try {
assets = await getAssetsWhitelist();
break;
} on ZanoWalletBusyException {
if (retryCount < maxRetries - 1) {
retryCount++;
await Future.delayed(Duration(seconds: 1));
} else {
ZanoWalletApi.error('failed to load assets after $retryCount retries');
break;
}
}
}
zanoAssets = {};
for (final asset in assets) {
final newAsset = ZanoAsset.copyWith(asset,
icon: _getIconPath(asset.title), enabled: balances.any((element) => element.assetId == asset.assetId));
zanoAssets.putIfAbsent(asset.assetId, () => newAsset);
} }
} }
@ -308,7 +335,13 @@ abstract class ZanoWalletBase extends WalletBase<ZanoBalance, ZanoTransactionHis
_lastKnownBlockHeight = 0; _lastKnownBlockHeight = 0;
_initialSyncHeight = 0; _initialSyncHeight = 0;
_updateSyncInfoTimer ??= Timer.periodic(Duration(milliseconds: _pollIntervalMilliseconds), (_) async { _updateSyncInfoTimer ??= Timer.periodic(Duration(milliseconds: _pollIntervalMilliseconds), (_) async {
final walletStatus = await getWalletStatus(); GetWalletStatusResult walletStatus;
// ignoring get wallet status exception (in case of wrong wallet id)
try {
walletStatus = await getWalletStatus();
} on ZanoWalletException {
return;
}
currentDaemonHeight = walletStatus.currentDaemonHeight; currentDaemonHeight = walletStatus.currentDaemonHeight;
_updateSyncProgress(walletStatus); _updateSyncProgress(walletStatus);
@ -322,14 +355,7 @@ abstract class ZanoWalletBase extends WalletBase<ZanoBalance, ZanoTransactionHis
publicSpendKey: walletInfo.wiExtended.spendPublicKey, publicSpendKey: walletInfo.wiExtended.spendPublicKey,
publicViewKey: walletInfo.wiExtended.viewPublicKey, publicViewKey: walletInfo.wiExtended.viewPublicKey,
); );
loadAssets(walletInfo.wi.balances);
final assets = await getAssetsWhitelist();
zanoAssets = {};
for (final asset in assets) {
final newAsset = ZanoAsset.copyWith(asset,
icon: _getIconPath(asset.title), enabled: walletInfo.wi.balances.any((element) => element.assetId == asset.assetId));
zanoAssets.putIfAbsent(asset.assetId, () => newAsset);
}
// matching balances and whitelists // matching balances and whitelists
// 1. show only balances available in whitelists // 1. show only balances available in whitelists
// 2. set whitelists available in balances as 'enabled' ('disabled' by default) // 2. set whitelists available in balances as 'enabled' ('disabled' by default)
@ -358,8 +384,8 @@ abstract class ZanoWalletBase extends WalletBase<ZanoBalance, ZanoTransactionHis
}); });
} catch (e) { } catch (e) {
syncStatus = FailedSyncStatus(); syncStatus = FailedSyncStatus();
print(e); ZanoWalletApi.error(e.toString());
rethrow; //rethrow; // TODO: we don't need to propagate exception here
} }
} }
@ -377,7 +403,7 @@ abstract class ZanoWalletBase extends WalletBase<ZanoBalance, ZanoTransactionHis
await transactionHistory.save(); await transactionHistory.save();
_isTransactionUpdating = false; _isTransactionUpdating = false;
} catch (e) { } catch (e) {
print(e); ZanoWalletApi.error(e.toString());
_isTransactionUpdating = false; _isTransactionUpdating = false;
} }
} }
@ -407,12 +433,12 @@ abstract class ZanoWalletBase extends WalletBase<ZanoBalance, ZanoTransactionHis
if (asset.enabled) { if (asset.enabled) {
final assetDescriptor = await addAssetsWhitelist(asset.assetId); final assetDescriptor = await addAssetsWhitelist(asset.assetId);
if (assetDescriptor == null) { if (assetDescriptor == null) {
print('Error adding zano asset'); ZanoWalletApi.error('Error adding zano asset');
} }
} else { } else {
final result = await removeAssetsWhitelist(asset.assetId); final result = await removeAssetsWhitelist(asset.assetId);
if (result == false) { if (result == false) {
print('Error removing zano asset'); ZanoWalletApi.error('Error removing zano asset');
} }
} }
} }
@ -441,7 +467,7 @@ abstract class ZanoWalletBase extends WalletBase<ZanoBalance, ZanoTransactionHis
syncStatus = SyncingSyncStatus(blocksLeft, ptc); syncStatus = SyncingSyncStatus(blocksLeft, ptc);
} }
} catch (e) { } catch (e) {
print(e.toString()); ZanoWalletApi.error(e.toString());
} }
} }

View file

@ -1,5 +1,6 @@
import 'package:cw_core/wallet_addresses.dart'; import 'package:cw_core/wallet_addresses.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
import 'package:cw_zano/zano_wallet_api.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
part 'zano_wallet_addresses.g.dart'; part 'zano_wallet_addresses.g.dart';
@ -33,7 +34,7 @@ abstract class ZanoWalletAddressesBase extends WalletAddresses with Store {
addressesMap[address] = ''; addressesMap[address] = '';
await saveAddressesInBox(); await saveAddressesInBox();
} catch (e) { } catch (e) {
print(e.toString()); ZanoWalletApi.error(e.toString());
} }
} }
} }

View file

@ -1,4 +1,4 @@
import 'dart:convert'; import 'dart:convert' as convert;
import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/transaction_priority.dart';
import 'package:cw_zano/api/api_calls.dart'; import 'package:cw_zano/api/api_calls.dart';
@ -20,14 +20,16 @@ import 'package:cw_zano/api/model/transfer_result.dart';
import 'package:cw_zano/model/zano_asset.dart'; import 'package:cw_zano/model/zano_asset.dart';
import 'package:cw_zano/zano_wallet_exceptions.dart'; import 'package:cw_zano/zano_wallet_exceptions.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:json_bigint/json_bigint.dart';
mixin ZanoWalletApi { mixin ZanoWalletApi {
static const _defaultNodeUri = '195.201.107.230:33336'; static const _defaultNodeUri = '195.201.107.230:33336';
static const _statusDelivered = 'delivered'; static const _statusDelivered = 'delivered';
static const _maxInvokeAttempts = 10; static const _maxInvokeAttempts = 10;
static const _maxReopenAttempts = 5;
static const _logInfo = true; static const _logInfo = true;
static const _logError = true; static const _logError = true;
static const _logJson = true; static const _logJson = false;
static const int _zanoMixinValue = 10; static const int _zanoMixinValue = 10;
int _hWallet = 0; int _hWallet = 0;
@ -40,6 +42,9 @@ mixin ZanoWalletApi {
int getCurrentTxFee(TransactionPriority priority) => ApiCalls.getCurrentTxFee(priority: priority.raw); int getCurrentTxFee(TransactionPriority priority) => ApiCalls.getCurrentTxFee(priority: priority.raw);
String getOpenedWallets() => ApiCalls.getOpenedWallets();
String getConnectivityStatus() => ApiCalls.getConnectivityStatus();
void setPassword(String password) => ApiCalls.setPassword(hWallet: hWallet, password: password); void setPassword(String password) => ApiCalls.setPassword(hWallet: hWallet, password: password);
void closeWallet([int? walletToClose]) { void closeWallet([int? walletToClose]) {
@ -65,7 +70,6 @@ mixin ZanoWalletApi {
final json = ApiCalls.getWalletInfo(hWallet); final json = ApiCalls.getWalletInfo(hWallet);
final result = GetWalletInfoResult.fromJson(jsonDecode(json) as Map<String, dynamic>); final result = GetWalletInfoResult.fromJson(jsonDecode(json) as Map<String, dynamic>);
_json('get_wallet_info', json); _json('get_wallet_info', json);
//await _writeLog('get_wallet_info', 'get_wallet_info result $json');
info('get_wallet_info got ${result.wi.balances.length} balances: ${result.wi.balances} seed: ${_shorten(result.wiExtended.seed)}'); info('get_wallet_info got ${result.wi.balances.length} balances: ${result.wi.balances} seed: ${_shorten(result.wiExtended.seed)}');
return result; return result;
} }
@ -78,7 +82,6 @@ mixin ZanoWalletApi {
} }
final status = GetWalletStatusResult.fromJson(jsonDecode(json) as Map<String, dynamic>); final status = GetWalletStatusResult.fromJson(jsonDecode(json) as Map<String, dynamic>);
_json('get_wallet_status', json); _json('get_wallet_status', json);
//await _writeLog('get_wallet_status', 'get_wallet_status result $json');
if (_logInfo) if (_logInfo)
info( info(
'get_wallet_status connected: ${status.isDaemonConnected} in refresh: ${status.isInLongRefresh} progress: ${status.progress} wallet state: ${status.walletState}'); 'get_wallet_status connected: ${status.isDaemonConnected} in refresh: ${status.isInLongRefresh} progress: ${status.progress} wallet state: ${status.walletState}');
@ -86,13 +89,13 @@ mixin ZanoWalletApi {
} }
Future<String> invokeMethod(String methodName, Object params) async { Future<String> invokeMethod(String methodName, Object params) async {
//await _writeLog(methodName, 'invoke method $methodName params: ${jsonEncode(params)} hWallet: $hWallet');
var invokeResult = var invokeResult =
ApiCalls.asyncCall(methodName: 'invoke', hWallet: hWallet, params: '{"method": "$methodName","params": ${jsonEncode(params)}}'); ApiCalls.asyncCall(methodName: 'invoke', hWallet: hWallet, params: '{"method": "$methodName","params": ${jsonEncode(params)}}');
Map<String, dynamic> map; Map<String, dynamic> map;
try { try {
map = jsonDecode(invokeResult) as Map<String, dynamic>; map = jsonDecode(invokeResult) as Map<String, dynamic>;
} catch (e) { } catch (e) {
if (invokeResult.contains(Consts.errorWalletWrongId)) throw ZanoWalletException('Wrong wallet id');
error('exception in parsing json in invokeMethod: $invokeResult'); error('exception in parsing json in invokeMethod: $invokeResult');
rethrow; rethrow;
} }
@ -105,16 +108,15 @@ mixin ZanoWalletApi {
try { try {
map = jsonDecode(result) as Map<String, dynamic>; map = jsonDecode(result) as Map<String, dynamic>;
} catch (e) { } catch (e) {
if (result.contains(Consts.errorWalletWrongId)) throw ZanoWalletException('Wrong wallet id');
error('exception in parsing json in invokeMethod: $result'); error('exception in parsing json in invokeMethod: $result');
rethrow; rethrow;
} }
if (map['status'] != null && map['status'] == _statusDelivered && map['result'] != null) { if (map['status'] != null && map['status'] == _statusDelivered && map['result'] != null) {
//await _writeLog(methodName, 'invoke method $methodName result $result');
return result; return result;
} }
} while (++attempts < _maxInvokeAttempts); } while (++attempts < _maxInvokeAttempts);
} }
//await _writeLog(methodName, 'invoke method $methodName result: $invokeResult');
return invokeResult; return invokeResult;
} }
@ -139,7 +141,8 @@ mixin ZanoWalletApi {
return [...globalWhitelist, ...localWhitelist, ...ownAssets]; return [...globalWhitelist, ...localWhitelist, ...ownAssets];
} catch (e) { } catch (e) {
error('assets_whitelist_get $e'); error('assets_whitelist_get $e');
return []; //return [];
rethrow;
} }
} }
@ -190,7 +193,7 @@ mixin ZanoWalletApi {
final result = await _proxyToDaemon('/json_rpc', '{"method": "$methodName","params": ${jsonEncode(params)}}'); final result = await _proxyToDaemon('/json_rpc', '{"method": "$methodName","params": ${jsonEncode(params)}}');
_json('$methodName $assetId', result?.body ?? ''); _json('$methodName $assetId', result?.body ?? '');
if (result == null) { if (result == null) {
debugPrint('get_asset_info empty result'); error('get_asset_info empty result');
return null; return null;
} }
final map = jsonDecode(result.body) as Map<String, dynamic>?; final map = jsonDecode(result.body) as Map<String, dynamic>?;
@ -253,10 +256,8 @@ mixin ZanoWalletApi {
Future<CreateWalletResult> createWallet(String path, String password) async { Future<CreateWalletResult> createWallet(String path, String password) async {
info('create_wallet path $path password ${_shorten(password)}'); 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); final json = ApiCalls.createWallet(path: path, password: password);
_json('create_wallet', json); _json('create_wallet', json);
//await _writeLog('create_wallet', 'create_wallet result $json');
final map = jsonDecode(json) as Map<String, dynamic>?; final map = jsonDecode(json) as Map<String, dynamic>?;
if (map?['error'] != null) { if (map?['error'] != null) {
final code = map!['error']!['code'] ?? ''; final code = map!['error']!['code'] ?? '';
@ -273,10 +274,8 @@ mixin ZanoWalletApi {
Future<CreateWalletResult> restoreWalletFromSeed(String path, String password, String seed) async { Future<CreateWalletResult> restoreWalletFromSeed(String path, String password, String seed) async {
info('restore_wallet path $path password ${_shorten(password)} seed ${_shorten(seed)}'); 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); final json = ApiCalls.restoreWalletFromSeed(path: path, password: password, seed: seed);
_json('restore_wallet', json); _json('restore_wallet', json);
//await _writeLog('restore_wallet', 'restore_wallet result $json');
final map = jsonDecode(json) as Map<String, dynamic>?; final map = jsonDecode(json) as Map<String, dynamic>?;
if (map?['error'] != null) { if (map?['error'] != null) {
final code = map!['error']!['code'] ?? ''; final code = map!['error']!['code'] ?? '';
@ -298,15 +297,19 @@ mixin ZanoWalletApi {
Future<CreateWalletResult> loadWallet(String path, String password, [int attempt = 0]) async { Future<CreateWalletResult> loadWallet(String path, String password, [int attempt = 0]) async {
info('load_wallet path $path password ${_shorten(password)}'); info('load_wallet path $path password ${_shorten(password)}');
//await _writeLog('load_wallet', 'load_wallet path $path password ${_shorten(password)}'); final String json;
final json = ApiCalls.loadWallet(path: path, password: password); try {
json = ApiCalls.loadWallet(path: path, password: password);
} catch (e) {
error('error in loadingWallet $e');
rethrow;
}
_json('load_wallet', json); _json('load_wallet', json);
//await _writeLog('load_wallet', 'load_wallet result $json');
final map = jsonDecode(json) as Map<String, dynamic>?; final map = jsonDecode(json) as Map<String, dynamic>?;
if (map?['error'] != null) { if (map?['error'] != null) {
final code = map?['error']!['code'] ?? ''; final code = map?['error']!['code'] ?? '';
final message = map?['error']!['message'] ?? ''; final message = map?['error']!['message'] ?? '';
if (code == Consts.errorAlreadyExists && attempt <= 5) { if (code == Consts.errorAlreadyExists && attempt <= _maxReopenAttempts) {
// already connected to this wallet. closing and trying to reopen // already connected to this wallet. closing and trying to reopen
info('already connected. closing and reopen wallet (attempt $attempt)'); info('already connected. closing and reopen wallet (attempt $attempt)');
closeWallet(attempt); closeWallet(attempt);
@ -347,11 +350,11 @@ mixin ZanoWalletApi {
final errorCode = resultMap['error']['code']; final errorCode = resultMap['error']['code'];
final code = errorCode is int ? errorCode.toString() : errorCode as String? ?? ''; final code = errorCode is int ? errorCode.toString() : errorCode as String? ?? '';
final message = resultMap['error']['message'] as String? ?? ''; final message = resultMap['error']['message'] as String? ?? '';
debugPrint('transfer error $code $message'); error('transfer error $code $message');
throw TransferException('Transfer error, $message ($code)'); throw TransferException('Transfer error, $message ($code)');
} }
} }
debugPrint('transfer error empty result'); error('transfer error empty result');
throw TransferException('Transfer error, empty result'); throw TransferException('Transfer error, empty result');
} }
@ -366,6 +369,9 @@ mixin ZanoWalletApi {
if (result['error'] != null) { if (result['error'] != null) {
final code = result['error']!['code'] ?? ''; final code = result['error']!['code'] ?? '';
final message = result['error']!['message'] ?? ''; final message = result['error']!['message'] ?? '';
if (code == -1 && message == Consts.errorBusy) {
throw ZanoWalletBusyException();
}
throw ZanoWalletException('Error, $message ($code)'); throw ZanoWalletException('Error, $message ($code)');
} }
} }
@ -382,5 +388,18 @@ mixin ZanoWalletApi {
static void info(String s) => _logInfo ? debugPrint('[info] $s') : null; static void info(String s) => _logInfo ? debugPrint('[info] $s') : null;
static void error(String s) => _logError ? debugPrint('[error] $s') : null; static void error(String s) => _logError ? debugPrint('[error] $s') : null;
static void _json(String methodName, String json) => _logJson ? debugPrint('$methodName $json') : null; static void printWrapped(String text) => RegExp('.{1,800}').allMatches(text).map((m) => m.group(0)).forEach(print);
static void _json(String methodName, String json) => _logJson ? printWrapped('$methodName $json') : null;
Map<String, dynamic> jsonDecode(String json) {
try {
return decodeJson(json.replaceAll("\\/", "/")) as Map<String, dynamic>;
} catch (e) {
return convert.jsonDecode(json) as Map<String, dynamic>;
}
}
String jsonEncode(Object? object) {
return convert.jsonEncode(object);
}
} }

View file

@ -12,4 +12,8 @@ class RestoreFromKeysException extends ZanoWalletException {
class TransferException extends ZanoWalletException { class TransferException extends ZanoWalletException {
TransferException(String message): super(message); TransferException(String message): super(message);
}
class ZanoWalletBusyException extends ZanoWalletException {
ZanoWalletBusyException(): super('');
} }

View file

@ -9,6 +9,7 @@ import 'package:cw_core/wallet_service.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:cw_zano/api/api_calls.dart'; import 'package:cw_zano/api/api_calls.dart';
import 'package:cw_zano/zano_wallet.dart'; import 'package:cw_zano/zano_wallet.dart';
import 'package:cw_zano/zano_wallet_api.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
class ZanoNewWalletCredentials extends WalletCredentials { class ZanoNewWalletCredentials extends WalletCredentials {
@ -54,7 +55,7 @@ class ZanoWalletService extends WalletService<ZanoNewWalletCredentials,
@override @override
Future<ZanoWallet> create(WalletCredentials credentials, {bool? isTestnet}) async { Future<ZanoWallet> create(WalletCredentials credentials, {bool? isTestnet}) async {
print('zanowallet service create isTestnet $isTestnet'); ZanoWalletApi.info('zanowallet service create isTestnet $isTestnet');
return await ZanoWalletBase.create(credentials: credentials); return await ZanoWalletBase.create(credentials: credentials);
} }

View file

@ -250,6 +250,19 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
fluttertoast:
dependency: "direct main"
description:
name: fluttertoast
sha256: "4215b0085ebf737120ab6b06fefeadfae709c74f880a351a73d6d007f74e7631"
url: "https://pub.dev"
source: hosted
version: "8.1.4"
frontend_server_client: frontend_server_client:
dependency: transitive dependency: transitive
description: description:
@ -346,6 +359,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.8.0" version: "4.8.0"
json_bigint:
dependency: "direct main"
description:
name: json_bigint
sha256: "77f5cc47ec936b37ff5016394b0ed136fe2231e35dcbaed32fe749c221264cac"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
leak_tracker: leak_tracker:
dependency: transitive dependency: transitive
description: description:
@ -490,6 +511,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.5" version: "2.1.5"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750
url: "https://pub.dev"
source: hosted
version: "5.4.0"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@ -737,4 +766,4 @@ packages:
version: "3.1.1" version: "3.1.1"
sdks: sdks:
dart: ">=3.2.0-0 <4.0.0" dart: ">=3.2.0-0 <4.0.0"
flutter: ">=3.0.0" flutter: ">=3.7.0"

View file

@ -21,6 +21,8 @@ dependencies:
decimal: ^2.3.3 decimal: ^2.3.3
cw_core: cw_core:
path: ../cw_core path: ../cw_core
json_bigint: ^3.0.0
fluttertoast: 8.1.4
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View file

@ -76,6 +76,8 @@ class DecimalAmountValidator extends TextValidator {
return '^([0-9]+([.\,][0-9]{1,12})?|[.\,][0-9]{1,12})\$'; return '^([0-9]+([.\,][0-9]{1,12})?|[.\,][0-9]{1,12})\$';
case CryptoCurrency.btc: case CryptoCurrency.btc:
return '^([0-9]+([.\,][0-9]{1,8})?|[.\,][0-9]{1,8})\$'; return '^([0-9]+([.\,][0-9]{1,8})?|[.\,][0-9]{1,8})\$';
case CryptoCurrency.zano:
return '^([0-9]+([.\,][0-9]{1,12})?|[.\,][0-9]{1,18})\$';
default: default:
return '^([0-9]+([.\,][0-9]{1,12})?|[.\,][0-9]{1,12})\$'; return '^([0-9]+([.\,][0-9]{1,12})?|[.\,][0-9]{1,12})\$';
} }

View file

@ -1,331 +0,0 @@
part of 'haven.dart';
class CWHavenAccountList extends HavenAccountList {
CWHavenAccountList(this._wallet);
final Object _wallet;
@override
@computed
ObservableList<Account> get accounts {
final havenWallet = _wallet as HavenWallet;
final accounts = havenWallet.walletAddresses.accountList.accounts
.map((acc) => Account(id: acc.id, label: acc.label))
.toList();
return ObservableList<Account>.of(accounts);
}
@override
void update(Object wallet) {
final havenWallet = wallet as HavenWallet;
havenWallet.walletAddresses.accountList.update();
}
@override
void refresh(Object wallet) {
final havenWallet = wallet as HavenWallet;
havenWallet.walletAddresses.accountList.refresh();
}
@override
List<Account> getAll(Object wallet) {
final havenWallet = wallet as HavenWallet;
return havenWallet.walletAddresses.accountList
.getAll()
.map((acc) => Account(id: acc.id, label: acc.label))
.toList();
}
@override
Future<void> addAccount(Object wallet, {required String label}) async {
final havenWallet = wallet as HavenWallet;
await havenWallet.walletAddresses.accountList.addAccount(label: label);
}
@override
Future<void> setLabelAccount(Object wallet,
{required int accountIndex, required String label}) async {
final havenWallet = wallet as HavenWallet;
await havenWallet.walletAddresses.accountList
.setLabelAccount(accountIndex: accountIndex, label: label);
}
}
class CWHavenSubaddressList extends MoneroSubaddressList {
CWHavenSubaddressList(this._wallet);
final Object _wallet;
@override
@computed
ObservableList<Subaddress> get subaddresses {
final havenWallet = _wallet as HavenWallet;
final subAddresses = havenWallet.walletAddresses.subaddressList.subaddresses
.map((sub) => Subaddress(id: sub.id, address: sub.address, label: sub.label))
.toList();
return ObservableList<Subaddress>.of(subAddresses);
}
@override
void update(Object wallet, {required int accountIndex}) {
final havenWallet = wallet as HavenWallet;
havenWallet.walletAddresses.subaddressList.update(accountIndex: accountIndex);
}
@override
void refresh(Object wallet, {required int accountIndex}) {
final havenWallet = wallet as HavenWallet;
havenWallet.walletAddresses.subaddressList.refresh(accountIndex: accountIndex);
}
@override
List<Subaddress> getAll(Object wallet) {
final havenWallet = wallet as HavenWallet;
return havenWallet.walletAddresses.subaddressList
.getAll()
.map((sub) => Subaddress(id: sub.id, label: sub.label, address: sub.address))
.toList();
}
@override
Future<void> addSubaddress(Object wallet,
{required int accountIndex, required String label}) async {
final havenWallet = wallet as HavenWallet;
await havenWallet.walletAddresses.subaddressList
.addSubaddress(accountIndex: accountIndex, label: label);
}
@override
Future<void> setLabelSubaddress(Object wallet,
{required int accountIndex, required int addressIndex, required String label}) async {
final havenWallet = wallet as HavenWallet;
await havenWallet.walletAddresses.subaddressList
.setLabelSubaddress(accountIndex: accountIndex, addressIndex: addressIndex, label: label);
}
}
class CWHavenWalletDetails extends HavenWalletDetails {
CWHavenWalletDetails(this._wallet);
final Object _wallet;
@computed
@override
Account get account {
final havenWallet = _wallet as HavenWallet;
final acc = havenWallet.walletAddresses.account as monero_account.Account;
return Account(id: acc.id, label: acc.label);
}
@computed
@override
HavenBalance get balance {
final havenWallet = _wallet as HavenWallet;
final balance = havenWallet.balance;
throw Exception('Unimplemented');
//return HavenBalance(
// fullBalance: balance.fullBalance,
// unlockedBalance: balance.unlockedBalance);
}
}
class CWHaven extends Haven {
@override
HavenAccountList getAccountList(Object wallet) {
return CWHavenAccountList(wallet);
}
@override
MoneroSubaddressList getSubaddressList(Object wallet) {
return CWHavenSubaddressList(wallet);
}
@override
TransactionHistoryBase getTransactionHistory(Object wallet) {
final havenWallet = wallet as HavenWallet;
return havenWallet.transactionHistory;
}
@override
HavenWalletDetails getMoneroWalletDetails(Object wallet) {
return CWHavenWalletDetails(wallet);
}
@override
int getHeightByDate({required DateTime date}) => getHavenHeightByDate(date: date);
@override
Future<int> getCurrentHeight() => getHavenCurrentHeight();
@override
TransactionPriority getDefaultTransactionPriority() {
return MoneroTransactionPriority.automatic;
}
@override
TransactionPriority deserializeMoneroTransactionPriority({required int raw}) {
return MoneroTransactionPriority.deserialize(raw: raw);
}
@override
List<TransactionPriority> getTransactionPriorities() {
return MoneroTransactionPriority.all;
}
@override
List<String> getMoneroWordList(String language) {
switch (language.toLowerCase()) {
case 'english':
return EnglishMnemonics.words;
case 'chinese (simplified)':
return ChineseSimplifiedMnemonics.words;
case 'dutch':
return DutchMnemonics.words;
case 'german':
return GermanMnemonics.words;
case 'japanese':
return JapaneseMnemonics.words;
case 'portuguese':
return PortugueseMnemonics.words;
case 'russian':
return RussianMnemonics.words;
case 'spanish':
return SpanishMnemonics.words;
case 'french':
return FrenchMnemonics.words;
case 'italian':
return ItalianMnemonics.words;
default:
return EnglishMnemonics.words;
}
}
@override
WalletCredentials createHavenRestoreWalletFromKeysCredentials(
{required String name,
required String spendKey,
required String viewKey,
required String address,
required String password,
required String language,
required int height}) {
return HavenRestoreWalletFromKeysCredentials(
name: name,
spendKey: spendKey,
viewKey: viewKey,
address: address,
password: password,
language: language,
height: height);
}
@override
WalletCredentials createHavenRestoreWalletFromSeedCredentials(
{required String name,
required String password,
required int height,
required String mnemonic}) {
return HavenRestoreWalletFromSeedCredentials(
name: name, password: password, height: height, mnemonic: mnemonic);
}
@override
WalletCredentials createHavenNewWalletCredentials(
{required String name, required String language, String? password}) {
return HavenNewWalletCredentials(name: name, password: password, language: language);
}
@override
Map<String, String> getKeys(Object wallet) {
final havenWallet = wallet as HavenWallet;
final keys = havenWallet.keys;
return <String, String>{
'privateSpendKey': keys.privateSpendKey,
'privateViewKey': keys.privateViewKey,
'publicSpendKey': keys.publicSpendKey,
'publicViewKey': keys.publicViewKey
};
}
@override
Object createHavenTransactionCreationCredentials(
{required List<Output> outputs,
required TransactionPriority priority,
required String assetType}) {
return HavenTransactionCreationCredentials(
outputs: outputs
.map((out) => OutputInfo(
fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmount,
address: out.address,
note: out.note,
sendAll: out.sendAll,
extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress,
formattedCryptoAmount: out.formattedCryptoAmount))
.toList(),
priority: priority as MoneroTransactionPriority,
assetType: assetType);
}
@override
String formatterMoneroAmountToString({required int amount}) {
return moneroAmountToString(amount: amount);
}
@override
double formatterMoneroAmountToDouble({required int amount}) {
return moneroAmountToDouble(amount: amount);
}
@override
int formatterMoneroParseAmount({required String amount}) {
return moneroParseAmount(amount: amount);
}
@override
Account getCurrentAccount(Object wallet) {
final havenWallet = wallet as HavenWallet;
final acc = havenWallet.walletAddresses.account as monero_account.Account;
return Account(id: acc.id, label: acc.label);
}
@override
void setCurrentAccount(Object wallet, int id, String label) {
final havenWallet = wallet as HavenWallet;
havenWallet.walletAddresses.account = monero_account.Account(id: id, label: label);
}
@override
void onStartup() {
monero_wallet_api.onStartup();
}
@override
int getTransactionInfoAccountId(TransactionInfo tx) {
final havenTransactionInfo = tx as HavenTransactionInfo;
return havenTransactionInfo.accountIndex;
}
@override
WalletService createHavenWalletService(Box<WalletInfo> walletInfoSource) {
return HavenWalletService(walletInfoSource);
}
@override
String getTransactionAddress(Object wallet, int accountIndex, int addressIndex) {
final havenWallet = wallet as HavenWallet;
return havenWallet.getTransactionAddress(accountIndex, addressIndex);
}
@override
CryptoCurrency assetOfTransaction(TransactionInfo tx) {
final transaction = tx as HavenTransactionInfo;
final asset = CryptoCurrency.fromString(transaction.assetType);
return asset;
}
@override
List<AssetRate> getAssetRate() =>
getRate().map((rate) => AssetRate(rate.getAssetType(), rate.getRate())).toList();
}

View file

@ -88,9 +88,7 @@ abstract class HomeSettingsViewModelBase with Store {
} }
if (_balanceViewModel.wallet.type == WalletType.zano) { if (_balanceViewModel.wallet.type == WalletType.zano) {
// TODO: assuming that token is Erc20Token await zano!.addZanoAssetById(_balanceViewModel.wallet, contractAddress);
token as Erc20Token;
await zano!.addZanoAssetById(_balanceViewModel.wallet, token.contractAddress);
} }
_updateTokensList(); _updateTokensList();

View file

@ -177,7 +177,7 @@ class TransactionListItem extends ActionListItem with Keyable {
final asset = zano!.assetOfTransaction(balanceViewModel.wallet, transaction); final asset = zano!.assetOfTransaction(balanceViewModel.wallet, transaction);
final price = balanceViewModel.fiatConvertationStore.prices[asset]; final price = balanceViewModel.fiatConvertationStore.prices[asset];
amount = calculateFiatAmountRaw( amount = calculateFiatAmountRaw(
cryptoAmount: zano!.formatterIntAmountToDouble(amount: transaction.amount, currency: asset), cryptoAmount: zano!.formatterIntAmountToDouble(amount: transaction.amount, currency: asset, forFee: false),
price: price); price: price);
break; break;
default: default:

View file

@ -183,7 +183,7 @@ abstract class OutputBase with Store {
} }
if (_wallet.type == WalletType.zano) { if (_wallet.type == WalletType.zano) {
return zano!.formatterIntAmountToDouble(amount: fee, currency: cryptoCurrencyHandler()); return zano!.formatterIntAmountToDouble(amount: fee, currency: cryptoCurrencyHandler(), forFee: true);
} }
} catch (e) { } catch (e) {
print(e.toString()); print(e.toString());

View file

@ -20,6 +20,7 @@ import 'package:cake_wallet/view_model/send/send_view_model.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/transaction_priority.dart';
import 'package:cw_zano/model/zano_transaction_info.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:intl/src/intl/date_format.dart'; import 'package:intl/src/intl/date_format.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
@ -497,9 +498,11 @@ abstract class TransactionDetailsViewModelBase with Store {
} }
void _addZanoListItems(TransactionInfo tx, DateFormat dateFormat) { void _addZanoListItems(TransactionInfo tx, DateFormat dateFormat) {
tx as ZanoTransactionInfo;
final comment = tx.additionalInfo['comment'] as String?; final comment = tx.additionalInfo['comment'] as String?;
items.addAll([ items.addAll([
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id), StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id),
StandartListItem(title: 'Asset ID', value: tx.assetId),
StandartListItem( StandartListItem(
title: S.current.transaction_details_date, value: dateFormat.format(tx.date)), title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'), StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),

View file

@ -102,7 +102,9 @@ class CWZano extends Zano {
} }
@override @override
double formatterIntAmountToDouble({required int amount, required CryptoCurrency currency}) { double formatterIntAmountToDouble({required int amount, required CryptoCurrency currency, required bool forFee}) {
// fee always counted in zano with default decimal points
if (forFee) return ZanoFormatter.intAmountToDouble(amount);
if (currency is ZanoAsset) return ZanoFormatter.intAmountToDouble(amount, currency.decimalPoint); if (currency is ZanoAsset) return ZanoFormatter.intAmountToDouble(amount, currency.decimalPoint);
return ZanoFormatter.intAmountToDouble(amount); return ZanoFormatter.intAmountToDouble(amount);
} }

View file

@ -42,7 +42,7 @@ abstract class Zano {
WalletCredentials createZanoRestoreWalletFromSeedCredentials({required String name, required String password, required int height, required String mnemonic}); WalletCredentials createZanoRestoreWalletFromSeedCredentials({required String name, required String password, required int height, required String mnemonic});
WalletCredentials createZanoNewWalletCredentials({required String name, String password}); WalletCredentials createZanoNewWalletCredentials({required String name, String password});
Object createZanoTransactionCredentials({required List<Output> outputs, required TransactionPriority priority, required CryptoCurrency currency}); Object createZanoTransactionCredentials({required List<Output> outputs, required TransactionPriority priority, required CryptoCurrency currency});
double formatterIntAmountToDouble({required int amount, required CryptoCurrency currency}); double formatterIntAmountToDouble({required int amount, required CryptoCurrency currency, required bool forFee});
int formatterParseAmount({required String amount, required CryptoCurrency currency}); int formatterParseAmount({required String amount, required CryptoCurrency currency});
WalletService createZanoWalletService(Box<WalletInfo> walletInfoSource); WalletService createZanoWalletService(Box<WalletInfo> walletInfoSource);
CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo tx); CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo tx);

View file

@ -1394,7 +1394,7 @@ abstract class Zano {
WalletCredentials createZanoRestoreWalletFromSeedCredentials({required String name, required String password, required int height, required String mnemonic}); WalletCredentials createZanoRestoreWalletFromSeedCredentials({required String name, required String password, required int height, required String mnemonic});
WalletCredentials createZanoNewWalletCredentials({required String name, String password}); WalletCredentials createZanoNewWalletCredentials({required String name, String password});
Object createZanoTransactionCredentials({required List<Output> outputs, required TransactionPriority priority, required CryptoCurrency currency}); Object createZanoTransactionCredentials({required List<Output> outputs, required TransactionPriority priority, required CryptoCurrency currency});
double formatterIntAmountToDouble({required int amount, required CryptoCurrency currency}); double formatterIntAmountToDouble({required int amount, required CryptoCurrency currency, required bool forFee});
int formatterParseAmount({required String amount, required CryptoCurrency currency}); int formatterParseAmount({required String amount, required CryptoCurrency currency});
WalletService createZanoWalletService(Box<WalletInfo> walletInfoSource); WalletService createZanoWalletService(Box<WalletInfo> walletInfoSource);
CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo tx); CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo tx);