diff --git a/.github/workflows/pr_test_build.yml b/.github/workflows/pr_test_build.yml index 7b2b611d3..88cdc6f6c 100644 --- a/.github/workflows/pr_test_build.yml +++ b/.github/workflows/pr_test_build.yml @@ -3,6 +3,12 @@ name: PR Test Build on: pull_request: branches: [main] + workflow_dispatch: + inputs: + branch: + description: 'Branch name to build' + required: true + default: 'main' jobs: PR_test_build: @@ -12,6 +18,14 @@ jobs: KEY_PASS: test@cake_wallet steps: + - name: is pr + if: github.event_name == 'pull_request' + run: echo "BRANCH_NAME=${GITHUB_HEAD_REF}" >> $GITHUB_ENV + + - name: is not pr + if: github.event_name != 'pull_request' + run: echo "BRANCH_NAME=${{ github.event.inputs.branch }}" >> $GITHUB_ENV + - name: Free Up GitHub Actions Ubuntu Runner Disk Space run: | sudo rm -rf /usr/share/dotnet @@ -40,7 +54,7 @@ jobs: cd /opt/android -y curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh cargo install cargo-ndk - git clone https://github.com/cake-tech/cake_wallet.git --branch $GITHUB_HEAD_REF + git clone https://github.com/cake-tech/cake_wallet.git --branch ${{ env.BRANCH_NAME }} cd cake_wallet/scripts/android/ ./install_ndk.sh source ./app_env.sh cakewallet @@ -97,6 +111,7 @@ jobs: cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. + cd cw_polygon && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. flutter packages pub run build_runner build --delete-conflicting-outputs - name: Add secrets @@ -104,6 +119,7 @@ jobs: cd /opt/android/cake_wallet touch lib/.secrets.g.dart touch cw_ethereum/lib/.secrets.g.dart + touch cw_polygon/lib/.secrets.g.dart echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart @@ -137,9 +153,10 @@ jobs: echo "const robinhoodCIdApiSecret = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart + echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_ethereum/lib/.secrets.g.dart - name: Rename app - run: echo -e "id=com.cakewallet.test\nname=$GITHUB_HEAD_REF" > /opt/android/cake_wallet/android/app.properties + run: echo -e "id=com.cakewallet.test\nname=${{ env.BRANCH_NAME }}" > /opt/android/cake_wallet/android/app.properties - name: Build run: | @@ -154,7 +171,7 @@ jobs: # appcenter distribute release \ # --group "Testers" \ # --file "/opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk" \ -# --release-notes ${GITHUB_HEAD_REF} \ +# --release-notes ${{ env.BRANCH_NAME }} \ # --app Cake-Labs/Cake-Wallet \ # --token ${{ secrets.APP_CENTER_TOKEN }} \ # --quiet @@ -163,7 +180,7 @@ jobs: run: | cd /opt/android/cake_wallet/build/app/outputs/apk/release mkdir test-apk - cp app-release.apk test-apk/$GITHUB_HEAD_REF.apk + cp app-release.apk test-apk/${{env.BRANCH_NAME}}.apk - name: Upload Artifact uses: kittaakos/upload-artifact-as-is@v0 @@ -177,6 +194,6 @@ jobs: token: ${{ secrets.SLACK_APP_TOKEN }} path: /opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk channel: ${{ secrets.SLACK_APK_CHANNEL }} - title: "${{github.head_ref}}.apk" - filename: ${{github.head_ref}}.apk + title: "${{ env.BRANCH_NAME }}.apk" + filename: ${{ env.BRANCH_NAME }}.apk initial_comment: ${{ github.event.head_commit.message }} diff --git a/.gitignore b/.gitignore index c735d4058..0a883dd18 100644 --- a/.gitignore +++ b/.gitignore @@ -126,6 +126,7 @@ lib/haven/haven.dart lib/ethereum/ethereum.dart lib/bitcoin_cash/bitcoin_cash.dart lib/nano/nano.dart +lib/polygon/polygon.dart ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png diff --git a/android/app/src/main/AndroidManifestBase.xml b/android/app/src/main/AndroidManifestBase.xml index 910149f60..f32482e22 100644 --- a/android/app/src/main/AndroidManifestBase.xml +++ b/android/app/src/main/AndroidManifestBase.xml @@ -62,6 +62,9 @@ + + + bitcoinAmountToString(amount: confirmed - frozen); + String get formattedAvailableBalance => + bitcoinAmountToString(amount: confirmed - unconfirmed.abs() - frozen); @override - String get formattedAdditionalBalance => bitcoinAmountToString(amount: unconfirmed); + String get formattedAdditionalBalance => + bitcoinAmountToString(amount: unconfirmed); - String get formattedFrozenBalance { + @override + String get formattedUnAvailableBalance { final frozenFormatted = bitcoinAmountToString(amount: frozen); return frozenFormatted == '0.0' ? '' : frozenFormatted; } - String toJSON() => - json.encode({'confirmed': confirmed, 'unconfirmed': unconfirmed, 'frozen': frozen}); + String toJSON() => json.encode( + {'confirmed': confirmed, 'unconfirmed': unconfirmed, 'frozen': frozen}); } diff --git a/cw_bitcoin/lib/electrum_transaction_history.dart b/cw_bitcoin/lib/electrum_transaction_history.dart index be039fa36..d478c3b12 100644 --- a/cw_bitcoin/lib/electrum_transaction_history.dart +++ b/cw_bitcoin/lib/electrum_transaction_history.dart @@ -1,10 +1,11 @@ import 'dart:convert'; + +import 'package:cw_bitcoin/electrum_transaction_info.dart'; import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/transaction_history.dart'; +import 'package:cw_core/utils/file.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:mobx/mobx.dart'; -import 'package:cw_core/transaction_history.dart'; -import 'package:cw_bitcoin/file.dart'; -import 'package:cw_bitcoin/electrum_transaction_info.dart'; part 'electrum_transaction_history.g.dart'; diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 903fa14cc..4c5ecc7f2 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -18,7 +18,6 @@ import 'package:cw_bitcoin/electrum_balance.dart'; import 'package:cw_bitcoin/electrum_transaction_history.dart'; import 'package:cw_bitcoin/electrum_transaction_info.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; -import 'package:cw_bitcoin/file.dart'; import 'package:cw_bitcoin/pending_bitcoin_transaction.dart'; import 'package:cw_bitcoin/script_hash.dart'; import 'package:cw_bitcoin/utils.dart'; @@ -30,6 +29,7 @@ import 'package:cw_core/sync_status.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/utils/file.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:flutter/foundation.dart'; @@ -82,7 +82,7 @@ abstract class ElectrumWalletBase bitcoin.HDWallet.fromSeed(seedBytes).derivePath("m/44'/145'/0'/0"); static int estimatedTransactionSize(int inputsCount, int outputsCounts) => - inputsCount * 146 + outputsCounts * 33 + 8; + inputsCount * 68 + outputsCounts * 34 + 10; final bitcoin.HDWallet hd; final String mnemonic; @@ -725,8 +725,7 @@ abstract class ElectrumWalletBase final index = address != null ? walletAddresses.addresses.firstWhere((element) => element.address == address).index : null; - return index == null - ? base64Encode(hd.sign(message)) - : base64Encode(hd.derive(index).sign(message)); + final HD = index == null ? hd : hd.derive(index); + return base64Encode(HD.signMessage(message)); } } diff --git a/cw_bitcoin/lib/electrum_wallet_snapshot.dart b/cw_bitcoin/lib/electrum_wallet_snapshot.dart index 0f570071b..ca7c19927 100644 --- a/cw_bitcoin/lib/electrum_wallet_snapshot.dart +++ b/cw_bitcoin/lib/electrum_wallet_snapshot.dart @@ -1,9 +1,9 @@ import 'dart:convert'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/electrum_balance.dart'; -import 'package:cw_bitcoin/file.dart'; import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/utils/file.dart'; import 'package:cw_core/wallet_type.dart'; class ElectrumWallletSnapshot { @@ -67,4 +67,4 @@ class ElectrumWallletSnapshot { derivationType: derivationType, derivationPath: derivationPath); } -} \ No newline at end of file +} diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index 43391881f..3344cb807 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -79,11 +79,11 @@ packages: dependency: "direct main" description: path: "." - ref: cake-update-v3 - resolved-ref: df9204144011ed9419eff7d9ef3143102a40252d + ref: cake-update-v4 + resolved-ref: e19ffb7e7977278a75b27e0479b3c6f4034223b3 url: "https://github.com/cake-tech/bitcoin_flutter.git" source: git - version: "2.0.2" + version: "2.1.0" boolean_selector: dependency: transitive description: @@ -244,7 +244,7 @@ packages: source: hosted version: "2.2.4" encrypt: - dependency: "direct main" + dependency: transitive description: name: encrypt sha256: "4fd4e4fdc21b9d7d4141823e1e6515cd94e7b8d84749504c232999fba25d9bbb" @@ -698,15 +698,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" - tor: - dependency: transitive - description: - path: "." - ref: main - resolved-ref: "09ba92cb11d4e3cacf97256e57863b805f79f2e5" - url: "https://github.com/cake-tech/tor.git" - source: git - version: "0.0.1" typed_data: dependency: transitive description: diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml index 693d5af7a..a50ff68ad 100644 --- a/cw_bitcoin/pubspec.yaml +++ b/cw_bitcoin/pubspec.yaml @@ -22,7 +22,7 @@ dependencies: bitcoin_flutter: git: url: https://github.com/cake-tech/bitcoin_flutter.git - ref: cake-update-v3 + ref: cake-update-v4 bitbox: git: url: https://github.com/cake-tech/bitbox-flutter.git @@ -30,7 +30,6 @@ dependencies: rxdart: ^0.27.5 unorm_dart: ^0.2.0 cryptography: ^2.0.5 - encrypt: ^5.0.1 dev_dependencies: flutter_test: diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart index 5f2a33ab6..c23220423 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:bitbox/bitbox.dart' as bitbox; +import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart'; @@ -210,9 +211,28 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { txb.addInput(input.hash, input.vout); }); + final String bchPrefix = "bitcoincash:"; + outputs.forEach((item) { final outputAmount = hasMultiDestination ? item.formattedCryptoAmount : amount; - final outputAddress = item.isParsedAddress ? item.extractedAddress! : item.address; + String outputAddress = item.isParsedAddress ? item.extractedAddress! : item.address; + + if (!outputAddress.startsWith(bchPrefix)) { + outputAddress = "$bchPrefix$outputAddress"; + } + + bool isP2sh = outputAddress.startsWith("p", bchPrefix.length); + + if (isP2sh) { + final p2sh = P2shAddress.fromAddress( + address: outputAddress, + network: BitcoinCashNetwork.mainnet, + ); + + txb.addOutput(Uint8List.fromList(p2sh.toScriptPubKey().toBytes()), outputAmount!); + return; + } + txb.addOutput(outputAddress, outputAmount!); }); @@ -302,10 +322,8 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { final index = address != null ? walletAddresses.addresses .firstWhere((element) => element.address == AddressUtils.toLegacyAddress(address)) - .index - : null; - return index == null - ? base64Encode(hd.sign(message)) - : base64Encode(hd.derive(index).sign(message)); + .index : null; + final HD = index == null ? hd : hd.derive(index); + return base64Encode(HD.signMessage(message)); } } diff --git a/cw_bitcoin_cash/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux b/cw_bitcoin_cash/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux deleted file mode 120000 index 0ed52b295..000000000 --- a/cw_bitcoin_cash/linux/flutter/ephemeral/.plugin_symlinks/path_provider_linux +++ /dev/null @@ -1 +0,0 @@ -/Users/blazebrain/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/ \ No newline at end of file diff --git a/cw_bitcoin_cash/linux/flutter/ephemeral/.plugin_symlinks/tor b/cw_bitcoin_cash/linux/flutter/ephemeral/.plugin_symlinks/tor deleted file mode 120000 index 7ee2b206d..000000000 --- a/cw_bitcoin_cash/linux/flutter/ephemeral/.plugin_symlinks/tor +++ /dev/null @@ -1 +0,0 @@ -/Users/blazebrain/.pub-cache/git/tor-09ba92cb11d4e3cacf97256e57863b805f79f2e5/ \ No newline at end of file diff --git a/cw_bitcoin_cash/linux/flutter/generated_plugin_registrant.cc b/cw_bitcoin_cash/linux/flutter/generated_plugin_registrant.cc deleted file mode 100644 index e71a16d23..000000000 --- a/cw_bitcoin_cash/linux/flutter/generated_plugin_registrant.cc +++ /dev/null @@ -1,11 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#include "generated_plugin_registrant.h" - - -void fl_register_plugins(FlPluginRegistry* registry) { -} diff --git a/cw_bitcoin_cash/linux/flutter/generated_plugin_registrant.h b/cw_bitcoin_cash/linux/flutter/generated_plugin_registrant.h deleted file mode 100644 index e0f0a47bc..000000000 --- a/cw_bitcoin_cash/linux/flutter/generated_plugin_registrant.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#ifndef GENERATED_PLUGIN_REGISTRANT_ -#define GENERATED_PLUGIN_REGISTRANT_ - -#include - -// Registers Flutter plugins. -void fl_register_plugins(FlPluginRegistry* registry); - -#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/cw_bitcoin_cash/linux/flutter/generated_plugins.cmake b/cw_bitcoin_cash/linux/flutter/generated_plugins.cmake deleted file mode 100644 index 3d57782b2..000000000 --- a/cw_bitcoin_cash/linux/flutter/generated_plugins.cmake +++ /dev/null @@ -1,24 +0,0 @@ -# -# Generated file, do not edit. -# - -list(APPEND FLUTTER_PLUGIN_LIST -) - -list(APPEND FLUTTER_FFI_PLUGIN_LIST - tor -) - -set(PLUGIN_BUNDLED_LIBRARIES) - -foreach(plugin ${FLUTTER_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) - target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) - list(APPEND PLUGIN_BUNDLED_LIBRARIES $) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) -endforeach(plugin) - -foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) -endforeach(ffi_plugin) diff --git a/cw_bitcoin_cash/macos/Flutter/GeneratedPluginRegistrant.swift b/cw_bitcoin_cash/macos/Flutter/GeneratedPluginRegistrant.swift deleted file mode 100644 index e777c67df..000000000 --- a/cw_bitcoin_cash/macos/Flutter/GeneratedPluginRegistrant.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// Generated file. Do not edit. -// - -import FlutterMacOS -import Foundation - -import path_provider_foundation - -func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { - PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) -} diff --git a/cw_bitcoin_cash/macos/Flutter/ephemeral/Flutter-Generated.xcconfig b/cw_bitcoin_cash/macos/Flutter/ephemeral/Flutter-Generated.xcconfig deleted file mode 100644 index 2f46994d3..000000000 --- a/cw_bitcoin_cash/macos/Flutter/ephemeral/Flutter-Generated.xcconfig +++ /dev/null @@ -1,11 +0,0 @@ -// This is a generated file; do not edit or check into version control. -FLUTTER_ROOT=C:\Users\borod\flutter -FLUTTER_APPLICATION_PATH=C:\cake_wallet\cw_bitcoin_cash -COCOAPODS_PARALLEL_CODE_SIGN=true -FLUTTER_BUILD_DIR=build -FLUTTER_BUILD_NAME=0.0.1 -FLUTTER_BUILD_NUMBER=0.0.1 -DART_OBFUSCATION=false -TRACK_WIDGET_CREATION=true -TREE_SHAKE_ICONS=false -PACKAGE_CONFIG=.dart_tool/package_config.json diff --git a/cw_bitcoin_cash/macos/Flutter/ephemeral/flutter_export_environment.sh b/cw_bitcoin_cash/macos/Flutter/ephemeral/flutter_export_environment.sh deleted file mode 100644 index 2a3bcca5a..000000000 --- a/cw_bitcoin_cash/macos/Flutter/ephemeral/flutter_export_environment.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/sh -# This is a generated file; do not edit or check into version control. -export "FLUTTER_ROOT=C:\Users\borod\flutter" -export "FLUTTER_APPLICATION_PATH=C:\cake_wallet\cw_bitcoin_cash" -export "COCOAPODS_PARALLEL_CODE_SIGN=true" -export "FLUTTER_BUILD_DIR=build" -export "FLUTTER_BUILD_NAME=0.0.1" -export "FLUTTER_BUILD_NUMBER=0.0.1" -export "DART_OBFUSCATION=false" -export "TRACK_WIDGET_CREATION=true" -export "TREE_SHAKE_ICONS=false" -export "PACKAGE_CONFIG=.dart_tool/package_config.json" diff --git a/cw_bitcoin_cash/pubspec.yaml b/cw_bitcoin_cash/pubspec.yaml index 30ed49e80..49a5efb15 100644 --- a/cw_bitcoin_cash/pubspec.yaml +++ b/cw_bitcoin_cash/pubspec.yaml @@ -24,11 +24,12 @@ dependencies: bitcoin_flutter: git: url: https://github.com/cake-tech/bitcoin_flutter.git - ref: cake-update-v3 + ref: cake-update-v4 bitbox: git: url: https://github.com/cake-tech/bitbox-flutter.git ref: master + bitcoin_base: ^3.0.1 diff --git a/cw_bitcoin_cash/windows/flutter/generated_plugin_registrant.cc b/cw_bitcoin_cash/windows/flutter/generated_plugin_registrant.cc deleted file mode 100644 index 8b6d4680a..000000000 --- a/cw_bitcoin_cash/windows/flutter/generated_plugin_registrant.cc +++ /dev/null @@ -1,11 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#include "generated_plugin_registrant.h" - - -void RegisterPlugins(flutter::PluginRegistry* registry) { -} diff --git a/cw_bitcoin_cash/windows/flutter/generated_plugin_registrant.h b/cw_bitcoin_cash/windows/flutter/generated_plugin_registrant.h deleted file mode 100644 index dc139d85a..000000000 --- a/cw_bitcoin_cash/windows/flutter/generated_plugin_registrant.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#ifndef GENERATED_PLUGIN_REGISTRANT_ -#define GENERATED_PLUGIN_REGISTRANT_ - -#include - -// Registers Flutter plugins. -void RegisterPlugins(flutter::PluginRegistry* registry); - -#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/cw_bitcoin_cash/windows/flutter/generated_plugins.cmake b/cw_bitcoin_cash/windows/flutter/generated_plugins.cmake deleted file mode 100644 index b93c4c30c..000000000 --- a/cw_bitcoin_cash/windows/flutter/generated_plugins.cmake +++ /dev/null @@ -1,23 +0,0 @@ -# -# Generated file, do not edit. -# - -list(APPEND FLUTTER_PLUGIN_LIST -) - -list(APPEND FLUTTER_FFI_PLUGIN_LIST -) - -set(PLUGIN_BUNDLED_LIBRARIES) - -foreach(plugin ${FLUTTER_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) - target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) - list(APPEND PLUGIN_BUNDLED_LIBRARIES $) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) -endforeach(plugin) - -foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) -endforeach(ffi_plugin) diff --git a/cw_core/lib/crypto_currency.dart b/cw_core/lib/crypto_currency.dart index 1936a87cf..0f7f25d9b 100644 --- a/cw_core/lib/crypto_currency.dart +++ b/cw_core/lib/crypto_currency.dart @@ -93,6 +93,8 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen CryptoCurrency.dydx, CryptoCurrency.steth, CryptoCurrency.banano, + CryptoCurrency.usdtPoly, + CryptoCurrency.usdcEPoly, ]; static const havenCurrencies = [ @@ -202,6 +204,8 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen static const dydx = CryptoCurrency(title: 'DYDX', tag: 'ETH', fullName: 'dYdX', raw: 84, name: 'dydx', iconPath: 'assets/images/dydx_icon.png', decimals: 18); static const steth = CryptoCurrency(title: 'STETH', tag: 'ETH', fullName: 'Lido Staked Ethereum', raw: 85, name: 'steth', iconPath: 'assets/images/steth_icon.png', decimals: 18); static const banano = CryptoCurrency(title: 'BAN', fullName: 'Banano', raw: 86, name: 'banano', iconPath: 'assets/images/nano_icon.png', decimals: 29); + static const usdtPoly = CryptoCurrency(title: 'USDT', tag: 'POLY', fullName: 'Tether USD (PoS)', raw: 87, name: 'usdtpoly', iconPath: 'assets/images/usdt_icon.png', decimals: 6); + static const usdcEPoly = CryptoCurrency(title: 'USDC.E', tag: 'POLY', fullName: 'USD Coin (PoS)', raw: 88, name: 'usdcepoly', iconPath: 'assets/images/usdc_icon.png', decimals: 6); static final Map _rawCurrencyMap = @@ -241,7 +245,7 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen } static CryptoCurrency fromFullName(String name) { - if (CryptoCurrency._fullNameCurrencyMap[name.toLowerCase()] == null) { + if (CryptoCurrency._fullNameCurrencyMap[name.split("(").first.trim().toLowerCase()] == null) { final s = 'Unexpected token: $name for CryptoCurrency fromFullName'; throw ArgumentError.value(name, 'Fullname', s); } diff --git a/cw_core/lib/currency_for_wallet_type.dart b/cw_core/lib/currency_for_wallet_type.dart index 4c330b073..ce0219f1f 100644 --- a/cw_core/lib/currency_for_wallet_type.dart +++ b/cw_core/lib/currency_for_wallet_type.dart @@ -19,6 +19,8 @@ CryptoCurrency currencyForWalletType(WalletType type) { return CryptoCurrency.nano; case WalletType.banano: return CryptoCurrency.banano; + case WalletType.polygon: + return CryptoCurrency.maticpoly; default: throw Exception('Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType'); } diff --git a/cw_core/lib/erc20_token.dart b/cw_core/lib/erc20_token.dart index 011fdef1d..f8c2afc06 100644 --- a/cw_core/lib/erc20_token.dart +++ b/cw_core/lib/erc20_token.dart @@ -18,6 +18,8 @@ class Erc20Token extends CryptoCurrency with HiveObjectMixin { bool _enabled; @HiveField(5) final String? iconPath; + @HiveField(6) + final String? tag; bool get enabled => _enabled; @@ -30,37 +32,41 @@ class Erc20Token extends CryptoCurrency with HiveObjectMixin { required this.decimal, bool enabled = true, this.iconPath, + this.tag, }) : _enabled = enabled, super( - name: symbol.toLowerCase(), - title: symbol.toUpperCase(), - fullName: name, - tag: "ETH", - iconPath: iconPath, - decimals: decimal - ); + name: symbol.toLowerCase(), + title: symbol.toUpperCase(), + fullName: name, + tag: tag, + iconPath: iconPath, + decimals: decimal); - Erc20Token.copyWith(Erc20Token other, String? icon) + Erc20Token.copyWith(Erc20Token other, String? icon, String? tag) : this.name = other.name, this.symbol = other.symbol, this.contractAddress = other.contractAddress, this.decimal = other.decimal, this._enabled = other.enabled, + this.tag = tag, this.iconPath = icon, super( name: other.name, title: other.symbol.toUpperCase(), fullName: other.name, - tag: "ETH", + tag: tag, iconPath: icon, - decimals: other.decimal + decimals: other.decimal, ); static const typeId = ERC20_TOKEN_TYPE_ID; static const boxName = 'Erc20Tokens'; + static const ethereumBoxName = 'EthereumErc20Tokens'; + static const polygonBoxName = 'PolygonErc20Tokens'; @override - bool operator ==(other) => (other is Erc20Token && other.contractAddress == contractAddress) || + bool operator ==(other) => + (other is Erc20Token && other.contractAddress == contractAddress) || (other is CryptoCurrency && other.title == title); @override diff --git a/cw_core/lib/get_height_by_date.dart b/cw_core/lib/get_height_by_date.dart index a680e6c25..6f3ccaf68 100644 --- a/cw_core/lib/get_height_by_date.dart +++ b/cw_core/lib/get_height_by_date.dart @@ -118,7 +118,10 @@ final dates = { "2023-6": 2898234, "2023-7": 2919771, "2023-8": 2942045, - "2023-9": 2964280 + "2023-9": 2964280, + "2023-10": 2985937, + "2023-11": 3008178, + "2023-12": 3029759 }; int getMoneroHeigthByDate({required DateTime date}) { diff --git a/cw_core/lib/node.dart b/cw_core/lib/node.dart index 484325f91..2c43dd21a 100644 --- a/cw_core/lib/node.dart +++ b/cw_core/lib/node.dart @@ -6,7 +6,7 @@ import 'package:hive/hive.dart'; import 'package:cw_core/hive_type_ids.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:http/io_client.dart' as ioc; -import 'package:tor/tor.dart'; +// import 'package:tor/tor.dart'; part 'node.g.dart'; @@ -88,6 +88,8 @@ class Node extends HiveObject with Keyable { } else { return Uri.http(uriRaw, ''); } + case WalletType.polygon: + return Uri.https(uriRaw, ''); default: throw Exception('Unexpected type ${type.toString()} for Node uri'); } @@ -146,6 +148,8 @@ class Node extends HiveObject with Keyable { case WalletType.nano: case WalletType.banano: return requestNanoNode(); + case WalletType.polygon: + return requestElectrumServer(); default: return false; } @@ -210,14 +214,17 @@ class Node extends HiveObject with Keyable { } Future requestNodeWithProxy() async { - if (!isValidProxyAddress && !Tor.instance.enabled) { + if (!isValidProxyAddress /* && !Tor.instance.enabled*/) { return false; } String? proxy = socksProxyAddress; - if ((proxy?.isEmpty ?? true) && Tor.instance.enabled) { - proxy = "${InternetAddress.loopbackIPv4.address}:${Tor.instance.port}"; + // if ((proxy?.isEmpty ?? true) && Tor.instance.enabled) { + // proxy = "${InternetAddress.loopbackIPv4.address}:${Tor.instance.port}"; + // } + if (proxy == null) { + return false; } final proxyAddress = proxy!.split(':')[0]; final proxyPort = int.parse(proxy.split(':')[1]); diff --git a/cw_bitcoin/lib/file.dart b/cw_core/lib/utils/file.dart similarity index 66% rename from cw_bitcoin/lib/file.dart rename to cw_core/lib/utils/file.dart index 8fd236ec3..0b1c5cffd 100644 --- a/cw_bitcoin/lib/file.dart +++ b/cw_core/lib/utils/file.dart @@ -2,17 +2,8 @@ import 'dart:io'; import 'package:cw_core/key.dart'; import 'package:encrypt/encrypt.dart' as encrypt; -Future write( - {required String path, - required String password, - required String data}) async { - final keys = extractKeys(password); - final key = encrypt.Key.fromBase64(keys.first); - final iv = encrypt.IV.fromBase64(keys.last); - final encrypted = await encode(key: key, iv: iv, data: data); - final f = File(path); - f.writeAsStringSync(encrypted); -} +Future write({required String path, required String password, required String data}) async => + writeData(path: path, password: password, data: data); Future writeData( {required String path, diff --git a/cw_core/lib/wallet_type.dart b/cw_core/lib/wallet_type.dart index debf92e11..20f0bdb19 100644 --- a/cw_core/lib/wallet_type.dart +++ b/cw_core/lib/wallet_type.dart @@ -13,6 +13,7 @@ const walletTypes = [ WalletType.bitcoinCash, WalletType.nano, WalletType.banano, + WalletType.polygon, ]; @HiveType(typeId: WALLET_TYPE_TYPE_ID) @@ -44,6 +45,8 @@ enum WalletType { @HiveField(8) bitcoinCash, + @HiveField(9) + polygon } int serializeToInt(WalletType type) { @@ -64,6 +67,8 @@ int serializeToInt(WalletType type) { return 6; case WalletType.bitcoinCash: return 7; + case WalletType.polygon: + return 8; default: return -1; } @@ -87,6 +92,8 @@ WalletType deserializeFromInt(int raw) { return WalletType.banano; case 7: return WalletType.bitcoinCash; + case 8: + return WalletType.polygon; default: throw Exception('Unexpected token: $raw for WalletType deserializeFromInt'); } @@ -110,6 +117,8 @@ String walletTypeToString(WalletType type) { return 'Nano'; case WalletType.banano: return 'Banano'; + case WalletType.polygon: + return 'Polygon'; default: return ''; } @@ -133,6 +142,8 @@ String walletTypeToDisplayName(WalletType type) { return 'Nano (XNO)'; case WalletType.banano: return 'Banano (BAN)'; + case WalletType.polygon: + return 'Polygon (MATIC)'; default: return ''; } @@ -156,7 +167,10 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type) { return CryptoCurrency.nano; case WalletType.banano: return CryptoCurrency.banano; + case WalletType.polygon: + return CryptoCurrency.maticpoly; default: - throw Exception('Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency'); + throw Exception( + 'Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency'); } } diff --git a/cw_core/pubspec.lock b/cw_core/pubspec.lock index f351759ed..aacbd9ddd 100644 --- a/cw_core/pubspec.lock +++ b/cw_core/pubspec.lock @@ -616,15 +616,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" - tor: - dependency: "direct main" - description: - path: "." - ref: main - resolved-ref: "09ba92cb11d4e3cacf97256e57863b805f79f2e5" - url: "https://github.com/cake-tech/tor.git" - source: git - version: "0.0.1" typed_data: dependency: transitive description: diff --git a/cw_core/pubspec.yaml b/cw_core/pubspec.yaml index 533b578ad..04a840d4e 100644 --- a/cw_core/pubspec.yaml +++ b/cw_core/pubspec.yaml @@ -20,10 +20,10 @@ dependencies: intl: ^0.18.0 encrypt: ^5.0.1 socks5_proxy: ^1.0.4 - tor: - git: - url: https://github.com/cake-tech/tor.git - ref: main +# tor: +# git: +# url: https://github.com/cake-tech/tor.git +# ref: main dev_dependencies: flutter_test: diff --git a/cw_ethereum/lib/default_erc20_tokens.dart b/cw_ethereum/lib/default_ethereum_erc20_tokens.dart similarity index 95% rename from cw_ethereum/lib/default_erc20_tokens.dart rename to cw_ethereum/lib/default_ethereum_erc20_tokens.dart index 8c38e2e64..a8f82d181 100644 --- a/cw_ethereum/lib/default_erc20_tokens.dart +++ b/cw_ethereum/lib/default_ethereum_erc20_tokens.dart @@ -293,17 +293,13 @@ class DefaultErc20Tokens { ]; List get initialErc20Tokens => _defaultTokens.map((token) { - String? iconPath; - try { - iconPath = CryptoCurrency.all - .firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase()) - .iconPath; - } catch (_) {} + String? iconPath; + try { + iconPath = CryptoCurrency.all + .firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase()) + .iconPath; + } catch (_) {} - if (iconPath != null) { - return Erc20Token.copyWith(token, iconPath); - } - - return token; - }).toList(); + return Erc20Token.copyWith(token, iconPath, 'ETH'); + }).toList(); } diff --git a/cw_ethereum/lib/ethereum_client.dart b/cw_ethereum/lib/ethereum_client.dart index f0c7381e8..fccbf778d 100644 --- a/cw_ethereum/lib/ethereum_client.dart +++ b/cw_ethereum/lib/ethereum_client.dart @@ -15,12 +15,12 @@ import 'package:cw_ethereum/ethereum_transaction_priority.dart'; import 'package:cw_ethereum/.secrets.g.dart' as secrets; class EthereumClient { - final _httpClient = Client(); + final httpClient = Client(); Web3Client? _client; bool connect(Node node) { try { - _client = Web3Client(node.uri.toString(), _httpClient); + _client = Web3Client(node.uri.toString(), httpClient); return true; } catch (e) { @@ -74,29 +74,34 @@ class EthereumClient { required int exponent, String? contractAddress, }) async { - assert(currency == CryptoCurrency.eth || contractAddress != null); + assert(currency == CryptoCurrency.eth || + currency == CryptoCurrency.maticpoly || + contractAddress != null); - bool _isEthereum = currency == CryptoCurrency.eth; + bool _isEVMCompatibleChain = + currency == CryptoCurrency.eth || currency == CryptoCurrency.maticpoly; final price = _client!.getGasPrice(); - final Transaction transaction = Transaction( + final Transaction transaction = createTransaction( from: privateKey.address, to: EthereumAddress.fromHex(toAddress), maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip), - value: _isEthereum ? EtherAmount.inWei(BigInt.parse(amount)) : EtherAmount.zero(), + amount: _isEVMCompatibleChain ? EtherAmount.inWei(BigInt.parse(amount)) : EtherAmount.zero(), ); - final signedTransaction = await _client!.signTransaction(privateKey, transaction); + final signedTransaction = + await _client!.signTransaction(privateKey, transaction, chainId: chainId); final Function _sendTransaction; - if (_isEthereum) { + if (_isEVMCompatibleChain) { _sendTransaction = () async => await sendTransaction(signedTransaction); } else { final erc20 = ERC20( client: _client!, address: EthereumAddress.fromHex(contractAddress!), + chainId: chainId, ); _sendTransaction = () async { @@ -118,8 +123,27 @@ class EthereumClient { ); } + int get chainId => 1; + + Transaction createTransaction({ + required EthereumAddress from, + required EthereumAddress to, + required EtherAmount amount, + EtherAmount? maxPriorityFeePerGas, + }) { + return Transaction( + from: from, + to: to, + maxPriorityFeePerGas: maxPriorityFeePerGas, + value: amount, + ); + } + Future sendTransaction(Uint8List signedTransaction) async => - await _client!.sendRawTransaction(prependTransactionType(0x02, signedTransaction)); + await _client!.sendRawTransaction(prepareSignedTransactionForSending(signedTransaction)); + + Uint8List prepareSignedTransactionForSending(Uint8List signedTransaction) => + prependTransactionType(0x02, signedTransaction); Future getTransactionDetails(String transactionHash) async { // Wait for the transaction receipt to become available @@ -198,7 +222,7 @@ I/flutter ( 4474): Gas Used: 53000 Future> fetchTransactions(String address, {String? contractAddress}) async { try { - final response = await _httpClient.get(Uri.https("api.etherscan.io", "/api", { + final response = await httpClient.get(Uri.https("api.etherscan.io", "/api", { "module": "account", "action": contractAddress != null ? "tokentx" : "txlist", if (contractAddress != null) "contractaddress": contractAddress, diff --git a/cw_ethereum/lib/ethereum_transaction_info.dart b/cw_ethereum/lib/ethereum_transaction_info.dart index a0649ba25..f0deae931 100644 --- a/cw_ethereum/lib/ethereum_transaction_info.dart +++ b/cw_ethereum/lib/ethereum_transaction_info.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:cw_core/format_amount.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_info.dart'; @@ -34,8 +36,10 @@ class EthereumTransactionInfo extends TransactionInfo { final String? to; @override - String amountFormatted() => - '${formatAmount((ethAmount / BigInt.from(10).pow(exponent)).toString())} $tokenSymbol'; + String amountFormatted() { + final amount = formatAmount((ethAmount / BigInt.from(10).pow(exponent)).toString()); + return '${amount.substring(0, min(10, amount.length))} $tokenSymbol'; + } @override String fiatAmount() => _fiatAmount ?? ''; @@ -44,7 +48,10 @@ class EthereumTransactionInfo extends TransactionInfo { void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount); @override - String feeFormatted() => '${(ethFee / BigInt.from(10).pow(18)).toString()} ETH'; + String feeFormatted() { + final amount = (ethFee / BigInt.from(10).pow(18)).toString(); + return '${amount.substring(0, min(10, amount.length))} ETH'; + } factory EthereumTransactionInfo.fromJson(Map data) { return EthereumTransactionInfo( diff --git a/cw_ethereum/lib/ethereum_transaction_model.dart b/cw_ethereum/lib/ethereum_transaction_model.dart index c1260795a..3b5f724fc 100644 --- a/cw_ethereum/lib/ethereum_transaction_model.dart +++ b/cw_ethereum/lib/ethereum_transaction_model.dart @@ -1,3 +1,4 @@ +//! Model used for in parsing transactions fetched using etherscan class EthereumTransactionModel { final DateTime date; final String hash; diff --git a/cw_ethereum/lib/ethereum_wallet.dart b/cw_ethereum/lib/ethereum_wallet.dart index 21bde1233..cd4bd84cc 100644 --- a/cw_ethereum/lib/ethereum_wallet.dart +++ b/cw_ethereum/lib/ethereum_wallet.dart @@ -14,7 +14,7 @@ import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/wallet_addresses.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_info.dart'; -import 'package:cw_ethereum/default_erc20_tokens.dart'; +import 'package:cw_ethereum/default_ethereum_erc20_tokens.dart'; import 'package:cw_ethereum/erc20_balance.dart'; import 'package:cw_ethereum/ethereum_client.dart'; import 'package:cw_ethereum/ethereum_exceptions.dart'; @@ -75,6 +75,8 @@ abstract class EthereumWalletBase late final Box erc20TokensBox; + late final Box ethereumErc20TokensBox; + late final EthPrivateKey _ethPrivateKey; EthPrivateKey get ethPrivateKey => _ethPrivateKey; @@ -102,7 +104,8 @@ abstract class EthereumWalletBase Completer _sharedPrefs = Completer(); Future init() async { - erc20TokensBox = await CakeHive.openBox(Erc20Token.boxName); + await movePreviousErc20BoxConfigsToNewBox(); + await walletAddresses.init(); await transactionHistory.init(); _ethPrivateKey = await getPrivateKey( @@ -114,6 +117,33 @@ abstract class EthereumWalletBase await save(); } + /// Majorly for backward compatibility for previous configs that have been set. + Future movePreviousErc20BoxConfigsToNewBox() async { + // Opens a box specific to this wallet + ethereumErc20TokensBox = await CakeHive.openBox( + "${walletInfo.name.replaceAll(" ", "_")}_${Erc20Token.ethereumBoxName}"); + + //Open the previous token configs box + erc20TokensBox = await CakeHive.openBox(Erc20Token.boxName); + + // Check if it's empty, if it is, we stop the flow and return. + if (erc20TokensBox.isEmpty) { + // If it's empty, but the new wallet specific box is also empty, + // we load the initial tokens to the new box. + if (ethereumErc20TokensBox.isEmpty) addInitialTokens(); + return; + } + + final allValues = erc20TokensBox.values.toList(); + + // Clear and delete the old token box + await erc20TokensBox.clear(); + await erc20TokensBox.deleteFromDisk(); + + // Add all the previous tokens with configs to the new box + ethereumErc20TokensBox.addAll(allValues); + } + @override int calculateEstimatedFee(TransactionPriority priority, int? amount) { try { @@ -378,7 +408,7 @@ abstract class EthereumWalletBase } Future _fetchErc20Balances() async { - for (var token in erc20TokensBox.values) { + for (var token in ethereumErc20TokensBox.values) { try { if (token.enabled) { balance[token] = await _client.fetchERC20Balances( @@ -413,7 +443,7 @@ abstract class EthereumWalletBase Future? updateBalance() async => await _updateBalance(); - List get erc20Currencies => erc20TokensBox.values.toList(); + List get erc20Currencies => ethereumErc20TokensBox.values.toList(); Future addErc20Token(Erc20Token token) async { String? iconPath; @@ -429,10 +459,11 @@ abstract class EthereumWalletBase contractAddress: token.contractAddress, decimal: token.decimal, enabled: token.enabled, + tag: token.tag ?? "ETH", iconPath: iconPath, ); - await erc20TokensBox.put(_token.contractAddress, _token); + await ethereumErc20TokensBox.put(_token.contractAddress, _token); if (_token.enabled) { balance[_token] = await _client.fetchERC20Balances( @@ -462,7 +493,7 @@ abstract class EthereumWalletBase void addInitialTokens() { final initialErc20Tokens = DefaultErc20Tokens().initialErc20Tokens; - initialErc20Tokens.forEach((token) => erc20TokensBox.put(token.contractAddress, token)); + initialErc20Tokens.forEach((token) => ethereumErc20TokensBox.put(token.contractAddress, token)); } @override @@ -492,7 +523,7 @@ abstract class EthereumWalletBase _transactionsUpdateTimer!.cancel(); } - _transactionsUpdateTimer = Timer.periodic(Duration(seconds: 10), (_) { + _transactionsUpdateTimer = Timer.periodic(const Duration(seconds: 10), (_) { _updateTransactions(); _updateBalance(); }); @@ -508,7 +539,7 @@ abstract class EthereumWalletBase } @override - String signMessage(String message, {String? address = null}) => + String signMessage(String message, {String? address}) => bytesToHex(_ethPrivateKey.signPersonalMessageToUint8List(ascii.encode(message))); Web3Client? getWeb3Client() => _client.getWeb3Client(); diff --git a/cw_ethereum/lib/ethereum_wallet_service.dart b/cw_ethereum/lib/ethereum_wallet_service.dart index 8810d6014..0acc90bac 100644 --- a/cw_ethereum/lib/ethereum_wallet_service.dart +++ b/cw_ethereum/lib/ethereum_wallet_service.dart @@ -20,12 +20,7 @@ class EthereumWalletService extends WalletService create(EthereumNewWalletCredentials credentials) async { - - final strength = (credentials.seedPhraseLength == 12) - ? 128 - : (credentials.seedPhraseLength == 24) - ? 256 - : 128; + final strength = credentials.seedPhraseLength == 24 ? 256 : 128; final mnemonic = bip39.generateMnemonic(strength: strength); final wallet = EthereumWallet( @@ -67,8 +62,8 @@ class EthereumWalletService extends WalletService remove(String wallet) async { File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true); - final walletInfo = walletInfoSource.values.firstWhereOrNull( - (info) => info.id == WalletBase.idFor(wallet, getType()))!; + final walletInfo = walletInfoSource.values + .firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!; await walletInfoSource.delete(walletInfo.key); } diff --git a/cw_ethereum/lib/pending_ethereum_transaction.dart b/cw_ethereum/lib/pending_ethereum_transaction.dart index 35b0123cc..d47630fd6 100644 --- a/cw_ethereum/lib/pending_ethereum_transaction.dart +++ b/cw_ethereum/lib/pending_ethereum_transaction.dart @@ -21,8 +21,8 @@ class PendingEthereumTransaction with PendingTransaction { @override String get amountFormatted { - final _amount = BigInt.parse(amount) / BigInt.from(pow(10, exponent)); - return _amount.toStringAsFixed(min(15, _amount.toString().length)); + final _amount = (BigInt.parse(amount) / BigInt.from(pow(10, exponent))).toString(); + return _amount.substring(0, min(10, _amount.length)); } @override @@ -30,8 +30,8 @@ class PendingEthereumTransaction with PendingTransaction { @override String get feeFormatted { - final _fee = fee / BigInt.from(pow(10, 18)); - return _fee.toStringAsFixed(min(15, _fee.toString().length)); + final _fee = (fee / BigInt.from(pow(10, 18))).toString(); + return _fee.substring(0, min(10, _fee.length)); } @override diff --git a/cw_ethereum/pubspec.yaml b/cw_ethereum/pubspec.yaml index 5d19589f3..6946a43a1 100644 --- a/cw_ethereum/pubspec.yaml +++ b/cw_ethereum/pubspec.yaml @@ -17,7 +17,6 @@ dependencies: mobx: ^2.0.7+4 bip39: ^1.0.6 bip32: ^2.0.0 - ed25519_hd_key: ^2.2.0 hex: ^0.2.0 http: ^1.1.0 shared_preferences: ^2.0.15 diff --git a/cw_haven/pubspec.lock b/cw_haven/pubspec.lock index 525e8e5fa..b0a350cc7 100644 --- a/cw_haven/pubspec.lock +++ b/cw_haven/pubspec.lock @@ -623,15 +623,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" - tor: - dependency: transitive - description: - path: "." - ref: main - resolved-ref: "09ba92cb11d4e3cacf97256e57863b805f79f2e5" - url: "https://github.com/cake-tech/tor.git" - source: git - version: "0.0.1" typed_data: dependency: transitive description: diff --git a/cw_monero/example/pubspec.lock b/cw_monero/example/pubspec.lock index 3dd2b5b61..c9ca8d92b 100644 --- a/cw_monero/example/pubspec.lock +++ b/cw_monero/example/pubspec.lock @@ -153,6 +153,22 @@ packages: description: flutter source: sdk version: "0.0.0" + hashlib: + dependency: transitive + description: + name: hashlib + sha256: "71bf102329ddb8e50c8a995ee4645ae7f1728bb65e575c17196b4d8262121a96" + url: "https://pub.dev" + source: hosted + version: "1.12.0" + hashlib_codecs: + dependency: transitive + description: + name: hashlib_codecs + sha256: "49e2a471f74b15f1854263e58c2ac11f2b631b5b12c836f9708a35397d36d626" + url: "https://pub.dev" + source: hosted + version: "2.2.0" http: dependency: transitive description: @@ -308,12 +324,11 @@ packages: polyseed: dependency: transitive description: - path: "." - ref: HEAD - resolved-ref: "504d58a5b147fccd3bc85a25f2e72fb32771ddd7" - url: "https://github.com/cake-tech/polyseed_dart.git" - source: git - version: "0.0.1" + name: polyseed + sha256: "9b48ec535b10863f78f6354ec983b4cc0c88ca69ff48fee469d0fd1954b01d4f" + url: "https://pub.dev" + source: hosted + version: "0.0.2" process: dependency: transitive description: @@ -383,15 +398,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.5.1" - tor: - dependency: transitive - description: - path: "." - ref: main - resolved-ref: "09ba92cb11d4e3cacf97256e57863b805f79f2e5" - url: "https://github.com/cake-tech/tor.git" - source: git - version: "0.0.1" typed_data: dependency: transitive description: diff --git a/cw_monero/ios/Classes/monero_api.cpp b/cw_monero/ios/Classes/monero_api.cpp index 7ad873647..87be785ac 100644 --- a/cw_monero/ios/Classes/monero_api.cpp +++ b/cw_monero/ios/Classes/monero_api.cpp @@ -385,6 +385,9 @@ extern "C" (uint64_t)restoreHeight, std::string(spendKey)); + // Cache Raw to support Polyseed + wallet->setCacheAttribute("cakewallet.seed", std::string(seed)); + int status; std::string errorString; @@ -396,9 +399,6 @@ extern "C" return false; } - // Cache Raw to support Polyseed - wallet->setCacheAttribute("cakewallet.seed", std::string(seed)); - change_current_wallet(wallet); return true; } @@ -926,6 +926,8 @@ extern "C" return m_wallet->trustedDaemon(); } + // Coin Control // + CoinsInfoRow* coin(int index) { if (index >= 0 && index < m_coins_info.size()) { @@ -1020,6 +1022,13 @@ extern "C" m_coins->thaw(index); } + // Sign Messages // + + char *sign_message(char *message, char *address = "") + { + return strdup(get_current_wallet()->signMessage(std::string(message), std::string(address)).c_str()); + } + #ifdef __cplusplus } #endif diff --git a/cw_monero/ios/Classes/monero_api.h b/cw_monero/ios/Classes/monero_api.h index 74258ba4c..fa92a038d 100644 --- a/cw_monero/ios/Classes/monero_api.h +++ b/cw_monero/ios/Classes/monero_api.h @@ -32,7 +32,8 @@ void store(char *path); void set_trusted_daemon(bool arg); bool trusted_daemon(); +char *sign_message(char *message, char *address); #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/cw_monero/lib/api/signatures.dart b/cw_monero/lib/api/signatures.dart index 17099db91..bc4fc9d38 100644 --- a/cw_monero/lib/api/signatures.dart +++ b/cw_monero/lib/api/signatures.dart @@ -149,3 +149,5 @@ typedef coin = Pointer Function(Int32 index); typedef freeze_coin = Void Function(Int32 index); typedef thaw_coin = Void Function(Int32 index); + +typedef sign_message = Pointer Function(Pointer message, Pointer address); diff --git a/cw_monero/lib/api/types.dart b/cw_monero/lib/api/types.dart index 1d3904870..40a1e0321 100644 --- a/cw_monero/lib/api/types.dart +++ b/cw_monero/lib/api/types.dart @@ -149,3 +149,5 @@ typedef GetCoin = Pointer Function(int); typedef FreezeCoin = void Function(int); typedef ThawCoin = void Function(int); + +typedef SignMessage = Pointer Function(Pointer, Pointer); diff --git a/cw_monero/lib/api/wallet.dart b/cw_monero/lib/api/wallet.dart index 1680918e5..ffa5fe13b 100644 --- a/cw_monero/lib/api/wallet.dart +++ b/cw_monero/lib/api/wallet.dart @@ -8,7 +8,6 @@ import 'package:cw_monero/api/types.dart'; import 'package:cw_monero/api/monero_api.dart'; import 'package:cw_monero/api/exceptions/setup_wallet_exception.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; int _boolToInt(bool value) => value ? 1 : 0; @@ -128,6 +127,10 @@ final trustedDaemonNative = moneroApi .lookup>('trusted_daemon') .asFunction(); +final signMessageNative = moneroApi + .lookup>('sign_message') + .asFunction(); + int getSyncingHeight() => getSyncingHeightNative(); bool isNeededToRefresh() => isNeededToRefreshNative() != 0; @@ -296,7 +299,7 @@ class SyncListener { final bchHeight = await getNodeHeightOrUpdate(syncHeight); - if (_lastKnownBlockHeight == syncHeight || syncHeight == null) { + if (_lastKnownBlockHeight == syncHeight) { return; } @@ -311,7 +314,7 @@ class SyncListener { } // 1. Actual new height; 2. Blocks left to finish; 3. Progress in percents; - onNewBlock?.call(syncHeight, left, ptc); + onNewBlock.call(syncHeight, left, ptc); }); } @@ -382,4 +385,15 @@ String getSubaddressLabel(int accountIndex, int addressIndex) { Future setTrustedDaemon(bool trusted) async => setTrustedDaemonNative(_boolToInt(trusted)); -Future trustedDaemon() async => trustedDaemonNative() != 0; \ No newline at end of file +Future trustedDaemon() async => trustedDaemonNative() != 0; + +String signMessage(String message, {String address = ""}) { + final messagePointer = message.toNativeUtf8(); + final addressPointer = address.toNativeUtf8(); + + final signature = convertUTF8ToString(pointer: signMessageNative(messagePointer, addressPointer)); + calloc.free(messagePointer); + calloc.free(addressPointer); + + return signature; +} diff --git a/cw_monero/lib/api/wallet_manager.dart b/cw_monero/lib/api/wallet_manager.dart index 260b420c5..0aa694e9a 100644 --- a/cw_monero/lib/api/wallet_manager.dart +++ b/cw_monero/lib/api/wallet_manager.dart @@ -1,14 +1,16 @@ import 'dart:ffi'; -import 'package:ffi/ffi.dart'; -import 'package:flutter/foundation.dart'; + import 'package:cw_monero/api/convert_utf8_to_string.dart'; -import 'package:cw_monero/api/signatures.dart'; -import 'package:cw_monero/api/types.dart'; -import 'package:cw_monero/api/monero_api.dart'; -import 'package:cw_monero/api/exceptions/wallet_opening_exception.dart'; import 'package:cw_monero/api/exceptions/wallet_creation_exception.dart'; +import 'package:cw_monero/api/exceptions/wallet_opening_exception.dart'; import 'package:cw_monero/api/exceptions/wallet_restore_from_keys_exception.dart'; import 'package:cw_monero/api/exceptions/wallet_restore_from_seed_exception.dart'; +import 'package:cw_monero/api/monero_api.dart'; +import 'package:cw_monero/api/signatures.dart'; +import 'package:cw_monero/api/types.dart'; +import 'package:cw_monero/api/wallet.dart'; +import 'package:ffi/ffi.dart'; +import 'package:flutter/foundation.dart'; final createWalletNative = moneroApi .lookup>('create_wallet') @@ -175,6 +177,8 @@ void restoreWalletFromSpendKeySync( calloc.free(languagePointer); calloc.free(spendKeyPointer); + storeSync(); + if (!isWalletRestored) { throw WalletRestoreFromKeysException( message: convertUTF8ToString(pointer: errorMessagePointer)); diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index 71c7e3967..4b71fb5ff 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -651,4 +651,10 @@ abstract class MoneroWalletBase @override void setExceptionHandler(void Function(FlutterErrorDetails) onError) => _onError = onError; + + @override + String signMessage(String message, {String? address}) { + final useAddress = address ?? ""; + return monero_wallet.signMessage(message, address: useAddress); + } } diff --git a/cw_monero/lib/monero_wallet_service.dart b/cw_monero/lib/monero_wallet_service.dart index 8e9eb6eac..9b9e254d0 100644 --- a/cw_monero/lib/monero_wallet_service.dart +++ b/cw_monero/lib/monero_wallet_service.dart @@ -13,7 +13,6 @@ import 'package:cw_monero/api/wallet_manager.dart' as monero_wallet_manager; import 'package:cw_monero/monero_wallet.dart'; import 'package:hive/hive.dart'; import 'package:polyseed/polyseed.dart'; -import 'package:polyseed/src/utils/key_utils.dart'; class MoneroNewWalletCredentials extends WalletCredentials { MoneroNewWalletCredentials({required String name, required this.language, required this.isPolyseed, String? password}) @@ -77,8 +76,12 @@ class MoneroWalletService extends WalletService< final polyseed = Polyseed.create(); final lang = PolyseedLang.getByEnglishName(credentials.language); + final heightOverride = + getMoneroHeigthByDate(date: DateTime.now().subtract(Duration(days: 2))); + return _restoreFromPolyseed( - path, credentials.password!, polyseed, credentials.walletInfo!, lang); + path, credentials.password!, polyseed, credentials.walletInfo!, lang, + overrideHeight: heightOverride); } await monero_wallet_manager.createWallet( @@ -268,18 +271,23 @@ class MoneroWalletService extends WalletService< Future _restoreFromPolyseed(String path, String password, Polyseed polyseed, WalletInfo walletInfo, PolyseedLang lang, - {PolyseedCoin coin = PolyseedCoin.POLYSEED_MONERO}) async { - final height = getMoneroHeigthByDate( + {PolyseedCoin coin = PolyseedCoin.POLYSEED_MONERO, int? overrideHeight}) async { + final height = overrideHeight ?? getMoneroHeigthByDate( date: DateTime.fromMillisecondsSinceEpoch(polyseed.birthday * 1000)); - final spendKey = keyToHexString(polyseed.generateKey(coin, 32)); + final spendKey = polyseed.generateKey(coin, 32).toHexString(); + final seed = polyseed.encode(lang, coin); + + walletInfo.isRecovery = true; + walletInfo.restoreHeight = height; await monero_wallet_manager.restoreFromSpendKey( path: path, password: password, - seed: polyseed.encode(lang, coin), + seed: seed, language: lang.nameEnglish, restoreHeight: height, spendKey: spendKey); + final wallet = MoneroWallet( walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource); await wallet.init(); diff --git a/cw_monero/macos/Classes/monero_api.cpp b/cw_monero/macos/Classes/monero_api.cpp index ac8a64861..fe75dea98 100644 --- a/cw_monero/macos/Classes/monero_api.cpp +++ b/cw_monero/macos/Classes/monero_api.cpp @@ -234,7 +234,6 @@ extern "C" } void setUnlocked(bool unlocked); - }; Monero::Coins *m_coins; @@ -375,6 +374,35 @@ extern "C" return true; } + bool restore_wallet_from_spend_key(char *path, char *password, char *seed, char *language, char *spendKey, int32_t networkType, uint64_t restoreHeight, char *error) + { + Monero::NetworkType _networkType = static_cast(networkType); + Monero::Wallet *wallet = Monero::WalletManagerFactory::getWalletManager()->createDeterministicWalletFromSpendKey( + std::string(path), + std::string(password), + std::string(language), + _networkType, + (uint64_t)restoreHeight, + std::string(spendKey)); + + // Cache Raw to support Polyseed + wallet->setCacheAttribute("cakewallet.seed", std::string(seed)); + + int status; + std::string errorString; + + wallet->statusWithErrorString(status, errorString); + + if (status != Monero::Wallet::Status_Ok || !errorString.empty()) + { + error = strdup(errorString.c_str()); + return false; + } + + change_current_wallet(wallet); + return true; + } + bool load_wallet(char *path, char *password, int32_t nettype) { nice(19); @@ -439,6 +467,11 @@ extern "C" const char *seed() { + std::string _rawSeed = get_current_wallet()->getCacheAttribute("cakewallet.seed"); + if (!_rawSeed.empty()) + { + return strdup(_rawSeed.c_str()); + } return strdup(get_current_wallet()->seed().c_str()); } @@ -842,6 +875,12 @@ extern "C" return m_transaction_history->count(); } + TransactionInfoRow* get_transaction(char * txId) + { + Monero::TransactionInfo *row = m_transaction_history->transaction(std::string(txId)); + return new TransactionInfoRow(row); + } + int LedgerExchange( unsigned char *command, unsigned int cmd_len, @@ -971,6 +1010,22 @@ extern "C" return result; } + void freeze_coin(int index) + { + m_coins->setFrozen(index); + } + + void thaw_coin(int index) + { + m_coins->thaw(index); + } + + // Sign Messages // + + char *sign_message(char *message, char *address = "") + { + return strdup(get_current_wallet()->signMessage(std::string(message), std::string(address)).c_str()); + } #ifdef __cplusplus } diff --git a/cw_monero/macos/Classes/monero_api.h b/cw_monero/macos/Classes/monero_api.h index 74258ba4c..fa92a038d 100644 --- a/cw_monero/macos/Classes/monero_api.h +++ b/cw_monero/macos/Classes/monero_api.h @@ -32,7 +32,8 @@ void store(char *path); void set_trusted_daemon(bool arg); bool trusted_daemon(); +char *sign_message(char *message, char *address); #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/cw_monero/pubspec.lock b/cw_monero/pubspec.lock index 46ebe8028..0f8f2c90e 100644 --- a/cw_monero/pubspec.lock +++ b/cw_monero/pubspec.lock @@ -266,6 +266,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + hashlib: + dependency: transitive + description: + name: hashlib + sha256: "71bf102329ddb8e50c8a995ee4645ae7f1728bb65e575c17196b4d8262121a96" + url: "https://pub.dev" + source: hosted + version: "1.12.0" + hashlib_codecs: + dependency: transitive + description: + name: hashlib_codecs + sha256: "49e2a471f74b15f1854263e58c2ac11f2b631b5b12c836f9708a35397d36d626" + url: "https://pub.dev" + source: hosted + version: "2.2.0" hive: dependency: transitive description: @@ -485,12 +501,11 @@ packages: polyseed: dependency: "direct main" description: - path: "." - ref: HEAD - resolved-ref: "504d58a5b147fccd3bc85a25f2e72fb32771ddd7" - url: "https://github.com/cake-tech/polyseed_dart.git" - source: git - version: "0.0.1" + name: polyseed + sha256: "9b48ec535b10863f78f6354ec983b4cc0c88ca69ff48fee469d0fd1954b01d4f" + url: "https://pub.dev" + source: hosted + version: "0.0.2" pool: dependency: transitive description: @@ -632,15 +647,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" - tor: - dependency: transitive - description: - path: "." - ref: main - resolved-ref: "09ba92cb11d4e3cacf97256e57863b805f79f2e5" - url: "https://github.com/cake-tech/tor.git" - source: git - version: "0.0.1" typed_data: dependency: transitive description: diff --git a/cw_monero/pubspec.yaml b/cw_monero/pubspec.yaml index 55e9b354c..a6fe7f967 100644 --- a/cw_monero/pubspec.yaml +++ b/cw_monero/pubspec.yaml @@ -19,9 +19,7 @@ dependencies: flutter_mobx: ^2.0.6+1 intl: ^0.18.0 encrypt: ^5.0.1 - polyseed: - git: - url: https://github.com/cake-tech/polyseed_dart.git + polyseed: ^0.0.2 cw_core: path: ../cw_core diff --git a/cw_polygon/.gitignore b/cw_polygon/.gitignore new file mode 100644 index 000000000..96486fd93 --- /dev/null +++ b/cw_polygon/.gitignore @@ -0,0 +1,30 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ diff --git a/cw_polygon/.metadata b/cw_polygon/.metadata new file mode 100644 index 000000000..fa347fc6a --- /dev/null +++ b/cw_polygon/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: f468f3366c26a5092eb964a230ce7892fda8f2f8 + channel: stable + +project_type: package diff --git a/cw_polygon/CHANGELOG.md b/cw_polygon/CHANGELOG.md new file mode 100644 index 000000000..41cc7d819 --- /dev/null +++ b/cw_polygon/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: Describe initial release. diff --git a/cw_polygon/LICENSE b/cw_polygon/LICENSE new file mode 100644 index 000000000..ba75c69f7 --- /dev/null +++ b/cw_polygon/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/cw_polygon/README.md b/cw_polygon/README.md new file mode 100644 index 000000000..02fe8ecab --- /dev/null +++ b/cw_polygon/README.md @@ -0,0 +1,39 @@ + + +TODO: Put a short description of the package here that helps potential users +know whether this package might be useful for them. + +## Features + +TODO: List what your package can do. Maybe include images, gifs, or videos. + +## Getting started + +TODO: List prerequisites and provide or point to information on how to +start using the package. + +## Usage + +TODO: Include short and useful examples for package users. Add longer examples +to `/example` folder. + +```dart +const like = 'sample'; +``` + +## Additional information + +TODO: Tell users more about the package: where to find more information, how to +contribute to the package, how to file issues, what response they can expect +from the package authors, and more. diff --git a/cw_polygon/analysis_options.yaml b/cw_polygon/analysis_options.yaml new file mode 100644 index 000000000..a5744c1cf --- /dev/null +++ b/cw_polygon/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/cw_polygon/lib/cw_polygon.dart b/cw_polygon/lib/cw_polygon.dart new file mode 100644 index 000000000..5d4e447d1 --- /dev/null +++ b/cw_polygon/lib/cw_polygon.dart @@ -0,0 +1,7 @@ +library cw_polygon; + +/// A Calculator. +class Calculator { + /// Returns [value] plus 1. + int addOne(int value) => value + 1; +} diff --git a/cw_polygon/lib/default_polygon_erc20_tokens.dart b/cw_polygon/lib/default_polygon_erc20_tokens.dart new file mode 100644 index 000000000..deff285c0 --- /dev/null +++ b/cw_polygon/lib/default_polygon_erc20_tokens.dart @@ -0,0 +1,82 @@ +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/erc20_token.dart'; + +class DefaultPolygonErc20Tokens { + final List _defaultTokens = [ + Erc20Token( + name: "Wrapped Ether", + symbol: "WETH", + contractAddress: "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619", + decimal: 18, + enabled: false, + ), + Erc20Token( + name: "Tether USD (PoS)", + symbol: "USDT", + contractAddress: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F", + decimal: 6, + enabled: true, + ), + Erc20Token( + name: "USD Coin", + symbol: "USDC", + contractAddress: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359", + decimal: 6, + enabled: true, + ), + Erc20Token( + name: "USD Coin (POS)", + symbol: "USDC.e", + contractAddress: "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174", + decimal: 6, + enabled: true, + ), + Erc20Token( + name: "Avalanche Token", + symbol: "AVAX", + contractAddress: "0x2C89bbc92BD86F8075d1DEcc58C7F4E0107f286b", + decimal: 18, + enabled: false, + ), + Erc20Token( + name: "Wrapped BTC (PoS)", + symbol: "WBTC", + contractAddress: "0x1BFD67037B42Cf73acF2047067bd4F2C47D9BfD6", + decimal: 8, + enabled: false, + ), + Erc20Token( + name: "Dai (PoS)", + symbol: "DAI", + contractAddress: "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063", + decimal: 18, + enabled: true, + ), + Erc20Token( + name: "SHIBA INU (PoS)", + symbol: "SHIB", + contractAddress: "0x6f8a06447Ff6FcF75d803135a7de15CE88C1d4ec", + decimal: 18, + enabled: false, + ), + Erc20Token( + name: "Uniswap (PoS)", + symbol: "UNI", + contractAddress: "0xb33EaAd8d922B1083446DC23f610c2567fB5180f", + decimal: 18, + enabled: false, + ), + ]; + + List get initialPolygonErc20Tokens => _defaultTokens.map((token) { + String? iconPath; + try { + iconPath = CryptoCurrency.all + .firstWhere((element) => + element.title.toUpperCase() == token.symbol.split(".").first.toUpperCase()) + .iconPath; + } catch (_) {} + + return Erc20Token.copyWith(token, iconPath, 'POLY'); + }).toList(); +} diff --git a/cw_polygon/lib/pending_polygon_transaction.dart b/cw_polygon/lib/pending_polygon_transaction.dart new file mode 100644 index 000000000..50f1f0638 --- /dev/null +++ b/cw_polygon/lib/pending_polygon_transaction.dart @@ -0,0 +1,19 @@ +import 'dart:typed_data'; + +import 'package:cw_ethereum/pending_ethereum_transaction.dart'; + +class PendingPolygonTransaction extends PendingEthereumTransaction { + PendingPolygonTransaction({ + required Function sendTransaction, + required Uint8List signedTransaction, + required BigInt fee, + required String amount, + required int exponent, + }) : super( + amount: amount, + sendTransaction: sendTransaction, + signedTransaction: signedTransaction, + fee: fee, + exponent: exponent, + ); +} diff --git a/cw_polygon/lib/polygon_client.dart b/cw_polygon/lib/polygon_client.dart new file mode 100644 index 000000000..876f4c60d --- /dev/null +++ b/cw_polygon/lib/polygon_client.dart @@ -0,0 +1,55 @@ +import 'dart:convert'; + +import 'package:cw_ethereum/ethereum_client.dart'; +import 'package:cw_polygon/polygon_transaction_model.dart'; +import 'package:cw_ethereum/.secrets.g.dart' as secrets; +import 'package:flutter/foundation.dart'; +import 'package:web3dart/web3dart.dart'; + +class PolygonClient extends EthereumClient { + @override + Transaction createTransaction({ + required EthereumAddress from, + required EthereumAddress to, + required EtherAmount amount, + EtherAmount? maxPriorityFeePerGas, + }) { + return Transaction( + from: from, + to: to, + value: amount, + ); + } + + @override + Uint8List prepareSignedTransactionForSending(Uint8List signedTransaction) => signedTransaction; + + @override + int get chainId => 137; + + @override + Future> fetchTransactions(String address, + {String? contractAddress}) async { + try { + final response = await httpClient.get(Uri.https("api.polygonscan.com", "/api", { + "module": "account", + "action": contractAddress != null ? "tokentx" : "txlist", + if (contractAddress != null) "contractaddress": contractAddress, + "address": address, + "apikey": secrets.polygonScanApiKey, + })); + + final jsonResponse = json.decode(response.body) as Map; + + if (response.statusCode >= 200 && response.statusCode < 300 && jsonResponse['status'] != 0) { + return (jsonResponse['result'] as List) + .map((e) => PolygonTransactionModel.fromJson(e as Map)) + .toList(); + } + + return []; + } catch (e) { + return []; + } + } +} diff --git a/cw_polygon/lib/polygon_exceptions.dart b/cw_polygon/lib/polygon_exceptions.dart new file mode 100644 index 000000000..2d08106b6 --- /dev/null +++ b/cw_polygon/lib/polygon_exceptions.dart @@ -0,0 +1,6 @@ +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_ethereum/ethereum_exceptions.dart'; + +class PolygonTransactionCreationException extends EthereumTransactionCreationException { + PolygonTransactionCreationException(CryptoCurrency currency) : super(currency); +} diff --git a/cw_polygon/lib/polygon_formatter.dart b/cw_polygon/lib/polygon_formatter.dart new file mode 100644 index 000000000..f016db7ab --- /dev/null +++ b/cw_polygon/lib/polygon_formatter.dart @@ -0,0 +1,25 @@ +import 'package:intl/intl.dart'; + +const polygonAmountLength = 12; +const polygonAmountDivider = 1000000000000; +final polygonAmountFormat = NumberFormat() + ..maximumFractionDigits = polygonAmountLength + ..minimumFractionDigits = 1; + +class PolygonFormatter { + static int parsePolygonAmount(String amount) { + try { + return (double.parse(amount) * polygonAmountDivider).round(); + } catch (_) { + return 0; + } + } + + static double parsePolygonAmountToDouble(int amount) { + try { + return amount / polygonAmountDivider; + } catch (_) { + return 0; + } + } +} diff --git a/cw_polygon/lib/polygon_mnemonics_exception.dart b/cw_polygon/lib/polygon_mnemonics_exception.dart new file mode 100644 index 000000000..c1a2fcc84 --- /dev/null +++ b/cw_polygon/lib/polygon_mnemonics_exception.dart @@ -0,0 +1,5 @@ +class PolygonMnemonicIsIncorrectException implements Exception { + @override + String toString() => + 'Polygon mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.'; +} diff --git a/cw_polygon/lib/polygon_transaction_credentials.dart b/cw_polygon/lib/polygon_transaction_credentials.dart new file mode 100644 index 000000000..6611e15da --- /dev/null +++ b/cw_polygon/lib/polygon_transaction_credentials.dart @@ -0,0 +1,18 @@ +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/output_info.dart'; +import 'package:cw_ethereum/ethereum_transaction_credentials.dart'; +import 'package:cw_polygon/polygon_transaction_priority.dart'; + +class PolygonTransactionCredentials extends EthereumTransactionCredentials { + PolygonTransactionCredentials( + List outputs, { + required PolygonTransactionPriority? priority, + required CryptoCurrency currency, + final int? feeRate, + }) : super( + outputs, + currency: currency, + priority: priority, + feeRate: feeRate, + ); +} diff --git a/cw_polygon/lib/polygon_transaction_history.dart b/cw_polygon/lib/polygon_transaction_history.dart new file mode 100644 index 000000000..a06b8be4a --- /dev/null +++ b/cw_polygon/lib/polygon_transaction_history.dart @@ -0,0 +1,77 @@ +import 'dart:convert'; +import 'dart:core'; +import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_ethereum/file.dart'; +import 'package:cw_polygon/polygon_transaction_info.dart'; +import 'package:mobx/mobx.dart'; +import 'package:cw_core/transaction_history.dart'; + +part 'polygon_transaction_history.g.dart'; + +const transactionsHistoryFileName = 'polygon_transactions.json'; + +class PolygonTransactionHistory = PolygonTransactionHistoryBase with _$PolygonTransactionHistory; + +abstract class PolygonTransactionHistoryBase extends TransactionHistoryBase + with Store { + PolygonTransactionHistoryBase({required this.walletInfo, required String password}) + : _password = password { + transactions = ObservableMap(); + } + + final WalletInfo walletInfo; + String _password; + + Future init() async => await _load(); + + @override + Future save() async { + try { + final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type); + final path = '$dirPath/$transactionsHistoryFileName'; + final data = json.encode({'transactions': transactions}); + await writeData(path: path, password: _password, data: data); + } catch (e, s) { + print('Error while saving polygon transaction history: ${e.toString()}'); + print(s); + } + } + + @override + void addOne(PolygonTransactionInfo transaction) => transactions[transaction.id] = transaction; + + @override + void addMany(Map transactions) => + this.transactions.addAll(transactions); + + Future> _read() async { + final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type); + final path = '$dirPath/$transactionsHistoryFileName'; + final content = await read(path: path, password: _password); + if (content.isEmpty) { + return {}; + } + return json.decode(content) as Map; + } + + Future _load() async { + try { + final content = await _read(); + final txs = content['transactions'] as Map? ?? {}; + + txs.entries.forEach((entry) { + final val = entry.value; + + if (val is Map) { + final tx = PolygonTransactionInfo.fromJson(val); + _update(tx); + } + }); + } catch (e) { + print(e); + } + } + + void _update(PolygonTransactionInfo transaction) => transactions[transaction.id] = transaction; +} diff --git a/cw_polygon/lib/polygon_transaction_info.dart b/cw_polygon/lib/polygon_transaction_info.dart new file mode 100644 index 000000000..f1976a601 --- /dev/null +++ b/cw_polygon/lib/polygon_transaction_info.dart @@ -0,0 +1,49 @@ +import 'package:cw_core/transaction_direction.dart'; +import 'package:cw_ethereum/ethereum_transaction_info.dart'; + +class PolygonTransactionInfo extends EthereumTransactionInfo { + PolygonTransactionInfo({ + required String id, + required int height, + required BigInt ethAmount, + int exponent = 18, + required TransactionDirection direction, + required DateTime date, + required bool isPending, + required BigInt ethFee, + required int confirmations, + String tokenSymbol = "MATIC", + required String? to, + }) : super( + confirmations: confirmations, + id: id, + height: height, + ethAmount: ethAmount, + exponent: exponent, + direction: direction, + date: date, + isPending: isPending, + ethFee: ethFee, + to: to, + tokenSymbol: tokenSymbol, + ); + + factory PolygonTransactionInfo.fromJson(Map data) { + return PolygonTransactionInfo( + id: data['id'] as String, + height: data['height'] as int, + ethAmount: BigInt.parse(data['amount']), + exponent: data['exponent'] as int, + ethFee: BigInt.parse(data['fee']), + direction: parseTransactionDirectionFromInt(data['direction'] as int), + date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int), + isPending: data['isPending'] as bool, + confirmations: data['confirmations'] as int, + tokenSymbol: data['tokenSymbol'] as String, + to: data['to'], + ); + } + + @override + String feeFormatted() => '${(ethFee / BigInt.from(10).pow(18)).toString()} MATIC'; +} diff --git a/cw_polygon/lib/polygon_transaction_model.dart b/cw_polygon/lib/polygon_transaction_model.dart new file mode 100644 index 000000000..704d674e5 --- /dev/null +++ b/cw_polygon/lib/polygon_transaction_model.dart @@ -0,0 +1,49 @@ +import 'package:cw_ethereum/ethereum_transaction_model.dart'; + +class PolygonTransactionModel extends EthereumTransactionModel { + PolygonTransactionModel({ + required DateTime date, + required String hash, + required String from, + required String to, + required BigInt amount, + required int gasUsed, + required BigInt gasPrice, + required String contractAddress, + required int confirmations, + required int blockNumber, + required String? tokenSymbol, + required int? tokenDecimal, + required bool isError, + }) : super( + amount: amount, + date: date, + hash: hash, + from: from, + to: to, + gasPrice: gasPrice, + gasUsed: gasUsed, + confirmations: confirmations, + contractAddress: contractAddress, + blockNumber: blockNumber, + tokenDecimal: tokenDecimal, + tokenSymbol: tokenSymbol, + isError: isError, + ); + + factory PolygonTransactionModel.fromJson(Map json) => PolygonTransactionModel( + date: DateTime.fromMillisecondsSinceEpoch(int.parse(json["timeStamp"]) * 1000), + hash: json["hash"], + from: json["from"], + to: json["to"], + amount: BigInt.parse(json["value"]), + gasUsed: int.parse(json["gasUsed"]), + gasPrice: BigInt.parse(json["gasPrice"]), + contractAddress: json["contractAddress"], + confirmations: int.parse(json["confirmations"]), + blockNumber: int.parse(json["blockNumber"]), + tokenSymbol: json["tokenSymbol"] ?? "MATIC", + tokenDecimal: int.tryParse(json["tokenDecimal"] ?? ""), + isError: json["isError"] == "1", + ); +} diff --git a/cw_polygon/lib/polygon_transaction_priority.dart b/cw_polygon/lib/polygon_transaction_priority.dart new file mode 100644 index 000000000..dba1dab55 --- /dev/null +++ b/cw_polygon/lib/polygon_transaction_priority.dart @@ -0,0 +1,51 @@ +import 'package:cw_ethereum/ethereum_transaction_priority.dart'; + +class PolygonTransactionPriority extends EthereumTransactionPriority { + const PolygonTransactionPriority({required String title, required int raw, required int tip}) + : super(title: title, raw: raw, tip: tip); + + static const List all = [fast, medium, slow]; + static const PolygonTransactionPriority slow = + PolygonTransactionPriority(title: 'slow', raw: 0, tip: 1); + static const PolygonTransactionPriority medium = + PolygonTransactionPriority(title: 'Medium', raw: 1, tip: 2); + static const PolygonTransactionPriority fast = + PolygonTransactionPriority(title: 'Fast', raw: 2, tip: 4); + + static PolygonTransactionPriority deserialize({required int raw}) { + switch (raw) { + case 0: + return slow; + case 1: + return medium; + case 2: + return fast; + default: + throw Exception('Unexpected token: $raw for PolygonTransactionPriority deserialize'); + } + } + + @override + String get units => 'gas'; + + @override + String toString() { + var label = ''; + + switch (this) { + case PolygonTransactionPriority.slow: + label = 'Slow'; + break; + case PolygonTransactionPriority.medium: + label = 'Medium'; + break; + case PolygonTransactionPriority.fast: + label = 'Fast'; + break; + default: + break; + } + + return label; + } +} diff --git a/cw_polygon/lib/polygon_wallet.dart b/cw_polygon/lib/polygon_wallet.dart new file mode 100644 index 000000000..5749a95ac --- /dev/null +++ b/cw_polygon/lib/polygon_wallet.dart @@ -0,0 +1,521 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'dart:math'; + +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/cake_hive.dart'; +import 'package:cw_core/node.dart'; +import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/pending_transaction.dart'; +import 'package:cw_core/sync_status.dart'; +import 'package:cw_core/transaction_direction.dart'; +import 'package:cw_core/transaction_priority.dart'; +import 'package:cw_core/wallet_addresses.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_ethereum/erc20_balance.dart'; +import 'package:cw_ethereum/ethereum_formatter.dart'; +import 'package:cw_ethereum/file.dart'; +import 'package:cw_core/erc20_token.dart'; +import 'package:cw_polygon/default_polygon_erc20_tokens.dart'; +import 'package:cw_polygon/polygon_client.dart'; +import 'package:cw_polygon/polygon_exceptions.dart'; +import 'package:cw_polygon/polygon_formatter.dart'; +import 'package:cw_polygon/polygon_transaction_credentials.dart'; +import 'package:cw_polygon/polygon_transaction_history.dart'; +import 'package:cw_polygon/polygon_transaction_info.dart'; +import 'package:cw_polygon/polygon_transaction_model.dart'; +import 'package:cw_polygon/polygon_transaction_priority.dart'; +import 'package:cw_polygon/polygon_wallet_addresses.dart'; +import 'package:hive/hive.dart'; +import 'package:hex/hex.dart'; +import 'package:mobx/mobx.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:web3dart/crypto.dart'; +import 'package:web3dart/web3dart.dart'; +import 'package:bip39/bip39.dart' as bip39; +import 'package:bip32/bip32.dart' as bip32; + +part 'polygon_wallet.g.dart'; + +class PolygonWallet = PolygonWalletBase with _$PolygonWallet; + +abstract class PolygonWalletBase + extends WalletBase with Store { + PolygonWalletBase({ + required WalletInfo walletInfo, + String? mnemonic, + String? privateKey, + required String password, + ERC20Balance? initialBalance, + }) : syncStatus = const NotConnectedSyncStatus(), + _password = password, + _mnemonic = mnemonic, + _hexPrivateKey = privateKey, + _isTransactionUpdating = false, + _client = PolygonClient(), + walletAddresses = PolygonWalletAddresses(walletInfo), + balance = ObservableMap.of( + {CryptoCurrency.maticpoly: initialBalance ?? ERC20Balance(BigInt.zero)}), + super(walletInfo) { + this.walletInfo = walletInfo; + transactionHistory = PolygonTransactionHistory(walletInfo: walletInfo, password: password); + + if (!CakeHive.isAdapterRegistered(Erc20Token.typeId)) { + CakeHive.registerAdapter(Erc20TokenAdapter()); + } + + _sharedPrefs.complete(SharedPreferences.getInstance()); + } + + final String? _mnemonic; + final String? _hexPrivateKey; + final String _password; + + late final Box polygonErc20TokensBox; + + late final EthPrivateKey _polygonPrivateKey; + + late final PolygonClient _client; + + EthPrivateKey get polygonPrivateKey => _polygonPrivateKey; + + int? _gasPrice; + int? _estimatedGas; + bool _isTransactionUpdating; + + // TODO: remove after integrating our own node and having eth_newPendingTransactionFilter + Timer? _transactionsUpdateTimer; + + @override + WalletAddresses walletAddresses; + + @override + @observable + SyncStatus syncStatus; + + @override + @observable + late ObservableMap balance; + + final Completer _sharedPrefs = Completer(); + + Future init() async { + polygonErc20TokensBox = await CakeHive.openBox( + "${walletInfo.name.replaceAll(" ", "_")}_${Erc20Token.polygonBoxName}"); + await walletAddresses.init(); + await transactionHistory.init(); + _polygonPrivateKey = await getPrivateKey( + mnemonic: _mnemonic, + privateKey: _hexPrivateKey, + password: _password, + ); + walletAddresses.address = _polygonPrivateKey.address.toString(); + await save(); + } + + @override + int calculateEstimatedFee(TransactionPriority priority, int? amount) { + try { + if (priority is PolygonTransactionPriority) { + final priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt(); + return (_gasPrice! + priorityFee) * (_estimatedGas ?? 0); + } + + return 0; + } catch (e) { + return 0; + } + } + + @override + Future changePassword(String password) { + throw UnimplementedError("changePassword"); + } + + @override + void close() { + _client.stop(); + _transactionsUpdateTimer?.cancel(); + } + + @action + @override + Future connectToNode({required Node node}) async { + try { + syncStatus = ConnectingSyncStatus(); + + final isConnected = _client.connect(node); + + if (!isConnected) { + throw Exception("Polygon Node connection failed"); + } + + _client.setListeners(_polygonPrivateKey.address, _onNewTransaction); + + _setTransactionUpdateTimer(); + + syncStatus = ConnectedSyncStatus(); + } catch (e) { + syncStatus = FailedSyncStatus(); + } + } + + @override + Future createTransaction(Object credentials) async { + final credentials0 = credentials as PolygonTransactionCredentials; + final outputs = credentials0.outputs; + final hasMultiDestination = outputs.length > 1; + + final CryptoCurrency transactionCurrency = + balance.keys.firstWhere((element) => element.title == credentials0.currency.title); + + final erc20Balance = balance[transactionCurrency]!; + BigInt totalAmount = BigInt.zero; + int exponent = transactionCurrency is Erc20Token ? transactionCurrency.decimal : 18; + num amountToPolygonMultiplier = pow(10, exponent); + + // so far this can not be made with Polygon as Polygon does not support multiple recipients + if (hasMultiDestination) { + if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) { + throw PolygonTransactionCreationException(transactionCurrency); + } + + final totalOriginalAmount = PolygonFormatter.parsePolygonAmountToDouble( + outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0))); + totalAmount = BigInt.from(totalOriginalAmount * amountToPolygonMultiplier); + + if (erc20Balance.balance < totalAmount) { + throw PolygonTransactionCreationException(transactionCurrency); + } + } else { + final output = outputs.first; + // since the fees are taken from Ethereum + // then no need to subtract the fees from the amount if send all + final BigInt allAmount; + if (transactionCurrency is Erc20Token) { + allAmount = erc20Balance.balance; + } else { + allAmount = + erc20Balance.balance - BigInt.from(calculateEstimatedFee(credentials0.priority!, null)); + } + final totalOriginalAmount = + EthereumFormatter.parseEthereumAmountToDouble(output.formattedCryptoAmount ?? 0); + totalAmount = + output.sendAll ? allAmount : BigInt.from(totalOriginalAmount * amountToPolygonMultiplier); + + if (erc20Balance.balance < totalAmount) { + throw PolygonTransactionCreationException(transactionCurrency); + } + } + + final pendingPolygonTransaction = await _client.signTransaction( + privateKey: _polygonPrivateKey, + toAddress: credentials0.outputs.first.isParsedAddress + ? credentials0.outputs.first.extractedAddress! + : credentials0.outputs.first.address, + amount: totalAmount.toString(), + gas: _estimatedGas!, + priority: credentials0.priority!, + currency: transactionCurrency, + exponent: exponent, + contractAddress: + transactionCurrency is Erc20Token ? transactionCurrency.contractAddress : null, + ); + + return pendingPolygonTransaction; + } + + Future _updateTransactions() async { + try { + if (_isTransactionUpdating) { + return; + } + bool isPolygonScanEnabled = (await _sharedPrefs.future).getBool("use_polygonscan") ?? true; + if (!isPolygonScanEnabled) { + return; + } + + _isTransactionUpdating = true; + final transactions = await fetchTransactions(); + transactionHistory.addMany(transactions); + await transactionHistory.save(); + _isTransactionUpdating = false; + } catch (_) { + _isTransactionUpdating = false; + } + } + + @override + Future> fetchTransactions() async { + final address = _polygonPrivateKey.address.hex; + final transactions = await _client.fetchTransactions(address); + + final List>> polygonErc20TokensTransactions = []; + + for (var token in balance.keys) { + if (token is Erc20Token) { + polygonErc20TokensTransactions.add( + _client.fetchTransactions( + address, + contractAddress: token.contractAddress, + ), + ); + } + } + + final tokensTransaction = await Future.wait(polygonErc20TokensTransactions); + transactions.addAll(tokensTransaction.expand((element) => element)); + + final Map result = {}; + + for (var transactionModel in transactions) { + if (transactionModel.isError) { + continue; + } + + result[transactionModel.hash] = PolygonTransactionInfo( + id: transactionModel.hash, + height: transactionModel.blockNumber, + ethAmount: transactionModel.amount, + direction: transactionModel.from == address + ? TransactionDirection.outgoing + : TransactionDirection.incoming, + isPending: false, + date: transactionModel.date, + confirmations: transactionModel.confirmations, + ethFee: BigInt.from(transactionModel.gasUsed) * transactionModel.gasPrice, + exponent: transactionModel.tokenDecimal ?? 18, + tokenSymbol: transactionModel.tokenSymbol ?? "MATIC", + to: transactionModel.to, + ); + } + + return result; + } + + @override + Object get keys => throw UnimplementedError("keys"); + + @override + Future rescan({required int height}) { + throw UnimplementedError("rescan"); + } + + @override + Future save() async { + await walletAddresses.updateAddressesInBox(); + final path = await makePath(); + await write(path: path, password: _password, data: toJSON()); + await transactionHistory.save(); + } + + @override + String? get seed => _mnemonic; + + @override + String get privateKey => HEX.encode(_polygonPrivateKey.privateKey); + + @action + @override + Future startSync() async { + try { + syncStatus = AttemptingSyncStatus(); + await _updateBalance(); + await _updateTransactions(); + _gasPrice = await _client.getGasUnitPrice(); + _estimatedGas = await _client.getEstimatedGas(); + + Timer.periodic( + const Duration(minutes: 1), (timer) async => _gasPrice = await _client.getGasUnitPrice()); + Timer.periodic(const Duration(seconds: 10), + (timer) async => _estimatedGas = await _client.getEstimatedGas()); + + syncStatus = SyncedSyncStatus(); + } catch (e) { + syncStatus = FailedSyncStatus(); + } + } + + Future makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type); + + String toJSON() => json.encode({ + 'mnemonic': _mnemonic, + 'private_key': privateKey, + 'balance': balance[currency]!.toJSON(), + }); + + static Future open({ + required String name, + required String password, + required WalletInfo walletInfo, + }) async { + final path = await pathForWallet(name: name, type: walletInfo.type); + final jsonSource = await read(path: path, password: password); + final data = json.decode(jsonSource) as Map; + final mnemonic = data['mnemonic'] as String?; + final privateKey = data['private_key'] as String?; + final balance = ERC20Balance.fromJSON(data['balance'] as String) ?? ERC20Balance(BigInt.zero); + + return PolygonWallet( + walletInfo: walletInfo, + password: password, + mnemonic: mnemonic, + privateKey: privateKey, + initialBalance: balance, + ); + } + + Future _updateBalance() async { + balance[currency] = await _fetchMaticBalance(); + + await _fetchErc20Balances(); + await save(); + } + + Future _fetchMaticBalance() async { + final balance = await _client.getBalance(_polygonPrivateKey.address); + return ERC20Balance(balance.getInWei); + } + + Future _fetchErc20Balances() async { + for (var token in polygonErc20TokensBox.values) { + try { + if (token.enabled) { + balance[token] = await _client.fetchERC20Balances( + _polygonPrivateKey.address, + token.contractAddress, + ); + } else { + balance.remove(token); + } + } catch (_) {} + } + } + + Future getPrivateKey( + {String? mnemonic, String? privateKey, required String password}) async { + assert(mnemonic != null || privateKey != null); + + if (privateKey != null) { + return EthPrivateKey.fromHex(privateKey); + } + + final seed = bip39.mnemonicToSeed(mnemonic!); + + final root = bip32.BIP32.fromSeed(seed); + + const hdPathPolygon = "m/44'/60'/0'/0"; + const index = 0; + final addressAtIndex = root.derivePath("$hdPathPolygon/$index"); + + return EthPrivateKey.fromHex(HEX.encode(addressAtIndex.privateKey as List)); + } + + @override + Future? updateBalance() async => await _updateBalance(); + + List get erc20Currencies => polygonErc20TokensBox.values.toList(); + + Future addErc20Token(Erc20Token token) async { + String? iconPath; + try { + iconPath = CryptoCurrency.all + .firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase()) + .iconPath; + } catch (_) {} + + final token0 = Erc20Token( + name: token.name, + symbol: token.symbol, + contractAddress: token.contractAddress, + decimal: token.decimal, + enabled: token.enabled, + tag: token.tag ?? "POLY", + iconPath: iconPath, + ); + + await polygonErc20TokensBox.put(token0.contractAddress, token0); + + if (token0.enabled) { + balance[token0] = await _client.fetchERC20Balances( + _polygonPrivateKey.address, + token0.contractAddress, + ); + } else { + balance.remove(token0); + } + } + + Future deleteErc20Token(Erc20Token token) async { + await token.delete(); + + balance.remove(token); + _updateBalance(); + } + + Future getErc20Token(String contractAddress) async => + await _client.getErc20Token(contractAddress); + + void _onNewTransaction() { + _updateBalance(); + _updateTransactions(); + } + + void addInitialTokens() { + final initialErc20Tokens = DefaultPolygonErc20Tokens().initialPolygonErc20Tokens; + + for (var token in initialErc20Tokens) { + polygonErc20TokensBox.put(token.contractAddress, token); + } + } + + @override + Future renameWalletFiles(String newWalletName) async { + final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type); + final currentWalletFile = File(currentWalletPath); + + final currentDirPath = await pathForWalletDir(name: walletInfo.name, type: type); + final currentTransactionsFile = File('$currentDirPath/$transactionsHistoryFileName'); + + // Copies current wallet files into new wallet name's dir and files + if (currentWalletFile.existsSync()) { + final newWalletPath = await pathForWallet(name: newWalletName, type: type); + await currentWalletFile.copy(newWalletPath); + } + if (currentTransactionsFile.existsSync()) { + final newDirPath = await pathForWalletDir(name: newWalletName, type: type); + await currentTransactionsFile.copy('$newDirPath/$transactionsHistoryFileName'); + } + + // Delete old name's dir and files + await Directory(currentDirPath).delete(recursive: true); + } + + void _setTransactionUpdateTimer() { + if (_transactionsUpdateTimer?.isActive ?? false) { + _transactionsUpdateTimer!.cancel(); + } + + _transactionsUpdateTimer = Timer.periodic(const Duration(seconds: 10), (_) { + _updateTransactions(); + _updateBalance(); + }); + } + + void updatePolygonScanUsageState(bool isEnabled) { + if (isEnabled) { + _updateTransactions(); + _setTransactionUpdateTimer(); + } else { + _transactionsUpdateTimer?.cancel(); + } + } + + @override + String signMessage(String message, {String? address}) => + bytesToHex(_polygonPrivateKey.signPersonalMessageToUint8List(ascii.encode(message))); + + Web3Client? getWeb3Client() => _client.getWeb3Client(); +} diff --git a/cw_polygon/lib/polygon_wallet_addresses.dart b/cw_polygon/lib/polygon_wallet_addresses.dart new file mode 100644 index 000000000..0a6a407c7 --- /dev/null +++ b/cw_polygon/lib/polygon_wallet_addresses.dart @@ -0,0 +1,5 @@ +import 'package:cw_ethereum/ethereum_wallet_addresses.dart'; + +class PolygonWalletAddresses extends EthereumWalletAddresses { + PolygonWalletAddresses(super.walletInfo); +} diff --git a/cw_polygon/lib/polygon_wallet_creation_credentials.dart b/cw_polygon/lib/polygon_wallet_creation_credentials.dart new file mode 100644 index 000000000..74c7c5ed7 --- /dev/null +++ b/cw_polygon/lib/polygon_wallet_creation_credentials.dart @@ -0,0 +1,28 @@ +import 'package:cw_core/wallet_credentials.dart'; +import 'package:cw_core/wallet_info.dart'; +class PolygonNewWalletCredentials extends WalletCredentials { + PolygonNewWalletCredentials({required String name, WalletInfo? walletInfo}) + : super(name: name, walletInfo: walletInfo); +} + +class PolygonRestoreWalletFromSeedCredentials extends WalletCredentials { + PolygonRestoreWalletFromSeedCredentials( + {required String name, + required String password, + required this.mnemonic, + WalletInfo? walletInfo}) + : super(name: name, password: password, walletInfo: walletInfo); + + final String mnemonic; +} + +class PolygonRestoreWalletFromPrivateKey extends WalletCredentials { + PolygonRestoreWalletFromPrivateKey( + {required String name, + required String password, + required this.privateKey, + WalletInfo? walletInfo}) + : super(name: name, password: password, walletInfo: walletInfo); + + final String privateKey; +} diff --git a/cw_polygon/lib/polygon_wallet_service.dart b/cw_polygon/lib/polygon_wallet_service.dart new file mode 100644 index 000000000..43c6269f6 --- /dev/null +++ b/cw_polygon/lib/polygon_wallet_service.dart @@ -0,0 +1,119 @@ +import 'dart:io'; + +import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_service.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:cw_ethereum/ethereum_mnemonics.dart'; +import 'package:cw_polygon/polygon_wallet.dart'; +import 'package:bip39/bip39.dart' as bip39; +import 'package:hive/hive.dart'; +import 'polygon_wallet_creation_credentials.dart'; +import 'package:collection/collection.dart'; + +class PolygonWalletService extends WalletService { + PolygonWalletService(this.walletInfoSource); + + final Box walletInfoSource; + + @override + Future create(PolygonNewWalletCredentials credentials) async { + final strength = credentials.seedPhraseLength == 24 ? 256 : 128; + + final mnemonic = bip39.generateMnemonic(strength: strength); + final wallet = PolygonWallet( + walletInfo: credentials.walletInfo!, + mnemonic: mnemonic, + password: credentials.password!, + ); + + await wallet.init(); + wallet.addInitialTokens(); + await wallet.save(); + + return wallet; + } + + @override + WalletType getType() => WalletType.polygon; + + @override + Future isWalletExit(String name) async => + File(await pathForWallet(name: name, type: getType())).existsSync(); + + @override + Future openWallet(String name, String password) async { + final walletInfo = + walletInfoSource.values.firstWhere((info) => info.id == WalletBase.idFor(name, getType())); + final wallet = await PolygonWalletBase.open( + name: name, + password: password, + walletInfo: walletInfo, + ); + + await wallet.init(); + await wallet.save(); + + return wallet; + } + + @override + Future remove(String wallet) async { + File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true); + final walletInfo = walletInfoSource.values + .firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!; + await walletInfoSource.delete(walletInfo.key); + } + + @override + Future restoreFromKeys(PolygonRestoreWalletFromPrivateKey credentials) async { + final wallet = PolygonWallet( + password: credentials.password!, + privateKey: credentials.privateKey, + walletInfo: credentials.walletInfo!, + ); + + await wallet.init(); + wallet.addInitialTokens(); + await wallet.save(); + + return wallet; + } + + @override + Future restoreFromSeed(PolygonRestoreWalletFromSeedCredentials credentials) async { + if (!bip39.validateMnemonic(credentials.mnemonic)) { + throw EthereumMnemonicIsIncorrectException(); + } + + final wallet = PolygonWallet( + password: credentials.password!, + mnemonic: credentials.mnemonic, + walletInfo: credentials.walletInfo!, + ); + + await wallet.init(); + wallet.addInitialTokens(); + await wallet.save(); + + return wallet; + } + + @override + Future rename(String currentName, String password, String newName) async { + final currentWalletInfo = walletInfoSource.values + .firstWhere((info) => info.id == WalletBase.idFor(currentName, getType())); + final currentWallet = await PolygonWalletBase.open( + password: password, name: currentName, walletInfo: currentWalletInfo); + + await currentWallet.renameWalletFiles(newName); + + final newWalletInfo = currentWalletInfo; + newWalletInfo.id = WalletBase.idFor(newName, getType()); + newWalletInfo.name = newName; + + await walletInfoSource.put(currentWalletInfo.key, newWalletInfo); + } +} diff --git a/cw_polygon/pubspec.yaml b/cw_polygon/pubspec.yaml new file mode 100644 index 000000000..e99e6dbbb --- /dev/null +++ b/cw_polygon/pubspec.yaml @@ -0,0 +1,73 @@ +name: cw_polygon +description: A new Flutter package project. +version: 0.0.1 +publish_to: none +author: Cake Wallet +homepage: https://cakewallet.com + +environment: + sdk: '>=3.0.6 <4.0.0' + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + cw_core: + path: ../cw_core + cw_ethereum: + path: ../cw_ethereum + mobx: ^2.0.7+4 + intl: ^0.18.0 + bip39: ^1.0.6 + hive: ^2.2.3 + collection: ^1.17.1 + web3dart: ^2.7.1 + bip32: ^2.0.0 + hex: ^0.2.0 + shared_preferences: ^2.0.15 + + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + build_runner: ^2.1.11 + mobx_codegen: ^2.0.7 + hive_generator: ^1.1.3 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # To add assets to your package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # To add custom fonts to your package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/cw_polygon/test/cw_polygon_test.dart b/cw_polygon/test/cw_polygon_test.dart new file mode 100644 index 000000000..554e28795 --- /dev/null +++ b/cw_polygon/test/cw_polygon_test.dart @@ -0,0 +1,12 @@ +import 'package:flutter_test/flutter_test.dart'; + +import 'package:cw_polygon/cw_polygon.dart'; + +void main() { + test('adds one to input values', () { + final calculator = Calculator(); + expect(calculator.addOne(2), 3); + expect(calculator.addOne(-7), -6); + expect(calculator.addOne(0), 1); + }); +} diff --git a/ios/Podfile.lock b/ios/Podfile.lock index bbb088325..3118e6ad7 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -145,8 +145,6 @@ PODS: - SwiftProtobuf (1.22.0) - SwiftyGif (5.4.4) - Toast (4.0.0) - - tor (0.0.1): - - Flutter - uni_links (0.0.1): - Flutter - UnstoppableDomainsResolution (4.0.0): @@ -184,7 +182,6 @@ DEPENDENCIES: - sensitive_clipboard (from `.symlinks/plugins/sensitive_clipboard/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - - tor (from `.symlinks/plugins/tor/ios`) - uni_links (from `.symlinks/plugins/uni_links/ios`) - UnstoppableDomainsResolution (~> 4.0.0) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) @@ -253,8 +250,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/share_plus/ios" shared_preferences_foundation: :path: ".symlinks/plugins/shared_preferences_foundation/darwin" - tor: - :path: ".symlinks/plugins/tor/ios" uni_links: :path: ".symlinks/plugins/uni_links/ios" url_launcher_ios: @@ -299,7 +294,6 @@ SPEC CHECKSUMS: SwiftProtobuf: 40bd808372cb8706108f22d28f8ab4a6b9bc6989 SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f Toast: 91b396c56ee72a5790816f40d3a94dd357abc196 - tor: 662a9f5b980b5c86decb8ba611de9bcd4c8286eb uni_links: d97da20c7701486ba192624d99bffaaffcfc298a UnstoppableDomainsResolution: c3c67f4d0a5e2437cb00d4bd50c2e00d6e743841 url_launcher_ios: bf5ce03e0e2088bad9cc378ea97fa0ed5b49673b diff --git a/ios/Runner/InfoBase.plist b/ios/Runner/InfoBase.plist index 6cea7a730..4f7036498 100644 --- a/ios/Runner/InfoBase.plist +++ b/ios/Runner/InfoBase.plist @@ -160,6 +160,26 @@ bitcoincash-wallet + + CFBundleTypeRole + Editor + CFBundleURLName + polygon + CFBundleURLSchemes + + polygon + + + + CFBundleTypeRole + Viewer + CFBundleURLName + polygon-wallet + CFBundleURLSchemes + + polygon-wallet + + CFBundleVersion $(CURRENT_PROJECT_VERSION) diff --git a/lib/buy/buy_exception.dart b/lib/buy/buy_exception.dart index edc6a7db0..c201b3b2d 100644 --- a/lib/buy/buy_exception.dart +++ b/lib/buy/buy_exception.dart @@ -2,11 +2,11 @@ import 'package:flutter/foundation.dart'; import 'package:cake_wallet/buy/buy_provider_description.dart'; class BuyException implements Exception { - BuyException({required this.description, required this.text}); + BuyException({required this.title, required this.content}); - final BuyProviderDescription description; - final String text; + final String title; + final String content; @override - String toString() => '${description.title}: $text'; + String toString() => '$title: $content'; } \ No newline at end of file diff --git a/lib/buy/buy_provider.dart b/lib/buy/buy_provider.dart index 10a13ed94..4e4c113f4 100644 --- a/lib/buy/buy_provider.dart +++ b/lib/buy/buy_provider.dart @@ -1,27 +1,33 @@ import 'package:cake_wallet/buy/buy_amount.dart'; -import 'package:cake_wallet/buy/buy_provider_description.dart'; import 'package:cake_wallet/buy/order.dart'; import 'package:cw_core/wallet_base.dart'; -import 'package:cw_core/wallet_type.dart'; +import 'package:flutter/material.dart'; abstract class BuyProvider { - BuyProvider({required this.wallet, required this.isTestEnvironment}); + BuyProvider({ + required this.wallet, + required this.isTestEnvironment, + }); final WalletBase wallet; final bool isTestEnvironment; String get title; - BuyProviderDescription get description; - String get trackUrl; - WalletType get walletType => wallet.type; - String get walletAddress => wallet.walletAddresses.address; - String get walletId => wallet.id; + String get providerDescription; + + String get lightIcon; + + String get darkIcon; @override String toString() => title; - Future requestUrl(String amount, String sourceCurrency); - Future findOrderById(String id); - Future calculateAmount(String amount, String sourceCurrency); -} \ No newline at end of file + Future launchProvider(BuildContext context, bool? isBuyAction); + + Future requestUrl(String amount, String sourceCurrency) => throw UnimplementedError(); + + Future findOrderById(String id) => throw UnimplementedError(); + + Future calculateAmount(String amount, String sourceCurrency) => throw UnimplementedError(); +} diff --git a/lib/buy/dfx/dfx_buy_provider.dart b/lib/buy/dfx/dfx_buy_provider.dart new file mode 100644 index 000000000..f74039caa --- /dev/null +++ b/lib/buy/dfx/dfx_buy_provider.dart @@ -0,0 +1,201 @@ +import 'dart:convert'; + +import 'package:cake_wallet/buy/buy_provider.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; +import 'package:cake_wallet/utils/device_info.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; +import 'package:url_launcher/url_launcher.dart'; + +class DFXBuyProvider extends BuyProvider { + DFXBuyProvider({required WalletBase wallet, bool isTestEnvironment = false}) + : super(wallet: wallet, isTestEnvironment: isTestEnvironment); + + static const _baseUrl = 'api.dfx.swiss'; + static const _authPath = '/v1/auth/signMessage'; + static const _signUpPath = '/v1/auth/signUp'; + static const _signInPath = '/v1/auth/signIn'; + static const walletName = 'CakeWallet'; + + @override + String get title => 'DFX Connect'; + + @override + String get providerDescription => S.current.dfx_option_description; + + @override + String get lightIcon => 'assets/images/dfx_light.png'; + + @override + String get darkIcon => 'assets/images/dfx_dark.png'; + + String get assetOut { + switch (wallet.type) { + case WalletType.bitcoin: + return 'BTC'; + case WalletType.bitcoinCash: + return 'BCH'; + case WalletType.litecoin: + return 'LTC'; + case WalletType.monero: + return 'XMR'; + case WalletType.ethereum: + return 'ETH'; + default: + throw Exception("WalletType is not available for DFX ${wallet.type}"); + } + } + + String get blockchain { + switch (wallet.type) { + case WalletType.bitcoin: + case WalletType.bitcoinCash: + case WalletType.litecoin: + return 'Bitcoin'; + case WalletType.monero: + return 'Monero'; + case WalletType.ethereum: + return 'Ethereum'; + default: + throw Exception("WalletType is not available for DFX ${wallet.type}"); + } + } + + Future getSignMessage() async { + final walletAddress = wallet.walletAddresses.address; + final uri = Uri.https(_baseUrl, _authPath, {'address': walletAddress}); + + var response = await http.get(uri, headers: {'accept': 'application/json'}); + + if (response.statusCode == 200) { + final responseBody = jsonDecode(response.body); + return responseBody['message'] as String; + } else { + throw Exception( + 'Failed to get sign message. Status: ${response.statusCode} ${response.body}'); + } + } + + Future signUp() async { + final signMessage = getSignature(await getSignMessage()); + final walletAddress = wallet.walletAddresses.address; + + final requestBody = jsonEncode({ + 'wallet': walletName, + 'address': walletAddress, + 'signature': signMessage, + }); + + final uri = Uri.https(_baseUrl, _signUpPath); + var response = await http.post(uri, + headers: {'Content-Type': 'application/json'}, body: requestBody); + + if (response.statusCode == 201) { + final responseBody = jsonDecode(response.body); + return responseBody['accessToken'] as String; + } else if (response.statusCode == 403) { + final responseBody = jsonDecode(response.body); + final message = responseBody['message'] ?? 'Service unavailable in your country'; + throw Exception(message); + } else { + throw Exception( + 'Failed to sign up. Status: ${response.statusCode} ${response.body}'); + } + } + + Future signIn() async { + final signMessage = getSignature(await getSignMessage()); + final walletAddress = wallet.walletAddresses.address; + + final requestBody = jsonEncode({ + 'address': walletAddress, + 'signature': signMessage, + }); + + final uri = Uri.https(_baseUrl, _signInPath); + var response = await http.post(uri, + headers: {'Content-Type': 'application/json'}, body: requestBody); + + if (response.statusCode == 201) { + final responseBody = jsonDecode(response.body); + return responseBody['accessToken'] as String; + } else if (response.statusCode == 403) { + final responseBody = jsonDecode(response.body); + final message = responseBody['message'] ?? 'Service unavailable in your country'; + throw Exception(message); + } else { + throw Exception( + 'Failed to sign in. Status: ${response.statusCode} ${response.body}'); + } + } + + String getSignature(String message) { + switch (wallet.type) { + case WalletType.ethereum: + return wallet.signMessage(message); + case WalletType.monero: + case WalletType.litecoin: + case WalletType.bitcoin: + case WalletType.bitcoinCash: + return wallet.signMessage(message, + address: wallet.walletAddresses.address); + default: + throw Exception("WalletType is not available for DFX ${wallet.type}"); + } + } + + @override + Future launchProvider(BuildContext context, bool? isBuyAction) async { + try { + final assetOut = this.assetOut; + final blockchain = this.blockchain; + final actionType = isBuyAction == true ? '/buy' : '/sell'; + + String accessToken; + + try { + accessToken = await signUp(); + } on Exception catch (e) { + if (e.toString().contains('409')) { + accessToken = await signIn(); + } else { + rethrow; + } + } + + final uri = Uri.https('services.dfx.swiss', actionType, { + 'session': accessToken, + 'lang': 'en', + 'asset-out': assetOut, + 'blockchain': blockchain, + 'asset-in': 'EUR', + }); + + if (await canLaunchUrl(uri)) { + if (DeviceInfo.instance.isMobile) { + Navigator.of(context) + .pushNamed(Routes.webViewPage, arguments: ["DFX Connect", uri]); + } else { + await launchUrl(uri, mode: LaunchMode.externalApplication); + } + } else { + throw Exception('Could not launch URL'); + } + } catch (e) { + await showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: "DFX Connect", + alertContent: S.of(context).buy_provider_unavailable + ': $e', + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }); + } + } +} diff --git a/lib/buy/moonpay/moonpay_buy_provider.dart b/lib/buy/moonpay/moonpay_provider.dart similarity index 54% rename from lib/buy/moonpay/moonpay_buy_provider.dart rename to lib/buy/moonpay/moonpay_provider.dart index 0736edeb7..0ccb73e1c 100644 --- a/lib/buy/moonpay/moonpay_buy_provider.dart +++ b/lib/buy/moonpay/moonpay_provider.dart @@ -1,9 +1,14 @@ import 'dart:convert'; import 'package:cake_wallet/palette.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:cake_wallet/utils/device_info.dart'; import 'package:crypto/crypto.dart'; import 'package:cake_wallet/buy/buy_exception.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:flutter/material.dart'; import 'package:http/http.dart'; import 'package:cake_wallet/buy/buy_amount.dart'; import 'package:cake_wallet/buy/buy_provider.dart'; @@ -14,33 +19,55 @@ import 'package:cw_core/wallet_type.dart'; import 'package:cake_wallet/exchange/trade_state.dart'; import 'package:cake_wallet/.secrets.g.dart' as secrets; import 'package:cw_core/crypto_currency.dart'; +import 'package:url_launcher/url_launcher.dart'; -class MoonPaySellProvider { - MoonPaySellProvider({this.isTest = false}) - : baseUrl = isTest ? _baseTestUrl : _baseProductUrl; +class MoonPaySellProvider extends BuyProvider { + MoonPaySellProvider({ + required SettingsStore settingsStore, + required WalletBase wallet, + bool isTestEnvironment = false, + }) : baseUrl = isTestEnvironment ? _baseTestUrl : _baseProductUrl, + this._settingsStore = settingsStore, + super(wallet: wallet, isTestEnvironment: isTestEnvironment); + + final SettingsStore _settingsStore; static const _baseTestUrl = 'sell-sandbox.moonpay.com'; static const _baseProductUrl = 'sell.moonpay.com'; + + @override + String get providerDescription => + 'MoonPay offers a fast and simple way to buy and sell cryptocurrencies'; + + @override + String get title => 'MoonPay'; + + @override + String get lightIcon => 'assets/images/moonpay_light.png'; + + @override + String get darkIcon => 'assets/images/moonpay_dark.png'; + static String themeToMoonPayTheme(ThemeBase theme) { switch (theme.type) { case ThemeType.bright: - return 'light'; case ThemeType.light: return 'light'; case ThemeType.dark: return 'dark'; } } - static String get _apiKey => secrets.moonPayApiKey; - static String get _secretKey => secrets.moonPaySecretKey; - final bool isTest; + + static String get _apiKey => secrets.moonPayApiKey; + + static String get _secretKey => secrets.moonPaySecretKey; final String baseUrl; - Future requestUrl( - {required CryptoCurrency currency, - required String refundWalletAddress, - required SettingsStore settingsStore}) async { - + Future requestMoonPayUrl({ + required CryptoCurrency currency, + required String refundWalletAddress, + required SettingsStore settingsStore, + }) async { final customParams = { 'theme': themeToMoonPayTheme(settingsStore.currentTheme), 'language': settingsStore.languageCode, @@ -50,18 +77,22 @@ class MoonPaySellProvider { }; final originalUri = Uri.https( - baseUrl, '', { + baseUrl, + '', + { 'apiKey': _apiKey, 'defaultBaseCurrencyCode': currency.toString().toLowerCase(), - 'refundWalletAddress': refundWalletAddress - }..addAll(customParams)); + 'refundWalletAddress': refundWalletAddress, + }..addAll(customParams), + ); + final messageBytes = utf8.encode('?${originalUri.query}'); final key = utf8.encode(_secretKey); final hmac = Hmac(sha256, key); final digest = hmac.convert(messageBytes); final signature = base64.encode(digest.bytes); - if (isTest) { + if (isTestEnvironment) { return originalUri; } @@ -70,6 +101,39 @@ class MoonPaySellProvider { final signedUri = originalUri.replace(queryParameters: query); return signedUri; } + + @override + Future launchProvider(BuildContext context, bool? isBuyAction) async { + try { + final uri = await requestMoonPayUrl( + currency: wallet.currency, + refundWalletAddress: wallet.walletAddresses.address, + settingsStore: _settingsStore, + ); + + if (await canLaunchUrl(uri)) { + if (DeviceInfo.instance.isMobile) { + Navigator.of(context).pushNamed(Routes.webViewPage, arguments: ['MoonPay', uri]); + } else { + await launchUrl(uri, mode: LaunchMode.externalApplication); + } + } else { + throw Exception('Could not launch URL'); + } + } catch (e) { + await showDialog( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: 'MoonPay', + alertContent: 'The MoonPay service is currently unavailable: $e', + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop(), + ); + }, + ); + } + } } class MoonPayBuyProvider extends BuyProvider { @@ -91,28 +155,40 @@ class MoonPayBuyProvider extends BuyProvider { String get title => 'MoonPay'; @override - BuyProviderDescription get description => BuyProviderDescription.moonPay; - - String get currencyCode => - walletTypeToCryptoCurrency(walletType).title.toLowerCase(); + String get providerDescription => + 'MoonPay offers a fast and simple way to buy and sell cryptocurrencies'; @override + String get lightIcon => 'assets/images/moonpay_light.png'; + + @override + String get darkIcon => 'assets/images/moonpay_dark.png'; + + String get currencyCode => walletTypeToCryptoCurrency(wallet.type).title.toLowerCase(); + String get trackUrl => baseUrl + '/transaction_receipt?transactionId='; String baseUrl; - @override Future requestUrl(String amount, String sourceCurrency) async { - final enabledPaymentMethods = - 'credit_debit_card%2Capple_pay%2Cgoogle_pay%2Csamsung_pay' + final enabledPaymentMethods = 'credit_debit_card%2Capple_pay%2Cgoogle_pay%2Csamsung_pay' '%2Csepa_bank_transfer%2Cgbp_bank_transfer%2Cgbp_open_banking_payment'; - final suffix = '?apiKey=' + _apiKey + '¤cyCode=' + - currencyCode + '&enabledPaymentMethods=' + enabledPaymentMethods + - '&walletAddress=' + walletAddress + - '&baseCurrencyCode=' + sourceCurrency.toLowerCase() + - '&baseCurrencyAmount=' + amount + '&lockAmount=true' + - '&showAllCurrencies=false' + '&showWalletAddressForm=false'; + final suffix = '?apiKey=' + + _apiKey + + '¤cyCode=' + + currencyCode + + '&enabledPaymentMethods=' + + enabledPaymentMethods + + '&walletAddress=' + + wallet.walletAddresses.address + + '&baseCurrencyCode=' + + sourceCurrency.toLowerCase() + + '&baseCurrencyAmount=' + + amount + + '&lockAmount=true' + + '&showAllCurrencies=false' + + '&showWalletAddressForm=false'; final originalUrl = baseUrl + suffix; @@ -121,25 +197,27 @@ class MoonPayBuyProvider extends BuyProvider { final hmac = Hmac(sha256, key); final digest = hmac.convert(messageBytes); final signature = base64.encode(digest.bytes); - final urlWithSignature = originalUrl + - '&signature=${Uri.encodeComponent(signature)}'; + final urlWithSignature = originalUrl + '&signature=${Uri.encodeComponent(signature)}'; return isTestEnvironment ? originalUrl : urlWithSignature; } - @override Future calculateAmount(String amount, String sourceCurrency) async { - final url = _apiUrl + _currenciesSuffix + '/$currencyCode' + - _quoteSuffix + '/?apiKey=' + _apiKey + - '&baseCurrencyAmount=' + amount + - '&baseCurrencyCode=' + sourceCurrency.toLowerCase(); + final url = _apiUrl + + _currenciesSuffix + + '/$currencyCode' + + _quoteSuffix + + '/?apiKey=' + + _apiKey + + '&baseCurrencyAmount=' + + amount + + '&baseCurrencyCode=' + + sourceCurrency.toLowerCase(); final uri = Uri.parse(url); final response = await get(uri); if (response.statusCode != 200) { - throw BuyException( - description: description, - text: 'Quote is not found!'); + throw BuyException(title: providerDescription, content: 'Quote is not found!'); } final responseJSON = json.decode(response.body) as Map; @@ -148,22 +226,16 @@ class MoonPayBuyProvider extends BuyProvider { final minSourceAmount = responseJSON['baseCurrency']['minAmount'] as int; return BuyAmount( - sourceAmount: sourceAmount, - destAmount: destAmount, - minAmount: minSourceAmount); + sourceAmount: sourceAmount, destAmount: destAmount, minAmount: minSourceAmount); } - @override Future findOrderById(String id) async { - final url = _apiUrl + _transactionsSuffix + '/$id' + - '?apiKey=' + _apiKey; + final url = _apiUrl + _transactionsSuffix + '/$id' + '?apiKey=' + _apiKey; final uri = Uri.parse(url); final response = await get(uri); if (response.statusCode != 200) { - throw BuyException( - description: description, - text: 'Transaction $id is not found!'); + throw BuyException(title: providerDescription, content: 'Transaction $id is not found!'); } final responseJSON = json.decode(response.body) as Map; @@ -175,14 +247,13 @@ class MoonPayBuyProvider extends BuyProvider { return Order( id: id, - provider: description, + provider: BuyProviderDescription.moonPay, transferId: id, state: state, createdAt: createdAt, amount: amount.toString(), - receiveAddress: walletAddress, - walletId: walletId - ); + receiveAddress: wallet.walletAddresses.address, + walletId: wallet.id); } static Future onEnabled() async { @@ -201,4 +272,8 @@ class MoonPayBuyProvider extends BuyProvider { return isBuyEnable; } -} \ No newline at end of file + + @override + Future launchProvider(BuildContext context, bool? isBuyAction) => + throw UnimplementedError(); +} diff --git a/lib/buy/onramper/onramper_buy_provider.dart b/lib/buy/onramper/onramper_buy_provider.dart index 872fcebf5..014edb813 100644 --- a/lib/buy/onramper/onramper_buy_provider.dart +++ b/lib/buy/onramper/onramper_buy_provider.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/.secrets.g.dart' as secrets; +import 'package:cake_wallet/buy/buy_provider.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/store/settings_store.dart'; @@ -9,20 +10,31 @@ import 'package:cw_core/wallet_base.dart'; import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; -class OnRamperBuyProvider { - OnRamperBuyProvider({required SettingsStore settingsStore, required WalletBase wallet}) - : this._settingsStore = settingsStore, - this._wallet = wallet; - - final SettingsStore _settingsStore; - final WalletBase _wallet; +class OnRamperBuyProvider extends BuyProvider { + OnRamperBuyProvider(this._settingsStore, + {required WalletBase wallet, bool isTestEnvironment = false}) + : super(wallet: wallet, isTestEnvironment: isTestEnvironment); static const _baseUrl = 'buy.onramper.com'; + final SettingsStore _settingsStore; + + @override + String get title => 'Onramper'; + + @override + String get providerDescription => S.current.onramper_option_description; + + @override + String get lightIcon => 'assets/images/onramper_light.png'; + + @override + String get darkIcon => 'assets/images/onramper_dark.png'; + String get _apiKey => secrets.onramperApiKey; String get _normalizeCryptoCurrency { - switch (_wallet.currency) { + switch (wallet.currency) { case CryptoCurrency.ltc: return "LTC_LITECOIN"; case CryptoCurrency.xmr: @@ -32,7 +44,7 @@ class OnRamperBuyProvider { case CryptoCurrency.nano: return "XNO_NANO"; default: - return _wallet.currency.title; + return wallet.currency.title; } } @@ -40,7 +52,7 @@ class OnRamperBuyProvider { return color.value.toRadixString(16).replaceAll(RegExp(r'^ff'), ""); } - Uri requestUrl(BuildContext context) { + Uri requestOnramperUrl(BuildContext context) { String primaryColor, secondaryColor, primaryTextColor, @@ -50,9 +62,10 @@ class OnRamperBuyProvider { primaryColor = getColorStr(Theme.of(context).primaryColor); secondaryColor = getColorStr(Theme.of(context).colorScheme.background); - primaryTextColor = getColorStr(Theme.of(context).extension()!.titleColor); - secondaryTextColor = - getColorStr(Theme.of(context).extension()!.secondaryTextColor); + primaryTextColor = + getColorStr(Theme.of(context).extension()!.titleColor); + secondaryTextColor = getColorStr( + Theme.of(context).extension()!.secondaryTextColor); containerColor = getColorStr(Theme.of(context).colorScheme.background); cardColor = getColorStr(Theme.of(context).cardColor); @@ -60,12 +73,13 @@ class OnRamperBuyProvider { cardColor = getColorStr(Colors.white); } - final networkName = _wallet.currency.fullName?.toUpperCase().replaceAll(" ", ""); + final networkName = + wallet.currency.fullName?.toUpperCase().replaceAll(" ", ""); return Uri.https(_baseUrl, '', { 'apiKey': _apiKey, 'defaultCrypto': _normalizeCryptoCurrency, - 'networkWallets': '${networkName}:${_wallet.walletAddresses.address}', + 'networkWallets': '${networkName}:${wallet.walletAddresses.address}', 'supportSell': "false", 'supportSwap': "false", 'primaryColor': primaryColor, @@ -77,10 +91,11 @@ class OnRamperBuyProvider { }); } - Future launchProvider(BuildContext context) async { - final uri = requestUrl(context); + Future launchProvider(BuildContext context, bool? isBuyAction) async { + final uri = requestOnramperUrl(context); if (DeviceInfo.instance.isMobile) { - Navigator.of(context).pushNamed(Routes.webViewPage, arguments: [S.of(context).buy, uri]); + Navigator.of(context) + .pushNamed(Routes.webViewPage, arguments: [S.of(context).buy, uri]); } else { await launchUrl(uri); } diff --git a/lib/buy/robinhood/robinhood_buy_provider.dart b/lib/buy/robinhood/robinhood_buy_provider.dart index ade0bf99f..47c3ab1ea 100644 --- a/lib/buy/robinhood/robinhood_buy_provider.dart +++ b/lib/buy/robinhood/robinhood_buy_provider.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:cake_wallet/.secrets.g.dart' as secrets; +import 'package:cake_wallet/buy/buy_provider.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; @@ -10,40 +11,44 @@ import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:url_launcher/url_launcher.dart'; -class RobinhoodBuyProvider { - RobinhoodBuyProvider({required WalletBase wallet}) : this._wallet = wallet; - - final WalletBase _wallet; +class RobinhoodBuyProvider extends BuyProvider { + RobinhoodBuyProvider({required WalletBase wallet, bool isTestEnvironment = false}) + : super(wallet: wallet, isTestEnvironment: isTestEnvironment); static const _baseUrl = 'applink.robinhood.com'; static const _cIdBaseUrl = 'exchange-helper.cakewallet.com'; + @override + String get title => 'Robinhood Connect'; + + @override + String get providerDescription => S.current.robinhood_option_description; + + @override + String get lightIcon => 'assets/images/robinhood_light.png'; + + @override + String get darkIcon => 'assets/images/robinhood_dark.png'; + String get _applicationId => secrets.robinhoodApplicationId; String get _apiSecret => secrets.robinhoodCIdApiSecret; - bool get isAvailable => [ - WalletType.bitcoin, - WalletType.bitcoinCash, - WalletType.litecoin, - WalletType.ethereum - ].contains(_wallet.type); - String getSignature(String message) { - switch (_wallet.type) { + switch (wallet.type) { case WalletType.ethereum: - return _wallet.signMessage(message); + return wallet.signMessage(message); case WalletType.litecoin: case WalletType.bitcoin: case WalletType.bitcoinCash: - return _wallet.signMessage(message, address: _wallet.walletAddresses.address); + return wallet.signMessage(message, address: wallet.walletAddresses.address); default: - throw Exception("WalletType is not available for Robinhood ${_wallet.type}"); + throw Exception("WalletType is not available for Robinhood ${wallet.type}"); } } Future getConnectId() async { - final walletAddress = _wallet.walletAddresses.address; + final walletAddress = wallet.walletAddresses.address; final valid_until = (DateTime.now().millisecondsSinceEpoch / 1000).round() + 10; final message = "$_apiSecret:${valid_until}"; @@ -64,22 +69,22 @@ class RobinhoodBuyProvider { } } - Future requestUrl() async { + Future requestProviderUrl() async { final connectId = await getConnectId(); - final networkName = _wallet.currency.fullName?.toUpperCase().replaceAll(" ", "_"); + final networkName = wallet.currency.fullName?.toUpperCase().replaceAll(" ", "_"); return Uri.https(_baseUrl, '/u/connect', { 'applicationId': _applicationId, 'connectId': connectId, - 'walletAddress': _wallet.walletAddresses.address, - 'userIdentifier': _wallet.walletAddresses.address, + 'walletAddress': wallet.walletAddresses.address, + 'userIdentifier': wallet.walletAddresses.address, 'supportedNetworks': networkName }); } - Future launchProvider(BuildContext context) async { + Future launchProvider(BuildContext context, bool? isBuyAction) async { try { - final uri = await requestUrl(); + final uri = await requestProviderUrl(); await launchUrl(uri, mode: LaunchMode.externalApplication); } catch (_) { await showPopUp( diff --git a/lib/buy/wyre/wyre_buy_provider.dart b/lib/buy/wyre/wyre_buy_provider.dart index 652c92f58..4dd091c33 100644 --- a/lib/buy/wyre/wyre_buy_provider.dart +++ b/lib/buy/wyre/wyre_buy_provider.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'package:cake_wallet/buy/buy_exception.dart'; +import 'package:flutter/src/widgets/framework.dart'; import 'package:http/http.dart'; import 'package:cake_wallet/buy/buy_amount.dart'; import 'package:cake_wallet/buy/buy_provider.dart'; @@ -12,10 +13,8 @@ import 'package:cake_wallet/.secrets.g.dart' as secrets; class WyreBuyProvider extends BuyProvider { WyreBuyProvider({required WalletBase wallet, bool isTestEnvironment = false}) - : baseApiUrl = isTestEnvironment - ? _baseTestApiUrl - : _baseProductApiUrl, - super(wallet: wallet, isTestEnvironment: isTestEnvironment); + : baseApiUrl = isTestEnvironment ? _baseTestApiUrl : _baseProductApiUrl, + super(wallet: wallet, isTestEnvironment: isTestEnvironment); static const _baseTestApiUrl = 'https://api.testwyre.com'; static const _baseProductApiUrl = 'https://api.sendwyre.com'; @@ -35,26 +34,27 @@ class WyreBuyProvider extends BuyProvider { String get title => 'Wyre'; @override - BuyProviderDescription get description => BuyProviderDescription.wyre; + String get providerDescription => ''; @override - String get trackUrl => isTestEnvironment - ? _trackTestUrl - : _trackProductUrl; + String get lightIcon => 'assets/images/robinhood_light.png'; + + @override + String get darkIcon => 'assets/images/robinhood_dark.png'; + + String get trackUrl => isTestEnvironment ? _trackTestUrl : _trackProductUrl; String baseApiUrl; - @override Future requestUrl(String amount, String sourceCurrency) async { final timestamp = DateTime.now().millisecondsSinceEpoch.toString(); - final url = baseApiUrl + _ordersSuffix + _reserveSuffix + - _timeStampSuffix + timestamp; + final url = baseApiUrl + _ordersSuffix + _reserveSuffix + _timeStampSuffix + timestamp; final uri = Uri.parse(url); final body = { 'amount': amount, 'sourceCurrency': sourceCurrency, - 'destCurrency': walletTypeToCryptoCurrency(walletType).title, - 'dest': walletTypeToString(walletType).toLowerCase() + ':' + walletAddress, + 'destCurrency': walletTypeToCryptoCurrency(wallet.type).title, + 'dest': walletTypeToString(wallet.type).toLowerCase() + ':' + wallet.walletAddresses.address, 'referrerAccountId': _accountId, 'lockFields': ['amount', 'sourceCurrency', 'destCurrency', 'dest'] }; @@ -67,9 +67,7 @@ class WyreBuyProvider extends BuyProvider { body: json.encode(body)); if (response.statusCode != 200) { - throw BuyException( - description: description, - text: 'Url $url is not found!'); + throw BuyException(title: providerDescription, content: 'Url $url is not found!'); } final responseJSON = json.decode(response.body) as Map; @@ -77,14 +75,13 @@ class WyreBuyProvider extends BuyProvider { return urlFromResponse; } - @override Future calculateAmount(String amount, String sourceCurrency) async { final quoteUrl = _baseProductApiUrl + _ordersSuffix + _quoteSuffix; final body = { 'amount': amount, 'sourceCurrency': sourceCurrency, - 'destCurrency': walletTypeToCryptoCurrency(walletType).title, - 'dest': walletTypeToString(walletType).toLowerCase() + ':' + walletAddress, + 'destCurrency': walletTypeToCryptoCurrency(wallet.type).title, + 'dest': walletTypeToString(wallet.type).toLowerCase() + ':' + wallet.walletAddresses.address, 'accountId': _accountId, 'country': _countryCode }; @@ -98,9 +95,7 @@ class WyreBuyProvider extends BuyProvider { body: json.encode(body)); if (response.statusCode != 200) { - throw BuyException( - description: description, - text: 'Quote is not found!'); + throw BuyException(title: providerDescription, content: 'Quote is not found!'); } final responseJSON = json.decode(response.body) as Map; @@ -108,58 +103,55 @@ class WyreBuyProvider extends BuyProvider { final destAmount = responseJSON['destAmount'] as double; final achAmount = responseJSON['sourceAmountWithoutFees'] as double; - return BuyAmount(sourceAmount: sourceAmount, destAmount: destAmount, achSourceAmount: achAmount); + return BuyAmount( + sourceAmount: sourceAmount, destAmount: destAmount, achSourceAmount: achAmount); } - @override Future findOrderById(String id) async { final orderUrl = baseApiUrl + _ordersSuffix + '/$id'; final orderUri = Uri.parse(orderUrl); final orderResponse = await get(orderUri); if (orderResponse.statusCode != 200) { - throw BuyException( - description: description, - text: 'Order $id is not found!'); + throw BuyException(title: providerDescription, content: 'Order $id is not found!'); } - final orderResponseJSON = - json.decode(orderResponse.body) as Map; + final orderResponseJSON = json.decode(orderResponse.body) as Map; final transferId = orderResponseJSON['transferId'] as String; final from = orderResponseJSON['sourceCurrency'] as String; final to = orderResponseJSON['destCurrency'] as String; final status = orderResponseJSON['status'] as String; final state = TradeState.deserialize(raw: status.toLowerCase()); final createdAtRaw = orderResponseJSON['createdAt'] as int; - final createdAt = - DateTime.fromMillisecondsSinceEpoch(createdAtRaw).toLocal(); + final createdAt = DateTime.fromMillisecondsSinceEpoch(createdAtRaw).toLocal(); - final transferUrl = - baseApiUrl + _transferSuffix + transferId + _trackSuffix; + final transferUrl = baseApiUrl + _transferSuffix + transferId + _trackSuffix; final transferUri = Uri.parse(transferUrl); final transferResponse = await get(transferUri); if (transferResponse.statusCode != 200) { - throw BuyException( - description: description, - text: 'Transfer $transferId is not found!'); + throw BuyException(title: providerDescription, content: 'Transfer $transferId is not found!'); } - final transferResponseJSON = - json.decode(transferResponse.body) as Map; + final transferResponseJSON = json.decode(transferResponse.body) as Map; final amount = transferResponseJSON['destAmount'] as double; return Order( id: id, - provider: description, + provider: BuyProviderDescription.wyre, transferId: transferId, from: from, to: to, state: state, createdAt: createdAt, amount: amount.toString(), - receiveAddress: walletAddress, - walletId: walletId - ); + receiveAddress: wallet.walletAddresses.address, + walletId: wallet.id); } -} \ No newline at end of file + + @override + Future launchProvider(BuildContext context, bool? isBuyAction) { + // TODO: implement launchProvider + throw UnimplementedError(); + } +} diff --git a/lib/core/address_validator.dart b/lib/core/address_validator.dart index fcb881943..2ae9e3297 100644 --- a/lib/core/address_validator.dart +++ b/lib/core/address_validator.dart @@ -32,6 +32,8 @@ class AddressValidator extends TextValidator { return '[0-9a-zA-Z_]'; case CryptoCurrency.usdc: case CryptoCurrency.usdcpoly: + case CryptoCurrency.usdtPoly: + case CryptoCurrency.usdcEPoly: case CryptoCurrency.ape: case CryptoCurrency.avaxc: case CryptoCurrency.eth: @@ -90,7 +92,7 @@ class AddressValidator extends TextValidator { case CryptoCurrency.eos: return '[0-9a-zA-Z]'; case CryptoCurrency.bch: - return '^(?!bitcoincash:)[0-9a-zA-Z]*\$|^(?!bitcoincash:)q[0-9a-zA-Z]{41}\$|^(?!bitcoincash:)q[0-9a-zA-Z]{42}\$|^bitcoincash:q[0-9a-zA-Z]{41}\$|^bitcoincash:q[0-9a-zA-Z]{42}\$'; + return '^(?!bitcoincash:)[0-9a-zA-Z]*\$|^(?!bitcoincash:)q|p[0-9a-zA-Z]{41}\$|^(?!bitcoincash:)q|p[0-9a-zA-Z]{42}\$|^bitcoincash:q|p[0-9a-zA-Z]{41}\$|^bitcoincash:q|p[0-9a-zA-Z]{42}\$'; case CryptoCurrency.bnb: return '[0-9a-zA-Z]'; case CryptoCurrency.ltc: @@ -141,6 +143,8 @@ class AddressValidator extends TextValidator { return [42]; case CryptoCurrency.eth: case CryptoCurrency.usdcpoly: + case CryptoCurrency.usdtPoly: + case CryptoCurrency.usdcEPoly: case CryptoCurrency.mana: case CryptoCurrency.matic: case CryptoCurrency.maticpoly: @@ -271,6 +275,8 @@ class AddressValidator extends TextValidator { '|([^0-9a-zA-Z]|^)ltc[a-zA-Z0-9]{26,45}([^0-9a-zA-Z]|\$)'; case CryptoCurrency.eth: return '0x[0-9a-zA-Z]{42}'; + case CryptoCurrency.maticpoly: + return '0x[0-9a-zA-Z]{42}'; case CryptoCurrency.nano: return 'nano_[0-9a-zA-Z]{60}'; case CryptoCurrency.banano: diff --git a/lib/core/fiat_conversion_service.dart b/lib/core/fiat_conversion_service.dart index 479aa3b82..8a37175b4 100644 --- a/lib/core/fiat_conversion_service.dart +++ b/lib/core/fiat_conversion_service.dart @@ -16,7 +16,7 @@ Future _fetchPrice(Map args) async { final Map queryParams = { 'interval_count': '1', - 'base': crypto, + 'base': crypto.split(".").first, 'quote': fiat, 'key': secrets.fiatApiKey, }; diff --git a/lib/core/seed_validator.dart b/lib/core/seed_validator.dart index 95ccf89ac..8f65159e1 100644 --- a/lib/core/seed_validator.dart +++ b/lib/core/seed_validator.dart @@ -3,6 +3,7 @@ import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/haven/haven.dart'; import 'package:cake_wallet/core/validator.dart'; import 'package:cake_wallet/entities/mnemonic_item.dart'; +import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/nano/nano.dart'; @@ -34,6 +35,8 @@ class SeedValidator extends Validator { case WalletType.nano: case WalletType.banano: return nano!.getNanoWordList(language); + case WalletType.polygon: + return polygon!.getPolygonWordList(language); default: return []; } diff --git a/lib/core/wallet_connect/evm_chain_service.dart b/lib/core/wallet_connect/evm_chain_service.dart index dc22e3dda..81b934e08 100644 --- a/lib/core/wallet_connect/evm_chain_service.dart +++ b/lib/core/wallet_connect/evm_chain_service.dart @@ -6,6 +6,7 @@ import 'package:cake_wallet/core/wallet_connect/eth_transaction_model.dart'; import 'package:cake_wallet/core/wallet_connect/evm_chain_id.dart'; import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart'; import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/src/screens/wallet_connect/widgets/message_display_widget.dart'; import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/core/wallet_connect/models/chain_key_model.dart'; @@ -14,7 +15,6 @@ import 'package:cake_wallet/src/screens/wallet_connect/widgets/connection_widget import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/web3_request_modal.dart'; import 'package:cake_wallet/src/screens/wallet_connect/utils/string_parsing.dart'; import 'package:convert/convert.dart'; -import 'package:cw_core/wallet_type.dart'; import 'package:eth_sig_util/eth_sig_util.dart'; import 'package:eth_sig_util/util/utils.dart'; import 'package:http/http.dart' as http; @@ -46,13 +46,12 @@ class EvmChainServiceImpl implements ChainService { required this.wcKeyService, required this.bottomSheetService, required this.wallet, - Web3Client? ethClient, - }) : ethClient = ethClient ?? + Web3Client? web3Client, + }) : ethClient = web3Client ?? Web3Client( - appStore.settingsStore.getCurrentNode(WalletType.ethereum).uri.toString(), + appStore.settingsStore.getCurrentNode(appStore.wallet!.type).uri.toString(), http.Client(), ) { - for (final String event in getEvents()) { wallet.registerEventEmitter(chainId: getChainId(), event: event); } @@ -138,7 +137,8 @@ class EvmChainServiceImpl implements ChainService { try { // Load the private key - final List keys = wcKeyService.getKeysForChain(getChainId()); + final List keys = wcKeyService + .getKeysForChain(appStore.wallet!); final Credentials credentials = EthPrivateKey.fromHex(keys[0].privateKey); @@ -176,13 +176,15 @@ class EvmChainServiceImpl implements ChainService { try { // Load the private key - final List keys = wcKeyService.getKeysForChain(getChainId()); + final List keys = wcKeyService + .getKeysForChain(appStore.wallet!); final EthPrivateKey credentials = EthPrivateKey.fromHex(keys[0].privateKey); final String signature = hex.encode( credentials.signPersonalMessageToUint8List( Uint8List.fromList(utf8.encode(message)), + chainId: getChainIdBasedOnWalletType(appStore.wallet!.type), ), ); log(signature); @@ -212,7 +214,8 @@ class EvmChainServiceImpl implements ChainService { } // Load the private key - final List keys = wcKeyService.getKeysForChain(getChainId()); + final List keys = wcKeyService + .getKeysForChain(appStore.wallet!); final Credentials credentials = EthPrivateKey.fromHex(keys[0].privateKey); @@ -232,7 +235,11 @@ class EvmChainServiceImpl implements ChainService { ); try { - final result = await ethClient.sendTransaction(credentials, transaction); + final result = await ethClient.sendTransaction( + credentials, + transaction, + chainId: getChainIdBasedOnWalletType(appStore.wallet!.type), + ); log('Result: $result'); @@ -267,7 +274,8 @@ class EvmChainServiceImpl implements ChainService { return authError; } - final List keys = wcKeyService.getKeysForChain(getChainId()); + final List keys = wcKeyService + .getKeysForChain(appStore.wallet!); return EthSigUtil.signTypedData( privateKey: keys[0].privateKey, @@ -277,10 +285,12 @@ class EvmChainServiceImpl implements ChainService { } String _convertToReadable(Map data) { + final tokenName = getTokenNameBasedOnWalletType(appStore.wallet!.type); String gas = int.parse((data['gas'] as String).substring(2), radix: 16).toString(); String value = data['value'] != null - ? (int.parse((data['value'] as String).substring(2), radix: 16) / 1e18).toString() + ' ETH' - : '0 ETH'; + ? (int.parse((data['value'] as String).substring(2), radix: 16) / 1e18).toString() + + ' $tokenName' + : '0 $tokenName'; String from = data['from'] as String; String to = data['to'] as String; diff --git a/lib/core/wallet_connect/wallet_connect_key_service.dart b/lib/core/wallet_connect/wallet_connect_key_service.dart index 2e61ebb99..33d721073 100644 --- a/lib/core/wallet_connect/wallet_connect_key_service.dart +++ b/lib/core/wallet_connect/wallet_connect_key_service.dart @@ -1,72 +1,68 @@ import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/core/wallet_connect/models/chain_key_model.dart'; -import 'package:cw_core/balance.dart'; -import 'package:cw_core/transaction_history.dart'; -import 'package:cw_core/transaction_info.dart'; +import 'package:cake_wallet/polygon/polygon.dart'; +import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_type.dart'; abstract class WalletConnectKeyService { /// Returns a list of all the keys. - List getKeys(); - - /// Returns a list of all the chain ids. - List getChains(); + List getKeys(WalletBase wallet); /// Returns a list of all the keys for a given chain id. /// If the chain is not found, returns an empty list. /// - [chain]: The chain to get the keys for. - List getKeysForChain(String chain); + List getKeysForChain(WalletBase wallet); - /// Returns a list of all the accounts in namespace:chainId:address format. - List getAllAccounts(); } class KeyServiceImpl implements WalletConnectKeyService { - KeyServiceImpl(this.wallet) - : _keys = [ - ChainKeyModel( - chains: [ - 'eip155:1', - 'eip155:5', - 'eip155:137', - 'eip155:42161', - 'eip155:80001', - ], - privateKey: ethereum!.getPrivateKey(wallet), - publicKey: ethereum!.getPublicKey(wallet), - ), - - ]; - - late final WalletBase, TransactionInfo> wallet; - - late final List _keys; - - @override - List getChains() { - final List chainIds = []; - for (final ChainKeyModel key in _keys) { - chainIds.addAll(key.chains); + static String _getPrivateKeyForWallet(WalletBase wallet) { + switch (wallet.type) { + case WalletType.ethereum: + return ethereum!.getPrivateKey(wallet); + case WalletType.polygon: + return polygon!.getPrivateKey(wallet); + default: + return ''; + } + } + + static String _getPublicKeyForWallet(WalletBase wallet) { + switch (wallet.type) { + case WalletType.ethereum: + return ethereum!.getPublicKey(wallet); + case WalletType.polygon: + return polygon!.getPublicKey(wallet); + default: + return ''; } - return chainIds; } @override - List getKeys() => _keys; - - @override - List getKeysForChain(String chain) { - return _keys.where((e) => e.chains.contains(chain)).toList(); + List getKeys(WalletBase wallet) { + final keys = [ + ChainKeyModel( + chains: [ + 'eip155:1', + 'eip155:5', + 'eip155:137', + 'eip155:42161', + 'eip155:80001', + ], + privateKey: _getPrivateKeyForWallet(wallet), + publicKey: _getPublicKeyForWallet(wallet), + ), + ]; + return keys; } @override - List getAllAccounts() { - final List accounts = []; - for (final ChainKeyModel key in _keys) { - for (final String chain in key.chains) { - accounts.add('$chain:${key.publicKey}'); - } - } - return accounts; + List getKeysForChain(WalletBase wallet) { + final chain = getChainNameSpaceAndIdBasedOnWalletType(wallet.type); + + final keys = getKeys(wallet); + + return keys.where((e) => e.chains.contains(chain)).toList(); } } diff --git a/lib/core/wallet_connect/web3wallet_service.dart b/lib/core/wallet_connect/web3wallet_service.dart index c69692c9d..ee560a0e0 100644 --- a/lib/core/wallet_connect/web3wallet_service.dart +++ b/lib/core/wallet_connect/web3wallet_service.dart @@ -9,6 +9,7 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/core/wallet_connect/models/auth_request_model.dart'; import 'package:cake_wallet/core/wallet_connect/models/chain_key_model.dart'; import 'package:cake_wallet/core/wallet_connect/models/session_request_model.dart'; +import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/src/screens/wallet_connect/widgets/connection_request_widget.dart'; import 'package:cake_wallet/src/screens/wallet_connect/widgets/message_display_widget.dart'; import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/web3_request_modal.dart'; @@ -67,7 +68,7 @@ abstract class Web3WalletServiceBase with Store { ); // Setup our accounts - List chainKeys = walletKeyService.getKeys(); + List chainKeys = walletKeyService.getKeys(appStore.wallet!); for (final chainKey in chainKeys) { for (final chainId in chainKey.chains) { _web3Wallet.registerAccount( @@ -135,6 +136,7 @@ abstract class Web3WalletServiceBase with Store { _web3Wallet.onAuthRequest.unsubscribe(_onAuthRequest); _web3Wallet.core.pairing.onPairingDelete.unsubscribe(_onPairingDelete); _web3Wallet.core.pairing.onPairingExpire.unsubscribe(_onPairingDelete); + isInitialized = false; } Web3Wallet getWeb3Wallet() { @@ -164,8 +166,10 @@ abstract class Web3WalletServiceBase with Store { void _onSessionProposal(SessionProposalEvent? args) async { if (args != null) { + final chaindIdNamespace = getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type); final Widget modalWidget = Web3RequestModal( child: ConnectionRequestWidget( + chaindIdNamespace: chaindIdNamespace, wallet: _web3Wallet, sessionProposal: SessionRequestModel(request: args.params), ), @@ -232,12 +236,13 @@ abstract class Web3WalletServiceBase with Store { @action Future _onAuthRequest(AuthRequest? args) async { if (args != null) { - List chainKeys = walletKeyService.getKeysForChain('eip155:1'); + final chaindIdNamespace = getChainNameSpaceAndIdBasedOnWalletType(appStore.wallet!.type); + List chainKeys = walletKeyService.getKeysForChain(appStore.wallet!); // Create the message to be signed - final String iss = 'did:pkh:eip155:1:${chainKeys.first.publicKey}'; - + final String iss = 'did:pkh:$chaindIdNamespace:${chainKeys.first.publicKey}'; final Widget modalWidget = Web3RequestModal( child: ConnectionRequestWidget( + chaindIdNamespace: chaindIdNamespace, wallet: _web3Wallet, authRequest: AuthRequestModel(iss: iss, request: args), ), diff --git a/lib/di.dart b/lib/di.dart index 0a7097b7b..61a04bf1c 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -1,6 +1,7 @@ import 'package:cake_wallet/anonpay/anonpay_api.dart'; import 'package:cake_wallet/anonpay/anonpay_info_base.dart'; import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart'; +import 'package:cake_wallet/buy/moonpay/moonpay_provider.dart'; import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart'; import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/buy/payfura/payfura_buy_provider.dart'; @@ -18,6 +19,7 @@ import 'package:cake_wallet/nano/nano.dart'; import 'package:cake_wallet/ionia/ionia_anypay.dart'; import 'package:cake_wallet/ionia/ionia_gift_card.dart'; import 'package:cake_wallet/ionia/ionia_tip.dart'; +import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/anonpay_details/anonpay_details_page.dart'; import 'package:cake_wallet/src/screens/buy/buy_options_page.dart'; @@ -48,6 +50,7 @@ import 'package:cake_wallet/src/screens/settings/connection_sync_page.dart'; import 'package:cake_wallet/src/screens/settings/trocador_providers_page.dart'; import 'package:cake_wallet/src/screens/settings/tor_page.dart'; import 'package:cake_wallet/src/screens/setup_2fa/modify_2fa_page.dart'; +import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_info_page.dart'; import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_qr_page.dart'; import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa.dart'; import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_enter_code_page.dart'; @@ -88,6 +91,7 @@ import 'package:cake_wallet/view_model/ionia/ionia_purchase_merch_view_model.dar import 'package:cake_wallet/view_model/nano_account_list/nano_account_edit_or_create_view_model.dart'; import 'package:cake_wallet/view_model/nano_account_list/nano_account_list_view_model.dart'; import 'package:cake_wallet/view_model/node_list/pow_node_list_view_model.dart'; +import 'package:cake_wallet/view_model/seed_type_view_model.dart'; import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart'; import 'package:cake_wallet/view_model/restore/restore_from_qr_vm.dart'; import 'package:cake_wallet/view_model/settings/display_settings_view_model.dart'; @@ -225,6 +229,7 @@ import 'package:cake_wallet/core/wallet_loading_service.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cake_wallet/entities/qr_view_data.dart'; +import 'buy/dfx/dfx_buy_provider.dart'; import 'core/totp_request_details.dart'; import 'src/screens/settings/desktop_settings/desktop_settings_page.dart'; @@ -338,14 +343,18 @@ Future setup({ settingsStore: getIt.get(), walletInfoSource: _walletInfoSource)); + getIt.registerFactoryParam( + (type, _) => AdvancedPrivacySettingsViewModel(type, getIt.get())); + getIt.registerFactory(() => WalletLoadingService( getIt.get(), getIt.get(), (WalletType type) => getIt.get(param1: type))); - - getIt.registerFactoryParam((type, _) => WalletNewVM( - getIt.get(), getIt.get(param1: type), _walletInfoSource, - type: type)); + + getIt.registerFactoryParam((type, _) => + WalletNewVM(getIt.get(), + getIt.get(param1: type), _walletInfoSource, + getIt.get(param1: type),type: type)); getIt.registerFactoryParam((WalletType type, _) { return WalletRestorationFromQRVM(getIt.get(), @@ -371,7 +380,8 @@ Future setup({ settingsStore: settingsStore, yatStore: getIt.get(), ordersStore: getIt.get(), - anonpayTransactionsStore: getIt.get())); + anonpayTransactionsStore: getIt.get(), + keyService: getIt.get())); getIt.registerFactory( () => AuthService( @@ -470,7 +480,7 @@ Future setup({ final appStore = getIt.get(); - getIt.registerLazySingleton(() => KeyServiceImpl(appStore.wallet!)); + getIt.registerLazySingleton(() => KeyServiceImpl()); getIt.registerLazySingleton(() { final Web3WalletService web3WalletService = Web3WalletService( @@ -515,6 +525,8 @@ Future setup({ getIt.registerFactory( () => TransactionsPage(dashboardViewModel: getIt.get())); + getIt.registerFactory(() => Setup2FAInfoPage()); + getIt.registerFactory( () => Setup2FAPage(setup2FAViewModel: getIt.get())); @@ -716,6 +728,8 @@ Future setup({ getIt.registerFactory(() => WalletSeedViewModel(getIt.get().wallet!)); + getIt.registerFactory(() => SeedTypeViewModel(getIt.get())); + getIt.registerFactoryParam((bool isWalletCreated, _) => WalletSeedPage(getIt.get(), isNewWalletCreated: isWalletCreated)); @@ -746,13 +760,7 @@ Future setup({ return PowNodeListViewModel(_powNodeSource, appStore); }); - getIt.registerFactory(() { - final wallet = getIt.get().wallet; - return ConnectionSyncPage( - getIt.get(), - wallet?.type == WalletType.ethereum ? getIt.get() : null, - ); - }); + getIt.registerFactory(() => ConnectionSyncPage(getIt.get())); getIt.registerFactory( () => SecurityBackupPage(getIt.get(), getIt.get())); @@ -793,8 +801,15 @@ Future setup({ getIt.registerFactory( () => RobinhoodBuyProvider(wallet: getIt.get().wallet!)); + getIt + .registerFactory(() => DFXBuyProvider(wallet: getIt.get().wallet!)); + + getIt.registerFactory(() => MoonPaySellProvider( + settingsStore: getIt.get().settingsStore, + wallet: getIt.get().wallet!)); + getIt.registerFactory(() => OnRamperBuyProvider( - settingsStore: getIt.get().settingsStore, + getIt.get().settingsStore, wallet: getIt.get().wallet!, )); @@ -844,9 +859,11 @@ Future setup({ return ethereum!.createEthereumWalletService(_walletInfoSource); case WalletType.bitcoinCash: return bitcoinCash! - .createBitcoinCashWalletService(_walletInfoSource, _unspentCoinsInfoSource!); + .createBitcoinCashWalletService(_walletInfoSource, _unspentCoinsInfoSource); case WalletType.nano: return nano!.createNanoWalletService(_walletInfoSource); + case WalletType.polygon: + return polygon!.createPolygonWalletService(_walletInfoSource); default: throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService'); } @@ -871,8 +888,8 @@ Future setup({ getIt.get(), getIt.get(param1: type), _walletInfoSource, type: type)); - getIt.registerFactoryParam( - (type, _) => WalletRestorePage(getIt.get(param1: type))); + getIt.registerFactoryParam((type, _) => WalletRestorePage( + getIt.get(param1: type), getIt.get())); getIt.registerFactoryParam, void>( (derivations, _) => WalletRestoreChooseDerivationViewModel(derivationInfos: derivations)); @@ -901,9 +918,9 @@ Future setup({ getIt.registerFactoryParam( (param1, isCreate) => NewWalletTypePage(onTypeSelected: param1, isCreate: isCreate ?? true)); - getIt.registerFactoryParam( - (WalletType type, AdvancedPrivacySettingsViewModel advancedPrivacySettingsViewModel) => - PreSeedPage(type, advancedPrivacySettingsViewModel)); + getIt.registerFactoryParam( + (seedPhraseLength, _) + => PreSeedPage(seedPhraseLength)); getIt.registerFactoryParam((trade, _) => TradeDetailsViewModel( @@ -936,7 +953,8 @@ Future setup({ getIt.registerFactory(() => BuyAmountViewModel()); - getIt.registerFactory(() => BuyOptionsPage()); + getIt.registerFactoryParam( + (isBuyOption, _) => BuySellOptionsPage(getIt.get(), isBuyOption)); getIt.registerFactory(() { final wallet = getIt.get().wallet; @@ -1149,9 +1167,6 @@ Future setup({ IoniaPaymentStatusPage( getIt.get(param1: paymentInfo, param2: committedInfo))); - getIt.registerFactoryParam( - (type, _) => AdvancedPrivacySettingsViewModel(type, getIt.get())); - getIt.registerFactoryParam((balanceViewModel, _) => HomeSettingsPage(getIt.get(param1: balanceViewModel))); diff --git a/lib/entities/buy_provider_types.dart b/lib/entities/buy_provider_types.dart deleted file mode 100644 index 90c070e86..000000000 --- a/lib/entities/buy_provider_types.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:cake_wallet/generated/i18n.dart'; - -enum BuyProviderType { - AskEachTime, - Robinhood, - Onramper; - - @override - String toString() { - switch (this) { - case BuyProviderType.AskEachTime: - return S.current.ask_each_time; - case BuyProviderType.Robinhood: - return "Robinhood"; - case BuyProviderType.Onramper: - return "Onramper"; - } - } -} diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index a68f2ce34..765316f4e 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -26,6 +26,7 @@ const cakeWalletBitcoinElectrumUri = 'electrum.cakewallet.com:50002'; const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002'; const havenDefaultNodeUri = 'nodes.havenprotocol.org:443'; const ethereumDefaultNodeUri = 'ethereum.publicnode.com'; +const polygonDefaultNodeUri = 'polygon-bor.publicnode.com'; const cakeWalletBitcoinCashDefaultNodeUri = 'bitcoincash.stackwallet.com:50002'; const nanoDefaultNodeUri = 'rpc.nano.to'; const nanoDefaultPowNodeUri = 'rpc.nano.to'; @@ -64,6 +65,8 @@ Future defaultSettingsMigration( final migrationVersions = List.generate(migrationVersionsLength, (i) => currentVersion + (i + 1)); + /// When you add a new case, increase the initialMigrationVersion parameter in the main.dart file. + /// This ensures that this switch case runs the newly added case. await Future.forEach(migrationVersions, (int version) async { try { switch (version) { @@ -175,9 +178,16 @@ Future defaultSettingsMigration( sharedPreferences: sharedPreferences, nodes: nodes); break; case 24: + await addPolygonNodeList(nodes: nodes); + await changePolygonCurrentNodeToDefault( + sharedPreferences: sharedPreferences, nodes: nodes); + break; + case 25: + await rewriteSecureStoragePin(secureStorage: secureStorage); + break; + case 26: await updateBtcNanoWalletInfos(walletInfoSource); break; - default: break; } @@ -331,6 +341,11 @@ Node? getEthereumDefaultNode({required Box nodes}) { nodes.values.firstWhereOrNull((node) => node.type == WalletType.ethereum); } +Node? getPolygonDefaultNode({required Box nodes}) { + return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == polygonDefaultNodeUri) ?? + nodes.values.firstWhereOrNull((node) => node.type == WalletType.polygon); +} + Node? getNanoDefaultNode({required Box nodes}) { return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == nanoDefaultNodeUri) ?? nodes.values.firstWhereOrNull((node) => node.type == WalletType.nano); @@ -366,6 +381,37 @@ Node getMoneroDefaultNode({required Box nodes}) { } } +Future rewriteSecureStoragePin({required FlutterSecureStorage secureStorage}) async { + // the bug only affects ios/mac: + if (!Platform.isIOS && !Platform.isMacOS) { + return; + } + + // first, get the encoded pin: + final keyForPinCode = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword); + String? encodedPin; + try { + encodedPin = await secureStorage.read(key: keyForPinCode); + } catch (e) { + // either we don't have a pin, or we can't read it (maybe even because of the bug!) + // the only option here is to abort the migration or we risk losing the pin and locking the user out + return; + } + + if (encodedPin == null) { + return; + } + + // ensure we overwrite by deleting the old key first: + await secureStorage.delete(key: keyForPinCode); + await secureStorage.write( + key: keyForPinCode, + value: encodedPin, + iOptions: IOSOptions(accessibility: KeychainAccessibility.first_unlock), + mOptions: MacOsOptions(accessibility: KeychainAccessibility.first_unlock), + ); +} + Future changeBitcoinCurrentElectrumServerToDefault( {required SharedPreferences sharedPreferences, required Box nodes}) async { final server = getBitcoinDefaultElectrumServer(nodes: nodes); @@ -557,6 +603,7 @@ Future checkCurrentNodes( sharedPreferences.getInt(PreferencesKey.currentLitecoinElectrumSererIdKey); final currentHavenNodeId = sharedPreferences.getInt(PreferencesKey.currentHavenNodeIdKey); final currentEthereumNodeId = sharedPreferences.getInt(PreferencesKey.currentEthereumNodeIdKey); + final currentPolygonNodeId = sharedPreferences.getInt(PreferencesKey.currentPolygonNodeIdKey); final currentNanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey); final currentNanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey); final currentBitcoinCashNodeId = @@ -571,6 +618,8 @@ Future checkCurrentNodes( nodeSource.values.firstWhereOrNull((node) => node.key == currentHavenNodeId); final currentEthereumNodeServer = nodeSource.values.firstWhereOrNull((node) => node.key == currentEthereumNodeId); + final currentPolygonNodeServer = + nodeSource.values.firstWhereOrNull((node) => node.key == currentPolygonNodeId); final currentNanoNodeServer = nodeSource.values.firstWhereOrNull((node) => node.key == currentNanoNodeId); final currentNanoPowNodeServer = @@ -630,6 +679,12 @@ Future checkCurrentNodes( await nodeSource.add(node); await sharedPreferences.setInt(PreferencesKey.currentBitcoinCashNodeIdKey, node.key as int); } + + if (currentPolygonNodeServer == null) { + final node = Node(uri: polygonDefaultNodeUri, type: WalletType.polygon); + await nodeSource.add(node); + await sharedPreferences.setInt(PreferencesKey.currentPolygonNodeIdKey, node.key as int); + } } Future resetBitcoinElectrumServer( @@ -724,3 +779,20 @@ Future changeNanoCurrentPowNodeToDefault( final nodeId = node?.key as int? ?? 0; await sharedPreferences.setInt(PreferencesKey.currentNanoPowNodeIdKey, nodeId); } + +Future addPolygonNodeList({required Box nodes}) async { + final nodeList = await loadDefaultPolygonNodes(); + for (var node in nodeList) { + if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) { + await nodes.add(node); + } + } +} + +Future changePolygonCurrentNodeToDefault( + {required SharedPreferences sharedPreferences, required Box nodes}) async { + final node = getPolygonDefaultNode(nodes: nodes); + final nodeId = node?.key as int? ?? 0; + + await sharedPreferences.setInt(PreferencesKey.currentPolygonNodeIdKey, nodeId); +} diff --git a/lib/entities/ens_record.dart b/lib/entities/ens_record.dart index 8cf62d79b..b2ce51806 100644 --- a/lib/entities/ens_record.dart +++ b/lib/entities/ens_record.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/ethereum/ethereum.dart'; +import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:ens_dart/ens_dart.dart'; @@ -12,6 +13,10 @@ class EnsRecord { if (wallet != null && wallet.type == WalletType.ethereum) { _client = ethereum!.getWeb3Client(wallet); } + + if (wallet != null && wallet.type == WalletType.polygon) { + _client = polygon!.getWeb3Client(wallet); + } if (_client == null) { _client = Web3Client("https://ethereum.publicnode.com", Client()); @@ -31,6 +36,7 @@ class EnsRecord { case WalletType.haven: return await ens.withName(name).getCoinAddress(CoinType.XHV); case WalletType.ethereum: + case WalletType.polygon: default: return (await ens.withName(name).getAddress()).hex; } diff --git a/lib/entities/list_order_mode.dart b/lib/entities/list_order_mode.dart new file mode 100644 index 000000000..b9eae6820 --- /dev/null +++ b/lib/entities/list_order_mode.dart @@ -0,0 +1,34 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cw_core/enumerable_item.dart'; + +class ListOrderMode extends EnumerableItem with Serializable { + const ListOrderMode({required String title, required int raw}) : super(title: title, raw: raw); + + static const all = [ListOrderMode.ascending, ListOrderMode.descending]; + + static const ascending = ListOrderMode(raw: 0, title: 'Ascending'); + static const descending = ListOrderMode(raw: 1, title: 'Descending'); + + static ListOrderMode deserialize({required int raw}) { + switch (raw) { + case 0: + return ascending; + case 1: + return descending; + default: + throw Exception('Unexpected token: $raw for ListOrderMode deserialize'); + } + } + + @override + String toString() { + switch (this) { + case ListOrderMode.ascending: + return S.current.ascending; + case ListOrderMode.descending: + return S.current.descending; + default: + return ''; + } + } +} diff --git a/lib/entities/load_current_wallet.dart b/lib/entities/load_current_wallet.dart index d758b6697..595bc2233 100644 --- a/lib/entities/load_current_wallet.dart +++ b/lib/entities/load_current_wallet.dart @@ -22,7 +22,7 @@ Future loadCurrentWallet() async { final type = deserializeFromInt(typeRaw); final walletLoadingService = getIt.get(); final wallet = await walletLoadingService.load(type, name); - appStore.changeCurrentWallet(wallet); + await appStore.changeCurrentWallet(wallet); getIt.get().registerSyncTask(); } diff --git a/lib/entities/main_actions.dart b/lib/entities/main_actions.dart index 46865cbcc..c1dd71cc9 100644 --- a/lib/entities/main_actions.dart +++ b/lib/entities/main_actions.dart @@ -1,17 +1,9 @@ -import 'package:cake_wallet/buy/moonpay/moonpay_buy_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:cake_wallet/entities/buy_provider_types.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; -import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; -import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; -import 'package:url_launcher/url_launcher.dart'; class MainActions { final String Function(BuildContext context) name; @@ -19,7 +11,8 @@ class MainActions { final bool Function(DashboardViewModel viewModel)? isEnabled; final bool Function(DashboardViewModel viewModel)? canShow; - final Future Function(BuildContext context, DashboardViewModel viewModel) onTap; + final Future Function( + BuildContext context, DashboardViewModel viewModel) onTap; MainActions._({ required this.name, @@ -43,43 +36,17 @@ class MainActions { isEnabled: (viewModel) => viewModel.isEnabledBuyAction, canShow: (viewModel) => viewModel.hasBuyAction, onTap: (BuildContext context, DashboardViewModel viewModel) async { + if (!viewModel.isEnabledBuyAction) { + return; + } + final defaultBuyProvider = viewModel.defaultBuyProvider; - final walletType = viewModel.type; - - if (!viewModel.isEnabledBuyAction) return; - - switch (walletType) { - case WalletType.bitcoin: - case WalletType.litecoin: - case WalletType.ethereum: - case WalletType.bitcoinCash: - switch (defaultBuyProvider) { - case BuyProviderType.AskEachTime: - Navigator.pushNamed(context, Routes.buy); - break; - case BuyProviderType.Onramper: - await getIt.get().launchProvider(context); - break; - case BuyProviderType.Robinhood: - await getIt.get().launchProvider(context); - break; - } - break; - case WalletType.nano: - case WalletType.banano: - case WalletType.monero: - await getIt.get().launchProvider(context); - break; - default: - await showPopUp( - context: context, - builder: (BuildContext context) { - return AlertWithOneAction( - alertTitle: S.of(context).buy, - alertContent: S.of(context).unsupported_asset, - buttonText: S.of(context).ok, - buttonAction: () => Navigator.of(context).pop()); - }); + try { + defaultBuyProvider != null + ? await defaultBuyProvider.launchProvider(context, true) + : await Navigator.of(context).pushNamed(Routes.buySellPage, arguments: true); + } catch (e) { + await _showErrorDialog(context, defaultBuyProvider.toString(), e.toString()); } }, ); @@ -118,41 +85,33 @@ class MainActions { isEnabled: (viewModel) => viewModel.isEnabledSellAction, canShow: (viewModel) => viewModel.hasSellAction, onTap: (BuildContext context, DashboardViewModel viewModel) async { - final walletType = viewModel.type; + if (!viewModel.isEnabledSellAction) { + return; + } - switch (walletType) { - case WalletType.bitcoin: - case WalletType.litecoin: - case WalletType.ethereum: - case WalletType.bitcoinCash: - if (viewModel.isEnabledSellAction) { - final moonPaySellProvider = MoonPaySellProvider(); - final uri = await moonPaySellProvider.requestUrl( - currency: viewModel.wallet.currency, - refundWalletAddress: viewModel.wallet.walletAddresses.address, - settingsStore: viewModel.settingsStore, - ); - if (DeviceInfo.instance.isMobile) { - Navigator.of(context).pushNamed(Routes.webViewPage, - arguments: [S.of(context).sell, uri]); - } else { - await launchUrl(uri); - } - } - - break; - default: - await showPopUp( - context: context, - builder: (BuildContext context) { - return AlertWithOneAction( - alertTitle: S.of(context).sell, - alertContent: S.of(context).unsupported_asset, - buttonText: S.of(context).ok, - buttonAction: () => Navigator.of(context).pop()); - }, - ); + final defaultSellProvider = viewModel.defaultSellProvider; + try { + defaultSellProvider != null + ? await defaultSellProvider.launchProvider(context, false) + : await Navigator.of(context).pushNamed(Routes.buySellPage, arguments: false); + } catch (e) { + await _showErrorDialog(context, defaultSellProvider.toString(), e.toString()); } }, ); -} + + static Future _showErrorDialog( + BuildContext context, String title, String errorMessage) async { + await showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: title, + alertContent: errorMessage, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop(), + ); + }, + ); + } +} \ No newline at end of file diff --git a/lib/entities/node_list.dart b/lib/entities/node_list.dart index 53facf18c..aaac8a5c2 100644 --- a/lib/entities/node_list.dart +++ b/lib/entities/node_list.dart @@ -85,8 +85,7 @@ Future> loadDefaultEthereumNodes() async { } Future> loadBitcoinCashElectrumServerList() async { - final serverListRaw = - await rootBundle.loadString('assets/bitcoin_cash_electrum_server_list.yml'); + final serverListRaw = await rootBundle.loadString('assets/bitcoin_cash_electrum_server_list.yml'); final loadedServerList = loadYaml(serverListRaw) as YamlList; final serverList = []; @@ -133,6 +132,23 @@ Future> loadDefaultNanoPowNodes() async { return nodes; } +Future> loadDefaultPolygonNodes() async { + final nodesRaw = await rootBundle.loadString('assets/polygon_node_list.yml'); + final loadedNodes = loadYaml(nodesRaw) as YamlList; + final nodes = []; + + for (final raw in loadedNodes) { + if (raw is Map) { + final node = Node.fromMap(Map.from(raw)); + + node.type = WalletType.polygon; + nodes.add(node); + } + } + + return nodes; +} + Future resetToDefault(Box nodeSource) async { final moneroNodes = await loadDefaultNodes(); final bitcoinElectrumServerList = await loadBitcoinElectrumServerList(); @@ -141,6 +157,7 @@ Future resetToDefault(Box nodeSource) async { final havenNodes = await loadDefaultHavenNodes(); final ethereumNodes = await loadDefaultEthereumNodes(); final nanoNodes = await loadDefaultNanoNodes(); + final polygonNodes = await loadDefaultPolygonNodes(); final nodes = moneroNodes + bitcoinElectrumServerList + @@ -148,7 +165,8 @@ Future resetToDefault(Box nodeSource) async { havenNodes + ethereumNodes + bitcoinCashElectrumServerList + - nanoNodes; + nanoNodes + + polygonNodes; await nodeSource.clear(); await nodeSource.addAll(nodes); @@ -159,4 +177,4 @@ Future resetPowToDefault(Box powNodeSource) async { final nodes = nanoPowNodes; await powNodeSource.clear(); await powNodeSource.addAll(nodes); -} \ No newline at end of file +} diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index bdd6c24a7..2d5e64817 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -6,6 +6,7 @@ class PreferencesKey { static const currentLitecoinElectrumSererIdKey = 'current_node_id_ltc'; static const currentHavenNodeIdKey = 'current_node_id_xhv'; static const currentEthereumNodeIdKey = 'current_node_id_eth'; + static const currentPolygonNodeIdKey = 'current_node_id_matic'; static const currentNanoNodeIdKey = 'current_node_id_nano'; static const currentNanoPowNodeIdKey = 'current_node_id_nano_pow'; static const currentBananoNodeIdKey = 'current_node_id_banano'; @@ -19,6 +20,8 @@ class PreferencesKey { static const disableBuyKey = 'disable_buy'; static const disableSellKey = 'disable_sell'; static const defaultBuyProvider = 'default_buy_provider'; + static const walletListOrder = 'wallet_list_order'; + static const walletListAscending = 'wallet_list_ascending'; static const currentFiatApiModeKey = 'current_fiat_api_mode'; static const allowBiometricalAuthenticationKey = 'allow_biometrical_authentication'; static const useTOTP2FA = 'use_totp_2fa'; @@ -37,6 +40,7 @@ class PreferencesKey { static const havenTransactionPriority = 'current_fee_priority_haven'; static const litecoinTransactionPriority = 'current_fee_priority_litecoin'; static const ethereumTransactionPriority = 'current_fee_priority_ethereum'; + static const polygonTransactionPriority = 'current_fee_priority_polygon'; static const bitcoinCashTransactionPriority = 'current_fee_priority_bitcoin_cash'; static const shouldShowReceiveWarning = 'should_show_receive_warning'; static const shouldShowYatPopup = 'should_show_yat_popup'; @@ -50,6 +54,7 @@ class PreferencesKey { static const sortBalanceBy = 'sort_balance_by'; static const pinNativeTokenAtTop = 'pin_native_token_at_top'; static const useEtherscan = 'use_etherscan'; + static const usePolygonScan = 'use_polygonscan'; static const defaultNanoRep = 'default_nano_representative'; static const defaultBananoRep = 'default_banano_representative'; static const lookupsTwitter = 'looks_up_twitter'; diff --git a/lib/entities/priority_for_wallet_type.dart b/lib/entities/priority_for_wallet_type.dart index bf6f8157d..70b072c55 100644 --- a/lib/entities/priority_for_wallet_type.dart +++ b/lib/entities/priority_for_wallet_type.dart @@ -3,6 +3,7 @@ import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/haven/haven.dart'; import 'package:cake_wallet/monero/monero.dart'; +import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/wallet_type.dart'; @@ -24,6 +25,8 @@ List priorityForWalletType(WalletType type) { case WalletType.nano: case WalletType.banano: return []; + case WalletType.polygon: + return polygon!.getTransactionPriorities(); default: return []; } diff --git a/lib/entities/provider_types.dart b/lib/entities/provider_types.dart new file mode 100644 index 000000000..4cb9a934f --- /dev/null +++ b/lib/entities/provider_types.dart @@ -0,0 +1,108 @@ +import 'package:cake_wallet/buy/buy_provider.dart'; +import 'package:cake_wallet/buy/dfx/dfx_buy_provider.dart'; +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 { + askEachTime, + robinhood, + dfx, + onramper, + moonpaySell, +} + +extension ProviderTypeName on ProviderType { + String get title { + switch (this) { + case ProviderType.askEachTime: + return 'Ask each time'; + case ProviderType.robinhood: + return 'Robinhood Connect'; + case ProviderType.dfx: + return 'DFX Connect'; + case ProviderType.onramper: + return 'Onramper'; + case ProviderType.moonpaySell: + return 'MoonPay'; + } + } + + String get id { + switch (this) { + case ProviderType.askEachTime: + return 'ask_each_time_provider'; + case ProviderType.robinhood: + return 'robinhood_connect_provider'; + case ProviderType.dfx: + return 'dfx_connect_provider'; + case ProviderType.onramper: + return 'onramper_provider'; + case ProviderType.moonpaySell: + return 'moonpay_provider'; + } + } +} + +class ProvidersHelper { + static List getAvailableBuyProviderTypes(WalletType walletType) { + switch (walletType) { + case WalletType.nano: + case WalletType.banano: + return [ProviderType.askEachTime, ProviderType.onramper]; + case WalletType.monero: + return [ProviderType.askEachTime, ProviderType.onramper, ProviderType.dfx]; + case WalletType.bitcoin: + case WalletType.ethereum: + return [ + ProviderType.askEachTime, + ProviderType.onramper, + ProviderType.dfx, + ProviderType.robinhood, + ]; + case WalletType.litecoin: + case WalletType.bitcoinCash: + return [ProviderType.askEachTime, ProviderType.onramper, ProviderType.robinhood]; + case WalletType.none: + case WalletType.haven: + case WalletType.polygon: + return []; + } + } + + static List getAvailableSellProviderTypes(WalletType walletType) { + switch (walletType) { + case WalletType.monero: + return [ProviderType.askEachTime, ProviderType.dfx]; + case WalletType.bitcoin: + case WalletType.ethereum: + return [ProviderType.askEachTime, ProviderType.moonpaySell, ProviderType.dfx]; + case WalletType.litecoin: + case WalletType.bitcoinCash: + return [ProviderType.askEachTime, ProviderType.moonpaySell]; + case WalletType.nano: + case WalletType.banano: + case WalletType.none: + case WalletType.haven: + case WalletType.polygon: + return []; + } + } + + static BuyProvider? getProviderByType(ProviderType type) { + switch (type) { + case ProviderType.robinhood: + return getIt.get(); + case ProviderType.dfx: + return getIt.get(); + case ProviderType.onramper: + return getIt.get(); + case ProviderType.askEachTime: + return null; + case ProviderType.moonpaySell: + return getIt.get(); + } + } +} diff --git a/lib/entities/seed_type.dart b/lib/entities/seed_type.dart index 0a1a191c6..ab9965528 100644 --- a/lib/entities/seed_type.dart +++ b/lib/entities/seed_type.dart @@ -6,7 +6,7 @@ class SeedType extends EnumerableItem with Serializable { static const all = [SeedType.legacy, SeedType.polyseed]; - static const defaultSeedType = legacy; + static const defaultSeedType = polyseed; static const legacy = SeedType(raw: 0, title: 'Legacy (25 words)'); static const polyseed = SeedType(raw: 1, title: 'Polyseed (16 words)'); diff --git a/lib/entities/wallet_list_order_types.dart b/lib/entities/wallet_list_order_types.dart new file mode 100644 index 000000000..f848170f4 --- /dev/null +++ b/lib/entities/wallet_list_order_types.dart @@ -0,0 +1,22 @@ +import 'package:cake_wallet/generated/i18n.dart'; + +enum WalletListOrderType { + CreationDate, + Alphabetical, + GroupByType, + Custom; + + @override + String toString() { + switch (this) { + case WalletListOrderType.CreationDate: + return S.current.creation_date; + case WalletListOrderType.Alphabetical: + return S.current.alphabetical; + case WalletListOrderType.GroupByType: + return S.current.group_by_type; + case WalletListOrderType.Custom: + return S.current.custom_drag; + } + } +} diff --git a/lib/exchange/provider/sideshift_exchange_provider.dart b/lib/exchange/provider/sideshift_exchange_provider.dart index 7a466e213..261aeedf3 100644 --- a/lib/exchange/provider/sideshift_exchange_provider.dart +++ b/lib/exchange/provider/sideshift_exchange_provider.dart @@ -159,8 +159,8 @@ class SideShiftExchangeProvider extends ExchangeProvider { url = apiBaseUrl + orderPath + '/fixed'; } else { url = apiBaseUrl + orderPath + '/variable'; - body["depositCoin"] = request.fromCurrency.title.toLowerCase(); - body["settleCoin"] = request.toCurrency.title.toLowerCase(); + body["depositCoin"] = _normalizeCurrency(request.fromCurrency); + body["settleCoin"] = _normalizeCurrency(request.toCurrency); body["settleNetwork"] = _networkFor(request.toCurrency); body["depositNetwork"] = _networkFor(request.fromCurrency); } @@ -248,8 +248,8 @@ class SideShiftExchangeProvider extends ExchangeProvider { final url = apiBaseUrl + quotePath; final headers = {'Content-Type': 'application/json'}; final body = { - 'depositCoin': request.fromCurrency.title.toLowerCase(), - 'settleCoin': request.toCurrency.title.toLowerCase(), + 'depositCoin': _normalizeCurrency(request.fromCurrency), + 'settleCoin': _normalizeCurrency(request.toCurrency), 'affiliateId': affiliateId, 'settleAmount': request.toAmount, 'settleNetwork': _networkFor(request.toCurrency), @@ -274,6 +274,15 @@ class SideShiftExchangeProvider extends ExchangeProvider { return responseJSON['id'] as String; } + String _normalizeCurrency(CryptoCurrency currency) { + switch (currency) { + case CryptoCurrency.usdcEPoly: + return 'usdc'; + default: + return currency.title.toLowerCase(); + } + } + String _networkFor(CryptoCurrency currency) => currency.tag != null ? _normalizeTag(currency.tag!) : 'mainnet'; diff --git a/lib/exchange/provider/simpleswap_exchange_provider.dart b/lib/exchange/provider/simpleswap_exchange_provider.dart index 091c3a913..5c162a995 100644 --- a/lib/exchange/provider/simpleswap_exchange_provider.dart +++ b/lib/exchange/provider/simpleswap_exchange_provider.dart @@ -222,6 +222,10 @@ class SimpleSwapExchangeProvider extends ExchangeProvider { return 'usdttrc20'; case CryptoCurrency.usdcpoly: return 'usdcpoly'; + case CryptoCurrency.usdtPoly: + return 'usdtpoly'; + case CryptoCurrency.usdcEPoly: + return 'usdcepoly'; case CryptoCurrency.usdcsol: return 'usdcspl'; case CryptoCurrency.matic: diff --git a/lib/exchange/provider/trocador_exchange_provider.dart b/lib/exchange/provider/trocador_exchange_provider.dart index 52e38ecc8..faa4cc060 100644 --- a/lib/exchange/provider/trocador_exchange_provider.dart +++ b/lib/exchange/provider/trocador_exchange_provider.dart @@ -271,6 +271,8 @@ class TrocadorExchangeProvider extends ExchangeProvider { case CryptoCurrency.maticpoly: return 'Mainnet'; case CryptoCurrency.usdcpoly: + case CryptoCurrency.usdtPoly: + case CryptoCurrency.usdcEPoly: return 'MATIC'; case CryptoCurrency.zec: return 'Mainnet'; @@ -283,6 +285,8 @@ class TrocadorExchangeProvider extends ExchangeProvider { switch (currency) { case CryptoCurrency.zec: return 'zec'; + case CryptoCurrency.usdcEPoly: + return 'usdce'; default: return currency.title.toLowerCase(); } diff --git a/lib/main.dart b/lib/main.dart index 80c08ec91..b93d678e4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -167,8 +167,8 @@ Future initializeAppConfigs() async { transactionDescriptions: transactionDescriptions, secureStorage: secureStorage, anonpayInvoiceInfo: anonpayInvoiceInfo, - initialMigrationVersion: 24); - } + initialMigrationVersion: 26); +} Future initialSetup( {required SharedPreferences sharedPreferences, diff --git a/lib/polygon/cw_polygon.dart b/lib/polygon/cw_polygon.dart new file mode 100644 index 000000000..066b29d43 --- /dev/null +++ b/lib/polygon/cw_polygon.dart @@ -0,0 +1,156 @@ +part of 'polygon.dart'; + +class CWPolygon extends Polygon { + @override + List getPolygonWordList(String language) => EthereumMnemonics.englishWordlist; + + WalletService createPolygonWalletService(Box walletInfoSource) => + PolygonWalletService(walletInfoSource); + + @override + WalletCredentials createPolygonNewWalletCredentials({ + required String name, + WalletInfo? walletInfo, + }) => + PolygonNewWalletCredentials(name: name, walletInfo: walletInfo); + + @override + WalletCredentials createPolygonRestoreWalletFromSeedCredentials({ + required String name, + required String mnemonic, + required String password, + }) => + PolygonRestoreWalletFromSeedCredentials(name: name, password: password, mnemonic: mnemonic); + + @override + WalletCredentials createPolygonRestoreWalletFromPrivateKey({ + required String name, + required String privateKey, + required String password, + }) => + PolygonRestoreWalletFromPrivateKey(name: name, password: password, privateKey: privateKey); + + @override + String getAddress(WalletBase wallet) => (wallet as PolygonWallet).walletAddresses.address; + + @override + String getPrivateKey(WalletBase wallet) { + final privateKeyHolder = (wallet as PolygonWallet).polygonPrivateKey; + String stringKey = bytesToHex(privateKeyHolder.privateKey); + return stringKey; + } + + @override + String getPublicKey(WalletBase wallet) { + final privateKeyInUnitInt = (wallet as PolygonWallet).polygonPrivateKey; + final publicKey = privateKeyInUnitInt.address.hex; + return publicKey; + } + + @override + TransactionPriority getDefaultTransactionPriority() => PolygonTransactionPriority.medium; + + @override + TransactionPriority getPolygonTransactionPrioritySlow() => PolygonTransactionPriority.slow; + + @override + List getTransactionPriorities() => PolygonTransactionPriority.all; + + @override + TransactionPriority deserializePolygonTransactionPriority(int raw) => + PolygonTransactionPriority.deserialize(raw: raw); + + Object createPolygonTransactionCredentials( + List outputs, { + required TransactionPriority priority, + required CryptoCurrency currency, + int? feeRate, + }) => + PolygonTransactionCredentials( + outputs + .map((out) => OutputInfo( + fiatAmount: out.fiatAmount, + cryptoAmount: out.cryptoAmount, + address: out.address, + note: out.note, + sendAll: out.sendAll, + extractedAddress: out.extractedAddress, + isParsedAddress: out.isParsedAddress, + formattedCryptoAmount: out.formattedCryptoAmount)) + .toList(), + priority: priority as PolygonTransactionPriority, + currency: currency, + feeRate: feeRate, + ); + + Object createPolygonTransactionCredentialsRaw( + List outputs, { + TransactionPriority? priority, + required CryptoCurrency currency, + required int feeRate, + }) => + PolygonTransactionCredentials( + outputs, + priority: priority as PolygonTransactionPriority?, + currency: currency, + feeRate: feeRate, + ); + + @override + int formatterPolygonParseAmount(String amount) => PolygonFormatter.parsePolygonAmount(amount); + + @override + double formatterPolygonAmountToDouble( + {TransactionInfo? transaction, BigInt? amount, int exponent = 18}) { + assert(transaction != null || amount != null); + + if (transaction != null) { + transaction as PolygonTransactionInfo; + return transaction.ethAmount / BigInt.from(10).pow(transaction.exponent); + } else { + return (amount!) / BigInt.from(10).pow(exponent); + } + } + + @override + List getERC20Currencies(WalletBase wallet) { + final polygonWallet = wallet as PolygonWallet; + return polygonWallet.erc20Currencies; + } + + @override + Future addErc20Token(WalletBase wallet, Erc20Token token) async => + await (wallet as PolygonWallet).addErc20Token(token); + + @override + Future deleteErc20Token(WalletBase wallet, Erc20Token token) async => + await (wallet as PolygonWallet).deleteErc20Token(token); + + @override + Future getErc20Token(WalletBase wallet, String contractAddress) async { + final polygonWallet = wallet as PolygonWallet; + return await polygonWallet.getErc20Token(contractAddress); + } + + @override + CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) { + transaction as PolygonTransactionInfo; + if (transaction.tokenSymbol == CryptoCurrency.maticpoly.title) { + return CryptoCurrency.maticpoly; + } + + wallet as PolygonWallet; + return wallet.erc20Currencies.firstWhere( + (element) => transaction.tokenSymbol.toLowerCase() == element.symbol.toLowerCase()); + } + + @override + void updatePolygonScanUsageState(WalletBase wallet, bool isEnabled) { + (wallet as PolygonWallet).updatePolygonScanUsageState(isEnabled); + } + + @override + Web3Client? getWeb3Client(WalletBase wallet) { + return (wallet as PolygonWallet).getWeb3Client(); + } +} diff --git a/lib/reactions/fiat_rate_update.dart b/lib/reactions/fiat_rate_update.dart index 141401e6a..2b757ad44 100644 --- a/lib/reactions/fiat_rate_update.dart +++ b/lib/reactions/fiat_rate_update.dart @@ -3,9 +3,11 @@ import 'package:cake_wallet/core/fiat_conversion_service.dart'; import 'package:cake_wallet/entities/fiat_api_mode.dart'; import 'package:cake_wallet/entities/update_haven_rate.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; +import 'package:cake_wallet/polygon/polygon.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:cw_core/erc20_token.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:mobx/mobx.dart'; @@ -33,10 +35,18 @@ Future startFiatRateUpdate( torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly); } + Iterable? currencies; if (appStore.wallet!.type == WalletType.ethereum) { - final currencies = - ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled); + currencies = + ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled); + } + if (appStore.wallet!.type == WalletType.polygon) { + currencies = + polygon!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled); + } + + if (currencies != null) { for (final currency in currencies) { () async { fiatConversionStore.prices[currency] = await FiatConversionService.fetchPrice( diff --git a/lib/reactions/on_current_wallet_change.dart b/lib/reactions/on_current_wallet_change.dart index 5f956dc1a..42fbd182e 100644 --- a/lib/reactions/on_current_wallet_change.dart +++ b/lib/reactions/on_current_wallet_change.dart @@ -2,7 +2,8 @@ import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart'; import 'package:cake_wallet/entities/fiat_api_mode.dart'; import 'package:cake_wallet/entities/update_haven_rate.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; -import 'package:cake_wallet/nano/nano.dart'; +import 'package:cake_wallet/polygon/polygon.dart'; +import 'package:cw_core/erc20_token.dart'; import 'package:cw_core/transaction_history.dart'; import 'package:cw_core/balance.dart'; import 'package:cw_core/transaction_info.dart'; @@ -107,10 +108,17 @@ void startCurrentWalletChangeReaction( fiat: settingsStore.fiatCurrency, torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly); + Iterable? currencies; if (wallet.type == WalletType.ethereum) { - final currencies = + currencies = ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled); + } + if (wallet.type == WalletType.polygon) { + currencies = + polygon!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled); + } + if (currencies != null) { for (final currency in currencies) { () async { fiatConversionStore.prices[currency] = await FiatConversionService.fetchPrice( diff --git a/lib/reactions/wallet_connect.dart b/lib/reactions/wallet_connect.dart new file mode 100644 index 000000000..4f5923e26 --- /dev/null +++ b/lib/reactions/wallet_connect.dart @@ -0,0 +1,57 @@ +import 'package:cake_wallet/core/wallet_connect/evm_chain_id.dart'; +import 'package:cw_core/wallet_type.dart'; + +bool isEVMCompatibleChain(WalletType walletType) { + switch (walletType) { + case WalletType.polygon: + case WalletType.ethereum: + return true; + default: + return false; + } +} + +String getChainNameSpaceAndIdBasedOnWalletType(WalletType walletType) { + switch (walletType) { + case WalletType.ethereum: + return EVMChainId.ethereum.chain(); + case WalletType.polygon: + return EVMChainId.polygon.chain(); + default: + return ''; + } +} + +int getChainIdBasedOnWalletType(WalletType walletType) { + switch (walletType) { + case WalletType.polygon: + return 137; + + // For now, we return eth chain Id as the default, we'll modify as we add more wallets + case WalletType.ethereum: + default: + return 1; + } +} + +String getChainNameBasedOnWalletType(WalletType walletType) { + switch (walletType) { + case WalletType.ethereum: + return 'eth'; + case WalletType.polygon: + return 'polygon'; + default: + return ''; + } +} + +String getTokenNameBasedOnWalletType(WalletType walletType) { + switch (walletType) { + case WalletType.ethereum: + return 'ETH'; + case WalletType.polygon: + return 'MATIC'; + default: + return ''; + } +} diff --git a/lib/router.dart b/lib/router.dart index 313751ab3..b7b7c9a8e 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -47,6 +47,7 @@ import 'package:cake_wallet/src/screens/settings/connection_sync_page.dart'; import 'package:cake_wallet/src/screens/settings/trocador_providers_page.dart'; import 'package:cake_wallet/src/screens/settings/tor_page.dart'; import 'package:cake_wallet/src/screens/setup_2fa/modify_2fa_page.dart'; +import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_info_page.dart'; import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_qr_page.dart'; import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa.dart'; import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_enter_code_page.dart'; @@ -56,13 +57,13 @@ import 'package:cake_wallet/src/screens/support_other_links/support_other_links_ import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart'; import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart'; import 'package:cake_wallet/src/screens/wallet_connect/wc_connections_listing_view.dart'; -import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/utils/payment_request.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart'; import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart'; import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart'; import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart'; +import 'package:cake_wallet/view_model/seed_type_view_model.dart'; import 'package:cake_wallet/wallet_type_utils.dart'; import 'package:cw_core/nano_account.dart'; import 'package:cw_core/wallet_info.dart'; @@ -147,9 +148,9 @@ Route createRoute(RouteSettings settings) { case Routes.newWallet: final type = settings.arguments as WalletType; final walletNewVM = getIt.get(param1: type); - final settingsStore = getIt.get(); + final seedTypeViewModel = getIt.get(); - return CupertinoPageRoute(builder: (_) => NewWalletPage(walletNewVM, settingsStore)); + return CupertinoPageRoute(builder: (_) => NewWalletPage(walletNewVM, seedTypeViewModel)); case Routes.setupPin: Function(PinCodeState, String)? callback; @@ -390,8 +391,10 @@ Route createRoute(RouteSettings settings) { return MaterialPageRoute( builder: (_) => getIt.get(param1: settings.arguments as Order)); - case Routes.buy: - return MaterialPageRoute(builder: (_) => getIt.get()); + case Routes.buySellPage: + final args = settings.arguments as bool; + return MaterialPageRoute( + builder: (_) => getIt.get(param1: args)); case Routes.buyWebView: final args = settings.arguments as List; @@ -412,12 +415,10 @@ Route createRoute(RouteSettings settings) { case Routes.faq: return MaterialPageRoute(builder: (_) => getIt.get()); - case Routes.preSeed: + case Routes.preSeedPage: return MaterialPageRoute( builder: (_) => getIt.get( - param1: settings.arguments as WalletType, - param2: getIt.get( - param1: settings.arguments as WalletType))); + param1: settings.arguments as int)); case Routes.backup: return CupertinoPageRoute( @@ -535,9 +536,9 @@ Route createRoute(RouteSettings settings) { return CupertinoPageRoute( builder: (_) => AdvancedPrivacySettingsPage( - getIt.get(param1: type), - getIt.get(param1: type, param2: false), - )); + getIt.get(param1: type), + getIt.get(param1: type, param2: false), + getIt.get())); case Routes.anonPayInvoicePage: final args = settings.arguments as List; @@ -580,6 +581,9 @@ Route createRoute(RouteSettings settings) { case Routes.modify2FAPage: return MaterialPageRoute(builder: (_) => getIt.get()); + case Routes.setup2faInfoPage: + return MaterialPageRoute(builder: (_) => getIt.get()); + case Routes.homeSettings: return CupertinoPageRoute( builder: (_) => getIt.get(param1: settings.arguments), diff --git a/lib/routes.dart b/lib/routes.dart index 60801297a..7ad5c70bc 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -47,7 +47,7 @@ class Routes { static const exchangeTemplate = '/exchange_template'; static const restoreWalletType = '/restore_wallet_type'; static const restoreWallet = '/restore_wallet'; - static const preSeed = '/pre_seed'; + static const preSeedPage = '/pre_seed_page'; static const backup = '/backup'; static const editBackupPassword = '/edit_backup_passowrd'; static const restoreFromBackup = '/restore_from_backup'; @@ -55,7 +55,7 @@ class Routes { static const supportLiveChat = '/support/live_chat'; static const supportOtherLinks = '/support/other'; static const orderDetails = '/order_details'; - static const buy = '/buy'; + static const buySellPage = '/buy_sell_page'; static const buyWebView = '/buy_web_view'; static const unspentCoinsList = '/unspent_coins_list'; static const unspentCoinsDetails = '/unspent_coins_details'; @@ -96,6 +96,7 @@ class Routes { static const setup_2faQRPage = '/setup_2fa_qr_page'; static const totpAuthCodePage = '/totp_auth_code_page'; static const modify2FAPage = '/modify_2fa_page'; + static const setup2faInfoPage = '/setup_2fa_info_page'; static const homeSettings = '/home_settings'; static const editToken = '/edit_token'; static const manageNodes = '/manage_nodes'; diff --git a/lib/src/screens/InfoPage.dart b/lib/src/screens/InfoPage.dart new file mode 100644 index 000000000..5398df22c --- /dev/null +++ b/lib/src/screens/InfoPage.dart @@ -0,0 +1,83 @@ +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:cake_wallet/utils/responsive_layout_util.dart'; +import 'package:flutter/material.dart'; + +abstract class InfoPage extends BasePage { + InfoPage({ + this.imageLightPath = 'assets/images/pre_seed_light.png', + this.imageDarkPath = 'assets/images/pre_seed_dark.png', + }); + + final String imageLightPath; + final String imageDarkPath; + + Image get imageLight => Image.asset(imageLightPath); + Image get imageDark => Image.asset(imageDarkPath); + + bool get onWillPop => true; + String get pageTitle; + String get pageDescription; + String get buttonText; + void Function(BuildContext) get onPressed; + + @override + Widget? leading(BuildContext context) => null; + + @override + String get title => pageTitle; + + @override + Widget body(BuildContext context) { + final image = currentTheme.type == ThemeType.dark ? imageDark : imageLight; + + return WillPopScope( + onWillPop: () async => onWillPop, + child: Container( + alignment: Alignment.center, + padding: EdgeInsets.all(24), + child: ConstrainedBox( + constraints: BoxConstraints( + maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: ConstrainedBox( + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height * 0.3), + child: AspectRatio(aspectRatio: 1, child: image), + ), + ), + Expanded( + child: Padding( + padding: EdgeInsets.all(10), + child: Text( + pageDescription, + textAlign: TextAlign.center, + style: TextStyle( + height: 1.7, + fontSize: 14, + fontWeight: FontWeight.normal, + color: Theme.of(context) + .extension()! + .secondaryTextColor, + ), + ), + ), + ), + PrimaryButton( + onPressed: () => onPressed(context), + text: buttonText, + color: Theme.of(context).primaryColor, + textColor: Colors.white, + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/src/screens/buy/buy_options_page.dart b/lib/src/screens/buy/buy_options_page.dart index 930878544..50f041d2e 100644 --- a/lib/src/screens/buy/buy_options_page.dart +++ b/lib/src/screens/buy/buy_options_page.dart @@ -1,21 +1,19 @@ -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:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/widgets/option_tile.dart'; import 'package:cake_wallet/themes/extensions/option_tile_theme.dart'; import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart'; +import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:flutter/material.dart'; -class BuyOptionsPage extends BasePage { - final iconDarkRobinhood = 'assets/images/robinhood_dark.png'; - final iconLightRobinhood = 'assets/images/robinhood_light.png'; - final iconDarkOnramper = 'assets/images/onramper_dark.png'; - final iconLightOnramper = 'assets/images/onramper_light.png'; +class BuySellOptionsPage extends BasePage { + BuySellOptionsPage(this.dashboardViewModel, this.isBuyAction); + + final DashboardViewModel dashboardViewModel; + final bool isBuyAction; @override - String get title => S.current.buy; + String get title => isBuyAction ? S.current.buy : S.current.sell; @override AppBarStyle get appBarStyle => AppBarStyle.regular; @@ -23,10 +21,9 @@ class BuyOptionsPage extends BasePage { @override Widget body(BuildContext context) { final isLightMode = Theme.of(context).extension()?.useDarkImage ?? false; - final iconRobinhood = - Image.asset(isLightMode ? iconLightRobinhood : iconDarkRobinhood, height: 40, width: 40); - final iconOnramper = - Image.asset(isLightMode ? iconLightOnramper : iconDarkOnramper, height: 40, width: 40); + final availableProviders = isBuyAction + ? dashboardViewModel.availableBuyProviders + : dashboardViewModel.availableSellProviders; return Container( child: Center( @@ -34,31 +31,30 @@ class BuyOptionsPage extends BasePage { constraints: BoxConstraints(maxWidth: 330), child: Column( children: [ - Padding( - padding: EdgeInsets.only(top: 24), - child: OptionTile( - image: iconOnramper, - title: "Onramper", - description: S.of(context).onramper_option_description, - onPressed: () async => - await getIt.get().launchProvider(context), - ), - ), - Padding( - padding: EdgeInsets.only(top: 24), - child: OptionTile( - image: iconRobinhood, - title: "Robinhood Connect", - description: S.of(context).robinhood_option_description, - onPressed: () async => - await getIt.get().launchProvider(context), - ), - ), + ...availableProviders.map((provider) { + final icon = Image.asset( + isLightMode ? provider.lightIcon : provider.darkIcon, + height: 40, + width: 40, + ); + + return Padding( + padding: EdgeInsets.only(top: 24), + child: OptionTile( + image: icon, + title: provider.toString(), + description: provider.providerDescription, + onPressed: () => provider.launchProvider(context, isBuyAction), + ), + ); + }).toList(), Spacer(), Padding( padding: EdgeInsets.fromLTRB(24, 24, 24, 32), child: Text( - S.of(context).select_buy_provider_notice, + isBuyAction + ? S.of(context).select_buy_provider_notice + : S.of(context).select_sell_provider_notice, textAlign: TextAlign.center, style: TextStyle( fontSize: 14, diff --git a/lib/src/screens/buy/buy_webview_page.dart b/lib/src/screens/buy/buy_webview_page.dart index a6ea2ab7b..6f7a39322 100644 --- a/lib/src/screens/buy/buy_webview_page.dart +++ b/lib/src/screens/buy/buy_webview_page.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'dart:io'; import 'package:cake_wallet/buy/buy_provider.dart'; -import 'package:cake_wallet/buy/moonpay/moonpay_buy_provider.dart'; +import 'package:cake_wallet/buy/moonpay/moonpay_provider.dart'; import 'package:cake_wallet/buy/wyre/wyre_buy_provider.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/palette.dart'; diff --git a/lib/src/screens/buy/widgets/buy_list_item.dart b/lib/src/screens/buy/widgets/buy_list_item.dart index fc8fea81d..d8c457ac0 100644 --- a/lib/src/screens/buy/widgets/buy_list_item.dart +++ b/lib/src/screens/buy/widgets/buy_list_item.dart @@ -29,11 +29,10 @@ class BuyListItem extends StatelessWidget { @override Widget build(BuildContext context) { - final isSelected = selectedProvider?.description == provider.description; + final isSelected = selectedProvider?.providerDescription == provider.providerDescription; final iconColor = isSelected ? Colors.white : Colors.black; - final providerIcon = getBuyProviderIcon(provider.description, - iconColor: iconColor)!; + final providerIcon = Image.asset('assets/images/wyre-icon.png', width: 36, height: 36); final backgroundColor = isSelected ? Palette.greyBlueCraiola @@ -76,7 +75,7 @@ class BuyListItem extends StatelessWidget { padding: EdgeInsets.only(right: 10), child: providerIcon), Text( - provider.description.title, + provider.title, style: TextStyle( color: secondaryTextColor, fontSize: 20, diff --git a/lib/src/screens/contact/contact_list_page.dart b/lib/src/screens/contact/contact_list_page.dart index d9e3c0c85..99f2aa251 100644 --- a/lib/src/screens/contact/contact_list_page.dart +++ b/lib/src/screens/contact/contact_list_page.dart @@ -135,6 +135,7 @@ class ContactListPage extends BasePage { await showBar(context, S.of(context).copied_to_clipboard); } }, + behavior: HitTestBehavior.opaque, child: Container( padding: const EdgeInsets.only(top: 16, bottom: 16, right: 24), child: Row( diff --git a/lib/src/screens/dashboard/dashboard_page.dart b/lib/src/screens/dashboard/dashboard_page.dart index 98b24f783..356c69c00 100644 --- a/lib/src/screens/dashboard/dashboard_page.dart +++ b/lib/src/screens/dashboard/dashboard_page.dart @@ -7,6 +7,7 @@ import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sideba import 'package:cake_wallet/src/screens/dashboard/pages/market_place_page.dart'; import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/bottom_sheet_listener.dart'; import 'package:cake_wallet/src/widgets/gradient_background.dart'; +import 'package:cake_wallet/src/widgets/vulnerable_seeds_popup.dart'; import 'package:cake_wallet/themes/extensions/sync_indicator_theme.dart'; import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/utils/version_comparator.dart'; @@ -60,7 +61,8 @@ class DashboardPage extends StatelessWidget { ); if (DeviceInfo.instance.isDesktop) { - if (responsiveLayoutUtil.screenWidth > ResponsiveLayoutUtilBase.kDesktopMaxDashBoardWidthConstraint) { + if (responsiveLayoutUtil.screenWidth > + ResponsiveLayoutUtilBase.kDesktopMaxDashBoardWidthConstraint) { return getIt.get(); } else { return dashboardPageView; @@ -295,6 +297,8 @@ class _DashboardPageView extends BasePage { _showReleaseNotesPopup(context); + _showVulnerableSeedsPopup(context); + var needToPresentYat = false; var isInactive = false; @@ -354,4 +358,22 @@ class _DashboardPageView extends BasePage { sharedPrefs.setInt(PreferencesKey.lastSeenAppVersion, currentAppVersion); } } + + void _showVulnerableSeedsPopup(BuildContext context) async { + final List affectedWalletNames = await dashboardViewModel.checkAffectedWallets(); + + if (affectedWalletNames.isNotEmpty) { + Future.delayed( + Duration(seconds: 1), + () { + showPopUp( + context: context, + builder: (BuildContext context) { + return VulnerableSeedsPopup(affectedWalletNames); + }, + ); + }, + ); + } + } } diff --git a/lib/src/screens/dashboard/desktop_dashboard_page.dart b/lib/src/screens/dashboard/desktop_dashboard_page.dart index 216ea152d..b25d0774b 100644 --- a/lib/src/screens/dashboard/desktop_dashboard_page.dart +++ b/lib/src/screens/dashboard/desktop_dashboard_page.dart @@ -3,6 +3,7 @@ import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/release_notes/release_notes_screen.dart'; import 'package:cake_wallet/src/screens/yat_emoji_id.dart'; +import 'package:cake_wallet/src/widgets/vulnerable_seeds_popup.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/utils/version_comparator.dart'; import 'package:flutter/material.dart'; @@ -110,5 +111,25 @@ class DesktopDashboardPage extends StatelessWidget { } else if (isNewInstall!) { sharedPrefs.setInt(PreferencesKey.lastSeenAppVersion, currentAppVersion); } + + _showVulnerableSeedsPopup(context); + } + + void _showVulnerableSeedsPopup(BuildContext context) async { + final List affectedWalletNames = await dashboardViewModel.checkAffectedWallets(); + + if (affectedWalletNames.isNotEmpty) { + Future.delayed( + Duration(seconds: 1), + () { + showPopUp( + context: context, + builder: (BuildContext context) { + return VulnerableSeedsPopup(affectedWalletNames); + }, + ); + }, + ); + } } } diff --git a/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart b/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart index 1aa7f6c4a..59c31aa62 100644 --- a/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart +++ b/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart @@ -33,6 +33,7 @@ class _DesktopWalletSelectionDropDownState extends State()!.titleColor, size: 22.0, ), padding: EdgeInsets.all(12), diff --git a/lib/src/screens/dashboard/pages/balance_page.dart b/lib/src/screens/dashboard/pages/balance_page.dart index 057352456..283d2171d 100644 --- a/lib/src/screens/dashboard/pages/balance_page.dart +++ b/lib/src/screens/dashboard/pages/balance_page.dart @@ -1,5 +1,6 @@ import 'package:auto_size_text/auto_size_text.dart'; import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/dashboard/pages/nft_listing_page.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/home_screen_account_widget.dart'; @@ -13,7 +14,6 @@ import 'package:cake_wallet/utils/feature_flag.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart'; -import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; @@ -32,12 +32,12 @@ class BalancePage extends StatelessWidget { Widget build(BuildContext context) { return Observer( builder: (context) { - final isEthereumWallet = dashboardViewModel.type == WalletType.ethereum; + final isEVMCompatible = isEVMCompatibleChain(dashboardViewModel.type); return DefaultTabController( - length: isEthereumWallet ? 2 : 1, + length: isEVMCompatible ? 2 : 1, child: Column( children: [ - if (isEthereumWallet) + if (isEVMCompatible) Align( alignment: Alignment.centerLeft, child: Padding( @@ -54,6 +54,24 @@ class BalancePage extends StatelessWidget { Theme.of(context).extension()!.pageTitleTextColor, height: 1, ), + unselectedLabelStyle: TextStyle( + fontSize: 18, + fontFamily: 'Lato', + fontWeight: FontWeight.w600, + color: + Theme.of(context).extension()!.pageTitleTextColor, + height: 1, + ), + labelColor: + Theme.of(context).extension()!.pageTitleTextColor, + dividerColor: + Theme.of(context).extension()!.pageTitleTextColor, + indicatorColor: + Theme.of(context).extension()!.pageTitleTextColor, + unselectedLabelColor: Theme.of(context) + .extension()! + .pageTitleTextColor + .withOpacity(0.5), tabs: [ Tab(text: 'My Crypto'), Tab(text: 'My NFTs'), @@ -66,7 +84,7 @@ class BalancePage extends StatelessWidget { physics: NeverScrollableScrollPhysics(), children: [ CryptoBalanceWidget(dashboardViewModel: dashboardViewModel), - if (isEthereumWallet) NFTListingPage(nftViewModel: nftViewModel) + if (isEVMCompatible) NFTListingPage(nftViewModel: nftViewModel) ], ), ), diff --git a/lib/src/screens/dashboard/widgets/filter_list_widget.dart b/lib/src/screens/dashboard/widgets/filter_list_widget.dart new file mode 100644 index 000000000..cda4f5f10 --- /dev/null +++ b/lib/src/screens/dashboard/widgets/filter_list_widget.dart @@ -0,0 +1,158 @@ +import 'package:cake_wallet/entities/list_order_mode.dart'; +import 'package:cake_wallet/entities/wallet_list_order_types.dart'; +import 'package:cake_wallet/src/screens/settings/widgets/settings_choices_cell.dart'; +import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; +import 'package:cake_wallet/src/widgets/section_divider.dart'; +import 'package:cake_wallet/themes/extensions/menu_theme.dart'; +import 'package:cake_wallet/view_model/settings/choices_list_item.dart'; +import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart'; +import 'package:flutter/material.dart'; +import 'package:cake_wallet/src/widgets/picker_wrapper_widget.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart'; + +class FilterListWidget extends StatefulWidget { + FilterListWidget({ + required this.initalType, + required this.initalAscending, + required this.onClose, + }); + + final WalletListOrderType? initalType; + final bool initalAscending; + final Function(bool, WalletListOrderType) onClose; + + @override + FilterListWidgetState createState() => FilterListWidgetState(); +} + +class FilterListWidgetState extends State { + late bool ascending; + late WalletListOrderType? type; + + @override + void initState() { + super.initState(); + ascending = widget.initalAscending; + type = widget.initalType; + } + + void setSelectedOrderType(WalletListOrderType? orderType) { + setState(() { + type = orderType; + }); + } + + @override + Widget build(BuildContext context) { + const sectionDivider = const HorizontalSectionDivider(); + return PickerWrapperWidget( + onClose: () { + widget.onClose(ascending, type!); + Navigator.of(context).pop(); + }, + children: [ + Padding( + padding: EdgeInsets.only(left: 24, right: 24, top: 24), + child: ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(24)), + child: Container( + color: Theme.of(context).extension()!.backgroundColor, + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Padding( + padding: EdgeInsets.all(24.0), + child: Text( + S.of(context).order_by, + style: TextStyle( + color: + Theme.of(context).extension()!.detailsTitlesColor, + fontSize: 16, + fontFamily: 'Lato', + decoration: TextDecoration.none, + ), + ), + ), + if (type != WalletListOrderType.Custom) ...[ + sectionDivider, + SettingsChoicesCell( + ChoicesListItem( + title: "", + items: ListOrderMode.all, + selectedItem: ascending ? ListOrderMode.ascending : ListOrderMode.descending, + onItemSelected: (ListOrderMode listOrderMode) { + setState(() { + ascending = listOrderMode == ListOrderMode.ascending; + }); + }, + ), + ), + ], + sectionDivider, + RadioListTile( + value: WalletListOrderType.CreationDate, + groupValue: type, + title: Text( + WalletListOrderType.CreationDate.toString(), + style: TextStyle( + color: Theme.of(context).extension()!.titleColor, + fontSize: 16, + fontFamily: 'Lato', + fontWeight: FontWeight.bold, + decoration: TextDecoration.none), + ), + onChanged: setSelectedOrderType, + activeColor: Theme.of(context).primaryColor, + ), + RadioListTile( + value: WalletListOrderType.Alphabetical, + groupValue: type, + title: Text( + WalletListOrderType.Alphabetical.toString(), + style: TextStyle( + color: Theme.of(context).extension()!.titleColor, + fontSize: 16, + fontFamily: 'Lato', + fontWeight: FontWeight.bold, + decoration: TextDecoration.none), + ), + onChanged: setSelectedOrderType, + activeColor: Theme.of(context).primaryColor, + ), + RadioListTile( + value: WalletListOrderType.GroupByType, + groupValue: type, + title: Text( + WalletListOrderType.GroupByType.toString(), + style: TextStyle( + color: Theme.of(context).extension()!.titleColor, + fontSize: 16, + fontFamily: 'Lato', + fontWeight: FontWeight.bold, + decoration: TextDecoration.none), + ), + onChanged: setSelectedOrderType, + activeColor: Theme.of(context).primaryColor, + ), + RadioListTile( + value: WalletListOrderType.Custom, + groupValue: type, + title: Text( + WalletListOrderType.Custom.toString(), + style: TextStyle( + color: Theme.of(context).extension()!.titleColor, + fontSize: 16, + fontFamily: 'Lato', + fontWeight: FontWeight.bold, + decoration: TextDecoration.none), + ), + onChanged: setSelectedOrderType, + activeColor: Theme.of(context).primaryColor, + ), + ]), + ), + ), + ) + ], + ); + } +} diff --git a/lib/src/screens/dashboard/widgets/home_screen_account_widget.dart b/lib/src/screens/dashboard/widgets/home_screen_account_widget.dart index f548a8737..fa036978f 100644 --- a/lib/src/screens/dashboard/widgets/home_screen_account_widget.dart +++ b/lib/src/screens/dashboard/widgets/home_screen_account_widget.dart @@ -18,6 +18,7 @@ class HomeScreenAccountWidget extends StatelessWidget { context: context, builder: (_) => getIt.get()); }, + behavior: HitTestBehavior.opaque, child: Container( height: 100.0, child: Row( diff --git a/lib/src/screens/dashboard/widgets/menu_widget.dart b/lib/src/screens/dashboard/widgets/menu_widget.dart index bf001eb32..ed9b823ad 100644 --- a/lib/src/screens/dashboard/widgets/menu_widget.dart +++ b/lib/src/screens/dashboard/widgets/menu_widget.dart @@ -32,7 +32,8 @@ class MenuWidgetState extends State { this.ethereumIcon = Image.asset('assets/images/eth_icon.png'), this.nanoIcon = Image.asset('assets/images/nano_icon.png'), this.bananoIcon = Image.asset('assets/images/nano_icon.png'), - this.bitcoinCashIcon = Image.asset('assets/images/bch_icon.png'); + this.bitcoinCashIcon = Image.asset('assets/images/bch_icon.png'), + this.polygonIcon = Image.asset('assets/images/matic_icon.png'); final largeScreen = 731; @@ -54,6 +55,8 @@ class MenuWidgetState extends State { Image bitcoinCashIcon; Image nanoIcon; Image bananoIcon; + Image polygonIcon; + @override void initState() { @@ -219,6 +222,8 @@ class MenuWidgetState extends State { return nanoIcon; case WalletType.banano: return bananoIcon; + case WalletType.polygon: + return polygonIcon; default: throw Exception('No icon for ${type.toString()}'); } diff --git a/lib/src/screens/exchange/exchange_page.dart b/lib/src/screens/exchange/exchange_page.dart index 1f441ea99..b3fbd19a7 100644 --- a/lib/src/screens/exchange/exchange_page.dart +++ b/lib/src/screens/exchange/exchange_page.dart @@ -411,10 +411,6 @@ class ExchangePage extends BasePage { } }); - reaction((_) => exchangeViewModel.isReceiveAddressEnabled, (bool isEnabled) { - receiveKey.currentState!.isAddressEditable(isEditable: isEnabled); - }); - reaction((_) => exchangeViewModel.isReceiveAmountEditable, (bool isReceiveAmountEditable) { receiveKey.currentState!.isAmountEditable(isEditable: isReceiveAmountEditable); }); @@ -670,7 +666,6 @@ class ExchangePage extends BasePage { ? exchangeViewModel.wallet.walletAddresses.address : exchangeViewModel.receiveAddress, initialIsAmountEditable: exchangeViewModel.isReceiveAmountEditable, - initialIsAddressEditable: exchangeViewModel.isReceiveAddressEnabled, isAmountEstimated: true, isMoneroWallet: exchangeViewModel.isMoneroWallet, currencies: exchangeViewModel.receiveCurrencies, diff --git a/lib/src/screens/exchange/exchange_template_page.dart b/lib/src/screens/exchange/exchange_template_page.dart index 31f35661d..3a7456dd8 100644 --- a/lib/src/screens/exchange/exchange_template_page.dart +++ b/lib/src/screens/exchange/exchange_template_page.dart @@ -174,8 +174,6 @@ class ExchangeTemplatePage extends BasePage { ? exchangeViewModel.wallet.walletAddresses.address : exchangeViewModel.receiveAddress, initialIsAmountEditable: false, - initialIsAddressEditable: - exchangeViewModel.isReceiveAddressEnabled, isAmountEstimated: true, isMoneroWallet: exchangeViewModel.isMoneroWallet, currencies: exchangeViewModel.receiveCurrencies, @@ -328,11 +326,6 @@ class ExchangeTemplatePage extends BasePage { } }); - reaction((_) => exchangeViewModel.isReceiveAddressEnabled, - (bool isEnabled) { - receiveKey.currentState!.isAddressEditable(isEditable: isEnabled); - }); - reaction((_) => exchangeViewModel.provider, (ExchangeProvider? provider) { receiveKey.currentState!.isAmountEditable(isEditable: false); }); diff --git a/lib/src/screens/exchange/widgets/exchange_card.dart b/lib/src/screens/exchange/widgets/exchange_card.dart index b55e96e85..706ace7de 100644 --- a/lib/src/screens/exchange/widgets/exchange_card.dart +++ b/lib/src/screens/exchange/widgets/exchange_card.dart @@ -23,7 +23,6 @@ class ExchangeCard extends StatefulWidget { required this.initialAddress, required this.initialWalletName, required this.initialIsAmountEditable, - required this.initialIsAddressEditable, required this.isAmountEstimated, required this.currencies, required this.onCurrencySelected, @@ -31,6 +30,7 @@ class ExchangeCard extends StatefulWidget { this.currencyValueValidator, this.addressTextFieldValidator, this.title = '', + this.initialIsAddressEditable = true, this.hasRefundAddress = false, this.isMoneroWallet = false, this.currencyButtonColor = Colors.transparent, diff --git a/lib/src/screens/exchange_trade/exchange_trade_page.dart b/lib/src/screens/exchange_trade/exchange_trade_page.dart index 23595efdf..e72b0f0a8 100644 --- a/lib/src/screens/exchange_trade/exchange_trade_page.dart +++ b/lib/src/screens/exchange_trade/exchange_trade_page.dart @@ -299,7 +299,8 @@ class ExchangeTradeState extends State { } void transactionStatePopup() { - showPopUp( + if (this.mounted) { + showPopUp( context: context, builder: (BuildContext popupContext) { return Observer(builder: (_) { @@ -344,7 +345,7 @@ class ExchangeTradeState extends State { onPressed: () { Navigator.of(popupContext).pop(); RequestReviewHandler.requestReview(); - }, + }, text: S.of(popupContext).got_it, color: Theme.of(popupContext).primaryColor, textColor: Colors.white)) @@ -391,5 +392,6 @@ class ExchangeTradeState extends State { ); }); }); + } } } 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 4b61e7889..a4bd6c7b9 100644 --- a/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart +++ b/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart @@ -9,6 +9,7 @@ import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell. import 'package:cake_wallet/themes/extensions/new_wallet_theme.dart'; import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart'; import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart'; +import 'package:cake_wallet/view_model/seed_type_view_model.dart'; import 'package:cake_wallet/view_model/settings/choices_list_item.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter/material.dart'; @@ -18,25 +19,30 @@ import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; class AdvancedPrivacySettingsPage extends BasePage { - AdvancedPrivacySettingsPage(this.advancedPrivacySettingsViewModel, this.nodeViewModel); + AdvancedPrivacySettingsPage( + this.advancedPrivacySettingsViewModel, this.nodeViewModel, this.seedTypeViewModel); final AdvancedPrivacySettingsViewModel advancedPrivacySettingsViewModel; final NodeCreateOrEditViewModel nodeViewModel; + final SeedTypeViewModel seedTypeViewModel; @override String get title => S.current.privacy_settings; @override - Widget body(BuildContext context) => - AdvancedPrivacySettingsBody(advancedPrivacySettingsViewModel, nodeViewModel); + Widget body(BuildContext context) => AdvancedPrivacySettingsBody( + advancedPrivacySettingsViewModel, nodeViewModel, seedTypeViewModel); } class AdvancedPrivacySettingsBody extends StatefulWidget { - const AdvancedPrivacySettingsBody(this.privacySettingsViewModel, this.nodeViewModel, {Key? key}) + const AdvancedPrivacySettingsBody( + this.privacySettingsViewModel, this.nodeViewModel, this.seedTypeViewModel, + {Key? key}) : super(key: key); final AdvancedPrivacySettingsViewModel privacySettingsViewModel; final NodeCreateOrEditViewModel nodeViewModel; + final SeedTypeViewModel seedTypeViewModel; @override _AdvancedPrivacySettingsBodyState createState() => _AdvancedPrivacySettingsBodyState(); @@ -59,7 +65,7 @@ class _AdvancedPrivacySettingsBodyState extends State( - title: S.current.disable_fiat, + title: S.current.fiat_api, items: FiatApiMode.all, selectedItem: widget.privacySettingsViewModel.fiatApiMode, onItemSelected: (FiatApiMode mode) => @@ -114,8 +120,8 @@ class _AdvancedPrivacySettingsBodyState extends State( title: S.current.seedtype, items: SeedType.all, - selectedItem: widget.privacySettingsViewModel.seedType, - onItemSelected: widget.privacySettingsViewModel.setSeedType, + selectedItem: widget.seedTypeViewModel.moneroSeedType, + onItemSelected: widget.seedTypeViewModel.setMoneroSeedType, ), ); }), diff --git a/lib/src/screens/new_wallet/new_wallet_page.dart b/lib/src/screens/new_wallet/new_wallet_page.dart index d86d51e93..499e5feff 100644 --- a/lib/src/screens/new_wallet/new_wallet_page.dart +++ b/lib/src/screens/new_wallet/new_wallet_page.dart @@ -1,10 +1,13 @@ import 'package:cake_wallet/entities/generate_name.dart'; +import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart'; +import 'package:cake_wallet/src/widgets/picker.dart'; import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:cake_wallet/main.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/themes/theme_base.dart'; 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_type_view_model.dart'; import 'package:mobx/mobx.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter/material.dart'; @@ -21,13 +24,12 @@ import 'package:cake_wallet/view_model/wallet_new_vm.dart'; import 'package:cake_wallet/themes/extensions/new_wallet_theme.dart'; import 'package:cake_wallet/themes/extensions/send_page_theme.dart'; import 'package:cake_wallet/entities/seed_type.dart'; -import 'package:cake_wallet/store/settings_store.dart'; class NewWalletPage extends BasePage { - NewWalletPage(this._walletNewVM, this._settingsStore); + NewWalletPage(this._walletNewVM, this._seedTypeViewModel); final WalletNewVM _walletNewVM; - final SettingsStore _settingsStore; + final SeedTypeViewModel _seedTypeViewModel; final walletNameImage = Image.asset('assets/images/wallet_name.png'); @@ -38,15 +40,15 @@ class NewWalletPage extends BasePage { @override Widget body(BuildContext context) => WalletNameForm( - _walletNewVM, currentTheme.type == ThemeType.dark ? walletNameImage : walletNameLightImage, _settingsStore); + _walletNewVM, currentTheme.type == ThemeType.dark ? walletNameImage : walletNameLightImage, _seedTypeViewModel); } class WalletNameForm extends StatefulWidget { - WalletNameForm(this._walletNewVM, this.walletImage, this._settingsStore); + WalletNameForm(this._walletNewVM, this.walletImage, this._seedTypeViewModel); final WalletNewVM _walletNewVM; final Image walletImage; - final SettingsStore _settingsStore; + final SeedTypeViewModel _seedTypeViewModel; @override _WalletNameFormState createState() => _WalletNameFormState(_walletNewVM); @@ -71,7 +73,7 @@ class _WalletNameFormState extends State { _stateReaction ??= reaction((_) => _walletNewVM.state, (ExecutionState state) async { if (state is ExecutedSuccessfullyState) { Navigator.of(navigatorKey.currentContext!) - .pushNamed(Routes.preSeed, arguments: _walletNewVM.type); + .pushNamed(Routes.preSeedPage, arguments: _walletNewVM.seedPhraseWordsLength); } if (state is FailureState) { @@ -88,6 +90,8 @@ class _WalletNameFormState extends State { }); } }); + + _setSeedType(SeedType.defaultSeedType); super.initState(); } @@ -183,25 +187,38 @@ class _WalletNameFormState extends State { ), ), ), + if (_walletNewVM.hasLanguageSelector) ...[ - Padding( - padding: EdgeInsets.only(top: 40), - child: Text( - S.of(context).seed_language_choose, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 16.0, - fontWeight: FontWeight.w500, - color: Theme.of(context).extension()!.titleColor), + if (_walletNewVM.hasSeedType) ...[ + Observer( + builder: (BuildContext build) => Padding( + padding: EdgeInsets.only(top: 24), + child: SelectButton( + text: widget._seedTypeViewModel.moneroSeedType.title, + onTap: () async { + await showPopUp( + context: context, + builder: (_) => Picker( + items: SeedType.all, + selectedAtIndex: isPolyseed ? 1 : 0, + onItemSelected: _setSeedType, + isSeparated: false, + ), + ); + }, + ), + ), ), - ), + ], Observer( builder: (BuildContext build) => Padding( - padding: EdgeInsets.only(top: 24), + padding: EdgeInsets.only(top: 10), child: SeedLanguageSelector( key: _languageSelectorKey, initialSelected: defaultSeedLanguage, - seedType: widget._settingsStore.moneroSeedType, + seedType: _walletNewVM.hasSeedType + ? widget._seedTypeViewModel.moneroSeedType + : SeedType.legacy, ), ), ) @@ -231,7 +248,7 @@ class _WalletNameFormState extends State { Navigator.of(context) .pushNamed(Routes.advancedPrivacySettings, arguments: _walletNewVM.type); }, - child: Text(S.of(context).advanced_privacy_settings), + child: Text(S.of(context).advanced_settings), ), ], )), @@ -253,11 +270,17 @@ class _WalletNameFormState extends State { buttonAction: () => Navigator.of(context).pop()); }); } else { - final isPolyseed = widget._settingsStore.moneroSeedType == SeedType.polyseed; _walletNewVM.create( options: _walletNewVM.hasLanguageSelector ? [_languageSelectorKey.currentState!.selected, isPolyseed] : null); } } + + bool get isPolyseed => widget._seedTypeViewModel.moneroSeedType == SeedType.polyseed; + + void _setSeedType(SeedType item) { + widget._seedTypeViewModel.setMoneroSeedType(item); + _languageSelectorKey.currentState?.selected = defaultSeedLanguage; // Reset Seed language + } } diff --git a/lib/src/screens/receive/widgets/currency_input_field.dart b/lib/src/screens/receive/widgets/currency_input_field.dart index 1241b2ba7..84b2a7bca 100644 --- a/lib/src/screens/receive/widgets/currency_input_field.dart +++ b/lib/src/screens/receive/widgets/currency_input_field.dart @@ -1,5 +1,6 @@ import 'package:cake_wallet/src/widgets/base_text_form_field.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:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -23,6 +24,13 @@ class CurrencyInputField extends StatelessWidget { final TextEditingController controller; final bool isLight; + String get _currencyName { + if (selectedCurrency is CryptoCurrency) { + return (selectedCurrency as CryptoCurrency).title.toUpperCase(); + } + return selectedCurrency.name.toUpperCase(); + } + @override Widget build(BuildContext context) { final arrowBottomPurple = Image.asset( @@ -74,7 +82,7 @@ class CurrencyInputField extends StatelessWidget { child: arrowBottomPurple, ), Text( - selectedCurrency.name.toUpperCase(), + _currencyName, style: TextStyle( fontWeight: FontWeight.w600, fontSize: 16, @@ -83,7 +91,7 @@ class CurrencyInputField extends StatelessWidget { ), if (selectedCurrency.tag != null) Padding( - padding: const EdgeInsets.only(right: 3.0), + padding: const EdgeInsets.symmetric(horizontal: 3.0), child: Container( decoration: BoxDecoration( color: Theme.of(context).extension()!.textFieldButtonColor, diff --git a/lib/src/screens/receive/widgets/qr_image.dart b/lib/src/screens/receive/widgets/qr_image.dart index f4c1eb177..f388fdd0b 100644 --- a/lib/src/screens/receive/widgets/qr_image.dart +++ b/lib/src/screens/receive/widgets/qr_image.dart @@ -4,12 +4,16 @@ import 'package:qr_flutter/qr_flutter.dart' as qr; class QrImage extends StatelessWidget { QrImage({ required this.data, + this.foregroundColor = Colors.black, + this.backgroundColor = Colors.white, this.size = 100.0, this.version, this.errorCorrectionLevel = qr.QrErrorCorrectLevel.L, }); final double size; + final Color foregroundColor; + final Color backgroundColor; final String data; final int? version; final int errorCorrectionLevel; @@ -21,8 +25,8 @@ class QrImage extends StatelessWidget { errorCorrectionLevel: errorCorrectionLevel, version: version ?? 9, // Previous value: 7 something happened after flutter upgrade monero wallets addresses are longer than ver. 7 ??? size: size, - foregroundColor: Colors.black, - backgroundColor: Colors.white, + foregroundColor: foregroundColor, + backgroundColor: backgroundColor, padding: const EdgeInsets.all(8.0), ); } 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 ac8413212..6f8f9eb2b 100644 --- a/lib/src/screens/restore/wallet_restore_from_seed_form.dart +++ b/lib/src/screens/restore/wallet_restore_from_seed_form.dart @@ -9,8 +9,10 @@ import 'package:cake_wallet/src/widgets/seed_language_picker.dart'; import 'package:cake_wallet/src/widgets/seed_widget.dart'; 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/seed_type_view_model.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 { @@ -19,6 +21,7 @@ class WalletRestoreFromSeedForm extends StatefulWidget { required this.displayLanguageSelector, required this.displayBlockHeightSelector, required this.type, + required this.seedTypeViewModel, this.blockHeightFocusNode, this.onHeightOrDateEntered, this.onSeedChange, @@ -28,6 +31,7 @@ class WalletRestoreFromSeedForm extends StatefulWidget { final WalletType type; final bool displayLanguageSelector; final bool displayBlockHeightSelector; + final SeedTypeViewModel seedTypeViewModel; final FocusNode? blockHeightFocusNode; final Function(bool)? onHeightOrDateEntered; final void Function(String)? onSeedChange; @@ -52,16 +56,28 @@ class WalletRestoreFromSeedFormState extends State { final TextEditingController nameTextEditingController; final TextEditingController seedTypeController; final GlobalKey formKey; + late ReactionDisposer moneroSeedTypeReaction; String language; - bool isPolyseed = false; @override void initState() { + _setSeedType(widget.seedTypeViewModel.moneroSeedType); _setLanguageLabel(language); - _setSeedType(SeedType.defaultSeedType); + moneroSeedTypeReaction = + reaction((_) => widget.seedTypeViewModel.moneroSeedType, (SeedType item) { + _setSeedType(item); + _changeLanguage('English'); + }); + super.initState(); } + @override + void dispose() { + super.dispose(); + moneroSeedTypeReaction(); + } + void onSeedChange(String seed) { if (widget.type == WalletType.monero && Polyseed.isValidSeed(seed)) { final lang = PolyseedLang.getByPhrase(seed); @@ -124,25 +140,30 @@ class WalletRestoreFromSeedFormState extends State { onSeedChange: onSeedChange), if (widget.type == WalletType.monero) GestureDetector( - onTap: () async { - await showPopUp( - context: context, - builder: (_) => Picker( - items: SeedType.all, - selectedAtIndex: isPolyseed ? 1 : 0, - mainAxisAlignment: MainAxisAlignment.start, - onItemSelected: _changeSeedType, - isSeparated: false, - )); - }, - child: Container( - color: Colors.transparent, - padding: EdgeInsets.only(top: 20.0), - child: IgnorePointer( - child: BaseTextFormField( - controller: seedTypeController, - enableInteractiveSelection: false, - readOnly: true)))), + onTap: () async { + await showPopUp( + context: context, + builder: (_) => Picker( + items: SeedType.all, + selectedAtIndex: isPolyseed ? 1 : 0, + mainAxisAlignment: MainAxisAlignment.start, + onItemSelected: _changeSeedType, + isSeparated: false, + )); + }, + child: Container( + color: Colors.transparent, + padding: EdgeInsets.only(top: 20.0), + child: IgnorePointer( + child: BaseTextFormField( + controller: seedTypeController, + enableInteractiveSelection: false, + readOnly: true, + suffixIcon: expandIcon, + ), + ), + ), + ), if (widget.displayLanguageSelector) GestureDetector( onTap: () async { @@ -154,14 +175,19 @@ class WalletRestoreFromSeedFormState extends State { seedType: isPolyseed ? SeedType.polyseed : SeedType.legacy, )); }, - child: Container( - color: Colors.transparent, - padding: EdgeInsets.only(top: 20.0), - child: IgnorePointer( - child: BaseTextFormField( - controller: languageController, - enableInteractiveSelection: false, - readOnly: true)))), + child: Container( + color: Colors.transparent, + padding: EdgeInsets.only(top: 20.0), + child: IgnorePointer( + child: BaseTextFormField( + controller: languageController, + enableInteractiveSelection: false, + readOnly: true, + suffixIcon: expandIcon, + ), + ), + ), + ), if (!isPolyseed && widget.displayBlockHeightSelector) BlockchainHeightWidget( focusNode: widget.blockHeightFocusNode, @@ -171,6 +197,19 @@ class WalletRestoreFromSeedFormState extends State { ])); } + bool get isPolyseed => widget.seedTypeViewModel.moneroSeedType == SeedType.polyseed; + + 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, + ), + ); + void _changeLanguage(String language) { final setLang = isPolyseed ? "POLYSEED_$language" : language; setState(() { @@ -187,10 +226,10 @@ class WalletRestoreFromSeedFormState extends State { void _changeSeedType(SeedType item) { _setSeedType(item); _changeLanguage('English'); + widget.seedTypeViewModel.setMoneroSeedType(item); } void _setSeedType(SeedType item) { - setState(() => isPolyseed = item == SeedType.polyseed); seedTypeController.text = item.toString(); } } diff --git a/lib/src/screens/restore/wallet_restore_page.dart b/lib/src/screens/restore/wallet_restore_page.dart index e45316774..4cb50e087 100644 --- a/lib/src/screens/restore/wallet_restore_page.dart +++ b/lib/src/screens/restore/wallet_restore_page.dart @@ -12,6 +12,7 @@ import 'package:cake_wallet/themes/extensions/wallet_list_theme.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/restore/restore_mode.dart'; +import 'package:cake_wallet/view_model/seed_type_view_model.dart'; import 'package:cake_wallet/view_model/wallet_restore_view_model.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; @@ -23,7 +24,7 @@ import 'package:polyseed/polyseed.dart'; import 'package:smooth_page_indicator/smooth_page_indicator.dart'; class WalletRestorePage extends BasePage { - WalletRestorePage(this.walletRestoreViewModel) + WalletRestorePage(this.walletRestoreViewModel, this.seedTypeViewModel) : walletRestoreFromSeedFormKey = GlobalKey(), walletRestoreFromKeysFormKey = GlobalKey(), _pages = [], @@ -33,6 +34,7 @@ class WalletRestorePage extends BasePage { switch (mode) { case WalletRestoreMode.seed: _pages.add(WalletRestoreFromSeedForm( + seedTypeViewModel: seedTypeViewModel, displayBlockHeightSelector: walletRestoreViewModel.hasBlockchainHeightLanguageSelector, displayLanguageSelector: walletRestoreViewModel.hasSeedLanguageSelector, @@ -87,6 +89,7 @@ class WalletRestorePage extends BasePage { )); final WalletRestoreViewModel walletRestoreViewModel; + final SeedTypeViewModel seedTypeViewModel; final PageController _controller; final List _pages; final GlobalKey walletRestoreFromSeedFormKey; @@ -207,7 +210,7 @@ class WalletRestorePage extends BasePage { Navigator.of(context).pushNamed(Routes.advancedPrivacySettings, arguments: walletRestoreViewModel.type); }, - child: Text(S.of(context).advanced_privacy_settings), + child: Text(S.of(context).advanced_settings), ), ], ), diff --git a/lib/src/screens/root/root.dart b/lib/src/screens/root/root.dart index ca86cdccc..5f7fcd205 100644 --- a/lib/src/screens/root/root.dart +++ b/lib/src/screens/root/root.dart @@ -2,9 +2,9 @@ import 'dart:async'; import 'package:cake_wallet/core/auth_service.dart'; import 'package:cake_wallet/core/totp_request_details.dart'; import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/utils/payment_request.dart'; -import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/auth/auth_page.dart'; @@ -169,17 +169,17 @@ class RootState extends State with WidgetsBindingObserver { ); launchUri = null; } else if (isWalletConnectLink) { - if (widget.appStore.wallet!.type == WalletType.ethereum) { + if (isEVMCompatibleChain(widget.appStore.wallet!.type)) { widget.navigatorKey.currentState?.pushNamed( Routes.walletConnectConnectionsListing, arguments: launchUri, ); launchUri = null; } else { - _nonETHWalletErrorToast(S.current.switchToETHWallet); + _nonETHWalletErrorToast(S.current.switchToEVMCompatibleWallet); } } - + launchUri = null; return WillPopScope( onWillPop: () async => false, @@ -205,8 +205,8 @@ class RootState extends State with WidgetsBindingObserver { String? _getRouteToGo() { if (isWalletConnectLink) { - if (widget.appStore.wallet!.type != WalletType.ethereum) { - _nonETHWalletErrorToast(S.current.switchToETHWallet); + if (isEVMCompatibleChain(widget.appStore.wallet!.type)) { + _nonETHWalletErrorToast(S.current.switchToEVMCompatibleWallet); return null; } return Routes.walletConnectConnectionsListing; diff --git a/lib/src/screens/seed/pre_seed_page.dart b/lib/src/screens/seed/pre_seed_page.dart index 947099983..730dfa5f8 100644 --- a/lib/src/screens/seed/pre_seed_page.dart +++ b/lib/src/screens/seed/pre_seed_page.dart @@ -1,87 +1,27 @@ -import 'package:cake_wallet/utils/responsive_layout_util.dart'; -import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; -import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart'; -import 'package:cw_core/wallet_type.dart'; -import 'package:cake_wallet/src/screens/base_page.dart'; -import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; -import 'package:cake_wallet/themes/theme_base.dart'; -import 'package:flutter/material.dart'; +import 'package:cake_wallet/src/screens/InfoPage.dart'; +import 'package:flutter/cupertino.dart'; -class PreSeedPage extends BasePage { - PreSeedPage(this.type, this.advancedPrivacySettingsViewModel) - : imageLight = Image.asset('assets/images/pre_seed_light.png'), - imageDark = Image.asset('assets/images/pre_seed_dark.png'), - seedPhraseLength = advancedPrivacySettingsViewModel.seedPhraseLength.value { - wordsCount = _wordsCount(type, seedPhraseLength); - } +class PreSeedPage extends InfoPage { + PreSeedPage(this.seedPhraseLength); - final Image imageDark; - final Image imageLight; - final WalletType type; - final AdvancedPrivacySettingsViewModel advancedPrivacySettingsViewModel; final int seedPhraseLength; - late final int wordsCount; @override - Widget? leading(BuildContext context) => null; + bool get onWillPop => false; @override - String? get title => S.current.pre_seed_title; + String get pageTitle => S.current.pre_seed_title; @override - Widget body(BuildContext context) { - final image = currentTheme.type == ThemeType.dark ? imageDark : imageLight; + String get pageDescription => + S.current.pre_seed_description(seedPhraseLength.toString()); - return WillPopScope( - onWillPop: () async => false, - child: Container( - alignment: Alignment.center, - padding: EdgeInsets.all(24), - child: ConstrainedBox( - constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - ConstrainedBox( - constraints: BoxConstraints( - maxHeight: MediaQuery.of(context).size.height * 0.3 - ), - child: AspectRatio(aspectRatio: 1, child: image), - ), - Padding( - padding: EdgeInsets.all(10), - child: Text( - S.of(context).pre_seed_description(wordsCount.toString()), - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.normal, - color: Theme.of(context).extension()!.secondaryTextColor), - ), - ), - PrimaryButton( - onPressed: () => - Navigator.of(context).popAndPushNamed(Routes.seed, arguments: true), - text: S.of(context).pre_seed_button_text, - color: Theme.of(context).primaryColor, - textColor: Colors.white) - ], - ), - ), - )); - } + @override + String get buttonText => S.current.pre_seed_button_text; - static int _wordsCount(WalletType type, int seedPhraseLength) { - switch (type) { - case WalletType.monero: - return 25; - case WalletType.ethereum: - case WalletType.bitcoinCash: - return seedPhraseLength; - default: - return 24; - } - } + @override + void Function(BuildContext) get onPressed => (BuildContext context) => + Navigator.of(context).popAndPushNamed(Routes.seed, arguments: true); } diff --git a/lib/src/screens/settings/connection_sync_page.dart b/lib/src/screens/settings/connection_sync_page.dart index 50881dd57..bec2e6296 100644 --- a/lib/src/screens/settings/connection_sync_page.dart +++ b/lib/src/screens/settings/connection_sync_page.dart @@ -1,4 +1,4 @@ -import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart'; +import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart'; import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart'; import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart'; @@ -8,7 +8,6 @@ import 'package:cake_wallet/utils/feature_flag.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:cake_wallet/view_model/settings/sync_mode.dart'; -import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/generated/i18n.dart'; @@ -18,12 +17,11 @@ import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; class ConnectionSyncPage extends BasePage { - ConnectionSyncPage(this.dashboardViewModel, this.web3walletService); + ConnectionSyncPage(this.dashboardViewModel); @override String get title => S.current.connection_sync; - final Web3WalletService? web3walletService; final DashboardViewModel dashboardViewModel; @override @@ -85,7 +83,7 @@ class ConnectionSyncPage extends BasePage { ); }, ), - if (dashboardViewModel.wallet.type == WalletType.ethereum) ...[ + if (isEVMCompatibleChain(dashboardViewModel.wallet.type)) ...[ WalletConnectTile( onTap: () => Navigator.of(context).pushNamed(Routes.walletConnectConnectionsListing), ), @@ -101,6 +99,7 @@ class ConnectionSyncPage extends BasePage { ); } + Future _presentReconnectAlert(BuildContext context) async { await showPopUp( context: context, diff --git a/lib/src/screens/settings/display_settings_page.dart b/lib/src/screens/settings/display_settings_page.dart index 505573f80..ba59df3af 100644 --- a/lib/src/screens/settings/display_settings_page.dart +++ b/lib/src/screens/settings/display_settings_page.dart @@ -76,7 +76,7 @@ class DisplaySettingsPage extends BasePage { }, ), if (responsiveLayoutUtil.shouldRenderMobileUI && DeviceInfo.instance.isMobile) - SettingsThemeChoicesCell(_displaySettingsViewModel), + Semantics(label: S.current.color_theme, child: SettingsThemeChoicesCell(_displaySettingsViewModel)), ], ), ); diff --git a/lib/src/screens/settings/other_settings_page.dart b/lib/src/screens/settings/other_settings_page.dart index ede816893..c58523185 100644 --- a/lib/src/screens/settings/other_settings_page.dart +++ b/lib/src/screens/settings/other_settings_page.dart @@ -1,5 +1,3 @@ -import 'package:cake_wallet/buy/buy_provider.dart'; -import 'package:cake_wallet/entities/buy_provider_types.dart'; import 'package:cake_wallet/entities/priority_for_wallet_type.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/routes.dart'; @@ -42,12 +40,21 @@ class OtherSettingsPage extends BasePage { handler: (BuildContext context) => Navigator.of(context).pushNamed(Routes.changeRep), ), + if(_otherSettingsViewModel.isEnabledBuyAction) SettingsPickerCell( title: S.current.default_buy_provider, - items: BuyProviderType.values, + items: _otherSettingsViewModel.availableBuyProvidersTypes, displayItem: _otherSettingsViewModel.getBuyProviderType, selectedItem: _otherSettingsViewModel.buyProviderType, - onItemSelected: _otherSettingsViewModel.onBuyProviderTypeSelected, + onItemSelected: _otherSettingsViewModel.onBuyProviderTypeSelected + ), + if(_otherSettingsViewModel.isEnabledSellAction) + SettingsPickerCell( + title: S.current.default_sell_provider, + items: _otherSettingsViewModel.availableSellProvidersTypes, + displayItem: _otherSettingsViewModel.getSellProviderType, + selectedItem: _otherSettingsViewModel.sellProviderType, + onItemSelected: _otherSettingsViewModel.onSellProviderTypeSelected, ), SettingsCellWithArrow( title: S.current.settings_terms_and_conditions, diff --git a/lib/src/screens/settings/privacy_page.dart b/lib/src/screens/settings/privacy_page.dart index b1927648a..dcc41254e 100644 --- a/lib/src/screens/settings/privacy_page.dart +++ b/lib/src/screens/settings/privacy_page.dart @@ -87,6 +87,14 @@ class PrivacyPage extends BasePage { onValueChange: (BuildContext _, bool value) { _privacySettingsViewModel.setUseEtherscan(value); }), + if (_privacySettingsViewModel.canUsePolygonScan) + SettingsSwitcherCell( + title: S.current.polygonscan_history, + value: _privacySettingsViewModel.usePolygonScan, + onValueChange: (BuildContext _, bool value) { + _privacySettingsViewModel.setUsePolygonScan(value); + }, + ), SettingsCellWithArrow( title: S.current.domain_looks_up, handler: (context) => Navigator.of(context).pushNamed(Routes.domainLookupsPage), diff --git a/lib/src/screens/settings/security_backup_page.dart b/lib/src/screens/settings/security_backup_page.dart index a0fb16cb6..56ba049ba 100644 --- a/lib/src/screens/settings/security_backup_page.dart +++ b/lib/src/screens/settings/security_backup_page.dart @@ -108,7 +108,7 @@ class SecurityBackupPage extends BasePage { context, route: _securitySettingsViewModel.useTotp2FA ? Routes.modify2FAPage - : Routes.setup_2faPage, + : Routes.setup2faInfoPage, conditionToDetermineIfToUse2FA: _securitySettingsViewModel .shouldRequireTOTP2FAForAllSecurityAndBackupSettings, ), diff --git a/lib/src/screens/settings/tor_page.dart b/lib/src/screens/settings/tor_page.dart index d1d5c7e83..ae1ef1677 100644 --- a/lib/src/screens/settings/tor_page.dart +++ b/lib/src/screens/settings/tor_page.dart @@ -4,7 +4,7 @@ import 'dart:io'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/store/app_store.dart'; import 'package:flutter/material.dart'; -import 'package:tor/tor.dart'; +// import 'package:tor/tor.dart'; class TorPage extends BasePage { final AppStore appStore; @@ -40,44 +40,44 @@ class _TorPageBodyState extends State { connecting = true; // Update flag }); - await Tor.init(); - - // Start the proxy - await Tor.instance.start(); - - // Toggle started flag. - setState(() { - torEnabled = Tor.instance.enabled; // Update flag - connecting = false; - }); - - final node = widget.appStore.settingsStore.getCurrentNode(widget.appStore.wallet!.type); - if (node.socksProxyAddress?.isEmpty ?? true) { - node.socksProxyAddress = "${InternetAddress.loopbackIPv4.address}:${Tor.instance.port}"; - } - widget.appStore.wallet!.connectToNode(node: node); + // await Tor.init(); + // + // // Start the proxy + // await Tor.instance.start(); + // + // // Toggle started flag. + // setState(() { + // torEnabled = Tor.instance.enabled; // Update flag + // connecting = false; + // }); + // + // final node = widget.appStore.settingsStore.getCurrentNode(widget.appStore.wallet!.type); + // if (node.socksProxyAddress?.isEmpty ?? true) { + // node.socksProxyAddress = "${InternetAddress.loopbackIPv4.address}:${Tor.instance.port}"; + // } + // widget.appStore.wallet!.connectToNode(node: node); print('Done awaiting; tor should be running'); } Future endTor() async { - // Start the proxy - Tor.instance.disable(); - - // Toggle started flag. - setState(() { - torEnabled = Tor.instance.enabled; // Update flag - }); - - print('Done awaiting; tor should be stopped'); - } - - @override - void initState() { - super.initState(); - - torEnabled = Tor.instance.enabled; + // // Start the proxy + // Tor.instance.disable(); + // + // // Toggle started flag. + // setState(() { + // torEnabled = Tor.instance.enabled; // Update flag + // }); + // + // print('Done awaiting; tor should be stopped'); } + // + // @override + // void initState() { + // super.initState(); + // + // torEnabled = Tor.instance.enabled; + // } @override void dispose() { diff --git a/lib/src/screens/settings/widgets/settings_choices_cell.dart b/lib/src/screens/settings/widgets/settings_choices_cell.dart index aea5ecbb9..63ad1ef9a 100644 --- a/lib/src/screens/settings/widgets/settings_choices_cell.dart +++ b/lib/src/screens/settings/widgets/settings_choices_cell.dart @@ -17,56 +17,57 @@ class SettingsChoicesCell extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - children: [ - Text( - choicesListItem.title, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.normal, - color: Theme.of(context).extension()!.titleColor, + if (choicesListItem.title.isNotEmpty) ...[ + Row( + children: [ + Text( + choicesListItem.title, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.normal, + color: Theme.of(context).extension()!.titleColor, + ), ), - ), - ], - ), - const SizedBox(height: 24), + ], + ), + const SizedBox(height: 24), + ], Center( - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(30), - color: Theme.of(context).extension()!.actionButtonColor, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: choicesListItem.items.map((dynamic e) { - final isSelected = choicesListItem.selectedItem == e; - return GestureDetector( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30), + color: Theme.of(context).extension()!.actionButtonColor, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: choicesListItem.items.map((dynamic e) { + final isSelected = choicesListItem.selectedItem == e; + return Expanded( + child: GestureDetector( onTap: () { choicesListItem.onItemSelected.call(e); }, child: Container( - padding: EdgeInsets.symmetric(horizontal: 32, vertical: 8), + padding: EdgeInsets.symmetric(vertical: 8), decoration: BoxDecoration( borderRadius: BorderRadius.circular(30), - color: isSelected - ? Theme.of(context).primaryColor - : null, + color: isSelected ? Theme.of(context).primaryColor : null, ), - child: Text( - choicesListItem.displayItem.call(e), - style: TextStyle( - color: isSelected - ? Colors.white - : Theme.of(context).extension()!.secondaryTextColor, - fontWeight: isSelected ? FontWeight.w700 : FontWeight.normal, + child: Center( + child: Text( + choicesListItem.displayItem.call(e), + style: TextStyle( + color: isSelected + ? Colors.white + : Theme.of(context).extension()!.secondaryTextColor, + fontWeight: isSelected ? FontWeight.w700 : FontWeight.normal, + ), ), ), ), - ); - }).toList(), - ), + ), + ); + }).toList(), ), ), ), diff --git a/lib/src/screens/settings/widgets/settings_theme_choice.dart b/lib/src/screens/settings/widgets/settings_theme_choice.dart index ffb9d7e7e..abe9a4eab 100644 --- a/lib/src/screens/settings/widgets/settings_theme_choice.dart +++ b/lib/src/screens/settings/widgets/settings_theme_choice.dart @@ -54,57 +54,61 @@ class SettingsThemeChoicesCell extends StatelessWidget { return Padding( padding: EdgeInsets.all(5), - child: GestureDetector( - onTap: () { - _displaySettingsViewModel.setTheme(e); - }, - child: Container( - padding: EdgeInsets.all(5), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(cellRadius), - border: isSelected - ? Border.all( - color: Theme.of(context).primaryColor) - : null, - color: Theme.of(context) - .extension()! - .secondaryTextColor - .withOpacity( - currentTheme.brightness == Brightness.light - ? 0.1 - : 0.3), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - padding: EdgeInsets.symmetric( - horizontal: cellWidth, vertical: cellHeight), - decoration: BoxDecoration( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(cellRadius), - bottomLeft: Radius.circular(cellRadius)), - color: e.themeData.primaryColor, + child: Semantics( + label: e.toString(), + selected: isSelected, + child: GestureDetector( + onTap: () { + _displaySettingsViewModel.setTheme(e); + }, + child: Container( + padding: EdgeInsets.all(5), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(cellRadius), + border: isSelected + ? Border.all( + color: Theme.of(context).primaryColor) + : null, + color: Theme.of(context) + .extension()! + .secondaryTextColor + .withOpacity( + currentTheme.brightness == Brightness.light + ? 0.1 + : 0.3), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.symmetric( + horizontal: cellWidth, vertical: cellHeight), + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(cellRadius), + bottomLeft: Radius.circular(cellRadius)), + color: e.themeData.primaryColor, + ), ), - ), - Container( - padding: EdgeInsets.symmetric( - horizontal: cellWidth, vertical: cellHeight), - decoration: BoxDecoration( - color: e.themeData.colorScheme.background, + Container( + padding: EdgeInsets.symmetric( + horizontal: cellWidth, vertical: cellHeight), + decoration: BoxDecoration( + color: e.themeData.colorScheme.background, + ), ), - ), - Container( - padding: EdgeInsets.symmetric( - horizontal: cellWidth, vertical: cellHeight), - decoration: BoxDecoration( - borderRadius: BorderRadius.only( - topRight: Radius.circular(cellRadius), - bottomRight: Radius.circular(cellRadius)), - color: e.themeData.cardColor, + Container( + padding: EdgeInsets.symmetric( + horizontal: cellWidth, vertical: cellHeight), + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + topRight: Radius.circular(cellRadius), + bottomRight: Radius.circular(cellRadius)), + color: e.themeData.cardColor, + ), ), - ), - ], + ], + ), ), ), ), diff --git a/lib/src/screens/setup_2fa/setup_2fa.dart b/lib/src/screens/setup_2fa/setup_2fa.dart index 895fbb9c0..5fa5cabb8 100644 --- a/lib/src/screens/setup_2fa/setup_2fa.dart +++ b/lib/src/screens/setup_2fa/setup_2fa.dart @@ -1,13 +1,12 @@ import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; -import 'package:flutter/material.dart'; - import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart'; +import 'package:cake_wallet/src/widgets/standard_list.dart'; +import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart'; - -import '../../widgets/standard_list.dart'; +import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher.dart'; class Setup2FAPage extends BasePage { Setup2FAPage({required this.setup2FAViewModel}); @@ -15,52 +14,64 @@ class Setup2FAPage extends BasePage { final Setup2FAViewModel setup2FAViewModel; @override - String get title => S.current.setup_2fa; + String get title => 'Cake 2FA'; @override Widget body(BuildContext context) { - return SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( + final cake2FAGuideTitle = 'Cake 2FA Guide'; + final cake2FAGuideUri = + Uri.parse('https://guides.cakewallet.com/docs/advanced-features/authentication'); + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + flex: 2, + child: ConstrainedBox( + constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.3), + child: + AspectRatio(aspectRatio: 0.6, child: Image.asset('assets/images/setup_2fa_img.png')), + ), + ), + Expanded( + flex: 2, + child: Padding( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - S.current.important_note, - style: TextStyle( - fontWeight: FontWeight.w700, - fontSize: 14, - height: 1.571, - color: Theme.of(context).extension()!.titleColor, - ), - ), - SizedBox(height: 16), - Text( - S.current.setup_2fa_text, - style: TextStyle( - fontWeight: FontWeight.w400, - fontSize: 14, - height: 1.571, - color: Theme.of(context).extension()!.titleColor, - ), - ), - ], + child: Text( + S.current.setup_2fa_text, + textAlign: TextAlign.center, + style: TextStyle( + fontWeight: FontWeight.w400, + fontSize: 14, + height: 1.571, + color: Theme.of(context).extension()!.titleColor, + ), ), ), - SizedBox(height: 86), - SettingsCellWithArrow( - title: S.current.setup_totp_recommended, - handler: (_) { - setup2FAViewModel.generateSecretKey(); - return Navigator.of(context).pushReplacementNamed(Routes.setup_2faQRPage); - }, + ), + Expanded( + child: Column( + children: [ + SettingsCellWithArrow( + title: S.current.setup_totp_recommended, + handler: (_) { + setup2FAViewModel.generateSecretKey(); + return Navigator.of(context).pushReplacementNamed(Routes.setup_2faQRPage); + }, + ), + StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)), + SettingsCellWithArrow( + title: cake2FAGuideTitle, handler: (_) => _launchUrl(cake2FAGuideUri)), + StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)), + ], ), - StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)), - ], - ), + ), + ], ); } + + static void _launchUrl(Uri url) async { + try { + await launchUrl(url, mode: LaunchMode.externalApplication); + } catch (e) {} + } } diff --git a/lib/src/screens/setup_2fa/setup_2fa_info_page.dart b/lib/src/screens/setup_2fa/setup_2fa_info_page.dart new file mode 100644 index 000000000..ff6187665 --- /dev/null +++ b/lib/src/screens/setup_2fa/setup_2fa_info_page.dart @@ -0,0 +1,20 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/screens/InfoPage.dart'; +import 'package:flutter/cupertino.dart'; + +class Setup2FAInfoPage extends InfoPage { + + @override + String get pageTitle => S.current.pre_seed_title; + + @override + String get pageDescription => S.current.setup_warning_2fa_text; + + @override + String get buttonText => S.current.understand; + + @override + void Function(BuildContext) get onPressed => (BuildContext context) => + Navigator.of(context).popAndPushNamed(Routes.setup_2faPage); +} diff --git a/lib/src/screens/setup_2fa/setup_2fa_qr_page.dart b/lib/src/screens/setup_2fa/setup_2fa_qr_page.dart index 43dbab05f..3ce10b596 100644 --- a/lib/src/screens/setup_2fa/setup_2fa_qr_page.dart +++ b/lib/src/screens/setup_2fa/setup_2fa_qr_page.dart @@ -4,8 +4,8 @@ import 'package:cake_wallet/src/screens/receive/widgets/qr_image.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/src/widgets/standard_list.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/themes/extensions/dashboard_page_theme.dart'; import 'package:cake_wallet/utils/clipboard_util.dart'; import 'package:cake_wallet/utils/show_bar.dart'; @@ -13,6 +13,7 @@ import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:qr_flutter/qr_flutter.dart' as qr; +import 'package:url_launcher/url_launcher.dart'; class Setup2FAQRPage extends BasePage { Setup2FAQRPage({required this.setup2FAViewModel}); @@ -24,51 +25,63 @@ class Setup2FAQRPage extends BasePage { @override Widget body(BuildContext context) { - final copyImage = Image.asset( - 'assets/images/copy_content.png', - height: 12, - width: 12, - color: Color(0xFF355688), - ); + final copyImage = Image.asset('assets/images/copy_content.png', + height: 16, + width: 16, + color: Theme.of(context).extension()!.titleColor); + final cake2FAHowToUseUrl = Uri.parse( + 'https://guides.cakewallet.com/docs/advanced-features/authentication/#enabling-cake-2fa'); return Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: Column( children: [ - SizedBox(height: 58), + Spacer(), + Text( + S.current.scan_qr_on_device, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w700, + height: 1.5714, + color: Theme.of(context).extension()!.titleColor, + ), + ), + SizedBox(height: 10), + ConstrainedBox( + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height * 0.4), + child: AspectRatio( + aspectRatio: 1.0, + child: Container( + padding: EdgeInsets.all(5), + decoration: BoxDecoration( + border: Border.all( + width: 3, + color: Theme.of(context) + .extension()! + .titleColor, + ), + ), + child: Container( + child: QrImage( + data: setup2FAViewModel.totpVersionOneLink, + version: qr.QrVersions.auto, + foregroundColor: + Theme.of(context).extension()!.titleColor, + backgroundColor: Colors.transparent, + )), + ), + ), + ), + SizedBox(height: 26), Text( S.current.add_secret_code, style: TextStyle( fontSize: 14, fontWeight: FontWeight.w700, height: 1.5714, - color: Palette.darkBlueCraiola, + color: Theme.of(context).extension()!.titleColor, ), ), - SizedBox(height: 10), - AspectRatio( - aspectRatio: 1.0, - child: Container( - padding: EdgeInsets.all(5), - decoration: BoxDecoration( - border: Border.all( - width: 3, - color: Theme.of(context).extension()!.textColor, - ), - ), - child: Container( - decoration: BoxDecoration( - border: Border.all( - width: 3, - color: Colors.white, - ), - ), - child: QrImage( - data: setup2FAViewModel.totpVersionOneLink, - version: qr.QrVersions.auto, - )), - ), - ), - SizedBox(height: 13), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, @@ -83,7 +96,9 @@ class Setup2FAQRPage extends BasePage { style: TextStyle( fontSize: 12, fontWeight: FontWeight.w500, - color: Palette.darkGray, + color: Theme.of(context) + .extension()! + .secondaryTextColor, height: 1.8333, ), ), @@ -91,10 +106,10 @@ class Setup2FAQRPage extends BasePage { Text( '${setup2FAViewModel.totpSecretKey}', style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w700, - height: 1.375, - ), + fontSize: 18, + color: Theme.of(context) + .extension()! + .titleColor), maxLines: 1, overflow: TextOverflow.ellipsis, ), @@ -107,15 +122,11 @@ class Setup2FAQRPage extends BasePage { height: 32, child: InkWell( onTap: () { - ClipboardUtil.setSensitiveDataToClipboard( - ClipboardData(text: '${setup2FAViewModel.totpSecretKey}')); + ClipboardUtil.setSensitiveDataToClipboard(ClipboardData( + text: '${setup2FAViewModel.totpSecretKey}')); showBar(context, S.of(context).copied_to_clipboard); }, child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(6), - color: Color(0xFFF2F0FA), - ), child: copyImage, ), ), @@ -139,7 +150,9 @@ class Setup2FAQRPage extends BasePage { style: TextStyle( fontSize: 12, fontWeight: FontWeight.w500, - color: Palette.darkGray, + color: Theme.of(context) + .extension()! + .secondaryTextColor, height: 1.8333, ), ), @@ -147,10 +160,10 @@ class Setup2FAQRPage extends BasePage { Text( '${setup2FAViewModel.totpVersionOneLink}', style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w700, - height: 1.375, - ), + fontSize: 18, + color: Theme.of(context) + .extension()! + .titleColor), maxLines: 1, overflow: TextOverflow.ellipsis, ), @@ -163,15 +176,11 @@ class Setup2FAQRPage extends BasePage { height: 32, child: InkWell( onTap: () { - ClipboardUtil.setSensitiveDataToClipboard( - ClipboardData(text: '${setup2FAViewModel.totpVersionOneLink}')); + ClipboardUtil.setSensitiveDataToClipboard(ClipboardData( + text: '${setup2FAViewModel.totpVersionOneLink}')); showBar(context, S.of(context).copied_to_clipboard); }, child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(6), - color: Color(0xFFF2F0FA), - ), child: copyImage, ), ), @@ -180,13 +189,36 @@ class Setup2FAQRPage extends BasePage { ), SizedBox(height: 8), StandardListSeparator(), - Spacer(), + SizedBox(height: 16), + GestureDetector( + onTap: () => _launchUrl(cake2FAHowToUseUrl), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(S.current.how_to_use, + style: TextStyle( + decoration: TextDecoration.underline, + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context) + .extension()! + .titleColor)), + Icon(Icons.info_outline, + size: 20, + color: Theme.of(context) + .extension()! + .titleColor) + ], + )), + Spacer(flex: 5), PrimaryButton( onPressed: () { - Navigator.of(context).pushReplacementNamed(Routes.totpAuthCodePage, - arguments: TotpAuthArgumentsModel( - isForSetup: true, - )); + Navigator.of(context) + .pushReplacementNamed(Routes.totpAuthCodePage, + arguments: TotpAuthArgumentsModel( + isForSetup: true, + )); }, text: S.current.continue_text, color: Theme.of(context).primaryColor, @@ -197,4 +229,10 @@ class Setup2FAQRPage extends BasePage { ), ); } + + static void _launchUrl(Uri url) async { + try { + await launchUrl(url, mode: LaunchMode.externalApplication); + } catch (e) {} + } } diff --git a/lib/src/screens/wallet_connect/widgets/connection_request_widget.dart b/lib/src/screens/wallet_connect/widgets/connection_request_widget.dart index c73c4bfa8..179bd6b93 100644 --- a/lib/src/screens/wallet_connect/widgets/connection_request_widget.dart +++ b/lib/src/screens/wallet_connect/widgets/connection_request_widget.dart @@ -14,12 +14,14 @@ import 'connection_widget.dart'; class ConnectionRequestWidget extends StatefulWidget { const ConnectionRequestWidget({ required this.wallet, + required this.chaindIdNamespace, this.authRequest, this.sessionProposal, Key? key, }) : super(key: key); final Web3Wallet wallet; + final String chaindIdNamespace; final AuthRequestModel? authRequest; final SessionRequestModel? sessionProposal; @@ -52,23 +54,26 @@ class _ConnectionRequestWidgetState extends State { return _ConnectionMetadataDisplayWidget( metadata: metadata, + wallet: widget.wallet, authRequest: widget.authRequest, sessionProposal: widget.sessionProposal, - wallet: widget.wallet, + chaindIdNamespace: widget.chaindIdNamespace, ); } } class _ConnectionMetadataDisplayWidget extends StatelessWidget { const _ConnectionMetadataDisplayWidget({ - required this.metadata, required this.wallet, - this.authRequest, + required this.metadata, required this.sessionProposal, + required this.chaindIdNamespace, + this.authRequest, }); final ConnectionMetadata? metadata; final Web3Wallet wallet; + final String chaindIdNamespace; final AuthRequestModel? authRequest; final SessionRequestModel? sessionProposal; @@ -114,7 +119,11 @@ class _ConnectionMetadataDisplayWidget extends StatelessWidget { const SizedBox(height: 8), Visibility( visible: authRequest != null, - child: _AuthRequestWidget(wallet: wallet, authRequest: authRequest), + child: _AuthRequestWidget( + wallet: wallet, + authRequest: authRequest, + chaindIdNamespace: chaindIdNamespace, + ), //If authRequest is null, sessionProposal is not null. replacement: _SessionProposalWidget(sessionProposal: sessionProposal!), @@ -126,16 +135,21 @@ class _ConnectionMetadataDisplayWidget extends StatelessWidget { } class _AuthRequestWidget extends StatelessWidget { - const _AuthRequestWidget({required this.wallet, this.authRequest}); + const _AuthRequestWidget({ + required this.wallet, + required this.chaindIdNamespace, + this.authRequest, + }); final Web3Wallet wallet; + final String chaindIdNamespace; final AuthRequestModel? authRequest; @override Widget build(BuildContext context) { final model = ConnectionModel( text: wallet.formatAuthMessage( - iss: 'did:pkh:eip155:1:${authRequest!.iss}', + iss: 'did:pkh:$chaindIdNamespace:${authRequest!.iss}', cacaoPayload: CacaoRequestPayload.fromPayloadParams( authRequest!.request.payloadParams, ), diff --git a/lib/src/screens/wallet_list/filtered_list.dart b/lib/src/screens/wallet_list/filtered_list.dart new file mode 100644 index 000000000..7149833a8 --- /dev/null +++ b/lib/src/screens/wallet_list/filtered_list.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:mobx/mobx.dart'; + +class FilteredList extends StatefulWidget { + FilteredList({ + required this.list, + required this.itemBuilder, + required this.updateFunction, + }); + + final ObservableList list; + final Widget Function(BuildContext, int) itemBuilder; + final Function updateFunction; + + @override + FilteredListState createState() => FilteredListState(); +} + +class FilteredListState extends State { + @override + Widget build(BuildContext context) { + return Observer( + builder: (_) => ReorderableListView.builder( + physics: const BouncingScrollPhysics(), + itemBuilder: widget.itemBuilder, + itemCount: widget.list.length, + onReorder: (int oldIndex, int newIndex) { + if (oldIndex < newIndex) { + newIndex -= 1; + } + final dynamic item = widget.list.removeAt(oldIndex); + widget.list.insert(newIndex, item); + widget.updateFunction(); + }, + ), + ); + } +} diff --git a/lib/src/screens/wallet_list/wallet_list_page.dart b/lib/src/screens/wallet_list/wallet_list_page.dart index da5df874a..717bb0a94 100644 --- a/lib/src/screens/wallet_list/wallet_list_page.dart +++ b/lib/src/screens/wallet_list/wallet_list_page.dart @@ -1,8 +1,14 @@ +import 'package:cake_wallet/entities/wallet_list_order_types.dart'; +import 'package:cake_wallet/src/screens/dashboard/widgets/filter_list_widget.dart'; +import 'package:cake_wallet/src/screens/wallet_list/filtered_list.dart'; import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:cake_wallet/core/auth_service.dart'; +import 'package:cake_wallet/themes/extensions/filter_theme.dart'; import 'package:cake_wallet/themes/extensions/receive_page_theme.dart'; +import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/show_bar.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart'; import 'package:another_flushbar/flushbar.dart'; import 'package:flutter/material.dart'; @@ -28,6 +34,53 @@ class WalletListPage extends BasePage { @override Widget body(BuildContext context) => WalletListBody(walletListViewModel: walletListViewModel, authService: authService); + + @override + Widget trailing(BuildContext context) { + final filterIcon = Image.asset('assets/images/filter_icon.png', + color: Theme.of(context).extension()!.iconColor); + return MergeSemantics( + child: SizedBox( + height: 37, + width: 37, + child: ButtonTheme( + minWidth: double.minPositive, + child: Semantics( + container: true, + child: GestureDetector( + onTap: () async { + await showPopUp( + context: context, + builder: (context) => FilterListWidget( + initalType: walletListViewModel.orderType, + initalAscending: walletListViewModel.ascending, + onClose: (bool ascending, WalletListOrderType type) async { + walletListViewModel.setAscending(ascending); + await walletListViewModel.setOrderType(type); + }, + ), + ); + }, + child: Semantics( + label: 'Transaction Filter', + button: true, + enabled: true, + child: Container( + height: 36, + width: 36, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Theme.of(context).extension()!.buttonColor, + ), + child: filterIcon, + ), + ), + ), + ), + ), + ), + ); + } } class WalletListBody extends StatefulWidget { @@ -49,6 +102,7 @@ class WalletListBodyState extends State { final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24); final bitcoinCashIcon = Image.asset('assets/images/bch_icon.png', height: 24, width: 24); final nanoIcon = Image.asset('assets/images/nano_icon.png', height: 24, width: 24); + final polygonIcon = Image.asset('assets/images/matic_icon.png', height: 24, width: 24); final scrollController = ScrollController(); final double tileHeight = 60; Flushbar? _progressBar; @@ -69,11 +123,9 @@ class WalletListBodyState extends State { Expanded( child: Container( child: Observer( - builder: (_) => ListView.separated( - physics: const BouncingScrollPhysics(), - separatorBuilder: (_, index) => - Divider(color: Theme.of(context).colorScheme.background, height: 32), - itemCount: widget.walletListViewModel.wallets.length, + builder: (_) => FilteredList( + list: widget.walletListViewModel.wallets, + updateFunction: widget.walletListViewModel.reorderAccordingToWalletList, itemBuilder: (__, index) { final wallet = widget.walletListViewModel.wallets[index]; final currentColor = wallet.isCurrent @@ -82,6 +134,7 @@ class WalletListBodyState extends State { .createNewWalletButtonBackgroundColor : Theme.of(context).colorScheme.background; final row = GestureDetector( + key: ValueKey(wallet.name), onTap: () => wallet.isCurrent ? null : _loadWallet(wallet), child: Container( height: tileHeight, @@ -116,7 +169,7 @@ class WalletListBodyState extends State { maxLines: null, softWrap: true, style: TextStyle( - fontSize: 22, + fontSize: 20, fontWeight: FontWeight.w500, color: Theme.of(context) .extension()! @@ -136,13 +189,15 @@ class WalletListBodyState extends State { return wallet.isCurrent ? row : Row( + key: ValueKey(wallet.name), children: [ Expanded(child: row), GestureDetector( onTap: () => Navigator.of(context).pushNamed(Routes.walletEdit, arguments: [widget.walletListViewModel, wallet]), child: Container( - padding: EdgeInsets.only(right: 20), + padding: EdgeInsets.only( + right: DeviceInfo.instance.isMobile ? 20 : 40), child: Center( child: Container( height: 40, @@ -256,6 +311,8 @@ class WalletListBodyState extends State { return bitcoinCashIcon; case WalletType.nano: return nanoIcon; + case WalletType.polygon: + return polygonIcon; default: return nonWalletTypeIcon; } @@ -277,7 +334,9 @@ class WalletListBodyState extends State { // in desktop platforms the navigation tree is different if (responsiveLayoutUtil.shouldRenderMobileUI) { WidgetsBinding.instance.addPostFrameCallback((_) { - Navigator.of(context).pop(); + if (this.mounted) { + Navigator.of(context).pop(); + } }); } } catch (e) { diff --git a/lib/src/widgets/picker_wrapper_widget.dart b/lib/src/widgets/picker_wrapper_widget.dart index f69bcd514..f4e52c5cd 100644 --- a/lib/src/widgets/picker_wrapper_widget.dart +++ b/lib/src/widgets/picker_wrapper_widget.dart @@ -4,10 +4,11 @@ import 'package:cake_wallet/src/widgets/alert_background.dart'; import 'package:cake_wallet/src/widgets/alert_close_button.dart'; class PickerWrapperWidget extends StatelessWidget { - PickerWrapperWidget({required this.children, this.hasTitle = false}); + PickerWrapperWidget({required this.children, this.hasTitle = false, this.onClose}); final List children; final bool hasTitle; + final Function()? onClose; @override Widget build(BuildContext context) { @@ -45,7 +46,7 @@ class PickerWrapperWidget extends StatelessWidget { children: children, ), SizedBox(height: ResponsiveLayoutUtilBase.kPopupSpaceHeight), - AlertCloseButton(bottom: closeButtonBottom), + AlertCloseButton(bottom: closeButtonBottom, onTap: onClose), ], ), ), diff --git a/lib/src/widgets/seed_language_selector.dart b/lib/src/widgets/seed_language_selector.dart index 07638bb2f..dde78c58c 100644 --- a/lib/src/widgets/seed_language_selector.dart +++ b/lib/src/widgets/seed_language_selector.dart @@ -1,19 +1,20 @@ import 'package:cake_wallet/entities/seed_type.dart'; +import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart'; import 'package:cake_wallet/src/widgets/seed_language_picker.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:flutter/material.dart'; class SeedLanguageSelector extends StatefulWidget { - SeedLanguageSelector({Key? key, required this.initialSelected, this.seedType = SeedType.defaultSeedType}) + SeedLanguageSelector( + {Key? key, required this.initialSelected, this.seedType = SeedType.defaultSeedType}) : super(key: key); final String initialSelected; final SeedType seedType; @override - SeedLanguageSelectorState createState() => - SeedLanguageSelectorState(selected: initialSelected); + SeedLanguageSelectorState createState() => SeedLanguageSelectorState(selected: initialSelected); } class SeedLanguageSelectorState extends State { @@ -25,15 +26,17 @@ class SeedLanguageSelectorState extends State { Widget build(BuildContext context) { return SelectButton( image: null, - text: seedLanguages.firstWhere((e) => e.name == selected).nameLocalized, + text: + "${seedLanguages.firstWhere((e) => e.name == selected).nameLocalized} (${S.of(context).seed_language})", onTap: () async { await showPopUp( - context: context, - builder: (_) => SeedLanguagePicker( - selected: this.selected, - seedType: widget.seedType, - onItemSelected: (String selected) => - setState(() => this.selected = selected))); + context: context, + builder: (_) => SeedLanguagePicker( + selected: this.selected, + seedType: widget.seedType, + onItemSelected: (String selected) => setState(() => this.selected = selected), + ), + ); }, ); } diff --git a/lib/src/widgets/standard_switch.dart b/lib/src/widgets/standard_switch.dart index 064faea48..b80d487fe 100644 --- a/lib/src/widgets/standard_switch.dart +++ b/lib/src/widgets/standard_switch.dart @@ -14,25 +14,29 @@ class StandardSwitch extends StatefulWidget { class StandardSwitchState extends State { @override Widget build(BuildContext context) { - return GestureDetector( - onTap: widget.onTaped, - child: AnimatedContainer( - padding: EdgeInsets.only(left: 2.0, right: 2.0), - alignment: widget.value ? Alignment.centerRight : Alignment.centerLeft, - duration: Duration(milliseconds: 250), - width: 50, - height: 28, - decoration: BoxDecoration( - color: widget.value - ? Theme.of(context).primaryColor - : Theme.of(context).disabledColor, - borderRadius: BorderRadius.all(Radius.circular(14.0))), - child: Container( - width: 24.0, - height: 24.0, + + return Semantics( + toggled: widget.value, + child: GestureDetector( + onTap: widget.onTaped, + child: AnimatedContainer( + padding: EdgeInsets.only(left: 2.0, right: 2.0), + alignment: widget.value ? Alignment.centerRight : Alignment.centerLeft, + duration: Duration(milliseconds: 250), + width: 50, + height: 28, decoration: BoxDecoration( - color: Colors.white, - shape: BoxShape.circle), + color: widget.value + ? Theme.of(context).primaryColor + : Theme.of(context).disabledColor, + borderRadius: BorderRadius.all(Radius.circular(14.0))), + child: Container( + width: 24.0, + height: 24.0, + decoration: BoxDecoration( + color: Colors.white, + shape: BoxShape.circle), + ), ), ), ); diff --git a/lib/src/widgets/vulnerable_seeds_popup.dart b/lib/src/widgets/vulnerable_seeds_popup.dart new file mode 100644 index 000000000..1fcf474ee --- /dev/null +++ b/lib/src/widgets/vulnerable_seeds_popup.dart @@ -0,0 +1,91 @@ +import 'package:cake_wallet/src/widgets/alert_background.dart'; +import 'package:cake_wallet/src/widgets/alert_close_button.dart'; +import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart'; +import 'package:flutter/material.dart'; + +class VulnerableSeedsPopup extends StatelessWidget { + final List affectedWalletNames; + + const VulnerableSeedsPopup(this.affectedWalletNames, {Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Stack( + alignment: Alignment.center, + children: [ + AlertBackground( + child: AlertDialog( + insetPadding: EdgeInsets.only(left: 16, right: 16, bottom: 48), + elevation: 0.0, + contentPadding: EdgeInsets.zero, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(30))), + content: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30.0), + gradient: LinearGradient(colors: [ + Theme.of(context).extension()!.firstGradientBackgroundColor, + Theme.of(context) + .extension()! + .secondGradientBackgroundColor, + ], begin: Alignment.centerLeft, end: Alignment.centerRight)), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24.0), + child: Stack( + children: [ + SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.only(top: 16.0), + child: Container( + alignment: Alignment.bottomCenter, + child: DefaultTextStyle( + style: TextStyle( + decoration: TextDecoration.none, + fontSize: 24.0, + fontWeight: FontWeight.bold, + fontFamily: 'Lato', + color: Theme.of(context).extension()!.textColor, + ), + child: Text("Emergency Notice"), + ), + ), + ), + ), + SingleChildScrollView( + child: Padding( + padding: EdgeInsets.only(top: 48, bottom: 16), + child: Container( + width: double.maxFinite, + child: Column( + children: [ + ConstrainedBox( + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height * 0.7, + ), + child: Text( + "Your Bitcoin wallet(s) below use a legacy seed format that is vulnerable, which MAY result in you losing money from these wallet(s) if no action is taken.\nWe recommend that you IMMEDIATELY create wallet(s) in Cake Wallet and immediately transfer the funds to these wallet(s).\nVulnerable wallet name(s):\n\n[${affectedWalletNames.join(", ")}]\n\nFor assistance, please use the in-app support or email support@cakewallet.com", + style: TextStyle( + decoration: TextDecoration.none, + fontSize: 16.0, + fontFamily: 'Lato', + color: Theme.of(context) + .extension()! + .textColor, + ), + ), + ) + ], + ), + ), + ), + ), + ], + ), + ), + ), + ), + ), + AlertCloseButton(bottom: 30) + ], + ); + } +} diff --git a/lib/store/app_store.dart b/lib/store/app_store.dart index e814ff44b..a5a2b95e0 100644 --- a/lib/store/app_store.dart +++ b/lib/store/app_store.dart @@ -1,8 +1,8 @@ import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart'; import 'package:cake_wallet/di.dart'; +import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/utils/exception_handler.dart'; import 'package:cw_core/transaction_info.dart'; -import 'package:cw_core/wallet_type.dart'; import 'package:mobx/mobx.dart'; import 'package:cw_core/balance.dart'; import 'package:cw_core/wallet_base.dart'; @@ -26,8 +26,7 @@ abstract class AppStoreBase with Store { AuthenticationStore authenticationStore; @observable - WalletBase, TransactionInfo>? - wallet; + WalletBase, TransactionInfo>? wallet; WalletListStore walletList; @@ -36,16 +35,16 @@ abstract class AppStoreBase with Store { NodeListStore nodeListStore; @action - void changeCurrentWallet( - WalletBase, - TransactionInfo> - wallet) { + Future changeCurrentWallet( + WalletBase, TransactionInfo> wallet) async { this.wallet?.close(); this.wallet = wallet; this.wallet!.setExceptionHandler(ExceptionHandler.onError); - if (wallet.type == WalletType.ethereum) { - getIt.get().init(); + if (isEVMCompatibleChain(wallet.type)) { + await getIt.get().onDispose(); + getIt.get().create(); + await getIt.get().init(); } } } diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 4aaa44e13..7bccc4fc5 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -2,8 +2,9 @@ import 'dart:io'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; +import 'package:cake_wallet/buy/buy_provider.dart'; import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart'; -import 'package:cake_wallet/entities/buy_provider_types.dart'; +import 'package:cake_wallet/entities/provider_types.dart'; import 'package:cake_wallet/entities/cake_2fa_preset_options.dart'; import 'package:cake_wallet/entities/background_tasks.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart'; @@ -12,6 +13,8 @@ import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/entities/seed_phrase_length.dart'; import 'package:cake_wallet/entities/seed_type.dart'; import 'package:cake_wallet/entities/sort_balance_types.dart'; +import 'package:cake_wallet/entities/wallet_list_order_types.dart'; +import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart'; import 'package:cake_wallet/view_model/settings/sync_mode.dart'; import 'package:cake_wallet/utils/device_info.dart'; @@ -35,7 +38,6 @@ import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/entities/action_list_display_mode.dart'; import 'package:cake_wallet/entities/fiat_api_mode.dart'; import 'package:cw_core/set_app_secure_native.dart'; - part 'settings_store.g.dart'; class SettingsStore = SettingsStoreBase with _$SettingsStore; @@ -53,7 +55,8 @@ abstract class SettingsStoreBase with Store { required bool initialAppSecure, required bool initialDisableBuy, required bool initialDisableSell, - required BuyProviderType initialDefaultBuyProvider, + required WalletListOrderType initialWalletListOrder, + required bool initialWalletListAscending, required FiatApiMode initialFiatMode, required bool initialAllowBiometricalAuthentication, required String initialTotpSecretKey, @@ -88,6 +91,7 @@ abstract class SettingsStoreBase with Store { required this.sortBalanceBy, required this.pinNativeTokenAtTop, required this.useEtherscan, + required this.usePolygonScan, required this.defaultNanoRep, required this.defaultBananoRep, required this.lookupsTwitter, @@ -101,6 +105,7 @@ abstract class SettingsStoreBase with Store { TransactionPriority? initialHavenTransactionPriority, TransactionPriority? initialLitecoinTransactionPriority, TransactionPriority? initialEthereumTransactionPriority, + TransactionPriority? initialPolygonTransactionPriority, TransactionPriority? initialBitcoinCashTransactionPriority}) : nodes = ObservableMap.of(nodes), powNodes = ObservableMap.of(powNodes), @@ -120,7 +125,8 @@ abstract class SettingsStoreBase with Store { isAppSecure = initialAppSecure, disableBuy = initialDisableBuy, disableSell = initialDisableSell, - defaultBuyProvider = initialDefaultBuyProvider, + walletListOrder = initialWalletListOrder, + walletListAscending = initialWalletListAscending, shouldShowMarketPlaceInDashboard = initialShouldShowMarketPlaceInDashboard, exchangeStatus = initialExchangeStatus, currentTheme = initialTheme, @@ -142,7 +148,9 @@ abstract class SettingsStoreBase with Store { initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings, currentSyncMode = initialSyncMode, currentSyncAll = initialSyncAll, - priority = ObservableMap() { + priority = ObservableMap(), + defaultBuyProviders = ObservableMap(), + defaultSellProviders = ObservableMap() { //this.nodes = ObservableMap.of(nodes); if (initialMoneroTransactionPriority != null) { @@ -165,12 +173,38 @@ abstract class SettingsStoreBase with Store { priority[WalletType.ethereum] = initialEthereumTransactionPriority; } + if (initialPolygonTransactionPriority != null) { + priority[WalletType.polygon] = initialPolygonTransactionPriority; + } + if (initialBitcoinCashTransactionPriority != null) { priority[WalletType.bitcoinCash] = initialBitcoinCashTransactionPriority; } initializeTrocadorProviderStates(); + WalletType.values.forEach((walletType) { + final key = 'buyProvider_${walletType.toString()}'; + final providerId = sharedPreferences.getString(key); + if (providerId != null) { + defaultBuyProviders[walletType] = ProviderType.values + .firstWhere((provider) => provider.id == providerId, orElse: () => ProviderType.askEachTime); + } else { + defaultBuyProviders[walletType] = ProviderType.askEachTime; + } + }); + + WalletType.values.forEach((walletType) { + final key = 'sellProvider_${walletType.toString()}'; + final providerId = sharedPreferences.getString(key); + if (providerId != null) { + defaultSellProviders[walletType] = ProviderType.values + .firstWhere((provider) => provider.id == providerId, orElse: () => ProviderType.askEachTime); + } else { + defaultSellProviders[walletType] = ProviderType.askEachTime; + } + }); + reaction( (_) => fiatCurrency, (FiatCurrency fiatCurrency) => sharedPreferences.setString( @@ -181,6 +215,20 @@ abstract class SettingsStoreBase with Store { (bool shouldShowYatPopup) => sharedPreferences.setBool(PreferencesKey.shouldShowYatPopup, shouldShowYatPopup)); + defaultBuyProviders.observe((change) { + final String key = 'buyProvider_${change.key.toString()}'; + if (change.newValue != null) { + sharedPreferences.setString(key, change.newValue!.id); + } + }); + + defaultSellProviders.observe((change) { + final String key = 'sellProvider_${change.key.toString()}'; + if (change.newValue != null) { + sharedPreferences.setString(key, change.newValue!.id); + } + }); + priority.observe((change) { final String? key; switch (change.key) { @@ -202,6 +250,9 @@ abstract class SettingsStoreBase with Store { case WalletType.bitcoinCash: key = PreferencesKey.bitcoinCashTransactionPriority; break; + case WalletType.polygon: + key = PreferencesKey.polygonTransactionPriority; + break; default: key = null; } @@ -234,9 +285,14 @@ abstract class SettingsStoreBase with Store { sharedPreferences.setBool(PreferencesKey.disableSellKey, disableSell)); reaction( - (_) => defaultBuyProvider, - (BuyProviderType defaultBuyProvider) => - sharedPreferences.setInt(PreferencesKey.defaultBuyProvider, defaultBuyProvider.index)); + (_) => walletListOrder, + (WalletListOrderType walletListOrder) => + sharedPreferences.setInt(PreferencesKey.walletListOrder, walletListOrder.index)); + + reaction( + (_) => walletListAscending, + (bool walletListAscending) => + sharedPreferences.setBool(PreferencesKey.walletListAscending, walletListAscending)); reaction( (_) => autoGenerateSubaddressStatus, @@ -245,8 +301,8 @@ abstract class SettingsStoreBase with Store { reaction( (_) => moneroSeedType, - (SeedType moneroSeedType) => sharedPreferences.setInt( - PreferencesKey.moneroSeedType, moneroSeedType.raw)); + (SeedType moneroSeedType) => + sharedPreferences.setInt(PreferencesKey.moneroSeedType, moneroSeedType.raw)); reaction( (_) => fiatApiMode, @@ -323,6 +379,7 @@ abstract class SettingsStoreBase with Store { reaction((_) => totpSecretKey, (String totpKey) => sharedPreferences.setString(PreferencesKey.totpSecretKey, totpKey)); + reaction( (_) => numberOfFailedTokenTrials, (int failedTokenTrail) => @@ -342,9 +399,9 @@ abstract class SettingsStoreBase with Store { sharedPreferences.setString(PreferencesKey.currentLanguageCode, languageCode)); reaction( - (_) => seedPhraseLength, - (SeedPhraseLength seedPhraseWordCount) => - sharedPreferences.setInt(PreferencesKey.currentSeedPhraseLength, seedPhraseWordCount.value)); + (_) => seedPhraseLength, + (SeedPhraseLength seedPhraseWordCount) => sharedPreferences.setInt( + PreferencesKey.currentSeedPhraseLength, seedPhraseWordCount.value)); reaction( (_) => pinTimeOutDuration, @@ -388,6 +445,11 @@ abstract class SettingsStoreBase with Store { (bool useEtherscan) => _sharedPreferences.setBool(PreferencesKey.useEtherscan, useEtherscan)); + reaction( + (_) => usePolygonScan, + (bool usePolygonScan) => + _sharedPreferences.setBool(PreferencesKey.usePolygonScan, usePolygonScan)); + reaction((_) => defaultNanoRep, (String nanoRep) => _sharedPreferences.setString(PreferencesKey.defaultNanoRep, nanoRep)); @@ -396,34 +458,32 @@ abstract class SettingsStoreBase with Store { (String bananoRep) => _sharedPreferences.setString(PreferencesKey.defaultBananoRep, bananoRep)); reaction( - (_) => lookupsTwitter, - (bool looksUpTwitter) => + (_) => lookupsTwitter, + (bool looksUpTwitter) => _sharedPreferences.setBool(PreferencesKey.lookupsTwitter, looksUpTwitter)); reaction( - (_) => lookupsMastodon, - (bool looksUpMastodon) => + (_) => lookupsMastodon, + (bool looksUpMastodon) => _sharedPreferences.setBool(PreferencesKey.lookupsMastodon, looksUpMastodon)); reaction( - (_) => lookupsYatService, - (bool looksUpYatService) => + (_) => lookupsYatService, + (bool looksUpYatService) => _sharedPreferences.setBool(PreferencesKey.lookupsYatService, looksUpYatService)); reaction( - (_) => lookupsUnstoppableDomains, - (bool looksUpUnstoppableDomains) => - _sharedPreferences.setBool(PreferencesKey.lookupsUnstoppableDomains, looksUpUnstoppableDomains)); + (_) => lookupsUnstoppableDomains, + (bool looksUpUnstoppableDomains) => _sharedPreferences.setBool( + PreferencesKey.lookupsUnstoppableDomains, looksUpUnstoppableDomains)); reaction( - (_) => lookupsOpenAlias, - (bool looksUpOpenAlias) => + (_) => lookupsOpenAlias, + (bool looksUpOpenAlias) => _sharedPreferences.setBool(PreferencesKey.lookupsOpenAlias, looksUpOpenAlias)); - reaction( - (_) => lookupsENS, - (bool looksUpENS) => - _sharedPreferences.setBool(PreferencesKey.lookupsENS, looksUpENS)); + reaction((_) => lookupsENS, + (bool looksUpENS) => _sharedPreferences.setBool(PreferencesKey.lookupsENS, looksUpENS)); this.nodes.observe((change) { if (change.newValue != null && change.key != null) { @@ -482,7 +542,10 @@ abstract class SettingsStoreBase with Store { bool disableSell; @observable - BuyProviderType defaultBuyProvider; + WalletListOrderType walletListOrder; + + @observable + bool walletListAscending; @observable bool allowBiometricalAuthentication; @@ -553,6 +616,12 @@ abstract class SettingsStoreBase with Store { @observable ObservableMap trocadorProviderStates = ObservableMap(); + @observable + ObservableMap defaultBuyProviders; + + @observable + ObservableMap defaultSellProviders; + @observable SortBalanceBy sortBalanceBy; @@ -562,6 +631,9 @@ abstract class SettingsStoreBase with Store { @observable bool useEtherscan; + @observable + bool usePolygonScan; + @observable String defaultNanoRep; @@ -651,6 +723,7 @@ abstract class SettingsStoreBase with Store { TransactionPriority? havenTransactionPriority; TransactionPriority? litecoinTransactionPriority; TransactionPriority? ethereumTransactionPriority; + TransactionPriority? polygonTransactionPriority; TransactionPriority? bitcoinCashTransactionPriority; if (sharedPreferences.getInt(PreferencesKey.havenTransactionPriority) != null) { @@ -662,9 +735,13 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getInt(PreferencesKey.litecoinTransactionPriority)!); } if (sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority) != null) { - ethereumTransactionPriority = bitcoin?.deserializeLitecoinTransactionPriority( + ethereumTransactionPriority = ethereum?.deserializeEthereumTransactionPriority( sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!); } + if (sharedPreferences.getInt(PreferencesKey.polygonTransactionPriority) != null) { + polygonTransactionPriority = polygon?.deserializePolygonTransactionPriority( + sharedPreferences.getInt(PreferencesKey.polygonTransactionPriority)!); + } if (sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority) != null) { bitcoinCashTransactionPriority = bitcoinCash?.deserializeBitcoinCashTransactionPriority( sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority)!); @@ -676,6 +753,7 @@ abstract class SettingsStoreBase with Store { litecoinTransactionPriority ??= bitcoin?.getLitecoinTransactionPriorityMedium(); ethereumTransactionPriority ??= ethereum?.getDefaultTransactionPriority(); bitcoinCashTransactionPriority ??= bitcoinCash?.getDefaultTransactionPriority(); + polygonTransactionPriority ??= polygon?.getDefaultTransactionPriority(); final currentBalanceDisplayMode = BalanceDisplayMode.deserialize( raw: sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey)!); @@ -685,8 +763,10 @@ abstract class SettingsStoreBase with Store { final isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? false; final disableBuy = sharedPreferences.getBool(PreferencesKey.disableBuyKey) ?? false; final disableSell = sharedPreferences.getBool(PreferencesKey.disableSellKey) ?? false; - final defaultBuyProvider = - BuyProviderType.values[sharedPreferences.getInt(PreferencesKey.defaultBuyProvider) ?? 0]; + final walletListOrder = + WalletListOrderType.values[sharedPreferences.getInt(PreferencesKey.walletListOrder) ?? 0]; + final walletListAscending = + sharedPreferences.getBool(PreferencesKey.walletListAscending) ?? true; final currentFiatApiMode = FiatApiMode.deserialize( raw: sharedPreferences.getInt(PreferencesKey.currentFiatApiModeKey) ?? FiatApiMode.enabled.raw); @@ -749,12 +829,14 @@ abstract class SettingsStoreBase with Store { final pinNativeTokenAtTop = sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop) ?? true; final useEtherscan = sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true; + final usePolygonScan = sharedPreferences.getBool(PreferencesKey.usePolygonScan) ?? true; final defaultNanoRep = sharedPreferences.getString(PreferencesKey.defaultNanoRep) ?? ""; final defaultBananoRep = sharedPreferences.getString(PreferencesKey.defaultBananoRep) ?? ""; final lookupsTwitter = sharedPreferences.getBool(PreferencesKey.lookupsTwitter) ?? true; final lookupsMastodon = sharedPreferences.getBool(PreferencesKey.lookupsMastodon) ?? true; final lookupsYatService = sharedPreferences.getBool(PreferencesKey.lookupsYatService) ?? true; - final lookupsUnstoppableDomains = sharedPreferences.getBool(PreferencesKey.lookupsUnstoppableDomains) ?? true; + final lookupsUnstoppableDomains = + sharedPreferences.getBool(PreferencesKey.lookupsUnstoppableDomains) ?? true; final lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true; final lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true; @@ -774,6 +856,7 @@ abstract class SettingsStoreBase with Store { 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); final nanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey); final moneroNode = nodeSource.get(nodeId); @@ -781,6 +864,7 @@ abstract class SettingsStoreBase with Store { 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 nanoPowNode = powNodeSource.get(nanoPowNodeId); @@ -824,6 +908,10 @@ abstract class SettingsStoreBase with Store { nodes[WalletType.ethereum] = ethereumNode; } + if (polygonNode != null) { + nodes[WalletType.polygon] = polygonNode; + } + if (bitcoinCashElectrumServer != null) { nodes[WalletType.bitcoinCash] = bitcoinCashElectrumServer; } @@ -837,27 +925,28 @@ abstract class SettingsStoreBase with Store { } final savedSyncMode = SyncMode.all.firstWhere((element) { - return element.type.index == (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 1); + return element.type.index == (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 0); }); final savedSyncAll = sharedPreferences.getBool(PreferencesKey.syncAllKey) ?? true; - return SettingsStore( - sharedPreferences: sharedPreferences, - initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard, - nodes: nodes, - powNodes: powNodes, - appVersion: packageInfo.version, - deviceName: deviceName, - isBitcoinBuyEnabled: isBitcoinBuyEnabled, - initialFiatCurrency: currentFiatCurrency, - initialBalanceDisplayMode: currentBalanceDisplayMode, - initialSaveRecipientAddress: shouldSaveRecipientAddress, - initialAutoGenerateSubaddressStatus: autoGenerateSubaddressStatus, - initialMoneroSeedType: moneroSeedType, + return SettingsStore( + sharedPreferences: sharedPreferences, + initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard, + nodes: nodes, + powNodes: powNodes, + appVersion: packageInfo.version, + deviceName: deviceName, + isBitcoinBuyEnabled: isBitcoinBuyEnabled, + initialFiatCurrency: currentFiatCurrency, + initialBalanceDisplayMode: currentBalanceDisplayMode, + initialSaveRecipientAddress: shouldSaveRecipientAddress, + initialAutoGenerateSubaddressStatus: autoGenerateSubaddressStatus, + initialMoneroSeedType: moneroSeedType, initialAppSecure: isAppSecure, initialDisableBuy: disableBuy, initialDisableSell: disableSell, - initialDefaultBuyProvider: defaultBuyProvider, + initialWalletListOrder: walletListOrder, + initialWalletListAscending: walletListAscending, initialFiatMode: currentFiatApiMode, initialAllowBiometricalAuthentication: allowBiometricalAuthentication, initialCake2FAPresetOptions: selectedCake2FAPreset, @@ -869,42 +958,45 @@ abstract class SettingsStoreBase with Store { actionlistDisplayMode: actionListDisplayMode, initialPinLength: pinLength, pinTimeOutDuration: pinCodeTimeOutDuration, - seedPhraseLength: seedPhraseWordCount,initialLanguageCode: savedLanguageCode, + seedPhraseLength: seedPhraseWordCount, + initialLanguageCode: savedLanguageCode, sortBalanceBy: sortBalanceBy, pinNativeTokenAtTop: pinNativeTokenAtTop, useEtherscan: useEtherscan, + usePolygonScan: usePolygonScan, defaultNanoRep: defaultNanoRep, - defaultBananoRep: defaultBananoRep, - lookupsTwitter: lookupsTwitter, - lookupsMastodon: lookupsMastodon, - lookupsYatService: lookupsYatService, - lookupsUnstoppableDomains: lookupsUnstoppableDomains, - lookupsOpenAlias: lookupsOpenAlias, - lookupsENS: lookupsENS, - initialMoneroTransactionPriority: moneroTransactionPriority, + defaultBananoRep: defaultBananoRep, + lookupsTwitter: lookupsTwitter, + lookupsMastodon: lookupsMastodon, + lookupsYatService: lookupsYatService, + lookupsUnstoppableDomains: lookupsUnstoppableDomains, + lookupsOpenAlias: lookupsOpenAlias, + lookupsENS: lookupsENS, + initialMoneroTransactionPriority: moneroTransactionPriority, initialBitcoinTransactionPriority: bitcoinTransactionPriority, initialHavenTransactionPriority: havenTransactionPriority, initialLitecoinTransactionPriority: litecoinTransactionPriority, initialBitcoinCashTransactionPriority: bitcoinCashTransactionPriority, - initialShouldRequireTOTP2FAForAccessingWallet: shouldRequireTOTP2FAForAccessingWallet, - initialShouldRequireTOTP2FAForSendsToContact: shouldRequireTOTP2FAForSendsToContact, - initialShouldRequireTOTP2FAForSendsToNonContact: shouldRequireTOTP2FAForSendsToNonContact, - initialShouldRequireTOTP2FAForSendsToInternalWallets: - shouldRequireTOTP2FAForSendsToInternalWallets, - initialShouldRequireTOTP2FAForExchangesToInternalWallets: - shouldRequireTOTP2FAForExchangesToInternalWallets, + initialShouldRequireTOTP2FAForAccessingWallet: shouldRequireTOTP2FAForAccessingWallet, + initialShouldRequireTOTP2FAForSendsToContact: shouldRequireTOTP2FAForSendsToContact, + initialShouldRequireTOTP2FAForSendsToNonContact: shouldRequireTOTP2FAForSendsToNonContact, + initialShouldRequireTOTP2FAForSendsToInternalWallets: + shouldRequireTOTP2FAForSendsToInternalWallets, + initialShouldRequireTOTP2FAForExchangesToInternalWallets: + shouldRequireTOTP2FAForExchangesToInternalWallets, initialShouldRequireTOTP2FAForExchangesToExternalWallets: shouldRequireTOTP2FAForExchangesToExternalWallets, - initialShouldRequireTOTP2FAForAddingContacts: shouldRequireTOTP2FAForAddingContacts, - initialShouldRequireTOTP2FAForCreatingNewWallets: shouldRequireTOTP2FAForCreatingNewWallets, - initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings: - shouldRequireTOTP2FAForAllSecurityAndBackupSettings, - initialEthereumTransactionPriority: ethereumTransactionPriority, - backgroundTasks: backgroundTasks, - initialSyncMode: savedSyncMode, - initialSyncAll: savedSyncAll, - shouldShowYatPopup: shouldShowYatPopup); - } + initialShouldRequireTOTP2FAForAddingContacts: shouldRequireTOTP2FAForAddingContacts, + initialShouldRequireTOTP2FAForCreatingNewWallets: shouldRequireTOTP2FAForCreatingNewWallets, + initialShouldRequireTOTP2FAForAllSecurityAndBackupSettings: + shouldRequireTOTP2FAForAllSecurityAndBackupSettings, + initialEthereumTransactionPriority: ethereumTransactionPriority, + initialPolygonTransactionPriority: polygonTransactionPriority, + backgroundTasks: backgroundTasks, + initialSyncMode: savedSyncMode, + initialSyncAll: savedSyncAll, + shouldShowYatPopup: shouldShowYatPopup); + } Future reload({required Box nodeSource}) async { final sharedPreferences = await getIt.getAsync(); @@ -934,6 +1026,11 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getInt(PreferencesKey.ethereumTransactionPriority)!) ?? priority[WalletType.ethereum]!; } + if (sharedPreferences.getInt(PreferencesKey.polygonTransactionPriority) != null) { + priority[WalletType.polygon] = polygon?.deserializePolygonTransactionPriority( + sharedPreferences.getInt(PreferencesKey.polygonTransactionPriority)!) ?? + priority[WalletType.polygon]!; + } if (sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority) != null) { priority[WalletType.bitcoinCash] = bitcoinCash?.deserializeBitcoinCashTransactionPriority( sharedPreferences.getInt(PreferencesKey.bitcoinCashTransactionPriority)!) ?? @@ -965,8 +1062,9 @@ abstract class SettingsStoreBase with Store { isAppSecure = sharedPreferences.getBool(PreferencesKey.isAppSecureKey) ?? isAppSecure; disableBuy = sharedPreferences.getBool(PreferencesKey.disableBuyKey) ?? disableBuy; disableSell = sharedPreferences.getBool(PreferencesKey.disableSellKey) ?? disableSell; - defaultBuyProvider = - BuyProviderType.values[sharedPreferences.getInt(PreferencesKey.defaultBuyProvider) ?? 0]; + walletListOrder = + WalletListOrderType.values[sharedPreferences.getInt(PreferencesKey.walletListOrder) ?? 0]; + walletListAscending = sharedPreferences.getBool(PreferencesKey.walletListAscending) ?? true; allowBiometricalAuthentication = sharedPreferences.getBool(PreferencesKey.allowBiometricalAuthenticationKey) ?? allowBiometricalAuthentication; @@ -1027,12 +1125,14 @@ abstract class SettingsStoreBase with Store { .values[sharedPreferences.getInt(PreferencesKey.sortBalanceBy) ?? sortBalanceBy.index]; pinNativeTokenAtTop = sharedPreferences.getBool(PreferencesKey.pinNativeTokenAtTop) ?? true; useEtherscan = sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true; + usePolygonScan = sharedPreferences.getBool(PreferencesKey.usePolygonScan) ?? true; defaultNanoRep = sharedPreferences.getString(PreferencesKey.defaultNanoRep) ?? ""; defaultBananoRep = sharedPreferences.getString(PreferencesKey.defaultBananoRep) ?? ""; lookupsTwitter = sharedPreferences.getBool(PreferencesKey.lookupsTwitter) ?? true; lookupsMastodon = sharedPreferences.getBool(PreferencesKey.lookupsMastodon) ?? true; lookupsYatService = sharedPreferences.getBool(PreferencesKey.lookupsYatService) ?? true; - lookupsUnstoppableDomains = sharedPreferences.getBool(PreferencesKey.lookupsUnstoppableDomains) ?? true; + lookupsUnstoppableDomains = + sharedPreferences.getBool(PreferencesKey.lookupsUnstoppableDomains) ?? true; lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true; lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true; @@ -1045,6 +1145,7 @@ abstract class SettingsStoreBase with Store { 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); final nanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey); final moneroNode = nodeSource.get(nodeId); @@ -1052,6 +1153,7 @@ abstract class SettingsStoreBase with Store { final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId); final havenNode = nodeSource.get(havenNodeId); final ethereumNode = nodeSource.get(ethereumNodeId); + final polygonNode = nodeSource.get(polygonNodeId); final bitcoinCashNode = nodeSource.get(bitcoinCashElectrumServerId); final nanoNode = nodeSource.get(nanoNodeId); @@ -1075,6 +1177,10 @@ abstract class SettingsStoreBase with Store { nodes[WalletType.ethereum] = ethereumNode; } + if (polygonNode != null) { + nodes[WalletType.polygon] = polygonNode; + } + if (bitcoinCashNode != null) { nodes[WalletType.bitcoinCash] = bitcoinCashNode; } @@ -1110,6 +1216,9 @@ abstract class SettingsStoreBase with Store { case WalletType.nano: await _sharedPreferences.setInt(PreferencesKey.currentNanoNodeIdKey, node.key as int); break; + case WalletType.polygon: + await _sharedPreferences.setInt(PreferencesKey.currentPolygonNodeIdKey, node.key as int); + break; default: break; } @@ -1141,7 +1250,6 @@ abstract class SettingsStoreBase with Store { trocadorProviderStates[providerName] = state; } - static Future _getDeviceName() async { String? deviceName = ''; final deviceInfoPlugin = DeviceInfoPlugin(); diff --git a/lib/themes/bright_theme.dart b/lib/themes/bright_theme.dart index 0ea2bb6f2..42510a389 100644 --- a/lib/themes/bright_theme.dart +++ b/lib/themes/bright_theme.dart @@ -78,7 +78,7 @@ class BrightTheme extends LightTheme { FilterTheme get filterTheme => super.filterTheme.copyWith( checkboxSecondGradientColor: Palette.pinkFlamingo, checkboxBackgroundColor: Colors.white, - buttonColor: Colors.white.withOpacity(0.2), + buttonColor: Palette.darkGray.withOpacity(0.2), iconColor: Colors.white); @override diff --git a/lib/view_model/advanced_privacy_settings_view_model.dart b/lib/view_model/advanced_privacy_settings_view_model.dart index f62815346..75d7a9eb4 100644 --- a/lib/view_model/advanced_privacy_settings_view_model.dart +++ b/lib/view_model/advanced_privacy_settings_view_model.dart @@ -20,9 +20,6 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store { @computed FiatApiMode get fiatApiMode => _settingsStore.fiatApiMode; - @computed - SeedType get seedType => _settingsStore.moneroSeedType; - @observable bool _addCustomNode = false; @@ -41,11 +38,11 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store { @computed SeedPhraseLength get seedPhraseLength => _settingsStore.seedPhraseLength; - @action - void setFiatApiMode(FiatApiMode fiatApiMode) => _settingsStore.fiatApiMode = fiatApiMode; + @computed + bool get isPolySeed => _settingsStore.moneroSeedType == SeedType.polyseed; @action - void setSeedType(SeedType seedType) => _settingsStore.moneroSeedType = seedType; + void setFiatApiMode(FiatApiMode fiatApiMode) => _settingsStore.fiatApiMode = fiatApiMode; @action void setExchangeApiMode(ExchangeApiMode value) => _settingsStore.exchangeStatus = value; diff --git a/lib/view_model/buy/buy_view_model.dart b/lib/view_model/buy/buy_view_model.dart index 080caa3ad..d73396e1b 100644 --- a/lib/view_model/buy/buy_view_model.dart +++ b/lib/view_model/buy/buy_view_model.dart @@ -1,5 +1,5 @@ import 'package:cake_wallet/buy/buy_provider.dart'; -import 'package:cake_wallet/buy/moonpay/moonpay_buy_provider.dart'; +import 'package:cake_wallet/buy/moonpay/moonpay_provider.dart'; import 'package:cake_wallet/buy/wyre/wyre_buy_provider.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cake_wallet/entities/fiat_currency.dart'; @@ -61,8 +61,7 @@ abstract class BuyViewModelBase with Store { String _url = ''; try { - _url = await selectedProvider - !.requestUrl(doubleAmount.toString(), fiatCurrency.title); + _url = await selectedProvider!.requestUrl(doubleAmount.toString(), fiatCurrency.title); } catch (e) { print(e.toString()); } diff --git a/lib/view_model/contact_list/contact_list_view_model.dart b/lib/view_model/contact_list/contact_list_view_model.dart index 0dcd8108a..b53b67c43 100644 --- a/lib/view_model/contact_list/contact_list_view_model.dart +++ b/lib/view_model/contact_list/contact_list_view_model.dart @@ -91,6 +91,9 @@ abstract class ContactListViewModelBase with Store { walletContacts.where((element) => _isValidForCurrency(element)).toList(); bool _isValidForCurrency(ContactBase element) { - return _currency == null || element.type == _currency || element.type.title == _currency!.tag; + return _currency == null || + element.type == _currency || + element.type.title == _currency!.tag || + element.type.tag == _currency!.tag; } } diff --git a/lib/view_model/dashboard/balance_view_model.dart b/lib/view_model/dashboard/balance_view_model.dart index 9366985b5..5c95ab3ab 100644 --- a/lib/view_model/dashboard/balance_view_model.dart +++ b/lib/view_model/dashboard/balance_view_model.dart @@ -1,5 +1,6 @@ import 'package:cake_wallet/entities/fiat_api_mode.dart'; import 'package:cake_wallet/entities/sort_balance_types.dart'; +import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cw_core/transaction_history.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/balance.dart'; @@ -81,7 +82,7 @@ abstract class BalanceViewModelBase with Store { bool get isFiatDisabled => settingsStore.fiatApiMode == FiatApiMode.disabled; @computed - bool get isHomeScreenSettingsEnabled => wallet.type == WalletType.ethereum; + bool get isHomeScreenSettingsEnabled => isEVMCompatibleChain(wallet.type); @computed bool get hasAccounts => wallet.type == WalletType.monero; @@ -123,6 +124,7 @@ abstract class BalanceViewModelBase with Store { case WalletType.monero: case WalletType.haven: case WalletType.ethereum: + case WalletType.polygon: return S.current.xmr_available_balance; default: return S.current.confirmed; @@ -135,6 +137,7 @@ abstract class BalanceViewModelBase with Store { case WalletType.monero: case WalletType.haven: case WalletType.ethereum: + case WalletType.polygon: return S.current.xmr_full_balance; default: return S.current.unconfirmed; @@ -272,7 +275,8 @@ abstract class BalanceViewModelBase with Store { } @computed - bool get hasAdditionalBalance => wallet.type != WalletType.ethereum; + bool get hasAdditionalBalance => !isEVMCompatibleChain(wallet.type); + @computed List get formattedBalances { diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index fbb2fc76f..a794c2262 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -1,6 +1,10 @@ +import 'dart:convert'; + +import 'package:cake_wallet/buy/buy_provider.dart'; +import 'package:cake_wallet/core/key_service.dart'; import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart'; import 'package:cake_wallet/entities/balance_display_mode.dart'; -import 'package:cake_wallet/entities/buy_provider_types.dart'; +import 'package:cake_wallet/entities/provider_types.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/exchange/exchange_provider_description.dart'; import 'package:cake_wallet/generated/i18n.dart'; @@ -24,13 +28,21 @@ import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart'; import 'package:cake_wallet/view_model/dashboard/transaction_list_item.dart'; import 'package:cake_wallet/view_model/settings/sync_mode.dart'; import 'package:cake_wallet/wallet_type_utils.dart'; +import 'package:cryptography/cryptography.dart'; import 'package:cw_core/balance.dart'; +import 'package:cw_core/cake_hive.dart'; +import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/sync_status.dart'; import 'package:cw_core/transaction_history.dart'; import 'package:cw_core/transaction_info.dart'; +import 'package:cw_core/utils/file.dart'; import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; +import 'package:eth_sig_util/util/utils.dart'; +import 'package:flutter/services.dart'; import 'package:mobx/mobx.dart'; +import 'package:cake_wallet/entities/provider_types.dart'; part 'dashboard_view_model.g.dart'; @@ -46,7 +58,8 @@ abstract class DashboardViewModelBase with Store { required this.settingsStore, required this.yatStore, required this.ordersStore, - required this.anonpayTransactionsStore}) + required this.anonpayTransactionsStore, + required this.keyService}) : hasSellAction = false, hasBuyAction = false, hasExchangeAction = false, @@ -262,6 +275,8 @@ abstract class DashboardViewModelBase with Store { bool get hasRescan => wallet.type == WalletType.monero || wallet.type == WalletType.haven; + final KeyService keyService; + BalanceViewModel balanceViewModel; AppStore appStore; @@ -282,10 +297,32 @@ abstract class DashboardViewModelBase with Store { Map> filterItems; - BuyProviderType get defaultBuyProvider => settingsStore.defaultBuyProvider; + BuyProvider? get defaultBuyProvider => ProvidersHelper.getProviderByType( + settingsStore.defaultBuyProviders[wallet.type] ?? ProviderType.askEachTime); + + BuyProvider? get defaultSellProvider => ProvidersHelper.getProviderByType( + settingsStore.defaultSellProviders[wallet.type] ?? ProviderType.askEachTime); bool get isBuyEnabled => settingsStore.isBitcoinBuyEnabled; + List get availableBuyProviders { + final providerTypes = ProvidersHelper.getAvailableBuyProviderTypes(wallet.type); + return providerTypes + .map((type) => ProvidersHelper.getProviderByType(type)) + .where((provider) => provider != null) + .cast() + .toList(); + } + + List get availableSellProviders { + final providerTypes = ProvidersHelper.getAvailableSellProviderTypes(wallet.type); + return providerTypes + .map((type) => ProvidersHelper.getProviderByType(type)) + .where((provider) => provider != null) + .cast() + .toList(); + } + bool get shouldShowYatPopup => settingsStore.shouldShowYatPopup; @action @@ -298,16 +335,15 @@ abstract class DashboardViewModelBase with Store { bool hasExchangeAction; @computed - bool get isEnabledBuyAction => !settingsStore.disableBuy && wallet.type != WalletType.haven; + bool get isEnabledBuyAction => + !settingsStore.disableBuy && availableBuyProviders.isNotEmpty; @observable bool hasBuyAction; @computed bool get isEnabledSellAction => - !settingsStore.disableSell && - wallet.type != WalletType.haven && - wallet.type != WalletType.monero; + !settingsStore.disableSell && availableSellProviders.isNotEmpty; @observable bool hasSellAction; @@ -429,4 +465,32 @@ abstract class DashboardViewModelBase with Store { @action void setSyncAll(bool value) => settingsStore.currentSyncAll = value; + + Future> checkAffectedWallets() async { + // await load file + final vulnerableSeedsString = await rootBundle.loadString('assets/text/cakewallet_weak_bitcoin_seeds_hashed_sorted_version1.txt'); + final vulnerableSeeds = vulnerableSeedsString.split("\n"); + + final walletInfoSource = await CakeHive.openBox(WalletInfo.boxName); + + List affectedWallets = []; + for (var walletInfo in walletInfoSource.values) { + if (walletInfo.type == WalletType.bitcoin) { + final password = await keyService.getWalletPassword(walletName: walletInfo.name); + final path = await pathForWallet(name: walletInfo.name, type: walletInfo.type); + final jsonSource = await read(path: path, password: password); + final data = json.decode(jsonSource) as Map; + final mnemonic = data['mnemonic'] as String; + + final hash = await Cryptography.instance.sha256().hash(utf8.encode(mnemonic)); + final seedSha = bytesToHex(hash.bytes); + + if (vulnerableSeeds.contains(seedSha)) { + affectedWallets.add(walletInfo.name); + } + } + } + + return affectedWallets; + } } diff --git a/lib/view_model/dashboard/home_settings_view_model.dart b/lib/view_model/dashboard/home_settings_view_model.dart index 66620f951..fc2c27a7c 100644 --- a/lib/view_model/dashboard/home_settings_view_model.dart +++ b/lib/view_model/dashboard/home_settings_view_model.dart @@ -2,10 +2,12 @@ import 'package:cake_wallet/core/fiat_conversion_service.dart'; import 'package:cake_wallet/entities/fiat_api_mode.dart'; import 'package:cake_wallet/entities/sort_balance_types.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; +import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/erc20_token.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:mobx/mobx.dart'; part 'home_settings_view_model.g.dart'; @@ -42,18 +44,41 @@ abstract class HomeSettingsViewModelBase with Store { void setPinNativeToken(bool value) => _settingsStore.pinNativeTokenAtTop = value; Future addErc20Token(Erc20Token token) async { - await ethereum!.addErc20Token(_balanceViewModel.wallet, token); + if (_balanceViewModel.wallet.type == WalletType.ethereum) { + await ethereum!.addErc20Token(_balanceViewModel.wallet, token); + } + + if (_balanceViewModel.wallet.type == WalletType.polygon) { + await polygon!.addErc20Token(_balanceViewModel.wallet, token); + } + _updateTokensList(); _updateFiatPrices(token); } Future deleteErc20Token(Erc20Token token) async { - await ethereum!.deleteErc20Token(_balanceViewModel.wallet, token); + if (_balanceViewModel.wallet.type == WalletType.ethereum) { + await ethereum!.deleteErc20Token(_balanceViewModel.wallet, token); + } + + if (_balanceViewModel.wallet.type == WalletType.polygon) { + await polygon!.deleteErc20Token(_balanceViewModel.wallet, token); + } + _updateTokensList(); } - Future getErc20Token(String contractAddress) async => - await ethereum!.getErc20Token(_balanceViewModel.wallet, contractAddress); + Future getErc20Token(String contractAddress) async { + if (_balanceViewModel.wallet.type == WalletType.ethereum) { + return await ethereum!.getErc20Token(_balanceViewModel.wallet, contractAddress); + } + + if (_balanceViewModel.wallet.type == WalletType.polygon) { + return await polygon!.getErc20Token(_balanceViewModel.wallet, contractAddress); + } + + return null; + } CryptoCurrency get nativeToken => _balanceViewModel.wallet.currency; @@ -69,7 +94,12 @@ abstract class HomeSettingsViewModelBase with Store { void changeTokenAvailability(Erc20Token token, bool value) async { token.enabled = value; - ethereum!.addErc20Token(_balanceViewModel.wallet, token); + if (_balanceViewModel.wallet.type == WalletType.ethereum) { + ethereum!.addErc20Token(_balanceViewModel.wallet, token); + } + if (_balanceViewModel.wallet.type == WalletType.polygon) { + polygon!.addErc20Token(_balanceViewModel.wallet, token); + } _refreshTokensList(); } @@ -83,7 +113,8 @@ abstract class HomeSettingsViewModelBase with Store { return -1; } else if (e2.enabled && !e1.enabled) { return 1; - } else if (!e1.enabled && !e2.enabled) { // if both are disabled then sort alphabetically + } else if (!e1.enabled && !e2.enabled) { + // if both are disabled then sort alphabetically return e1.name.compareTo(e2.name); } @@ -92,11 +123,21 @@ abstract class HomeSettingsViewModelBase with Store { tokens.clear(); - tokens.addAll(ethereum! - .getERC20Currencies(_balanceViewModel.wallet) - .where((element) => _matchesSearchText(element)) - .toList() - ..sort(_sortFunc)); + if (_balanceViewModel.wallet.type == WalletType.ethereum) { + tokens.addAll(ethereum! + .getERC20Currencies(_balanceViewModel.wallet) + .where((element) => _matchesSearchText(element)) + .toList() + ..sort(_sortFunc)); + } + + if (_balanceViewModel.wallet.type == WalletType.polygon) { + tokens.addAll(polygon! + .getERC20Currencies(_balanceViewModel.wallet) + .where((element) => _matchesSearchText(element)) + .toList() + ..sort(_sortFunc)); + } } @action diff --git a/lib/view_model/dashboard/nft_view_model.dart b/lib/view_model/dashboard/nft_view_model.dart index c5acf5523..f00f929a3 100644 --- a/lib/view_model/dashboard/nft_view_model.dart +++ b/lib/view_model/dashboard/nft_view_model.dart @@ -1,10 +1,9 @@ -// ignore_for_file: public_member_api_docs, sort_constructors_first import 'dart:convert'; import 'dart:developer'; import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart'; +import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/src/screens/wallet_connect/widgets/message_display_widget.dart'; -import 'package:cw_core/wallet_type.dart'; import 'package:http/http.dart' as http; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/.secrets.g.dart' as secrets; @@ -39,23 +38,26 @@ abstract class NFTViewModelBase with Store { @action Future getNFTAssetByWallet() async { - if (appStore.wallet!.type != WalletType.ethereum) return; + if (!isEVMCompatibleChain(appStore.wallet!.type)) return; final walletAddress = appStore.wallet!.walletInfo.address; log('Fetching wallet NFTs for $walletAddress'); + final chainName = getChainNameBasedOnWalletType(appStore.wallet!.type); // the [chain] refers to the chain network that the nft is on // the [format] refers to the number format type of the responses // the [normalizedMetadata] field is a boolean that determines if // the response would include a json string of the NFT Metadata that can be decoded // and used within the wallet + // the [excludeSpam] field is a boolean that determines if spam nfts be excluded from the response. final uri = Uri.https( 'deep-index.moralis.io', '/api/v2.2/$walletAddress/nft', { - "chain": "eth", + "chain": chainName, "format": "decimal", "media_items": "false", + "exclude_spam": "true", "normalizeMetadata": "true", }, ); @@ -94,7 +96,7 @@ abstract class NFTViewModelBase with Store { @action Future importNFT(String tokenAddress, String tokenId) async { - + final chainName = getChainNameBasedOnWalletType(appStore.wallet!.type); // the [chain] refers to the chain network that the nft is on // the [format] refers to the number format type of the responses // the [normalizedMetadata] field is a boolean that determines if @@ -104,7 +106,7 @@ abstract class NFTViewModelBase with Store { 'deep-index.moralis.io', '/api/v2.2/nft/$tokenAddress/$tokenId', { - "chain": "eth", + "chain": chainName, "format": "decimal", "media_items": "false", "normalizeMetadata": "true", diff --git a/lib/view_model/dashboard/transaction_list_item.dart b/lib/view_model/dashboard/transaction_list_item.dart index fd7971001..bc7f70517 100644 --- a/lib/view_model/dashboard/transaction_list_item.dart +++ b/lib/view_model/dashboard/transaction_list_item.dart @@ -3,6 +3,7 @@ import 'package:cake_wallet/entities/fiat_currency.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/nano/nano.dart'; +import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_info.dart'; import 'package:cake_wallet/store/settings_store.dart'; @@ -91,6 +92,13 @@ class TransactionListItem extends ActionListItem with Keyable { cryptoAmount: ethereum!.formatterEthereumAmountToDouble(transaction: transaction), price: price); break; + case WalletType.polygon: + final asset = polygon!.assetOfTransaction(balanceViewModel.wallet, transaction); + final price = balanceViewModel.fiatConvertationStore.prices[asset]; + amount = calculateFiatAmountRaw( + cryptoAmount: polygon!.formatterPolygonAmountToDouble(transaction: transaction), + price: price); + break; case WalletType.nano: amount = calculateFiatAmountRaw( cryptoAmount: double.parse(nanoUtil!.getRawAsDecimalString( diff --git a/lib/view_model/exchange/exchange_trade_view_model.dart b/lib/view_model/exchange/exchange_trade_view_model.dart index bc7f53af0..93877a525 100644 --- a/lib/view_model/exchange/exchange_trade_view_model.dart +++ b/lib/view_model/exchange/exchange_trade_view_model.dart @@ -28,10 +28,7 @@ abstract class ExchangeTradeViewModelBase with Store { required this.tradesStore, required this.sendViewModel}) : trade = tradesStore.trade!, - isSendable = tradesStore.trade!.from == wallet.currency || - tradesStore.trade!.provider == ExchangeProviderDescription.xmrto || - (wallet.currency == CryptoCurrency.eth && - tradesStore.trade!.from.tag == CryptoCurrency.eth.title), + isSendable = _checkIfCanSend(tradesStore, wallet), items = ObservableList() { switch (trade.provider) { case ExchangeProviderDescription.changeNow: @@ -155,4 +152,19 @@ abstract class ExchangeTradeViewModelBase with Store { isCopied: true), ]); } + + static bool _checkIfCanSend(TradesStore tradesStore, WalletBase wallet) { + bool _isEthToken() => + wallet.currency == CryptoCurrency.eth && + tradesStore.trade!.from.tag == CryptoCurrency.eth.title; + + bool _isPolygonToken() => + wallet.currency == CryptoCurrency.maticpoly && + tradesStore.trade!.from.tag == CryptoCurrency.maticpoly.tag; + + return tradesStore.trade!.from == wallet.currency || + tradesStore.trade!.provider == ExchangeProviderDescription.xmrto || + _isEthToken() || + _isPolygonToken(); + } } diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index 80c331ab2..afe617803 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -23,6 +23,7 @@ import 'package:cake_wallet/exchange/trade.dart'; import 'package:cake_wallet/exchange/trade_request.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/monero/monero.dart'; +import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/store/dashboard/trades_store.dart'; import 'package:cake_wallet/store/settings_store.dart'; @@ -65,7 +66,6 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with receiveAddress = '', depositAddress = '', isDepositAddressEnabled = false, - isReceiveAddressEnabled = false, isReceiveAmountEditable = false, _useTorOnly = false, receiveCurrencies = [], @@ -107,7 +107,6 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with bestRateSync = Timer.periodic(Duration(seconds: 10), (timer) => _calculateBestRate()); isDepositAddressEnabled = !(depositCurrency == wallet.currency); - isReceiveAddressEnabled = !(receiveCurrency == wallet.currency); depositAmount = ''; receiveAmount = ''; receiveAddress = ''; @@ -200,9 +199,6 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with @observable bool isDepositAddressEnabled; - @observable - bool isReceiveAddressEnabled; - @observable bool isReceiveAmountEntered; @@ -287,6 +283,8 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with return transactionPriority == ethereum!.getEthereumTransactionPrioritySlow(); case WalletType.bitcoinCash: return transactionPriority == bitcoinCash!.getBitcoinCashTransactionPrioritySlow(); + case WalletType.polygon: + return transactionPriority == polygon!.getPolygonTransactionPrioritySlow(); default: return false; } @@ -312,7 +310,6 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with isFixedRateMode = false; _onPairChange(); isDepositAddressEnabled = !(depositCurrency == wallet.currency); - isReceiveAddressEnabled = !(receiveCurrency == wallet.currency); } @action @@ -321,7 +318,6 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with isFixedRateMode = false; _onPairChange(); isDepositAddressEnabled = !(depositCurrency == wallet.currency); - isReceiveAddressEnabled = !(receiveCurrency == wallet.currency); } @action @@ -532,7 +528,6 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with depositAddress = depositCurrency == wallet.currency ? wallet.walletAddresses.address : ''; receiveAddress = receiveCurrency == wallet.currency ? wallet.walletAddresses.address : ''; isDepositAddressEnabled = !(depositCurrency == wallet.currency); - isReceiveAddressEnabled = !(receiveCurrency == wallet.currency); isFixedRateMode = false; _onPairChange(); } @@ -626,6 +621,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with depositCurrency = CryptoCurrency.nano; receiveCurrency = CryptoCurrency.xmr; break; + case WalletType.polygon: + depositCurrency = CryptoCurrency.maticpoly; + receiveCurrency = CryptoCurrency.xmr; + break; default: break; } @@ -713,6 +712,9 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with case WalletType.bitcoinCash: _settingsStore.priority[wallet.type] = bitcoinCash!.getDefaultTransactionPriority(); break; + case WalletType.polygon: + _settingsStore.priority[wallet.type] = polygon!.getDefaultTransactionPriority(); + break; default: break; } diff --git a/lib/view_model/node_list/node_list_view_model.dart b/lib/view_model/node_list/node_list_view_model.dart index ae0edba30..0cd4d7491 100644 --- a/lib/view_model/node_list/node_list_view_model.dart +++ b/lib/view_model/node_list/node_list_view_model.dart @@ -72,6 +72,9 @@ abstract class NodeListViewModelBase with Store { case WalletType.nano: node = getNanoDefaultNode(nodes: _nodeSource)!; break; + case WalletType.polygon: + node = getPolygonDefaultNode(nodes: _nodeSource)!; + break; default: throw Exception('Unexpected wallet type: ${_appStore.wallet!.type}'); } diff --git a/lib/view_model/order_details_view_model.dart b/lib/view_model/order_details_view_model.dart index 0047ed7b2..9b00bbb46 100644 --- a/lib/view_model/order_details_view_model.dart +++ b/lib/view_model/order_details_view_model.dart @@ -9,7 +9,7 @@ import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.d import 'package:cake_wallet/src/screens/trade_details/track_trade_list_item.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:cw_core/wallet_base.dart'; -import 'package:cake_wallet/buy/moonpay/moonpay_buy_provider.dart'; +import 'package:cake_wallet/buy/moonpay/moonpay_provider.dart'; import 'package:cake_wallet/buy/wyre/wyre_buy_provider.dart'; part 'order_details_view_model.g.dart'; @@ -50,8 +50,10 @@ abstract class OrderDetailsViewModelBase with Store { @action Future _updateOrder() async { try { - if (_provider != null) { - final updatedOrder = await _provider!.findOrderById(order.id); + if (_provider != null && (_provider is MoonPayBuyProvider || _provider is WyreBuyProvider)) { + final updatedOrder = _provider is MoonPayBuyProvider + ? await (_provider as MoonPayBuyProvider).findOrderById(order.id) + : await (_provider as WyreBuyProvider).findOrderById(order.id); updatedOrder.from = order.from; updatedOrder.to = order.to; updatedOrder.receiveAddress = order.receiveAddress; @@ -87,19 +89,26 @@ abstract class OrderDetailsViewModelBase with Store { value: order.provider.title) ); - if (_provider?.trackUrl.isNotEmpty ?? false) { - final buildURL = _provider!.trackUrl + '${order.transferId}'; - items.add( - TrackTradeListItem( - title: 'Track', - value: buildURL, - onTap: () { - try { - launch(buildURL); - } catch (e) {} - } - ) - ); + if (_provider != null && (_provider is MoonPayBuyProvider || _provider is WyreBuyProvider)) { + + final trackUrl = _provider is MoonPayBuyProvider + ? (_provider as MoonPayBuyProvider).trackUrl + : (_provider as WyreBuyProvider).trackUrl; + + if (trackUrl.isNotEmpty ?? false) { + final buildURL = trackUrl + '${order.transferId}'; + items.add( + TrackTradeListItem( + title: 'Track', + value: buildURL, + onTap: () { + try { + launch(buildURL); + } catch (e) {} + } + ) + ); + } } items.add( diff --git a/lib/view_model/restore/restore_from_qr_vm.dart b/lib/view_model/restore/restore_from_qr_vm.dart index 3cd1d91bc..e6883e991 100644 --- a/lib/view_model/restore/restore_from_qr_vm.dart +++ b/lib/view_model/restore/restore_from_qr_vm.dart @@ -2,6 +2,7 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/nano/nano.dart'; +import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/view_model/restore/restore_mode.dart'; import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; import 'package:hive/hive.dart'; @@ -76,6 +77,9 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store case WalletType.ethereum: return ethereum!.createEthereumRestoreWalletFromPrivateKey( name: name, password: password, privateKey: restoreWallet.privateKey!); + case WalletType.polygon: + return polygon!.createPolygonRestoreWalletFromPrivateKey( + name: name, password: password, privateKey: restoreWallet.privateKey!); default: throw Exception('Unexpected type: ${restoreWallet.type.toString()}'); } @@ -108,6 +112,9 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store mnemonic: restoreWallet.mnemonicSeed ?? '', password: password, derivationType: derivationInfo.derivationType!); + case WalletType.polygon: + return polygon!.createPolygonRestoreWalletFromSeedCredentials( + name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password); default: throw Exception('Unexpected type: ${type.toString()}'); } diff --git a/lib/view_model/restore/wallet_restore_from_qr_code.dart b/lib/view_model/restore/wallet_restore_from_qr_code.dart index 6085f4354..bfc9b7980 100644 --- a/lib/view_model/restore/wallet_restore_from_qr_code.dart +++ b/lib/view_model/restore/wallet_restore_from_qr_code.dart @@ -26,6 +26,7 @@ class WalletRestoreFromQRCode { 'litecoin-wallet': WalletType.litecoin, 'litecoin_wallet': WalletType.litecoin, 'ethereum-wallet': WalletType.ethereum, + 'polygon-wallet': WalletType.polygon, 'nano-wallet': WalletType.nano, 'nano_wallet': WalletType.nano, 'bitcoincash': WalletType.bitcoinCash, @@ -157,7 +158,16 @@ class WalletRestoreFromQRCode { return WalletRestoreMode.keys; } - if ((type == WalletType.nano || type == WalletType.banano) && credentials.containsKey('hexSeed')) { + if (type == WalletType.polygon && credentials.containsKey('private_key')) { + final privateKey = credentials['private_key'] as String; + if (privateKey.isEmpty) { + throw Exception('Unexpected restore mode: private_key'); + } + return WalletRestoreMode.keys; + } + + if ((type == WalletType.nano || type == WalletType.banano) && + credentials.containsKey('hexSeed')) { final hexSeed = credentials['hexSeed'] as String; if (hexSeed.isEmpty) { throw Exception('Unexpected restore mode: hexSeed'); diff --git a/lib/view_model/seed_type_view_model.dart b/lib/view_model/seed_type_view_model.dart new file mode 100644 index 000000000..e3597be1c --- /dev/null +++ b/lib/view_model/seed_type_view_model.dart @@ -0,0 +1,19 @@ +import 'package:cake_wallet/entities/seed_type.dart'; +import 'package:cake_wallet/store/app_store.dart'; +import 'package:mobx/mobx.dart'; + +part 'seed_type_view_model.g.dart'; + +class SeedTypeViewModel = SeedTypeViewModelBase with _$SeedTypeViewModel; + +abstract class SeedTypeViewModelBase with Store { + SeedTypeViewModelBase(this._appStore); + + @computed + SeedType get moneroSeedType => _appStore.settingsStore.moneroSeedType; + + @action + void setMoneroSeedType(SeedType seedType) => _appStore.settingsStore.moneroSeedType = seedType; + + final AppStore _appStore; +} diff --git a/lib/view_model/send/output.dart b/lib/view_model/send/output.dart index 2e696e16f..73fb535f2 100644 --- a/lib/view_model/send/output.dart +++ b/lib/view_model/send/output.dart @@ -4,6 +4,8 @@ import 'package:cake_wallet/entities/parse_address_from_domain.dart'; import 'package:cake_wallet/entities/parsed_address.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/haven/haven.dart'; +import 'package:cake_wallet/polygon/polygon.dart'; +import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/src/screens/send/widgets/extract_address_from_parsed.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:flutter/material.dart'; @@ -27,7 +29,8 @@ const String cryptoNumberPattern = '0.0'; class Output = OutputBase with _$Output; abstract class OutputBase with Store { - OutputBase(this._wallet, this._settingsStore, this._fiatConversationStore, this.cryptoCurrencyHandler) + OutputBase( + this._wallet, this._settingsStore, this._fiatConversationStore, this.cryptoCurrencyHandler) : _cryptoNumberFormat = NumberFormat(cryptoNumberPattern), key = UniqueKey(), sendAll = false, @@ -65,8 +68,7 @@ abstract class OutputBase with Store { @computed bool get isParsedAddress => - parsedAddress.parseFrom != ParseFrom.notParsed && - parsedAddress.name.isNotEmpty; + parsedAddress.parseFrom != ParseFrom.notParsed && parsedAddress.name.isNotEmpty; @computed int get formattedCryptoAmount { @@ -83,8 +85,7 @@ abstract class OutputBase with Store { case WalletType.bitcoin: case WalletType.litecoin: case WalletType.bitcoinCash: - _amount = - bitcoin!.formatterStringDoubleToBitcoinAmount(_cryptoAmount); + _amount = bitcoin!.formatterStringDoubleToBitcoinAmount(_cryptoAmount); break; case WalletType.haven: _amount = haven!.formatterMoneroParseAmount(amount: _cryptoAmount); @@ -92,6 +93,9 @@ abstract class OutputBase with Store { case WalletType.ethereum: _amount = ethereum!.formatterEthereumParseAmount(_cryptoAmount); break; + case WalletType.polygon: + _amount = polygon!.formatterPolygonParseAmount(_cryptoAmount); + break; default: break; } @@ -130,6 +134,10 @@ abstract class OutputBase with Store { if (_wallet.type == WalletType.ethereum) { return ethereum!.formatterEthereumAmountToDouble(amount: BigInt.from(fee)); } + + if (_wallet.type == WalletType.polygon) { + return polygon!.formatterPolygonAmountToDouble(amount: BigInt.from(fee)); + } } catch (e) { print(e.toString()); } @@ -140,10 +148,11 @@ abstract class OutputBase with Store { @computed String get estimatedFeeFiatAmount { try { - final currency = _wallet.type == WalletType.ethereum ? _wallet.currency : cryptoCurrencyHandler(); + final currency = isEVMCompatibleChain(_wallet.type) + ? _wallet.currency + : cryptoCurrencyHandler(); final fiat = calculateFiatAmountRaw( - price: _fiatConversationStore.prices[currency]!, - cryptoAmount: estimatedFee); + price: _fiatConversationStore.prices[currency]!, cryptoAmount: estimatedFee); return fiat; } catch (_) { return '0.00'; @@ -240,6 +249,7 @@ abstract class OutputBase with Store { maximumFractionDigits = 12; break; case WalletType.ethereum: + case WalletType.polygon: maximumFractionDigits = 12; break; default: diff --git a/lib/view_model/send/send_template_view_model.dart b/lib/view_model/send/send_template_view_model.dart index b881ed71f..007c4b8c0 100644 --- a/lib/view_model/send/send_template_view_model.dart +++ b/lib/view_model/send/send_template_view_model.dart @@ -50,7 +50,9 @@ abstract class SendTemplateViewModelBase with Store { TemplateValidator get templateValidator => TemplateValidator(); bool get hasMultiRecipient => - _wallet.type != WalletType.haven && _wallet.type != WalletType.ethereum; + _wallet.type != WalletType.haven && + _wallet.type != WalletType.ethereum && + _wallet.type != WalletType.polygon; @computed CryptoCurrency get cryptoCurrency => _wallet.currency; diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index f9de7ea6d..6acc2831e 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -5,6 +5,8 @@ import 'package:cake_wallet/nano/nano.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/store/app_store.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'; @@ -42,7 +44,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor void onWalletChange(wallet) { currencies = wallet.balance.keys.toList(); selectedCryptoCurrency = wallet.currency; - hasMultipleTokens = wallet.type == WalletType.ethereum; + hasMultipleTokens = isEVMCompatibleChain(wallet.type); } SendViewModelBase( @@ -55,7 +57,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor ) : state = InitialExecutionState(), currencies = appStore.wallet!.balance.keys.toList(), selectedCryptoCurrency = appStore.wallet!.currency, - hasMultipleTokens = appStore.wallet!.type == WalletType.ethereum, + hasMultipleTokens = isEVMCompatibleChain(appStore.wallet!.type), outputs = ObservableList(), _settingsStore = appStore.settingsStore, fiatFromSettings = appStore.settingsStore.fiatCurrency, @@ -119,7 +121,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor try { if (pendingTransaction != null) { final currency = - walletType == WalletType.ethereum ? wallet.currency : selectedCryptoCurrency; + isEVMCompatibleChain(walletType) ? wallet.currency : selectedCryptoCurrency; final fiat = calculateFiatAmount( price: _fiatConversationStore.prices[currency]!, cryptoAmount: pendingTransaction!.feeFormatted); @@ -372,6 +374,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor priority: priority!, currency: selectedCryptoCurrency); case WalletType.nano: return nano!.createNanoTransactionCredentials(outputs); + case WalletType.polygon: + return polygon!.createPolygonTransactionCredentials(outputs, + priority: priority!, currency: selectedCryptoCurrency); default: throw Exception('Unexpected wallet type: ${wallet.type}'); } @@ -412,11 +417,15 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor WalletType walletType, CryptoCurrency currency, ) { - if (walletType == WalletType.ethereum || walletType == WalletType.haven) { + if (walletType == WalletType.ethereum || + walletType == WalletType.polygon || + walletType == WalletType.haven) { if (error.contains('gas required exceeds allowance') || error.contains('insufficient funds for')) { return S.current.do_not_have_enough_gas_asset(currency.toString()); } + + return error; } return error; diff --git a/lib/view_model/settings/other_settings_view_model.dart b/lib/view_model/settings/other_settings_view_model.dart index b4ca46f70..e44eb8fc7 100644 --- a/lib/view_model/settings/other_settings_view_model.dart +++ b/lib/view_model/settings/other_settings_view_model.dart @@ -1,6 +1,7 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart'; -import 'package:cake_wallet/entities/buy_provider_types.dart'; +import 'package:cake_wallet/entities/provider_types.dart'; import 'package:cake_wallet/entities/priority_for_wallet_type.dart'; +import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cw_core/balance.dart'; import 'package:cw_core/transaction_history.dart'; @@ -13,14 +14,15 @@ import 'package:package_info/package_info.dart'; part 'other_settings_view_model.g.dart'; -class OtherSettingsViewModel = OtherSettingsViewModelBase with _$OtherSettingsViewModel; +class OtherSettingsViewModel = OtherSettingsViewModelBase + with _$OtherSettingsViewModel; abstract class OtherSettingsViewModelBase with Store { OtherSettingsViewModelBase(this._settingsStore, this._wallet) : walletType = _wallet.type, currentVersion = '' { - PackageInfo.fromPlatform() - .then((PackageInfo packageInfo) => currentVersion = packageInfo.version); + PackageInfo.fromPlatform().then( + (PackageInfo packageInfo) => currentVersion = packageInfo.version); final priority = _settingsStore.priority[_wallet.type]; final priorities = priorityForWalletType(_wallet.type); @@ -31,7 +33,8 @@ abstract class OtherSettingsViewModelBase with Store { } final WalletType walletType; - final WalletBase, TransactionInfo> _wallet; + final WalletBase, + TransactionInfo> _wallet; @observable String currentVersion; @@ -50,15 +53,31 @@ abstract class OtherSettingsViewModelBase with Store { } @computed - bool get changeRepresentativeEnabled { - if (_wallet.type == WalletType.nano || _wallet.type == WalletType.banano) { - return true; - } + bool get changeRepresentativeEnabled => + _wallet.type == WalletType.nano || _wallet.type == WalletType.banano; - return false; + @computed + bool get isEnabledBuyAction => + !_settingsStore.disableBuy && _wallet.type != WalletType.haven; + + @computed + bool get isEnabledSellAction => + !_settingsStore.disableSell && _wallet.type != WalletType.haven; + + List get availableBuyProvidersTypes { + return ProvidersHelper.getAvailableBuyProviderTypes(walletType); } - - BuyProviderType get buyProviderType { return _settingsStore.defaultBuyProvider; } + + List get availableSellProvidersTypes => + ProvidersHelper.getAvailableSellProviderTypes(walletType); + + ProviderType get buyProviderType => + _settingsStore.defaultBuyProviders[walletType] ?? + ProviderType.askEachTime; + + ProviderType get sellProviderType => + _settingsStore.defaultSellProviders[walletType] ?? + ProviderType.askEachTime; String getDisplayPriority(dynamic priority) { final _priority = priority as TransactionPriority; @@ -73,16 +92,29 @@ abstract class OtherSettingsViewModelBase with Store { return priority.toString(); } - String getBuyProviderType (dynamic buyProviderType) { - final _buyProviderType = buyProviderType as BuyProviderType; + String getBuyProviderType(dynamic buyProviderType) { + final _buyProviderType = buyProviderType as ProviderType; + return _buyProviderType == ProviderType.askEachTime + ? S.current.ask_each_time + : _buyProviderType.title; + } - return _buyProviderType.toString(); + String getSellProviderType(dynamic sellProviderType) { + final _sellProviderType = sellProviderType as ProviderType; + return _sellProviderType == ProviderType.askEachTime + ? S.current.ask_each_time + : _sellProviderType.title; } void onDisplayPrioritySelected(TransactionPriority priority) => _settingsStore.priority[_wallet.type] = priority; - void onBuyProviderTypeSelected(BuyProviderType buyProviderType) => - _settingsStore.defaultBuyProvider = buyProviderType; + @action + ProviderType onBuyProviderTypeSelected(ProviderType buyProviderType) => + _settingsStore.defaultBuyProviders[walletType] = buyProviderType; + @action + ProviderType onSellProviderTypeSelected( + ProviderType sellProviderType) => + _settingsStore.defaultSellProviders[walletType] = sellProviderType; } diff --git a/lib/view_model/settings/privacy_settings_view_model.dart b/lib/view_model/settings/privacy_settings_view_model.dart index b3ffeb353..e4dd86b37 100644 --- a/lib/view_model/settings/privacy_settings_view_model.dart +++ b/lib/view_model/settings/privacy_settings_view_model.dart @@ -1,6 +1,7 @@ import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; +import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/store/settings_store.dart'; import 'package:cw_core/balance.dart'; import 'package:cw_core/transaction_history.dart'; @@ -12,8 +13,7 @@ import 'package:cake_wallet/entities/fiat_api_mode.dart'; part 'privacy_settings_view_model.g.dart'; -class -PrivacySettingsViewModel = PrivacySettingsViewModelBase with _$PrivacySettingsViewModel; +class PrivacySettingsViewModel = PrivacySettingsViewModelBase with _$PrivacySettingsViewModel; abstract class PrivacySettingsViewModelBase with Store { PrivacySettingsViewModelBase(this._settingsStore, this._wallet); @@ -58,6 +58,9 @@ abstract class PrivacySettingsViewModelBase with Store { @computed bool get useEtherscan => _settingsStore.useEtherscan; + @computed + bool get usePolygonScan => _settingsStore.usePolygonScan; + @computed bool get lookupTwitter => _settingsStore.lookupsTwitter; @@ -78,6 +81,8 @@ abstract class PrivacySettingsViewModelBase with Store { bool get canUseEtherscan => _wallet.type == WalletType.ethereum; + bool get canUsePolygonScan => _wallet.type == WalletType.polygon; + @action void setShouldSaveRecipientAddress(bool value) => _settingsStore.shouldSaveRecipientAddress = value; @@ -120,4 +125,10 @@ abstract class PrivacySettingsViewModelBase with Store { _settingsStore.useEtherscan = value; ethereum!.updateEtherscanUsageState(_wallet, value); } + + @action + void setUsePolygonScan(bool value) { + _settingsStore.usePolygonScan = value; + polygon!.updatePolygonScanUsageState(_wallet, value); + } } diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart index a8c892284..4e17866cb 100644 --- a/lib/view_model/transaction_details_view_model.dart +++ b/lib/view_model/transaction_details_view_model.dart @@ -51,6 +51,9 @@ abstract class TransactionDetailsViewModelBase with Store { case WalletType.nano: _addNanoListItems(tx, dateFormat); break; + case WalletType.polygon: + _addPolygonListItems(tx, dateFormat); + break; default: break; } @@ -125,7 +128,9 @@ abstract class TransactionDetailsViewModelBase with Store { case WalletType.nano: return 'https://nanolooker.com/block/${txId}'; case WalletType.banano: - return 'https://bananolooker.com/block/${txId}'; + return 'https://bananolooker.com/block/${txId}'; + case WalletType.polygon: + return 'https://polygonscan.com/tx/${txId}'; default: return ''; } @@ -148,6 +153,8 @@ abstract class TransactionDetailsViewModelBase with Store { return S.current.view_transaction_on + 'nanolooker.com'; case WalletType.banano: return S.current.view_transaction_on + 'bananolooker.com'; + case WalletType.polygon: + return S.current.view_transaction_on + 'polygonscan.com'; default: return ''; } @@ -237,7 +244,6 @@ abstract class TransactionDetailsViewModelBase with Store { items.addAll(_items); } - void _addNanoListItems(TransactionInfo tx, DateFormat dateFormat) { final _items = [ StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id), @@ -250,4 +256,21 @@ abstract class TransactionDetailsViewModelBase with Store { items.addAll(_items); } + + void _addPolygonListItems(TransactionInfo tx, DateFormat dateFormat) { + final _items = [ + StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id), + StandartListItem( + title: S.current.transaction_details_date, value: dateFormat.format(tx.date)), + StandartListItem(title: S.current.confirmations, value: tx.confirmations.toString()), + StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'), + StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()), + if (tx.feeFormatted()?.isNotEmpty ?? false) + StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!), + if (showRecipientAddress && tx.to != null) + StandartListItem(title: S.current.transaction_details_recipient_address, value: tx.to!), + ]; + + items.addAll(_items); + } } diff --git a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart index 4d5eefdb7..ade279124 100644 --- a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart @@ -1,6 +1,7 @@ import 'package:cake_wallet/core/wallet_change_listener_view_model.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/entities/fiat_currency.dart'; +import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart'; import 'package:cake_wallet/store/yat/yat_store.dart'; import 'package:cw_core/currency.dart'; @@ -139,6 +140,22 @@ class NanoURI extends PaymentURI { } } +class PolygonURI extends PaymentURI { + PolygonURI({required String amount, required String address}) + : super(amount: amount, address: address); + + @override + String toString() { + var base = 'polygon:' + address; + + if (amount.isNotEmpty) { + base += '?amount=${amount.replaceAll(',', '.')}'; + } + + return base; + } +} + abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewModel with Store { WalletAddressListViewModelBase({ required AppStore appStore, @@ -216,6 +233,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo return NanoURI(amount: amount, address: address.address); } + if (wallet.type == WalletType.polygon) { + return PolygonURI(amount: amount, address: address.address); + } + throw Exception('Unexpected type: ${type.toString()}'); } @@ -272,6 +293,12 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress)); } + if (wallet.type == WalletType.polygon) { + final primaryAddress = polygon!.getAddress(wallet); + + addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress)); + } + return addressList; } diff --git a/lib/view_model/wallet_creation_vm.dart b/lib/view_model/wallet_creation_vm.dart index ddf8843ef..af58d97a8 100644 --- a/lib/view_model/wallet_creation_vm.dart +++ b/lib/view_model/wallet_creation_vm.dart @@ -74,7 +74,7 @@ abstract class WalletCreationVMBase with Store { : await process(credentials); walletInfo.address = wallet.walletAddresses.address; await _walletInfoSource.add(walletInfo); - _appStore.changeCurrentWallet(wallet); + await _appStore.changeCurrentWallet(wallet); getIt.get().registerSyncTask(); _appStore.authenticationStore.allowed(); state = ExecutedSuccessfullyState(); diff --git a/lib/view_model/wallet_keys_view_model.dart b/lib/view_model/wallet_keys_view_model.dart index d9de9473d..f931fec19 100644 --- a/lib/view_model/wallet_keys_view_model.dart +++ b/lib/view_model/wallet_keys_view_model.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/store/app_store.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_info.dart'; @@ -9,6 +10,7 @@ import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.d import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/haven/haven.dart'; import 'package:cw_monero/api/wallet.dart' as monero_wallet; +import 'package:polyseed/polyseed.dart'; part 'wallet_keys_view_model.g.dart'; @@ -19,7 +21,8 @@ abstract class WalletKeysViewModelBase with Store { : title = _appStore.wallet!.type == WalletType.bitcoin || _appStore.wallet!.type == WalletType.litecoin || _appStore.wallet!.type == WalletType.bitcoinCash || - _appStore.wallet!.type == WalletType.ethereum + _appStore.wallet!.type == WalletType.ethereum || + _appStore.wallet!.type == WalletType.polygon ? S.current.wallet_seed : S.current.wallet_keys, _restoreHeight = _appStore.wallet!.walletInfo.restoreHeight, @@ -72,6 +75,15 @@ abstract class WalletKeysViewModelBase with Store { StandartListItem(title: S.current.view_key_private, value: keys['privateViewKey']!), StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed!), ]); + + if (_appStore.wallet?.seed != null && Polyseed.isValidSeed(_appStore.wallet!.seed!)) { + final lang = PolyseedLang.getByPhrase(_appStore.wallet!.seed!); + final legacyLang = _getLegacySeedLang(lang); + final legacySeed = + Polyseed.decode(_appStore.wallet!.seed!, lang, PolyseedCoin.POLYSEED_MONERO) + .toLegacySeed(legacyLang); + items.add(StandartListItem(title: S.current.wallet_seed_legacy, value: legacySeed)); + } } if (_appStore.wallet!.type == WalletType.haven) { @@ -98,7 +110,7 @@ abstract class WalletKeysViewModelBase with Store { ]); } - if (_appStore.wallet!.type == WalletType.ethereum) { + if (isEVMCompatibleChain(_appStore.wallet!.type)) { items.addAll([ if (_appStore.wallet!.privateKey != null) StandartListItem(title: S.current.private_key, value: _appStore.wallet!.privateKey!), @@ -151,6 +163,8 @@ abstract class WalletKeysViewModelBase with Store { return 'nano-wallet'; case WalletType.banano: return 'banano-wallet'; + case WalletType.polygon: + return 'polygon-wallet'; default: throw Exception('Unexpected wallet type: ${_appStore.wallet!.toString()}'); } @@ -203,4 +217,23 @@ abstract class WalletKeysViewModelBase with Store { } String getRoundedRestoreHeight(int height) => ((height / 1000).floor() * 1000).toString(); + + LegacySeedLang _getLegacySeedLang(PolyseedLang lang) { + switch (lang.nameEnglish) { + case "Spanish": + return LegacySeedLang.getByEnglishName("Spanish"); + case "French": + return LegacySeedLang.getByEnglishName("French"); + case "Italian": + return LegacySeedLang.getByEnglishName("Italian"); + case "Japanese": + return LegacySeedLang.getByEnglishName("Japanese"); + case "Portuguese": + return LegacySeedLang.getByEnglishName("Portuguese"); + case "Chinese (Simplified)": + return LegacySeedLang.getByEnglishName("Chinese (simplified)"); + default: + return LegacySeedLang.getByEnglishName("English"); + } + } } 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 0abebba15..407b6d3bc 100644 --- a/lib/view_model/wallet_list/wallet_list_view_model.dart +++ b/lib/view_model/wallet_list/wallet_list_view_model.dart @@ -1,5 +1,6 @@ import 'package:cake_wallet/core/auth_service.dart'; import 'package:cake_wallet/core/wallet_loading_service.dart'; +import 'package:cake_wallet/entities/wallet_list_order_types.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/store/app_store.dart'; @@ -19,8 +20,9 @@ abstract class WalletListViewModelBase with Store { this._walletLoadingService, this._authService, ) : wallets = ObservableList() { - updateList(); + setOrderType(_appStore.settingsStore.walletListOrder); reaction((_) => _appStore.wallet, (_) => updateList()); + updateList(); } @observable @@ -43,11 +45,14 @@ abstract class WalletListViewModelBase with Store { @action Future loadWallet(WalletListItem walletItem) async { - final wallet = - await _walletLoadingService.load(walletItem.type, walletItem.name); - _appStore.changeCurrentWallet(wallet); + final wallet = await _walletLoadingService.load(walletItem.type, walletItem.name); + await _appStore.changeCurrentWallet(wallet); } + WalletListOrderType? get orderType => _appStore.settingsStore.walletListOrder; + + bool get ascending => _appStore.settingsStore.walletListAscending; + @action void updateList() { wallets.clear(); @@ -57,14 +62,105 @@ abstract class WalletListViewModelBase with Store { name: info.name, type: info.type, key: info.key, - isCurrent: info.name == _appStore.wallet?.name && - info.type == _appStore.wallet?.type, + isCurrent: info.name == _appStore.wallet?.name && info.type == _appStore.wallet?.type, isEnabled: availableWalletTypes.contains(info.type), ), ), ); } + Future reorderAccordingToWalletList() async { + if (wallets.isEmpty) { + updateList(); + return; + } + + _appStore.settingsStore.walletListOrder = WalletListOrderType.Custom; + + // make a copy of the walletInfoSource: + List walletInfoSourceCopy = _walletInfoSource.values.toList(); + // delete all wallets from walletInfoSource: + await _walletInfoSource.clear(); + + // add wallets from wallets list in order of wallets list, by name: + for (WalletListItem wallet in wallets) { + for (int i = 0; i < walletInfoSourceCopy.length; i++) { + if (walletInfoSourceCopy[i].name == wallet.name) { + await _walletInfoSource.add(walletInfoSourceCopy[i]); + walletInfoSourceCopy.removeAt(i); + break; + } + } + } + + updateList(); + } + + Future sortGroupByType() async { + // sort the wallets by type: + List walletInfoSourceCopy = _walletInfoSource.values.toList(); + await _walletInfoSource.clear(); + if (ascending) { + walletInfoSourceCopy.sort((a, b) => a.type.toString().compareTo(b.type.toString())); + } else { + walletInfoSourceCopy.sort((a, b) => b.type.toString().compareTo(a.type.toString())); + } + await _walletInfoSource.addAll(walletInfoSourceCopy); + updateList(); + } + + Future sortAlphabetically() async { + // sort the wallets alphabetically: + List walletInfoSourceCopy = _walletInfoSource.values.toList(); + await _walletInfoSource.clear(); + if (ascending) { + walletInfoSourceCopy.sort((a, b) => a.name.compareTo(b.name)); + } else { + walletInfoSourceCopy.sort((a, b) => b.name.compareTo(a.name)); + } + await _walletInfoSource.addAll(walletInfoSourceCopy); + updateList(); + } + + Future sortByCreationDate() async { + // sort the wallets by creation date: + List walletInfoSourceCopy = _walletInfoSource.values.toList(); + await _walletInfoSource.clear(); + if (ascending) { + walletInfoSourceCopy.sort((a, b) => a.date.compareTo(b.date)); + } else { + walletInfoSourceCopy.sort((a, b) => b.date.compareTo(a.date)); + } + await _walletInfoSource.addAll(walletInfoSourceCopy); + updateList(); + } + + void setAscending(bool ascending) { + _appStore.settingsStore.walletListAscending = ascending; + } + + Future setOrderType(WalletListOrderType? type) async { + if (type == null) return; + + _appStore.settingsStore.walletListOrder = type; + + switch (type) { + case WalletListOrderType.CreationDate: + await sortByCreationDate(); + break; + case WalletListOrderType.Alphabetical: + await sortAlphabetically(); + break; + case WalletListOrderType.GroupByType: + await sortGroupByType(); + break; + case WalletListOrderType.Custom: + default: + await reorderAccordingToWalletList(); + break; + } + } + bool checkIfAuthRequired() { return _authService.requireAuth(); } diff --git a/lib/view_model/wallet_new_vm.dart b/lib/view_model/wallet_new_vm.dart index 696559198..d4a0c4c00 100644 --- a/lib/view_model/wallet_new_vm.dart +++ b/lib/view_model/wallet_new_vm.dart @@ -13,6 +13,9 @@ import 'package:cw_core/wallet_type.dart'; import 'package:cake_wallet/view_model/wallet_creation_vm.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/haven/haven.dart'; +import 'advanced_privacy_settings_view_model.dart'; + +import '../polygon/polygon.dart'; part 'wallet_new_vm.g.dart'; @@ -20,36 +23,57 @@ class WalletNewVM = WalletNewVMBase with _$WalletNewVM; abstract class WalletNewVMBase extends WalletCreationVM with Store { WalletNewVMBase(AppStore appStore, WalletCreationService walletCreationService, - Box walletInfoSource, + Box walletInfoSource, this.advancedPrivacySettingsViewModel, {required WalletType type}) : selectedMnemonicLanguage = '', super(appStore, walletInfoSource, walletCreationService, type: type, isRecovery: false); + final AdvancedPrivacySettingsViewModel advancedPrivacySettingsViewModel; + @observable String selectedMnemonicLanguage; bool get hasLanguageSelector => type == WalletType.monero || type == WalletType.haven; + int get seedPhraseWordsLength { + switch (type) { + case WalletType.monero: + if(advancedPrivacySettingsViewModel.isPolySeed) { + return 16; + } + return 25; + case WalletType.ethereum: + case WalletType.bitcoinCash: + return advancedPrivacySettingsViewModel.seedPhraseLength.value; + default: + return 24; + } + } + + bool get hasSeedType => type == WalletType.monero; + @override WalletCredentials getCredentials(dynamic _options) { - final options = _options as List; + final options = _options as List?; switch (type) { case WalletType.monero: return monero!.createMoneroNewWalletCredentials( - name: name, language: options.first as String, isPolyseed: options.last as bool); + name: name, language: options!.first as String, isPolyseed: options.last as bool); case WalletType.bitcoin: return bitcoin!.createBitcoinNewWalletCredentials(name: name); case WalletType.litecoin: return bitcoin!.createBitcoinNewWalletCredentials(name: name); case WalletType.haven: return haven!.createHavenNewWalletCredentials( - name: name, language: options.first as String); + name: name, language: options!.first as String); case WalletType.ethereum: return ethereum!.createEthereumNewWalletCredentials(name: name); case WalletType.bitcoinCash: return bitcoinCash!.createBitcoinCashNewWalletCredentials(name: name); case WalletType.nano: return nano!.createNanoNewWalletCredentials(name: name); + case WalletType.polygon: + return polygon!.createPolygonNewWalletCredentials(name: name); default: throw Exception('Unexpected type: ${type.toString()}'); } diff --git a/lib/view_model/wallet_restore_view_model.dart b/lib/view_model/wallet_restore_view_model.dart index 30a69ef4a..9a4f6f8c7 100644 --- a/lib/view_model/wallet_restore_view_model.dart +++ b/lib/view_model/wallet_restore_view_model.dart @@ -4,6 +4,7 @@ import 'package:cake_wallet/nano/nano.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cw_core/nano_account_info_response.dart'; import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; +import 'package:cake_wallet/polygon/polygon.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/store/app_store.dart'; @@ -28,8 +29,10 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { {required WalletType type}) : hasSeedLanguageSelector = type == WalletType.monero || type == WalletType.haven, hasBlockchainHeightLanguageSelector = type == WalletType.monero || type == WalletType.haven, - hasRestoreFromPrivateKey = - type == WalletType.ethereum || type == WalletType.nano || type == WalletType.banano, + hasRestoreFromPrivateKey = type == WalletType.ethereum || + type == WalletType.polygon || + type == WalletType.nano || + type == WalletType.banano, isButtonEnabled = false, mode = WalletRestoreMode.seed, super(appStore, walletInfoSource, walletCreationService, type: type, isRecovery: true) { @@ -37,6 +40,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { case WalletType.monero: case WalletType.haven: case WalletType.ethereum: + case WalletType.polygon: availableModes = WalletRestoreMode.values; break; case WalletType.nano: @@ -100,20 +104,21 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { name: name, height: height, mnemonic: seed, password: password); case WalletType.ethereum: return ethereum!.createEthereumRestoreWalletFromSeedCredentials( - name: name, - mnemonic: seed, - password: password); + name: name, mnemonic: seed, password: password); case WalletType.bitcoinCash: return bitcoinCash!.createBitcoinCashRestoreWalletFromSeedCredentials( - name: name, - mnemonic: seed, - password: password); + name: name, mnemonic: seed, password: password); case WalletType.nano: return nano!.createNanoRestoreWalletFromSeedCredentials( + name: name, + mnemonic: seed, + password: password, + derivationType: derivationInfo!.derivationType!); + case WalletType.polygon: + return polygon!.createPolygonRestoreWalletFromSeedCredentials( name: name, mnemonic: seed, password: password, - derivationType: derivationInfo!.derivationType!, ); default: break; @@ -157,10 +162,16 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { case WalletType.nano: return nano!.createNanoRestoreWalletFromKeysCredentials( + name: name, + password: password, + seedKey: options['private_key'] as String, + derivationType: options["derivationType"] as DerivationType); + case WalletType.polygon: + return polygon!.createPolygonRestoreWalletFromPrivateKey( name: name, password: password, - seedKey: options['private_key'] as String, - derivationType: options["derivationType"] as DerivationType); + privateKey: options['private_key'] as String, + ); default: break; } diff --git a/model_generator.sh b/model_generator.sh index 50cb3d353..32d863aeb 100755 --- a/model_generator.sh +++ b/model_generator.sh @@ -5,4 +5,5 @@ cd cw_haven && flutter pub get && flutter packages pub run build_runner build -- cd cw_ethereum && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. +cd cw_polygon && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. flutter packages pub run build_runner build --delete-conflicting-outputs \ No newline at end of file diff --git a/pubspec_base.yaml b/pubspec_base.yaml index f71d38578..b85171ed0 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -96,17 +96,15 @@ dependencies: bitcoin_flutter: git: url: https://github.com/cake-tech/bitcoin_flutter.git - ref: cake-update-v3 + ref: cake-update-v4 fluttertoast: 8.1.4 - tor: - git: - url: https://github.com/cake-tech/tor.git - ref: main +# tor: +# git: +# url: https://github.com/cake-tech/tor.git +# ref: main socks5_proxy: ^1.0.4 flutter_svg: ^2.0.9 - polyseed: - git: - url: https://github.com/cake-tech/polyseed_dart.git + polyseed: ^0.0.2 dev_dependencies: flutter_test: @@ -147,6 +145,7 @@ flutter: - assets/bitcoin_cash_electrum_server_list.yml - assets/nano_node_list.yml - assets/nano_pow_node_list.yml + - assets/polygon_node_list.yml - assets/text/ - assets/faq/ - assets/animation/ diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 47b11c78e..95871b904 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -160,7 +160,7 @@ "seed_title": "سييد", "seed_share": "شارك السييد", "copy": "نسخ", - "seed_language_choose": "الرجاء اختيار لغة السييد:", + "seed_language": "لغة البذور", "seed_choose": "اختر لغة السييد", "seed_language_next": "التالي", "seed_language_english": "إنجليزي", @@ -569,7 +569,7 @@ "always": "دائماً", "minutes_to_pin_code": "${minutes} دقيقة", "disable_exchange": "تعطيل التبادل", - "advanced_privacy_settings": "إعدادات الخصوصية المتقدمة", + "advanced_settings": "إعدادات متقدمة", "settings_can_be_changed_later": "يمكن تغيير هذه الإعدادات لاحقًا في إعدادات التطبيق", "add_custom_node": "إضافة عقدة مخصصة جديدة", "disable_fiat": "تعطيل fiat", @@ -621,11 +621,10 @@ "totp_verification_success": "تم التحقق بنجاح!", "totp_2fa_failure": "شفرة خاطئة. يرجى تجربة رمز مختلف أو إنشاء مفتاح سري جديد. استخدم تطبيق 2FA متوافقًا يدعم الرموز المكونة من 8 أرقام و SHA512.", "enter_totp_code": "الرجاء إدخال رمز TOTP.", - "add_secret_code": "أضف هذا الرمز السري إلى جهاز آخر", + "add_secret_code": " ﺔﻗﺩﺎﺼﻤﻟﺍ ﻖﻴﺒﻄﺗ ﻰﻟﺇ ﻱﺮﺴﻟﺍ ﺰﻣﺮﻟﺍ ﺍﺬﻫ ﻒﺿﺃ ﻭﺃ", "totp_secret_code": "كود TOTP السري", - "important_note": "ملاحظة مهمة", - "setup_2fa_text": "كعكة 2FA ليست آمنة مثل التخزين البارد. تحمي 2FA من الأنواع الأساسية للهجمات ، مثل قيام صديقك بتقديم بصمة إصبعك أثناء نومك. لا تحمي Cake 2FA من جهاز مخترق من قِبل مهاجم متطور. إذا فقدت الوصول إلى رموز 2FA الخاصة بك ، ستفقد إمكانية الوصول إلى هذه المحفظة. سوف تحتاج إلى استعادة محفظتك من بذرة ذاكري. يجب عليك بالتالي الاحتفاظ بنسخة احتياطية من بذور الذاكرة الخاصة بك! علاوة على ذلك ، سيتمكن أي شخص لديه حق الوصول إلى بذرة (بذور) ذاكري من سرقة أموالك ، متجاوزًا Cake 2FA. لن يتمكن فريق دعم الكيك من مساعدتك إذا فقدت الوصول إلى بذرتك ، نظرًا لأن Cake هي المحفظة غير الحافظة.", - "setup_totp_recommended": "إعداد TOTP (موصى به)", + "setup_2fa_text": " .ﻲﻧﺎﺜﻟﺍ ﺔﻗﺩﺎﺼﻤﻟﺍ ﻞﻣﺎﻌﻛ TOTP ﻡﺍﺪﺨﺘﺳﺎﺑ Cake 2FA ﻞﻤﻌﻳ", + "setup_totp_recommended": " TOTP ﺩﺍﺪﻋﺇ", "disable_buy": "تعطيل إجراء الشراء", "disable_sell": "قم بتعطيل إجراء البيع", "cake_2fa_preset": " كعكة 2FA مسبقا", @@ -727,6 +726,9 @@ "require_for_exchanges_to_external_wallets": "ﺔﻴﺟﺭﺎﺧ ﻆﻓﺎﺤﻣ ﻰﻟﺇ ﺕﻻﺩﺎﺒﺘﻟﺍ ﺐﻠﻄﺘﺗ", "camera_permission_is_required": ".ﺍﺮﻴﻣﺎﻜﻟﺍ ﻥﺫﺇ ﺏﻮﻠﻄﻣ", "switchToETHWallet": "ﻯﺮﺧﺃ ﺓﺮﻣ ﺔﻟﻭﺎﺤﻤﻟﺍﻭ Ethereum ﺔﻈﻔﺤﻣ ﻰﻟﺇ ﻞﻳﺪﺒﺘﻟﺍ ﻰﺟﺮﻳ", + "order_by": "ترتيب حسب", + "creation_date": "تاريخ الإنشاء", + "group_by_type": "مجموعة حسب النوع", "importNFTs": "NFTs ﺩﺍﺮﻴﺘﺳﺍ", "noNFTYet": "ﻥﻵﺍ ﻰﺘﺣ NFTs ﺪﺟﻮﻳ ﻻ", "address": " ﻥﺍﻮﻨﻋ", @@ -738,6 +740,9 @@ "unavailable_balance": "ﺮﻓﻮﺘﻣ ﺮﻴﻏ ﺪﻴﺻﺭ", "unavailable_balance_description": ".ﺎﻫﺪﻴﻤﺠﺗ ءﺎﻐﻟﺇ ﺭﺮﻘﺗ ﻰﺘﺣ ﺕﻼﻣﺎﻌﻤﻠﻟ ﻝﻮﺻﻮﻠﻟ ﺔﻠﺑﺎﻗ ﺮﻴﻏ ﺓﺪﻤﺠﻤﻟﺍ ﺓﺪﺻﺭﻷﺍ ﻞﻈﺗ ﺎﻤﻨﻴﺑ ،ﺎﻬﺑ ﺔﺻﺎﺨﻟﺍ ﺕﻼﻣﺎﻌﻤﻟﺍ ﻝﺎﻤﺘﻛﺍ ﺩﺮﺠﻤﺑ ﺔﺣﺎﺘﻣ ﺔﻠﻔﻘﻤﻟﺍ ﺓﺪﺻﺭﻷﺍ ﺢﺒﺼﺘﺳ .ﻚﺑ ﺔﺻﺎﺨﻟﺍ ﺕﻼﻤﻌﻟﺍ ﻲﻓ ﻢﻜﺤﺘﻟﺍ ﺕﺍﺩﺍﺪﻋﺇ ﻲﻓ ﻂﺸﻧ ﻞﻜﺸﺑ ﺎﻫﺪﻴﻤﺠﺘﺑ ﺖﻤﻗ", "unspent_change": "يتغير", + "setup_warning_2fa_text": ".ﺩﺭﺎﺒﻟﺍ ﻦﻳﺰﺨﺘﻟﺍ ﻞﺜﻣ ﺔﻨﻣﺁ ﺖﺴﻴﻟ ﺎﻬﻧﺇ .ﺔﻈﻔﺤﻤﻟﺍ ﻲﻓ ﺔﻨﻴﻌﻣ ﺕﺍءﺍﺮﺟﻹ ﺔﻴﻧﺎﺛ ﺔﻗﺩﺎﺼﻣ ﺔﺑﺎﺜ", + "scan_qr_on_device": " ﺮﺧﺁ ﺯﺎﻬﺟ ﻰﻠﻋ ﺎﻴًﺋﻮﺿ ﺍﺬﻫ ﺔﻌﻳﺮﺴﻟﺍ ﺔﺑﺎﺠﺘﺳﻻﺍ ﺰﻣﺭ ﺢﺴﻤﺑ ﻢﻗ", + "how_to_use": " ﻞﻤﻌﺘﺴﺗ ﻒﻴﻛ", "seed_hex_form": "بذور المحفظة (شكل عرافة)", "tor_connection": "ﺭﻮﺗ ﻝﺎﺼﺗﺍ", "seedtype": "البذور", @@ -745,5 +750,14 @@ "seedtype_polyseed": "بوليسيد (16 كلمة)", "seed_language_czech": "التشيكية", "seed_language_korean": "الكورية", - "seed_language_chinese_traditional": "تقاليد صينية)" + "seed_language_chinese_traditional": "تقاليد صينية)", + "ascending": "تصاعدي", + "descending": "النزول", + "dfx_option_description": "ﺎﺑﻭﺭﻭﺃ ﻲﻓ ﺕﺎﻛﺮﺸﻟﺍﻭ ﺔﺋﺰﺠﺘﻟﺍ ءﻼﻤﻌﻟ .ﻲﻓﺎﺿﺇ KYC ﻥﻭﺪﺑ ﻭﺭﻮﻳ 990 ﻰﻟﺇ ﻞﺼﻳ ﺎﻣ .ﻱﺮﺴﻳﻮﺴﻟﺍ", + "polygonscan_history": "ﻥﺎﻜﺴﻧﻮﺠﻴﻟﻮﺑ ﺦﻳﺭﺎﺗ", + "wallet_seed_legacy": "بذرة محفظة قديمة", + "default_sell_provider": " ﻲﺿﺍﺮﺘﻓﻻﺍ ﻊﻴﺒﻟﺍ ﺩﻭﺰﻣ", + "select_sell_provider_notice": ".ﻖﻴﺒﻄﺘﻟﺍ ﺕﺍﺩﺍﺪﻋﺇ ﻲﻓ ﻚﺑ ﺹﺎﺨﻟﺍ ﻲﺿﺍﺮﺘﻓﻻﺍ ﻊﻴﺒﻟﺍ ﺩﻭﺰﻣ ﻦﻴﻴﻌﺗ ﻖﻳﺮﻃ ﻦﻋ ﺔﺷﺎﺸﻟﺍ ﻩﺬﻫ ﻲﻄﺨﺗ", + "custom_drag": "مخصص (عقد وسحب)", + "switchToEVMCompatibleWallet": " (Ethereum، Polygon) ﻯﺮﺧﺃ ﺓﺮﻣ ﺔﻟﻭﺎﺤﻤﻟﺍﻭ EVM ﻊﻣ ﺔﻘﻓﺍﻮﺘﻣ ﺔﻈﻔﺤﻣ ﻰﻟﺇ ﻞﻳﺪﺒﺘﻟﺍ ﻰﺟﺮﻳ" } diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 162e85cad..197dbe552 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -160,7 +160,7 @@ "seed_title": "Seed", "seed_share": "Споделяне на seed", "copy": "Копиране", - "seed_language_choose": "Моля, изберете език на seed-а:", + "seed_language": "Език на семената", "seed_choose": "Изберете език на seed-а", "seed_language_next": "Следващ", "seed_language_english": "Английски", @@ -569,7 +569,7 @@ "always": "Винаги", "minutes_to_pin_code": "${minute} минути", "disable_exchange": "Деактивиране на борса", - "advanced_privacy_settings": "Допълнителни настройки за поверителност", + "advanced_settings": "Разширени настройки", "settings_can_be_changed_later": "Тези настройки могат да бъдат променени по-късно от приложението", "add_custom_node": "Добавяне на нов персонализиран Node", "disable_fiat": "Деактивиране на fiat", @@ -617,11 +617,10 @@ "totp_verification_success": "Проверката е успешна!", "totp_2fa_failure": "Грешен код. Моля, опитайте с различен код или генерирайте нов таен ключ. Използвайте съвместимо 2FA приложение, което поддържа 8-цифрени кодове и SHA512.", "enter_totp_code": "Моля, въведете TOTP кода.", - "add_secret_code": "Добавете този таен код към друго устройство", + "add_secret_code": "Или добавете този таен код към приложение за удостоверяване", "totp_secret_code": "TOTP таен код", - "important_note": "Важна забележка", - "setup_2fa_text": "Тортата 2FA НЕ е толкова сигурна, колкото хладилното съхранение. 2FA защитава срещу основни видове атаки, като например вашият приятел да предостави вашия пръстов отпечатък, докато спите.\n\n Cake 2FA НЕ защитава срещу компрометирано устройство от сложен хакер.\n\n Ако загубите достъп до своите 2FA кодове , ЩЕ ЗАГУБИТЕ ДОСТЪП ДО ТОЗИ ПОРТФЕЙЛ. Ще трябва да възстановите портфейла си от мнемонично семе. ЗАТОВА ТРЯБВА ДА НАПРАВИТЕ РЕЗЕРВНО КОПИЕ НА ВАШИТЕ МНЕМОНИЧНИ СЕМЕНА! Освен това, някой с достъп до вашите мнемонични начални точки ще може да открадне вашите средства, заобикаляйки Cake 2FA.\n\n Персоналът по поддръжката на Cake няма да може да ви помогне, ако загубите достъп до вашите мнемонични начални стойности, тъй като Cake е портфейл без попечителство.", - "setup_totp_recommended": "Настройка на TOTP (препоръчително)", + "setup_2fa_text": "Cake 2FA работи с помощта на TOTP като втори фактор за удостоверяване.\n\nTOTP на Cake 2FA изисква SHA-512 и поддръжка на 8 цифри; това осигурява повишена сигурност. Повече информация и поддържани приложения можете да намерите в ръководството.", + "setup_totp_recommended": "Настройка на TOTP", "disable_buy": "Деактивирайте действието за покупка", "disable_sell": "Деактивирайте действието за продажба", "auto_generate_subaddresses": "Автоматично генериране на подадреси", @@ -723,6 +722,9 @@ "require_for_exchanges_to_external_wallets": "Изискване за обмен към външни портфейли", "camera_permission_is_required": "Изисква се разрешение за камерата.\nМоля, активирайте го от настройките на приложението.", "switchToETHWallet": "Моля, преминете към портфейл Ethereum и опитайте отново", + "order_by": "Подредени по", + "creation_date": "Дата на създаване", + "group_by_type": "Група по вид", "importNFTs": "Импортирайте NFT", "noNFTYet": "Все още няма NFT", "address": "Адрес", @@ -735,11 +737,23 @@ "unavailable_balance_description": "Неналично салдо: Тази обща сума включва средства, които са заключени в чакащи транзакции и тези, които сте замразили активно в настройките за контрол на монетите. Заключените баланси ще станат достъпни, след като съответните им транзакции бъдат завършени, докато замразените баланси остават недостъпни за транзакции, докато не решите да ги размразите.", "unspent_change": "Промяна", "tor_connection": "Tor връзка", + "setup_warning_2fa_text": "Cake 2FA е второ удостоверяване за определени действия в портфейла. НЕ е толкова сигурно, колкото хладилното съхранение.\n\nАко загубите достъп до вашето 2FA приложение или TOTP ключове, ЩЕ загубите достъп до този портфейл. Ще трябва да възстановите портфейла си от мнемоничното семе.\n\nПоддръжката на Cake няма да може да ви помогне, ако загубите достъп до вашите 2FA или мнемонични семена.\nПреди да използвате Cake 2FA, препоръчваме да прочетете ръководството.", + "scan_qr_on_device": "Сканирайте този QR код на друго устройство", + "how_to_use": "Как да използвам", "seed_hex_form": "Семена от портфейл (шестнадесетична форма)", "seedtype": "Семенна тип", "seedtype_legacy": "Наследство (25 думи)", "seedtype_polyseed": "Поли семе (16 думи)", "seed_language_czech": "Чех", "seed_language_korean": "Корейски", - "seed_language_chinese_traditional": "Традиционен китайски)" -} \ No newline at end of file + "seed_language_chinese_traditional": "Традиционен китайски)", + "ascending": "Възходящ", + "descending": "Низходящ", + "dfx_option_description": "Купете крипто с EUR и CHF. До 990 € без допълнителен KYC. За клиенти на дребно и корпоративни клиенти в Европа", + "polygonscan_history": "История на PolygonScan", + "wallet_seed_legacy": "Наследено портфейл семе", + "default_sell_provider": "Доставчик за продажба по подразбиране", + "select_sell_provider_notice": "Изберете доставчик на продажба по-горе. Можете да пропуснете този екран, като зададете своя доставчик на продажба по подразбиране в настройките на приложението.", + "custom_drag": "Персонализиране (задръжте и плъзнете)", + "switchToEVMCompatibleWallet": "Моля, превключете към портфейл, съвместим с EVM, и опитайте отново (Ethereum, Polygon)" +} diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 8efdb59b5..bed4e3193 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -160,7 +160,7 @@ "seed_title": "Seed", "seed_share": "Sdílet seed", "copy": "Kopírovat", - "seed_language_choose": "Prosím zvolte si jazyk seedu:", + "seed_language": "Jazyk semen", "seed_choose": "Zvolte si jazyk seedu", "seed_language_next": "Další", "seed_language_english": "Angličtina", @@ -569,7 +569,7 @@ "always": "Vždy", "minutes_to_pin_code": "${minute} minutách", "disable_exchange": "Zakázat směnárny", - "advanced_privacy_settings": "Pokročilá nastavení soukromí", + "advanced_settings": "Pokročilé nastavení", "settings_can_be_changed_later": "Tato nastavení mohou být změněna později v nastavení v této aplikaci", "add_custom_node": "Přidat vlastní uzel", "disable_fiat": "Zakázat fiat", @@ -617,11 +617,10 @@ "totp_verification_success": "Ověření proběhlo úspěšně!", "totp_2fa_failure": "Nesprávný kód. Zkuste prosím jiný kód nebo vygenerujte nový tajný klíč. Použijte kompatibilní aplikaci 2FA, která podporuje 8místné kódy a SHA512.", "enter_totp_code": "Zadejte kód TOTP.", - "add_secret_code": "Přidejte tento tajný kód do jiného zařízení", + "add_secret_code": "Nebo přidejte tento tajný kód do ověřovací aplikace", "totp_secret_code": "Tajný kód TOTP", - "important_note": "Důležitá poznámka", - "setup_2fa_text": "Cake 2FA NENÍ tak bezpečný jako skladování v chladu. 2FA chrání před základními typy útoků, jako je váš přítel, který vám poskytne otisk prstu, když spíte.\n\n Cake 2FA nechrání před napadením zařízení sofistikovaným útočníkem.\n\n Pokud ztratíte přístup ke svým kódům 2FA , ZTRÁTÍTE PŘÍSTUP K TÉTO PENĚŽENCE. Budete muset obnovit svou peněženku z mnemotechnického semínka. MUSÍTE TEDY ZÁLOHOVAT SVÉ MNEMONICKÉ SEMÉNKY! Kromě toho někdo s přístupem k vašemu mnemotechnickému semenu bude moci ukrást vaše finanční prostředky a obejít Cake 2FA.\n\n Pracovníci podpory Cake vám nebudou schopni pomoci, pokud ztratíte přístup k vašemu mnemotechnickému semenu, protože Cake je nevazební peněženka.", - "setup_totp_recommended": "Nastavit TOTP (doporučeno)", + "setup_2fa_text": "Cake 2FA pracuje s použitím TOTP jako druhého autentizačního faktoru.\n\nTOTP Cake 2FA vyžaduje SHA-512 a podporu 8 číslic; to poskytuje zvýšenou bezpečnost. Další informace a podporované aplikace naleznete v průvodci.", + "setup_totp_recommended": "Nastavení TOTP", "disable_buy": "Zakázat akci nákupu", "disable_sell": "Zakázat akci prodeje", "auto_generate_subaddresses": "Automaticky generovat podadresy", @@ -723,6 +722,9 @@ "require_for_exchanges_to_external_wallets": "Vyžadovat pro výměny do externích peněženek", "camera_permission_is_required": "Vyžaduje se povolení fotoaparátu.\nPovolte jej v nastavení aplikace.", "switchToETHWallet": "Přejděte na peněženku Ethereum a zkuste to znovu", + "order_by": "Seřadit podle", + "creation_date": "Datum vzniku", + "group_by_type": "Skupina podle typu", "importNFTs": "Importujte NFT", "noNFTYet": "Zatím žádné NFT", "address": "Adresa", @@ -735,11 +737,23 @@ "unavailable_balance_description": "Nedostupný zůstatek: Tento součet zahrnuje prostředky, které jsou uzamčeny v nevyřízených transakcích a ty, které jste aktivně zmrazili v nastavení kontroly mincí. Uzamčené zůstatky budou k dispozici po dokončení příslušných transakcí, zatímco zmrazené zůstatky zůstanou pro transakce nepřístupné, dokud se nerozhodnete je uvolnit.", "unspent_change": "Změna", "tor_connection": "Připojení Tor", + "setup_warning_2fa_text": "Budete muset obnovit svou peněženku z mnemotechnického semínka.\n\nPodpora dortů vám nebude schopna pomoci, pokud ztratíte přístup ke svým 2FA nebo mnemotechnickým semenům.\nCake 2FA je druhá autentizace pro určité akce v peněžence. Před použitím Cake 2FA doporučujeme přečíst si průvodce.NENÍ tak bezpečný jako skladování v chladu.\n\nPokud ztratíte přístup ke své aplikaci 2FA nebo klíčům TOTP, ztratíte přístup k této peněžence. ", + "scan_qr_on_device": "Naskenujte tento QR kód na jiném zařízení", + "how_to_use": "Jak používat", "seed_hex_form": "Semeno peněženky (hex formulář)", "seedtype": "SeedType", "seedtype_legacy": "Legacy (25 slov)", "seedtype_polyseed": "Polyseed (16 slov)", "seed_language_czech": "čeština", "seed_language_korean": "korejština", - "seed_language_chinese_traditional": "Číňan (tradiční)" -} \ No newline at end of file + "seed_language_chinese_traditional": "Číňan (tradiční)", + "ascending": "Vzestupné", + "descending": "Klesající", + "dfx_option_description": "Nakupujte kryptoměny za EUR a CHF. Až 990 € bez dalších KYC. Pro maloobchodní a firemní zákazníky v Evropě", + "polygonscan_history": "Historie PolygonScan", + "wallet_seed_legacy": "Starší semeno peněženky", + "default_sell_provider": "Výchozí poskytovatel prodeje", + "select_sell_provider_notice": "Výše vyberte poskytovatele prodeje. Tuto obrazovku můžete přeskočit nastavením výchozího poskytovatele prodeje v nastavení aplikace.", + "custom_drag": "Custom (Hold and Drag)", + "switchToEVMCompatibleWallet": "Přepněte na peněženku kompatibilní s EVM a zkuste to znovu (Ethereum, Polygon)" +} diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index 754a0f22e..1d63bffc6 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -160,7 +160,7 @@ "seed_title": "Seed", "seed_share": "Seed teilen", "copy": "Kopieren", - "seed_language_choose": "Bitte wählen Sie die Sprache des Seeds:", + "seed_language": "Seed-Sprache", "seed_choose": "Seed-Sprache auswählen", "seed_language_next": "Weiter", "seed_language_english": "Englisch", @@ -571,7 +571,7 @@ "always": "immer", "minutes_to_pin_code": "${minute} Minuten", "disable_exchange": "Exchange deaktivieren", - "advanced_privacy_settings": "Erweiterte Datenschutzeinstellungen", + "advanced_settings": "Erweiterte Einstellungen", "settings_can_be_changed_later": "Diese Einstellungen können später in den App-Einstellungen geändert werden", "add_custom_node": "Neuen benutzerdefinierten Knoten hinzufügen", "disable_fiat": "Fiat deaktivieren", @@ -625,11 +625,10 @@ "totp_verification_success": "Verifizierung erfolgreich!", "totp_2fa_failure": "Falscher Code. Bitte versuchen Sie es mit einem anderen Code oder generieren Sie einen neuen geheimen Schlüssel. Verwenden Sie eine kompatible 2FA-App, die 8-stellige Codes und SHA512 unterstützt.", "enter_totp_code": "Bitte geben Sie den TOTP-Code ein.", - "add_secret_code": "Fügen Sie diesen Geheimcode einem anderen Gerät hinzu", + "add_secret_code": "Oder fügen Sie diesen Geheimcode einer Authentifizierungs-App hinzu", "totp_secret_code": "TOTP-Geheimcode", - "important_note": "Wichtiger Hinweis", - "setup_2fa_text": "Cake 2FA ist NICHT so sicher wie eine Cold Wallet. 2FA schützt vor grundlegenden Arten von Angriffen, z.B. wenn Ihr Freund Ihren Fingerabdruck verwendet, während Sie schlafen.\n\n Cake 2FA schützt NICHT vor einem kompromittierten Gerät durch einen raffinierten Angreifer.\n\n Wenn Sie den Zugriff auf Ihre 2FA-Codes verlieren , VERLIEREN SIE DEN ZUGANG ZU DIESEM WALLET. Sie müssen Ihre Wallet aus mnemonic Seed wiederherstellen. SIE MÜSSEN DESHALB IHRE MNEMONISCHEN SEEDS SICHERN! Außerdem kann jemand mit Zugriff auf Ihre mnemonischen Seed(s) Ihr Geld stehlen und Cake 2FA umgehen.\n\n Cake-Supportmitarbeiter können Ihnen nicht helfen, wenn Sie den Zugriff auf Ihre mnemonischen Seed(s) verlieren, da Cake Wallet eine Wallet ohne treuhänderische Verwahrung ist.", - "setup_totp_recommended": "TOTP einrichten (empfohlen)", + "setup_2fa_text": "Cake 2FA verwendet TOTP als zweiten Authentifizierungsfaktor.\n\nDas TOTP von Cake 2FA erfordert SHA-512 und 8-stellige Unterstützung; Dies sorgt für erhöhte Sicherheit. Weitere Informationen und unterstützte Apps finden Sie im Leitfaden.", + "setup_totp_recommended": "TOTP einrichten", "disable_buy": "Kaufaktion deaktivieren", "disable_sell": "Verkaufsaktion deaktivieren", "cake_2fa_preset": "Kuchen 2FA-Voreinstellung", @@ -731,6 +730,9 @@ "require_for_exchanges_to_external_wallets": "Erforderlich für den Umtausch in externe Wallets", "camera_permission_is_required": "Eine Kameraerlaubnis ist erforderlich.\nBitte aktivieren Sie es in den App-Einstellungen.", "switchToETHWallet": "Bitte wechseln Sie zu einem Ethereum-Wallet und versuchen Sie es erneut", + "order_by": "Sortieren nach", + "creation_date": "Erstellungsdatum", + "group_by_type": "Gruppe nach Typ", "importNFTs": "NFTs importieren", "noNFTYet": "Noch keine NFTs", "address": "Adresse", @@ -743,11 +745,23 @@ "unavailable_balance_description": "Nicht verfügbares Guthaben: Diese Summe umfasst Gelder, die in ausstehenden Transaktionen gesperrt sind, und solche, die Sie in Ihren Münzkontrolleinstellungen aktiv eingefroren haben. Gesperrte Guthaben werden verfügbar, sobald die entsprechenden Transaktionen abgeschlossen sind, während eingefrorene Guthaben für Transaktionen nicht zugänglich bleiben, bis Sie sich dazu entschließen, sie wieder freizugeben.", "unspent_change": "Wechselgeld", "tor_connection": "Tor-Verbindung", + "setup_warning_2fa_text": "Sie müssen Ihr Wallet aus dem mnemonischen Seed wiederherstellen.\n\nDer Cake-Support kann Ihnen nicht weiterhelfen, wenn Sie den Zugriff auf Ihre 2FA- oder Mnemonik-Seeds verlieren.\nCake 2FA ist eine zweite Authentifizierung für bestimmte Aktionen im Wallet. Bevor Sie Cake 2FA verwenden, empfehlen wir Ihnen, die Anleitung durchzulesen.Es ist NICHT so sicher wie eine Kühllagerung.\n\nWenn Sie den Zugriff auf Ihre 2FA-App oder Ihre TOTP-Schlüssel verlieren, verlieren Sie auch den Zugriff auf dieses Wallet. ", + "scan_qr_on_device": "Scannen Sie diesen QR-Code auf einem anderen Gerät", + "how_to_use": "Wie benutzt man", "seed_hex_form": "Brieftaschensamen (Sechskantform)", "seedtype": "Seedtyp", "seedtype_legacy": "Veraltet (25 Wörter)", "seedtype_polyseed": "Polyseed (16 Wörter)", "seed_language_czech": "Tschechisch", "seed_language_korean": "Koreanisch", - "seed_language_chinese_traditional": "Chinesisch (Traditionell)" -} \ No newline at end of file + "seed_language_chinese_traditional": "Chinesisch (Traditionell)", + "ascending": "Aufsteigend", + "descending": "Absteigend", + "dfx_option_description": "Krypto mit EUR und CHF kaufen. Bis zu 990€ ohne zusätzliches KYC. Für Privat- und Firmenkunden in Europa", + "polygonscan_history": "PolygonScan-Verlauf", + "wallet_seed_legacy": "Legacy Wallet Seed", + "default_sell_provider": "Standard-Verkaufsanbieter", + "select_sell_provider_notice": "Wählen Sie oben einen Verkaufsanbieter aus. Sie können diesen Bildschirm überspringen, indem Sie in den App-Einstellungen Ihren Standard-Verkaufsanbieter festlegen.", + "custom_drag": "Custom (Hold and Drag)", + "switchToEVMCompatibleWallet": "Bitte wechseln Sie zu einem EVM-kompatiblen Wallet und versuchen Sie es erneut (Ethereum, Polygon)" +} diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index d65aebe15..e6c95270e 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -160,7 +160,7 @@ "seed_title": "Seed", "seed_share": "Share seed", "copy": "Copy", - "seed_language_choose": "Please choose seed language:", + "seed_language": "Seed language", "seed_choose": "Choose seed language", "seed_language_next": "Next", "seed_language_english": "English", @@ -498,7 +498,7 @@ "bill_amount": "Bill Amount", "you_pay": "You Pay", "tip": "Tip:", - "custom": "custom", + "custom": "Custom", "by_cake_pay": "by Cake Pay", "expires": "Expires", "mm": "MM", @@ -572,7 +572,7 @@ "always": "Always", "minutes_to_pin_code": "${minute} minutes", "disable_exchange": "Disable exchange", - "advanced_privacy_settings": "Advanced Privacy Settings", + "advanced_settings": "Advanced Settings", "settings_can_be_changed_later": "These settings can be changed later in the app settings", "add_custom_node": "Add New Custom Node", "disable_fiat": "Disable fiat", @@ -626,11 +626,12 @@ "totp_verification_success": "Verification Successful!", "totp_2fa_failure": "Incorrect code. Please try a different code or generate a new secret key. Use a compatible 2FA app that supports 8-digit codes and SHA512.", "enter_totp_code": "Please enter the TOTP Code.", - "add_secret_code": "Add this secret code to another device", + "scan_qr_on_device": "Scan this QR code on another device", + "add_secret_code": "Or, add this secret code to an authenticator app", "totp_secret_code": "TOTP Secret Code", - "important_note": "Important note", - "setup_2fa_text": "Cake 2FA is NOT as secure as cold storage. 2FA protects against basic types of attacks, such as your friend providing your fingerprint while you are sleeping.\n\n Cake 2FA does NOT protect against a compromised device by a sophisticated attacker.\n\n If you lose access to your 2FA codes, YOU WILL LOSE ACCESS TO THIS WALLET. You will need to restore your wallet from mnemonic seed. YOU MUST THEREFORE BACK UP YOUR MNEMONIC SEEDS! Further, someone with access to your mnemonic seed(s) will be able to steal your funds, bypassing Cake 2FA.\n\n Cake support staff will be unable to assist you if you lose access to your mnemonic seed, since Cake is a noncustodial wallet.", - "setup_totp_recommended": "Set up TOTP (Recommended)", + "setup_2fa_text": "Cake 2FA works using TOTP as the second authentication factor.\n\nCake 2FA's TOTP requires SHA-512 and 8 digit support; this provides increased security. More information and supported apps can be found in the guide.", + "setup_warning_2fa_text": "Cake 2FA is a second authentication for certain actions in the wallet. It is NOT as secure as cold storage.\n\nIf you lose access to your 2FA app or TOTP keys, you WILL lose access to this wallet. You will need to restore your wallet from the mnemonic seed.\n\nCake support will be unable to assist you if you lose access to your 2FA or mnemonic seeds.\nBefore using Cake 2FA, we recommend reading through the guide.", + "setup_totp_recommended": "Setup TOTP", "disable_buy": "Disable buy action", "disable_sell": "Disable sell action", "cake_2fa_preset": "Cake 2FA Preset", @@ -732,6 +733,9 @@ "require_for_exchanges_to_external_wallets": "Require for exchanges to external wallets", "camera_permission_is_required": "Camera permission is required. \nPlease enable it from app settings.", "switchToETHWallet": "Please switch to an Ethereum wallet and try again", + "order_by": "Order by", + "creation_date": "Creation Date", + "group_by_type": "Group by type", "importNFTs": "Import NFTs", "noNFTYet": "No NFTs yet", "address": "Address", @@ -744,11 +748,21 @@ "unavailable_balance_description": "Unavailable Balance: This total includes funds that are locked in pending transactions and those you have actively frozen in your coin control settings. Locked balances will become available once their respective transactions are completed, while frozen balances remain inaccessible for transactions until you decide to unfreeze them.", "unspent_change": "Change", "tor_connection": "Tor connection", + "how_to_use": "How to use", "seed_hex_form": "Wallet seed (hex form)", "seedtype": "Seedtype", "seedtype_legacy": "Legacy (25 words)", "seedtype_polyseed": "Polyseed (16 words)", "seed_language_czech": "Czech", "seed_language_korean": "Korean", - "seed_language_chinese_traditional": "Chinese (Traditional)" -} \ No newline at end of file + "seed_language_chinese_traditional": "Chinese (Traditional)", + "ascending": "Ascending", + "descending": "Descending", + "dfx_option_description": "Buy crypto with EUR & CHF. Up to 990€ without additional KYC. For retail and corporate customers in Europe", + "polygonscan_history": "PolygonScan history", + "wallet_seed_legacy": "Legacy wallet seed", + "default_sell_provider": "Default Sell Provider", + "select_sell_provider_notice": "Select a sell provider above. You can skip this screen by setting your default sell provider in app settings.", + "custom_drag": "Custom (Hold and Drag)", + "switchToEVMCompatibleWallet": "Please switch to an EVM compatible wallet and try again (Ethereum, Polygon)" +} diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index f785a15d5..6ee0f509c 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -160,7 +160,7 @@ "seed_title": "Semilla", "seed_share": "Compartir semillas", "copy": "Dupdo", - "seed_language_choose": "Por favor elija el idioma semilla:", + "seed_language": "Lenguaje de semillas", "seed_choose": "Elige el idioma semilla", "seed_language_next": "Próximo", "seed_language_english": "Inglés", @@ -571,7 +571,7 @@ "always": "siempre", "minutes_to_pin_code": "${minute} minutos", "disable_exchange": "Deshabilitar intercambio", - "advanced_privacy_settings": "Configuración avanzada de privacidad", + "advanced_settings": "Ajustes avanzados", "settings_can_be_changed_later": "Estas configuraciones se pueden cambiar más tarde en la configuración de la aplicación", "add_custom_node": "Agregar nuevo nodo personalizado", "disable_fiat": "Deshabilitar fiat", @@ -625,11 +625,10 @@ "totp_verification_success": "¡Verificación exitosa!", "totp_2fa_failure": "Código incorrecto. Intente con un código diferente o genere una nueva clave secreta. Use una aplicación 2FA compatible que admita códigos de 8 dígitos y SHA512.", "enter_totp_code": "Ingrese el código TOTP.", - "add_secret_code": "Agregue este código secreto a otro dispositivo", + "add_secret_code": "O agregue este código secreto a una aplicación de autenticación", "totp_secret_code": "Código secreto TOTP", - "important_note": "Nota IMPORTANTE", - "setup_2fa_text": "Cake 2FA NO es tan seguro como el almacenamiento en frío. 2FA protege contra tipos básicos de ataques, como cuando un amigo proporciona su huella digital mientras usted duerme.\n\n Cake 2FA NO protege contra un dispositivo comprometido por un atacante sofisticado.\n\n Si pierde el acceso a sus códigos 2FA , PERDERÁS EL ACCESO A ESTA BILLETERA. Deberá restaurar su billetera desde la semilla mnemotécnica. ¡POR LO TANTO, DEBE HACER UNA COPIA DE SEGURIDAD DE SUS SEMILLAS MNEMÓNICAS! Además, alguien con acceso a sus semillas mnemotécnicas podrá robar sus fondos, sin pasar por Cake 2FA.\n\n El personal de soporte de Cake no podrá ayudarlo si pierde el acceso a su semilla mnemotécnica, ya que Cake es un billetera sin custodia.", - "setup_totp_recommended": "Configurar TOTP (Recomendado)", + "setup_2fa_text": "Cake 2FA funciona utilizando TOTP como segundo factor de autenticación.\n\nEl TOTP de Cake 2FA requiere SHA-512 y soporte de 8 dígitos; esto proporciona una mayor seguridad. Puede encontrar más información y aplicaciones compatibles en la guía.", + "setup_totp_recommended": "Configurar TOTP", "disable_buy": "Desactivar acción de compra", "disable_sell": "Desactivar acción de venta", "cake_2fa_preset": "Pastel 2FA preestablecido", @@ -731,6 +730,9 @@ "require_for_exchanges_to_external_wallets": "Requerido para intercambios a billeteras externas", "camera_permission_is_required": "Se requiere permiso de la cámara.\nHabilítelo desde la configuración de la aplicación.", "switchToETHWallet": "Cambie a una billetera Ethereum e inténtelo nuevamente.", + "order_by": "Ordenar", + "creation_date": "Fecha de creación", + "group_by_type": "Grupo por tipo", "importNFTs": "Importar NFT", "noNFTYet": "Aún no hay NFT", "address": "DIRECCIÓN", @@ -743,11 +745,23 @@ "unavailable_balance_description": "Saldo no disponible: este total incluye fondos que están bloqueados en transacciones pendientes y aquellos que usted ha congelado activamente en su configuración de control de monedas. Los saldos bloqueados estarán disponibles una vez que se completen sus respectivas transacciones, mientras que los saldos congelados permanecerán inaccesibles para las transacciones hasta que usted decida descongelarlos.", "unspent_change": "Cambiar", "tor_connection": "conexión tor", + "setup_warning_2fa_text": "Deberá restaurar su billetera a partir de la semilla mnemotécnica.\n\nEl soporte de Cake no podrá ayudarlo si pierde el acceso a su 2FA o a sus semillas mnemotécnicas.\nCake 2FA es una segunda autenticación para ciertas acciones en la billetera. Antes de usar Cake 2FA, recomendamos leer la guía.NO es tan seguro como el almacenamiento en frío.\n\nSi pierde el acceso a su aplicación 2FA o a sus claves TOTP, perderá el acceso a esta billetera. ", + "scan_qr_on_device": "Escanea este código QR en otro dispositivo", + "how_to_use": "Cómo utilizar", "seed_hex_form": "Semilla de billetera (forma hexadecimal)", "seedtype": "Type de semillas", "seedtype_legacy": "Legado (25 palabras)", "seedtype_polyseed": "Polieta (16 palabras)", "seed_language_czech": "checo", "seed_language_korean": "coreano", - "seed_language_chinese_traditional": "Chino tradicional)" -} \ No newline at end of file + "ascending": "Ascendente", + "descending": "Descendente", + "dfx_option_description": "Compre criptomonedas con EUR y CHF. Hasta 990€ sin KYC adicional. Para clientes minoristas y corporativos en Europa", + "seed_language_chinese_traditional": "Chino (tradicional)", + "polygonscan_history": "Historial de PolygonScan", + "wallet_seed_legacy": "Semilla de billetera heredada", + "default_sell_provider": "Proveedor de venta predeterminado", + "select_sell_provider_notice": "Seleccione un proveedor de venta arriba. Puede omitir esta pantalla configurando su proveedor de venta predeterminado en la configuración de la aplicación.", + "custom_drag": "Custom (mantenía y arrastre)", + "switchToEVMCompatibleWallet": "Cambie a una billetera compatible con EVM e inténtelo nuevamente (Ethereum, Polygon)" +} diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 88263ec05..862f9ae7d 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -160,7 +160,7 @@ "seed_title": "Phrase secrète (seed)", "seed_share": "Partager la phrase secrète (seed)", "copy": "Copier", - "seed_language_choose": "Merci de choisir la langue de la phrase secrète (seed) :", + "seed_language": "Langage de la phrase secrète", "seed_choose": "Choisissez la langue de la phrase secrète (seed)", "seed_language_next": "Suivant", "seed_language_english": "Anglais", @@ -571,7 +571,7 @@ "always": "toujours", "minutes_to_pin_code": "${minute} minutes", "disable_exchange": "Désactiver l'échange", - "advanced_privacy_settings": "Paramètres de confidentialité avancés", + "advanced_settings": "Réglages avancés", "settings_can_be_changed_later": "Ces paramètres peuvent être modifiés ultérieurement dans les paramètres de l'application", "add_custom_node": "Ajouter un nouveau nœud personnalisé", "disable_fiat": "Désactiver les montants en fiat", @@ -625,11 +625,10 @@ "totp_verification_success": "Vérification réussie !", "totp_2fa_failure": "Code incorrect. Veuillez essayer un code différent ou générer un nouveau secret TOTP. Utilisez une application 2FA compatible qui prend en charge les codes à 8 chiffres et SHA512.", "enter_totp_code": "Veuillez entrer le code TOTP.", - "add_secret_code": "Configurer un autre appareil à l'aide de ce secret TOTP", + "add_secret_code": "Ou ajoutez ce code secret à une application d'authentification", "totp_secret_code": "Secret TOTP", - "important_note": "Note importante", - "setup_2fa_text": "Cake 2FA (Authentification à 2 Facteurs) n'est PAS aussi sûr que le stockage à froid. Cake 2FA protège contre les attaques basiques, comme un ami fournissant votre empreinte digitale pendant que vous dormez.\n\n Cake 2FA ne protège PAS contre un appareil compromis par un attaquant sophistiqué.\n\n Si vous perdez l'accès à vos codes 2FA , VOUS PERDREZ L'ACCÈS À CE PORTEFEUILLE (WALLET). Vous devrez restaurer votre portefeuille à partir de sa phrase secrète (seed). VOUS DEVEZ DONC SAUVEGARDER VOTRE PHRASE SECRÈTE ! De plus, quelqu'un ayant accès à votre phrase secrète pourra voler vos fonds, en contournant Cake 2FA.\n\n Le personnel d'assistance de Cake ne pourra pas vous aider si vous perdez l'accès à votre phrase secrète, puisque Cake est un portefeuille non dépositaire (non custodial).", - "setup_totp_recommended": "Configurer TOTP (recommandé)", + "setup_2fa_text": "Cake 2FA fonctionne en utilisant TOTP comme deuxième facteur d'authentification.\n\nLe TOTP de Cake 2FA nécessite la prise en charge de SHA-512 et de 8 chiffres ; cela offre une sécurité accrue. Plus d’informations et les applications prises en charge peuvent être trouvées dans le guide.", + "setup_totp_recommended": "Configurer TOTP", "disable_buy": "Désactiver l'action d'achat", "disable_sell": "Désactiver l'action de vente", "cake_2fa_preset": "Cake 2FA prédéfini", @@ -730,7 +729,6 @@ "domain_looks_up": "Résolution de nom", "require_for_exchanges_to_external_wallets": "Exiger pour les échanges vers des portefeuilles externes", "camera_permission_is_required": "L'autorisation de la caméra est requise.\nVeuillez l'activer à partir des paramètres de l'application.", - "switchToETHWallet": "Veuillez passer à un portefeuille (wallet) Ethereum et réessayer", "importNFTs": "Importer des NFT", "noNFTYet": "Pas encore de NFT", "address": "Adresse", @@ -741,16 +739,29 @@ "seed_phrase_length": "Longueur de la phrase de départ", "unavailable_balance": "Solde indisponible", "unavailable_balance_description": "Solde indisponible : ce total comprend les fonds bloqués dans les transactions en attente et ceux que vous avez activement gelés dans vos paramètres de contrôle des pièces. Les soldes bloqués deviendront disponibles une fois leurs transactions respectives terminées, tandis que les soldes gelés resteront inaccessibles aux transactions jusqu'à ce que vous décidiez de les débloquer.", - "camera_permission_is_required": "L'autorisation d'accès à la caméra est requise.\nVeuillez l'activer depuis les paramètres de l'application.", "switchToETHWallet": "Veuillez passer à un portefeuille (wallet) Ethereum et réessayer", "unspent_change": "Changement", + "order_by": "Commandé par", + "creation_date": "Date de création", + "group_by_type": "Groupe par type", "tor_connection": "Connexion Tor", + "setup_warning_2fa_text": "Vous devrez restaurer votre portefeuille à partir de la graine mnémonique.\n\nLe support Cake ne pourra pas vous aider si vous perdez l'accès à vos graines 2FA ou mnémoniques.\nCake 2FA est une seconde authentification pour certaines actions dans le portefeuille. Avant d'utiliser Cake 2FA, nous vous recommandons de lire le guide.Ce n’est PAS aussi sécurisé que l’entreposage frigorifique.\n\nSi vous perdez l'accès à votre application 2FA ou à vos clés TOTP, vous perdrez l'accès à ce portefeuille. ", + "scan_qr_on_device": "Scannez ce code QR sur un autre appareil", + "how_to_use": "Comment utiliser", "seed_hex_form": "Graine du portefeuille (forme hexagonale)", - "camera_permission_is_required": "L'autorisation de la caméra est requise.\nVeuillez l'activer à partir des paramètres de l'application.", "seedtype": "Type de type graine", "seedtype_legacy": "Héritage (25 mots)", "seedtype_polyseed": "Polyseed (16 mots)", "seed_language_czech": "tchèque", "seed_language_korean": "coréen", - "seed_language_chinese_traditional": "Chinois (Traditionnel)" -} \ No newline at end of file + "seed_language_chinese_traditional": "Chinois (Traditionnel)", + "ascending": "Ascendant", + "descending": "Descendant", + "dfx_option_description": "Achetez des crypto-monnaies avec EUR et CHF. Jusqu'à 990€ sans KYC supplémentaire. Pour les clients particuliers et entreprises en Europe", + "polygonscan_history": "Historique de PolygonScan", + "wallet_seed_legacy": "Graine de portefeuille hérité", + "default_sell_provider": "Fournisseur de vente par défaut", + "select_sell_provider_notice": "Sélectionnez un fournisseur de vente ci-dessus. Vous pouvez ignorer cet écran en définissant votre fournisseur de vente par défaut dans les paramètres de l'application.", + "custom_drag": "Custom (maintenir et traîner)", + "switchToEVMCompatibleWallet": "Veuillez passer à un portefeuille compatible EVM et réessayer (Ethereum, Polygon)" +} diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index 39b98b51a..9456085dc 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -160,7 +160,7 @@ "seed_title": "iri", "seed_share": "Raba iri", "copy": "Kwafi", - "seed_language_choose": "Don Allah zaɓi harshen seed:", + "seed_language": "Harshen Magani", "seed_choose": "Zaɓi harshen seed", "seed_language_next": "Na gaba", "seed_language_english": "Ingilishi", @@ -570,7 +570,7 @@ "always": "Koyaushe", "minutes_to_pin_code": "${minute} minti", "disable_exchange": "Kashe musanya", - "advanced_privacy_settings": "Babban Saitunan Sirri", + "advanced_settings": "Saitunan ci gaba", "settings_can_be_changed_later": "Ana iya canza waɗannan saitunan daga baya a cikin saitunan app", "add_custom_node": "Ƙara Sabon Kulli na Custom", "disable_fiat": "Dakatar da fiat", @@ -709,6 +709,9 @@ "require_for_exchanges_to_external_wallets": "Bukatar musanya zuwa wallet na waje", "camera_permission_is_required": "Ana buƙatar izinin kyamara.\nDa fatan za a kunna shi daga saitunan app.", "switchToETHWallet": "Da fatan za a canza zuwa walat ɗin Ethereum kuma a sake gwadawa", + "order_by": "Oda ta", + "creation_date": "Ranar halitta", + "group_by_type": "Rukuni ta nau'in", "importNFTs": "Shigo da NFTs", "noNFTYet": "Babu NFTs tukuna", "address": "Adireshi", @@ -721,11 +724,26 @@ "unavailable_balance_description": "Ma'auni Babu: Wannan jimlar ya haɗa da kuɗi waɗanda ke kulle a cikin ma'amaloli da ke jiran aiki da waɗanda kuka daskare sosai a cikin saitunan sarrafa kuɗin ku. Ma'auni da aka kulle za su kasance da zarar an kammala ma'amalolinsu, yayin da daskararrun ma'auni ba za su iya samun damar yin ciniki ba har sai kun yanke shawarar cire su.", "unspent_change": "Canza", "tor_connection": "Tor haɗin gwiwa", + "setup_2fa_text": "Cake 2FA yana aiki ta amfani da TOTP azaman ƙimar tabbatarwa ta biyu.\n\nCake 2FA's TOTP yana buƙatar tallafin lambobi SHA-512 da 8; wannan yana ba da ƙarin tsaro. Ana iya samun ƙarin bayani da ƙa'idodi masu goyan baya a cikin jagorar.", + "setup_warning_2fa_text": "Kuna buƙatar dawo da walat ɗin ku daga zuriyar mnemonic.\n\nTallafin kek ba zai iya taimaka muku ba idan kun rasa damar yin amfani da 2FA ko tsaba na mnemonic.\nCake 2FA tabbaci ne na biyu don wasu ayyuka a cikin walat. Kafin amfani da Cake 2FA, muna ba da shawarar karanta ta cikin jagorar.BA shi da tsaro kamar ajiyar sanyi.\n\nIdan ka rasa damar yin amfani da app ɗinka na 2FA ko maɓallan TOTP, ZA KA rasa damar shiga wannan wallet ɗin. ", + "add_secret_code": "Ko, ƙara wannan lambar sirrin zuwa ƙa'idar mai tabbatarwa", + "scan_qr_on_device": "Duba wannan lambar QR akan wata na'ura", + "how_to_use": "Yadda ake amfani da shi", "seed_hex_form": "Gany Sero (form form)", "seedtype": "Seedtype", "seedtype_legacy": "Legacy (25 kalmomi)", "seedtype_polyseed": "Polyseed (16 kalmomi)", "seed_language_czech": "Czech", "seed_language_korean": "Yaren Koriya", - "seed_language_chinese_traditional": "Sinanci (na gargajiya)" -} \ No newline at end of file + "seed_language_chinese_traditional": "Sinanci (na gargajiya)", + "ascending": "Hau", + "descending": "Saukowa", + "dfx_option_description": "Sayi crypto tare da EUR & CHF. Har zuwa € 990 ba tare da ƙarin KYC ba. Don 'yan kasuwa da abokan ciniki na kamfanoni a Turai", + "polygonscan_history": "PolygonScan tarihin kowane zamani", + "wallet_seed_legacy": "Tallarin walat walat", + "setup_totp_recommended": "Saita TOTP", + "default_sell_provider": "Tsohuwar Mai Bayar Siyarwa", + "select_sell_provider_notice": "Zaɓi mai bada siyarwa a sama. Kuna iya tsallake wannan allon ta saita mai bada siyar da ku a cikin saitunan app.", + "custom_drag": "Al'ada (riƙe da ja)", + "switchToEVMCompatibleWallet": "Da fatan za a canza zuwa walat ɗin EVM mai jituwa kuma a sake gwadawa (Ethereum, Polygon)" +} diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index b050ef43b..f6a7701ee 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -160,7 +160,7 @@ "seed_title": "बीज", "seed_share": "बीज साझा करें", "copy": "प्रतिलिपि", - "seed_language_choose": "कृपया बीज भाषा चुनें:", + "seed_language": "बीज", "seed_choose": "बीज भाषा चुनें", "seed_language_next": "आगामी", "seed_language_english": "अंग्रेज़ी", @@ -571,7 +571,7 @@ "always": "हमेशा", "minutes_to_pin_code": "${minute} मिनट", "disable_exchange": "एक्सचेंज अक्षम करें", - "advanced_privacy_settings": "उन्नत गोपनीयता सेटिंग्स", + "advanced_settings": "एडवांस सेटिंग", "settings_can_be_changed_later": "इन सेटिंग्स को बाद में ऐप सेटिंग में बदला जा सकता है", "add_custom_node": "नया कस्टम नोड जोड़ें", "disable_fiat": "िएट को अक्षम करें", @@ -625,11 +625,10 @@ "totp_verification_success": "सत्यापन सफल!", "totp_2fa_failure": "गलत कोड़। कृपया एक अलग कोड का प्रयास करें या एक नई गुप्त कुंजी उत्पन्न करें। 8-अंकीय कोड और SHA512 का समर्थन करने वाले संगत 2FA ऐप का उपयोग करें।", "enter_totp_code": "कृपया TOTP कोड दर्ज करें।", - "add_secret_code": "इस गुप्त कोड को किसी अन्य डिवाइस में जोड़ें", + "add_secret_code": "या, इस गुप्त कोड को प्रमाणक ऐप में जोड़ें", "totp_secret_code": "टीओटीपी गुप्त कोड", - "important_note": "महत्वपूर्ण लेख", - "setup_2fa_text": "केक 2FA कोल्ड स्टोरेज जितना सुरक्षित नहीं है। 2FA बुनियादी प्रकार के हमलों से बचाता है, जैसे कि आपका मित्र सोते समय आपको अपना फ़िंगरप्रिंट प्रदान करता है।\n\n Cake 2FA परिष्कृत हमलावर द्वारा किसी डिवाइस से छेड़छाड़ से रक्षा नहीं करता है।\n\n यदि आप अपने 2FA कोड तक पहुंच खो देते हैं , आप इस वॉलेट तक पहुंच खो देंगे। आपको अपने बटुए को स्मरणीय बीज से पुनर्स्थापित करने की आवश्यकता होगी। इसलिए आपको अपने स्मरणीय बीजों का बैकअप लेना चाहिए! इसके अलावा, आपके स्मरक बीज (बीजों) तक पहुंच रखने वाला कोई व्यक्ति केक 2FA को दरकिनार कर आपके धन की चोरी करने में सक्षम होगा। अप्रबंधित बटुआ।", - "setup_totp_recommended": "टीओटीपी सेट अप करें (अनुशंसित)", + "setup_2fa_text": "केक 2FA दूसरे प्रमाणीकरण कारक के रूप में TOTP का उपयोग करके काम करता है।\n\nकेक 2FA के TOTP को SHA-512 और 8 अंकों के समर्थन की आवश्यकता है; इससे अधिक सुरक्षा मिलती है. अधिक जानकारी और समर्थित ऐप्स गाइड में पाए जा सकते हैं।", + "setup_totp_recommended": "सेटअप टीओटीपी", "disable_buy": "खरीद कार्रवाई अक्षम करें", "disable_sell": "बेचने की कार्रवाई अक्षम करें", "cake_2fa_preset": "केक 2एफए प्रीसेट", @@ -731,6 +730,9 @@ "require_for_exchanges_to_external_wallets": "बाहरी वॉलेट में एक्सचेंज की आवश्यकता है", "camera_permission_is_required": "कैमरे की अनुमति आवश्यक है.\nकृपया इसे ऐप सेटिंग से सक्षम करें।", "switchToETHWallet": "कृपया एथेरियम वॉलेट पर स्विच करें और पुनः प्रयास करें", + "order_by": "द्वारा आदेश", + "creation_date": "निर्माण तिथि", + "group_by_type": "प्रकार द्वारा समूह", "importNFTs": "एनएफटी आयात करें", "noNFTYet": "अभी तक कोई एनएफटी नहीं", "address": "पता", @@ -743,11 +745,23 @@ "unavailable_balance_description": "अनुपलब्ध शेष राशि: इस कुल में वे धनराशि शामिल हैं जो लंबित लेनदेन में बंद हैं और जिन्हें आपने अपनी सिक्का नियंत्रण सेटिंग्स में सक्रिय रूप से जमा कर रखा है। लॉक किए गए शेष उनके संबंधित लेन-देन पूरे होने के बाद उपलब्ध हो जाएंगे, जबकि जमे हुए शेष लेन-देन के लिए अप्राप्य रहेंगे जब तक कि आप उन्हें अनफ्रीज करने का निर्णय नहीं लेते।", "unspent_change": "परिवर्तन", "tor_connection": "टोर कनेक्शन", + "setup_warning_2fa_text": "केक 2एफए वॉलेट में कुछ कार्यों के लिए दूसरा प्रमाणीकरण है। यह कोल्ड स्टोरेज जितना सुरक्षित नहीं है।\n\nयदि आप अपने 2एफए ऐप या टीओटीपी कुंजियों तक पहुंच खो देते हैं, तो आप इस वॉलेट तक पहुंच खो देंगे। आपको अपने बटुए को स्मरक बीज से पुनर्स्थापित करने की आवश्यकता होगी।\n\nयदि आप अपने 2एफए या निमोनिक बीजों तक पहुंच खो देते हैं तो केक समर्थन आपकी सहायता करने में असमर्थ होगा।\nकेक 2एफए का उपयोग करने से पहले, हम गाइड को पढ़ने की सलाह देते हैं।", + "scan_qr_on_device": "इस QR कोड को किसी अन्य डिवाइस पर स्कैन करें", + "how_to_use": "का उपयोग कैसे करें", "seed_hex_form": "वॉलेट सीड (हेक्स फॉर्म)", "seedtype": "बीज", "seedtype_legacy": "विरासत (25 शब्द)", "seedtype_polyseed": "पॉलीसीड (16 शब्द)", "seed_language_czech": "चेक", "seed_language_korean": "कोरियाई", - "seed_language_chinese_traditional": "चीनी पारंपरिक)" -} \ No newline at end of file + "seed_language_chinese_traditional": "चीनी पारंपरिक)", + "ascending": "आरोही", + "descending": "अवरोही", + "dfx_option_description": "EUR और CHF के साथ क्रिप्टो खरीदें। अतिरिक्त केवाईसी के बिना 990€ तक। यूरोप में खुदरा और कॉर्पोरेट ग्राहकों के लिए", + "polygonscan_history": "पॉलीगॉनस्कैन इतिहास", + "wallet_seed_legacy": "विरासत बटुए बीज", + "default_sell_provider": "डिफ़ॉल्ट विक्रय प्रदाता", + "select_sell_provider_notice": "ऊपर एक विक्रय प्रदाता का चयन करें। आप ऐप सेटिंग में अपना डिफ़ॉल्ट विक्रय प्रदाता सेट करके इस स्क्रीन को छोड़ सकते हैं।", + "custom_drag": "कस्टम (पकड़ और खींचें)", + "switchToEVMCompatibleWallet": "कृपया ईवीएम संगत वॉलेट पर स्विच करें और पुनः प्रयास करें (एथेरियम, पॉलीगॉन)" +} diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 3c0f895e7..3565c89a0 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -160,7 +160,7 @@ "seed_title": "Prisupni izraz", "seed_share": "Podijeli pristupni izraz", "copy": "Kopiraj", - "seed_language_choose": "Molimo odaberite jezik pristupnog izraza:", + "seed_language": "Sjemeni jezik", "seed_choose": "Odaberi jezik pristupnog izraza", "seed_language_next": "Dalje", "seed_language_english": "Engleski", @@ -571,7 +571,7 @@ "always": "Uvijek", "minutes_to_pin_code": "${minute} minuta", "disable_exchange": "Onemogući exchange", - "advanced_privacy_settings": "Napredne postavke privatnosti", + "advanced_settings": "Napredne postavke", "settings_can_be_changed_later": "Te se postavke mogu promijeniti kasnije u postavkama aplikacije", "add_custom_node": "Dodaj novi prilagođeni čvor", "disable_fiat": "Isključi, fiat", @@ -625,11 +625,10 @@ "totp_verification_success": "Provjera uspješna!", "totp_2fa_failure": "Neispravan kod. Pokušajte s drugim kodom ili generirajte novi tajni ključ. Koristite kompatibilnu 2FA aplikaciju koja podržava 8-znamenkasti kod i SHA512.", "enter_totp_code": "Unesite TOTP kod.", - "add_secret_code": "Dodajte ovaj tajni kod na drugi uređaj", + "add_secret_code": "Ili dodajte ovaj tajni kod u aplikaciju za autentifikaciju", "totp_secret_code": "TOTP tajni kod", - "important_note": "Važna nota", - "setup_2fa_text": "Torta 2FA NIJE sigurna kao hladno skladište. 2FA štiti od osnovnih vrsta napada, kao što je vaš prijatelj koji vam daje otisak prsta dok spavate.\n\n Cake 2FA NE štiti od kompromitiranog uređaja od strane sofisticiranog napadača.\n\n Ako izgubite pristup svojim 2FA kodovima , IZGUBIT ĆETE PRISTUP OVOM NOVČANIKU. Morat ćete obnoviti svoj novčanik iz mnemoničkog sjemena. STOGA MORATE NAPRAVITI SIGURNOSNE KOPIJE SVOJIH MNEMONIČKIH SJEMENA! Nadalje, netko tko ima pristup vašem mnemoničkom seedu(ima) moći će ukrasti vaša sredstva, zaobilazeći Cake 2FA.\n\n Cake osoblje za podršku neće vam moći pomoći ako izgubite pristup svom mnemoničkom seedu, budući da je Cake neskrbnički novčanik.", - "setup_totp_recommended": "Postavite TOTP (preporučeno)", + "setup_2fa_text": "Cake 2FA radi koristeći TOTP kao drugi faktor provjere autentičnosti.\n\nCake 2FA TOTP zahtijeva SHA-512 i podršku za 8 znamenki; ovo osigurava povećanu sigurnost. Više informacija i podržanih aplikacija možete pronaći u vodiču.", + "setup_totp_recommended": "Postavite TOTP", "disable_buy": "Onemogući kupnju", "disable_sell": "Onemogući akciju prodaje", "cake_2fa_preset": "Cake 2FA Preset", @@ -729,6 +728,9 @@ "require_for_exchanges_to_external_wallets": "Zahtijeva razmjene na vanjske novčanike", "camera_permission_is_required": "Potrebno je dopuštenje kamere.\nOmogućite ga u postavkama aplikacije.", "switchToETHWallet": "Prijeđite na Ethereum novčanik i pokušajte ponovno", + "order_by": "Narediti", + "creation_date": "Datum stvaranja", + "group_by_type": "Grupirati", "importNFTs": "Uvoz NFT-ova", "noNFTYet": "Još nema NFT-ova", "address": "Adresa", @@ -741,11 +743,23 @@ "unavailable_balance_description": "Nedostupno stanje: Ovaj ukupni iznos uključuje sredstva koja su zaključana u transakcijama na čekanju i ona koja ste aktivno zamrznuli u postavkama kontrole novčića. Zaključani saldi postat će dostupni kada se dovrše njihove transakcije, dok zamrznuti saldi ostaju nedostupni za transakcije sve dok ih ne odlučite odmrznuti.", "unspent_change": "Promijeniti", "tor_connection": "Tor veza", + "setup_warning_2fa_text": "Morat ćete obnoviti svoj novčanik iz mnemoničkog sjemena.\n\nPodrška za kolače neće vam moći pomoći ako izgubite pristup svojim 2FA ili mnemoničkim izvorima.\nCake 2FA je druga provjera autentičnosti za određene radnje u novčaniku. Prije uporabe Cake 2FA preporučujemo da pročitate vodič.NIJE siguran kao hladno skladište.\n\nAko izgubite pristup svojoj 2FA aplikaciji ili TOTP ključevima, IZGUBIT ĆETE pristup ovom novčaniku. ", + "scan_qr_on_device": "Skenirajte ovaj QR kod na drugom uređaju", + "how_to_use": "Kako koristiti", "seed_hex_form": "Sjeme novčanika (šesterokutni oblik)", "seedtype": "Sjemenska vrsta", "seedtype_legacy": "Nasljeđe (25 riječi)", "seedtype_polyseed": "Poliseed (16 riječi)", "seed_language_czech": "češki", "seed_language_korean": "korejski", - "seed_language_chinese_traditional": "Kinesko tradicionalno)" -} \ No newline at end of file + "ascending": "Uzlazni", + "descending": "Silazni", + "seed_language_chinese_traditional": "Kinesko (tradicionalno)", + "dfx_option_description": "Kupujte kripto s EUR i CHF. Do 990 € bez dodatnog KYC-a. Za maloprodajne i poslovne korisnike u Europi", + "polygonscan_history": "Povijest PolygonScan", + "wallet_seed_legacy": "Sjeme naslijeđenog novčanika", + "default_sell_provider": "Zadani dobavljač prodaje", + "select_sell_provider_notice": "Gore odaberite pružatelja usluga prodaje. Ovaj zaslon možete preskočiti postavljanjem zadanog pružatelja usluga prodaje u postavkama aplikacije.", + "custom_drag": "Prilagođeni (držite i povucite)", + "switchToEVMCompatibleWallet": "Prijeđite na novčanik kompatibilan s EVM-om i pokušajte ponovno (Ethereum, Polygon)" +} diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index b29572746..6744f0a38 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -160,7 +160,7 @@ "seed_title": "Bibit", "seed_share": "Bagikan bibit", "copy": "Salin", - "seed_language_choose": "Silakan pilih bahasa bibit:", + "seed_language": "Bahasa benih", "seed_choose": "Pilih bahasa bibit", "seed_language_next": "Selanjutnya", "seed_language_english": "Inggris", @@ -570,7 +570,7 @@ "always": "Selalu", "minutes_to_pin_code": "${minute} menit", "disable_exchange": "Nonaktifkan pertukaran", - "advanced_privacy_settings": "Pengaturan Privasi Lanjutan", + "advanced_settings": "Pengaturan lanjutan", "settings_can_be_changed_later": "Pengaturan ini dapat diubah nanti di pengaturan aplikasi", "add_custom_node": "Tambahkan Node Kustom Baru", "disable_fiat": "Nonaktifkan fiat", @@ -613,11 +613,10 @@ "totp_verification_success": "Verifikasi Berhasil!", "totp_2fa_failure": "Kode salah. Silakan coba kode lain atau buat kunci rahasia baru. Gunakan aplikasi 2FA yang kompatibel yang mendukung kode 8 digit dan SHA512.", "enter_totp_code": "Masukkan Kode TOTP.", - "add_secret_code": "Tambahkan kode rahasia ini ke perangkat lain", + "add_secret_code": "Atau, tambahkan kode rahasia ini ke aplikasi autentikator", "totp_secret_code": "Kode Rahasia TOTP", - "important_note": "Catatan penting", - "setup_2fa_text": "Cake 2FA TIDAK seaman cold storage. 2FA melindungi dari jenis serangan dasar, seperti teman Anda memberikan sidik jari saat Anda sedang tidur.\n\n Cake 2FA TIDAK melindungi dari perangkat yang disusupi oleh penyerang canggih.\n\n Jika Anda kehilangan akses ke kode 2FA , ANDA AKAN KEHILANGAN AKSES KE DOMPET INI. Anda perlu memulihkan dompet Anda dari benih mnemonik. OLEH KARENA ITU, ANDA HARUS MENYIMPAN BIJI MNEMONIK ANDA! Selanjutnya, seseorang yang memiliki akses ke benih mnemonik Anda akan dapat mencuri dana Anda, melewati Cake 2FA.\n\n Staf pendukung Cake tidak akan dapat membantu Anda jika Anda kehilangan akses ke benih mnemonik Anda, karena Cake adalah dompet tanpa hak asuh.", - "setup_totp_recommended": "Siapkan TOTP (Disarankan)", + "setup_2fa_text": "Cake 2FA bekerja menggunakan TOTP sebagai faktor otentikasi kedua.\n\nTOTP Cake 2FA memerlukan SHA-512 dan dukungan 8 digit; ini memberikan peningkatan keamanan. Informasi lebih lanjut dan aplikasi yang didukung dapat ditemukan di panduan.", + "setup_totp_recommended": "Pengaturan TOTP", "disable_buy": "Nonaktifkan tindakan beli", "disable_sell": "Nonaktifkan aksi jual", "auto_generate_subaddresses": "Menghasilkan subalamat secara otomatis", @@ -719,6 +718,9 @@ "require_for_exchanges_to_external_wallets": "Memerlukan pertukaran ke dompet eksternal", "camera_permission_is_required": "Izin kamera diperlukan.\nSilakan aktifkan dari pengaturan aplikasi.", "switchToETHWallet": "Silakan beralih ke dompet Ethereum dan coba lagi", + "order_by": "Dipesan oleh", + "creation_date": "Tanggal Pembuatan", + "group_by_type": "Grup demi jenis", "importNFTs": "Impor NFT", "noNFTYet": "Belum ada NFT", "address": "Alamat", @@ -731,11 +733,23 @@ "unavailable_balance_description": "Saldo Tidak Tersedia: Total ini termasuk dana yang terkunci dalam transaksi yang tertunda dan dana yang telah Anda bekukan secara aktif di pengaturan kontrol koin Anda. Saldo yang terkunci akan tersedia setelah transaksi masing-masing selesai, sedangkan saldo yang dibekukan tetap tidak dapat diakses untuk transaksi sampai Anda memutuskan untuk mencairkannya.", "unspent_change": "Mengubah", "tor_connection": "koneksi Tor", + "setup_warning_2fa_text": "Anda perlu memulihkan dompet Anda dari benih mnemonik.\n\nDukungan kue tidak akan dapat membantu Anda jika Anda kehilangan akses ke 2FA atau benih mnemonik.\nCake 2FA adalah otentikasi kedua untuk tindakan tertentu di dompet. Sebelum menggunakan Cake 2FA, kami sarankan untuk membaca panduannya.Ini TIDAK seaman penyimpanan dingin.\n\nJika Anda kehilangan akses ke aplikasi 2FA atau kunci TOTP, Anda AKAN kehilangan akses ke dompet ini. ", + "scan_qr_on_device": "Pindai kode QR ini di perangkat lain", + "how_to_use": "Cara Penggunaan", "seed_hex_form": "Biji dompet (bentuk hex)", "seedtype": "Seedtype", "seedtype_legacy": "Legacy (25 kata)", "seedtype_polyseed": "Polyseed (16 kata)", "seed_language_czech": "Ceko", "seed_language_korean": "Korea", - "seed_language_chinese_traditional": "Cina tradisional)" -} \ No newline at end of file + "ascending": "Naik", + "descending": "Menurun", + "seed_language_chinese_traditional": "Cina (tradisional)", + "dfx_option_description": "Beli kripto dengan EUR & CHF. Hingga 990€ tanpa KYC tambahan. Untuk pelanggan ritel dan korporat di Eropa", + "polygonscan_history": "Sejarah PolygonScan", + "wallet_seed_legacy": "Biji dompet warisan", + "default_sell_provider": "Penyedia Penjualan Default", + "select_sell_provider_notice": "Pilih penyedia jual di atas. Anda dapat melewati layar ini dengan mengatur penyedia penjualan default Anda di pengaturan aplikasi.", + "custom_drag": "Khusus (tahan dan seret)", + "switchToEVMCompatibleWallet": "Silakan beralih ke dompet yang kompatibel dengan EVM dan coba lagi (Ethereum, Polygon)" +} diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index 19912d277..bb909a7e0 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -160,7 +160,7 @@ "seed_title": "Seme", "seed_share": "Condividi seme", "copy": "Copia", - "seed_language_choose": "Gentilmente scegli la lingua del seme:", + "seed_language": "Linguaggio di semi", "seed_choose": "Scegli la lingua del seme", "seed_language_next": "Prossimo", "seed_language_english": "Inglese", @@ -571,7 +571,7 @@ "always": "sempre", "minutes_to_pin_code": "${minute} minuti", "disable_exchange": "Disabilita scambio", - "advanced_privacy_settings": "Impostazioni avanzate sulla privacy", + "advanced_settings": "Impostazioni avanzate", "settings_can_be_changed_later": "Queste impostazioni possono essere modificate in seguito nelle impostazioni dell'app", "add_custom_node": "Aggiungi nuovo nodo personalizzato", "disable_fiat": "Disabilita fiat", @@ -625,11 +625,10 @@ "totp_verification_success": "Verifica riuscita!", "totp_2fa_failure": "Codice non corretto. Prova un codice diverso o genera una nuova chiave segreta. Utilizza un'app 2FA compatibile che supporti codici a 8 cifre e SHA512.", "enter_totp_code": "Inserisci il codice TOTP.", - "add_secret_code": "Aggiungi questo codice segreto a un altro dispositivo", + "add_secret_code": "Oppure aggiungi questo codice segreto a un'app di autenticazione", "totp_secret_code": "TOTP codice segreto", - "important_note": "Nota importante", - "setup_2fa_text": "Cake 2FA NON è sicuro come la cella frigorifera. 2FA protegge da tipi di attacchi di base, come il tuo amico che fornisce la tua impronta digitale mentre dormi.\n\n Cake 2FA NON protegge da un dispositivo compromesso da un aggressore sofisticato.\n\n Se perdi l'accesso ai tuoi codici 2FA , PERDERAI L'ACCESSO A QUESTO PORTAFOGLIO. Dovrai ripristinare il tuo portafoglio dal seme mnemonico. DOVETE QUINDI SOSTITUIRE I VOSTRI SEMI MNEMONICI! Inoltre, qualcuno con accesso ai tuoi seed mnemonici sarà in grado di rubare i tuoi fondi, aggirando Cake 2FA.\n\n Il personale di supporto di Cake non sarà in grado di aiutarti se perdi l'accesso al tuo seed mnemonico, poiché Cake è un portafoglio non detentivo.", - "setup_totp_recommended": "Imposta TOTP (consigliato)", + "setup_2fa_text": "Cake 2FA funziona utilizzando TOTP come secondo fattore di autenticazione.\n\nIl TOTP di Cake 2FA richiede il supporto SHA-512 e 8 cifre; ciò fornisce una maggiore sicurezza. Maggiori informazioni e app supportate sono disponibili nella guida.", + "setup_totp_recommended": "Imposta TOTP", "disable_buy": "Disabilita l'azione di acquisto", "disable_sell": "Disabilita l'azione di vendita", "auto_generate_subaddresses": "Genera automaticamente sottindirizzi", @@ -731,6 +730,9 @@ "require_for_exchanges_to_external_wallets": "Richiede scambi con portafogli esterni", "camera_permission_is_required": "È richiesta l'autorizzazione della fotocamera.\nAbilitalo dalle impostazioni dell'app.", "switchToETHWallet": "Passa a un portafoglio Ethereum e riprova", + "order_by": "Ordinato da", + "creation_date": "Data di creazione", + "group_by_type": "Gruppo per tipo", "importNFTs": "Importa NFT", "noNFTYet": "Nessun NFT ancora", "address": "Indirizzo", @@ -743,11 +745,23 @@ "unavailable_balance_description": "Saldo non disponibile: questo totale include i fondi bloccati nelle transazioni in sospeso e quelli che hai congelato attivamente nelle impostazioni di controllo delle monete. I saldi bloccati diventeranno disponibili una volta completate le rispettive transazioni, mentre i saldi congelati rimarranno inaccessibili per le transazioni finché non deciderai di sbloccarli.", "unspent_change": "Modifica", "tor_connection": "Connessione Tor", + "setup_warning_2fa_text": "Dovrai ripristinare il tuo portafoglio dal seme mnemonico.\n\nIl supporto di Cake non sarà in grado di assisterti se perdi l'accesso ai tuoi seed 2FA o mnemonici.\nCake 2FA è una seconda autenticazione per determinate azioni nel portafoglio. Prima di utilizzare Cake 2FA, ti consigliamo di leggere la guida.NON è sicuro come la conservazione a freddo.\n\nSe perdi l'accesso alla tua app 2FA o alle chiavi TOTP, perderai l'accesso a questo portafoglio. ", + "scan_qr_on_device": "Scansiona questo codice QR su un altro dispositivo", + "how_to_use": "Come usare", "seed_hex_form": "Seme di portafoglio (forma esadecimale)", "seedtype": "Seedtype", "seedtype_legacy": "Legacy (25 parole)", "seedtype_polyseed": "Polyseed (16 parole)", "seed_language_czech": "ceco", "seed_language_korean": "coreano", - "seed_language_chinese_traditional": "Cinese tradizionale)" -} \ No newline at end of file + "seed_language_chinese_traditional": "Cinese tradizionale)", + "ascending": "Ascendente", + "descending": "Discendente", + "dfx_option_description": "Acquista criptovalute con EUR e CHF. Fino a 990€ senza KYC aggiuntivi. Per clienti al dettaglio e aziendali in Europa", + "polygonscan_history": "Cronologia PolygonScan", + "wallet_seed_legacy": "Seme di portafoglio legacy", + "default_sell_provider": "Fornitore di vendita predefinito", + "select_sell_provider_notice": "Seleziona un fornitore di vendita sopra. Puoi saltare questa schermata impostando il tuo fornitore di vendita predefinito nelle impostazioni dell'app.", + "custom_drag": "Custom (Hold and Drag)", + "switchToEVMCompatibleWallet": "Passa a un portafoglio compatibile con EVM e riprova (Ethereum, Polygon)" +} diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index ea138b317..2152a3dcc 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -160,7 +160,7 @@ "seed_title": "シード", "seed_share": "シードを共有する", "copy": "コピー", - "seed_language_choose": "シード言語を選択してください:", + "seed_language": "シード言語", "seed_choose": "シード言語を選択してください", "seed_language_next": "次", "seed_language_english": "英語", @@ -571,7 +571,7 @@ "always": "いつも", "minutes_to_pin_code": "${minute} 分", "disable_exchange": "交換を無効にする", - "advanced_privacy_settings": "高度なプライバシー設定", + "advanced_settings": "高度な設定", "settings_can_be_changed_later": "これらの設定は、後でアプリの設定で変更できます", "add_custom_node": "新しいカスタム ノードを追加", "disable_fiat": "フィアットを無効にする", @@ -625,11 +625,10 @@ "totp_verification_success": "検証成功!", "totp_2fa_failure": "コードが正しくありません。 別のコードを試すか、新しい秘密鍵を生成してください。 8 桁のコードと SHA512 をサポートする互換性のある 2FA アプリを使用してください。", "enter_totp_code": "TOTPコードを入力してください。", - "add_secret_code": "このシークレット コードを別のデバイスに追加する", + "add_secret_code": "または、このシークレット コードを認証アプリに追加します", "totp_secret_code": "TOTPシークレットコード", - "important_note": "重要な注意点", - "setup_2fa_text": "Cake 2FA は、コールド ストレージほど安全ではありません。 2FA は、あなたが寝ているときに友人が指紋を提供するなどの基本的なタイプの攻撃から保護します。\n\n Cake 2FA は、巧妙な攻撃者による侵害されたデバイスから保護しません。\n\n 2FA コードにアクセスできなくなった場合、このウォレットにアクセスできなくなります。ニーモニック シードからウォレットを復元する必要があります。したがって、ニーモニック シードをバックアップする必要があります。さらに、あなたのニーモニック シードにアクセスできる誰かが、Cake 2FA をバイパスして、あなたの資金を盗むことができます。\n\n Cake は無印の財布。", - "setup_totp_recommended": "TOTP を設定する (推奨)", + "setup_2fa_text": "Cake 2FA は、TOTP を 2 番目の認証要素として使用して機能します。\n\nCake 2FA の TOTP には SHA-512 と 8 桁のサポートが必要です。これによりセキュリティが強化されます。詳細とサポートされているアプリについてはガイドをご覧ください。", + "setup_totp_recommended": "TOTPのセットアップ", "disable_buy": "購入アクションを無効にする", "disable_sell": "販売アクションを無効にする", "cake_2fa_preset": "ケーキ 2FA プリセット", @@ -731,6 +730,9 @@ "require_for_exchanges_to_external_wallets": "外部ウォレットへの交換に必要", "camera_permission_is_required": "カメラの許可が必要です。\nアプリの設定から有効にしてください。", "switchToETHWallet": "イーサリアムウォレットに切り替えてもう一度お試しください", + "order_by": "注文", + "creation_date": "作成日", + "group_by_type": "タイプごとにグループ", "importNFTs": "NFTのインポート", "noNFTYet": "NFTはまだありません", "address": "住所", @@ -743,11 +745,23 @@ "unavailable_balance_description": "利用不可能な残高: この合計には、保留中のトランザクションにロックされている資金と、コイン管理設定でアクティブに凍結した資金が含まれます。ロックされた残高は、それぞれの取引が完了すると利用可能になりますが、凍結された残高は、凍結を解除するまで取引にアクセスできません。", "unspent_change": "変化", "tor_connection": "Tor接続", + "setup_warning_2fa_text": "Cake 2FA は、ウォレット内の特定のアクションに対する 2 番目の認証です。冷蔵保存ほど安全ではありません。\n\n2FA アプリまたは TOTP キーにアクセスできなくなると、このウォレットにもアクセスできなくなります。ニーモニックシードからウォレットを復元する必要があります。\n\n2FA またはニーモニック シードにアクセスできなくなった場合、Cake サポートはサポートできません。\nCake 2FA を使用する前に、ガイドを一読することをお勧めします。", + "scan_qr_on_device": "別のデバイスでこの QR コードをスキャンします", + "how_to_use": "使い方", "seed_hex_form": "ウォレットシード(ヘックスフォーム)", "seedtype": "SeedType", "seedtype_legacy": "レガシー(25語)", "seedtype_polyseed": "ポリシード(16語)", "seed_language_czech": "チェコ", "seed_language_korean": "韓国語", - "seed_language_chinese_traditional": "中国の伝統的な)" -} \ No newline at end of file + "seed_language_chinese_traditional": "中国の伝統的な)", + "ascending": "上昇", + "descending": "下降", + "dfx_option_description": "EUR と CHF で暗号通貨を購入します。追加のKYCなしで最大990ユーロ。ヨーロッパの小売および法人顧客向け", + "polygonscan_history": "ポリゴンスキャン履歴", + "wallet_seed_legacy": "レガシーウォレットシード", + "default_sell_provider": "デフォルトの販売プロバイダー", + "select_sell_provider_notice": "上記の販売プロバイダーを選択してください。アプリ設定でデフォルトの販売プロバイダーを設定することで、この画面をスキップできます。", + "custom_drag": "カスタム(ホールドとドラッグ)", + "switchToEVMCompatibleWallet": "EVM 互換のウォレットに切り替えて再試行してください (イーサリアム、ポリゴン)" +} diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 4df43018d..5ad5482ed 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -160,7 +160,7 @@ "seed_title": "씨", "seed_share": "시드 공유", "copy": "부", - "seed_language_choose": "종자 언어를 선택하십시오:", + "seed_language": "종자 언어", "seed_choose": "시드 언어를 선택하십시오", "seed_language_next": "다음 것", "seed_language_english": "영어", @@ -571,7 +571,7 @@ "always": "언제나", "minutes_to_pin_code": "${minute}분", "disable_exchange": "교환 비활성화", - "advanced_privacy_settings": "고급 개인 정보 설정", + "advanced_settings": "고급 설정", "settings_can_be_changed_later": "이 설정은 나중에 앱 설정에서 변경할 수 있습니다.", "add_custom_node": "새 사용자 정의 노드 추가", "disable_fiat": "법정화폐 비활성화", @@ -625,11 +625,10 @@ "totp_verification_success": "확인 성공!", "totp_2fa_failure": "잘못된 코드입니다. 다른 코드를 시도하거나 새 비밀 키를 생성하십시오. 8자리 코드와 SHA512를 지원하는 호환되는 2FA 앱을 사용하세요.", "enter_totp_code": "TOTP 코드를 입력하세요.", - "add_secret_code": "이 비밀 코드를 다른 장치에 추가", + "add_secret_code": "또는 이 비밀 코드를 인증 앱에 추가하세요.", "totp_secret_code": "TOTP 비밀 코드", - "important_note": "중요 사항", - "setup_2fa_text": "케이크 2FA는 냉장 보관만큼 안전하지 않습니다. 2FA는 당신이 잠자는 동안 친구가 지문을 제공하는 것과 같은 기본적인 유형의 공격으로부터 보호합니다.\n\n Cake 2FA는 정교한 공격자에 의해 손상된 장치로부터 보호하지 않습니다.\n\n 2FA 코드에 대한 액세스 권한을 잃으면 , 이 지갑에 대한 액세스 권한을 잃게 됩니다. 니모닉 시드에서 지갑을 복원해야 합니다. 따라서 니모닉 시드를 백업해야 합니다! 또한 니모닉 시드에 액세스할 수 있는 사람이 Cake 2FA를 우회하여 자금을 훔칠 수 있습니다.\n\n 니모닉 시드에 대한 액세스 권한을 잃으면 Cake 지원 직원이 도와줄 수 없습니다. 비수탁 지갑.", - "setup_totp_recommended": "TOTP 설정(권장)", + "setup_2fa_text": "Cake 2FA는 TOTP를 두 번째 인증 요소로 사용하여 작동합니다.\n\nCake 2FA의 TOTP에는 SHA-512 및 8자리 지원이 필요합니다. 이는 보안을 강화합니다. 자세한 정보와 지원되는 앱은 가이드에서 확인할 수 있습니다.", + "setup_totp_recommended": "TOTP 설정", "disable_buy": "구매 행동 비활성화", "disable_sell": "판매 조치 비활성화", "cake_2fa_preset": "케이크 2FA 프리셋", @@ -729,6 +728,9 @@ "require_for_exchanges_to_external_wallets": "외부 지갑으로의 교환을 위해 필요", "camera_permission_is_required": "카메라 권한이 필요합니다.\n앱 설정에서 활성화해 주세요.", "switchToETHWallet": "이더리움 지갑으로 전환한 후 다시 시도해 주세요.", + "order_by": "주문", + "creation_date": "생산 일", + "group_by_type": "유형별 그룹", "importNFTs": "NFT 가져오기", "noNFTYet": "아직 NFT가 없습니다", "address": "주소", @@ -741,11 +743,23 @@ "unavailable_balance_description": "사용할 수 없는 잔액: 이 총계에는 보류 중인 거래에 잠겨 있는 자금과 코인 관리 설정에서 적극적으로 동결된 자금이 포함됩니다. 잠긴 잔액은 해당 거래가 완료되면 사용할 수 있게 되며, 동결된 잔액은 동결을 해제하기 전까지 거래에 액세스할 수 없습니다.", "unspent_change": "변화", "tor_connection": "토르 연결", + "setup_warning_2fa_text": "Cake 2FA는 지갑의 특정 작업에 대한 두 번째 인증입니다. 냉장 보관만큼 안전하지 않습니다.\n\n2FA 앱 또는 TOTP 키에 대한 액세스 권한을 상실하면 이 지갑에 대한 액세스 권한도 잃게 됩니다. 니모닉 시드에서 지갑을 복원해야 합니다.\n\n2FA 또는 니모닉 시드에 액세스할 수 없는 경우 Cake 지원팀에서 도움을 드릴 수 없습니다.\nCake 2FA를 사용하기 전에 가이드를 읽어 보시기 바랍니다.", + "scan_qr_on_device": "다른 기기에서 이 QR 코드를 스캔하세요.", + "how_to_use": "사용하는 방법", "seed_hex_form": "지갑 씨앗 (16 진 양식)", "seedtype": "시드 타입", "seedtype_legacy": "레거시 (25 단어)", "seedtype_polyseed": "다문 (16 단어)", "seed_language_czech": "체코 사람", "seed_language_korean": "한국인", - "seed_language_chinese_traditional": "중국 전통)" -} \ No newline at end of file + "seed_language_chinese_traditional": "중국 전통)", + "ascending": "오름차순", + "descending": "내림차순", + "dfx_option_description": "EUR 및 CHF로 암호화폐를 구매하세요. 추가 KYC 없이 최대 990€. 유럽의 소매 및 기업 고객용", + "polygonscan_history": "다각형 스캔 기록", + "wallet_seed_legacy": "레거시 지갑 시드", + "default_sell_provider": "기본 판매 공급자", + "select_sell_provider_notice": "위에서 판매 공급자를 선택하세요. 앱 설정에서 기본 판매 공급자를 설정하면 이 화면을 건너뛸 수 있습니다.", + "custom_drag": "사용자 정의 (홀드 앤 드래그)", + "switchToEVMCompatibleWallet": "EVM 호환 지갑으로 전환 후 다시 시도해 주세요. (이더리움, 폴리곤)" +} diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index d82e30116..73f4e5dcd 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -160,7 +160,7 @@ "seed_title": "မျိုးစေ့", "seed_share": "မျိုးစေ့မျှဝေပါ။", "copy": "ကော်ပီ", - "seed_language_choose": "ကျေးဇူးပြု၍ မျိုးစေ့ဘာသာစကားကို ရွေးပါ-", + "seed_language": "မျိုးစေ့ဘာသာ", "seed_choose": "မျိုးစေ့ဘာသာစကားကို ရွေးချယ်ပါ။", "seed_language_next": "နောက်တစ်ခု", "seed_language_english": "အင်္ဂလိပ်စာ", @@ -569,7 +569,7 @@ "always": "အမြဲတမ်း", "minutes_to_pin_code": "${minute} မိနစ်", "disable_exchange": "လဲလှယ်မှုကို ပိတ်ပါ။", - "advanced_privacy_settings": "အဆင့်မြင့် ကိုယ်ရေးကိုယ်တာ ဆက်တင်များ", + "advanced_settings": "အဆင့်မြင့်ချိန်ညှိချက်များ", "settings_can_be_changed_later": "အက်ပ်ဆက်တင်များတွင် ဤဆက်တင်များကို နောက်ပိုင်းတွင် ပြောင်းလဲနိုင်သည်။", "add_custom_node": "စိတ်ကြိုက် Node အသစ်ကို ထည့်ပါ။", "disable_fiat": "Fiat ကိုပိတ်ပါ။", @@ -623,11 +623,10 @@ "totp_verification_success": "အတည်ပြုခြင်း အောင်မြင်ပါသည်။", "totp_2fa_failure": "ကုဒ်မမှန်ပါ။ ကျေးဇူးပြု၍ အခြားကုဒ်တစ်ခုကို စမ်းကြည့်ပါ သို့မဟုတ် လျှို့ဝှက်သော့အသစ်တစ်ခု ဖန်တီးပါ။ ဂဏန်း ၈ လုံးကုဒ်များနှင့် SHA512 ကို ပံ့ပိုးပေးသည့် တွဲဖက်အသုံးပြုနိုင်သော 2FA အက်ပ်ကို အသုံးပြုပါ။", "enter_totp_code": "ကျေးဇူးပြု၍ TOTP ကုဒ်ကို ထည့်ပါ။", - "add_secret_code": "ဤလျှို့ဝှက်ကုဒ်ကို အခြားစက်ပစ္စည်းသို့ ထည့်ပါ။", + "add_secret_code": "သို့မဟုတ် ဤလျှို့ဝှက်ကုဒ်ကို အထောက်အထားစိစစ်ခြင်းအက်ပ်တစ်ခုသို့ ထည့်ပါ။", "totp_secret_code": "TOTP လျှို့ဝှက်ကုဒ်", - "important_note": "အရေးကြီးသောမှတ်ချက်", - "setup_2fa_text": "ကိတ်မုန့် 2FA သည် အအေးခန်းကဲ့သို့ မလုံခြုံပါ။ 2FA သည် သင်အိပ်နေစဉ်တွင် သင့်သူငယ်ချင်းသည် သင့်လက်ဗွေရာကို ပေးဆောင်ခြင်းကဲ့သို့သော အခြေခံတိုက်ခိုက်မှုအမျိုးအစားများကို ကာကွယ်ပေးပါသည်။\n\n Cake 2FA သည် ခေတ်မီဆန်းပြားသော တိုက်ခိုက်သူ၏ အန္တရာယ်ပြုသည့်စက်ပစ္စည်းကို မကာကွယ်ပါ။\n\n သင်၏ 2FA ကုဒ်များကို အသုံးပြုခွင့်ဆုံးရှုံးသွားပါက၊ ဤပိုက်ဆံအိတ်ကို သင်ဝင်ရောက်ခွင့်ဆုံးရှုံးလိမ့်မည်။ သင့်ပိုက်ဆံအိတ်ကို mnemonic မျိုးစေ့မှ ပြန်လည်ရယူရန် လိုအပ်မည်ဖြစ်သည်။ ထို့ကြောင့် သင်၏ MNEMONIC မျိုးစေ့များကို အရန်သိမ်းထားရပါမည်။ ထို့အပြင်၊ သင်၏ mnemonic မျိုးစေ့(များ) ကို အသုံးပြုခွင့်ရှိသူတစ်ဦးက Cake 2FA ကိုကျော်ဖြတ်ကာ သင့်ရန်ပုံငွေများကို ခိုးယူနိုင်ပါမည်။\n\n ကိတ်မုန့်သည် သင့် mnemonic မျိုးစေ့သို့ ဝင်ရောက်ခွင့်ဆုံးရှုံးသွားပါက သင့်အား ကူညီပေးနိုင်မည်မဟုတ်ပါ၊ အထိန်းအချုပ်မရှိသော ပိုက်ဆံအိတ်။", - "setup_totp_recommended": "TOTP ကို ​​စနစ်ထည့်သွင်းပါ (အကြံပြုထားသည်)", + "setup_2fa_text": "ကိတ်မုန့် 2FA သည် TOTP ကို ဒုတိယ စစ်မှန်ကြောင်းအထောက်အထားအဖြစ် အသုံးပြု၍ လုပ်ဆောင်သည်။\n\nကိတ်မုန့် 2FA ၏ TOTP သည် SHA-512 နှင့် 8 ဂဏန်းပံ့ပိုးမှု လိုအပ်သည်။ ဒါက လုံခြုံရေးကို တိုးမြှင့်ပေးတယ်။ နောက်ထပ်အချက်အလက်များနှင့် ပံ့ပိုးပေးထားသောအက်ပ်များကို လမ်းညွှန်တွင် တွေ့နိုင်ပါသည်။", + "setup_totp_recommended": "TOTP စနစ်ထည့်သွင်းပါ။", "disable_buy": "ဝယ်ယူမှု လုပ်ဆောင်ချက်ကို ပိတ်ပါ။", "disable_sell": "ရောင်းချခြင်းလုပ်ဆောင်ချက်ကို ပိတ်ပါ။", "cake_2fa_preset": "ကိတ်မုန့် 2FA ကြိုတင်သတ်မှတ်", @@ -729,6 +728,9 @@ "require_for_exchanges_to_external_wallets": "ပြင်ပပိုက်ဆံအိတ်များသို့ လဲလှယ်ရန် လိုအပ်သည်။", "camera_permission_is_required": "ကင်မရာခွင့်ပြုချက် လိုအပ်ပါသည်။\nအက်ပ်ဆက်တင်များမှ ၎င်းကိုဖွင့်ပါ။", "switchToETHWallet": "ကျေးဇူးပြု၍ Ethereum ပိုက်ဆံအိတ်သို့ ပြောင်းပြီး ထပ်စမ်းကြည့်ပါ။", + "order_by": "အမှာစာ", + "creation_date": "ဖန်တီးမှုနေ့စွဲ", + "group_by_type": "အမျိုးအစားအလိုက်အုပ်စုဖွဲ့", "importNFTs": "NFTs များကို တင်သွင်းပါ။", "noNFTYet": "NFTs မရှိသေးပါ။", "address": "လိပ်စာ", @@ -741,11 +743,23 @@ "unavailable_balance_description": "မရရှိနိုင်သော လက်ကျန်ငွေ- ဤစုစုပေါင်းတွင် ဆိုင်းငံ့ထားသော ငွေပေးငွေယူများတွင် သော့ခတ်ထားသော ငွေကြေးများနှင့် သင်၏ coin ထိန်းချုပ်မှုဆက်တင်များတွင် သင် တက်ကြွစွာ အေးခဲထားသော ငွေများ ပါဝင်သည်။ သော့ခတ်ထားသော လက်ကျန်ငွေများကို ၎င်းတို့၏ သက်ဆိုင်ရာ ငွေပေးငွေယူများ ပြီးမြောက်သည်နှင့် တပြိုင်နက် ရရှိနိုင်မည်ဖြစ်ပြီး၊ အေးခဲထားသော လက်ကျန်များကို ၎င်းတို့အား ပြန်ဖြုတ်ရန် သင်ဆုံးဖြတ်သည်အထိ ငွေပေးငွေယူများအတွက် ဆက်လက်၍မရနိုင်ပါ။", "unspent_change": "ပေြာင်းလဲခြင်း", "tor_connection": "Tor ချိတ်ဆက်မှု", + "setup_warning_2fa_text": "ကိတ်မုန့် 2FA သည် ပိုက်ဆံအိတ်ရှိ အချို့သော လုပ်ဆောင်ချက်များ အတွက် ဒုတိယ စစ်မှန်ကြောင်း အထောက်အထား ဖြစ်သည်။ ၎င်းသည် အအေးခန်းကဲ့သို့ မလုံခြုံပါ။\n\nသင်၏ 2FA အက်ပ် သို့မဟုတ် TOTP သော့များကို အသုံးပြုခွင့် ဆုံးရှုံးပါက၊ သင်သည် ဤပိုက်ဆံအိတ်သို့ ဝင်ရောက်ခွင့် ဆုံးရှုံးမည်ဖြစ်သည်။ သင့်ပိုက်ဆံအိတ်ကို mnemonic မျိုးစေ့မှ ပြန်လည်ရယူရန် လိုအပ်မည်ဖြစ်သည်။\n\nသင်သည် သင်၏ 2FA သို့မဟုတ် mnemonic အစေ့များကို အသုံးပြုခွင့်ဆုံးရှုံးသွားပါက ကိတ်မုန့်ပံ့ပိုးကူညီမှု မပေးနိုင်ပါ။\nCake 2FA ကို အသုံးမပြုမီ၊ လမ်းညွှန်ချက်မှတစ်ဆင့် ဖတ်ရန် အကြံပြုအပ်ပါသည်။", + "scan_qr_on_device": "အခြားစက်တွင် ဤ QR ကုဒ်ကို စကင်ဖတ်ပါ။", + "how_to_use": "အသုံးပြုနည်း", "seed_hex_form": "ပိုက်ဆံအိတ်မျိုးစေ့ (Hex Form)", "seedtype": "မျိုးပွားခြင်း", "seedtype_legacy": "အမွေအနှစ် (စကားလုံး 25 လုံး)", "seedtype_polyseed": "polyseed (စကားလုံး 16 လုံး)", "seed_language_czech": "ချက်", "seed_language_korean": "ကိုးရီးယား", - "seed_language_chinese_traditional": "တရုတ်ရိုးရာ)" -} \ No newline at end of file + "seed_language_chinese_traditional": "တရုတ်ရိုးရာ)", + "ascending": "တက်", + "descending": "ဆင်း", + "dfx_option_description": "EUR & CHF ဖြင့် crypto ကိုဝယ်ပါ။ အပို KYC မပါဘဲ 990€ အထိ။ ဥရောပရှိ လက်လီရောင်းချသူများနှင့် ကော်ပိုရိတ်ဖောက်သည်များအတွက်", + "polygonscan_history": "PolygonScan မှတ်တမ်း", + "wallet_seed_legacy": "အမွေအနှစ်ပိုက်ဆံအိတ်မျိုးစေ့", + "default_sell_provider": "ပုံသေရောင်းချပေးသူ", + "select_sell_provider_notice": "အထက်ဖော်ပြပါ အရောင်းဝန်ဆောင်မှုပေးသူကို ရွေးပါ။ အက်ပ်ဆက်တင်များတွင် သင်၏မူလရောင်းချပေးသူကို သတ်မှတ်ခြင်းဖြင့် ဤစခရင်ကို ကျော်နိုင်သည်။", + "custom_drag": "စိတ်ကြိုက် (Drag)", + "switchToEVMCompatibleWallet": "ကျေးဇူးပြု၍ EVM တွဲဖက်သုံးနိုင်သော ပိုက်ဆံအိတ်သို့ ပြောင်းပြီး ထပ်စမ်းကြည့်ပါ (Ethereum၊ Polygon)" +} diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 8a290618d..6cf41e0b9 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -160,7 +160,7 @@ "seed_title": "Zaad", "seed_share": "Deel zaad", "copy": "Kopiëren", - "seed_language_choose": "Kies een starttaal:", + "seed_language": "Zaadtaal", "seed_choose": "Kies een starttaal", "seed_language_next": "Volgende", "seed_language_english": "Engels", @@ -571,7 +571,7 @@ "always": "altijd", "minutes_to_pin_code": "${minute} minuten", "disable_exchange": "Uitwisseling uitschakelen", - "advanced_privacy_settings": "Geavanceerde privacy-instellingen", + "advanced_settings": "Geavanceerde instellingen", "settings_can_be_changed_later": "Deze instellingen kunnen later worden gewijzigd in de app-instellingen", "add_custom_node": "Voeg een nieuw aangepast knooppunt toe", "disable_fiat": "Schakel Fiat uit", @@ -625,11 +625,10 @@ "totp_verification_success": "Verificatie geslaagd!", "totp_2fa_failure": "Foute code. Probeer een andere code of genereer een nieuwe geheime sleutel. Gebruik een compatibele 2FA-app die 8-cijferige codes en SHA512 ondersteunt.", "enter_totp_code": "Voer de TOTP-code in.", - "add_secret_code": "Voeg deze geheime code toe aan een ander apparaat", + "add_secret_code": "Of voeg deze geheime code toe aan een authenticator-app", "totp_secret_code": "TOTP-geheime code", - "important_note": "Belangrijke notitie", - "setup_2fa_text": "Cake 2FA is NIET zo veilig als koude opslag. 2FA beschermt tegen basistypen aanvallen, zoals uw vriend die uw vingerafdruk geeft terwijl u slaapt.\n\n Cake 2FA biedt GEEN bescherming tegen een gecompromitteerd apparaat door een geavanceerde aanvaller.\n\n Als u de toegang tot uw 2FA-codes kwijtraakt , VERLIEST U DE TOEGANG TOT DEZE PORTEFEUILLE. U moet uw portemonnee herstellen van mnemonic seed. JE MOET DAAROM EEN BACK-UP MAKEN VAN JE MNEMONISCHE ZADEN! Verder kan iemand met toegang tot je geheugensteuntje(s) je geld stelen, waarbij Cake 2FA wordt omzeild.\n\n Het ondersteunend personeel van Cake kan je niet helpen als je de toegang tot je geheugensteuntje kwijtraakt, aangezien Cake een niet-bewaarbare portemonnee.", - "setup_totp_recommended": "TOTP instellen (aanbevolen)", + "setup_2fa_text": "Cake 2FA werkt met TOTP als tweede authenticatiefactor.\n\nCake 2FA's TOTP vereist SHA-512 en 8-cijferige ondersteuning; dit zorgt voor meer veiligheid. Meer informatie en ondersteunde apps vindt u in de gids.", + "setup_totp_recommended": "TOTP instellen", "disable_buy": "Koopactie uitschakelen", "disable_sell": "Verkoopactie uitschakelen", "auto_generate_subaddresses": "Automatisch subadressen genereren", @@ -731,6 +730,9 @@ "require_for_exchanges_to_external_wallets": "Vereist voor uitwisselingen naar externe portemonnees", "camera_permission_is_required": "Cameratoestemming is vereist.\nSchakel dit in via de app-instellingen.", "switchToETHWallet": "Schakel over naar een Ethereum-portemonnee en probeer het opnieuw", + "order_by": "Bestellen door", + "creation_date": "Aanmaakdatum", + "group_by_type": "Groep voor type", "importNFTs": "NFT's importeren", "noNFTYet": "Nog geen NFT's", "address": "Adres", @@ -743,11 +745,23 @@ "unavailable_balance_description": "Niet-beschikbaar saldo: Dit totaal omvat het geld dat is vergrendeld in lopende transacties en het geld dat u actief hebt bevroren in uw muntcontrole-instellingen. Vergrendelde saldi komen beschikbaar zodra de betreffende transacties zijn voltooid, terwijl bevroren saldi ontoegankelijk blijven voor transacties totdat u besluit ze weer vrij te geven.", "unspent_change": "Wijziging", "tor_connection": "Tor-verbinding", + "setup_warning_2fa_text": "U moet uw portemonnee herstellen vanuit het geheugensteuntje.\n\nCake Support kan u niet helpen als u de toegang tot uw 2FA- of mnemonic-zaden verliest.\nCake 2FA is een tweede authenticatie voor bepaalde acties in de portemonnee. Voordat u Cake 2FA gebruikt, raden wij u aan de handleiding door te lezen.Het is NIET zo veilig als koude opslag.\n\nAls u de toegang tot uw 2FA-app of TOTP-sleutels verliest, verliest u de toegang tot deze portemonnee. ", + "scan_qr_on_device": "Scan deze QR-code op een ander apparaat", + "how_to_use": "Hoe te gebruiken", "seed_hex_form": "Portemonnee zaad (hexvorm)", "seedtype": "Zaadtype", "seedtype_legacy": "Legacy (25 woorden)", "seedtype_polyseed": "Polyseed (16 woorden)", "seed_language_czech": "Tsjechisch", "seed_language_korean": "Koreaans", - "seed_language_chinese_traditional": "Chinese traditionele)" -} \ No newline at end of file + "ascending": "Stijgend", + "descending": "Aflopend", + "seed_language_chinese_traditional": "Chinese (traditionele)", + "dfx_option_description": "Koop crypto met EUR & CHF. Tot 990€ zonder extra KYC. Voor particuliere en zakelijke klanten in Europa", + "polygonscan_history": "PolygonScan-geschiedenis", + "wallet_seed_legacy": "Legacy portemonnee zaad", + "default_sell_provider": "Standaard verkoopaanbieder", + "select_sell_provider_notice": "Selecteer hierboven een verkoopaanbieder. U kunt dit scherm overslaan door uw standaardverkoopprovider in te stellen in de app-instellingen.", + "custom_drag": "Custom (vasthouden en slepen)", + "switchToEVMCompatibleWallet": "Schakel over naar een EVM-compatibele portemonnee en probeer het opnieuw (Ethereum, Polygon)" +} diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index b84ddc98c..9d3fb3f93 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -160,7 +160,7 @@ "seed_title": "Seed", "seed_share": "Udostępnij seed", "copy": "Kopiuj", - "seed_language_choose": "Proszę wybrać język słów we frazie seed:", + "seed_language": "Język nasion", "seed_choose": "Wybierz język", "seed_language_next": "Następny", "seed_language_english": "Angielski", @@ -571,7 +571,7 @@ "always": "zawsze", "minutes_to_pin_code": "${minute} minut", "disable_exchange": "Wyłącz wymianę", - "advanced_privacy_settings": "Zaawansowane ustawienia prywatności", + "advanced_settings": "Zaawansowane ustawienia", "settings_can_be_changed_later": "Te ustawienia można później zmienić w ustawieniach aplikacji", "add_custom_node": "Dodaj nowy węzeł niestandardowy", "disable_fiat": "Wyłącz waluty FIAT", @@ -625,11 +625,10 @@ "totp_verification_success": "Weryfikacja powiodła się!", "totp_2fa_failure": "Błędny kod. Spróbuj użyć innego kodu lub wygeneruj nowy tajny klucz. Użyj kompatybilnej aplikacji 2FA, która obsługuje 8-cyfrowe kody i SHA512.", "enter_totp_code": "Wprowadź kod TOTP.", - "add_secret_code": "Dodaj ten tajny kod do innego urządzenia", + "add_secret_code": "Możesz też dodać ten tajny kod do aplikacji uwierzytelniającej", "totp_secret_code": "Tajny kod TOTP", - "important_note": "Ważna uwaga", - "setup_2fa_text": "Cake 2FA NIE jest tak bezpieczny jak przechowywanie w chłodni. 2FA chroni przed podstawowymi typami ataków, takimi jak udostępnienie odcisku palca przez znajomego podczas snu.\n\n Cake 2FA NIE chroni przed zhakowanym urządzeniem przez wyrafinowanego atakującego.\n\n Jeśli utracisz dostęp do swoich kodów 2FA , UTRACISZ DOSTĘP DO TEGO PORTFELA. Będziesz musiał przywrócić swój portfel z mnemonicznego materiału siewnego. DLATEGO MUSISZ ZROBIĆ KOPIĘ SWOICH NASION MNEMONICZNYCH! Co więcej, ktoś z dostępem do twoich mnemonicznych nasion będzie mógł ukraść twoje fundusze, omijając Cake 2FA.\n\n Personel pomocniczy Cake nie będzie mógł ci pomóc, jeśli stracisz dostęp do swojego mnemonicznego seeda, ponieważ Cake jest portfel niezabezpieczony.", - "setup_totp_recommended": "Skonfiguruj TOTP (zalecane)", + "setup_2fa_text": "Cake 2FA działa przy użyciu TOTP jako drugiego czynnika uwierzytelniającego.\n\nTOTP Cake 2FA wymaga obsługi SHA-512 i 8 cyfr; zapewnia to większe bezpieczeństwo. Więcej informacji i obsługiwane aplikacje znajdziesz w przewodniku.", + "setup_totp_recommended": "Skonfiguruj TOTP", "disable_buy": "Wyłącz akcję kupna", "disable_sell": "Wyłącz akcję sprzedaży", "auto_generate_subaddresses": "Automatycznie generuj podadresy", @@ -731,6 +730,9 @@ "require_for_exchanges_to_external_wallets": "Wymagaj wymiany na portfele zewnętrzne", "camera_permission_is_required": "Wymagane jest pozwolenie na korzystanie z aparatu.\nWłącz tę funkcję w ustawieniach aplikacji.", "switchToETHWallet": "Przejdź na portfel Ethereum i spróbuj ponownie", + "order_by": "Zamów przez", + "creation_date": "Data utworzenia", + "group_by_type": "Grupa według typu", "importNFTs": "Importuj NFT", "noNFTYet": "Nie ma jeszcze NFT", "address": "Adres", @@ -743,11 +745,23 @@ "unavailable_balance_description": "Niedostępne saldo: Suma ta obejmuje środki zablokowane w transakcjach oczekujących oraz te, które aktywnie zamroziłeś w ustawieniach kontroli monet. Zablokowane salda staną się dostępne po zakończeniu odpowiednich transakcji, natomiast zamrożone salda pozostaną niedostępne dla transakcji, dopóki nie zdecydujesz się ich odblokować.", "unspent_change": "Zmiana", "tor_connection": "Połączenie Torem", + "setup_warning_2fa_text": "Będziesz musiał przywrócić swój portfel z nasion mnemonicznych.\n\nWsparcie Cake nie będzie w stanie Ci pomóc, jeśli utracisz dostęp do swoich nasion 2FA lub mnemoników.\nCake 2FA to drugie uwierzytelnienie niektórych działań w portfelu. Przed użyciem Cake 2FA zalecamy zapoznanie się z instrukcją.NIE jest tak bezpieczne jak przechowywanie w chłodni.\n\nJeśli utracisz dostęp do aplikacji 2FA lub kluczy TOTP, UTRAcisz dostęp do tego portfela. ", + "scan_qr_on_device": "Zeskanuj ten kod QR na innym urządzeniu", + "how_to_use": "Jak używać", "seed_hex_form": "Nasiona portfela (forma sześciokątna)", "seedtype": "Sedtype", "seedtype_legacy": "Dziedzictwo (25 słów)", "seedtype_polyseed": "Poliqueed (16 słów)", "seed_language_czech": "Czech", "seed_language_korean": "koreański", - "seed_language_chinese_traditional": "Chiński tradycyjny)" -} \ No newline at end of file + "seed_language_chinese_traditional": "Chiński tradycyjny)", + "ascending": "Wznoszący się", + "descending": "Schodzenie", + "dfx_option_description": "Kupuj kryptowaluty za EUR i CHF. Do 990 € bez dodatkowego KYC. Dla klientów detalicznych i korporacyjnych w Europie", + "polygonscan_history": "Historia PolygonScan", + "wallet_seed_legacy": "Dziedziczne ziarno portfela", + "default_sell_provider": "Domyślny dostawca sprzedaży", + "select_sell_provider_notice": "Wybierz dostawcę sprzedaży powyżej. Możesz pominąć ten ekran, ustawiając domyślnego dostawcę sprzedaży w ustawieniach aplikacji.", + "custom_drag": "Niestandardowe (trzymaj i przeciągnij)", + "switchToEVMCompatibleWallet": "Przejdź na portfel zgodny z EVM i spróbuj ponownie (Ethereum, Polygon)" +} diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index ed7b8a801..5cf1fd370 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -160,7 +160,7 @@ "seed_title": "Semente", "seed_share": "Compartilhar semente", "copy": "Copiar", - "seed_language_choose": "Por favor, escolha o idioma da semente:", + "seed_language": "Linguagem de semente", "seed_choose": "Escolha o idioma da semente", "seed_language_next": "Próximo", "seed_language_english": "Inglesa", @@ -570,7 +570,7 @@ "always": "sempre", "minutes_to_pin_code": "${minute} minutos", "disable_exchange": "Desativar troca", - "advanced_privacy_settings": "Configurações de privacidade avançadas", + "advanced_settings": "Configurações avançadas", "settings_can_be_changed_later": "Essas configurações podem ser alteradas posteriormente nas configurações do aplicativo", "add_custom_node": "Adicionar novo nó personalizado", "disable_fiat": "Desativar fiat", @@ -624,11 +624,10 @@ "totp_verification_success": "Verificação bem-sucedida!", "totp_2fa_failure": "Código incorreto. Tente um código diferente ou gere uma nova chave secreta. Use um aplicativo 2FA compatível com códigos de 8 dígitos e SHA512.", "enter_totp_code": "Digite o código TOTP.", - "add_secret_code": "Adicione este código secreto a outro dispositivo", + "add_secret_code": "Ou adicione este código secreto a um aplicativo autenticador", "totp_secret_code": "Código Secreto TOTP", - "important_note": "Nota importante", - "setup_2fa_text": "O Cake 2FA NÃO é tão seguro quanto o armazenamento a frio. O 2FA protege contra tipos básicos de ataques, como seu amigo fornecer sua impressão digital enquanto você está dormindo.\n\n O Cake 2FA NÃO protege contra um dispositivo comprometido por um invasor sofisticado.\n\n Se você perder o acesso aos seus códigos 2FA , VOCÊ PERDERÁ O ACESSO A ESTA CARTEIRA. Você precisará restaurar sua carteira da semente mnemônica. VOCÊ DEVE, PORTANTO, FAZER BACKUP DE SUAS SEMENTES MNEMÔNICAS! Além disso, alguém com acesso às suas sementes mnemônicas poderá roubar seus fundos, ignorando o Cake 2FA.\n\n A equipe de suporte do Cake não poderá ajudá-lo se você perder o acesso à sua semente mnemônica, pois o Cake é um carteira não custodial.", - "setup_totp_recommended": "Configurar TOTP (recomendado)", + "setup_2fa_text": "Cake 2FA funciona usando TOTP como segundo fator de autenticação.\n\nO TOTP do Cake 2FA requer suporte SHA-512 e 8 dígitos; isso proporciona maior segurança. Mais informações e aplicativos suportados podem ser encontrados no guia.", + "setup_totp_recommended": "Configurar TOTP", "disable_buy": "Desativar ação de compra", "disable_sell": "Desativar ação de venda", "auto_generate_subaddresses": "Gerar subendereços automaticamente", @@ -730,6 +729,9 @@ "require_for_exchanges_to_external_wallets": "Exigir trocas para carteiras externas", "camera_permission_is_required": "É necessária permissão da câmera.\nAtive-o nas configurações do aplicativo.", "switchToETHWallet": "Mude para uma carteira Ethereum e tente novamente", + "order_by": "Ordenar por", + "creation_date": "Data de criação", + "group_by_type": "Grupo por tipo", "importNFTs": "Importar NFTs", "noNFTYet": "Ainda não há NFT", "address": "Endereço", @@ -742,11 +744,23 @@ "unavailable_balance_description": "Saldo Indisponível: Este total inclui fundos bloqueados em transações pendentes e aqueles que você congelou ativamente nas configurações de controle de moedas. Os saldos bloqueados ficarão disponíveis assim que suas respectivas transações forem concluídas, enquanto os saldos congelados permanecerão inacessíveis para transações até que você decida descongelá-los.", "unspent_change": "Mudar", "tor_connection": "Conexão Tor", + "setup_warning_2fa_text": "Você precisará restaurar sua carteira a partir da semente mnemônica.\n\nO suporte do Cake não poderá ajudá-lo se você perder o acesso ao seu 2FA ou sementes mnemônicas.\nCake 2FA é uma segunda autenticação para determinadas ações na carteira. Antes de usar o Cake 2FA, recomendamos a leitura do guia.NÃO é tão seguro quanto o armazenamento refrigerado.\n\nSe você perder o acesso ao seu aplicativo 2FA ou às chaves TOTP, você perderá o acesso a esta carteira. ", + "scan_qr_on_device": "Digitalize este código QR em outro dispositivo", + "how_to_use": "Como usar", "seed_hex_form": "Semente de carteira (forma hexadecimal)", "seedtype": "SeedType", "seedtype_legacy": "Legado (25 palavras)", "seedtype_polyseed": "Polyseed (16 palavras)", "seed_language_czech": "Tcheco", "seed_language_korean": "coreano", - "seed_language_chinese_traditional": "Chinês tradicional)" -} \ No newline at end of file + "seed_language_chinese_traditional": "Chinês tradicional)", + "ascending": "Ascendente", + "descending": "descendente", + "dfx_option_description": "Compre criptografia com EUR e CHF. Até 990€ sem KYC adicional. Para clientes de varejo e corporativos na Europa", + "polygonscan_history": "História do PolygonScan", + "wallet_seed_legacy": "Semente de carteira herdada", + "default_sell_provider": "Provedor de venda padrão", + "select_sell_provider_notice": "Selecione um fornecedor de venda acima. Você pode pular esta tela definindo seu provedor de venda padrão nas configurações do aplicativo.", + "custom_drag": "Personalizado (segure e arraste)", + "switchToEVMCompatibleWallet": "Mude para uma carteira compatível com EVM e tente novamente (Ethereum, Polygon)" +} diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index c7749d0ea..c81c0bd82 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -160,7 +160,7 @@ "seed_title": "Мнемоническая фраза", "seed_share": "Поделиться мнемонической фразой", "copy": "Скопировать", - "seed_language_choose": "Пожалуйста, выберите язык мнемонической фразы:", + "seed_language": "Язык семян", "seed_choose": "Выберите язык мнемонической фразы", "seed_language_next": "Продолжить", "seed_language_english": "Английский", @@ -571,7 +571,7 @@ "always": "всегда", "minutes_to_pin_code": "${minute} минут", "disable_exchange": "Отключить обмен", - "advanced_privacy_settings": "Расширенные настройки конфиденциальности", + "advanced_settings": "Расширенные настройки", "settings_can_be_changed_later": "Эти настройки можно изменить позже в настройках приложения.", "add_custom_node": "Добавить новый пользовательский узел", "disable_fiat": "Отключить фиат", @@ -625,11 +625,10 @@ "totp_verification_success": "Проверка прошла успешно!", "totp_2fa_failure": "Неверный код. Пожалуйста, попробуйте другой код или создайте новый секретный ключ. Используйте совместимое приложение 2FA, которое поддерживает 8-значные коды и SHA512.", "enter_totp_code": "Пожалуйста, введите TOTP-код.", - "add_secret_code": "Добавьте этот секретный код на другое устройство", + "add_secret_code": "Или добавьте этот секретный код в приложение для аутентификации.", "totp_secret_code": "Секретный код ТОТП", - "important_note": "Важная заметка", - "setup_2fa_text": "Cake 2FA НЕ так безопасен, как холодное хранилище. Двухфакторная аутентификация защищает от основных типов атак, таких как отпечаток вашего друга, когда вы спите.\n\n Двухфакторная аутентификация Cake НЕ защищает от взлома устройства опытным злоумышленником.\n\n Если вы потеряете доступ к своим кодам двухфакторной аутентификации. , ВЫ ПОТЕРЯЕТЕ ДОСТУП К ЭТОМУ КОШЕЛЬКУ. Вам нужно будет восстановить свой кошелек из мнемонической семени. ПОЭТОМУ ВЫ ДОЛЖНЫ СОЗДАТЬ РЕЗЕРВНУЮ ВЕРСИЮ СВОИХ МНЕМОНИКОВ! Кроме того, кто-то, имеющий доступ к вашему мнемоническому семени, сможет украсть ваши средства, минуя Cake 2FA.\n\n Персонал службы поддержки Cake не сможет помочь вам, если вы потеряете доступ к своему мнемоническому семени, поскольку Cake — это некастодиальный кошелек.", - "setup_totp_recommended": "Настроить TOTP (рекомендуется)", + "setup_2fa_text": "Cake 2FA работает с использованием TOTP в качестве второго фактора аутентификации.\n\nTOTP Cake 2FA требует SHA-512 и поддержки 8 цифр; это обеспечивает повышенную безопасность. Дополнительную информацию и поддерживаемые приложения можно найти в руководстве.", + "setup_totp_recommended": "Настройка ТОТП", "disable_buy": "Отключить действие покупки", "disable_sell": "Отключить действие продажи", "cake_2fa_preset": "Торт 2FA Preset", @@ -731,6 +730,9 @@ "require_for_exchanges_to_external_wallets": "Требовать обмена на внешние кошельки", "camera_permission_is_required": "Требуется разрешение камеры.\nПожалуйста, включите его в настройках приложения.", "switchToETHWallet": "Пожалуйста, переключитесь на кошелек Ethereum и повторите попытку.", + "order_by": "Сортировать по", + "creation_date": "Дата создания", + "group_by_type": "Группа по типу", "importNFTs": "Импортировать NFT", "noNFTYet": "NFT пока нет", "address": "Адрес", @@ -743,11 +745,23 @@ "unavailable_balance_description": "Недоступный баланс: в эту сумму входят средства, заблокированные в ожидающих транзакциях, и средства, которые вы активно заморозили в настройках управления монетами. Заблокированные балансы станут доступны после завершения соответствующих транзакций, а замороженные балансы останутся недоступными для транзакций, пока вы не решите их разморозить.", "unspent_change": "Изменять", "tor_connection": "Тор соединение", + "setup_warning_2fa_text": "Cake 2FA — это вторая аутентификация для определенных действий в кошельке. Это НЕ так безопасно, как холодное хранение.\n\nЕсли вы потеряете доступ к своему приложению 2FA или ключам TOTP, вы потеряете доступ к этому кошельку. Вам нужно будет восстановить свой кошелек из мнемонического сида.\n\nСлужба поддержки Cake не сможет вам помочь, если вы потеряете доступ к своим 2FA или мнемоническим идентификаторам.\nПрежде чем использовать Cake 2FA, мы рекомендуем прочитать руководство.", + "scan_qr_on_device": "Отсканируйте этот QR-код на другом устройстве", + "how_to_use": "Как использовать", "seed_hex_form": "Семя кошелька (шестнадцатеричная форма)", "seedtype": "SEEDTYPE", "seedtype_legacy": "Наследие (25 слов)", "seedtype_polyseed": "Полиса (16 слов)", "seed_language_czech": "Чешский", "seed_language_korean": "Корейский", - "seed_language_chinese_traditional": "Китайский традиционный)" -} \ No newline at end of file + "seed_language_chinese_traditional": "Китайский традиционный)", + "ascending": "Восходящий", + "descending": "Нисходящий", + "dfx_option_description": "Покупайте криптовалюту за EUR и CHF. До 990€ без дополнительного KYC. Для розничных и корпоративных клиентов в Европе", + "polygonscan_history": "История PolygonScan", + "wallet_seed_legacy": "Наследие семя кошелька", + "default_sell_provider": "Поставщик продаж по умолчанию", + "select_sell_provider_notice": "Выберите поставщика услуг продажи выше. Вы можете пропустить этот экран, установив поставщика услуг продаж по умолчанию в настройках приложения.", + "custom_drag": "Пользователь (удерживайте и перетаскивайте)", + "switchToEVMCompatibleWallet": "Пожалуйста, переключитесь на кошелек, совместимый с EVM, и повторите попытку (Ethereum, Polygon)." +} diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index 791b0f005..0965d3979 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -160,7 +160,7 @@ "seed_title": "Seed", "seed_share": "แบ่งปัน seed", "copy": "คัดลอก", - "seed_language_choose": "โปรดเลือกภาษาของ seed:", + "seed_language": "ภาษาเมล็ด", "seed_choose": "เลือกภาษาของ seed", "seed_language_next": "ถัดไป", "seed_language_english": "อังกฤษ", @@ -569,7 +569,7 @@ "always": "เสมอ", "minutes_to_pin_code": "${minute} นาที", "disable_exchange": "ปิดใช้งานการแลกเปลี่ยน", - "advanced_privacy_settings": "การตั้งค่าความเป็นส่วนตัวขั้นสูง", + "advanced_settings": "ตั้งค่าขั้นสูง", "settings_can_be_changed_later": "การตั้งค่านี้สามารถเปลี่ยนแปลงได้ภายหลังในการตั้งค่าแอพฯ", "add_custom_node": "เพิ่มจุดโหนดแบบกำหนดเอง", "disable_fiat": "ปิดใช้งานสกุลเงินตรา", @@ -623,11 +623,10 @@ "totp_verification_success": "การยืนยันสำเร็จ!", "totp_2fa_failure": "รหัสไม่ถูกต้อง. โปรดลองใช้รหัสอื่นหรือสร้างรหัสลับใหม่ ใช้แอพ 2FA ที่เข้ากันได้ซึ่งรองรับรหัส 8 หลักและ SHA512", "enter_totp_code": "กรุณาใส่รหัสทีโอที", - "add_secret_code": "เพิ่มรหัสลับนี้ไปยังอุปกรณ์อื่น", + "add_secret_code": "หรือเพิ่มรหัสลับนี้ลงในแอปตรวจสอบความถูกต้อง", "totp_secret_code": "รหัสลับ TOTP", - "important_note": "โน๊ตสำคัญ", - "setup_2fa_text": "Cake 2FA ไม่ปลอดภัยเท่าห้องเย็น 2FA ป้องกันการโจมตีประเภทพื้นฐาน เช่น เพื่อนของคุณให้ลายนิ้วมือขณะที่คุณนอนหลับ\n\n Cake 2FA ไม่ป้องกันอุปกรณ์ที่ถูกบุกรุกโดยผู้โจมตีที่เชี่ยวชาญ\n\n หากคุณสูญเสียการเข้าถึงรหัส 2FA ของคุณ คุณจะสูญเสียการเข้าถึงกระเป๋าเงินนี้ คุณจะต้องกู้คืนกระเป๋าเงินของคุณจากเมล็ดช่วยจำ คุณต้องสำรองเมล็ดความจำของคุณ! นอกจากนี้ ผู้ที่สามารถเข้าถึงเมล็ดช่วยจำของคุณจะสามารถขโมยเงินของคุณ โดยผ่าน Cake 2FA\n\n เจ้าหน้าที่ช่วยเหลือของ Cake จะไม่สามารถช่วยเหลือคุณได้ หากคุณสูญเสียการเข้าถึงเมล็ดช่วยจำ เนื่องจาก Cake เป็น กระเป๋าสตางค์ที่ไม่เป็นผู้ดูแล", - "setup_totp_recommended": "ตั้งค่า TOTP (แนะนำ)", + "setup_2fa_text": "Cake 2FA ทำงานโดยใช้ TOTP เป็นปัจจัยการตรวจสอบสิทธิ์ที่สอง\n\nTOTP ของ Cake 2FA ต้องการการสนับสนุน SHA-512 และ 8 หลัก สิ่งนี้ให้ความปลอดภัยเพิ่มขึ้น ข้อมูลเพิ่มเติมและแอปที่รองรับมีอยู่ในคำแนะนำ", + "setup_totp_recommended": "ตั้งค่า TOTP", "disable_buy": "ปิดการใช้งานการซื้อ", "disable_sell": "ปิดการใช้งานการขาย", "cake_2fa_preset": "เค้ก 2FA ที่ตั้งไว้ล่วงหน้า", @@ -729,6 +728,9 @@ "require_for_exchanges_to_external_wallets": "จำเป็นต้องแลกเปลี่ยนกับกระเป๋าเงินภายนอก", "camera_permission_is_required": "ต้องได้รับอนุญาตจากกล้อง\nโปรดเปิดใช้งานจากการตั้งค่าแอป", "switchToETHWallet": "โปรดเปลี่ยนไปใช้กระเป๋าเงิน Ethereum แล้วลองอีกครั้ง", + "order_by": "สั่งโดย", + "creation_date": "วันที่สร้าง", + "group_by_type": "กลุ่มตามประเภท", "importNFTs": "นำเข้า NFT", "noNFTYet": "ยังไม่มี NFT", "address": "ที่อยู่", @@ -741,11 +743,23 @@ "unavailable_balance_description": "ยอดคงเหลือที่ไม่พร้อมใช้งาน: ยอดรวมนี้รวมถึงเงินทุนที่ถูกล็อคในการทำธุรกรรมที่รอดำเนินการและที่คุณได้แช่แข็งไว้ในการตั้งค่าการควบคุมเหรียญของคุณ ยอดคงเหลือที่ถูกล็อคจะพร้อมใช้งานเมื่อธุรกรรมที่เกี่ยวข้องเสร็จสมบูรณ์ ในขณะที่ยอดคงเหลือที่แช่แข็งจะไม่สามารถเข้าถึงได้สำหรับธุรกรรมจนกว่าคุณจะตัดสินใจยกเลิกการแช่แข็ง", "unspent_change": "เปลี่ยน", "tor_connection": "การเชื่อมต่อทอร์", + "setup_warning_2fa_text": "Cake 2FA เป็นการรับรองความถูกต้องครั้งที่สองสำหรับการกระทำบางอย่างในกระเป๋าเงิน มันไม่ปลอดภัยเท่ากับห้องเย็น\n\nหากคุณสูญเสียการเข้าถึงแอป 2FA หรือคีย์ TOTP คุณจะสูญเสียการเข้าถึงกระเป๋าเงินนี้ คุณจะต้องกู้คืนกระเป๋าเงินของคุณจากเมล็ดช่วยในการจำ\n\nการสนับสนุนเค้กจะไม่สามารถช่วยเหลือคุณได้หากคุณสูญเสียการเข้าถึง 2FA หรือเมล็ดช่วยในการจำ\nก่อนใช้ Cake 2FA เราขอแนะนำให้อ่านคำแนะนำโดยละเอียด", + "scan_qr_on_device": "สแกนโค้ด QR นี้บนอุปกรณ์อื่น", + "how_to_use": "วิธีใช้", "seed_hex_form": "เมล็ดกระเป๋าเงิน (รูปแบบฐานสิบหก)", "seedtype": "เมล็ดพันธุ์", "seedtype_legacy": "มรดก (25 คำ)", "seedtype_polyseed": "โพลีส (16 คำ)", "seed_language_czech": "ภาษาเช็ก", "seed_language_korean": "เกาหลี", - "seed_language_chinese_traditional": "จีน (ดั้งเดิม)" -} \ No newline at end of file + "seed_language_chinese_traditional": "จีน (ดั้งเดิม)", + "ascending": "จากน้อยไปมาก", + "descending": "ลงมา", + "dfx_option_description": "ซื้อ crypto ด้วย EUR และ CHF สูงถึง 990€ โดยไม่มี KYC เพิ่มเติม สำหรับลูกค้ารายย่อยและลูกค้าองค์กรในยุโรป", + "polygonscan_history": "ประวัติ PolygonScan", + "wallet_seed_legacy": "เมล็ดกระเป๋าเงินมรดก", + "default_sell_provider": "ผู้ให้บริการการขายเริ่มต้น", + "select_sell_provider_notice": "เลือกผู้ให้บริการการขายด้านบน คุณสามารถข้ามหน้าจอนี้ได้โดยการตั้งค่าผู้ให้บริการการขายเริ่มต้นในการตั้งค่าแอป", + "custom_drag": "กำหนดเอง (ค้างและลาก)", + "switchToEVMCompatibleWallet": "โปรดเปลี่ยนไปใช้กระเป๋าเงินที่รองรับ EVM แล้วลองอีกครั้ง (Ethereum, Polygon)" +} diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 8891563b0..f9bbb5dea 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -160,7 +160,7 @@ "seed_title": "Binhi", "seed_share": "Magbahagi ng binhi", "copy": "Kopya", - "seed_language_choose": "Mangyaring pumili ng wika ng binhi:", + "seed_language": "Wika ng binhi", "seed_choose": "Pumili ng wika ng binhi", "seed_language_next": "Susunod", "seed_language_english": "Ingles", @@ -572,7 +572,7 @@ "always": "Palagi", "minutes_to_pin_code": "${minute} minuto", "disable_exchange": "Huwag paganahin ang palitan", - "advanced_privacy_settings": "Mga setting ng advanced na privacy", + "advanced_settings": "Mga Advanced na Setting", "settings_can_be_changed_later": "Ang mga setting na ito ay maaaring mabago mamaya sa mga setting ng app", "add_custom_node": "Magdagdag ng bagong pasadyang node", "disable_fiat": "Huwag paganahin ang Fiat", @@ -626,11 +626,10 @@ "totp_verification_success": "Matagumpay ang pagpapatunay!", "totp_2fa_failure": "Maling code. Mangyaring subukan ang ibang code o makabuo ng isang bagong lihim na susi. Gumamit ng isang katugmang 2FA app na sumusuporta sa 8-digit na mga code at SHA512.", "enter_totp_code": "Mangyaring ipasok ang TOTP code.", - "add_secret_code": "Idagdag ang lihim na code na ito sa isa pang aparato", + "add_secret_code": "O, idagdag ang sikretong code na ito sa isang authenticator app", "totp_secret_code": "TOTP Secret Code", - "important_note": "Mahalagang paalaala", - "setup_2fa_text": "Ang cake 2FA ay hindi ligtas tulad ng malamig na imbakan. Pinoprotektahan ng 2FA laban sa mga pangunahing uri ng pag -atake, tulad ng iyong kaibigan na nagbibigay ng iyong fingerprint habang natutulog ka.\n\n Hindi pinoprotektahan ng cake 2FA laban sa isang nakompromiso na aparato ng isang sopistikadong umaatake.\n\n Kung nawalan ka ng pag -access sa iyong 2FA code, mawawalan ka ng access sa pitaka na ito. Kakailanganin mong ibalik ang iyong pitaka mula sa binhi ng mnemonic. Dapat mong i -back up ang iyong mga buto ng mnemonic! Dagdag pa, ang isang tao na may access sa iyong (mga) binhi ng mnemonic ay maaaring magnakaw ng iyong mga pondo, na lumampas sa cake 2FA.\n\n Ang mga kawani ng suporta sa cake ay hindi makakatulong sa iyo kung nawalan ka ng pag -access sa iyong mnemonic seed, dahil ang cake ay isang noncustodial wallet.", - "setup_totp_recommended": "I -set up ang TOTP (inirerekomenda)", + "setup_2fa_text": "Gumagana ang Cake 2FA gamit ang TOTP bilang pangalawang kadahilanan sa pagpapatunay.\n\nAng TOTP ng Cake 2FA ay nangangailangan ng SHA-512 at 8 digit na suporta; nagbibigay ito ng mas mataas na seguridad. Higit pang impormasyon at suportadong app ang makikita sa gabay.", + "setup_totp_recommended": "I-setup ang TOTP", "disable_buy": "Huwag paganahin ang pagkilos ng pagbili", "disable_sell": "Huwag paganahin ang pagkilos ng pagbebenta", "monero_dark_theme": "Monero Madilim na Tema", @@ -726,6 +725,9 @@ "require_for_exchanges_to_external_wallets": "Kinakailangan para sa mga palitan sa mga panlabas na wallet", "camera_permission_is_required": "Kinakailangan ang pahintulot sa camera.\nMangyaring paganahin ito mula sa mga setting ng app.", "switchToETHWallet": "Mangyaring lumipat sa isang Ethereum wallet at subukang muli", + "order_by": "Iniutos ni", + "creation_date": "Petsa ng paglikha", + "group_by_type": "Pangkat ayon sa uri", "importNFTs": "Mag-import ng mga NFT", "noNFTYet": "Wala pang NFT", "address": "Address", @@ -738,10 +740,22 @@ "unavailable_balance_description": "Hindi Available na Balanse: Kasama sa kabuuang ito ang mga pondong naka-lock sa mga nakabinbing transaksyon at ang mga aktibong na-freeze mo sa iyong mga setting ng kontrol ng coin. Magiging available ang mga naka-lock na balanse kapag nakumpleto na ang kani-kanilang mga transaksyon, habang ang mga nakapirming balanse ay nananatiling hindi naa-access para sa mga transaksyon hanggang sa magpasya kang i-unfreeze ang mga ito.", "unspent_change": "Baguhin", "tor_connection": "Koneksyon ng Tor", + "setup_warning_2fa_text": "Kakailanganin mong ibalik ang iyong wallet mula sa mnemonic seed.\n\nHindi ka matutulungan ng suporta sa cake kung mawawalan ka ng access sa iyong 2FA o mnemonic seeds.\nAng Cake 2FA ay pangalawang pagpapatotoo para sa ilang partikular na pagkilos sa wallet. Bago gamitin ang Cake 2FA, inirerekomenda naming basahin ang gabay.HINDI ito kasing-secure ng malamig na imbakan.\n\nKung nawalan ka ng access sa iyong 2FA app o TOTP keys, MAWAWALA ka ng access sa wallet na ito. ", + "scan_qr_on_device": "I-scan ang QR code na ito sa ibang device", + "how_to_use": "Paano gamitin", "seed_hex_form": "Wallet seed (hex form)", "seedtype_legacy": "Pamana (25 salita)", "seedtype_polyseed": "Polyseed (16 na salita)", "seed_language_czech": "Czech", "seed_language_korean": "Korean", - "seed_language_chinese_traditional": "Intsik (tradisyonal)" -} \ No newline at end of file + "seed_language_chinese_traditional": "Intsik (tradisyonal)", + "ascending": "Umakyat", + "descending": "Pababang", + "dfx_option_description": "Bumili ng crypto gamit ang EUR at CHF. Hanggang 990€ nang walang karagdagang KYC. Para sa retail at corporate na mga customer sa Europe", + "polygonscan_history": "Kasaysayan ng PolygonScan", + "wallet_seed_legacy": "Legacy wallet seed", + "default_sell_provider": "Default na Sell Provider", + "select_sell_provider_notice": "Pumili ng provider ng nagbebenta sa itaas. Maaari mong laktawan ang screen na ito sa pamamagitan ng pagtatakda ng iyong default na sell provider sa mga setting ng app.", + "custom_drag": "Pasadyang (hawakan at i -drag)", + "switchToEVMCompatibleWallet": "Mangyaring lumipat sa isang EVM compatible na wallet at subukang muli (Ethereum, Polygon)" +} diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index a5d6a330a..6fdb8bb99 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -160,7 +160,7 @@ "seed_title": "Tohum", "seed_share": "Tohumu paylaş", "copy": "Kopyala", - "seed_language_choose": "Lütfen tohum dilini seç:", + "seed_language": "Tohum dili", "seed_choose": "Tohum dilini seçin", "seed_language_next": "İleri", "seed_language_english": "İngilizce", @@ -569,7 +569,7 @@ "always": "Her Zaman", "minutes_to_pin_code": "${minute} dakika", "disable_exchange": "Borsayı devre dışı bırak", - "advanced_privacy_settings": "Gelişmiş Gizlilik Ayarları", + "advanced_settings": "Gelişmiş Ayarlar", "settings_can_be_changed_later": "Bu ayarlar daha sonra uygulama ayarlarından da değiştirilebilir", "add_custom_node": "Yeni Özel Düğüm Ekleme", "disable_fiat": "İtibari paraları devre dışı bırak", @@ -623,11 +623,10 @@ "totp_verification_success": "Doğrulama Başarılı!", "totp_2fa_failure": "Yanlış kod. Lütfen farklı bir kod deneyin veya yeni bir gizli anahtar oluşturun. 8 basamaklı kodları ve SHA512'yi destekleyen uyumlu bir 2FA uygulaması kullanın.", "enter_totp_code": "Lütfen TOTP Kodunu giriniz.", - "add_secret_code": "Bu gizli kodu başka bir cihaza ekleyin", + "add_secret_code": "Veya bu gizli kodu bir kimlik doğrulama uygulamasına ekleyin", "totp_secret_code": "TOTP Gizli Kodu", - "important_note": "Önemli Not", - "setup_2fa_text": "Cake 2FA, soğuk hava deposu kadar güvenli DEĞİLDİR. 2FA, siz uyurken arkadaşınızın parmak izinizi sağlaması gibi temel saldırı türlerine karşı koruma sağlar.\n\n Cake 2FA, gelişmiş bir saldırgan tarafından güvenliği ihlal edilmiş bir cihaza karşı koruma SAĞLAMAZ.\n\n 2FA kodlarınıza erişimi kaybederseniz , BU CÜZDANA ERİŞİMİNİZİ KAYBEDECEKSİNİZ. Mnemonic seed'den cüzdanınızı geri yüklemeniz gerekecek. BU NEDENLE HATIRLAYICI TOHUMLARINIZI YEDEKLEMELİSİNİZ! Ayrıca anımsatıcı tohumlarınıza erişimi olan biri, Cake 2FA'yı atlayarak paranızı çalabilir.\n\n Cake, anımsatıcı tohumlarınıza erişimi kaybederseniz size yardımcı olamaz, çünkü Cake bir saklama dışı cüzdan.", - "setup_totp_recommended": "TOTP'yi kurun (Önerilir)", + "setup_2fa_text": "Cake 2FA, ikinci kimlik doğrulama faktörü olarak TOTP'yi kullanarak çalışır.\n\nCake 2FA'nın TOTP'si SHA-512 ve 8 haneli destek gerektirir; bu daha fazla güvenlik sağlar. Daha fazla bilgi ve desteklenen uygulamalar kılavuzda bulunabilir.", + "setup_totp_recommended": "TOTP'yi kur", "disable_buy": "Satın alma işlemini devre dışı bırak", "disable_sell": "Satış işlemini devre dışı bırak", "auto_generate_subaddresses": "Alt adresleri otomatik olarak oluştur", @@ -729,6 +728,9 @@ "require_for_exchanges_to_external_wallets": "Harici cüzdanlara geçiş yapılmasını zorunlu kılın", "camera_permission_is_required": "Kamera izni gereklidir.\nLütfen uygulama ayarlarından etkinleştirin.", "switchToETHWallet": "Lütfen bir Ethereum cüzdanına geçin ve tekrar deneyin", + "order_by": "Tarafından sipariş", + "creation_date": "Oluşturulma tarihi", + "group_by_type": "Türüne göre grup", "importNFTs": "NFT'leri içe aktar", "noNFTYet": "Henüz NFT yok", "address": "Adres", @@ -741,11 +743,23 @@ "unavailable_balance_description": "Kullanılamayan Bakiye: Bu toplam, bekleyen işlemlerde kilitlenen fonları ve jeton kontrol ayarlarınızda aktif olarak dondurduğunuz fonları içerir. Kilitli bakiyeler, ilgili işlemleri tamamlandıktan sonra kullanılabilir hale gelir; dondurulmuş bakiyeler ise siz onları dondurmaya karar verene kadar işlemler için erişilemez durumda kalır.", "unspent_change": "Değiştirmek", "tor_connection": "Tor bağlantısı", + "setup_warning_2fa_text": "Cüzdanınızı anımsatıcı tohumdan geri yüklemeniz gerekecek.\n\n2FA veya anımsatıcı tohumlarınıza erişiminizi kaybederseniz pasta desteği size yardımcı olamayacaktır.\nCake 2FA, cüzdandaki belirli eylemler için ikinci bir kimlik doğrulamadır. Cake 2FA'yı kullanmadan önce kılavuzu okumanızı öneririz.Soğuk hava deposu kadar güvenli DEĞİLDİR.\n\n2FA uygulamanıza veya TOTP anahtarlarınıza erişiminizi kaybederseniz bu cüzdana erişimi KAYBEDECEKSİNİZ. ", + "scan_qr_on_device": "Bu QR kodunu başka bir cihazda tarayın", + "how_to_use": "Nasıl kullanılır", "seed_hex_form": "Cüzdan tohumu (onaltılık form)", "seedtype": "Tohum", "seedtype_legacy": "Miras (25 kelime)", "seedtype_polyseed": "Polyseed (16 kelime)", "seed_language_czech": "Çek", "seed_language_korean": "Koreli", - "seed_language_chinese_traditional": "Çin geleneği)" -} \ No newline at end of file + "seed_language_chinese_traditional": "Çin geleneği)", + "ascending": "Yükselen", + "descending": "Azalan", + "dfx_option_description": "EUR ve CHF ile kripto satın alın. Ek KYC olmadan 990 €'ya kadar. Avrupa'daki perakende ve kurumsal müşteriler için", + "polygonscan_history": "PolygonScan geçmişi", + "wallet_seed_legacy": "Eski cüzdan tohumu", + "default_sell_provider": "Varsayılan Satış Sağlayıcısı", + "select_sell_provider_notice": "Yukarıdan bir satış sağlayıcısı seçin. Uygulama ayarlarında varsayılan satış sağlayıcınızı ayarlayarak bu ekranı atlayabilirsiniz.", + "custom_drag": "Özel (Bekle ve Sürükle)", + "switchToEVMCompatibleWallet": "Lütfen EVM uyumlu bir cüzdana geçin ve tekrar deneyin (Ethereum, Polygon)" +} diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 81bc3c8aa..721a3970d 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -160,7 +160,7 @@ "seed_title": "Мнемонічна фраза", "seed_share": "Поділитися мнемонічною фразою", "copy": "Скопіювати", - "seed_language_choose": "Будь ласка, виберіть мову мнемонічної фрази:", + "seed_language": "Насіннєва мова", "seed_choose": "Виберіть мову мнемонічної фрази", "seed_language_next": "Продовжити", "seed_language_english": "Англійська", @@ -571,7 +571,7 @@ "always": "Завжди", "minutes_to_pin_code": "${minute} хвилин", "disable_exchange": "Вимкнути exchange", - "advanced_privacy_settings": "Розширені налаштування конфіденційності", + "advanced_settings": "Розширені налаштування", "settings_can_be_changed_later": "Ці параметри можна змінити пізніше в налаштуваннях програми", "add_custom_node": "Додати новий спеціальний вузол", "disable_fiat": "Вимкнути фиат", @@ -625,11 +625,10 @@ "totp_verification_success": "Перевірка успішна!", "totp_2fa_failure": "Невірний код. Спробуйте інший код або створіть новий секретний ключ. Використовуйте сумісний додаток 2FA, який підтримує 8-значні коди та SHA512.", "enter_totp_code": "Будь ласка, введіть код TOTP.", - "add_secret_code": "Додайте цей секретний код на інший пристрій", + "add_secret_code": "Або додайте цей секретний код до програми автентифікації", "totp_secret_code": "Секретний код TOTP", - "important_note": "Важливе зауваження", - "setup_2fa_text": "Торт 2FA НЕ такий безпечний, як холодне зберігання. 2FA захищає від основних типів атак, наприклад ваш друг надає ваш відбиток пальця, поки ви спите.\n\n Cake 2FA НЕ захищає від скомпрометованого пристрою досвідченим зловмисником.\n\n Якщо ви втратите доступ до своїх кодів 2FA , ВИ ВТРАТИТЕ ДОСТУП ДО ЦЬОГО ГАМАНЦЯ. Вам потрібно буде відновити свій гаманець з мнемонічного коду. ТОМУ ВИ ПОВИННІ СТВОРИТИ РЕЗЕРВНУ КОПІЮ СВОЇХ МНЕМОНІЧНИХ НАСІН! Крім того, хтось із доступом до ваших мнемонічних початкових значень зможе викрасти ваші кошти, оминаючи Cake 2FA.\n\n Співробітники служби підтримки Cake не зможуть вам допомогти, якщо ви втратите доступ до своїх мнемонічних вихідних даних, оскільки Cake є гаманець без опіки.", - "setup_totp_recommended": "Налаштувати TOTP (рекомендовано)", + "setup_2fa_text": "Cake 2FA працює з використанням TOTP як другого фактора автентифікації.\n\nДля TOTP Cake 2FA потрібен SHA-512 і підтримка 8 цифр; це забезпечує підвищену безпеку. Додаткову інформацію та підтримувані програми можна знайти в посібнику.", + "setup_totp_recommended": "Налаштувати TOTP", "disable_buy": "Вимкнути дію покупки", "disable_sell": "Вимкнути дію продажу", "auto_generate_subaddresses": "Автоматично генерувати підадреси", @@ -731,6 +730,9 @@ "require_for_exchanges_to_external_wallets": "Потрібен для обміну на зовнішні гаманці", "camera_permission_is_required": "Потрібен дозвіл камери.\nУвімкніть його в налаштуваннях програми.", "switchToETHWallet": "Перейдіть на гаманець Ethereum і повторіть спробу", + "order_by": "Сортувати за", + "creation_date": "Дата створення", + "group_by_type": "Група за типом", "importNFTs": "Імпорт NFT", "noNFTYet": "NFT ще немає", "address": "Адреса", @@ -743,11 +745,23 @@ "unavailable_balance_description": "Недоступний баланс: ця сума включає кошти, заблоковані в незавершених транзакціях, і ті, які ви активно заморозили в налаштуваннях контролю монет. Заблоковані баланси стануть доступними після завершення відповідних транзакцій, тоді як заморожені баланси залишаються недоступними для транзакцій, доки ви не вирішите їх розморозити.", "unspent_change": "Зміна", "tor_connection": "Підключення Tor", + "setup_warning_2fa_text": "Cake 2FA — друга аутентифікація для певних дій у гаманці. Це НЕ так безпечно, як холодне зберігання.\n\nЯкщо ви втратите доступ до своєї програми 2FA або ключів TOTP, ви втратите доступ до цього гаманця. Вам потрібно буде відновити свій гаманець з мнемоніки.\n\nСлужба підтримки Cake не зможе вам допомогти, якщо ви втратите доступ до 2FA або мнемонічних насадок.\nПеред використанням Cake 2FA рекомендуємо прочитати інструкцію.", + "scan_qr_on_device": "Відскануйте цей QR-код на іншому пристрої", + "how_to_use": "Як використовувати", "seed_hex_form": "Насіння гаманця (шістнадцяткова форма)", "seedtype": "Насіннєвий тип", "seedtype_legacy": "Спадщина (25 слів)", "seedtype_polyseed": "Полісей (16 слів)", "seed_language_czech": "Чеський", "seed_language_korean": "Корейський", - "seed_language_chinese_traditional": "Китайський традиційний)" -} \ No newline at end of file + "ascending": "Висхід", + "descending": "Низхідний", + "dfx_option_description": "Купуйте криптовалюту за EUR і CHF. До 990 євро без додаткового KYC. Для роздрібних і корпоративних клієнтів у Європі", + "seed_language_chinese_traditional": "Китайський (традиційний)", + "polygonscan_history": "Історія PolygonScan", + "wallet_seed_legacy": "Спадець насіння гаманця", + "default_sell_provider": "Постачальник продажу за замовчуванням", + "select_sell_provider_notice": "Виберіть вище постачальника послуг продажу. Ви можете пропустити цей екран, встановивши постачальника послуг продажу за умовчанням у налаштуваннях програми.", + "custom_drag": "На замовлення (утримуйте та перетягується)", + "switchToEVMCompatibleWallet": "Перейдіть на гаманець, сумісний з EVM, і повторіть спробу (Ethereum, Polygon)" +} diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 5b7f18ae7..e4a9f4590 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -160,7 +160,7 @@ "seed_title": "بیج", "seed_share": "بیج بانٹیں۔", "copy": "کاپی", - "seed_language_choose": "براہ کرم بیج کی زبان کا انتخاب کریں:", + "seed_language": "بیج کی زبان", "seed_choose": "بیج کی زبان کا انتخاب کریں۔", "seed_language_next": "اگلے", "seed_language_english": "انگریزی", @@ -570,7 +570,7 @@ "always": "ہمیشہ", "minutes_to_pin_code": "${minute} منٹ", "disable_exchange": "تبادلے کو غیر فعال کریں۔", - "advanced_privacy_settings": "اعلی درجے کی رازداری کی ترتیبات", + "advanced_settings": "اعلی درجے کی ترتیبات", "settings_can_be_changed_later": "ان ترتیبات کو بعد میں ایپ کی ترتیبات میں تبدیل کیا جا سکتا ہے۔", "add_custom_node": "نیا کسٹم نوڈ شامل کریں۔", "disable_fiat": "فیاٹ کو غیر فعال کریں۔", @@ -617,11 +617,10 @@ "totp_verification_success": "توثیق کامیاب!", "totp_2fa_failure": "غلط کوڈ. براہ کرم ایک مختلف کوڈ آزمائیں یا ایک نئی خفیہ کلید بنائیں۔ ایک ہم آہنگ 2FA ایپ استعمال کریں جو 8 ہندسوں کے کوڈز اور SHA512 کو سپورٹ کرتی ہو۔", "enter_totp_code": "براہ کرم TOTP کوڈ درج کریں۔", - "add_secret_code": "اس خفیہ کوڈ کو کسی اور ڈیوائس میں شامل کریں۔", + "add_secret_code": " ۔ﮟﯾﺮﮐ ﻞﻣﺎﺷ ﮟﯿﻣ ﭗﯾﺍ ﮦﺪﻨﻨﮐ ﻖﯾﺪﺼﺗ ﻮﮐ ﮈﻮﮐ ﮧﯿﻔﺧ ﺱﺍ ،ﺎﯾ", "totp_secret_code": "TOTP خفیہ کوڈ", - "important_note": "اہم نوٹ", - "setup_2fa_text": "کیک 2FA کولڈ اسٹوریج کی طرح محفوظ نہیں ہے۔ 2FA بنیادی قسم کے حملوں سے حفاظت کرتا ہے، جیسے کہ آپ کا دوست آپ کے سوتے وقت آپ کے فنگر پرنٹ فراہم کرتا ہے۔\n\n کیک 2FA کسی جدید حملہ آور کے ذریعے سمجھوتہ کرنے والے آلہ سے حفاظت نہیں کرتا ہے۔\n\n اگر آپ اپنے 2FA کوڈز تک رسائی کھو دیتے ہیں ، آپ اس بٹوے تک رسائی سے محروم ہو جائیں گے۔ آپ کو یادداشت کے بیج سے اپنے بٹوے کو بحال کرنے کی ضرورت ہوگی۔ اس لیے آپ کو اپنے یادداشت کے بیجوں کا بیک اپ لینا چاہیے! اس کے علاوہ، آپ کے یادداشت کے بیج تک رسائی رکھنے والا کوئی شخص کیک 2FA کو نظرانداز کرتے ہوئے آپ کے فنڈز چرا سکے گا۔\n\n اگر آپ اپنے یادداشت کے بیج تک رسائی کھو دیتے ہیں تو کیک کا معاون عملہ آپ کی مدد نہیں کر سکے گا، کیونکہ کیک ایک ہے غیر نگہداشت پرس.", - "setup_totp_recommended": "TOTP ترتیب دیں (تجویز کردہ)", + "setup_2fa_text": " ۔ﮯﮨ ﺎﺗﺮﮐ ﻡﺎﮐ ﮯﺋﻮﮨ ﮯﺗﺮﮐ ﻝﺎﻤﻌﺘﺳﺍ ﺎﮐ TOTP ﺮﭘ ﺭﻮﻃ ﮯﮐ ﺮﺼﻨﻋ ﯽﻘﯾﺪﺼﺗ ﮮﺮﺳﻭﺩ 2FA ﮏﯿﮐ", + "setup_totp_recommended": "TOTP ۔ﮟﯾﺮﮐ ﭖﺍ ﭧﯿﺳ", "disable_buy": "خرید ایکشن کو غیر فعال کریں۔", "disable_sell": "فروخت کی کارروائی کو غیر فعال کریں۔", "cake_2fa_preset": "کیک 2FA پیش سیٹ", @@ -723,6 +722,9 @@ "require_for_exchanges_to_external_wallets": "۔ﮯﮨ ﺕﺭﻭﺮﺿ ﯽﮐ ﮯﻟﺩﺎﺒﺗ ﮟﯿﻣ ﮮﻮﭩﺑ ﯽﻧﻭﺮﯿﺑ", "camera_permission_is_required": "۔ﮯﮨ ﺭﺎﮐﺭﺩ ﺕﺯﺎﺟﺍ ﯽﮐ ﮮﺮﻤﯿﮐ", "switchToETHWallet": "۔ﮟﯾﺮﮐ ﺶﺷﻮﮐ ﮦﺭﺎﺑﻭﺩ ﺭﻭﺍ ﮟﯾﺮﮐ ﭻﺋﻮﺳ ﺮﭘ ﭧﯿﻟﺍﻭ Ethereum ﻡﺮﮐ ﮦﺍﺮﺑ", + "order_by": "آرڈر بذریعہ", + "creation_date": "بنانے کی تاریخ", + "group_by_type": "قسم کے لحاظ سے گروپ", "importNFTs": "NFTs ۔ﮟﯾﺮﮐ ﺪﻣﺁﺭﺩ", "noNFTYet": "۔ﮟﯿﮨ ﮟﯿﮩﻧ NFTs ﯽﺋﻮﮐ ﮏﺗ ﯽﮭﺑﺍ", "address": "ﮧﺘﭘ", @@ -734,6 +736,9 @@ "unavailable_balance": "ﺲﻨﻠﯿﺑ ﺏﺎﯿﺘﺳﺩ ﺮﯿﻏ", "unavailable_balance_description": "۔ﮯﺗﺮﮐ ﮟﯿﮩﻧ ﮧﻠﺼﯿﻓ ﺎﮐ ﮯﻧﺮﮐ ﺪﻤﺠﻨﻣ ﻥﺍ ﮟﯿﮩﻧﺍ ﭖﺁ ﮧﮐ ﮏﺗ ﺐﺟ ﮟﯿﮨ ﮯﺘﮨﺭ ﯽﺋﺎﺳﺭ ﻞﺑﺎﻗﺎﻧ ﮏﺗ ﺖﻗﻭ ﺱﺍ ﮯﯿﻟ ﮯﮐ ﻦﯾﺩ ﻦﯿﻟ ﺲﻨﻠﯿﺑ ﺪﻤﺠﻨﻣ ﮧﮐ ﺐﺟ ،ﮯﮔ ﮟﯿﺋﺎﺟ ﻮﮨ ﺏﺎﯿﺘﺳﺩ ﺲﻨﻠﯿﺑ ﻞﻔﻘﻣ ﺪﻌﺑ ﮯﮐ ﮯﻧﻮﮨ ﻞﻤﮑﻣ ﻦﯾﺩ ﻦﯿﻟ ﮧﻘﻠﻌﺘﻣ ﮯﮐ ﻥﺍ ۔ﮯﮨ ﺎﮭﮐﺭ ﺮ", "unspent_change": "تبدیل کریں", + "setup_warning_2fa_text": " ۔ﯽﮔﻮﮨ ﺕﺭﻭﺮﺿ ﯽﮐ ﮯﻧﺮﮐ ﻝﺎﺤﺑ ﻮﮐ ﮮﻮﭩﺑ ﮯﻨﭘﺍ ﮯﺳ ﺞﯿﺑ ﮯﮐ ﺖﺷﺍﺩﺩﺎﯾ ﻮﮐ ﭖﺁ", + "scan_qr_on_device": " ۔ﮟﯾﺮﮐ ﻦﯿﮑﺳﺍ ﺮﭘ ﺲﺋﺍﻮﯾﮈ ﺭﻭﺍ ﯽﺴﮐ ﻮﮐ ﮈﻮﮐ QR ﺱﺍ", + "how_to_use": " ﮧﻘﯾﺮﻃ ﺎﮐ ﮯﻧﺮﮐ ﻝﺎﻤﻌﺘﺳﺍ", "seed_hex_form": "پرس بیج (ہیکس فارم)", "tor_connection": "ﻦﺸﮑﻨﮐ ﺭﻮﭨ", "seedtype": "سیڈ ٹائپ", @@ -741,5 +746,14 @@ "seedtype_polyseed": "پالیسیڈ (16 الفاظ)", "seed_language_czech": "چیک", "seed_language_korean": "کورین", - "seed_language_chinese_traditional": "چینی (روایتی)" -} \ No newline at end of file + "seed_language_chinese_traditional": "چینی (روایتی)", + "ascending": "چڑھنے", + "descending": "اترتے ہوئے", + "dfx_option_description": "EUR ﺭﻭﺍ CHF ﯽﻓﺎﺿﺍ ۔ﮟﯾﺪﯾﺮﺧ ﻮﭩﭘﺮﮐ ﮫﺗﺎﺳ ﮯﮐ KYC ﮯﯿﻟ ﮯﮐ ﻦﯿﻓﺭﺎﺻ ﭧﯾﺭﻮﭘﺭﺎﮐ ﺭﻭﺍ ﮦﺩﺭﻮﺧ ﮟ", + "polygonscan_history": "ﺦﯾﺭﺎﺗ ﯽﮐ ﻦﯿﮑﺳﺍ ﻥﻮﮔ ﯽﻟﻮﭘ", + "wallet_seed_legacy": "میراثی پرس کا بیج", + "default_sell_provider": " ﮦﺪﻨﻨﮐ ﻢﮨﺍﺮﻓ ﻞﯿﺳ ﭧﻟﺎﻔﯾﮈ", + "select_sell_provider_notice": "۔ﮟﯿﮨ ﮯﺘﮑﺳ ﮌﻮﮭﭼ ﻮﮐ ﻦﯾﺮﮑﺳﺍ ﺱﺍ ﺮﮐ ﮮﺩ ﺐﯿﺗﺮﺗ ﻮﮐ ﮦﺪﻨﻨﮐ ﻢﮨﺍﺮﻓ ﻞﯿﺳ ﭧﻟﺎﻔﯾﮈ ﮯﻨﭘﺍ ﮟﯿﻣ ﺕﺎﺒ", + "custom_drag": "کسٹم (ہولڈ اینڈ ڈریگ)", + "switchToEVMCompatibleWallet": "(Ethereum, Polygon) ﮟﯾﺮﮐ ﺶﺷﻮﮐ ﮦﺭﺎﺑﻭﺩ ﺭﻭﺍ ﮟﯾﺮﮐ ﭻﺋﻮﺳ ﺮﭘ ﭧﯿﻟﺍﻭ ﮯﻟﺍﻭ ﮯﻨﮭﮐﺭ ﺖﻘﺑﺎﻄﻣ " +} diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index 6821aa7f9..964307e6b 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -160,7 +160,7 @@ "seed_title": "Hóró", "seed_share": "Pín hóró", "copy": "Ṣẹ̀dà", - "seed_language_choose": "Ẹ jọ̀wọ́ yan èdè hóró:", + "seed_language": "Ewu ọmọ", "seed_choose": "Yan èdè hóró", "seed_language_next": "Tẹ̀síwájú", "seed_language_english": "Èdè Gẹ̀ẹ́sì", @@ -567,7 +567,7 @@ "always": "Ní gbogbo àwọn ìgbà", "minutes_to_pin_code": "${minute} ìṣẹ́jú", "disable_exchange": "Pa ilé pàṣípààrọ̀", - "advanced_privacy_settings": "Àwọn ààtò àdáni títóbi", + "advanced_settings": "Awọn eto ilọsiwaju", "settings_can_be_changed_later": "Ẹ lè pààrọ̀ àwọn ààtò yìí nínú ààtò áàpù t’ó bá yá", "add_custom_node": "Fikún apẹka títun t'ẹ́ pààrọ̀", "disable_fiat": "Pa owó tí ìjọba pàṣẹ wa lò", @@ -619,11 +619,10 @@ "totp_verification_success": "Ìbẹrẹ dọkita!", "totp_2fa_failure": "Koodu ti o daju ko ri. Jọwọ jẹ koodu miiran tabi ṣiṣẹ iwe kiakia. Lo fun 2FA eto ti o ba ṣe ni jẹ 2FA ti o gba idaniloju 8-digits ati SHA512.", "enter_totp_code": "Jọwọ pọ koodu TOTP.", - "add_secret_code": "Fọya koodu iye yii si eto miiran", + "add_secret_code": "Tabi, ṣafikun koodu aṣiri yii si ohun elo onijeri kan", "totp_secret_code": "Koodu iye TOTP", - "important_note": "Iwọ nikan nipasẹ iwe iṣẹ kan", - "setup_2fa_text": "Cake 2FA kii ṣe nipasẹ aisan tabi ni akoso aisan. 2FA ti ṣe pada ninu awọn iṣẹ pataki, bi atilẹyin ti o fun iṣẹ rẹ ti o ti jẹ saanu.\n\n Cake 2FA kii ṣe pada ninu atilẹyin ti o ti ba alabara kan ti o sise gidi gan.\n\n Ti o ba pọ akosile rẹ 2FA, O YOO RI ATOJU SI IWE IWE NA. O yoo nilo lati yan pẹlu iwe iwe ni o ba ṣe iṣẹ rẹ. O ni aṣẹ iru ki o gba asise akojọ iwe iwe rẹ! Nitori a ko ni aṣẹ pẹlu ohun ti o ba ṣe iṣẹ rẹ lati yan pẹlu akojọ iwe iwe rẹ, nitori Cake ni iwe iwe ti ko se iṣẹ itumọ.", - "setup_totp_recommended": "Sọ TOTP (Kẹṣọdọ)", + "setup_2fa_text": "Akara oyinbo 2FA ṣiṣẹ ni lilo TOTP bi ifosiwewe ijẹrisi keji.\n\nAkara oyinbo 2FA's TOTP nilo SHA-512 ati atilẹyin oni-nọmba 8; eyi pese aabo ti o pọ sii. Alaye diẹ sii ati awọn ohun elo atilẹyin ni a le rii ninu itọsọna naa.", + "setup_totp_recommended": "Ṣeto TOTP", "disable_buy": "Ko iṣọrọ ọja", "disable_sell": "Ko iṣọrọ iṣọrọ", "cake_2fa_preset": "Cake 2FA Tito", @@ -725,6 +724,9 @@ "require_for_exchanges_to_external_wallets": "Beere fun awọn paṣipaarọ si awọn apamọwọ ita", "camera_permission_is_required": "A nilo igbanilaaye kamẹra.\nJọwọ jeki o lati app eto.", "switchToETHWallet": "Jọwọ yipada si apamọwọ Ethereum ki o tun gbiyanju lẹẹkansi", + "order_by": "Bere fun nipasẹ", + "creation_date": "Ọjọ ẹda", + "group_by_type": "Ẹgbẹ nipasẹ Iru", "importNFTs": "Gbe awọn NFT wọle", "noNFTYet": "Ko si awọn NFT sibẹsibẹ", "address": "Adirẹsi", @@ -737,11 +739,23 @@ "unavailable_balance_description": "Iwontunws.funfun ti ko si: Lapapọ yii pẹlu awọn owo ti o wa ni titiipa ni awọn iṣowo isunmọ ati awọn ti o ti didi ni itara ninu awọn eto iṣakoso owo rẹ. Awọn iwọntunwọnsi titiipa yoo wa ni kete ti awọn iṣowo oniwun wọn ba ti pari, lakoko ti awọn iwọntunwọnsi tio tutunini ko ni iraye si fun awọn iṣowo titi iwọ o fi pinnu lati mu wọn kuro.", "unspent_change": "Yipada", "tor_connection": "Tor asopọ", + "setup_warning_2fa_text": "Iwọ yoo nilo lati mu pada apamọwọ rẹ lati inu irugbin mnemonic.\n\nAtilẹyin akara oyinbo kii yoo ni anfani lati ṣe iranlọwọ fun ọ ti o ba padanu iraye si 2FA tabi awọn irugbin mnemonic rẹ.\nAkara oyinbo 2FA jẹ ijẹrisi keji fun awọn iṣe kan ninu apamọwọ. Ṣaaju lilo akara oyinbo 2FA, a ṣeduro kika nipasẹ itọsọna naa.Ko ṣe aabo bi ibi ipamọ tutu.\n\nTi o ba padanu iraye si ohun elo 2FA tabi awọn bọtini TOTP, iwọ YOO padanu iraye si apamọwọ yii. ", + "scan_qr_on_device": "Ṣe ayẹwo koodu QR yii lori ẹrọ miiran", + "how_to_use": "Bawo ni lati lo", "seed_hex_form": "Irú Opamọwọ apamọwọ (HOX)", "seedtype": "Irugbin-seetypu", "seedtype_legacy": "Legacy (awọn ọrọ 25)", "seedtype_polyseed": "Polyseed (awọn ọrọ 16)", "seed_language_czech": "Czech", "seed_language_korean": "Ara ẹni", - "seed_language_chinese_traditional": "Kannada (ibile)" -} \ No newline at end of file + "seed_language_chinese_traditional": "Kannada (ibile)", + "ascending": "Goke", + "descending": "Sọkalẹ", + "dfx_option_description": "Ra crypto pẹlu EUR & CHF. Titi di 990 € laisi afikun KYC. Fun soobu ati awọn onibara ile-iṣẹ ni Yuroopu", + "polygonscan_history": "PolygonScan itan", + "wallet_seed_legacy": "Irugbin akole", + "default_sell_provider": "Aiyipada Olupese Tita", + "select_sell_provider_notice": "Yan olupese ti o ta loke. O le foju iboju yii nipa tito olupese iṣẹ tita aiyipada rẹ ni awọn eto app.", + "custom_drag": "Aṣa (mu ati fa)", + "switchToEVMCompatibleWallet": "Jọwọ yipada si apamọwọ ibaramu EVM ki o tun gbiyanju lẹẹkansi (Ethereum, Polygon)" +} diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index b66d0e65e..43e8c6ce1 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -160,7 +160,7 @@ "seed_title": "种子", "seed_share": "分享种子", "copy": "复制", - "seed_language_choose": "请选择种子语言:", + "seed_language": "种子语言", "seed_choose": "选择种子语言", "seed_language_next": "下一个", "seed_language_english": "英文", @@ -570,7 +570,7 @@ "always": "总是", "minutes_to_pin_code": "${minute} 分钟", "disable_exchange": "禁用交换", - "advanced_privacy_settings": "高级隐私设置", + "advanced_settings": "高级设置", "settings_can_be_changed_later": "稍后可以在应用设置中更改这些设置", "add_custom_node": "添加新的自定义节点", "disable_fiat": "禁用法令", @@ -624,11 +624,10 @@ "totp_verification_success": "验证成功!", "totp_2fa_failure": "不正确的代码。 请尝试不同的代码或生成新的密钥。 使用支持 8 位代码和 SHA512 的兼容 2FA 应用程序。", "enter_totp_code": "请输入 TOTP 代码。", - "add_secret_code": "将此密码添加到另一台设备", + "add_secret_code": "或者,将此密码添加到身份验证器应用程序中", "totp_secret_code": "TOTP密码", - "important_note": "重要的提示", - "setup_2fa_text": "Cake 2FA 不如冷藏安全。 2FA 可防止基本类型的攻击,例如您的朋友在您睡觉时提供您的指纹。\n\n Cake 2FA 无法防止老练的攻击者破坏设备。\n\n 如果您无法访问您的 2FA 代码, 您将无法访问此钱包。您将需要从助记词种子恢复您的钱包。因此,您必须备份您的助记词种子!此外,有权访问您的助记种子的人将能够绕过 Cake 2FA 窃取您的资金。\n\n 如果您无法访问您的助记种子,Cake 支持人员将无法帮助您,因为 Cake 是一个非托管钱包。", - "setup_totp_recommended": "设置 TOTP(推荐)", + "setup_2fa_text": "Cake 2FA 使用 TOTP 作为第二个身份验证因素。\n\nCake 2FA 的 TOTP 需要 SHA-512 和 8 位数字支持;这提供了更高的安全性。更多信息和支持的应用程序可以在指南中找到。", + "setup_totp_recommended": "设置 TOTP", "disable_buy": "禁用购买操作", "disable_sell": "禁用卖出操作", "cake_2fa_preset": "蛋糕 2FA 预设", @@ -730,6 +729,9 @@ "require_for_exchanges_to_external_wallets": "需要兑换到外部钱包", "camera_permission_is_required": "需要相机许可。\n请从应用程序设置中启用它。", "switchToETHWallet": "请切换到以太坊钱包并重试", + "order_by": "订购", + "creation_date": "创建日期", + "group_by_type": "按类型组", "importNFTs": "导入 NFT", "noNFTYet": "还没有 NFT", "address": "地址", @@ -742,11 +744,23 @@ "unavailable_balance_description": "不可用余额:此总额包括锁定在待处理交易中的资金以及您在硬币控制设置中主动冻结的资金。一旦各自的交易完成,锁定的余额将变得可用,而冻结的余额在您决定解冻之前仍然无法进行交易。", "unspent_change": "改变", "tor_connection": "Tor连接", + "setup_warning_2fa_text": "Cake 2FA 是对钱包中某些操作的二次验证。它不如冷藏那么安全。\n\n如果您无法访问 2FA 应用程序或 TOTP 密钥,您将无法访问此钱包。您需要从助记词种子中恢复您的钱包。\n\n如果您无法访问 2FA 或助记词种子,Cake 支持将无法为您提供帮助。\n在使用 Cake 2FA 之前,我们建议您阅读该指南。", + "scan_qr_on_device": "在另一台设备上扫描此二维码", + "how_to_use": "如何使用", "seed_hex_form": "钱包种子(十六进制形式)", "seedtype": "籽粒", "seedtype_legacy": "遗产(25个单词)", "seedtype_polyseed": "多种物品(16个单词)", "seed_language_czech": "捷克", "seed_language_korean": "韩国人", - "seed_language_chinese_traditional": "中国传统的)" -} \ No newline at end of file + "seed_language_chinese_traditional": "中国传统的)", + "ascending": "上升", + "descending": "下降", + "dfx_option_description": "用欧元和瑞士法郎购买加密货币。高达 990 欧元,无需额外 KYC。对于欧洲的零售和企业客户", + "polygonscan_history": "多边形扫描历史", + "wallet_seed_legacy": "旧的钱包种子", + "default_sell_provider": "默认销售提供商", + "select_sell_provider_notice": "选择上面的销售提供商。您可以通过在应用程序设置中设置默认销售提供商来跳过此屏幕。", + "custom_drag": "定制(保持和拖动)", + "switchToEVMCompatibleWallet": "请切换到 EVM 兼容钱包并重试(以太坊、Polygon)" +} diff --git a/scripts/android/app_env.sh b/scripts/android/app_env.sh index 5dd9f2edf..5feae289e 100644 --- a/scripts/android/app_env.sh +++ b/scripts/android/app_env.sh @@ -15,15 +15,15 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN) APP_ANDROID_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.7.5" -MONERO_COM_BUILD_NUMBER=67 +MONERO_COM_VERSION="1.9.0" +MONERO_COM_BUILD_NUMBER=71 MONERO_COM_BUNDLE_ID="com.monero.app" MONERO_COM_PACKAGE="com.monero.app" MONERO_COM_SCHEME="monero.com" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.10.5" -CAKEWALLET_BUILD_NUMBER=181 +CAKEWALLET_VERSION="4.12.1" +CAKEWALLET_BUILD_NUMBER=188 CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet" CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet" CAKEWALLET_SCHEME="cakewallet" diff --git a/scripts/android/pubspec_gen.sh b/scripts/android/pubspec_gen.sh index dd9852072..4b89c4afa 100755 --- a/scripts/android/pubspec_gen.sh +++ b/scripts/android/pubspec_gen.sh @@ -10,7 +10,7 @@ case $APP_ANDROID_TYPE in CONFIG_ARGS="--monero" ;; $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --haven --ethereum --nano --bitcoinCash" + CONFIG_ARGS="--monero --bitcoin --haven --ethereum --polygon --nano --bitcoinCash" ;; $HAVEN) CONFIG_ARGS="--haven" diff --git a/scripts/ios/app_config.sh b/scripts/ios/app_config.sh index 8d999f594..81752a015 100755 --- a/scripts/ios/app_config.sh +++ b/scripts/ios/app_config.sh @@ -28,7 +28,7 @@ case $APP_IOS_TYPE in CONFIG_ARGS="--monero" ;; $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --haven --ethereum --nano --bitcoinCash" + CONFIG_ARGS="--monero --bitcoin --haven --ethereum --polygon --nano --bitcoinCash" ;; $HAVEN) diff --git a/scripts/ios/app_env.sh b/scripts/ios/app_env.sh index 810b2b78c..ee59e837c 100644 --- a/scripts/ios/app_env.sh +++ b/scripts/ios/app_env.sh @@ -13,13 +13,13 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN) APP_IOS_TYPE=$1 MONERO_COM_NAME="Monero.com" -MONERO_COM_VERSION="1.7.5" -MONERO_COM_BUILD_NUMBER=65 +MONERO_COM_VERSION="1.9.0" +MONERO_COM_BUILD_NUMBER=69 MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="4.10.5" -CAKEWALLET_BUILD_NUMBER=199 +CAKEWALLET_VERSION="4.12.1" +CAKEWALLET_BUILD_NUMBER=206 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" HAVEN_NAME="Haven" diff --git a/scripts/macos/app_config.sh b/scripts/macos/app_config.sh index 48b680330..2e6a945bf 100755 --- a/scripts/macos/app_config.sh +++ b/scripts/macos/app_config.sh @@ -23,7 +23,7 @@ CONFIG_ARGS="" case $APP_MACOS_TYPE in $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --ethereum --nano --bitcoinCash";; #--haven + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash";; #--haven esac cp -rf pubspec_description.yaml pubspec.yaml diff --git a/scripts/macos/app_env.sh b/scripts/macos/app_env.sh index 665da3425..a37d67a82 100755 --- a/scripts/macos/app_env.sh +++ b/scripts/macos/app_env.sh @@ -15,8 +15,8 @@ if [ -n "$1" ]; then fi CAKEWALLET_NAME="Cake Wallet" -CAKEWALLET_VERSION="1.3.5" -CAKEWALLET_BUILD_NUMBER=42 +CAKEWALLET_VERSION="1.5.1" +CAKEWALLET_BUILD_NUMBER=48 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then diff --git a/tool/configure.dart b/tool/configure.dart index 3e4c6ba22..43258a235 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -6,6 +6,7 @@ const havenOutputPath = 'lib/haven/haven.dart'; const ethereumOutputPath = 'lib/ethereum/ethereum.dart'; const bitcoinCashOutputPath = 'lib/bitcoin_cash/bitcoin_cash.dart'; const nanoOutputPath = 'lib/nano/nano.dart'; +const polygonOutputPath = 'lib/polygon/polygon.dart'; const walletTypesPath = 'lib/wallet_types.g.dart'; const pubspecDefaultPath = 'pubspec_default.yaml'; const pubspecOutputPath = 'pubspec.yaml'; @@ -19,6 +20,7 @@ Future main(List args) async { final hasBitcoinCash = args.contains('${prefix}bitcoinCash'); final hasNano = args.contains('${prefix}nano'); final hasBanano = args.contains('${prefix}banano'); + final hasPolygon = args.contains('${prefix}polygon'); await generateBitcoin(hasBitcoin); await generateMonero(hasMonero); @@ -26,6 +28,7 @@ Future main(List args) async { await generateEthereum(hasEthereum); await generateBitcoinCash(hasBitcoinCash); await generateNano(hasNano); + await generatePolygon(hasPolygon); // await generateBanano(hasEthereum); await generatePubspec( @@ -36,6 +39,7 @@ Future main(List args) async { hasNano: hasNano, hasBanano: hasBanano, hasBitcoinCash: hasBitcoinCash, + hasPolygon: hasPolygon, ); await generateWalletTypes( hasMonero: hasMonero, @@ -45,6 +49,7 @@ Future main(List args) async { hasNano: hasNano, hasBanano: hasBanano, hasBitcoinCash: hasBitcoinCash, + hasPolygon: hasPolygon, ); } @@ -590,6 +595,93 @@ abstract class Ethereum { await outputFile.writeAsString(output); } +Future generatePolygon(bool hasImplementation) async { + final outputFile = File(polygonOutputPath); + const polygonCommonHeaders = """ +import 'package:cake_wallet/view_model/send/output.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/erc20_token.dart'; +import 'package:cw_core/output_info.dart'; +import 'package:cw_core/transaction_info.dart'; +import 'package:cw_core/transaction_priority.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_credentials.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_service.dart'; +import 'package:eth_sig_util/util/utils.dart'; +import 'package:hive/hive.dart'; +import 'package:web3dart/web3dart.dart'; +"""; + const polygonCWHeaders = """ +import 'package:cw_polygon/polygon_formatter.dart'; +import 'package:cw_polygon/polygon_transaction_credentials.dart'; +import 'package:cw_polygon/polygon_transaction_info.dart'; +import 'package:cw_polygon/polygon_wallet.dart'; +import 'package:cw_polygon/polygon_wallet_creation_credentials.dart'; +import 'package:cw_polygon/polygon_wallet_service.dart'; +import 'package:cw_polygon/polygon_transaction_priority.dart'; +import 'package:cw_ethereum/ethereum_mnemonics.dart'; +"""; + const polygonCwPart = "part 'cw_polygon.dart';"; + const polygonContent = """ +abstract class Polygon { + List getPolygonWordList(String language); + WalletService createPolygonWalletService(Box walletInfoSource); + WalletCredentials createPolygonNewWalletCredentials({required String name, WalletInfo? walletInfo}); + WalletCredentials createPolygonRestoreWalletFromSeedCredentials({required String name, required String mnemonic, required String password}); + WalletCredentials createPolygonRestoreWalletFromPrivateKey({required String name, required String privateKey, required String password}); + String getAddress(WalletBase wallet); + String getPrivateKey(WalletBase wallet); + String getPublicKey(WalletBase wallet); + TransactionPriority getDefaultTransactionPriority(); + TransactionPriority getPolygonTransactionPrioritySlow(); + List getTransactionPriorities(); + TransactionPriority deserializePolygonTransactionPriority(int raw); + + Object createPolygonTransactionCredentials( + List outputs, { + required TransactionPriority priority, + required CryptoCurrency currency, + int? feeRate, + }); + + Object createPolygonTransactionCredentialsRaw( + List outputs, { + TransactionPriority? priority, + required CryptoCurrency currency, + required int feeRate, + }); + + int formatterPolygonParseAmount(String amount); + double formatterPolygonAmountToDouble({TransactionInfo? transaction, BigInt? amount, int exponent = 18}); + List getERC20Currencies(WalletBase wallet); + Future addErc20Token(WalletBase wallet, Erc20Token token); + Future deleteErc20Token(WalletBase wallet, Erc20Token token); + Future getErc20Token(WalletBase wallet, String contractAddress); + + CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction); + void updatePolygonScanUsageState(WalletBase wallet, bool isEnabled); + Web3Client? getWeb3Client(WalletBase wallet); +} + """; + + const polygonEmptyDefinition = 'Polygon? polygon;\n'; + const polygonCWDefinition = 'Polygon? polygon = CWPolygon();\n'; + + final output = '$polygonCommonHeaders\n' + + (hasImplementation ? '$polygonCWHeaders\n' : '\n') + + (hasImplementation ? '$polygonCwPart\n\n' : '\n') + + (hasImplementation ? polygonCWDefinition : polygonEmptyDefinition) + + '\n' + + polygonContent; + + if (outputFile.existsSync()) { + await outputFile.delete(); + } + + await outputFile.writeAsString(output); +} + Future generateBitcoinCash(bool hasImplementation) async { final outputFile = File(bitcoinCashOutputPath); const bitcoinCashCommonHeaders = """ @@ -801,7 +893,8 @@ Future generatePubspec( required bool hasEthereum, required bool hasNano, required bool hasBanano, - required bool hasBitcoinCash}) async { + required bool hasBitcoinCash, + required bool hasPolygon}) async { const cwCore = """ cw_core: path: ./cw_core @@ -838,6 +931,10 @@ Future generatePubspec( cw_banano: path: ./cw_banano """; + const cwPolygon = """ + cw_polygon: + path: ./cw_polygon + """; final inputFile = File(pubspecOutputPath); final inputText = await inputFile.readAsString(); final inputLines = inputText.split('\n'); @@ -868,6 +965,10 @@ Future generatePubspec( output += '\n$cwBitcoinCash'; } + if (hasPolygon) { + output += '\n$cwPolygon'; + } + if (hasHaven && !hasMonero) { output += '\n$cwSharedExternal\n$cwHaven'; } else if (hasHaven) { @@ -893,7 +994,8 @@ Future generateWalletTypes( required bool hasEthereum, required bool hasNano, required bool hasBanano, - required bool hasBitcoinCash}) async { + required bool hasBitcoinCash, + required bool hasPolygon}) async { final walletTypesFile = File(walletTypesPath); if (walletTypesFile.existsSync()) { @@ -924,6 +1026,10 @@ Future generateWalletTypes( outputContent += '\tWalletType.bitcoinCash,\n'; } + if (hasPolygon) { + outputContent += '\tWalletType.polygon,\n'; + } + if (hasNano) { outputContent += '\tWalletType.nano,\n'; } diff --git a/tool/utils/secret_key.dart b/tool/utils/secret_key.dart index 163b80135..e6f625426 100644 --- a/tool/utils/secret_key.dart +++ b/tool/utils/secret_key.dart @@ -41,6 +41,7 @@ class SecretKey { static final ethereumSecrets = [ SecretKey('etherScanApiKey', () => ''), + SecretKey('polygonScanApiKey', () => ''), ]; final String name;