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

Matthew Fosse committed Jan 11, 2024 at 15:00 UTC fe2e26f14600b19125a3bce2369fb61a742ceda1
42 files changed +306 -518
configure_cake_wallet.sh renamed
+21 -1
@@ -1,4 +1,24 @@
1 -cd scripts/android
1 +IOS="ios"
2 +ANDROID="android"
3 +
4 +PLATFORMS=($IOS $ANDROID)
5 +PLATFORM=$1
6 +
7 +if ! [[ " ${PLATFORMS[*]} " =~ " ${PLATFORM} " ]]; then
8 + echo "specify platform: ./configure_cake_wallet.sh ios|android"
9 + exit 1
10 +fi
11 +
12 +if [ "$PLATFORM" == "$IOS" ]; then
13 + echo "Configuring for iOS"
14 + cd scripts/ios
15 +fi
16 +
17 +if [ "$PLATFORM" == "$ANDROID" ]; then
18 + echo "Configuring for Android"
19 + cd scripts/android
20 +fi
21 +
22 source ./app_env.sh cakewallet
23 ./app_config.sh
24 cd ../.. && flutter pub get
cw_core/lib/transaction_info.dart
+1
@@ -15,6 +15,7 @@ abstract class TransactionInfo extends Object with Keyable {
15 String? feeFormatted();
16 void changeFiatAmount(String amount);
17 String? to;
18 + String? from;
19
20 @override
21 dynamic get keyIndex => id;
cw_nano/lib/banano_balance.dart
+4 -5
@@ -1,20 +1,19 @@
1 import 'package:cw_core/balance.dart';
2 -import 'package:cw_nano/nano_util.dart';
2 +import 'package:nanoutil/nanoutil.dart';
3
4 class BananoBalance extends Balance {
5 final BigInt currentBalance;
6 final BigInt receivableBalance;
7
8 - BananoBalance({required this.currentBalance, required this.receivableBalance}) : super(0, 0) {
9 - }
8 + BananoBalance({required this.currentBalance, required this.receivableBalance}) : super(0, 0);
9
10 @override
11 String get formattedAvailableBalance {
13 - return NanoUtil.getRawAsUsableString(currentBalance.toString(), NanoUtil.rawPerBanano);
12 + return NanoAmounts.getRawAsUsableString(currentBalance.toString(), NanoAmounts.rawPerBanano);
13 }
14
15 @override
16 String get formattedAdditionalBalance {
18 - return NanoUtil.getRawAsUsableString(receivableBalance.toString(), NanoUtil.rawPerBanano);
17 + return NanoAmounts.getRawAsUsableString(receivableBalance.toString(), NanoAmounts.rawPerBanano);
18 }
19 }
cw_nano/lib/nano_balance.dart
+13 -12
@@ -1,34 +1,35 @@
1 import 'package:cw_core/balance.dart';
2 -import 'package:cw_nano/nano_util.dart';
2 +import 'package:nanoutil/nanoutil.dart';
3
4 BigInt stringAmountToBigInt(String amount) {
5 - return BigInt.parse(NanoUtil.getAmountAsRaw(amount, NanoUtil.rawPerNano));
5 + return BigInt.parse(NanoAmounts.getAmountAsRaw(amount, NanoAmounts.rawPerNano));
6 }
7
8 class NanoBalance extends Balance {
9 final BigInt currentBalance;
10 final BigInt receivableBalance;
11 - late String formattedCurrentBalance;
12 - late String formattedReceivableBalance;
11
14 - NanoBalance({required this.currentBalance, required this.receivableBalance}) : super(0, 0) {
15 - this.formattedCurrentBalance = "";
16 - this.formattedReceivableBalance = "";
17 - }
12 + NanoBalance({required this.currentBalance, required this.receivableBalance}) : super(0, 0);
13
19 - NanoBalance.fromString(
20 - {required this.formattedCurrentBalance, required this.formattedReceivableBalance})
14 + NanoBalance.fromFormattedString(
15 + {required String formattedCurrentBalance, required String formattedReceivableBalance})
16 : currentBalance = stringAmountToBigInt(formattedCurrentBalance),
17 receivableBalance = stringAmountToBigInt(formattedReceivableBalance),
18 super(0, 0);
19
20 + NanoBalance.fromRawString(
21 + {required String currentBalance, required String receivableBalance})
22 + : currentBalance = BigInt.parse(currentBalance),
23 + receivableBalance = BigInt.parse(receivableBalance),
24 + super(0, 0);
25 +
26 @override
27 String get formattedAvailableBalance {
27 - return NanoUtil.getRawAsUsableString(currentBalance.toString(), NanoUtil.rawPerNano);
28 + return NanoAmounts.getRawAsUsableString(currentBalance.toString(), NanoAmounts.rawPerNano);
29 }
30
31 @override
32 String get formattedAdditionalBalance {
32 - return NanoUtil.getRawAsUsableString(receivableBalance.toString(), NanoUtil.rawPerNano);
33 + return NanoAmounts.getRawAsUsableString(receivableBalance.toString(), NanoAmounts.rawPerNano);
34 }
35 }
cw_nano/lib/nano_client.dart
+9 -2
@@ -4,10 +4,10 @@ import 'dart:convert';
4 import 'package:cw_core/nano_account_info_response.dart';
5 import 'package:cw_nano/nano_balance.dart';
6 import 'package:cw_nano/nano_transaction_model.dart';
7 -import 'package:cw_nano/nano_util.dart';
7 import 'package:http/http.dart' as http;
8 import 'package:nanodart/nanodart.dart';
9 import 'package:cw_core/node.dart';
10 +import 'package:nanoutil/nanoutil.dart';
11 import 'package:shared_preferences/shared_preferences.dart';
12
13 class NanoClient {
@@ -61,6 +61,13 @@ class NanoClient {
61 ),
62 );
63 final data = await jsonDecode(response.body);
64 + if (response.statusCode != 200 ||
65 + data["error"] != null ||
66 + data["balance"] == null ||
67 + data["receivable"] == null) {
68 + throw Exception(
69 + "Error while trying to get balance! ${data["error"] != null ? data["error"] : ""}");
70 + }
71 final String currentBalance = data["balance"] as String;
72 final String receivableBalance = data["receivable"] as String;
73 final BigInt cur = BigInt.parse(currentBalance);
@@ -203,7 +210,7 @@ class NanoClient {
210 String? previousHash,
211 }) async {
212 // our address:
206 - final String publicAddress = NanoUtil.privateKeyToAddress(privateKey);
213 + final String publicAddress = NanoDerivations.privateKeyToAddress(privateKey);
214
215 // first get the current account balance:
216 if (balanceAfterTx == null) {
cw_nano/lib/nano_transaction_info.dart
+12 -3
@@ -1,7 +1,7 @@
1 import 'package:cw_core/format_amount.dart';
2 import 'package:cw_core/transaction_direction.dart';
3 import 'package:cw_core/transaction_info.dart';
4 -import 'package:cw_nano/nano_util.dart';
4 +import 'package:nanoutil/nanoutil.dart';
5
6 class NanoTransactionInfo extends TransactionInfo {
7 NanoTransactionInfo({
@@ -13,6 +13,8 @@ class NanoTransactionInfo extends TransactionInfo {
13 required this.confirmed,
14 required this.date,
15 required this.confirmations,
16 + required this.to,
17 + required this.from,
18 }) : this.amount = amountRaw.toInt();
19
20 final String id;
@@ -24,14 +26,17 @@ class NanoTransactionInfo extends TransactionInfo {
26 final bool confirmed;
27 final int confirmations;
28 final String tokenSymbol;
29 + final String? to;
30 + final String? from;
31 String? _fiatAmount;
32
33 bool get isPending => !this.confirmed;
34
35 @override
36 String amountFormatted() {
33 - final String amt = NanoUtil.getRawAsUsableString(amountRaw.toString(), NanoUtil.rawPerNano);
34 - final String acc = NanoUtil.getRawAccuracy(amountRaw.toString(), NanoUtil.rawPerNano);
37 + final String amt =
38 + NanoAmounts.getRawAsUsableString(amountRaw.toString(), NanoAmounts.rawPerNano);
39 + final String acc = NanoAmounts.getRawAccuracy(amountRaw.toString(), NanoAmounts.rawPerNano);
40 return "$acc$amt $tokenSymbol";
41 }
42
@@ -54,6 +59,8 @@ class NanoTransactionInfo extends TransactionInfo {
59 confirmed: data['confirmed'] as bool,
60 confirmations: data['confirmations'] as int,
61 tokenSymbol: data['tokenSymbol'] as String,
62 + to: data['to'] as String,
63 + from: data['from'] as String,
64 );
65 }
66
@@ -66,5 +73,7 @@ class NanoTransactionInfo extends TransactionInfo {
73 'confirmed': confirmed,
74 'confirmations': confirmations,
75 'tokenSymbol': tokenSymbol,
76 + 'to': to,
77 + 'from': from,
78 };
79 }
cw_nano/lib/nano_util.dart deleted
-193
@@ -1,193 +0,0 @@
1 -import 'dart:convert';
2 -import 'dart:typed_data';
3 -
4 -import 'package:convert/convert.dart';
5 -import "package:ed25519_hd_key/ed25519_hd_key.dart";
6 -import 'package:libcrypto/libcrypto.dart';
7 -import 'package:nanodart/nanodart.dart';
8 -import 'package:decimal/decimal.dart';
9 -
10 -class NanoUtil {
11 - // standard:
12 - static String seedToPrivate(String seed, int index) {
13 - return NanoKeys.seedToPrivate(seed, index);
14 - }
15 -
16 - static String seedToAddress(String seed, int index) {
17 - return NanoAccounts.createAccount(
18 - NanoAccountType.NANO, privateKeyToPublic(seedToPrivate(seed, index)));
19 - }
20 -
21 - static String seedToMnemonic(String seed) {
22 - return NanoMnemomics.seedToMnemonic(seed).join(" ");
23 - }
24 -
25 - static Future<String> mnemonicToSeed(String mnemonic) async {
26 - return NanoMnemomics.mnemonicListToSeed(mnemonic.split(' '));
27 - }
28 -
29 - static String privateKeyToPublic(String privateKey) {
30 - // return NanoHelpers.byteToHex(Ed25519Blake2b.getPubkey(NanoHelpers.hexToBytes(privateKey))!);
31 - return NanoKeys.createPublicKey(privateKey);
32 - }
33 -
34 - static String addressToPublicKey(String publicAddress) {
35 - return NanoAccounts.extractPublicKey(publicAddress);
36 - }
37 -
38 - // universal:
39 - static String privateKeyToAddress(String privateKey) {
40 - return NanoAccounts.createAccount(NanoAccountType.NANO, privateKeyToPublic(privateKey));
41 - }
42 -
43 - static String publicKeyToAddress(String publicKey) {
44 - return NanoAccounts.createAccount(NanoAccountType.NANO, publicKey);
45 - }
46 -
47 - // standard + hd:
48 - static bool isValidSeed(String seed) {
49 - // Ensure seed is 64 or 128 characters long
50 - if (seed == null || (seed.length != 64 && seed.length != 128)) {
51 - return false;
52 - }
53 - // Ensure seed only contains hex characters, 0-9;A-F
54 - return NanoHelpers.isHexString(seed);
55 - }
56 -
57 - // // hd:
58 - static Future<String> hdMnemonicListToSeed(List<String> words) async {
59 - // if (words.length != 24) {
60 - // throw Exception('Expected a 24-word list, got a ${words.length} list');
61 - // }
62 - final Uint8List salt = Uint8List.fromList(utf8.encode('mnemonic'));
63 - final Pbkdf2 hasher = Pbkdf2(iterations: 2048);
64 - final String seed = await hasher.sha512(words.join(' '), salt);
65 - return seed;
66 - }
67 -
68 - static Future<String> hdSeedToPrivate(String seed, int index) async {
69 - List<int> seedBytes = hex.decode(seed);
70 - KeyData data = await ED25519_HD_KEY.derivePath("m/44'/165'/$index'", seedBytes);
71 - return hex.encode(data.key);
72 - }
73 -
74 - static Future<String> hdSeedToAddress(String seed, int index) async {
75 - return NanoAccounts.createAccount(
76 - NanoAccountType.NANO, privateKeyToPublic(await hdSeedToPrivate(seed, index)));
77 - }
78 -
79 - static Future<String> uniSeedToAddress(String seed, int index, String type) {
80 - if (type == "standard") {
81 - return Future<String>.value(seedToAddress(seed, index));
82 - } else if (type == "hd") {
83 - return hdSeedToAddress(seed, index);
84 - } else {
85 - throw Exception('Unknown seed type');
86 - }
87 - }
88 -
89 - static Future<String> uniSeedToPrivate(String seed, int index, String type) {
90 - if (type == "standard") {
91 - return Future<String>.value(seedToPrivate(seed, index));
92 - } else if (type == "hd") {
93 - return hdSeedToPrivate(seed, index);
94 - } else {
95 - throw Exception('Unknown seed type');
96 - }
97 - }
98 -
99 - static bool isValidBip39Seed(String seed) {
100 - // Ensure seed is 128 characters long
101 - if (seed.length != 128) {
102 - return false;
103 - }
104 - // Ensure seed only contains hex characters, 0-9;A-F
105 - return NanoHelpers.isHexString(seed);
106 - }
107 -
108 - // number util:
109 -
110 - static const int maxDecimalDigits = 6; // Max digits after decimal
111 - static BigInt rawPerNano = BigInt.parse("1000000000000000000000000000000");
112 - static BigInt rawPerNyano = BigInt.parse("1000000000000000000000000");
113 - static BigInt rawPerBanano = BigInt.parse("100000000000000000000000000000");
114 - static BigInt rawPerXMR = BigInt.parse("1000000000000");
115 - static BigInt convertXMRtoNano = BigInt.parse("1000000000000000000");
116 - // static BigInt convertXMRtoNano = BigInt.parse("1000000000000000000000000000");
117 -
118 - /// Convert raw to ban and return as BigDecimal
119 - ///
120 - /// @param raw 100000000000000000000000000000
121 - /// @return Decimal value 1.000000000000000000000000000000
122 - ///
123 - static Decimal getRawAsDecimal(String? raw, BigInt? rawPerCur) {
124 - rawPerCur ??= rawPerNano;
125 - final Decimal amount = Decimal.parse(raw.toString());
126 - final Decimal result = (amount / Decimal.parse(rawPerCur.toString())).toDecimal();
127 - return result;
128 - }
129 -
130 - static String truncateDecimal(Decimal input, {int digits = maxDecimalDigits}) {
131 - Decimal bigger = input.shift(digits);
132 - bigger = bigger.floor(); // chop off the decimal: 1.059 -> 1.05
133 - bigger = bigger.shift(-digits);
134 - return bigger.toString();
135 - }
136 -
137 - /// Return raw as a NANO amount.
138 - ///
139 - /// @param raw 100000000000000000000000000000
140 - /// @returns 1
141 - ///
142 - static String getRawAsUsableString(String? raw, BigInt rawPerCur) {
143 - final String res =
144 - truncateDecimal(getRawAsDecimal(raw, rawPerCur), digits: maxDecimalDigits + 9);
145 -
146 - if (raw == null || raw == "0" || raw == "00000000000000000000000000000000") {
147 - return "0";
148 - }
149 -
150 - if (!res.contains(".")) {
151 - return res;
152 - }
153 -
154 - final String numAmount = res.split(".")[0];
155 - String decAmount = res.split(".")[1];
156 -
157 - // truncate:
158 - if (decAmount.length > maxDecimalDigits) {
159 - decAmount = decAmount.substring(0, maxDecimalDigits);
160 - // remove trailing zeros:
161 - decAmount = decAmount.replaceAllMapped(RegExp(r'0+$'), (Match match) => '');
162 - if (decAmount.isEmpty) {
163 - return numAmount;
164 - }
165 - }
166 -
167 - return "$numAmount.$decAmount";
168 - }
169 -
170 - static String getRawAccuracy(String? raw, BigInt rawPerCur) {
171 - final String rawString = getRawAsUsableString(raw, rawPerCur);
172 - final String rawDecimalString = getRawAsDecimal(raw, rawPerCur).toString();
173 -
174 - if (raw == null || raw.isEmpty || raw == "0") {
175 - return "";
176 - }
177 -
178 - if (rawString != rawDecimalString) {
179 - return "~";
180 - }
181 - return "";
182 - }
183 -
184 - /// Return readable string amount as raw string
185 - /// @param amount 1.01
186 - /// @returns 101000000000000000000000000000
187 - ///
188 - static String getAmountAsRaw(String amount, BigInt rawPerCur) {
189 - final Decimal asDecimal = Decimal.parse(amount);
190 - final Decimal rawDecimal = Decimal.parse(rawPerCur.toString());
191 - return (asDecimal * rawDecimal).toString();
192 - }
193 -}
cw_nano/lib/nano_wallet.dart
+72 -26
@@ -18,7 +18,6 @@ import 'package:cw_nano/nano_client.dart';
18 import 'package:cw_nano/nano_transaction_credentials.dart';
19 import 'package:cw_nano/nano_transaction_history.dart';
20 import 'package:cw_nano/nano_transaction_info.dart';
21 -import 'package:cw_nano/nano_util.dart';
21 import 'package:cw_nano/nano_wallet_keys.dart';
22 import 'package:cw_nano/pending_nano_transaction.dart';
23 import 'package:mobx/mobx.dart';
@@ -27,6 +26,7 @@ import 'package:cw_nano/nano_wallet_addresses.dart';
26 import 'package:cw_core/wallet_base.dart';
27 import 'package:nanodart/nanodart.dart';
28 import 'package:bip39/bip39.dart' as bip39;
29 +import 'package:nanoutil/nanoutil.dart';
30
31 part 'nano_wallet.g.dart';
32
@@ -83,6 +83,8 @@ abstract class NanoWalletBase
83 @observable
84 late ObservableMap<CryptoCurrency, NanoBalance> balance;
85
86 + static const int POLL_INTERVAL_SECONDS = 10;
87 +
88 // initialize the different forms of private / public key we'll need:
89 Future<void> init() async {
90 if (_derivationType == DerivationType.unknown) {
@@ -100,11 +102,21 @@ abstract class NanoWalletBase
102 if (_derivationType == DerivationType.nano) {
103 _hexSeed = bip39.mnemonicToEntropy(_mnemonic).toUpperCase();
104 } else {
103 - _hexSeed = await NanoUtil.hdMnemonicListToSeed(_mnemonic.split(' '));
105 + _hexSeed = await NanoDerivations.hdMnemonicListToSeed(_mnemonic.split(' '));
106 }
107 }
106 - _privateKey = await NanoUtil.uniSeedToPrivate(_hexSeed!, 0, type);
107 - _publicAddress = await NanoUtil.uniSeedToAddress(_hexSeed!, 0, type);
108 + NanoDerivationType derivationType =
109 + type == "standard" ? NanoDerivationType.STANDARD : NanoDerivationType.HD;
110 + _privateKey = await NanoDerivations.universalSeedToPrivate(
111 + _hexSeed!,
112 + index: 0,
113 + type: derivationType,
114 + );
115 + _publicAddress = await NanoDerivations.universalSeedToAddress(
116 + _hexSeed!,
117 + index: 0,
118 + type: derivationType,
119 + );
120 this.walletInfo.address = _publicAddress!;
121
122 await walletAddresses.init();
@@ -125,6 +137,7 @@ abstract class NanoWalletBase
137 @override
138 void close() {
139 _client.stop();
140 + _receiveTimer?.cancel();
141 }
142
143 @action
@@ -139,6 +152,7 @@ abstract class NanoWalletBase
152
153 try {
154 await _updateBalance();
155 + await updateTransactions();
156 await _updateRep();
157 await _receiveAll();
158 } catch (e) {
@@ -173,8 +187,8 @@ abstract class NanoWalletBase
187 if (txOut.sendAll) {
188 amt = balance[currency]?.currentBalance ?? BigInt.zero;
189 } else {
176 - amt = BigInt.tryParse(NanoUtil.getAmountAsRaw(
177 - txOut.cryptoAmount?.replaceAll(',', '.') ?? "0", NanoUtil.rawPerNano)) ??
190 + amt = BigInt.tryParse(NanoAmounts.getAmountAsRaw(
191 + txOut.cryptoAmount?.replaceAll(',', '.') ?? "0", NanoAmounts.rawPerNano)) ??
192 BigInt.zero;
193 }
194
@@ -186,9 +200,7 @@ abstract class NanoWalletBase
200
201 final block = await _client.constructSendBlock(
202 amountRaw: amt.toString(),
189 - destinationAddress: txOut.isParsedAddress
190 - ? txOut.extractedAddress!
191 - : txOut.address,
203 + destinationAddress: txOut.isParsedAddress ? txOut.extractedAddress! : txOut.address,
204 privateKey: _privateKey!,
205 balanceAfterTx: runningBalance,
206 previousHash: previousHash,
@@ -236,10 +248,10 @@ abstract class NanoWalletBase
248 }
249 }
250
239 - Future<void> updateTransactions() async {
251 + Future<bool> updateTransactions() async {
252 try {
253 if (_isTransactionUpdating) {
242 - return;
254 + return false;
255 }
256
257 _isTransactionUpdating = true;
@@ -247,8 +259,10 @@ abstract class NanoWalletBase
259 transactionHistory.addMany(transactions);
260 await transactionHistory.save();
261 _isTransactionUpdating = false;
262 + return true;
263 } catch (_) {
264 _isTransactionUpdating = false;
265 + return false;
266 }
267 }
268
@@ -261,16 +275,17 @@ abstract class NanoWalletBase
275 final Map<String, NanoTransactionInfo> result = {};
276
277 for (var transactionModel in transactions) {
278 + final bool isSend = transactionModel.type == "send";
279 result[transactionModel.hash] = NanoTransactionInfo(
280 id: transactionModel.hash,
281 amountRaw: transactionModel.amount,
282 height: transactionModel.height,
268 - direction: transactionModel.type == "send"
269 - ? TransactionDirection.outgoing
270 - : TransactionDirection.incoming,
283 + direction: isSend ? TransactionDirection.outgoing : TransactionDirection.incoming,
284 confirmed: transactionModel.confirmed,
285 date: transactionModel.date ?? DateTime.now(),
286 confirmations: transactionModel.confirmed ? 1 : 0,
287 + to: isSend ? transactionModel.account : address,
288 + from: isSend ? address : transactionModel.account,
289 );
290 }
291
@@ -312,11 +327,10 @@ abstract class NanoWalletBase
327 Future<void> startSync() async {
328 try {
329 syncStatus = AttemptingSyncStatus();
315 - await _updateBalance();
316 - await updateTransactions();
330
331 + // setup a timer to receive transactions periodically:
332 _receiveTimer?.cancel();
319 - _receiveTimer = Timer.periodic(const Duration(seconds: 15), (timer) async {
333 + _receiveTimer = Timer.periodic(const Duration(seconds: POLL_INTERVAL_SECONDS), (timer) async {
334 // get our balance:
335 await _updateBalance();
336 // if we have anything to receive, process it:
@@ -325,6 +339,14 @@ abstract class NanoWalletBase
339 }
340 });
341
342 + // also run once, immediately:
343 + await _updateBalance();
344 + bool updateSuccess = await updateTransactions();
345 + if (!updateSuccess) {
346 + syncStatus = FailedSyncStatus();
347 + return;
348 + }
349 +
350 syncStatus = SyncedSyncStatus();
351 } catch (e) {
352 print(e);
@@ -353,9 +375,11 @@ abstract class NanoWalletBase
375
376 final data = json.decode(jsonSource) as Map;
377 final mnemonic = data['mnemonic'] as String;
356 - final balance = NanoBalance.fromString(
357 - formattedCurrentBalance: data['currentBalance'] as String? ?? "0",
358 - formattedReceivableBalance: data['receivableBalance'] as String? ?? "0");
378 +
379 + final balance = NanoBalance.fromRawString(
380 + currentBalance: data['currentBalance'] as String? ?? "0",
381 + receivableBalance: data['receivableBalance'] as String? ?? "0",
382 + );
383
384 DerivationType derivationType = DerivationType.nano;
385 if (data['derivationType'] == "DerivationType.bip39") {
@@ -374,12 +398,26 @@ abstract class NanoWalletBase
398 }
399
400 Future<void> _updateBalance() async {
401 + var oldBalance = balance[currency];
402 try {
403 balance[currency] = await _client.getBalance(_publicAddress!);
404 } catch (e) {
405 print("Failed to get balance $e");
406 + // if we don't have a balance, we should at least create one, since it's a late binding
407 + // otherwise, it's better to just leave it as whatever it was before:
408 + if (balance[currency] == null) {
409 + balance[currency] =
410 + NanoBalance(currentBalance: BigInt.zero, receivableBalance: BigInt.zero);
411 + }
412 + }
413 + // don't save unnecessarily:
414 + // trying to save too frequently can cause problems with the file system
415 + // since nano is updated frequently this can be a problem, so we only save if there is a change:
416 + if (oldBalance == null ||
417 + balance[currency]!.currentBalance != oldBalance.currentBalance ||
418 + balance[currency]!.receivableBalance != oldBalance.receivableBalance) {
419 + await save();
420 }
382 - await save();
421 }
422
423 Future<void> _updateRep() async {
@@ -394,11 +432,19 @@ abstract class NanoWalletBase
432 }
433
434 Future<void> regenerateAddress() async {
397 - final String type = (_derivationType == DerivationType.nano) ? "standard" : "hd";
398 - _privateKey =
399 - await NanoUtil.uniSeedToPrivate(_hexSeed!, this.walletAddresses.account!.id, type);
400 - _publicAddress =
401 - await NanoUtil.uniSeedToAddress(_hexSeed!, this.walletAddresses.account!.id, type);
435 + final NanoDerivationType type = (_derivationType == DerivationType.nano)
436 + ? NanoDerivationType.STANDARD
437 + : NanoDerivationType.HD;
438 + _privateKey = await NanoDerivations.universalSeedToPrivate(
439 + _hexSeed!,
440 + index: this.walletAddresses.account!.id,
441 + type: type,
442 + );
443 + _publicAddress = await NanoDerivations.universalSeedToAddress(
444 + _hexSeed!,
445 + index: this.walletAddresses.account!.id,
446 + type: type,
447 + );
448
449 this.walletInfo.address = _publicAddress!;
450 this.walletAddresses.address = _publicAddress!;
cw_nano/lib/nano_wallet_service.dart
+3 -3
@@ -6,12 +6,12 @@ import 'package:cw_core/wallet_info.dart';
6 import 'package:cw_core/wallet_service.dart';
7 import 'package:cw_core/wallet_type.dart';
8 import 'package:cw_nano/nano_mnemonic.dart' as nm;
9 -import 'package:cw_nano/nano_util.dart';
9 import 'package:cw_nano/nano_wallet.dart';
10 import 'package:cw_nano/nano_wallet_creation_credentials.dart';
11 import 'package:hive/hive.dart';
12 import 'package:bip39/bip39.dart' as bip39;
13 import 'package:nanodart/nanodart.dart';
14 +import 'package:nanoutil/nanoutil.dart';
15
16 class NanoWalletService extends WalletService<NanoNewWalletCredentials,
17 NanoRestoreWalletFromSeedCredentials, NanoRestoreWalletFromKeysCredentials> {
@@ -30,7 +30,7 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
30 // nano standard:
31 DerivationType derivationType = DerivationType.nano;
32 String seedKey = NanoSeeds.generateSeed();
33 - String mnemonic = NanoUtil.seedToMnemonic(seedKey);
33 + String mnemonic = NanoDerivations.standardSeedToMnemonic(seedKey);
34
35 credentials.walletInfo!.derivationType = derivationType;
36
@@ -95,7 +95,7 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
95 // we can't derive the mnemonic from the key in all cases, only if it's a "nano" seed
96 if (credentials.seedKey.length == 64) {
97 try {
98 - mnemonic = NanoUtil.seedToMnemonic(credentials.seedKey);
98 + mnemonic = NanoDerivations.standardSeedToMnemonic(credentials.seedKey);
99 } catch (e) {
100 throw Exception("Wasn't a valid nano style seed!");
101 }
cw_nano/lib/pending_nano_transaction.dart
+4 -4
@@ -1,6 +1,6 @@
1 import 'package:cw_core/pending_transaction.dart';
2 import 'package:cw_nano/nano_client.dart';
3 -import 'package:cw_nano/nano_util.dart';
3 +import 'package:nanoutil/nanoutil.dart';
4
5 class PendingNanoTransaction with PendingTransaction {
6 PendingNanoTransaction({
@@ -18,13 +18,13 @@ class PendingNanoTransaction with PendingTransaction {
18
19 @override
20 String get amountFormatted {
21 - final String amt = NanoUtil.getRawAsUsableString(amount.toString(), NanoUtil.rawPerNano);
21 + final String amt = NanoAmounts.getRawAsUsableString(amount.toString(), NanoAmounts.rawPerNano);
22 return amt;
23 }
24
25 String get accurateAmountFormatted {
26 - final String amt = NanoUtil.getRawAsUsableString(amount.toString(), NanoUtil.rawPerNano);
27 - final String acc = NanoUtil.getRawAccuracy(amount.toString(), NanoUtil.rawPerNano);
26 + final String amt = NanoAmounts.getRawAsUsableString(amount.toString(), NanoAmounts.rawPerNano);
27 + final String acc = NanoAmounts.getRawAccuracy(amount.toString(), NanoAmounts.rawPerNano);
28 return "$acc$amt";
29 }
30
cw_nano/pubspec.yaml
+4
@@ -22,6 +22,10 @@ dependencies:
22 hex: ^0.2.0
23 http: ^1.1.0
24 shared_preferences: ^2.0.15
25 + nanoutil:
26 + git:
27 + url: https://github.com/perishllc/nanoutil.git
28 + ref: c37e72817cf0a28162f43124f79661d6c8e0098f
29 cw_core:
30 path: ../cw_core
31
lib/nano/cw_nano.dart
+19 -197
@@ -173,7 +173,7 @@ class CWNano extends Nano {
173 }
174
175 @override
176 - Future<void> updateTransactions(Object wallet) async {
176 + Future<bool> updateTransactions(Object wallet) async {
177 return (wallet as NanoWallet).updateTransactions();
178 }
179
@@ -189,116 +189,10 @@ class CWNano extends Nano {
189 }
190
191 class CWNanoUtil extends NanoUtil {
192 - // standard:
193 - @override
194 - String seedToPrivate(String seed, int index) {
195 - return ND.NanoKeys.seedToPrivate(seed, index);
196 - }
197 -
198 - @override
199 - String seedToAddress(String seed, int index) {
200 - return ND.NanoAccounts.createAccount(
201 - ND.NanoAccountType.NANO, privateKeyToPublic(seedToPrivate(seed, index)));
202 - }
203 -
204 - @override
205 - String seedToMnemonic(String seed) {
206 - return NanoMnemomics.seedToMnemonic(seed).join(" ");
207 - }
208 -
209 - @override
210 - Future<String> mnemonicToSeed(String mnemonic) async {
211 - return NanoMnemomics.mnemonicListToSeed(mnemonic.split(' '));
212 - }
213 -
214 - @override
215 - String privateKeyToPublic(String privateKey) {
216 - // return NanoHelpers.byteToHex(Ed25519Blake2b.getPubkey(NanoHelpers.hexToBytes(privateKey))!);
217 - return ND.NanoKeys.createPublicKey(privateKey);
218 - }
219 -
220 - @override
221 - String addressToPublicKey(String publicAddress) {
222 - return ND.NanoAccounts.extractPublicKey(publicAddress);
223 - }
224 -
225 - // universal:
226 - @override
227 - String privateKeyToAddress(String privateKey) {
228 - return ND.NanoAccounts.createAccount(ND.NanoAccountType.NANO, privateKeyToPublic(privateKey));
229 - }
230 -
231 - @override
232 - String publicKeyToAddress(String publicKey) {
233 - return ND.NanoAccounts.createAccount(ND.NanoAccountType.NANO, publicKey);
234 - }
235 -
236 - // standard + hd:
237 - @override
238 - bool isValidSeed(String seed) {
239 - // Ensure seed is 64 or 128 characters long
240 - if (seed.length != 64 && seed.length != 128) {
241 - return false;
242 - }
243 - // Ensure seed only contains hex characters, 0-9;A-F
244 - return ND.NanoHelpers.isHexString(seed);
245 - }
246 -
247 - // hd:
248 - @override
249 - Future<String> hdMnemonicListToSeed(List<String> words) async {
250 - // if (words.length != 24) {
251 - // throw Exception('Expected a 24-word list, got a ${words.length} list');
252 - // }
253 - final Uint8List salt = Uint8List.fromList(utf8.encode('mnemonic'));
254 - final Pbkdf2 hasher = Pbkdf2(iterations: 2048);
255 - final String seed = await hasher.sha512(words.join(' '), salt);
256 - return seed;
257 - }
258 -
259 - @override
260 - Future<String> hdSeedToPrivate(String seed, int index) async {
261 - List<int> seedBytes = hex.decode(seed);
262 - KeyData data = await ED25519_HD_KEY.derivePath("m/44'/165'/$index'", seedBytes);
263 - return hex.encode(data.key);
264 - }
265 -
266 - @override
267 - Future<String> hdSeedToAddress(String seed, int index) async {
268 - return ND.NanoAccounts.createAccount(
269 - ND.NanoAccountType.NANO, privateKeyToPublic(await hdSeedToPrivate(seed, index)));
270 - }
271 -
272 - @override
273 - Future<String> uniSeedToAddress(String seed, int index, String type) {
274 - if (type == "standard") {
275 - return Future<String>.value(seedToAddress(seed, index));
276 - } else if (type == "hd") {
277 - return hdSeedToAddress(seed, index);
278 - } else {
279 - throw Exception('Unknown seed type');
280 - }
281 - }
282 -
283 - @override
284 - Future<String> uniSeedToPrivate(String seed, int index, String type) {
285 - if (type == "standard") {
286 - return Future<String>.value(seedToPrivate(seed, index));
287 - } else if (type == "hd") {
288 - return hdSeedToPrivate(seed, index);
289 - } else {
290 - throw Exception('Unknown seed type');
291 - }
292 - }
192
193 @override
194 bool isValidBip39Seed(String seed) {
296 - // Ensure seed is 128 characters long
297 - if (seed.length != 128) {
298 - return false;
299 - }
300 - // Ensure seed only contains hex characters, 0-9;A-F
301 - return ND.NanoHelpers.isHexString(seed);
195 + return NanoDerivations.isValidBip39Seed(seed);
196 }
197
198 // number util:
@@ -309,92 +203,20 @@ class CWNanoUtil extends NanoUtil {
203 BigInt rawPerBanano = BigInt.parse("100000000000000000000000000000");
204 BigInt rawPerXMR = BigInt.parse("1000000000000");
205 BigInt convertXMRtoNano = BigInt.parse("1000000000000000000");
312 - // static BigInt convertXMRtoNano = BigInt.parse("1000000000000000000000000000");
313 -
314 - /// Convert raw to ban and return as BigDecimal
315 - ///
316 - /// @param raw 100000000000000000000000000000
317 - /// @return Decimal value 1.000000000000000000000000000000
318 - ///
319 - Decimal _getRawAsDecimal(String? raw, BigInt? rawPerCur) {
320 - rawPerCur ??= rawPerNano;
321 - final Decimal amount = Decimal.parse(raw.toString());
322 - final Decimal result = (amount / Decimal.parse(rawPerCur.toString())).toDecimal();
323 - return result;
324 - }
325 -
326 - @override
327 - String getRawAsDecimalString(String? raw, BigInt? rawPerCur) {
328 - final Decimal result = _getRawAsDecimal(raw, rawPerCur);
329 - return result.toString();
330 - }
206
332 - @override
333 - String truncateDecimal(Decimal input, {int digits = maxDecimalDigits}) {
334 - Decimal bigger = input.shift(digits);
335 - bigger = bigger.floor(); // chop off the decimal: 1.059 -> 1.05
336 - bigger = bigger.shift(-digits);
337 - return bigger.toString();
338 - }
339 -
340 - /// Return raw as a NANO amount.
341 - ///
342 - /// @param raw 100000000000000000000000000000
343 - /// @returns 1
344 - ///
207 @override
208 String getRawAsUsableString(String? raw, BigInt rawPerCur) {
347 - final String res =
348 - truncateDecimal(_getRawAsDecimal(raw, rawPerCur), digits: maxDecimalDigits + 9);
349 -
350 - if (raw == null || raw == "0" || raw == "00000000000000000000000000000000") {
351 - return "0";
352 - }
353 -
354 - if (!res.contains(".")) {
355 - return res;
356 - }
357 -
358 - final String numAmount = res.split(".")[0];
359 - String decAmount = res.split(".")[1];
360 -
361 - // truncate:
362 - if (decAmount.length > maxDecimalDigits) {
363 - decAmount = decAmount.substring(0, maxDecimalDigits);
364 - // remove trailing zeros:
365 - decAmount = decAmount.replaceAllMapped(RegExp(r'0+$'), (Match match) => '');
366 - if (decAmount.isEmpty) {
367 - return numAmount;
368 - }
369 - }
370 -
371 - return "$numAmount.$decAmount";
209 + return NanoAmounts.getRawAsUsableString(raw, rawPerCur);
210 }
211
212 @override
213 String getRawAccuracy(String? raw, BigInt rawPerCur) {
376 - final String rawString = getRawAsUsableString(raw, rawPerCur);
377 - final String rawDecimalString = _getRawAsDecimal(raw, rawPerCur).toString();
378 -
379 - if (raw == null || raw.isEmpty || raw == "0") {
380 - return "";
381 - }
382 -
383 - if (rawString != rawDecimalString) {
384 - return "~";
385 - }
386 - return "";
214 + return NanoAmounts.getRawAccuracy(raw, rawPerCur);
215 }
216
389 - /// Return readable string amount as raw string
390 - /// @param amount 1.01
391 - /// @returns 101000000000000000000000000000
392 - ///
217 @override
218 String getAmountAsRaw(String amount, BigInt rawPerCur) {
395 - final Decimal asDecimal = Decimal.parse(amount);
396 - final Decimal rawDecimal = Decimal.parse(rawPerCur.toString());
397 - return (asDecimal * rawDecimal).toString();
219 + return NanoAmounts.getAmountAsRaw(amount, rawPerCur);
220 }
221
222 @override
@@ -411,29 +233,29 @@ class CWNanoUtil extends NanoUtil {
233 if (seedKey != null) {
234 if (seedKey.length == 64) {
235 try {
414 - mnemonic = nanoUtil!.seedToMnemonic(seedKey);
236 + mnemonic = NanoDerivations.standardSeedToMnemonic(seedKey);
237 } catch (e) {
238 print("not a valid 'nano' seed key");
239 }
240 }
241 if (derivationType == DerivationType.bip39) {
420 - publicAddress = await hdSeedToAddress(seedKey, 0);
242 + publicAddress = await NanoDerivations.hdSeedToAddress(seedKey, index: 0);
243 } else if (derivationType == DerivationType.nano) {
422 - publicAddress = await seedToAddress(seedKey, 0);
244 + publicAddress = await NanoDerivations.standardSeedToAddress(seedKey, index: 0);
245 }
246 }
247
248 if (derivationType == DerivationType.bip39) {
249 if (mnemonic != null) {
428 - seedKey = await hdMnemonicListToSeed(mnemonic.split(' '));
429 - publicAddress = await hdSeedToAddress(seedKey, 0);
250 + seedKey = await NanoDerivations.hdMnemonicListToSeed(mnemonic.split(' '));
251 + publicAddress = await NanoDerivations.hdSeedToAddress(seedKey, index: 0);
252 }
253 }
254
255 if (derivationType == DerivationType.nano) {
256 if (mnemonic != null) {
435 - seedKey = await mnemonicToSeed(mnemonic);
436 - publicAddress = await seedToAddress(seedKey, 0);
257 + seedKey = await NanoDerivations.standardMnemonicToSeed(mnemonic);
258 + publicAddress = await NanoDerivations.standardSeedToAddress(seedKey, index: 0);
259 }
260 }
261
@@ -461,7 +283,7 @@ class CWNanoUtil extends NanoUtil {
283 return [DerivationType.bip39];
284 } else if (seedKey?.length == 64) {
285 try {
464 - mnemonic = nanoUtil!.seedToMnemonic(seedKey!);
286 + mnemonic = NanoDerivations.standardSeedToMnemonic(seedKey!);
287 } catch (e) {
288 print("not a valid 'nano' seed key");
289 }
@@ -475,19 +297,19 @@ class CWNanoUtil extends NanoUtil {
297 nanoClient.connect(node);
298
299 if (mnemonic != null) {
478 - seedKey = await hdMnemonicListToSeed(mnemonic.split(' '));
479 - publicAddressBip39 = await hdSeedToAddress(seedKey, 0);
300 + seedKey = await NanoDerivations.hdMnemonicListToSeed(mnemonic.split(' '));
301 + publicAddressBip39 = await NanoDerivations.hdSeedToAddress(seedKey, index: 0);
302
481 - seedKey = await mnemonicToSeed(mnemonic);
482 - publicAddressStandard = await seedToAddress(seedKey, 0);
303 + seedKey = await NanoDerivations.standardMnemonicToSeed(mnemonic);
304 + publicAddressStandard = await NanoDerivations.standardSeedToAddress(seedKey, index: 0);
305 } else if (seedKey != null) {
306 try {
485 - publicAddressBip39 = await hdSeedToAddress(seedKey, 0);
307 + publicAddressBip39 = await NanoDerivations.hdSeedToAddress(seedKey, index: 0);
308 } catch (e) {
309 return [DerivationType.nano];
310 }
311 try {
490 - publicAddressStandard = await seedToAddress(seedKey, 0);
312 + publicAddressStandard = await NanoDerivations.standardSeedToAddress(seedKey, index: 0);
313 } catch (e) {
314 return [DerivationType.bip39];
315 }
lib/view_model/dashboard/balance_view_model.dart
+5
@@ -125,6 +125,8 @@ abstract class BalanceViewModelBase with Store {
125 case WalletType.haven:
126 case WalletType.ethereum:
127 case WalletType.polygon:
128 + case WalletType.nano:
129 + case WalletType.banano:
130 return S.current.xmr_available_balance;
131 default:
132 return S.current.confirmed;
@@ -139,6 +141,9 @@ abstract class BalanceViewModelBase with Store {
141 case WalletType.ethereum:
142 case WalletType.polygon:
143 return S.current.xmr_full_balance;
144 + case WalletType.nano:
145 + case WalletType.banano:
146 + return S.current.receivable_balance;
147 default:
148 return S.current.unconfirmed;
149 }
lib/view_model/dashboard/transaction_list_item.dart
+1 -1
@@ -101,7 +101,7 @@ class TransactionListItem extends ActionListItem with Keyable {
101 break;
102 case WalletType.nano:
103 amount = calculateFiatAmountRaw(
104 - cryptoAmount: double.parse(nanoUtil!.getRawAsDecimalString(
104 + cryptoAmount: double.parse(nanoUtil!.getRawAsUsableString(
105 nano!.getTransactionAmountRaw(transaction).toString(), nanoUtil!.rawPerNano)),
106 price: price);
107 break;
lib/view_model/transaction_details_view_model.dart
+6 -2
@@ -247,11 +247,15 @@ abstract class TransactionDetailsViewModelBase with Store {
247 void _addNanoListItems(TransactionInfo tx, DateFormat dateFormat) {
248 final _items = [
249 StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id),
250 + if (showRecipientAddress && tx.to != null)
251 + StandartListItem(title: S.current.transaction_details_recipient_address, value: tx.to!),
252 + if (showRecipientAddress && tx.from != null)
253 + StandartListItem(title: S.current.transaction_details_source_address, value: tx.from!),
254 + StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
255 StandartListItem(
256 title: S.current.transaction_details_date, value: dateFormat.format(tx.date)),
252 - StandartListItem(title: S.current.confirmations, value: (tx.confirmations > 0).toString()),
257 + StandartListItem(title: S.current.confirmed_tx, value: (tx.confirmations > 0).toString()),
258 StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'),
254 - StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()),
259 ];
260
261 items.addAll(_items);
res/values/strings_ar.arb
+5 -2
@@ -759,5 +759,8 @@
759 "default_sell_provider": " ﻲﺿﺍﺮﺘﻓﻻﺍ ﻊﻴﺒﻟﺍ ﺩﻭﺰﻣ",
760 "select_sell_provider_notice": ".ﻖﻴﺒﻄﺘﻟﺍ ﺕﺍﺩﺍﺪﻋﺇ ﻲﻓ ﻚﺑ ﺹﺎﺨﻟﺍ ﻲﺿﺍﺮﺘﻓﻻﺍ ﻊﻴﺒﻟﺍ ﺩﻭﺰﻣ ﻦﻴﻴﻌﺗ ﻖﻳﺮﻃ ﻦﻋ ﺔﺷﺎﺸﻟﺍ ﻩﺬﻫ ﻲﻄﺨﺗ",
761 "custom_drag": "مخصص (عقد وسحب)",
762 - "switchToEVMCompatibleWallet": " (Ethereum، Polygon) ﻯﺮﺧﺃ ﺓﺮﻣ ﺔﻟﻭﺎﺤﻤﻟﺍﻭ EVM ﻊﻣ ﺔﻘﻓﺍﻮﺘﻣ ﺔﻈﻔﺤﻣ ﻰﻟﺇ ﻞﻳﺪﺒﺘﻟﺍ ﻰﺟﺮﻳ"
763 -}
762 + "switchToEVMCompatibleWallet": " (Ethereum، Polygon) ﻯﺮﺧﺃ ﺓﺮﻣ ﺔﻟﻭﺎﺤﻤﻟﺍﻭ EVM ﻊﻣ ﺔﻘﻓﺍﻮﺘﻣ ﺔﻈﻔﺤﻣ ﻰﻟﺇ ﻞﻳﺪﺒﺘﻟﺍ ﻰﺟﺮﻳ",
763 + "receivable_balance": "التوازن القادم",
764 + "confirmed_tx": "مؤكد",
765 + "transaction_details_source_address": "عنوان المصدر"
766 +}
\ No newline at end of file
res/values/strings_bg.arb
+5 -2
@@ -755,5 +755,8 @@
755 "default_sell_provider": "Доставчик за продажба по подразбиране",
756 "select_sell_provider_notice": "Изберете доставчик на продажба по-горе. Можете да пропуснете този екран, като зададете своя доставчик на продажба по подразбиране в настройките на приложението.",
757 "custom_drag": "Персонализиране (задръжте и плъзнете)",
758 - "switchToEVMCompatibleWallet": "Моля, превключете към портфейл, съвместим с EVM, и опитайте отново (Ethereum, Polygon)"
759 -}
758 + "switchToEVMCompatibleWallet": "Моля, превключете към портфейл, съвместим с EVM, и опитайте отново (Ethereum, Polygon)",
759 + "receivable_balance": "Баланс за вземания",
760 + "confirmed_tx": "Потвърдено",
761 + "transaction_details_source_address": "Адрес на източника"
762 +}
\ No newline at end of file
res/values/strings_cs.arb
+5 -2
@@ -755,5 +755,8 @@
755 "default_sell_provider": "Výchozí poskytovatel prodeje",
756 "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.",
757 "custom_drag": "Custom (Hold and Drag)",
758 - "switchToEVMCompatibleWallet": "Přepněte na peněženku kompatibilní s EVM a zkuste to znovu (Ethereum, Polygon)"
759 -}
758 + "switchToEVMCompatibleWallet": "Přepněte na peněženku kompatibilní s EVM a zkuste to znovu (Ethereum, Polygon)",
759 + "receivable_balance": "Zůstatek pohledávek",
760 + "confirmed_tx": "Potvrzeno",
761 + "transaction_details_source_address": "Zdrojová adresa"
762 +}
\ No newline at end of file
res/values/strings_de.arb
+5 -2
@@ -763,5 +763,8 @@
763 "default_sell_provider": "Standard-Verkaufsanbieter",
764 "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.",
765 "custom_drag": "Custom (Hold and Drag)",
766 - "switchToEVMCompatibleWallet": "Bitte wechseln Sie zu einem EVM-kompatiblen Wallet und versuchen Sie es erneut (Ethereum, Polygon)"
767 -}
766 + "switchToEVMCompatibleWallet": "Bitte wechseln Sie zu einem EVM-kompatiblen Wallet und versuchen Sie es erneut (Ethereum, Polygon)",
767 + "receivable_balance": "Forderungsbilanz",
768 + "confirmed_tx": "Bestätigt",
769 + "transaction_details_source_address": "Quelladresse"
770 +}
\ No newline at end of file
res/values/strings_en.arb
+5 -2
@@ -764,5 +764,8 @@
764 "default_sell_provider": "Default Sell Provider",
765 "select_sell_provider_notice": "Select a sell provider above. You can skip this screen by setting your default sell provider in app settings.",
766 "custom_drag": "Custom (Hold and Drag)",
767 - "switchToEVMCompatibleWallet": "Please switch to an EVM compatible wallet and try again (Ethereum, Polygon)"
768 -}
767 + "switchToEVMCompatibleWallet": "Please switch to an EVM compatible wallet and try again (Ethereum, Polygon)",
768 + "receivable_balance": "Receivable Balance",
769 + "confirmed_tx": "Confirmed",
770 + "transaction_details_source_address": "Source address"
771 +}
\ No newline at end of file
res/values/strings_es.arb
+5 -2
@@ -763,5 +763,8 @@
763 "default_sell_provider": "Proveedor de venta predeterminado",
764 "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.",
765 "custom_drag": "Custom (mantenía y arrastre)",
766 - "switchToEVMCompatibleWallet": "Cambie a una billetera compatible con EVM e inténtelo nuevamente (Ethereum, Polygon)"
767 -}
766 + "switchToEVMCompatibleWallet": "Cambie a una billetera compatible con EVM e inténtelo nuevamente (Ethereum, Polygon)",
767 + "receivable_balance": "Saldo de cuentas por cobrar",
768 + "confirmed_tx": "Confirmado",
769 + "transaction_details_source_address": "Dirección de la fuente"
770 +}
\ No newline at end of file
res/values/strings_fr.arb
+5 -2
@@ -763,5 +763,8 @@
763 "default_sell_provider": "Fournisseur de vente par défaut",
764 "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.",
765 "custom_drag": "Custom (maintenir et traîner)",
766 - "switchToEVMCompatibleWallet": "Veuillez passer à un portefeuille compatible EVM et réessayer (Ethereum, Polygon)"
767 -}
766 + "switchToEVMCompatibleWallet": "Veuillez passer à un portefeuille compatible EVM et réessayer (Ethereum, Polygon)",
767 + "receivable_balance": "Solde de créances",
768 + "confirmed_tx": "Confirmé",
769 + "transaction_details_source_address": "Adresse source"
770 +}
\ No newline at end of file
res/values/strings_ha.arb
+5 -2
@@ -745,5 +745,8 @@
745 "default_sell_provider": "Tsohuwar Mai Bayar Siyarwa",
746 "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.",
747 "custom_drag": "Al'ada (riƙe da ja)",
748 - "switchToEVMCompatibleWallet": "Da fatan za a canza zuwa walat ɗin EVM mai jituwa kuma a sake gwadawa (Ethereum, Polygon)"
749 -}
748 + "switchToEVMCompatibleWallet": "Da fatan za a canza zuwa walat ɗin EVM mai jituwa kuma a sake gwadawa (Ethereum, Polygon)",
749 + "receivable_balance": "Daidaituwa da daidaituwa",
750 + "confirmed_tx": "Tabbatar",
751 + "transaction_details_source_address": "Adireshin Incord"
752 +}
\ No newline at end of file
res/values/strings_hi.arb
+5 -2
@@ -763,5 +763,8 @@
763 "default_sell_provider": "डिफ़ॉल्ट विक्रय प्रदाता",
764 "select_sell_provider_notice": "ऊपर एक विक्रय प्रदाता का चयन करें। आप ऐप सेटिंग में अपना डिफ़ॉल्ट विक्रय प्रदाता सेट करके इस स्क्रीन को छोड़ सकते हैं।",
765 "custom_drag": "कस्टम (पकड़ और खींचें)",
766 - "switchToEVMCompatibleWallet": "कृपया ईवीएम संगत वॉलेट पर स्विच करें और पुनः प्रयास करें (एथेरियम, पॉलीगॉन)"
767 -}
766 + "switchToEVMCompatibleWallet": "कृपया ईवीएम संगत वॉलेट पर स्विच करें और पुनः प्रयास करें (एथेरियम, पॉलीगॉन)",
767 + "receivable_balance": "प्राप्य शेष",
768 + "confirmed_tx": "की पुष्टि",
769 + "transaction_details_source_address": "स्रोत पता"
770 +}
\ No newline at end of file
res/values/strings_hr.arb
+5 -2
@@ -761,5 +761,8 @@
761 "default_sell_provider": "Zadani dobavljač prodaje",
762 "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.",
763 "custom_drag": "Prilagođeni (držite i povucite)",
764 - "switchToEVMCompatibleWallet": "Prijeđite na novčanik kompatibilan s EVM-om i pokušajte ponovno (Ethereum, Polygon)"
765 -}
764 + "switchToEVMCompatibleWallet": "Prijeđite na novčanik kompatibilan s EVM-om i pokušajte ponovno (Ethereum, Polygon)",
765 + "receivable_balance": "Stanje potraživanja",
766 + "confirmed_tx": "Potvrđen",
767 + "transaction_details_source_address": "Adresa izvora"
768 +}
\ No newline at end of file
res/values/strings_id.arb
+5 -2
@@ -751,5 +751,8 @@
751 "default_sell_provider": "Penyedia Penjualan Default",
752 "select_sell_provider_notice": "Pilih penyedia jual di atas. Anda dapat melewati layar ini dengan mengatur penyedia penjualan default Anda di pengaturan aplikasi.",
753 "custom_drag": "Khusus (tahan dan seret)",
754 - "switchToEVMCompatibleWallet": "Silakan beralih ke dompet yang kompatibel dengan EVM dan coba lagi (Ethereum, Polygon)"
755 -}
754 + "switchToEVMCompatibleWallet": "Silakan beralih ke dompet yang kompatibel dengan EVM dan coba lagi (Ethereum, Polygon)",
755 + "receivable_balance": "Saldo piutang",
756 + "confirmed_tx": "Dikonfirmasi",
757 + "transaction_details_source_address": "Alamat sumber"
758 +}
\ No newline at end of file
res/values/strings_it.arb
+5 -2
@@ -763,5 +763,8 @@
763 "default_sell_provider": "Fornitore di vendita predefinito",
764 "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.",
765 "custom_drag": "Custom (Hold and Drag)",
766 - "switchToEVMCompatibleWallet": "Passa a un portafoglio compatibile con EVM e riprova (Ethereum, Polygon)"
767 -}
766 + "switchToEVMCompatibleWallet": "Passa a un portafoglio compatibile con EVM e riprova (Ethereum, Polygon)",
767 + "receivable_balance": "Bilanciamento creditizio",
768 + "confirmed_tx": "Confermato",
769 + "transaction_details_source_address": "Indirizzo di partenza"
770 +}
\ No newline at end of file
res/values/strings_ja.arb
+5 -2
@@ -763,5 +763,8 @@
763 "default_sell_provider": "デフォルトの販売プロバイダー",
764 "select_sell_provider_notice": "上記の販売プロバイダーを選択してください。アプリ設定でデフォルトの販売プロバイダーを設定することで、この画面をスキップできます。",
765 "custom_drag": "カスタム(ホールドとドラッグ)",
766 - "switchToEVMCompatibleWallet": "EVM 互換のウォレットに切り替えて再試行してください (イーサリアム、ポリゴン)"
767 -}
766 + "switchToEVMCompatibleWallet": "EVM 互換のウォレットに切り替えて再試行してください (イーサリアム、ポリゴン)",
767 + "receivable_balance": "売掛金残高",
768 + "confirmed_tx": "確認済み",
769 + "transaction_details_source_address": "ソースアドレス"
770 +}
\ No newline at end of file
res/values/strings_ko.arb
+5 -2
@@ -761,5 +761,8 @@
761 "default_sell_provider": "기본 판매 공급자",
762 "select_sell_provider_notice": "위에서 판매 공급자를 선택하세요. 앱 설정에서 기본 판매 공급자를 설정하면 이 화면을 건너뛸 수 있습니다.",
763 "custom_drag": "사용자 정의 (홀드 앤 드래그)",
764 - "switchToEVMCompatibleWallet": "EVM 호환 지갑으로 전환 후 다시 시도해 주세요. (이더리움, 폴리곤)"
765 -}
764 + "switchToEVMCompatibleWallet": "EVM 호환 지갑으로 전환 후 다시 시도해 주세요. (이더리움, 폴리곤)",
765 + "receivable_balance": "채권 잔액",
766 + "confirmed_tx": "확인",
767 + "transaction_details_source_address": "소스 주소"
768 +}
\ No newline at end of file
res/values/strings_my.arb
+5 -2
@@ -761,5 +761,8 @@
761 "default_sell_provider": "ပုံသေရောင်းချပေးသူ",
762 "select_sell_provider_notice": "အထက်ဖော်ပြပါ အရောင်းဝန်ဆောင်မှုပေးသူကို ရွေးပါ။ အက်ပ်ဆက်တင်များတွင် သင်၏မူလရောင်းချပေးသူကို သတ်မှတ်ခြင်းဖြင့် ဤစခရင်ကို ကျော်နိုင်သည်။",
763 "custom_drag": "စိတ်ကြိုက် (Drag)",
764 - "switchToEVMCompatibleWallet": "ကျေးဇူးပြု၍ EVM တွဲဖက်သုံးနိုင်သော ပိုက်ဆံအိတ်သို့ ပြောင်းပြီး ထပ်စမ်းကြည့်ပါ (Ethereum၊ Polygon)"
765 -}
764 + "switchToEVMCompatibleWallet": "ကျေးဇူးပြု၍ EVM တွဲဖက်သုံးနိုင်သော ပိုက်ဆံအိတ်သို့ ပြောင်းပြီး ထပ်စမ်းကြည့်ပါ (Ethereum၊ Polygon)",
765 + "receivable_balance": "လက်ကျန်ငွေ",
766 + "confirmed_tx": "အတည်ပြုသည်",
767 + "transaction_details_source_address": "အရင်းအမြစ်လိပ်စာ"
768 +}
\ No newline at end of file
res/values/strings_nl.arb
+5 -2
@@ -763,5 +763,8 @@
763 "default_sell_provider": "Standaard verkoopaanbieder",
764 "select_sell_provider_notice": "Selecteer hierboven een verkoopaanbieder. U kunt dit scherm overslaan door uw standaardverkoopprovider in te stellen in de app-instellingen.",
765 "custom_drag": "Custom (vasthouden en slepen)",
766 - "switchToEVMCompatibleWallet": "Schakel over naar een EVM-compatibele portemonnee en probeer het opnieuw (Ethereum, Polygon)"
767 -}
766 + "switchToEVMCompatibleWallet": "Schakel over naar een EVM-compatibele portemonnee en probeer het opnieuw (Ethereum, Polygon)",
767 + "receivable_balance": "Het saldo",
768 + "confirmed_tx": "Bevestigd",
769 + "transaction_details_source_address": "Bron adres"
770 +}
\ No newline at end of file
res/values/strings_pl.arb
+5 -2
@@ -763,5 +763,8 @@
763 "default_sell_provider": "Domyślny dostawca sprzedaży",
764 "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.",
765 "custom_drag": "Niestandardowe (trzymaj i przeciągnij)",
766 - "switchToEVMCompatibleWallet": "Przejdź na portfel zgodny z EVM i spróbuj ponownie (Ethereum, Polygon)"
767 -}
766 + "switchToEVMCompatibleWallet": "Przejdź na portfel zgodny z EVM i spróbuj ponownie (Ethereum, Polygon)",
767 + "receivable_balance": "Saldo należności",
768 + "confirmed_tx": "Potwierdzony",
769 + "transaction_details_source_address": "Adres źródłowy"
770 +}
\ No newline at end of file
res/values/strings_pt.arb
+5 -2
@@ -762,5 +762,8 @@
762 "default_sell_provider": "Provedor de venda padrão",
763 "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.",
764 "custom_drag": "Personalizado (segure e arraste)",
765 - "switchToEVMCompatibleWallet": "Mude para uma carteira compatível com EVM e tente novamente (Ethereum, Polygon)"
766 -}
765 + "switchToEVMCompatibleWallet": "Mude para uma carteira compatível com EVM e tente novamente (Ethereum, Polygon)",
766 + "receivable_balance": "Saldo a receber",
767 + "confirmed_tx": "Confirmado",
768 + "transaction_details_source_address": "Endereço de Origem"
769 +}
\ No newline at end of file
res/values/strings_ru.arb
+5 -2
@@ -763,5 +763,8 @@
763 "default_sell_provider": "Поставщик продаж по умолчанию",
764 "select_sell_provider_notice": "Выберите поставщика услуг продажи выше. Вы можете пропустить этот экран, установив поставщика услуг продаж по умолчанию в настройках приложения.",
765 "custom_drag": "Пользователь (удерживайте и перетаскивайте)",
766 - "switchToEVMCompatibleWallet": "Пожалуйста, переключитесь на кошелек, совместимый с EVM, и повторите попытку (Ethereum, Polygon)."
767 -}
766 + "switchToEVMCompatibleWallet": "Пожалуйста, переключитесь на кошелек, совместимый с EVM, и повторите попытку (Ethereum, Polygon).",
767 + "receivable_balance": "Баланс дебиторской задолженности",
768 + "confirmed_tx": "Подтвержденный",
769 + "transaction_details_source_address": "Адрес источника"
770 +}
\ No newline at end of file
res/values/strings_th.arb
+5 -2
@@ -761,5 +761,8 @@
761 "default_sell_provider": "ผู้ให้บริการการขายเริ่มต้น",
762 "select_sell_provider_notice": "เลือกผู้ให้บริการการขายด้านบน คุณสามารถข้ามหน้าจอนี้ได้โดยการตั้งค่าผู้ให้บริการการขายเริ่มต้นในการตั้งค่าแอป",
763 "custom_drag": "กำหนดเอง (ค้างและลาก)",
764 - "switchToEVMCompatibleWallet": "โปรดเปลี่ยนไปใช้กระเป๋าเงินที่รองรับ EVM แล้วลองอีกครั้ง (Ethereum, Polygon)"
765 -}
764 + "switchToEVMCompatibleWallet": "โปรดเปลี่ยนไปใช้กระเป๋าเงินที่รองรับ EVM แล้วลองอีกครั้ง (Ethereum, Polygon)",
765 + "receivable_balance": "ยอดลูกหนี้",
766 + "confirmed_tx": "ซึ่งยืนยันแล้ว",
767 + "transaction_details_source_address": "ที่อยู่แหล่งกำเนิด"
768 +}
\ No newline at end of file
res/values/strings_tl.arb
+5 -2
@@ -757,5 +757,8 @@
757 "default_sell_provider": "Default na Sell Provider",
758 "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.",
759 "custom_drag": "Pasadyang (hawakan at i -drag)",
760 - "switchToEVMCompatibleWallet": "Mangyaring lumipat sa isang EVM compatible na wallet at subukang muli (Ethereum, Polygon)"
761 -}
760 + "switchToEVMCompatibleWallet": "Mangyaring lumipat sa isang EVM compatible na wallet at subukang muli (Ethereum, Polygon)",
761 + "receivable_balance": "Natatanggap na balanse",
762 + "confirmed_tx": "Nakumpirma",
763 + "transaction_details_source_address": "SOURCE ADDRESS"
764 +}
\ No newline at end of file
res/values/strings_tr.arb
+5 -2
@@ -761,5 +761,8 @@
761 "default_sell_provider": "Varsayılan Satış Sağlayıcısı",
762 "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.",
763 "custom_drag": "Özel (Bekle ve Sürükle)",
764 - "switchToEVMCompatibleWallet": "Lütfen EVM uyumlu bir cüzdana geçin ve tekrar deneyin (Ethereum, Polygon)"
765 -}
764 + "switchToEVMCompatibleWallet": "Lütfen EVM uyumlu bir cüzdana geçin ve tekrar deneyin (Ethereum, Polygon)",
765 + "receivable_balance": "Alacak bakiyesi",
766 + "confirmed_tx": "Onaylanmış",
767 + "transaction_details_source_address": "Kaynak adresi"
768 +}
\ No newline at end of file
res/values/strings_uk.arb
+5 -2
@@ -763,5 +763,8 @@
763 "default_sell_provider": "Постачальник продажу за замовчуванням",
764 "select_sell_provider_notice": "Виберіть вище постачальника послуг продажу. Ви можете пропустити цей екран, встановивши постачальника послуг продажу за умовчанням у налаштуваннях програми.",
765 "custom_drag": "На замовлення (утримуйте та перетягується)",
766 - "switchToEVMCompatibleWallet": "Перейдіть на гаманець, сумісний з EVM, і повторіть спробу (Ethereum, Polygon)"
767 -}
766 + "switchToEVMCompatibleWallet": "Перейдіть на гаманець, сумісний з EVM, і повторіть спробу (Ethereum, Polygon)",
767 + "receivable_balance": "Баланс дебіторської заборгованості",
768 + "confirmed_tx": "Підтверджений",
769 + "transaction_details_source_address": "Адреса джерела"
770 +}
\ No newline at end of file
res/values/strings_ur.arb
+5 -2
@@ -755,5 +755,8 @@
755 "default_sell_provider": " ﮦﺪﻨﻨﮐ ﻢﮨﺍﺮﻓ ﻞﯿﺳ ﭧﻟﺎﻔﯾﮈ",
756 "select_sell_provider_notice": "۔ﮟﯿﮨ ﮯﺘﮑﺳ ﮌﻮﮭﭼ ﻮﮐ ﻦﯾﺮﮑﺳﺍ ﺱﺍ ﺮﮐ ﮮﺩ ﺐﯿﺗﺮﺗ ﻮﮐ ﮦﺪﻨﻨﮐ ﻢﮨﺍﺮﻓ ﻞﯿﺳ ﭧﻟﺎﻔﯾﮈ ﮯﻨﭘﺍ ﮟﯿﻣ ﺕﺎﺒ",
757 "custom_drag": "کسٹم (ہولڈ اینڈ ڈریگ)",
758 - "switchToEVMCompatibleWallet": "(Ethereum, Polygon) ﮟﯾﺮﮐ ﺶﺷﻮﮐ ﮦﺭﺎﺑﻭﺩ ﺭﻭﺍ ﮟﯾﺮﮐ ﭻﺋﻮﺳ ﺮﭘ ﭧﯿﻟﺍﻭ ﮯﻟﺍﻭ ﮯﻨﮭﮐﺭ ﺖﻘﺑﺎﻄﻣ "
759 -}
758 + "switchToEVMCompatibleWallet": "(Ethereum, Polygon) ﮟﯾﺮﮐ ﺶﺷﻮﮐ ﮦﺭﺎﺑﻭﺩ ﺭﻭﺍ ﮟﯾﺮﮐ ﭻﺋﻮﺳ ﺮﭘ ﭧﯿﻟﺍﻭ ﮯﻟﺍﻭ ﮯﻨﮭﮐﺭ ﺖﻘﺑﺎﻄﻣ ",
759 + "receivable_balance": "قابل وصول توازن",
760 + "confirmed_tx": "تصدیق",
761 + "transaction_details_source_address": "ماخذ ایڈریس"
762 +}
\ No newline at end of file
res/values/strings_yo.arb
+5 -2
@@ -757,5 +757,8 @@
757 "default_sell_provider": "Aiyipada Olupese Tita",
758 "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.",
759 "custom_drag": "Aṣa (mu ati fa)",
760 - "switchToEVMCompatibleWallet": "Jọwọ yipada si apamọwọ ibaramu EVM ki o tun gbiyanju lẹẹkansi (Ethereum, Polygon)"
761 -}
760 + "switchToEVMCompatibleWallet": "Jọwọ yipada si apamọwọ ibaramu EVM ki o tun gbiyanju lẹẹkansi (Ethereum, Polygon)",
761 + "receivable_balance": "Iwontunws.funfun ti o gba",
762 + "confirmed_tx": "Jẹrisi",
763 + "transaction_details_source_address": "Adirẹsi orisun"
764 +}
\ No newline at end of file
res/values/strings_zh.arb
+5 -2
@@ -762,5 +762,8 @@
762 "default_sell_provider": "默认销售提供商",
763 "select_sell_provider_notice": "选择上面的销售提供商。您可以通过在应用程序设置中设置默认销售提供商来跳过此屏幕。",
764 "custom_drag": "定制(保持和拖动)",
765 - "switchToEVMCompatibleWallet": "请切换到 EVM 兼容钱包并重试(以太坊、Polygon)"
766 -}
765 + "switchToEVMCompatibleWallet": "请切换到 EVM 兼容钱包并重试(以太坊、Polygon)",
766 + "receivable_balance": "应收余额",
767 + "confirmed_tx": "确认的",
768 + "transaction_details_source_address": "源地址"
769 +}
\ No newline at end of file
tool/configure.dart
+2 -17
@@ -759,7 +759,7 @@ import 'package:convert/convert.dart';
759 import "package:ed25519_hd_key/ed25519_hd_key.dart";
760 import 'package:libcrypto/libcrypto.dart';
761 import 'package:nanodart/nanodart.dart' as ND;
762 -import 'package:decimal/decimal.dart';
762 +import 'package:nanoutil/nanoutil.dart';
763 """;
764 const nanoCwPart = "part 'cw_nano.dart';";
765 const nanoContent = """
@@ -795,7 +795,7 @@ abstract class Nano {
795 Map<String, String> getKeys(Object wallet);
796 Object createNanoTransactionCredentials(List<Output> outputs);
797 Future<void> changeRep(Object wallet, String address);
798 - Future<void> updateTransactions(Object wallet);
798 + Future<bool> updateTransactions(Object wallet);
799 BigInt getTransactionAmountRaw(TransactionInfo transactionInfo);
800 String getRepresentative(Object wallet);
801 }
@@ -810,20 +810,6 @@ abstract class NanoAccountList {
810 }
811
812 abstract class NanoUtil {
813 - String seedToPrivate(String seed, int index);
814 - String seedToAddress(String seed, int index);
815 - String seedToMnemonic(String seed);
816 - Future<String> mnemonicToSeed(String mnemonic);
817 - String privateKeyToPublic(String privateKey);
818 - String addressToPublicKey(String publicAddress);
819 - String privateKeyToAddress(String privateKey);
820 - String publicKeyToAddress(String publicKey);
821 - bool isValidSeed(String seed);
822 - Future<String> hdMnemonicListToSeed(List<String> words);
823 - Future<String> hdSeedToPrivate(String seed, int index);
824 - Future<String> hdSeedToAddress(String seed, int index);
825 - Future<String> uniSeedToAddress(String seed, int index, String type);
826 - Future<String> uniSeedToPrivate(String seed, int index, String type);
813 bool isValidBip39Seed(String seed);
814 static const int maxDecimalDigits = 6; // Max digits after decimal
815 BigInt rawPerNano = BigInt.parse("1000000000000000000000000000000");
@@ -831,7 +817,6 @@ abstract class NanoUtil {
817 BigInt rawPerBanano = BigInt.parse("100000000000000000000000000000");
818 BigInt rawPerXMR = BigInt.parse("1000000000000");
819 BigInt convertXMRtoNano = BigInt.parse("1000000000000000000");
834 - String getRawAsDecimalString(String? raw, BigInt? rawPerCur);
820 String getRawAsUsableString(String? raw, BigInt rawPerCur);
821 String getRawAccuracy(String? raw, BigInt rawPerCur);
822 String getAmountAsRaw(String amount, BigInt rawPerCur);