diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index 1ed5baf9f..84c680dda 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -12,7 +12,7 @@ on: jobs: Automated_integration_test: - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: diff --git a/.github/workflows/no_print_in_dart.yaml b/.github/workflows/no_print_in_dart.yaml index b1c356c31..9c3d82bc2 100644 --- a/.github/workflows/no_print_in_dart.yaml +++ b/.github/workflows/no_print_in_dart.yaml @@ -4,7 +4,7 @@ on: [pull_request] jobs: PR_test_build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/no_restricted_imports.yaml b/.github/workflows/no_restricted_imports.yaml index 4b17de31a..03c3de018 100644 --- a/.github/workflows/no_restricted_imports.yaml +++ b/.github/workflows/no_restricted_imports.yaml @@ -4,7 +4,7 @@ on: [pull_request] jobs: check_restricted_imports: - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/pr_test_build_android.yml b/.github/workflows/pr_test_build_android.yml index 08a283467..8f6139747 100644 --- a/.github/workflows/pr_test_build_android.yml +++ b/.github/workflows/pr_test_build_android.yml @@ -274,7 +274,7 @@ jobs: - name: Build run: | - flutter build apk --release --split-per-abi + flutter build apk --dart-define=hasDevOptions=true --release --split-per-abi - name: Rename apk file run: | diff --git a/.github/workflows/pr_test_build_linux.yml b/.github/workflows/pr_test_build_linux.yml index bf55134ad..476a033a0 100644 --- a/.github/workflows/pr_test_build_linux.yml +++ b/.github/workflows/pr_test_build_linux.yml @@ -225,7 +225,7 @@ jobs: - name: Build linux run: | - flutter build linux --release + flutter build linux --dart-define=hasDevOptions=true --release - name: Compress release run: | @@ -283,6 +283,9 @@ jobs: xmessage -timeout 30 "restore_wallet_through_seeds_flow_test" & rm -rf ~/.local/share/com.example.cake_wallet/ ~/Documents/cake_wallet/ ~/cake_wallet exec timeout --signal=SIGKILL 900 flutter drive --driver=test_driver/integration_test.dart --target=integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart + - name: Test [cw_monero] + timeout-minutes: 2 + run: cd cw_monero && flutter test - name: Stop screen recording, encrypt and upload if: always() run: | diff --git a/.gitignore b/.gitignore index 37db583e5..83e6d03e8 100644 --- a/.gitignore +++ b/.gitignore @@ -142,9 +142,28 @@ lib/zano/zano.dart lib/decred/decred.dart lib/tari/tari.dart -ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png -ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png -ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@2x.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@2x~ipad.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@3x.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20@2x.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20@2x~ipad.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20@3x.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-20~ipad.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29@2x.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29@2x~ipad.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29@3x.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-29~ipad.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40@2x.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40@2x~ipad.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40@3x.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-40~ipad.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-60@2x~car.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-60@3x~car.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon-83.5@2x~ipad.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon~ios-marketing.png +ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon~ipad.png + ios/Runner/Info.plist android/app/src/main/res/mipmap-* android/app/src/main/res/drawable/ic_launcher.png diff --git a/README.md b/README.md index ea8f34624..ea796dbf2 100644 --- a/README.md +++ b/README.md @@ -26,10 +26,13 @@ Cake Wallet includes support for several cryptocurrencies, including: * Ethereum (ETH) * Litecoin (LTC) * Bitcoin Cash (BCH) -* Polygon (Pol) +* Polygon (POL) * Solana (SOL) +* Tron (TRX) * Nano (XNO) -* Haven (XHV) +* Zano (ZANO) +* Decred (DCR) +* Wownero (WOW) ## Features @@ -81,10 +84,6 @@ Cake Wallet includes support for several cryptocurrencies, including: * Automatically generate new addresses * Specify multiple recipients for batch sending -### Haven Specific Features - -* Send, receive, and store XHV and all xAssets like xUSD, xEUR, xAG, etc. - # Monero.com by Cake Wallet for Android and iOS ## Open Source Monero-Only Wallet diff --git a/android/app/build.gradle b/android/app/build.gradle index c45ed9368..6c299c929 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -74,9 +74,6 @@ android { release { signingConfig signingConfigs.release - shrinkResources false - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } debug { diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro index d24d7f10a..921ee4d4c 100644 --- a/android/app/proguard-rules.pro +++ b/android/app/proguard-rules.pro @@ -5,4 +5,5 @@ -keep class io.flutter.view.** { *; } -keep class io.flutter.** { *; } -keep class io.flutter.plugins.** { *; } --dontwarn io.flutter.embedding.** \ No newline at end of file +-dontwarn io.flutter.embedding.** +-dontwarn com.google.android.play.core.splitcompat.SplitCompatApplication \ No newline at end of file diff --git a/android/app/src/main/AndroidManifestBase.xml b/android/app/src/main/AndroidManifestBase.xml index 9a324edf3..4f15370c3 100644 --- a/android/app/src/main/AndroidManifestBase.xml +++ b/android/app/src/main/AndroidManifestBase.xml @@ -107,6 +107,9 @@ + _enabled; @@ -33,14 +35,17 @@ class Erc20Token extends CryptoCurrency with HiveObjectMixin { bool enabled = true, this.iconPath, this.tag, + this.isPotentialScam = false, }) : _enabled = enabled, super( - name: symbol.toLowerCase(), - title: symbol.toUpperCase(), - fullName: name, - tag: tag, - iconPath: iconPath, - decimals: decimal); + name: symbol.toLowerCase(), + title: symbol.toUpperCase(), + fullName: name, + tag: tag, + iconPath: iconPath, + decimals: decimal, + isPotentialScam: isPotentialScam, + ); Erc20Token.copyWith(Erc20Token other, String? icon, String? tag) : this.name = other.name, @@ -50,6 +55,7 @@ class Erc20Token extends CryptoCurrency with HiveObjectMixin { this._enabled = other.enabled, this.tag = tag, this.iconPath = icon, + this.isPotentialScam = other.isPotentialScam, super( name: other.name, title: other.symbol.toUpperCase(), @@ -57,6 +63,7 @@ class Erc20Token extends CryptoCurrency with HiveObjectMixin { tag: tag, iconPath: icon, decimals: other.decimal, + isPotentialScam: other.isPotentialScam, ); static const typeId = ERC20_TOKEN_TYPE_ID; diff --git a/cw_core/lib/monero_wallet_utils.dart b/cw_core/lib/monero_wallet_utils.dart index 8a4990f78..9682784f9 100644 --- a/cw_core/lib/monero_wallet_utils.dart +++ b/cw_core/lib/monero_wallet_utils.dart @@ -19,15 +19,15 @@ Future backupWalletFiles(String name) async { final newKeysFilePath = backupFileName(keysFile.path); final newAddressListFilePath = backupFileName(addressListFile.path); - if (cacheFile.existsSync()) { + if (cacheFile.existsSync() && !File(newCacheFilePath).existsSync()) { await cacheFile.copy(newCacheFilePath); } - if (keysFile.existsSync()) { + if (keysFile.existsSync() && !File(newKeysFilePath).existsSync()) { await keysFile.copy(newKeysFilePath); } - if (addressListFile.existsSync()) { + if (addressListFile.existsSync() && !File(newAddressListFilePath).existsSync()) { await addressListFile.copy(newAddressListFilePath); } } @@ -83,10 +83,13 @@ Future backupWalletFilesExists(String name) async { Future removeCache(String name) async { final path = await pathForWallet(name: name, type: WalletType.monero); final cacheFile = File(path); - + final backgroundCacheFile = File(path + ".background"); if (cacheFile.existsSync()) { cacheFile.deleteSync(); } + if (backgroundCacheFile.existsSync()) { + backgroundCacheFile.deleteSync(); + } } Future restoreOrResetWalletFiles(String name) async { @@ -94,7 +97,8 @@ Future restoreOrResetWalletFiles(String name) async { if (backupsExists) { await removeCache(name); - + // TODO(mrcyjanek): is this needed? + // If we remove cache then wallet should be restored from .keys file. await restoreWalletFiles(name); } } diff --git a/cw_core/lib/unspent_transaction_output.dart b/cw_core/lib/unspent_transaction_output.dart index da71f6983..31820e3a1 100644 --- a/cw_core/lib/unspent_transaction_output.dart +++ b/cw_core/lib/unspent_transaction_output.dart @@ -21,4 +21,9 @@ class Unspent with UnspentComparable { bool get isP2wpkh => address.startsWith('bc') || address.startsWith('tb') || address.startsWith('ltc'); + + @override + String toString() { + return 'Unspent(address: $address, hash: $hash, value: $value, vout: $vout, keyImage: $keyImage, isSending: $isSending, isFrozen: $isFrozen, isChange: $isChange, note: $note)'; + } } diff --git a/cw_core/lib/wallet_type.dart b/cw_core/lib/wallet_type.dart index 0686e58c9..46f0b2163 100644 --- a/cw_core/lib/wallet_type.dart +++ b/cw_core/lib/wallet_type.dart @@ -202,7 +202,7 @@ String walletTypeToDisplayName(WalletType type) { case WalletType.banano: return 'Banano (BAN)'; case WalletType.polygon: - return 'Polygon (MATIC)'; + return 'Polygon (POL)'; case WalletType.solana: return 'Solana (SOL)'; case WalletType.tron: @@ -260,3 +260,38 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type, {bool isTestnet = fal 'Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency'); } } + +WalletType? cryptoCurrencyToWalletType(CryptoCurrency type) { + switch (type) { + case CryptoCurrency.xmr: + return WalletType.monero; + case CryptoCurrency.btc: + return WalletType.bitcoin; + case CryptoCurrency.ltc: + return WalletType.litecoin; + case CryptoCurrency.xhv: + return WalletType.haven; + case CryptoCurrency.eth: + return WalletType.ethereum; + case CryptoCurrency.bch: + return WalletType.bitcoinCash; + case CryptoCurrency.nano: + return WalletType.nano; + case CryptoCurrency.banano: + return WalletType.banano; + case CryptoCurrency.maticpoly: + return WalletType.polygon; + case CryptoCurrency.sol: + return WalletType.solana; + case CryptoCurrency.trx: + return WalletType.tron; + case CryptoCurrency.wow: + return WalletType.wownero; + case CryptoCurrency.zano: + return WalletType.zano; + case CryptoCurrency.dcr: + return WalletType.decred; + default: + return null; + } +} diff --git a/cw_decred/lib/wallet.dart b/cw_decred/lib/wallet.dart index db30ab373..a63a5e2e5 100644 --- a/cw_decred/lib/wallet.dart +++ b/cw_decred/lib/wallet.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'package:cw_core/exceptions.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/utils/print_verbose.dart'; +import 'package:cw_decred/amount_format.dart'; import 'package:cw_decred/pending_transaction.dart'; import 'package:cw_decred/transaction_credentials.dart'; import 'package:flutter/foundation.dart'; @@ -122,6 +123,9 @@ abstract class DecredWalletBase return _pubkey; } + @override + String formatCryptoAmount(String amount) => decredAmountToString(amount: int.parse(amount)); + Future init() async { final getSeed = () async { if (!watchingOnly) { @@ -218,7 +222,7 @@ abstract class DecredWalletBase Future checkSync() async { final syncStatusJSON = await _libwallet.syncStatus(walletInfo.name); - final decoded = json.decode(syncStatusJSON); + final decoded = json.decode(syncStatusJSON.isEmpty ? "{}" : syncStatusJSON); final syncStatusCode = decoded["syncstatuscode"] ?? 0; // final syncStatusStr = decoded["syncstatus"] ?? ""; @@ -706,14 +710,18 @@ abstract class DecredWalletBase // walletBirthdayBlockHeight checks if the wallet birthday is set and returns // it. Returns -1 if not. Future walletBirthdayBlockHeight() async { - final res = await _libwallet.birthState(walletInfo.name); - final decoded = json.decode(res); - // Having these values set indicates that sync has not reached the birthday - // yet, so no birthday is set. - if (decoded["setfromheight"] == true || decoded["setfromtime"] == true) { - return -1; + try { + final res = await _libwallet.birthState(walletInfo.name); + final decoded = json.decode(res); + // Having these values set indicates that sync has not reached the birthday + // yet, so no birthday is set. + if (decoded["setfromheight"] == true || decoded["setfromtime"] == true) { + return -1; + } + return decoded["height"] ?? 0; + } on FormatException catch (_) { + return 0; } - return decoded["height"] ?? 0; } Future verifyMessage(String message, String signature, {String? address = null}) async { diff --git a/cw_ethereum/lib/ethereum_wallet.dart b/cw_ethereum/lib/ethereum_wallet.dart index 765ace052..7cc140c5a 100644 --- a/cw_ethereum/lib/ethereum_wallet.dart +++ b/cw_ethereum/lib/ethereum_wallet.dart @@ -76,9 +76,13 @@ class EthereumWallet extends EVMChainWallet { await erc20TokensBox.deleteFromDisk(); // Add all the previous tokens with configs to the new box - evmChainErc20TokensBox.addAll(allValues); + await evmChainErc20TokensBox.addAll(allValues); } + @override + List get getDefaultTokenContractAddresses => + DefaultEthereumErc20Tokens().initialErc20Tokens.map((e) => e.contractAddress).toList(); + @override EVMChainTransactionInfo getTransactionInfo( EVMChainTransactionModel transactionModel, String address) { @@ -115,6 +119,7 @@ class EthereumWallet extends EVMChainWallet { enabled: token.enabled, tag: token.tag ?? "ETH", iconPath: iconPath, + isPotentialScam: token.isPotentialScam, ); } diff --git a/cw_evm/lib/evm_chain_client.dart b/cw_evm/lib/evm_chain_client.dart index b505577e9..7e6caf374 100644 --- a/cw_evm/lib/evm_chain_client.dart +++ b/cw_evm/lib/evm_chain_client.dart @@ -190,7 +190,7 @@ abstract class EVMChainClient { _sendTransaction = () async => await sendTransaction(signedTransaction); return PendingEVMChainTransaction( - signedTransaction: signedTransaction, + signedTransaction: prepareSignedTransactionForSending(signedTransaction), amount: amount.toString(), fee: gasFee, sendTransaction: _sendTransaction, diff --git a/cw_evm/lib/evm_chain_wallet.dart b/cw_evm/lib/evm_chain_wallet.dart index d3efdaa7c..d640f8c14 100644 --- a/cw_evm/lib/evm_chain_wallet.dart +++ b/cw_evm/lib/evm_chain_wallet.dart @@ -144,6 +144,8 @@ abstract class EVMChainWalletBase // required WalletInfo walletInfo, // }); + List get getDefaultTokenContractAddresses; + Future initErc20TokensBox(); String getTransactionHistoryFileName(); @@ -171,6 +173,9 @@ abstract class EVMChainWalletBase await walletAddresses.init(); await transactionHistory.init(); + // check for Already existing scam tokens, cuz users can get scammed twice ¯\_(ツ)_/¯ + await _checkForExistingScamTokens(); + if (walletInfo.isHardwareWallet) { _evmChainPrivateKey = EvmLedgerCredentials(walletInfo.address); walletAddresses.address = walletInfo.address; @@ -186,6 +191,31 @@ abstract class EVMChainWalletBase await save(); } + Future _checkForExistingScamTokens() async { + final baseCurrencySymbols = CryptoCurrency.all.map((e) => e.title.toUpperCase()).toList(); + + for (var token in erc20Currencies) { + bool isPotentialScam = false; + + bool isWhitelisted = + getDefaultTokenContractAddresses.any((element) => element == token.contractAddress); + + final tokenSymbol = token.title.toUpperCase(); + + // check if the token symbol is the same as any of the base currencies symbols (ETH, SOL, POL, TRX, etc): + // if it is, then it's probably a scam unless it's in the whitelist + if (baseCurrencySymbols.contains(tokenSymbol.trim().toUpperCase()) && !isWhitelisted) { + isPotentialScam = true; + } + + if (isPotentialScam) { + token.isPotentialScam = true; + token.iconPath = null; + await token.save(); + } + } + } + @override int calculateEstimatedFee(TransactionPriority priority, int? amount) { { @@ -626,13 +656,13 @@ abstract class EVMChainWalletBase Future addErc20Token(Erc20Token token) async { String? iconPath; - if (token.iconPath == null || token.iconPath!.isEmpty) { + if ((token.iconPath == null || token.iconPath!.isEmpty) && !token.isPotentialScam) { try { iconPath = CryptoCurrency.all .firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase()) .iconPath; } catch (_) {} - } else { + } else if (!token.isPotentialScam) { iconPath = token.iconPath; } diff --git a/cw_monero/.gitignore b/cw_monero/.gitignore index ebb19df82..2bf094c85 100644 --- a/cw_monero/.gitignore +++ b/cw_monero/.gitignore @@ -11,4 +11,5 @@ android/.externalNativeBuild/ android/.cxx/ macos/cw_monero.podspec -macos/External/ \ No newline at end of file +macos/External/ +*monero_libwallet2_api_c.* \ No newline at end of file diff --git a/cw_monero/lib/api/account_list.dart b/cw_monero/lib/api/account_list.dart index 0e55ce15c..3ceef5815 100644 --- a/cw_monero/lib/api/account_list.dart +++ b/cw_monero/lib/api/account_list.dart @@ -1,4 +1,7 @@ +import 'dart:async'; + import 'package:cw_monero/api/wallet.dart'; +import 'package:cw_monero/monero_account_list.dart'; import 'package:monero/monero.dart' as monero; monero.wallet? wptr = null; @@ -7,14 +10,14 @@ bool get isViewOnly => int.tryParse(monero.Wallet_secretSpendKey(wptr!)) == 0; int _wlptrForW = 0; monero.WalletListener? _wlptr = null; -monero.WalletListener getWlptr() { +monero.WalletListener? getWlptr() { + if (wptr == null) return null; if (wptr!.address == _wlptrForW) return _wlptr!; _wlptrForW = wptr!.address; _wlptr = monero.MONERO_cw_getWalletListener(wptr!); return _wlptr!; } - monero.SubaddressAccount? subaddressAccount; bool isUpdating = false; @@ -50,8 +53,9 @@ void addAccountSync({required String label}) { } void setLabelForAccountSync({required int accountIndex, required String label}) { - // TODO(mrcyjanek): this may be wrong function? - monero.Wallet_setSubaddressLabel(wptr!, accountIndex: accountIndex, addressIndex: 0, label: label); + monero.SubaddressAccount_setLabel(subaddressAccount!, accountIndex: accountIndex, label: label); + MoneroAccountListBase.cachedAccounts[wptr!.address] = []; + refreshAccounts(); } void _addAccount(String label) => addAccountSync(label: label); @@ -65,10 +69,10 @@ void _setLabelForAccount(Map args) { Future addAccount({required String label}) async { _addAccount(label); - await store(); + unawaited(store()); } Future setLabelForAccount({required int accountIndex, required String label}) async { _setLabelForAccount({'accountIndex': accountIndex, 'label': label}); - await store(); + unawaited(store()); } \ No newline at end of file diff --git a/cw_monero/lib/api/coins_info.dart b/cw_monero/lib/api/coins_info.dart index ef7d3cfd6..83382f001 100644 --- a/cw_monero/lib/api/coins_info.dart +++ b/cw_monero/lib/api/coins_info.dart @@ -1,21 +1,42 @@ +import 'dart:ffi'; +import 'dart:isolate'; + import 'package:cw_monero/api/account_list.dart'; import 'package:monero/monero.dart' as monero; +import 'package:mutex/mutex.dart'; monero.Coins? coins = null; +final coinsMutex = Mutex(); -void refreshCoins(int accountIndex) { +Future refreshCoins(int accountIndex) async { + if (coinsMutex.isLocked) { + return; + } coins = monero.Wallet_coins(wptr!); - monero.Coins_refresh(coins!); + final coinsPtr = coins!.address; + await coinsMutex.acquire(); + await Isolate.run(() => monero.Coins_refresh(Pointer.fromAddress(coinsPtr))); + coinsMutex.release(); } -int countOfCoins() => monero.Coins_count(coins!); +Future countOfCoins() async { + await coinsMutex.acquire(); + final count = monero.Coins_count(coins!); + coinsMutex.release(); + return count; +} -monero.CoinsInfo getCoin(int index) => monero.Coins_coin(coins!, index); +Future getCoin(int index) async { + await coinsMutex.acquire(); + final coin = monero.Coins_coin(coins!, index); + coinsMutex.release(); + return coin; +} -int? getCoinByKeyImage(String keyImage) { - final count = countOfCoins(); +Future getCoinByKeyImage(String keyImage) async { + final count = await countOfCoins(); for (int i = 0; i < count; i++) { - final coin = getCoin(i); + final coin = await getCoin(i); final coinAddress = monero.CoinsInfo_keyImage(coin); if (keyImage == coinAddress) { return i; @@ -24,6 +45,16 @@ int? getCoinByKeyImage(String keyImage) { return null; } -void freezeCoin(int index) => monero.Coins_setFrozen(coins!, index: index); +Future freezeCoin(int index) async { + await coinsMutex.acquire(); + final coinsPtr = coins!.address; + await Isolate.run(() => monero.Coins_setFrozen(Pointer.fromAddress(coinsPtr), index: index)); + coinsMutex.release(); +} -void thawCoin(int index) => monero.Coins_thaw(coins!, index: index); +Future thawCoin(int index) async { + await coinsMutex.acquire(); + final coinsPtr = coins!.address; + await Isolate.run(() => monero.Coins_thaw(Pointer.fromAddress(coinsPtr), index: index)); + coinsMutex.release(); +} diff --git a/cw_monero/lib/api/transaction_history.dart b/cw_monero/lib/api/transaction_history.dart index 162b9ac1a..8c7b1e902 100644 --- a/cw_monero/lib/api/transaction_history.dart +++ b/cw_monero/lib/api/transaction_history.dart @@ -1,6 +1,7 @@ import 'dart:ffi'; import 'dart:isolate'; +import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_monero/api/account_list.dart'; import 'package:cw_monero/api/exceptions/creation_transaction_exception.dart'; import 'package:cw_monero/api/monero_output.dart'; @@ -13,15 +14,23 @@ import 'package:monero/src/generated_bindings_monero.g.dart' as monero_gen; import 'package:mutex/mutex.dart'; +Map> txKeys = {}; String getTxKey(String txId) { + txKeys[wptr!.address] ??= {}; + if (txKeys[wptr!.address]![txId] != null) { + return txKeys[wptr!.address]![txId]!; + } final txKey = monero.Wallet_getTxKey(wptr!, txid: txId); final status = monero.Wallet_status(wptr!); if (status != 0) { - final error = monero.Wallet_errorString(wptr!); + monero.Wallet_errorString(wptr!); + txKeys[wptr!.address]![txId] = ""; return ""; } + txKeys[wptr!.address]![txId] = txKey; return txKey; } + final txHistoryMutex = Mutex(); monero.TransactionHistory? txhistory; bool isRefreshingTx = false; @@ -34,6 +43,7 @@ Future refreshTransactions() async { await Isolate.run(() { monero.TransactionHistory_refresh(Pointer.fromAddress(ptr)); }); + await Future.delayed(Duration.zero); txHistoryMutex.release(); isRefreshingTx = false; } @@ -45,8 +55,24 @@ Future> getAllTransactions() async { await txHistoryMutex.acquire(); txhistory ??= monero.Wallet_history(wptr!); + final startAddress = txhistory!.address * wptr!.address; int size = countOfTransactions(); - final list = List.generate(size, (index) => Transaction(txInfo: monero.TransactionHistory_transaction(txhistory!, index: index))); + final list = []; + for (int index = 0; index < size; index++) { + if (index % 25 == 0) { + // Give main thread a chance to do other things. + await Future.delayed(Duration.zero); + } + if (txhistory!.address * wptr!.address != startAddress) { + printV("Loop broken because txhistory!.address * wptr!.address != startAddress"); + break; + } + final txInfo = monero.TransactionHistory_transaction(txhistory!, index: index); + final txHash = monero.TransactionInfo_hash(txInfo); + txCache[wptr!.address] ??= {}; + txCache[wptr!.address]![txHash] = Transaction(txInfo: txInfo); + list.add(txCache[wptr!.address]![txHash]!); + } txHistoryMutex.release(); final accts = monero.Wallet_numSubaddressAccounts(wptr!); for (var i = 0; i < accts; i++) { @@ -79,8 +105,18 @@ Future> getAllTransactions() async { return list; } -Transaction getTransaction(String txId) { - return Transaction(txInfo: monero.TransactionHistory_transactionById(txhistory!, txid: txId)); +Map> txCache = {}; +Future getTransaction(String txId) async { + if (txCache[wptr!.address] != null && txCache[wptr!.address]![txId] != null) { + return txCache[wptr!.address]![txId]!; + } + await txHistoryMutex.acquire(); + final tx = monero.TransactionHistory_transactionById(txhistory!, txid: txId); + final txDart = Transaction(txInfo: tx); + txCache[wptr!.address] ??= {}; + txCache[wptr!.address]![txId] = txDart; + txHistoryMutex.release(); + return txDart; } Future createTransactionSync( @@ -149,13 +185,14 @@ Future createTransactionSync( final rAmt = monero.PendingTransaction_amount(pendingTx); final rFee = monero.PendingTransaction_fee(pendingTx); final rHash = monero.PendingTransaction_txid(pendingTx, ''); + final rHex = monero.PendingTransaction_hex(pendingTx, ''); final rTxKey = rHash; return PendingTransactionDescription( amount: rAmt, fee: rFee, hash: rHash, - hex: '', + hex: rHex, txKey: rTxKey, pointerAddress: pendingTx.address, ); @@ -198,7 +235,7 @@ Future createTransactionMultDest( amount: monero.PendingTransaction_amount(txptr), fee: monero.PendingTransaction_fee(txptr), hash: monero.PendingTransaction_txid(txptr, ''), - hex: monero.PendingTransaction_txid(txptr, ''), + hex: monero.PendingTransaction_hex(txptr, ''), txKey: monero.PendingTransaction_txid(txptr, ''), pointerAddress: txptr.address, ); diff --git a/cw_monero/lib/api/wallet.dart b/cw_monero/lib/api/wallet.dart index 033df53cf..7e64c7f08 100644 --- a/cw_monero/lib/api/wallet.dart +++ b/cw_monero/lib/api/wallet.dart @@ -2,10 +2,10 @@ import 'dart:async'; import 'dart:ffi'; import 'dart:isolate'; -import 'package:cw_core/root_dir.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_monero/api/account_list.dart'; import 'package:cw_monero/api/exceptions/setup_wallet_exception.dart'; +import 'package:cw_monero/api/wallet_manager.dart'; import 'package:flutter/foundation.dart'; import 'package:monero/monero.dart' as monero; import 'package:mutex/mutex.dart'; @@ -21,14 +21,18 @@ int getSyncingHeight() { } bool isNeededToRefresh() { - final ret = monero.MONERO_cw_WalletListener_isNeedToRefresh(getWlptr()); - monero.MONERO_cw_WalletListener_resetNeedToRefresh(getWlptr()); + final wlptr = getWlptr(); + if (wlptr == null) return false; + final ret = monero.MONERO_cw_WalletListener_isNeedToRefresh(wlptr); + monero.MONERO_cw_WalletListener_resetNeedToRefresh(wlptr); return ret; } bool isNewTransactionExist() { - final ret = monero.MONERO_cw_WalletListener_isNewTransactionExist(getWlptr()); - monero.MONERO_cw_WalletListener_resetIsNewTransactionExist(getWlptr()); + final wlptr = getWlptr(); + if (wlptr == null) return false; + final ret = monero.MONERO_cw_WalletListener_isNewTransactionExist(wlptr); + monero.MONERO_cw_WalletListener_resetIsNewTransactionExist(wlptr); return ret; } @@ -58,6 +62,11 @@ String getSeed() { } return cakepolyseed; } + + final bip39 = monero.Wallet_getCacheAttribute(wptr!, key: "cakewallet.seed.bip39"); + + if(bip39.isNotEmpty) return bip39; + final legacy = getSeedLegacy(null); return legacy; } @@ -199,12 +208,15 @@ void startRefreshSync() { } -void setRefreshFromBlockHeight({required int height}) => - monero.Wallet_setRefreshFromBlockHeight(wptr!, - refresh_from_block_height: height); +void setRefreshFromBlockHeight({required int height}) { + monero.Wallet_setRefreshFromBlockHeight(wptr!, + refresh_from_block_height: height); +} -void setRecoveringFromSeed({required bool isRecovery}) => - monero.Wallet_setRecoveringFromSeed(wptr!, recoveringFromSeed: isRecovery); +void setRecoveringFromSeed({required bool isRecovery}) { + monero.Wallet_setRecoveringFromSeed(wptr!, recoveringFromSeed: isRecovery); + monero.Wallet_store(wptr!); +} final storeMutex = Mutex(); @@ -394,4 +406,6 @@ String signMessage(String message, {String address = ""}) { bool verifyMessage(String message, String address, String signature) { return monero.Wallet_verifySignedMessage(wptr!, message: message, address: address, signature: signature); -} \ No newline at end of file +} + +Map> debugCallLength() => monero.debugCallLength; diff --git a/cw_monero/lib/api/wallet_manager.dart b/cw_monero/lib/api/wallet_manager.dart index 0dcb3c851..b43773447 100644 --- a/cw_monero/lib/api/wallet_manager.dart +++ b/cw_monero/lib/api/wallet_manager.dart @@ -137,6 +137,7 @@ void restoreWalletFromSeedSync( wptr = newWptr; setRefreshFromBlockHeight(height: restoreHeight); + setupBackgroundSync(password, newWptr); monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.passphrase", value: passphrase); diff --git a/cw_monero/lib/bip39_seed.dart b/cw_monero/lib/bip39_seed.dart new file mode 100644 index 000000000..338516e66 --- /dev/null +++ b/cw_monero/lib/bip39_seed.dart @@ -0,0 +1,59 @@ +import 'dart:typed_data'; + +import 'package:bip32/bip32.dart' as bip32; +import 'package:bip39/bip39.dart' as bip39; +import 'package:polyseed/polyseed.dart'; + +bool isBip39Seed(String mnemonic) => bip39.validateMnemonic(mnemonic); + +String getBip39Seed() => bip39.generateMnemonic(); + +String getLegacySeedFromBip39(String mnemonic, + {int accountIndex = 0, String passphrase = ""}) { + final seed = bip39.mnemonicToSeed(mnemonic, passphrase: passphrase); + + final bip32KeyPair = + bip32.BIP32.fromSeed(seed).derivePath("m/44'/128'/$accountIndex'/0/0"); + + final spendKey = _reduceECKey(bip32KeyPair.privateKey!); + + return LegacySeedLang.getByEnglishName("English") + .encodePhrase(spendKey.toHexString()); +} + +const _ed25519CurveOrder = + "1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED"; + +Uint8List _reduceECKey(Uint8List buffer) { + final curveOrder = BigInt.parse(_ed25519CurveOrder, radix: 16); + final bigNumber = _readBytes(buffer); + + var result = bigNumber % curveOrder; + + final resultBuffer = Uint8List(32); + for (var i = 0; i < 32; i++) { + resultBuffer[i] = (result & BigInt.from(0xff)).toInt(); + result = result >> 8; + } + + return resultBuffer; +} + +/// Read BigInt from a little-endian Uint8List +/// From https://github.com/dart-lang/sdk/issues/32803#issuecomment-387405784 +BigInt _readBytes(Uint8List bytes) { + BigInt read(int start, int end) { + if (end - start <= 4) { + var result = 0; + for (int i = end - 1; i >= start; i--) { + result = result * 256 + bytes[i]; + } + return BigInt.from(result); + } + final mid = start + ((end - start) >> 1); + return read(start, mid) + + read(mid, end) * (BigInt.one << ((mid - start) * 8)); + } + + return read(0, bytes.length); +} diff --git a/cw_monero/lib/monero_account_list.dart b/cw_monero/lib/monero_account_list.dart index aa23e276f..c9a48a939 100644 --- a/cw_monero/lib/monero_account_list.dart +++ b/cw_monero/lib/monero_account_list.dart @@ -1,5 +1,6 @@ import 'package:cw_core/monero_amount_format.dart'; import 'package:cw_core/utils/print_verbose.dart'; +import 'package:cw_monero/api/wallet_manager.dart'; import 'package:mobx/mobx.dart'; import 'package:cw_core/account.dart'; import 'package:cw_monero/api/account_list.dart' as account_list; @@ -44,7 +45,18 @@ abstract class MoneroAccountListBase with Store { } } - List getAll() => account_list.getAllAccount().map((accountRow) { + static Map> cachedAccounts = {}; + + List getAll() { + final allAccounts = account_list.getAllAccount(); + final currentCount = allAccounts.length; + cachedAccounts[account_list.wptr!.address] ??= []; + + if (cachedAccounts[account_list.wptr!.address]!.length == currentCount) { + return cachedAccounts[account_list.wptr!.address]!; + } + + cachedAccounts[account_list.wptr!.address] = allAccounts.map((accountRow) { final balance = monero.SubaddressAccountRow_getUnlockedBalance(accountRow); return Account( @@ -53,6 +65,9 @@ abstract class MoneroAccountListBase with Store { balance: moneroAmountToString(amount: monero.Wallet_amountFromString(balance)), ); }).toList(); + + return cachedAccounts[account_list.wptr!.address]!; + } Future addAccount({required String label}) async { await account_list.addAccount(label: label); diff --git a/cw_monero/lib/monero_unspent.dart b/cw_monero/lib/monero_unspent.dart index 8a104edf4..224e33b2a 100644 --- a/cw_monero/lib/monero_unspent.dart +++ b/cw_monero/lib/monero_unspent.dart @@ -4,31 +4,47 @@ import 'package:cw_monero/api/coins_info.dart'; import 'package:monero/monero.dart' as monero; class MoneroUnspent extends Unspent { - MoneroUnspent( - String address, String hash, String keyImage, int value, bool isFrozen, this.isUnlocked) - : super(address, hash, value, 0, keyImage) { + static Future fromUnspent(String address, String hash, String keyImage, int value, bool isFrozen, bool isUnlocked) async { + return MoneroUnspent( + address: address, + hash: hash, + keyImage: keyImage, + value: value, + isFrozen: isFrozen, + isUnlocked: isUnlocked); } + MoneroUnspent( + {required String address, + required String hash, + required String keyImage, + required int value, + required bool isFrozen, + required this.isUnlocked}) + : super(address, hash, value, 0, keyImage) { + _frozen = isFrozen; + } + + bool _frozen = false; + @override set isFrozen(bool freeze) { + _frozen = freeze; printV("set isFrozen: $freeze ($keyImage): $freeze"); - final coinId = getCoinByKeyImage(keyImage!); - if (coinId == null) throw Exception("Unable to find a coin for address $address"); - if (freeze) { - freezeCoin(coinId); - } else { - thawCoin(coinId); - } + getCoinByKeyImage(keyImage!).then((coinId) async { + if (coinId == null) return; + if (freeze) { + await freezeCoin(coinId); + _frozen = true; + } else { + await thawCoin(coinId); + _frozen = false; + } + }); } @override - bool get isFrozen { - printV("get isFrozen"); - final coinId = getCoinByKeyImage(keyImage!); - if (coinId == null) throw Exception("Unable to find a coin for address $address"); - final coin = getCoin(coinId); - return monero.CoinsInfo_frozen(coin); - } + bool get isFrozen => _frozen; final bool isUnlocked; } diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index 00e50f37f..dc346a22c 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -169,6 +169,7 @@ abstract class MoneroWalletBase extends WalletBase> fetchTransactions() async { - transaction_history.refreshTransactions(); - return (await _getAllTransactionsOfAccount(walletAddresses.account?.id)) + await transaction_history.refreshTransactions(); + final resp = (await _getAllTransactionsOfAccount(walletAddresses.account?.id)) .fold>( {}, (Map acc, MoneroTransactionInfo tx) { acc[tx.id] = tx; return acc; }); + return resp; } Future updateTransactions() async { @@ -710,8 +714,17 @@ abstract class MoneroWalletBase extends WalletBase + transactionHistory.transactions.remove(id)); + + // Add or update transactions + transactions.forEach((key, tx) => + transactionHistory.transactions[key] = tx); await transactionHistory.save(); _isTransactionUpdating = false; } catch (e) { @@ -778,6 +791,7 @@ abstract class MoneroWalletBase extends WalletBase WalletType.monero; - @override - Future create(MoneroNewWalletCredentials credentials, {bool? isTestnet}) async { + @override + Future create(MoneroNewWalletCredentials credentials, + {bool? isTestnet}) async { try { final path = await pathForWallet(name: credentials.name, type: getType()); - if (credentials.isPolyseed) { + if (credentials.seedType == MoneroSeedType.bip39) { + return _restoreFromBip39( + path: path, + password: credentials.password!, + mnemonic: credentials.mnemonic ?? getBip39Seed(), + passphrase: credentials.passphrase, + walletInfo: credentials.walletInfo!, + ); + } + + if (credentials.seedType == MoneroSeedType.polyseed) { final polyseed = Polyseed.create(); final lang = PolyseedLang.getByEnglishName(credentials.language); - if (credentials.passphrase != null) polyseed.crypt(credentials.passphrase!); + if (credentials.passphrase != null) + polyseed.crypt(credentials.passphrase!); - final heightOverride = - getMoneroHeigthByDate(date: DateTime.now().subtract(Duration(days: 2))); + final heightOverride = getMoneroHeigthByDate( + date: DateTime.now().subtract(Duration(days: 2))); - return _restoreFromPolyseed( - path, credentials.password!, polyseed, credentials.walletInfo!, lang, + return _restoreFromPolyseed(path, credentials.password!, polyseed, + credentials.walletInfo!, lang, overrideHeight: heightOverride, passphrase: credentials.passphrase); } await monero_wallet_manager.createWallet( - path: path, password: credentials.password!, language: credentials.language, passphrase: credentials.passphrase??""); + path: path, + password: credentials.password!, + language: credentials.language, + passphrase: credentials.passphrase ?? ""); final wallet = MoneroWallet( walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource, @@ -145,7 +171,8 @@ class MoneroWalletService extends WalletService< } @override - Future openWallet(String name, String password, {OpenWalletTry openWalletTry = OpenWalletTry.initial}) async { + Future openWallet(String name, String password, + {OpenWalletTry openWalletTry = OpenWalletTry.initial}) async { try { final path = await pathForWallet(name: name, type: getType()); @@ -159,19 +186,12 @@ class MoneroWalletService extends WalletService< walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource, password: password); - final isValid = wallet.walletAddresses.validate(); if (wallet.isHardwareWallet) { wallet.setLedgerConnection(gLedger!); gLedger = null; } - if (!isValid) { - await restoreOrResetWalletFiles(name); - wallet.close(shouldCleanup: false); - return openWallet(name, password); - } - await wallet.init(); return wallet; @@ -310,8 +330,28 @@ class MoneroWalletService extends WalletService< rethrow; } + try { + if (isBip39Seed(credentials.mnemonic)) { + final path = + await pathForWallet(name: credentials.name, type: getType()); + + return _restoreFromBip39( + path: path, + password: credentials.password!, + mnemonic: credentials.mnemonic, + walletInfo: credentials.walletInfo!, + overrideHeight: credentials.height!, + passphrase: credentials.passphrase, + ); + } + } catch (e) { + printV("Bip39 restore failed: $e"); + rethrow; + } + try { final path = await pathForWallet(name: credentials.name, type: getType()); + monero_wallet_manager.restoreFromSeed( path: path, password: credentials.password!, @@ -332,6 +372,52 @@ class MoneroWalletService extends WalletService< } } + Future _restoreFromBip39({ + required String path, + required String password, + required String mnemonic, + required WalletInfo walletInfo, + String? passphrase, + int? overrideHeight, + }) async { + walletInfo.derivationInfo = DerivationInfo( + derivationType: DerivationType.bip39, + derivationPath: "m/44'/128'/0'/0/0", + ); + + final legacyMnemonic = + getLegacySeedFromBip39(mnemonic, passphrase: passphrase ?? ""); + final height = + overrideHeight ?? getMoneroHeigthByDate(date: DateTime.now()); + + walletInfo.isRecovery = true; + walletInfo.restoreHeight = height; + + monero_wallet_manager.restoreFromSeed( + path: path, + password: password, + passphrase: '', + seed: legacyMnemonic, + restoreHeight: height, + ); + + monero.Wallet_setCacheAttribute(wptr!, + key: "cakewallet.seed.bip39", value: mnemonic); + monero.Wallet_setCacheAttribute(wptr!, + key: "cakewallet.passphrase", value: passphrase ?? ''); + + monero.Wallet_store(wptr!); + + final wallet = MoneroWallet( + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfoSource, + password: password, + ); + await wallet.init(); + + return wallet; + } + Future restoreFromPolyseed( MoneroRestoreWalletFromSeedCredentials credentials) async { try { @@ -351,23 +437,21 @@ class MoneroWalletService extends WalletService< } } - Future _restoreFromPolyseed( - String path, String password, Polyseed polyseed, WalletInfo walletInfo, PolyseedLang lang, + Future _restoreFromPolyseed(String path, String password, + Polyseed polyseed, WalletInfo walletInfo, PolyseedLang lang, {PolyseedCoin coin = PolyseedCoin.POLYSEED_MONERO, int? overrideHeight, String? passphrase}) async { - - if (polyseed.isEncrypted == false && - (passphrase??'') != "") { + if (polyseed.isEncrypted == false && (passphrase ?? '') != "") { // Fallback to the different passphrase offset method, when a passphrase // was provided but the polyseed is not encrypted. monero_wallet_manager.restoreWalletFromPolyseedWithOffset( - path: path, - password: password, - seed: polyseed.encode(lang, coin), - seedOffset: passphrase??'', - language: "English"); - + path: path, + password: password, + seed: polyseed.encode(lang, coin), + seedOffset: passphrase ?? '', + language: "English"); + final wallet = MoneroWallet( walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource, @@ -444,7 +528,8 @@ class MoneroWalletService extends WalletService< if (walletFilesExist(path)) await repairOldAndroidWallet(name); - await monero_wallet_manager.openWalletAsync({'path': path, 'password': password}); + await monero_wallet_manager + .openWalletAsync({'path': path, 'password': password}); final walletInfo = walletInfoSource.values .firstWhere((info) => info.id == WalletBase.idFor(name, getType())); final wallet = MoneroWallet( diff --git a/cw_monero/pubspec.lock b/cw_monero/pubspec.lock index c808e5edb..278226b07 100644 --- a/cw_monero/pubspec.lock +++ b/cw_monero/pubspec.lock @@ -5,18 +5,23 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "4897882604d919befd350648c7f91926a9d5de99e67b455bf0917cc2362f4bb8" + sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" url: "https://pub.dev" source: hosted - version: "47.0.0" + version: "76.0.0" + _macros: + dependency: transitive + description: dart + source: sdk + version: "0.3.3" analyzer: dependency: transitive description: name: analyzer - sha256: "690e335554a8385bc9d787117d9eb52c0c03ee207a607e593de3c9d71b1cfe80" + sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" url: "https://pub.dev" source: hosted - version: "4.7.0" + version: "6.11.0" args: dependency: transitive description: @@ -41,6 +46,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.11.0" + bip32: + dependency: "direct main" + description: + name: bip32 + sha256: "54787cd7a111e9d37394aabbf53d1fc5e2e0e0af2cd01c459147a97c0e3f8a97" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + bip39: + dependency: "direct main" + description: + name: bip39 + sha256: de1ee27ebe7d96b84bb3a04a4132a0a3007dcdd5ad27dd14aa87a29d97c45edc + url: "https://pub.dev" + source: hosted + version: "1.0.6" blockchain_utils: dependency: transitive description: @@ -66,6 +87,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + bs58check: + dependency: transitive + description: + name: bs58check + sha256: c4a164d42b25c2f6bc88a8beccb9fc7d01440f3c60ba23663a20a70faf484ea9 + url: "https://pub.dev" + source: hosted + version: "1.0.2" build: dependency: transitive description: @@ -94,10 +123,10 @@ packages: dependency: "direct dev" description: name: build_resolvers - sha256: "687cf90a3951affac1bd5f9ecb5e3e90b60487f3d9cdc359bb310f8876bb02a6" + sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0 url: "https://pub.dev" source: hosted - version: "2.0.10" + version: "2.4.4" build_runner: dependency: "direct dev" description: @@ -222,10 +251,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "7a03456c3490394c8e7665890333e91ae8a49be43542b616e414449ac358acd4" + sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820" url: "https://pub.dev" source: hosted - version: "2.2.4" + version: "2.3.8" dbus: dependency: transitive description: @@ -348,6 +377,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.6.0" + hex: + dependency: transitive + description: + name: hex + sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a" + url: "https://pub.dev" + source: hosted + version: "0.2.0" hive: dependency: transitive description: @@ -360,10 +397,10 @@ packages: dependency: "direct dev" description: name: hive_generator - sha256: "81fd20125cb2ce8fd23623d7744ffbaf653aae93706c9bd3bf7019ea0ace3938" + sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4" url: "https://pub.dev" source: hosted - version: "1.1.3" + version: "2.0.1" http: dependency: "direct main" description: @@ -468,6 +505,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + macros: + dependency: transitive + description: + name: macros + sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" + url: "https://pub.dev" + source: hosted + version: "0.1.3-main.0" matcher: dependency: transitive description: @@ -512,10 +557,18 @@ packages: dependency: "direct dev" description: name: mobx_codegen - sha256: d4beb9cea4b7b014321235f8fdc7c2193ee0fe1d1198e9da7403f8bc85c4407c + sha256: "990da80722f7d7c0017dec92040b31545d625b15d40204c36a1e63d167c73cdc" url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.7.0" + mockito: + dependency: "direct dev" + description: + name: mockito + sha256: f99d8d072e249f719a5531735d146d8cf04c580d93920b04de75bef6dfb2daf6 + url: "https://pub.dev" + source: hosted + version: "5.4.5" monero: dependency: "direct main" description: @@ -735,18 +788,18 @@ packages: dependency: transitive description: name: source_gen - sha256: "2d79738b6bbf38a43920e2b8d189e9a3ce6cc201f4b8fc76be5e4fe377b1c38d" + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" url: "https://pub.dev" source: hosted - version: "1.2.6" + version: "1.5.0" source_helper: dependency: transitive description: name: source_helper - sha256: "3b67aade1d52416149c633ba1bb36df44d97c6b51830c2198e934e3fca87ca1f" + sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c" url: "https://pub.dev" source: hosted - version: "1.3.3" + version: "1.3.5" source_span: dependency: transitive description: @@ -924,5 +977,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.5.0 <4.0.0" + dart: ">=3.6.0 <4.0.0" flutter: ">=3.24.0" diff --git a/cw_monero/pubspec.yaml b/cw_monero/pubspec.yaml index 862109e94..0e1537ee0 100644 --- a/cw_monero/pubspec.yaml +++ b/cw_monero/pubspec.yaml @@ -12,6 +12,8 @@ environment: dependencies: flutter: sdk: flutter + bip39: ^1.0.6 + bip32: ^2.0.0 ffi: ^2.0.1 http: ^1.1.0 path_provider: ^2.0.11 @@ -36,7 +38,8 @@ dev_dependencies: build_runner: ^2.4.7 build_resolvers: ^2.0.9 mobx_codegen: ^2.0.7 - hive_generator: ^1.1.3 + mockito: ^5.4.5 + hive_generator: ^2.0.1 dependency_overrides: watcher: ^1.1.0 diff --git a/cw_monero/test/bip39_seed_test.dart b/cw_monero/test/bip39_seed_test.dart new file mode 100644 index 000000000..add461553 --- /dev/null +++ b/cw_monero/test/bip39_seed_test.dart @@ -0,0 +1,39 @@ +import 'package:cw_monero/bip39_seed.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group("Exodus Style bip39", () { + group("Test Wallet 1", () { + final bip39Seed = 'meadow tip best belt boss eyebrow control affair eternal piece very shiver'; + final expectedLegacySeed0 = "tasked eight afraid laboratory tail feline rift reinvest vane cafe bailed foggy dormant paper jigsaw king hazard suture king dapper dummy jolted dating dwindling king"; + final expectedLegacySeed1 = "palace pairing axes mohawk rekindle excess awful juvenile shipped talent nibs efficient dapper biggest swung fight pact innocent emerge issued titans affair nearby noises emerge"; + + test("Get legacy Seed from bip39", () { + final legacySeed = getLegacySeedFromBip39(bip39Seed); + expect(legacySeed, expectedLegacySeed0); + }); + + test("Get legacy Seed from bip39 with account index", () { + final legacySeed = getLegacySeedFromBip39(bip39Seed, accountIndex: 1); + expect(legacySeed, expectedLegacySeed1); + }); + }); + + group("Test Wallet 2", () { + final bip39Seed = "color ranch color remove subway public water embrace before begin liberty fault"; + final expectedLegacySeed0 = "somewhere problems gauze gigantic intended foxes upcoming saved waffle pipeline lurk bogeys empty wipeout abbey italics novelty tucks rafts elite lunar obnoxious awful bugs elite"; + final expectedLegacySeed1 = "playful toxic wildly eluded mesh fainted february mugged maps repent vigilant hitched seventh threaten clue fetches sample diet number alkaline future cottage tuition vegan alkaline"; + + test("Get legacy Seed from bip39", () { + final legacySeed = getLegacySeedFromBip39(bip39Seed); + expect(legacySeed, expectedLegacySeed0); + }); + + test("Get legacy Seed from bip39 with account index", () { + final legacySeed = getLegacySeedFromBip39(bip39Seed, accountIndex: 1); + expect(legacySeed, expectedLegacySeed1); + }); + }); + + }); +} diff --git a/cw_monero/test/mock/path_provider.dart b/cw_monero/test/mock/path_provider.dart new file mode 100644 index 000000000..7902d5bf0 --- /dev/null +++ b/cw_monero/test/mock/path_provider.dart @@ -0,0 +1,29 @@ +import 'package:mockito/mockito.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +class MockPathProviderPlatform extends Mock + with MockPlatformInterfaceMixin + implements PathProviderPlatform { + Future getTemporaryPath() => throw UnimplementedError(); + + Future getApplicationSupportPath() => throw UnimplementedError(); + + Future getLibraryPath() => throw UnimplementedError(); + + Future getApplicationDocumentsPath() async => "./test/data"; + + Future getExternalStoragePath() => throw UnimplementedError(); + + Future> getExternalCachePaths() => throw UnimplementedError(); + + Future getDownloadsPath() => throw UnimplementedError(); + + @override + Future getApplicationCachePath() => throw UnimplementedError(); + + @override + Future?> getExternalStoragePaths({StorageDirectory? type}) => + throw UnimplementedError(); +} diff --git a/cw_monero/test/monero_wallet_service_test.dart b/cw_monero/test/monero_wallet_service_test.dart new file mode 100644 index 000000000..a809dbc23 --- /dev/null +++ b/cw_monero/test/monero_wallet_service_test.dart @@ -0,0 +1,148 @@ +import 'dart:io'; + +import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:cw_monero/monero_wallet_service.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:hive/hive.dart'; +import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; + +import 'mock/path_provider.dart'; +import 'utils/setup_monero_c.dart'; + +Future main() async { + group("MoneroWalletService Tests", () { + Hive.init('./test/data/db'); + late MoneroWalletService walletService; + late File moneroCBinary; + + setUpAll(() async { + PathProviderPlatform.instance = MockPathProviderPlatform(); + + final Box walletInfoSource = + await Hive.openBox('testWalletInfo'); + final Box unspentCoinsInfoSource = + await Hive.openBox('testUnspentCoinsInfo'); + + walletService = MoneroWalletService(walletInfoSource, unspentCoinsInfoSource); + moneroCBinary = getMoneroCBinary().copySync(moneroCBinaryName); + }); + + tearDownAll(() { + Directory('./test/data').deleteSync(recursive: true); + moneroCBinary.deleteSync(); + }); + + group("Create wallet", () { + test("Create Legacy Wallet", () async { + final credentials = _getTestCreateCredentials( + name: 'Create Wallet LS', + language: 'English', + seedType: MoneroSeedType.legacy); + final wallet = await walletService.create(credentials); + + expect(wallet.seed.split(" ").length, 25); + expect(wallet.restoreHeight, greaterThan(3000000)); + }); + + test("Create Polyseed Wallet", () async { + final credentials = _getTestCreateCredentials( + name: 'Create Wallet PS', + language: 'English', + seedType: MoneroSeedType.polyseed); + final wallet = await walletService.create(credentials); + + expect(wallet.seed.split(" ").length, 16); + expect(wallet.restoreHeight, greaterThan(3000000)); + }); + + test("Create Bip39 Wallet", () async { + final credentials = _getTestCreateCredentials( + name: 'Create Wallet BS', + language: 'English', + seedType: MoneroSeedType.bip39); + final wallet = await walletService.create(credentials); + + expect(wallet.seed.split(" ").length, 12); + expect(wallet.restoreHeight, greaterThan(3000000)); + }); + }); + + group("Restore wallet", () { + test('Legacy Seed', () async { + final credentials = _getTestRestoreCredentials( + name: 'Test Wallet LS', + mnemonic: + 'ability pockets lordship tomorrow gypsy match neutral uncle avatar betting bicycle junk unzip pyramid lynx mammal edgy empty uneven knowledge juvenile wiring paradise psychic betting', + ); + + final wallet = await walletService.restoreFromSeed(credentials); + expect(wallet.walletAddresses.primaryAddress, + '48tLyQXpcwt8w6uKHyb5Zs3vdnoDWAEKFQr1c198o7aX9dBzXP3BTSMVsDiuH3ozDCNqwojb4vNeQZf7xg6URimDLaNtGSN'); + }); + + test('Bip39 Seed', () async { + final credentials = _getTestRestoreCredentials( + name: 'Test Wallet BS', + mnemonic: + 'color ranch color remove subway public water embrace before begin liberty fault'); + + final wallet = await walletService.restoreFromSeed(credentials); + expect(wallet.walletAddresses.primaryAddress, + '49MggvPosJugF8Zq7WAKbsSchz6vbyL6YiUxM4ryfGQDXphs6wiWiXLFWCSshnLPcceGTWUaKfWWMHQAAKESV3TQJVQsL9a'); + }); + }); + }); +} + +MoneroRestoreWalletFromSeedCredentials _getTestRestoreCredentials({ + required String name, + required String mnemonic, +}) { + final credentials = MoneroRestoreWalletFromSeedCredentials( + name: name, mnemonic: mnemonic, passphrase: '', password: "test"); + + credentials.walletInfo = WalletInfo.external( + id: WalletBase.idFor(name, WalletType.monero), + name: name, + type: WalletType.monero, + isRecovery: true, + restoreHeight: credentials.height ?? 0, + date: DateTime.now(), + path: '', + dirPath: '', + address: '', + ); + return credentials; +} + +MoneroNewWalletCredentials _getTestCreateCredentials({ + required String name, + required String language, + required MoneroSeedType seedType, + String? mnemonic, +}) { + final credentials = MoneroNewWalletCredentials( + name: name, + language: language, + seedType: seedType, + password: "test", + mnemonic: mnemonic, + passphrase: '', + ); + + credentials.walletInfo = WalletInfo.external( + id: WalletBase.idFor(name, WalletType.monero), + name: name, + type: WalletType.monero, + isRecovery: false, + restoreHeight: credentials.height ?? 0, + date: DateTime.now(), + path: '', + dirPath: '', + address: '', + ); + return credentials; +} diff --git a/cw_monero/test/utils/setup_monero_c.dart b/cw_monero/test/utils/setup_monero_c.dart new file mode 100644 index 000000000..1a0981000 --- /dev/null +++ b/cw_monero/test/utils/setup_monero_c.dart @@ -0,0 +1,16 @@ +import 'dart:io'; + +File getMoneroCBinary() { + if (Platform.isWindows) + return File( + '../scripts/monero_c/release/monero/x86_64-w64-mingw32_libwallet2_api_c.dll'); + if (Platform.isMacOS) return File('../macos/monero_libwallet2_api_c.dylib'); + return File('../scripts/monero_c/release/monero/x86_64-linux-gnu_libwallet2_api_c.so'); +} + +String get moneroCBinaryName { + if (Platform.isWindows) + return "monero_libwallet2_api_c.dll"; + if (Platform.isMacOS) return "monero_libwallet2_api_c.dylib"; + return "/usr/lib/monero_libwallet2_api_c.so"; +} diff --git a/cw_polygon/lib/polygon_wallet.dart b/cw_polygon/lib/polygon_wallet.dart index 4db24b32a..b2bf064b1 100644 --- a/cw_polygon/lib/polygon_wallet.dart +++ b/cw_polygon/lib/polygon_wallet.dart @@ -49,6 +49,10 @@ class PolygonWallet extends EVMChainWallet { } } + @override + List get getDefaultTokenContractAddresses => + DefaultPolygonErc20Tokens().initialPolygonErc20Tokens.map((e) => e.contractAddress).toList(); + @override Future checkIfScanProviderIsEnabled() async { bool isPolygonScanEnabled = (await sharedPrefs.future).getBool("use_polygonscan") ?? true; @@ -68,6 +72,7 @@ class PolygonWallet extends EVMChainWallet { enabled: token.enabled, tag: token.tag ?? "MATIC", iconPath: iconPath, + isPotentialScam: token.isPotentialScam, ); } diff --git a/cw_solana/lib/solana_client.dart b/cw_solana/lib/solana_client.dart index 0e08f9abb..858fb25d4 100644 --- a/cw_solana/lib/solana_client.dart +++ b/cw_solana/lib/solana_client.dart @@ -254,8 +254,9 @@ class SolanaWalletClient { final receiver = message.accountKeys[instruction.accounts[1]].address; String? tokenSymbol = splTokenSymbol; + if (tokenSymbol == null && mintAddress != null) { - final token = await fetchSPLTokenInfo(mintAddress); + final token = await getTokenInfo(mintAddress); tokenSymbol = token?.symbol; } @@ -288,9 +289,9 @@ class SolanaWalletClient { int? splTokenDecimal, Commitment? commitment, SolAddress? walletAddress, + required void Function(List) onUpdate, }) async { List transactions = []; - try { final signatures = await _provider!.request( SolanaRPCGetSignaturesForAddress( @@ -299,10 +300,11 @@ class SolanaWalletClient { ), ); - final List transactionDetails = []; + // The maximum concurrent batch size. + const int batchSize = 10; - for (int i = 0; i < signatures.length; i += 20) { - final batch = signatures.skip(i).take(20).toList(); // Get the next 20 signatures + for (int i = 0; i < signatures.length; i += batchSize) { + final batch = signatures.skip(i).take(batchSize).toList(); final batchResponses = await Future.wait(batch.map((signature) async { try { @@ -314,30 +316,31 @@ class SolanaWalletClient { ), ); } catch (e) { - printV("Error fetching transaction: $e"); + // printV("Error fetching transaction: $e"); return null; } })); - transactionDetails.addAll(batchResponses.whereType()); + final versionedBatchResponses = batchResponses.whereType(); - // to avoid reaching the node RPS limit - if (i + 20 < signatures.length) { + final parsedTransactionsFutures = versionedBatchResponses.map((tx) => parseTransaction( + txResponse: tx, + splTokenSymbol: splTokenSymbol, + walletAddress: walletAddress?.address ?? address.address, + )); + + final parsedTransactions = await Future.wait(parsedTransactionsFutures); + + transactions.addAll(parsedTransactions.whereType().toList()); + + // Calling the callback after each batch is processed, therefore passing the current list of transactions. + onUpdate(List.from(transactions)); + + if (i + batchSize < signatures.length) { await Future.delayed(const Duration(milliseconds: 500)); } } - for (final tx in transactionDetails) { - final parsedTx = await parseTransaction( - txResponse: tx, - splTokenSymbol: splTokenSymbol, - walletAddress: walletAddress?.address ?? address.address, - ); - if (parsedTx != null) { - transactions.add(parsedTx); - } - } - return transactions; } catch (err, s) { printV('Error fetching transactions: $err \n$s'); @@ -350,6 +353,7 @@ class SolanaWalletClient { required String splTokenSymbol, required int splTokenDecimal, required SolanaPrivateKey privateKey, + required void Function(List) onUpdate, }) async { ProgramDerivedAddress? associatedTokenAccount; final ownerWalletAddress = privateKey.publicKey().toAddress(); @@ -373,11 +377,26 @@ class SolanaWalletClient { splTokenSymbol: splTokenSymbol, splTokenDecimal: splTokenDecimal, walletAddress: ownerWalletAddress, + onUpdate: onUpdate, ); return tokenTransactions; } + final Map tokenInfoCache = {}; + + Future getTokenInfo(String mintAddress) async { + if (tokenInfoCache.containsKey(mintAddress)) { + return tokenInfoCache[mintAddress]; + } else { + final token = await fetchSPLTokenInfo(mintAddress); + if (token != null) { + tokenInfoCache[mintAddress] = token; + } + return token; + } + } + Future fetchSPLTokenInfo(String mintAddress) async { final programAddress = MetaplexTokenMetaDataProgramUtils.findMetadataPda(mint: SolAddress(mintAddress)); @@ -747,9 +766,7 @@ class SolanaWalletClient { } catch (e) { associatedRecipientAccount = null; - throw SolanaCreateAssociatedTokenAccountException( - 'Error fetching recipient associated token account: ${e.toString()}', - ); + throw SolanaCreateAssociatedTokenAccountException(e.toString()); } if (associatedRecipientAccount == null) { diff --git a/cw_solana/lib/solana_wallet.dart b/cw_solana/lib/solana_wallet.dart index 1495189db..ec65642bc 100644 --- a/cw_solana/lib/solana_wallet.dart +++ b/cw_solana/lib/solana_wallet.dart @@ -167,10 +167,10 @@ abstract class SolanaWalletBase try { final keypairBytes = Base58Decoder.decode(privateKey!); - return SolanaPrivateKey.fromSeed(keypairBytes); + return SolanaPrivateKey.fromBytes(keypairBytes); } catch (_) { final privateKeyBytes = HEX.decode(privateKey!); - return SolanaPrivateKey.fromBytes(privateKeyBytes); + return SolanaPrivateKey.fromSeed(privateKeyBytes); } } @@ -292,9 +292,14 @@ abstract class SolanaWalletBase @override Future> fetchTransactions() async => {}; + void updateTransactions(List updatedTx) { + _addTransactionsToTransactionHistory(updatedTx); + } + /// Fetches the native SOL transactions linked to the wallet Public Key Future _updateNativeSOLTransactions() async { - final transactions = await _client.fetchTransactions(_solanaPublicKey.toAddress()); + final transactions = + await _client.fetchTransactions(_solanaPublicKey.toAddress(), onUpdate: updateTransactions); await _addTransactionsToTransactionHistory(transactions); } @@ -313,6 +318,7 @@ abstract class SolanaWalletBase splTokenSymbol: token.symbol, splTokenDecimal: token.decimal, privateKey: _solanaPrivateKey, + onUpdate: updateTransactions, ); // splTokenTransactions.addAll(tokenTxs); diff --git a/cw_solana/lib/spl_token.dart b/cw_solana/lib/spl_token.dart index 97538a706..fe793bbf6 100644 --- a/cw_solana/lib/spl_token.dart +++ b/cw_solana/lib/spl_token.dart @@ -6,6 +6,7 @@ part 'spl_token.g.dart'; @HiveType(typeId: SPLToken.typeId) class SPLToken extends CryptoCurrency with HiveObjectMixin { + @override @HiveField(0) final String name; @@ -24,12 +25,18 @@ class SPLToken extends CryptoCurrency with HiveObjectMixin { @HiveField(5) final String mint; + @override @HiveField(6) final String? iconPath; + @override @HiveField(7) final String? tag; + @override + @HiveField(8, defaultValue: false) + final bool isPotentialScam; + SPLToken({ required this.name, required this.symbol, @@ -39,6 +46,7 @@ class SPLToken extends CryptoCurrency with HiveObjectMixin { this.iconPath, this.tag = 'SOL', bool enabled = true, + this.isPotentialScam = false, }) : _enabled = enabled, super( name: mint.toLowerCase(), @@ -47,6 +55,7 @@ class SPLToken extends CryptoCurrency with HiveObjectMixin { tag: tag, iconPath: iconPath, decimals: decimal, + isPotentialScam: isPotentialScam, ); factory SPLToken.fromMetadata({ @@ -55,6 +64,7 @@ class SPLToken extends CryptoCurrency with HiveObjectMixin { required String symbol, required String mintAddress, String? iconPath, + bool isPotentialScam = false, }) { return SPLToken( name: name, @@ -63,28 +73,14 @@ class SPLToken extends CryptoCurrency with HiveObjectMixin { decimal: 0, mint: mint, iconPath: iconPath, + isPotentialScam: isPotentialScam, ); } - factory SPLToken.cryptoCurrency({ - required String name, - required String symbol, - required int decimals, - required String iconPath, - required String mint, - }) { - return SPLToken( - name: name, - symbol: symbol, - decimal: decimals, - mint: mint, - iconPath: iconPath, - mintAddress: '', - ); - } - + @override bool get enabled => _enabled; + @override set enabled(bool value) => _enabled = value; SPLToken.copyWith(SPLToken other, String? icon, String? tag) @@ -96,6 +92,7 @@ class SPLToken extends CryptoCurrency with HiveObjectMixin { mint = other.mint, tag = other.tag, iconPath = icon, + isPotentialScam = other.isPotentialScam, super( title: other.symbol.toUpperCase(), name: other.symbol.toLowerCase(), @@ -103,6 +100,7 @@ class SPLToken extends CryptoCurrency with HiveObjectMixin { fullName: other.name, tag: other.tag, iconPath: icon, + isPotentialScam: other.isPotentialScam, ); static const typeId = SPL_TOKEN_TYPE_ID; diff --git a/cw_tron/lib/tron_token.dart b/cw_tron/lib/tron_token.dart index 8c45ab486..182b84884 100644 --- a/cw_tron/lib/tron_token.dart +++ b/cw_tron/lib/tron_token.dart @@ -25,10 +25,13 @@ class TronToken extends CryptoCurrency with HiveObjectMixin { @HiveField(5) final String? iconPath; - + @HiveField(6) final String? tag; + @HiveField(7, defaultValue: false) + final bool isPotentialScam; + bool get enabled => _enabled; set enabled(bool value) => _enabled = value; @@ -41,14 +44,17 @@ class TronToken extends CryptoCurrency with HiveObjectMixin { bool enabled = true, this.iconPath, this.tag = 'TRX', + this.isPotentialScam = false, }) : _enabled = enabled, super( - name: symbol.toLowerCase(), - title: symbol.toUpperCase(), - fullName: name, - tag: tag, - iconPath: iconPath, - decimals: decimal); + name: symbol.toLowerCase(), + title: symbol.toUpperCase(), + fullName: name, + tag: tag, + iconPath: iconPath, + decimals: decimal, + isPotentialScam: isPotentialScam, + ); TronToken.copyWith(TronToken other, String? icon, String? tag) : name = other.name, @@ -58,6 +64,7 @@ class TronToken extends CryptoCurrency with HiveObjectMixin { _enabled = other.enabled, tag = tag ?? other.tag, iconPath = icon ?? other.iconPath, + isPotentialScam = other.isPotentialScam, super( name: other.name, title: other.symbol.toUpperCase(), @@ -65,6 +72,7 @@ class TronToken extends CryptoCurrency with HiveObjectMixin { tag: tag ?? other.tag, iconPath: icon ?? other.iconPath, decimals: other.decimal, + isPotentialScam: other.isPotentialScam, ); static const typeId = TRON_TOKEN_TYPE_ID; diff --git a/cw_tron/lib/tron_wallet.dart b/cw_tron/lib/tron_wallet.dart index c6175dd32..eb80696ce 100644 --- a/cw_tron/lib/tron_wallet.dart +++ b/cw_tron/lib/tron_wallet.dart @@ -509,11 +509,15 @@ abstract class TronWalletBase Future addTronToken(TronToken token) async { String? iconPath; - try { - iconPath = CryptoCurrency.all - .firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase()) - .iconPath; - } catch (_) {} + if ((token.iconPath == null || token.iconPath!.isEmpty) && !token.isPotentialScam) { + try { + iconPath = CryptoCurrency.all + .firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase()) + .iconPath; + } catch (_) {} + } else if (!token.isPotentialScam) { + iconPath = token.iconPath; + } final newToken = TronToken( name: token.name, @@ -523,6 +527,7 @@ abstract class TronWalletBase enabled: token.enabled, tag: token.tag ?? "TRX", iconPath: iconPath, + isPotentialScam: token.isPotentialScam, ); await tronTokensBox.put(newToken.contractAddress, newToken); diff --git a/cw_wownero/lib/api/account_list.dart b/cw_wownero/lib/api/account_list.dart index a73e4dcd2..5bd18d51e 100644 --- a/cw_wownero/lib/api/account_list.dart +++ b/cw_wownero/lib/api/account_list.dart @@ -48,8 +48,7 @@ void addAccountSync({required String label}) { } void setLabelForAccountSync({required int accountIndex, required String label}) { - // TODO(mrcyjanek): this may be wrong function? - wownero.Wallet_setSubaddressLabel(wptr!, accountIndex: accountIndex, addressIndex: 0, label: label); + wownero.SubaddressAccount_setLabel(subaddressAccount!, accountIndex: accountIndex, label: label); } void _addAccount(String label) => addAccountSync(label: label); diff --git a/cw_wownero/lib/api/wallet.dart b/cw_wownero/lib/api/wallet.dart index e124e2979..20783490d 100644 --- a/cw_wownero/lib/api/wallet.dart +++ b/cw_wownero/lib/api/wallet.dart @@ -354,3 +354,5 @@ String signMessage(String message, {String address = ""}) { bool verifyMessage(String message, String address, String signature) { return wownero.Wallet_verifySignedMessage(wptr!, message: message, address: address, signature: signature); } + +Map> debugCallLength() => wownero.debugCallLength; \ No newline at end of file diff --git a/cw_zano/lib/zano_wallet_api.dart b/cw_zano/lib/zano_wallet_api.dart index f2c5469c4..f10e0b2d6 100644 --- a/cw_zano/lib/zano_wallet_api.dart +++ b/cw_zano/lib/zano_wallet_api.dart @@ -508,4 +508,6 @@ Future _closeWallet(int hWallet) async { }); printV("Closing wallet: $str"); return str; -} \ No newline at end of file +} + +Map> debugCallLength() => zano.debugCallLength; \ No newline at end of file diff --git a/devtools_options.yaml b/devtools_options.yaml deleted file mode 100644 index 7e7e7f67d..000000000 --- a/devtools_options.yaml +++ /dev/null @@ -1 +0,0 @@ -extensions: diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json index df5cf1cc5..bd04914ae 100644 --- a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,114 +1,134 @@ { - "images" : [ + "images": [ { - "filename" : "Icon-App-40x40@1x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "20x20" + "filename": "AppIcon@2x.png", + "idiom": "iphone", + "scale": "2x", + "size": "60x60" }, { - "filename" : "Icon-App-20x20@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "20x20" + "filename": "AppIcon@3x.png", + "idiom": "iphone", + "scale": "3x", + "size": "60x60" }, { - "filename" : "Icon-App-29x29@2x 1.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "29x29" + "filename": "AppIcon~ipad.png", + "idiom": "ipad", + "scale": "1x", + "size": "76x76" }, { - "filename" : "Icon-App-29x29@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "29x29" + "filename": "AppIcon@2x~ipad.png", + "idiom": "ipad", + "scale": "2x", + "size": "76x76" }, { - "idiom" : "iphone", - "scale" : "2x", - "size" : "40x40" + "filename": "AppIcon-83.5@2x~ipad.png", + "idiom": "ipad", + "scale": "2x", + "size": "83.5x83.5" }, { - "filename" : "Icon-App-40x40@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "40x40" + "filename": "AppIcon-40@2x.png", + "idiom": "iphone", + "scale": "2x", + "size": "40x40" }, { - "filename" : "app_icon_120.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "60x60" + "filename": "AppIcon-40@3x.png", + "idiom": "iphone", + "scale": "3x", + "size": "40x40" }, { - "filename" : "app_icon_180.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "60x60" + "filename": "AppIcon-40~ipad.png", + "idiom": "ipad", + "scale": "1x", + "size": "40x40" }, { - "filename" : "Icon-App-20x20@1x.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "20x20" + "filename": "AppIcon-40@2x~ipad.png", + "idiom": "ipad", + "scale": "2x", + "size": "40x40" }, { - "filename" : "Icon-App-20x20@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "20x20" + "filename": "AppIcon-20@2x.png", + "idiom": "iphone", + "scale": "2x", + "size": "20x20" }, { - "filename" : "Icon-App-29x29@1x.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "29x29" + "filename": "AppIcon-20@3x.png", + "idiom": "iphone", + "scale": "3x", + "size": "20x20" }, { - "filename" : "Icon-App-29x29@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "29x29" + "filename": "AppIcon-20~ipad.png", + "idiom": "ipad", + "scale": "1x", + "size": "20x20" }, { - "filename" : "Icon-App-40x40@1x 1.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "40x40" + "filename": "AppIcon-20@2x~ipad.png", + "idiom": "ipad", + "scale": "2x", + "size": "20x20" }, { - "idiom" : "ipad", - "scale" : "2x", - "size" : "40x40" + "filename": "AppIcon-29.png", + "idiom": "iphone", + "scale": "1x", + "size": "29x29" }, { - "filename" : "Icon-App-76x76@1x.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "76x76" + "filename": "AppIcon-29@2x.png", + "idiom": "iphone", + "scale": "2x", + "size": "29x29" }, { - "filename" : "Icon-App-76x76@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "76x76" + "filename": "AppIcon-29@3x.png", + "idiom": "iphone", + "scale": "3x", + "size": "29x29" }, { - "filename" : "Icon-App-83.5x83.5@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "83.5x83.5" + "filename": "AppIcon-29~ipad.png", + "idiom": "ipad", + "scale": "1x", + "size": "29x29" }, { - "filename" : "app_icon_1024.png", - "idiom" : "ios-marketing", - "scale" : "1x", - "size" : "1024x1024" + "filename": "AppIcon-29@2x~ipad.png", + "idiom": "ipad", + "scale": "2x", + "size": "29x29" + }, + { + "filename": "AppIcon-60@2x~car.png", + "idiom": "car", + "scale": "2x", + "size": "60x60" + }, + { + "filename": "AppIcon-60@3x~car.png", + "idiom": "car", + "scale": "3x", + "size": "60x60" + }, + { + "filename": "AppIcon~ios-marketing.png", + "idiom": "ios-marketing", + "scale": "1x", + "size": "1024x1024" } ], - "info" : { - "author" : "xcode", - "version" : 1 + "info": { + "author": "iconkitchen", + "version": 1 } -} +} \ No newline at end of file diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png deleted file mode 100644 index 369d8d9a4..000000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png deleted file mode 100644 index 65ed7f3db..000000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index fb14bfc55..000000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index d24d594a3..000000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x 1.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x 1.png deleted file mode 100644 index 07acd0a82..000000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x 1.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index 07acd0a82..000000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png deleted file mode 100644 index bdc20091d..000000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x 1.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x 1.png deleted file mode 100644 index 65ed7f3db..000000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x 1.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png deleted file mode 100644 index 65ed7f3db..000000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png deleted file mode 100644 index 80e78be41..000000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png deleted file mode 100644 index e06998b67..000000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png deleted file mode 100644 index 78a2ccfb1..000000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and /dev/null differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index 0ba8d647c..000000000 Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and /dev/null differ diff --git a/lib/buy/onramper/onramper_buy_provider.dart b/lib/buy/onramper/onramper_buy_provider.dart index 827877dcb..ac198b488 100644 --- a/lib/buy/onramper/onramper_buy_provider.dart +++ b/lib/buy/onramper/onramper_buy_provider.dart @@ -37,6 +37,7 @@ class OnRamperBuyProvider extends BuyProvider { static const List _notSupportedCrypto = []; static const List _notSupportedFiat = []; + static Map _onrampMetadata = {}; final SettingsStore _settingsStore; @@ -59,11 +60,8 @@ class OnRamperBuyProvider extends BuyProvider { Future> getAvailablePaymentTypes( String fiatCurrency, CryptoCurrency cryptoCurrency, bool isBuyAction) async { - final params = { - 'fiatCurrency': fiatCurrency, - 'type': isBuyAction ? 'buy' : 'sell', - 'isRecurringPayment': 'false' - }; + + final params = {'type': isBuyAction ? 'buy' : 'sell'}; final url = Uri.https(_baseApiUrl, '$supported$paymentTypes/$fiatCurrency', params); @@ -136,16 +134,14 @@ class OnRamperBuyProvider extends BuyProvider { final actionType = isBuyAction ? 'buy' : 'sell'; - final normalizedCryptoCurrency = _getNormalizeCryptoCurrency(cryptoCurrency); + final normalizedCryptoCurrency = + cryptoCurrency.title + _getNormalizeNetwork(cryptoCurrency).toUpperCase(); final params = { 'amount': amount.toString(), if (paymentMethod != null) 'paymentMethod': paymentMethod, 'clientName': 'CakeWallet', - 'type': actionType, - 'walletAddress': walletAddress, - 'isRecurringPayment': 'false', - 'input': 'source', + if (actionType == 'sell') 'type': actionType, }; log('Onramper: Fetching $actionType quote: ${isBuyAction ? normalizedCryptoCurrency : fiatCurrency.name} -> ${isBuyAction ? fiatCurrency.name : normalizedCryptoCurrency}, amount: $amount, paymentMethod: $paymentMethod'); @@ -165,7 +161,7 @@ class OnRamperBuyProvider extends BuyProvider { List validQuotes = []; - final onrampMetadata = await getOnrampMetadata(); + if (_onrampMetadata.isEmpty) _onrampMetadata = await getOnrampMetadata(); for (var item in data) { @@ -174,12 +170,12 @@ class OnRamperBuyProvider extends BuyProvider { final paymentMethod = (item as Map)['paymentMethod'] as String; final rampId = item['ramp'] as String?; - final rampMetaData = onrampMetadata[rampId] as Map?; + final rampMetaData = _onrampMetadata[rampId] as Map?; if (rampMetaData == null) continue; final quote = Quote.fromOnramperJson( - item, isBuyAction, onrampMetadata, _getPaymentTypeByString(paymentMethod)); + item, isBuyAction, _onrampMetadata, _getPaymentTypeByString(paymentMethod)); quote.setFiatCurrency = fiatCurrency; quote.setCryptoCurrency = cryptoCurrency; validQuotes.add(quote); @@ -206,7 +202,6 @@ class OnRamperBuyProvider extends BuyProvider { required String cryptoCurrencyAddress, String? countryCode}) async { final actionType = isBuyAction ? 'buy' : 'sell'; - final prefix = actionType == 'sell' ? actionType + '_' : ''; final primaryColor = getColorStr(Theme.of(context).primaryColor); final secondaryColor = getColorStr(Theme.of(context).colorScheme.background); @@ -220,19 +215,21 @@ class OnRamperBuyProvider extends BuyProvider { cardColor = getColorStr(Colors.white); } - final defaultCrypto = _getNormalizeCryptoCurrency(quote.cryptoCurrency); + final defaultCrypto = + quote.cryptoCurrency.title + _getNormalizeNetwork(quote.cryptoCurrency).toLowerCase(); final paymentMethod = normalizePaymentMethod(quote.paymentType); final uri = Uri.https(_baseUrl, '', { 'apiKey': _apiKey, - 'mode': actionType, - '${prefix}defaultFiat': quote.fiatCurrency.name, - '${prefix}defaultCrypto': defaultCrypto, - '${prefix}defaultAmount': amount.toString(), - if (paymentMethod != null) '${prefix}defaultPaymentMethod': paymentMethod, - 'onlyOnramps': quote.rampId, - 'networkWallets': '${quote.cryptoCurrency.fullName?.toUpperCase()}:$cryptoCurrencyAddress', + 'txnType': actionType, + 'txnFiat': quote.fiatCurrency.name, + 'txnCrypto': defaultCrypto, + 'txnAmount': amount.toString(), + 'skipTransactionScreen': "true", + if (paymentMethod != null) 'txnPaymentMethod': paymentMethod, + 'txnOnramp': quote.rampId, + 'networkWallets': '${_tagToNetwork(quote.cryptoCurrency.tag ?? quote.cryptoCurrency.title)}:$cryptoCurrencyAddress', 'supportSwap': "false", 'primaryColor': primaryColor, 'secondaryColor': secondaryColor, @@ -257,31 +254,29 @@ class OnRamperBuyProvider extends BuyProvider { String _tagToNetwork(String tag) { switch (tag) { - case 'OMNI': - case 'BSC': - return tag; case 'POL': return 'POLYGON'; + case 'ETH': + return 'ETHEREUM'; + case 'TRX': + return 'TRON'; + case 'SOL': + return 'SOLANA'; case 'ZEC': return 'ZCASH'; - default: - try { - return CryptoCurrency.fromString(tag).fullName!; - } catch (_) { - return tag; - } + default: + return tag; } } - String _getNormalizeCryptoCurrency(Currency currency) { - if (currency is CryptoCurrency) { - if (!mainCurrency.contains(currency)) { - final network = currency.tag == null ? currency.fullName : _tagToNetwork(currency.tag!); - return '${currency.title}_${network?.replaceAll(' ', '')}'.toUpperCase(); - } - return currency.title.toUpperCase(); - } - return currency.name.toUpperCase(); + String _getNormalizeNetwork(CryptoCurrency currency) { + if (mainCurrency.contains(currency)) return ''; + + if (currency == CryptoCurrency.eos) return '_EOSIO'; + + if (currency.tag != null) return '_' + _tagToNetwork(currency.tag!); + + return '_' + (currency.fullName?.replaceAll(' ', '') ?? currency.title);; } String? normalizePaymentMethod(PaymentType paymentType) { diff --git a/lib/buy/robinhood/robinhood_buy_provider.dart b/lib/buy/robinhood/robinhood_buy_provider.dart index 90177ec61..93efd5642 100644 --- a/lib/buy/robinhood/robinhood_buy_provider.dart +++ b/lib/buy/robinhood/robinhood_buy_provider.dart @@ -35,9 +35,16 @@ class RobinhoodBuyProvider extends BuyProvider { static const _baseUrl = 'applink.robinhood.com'; static const _cIdBaseUrl = 'exchange-helper.cakewallet.com'; + static const _apiBaseUrl = 'api.robinhood.com'; + static const _assetsPath = '/catpay/v1/supported_currencies/'; - static const List _notSupportedCrypto = []; - static const List _notSupportedFiat = []; + static List _supportedCrypto = []; + static final List _supportedFiat = [FiatCurrency.usd]; + + static final List _notSupportedCrypto = []; + + static final List _notSupportedFiat = + FiatCurrency.all.where((fiat) => !_supportedFiat.contains(fiat)).toList(); @override String get title => 'Robinhood Connect'; @@ -58,6 +65,38 @@ class RobinhoodBuyProvider extends BuyProvider { String get _apiSecret => secrets.exchangeHelperApiKey; + Future> getSupportedAssets() async { + final uri = Uri.https(_apiBaseUrl, '$_assetsPath', {'applicationId': _applicationId}); + + try { + final response = await http.get(uri, headers: {'accept': 'application/json'}); + + if (response.statusCode == 200) { + final responseData = jsonDecode(response.body) as Map; + final pairs = responseData['cryptoCurrencyPairs'] as List; + + final supportedAssets = []; + + for (final item in pairs) { + String code = item['assetCurrency']['code'] as String; + if (code == 'AVAX') code = 'AVAXC'; + try { + final currency = CryptoCurrency.fromString(code); + supportedAssets.add(currency); + } catch (e) { + log('Robinhood: Unknown asset code "$code" - skipped'); + } + } + return supportedAssets; + } else { + return []; + } + } catch (e) { + log('Robinhood: Failed to fetch supported assets: $e'); + return []; + } + } + Future getSignature(String message) async { switch (wallet.type) { case WalletType.ethereum: @@ -156,6 +195,9 @@ class RobinhoodBuyProvider extends BuyProvider { String? countryCode}) async { String? paymentMethod; + if (_supportedCrypto.isEmpty) _supportedCrypto = await getSupportedAssets(); + if (_supportedCrypto.isNotEmpty && !(_supportedCrypto.contains(cryptoCurrency))) return null; + if (paymentType != null && paymentType != PaymentType.all) { paymentMethod = normalizePaymentMethod(paymentType); if (paymentMethod == null) return null; diff --git a/lib/core/open_crypto_pay/exceptions.dart b/lib/core/open_crypto_pay/exceptions.dart new file mode 100644 index 000000000..71010482f --- /dev/null +++ b/lib/core/open_crypto_pay/exceptions.dart @@ -0,0 +1,18 @@ +class OpenCryptoPayException implements Exception { + final String message; + + OpenCryptoPayException([this.message = '']); + + @override + String toString() => + 'OpenCryptoPayException${message.isNotEmpty ? ': $message' : ''}'; +} + +class OpenCryptoPayNotSupportedException extends OpenCryptoPayException { + final String provider; + + OpenCryptoPayNotSupportedException(this.provider); + + @override + String get message => "$provider does not support Open CryptoPay"; +} diff --git a/lib/core/open_crypto_pay/lnurl.dart b/lib/core/open_crypto_pay/lnurl.dart new file mode 100644 index 000000000..0087bab51 --- /dev/null +++ b/lib/core/open_crypto_pay/lnurl.dart @@ -0,0 +1,78 @@ +import 'dart:convert'; + +import 'package:bech32/bech32.dart'; + +Uri decodeLNURL(String encodedUrl) { + Uri decodedUri; + + /// The URL doesn't have to be encoded at all as per LUD-17: Protocol schemes and raw (non bech32-encoded) URLs. + /// https://github.com/lnurl/luds/blob/luds/17.md + /// Handle non bech32-encoded LNURL + final lud17prefixes = ['lnurlw', 'lnurlc', 'lnurlp', 'keyauth']; + decodedUri = Uri.parse(encodedUrl); + for (final prefix in lud17prefixes) { + if (decodedUri.scheme.contains(prefix)) { + decodedUri = decodedUri.replace(scheme: prefix); + } + } + if (lud17prefixes.contains(decodedUri.scheme)) { + /// If the non-bech32 LNURL is a Tor address, the port has to be http instead of https for the clearnet LNURL so check if the host ends with '.onion' or '.onion.' + decodedUri = decodedUri.replace( + scheme: decodedUri.host.endsWith('onion') || + decodedUri.host.endsWith('onion.') + ? 'http' + : 'https'); + } else { + /// Try to parse the input as a lnUrl. Will throw an error if it fails. + final lnUrl = _findLnUrl(encodedUrl); + + /// Decode the lnurl using bech32 + final bech32 = const Bech32Codec().decode(lnUrl, lnUrl.length); + decodedUri = Uri.parse(utf8.decode(_convert(bech32.data, 5, 8, false))); + } + return decodedUri; +} + +/// Parse and return a given lnurl string if it's valid. Will remove +/// `lightning:` from the beginning of it if present. +String _findLnUrl(String input) { + final res = RegExp( + r',*?((lnurl)([0-9]+[a-z0-9]+))', + ).allMatches(input.toLowerCase()); + + if (res.length == 1) { + return res.first.group(0)!; + } else { + throw ArgumentError('Not a valid lnurl string'); + } +} + +/// Taken from bech32 (bitcoinjs): https://github.com/bitcoinjs/bech32 +List _convert(List data, int inBits, int outBits, bool pad) { + var value = 0; + var bits = 0; + final maxV = (1 << outBits) - 1; + + final result = []; + for (final dataValue in data) { + value = (value << inBits) | dataValue; + bits += inBits; + + while (bits >= outBits) { + bits -= outBits; + result.add((value >> bits) & maxV); + } + } + + if (pad) { + if (bits > 0) result.add((value << (outBits - bits)) & maxV); + } else { + if (bits >= inBits) throw Exception('Excess padding'); + + if ((value << (outBits - bits)) & maxV > 0) { + throw Exception('Non-zero padding'); + } + } + + return result; +} diff --git a/lib/core/open_crypto_pay/models.dart b/lib/core/open_crypto_pay/models.dart new file mode 100644 index 000000000..760200f8d --- /dev/null +++ b/lib/core/open_crypto_pay/models.dart @@ -0,0 +1,26 @@ +class OpenCryptoPayRequest { + final String receiverName; + final int expiry; + final String callbackUrl; + final String quote; + final Map> methods; + + OpenCryptoPayRequest({ + required this.receiverName, + required this.expiry, + required this.callbackUrl, + required this.quote, + required this.methods, + }); +} + +class OpenCryptoPayQuoteAsset { + final String symbol; + final String amount; + + const OpenCryptoPayQuoteAsset(this.symbol, this.amount); + + OpenCryptoPayQuoteAsset.fromJson(Map json) + : symbol = json['asset'] as String, + amount = json['amount'] as String; +} diff --git a/lib/core/open_crypto_pay/open_cryptopay_service.dart b/lib/core/open_crypto_pay/open_cryptopay_service.dart new file mode 100644 index 000000000..6449bbf90 --- /dev/null +++ b/lib/core/open_crypto_pay/open_cryptopay_service.dart @@ -0,0 +1,184 @@ +import 'dart:convert'; +import 'dart:developer'; + +import 'package:cake_wallet/core/open_crypto_pay/exceptions.dart'; +import 'package:cake_wallet/core/open_crypto_pay/lnurl.dart'; +import 'package:cake_wallet/core/open_crypto_pay/models.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:http/http.dart'; + +class OpenCryptoPayService { + static bool isOpenCryptoPayQR(String value) => + value.toLowerCase().contains("lightning=lnurl") || + value.toLowerCase().startsWith("lnurl"); + + final Client _httpClient = Client(); + + Future commitOpenCryptoPayRequest( + String txHex, { + required String txId, + required OpenCryptoPayRequest request, + required CryptoCurrency asset, + }) async { + final uri = Uri.parse(request.callbackUrl.replaceAll("/cb/", "/tx/")); + + final queryParams = Map.of(uri.queryParameters); + + queryParams['quote'] = request.quote; + queryParams['asset'] = asset.title; + queryParams['method'] = _getMethod(asset); + queryParams['hex'] = txHex; + queryParams['tx'] = txId; + + final response = + await _httpClient.get(Uri.https(uri.authority, uri.path, queryParams)); + + if (response.statusCode == 200) { + final body = jsonDecode(response.body) as Map; + + if (body.keys.contains("txId")) return body["txId"] as String; + throw OpenCryptoPayException(body.toString()); + } + throw OpenCryptoPayException( + "Unexpected status code ${response.statusCode} ${response.body}"); + } + + Future cancelOpenCryptoPayRequest(OpenCryptoPayRequest request) async { + final uri = Uri.parse(request.callbackUrl.replaceAll("/cb/", "/cancel/")); + + await _httpClient.delete(uri); + } + + Future getOpenCryptoPayInvoice(String lnUrl) async { + if (lnUrl.toLowerCase().startsWith("http")) { + final uri = Uri.parse(lnUrl); + final params = uri.queryParameters; + if (!params.containsKey("lightning")) { + throw OpenCryptoPayNotSupportedException(uri.authority); + } + + lnUrl = params["lightning"] as String; + } + final url = decodeLNURL(lnUrl); + final params = await _getOpenCryptoPayParams(url); + + return OpenCryptoPayRequest( + receiverName: params.$1.displayName ?? "Unknown", + expiry: params.$1.expiration.difference(DateTime.now()).inSeconds, + callbackUrl: params.$1.callbackUrl, + quote: params.$1.id, + methods: params.$2, + ); + } + + Future<(_OpenCryptoPayQuote, Map>)> + _getOpenCryptoPayParams(Uri uri) async { + final response = await _httpClient.get(uri); + + if (response.statusCode == 200) { + final responseBody = jsonDecode(response.body) as Map; + + for (final key in ['callback', 'transferAmounts', 'quote']) { + if (!responseBody.keys.contains(key)) { + throw OpenCryptoPayNotSupportedException(uri.authority); + } + } + + final methods = >{}; + for (final transferAmountRaw in responseBody['transferAmounts'] as List) { + final transferAmount = transferAmountRaw as Map; + final method = transferAmount['method'] as String; + methods[method] = []; + for (final assetJson in transferAmount['assets'] as List) { + final asset = OpenCryptoPayQuoteAsset.fromJson( + assetJson as Map); + methods[method]?.add(asset); + } + } + + log(responseBody.toString()); + + final quote = _OpenCryptoPayQuote.fromJson( + responseBody['callback'] as String, + responseBody['displayName'] as String?, + responseBody['quote'] as Map); + + return (quote, methods); + } else { + throw OpenCryptoPayException( + 'Failed to get Open CryptoPay Request. Status: ${response.statusCode} ${response.body}'); + } + } + + Future getOpenCryptoPayAddress( + OpenCryptoPayRequest request, CryptoCurrency asset) async { + final uri = Uri.parse(request.callbackUrl); + final queryParams = Map.of(uri.queryParameters); + + queryParams['quote'] = request.quote; + queryParams['asset'] = asset.title; + queryParams['method'] = _getMethod(asset); + + final response = + await _httpClient.get(Uri.https(uri.authority, uri.path, queryParams)); + + if (response.statusCode == 200) { + final responseBody = jsonDecode(response.body) as Map; + + for (final key in ['expiryDate', 'uri']) { + if (!responseBody.keys.contains(key)) { + throw OpenCryptoPayNotSupportedException(uri.authority); + } + } + + final result = Uri.parse(responseBody['uri'] as String); + + if (result.queryParameters['amount'] != null) return result; + + final newQueryParameters = + Map.from(result.queryParameters); + + newQueryParameters['amount'] = _getAmountByAsset(request, asset); + return Uri( + scheme: result.scheme, + path: result.path.split("@").first, + queryParameters: newQueryParameters); + } else { + throw OpenCryptoPayException( + 'Failed to create Open CryptoPay Request. Status: ${response.statusCode} ${response.body}'); + } + } + + String _getAmountByAsset(OpenCryptoPayRequest request, CryptoCurrency asset) { + final method = _getMethod(asset); + return request.methods[method]! + .firstWhere((e) => e.symbol.toUpperCase() == asset.title.toUpperCase()) + .amount; + } + + String _getMethod(CryptoCurrency asset) { + switch (asset.tag) { + case "ETH": + return "Ethereum"; + case "POL": + return "Polygon"; + default: + return asset.fullName!; + } + } +} + +class _OpenCryptoPayQuote { + final String callbackUrl; + final String? displayName; + final String id; + final DateTime expiration; + + _OpenCryptoPayQuote( + this.callbackUrl, this.displayName, this.id, this.expiration); + + _OpenCryptoPayQuote.fromJson( + this.callbackUrl, this.displayName, Map json) + : id = json['id'] as String, + expiration = DateTime.parse(json['expiration'] as String); +} diff --git a/lib/core/utilities.dart b/lib/core/utilities.dart new file mode 100644 index 000000000..1278dfc44 --- /dev/null +++ b/lib/core/utilities.dart @@ -0,0 +1,9 @@ +extension EnhancedList on Iterable { + T? firstWhereOrNull(bool Function(T element) test) { + try { + return firstWhere(test); + } catch (e) { + return null; + } + } +} \ No newline at end of file diff --git a/lib/di.dart b/lib/di.dart index 9a28f94a8..ee9cf90d6 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -15,7 +15,6 @@ import 'package:cake_wallet/core/backup_service_v3.dart'; import 'package:cake_wallet/core/new_wallet_arguments.dart'; import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart'; import 'package:cake_wallet/core/auth_service.dart'; -import 'package:cake_wallet/core/backup_service.dart'; import 'package:cake_wallet/core/key_service.dart'; import 'package:cake_wallet/core/new_wallet_type_arguments.dart'; import 'package:cake_wallet/core/secure_storage.dart'; @@ -35,6 +34,7 @@ import 'package:cake_wallet/entities/hardware_wallet/require_hardware_wallet_con import 'package:cake_wallet/entities/parse_address_from_domain.dart'; import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart'; import 'package:cake_wallet/src/screens/dev/monero_background_sync.dart'; +import 'package:cake_wallet/src/screens/dev/moneroc_call_profiler.dart'; import 'package:cake_wallet/src/screens/settings/background_sync_page.dart'; import 'package:cake_wallet/tari/tari.dart'; import 'package:cake_wallet/view_model/dev/monero_background_sync.dart'; @@ -349,7 +349,7 @@ Future setup({ getIt.registerSingleton( OrdersStore(ordersSource: _ordersSource, settingsStore: getIt.get())); getIt.registerSingleton(TradeFilterStore()); - getIt.registerSingleton(TransactionFilterStore()); + getIt.registerSingleton(TransactionFilterStore(getIt.get())); getIt.registerSingleton(FiatConversionStore()); getIt.registerSingleton(SendTemplateStore(templateSource: _templates)); getIt.registerSingleton( @@ -1205,8 +1205,7 @@ Future setup({ ); }); - getIt.registerFactoryParam( - (seedPhraseLength, _) => PreSeedPage(seedPhraseLength)); + getIt.registerFactory(() => PreSeedPage()); getIt.registerFactoryParam( (content, _) => TransactionSuccessPage(content: content)); @@ -1453,5 +1452,6 @@ Future setup({ getIt.registerFactory(() => SeedVerificationPage(getIt.get())); getIt.registerFactory(() => DevMoneroBackgroundSyncPage(getIt.get())); + getIt.registerFactory(() => DevMoneroCallProfilerPage()); _isSetupFinished = true; } diff --git a/lib/entities/fiat_currency.dart b/lib/entities/fiat_currency.dart index cf1112096..402c928b9 100644 --- a/lib/entities/fiat_currency.dart +++ b/lib/entities/fiat_currency.dart @@ -9,8 +9,50 @@ class FiatCurrency extends EnumerableItem with Serializable impl static List get all => _all.values.toList(); - static List get currenciesAvailableToBuyWith => - [amd, 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 List get currenciesAvailableToBuyWith => [ + amd, + 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, + kes, + ]; static const amd = FiatCurrency(symbol: 'AMD', countryCode: "arm", fullName: "Armenian Dram"); static const ars = FiatCurrency(symbol: 'ARS', countryCode: "arg", fullName: "Argentine Peso"); @@ -62,6 +104,7 @@ class FiatCurrency extends EnumerableItem with Serializable impl 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 const kes = FiatCurrency(symbol: 'KES', countryCode: "ken", fullName: "Kenyan Shillings"); static final _all = { FiatCurrency.amd.raw: FiatCurrency.amd, @@ -114,6 +157,7 @@ class FiatCurrency extends EnumerableItem with Serializable impl FiatCurrency.vnd.raw: FiatCurrency.vnd, FiatCurrency.zar.raw: FiatCurrency.zar, FiatCurrency.tur.raw: FiatCurrency.tur, + FiatCurrency.kes.raw: FiatCurrency.kes, }; static FiatCurrency deserialize({required String raw}) => _all[raw] ?? FiatCurrency.usd; @@ -123,13 +167,13 @@ class FiatCurrency extends EnumerableItem with Serializable impl @override int get hashCode => raw.hashCode ^ title.hashCode; - + @override String get name => raw; - + @override String? get tag => null; @override - String get iconPath => "assets/images/flags/$countryCode.png"; + String get iconPath => "assets/images/flags/$countryCode.png"; } diff --git a/lib/entities/provider_types.dart b/lib/entities/provider_types.dart index 63918aa8b..400b5b9d4 100644 --- a/lib/entities/provider_types.dart +++ b/lib/entities/provider_types.dart @@ -6,7 +6,6 @@ import 'package:cake_wallet/buy/moonpay/moonpay_provider.dart'; import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart'; import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart'; import 'package:cake_wallet/di.dart'; -import 'package:cw_core/wallet_type.dart'; enum ProviderType { robinhood, dfx, onramper, moonpay, meld, kriptonim } @@ -47,7 +46,7 @@ extension ProviderTypeName on ProviderType { } class ProvidersHelper { - static List getAvailableBuyProviderTypes(WalletType walletType) => [ + static List getAvailableBuyProviderTypes() => [ ProviderType.robinhood, ProviderType.dfx, ProviderType.onramper, @@ -55,7 +54,7 @@ class ProvidersHelper { ProviderType.kriptonim ]; - static List getAvailableSellProviderTypes(WalletType walletType) => [ + static List getAvailableSellProviderTypes() => [ ProviderType.robinhood, ProviderType.dfx, ProviderType.onramper, @@ -63,7 +62,7 @@ class ProvidersHelper { ProviderType.kriptonim ]; - static BuyProvider? getProviderByType(ProviderType type) { + static BuyProvider getProviderByType(ProviderType type) { switch (type) { case ProviderType.robinhood: return getIt.get(); @@ -77,8 +76,6 @@ class ProvidersHelper { return getIt.get(); case ProviderType.kriptonim: return getIt.get(); - default: - return null; - } + } } } diff --git a/lib/entities/seed_type.dart b/lib/entities/seed_type.dart index 20600e704..0548d9cee 100644 --- a/lib/entities/seed_type.dart +++ b/lib/entities/seed_type.dart @@ -1,17 +1,17 @@ -import 'package:cake_wallet/generated/i18n.dart'; import 'package:cw_core/enumerable_item.dart'; import 'package:cw_core/wallet_info.dart'; class MoneroSeedType extends EnumerableItem with Serializable { const MoneroSeedType({required String title, required int raw}) : super(title: title, raw: raw); - static const all = [MoneroSeedType.legacy, MoneroSeedType.polyseed]; + static const all = [legacy, polyseed, bip39]; static const defaultSeedType = polyseed; - static const legacy = MoneroSeedType(raw: 0, title: 'Legacy (25 words)'); - static const polyseed = MoneroSeedType(raw: 1, title: 'Polyseed (16 words)'); - static const wowneroSeed = MoneroSeedType(raw: 2, title: 'Wownero (14 words)'); + static const legacy = MoneroSeedType(raw: 0, title: 'Legacy'); + static const polyseed = MoneroSeedType(raw: 1, title: 'Polyseed'); + static const wowneroSeed = MoneroSeedType(raw: 2, title: 'Wownero'); + static const bip39 = MoneroSeedType(raw: 3, title: 'BIP39'); static MoneroSeedType deserialize({required int raw}) { switch (raw) { @@ -21,24 +21,15 @@ class MoneroSeedType extends EnumerableItem with Serializable { return polyseed; case 2: return wowneroSeed; + case 3: + return bip39; default: throw Exception('Unexpected token: $raw for SeedType deserialize'); } } @override - String toString() { - switch (this) { - case MoneroSeedType.legacy: - return S.current.seedtype_legacy; - case MoneroSeedType.polyseed: - return S.current.seedtype_polyseed; - case MoneroSeedType.wowneroSeed: - return S.current.seedtype_wownero; - default: - return ''; - } - } + String toString() => title; } class BitcoinSeedType extends EnumerableItem with Serializable { diff --git a/lib/entities/wallet_manager.dart b/lib/entities/wallet_manager.dart index 8bcabcaf2..09b764289 100644 --- a/lib/entities/wallet_manager.dart +++ b/lib/entities/wallet_manager.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:cake_wallet/entities/hash_wallet_identifier.dart'; import 'package:cake_wallet/entities/wallet_group.dart'; import 'package:cw_core/wallet_base.dart'; @@ -33,7 +35,11 @@ class WalletManager { } // Fallback to old logic - return walletInfo.parentAddress ?? walletInfo.address; + final address = walletInfo.parentAddress ?? walletInfo.address; + if (address.isEmpty) { + return Random().nextInt(100000).toString(); + } + return address; } WalletGroup _getOrCreateGroup(String groupKey) { diff --git a/lib/exchange/provider/simpleswap_exchange_provider.dart b/lib/exchange/provider/simpleswap_exchange_provider.dart index 2a72ac793..5391d5f89 100644 --- a/lib/exchange/provider/simpleswap_exchange_provider.dart +++ b/lib/exchange/provider/simpleswap_exchange_provider.dart @@ -34,10 +34,10 @@ class SimpleSwapExchangeProvider extends ExchangeProvider { String get title => 'SimpleSwap'; @override - bool get isAvailable => true; + bool get isAvailable => false; @override - bool get isEnabled => true; + bool get isEnabled => false; @override bool get supportsFixedRate => false; diff --git a/lib/exchange/provider/xoswap_exchange_provider.dart b/lib/exchange/provider/xoswap_exchange_provider.dart index 0b3826aa9..5611d6855 100644 --- a/lib/exchange/provider/xoswap_exchange_provider.dart +++ b/lib/exchange/provider/xoswap_exchange_provider.dart @@ -188,12 +188,21 @@ class XOSwapExchangeProvider extends ExchangeProvider { try { final uri = Uri.https(_apiAuthority, '$_apiPath$_orders'); + final curFrom = await _getAssets(request.fromCurrency); + final curTo = await _getAssets(request.toCurrency); + + if (curFrom == null || curTo == null) { + throw TradeNotCreatedException(description); + } + + final pairId = curFrom + '_' + curTo; + final payload = { 'fromAmount': request.fromAmount, 'fromAddress': request.refundAddress, 'toAmount': request.toAmount, 'toAddress': request.toAddress, - 'pairId': '${request.fromCurrency.title}_${request.toCurrency.title}', + 'pairId': pairId, }; final response = await http.post(uri, headers: _headers, body: json.encode(payload)); diff --git a/lib/exchange/trade_state.dart b/lib/exchange/trade_state.dart index f63b41e5d..e1c4470b4 100644 --- a/lib/exchange/trade_state.dart +++ b/lib/exchange/trade_state.dart @@ -7,6 +7,7 @@ class TradeState extends EnumerableItem with Serializable { bool operator ==(Object other) => other is TradeState && other.raw == raw; static const pending = TradeState(raw: 'pending', title: 'Pending'); + static const awaiting = TradeState(raw: 'awaiting', title: 'Awaiting'); static const confirming = TradeState(raw: 'confirming', title: 'Confirming'); static const trading = TradeState(raw: 'trading', title: 'Trading'); static const traded = TradeState(raw: 'traded', title: 'Traded'); @@ -134,6 +135,8 @@ class TradeState extends EnumerableItem with Serializable { return success; case 'expired': return expired; + case 'awaiting': + return awaiting; default: throw Exception('Unexpected token: $raw in TradeState deserialize'); } diff --git a/lib/monero/cw_monero.dart b/lib/monero/cw_monero.dart index a955e1d98..b6be123c2 100644 --- a/lib/monero/cw_monero.dart +++ b/lib/monero/cw_monero.dart @@ -252,11 +252,21 @@ class CWMonero extends Monero { WalletCredentials createMoneroNewWalletCredentials({ required String name, required String language, - required bool isPolyseed, + required int seedType, required String? passphrase, - String? password}) => + String? password, + String? mnemonic, + }) => MoneroNewWalletCredentials( - name: name, password: password, language: language, isPolyseed: isPolyseed, passphrase: passphrase); + name: name, + password: password, + language: language, + seedType: seedType == 1 + ? MoneroSeedType.polyseed + : (seedType == 3 ? MoneroSeedType.bip39 : MoneroSeedType.legacy), + passphrase: passphrase, + mnemonic: mnemonic, + ); @override Map getKeys(Object wallet) { @@ -424,4 +434,10 @@ class CWMonero extends Monero { bool isViewOnly() { return isViewOnlyBySpendKey(null); } + + @override + Map> debugCallLength() { + return monero_wallet_api.debugCallLength(); + } + } diff --git a/lib/reactions/bip39_wallet_utils.dart b/lib/reactions/bip39_wallet_utils.dart index 77e990012..22741e22a 100644 --- a/lib/reactions/bip39_wallet_utils.dart +++ b/lib/reactions/bip39_wallet_utils.dart @@ -11,8 +11,8 @@ bool isBIP39Wallet(WalletType walletType) { case WalletType.bitcoinCash: case WalletType.nano: case WalletType.banano: - return true; case WalletType.monero: + return true; case WalletType.wownero: case WalletType.haven: case WalletType.zano: diff --git a/lib/router.dart b/lib/router.dart index 35177c0eb..8511714fc 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -9,7 +9,6 @@ import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/entities/contact_record.dart'; import 'package:cake_wallet/entities/qr_view_data.dart'; import 'package:cake_wallet/entities/wallet_edit_page_arguments.dart'; -import 'package:cake_wallet/entities/wallet_nft_response.dart'; import 'package:cake_wallet/exchange/trade.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; @@ -37,6 +36,7 @@ import 'package:cake_wallet/src/screens/dashboard/pages/nft_details_page.dart'; import 'package:cake_wallet/src/screens/dashboard/pages/transactions_page.dart'; import 'package:cake_wallet/src/screens/dashboard/sign_page.dart'; import 'package:cake_wallet/src/screens/dev/monero_background_sync.dart'; +import 'package:cake_wallet/src/screens/dev/moneroc_call_profiler.dart'; import 'package:cake_wallet/src/screens/disclaimer/disclaimer_page.dart'; import 'package:cake_wallet/src/screens/exchange/exchange_page.dart'; import 'package:cake_wallet/src/screens/exchange/exchange_template_page.dart'; @@ -139,7 +139,6 @@ import 'package:cw_core/wallet_type.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:cake_wallet/src/screens/cake_pay/cake_pay.dart'; import 'src/screens/buy/buy_sell_page.dart'; import 'src/screens/dashboard/pages/nft_import_page.dart'; @@ -277,33 +276,40 @@ Route createRoute(RouteSettings settings) { case Routes.restoreWalletFromHardwareWallet: if (isSingleCoin) { return MaterialPageRoute( - builder: (_) => ConnectDevicePage( - ConnectDevicePageParams( - walletType: availableWalletTypes.first, - onConnectDevice: (BuildContext context, _) => Navigator.of(context).pushNamed( - Routes.chooseHardwareWalletAccount, - arguments: [availableWalletTypes.first]), - ), - getIt.get(), - )); - } - return CupertinoPageRoute( - builder: (_) => getIt.get( - param1: NewWalletTypeArguments( - onTypeSelected: (BuildContext context, WalletType type) { - final arguments = ConnectDevicePageParams( - walletType: type, - onConnectDevice: (BuildContext context, _) => Navigator.of(context) - .pushNamed(Routes.chooseHardwareWalletAccount, arguments: [type]), - ); - - Navigator.of(context).pushNamed(Routes.connectDevices, arguments: arguments); - }, - isCreate: false, - isHardwareWallet: true, + builder: (_) => ConnectDevicePage( + ConnectDevicePageParams( + walletType: availableWalletTypes.first, + onConnectDevice: (BuildContext context, _) => + Navigator.of(context).pushNamed( + Routes.chooseHardwareWalletAccount, + arguments: [availableWalletTypes.first]), + isReconnect: false, ), + getIt.get(), ), ); + } + return CupertinoPageRoute( + builder: (_) => getIt.get( + param1: NewWalletTypeArguments( + onTypeSelected: (BuildContext context, WalletType type) { + final arguments = ConnectDevicePageParams( + walletType: type, + onConnectDevice: (BuildContext context, _) => + Navigator.of(context).pushNamed( + Routes.chooseHardwareWalletAccount, + arguments: [type]), + isReconnect: false, + ); + + Navigator.of(context) + .pushNamed(Routes.connectDevices, arguments: arguments); + }, + isCreate: false, + isHardwareWallet: true, + ), + ), + ); case Routes.restoreWalletTypeFromQR: return CupertinoPageRoute( @@ -595,13 +601,10 @@ Route createRoute(RouteSettings settings) { return MaterialPageRoute(builder: (_) => getIt.get()); case Routes.preSeedPage: - return MaterialPageRoute( - builder: (_) => getIt.get(param1: settings.arguments as int)); + return MaterialPageRoute(builder: (_) => getIt.get()); case Routes.walletGroupExistingSeedDescriptionPage: - return MaterialPageRoute( - builder: (_) => WalletGroupExistingSeedDescriptionPage( - seedPhraseWordsLength: settings.arguments as int)); + return MaterialPageRoute(builder: (_) => WalletGroupExistingSeedDescriptionPage()); case Routes.transactionSuccessPage: return MaterialPageRoute( @@ -834,6 +837,11 @@ Route createRoute(RouteSettings settings) { builder: (_) => getIt.get(), ); + case Routes.devMoneroCallProfiler: + return MaterialPageRoute( + builder: (_) => getIt.get(), + ); + default: return MaterialPageRoute( builder: (_) => Scaffold( diff --git a/lib/routes.dart b/lib/routes.dart index be5e9f05d..f7f3e2f6f 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -112,7 +112,7 @@ class Routes { static const torPage = '/tor_page'; static const backgroundSync = '/background_sync'; static const devMoneroBackgroundSync = '/dev/monero_background_sync'; - + static const devMoneroCallProfiler = '/dev/monero_call_profiler'; static const signPage = '/sign_page'; static const connectDevices = '/device/connect'; static const urqrAnimatedPage = '/urqr/animated_page'; diff --git a/lib/solana/cw_solana.dart b/lib/solana/cw_solana.dart index 937b5b3bf..9036f5584 100644 --- a/lib/solana/cw_solana.dart +++ b/lib/solana/cw_solana.dart @@ -99,6 +99,7 @@ class CWSolana extends Solana { mint: token.name.toUpperCase(), enabled: token.enabled, iconPath: token.iconPath, + isPotentialScam: token.isPotentialScam, ); await (wallet as SolanaWallet).addSPLToken(splToken); diff --git a/lib/src/screens/backup/backup_page.dart b/lib/src/screens/backup/backup_page.dart index 8f520b15f..12c8594ae 100644 --- a/lib/src/screens/backup/backup_page.dart +++ b/lib/src/screens/backup/backup_page.dart @@ -3,10 +3,9 @@ import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; -import 'package:cake_wallet/src/widgets/trail_button.dart'; import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/palette.dart'; import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:cake_wallet/utils/clipboard_util.dart'; import 'package:cake_wallet/utils/exception_handler.dart'; import 'package:cake_wallet/utils/share_util.dart'; @@ -76,7 +75,9 @@ class BackupPage extends BasePage { Navigator.of(context).pushNamed(Routes.editBackupPassword), text: S.of(context).change_password, color: Theme.of(context).cardColor, - textColor: Colors.white, + textColor: Theme.of(context) + .extension()! + .buttonTextColor, ), SizedBox(height: 10), Observer( @@ -139,6 +140,7 @@ class BackupPage extends BasePage { await backupViewModelBase.saveToDownload( backup.name, backup.file); Navigator.of(dialogContext).pop(); + await showBar(context, S.of(context).file_saved); }, actionLeftButton: () async { Navigator.of(dialogContext).pop(); diff --git a/lib/src/screens/backup/edit_backup_password_page.dart b/lib/src/screens/backup/edit_backup_password_page.dart index 93207c191..1c80f1bb3 100644 --- a/lib/src/screens/backup/edit_backup_password_page.dart +++ b/lib/src/screens/backup/edit_backup_password_page.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; @@ -68,7 +67,9 @@ class EditBackupPasswordPage extends BasePage { actionRightButton: () async { await editBackupPasswordViewModel.save(); Navigator.of(dialogContext).pop(); - Navigator.of(context).pop(); + if (context.mounted) { + Navigator.of(context).pop(); + } }, actionLeftButton: () => Navigator.of(dialogContext).pop()); }); diff --git a/lib/src/screens/cake_pay/cards/cake_pay_confirm_purchase_card_page.dart b/lib/src/screens/cake_pay/cards/cake_pay_confirm_purchase_card_page.dart index 9ae2acf8e..51c17bc8b 100644 --- a/lib/src/screens/cake_pay/cards/cake_pay_confirm_purchase_card_page.dart +++ b/lib/src/screens/cake_pay/cards/cake_pay_confirm_purchase_card_page.dart @@ -381,6 +381,7 @@ class CakePayBuyCardDetailPage extends BasePage { return ConfirmSendingBottomSheet( key: ValueKey('send_page_confirm_sending_dialog_key'), currentTheme: currentTheme, + walletType: cakePayPurchaseViewModel.sendViewModel.walletType, paymentId: S.of(popupContext).payment_id, paymentIdValue: order?.orderId, expirationTime: cakePayPurchaseViewModel.formattedRemainingTime, diff --git a/lib/src/screens/connect_device/connect_device_page.dart b/lib/src/screens/connect_device/connect_device_page.dart index 5e52b887c..8452a59be 100644 --- a/lib/src/screens/connect_device/connect_device_page.dart +++ b/lib/src/screens/connect_device/connect_device_page.dart @@ -28,7 +28,7 @@ class ConnectDevicePageParams { required this.walletType, required this.onConnectDevice, this.allowChangeWallet = false, - this.isReconnect = false, + this.isReconnect = true, }); } diff --git a/lib/src/screens/contact/contact_list_page.dart b/lib/src/screens/contact/contact_list_page.dart index ee1dcd112..4dfb2a1eb 100644 --- a/lib/src/screens/contact/contact_list_page.dart +++ b/lib/src/screens/contact/contact_list_page.dart @@ -12,9 +12,11 @@ import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; import 'package:cake_wallet/src/widgets/standard_list.dart'; import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart'; +import 'package:cake_wallet/utils/address_formatter.dart'; import 'package:cake_wallet/utils/show_bar.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -235,7 +237,7 @@ class _ContactPageBodyState extends State with SingleTickerProv return; } - final isCopied = await showNameAndAddressDialog(context, contact.name, contact.address); + final isCopied = await DialogService.showNameAndAddressDialog(context, contact); if (isCopied) { await Clipboard.setData(ClipboardData(text: contact.address)); @@ -280,21 +282,6 @@ class _ContactPageBodyState extends State with SingleTickerProv ? Image.asset(image, height: 24, width: 24) : const SizedBox(height: 24, width: 24); } - - Future showNameAndAddressDialog(BuildContext context, String name, String address) async { - return await showPopUp( - context: context, - builder: (BuildContext context) { - return AlertWithTwoActions( - alertTitle: name, - alertContent: address, - rightButtonText: S.of(context).copy, - leftButtonText: S.of(context).cancel, - actionRightButton: () => Navigator.of(context).pop(true), - actionLeftButton: () => Navigator.of(context).pop(false)); - }) ?? - false; - } } class ContactListBody extends StatefulWidget { @@ -357,7 +344,7 @@ class _ContactListBodyState extends State { } final isCopied = - await showNameAndAddressDialog(context, contact.name, contact.address); + await DialogService.showNameAndAddressDialog(context, contact); if (isCopied) { await Clipboard.setData(ClipboardData(text: contact.address)); @@ -434,7 +421,7 @@ class _ContactListBodyState extends State { ), SlidableAction( onPressed: (_) async { - final isDelete = await showAlertDialog(context); + final isDelete = await DialogService.showAlertDialog(context); if (isDelete) { await widget.contactListViewModel.delete(contact); @@ -494,33 +481,49 @@ class _ContactListBodyState extends State { ); } - Future showAlertDialog(BuildContext context) async { +} + +class DialogService { + static Future showAlertDialog(BuildContext context) async { return await showPopUp( - context: context, - builder: (BuildContext context) { - return AlertWithTwoActions( - alertTitle: S.of(context).address_remove_contact, - alertContent: S.of(context).address_remove_content, - rightButtonText: S.of(context).remove, - leftButtonText: S.of(context).cancel, - actionRightButton: () => Navigator.of(context).pop(true), - actionLeftButton: () => Navigator.of(context).pop(false)); - }) ?? + context: context, + builder: (BuildContext context) { + return AlertWithTwoActions( + alertTitle: S.of(context).address_remove_contact, + alertContent: S.of(context).address_remove_content, + rightButtonText: S.of(context).remove, + leftButtonText: S.of(context).cancel, + actionRightButton: () => Navigator.of(context).pop(true), + actionLeftButton: () => Navigator.of(context).pop(false)); + }) ?? false; } - Future showNameAndAddressDialog(BuildContext context, String name, String address) async { + static Future showNameAndAddressDialog(BuildContext context,ContactBase contact) async { return await showPopUp( - context: context, - builder: (BuildContext context) { - return AlertWithTwoActions( - alertTitle: name, - alertContent: address, - rightButtonText: S.of(context).copy, - leftButtonText: S.of(context).cancel, - actionRightButton: () => Navigator.of(context).pop(true), - actionLeftButton: () => Navigator.of(context).pop(false)); - }) ?? + context: context, + builder: (BuildContext context) { + return AlertWithTwoActions( + alertTitle: contact.name, + alertContent: contact.address, + alertContentTextWidget: AddressFormatter.buildSegmentedAddress( + address: contact.address, + textAlign: TextAlign.center, + walletType: cryptoCurrencyToWalletType(contact.type), + evenTextStyle: TextStyle( + fontSize: 16, + fontWeight: FontWeight.normal, + fontFamily: 'Lato', + color: Theme.of(context).extension()!.titleColor, + decoration: TextDecoration.none, + ), + ), + rightButtonText: S.of(context).copy, + leftButtonText: S.of(context).cancel, + actionRightButton: () => Navigator.of(context).pop(true), + actionLeftButton: () => Navigator.of(context).pop(false)); + }) ?? false; } } + diff --git a/lib/src/screens/dashboard/edit_token_page.dart b/lib/src/screens/dashboard/edit_token_page.dart index 11a584b39..8cf4ff8e2 100644 --- a/lib/src/screens/dashboard/edit_token_page.dart +++ b/lib/src/screens/dashboard/edit_token_page.dart @@ -207,22 +207,23 @@ class _EditTokenPageBodyState extends State { onPressed: () async { if (_formKey.currentState!.validate() && (!_showDisclaimer || _disclaimerChecked)) { - final hasPotentialError = await widget.homeSettingsViewModel + final isWhitelisted = await widget.homeSettingsViewModel + .checkIfTokenIsWhitelisted(_contractAddressController.text); + + final hasPotentialError = !isWhitelisted && await widget.homeSettingsViewModel .checkIfERC20TokenContractAddressIsAPotentialScamAddress( _contractAddressController.text, ); - final isWhitelisted = await widget.homeSettingsViewModel - .checkIfTokenIsWhitelisted(_contractAddressController.text); - - bool isPotentialScam = hasPotentialError; + bool isPotentialScam = hasPotentialError && !isWhitelisted; final tokenSymbol = _tokenSymbolController.text.toUpperCase(); - + // check if the token symbol is the same as any of the base currencies symbols (ETH, SOL, POL, TRX, etc): // if it is, then it's probably a scam unless it's in the whitelist final baseCurrencySymbols = CryptoCurrency.all.map((e) => e.title.toUpperCase()).toList(); - if (baseCurrencySymbols.contains(tokenSymbol) && !isWhitelisted) { + if (baseCurrencySymbols.contains(tokenSymbol.trim().toUpperCase()) && + !isWhitelisted) { isPotentialScam = true; } @@ -257,7 +258,7 @@ class _EditTokenPageBodyState extends State { } }; - if (hasPotentialError) { + if (hasPotentialError && !isWhitelisted) { showPopUp( context: context, builder: (dialogContext) { diff --git a/lib/src/screens/dashboard/pages/address_page.dart b/lib/src/screens/dashboard/pages/address_page.dart index 10f9aef43..a9c265e58 100644 --- a/lib/src/screens/dashboard/pages/address_page.dart +++ b/lib/src/screens/dashboard/pages/address_page.dart @@ -185,7 +185,7 @@ class AddressPage extends BasePage { } reaction((_) => receiveOptionViewModel.selectedReceiveOption, (ReceivePageOption option) { - if (bitcoin!.isBitcoinReceivePageOption(option)) { + if (dashboardViewModel.type == WalletType.bitcoin && bitcoin!.isBitcoinReceivePageOption(option)) { addressListViewModel.setAddressType(bitcoin!.getOptionToType(option)); return; } diff --git a/lib/src/screens/dashboard/pages/balance/balance_row_widget.dart b/lib/src/screens/dashboard/pages/balance/balance_row_widget.dart index 875618784..47d522173 100644 --- a/lib/src/screens/dashboard/pages/balance/balance_row_widget.dart +++ b/lib/src/screens/dashboard/pages/balance/balance_row_widget.dart @@ -16,7 +16,6 @@ import 'package:cw_core/unspent_coin_type.dart'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:url_launcher/url_launcher.dart'; -import 'package:cake_wallet/themes/theme_base.dart'; class BalanceRowWidget extends StatelessWidget { BalanceRowWidget({ @@ -66,8 +65,6 @@ class BalanceRowWidget extends StatelessWidget { @override Widget build(BuildContext context) { - bool brightThemeType = false; - if (dashboardViewModel.settingsStore.currentTheme.type == ThemeType.bright) brightThemeType = true; return Column( children: [ Container( @@ -221,7 +218,33 @@ class BalanceRowWidget extends StatelessWidget { ), ], ), - //), + if (currency.isPotentialScam) + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + margin: EdgeInsets.only(top: 4), + decoration: BoxDecoration( + color: Colors.red[800], + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.warning_amber_outlined, + size: 16, + color: Colors.white, + ), + const SizedBox(width: 4), + Text( + S.of(context).potential_scam, + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.w700, + ), + ), + ], + ), + ), if (frozenBalance.isNotEmpty) Column( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/src/screens/dashboard/pages/navigation_dock.dart b/lib/src/screens/dashboard/pages/navigation_dock.dart index 4eda169d3..e2a2362ee 100644 --- a/lib/src/screens/dashboard/pages/navigation_dock.dart +++ b/lib/src/screens/dashboard/pages/navigation_dock.dart @@ -32,7 +32,7 @@ class NavigationDock extends StatelessWidget { ), ), child: Container( - margin: const EdgeInsets.only(left: 16, right: 16, bottom: 16), + margin: const EdgeInsets.only(left: 8, right: 8, bottom: 16), child: ClipRRect( borderRadius: BorderRadius.circular(50), child: BackdropFilter( @@ -48,7 +48,7 @@ class NavigationDock extends StatelessWidget { Theme.of(context).extension()!.syncedBackgroundColor, ), child: Container( - padding: EdgeInsets.symmetric(horizontal: 10), + padding: EdgeInsets.symmetric(horizontal: 8), child: IntrinsicHeight( child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, diff --git a/lib/src/screens/dashboard/pages/transactions_page.dart b/lib/src/screens/dashboard/pages/transactions_page.dart index 654663f92..be353677b 100644 --- a/lib/src/screens/dashboard/pages/transactions_page.dart +++ b/lib/src/screens/dashboard/pages/transactions_page.dart @@ -45,20 +45,17 @@ class TransactionsPage extends StatelessWidget { Observer(builder: (_) { final status = dashboardViewModel.status; if (status is SyncingSyncStatus) { - return Padding( - padding: const EdgeInsets.fromLTRB(24, 0, 24, 8), - child: DashBoardRoundedCardWidget( - key: ValueKey('transactions_page_syncing_alert_card_key'), - onTap: () { - try { - final uri = Uri.parse( - "https://docs.cakewallet.com/faq/funds-not-appearing"); - launchUrl(uri, mode: LaunchMode.externalApplication); - } catch (_) {} - }, - title: S.of(context).syncing_wallet_alert_title, - subTitle: S.of(context).syncing_wallet_alert_content, - ), + return DashBoardRoundedCardWidget( + key: ValueKey('transactions_page_syncing_alert_card_key'), + onTap: () { + try { + final uri = Uri.parse( + "https://docs.cakewallet.com/faq/funds-not-appearing"); + launchUrl(uri, mode: LaunchMode.externalApplication); + } catch (_) {} + }, + title: S.of(context).syncing_wallet_alert_title, + subTitle: S.of(context).syncing_wallet_alert_content, ); } else { return Container(); @@ -72,7 +69,6 @@ class TransactionsPage extends StatelessWidget { child: Observer( builder: (_) { final items = dashboardViewModel.items; - final amount = items.length + 1; return items.isNotEmpty ? ListView.builder( key: ValueKey('transactions_page_list_view_builder_key'), diff --git a/lib/src/screens/dashboard/widgets/action_button.dart b/lib/src/screens/dashboard/widgets/action_button.dart index 786c56658..b26dadb67 100644 --- a/lib/src/screens/dashboard/widgets/action_button.dart +++ b/lib/src/screens/dashboard/widgets/action_button.dart @@ -47,6 +47,8 @@ class ActionButton extends StatelessWidget { SizedBox(height: 4), Text( title, + maxLines: 1, + overflow: TextOverflow.visible, style: TextStyle( fontSize: 9, color: textColor ?? diff --git a/lib/src/screens/dev/moneroc_call_profiler.dart b/lib/src/screens/dev/moneroc_call_profiler.dart new file mode 100644 index 000000000..e7043fea9 --- /dev/null +++ b/lib/src/screens/dev/moneroc_call_profiler.dart @@ -0,0 +1,253 @@ +// code shamelessly stolen from xmruw +// https://raw.githubusercontent.com/MrCyjaneK/unnamed_monero_wallet/refs/heads/master-rewrite/lib/pages/debug/performance.dart +import 'dart:math'; + +import 'package:cake_wallet/di.dart'; +import 'package:cake_wallet/monero/monero.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; +import 'package:cake_wallet/wownero/wownero.dart'; +import 'package:cake_wallet/zano/zano.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:flutter/material.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; + +class DevMoneroCallProfilerPage extends BasePage { + DevMoneroCallProfilerPage(); + + @override + String? get title => "[dev] xmr call profiler"; + + @override + Widget body(BuildContext context) { + return PerformanceDebug(); + } +} + + + +class PerformanceDebug extends StatefulWidget { + const PerformanceDebug({super.key}); + + @override + State createState() => _PerformanceDebugState(); +} + +enum ProfilableWallet { + monero, + wownero, + zano, +} + +class _PerformanceDebugState extends State { + List widgets = []; + + final dashboardViewModel = getIt.get(); + + late ProfilableWallet wallet = switch (dashboardViewModel.wallet.type) { + WalletType.monero => ProfilableWallet.monero, + WalletType.wownero => ProfilableWallet.wownero, + WalletType.zano => ProfilableWallet.zano, + _ => throw Exception("Unknown wallet type"), + }; + final precalc = 1700298; + + late Map> debugCallLength = switch (wallet) { + ProfilableWallet.monero => monero!.debugCallLength(), + ProfilableWallet.wownero => wownero!.debugCallLength(), + ProfilableWallet.zano => zano!.debugCallLength(), + }; + + int getOpenWalletTime() { + if (debugCallLength["MONERO_Wallet_init"] == null) { + return precalc; + } + if (debugCallLength["MONERO_Wallet_init"]!.isEmpty) { + return precalc; + } + return debugCallLength["MONERO_Wallet_init"]!.last; + } + +late final String perfInfo = """ +---- Performance tuning +This page lists all calls that take place during the app runtime.- +As per Flutter docs we can read: +> Flutter aims to provide 60 frames per second (fps) performance, or 120 fps- +performance on devices capable of 120Hz updates. + +With that in mind we will aim to render frames every 8.3ms (~8333 µs). It is- +however acceptable to reach 16.6 ms (~16666 µs) but we should also keep in mind- +that there are also UI costs that aren't part of this benchmark. + +For some calls it is also acceptable to exceed this amount of time, for example- +MONERO_Wallet_init takes ~${getOpenWalletTime()}µs- +(${(getOpenWalletTime() / frameTime).toStringAsFixed(2)} frames). That time would- +be unnaceptable in most situations but since we call this function only when- +opening the wallet it is completely fine to freeze the UI for the time being -- +as the user won't even notice that something happened. + +---- Details +count: how many times did we call this function [total time (% of frame)] +average: average execution time (% of frame) +min: fastest execution (% of frame) +max: slowest execution (% of frame) +95th: 95% of the time, the function is faster than this amount of time (% of frame) +""" + .split("-\n") + .join(" "); + + late final frameTime = 8333; + late final frameGreenTier = frameTime ~/ 100; + late final frameBlueTier = frameTime ~/ 10; + late final frameBlueGreyTier = frameTime ~/ 2; + late final frameYellowTier = frameTime; + late final frameOrangeTier = frameTime * 2; + + Color? perfc(num frame) { + if (frame < frameGreenTier) return Colors.green; + if (frame < frameBlueTier) return Colors.blue; + if (frame < frameBlueGreyTier) return Colors.blueGrey; + if (frame < frameGreenTier) return Colors.green; + if (frame < frameYellowTier) return Colors.yellow; + if (frame < frameOrangeTier) return Colors.orange; + return Colors.red; + } + + + @override + void initState() { + _buildWidgets(); + super.initState(); + } + + SelectableText cw(String text, Color? color) { + return SelectableText( + text, + style: TextStyle(color: color), + ); + } + + void _buildWidgets() { + List ws = []; + ws.add(Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SelectableText(perfInfo), + cw("< 1% of a frame (max: $frameGreenTierµs)", Colors.green), + cw("< 10% of a frame (max: $frameBlueTierµs)", Colors.blue), + cw("< 50% of a frame (max: $frameBlueGreyTierµs)", Colors.blueGrey), + cw("< 100% of a frame (max: $frameYellowTierµs)", Colors.yellow), + cw("< 200% of a frame (max: $frameOrangeTierµs)", Colors.orange), + cw("> 200% of a frame (UI junk visible)", Colors.red), + ], + )); + final keys = debugCallLength.keys.toList(); + keys.sort((s1, s2) => + _n95th(debugCallLength[s2]!) - + _n95th(debugCallLength[s1]!)); + for (var key in keys) { + final value = debugCallLength[key]; + if (value == null) continue; + final avg = _avg(value); + final min = _min(value); + final max = _max(value); + final np = _n95th(value); + final total = _total(value); + ws.add( + Card( + child: ListTile( + title: Text( + key, + style: TextStyle(color: perfc(np)), + ), + subtitle: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row(children: [ + cw("count: ${value.length}", null), + const Spacer(), + cw("${_str(total / 1000)}ms", perfc(total)), + ]), + cw("average: ${_str(avg)}µs (~${_str(avg / (frameTime))}f)", + perfc(avg)), + cw("min: $minµs (~${_str(min / (frameTime) * 100)})", + perfc(min)), + cw("max: $maxµs (~${_str(max / (frameTime) * 100)}%)", + perfc(max)), + cw("95th: $npµs (~${_str(np / (frameTime) * 100)}%)", + perfc(np)), + ], + ), + ), + ), + ); + } + if (debugCallLength.isNotEmpty) { + ws.add( + PrimaryButton( + text: "Purge statistics", + onPressed: _purgeStats, + color: Colors.red, + textColor: Colors.white, + ), + ); + } + setState(() { + widgets = ws; + }); + } + + void _purgeStats() { + debugCallLength.clear(); + _buildWidgets(); + } + + int _min(List l) { + return l.reduce(min); + } + + int _max(List l) { + return l.reduce(max); + } + + int _n95th(List l) { + final l0 = l.toList(); + l0.sort(); + int i = (0.95 * l.length).ceil() - 1; + return l0[i]; + } + + double _avg(List l) { + int c = 0; + for (var i = 0; i < l.length; i++) { + c += l[i]; + } + return c / l.length; + } + + int _total(List l) { + int c = 0; + for (var i = 0; i < l.length; i++) { + c += l[i]; + } + return c; + } + + String _str(num d) => d.toStringAsFixed(2); + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(8), + child: Column( + children: widgets, + ), + ), + ); + } +} diff --git a/lib/src/screens/exchange_trade/exchange_confirm_page.dart b/lib/src/screens/exchange_trade/exchange_confirm_page.dart index 60b534c6e..4b7e27294 100644 --- a/lib/src/screens/exchange_trade/exchange_confirm_page.dart +++ b/lib/src/screens/exchange_trade/exchange_confirm_page.dart @@ -118,7 +118,7 @@ class ExchangeConfirmPage extends BasePage { mainAxisAlignment: MainAxisAlignment.center, children: [ (trade.provider.image?.isNotEmpty ?? false) - ? Image.asset(trade.provider.image, height: 50) + ? ImageUtil.getImageFromPath(imagePath: trade.provider.image, height: 50, width: 50) : const SizedBox(), if (!trade.provider.horizontalLogo) Padding( diff --git a/lib/src/screens/exchange_trade/exchange_trade_external_send_page.dart b/lib/src/screens/exchange_trade/exchange_trade_external_send_page.dart index 5ed82e71c..036b5bc6d 100644 --- a/lib/src/screens/exchange_trade/exchange_trade_external_send_page.dart +++ b/lib/src/screens/exchange_trade/exchange_trade_external_send_page.dart @@ -19,9 +19,6 @@ class ExchangeTradeExternalSendPage extends BasePage { @override String get title => S.current.swap; - @override - bool get gradientBackground => true; - @override bool get gradientAll => true; @@ -46,7 +43,7 @@ class ExchangeTradeExternalSendPage extends BasePage { ); return Container( child: ScrollableWithBottomSection( - contentPadding: EdgeInsets.only(top: 36, bottom: 24), + contentPadding: EdgeInsets.only(bottom: 24), content: Observer( builder: (_) { return Column( diff --git a/lib/src/screens/exchange_trade/exchange_trade_page.dart b/lib/src/screens/exchange_trade/exchange_trade_page.dart index 97d9a34bb..f4c8d590f 100644 --- a/lib/src/screens/exchange_trade/exchange_trade_page.dart +++ b/lib/src/screens/exchange_trade/exchange_trade_page.dart @@ -26,23 +26,20 @@ import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart'; -void showInformation( - ExchangeTradeViewModel exchangeTradeViewModel, BuildContext context) { +void showInformation(ExchangeTradeViewModel exchangeTradeViewModel, BuildContext context) { final trade = exchangeTradeViewModel.trade; final walletName = exchangeTradeViewModel.wallet.name; final information = exchangeTradeViewModel.isSendable ? S.current.exchange_trade_result_confirm(trade.amount, trade.from.toString(), walletName) + - exchangeTradeViewModel.extraInfo - : S.current.exchange_result_description( - trade.amount, trade.from.toString()) + - exchangeTradeViewModel.extraInfo; + exchangeTradeViewModel.extraInfo + : S.current.exchange_result_description(trade.amount, trade.from.toString()) + + exchangeTradeViewModel.extraInfo; showPopUp( context: context, - builder: (_) => InformationPage( - key: ValueKey('information_page_dialog_key'), - information: information)); + builder: (_) => + InformationPage(key: ValueKey('information_page_dialog_key'), information: information)); } class ExchangeTradePage extends BasePage { @@ -53,9 +50,6 @@ class ExchangeTradePage extends BasePage { @override String get title => S.current.swap; - @override - bool get gradientBackground => true; - @override bool get gradientAll => true; @@ -141,7 +135,7 @@ class ExchangeTradeState extends State { return Container( child: ScrollableWithBottomSection( - contentPadding: EdgeInsets.only(top: 10, bottom: 16), + contentPadding: EdgeInsets.only(bottom: 16), content: Observer(builder: (_) { final trade = widget.exchangeTradeViewModel.trade; @@ -198,7 +192,7 @@ class ExchangeTradeState extends State { isDisabled: trade.inputAddress == null || trade.inputAddress!.isEmpty, isLoading: sendingState is IsExecutingState, onPressed: () => widget.exchangeTradeViewModel.confirmSending(), - text:S.current.send_from_cake_wallet, + text: S.current.send_from_cake_wallet, color: Theme.of(context).primaryColor, textColor: Colors.white, ) @@ -211,6 +205,7 @@ class ExchangeTradeState extends State { ); } + BuildContext? dialogContext; BuildContext? loadingBottomSheetContext; void _setEffects() { @@ -219,14 +214,18 @@ class ExchangeTradeState extends State { } _exchangeStateReaction = reaction((_) => this.widget.exchangeTradeViewModel.sendViewModel.state, - (ExecutionState state) { + (ExecutionState state) async { - if (state is! IsExecutingState && - loadingBottomSheetContext != null && - loadingBottomSheetContext!.mounted) { - Navigator.of(loadingBottomSheetContext!).pop(); + if (dialogContext != null && dialogContext?.mounted == true) { + Navigator.of(dialogContext!).pop(); } + if (state is! IsExecutingState && + loadingBottomSheetContext != null && + loadingBottomSheetContext!.mounted) { + Navigator.of(loadingBottomSheetContext!).pop(); + } + if (state is FailureState) { WidgetsBinding.instance.addPostFrameCallback((_) { showPopUp( @@ -244,6 +243,13 @@ class ExchangeTradeState extends State { } if (state is IsExecutingState) { + // wait a bit to avoid showing the loading dialog if transaction is failed + await Future.delayed(const Duration(milliseconds: 300)); + final currentState = widget.exchangeTradeViewModel.sendViewModel.state; + if (currentState is ExecutedSuccessfullyState || currentState is FailureState) { + return; + } + WidgetsBinding.instance.addPostFrameCallback((_) { if (context.mounted) { showModalBottomSheet( @@ -271,17 +277,23 @@ class ExchangeTradeState extends State { return ConfirmSendingBottomSheet( key: ValueKey('exchange_trade_page_confirm_sending_bottom_sheet_key'), currentTheme: widget.currentTheme, + walletType: widget.exchangeTradeViewModel.sendViewModel.walletType, titleText: S.of(bottomSheetContext).confirm_transaction, - titleIconPath: widget.exchangeTradeViewModel.sendViewModel.selectedCryptoCurrency.iconPath, + titleIconPath: + widget.exchangeTradeViewModel.sendViewModel.selectedCryptoCurrency.iconPath, currency: widget.exchangeTradeViewModel.sendViewModel.selectedCryptoCurrency, amount: S.of(bottomSheetContext).send_amount, - amountValue: widget.exchangeTradeViewModel.sendViewModel.pendingTransaction!.amountFormatted, - fiatAmountValue: widget.exchangeTradeViewModel.sendViewModel.pendingTransactionFiatAmountFormatted, + amountValue: widget + .exchangeTradeViewModel.sendViewModel.pendingTransaction!.amountFormatted, + fiatAmountValue: widget + .exchangeTradeViewModel.sendViewModel.pendingTransactionFiatAmountFormatted, fee: isEVMCompatibleChain(widget.exchangeTradeViewModel.sendViewModel.walletType) ? S.of(bottomSheetContext).send_estimated_fee : S.of(bottomSheetContext).send_fee, - feeValue: widget.exchangeTradeViewModel.sendViewModel.pendingTransaction!.feeFormatted, - feeFiatAmount: widget.exchangeTradeViewModel.sendViewModel.pendingTransactionFeeFiatAmountFormatted, + feeValue: + widget.exchangeTradeViewModel.sendViewModel.pendingTransaction!.feeFormatted, + feeFiatAmount: widget.exchangeTradeViewModel.sendViewModel + .pendingTransactionFeeFiatAmountFormatted, outputs: widget.exchangeTradeViewModel.sendViewModel.outputs, onSlideComplete: () async { Navigator.of(bottomSheetContext).pop(); @@ -300,7 +312,6 @@ class ExchangeTradeState extends State { return; } - await showModalBottomSheet( context: context, isScrollControlled: true, @@ -321,10 +332,8 @@ class ExchangeTradeState extends State { }); }, ); - }); } - }); _effectsInstalled = true; diff --git a/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart b/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart index f8901918f..d03f334a1 100644 --- a/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart +++ b/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart @@ -144,7 +144,9 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo ), ); }), - if (widget.privacySettingsViewModel.isMoneroSeedTypeOptionsEnabled) + if (widget + .privacySettingsViewModel.isMoneroSeedTypeOptionsEnabled && + !widget.isChildWallet) Observer(builder: (_) { return SettingsChoicesCell( ChoicesListItem( diff --git a/lib/src/screens/new_wallet/new_wallet_page.dart b/lib/src/screens/new_wallet/new_wallet_page.dart index 368b3440d..1304b4e1d 100644 --- a/lib/src/screens/new_wallet/new_wallet_page.dart +++ b/lib/src/screens/new_wallet/new_wallet_page.dart @@ -21,6 +21,7 @@ import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/seed_settings_view_model.dart'; import 'package:cake_wallet/view_model/wallet_new_vm.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:mobx/mobx.dart'; @@ -102,12 +103,10 @@ class _WalletNameFormState extends State { _stateReaction ??= reaction((_) => _walletNewVM.state, (ExecutionState state) async { if (state is ExecutedSuccessfullyState) { if (widget.isChildWallet) { - Navigator.of(navigatorKey.currentContext ?? context) - .pushNamed(Routes.walletGroupExistingSeedDescriptionPage, - arguments: _walletNewVM.seedPhraseWordsLength); + Navigator.of(navigatorKey.currentContext ?? context).pushNamed( + Routes.walletGroupExistingSeedDescriptionPage); } else { - Navigator.of(navigatorKey.currentContext ?? context) - .pushNamed(Routes.preSeedPage, arguments: _walletNewVM.seedPhraseWordsLength); + Navigator.of(navigatorKey.currentContext ?? context).pushNamed(Routes.preSeedPage); } } @@ -305,7 +304,7 @@ class _WalletNameFormState extends State { ), ), ), - if (_walletNewVM.hasLanguageSelector) ...[ + if (_walletNewVM.showLanguageSelector) ...[ if (_walletNewVM.hasSeedType) ...[ Observer( builder: (BuildContext build) => Padding( @@ -317,7 +316,11 @@ class _WalletNameFormState extends State { await showPopUp( context: context, builder: (_) => Picker( - items: MoneroSeedType.all, + items: MoneroSeedType.all + .where((e) => // exclude bip39 in case of Wownero + widget._walletNewVM.type != WalletType.wownero || + e.raw != MoneroSeedType.bip39.raw) + .toList(), selectedAtIndex: isPolyseed ? 1 : 0, onItemSelected: _setSeedType, isSeparated: false, @@ -401,7 +404,10 @@ class _WalletNameFormState extends State { } else { await _walletNewVM.create( options: _walletNewVM.hasLanguageSelector - ? [_languageSelectorKey.currentState!.selected, isPolyseed] + ? [ + _languageSelectorKey.currentState?.selected ?? defaultSeedLanguage, + widget._seedSettingsViewModel.moneroSeedType + ] : null); } } catch (e) { diff --git a/lib/src/screens/new_wallet/wallet_group_existing_seed_description_page.dart b/lib/src/screens/new_wallet/wallet_group_existing_seed_description_page.dart index 34e07bbaf..a1bd4ebff 100644 --- a/lib/src/screens/new_wallet/wallet_group_existing_seed_description_page.dart +++ b/lib/src/screens/new_wallet/wallet_group_existing_seed_description_page.dart @@ -9,9 +9,7 @@ import 'package:cake_wallet/themes/theme_base.dart'; import 'package:flutter/material.dart'; class WalletGroupExistingSeedDescriptionPage extends BasePage { - WalletGroupExistingSeedDescriptionPage({required this.seedPhraseWordsLength}); - - final int seedPhraseWordsLength; + WalletGroupExistingSeedDescriptionPage(); @override String get title => S.current.wallet_group; @@ -69,8 +67,7 @@ class WalletGroupExistingSeedDescriptionPage extends BasePage { child: PrimaryButton( key: ValueKey( 'wallet_group_existing_seed_description_page_verify_seed_button_key'), - onPressed: () => Navigator.pushNamed(context, Routes.preSeedPage, - arguments: seedPhraseWordsLength), + onPressed: () => Navigator.pushNamed(context, Routes.preSeedPage), text: S.current.verify_seed, color: Theme.of(context).cardColor, textColor: currentTheme.type == ThemeType.dark diff --git a/lib/src/screens/nodes/node_create_or_edit_page.dart b/lib/src/screens/nodes/node_create_or_edit_page.dart index fc6ac07e0..e62b6cb01 100644 --- a/lib/src/screens/nodes/node_create_or_edit_page.dart +++ b/lib/src/screens/nodes/node_create_or_edit_page.dart @@ -182,7 +182,9 @@ class NodeCreateOrEditPage extends BasePage { await nodeCreateOrEditViewModel.save( editingNode: editingNode, saveAsCurrent: isSelected ?? false); - Navigator.of(context).pop(); + if (context.mounted) { + Navigator.of(context).pop(); + } }, text: S.of(context).save, color: Theme.of(context).primaryColor, diff --git a/lib/src/screens/receive/address_list_page.dart b/lib/src/screens/receive/address_list_page.dart index 5f6794715..b2af4389c 100644 --- a/lib/src/screens/receive/address_list_page.dart +++ b/lib/src/screens/receive/address_list_page.dart @@ -20,6 +20,7 @@ class AddressListPage extends BasePage { children: [ AddressList( addressListViewModel: addressListViewModel, + currentTheme: currentTheme, onSelect: (String address) async { Navigator.of(context).pop(address); }, diff --git a/lib/src/screens/receive/receive_page.dart b/lib/src/screens/receive/receive_page.dart index 7e3c2b555..2a18f4d08 100644 --- a/lib/src/screens/receive/receive_page.dart +++ b/lib/src/screens/receive/receive_page.dart @@ -124,7 +124,7 @@ class ReceivePage extends BasePage { isLight: currentTheme.type == ThemeType.light, ), ), - AddressList(addressListViewModel: addressListViewModel), + AddressList(addressListViewModel: addressListViewModel, currentTheme: currentTheme), Padding( padding: EdgeInsets.fromLTRB(24, 24, 24, 32), child: Text( diff --git a/lib/src/screens/receive/widgets/address_cell.dart b/lib/src/screens/receive/widgets/address_cell.dart index beef7c762..354a4fcb5 100644 --- a/lib/src/screens/receive/widgets/address_cell.dart +++ b/lib/src/screens/receive/widgets/address_cell.dart @@ -1,7 +1,11 @@ -import 'package:auto_size_text/auto_size_text.dart'; import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/themes/extensions/qr_code_theme.dart'; +import 'package:cake_wallet/themes/extensions/receive_page_theme.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:cake_wallet/utils/address_formatter.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; @@ -14,6 +18,8 @@ class AddressCell extends StatelessWidget { required this.isPrimary, required this.backgroundColor, required this.textColor, + required this.walletType, + required this.currentTheme, this.onTap, this.onEdit, this.onHide, @@ -30,6 +36,8 @@ class AddressCell extends StatelessWidget { required bool isCurrent, required Color backgroundColor, required Color textColor, + required WalletType walletType, + required ThemeBase currentTheme, Function(String)? onTap, bool hasBalance = false, bool hasReceived = false, @@ -45,6 +53,8 @@ class AddressCell extends StatelessWidget { isPrimary: item.isPrimary, backgroundColor: backgroundColor, textColor: textColor, + walletType: walletType, + currentTheme: currentTheme, onTap: onTap, onEdit: onEdit, onHide: onHide, @@ -62,6 +72,8 @@ class AddressCell extends StatelessWidget { final bool isPrimary; final Color backgroundColor; final Color textColor; + final WalletType walletType; + final ThemeBase currentTheme; final Function(String)? onTap; final Function()? onEdit; final Function()? onHide; @@ -73,21 +85,6 @@ class AddressCell extends StatelessWidget { final bool hasBalance; final bool hasReceived; - static const int addressPreviewLength = 8; - - String get formattedAddress { - final formatIfCashAddr = address.replaceAll('bitcoincash:', ''); - - if (formatIfCashAddr.length <= (name.isNotEmpty ? 16 : 43)) { - return formatIfCashAddr; - } else { - return formatIfCashAddr.substring(0, addressPreviewLength) + - '...' + - formatIfCashAddr.substring( - formatIfCashAddr.length - addressPreviewLength, formatIfCashAddr.length); - } - } - @override Widget build(BuildContext context) { final Widget cell = InkWell( @@ -139,16 +136,14 @@ class AddressCell extends StatelessWidget { ], ), Flexible( - child: AutoSizeText( - responsiveLayoutUtil.shouldRenderTabletUI ? address : formattedAddress, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: isChange ? 10 : 14, - color: textColor, - ), - ), - ), + child: AddressFormatter.buildSegmentedAddress( + address: address, + walletType: walletType, + shouldTruncate: name.isNotEmpty || address.length > 43 , + evenTextStyle: TextStyle( + fontSize: isChange ? 10 : 14, + color: textColor + ))), ], ), if (hasBalance || hasReceived) diff --git a/lib/src/screens/receive/widgets/address_list.dart b/lib/src/screens/receive/widgets/address_list.dart index 8d22d81ef..c5a1bc871 100644 --- a/lib/src/screens/receive/widgets/address_list.dart +++ b/lib/src/screens/receive/widgets/address_list.dart @@ -1,5 +1,3 @@ -import 'dart:math'; - import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; @@ -9,6 +7,7 @@ import 'package:cake_wallet/src/screens/receive/widgets/address_cell.dart'; import 'package:cake_wallet/src/screens/receive/widgets/header_tile.dart'; import 'package:cake_wallet/src/widgets/section_divider.dart'; import 'package:cake_wallet/themes/extensions/receive_page_theme.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/utils/list_item.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/wallet_address_list/wallet_account_list_header.dart'; @@ -20,16 +19,17 @@ import 'package:cw_core/wallet_type.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; -import 'package:mobx/mobx.dart'; class AddressList extends StatefulWidget { const AddressList({ super.key, required this.addressListViewModel, + required this.currentTheme, this.onSelect, }); final WalletAddressListViewModel addressListViewModel; + final ThemeBase currentTheme; final Function(String)? onSelect; @override @@ -161,6 +161,8 @@ class _AddressListState extends State { return AddressCell.fromItem( item, isCurrent: isCurrent, + currentTheme: widget.currentTheme, + walletType: widget.addressListViewModel.type, hasBalance: widget.addressListViewModel.isBalanceAvailable, hasReceived: widget.addressListViewModel.isReceivedAvailable, // hasReceived: diff --git a/lib/src/screens/receive/widgets/qr_widget.dart b/lib/src/screens/receive/widgets/qr_widget.dart index 9d09e57a1..b263ea7ef 100644 --- a/lib/src/screens/receive/widgets/qr_widget.dart +++ b/lib/src/screens/receive/widgets/qr_widget.dart @@ -5,6 +5,7 @@ import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart'; import 'package:cake_wallet/src/screens/receive/widgets/currency_input_field.dart'; import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:cake_wallet/utils/address_formatter.dart'; import 'package:cake_wallet/utils/brightness_util.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/show_bar.dart'; @@ -160,16 +161,15 @@ class QRWidget extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( - child: Text( - addressUri.address, + child: AddressFormatter.buildSegmentedAddress( + address: addressUri.address, + walletType: addressListViewModel.type, textAlign: TextAlign.center, - style: TextStyle( + evenTextStyle: TextStyle( fontSize: 15, fontWeight: FontWeight.w500, color: - Theme.of(context).extension()!.textColor), - ), - ), + Theme.of(context).extension()!.textColor))), Padding( padding: EdgeInsets.only(left: 12), child: copyImage, diff --git a/lib/src/screens/restore/wallet_restore_from_seed_form.dart b/lib/src/screens/restore/wallet_restore_from_seed_form.dart index d089f8c1c..af8261662 100644 --- a/lib/src/screens/restore/wallet_restore_from_seed_form.dart +++ b/lib/src/screens/restore/wallet_restore_from_seed_form.dart @@ -11,13 +11,15 @@ import 'package:cake_wallet/themes/extensions/send_page_theme.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; import 'package:cake_wallet/view_model/seed_settings_view_model.dart'; +import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:mobx/mobx.dart'; import 'package:polyseed/polyseed.dart'; class WalletRestoreFromSeedForm extends StatefulWidget { - WalletRestoreFromSeedForm({Key? key, + WalletRestoreFromSeedForm({ + Key? key, required this.displayLanguageSelector, required this.displayBlockHeightSelector, required this.type, @@ -47,21 +49,23 @@ class WalletRestoreFromSeedForm extends StatefulWidget { @override WalletRestoreFromSeedFormState createState() => - WalletRestoreFromSeedFormState('English', displayWalletPassword: displayWalletPassword); + WalletRestoreFromSeedFormState('English', + displayWalletPassword: displayWalletPassword); } class WalletRestoreFromSeedFormState extends State { - WalletRestoreFromSeedFormState(this.language, {required bool displayWalletPassword}) + WalletRestoreFromSeedFormState(this.language, + {required bool displayWalletPassword}) : seedWidgetStateKey = GlobalKey(), blockchainHeightKey = GlobalKey(), formKey = GlobalKey(), languageController = TextEditingController(), nameTextEditingController = TextEditingController(), - passwordTextEditingController = displayWalletPassword ? TextEditingController() : null, - repeatedPasswordTextEditingController = displayWalletPassword - ? TextEditingController() - : null, - seedTypeController = TextEditingController(); + passwordTextEditingController = + displayWalletPassword ? TextEditingController() : null, + repeatedPasswordTextEditingController = + displayWalletPassword ? TextEditingController() : null, + seedTypeController = TextEditingController(); final GlobalKey seedWidgetStateKey; final GlobalKey blockchainHeightKey; @@ -83,27 +87,30 @@ class WalletRestoreFromSeedFormState extends State { _setLanguageLabel(language); if (passwordTextEditingController != null) { - passwordListener = () => widget.onPasswordChange?.call(passwordTextEditingController!.text); + passwordListener = () => + widget.onPasswordChange?.call(passwordTextEditingController!.text); passwordTextEditingController?.addListener(passwordListener!); } if (repeatedPasswordTextEditingController != null) { - repeatedPasswordListener = - () => widget.onRepeatedPasswordChange?.call(repeatedPasswordTextEditingController!.text); - repeatedPasswordTextEditingController?.addListener(repeatedPasswordListener!); + repeatedPasswordListener = () => widget.onRepeatedPasswordChange + ?.call(repeatedPasswordTextEditingController!.text); + repeatedPasswordTextEditingController + ?.addListener(repeatedPasswordListener!); } moneroSeedTypeReaction = - reaction((_) => widget.seedSettingsViewModel.moneroSeedType, (MoneroSeedType item) { - _setSeedType(item); - _changeLanguage('English'); - }); + reaction((_) => widget.seedSettingsViewModel.moneroSeedType, + (MoneroSeedType item) { + _setSeedType(item); + _changeLanguage('English'); + }); super.initState(); } @override - void dispose() { + void dispose() { moneroSeedTypeReaction(); if (passwordListener != null) { @@ -118,16 +125,22 @@ class WalletRestoreFromSeedFormState extends State { } void onSeedChange(String seed) { - if ((widget.type == WalletType.monero || widget.type == WalletType.wownero) && - Polyseed.isValidSeed(seed)) { - final lang = PolyseedLang.getByPhrase(seed); + if ([WalletType.monero, WalletType.wownero].contains(widget.type) && + (seed.split(" ").length == 12 || Polyseed.isValidSeed(seed))) { + try { + final lang = PolyseedLang.getByPhrase(seed); - _changeSeedType(MoneroSeedType.polyseed); - _changeLanguage(lang.nameEnglish); + if (widget.type == WalletType.monero && seed.split(" ").length == 12) { + _changeSeedType(MoneroSeedType.bip39); + } else { + _changeSeedType(MoneroSeedType.polyseed); + } + _changeLanguage(lang.nameEnglish, true); + } catch (e) { + printV(e); + } } - if (widget.type == WalletType.wownero && seed - .split(" ") - .length == 14) { + if (widget.type == WalletType.wownero && seed.split(" ").length == 14) { _changeSeedType(MoneroSeedType.wowneroSeed); _changeLanguage("English"); } @@ -147,9 +160,7 @@ class WalletRestoreFromSeedFormState extends State { BaseTextFormField( key: ValueKey('wallet_restore_from_seed_wallet_name_textfield_key'), controller: nameTextEditingController, - hintText: S - .of(context) - .wallet_name, + hintText: S.of(context).wallet_name, suffixIcon: IconButton( key: ValueKey('wallet_restore_from_seed_wallet_name_refresh_button_key'), onPressed: () async { @@ -158,17 +169,17 @@ class WalletRestoreFromSeedFormState extends State { setState(() { nameTextEditingController.text = rName; - nameTextEditingController.selection = TextSelection.fromPosition( - TextPosition(offset: nameTextEditingController.text.length)); + nameTextEditingController.selection = + TextSelection.fromPosition(TextPosition( + offset: + nameTextEditingController.text.length)); }); }, icon: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( borderRadius: BorderRadius.circular(6.0), - color: Theme - .of(context) - .hintColor, + color: Theme.of(context).hintColor, ), width: 34, height: 34, @@ -194,14 +205,13 @@ class WalletRestoreFromSeedFormState extends State { seedTextFieldKey: ValueKey('wallet_restore_from_seed_wallet_seeds_textfield_key'), pasteButtonKey: ValueKey('wallet_restore_from_seed_wallet_seeds_paste_button_key'), ), - if (widget.type == WalletType.monero || widget.type == WalletType.wownero) + if ([WalletType.monero, WalletType.wownero].contains(widget.type)) GestureDetector( key: ValueKey('wallet_restore_from_seed_seedtype_picker_button_key'), onTap: () async { await showPopUp( context: context, - builder: (_) => - Picker( + builder: (_) => Picker( items: _getItems(), selectedAtIndex: isPolyseed ? 1 @@ -226,33 +236,30 @@ class WalletRestoreFromSeedFormState extends State { ), ), ), - if (widget.displayWalletPassword) - ...[BaseTextFormField( + if (widget.displayWalletPassword) ...[ + BaseTextFormField( key: ValueKey('password'), controller: passwordTextEditingController, - hintText: S - .of(context) - .password, + hintText: S.of(context).password, obscureText: true), - BaseTextFormField( - key: ValueKey('repeat_wallet_password'), - controller: repeatedPasswordTextEditingController, - hintText: S - .of(context) - .repeat_wallet_password, - obscureText: true) - ], + BaseTextFormField( + key: ValueKey('repeat_wallet_password'), + controller: repeatedPasswordTextEditingController, + hintText: S.of(context).repeat_wallet_password, + obscureText: true) + ], if (widget.displayLanguageSelector) if (!seedTypeController.value.text.contains("14") && widget.displayLanguageSelector) GestureDetector( onTap: () async { await showPopUp( context: context, - builder: (_) => - SeedLanguagePicker( + builder: (_) => SeedLanguagePicker( selected: language, - onItemSelected: _changeLanguage, - seedType: isPolyseed ? MoneroSeedType.polyseed : MoneroSeedType.legacy, + onItemSelected: (lang) => + _changeLanguage(lang, isPolyseed || isBip39), + seedType: + widget.seedSettingsViewModel.moneroSeedType, )); }, child: Container( @@ -274,7 +281,8 @@ class WalletRestoreFromSeedFormState extends State { key: blockchainHeightKey, blockHeightTextFieldKey: ValueKey('wallet_restore_from_seed_blockheight_textfield_key'), onHeightOrDateEntered: widget.onHeightOrDateEntered, - hasDatePicker: widget.type == WalletType.monero || widget.type == WalletType.wownero, + hasDatePicker: + [WalletType.monero, WalletType.wownero].contains(widget.type), walletType: widget.type, ), ])); @@ -282,28 +290,29 @@ class WalletRestoreFromSeedFormState extends State { bool get isPolyseed => widget.seedSettingsViewModel.moneroSeedType == MoneroSeedType.polyseed && - (widget.type == WalletType.monero || widget.type == WalletType.wownero); + [WalletType.monero, WalletType.wownero].contains(widget.type); - Widget get expandIcon => - Container( + bool get isBip39 => + widget.seedSettingsViewModel.moneroSeedType == MoneroSeedType.bip39 && + WalletType.monero == widget.type; + + Widget get expandIcon => Container( padding: EdgeInsets.all(18), width: 24, height: 24, child: Image.asset( 'assets/images/arrow_bottom_purple_icon.png', height: 8, - color: Theme - .of(context) - .hintColor, + color: Theme.of(context).hintColor, ), ); - void _changeLanguage(String language) { - final setLang = isPolyseed + void _changeLanguage(String language, [bool useBip39Wordlist = false]) { + final setLang = useBip39Wordlist ? "POLYSEED_$language" : seedTypeController.value.text.contains("14") - ? "WOWSEED_" + language - : language; + ? "WOWSEED_" + language + : language; setState(() { this.language = setLang; seedWidgetStateKey.currentState!.changeSeedLanguage(setLang); @@ -312,8 +321,8 @@ class WalletRestoreFromSeedFormState extends State { }); } - void _setLanguageLabel(String language) => - languageController.text = '${language.replaceAll("POLYSEED_", "")} (Seed language)'; + void _setLanguageLabel(String language) => languageController.text = + '${language.replaceAll("POLYSEED_", "")} (Seed language)'; void _changeSeedType(MoneroSeedType item) { _setSeedType(item); @@ -328,9 +337,17 @@ class WalletRestoreFromSeedFormState extends State { List _getItems() { switch (widget.type) { case WalletType.monero: - return [MoneroSeedType.legacy, MoneroSeedType.polyseed]; + return [ + MoneroSeedType.legacy, + MoneroSeedType.polyseed, + MoneroSeedType.bip39 + ]; case WalletType.wownero: - return [MoneroSeedType.legacy, MoneroSeedType.polyseed, MoneroSeedType.wowneroSeed]; + return [ + MoneroSeedType.legacy, + MoneroSeedType.polyseed, + MoneroSeedType.wowneroSeed + ]; default: return [MoneroSeedType.legacy]; } diff --git a/lib/src/screens/restore/wallet_restore_page.dart b/lib/src/screens/restore/wallet_restore_page.dart index ce8595ba4..e2e149644 100644 --- a/lib/src/screens/restore/wallet_restore_page.dart +++ b/lib/src/screens/restore/wallet_restore_page.dart @@ -527,25 +527,42 @@ class _WalletRestorePageBodyState extends State<_WalletRestorePageBody> } bool _isValidSeed() { - final seedPhrase = - walletRestoreFromSeedFormKey.currentState!.seedWidgetStateKey.currentState!.text; + final seedPhrase = walletRestoreFromSeedFormKey + .currentState!.seedWidgetStateKey.currentState!.text; if (walletRestoreViewModel.isPolyseed(seedPhrase)) return true; final seedWords = seedPhrase.split(' '); - if (seedWords.length == 14 && walletRestoreViewModel.type == WalletType.wownero) return true; - if (seedWords.length == 26 && walletRestoreViewModel.type == WalletType.zano) return true; + if (seedWords.length == 14 && + walletRestoreViewModel.type == WalletType.wownero) return true; + if (seedWords.length == 26 && + walletRestoreViewModel.type == WalletType.zano) return true; - if ((walletRestoreViewModel.type == WalletType.monero || - walletRestoreViewModel.type == WalletType.wownero || - walletRestoreViewModel.type == WalletType.haven) && - seedWords.length != WalletRestoreViewModelBase.moneroSeedMnemonicLength) { - return false; + if (seedWords.length == 12 && + walletRestoreViewModel.type == WalletType.monero) { + return walletRestoreFromSeedFormKey + .currentState + ?.blockchainHeightKey + .currentState + ?.restoreHeightController + .text + .isNotEmpty == true; + } + + if ([WalletType.monero, WalletType.wownero, WalletType.haven] + .contains(walletRestoreViewModel.type) && + seedWords.length == + WalletRestoreViewModelBase.moneroSeedMnemonicLength) { + return true; } // bip39: final validBip39SeedLengths = [12, 18, 24]; - final nonBip39WalletTypes = [WalletType.monero, WalletType.wownero, WalletType.haven, WalletType.decred]; + final nonBip39WalletTypes = [ + WalletType.wownero, + WalletType.haven, + WalletType.decred + ]; // if it's a bip39 wallet and the length is not valid return false if (!nonBip39WalletTypes.contains(walletRestoreViewModel.type) && !(validBip39SeedLengths.contains(seedWords.length))) { @@ -558,8 +575,9 @@ class _WalletRestorePageBodyState extends State<_WalletRestorePageBody> return false; } - final words = - walletRestoreFromSeedFormKey.currentState!.seedWidgetStateKey.currentState!.words.toSet(); + final words = walletRestoreFromSeedFormKey + .currentState!.seedWidgetStateKey.currentState!.words + .toSet(); return seedWords.toSet().difference(words).toSet().isEmpty; } diff --git a/lib/src/screens/seed/pre_seed_page.dart b/lib/src/screens/seed/pre_seed_page.dart index 91a47fda5..5fb6bb8c1 100644 --- a/lib/src/screens/seed/pre_seed_page.dart +++ b/lib/src/screens/seed/pre_seed_page.dart @@ -4,9 +4,7 @@ import 'package:cake_wallet/src/screens/Info_page.dart'; import 'package:flutter/cupertino.dart'; class PreSeedPage extends InfoPage { - PreSeedPage(this.seedPhraseLength); - - final int seedPhraseLength; + PreSeedPage(); @override bool get onWillPop => false; @@ -15,7 +13,7 @@ class PreSeedPage extends InfoPage { String get pageTitle => S.current.pre_seed_title; @override - String get pageDescription => S.current.pre_seed_description(seedPhraseLength.toString()); + String get pageDescription => S.current.pre_seed_description; @override String get buttonText => S.current.pre_seed_button_text; diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index 277986c3f..8c42f1129 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -98,7 +98,7 @@ class SendPage extends BasePage { return MergeSemantics( child: SizedBox( height: isMobileView ? 37 : 45, - width: isMobileView ? 47: 45, + width: isMobileView ? 47 : 45, child: ButtonTheme( minWidth: double.minPositive, child: Semantics( @@ -397,7 +397,6 @@ class SendPage extends BasePage { return LoadingPrimaryButton( key: ValueKey('send_page_send_button_key'), onPressed: () async { - //Request dummy node to get the focus out of the text fields FocusScope.of(context).requestFocus(FocusNode()); @@ -496,7 +495,7 @@ class SendPage extends BasePage { bitcoin!.updateFeeRates(sendViewModel.wallet); } - reaction((_) => sendViewModel.state, (ExecutionState state) { + reaction((_) => sendViewModel.state, (ExecutionState state) async { if (dialogContext != null && dialogContext?.mounted == true) { Navigator.of(dialogContext!).pop(); } @@ -507,7 +506,6 @@ class SendPage extends BasePage { Navigator.of(loadingBottomSheetContext!).pop(); } - if (state is FailureState) { WidgetsBinding.instance.addPostFrameCallback((_) { showPopUp( @@ -525,6 +523,13 @@ class SendPage extends BasePage { } if (state is IsExecutingState) { + // wait a bit to avoid showing the loading dialog if transaction is failed + await Future.delayed(const Duration(milliseconds: 300)); + final currentState = sendViewModel.state; + if (currentState is ExecutedSuccessfullyState || currentState is FailureState) { + return; + } + WidgetsBinding.instance.addPostFrameCallback((_) { if (context.mounted) { showModalBottomSheet( @@ -542,9 +547,9 @@ class SendPage extends BasePage { } if (state is ExecutedSuccessfullyState) { - WidgetsBinding.instance.addPostFrameCallback((_) { + WidgetsBinding.instance.addPostFrameCallback((_) async { if (context.mounted) { - showModalBottomSheet( + final result = await showModalBottomSheet( context: context, isDismissible: false, isScrollControlled: true, @@ -553,6 +558,7 @@ class SendPage extends BasePage { key: ValueKey('send_page_confirm_sending_dialog_key'), titleText: S.of(bottomSheetContext).confirm_transaction, currentTheme: currentTheme, + walletType: sendViewModel.walletType, titleIconPath: sendViewModel.selectedCryptoCurrency.iconPath, currency: sendViewModel.selectedCryptoCurrency, amount: S.of(bottomSheetContext).send_amount, @@ -565,19 +571,20 @@ class SendPage extends BasePage { feeFiatAmount: sendViewModel.pendingTransactionFeeFiatAmountFormatted, outputs: sendViewModel.outputs, onSlideComplete: () async { - Navigator.of(bottomSheetContext).pop(); + Navigator.of(bottomSheetContext).pop(true); sendViewModel.commitTransaction(context); }, change: sendViewModel.pendingTransaction!.change, + isOpenCryptoPay: sendViewModel.ocpRequest != null, ); }, ); + + if (result == null) sendViewModel.dismissTransaction(); } }); } - - if (state is TransactionCommitted) { WidgetsBinding.instance.addPostFrameCallback((_) async { if (!context.mounted) { @@ -586,15 +593,19 @@ class SendPage extends BasePage { newContactAddress = newContactAddress ?? sendViewModel.newContactAddress(); - if (newContactAddress?.address != null && isRegularElectrumAddress(newContactAddress!.address)) { + if (newContactAddress?.address != null && + isRegularElectrumAddress(newContactAddress!.address)) { newContactAddress = null; } + bool showContactSheet = (newContactAddress != null && sendViewModel.showAddressBookPopup); + await showModalBottomSheet( context: context, isDismissible: false, builder: (BuildContext bottomSheetContext) { - return newContactAddress != null && sendViewModel.showAddressBookPopup + return showContactSheet && + sendViewModel.ocpRequest == null ? InfoBottomSheet( currentTheme: currentTheme, showDontAskMeCheckbox: true, @@ -608,16 +619,20 @@ class SendPage extends BasePage { rightButtonText: 'Yes', actionLeftButton: () { Navigator.of(bottomSheetContext).pop(); - Navigator.of(context) - .pushNamedAndRemoveUntil(Routes.dashboard, (route) => false); + if (context.mounted) { + Navigator.of(context) + .pushNamedAndRemoveUntil(Routes.dashboard, (route) => false); + } RequestReviewHandler.requestReview(); newContactAddress = null; }, actionRightButton: () { Navigator.of(bottomSheetContext).pop(); RequestReviewHandler.requestReview(); - Navigator.of(context) - .pushNamed(Routes.addressBookAddContact, arguments: newContactAddress); + if (context.mounted) { + Navigator.of(context).pushNamed(Routes.addressBookAddContact, + arguments: newContactAddress); + } newContactAddress = null; }, ) @@ -629,11 +644,16 @@ class SendPage extends BasePage { actionButtonKey: ValueKey('send_page_sent_dialog_ok_button_key'), actionButton: () { Navigator.of(bottomSheetContext).pop(); - Navigator.of(context) - .pushNamedAndRemoveUntil(Routes.dashboard, (route) => false); - RequestReviewHandler.requestReview(); - newContactAddress = null; - }); + Future.delayed(Duration.zero, () { + if (context.mounted) { + Navigator.of(context) + .pushNamedAndRemoveUntil(Routes.dashboard, (route) => false); + } + RequestReviewHandler.requestReview(); + newContactAddress = null; + }); + }, + ); }, ); @@ -656,20 +676,25 @@ class SendPage extends BasePage { if (state is IsAwaitingDeviceResponseState) { WidgetsBinding.instance.addPostFrameCallback((_) { - showPopUp( - context: context, - builder: (BuildContext context) { - dialogContext = context; - return AlertWithOneAction( - alertTitle: S.of(context).proceed_on_device, - alertContent: S.of(context).proceed_on_device_description, - buttonText: S.of(context).cancel, - alertBarrierDismissible: false, - buttonAction: () { - sendViewModel.state = InitialExecutionState(); - Navigator.of(context).pop(); - }); - }); + if (!context.mounted) return; + + showModalBottomSheet( + context: context, + isDismissible: false, + builder: (BuildContext bottomSheetContext) => InfoBottomSheet( + currentTheme: currentTheme, + titleText: S.of(bottomSheetContext).proceed_on_device, + contentImage: 'assets/images/hardware_wallet/ledger_nano_x.png', + contentImageColor: Theme.of(context).extension()!.titleColor, + content: S.of(bottomSheetContext).proceed_on_device_description, + isTwoAction: false, + actionButtonText: S.of(context).cancel, + actionButton: () { + sendViewModel.state = InitialExecutionState(); + Navigator.of(bottomSheetContext).pop(); + }, + ), + ); }); } }); @@ -759,5 +784,4 @@ class SendPage extends BasePage { return isValid; } - } diff --git a/lib/src/screens/send/widgets/send_card.dart b/lib/src/screens/send/widgets/send_card.dart index 7c54f2b21..c96992a00 100644 --- a/lib/src/screens/send/widgets/send_card.dart +++ b/lib/src/screens/send/widgets/send_card.dart @@ -1,16 +1,17 @@ +import 'package:cake_wallet/core/open_crypto_pay/open_cryptopay_service.dart'; import 'package:cake_wallet/entities/priority_for_wallet_type.dart'; import 'package:cake_wallet/src/screens/receive/widgets/currency_input_field.dart'; import 'package:cake_wallet/src/widgets/picker.dart'; import 'package:cake_wallet/src/widgets/standard_checkbox.dart'; -import 'package:cake_wallet/themes/extensions/keyboard_theme.dart'; import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; +import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/utils/payment_request.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/currency.dart'; import 'package:cake_wallet/routes.dart'; -import 'package:cake_wallet/src/widgets/keyboard_done_button.dart'; import 'package:cake_wallet/view_model/send/output.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/unspent_coin_type.dart'; @@ -18,7 +19,6 @@ import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:mobx/mobx.dart'; -import 'package:keyboard_actions/keyboard_actions.dart'; import 'package:cake_wallet/view_model/send/send_view_model.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/src/widgets/address_text_field.dart'; @@ -26,9 +26,6 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; import 'package:cake_wallet/themes/extensions/send_page_theme.dart'; -import '../../../../themes/extensions/cake_text_theme.dart'; -import '../../../../themes/theme_base.dart'; - class SendCard extends StatefulWidget { SendCard({ Key? key, @@ -94,6 +91,9 @@ class SendCardState extends State with AutomaticKeepAliveClientMixin with AutomaticKeepAliveClientMixin with AutomaticKeepAliveClientMixin output.setSendAll(sendViewModel.sendingBalance)), + allAmountCallback: () async => output.setSendAll(await sendViewModel.sendingBalance)), Divider( height: 1, color: Theme.of(context).extension()!.textFieldHintColor), Observer( - builder: (_) => Padding( - padding: EdgeInsets.only(top: 10), - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text( - S.of(context).available_balance + ':', - style: TextStyle( + builder: (_) { + // force rebuild on mobx + final _ = sendViewModel.coinTypeToSpendFrom; + return Padding( + padding: EdgeInsets.only(top: 10), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + S.of(context).available_balance + ':', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: + Theme.of(context).extension()!.textFieldHintColor), + ), + ), + FutureBuilder( + future: sendViewModel.sendingBalance, + builder: (context, snapshot) { + return Text( + snapshot.data ?? sendViewModel.balance, // default to balance while loading + style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: Theme.of(context).extension()!.textFieldHintColor), - ), - ), - Text( - sendViewModel.sendingBalance, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: - Theme.of(context).extension()!.textFieldHintColor), - ) - ], - ), - ), + ); + }, + ) + ], + ), + ); + }, ), if (!sendViewModel.isFiatDisabled) CurrencyAmountTextField( @@ -509,9 +523,9 @@ class SendCardState extends State with AutomaticKeepAliveClientMixin sendViewModel.selectedCryptoCurrency, (Currency currency) { + reaction((_) => sendViewModel.selectedCryptoCurrency, (Currency currency) async { if (output.sendAll) { - output.setSendAll(sendViewModel.sendingBalance); + output.setSendAll(await sendViewModel.sendingBalance); } output.setCryptoAmount(cryptoAmountController.text); diff --git a/lib/src/screens/settings/other_settings_page.dart b/lib/src/screens/settings/other_settings_page.dart index ca1c1b2cb..f841e2998 100644 --- a/lib/src/screens/settings/other_settings_page.dart +++ b/lib/src/screens/settings/other_settings_page.dart @@ -8,6 +8,7 @@ import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arro import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart'; import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart'; import 'package:cake_wallet/src/screens/settings/widgets/settings_version_cell.dart'; +import 'package:cake_wallet/utils/feature_flag.dart'; import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/foundation.dart'; @@ -64,12 +65,18 @@ class OtherSettingsPage extends BasePage { handler: (BuildContext context) => Navigator.of(context).pushNamed(Routes.readDisclaimer), ), - if (kDebugMode && _otherSettingsViewModel.walletType == WalletType.monero) + if (FeatureFlag.hasDevOptions && _otherSettingsViewModel.walletType == WalletType.monero) SettingsCellWithArrow( title: '[dev] monero background sync', handler: (BuildContext context) => Navigator.of(context).pushNamed(Routes.devMoneroBackgroundSync), ), + if (FeatureFlag.hasDevOptions && [WalletType.monero, WalletType.wownero, WalletType.zano].contains(_otherSettingsViewModel.walletType)) + SettingsCellWithArrow( + title: '[dev] xmr call profiler', + handler: (BuildContext context) => + Navigator.of(context).pushNamed(Routes.devMoneroCallProfiler), + ), Spacer(), SettingsVersionCell( title: S.of(context).version(_otherSettingsViewModel.currentVersion)), diff --git a/lib/src/screens/transaction_details/rbf_details_page.dart b/lib/src/screens/transaction_details/rbf_details_page.dart index 10cd40940..ce6ee122b 100644 --- a/lib/src/screens/transaction_details/rbf_details_page.dart +++ b/lib/src/screens/transaction_details/rbf_details_page.dart @@ -190,6 +190,7 @@ class RBFDetailsPage extends BasePage { key: ValueKey('rbf_confirm_sending_bottom_sheet'), titleText: S.of(bottomSheetContext).confirm_transaction, currentTheme: currentTheme, + walletType: transactionDetailsViewModel.sendViewModel.walletType, titleIconPath: transactionDetailsViewModel.sendViewModel.selectedCryptoCurrency.iconPath, currency: transactionDetailsViewModel.sendViewModel.selectedCryptoCurrency, amount: S.of(bottomSheetContext).send_amount, diff --git a/lib/src/screens/transaction_details/transaction_details_page.dart b/lib/src/screens/transaction_details/transaction_details_page.dart index 9484bf4da..c1188d0c7 100644 --- a/lib/src/screens/transaction_details/transaction_details_page.dart +++ b/lib/src/screens/transaction_details/transaction_details_page.dart @@ -9,8 +9,11 @@ import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item. import 'package:cake_wallet/src/screens/transaction_details/widgets/textfield_list_row.dart'; import 'package:cake_wallet/src/widgets/list_row.dart'; import 'package:cake_wallet/src/widgets/standard_list.dart'; +import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; +import 'package:cake_wallet/utils/address_formatter.dart'; import 'package:cake_wallet/utils/show_bar.dart'; import 'package:cake_wallet/view_model/transaction_details_view_model.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; @@ -39,13 +42,28 @@ class TransactionDetailsPage extends BasePage { final item = transactionDetailsViewModel.items[index]; if (item is StandartListItem) { + Widget? addressTextWidget; + + if (item.title.toLowerCase() == 'recipient addresses' || + item.title.toLowerCase() == 'source address') { + addressTextWidget = getFormattedAddress( + context: context, + value: item.value, + walletType: transactionDetailsViewModel.sendViewModel.walletType, + ); + } + return GestureDetector( key: item.key, onTap: () { Clipboard.setData(ClipboardData(text: item.value)); showBar(context, S.of(context).transaction_details_copied(item.title)); }, - child: ListRow(title: '${item.title}:', value: item.value), + child: ListRow( + title: '${item.title}:', + value: item.value, + textWidget: addressTextWidget, + ), ); } @@ -91,4 +109,80 @@ class TransactionDetailsPage extends BasePage { ], ); } + + Widget getFormattedAddress({ + required BuildContext context, + required String value, + required WalletType walletType, + }) { + final textStyle = TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).extension()!.titleColor, + ); + final List children = []; + final bool hasDoubleNewline = value.contains('\n\n'); + + if (hasDoubleNewline) { + final blocks = value + .split('\n\n') + .map((b) => b.trim()) + .where((b) => b.isNotEmpty) + .toList(); + for (final block in blocks) { + final lines = block + .split('\n') + .map((l) => l.trim()) + .where((l) => l.isNotEmpty) + .toList(); + if (lines.length > 1) { + children.add(Text(lines.first, style: textStyle)); + for (int i = 1; i < lines.length; i++) { + children.add( + AddressFormatter.buildSegmentedAddress( + address: lines[i], + walletType: walletType, + evenTextStyle: textStyle, + ), + ); + } + } else { + children.add( + AddressFormatter.buildSegmentedAddress( + address: lines.first, + walletType: walletType, + evenTextStyle: textStyle, + ), + ); + } + children.add(SizedBox(height: 8)); + } + } else { + final lines = value + .split('\n') + .map((l) => l.trim()) + .where((l) => l.isNotEmpty) + .toList(); + bool firstLineIsContactName = (lines.length > 1 && lines.first.length < 20); + int startIndex = 0; + if (firstLineIsContactName) { + children.add(Text(lines.first, style: textStyle)); + startIndex = 1; + } + for (int i = startIndex; i < lines.length; i++) { + children.add( + AddressFormatter.buildSegmentedAddress( + address: lines[i], + walletType: walletType, + evenTextStyle: textStyle, + ), + ); + } + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: children, + ); + } } diff --git a/lib/src/screens/wallet_connect/wc_pairing_detail_page.dart b/lib/src/screens/wallet_connect/wc_pairing_detail_page.dart index f99eb9cdb..321d1354a 100644 --- a/lib/src/screens/wallet_connect/wc_pairing_detail_page.dart +++ b/lib/src/screens/wallet_connect/wc_pairing_detail_page.dart @@ -101,7 +101,7 @@ class WCCDetailsWidget extends BasePage { child: CircleAvatar( backgroundImage: (pairing.peerMetadata!.icons.isNotEmpty ? NetworkImage(pairing.peerMetadata!.icons[0]) - : const AssetImage('assets/images/default_icon.png')) + : const AssetImage('assets/images/app_logo.png')) as ImageProvider, ), ), diff --git a/lib/src/screens/wallet_list/wallet_list_page.dart b/lib/src/screens/wallet_list/wallet_list_page.dart index 62f79bdc4..507773467 100644 --- a/lib/src/screens/wallet_list/wallet_list_page.dart +++ b/lib/src/screens/wallet_list/wallet_list_page.dart @@ -525,6 +525,7 @@ class WalletListBodyState extends State { didConnect = true; Navigator.of(context).pop(); }, + isReconnect: true, ), ); diff --git a/lib/src/widgets/alert_with_two_actions.dart b/lib/src/widgets/alert_with_two_actions.dart index e3d4408a6..c2ab872af 100644 --- a/lib/src/widgets/alert_with_two_actions.dart +++ b/lib/src/widgets/alert_with_two_actions.dart @@ -6,6 +6,7 @@ class AlertWithTwoActions extends BaseAlertDialog { AlertWithTwoActions({ required this.alertTitle, required this.alertContent, + this.alertContentTextWidget, required this.leftButtonText, required this.rightButtonText, required this.actionLeftButton, @@ -21,6 +22,7 @@ class AlertWithTwoActions extends BaseAlertDialog { final String alertTitle; final String alertContent; + final Widget? alertContentTextWidget; final String leftButtonText; final String rightButtonText; final VoidCallback actionLeftButton; @@ -38,6 +40,8 @@ class AlertWithTwoActions extends BaseAlertDialog { @override String get contentText => alertContent; @override + Widget? get contentTextWidget => alertContentTextWidget; + @override String get leftActionButtonText => leftButtonText; @override String get rightActionButtonText => rightButtonText; diff --git a/lib/src/widgets/base_alert_dialog.dart b/lib/src/widgets/base_alert_dialog.dart index 02b8f85ab..2ea067a76 100644 --- a/lib/src/widgets/base_alert_dialog.dart +++ b/lib/src/widgets/base_alert_dialog.dart @@ -13,6 +13,8 @@ class BaseAlertDialog extends StatelessWidget { String get contentText => ''; + Widget? get contentTextWidget => null; + String get leftActionButtonText => ''; String get rightActionButtonText => ''; @@ -79,7 +81,8 @@ class BaseAlertDialog extends StatelessWidget { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Text( + contentTextWidget ?? + Text( contentText, textAlign: TextAlign.center, style: TextStyle( diff --git a/lib/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart b/lib/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart index 206780eae..923062c2e 100644 --- a/lib/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart +++ b/lib/src/widgets/bottom_sheet/base_bottom_sheet_widget.dart @@ -33,7 +33,7 @@ abstract class BaseBottomSheet extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ if (titleIconPath != null) - Image.asset(titleIconPath!, height: 24, width: 24) + Image.asset(titleIconPath!, height: 24, width: 24, excludeFromSemantics: true) else Container(), const SizedBox(width: 6), diff --git a/lib/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart b/lib/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart index a5bdafc7c..764429bf1 100644 --- a/lib/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart +++ b/lib/src/widgets/bottom_sheet/confirm_sending_bottom_sheet_widget.dart @@ -5,11 +5,12 @@ import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:cake_wallet/themes/extensions/filter_theme.dart'; import 'package:cake_wallet/themes/extensions/sync_indicator_theme.dart'; import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:cake_wallet/utils/address_formatter.dart'; import 'package:cake_wallet/view_model/send/output.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/pending_transaction.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; -import 'dart:math' as math; import 'base_bottom_sheet_widget.dart'; @@ -27,7 +28,9 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet { final String feeFiatAmount; final List outputs; final VoidCallback onSlideComplete; + final WalletType walletType; final PendingChange? change; + final bool isOpenCryptoPay; ConfirmSendingBottomSheet({ required String titleText, @@ -45,7 +48,9 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet { required this.feeFiatAmount, required this.outputs, required this.onSlideComplete, + required this.walletType, this.change, + this.isOpenCryptoPay = false, Key? key, }) : showScrollbar = outputs.length > 3, super(titleText: titleText, titleIconPath: titleIconPath); @@ -89,6 +94,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet { itemTitle: paymentId!, currentTheme: currentTheme, itemTitleTextStyle: itemTitleTextStyle, + walletType: walletType, isBatchSending: false, amount: '', address: paymentIdValue!, @@ -132,21 +138,23 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet { final _amount = item.cryptoAmount.replaceAll(',', '.') + ' ${currency.title}'; return isBatchSending || contactName.isNotEmpty ? AddressExpansionTile( - contactType: 'Contact', + contactType: isOpenCryptoPay ? 'Open CryptoPay' : S.of(context).contact, currentTheme: currentTheme, name: isBatchSending ? batchContactTitle : contactName, address: _address, amount: _amount, + walletType: walletType, isBatchSending: isBatchSending, itemTitleTextStyle: itemTitleTextStyle, itemSubTitleTextStyle: itemSubTitleTextStyle, tileBackgroundColor: tileBackgroundColor, ) : AddressTile( - itemTitle: 'Address', + itemTitle: S.of(context).address, currentTheme: currentTheme, itemTitleTextStyle: itemTitleTextStyle, isBatchSending: isBatchSending, + walletType: walletType, amount: _amount, address: _address, itemSubTitleTextStyle: itemSubTitleTextStyle, @@ -164,6 +172,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet { address: change!.address, amount: change!.amount + ' ${currency.title}', isBatchSending: true, + walletType: walletType, itemTitleTextStyle: itemTitleTextStyle, itemSubTitleTextStyle: itemSubTitleTextStyle, tileBackgroundColor: tileBackgroundColor, @@ -214,6 +223,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet { onSlideComplete: onSlideComplete, buttonText: 'Swipe to send', currentTheme: currentTheme, + accessibleNavigationModeButtonText: S.of(context).send, ), ); } @@ -239,24 +249,28 @@ class StandardTile extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), - decoration: - BoxDecoration(borderRadius: BorderRadius.circular(10), color: tileBackgroundColor), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(itemTitle, style: itemTitleTextStyle), - Column( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Text(itemValue, style: itemTitleTextStyle), - itemSubTitle == null - ? Container() - : Text(itemSubTitle!, style: itemSubTitleTextStyle), - ], - ), - ], + return Semantics( + container: true, + label: itemTitle, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), + decoration: + BoxDecoration(borderRadius: BorderRadius.circular(10), color: tileBackgroundColor), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(itemTitle, style: itemTitleTextStyle), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text(itemValue, style: itemTitleTextStyle), + itemSubTitle == null + ? Container() + : Text(itemSubTitle!, style: itemSubTitleTextStyle), + ], + ), + ], + ), ), ); } @@ -273,6 +287,7 @@ class AddressTile extends StatelessWidget { required this.address, required this.itemSubTitleTextStyle, required this.tileBackgroundColor, + required this.walletType, }); final String itemTitle; @@ -283,18 +298,10 @@ class AddressTile extends StatelessWidget { final String address; final TextStyle itemSubTitleTextStyle; final Color tileBackgroundColor; + final WalletType walletType; @override Widget build(BuildContext context) { - final addressTextStyle = TextStyle( - fontSize: 12, - fontFamily: 'Lato', - fontWeight: FontWeight.w600, - color: currentTheme.type == ThemeType.bright - ? Theme.of(context).extension()!.titleColor.withOpacity(0.5) - : Theme.of(context).extension()!.titleColor, - decoration: TextDecoration.none, - ); return Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), decoration: BoxDecoration( @@ -311,41 +318,20 @@ class AddressTile extends StatelessWidget { if (isBatchSending) Text(amount, style: itemTitleTextStyle), ], ), - buildSegmentedAddress( + AddressFormatter.buildSegmentedAddress( address: address, - evenTextStyle: addressTextStyle, - oddTextStyle: itemSubTitleTextStyle, + walletType: walletType, + evenTextStyle: TextStyle( + fontSize: 12, + fontFamily: 'Lato', + fontWeight: FontWeight.w600, + color: Theme.of(context).extension()!.titleColor, + decoration: TextDecoration.none) ), ], ), ); } - - Widget buildSegmentedAddress({ - required String address, - int chunkSize = 6, - required TextStyle evenTextStyle, - required TextStyle oddTextStyle, - }) { - final spans = []; - - int index = 0; - for (int i = 0; i < address.length; i += chunkSize) { - final chunk = address.substring(i, math.min(i + chunkSize, address.length)); - final style = (index % 2 == 0) ? evenTextStyle : oddTextStyle; - - spans.add( - TextSpan(text: '$chunk ', style: style), - ); - - index++; - } - - return RichText( - text: TextSpan(children: spans, style: evenTextStyle), - overflow: TextOverflow.visible, - ); - } } class AddressExpansionTile extends StatelessWidget { @@ -360,6 +346,7 @@ class AddressExpansionTile extends StatelessWidget { required this.itemTitleTextStyle, required this.itemSubTitleTextStyle, required this.tileBackgroundColor, + required this.walletType, }); final String contactType; @@ -371,91 +358,65 @@ class AddressExpansionTile extends StatelessWidget { final TextStyle itemTitleTextStyle; final TextStyle itemSubTitleTextStyle; final Color tileBackgroundColor; + final WalletType walletType; @override Widget build(BuildContext context) { - final addressTextStyle = TextStyle( - fontSize: 12, - fontFamily: 'Lato', - fontWeight: FontWeight.w600, - color: currentTheme.type == ThemeType.bright - ? Theme.of(context).extension()!.titleColor.withOpacity(0.5) - : Theme.of(context).extension()!.titleColor, - decoration: TextDecoration.none, - ); - - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(10)), - color: tileBackgroundColor, - ), - child: Theme( - data: Theme.of(context).copyWith(dividerColor: Colors.transparent), - child: Padding( - padding: EdgeInsets.symmetric(horizontal: 14, vertical: isBatchSending ? 0 : 8), - child: ExpansionTile( - childrenPadding: isBatchSending ? const EdgeInsets.only(bottom: 8) : EdgeInsets.zero, - tilePadding: EdgeInsets.zero, - dense: true, - visualDensity: VisualDensity.compact, - title: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text(isBatchSending ? name : contactType, - style: itemTitleTextStyle, softWrap: true)), - Text(isBatchSending ? amount : name, - style: TextStyle( - fontSize: 14, - fontFamily: 'Lato', - fontWeight: FontWeight.w600, - color: Theme.of(context).extension()!.titleColor, - decoration: TextDecoration.none, - )), - ], - ), - children: [ - Row( + return Semantics( + container: true, + label: name, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(10)), + color: tileBackgroundColor, + ), + child: Theme( + data: Theme.of(context).copyWith(dividerColor: Colors.transparent), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 14, vertical: isBatchSending ? 0 : 8), + child: ExpansionTile( + childrenPadding: isBatchSending ? const EdgeInsets.only(bottom: 8) : EdgeInsets.zero, + tilePadding: EdgeInsets.zero, + dense: true, + visualDensity: VisualDensity.compact, + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( - child: buildSegmentedAddress( - address: address, - evenTextStyle: addressTextStyle, - oddTextStyle: itemSubTitleTextStyle, - ), - ), + child: Text(isBatchSending ? name : contactType, + style: itemTitleTextStyle, softWrap: true)), + Text(isBatchSending ? amount : name, + style: TextStyle( + fontSize: 14, + fontFamily: 'Lato', + fontWeight: FontWeight.w600, + color: Theme.of(context).extension()!.titleColor, + decoration: TextDecoration.none, + )), ], ), - ], + children: [ + Row( + children: [ + Expanded( + child: AddressFormatter.buildSegmentedAddress( + address: address, + walletType: walletType, + evenTextStyle: TextStyle( + fontSize: 12, + fontFamily: 'Lato', + fontWeight: FontWeight.w600, + color: Theme.of(context).extension()!.titleColor, + decoration: TextDecoration.none) + ), + ), + ], + ), + ], + ), ), ), ), ); } - - Widget buildSegmentedAddress({ - required String address, - int chunkSize = 6, - required TextStyle evenTextStyle, - required TextStyle oddTextStyle, - }) { - final spans = []; - - int index = 0; - for (int i = 0; i < address.length; i += chunkSize) { - final chunk = address.substring(i, math.min(i + chunkSize, address.length)); - final style = (index % 2 == 0) ? evenTextStyle : oddTextStyle; - - spans.add( - TextSpan(text: '$chunk ', style: style), - ); - - index++; - } - - return RichText( - text: TextSpan(children: spans, style: evenTextStyle), - overflow: TextOverflow.visible, - ); - } } diff --git a/lib/src/widgets/list_row.dart b/lib/src/widgets/list_row.dart index 417c7836b..11b27845b 100644 --- a/lib/src/widgets/list_row.dart +++ b/lib/src/widgets/list_row.dart @@ -12,7 +12,8 @@ class ListRow extends StatelessWidget { this.padding, this.color, this.hintTextColor, - this.mainTextColor + this.mainTextColor, + this.textWidget }); final String title; @@ -24,6 +25,16 @@ class ListRow extends StatelessWidget { final Color? color; final Color? hintTextColor; final Color? mainTextColor; + final Widget? textWidget; + + Widget _getTextWidget (BuildContext context) => textWidget ?? Text( + value, + style: TextStyle( + fontSize: valueFontSize, + fontWeight: FontWeight.w500, + color: mainTextColor ?? Theme.of(context).extension()!.titleColor + ), + ); @override Widget build(BuildContext context) { @@ -49,12 +60,7 @@ class ListRow extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( - child: Text(value, - style: TextStyle( - fontSize: valueFontSize, - fontWeight: FontWeight.w500, - color: mainTextColor ?? Theme.of(context).extension()!.titleColor)), - ), + child: _getTextWidget(context)), image != null ? Padding( padding: EdgeInsets.only(left: 24), diff --git a/lib/src/widgets/primary_button.dart b/lib/src/widgets/primary_button.dart index d5800aa5b..fcabe5b83 100644 --- a/lib/src/widgets/primary_button.dart +++ b/lib/src/widgets/primary_button.dart @@ -104,7 +104,7 @@ class LoadingPrimaryButton extends StatelessWidget { ), )), child: isLoading - ? CupertinoActivityIndicator(animating: true) + ? CupertinoActivityIndicator(animating: true, color: textColor) : Text(text, style: TextStyle( fontSize: 15.0, diff --git a/lib/src/widgets/seed_language_picker.dart b/lib/src/widgets/seed_language_picker.dart index 4a63e3092..e2b10aa4c 100644 --- a/lib/src/widgets/seed_language_picker.dart +++ b/lib/src/widgets/seed_language_picker.dart @@ -16,7 +16,7 @@ class SeedLanguagePickerOption { final List seedLanguages = [ SeedLanguagePickerOption('English', S.current.seed_language_english, - Image.asset('assets/images/flags/usa.png'), [MoneroSeedType.legacy, MoneroSeedType.polyseed]), + Image.asset('assets/images/flags/usa.png'), [MoneroSeedType.legacy, MoneroSeedType.polyseed, MoneroSeedType.bip39]), SeedLanguagePickerOption('Chinese (Simplified)', S.current.seed_language_chinese, Image.asset('assets/images/flags/chn.png'), [MoneroSeedType.legacy, MoneroSeedType.polyseed]), SeedLanguagePickerOption('Chinese (Traditional)', S.current.seed_language_chinese_traditional, diff --git a/lib/src/widgets/standard_slide_button_widget.dart b/lib/src/widgets/standard_slide_button_widget.dart index 57271b6b5..299f6bdf7 100644 --- a/lib/src/widgets/standard_slide_button_widget.dart +++ b/lib/src/widgets/standard_slide_button_widget.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:cake_wallet/themes/extensions/filter_theme.dart'; import 'package:cake_wallet/themes/extensions/menu_theme.dart'; @@ -12,12 +13,14 @@ class StandardSlideButton extends StatefulWidget { this.buttonText = '', this.height = 48.0, required this.currentTheme, + required this.accessibleNavigationModeButtonText, }) : super(key: key); final VoidCallback onSlideComplete; final String buttonText; final double height; final ThemeBase currentTheme; + final String accessibleNavigationModeButtonText; @override _StandardSlideButtonState createState() => _StandardSlideButtonState(); @@ -28,68 +31,77 @@ class _StandardSlideButtonState extends State { @override Widget build(BuildContext context) { - return LayoutBuilder(builder: (context, constraints) { - final double maxWidth = constraints.maxWidth; - const double sideMargin = 4.0; - final double effectiveMaxWidth = maxWidth - 2 * sideMargin; - const double sliderWidth = 42.0; + final bool accessible = MediaQuery.of(context).accessibleNavigation; - final tileBackgroundColor = widget.currentTheme.type == ThemeType.light - ? Theme.of(context).extension()!.syncedBackgroundColor - : widget.currentTheme.type == ThemeType.oled - ? Colors.black.withOpacity(0.5) - : Theme.of(context).extension()!.buttonColor; + final tileBackgroundColor = widget.currentTheme.type == ThemeType.light + ? Theme.of(context).extension()!.syncedBackgroundColor + : widget.currentTheme.type == ThemeType.oled + ? Colors.black.withOpacity(0.5) + : Theme.of(context).extension()!.buttonColor; - return Container( - height: widget.height, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - color: tileBackgroundColor), - child: Stack( - alignment: Alignment.centerLeft, - children: [ - Center( - child: Text(widget.buttonText, - style: TextStyle( - fontSize: 16, - fontFamily: 'Lato', - fontWeight: FontWeight.w600, - color: Theme.of(context).extension()!.titleColor))), - Positioned( - left: sideMargin + _dragPosition, - child: GestureDetector( - onHorizontalDragUpdate: (details) { - setState(() { - _dragPosition += details.delta.dx; - if (_dragPosition < 0) _dragPosition = 0; - if (_dragPosition > effectiveMaxWidth - sliderWidth) { - _dragPosition = effectiveMaxWidth - sliderWidth; - } - }); - }, - onHorizontalDragEnd: (details) { - if (_dragPosition >= effectiveMaxWidth - sliderWidth - 10) { - widget.onSlideComplete(); - } else { - setState(() => _dragPosition = 0); - } - }, - child: Container( - width: sliderWidth, - height: widget.height - 8, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - color: Theme.of(context).extension()!.titleColor, - ), - alignment: Alignment.center, - child: Icon(Icons.arrow_forward, - color: widget.currentTheme.type == ThemeType.bright ? Theme.of(context).extension()!.backgroundColor : Theme.of(context).extension()!.buttonColor), - ), + return accessible + ? PrimaryButton( + text: widget.accessibleNavigationModeButtonText, + color: Theme.of(context).primaryColor, + textColor: Colors.white, + onPressed: () => widget.onSlideComplete()) + : LayoutBuilder(builder: (context, constraints) { + final double maxWidth = constraints.maxWidth; + const double sideMargin = 4.0; + final double effectiveMaxWidth = maxWidth - 2 * sideMargin; + const double sliderWidth = 42.0; + + return Container( + height: widget.height, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), color: tileBackgroundColor), + child: Stack( + alignment: Alignment.centerLeft, + children: [ + Center( + child: Text(widget.buttonText, + style: TextStyle( + fontSize: 16, + fontFamily: 'Lato', + fontWeight: FontWeight.w600, + color: Theme.of(context).extension()!.titleColor))), + Positioned( + left: sideMargin + _dragPosition, + child: GestureDetector( + onHorizontalDragUpdate: (details) { + setState(() { + _dragPosition += details.delta.dx; + if (_dragPosition < 0) _dragPosition = 0; + if (_dragPosition > effectiveMaxWidth - sliderWidth) { + _dragPosition = effectiveMaxWidth - sliderWidth; + } + }); + }, + onHorizontalDragEnd: (details) { + if (_dragPosition >= effectiveMaxWidth - sliderWidth - 10) { + widget.onSlideComplete(); + } else { + setState(() => _dragPosition = 0); + } + }, + child: Container( + width: sliderWidth, + height: widget.height - 8, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Theme.of(context).extension()!.titleColor, + ), + alignment: Alignment.center, + child: Icon(Icons.arrow_forward, + color: widget.currentTheme.type == ThemeType.bright + ? Theme.of(context).extension()!.backgroundColor + : Theme.of(context).extension()!.buttonColor), + ), + ), + ) + ], ), - ) - ], - ), - ); - }); + ); + }); } } diff --git a/lib/store/dashboard/transaction_filter_store.dart b/lib/store/dashboard/transaction_filter_store.dart index f28f7e915..81888109c 100644 --- a/lib/store/dashboard/transaction_filter_store.dart +++ b/lib/store/dashboard/transaction_filter_store.dart @@ -1,6 +1,8 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/view_model/dashboard/action_list_item.dart'; import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:mobx/mobx.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cake_wallet/view_model/dashboard/transaction_list_item.dart'; @@ -10,11 +12,13 @@ part 'transaction_filter_store.g.dart'; class TransactionFilterStore = TransactionFilterStoreBase with _$TransactionFilterStore; abstract class TransactionFilterStoreBase with Store { - TransactionFilterStoreBase() + TransactionFilterStoreBase(this._appStore) : displayIncoming = true, displayOutgoing = true, displaySilentPayments = true; + final AppStore _appStore; + @observable bool displayIncoming; @@ -87,13 +91,15 @@ abstract class TransactionFilterStoreBase with Store { if (allowed && (!displayAll)) { if (item is TransactionListItem) { + final canShowSilentPayment = _appStore.wallet?.type == WalletType.bitcoin && + (bitcoin?.txIsReceivedSilentPayment(item.transaction) ?? false); + allowed = (displayOutgoing && item.transaction.direction == TransactionDirection.outgoing) || (displayIncoming && item.transaction.direction == TransactionDirection.incoming && - !(bitcoin?.txIsReceivedSilentPayment(item.transaction) ?? false)) || - (displaySilentPayments && - (bitcoin?.txIsReceivedSilentPayment(item.transaction) ?? false)); + !canShowSilentPayment) || + (displaySilentPayments && canShowSilentPayment); } else if (item is AnonpayTransactionListItem) { allowed = displayIncoming; } diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 5f6b415b9..1e32f3189 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import 'package:cake_wallet/core/utilities.dart'; import 'package:cake_wallet/decred/decred.dart'; import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/core/secure_storage.dart'; @@ -12,13 +13,13 @@ import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart'; import 'package:cake_wallet/entities/balance_display_mode.dart'; import 'package:cake_wallet/entities/cake_2fa_preset_options.dart'; import 'package:cake_wallet/entities/country.dart'; +import 'package:cake_wallet/entities/default_settings_migration.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/fiat_api_mode.dart'; import 'package:cake_wallet/entities/fiat_currency.dart'; import 'package:cake_wallet/entities/language_service.dart'; import 'package:cake_wallet/entities/pin_code_required_duration.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; -import 'package:cake_wallet/entities/provider_types.dart'; import 'package:cake_wallet/entities/secret_store_key.dart'; import 'package:cake_wallet/entities/seed_phrase_length.dart'; import 'package:cake_wallet/entities/seed_type.dart'; @@ -37,11 +38,8 @@ import 'package:cake_wallet/themes/theme_list.dart'; import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/utils/package_info.dart'; import 'package:cake_wallet/view_model/settings/sync_mode.dart'; -import 'package:cake_wallet/wallet_type_utils.dart'; -import 'package:cake_wallet/wownero/wownero.dart'; import 'package:cw_core/node.dart'; import 'package:cw_core/set_app_secure_native.dart'; -import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:device_info_plus/device_info_plus.dart'; @@ -312,7 +310,7 @@ abstract class SettingsStoreBase with Store { reaction((_) => disableTradeOption, (bool disableTradeOption) => sharedPreferences.setBool(PreferencesKey.disableTradeOption, disableTradeOption)); - + reaction( (_) => disableBulletin, (bool disableBulletin) => @@ -1038,7 +1036,6 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey); final bitcoinCashElectrumServerId = sharedPreferences.getInt(PreferencesKey.currentBitcoinCashNodeIdKey); - final havenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey); final ethereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey); final polygonNodeId = sharedPreferences.getInt(PreferencesKey.currentPolygonNodeIdKey); final nanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey); @@ -1048,20 +1045,35 @@ abstract class SettingsStoreBase with Store { final wowneroNodeId = sharedPreferences.getInt(PreferencesKey.currentWowneroNodeIdKey); final zanoNodeId = sharedPreferences.getInt(PreferencesKey.currentZanoNodeIdKey); final decredNodeId = sharedPreferences.getInt(PreferencesKey.currentDecredNodeIdKey); - final moneroNode = nodeSource.get(nodeId); - final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId); - final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId); - final havenNode = nodeSource.get(havenNodeId); - final ethereumNode = nodeSource.get(ethereumNodeId); - final polygonNode = nodeSource.get(polygonNodeId); - final bitcoinCashElectrumServer = nodeSource.get(bitcoinCashElectrumServerId); - final nanoNode = nodeSource.get(nanoNodeId); - final decredNode = nodeSource.get(decredNodeId); - final nanoPowNode = powNodeSource.get(nanoPowNodeId); - final solanaNode = nodeSource.get(solanaNodeId); - final tronNode = nodeSource.get(tronNodeId); - final wowneroNode = nodeSource.get(wowneroNodeId); - final zanoNode = nodeSource.get(zanoNodeId); + + /// get the selected node, if null, then use the default + final moneroNode = nodeSource.get(nodeId) ?? + nodeSource.values.firstWhereOrNull((e) => e.uriRaw == newCakeWalletMoneroUri); + final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId) ?? + nodeSource.values.firstWhereOrNull((e) => e.uriRaw == newCakeWalletBitcoinUri); + final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId) ?? + nodeSource.values.firstWhereOrNull((e) => e.uriRaw == cakeWalletLitecoinElectrumUri); + final ethereumNode = nodeSource.get(ethereumNodeId) ?? + nodeSource.values.firstWhereOrNull((e) => e.uriRaw == ethereumDefaultNodeUri); + final polygonNode = nodeSource.get(polygonNodeId) ?? + nodeSource.values.firstWhereOrNull((e) => e.uriRaw == polygonDefaultNodeUri); + final bitcoinCashElectrumServer = nodeSource.get(bitcoinCashElectrumServerId) ?? + nodeSource.values.firstWhereOrNull((e) => e.uriRaw == cakeWalletBitcoinCashDefaultNodeUri); + final nanoNode = nodeSource.get(nanoNodeId) ?? + nodeSource.values.firstWhereOrNull((e) => e.uriRaw == nanoDefaultNodeUri); + final decredNode = nodeSource.get(decredNodeId) ?? + nodeSource.values.firstWhereOrNull((e) => e.uriRaw == decredDefaultUri); + final nanoPowNode = powNodeSource.get(nanoPowNodeId) ?? + nodeSource.values.firstWhereOrNull((e) => e.uriRaw == nanoDefaultPowNodeUri); + final solanaNode = nodeSource.get(solanaNodeId) ?? + nodeSource.values.firstWhereOrNull((e) => e.uriRaw == solanaDefaultNodeUri); + final tronNode = nodeSource.get(tronNodeId) ?? + nodeSource.values.firstWhereOrNull((e) => e.uriRaw == tronDefaultNodeUri); + final wowneroNode = nodeSource.get(wowneroNodeId) ?? + nodeSource.values.firstWhereOrNull((e) => e.uriRaw == wowneroDefaultNodeUri); + final zanoNode = nodeSource.get(zanoNodeId) ?? + nodeSource.values.firstWhereOrNull((e) => e.uriRaw == zanoDefaultNodeUri); + final packageInfo = await PackageInfo.fromPlatform(); final deviceName = await _getDeviceName() ?? ''; final shouldShowYatPopup = sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? true; @@ -1107,10 +1119,6 @@ abstract class SettingsStoreBase with Store { nodes[WalletType.litecoin] = litecoinElectrumServer; } - if (havenNode != null) { - nodes[WalletType.haven] = havenNode; - } - if (ethereumNode != null) { nodes[WalletType.ethereum] = ethereumNode; } diff --git a/lib/tron/cw_tron.dart b/lib/tron/cw_tron.dart index 2726e873d..8c29dab58 100644 --- a/lib/tron/cw_tron.dart +++ b/lib/tron/cw_tron.dart @@ -84,6 +84,7 @@ class CWTron extends Tron { decimal: token.decimals, enabled: token.enabled, iconPath: token.iconPath, + isPotentialScam: token.isPotentialScam, ); await (wallet as TronWallet).addTronToken(tronToken); } diff --git a/lib/utils/address_formatter.dart b/lib/utils/address_formatter.dart new file mode 100644 index 000000000..f2083c772 --- /dev/null +++ b/lib/utils/address_formatter.dart @@ -0,0 +1,161 @@ +import 'package:cw_core/wallet_type.dart'; +import 'package:flutter/widgets.dart'; +import 'dart:math' as math; + +class AddressFormatter { + static Widget buildSegmentedAddress({ + required String address, + WalletType? walletType, + required TextStyle evenTextStyle, + TextStyle? oddTextStyle, + TextAlign? textAlign, + bool shouldTruncate = false, + }) { + + final cleanAddress = address.replaceAll('bitcoincash:', ''); + final isMWEB = address.startsWith('ltcmweb'); + final chunkSize = walletType != null ? _getChunkSize(walletType) : 4; + + if (shouldTruncate) { + return _buildTruncatedAddress( + address: cleanAddress, + isMWEB: isMWEB, + chunkSize: chunkSize, + evenTextStyle: evenTextStyle, + oddTextStyle: oddTextStyle ?? evenTextStyle.copyWith(color: evenTextStyle.color!.withAlpha(150)), + textAlign: textAlign, + ); + } else { + return _buildFullSegmentedAddress( + address: cleanAddress, + isMWEB: isMWEB, + chunkSize: chunkSize, + evenTextStyle: evenTextStyle, + oddTextStyle: oddTextStyle ?? evenTextStyle.copyWith(color: evenTextStyle.color!.withAlpha(128)), + textAlign: textAlign, + ); + } + } + + static Widget _buildFullSegmentedAddress({ + required String address, + required bool isMWEB, + required int chunkSize, + required TextStyle evenTextStyle, + required TextStyle oddTextStyle, + TextAlign? textAlign, + }) { + + final chunks = []; + + if (isMWEB) { + const mwebDisplayPrefix = 'ltcmweb'; + chunks.add(mwebDisplayPrefix); + final startIndex = mwebDisplayPrefix.length; + for (int i = startIndex; i < address.length; i += chunkSize) { + final chunk = address.substring( + i, + math.min(i + chunkSize, address.length), + ); + chunks.add(chunk); + } + } else { + for (int i = 0; i < address.length; i += chunkSize) { + final chunk = address.substring( + i, + math.min(i + chunkSize, address.length), + ); + chunks.add(chunk); + } + } + + final spans = []; + for (int i = 0; i < chunks.length; i++) { + final style = (i % 2 == 0) ? evenTextStyle : oddTextStyle; + spans.add(TextSpan(text: '${chunks[i]} ', style: style)); + } + + return RichText( + text: TextSpan(children: spans), + textAlign: textAlign ?? TextAlign.start, + overflow: TextOverflow.visible, + ); + } + + static Widget _buildTruncatedAddress({ + required String address, + required bool isMWEB, + required int chunkSize, + required TextStyle evenTextStyle, + required TextStyle oddTextStyle, + TextAlign? textAlign, + }) { + + if (isMWEB) { + const fixedPrefix = 'ltcmweb'; + final secondChunkStart = fixedPrefix.length; + const chunkSize = 4; + final secondChunk = address.substring( + secondChunkStart, + math.min(secondChunkStart + chunkSize, address.length), + ); + final lastChunk = address.substring(address.length - chunkSize); + + final spans = [ + TextSpan(text: '$fixedPrefix ', style: evenTextStyle), + TextSpan(text: '$secondChunk ', style: oddTextStyle), + TextSpan(text: '... ', style: oddTextStyle), + TextSpan(text: lastChunk, style: evenTextStyle), + ]; + + return RichText( + text: TextSpan(children: spans), + textAlign: textAlign ?? TextAlign.start, + overflow: TextOverflow.visible, + ); + } else { + final int digitCount = chunkSize; + + if (address.length <= 2 * digitCount) { + return _buildFullSegmentedAddress( + address: address, + isMWEB: isMWEB, + chunkSize: chunkSize, + evenTextStyle: evenTextStyle, + oddTextStyle: oddTextStyle, + textAlign: textAlign, + ); + } + + final String firstPart = address.substring(0, digitCount); + final String secondPart = + address.substring(digitCount, digitCount * 2); + final String lastPart = + address.substring(address.length - digitCount); + + final spans = [ + TextSpan(text: '$firstPart ', style: evenTextStyle), + TextSpan(text: '$secondPart ', style: oddTextStyle), + TextSpan(text: '... ', style: oddTextStyle), + TextSpan(text: lastPart, style: evenTextStyle), + ]; + + return RichText( + text: TextSpan(children: spans), + textAlign: textAlign ?? TextAlign.start, + overflow: TextOverflow.visible, + ); + } + } + + static int _getChunkSize(WalletType walletType) { + switch (walletType) { + case WalletType.monero: + case WalletType.wownero: + case WalletType.zano: + return 6; + default: + return 4; + } + } +} \ No newline at end of file diff --git a/lib/utils/feature_flag.dart b/lib/utils/feature_flag.dart index 6e829d474..c968c5d75 100644 --- a/lib/utils/feature_flag.dart +++ b/lib/utils/feature_flag.dart @@ -6,4 +6,5 @@ class FeatureFlag { static const bool isInAppTorEnabled = false; static const bool isBackgroundSyncEnabled = true; static const int verificationWordsCount = kDebugMode ? 0 : 2; + static const bool hasDevOptions = bool.fromEnvironment('hasDevOptions', defaultValue: kDebugMode); } \ No newline at end of file diff --git a/lib/view_model/anon_invoice_page_view_model.dart b/lib/view_model/anon_invoice_page_view_model.dart index 39992dca7..05467bd71 100644 --- a/lib/view_model/anon_invoice_page_view_model.dart +++ b/lib/view_model/anon_invoice_page_view_model.dart @@ -107,20 +107,24 @@ abstract class AnonInvoicePageViewModelBase with Store { return; } } - final result = await anonPayApi.createInvoice(AnonPayRequest( - cryptoCurrency: cryptoCurrency, - address: address, - amount: amount.isEmpty ? null : amount, - description: description, - email: receipientEmail, - name: receipientName, - fiatEquivalent: - selectedCurrency is FiatCurrency ? (selectedCurrency as FiatCurrency).raw : null, - )); + try { + final result = await anonPayApi.createInvoice(AnonPayRequest( + cryptoCurrency: cryptoCurrency, + address: address, + amount: amount.isEmpty ? null : amount, + description: description, + email: receipientEmail, + name: receipientName, + fiatEquivalent: + selectedCurrency is FiatCurrency ? (selectedCurrency as FiatCurrency).raw : null, + )); - _anonpayInvoiceInfoSource.add(result); + _anonpayInvoiceInfoSource.add(result); - state = ExecutedSuccessfullyState(payload: result); + state = ExecutedSuccessfullyState(payload: result); + } catch (e) { + state = FailureState(e.toString()); + } } @action @@ -156,12 +160,16 @@ abstract class AnonInvoicePageViewModelBase with Store { } Future _fetchLimits() async { - final limit = await anonPayApi.fetchLimits( - cryptoCurrency: cryptoCurrency, - fiatCurrency: selectedCurrency is FiatCurrency ? selectedCurrency as FiatCurrency : null, - ); - minimum = limit.min; - maximum = limit.max != null ? limit.max! / 4 : null; + try { + final limit = await anonPayApi.fetchLimits( + cryptoCurrency: cryptoCurrency, + fiatCurrency: selectedCurrency is FiatCurrency ? selectedCurrency as FiatCurrency : null, + ); + minimum = limit.min; + maximum = limit.max != null ? limit.max! / 4 : null; + } catch (e) { + state = FailureState(e.toString()); + } } @computed diff --git a/lib/view_model/buy/buy_sell_view_model.dart b/lib/view_model/buy/buy_sell_view_model.dart index bf05109ad..df4b838f2 100644 --- a/lib/view_model/buy/buy_sell_view_model.dart +++ b/lib/view_model/buy/buy_sell_view_model.dart @@ -15,7 +15,6 @@ import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cw_core/crypto_currency.dart'; -import 'package:cw_core/currency_for_wallet_type.dart'; import 'package:flutter/cupertino.dart'; import 'package:intl/intl.dart'; import 'package:mobx/mobx.dart'; @@ -61,21 +60,17 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S late Timer bestRateSync; List get availableBuyProviders { - final providerTypes = ProvidersHelper.getAvailableBuyProviderTypes( - walletTypeForCurrency(cryptoCurrency) ?? wallet.type); + final providerTypes = ProvidersHelper.getAvailableBuyProviderTypes(); return providerTypes .map((type) => ProvidersHelper.getProviderByType(type)) - .where((provider) => provider != null) .cast() .toList(); } List get availableSellProviders { - final providerTypes = ProvidersHelper.getAvailableSellProviderTypes( - walletTypeForCurrency(cryptoCurrency) ?? wallet.type); + final providerTypes = ProvidersHelper.getAvailableSellProviderTypes(); return providerTypes .map((type) => ProvidersHelper.getProviderByType(type)) - .where((provider) => provider != null) .cast() .toList(); } diff --git a/lib/view_model/dashboard/balance_view_model.dart b/lib/view_model/dashboard/balance_view_model.dart index ef5676138..ea9272a5c 100644 --- a/lib/view_model/dashboard/balance_view_model.dart +++ b/lib/view_model/dashboard/balance_view_model.dart @@ -229,7 +229,7 @@ abstract class BalanceViewModelBase with Store { formattedAssetTitle: _formatterAsset(key))); } final fiatCurrency = settingsStore.fiatCurrency; - final price = fiatConvertationStore.prices[key] ?? 0; + final price = key.isPotentialScam ? 0.0 : fiatConvertationStore.prices[key] ?? 0; // if (price == null) { // throw Exception('Price is null for: $key'); diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index f0248e520..a61f939f5 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -161,8 +161,8 @@ abstract class DashboardViewModelBase with Store { FilterItem( value: () => tradeFilterStore.displaySwapTrade, caption: ExchangeProviderDescription.swapTrade.title, - onChanged: () => tradeFilterStore - .toggleDisplayExchange(ExchangeProviderDescription.swapTrade)), + onChanged: () => + tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.swapTrade)), ] }, subname = '', @@ -271,32 +271,9 @@ abstract class DashboardViewModelBase with Store { }); _transactionDisposer?.reaction.dispose(); - _transactionDisposer = reaction( - (_) => appStore.wallet!.transactionHistory.transactions.values.toList(), - (List txs) { - - transactions.clear(); - - transactions.addAll( - txs.where((tx) { - if (wallet.type == WalletType.monero) { - return monero!.getTransactionInfoAccountId(tx) == monero!.getCurrentAccount(wallet).id; - } - if (wallet.type == WalletType.wownero) { - return wow.wownero!.getTransactionInfoAccountId(tx) == wow.wownero!.getCurrentAccount(wallet).id; - } - return true; - }).map( - (tx) => TransactionListItem( - transaction: tx, - balanceViewModel: balanceViewModel, - settingsStore: appStore.settingsStore, - key: ValueKey('${wallet.type.name}_transaction_history_item_${tx.id}_key'), - ), - ), - ); - } + (_) => appStore.wallet!.transactionHistory.transactions.length, + _transactionDisposerCallback, ); if (hasSilentPayments) { @@ -311,6 +288,48 @@ abstract class DashboardViewModelBase with Store { reaction((_) => settingsStore.mwebAlwaysScan, (bool value) => _checkMweb()); } + bool _isTransactionDisposerCallbackRunning = false; + + void _transactionDisposerCallback(int _) async { + // Simple check to prevent the callback from being called multiple times in the same frame + if (_isTransactionDisposerCallbackRunning) return; + _isTransactionDisposerCallbackRunning = true; + await Future.delayed(Duration.zero); + + try { + final currentAccountId = wallet.type == WalletType.monero + ? monero!.getCurrentAccount(wallet).id + : wallet.type == WalletType.wownero + ? wow.wownero!.getCurrentAccount(wallet).id + : null; + final List relevantTxs = []; + + for (final tx in appStore.wallet!.transactionHistory.transactions.values) { + bool isRelevant = true; + if (wallet.type == WalletType.monero) { + isRelevant = monero!.getTransactionInfoAccountId(tx) == currentAccountId; + } else if (wallet.type == WalletType.wownero) { + isRelevant = wow.wownero!.getTransactionInfoAccountId(tx) == currentAccountId; + } + + if (isRelevant) { + relevantTxs.add(tx); + } + } + // printV("Transaction disposer callback (relevantTxs: ${relevantTxs.length} current: ${transactions.length})"); + + transactions.clear(); + transactions.addAll(relevantTxs.map((tx) => TransactionListItem( + transaction: tx, + balanceViewModel: balanceViewModel, + settingsStore: appStore.settingsStore, + key: ValueKey('${wallet.type.name}_transaction_history_item_${tx.id}_key'), + ))); + } finally { + _isTransactionDisposerCallbackRunning = false; + } + } + void _checkMweb() { if (hasMweb) { mwebEnabled = bitcoin!.getMwebEnabled(wallet); @@ -453,7 +472,8 @@ abstract class DashboardViewModelBase with Store { // to not cause work duplication, this will do the job as well, it will be slightly less precise // about what happened - but still enough. // if (keys['privateSpendKey'] == List.generate(64, (index) => "0").join("")) "Private spend key is 0", - if (keys['privateViewKey'] == List.generate(64, (index) => "0").join("") && !wallet.isHardwareWallet) + if (keys['privateViewKey'] == List.generate(64, (index) => "0").join("") && + !wallet.isHardwareWallet) "private view key is 0", // if (keys['publicSpendKey'] == List.generate(64, (index) => "0").join("")) "public spend key is 0", if (keys['publicViewKey'] == List.generate(64, (index) => "0").join("")) @@ -554,7 +574,8 @@ abstract class DashboardViewModelBase with Store { disableBackgroundSync(); return; } - final resp = await FlutterDaemon().startBackgroundSync(settingsStore.currentSyncMode.frequency.inMinutes); + final resp = await FlutterDaemon() + .startBackgroundSync(settingsStore.currentSyncMode.frequency.inMinutes); printV("Background sync enabled: $resp"); backgroundSyncEnabled = true; } @@ -578,8 +599,7 @@ abstract class DashboardViewModelBase with Store { spread = 0; else if (settingsStore.currentTheme.type == ThemeType.dark) spread = 0; - else if (settingsStore.currentTheme.type == ThemeType.oled) - spread = 0; + else if (settingsStore.currentTheme.type == ThemeType.oled) spread = 0; return spread; } @@ -592,8 +612,7 @@ abstract class DashboardViewModelBase with Store { blur = 0; else if (settingsStore.currentTheme.type == ThemeType.dark) blur = 0; - else if (settingsStore.currentTheme.type == ThemeType.oled) - blur = 0; + else if (settingsStore.currentTheme.type == ThemeType.oled) blur = 0; return blur; } @@ -789,32 +808,8 @@ abstract class DashboardViewModelBase with Store { _transactionDisposer?.reaction.dispose(); - _transactionDisposer = reaction( - (_) => appStore.wallet!.transactionHistory.transactions.values.toList(), - (List txs) { - - transactions.clear(); - - transactions.addAll( - txs.where((tx) { - if (wallet.type == WalletType.monero) { - return monero!.getTransactionInfoAccountId(tx) == monero!.getCurrentAccount(wallet).id; - } - if (wallet.type == WalletType.wownero) { - return wow.wownero!.getTransactionInfoAccountId(tx) == wow.wownero!.getCurrentAccount(wallet).id; - } - return true; - }).map( - (tx) => TransactionListItem( - transaction: tx, - balanceViewModel: balanceViewModel, - settingsStore: appStore.settingsStore, - key: ValueKey('${wallet.type.name}_transaction_history_item_${tx.id}_key'), - ), - ), - ); - } - ); + _transactionDisposer = reaction((_) => appStore.wallet!.transactionHistory.transactions.length, + _transactionDisposerCallback); } @action diff --git a/lib/view_model/dashboard/home_settings_view_model.dart b/lib/view_model/dashboard/home_settings_view_model.dart index bd56e57a8..099cce92d 100644 --- a/lib/view_model/dashboard/home_settings_view_model.dart +++ b/lib/view_model/dashboard/home_settings_view_model.dart @@ -83,6 +83,7 @@ abstract class HomeSettingsViewModelBase with Store { decimal: token.decimals, contractAddress: contractAddress, iconPath: token.iconPath, + isPotentialScam: token.isPotentialScam, ); await ethereum!.addErc20Token(_balanceViewModel.wallet, erc20token); @@ -95,6 +96,7 @@ abstract class HomeSettingsViewModelBase with Store { decimal: token.decimals, contractAddress: contractAddress, iconPath: token.iconPath, + isPotentialScam: token.isPotentialScam, ); await polygon!.addErc20Token(_balanceViewModel.wallet, polygonToken); } @@ -220,7 +222,8 @@ abstract class HomeSettingsViewModelBase with Store { } // check if the contractAddress is in the defaultTokenAddresses - bool isInWhitelist = defaultTokenAddresses.any((element) => element == contractAddress); + bool isInWhitelist = defaultTokenAddresses + .any((element) => element.toLowerCase() == contractAddress.toLowerCase()); return isInWhitelist; } diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index e8f8afc5d..b41a7dcf5 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -23,8 +23,6 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'package:cake_wallet/.secrets.g.dart' as secrets; import 'package:cake_wallet/bitcoin/bitcoin.dart'; -import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; -import 'package:cake_wallet/decred/decred.dart'; import 'package:cake_wallet/core/wallet_change_listener_view_model.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; @@ -39,7 +37,6 @@ import 'package:cake_wallet/exchange/provider/exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/exolix_exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/swaptrade_exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/sideshift_exchange_provider.dart'; -import 'package:cake_wallet/exchange/provider/simpleswap_exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart'; import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart'; import 'package:cake_wallet/exchange/trade.dart'; @@ -181,7 +178,6 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with List get _allProviders => [ ChangeNowExchangeProvider(settingsStore: _settingsStore), SideShiftExchangeProvider(), - SimpleSwapExchangeProvider(), ThorChainExchangeProvider(tradesStore: trades), ChainflipExchangeProvider(tradesStore: trades), if (FeatureFlag.isExolixEnabled) ExolixExchangeProvider(), diff --git a/lib/view_model/hardware_wallet/ledger_view_model.dart b/lib/view_model/hardware_wallet/ledger_view_model.dart index 4f80aa698..f555da0cf 100644 --- a/lib/view_model/hardware_wallet/ledger_view_model.dart +++ b/lib/view_model/hardware_wallet/ledger_view_model.dart @@ -137,7 +137,9 @@ abstract class LedgerViewModelBase with Store { allowChangeWallet: true, isReconnect: true, onConnectDevice: (context, ledgerVM) async { - Navigator.of(context).pop(); + if (context.mounted) { + Navigator.of(context).pop(); + } }, ), ); diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 4ee9cb86f..2244c9be3 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -1,53 +1,57 @@ +import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import 'package:cake_wallet/core/address_validator.dart'; +import 'package:cake_wallet/core/amount_validator.dart'; +import 'package:cake_wallet/core/execution_state.dart'; +import 'package:cake_wallet/core/open_crypto_pay/models.dart'; +import 'package:cake_wallet/core/open_crypto_pay/open_cryptopay_service.dart'; +import 'package:cake_wallet/core/validator.dart'; +import 'package:cake_wallet/core/wallet_change_listener_view_model.dart'; +import 'package:cake_wallet/decred/decred.dart'; +import 'package:cake_wallet/entities/calculate_fiat_amount.dart'; import 'package:cake_wallet/entities/contact.dart'; +import 'package:cake_wallet/entities/contact_record.dart'; import 'package:cake_wallet/entities/evm_transaction_error_fees_handler.dart'; +import 'package:cake_wallet/entities/fiat_currency.dart'; +import 'package:cake_wallet/entities/parsed_address.dart'; +import 'package:cake_wallet/entities/template.dart'; import 'package:cake_wallet/entities/transaction_description.dart'; +import 'package:cake_wallet/entities/wallet_contact.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/exchange/provider/exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/nano/nano.dart'; -import 'package:cake_wallet/decred/decred.dart'; -import 'package:cake_wallet/core/wallet_change_listener_view_model.dart'; -import 'package:cake_wallet/entities/contact_record.dart'; -import 'package:cake_wallet/entities/wallet_contact.dart'; import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/solana/solana.dart'; import 'package:cake_wallet/store/app_store.dart'; +import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart'; +import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/tron/tron.dart'; +import 'package:cake_wallet/utils/payment_request.dart'; import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart'; import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart'; import 'package:cake_wallet/view_model/send/fees_view_model.dart'; +import 'package:cake_wallet/view_model/send/output.dart'; +import 'package:cake_wallet/view_model/send/send_template_view_model.dart'; +import 'package:cake_wallet/view_model/send/send_view_model_state.dart'; import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart'; import 'package:cake_wallet/wownero/wownero.dart'; import 'package:cake_wallet/zano/zano.dart'; +import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/exceptions.dart'; +import 'package:cw_core/pending_transaction.dart'; +import 'package:cw_core/sync_status.dart'; import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/unspent_coin_type.dart'; -import 'package:cake_wallet/view_model/send/output.dart'; -import 'package:cake_wallet/view_model/send/send_template_view_model.dart'; +import 'package:cw_core/utils/print_verbose.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; -import 'package:cake_wallet/entities/template.dart'; -import 'package:cake_wallet/core/address_validator.dart'; -import 'package:cake_wallet/core/amount_validator.dart'; -import 'package:cw_core/pending_transaction.dart'; -import 'package:cake_wallet/core/validator.dart'; -import 'package:cake_wallet/core/execution_state.dart'; -import 'package:cake_wallet/monero/monero.dart'; -import 'package:cw_core/sync_status.dart'; -import 'package:cw_core/crypto_currency.dart'; -import 'package:cake_wallet/entities/fiat_currency.dart'; -import 'package:cake_wallet/entities/calculate_fiat_amount.dart'; -import 'package:cw_core/wallet_type.dart'; -import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart'; -import 'package:cake_wallet/store/settings_store.dart'; -import 'package:cake_wallet/view_model/send/send_view_model_state.dart'; -import 'package:cake_wallet/entities/parsed_address.dart'; -import 'package:cake_wallet/bitcoin/bitcoin.dart'; -import 'package:cake_wallet/generated/i18n.dart'; part 'send_view_model.g.dart'; @@ -217,9 +221,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor @computed String get balance { - if (coinTypeToSpendFrom == UnspentCoinType.mweb) { + if (walletType == WalletType.litecoin && coinTypeToSpendFrom == UnspentCoinType.mweb) { return balanceViewModel.balances.values.first.secondAvailableBalance; - } else if (coinTypeToSpendFrom == UnspentCoinType.nonMweb) { + } else if (walletType == WalletType.litecoin && + coinTypeToSpendFrom == UnspentCoinType.nonMweb) { return balanceViewModel.balances.values.first.availableBalance; } return wallet.balance[selectedCryptoCurrency]!.formattedFullAvailableBalance; @@ -245,7 +250,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor } @computed - String get sendingBalance { + Future get sendingBalance async { // only for electrum, monero, wownero, decred wallets atm: switch (wallet.type) { case WalletType.bitcoin: @@ -255,7 +260,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor case WalletType.wownero: case WalletType.decred: return wallet.formatCryptoAmount( - unspentCoinsListViewModel.getSendingBalance(coinTypeToSpendFrom).toString()); + (await unspentCoinsListViewModel.getSendingBalance(coinTypeToSpendFrom)).toString()); default: return balance; } @@ -390,10 +395,55 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor return conditionsList.contains(true); } + final _ocpService = OpenCryptoPayService(); + + @observable + OpenCryptoPayRequest? ocpRequest; + + @action + Future dismissTransaction() async { + state = InitialExecutionState(); + if (ocpRequest != null) { + clearOutputs(); + _ocpService.cancelOpenCryptoPayRequest(ocpRequest!); + ocpRequest = null; + } + } + + @action + Future createOpenCryptoPayTransaction(String uri) async { + state = IsExecutingState(); + + try { + final originalOCPRequest = await _ocpService.getOpenCryptoPayInvoice(uri.toString()); + final paymentUri = await _ocpService.getOpenCryptoPayAddress( + originalOCPRequest, + selectedCryptoCurrency, + ); + + ocpRequest = originalOCPRequest; + + final paymentRequest = PaymentRequest.fromUri(paymentUri); + clearOutputs(); + + outputs.first.address = paymentRequest.address; + outputs.first.parsedAddress = + ParsedAddress(addresses: [paymentRequest.address], name: ocpRequest!.receiverName); + outputs.first.setCryptoAmount(paymentRequest.amount); + outputs.first.note = ocpRequest!.receiverName; + + return createTransaction(); + } catch (e) { + printV(e); + state = FailureState(translateErrorMessage(e, walletType, currency)); + return null; + } + } + @action Future createTransaction({ExchangeProvider? provider}) async { try { - state = IsExecutingState(); + if (!(state is IsExecutingState)) state = IsExecutingState(); if (wallet.isHardwareWallet) state = IsAwaitingDeviceResponseState(); @@ -475,6 +525,24 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor throw Exception("Pending transaction doesn't exist. It should not be happened."); } + if (ocpRequest != null) { + state = TransactionCommitting(); + if (selectedCryptoCurrency == CryptoCurrency.xmr) { + await pendingTransaction!.commit(); + } + + await _ocpService.commitOpenCryptoPayRequest( + pendingTransaction!.hex, + txId: pendingTransaction!.id, + request: ocpRequest!, + asset: selectedCryptoCurrency, + ); + + state = TransactionCommitted(); + + return; + } + String address = outputs.fold('', (acc, value) { return value.isParsedAddress ? '$acc${value.address}\n${value.extractedAddress}\n\n' @@ -510,9 +578,14 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor final descriptionKey = '${pendingTransaction!.id}_${wallet.walletAddresses.primaryAddress}'; _settingsStore.shouldSaveRecipientAddress ? await transactionDescriptionBox.add(TransactionDescription( - id: descriptionKey, recipientAddress: address, transactionNote: note)) - : await transactionDescriptionBox - .add(TransactionDescription(id: descriptionKey, transactionNote: note)); + id: descriptionKey, + recipientAddress: address, + transactionNote: note, + )) + : await transactionDescriptionBox.add(TransactionDescription( + id: descriptionKey, + transactionNote: note, + )); } state = TransactionCommitted(); @@ -665,7 +738,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor } if (error is CreateAssociatedTokenAccountException) { - return "${S.current.solana_create_associated_token_account_exception}\n\n${error.errorMessage}"; + return "${S.current.solana_create_associated_token_account_exception} ${S.current.added_message_for_ata_error}\n\n${error.errorMessage}"; } if (error is SignSPLTokenTransactionRentException) { @@ -693,7 +766,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor final parsedErrorMessageResult = EVMTransactionErrorFeesHandler.parseEthereumFeesErrorMessage( errorMessage, - _fiatConversationStore.prices[currency]!, + _fiatConversationStore.prices[currency] ?? 0.0, ); if (parsedErrorMessageResult.error != null) { diff --git a/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart b/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart index a54e2be49..bae4677cf 100644 --- a/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart +++ b/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart @@ -149,12 +149,15 @@ abstract class UnspentCoinsListViewModelBase with Store { } @action - int getSendingBalance(UnspentCoinType overrideCoinTypeToSpendFrom) { + Future getSendingBalance(UnspentCoinType overrideCoinTypeToSpendFrom) async { // return items.where((element) => element.isSending).fold(0, (previousValue, element) => previousValue + element.value); // go through all unspent coins and add up the value minus frozen and non sending: int total = 0; - + await _updateUnspents(); + Set seen = {}; for (final item in _getSpecificUnspents(overrideCoinTypeToSpendFrom)) { + if (seen.contains(item.toString())) continue; + seen.add(item.toString()); if (item.isFrozen || !item.isSending) continue; total += item.value; } @@ -163,8 +166,6 @@ abstract class UnspentCoinsListViewModelBase with Store { @action void _updateUnspentCoinsInfo() { - items.clear(); - final unspents = _getUnspents() .map((elem) { try { @@ -198,7 +199,7 @@ abstract class UnspentCoinsListViewModelBase with Store { .toList(); unspents.sort((a, b) => b.value.compareTo(a.value)); - + items.clear(); items.addAll(unspents); } diff --git a/lib/view_model/wallet_groups_display_view_model.dart b/lib/view_model/wallet_groups_display_view_model.dart index 056d713aa..ef5b245ba 100644 --- a/lib/view_model/wallet_groups_display_view_model.dart +++ b/lib/view_model/wallet_groups_display_view_model.dart @@ -6,6 +6,7 @@ import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart'; import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart'; import 'package:cake_wallet/wallet_types.g.dart'; +import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:mobx/mobx.dart'; @@ -127,12 +128,16 @@ abstract class WalletGroupsDisplayViewModelBase with Store { bool isNonSeedWallet = wallet.isNonSeedWallet; + bool isNotMoneroBip39Wallet = wallet.type == WalletType.monero && + wallet.derivationInfo?.derivationType != DerivationType.bip39; + // Exclude if any of these conditions are true return isNonBIP39Wallet || isNanoDerivationType || isElectrumDerivationType || isSameTypeAsSelectedWallet || - isNonSeedWallet; + isNonSeedWallet || + isNotMoneroBip39Wallet; }); if (shouldExcludeGroup) continue; diff --git a/lib/view_model/wallet_keys_view_model.dart b/lib/view_model/wallet_keys_view_model.dart index a3e0e41c3..3382e4144 100644 --- a/lib/view_model/wallet_keys_view_model.dart +++ b/lib/view_model/wallet_keys_view_model.dart @@ -63,14 +63,15 @@ abstract class WalletKeysViewModelBase with Store { String get seed => _wallet.seed != null ? _wallet.seed! : ''; bool get isLegacySeedOnly => - (_wallet.type == WalletType.monero || _wallet.type == WalletType.wownero) && + [WalletType.monero, WalletType.wownero].contains(_wallet.type) && _wallet.seed != null && - !Polyseed.isValidSeed(_wallet.seed!); + !(Polyseed.isValidSeed(_wallet.seed!) || + _wallet.seed!.split(' ').length == 12); String get legacySeed { if ((_wallet.type == WalletType.monero || _wallet.type == WalletType.wownero) && _wallet.seed != null && - Polyseed.isValidSeed(_wallet.seed!)) { + (Polyseed.isValidSeed(_wallet.seed!) || _wallet.seed!.split(' ').length == 12)) { final langName = PolyseedLang.getByPhrase(_wallet.seed!).nameEnglish; if (_wallet.type == WalletType.monero) { diff --git a/lib/view_model/wallet_list/wallet_list_view_model.dart b/lib/view_model/wallet_list/wallet_list_view_model.dart index 8c36f8412..96662e0b6 100644 --- a/lib/view_model/wallet_list/wallet_list_view_model.dart +++ b/lib/view_model/wallet_list/wallet_list_view_model.dart @@ -3,6 +3,7 @@ import 'package:cake_wallet/entities/wallet_group.dart'; import 'package:cake_wallet/entities/wallet_list_order_types.dart'; import 'package:cake_wallet/entities/wallet_manager.dart'; import 'package:cake_wallet/reactions/bip39_wallet_utils.dart'; +import 'package:cw_core/utils/print_verbose.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/store/app_store.dart'; @@ -75,6 +76,9 @@ abstract class WalletListViewModelBase with Store { @action Future loadWallet(WalletListItem walletItem) async { + if (walletItem.type == WalletType.haven) { + return; + } // bool switchingToSameWalletType = walletItem.type == _appStore.wallet?.type; // await _appStore.wallet?.close(shouldCleanup: !switchingToSameWalletType); final wallet = await _walletLoadingService.load(walletItem.type, walletItem.name); @@ -106,45 +110,7 @@ abstract class WalletListViewModelBase with Store { continue; } - // Identify wallets that should be moved to singleWalletsList using the filters: the type/derivation - final excludedWallets = []; - - for (var wallet in group.wallets) { - // Check for non-BIP39 wallet types - final isNonBIP39 = !isBIP39Wallet(wallet.type); - - // Check for nano derivation type - final isNanoDerivation = wallet.type == WalletType.nano && - wallet.derivationInfo?.derivationType == DerivationType.nano; - - // Check for electrum derivation type - final isElectrumDerivation = - (wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin) && - wallet.derivationInfo?.derivationType == DerivationType.electrum; - - if (isNonBIP39 || isNanoDerivation || isElectrumDerivation) { - excludedWallets.add(wallet); - } - } - - // Add excluded wallets to singleWalletsList - for (var excludedWallet in excludedWallets) { - singleWalletsList.add(convertWalletInfoToWalletListItem(excludedWallet)); - } - - // Remove excluded wallets from the group's wallets to avoid duplication - group.wallets.removeWhere((wallet) { - return excludedWallets.any((excluded) => excluded.address == wallet.address); - }); - - // Check if the group has more than one wallet after the excluded wallets are removed. - if (group.wallets.length > 1) { - //Add the entire group to the multi wallet group list since its still a multi wallet - multiWalletGroups.add(group); - } else if (group.wallets.length == 1) { - // Add the group to the wallet left to the single wallets list - singleWalletsList.add(convertWalletInfoToWalletListItem(group.wallets.first)); - } + multiWalletGroups.add(group); } } @@ -250,7 +216,6 @@ abstract class WalletListViewModelBase with Store { await sortGroupByType(); break; case FilterListOrderType.Custom: - default: await reorderAccordingToWalletList(); break; } diff --git a/lib/view_model/wallet_new_vm.dart b/lib/view_model/wallet_new_vm.dart index 481592732..2e46996c1 100644 --- a/lib/view_model/wallet_new_vm.dart +++ b/lib/view_model/wallet_new_vm.dart @@ -50,40 +50,12 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { bool get hasLanguageSelector => [WalletType.monero, WalletType.haven, WalletType.wownero].contains(type); - int get seedPhraseWordsLength { - switch (type) { - case WalletType.monero: - case WalletType.wownero: - return advancedPrivacySettingsViewModel.isPolySeed ? 16 : 25; - case WalletType.tron: - case WalletType.solana: - case WalletType.polygon: - case WalletType.ethereum: - case WalletType.bitcoinCash: - return advancedPrivacySettingsViewModel.seedPhraseLength.value; - case WalletType.bitcoin: - case WalletType.litecoin: - return seedSettingsViewModel.bitcoinSeedType == BitcoinSeedType.bip39 - ? advancedPrivacySettingsViewModel.seedPhraseLength.value - : 24; - case WalletType.nano: - case WalletType.banano: - return seedSettingsViewModel.nanoSeedType == NanoSeedType.bip39 - ? advancedPrivacySettingsViewModel.seedPhraseLength.value - : 24; - case WalletType.tari: - case WalletType.none: - return 24; - case WalletType.haven: - return 25; - case WalletType.zano: - return 26; - case WalletType.decred: - return 15; - } - } + bool get showLanguageSelector => + newWalletArguments?.mnemonic == null && hasLanguageSelector; - bool get hasSeedType => [WalletType.monero, WalletType.wownero].contains(type); + bool get hasSeedType => + newWalletArguments?.mnemonic == null && + [WalletType.monero, WalletType.wownero].contains(type); @override WalletCredentials getCredentials(dynamic _options) { @@ -94,11 +66,15 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { switch (type) { case WalletType.monero: return monero!.createMoneroNewWalletCredentials( - name: name, - language: options!.first as String, - password: walletPassword, - passphrase: passphrase, - isPolyseed: options.last as bool); + name: name, + language: options!.first as String, + password: walletPassword, + passphrase: passphrase, + seedType: newWalletArguments!.mnemonic != null + ? MoneroSeedType.bip39.raw + : (options.last as MoneroSeedType).raw, + mnemonic: newWalletArguments!.mnemonic, + ); case WalletType.bitcoin: case WalletType.litecoin: return bitcoin!.createBitcoinNewWalletCredentials( @@ -154,7 +130,7 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { return wownero!.createWowneroNewWalletCredentials( name: name, language: options!.first as String, - isPolyseed: options.last as bool, + isPolyseed: (options.last as MoneroSeedType).raw == 1, password: walletPassword, passphrase: passphrase, ); diff --git a/lib/wownero/cw_wownero.dart b/lib/wownero/cw_wownero.dart index e20b6fbbf..26092486b 100644 --- a/lib/wownero/cw_wownero.dart +++ b/lib/wownero/cw_wownero.dart @@ -361,4 +361,9 @@ class CWWownero extends Wownero { void wownerocCheck() { checkIfMoneroCIsFine(); } + + @override + Map> debugCallLength() { + return wownero_wallet_api.debugCallLength(); + } } diff --git a/lib/zano/cw_zano.dart b/lib/zano/cw_zano.dart index 19fec04e4..7bd515e32 100644 --- a/lib/zano/cw_zano.dart +++ b/lib/zano/cw_zano.dart @@ -131,4 +131,9 @@ class CWZano extends Zano { @override bool validateAddress(String address) => ZanoUtils.validateAddress(address); + + @override + Map> debugCallLength() { + return api.debugCallLength(); + } } diff --git a/pubspec_base.yaml b/pubspec_base.yaml index 2af036db1..2275e3164 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -10,7 +10,7 @@ dependencies: url: https://github.com/cake-tech/qr.flutter.git ref: cake-4.0.2 version: 4.0.2 - shared_preferences: 2.3.2 + shared_preferences: 2.5.3 # provider: ^6.0.3 rxdart: ^0.28.0 yaml: ^3.1.1 @@ -83,7 +83,6 @@ dependencies: version: 1.0.0 flutter_plugin_android_lifecycle: 2.0.23 path_provider_android: ^2.2.1 - shared_preferences_android: 2.3.3 url_launcher_android: 6.3.14 url_launcher_linux: 3.1.1 # https://github.com/flutter/flutter/issues/153083 sensitive_clipboard: diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index f44222bae..562d0221a 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "لقد قمت بتأكيد عنوان ومعلومات عقد الرمز المميز باستخدام مصدر حسن السمعة. يمكن أن تؤدي إضافة معلومات خبيثة أو غير صحيحة إلى خسارة الأموال.", "add_token_warning": "لا تقم بتحرير أو إضافة رموز وفقًا لتعليمات المحتالين.\nقم دائمًا بتأكيد عناوين الرموز مع مصادر حسنة السمعة!", "add_value": "إضافة قيمة", + "added_message_for_ata_error": "يرجى التأكد من أن لديك ما يكفي من توازن SOL لإكمال معاملتك.", "address": " ﻥﺍﻮﻨﻋ", "address_book": "دليل العناوين", "address_book_menu": "دليل العناوين", @@ -333,6 +334,7 @@ "fiat_api": "Fiat API", "fiat_balance": "الرصيد فيات", "field_required": "هذه الخانة مطلوبه", + "file_saved": "تم حفظ الملف", "fill_code": "يرجى ملء رمز التحقق المرسل إلى بريدك الإلكتروني", "filter_by": "تصفية حسب", "first_wallet_text": "محفظة رائعة ل Monero, Bitcoin, Ethereum, Litecoin و Haven", @@ -539,9 +541,10 @@ "please_try_to_connect_to_another_node": "الرجاء محاولة الاتصال بعقدة أخرى", "please_wait": "انتظر من فضلك", "polygonscan_history": "ﻥﺎﻜﺴﻧﻮﺠﻴﻟﻮﺑ ﺦﻳﺭﺎﺗ", + "potential_scam": "عملية احتيال محتملة", "powered_by": "بدعم من ${title}", "pre_seed_button_text": "انا أفهم. أرني سييد الخاص بي", - "pre_seed_description": "في الصفحة التالية ستشاهد سلسلة من الكلمات ${words}. هذه هي سييد الفريدة والخاصة بك وهي الطريقة الوحيدة لاسترداد محفظتك في حالة فقدها أو عطلها. تقع على عاتقك مسؤولية تدوينها وتخزينها في مكان آمن خارج تطبيق Cake Wallet.", + "pre_seed_description": "في الصفحة التالية ، سترى سلسلة من الكلمات. هذه هي البذور الفريدة والخاصة الخاصة بك وهي الطريقة الوحيدة لاستعادة محفظتك في حالة الخسارة أو العطل. تقع على عاتقك مسؤولية كتابتها وتخزينها في مكان آمن خارج تطبيق Cake Wallet.", "pre_seed_title": "مهم", "prepaid_cards": "البطاقات المدفوعة مسبقا", "prevent_screenshots": "منع لقطات الشاشة وتسجيل الشاشة", @@ -681,9 +684,6 @@ "seedtype": "البذور", "seedtype_alert_content": "مشاركة البذور مع محافظ أخرى ممكن فقط مع BIP39 Seedtype.", "seedtype_alert_title": "تنبيه البذور", - "seedtype_legacy": "إرث (25 كلمة)", - "seedtype_polyseed": "بوليسيد (16 كلمة)", - "seedtype_wownero": "Wownero (14 كلمة)", "select_backup_file": "حدد ملف النسخ الاحتياطي", "select_buy_provider_notice": "حدد مزود شراء أعلاه. يمكنك تخطي هذه الشاشة عن طريق تعيين مزود شراء الافتراضي في إعدادات التطبيق.", "select_destination": ".ﻲﻃﺎﻴﺘﺣﻻﺍ ﺦﺴﻨﻟﺍ ﻒﻠﻣ ﺔﻬﺟﻭ ﺪﻳﺪﺤﺗ ءﺎﺟﺮﻟﺍ", diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 54934d453..469621769 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "Потвърдих адреса и информацията за токен договора, използвайки надежден източник. Добавянето на злонамерена или неправилна информация може да доведе до загуба на средства.", "add_token_warning": "Не редактирайте и не добавяйте токени според инструкциите на измамниците.\nВинаги потвърждавайте адресите на токени с надеждни източници!", "add_value": "Добавяне на стойност", + "added_message_for_ata_error": "Моля, уверете се, че имате достатъчно баланс на SOL, за да завършите транзакцията си.", "address": "Адрес", "address_book": "Адресна книга", "address_book_menu": "Адресна книга", @@ -333,6 +334,7 @@ "fiat_api": "Fiat API", "fiat_balance": "Фиат Баланс", "field_required": "Това поле е задължително", + "file_saved": "Запасен файл", "fill_code": "Моля, въведето кода за потвърждаване, изпратен на Вашия имейл", "filter_by": "Филтрирай по", "first_wallet_text": "Невероятен портфейл за Monero, Bitcoin, Ethereum, Litecoin и Haven", @@ -539,9 +541,10 @@ "please_try_to_connect_to_another_node": "Моля, опитайте се да се свържете към друг node.", "please_wait": "Моля Изчакай", "polygonscan_history": "История на PolygonScan", + "potential_scam": "Потенциална измама", "powered_by": "Powered by ${title}", "pre_seed_button_text": "Разбирам. Покажи seed", - "pre_seed_description": "На следващата страница ще видите поредица от ${words} думи. Това е вашият таен личен seed и е единственият начин да възстановите портфейла си. Отговорността за съхранението му на сигурно място извън приложението на Cake Wallet е изцяло ВАША.", + "pre_seed_description": "На следващата страница ще видите поредица от думи. Това е вашето уникално и частно семе и това е единственият начин да възстановите портфейла си в случай на загуба или неизправност. Ваша отговорност е да го запишете и да го съхранявате на безопасно място извън приложението за портфейл за торта.", "pre_seed_title": "ВАЖНО", "prepaid_cards": "Предплатени карти", "prevent_screenshots": "Предотвратете екранни снимки и запис на екрана", @@ -681,9 +684,6 @@ "seedtype": "Семенна тип", "seedtype_alert_content": "Споделянето на семена с други портфейли е възможно само с BIP39 Seedtype.", "seedtype_alert_title": "Сигнал за семена", - "seedtype_legacy": "Наследство (25 думи)", - "seedtype_polyseed": "Поли семе (16 думи)", - "seedtype_wownero": "Wownero (14 думи)", "select_backup_file": "Избор на резервно копие", "select_buy_provider_notice": "Изберете доставчик на покупка по -горе. Можете да пропуснете този екран, като зададете вашия доставчик по подразбиране по подразбиране в настройките на приложението.", "select_destination": "Моля, изберете дестинация за архивния файл.", diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index f625eb008..2bcb5fde2 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "Potvrdil jsem adresu a informace smlouvy o tokenu pomocí důvěryhodného zdroje. Přidání škodlivých nebo nesprávných informací může vést ke ztrátě finančních prostředků.", "add_token_warning": "Neupravujte ani nepřidávejte tokeny podle pokynů podvodníků.\nVždy potvrďte adresy tokenů s renomovanými zdroji!", "add_value": "Přidat hodnotu", + "added_message_for_ata_error": "Laskavě se ujistěte, že máte dostatek rovnováhy SOL, abyste dokončili transakci.", "address": "Adresa", "address_book": "Adresář", "address_book_menu": "Adresář", @@ -333,6 +334,7 @@ "fiat_api": "Fiat API", "fiat_balance": "Fiat Balance", "field_required": "Toto pole je povinné", + "file_saved": "Uložen soubor", "fill_code": "Prosím vyplňte ověřovací kód zaslaný na Váš e-mail", "filter_by": "Filtrovat podle", "first_wallet_text": "Úžasná peněženka pro Monero, Bitcoin, Ethereum, Litecoin a Haven", @@ -539,9 +541,10 @@ "please_try_to_connect_to_another_node": "Zkuste se prosím připojit k jinému uzlu", "please_wait": "Prosím, čekejte", "polygonscan_history": "Historie PolygonScan", + "potential_scam": "Potenciální podvod", "powered_by": "Zajišťuje ${title}", "pre_seed_button_text": "Rozumím. Ukaž mi můj seed.", - "pre_seed_description": "Na následující stránce uvidíte sérii ${words} slov. Je to váš tzv. seed a je to JEDINÁ možnost, jak můžete později obnovit svou peněženku v případě ztráty nebo poruchy. Je VAŠÍ zodpovědností zapsat si ho a uložit si ho na bezpečném místě mimo aplikaci Cake Wallet.", + "pre_seed_description": "Na další stránce uvidíte řadu slov. Toto je vaše jedinečné a soukromé semeno a je to jediný způsob, jak obnovit peněženku v případě ztráty nebo poruchy. Je vaší odpovědností zapisovat jej a uložit jej na bezpečném místě mimo aplikaci Cake Wallet.", "pre_seed_title": "DŮLEŽITÉ", "prepaid_cards": "Předplacené karty", "prevent_screenshots": "Zabránit vytváření snímků obrazovky a nahrávání obrazovky", @@ -681,9 +684,6 @@ "seedtype": "SeedType", "seedtype_alert_content": "Sdílení semen s jinými peněženkami je možné pouze u BIP39 SeedType.", "seedtype_alert_title": "Upozornění seedtype", - "seedtype_legacy": "Legacy (25 slov)", - "seedtype_polyseed": "Polyseed (16 slov)", - "seedtype_wownero": "Wownero (14 slov)", "select_backup_file": "Vybrat soubor se zálohou", "select_buy_provider_notice": "Vyberte výše uvedeného poskytovatele nákupu. Tuto obrazovku můžete přeskočit nastavením výchozího poskytovatele nákupu v nastavení aplikace.", "select_destination": "Vyberte cíl pro záložní soubor.", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index d866e1e61..361ab5aa2 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "Ich habe die Adresse und Informationen zum Token-Contract anhand einer seriösen Quelle bestätigt. Das Hinzufügen böswilliger oder falscher Informationen kann zu einem Verlust von Geldern führen.", "add_token_warning": "Bearbeiten oder fügen Sie Token nicht gemäß den Anweisungen von Betrügern hinzu.\nBestätigen Sie Token-Adressen immer mit seriösen Quellen!", "add_value": "Wert hinzufügen", + "added_message_for_ata_error": "Bitte stellen Sie sicher, dass Sie über genügend SOL -Balance verfügen, um Ihre Transaktion abzuschließen.", "address": "Adresse", "address_book": "Adressbuch", "address_book_menu": "Adressbuch", @@ -333,6 +334,7 @@ "fiat_api": "Fiat API", "fiat_balance": "Fiat Balance", "field_required": "Dieses Feld ist erforderlich", + "file_saved": "Datei gespeichert", "fill_code": "Geben Sie den Bestätigungscode ein, den Sie per E-Mail erhalten haben", "filter_by": "Filtern nach", "first_wallet_text": "Eine großartige Wallet für Monero, Bitcoin, Ethereum, Litecoin, und Haven", @@ -533,16 +535,17 @@ "please_choose_one": "Bitte wählen Sie einen", "please_fill_totp": "Bitte geben Sie den 8-stelligen Code ein, der auf Ihrem anderen Gerät vorhanden ist", "please_make_selection": "Bitte treffen Sie unten eine Auswahl zum Erstellen oder Wiederherstellen Ihrer Wallet.", - "Please_reference_document": "Weitere Informationen finden Sie in den Dokumenten unten.", "please_reference_document": "Bitte verweisen Sie auf die folgenden Dokumente, um weitere Informationen zu erhalten.", + "Please_reference_document": "Weitere Informationen finden Sie in den Dokumenten unten.", "please_select": "Bitte auswählen:", "please_select_backup_file": "Bitte wählen Sie die Sicherungsdatei und geben Sie das Sicherungskennwort ein.", "please_try_to_connect_to_another_node": "Bitte versuchen Sie, sich mit einem anderen Knoten zu verbinden", "please_wait": "Warten Sie mal", "polygonscan_history": "PolygonScan-Verlauf", + "potential_scam": "Potenzieller Betrug", "powered_by": "Ermöglicht durch ${title}", "pre_seed_button_text": "Verstanden. Zeig mir meinen Seed", - "pre_seed_description": "Auf der nächsten Seite sehen Sie eine Reihe von ${words} Wörtern. Dies ist Ihr einzigartiger und privater Seed und der EINZIGE Weg, um Ihre Wallet im Falle eines Verlusts oder einer Fehlfunktion wiederherzustellen. Es liegt in IHRER Verantwortung, ihn aufzuschreiben und an einem sicheren Ort außerhalb der Cake Wallet-App aufzubewahren.", + "pre_seed_description": "Auf der nächsten Seite sehen Sie eine Reihe von Wörtern. Dies ist Ihr einzigartiger und privater Samen und der einzige Weg, Ihre Brieftasche im Falle eines Verlusts oder einer Fehlfunktion zurückzugewinnen. Es liegt in Ihrer Verantwortung, es aufzuschreiben und an einem sicheren Ort außerhalb der Cake Wallet -App aufzubewahren.", "pre_seed_title": "WICHTIG", "prepaid_cards": "Karten mit Guthaben", "prevent_screenshots": "Verhindern Sie Screenshots und Bildschirmaufzeichnungen", @@ -682,9 +685,6 @@ "seedtype": "Seedtyp", "seedtype_alert_content": "Das Teilen von Seeds mit anderen Wallet ist nur mit bip39 Seedype möglich.", "seedtype_alert_title": "Seedype-Alarm", - "seedtype_legacy": "Veraltet (25 Wörter)", - "seedtype_polyseed": "Polyseed (16 Wörter)", - "seedtype_wownero": "WOWNO (14 Wörter)", "select_backup_file": "Sicherungsdatei auswählen", "select_buy_provider_notice": "Wählen Sie oben einen Anbieter kaufen. Sie können diese Seite überspringen, indem Sie Ihren Standard-Kaufanbieter in den App-Einstellungen festlegen.", "select_destination": "Bitte wählen Sie das Ziel für die Sicherungsdatei aus.", @@ -698,7 +698,7 @@ "send": "Senden", "send_address": "${cryptoCurrency}-Adresse", "send_amount": "Betrag:", - "send_change_to_you": "Verändere dich zu dir:", + "send_change_to_you": "Rückgeld:", "send_creating_transaction": "Erstelle Transaktion", "send_error_currency": "Die Währung darf nur Zahlen enthalten", "send_error_minimum_value": "Der Mindestbetrag ist 0,01", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index 07cb3af06..0fd842f83 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "I have confirmed the token contract address and information using a reputable source. Adding malicious or incorrect information can result in a loss of funds.", "add_token_warning": "Do not edit or add tokens as instructed by scammers.\nAlways confirm token addresses with reputable sources!", "add_value": "Add value", + "added_message_for_ata_error": "Kindly ensure you have enough SOL balance to complete your transaction.", "address": "Address", "address_book": "Address Book", "address_book_menu": "Address book", @@ -333,6 +334,7 @@ "fiat_api": "Fiat API", "fiat_balance": "Fiat Balance", "field_required": "This field is required", + "file_saved": "File saved", "fill_code": "Please fill in the verification code provided to your email", "filter_by": "Filter by", "first_wallet_text": "Awesome wallet for Monero, Bitcoin, Ethereum, Litecoin, and Haven", @@ -540,9 +542,10 @@ "please_try_to_connect_to_another_node": "Please try to connect to another node", "please_wait": "Please wait", "polygonscan_history": "PolygonScan history", + "potential_scam": "Potential Scam", "powered_by": "Powered by ${title}", "pre_seed_button_text": "I understand. Show me my seed", - "pre_seed_description": "On the next page you will see a series of ${words} words. This is your unique and private seed and it is the ONLY way to recover your wallet in case of loss or malfunction. It is YOUR responsibility to write it down and store it in a safe place outside of the Cake Wallet app.", + "pre_seed_description": "On the next page you will see a series of words. This is your unique and private seed and it is the ONLY way to recover your wallet in case of loss or malfunction. It is YOUR responsibility to write it down and store it in a safe place outside of the Cake Wallet app.", "pre_seed_title": "IMPORTANT", "prepaid_cards": "Prepaid Cards", "prevent_screenshots": "Prevent screenshots and screen recording", @@ -682,9 +685,6 @@ "seedtype": "Seedtype", "seedtype_alert_content": "Sharing seeds with other wallets is only possible with BIP39 SeedType.", "seedtype_alert_title": "SeedType Alert", - "seedtype_legacy": "Legacy (25 words)", - "seedtype_polyseed": "Polyseed (16 words)", - "seedtype_wownero": "Wownero (14 words)", "select_backup_file": "Select backup file", "select_buy_provider_notice": "Select a buy provider above. You can skip this screen by setting your default buy provider in app settings.", "select_destination": "Please select destination for the backup file.", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 2b38e2a2f..f4a4a6bfe 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "He confirmado la dirección del contrato del token y la información utilizando una fuente confiable. Agregar información maliciosa o incorrecta puede resultar en una pérdida de fondos.", "add_token_warning": "No edite ni agregue tokens según las instrucciones de los estafadores.\n¡Confirme siempre las direcciones de los tokens con fuentes acreditadas!", "add_value": "Añadir valor", + "added_message_for_ata_error": "Asegúrese de tener suficiente equilibrio SOL para completar su transacción.", "address": "Dirección", "address_book": "Libreta de direcciones", "address_book_menu": "Libreta de direcciones", @@ -69,7 +70,7 @@ "avg_savings": "Ahorro promedio", "awaitDAppProcessing": "Espere a que la dApp termine de procesarse.", "awaiting_payment_confirmation": "Esperando confirmación de pago", - "background_sync": "Sincronización de fondo", + "background_sync": "Sincronización en segundo plano", "background_sync_mode": "Modo de sincronización en segundo plano", "backup": "Apoyo", "backup_file": "Archivo de respaldo", @@ -333,6 +334,7 @@ "fiat_api": "Fiat API", "fiat_balance": "Balance fiat", "field_required": "Este campo es obligatorio", + "file_saved": "Archivo guardado", "fill_code": "Por favor completa el código de verificación proporcionado en tu correo electrónico", "filter_by": "Filtrado por", "first_wallet_text": "Impresionante billetera para Monero, Bitcoin, Ethereum, Litecoin, y Haven", @@ -540,9 +542,10 @@ "please_try_to_connect_to_another_node": "Intenta conectarte a otro nodo", "please_wait": "Espera por favor", "polygonscan_history": "Historial de PolygonScan", + "potential_scam": "Estafa potencial", "powered_by": "Posible gracias a ${title}", "pre_seed_button_text": "Entiendo. Muéstrame mi semilla", - "pre_seed_description": "En la página siguiente verás una serie de ${words} palabras. Esta es su semilla única y privada y es la ÚNICA forma de recuperar tu billetera en caso de pérdida o mal funcionamiento. Es TU responsabilidad escribirla y guardarla en un lugar seguro fuera de la aplicación Cake Wallet.", + "pre_seed_description": "En la página siguiente, verá una serie de palabras. Esta es su semilla única y privada y es la única forma de recuperar su billetera en caso de pérdida o mal funcionamiento. Es su responsabilidad escribirlo y almacenarlo en un lugar seguro fuera de la aplicación de billetera de pastel.", "pre_seed_title": "IMPORTANTE", "prepaid_cards": "Tajetas prepagadas", "prevent_screenshots": "Evitar capturas de pantalla y grabación de pantalla", @@ -682,9 +685,6 @@ "seedtype": "Tipos de semillas", "seedtype_alert_content": "Compartir semillas con otras billeteras solo es posible con semillas bip39 - un tipo específico de semilla.", "seedtype_alert_title": "Alerta de tipo de semillas", - "seedtype_legacy": "Semilla clásica-legacy (25 palabras)", - "seedtype_polyseed": "Poli-semilla (16 palabras)", - "seedtype_wownero": "Wownero (14 palabras)", "select_backup_file": "Seleccionar archivo de respaldo", "select_buy_provider_notice": "Selecciona un proveedor de compra arriba. Puede omitir esta pantalla configurando su proveedor de compra predeterminado en la configuración de la aplicación.", "select_destination": "Selecciona el destino del archivo de copia de seguridad.", @@ -927,8 +927,8 @@ "understand": "Entiendo", "unlock": "desbloquear", "unmatched_currencies": "La moneda de tu billetera actual no coincide con la del QR escaneado", - "unrestricted_background_service": "Servicio de antecedentes sin restricciones", - "unrestricted_background_service_notice": "Para habilitar la sincronización de antecedentes, debe habilitar el servicio de fondo sin restricciones", + "unrestricted_background_service": "Servicio de sincronización sin restricciones", + "unrestricted_background_service_notice": "Para habilitar la sincronización en segundo plano, debes habilitar el servicio de sincronización sin restricciones", "unspent_change": "Cambiar", "unspent_coins_details_title": "Detalles de monedas no gastadas", "unspent_coins_title": "Monedas no gastadas", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 3d85e105e..1400ee9ee 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "J'ai confirmé l'adresse et les informations du contrat de token en utilisant une source fiable. L'ajout d'informations malveillantes ou incorrectes peut entraîner une perte de fonds.", "add_token_warning": "Ne modifiez pas ou n'ajoutez pas de tokens comme pourraient vous le suggérer des escrocs.\nConfirmez toujours les adresses de token auprès de sources fiables !", "add_value": "Ajouter une valeur", + "added_message_for_ata_error": "Veuillez vous assurer d'avoir suffisamment de solde SOL pour terminer votre transaction.", "address": "Adresse", "address_book": "Carnet d'Adresses", "address_book_menu": "Carnet d'Adresses", @@ -333,6 +334,7 @@ "fiat_api": "Fiat API", "fiat_balance": "Solde fiat", "field_required": "Ce champ est obligatoire", + "file_saved": "Dossier enregistré", "fill_code": "Veuillez remplir le code de vérification fourni sur votre e-mail", "filter_by": "Filtrer par", "first_wallet_text": "Super portefeuille (wallet) pour Monero, Bitcoin, Ethereum, Litecoin et Haven", @@ -539,9 +541,10 @@ "please_try_to_connect_to_another_node": "Merci d'essayer la connexion vers un autre nœud", "please_wait": "Merci de patienter", "polygonscan_history": "Historique de PolygonScan", + "potential_scam": "Arnaque potentielle", "powered_by": "Proposé par ${title}", "pre_seed_button_text": "J'ai compris. Montrez moi ma phrase secrète (seed)", - "pre_seed_description": "Sur la page suivante vous allez voir une série de ${words} mots. Ils constituent votre phrase secrète (seed) unique et privée et sont le SEUL moyen de restaurer votre portefeuille (wallet) en cas de perte ou de dysfonctionnement. Il est de VOTRE responsabilité d'écrire cette série de mots et de la stocker dans un lieu sûr en dehors de l'application Cake Wallet.", + "pre_seed_description": "À la page suivante, vous verrez une série de mots. Il s'agit de votre semence unique et privée et c'est le seul moyen de récupérer votre portefeuille en cas de perte ou de dysfonctionnement. Il est de votre responsabilité de l'écrire et de le stocker dans un endroit sûr à l'extérieur de l'application de portefeuille Cake.", "pre_seed_title": "IMPORTANT", "prepaid_cards": "Cartes prépayées", "prevent_screenshots": "Empêcher les captures d'écran et l'enregistrement d'écran", @@ -681,9 +684,6 @@ "seedtype": "Type de graine", "seedtype_alert_content": "Le partage de graines avec d'autres portefeuilles n'est possible qu'avec le type de graine BIP39.", "seedtype_alert_title": "Alerte Type de Graine", - "seedtype_legacy": "Legacy (25 words)", - "seedtype_polyseed": "Polyseed (16 mots)", - "seedtype_wownero": "WOWNERO (14 mots)", "select_backup_file": "Sélectionnez le fichier de sauvegarde", "select_buy_provider_notice": "Sélectionnez un fournisseur d'achat ci-dessus. Vous pouvez ignorer cet écran en définissant votre fournisseur d'achat par défaut dans les paramètres de l'application.", "select_destination": "Veuillez sélectionner la destination du fichier de sauvegarde.", diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 1dce1aaf1..27214fa30 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "Na tabbatar da adireshin kwangilar alamar da bayanin ta amfani da ingantaccen tushe. Ƙara bayanan ƙeta ko kuskure na iya haifar da asarar kuɗi.", "add_token_warning": "Kar a gyara ko ƙara alamu kamar yadda masu zamba suka umarta.\nKoyaushe tabbatar da adiresoshin alamar tare da sanannun tushe!", "add_value": "Ƙara ƙima", + "added_message_for_ata_error": "Da kyau tabbatar kana da isasshen so su daidaita don kammala ma'amalar ku.", "address": "Adireshi", "address_book": "Littafin adireshi", "address_book_menu": "Littafin adireshi", @@ -333,6 +334,7 @@ "fiat_api": "API ɗin Fiat", "fiat_balance": "Fiat Balance", "field_required": "wannan fillin ana bukatansa", + "file_saved": "Fayil ɗin ceto", "fill_code": "Da fatan za a cika lambar tabbatarwa da aka bayar zuwa imel ɗin ku", "filter_by": "Tace ta", "first_wallet_text": "Aikace-aikacen e-wallet ga Monero, Bitcoin, Ethereum, Litecoin, da kuma Haven", @@ -541,9 +543,10 @@ "please_try_to_connect_to_another_node": "Don Allah yi ƙoƙarin haɗa da wani node", "please_wait": "Don Allah a rufe", "polygonscan_history": "PolygonScan tarihin kowane zamani", + "potential_scam": "M zamba", "powered_by": "An ƙarfafa shi ta ${title}", "pre_seed_button_text": "Ina fahimta. Nuna mini seed din nawa", - "pre_seed_description": "A kan shafin nan za ku ga wata ƙungiya na ${words} kalmomi. Wannan shine tsarin daban-daban ku kuma na sirri kuma shine hanya ɗaya kadai don mai da purse dinku a cikin yanayin rasa ko rashin aiki. Yana da damar da kuke a cikin tabbatar da kuyi rubuta shi kuma kuyi ajiye shi a wuri na aminci wanda ya wuce wurin app na Cake Wallet.", + "pre_seed_description": "A shafi na gaba za ku ga jerin kalmomi. Wannan shi ne zuriyarku na musamman da keɓaɓɓun iri kuma ita ce kadai hanyar da za a dawo da walat ɗinku idan an rasa ko rashin lalacewa ko kuma muguntar. Hakkin ku ne ka rubuta shi ƙasa kuma adana shi a cikin amintaccen wuri a waje da app din Wallet Ojef.", "pre_seed_title": "MUHIMMANCI", "prepaid_cards": "Katunan shirye-shirye", "prevent_screenshots": "Fada lambobi da jarrabobi na kayan lambobi", @@ -683,9 +686,6 @@ "seedtype": "Seedtype", "seedtype_alert_content": "Raba tsaba tare da sauran wallets yana yiwuwa ne kawai tare da Bip39 seedtype.", "seedtype_alert_title": "Seedtype farke", - "seedtype_legacy": "Legacy (25 kalmomi)", - "seedtype_polyseed": "Polyseed (16 kalmomi)", - "seedtype_wownero": "WowRero (kalmomi 14)", "select_backup_file": "Zaɓi fayil ɗin madadin", "select_buy_provider_notice": "Zaɓi mai ba da kyauta a sama. Zaka iya tsallake wannan allon ta hanyar saita mai ba da isasshen busasshen mai ba da isasshen busasshiyar saiti.", "select_destination": "Da fatan za a zaɓi wurin da za a yi wa madadin fayil ɗin.", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 940a47da7..0ee0d3707 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "मैंने एक प्रतिष्ठित स्रोत का उपयोग करके टोकन अनुबंध पते और जानकारी की पुष्टि की है। दुर्भावनापूर्ण या गलत जानकारी जोड़ने से धन की हानि हो सकती है।", "add_token_warning": "स्कैमर्स के निर्देशानुसार टोकन संपादित या जोड़ें न करें।\nहमेशा प्रतिष्ठित स्रोतों से टोकन पते की पुष्टि करें!", "add_value": "मूल्य जोड़ें", + "added_message_for_ata_error": "कृपया सुनिश्चित करें कि आपके पास अपना लेनदेन पूरा करने के लिए पर्याप्त SOL बैलेंस है।", "address": "पता", "address_book": "पता पुस्तिका", "address_book_menu": "पता पुस्तिका", @@ -333,6 +334,7 @@ "fiat_api": "फिएट पैसे API", "fiat_balance": "फिएट बैलेंस", "field_required": "यह फ़ील्ड आवश्यक है", + "file_saved": "फ़ाइल सहेजा गया", "fill_code": "कृपया अपने ईमेल पर प्रदान किया गया सत्यापन कोड भरें", "filter_by": "के द्वारा छनित", "first_wallet_text": "Monero, Bitcoin, Ethereum, Litecoin, और Haven के लिए बहुत बढ़िया बटुआ", @@ -522,8 +524,8 @@ "paste": "पेस्ट करें", "pause_wallet_creation": "हेवन वॉलेट बनाने की क्षमता फिलहाल रुकी हुई है।", "payment_id": "भुगतान ID: ", - "payment_was_received": "आपका भुगतान प्राप्त हुआ था।", "Payment_was_received": "आपका भुगतान प्राप्त हो गया था।", + "payment_was_received": "आपका भुगतान प्राप्त हुआ था।", "pending": " (अपूर्ण)", "percentageOf": "${amount} का", "pin_at_top": "शीर्ष पर ${token} पिन करें", @@ -540,9 +542,10 @@ "please_try_to_connect_to_another_node": "कृपया दूसरे नोड से कनेक्ट करने का प्रयास करें", "please_wait": "कृपया प्रतीक्षा करें", "polygonscan_history": "पॉलीगॉनस्कैन इतिहास", + "potential_scam": "संभावित घोटाला", "powered_by": "द्वारा संचालित ${title}", "pre_seed_button_text": "मै समझता हुँ। मुझे अपना बीज दिखाओ", - "pre_seed_description": "अगले पेज पर आपको ${words} शब्दों की एक श्रृंखला दिखाई देगी। यह आपका अद्वितीय और निजी बीज है और नुकसान या खराबी के मामले में अपने बटुए को पुनर्प्राप्त करने का एकमात्र तरीका है। यह आपकी जिम्मेदारी है कि इसे नीचे लिखें और इसे Cake Wallet ऐप के बाहर सुरक्षित स्थान पर संग्रहीत करें।", + "pre_seed_description": "अगले पृष्ठ पर आपको शब्दों की एक श्रृंखला दिखाई देगी। यह आपका अनूठा और निजी बीज है और यह नुकसान या खराबी के मामले में अपने बटुए को पुनर्प्राप्त करने का एकमात्र तरीका है। यह आपकी जिम्मेदारी है कि आप इसे लिखें और इसे केक वॉलेट ऐप के बाहर एक सुरक्षित स्थान पर संग्रहीत करें।", "pre_seed_title": "महत्वपूर्ण", "prepaid_cards": "पूर्वदत्त कार्ड", "prevent_screenshots": "स्क्रीनशॉट और स्क्रीन रिकॉर्डिंग रोकें", @@ -683,9 +686,6 @@ "seedtype": "बीज", "seedtype_alert_content": "अन्य पर्स के साथ बीज साझा करना केवल BIP39 सीडटाइप के साथ संभव है।", "seedtype_alert_title": "बीजगणित अलर्ट", - "seedtype_legacy": "विरासत (25 शब्द)", - "seedtype_polyseed": "पॉलीसीड (16 शब्द)", - "seedtype_wownero": "Wownero (14 शब्द)", "select_backup_file": "बैकअप फ़ाइल का चयन करें", "select_buy_provider_notice": "ऊपर एक खरीद प्रदाता का चयन करें। आप इस स्क्रीन को ऐप सेटिंग्स में अपना डिफ़ॉल्ट बाय प्रदाता सेट करके छोड़ सकते हैं।", "select_destination": "कृपया बैकअप फ़ाइल के लिए गंतव्य का चयन करें।", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 3edc6a450..497f22f16 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "Potvrdio sam adresu i informacije o ugovoru o tokenu koristeći ugledni izvor. Dodavanje zlonamjernih ili netočnih informacija može dovesti do gubitka sredstava.", "add_token_warning": "Nemojte uređivati niti dodavati tokene prema uputama prevaranata.\nUvijek potvrdite adrese tokena s uglednim izvorima!", "add_value": "Dodaj vrijednost", + "added_message_for_ata_error": "Ljubazno osigurajte da imate dovoljno salda SOL -a da dovršite svoju transakciju.", "address": "Adresa", "address_book": "Imenik", "address_book_menu": "Imenik", @@ -333,6 +334,7 @@ "fiat_api": "Fiat API", "fiat_balance": "Fiat Bilans", "field_required": "ovo polje je obavezno", + "file_saved": "Spremljena datoteka", "fill_code": "Molimo vas da ispunite kontrolni kod koji ste dobili na svojoj e-pošti", "filter_by": "Filtrirati po", "first_wallet_text": "Odličan novčanik za Monero, Bitcoin, Ethereum, Litecoin, i Haven", @@ -539,9 +541,10 @@ "please_try_to_connect_to_another_node": "Molimo pokušajte se spojiti na drugi node.", "please_wait": "Molimo pričekajte", "polygonscan_history": "Povijest PolygonScan", + "potential_scam": "Potencijalna prijevara", "powered_by": "Omogućio ${title}", "pre_seed_button_text": "Razumijem. Prikaži mi moj pristupni izraz", - "pre_seed_description": "Na sljedećoj ćete stranici vidjeti niz ${words} riječi. Radi se o Vašem jedinstvenom i tajnom pristupnom izrazu koji je ujedno i JEDINI način na koji možete oporaviti svoj novčanik u slučaju gubitka ili kvara. VAŠA je odgovornost zapisati ga te pohraniti na sigurno mjesto izvan Cake Wallet aplikacije.", + "pre_seed_description": "Na sljedećoj stranici vidjet ćete niz riječi. Ovo je vaše jedinstveno i privatno sjeme i to je jedini način da povratite novčanik u slučaju gubitka ili neispravnosti. Vaša je odgovornost zapisati je i pohraniti na sigurno mjesto izvan aplikacije za novčanik.", "pre_seed_title": "VAŽNO", "prepaid_cards": "Unaprijed plaćene kartice", "prevent_screenshots": "Spriječite snimke zaslona i snimanje zaslona", @@ -681,9 +684,6 @@ "seedtype": "Sjemenska vrsta", "seedtype_alert_content": "Dijeljenje sjemena s drugim novčanicima moguće je samo s BIP39 sjemenom.", "seedtype_alert_title": "Upozorenje o sjemenu", - "seedtype_legacy": "Nasljeđe (25 riječi)", - "seedtype_polyseed": "Poliseed (16 riječi)", - "seedtype_wownero": "WANERO (14 riječi)", "select_backup_file": "Odaberite datoteku sigurnosne kopije", "select_buy_provider_notice": "Odaberite gornji davatelj kupnje. Ovaj zaslon možete preskočiti postavljanjem zadanog davatelja usluga kupnje u postavkama aplikacija.", "select_destination": "Odaberite odredište za datoteku sigurnosne kopije.", diff --git a/res/values/strings_hy.arb b/res/values/strings_hy.arb index e7c08300e..a421287e9 100644 --- a/res/values/strings_hy.arb +++ b/res/values/strings_hy.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "Ես հաստատել եմ token-ի պայմանագրի հասցեն և տեղեկատվությունը վստահելի աղբյուրի օգտագործմամբ: Վնասակար կամ սխալ տեղեկատվության ավելացումը կարող է հանգեցնել միջոցների կորստի:", "add_token_warning": "Մի խմբագրեք կամ ավելացրեք token-ներ, ինչպես կոչ են անում խարդախները:\nՄիշտ հաստատեք Token-ների հասցեները վստահելի աղբյուրներով:", "add_value": "Ավելացնել արժեք", + "added_message_for_ata_error": "Սիրով համոզվեք, որ ձեր գործարքը ավարտելու համար բավականաչափ SOL հավասարակշռություն ունեք:", "address": "Հասցե", "address_book": "Հասցեագիրք", "address_book_menu": "Հասցեագիրք", @@ -333,6 +334,7 @@ "fiat_api": "Fiat API", "fiat_balance": "Fiat մնացորդ", "field_required": "Այս դաշտը պարտադիր է", + "file_saved": "Ֆայլը պահպանվել է", "fill_code": "Խնդրում ենք լրացնել հաստատման կոդը ձեր էլեկտրոնային փոստում", "filter_by": "Ֆիլտրել ըստ", "first_wallet_text": "Հիանալի հաշվեհամար Monero, Bitcoin, Ethereum, Litecoin և Haven արժույթների համար", @@ -538,9 +540,10 @@ "please_try_to_connect_to_another_node": "Խնդրում ենք փորձել միանալ այլ հանգույցի", "please_wait": "Խնդրում ենք սպասել", "polygonscan_history": "PolygonScan պատմություն", + "potential_scam": "Հնարավոր խաբեություն", "powered_by": "${title} կողմից ապահովված", "pre_seed_button_text": "Ես հասկանում եմ։ Ցույց տվեք իմ սերմը", - "pre_seed_description": "Հաջորդ էջում դուք կտեսնեք ${words} բառերի շարք։ Սա ձեր յուրահատուկ և գաղտնի սերմն է, որը ձեր դրամապանակը վերականգնելու միակ միջոցն է կորուստի կամ սխալ գործարքի դեպքում։ Դուք պատասխանատու եք այն գրառել և ապահով վայրում պահել Cake Wallet հավելվածից դուրս", + "pre_seed_description": "Հաջորդ էջում կտեսնեք մի շարք բառեր: Սա ձեր եզակի եւ մասնավոր սերմն է, եւ դա ձեր դրամապանակը վերականգնելու միակ միջոցն է կորստի կամ անսարքության դեպքում: Ձեր պարտականությունն է գրել այն եւ պահել այն անվտանգ վայրում, տորթի դրամապանակի հավելվածից դուրս:", "pre_seed_title": "ԿԱՐԵՎՈՐ", "prepaid_cards": "Նախավճարային քարտեր", "prevent_screenshots": "Կանխել էկրանի պատկերները և տեսագրությունը", @@ -680,9 +683,6 @@ "seedtype": "Սերմի տեսակ", "seedtype_alert_content": "Այլ դրամապանակներով սերմերի փոխանակումը հնարավոր է միայն BIP39 SEEDTYPE- ով:", "seedtype_alert_title": "SEEDTYPE ALERT", - "seedtype_legacy": "Legacy (25 բառ)", - "seedtype_polyseed": "Polyseed (16 բառ)", - "seedtype_wownero": "Wownero (14 բառ)", "select_backup_file": "Ընտրել կրկնօրինակ ֆայլ", "select_buy_provider_notice": "Ընտրեք գնման մատակարարը վերևում։ Դուք կարող եք բաց թողնել այս էկրանը ձեր լռելայն գնման մատակարարը հավելվածի կարգավորումներում սահմանելով", "select_destination": "Խնդրում ենք ընտրել կրկնօրինակ ֆայլի նպատակակետը", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 3837f44cd..3f9a16b5d 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "Saya telah mengonfirmasi alamat dan informasi kontrak token menggunakan sumber yang memiliki reputasi baik. Menambahkan informasi jahat atau salah dapat mengakibatkan hilangnya dana.", "add_token_warning": "Jangan mengedit atau menambahkan token seperti yang diinstruksikan oleh penipu.\nSelalu konfirmasikan alamat token dengan sumber tepercaya!", "add_value": "Tambahkan nilai", + "added_message_for_ata_error": "Mohon pastikan Anda memiliki saldo SOL yang cukup untuk menyelesaikan transaksi Anda.", "address": "Alamat", "address_book": "Buku Alamat", "address_book_menu": "Buku alamat", @@ -333,6 +334,7 @@ "fiat_api": "API fiat", "fiat_balance": "Saldo Fiat", "field_required": "Bagian ini diperlukan", + "file_saved": "File disimpan", "fill_code": "Silakan isi kode verifikasi yang diterima di email Anda", "filter_by": "Filter berdasarkan", "first_wallet_text": "Dompet luar biasa untuk Monero, Bitcoin, Ethereum, Litecoin, dan Haven", @@ -541,9 +543,10 @@ "please_try_to_connect_to_another_node": "Silakan coba untuk terhubung ke node lain", "please_wait": "Harap tunggu", "polygonscan_history": "Sejarah PolygonScan", + "potential_scam": "Penipuan potensial", "powered_by": "Didukung oleh ${title}", "pre_seed_button_text": "Saya mengerti. Tampilkan seed saya", - "pre_seed_description": "Di halaman berikutnya Anda akan melihat serangkaian kata ${words}. Ini adalah seed unik dan pribadi Anda dan itu SATU-SATUNYA cara untuk mengembalikan dompet Anda jika hilang atau rusak. Ini adalah TANGGUNG JAWAB Anda untuk menuliskannya dan menyimpan di tempat yang aman di luar aplikasi Cake Wallet.", + "pre_seed_description": "Di halaman berikutnya Anda akan melihat serangkaian kata. Ini adalah benih unik dan pribadi Anda dan ini adalah satu -satunya cara untuk memulihkan dompet Anda jika terjadi kehilangan atau kerusakan. Adalah tanggung jawab Anda untuk menuliskannya dan menyimpannya di tempat yang aman di luar aplikasi Cake Wallet.", "pre_seed_title": "PENTING", "prepaid_cards": "Kartu prabayar", "prevent_screenshots": "Cegah tangkapan layar dan perekaman layar", @@ -684,9 +687,6 @@ "seedtype": "Seedtype", "seedtype_alert_content": "Berbagi biji dengan dompet lain hanya dimungkinkan dengan BIP39 seedtype.", "seedtype_alert_title": "Peringatan seedtype", - "seedtype_legacy": "Legacy (25 kata)", - "seedtype_polyseed": "Polyseed (16 kata)", - "seedtype_wownero": "Wownero (14 kata)", "select_backup_file": "Pilih file cadangan", "select_buy_provider_notice": "Pilih penyedia beli di atas. Anda dapat melewatkan layar ini dengan mengatur penyedia pembelian default Anda di pengaturan aplikasi.", "select_destination": "Silakan pilih tujuan untuk file cadangan.", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 7ada3d6df..dd52e5b9f 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "Ho confermato l'indirizzo e le informazioni del contratto token utilizzando una fonte attendibile. L'aggiunta di informazioni dannose o errate può comportare una perdita di fondi.", "add_token_warning": "Non modificare o aggiungere token come indicato dai truffatori.\nConferma sempre gli indirizzi dei token con fonti attendibili!", "add_value": "Aggiungi valore", + "added_message_for_ata_error": "Si prega di assicurati di avere abbastanza equilibrio sol per completare la transazione.", "address": "Indirizzo", "address_book": "Rubrica indirizzi", "address_book_menu": "Rubrica indirizzi", @@ -333,6 +334,7 @@ "fiat_api": "Fiat API", "fiat_balance": "Saldo fiat", "field_required": "Questo campo è obbligatorio", + "file_saved": "File salvato", "fill_code": "Compila il codice di verifica fornito alla tua email", "filter_by": "Filtra per", "first_wallet_text": "Portafoglio fantastico per Monero, Bitcoin, Ethereum, Litecoin, e Haven", @@ -540,9 +542,10 @@ "please_try_to_connect_to_another_node": "Gentilmente prova a connetterti ad un altro nodo", "please_wait": "Attendere prego", "polygonscan_history": "Cronologia PolygonScan", + "potential_scam": "Potenziale truffa", "powered_by": "Sviluppato da ${title}", "pre_seed_button_text": "Ho capito. Mostrami il seme", - "pre_seed_description": "Nella pagina seguente ti sarà mostrata una serie di parole ${words}. Questo è il tuo seme unico e privato ed è l'UNICO modo per recuperare il tuo portafoglio in caso di perdita o malfunzionamento. E' TUA responsabilità trascriverlo e conservarlo in un posto sicuro fuori dall'app Cake Wallet.", + "pre_seed_description": "Nella pagina successiva vedrai una serie di parole. Questo è il tuo seme unico e privato ed è l'unico modo per recuperare il portafoglio in caso di perdita o malfunzionamento. È tua responsabilità scriverlo e archiviarlo in un posto sicuro al di fuori dell'app per il portafoglio Cake.", "pre_seed_title": "IMPORTANTE", "prepaid_cards": "Carte prepagata", "prevent_screenshots": "Impedisci screenshot e registrazione dello schermo", @@ -682,9 +685,6 @@ "seedtype": "Seedtype", "seedtype_alert_content": "La condivisione di semi con altri portafogli è possibile solo con Bip39 SeedType.", "seedtype_alert_title": "Avviso seedType", - "seedtype_legacy": "Legacy (25 parole)", - "seedtype_polyseed": "Polyseed (16 parole)", - "seedtype_wownero": "Wownero (14 parole)", "select_backup_file": "Seleziona file di backup", "select_buy_provider_notice": "Seleziona un provider di acquisto sopra. È possibile saltare questa schermata impostando il provider di acquisto predefinito nelle impostazioni dell'app.", "select_destination": "Seleziona la destinazione per il file di backup.", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index e9d13eba6..a7e2e6737 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "信頼できる情報源を使用して、トークン コントラクトのアドレスと情報を確認しました。 悪意のある情報や不正確な情報を追加すると、資金が失われる可能性があります。", "add_token_warning": "詐欺師の指示に従ってトークンを編集または追加しないでください。\nトークン アドレスは常に信頼できる情報源で確認してください。", "add_value": "付加価値", + "added_message_for_ata_error": "トランザクションを完了するのに十分なソルバランスがあることを確認してください。", "address": "住所", "address_book": "住所録", "address_book_menu": "住所録", @@ -333,6 +334,7 @@ "fiat_api": "不換紙幣 API", "fiat_balance": "フィアットバランス", "field_required": "この項目は必須です", + "file_saved": "保存されたファイル", "fill_code": "メールアドレスに記載されている確認コードを入力してください", "filter_by": "でフィルタリング", "first_wallet_text": "Monero、Bitcoin、Ethereum、Litecoin、Haven用の素晴らしいウォレット", @@ -540,9 +542,10 @@ "please_try_to_connect_to_another_node": "別のノードに接続してみてください", "please_wait": "お待ちください", "polygonscan_history": "ポリゴンスキャン履歴", + "potential_scam": "潜在的な詐欺", "powered_by": "搭載 ${title}", "pre_seed_button_text": "わかります。 種を見せて", - "pre_seed_description": "次のページでは、一連の${words}語が表示されます。 これはあなたのユニークでプライベートなシードであり、紛失や誤動作が発生した場合にウォレットを回復する唯一の方法です。 それを書き留めて、Cake Wallet アプリの外の安全な場所に保管するのはあなたの責任です。", + "pre_seed_description": "次のページには、一連の単語が表示されます。これはあなたのユニークでプライベートな種であり、損失や誤動作の場合に財布を回復する唯一の方法です。それを書き留めて、ケーキウォレットアプリの外の安全な場所に保管するのはあなたの責任です。", "pre_seed_title": "重要", "prepaid_cards": "プリペイドカード", "prevent_screenshots": "スクリーンショットと画面録画を防止する", @@ -682,9 +685,6 @@ "seedtype": "SeedType", "seedtype_alert_content": "他の財布と種子を共有することは、BIP39 SeedTypeでのみ可能です。", "seedtype_alert_title": "SeedTypeアラート", - "seedtype_legacy": "レガシー(25語)", - "seedtype_polyseed": "ポリシード(16語)", - "seedtype_wownero": "wownero(14ワード)", "select_backup_file": "バックアップファイルを選択", "select_buy_provider_notice": "上記の購入プロバイダーを選択してください。デフォルトの購入プロバイダーをアプリ設定で設定して、この画面をスキップできます。", "select_destination": "バックアップファイルの保存先を選択してください。", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 19d21fa5b..bef6d359b 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "신뢰할 수 있는 출처를 통해 토큰 컨트랙트 주소와 정보를 확인했습니다. 악의적이거나 잘못된 정보를 추가하면 자금 손실이 발생할 수 있습니다.", "add_token_warning": "사기꾼의 지시에 따라 토큰을 편집하거나 추가하지 마십시오.\n항상 신뢰할 수 있는 출처를 통해 토큰 주소를 확인하세요!", "add_value": "값 추가", + "added_message_for_ata_error": "거래를 완료하기에 충분한 SOL 잔액이 있는지 확인하십시오.", "address": "주소", "address_book": "주소록", "address_book_menu": "주소록", @@ -333,6 +334,7 @@ "fiat_api": "명목 화폐 API", "fiat_balance": "피아트 잔액", "field_required": "이 필드는 필수입니다", + "file_saved": "파일이 저장되었습니다", "fill_code": "이메일에 제공된 인증 코드를 입력하세요.", "filter_by": "필터링 기준", "first_wallet_text": "Monero, Bitcoin, Ethereum, Litecoin 및 Haven을 위한 멋진 지갑", @@ -539,9 +541,10 @@ "please_try_to_connect_to_another_node": "다른 노드에 연결을 시도하십시오", "please_wait": "기다리세요", "polygonscan_history": "다각형 스캔 기록", + "potential_scam": "잠재적 사기", "powered_by": "에 의해 구동 ${title}", "pre_seed_button_text": "이해 했어요. 내 씨앗을 보여줘", - "pre_seed_description": "다음 페이지에서 ${words} 개의 단어를 볼 수 있습니다. 이것은 귀하의 고유하고 개인적인 시드이며 분실 또는 오작동시 지갑을 복구하는 유일한 방법입니다. 기록해두고 Cake Wallet 앱 외부의 안전한 장소에 보관하는 것은 귀하의 책임입니다.", + "pre_seed_description": "다음 페이지에는 일련의 단어가 표시됩니다. 이것은 독특하고 개인적인 씨앗이며 손실이나 오작동의 경우 지갑을 회수하는 유일한 방법입니다. 케이크 지갑 앱 외부의 안전한 장소에 그것을 적어두고 보관하는 것은 귀하의 책임입니다.", "pre_seed_title": "중대한", "prepaid_cards": "선불 카드", "prevent_screenshots": "스크린샷 및 화면 녹화 방지", @@ -681,9 +684,6 @@ "seedtype": "시드 타입", "seedtype_alert_content": "다른 지갑과 씨앗을 공유하는 것은 BIP39 SeedType에서만 가능합니다.", "seedtype_alert_title": "종자 경보", - "seedtype_legacy": "레거시 (25 단어)", - "seedtype_polyseed": "다문 (16 단어)", - "seedtype_wownero": "Wownero (14 단어)", "select_backup_file": "백업 파일 선택", "select_buy_provider_notice": "위의 구매 제공자를 선택하십시오. 앱 설정에서 기본 구매 제공자를 설정 하여이 화면을 건너 뛸 수 있습니다.", "select_destination": "백업 파일의 대상을 선택하십시오.", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 06c941e0b..648650b3a 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "ဂုဏ်သိက္ခာရှိသော အရင်းအမြစ်ကို အသုံးပြု၍ တိုကင်စာချုပ်လိပ်စာနှင့် အချက်အလက်ကို ကျွန်ုပ်အတည်ပြုပြီးဖြစ်သည်။ အန္တရာယ်ရှိသော သို့မဟုတ် မမှန်ကန်သော အချက်အလက်များကို ထည့်သွင်းခြင်းသည် ရန်ပုံငွေများ ဆုံးရှုံးသွားနိုင်သည်။", "add_token_warning": "လိမ်လည်သူများ ညွှန်ကြားထားသည့်အတိုင်း တိုကင်များကို တည်းဖြတ်ခြင်း သို့မဟုတ် မထည့်ပါနှင့်။\nဂုဏ်သိက္ခာရှိသော အရင်းအမြစ်များဖြင့် အမြဲတမ်း တိုကင်လိပ်စာများကို အတည်ပြုပါ။", "add_value": "တန်ဖိုးထည့်ပါ။", + "added_message_for_ata_error": "ကြင်နာစွာသင့်ငွေပေးငွေယူကိုဖြည့်စွက်ရန်သင်အလုံအလောက် sol မျှတမှုရှိသည်။", "address": "လိပ်စာ", "address_book": "လိပ်စာစာအုပ်", "address_book_menu": "လိပ်စာစာအုပ်", @@ -333,6 +334,7 @@ "fiat_api": "Fiat API", "fiat_balance": "Fiat Balance", "field_required": "ဤစာကွက်လပ်မှာဖြည့်ရန်လိုအပ်ပါသည်", + "file_saved": "သိမ်းဆည်းထားသည့်ဖိုင်", "fill_code": "သင့်အီးမေးလ်သို့ ပေးထားသည့် အတည်ပြုကုဒ်ကို ဖြည့်ပါ။", "filter_by": "အလိုက် စစ်ထုတ်ပါ။", "first_wallet_text": "Monero၊ Bitcoin၊ Ethereum၊ Litecoin နှင့် Haven အတွက် အလွန်ကောင်းမွန်သော ပိုက်ဆံအိတ်", @@ -539,9 +541,10 @@ "please_try_to_connect_to_another_node": "အခြား node သို့ ချိတ်ဆက်ရန် ကြိုးစားပါ။", "please_wait": "ကျေးဇူးပြုပြီးခဏစောင့်ပါ", "polygonscan_history": "PolygonScan မှတ်တမ်း", + "potential_scam": "အလားအလာရှိသောလိမ်လည်မှု", "powered_by": "${title} မှ ပံ့ပိုးပေးသည်", "pre_seed_button_text": "ကျွန်တော်နားလည်ပါတယ်။ ငါ့အမျိုးအနွယ်ကို ပြလော့", - "pre_seed_description": "နောက်စာမျက်နှာတွင် ${words} စကားလုံးများ အတွဲလိုက်ကို တွေ့ရပါမည်။ ၎င်းသည် သင်၏ထူးခြားပြီး သီးသန့်မျိုးစေ့ဖြစ်ပြီး ပျောက်ဆုံးခြင်း သို့မဟုတ် ချွတ်ယွင်းမှုရှိပါက သင့်ပိုက်ဆံအိတ်ကို ပြန်လည်ရယူရန် တစ်ခုတည်းသောနည်းလမ်းဖြစ်သည်။ ၎င်းကို Cake Wallet အက်ပ်၏အပြင်ဘက်တွင် လုံခြုံသောနေရာတွင် သိမ်းဆည်းရန်မှာ သင်၏တာဝန်ဖြစ်သည်။", + "pre_seed_description": "နောက်စာမျက်နှာမှာစကားလုံးတစ်လုံးကိုတွေ့ရလိမ့်မယ်။ ၎င်းသည်သင်၏ထူးခြားသည့်နှင့်ပုဂ္ဂလိကမျိုးစေ့ဖြစ်ပြီးဆုံးရှုံးမှုသို့မဟုတ်ချွတ်ယွင်းမှုကိစ္စတွင်သင်၏ပိုက်ဆံအိတ်ကိုပြန်လည်ရယူရန်တစ်ခုတည်းသောနည်းလမ်းဖြစ်သည်။ ၎င်းကိုရေးရန်နှင့်၎င်းကို Cake Wallet App အပြင်ဘက်တွင်လုံခြုံသောနေရာ၌သိမ်းဆည်းရန်သင်၏တာ 0 န်ဖြစ်သည်။", "pre_seed_title": "အရေးကြီးသည်။", "prepaid_cards": "ကြိုတင်ငွေဖြည့်ကဒ်များ", "prevent_screenshots": "ဖန်သားပြင်ဓာတ်ပုံများနှင့် မျက်နှာပြင်ရိုက်ကူးခြင်းကို တားဆီးပါ။", @@ -681,9 +684,6 @@ "seedtype": "မျိုးပွားခြင်း", "seedtype_alert_content": "အခြားပိုက်ဆံအိတ်များနှင့်မျိုးစေ့များကိုမျှဝေခြင်းသည် BIP39 sebyspe ဖြင့်သာဖြစ်သည်။", "seedtype_alert_title": "ပျိုးပင်သတိပေးချက်", - "seedtype_legacy": "အမွေအနှစ် (စကားလုံး 25 လုံး)", - "seedtype_polyseed": "polyseed (စကားလုံး 16 လုံး)", - "seedtype_wownero": "Wownero (စကားလုံး 14 လုံး)", "select_backup_file": "အရန်ဖိုင်ကို ရွေးပါ။", "select_buy_provider_notice": "အပေါ်ကဝယ်သူတစ် ဦး ကိုရွေးချယ်ပါ။ သင်၏ default 0 ယ်သူအား app settings တွင် setting လုပ်ခြင်းဖြင့်ဤ screen ကိုကျော်သွားနိုင်သည်။", "select_destination": "အရန်ဖိုင်အတွက် ဦးတည်ရာကို ရွေးပါ။", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index a04f02c60..4cd39fa40 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "Ik heb het adres en de informatie van het tokencontract bevestigd met behulp van een betrouwbare bron. Het toevoegen van kwaadaardige of onjuiste informatie kan leiden tot verlies van geld.", "add_token_warning": "Bewerk of voeg geen tokens toe volgens de instructies van oplichters.\nBevestig tokenadressen altijd met betrouwbare bronnen!", "add_value": "Waarde toevoegen", + "added_message_for_ata_error": "Zorg ervoor dat u voldoende SOL -balans hebt om uw transactie te voltooien.", "address": "Adres", "address_book": "Adresboek", "address_book_menu": "Adresboek", @@ -333,6 +334,7 @@ "fiat_api": "Fiat API", "fiat_balance": "Fiat Balans", "field_required": "dit veld is verplicht", + "file_saved": "Bestand opgeslagen", "fill_code": "Vul de verificatiecode in die u in uw e-mail hebt ontvangen", "filter_by": "Filteren op", "first_wallet_text": "Geweldige portemonnee voor Monero, Bitcoin, Ethereum, Litecoin, en Haven", @@ -539,9 +541,10 @@ "please_try_to_connect_to_another_node": "Probeer verbinding te maken met een ander knooppunt", "please_wait": "Even geduld aub", "polygonscan_history": "PolygonScan-geschiedenis", + "potential_scam": "Potentiële zwendel", "powered_by": "Aangedreven door ${title}", "pre_seed_button_text": "Ik begrijp het. Laat me mijn zaad zien", - "pre_seed_description": "Op de volgende pagina ziet u een reeks van ${words} woorden. Dit is uw unieke en persoonlijke zaadje en het is de ENIGE manier om uw portemonnee te herstellen in geval van verlies of storing. Het is JOUW verantwoordelijkheid om het op te schrijven en op een veilige plaats op te slaan buiten de Cake Wallet app.", + "pre_seed_description": "Op de volgende pagina ziet u een reeks woorden. Dit is uw unieke en privézaad en het is de enige manier om uw portemonnee te herstellen in geval van verlies of storing. Het is uw verantwoordelijkheid om het op te schrijven en op te slaan op een veilige plek buiten de cake -portemonnee -app.", "pre_seed_title": "BELANGRIJK", "prepaid_cards": "Prepaid-kaarten", "prevent_screenshots": "Voorkom screenshots en schermopname", @@ -681,9 +684,6 @@ "seedtype": "Zaadtype", "seedtype_alert_content": "Het delen van zaden met andere portefeuilles is alleen mogelijk met BIP39 SeedType.", "seedtype_alert_title": "Zaadtype alert", - "seedtype_legacy": "Legacy (25 woorden)", - "seedtype_polyseed": "Polyseed (16 woorden)", - "seedtype_wownero": "WOWNERO (14 woorden)", "select_backup_file": "Selecteer een back-upbestand", "select_buy_provider_notice": "Selecteer hierboven een koopprovider. U kunt dit scherm overslaan door uw standaard kopenprovider in te stellen in app -instellingen.", "select_destination": "Selecteer de bestemming voor het back-upbestand.", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 2b55dd71f..5605603d9 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "Potwierdziłem adres kontraktu tokena i informacje, korzystając z renomowanego źródła. Dodanie złośliwych lub niepoprawnych informacji może spowodować utratę środków.", "add_token_warning": "Nie edytuj ani nie dodawaj tokenów zgodnie z instrukcjami oszustów.\nZawsze potwierdzaj adresy tokenów z renomowanymi źródłami!", "add_value": "Dodaj wartość", + "added_message_for_ata_error": "Upewnij się, że masz wystarczającą saldo SOL, aby ukończyć transakcję.", "address": "Adres", "address_book": "Kontakty", "address_book_menu": "Kontakty", @@ -333,6 +334,7 @@ "fiat_api": "API Walut FIAT", "fiat_balance": "Bilans Fiata", "field_required": "To pole jest wymagane", + "file_saved": "Zapisano plik", "fill_code": "Proszę wpisać kod weryfikacyjny który otrzymałeś w wiadomości e-mail", "filter_by": "Filtruj według", "first_wallet_text": "Świetny portfel na Monero, Bitcoin, Ethereum, Litecoin, i Haven", @@ -539,9 +541,10 @@ "please_try_to_connect_to_another_node": "Spróbuj połączyć się z innym węzłem", "please_wait": "Proszę czekać", "polygonscan_history": "Historia PolygonScan", + "potential_scam": "Potencjalne oszustwo", "powered_by": "Obsługiwane przez ${title}", "pre_seed_button_text": "Rozumiem. Pokaż mi moją fraze seed", - "pre_seed_description": "Na następnej stronie zobaczysz serię ${words} słów. To jest Twoja unikalna i prywatna fraza seed i jest to JEDYNY sposób na odzyskanie portfela w przypadku utraty lub awarii telefonu. Twoim obowiązkiem jest zapisanie go i przechowywanie w bezpiecznym miejscu (np. na kartce w sejfie).", + "pre_seed_description": "Na następnej stronie zobaczysz serię słów. To jest twoje wyjątkowe i prywatne ziarno i jest to jedyny sposób na odzyskanie portfela w przypadku utraty lub awarii. Twoim obowiązkiem jest zapisanie go i przechowywanie w bezpiecznym miejscu poza aplikacją do portfela ciasta.", "pre_seed_title": "WAŻNE", "prepaid_cards": "Karty przedpłacone", "prevent_screenshots": "Zapobiegaj zrzutom ekranu i nagrywaniu ekranu", @@ -681,9 +684,6 @@ "seedtype": "Sedtype", "seedtype_alert_content": "Dzielenie się nasionami z innymi portfelami jest możliwe tylko z BIP39 sededType.", "seedtype_alert_title": "Ustanowienie typu sedype", - "seedtype_legacy": "Legacy (25 słów)", - "seedtype_polyseed": "Polyseed (16 słów)", - "seedtype_wownero": "Wowero (14 słów)", "select_backup_file": "Wybierz plik kopii zapasowej", "select_buy_provider_notice": "Wybierz powyższe dostawcę zakupu. Możesz pominąć ten ekran, ustawiając domyślnego dostawcę zakupu w ustawieniach aplikacji.", "select_destination": "Wybierz miejsce docelowe dla pliku kopii zapasowej.", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 741187be4..390ab40b9 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "Confirmei o endereço e as informações do contrato de token usando uma fonte confiável. Adicionar informações maliciosas ou incorretas pode resultar em perda de fundos.", "add_token_warning": "Não edite ou adicione tokens de acordo com as instruções dos golpistas.\nSempre confirme os endereços de token com fontes confiáveis!", "add_value": "Adicionar valor", + "added_message_for_ata_error": "Por favor, certifique -se de ter um equilíbrio SOL suficiente para concluir sua transação.", "address": "Endereço", "address_book": "Livro de endereços", "address_book_menu": "Livro de endereços", @@ -333,6 +334,7 @@ "fiat_api": "API da Fiat", "fiat_balance": "Equilíbrio Fiat", "field_required": "Este campo é obrigatório", + "file_saved": "Arquivo salvo", "fill_code": "Por favor, preencha o código de verificação fornecido ao seu e-mail", "filter_by": "Filtrar por", "first_wallet_text": "Carteira incrível para Monero, Bitcoin, Ethereum, Litecoin, e Haven", @@ -541,9 +543,10 @@ "please_try_to_connect_to_another_node": "Por favor, tente conectar-se a outro nó", "please_wait": "Por favor, aguarde", "polygonscan_history": "História do PolygonScan", + "potential_scam": "Golpe potencial", "powered_by": "Troca realizada por ${title}", "pre_seed_button_text": "Compreendo. Me mostre minha semente", - "pre_seed_description": "Na próxima página, você verá uma série de ${words} palavras. Esta é a sua semente única e privada e é a ÚNICA maneira de recuperar sua carteira em caso de perda ou mau funcionamento. É SUA responsabilidade anotá-lo e armazená-lo em um local seguro fora do aplicativo Cake Wallet.", + "pre_seed_description": "Na próxima página, você verá uma série de palavras. Esta é a sua semente única e privada e é a única maneira de recuperar sua carteira em caso de perda ou mau funcionamento. É sua responsabilidade anotar e armazená -lo em um local seguro fora do aplicativo de carteira de bolo.", "pre_seed_title": "IMPORTANTE", "prepaid_cards": "Cartões pré-pagos", "prevent_screenshots": "Evite capturas de tela e gravação de tela", @@ -683,9 +686,6 @@ "seedtype": "SeedType", "seedtype_alert_content": "Compartilhar sementes com outras carteiras só é possível com o BIP39 SeedType.", "seedtype_alert_title": "Alerta de SeedType", - "seedtype_legacy": "Legado (25 palavras)", - "seedtype_polyseed": "Polyseed (16 palavras)", - "seedtype_wownero": "Wowrone (14 palavras)", "select_backup_file": "Selecione o arquivo de backup", "select_buy_provider_notice": "Selecione um provedor de compra acima. Você pode pular esta tela definindo seu provedor de compra padrão nas configurações de aplicativos.", "select_destination": "Selecione o destino para o arquivo de backup.", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index 5b7c72202..848c7dd57 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "Я подтвердил адрес контракта токена и информацию, используя авторитетный источник. Добавление вредоносной или неверной информации может привести к потере средств.", "add_token_warning": "Не редактируйте и не добавляйте токены по указанию мошенников.\nВсегда подтверждайте адреса токенов из авторитетных источников!", "add_value": "Добавить значение", + "added_message_for_ata_error": "Пожалуйста, убедитесь, что у вас достаточно баланса Sol, чтобы завершить вашу транзакцию.", "address": "Адрес", "address_book": "Адресная книга", "address_book_menu": "Адресная книга", @@ -333,6 +334,7 @@ "fiat_api": "Фиат API", "fiat_balance": "Фиатный баланс", "field_required": "Это поле обязательно к заполнению", + "file_saved": "Файл сохранен", "fill_code": "Пожалуйста, введите код подтверждения, отправленный на вашу электронную почту", "filter_by": "Фильтровать по", "first_wallet_text": "В самом удобном кошельке для Monero, Bitcoin, Ethereum, Litecoin, и Haven", @@ -540,9 +542,10 @@ "please_try_to_connect_to_another_node": "Пожалуйста, попробуйте подключиться к другой ноде", "please_wait": "Пожалуйста, подождите", "polygonscan_history": "История PolygonScan", + "potential_scam": "Потенциальная афера", "powered_by": "Используя ${title}", "pre_seed_button_text": "Понятно. Покажите мнемоническую фразу", - "pre_seed_description": "На следующей странице вы увидите серию из ${words} слов. Это ваша уникальная и личная мнемоническая фраза, и это ЕДИНСТВЕННЫЙ способ восстановить свой кошелек в случае потери или неисправности. ВАМ необходимо записать ее и хранить в надежном месте вне приложения Cake Wallet.", + "pre_seed_description": "На следующей странице вы увидите серию слов. Это ваше уникальное и частное семя, и это единственный способ восстановить ваш кошелек в случае потери или неисправности. Вы несете ответственность за то, чтобы записать его и хранить в безопасном месте за пределами приложения кошелька для торта.", "pre_seed_title": "ВАЖНО", "prepaid_cards": "Предоплаченные карты", "prevent_screenshots": "Предотвратить скриншоты и запись экрана", @@ -682,9 +685,6 @@ "seedtype": "SEEDTYPE", "seedtype_alert_content": "Обмен семенами с другими кошельками возможно только с BIP39 SeedType.", "seedtype_alert_title": "SEEDTYPE ALERT", - "seedtype_legacy": "Наследие (25 слов)", - "seedtype_polyseed": "Полиса (16 слов)", - "seedtype_wownero": "Wownero (14 слов)", "select_backup_file": "Выберите файл резервной копии", "select_buy_provider_notice": "Выберите поставщика покупки выше. Вы можете пропустить этот экран, установив поставщика покупки по умолчанию в настройках приложения.", "select_destination": "Пожалуйста, выберите место для файла резервной копии.", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 2fee871bc..e9063cb2a 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "ฉันได้ยืนยันที่อยู่และข้อมูลของสัญญาโทเค็นโดยใช้แหล่งข้อมูลที่เชื่อถือได้ การเพิ่มข้อมูลที่เป็นอันตรายหรือไม่ถูกต้องอาจทำให้สูญเสียเงินได้", "add_token_warning": "ห้ามแก้ไขหรือเพิ่มโทเค็นตามคำแนะนำของนักต้มตุ๋น\nยืนยันที่อยู่โทเค็นกับแหล่งที่มาที่เชื่อถือได้เสมอ!", "add_value": "เพิ่มมูลค่า", + "added_message_for_ata_error": "กรุณาตรวจสอบให้แน่ใจว่าคุณมียอดคงเหลือโซลเพียงพอที่จะทำธุรกรรมให้เสร็จสมบูรณ์", "address": "ที่อยู่", "address_book": "สมุดที่อยู่", "address_book_menu": "สมุดที่อยู่", @@ -333,6 +334,7 @@ "fiat_api": "API สกุลเงินตรา", "fiat_balance": "เฟียต บาลานซ์", "field_required": "ช่องนี้จำเป็น", + "file_saved": "บันทึกไฟล์", "fill_code": "โปรดกรอกรหัสยืนยันที่ส่งไปยังอีเมลของคุณ", "filter_by": "กรองตาม", "first_wallet_text": "กระเป๋าสตางค์ที่สวยงามสำหรับ Monero, Bitcoin, Ethereum, Litecoin และ Haven", @@ -539,9 +541,10 @@ "please_try_to_connect_to_another_node": "โปรดลองเชื่อมต่อกับโหนดอื่น", "please_wait": "โปรดรอ", "polygonscan_history": "ประวัติ PolygonScan", + "potential_scam": "การหลอกลวงที่มีศักยภาพ", "powered_by": "พัฒนาขึ้นโดย ${title}", "pre_seed_button_text": "ฉันเข้าใจ แสดง seed ของฉัน", - "pre_seed_description": "บนหน้าถัดไปคุณจะเห็นชุดของคำ ${words} คำ นี่คือ seed ของคุณที่ไม่ซ้ำใดๆ และเป็นความลับเพียงของคุณ และนี่คือเพียงวิธีเดียวที่จะกู้กระเป๋าของคุณในกรณีที่สูญหายหรือมีปัญหา มันเป็นความรับผิดชอบของคุณเพื่อเขียนมันลงบนกระดาษและจัดเก็บไว้ในที่ปลอดภัยนอกแอป Cake Wallet", + "pre_seed_description": "ในหน้าถัดไปคุณจะเห็นชุดคำ นี่คือเมล็ดพันธุ์ที่ไม่เหมือนใครและเป็นส่วนตัวของคุณและเป็นวิธีเดียวที่จะกู้คืนกระเป๋าเงินของคุณในกรณีที่สูญเสียหรือทำงานผิดปกติ มันเป็นความรับผิดชอบของคุณที่จะเขียนลงและเก็บไว้ในที่ปลอดภัยนอกแอพเค้กกระเป๋าเงิน", "pre_seed_title": "สำคัญ", "prepaid_cards": "บัตรเติมเงิน", "prevent_screenshots": "ป้องกันภาพหน้าจอและการบันทึกหน้าจอ", @@ -681,9 +684,6 @@ "seedtype": "เมล็ดพันธุ์", "seedtype_alert_content": "การแบ่งปันเมล็ดกับกระเป๋าเงินอื่น ๆ เป็นไปได้เฉพาะกับ bip39 seedtype", "seedtype_alert_title": "การแจ้งเตือน seedtype", - "seedtype_legacy": "มรดก (25 คำ)", - "seedtype_polyseed": "โพลีส (16 คำ)", - "seedtype_wownero": "wownero (14 คำ)", "select_backup_file": "เลือกไฟล์สำรอง", "select_buy_provider_notice": "เลือกผู้ให้บริการซื้อด้านบน คุณสามารถข้ามหน้าจอนี้ได้โดยการตั้งค่าผู้ให้บริการซื้อเริ่มต้นในการตั้งค่าแอป", "select_destination": "โปรดเลือกปลายทางสำหรับไฟล์สำรอง", diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index a8a7c2da5..a390b218a 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "Kinumpirma ko ang address ng kontrata ng token at impormasyon gamit ang isang kagalang-galang na mapagkukunan. Ang pagdaragdag ng nakakahamak o hindi tamang impormasyon ay maaaring magresulta sa pagkawala ng mga pondo.", "add_token_warning": "Huwag i-edit o magdagdag ng mga token tulad ng itinuro ng mga scammers.\nLaging kumpirmahin ang mga token address na may mga kagalang-galang na mapagkukunan!", "add_value": "Magdagdag ng halaga", + "added_message_for_ata_error": "Mabuting tiyakin na mayroon kang sapat na balanse ng sol upang makumpleto ang iyong transaksyon.", "address": "Address", "address_book": "Address Book", "address_book_menu": "Address book", @@ -333,6 +334,7 @@ "fiat_api": "Fiat API", "fiat_balance": "Balanse ng fiat", "field_required": "Kinakailangan ang patlang na ito", + "file_saved": "Nai -save ang file", "fill_code": "Mangyaring ilagay ang verfification code na ibinigay sa iyong email", "filter_by": "Filter ni", "first_wallet_text": "Kahanga-hangang wallet para sa Monero, Bitcoin, Litecoin, Ethereum, at Haven", @@ -539,9 +541,10 @@ "please_try_to_connect_to_another_node": "Pakisubukang kumonekta sa iba pang node", "please_wait": "Mangyaring maghintay", "polygonscan_history": "Kasaysayan ng PolygonScan", + "potential_scam": "Potensyal na scam", "powered_by": "Pinapagana ng ${title}", "pre_seed_button_text": "Naiintindihan ko. Ipakita sa akin ang aking binhi", - "pre_seed_description": "Sa susunod na pahina makikita mo ang isang serye ng mga ${words} na mga salita. Ito ang iyong natatangi at pribadong binhi at ito ang tanging paraan upang mabawi ang iyong pitaka kung sakaling mawala o madepektong paggawa. Responsibilidad mong isulat ito at itago ito sa isang ligtas na lugar sa labas ng cake wallet app.", + "pre_seed_description": "Sa susunod na pahina makikita mo ang isang serye ng mga salita. Ito ang iyong natatangi at pribadong binhi at ito ang tanging paraan upang mabawi ang iyong pitaka kung sakaling mawala o madepektong paggawa. Responsibilidad mong isulat ito at itago ito sa isang ligtas na lugar sa labas ng cake wallet app.", "pre_seed_title": "Mahalaga", "prepaid_cards": "Mga Prepaid Card", "prevent_screenshots": "Maiwasan ang mga screenshot at pag -record ng screen", @@ -681,9 +684,6 @@ "seedtype": "Seed type", "seedtype_alert_content": "Ang pagbabahagi ng mga buto sa iba pang mga pitaka ay posible lamang sa bip39 seedtype.", "seedtype_alert_title": "Alerto ng Seedtype", - "seedtype_legacy": "Legacy (25 na salita)", - "seedtype_polyseed": "Polyseed (16 na salita)", - "seedtype_wownero": "Wownero (14 na salita)", "select_backup_file": "Piliin ang backup na file", "select_buy_provider_notice": "Pumili ng provider ng pagbili sa itaas. Maaari mong laktawan ang screen na ito sa pamamagitan ng pagtatakda ng iyong default na provider ng pagbili sa mga setting ng app.", "select_destination": "Mangyaring piliin ang patutunguhan para sa backup na file.", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 07433ac13..c4bd16b24 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "Belirteç sözleşmesi adresini ve bilgilerini saygın bir kaynak kullanarak onayladım. Kötü amaçlı veya yanlış bilgilerin eklenmesi para kaybına neden olabilir.", "add_token_warning": "Dolandırıcıların talimatına göre jetonları düzenlemeyin veya eklemeyin.\nBelirteç adreslerini her zaman saygın kaynaklarla onaylayın!", "add_value": "Değer ekle", + "added_message_for_ata_error": "Lütfen işleminizi tamamlamak için yeterli SOL bakiyesine sahip olduğunuzdan emin olun.", "address": "Adres", "address_book": "Adres Defteri", "address_book_menu": "Adres defteri", @@ -333,6 +334,7 @@ "fiat_api": "İtibari Para API", "fiat_balance": "Fiat Bakiyesi", "field_required": "Bu alan gereklidir", + "file_saved": "Dosya kaydedildi", "fill_code": "Lütfen e-postanıza gelen doğrulama kodunu girin", "filter_by": "Şuna göre filtrele", "first_wallet_text": "Monero, Bitcoin, Ethereum, Litecoin ve Haven için harika cüzdan", @@ -539,9 +541,10 @@ "please_try_to_connect_to_another_node": "Lütfen farklı düğüme bağlanmayı dene", "please_wait": "Lütfen bekleyin", "polygonscan_history": "PolygonScan geçmişi", + "potential_scam": "Potansiyel aldatmaca", "powered_by": "${title} tarafından desteklenmektedir", "pre_seed_button_text": "Anladım. Bana tohumumu göster.", - "pre_seed_description": "Bir sonraki sayfada ${words} kelime göreceksin. Bu senin benzersiz ve özel tohumundur, kaybetmen veya silinmesi durumunda cüzdanını kurtarmanın TEK YOLUDUR. Bunu yazmak ve Cake Wallet uygulaması dışında güvenli bir yerde saklamak tamamen SENİN sorumluluğunda.", + "pre_seed_description": "Bir sonraki sayfada bir dizi kelime göreceksiniz. Bu sizin eşsiz ve özel tohumunuzdur ve kayıp veya arıza durumunda cüzdanınızı kurtarmanın tek yolu budur. Yazmak ve kek cüzdan uygulamasının dışında güvenli bir yerde saklamak sizin sorumluluğunuzdadır.", "pre_seed_title": "UYARI", "prepaid_cards": "Ön ödemeli kartlar", "prevent_screenshots": "Ekran görüntülerini ve ekran kaydını önleyin", @@ -681,9 +684,6 @@ "seedtype": "Tohum", "seedtype_alert_content": "Tohumları diğer cüzdanlarla paylaşmak sadece BIP39 tohumu ile mümkündür.", "seedtype_alert_title": "SeedType uyarısı", - "seedtype_legacy": "Miras (25 kelime)", - "seedtype_polyseed": "Polyseed (16 kelime)", - "seedtype_wownero": "Wownero (14 kelime)", "select_backup_file": "Yedek dosyası seç", "select_buy_provider_notice": "Yukarıda bir satın alma sağlayıcısı seçin. App ayarlarında varsayılan satın alma sağlayıcınızı ayarlayarak bu ekranı atlayabilirsiniz.", "select_destination": "Lütfen yedekleme dosyası için hedef seçin.", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 2699c8d62..48039f0de 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "Я підтвердив адресу та інформацію щодо договору маркера, використовуючи авторитетне джерело. Додавання зловмисної або невірної інформації може призвести до втрати коштів.", "add_token_warning": "Не редагуйте та не додавайте токени за вказівками шахраїв.\nЗавжди підтверджуйте адреси токенів у авторитетних джерелах!", "add_value": "Додати значення", + "added_message_for_ata_error": "Будь ласка, переконайтеся, що у вас є достатньо балансу SOL, щоб завершити свою транзакцію.", "address": "Адреса", "address_book": "Адресна книга", "address_book_menu": "Адресна книга", @@ -333,6 +334,7 @@ "fiat_api": "Фіат API", "fiat_balance": "Фіат Баланс", "field_required": "Це поле є обов'язковим", + "file_saved": "Збережено файл", "fill_code": "Будь ласка, введіть код підтвердження, надісланий на вашу електронну адресу", "filter_by": "Фільтрувати по", "first_wallet_text": "В самому зручному гаманці для Monero, Bitcoin, Ethereum, Litecoin, та Haven", @@ -539,9 +541,10 @@ "please_try_to_connect_to_another_node": "Будь ласка, спробуйте підключитися до іншого вузлу", "please_wait": "Будь ласка, зачекайте", "polygonscan_history": "Історія PolygonScan", + "potential_scam": "Потенційна афера", "powered_by": "Використовуючи ${title}", "pre_seed_button_text": "Зрозуміло. Покажіть мнемонічну фразу", - "pre_seed_description": "На наступній сторінці ви побачите серію з ${words} слів. Це ваша унікальна та приватна мнемонічна фраза, і це ЄДИНИЙ спосіб відновити ваш гаманець на випадок втрати або несправності. ВАМ необхідно записати її та зберігати в безпечному місці поза програмою Cake Wallet.", + "pre_seed_description": "На наступній сторінці ви побачите ряд слів. Це ваше унікальне та приватне насіння, і це єдиний спосіб відновити гаманець у разі втрати або несправності. Ви несете відповідальність записати його та зберігати в безпечному місці поза додатком для гаманця тортів.", "pre_seed_title": "ВАЖЛИВО", "prepaid_cards": "Передплачені картки", "prevent_screenshots": "Запобігати знімкам екрана та запису екрана", @@ -682,9 +685,6 @@ "seedtype": "Насіннєвий тип", "seedtype_alert_content": "Спільний доступ до інших гаманців можливе лише за допомогою BIP39 Seedtype.", "seedtype_alert_title": "Попередження насінника", - "seedtype_legacy": "Спадщина (25 слів)", - "seedtype_polyseed": "Полісей (16 слів)", - "seedtype_wownero": "Влонеро (14 слів)", "select_backup_file": "Виберіть файл резервної копії", "select_buy_provider_notice": "Виберіть постачальника купівлі вище. Ви можете пропустити цей екран, встановивши свого постачальника купівлі за замовчуванням у налаштуваннях додатків.", "select_destination": "Виберіть місце призначення для файлу резервної копії.", diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 6fd6003e5..1582abdb6 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "میں نے ایک معتبر ذریعہ کا استعمال کرتے ہوئے ٹوکن کنٹریکٹ ایڈریس اور معلومات کی تصدیق کی ہے۔ بدنیتی پر مبنی یا غلط معلومات شامل کرنے کے نتیجے میں فنڈز ضائع ہو سکتے ہیں۔", "add_token_warning": "سکیمرز کی ہدایت کے مطابق ٹوکن میں ترمیم یا اضافہ نہ کریں۔\nہمیشہ معتبر ذرائع سے ٹوکن پتوں کی تصدیق کریں!", "add_value": "قدر شامل کریں۔", + "added_message_for_ata_error": "برائے مہربانی اس بات کو یقینی بنائیں کہ آپ کے لین دین کو مکمل کرنے کے ل enough آپ کے پاس کافی SOL توازن موجود ہے۔", "address": "ﮧﺘﭘ", "address_book": "ایڈریس بک", "address_book_menu": "ایڈریس بک", @@ -333,6 +334,7 @@ "fiat_api": "Fiat API", "fiat_balance": "فیاٹ بیلنس", "field_required": "اس کو پر کرنا ضروری ہے", + "file_saved": "فائل محفوظ ہوگئی", "fill_code": "براہ کرم اپنے ای میل پر فراہم کردہ تصدیقی کوڈ کو پُر کریں۔", "filter_by": "کی طرف سے فلٹر", "first_wallet_text": "Monero، Bitcoin، Ethereum، Litecoin، اور Haven کے لیے زبردست پرس", @@ -541,9 +543,10 @@ "please_try_to_connect_to_another_node": "براہ کرم کسی دوسرے نوڈ سے جڑنے کی کوشش کریں۔", "please_wait": "برائے مہربانی انتظار کریں", "polygonscan_history": "ﺦﯾﺭﺎﺗ ﯽﮐ ﻦﯿﮑﺳﺍ ﻥﻮﮔ ﯽﻟﻮﭘ", + "potential_scam": "ممکنہ گھوٹالہ", "powered_by": "${title} کے ذریعے تقویت یافتہ", "pre_seed_button_text": "میں سمجھتا ہوں۔ مجھے میرا بیج دکھاؤ", - "pre_seed_description": "اگلے صفحے پر آپ کو ${words} الفاظ کا ایک سلسلہ نظر آئے گا۔ یہ آپ کا انوکھا اور نجی بیج ہے اور یہ آپ کے بٹوے کو ضائع یا خرابی کی صورت میں بازیافت کرنے کا واحد طریقہ ہے۔ اسے لکھنا اور اسے کیک والیٹ ایپ سے باہر کسی محفوظ جگہ پر اسٹور کرنا آپ کی ذمہ داری ہے۔", + "pre_seed_description": "اگلے صفحے پر آپ کو الفاظ کا ایک سلسلہ نظر آئے گا۔ یہ آپ کا انوکھا اور نجی بیج ہے اور نقصان یا خرابی کی صورت میں اپنے بٹوے کی بازیابی کا واحد راستہ ہے۔ آپ کی ذمہ داری ہے کہ وہ اسے لکھیں اور اسے کیک پرس ایپ کے باہر کسی محفوظ جگہ پر محفوظ کریں۔", "pre_seed_title": "اہم", "prepaid_cards": "پری پیڈ کارڈز", "prevent_screenshots": "اسکرین شاٹس اور اسکرین ریکارڈنگ کو روکیں۔", @@ -683,9 +686,6 @@ "seedtype": "سیڈ ٹائپ", "seedtype_alert_content": "دوسرے بٹوے کے ساتھ بیجوں کا اشتراک صرف BIP39 بیج ٹائپ کے ساتھ ہی ممکن ہے۔", "seedtype_alert_title": "سیڈ ٹائپ الرٹ", - "seedtype_legacy": "میراث (25 الفاظ)", - "seedtype_polyseed": "پالیسیڈ (16 الفاظ)", - "seedtype_wownero": "واونرو (14 الفاظ)", "select_backup_file": "بیک اپ فائل کو منتخب کریں۔", "select_buy_provider_notice": "اوپر خریدنے والا خریدنے والا منتخب کریں۔ آپ ایپ کی ترتیبات میں اپنے پہلے سے طے شدہ خریدنے والے کو ترتیب دے کر اس اسکرین کو چھوڑ سکتے ہیں۔", "select_destination": "۔ﮟﯾﺮﮐ ﺏﺎﺨﺘﻧﺍ ﺎﮐ ﻝﺰﻨﻣ ﮯﯿﻟ ﮯﮐ ﻞﺋﺎﻓ ﭖﺍ ﮏﯿﺑ ﻡﺮﮐ ﮦﺍﺮﺑ", diff --git a/res/values/strings_vi.arb b/res/values/strings_vi.arb index 22168ad15..ef75afd57 100644 --- a/res/values/strings_vi.arb +++ b/res/values/strings_vi.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "Tôi đã xác nhận địa chỉ hợp đồng token và thông tin bằng nguồn đáng tin cậy. Thêm thông tin sai hoặc độc hại có thể dẫn đến mất tiền.", "add_token_warning": "Không chỉnh sửa hoặc thêm token theo yêu cầu của kẻ lừa đảo.\nLuôn xác nhận địa chỉ token từ các nguồn đáng tin cậy!", "add_value": "Thêm giá trị", + "added_message_for_ata_error": "Vui lòng đảm bảo bạn có đủ số dư SOL để hoàn thành giao dịch của mình.", "address": "Địa chỉ", "address_book": "Sổ địa chỉ", "address_book_menu": "Sổ địa chỉ", @@ -332,6 +333,7 @@ "fiat_api": "API Fiat", "fiat_balance": "Số dư Fiat", "field_required": "Trường này là bắt buộc", + "file_saved": "Tệp lưu", "fill_code": "Vui lòng điền mã xác minh được gửi đến email của bạn", "filter_by": "Lọc theo", "first_wallet_text": "Ví tuyệt vời cho Monero, Bitcoin, Ethereum, Litecoin, và Haven", @@ -537,9 +539,10 @@ "please_try_to_connect_to_another_node": "Vui lòng thử kết nối với một nút khác", "please_wait": "Vui lòng chờ", "polygonscan_history": "Lịch sử PolygonScan", + "potential_scam": "Lừa đảo tiềm năng", "powered_by": "Được cung cấp bởi ${title}", "pre_seed_button_text": "Tôi hiểu. Hiển thị hạt giống của tôi", - "pre_seed_description": "Trên trang tiếp theo, bạn sẽ thấy một chuỗi ${words} từ. Đây là hạt giống riêng tư và duy nhất của bạn và là CÁCH DUY NHẤT để khôi phục ví của bạn trong trường hợp mất hoặc hỏng hóc. Đây là TRÁCH NHIỆM của bạn để ghi lại và lưu trữ nó ở một nơi an toàn ngoài ứng dụng Cake Wallet.", + "pre_seed_description": "Trên trang tiếp theo, bạn sẽ thấy một loạt các từ. Đây là hạt giống độc đáo và riêng tư của bạn và đó là cách duy nhất để phục hồi ví của bạn trong trường hợp mất hoặc trục trặc. Bạn có trách nhiệm viết nó ra và lưu trữ nó ở một nơi an toàn bên ngoài ứng dụng ví Cake.", "pre_seed_title": "QUAN TRỌNG", "prepaid_cards": "Thẻ trả trước", "prevent_screenshots": "Ngăn chặn ảnh chụp màn hình và ghi hình màn hình", @@ -679,9 +682,6 @@ "seedtype": "Loại hạt giống", "seedtype_alert_content": "Chia sẻ hạt giống với ví khác chỉ có thể với BIP39 SeedType.", "seedtype_alert_title": "Cảnh báo hạt giống", - "seedtype_legacy": "Di sản (25 từ)", - "seedtype_polyseed": "Polyseed (16 từ)", - "seedtype_wownero": "Wownero (14 từ)", "select_backup_file": "Chọn tệp sao lưu", "select_buy_provider_notice": "Chọn nhà cung cấp mua ở trên. Bạn có thể bỏ qua màn hình này bằng cách thiết lập nhà cung cấp mua mặc định trong cài đặt ứng dụng.", "select_destination": "Vui lòng chọn đích cho tệp sao lưu.", diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index e750dffec..5e4cc331c 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "Mo ti jẹrisi adirẹsi adehun ami ati alaye nipa lilo orisun olokiki kan. Fifi irira tabi alaye ti ko tọ le ja si isonu ti owo.", "add_token_warning": "Ma ṣe ṣatunkọ tabi ṣafikun awọn ami bi a ti fun ni aṣẹ nipasẹ awọn scammers.\nNigbagbogbo jẹrisi awọn adirẹsi ami pẹlu awọn orisun olokiki!", "add_value": "Fikún owó", + "added_message_for_ata_error": "Daradara rii daju pe o ni iwọntunwọnsi Sol Sol lati pari iṣowo rẹ.", "address": "Adirẹsi", "address_book": "Ìwé Àdírẹ́sì", "address_book_menu": "Ìwé Àdírẹ́sì", @@ -334,6 +335,7 @@ "fiat_api": "Ojú ètò áàpù owó tí ìjọba pàṣẹ wa lò", "fiat_balance": "Fiat Iwontunws.funfun", "field_required": "E ni lati se nkan si aye yi", + "file_saved": "Faili faili ti o ti fipamọ", "fill_code": "Ẹ jọ̀wọ́ tẹ̀ ọ̀rọ̀ ìjẹ́rìísí t'á ti ránṣẹ́ sí ímeèlì yín.", "filter_by": "Ṣẹ́ láti", "first_wallet_text": "Àpamọ́wọ́ t'á fi Monero, Bitcoin, Ethereum, Litecoin, àti Haven pamọ́ wà pa", @@ -540,9 +542,10 @@ "please_try_to_connect_to_another_node": "Ẹ jọ̀wọ́, gbìyànjú dárapọ̀ mọ́ apẹka mìíràn yí wọlé", "please_wait": "Jọwọ saa", "polygonscan_history": "PolygonScan itan", + "potential_scam": "Egan ti o ni agbara", "powered_by": "Láti ọwọ́ ${title}", "pre_seed_button_text": "Mo ti gbọ́. O fi hóró mi hàn mi", - "pre_seed_description": "Ẹ máa wo àwọn ọ̀rọ̀ ${words} lórí ojú tó ń bọ̀. Èyí ni hóró aládàáni yín tó kì í jọra. Ẹ lè fi í nìkan dá àpamọ́wọ́ yín padà sípò tí àṣìṣe tàbí ìbàjẹ́ bá ṣẹlẹ̀. Hóró yín ni ẹ gbọ́dọ̀ kọ sílẹ̀ àti pamọ́ síbí tó kò léwu níta Cake Wallet.", + "pre_seed_description": "Ni oju-iwe ti o tẹle iwọ yoo rii lẹsẹsẹ awọn ọrọ. Eyi jẹ irugbin alailẹgbẹ ati ti ikọkọ ati pe o jẹ ọna nikan lati bọsi apamọwọ rẹ ni ọran ti pipadanu tabi aisise. O jẹ ojuṣe rẹ lati kọ ọ silẹ ki o tọju rẹ ni aye ailewu ti ita ti app Fifun akara naa.", "pre_seed_title": "Ó TI ṢE PÀTÀKÌ", "prepaid_cards": "Awọn kaadi ti a ti sanwo", "prevent_screenshots": "Pese asapọ ti awọn ẹrọ eto aṣa", @@ -682,9 +685,6 @@ "seedtype": "Irugbin-seetypu", "seedtype_alert_content": "Pinpin awọn irugbin pẹlu awọn gedo miiran ṣee ṣe pẹlu Bip39 irugbin.", "seedtype_alert_title": "Ṣajọpọ Seeytype", - "seedtype_legacy": "Legacy (awọn ọrọ 25)", - "seedtype_polyseed": "Polyseed (awọn ọrọ 16)", - "seedtype_wownero": "Wowero (awọn ọrọ 14)", "select_backup_file": "Select backup file", "select_buy_provider_notice": "Yan olupese Ra loke. O le skii iboju yii nipa ṣiṣeto olupese rẹ ni awọn eto App.", "select_destination": "Jọwọ yan ibi ti o nlo fun faili afẹyinti.", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 73a519b46..089d4bd41 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -21,6 +21,7 @@ "add_token_disclaimer_check": "我已使用信誉良好的来源确认了代币合约地址和信息。 添加恶意或不正确的信息可能会导致资金损失。", "add_token_warning": "请勿按照诈骗者的指示编辑或添加令牌。\n始终通过信誉良好的来源确认代币地址!", "add_value": "增加价值", + "added_message_for_ata_error": "请确保您有足够的SOL余额来完成交易。", "address": "地址", "address_book": "地址簿", "address_book_menu": "地址簿", @@ -333,6 +334,7 @@ "fiat_api": "法币API", "fiat_balance": "法币余额", "field_required": "此字段是必需的", + "file_saved": "保存的文件", "fill_code": "请填写提供给您邮箱的验证码", "filter_by": "过滤", "first_wallet_text": "适用于门罗币、比特币、以太坊、莱特币和避风港的超棒钱包", @@ -539,9 +541,10 @@ "please_try_to_connect_to_another_node": "请尝试连接到其他节点", "please_wait": "请稍等", "polygonscan_history": "多边形扫描历史", + "potential_scam": "潜在骗局", "powered_by": "Powered by ${title}", "pre_seed_button_text": "我明白。 查看种子", - "pre_seed_description": "在下一页上,您将看到${words}个文字。 这是您独有的种子,是丟失或出现故障时恢复钱包的唯一方法。 您有必须将其写下并储存在Cake Wallet应用程序以外的安全地方。", + "pre_seed_description": "在下一页上,您将看到一系列单词。这是您独特的私人种子,这是在损失或故障时恢复钱包的唯一方法。将其写下来并将其存储在蛋糕钱包应用程序外的安全地方是您的责任。", "pre_seed_title": "重要", "prepaid_cards": "预付费卡", "prevent_screenshots": "防止截屏和录屏", @@ -681,9 +684,6 @@ "seedtype": "籽粒", "seedtype_alert_content": "只有BIP39籽粒可以与其他钱包共享种子。", "seedtype_alert_title": "籽粒警报", - "seedtype_legacy": "遗产(25个单词)", - "seedtype_polyseed": "多种物品(16个单词)", - "seedtype_wownero": "沃恩罗(14个单词)", "select_backup_file": "选择备份文件", "select_buy_provider_notice": "在上面选择买入提供商。您可以通过在应用程序设置中设置默认的购买提供商来跳过此屏幕。", "select_destination": "请选择备份文件的目的地。", diff --git a/scripts/android/app_env.sh b/scripts/android/app_env.sh index 9b5685a07..4de7a3373 100644 --- a/scripts/android/app_env.sh +++ b/scripts/android/app_env.sh @@ -14,15 +14,15 @@ TYPES=($MONERO_COM $CAKEWALLET) APP_ANDROID_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.21.0" -MONERO_COM_BUILD_NUMBER=116 +MONERO_COM_VERSION="4.26.0" +MONERO_COM_BUILD_NUMBER=120 MONERO_COM_BUNDLE_ID="com.monero.app" MONERO_COM_PACKAGE="com.monero.app" MONERO_COM_SCHEME="monero.com" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="5.0.0" -CAKEWALLET_BUILD_NUMBER=254 +CAKEWALLET_VERSION="4.26.0" +CAKEWALLET_BUILD_NUMBER=258 CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet" CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet" CAKEWALLET_SCHEME="cakewallet" diff --git a/scripts/ios/app_env.sh b/scripts/ios/app_env.sh index a9e86bb4a..b42a8a2da 100644 --- a/scripts/ios/app_env.sh +++ b/scripts/ios/app_env.sh @@ -12,13 +12,13 @@ TYPES=($MONERO_COM $CAKEWALLET) APP_IOS_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.21.0" -MONERO_COM_BUILD_NUMBER=114 +MONERO_COM_VERSION="4.26.0" +MONERO_COM_BUILD_NUMBER=117 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="5.0.0" -CAKEWALLET_BUILD_NUMBER=308 +CAKEWALLET_VERSION="4.26.0" +CAKEWALLET_BUILD_NUMBER=312 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" diff --git a/scripts/ios/app_icon.sh b/scripts/ios/app_icon.sh index e3eb36b3c..dcb168888 100755 --- a/scripts/ios/app_icon.sh +++ b/scripts/ios/app_icon.sh @@ -1,24 +1,101 @@ #!/bin/sh -ICON_120_PATH="" -ICON_180_PATH="" -ICON_1024_PATH="" +ICON_2x_PATH="" +ICON_2x_ipad_PATH="" +ICON_3x_PATH="" +ICON_20_2x_PATH="" +ICON_20_2x_ipad_PATH="" +ICON_20_3x_PATH="" +ICON_20_ipad_PATH="" +ICON_29_PATH="" +ICON_29_2x_PATH="" +ICON_29_2x_ipad_PATH="" +ICON_29_3x_PATH="" +ICON_29_3x_ipad_PATH="" +ICON_29_ipad_PATH="" +ICON_40_2x_PATH="" +ICON_40_2x_ipad_PATH="" +ICON_40_3x_PATH="" +ICON_40_ipad_PATH="" +ICON_60_2x_PATH="" +ICON_60_3x_PATH="" +ICON_83_2x_ipad_PATH="" +ICON_marketing_PATH="" +ICON_ipad_PATH="" + +ICON_DIRECTORY="" + DEST_DIR_PATH=`pwd`/../../ios/Runner/Assets.xcassets/AppIcon.appiconset case $APP_IOS_TYPE in "monero.com") - ICON_120_PATH=`pwd`/../../assets/images/monero.com_icon_120.png - ICON_180_PATH=`pwd`/../../assets/images/monero.com_icon_180.png - ICON_1024_PATH=`pwd`/../../assets/images/monero.com_icon_1024.png;; + ICON_DIRECTORY=monero_ios_icons;; "cakewallet") - ICON_120_PATH=`pwd`/../../assets/images/cakewallet_icon_120.png - ICON_180_PATH=`pwd`/../../assets/images/cakewallet_icon_180.png - ICON_1024_PATH=`pwd`/../../assets/images/cakewallet_icon_1024.png;; + ICON_DIRECTORY=cakewallet_ios_icons;; esac -rm $DEST_DIR_PATH/app_icon_120.png -rm $DEST_DIR_PATH/app_icon_180.png -rm $DEST_DIR_PATH/app_icon_1024.png -ln -s $ICON_120_PATH $DEST_DIR_PATH/app_icon_120.png -ln -s $ICON_180_PATH $DEST_DIR_PATH/app_icon_180.png -ln -s $ICON_1024_PATH $DEST_DIR_PATH/app_icon_1024.png \ No newline at end of file +ICON_2x_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon@2x.png +ICON_2x_ipad_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon@2x~ipad.png +ICON_3x_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon@3x.png +ICON_20_2x_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon-20@2x.png +ICON_20_2x_ipad_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon-20@2x~ipad.png +ICON_20_3x_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon-20@3x.png +ICON_20_ipad_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon-20~ipad.png +ICON_29_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon-29.png +ICON_29_2x_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon-29@2x.png +ICON_29_2x_ipad_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon-29@2x~ipad.png +ICON_29_3x_ipad_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon-29@3x.png +ICON_29_ipad_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon-29~ipad.png +ICON_40_2x_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon-40@2x.png +ICON_40_2x_ipad_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon-40@2x~ipad.png +ICON_40_3x_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon-40@3x.png +ICON_40_ipad_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon-40~ipad.png +ICON_60_2x_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon-60@2x~car.png +ICON_60_3x_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon-60@3x~car.png +ICON_83_2x_ipad_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon-83.5@2x~ipad.png +ICON_marketing_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon~ios-marketing.png +ICON_ipad_PATH=`pwd`/../../assets/images/ios_icons/$ICON_DIRECTORY/AppIcon~ipad.png + +rm $DEST_DIR_PATH/AppIcon@2x.png +rm $DEST_DIR_PATH/AppIcon@2x~ipad.png +rm $DEST_DIR_PATH/AppIcon@3x.png +rm $DEST_DIR_PATH/AppIcon-20@2x.png +rm $DEST_DIR_PATH/AppIcon-20@2x~ipad.png +rm $DEST_DIR_PATH/AppIcon-20@3x.png +rm $DEST_DIR_PATH/AppIcon-20~ipad.png +rm $DEST_DIR_PATH/AppIcon-29.png +rm $DEST_DIR_PATH/AppIcon-29@2x.png +rm $DEST_DIR_PATH/AppIcon-29@2x~ipad.png +rm $DEST_DIR_PATH/AppIcon-29@3x.png +rm $DEST_DIR_PATH/AppIcon-29~ipad.png +rm $DEST_DIR_PATH/AppIcon-40@2x.png +rm $DEST_DIR_PATH/AppIcon-40@2x~ipad.png +rm $DEST_DIR_PATH/AppIcon-40@3x.png +rm $DEST_DIR_PATH/AppIcon-40~ipad.png +rm $DEST_DIR_PATH/AppIcon-60@2x~car.png +rm $DEST_DIR_PATH/AppIcon-60@3x~car.png +rm $DEST_DIR_PATH/AppIcon-83.5@2x~ipad.png +rm $DEST_DIR_PATH/AppIcon~ios-marketing.png +rm $DEST_DIR_PATH/AppIcon~ipad.png + +ln -s $ICON_2x_PATH $DEST_DIR_PATH/AppIcon@2x.png +ln -s $ICON_2x_ipad_PATH $DEST_DIR_PATH/AppIcon@2x~ipad.png +ln -s $ICON_3x_PATH $DEST_DIR_PATH/AppIcon@3x.png +ln -s $ICON_20_2x_PATH $DEST_DIR_PATH/AppIcon-20@2x.png +ln -s $ICON_20_2x_ipad_PATH $DEST_DIR_PATH/AppIcon-20@2x~ipad.png +ln -s $ICON_20_3x_PATH $DEST_DIR_PATH/AppIcon-20@3x.png +ln -s $ICON_20_ipad_PATH $DEST_DIR_PATH/AppIcon-20~ipad.png +ln -s $ICON_29_PATH $DEST_DIR_PATH/AppIcon-29.png +ln -s $ICON_29_2x_PATH $DEST_DIR_PATH/AppIcon-29@2x.png +ln -s $ICON_29_2x_ipad_PATH $DEST_DIR_PATH/AppIcon-29@2x~ipad.png +ln -s $ICON_29_3x_ipad_PATH $DEST_DIR_PATH/AppIcon-29@3x.png +ln -s $ICON_29_ipad_PATH $DEST_DIR_PATH/AppIcon-29~ipad.png +ln -s $ICON_40_2x_PATH $DEST_DIR_PATH/AppIcon-40@2x.png +ln -s $ICON_40_2x_ipad_PATH $DEST_DIR_PATH/AppIcon-40@2x~ipad.png +ln -s $ICON_40_3x_PATH $DEST_DIR_PATH/AppIcon-40@3x.png +ln -s $ICON_40_ipad_PATH $DEST_DIR_PATH/AppIcon-40~ipad.png +ln -s $ICON_60_2x_PATH $DEST_DIR_PATH/AppIcon-60@2x~car.png +ln -s $ICON_60_3x_PATH $DEST_DIR_PATH/AppIcon-60@3x~car.png +ln -s $ICON_83_2x_ipad_PATH $DEST_DIR_PATH/AppIcon-83.5@2x~ipad.png +ln -s $ICON_marketing_PATH $DEST_DIR_PATH/AppIcon~ios-marketing.png +ln -s $ICON_ipad_PATH $DEST_DIR_PATH/AppIcon~ipad.png diff --git a/scripts/linux/app_env.sh b/scripts/linux/app_env.sh index 9d97b63cb..574cce2c9 100755 --- a/scripts/linux/app_env.sh +++ b/scripts/linux/app_env.sh @@ -14,8 +14,8 @@ if [ -n "$1" ]; then fi CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="1.14.0" -CAKEWALLET_BUILD_NUMBER=50 +CAKEWALLET_VERSION="4.26.0" +CAKEWALLET_BUILD_NUMBER=52 if ! [[ " ${TYPES[*]} " =~ " ${APP_LINUX_TYPE} " ]]; then echo "Wrong app type." diff --git a/scripts/macos/app_env.sh b/scripts/macos/app_env.sh index 4ac64ba42..651ad48e9 100755 --- a/scripts/macos/app_env.sh +++ b/scripts/macos/app_env.sh @@ -16,13 +16,13 @@ if [ -n "$1" ]; then fi MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.11.0" -MONERO_COM_BUILD_NUMBER=46 +MONERO_COM_VERSION="4.26.0" +MONERO_COM_BUILD_NUMBER=49 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="1.17.0" -CAKEWALLET_BUILD_NUMBER=108 +CAKEWALLET_VERSION="4.26.0" +CAKEWALLET_BUILD_NUMBER=111 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then diff --git a/scripts/windows/build_exe_installer.iss b/scripts/windows/build_exe_installer.iss index 3f433e1ae..1f152f635 100644 --- a/scripts/windows/build_exe_installer.iss +++ b/scripts/windows/build_exe_installer.iss @@ -1,5 +1,5 @@ #define MyAppName "Cake Wallet" -#define MyAppVersion "0.5.0" +#define MyAppVersion "4.26.0" #define MyAppPublisher "Cake Labs LLC" #define MyAppURL "https://cakewallet.com/" #define MyAppExeName "CakeWallet.exe" diff --git a/tool/configure.dart b/tool/configure.dart index 4d01df4ab..9f104a992 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -410,7 +410,7 @@ abstract class Monero { required int height}); WalletCredentials createMoneroRestoreWalletFromSeedCredentials({required String name, required String password, required String passphrase, required int height, required String mnemonic}); WalletCredentials createMoneroRestoreWalletFromHardwareCredentials({required String name, required String password, required int height, required ledger.LedgerConnection ledgerConnection}); - WalletCredentials createMoneroNewWalletCredentials({required String name, required String language, required bool isPolyseed, required String? passphrase, String? password}); +WalletCredentials createMoneroNewWalletCredentials({required String name, required String language, required int seedType, required String? passphrase, String? password, String? mnemonic}); Map getKeys(Object wallet); int? getRestoreHeight(Object wallet); Object createMoneroTransactionCreationCredentials({required List outputs, required TransactionPriority priority}); @@ -429,6 +429,7 @@ abstract class Monero { void setLedgerConnection(Object wallet, ledger.LedgerConnection connection); void resetLedgerConnection(); void setGlobalLedgerConnection(ledger.LedgerConnection connection); + Map> debugCallLength(); } abstract class MoneroSubaddressList { @@ -614,6 +615,7 @@ abstract class Wownero { WalletService createWowneroWalletService(Box walletInfoSource, Box unspentCoinSource); Map pendingTransactionInfo(Object transaction); String getLegacySeed(Object wallet, String langName); + Map> debugCallLength(); } abstract class WowneroSubaddressList { @@ -1257,6 +1259,7 @@ import 'package:cw_zano/model/zano_transaction_info.dart'; import 'package:cw_zano/zano_formatter.dart'; import 'package:cw_zano/zano_wallet.dart'; import 'package:cw_zano/zano_wallet_service.dart'; +import 'package:cw_zano/zano_wallet_api.dart' as api; import 'package:cw_zano/zano_utils.dart'; """; const zanoCwPart = "part 'cw_zano.dart';"; @@ -1283,6 +1286,7 @@ abstract class Zano { Future getZanoAsset(WalletBase wallet, String contractAddress); String getAddress(WalletBase wallet); bool validateAddress(String address); + Map> debugCallLength(); } """; const zanoEmptyDefinition = 'Zano? zano;\n';