Merge remote-tracking branch 'origin/main' into electrum-sp-refactors

This commit is contained in:
Rafael Saes 2024-12-16 10:48:27 -03:00
commit 6cbb4c60d6
142 changed files with 3179 additions and 671 deletions

View 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

View file

@ -62,6 +62,7 @@ jobs:
/opt/android/cake_wallet/cw_haven/android/.cxx /opt/android/cake_wallet/cw_haven/android/.cxx
/opt/android/cake_wallet/scripts/monero_c/release /opt/android/cake_wallet/scripts/monero_c/release
key: ${{ hashFiles('**/prepare_moneroc.sh' ,'**/build_monero_all.sh' ,'**/cache_dependencies.yml') }} key: ${{ hashFiles('**/prepare_moneroc.sh' ,'**/build_monero_all.sh' ,'**/cache_dependencies.yml') }}
- if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }} - if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }}
name: Generate Externals name: Generate Externals
run: | run: |
@ -73,8 +74,8 @@ jobs:
id: cache-keystore id: cache-keystore
uses: actions/cache@v3 uses: actions/cache@v3
with: with:
path: /opt/android/cake_wallet/android/app/key.jks path: /opt/android/cake_wallet/android/app
key: $STORE_PASS key: keystore
- if: ${{ steps.cache-keystore.outputs.cache-hit != 'true' }} - if: ${{ steps.cache-keystore.outputs.cache-hit != 'true' }}
name: Generate KeyStore name: Generate KeyStore

View file

@ -61,14 +61,32 @@ jobs:
sudo apt update sudo apt update
sudo apt-get install -y curl unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake clang 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: | run: |
sudo mkdir -p /opt/android sudo mkdir -p /opt/android
sudo chown $USER /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 cd /opt/android
-y curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -y curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
cargo install cargo-ndk cargo install cargo-ndk
git clone https://github.com/cake-tech/cake_wallet.git --branch ${{ env.BRANCH_NAME }}
cd cake_wallet/scripts/android/ cd cake_wallet/scripts/android/
./install_ndk.sh ./install_ndk.sh
source ./app_env.sh cakewallet source ./app_env.sh cakewallet
@ -115,19 +133,6 @@ jobs:
cd /opt/android/cake_wallet/scripts/android/ cd /opt/android/cake_wallet/scripts/android/
./build_mwebd.sh --dont-install ./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 - name: Generate key properties
run: | run: |
cd /opt/android/cake_wallet cd /opt/android/cake_wallet
@ -183,6 +188,7 @@ jobs:
echo "const polygonScanApiKey = '${{ secrets.POLYGON_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 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 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 chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart
echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> 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 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 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 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 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 letsExchangeBearerToken = '${{ secrets.LETS_EXCHANGE_TOKEN }}';" >> lib/.secrets.g.dart
echo "const letsExchangeAffiliateId = '${{ secrets.LETS_EXCHANGE_AFFILIATE_ID }}';" >> 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 stealthExBearerToken = '${{ secrets.STEALTH_EX_BEARER_TOKEN }}';" >> lib/.secrets.g.dart

View file

@ -165,6 +165,7 @@ jobs:
echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> 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 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 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 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 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 cakePayApiKey = '${{ secrets.CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart

View file

@ -6,5 +6,7 @@
uri: rpc.flashbots.net uri: rpc.flashbots.net
- -
uri: eth-mainnet.public.blastapi.io uri: eth-mainnet.public.blastapi.io
-
uri: eth.nownodes.io
- -
uri: ethereum.publicnode.com uri: ethereum.publicnode.com

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View file

@ -4,3 +4,5 @@
uri: polygon-bor.publicnode.com uri: polygon-bor.publicnode.com
- -
uri: polygon.llamarpc.com uri: polygon.llamarpc.com
-
uri: matic.nownodes.io

View file

