diff --git a/.github/ISSUE_TEMPLATE/bug-report-🪲-.md b/.github/ISSUE_TEMPLATE/bug-report-🪲-.md new file mode 100644 index 000000000..457cb84a4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report-🪲-.md @@ -0,0 +1,33 @@ +--- +name: "Bug Report \U0001FAB2 " +about: 'Report a bug ' +title: '' +labels: Bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Platform:** + - OS: [e.g. iOS 15.1, Android 14] + - Device: [e.g. iPhone 14, Galaxy S21] + - Cake Wallet Version: [e.g. 4.12.1] + + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..d7a1a3ed9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Not sure where to start? + url: https://guides.cakewallet.com + about: Start by reading checking out the guides! + - name: Need help? + url: https://cakewallet.com/#contact + about: Use our live chat or send a support email! \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature-or-enhancement-request-✨.md b/.github/ISSUE_TEMPLATE/feature-or-enhancement-request-✨.md new file mode 100644 index 000000000..20bf2d53f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-or-enhancement-request-✨.md @@ -0,0 +1,20 @@ +--- +name: Feature or Enhancement Request ✨ +about: Suggest an idea for Cake Wallet +title: '' +labels: Enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/android/app/build.gradle b/android/app/build.gradle index e6b9eb2f8..5e27aeb9e 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -37,7 +37,7 @@ if (appPropertiesFile.exists()) { } android { - compileSdkVersion 33 + compileSdkVersion 34 lintOptions { disable 'InvalidPackage' diff --git a/android/app/src/main/AndroidManifestBase.xml b/android/app/src/main/AndroidManifestBase.xml index f32482e22..2dceca577 100644 --- a/android/app/src/main/AndroidManifestBase.xml +++ b/android/app/src/main/AndroidManifestBase.xml @@ -71,8 +71,8 @@ android:name="flutterEmbedding" android:value="2" /> <provider - android:name="com.pichillilorenzo.flutter_inappwebview.InAppWebViewFileProvider" - android:authorities="${applicationId}.flutter_inappwebview.fileprovider" + android:name="com.pichillilorenzo.flutter_inappwebview_android.InAppWebViewFileProvider" + android:authorities="${applicationId}.flutter_inappwebview_android.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data diff --git a/assets/images/kaspa_icon.png b/assets/images/kaspa_icon.png new file mode 100644 index 000000000..5201174ef Binary files /dev/null and b/assets/images/kaspa_icon.png differ diff --git a/configure_cake_wallet_android.sh b/configure_cake_wallet.sh similarity index 74% rename from configure_cake_wallet_android.sh rename to configure_cake_wallet.sh index da794a35c..df96c70f6 100755 --- a/configure_cake_wallet_android.sh +++ b/configure_cake_wallet.sh @@ -1,4 +1,24 @@ -cd scripts/android +IOS="ios" +ANDROID="android" + +PLATFORMS=($IOS $ANDROID) +PLATFORM=$1 + +if ! [[ " ${PLATFORMS[*]} " =~ " ${PLATFORM} " ]]; then + echo "specify platform: ./configure_cake_wallet.sh ios|android" + exit 1 +fi + +if [ "$PLATFORM" == "$IOS" ]; then + echo "Configuring for iOS" + cd scripts/ios +fi + +if [ "$PLATFORM" == "$ANDROID" ]; then + echo "Configuring for Android" + cd scripts/android +fi + source ./app_env.sh cakewallet ./app_config.sh cd ../.. && flutter pub get diff --git a/cw_bitcoin/lib/electrum_balance.dart b/cw_bitcoin/lib/electrum_balance.dart index a4824db0f..165ea447e 100644 --- a/cw_bitcoin/lib/electrum_balance.dart +++ b/cw_bitcoin/lib/electrum_balance.dart @@ -1,13 +1,9 @@ import 'dart:convert'; -import 'package:flutter/foundation.dart'; import 'package:cw_bitcoin/bitcoin_amount_format.dart'; import 'package:cw_core/balance.dart'; class ElectrumBalance extends Balance { - const ElectrumBalance( - {required this.confirmed, - required this.unconfirmed, - required this.frozen}) + const ElectrumBalance({required this.confirmed, required this.unconfirmed, required this.frozen}) : super(confirmed, unconfirmed); static ElectrumBalance? fromJSON(String? jsonSource) { @@ -28,12 +24,10 @@ class ElectrumBalance extends Balance { final int frozen; @override - String get formattedAvailableBalance => - bitcoinAmountToString(amount: confirmed - unconfirmed.abs() - frozen); + String get formattedAvailableBalance => bitcoinAmountToString(amount: confirmed - frozen); @override - String get formattedAdditionalBalance => - bitcoinAmountToString(amount: unconfirmed); + String get formattedAdditionalBalance => bitcoinAmountToString(amount: unconfirmed); @override String get formattedUnAvailableBalance { @@ -41,6 +35,6 @@ class ElectrumBalance extends Balance { return frozenFormatted == '0.0' ? '' : frozenFormatted; } - String toJSON() => json.encode( - {'confirmed': confirmed, 'unconfirmed': unconfirmed, 'frozen': frozen}); + String toJSON() => + json.encode({'confirmed': confirmed, 'unconfirmed': unconfirmed, 'frozen': frozen}); } diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index ab99a875c..c9e4f8200 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -62,6 +62,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { return walletInfo.type == WalletType.bitcoinCash ? toCashAddr(receiveAddress) : receiveAddress; } + @override + String get primaryAddress => getAddress(index: 0, hd: mainHd); + @override set address(String addr) => null; diff --git a/cw_core/lib/crypto_currency.dart b/cw_core/lib/crypto_currency.dart index 0f7f25d9b..162ffb1d2 100644 --- a/cw_core/lib/crypto_currency.dart +++ b/cw_core/lib/crypto_currency.dart @@ -95,6 +95,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen CryptoCurrency.banano, CryptoCurrency.usdtPoly, CryptoCurrency.usdcEPoly, + CryptoCurrency.kaspa, ]; static const havenCurrencies = [ @@ -206,6 +207,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen static const banano = CryptoCurrency(title: 'BAN', fullName: 'Banano', raw: 86, name: 'banano', iconPath: 'assets/images/nano_icon.png', decimals: 29); static const usdtPoly = CryptoCurrency(title: 'USDT', tag: 'POLY', fullName: 'Tether USD (PoS)', raw: 87, name: 'usdtpoly', iconPath: 'assets/images/usdt_icon.png', decimals: 6); static const usdcEPoly = CryptoCurrency(title: 'USDC.E', tag: 'POLY', fullName: 'USD Coin (PoS)', raw: 88, name: 'usdcepoly', iconPath: 'assets/images/usdc_icon.png', decimals: 6); + static const kaspa = CryptoCurrency(title: 'KAS', fullName: 'Kaspa', raw: 89, name: 'kaspa', iconPath: 'assets/images/kaspa_icon.png', decimals: 8); static final Map<int, CryptoCurrency> _rawCurrencyMap = diff --git a/cw_core/lib/transaction_info.dart b/cw_core/lib/transaction_info.dart index 38b4b799d..7624b147f 100644 --- a/cw_core/lib/transaction_info.dart +++ b/cw_core/lib/transaction_info.dart @@ -15,6 +15,7 @@ abstract class TransactionInfo extends Object with Keyable { String? feeFormatted(); void changeFiatAmount(String amount); String? to; + String? from; @override dynamic get keyIndex => id; diff --git a/cw_core/lib/wallet_addresses.dart b/cw_core/lib/wallet_addresses.dart index 632eb1332..d8c84c80c 100644 --- a/cw_core/lib/wallet_addresses.dart +++ b/cw_core/lib/wallet_addresses.dart @@ -10,6 +10,8 @@ abstract class WalletAddresses { String get address; + String? get primaryAddress => null; + set address(String address); Map<String, String> addressesMap; diff --git a/cw_nano/lib/banano_balance.dart b/cw_nano/lib/banano_balance.dart index b85609b60..b904a35cb 100644 --- a/cw_nano/lib/banano_balance.dart +++ b/cw_nano/lib/banano_balance.dart @@ -1,20 +1,19 @@ import 'package:cw_core/balance.dart'; -import 'package:cw_nano/nano_util.dart'; +import 'package:nanoutil/nanoutil.dart'; class BananoBalance extends Balance { final BigInt currentBalance; final BigInt receivableBalance; - BananoBalance({required this.currentBalance, required this.receivableBalance}) : super(0, 0) { - } + BananoBalance({required this.currentBalance, required this.receivableBalance}) : super(0, 0); @override String get formattedAvailableBalance { - return NanoUtil.getRawAsUsableString(currentBalance.toString(), NanoUtil.rawPerBanano); + return NanoAmounts.getRawAsUsableString(currentBalance.toString(), NanoAmounts.rawPerBanano); } @override String get formattedAdditionalBalance { - return NanoUtil.getRawAsUsableString(receivableBalance.toString(), NanoUtil.rawPerBanano); + return NanoAmounts.getRawAsUsableString(receivableBalance.toString(), NanoAmounts.rawPerBanano); } } diff --git a/cw_nano/lib/nano_balance.dart b/cw_nano/lib/nano_balance.dart index dbb39d2a3..8b8c93b33 100644 --- a/cw_nano/lib/nano_balance.dart +++ b/cw_nano/lib/nano_balance.dart @@ -1,34 +1,35 @@ import 'package:cw_core/balance.dart'; -import 'package:cw_nano/nano_util.dart'; +import 'package:nanoutil/nanoutil.dart'; BigInt stringAmountToBigInt(String amount) { - return BigInt.parse(NanoUtil.getAmountAsRaw(amount, NanoUtil.rawPerNano)); + return BigInt.parse(NanoAmounts.getAmountAsRaw(amount, NanoAmounts.rawPerNano)); } class NanoBalance extends Balance { final BigInt currentBalance; final BigInt receivableBalance; - late String formattedCurrentBalance; - late String formattedReceivableBalance; - NanoBalance({required this.currentBalance, required this.receivableBalance}) : super(0, 0) { - this.formattedCurrentBalance = ""; - this.formattedReceivableBalance = ""; - } + NanoBalance({required this.currentBalance, required this.receivableBalance}) : super(0, 0); - NanoBalance.fromString( - {required this.formattedCurrentBalance, required this.formattedReceivableBalance}) + NanoBalance.fromFormattedString( + {required String formattedCurrentBalance, required String formattedReceivableBalance}) : currentBalance = stringAmountToBigInt(formattedCurrentBalance), receivableBalance = stringAmountToBigInt(formattedReceivableBalance), super(0, 0); + NanoBalance.fromRawString( + {required String currentBalance, required String receivableBalance}) + : currentBalance = BigInt.parse(currentBalance), + receivableBalance = BigInt.parse(receivableBalance), + super(0, 0); + @override String get formattedAvailableBalance { - return NanoUtil.getRawAsUsableString(currentBalance.toString(), NanoUtil.rawPerNano); + return NanoAmounts.getRawAsUsableString(currentBalance.toString(), NanoAmounts.rawPerNano); } @override String get formattedAdditionalBalance { - return NanoUtil.getRawAsUsableString(receivableBalance.toString(), NanoUtil.rawPerNano); + return NanoAmounts.getRawAsUsableString(receivableBalance.toString(), NanoAmounts.rawPerNano); } } diff --git a/cw_nano/lib/nano_client.dart b/cw_nano/lib/nano_client.dart index f1d08204a..661fbcab8 100644 --- a/cw_nano/lib/nano_client.dart +++ b/cw_nano/lib/nano_client.dart @@ -4,10 +4,10 @@ import 'dart:convert'; import 'package:cw_core/nano_account_info_response.dart'; import 'package:cw_nano/nano_balance.dart'; import 'package:cw_nano/nano_transaction_model.dart'; -import 'package:cw_nano/nano_util.dart'; import 'package:http/http.dart' as http; import 'package:nanodart/nanodart.dart'; import 'package:cw_core/node.dart'; +import 'package:nanoutil/nanoutil.dart'; import 'package:shared_preferences/shared_preferences.dart'; class NanoClient { @@ -61,6 +61,13 @@ class NanoClient { ), ); final data = await jsonDecode(response.body); + if (response.statusCode != 200 || + data["error"] != null || + data["balance"] == null || + data["receivable"] == null) { + throw Exception( + "Error while trying to get balance! ${data["error"] != null ? data["error"] : ""}"); + } final String currentBalance = data["balance"] as String; final String receivableBalance = data["receivable"] as String; final BigInt cur = BigInt.parse(currentBalance); @@ -203,7 +210,7 @@ class NanoClient { String? previousHash, }) async { // our address: - final String publicAddress = NanoUtil.privateKeyToAddress(privateKey); + final String publicAddress = NanoDerivations.privateKeyToAddress(privateKey); // first get the current account balance: if (balanceAfterTx == null) { diff --git a/cw_nano/lib/nano_transaction_info.dart b/cw_nano/lib/nano_transaction_info.dart index 8958086dd..9195a06ef 100644 --- a/cw_nano/lib/nano_transaction_info.dart +++ b/cw_nano/lib/nano_transaction_info.dart @@ -1,7 +1,7 @@ import 'package:cw_core/format_amount.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_info.dart'; -import 'package:cw_nano/nano_util.dart'; +import 'package:nanoutil/nanoutil.dart'; class NanoTransactionInfo extends TransactionInfo { NanoTransactionInfo({ @@ -13,6 +13,8 @@ class NanoTransactionInfo extends TransactionInfo { required this.confirmed, required this.date, required this.confirmations, + required this.to, + required this.from, }) : this.amount = amountRaw.toInt(); final String id; @@ -24,14 +26,17 @@ class NanoTransactionInfo extends TransactionInfo { final bool confirmed; final int confirmations; final String tokenSymbol; + final String? to; + final String? from; String? _fiatAmount; bool get isPending => !this.confirmed; @override String amountFormatted() { - final String amt = NanoUtil.getRawAsUsableString(amountRaw.toString(), NanoUtil.rawPerNano); - final String acc = NanoUtil.getRawAccuracy(amountRaw.toString(), NanoUtil.rawPerNano); + final String amt = + NanoAmounts.getRawAsUsableString(amountRaw.toString(), NanoAmounts.rawPerNano); + final String acc = NanoAmounts.getRawAccuracy(amountRaw.toString(), NanoAmounts.rawPerNano); return "$acc$amt $tokenSymbol"; } @@ -54,6 +59,8 @@ class NanoTransactionInfo extends TransactionInfo { confirmed: data['confirmed'] as bool, confirmations: data['confirmations'] as int, tokenSymbol: data['tokenSymbol'] as String, + to: data['to'] as String, + from: data['from'] as String, ); } @@ -66,5 +73,7 @@ class NanoTransactionInfo extends TransactionInfo { 'confirmed': confirmed, 'confirmations': confirmations, 'tokenSymbol': tokenSymbol, + 'to': to, + 'from': from, }; } diff --git a/cw_nano/lib/nano_util.dart b/cw_nano/lib/nano_util.dart deleted file mode 100644 index 13d6f5649..000000000 --- a/cw_nano/lib/nano_util.dart +++ /dev/null @@ -1,193 +0,0 @@ -import 'dart:convert'; -import 'dart:typed_data'; - -import 'package:convert/convert.dart'; -import "package:ed25519_hd_key/ed25519_hd_key.dart"; -import 'package:libcrypto/libcrypto.dart'; -import 'package:nanodart/nanodart.dart'; -import 'package:decimal/decimal.dart'; - -class NanoUtil { - // standard: - static String seedToPrivate(String seed, int index) { - return NanoKeys.seedToPrivate(seed, index); - } - - static String seedToAddress(String seed, int index) { - return NanoAccounts.createAccount( - NanoAccountType.NANO, privateKeyToPublic(seedToPrivate(seed, index))); - } - - static String seedToMnemonic(String seed) { - return NanoMnemomics.seedToMnemonic(seed).join(" "); - } - - static Future<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(); - } -} diff --git a/cw_nano/lib/nano_wallet.dart b/cw_nano/lib/nano_wallet.dart index b0d98efec..1f6ec36ae 100644 --- a/cw_nano/lib/nano_wallet.dart +++ b/cw_nano/lib/nano_wallet.dart @@ -18,7 +18,6 @@ import 'package:cw_nano/nano_client.dart'; import 'package:cw_nano/nano_transaction_credentials.dart'; import 'package:cw_nano/nano_transaction_history.dart'; import 'package:cw_nano/nano_transaction_info.dart'; -import 'package:cw_nano/nano_util.dart'; import 'package:cw_nano/nano_wallet_keys.dart'; import 'package:cw_nano/pending_nano_transaction.dart'; import 'package:mobx/mobx.dart'; @@ -27,6 +26,7 @@ import 'package:cw_nano/nano_wallet_addresses.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:nanodart/nanodart.dart'; import 'package:bip39/bip39.dart' as bip39; +import 'package:nanoutil/nanoutil.dart'; part 'nano_wallet.g.dart'; @@ -83,6 +83,8 @@ abstract class NanoWalletBase @observable late ObservableMap<CryptoCurrency, NanoBalance> balance; + static const int POLL_INTERVAL_SECONDS = 10; + // initialize the different forms of private / public key we'll need: Future<void> init() async { if (_derivationType == DerivationType.unknown) { @@ -100,11 +102,21 @@ abstract class NanoWalletBase if (_derivationType == DerivationType.nano) { _hexSeed = bip39.mnemonicToEntropy(_mnemonic).toUpperCase(); } else { - _hexSeed = await NanoUtil.hdMnemonicListToSeed(_mnemonic.split(' ')); + _hexSeed = await NanoDerivations.hdMnemonicListToSeed(_mnemonic.split(' ')); } } - _privateKey = await NanoUtil.uniSeedToPrivate(_hexSeed!, 0, type); - _publicAddress = await NanoUtil.uniSeedToAddress(_hexSeed!, 0, type); + NanoDerivationType derivationType = + type == "standard" ? NanoDerivationType.STANDARD : NanoDerivationType.HD; + _privateKey = await NanoDerivations.universalSeedToPrivate( + _hexSeed!, + index: 0, + type: derivationType, + ); + _publicAddress = await NanoDerivations.universalSeedToAddress( + _hexSeed!, + index: 0, + type: derivationType, + ); this.walletInfo.address = _publicAddress!; await walletAddresses.init(); @@ -125,6 +137,7 @@ abstract class NanoWalletBase @override void close() { _client.stop(); + _receiveTimer?.cancel(); } @action @@ -139,6 +152,7 @@ abstract class NanoWalletBase try { await _updateBalance(); + await updateTransactions(); await _updateRep(); await _receiveAll(); } catch (e) { @@ -173,8 +187,8 @@ abstract class NanoWalletBase if (txOut.sendAll) { amt = balance[currency]?.currentBalance ?? BigInt.zero; } else { - amt = BigInt.tryParse(NanoUtil.getAmountAsRaw( - txOut.cryptoAmount?.replaceAll(',', '.') ?? "0", NanoUtil.rawPerNano)) ?? + amt = BigInt.tryParse(NanoAmounts.getAmountAsRaw( + txOut.cryptoAmount?.replaceAll(',', '.') ?? "0", NanoAmounts.rawPerNano)) ?? BigInt.zero; } @@ -186,9 +200,7 @@ abstract class NanoWalletBase final block = await _client.constructSendBlock( amountRaw: amt.toString(), - destinationAddress: txOut.isParsedAddress - ? txOut.extractedAddress! - : txOut.address, + destinationAddress: txOut.isParsedAddress ? txOut.extractedAddress! : txOut.address, privateKey: _privateKey!, balanceAfterTx: runningBalance, previousHash: previousHash, @@ -236,10 +248,10 @@ abstract class NanoWalletBase } } - Future<void> updateTransactions() async { + Future<bool> updateTransactions() async { try { if (_isTransactionUpdating) { - return; + return false; } _isTransactionUpdating = true; @@ -247,8 +259,10 @@ abstract class NanoWalletBase transactionHistory.addMany(transactions); await transactionHistory.save(); _isTransactionUpdating = false; + return true; } catch (_) { _isTransactionUpdating = false; + return false; } } @@ -261,16 +275,17 @@ abstract class NanoWalletBase final Map<String, NanoTransactionInfo> result = {}; for (var transactionModel in transactions) { + final bool isSend = transactionModel.type == "send"; result[transactionModel.hash] = NanoTransactionInfo( id: transactionModel.hash, amountRaw: transactionModel.amount, height: transactionModel.height, - direction: transactionModel.type == "send" - ? TransactionDirection.outgoing - : TransactionDirection.incoming, + direction: isSend ? TransactionDirection.outgoing : TransactionDirection.incoming, confirmed: transactionModel.confirmed, date: transactionModel.date ?? DateTime.now(), confirmations: transactionModel.confirmed ? 1 : 0, + to: isSend ? transactionModel.account : address, + from: isSend ? address : transactionModel.account, ); } @@ -312,11 +327,10 @@ abstract class NanoWalletBase Future<void> startSync() async { try { syncStatus = AttemptingSyncStatus(); - await _updateBalance(); - await updateTransactions(); + // setup a timer to receive transactions periodically: _receiveTimer?.cancel(); - _receiveTimer = Timer.periodic(const Duration(seconds: 15), (timer) async { + _receiveTimer = Timer.periodic(const Duration(seconds: POLL_INTERVAL_SECONDS), (timer) async { // get our balance: await _updateBalance(); // if we have anything to receive, process it: @@ -325,6 +339,14 @@ abstract class NanoWalletBase } }); + // also run once, immediately: + await _updateBalance(); + bool updateSuccess = await updateTransactions(); + if (!updateSuccess) { + syncStatus = FailedSyncStatus(); + return; + } + syncStatus = SyncedSyncStatus(); } catch (e) { print(e); @@ -353,9 +375,11 @@ abstract class NanoWalletBase final data = json.decode(jsonSource) as Map; final mnemonic = data['mnemonic'] as String; - final balance = NanoBalance.fromString( - formattedCurrentBalance: data['currentBalance'] as String? ?? "0", - formattedReceivableBalance: data['receivableBalance'] as String? ?? "0"); + + final balance = NanoBalance.fromRawString( + currentBalance: data['currentBalance'] as String? ?? "0", + receivableBalance: data['receivableBalance'] as String? ?? "0", + ); DerivationType derivationType = DerivationType.nano; if (data['derivationType'] == "DerivationType.bip39") { @@ -374,12 +398,26 @@ abstract class NanoWalletBase } Future<void> _updateBalance() async { + var oldBalance = balance[currency]; try { balance[currency] = await _client.getBalance(_publicAddress!); } catch (e) { print("Failed to get balance $e"); + // if we don't have a balance, we should at least create one, since it's a late binding + // otherwise, it's better to just leave it as whatever it was before: + if (balance[currency] == null) { + balance[currency] = + NanoBalance(currentBalance: BigInt.zero, receivableBalance: BigInt.zero); + } + } + // don't save unnecessarily: + // trying to save too frequently can cause problems with the file system + // since nano is updated frequently this can be a problem, so we only save if there is a change: + if (oldBalance == null || + balance[currency]!.currentBalance != oldBalance.currentBalance || + balance[currency]!.receivableBalance != oldBalance.receivableBalance) { + await save(); } - await save(); } Future<void> _updateRep() async { @@ -394,11 +432,19 @@ abstract class NanoWalletBase } Future<void> regenerateAddress() async { - final String type = (_derivationType == DerivationType.nano) ? "standard" : "hd"; - _privateKey = - await NanoUtil.uniSeedToPrivate(_hexSeed!, this.walletAddresses.account!.id, type); - _publicAddress = - await NanoUtil.uniSeedToAddress(_hexSeed!, this.walletAddresses.account!.id, type); + final NanoDerivationType type = (_derivationType == DerivationType.nano) + ? NanoDerivationType.STANDARD + : NanoDerivationType.HD; + _privateKey = await NanoDerivations.universalSeedToPrivate( + _hexSeed!, + index: this.walletAddresses.account!.id, + type: type, + ); + _publicAddress = await NanoDerivations.universalSeedToAddress( + _hexSeed!, + index: this.walletAddresses.account!.id, + type: type, + ); this.walletInfo.address = _publicAddress!; this.walletAddresses.address = _publicAddress!; diff --git a/cw_nano/lib/nano_wallet_service.dart b/cw_nano/lib/nano_wallet_service.dart index 2f183d1cc..7a66f8c9f 100644 --- a/cw_nano/lib/nano_wallet_service.dart +++ b/cw_nano/lib/nano_wallet_service.dart @@ -6,12 +6,12 @@ import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cw_nano/nano_mnemonic.dart' as nm; -import 'package:cw_nano/nano_util.dart'; import 'package:cw_nano/nano_wallet.dart'; import 'package:cw_nano/nano_wallet_creation_credentials.dart'; import 'package:hive/hive.dart'; import 'package:bip39/bip39.dart' as bip39; import 'package:nanodart/nanodart.dart'; +import 'package:nanoutil/nanoutil.dart'; class NanoWalletService extends WalletService<NanoNewWalletCredentials, NanoRestoreWalletFromSeedCredentials, NanoRestoreWalletFromKeysCredentials> { @@ -30,7 +30,7 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials, // nano standard: DerivationType derivationType = DerivationType.nano; String seedKey = NanoSeeds.generateSeed(); - String mnemonic = NanoUtil.seedToMnemonic(seedKey); + String mnemonic = NanoDerivations.standardSeedToMnemonic(seedKey); 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 if (credentials.seedKey.length == 64) { try { - mnemonic = NanoUtil.seedToMnemonic(credentials.seedKey); + mnemonic = NanoDerivations.standardSeedToMnemonic(credentials.seedKey); } catch (e) { throw Exception("Wasn't a valid nano style seed!"); } diff --git a/cw_nano/lib/pending_nano_transaction.dart b/cw_nano/lib/pending_nano_transaction.dart index 727f02534..a027100fd 100644 --- a/cw_nano/lib/pending_nano_transaction.dart +++ b/cw_nano/lib/pending_nano_transaction.dart @@ -1,6 +1,6 @@ import 'package:cw_core/pending_transaction.dart'; import 'package:cw_nano/nano_client.dart'; -import 'package:cw_nano/nano_util.dart'; +import 'package:nanoutil/nanoutil.dart'; class PendingNanoTransaction with PendingTransaction { PendingNanoTransaction({ @@ -18,13 +18,13 @@ class PendingNanoTransaction with PendingTransaction { @override String get amountFormatted { - final String amt = NanoUtil.getRawAsUsableString(amount.toString(), NanoUtil.rawPerNano); + final String amt = NanoAmounts.getRawAsUsableString(amount.toString(), NanoAmounts.rawPerNano); return amt; } String get accurateAmountFormatted { - final String amt = NanoUtil.getRawAsUsableString(amount.toString(), NanoUtil.rawPerNano); - final String acc = NanoUtil.getRawAccuracy(amount.toString(), NanoUtil.rawPerNano); + final String amt = NanoAmounts.getRawAsUsableString(amount.toString(), NanoAmounts.rawPerNano); + final String acc = NanoAmounts.getRawAccuracy(amount.toString(), NanoAmounts.rawPerNano); return "$acc$amt"; } diff --git a/cw_nano/pubspec.yaml b/cw_nano/pubspec.yaml index f06177d3c..a4b8732fd 100644 --- a/cw_nano/pubspec.yaml +++ b/cw_nano/pubspec.yaml @@ -22,6 +22,10 @@ dependencies: hex: ^0.2.0 http: ^1.1.0 shared_preferences: ^2.0.15 + nanoutil: + git: + url: https://github.com/perishllc/nanoutil.git + ref: c37e72817cf0a28162f43124f79661d6c8e0098f cw_core: path: ../cw_core diff --git a/lib/buy/dfx/dfx_buy_provider.dart b/lib/buy/dfx/dfx_buy_provider.dart index f74039caa..78a5277ce 100644 --- a/lib/buy/dfx/dfx_buy_provider.dart +++ b/lib/buy/dfx/dfx_buy_provider.dart @@ -66,8 +66,10 @@ class DFXBuyProvider extends BuyProvider { } } + String get walletAddress => + wallet.walletAddresses.primaryAddress ?? wallet.walletAddresses.address; + Future<String> getSignMessage() async { - final walletAddress = wallet.walletAddresses.address; final uri = Uri.https(_baseUrl, _authPath, {'address': walletAddress}); var response = await http.get(uri, headers: {'accept': 'application/json'}); @@ -83,7 +85,6 @@ class DFXBuyProvider extends BuyProvider { Future<String> signUp() async { final signMessage = getSignature(await getSignMessage()); - final walletAddress = wallet.walletAddresses.address; final requestBody = jsonEncode({ 'wallet': walletName, @@ -92,8 +93,11 @@ class DFXBuyProvider extends BuyProvider { }); final uri = Uri.https(_baseUrl, _signUpPath); - var response = await http.post(uri, - headers: {'Content-Type': 'application/json'}, body: requestBody); + var response = await http.post( + uri, + headers: {'Content-Type': 'application/json'}, + body: requestBody, + ); if (response.statusCode == 201) { final responseBody = jsonDecode(response.body); @@ -103,14 +107,12 @@ class DFXBuyProvider extends BuyProvider { final message = responseBody['message'] ?? 'Service unavailable in your country'; throw Exception(message); } else { - throw Exception( - 'Failed to sign up. Status: ${response.statusCode} ${response.body}'); + throw Exception('Failed to sign up. Status: ${response.statusCode} ${response.body}'); } } Future<String> signIn() async { final signMessage = getSignature(await getSignMessage()); - final walletAddress = wallet.walletAddresses.address; final requestBody = jsonEncode({ 'address': walletAddress, @@ -118,8 +120,11 @@ class DFXBuyProvider extends BuyProvider { }); final uri = Uri.https(_baseUrl, _signInPath); - var response = await http.post(uri, - headers: {'Content-Type': 'application/json'}, body: requestBody); + var response = await http.post( + uri, + headers: {'Content-Type': 'application/json'}, + body: requestBody, + ); if (response.statusCode == 201) { final responseBody = jsonDecode(response.body); @@ -129,8 +134,7 @@ class DFXBuyProvider extends BuyProvider { final message = responseBody['message'] ?? 'Service unavailable in your country'; throw Exception(message); } else { - throw Exception( - 'Failed to sign in. Status: ${response.statusCode} ${response.body}'); + throw Exception('Failed to sign in. Status: ${response.statusCode} ${response.body}'); } } @@ -142,8 +146,7 @@ class DFXBuyProvider extends BuyProvider { case WalletType.litecoin: case WalletType.bitcoin: case WalletType.bitcoinCash: - return wallet.signMessage(message, - address: wallet.walletAddresses.address); + return wallet.signMessage(message, address: walletAddress); default: throw Exception("WalletType is not available for DFX ${wallet.type}"); } @@ -178,8 +181,7 @@ class DFXBuyProvider extends BuyProvider { if (await canLaunchUrl(uri)) { if (DeviceInfo.instance.isMobile) { - Navigator.of(context) - .pushNamed(Routes.webViewPage, arguments: ["DFX Connect", uri]); + Navigator.of(context).pushNamed(Routes.webViewPage, arguments: [title, uri]); } else { await launchUrl(uri, mode: LaunchMode.externalApplication); } diff --git a/lib/buy/onramper/onramper_buy_provider.dart b/lib/buy/onramper/onramper_buy_provider.dart index 014edb813..3819f074d 100644 --- a/lib/buy/onramper/onramper_buy_provider.dart +++ b/lib/buy/onramper/onramper_buy_provider.dart @@ -52,7 +52,7 @@ class OnRamperBuyProvider extends BuyProvider { return color.value.toRadixString(16).replaceAll(RegExp(r'^ff'), ""); } - Uri requestOnramperUrl(BuildContext context) { + Uri requestOnramperUrl(BuildContext context, bool? isBuyAction) { String primaryColor, secondaryColor, primaryTextColor, @@ -79,23 +79,24 @@ class OnRamperBuyProvider extends BuyProvider { return Uri.https(_baseUrl, '', <String, dynamic>{ 'apiKey': _apiKey, 'defaultCrypto': _normalizeCryptoCurrency, + 'sell_defaultCrypto': _normalizeCryptoCurrency, 'networkWallets': '${networkName}:${wallet.walletAddresses.address}', - 'supportSell': "false", 'supportSwap': "false", 'primaryColor': primaryColor, 'secondaryColor': secondaryColor, 'primaryTextColor': primaryTextColor, 'secondaryTextColor': secondaryTextColor, 'containerColor': containerColor, - 'cardColor': cardColor + 'cardColor': cardColor, + 'mode': isBuyAction == true ? 'buy' : 'sell', }); } Future<void> launchProvider(BuildContext context, bool? isBuyAction) async { - final uri = requestOnramperUrl(context); + final uri = requestOnramperUrl(context, isBuyAction); if (DeviceInfo.instance.isMobile) { Navigator.of(context) - .pushNamed(Routes.webViewPage, arguments: [S.of(context).buy, uri]); + .pushNamed(Routes.webViewPage, arguments: [title, uri]); } else { await launchUrl(uri); } diff --git a/lib/entities/fiat_currency.dart b/lib/entities/fiat_currency.dart index 8310eb18f..a8e2829d8 100644 --- a/lib/entities/fiat_currency.dart +++ b/lib/entities/fiat_currency.dart @@ -10,7 +10,7 @@ class FiatCurrency extends EnumerableItem<String> with Serializable<String> impl static List<FiatCurrency> get all => _all.values.toList(); static List<FiatCurrency> get currenciesAvailableToBuyWith => - [aud, bgn, brl, cad, chf, clp, cop, czk, dkk, egp, eur, gbp, gtq, hkd, hrk, huf, idr, ils, inr, isk, jpy, krw, mad, mxn, myr, ngn, nok, nzd, php, pkr, pln, ron, sek, sgd, thb, twd, usd, vnd, zar]; + [aud, bgn, brl, cad, chf, clp, cop, czk, dkk, egp, eur, gbp, gtq, hkd, hrk, huf, idr, ils, inr, isk, jpy, krw, mad, mxn, myr, ngn, nok, nzd, php, pkr, pln, ron, sek, sgd, thb, twd, usd, vnd, zar, tur]; static const ars = FiatCurrency(symbol: 'ARS', countryCode: "arg", fullName: "Argentine Peso"); static const aud = FiatCurrency(symbol: 'AUD', countryCode: "aus", fullName: "Australian Dollar"); @@ -60,6 +60,7 @@ class FiatCurrency extends EnumerableItem<String> with Serializable<String> impl static const vef = FiatCurrency(symbol: 'VEF', countryCode: "ven", fullName: "Venezuelan Bolivar Bolívar"); static const vnd = FiatCurrency(symbol: 'VND', countryCode: "vnm", fullName: "Vietnamese Dong đồng"); static const zar = FiatCurrency(symbol: 'ZAR', countryCode: "saf", fullName: "South African Rand"); + static const tur = FiatCurrency(symbol: 'TRY', countryCode: "tur", fullName: "Turkish Lira"); static final _all = { FiatCurrency.ars.raw: FiatCurrency.ars, @@ -109,7 +110,8 @@ class FiatCurrency extends EnumerableItem<String> with Serializable<String> impl FiatCurrency.usd.raw: FiatCurrency.usd, FiatCurrency.vef.raw: FiatCurrency.vef, FiatCurrency.vnd.raw: FiatCurrency.vnd, - FiatCurrency.zar.raw: FiatCurrency.zar + FiatCurrency.zar.raw: FiatCurrency.zar, + FiatCurrency.tur.raw: FiatCurrency.tur, }; static FiatCurrency deserialize({required String raw}) => _all[raw]!; diff --git a/lib/entities/provider_types.dart b/lib/entities/provider_types.dart index 4cb9a934f..ced79eae7 100644 --- a/lib/entities/provider_types.dart +++ b/lib/entities/provider_types.dart @@ -78,7 +78,8 @@ class ProvidersHelper { return [ProviderType.askEachTime, ProviderType.dfx]; case WalletType.bitcoin: case WalletType.ethereum: - return [ProviderType.askEachTime, ProviderType.moonpaySell, ProviderType.dfx]; + return [ProviderType.askEachTime, ProviderType.onramper, + ProviderType.moonpaySell, ProviderType.dfx]; case WalletType.litecoin: case WalletType.bitcoinCash: return [ProviderType.askEachTime, ProviderType.moonpaySell]; diff --git a/lib/exchange/provider/changenow_exchange_provider.dart b/lib/exchange/provider/changenow_exchange_provider.dart index 300741a08..c4a96bc5b 100644 --- a/lib/exchange/provider/changenow_exchange_provider.dart +++ b/lib/exchange/provider/changenow_exchange_provider.dart @@ -265,6 +265,9 @@ class ChangeNowExchangeProvider extends ExchangeProvider { } String _normalizeCurrency(CryptoCurrency currency) { + if (currency.title == "USDC" && currency.tag == "POLY") { + throw "Only Bridged USDC (USDC.e) is allowed in ChangeNow"; + } switch (currency) { case CryptoCurrency.zec: return 'zec'; diff --git a/lib/nano/cw_nano.dart b/lib/nano/cw_nano.dart index 0c072c95c..06ebf60c2 100644 --- a/lib/nano/cw_nano.dart +++ b/lib/nano/cw_nano.dart @@ -173,7 +173,7 @@ class CWNano extends Nano { } @override - Future<void> updateTransactions(Object wallet) async { + Future<bool> updateTransactions(Object wallet) async { return (wallet as NanoWallet).updateTransactions(); } @@ -189,116 +189,10 @@ class CWNano extends Nano { } class CWNanoUtil extends NanoUtil { - // standard: - @override - String seedToPrivate(String seed, int index) { - return ND.NanoKeys.seedToPrivate(seed, index); - } - - @override - String seedToAddress(String seed, int index) { - return ND.NanoAccounts.createAccount( - ND.NanoAccountType.NANO, privateKeyToPublic(seedToPrivate(seed, index))); - } - - @override - String seedToMnemonic(String seed) { - return NanoMnemomics.seedToMnemonic(seed).join(" "); - } - - @override - Future<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 bool isValidBip39Seed(String seed) { - // Ensure seed is 128 characters long - if (seed.length != 128) { - return false; - } - // Ensure seed only contains hex characters, 0-9;A-F - return ND.NanoHelpers.isHexString(seed); + return NanoDerivations.isValidBip39Seed(seed); } // number util: @@ -309,92 +203,20 @@ class CWNanoUtil extends NanoUtil { BigInt rawPerBanano = BigInt.parse("100000000000000000000000000000"); BigInt rawPerXMR = BigInt.parse("1000000000000"); BigInt convertXMRtoNano = BigInt.parse("1000000000000000000"); - // static BigInt convertXMRtoNano = BigInt.parse("1000000000000000000000000000"); - /// Convert raw to ban and return as BigDecimal - /// - /// @param raw 100000000000000000000000000000 - /// @return Decimal value 1.000000000000000000000000000000 - /// - Decimal _getRawAsDecimal(String? raw, BigInt? rawPerCur) { - rawPerCur ??= rawPerNano; - final Decimal amount = Decimal.parse(raw.toString()); - final Decimal result = (amount / Decimal.parse(rawPerCur.toString())).toDecimal(); - return result; - } - - @override - String getRawAsDecimalString(String? raw, BigInt? rawPerCur) { - final Decimal result = _getRawAsDecimal(raw, rawPerCur); - return result.toString(); - } - - @override - String truncateDecimal(Decimal input, {int digits = maxDecimalDigits}) { - Decimal bigger = input.shift(digits); - bigger = bigger.floor(); // chop off the decimal: 1.059 -> 1.05 - bigger = bigger.shift(-digits); - return bigger.toString(); - } - - /// Return raw as a NANO amount. - /// - /// @param raw 100000000000000000000000000000 - /// @returns 1 - /// @override String getRawAsUsableString(String? raw, BigInt rawPerCur) { - final String res = - truncateDecimal(_getRawAsDecimal(raw, rawPerCur), digits: maxDecimalDigits + 9); - - if (raw == null || raw == "0" || raw == "00000000000000000000000000000000") { - return "0"; - } - - if (!res.contains(".")) { - return res; - } - - final String numAmount = res.split(".")[0]; - String decAmount = res.split(".")[1]; - - // truncate: - if (decAmount.length > maxDecimalDigits) { - decAmount = decAmount.substring(0, maxDecimalDigits); - // remove trailing zeros: - decAmount = decAmount.replaceAllMapped(RegExp(r'0+$'), (Match match) => ''); - if (decAmount.isEmpty) { - return numAmount; - } - } - - return "$numAmount.$decAmount"; + return NanoAmounts.getRawAsUsableString(raw, rawPerCur); } @override String getRawAccuracy(String? raw, BigInt rawPerCur) { - final String rawString = getRawAsUsableString(raw, rawPerCur); - final String rawDecimalString = _getRawAsDecimal(raw, rawPerCur).toString(); - - if (raw == null || raw.isEmpty || raw == "0") { - return ""; - } - - if (rawString != rawDecimalString) { - return "~"; - } - return ""; + return NanoAmounts.getRawAccuracy(raw, rawPerCur); } - /// Return readable string amount as raw string - /// @param amount 1.01 - /// @returns 101000000000000000000000000000 - /// @override String getAmountAsRaw(String amount, BigInt rawPerCur) { - final Decimal asDecimal = Decimal.parse(amount); - final Decimal rawDecimal = Decimal.parse(rawPerCur.toString()); - return (asDecimal * rawDecimal).toString(); + return NanoAmounts.getAmountAsRaw(amount, rawPerCur); } @override @@ -411,29 +233,29 @@ class CWNanoUtil extends NanoUtil { if (seedKey != null) { if (seedKey.length == 64) { try { - mnemonic = nanoUtil!.seedToMnemonic(seedKey); + mnemonic = NanoDerivations.standardSeedToMnemonic(seedKey); } catch (e) { print("not a valid 'nano' seed key"); } } if (derivationType == DerivationType.bip39) { - publicAddress = await hdSeedToAddress(seedKey, 0); + publicAddress = await NanoDerivations.hdSeedToAddress(seedKey, index: 0); } else if (derivationType == DerivationType.nano) { - publicAddress = await seedToAddress(seedKey, 0); + publicAddress = await NanoDerivations.standardSeedToAddress(seedKey, index: 0); } } if (derivationType == DerivationType.bip39) { if (mnemonic != null) { - seedKey = await hdMnemonicListToSeed(mnemonic.split(' ')); - publicAddress = await hdSeedToAddress(seedKey, 0); + seedKey = await NanoDerivations.hdMnemonicListToSeed(mnemonic.split(' ')); + publicAddress = await NanoDerivations.hdSeedToAddress(seedKey, index: 0); } } if (derivationType == DerivationType.nano) { if (mnemonic != null) { - seedKey = await mnemonicToSeed(mnemonic); - publicAddress = await seedToAddress(seedKey, 0); + seedKey = await NanoDerivations.standardMnemonicToSeed(mnemonic); + publicAddress = await NanoDerivations.standardSeedToAddress(seedKey, index: 0); } } @@ -461,7 +283,7 @@ class CWNanoUtil extends NanoUtil { return [DerivationType.bip39]; } else if (seedKey?.length == 64) { try { - mnemonic = nanoUtil!.seedToMnemonic(seedKey!); + mnemonic = NanoDerivations.standardSeedToMnemonic(seedKey!); } catch (e) { print("not a valid 'nano' seed key"); } @@ -475,19 +297,19 @@ class CWNanoUtil extends NanoUtil { nanoClient.connect(node); if (mnemonic != null) { - seedKey = await hdMnemonicListToSeed(mnemonic.split(' ')); - publicAddressBip39 = await hdSeedToAddress(seedKey, 0); + seedKey = await NanoDerivations.hdMnemonicListToSeed(mnemonic.split(' ')); + publicAddressBip39 = await NanoDerivations.hdSeedToAddress(seedKey, index: 0); - seedKey = await mnemonicToSeed(mnemonic); - publicAddressStandard = await seedToAddress(seedKey, 0); + seedKey = await NanoDerivations.standardMnemonicToSeed(mnemonic); + publicAddressStandard = await NanoDerivations.standardSeedToAddress(seedKey, index: 0); } else if (seedKey != null) { try { - publicAddressBip39 = await hdSeedToAddress(seedKey, 0); + publicAddressBip39 = await NanoDerivations.hdSeedToAddress(seedKey, index: 0); } catch (e) { return [DerivationType.nano]; } try { - publicAddressStandard = await seedToAddress(seedKey, 0); + publicAddressStandard = await NanoDerivations.standardSeedToAddress(seedKey, index: 0); } catch (e) { return [DerivationType.bip39]; } diff --git a/lib/src/screens/buy/buy_webview_page.dart b/lib/src/screens/buy/buy_webview_page.dart index 6f7a39322..829bff3d9 100644 --- a/lib/src/screens/buy/buy_webview_page.dart +++ b/lib/src/screens/buy/buy_webview_page.dart @@ -1,10 +1,7 @@ import 'dart:async'; -import 'dart:io'; -import 'package:cake_wallet/buy/buy_provider.dart'; import 'package:cake_wallet/buy/moonpay/moonpay_provider.dart'; import 'package:cake_wallet/buy/wyre/wyre_buy_provider.dart'; import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/palette.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/store/dashboard/orders_store.dart'; import 'package:cake_wallet/view_model/buy/buy_view_model.dart'; @@ -72,10 +69,10 @@ class BuyWebViewPageBodyState extends State<BuyWebViewPageBody> { Widget build(BuildContext context) { return InAppWebView( key: _webViewkey, - initialOptions: InAppWebViewGroupOptions( - crossPlatform: InAppWebViewOptions(transparentBackground: true), + initialSettings: InAppWebViewSettings( + transparentBackground: true, ), - initialUrlRequest: URLRequest(url: Uri.tryParse(widget.url ?? '')), + initialUrlRequest: URLRequest(url: WebUri(widget.url ?? '')), onWebViewCreated: (InAppWebViewController controller) => setState(() => _webViewController = controller)); } diff --git a/lib/src/screens/buy/webview_page.dart b/lib/src/screens/buy/webview_page.dart index 205b87c47..97387c29a 100644 --- a/lib/src/screens/buy/webview_page.dart +++ b/lib/src/screens/buy/webview_page.dart @@ -1,5 +1,3 @@ -import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart'; -import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:flutter/material.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; @@ -35,21 +33,21 @@ class WebViewPageBodyState extends State<WebViewPageBody> { @override Widget build(BuildContext context) { return InAppWebView( - initialOptions: InAppWebViewGroupOptions( - crossPlatform: InAppWebViewOptions(transparentBackground: true), + initialSettings: InAppWebViewSettings( + transparentBackground: true, ), - initialUrlRequest: URLRequest(url: widget.uri), - androidOnPermissionRequest: (_, __, resources) async { + initialUrlRequest: URLRequest(url: WebUri.uri(widget.uri)), + onPermissionRequest: (controller, request) async { bool permissionGranted = await Permission.camera.status == PermissionStatus.granted; if (!permissionGranted) { permissionGranted = await Permission.camera.request().isGranted; } - return PermissionRequestResponse( - resources: resources, + return PermissionResponse( + resources: request.resources, action: permissionGranted - ? PermissionRequestResponseAction.GRANT - : PermissionRequestResponseAction.DENY, + ? PermissionResponseAction.GRANT + : PermissionResponseAction.DENY, ); }, ); diff --git a/lib/src/screens/dashboard/pages/balance_page.dart b/lib/src/screens/dashboard/pages/balance_page.dart index 283d2171d..6ddd8f6e1 100644 --- a/lib/src/screens/dashboard/pages/balance_page.dart +++ b/lib/src/screens/dashboard/pages/balance_page.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:auto_size_text/auto_size_text.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/reactions/wallet_connect.dart'; @@ -14,6 +16,7 @@ import 'package:cake_wallet/utils/feature_flag.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart'; +import 'package:cw_core/crypto_currency.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; @@ -200,7 +203,7 @@ class CryptoBalanceWidget extends StatelessWidget { additionalFiatBalance: balance.fiatAdditionalBalance, frozenBalance: balance.frozenBalance, frozenFiatBalance: balance.fiatFrozenBalance, - currency: balance.formattedAssetTitle, + currency: balance.asset, hasAdditionalBalance: dashboardViewModel.balanceViewModel.hasAdditionalBalance, ); @@ -216,7 +219,7 @@ class CryptoBalanceWidget extends StatelessWidget { } class BalanceRowWidget extends StatelessWidget { - const BalanceRowWidget({ + BalanceRowWidget({ required this.availableBalanceLabel, required this.availableBalance, required this.availableFiatBalance, @@ -238,7 +241,7 @@ class BalanceRowWidget extends StatelessWidget { final String additionalFiatBalance; final String frozenBalance; final String frozenFiatBalance; - final String currency; + final CryptoCurrency currency; final bool hasAdditionalBalance; // void _showBalanceDescription(BuildContext context) { @@ -268,7 +271,7 @@ class BalanceRowWidget extends StatelessWidget { children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, children: [ GestureDetector( behavior: HitTestBehavior.opaque, @@ -325,13 +328,48 @@ class BalanceRowWidget extends StatelessWidget { ], ), ), - Text(currency, - style: TextStyle( - fontSize: 28, - fontFamily: 'Lato', - fontWeight: FontWeight.w800, - color: Theme.of(context).extension<BalancePageTheme>()!.assetTitleColor, - height: 1)), + SizedBox( + width: MediaQuery.of(context).size.width * 0.18, + child: Center( + child: Column( + children: [ + currency.iconPath != null + ? Container( + child: Image.asset( + currency.iconPath!, + height: 30.0, + width: 30.0, + ), + ) + : Container( + height: 30.0, + width: 30.0, + child: Center( + child: Text( + currency.title.substring(0, min(currency.title.length, 2)), + style: TextStyle(fontSize: 11), + ), + ), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.grey.shade400, + ), + ), + const SizedBox(height: 10), + Text( + currency.title, + style: TextStyle( + fontSize: 18, + fontFamily: 'Lato', + fontWeight: FontWeight.w800, + color: Theme.of(context).extension<BalancePageTheme>()!.assetTitleColor, + height: 1, + ), + ), + ], + ), + ), + ), ], ), if (frozenBalance.isNotEmpty) @@ -374,7 +412,9 @@ class BalanceRowWidget extends StatelessWidget { fontSize: 20, fontFamily: 'Lato', fontWeight: FontWeight.w400, - color: Theme.of(context).extension<DashboardPageTheme>()!.textColor, + color: Theme.of(context) + .extension<BalancePageTheme>()! + .balanceAmountColor, height: 1, ), maxLines: 1, @@ -388,7 +428,7 @@ class BalanceRowWidget extends StatelessWidget { fontSize: 12, fontFamily: 'Lato', fontWeight: FontWeight.w400, - color: Theme.of(context).extension<DashboardPageTheme>()!.textColor, + color: Theme.of(context).extension<BalancePageTheme>()!.textColor, height: 1, ), ), diff --git a/lib/src/screens/dashboard/pages/transactions_page.dart b/lib/src/screens/dashboard/pages/transactions_page.dart index 1be8a2a65..c983b1c37 100644 --- a/lib/src/screens/dashboard/pages/transactions_page.dart +++ b/lib/src/screens/dashboard/pages/transactions_page.dart @@ -49,7 +49,7 @@ class TransactionsPage extends StatelessWidget { onTap: () => Navigator.of(context).pushNamed(Routes.webViewPage, arguments: [ '', Uri.parse( - 'https://guides.cakewallet.com/docs/bugs-service-status/why_are_my_funds_not_appearing/') + 'https://guides.cakewallet.com/docs/FAQ/why_are_my_funds_not_appearing/') ]), title: S.of(context).syncing_wallet_alert_title, subTitle: S.of(context).syncing_wallet_alert_content, diff --git a/lib/src/screens/exchange_trade/exchange_trade_page.dart b/lib/src/screens/exchange_trade/exchange_trade_page.dart index e72b0f0a8..7930c9c82 100644 --- a/lib/src/screens/exchange_trade/exchange_trade_page.dart +++ b/lib/src/screens/exchange_trade/exchange_trade_page.dart @@ -261,7 +261,7 @@ class ExchangeTradeState extends State<ExchangeTradeForm> { fee: S.of(popupContext).send_fee, feeValue: widget.exchangeTradeViewModel.sendViewModel .pendingTransaction!.feeFormatted, - rightButtonText: S.of(popupContext).ok, + rightButtonText: S.of(popupContext).send, leftButtonText: S.of(popupContext).cancel, actionRightButton: () async { Navigator.of(popupContext).pop(); diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index b20b94cba..3746118d8 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -423,7 +423,7 @@ class SendPage extends BasePage { feeValue: sendViewModel.pendingTransaction!.feeFormatted, feeFiatAmount: sendViewModel.pendingTransactionFeeFiatAmountFormatted, outputs: sendViewModel.outputs, - rightButtonText: S.of(_dialogContext).ok, + rightButtonText: S.of(_dialogContext).send, leftButtonText: S.of(_dialogContext).cancel, actionRightButton: () { Navigator.of(_dialogContext).pop(); diff --git a/lib/src/screens/support_chat/widgets/chatwoot_widget.dart b/lib/src/screens/support_chat/widgets/chatwoot_widget.dart index 73403d667..2557761a7 100644 --- a/lib/src/screens/support_chat/widgets/chatwoot_widget.dart +++ b/lib/src/screens/support_chat/widgets/chatwoot_widget.dart @@ -22,17 +22,17 @@ class ChatwootWidgetState extends State<ChatwootWidget> { @override Widget build(BuildContext context) => InAppWebView( key: _webViewkey, - initialOptions: InAppWebViewGroupOptions( - crossPlatform: InAppWebViewOptions(transparentBackground: true), - ), - initialUrlRequest: URLRequest(url: Uri.tryParse(widget.supportUrl)), + initialSettings: InAppWebViewSettings( + transparentBackground: true, + ), + initialUrlRequest: URLRequest(url: WebUri(widget.supportUrl)), onWebViewCreated: (InAppWebViewController controller) { controller.addWebMessageListener( WebMessageListener( jsObjectName: 'ReactNativeWebView', - onPostMessage: (String? message, Uri? sourceOrigin, bool isMainFrame, - JavaScriptReplyProxy replyProxy) { - final shortenedMessage = message?.substring(16); + onPostMessage: (WebMessage? message, WebUri? sourceOrigin, bool isMainFrame, + PlatformJavaScriptReplyProxy replyProxy) { + final shortenedMessage = message?.data.toString().substring(16); if (shortenedMessage != null && isJsonString(shortenedMessage)) { final parsedMessage = jsonDecode(shortenedMessage); final eventType = parsedMessage["event"]; diff --git a/lib/view_model/dashboard/balance_view_model.dart b/lib/view_model/dashboard/balance_view_model.dart index 5c95ab3ab..e2c0382b0 100644 --- a/lib/view_model/dashboard/balance_view_model.dart +++ b/lib/view_model/dashboard/balance_view_model.dart @@ -125,6 +125,8 @@ abstract class BalanceViewModelBase with Store { case WalletType.haven: case WalletType.ethereum: case WalletType.polygon: + case WalletType.nano: + case WalletType.banano: return S.current.xmr_available_balance; default: return S.current.confirmed; @@ -139,6 +141,9 @@ abstract class BalanceViewModelBase with Store { case WalletType.ethereum: case WalletType.polygon: return S.current.xmr_full_balance; + case WalletType.nano: + case WalletType.banano: + return S.current.receivable_balance; default: return S.current.unconfirmed; } diff --git a/lib/view_model/dashboard/transaction_list_item.dart b/lib/view_model/dashboard/transaction_list_item.dart index bc7f70517..d8c4776b7 100644 --- a/lib/view_model/dashboard/transaction_list_item.dart +++ b/lib/view_model/dashboard/transaction_list_item.dart @@ -101,7 +101,7 @@ class TransactionListItem extends ActionListItem with Keyable { break; case WalletType.nano: amount = calculateFiatAmountRaw( - cryptoAmount: double.parse(nanoUtil!.getRawAsDecimalString( + cryptoAmount: double.parse(nanoUtil!.getRawAsUsableString( nano!.getTransactionAmountRaw(transaction).toString(), nanoUtil!.rawPerNano)), price: price); break; diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart index 4e17866cb..f9ff466a7 100644 --- a/lib/view_model/transaction_details_view_model.dart +++ b/lib/view_model/transaction_details_view_model.dart @@ -247,11 +247,15 @@ abstract class TransactionDetailsViewModelBase with Store { void _addNanoListItems(TransactionInfo tx, DateFormat dateFormat) { final _items = [ StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id), + if (showRecipientAddress && tx.to != null) + StandartListItem(title: S.current.transaction_details_recipient_address, value: tx.to!), + if (showRecipientAddress && tx.from != null) + StandartListItem(title: S.current.transaction_details_source_address, value: tx.from!), + StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()), StandartListItem( title: S.current.transaction_details_date, value: dateFormat.format(tx.date)), - StandartListItem(title: S.current.confirmations, value: (tx.confirmations > 0).toString()), + StandartListItem(title: S.current.confirmed_tx, value: (tx.confirmations > 0).toString()), StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'), - StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()), ]; items.addAll(_items); diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 68d03b5f8..05996a674 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -9,6 +9,7 @@ import connectivity_plus_macos import cw_monero import device_info_plus import devicelocale +import flutter_inappwebview_macos import flutter_secure_storage_macos import in_app_review import package_info @@ -24,6 +25,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { CwMoneroPlugin.register(with: registry.registrar(forPlugin: "CwMoneroPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) DevicelocalePlugin.register(with: registry.registrar(forPlugin: "DevicelocalePlugin")) + InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin")) FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin")) FLTPackageInfoPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlugin")) diff --git a/pubspec_base.yaml b/pubspec_base.yaml index e326e8ea1..63a97b7d2 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -43,7 +43,7 @@ dependencies: auto_size_text: ^3.0.0 dotted_border: ^2.0.0+2 smooth_page_indicator: ^1.0.0+2 - flutter_inappwebview: ^5.7.2+3 + flutter_inappwebview: ^6.0.0 flutter_spinkit: ^5.1.0 uni_links: ^0.5.1 lottie: ^1.3.0 diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index e10f136e4..8fcccd967 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -765,5 +765,8 @@ "connected": "متصل", "disconnected": "انقطع الاتصال", "onion_only": "البصل فقط", - "connecting": "توصيل" + "connecting": "توصيل", + "receivable_balance": "التوازن القادم", + "confirmed_tx": "مؤكد", + "transaction_details_source_address": "عنوان المصدر" } \ No newline at end of file diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 62480c1da..f859d839f 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -761,5 +761,8 @@ "connected": "Свързани", "disconnected": "Изключен", "onion_only": "Само лук", - "connecting": "Свързване" + "connecting": "Свързване", + "receivable_balance": "Баланс за вземания", + "confirmed_tx": "Потвърдено", + "transaction_details_source_address": "Адрес на източника" } \ No newline at end of file diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index af2722c01..061e4d9bf 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -761,5 +761,8 @@ "connected": "Připojeno", "disconnected": "Odpojené", "onion_only": "Pouze cibule", - "connecting": "Spojovací" + "connecting": "Spojovací", + "receivable_balance": "Zůstatek pohledávek", + "confirmed_tx": "Potvrzeno", + "transaction_details_source_address": "Zdrojová adresa" } \ No newline at end of file diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 8d83c101a..a16c0c492 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -769,5 +769,8 @@ "connected": "In Verbindung gebracht", "disconnected": "Getrennt", "onion_only": "Nur Zwiebel", - "connecting": "Verbinden" + "connecting": "Verbinden", + "receivable_balance": "Forderungsbilanz", + "confirmed_tx": "Bestätigt", + "transaction_details_source_address": "Quelladresse" } \ No newline at end of file diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index e0ab03a75..2f73d5fbf 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -770,5 +770,8 @@ "connected": "Connected", "disconnected": "Disconnected", "onion_only": "Onion only", - "connecting": "Connecting" + "connecting": "Connecting", + "receivable_balance": "Receivable Balance", + "confirmed_tx": "Confirmed", + "transaction_details_source_address": "Source address" } \ No newline at end of file diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 3dfe21be6..02de41f99 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -769,5 +769,8 @@ "connected": "Conectado", "disconnected": "Desconectado", "onion_only": "Solo cebolla", - "connecting": "Conexión" + "connecting": "Conexión", + "receivable_balance": "Saldo de cuentas por cobrar", + "confirmed_tx": "Confirmado", + "transaction_details_source_address": "Dirección de la fuente" } \ No newline at end of file diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 73810551f..5a54669fe 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -769,5 +769,8 @@ "connected": "Connecté", "disconnected": "Débranché", "onion_only": "Oignon seulement", - "connecting": "De liaison" + "connecting": "De liaison", + "receivable_balance": "Solde de créances", + "confirmed_tx": "Confirmé", + "transaction_details_source_address": "Adresse source" } \ No newline at end of file diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 36662695c..bbb1e3e67 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -751,5 +751,8 @@ "connected": "Wanda aka haɗa", "disconnected": "Katse", "onion_only": "Albasa kawai", - "connecting": "Haɗa" + "connecting": "Haɗa", + "receivable_balance": "Daidaituwa da daidaituwa", + "confirmed_tx": "Tabbatar", + "transaction_details_source_address": "Adireshin Incord" } \ No newline at end of file diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 94a7ec371..ec2f9d197 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -769,5 +769,8 @@ "connected": "जुड़े हुए", "disconnected": "डिस्कनेक्ट किया गया", "onion_only": "केवल प्याज", - "connecting": "कनेक्ट" + "connecting": "कनेक्ट", + "receivable_balance": "प्राप्य शेष", + "confirmed_tx": "की पुष्टि", + "transaction_details_source_address": "स्रोत पता" } \ No newline at end of file diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 9f5cae922..87e602257 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -767,5 +767,8 @@ "connected": "Povezan", "disconnected": "Isključen", "onion_only": "Samo luk", - "connecting": "Spoj" + "connecting": "Spoj", + "receivable_balance": "Stanje potraživanja", + "confirmed_tx": "Potvrđen", + "transaction_details_source_address": "Adresa izvora" } \ No newline at end of file diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index d7c401fbd..e80265c0b 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -757,5 +757,8 @@ "connected": "Terhubung", "disconnected": "Terputus", "onion_only": "Bawang saja", - "connecting": "Menghubungkan" + "connecting": "Menghubungkan", + "receivable_balance": "Saldo piutang", + "confirmed_tx": "Dikonfirmasi", + "transaction_details_source_address": "Alamat sumber" } \ No newline at end of file diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 1421ce901..8ff479392 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -769,5 +769,8 @@ "connected": "Collegato", "disconnected": "Disconnesso", "onion_only": "Solo cipolla", - "connecting": "Connessione" + "connecting": "Connessione", + "receivable_balance": "Bilanciamento creditizio", + "confirmed_tx": "Confermato", + "transaction_details_source_address": "Indirizzo di partenza" } \ No newline at end of file diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index d09046388..befaea9b1 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -769,5 +769,8 @@ "connected": "接続", "disconnected": "切断された", "onion_only": "オニオンのみ", - "connecting": "接続" + "connecting": "接続", + "receivable_balance": "売掛金残高", + "confirmed_tx": "確認済み", + "transaction_details_source_address": "ソースアドレス" } \ No newline at end of file diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 7a144059e..3fbce31da 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -767,5 +767,8 @@ "connected": "연결", "disconnected": "연결이 끊어졌습니다", "onion_only": "양파 만", - "connecting": "연결" + "connecting": "연결", + "receivable_balance": "채권 잔액", + "confirmed_tx": "확인", + "transaction_details_source_address": "소스 주소" } \ No newline at end of file diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 1a8e941ca..f3eb26383 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -767,5 +767,8 @@ "connected": "ချိတ်ဆက်ထားသော", "disconnected": "ချို့ယွင်းချက်", "onion_only": "သာကြက်သွန်", - "connecting": "ချိတ်ဆက်" + "connecting": "ချိတ်ဆက်", + "receivable_balance": "လက်ကျန်ငွေ", + "confirmed_tx": "အတည်ပြုသည်", + "transaction_details_source_address": "အရင်းအမြစ်လိပ်စာ" } \ No newline at end of file diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index ade120a6d..fdb64f29c 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -769,5 +769,8 @@ "connected": "Verbonden", "disconnected": "Losgekoppeld", "onion_only": "Alleen ui", - "connecting": "Verbinden" + "connecting": "Verbinden", + "receivable_balance": "Het saldo", + "confirmed_tx": "Bevestigd", + "transaction_details_source_address": "Bron adres" } \ No newline at end of file diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index b4b18e601..7ae4245a2 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -769,5 +769,8 @@ "connected": "Połączony", "disconnected": "Bezładny", "onion_only": "Tylko cebula", - "connecting": "Złączony" + "connecting": "Złączony", + "receivable_balance": "Saldo należności", + "confirmed_tx": "Potwierdzony", + "transaction_details_source_address": "Adres źródłowy" } \ No newline at end of file diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 23aa08742..47bce63ed 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -768,5 +768,8 @@ "connected": "Conectado", "disconnected": "Desconectado", "onion_only": "Apenas cebola", - "connecting": "Conectando" + "connecting": "Conectando", + "receivable_balance": "Saldo a receber", + "confirmed_tx": "Confirmado", + "transaction_details_source_address": "Endereço de Origem" } \ No newline at end of file diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index d7a54f8d2..5e545eb66 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -769,5 +769,8 @@ "connected": "Связанный", "disconnected": "Отключен", "onion_only": "Только лук", - "connecting": "Соединение" + "connecting": "Соединение", + "receivable_balance": "Баланс дебиторской задолженности", + "confirmed_tx": "Подтвержденный", + "transaction_details_source_address": "Адрес источника" } \ No newline at end of file diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 250010649..33facbf80 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -767,5 +767,8 @@ "connected": "ซึ่งเชื่อมต่อกัน", "disconnected": "ตัดการเชื่อมต่อ", "onion_only": "หัวหอมเท่านั้น", - "connecting": "การเชื่อมต่อ" + "connecting": "การเชื่อมต่อ", + "receivable_balance": "ยอดลูกหนี้", + "confirmed_tx": "ซึ่งยืนยันแล้ว", + "transaction_details_source_address": "ที่อยู่แหล่งกำเนิด" } \ No newline at end of file diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 4ae759387..c996aca71 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -763,5 +763,8 @@ "connected": "Konektado", "disconnected": "Naka -disconnect", "onion_only": "Onion lamang", - "connecting": "Pagkonekta" + "connecting": "Pagkonekta", + "receivable_balance": "Natatanggap na balanse", + "confirmed_tx": "Nakumpirma", + "transaction_details_source_address": "SOURCE ADDRESS" } \ No newline at end of file diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 19e990b97..0b86b9c6d 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -767,5 +767,8 @@ "connected": "Bağlı", "disconnected": "Bağlantı kesildi", "onion_only": "Sadece soğan", - "connecting": "Bağlanıyor" + "connecting": "Bağlanıyor", + "receivable_balance": "Alacak bakiyesi", + "confirmed_tx": "Onaylanmış", + "transaction_details_source_address": "Kaynak adresi" } \ No newline at end of file diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 1da3b4435..63ee5c142 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -769,5 +769,8 @@ "connected": "З'єднаний", "disconnected": "Роз'єднаний", "onion_only": "Тільки цибуля", - "connecting": "З'єднання" + "connecting": "З'єднання", + "receivable_balance": "Баланс дебіторської заборгованості", + "confirmed_tx": "Підтверджений", + "transaction_details_source_address": "Адреса джерела" } \ No newline at end of file diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 69abf78e1..e75cb21ba 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -761,5 +761,8 @@ "connected": "منسلک", "disconnected": "منقطع", "onion_only": "صرف پیاز", - "connecting": "رابطہ قائم کرنا" + "connecting": "رابطہ قائم کرنا", + "receivable_balance": "قابل وصول توازن", + "confirmed_tx": "تصدیق", + "transaction_details_source_address": "ماخذ ایڈریس" } \ No newline at end of file diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index b88126fe8..b7023de7c 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -763,5 +763,8 @@ "connected": "Sopọ", "disconnected": "Ge asopọ", "onion_only": "Alubosa nikan", - "connecting": "Asopọ" + "connecting": "Asopọ", + "receivable_balance": "Iwontunws.funfun ti o gba", + "confirmed_tx": "Jẹrisi", + "transaction_details_source_address": "Adirẹsi orisun" } \ No newline at end of file diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 7f43520c4..b67e9c7ff 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -768,5 +768,8 @@ "connected": "连接的", "disconnected": "断开连接", "onion_only": "仅洋葱", - "connecting": "连接" + "connecting": "连接", + "receivable_balance": "应收余额", + "confirmed_tx": "确认的", + "transaction_details_source_address": "源地址" } \ No newline at end of file diff --git a/tool/configure.dart b/tool/configure.dart index 67732faa9..3d1bccb12 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -759,7 +759,7 @@ import 'package:convert/convert.dart'; import "package:ed25519_hd_key/ed25519_hd_key.dart"; import 'package:libcrypto/libcrypto.dart'; import 'package:nanodart/nanodart.dart' as ND; -import 'package:decimal/decimal.dart'; +import 'package:nanoutil/nanoutil.dart'; """; const nanoCwPart = "part 'cw_nano.dart';"; const nanoContent = """ @@ -795,7 +795,7 @@ abstract class Nano { Map<String, String> getKeys(Object wallet); Object createNanoTransactionCredentials(List<Output> outputs); Future<void> changeRep(Object wallet, String address); - Future<void> updateTransactions(Object wallet); + Future<bool> updateTransactions(Object wallet); BigInt getTransactionAmountRaw(TransactionInfo transactionInfo); String getRepresentative(Object wallet); } @@ -810,20 +810,6 @@ abstract class NanoAccountList { } abstract class NanoUtil { - String seedToPrivate(String seed, int index); - String seedToAddress(String seed, int index); - String seedToMnemonic(String seed); - Future<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); static const int maxDecimalDigits = 6; // Max digits after decimal BigInt rawPerNano = BigInt.parse("1000000000000000000000000000000"); @@ -831,7 +817,6 @@ abstract class NanoUtil { BigInt rawPerBanano = BigInt.parse("100000000000000000000000000000"); BigInt rawPerXMR = BigInt.parse("1000000000000"); BigInt convertXMRtoNano = BigInt.parse("1000000000000000000"); - String getRawAsDecimalString(String? raw, BigInt? rawPerCur); String getRawAsUsableString(String? raw, BigInt rawPerCur); String getRawAccuracy(String? raw, BigInt rawPerCur); String getAmountAsRaw(String amount, BigInt rawPerCur);