From fe2e26f14600b19125a3bce2369fb61a742ceda1 Mon Sep 17 00:00:00 2001 From: Matthew Fosse Date: Thu, 11 Jan 2024 15:00:41 -0800 Subject: [PATCH] Minor nano fixes + abstract nano utils out into package (#1250) * fixes * refactors * fixes * more fixes * fixes for monero.com * update confirmed balance to receivable balance * fix balance display bug * remove print statements * prevent unnecessary writes * review fixes * forgot to add pubspec changes * fix * fix sync status desyncing on rpc fail * fix * update translations + txDetails fixes * squash balance bug * add source address on tx info * fix --- ...let_android.sh => configure_cake_wallet.sh | 22 +- cw_core/lib/transaction_info.dart | 1 + cw_nano/lib/banano_balance.dart | 9 +- cw_nano/lib/nano_balance.dart | 25 +- cw_nano/lib/nano_client.dart | 11 +- cw_nano/lib/nano_transaction_info.dart | 15 +- cw_nano/lib/nano_util.dart | 193 ---------------- cw_nano/lib/nano_wallet.dart | 98 +++++--- cw_nano/lib/nano_wallet_service.dart | 6 +- cw_nano/lib/pending_nano_transaction.dart | 8 +- cw_nano/pubspec.yaml | 4 + lib/nano/cw_nano.dart | 216 ++---------------- .../dashboard/balance_view_model.dart | 5 + .../dashboard/transaction_list_item.dart | 2 +- .../transaction_details_view_model.dart | 8 +- res/values/strings_ar.arb | 7 +- res/values/strings_bg.arb | 7 +- res/values/strings_cs.arb | 7 +- res/values/strings_de.arb | 7 +- res/values/strings_en.arb | 7 +- res/values/strings_es.arb | 7 +- res/values/strings_fr.arb | 7 +- res/values/strings_ha.arb | 7 +- res/values/strings_hi.arb | 7 +- res/values/strings_hr.arb | 7 +- res/values/strings_id.arb | 7 +- res/values/strings_it.arb | 7 +- res/values/strings_ja.arb | 7 +- res/values/strings_ko.arb | 7 +- res/values/strings_my.arb | 7 +- res/values/strings_nl.arb | 7 +- res/values/strings_pl.arb | 7 +- res/values/strings_pt.arb | 7 +- res/values/strings_ru.arb | 7 +- res/values/strings_th.arb | 7 +- res/values/strings_tl.arb | 7 +- res/values/strings_tr.arb | 7 +- res/values/strings_uk.arb | 7 +- res/values/strings_ur.arb | 7 +- res/values/strings_yo.arb | 7 +- res/values/strings_zh.arb | 7 +- tool/configure.dart | 19 +- 42 files changed, 306 insertions(+), 518 deletions(-) rename configure_cake_wallet_android.sh => configure_cake_wallet.sh (74%) delete mode 100644 cw_nano/lib/nano_util.dart diff --git a/configure_cake_wallet_android.sh b/configure_cake_wallet.sh similarity index 74% rename from configure_cake_wallet_android.sh rename to configure_cake_wallet.sh index da794a35c..df96c70f6 100755 --- a/configure_cake_wallet_android.sh +++ b/configure_cake_wallet.sh @@ -1,4 +1,24 @@ -cd scripts/android +IOS="ios" +ANDROID="android" + +PLATFORMS=($IOS $ANDROID) +PLATFORM=$1 + +if ! [[ " ${PLATFORMS[*]} " =~ " ${PLATFORM} " ]]; then + echo "specify platform: ./configure_cake_wallet.sh ios|android" + exit 1 +fi + +if [ "$PLATFORM" == "$IOS" ]; then + echo "Configuring for iOS" + cd scripts/ios +fi + +if [ "$PLATFORM" == "$ANDROID" ]; then + echo "Configuring for Android" + cd scripts/android +fi + source ./app_env.sh cakewallet ./app_config.sh cd ../.. && flutter pub get diff --git a/cw_core/lib/transaction_info.dart b/cw_core/lib/transaction_info.dart index 38b4b799d..7624b147f 100644 --- a/cw_core/lib/transaction_info.dart +++ b/cw_core/lib/transaction_info.dart @@ -15,6 +15,7 @@ abstract class TransactionInfo extends Object with Keyable { String? feeFormatted(); void changeFiatAmount(String amount); String? to; + String? from; @override dynamic get keyIndex => id; diff --git a/cw_nano/lib/banano_balance.dart b/cw_nano/lib/banano_balance.dart index b85609b60..b904a35cb 100644 --- a/cw_nano/lib/banano_balance.dart +++ b/cw_nano/lib/banano_balance.dart @@ -1,20 +1,19 @@ import 'package:cw_core/balance.dart'; -import 'package:cw_nano/nano_util.dart'; +import 'package:nanoutil/nanoutil.dart'; class BananoBalance extends Balance { final BigInt currentBalance; final BigInt receivableBalance; - BananoBalance({required this.currentBalance, required this.receivableBalance}) : super(0, 0) { - } + BananoBalance({required this.currentBalance, required this.receivableBalance}) : super(0, 0); @override String get formattedAvailableBalance { - return NanoUtil.getRawAsUsableString(currentBalance.toString(), NanoUtil.rawPerBanano); + return NanoAmounts.getRawAsUsableString(currentBalance.toString(), NanoAmounts.rawPerBanano); } @override String get formattedAdditionalBalance { - return NanoUtil.getRawAsUsableString(receivableBalance.toString(), NanoUtil.rawPerBanano); + return NanoAmounts.getRawAsUsableString(receivableBalance.toString(), NanoAmounts.rawPerBanano); } } diff --git a/cw_nano/lib/nano_balance.dart b/cw_nano/lib/nano_balance.dart index dbb39d2a3..8b8c93b33 100644 --- a/cw_nano/lib/nano_balance.dart +++ b/cw_nano/lib/nano_balance.dart @@ -1,34 +1,35 @@ import 'package:cw_core/balance.dart'; -import 'package:cw_nano/nano_util.dart'; +import 'package:nanoutil/nanoutil.dart'; BigInt stringAmountToBigInt(String amount) { - return BigInt.parse(NanoUtil.getAmountAsRaw(amount, NanoUtil.rawPerNano)); + return BigInt.parse(NanoAmounts.getAmountAsRaw(amount, NanoAmounts.rawPerNano)); } class NanoBalance extends Balance { final BigInt currentBalance; final BigInt receivableBalance; - late String formattedCurrentBalance; - late String formattedReceivableBalance; - NanoBalance({required this.currentBalance, required this.receivableBalance}) : super(0, 0) { - this.formattedCurrentBalance = ""; - this.formattedReceivableBalance = ""; - } + NanoBalance({required this.currentBalance, required this.receivableBalance}) : super(0, 0); - NanoBalance.fromString( - {required this.formattedCurrentBalance, required this.formattedReceivableBalance}) + NanoBalance.fromFormattedString( + {required String formattedCurrentBalance, required String formattedReceivableBalance}) : currentBalance = stringAmountToBigInt(formattedCurrentBalance), receivableBalance = stringAmountToBigInt(formattedReceivableBalance), super(0, 0); + NanoBalance.fromRawString( + {required String currentBalance, required String receivableBalance}) + : currentBalance = BigInt.parse(currentBalance), + receivableBalance = BigInt.parse(receivableBalance), + super(0, 0); + @override String get formattedAvailableBalance { - return NanoUtil.getRawAsUsableString(currentBalance.toString(), NanoUtil.rawPerNano); + return NanoAmounts.getRawAsUsableString(currentBalance.toString(), NanoAmounts.rawPerNano); } @override String get formattedAdditionalBalance { - return NanoUtil.getRawAsUsableString(receivableBalance.toString(), NanoUtil.rawPerNano); + return NanoAmounts.getRawAsUsableString(receivableBalance.toString(), NanoAmounts.rawPerNano); } } diff --git a/cw_nano/lib/nano_client.dart b/cw_nano/lib/nano_client.dart index f1d08204a..661fbcab8 100644 --- a/cw_nano/lib/nano_client.dart +++ b/cw_nano/lib/nano_client.dart @@ -4,10 +4,10 @@ import 'dart:convert'; import 'package:cw_core/nano_account_info_response.dart'; import 'package:cw_nano/nano_balance.dart'; import 'package:cw_nano/nano_transaction_model.dart'; -import 'package:cw_nano/nano_util.dart'; import 'package:http/http.dart' as http; import 'package:nanodart/nanodart.dart'; import 'package:cw_core/node.dart'; +import 'package:nanoutil/nanoutil.dart'; import 'package:shared_preferences/shared_preferences.dart'; class NanoClient { @@ -61,6 +61,13 @@ class NanoClient { ), ); final data = await jsonDecode(response.body); + if (response.statusCode != 200 || + data["error"] != null || + data["balance"] == null || + data["receivable"] == null) { + throw Exception( + "Error while trying to get balance! ${data["error"] != null ? data["error"] : ""}"); + } final String currentBalance = data["balance"] as String; final String receivableBalance = data["receivable"] as String; final BigInt cur = BigInt.parse(currentBalance); @@ -203,7 +210,7 @@ class NanoClient { String? previousHash, }) async { // our address: - final String publicAddress = NanoUtil.privateKeyToAddress(privateKey); + final String publicAddress = NanoDerivations.privateKeyToAddress(privateKey); // first get the current account balance: if (balanceAfterTx == null) { diff --git a/cw_nano/lib/nano_transaction_info.dart b/cw_nano/lib/nano_transaction_info.dart index 8958086dd..9195a06ef 100644 --- a/cw_nano/lib/nano_transaction_info.dart +++ b/cw_nano/lib/nano_transaction_info.dart @@ -1,7 +1,7 @@ import 'package:cw_core/format_amount.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_info.dart'; -import 'package:cw_nano/nano_util.dart'; +import 'package:nanoutil/nanoutil.dart'; class NanoTransactionInfo extends TransactionInfo { NanoTransactionInfo({ @@ -13,6 +13,8 @@ class NanoTransactionInfo extends TransactionInfo { required this.confirmed, required this.date, required this.confirmations, + required this.to, + required this.from, }) : this.amount = amountRaw.toInt(); final String id; @@ -24,14 +26,17 @@ class NanoTransactionInfo extends TransactionInfo { final bool confirmed; final int confirmations; final String tokenSymbol; + final String? to; + final String? from; String? _fiatAmount; bool get isPending => !this.confirmed; @override String amountFormatted() { - final String amt = NanoUtil.getRawAsUsableString(amountRaw.toString(), NanoUtil.rawPerNano); - final String acc = NanoUtil.getRawAccuracy(amountRaw.toString(), NanoUtil.rawPerNano); + final String amt = + NanoAmounts.getRawAsUsableString(amountRaw.toString(), NanoAmounts.rawPerNano); + final String acc = NanoAmounts.getRawAccuracy(amountRaw.toString(), NanoAmounts.rawPerNano); return "$acc$amt $tokenSymbol"; } @@ -54,6 +59,8 @@ class NanoTransactionInfo extends TransactionInfo { confirmed: data['confirmed'] as bool, confirmations: data['confirmations'] as int, tokenSymbol: data['tokenSymbol'] as String, + to: data['to'] as String, + from: data['from'] as String, ); } @@ -66,5 +73,7 @@ class NanoTransactionInfo extends TransactionInfo { 'confirmed': confirmed, 'confirmations': confirmations, 'tokenSymbol': tokenSymbol, + 'to': to, + 'from': from, }; } diff --git a/cw_nano/lib/nano_util.dart b/cw_nano/lib/nano_util.dart deleted file mode 100644 index 13d6f5649..000000000 --- a/cw_nano/lib/nano_util.dart +++ /dev/null @@ -1,193 +0,0 @@ -import 'dart:convert'; -import 'dart:typed_data'; - -import 'package:convert/convert.dart'; -import "package:ed25519_hd_key/ed25519_hd_key.dart"; -import 'package:libcrypto/libcrypto.dart'; -import 'package:nanodart/nanodart.dart'; -import 'package:decimal/decimal.dart'; - -class NanoUtil { - // standard: - static String seedToPrivate(String seed, int index) { - return NanoKeys.seedToPrivate(seed, index); - } - - static String seedToAddress(String seed, int index) { - return NanoAccounts.createAccount( - NanoAccountType.NANO, privateKeyToPublic(seedToPrivate(seed, index))); - } - - static String seedToMnemonic(String seed) { - return NanoMnemomics.seedToMnemonic(seed).join(" "); - } - - static Future mnemonicToSeed(String mnemonic) async { - return NanoMnemomics.mnemonicListToSeed(mnemonic.split(' ')); - } - - static String privateKeyToPublic(String privateKey) { - // return NanoHelpers.byteToHex(Ed25519Blake2b.getPubkey(NanoHelpers.hexToBytes(privateKey))!); - return NanoKeys.createPublicKey(privateKey); - } - - static String addressToPublicKey(String publicAddress) { - return NanoAccounts.extractPublicKey(publicAddress); - } - - // universal: - static String privateKeyToAddress(String privateKey) { - return NanoAccounts.createAccount(NanoAccountType.NANO, privateKeyToPublic(privateKey)); - } - - static String publicKeyToAddress(String publicKey) { - return NanoAccounts.createAccount(NanoAccountType.NANO, publicKey); - } - - // standard + hd: - static bool isValidSeed(String seed) { - // Ensure seed is 64 or 128 characters long - if (seed == null || (seed.length != 64 && seed.length != 128)) { - return false; - } - // Ensure seed only contains hex characters, 0-9;A-F - return NanoHelpers.isHexString(seed); - } - - // // hd: - static Future hdMnemonicListToSeed(List words) async { - // if (words.length != 24) { - // throw Exception('Expected a 24-word list, got a ${words.length} list'); - // } - final Uint8List salt = Uint8List.fromList(utf8.encode('mnemonic')); - final Pbkdf2 hasher = Pbkdf2(iterations: 2048); - final String seed = await hasher.sha512(words.join(' '), salt); - return seed; - } - - static Future hdSeedToPrivate(String seed, int index) async { - List seedBytes = hex.decode(seed); - KeyData data = await ED25519_HD_KEY.derivePath("m/44'/165'/$index'", seedBytes); - return hex.encode(data.key); - } - - static Future hdSeedToAddress(String seed, int index) async { - return NanoAccounts.createAccount( - NanoAccountType.NANO, privateKeyToPublic(await hdSeedToPrivate(seed, index))); - } - - static Future uniSeedToAddress(String seed, int index, String type) { - if (type == "standard") { - return Future.value(seedToAddress(seed, index)); - } else if (type == "hd") { - return hdSeedToAddress(seed, index); - } else { - throw Exception('Unknown seed type'); - } - } - - static Future uniSeedToPrivate(String seed, int index, String type) { - if (type == "standard") { - return Future.value(seedToPrivate(seed, index)); - } else if (type == "hd") { - return hdSeedToPrivate(seed, index); - } else { - throw Exception('Unknown seed type'); - } - } - - static bool isValidBip39Seed(String seed) { - // Ensure seed is 128 characters long - if (seed.length != 128) { - return false; - } - // Ensure seed only contains hex characters, 0-9;A-F - return NanoHelpers.isHexString(seed); - } - - // number util: - - static const int maxDecimalDigits = 6; // Max digits after decimal - static BigInt rawPerNano = BigInt.parse("1000000000000000000000000000000"); - static BigInt rawPerNyano = BigInt.parse("1000000000000000000000000"); - static BigInt rawPerBanano = BigInt.parse("100000000000000000000000000000"); - static BigInt rawPerXMR = BigInt.parse("1000000000000"); - static BigInt convertXMRtoNano = BigInt.parse("1000000000000000000"); - // static BigInt convertXMRtoNano = BigInt.parse("1000000000000000000000000000"); - - /// Convert raw to ban and return as BigDecimal - /// - /// @param raw 100000000000000000000000000000 - /// @return Decimal value 1.000000000000000000000000000000 - /// - static Decimal getRawAsDecimal(String? raw, BigInt? rawPerCur) { - rawPerCur ??= rawPerNano; - final Decimal amount = Decimal.parse(raw.toString()); - final Decimal result = (amount / Decimal.parse(rawPerCur.toString())).toDecimal(); - return result; - } - - static String truncateDecimal(Decimal input, {int digits = maxDecimalDigits}) { - Decimal bigger = input.shift(digits); - bigger = bigger.floor(); // chop off the decimal: 1.059 -> 1.05 - bigger = bigger.shift(-digits); - return bigger.toString(); - } - - /// Return raw as a NANO amount. - /// - /// @param raw 100000000000000000000000000000 - /// @returns 1 - /// - static String getRawAsUsableString(String? raw, BigInt rawPerCur) { - final String res = - truncateDecimal(getRawAsDecimal(raw, rawPerCur), digits: maxDecimalDigits + 9); - - if (raw == null || raw == "0" || raw == "00000000000000000000000000000000") { - return "0"; - } - - if (!res.contains(".")) { - return res; - } - - final String numAmount = res.split(".")[0]; - String decAmount = res.split(".")[1]; - - // truncate: - if (decAmount.length > maxDecimalDigits) { - decAmount = decAmount.substring(0, maxDecimalDigits); - // remove trailing zeros: - decAmount = decAmount.replaceAllMapped(RegExp(r'0+$'), (Match match) => ''); - if (decAmount.isEmpty) { - return numAmount; - } - } - - return "$numAmount.$decAmount"; - } - - static String getRawAccuracy(String? raw, BigInt rawPerCur) { - final String rawString = getRawAsUsableString(raw, rawPerCur); - final String rawDecimalString = getRawAsDecimal(raw, rawPerCur).toString(); - - if (raw == null || raw.isEmpty || raw == "0") { - return ""; - } - - if (rawString != rawDecimalString) { - return "~"; - } - return ""; - } - - /// Return readable string amount as raw string - /// @param amount 1.01 - /// @returns 101000000000000000000000000000 - /// - static String getAmountAsRaw(String amount, BigInt rawPerCur) { - final Decimal asDecimal = Decimal.parse(amount); - final Decimal rawDecimal = Decimal.parse(rawPerCur.toString()); - return (asDecimal * rawDecimal).toString(); - } -} diff --git a/cw_nano/lib/nano_wallet.dart b/cw_nano/lib/nano_wallet.dart index b0d98efec..1f6ec36ae 100644 --- a/cw_nano/lib/nano_wallet.dart +++ b/cw_nano/lib/nano_wallet.dart @@ -18,7 +18,6 @@ import 'package:cw_nano/nano_client.dart'; import 'package:cw_nano/nano_transaction_credentials.dart'; import 'package:cw_nano/nano_transaction_history.dart'; import 'package:cw_nano/nano_transaction_info.dart'; -import 'package:cw_nano/nano_util.dart'; import 'package:cw_nano/nano_wallet_keys.dart'; import 'package:cw_nano/pending_nano_transaction.dart'; import 'package:mobx/mobx.dart'; @@ -27,6 +26,7 @@ import 'package:cw_nano/nano_wallet_addresses.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:nanodart/nanodart.dart'; import 'package:bip39/bip39.dart' as bip39; +import 'package:nanoutil/nanoutil.dart'; part 'nano_wallet.g.dart'; @@ -83,6 +83,8 @@ abstract class NanoWalletBase @observable late ObservableMap balance; + static const int POLL_INTERVAL_SECONDS = 10; + // initialize the different forms of private / public key we'll need: Future init() async { if (_derivationType == DerivationType.unknown) { @@ -100,11 +102,21 @@ abstract class NanoWalletBase if (_derivationType == DerivationType.nano) { _hexSeed = bip39.mnemonicToEntropy(_mnemonic).toUpperCase(); } else { - _hexSeed = await NanoUtil.hdMnemonicListToSeed(_mnemonic.split(' ')); + _hexSeed = await NanoDerivations.hdMnemonicListToSeed(_mnemonic.split(' ')); } } - _privateKey = await NanoUtil.uniSeedToPrivate(_hexSeed!, 0, type); - _publicAddress = await NanoUtil.uniSeedToAddress(_hexSeed!, 0, type); + NanoDerivationType derivationType = + type == "standard" ? NanoDerivationType.STANDARD : NanoDerivationType.HD; + _privateKey = await NanoDerivations.universalSeedToPrivate( + _hexSeed!, + index: 0, + type: derivationType, + ); + _publicAddress = await NanoDerivations.universalSeedToAddress( + _hexSeed!, + index: 0, + type: derivationType, + ); this.walletInfo.address = _publicAddress!; await walletAddresses.init(); @@ -125,6 +137,7 @@ abstract class NanoWalletBase @override void close() { _client.stop(); + _receiveTimer?.cancel(); } @action @@ -139,6 +152,7 @@ abstract class NanoWalletBase try { await _updateBalance(); + await updateTransactions(); await _updateRep(); await _receiveAll(); } catch (e) { @@ -173,8 +187,8 @@ abstract class NanoWalletBase if (txOut.sendAll) { amt = balance[currency]?.currentBalance ?? BigInt.zero; } else { - amt = BigInt.tryParse(NanoUtil.getAmountAsRaw( - txOut.cryptoAmount?.replaceAll(',', '.') ?? "0", NanoUtil.rawPerNano)) ?? + amt = BigInt.tryParse(NanoAmounts.getAmountAsRaw( + txOut.cryptoAmount?.replaceAll(',', '.') ?? "0", NanoAmounts.rawPerNano)) ?? BigInt.zero; } @@ -186,9 +200,7 @@ abstract class NanoWalletBase final block = await _client.constructSendBlock( amountRaw: amt.toString(), - destinationAddress: txOut.isParsedAddress - ? txOut.extractedAddress! - : txOut.address, + destinationAddress: txOut.isParsedAddress ? txOut.extractedAddress! : txOut.address, privateKey: _privateKey!, balanceAfterTx: runningBalance, previousHash: previousHash, @@ -236,10 +248,10 @@ abstract class NanoWalletBase } } - Future updateTransactions() async { + Future updateTransactions() async { try { if (_isTransactionUpdating) { - return; + return false; } _isTransactionUpdating = true; @@ -247,8 +259,10 @@ abstract class NanoWalletBase transactionHistory.addMany(transactions); await transactionHistory.save(); _isTransactionUpdating = false; + return true; } catch (_) { _isTransactionUpdating = false; + return false; } } @@ -261,16 +275,17 @@ abstract class NanoWalletBase final Map result = {}; for (var transactionModel in transactions) { + final bool isSend = transactionModel.type == "send"; result[transactionModel.hash] = NanoTransactionInfo( id: transactionModel.hash, amountRaw: transactionModel.amount, height: transactionModel.height, - direction: transactionModel.type == "send" - ? TransactionDirection.outgoing - : TransactionDirection.incoming, + direction: isSend ? TransactionDirection.outgoing : TransactionDirection.incoming, confirmed: transactionModel.confirmed, date: transactionModel.date ?? DateTime.now(), confirmations: transactionModel.confirmed ? 1 : 0, + to: isSend ? transactionModel.account : address, + from: isSend ? address : transactionModel.account, ); } @@ -312,11 +327,10 @@ abstract class NanoWalletBase Future startSync() async { try { syncStatus = AttemptingSyncStatus(); - await _updateBalance(); - await updateTransactions(); + // setup a timer to receive transactions periodically: _receiveTimer?.cancel(); - _receiveTimer = Timer.periodic(const Duration(seconds: 15), (timer) async { + _receiveTimer = Timer.periodic(const Duration(seconds: POLL_INTERVAL_SECONDS), (timer) async { // get our balance: await _updateBalance(); // if we have anything to receive, process it: @@ -325,6 +339,14 @@ abstract class NanoWalletBase } }); + // also run once, immediately: + await _updateBalance(); + bool updateSuccess = await updateTransactions(); + if (!updateSuccess) { + syncStatus = FailedSyncStatus(); + return; + } + syncStatus = SyncedSyncStatus(); } catch (e) { print(e); @@ -353,9 +375,11 @@ abstract class NanoWalletBase final data = json.decode(jsonSource) as Map; final mnemonic = data['mnemonic'] as String; - final balance = NanoBalance.fromString( - formattedCurrentBalance: data['currentBalance'] as String? ?? "0", - formattedReceivableBalance: data['receivableBalance'] as String? ?? "0"); + + final balance = NanoBalance.fromRawString( + currentBalance: data['currentBalance'] as String? ?? "0", + receivableBalance: data['receivableBalance'] as String? ?? "0", + ); DerivationType derivationType = DerivationType.nano; if (data['derivationType'] == "DerivationType.bip39") { @@ -374,12 +398,26 @@ abstract class NanoWalletBase } Future _updateBalance() async { + var oldBalance = balance[currency]; try { balance[currency] = await _client.getBalance(_publicAddress!); } catch (e) { print("Failed to get balance $e"); + // if we don't have a balance, we should at least create one, since it's a late binding + // otherwise, it's better to just leave it as whatever it was before: + if (balance[currency] == null) { + balance[currency] = + NanoBalance(currentBalance: BigInt.zero, receivableBalance: BigInt.zero); + } + } + // don't save unnecessarily: + // trying to save too frequently can cause problems with the file system + // since nano is updated frequently this can be a problem, so we only save if there is a change: + if (oldBalance == null || + balance[currency]!.currentBalance != oldBalance.currentBalance || + balance[currency]!.receivableBalance != oldBalance.receivableBalance) { + await save(); } - await save(); } Future _updateRep() async { @@ -394,11 +432,19 @@ abstract class NanoWalletBase } Future regenerateAddress() async { - final String type = (_derivationType == DerivationType.nano) ? "standard" : "hd"; - _privateKey = - await NanoUtil.uniSeedToPrivate(_hexSeed!, this.walletAddresses.account!.id, type); - _publicAddress = - await NanoUtil.uniSeedToAddress(_hexSeed!, this.walletAddresses.account!.id, type); + final NanoDerivationType type = (_derivationType == DerivationType.nano) + ? NanoDerivationType.STANDARD + : NanoDerivationType.HD; + _privateKey = await NanoDerivations.universalSeedToPrivate( + _hexSeed!, + index: this.walletAddresses.account!.id, + type: type, + ); + _publicAddress = await NanoDerivations.universalSeedToAddress( + _hexSeed!, + index: this.walletAddresses.account!.id, + type: type, + ); this.walletInfo.address = _publicAddress!; this.walletAddresses.address = _publicAddress!; diff --git a/cw_nano/lib/nano_wallet_service.dart b/cw_nano/lib/nano_wallet_service.dart index 2f183d1cc..7a66f8c9f 100644 --- a/cw_nano/lib/nano_wallet_service.dart +++ b/cw_nano/lib/nano_wallet_service.dart @@ -6,12 +6,12 @@ import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cw_nano/nano_mnemonic.dart' as nm; -import 'package:cw_nano/nano_util.dart'; import 'package:cw_nano/nano_wallet.dart'; import 'package:cw_nano/nano_wallet_creation_credentials.dart'; import 'package:hive/hive.dart'; import 'package:bip39/bip39.dart' as bip39; import 'package:nanodart/nanodart.dart'; +import 'package:nanoutil/nanoutil.dart'; class NanoWalletService extends WalletService { @@ -30,7 +30,7 @@ class NanoWalletService extends WalletService updateTransactions(Object wallet) async { + Future updateTransactions(Object wallet) async { return (wallet as NanoWallet).updateTransactions(); } @@ -189,116 +189,10 @@ class CWNano extends Nano { } class CWNanoUtil extends NanoUtil { - // standard: - @override - String seedToPrivate(String seed, int index) { - return ND.NanoKeys.seedToPrivate(seed, index); - } - - @override - String seedToAddress(String seed, int index) { - return ND.NanoAccounts.createAccount( - ND.NanoAccountType.NANO, privateKeyToPublic(seedToPrivate(seed, index))); - } - - @override - String seedToMnemonic(String seed) { - return NanoMnemomics.seedToMnemonic(seed).join(" "); - } - - @override - Future mnemonicToSeed(String mnemonic) async { - return NanoMnemomics.mnemonicListToSeed(mnemonic.split(' ')); - } - - @override - String privateKeyToPublic(String privateKey) { - // return NanoHelpers.byteToHex(Ed25519Blake2b.getPubkey(NanoHelpers.hexToBytes(privateKey))!); - return ND.NanoKeys.createPublicKey(privateKey); - } - - @override - String addressToPublicKey(String publicAddress) { - return ND.NanoAccounts.extractPublicKey(publicAddress); - } - - // universal: - @override - String privateKeyToAddress(String privateKey) { - return ND.NanoAccounts.createAccount(ND.NanoAccountType.NANO, privateKeyToPublic(privateKey)); - } - - @override - String publicKeyToAddress(String publicKey) { - return ND.NanoAccounts.createAccount(ND.NanoAccountType.NANO, publicKey); - } - - // standard + hd: - @override - bool isValidSeed(String seed) { - // Ensure seed is 64 or 128 characters long - if (seed.length != 64 && seed.length != 128) { - return false; - } - // Ensure seed only contains hex characters, 0-9;A-F - return ND.NanoHelpers.isHexString(seed); - } - - // hd: - @override - Future hdMnemonicListToSeed(List words) async { - // if (words.length != 24) { - // throw Exception('Expected a 24-word list, got a ${words.length} list'); - // } - final Uint8List salt = Uint8List.fromList(utf8.encode('mnemonic')); - final Pbkdf2 hasher = Pbkdf2(iterations: 2048); - final String seed = await hasher.sha512(words.join(' '), salt); - return seed; - } - - @override - Future hdSeedToPrivate(String seed, int index) async { - List seedBytes = hex.decode(seed); - KeyData data = await ED25519_HD_KEY.derivePath("m/44'/165'/$index'", seedBytes); - return hex.encode(data.key); - } - - @override - Future hdSeedToAddress(String seed, int index) async { - return ND.NanoAccounts.createAccount( - ND.NanoAccountType.NANO, privateKeyToPublic(await hdSeedToPrivate(seed, index))); - } - - @override - Future uniSeedToAddress(String seed, int index, String type) { - if (type == "standard") { - return Future.value(seedToAddress(seed, index)); - } else if (type == "hd") { - return hdSeedToAddress(seed, index); - } else { - throw Exception('Unknown seed type'); - } - } - - @override - Future uniSeedToPrivate(String seed, int index, String type) { - if (type == "standard") { - return Future.value(seedToPrivate(seed, index)); - } else if (type == "hd") { - return hdSeedToPrivate(seed, index); - } else { - throw Exception('Unknown seed type'); - } - } @override bool isValidBip39Seed(String seed) { - // Ensure seed is 128 characters long - if (seed.length != 128) { - return false; - } - // Ensure seed only contains hex characters, 0-9;A-F - return ND.NanoHelpers.isHexString(seed); + return NanoDerivations.isValidBip39Seed(seed); } // number util: @@ -309,92 +203,20 @@ class CWNanoUtil extends NanoUtil { BigInt rawPerBanano = BigInt.parse("100000000000000000000000000000"); BigInt rawPerXMR = BigInt.parse("1000000000000"); BigInt convertXMRtoNano = BigInt.parse("1000000000000000000"); - // static BigInt convertXMRtoNano = BigInt.parse("1000000000000000000000000000"); - /// Convert raw to ban and return as BigDecimal - /// - /// @param raw 100000000000000000000000000000 - /// @return Decimal value 1.000000000000000000000000000000 - /// - Decimal _getRawAsDecimal(String? raw, BigInt? rawPerCur) { - rawPerCur ??= rawPerNano; - final Decimal amount = Decimal.parse(raw.toString()); - final Decimal result = (amount / Decimal.parse(rawPerCur.toString())).toDecimal(); - return result; - } - - @override - String getRawAsDecimalString(String? raw, BigInt? rawPerCur) { - final Decimal result = _getRawAsDecimal(raw, rawPerCur); - return result.toString(); - } - - @override - String truncateDecimal(Decimal input, {int digits = maxDecimalDigits}) { - Decimal bigger = input.shift(digits); - bigger = bigger.floor(); // chop off the decimal: 1.059 -> 1.05 - bigger = bigger.shift(-digits); - return bigger.toString(); - } - - /// Return raw as a NANO amount. - /// - /// @param raw 100000000000000000000000000000 - /// @returns 1 - /// @override String getRawAsUsableString(String? raw, BigInt rawPerCur) { - final String res = - truncateDecimal(_getRawAsDecimal(raw, rawPerCur), digits: maxDecimalDigits + 9); - - if (raw == null || raw == "0" || raw == "00000000000000000000000000000000") { - return "0"; - } - - if (!res.contains(".")) { - return res; - } - - final String numAmount = res.split(".")[0]; - String decAmount = res.split(".")[1]; - - // truncate: - if (decAmount.length > maxDecimalDigits) { - decAmount = decAmount.substring(0, maxDecimalDigits); - // remove trailing zeros: - decAmount = decAmount.replaceAllMapped(RegExp(r'0+$'), (Match match) => ''); - if (decAmount.isEmpty) { - return numAmount; - } - } - - return "$numAmount.$decAmount"; + return NanoAmounts.getRawAsUsableString(raw, rawPerCur); } @override String getRawAccuracy(String? raw, BigInt rawPerCur) { - final String rawString = getRawAsUsableString(raw, rawPerCur); - final String rawDecimalString = _getRawAsDecimal(raw, rawPerCur).toString(); - - if (raw == null || raw.isEmpty || raw == "0") { - return ""; - } - - if (rawString != rawDecimalString) { - return "~"; - } - return ""; + return NanoAmounts.getRawAccuracy(raw, rawPerCur); } - /// Return readable string amount as raw string - /// @param amount 1.01 - /// @returns 101000000000000000000000000000 - /// @override String getAmountAsRaw(String amount, BigInt rawPerCur) { - final Decimal asDecimal = Decimal.parse(amount); - final Decimal rawDecimal = Decimal.parse(rawPerCur.toString()); - return (asDecimal * rawDecimal).toString(); + return NanoAmounts.getAmountAsRaw(amount, rawPerCur); } @override @@ -411,29 +233,29 @@ class CWNanoUtil extends NanoUtil { if (seedKey != null) { if (seedKey.length == 64) { try { - mnemonic = nanoUtil!.seedToMnemonic(seedKey); + mnemonic = NanoDerivations.standardSeedToMnemonic(seedKey); } catch (e) { print("not a valid 'nano' seed key"); } } if (derivationType == DerivationType.bip39) { - publicAddress = await hdSeedToAddress(seedKey, 0); + publicAddress = await NanoDerivations.hdSeedToAddress(seedKey, index: 0); } else if (derivationType == DerivationType.nano) { - publicAddress = await seedToAddress(seedKey, 0); + publicAddress = await NanoDerivations.standardSeedToAddress(seedKey, index: 0); } } if (derivationType == DerivationType.bip39) { if (mnemonic != null) { - seedKey = await hdMnemonicListToSeed(mnemonic.split(' ')); - publicAddress = await hdSeedToAddress(seedKey, 0); + seedKey = await NanoDerivations.hdMnemonicListToSeed(mnemonic.split(' ')); + publicAddress = await NanoDerivations.hdSeedToAddress(seedKey, index: 0); } } if (derivationType == DerivationType.nano) { if (mnemonic != null) { - seedKey = await mnemonicToSeed(mnemonic); - publicAddress = await seedToAddress(seedKey, 0); + seedKey = await NanoDerivations.standardMnemonicToSeed(mnemonic); + publicAddress = await NanoDerivations.standardSeedToAddress(seedKey, index: 0); } } @@ -461,7 +283,7 @@ class CWNanoUtil extends NanoUtil { return [DerivationType.bip39]; } else if (seedKey?.length == 64) { try { - mnemonic = nanoUtil!.seedToMnemonic(seedKey!); + mnemonic = NanoDerivations.standardSeedToMnemonic(seedKey!); } catch (e) { print("not a valid 'nano' seed key"); } @@ -475,19 +297,19 @@ class CWNanoUtil extends NanoUtil { nanoClient.connect(node); if (mnemonic != null) { - seedKey = await hdMnemonicListToSeed(mnemonic.split(' ')); - publicAddressBip39 = await hdSeedToAddress(seedKey, 0); + seedKey = await NanoDerivations.hdMnemonicListToSeed(mnemonic.split(' ')); + publicAddressBip39 = await NanoDerivations.hdSeedToAddress(seedKey, index: 0); - seedKey = await mnemonicToSeed(mnemonic); - publicAddressStandard = await seedToAddress(seedKey, 0); + seedKey = await NanoDerivations.standardMnemonicToSeed(mnemonic); + publicAddressStandard = await NanoDerivations.standardSeedToAddress(seedKey, index: 0); } else if (seedKey != null) { try { - publicAddressBip39 = await hdSeedToAddress(seedKey, 0); + publicAddressBip39 = await NanoDerivations.hdSeedToAddress(seedKey, index: 0); } catch (e) { return [DerivationType.nano]; } try { - publicAddressStandard = await seedToAddress(seedKey, 0); + publicAddressStandard = await NanoDerivations.standardSeedToAddress(seedKey, index: 0); } catch (e) { return [DerivationType.bip39]; } diff --git a/lib/view_model/dashboard/balance_view_model.dart b/lib/view_model/dashboard/balance_view_model.dart index 5c95ab3ab..e2c0382b0 100644 --- a/lib/view_model/dashboard/balance_view_model.dart +++ b/lib/view_model/dashboard/balance_view_model.dart @@ -125,6 +125,8 @@ abstract class BalanceViewModelBase with Store { case WalletType.haven: case WalletType.ethereum: case WalletType.polygon: + case WalletType.nano: + case WalletType.banano: return S.current.xmr_available_balance; default: return S.current.confirmed; @@ -139,6 +141,9 @@ abstract class BalanceViewModelBase with Store { case WalletType.ethereum: case WalletType.polygon: return S.current.xmr_full_balance; + case WalletType.nano: + case WalletType.banano: + return S.current.receivable_balance; default: return S.current.unconfirmed; } diff --git a/lib/view_model/dashboard/transaction_list_item.dart b/lib/view_model/dashboard/transaction_list_item.dart index bc7f70517..d8c4776b7 100644 --- a/lib/view_model/dashboard/transaction_list_item.dart +++ b/lib/view_model/dashboard/transaction_list_item.dart @@ -101,7 +101,7 @@ class TransactionListItem extends ActionListItem with Keyable { break; case WalletType.nano: amount = calculateFiatAmountRaw( - cryptoAmount: double.parse(nanoUtil!.getRawAsDecimalString( + cryptoAmount: double.parse(nanoUtil!.getRawAsUsableString( nano!.getTransactionAmountRaw(transaction).toString(), nanoUtil!.rawPerNano)), price: price); break; diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart index 4e17866cb..f9ff466a7 100644 --- a/lib/view_model/transaction_details_view_model.dart +++ b/lib/view_model/transaction_details_view_model.dart @@ -247,11 +247,15 @@ abstract class TransactionDetailsViewModelBase with Store { void _addNanoListItems(TransactionInfo tx, DateFormat dateFormat) { final _items = [ StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id), + if (showRecipientAddress && tx.to != null) + StandartListItem(title: S.current.transaction_details_recipient_address, value: tx.to!), + if (showRecipientAddress && tx.from != null) + StandartListItem(title: S.current.transaction_details_source_address, value: tx.from!), + StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()), StandartListItem( title: S.current.transaction_details_date, value: dateFormat.format(tx.date)), - StandartListItem(title: S.current.confirmations, value: (tx.confirmations > 0).toString()), + StandartListItem(title: S.current.confirmed_tx, value: (tx.confirmations > 0).toString()), StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'), - StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()), ]; items.addAll(_items); diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 95871b904..18035bce5 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -759,5 +759,8 @@ "default_sell_provider": " ﻲﺿﺍﺮﺘﻓﻻﺍ ﻊﻴﺒﻟﺍ ﺩﻭﺰﻣ", "select_sell_provider_notice": ".ﻖﻴﺒﻄﺘﻟﺍ ﺕﺍﺩﺍﺪﻋﺇ ﻲﻓ ﻚﺑ ﺹﺎﺨﻟﺍ ﻲﺿﺍﺮﺘﻓﻻﺍ ﻊﻴﺒﻟﺍ ﺩﻭﺰﻣ ﻦﻴﻴﻌﺗ ﻖﻳﺮﻃ ﻦﻋ ﺔﺷﺎﺸﻟﺍ ﻩﺬﻫ ﻲﻄﺨﺗ", "custom_drag": "مخصص (عقد وسحب)", - "switchToEVMCompatibleWallet": " (Ethereum، Polygon) ﻯﺮﺧﺃ ﺓﺮﻣ ﺔﻟﻭﺎﺤﻤﻟﺍﻭ EVM ﻊﻣ ﺔﻘﻓﺍﻮﺘﻣ ﺔﻈﻔﺤﻣ ﻰﻟﺇ ﻞﻳﺪﺒﺘﻟﺍ ﻰﺟﺮﻳ" -} + "switchToEVMCompatibleWallet": " (Ethereum، Polygon) ﻯﺮﺧﺃ ﺓﺮﻣ ﺔﻟﻭﺎﺤﻤﻟﺍﻭ EVM ﻊﻣ ﺔﻘﻓﺍﻮﺘﻣ ﺔﻈﻔﺤﻣ ﻰﻟﺇ ﻞﻳﺪﺒﺘﻟﺍ ﻰﺟﺮﻳ", + "receivable_balance": "التوازن القادم", + "confirmed_tx": "مؤكد", + "transaction_details_source_address": "عنوان المصدر" +} \ No newline at end of file diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 197dbe552..e2c88a45d 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -755,5 +755,8 @@ "default_sell_provider": "Доставчик за продажба по подразбиране", "select_sell_provider_notice": "Изберете доставчик на продажба по-горе. Можете да пропуснете този екран, като зададете своя доставчик на продажба по подразбиране в настройките на приложението.", "custom_drag": "Персонализиране (задръжте и плъзнете)", - "switchToEVMCompatibleWallet": "Моля, превключете към портфейл, съвместим с EVM, и опитайте отново (Ethereum, Polygon)" -} + "switchToEVMCompatibleWallet": "Моля, превключете към портфейл, съвместим с EVM, и опитайте отново (Ethereum, Polygon)", + "receivable_balance": "Баланс за вземания", + "confirmed_tx": "Потвърдено", + "transaction_details_source_address": "Адрес на източника" +} \ No newline at end of file diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index bed4e3193..7d2e8a86f 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -755,5 +755,8 @@ "default_sell_provider": "Výchozí poskytovatel prodeje", "select_sell_provider_notice": "Výše vyberte poskytovatele prodeje. Tuto obrazovku můžete přeskočit nastavením výchozího poskytovatele prodeje v nastavení aplikace.", "custom_drag": "Custom (Hold and Drag)", - "switchToEVMCompatibleWallet": "Přepněte na peněženku kompatibilní s EVM a zkuste to znovu (Ethereum, Polygon)" -} + "switchToEVMCompatibleWallet": "Přepněte na peněženku kompatibilní s EVM a zkuste to znovu (Ethereum, Polygon)", + "receivable_balance": "Zůstatek pohledávek", + "confirmed_tx": "Potvrzeno", + "transaction_details_source_address": "Zdrojová adresa" +} \ No newline at end of file diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 1d63bffc6..b151cbf6a 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -763,5 +763,8 @@ "default_sell_provider": "Standard-Verkaufsanbieter", "select_sell_provider_notice": "Wählen Sie oben einen Verkaufsanbieter aus. Sie können diesen Bildschirm überspringen, indem Sie in den App-Einstellungen Ihren Standard-Verkaufsanbieter festlegen.", "custom_drag": "Custom (Hold and Drag)", - "switchToEVMCompatibleWallet": "Bitte wechseln Sie zu einem EVM-kompatiblen Wallet und versuchen Sie es erneut (Ethereum, Polygon)" -} + "switchToEVMCompatibleWallet": "Bitte wechseln Sie zu einem EVM-kompatiblen Wallet und versuchen Sie es erneut (Ethereum, Polygon)", + "receivable_balance": "Forderungsbilanz", + "confirmed_tx": "Bestätigt", + "transaction_details_source_address": "Quelladresse" +} \ No newline at end of file diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index e6c95270e..c70d1f7c2 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -764,5 +764,8 @@ "default_sell_provider": "Default Sell Provider", "select_sell_provider_notice": "Select a sell provider above. You can skip this screen by setting your default sell provider in app settings.", "custom_drag": "Custom (Hold and Drag)", - "switchToEVMCompatibleWallet": "Please switch to an EVM compatible wallet and try again (Ethereum, Polygon)" -} + "switchToEVMCompatibleWallet": "Please switch to an EVM compatible wallet and try again (Ethereum, Polygon)", + "receivable_balance": "Receivable Balance", + "confirmed_tx": "Confirmed", + "transaction_details_source_address": "Source address" +} \ No newline at end of file diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 6ee0f509c..9655edf60 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -763,5 +763,8 @@ "default_sell_provider": "Proveedor de venta predeterminado", "select_sell_provider_notice": "Seleccione un proveedor de venta arriba. Puede omitir esta pantalla configurando su proveedor de venta predeterminado en la configuración de la aplicación.", "custom_drag": "Custom (mantenía y arrastre)", - "switchToEVMCompatibleWallet": "Cambie a una billetera compatible con EVM e inténtelo nuevamente (Ethereum, Polygon)" -} + "switchToEVMCompatibleWallet": "Cambie a una billetera compatible con EVM e inténtelo nuevamente (Ethereum, Polygon)", + "receivable_balance": "Saldo de cuentas por cobrar", + "confirmed_tx": "Confirmado", + "transaction_details_source_address": "Dirección de la fuente" +} \ No newline at end of file diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 862f9ae7d..ea5862e80 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -763,5 +763,8 @@ "default_sell_provider": "Fournisseur de vente par défaut", "select_sell_provider_notice": "Sélectionnez un fournisseur de vente ci-dessus. Vous pouvez ignorer cet écran en définissant votre fournisseur de vente par défaut dans les paramètres de l'application.", "custom_drag": "Custom (maintenir et traîner)", - "switchToEVMCompatibleWallet": "Veuillez passer à un portefeuille compatible EVM et réessayer (Ethereum, Polygon)" -} + "switchToEVMCompatibleWallet": "Veuillez passer à un portefeuille compatible EVM et réessayer (Ethereum, Polygon)", + "receivable_balance": "Solde de créances", + "confirmed_tx": "Confirmé", + "transaction_details_source_address": "Adresse source" +} \ No newline at end of file diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 9456085dc..8b1b58c74 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -745,5 +745,8 @@ "default_sell_provider": "Tsohuwar Mai Bayar Siyarwa", "select_sell_provider_notice": "Zaɓi mai bada siyarwa a sama. Kuna iya tsallake wannan allon ta saita mai bada siyar da ku a cikin saitunan app.", "custom_drag": "Al'ada (riƙe da ja)", - "switchToEVMCompatibleWallet": "Da fatan za a canza zuwa walat ɗin EVM mai jituwa kuma a sake gwadawa (Ethereum, Polygon)" -} + "switchToEVMCompatibleWallet": "Da fatan za a canza zuwa walat ɗin EVM mai jituwa kuma a sake gwadawa (Ethereum, Polygon)", + "receivable_balance": "Daidaituwa da daidaituwa", + "confirmed_tx": "Tabbatar", + "transaction_details_source_address": "Adireshin Incord" +} \ No newline at end of file diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index f6a7701ee..a4070cb12 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -763,5 +763,8 @@ "default_sell_provider": "डिफ़ॉल्ट विक्रय प्रदाता", "select_sell_provider_notice": "ऊपर एक विक्रय प्रदाता का चयन करें। आप ऐप सेटिंग में अपना डिफ़ॉल्ट विक्रय प्रदाता सेट करके इस स्क्रीन को छोड़ सकते हैं।", "custom_drag": "कस्टम (पकड़ और खींचें)", - "switchToEVMCompatibleWallet": "कृपया ईवीएम संगत वॉलेट पर स्विच करें और पुनः प्रयास करें (एथेरियम, पॉलीगॉन)" -} + "switchToEVMCompatibleWallet": "कृपया ईवीएम संगत वॉलेट पर स्विच करें और पुनः प्रयास करें (एथेरियम, पॉलीगॉन)", + "receivable_balance": "प्राप्य शेष", + "confirmed_tx": "की पुष्टि", + "transaction_details_source_address": "स्रोत पता" +} \ No newline at end of file diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 3565c89a0..94383e1d2 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -761,5 +761,8 @@ "default_sell_provider": "Zadani dobavljač prodaje", "select_sell_provider_notice": "Gore odaberite pružatelja usluga prodaje. Ovaj zaslon možete preskočiti postavljanjem zadanog pružatelja usluga prodaje u postavkama aplikacije.", "custom_drag": "Prilagođeni (držite i povucite)", - "switchToEVMCompatibleWallet": "Prijeđite na novčanik kompatibilan s EVM-om i pokušajte ponovno (Ethereum, Polygon)" -} + "switchToEVMCompatibleWallet": "Prijeđite na novčanik kompatibilan s EVM-om i pokušajte ponovno (Ethereum, Polygon)", + "receivable_balance": "Stanje potraživanja", + "confirmed_tx": "Potvrđen", + "transaction_details_source_address": "Adresa izvora" +} \ No newline at end of file diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 6744f0a38..86997c8aa 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -751,5 +751,8 @@ "default_sell_provider": "Penyedia Penjualan Default", "select_sell_provider_notice": "Pilih penyedia jual di atas. Anda dapat melewati layar ini dengan mengatur penyedia penjualan default Anda di pengaturan aplikasi.", "custom_drag": "Khusus (tahan dan seret)", - "switchToEVMCompatibleWallet": "Silakan beralih ke dompet yang kompatibel dengan EVM dan coba lagi (Ethereum, Polygon)" -} + "switchToEVMCompatibleWallet": "Silakan beralih ke dompet yang kompatibel dengan EVM dan coba lagi (Ethereum, Polygon)", + "receivable_balance": "Saldo piutang", + "confirmed_tx": "Dikonfirmasi", + "transaction_details_source_address": "Alamat sumber" +} \ No newline at end of file diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index bb909a7e0..257139d54 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -763,5 +763,8 @@ "default_sell_provider": "Fornitore di vendita predefinito", "select_sell_provider_notice": "Seleziona un fornitore di vendita sopra. Puoi saltare questa schermata impostando il tuo fornitore di vendita predefinito nelle impostazioni dell'app.", "custom_drag": "Custom (Hold and Drag)", - "switchToEVMCompatibleWallet": "Passa a un portafoglio compatibile con EVM e riprova (Ethereum, Polygon)" -} + "switchToEVMCompatibleWallet": "Passa a un portafoglio compatibile con EVM e riprova (Ethereum, Polygon)", + "receivable_balance": "Bilanciamento creditizio", + "confirmed_tx": "Confermato", + "transaction_details_source_address": "Indirizzo di partenza" +} \ No newline at end of file diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 2152a3dcc..3c648f1ad 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -763,5 +763,8 @@ "default_sell_provider": "デフォルトの販売プロバイダー", "select_sell_provider_notice": "上記の販売プロバイダーを選択してください。アプリ設定でデフォルトの販売プロバイダーを設定することで、この画面をスキップできます。", "custom_drag": "カスタム(ホールドとドラッグ)", - "switchToEVMCompatibleWallet": "EVM 互換のウォレットに切り替えて再試行してください (イーサリアム、ポリゴン)" -} + "switchToEVMCompatibleWallet": "EVM 互換のウォレットに切り替えて再試行してください (イーサリアム、ポリゴン)", + "receivable_balance": "売掛金残高", + "confirmed_tx": "確認済み", + "transaction_details_source_address": "ソースアドレス" +} \ No newline at end of file diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 5ad5482ed..1903ff1db 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -761,5 +761,8 @@ "default_sell_provider": "기본 판매 공급자", "select_sell_provider_notice": "위에서 판매 공급자를 선택하세요. 앱 설정에서 기본 판매 공급자를 설정하면 이 화면을 건너뛸 수 있습니다.", "custom_drag": "사용자 정의 (홀드 앤 드래그)", - "switchToEVMCompatibleWallet": "EVM 호환 지갑으로 전환 후 다시 시도해 주세요. (이더리움, 폴리곤)" -} + "switchToEVMCompatibleWallet": "EVM 호환 지갑으로 전환 후 다시 시도해 주세요. (이더리움, 폴리곤)", + "receivable_balance": "채권 잔액", + "confirmed_tx": "확인", + "transaction_details_source_address": "소스 주소" +} \ No newline at end of file diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 73f4e5dcd..8933afeff 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -761,5 +761,8 @@ "default_sell_provider": "ပုံသေရောင်းချပေးသူ", "select_sell_provider_notice": "အထက်ဖော်ပြပါ အရောင်းဝန်ဆောင်မှုပေးသူကို ရွေးပါ။ အက်ပ်ဆက်တင်များတွင် သင်၏မူလရောင်းချပေးသူကို သတ်မှတ်ခြင်းဖြင့် ဤစခရင်ကို ကျော်နိုင်သည်။", "custom_drag": "စိတ်ကြိုက် (Drag)", - "switchToEVMCompatibleWallet": "ကျေးဇူးပြု၍ EVM တွဲဖက်သုံးနိုင်သော ပိုက်ဆံအိတ်သို့ ပြောင်းပြီး ထပ်စမ်းကြည့်ပါ (Ethereum၊ Polygon)" -} + "switchToEVMCompatibleWallet": "ကျေးဇူးပြု၍ EVM တွဲဖက်သုံးနိုင်သော ပိုက်ဆံအိတ်သို့ ပြောင်းပြီး ထပ်စမ်းကြည့်ပါ (Ethereum၊ Polygon)", + "receivable_balance": "လက်ကျန်ငွေ", + "confirmed_tx": "အတည်ပြုသည်", + "transaction_details_source_address": "အရင်းအမြစ်လိပ်စာ" +} \ No newline at end of file diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 6cf41e0b9..b05e29efd 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -763,5 +763,8 @@ "default_sell_provider": "Standaard verkoopaanbieder", "select_sell_provider_notice": "Selecteer hierboven een verkoopaanbieder. U kunt dit scherm overslaan door uw standaardverkoopprovider in te stellen in de app-instellingen.", "custom_drag": "Custom (vasthouden en slepen)", - "switchToEVMCompatibleWallet": "Schakel over naar een EVM-compatibele portemonnee en probeer het opnieuw (Ethereum, Polygon)" -} + "switchToEVMCompatibleWallet": "Schakel over naar een EVM-compatibele portemonnee en probeer het opnieuw (Ethereum, Polygon)", + "receivable_balance": "Het saldo", + "confirmed_tx": "Bevestigd", + "transaction_details_source_address": "Bron adres" +} \ No newline at end of file diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 9d3fb3f93..a4f63255a 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -763,5 +763,8 @@ "default_sell_provider": "Domyślny dostawca sprzedaży", "select_sell_provider_notice": "Wybierz dostawcę sprzedaży powyżej. Możesz pominąć ten ekran, ustawiając domyślnego dostawcę sprzedaży w ustawieniach aplikacji.", "custom_drag": "Niestandardowe (trzymaj i przeciągnij)", - "switchToEVMCompatibleWallet": "Przejdź na portfel zgodny z EVM i spróbuj ponownie (Ethereum, Polygon)" -} + "switchToEVMCompatibleWallet": "Przejdź na portfel zgodny z EVM i spróbuj ponownie (Ethereum, Polygon)", + "receivable_balance": "Saldo należności", + "confirmed_tx": "Potwierdzony", + "transaction_details_source_address": "Adres źródłowy" +} \ No newline at end of file diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 5cf1fd370..f30ea9635 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -762,5 +762,8 @@ "default_sell_provider": "Provedor de venda padrão", "select_sell_provider_notice": "Selecione um fornecedor de venda acima. Você pode pular esta tela definindo seu provedor de venda padrão nas configurações do aplicativo.", "custom_drag": "Personalizado (segure e arraste)", - "switchToEVMCompatibleWallet": "Mude para uma carteira compatível com EVM e tente novamente (Ethereum, Polygon)" -} + "switchToEVMCompatibleWallet": "Mude para uma carteira compatível com EVM e tente novamente (Ethereum, Polygon)", + "receivable_balance": "Saldo a receber", + "confirmed_tx": "Confirmado", + "transaction_details_source_address": "Endereço de Origem" +} \ No newline at end of file diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index c81c0bd82..47c82263b 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -763,5 +763,8 @@ "default_sell_provider": "Поставщик продаж по умолчанию", "select_sell_provider_notice": "Выберите поставщика услуг продажи выше. Вы можете пропустить этот экран, установив поставщика услуг продаж по умолчанию в настройках приложения.", "custom_drag": "Пользователь (удерживайте и перетаскивайте)", - "switchToEVMCompatibleWallet": "Пожалуйста, переключитесь на кошелек, совместимый с EVM, и повторите попытку (Ethereum, Polygon)." -} + "switchToEVMCompatibleWallet": "Пожалуйста, переключитесь на кошелек, совместимый с EVM, и повторите попытку (Ethereum, Polygon).", + "receivable_balance": "Баланс дебиторской задолженности", + "confirmed_tx": "Подтвержденный", + "transaction_details_source_address": "Адрес источника" +} \ No newline at end of file diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 0965d3979..a4abe9b14 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -761,5 +761,8 @@ "default_sell_provider": "ผู้ให้บริการการขายเริ่มต้น", "select_sell_provider_notice": "เลือกผู้ให้บริการการขายด้านบน คุณสามารถข้ามหน้าจอนี้ได้โดยการตั้งค่าผู้ให้บริการการขายเริ่มต้นในการตั้งค่าแอป", "custom_drag": "กำหนดเอง (ค้างและลาก)", - "switchToEVMCompatibleWallet": "โปรดเปลี่ยนไปใช้กระเป๋าเงินที่รองรับ EVM แล้วลองอีกครั้ง (Ethereum, Polygon)" -} + "switchToEVMCompatibleWallet": "โปรดเปลี่ยนไปใช้กระเป๋าเงินที่รองรับ EVM แล้วลองอีกครั้ง (Ethereum, Polygon)", + "receivable_balance": "ยอดลูกหนี้", + "confirmed_tx": "ซึ่งยืนยันแล้ว", + "transaction_details_source_address": "ที่อยู่แหล่งกำเนิด" +} \ No newline at end of file diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index f9bbb5dea..e2c29c742 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -757,5 +757,8 @@ "default_sell_provider": "Default na Sell Provider", "select_sell_provider_notice": "Pumili ng provider ng nagbebenta sa itaas. Maaari mong laktawan ang screen na ito sa pamamagitan ng pagtatakda ng iyong default na sell provider sa mga setting ng app.", "custom_drag": "Pasadyang (hawakan at i -drag)", - "switchToEVMCompatibleWallet": "Mangyaring lumipat sa isang EVM compatible na wallet at subukang muli (Ethereum, Polygon)" -} + "switchToEVMCompatibleWallet": "Mangyaring lumipat sa isang EVM compatible na wallet at subukang muli (Ethereum, Polygon)", + "receivable_balance": "Natatanggap na balanse", + "confirmed_tx": "Nakumpirma", + "transaction_details_source_address": "SOURCE ADDRESS" +} \ No newline at end of file diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 6fdb8bb99..91d130d60 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -761,5 +761,8 @@ "default_sell_provider": "Varsayılan Satış Sağlayıcısı", "select_sell_provider_notice": "Yukarıdan bir satış sağlayıcısı seçin. Uygulama ayarlarında varsayılan satış sağlayıcınızı ayarlayarak bu ekranı atlayabilirsiniz.", "custom_drag": "Özel (Bekle ve Sürükle)", - "switchToEVMCompatibleWallet": "Lütfen EVM uyumlu bir cüzdana geçin ve tekrar deneyin (Ethereum, Polygon)" -} + "switchToEVMCompatibleWallet": "Lütfen EVM uyumlu bir cüzdana geçin ve tekrar deneyin (Ethereum, Polygon)", + "receivable_balance": "Alacak bakiyesi", + "confirmed_tx": "Onaylanmış", + "transaction_details_source_address": "Kaynak adresi" +} \ No newline at end of file diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 721a3970d..5cb5a6754 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -763,5 +763,8 @@ "default_sell_provider": "Постачальник продажу за замовчуванням", "select_sell_provider_notice": "Виберіть вище постачальника послуг продажу. Ви можете пропустити цей екран, встановивши постачальника послуг продажу за умовчанням у налаштуваннях програми.", "custom_drag": "На замовлення (утримуйте та перетягується)", - "switchToEVMCompatibleWallet": "Перейдіть на гаманець, сумісний з EVM, і повторіть спробу (Ethereum, Polygon)" -} + "switchToEVMCompatibleWallet": "Перейдіть на гаманець, сумісний з EVM, і повторіть спробу (Ethereum, Polygon)", + "receivable_balance": "Баланс дебіторської заборгованості", + "confirmed_tx": "Підтверджений", + "transaction_details_source_address": "Адреса джерела" +} \ No newline at end of file diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index e4a9f4590..d367d11cb 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -755,5 +755,8 @@ "default_sell_provider": " ﮦﺪﻨﻨﮐ ﻢﮨﺍﺮﻓ ﻞﯿﺳ ﭧﻟﺎﻔﯾﮈ", "select_sell_provider_notice": "۔ﮟﯿﮨ ﮯﺘﮑﺳ ﮌﻮﮭﭼ ﻮﮐ ﻦﯾﺮﮑﺳﺍ ﺱﺍ ﺮﮐ ﮮﺩ ﺐﯿﺗﺮﺗ ﻮﮐ ﮦﺪﻨﻨﮐ ﻢﮨﺍﺮﻓ ﻞﯿﺳ ﭧﻟﺎﻔﯾﮈ ﮯﻨﭘﺍ ﮟﯿﻣ ﺕﺎﺒ", "custom_drag": "کسٹم (ہولڈ اینڈ ڈریگ)", - "switchToEVMCompatibleWallet": "(Ethereum, Polygon) ﮟﯾﺮﮐ ﺶﺷﻮﮐ ﮦﺭﺎﺑﻭﺩ ﺭﻭﺍ ﮟﯾﺮﮐ ﭻﺋﻮﺳ ﺮﭘ ﭧﯿﻟﺍﻭ ﮯﻟﺍﻭ ﮯﻨﮭﮐﺭ ﺖﻘﺑﺎﻄﻣ " -} + "switchToEVMCompatibleWallet": "(Ethereum, Polygon) ﮟﯾﺮﮐ ﺶﺷﻮﮐ ﮦﺭﺎﺑﻭﺩ ﺭﻭﺍ ﮟﯾﺮﮐ ﭻﺋﻮﺳ ﺮﭘ ﭧﯿﻟﺍﻭ ﮯﻟﺍﻭ ﮯﻨﮭﮐﺭ ﺖﻘﺑﺎﻄﻣ ", + "receivable_balance": "قابل وصول توازن", + "confirmed_tx": "تصدیق", + "transaction_details_source_address": "ماخذ ایڈریس" +} \ No newline at end of file diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 964307e6b..332297b4b 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -757,5 +757,8 @@ "default_sell_provider": "Aiyipada Olupese Tita", "select_sell_provider_notice": "Yan olupese ti o ta loke. O le foju iboju yii nipa tito olupese iṣẹ tita aiyipada rẹ ni awọn eto app.", "custom_drag": "Aṣa (mu ati fa)", - "switchToEVMCompatibleWallet": "Jọwọ yipada si apamọwọ ibaramu EVM ki o tun gbiyanju lẹẹkansi (Ethereum, Polygon)" -} + "switchToEVMCompatibleWallet": "Jọwọ yipada si apamọwọ ibaramu EVM ki o tun gbiyanju lẹẹkansi (Ethereum, Polygon)", + "receivable_balance": "Iwontunws.funfun ti o gba", + "confirmed_tx": "Jẹrisi", + "transaction_details_source_address": "Adirẹsi orisun" +} \ No newline at end of file diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 43e8c6ce1..76afa40d1 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -762,5 +762,8 @@ "default_sell_provider": "默认销售提供商", "select_sell_provider_notice": "选择上面的销售提供商。您可以通过在应用程序设置中设置默认销售提供商来跳过此屏幕。", "custom_drag": "定制(保持和拖动)", - "switchToEVMCompatibleWallet": "请切换到 EVM 兼容钱包并重试(以太坊、Polygon)" -} + "switchToEVMCompatibleWallet": "请切换到 EVM 兼容钱包并重试(以太坊、Polygon)", + "receivable_balance": "应收余额", + "confirmed_tx": "确认的", + "transaction_details_source_address": "源地址" +} \ No newline at end of file diff --git a/tool/configure.dart b/tool/configure.dart index 67732faa9..3d1bccb12 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -759,7 +759,7 @@ import 'package:convert/convert.dart'; import "package:ed25519_hd_key/ed25519_hd_key.dart"; import 'package:libcrypto/libcrypto.dart'; import 'package:nanodart/nanodart.dart' as ND; -import 'package:decimal/decimal.dart'; +import 'package:nanoutil/nanoutil.dart'; """; const nanoCwPart = "part 'cw_nano.dart';"; const nanoContent = """ @@ -795,7 +795,7 @@ abstract class Nano { Map getKeys(Object wallet); Object createNanoTransactionCredentials(List outputs); Future changeRep(Object wallet, String address); - Future updateTransactions(Object wallet); + Future updateTransactions(Object wallet); BigInt getTransactionAmountRaw(TransactionInfo transactionInfo); String getRepresentative(Object wallet); } @@ -810,20 +810,6 @@ abstract class NanoAccountList { } abstract class NanoUtil { - String seedToPrivate(String seed, int index); - String seedToAddress(String seed, int index); - String seedToMnemonic(String seed); - Future mnemonicToSeed(String mnemonic); - String privateKeyToPublic(String privateKey); - String addressToPublicKey(String publicAddress); - String privateKeyToAddress(String privateKey); - String publicKeyToAddress(String publicKey); - bool isValidSeed(String seed); - Future hdMnemonicListToSeed(List words); - Future hdSeedToPrivate(String seed, int index); - Future hdSeedToAddress(String seed, int index); - Future uniSeedToAddress(String seed, int index, String type); - Future uniSeedToPrivate(String seed, int index, String type); bool isValidBip39Seed(String seed); static const int maxDecimalDigits = 6; // Max digits after decimal BigInt rawPerNano = BigInt.parse("1000000000000000000000000000000"); @@ -831,7 +817,6 @@ abstract class NanoUtil { BigInt rawPerBanano = BigInt.parse("100000000000000000000000000000"); BigInt rawPerXMR = BigInt.parse("1000000000000"); BigInt convertXMRtoNano = BigInt.parse("1000000000000000000"); - String getRawAsDecimalString(String? raw, BigInt? rawPerCur); String getRawAsUsableString(String? raw, BigInt rawPerCur); String getRawAccuracy(String? raw, BigInt rawPerCur); String getAmountAsRaw(String amount, BigInt rawPerCur);