@ -227,9 +227,9 @@ class ElectrumClient {
return []; return [];
}); });
Future<List<Map<String, dynamic>>> getListUnspent(String scriptHash) => Future<List<Map<String, dynamic>>> getListUnspent(String scriptHash) async {
call(method: 'blockchain.scripthash.listunspent', params: [scriptHash]) final result = await call(method: 'blockchain.scripthash.listunspent', params: [scriptHash]);
.then((dynamic result) {
if (result is List) { if (result is List) {
return result.map((dynamic val) { return result.map((dynamic val) {
if (val is Map<String, dynamic>) { if (val is Map<String, dynamic>) {
@ -241,7 +241,7 @@ class ElectrumClient {
} }
return []; return [];
}); }
Future<List<Map<String, dynamic>>> getMempool(String scriptHash) => Future<List<Map<String, dynamic>>> getMempool(String scriptHash) =>
call(method: 'blockchain.scripthash.get_mempool', params: [scriptHash]) call(method: 'blockchain.scripthash.get_mempool', params: [scriptHash])

View file

@ -28,7 +28,10 @@ abstract class ElectrumTransactionHistoryBase
String _password; String _password;
int _height; int _height;
Future<void> init() async => await _load(); Future<void> init() async {
clear();
await _load();
}
@override @override
void addOne(ElectrumTransactionInfo transaction) => transactions[transaction.id] = transaction; void addOne(ElectrumTransactionInfo transaction) => transactions[transaction.id] = transaction;

View file

@ -1150,7 +1150,7 @@ abstract class ElectrumWalletBase
} }
@override @override
Future<void> close({required bool shouldCleanup}) async { Future<void> close({bool shouldCleanup = false}) async {
try { try {
_workerIsolate!.kill(priority: Isolate.immediate); _workerIsolate!.kill(priority: Isolate.immediate);
await _workerSubscription?.cancel(); await _workerSubscription?.cancel();
@ -1254,19 +1254,18 @@ abstract class ElectrumWalletBase
Future<void> refreshUnspentCoinsInfo() async { Future<void> refreshUnspentCoinsInfo() async {
try { try {
final List<dynamic> keys = <dynamic>[]; final List<dynamic> keys = [];
final currentWalletUnspentCoins = final currentWalletUnspentCoins =
unspentCoinsInfo.values.where((element) => element.walletId.contains(id)); unspentCoinsInfo.values.where((record) => record.walletId == id);
if (currentWalletUnspentCoins.isNotEmpty) { for (final element in currentWalletUnspentCoins) {
currentWalletUnspentCoins.forEach((element) { if (RegexUtils.addressTypeFromStr(element.address, network) is MwebAddress) continue;
final existUnspentCoins = unspentCoins
.where((coin) => element.hash.contains(coin.hash) && element.vout == coin.vout); final existUnspentCoins = unspentCoins.where((coin) => element == coin);
if (existUnspentCoins.isEmpty) { if (existUnspentCoins.isEmpty) {
keys.add(element.key); keys.add(element.key);
} }
});
} }
if (keys.isNotEmpty) { if (keys.isNotEmpty) {

View file

@ -1302,7 +1302,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
} }
@override @override
Future<void> close({required bool shouldCleanup}) async { Future<void> close({bool shouldCleanup = false}) async {
_utxoStream?.cancel(); _utxoStream?.cancel();
_feeRatesTimer?.cancel(); _feeRatesTimer?.cancel();
_syncTimer?.cancel(); _syncTimer?.cancel();

View file

@ -7,7 +7,7 @@ enum DeviceConnectionType {
static List<DeviceConnectionType> supportedConnectionTypes(WalletType walletType, static List<DeviceConnectionType> supportedConnectionTypes(WalletType walletType,
[bool isIOS = false]) { [bool isIOS = false]) {
switch (walletType) { switch (walletType) {
// case WalletType.monero: case WalletType.monero:
case WalletType.bitcoin: case WalletType.bitcoin:
case WalletType.litecoin: case WalletType.litecoin:
case WalletType.ethereum: case WalletType.ethereum:

View file

@ -19,3 +19,4 @@ const DERIVATION_INFO_TYPE_ID = 17;
const TRON_TOKEN_TYPE_ID = 18; const TRON_TOKEN_TYPE_ID = 18;
const HARDWARE_WALLET_TYPE_TYPE_ID = 19; 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;

View file

@ -83,7 +83,7 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
Future<void> rescan({required int height}); Future<void> rescan({required int height});
Future<void> close({required bool shouldCleanup}); Future<void> close({bool shouldCleanup = false});
Future<void> changePassword(String password); Future<void> changePassword(String password);

View file

@ -36,7 +36,18 @@ abstract class EVMChainClient {
bool connect(Node node) { bool connect(Node node) {
try { 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; return true;
} catch (e) { } catch (e) {
@ -83,23 +94,20 @@ abstract class EVMChainClient {
} }
} }
Future<int> getEstimatedGas({ Future<int> getEstimatedGasUnitsForTransaction({
String? contractAddress,
required EthereumAddress toAddress, required EthereumAddress toAddress,
required EthereumAddress senderAddress, required EthereumAddress senderAddress,
required EtherAmount value, required EtherAmount value,
String? contractAddress,
EtherAmount? gasPrice, EtherAmount? gasPrice,
// EtherAmount? maxFeePerGas, EtherAmount? maxFeePerGas,
// EtherAmount? maxPriorityFeePerGas,
}) async { }) async {
try { try {
if (contractAddress == null) { if (contractAddress == null) {
final estimatedGas = await _client!.estimateGas( final estimatedGas = await _client!.estimateGas(
sender: senderAddress, sender: senderAddress,
gasPrice: gasPrice,
to: toAddress, to: toAddress,
value: value, value: value,
// maxPriorityFeePerGas: maxPriorityFeePerGas,
// maxFeePerGas: maxFeePerGas, // maxFeePerGas: maxFeePerGas,
); );
@ -133,7 +141,9 @@ abstract class EVMChainClient {
required Credentials privateKey, required Credentials privateKey,
required String toAddress, required String toAddress,
required BigInt amount, required BigInt amount,
required BigInt gas, required BigInt gasFee,
required int estimatedGasUnits,
required int maxFeePerGas,
required EVMChainTransactionPriority priority, required EVMChainTransactionPriority priority,
required CryptoCurrency currency, required CryptoCurrency currency,
required int exponent, required int exponent,
@ -152,6 +162,8 @@ abstract class EVMChainClient {
maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip), maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip),
amount: isNativeToken ? EtherAmount.inWei(amount) : EtherAmount.zero(), amount: isNativeToken ? EtherAmount.inWei(amount) : EtherAmount.zero(),
data: data != null ? hexToBytes(data) : null, data: data != null ? hexToBytes(data) : null,
maxGas: estimatedGasUnits,
maxFeePerGas: EtherAmount.fromInt(EtherUnit.wei, maxFeePerGas),
); );
Uint8List signedTransaction; Uint8List signedTransaction;
@ -180,7 +192,7 @@ abstract class EVMChainClient {
return PendingEVMChainTransaction( return PendingEVMChainTransaction(
signedTransaction: signedTransaction, signedTransaction: signedTransaction,
amount: amount.toString(), amount: amount.toString(),
fee: gas, fee: gasFee,
sendTransaction: _sendTransaction, sendTransaction: _sendTransaction,
exponent: exponent, exponent: exponent,
); );
@ -191,7 +203,10 @@ abstract class EVMChainClient {
required EthereumAddress to, required EthereumAddress to,
required EtherAmount amount, required EtherAmount amount,
EtherAmount? maxPriorityFeePerGas, EtherAmount? maxPriorityFeePerGas,
EtherAmount? gasPrice,
EtherAmount? maxFeePerGas,
Uint8List? data, Uint8List? data,
int? maxGas,
}) { }) {
return Transaction( return Transaction(
from: from, from: from,
@ -199,6 +214,9 @@ abstract class EVMChainClient {
maxPriorityFeePerGas: maxPriorityFeePerGas, maxPriorityFeePerGas: maxPriorityFeePerGas,
value: amount, value: amount,
data: data, data: data,
maxGas: maxGas,
gasPrice: gasPrice,
maxFeePerGas: maxFeePerGas,
); );
} }

View file

@ -34,7 +34,10 @@ abstract class EVMChainTransactionHistoryBase
//! Common methods across all child classes //! Common methods across all child classes
Future<void> init() async => await _load(); Future<void> init() async {
clear();
await _load();
}
@override @override
Future<void> save() async { Future<void> save() async {

View file

@ -221,7 +221,7 @@ abstract class EVMChainWalletBase
/// - The exact amount the user wants to send, /// - The exact amount the user wants to send,
/// - The addressHex for the receiving wallet, /// - 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 /// - 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 amount,
required String? contractAddress, required String? contractAddress,
required String receivingAddressHex, required String receivingAddressHex,
@ -240,22 +240,27 @@ abstract class EVMChainWalletBase
maxFeePerGas = gasPrice; maxFeePerGas = gasPrice;
} }
final estimatedGas = await _client.getEstimatedGas( final estimatedGas = await _client.getEstimatedGasUnitsForTransaction(
contractAddress: contractAddress, contractAddress: contractAddress,
senderAddress: _evmChainPrivateKey.address, senderAddress: _evmChainPrivateKey.address,
value: EtherAmount.fromBigInt(EtherUnit.wei, amount!), value: EtherAmount.fromBigInt(EtherUnit.wei, amount!),
gasPrice: EtherAmount.fromInt(EtherUnit.wei, gasPrice), gasPrice: EtherAmount.fromInt(EtherUnit.wei, gasPrice),
toAddress: EthereumAddress.fromHex(receivingAddressHex), toAddress: EthereumAddress.fromHex(receivingAddressHex),
// maxFeePerGas: EtherAmount.fromInt(EtherUnit.wei, maxFeePerGas), maxFeePerGas: EtherAmount.fromInt(EtherUnit.wei, maxFeePerGas),
// maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip),
); );
final totalGasFee = estimatedGas * maxFeePerGas; final totalGasFee = estimatedGas * maxFeePerGas;
return totalGasFee;
return GasParamsHandler(
estimatedGasUnits: estimatedGas,
estimatedGasFee: totalGasFee,
maxFeePerGas: maxFeePerGas,
gasPrice: gasPrice,
);
} }
return 0; return GasParamsHandler.zero();
} catch (e) { } catch (e) {
return 0; return GasParamsHandler.zero();
} }
} }
@ -265,7 +270,7 @@ abstract class EVMChainWalletBase
} }
@override @override
Future<void> close({required bool shouldCleanup}) async { Future<void> close({bool shouldCleanup = false}) async {
_client.stop(); _client.stop();
_transactionsUpdateTimer?.cancel(); _transactionsUpdateTimer?.cancel();
_updateFeesTimer?.cancel(); _updateFeesTimer?.cancel();
@ -318,7 +323,7 @@ abstract class EVMChainWalletBase
gasPrice = await _client.getGasUnitPrice(); gasPrice = await _client.getGasUnitPrice();
estimatedGasUnits = await _client.getEstimatedGas( estimatedGasUnits = await _client.getEstimatedGasUnitsForTransaction(
senderAddress: _evmChainPrivateKey.address, senderAddress: _evmChainPrivateKey.address,
toAddress: _evmChainPrivateKey.address, toAddress: _evmChainPrivateKey.address,
gasPrice: EtherAmount.fromInt(EtherUnit.wei, gasPrice), gasPrice: EtherAmount.fromInt(EtherUnit.wei, gasPrice),
@ -349,6 +354,8 @@ abstract class EVMChainWalletBase
int exponent = transactionCurrency is Erc20Token ? transactionCurrency.decimal : 18; int exponent = transactionCurrency is Erc20Token ? transactionCurrency.decimal : 18;
num amountToEVMChainMultiplier = pow(10, exponent); num amountToEVMChainMultiplier = pow(10, exponent);
String? contractAddress; String? contractAddress;
int estimatedGasUnitsForTransaction = 0;
int maxFeePerGasForTransaction = 0;
String toAddress = _credentials.outputs.first.isParsedAddress String toAddress = _credentials.outputs.first.isParsedAddress
? _credentials.outputs.first.extractedAddress! ? _credentials.outputs.first.extractedAddress!
: _credentials.outputs.first.address; : _credentials.outputs.first.address;
@ -367,14 +374,16 @@ abstract class EVMChainWalletBase
outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0))); outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0)));
totalAmount = BigInt.from(totalOriginalAmount * amountToEVMChainMultiplier); totalAmount = BigInt.from(totalOriginalAmount * amountToEVMChainMultiplier);
final estimateFees = await calculateActualEstimatedFeeForCreateTransaction( final gasFeesModel = await calculateActualEstimatedFeeForCreateTransaction(
amount: totalAmount, amount: totalAmount,
receivingAddressHex: toAddress, receivingAddressHex: toAddress,
priority: _credentials.priority!, priority: _credentials.priority!,
contractAddress: contractAddress, contractAddress: contractAddress,
); );
estimatedFeesForTransaction = BigInt.from(estimateFees); estimatedFeesForTransaction = BigInt.from(gasFeesModel.estimatedGasFee);
estimatedGasUnitsForTransaction = gasFeesModel.estimatedGasUnits;
maxFeePerGasForTransaction = gasFeesModel.maxFeePerGas;
if (erc20Balance.balance < totalAmount) { if (erc20Balance.balance < totalAmount) {
throw EVMChainTransactionCreationException(transactionCurrency); throw EVMChainTransactionCreationException(transactionCurrency);
@ -392,14 +401,16 @@ abstract class EVMChainWalletBase
totalAmount = erc20Balance.balance; totalAmount = erc20Balance.balance;
} }
final estimateFees = await calculateActualEstimatedFeeForCreateTransaction( final gasFeesModel = await calculateActualEstimatedFeeForCreateTransaction(
amount: totalAmount, amount: totalAmount,
receivingAddressHex: toAddress, receivingAddressHex: toAddress,
priority: _credentials.priority!, priority: _credentials.priority!,
contractAddress: contractAddress, contractAddress: contractAddress,
); );
estimatedFeesForTransaction = BigInt.from(estimateFees); estimatedFeesForTransaction = BigInt.from(gasFeesModel.estimatedGasFee);
estimatedGasUnitsForTransaction = gasFeesModel.estimatedGasUnits;
maxFeePerGasForTransaction = gasFeesModel.maxFeePerGas;
if (output.sendAll && transactionCurrency is! Erc20Token) { if (output.sendAll && transactionCurrency is! Erc20Token) {
totalAmount = (erc20Balance.balance - estimatedFeesForTransaction); totalAmount = (erc20Balance.balance - estimatedFeesForTransaction);
@ -420,12 +431,14 @@ abstract class EVMChainWalletBase
} }
final pendingEVMChainTransaction = await _client.signTransaction( final pendingEVMChainTransaction = await _client.signTransaction(
estimatedGasUnits: estimatedGasUnitsForTransaction,
privateKey: _evmChainPrivateKey, privateKey: _evmChainPrivateKey,
toAddress: toAddress, toAddress: toAddress,
amount: totalAmount, amount: totalAmount,
gas: estimatedFeesForTransaction, gasFee: estimatedFeesForTransaction,
priority: _credentials.priority!, priority: _credentials.priority!,
currency: transactionCurrency, currency: transactionCurrency,
maxFeePerGas: maxFeePerGasForTransaction,
exponent: exponent, exponent: exponent,
contractAddress: contractAddress:
transactionCurrency is Erc20Token ? transactionCurrency.contractAddress : null, transactionCurrency is Erc20Token ? transactionCurrency.contractAddress : null,
@ -728,3 +741,25 @@ abstract class EVMChainWalletBase
@override @override
final String? passphrase; 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,
);
}
}

View file

@ -108,7 +108,7 @@ abstract class HavenWalletBase
Future<void>? updateBalance() => null; Future<void>? updateBalance() => null;
@override @override
Future<void> close({required bool shouldCleanup}) async { Future<void> close({bool shouldCleanup = false}) async {
_listener?.stop(); _listener?.stop();
_onAccountChangeReaction?.reaction.dispose(); _onAccountChangeReaction?.reaction.dispose();
_autoSaveTimer?.cancel(); _autoSaveTimer?.cancel();

View file

@ -200,9 +200,16 @@ String? commitTransactionFromPointerAddress({required int address, required bool
commitTransaction(transactionPointer: monero.PendingTransaction.fromAddress(address), useUR: useUR); commitTransaction(transactionPointer: monero.PendingTransaction.fromAddress(address), useUR: useUR);
String? commitTransaction({required monero.PendingTransaction transactionPointer, required bool useUR}) { String? commitTransaction({required monero.PendingTransaction transactionPointer, required bool useUR}) {
final transactionPointerAddress = transactionPointer.address;
final txCommit = useUR final txCommit = useUR
? monero.PendingTransaction_commitUR(transactionPointer, 120) ? monero.PendingTransaction_commitUR(transactionPointer, 120)
: monero.PendingTransaction_commit(transactionPointer, filename: '', overwrite: false); : Isolate.run(() {
monero.PendingTransaction_commit(
Pointer.fromAddress(transactionPointerAddress),
filename: '',
overwrite: false,
);
});
String? error = (() { String? error = (() {
final status = monero.PendingTransaction_status(transactionPointer.cast()); 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); throw CreationTransactionException(message: error);
} }
if (useUR) { if (useUR) {

View file

@ -121,7 +121,6 @@ Future<bool> setupNodeSync(
daemonUsername: login ?? '', daemonUsername: login ?? '',
daemonPassword: password ?? ''); daemonPassword: password ?? '');
}); });
// monero.Wallet_init3(wptr!, argv0: '', defaultLogBaseName: 'moneroc', console: true, logPath: '');
final status = monero.Wallet_status(wptr!); final status = monero.Wallet_status(wptr!);

View file

@ -2,11 +2,12 @@ import 'dart:async';
import 'dart:ffi'; import 'dart:ffi';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:collection/collection.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:ffi/ffi.dart'; import 'package:ffi/ffi.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus_dart.dart'; import 'package:ledger_flutter_plus/ledger_flutter_plus_dart.dart';
import 'package:monero/monero.dart' as monero; import 'package:monero/monero.dart' as monero;
// import 'package:polyseed/polyseed.dart';
LedgerConnection? gLedger; LedgerConnection? gLedger;
@ -28,9 +29,16 @@ void enableLedgerExchange(monero.wallet ptr, LedgerConnection connection) {
ptr, emptyPointer.cast<UnsignedChar>(), 0); ptr, emptyPointer.cast<UnsignedChar>(), 0);
malloc.free(emptyPointer); malloc.free(emptyPointer);
// printV("> ${ledgerRequest.toHexString()}"); _logLedgerCommand(ledgerRequest, false);
final response = await exchange(connection, ledgerRequest); 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); final Pointer<Uint8> result = malloc<Uint8>(response.length);
for (var i = 0; i < response.length; i++) { for (var i = 0; i < response.length; i++) {
@ -82,3 +90,59 @@ class ExchangeOperation extends LedgerRawOperation<Uint8List> {
@override @override
Future<List<Uint8List>> write(ByteDataWriter writer) async => [inputData]; 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))}");
}
}

View file

@ -175,7 +175,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
Future<void>? updateBalance() => null; Future<void>? updateBalance() => null;
@override @override
Future<void> close({required bool shouldCleanup}) async { Future<void> close({bool shouldCleanup = false}) async {
_listener?.stop(); _listener?.stop();
_onAccountChangeReaction?.reaction.dispose(); _onAccountChangeReaction?.reaction.dispose();
_onTxHistoryChangeReaction?.reaction.dispose(); _onTxHistoryChangeReaction?.reaction.dispose();

View file

@ -1,5 +1,7 @@
import 'dart:ffi'; import 'dart:ffi';
import 'dart:io'; import 'dart:io';
import 'package:cw_core/get_height_by_date.dart';
import 'package:cw_core/monero_wallet_utils.dart'; import 'package:cw_core/monero_wallet_utils.dart';
import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/unspent_coins_info.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_info.dart';
import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/wallet_service.dart';
import 'package:cw_core/wallet_type.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/account_list.dart';
import 'package:cw_monero/api/wallet_manager.dart' as monero_wallet_manager; import 'package:cw_monero/api/wallet_manager.dart' as monero_wallet_manager;
import 'package:cw_monero/api/wallet_manager.dart'; import 'package:cw_monero/api/wallet_manager.dart';
import 'package:cw_monero/ledger.dart'; import 'package:cw_monero/ledger.dart';
import 'package:cw_monero/monero_wallet.dart'; import 'package:cw_monero/monero_wallet.dart';
import 'package:collection/collection.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
import 'package:polyseed/polyseed.dart';
import 'package:monero/monero.dart' as monero; import 'package:monero/monero.dart' as monero;
import 'package:polyseed/polyseed.dart';
class MoneroNewWalletCredentials extends WalletCredentials { class MoneroNewWalletCredentials extends WalletCredentials {
MoneroNewWalletCredentials( MoneroNewWalletCredentials(
@ -133,14 +135,12 @@ class MoneroWalletService extends WalletService<
try { try {
final path = await pathForWallet(name: name, type: getType()); final path = await pathForWallet(name: name, type: getType());
if (walletFilesExist(path)) { if (walletFilesExist(path)) await repairOldAndroidWallet(name);
await repairOldAndroidWallet(name);
}
await monero_wallet_manager await monero_wallet_manager
.openWalletAsync({'path': path, 'password': password}); .openWalletAsync({'path': path, 'password': password});
final walletInfo = walletInfoSource.values.firstWhere( final walletInfo = walletInfoSource.values
(info) => info.id == WalletBase.idFor(name, getType())); .firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
final wallet = MoneroWallet( final wallet = MoneroWallet(
walletInfo: walletInfo, walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource, unspentCoinsInfo: unspentCoinsInfoSource,
@ -279,7 +279,8 @@ class MoneroWalletService extends WalletService<
} }
@override @override
Future<MoneroWallet> restoreFromSeed(MoneroRestoreWalletFromSeedCredentials credentials, Future<MoneroWallet> restoreFromSeed(
MoneroRestoreWalletFromSeedCredentials credentials,
{bool? isTestnet}) async { {bool? isTestnet}) async {
// Restore from Polyseed // Restore from Polyseed
if (Polyseed.isValidSeed(credentials.mnemonic)) { if (Polyseed.isValidSeed(credentials.mnemonic)) {
@ -313,7 +314,8 @@ class MoneroWalletService extends WalletService<
final path = await pathForWallet(name: credentials.name, type: getType()); final path = await pathForWallet(name: credentials.name, type: getType());
final polyseedCoin = PolyseedCoin.POLYSEED_MONERO; final polyseedCoin = PolyseedCoin.POLYSEED_MONERO;
final lang = PolyseedLang.getByPhrase(credentials.mnemonic); final lang = PolyseedLang.getByPhrase(credentials.mnemonic);
final polyseed = Polyseed.decode(credentials.mnemonic, lang, polyseedCoin); final polyseed =
Polyseed.decode(credentials.mnemonic, lang, polyseedCoin);
return _restoreFromPolyseed( return _restoreFromPolyseed(
path, credentials.password!, polyseed, credentials.walletInfo!, lang); path, credentials.password!, polyseed, credentials.walletInfo!, lang);
@ -355,24 +357,18 @@ class MoneroWalletService extends WalletService<
Future<void> repairOldAndroidWallet(String name) async { Future<void> repairOldAndroidWallet(String name) async {
try { try {
if (!Platform.isAndroid) { if (!Platform.isAndroid) return;
return;
}
final oldAndroidWalletDirPath = await outdatedAndroidPathForWalletDir(name: name); final oldAndroidWalletDirPath = await outdatedAndroidPathForWalletDir(name: name);
final dir = Directory(oldAndroidWalletDirPath); final dir = Directory(oldAndroidWalletDirPath);
if (!dir.existsSync()) { if (!dir.existsSync()) return;
return;
}
final newWalletDirPath = await pathForWalletDir(name: name, type: getType()); final newWalletDirPath = await pathForWalletDir(name: name, type: getType());
dir.listSync().forEach((f) { dir.listSync().forEach((f) {
final file = File(f.path); final file = File(f.path);
final name = f.path final name = f.path.split('/').last;
.split('/')
.last;
final newPath = newWalletDirPath + '/$name'; final newPath = newWalletDirPath + '/$name';
final newFile = File(newPath); final newFile = File(newPath);
@ -391,9 +387,7 @@ class MoneroWalletService extends WalletService<
try { try {
final path = await pathForWallet(name: name, type: getType()); final path = await pathForWallet(name: name, type: getType());
if (walletFilesExist(path)) { if (walletFilesExist(path)) await repairOldAndroidWallet(name);
await repairOldAndroidWallet(name);
}
await monero_wallet_manager.openWalletAsync({'path': path, 'password': password}); await monero_wallet_manager.openWalletAsync({'path': path, 'password': password});
final walletInfo = walletInfoSource.values final walletInfo = walletInfoSource.values
@ -412,8 +406,10 @@ class MoneroWalletService extends WalletService<
@override @override
bool requireHardwareWalletConnection(String name) { bool requireHardwareWalletConnection(String name) {
final walletInfo = walletInfoSource.values return walletInfoSource.values
.firstWhere((info) => info.id == WalletBase.idFor(name, getType())); .firstWhereOrNull(
return walletInfo.isHardwareWallet; (info) => info.id == WalletBase.idFor(name, getType()))
?.isHardwareWallet ??
false;
} }
} }

View file

@ -503,8 +503,8 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
path: "impls/monero.dart" path: "impls/monero.dart"
ref: c41c4dad9aa5003a914cfb2c528c76386f952665 ref: af5277f96073917185864d3596e82b67bee54e78
resolved-ref: c41c4dad9aa5003a914cfb2c528c76386f952665 resolved-ref: af5277f96073917185864d3596e82b67bee54e78
url: "https://github.com/mrcyjanek/monero_c" url: "https://github.com/mrcyjanek/monero_c"
source: git source: git
version: "0.0.0" version: "0.0.0"

View file

@ -25,7 +25,7 @@ dependencies:
monero: monero:
git: git:
url: https://github.com/mrcyjanek/monero_c url: https://github.com/mrcyjanek/monero_c
ref: c41c4dad9aa5003a914cfb2c528c76386f952665 ref: af5277f96073917185864d3596e82b67bee54e78
# ref: 6eb571ea498ed7b854934785f00fabfd0dadf75b # monero_c hash # ref: 6eb571ea498ed7b854934785f00fabfd0dadf75b # monero_c hash
path: impls/monero.dart path: impls/monero.dart
mutex: ^3.1.0 mutex: ^3.1.0

View file

@ -28,7 +28,10 @@ abstract class NanoTransactionHistoryBase extends TransactionHistoryBase<NanoTra
final EncryptionFileUtils encryptionFileUtils; final EncryptionFileUtils encryptionFileUtils;
String _password; String _password;
Future<void> init() async => await _load(); Future<void> init() async {
clear();
await _load();
}
@override @override
Future<void> save() async { Future<void> save() async {

View file

@ -150,7 +150,7 @@ abstract class NanoWalletBase
Future<void> changePassword(String password) => throw UnimplementedError("changePassword"); Future<void> changePassword(String password) => throw UnimplementedError("changePassword");
@override @override
Future<void> close({required bool shouldCleanup}) async { Future<void> close({bool shouldCleanup = false}) async {
_client.stop(); _client.stop();
_receiveTimer?.cancel(); _receiveTimer?.cancel();
} }

View file

@ -14,11 +14,19 @@ class PolygonClient extends EVMChainClient {
required EtherAmount amount, required EtherAmount amount,
EtherAmount? maxPriorityFeePerGas, EtherAmount? maxPriorityFeePerGas,
Uint8List? data, Uint8List? data,
int? maxGas,
EtherAmount? gasPrice,
EtherAmount? maxFeePerGas,
}) { }) {
return Transaction( return Transaction(
from: from, from: from,
to: to, to: to,
value: amount, value: amount,
data: data,
maxGas: maxGas,
gasPrice: gasPrice,
maxFeePerGas: maxFeePerGas,
maxPriorityFeePerGas: maxPriorityFeePerGas,
); );
} }

View file

@ -1,12 +1,13 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:math'; import 'dart:math' as math;
import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/node.dart'; import 'package:cw_core/node.dart';
import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_solana/pending_solana_transaction.dart'; import 'package:cw_solana/pending_solana_transaction.dart';
import 'package:cw_solana/solana_balance.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:cw_solana/solana_transaction_model.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:solana/dto.dart'; import 'package:solana/dto.dart';
@ -180,7 +181,7 @@ class SolanaWalletClient {
bool isOutgoingTx = transfer.source == publicKey.toBase58(); bool isOutgoingTx = transfer.source == publicKey.toBase58();
double amount = (double.tryParse(transfer.amount) ?? 0.0) / double amount = (double.tryParse(transfer.amount) ?? 0.0) /
pow(10, splTokenDecimal ?? 9); math.pow(10, splTokenDecimal ?? 9);
transactions.add( transactions.add(
SolanaTransactionModel( SolanaTransactionModel(
@ -276,6 +277,7 @@ class SolanaWalletClient {
required String destinationAddress, required String destinationAddress,
required Ed25519HDKeyPair ownerKeypair, required Ed25519HDKeyPair ownerKeypair,
required bool isSendAll, required bool isSendAll,
required double solBalance,
String? tokenMint, String? tokenMint,
List<String> references = const [], List<String> references = const [],
}) async { }) async {
@ -290,6 +292,7 @@ class SolanaWalletClient {
ownerKeypair: ownerKeypair, ownerKeypair: ownerKeypair,
commitment: commitment, commitment: commitment,
isSendAll: isSendAll, isSendAll: isSendAll,
solBalance: solBalance,
); );
return pendingNativeTokenTransaction; return pendingNativeTokenTransaction;
} else { } else {
@ -301,6 +304,7 @@ class SolanaWalletClient {
destinationAddress: destinationAddress, destinationAddress: destinationAddress,
ownerKeypair: ownerKeypair, ownerKeypair: ownerKeypair,
commitment: commitment, commitment: commitment,
solBalance: solBalance,
); );
return pendingSPLTokenTransaction; return pendingSPLTokenTransaction;
} }
@ -353,6 +357,23 @@ class SolanaWalletClient {
return fee; 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({ Future<PendingSolanaTransaction> _signNativeTokenTransaction({
required String tokenTitle, required String tokenTitle,
required int tokenDecimals, required int tokenDecimals,
@ -361,6 +382,7 @@ class SolanaWalletClient {
required Ed25519HDKeyPair ownerKeypair, required Ed25519HDKeyPair ownerKeypair,
required Commitment commitment, required Commitment commitment,
required bool isSendAll, required bool isSendAll,
required double solBalance,
}) async { }) async {
// Convert SOL to lamport // Convert SOL to lamport
int lamports = (inputAmount * lamportsPerSol).toInt(); int lamports = (inputAmount * lamportsPerSol).toInt();
@ -378,6 +400,16 @@ class SolanaWalletClient {
commitment, commitment,
); );
bool hasSufficientFundsLeft = await hasSufficientFundsLeftForRent(
inputAmount: inputAmount,
fee: fee,
solBalance: solBalance,
);
if (!hasSufficientFundsLeft) {
throw SolanaSignNativeTokenTransactionRentException();
}
SignedTx signedTx; SignedTx signedTx;
if (isSendAll) { if (isSendAll) {
final feeInLamports = (fee * lamportsPerSol).toInt(); final feeInLamports = (fee * lamportsPerSol).toInt();
@ -425,6 +457,7 @@ class SolanaWalletClient {
required String destinationAddress, required String destinationAddress,
required Ed25519HDKeyPair ownerKeypair, required Ed25519HDKeyPair ownerKeypair,
required Commitment commitment, required Commitment commitment,
required double solBalance,
}) async { }) async {
final destinationOwner = Ed25519HDPublicKey.fromBase58(destinationAddress); final destinationOwner = Ed25519HDPublicKey.fromBase58(destinationAddress);
final mint = Ed25519HDPublicKey.fromBase58(tokenMint); final mint = Ed25519HDPublicKey.fromBase58(tokenMint);
@ -447,7 +480,7 @@ class SolanaWalletClient {
// Throw an appropriate exception if the sender has no associated // Throw an appropriate exception if the sender has no associated
// token account // token account
if (associatedSenderAccount == null) { if (associatedSenderAccount == null) {
throw NoAssociatedTokenAccountException(ownerKeypair.address, mint.toBase58()); throw SolanaNoAssociatedTokenAccountException(ownerKeypair.address, mint.toBase58());
} }
try { try {
@ -457,11 +490,11 @@ class SolanaWalletClient {
funder: ownerKeypair, funder: ownerKeypair,
); );
} catch (e) { } catch (e) {
throw Exception('Insufficient SOL balance to complete this transaction: ${e.toString()}'); throw SolanaCreateAssociatedTokenAccountException(e.toString());
} }
// Input by the user // Input by the user
final amount = (inputAmount * pow(10, tokenDecimals)).toInt(); final amount = (inputAmount * math.pow(10, tokenDecimals)).toInt();
final instruction = TokenInstruction.transfer( final instruction = TokenInstruction.transfer(
source: Ed25519HDPublicKey.fromBase58(associatedSenderAccount.pubkey), source: Ed25519HDPublicKey.fromBase58(associatedSenderAccount.pubkey),
@ -483,6 +516,16 @@ class SolanaWalletClient {
commitment, commitment,
); );
bool hasSufficientFundsLeft = await hasSufficientFundsLeftForRent(
inputAmount: inputAmount,
fee: fee,
solBalance: solBalance,
);
if (!hasSufficientFundsLeft) {
throw SolanaSignSPLTokenTransactionRentException();
}
final signedTx = await _signTransactionInternal( final signedTx = await _signTransactionInternal(
message: message, message: message,
signers: signers, signers: signers,

View file

@ -19,3 +19,20 @@ class SolanaTransactionWrongBalanceException implements Exception {
@override @override
String toString() => exceptionMessage; 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;
}

View file

@ -26,7 +26,10 @@ abstract class SolanaTransactionHistoryBase extends TransactionHistoryBase<Solan
final EncryptionFileUtils encryptionFileUtils; final EncryptionFileUtils encryptionFileUtils;
String _password; String _password;
Future<void> init() async => await _load(); Future<void> init() async {
clear();
await _load();
}
@override @override
Future<void> save() async { Future<void> save() async {

View file

@ -180,7 +180,7 @@ abstract class SolanaWalletBase
Future<void> changePassword(String password) => throw UnimplementedError("changePassword"); Future<void> changePassword(String password) => throw UnimplementedError("changePassword");
@override @override
Future<void> close({required bool shouldCleanup}) async { Future<void> close({bool shouldCleanup = false}) async {
_client.stop(); _client.stop();
_transactionsUpdateTimer?.cancel(); _transactionsUpdateTimer?.cancel();
} }
@ -228,6 +228,8 @@ abstract class SolanaWalletBase
final walletBalanceForCurrency = balance[transactionCurrency]!.balance; final walletBalanceForCurrency = balance[transactionCurrency]!.balance;
final solBalance = balance[CryptoCurrency.sol]!.balance;
double totalAmount = 0.0; double totalAmount = 0.0;
bool isSendAll = false; bool isSendAll = false;
@ -279,6 +281,7 @@ abstract class SolanaWalletBase
? solCredentials.outputs.first.extractedAddress! ? solCredentials.outputs.first.extractedAddress!
: solCredentials.outputs.first.address, : solCredentials.outputs.first.address,
isSendAll: isSendAll, isSendAll: isSendAll,
solBalance: solBalance,
); );
return pendingSolanaTransaction; return pendingSolanaTransaction;

View file

@ -25,7 +25,10 @@ abstract class TronTransactionHistoryBase extends TransactionHistoryBase<TronTra
final WalletInfo walletInfo; final WalletInfo walletInfo;
final EncryptionFileUtils encryptionFileUtils; final EncryptionFileUtils encryptionFileUtils;
Future<void> init() async => await _load(); Future<void> init() async {
clear();
await _load();
}
@override @override
Future<void> save() async { Future<void> save() async {

View file

@ -217,7 +217,7 @@ abstract class TronWalletBase
Future<void> changePassword(String password) => throw UnimplementedError("changePassword"); Future<void> changePassword(String password) => throw UnimplementedError("changePassword");
@override @override
Future<void> close({required bool shouldCleanup}) async => _transactionsUpdateTimer?.cancel(); Future<void> close({bool shouldCleanup = false}) async => _transactionsUpdateTimer?.cancel();
@action @action
@override @override

View file

@ -181,14 +181,24 @@ void commitTransaction({required wownero.PendingTransaction transactionPointer})
final txCommit = wownero.PendingTransaction_commit(transactionPointer, filename: '', overwrite: false); final txCommit = wownero.PendingTransaction_commit(transactionPointer, filename: '', overwrite: false);
final String? error = (() { String? error = (() {
final status = wownero.PendingTransaction_status(transactionPointer.cast()); final status = wownero.PendingTransaction_status(transactionPointer.cast());
if (status == 0) { if (status == 0) {
return null; return null;
} }
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!); return wownero.Wallet_errorString(wptr!);
})(); })();
}
if (error != null) { if (error != null) {
throw CreationTransactionException(message: error); throw CreationTransactionException(message: error);
} }

View file

@ -162,7 +162,7 @@ abstract class WowneroWalletBase
Future<void>? updateBalance() => null; Future<void>? updateBalance() => null;
@override @override
Future<void> close({required bool shouldCleanup}) async { Future<void> close({bool shouldCleanup = false}) async {
_listener?.stop(); _listener?.stop();
_onAccountChangeReaction?.reaction.dispose(); _onAccountChangeReaction?.reaction.dispose();
_onTxHistoryChangeReaction?.reaction.dispose(); _onTxHistoryChangeReaction?.reaction.dispose();

View file

@ -463,8 +463,8 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
path: "impls/monero.dart" path: "impls/monero.dart"
ref: c41c4dad9aa5003a914cfb2c528c76386f952665 ref: af5277f96073917185864d3596e82b67bee54e78
resolved-ref: c41c4dad9aa5003a914cfb2c528c76386f952665 resolved-ref: af5277f96073917185864d3596e82b67bee54e78
url: "https://github.com/mrcyjanek/monero_c" url: "https://github.com/mrcyjanek/monero_c"
source: git source: git
version: "0.0.0" version: "0.0.0"
@ -757,10 +757,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: vm_service name: vm_service
sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "14.2.4" version: "14.2.5"
watcher: watcher:
dependency: "direct overridden" dependency: "direct overridden"
description: description:

View file

@ -25,7 +25,7 @@ dependencies:
monero: monero:
git: git:
url: https://github.com/mrcyjanek/monero_c url: https://github.com/mrcyjanek/monero_c
ref: c41c4dad9aa5003a914cfb2c528c76386f952665 ref: af5277f96073917185864d3596e82b67bee54e78
# ref: 6eb571ea498ed7b854934785f00fabfd0dadf75b # monero_c hash # ref: 6eb571ea498ed7b854934785f00fabfd0dadf75b # monero_c hash
path: impls/monero.dart path: impls/monero.dart
mutex: ^3.1.0 mutex: ^3.1.0

View file

@ -4,10 +4,10 @@ import 'package:cw_core/wallet_type.dart';
class CommonTestConstants { class CommonTestConstants {
static final pin = [0, 8, 0, 1]; static final pin = [0, 8, 0, 1];
static final String sendTestAmount = '0.00008'; 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 WalletType testWalletType = WalletType.solana;
static final String testWalletName = 'Integrated Testing Wallet'; static final String testWalletName = 'Integrated Testing Wallet';
static final CryptoCurrency testReceiveCurrency = CryptoCurrency.sol; static final CryptoCurrency testReceiveCurrency = CryptoCurrency.usdtSol;
static final CryptoCurrency testDepositCurrency = CryptoCurrency.usdtSol; static final CryptoCurrency testDepositCurrency = CryptoCurrency.sol;
static final String testWalletAddress = '5v9gTW1yWPffhnbNKuvtL2frevAf4HpBMw8oYnfqUjhm'; static final String testWalletAddress = '5v9gTW1yWPffhnbNKuvtL2frevAf4HpBMw8oYnfqUjhm';
} }

View file

@ -7,6 +7,7 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:cake_wallet/main.dart' as app; import 'package:cake_wallet/main.dart' as app;
import '../robots/create_pin_welcome_page_robot.dart';
import '../robots/dashboard_page_robot.dart'; import '../robots/dashboard_page_robot.dart';
import '../robots/disclaimer_page_robot.dart'; import '../robots/disclaimer_page_robot.dart';
import '../robots/new_wallet_page_robot.dart'; import '../robots/new_wallet_page_robot.dart';
@ -37,6 +38,7 @@ class CommonTestFlows {
_walletListPageRobot = WalletListPageRobot(_tester), _walletListPageRobot = WalletListPageRobot(_tester),
_newWalletTypePageRobot = NewWalletTypePageRobot(_tester), _newWalletTypePageRobot = NewWalletTypePageRobot(_tester),
_restoreOptionsPageRobot = RestoreOptionsPageRobot(_tester), _restoreOptionsPageRobot = RestoreOptionsPageRobot(_tester),
_createPinWelcomePageRobot = CreatePinWelcomePageRobot(_tester),
_restoreFromSeedOrKeysPageRobot = RestoreFromSeedOrKeysPageRobot(_tester), _restoreFromSeedOrKeysPageRobot = RestoreFromSeedOrKeysPageRobot(_tester),
_walletGroupDescriptionPageRobot = WalletGroupDescriptionPageRobot(_tester); _walletGroupDescriptionPageRobot = WalletGroupDescriptionPageRobot(_tester);
@ -53,6 +55,7 @@ class CommonTestFlows {
final WalletListPageRobot _walletListPageRobot; final WalletListPageRobot _walletListPageRobot;
final NewWalletTypePageRobot _newWalletTypePageRobot; final NewWalletTypePageRobot _newWalletTypePageRobot;
final RestoreOptionsPageRobot _restoreOptionsPageRobot; final RestoreOptionsPageRobot _restoreOptionsPageRobot;
final CreatePinWelcomePageRobot _createPinWelcomePageRobot;
final RestoreFromSeedOrKeysPageRobot _restoreFromSeedOrKeysPageRobot; final RestoreFromSeedOrKeysPageRobot _restoreFromSeedOrKeysPageRobot;
final WalletGroupDescriptionPageRobot _walletGroupDescriptionPageRobot; final WalletGroupDescriptionPageRobot _walletGroupDescriptionPageRobot;
@ -190,10 +193,12 @@ class CommonTestFlows {
WalletType walletTypeToCreate, WalletType walletTypeToCreate,
List<int> pin, List<int> pin,
) async { ) async {
await _welcomePageRobot.navigateToCreateNewWalletPage(); await _createPinWelcomePageRobot.tapSetAPinButton();
await setupPinCodeForWallet(pin); await setupPinCodeForWallet(pin);
await _welcomePageRobot.navigateToCreateNewWalletPage();
await _selectWalletTypeForWallet(walletTypeToCreate); await _selectWalletTypeForWallet(walletTypeToCreate);
} }
@ -201,12 +206,14 @@ class CommonTestFlows {
WalletType walletTypeToRestore, WalletType walletTypeToRestore,
List<int> pin, List<int> pin,
) async { ) async {
await _createPinWelcomePageRobot.tapSetAPinButton();
await setupPinCodeForWallet(pin);
await _welcomePageRobot.navigateToRestoreWalletPage(); await _welcomePageRobot.navigateToRestoreWalletPage();
await _restoreOptionsPageRobot.navigateToRestoreFromSeedsOrKeysPage(); await _restoreOptionsPageRobot.navigateToRestoreFromSeedsOrKeysPage();
await setupPinCodeForWallet(pin);
await _selectWalletTypeForWallet(walletTypeToRestore); await _selectWalletTypeForWallet(walletTypeToRestore);
} }

View 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();
}
}

View file

@ -123,10 +123,8 @@ class ExchangePageRobot {
return; return;
} }
await commonTestCases.dragUntilVisible( await commonTestCases.enterText(depositCurrency.name, 'search_bar_widget_key');
'picker_items_index_${depositCurrency.name}_button_key',
'picker_scrollbar_key',
);
await commonTestCases.defaultSleepTime(); await commonTestCases.defaultSleepTime();
await commonTestCases.tapItemByKey('picker_items_index_${depositCurrency.name}_button_key'); await commonTestCases.tapItemByKey('picker_items_index_${depositCurrency.name}_button_key');
@ -149,10 +147,8 @@ class ExchangePageRobot {
return; return;
} }
await commonTestCases.dragUntilVisible( await commonTestCases.enterText(receiveCurrency.name, 'search_bar_widget_key');
'picker_items_index_${receiveCurrency.name}_button_key',
'picker_scrollbar_key',
);
await commonTestCases.defaultSleepTime(); await commonTestCases.defaultSleepTime();
await commonTestCases.tapItemByKey('picker_items_index_${receiveCurrency.name}_button_key'); await commonTestCases.tapItemByKey('picker_items_index_${receiveCurrency.name}_button_key');

View file

@ -84,13 +84,11 @@ class SendPageRobot {
return; return;
} }
await commonTestCases.dragUntilVisible( await commonTestCases.enterText(receiveCurrency.title, 'search_bar_widget_key');
'picker_items_index_${receiveCurrency.name}_button_key',
'picker_scrollbar_key',
);
await commonTestCases.defaultSleepTime(); 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 { Future<void> enterReceiveAddress(String receiveAddress) async {
@ -210,6 +208,7 @@ class SendPageRobot {
_handleAuthPage(); _handleAuthPage();
} }
} }
await tester.pump();
} }
Future<void> handleSendResult() async { Future<void> handleSendResult() async {

View file

@ -42,11 +42,11 @@ class WalletKeysAndSeedPageRobot {
bool hasPrivateKey = appStore.wallet!.privateKey != null; bool hasPrivateKey = appStore.wallet!.privateKey != null;
if (walletType == WalletType.monero) { if (walletType == WalletType.monero) {
final moneroWallet = appStore.wallet as MoneroWallet; final moneroWallet = appStore.wallet as MoneroWalletBase;
final lang = PolyseedLang.getByPhrase(moneroWallet.seed); final lang = PolyseedLang.getByPhrase(moneroWallet.seed);
final legacySeed = moneroWallet.seedLegacy(lang.nameEnglish); final legacySeed = moneroWallet.seedLegacy(lang.nameEnglish);
_confirmMoneroWalletCredentials( await _confirmMoneroWalletCredentials(
appStore, appStore,
walletName, walletName,
moneroWallet.seed, moneroWallet.seed,
@ -59,7 +59,7 @@ class WalletKeysAndSeedPageRobot {
final lang = PolyseedLang.getByPhrase(wowneroWallet.seed); final lang = PolyseedLang.getByPhrase(wowneroWallet.seed);
final legacySeed = wowneroWallet.seedLegacy(lang.nameEnglish); final legacySeed = wowneroWallet.seedLegacy(lang.nameEnglish);
_confirmMoneroWalletCredentials( await _confirmMoneroWalletCredentials(
appStore, appStore,
walletName, walletName,
wowneroWallet.seed, wowneroWallet.seed,
@ -105,12 +105,12 @@ class WalletKeysAndSeedPageRobot {
await commonTestCases.defaultSleepTime(seconds: 5); await commonTestCases.defaultSleepTime(seconds: 5);
} }
void _confirmMoneroWalletCredentials( Future<void> _confirmMoneroWalletCredentials(
AppStore appStore, AppStore appStore,
String walletName, String walletName,
String seed, String seed,
String legacySeed, String legacySeed,
) { ) async {
final keys = appStore.wallet!.keys as MoneroWalletKeys; final keys = appStore.wallet!.keys as MoneroWalletKeys;
final hasPublicSpendKey = commonTestCases.isKeyPresent( final hasPublicSpendKey = commonTestCases.isKeyPresent(
@ -145,10 +145,18 @@ class WalletKeysAndSeedPageRobot {
tester.printToConsole('$walletName wallet has private view key properly displayed'); tester.printToConsole('$walletName wallet has private view key properly displayed');
} }
if (hasSeeds) { if (hasSeeds) {
await commonTestCases.dragUntilVisible(
'${walletName}_wallet_seed_item_key',
'wallet_keys_page_credentials_list_view_key',
);
commonTestCases.hasText(seed); commonTestCases.hasText(seed);
tester.printToConsole('$walletName wallet has seeds properly displayed'); tester.printToConsole('$walletName wallet has seeds properly displayed');
} }
if (hasSeedLegacy) { if (hasSeedLegacy) {
await commonTestCases.dragUntilVisible(
'${walletName}_wallet_seed_legacy_item_key',
'wallet_keys_page_credentials_list_view_key',
);
commonTestCases.hasText(legacySeed); commonTestCases.hasText(legacySeed);
tester.printToConsole('$walletName wallet has legacy seeds properly displayed'); tester.printToConsole('$walletName wallet has legacy seeds properly displayed');
} }

View file

@ -101,7 +101,7 @@ Future<void> _confirmSeedsFlowForWalletType(
walletKeysAndSeedPageRobot.hasTitle(); walletKeysAndSeedPageRobot.hasTitle();
walletKeysAndSeedPageRobot.hasShareWarning(); walletKeysAndSeedPageRobot.hasShareWarning();
walletKeysAndSeedPageRobot.confirmWalletCredentials(walletType); await walletKeysAndSeedPageRobot.confirmWalletCredentials(walletType);
await walletKeysAndSeedPageRobot.backToDashboard(); await walletKeysAndSeedPageRobot.backToDashboard();
} }

45
integration_test_runner.sh Executable file
View 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

View file

@ -93,6 +93,9 @@ class CakePayApi {
required int quantity, required int quantity,
required String userEmail, required String userEmail,
required String token, required String token,
required bool confirmsNoVpn,
required bool confirmsVoidedRefund,
required bool confirmsTermsAgreed,
}) async { }) async {
final uri = Uri.https(baseCakePayUri, createOrderPath); final uri = Uri.https(baseCakePayUri, createOrderPath);
final headers = { final headers = {
@ -106,7 +109,10 @@ class CakePayApi {
'quantity': quantity, 'quantity': quantity,
'user_email': userEmail, 'user_email': userEmail,
'token': token, 'token': token,
'send_email': true 'send_email': true,
'confirms_no_vpn': confirmsNoVpn,
'confirms_voided_refund': confirmsVoidedRefund,
'confirms_terms_agreed': confirmsTermsAgreed,
}; };
try { try {

View file

@ -90,8 +90,14 @@ class CakePayService {
} }
/// Purchase Gift Card /// Purchase Gift Card
Future<CakePayOrder> createOrder( Future<CakePayOrder> createOrder({
{required int cardId, required String price, required int quantity}) async { 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 userEmail = (await secureStorage.read(key: cakePayEmailStorageKey))!;
final token = (await secureStorage.read(key: cakePayUserTokenKey))!; final token = (await secureStorage.read(key: cakePayUserTokenKey))!;
return await cakePayApi.createOrder( return await cakePayApi.createOrder(
@ -100,7 +106,11 @@ class CakePayService {
price: price, price: price,
quantity: quantity, quantity: quantity,
token: token, token: token,
userEmail: userEmail); userEmail: userEmail,
confirmsNoVpn: confirmsNoVpn,
confirmsVoidedRefund: confirmsVoidedRefund,
confirmsTermsAgreed: confirmsTermsAgreed,
);
} }
///Simulate Purchase Gift Card ///Simulate Purchase Gift Card

View file

@ -80,7 +80,7 @@ class AddressValidator extends TextValidator {
case CryptoCurrency.shib: case CryptoCurrency.shib:
pattern = '0x[0-9a-zA-Z]+'; pattern = '0x[0-9a-zA-Z]+';
case CryptoCurrency.xrp: 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: case CryptoCurrency.xhv:
pattern = 'hvx|hvi|hvs[0-9a-zA-Z]+'; pattern = 'hvx|hvi|hvs[0-9a-zA-Z]+';
case CryptoCurrency.xag: case CryptoCurrency.xag:
@ -106,9 +106,8 @@ class AddressValidator extends TextValidator {
case CryptoCurrency.wow: case CryptoCurrency.wow:
pattern = '[0-9a-zA-Z]+'; pattern = '[0-9a-zA-Z]+';
case CryptoCurrency.bch: case CryptoCurrency.bch:
pattern = '^(bitcoincash:)?(q|p)[0-9a-zA-Z]{41,42}'; pattern = '(?:bitcoincash:)?(q|p)[0-9a-zA-Z]{41}'
case CryptoCurrency.bnb: '|[13][a-km-zA-HJ-NP-Z1-9]{25,34}';
pattern = '[0-9a-zA-Z]+';
case CryptoCurrency.hbar: case CryptoCurrency.hbar:
pattern = '[0-9a-zA-Z.]+'; pattern = '[0-9a-zA-Z.]+';
case CryptoCurrency.zaddr: case CryptoCurrency.zaddr:
@ -203,7 +202,7 @@ class AddressValidator extends TextValidator {
case CryptoCurrency.avaxc: case CryptoCurrency.avaxc:
return [42]; return [42];
case CryptoCurrency.bch: 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: case CryptoCurrency.bnb:
return [42]; return [42];
case CryptoCurrency.nano: case CryptoCurrency.nano:
@ -282,10 +281,14 @@ class AddressValidator extends TextValidator {
switch (type) { switch (type) {
case CryptoCurrency.xmr: case CryptoCurrency.xmr:
case CryptoCurrency.wow:
pattern = '(4[0-9a-zA-Z]{94})' pattern = '(4[0-9a-zA-Z]{94})'
'|(8[0-9a-zA-Z]{94})' '|(8[0-9a-zA-Z]{94})'
'|([0-9a-zA-Z]{106})'; '|([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: case CryptoCurrency.btc:
pattern = pattern =
'${P2pkhAddress.regex.pattern}|${P2shAddress.regex.pattern}|${P2wpkhAddress.regex.pattern}|${P2trAddress.regex.pattern}|${P2wshAddress.regex.pattern}|${SilentPaymentAddress.regex.pattern}'; '${P2pkhAddress.regex.pattern}|${P2shAddress.regex.pattern}|${P2wpkhAddress.regex.pattern}|${P2trAddress.regex.pattern}|${P2wshAddress.regex.pattern}|${SilentPaymentAddress.regex.pattern}';

View file

@ -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/buy_sell_options_page.dart';
import 'package:cake_wallet/src/screens/buy/payment_method_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/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/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_logs_page.dart';
import 'package:cake_wallet/src/screens/settings/mweb_node_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>( getIt.registerFactoryParam<PreSeedPage, int, void>(
(seedPhraseLength, _) => PreSeedPage(seedPhraseLength)); (seedPhraseLength, _) => PreSeedPage(seedPhraseLength));
getIt.registerFactoryParam<TransactionSuccessPage, String, void>(
(content, _) => TransactionSuccessPage(content: content));
getIt.registerFactoryParam<TradeDetailsViewModel, Trade, void>((trade, _) => getIt.registerFactoryParam<TradeDetailsViewModel, Trade, void>((trade, _) =>
TradeDetailsViewModel( TradeDetailsViewModel(
tradeForDetails: trade, tradeForDetails: trade,
@ -1423,5 +1428,7 @@ Future<void> setup({
getIt.registerFactory(() => SignViewModel(getIt.get<AppStore>().wallet!)); getIt.registerFactory(() => SignViewModel(getIt.get<AppStore>().wallet!));
getIt.registerFactory(() => SeedVerificationPage(getIt.get<WalletSeedViewModel>()));
_isSetupFinished = true; _isSetupFinished = true;
} }

View file

@ -1,9 +1,13 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io' show Directory, File, Platform; import 'dart:io' show Directory, File, Platform;
import 'package:cake_wallet/bitcoin/bitcoin.dart'; 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/core/secure_storage.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart'; import 'package:cake_wallet/entities/fiat_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:cw_core/pathForWallet.dart';
import 'package:cake_wallet/entities/secret_store_key.dart'; import 'package:cake_wallet/entities/secret_store_key.dart';
import 'package:cw_core/root_dir.dart'; import 'package:cw_core/root_dir.dart';
@ -52,7 +56,8 @@ Future<void> defaultSettingsMigration(
required Box<Node> powNodes, required Box<Node> powNodes,
required Box<WalletInfo> walletInfoSource, required Box<WalletInfo> walletInfoSource,
required Box<Trade> tradeSource, required Box<Trade> tradeSource,
required Box<Contact> contactSource}) async { required Box<Contact> contactSource,
required Box<HavenSeedStore> havenSeedStore}) async {
if (Platform.isIOS) { if (Platform.isIOS) {
await ios_migrate_v1(walletInfoSource, tradeSource, contactSource); await ios_migrate_v1(walletInfoSource, tradeSource, contactSource);
} }
@ -289,7 +294,23 @@ Future<void> defaultSettingsMigration(
], ],
); );
break; 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: default:
break; break;
} }
@ -304,6 +325,13 @@ Future<void> defaultSettingsMigration(
await sharedPreferences.setInt(PreferencesKey.currentDefaultSettingsMigrationVersion, version); 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 /// generic function for changing any wallet default node
/// instead of making a new function for each change /// instead of making a new function for each change
Future<void> _changeDefaultNode({ 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 { Future<void> _updateCakeXmrNode(Box<Node> nodes) async {
final node = nodes.values.firstWhereOrNull((element) => element.uriRaw == newCakeWalletMoneroUri); final node = nodes.values.firstWhereOrNull((element) => element.uriRaw == newCakeWalletMoneroUri);

View 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.');
}
}
}

View 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;
}

View file

@ -92,6 +92,7 @@ class PreferencesKey {
static const donationLinkWalletName = 'donation_link_wallet_name'; static const donationLinkWalletName = 'donation_link_wallet_name';
static const lastSeenAppVersion = 'last_seen_app_version'; static const lastSeenAppVersion = 'last_seen_app_version';
static const shouldShowMarketPlaceInDashboard = 'should_show_marketplace_in_dashboard'; static const shouldShowMarketPlaceInDashboard = 'should_show_marketplace_in_dashboard';
static const showAddressBookPopupEnabled = 'show_address_book_popup_enabled';
static const isNewInstall = 'is_new_install'; static const isNewInstall = 'is_new_install';
static const serviceStatusShaKey = 'service_status_sha_key'; static const serviceStatusShaKey = 'service_status_sha_key';
static const walletConnectPairingTopicsList = 'wallet_connect_pairing_topics_list'; static const walletConnectPairingTopicsList = 'wallet_connect_pairing_topics_list';

View file

@ -60,15 +60,17 @@ class ExolixExchangeProvider extends ExchangeProvider {
Future<bool> checkIsAvailable() async => true; Future<bool> checkIsAvailable() async => true;
@override @override
Future<Limits> fetchLimits( Future<Limits> fetchLimits({
{required CryptoCurrency from, required CryptoCurrency from,
required CryptoCurrency to, required CryptoCurrency to,
required bool isFixedRateMode}) async { required bool isFixedRateMode,
}) async {
final params = <String, String>{ final params = <String, String>{
'rateType': _getRateType(isFixedRateMode), 'rateType': _getRateType(isFixedRateMode),
'amount': '1', 'amount': '1',
'apiToken': apiKey, 'apiToken': apiKey,
}; };
if (isFixedRateMode) { if (isFixedRateMode) {
params['coinFrom'] = _normalizeCurrency(to); params['coinFrom'] = _normalizeCurrency(to);
params['coinTo'] = _normalizeCurrency(from); params['coinTo'] = _normalizeCurrency(from);
@ -80,14 +82,30 @@ class ExolixExchangeProvider extends ExchangeProvider {
params['networkFrom'] = _networkFor(from); params['networkFrom'] = _networkFor(from);
params['networkTo'] = _networkFor(to); params['networkTo'] = _networkFor(to);
} }
// Maximum of 2 attempts to fetch limits
for (int i = 0; i < 2; i++) {
final uri = Uri.https(apiBaseUrl, ratePath, params); final uri = Uri.https(apiBaseUrl, ratePath, params);
final response = await get(uri); final response = await get(uri);
if (response.statusCode != 200) if (response.statusCode == 200) {
throw Exception('Unexpected http status: ${response.statusCode}');
final responseJSON = json.decode(response.body) as Map<String, dynamic>; final responseJSON = json.decode(response.body) as Map<String, dynamic>;
return Limits(min: responseJSON['minAmount'] as double?); 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 @override
@ -279,4 +297,15 @@ class ExolixExchangeProvider extends ExchangeProvider {
String _normalizeAddress(String address) => String _normalizeAddress(String address) =>
address.startsWith('bitcoincash:') ? address.replaceFirst('bitcoincash:', '') : 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;
}
} }

View file

@ -168,6 +168,7 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
final status = responseJSON['status'] as String; final status = responseJSON['status'] as String;
final createdAtString = responseJSON['created_at'] as String; final createdAtString = responseJSON['created_at'] as String;
final expiredAtTimestamp = responseJSON['expired_at'] as int; final expiredAtTimestamp = responseJSON['expired_at'] as int;
final extraId = responseJSON['deposit_extra_id'] as String?;
final createdAt = DateTime.parse(createdAtString); final createdAt = DateTime.parse(createdAtString);
final expiredAt = DateTime.fromMillisecondsSinceEpoch(expiredAtTimestamp * 1000); final expiredAt = DateTime.fromMillisecondsSinceEpoch(expiredAtTimestamp * 1000);
@ -199,6 +200,7 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
state: TradeState.deserialize(raw: status), state: TradeState.deserialize(raw: status),
createdAt: createdAt, createdAt: createdAt,
expiredAt: expiredAt, expiredAt: expiredAt,
extraId: extraId,
); );
} catch (e) { } catch (e) {
log(e.toString()); log(e.toString());
@ -231,6 +233,7 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
final status = responseJSON['status'] as String; final status = responseJSON['status'] as String;
final createdAtString = responseJSON['created_at'] as String; final createdAtString = responseJSON['created_at'] as String;
final expiredAtTimestamp = responseJSON['expired_at'] as int; final expiredAtTimestamp = responseJSON['expired_at'] as int;
final extraId = responseJSON['deposit_extra_id'] as String?;
final createdAt = DateTime.parse(createdAtString); final createdAt = DateTime.parse(createdAtString);
final expiredAt = DateTime.fromMillisecondsSinceEpoch(expiredAtTimestamp * 1000); final expiredAt = DateTime.fromMillisecondsSinceEpoch(expiredAtTimestamp * 1000);
@ -249,6 +252,7 @@ class LetsExchangeExchangeProvider extends ExchangeProvider {
createdAt: createdAt, createdAt: createdAt,
expiredAt: expiredAt, expiredAt: expiredAt,
isRefund: status == 'refund', isRefund: status == 'refund',
extraId: extraId,
); );
} }

View file

@ -203,6 +203,7 @@ class SideShiftExchangeProvider extends ExchangeProvider {
final inputAddress = responseJSON['depositAddress'] as String; final inputAddress = responseJSON['depositAddress'] as String;
final settleAddress = responseJSON['settleAddress'] as String; final settleAddress = responseJSON['settleAddress'] as String;
final depositAmount = responseJSON['depositAmount'] as String?; final depositAmount = responseJSON['depositAmount'] as String?;
final depositMemo = responseJSON['depositMemo'] as String?;
return Trade( return Trade(
id: id, id: id,
@ -217,6 +218,7 @@ class SideShiftExchangeProvider extends ExchangeProvider {
payoutAddress: settleAddress, payoutAddress: settleAddress,
createdAt: DateTime.now(), createdAt: DateTime.now(),
isSendAll: isSendAll, isSendAll: isSendAll,
extraId: depositMemo
); );
} }
@ -251,6 +253,7 @@ class SideShiftExchangeProvider extends ExchangeProvider {
final isVariable = (responseJSON['type'] as String) == 'variable'; final isVariable = (responseJSON['type'] as String) == 'variable';
final expiredAtRaw = responseJSON['expiresAt'] as String; final expiredAtRaw = responseJSON['expiresAt'] as String;
final expiredAt = isVariable ? null : DateTime.tryParse(expiredAtRaw)?.toLocal(); final expiredAt = isVariable ? null : DateTime.tryParse(expiredAtRaw)?.toLocal();
final depositMemo = responseJSON['depositMemo'] as String?;
return Trade( return Trade(
id: id, id: id,
@ -261,7 +264,8 @@ class SideShiftExchangeProvider extends ExchangeProvider {
amount: expectedSendAmount ?? '', amount: expectedSendAmount ?? '',
state: TradeState.deserialize(raw: status ?? 'created'), state: TradeState.deserialize(raw: status ?? 'created'),
expiredAt: expiredAt, expiredAt: expiredAt,
payoutAddress: settleAddress); payoutAddress: settleAddress,
extraId: depositMemo);
} }
Future<String> _createQuote(TradeRequest request) async { Future<String> _createQuote(TradeRequest request) async {

View file

@ -154,6 +154,7 @@ class StealthExExchangeProvider extends ExchangeProvider {
final receiveAmount = toDouble(withdrawal['amount']); final receiveAmount = toDouble(withdrawal['amount']);
final status = responseJSON['status'] as String; final status = responseJSON['status'] as String;
final createdAtString = responseJSON['created_at'] as String; final createdAtString = responseJSON['created_at'] as String;
final extraId = deposit['extra_id'] as String?;
final createdAt = DateTime.parse(createdAtString); final createdAt = DateTime.parse(createdAtString);
final expiredAt = validUntil != null final expiredAt = validUntil != null
@ -188,6 +189,7 @@ class StealthExExchangeProvider extends ExchangeProvider {
state: TradeState.deserialize(raw: status), state: TradeState.deserialize(raw: status),
createdAt: createdAt, createdAt: createdAt,
expiredAt: expiredAt, expiredAt: expiredAt,
extraId: extraId,
); );
} catch (e) { } catch (e) {
log(e.toString()); log(e.toString());
@ -220,6 +222,7 @@ class StealthExExchangeProvider extends ExchangeProvider {
final status = responseJSON['status'] as String; final status = responseJSON['status'] as String;
final createdAtString = responseJSON['created_at'] as String; final createdAtString = responseJSON['created_at'] as String;
final createdAt = DateTime.parse(createdAtString); final createdAt = DateTime.parse(createdAtString);
final extraId = deposit['extra_id'] as String?;
return Trade( return Trade(
id: respId, id: respId,
@ -234,6 +237,7 @@ class StealthExExchangeProvider extends ExchangeProvider {
state: TradeState.deserialize(raw: status), state: TradeState.deserialize(raw: status),
createdAt: createdAt, createdAt: createdAt,
isRefund: status == 'refunded', isRefund: status == 'refunded',
extraId: extraId,
); );
} }

View file

@ -171,7 +171,8 @@ class TrocadorExchangeProvider extends ExchangeProvider {
if (!isFixedRateMode) 'amount_from': request.fromAmount, if (!isFixedRateMode) 'amount_from': request.fromAmount,
if (isFixedRateMode) 'amount_to': request.toAmount, if (isFixedRateMode) 'amount_to': request.toAmount,
'address': request.toAddress, 'address': request.toAddress,
'refund': request.refundAddress 'refund': request.refundAddress,
'refund_memo' : '0',
}; };
if (isFixedRateMode) { if (isFixedRateMode) {
@ -262,6 +263,7 @@ class TrocadorExchangeProvider extends ExchangeProvider {
final password = responseJSON['password'] as String; final password = responseJSON['password'] as String;
final providerId = responseJSON['id_provider'] as String; final providerId = responseJSON['id_provider'] as String;
final providerName = responseJSON['provider'] as String; final providerName = responseJSON['provider'] as String;
final addressProviderMemo = responseJSON['address_provider_memo'] as String?;
return Trade( return Trade(
id: id, id: id,
@ -277,6 +279,7 @@ class TrocadorExchangeProvider extends ExchangeProvider {
password: password, password: password,
providerId: providerId, providerId: providerId,
providerName: providerName, providerName: providerName,
extraId: addressProviderMemo,
); );
}); });
} }

View file

@ -143,6 +143,7 @@ class Trade extends HiveObject {
isRefund: map['isRefund'] as bool?, isRefund: map['isRefund'] as bool?,
isSendAll: map['isSendAll'] as bool?, isSendAll: map['isSendAll'] as bool?,
router: map['router'] as String?, router: map['router'] as String?,
extraId: map['extra_id'] as String?,
); );
} }
@ -162,6 +163,7 @@ class Trade extends HiveObject {
'isRefund': isRefund, 'isRefund': isRefund,
'isSendAll': isSendAll, 'isSendAll': isSendAll,
'router': router, 'router': router,
'extra_id': extraId,
}; };
} }

View file

@ -307,6 +307,23 @@ class CWHaven extends Haven {
return havenTransactionInfo.accountIndex; 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 @override
WalletService createHavenWalletService(Box<WalletInfo> walletInfoSource) { WalletService createHavenWalletService(Box<WalletInfo> walletInfoSource) {
return HavenWalletService(walletInfoSource); return HavenWalletService(walletInfoSource);

View file

@ -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/default_settings_migration.dart';
import 'package:cake_wallet/entities/get_encryption_key.dart'; import 'package:cake_wallet/entities/get_encryption_key.dart';
import 'package:cake_wallet/core/secure_storage.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/language_service.dart';
import 'package:cake_wallet/entities/template.dart'; import 'package:cake_wallet/entities/template.dart';
import 'package:cake_wallet/entities/transaction_description.dart'; import 'package:cake_wallet/entities/transaction_description.dart';
@ -164,6 +165,10 @@ Future<void> initializeAppConfigs() async {
CakeHive.registerAdapter(AnonpayInvoiceInfoAdapter()); CakeHive.registerAdapter(AnonpayInvoiceInfoAdapter());
} }
if (!CakeHive.isAdapterRegistered(HavenSeedStore.typeId)) {
CakeHive.registerAdapter(HavenSeedStoreAdapter());
}
if (!CakeHive.isAdapterRegistered(MwebUtxo.typeId)) { if (!CakeHive.isAdapterRegistered(MwebUtxo.typeId)) {
CakeHive.registerAdapter(MwebUtxoAdapter()); CakeHive.registerAdapter(MwebUtxoAdapter());
} }
@ -188,6 +193,12 @@ Future<void> initializeAppConfigs() async {
final anonpayInvoiceInfo = await CakeHive.openBox<AnonpayInvoiceInfo>(AnonpayInvoiceInfo.boxName); final anonpayInvoiceInfo = await CakeHive.openBox<AnonpayInvoiceInfo>(AnonpayInvoiceInfo.boxName);
final unspentCoinsInfoSource = await CakeHive.openBox<UnspentCoinsInfo>(UnspentCoinsInfo.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( await initialSetup(
sharedPreferences: await SharedPreferences.getInstance(), sharedPreferences: await SharedPreferences.getInstance(),
nodes: nodes, nodes: nodes,
@ -203,7 +214,8 @@ Future<void> initializeAppConfigs() async {
transactionDescriptions: transactionDescriptions, transactionDescriptions: transactionDescriptions,
secureStorage: secureStorage, secureStorage: secureStorage,
anonpayInvoiceInfo: anonpayInvoiceInfo, anonpayInvoiceInfo: anonpayInvoiceInfo,
initialMigrationVersion: 44, havenSeedStore: havenSeedStore,
initialMigrationVersion: 45,
); );
} }
@ -222,7 +234,8 @@ Future<void> initialSetup(
required SecureStorage secureStorage, required SecureStorage secureStorage,
required Box<AnonpayInvoiceInfo> anonpayInvoiceInfo, required Box<AnonpayInvoiceInfo> anonpayInvoiceInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfoSource, required Box<UnspentCoinsInfo> unspentCoinsInfoSource,
int initialMigrationVersion = 15}) async { required Box<HavenSeedStore> havenSeedStore,
int initialMigrationVersion = 15, }) async {
LanguageService.loadLocaleList(); LanguageService.loadLocaleList();
await defaultSettingsMigration( await defaultSettingsMigration(
secureStorage: secureStorage, secureStorage: secureStorage,
@ -232,7 +245,8 @@ Future<void> initialSetup(
contactSource: contactSource, contactSource: contactSource,
tradeSource: tradesSource, tradeSource: tradesSource,
nodes: nodes, nodes: nodes,
powNodes: powNodes); powNodes: powNodes,
havenSeedStore: havenSeedStore);
await setup( await setup(
walletInfoSource: walletInfoSource, walletInfoSource: walletInfoSource,
nodeSource: nodes, nodeSource: nodes,

View file

@ -44,13 +44,16 @@ void startAuthenticationStateChange(
} catch (error, stack) { } catch (error, stack) {
loginError = error; loginError = error;
await ExceptionHandler.resetLastPopupDate(); await ExceptionHandler.resetLastPopupDate();
await ExceptionHandler.onError(FlutterErrorDetails(exception: error, stack: stack)); await ExceptionHandler.onError(
FlutterErrorDetails(exception: error, stack: stack));
} }
return; return;
} }
if (state == AuthenticationState.allowed) { if ([AuthenticationState.allowed, AuthenticationState.allowedCreate]
if (requireHardwareWalletConnection()) { .contains(state)) {
if (state == AuthenticationState.allowed &&
requireHardwareWalletConnection()) {
await navigatorKey.currentState!.pushNamedAndRemoveUntil( await navigatorKey.currentState!.pushNamedAndRemoveUntil(
Routes.connectDevices, Routes.connectDevices,
(route) => false, (route) => false,

View file

@ -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_choose_derivation.dart';
import 'package:cake_wallet/src/screens/restore/wallet_restore_page.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/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/seed/wallet_seed_page.dart';
import 'package:cake_wallet/src/screens/send/send_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/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/connection_sync_page.dart';
import 'package:cake_wallet/src/screens/settings/desktop_settings/desktop_settings_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'; import 'package:cake_wallet/src/screens/settings/display_settings_page.dart';
@ -597,6 +599,10 @@ Route<dynamic> createRoute(RouteSettings settings) {
return MaterialPageRoute<void>( return MaterialPageRoute<void>(
builder: (_) => getIt.get<PreSeedPage>(param1: settings.arguments as int)); 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: case Routes.backup:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<BackupPage>()); 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: default:
return MaterialPageRoute<void>( return MaterialPageRoute<void>(
builder: (_) => Scaffold( builder: (_) => Scaffold(

View file

@ -53,6 +53,7 @@ class Routes {
static const restoreWalletType = '/restore_wallet_type'; static const restoreWalletType = '/restore_wallet_type';
static const restoreWallet = '/restore_wallet'; static const restoreWallet = '/restore_wallet';
static const preSeedPage = '/pre_seed_page'; static const preSeedPage = '/pre_seed_page';
static const transactionSuccessPage = '/transaction_success_info_page';
static const backup = '/backup'; static const backup = '/backup';
static const editBackupPassword = '/edit_backup_passowrd'; static const editBackupPassword = '/edit_backup_passowrd';
static const restoreFromBackup = '/restore_from_backup'; static const restoreFromBackup = '/restore_from_backup';
@ -115,4 +116,5 @@ class Routes {
static const urqrAnimatedPage = '/urqr/animated_page'; static const urqrAnimatedPage = '/urqr/animated_page';
static const walletGroupsDisplayPage = '/wallet_groups_display_page'; static const walletGroupsDisplayPage = '/wallet_groups_display_page';
static const walletGroupDescription = '/wallet_group_description'; static const walletGroupDescription = '/wallet_group_description';
static const walletSeedVerificationPage = '/wallet_seed_verification_page';
} }

View file

@ -116,7 +116,7 @@ class CakePayCardsPage extends BasePage {
}, },
child: Container( child: Container(
width: 32, width: 32,
padding: EdgeInsets.all(8), padding: EdgeInsets.only(top: 7, bottom: 7),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor, color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
border: Border.all( border: Border.all(
@ -125,7 +125,7 @@ class CakePayCardsPage extends BasePage {
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),
child: Image.asset( child: Image.asset(
'assets/images/filter.png', 'assets/images/filter_icon.png',
color: Theme.of(context).extension<FilterTheme>()!.iconColor, color: Theme.of(context).extension<FilterTheme>()!.iconColor,
))), ))),
); );
@ -141,14 +141,14 @@ class CakePayCardsPage extends BasePage {
} }
}, },
child: Container( child: Container(
padding: EdgeInsets.symmetric(horizontal: 6), padding: EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor, color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
border: Border.all(color: Colors.transparent), border: Border.all(color: Colors.transparent),
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
), ),
child: Container( child: Container(
margin: EdgeInsets.symmetric(vertical: 2), margin: EdgeInsets.symmetric(vertical: 4),
child: Row( child: Row(
children: [ children: [
Image.asset( Image.asset(
@ -363,12 +363,9 @@ class _SearchWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final searchIcon = ExcludeSemantics( final searchIcon = ExcludeSemantics(
child: Padding( child: Icon( Icons.search,
padding: EdgeInsets.all(8),
child: Image.asset(
'assets/images/mini_search_icon.png',
color: Theme.of(context).extension<FilterTheme>()!.iconColor, color: Theme.of(context).extension<FilterTheme>()!.iconColor,
), //size: 24
), ),
); );
@ -379,8 +376,8 @@ class _SearchWidget extends StatelessWidget {
decoration: InputDecoration( decoration: InputDecoration(
filled: true, filled: true,
contentPadding: EdgeInsets.only( contentPadding: EdgeInsets.only(
top: 10, top: 8,
left: 10, left: 8,
), ),
fillColor: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor, fillColor: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
hintText: S.of(context).search, hintText: S.of(context).search,

View file

@ -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/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_one_action.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.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/primary_button.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.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/cake_text_theme.dart';
import 'package:cake_wallet/themes/extensions/picker_theme.dart'; import 'package:cake_wallet/themes/extensions/picker_theme.dart';
import 'package:cake_wallet/themes/extensions/receive_page_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/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:url_launcher/url_launcher.dart';
class CakePayBuyCardDetailPage extends BasePage { class CakePayBuyCardDetailPage extends BasePage {
CakePayBuyCardDetailPage(this.cakePayPurchaseViewModel); CakePayBuyCardDetailPage(this.cakePayPurchaseViewModel);
@ -207,8 +210,10 @@ class CakePayBuyCardDetailPage extends BasePage {
padding: EdgeInsets.only(bottom: 12), padding: EdgeInsets.only(bottom: 12),
child: Observer(builder: (_) { child: Observer(builder: (_) {
return LoadingPrimaryButton( return LoadingPrimaryButton(
isLoading: cakePayPurchaseViewModel.sendViewModel.state is IsExecutingState, isDisabled: cakePayPurchaseViewModel.isPurchasing,
onPressed: () => purchaseCard(context), isLoading: cakePayPurchaseViewModel.isPurchasing ||
cakePayPurchaseViewModel.sendViewModel.state is IsExecutingState,
onPressed: () => confirmPurchaseFirst(context),
text: S.of(context).purchase_gift_card, text: S.of(context).purchase_gift_card,
color: Theme.of(context).primaryColor, color: Theme.of(context).primaryColor,
textColor: Colors.white, 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 { Future<void> purchaseCard(BuildContext context) async {
bool isLogged = await cakePayPurchaseViewModel.cakePayService.isLogged(); bool isLogged = await cakePayPurchaseViewModel.cakePayService.isLogged();
if (!isLogged) { if (!isLogged) {
@ -263,7 +310,9 @@ class CakePayBuyCardDetailPage extends BasePage {
} catch (_) { } catch (_) {
await cakePayPurchaseViewModel.cakePayService.logout(); await cakePayPurchaseViewModel.cakePayService.logout();
} }
} }
cakePayPurchaseViewModel.isPurchasing = false;
} }
void _showHowToUseCard( 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,
),
),
),
],
),
);
}
}

View file

@ -22,11 +22,13 @@ class ConnectDevicePageParams {
final WalletType walletType; final WalletType walletType;
final OnConnectDevice onConnectDevice; final OnConnectDevice onConnectDevice;
final bool allowChangeWallet; final bool allowChangeWallet;
final bool isReconnect;
ConnectDevicePageParams({ ConnectDevicePageParams({
required this.walletType, required this.walletType,
required this.onConnectDevice, required this.onConnectDevice,
this.allowChangeWallet = false, this.allowChangeWallet = false,
this.isReconnect = false,
}); });
} }
@ -34,19 +36,33 @@ class ConnectDevicePage extends BasePage {
final WalletType walletType; final WalletType walletType;
final OnConnectDevice onConnectDevice; final OnConnectDevice onConnectDevice;
final bool allowChangeWallet; final bool allowChangeWallet;
final bool isReconnect;
final LedgerViewModel ledgerVM; final LedgerViewModel ledgerVM;
ConnectDevicePage(ConnectDevicePageParams params, this.ledgerVM) ConnectDevicePage(ConnectDevicePageParams params, this.ledgerVM)
: walletType = params.walletType, : walletType = params.walletType,
onConnectDevice = params.onConnectDevice, onConnectDevice = params.onConnectDevice,
allowChangeWallet = params.allowChangeWallet; allowChangeWallet = params.allowChangeWallet,
isReconnect = params.isReconnect;
@override @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 @override
Widget body(BuildContext context) => ConnectDevicePageBody( Widget? leading(BuildContext context) =>
walletType, onConnectDevice, allowChangeWallet, ledgerVM); !isReconnect ? super.leading(context) : null;
@override
Widget body(BuildContext context) => PopScope(
canPop: !isReconnect,
child: ConnectDevicePageBody(
walletType,
onConnectDevice,
allowChangeWallet,
ledgerVM,
));
} }
class ConnectDevicePageBody extends StatefulWidget { class ConnectDevicePageBody extends StatefulWidget {
@ -75,6 +91,8 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
late Timer? _bleStateTimer = null; late Timer? _bleStateTimer = null;
late StreamSubscription<LedgerDevice>? _bleRefresh = null; late StreamSubscription<LedgerDevice>? _bleRefresh = null;
bool longWait = false;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -89,6 +107,11 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
_usbRefreshTimer = _usbRefreshTimer =
Timer.periodic(Duration(seconds: 1), (_) => _refreshUsbDevices()); 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(); _bleStateTimer?.cancel();
_usbRefreshTimer?.cancel(); _usbRefreshTimer?.cancel();
_bleRefresh?.cancel(); _bleRefresh?.cancel();
widget.ledgerVM.stopScanning();
super.dispose(); super.dispose();
} }
@ -118,9 +143,11 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
Future<void> _refreshBleDevices() async { Future<void> _refreshBleDevices() async {
try { try {
if (widget.ledgerVM.bleIsEnabled) { if (widget.ledgerVM.bleIsEnabled) {
_bleRefresh = widget.ledgerVM _bleRefresh =
.scanForBleDevices() widget.ledgerVM.scanForBleDevices().listen((device) => setState(() {
.listen((device) => setState(() => bleDevices.add(device))) bleDevices.add(device);
if (longWait) longWait = false;
}))
..onError((e) { ..onError((e) {
throw e.toString(); throw e.toString();
}); });
@ -175,15 +202,21 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
), ),
// DeviceTile( Offstage(
// onPressed: () => Navigator.of(context).push( offstage: !longWait,
// MaterialPageRoute<void>( child: Padding(
// builder: (BuildContext context) => DebugDevicePage(), padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
// ), child: Text(S.of(context).if_you_dont_see_your_device,
// ), style: TextStyle(
// title: "Debug Ledger", fontSize: 16,
// leading: imageLedger, fontWeight: FontWeight.w500,
// ), color: Theme.of(context)
.extension<CakeTextTheme>()!
.titleColor),
textAlign: TextAlign.center,
),
),
),
Observer( Observer(
builder: (_) => Offstage( builder: (_) => Offstage(
offstage: widget.ledgerVM.bleIsEnabled, offstage: widget.ledgerVM.bleIsEnabled,

View file

@ -322,6 +322,7 @@ class _ContactListBodyState extends State<ContactListBody> {
? widget.contactListViewModel.contacts ? widget.contactListViewModel.contacts
: widget.contactListViewModel.contactsToShow; : widget.contactListViewModel.contactsToShow;
return Scaffold( return Scaffold(
backgroundColor: Theme.of(context).colorScheme.background,
body: Container( body: Container(
child: FilteredList( child: FilteredList(
list: contacts, list: contacts,

View file

@ -119,12 +119,7 @@ class CryptoBalanceWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return SingleChildScrollView(
onLongPress: () => dashboardViewModel.balanceViewModel.isReversing =
!dashboardViewModel.balanceViewModel.isReversing,
onLongPressUp: () => dashboardViewModel.balanceViewModel.isReversing =
!dashboardViewModel.balanceViewModel.isReversing,
child: SingleChildScrollView(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
@ -326,7 +321,7 @@ class CryptoBalanceWidget extends StatelessWidget {
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
onTap: () => launchUrl( onTap: () => launchUrl(
Uri.parse( Uri.parse(
"https://guides.cakewallet.com/docs/cryptos/bitcoin/#silent-payments"), "https://docs.cakewallet.com/cryptos/bitcoin#silent-payments"),
mode: LaunchMode.externalApplication, mode: LaunchMode.externalApplication,
), ),
child: Row( child: Row(
@ -556,7 +551,6 @@ class CryptoBalanceWidget extends StatelessWidget {
}), }),
], ],
), ),
),
); );
} }
@ -705,9 +699,14 @@ class BalanceRowWidget extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row( GestureDetector(
onTap: () => dashboardViewModel.balanceViewModel.switchBalanceValue(),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
GestureDetector( GestureDetector(
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
@ -715,10 +714,7 @@ class BalanceRowWidget extends StatelessWidget {
? () => _showBalanceDescription( ? () => _showBalanceDescription(
context, S.of(context).available_balance_description) context, S.of(context).available_balance_description)
: null, : null,
child: Column( child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [ children: [
Semantics( Semantics(
hint: 'Double tap to see more information', hint: 'Double tap to see more information',
@ -744,6 +740,7 @@ class BalanceRowWidget extends StatelessWidget {
), ),
], ],
), ),
),
SizedBox(height: 6), SizedBox(height: 6),
AutoSizeText(availableBalance, AutoSizeText(availableBalance,
style: TextStyle( style: TextStyle(
@ -775,9 +772,10 @@ class BalanceRowWidget extends StatelessWidget {
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Theme.of(context).extension<BalancePageTheme>()!.textColor, color: Theme.of(context).extension<BalancePageTheme>()!.textColor,
height: 1)), height: 1)),
], ],
), ),
),
SizedBox( SizedBox(
width: min(MediaQuery.of(context).size.width * 0.2, 100), width: min(MediaQuery.of(context).size.width * 0.2, 100),
child: Center( child: Center(
@ -820,6 +818,7 @@ class BalanceRowWidget extends StatelessWidget {
), ),
], ],
), ),
),
if (frozenBalance.isNotEmpty) if (frozenBalance.isNotEmpty)
GestureDetector( GestureDetector(
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
@ -886,7 +885,9 @@ class BalanceRowWidget extends StatelessWidget {
), ),
), ),
if (hasAdditionalBalance) if (hasAdditionalBalance)
Column( GestureDetector(
onTap: () => dashboardViewModel.balanceViewModel.switchBalanceValue(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SizedBox(height: 24), SizedBox(height: 24),
@ -929,12 +930,13 @@ class BalanceRowWidget extends StatelessWidget {
), ),
], ],
), ),
),
], ],
), ),
), ),
), ),
if (hasSecondAdditionalBalance || hasSecondAvailableBalance) ...[ if (hasSecondAdditionalBalance || hasSecondAvailableBalance) ...[
SizedBox(height: 16), SizedBox(height: 10),
Container( Container(
margin: const EdgeInsets.only(left: 16, right: 16), margin: const EdgeInsets.only(left: 16, right: 16),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -989,7 +991,9 @@ class BalanceRowWidget extends StatelessWidget {
], ],
), ),
if (hasSecondAvailableBalance) if (hasSecondAvailableBalance)
Row( GestureDetector(
onTap: () => dashboardViewModel.balanceViewModel.switchBalanceValue(),
child: Row(
children: [ children: [
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -998,7 +1002,7 @@ class BalanceRowWidget extends StatelessWidget {
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
onTap: () => launchUrl( onTap: () => launchUrl(
Uri.parse( Uri.parse(
"https://guides.cakewallet.com/docs/cryptos/litecoin/#mweb"), "https://docs.cakewallet.com/cryptos/litecoin.html#mweb"),
mode: LaunchMode.externalApplication, mode: LaunchMode.externalApplication,
), ),
child: Row( child: Row(
@ -1061,6 +1065,7 @@ class BalanceRowWidget extends StatelessWidget {
), ),
], ],
), ),
),
], ],
), ),
), ),

View file

@ -53,7 +53,7 @@ class TransactionsPage extends StatelessWidget {
onTap: () { onTap: () {
try { try {
final uri = Uri.parse( 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); launchUrl(uri, mode: LaunchMode.externalApplication);
} catch (_) {} } catch (_) {}
}, },

View file

@ -347,7 +347,7 @@ class _WalletNameFormState extends State<WalletNameForm> {
key: ValueKey('new_wallet_page_confirm_button_key'), key: ValueKey('new_wallet_page_confirm_button_key'),
onPressed: _confirmForm, onPressed: _confirmForm,
text: S.of(context).seed_language_next, text: S.of(context).seed_language_next,
color: Colors.green, color: Theme.of(context).primaryColor,
textColor: Colors.white, textColor: Colors.white,
isLoading: _walletNewVM.state is IsExecutingState, isLoading: _walletNewVM.state is IsExecutingState,
isDisabled: _walletNewVM.name.isEmpty, isDisabled: _walletNewVM.name.isEmpty,

View file

@ -40,7 +40,7 @@ class SelectButton extends StatelessWidget {
@override @override
Widget build(BuildContext context) { 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 ?? final effectiveTextColor = textColor ??
(isSelected (isSelected
? Theme.of(context).extension<WalletListTheme>()!.restoreWalletButtonTextColor ? Theme.of(context).extension<WalletListTheme>()!.restoreWalletButtonTextColor

View file

@ -31,7 +31,7 @@ class _HeaderTileState extends State<HeaderTile> {
@override @override
Widget build(BuildContext context) { 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); color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor);
return Container( return Container(

View file

@ -56,9 +56,8 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
} }
if (isMoneroOnly) { if (isMoneroOnly) {
// return DeviceConnectionType.supportedConnectionTypes(WalletType.monero, Platform.isIOS) return DeviceConnectionType.supportedConnectionTypes(WalletType.monero, Platform.isIOS)
// .isNotEmpty; .isNotEmpty;
return false;
} }
return true; return true;

View file

@ -25,5 +25,5 @@ class PreSeedPage extends InfoPage {
@override @override
void Function(BuildContext) get onPressed => 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);
} }

View file

@ -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),
),
);
},
);
}
}

View file

@ -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';
}
}
}

View file

@ -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),
],
),
);
}
}

View file

@ -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/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/themes/theme_base.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/utils/clipboard_util.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/widgets/primary_button.dart';
import 'package:cake_wallet/src/screens/base_page.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/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 { class WalletSeedPage extends BasePage {
WalletSeedPage(this.walletSeedViewModel, {required this.isNewWalletCreated}); WalletSeedPage(this.walletSeedViewModel, {required this.isNewWalletCreated});
@ -29,62 +30,34 @@ class WalletSeedPage extends BasePage {
final bool isNewWalletCreated; final bool isNewWalletCreated;
final WalletSeedViewModel walletSeedViewModel; 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 @override
Widget trailing(BuildContext context) { Widget trailing(BuildContext context) {
final copyImage = Image.asset(
'assets/images/copy_address.png',
color: Theme.of(context)
.extension<CakeTextTheme>()!
.buttonTextColor
);
return isNewWalletCreated return isNewWalletCreated
? GestureDetector( ? GestureDetector(
key: ValueKey('wallet_seed_page_next_button_key'), key: ValueKey('wallet_seed_page_copy_seeds_button_key'),
onTap: () => onClose(context), onTap: () {
ClipboardUtil.setSensitiveDataToClipboard(
ClipboardData(text: walletSeedViewModel.seed),
);
showBar<void>(context, S.of(context).copied_to_clipboard);
},
child: Container( child: Container(
width: 100, padding: EdgeInsets.all(8),
height: 32, width: 40,
alignment: Alignment.center, alignment: Alignment.center,
margin: EdgeInsets.only(left: 10), margin: EdgeInsets.only(left: 10),
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(16)), borderRadius: BorderRadius.all(Radius.circular(8)),
color: Theme.of(context).cardColor), 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),
), ),
child: copyImage,
), ),
) )
: Offstage(); : Offstage();
@ -92,12 +65,10 @@ class WalletSeedPage extends BasePage {
@override @override
Widget body(BuildContext context) { Widget body(BuildContext context) {
final image = currentTheme.type == ThemeType.dark ? imageDark : imageLight;
return WillPopScope( return WillPopScope(
onWillPop: () async => false, onWillPop: () async => false,
child: Container( child: Container(
padding: EdgeInsets.all(24), padding: EdgeInsets.symmetric(horizontal: 14, vertical: 8),
alignment: Alignment.center, alignment: Alignment.center,
child: ConstrainedBox( child: ConstrainedBox(
constraints: constraints:
@ -105,65 +76,130 @@ class WalletSeedPage extends BasePage {
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[ children: <Widget>[
ConstrainedBox( Observer(
constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.3), builder: (_) {
child: AspectRatio(aspectRatio: 1, child: image), return Expanded(
), child: Column(
Observer(builder: (_) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[ children: <Widget>[
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( Text(
key: ValueKey('wallet_seed_page_wallet_name_text_key'), key: ValueKey('wallet_seed_page_wallet_name_text_key'),
walletSeedViewModel.name, walletSeedViewModel.name,
style: TextStyle( style: TextStyle(
fontSize: 20, fontSize: 18,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor), color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
), ),
Padding( ),
padding: EdgeInsets.only(top: 20, left: 16, right: 16), 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( child: Text(
key: ValueKey('wallet_seed_page_wallet_seed_text_key'), //maxLines: 1,
walletSeedViewModel.seed, numberCount.toString(),
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, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.normal, height: 1,
fontWeight: FontWeight.w800,
color: Theme.of(context) color: Theme.of(context)
.extension<TransactionTradeTheme>()! .extension<CakeTextTheme>()!
.detailsTitlesColor, .buttonTextColor
.withOpacity(0.5)),
), ),
), ),
) const SizedBox(width: 6),
: Offstage(), 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( Row(
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
children: <Widget>[ children: <Widget>[
Flexible( Flexible(
child: Container( child: Container(
padding: EdgeInsets.only(right: 8.0), padding: EdgeInsets.only(right: 8.0, top: 8.0),
child: PrimaryButton( child: PrimaryButton(
key: ValueKey('wallet_seed_page_save_seeds_button_key'), key: ValueKey('wallet_seed_page_save_seeds_button_key'),
onPressed: () { onPressed: () {
@ -173,37 +209,37 @@ class WalletSeedPage extends BasePage {
); );
}, },
text: S.of(context).save, text: S.of(context).save,
color: Colors.green, color: Theme.of(context).cardColor,
textColor: Colors.white, textColor: currentTheme.type == ThemeType.dark
? Theme.of(context).extension<DashboardPageTheme>()!.textColor
: Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor,
), ),
), ),
), ),
Flexible( Flexible(
child: Container( child: Container(
padding: EdgeInsets.only(left: 8.0), padding: EdgeInsets.only(left: 8.0, top: 8.0),
child: Builder( child: Builder(
builder: (context) => PrimaryButton( builder: (context) => PrimaryButton(
key: ValueKey('wallet_seed_page_copy_seeds_button_key'), key: ValueKey('wallet_seed_page_verify_seed_button_key'),
onPressed: () { onPressed: () =>
ClipboardUtil.setSensitiveDataToClipboard( Navigator.pushNamed(context, Routes.walletSeedVerificationPage),
ClipboardData(text: walletSeedViewModel.seed), text: S.current.verify_seed,
); color: Theme.of(context).primaryColor,
showBar<void>(context, S.of(context).copied_to_clipboard);
},
text: S.of(context).copy,
color: Theme.of(context).extension<PinCodeTheme>()!.indicatorsColor,
textColor: Colors.white, textColor: Colors.white,
), ),
), ),
), ),
) )
], ],
) ),
SizedBox(height: 12),
], ],
) )
], ],
), ),
), ),
)); ),
);
} }
} }

View file

@ -500,82 +500,59 @@ class SendPage extends BasePage {
actionRightButton: () async { actionRightButton: () async {
Navigator.of(_dialogContext).pop(); Navigator.of(_dialogContext).pop();
sendViewModel.commitTransaction(context); sendViewModel.commitTransaction(context);
await showPopUp<void>( },
context: context, actionLeftButton: () => Navigator.of(_dialogContext).pop());
builder: (BuildContext _dialogContext) { });
return Observer(builder: (_) { }
final state = sendViewModel.state; });
if (state is FailureState) {
Navigator.of(_dialogContext).pop();
} }
if (state is TransactionCommitted) { if (state is TransactionCommitted) {
newContactAddress = WidgetsBinding.instance.addPostFrameCallback((_) async {
newContactAddress ?? sendViewModel.newContactAddress();
if (sendViewModel.coinTypeToSpendFrom != UnspentCoinType.any) { final successMessage = S.of(context).send_success(
newContactAddress = null;
}
final successMessage = S.of(_dialogContext).send_success(
sendViewModel.selectedCryptoCurrency.toString()); sendViewModel.selectedCryptoCurrency.toString());
final waitMessage = sendViewModel.walletType == WalletType.solana final waitMessage = sendViewModel.walletType == WalletType.solana
? '. ${S.of(_dialogContext).waitFewSecondForTxUpdate}' ? '. ${S.of(context).waitFewSecondForTxUpdate}'
: ''; : '';
final newContactMessage = newContactAddress != null String alertContent = "$successMessage$waitMessage";
? '\n${S.of(_dialogContext).add_contact_to_address_book}'
: '';
String alertContent = await Navigator.of(context).pushNamed(
"$successMessage$waitMessage$newContactMessage"; Routes.transactionSuccessPage,
arguments: alertContent
);
if (newContactAddress != null) { newContactAddress = newContactAddress ?? sendViewModel.newContactAddress();
return AlertWithTwoActions( 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'), alertDialogKey: ValueKey('send_page_sent_dialog_key'),
alertTitle: '', alertTitle: '',
alertContent: alertContent, alertContent: S.of(_dialogContext).add_contact_to_address_book,
rightButtonText: S.of(_dialogContext).add_contact, rightButtonText: S.of(_dialogContext).add_contact,
leftButtonText: S.of(_dialogContext).ignor, leftButtonText: S.of(_dialogContext).ignor,
alertLeftActionButtonKey: alertLeftActionButtonKey: ValueKey('send_page_sent_dialog_ignore_button_key'),
ValueKey('send_page_sent_dialog_ignore_button_key'), alertRightActionButtonKey:
alertRightActionButtonKey: ValueKey( ValueKey('send_page_sent_dialog_add_contact_button_key'),
'send_page_sent_dialog_add_contact_button_key'),
actionRightButton: () { actionRightButton: () {
Navigator.of(_dialogContext).pop(); Navigator.of(_dialogContext).pop();
RequestReviewHandler.requestReview(); RequestReviewHandler.requestReview();
Navigator.of(context).pushNamed( Navigator.of(context)
Routes.addressBookAddContact, .pushNamed(Routes.addressBookAddContact, arguments: newContactAddress);
arguments: newContactAddress);
newContactAddress = null; newContactAddress = null;
}, },
actionLeftButton: () { actionLeftButton: () {
Navigator.of(_dialogContext).pop(); Navigator.of(_dialogContext).pop();
RequestReviewHandler.requestReview(); RequestReviewHandler.requestReview();
newContactAddress = null; 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) { if (initialPaymentRequest?.callbackUrl?.isNotEmpty ?? false) {
// wait a second so it's not as jarring: // wait a second so it's not as jarring:
await Future.delayed(Duration(seconds: 1)); await Future.delayed(Duration(seconds: 1));
@ -588,16 +565,7 @@ class SendPage extends BasePage {
printV(e); printV(e);
} }
} }
}
},
actionLeftButton: () => Navigator.of(_dialogContext).pop());
});
}
});
}
if (state is TransactionCommitted) {
WidgetsBinding.instance.addPostFrameCallback((_) {
sendViewModel.clearOutputs(); sendViewModel.clearOutputs();
}); });
} }
@ -612,7 +580,10 @@ class SendPage extends BasePage {
alertTitle: S.of(context).proceed_on_device, alertTitle: S.of(context).proceed_on_device,
alertContent: S.of(context).proceed_on_device_description, alertContent: S.of(context).proceed_on_device_description,
buttonText: S.of(context).cancel, buttonText: S.of(context).cancel,
buttonAction: () => Navigator.of(context).pop()); buttonAction: () {
sendViewModel.state = InitialExecutionState();
Navigator.of(context).pop();
});
}); });
}); });
} }

View 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();
}

View file

@ -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/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_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_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/src/screens/settings/widgets/settings_version_cell.dart';
import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart'; import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
@ -62,6 +63,13 @@ class OtherSettingsPage extends BasePage {
handler: (BuildContext context) => handler: (BuildContext context) =>
Navigator.of(context).pushNamed(Routes.readDisclaimer), 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(), Spacer(),
SettingsVersionCell( SettingsVersionCell(
title: S.of(context).version(_otherSettingsViewModel.currentVersion)), title: S.of(context).version(_otherSettingsViewModel.currentVersion)),

View file

@ -20,7 +20,7 @@ class Setup2FAPage extends BasePage {
Widget body(BuildContext context) { Widget body(BuildContext context) {
final cake2FAGuideTitle = 'Cake 2FA Guide'; final cake2FAGuideTitle = 'Cake 2FA Guide';
final cake2FAGuideUri = final cake2FAGuideUri =
Uri.parse('https://guides.cakewallet.com/docs/advanced-features/authentication'); Uri.parse('https://docs.cakewallet.com/features/advanced/authentication/');
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [

View file

@ -198,8 +198,9 @@ class TOTPEnterCode extends BasePage {
}, },
); );
if (isForSetup && result) { if (isForSetup && result) {
Navigator.pushReplacementNamed( if (context.mounted) {
context, Routes.modify2FAPage); Navigator.pushReplacementNamed(context, Routes.modify2FAPage);
}
} }
}, },

View file

@ -6,7 +6,6 @@ import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.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/clipboard_util.dart';
import 'package:cake_wallet/utils/show_bar.dart'; import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart'; import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart';
@ -30,7 +29,7 @@ class Setup2FAQRPage extends BasePage {
width: 16, width: 16,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor); color: Theme.of(context).extension<CakeTextTheme>()!.titleColor);
final cake2FAHowToUseUrl = Uri.parse( 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( return Padding(
padding: const EdgeInsets.symmetric(horizontal: 24), padding: const EdgeInsets.symmetric(horizontal: 24),
child: Column( child: Column(

View file

@ -70,7 +70,7 @@ class SupportPage extends BasePage {
), ),
title: S.of(context).support_title_guides, title: S.of(context).support_title_guides,
description: S.of(context).support_description_guides, description: S.of(context).support_description_guides,
onPressed: () => _launchUrl(supportViewModel.guidesUrl), onPressed: () => _launchUrl(supportViewModel.docsUrl),
), ),
), ),
Padding( Padding(

View file

@ -8,7 +8,8 @@ class TradeDetailsListCardItem extends StandartListItem {
{required this.id, {required this.id,
required this.createdAt, required this.createdAt,
required this.pair, required this.pair,
required this.onTap}) required this.onTap,
this.extraId})
: super(title: '', value: ''); : super(title: '', value: '');
factory TradeDetailsListCardItem.tradeDetails( factory TradeDetailsListCardItem.tradeDetails(
@ -16,9 +17,19 @@ class TradeDetailsListCardItem extends StandartListItem {
required String createdAt, required String createdAt,
required CryptoCurrency from, required CryptoCurrency from,
required CryptoCurrency to, 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( return TradeDetailsListCardItem(
id: '${S.current.trade_details_id} ${formatAsText(id)}', id: '${S.current.trade_details_id} ${formatAsText(id)}',
extraId: extraId != null ? '$extraIdTitle $extraId' : null,
createdAt: formatAsText(createdAt), createdAt: formatAsText(createdAt),
pair: '${formatAsText(from)}${formatAsText(to)}', pair: '${formatAsText(from)}${formatAsText(to)}',
onTap: onTap); onTap: onTap);
@ -27,6 +38,7 @@ class TradeDetailsListCardItem extends StandartListItem {
final String id; final String id;
final String createdAt; final String createdAt;
final String pair; final String pair;
final String? extraId;
final void Function(BuildContext) onTap; final void Function(BuildContext) onTap;
static String formatAsText<T>(T value) => value?.toString() ?? ''; static String formatAsText<T>(T value) => value?.toString() ?? '';

View file

@ -69,6 +69,7 @@ class TradeDetailsPageBodyState extends State<TradeDetailsPageBody> {
if (item is TradeDetailsListCardItem) if (item is TradeDetailsListCardItem)
return TradeDetailsStandardListCard( return TradeDetailsStandardListCard(
id: item.id, id: item.id,
extraId: item.extraId,
create: item.createdAt, create: item.createdAt,
pair: item.pair, pair: item.pair,
currentTheme: tradeDetailsViewModel.settingsStore.currentTheme.type, currentTheme: tradeDetailsViewModel.settingsStore.currentTheme.type,

View file

@ -154,7 +154,7 @@ class UnspentCoinsListFormState extends State<UnspentCoinsListForm> {
SizedBox(height: 15), SizedBox(height: 15),
Expanded( Expanded(
child: unspentCoinsListViewModel.items.isEmpty 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( : ListView.separated(
itemCount: unspentCoinsListViewModel.items.length, itemCount: unspentCoinsListViewModel.items.length,
separatorBuilder: (_, __) => SizedBox(height: 15), separatorBuilder: (_, __) => SizedBox(height: 15),

View file

@ -84,6 +84,7 @@ class WalletKeysPage extends BasePage {
child: Observer( child: Observer(
builder: (_) { builder: (_) {
return ListView.separated( return ListView.separated(
key: ValueKey('wallet_keys_page_credentials_list_view_key'),
separatorBuilder: (context, index) => Container( separatorBuilder: (context, index) => Container(
height: 1, height: 1,
padding: EdgeInsets.only(left: 24), padding: EdgeInsets.only(left: 24),

View file

@ -334,6 +334,26 @@ class WalletListBodyState extends State<WalletListBody> {
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(24),
child: Column( child: Column(
children: <Widget>[ 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( PrimaryImageButton(
key: ValueKey('wallet_list_page_create_new_wallet_button_key'), key: ValueKey('wallet_list_page_create_new_wallet_button_key'),
onPressed: () { onPressed: () {
@ -373,26 +393,6 @@ class WalletListBodyState extends State<WalletListBody> {
color: Theme.of(context).primaryColor, color: Theme.of(context).primaryColor,
textColor: Colors.white, 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; if (!isAuthenticatedSuccessfully) return;
try { try {
if (widget.walletListViewModel final requireHardwareWalletConnection = widget.walletListViewModel
.requireHardwareWalletConnection(wallet)) { .requireHardwareWalletConnection(wallet);
if (requireHardwareWalletConnection) {
await Navigator.of(context).pushNamed( await Navigator.of(context).pushNamed(
Routes.connectDevices, Routes.connectDevices,
arguments: ConnectDevicePageParams( arguments: ConnectDevicePageParams(
@ -445,8 +446,6 @@ class WalletListBodyState extends State<WalletListBody> {
); );
} }
changeProcessText( changeProcessText(
S.of(context).wallet_list_loading_wallet(wallet.name)); S.of(context).wallet_list_loading_wallet(wallet.name));
await widget.walletListViewModel.loadWallet(wallet); await widget.walletListViewModel.loadWallet(wallet);
@ -456,6 +455,9 @@ class WalletListBodyState extends State<WalletListBody> {
if (responsiveLayoutUtil.shouldRenderMobileUI) { if (responsiveLayoutUtil.shouldRenderMobileUI) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (this.mounted) { if (this.mounted) {
if (requireHardwareWalletConnection) {
Navigator.of(context).pop();
}
widget.onWalletLoaded.call(context); widget.onWalletLoaded.call(context);
} }
}); });

View file

@ -17,13 +17,14 @@ class SearchBarWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return TextFormField( return TextFormField(
key: ValueKey('search_bar_widget_key'),
controller: searchController, controller: searchController,
style: TextStyle(color: Theme.of(context).extension<PickerTheme>()!.searchHintColor), style: TextStyle(color: Theme.of(context).extension<PickerTheme>()!.searchHintColor),
decoration: InputDecoration( decoration: InputDecoration(
hintText: hintText ?? S.of(context).search, hintText: hintText ?? S.of(context).search,
hintStyle: TextStyle(color: Theme.of(context).extension<PickerTheme>()!.searchHintColor), hintStyle: TextStyle(color: Theme.of(context).extension<PickerTheme>()!.searchHintColor),
prefixIcon: Image.asset("assets/images/search_icon.png", prefixIcon: Icon( Icons.search,
color: Theme.of(context).extension<PickerTheme>()!.searchIconColor), color: Theme.of(context).primaryColor),
filled: true, filled: true,
fillColor: Theme.of(context).extension<PickerTheme>()!.searchBackgroundFillColor, fillColor: Theme.of(context).extension<PickerTheme>()!.searchBackgroundFillColor,
alignLabelWithHint: false, alignLabelWithHint: false,

View file

@ -26,7 +26,9 @@ class StandardCheckbox extends StatelessWidget {
], begin: Alignment.centerLeft, end: Alignment.centerRight); ], begin: Alignment.centerLeft, end: Alignment.centerRight);
final boxBorder = Border.all( 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( final checkedBoxDecoration = BoxDecoration(
gradient: gradientBackground ? baseGradient : null, gradient: gradientBackground ? baseGradient : null,
@ -41,6 +43,7 @@ class StandardCheckbox extends StatelessWidget {
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Container( Container(
height: 24.0, height: 24.0,
@ -55,13 +58,22 @@ class StandardCheckbox extends StatelessWidget {
: Offstage(), : Offstage(),
), ),
if (caption.isNotEmpty) if (caption.isNotEmpty)
Padding( Flexible(
child: Padding(
padding: EdgeInsets.only(left: 10), padding: EdgeInsets.only(left: 10),
child: Text( child: Text(
caption, caption,
softWrap: true,
style: TextStyle( 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,
),
),
),
)
], ],
), ),
); );

View file

@ -6,12 +6,14 @@ import 'package:cake_wallet/themes/theme_base.dart';
class TradeDetailsStandardListCard extends StatelessWidget { class TradeDetailsStandardListCard extends StatelessWidget {
TradeDetailsStandardListCard( TradeDetailsStandardListCard(
{required this.id, {required this.id,
this.extraId,
required this.create, required this.create,
required this.pair, required this.pair,
required this.onTap, required this.onTap,
required this.currentTheme}); required this.currentTheme});
final String id; final String id;
final String? extraId;
final String create; final String create;
final String pair; final String pair;
final ThemeType currentTheme; final ThemeType currentTheme;
@ -57,6 +59,16 @@ class TradeDetailsStandardListCard extends StatelessWidget {
SizedBox( SizedBox(
height: 8, 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, Text(create,
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,

View file

@ -4,7 +4,7 @@ part 'authentication_store.g.dart';
class AuthenticationStore = AuthenticationStoreBase with _$AuthenticationStore; class AuthenticationStore = AuthenticationStoreBase with _$AuthenticationStore;
enum AuthenticationState { uninitialized, installed, allowed, _reset } enum AuthenticationState { uninitialized, installed, allowed, allowedCreate, _reset }
abstract class AuthenticationStoreBase with Store { abstract class AuthenticationStoreBase with Store {
AuthenticationStoreBase() : state = AuthenticationState.uninitialized; AuthenticationStoreBase() : state = AuthenticationState.uninitialized;
@ -23,4 +23,10 @@ abstract class AuthenticationStoreBase with Store {
state = AuthenticationState._reset; state = AuthenticationState._reset;
state = AuthenticationState.allowed; state = AuthenticationState.allowed;
} }
@action
void allowedCreate() {
state = AuthenticationState._reset;
state = AuthenticationState.allowedCreate;
}
} }

View file

@ -54,6 +54,7 @@ abstract class SettingsStoreBase with Store {
required BackgroundTasks backgroundTasks, required BackgroundTasks backgroundTasks,
required SharedPreferences sharedPreferences, required SharedPreferences sharedPreferences,
required bool initialShouldShowMarketPlaceInDashboard, required bool initialShouldShowMarketPlaceInDashboard,
required bool initialShowAddressBookPopupEnabled,
required FiatCurrency initialFiatCurrency, required FiatCurrency initialFiatCurrency,
required BalanceDisplayMode initialBalanceDisplayMode, required BalanceDisplayMode initialBalanceDisplayMode,
required bool initialSaveRecipientAddress, required bool initialSaveRecipientAddress,
@ -158,6 +159,7 @@ abstract class SettingsStoreBase with Store {
walletListAscending = initialWalletListAscending, walletListAscending = initialWalletListAscending,
contactListAscending = initialContactListAscending, contactListAscending = initialContactListAscending,
shouldShowMarketPlaceInDashboard = initialShouldShowMarketPlaceInDashboard, shouldShowMarketPlaceInDashboard = initialShouldShowMarketPlaceInDashboard,
showAddressBookPopupEnabled = initialShowAddressBookPopupEnabled,
exchangeStatus = initialExchangeStatus, exchangeStatus = initialExchangeStatus,
currentTheme = initialTheme, currentTheme = initialTheme,
pinCodeLength = initialPinLength, pinCodeLength = initialPinLength,
@ -355,6 +357,11 @@ abstract class SettingsStoreBase with Store {
(bool value) => (bool value) =>
sharedPreferences.setBool(PreferencesKey.shouldShowMarketPlaceInDashboard, value)); sharedPreferences.setBool(PreferencesKey.shouldShowMarketPlaceInDashboard, value));
reaction(
(_) => showAddressBookPopupEnabled,
(bool value) =>
sharedPreferences.setBool(PreferencesKey.showAddressBookPopupEnabled, value));
reaction((_) => pinCodeLength, reaction((_) => pinCodeLength,
(int pinLength) => sharedPreferences.setInt(PreferencesKey.currentPinLength, pinLength)); (int pinLength) => sharedPreferences.setInt(PreferencesKey.currentPinLength, pinLength));
@ -612,6 +619,9 @@ abstract class SettingsStoreBase with Store {
@observable @observable
bool shouldShowMarketPlaceInDashboard; bool shouldShowMarketPlaceInDashboard;
@observable
bool showAddressBookPopupEnabled;
@observable @observable
ObservableList<ActionListDisplayMode> actionlistDisplayMode; ObservableList<ActionListDisplayMode> actionlistDisplayMode;
@ -926,6 +936,8 @@ abstract class SettingsStoreBase with Store {
final tokenTrialNumber = sharedPreferences.getInt(PreferencesKey.failedTotpTokenTrials) ?? 0; final tokenTrialNumber = sharedPreferences.getInt(PreferencesKey.failedTotpTokenTrials) ?? 0;
final shouldShowMarketPlaceInDashboard = final shouldShowMarketPlaceInDashboard =
sharedPreferences.getBool(PreferencesKey.shouldShowMarketPlaceInDashboard) ?? true; sharedPreferences.getBool(PreferencesKey.shouldShowMarketPlaceInDashboard) ?? true;
final showAddressBookPopupEnabled =
sharedPreferences.getBool(PreferencesKey.showAddressBookPopupEnabled) ?? true;
final exchangeStatus = ExchangeApiMode.deserialize( final exchangeStatus = ExchangeApiMode.deserialize(
raw: sharedPreferences.getInt(PreferencesKey.exchangeStatusKey) ?? raw: sharedPreferences.getInt(PreferencesKey.exchangeStatusKey) ??
ExchangeApiMode.enabled.raw); ExchangeApiMode.enabled.raw);
@ -1196,6 +1208,7 @@ abstract class SettingsStoreBase with Store {
secureStorage: secureStorage, secureStorage: secureStorage,
sharedPreferences: sharedPreferences, sharedPreferences: sharedPreferences,
initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard, initialShouldShowMarketPlaceInDashboard: shouldShowMarketPlaceInDashboard,
initialShowAddressBookPopupEnabled: showAddressBookPopupEnabled,
nodes: nodes, nodes: nodes,
powNodes: powNodes, powNodes: powNodes,
appVersion: packageInfo.version, appVersion: packageInfo.version,
@ -1373,6 +1386,9 @@ abstract class SettingsStoreBase with Store {
shouldShowMarketPlaceInDashboard = shouldShowMarketPlaceInDashboard =
sharedPreferences.getBool(PreferencesKey.shouldShowMarketPlaceInDashboard) ?? sharedPreferences.getBool(PreferencesKey.shouldShowMarketPlaceInDashboard) ??
shouldShowMarketPlaceInDashboard; shouldShowMarketPlaceInDashboard;
showAddressBookPopupEnabled =
sharedPreferences.getBool(PreferencesKey.showAddressBookPopupEnabled) ??
showAddressBookPopupEnabled;
exchangeStatus = ExchangeApiMode.deserialize( exchangeStatus = ExchangeApiMode.deserialize(
raw: sharedPreferences.getInt(PreferencesKey.exchangeStatusKey) ?? raw: sharedPreferences.getInt(PreferencesKey.exchangeStatusKey) ??
ExchangeApiMode.enabled.raw); ExchangeApiMode.enabled.raw);

View file

@ -1,9 +1,11 @@
import 'dart:io'; import 'dart:io';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/main.dart'; import 'package:cake_wallet/main.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.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_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cw_core/root_dir.dart'; import 'package:cw_core/root_dir.dart';
@ -29,9 +31,21 @@ class ExceptionHandler {
_file = File('${appDocDir.path}/error.txt'); _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 = { final exception = {
"${DateTime.now()}": { "${DateTime.now()}": {
"Error": "$error\n\n", "Error": "$error\n\n",
"WalletType": "$walletType\n\n",
"VerboseLog":
"${programInfo?.fileName}#${programInfo?.lineNumber}:${programInfo?.columnNumber} ${programInfo?.callerFunctionName}\n\n",
"Library": "$library\n\n", "Library": "$library\n\n",
"StackTrace": stackTrace.toString(), "StackTrace": stackTrace.toString(),
} }

View file

@ -3,4 +3,5 @@ class FeatureFlag {
static const bool isExolixEnabled = true; static const bool isExolixEnabled = true;
static const bool isInAppTorEnabled = false; static const bool isInAppTorEnabled = false;
static const bool isBackgroundSyncEnabled = false; static const bool isBackgroundSyncEnabled = false;
static const int verificationWordsCount = 2;
} }

View file

@ -49,6 +49,13 @@ abstract class CakePayPurchaseViewModelBase with Store {
String get fiatCurrency => paymentCredential.fiatCurrency; String get fiatCurrency => paymentCredential.fiatCurrency;
bool confirmsNoVpn = false;
bool confirmsVoidedRefund = false;
bool confirmsTermsAgreed = false;
@observable
bool isPurchasing = false;
CryptoPaymentData? get cryptoPaymentData { CryptoPaymentData? get cryptoPaymentData {
if (order == null) return null; if (order == null) return null;
@ -88,7 +95,11 @@ abstract class CakePayPurchaseViewModelBase with Store {
order = await cakePayService.createOrder( order = await cakePayService.createOrder(
cardId: card.id, cardId: card.id,
price: paymentCredential.amount.toString(), price: paymentCredential.amount.toString(),
quantity: paymentCredential.quantity); quantity: paymentCredential.quantity,
confirmsNoVpn: confirmsNoVpn,
confirmsVoidedRefund: confirmsVoidedRefund,
confirmsTermsAgreed: confirmsTermsAgreed,
);
await confirmSending(); await confirmSending();
expirationTime = order!.paymentData.expirationTime; expirationTime = order!.paymentData.expirationTime;
updateRemainingTime(); updateRemainingTime();

Some files were not shown because too many files have changed in this diff Show more