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
This commit is contained in:
Matthew Fosse 2024-01-11 15:00:41 -08:00 committed by GitHub
parent 8d973cf919
commit fe2e26f146
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 306 additions and 518 deletions

View file

@ -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 source ./app_env.sh cakewallet
./app_config.sh ./app_config.sh
cd ../.. && flutter pub get cd ../.. && flutter pub get

View file

@ -15,6 +15,7 @@ abstract class TransactionInfo extends Object with Keyable {
String? feeFormatted(); String? feeFormatted();
void changeFiatAmount(String amount); void changeFiatAmount(String amount);
String? to; String? to;
String? from;
@override @override
dynamic get keyIndex => id; dynamic get keyIndex => id;

View file

@ -1,20 +1,19 @@
import 'package:cw_core/balance.dart'; import 'package:cw_core/balance.dart';
import 'package:cw_nano/nano_util.dart'; import 'package:nanoutil/nanoutil.dart';
class BananoBalance extends Balance { class BananoBalance extends Balance {
final BigInt currentBalance; final BigInt currentBalance;
final BigInt receivableBalance; final BigInt receivableBalance;
BananoBalance({required this.currentBalance, required this.receivableBalance}) : super(0, 0) { BananoBalance({required this.currentBalance, required this.receivableBalance}) : super(0, 0);
}
@override @override
String get formattedAvailableBalance { String get formattedAvailableBalance {
return NanoUtil.getRawAsUsableString(currentBalance.toString(), NanoUtil.rawPerBanano); return NanoAmounts.getRawAsUsableString(currentBalance.toString(), NanoAmounts.rawPerBanano);
} }
@override @override
String get formattedAdditionalBalance { String get formattedAdditionalBalance {
return NanoUtil.getRawAsUsableString(receivableBalance.toString(), NanoUtil.rawPerBanano); return NanoAmounts.getRawAsUsableString(receivableBalance.toString(), NanoAmounts.rawPerBanano);
} }
} }

View file

@ -1,34 +1,35 @@
import 'package:cw_core/balance.dart'; import 'package:cw_core/balance.dart';
import 'package:cw_nano/nano_util.dart'; import 'package:nanoutil/nanoutil.dart';
BigInt stringAmountToBigInt(String amount) { BigInt stringAmountToBigInt(String amount) {
return BigInt.parse(NanoUtil.getAmountAsRaw(amount, NanoUtil.rawPerNano)); return BigInt.parse(NanoAmounts.getAmountAsRaw(amount, NanoAmounts.rawPerNano));
} }
class NanoBalance extends Balance { class NanoBalance extends Balance {
final BigInt currentBalance; final BigInt currentBalance;
final BigInt receivableBalance; final BigInt receivableBalance;
late String formattedCurrentBalance;
late String formattedReceivableBalance;
NanoBalance({required this.currentBalance, required this.receivableBalance}) : super(0, 0) { NanoBalance({required this.currentBalance, required this.receivableBalance}) : super(0, 0);
this.formattedCurrentBalance = "";
this.formattedReceivableBalance = "";
}
NanoBalance.fromString( NanoBalance.fromFormattedString(
{required this.formattedCurrentBalance, required this.formattedReceivableBalance}) {required String formattedCurrentBalance, required String formattedReceivableBalance})
: currentBalance = stringAmountToBigInt(formattedCurrentBalance), : currentBalance = stringAmountToBigInt(formattedCurrentBalance),
receivableBalance = stringAmountToBigInt(formattedReceivableBalance), receivableBalance = stringAmountToBigInt(formattedReceivableBalance),
super(0, 0); super(0, 0);
NanoBalance.fromRawString(
{required String currentBalance, required String receivableBalance})
: currentBalance = BigInt.parse(currentBalance),
receivableBalance = BigInt.parse(receivableBalance),
super(0, 0);
@override @override
String get formattedAvailableBalance { String get formattedAvailableBalance {
return NanoUtil.getRawAsUsableString(currentBalance.toString(), NanoUtil.rawPerNano); return NanoAmounts.getRawAsUsableString(currentBalance.toString(), NanoAmounts.rawPerNano);
} }
@override @override
String get formattedAdditionalBalance { String get formattedAdditionalBalance {
return NanoUtil.getRawAsUsableString(receivableBalance.toString(), NanoUtil.rawPerNano); return NanoAmounts.getRawAsUsableString(receivableBalance.toString(), NanoAmounts.rawPerNano);
} }
} }

View file

@ -4,10 +4,10 @@ import 'dart:convert';
import 'package:cw_core/nano_account_info_response.dart'; import 'package:cw_core/nano_account_info_response.dart';
import 'package:cw_nano/nano_balance.dart'; import 'package:cw_nano/nano_balance.dart';
import 'package:cw_nano/nano_transaction_model.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:http/http.dart' as http;
import 'package:nanodart/nanodart.dart'; import 'package:nanodart/nanodart.dart';
import 'package:cw_core/node.dart'; import 'package:cw_core/node.dart';
import 'package:nanoutil/nanoutil.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
class NanoClient { class NanoClient {
@ -61,6 +61,13 @@ class NanoClient {
), ),
); );
final data = await jsonDecode(response.body); 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 currentBalance = data["balance"] as String;
final String receivableBalance = data["receivable"] as String; final String receivableBalance = data["receivable"] as String;
final BigInt cur = BigInt.parse(currentBalance); final BigInt cur = BigInt.parse(currentBalance);
@ -203,7 +210,7 @@ class NanoClient {
String? previousHash, String? previousHash,
}) async { }) async {
// our address: // our address:
final String publicAddress = NanoUtil.privateKeyToAddress(privateKey); final String publicAddress = NanoDerivations.privateKeyToAddress(privateKey);
// first get the current account balance: // first get the current account balance:
if (balanceAfterTx == null) { if (balanceAfterTx == null) {

View file

@ -1,7 +1,7 @@
import 'package:cw_core/format_amount.dart'; import 'package:cw_core/format_amount.dart';
import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/transaction_info.dart';
import 'package:cw_nano/nano_util.dart'; import 'package:nanoutil/nanoutil.dart';
class NanoTransactionInfo extends TransactionInfo { class NanoTransactionInfo extends TransactionInfo {
NanoTransactionInfo({ NanoTransactionInfo({
@ -13,6 +13,8 @@ class NanoTransactionInfo extends TransactionInfo {
required this.confirmed, required this.confirmed,
required this.date, required this.date,
required this.confirmations, required this.confirmations,
required this.to,
required this.from,
}) : this.amount = amountRaw.toInt(); }) : this.amount = amountRaw.toInt();
final String id; final String id;
@ -24,14 +26,17 @@ class NanoTransactionInfo extends TransactionInfo {
final bool confirmed; final bool confirmed;
final int confirmations; final int confirmations;
final String tokenSymbol; final String tokenSymbol;
final String? to;
final String? from;
String? _fiatAmount; String? _fiatAmount;
bool get isPending => !this.confirmed; bool get isPending => !this.confirmed;
@override @override
String amountFormatted() { String amountFormatted() {
final String amt = NanoUtil.getRawAsUsableString(amountRaw.toString(), NanoUtil.rawPerNano); final String amt =
final String acc = NanoUtil.getRawAccuracy(amountRaw.toString(), NanoUtil.rawPerNano); NanoAmounts.getRawAsUsableString(amountRaw.toString(), NanoAmounts.rawPerNano);
final String acc = NanoAmounts.getRawAccuracy(amountRaw.toString(), NanoAmounts.rawPerNano);
return "$acc$amt $tokenSymbol"; return "$acc$amt $tokenSymbol";
} }
@ -54,6 +59,8 @@ class NanoTransactionInfo extends TransactionInfo {
confirmed: data['confirmed'] as bool, confirmed: data['confirmed'] as bool,
confirmations: data['confirmations'] as int, confirmations: data['confirmations'] as int,
tokenSymbol: data['tokenSymbol'] as String, 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, 'confirmed': confirmed,
'confirmations': confirmations, 'confirmations': confirmations,
'tokenSymbol': tokenSymbol, 'tokenSymbol': tokenSymbol,
'to': to,
'from': from,
}; };
} }

View file

@ -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<String> 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<String> hdMnemonicListToSeed(List<String> 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<String> hdSeedToPrivate(String seed, int index) async {
List<int> seedBytes = hex.decode(seed);
KeyData data = await ED25519_HD_KEY.derivePath("m/44'/165'/$index'", seedBytes);
return hex.encode(data.key);
}
static Future<String> hdSeedToAddress(String seed, int index) async {
return NanoAccounts.createAccount(
NanoAccountType.NANO, privateKeyToPublic(await hdSeedToPrivate(seed, index)));
}
static Future<String> uniSeedToAddress(String seed, int index, String type) {
if (type == "standard") {
return Future<String>.value(seedToAddress(seed, index));
} else if (type == "hd") {
return hdSeedToAddress(seed, index);
} else {
throw Exception('Unknown seed type');
}
}
static Future<String> uniSeedToPrivate(String seed, int index, String type) {
if (type == "standard") {
return Future<String>.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();
}
}

View file

@ -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_credentials.dart';
import 'package:cw_nano/nano_transaction_history.dart'; import 'package:cw_nano/nano_transaction_history.dart';
import 'package:cw_nano/nano_transaction_info.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/nano_wallet_keys.dart';
import 'package:cw_nano/pending_nano_transaction.dart'; import 'package:cw_nano/pending_nano_transaction.dart';
import 'package:mobx/mobx.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:cw_core/wallet_base.dart';
import 'package:nanodart/nanodart.dart'; import 'package:nanodart/nanodart.dart';
import 'package:bip39/bip39.dart' as bip39; import 'package:bip39/bip39.dart' as bip39;
import 'package:nanoutil/nanoutil.dart';
part 'nano_wallet.g.dart'; part 'nano_wallet.g.dart';
@ -83,6 +83,8 @@ abstract class NanoWalletBase
@observable @observable
late ObservableMap<CryptoCurrency, NanoBalance> balance; late ObservableMap<CryptoCurrency, NanoBalance> balance;
static const int POLL_INTERVAL_SECONDS = 10;
// initialize the different forms of private / public key we'll need: // initialize the different forms of private / public key we'll need:
Future<void> init() async { Future<void> init() async {
if (_derivationType == DerivationType.unknown) { if (_derivationType == DerivationType.unknown) {
@ -100,11 +102,21 @@ abstract class NanoWalletBase
if (_derivationType == DerivationType.nano) { if (_derivationType == DerivationType.nano) {
_hexSeed = bip39.mnemonicToEntropy(_mnemonic).toUpperCase(); _hexSeed = bip39.mnemonicToEntropy(_mnemonic).toUpperCase();
} else { } else {
_hexSeed = await NanoUtil.hdMnemonicListToSeed(_mnemonic.split(' ')); _hexSeed = await NanoDerivations.hdMnemonicListToSeed(_mnemonic.split(' '));
} }
} }
_privateKey = await NanoUtil.uniSeedToPrivate(_hexSeed!, 0, type); NanoDerivationType derivationType =
_publicAddress = await NanoUtil.uniSeedToAddress(_hexSeed!, 0, type); 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!; this.walletInfo.address = _publicAddress!;
await walletAddresses.init(); await walletAddresses.init();
@ -125,6 +137,7 @@ abstract class NanoWalletBase
@override @override
void close() { void close() {
_client.stop(); _client.stop();
_receiveTimer?.cancel();
} }
@action @action
@ -139,6 +152,7 @@ abstract class NanoWalletBase
try { try {
await _updateBalance(); await _updateBalance();
await updateTransactions();
await _updateRep(); await _updateRep();
await _receiveAll(); await _receiveAll();
} catch (e) { } catch (e) {
@ -173,8 +187,8 @@ abstract class NanoWalletBase
if (txOut.sendAll) { if (txOut.sendAll) {
amt = balance[currency]?.currentBalance ?? BigInt.zero; amt = balance[currency]?.currentBalance ?? BigInt.zero;
} else { } else {
amt = BigInt.tryParse(NanoUtil.getAmountAsRaw( amt = BigInt.tryParse(NanoAmounts.getAmountAsRaw(
txOut.cryptoAmount?.replaceAll(',', '.') ?? "0", NanoUtil.rawPerNano)) ?? txOut.cryptoAmount?.replaceAll(',', '.') ?? "0", NanoAmounts.rawPerNano)) ??
BigInt.zero; BigInt.zero;
} }
@ -186,9 +200,7 @@ abstract class NanoWalletBase
final block = await _client.constructSendBlock( final block = await _client.constructSendBlock(
amountRaw: amt.toString(), amountRaw: amt.toString(),
destinationAddress: txOut.isParsedAddress destinationAddress: txOut.isParsedAddress ? txOut.extractedAddress! : txOut.address,
? txOut.extractedAddress!
: txOut.address,
privateKey: _privateKey!, privateKey: _privateKey!,
balanceAfterTx: runningBalance, balanceAfterTx: runningBalance,
previousHash: previousHash, previousHash: previousHash,
@ -236,10 +248,10 @@ abstract class NanoWalletBase
} }
} }
Future<void> updateTransactions() async { Future<bool> updateTransactions() async {
try { try {
if (_isTransactionUpdating) { if (_isTransactionUpdating) {
return; return false;
} }
_isTransactionUpdating = true; _isTransactionUpdating = true;
@ -247,8 +259,10 @@ abstract class NanoWalletBase
transactionHistory.addMany(transactions); transactionHistory.addMany(transactions);
await transactionHistory.save(); await transactionHistory.save();
_isTransactionUpdating = false; _isTransactionUpdating = false;
return true;
} catch (_) { } catch (_) {
_isTransactionUpdating = false; _isTransactionUpdating = false;
return false;
} }
} }
@ -261,16 +275,17 @@ abstract class NanoWalletBase
final Map<String, NanoTransactionInfo> result = {}; final Map<String, NanoTransactionInfo> result = {};
for (var transactionModel in transactions) { for (var transactionModel in transactions) {
final bool isSend = transactionModel.type == "send";
result[transactionModel.hash] = NanoTransactionInfo( result[transactionModel.hash] = NanoTransactionInfo(
id: transactionModel.hash, id: transactionModel.hash,
amountRaw: transactionModel.amount, amountRaw: transactionModel.amount,
height: transactionModel.height, height: transactionModel.height,
direction: transactionModel.type == "send" direction: isSend ? TransactionDirection.outgoing : TransactionDirection.incoming,
? TransactionDirection.outgoing
: TransactionDirection.incoming,
confirmed: transactionModel.confirmed, confirmed: transactionModel.confirmed,
date: transactionModel.date ?? DateTime.now(), date: transactionModel.date ?? DateTime.now(),
confirmations: transactionModel.confirmed ? 1 : 0, confirmations: transactionModel.confirmed ? 1 : 0,
to: isSend ? transactionModel.account : address,
from: isSend ? address : transactionModel.account,
); );
} }
@ -312,11 +327,10 @@ abstract class NanoWalletBase
Future<void> startSync() async { Future<void> startSync() async {
try { try {
syncStatus = AttemptingSyncStatus(); syncStatus = AttemptingSyncStatus();
await _updateBalance();
await updateTransactions();
// setup a timer to receive transactions periodically:
_receiveTimer?.cancel(); _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: // get our balance:
await _updateBalance(); await _updateBalance();
// if we have anything to receive, process it: // 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(); syncStatus = SyncedSyncStatus();
} catch (e) { } catch (e) {
print(e); print(e);
@ -353,9 +375,11 @@ abstract class NanoWalletBase
final data = json.decode(jsonSource) as Map; final data = json.decode(jsonSource) as Map;
final mnemonic = data['mnemonic'] as String; final mnemonic = data['mnemonic'] as String;
final balance = NanoBalance.fromString(
formattedCurrentBalance: data['currentBalance'] as String? ?? "0", final balance = NanoBalance.fromRawString(
formattedReceivableBalance: data['receivableBalance'] as String? ?? "0"); currentBalance: data['currentBalance'] as String? ?? "0",
receivableBalance: data['receivableBalance'] as String? ?? "0",
);
DerivationType derivationType = DerivationType.nano; DerivationType derivationType = DerivationType.nano;
if (data['derivationType'] == "DerivationType.bip39") { if (data['derivationType'] == "DerivationType.bip39") {
@ -374,12 +398,26 @@ abstract class NanoWalletBase
} }
Future<void> _updateBalance() async { Future<void> _updateBalance() async {
var oldBalance = balance[currency];
try { try {
balance[currency] = await _client.getBalance(_publicAddress!); balance[currency] = await _client.getBalance(_publicAddress!);
} catch (e) { } catch (e) {
print("Failed to get balance $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<void> _updateRep() async { Future<void> _updateRep() async {
@ -394,11 +432,19 @@ abstract class NanoWalletBase
} }
Future<void> regenerateAddress() async { Future<void> regenerateAddress() async {
final String type = (_derivationType == DerivationType.nano) ? "standard" : "hd"; final NanoDerivationType type = (_derivationType == DerivationType.nano)
_privateKey = ? NanoDerivationType.STANDARD
await NanoUtil.uniSeedToPrivate(_hexSeed!, this.walletAddresses.account!.id, type); : NanoDerivationType.HD;
_publicAddress = _privateKey = await NanoDerivations.universalSeedToPrivate(
await NanoUtil.uniSeedToAddress(_hexSeed!, this.walletAddresses.account!.id, type); _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.walletInfo.address = _publicAddress!;
this.walletAddresses.address = _publicAddress!; this.walletAddresses.address = _publicAddress!;

View file

@ -6,12 +6,12 @@ import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/wallet_service.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:cw_nano/nano_mnemonic.dart' as nm; 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.dart';
import 'package:cw_nano/nano_wallet_creation_credentials.dart'; import 'package:cw_nano/nano_wallet_creation_credentials.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:bip39/bip39.dart' as bip39; import 'package:bip39/bip39.dart' as bip39;
import 'package:nanodart/nanodart.dart'; import 'package:nanodart/nanodart.dart';
import 'package:nanoutil/nanoutil.dart';
class NanoWalletService extends WalletService<NanoNewWalletCredentials, class NanoWalletService extends WalletService<NanoNewWalletCredentials,
NanoRestoreWalletFromSeedCredentials, NanoRestoreWalletFromKeysCredentials> { NanoRestoreWalletFromSeedCredentials, NanoRestoreWalletFromKeysCredentials> {
@ -30,7 +30,7 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
// nano standard: // nano standard:
DerivationType derivationType = DerivationType.nano; DerivationType derivationType = DerivationType.nano;
String seedKey = NanoSeeds.generateSeed(); String seedKey = NanoSeeds.generateSeed();
String mnemonic = NanoUtil.seedToMnemonic(seedKey); String mnemonic = NanoDerivations.standardSeedToMnemonic(seedKey);
credentials.walletInfo!.derivationType = derivationType; credentials.walletInfo!.derivationType = derivationType;
@ -95,7 +95,7 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
// we can't derive the mnemonic from the key in all cases, only if it's a "nano" seed // we can't derive the mnemonic from the key in all cases, only if it's a "nano" seed
if (credentials.seedKey.length == 64) { if (credentials.seedKey.length == 64) {
try { try {
mnemonic = NanoUtil.seedToMnemonic(credentials.seedKey); mnemonic = NanoDerivations.standardSeedToMnemonic(credentials.seedKey);
} catch (e) { } catch (e) {
throw Exception("Wasn't a valid nano style seed!"); throw Exception("Wasn't a valid nano style seed!");
} }

View file

@ -1,6 +1,6 @@
import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/pending_transaction.dart';
import 'package:cw_nano/nano_client.dart'; import 'package:cw_nano/nano_client.dart';
import 'package:cw_nano/nano_util.dart'; import 'package:nanoutil/nanoutil.dart';
class PendingNanoTransaction with PendingTransaction { class PendingNanoTransaction with PendingTransaction {
PendingNanoTransaction({ PendingNanoTransaction({
@ -18,13 +18,13 @@ class PendingNanoTransaction with PendingTransaction {
@override @override
String get amountFormatted { String get amountFormatted {
final String amt = NanoUtil.getRawAsUsableString(amount.toString(), NanoUtil.rawPerNano); final String amt = NanoAmounts.getRawAsUsableString(amount.toString(), NanoAmounts.rawPerNano);
return amt; return amt;
} }
String get accurateAmountFormatted { String get accurateAmountFormatted {
final String amt = NanoUtil.getRawAsUsableString(amount.toString(), NanoUtil.rawPerNano); final String amt = NanoAmounts.getRawAsUsableString(amount.toString(), NanoAmounts.rawPerNano);
final String acc = NanoUtil.getRawAccuracy(amount.toString(), NanoUtil.rawPerNano); final String acc = NanoAmounts.getRawAccuracy(amount.toString(), NanoAmounts.rawPerNano);
return "$acc$amt"; return "$acc$amt";
} }

View file

@ -22,6 +22,10 @@ dependencies:
hex: ^0.2.0 hex: ^0.2.0
http: ^1.1.0 http: ^1.1.0
shared_preferences: ^2.0.15 shared_preferences: ^2.0.15
nanoutil:
git:
url: https://github.com/perishllc/nanoutil.git
ref: c37e72817cf0a28162f43124f79661d6c8e0098f
cw_core: cw_core:
path: ../cw_core path: ../cw_core

View file

@ -173,7 +173,7 @@ class CWNano extends Nano {
} }
@override @override
Future<void> updateTransactions(Object wallet) async { Future<bool> updateTransactions(Object wallet) async {
return (wallet as NanoWallet).updateTransactions(); return (wallet as NanoWallet).updateTransactions();
} }
@ -189,116 +189,10 @@ class CWNano extends Nano {
} }
class CWNanoUtil extends NanoUtil { 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<String> 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<String> hdMnemonicListToSeed(List<String> 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<String> hdSeedToPrivate(String seed, int index) async {
List<int> seedBytes = hex.decode(seed);
KeyData data = await ED25519_HD_KEY.derivePath("m/44'/165'/$index'", seedBytes);
return hex.encode(data.key);
}
@override
Future<String> hdSeedToAddress(String seed, int index) async {
return ND.NanoAccounts.createAccount(
ND.NanoAccountType.NANO, privateKeyToPublic(await hdSeedToPrivate(seed, index)));
}
@override
Future<String> uniSeedToAddress(String seed, int index, String type) {
if (type == "standard") {
return Future<String>.value(seedToAddress(seed, index));
} else if (type == "hd") {
return hdSeedToAddress(seed, index);
} else {
throw Exception('Unknown seed type');
}
}
@override
Future<String> uniSeedToPrivate(String seed, int index, String type) {
if (type == "standard") {
return Future<String>.value(seedToPrivate(seed, index));
} else if (type == "hd") {
return hdSeedToPrivate(seed, index);
} else {
throw Exception('Unknown seed type');
}
}
@override @override
bool isValidBip39Seed(String seed) { bool isValidBip39Seed(String seed) {
// Ensure seed is 128 characters long return NanoDerivations.isValidBip39Seed(seed);
if (seed.length != 128) {
return false;
}
// Ensure seed only contains hex characters, 0-9;A-F
return ND.NanoHelpers.isHexString(seed);
} }
// number util: // number util:
@ -309,92 +203,20 @@ class CWNanoUtil extends NanoUtil {
BigInt rawPerBanano = BigInt.parse("100000000000000000000000000000"); BigInt rawPerBanano = BigInt.parse("100000000000000000000000000000");
BigInt rawPerXMR = BigInt.parse("1000000000000"); BigInt rawPerXMR = BigInt.parse("1000000000000");
BigInt convertXMRtoNano = BigInt.parse("1000000000000000000"); 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 @override
String getRawAsUsableString(String? raw, BigInt rawPerCur) { String getRawAsUsableString(String? raw, BigInt rawPerCur) {
final String res = return NanoAmounts.getRawAsUsableString(raw, rawPerCur);
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";
} }
@override @override
String getRawAccuracy(String? raw, BigInt rawPerCur) { String getRawAccuracy(String? raw, BigInt rawPerCur) {
final String rawString = getRawAsUsableString(raw, rawPerCur); return NanoAmounts.getRawAccuracy(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
///
@override @override
String getAmountAsRaw(String amount, BigInt rawPerCur) { String getAmountAsRaw(String amount, BigInt rawPerCur) {
final Decimal asDecimal = Decimal.parse(amount); return NanoAmounts.getAmountAsRaw(amount, rawPerCur);
final Decimal rawDecimal = Decimal.parse(rawPerCur.toString());
return (asDecimal * rawDecimal).toString();
} }
@override @override
@ -411,29 +233,29 @@ class CWNanoUtil extends NanoUtil {
if (seedKey != null) { if (seedKey != null) {
if (seedKey.length == 64) { if (seedKey.length == 64) {
try { try {
mnemonic = nanoUtil!.seedToMnemonic(seedKey); mnemonic = NanoDerivations.standardSeedToMnemonic(seedKey);
} catch (e) { } catch (e) {
print("not a valid 'nano' seed key"); print("not a valid 'nano' seed key");
} }
} }
if (derivationType == DerivationType.bip39) { if (derivationType == DerivationType.bip39) {
publicAddress = await hdSeedToAddress(seedKey, 0); publicAddress = await NanoDerivations.hdSeedToAddress(seedKey, index: 0);
} else if (derivationType == DerivationType.nano) { } else if (derivationType == DerivationType.nano) {
publicAddress = await seedToAddress(seedKey, 0); publicAddress = await NanoDerivations.standardSeedToAddress(seedKey, index: 0);
} }
} }
if (derivationType == DerivationType.bip39) { if (derivationType == DerivationType.bip39) {
if (mnemonic != null) { if (mnemonic != null) {
seedKey = await hdMnemonicListToSeed(mnemonic.split(' ')); seedKey = await NanoDerivations.hdMnemonicListToSeed(mnemonic.split(' '));
publicAddress = await hdSeedToAddress(seedKey, 0); publicAddress = await NanoDerivations.hdSeedToAddress(seedKey, index: 0);
} }
} }
if (derivationType == DerivationType.nano) { if (derivationType == DerivationType.nano) {
if (mnemonic != null) { if (mnemonic != null) {
seedKey = await mnemonicToSeed(mnemonic); seedKey = await NanoDerivations.standardMnemonicToSeed(mnemonic);
publicAddress = await seedToAddress(seedKey, 0); publicAddress = await NanoDerivations.standardSeedToAddress(seedKey, index: 0);
} }
} }
@ -461,7 +283,7 @@ class CWNanoUtil extends NanoUtil {
return [DerivationType.bip39]; return [DerivationType.bip39];
} else if (seedKey?.length == 64) { } else if (seedKey?.length == 64) {
try { try {
mnemonic = nanoUtil!.seedToMnemonic(seedKey!); mnemonic = NanoDerivations.standardSeedToMnemonic(seedKey!);
} catch (e) { } catch (e) {
print("not a valid 'nano' seed key"); print("not a valid 'nano' seed key");
} }
@ -475,19 +297,19 @@ class CWNanoUtil extends NanoUtil {
nanoClient.connect(node); nanoClient.connect(node);
if (mnemonic != null) { if (mnemonic != null) {
seedKey = await hdMnemonicListToSeed(mnemonic.split(' ')); seedKey = await NanoDerivations.hdMnemonicListToSeed(mnemonic.split(' '));
publicAddressBip39 = await hdSeedToAddress(seedKey, 0); publicAddressBip39 = await NanoDerivations.hdSeedToAddress(seedKey, index: 0);
seedKey = await mnemonicToSeed(mnemonic); seedKey = await NanoDerivations.standardMnemonicToSeed(mnemonic);
publicAddressStandard = await seedToAddress(seedKey, 0); publicAddressStandard = await NanoDerivations.standardSeedToAddress(seedKey, index: 0);
} else if (seedKey != null) { } else if (seedKey != null) {
try { try {
publicAddressBip39 = await hdSeedToAddress(seedKey, 0); publicAddressBip39 = await NanoDerivations.hdSeedToAddress(seedKey, index: 0);
} catch (e) { } catch (e) {
return [DerivationType.nano]; return [DerivationType.nano];
} }
try { try {
publicAddressStandard = await seedToAddress(seedKey, 0); publicAddressStandard = await NanoDerivations.standardSeedToAddress(seedKey, index: 0);
} catch (e) { } catch (e) {
return [DerivationType.bip39]; return [DerivationType.bip39];
} }

View file

@ -125,6 +125,8 @@ abstract class BalanceViewModelBase with Store {
case WalletType.haven: case WalletType.haven:
case WalletType.ethereum: case WalletType.ethereum:
case WalletType.polygon: case WalletType.polygon:
case WalletType.nano:
case WalletType.banano:
return S.current.xmr_available_balance; return S.current.xmr_available_balance;
default: default:
return S.current.confirmed; return S.current.confirmed;
@ -139,6 +141,9 @@ abstract class BalanceViewModelBase with Store {
case WalletType.ethereum: case WalletType.ethereum:
case WalletType.polygon: case WalletType.polygon:
return S.current.xmr_full_balance; return S.current.xmr_full_balance;
case WalletType.nano:
case WalletType.banano:
return S.current.receivable_balance;
default: default:
return S.current.unconfirmed; return S.current.unconfirmed;
} }

View file

@ -101,7 +101,7 @@ class TransactionListItem extends ActionListItem with Keyable {
break; break;
case WalletType.nano: case WalletType.nano:
amount = calculateFiatAmountRaw( amount = calculateFiatAmountRaw(
cryptoAmount: double.parse(nanoUtil!.getRawAsDecimalString( cryptoAmount: double.parse(nanoUtil!.getRawAsUsableString(
nano!.getTransactionAmountRaw(transaction).toString(), nanoUtil!.rawPerNano)), nano!.getTransactionAmountRaw(transaction).toString(), nanoUtil!.rawPerNano)),
price: price); price: price);
break; break;

View file

@ -247,11 +247,15 @@ abstract class TransactionDetailsViewModelBase with Store {
void _addNanoListItems(TransactionInfo tx, DateFormat dateFormat) { void _addNanoListItems(TransactionInfo tx, DateFormat dateFormat) {
final _items = [ final _items = [
StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id), 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( 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.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_height, value: '${tx.height}'),
StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
]; ];
items.addAll(_items); items.addAll(_items);

View file

@ -759,5 +759,8 @@
"default_sell_provider": " ﻲﺿﺍﺮﺘﻓﻻﺍ ﻊﻴﺒﻟﺍ ﺩﻭﺰﻣ", "default_sell_provider": " ﻲﺿﺍﺮﺘﻓﻻﺍ ﻊﻴﺒﻟﺍ ﺩﻭﺰﻣ",
"select_sell_provider_notice": ".ﻖﻴﺒﻄﺘﻟﺍ ﺕﺍﺩﺍﺪﻋﺇ ﻲﻓ ﻚﺑ ﺹﺎﺨﻟﺍ ﻲﺿﺍﺮﺘﻓﻻﺍ ﻊﻴﺒﻟﺍ ﺩﻭﺰﻣ ﻦﻴﻴﻌﺗ ﻖﻳﺮﻃ ﻦﻋ ﺔﺷﺎﺸﻟﺍ ﻩﺬﻫ ﻲﻄﺨﺗ", "select_sell_provider_notice": ".ﻖﻴﺒﻄﺘﻟﺍ ﺕﺍﺩﺍﺪﻋﺇ ﻲﻓ ﻚﺑ ﺹﺎﺨﻟﺍ ﻲﺿﺍﺮﺘﻓﻻﺍ ﻊﻴﺒﻟﺍ ﺩﻭﺰﻣ ﻦﻴﻴﻌﺗ ﻖﻳﺮﻃ ﻦﻋ ﺔﺷﺎﺸﻟﺍ ﻩﺬﻫ ﻲﻄﺨﺗ",
"custom_drag": "مخصص (عقد وسحب)", "custom_drag": "مخصص (عقد وسحب)",
"switchToEVMCompatibleWallet": " (Ethereum، Polygon) ﻯﺮﺧﺃ ﺓﺮﻣ ﺔﻟﻭﺎﺤﻤﻟﺍﻭ EVM ﻊﻣ ﺔﻘﻓﺍﻮﺘﻣ ﺔﻈﻔﺤﻣ ﻰﻟﺇ ﻞﻳﺪﺒﺘﻟﺍ ﻰﺟﺮﻳ" "switchToEVMCompatibleWallet": " (Ethereum، Polygon) ﻯﺮﺧﺃ ﺓﺮﻣ ﺔﻟﻭﺎﺤﻤﻟﺍﻭ EVM ﻊﻣ ﺔﻘﻓﺍﻮﺘﻣ ﺔﻈﻔﺤﻣ ﻰﻟﺇ ﻞﻳﺪﺒﺘﻟﺍ ﻰﺟﺮﻳ",
} "receivable_balance": "التوازن القادم",
"confirmed_tx": "مؤكد",
"transaction_details_source_address": "عنوان المصدر"
}

View file

@ -755,5 +755,8 @@
"default_sell_provider": "Доставчик за продажба по подразбиране", "default_sell_provider": "Доставчик за продажба по подразбиране",
"select_sell_provider_notice": "Изберете доставчик на продажба по-горе. Можете да пропуснете този екран, като зададете своя доставчик на продажба по подразбиране в настройките на приложението.", "select_sell_provider_notice": "Изберете доставчик на продажба по-горе. Можете да пропуснете този екран, като зададете своя доставчик на продажба по подразбиране в настройките на приложението.",
"custom_drag": "Персонализиране (задръжте и плъзнете)", "custom_drag": "Персонализиране (задръжте и плъзнете)",
"switchToEVMCompatibleWallet": "Моля, превключете към портфейл, съвместим с EVM, и опитайте отново (Ethereum, Polygon)" "switchToEVMCompatibleWallet": "Моля, превключете към портфейл, съвместим с EVM, и опитайте отново (Ethereum, Polygon)",
} "receivable_balance": "Баланс за вземания",
"confirmed_tx": "Потвърдено",
"transaction_details_source_address": "Адрес на източника"
}

View file

@ -755,5 +755,8 @@
"default_sell_provider": "Výchozí poskytovatel prodeje", "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.", "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)", "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"
}

View file

@ -763,5 +763,8 @@
"default_sell_provider": "Standard-Verkaufsanbieter", "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.", "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)", "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"
}

View file

@ -764,5 +764,8 @@
"default_sell_provider": "Default Sell Provider", "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.", "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)", "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"
}

View file

@ -763,5 +763,8 @@
"default_sell_provider": "Proveedor de venta predeterminado", "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.", "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)", "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"
}

View file

@ -763,5 +763,8 @@
"default_sell_provider": "Fournisseur de vente par défaut", "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.", "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)", "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"
}

View file

@ -745,5 +745,8 @@
"default_sell_provider": "Tsohuwar Mai Bayar Siyarwa", "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.", "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)", "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"
}

View file

@ -763,5 +763,8 @@
"default_sell_provider": "डिफ़ॉल्ट विक्रय प्रदाता", "default_sell_provider": "डिफ़ॉल्ट विक्रय प्रदाता",
"select_sell_provider_notice": "ऊपर एक विक्रय प्रदाता का चयन करें। आप ऐप सेटिंग में अपना डिफ़ॉल्ट विक्रय प्रदाता सेट करके इस स्क्रीन को छोड़ सकते हैं।", "select_sell_provider_notice": "ऊपर एक विक्रय प्रदाता का चयन करें। आप ऐप सेटिंग में अपना डिफ़ॉल्ट विक्रय प्रदाता सेट करके इस स्क्रीन को छोड़ सकते हैं।",
"custom_drag": "कस्टम (पकड़ और खींचें)", "custom_drag": "कस्टम (पकड़ और खींचें)",
"switchToEVMCompatibleWallet": "कृपया ईवीएम संगत वॉलेट पर स्विच करें और पुनः प्रयास करें (एथेरियम, पॉलीगॉन)" "switchToEVMCompatibleWallet": "कृपया ईवीएम संगत वॉलेट पर स्विच करें और पुनः प्रयास करें (एथेरियम, पॉलीगॉन)",
} "receivable_balance": "प्राप्य शेष",
"confirmed_tx": "की पुष्टि",
"transaction_details_source_address": "स्रोत पता"
}

View file

@ -761,5 +761,8 @@
"default_sell_provider": "Zadani dobavljač prodaje", "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.", "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)", "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"
}

View file

@ -751,5 +751,8 @@
"default_sell_provider": "Penyedia Penjualan Default", "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.", "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)", "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"
}

View file

@ -763,5 +763,8 @@
"default_sell_provider": "Fornitore di vendita predefinito", "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.", "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)", "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"
}

View file

@ -763,5 +763,8 @@
"default_sell_provider": "デフォルトの販売プロバイダー", "default_sell_provider": "デフォルトの販売プロバイダー",
"select_sell_provider_notice": "上記の販売プロバイダーを選択してください。アプリ設定でデフォルトの販売プロバイダーを設定することで、この画面をスキップできます。", "select_sell_provider_notice": "上記の販売プロバイダーを選択してください。アプリ設定でデフォルトの販売プロバイダーを設定することで、この画面をスキップできます。",
"custom_drag": "カスタム(ホールドとドラッグ)", "custom_drag": "カスタム(ホールドとドラッグ)",
"switchToEVMCompatibleWallet": "EVM 互換のウォレットに切り替えて再試行してください (イーサリアム、ポリゴン)" "switchToEVMCompatibleWallet": "EVM 互換のウォレットに切り替えて再試行してください (イーサリアム、ポリゴン)",
} "receivable_balance": "売掛金残高",
"confirmed_tx": "確認済み",
"transaction_details_source_address": "ソースアドレス"
}

View file

@ -761,5 +761,8 @@
"default_sell_provider": "기본 판매 공급자", "default_sell_provider": "기본 판매 공급자",
"select_sell_provider_notice": "위에서 판매 공급자를 선택하세요. 앱 설정에서 기본 판매 공급자를 설정하면 이 화면을 건너뛸 수 있습니다.", "select_sell_provider_notice": "위에서 판매 공급자를 선택하세요. 앱 설정에서 기본 판매 공급자를 설정하면 이 화면을 건너뛸 수 있습니다.",
"custom_drag": "사용자 정의 (홀드 앤 드래그)", "custom_drag": "사용자 정의 (홀드 앤 드래그)",
"switchToEVMCompatibleWallet": "EVM 호환 지갑으로 전환 후 다시 시도해 주세요. (이더리움, 폴리곤)" "switchToEVMCompatibleWallet": "EVM 호환 지갑으로 전환 후 다시 시도해 주세요. (이더리움, 폴리곤)",
} "receivable_balance": "채권 잔액",
"confirmed_tx": "확인",
"transaction_details_source_address": "소스 주소"
}

View file

@ -761,5 +761,8 @@
"default_sell_provider": "ပုံသေရောင်းချပေးသူ", "default_sell_provider": "ပုံသေရောင်းချပေးသူ",
"select_sell_provider_notice": "အထက်ဖော်ပြပါ အရောင်းဝန်ဆောင်မှုပေးသူကို ရွေးပါ။ အက်ပ်ဆက်တင်များတွင် သင်၏မူလရောင်းချပေးသူကို သတ်မှတ်ခြင်းဖြင့် ဤစခရင်ကို ကျော်နိုင်သည်။", "select_sell_provider_notice": "အထက်ဖော်ပြပါ အရောင်းဝန်ဆောင်မှုပေးသူကို ရွေးပါ။ အက်ပ်ဆက်တင်များတွင် သင်၏မူလရောင်းချပေးသူကို သတ်မှတ်ခြင်းဖြင့် ဤစခရင်ကို ကျော်နိုင်သည်။",
"custom_drag": "စိတ်ကြိုက် (Drag)", "custom_drag": "စိတ်ကြိုက် (Drag)",
"switchToEVMCompatibleWallet": "ကျေးဇူးပြု၍ EVM တွဲဖက်သုံးနိုင်သော ပိုက်ဆံအိတ်သို့ ပြောင်းပြီး ထပ်စမ်းကြည့်ပါ (Ethereum၊ Polygon)" "switchToEVMCompatibleWallet": "ကျေးဇူးပြု၍ EVM တွဲဖက်သုံးနိုင်သော ပိုက်ဆံအိတ်သို့ ပြောင်းပြီး ထပ်စမ်းကြည့်ပါ (Ethereum၊ Polygon)",
} "receivable_balance": "လက်ကျန်ငွေ",
"confirmed_tx": "အတည်ပြုသည်",
"transaction_details_source_address": "အရင်းအမြစ်လိပ်စာ"
}

View file

@ -763,5 +763,8 @@
"default_sell_provider": "Standaard verkoopaanbieder", "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.", "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)", "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"
}

View file

@ -763,5 +763,8 @@
"default_sell_provider": "Domyślny dostawca sprzedaży", "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.", "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)", "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"
}

View file

@ -762,5 +762,8 @@
"default_sell_provider": "Provedor de venda padrão", "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.", "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)", "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"
}

View file

@ -763,5 +763,8 @@
"default_sell_provider": "Поставщик продаж по умолчанию", "default_sell_provider": "Поставщик продаж по умолчанию",
"select_sell_provider_notice": "Выберите поставщика услуг продажи выше. Вы можете пропустить этот экран, установив поставщика услуг продаж по умолчанию в настройках приложения.", "select_sell_provider_notice": "Выберите поставщика услуг продажи выше. Вы можете пропустить этот экран, установив поставщика услуг продаж по умолчанию в настройках приложения.",
"custom_drag": "Пользователь (удерживайте и перетаскивайте)", "custom_drag": "Пользователь (удерживайте и перетаскивайте)",
"switchToEVMCompatibleWallet": "Пожалуйста, переключитесь на кошелек, совместимый с EVM, и повторите попытку (Ethereum, Polygon)." "switchToEVMCompatibleWallet": "Пожалуйста, переключитесь на кошелек, совместимый с EVM, и повторите попытку (Ethereum, Polygon).",
} "receivable_balance": "Баланс дебиторской задолженности",
"confirmed_tx": "Подтвержденный",
"transaction_details_source_address": "Адрес источника"
}

View file

@ -761,5 +761,8 @@
"default_sell_provider": "ผู้ให้บริการการขายเริ่มต้น", "default_sell_provider": "ผู้ให้บริการการขายเริ่มต้น",
"select_sell_provider_notice": "เลือกผู้ให้บริการการขายด้านบน คุณสามารถข้ามหน้าจอนี้ได้โดยการตั้งค่าผู้ให้บริการการขายเริ่มต้นในการตั้งค่าแอป", "select_sell_provider_notice": "เลือกผู้ให้บริการการขายด้านบน คุณสามารถข้ามหน้าจอนี้ได้โดยการตั้งค่าผู้ให้บริการการขายเริ่มต้นในการตั้งค่าแอป",
"custom_drag": "กำหนดเอง (ค้างและลาก)", "custom_drag": "กำหนดเอง (ค้างและลาก)",
"switchToEVMCompatibleWallet": "โปรดเปลี่ยนไปใช้กระเป๋าเงินที่รองรับ EVM แล้วลองอีกครั้ง (Ethereum, Polygon)" "switchToEVMCompatibleWallet": "โปรดเปลี่ยนไปใช้กระเป๋าเงินที่รองรับ EVM แล้วลองอีกครั้ง (Ethereum, Polygon)",
} "receivable_balance": "ยอดลูกหนี้",
"confirmed_tx": "ซึ่งยืนยันแล้ว",
"transaction_details_source_address": "ที่อยู่แหล่งกำเนิด"
}

View file

@ -757,5 +757,8 @@
"default_sell_provider": "Default na Sell Provider", "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.", "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)", "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"
}

View file

@ -761,5 +761,8 @@
"default_sell_provider": "Varsayılan Satış Sağlayıcısı", "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.", "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)", "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"
}

View file

@ -763,5 +763,8 @@
"default_sell_provider": "Постачальник продажу за замовчуванням", "default_sell_provider": "Постачальник продажу за замовчуванням",
"select_sell_provider_notice": "Виберіть вище постачальника послуг продажу. Ви можете пропустити цей екран, встановивши постачальника послуг продажу за умовчанням у налаштуваннях програми.", "select_sell_provider_notice": "Виберіть вище постачальника послуг продажу. Ви можете пропустити цей екран, встановивши постачальника послуг продажу за умовчанням у налаштуваннях програми.",
"custom_drag": "На замовлення (утримуйте та перетягується)", "custom_drag": "На замовлення (утримуйте та перетягується)",
"switchToEVMCompatibleWallet": "Перейдіть на гаманець, сумісний з EVM, і повторіть спробу (Ethereum, Polygon)" "switchToEVMCompatibleWallet": "Перейдіть на гаманець, сумісний з EVM, і повторіть спробу (Ethereum, Polygon)",
} "receivable_balance": "Баланс дебіторської заборгованості",
"confirmed_tx": "Підтверджений",
"transaction_details_source_address": "Адреса джерела"
}

View file

@ -755,5 +755,8 @@
"default_sell_provider": " ﮦﺪﻨﻨﮐ ﻢﮨﺍﺮﻓ ﻞﯿﺳ ﭧﻟﺎﻔﯾﮈ", "default_sell_provider": " ﮦﺪﻨﻨﮐ ﻢﮨﺍﺮﻓ ﻞﯿﺳ ﭧﻟﺎﻔﯾﮈ",
"select_sell_provider_notice": "۔ﮟﯿﮨ ﮯﺘﮑﺳ ﮌﻮﮭﭼ ﻮﮐ ﻦﯾﺮﮑﺳﺍ ﺱﺍ ﺮﮐ ﮮﺩ ﺐﯿﺗﺮﺗ ﻮﮐ ﮦﺪﻨﻨﮐ ﻢﮨﺍﺮﻓ ﻞﯿﺳ ﭧﻟﺎﻔﯾﮈ ﮯﻨﭘﺍ ﮟﯿﻣ ﺕﺎﺒ", "select_sell_provider_notice": "۔ﮟﯿﮨ ﮯﺘﮑﺳ ﮌﻮﮭﭼ ﻮﮐ ﻦﯾﺮﮑﺳﺍ ﺱﺍ ﺮﮐ ﮮﺩ ﺐﯿﺗﺮﺗ ﻮﮐ ﮦﺪﻨﻨﮐ ﻢﮨﺍﺮﻓ ﻞﯿﺳ ﭧﻟﺎﻔﯾﮈ ﮯﻨﭘﺍ ﮟﯿﻣ ﺕﺎﺒ",
"custom_drag": "کسٹم (ہولڈ اینڈ ڈریگ)", "custom_drag": "کسٹم (ہولڈ اینڈ ڈریگ)",
"switchToEVMCompatibleWallet": "(Ethereum, Polygon) ﮟﯾﺮﮐ ﺶﺷﻮﮐ ﮦﺭﺎﺑﻭﺩ ﺭﻭﺍ ﮟﯾﺮﮐ ﭻﺋﻮﺳ ﺮﭘ ﭧﯿﻟﺍﻭ ﮯﻟﺍﻭ ﮯﻨﮭﮐﺭ ﺖﻘﺑﺎﻄﻣ " "switchToEVMCompatibleWallet": "(Ethereum, Polygon) ﮟﯾﺮﮐ ﺶﺷﻮﮐ ﮦﺭﺎﺑﻭﺩ ﺭﻭﺍ ﮟﯾﺮﮐ ﭻﺋﻮﺳ ﺮﭘ ﭧﯿﻟﺍﻭ ﮯﻟﺍﻭ ﮯﻨﮭﮐﺭ ﺖﻘﺑﺎﻄﻣ ",
} "receivable_balance": "قابل وصول توازن",
"confirmed_tx": "تصدیق",
"transaction_details_source_address": "ماخذ ایڈریس"
}

View file

@ -757,5 +757,8 @@
"default_sell_provider": "Aiyipada Olupese Tita", "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.", "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)", "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"
}

View file

@ -762,5 +762,8 @@
"default_sell_provider": "默认销售提供商", "default_sell_provider": "默认销售提供商",
"select_sell_provider_notice": "选择上面的销售提供商。您可以通过在应用程序设置中设置默认销售提供商来跳过此屏幕。", "select_sell_provider_notice": "选择上面的销售提供商。您可以通过在应用程序设置中设置默认销售提供商来跳过此屏幕。",
"custom_drag": "定制(保持和拖动)", "custom_drag": "定制(保持和拖动)",
"switchToEVMCompatibleWallet": "请切换到 EVM 兼容钱包并重试以太坊、Polygon" "switchToEVMCompatibleWallet": "请切换到 EVM 兼容钱包并重试以太坊、Polygon",
} "receivable_balance": "应收余额",
"confirmed_tx": "确认的",
"transaction_details_source_address": "源地址"
}

View file

@ -759,7 +759,7 @@ import 'package:convert/convert.dart';
import "package:ed25519_hd_key/ed25519_hd_key.dart"; import "package:ed25519_hd_key/ed25519_hd_key.dart";
import 'package:libcrypto/libcrypto.dart'; import 'package:libcrypto/libcrypto.dart';
import 'package:nanodart/nanodart.dart' as ND; import 'package:nanodart/nanodart.dart' as ND;
import 'package:decimal/decimal.dart'; import 'package:nanoutil/nanoutil.dart';
"""; """;
const nanoCwPart = "part 'cw_nano.dart';"; const nanoCwPart = "part 'cw_nano.dart';";
const nanoContent = """ const nanoContent = """
@ -795,7 +795,7 @@ abstract class Nano {
Map<String, String> getKeys(Object wallet); Map<String, String> getKeys(Object wallet);
Object createNanoTransactionCredentials(List<Output> outputs); Object createNanoTransactionCredentials(List<Output> outputs);
Future<void> changeRep(Object wallet, String address); Future<void> changeRep(Object wallet, String address);
Future<void> updateTransactions(Object wallet); Future<bool> updateTransactions(Object wallet);
BigInt getTransactionAmountRaw(TransactionInfo transactionInfo); BigInt getTransactionAmountRaw(TransactionInfo transactionInfo);
String getRepresentative(Object wallet); String getRepresentative(Object wallet);
} }
@ -810,20 +810,6 @@ abstract class NanoAccountList {
} }
abstract class NanoUtil { abstract class NanoUtil {
String seedToPrivate(String seed, int index);
String seedToAddress(String seed, int index);
String seedToMnemonic(String seed);
Future<String> mnemonicToSeed(String mnemonic);
String privateKeyToPublic(String privateKey);
String addressToPublicKey(String publicAddress);
String privateKeyToAddress(String privateKey);
String publicKeyToAddress(String publicKey);
bool isValidSeed(String seed);
Future<String> hdMnemonicListToSeed(List<String> words);
Future<String> hdSeedToPrivate(String seed, int index);
Future<String> hdSeedToAddress(String seed, int index);
Future<String> uniSeedToAddress(String seed, int index, String type);
Future<String> uniSeedToPrivate(String seed, int index, String type);
bool isValidBip39Seed(String seed); bool isValidBip39Seed(String seed);
static const int maxDecimalDigits = 6; // Max digits after decimal static const int maxDecimalDigits = 6; // Max digits after decimal
BigInt rawPerNano = BigInt.parse("1000000000000000000000000000000"); BigInt rawPerNano = BigInt.parse("1000000000000000000000000000000");
@ -831,7 +817,6 @@ abstract class NanoUtil {
BigInt rawPerBanano = BigInt.parse("100000000000000000000000000000"); BigInt rawPerBanano = BigInt.parse("100000000000000000000000000000");
BigInt rawPerXMR = BigInt.parse("1000000000000"); BigInt rawPerXMR = BigInt.parse("1000000000000");
BigInt convertXMRtoNano = BigInt.parse("1000000000000000000"); BigInt convertXMRtoNano = BigInt.parse("1000000000000000000");
String getRawAsDecimalString(String? raw, BigInt? rawPerCur);
String getRawAsUsableString(String? raw, BigInt rawPerCur); String getRawAsUsableString(String? raw, BigInt rawPerCur);
String getRawAccuracy(String? raw, BigInt rawPerCur); String getRawAccuracy(String? raw, BigInt rawPerCur);
String getAmountAsRaw(String amount, BigInt rawPerCur); String getAmountAsRaw(String amount, BigInt rawPerCur);