mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-22 11:39:22 +00:00
Merge remote-tracking branch 'origin/main' into electrum-sp-refactors
This commit is contained in:
commit
6cbb4c60d6
142 changed files with 3179 additions and 671 deletions
298
.github/workflows/automated_integration_test.yml
vendored
Normal file
298
.github/workflows/automated_integration_test.yml
vendored
Normal file
|
@ -0,0 +1,298 @@
|
|||
#name: Automated Integration Tests
|
||||
#
|
||||
#on:
|
||||
# pull_request:
|
||||
# branches: [main, CW-659-Transaction-History-Automated-Tests]
|
||||
# workflow_dispatch:
|
||||
# inputs:
|
||||
# branch:
|
||||
# description: "Branch name to build"
|
||||
# required: true
|
||||
# default: "main"
|
||||
#
|
||||
#jobs:
|
||||
# Automated_integration_test:
|
||||
# runs-on: ubuntu-20.04
|
||||
# strategy:
|
||||
# fail-fast: false
|
||||
# matrix:
|
||||
# api-level: [29]
|
||||
# # arch: [x86, x86_64]
|
||||
# env:
|
||||
# STORE_PASS: test@cake_wallet
|
||||
# KEY_PASS: test@cake_wallet
|
||||
# PR_NUMBER: ${{ github.event.number }}
|
||||
#
|
||||
# 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 Disk Space (Ubuntu)
|
||||
# uses: insightsengineering/disk-space-reclaimer@v1
|
||||
# with:
|
||||
# tools-cache: true
|
||||
# android: false
|
||||
# dotnet: true
|
||||
# haskell: true
|
||||
# large-packages: true
|
||||
# swap-storage: true
|
||||
# docker-images: true
|
||||
#
|
||||
# - uses: actions/checkout@v2
|
||||
# - uses: actions/setup-java@v2
|
||||
# with:
|
||||
# distribution: "temurin"
|
||||
# java-version: "17"
|
||||
# - name: Configure placeholder git details
|
||||
# run: |
|
||||
# git config --global user.email "CI@cakewallet.com"
|
||||
# git config --global user.name "Cake Github Actions"
|
||||
# - name: Flutter action
|
||||
# uses: subosito/flutter-action@v1
|
||||
# with:
|
||||
# flutter-version: "3.24.0"
|
||||
# channel: stable
|
||||
#
|
||||
# - name: Install package dependencies
|
||||
# run: |
|
||||
# sudo apt update
|
||||
# sudo apt-get install -y curl unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake clang
|
||||
#
|
||||
# - name: Execute Build and Setup Commands
|
||||
# run: |
|
||||
# sudo mkdir -p /opt/android
|
||||
# sudo chown $USER /opt/android
|
||||
# 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 ${{ env.BRANCH_NAME }}
|
||||
# cd cake_wallet/scripts/android/
|
||||
# ./install_ndk.sh
|
||||
# source ./app_env.sh cakewallet
|
||||
# chmod +x pubspec_gen.sh
|
||||
# ./app_config.sh
|
||||
#
|
||||
# - name: Cache Externals
|
||||
# id: cache-externals
|
||||
# uses: actions/cache@v3
|
||||
# with:
|
||||
# path: |
|
||||
# /opt/android/cake_wallet/cw_haven/android/.cxx
|
||||
# /opt/android/cake_wallet/scripts/monero_c/release
|
||||
# key: ${{ hashFiles('**/prepare_moneroc.sh' ,'**/build_monero_all.sh' ,'**/cache_dependencies.yml') }}
|
||||
#
|
||||
# - if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }}
|
||||
# name: Generate Externals
|
||||
# run: |
|
||||
# cd /opt/android/cake_wallet/scripts/android/
|
||||
# source ./app_env.sh cakewallet
|
||||
# ./build_monero_all.sh
|
||||
#
|
||||
# - name: Install Flutter dependencies
|
||||
# run: |
|
||||
# cd /opt/android/cake_wallet
|
||||
# flutter pub get
|
||||
#
|
||||
#
|
||||
# - name: Install go and gomobile
|
||||
# run: |
|
||||
# # install go > 1.23:
|
||||
# wget https://go.dev/dl/go1.23.1.linux-amd64.tar.gz
|
||||
# sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.23.1.linux-amd64.tar.gz
|
||||
# export PATH=$PATH:/usr/local/go/bin
|
||||
# export PATH=$PATH:~/go/bin
|
||||
# go install golang.org/x/mobile/cmd/gomobile@latest
|
||||
# gomobile init
|
||||
#
|
||||
# - name: Build mwebd
|
||||
# run: |
|
||||
# # paths are reset after each step, so we need to set them again:
|
||||
# export PATH=$PATH:/usr/local/go/bin
|
||||
# export PATH=$PATH:~/go/bin
|
||||
# cd /opt/android/cake_wallet/scripts/android/
|
||||
# ./build_mwebd.sh --dont-install
|
||||
#
|
||||
# - name: Generate KeyStore
|
||||
# run: |
|
||||
# cd /opt/android/cake_wallet/android/app
|
||||
# keytool -genkey -v -keystore key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias testKey -noprompt -dname "CN=CakeWallet, OU=CakeWallet, O=CakeWallet, L=Florida, S=America, C=USA" -storepass $STORE_PASS -keypass $KEY_PASS
|
||||
#
|
||||
# - name: Generate key properties
|
||||
# run: |
|
||||
# cd /opt/android/cake_wallet
|
||||
# flutter packages pub run tool/generate_android_key_properties.dart keyAlias=testKey storeFile=key.jks storePassword=$STORE_PASS keyPassword=$KEY_PASS
|
||||
#
|
||||
# - name: Generate localization
|
||||
# run: |
|
||||
# cd /opt/android/cake_wallet
|
||||
# flutter packages pub run tool/generate_localization.dart
|
||||
#
|
||||
# - name: Build generated code
|
||||
# run: |
|
||||
# cd /opt/android/cake_wallet
|
||||
# ./model_generator.sh
|
||||
#
|
||||
# - name: Add secrets
|
||||
# run: |
|
||||
# cd /opt/android/cake_wallet
|
||||
# touch lib/.secrets.g.dart
|
||||
# touch cw_evm/lib/.secrets.g.dart
|
||||
# touch cw_solana/lib/.secrets.g.dart
|
||||
# touch cw_core/lib/.secrets.g.dart
|
||||
# touch cw_nano/lib/.secrets.g.dart
|
||||
# touch cw_tron/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
|
||||
# echo "const walletSalt = '${{ secrets.WALLET_SALT }}';" >> lib/.secrets.g.dart
|
||||
# echo "const shortKey = '${{ secrets.SHORT_KEY }}';" >> lib/.secrets.g.dart
|
||||
# echo "const backupSalt = '${{ secrets.BACKUP_SALT }}';" >> lib/.secrets.g.dart
|
||||
# echo "const backupKeychainSalt = '${{ secrets.BACKUP_KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart
|
||||
# echo "const changeNowApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> lib/.secrets.g.dart
|
||||
# echo "const changeNowApiKeyDesktop = '${{ secrets.CHANGE_NOW_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart
|
||||
# echo "const wyreSecretKey = '${{ secrets.WYRE_SECRET_KEY }}';" >> lib/.secrets.g.dart
|
||||
# echo "const wyreApiKey = '${{ secrets.WYRE_API_KEY }}';" >> lib/.secrets.g.dart
|
||||
# echo "const wyreAccountId = '${{ secrets.WYRE_ACCOUNT_ID }}';" >> lib/.secrets.g.dart
|
||||
# echo "const moonPayApiKey = '${{ secrets.MOON_PAY_API_KEY }}';" >> lib/.secrets.g.dart
|
||||
# echo "const moonPaySecretKey = '${{ secrets.MOON_PAY_SECRET_KEY }}';" >> lib/.secrets.g.dart
|
||||
# echo "const sideShiftAffiliateId = '${{ secrets.SIDE_SHIFT_AFFILIATE_ID }}';" >> lib/.secrets.g.dart
|
||||
# echo "const simpleSwapApiKey = '${{ secrets.SIMPLE_SWAP_API_KEY }}';" >> lib/.secrets.g.dart
|
||||
# echo "const simpleSwapApiKeyDesktop = '${{ secrets.SIMPLE_SWAP_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart
|
||||
# echo "const onramperApiKey = '${{ secrets.ONRAMPER_API_KEY }}';" >> lib/.secrets.g.dart
|
||||
# echo "const anypayToken = '${{ secrets.ANY_PAY_TOKEN }}';" >> lib/.secrets.g.dart
|
||||
# echo "const ioniaClientId = '${{ secrets.IONIA_CLIENT_ID }}';" >> lib/.secrets.g.dart
|
||||
# echo "const twitterBearerToken = '${{ secrets.TWITTER_BEARER_TOKEN }}';" >> lib/.secrets.g.dart
|
||||
# echo "const trocadorApiKey = '${{ secrets.TROCADOR_API_KEY }}';" >> lib/.secrets.g.dart
|
||||
# echo "const trocadorExchangeMarkup = '${{ secrets.TROCADOR_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart
|
||||
# echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart
|
||||
# echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart
|
||||
# echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart
|
||||
# echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> lib/.secrets.g.dart
|
||||
# echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> lib/.secrets.g.dart
|
||||
# echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> lib/.secrets.g.dart
|
||||
# echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
|
||||
# echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
|
||||
# echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart
|
||||
# echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart
|
||||
# echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart
|
||||
# echo "const exchangeHelperApiKey = '${{ 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_evm/lib/.secrets.g.dart
|
||||
# echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart
|
||||
# echo "const testCakePayApiKey = '${{ secrets.TEST_CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart
|
||||
# echo "const cakePayApiKey = '${{ secrets.CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart
|
||||
# echo "const authorization = '${{ secrets.CAKE_PAY_AUTHORIZATION }}';" >> lib/.secrets.g.dart
|
||||
# echo "const CSRFToken = '${{ secrets.CSRF_TOKEN }}';" >> lib/.secrets.g.dart
|
||||
# echo "const quantexExchangeMarkup = '${{ secrets.QUANTEX_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart
|
||||
# echo "const nano2ApiKey = '${{ secrets.NANO2_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart
|
||||
# echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart
|
||||
# echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
|
||||
# echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
|
||||
# echo "const meldTestApiKey = '${{ secrets.MELD_TEST_API_KEY }}';" >> lib/.secrets.g.dart
|
||||
# echo "const meldTestPublicKey = '${{ secrets.MELD_TEST_PUBLIC_KEY}}';" >> lib/.secrets.g.dart
|
||||
# echo "const letsExchangeBearerToken = '${{ secrets.LETS_EXCHANGE_TOKEN }}';" >> lib/.secrets.g.dart
|
||||
# echo "const letsExchangeAffiliateId = '${{ secrets.LETS_EXCHANGE_AFFILIATE_ID }}';" >> lib/.secrets.g.dart
|
||||
# echo "const stealthExBearerToken = '${{ secrets.STEALTH_EX_BEARER_TOKEN }}';" >> lib/.secrets.g.dart
|
||||
# echo "const stealthExAdditionalFeePercent = '${{ secrets.STEALTH_EX_ADDITIONAL_FEE_PERCENT }}';" >> lib/.secrets.g.dart
|
||||
# echo "const moneroTestWalletSeeds ='${{ secrets.MONERO_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
|
||||
# echo "const moneroLegacyTestWalletSeeds = '${{ secrets.MONERO_LEGACY_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
|
||||
# echo "const bitcoinTestWalletSeeds = '${{ secrets.BITCOIN_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
|
||||
# echo "const ethereumTestWalletSeeds = '${{ secrets.ETHEREUM_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
|
||||
# echo "const litecoinTestWalletSeeds = '${{ secrets.LITECOIN_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
|
||||
# echo "const bitcoinCashTestWalletSeeds = '${{ secrets.BITCOIN_CASH_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
|
||||
# echo "const polygonTestWalletSeeds = '${{ secrets.POLYGON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
|
||||
# echo "const solanaTestWalletSeeds = '${{ secrets.SOLANA_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
|
||||
# echo "const tronTestWalletSeeds = '${{ secrets.TRON_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
|
||||
# echo "const nanoTestWalletSeeds = '${{ secrets.NANO_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
|
||||
# echo "const wowneroTestWalletSeeds = '${{ secrets.WOWNERO_TEST_WALLET_SEEDS }}';" >> lib/.secrets.g.dart
|
||||
# echo "const moneroTestWalletReceiveAddress = '${{ secrets.MONERO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
|
||||
# echo "const bitcoinTestWalletReceiveAddress = '${{ secrets.BITCOIN_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
|
||||
# echo "const ethereumTestWalletReceiveAddress = '${{ secrets.ETHEREUM_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
|
||||
# echo "const litecoinTestWalletReceiveAddress = '${{ secrets.LITECOIN_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
|
||||
# echo "const bitcoinCashTestWalletReceiveAddress = '${{ secrets.BITCOIN_CASH_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
|
||||
# echo "const polygonTestWalletReceiveAddress = '${{ secrets.POLYGON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
|
||||
# echo "const solanaTestWalletReceiveAddress = '${{ secrets.SOLANA_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
|
||||
# echo "const tronTestWalletReceiveAddress = '${{ secrets.TRON_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
|
||||
# echo "const nanoTestWalletReceiveAddress = '${{ secrets.NANO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
|
||||
# echo "const wowneroTestWalletReceiveAddress = '${{ secrets.WOWNERO_TEST_WALLET_RECEIVE_ADDRESS }}';" >> lib/.secrets.g.dart
|
||||
# echo "const moneroTestWalletBlockHeight = '${{ secrets.MONERO_TEST_WALLET_BLOCK_HEIGHT }}';" >> lib/.secrets.g.dart
|
||||
#
|
||||
# - name: Rename app
|
||||
# run: |
|
||||
# echo -e "id=com.cakewallet.test_${{ env.PR_NUMBER }}\nname=${{ env.BRANCH_NAME }}" > /opt/android/cake_wallet/android/app.properties
|
||||
#
|
||||
# - name: Build
|
||||
# run: |
|
||||
# cd /opt/android/cake_wallet
|
||||
# flutter build apk --release --split-per-abi
|
||||
#
|
||||
# # - name: Rename apk file
|
||||
# # run: |
|
||||
# # cd /opt/android/cake_wallet/build/app/outputs/flutter-apk
|
||||
# # mkdir test-apk
|
||||
# # cp app-arm64-v8a-release.apk test-apk/${{env.BRANCH_NAME}}.apk
|
||||
# # cp app-x86_64-release.apk test-apk/${{env.BRANCH_NAME}}_x86.apk
|
||||
#
|
||||
# # - name: Upload Artifact
|
||||
# # uses: kittaakos/upload-artifact-as-is@v0
|
||||
# # with:
|
||||
# # path: /opt/android/cake_wallet/build/app/outputs/flutter-apk/test-apk/
|
||||
#
|
||||
# # - name: Send Test APK
|
||||
# # continue-on-error: true
|
||||
# # uses: adrey/slack-file-upload-action@1.0.5
|
||||
# # with:
|
||||
# # token: ${{ secrets.SLACK_APP_TOKEN }}
|
||||
# # path: /opt/android/cake_wallet/build/app/outputs/flutter-apk/test-apk/${{env.BRANCH_NAME}}.apk
|
||||
# # channel: ${{ secrets.SLACK_APK_CHANNEL }}
|
||||
# # title: "${{ env.BRANCH_NAME }}.apk"
|
||||
# # filename: ${{ env.BRANCH_NAME }}.apk
|
||||
# # initial_comment: ${{ github.event.head_commit.message }}
|
||||
#
|
||||
# - name: 🦾 Enable KVM
|
||||
# run: |
|
||||
# echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
||||
# sudo udevadm control --reload-rules
|
||||
# sudo udevadm trigger --name-match=kvm
|
||||
#
|
||||
# - name: 🦾 Cache gradle
|
||||
# uses: gradle/actions/setup-gradle@v3
|
||||
#
|
||||
# - name: 🦾 Cache AVD
|
||||
# uses: actions/cache@v4
|
||||
# id: avd-cache
|
||||
# with:
|
||||
# path: |
|
||||
# ~/.android/avd/*
|
||||
# ~/.android/adb*
|
||||
# key: avd-${{ matrix.api-level }}
|
||||
#
|
||||
# - name: 🦾 Create AVD and generate snapshot for caching
|
||||
# if: steps.avd-cache.outputs.cache-hit != 'true'
|
||||
# uses: reactivecircus/android-emulator-runner@v2
|
||||
# with:
|
||||
# api-level: ${{ matrix.api-level }}
|
||||
# force-avd-creation: false
|
||||
# # arch: ${{ matrix.arch }}
|
||||
# emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
|
||||
# working-directory: /opt/android/cake_wallet
|
||||
# disable-animations: false
|
||||
# script: echo "Generated AVD snapshot for caching."
|
||||
#
|
||||
# - name: 🚀 Integration tests on Android Emulator
|
||||
# uses: reactivecircus/android-emulator-runner@v2
|
||||
# with:
|
||||
# api-level: ${{ matrix.api-level }}
|
||||
# force-avd-creation: false
|
||||
# emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
|
||||
# disable-animations: true
|
||||
# working-directory: /opt/android/cake_wallet
|
||||
# script: |
|
||||
# chmod a+rx integration_test_runner.sh
|
||||
# ./integration_test_runner.sh
|
5
.github/workflows/cache_dependencies.yml
vendored
5
.github/workflows/cache_dependencies.yml
vendored
|
@ -62,6 +62,7 @@ jobs:
|
|||
/opt/android/cake_wallet/cw_haven/android/.cxx
|
||||
/opt/android/cake_wallet/scripts/monero_c/release
|
||||
key: ${{ hashFiles('**/prepare_moneroc.sh' ,'**/build_monero_all.sh' ,'**/cache_dependencies.yml') }}
|
||||
|
||||
- if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }}
|
||||
name: Generate Externals
|
||||
run: |
|
||||
|
@ -73,8 +74,8 @@ jobs:
|
|||
id: cache-keystore
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: /opt/android/cake_wallet/android/app/key.jks
|
||||
key: $STORE_PASS
|
||||
path: /opt/android/cake_wallet/android/app
|
||||
key: keystore
|
||||
|
||||
- if: ${{ steps.cache-keystore.outputs.cache-hit != 'true' }}
|
||||
name: Generate KeyStore
|
||||
|
|
38
.github/workflows/pr_test_build_android.yml
vendored
38
.github/workflows/pr_test_build_android.yml
vendored
|
@ -61,14 +61,32 @@ jobs:
|
|||
sudo apt update
|
||||
sudo apt-get install -y curl unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake clang
|
||||
|
||||
- name: Execute Build and Setup Commands
|
||||
|
||||
- name: Clone Repo
|
||||
run: |
|
||||
sudo mkdir -p /opt/android
|
||||
sudo chown $USER /opt/android
|
||||
cd /opt/android
|
||||
git clone https://github.com/cake-tech/cake_wallet.git --branch ${{ env.BRANCH_NAME }}
|
||||
|
||||
# - name: Cache Keystore
|
||||
# id: cache-keystore
|
||||
# uses: actions/cache@v3
|
||||
# with:
|
||||
# path: /opt/android/cake_wallet/android/app
|
||||
# key: keystore
|
||||
#
|
||||
# - if: ${{ steps.cache-keystore.outputs.cache-hit != 'true' }}
|
||||
- name: Generate KeyStore
|
||||
run: |
|
||||
cd /opt/android/cake_wallet/android/app
|
||||
keytool -genkey -v -keystore key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias testKey -noprompt -dname "CN=CakeWallet, OU=CakeWallet, O=CakeWallet, L=Florida, S=America, C=USA" -storepass $STORE_PASS -keypass $KEY_PASS
|
||||
|
||||
- name: Execute Build and Setup Commands
|
||||
run: |
|
||||
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 ${{ env.BRANCH_NAME }}
|
||||
cd cake_wallet/scripts/android/
|
||||
./install_ndk.sh
|
||||
source ./app_env.sh cakewallet
|
||||
|
@ -115,19 +133,6 @@ jobs:
|
|||
cd /opt/android/cake_wallet/scripts/android/
|
||||
./build_mwebd.sh --dont-install
|
||||
|
||||
# - name: Cache Keystore
|
||||
# id: cache-keystore
|
||||
# uses: actions/cache@v3
|
||||
# with:
|
||||
# path: /opt/android/cake_wallet/android/app/key.jks
|
||||
# key: $STORE_PASS
|
||||
#
|
||||
# - if: ${{ steps.cache-keystore.outputs.cache-hit != 'true' }}
|
||||
- name: Generate KeyStore
|
||||
run: |
|
||||
cd /opt/android/cake_wallet/android/app
|
||||
keytool -genkey -v -keystore key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias testKey -noprompt -dname "CN=CakeWallet, OU=CakeWallet, O=CakeWallet, L=Florida, S=America, C=USA" -storepass $STORE_PASS -keypass $KEY_PASS
|
||||
|
||||
- name: Generate key properties
|
||||
run: |
|
||||
cd /opt/android/cake_wallet
|
||||
|
@ -183,6 +188,7 @@ jobs:
|
|||
echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> lib/.secrets.g.dart
|
||||
echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
|
||||
echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
|
||||
echo "const nowNodesApiKey = '${{ secrets.EVM_NOWNODES_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
|
||||
echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart
|
||||
echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart
|
||||
echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart
|
||||
|
@ -201,7 +207,7 @@ jobs:
|
|||
echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
|
||||
echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
|
||||
echo "const meldTestApiKey = '${{ secrets.MELD_TEST_API_KEY }}';" >> lib/.secrets.g.dart
|
||||
echo "const meldTestPublicKey = '${{ secrets.MELD_TEST_PUBLIC_KEY}}';" >> lib/.secrets.g.dar
|
||||
echo "const meldTestPublicKey = '${{ secrets.MELD_TEST_PUBLIC_KEY}}';" >> lib/.secrets.g.dart
|
||||
echo "const letsExchangeBearerToken = '${{ secrets.LETS_EXCHANGE_TOKEN }}';" >> lib/.secrets.g.dart
|
||||
echo "const letsExchangeAffiliateId = '${{ secrets.LETS_EXCHANGE_AFFILIATE_ID }}';" >> lib/.secrets.g.dart
|
||||
echo "const stealthExBearerToken = '${{ secrets.STEALTH_EX_BEARER_TOKEN }}';" >> lib/.secrets.g.dart
|
||||
|
|
1
.github/workflows/pr_test_build_linux.yml
vendored
1
.github/workflows/pr_test_build_linux.yml
vendored
|
@ -165,6 +165,7 @@ jobs:
|
|||
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_evm/lib/.secrets.g.dart
|
||||
echo "const nowNodesApiKey = '${{ secrets.EVM_NOWNODES_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart
|
||||
echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart
|
||||
echo "const testCakePayApiKey = '${{ secrets.TEST_CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart
|
||||
echo "const cakePayApiKey = '${{ secrets.CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart
|
||||
|
|
|
@ -99,4 +99,4 @@ configurations {
|
|||
implementation.exclude module:'proto-google-common-protos'
|
||||
implementation.exclude module:'protolite-well-known-types'
|
||||
implementation.exclude module:'protobuf-javalite'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,5 +6,7 @@
|
|||
uri: rpc.flashbots.net
|
||||
-
|
||||
uri: eth-mainnet.public.blastapi.io
|
||||
-
|
||||
uri: eth.nownodes.io
|
||||
-
|
||||
uri: ethereum.publicnode.com
|
BIN
assets/images/seed_verified.png
Normal file
BIN
assets/images/seed_verified.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
|
@ -3,4 +3,6 @@
|
|||
-
|
||||
uri: polygon-bor.publicnode.com
|
||||
-
|
||||
uri: polygon.llamarpc.com
|
||||
uri: polygon.llamarpc.com
|
||||
-
|
||||
uri: matic.nownodes.io
|
|
@ -227,21 +227,21 @@ class ElectrumClient {
|
|||
return [];
|
||||
});
|
||||
|
||||
Future<List<Map<String, dynamic>>> getListUnspent(String scriptHash) =>
|
||||
call(method: 'blockchain.scripthash.listunspent', params: [scriptHash])
|
||||
.then((dynamic result) {
|
||||
if (result is List) {
|
||||
return result.map((dynamic val) {
|
||||
if (val is Map<String, dynamic>) {
|
||||
return val;
|
||||
}
|
||||
Future<List<Map<String, dynamic>>> getListUnspent(String scriptHash) async {
|
||||
final result = await call(method: 'blockchain.scripthash.listunspent', params: [scriptHash]);
|
||||
|
||||
return <String, dynamic>{};
|
||||
}).toList();
|
||||
if (result is List) {
|
||||
return result.map((dynamic val) {
|
||||
if (val is Map<String, dynamic>) {
|
||||
return val;
|
||||
}
|
||||
|
||||
return [];
|
||||
});
|
||||
return <String, dynamic>{};
|
||||
}).toList();
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
Future<List<Map<String, dynamic>>> getMempool(String scriptHash) =>
|
||||
call(method: 'blockchain.scripthash.get_mempool', params: [scriptHash])
|
||||
|
|
|
@ -28,7 +28,10 @@ abstract class ElectrumTransactionHistoryBase
|
|||
String _password;
|
||||
int _height;
|
||||
|
||||
Future<void> init() async => await _load();
|
||||
Future<void> init() async {
|
||||
clear();
|
||||
await _load();
|
||||
}
|
||||
|
||||
@override
|
||||
void addOne(ElectrumTransactionInfo transaction) => transactions[transaction.id] = transaction;
|
||||
|
|
|
@ -1150,7 +1150,7 @@ abstract class ElectrumWalletBase
|
|||
}
|
||||
|
||||
@override
|
||||
Future<void> close({required bool shouldCleanup}) async {
|
||||
Future<void> close({bool shouldCleanup = false}) async {
|
||||
try {
|
||||
_workerIsolate!.kill(priority: Isolate.immediate);
|
||||
await _workerSubscription?.cancel();
|
||||
|
@ -1254,19 +1254,18 @@ abstract class ElectrumWalletBase
|
|||
|
||||
Future<void> refreshUnspentCoinsInfo() async {
|
||||
try {
|
||||
final List<dynamic> keys = <dynamic>[];
|
||||
final List<dynamic> keys = [];
|
||||
final currentWalletUnspentCoins =
|
||||
unspentCoinsInfo.values.where((element) => element.walletId.contains(id));
|
||||
unspentCoinsInfo.values.where((record) => record.walletId == id);
|
||||
|
||||
if (currentWalletUnspentCoins.isNotEmpty) {
|
||||
currentWalletUnspentCoins.forEach((element) {
|
||||
final existUnspentCoins = unspentCoins
|
||||
.where((coin) => element.hash.contains(coin.hash) && element.vout == coin.vout);
|
||||
for (final element in currentWalletUnspentCoins) {
|
||||
if (RegexUtils.addressTypeFromStr(element.address, network) is MwebAddress) continue;
|
||||
|
||||
if (existUnspentCoins.isEmpty) {
|
||||
keys.add(element.key);
|
||||
}
|
||||
});
|
||||
final existUnspentCoins = unspentCoins.where((coin) => element == coin);
|
||||
|
||||
if (existUnspentCoins.isEmpty) {
|
||||
keys.add(element.key);
|
||||
}
|
||||
}
|
||||
|
||||
if (keys.isNotEmpty) {
|
||||
|
|
|
@ -1302,7 +1302,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<void> close({required bool shouldCleanup}) async {
|
||||
Future<void> close({bool shouldCleanup = false}) async {
|
||||
_utxoStream?.cancel();
|
||||
_feeRatesTimer?.cancel();
|
||||
_syncTimer?.cancel();
|
||||
|
|
|
@ -7,7 +7,7 @@ enum DeviceConnectionType {
|
|||
static List<DeviceConnectionType> supportedConnectionTypes(WalletType walletType,
|
||||
[bool isIOS = false]) {
|
||||
switch (walletType) {
|
||||
// case WalletType.monero:
|
||||
case WalletType.monero:
|
||||
case WalletType.bitcoin:
|
||||
case WalletType.litecoin:
|
||||
case WalletType.ethereum:
|
||||
|
|
|
@ -18,4 +18,5 @@ const SPL_TOKEN_TYPE_ID = 16;
|
|||
const DERIVATION_INFO_TYPE_ID = 17;
|
||||
const TRON_TOKEN_TYPE_ID = 18;
|
||||
const HARDWARE_WALLET_TYPE_TYPE_ID = 19;
|
||||
const MWEB_UTXO_TYPE_ID = 20;
|
||||
const MWEB_UTXO_TYPE_ID = 20;
|
||||
const HAVEN_SEED_STORE_TYPE_ID = 21;
|
|
@ -83,7 +83,7 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
|
|||
|
||||
Future<void> rescan({required int height});
|
||||
|
||||
Future<void> close({required bool shouldCleanup});
|
||||
Future<void> close({bool shouldCleanup = false});
|
||||
|
||||
Future<void> changePassword(String password);
|
||||
|
||||
|
|
|
@ -36,7 +36,18 @@ abstract class EVMChainClient {
|
|||
|
||||
bool connect(Node node) {
|
||||
try {
|
||||
_client = Web3Client(node.uri.toString(), httpClient);
|
||||
Uri? rpcUri;
|
||||
bool isModifiedNodeUri = false;
|
||||
|
||||
if (node.uriRaw == 'eth.nownodes.io' || node.uriRaw == 'matic.nownodes.io') {
|
||||
isModifiedNodeUri = true;
|
||||
String nowNodeApiKey = secrets.nowNodesApiKey;
|
||||
|
||||
rpcUri = Uri.https(node.uriRaw, '/$nowNodeApiKey');
|
||||
}
|
||||
|
||||
_client =
|
||||
Web3Client(isModifiedNodeUri ? rpcUri!.toString() : node.uri.toString(), httpClient);
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
|
@ -83,23 +94,20 @@ abstract class EVMChainClient {
|
|||
}
|
||||
}
|
||||
|
||||
Future<int> getEstimatedGas({
|
||||
String? contractAddress,
|
||||
Future<int> getEstimatedGasUnitsForTransaction({
|
||||
required EthereumAddress toAddress,
|
||||
required EthereumAddress senderAddress,
|
||||
required EtherAmount value,
|
||||
String? contractAddress,
|
||||
EtherAmount? gasPrice,
|
||||
// EtherAmount? maxFeePerGas,
|
||||
// EtherAmount? maxPriorityFeePerGas,
|
||||
EtherAmount? maxFeePerGas,
|
||||
}) async {
|
||||
try {
|
||||
if (contractAddress == null) {
|
||||
final estimatedGas = await _client!.estimateGas(
|
||||
sender: senderAddress,
|
||||
gasPrice: gasPrice,
|
||||
to: toAddress,
|
||||
value: value,
|
||||
// maxPriorityFeePerGas: maxPriorityFeePerGas,
|
||||
// maxFeePerGas: maxFeePerGas,
|
||||
);
|
||||
|
||||
|
@ -133,7 +141,9 @@ abstract class EVMChainClient {
|
|||
required Credentials privateKey,
|
||||
required String toAddress,
|
||||
required BigInt amount,
|
||||
required BigInt gas,
|
||||
required BigInt gasFee,
|
||||
required int estimatedGasUnits,
|
||||
required int maxFeePerGas,
|
||||
required EVMChainTransactionPriority priority,
|
||||
required CryptoCurrency currency,
|
||||
required int exponent,
|
||||
|
@ -152,6 +162,8 @@ abstract class EVMChainClient {
|
|||
maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip),
|
||||
amount: isNativeToken ? EtherAmount.inWei(amount) : EtherAmount.zero(),
|
||||
data: data != null ? hexToBytes(data) : null,
|
||||
maxGas: estimatedGasUnits,
|
||||
maxFeePerGas: EtherAmount.fromInt(EtherUnit.wei, maxFeePerGas),
|
||||
);
|
||||
|
||||
Uint8List signedTransaction;
|
||||
|
@ -180,7 +192,7 @@ abstract class EVMChainClient {
|
|||
return PendingEVMChainTransaction(
|
||||
signedTransaction: signedTransaction,
|
||||
amount: amount.toString(),
|
||||
fee: gas,
|
||||
fee: gasFee,
|
||||
sendTransaction: _sendTransaction,
|
||||
exponent: exponent,
|
||||
);
|
||||
|
@ -191,7 +203,10 @@ abstract class EVMChainClient {
|
|||
required EthereumAddress to,
|
||||
required EtherAmount amount,
|
||||
EtherAmount? maxPriorityFeePerGas,
|
||||
EtherAmount? gasPrice,
|
||||
EtherAmount? maxFeePerGas,
|
||||
Uint8List? data,
|
||||
int? maxGas,
|
||||
}) {
|
||||
return Transaction(
|
||||
from: from,
|
||||
|
@ -199,6 +214,9 @@ abstract class EVMChainClient {
|
|||
maxPriorityFeePerGas: maxPriorityFeePerGas,
|
||||
value: amount,
|
||||
data: data,
|
||||
maxGas: maxGas,
|
||||
gasPrice: gasPrice,
|
||||
maxFeePerGas: maxFeePerGas,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,10 @@ abstract class EVMChainTransactionHistoryBase
|
|||
|
||||
//! Common methods across all child classes
|
||||
|
||||
Future<void> init() async => await _load();
|
||||
Future<void> init() async {
|
||||
clear();
|
||||
await _load();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> save() async {
|
||||
|
|
|
@ -221,7 +221,7 @@ abstract class EVMChainWalletBase
|
|||
/// - The exact amount the user wants to send,
|
||||
/// - The addressHex for the receiving wallet,
|
||||
/// - A contract address which would be essential in determining if to calcualate the estimate for ERC20 or native ETH
|
||||
Future<int> calculateActualEstimatedFeeForCreateTransaction({
|
||||
Future<GasParamsHandler> calculateActualEstimatedFeeForCreateTransaction({
|
||||
required amount,
|
||||
required String? contractAddress,
|
||||
required String receivingAddressHex,
|
||||
|
@ -240,22 +240,27 @@ abstract class EVMChainWalletBase
|
|||
maxFeePerGas = gasPrice;
|
||||
}
|
||||
|
||||
final estimatedGas = await _client.getEstimatedGas(
|
||||
final estimatedGas = await _client.getEstimatedGasUnitsForTransaction(
|
||||
contractAddress: contractAddress,
|
||||
senderAddress: _evmChainPrivateKey.address,
|
||||
value: EtherAmount.fromBigInt(EtherUnit.wei, amount!),
|
||||
gasPrice: EtherAmount.fromInt(EtherUnit.wei, gasPrice),
|
||||
toAddress: EthereumAddress.fromHex(receivingAddressHex),
|
||||
// maxFeePerGas: EtherAmount.fromInt(EtherUnit.wei, maxFeePerGas),
|
||||
// maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip),
|
||||
maxFeePerGas: EtherAmount.fromInt(EtherUnit.wei, maxFeePerGas),
|
||||
);
|
||||
|
||||
final totalGasFee = estimatedGas * maxFeePerGas;
|
||||
return totalGasFee;
|
||||
|
||||
return GasParamsHandler(
|
||||
estimatedGasUnits: estimatedGas,
|
||||
estimatedGasFee: totalGasFee,
|
||||
maxFeePerGas: maxFeePerGas,
|
||||
gasPrice: gasPrice,
|
||||
);
|
||||
}
|
||||
return 0;
|
||||
return GasParamsHandler.zero();
|
||||
} catch (e) {
|
||||
return 0;
|
||||
return GasParamsHandler.zero();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -265,7 +270,7 @@ abstract class EVMChainWalletBase
|
|||
}
|
||||
|
||||
@override
|
||||
Future<void> close({required bool shouldCleanup}) async {
|
||||
Future<void> close({bool shouldCleanup = false}) async {
|
||||
_client.stop();
|
||||
_transactionsUpdateTimer?.cancel();
|
||||
_updateFeesTimer?.cancel();
|
||||
|
@ -318,7 +323,7 @@ abstract class EVMChainWalletBase
|
|||
|
||||
gasPrice = await _client.getGasUnitPrice();
|
||||
|
||||
estimatedGasUnits = await _client.getEstimatedGas(
|
||||
estimatedGasUnits = await _client.getEstimatedGasUnitsForTransaction(
|
||||
senderAddress: _evmChainPrivateKey.address,
|
||||
toAddress: _evmChainPrivateKey.address,
|
||||
gasPrice: EtherAmount.fromInt(EtherUnit.wei, gasPrice),
|
||||
|
@ -349,6 +354,8 @@ abstract class EVMChainWalletBase
|
|||
int exponent = transactionCurrency is Erc20Token ? transactionCurrency.decimal : 18;
|
||||
num amountToEVMChainMultiplier = pow(10, exponent);
|
||||
String? contractAddress;
|
||||
int estimatedGasUnitsForTransaction = 0;
|
||||
int maxFeePerGasForTransaction = 0;
|
||||
String toAddress = _credentials.outputs.first.isParsedAddress
|
||||
? _credentials.outputs.first.extractedAddress!
|
||||
: _credentials.outputs.first.address;
|
||||
|
@ -367,14 +374,16 @@ abstract class EVMChainWalletBase
|
|||
outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0)));
|
||||
totalAmount = BigInt.from(totalOriginalAmount * amountToEVMChainMultiplier);
|
||||
|
||||
final estimateFees = await calculateActualEstimatedFeeForCreateTransaction(
|
||||
final gasFeesModel = await calculateActualEstimatedFeeForCreateTransaction(
|
||||
amount: totalAmount,
|
||||
receivingAddressHex: toAddress,
|
||||
priority: _credentials.priority!,
|
||||
contractAddress: contractAddress,
|
||||
);
|
||||
|
||||
estimatedFeesForTransaction = BigInt.from(estimateFees);
|
||||
estimatedFeesForTransaction = BigInt.from(gasFeesModel.estimatedGasFee);
|
||||
estimatedGasUnitsForTransaction = gasFeesModel.estimatedGasUnits;
|
||||
maxFeePerGasForTransaction = gasFeesModel.maxFeePerGas;
|
||||
|
||||
if (erc20Balance.balance < totalAmount) {
|
||||
throw EVMChainTransactionCreationException(transactionCurrency);
|
||||
|
@ -392,14 +401,16 @@ abstract class EVMChainWalletBase
|
|||
totalAmount = erc20Balance.balance;
|
||||
}
|
||||
|
||||
final estimateFees = await calculateActualEstimatedFeeForCreateTransaction(
|
||||
final gasFeesModel = await calculateActualEstimatedFeeForCreateTransaction(
|
||||
amount: totalAmount,
|
||||
receivingAddressHex: toAddress,
|
||||
priority: _credentials.priority!,
|
||||
contractAddress: contractAddress,
|
||||
);
|
||||
|
||||
estimatedFeesForTransaction = BigInt.from(estimateFees);
|
||||
estimatedFeesForTransaction = BigInt.from(gasFeesModel.estimatedGasFee);
|
||||
estimatedGasUnitsForTransaction = gasFeesModel.estimatedGasUnits;
|
||||
maxFeePerGasForTransaction = gasFeesModel.maxFeePerGas;
|
||||
|
||||
if (output.sendAll && transactionCurrency is! Erc20Token) {
|
||||
totalAmount = (erc20Balance.balance - estimatedFeesForTransaction);
|
||||
|
@ -420,12 +431,14 @@ abstract class EVMChainWalletBase
|
|||
}
|
||||
|
||||
final pendingEVMChainTransaction = await _client.signTransaction(
|
||||
estimatedGasUnits: estimatedGasUnitsForTransaction,
|
||||
privateKey: _evmChainPrivateKey,
|
||||
toAddress: toAddress,
|
||||
amount: totalAmount,
|
||||
gas: estimatedFeesForTransaction,
|
||||
gasFee: estimatedFeesForTransaction,
|
||||
priority: _credentials.priority!,
|
||||
currency: transactionCurrency,
|
||||
maxFeePerGas: maxFeePerGasForTransaction,
|
||||
exponent: exponent,
|
||||
contractAddress:
|
||||
transactionCurrency is Erc20Token ? transactionCurrency.contractAddress : null,
|
||||
|
@ -728,3 +741,25 @@ abstract class EVMChainWalletBase
|
|||
@override
|
||||
final String? passphrase;
|
||||
}
|
||||
|
||||
class GasParamsHandler {
|
||||
final int estimatedGasUnits;
|
||||
final int estimatedGasFee;
|
||||
final int maxFeePerGas;
|
||||
final int gasPrice;
|
||||
|
||||
GasParamsHandler(
|
||||
{required this.estimatedGasUnits,
|
||||
required this.estimatedGasFee,
|
||||
required this.maxFeePerGas,
|
||||
required this.gasPrice});
|
||||
|
||||
static GasParamsHandler zero() {
|
||||
return GasParamsHandler(
|
||||
estimatedGasUnits: 0,
|
||||
estimatedGasFee: 0,
|
||||
maxFeePerGas: 0,
|
||||
gasPrice: 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -108,7 +108,7 @@ abstract class HavenWalletBase
|
|||
Future<void>? updateBalance() => null;
|
||||
|
||||
@override
|
||||
Future<void> close({required bool shouldCleanup}) async {
|
||||
Future<void> close({bool shouldCleanup = false}) async {
|
||||
_listener?.stop();
|
||||
_onAccountChangeReaction?.reaction.dispose();
|
||||
_autoSaveTimer?.cancel();
|
||||
|
|
|
@ -200,9 +200,16 @@ String? commitTransactionFromPointerAddress({required int address, required bool
|
|||
commitTransaction(transactionPointer: monero.PendingTransaction.fromAddress(address), useUR: useUR);
|
||||
|
||||
String? commitTransaction({required monero.PendingTransaction transactionPointer, required bool useUR}) {
|
||||
final transactionPointerAddress = transactionPointer.address;
|
||||
final txCommit = useUR
|
||||
? monero.PendingTransaction_commitUR(transactionPointer, 120)
|
||||
: monero.PendingTransaction_commit(transactionPointer, filename: '', overwrite: false);
|
||||
? monero.PendingTransaction_commitUR(transactionPointer, 120)
|
||||
: Isolate.run(() {
|
||||
monero.PendingTransaction_commit(
|
||||
Pointer.fromAddress(transactionPointerAddress),
|
||||
filename: '',
|
||||
overwrite: false,
|
||||
);
|
||||
});
|
||||
|
||||
String? error = (() {
|
||||
final status = monero.PendingTransaction_status(transactionPointer.cast());
|
||||
|
@ -221,7 +228,7 @@ String? commitTransaction({required monero.PendingTransaction transactionPointer
|
|||
})();
|
||||
|
||||
}
|
||||
if (error != null) {
|
||||
if (error != null && error != "no tx keys found for this txid") {
|
||||
throw CreationTransactionException(message: error);
|
||||
}
|
||||
if (useUR) {
|
||||
|
|
|
@ -121,7 +121,6 @@ Future<bool> setupNodeSync(
|
|||
daemonUsername: login ?? '',
|
||||
daemonPassword: password ?? '');
|
||||
});
|
||||
// monero.Wallet_init3(wptr!, argv0: '', defaultLogBaseName: 'moneroc', console: true, logPath: '');
|
||||
|
||||
final status = monero.Wallet_status(wptr!);
|
||||
|
||||
|
|
|
@ -2,11 +2,12 @@ import 'dart:async';
|
|||
import 'dart:ffi';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:ffi/ffi.dart';
|
||||
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
|
||||
import 'package:ledger_flutter_plus/ledger_flutter_plus_dart.dart';
|
||||
import 'package:monero/monero.dart' as monero;
|
||||
// import 'package:polyseed/polyseed.dart';
|
||||
|
||||
LedgerConnection? gLedger;
|
||||
|
||||
|
@ -28,9 +29,16 @@ void enableLedgerExchange(monero.wallet ptr, LedgerConnection connection) {
|
|||
ptr, emptyPointer.cast<UnsignedChar>(), 0);
|
||||
malloc.free(emptyPointer);
|
||||
|
||||
// printV("> ${ledgerRequest.toHexString()}");
|
||||
_logLedgerCommand(ledgerRequest, false);
|
||||
final response = await exchange(connection, ledgerRequest);
|
||||
// printV("< ${response.toHexString()}");
|
||||
_logLedgerCommand(response, true);
|
||||
|
||||
if (ListEquality().equals(response, [0x55, 0x15])) {
|
||||
await connection.disconnect();
|
||||
// // TODO: Show POPUP pls unlock your device
|
||||
// await Future.delayed(Duration(seconds: 15));
|
||||
// response = await exchange(connection, ledgerRequest);
|
||||
}
|
||||
|
||||
final Pointer<Uint8> result = malloc<Uint8>(response.length);
|
||||
for (var i = 0; i < response.length; i++) {
|
||||
|
@ -82,3 +90,59 @@ class ExchangeOperation extends LedgerRawOperation<Uint8List> {
|
|||
@override
|
||||
Future<List<Uint8List>> write(ByteDataWriter writer) async => [inputData];
|
||||
}
|
||||
|
||||
const _ledgerMoneroCommands = {
|
||||
0x00: "INS_NONE",
|
||||
0x02: "INS_RESET",
|
||||
0x20: "INS_GET_KEY",
|
||||
0x21: "INS_DISPLAY_ADDRESS",
|
||||
0x22: "INS_PUT_KEY",
|
||||
0x24: "INS_GET_CHACHA8_PREKEY",
|
||||
0x26: "INS_VERIFY_KEY",
|
||||
0x28: "INS_MANAGE_SEEDWORDS",
|
||||
0x30: "INS_SECRET_KEY_TO_PUBLIC_KEY",
|
||||
0x32: "INS_GEN_KEY_DERIVATION",
|
||||
0x34: "INS_DERIVATION_TO_SCALAR",
|
||||
0x36: "INS_DERIVE_PUBLIC_KEY",
|
||||
0x38: "INS_DERIVE_SECRET_KEY",
|
||||
0x3A: "INS_GEN_KEY_IMAGE",
|
||||
0x3B: "INS_DERIVE_VIEW_TAG",
|
||||
0x3C: "INS_SECRET_KEY_ADD",
|
||||
0x3E: "INS_SECRET_KEY_SUB",
|
||||
0x40: "INS_GENERATE_KEYPAIR",
|
||||
0x42: "INS_SECRET_SCAL_MUL_KEY",
|
||||
0x44: "INS_SECRET_SCAL_MUL_BASE",
|
||||
0x46: "INS_DERIVE_SUBADDRESS_PUBLIC_KEY",
|
||||
0x48: "INS_GET_SUBADDRESS",
|
||||
0x4A: "INS_GET_SUBADDRESS_SPEND_PUBLIC_KEY",
|
||||
0x4C: "INS_GET_SUBADDRESS_SECRET_KEY",
|
||||
0x70: "INS_OPEN_TX",
|
||||
0x72: "INS_SET_SIGNATURE_MODE",
|
||||
0x74: "INS_GET_ADDITIONAL_KEY",
|
||||
0x76: "INS_STEALTH",
|
||||
0x77: "INS_GEN_COMMITMENT_MASK",
|
||||
0x78: "INS_BLIND",
|
||||
0x7A: "INS_UNBLIND",
|
||||
0x7B: "INS_GEN_TXOUT_KEYS",
|
||||
0x7D: "INS_PREFIX_HASH",
|
||||
0x7C: "INS_VALIDATE",
|
||||
0x7E: "INS_MLSAG",
|
||||
0x7F: "INS_CLSAG",
|
||||
0x80: "INS_CLOSE_TX",
|
||||
0xA0: "INS_GET_TX_PROOF",
|
||||
0xC0: "INS_GET_RESPONSE"
|
||||
};
|
||||
|
||||
void _logLedgerCommand(Uint8List command, [bool isResponse = true]) {
|
||||
String toHexString(Uint8List data) =>
|
||||
data.map((e) => e.toRadixString(16).padLeft(2, '0')).join();
|
||||
|
||||
|
||||
|
||||
if (isResponse) {
|
||||
printV("< ${toHexString(command)}");
|
||||
} else {
|
||||
printV(
|
||||
"> ${_ledgerMoneroCommands[command[1]]} ${toHexString(command.sublist(2))}");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -175,7 +175,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
Future<void>? updateBalance() => null;
|
||||
|
||||
@override
|
||||
Future<void> close({required bool shouldCleanup}) async {
|
||||
Future<void> close({bool shouldCleanup = false}) async {
|
||||
_listener?.stop();
|
||||
_onAccountChangeReaction?.reaction.dispose();
|
||||
_onTxHistoryChangeReaction?.reaction.dispose();
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cw_core/get_height_by_date.dart';
|
||||
import 'package:cw_core/monero_wallet_utils.dart';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/unspent_coins_info.dart';
|
||||
|
@ -9,16 +11,16 @@ import 'package:cw_core/wallet_credentials.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_core/get_height_by_date.dart';
|
||||
import 'package:cw_monero/api/account_list.dart';
|
||||
import 'package:cw_monero/api/wallet_manager.dart' as monero_wallet_manager;
|
||||
import 'package:cw_monero/api/wallet_manager.dart';
|
||||
import 'package:cw_monero/ledger.dart';
|
||||
import 'package:cw_monero/monero_wallet.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
|
||||
import 'package:polyseed/polyseed.dart';
|
||||
import 'package:monero/monero.dart' as monero;
|
||||
import 'package:polyseed/polyseed.dart';
|
||||
|
||||
class MoneroNewWalletCredentials extends WalletCredentials {
|
||||
MoneroNewWalletCredentials(
|
||||
|
@ -133,14 +135,12 @@ class MoneroWalletService extends WalletService<
|
|||
try {
|
||||
final path = await pathForWallet(name: name, type: getType());
|
||||
|
||||
if (walletFilesExist(path)) {
|
||||
await repairOldAndroidWallet(name);
|
||||
}
|
||||
if (walletFilesExist(path)) await repairOldAndroidWallet(name);
|
||||
|
||||
await monero_wallet_manager
|
||||
.openWalletAsync({'path': path, 'password': password});
|
||||
final walletInfo = walletInfoSource.values.firstWhere(
|
||||
(info) => info.id == WalletBase.idFor(name, getType()));
|
||||
final walletInfo = walletInfoSource.values
|
||||
.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
|
||||
final wallet = MoneroWallet(
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
|
@ -204,7 +204,7 @@ class MoneroWalletService extends WalletService<
|
|||
@override
|
||||
Future<void> rename(String currentName, String password, String newName) async {
|
||||
final currentWalletInfo = walletInfoSource.values.firstWhere(
|
||||
(info) => info.id == WalletBase.idFor(currentName, getType()));
|
||||
(info) => info.id == WalletBase.idFor(currentName, getType()));
|
||||
final currentWallet = MoneroWallet(
|
||||
walletInfo: currentWalletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
|
@ -255,14 +255,14 @@ class MoneroWalletService extends WalletService<
|
|||
final password = credentials.password;
|
||||
final height = credentials.height;
|
||||
|
||||
if (wptr == null ) monero_wallet_manager.createWalletPointer();
|
||||
if (wptr == null) monero_wallet_manager.createWalletPointer();
|
||||
|
||||
enableLedgerExchange(wptr!, credentials.ledgerConnection);
|
||||
await monero_wallet_manager.restoreWalletFromHardwareWallet(
|
||||
path: path,
|
||||
password: password!,
|
||||
restoreHeight: height!,
|
||||
deviceName: 'Ledger');
|
||||
path: path,
|
||||
password: password!,
|
||||
restoreHeight: height!,
|
||||
deviceName: 'Ledger');
|
||||
|
||||
final wallet = MoneroWallet(
|
||||
walletInfo: credentials.walletInfo!,
|
||||
|
@ -279,7 +279,8 @@ class MoneroWalletService extends WalletService<
|
|||
}
|
||||
|
||||
@override
|
||||
Future<MoneroWallet> restoreFromSeed(MoneroRestoreWalletFromSeedCredentials credentials,
|
||||
Future<MoneroWallet> restoreFromSeed(
|
||||
MoneroRestoreWalletFromSeedCredentials credentials,
|
||||
{bool? isTestnet}) async {
|
||||
// Restore from Polyseed
|
||||
if (Polyseed.isValidSeed(credentials.mnemonic)) {
|
||||
|
@ -313,7 +314,8 @@ class MoneroWalletService extends WalletService<
|
|||
final path = await pathForWallet(name: credentials.name, type: getType());
|
||||
final polyseedCoin = PolyseedCoin.POLYSEED_MONERO;
|
||||
final lang = PolyseedLang.getByPhrase(credentials.mnemonic);
|
||||
final polyseed = Polyseed.decode(credentials.mnemonic, lang, polyseedCoin);
|
||||
final polyseed =
|
||||
Polyseed.decode(credentials.mnemonic, lang, polyseedCoin);
|
||||
|
||||
return _restoreFromPolyseed(
|
||||
path, credentials.password!, polyseed, credentials.walletInfo!, lang);
|
||||
|
@ -355,24 +357,18 @@ class MoneroWalletService extends WalletService<
|
|||
|
||||
Future<void> repairOldAndroidWallet(String name) async {
|
||||
try {
|
||||
if (!Platform.isAndroid) {
|
||||
return;
|
||||
}
|
||||
if (!Platform.isAndroid) return;
|
||||
|
||||
final oldAndroidWalletDirPath = await outdatedAndroidPathForWalletDir(name: name);
|
||||
final dir = Directory(oldAndroidWalletDirPath);
|
||||
|
||||
if (!dir.existsSync()) {
|
||||
return;
|
||||
}
|
||||
if (!dir.existsSync()) return;
|
||||
|
||||
final newWalletDirPath = await pathForWalletDir(name: name, type: getType());
|
||||
|
||||
dir.listSync().forEach((f) {
|
||||
final file = File(f.path);
|
||||
final name = f.path
|
||||
.split('/')
|
||||
.last;
|
||||
final name = f.path.split('/').last;
|
||||
final newPath = newWalletDirPath + '/$name';
|
||||
final newFile = File(newPath);
|
||||
|
||||
|
@ -391,9 +387,7 @@ class MoneroWalletService extends WalletService<
|
|||
try {
|
||||
final path = await pathForWallet(name: name, type: getType());
|
||||
|
||||
if (walletFilesExist(path)) {
|
||||
await repairOldAndroidWallet(name);
|
||||
}
|
||||
if (walletFilesExist(path)) await repairOldAndroidWallet(name);
|
||||
|
||||
await monero_wallet_manager.openWalletAsync({'path': path, 'password': password});
|
||||
final walletInfo = walletInfoSource.values
|
||||
|
@ -412,8 +406,10 @@ class MoneroWalletService extends WalletService<
|
|||
|
||||
@override
|
||||
bool requireHardwareWalletConnection(String name) {
|
||||
final walletInfo = walletInfoSource.values
|
||||
.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
|
||||
return walletInfo.isHardwareWallet;
|
||||
return walletInfoSource.values
|
||||
.firstWhereOrNull(
|
||||
(info) => info.id == WalletBase.idFor(name, getType()))
|
||||
?.isHardwareWallet ??
|
||||
false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -503,8 +503,8 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
path: "impls/monero.dart"
|
||||
ref: c41c4dad9aa5003a914cfb2c528c76386f952665
|
||||
resolved-ref: c41c4dad9aa5003a914cfb2c528c76386f952665
|
||||
ref: af5277f96073917185864d3596e82b67bee54e78
|
||||
resolved-ref: af5277f96073917185864d3596e82b67bee54e78
|
||||
url: "https://github.com/mrcyjanek/monero_c"
|
||||
source: git
|
||||
version: "0.0.0"
|
||||
|
|
|
@ -25,7 +25,7 @@ dependencies:
|
|||
monero:
|
||||
git:
|
||||
url: https://github.com/mrcyjanek/monero_c
|
||||
ref: c41c4dad9aa5003a914cfb2c528c76386f952665
|
||||
ref: af5277f96073917185864d3596e82b67bee54e78
|
||||
# ref: 6eb571ea498ed7b854934785f00fabfd0dadf75b # monero_c hash
|
||||
path: impls/monero.dart
|
||||
mutex: ^3.1.0
|
||||
|
|
|
@ -28,7 +28,10 @@ abstract class NanoTransactionHistoryBase extends TransactionHistoryBase<NanoTra
|
|||
final EncryptionFileUtils encryptionFileUtils;
|
||||
String _password;
|
||||
|
||||
Future<void> init() async => await _load();
|
||||
Future<void> init() async {
|
||||
clear();
|
||||
await _load();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> save() async {
|
||||
|
|
|
@ -150,7 +150,7 @@ abstract class NanoWalletBase
|
|||
Future<void> changePassword(String password) => throw UnimplementedError("changePassword");
|
||||
|
||||
@override
|
||||
Future<void> close({required bool shouldCleanup}) async {
|
||||
Future<void> close({bool shouldCleanup = false}) async {
|
||||
_client.stop();
|
||||
_receiveTimer?.cancel();
|
||||
}
|
||||
|
|
|
@ -14,11 +14,19 @@ class PolygonClient extends EVMChainClient {
|
|||
required EtherAmount amount,
|
||||
EtherAmount? maxPriorityFeePerGas,
|
||||
Uint8List? data,
|
||||
int? maxGas,
|
||||
EtherAmount? gasPrice,
|
||||
EtherAmount? maxFeePerGas,
|
||||
}) {
|
||||
return Transaction(
|
||||
from: from,
|
||||
to: to,
|
||||
value: amount,
|
||||
data: data,
|
||||
maxGas: maxGas,
|
||||
gasPrice: gasPrice,
|
||||
maxFeePerGas: maxFeePerGas,
|
||||
maxPriorityFeePerGas: maxPriorityFeePerGas,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_solana/pending_solana_transaction.dart';
|
||||
import 'package:cw_solana/solana_balance.dart';
|
||||
import 'package:cw_solana/solana_exceptions.dart';
|
||||
import 'package:cw_solana/solana_transaction_model.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:solana/dto.dart';
|
||||
|
@ -180,7 +181,7 @@ class SolanaWalletClient {
|
|||
bool isOutgoingTx = transfer.source == publicKey.toBase58();
|
||||
|
||||
double amount = (double.tryParse(transfer.amount) ?? 0.0) /
|
||||
pow(10, splTokenDecimal ?? 9);
|
||||
math.pow(10, splTokenDecimal ?? 9);
|
||||
|
||||
transactions.add(
|
||||
SolanaTransactionModel(
|
||||
|
@ -276,6 +277,7 @@ class SolanaWalletClient {
|
|||
required String destinationAddress,
|
||||
required Ed25519HDKeyPair ownerKeypair,
|
||||
required bool isSendAll,
|
||||
required double solBalance,
|
||||
String? tokenMint,
|
||||
List<String> references = const [],
|
||||
}) async {
|
||||
|
@ -290,6 +292,7 @@ class SolanaWalletClient {
|
|||
ownerKeypair: ownerKeypair,
|
||||
commitment: commitment,
|
||||
isSendAll: isSendAll,
|
||||
solBalance: solBalance,
|
||||
);
|
||||
return pendingNativeTokenTransaction;
|
||||
} else {
|
||||
|
@ -301,6 +304,7 @@ class SolanaWalletClient {
|
|||
destinationAddress: destinationAddress,
|
||||
ownerKeypair: ownerKeypair,
|
||||
commitment: commitment,
|
||||
solBalance: solBalance,
|
||||
);
|
||||
return pendingSPLTokenTransaction;
|
||||
}
|
||||
|
@ -353,6 +357,23 @@ class SolanaWalletClient {
|
|||
return fee;
|
||||
}
|
||||
|
||||
Future<bool> hasSufficientFundsLeftForRent({
|
||||
required double inputAmount,
|
||||
required double solBalance,
|
||||
required double fee,
|
||||
}) async {
|
||||
final rent =
|
||||
await _client!.getMinimumBalanceForMintRentExemption(commitment: Commitment.confirmed);
|
||||
|
||||
final rentInSol = (rent / lamportsPerSol).toDouble();
|
||||
|
||||
final remnant = solBalance - (inputAmount + fee);
|
||||
|
||||
if (remnant > rentInSol) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<PendingSolanaTransaction> _signNativeTokenTransaction({
|
||||
required String tokenTitle,
|
||||
required int tokenDecimals,
|
||||
|
@ -361,6 +382,7 @@ class SolanaWalletClient {
|
|||
required Ed25519HDKeyPair ownerKeypair,
|
||||
required Commitment commitment,
|
||||
required bool isSendAll,
|
||||
required double solBalance,
|
||||
}) async {
|
||||
// Convert SOL to lamport
|
||||
int lamports = (inputAmount * lamportsPerSol).toInt();
|
||||
|
@ -378,6 +400,16 @@ class SolanaWalletClient {
|
|||
commitment,
|
||||
);
|
||||
|
||||
bool hasSufficientFundsLeft = await hasSufficientFundsLeftForRent(
|
||||
inputAmount: inputAmount,
|
||||
fee: fee,
|
||||
solBalance: solBalance,
|
||||
);
|
||||
|
||||
if (!hasSufficientFundsLeft) {
|
||||
throw SolanaSignNativeTokenTransactionRentException();
|
||||
}
|
||||
|
||||
SignedTx signedTx;
|
||||
if (isSendAll) {
|
||||
final feeInLamports = (fee * lamportsPerSol).toInt();
|
||||
|
@ -425,6 +457,7 @@ class SolanaWalletClient {
|
|||
required String destinationAddress,
|
||||
required Ed25519HDKeyPair ownerKeypair,
|
||||
required Commitment commitment,
|
||||
required double solBalance,
|
||||
}) async {
|
||||
final destinationOwner = Ed25519HDPublicKey.fromBase58(destinationAddress);
|
||||
final mint = Ed25519HDPublicKey.fromBase58(tokenMint);
|
||||
|
@ -447,7 +480,7 @@ class SolanaWalletClient {
|
|||
// Throw an appropriate exception if the sender has no associated
|
||||
// token account
|
||||
if (associatedSenderAccount == null) {
|
||||
throw NoAssociatedTokenAccountException(ownerKeypair.address, mint.toBase58());
|
||||
throw SolanaNoAssociatedTokenAccountException(ownerKeypair.address, mint.toBase58());
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -457,11 +490,11 @@ class SolanaWalletClient {
|
|||
funder: ownerKeypair,
|
||||
);
|
||||
} catch (e) {
|
||||
throw Exception('Insufficient SOL balance to complete this transaction: ${e.toString()}');
|
||||
throw SolanaCreateAssociatedTokenAccountException(e.toString());
|
||||
}
|
||||
|
||||
// Input by the user
|
||||
final amount = (inputAmount * pow(10, tokenDecimals)).toInt();
|
||||
final amount = (inputAmount * math.pow(10, tokenDecimals)).toInt();
|
||||
|
||||
final instruction = TokenInstruction.transfer(
|
||||
source: Ed25519HDPublicKey.fromBase58(associatedSenderAccount.pubkey),
|
||||
|
@ -483,6 +516,16 @@ class SolanaWalletClient {
|
|||
commitment,
|
||||
);
|
||||
|
||||
bool hasSufficientFundsLeft = await hasSufficientFundsLeftForRent(
|
||||
inputAmount: inputAmount,
|
||||
fee: fee,
|
||||
solBalance: solBalance,
|
||||
);
|
||||
|
||||
if (!hasSufficientFundsLeft) {
|
||||
throw SolanaSignSPLTokenTransactionRentException();
|
||||
}
|
||||
|
||||
final signedTx = await _signTransactionInternal(
|
||||
message: message,
|
||||
signers: signers,
|
||||
|
|
|
@ -19,3 +19,20 @@ class SolanaTransactionWrongBalanceException implements Exception {
|
|||
@override
|
||||
String toString() => exceptionMessage;
|
||||
}
|
||||
|
||||
class SolanaSignNativeTokenTransactionRentException implements Exception {}
|
||||
|
||||
class SolanaCreateAssociatedTokenAccountException implements Exception {
|
||||
final String exceptionMessage;
|
||||
|
||||
SolanaCreateAssociatedTokenAccountException(this.exceptionMessage);
|
||||
}
|
||||
|
||||
class SolanaSignSPLTokenTransactionRentException implements Exception {}
|
||||
|
||||
class SolanaNoAssociatedTokenAccountException implements Exception {
|
||||
const SolanaNoAssociatedTokenAccountException(this.account, this.mint);
|
||||
|
||||
final String account;
|
||||
final String mint;
|
||||
}
|
||||
|
|
|
@ -26,7 +26,10 @@ abstract class SolanaTransactionHistoryBase extends TransactionHistoryBase<Solan
|
|||
final EncryptionFileUtils encryptionFileUtils;
|
||||
String _password;
|
||||
|
||||
Future<void> init() async => await _load();
|
||||
Future<void> init() async {
|
||||
clear();
|
||||
await _load();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> save() async {
|
||||
|
|
|
@ -180,7 +180,7 @@ abstract class SolanaWalletBase
|
|||
Future<void> changePassword(String password) => throw UnimplementedError("changePassword");
|
||||
|
||||
@override
|
||||
Future<void> close({required bool shouldCleanup}) async {
|
||||
Future<void> close({bool shouldCleanup = false}) async {
|
||||
_client.stop();
|
||||
_transactionsUpdateTimer?.cancel();
|
||||
}
|
||||
|
@ -228,6 +228,8 @@ abstract class SolanaWalletBase
|
|||
|
||||
final walletBalanceForCurrency = balance[transactionCurrency]!.balance;
|
||||
|
||||
final solBalance = balance[CryptoCurrency.sol]!.balance;
|
||||
|
||||
double totalAmount = 0.0;
|
||||
|
||||
bool isSendAll = false;
|
||||
|
@ -279,6 +281,7 @@ abstract class SolanaWalletBase
|
|||
? solCredentials.outputs.first.extractedAddress!
|
||||
: solCredentials.outputs.first.address,
|
||||
isSendAll: isSendAll,
|
||||
solBalance: solBalance,
|
||||
);
|
||||
|
||||
return pendingSolanaTransaction;
|
||||
|
|
|
@ -25,7 +25,10 @@ abstract class TronTransactionHistoryBase extends TransactionHistoryBase<TronTra
|
|||
final WalletInfo walletInfo;
|
||||
final EncryptionFileUtils encryptionFileUtils;
|
||||
|
||||
Future<void> init() async => await _load();
|
||||
Future<void> init() async {
|
||||
clear();
|
||||
await _load();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> save() async {
|
||||
|
|
|
@ -217,7 +217,7 @@ abstract class TronWalletBase
|
|||
Future<void> changePassword(String password) => throw UnimplementedError("changePassword");
|
||||
|
||||
@override
|
||||
Future<void> close({required bool shouldCleanup}) async => _transactionsUpdateTimer?.cancel();
|
||||
Future<void> close({bool shouldCleanup = false}) async => _transactionsUpdateTimer?.cancel();
|
||||
|
||||
@action
|
||||
@override
|
||||
|
|
|
@ -181,13 +181,23 @@ void commitTransaction({required wownero.PendingTransaction transactionPointer})
|
|||
|
||||
final txCommit = wownero.PendingTransaction_commit(transactionPointer, filename: '', overwrite: false);
|
||||
|
||||
final String? error = (() {
|
||||
String? error = (() {
|
||||
final status = wownero.PendingTransaction_status(transactionPointer.cast());
|
||||
if (status == 0) {
|
||||
return null;
|
||||
}
|
||||
return wownero.Wallet_errorString(wptr!);
|
||||
return wownero.PendingTransaction_errorString(transactionPointer.cast());
|
||||
})();
|
||||
if (error == null) {
|
||||
error = (() {
|
||||
final status = wownero.Wallet_status(wptr!);
|
||||
if (status == 0) {
|
||||
return null;
|
||||
}
|
||||
return wownero.Wallet_errorString(wptr!);
|
||||
})();
|
||||
|
||||
}
|
||||
|
||||
if (error != null) {
|
||||
throw CreationTransactionException(message: error);
|
||||
|
|
|
@ -162,7 +162,7 @@ abstract class WowneroWalletBase
|
|||
Future<void>? updateBalance() => null;
|
||||
|
||||
@override
|
||||
Future<void> close({required bool shouldCleanup}) async {
|
||||
Future<void> close({bool shouldCleanup = false}) async {
|
||||
_listener?.stop();
|
||||
_onAccountChangeReaction?.reaction.dispose();
|
||||
_onTxHistoryChangeReaction?.reaction.dispose();
|
||||
|
|
|
@ -463,8 +463,8 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
path: "impls/monero.dart"
|
||||
ref: c41c4dad9aa5003a914cfb2c528c76386f952665
|
||||
resolved-ref: c41c4dad9aa5003a914cfb2c528c76386f952665
|
||||
ref: af5277f96073917185864d3596e82b67bee54e78
|
||||
resolved-ref: af5277f96073917185864d3596e82b67bee54e78
|
||||
url: "https://github.com/mrcyjanek/monero_c"
|
||||
source: git
|
||||
version: "0.0.0"
|
||||
|
@ -757,10 +757,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
|
||||
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.2.4"
|
||||
version: "14.2.5"
|
||||
watcher:
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
|
|
|
@ -25,7 +25,7 @@ dependencies:
|
|||
monero:
|
||||
git:
|
||||
url: https://github.com/mrcyjanek/monero_c
|
||||
ref: c41c4dad9aa5003a914cfb2c528c76386f952665
|
||||
ref: af5277f96073917185864d3596e82b67bee54e78
|
||||
# ref: 6eb571ea498ed7b854934785f00fabfd0dadf75b # monero_c hash
|
||||
path: impls/monero.dart
|
||||
mutex: ^3.1.0
|
||||
|
|
|
@ -4,10 +4,10 @@ import 'package:cw_core/wallet_type.dart';
|
|||
class CommonTestConstants {
|
||||
static final pin = [0, 8, 0, 1];
|
||||
static final String sendTestAmount = '0.00008';
|
||||
static final String exchangeTestAmount = '8';
|
||||
static final String exchangeTestAmount = '0.01';
|
||||
static final WalletType testWalletType = WalletType.solana;
|
||||
static final String testWalletName = 'Integrated Testing Wallet';
|
||||
static final CryptoCurrency testReceiveCurrency = CryptoCurrency.sol;
|
||||
static final CryptoCurrency testDepositCurrency = CryptoCurrency.usdtSol;
|
||||
static final CryptoCurrency testReceiveCurrency = CryptoCurrency.usdtSol;
|
||||
static final CryptoCurrency testDepositCurrency = CryptoCurrency.sol;
|
||||
static final String testWalletAddress = '5v9gTW1yWPffhnbNKuvtL2frevAf4HpBMw8oYnfqUjhm';
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import 'package:flutter_test/flutter_test.dart';
|
|||
|
||||
import 'package:cake_wallet/main.dart' as app;
|
||||
|
||||
import '../robots/create_pin_welcome_page_robot.dart';
|
||||
import '../robots/dashboard_page_robot.dart';
|
||||
import '../robots/disclaimer_page_robot.dart';
|
||||
import '../robots/new_wallet_page_robot.dart';
|
||||
|
@ -37,6 +38,7 @@ class CommonTestFlows {
|
|||
_walletListPageRobot = WalletListPageRobot(_tester),
|
||||
_newWalletTypePageRobot = NewWalletTypePageRobot(_tester),
|
||||
_restoreOptionsPageRobot = RestoreOptionsPageRobot(_tester),
|
||||
_createPinWelcomePageRobot = CreatePinWelcomePageRobot(_tester),
|
||||
_restoreFromSeedOrKeysPageRobot = RestoreFromSeedOrKeysPageRobot(_tester),
|
||||
_walletGroupDescriptionPageRobot = WalletGroupDescriptionPageRobot(_tester);
|
||||
|
||||
|
@ -53,6 +55,7 @@ class CommonTestFlows {
|
|||
final WalletListPageRobot _walletListPageRobot;
|
||||
final NewWalletTypePageRobot _newWalletTypePageRobot;
|
||||
final RestoreOptionsPageRobot _restoreOptionsPageRobot;
|
||||
final CreatePinWelcomePageRobot _createPinWelcomePageRobot;
|
||||
final RestoreFromSeedOrKeysPageRobot _restoreFromSeedOrKeysPageRobot;
|
||||
final WalletGroupDescriptionPageRobot _walletGroupDescriptionPageRobot;
|
||||
|
||||
|
@ -190,10 +193,12 @@ class CommonTestFlows {
|
|||
WalletType walletTypeToCreate,
|
||||
List<int> pin,
|
||||
) async {
|
||||
await _welcomePageRobot.navigateToCreateNewWalletPage();
|
||||
await _createPinWelcomePageRobot.tapSetAPinButton();
|
||||
|
||||
await setupPinCodeForWallet(pin);
|
||||
|
||||
await _welcomePageRobot.navigateToCreateNewWalletPage();
|
||||
|
||||
await _selectWalletTypeForWallet(walletTypeToCreate);
|
||||
}
|
||||
|
||||
|
@ -201,12 +206,14 @@ class CommonTestFlows {
|
|||
WalletType walletTypeToRestore,
|
||||
List<int> pin,
|
||||
) async {
|
||||
await _createPinWelcomePageRobot.tapSetAPinButton();
|
||||
|
||||
await setupPinCodeForWallet(pin);
|
||||
|
||||
await _welcomePageRobot.navigateToRestoreWalletPage();
|
||||
|
||||
await _restoreOptionsPageRobot.navigateToRestoreFromSeedsOrKeysPage();
|
||||
|
||||
await setupPinCodeForWallet(pin);
|
||||
|
||||
await _selectWalletTypeForWallet(walletTypeToRestore);
|
||||
}
|
||||
|
||||
|
|
53
integration_test/robots/create_pin_welcome_page_robot.dart
Normal file
53
integration_test/robots/create_pin_welcome_page_robot.dart
Normal file
|
@ -0,0 +1,53 @@
|
|||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/screens/welcome/create_pin_welcome_page.dart';
|
||||
import 'package:cake_wallet/wallet_type_utils.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../components/common_test_cases.dart';
|
||||
|
||||
class CreatePinWelcomePageRobot {
|
||||
CreatePinWelcomePageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
|
||||
|
||||
final WidgetTester tester;
|
||||
late CommonTestCases commonTestCases;
|
||||
|
||||
Future<void> isCreatePinWelcomePage() async {
|
||||
await commonTestCases.isSpecificPage<CreatePinWelcomePage>();
|
||||
}
|
||||
|
||||
void hasTitle() {
|
||||
String title;
|
||||
if (isMoneroOnly) {
|
||||
title = S.current.monero_com;
|
||||
}
|
||||
|
||||
if (isHaven) {
|
||||
title = S.current.haven_app;
|
||||
}
|
||||
|
||||
title = S.current.cake_wallet;
|
||||
|
||||
commonTestCases.hasText(title);
|
||||
}
|
||||
|
||||
void hasDescription() {
|
||||
String description;
|
||||
if (isMoneroOnly) {
|
||||
description = S.current.monero_com_wallet_text;
|
||||
}
|
||||
|
||||
if (isHaven) {
|
||||
description = S.current.haven_app_wallet_text;
|
||||
}
|
||||
|
||||
description = S.current.new_first_wallet_text;
|
||||
|
||||
commonTestCases.hasText(description);
|
||||
}
|
||||
|
||||
Future<void> tapSetAPinButton() async {
|
||||
await commonTestCases.tapItemByKey('create_pin_welcome_page_create_a_pin_button_key');
|
||||
|
||||
await commonTestCases.defaultSleepTime();
|
||||
}
|
||||
}
|
|
@ -123,10 +123,8 @@ class ExchangePageRobot {
|
|||
return;
|
||||
}
|
||||
|
||||
await commonTestCases.dragUntilVisible(
|
||||
'picker_items_index_${depositCurrency.name}_button_key',
|
||||
'picker_scrollbar_key',
|
||||
);
|
||||
await commonTestCases.enterText(depositCurrency.name, 'search_bar_widget_key');
|
||||
|
||||
await commonTestCases.defaultSleepTime();
|
||||
|
||||
await commonTestCases.tapItemByKey('picker_items_index_${depositCurrency.name}_button_key');
|
||||
|
@ -149,10 +147,8 @@ class ExchangePageRobot {
|
|||
return;
|
||||
}
|
||||
|
||||
await commonTestCases.dragUntilVisible(
|
||||
'picker_items_index_${receiveCurrency.name}_button_key',
|
||||
'picker_scrollbar_key',
|
||||
);
|
||||
await commonTestCases.enterText(receiveCurrency.name, 'search_bar_widget_key');
|
||||
|
||||
await commonTestCases.defaultSleepTime();
|
||||
|
||||
await commonTestCases.tapItemByKey('picker_items_index_${receiveCurrency.name}_button_key');
|
||||
|
@ -318,7 +314,7 @@ class ExchangePageRobot {
|
|||
|
||||
Future<void> handleErrors(String initialAmount) async {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
|
||||
await _handleMinLimitError(initialAmount);
|
||||
|
||||
await _handleMaxLimitError(initialAmount);
|
||||
|
|
|
@ -84,13 +84,11 @@ class SendPageRobot {
|
|||
return;
|
||||
}
|
||||
|
||||
await commonTestCases.dragUntilVisible(
|
||||
'picker_items_index_${receiveCurrency.name}_button_key',
|
||||
'picker_scrollbar_key',
|
||||
);
|
||||
await commonTestCases.enterText(receiveCurrency.title, 'search_bar_widget_key');
|
||||
|
||||
await commonTestCases.defaultSleepTime();
|
||||
|
||||
await commonTestCases.tapItemByKey('picker_items_index_${receiveCurrency.name}_button_key');
|
||||
await commonTestCases.tapItemByKey('picker_items_index_${receiveCurrency.fullName}_button_key');
|
||||
}
|
||||
|
||||
Future<void> enterReceiveAddress(String receiveAddress) async {
|
||||
|
@ -210,6 +208,7 @@ class SendPageRobot {
|
|||
_handleAuthPage();
|
||||
}
|
||||
}
|
||||
await tester.pump();
|
||||
}
|
||||
|
||||
Future<void> handleSendResult() async {
|
||||
|
@ -366,4 +365,4 @@ class SendPageRobot {
|
|||
Future<void> _onIgnoreButtonOnSentDialogPressed() async {
|
||||
await commonTestCases.tapItemByKey('send_page_sent_dialog_ignore_button_key');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,11 +42,11 @@ class WalletKeysAndSeedPageRobot {
|
|||
bool hasPrivateKey = appStore.wallet!.privateKey != null;
|
||||
|
||||
if (walletType == WalletType.monero) {
|
||||
final moneroWallet = appStore.wallet as MoneroWallet;
|
||||
final moneroWallet = appStore.wallet as MoneroWalletBase;
|
||||
final lang = PolyseedLang.getByPhrase(moneroWallet.seed);
|
||||
final legacySeed = moneroWallet.seedLegacy(lang.nameEnglish);
|
||||
|
||||
_confirmMoneroWalletCredentials(
|
||||
await _confirmMoneroWalletCredentials(
|
||||
appStore,
|
||||
walletName,
|
||||
moneroWallet.seed,
|
||||
|
@ -59,7 +59,7 @@ class WalletKeysAndSeedPageRobot {
|
|||
final lang = PolyseedLang.getByPhrase(wowneroWallet.seed);
|
||||
final legacySeed = wowneroWallet.seedLegacy(lang.nameEnglish);
|
||||
|
||||
_confirmMoneroWalletCredentials(
|
||||
await _confirmMoneroWalletCredentials(
|
||||
appStore,
|
||||
walletName,
|
||||
wowneroWallet.seed,
|
||||
|
@ -105,12 +105,12 @@ class WalletKeysAndSeedPageRobot {
|
|||
await commonTestCases.defaultSleepTime(seconds: 5);
|
||||
}
|
||||
|
||||
void _confirmMoneroWalletCredentials(
|
||||
Future<void> _confirmMoneroWalletCredentials(
|
||||
AppStore appStore,
|
||||
String walletName,
|
||||
String seed,
|
||||
String legacySeed,
|
||||
) {
|
||||
) async {
|
||||
final keys = appStore.wallet!.keys as MoneroWalletKeys;
|
||||
|
||||
final hasPublicSpendKey = commonTestCases.isKeyPresent(
|
||||
|
@ -145,10 +145,18 @@ class WalletKeysAndSeedPageRobot {
|
|||
tester.printToConsole('$walletName wallet has private view key properly displayed');
|
||||
}
|
||||
if (hasSeeds) {
|
||||
await commonTestCases.dragUntilVisible(
|
||||
'${walletName}_wallet_seed_item_key',
|
||||
'wallet_keys_page_credentials_list_view_key',
|
||||
);
|
||||
commonTestCases.hasText(seed);
|
||||
tester.printToConsole('$walletName wallet has seeds properly displayed');
|
||||
}
|
||||
if (hasSeedLegacy) {
|
||||
await commonTestCases.dragUntilVisible(
|
||||
'${walletName}_wallet_seed_legacy_item_key',
|
||||
'wallet_keys_page_credentials_list_view_key',
|
||||
);
|
||||
commonTestCases.hasText(legacySeed);
|
||||
tester.printToConsole('$walletName wallet has legacy seeds properly displayed');
|
||||
}
|
||||
|
|
|
@ -101,7 +101,7 @@ Future<void> _confirmSeedsFlowForWalletType(
|
|||
walletKeysAndSeedPageRobot.hasTitle();
|
||||
walletKeysAndSeedPageRobot.hasShareWarning();
|
||||
|
||||
walletKeysAndSeedPageRobot.confirmWalletCredentials(walletType);
|
||||
await walletKeysAndSeedPageRobot.confirmWalletCredentials(walletType);
|
||||
|
||||
await walletKeysAndSeedPageRobot.backToDashboard();
|
||||
}
|
||||
|
|
45
integration_test_runner.sh
Executable file
45
integration_test_runner.sh
Executable file
|
@ -0,0 +1,45 @@
|
|||
#!/bin/bash
|
||||
|
||||
declare -a targets
|
||||
declare -a passed_tests
|
||||
declare -a failed_tests
|
||||
|
||||
# Collect all Dart test files in the integration_test directory
|
||||
while IFS= read -r -d $'\0' file; do
|
||||
targets+=("$file")
|
||||
done < <(find integration_test/test_suites -name "*.dart" -type f -print0)
|
||||
|
||||
# Run each test and collect results
|
||||
for target in "${targets[@]}"
|
||||
do
|
||||
echo "Running test: $target"
|
||||
if flutter drive \
|
||||
--driver=test_driver/integration_test.dart \
|
||||
--target="$target"; then
|
||||
echo "✅ Test passed: $target"
|
||||
passed_tests+=("$target")
|
||||
else
|
||||
echo "❌ Test failed: $target"
|
||||
failed_tests+=("$target")
|
||||
fi
|
||||
done
|
||||
|
||||
# Provide a summary of test results
|
||||
echo -e "\n===== Test Summary ====="
|
||||
if [ ${#passed_tests[@]} -gt 0 ]; then
|
||||
echo "✅ Passed Tests:"
|
||||
for test in "${passed_tests[@]}"; do
|
||||
echo " - $test"
|
||||
done
|
||||
fi
|
||||
|
||||
if [ ${#failed_tests[@]} -gt 0 ]; then
|
||||
echo -e "\n❌ Failed Tests:"
|
||||
for test in "${failed_tests[@]}"; do
|
||||
echo " - $test"
|
||||
done
|
||||
# Exit with a non-zero status to indicate failure
|
||||
exit 1
|
||||
else
|
||||
echo -e "\n🎉 All tests passed successfully!"
|
||||
fi
|
|
@ -93,6 +93,9 @@ class CakePayApi {
|
|||
required int quantity,
|
||||
required String userEmail,
|
||||
required String token,
|
||||
required bool confirmsNoVpn,
|
||||
required bool confirmsVoidedRefund,
|
||||
required bool confirmsTermsAgreed,
|
||||
}) async {
|
||||
final uri = Uri.https(baseCakePayUri, createOrderPath);
|
||||
final headers = {
|
||||
|
@ -106,7 +109,10 @@ class CakePayApi {
|
|||
'quantity': quantity,
|
||||
'user_email': userEmail,
|
||||
'token': token,
|
||||
'send_email': true
|
||||
'send_email': true,
|
||||
'confirms_no_vpn': confirmsNoVpn,
|
||||
'confirms_voided_refund': confirmsVoidedRefund,
|
||||
'confirms_terms_agreed': confirmsTermsAgreed,
|
||||
};
|
||||
|
||||
try {
|
||||
|
|
|
@ -90,17 +90,27 @@ class CakePayService {
|
|||
}
|
||||
|
||||
/// Purchase Gift Card
|
||||
Future<CakePayOrder> createOrder(
|
||||
{required int cardId, required String price, required int quantity}) async {
|
||||
Future<CakePayOrder> createOrder({
|
||||
required int cardId,
|
||||
required String price,
|
||||
required int quantity,
|
||||
required bool confirmsNoVpn,
|
||||
required bool confirmsVoidedRefund,
|
||||
required bool confirmsTermsAgreed,
|
||||
}) async {
|
||||
final userEmail = (await secureStorage.read(key: cakePayEmailStorageKey))!;
|
||||
final token = (await secureStorage.read(key: cakePayUserTokenKey))!;
|
||||
return await cakePayApi.createOrder(
|
||||
apiKey: cakePayApiKey,
|
||||
cardId: cardId,
|
||||
price: price,
|
||||
quantity: quantity,
|
||||
token: token,
|
||||
userEmail: userEmail);
|
||||
apiKey: cakePayApiKey,
|
||||
cardId: cardId,
|
||||
price: price,
|
||||
quantity: quantity,
|
||||
token: token,
|
||||
userEmail: userEmail,
|
||||
confirmsNoVpn: confirmsNoVpn,
|
||||
confirmsVoidedRefund: confirmsVoidedRefund,
|
||||
confirmsTermsAgreed: confirmsTermsAgreed,
|
||||
);
|
||||
}
|
||||
|
||||
///Simulate Purchase Gift Card
|
||||
|
|
|
@ -80,7 +80,7 @@ class AddressValidator extends TextValidator {
|
|||
case CryptoCurrency.shib:
|
||||
pattern = '0x[0-9a-zA-Z]+';
|
||||
case CryptoCurrency.xrp:
|
||||
pattern = '[0-9a-zA-Z]{34}|X[0-9a-zA-Z]{46}';
|
||||
pattern = '[0-9a-zA-Z]{34}|[0-9a-zA-Z]{33}|X[0-9a-zA-Z]{46}';
|
||||
case CryptoCurrency.xhv:
|
||||
pattern = 'hvx|hvi|hvs[0-9a-zA-Z]+';
|
||||
case CryptoCurrency.xag:
|
||||
|
@ -106,9 +106,8 @@ class AddressValidator extends TextValidator {
|
|||
case CryptoCurrency.wow:
|
||||
pattern = '[0-9a-zA-Z]+';
|
||||
case CryptoCurrency.bch:
|
||||
pattern = '^(bitcoincash:)?(q|p)[0-9a-zA-Z]{41,42}';
|
||||
case CryptoCurrency.bnb:
|
||||
pattern = '[0-9a-zA-Z]+';
|
||||
pattern = '(?:bitcoincash:)?(q|p)[0-9a-zA-Z]{41}'
|
||||
'|[13][a-km-zA-HJ-NP-Z1-9]{25,34}';
|
||||
case CryptoCurrency.hbar:
|
||||
pattern = '[0-9a-zA-Z.]+';
|
||||
case CryptoCurrency.zaddr:
|
||||
|
@ -203,7 +202,7 @@ class AddressValidator extends TextValidator {
|
|||
case CryptoCurrency.avaxc:
|
||||
return [42];
|
||||
case CryptoCurrency.bch:
|
||||
return [42, 43, 44, 54, 55];
|
||||
return [42, 54, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35];
|
||||
case CryptoCurrency.bnb:
|
||||
return [42];
|
||||
case CryptoCurrency.nano:
|
||||
|
@ -282,10 +281,14 @@ class AddressValidator extends TextValidator {
|
|||
|
||||
switch (type) {
|
||||
case CryptoCurrency.xmr:
|
||||
case CryptoCurrency.wow:
|
||||
pattern = '(4[0-9a-zA-Z]{94})'
|
||||
'|(8[0-9a-zA-Z]{94})'
|
||||
'|([0-9a-zA-Z]{106})';
|
||||
case CryptoCurrency.wow:
|
||||
pattern = '(W[0-9a-zA-Z]{94})'
|
||||
'|(W[0-9a-zA-Z]{94})'
|
||||
'|(W[0-9a-zA-Z]{96})'
|
||||
'|([0-9a-zA-Z]{106})';
|
||||
case CryptoCurrency.btc:
|
||||
pattern =
|
||||
'${P2pkhAddress.regex.pattern}|${P2shAddress.regex.pattern}|${P2wpkhAddress.regex.pattern}|${P2trAddress.regex.pattern}|${P2wshAddress.regex.pattern}|${SilentPaymentAddress.regex.pattern}';
|
||||
|
|
|
@ -39,6 +39,8 @@ import 'package:cake_wallet/entities/wallet_manager.dart';
|
|||
import 'package:cake_wallet/src/screens/buy/buy_sell_options_page.dart';
|
||||
import 'package:cake_wallet/src/screens/buy/payment_method_options_page.dart';
|
||||
import 'package:cake_wallet/src/screens/receive/address_list_page.dart';
|
||||
import 'package:cake_wallet/src/screens/seed/seed_verification/seed_verification_page.dart';
|
||||
import 'package:cake_wallet/src/screens/send/transaction_success_info_page.dart';
|
||||
import 'package:cake_wallet/src/screens/wallet_list/wallet_list_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/mweb_logs_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/mweb_node_page.dart';
|
||||
|
@ -1188,6 +1190,9 @@ Future<void> setup({
|
|||
getIt.registerFactoryParam<PreSeedPage, int, void>(
|
||||
(seedPhraseLength, _) => PreSeedPage(seedPhraseLength));
|
||||
|
||||
getIt.registerFactoryParam<TransactionSuccessPage, String, void>(
|
||||
(content, _) => TransactionSuccessPage(content: content));
|
||||
|
||||
getIt.registerFactoryParam<TradeDetailsViewModel, Trade, void>((trade, _) =>
|
||||
TradeDetailsViewModel(
|
||||
tradeForDetails: trade,
|
||||
|
@ -1423,5 +1428,7 @@ Future<void> setup({
|
|||
|
||||
getIt.registerFactory(() => SignViewModel(getIt.get<AppStore>().wallet!));
|
||||
|
||||
getIt.registerFactory(() => SeedVerificationPage(getIt.get<WalletSeedViewModel>()));
|
||||
|
||||
_isSetupFinished = true;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io' show Directory, File, Platform;
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/core/key_service.dart';
|
||||
import 'package:cake_wallet/core/secure_storage.dart';
|
||||
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
||||
import 'package:cake_wallet/entities/fiat_api_mode.dart';
|
||||
import 'package:cake_wallet/entities/haven_seed_store.dart';
|
||||
import 'package:cake_wallet/haven/haven.dart';
|
||||
import 'package:cw_core/cake_hive.dart';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cake_wallet/entities/secret_store_key.dart';
|
||||
import 'package:cw_core/root_dir.dart';
|
||||
|
@ -52,7 +56,8 @@ Future<void> defaultSettingsMigration(
|
|||
required Box<Node> powNodes,
|
||||
required Box<WalletInfo> walletInfoSource,
|
||||
required Box<Trade> tradeSource,
|
||||
required Box<Contact> contactSource}) async {
|
||||
required Box<Contact> contactSource,
|
||||
required Box<HavenSeedStore> havenSeedStore}) async {
|
||||
if (Platform.isIOS) {
|
||||
await ios_migrate_v1(walletInfoSource, tradeSource, contactSource);
|
||||
}
|
||||
|
@ -289,7 +294,23 @@ Future<void> defaultSettingsMigration(
|
|||
],
|
||||
);
|
||||
break;
|
||||
case 45:
|
||||
await _backupHavenSeeds(havenSeedStore);
|
||||
|
||||
updateWalletTypeNodesWithNewNode(
|
||||
newNodeUri: 'matic.nownodes.io',
|
||||
sharedPreferences: sharedPreferences,
|
||||
nodes: nodes,
|
||||
type: WalletType.polygon,
|
||||
useSSL: true,
|
||||
);
|
||||
updateWalletTypeNodesWithNewNode(
|
||||
newNodeUri: 'eth.nownodes.io',
|
||||
sharedPreferences: sharedPreferences,
|
||||
nodes: nodes,
|
||||
type: WalletType.ethereum,
|
||||
useSSL: true,
|
||||
);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -304,6 +325,13 @@ Future<void> defaultSettingsMigration(
|
|||
await sharedPreferences.setInt(PreferencesKey.currentDefaultSettingsMigrationVersion, version);
|
||||
}
|
||||
|
||||
Future<void> _backupHavenSeeds(Box<HavenSeedStore> havenSeedStore) async {
|
||||
final future = haven?.backupHavenSeeds(havenSeedStore);
|
||||
if (future != null) {
|
||||
await future;
|
||||
}
|
||||
return;
|
||||
}
|
||||
/// generic function for changing any wallet default node
|
||||
/// instead of making a new function for each change
|
||||
Future<void> _changeDefaultNode({
|
||||
|
@ -339,6 +367,26 @@ Future<void> _changeDefaultNode({
|
|||
}
|
||||
}
|
||||
|
||||
/// Generic function for adding a new Node for a Wallet Type.
|
||||
Future<void> updateWalletTypeNodesWithNewNode({
|
||||
required SharedPreferences sharedPreferences,
|
||||
required Box<Node> nodes,
|
||||
required WalletType type,
|
||||
required String newNodeUri,
|
||||
required bool useSSL,
|
||||
}) async {
|
||||
// If it already exists in the box of nodes, no need to add it annymore.
|
||||
if (nodes.values.any((node) => node.uriRaw == newNodeUri)) return;
|
||||
|
||||
await nodes.add(
|
||||
Node(
|
||||
uri: newNodeUri,
|
||||
type: type,
|
||||
useSSL: useSSL,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _updateCakeXmrNode(Box<Node> nodes) async {
|
||||
final node = nodes.values.firstWhereOrNull((element) => element.uriRaw == newCakeWalletMoneroUri);
|
||||
|
||||
|
|
81
lib/entities/evm_transaction_error_fees_handler.dart
Normal file
81
lib/entities/evm_transaction_error_fees_handler.dart
Normal file
|
@ -0,0 +1,81 @@
|
|||
class EVMTransactionErrorFeesHandler {
|
||||
EVMTransactionErrorFeesHandler({
|
||||
this.balanceWei,
|
||||
this.balanceEth,
|
||||
this.balanceUsd,
|
||||
this.txCostWei,
|
||||
this.txCostEth,
|
||||
this.txCostUsd,
|
||||
this.overshotWei,
|
||||
this.overshotEth,
|
||||
this.overshotUsd,
|
||||
this.error,
|
||||
});
|
||||
|
||||
String? balanceWei;
|
||||
String? balanceEth;
|
||||
String? balanceUsd;
|
||||
|
||||
String? txCostWei;
|
||||
String? txCostEth;
|
||||
String? txCostUsd;
|
||||
|
||||
String? overshotWei;
|
||||
String? overshotEth;
|
||||
String? overshotUsd;
|
||||
|
||||
String? error;
|
||||
|
||||
factory EVMTransactionErrorFeesHandler.parseEthereumFeesErrorMessage(
|
||||
String errorMessage,
|
||||
double assetPriceUsd,
|
||||
) {
|
||||
// Define Regular Expressions to extract the numerical values
|
||||
RegExp balanceRegExp = RegExp(r'balance (\d+)');
|
||||
RegExp txCostRegExp = RegExp(r'tx cost (\d+)');
|
||||
RegExp overshotRegExp = RegExp(r'overshot (\d+)');
|
||||
|
||||
// Match the patterns in the error message
|
||||
Match? balanceMatch = balanceRegExp.firstMatch(errorMessage);
|
||||
Match? txCostMatch = txCostRegExp.firstMatch(errorMessage);
|
||||
Match? overshotMatch = overshotRegExp.firstMatch(errorMessage);
|
||||
|
||||
// Check if all required values are found
|
||||
if (balanceMatch != null && txCostMatch != null && overshotMatch != null) {
|
||||
// Extract the numerical strings
|
||||
String balanceStr = balanceMatch.group(1)!;
|
||||
String txCostStr = txCostMatch.group(1)!;
|
||||
String overshotStr = overshotMatch.group(1)!;
|
||||
|
||||
// Parse the numerical strings to BigInt
|
||||
BigInt balanceWei = BigInt.parse(balanceStr);
|
||||
BigInt txCostWei = BigInt.parse(txCostStr);
|
||||
BigInt overshotWei = BigInt.parse(overshotStr);
|
||||
|
||||
// Convert wei to ETH (1 ETH = 1e18 wei)
|
||||
double balanceEth = balanceWei.toDouble() / 1e18;
|
||||
double txCostEth = txCostWei.toDouble() / 1e18;
|
||||
double overshotEth = overshotWei.toDouble() / 1e18;
|
||||
|
||||
// Calculate the USD values
|
||||
double balanceUsd = balanceEth * assetPriceUsd;
|
||||
double txCostUsd = txCostEth * assetPriceUsd;
|
||||
double overshotUsd = overshotEth * assetPriceUsd;
|
||||
|
||||
return EVMTransactionErrorFeesHandler(
|
||||
balanceWei: balanceWei.toString(),
|
||||
balanceEth: balanceEth.toString().substring(0, 12),
|
||||
balanceUsd: balanceUsd.toString().substring(0, 4),
|
||||
txCostWei: txCostWei.toString(),
|
||||
txCostEth: txCostEth.toString().substring(0, 12),
|
||||
txCostUsd: txCostUsd.toString().substring(0, 4),
|
||||
overshotWei: overshotWei.toString(),
|
||||
overshotEth: overshotEth.toString().substring(0, 12),
|
||||
overshotUsd: overshotUsd.toString().substring(0, 4),
|
||||
);
|
||||
} else {
|
||||
// If any value is missing, return an error message
|
||||
return EVMTransactionErrorFeesHandler(error: 'Could not parse the error message.');
|
||||
}
|
||||
}
|
||||
}
|
19
lib/entities/haven_seed_store.dart
Normal file
19
lib/entities/haven_seed_store.dart
Normal file
|
@ -0,0 +1,19 @@
|
|||
import 'package:cw_core/hive_type_ids.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
part 'haven_seed_store.g.dart';
|
||||
|
||||
@HiveType(typeId: HavenSeedStore.typeId)
|
||||
class HavenSeedStore extends HiveObject {
|
||||
HavenSeedStore({required this.id, this.seed});
|
||||
|
||||
static const typeId = HAVEN_SEED_STORE_TYPE_ID;
|
||||
static const boxName = 'HavenSeedStore';
|
||||
static const boxKey = 'havenSeedStoreKey';
|
||||
|
||||
@HiveField(0, defaultValue: '')
|
||||
String id;
|
||||
|
||||
@HiveField(2)
|
||||
String? seed;
|
||||
}
|
|
@ -92,6 +92,7 @@ class PreferencesKey {
|
|||
static const donationLinkWalletName = 'donation_link_wallet_name';
|
||||
static const lastSeenAppVersion = 'last_seen_app_version';
|
||||
static const shouldShowMarketPlaceInDashboard = 'should_show_marketplace_in_dashboard';
|
||||
static const showAddressBookPopupEnabled = 'show_address_book_popup_enabled';
|
||||
static const isNewInstall = 'is_new_install';
|
||||
static const serviceStatusShaKey = 'service_status_sha_key';
|
||||
static const walletConnectPairingTopicsList = 'wallet_connect_pairing_topics_list';
|
||||
|
|
|
@ -60,15 +60,17 @@ class ExolixExchangeProvider extends ExchangeProvider {
|
|||
Future<bool> checkIsAvailable() async => true;
|
||||
|
||||
@override
|
||||
Future<Limits> fetchLimits(
|
||||
{required CryptoCurrency from,
|
||||
required CryptoCurrency to,
|
||||
required bool isFixedRateMode}) async {
|
||||
Future<Limits> fetchLimits({
|
||||
required CryptoCurrency from,
|
||||
required CryptoCurrency to,
|
||||
required bool isFixedRateMode,
|
||||
}) async {
|
||||
final params = <String, String>{
|
||||
'rateType': _getRateType(isFixedRateMode),
|
||||
'amount': '1',
|
||||
'apiToken': apiKey,
|
||||
};
|
||||
|
||||
if (isFixedRateMode) {
|
||||
params['coinFrom'] = _normalizeCurrency(to);
|
||||
params['coinTo'] = _normalizeCurrency(from);
|
||||
|
@ -80,14 +82,30 @@ class ExolixExchangeProvider extends ExchangeProvider {
|
|||
params['networkFrom'] = _networkFor(from);
|
||||
params['networkTo'] = _networkFor(to);
|
||||
}
|
||||
final uri = Uri.https(apiBaseUrl, ratePath, params);
|
||||
final response = await get(uri);
|
||||
|
||||
if (response.statusCode != 200)
|
||||
throw Exception('Unexpected http status: ${response.statusCode}');
|
||||
// Maximum of 2 attempts to fetch limits
|
||||
for (int i = 0; i < 2; i++) {
|
||||
final uri = Uri.https(apiBaseUrl, ratePath, params);
|
||||
final response = await get(uri);
|
||||
|
||||
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
||||
return Limits(min: responseJSON['minAmount'] as double?);
|
||||
if (response.statusCode == 200) {
|
||||
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
||||
final minAmount = responseJSON['minAmount'];
|
||||
final maxAmount = responseJSON['maxAmount'];
|
||||
return Limits(min: _toDouble(minAmount), max: _toDouble(maxAmount));
|
||||
} else if (response.statusCode == 422) {
|
||||
final errorResponse = json.decode(response.body) as Map<String, dynamic>;
|
||||
if (errorResponse.containsKey('minAmount')) {
|
||||
params['amount'] = errorResponse['minAmount'].toString();
|
||||
continue;
|
||||
}
|
||||
throw Exception('Error 422: ${errorResponse['message'] ?? 'Unknown error'}');
|
||||
} else {
|
||||
throw Exception('Unexpected HTTP status: ${response.statusCode}');
|
||||
}
|
||||
}
|
||||
|
||||
throw Exception('Failed to fetch limits after retrying.');
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -279,4 +297,15 @@ class ExolixExchangeProvider extends ExchangeProvider {
|
|||
|
||||
String _normalizeAddress(String address) =>
|
||||
address.startsWith('bitcoincash:') ? address.replaceFirst('bitcoincash:', '') : address;
|
||||
|
||||
static double? _toDouble(dynamic value) {
|
||||
if (value is int) {
|
||||
return value.toDouble();
|
||||
} else if (value is double) {
|
||||
return value;
|
||||
} else if (value is String) {
|
||||
return double.tryParse(value);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -168,6 +168,7 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
|
|||
final status = responseJSON['status'] as String;
|
||||
final createdAtString = responseJSON['created_at'] as String;
|
||||
final expiredAtTimestamp = responseJSON['expired_at'] as int;
|
||||
final extraId = responseJSON['deposit_extra_id'] as String?;
|
||||
|
||||
final createdAt = DateTime.parse(createdAtString);
|
||||
final expiredAt = DateTime.fromMillisecondsSinceEpoch(expiredAtTimestamp * 1000);
|
||||
|
@ -199,6 +200,7 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
|
|||
state: TradeState.deserialize(raw: status),
|
||||
createdAt: createdAt,
|
||||
expiredAt: expiredAt,
|
||||
extraId: extraId,
|
||||
);
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
|
@ -231,6 +233,7 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
|
|||
final status = responseJSON['status'] as String;
|
||||
final createdAtString = responseJSON['created_at'] as String;
|
||||
final expiredAtTimestamp = responseJSON['expired_at'] as int;
|
||||
final extraId = responseJSON['deposit_extra_id'] as String?;
|
||||
|
||||
final createdAt = DateTime.parse(createdAtString);
|
||||
final expiredAt = DateTime.fromMillisecondsSinceEpoch(expiredAtTimestamp * 1000);
|
||||
|
@ -249,6 +252,7 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
|
|||
createdAt: createdAt,
|
||||
expiredAt: expiredAt,
|
||||
isRefund: status == 'refund',
|
||||
extraId: extraId,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -203,6 +203,7 @@ class SideShiftExchangeProvider extends ExchangeProvider {
|
|||
final inputAddress = responseJSON['depositAddress'] as String;
|
||||
final settleAddress = responseJSON['settleAddress'] as String;
|
||||
final depositAmount = responseJSON['depositAmount'] as String?;
|
||||
final depositMemo = responseJSON['depositMemo'] as String?;
|
||||
|
||||
return Trade(
|
||||
id: id,
|
||||
|
@ -217,6 +218,7 @@ class SideShiftExchangeProvider extends ExchangeProvider {
|
|||
payoutAddress: settleAddress,
|
||||
createdAt: DateTime.now(),
|
||||
isSendAll: isSendAll,
|
||||
extraId: depositMemo
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -251,6 +253,7 @@ class SideShiftExchangeProvider extends ExchangeProvider {
|
|||
final isVariable = (responseJSON['type'] as String) == 'variable';
|
||||
final expiredAtRaw = responseJSON['expiresAt'] as String;
|
||||
final expiredAt = isVariable ? null : DateTime.tryParse(expiredAtRaw)?.toLocal();
|
||||
final depositMemo = responseJSON['depositMemo'] as String?;
|
||||
|
||||
return Trade(
|
||||
id: id,
|
||||
|
@ -261,7 +264,8 @@ class SideShiftExchangeProvider extends ExchangeProvider {
|
|||
amount: expectedSendAmount ?? '',
|
||||
state: TradeState.deserialize(raw: status ?? 'created'),
|
||||
expiredAt: expiredAt,
|
||||
payoutAddress: settleAddress);
|
||||
payoutAddress: settleAddress,
|
||||
extraId: depositMemo);
|
||||
}
|
||||
|
||||
Future<String> _createQuote(TradeRequest request) async {
|
||||
|
|
|
@ -154,6 +154,7 @@ class StealthExExchangeProvider extends ExchangeProvider {
|
|||
final receiveAmount = toDouble(withdrawal['amount']);
|
||||
final status = responseJSON['status'] as String;
|
||||
final createdAtString = responseJSON['created_at'] as String;
|
||||
final extraId = deposit['extra_id'] as String?;
|
||||
|
||||
final createdAt = DateTime.parse(createdAtString);
|
||||
final expiredAt = validUntil != null
|
||||
|
@ -188,6 +189,7 @@ class StealthExExchangeProvider extends ExchangeProvider {
|
|||
state: TradeState.deserialize(raw: status),
|
||||
createdAt: createdAt,
|
||||
expiredAt: expiredAt,
|
||||
extraId: extraId,
|
||||
);
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
|
@ -220,6 +222,7 @@ class StealthExExchangeProvider extends ExchangeProvider {
|
|||
final status = responseJSON['status'] as String;
|
||||
final createdAtString = responseJSON['created_at'] as String;
|
||||
final createdAt = DateTime.parse(createdAtString);
|
||||
final extraId = deposit['extra_id'] as String?;
|
||||
|
||||
return Trade(
|
||||
id: respId,
|
||||
|
@ -234,6 +237,7 @@ class StealthExExchangeProvider extends ExchangeProvider {
|
|||
state: TradeState.deserialize(raw: status),
|
||||
createdAt: createdAt,
|
||||
isRefund: status == 'refunded',
|
||||
extraId: extraId,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -171,7 +171,8 @@ class TrocadorExchangeProvider extends ExchangeProvider {
|
|||
if (!isFixedRateMode) 'amount_from': request.fromAmount,
|
||||
if (isFixedRateMode) 'amount_to': request.toAmount,
|
||||
'address': request.toAddress,
|
||||
'refund': request.refundAddress
|
||||
'refund': request.refundAddress,
|
||||
'refund_memo' : '0',
|
||||
};
|
||||
|
||||
if (isFixedRateMode) {
|
||||
|
@ -262,6 +263,7 @@ class TrocadorExchangeProvider extends ExchangeProvider {
|
|||
final password = responseJSON['password'] as String;
|
||||
final providerId = responseJSON['id_provider'] as String;
|
||||
final providerName = responseJSON['provider'] as String;
|
||||
final addressProviderMemo = responseJSON['address_provider_memo'] as String?;
|
||||
|
||||
return Trade(
|
||||
id: id,
|
||||
|
@ -277,6 +279,7 @@ class TrocadorExchangeProvider extends ExchangeProvider {
|
|||
password: password,
|
||||
providerId: providerId,
|
||||
providerName: providerName,
|
||||
extraId: addressProviderMemo,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -143,6 +143,7 @@ class Trade extends HiveObject {
|
|||
isRefund: map['isRefund'] as bool?,
|
||||
isSendAll: map['isSendAll'] as bool?,
|
||||
router: map['router'] as String?,
|
||||
extraId: map['extra_id'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -162,6 +163,7 @@ class Trade extends HiveObject {
|
|||
'isRefund': isRefund,
|
||||
'isSendAll': isSendAll,
|
||||
'router': router,
|
||||
'extra_id': extraId,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -307,6 +307,23 @@ class CWHaven extends Haven {
|
|||
return havenTransactionInfo.accountIndex;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> backupHavenSeeds(Box<HavenSeedStore> havenSeedStore) async {
|
||||
final walletInfoSource = await CakeHive.openBox<WalletInfo>(WalletInfo.boxName);
|
||||
final wallets = walletInfoSource.values
|
||||
.where((element) => element.type == WalletType.haven);
|
||||
for (var w in wallets) {
|
||||
final walletService = HavenWalletService(walletInfoSource);
|
||||
final flutterSecureStorage = secureStorageShared;
|
||||
final keyService = KeyService(flutterSecureStorage);
|
||||
final password = await keyService.getWalletPassword(walletName: w.name);
|
||||
final wallet = await walletService.openWallet(w.name, password);
|
||||
await havenSeedStore.add(HavenSeedStore(id: wallet.id, seed: wallet.seed));
|
||||
wallet.close();
|
||||
}
|
||||
await havenSeedStore.flush();
|
||||
}
|
||||
|
||||
@override
|
||||
WalletService createHavenWalletService(Box<WalletInfo> walletInfoSource) {
|
||||
return HavenWalletService(walletInfoSource);
|
||||
|
|
|
@ -9,6 +9,7 @@ import 'package:cake_wallet/entities/contact.dart';
|
|||
import 'package:cake_wallet/entities/default_settings_migration.dart';
|
||||
import 'package:cake_wallet/entities/get_encryption_key.dart';
|
||||
import 'package:cake_wallet/core/secure_storage.dart';
|
||||
import 'package:cake_wallet/entities/haven_seed_store.dart';
|
||||
import 'package:cake_wallet/entities/language_service.dart';
|
||||
import 'package:cake_wallet/entities/template.dart';
|
||||
import 'package:cake_wallet/entities/transaction_description.dart';
|
||||
|
@ -164,6 +165,10 @@ Future<void> initializeAppConfigs() async {
|
|||
CakeHive.registerAdapter(AnonpayInvoiceInfoAdapter());
|
||||
}
|
||||
|
||||
if (!CakeHive.isAdapterRegistered(HavenSeedStore.typeId)) {
|
||||
CakeHive.registerAdapter(HavenSeedStoreAdapter());
|
||||
}
|
||||
|
||||
if (!CakeHive.isAdapterRegistered(MwebUtxo.typeId)) {
|
||||
CakeHive.registerAdapter(MwebUtxoAdapter());
|
||||
}
|
||||
|
@ -188,6 +193,12 @@ Future<void> initializeAppConfigs() async {
|
|||
final anonpayInvoiceInfo = await CakeHive.openBox<AnonpayInvoiceInfo>(AnonpayInvoiceInfo.boxName);
|
||||
final unspentCoinsInfoSource = await CakeHive.openBox<UnspentCoinsInfo>(UnspentCoinsInfo.boxName);
|
||||
|
||||
final havenSeedStoreBoxKey =
|
||||
await getEncryptionKey(secureStorage: secureStorage, forKey: HavenSeedStore.boxKey);
|
||||
final havenSeedStore = await CakeHive.openBox<HavenSeedStore>(
|
||||
HavenSeedStore.boxName,
|
||||
encryptionKey: havenSeedStoreBoxKey);
|
||||
|
||||
await initialSetup(
|
||||
sharedPreferences: await SharedPreferences.getInstance(),
|
||||
nodes: nodes,
|
||||
|
@ -203,7 +214,8 @@ Future<void> initializeAppConfigs() async {
|
|||
transactionDescriptions: transactionDescriptions,
|
||||
secureStorage: secureStorage,
|
||||
anonpayInvoiceInfo: anonpayInvoiceInfo,
|
||||
initialMigrationVersion: 44,
|
||||
havenSeedStore: havenSeedStore,
|
||||
initialMigrationVersion: 45,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -222,7 +234,8 @@ Future<void> initialSetup(
|
|||
required SecureStorage secureStorage,
|
||||
required Box<AnonpayInvoiceInfo> anonpayInvoiceInfo,
|
||||
required Box<UnspentCoinsInfo> unspentCoinsInfoSource,
|
||||
int initialMigrationVersion = 15}) async {
|
||||
required Box<HavenSeedStore> havenSeedStore,
|
||||
int initialMigrationVersion = 15, }) async {
|
||||
LanguageService.loadLocaleList();
|
||||
await defaultSettingsMigration(
|
||||
secureStorage: secureStorage,
|
||||
|
@ -232,7 +245,8 @@ Future<void> initialSetup(
|
|||
contactSource: contactSource,
|
||||
tradeSource: tradesSource,
|
||||
nodes: nodes,
|
||||
powNodes: powNodes);
|
||||
powNodes: powNodes,
|
||||
havenSeedStore: havenSeedStore);
|
||||
await setup(
|
||||
walletInfoSource: walletInfoSource,
|
||||
nodeSource: nodes,
|
||||
|
|
|
@ -44,13 +44,16 @@ void startAuthenticationStateChange(
|
|||
} catch (error, stack) {
|
||||
loginError = error;
|
||||
await ExceptionHandler.resetLastPopupDate();
|
||||
await ExceptionHandler.onError(FlutterErrorDetails(exception: error, stack: stack));
|
||||
await ExceptionHandler.onError(
|
||||
FlutterErrorDetails(exception: error, stack: stack));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == AuthenticationState.allowed) {
|
||||
if (requireHardwareWalletConnection()) {
|
||||
if ([AuthenticationState.allowed, AuthenticationState.allowedCreate]
|
||||
.contains(state)) {
|
||||
if (state == AuthenticationState.allowed &&
|
||||
requireHardwareWalletConnection()) {
|
||||
await navigatorKey.currentState!.pushNamedAndRemoveUntil(
|
||||
Routes.connectDevices,
|
||||
(route) => false,
|
||||
|
@ -58,14 +61,14 @@ void startAuthenticationStateChange(
|
|||
walletType: WalletType.monero,
|
||||
onConnectDevice: (context, ledgerVM) async {
|
||||
monero!.setGlobalLedgerConnection(ledgerVM.connection);
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) => AlertWithOneAction(
|
||||
alertTitle: S.of(context).proceed_on_device,
|
||||
alertContent: S.of(context).proceed_on_device_description,
|
||||
buttonText: S.of(context).cancel,
|
||||
buttonAction: () => Navigator.of(context).pop()),
|
||||
);
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) => AlertWithOneAction(
|
||||
alertTitle: S.of(context).proceed_on_device,
|
||||
alertContent: S.of(context).proceed_on_device_description,
|
||||
buttonText: S.of(context).cancel,
|
||||
buttonAction: () => Navigator.of(context).pop()),
|
||||
);
|
||||
await loadCurrentWallet();
|
||||
getIt.get<BottomSheetService>().resetCurrentSheet();
|
||||
await navigatorKey.currentState!
|
||||
|
|
|
@ -66,9 +66,11 @@ import 'package:cake_wallet/src/screens/restore/sweeping_wallet_page.dart';
|
|||
import 'package:cake_wallet/src/screens/restore/wallet_restore_choose_derivation.dart';
|
||||
import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart';
|
||||
import 'package:cake_wallet/src/screens/seed/pre_seed_page.dart';
|
||||
import 'package:cake_wallet/src/screens/seed/seed_verification/seed_verification_page.dart';
|
||||
import 'package:cake_wallet/src/screens/seed/wallet_seed_page.dart';
|
||||
import 'package:cake_wallet/src/screens/send/send_page.dart';
|
||||
import 'package:cake_wallet/src/screens/send/send_template_page.dart';
|
||||
import 'package:cake_wallet/src/screens/send/transaction_success_info_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/connection_sync_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/desktop_settings/desktop_settings_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/display_settings_page.dart';
|
||||
|
@ -597,6 +599,10 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
|||
return MaterialPageRoute<void>(
|
||||
builder: (_) => getIt.get<PreSeedPage>(param1: settings.arguments as int));
|
||||
|
||||
case Routes.transactionSuccessPage:
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (_) => getIt.get<TransactionSuccessPage>(param1: settings.arguments as String));
|
||||
|
||||
case Routes.backup:
|
||||
return CupertinoPageRoute<void>(
|
||||
fullscreenDialog: true, builder: (_) => getIt.get<BackupPage>());
|
||||
|
@ -807,6 +813,12 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
|||
),
|
||||
);
|
||||
|
||||
case Routes.walletSeedVerificationPage:
|
||||
return MaterialPageRoute<void>(
|
||||
fullscreenDialog: true,
|
||||
builder: (_) => getIt.get<SeedVerificationPage>(),
|
||||
);
|
||||
|
||||
default:
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (_) => Scaffold(
|
||||
|
|
|
@ -53,6 +53,7 @@ class Routes {
|
|||
static const restoreWalletType = '/restore_wallet_type';
|
||||
static const restoreWallet = '/restore_wallet';
|
||||
static const preSeedPage = '/pre_seed_page';
|
||||
static const transactionSuccessPage = '/transaction_success_info_page';
|
||||
static const backup = '/backup';
|
||||
static const editBackupPassword = '/edit_backup_passowrd';
|
||||
static const restoreFromBackup = '/restore_from_backup';
|
||||
|
@ -115,4 +116,5 @@ class Routes {
|
|||
static const urqrAnimatedPage = '/urqr/animated_page';
|
||||
static const walletGroupsDisplayPage = '/wallet_groups_display_page';
|
||||
static const walletGroupDescription = '/wallet_group_description';
|
||||
static const walletSeedVerificationPage = '/wallet_seed_verification_page';
|
||||
}
|
||||
|
|
|
@ -116,7 +116,7 @@ class CakePayCardsPage extends BasePage {
|
|||
},
|
||||
child: Container(
|
||||
width: 32,
|
||||
padding: EdgeInsets.all(8),
|
||||
padding: EdgeInsets.only(top: 7, bottom: 7),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
|
||||
border: Border.all(
|
||||
|
@ -125,7 +125,7 @@ class CakePayCardsPage extends BasePage {
|
|||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Image.asset(
|
||||
'assets/images/filter.png',
|
||||
'assets/images/filter_icon.png',
|
||||
color: Theme.of(context).extension<FilterTheme>()!.iconColor,
|
||||
))),
|
||||
);
|
||||
|
@ -141,14 +141,14 @@ class CakePayCardsPage extends BasePage {
|
|||
}
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 6),
|
||||
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
|
||||
border: Border.all(color: Colors.transparent),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Container(
|
||||
margin: EdgeInsets.symmetric(vertical: 2),
|
||||
margin: EdgeInsets.symmetric(vertical: 4),
|
||||
child: Row(
|
||||
children: [
|
||||
Image.asset(
|
||||
|
@ -363,13 +363,10 @@ class _SearchWidget extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final searchIcon = ExcludeSemantics(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: Image.asset(
|
||||
'assets/images/mini_search_icon.png',
|
||||
child: Icon( Icons.search,
|
||||
color: Theme.of(context).extension<FilterTheme>()!.iconColor,
|
||||
//size: 24
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return TextField(
|
||||
|
@ -379,8 +376,8 @@ class _SearchWidget extends StatelessWidget {
|
|||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
contentPadding: EdgeInsets.only(
|
||||
top: 10,
|
||||
left: 10,
|
||||
top: 8,
|
||||
left: 8,
|
||||
),
|
||||
fillColor: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
|
||||
hintText: S.of(context).search,
|
||||
|
|
|
@ -10,8 +10,10 @@ import 'package:cake_wallet/src/screens/cake_pay/widgets/text_icon_button.dart';
|
|||
import 'package:cake_wallet/src/screens/send/widgets/confirm_sending_alert.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
import 'package:cake_wallet/src/widgets/base_alert_dialog.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||
import 'package:cake_wallet/src/widgets/standard_checkbox.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/picker_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/receive_page_theme.dart';
|
||||
|
@ -23,6 +25,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class CakePayBuyCardDetailPage extends BasePage {
|
||||
CakePayBuyCardDetailPage(this.cakePayPurchaseViewModel);
|
||||
|
@ -207,8 +210,10 @@ class CakePayBuyCardDetailPage extends BasePage {
|
|||
padding: EdgeInsets.only(bottom: 12),
|
||||
child: Observer(builder: (_) {
|
||||
return LoadingPrimaryButton(
|
||||
isLoading: cakePayPurchaseViewModel.sendViewModel.state is IsExecutingState,
|
||||
onPressed: () => purchaseCard(context),
|
||||
isDisabled: cakePayPurchaseViewModel.isPurchasing,
|
||||
isLoading: cakePayPurchaseViewModel.isPurchasing ||
|
||||
cakePayPurchaseViewModel.sendViewModel.state is IsExecutingState,
|
||||
onPressed: () => confirmPurchaseFirst(context),
|
||||
text: S.of(context).purchase_gift_card,
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white,
|
||||
|
@ -253,6 +258,48 @@ class CakePayBuyCardDetailPage extends BasePage {
|
|||
});
|
||||
}
|
||||
|
||||
Future<void> _showconfirmPurchaseFirstAlert(BuildContext context) async {
|
||||
if (!cakePayPurchaseViewModel.confirmsNoVpn ||
|
||||
!cakePayPurchaseViewModel.confirmsVoidedRefund ||
|
||||
!cakePayPurchaseViewModel.confirmsTermsAgreed) {
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) => ThreeCheckboxAlert(
|
||||
alertTitle: S.of(context).cakepay_confirm_purchase,
|
||||
leftButtonText: S.of(context).cancel,
|
||||
rightButtonText: S.of(context).confirm,
|
||||
actionLeftButton: () {
|
||||
cakePayPurchaseViewModel.isPurchasing = false;
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
actionRightButton: (confirmsNoVpn, confirmsVoidedRefund, confirmsTermsAgreed) {
|
||||
cakePayPurchaseViewModel.confirmsNoVpn = confirmsNoVpn;
|
||||
cakePayPurchaseViewModel.confirmsVoidedRefund = confirmsVoidedRefund;
|
||||
cakePayPurchaseViewModel.confirmsTermsAgreed = confirmsTermsAgreed;
|
||||
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (cakePayPurchaseViewModel.confirmsNoVpn &&
|
||||
cakePayPurchaseViewModel.confirmsVoidedRefund &&
|
||||
cakePayPurchaseViewModel.confirmsTermsAgreed) {
|
||||
await purchaseCard(context);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> confirmPurchaseFirst(BuildContext context) async {
|
||||
bool isLogged = await cakePayPurchaseViewModel.cakePayService.isLogged();
|
||||
if (!isLogged) {
|
||||
Navigator.of(context).pushNamed(Routes.cakePayWelcomePage);
|
||||
} else {
|
||||
cakePayPurchaseViewModel.isPurchasing = true;
|
||||
await _showconfirmPurchaseFirstAlert(context);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> purchaseCard(BuildContext context) async {
|
||||
bool isLogged = await cakePayPurchaseViewModel.cakePayService.isLogged();
|
||||
if (!isLogged) {
|
||||
|
@ -263,7 +310,9 @@ class CakePayBuyCardDetailPage extends BasePage {
|
|||
} catch (_) {
|
||||
await cakePayPurchaseViewModel.cakePayService.logout();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
cakePayPurchaseViewModel.isPurchasing = false;
|
||||
}
|
||||
|
||||
void _showHowToUseCard(
|
||||
|
@ -428,3 +477,201 @@ class CakePayBuyCardDetailPage extends BasePage {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ThreeCheckboxAlert extends BaseAlertDialog {
|
||||
ThreeCheckboxAlert({
|
||||
required this.alertTitle,
|
||||
required this.leftButtonText,
|
||||
required this.rightButtonText,
|
||||
required this.actionLeftButton,
|
||||
required this.actionRightButton,
|
||||
this.alertBarrierDismissible = true,
|
||||
Key? key,
|
||||
});
|
||||
|
||||
final String alertTitle;
|
||||
final String leftButtonText;
|
||||
final String rightButtonText;
|
||||
final VoidCallback actionLeftButton;
|
||||
final Function(bool, bool, bool) actionRightButton;
|
||||
final bool alertBarrierDismissible;
|
||||
|
||||
bool checkbox1 = false;
|
||||
void toggleCheckbox1() => checkbox1 = !checkbox1;
|
||||
bool checkbox2 = false;
|
||||
void toggleCheckbox2() => checkbox2 = !checkbox2;
|
||||
bool checkbox3 = false;
|
||||
void toggleCheckbox3() => checkbox3 = !checkbox3;
|
||||
|
||||
bool showValidationMessage = true;
|
||||
|
||||
@override
|
||||
String get titleText => alertTitle;
|
||||
|
||||
@override
|
||||
bool get isDividerExists => true;
|
||||
|
||||
@override
|
||||
String get leftActionButtonText => leftButtonText;
|
||||
|
||||
@override
|
||||
String get rightActionButtonText => rightButtonText;
|
||||
|
||||
@override
|
||||
VoidCallback get actionLeft => actionLeftButton;
|
||||
|
||||
@override
|
||||
VoidCallback get actionRight => () {
|
||||
actionRightButton(checkbox1, checkbox2, checkbox3);
|
||||
};
|
||||
|
||||
@override
|
||||
bool get barrierDismissible => alertBarrierDismissible;
|
||||
|
||||
@override
|
||||
Widget content(BuildContext context) {
|
||||
return ThreeCheckboxAlertContent(
|
||||
checkbox1: checkbox1,
|
||||
toggleCheckbox1: toggleCheckbox1,
|
||||
checkbox2: checkbox2,
|
||||
toggleCheckbox2: toggleCheckbox2,
|
||||
checkbox3: checkbox3,
|
||||
toggleCheckbox3: toggleCheckbox3,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ThreeCheckboxAlertContent extends StatefulWidget {
|
||||
ThreeCheckboxAlertContent({
|
||||
required this.checkbox1,
|
||||
required this.toggleCheckbox1,
|
||||
required this.checkbox2,
|
||||
required this.toggleCheckbox2,
|
||||
required this.checkbox3,
|
||||
required this.toggleCheckbox3,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
bool checkbox1;
|
||||
void Function() toggleCheckbox1;
|
||||
bool checkbox2;
|
||||
void Function() toggleCheckbox2;
|
||||
bool checkbox3;
|
||||
void Function() toggleCheckbox3;
|
||||
|
||||
@override
|
||||
_ThreeCheckboxAlertContentState createState() => _ThreeCheckboxAlertContentState(
|
||||
checkbox1: checkbox1,
|
||||
toggleCheckbox1: toggleCheckbox1,
|
||||
checkbox2: checkbox2,
|
||||
toggleCheckbox2: toggleCheckbox2,
|
||||
checkbox3: checkbox3,
|
||||
toggleCheckbox3: toggleCheckbox3,
|
||||
);
|
||||
|
||||
static _ThreeCheckboxAlertContentState? of(BuildContext context) {
|
||||
return context.findAncestorStateOfType<_ThreeCheckboxAlertContentState>();
|
||||
}
|
||||
}
|
||||
|
||||
class _ThreeCheckboxAlertContentState extends State<ThreeCheckboxAlertContent> {
|
||||
_ThreeCheckboxAlertContentState({
|
||||
required this.checkbox1,
|
||||
required this.toggleCheckbox1,
|
||||
required this.checkbox2,
|
||||
required this.toggleCheckbox2,
|
||||
required this.checkbox3,
|
||||
required this.toggleCheckbox3,
|
||||
});
|
||||
|
||||
bool checkbox1;
|
||||
void Function() toggleCheckbox1;
|
||||
bool checkbox2;
|
||||
void Function() toggleCheckbox2;
|
||||
bool checkbox3;
|
||||
void Function() toggleCheckbox3;
|
||||
|
||||
bool showValidationMessage = true;
|
||||
|
||||
bool get areAllCheckboxesChecked => checkbox1 && checkbox2 && checkbox3;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Form(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
StandardCheckbox(
|
||||
value: checkbox1,
|
||||
caption: S.of(context).cakepay_confirm_no_vpn,
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
checkbox1 = value ?? false;
|
||||
toggleCheckbox1();
|
||||
showValidationMessage = !areAllCheckboxesChecked;
|
||||
});
|
||||
},
|
||||
),
|
||||
StandardCheckbox(
|
||||
value: checkbox2,
|
||||
caption: S.of(context).cakepay_confirm_voided_refund,
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
checkbox2 = value ?? false;
|
||||
toggleCheckbox2();
|
||||
showValidationMessage = !areAllCheckboxesChecked;
|
||||
});
|
||||
},
|
||||
),
|
||||
StandardCheckbox(
|
||||
value: checkbox3,
|
||||
caption: S.of(context).cakepay_confirm_terms_agreed,
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
checkbox3 = value ?? false;
|
||||
toggleCheckbox3();
|
||||
showValidationMessage = !areAllCheckboxesChecked;
|
||||
});
|
||||
},
|
||||
),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => launchUrl(
|
||||
Uri.parse("https://cakepay.com/cakepay-web-terms.txt"),
|
||||
mode: LaunchMode.externalApplication,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
S.of(context).settings_terms_and_conditions,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).primaryColor,
|
||||
decoration: TextDecoration.none,
|
||||
height: 1,
|
||||
),
|
||||
softWrap: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (showValidationMessage)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
'Please confirm all checkboxes',
|
||||
style: TextStyle(
|
||||
color: Colors.red,
|
||||
fontSize: 14,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,11 +22,13 @@ class ConnectDevicePageParams {
|
|||
final WalletType walletType;
|
||||
final OnConnectDevice onConnectDevice;
|
||||
final bool allowChangeWallet;
|
||||
final bool isReconnect;
|
||||
|
||||
ConnectDevicePageParams({
|
||||
required this.walletType,
|
||||
required this.onConnectDevice,
|
||||
this.allowChangeWallet = false,
|
||||
this.isReconnect = false,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -34,19 +36,33 @@ class ConnectDevicePage extends BasePage {
|
|||
final WalletType walletType;
|
||||
final OnConnectDevice onConnectDevice;
|
||||
final bool allowChangeWallet;
|
||||
final bool isReconnect;
|
||||
final LedgerViewModel ledgerVM;
|
||||
|
||||
ConnectDevicePage(ConnectDevicePageParams params, this.ledgerVM)
|
||||
: walletType = params.walletType,
|
||||
onConnectDevice = params.onConnectDevice,
|
||||
allowChangeWallet = params.allowChangeWallet;
|
||||
allowChangeWallet = params.allowChangeWallet,
|
||||
isReconnect = params.isReconnect;
|
||||
|
||||
@override
|
||||
String get title => S.current.restore_title_from_hardware_wallet;
|
||||
String get title => isReconnect
|
||||
? S.current.reconnect_your_hardware_wallet
|
||||
: S.current.restore_title_from_hardware_wallet;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) => ConnectDevicePageBody(
|
||||
walletType, onConnectDevice, allowChangeWallet, ledgerVM);
|
||||
Widget? leading(BuildContext context) =>
|
||||
!isReconnect ? super.leading(context) : null;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) => PopScope(
|
||||
canPop: !isReconnect,
|
||||
child: ConnectDevicePageBody(
|
||||
walletType,
|
||||
onConnectDevice,
|
||||
allowChangeWallet,
|
||||
ledgerVM,
|
||||
));
|
||||
}
|
||||
|
||||
class ConnectDevicePageBody extends StatefulWidget {
|
||||
|
@ -75,6 +91,8 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
|
|||
late Timer? _bleStateTimer = null;
|
||||
late StreamSubscription<LedgerDevice>? _bleRefresh = null;
|
||||
|
||||
bool longWait = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
@ -89,6 +107,11 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
|
|||
_usbRefreshTimer =
|
||||
Timer.periodic(Duration(seconds: 1), (_) => _refreshUsbDevices());
|
||||
}
|
||||
|
||||
Future.delayed(Duration(seconds: 10), () {
|
||||
if (widget.ledgerVM.bleIsEnabled && bleDevices.isEmpty)
|
||||
setState(() => longWait = true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -98,6 +121,8 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
|
|||
_bleStateTimer?.cancel();
|
||||
_usbRefreshTimer?.cancel();
|
||||
_bleRefresh?.cancel();
|
||||
|
||||
widget.ledgerVM.stopScanning();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
@ -118,12 +143,14 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
|
|||
Future<void> _refreshBleDevices() async {
|
||||
try {
|
||||
if (widget.ledgerVM.bleIsEnabled) {
|
||||
_bleRefresh = widget.ledgerVM
|
||||
.scanForBleDevices()
|
||||
.listen((device) => setState(() => bleDevices.add(device)))
|
||||
..onError((e) {
|
||||
throw e.toString();
|
||||
});
|
||||
_bleRefresh =
|
||||
widget.ledgerVM.scanForBleDevices().listen((device) => setState(() {
|
||||
bleDevices.add(device);
|
||||
if (longWait) longWait = false;
|
||||
}))
|
||||
..onError((e) {
|
||||
throw e.toString();
|
||||
});
|
||||
_bleRefreshTimer?.cancel();
|
||||
_bleRefreshTimer = null;
|
||||
}
|
||||
|
@ -175,15 +202,21 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
|
|||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
// DeviceTile(
|
||||
// onPressed: () => Navigator.of(context).push(
|
||||
// MaterialPageRoute<void>(
|
||||
// builder: (BuildContext context) => DebugDevicePage(),
|
||||
// ),
|
||||
// ),
|
||||
// title: "Debug Ledger",
|
||||
// leading: imageLedger,
|
||||
// ),
|
||||
Offstage(
|
||||
offstage: !longWait,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
|
||||
child: Text(S.of(context).if_you_dont_see_your_device,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context)
|
||||
.extension<CakeTextTheme>()!
|
||||
.titleColor),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
Observer(
|
||||
builder: (_) => Offstage(
|
||||
offstage: widget.ledgerVM.bleIsEnabled,
|
||||
|
|
|
@ -322,6 +322,7 @@ class _ContactListBodyState extends State<ContactListBody> {
|
|||
? widget.contactListViewModel.contacts
|
||||
: widget.contactListViewModel.contactsToShow;
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
body: Container(
|
||||
child: FilteredList(
|
||||
list: contacts,
|
||||
|
|
|
@ -119,12 +119,7 @@ class CryptoBalanceWidget extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onLongPress: () => dashboardViewModel.balanceViewModel.isReversing =
|
||||
!dashboardViewModel.balanceViewModel.isReversing,
|
||||
onLongPressUp: () => dashboardViewModel.balanceViewModel.isReversing =
|
||||
!dashboardViewModel.balanceViewModel.isReversing,
|
||||
child: SingleChildScrollView(
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
|
@ -326,7 +321,7 @@ class CryptoBalanceWidget extends StatelessWidget {
|
|||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => launchUrl(
|
||||
Uri.parse(
|
||||
"https://guides.cakewallet.com/docs/cryptos/bitcoin/#silent-payments"),
|
||||
"https://docs.cakewallet.com/cryptos/bitcoin#silent-payments"),
|
||||
mode: LaunchMode.externalApplication,
|
||||
),
|
||||
child: Row(
|
||||
|
@ -556,7 +551,6 @@ class CryptoBalanceWidget extends StatelessWidget {
|
|||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -705,44 +699,47 @@ class BalanceRowWidget extends StatelessWidget {
|
|||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
GestureDetector(
|
||||
onTap: () => dashboardViewModel.balanceViewModel.switchBalanceValue(),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: hasAdditionalBalance
|
||||
? () => _showBalanceDescription(
|
||||
context, S.of(context).available_balance_description)
|
||||
: null,
|
||||
child: Column(
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Semantics(
|
||||
hint: 'Double tap to see more information',
|
||||
container: true,
|
||||
child: Text('${availableBalanceLabel}',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context)
|
||||
.extension<BalancePageTheme>()!
|
||||
.labelTextColor,
|
||||
height: 1)),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: hasAdditionalBalance
|
||||
? () => _showBalanceDescription(
|
||||
context, S.of(context).available_balance_description)
|
||||
: null,
|
||||
child: Row(
|
||||
children: [
|
||||
Semantics(
|
||||
hint: 'Double tap to see more information',
|
||||
container: true,
|
||||
child: Text('${availableBalanceLabel}',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context)
|
||||
.extension<BalancePageTheme>()!
|
||||
.labelTextColor,
|
||||
height: 1)),
|
||||
),
|
||||
if (hasAdditionalBalance)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Icon(Icons.help_outline,
|
||||
size: 16,
|
||||
color: Theme.of(context)
|
||||
.extension<BalancePageTheme>()!
|
||||
.labelTextColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (hasAdditionalBalance)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Icon(Icons.help_outline,
|
||||
size: 16,
|
||||
color: Theme.of(context)
|
||||
.extension<BalancePageTheme>()!
|
||||
.labelTextColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 6),
|
||||
AutoSizeText(availableBalance,
|
||||
|
@ -775,9 +772,10 @@ class BalanceRowWidget extends StatelessWidget {
|
|||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<BalancePageTheme>()!.textColor,
|
||||
height: 1)),
|
||||
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(
|
||||
width: min(MediaQuery.of(context).size.width * 0.2, 100),
|
||||
child: Center(
|
||||
|
@ -820,6 +818,7 @@ class BalanceRowWidget extends StatelessWidget {
|
|||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (frozenBalance.isNotEmpty)
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
|
@ -886,7 +885,9 @@ class BalanceRowWidget extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
if (hasAdditionalBalance)
|
||||
Column(
|
||||
GestureDetector(
|
||||
onTap: () => dashboardViewModel.balanceViewModel.switchBalanceValue(),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 24),
|
||||
|
@ -929,12 +930,13 @@ class BalanceRowWidget extends StatelessWidget {
|
|||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (hasSecondAdditionalBalance || hasSecondAvailableBalance) ...[
|
||||
SizedBox(height: 16),
|
||||
SizedBox(height: 10),
|
||||
Container(
|
||||
margin: const EdgeInsets.only(left: 16, right: 16),
|
||||
decoration: BoxDecoration(
|
||||
|
@ -989,7 +991,9 @@ class BalanceRowWidget extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
if (hasSecondAvailableBalance)
|
||||
Row(
|
||||
GestureDetector(
|
||||
onTap: () => dashboardViewModel.balanceViewModel.switchBalanceValue(),
|
||||
child: Row(
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
@ -998,7 +1002,7 @@ class BalanceRowWidget extends StatelessWidget {
|
|||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => launchUrl(
|
||||
Uri.parse(
|
||||
"https://guides.cakewallet.com/docs/cryptos/litecoin/#mweb"),
|
||||
"https://docs.cakewallet.com/cryptos/litecoin.html#mweb"),
|
||||
mode: LaunchMode.externalApplication,
|
||||
),
|
||||
child: Row(
|
||||
|
@ -1061,6 +1065,7 @@ class BalanceRowWidget extends StatelessWidget {
|
|||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -53,7 +53,7 @@ class TransactionsPage extends StatelessWidget {
|
|||
onTap: () {
|
||||
try {
|
||||
final uri = Uri.parse(
|
||||
"https://guides.cakewallet.com/docs/FAQ/why_are_my_funds_not_appearing/");
|
||||
"https://docs.cakewallet.com/faq/funds-not-appearing");
|
||||
launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
} catch (_) {}
|
||||
},
|
||||
|
|
|
@ -347,7 +347,7 @@ class _WalletNameFormState extends State<WalletNameForm> {
|
|||
key: ValueKey('new_wallet_page_confirm_button_key'),
|
||||
onPressed: _confirmForm,
|
||||
text: S.of(context).seed_language_next,
|
||||
color: Colors.green,
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white,
|
||||
isLoading: _walletNewVM.state is IsExecutingState,
|
||||
isDisabled: _walletNewVM.name.isEmpty,
|
||||
|
|
|
@ -40,7 +40,7 @@ class SelectButton extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final backgroundColor = color ?? (isSelected ? Colors.green : Theme.of(context).cardColor);
|
||||
final backgroundColor = color ?? (isSelected ? Theme.of(context).primaryColor : Theme.of(context).cardColor);
|
||||
final effectiveTextColor = textColor ??
|
||||
(isSelected
|
||||
? Theme.of(context).extension<WalletListTheme>()!.restoreWalletButtonTextColor
|
||||
|
|
|
@ -31,7 +31,7 @@ class _HeaderTileState extends State<HeaderTile> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final searchIcon = Image.asset("assets/images/search_icon.png",
|
||||
final searchIcon = Icon( Icons.search,
|
||||
color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor);
|
||||
|
||||
return Container(
|
||||
|
|
|
@ -56,9 +56,8 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
|
|||
}
|
||||
|
||||
if (isMoneroOnly) {
|
||||
// return DeviceConnectionType.supportedConnectionTypes(WalletType.monero, Platform.isIOS)
|
||||
// .isNotEmpty;
|
||||
return false;
|
||||
return DeviceConnectionType.supportedConnectionTypes(WalletType.monero, Platform.isIOS)
|
||||
.isNotEmpty;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
@ -25,5 +25,5 @@ class PreSeedPage extends InfoPage {
|
|||
|
||||
@override
|
||||
void Function(BuildContext) get onPressed =>
|
||||
(BuildContext context) => Navigator.of(context).popAndPushNamed(Routes.seed, arguments: true);
|
||||
(BuildContext context) => Navigator.of(context).pushNamed(Routes.seed, arguments: true);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/screens/seed/seed_verification/seed_verification_step_view.dart';
|
||||
import 'package:cake_wallet/src/screens/seed/seed_verification/seed_verification_success_view.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_seed_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
class SeedVerificationPage extends BasePage {
|
||||
final WalletSeedViewModel walletSeedViewModel;
|
||||
|
||||
SeedVerificationPage(this.walletSeedViewModel);
|
||||
|
||||
@override
|
||||
String? get title => S.current.verify_seed;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return Observer(
|
||||
builder: (context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: walletSeedViewModel.isVerificationComplete
|
||||
? SeedVerificationSuccessView(
|
||||
imageColor: titleColor(context),
|
||||
)
|
||||
: SeedVerificationStepView(
|
||||
walletSeedViewModel: walletSeedViewModel,
|
||||
questionTextColor: titleColor(context),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:cake_wallet/utils/show_bar.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_seed_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
class SeedVerificationStepView extends StatelessWidget {
|
||||
const SeedVerificationStepView({
|
||||
required this.walletSeedViewModel,
|
||||
required this.questionTextColor,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final WalletSeedViewModel walletSeedViewModel;
|
||||
final Color questionTextColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Observer(
|
||||
builder: (context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 48),
|
||||
Align(
|
||||
alignment: Alignment.center,
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: '${S.current.seed_position_question_one} ',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: questionTextColor,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: '${getOrdinal(walletSeedViewModel.currentWordIndex + 1)} ',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: questionTextColor,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: S.current.seed_position_question_two,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: questionTextColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Align(
|
||||
alignment: Alignment.center,
|
||||
child: Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
alignment: WrapAlignment.center,
|
||||
children: walletSeedViewModel.currentOptions.map(
|
||||
(option) {
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
final isCorrectWord = walletSeedViewModel.isChosenWordCorrect(option);
|
||||
final isSecondWrongEntry = walletSeedViewModel.wrongEntries == 2;
|
||||
if (!isCorrectWord) {
|
||||
await showBar<void>(
|
||||
context,
|
||||
isSecondWrongEntry
|
||||
? S.current.incorrect_seed_option_back
|
||||
: S.current.incorrect_seed_option,
|
||||
);
|
||||
|
||||
if (isSecondWrongEntry) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
color: Theme.of(context).cardColor,
|
||||
),
|
||||
child: Text(
|
||||
option,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
String getOrdinal(int number) {
|
||||
// Handle special cases for 11th, 12th, 13th
|
||||
final lastTwoDigits = number % 100;
|
||||
if (lastTwoDigits >= 11 && lastTwoDigits <= 13) {
|
||||
return '${number}th';
|
||||
}
|
||||
|
||||
// Check the last digit for st, nd, rd, or default th
|
||||
final lastDigit = number % 10;
|
||||
switch (lastDigit) {
|
||||
case 1:
|
||||
return '${number}st';
|
||||
case 2:
|
||||
return '${number}nd';
|
||||
case 3:
|
||||
return '${number}rd';
|
||||
default:
|
||||
return '${number}th';
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SeedVerificationSuccessView extends StatelessWidget {
|
||||
const SeedVerificationSuccessView({required this.imageColor, super.key});
|
||||
|
||||
final Color imageColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final image = Image.asset('assets/images/seed_verified.png', color: imageColor);
|
||||
|
||||
return Center(
|
||||
child: Column(
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.3),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1.8,
|
||||
child: image,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
S.current.seed_verified,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 48),
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: '${S.current.seed_verified_subtext} ',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: S.current.seed_display_path,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
Spacer(),
|
||||
PrimaryButton(
|
||||
key: ValueKey('wallet_seed_page_open_wallet_button_key'),
|
||||
onPressed: () {
|
||||
Navigator.of(context).popUntil((route) => route.isFirst);
|
||||
},
|
||||
text: S.current.open_wallet,
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
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/pin_code_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
import 'package:cake_wallet/utils/clipboard_util.dart';
|
||||
|
@ -15,7 +15,8 @@ import 'package:cake_wallet/generated/i18n.dart';
|
|||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_seed_view_model.dart';
|
||||
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
|
||||
|
||||
import '../../../themes/extensions/send_page_theme.dart';
|
||||
|
||||
class WalletSeedPage extends BasePage {
|
||||
WalletSeedPage(this.walletSeedViewModel, {required this.isNewWalletCreated});
|
||||
|
@ -29,62 +30,34 @@ class WalletSeedPage extends BasePage {
|
|||
final bool isNewWalletCreated;
|
||||
final WalletSeedViewModel walletSeedViewModel;
|
||||
|
||||
@override
|
||||
void onClose(BuildContext context) async {
|
||||
if (isNewWalletCreated) {
|
||||
final confirmed = await showPopUp<bool>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithTwoActions(
|
||||
alertDialogKey: ValueKey('wallet_seed_page_seed_alert_dialog_key'),
|
||||
alertRightActionButtonKey:
|
||||
ValueKey('wallet_seed_page_seed_alert_confirm_button_key'),
|
||||
alertLeftActionButtonKey: ValueKey('wallet_seed_page_seed_alert_back_button_key'),
|
||||
alertTitle: S.of(context).seed_alert_title,
|
||||
alertContent: S.of(context).seed_alert_content,
|
||||
leftButtonText: S.of(context).seed_alert_back,
|
||||
rightButtonText: S.of(context).seed_alert_yes,
|
||||
actionLeftButton: () => Navigator.of(context).pop(false),
|
||||
actionRightButton: () => Navigator.of(context).pop(true),
|
||||
);
|
||||
},
|
||||
) ??
|
||||
false;
|
||||
|
||||
if (confirmed) {
|
||||
Navigator.of(context).popUntil((route) => route.isFirst);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget? leading(BuildContext context) => isNewWalletCreated ? null : super.leading(context);
|
||||
|
||||
@override
|
||||
Widget trailing(BuildContext context) {
|
||||
final copyImage = Image.asset(
|
||||
'assets/images/copy_address.png',
|
||||
color: Theme.of(context)
|
||||
.extension<CakeTextTheme>()!
|
||||
.buttonTextColor
|
||||
);
|
||||
|
||||
return isNewWalletCreated
|
||||
? GestureDetector(
|
||||
key: ValueKey('wallet_seed_page_next_button_key'),
|
||||
onTap: () => onClose(context),
|
||||
key: ValueKey('wallet_seed_page_copy_seeds_button_key'),
|
||||
onTap: () {
|
||||
ClipboardUtil.setSensitiveDataToClipboard(
|
||||
ClipboardData(text: walletSeedViewModel.seed),
|
||||
);
|
||||
showBar<void>(context, S.of(context).copied_to_clipboard);
|
||||
},
|
||||
child: Container(
|
||||
width: 100,
|
||||
height: 32,
|
||||
padding: EdgeInsets.all(8),
|
||||
width: 40,
|
||||
alignment: Alignment.center,
|
||||
margin: EdgeInsets.only(left: 10),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||
color: Theme.of(context).cardColor),
|
||||
child: Text(
|
||||
S.of(context).seed_language_next,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor),
|
||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||
color: Theme.of(context).cardColor,
|
||||
),
|
||||
child: copyImage,
|
||||
),
|
||||
)
|
||||
: Offstage();
|
||||
|
@ -92,118 +65,181 @@ class WalletSeedPage extends BasePage {
|
|||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
final image = currentTheme.type == ThemeType.dark ? imageDark : imageLight;
|
||||
|
||||
return WillPopScope(
|
||||
onWillPop: () async => false,
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(24),
|
||||
alignment: Alignment.center,
|
||||
child: ConstrainedBox(
|
||||
constraints:
|
||||
BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
ConstrainedBox(
|
||||
constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.3),
|
||||
child: AspectRatio(aspectRatio: 1, child: image),
|
||||
),
|
||||
Observer(builder: (_) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
key: ValueKey('wallet_seed_page_wallet_name_text_key'),
|
||||
walletSeedViewModel.name,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20, left: 16, right: 16),
|
||||
child: Text(
|
||||
key: ValueKey('wallet_seed_page_wallet_seed_text_key'),
|
||||
walletSeedViewModel.seed,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
color:
|
||||
Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}),
|
||||
Column(
|
||||
children: <Widget>[
|
||||
isNewWalletCreated
|
||||
? Padding(
|
||||
padding: EdgeInsets.only(bottom: 43, left: 43, right: 43),
|
||||
child: Text(
|
||||
key: ValueKey(
|
||||
'wallet_seed_page_wallet_seed_reminder_text_key',
|
||||
),
|
||||
S.of(context).seed_reminder,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Theme.of(context)
|
||||
.extension<TransactionTradeTheme>()!
|
||||
.detailsTitlesColor,
|
||||
),
|
||||
),
|
||||
)
|
||||
: Offstage(),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
onWillPop: () async => false,
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 14, vertical: 8),
|
||||
alignment: Alignment.center,
|
||||
child: ConstrainedBox(
|
||||
constraints:
|
||||
BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Observer(
|
||||
builder: (_) {
|
||||
return Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Flexible(
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(right: 8.0),
|
||||
child: PrimaryButton(
|
||||
key: ValueKey('wallet_seed_page_save_seeds_button_key'),
|
||||
onPressed: () {
|
||||
ShareUtil.share(
|
||||
text: walletSeedViewModel.seed,
|
||||
context: context,
|
||||
);
|
||||
},
|
||||
text: S.of(context).save,
|
||||
color: Colors.green,
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: currentTheme.type == ThemeType.dark
|
||||
? Color.fromRGBO(132, 110, 64, 1)
|
||||
: Color.fromRGBO(194, 165, 94, 1),
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
border: Border.all(
|
||||
color: currentTheme.type == ThemeType.dark
|
||||
? Color.fromRGBO(177, 147, 41, 1)
|
||||
: Color.fromRGBO(125, 122, 15, 1),
|
||||
width: 2.0,
|
||||
)),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.warning_amber_rounded,
|
||||
size: 64,
|
||||
color: Colors.white.withOpacity(0.75),
|
||||
),
|
||||
SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
S.current.cake_seeds_save_disclaimer,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: currentTheme.type == ThemeType.dark
|
||||
? Colors.white.withOpacity(0.75)
|
||||
: Colors.white.withOpacity(0.85),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
Text(
|
||||
key: ValueKey('wallet_seed_page_wallet_name_text_key'),
|
||||
walletSeedViewModel.name,
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
Expanded(
|
||||
child: GridView.builder(
|
||||
itemCount: walletSeedViewModel.seedSplit.length,
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 3,
|
||||
childAspectRatio: 2.8,
|
||||
mainAxisSpacing: 8.0,
|
||||
crossAxisSpacing: 8.0,
|
||||
),
|
||||
itemBuilder: (context, index) {
|
||||
final item = walletSeedViewModel.seedSplit[index];
|
||||
final numberCount = index + 1;
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
color: Theme.of(context).cardColor,
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
child: Text(
|
||||
//maxLines: 1,
|
||||
numberCount.toString(),
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
height: 1,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: Theme.of(context)
|
||||
.extension<CakeTextTheme>()!
|
||||
.buttonTextColor
|
||||
.withOpacity(0.5)),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'${item[0].toLowerCase()}${item.substring(1)}',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
height: 0.8,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: Theme.of(context)
|
||||
.extension<CakeTextTheme>()!
|
||||
.buttonTextColor),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Column(
|
||||
children: <Widget>[
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: <Widget>[
|
||||
Flexible(
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(right: 8.0, top: 8.0),
|
||||
child: PrimaryButton(
|
||||
key: ValueKey('wallet_seed_page_save_seeds_button_key'),
|
||||
onPressed: () {
|
||||
ShareUtil.share(
|
||||
text: walletSeedViewModel.seed,
|
||||
context: context,
|
||||
);
|
||||
},
|
||||
text: S.of(context).save,
|
||||
color: Theme.of(context).cardColor,
|
||||
textColor: currentTheme.type == ThemeType.dark
|
||||
? Theme.of(context).extension<DashboardPageTheme>()!.textColor
|
||||
: Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(left: 8.0, top: 8.0),
|
||||
child: Builder(
|
||||
builder: (context) => PrimaryButton(
|
||||
key: ValueKey('wallet_seed_page_verify_seed_button_key'),
|
||||
onPressed: () =>
|
||||
Navigator.pushNamed(context, Routes.walletSeedVerificationPage),
|
||||
text: S.current.verify_seed,
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(left: 8.0),
|
||||
child: Builder(
|
||||
builder: (context) => PrimaryButton(
|
||||
key: ValueKey('wallet_seed_page_copy_seeds_button_key'),
|
||||
onPressed: () {
|
||||
ClipboardUtil.setSensitiveDataToClipboard(
|
||||
ClipboardData(text: walletSeedViewModel.seed),
|
||||
);
|
||||
showBar<void>(context, S.of(context).copied_to_clipboard);
|
||||
},
|
||||
text: S.of(context).copy,
|
||||
color: Theme.of(context).extension<PinCodeTheme>()!.indicatorsColor,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
));
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -500,95 +500,6 @@ class SendPage extends BasePage {
|
|||
actionRightButton: () async {
|
||||
Navigator.of(_dialogContext).pop();
|
||||
sendViewModel.commitTransaction(context);
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext _dialogContext) {
|
||||
return Observer(builder: (_) {
|
||||
final state = sendViewModel.state;
|
||||
|
||||
if (state is FailureState) {
|
||||
Navigator.of(_dialogContext).pop();
|
||||
}
|
||||
|
||||
if (state is TransactionCommitted) {
|
||||
newContactAddress =
|
||||
newContactAddress ?? sendViewModel.newContactAddress();
|
||||
|
||||
if (sendViewModel.coinTypeToSpendFrom != UnspentCoinType.any) {
|
||||
newContactAddress = null;
|
||||
}
|
||||
|
||||
final successMessage = S.of(_dialogContext).send_success(
|
||||
sendViewModel.selectedCryptoCurrency.toString());
|
||||
|
||||
final waitMessage = sendViewModel.walletType == WalletType.solana
|
||||
? '. ${S.of(_dialogContext).waitFewSecondForTxUpdate}'
|
||||
: '';
|
||||
|
||||
final newContactMessage = newContactAddress != null
|
||||
? '\n${S.of(_dialogContext).add_contact_to_address_book}'
|
||||
: '';
|
||||
|
||||
String alertContent =
|
||||
"$successMessage$waitMessage$newContactMessage";
|
||||
|
||||
if (newContactAddress != null) {
|
||||
return AlertWithTwoActions(
|
||||
alertDialogKey: ValueKey('send_page_sent_dialog_key'),
|
||||
alertTitle: '',
|
||||
alertContent: alertContent,
|
||||
rightButtonText: S.of(_dialogContext).add_contact,
|
||||
leftButtonText: S.of(_dialogContext).ignor,
|
||||
alertLeftActionButtonKey:
|
||||
ValueKey('send_page_sent_dialog_ignore_button_key'),
|
||||
alertRightActionButtonKey: ValueKey(
|
||||
'send_page_sent_dialog_add_contact_button_key'),
|
||||
actionRightButton: () {
|
||||
Navigator.of(_dialogContext).pop();
|
||||
RequestReviewHandler.requestReview();
|
||||
Navigator.of(context).pushNamed(
|
||||
Routes.addressBookAddContact,
|
||||
arguments: newContactAddress);
|
||||
newContactAddress = null;
|
||||
},
|
||||
actionLeftButton: () {
|
||||
Navigator.of(_dialogContext).pop();
|
||||
RequestReviewHandler.requestReview();
|
||||
newContactAddress = null;
|
||||
});
|
||||
} else {
|
||||
if (initialPaymentRequest?.callbackMessage?.isNotEmpty ??
|
||||
false) {
|
||||
alertContent = initialPaymentRequest!.callbackMessage!;
|
||||
}
|
||||
return AlertWithOneAction(
|
||||
alertTitle: '',
|
||||
alertContent: alertContent,
|
||||
buttonText: S.of(_dialogContext).ok,
|
||||
buttonAction: () {
|
||||
Navigator.of(_dialogContext).pop();
|
||||
RequestReviewHandler.requestReview();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return Offstage();
|
||||
});
|
||||
});
|
||||
if (state is TransactionCommitted) {
|
||||
if (initialPaymentRequest?.callbackUrl?.isNotEmpty ?? false) {
|
||||
// wait a second so it's not as jarring:
|
||||
await Future.delayed(Duration(seconds: 1));
|
||||
try {
|
||||
launchUrl(
|
||||
Uri.parse(initialPaymentRequest!.callbackUrl!),
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
} catch (e) {
|
||||
printV(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
actionLeftButton: () => Navigator.of(_dialogContext).pop());
|
||||
});
|
||||
|
@ -597,7 +508,64 @@ class SendPage extends BasePage {
|
|||
}
|
||||
|
||||
if (state is TransactionCommitted) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
|
||||
final successMessage = S.of(context).send_success(
|
||||
sendViewModel.selectedCryptoCurrency.toString());
|
||||
|
||||
final waitMessage = sendViewModel.walletType == WalletType.solana
|
||||
? '. ${S.of(context).waitFewSecondForTxUpdate}'
|
||||
: '';
|
||||
|
||||
String alertContent = "$successMessage$waitMessage";
|
||||
|
||||
await Navigator.of(context).pushNamed(
|
||||
Routes.transactionSuccessPage,
|
||||
arguments: alertContent
|
||||
);
|
||||
|
||||
newContactAddress = newContactAddress ?? sendViewModel.newContactAddress();
|
||||
if (sendViewModel.coinTypeToSpendFrom != UnspentCoinType.any) newContactAddress = null;
|
||||
|
||||
if (newContactAddress != null && sendViewModel.showAddressBookPopup) {
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext _dialogContext) => AlertWithTwoActions(
|
||||
alertDialogKey: ValueKey('send_page_sent_dialog_key'),
|
||||
alertTitle: '',
|
||||
alertContent: S.of(_dialogContext).add_contact_to_address_book,
|
||||
rightButtonText: S.of(_dialogContext).add_contact,
|
||||
leftButtonText: S.of(_dialogContext).ignor,
|
||||
alertLeftActionButtonKey: ValueKey('send_page_sent_dialog_ignore_button_key'),
|
||||
alertRightActionButtonKey:
|
||||
ValueKey('send_page_sent_dialog_add_contact_button_key'),
|
||||
actionRightButton: () {
|
||||
Navigator.of(_dialogContext).pop();
|
||||
RequestReviewHandler.requestReview();
|
||||
Navigator.of(context)
|
||||
.pushNamed(Routes.addressBookAddContact, arguments: newContactAddress);
|
||||
newContactAddress = null;
|
||||
},
|
||||
actionLeftButton: () {
|
||||
Navigator.of(_dialogContext).pop();
|
||||
RequestReviewHandler.requestReview();
|
||||
newContactAddress = null;
|
||||
}));
|
||||
}
|
||||
|
||||
if (initialPaymentRequest?.callbackUrl?.isNotEmpty ?? false) {
|
||||
// wait a second so it's not as jarring:
|
||||
await Future.delayed(Duration(seconds: 1));
|
||||
try {
|
||||
launchUrl(
|
||||
Uri.parse(initialPaymentRequest!.callbackUrl!),
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
} catch (e) {
|
||||
printV(e);
|
||||
}
|
||||
}
|
||||
|
||||
sendViewModel.clearOutputs();
|
||||
});
|
||||
}
|
||||
|
@ -612,7 +580,10 @@ class SendPage extends BasePage {
|
|||
alertTitle: S.of(context).proceed_on_device,
|
||||
alertContent: S.of(context).proceed_on_device_description,
|
||||
buttonText: S.of(context).cancel,
|
||||
buttonAction: () => Navigator.of(context).pop());
|
||||
buttonAction: () {
|
||||
sendViewModel.state = InitialExecutionState();
|
||||
Navigator.of(context).pop();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
32
lib/src/screens/send/transaction_success_info_page.dart
Normal file
32
lib/src/screens/send/transaction_success_info_page.dart
Normal file
|
@ -0,0 +1,32 @@
|
|||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/screens/Info_page.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
class TransactionSuccessPage extends InfoPage {
|
||||
TransactionSuccessPage({required this.content})
|
||||
: super(
|
||||
imageLightPath: 'assets/images/birthday_cake.png',
|
||||
imageDarkPath: 'assets/images/birthday_cake.png',
|
||||
);
|
||||
|
||||
final String content;
|
||||
|
||||
@override
|
||||
bool get onWillPop => false;
|
||||
|
||||
@override
|
||||
String get pageTitle => 'Transaction Sent Successfully';
|
||||
|
||||
@override
|
||||
String get pageDescription => content;
|
||||
|
||||
@override
|
||||
String get buttonText => S.current.ok;
|
||||
|
||||
@override
|
||||
Key? get buttonKey => ValueKey('transaction_success_info_page_button_key');
|
||||
|
||||
@override
|
||||
void Function(BuildContext) get onPressed =>
|
||||
(BuildContext context) => Navigator.of(context).pop();
|
||||
}
|
|
@ -6,6 +6,7 @@ import 'package:cake_wallet/src/screens/base_page.dart';
|
|||
import 'package:cake_wallet/src/screens/settings/widgets/setting_priority_picker_cell.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';
|
||||
import 'package:cake_wallet/src/screens/settings/widgets/settings_version_cell.dart';
|
||||
import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
|
@ -62,6 +63,13 @@ class OtherSettingsPage extends BasePage {
|
|||
handler: (BuildContext context) =>
|
||||
Navigator.of(context).pushNamed(Routes.readDisclaimer),
|
||||
),
|
||||
SettingsSwitcherCell(
|
||||
title: S.of(context).show_address_book_popup,
|
||||
value: _otherSettingsViewModel.showAddressBookPopup,
|
||||
onValueChange: (_, bool value) {
|
||||
_otherSettingsViewModel.setShowAddressBookPopup(value);
|
||||
},
|
||||
),
|
||||
Spacer(),
|
||||
SettingsVersionCell(
|
||||
title: S.of(context).version(_otherSettingsViewModel.currentVersion)),
|
||||
|
|
|
@ -20,7 +20,7 @@ class Setup2FAPage extends BasePage {
|
|||
Widget body(BuildContext context) {
|
||||
final cake2FAGuideTitle = 'Cake 2FA Guide';
|
||||
final cake2FAGuideUri =
|
||||
Uri.parse('https://guides.cakewallet.com/docs/advanced-features/authentication');
|
||||
Uri.parse('https://docs.cakewallet.com/features/advanced/authentication/');
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
|
|
|
@ -198,8 +198,9 @@ class TOTPEnterCode extends BasePage {
|
|||
},
|
||||
);
|
||||
if (isForSetup && result) {
|
||||
Navigator.pushReplacementNamed(
|
||||
context, Routes.modify2FAPage);
|
||||
if (context.mounted) {
|
||||
Navigator.pushReplacementNamed(context, Routes.modify2FAPage);
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
|
|
|
@ -6,7 +6,6 @@ import 'package:cake_wallet/src/widgets/standard_list.dart';
|
|||
import 'package:cake_wallet/generated/i18n.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';
|
||||
import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart';
|
||||
|
@ -30,7 +29,7 @@ class Setup2FAQRPage extends BasePage {
|
|||
width: 16,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor);
|
||||
final cake2FAHowToUseUrl = Uri.parse(
|
||||
'https://guides.cakewallet.com/docs/advanced-features/authentication/#enabling-cake-2fa');
|
||||
'https://docs.cakewallet.com/features/advanced/authentication/#enabling-cake-2fa');
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Column(
|
||||
|
|
|
@ -70,7 +70,7 @@ class SupportPage extends BasePage {
|
|||
),
|
||||
title: S.of(context).support_title_guides,
|
||||
description: S.of(context).support_description_guides,
|
||||
onPressed: () => _launchUrl(supportViewModel.guidesUrl),
|
||||
onPressed: () => _launchUrl(supportViewModel.docsUrl),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
|
|
|
@ -8,7 +8,8 @@ class TradeDetailsListCardItem extends StandartListItem {
|
|||
{required this.id,
|
||||
required this.createdAt,
|
||||
required this.pair,
|
||||
required this.onTap})
|
||||
required this.onTap,
|
||||
this.extraId})
|
||||
: super(title: '', value: '');
|
||||
|
||||
factory TradeDetailsListCardItem.tradeDetails(
|
||||
|
@ -16,9 +17,19 @@ class TradeDetailsListCardItem extends StandartListItem {
|
|||
required String createdAt,
|
||||
required CryptoCurrency from,
|
||||
required CryptoCurrency to,
|
||||
required void Function(BuildContext) onTap}) {
|
||||
required void Function(BuildContext) onTap,
|
||||
String? extraId}) {
|
||||
|
||||
|
||||
final extraIdTitle = from == CryptoCurrency.xrp
|
||||
? S.current.destination_tag
|
||||
: from == CryptoCurrency.xlm
|
||||
? S.current.memo
|
||||
: S.current.extra_id;
|
||||
|
||||
return TradeDetailsListCardItem(
|
||||
id: '${S.current.trade_details_id} ${formatAsText(id)}',
|
||||
extraId: extraId != null ? '$extraIdTitle $extraId' : null,
|
||||
createdAt: formatAsText(createdAt),
|
||||
pair: '${formatAsText(from)} → ${formatAsText(to)}',
|
||||
onTap: onTap);
|
||||
|
@ -27,6 +38,7 @@ class TradeDetailsListCardItem extends StandartListItem {
|
|||
final String id;
|
||||
final String createdAt;
|
||||
final String pair;
|
||||
final String? extraId;
|
||||
final void Function(BuildContext) onTap;
|
||||
|
||||
static String formatAsText<T>(T value) => value?.toString() ?? '';
|
||||
|
|
|
@ -69,6 +69,7 @@ class TradeDetailsPageBodyState extends State<TradeDetailsPageBody> {
|
|||
if (item is TradeDetailsListCardItem)
|
||||
return TradeDetailsStandardListCard(
|
||||
id: item.id,
|
||||
extraId: item.extraId,
|
||||
create: item.createdAt,
|
||||
pair: item.pair,
|
||||
currentTheme: tradeDetailsViewModel.settingsStore.currentTheme.type,
|
||||
|
|
|
@ -154,7 +154,7 @@ class UnspentCoinsListFormState extends State<UnspentCoinsListForm> {
|
|||
SizedBox(height: 15),
|
||||
Expanded(
|
||||
child: unspentCoinsListViewModel.items.isEmpty
|
||||
? Center(child: Text('No unspent coins available\ntry to reconnect',textAlign: TextAlign.center))
|
||||
? Center(child: Text('No unspent coins available',textAlign: TextAlign.center))
|
||||
: ListView.separated(
|
||||
itemCount: unspentCoinsListViewModel.items.length,
|
||||
separatorBuilder: (_, __) => SizedBox(height: 15),
|
||||
|
|
|
@ -84,6 +84,7 @@ class WalletKeysPage extends BasePage {
|
|||
child: Observer(
|
||||
builder: (_) {
|
||||
return ListView.separated(
|
||||
key: ValueKey('wallet_keys_page_credentials_list_view_key'),
|
||||
separatorBuilder: (context, index) => Container(
|
||||
height: 1,
|
||||
padding: EdgeInsets.only(left: 24),
|
||||
|
|
|
@ -334,6 +334,26 @@ class WalletListBodyState extends State<WalletListBody> {
|
|||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
PrimaryImageButton(
|
||||
key: ValueKey('wallet_list_page_restore_wallet_button_key'),
|
||||
onPressed: () {
|
||||
if (widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets) {
|
||||
widget.authService.authenticateAction(
|
||||
context,
|
||||
route: Routes.restoreOptions,
|
||||
arguments: false,
|
||||
conditionToDetermineIfToUse2FA: widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
|
||||
);
|
||||
} else {
|
||||
Navigator.of(context).pushNamed(Routes.restoreOptions, arguments: false);
|
||||
}
|
||||
},
|
||||
image: restoreWalletImage,
|
||||
text: S.of(context).wallet_list_restore_wallet,
|
||||
color: Theme.of(context).cardColor,
|
||||
textColor: Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor,
|
||||
),
|
||||
SizedBox(height: 10.0),
|
||||
PrimaryImageButton(
|
||||
key: ValueKey('wallet_list_page_create_new_wallet_button_key'),
|
||||
onPressed: () {
|
||||
|
@ -373,26 +393,6 @@ class WalletListBodyState extends State<WalletListBody> {
|
|||
color: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
SizedBox(height: 10.0),
|
||||
PrimaryImageButton(
|
||||
key: ValueKey('wallet_list_page_restore_wallet_button_key'),
|
||||
onPressed: () {
|
||||
if (widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets) {
|
||||
widget.authService.authenticateAction(
|
||||
context,
|
||||
route: Routes.restoreOptions,
|
||||
arguments: false,
|
||||
conditionToDetermineIfToUse2FA: widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
|
||||
);
|
||||
} else {
|
||||
Navigator.of(context).pushNamed(Routes.restoreOptions, arguments: false);
|
||||
}
|
||||
},
|
||||
image: restoreWalletImage,
|
||||
text: S.of(context).wallet_list_restore_wallet,
|
||||
color: Theme.of(context).cardColor,
|
||||
textColor: Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -422,8 +422,9 @@ class WalletListBodyState extends State<WalletListBody> {
|
|||
if (!isAuthenticatedSuccessfully) return;
|
||||
|
||||
try {
|
||||
if (widget.walletListViewModel
|
||||
.requireHardwareWalletConnection(wallet)) {
|
||||
final requireHardwareWalletConnection = widget.walletListViewModel
|
||||
.requireHardwareWalletConnection(wallet);
|
||||
if (requireHardwareWalletConnection) {
|
||||
await Navigator.of(context).pushNamed(
|
||||
Routes.connectDevices,
|
||||
arguments: ConnectDevicePageParams(
|
||||
|
@ -445,8 +446,6 @@ class WalletListBodyState extends State<WalletListBody> {
|
|||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
changeProcessText(
|
||||
S.of(context).wallet_list_loading_wallet(wallet.name));
|
||||
await widget.walletListViewModel.loadWallet(wallet);
|
||||
|
@ -456,6 +455,9 @@ class WalletListBodyState extends State<WalletListBody> {
|
|||
if (responsiveLayoutUtil.shouldRenderMobileUI) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (this.mounted) {
|
||||
if (requireHardwareWalletConnection) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
widget.onWalletLoaded.call(context);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -17,13 +17,14 @@ class SearchBarWidget extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextFormField(
|
||||
key: ValueKey('search_bar_widget_key'),
|
||||
controller: searchController,
|
||||
style: TextStyle(color: Theme.of(context).extension<PickerTheme>()!.searchHintColor),
|
||||
decoration: InputDecoration(
|
||||
hintText: hintText ?? S.of(context).search,
|
||||
hintStyle: TextStyle(color: Theme.of(context).extension<PickerTheme>()!.searchHintColor),
|
||||
prefixIcon: Image.asset("assets/images/search_icon.png",
|
||||
color: Theme.of(context).extension<PickerTheme>()!.searchIconColor),
|
||||
prefixIcon: Icon( Icons.search,
|
||||
color: Theme.of(context).primaryColor),
|
||||
filled: true,
|
||||
fillColor: Theme.of(context).extension<PickerTheme>()!.searchBackgroundFillColor,
|
||||
alignLabelWithHint: false,
|
||||
|
|
|
@ -26,7 +26,9 @@ class StandardCheckbox extends StatelessWidget {
|
|||
], begin: Alignment.centerLeft, end: Alignment.centerRight);
|
||||
|
||||
final boxBorder = Border.all(
|
||||
color: borderColor ?? Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor, width: 1.0);
|
||||
color: borderColor ?? Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor,
|
||||
width: 1.0,
|
||||
);
|
||||
|
||||
final checkedBoxDecoration = BoxDecoration(
|
||||
gradient: gradientBackground ? baseGradient : null,
|
||||
|
@ -41,6 +43,7 @@ class StandardCheckbox extends StatelessWidget {
|
|||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
height: 24.0,
|
||||
|
@ -55,13 +58,22 @@ class StandardCheckbox extends StatelessWidget {
|
|||
: Offstage(),
|
||||
),
|
||||
if (caption.isNotEmpty)
|
||||
Padding(
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: 10),
|
||||
child: Text(
|
||||
caption,
|
||||
softWrap: true,
|
||||
style: TextStyle(
|
||||
fontSize: 16.0, color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||
))
|
||||
fontSize: 16.0,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
decoration: TextDecoration.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
@ -6,12 +6,14 @@ import 'package:cake_wallet/themes/theme_base.dart';
|
|||
class TradeDetailsStandardListCard extends StatelessWidget {
|
||||
TradeDetailsStandardListCard(
|
||||
{required this.id,
|
||||
this.extraId,
|
||||
required this.create,
|
||||
required this.pair,
|
||||
required this.onTap,
|
||||
required this.currentTheme});
|
||||
|
||||
final String id;
|
||||
final String? extraId;
|
||||
final String create;
|
||||
final String pair;
|
||||
final ThemeType currentTheme;
|
||||
|
@ -57,6 +59,16 @@ class TradeDetailsStandardListCard extends StatelessWidget {
|
|||
SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (extraId != null && extraId!.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: Text(extraId!,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: textColor)),
|
||||
),
|
||||
Text(create,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
|
|
|
@ -4,7 +4,7 @@ part 'authentication_store.g.dart';
|
|||
|
||||
class AuthenticationStore = AuthenticationStoreBase with _$AuthenticationStore;
|
||||
|
||||
enum AuthenticationState { uninitialized, installed, allowed, _reset }
|
||||
enum AuthenticationState { uninitialized, installed, allowed, allowedCreate, _reset }
|
||||
|
||||
abstract class AuthenticationStoreBase with Store {
|
||||
AuthenticationStoreBase() : state = AuthenticationState.uninitialized;
|
||||
|
@ -23,4 +23,10 @@ abstract class AuthenticationStoreBase with Store {
|
|||
state = AuthenticationState._reset;
|
||||
state = AuthenticationState.allowed;
|
||||
}
|
||||
|
||||
@action
|
||||
void allowedCreate() {
|
||||
state = AuthenticationState._reset;
|
||||
state = AuthenticationState.allowedCreate;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,6 +54,7 @@ abstract class SettingsStoreBase with Store {
|
|||
required BackgroundTasks backgroundTasks,
|
||||
required SharedPreferences sharedPreferences,
|
||||
required bool initialShouldShowMarketPlaceInDashboard,
|
||||
required bool initialShowAddressBookPopupEnabled,
|
||||
required FiatCurrency initialFiatCurrency,
|
||||
required BalanceDisplayMode initialBalanceDisplayMode,
|
||||
required bool initialSaveRecipientAddress,
|
||||
|
@ -158,6 +159,7 @@ abstract class SettingsStoreBase with Store {
|
|||
walletListAscending = initialWalletListAscending,
|
||||
contactListAscending = initialContactListAscending,
|
||||
shouldShowMarketPlaceInDashboard = initialShouldShowMarketPlaceInDashboard,
|
||||
showAddressBookPopupEnabled = initialShowAddressBookPopupEnabled,
|
||||
exchangeStatus = initialExchangeStatus,
|
||||
currentTheme = initialTheme,
|
||||
pinCodeLength = initialPinLength,
|
||||
|
@ -355,6 +357,11 @@ abstract class SettingsStoreBase with Store {
|
|||
(bool value) =>
|
||||
sharedPreferences.setBool(PreferencesKey.shouldShowMarketPlaceInDashboard, value));
|
||||
|
||||
reaction(
|
||||
(_) => showAddressBookPopupEnabled,
|
||||
(bool value) =>
|
||||
sharedPreferences.setBool(PreferencesKey.showAddressBookPopupEnabled, value));
|
||||
|
||||
reaction((_) => pinCodeLength,
|
||||
(int pinLength) => sharedPreferences.setInt(PreferencesKey.currentPinLength, pinLength));
|
||||
|
||||
|
@ -612,6 +619,9 @@ abstract class SettingsStoreBase with Store {
|
|||
@observable
|
||||
bool shouldShowMarketPlaceInDashboard;
|
||||
|
||||
@observable
|
||||
bool showAddressBookPopupEnabled;
|
||||
|
||||
@observable
|
||||
ObservableList<ActionListDisplayMode> actionlistDisplayMode;
|
||||
|
||||
|
@ -926,6 +936,8 @@ abstract class SettingsStoreBase with Store {
|
|||
final tokenTrialNumber = sharedPreferences.getInt(PreferencesKey.failedTotpTokenTrials) ?? 0;
|
||||
final shouldShowMarketPlaceInDashboard =
|
||||
sharedPreferences.getBool(PreferencesKey.shouldShowMarketPlaceInDashboard) ?? true;
|
||||
final showAddressBookPopupEnabled =
|
||||
sharedPreferences.getBool(PreferencesKey.showAddressBookPopupEnabled) ?? true;
|
||||
final exchangeStatus = ExchangeApiMode.deserialize(
|
||||
raw: sharedPreferences.getInt(PreferencesKey.exchangeStatusKey) ??
|
||||
ExchangeApiMode.enabled.raw);
|
||||
|
@ -1196,6 +1208,7 @@ abstract class SettingsStoreBase with Store {
|
|||
secureStorage: secureStorage,
|
||||
sharedPreferences: sharedPreferences,
|
||||
initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard,
|
||||
initialShowAddressBookPopupEnabled: showAddressBookPopupEnabled,
|
||||
nodes: nodes,
|
||||
powNodes: powNodes,
|
||||
appVersion: packageInfo.version,
|
||||
|
@ -1373,6 +1386,9 @@ abstract class SettingsStoreBase with Store {
|
|||
shouldShowMarketPlaceInDashboard =
|
||||
sharedPreferences.getBool(PreferencesKey.shouldShowMarketPlaceInDashboard) ??
|
||||
shouldShowMarketPlaceInDashboard;
|
||||
showAddressBookPopupEnabled =
|
||||
sharedPreferences.getBool(PreferencesKey.showAddressBookPopupEnabled) ??
|
||||
showAddressBookPopupEnabled;
|
||||
exchangeStatus = ExchangeApiMode.deserialize(
|
||||
raw: sharedPreferences.getInt(PreferencesKey.exchangeStatusKey) ??
|
||||
ExchangeApiMode.enabled.raw);
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/main.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cake_wallet/utils/show_bar.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cw_core/root_dir.dart';
|
||||
|
@ -29,9 +31,21 @@ class ExceptionHandler {
|
|||
_file = File('${appDocDir.path}/error.txt');
|
||||
}
|
||||
|
||||
String? walletType;
|
||||
CustomTrace? programInfo;
|
||||
|
||||
try {
|
||||
walletType = getIt.get<AppStore>().wallet?.type.name;
|
||||
|
||||
programInfo = CustomTrace(stackTrace ?? StackTrace.current);
|
||||
} catch (_) {}
|
||||
|
||||
final exception = {
|
||||
"${DateTime.now()}": {
|
||||
"Error": "$error\n\n",
|
||||
"WalletType": "$walletType\n\n",
|
||||
"VerboseLog":
|
||||
"${programInfo?.fileName}#${programInfo?.lineNumber}:${programInfo?.columnNumber} ${programInfo?.callerFunctionName}\n\n",
|
||||
"Library": "$library\n\n",
|
||||
"StackTrace": stackTrace.toString(),
|
||||
}
|
||||
|
|
|
@ -3,4 +3,5 @@ class FeatureFlag {
|
|||
static const bool isExolixEnabled = true;
|
||||
static const bool isInAppTorEnabled = false;
|
||||
static const bool isBackgroundSyncEnabled = false;
|
||||
static const int verificationWordsCount = 2;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue