Merge branch 'main' into CW-873-fix-airgap-export-output-flow

This commit is contained in:
cyan 2025-01-02 09:14:35 +01:00 committed by GitHub
commit 2e4c5c4470
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
103 changed files with 2218 additions and 1983 deletions

View file

@ -4,7 +4,7 @@ contact_links:
url: https://github.com/cake-tech/cake_wallet/discussions/new?category=feature-requests
about: Suggest an idea for Cake Wallet
- name: Not sure where to start?
url: https://guides.cakewallet.com
url: https://docs.cakewallet.com
about: Start by reading checking out the guides!
- name: Need help?
url: https://cakewallet.com/#contact

View file

@ -1,298 +1,299 @@
#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
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 ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> lib/.secrets.g.dart
echo "const chainStackApiKey = '${{ secrets.CHAIN_STACK_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 chainStackApiKey = '${{ secrets.CHAIN_STACK_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

@ -182,8 +182,8 @@ jobs:
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 chainStackApiKey = '${{ secrets.CHAIN_STACK_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
@ -197,6 +197,7 @@ jobs:
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 chainStackApiKey = '${{ secrets.CHAIN_STACK_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

View file

@ -154,8 +154,8 @@ jobs:
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 chainStackApiKey = '${{ secrets.CHAIN_STACK_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
@ -167,6 +167,7 @@ jobs:
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 chainStackApiKey = '${{ secrets.CHAIN_STACK_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

View file

@ -1,5 +1,7 @@
-
uri: ethereum.publicnode.com
uri: ethereum-rpc.publicnode.com
useSSL: true
isDefault: true
-
uri: eth.llamarpc.com
-

BIN
assets/images/discord.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
assets/images/discourse.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View file

@ -1,7 +1,9 @@
-
uri: polygon-rpc.com
-
uri: polygon-bor.publicnode.com
uri: polygon-bor-rpc.publicnode.com
useSSL: true
isDefault: true
-
uri: polygon.llamarpc.com
-

View file

@ -1,10 +1,13 @@
-
uri: rpc.ankr.com
is_default: true
useSSL: true
-
uri: api.mainnet-beta.solana.com:443
useSSL: true
-
uri: solana-rpc.publicnode.com:443
useSSL: true
useSSL: true
-
uri: solana-mainnet.core.chainstack.com
useSSL: true
is_default: true

View file

@ -1,2 +1,3 @@
UI/UX enhancements
Bug fixes and app improvements
Support Monero Ledger
Bug fixes
New designs and better user experience

View file

@ -1,2 +1,5 @@
UI/UX enhancements
Bug fixes and app improvements
Support Monero Ledger
Prepare for Haven removal
Improve Ethereum and Polygon sending process
Bug fixes
New designs and better user experience

View file

@ -4,9 +4,8 @@
useSSL: true
-
uri: api.trongrid.io
is_default: false
is_default: true
useSSL: true
-
uri: trx.nownodes.io
is_default: true
useSSL: true

View file

@ -235,7 +235,7 @@ class ElectrumClient {
return [];
});
Future<List<Map<String, dynamic>>> getListUnspent(String scriptHash) async {
Future<List<Map<String, dynamic>>?> getListUnspent(String scriptHash) async {
final result = await call(method: 'blockchain.scripthash.listunspent', params: [scriptHash]);
if (result is List) {
@ -248,7 +248,7 @@ class ElectrumClient {
}).toList();
}
return [];
return null;
}
Future<List<Map<String, dynamic>>> getMempool(String scriptHash) =>

View file

@ -39,7 +39,7 @@ class ElectrumBalance extends Balance {
int secondUnconfirmed = 0;
@override
String get formattedAvailableBalance => bitcoinAmountToString(amount: confirmed - frozen);
String get formattedAvailableBalance => bitcoinAmountToString(amount: ((confirmed + unconfirmed) - frozen) );
@override
String get formattedAdditionalBalance => bitcoinAmountToString(amount: unconfirmed);
@ -58,7 +58,7 @@ class ElectrumBalance extends Balance {
@override
String get formattedFullAvailableBalance =>
bitcoinAmountToString(amount: confirmed + secondConfirmed - frozen);
bitcoinAmountToString(amount: (confirmed + unconfirmed) + secondConfirmed - frozen);
String toJSON() => json.encode({
'confirmed': confirmed,

View file

@ -4,7 +4,6 @@ import 'dart:io';
import 'dart:isolate';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/litecoin_wallet_addresses.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_bitcoin/bitcoin_wallet.dart';
import 'package:cw_bitcoin/litecoin_wallet.dart';
@ -478,6 +477,7 @@ abstract class ElectrumWalletBase
if (alwaysScan == true) {
_setListeners(walletInfo.restoreHeight);
} else {
if (syncStatus is LostConnectionSyncStatus) return;
syncStatus = SyncedSyncStatus();
}
} catch (e, stacktrace) {
@ -990,6 +990,9 @@ abstract class ElectrumWalletBase
@override
Future<PendingTransaction> createTransaction(Object credentials) async {
try {
// start by updating unspent coins
await updateAllUnspents();
final outputs = <BitcoinOutput>[];
final transactionCredentials = credentials as BitcoinTransactionCredentials;
final hasMultiDestination = transactionCredentials.outputs.length > 1;
@ -1099,6 +1102,7 @@ abstract class ElectrumWalletBase
)..addListener((transaction) async {
transactionHistory.addOne(transaction);
await updateBalance();
await updateAllUnspents();
});
}
@ -1191,6 +1195,7 @@ abstract class ElectrumWalletBase
.removeWhere((utxo) => estimatedTx.utxos.any((e) => e.utxo.txHash == utxo.hash));
await updateBalance();
await updateAllUnspents();
});
} catch (e) {
throw e;
@ -1359,6 +1364,10 @@ abstract class ElectrumWalletBase
Future<void> updateAllUnspents() async {
List<BitcoinUnspent> updatedUnspentCoins = [];
final previousUnspentCoins = List<BitcoinUnspent>.from(unspentCoins.where((utxo) =>
utxo.bitcoinAddressRecord.type != SegwitAddresType.mweb &&
utxo.bitcoinAddressRecord is! BitcoinSilentPaymentAddressRecord));
if (hasSilentPaymentsScanning) {
// Update unspents stored from scanned silent payment transactions
transactionHistory.transactions.values.forEach((tx) {
@ -1375,13 +1384,27 @@ abstract class ElectrumWalletBase
if (addr is! BitcoinSilentPaymentAddressRecord) addr.balance = 0;
});
await Future.wait(walletAddresses.allAddresses
final addressFutures = walletAddresses.allAddresses
.where((element) => element.type != SegwitAddresType.mweb)
.map((address) async {
updatedUnspentCoins.addAll(await fetchUnspent(address));
}));
.map((address) => fetchUnspent(address))
.toList();
unspentCoins = updatedUnspentCoins;
final results = await Future.wait(addressFutures);
final failedCount = results.where((result) => result == null).length;
if (failedCount == 0) {
for (final result in results) {
updatedUnspentCoins.addAll(result!);
}
unspentCoins = updatedUnspentCoins;
} else {
unspentCoins = handleFailedUtxoFetch(
failedCount: failedCount,
previousUnspentCoins: previousUnspentCoins,
updatedUnspentCoins: updatedUnspentCoins,
results: results,
);
}
final currentWalletUnspentCoins =
unspentCoinsInfo.values.where((element) => element.walletId == id);
@ -1394,6 +1417,38 @@ abstract class ElectrumWalletBase
await _refreshUnspentCoinsInfo();
}
List<BitcoinUnspent> handleFailedUtxoFetch({
required int failedCount,
required List<BitcoinUnspent> previousUnspentCoins,
required List<BitcoinUnspent> updatedUnspentCoins,
required List<List<BitcoinUnspent>?> results,
}) {
if (failedCount == results.length) {
printV("All UTXOs failed to fetch, falling back to previous UTXOs");
return previousUnspentCoins;
}
final successfulUtxos = <BitcoinUnspent>[];
for (final result in results) {
if (result != null) {
successfulUtxos.addAll(result);
}
}
if (failedCount > 0 && successfulUtxos.isEmpty) {
printV("Some UTXOs failed, but no successful UTXOs, falling back to previous UTXOs");
return previousUnspentCoins;
}
if (failedCount > 0) {
printV("Some UTXOs failed, updating with successful UTXOs");
updatedUnspentCoins.addAll(successfulUtxos);
}
return updatedUnspentCoins;
}
Future<void> updateCoins(List<BitcoinUnspent> newUnspentCoins) async {
if (newUnspentCoins.isEmpty) {
return;
@ -1425,15 +1480,17 @@ abstract class ElectrumWalletBase
@action
Future<void> updateUnspentsForAddress(BitcoinAddressRecord address) async {
final newUnspentCoins = await fetchUnspent(address);
await updateCoins(newUnspentCoins);
await updateCoins(newUnspentCoins ?? []);
}
@action
Future<List<BitcoinUnspent>> fetchUnspent(BitcoinAddressRecord address) async {
List<Map<String, dynamic>> unspents = [];
Future<List<BitcoinUnspent>?> fetchUnspent(BitcoinAddressRecord address) async {
List<BitcoinUnspent> updatedUnspentCoins = [];
unspents = await electrumClient.getListUnspent(address.getScriptHash(network));
final unspents = await electrumClient.getListUnspent(address.getScriptHash(network));
// Failed to fetch unspents
if (unspents == null) return null;
await Future.wait(unspents.map((unspent) async {
try {
@ -1796,6 +1853,7 @@ abstract class ElectrumWalletBase
});
transactionHistory.addOne(transaction);
await updateBalance();
await updateAllUnspents();
});
} catch (e) {
throw e;
@ -2157,18 +2215,6 @@ abstract class ElectrumWalletBase
var totalConfirmed = 0;
var totalUnconfirmed = 0;
unspentCoinsInfo.values.forEach((info) {
unspentCoins.forEach((element) {
if (element.hash == info.hash &&
element.vout == info.vout &&
info.isFrozen &&
element.bitcoinAddressRecord.address == info.address &&
element.value == info.value) {
totalFrozen += element.value;
}
});
});
if (hasSilentPaymentsScanning) {
// Add values from unspent coins that are not fetched by the address list
// i.e. scanned silent payments
@ -2184,6 +2230,20 @@ abstract class ElectrumWalletBase
});
}
unspentCoinsInfo.values.forEach((info) {
unspentCoins.forEach((element) {
if (element.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) return;
if (element.hash == info.hash &&
element.vout == info.vout &&
info.isFrozen &&
element.bitcoinAddressRecord.address == info.address &&
element.value == info.value) {
totalFrozen += element.value;
}
});
});
final balances = await Future.wait(balanceFutures);
if (balances.isNotEmpty && balances.first['confirmed'] == null) {

View file

@ -349,8 +349,10 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
type: addressPageType,
network: network,
);
_addresses.add(address);
Future.delayed(Duration.zero, () => updateAddressesByMatch());
Future.delayed(Duration.zero, () {
_addresses.add(address);
updateAddressesByMatch();
});
return address;
}

View file

@ -758,6 +758,13 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
if (!mwebEnabled) return false;
if (!tx.isPending) return false;
final isMwebTx = (tx.inputAddresses?.any((addr) => addr.contains("mweb")) ?? false) ||
(tx.outputAddresses?.any((addr) => addr.contains("mweb")) ?? false);
if (!isMwebTx) {
return false;
}
final outputId = <String>[], target = <String>{};
final isHash = RegExp(r'^[a-f0-9]{64}$').hasMatch;
final spendingOutputIds = tx.inputAddresses?.where(isHash) ?? [];

View file

@ -27,7 +27,7 @@ class TransactionCommitFailed implements Exception {
@override
String toString() {
return errorMessage??"unknown error";
return errorMessage ?? "unknown error";
}
}
@ -44,3 +44,15 @@ class TransactionCommitFailedBIP68Final implements Exception {}
class TransactionCommitFailedLessThanMin implements Exception {}
class TransactionInputNotSupported implements Exception {}
class SignNativeTokenTransactionRentException implements Exception {}
class CreateAssociatedTokenAccountException implements Exception {
final String errorMessage;
CreateAssociatedTokenAccountException(this.errorMessage);
}
class SignSPLTokenTransactionRentException implements Exception {}
class NoAssociatedTokenAccountException implements Exception {}

View file

@ -3,36 +3,25 @@ import 'package:cw_core/monero_amount_format.dart';
class MoneroBalance extends Balance {
MoneroBalance({required this.fullBalance, required this.unlockedBalance, this.frozenBalance = 0})
: formattedFullBalance = moneroAmountToString(amount: frozenBalance + fullBalance),
: formattedUnconfirmedBalance = moneroAmountToString(amount: fullBalance - unlockedBalance),
formattedUnlockedBalance = moneroAmountToString(amount: unlockedBalance),
formattedLockedBalance =
moneroAmountToString(amount: frozenBalance + fullBalance - unlockedBalance),
formattedFrozenBalance = moneroAmountToString(amount: frozenBalance),
super(unlockedBalance, fullBalance);
MoneroBalance.fromString(
{required this.formattedFullBalance,
required this.formattedUnlockedBalance,
this.formattedLockedBalance = '0.0'})
: fullBalance = moneroParseAmount(amount: formattedFullBalance),
unlockedBalance = moneroParseAmount(amount: formattedUnlockedBalance),
frozenBalance = moneroParseAmount(amount: formattedLockedBalance),
super(moneroParseAmount(amount: formattedUnlockedBalance),
moneroParseAmount(amount: formattedFullBalance));
final int fullBalance;
final int unlockedBalance;
final int frozenBalance;
final String formattedFullBalance;
final String formattedUnconfirmedBalance;
final String formattedUnlockedBalance;
final String formattedLockedBalance;
final String formattedFrozenBalance;
@override
String get formattedUnAvailableBalance =>
formattedLockedBalance == '0.0' ? '' : formattedLockedBalance;
formattedFrozenBalance == '0.0' ? '' : formattedFrozenBalance;
@override
String get formattedAvailableBalance => formattedUnlockedBalance;
@override
String get formattedAdditionalBalance => formattedFullBalance;
String get formattedAdditionalBalance => formattedUnconfirmedBalance;
}

View file

@ -22,8 +22,8 @@ class Node extends HiveObject with Keyable {
this.useSSL,
this.trusted = false,
this.socksProxyAddress,
this.path = '',
String? uri,
String? path,
WalletType? type,
}) {
if (uri != null) {
@ -32,9 +32,6 @@ class Node extends HiveObject with Keyable {
if (type != null) {
this.type = type;
}
if (path != null) {
this.path = path;
}
}
Node.fromMap(Map<String, Object?> map)
@ -95,19 +92,15 @@ class Node extends HiveObject with Keyable {
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.bitcoinCash:
return createUriFromElectrumAddress(uriRaw, path ?? '');
return createUriFromElectrumAddress(uriRaw, path!);
case WalletType.nano:
case WalletType.banano:
if (isSSL) {
return Uri.https(uriRaw, path ?? '');
} else {
return Uri.http(uriRaw, path ?? '');
}
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.solana:
case WalletType.tron:
return Uri.https(uriRaw, path ?? '');
return Uri.parse(
"http${isSSL ? "s" : ""}://$uriRaw${path!.startsWith("/") ? path : "/$path"}");
case WalletType.none:
throw Exception('Unexpected type ${type.toString()} for Node uri');
}
@ -159,6 +152,7 @@ class Node extends HiveObject with Keyable {
return requestMoneroNode();
case WalletType.nano:
case WalletType.banano:
return requestNanoNode();
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.bitcoinCash:
@ -205,14 +199,16 @@ class Node extends HiveObject with Keyable {
);
client.close();
if ((
response.body.contains("400 Bad Request") // Some other generic error
|| response.body.contains("plain HTTP request was sent to HTTPS port") // Cloudflare
|| response.headers["location"] != null // Generic reverse proxy
|| response.body.contains("301 Moved Permanently") // Poorly configured generic reverse proxy
) && !(useSSL??false)
) {
if ((response.body.contains("400 Bad Request") // Some other generic error
||
response.body.contains("plain HTTP request was sent to HTTPS port") // Cloudflare
||
response.headers["location"] != null // Generic reverse proxy
||
response.body
.contains("301 Moved Permanently") // Poorly configured generic reverse proxy
) &&
!(useSSL ?? false)) {
final oldUseSSL = useSSL;
useSSL = true;
try {
@ -247,7 +243,7 @@ class Node extends HiveObject with Keyable {
if (proxy == null) {
return false;
}
final proxyAddress = proxy!.split(':')[0];
final proxyAddress = proxy.split(':')[0];
final proxyPort = int.parse(proxy.split(':')[1]);
try {
final socket = await Socket.connect(proxyAddress, proxyPort, timeout: Duration(seconds: 5));
@ -278,6 +274,35 @@ class Node extends HiveObject with Keyable {
}
}
Future<bool> requestNanoNode() async {
try {
final response = await http.post(
uri,
headers: {
"Content-Type": "application/json",
"nano-app": "cake-wallet"
},
body: jsonEncode(
{
"action": "account_balance",
"account": "nano_38713x95zyjsqzx6nm1dsom1jmm668owkeb9913ax6nfgj15az3nu8xkx579",
},
),
);
final data = await jsonDecode(response.body);
if (response.statusCode != 200 ||
data["error"] != null ||
data["balance"] == null ||
data["receivable"] == null) {
throw Exception(
"Error while trying to get balance! ${data["error"] != null ? data["error"] : ""}");
}
return true;
} catch (_) {
return false;
}
}
Future<bool> requestEthereumServer() async {
try {
final response = await http.get(

View file

@ -3,36 +3,26 @@ import 'package:cw_core/wownero_amount_format.dart';
class WowneroBalance extends Balance {
WowneroBalance({required this.fullBalance, required this.unlockedBalance, this.frozenBalance = 0})
: formattedFullBalance = wowneroAmountToString(amount: fullBalance),
formattedUnlockedBalance = wowneroAmountToString(amount: unlockedBalance - frozenBalance),
formattedLockedBalance =
wowneroAmountToString(amount: frozenBalance + fullBalance - unlockedBalance),
: formattedUnconfirmedBalance = wowneroAmountToString(amount: fullBalance - unlockedBalance),
formattedUnlockedBalance = wowneroAmountToString(amount: unlockedBalance),
formattedFrozenBalance =
wowneroAmountToString(amount: frozenBalance),
super(unlockedBalance, fullBalance);
WowneroBalance.fromString(
{required this.formattedFullBalance,
required this.formattedUnlockedBalance,
this.formattedLockedBalance = '0.0'})
: fullBalance = wowneroParseAmount(amount: formattedFullBalance),
unlockedBalance = wowneroParseAmount(amount: formattedUnlockedBalance),
frozenBalance = wowneroParseAmount(amount: formattedLockedBalance),
super(wowneroParseAmount(amount: formattedUnlockedBalance),
wowneroParseAmount(amount: formattedFullBalance));
final int fullBalance;
final int unlockedBalance;
final int frozenBalance;
final String formattedFullBalance;
final String formattedUnconfirmedBalance;
final String formattedUnlockedBalance;
final String formattedLockedBalance;
final String formattedFrozenBalance;
@override
String get formattedUnAvailableBalance =>
formattedLockedBalance == '0.0' ? '' : formattedLockedBalance;
formattedFrozenBalance == '0.0' ? '' : formattedFrozenBalance;
@override
String get formattedAvailableBalance => formattedUnlockedBalance;
@override
String get formattedAdditionalBalance => formattedFullBalance;
String get formattedAdditionalBalance => formattedUnconfirmedBalance;
}

View file

@ -29,7 +29,6 @@ import 'package:cw_evm/evm_chain_transaction_model.dart';
import 'package:cw_evm/evm_chain_transaction_priority.dart';
import 'package:cw_evm/evm_chain_wallet_addresses.dart';
import 'package:cw_evm/evm_ledger_credentials.dart';
import 'package:flutter/foundation.dart';
import 'package:hex/hex.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
@ -348,7 +347,7 @@ abstract class EVMChainWalletBase
final CryptoCurrency transactionCurrency =
balance.keys.firstWhere((element) => element.title == _credentials.currency.title);
final erc20Balance = balance[transactionCurrency]!;
final currencyBalance = balance[transactionCurrency]!;
BigInt totalAmount = BigInt.zero;
BigInt estimatedFeesForTransaction = BigInt.zero;
int exponent = transactionCurrency is Erc20Token ? transactionCurrency.decimal : 18;
@ -385,7 +384,7 @@ abstract class EVMChainWalletBase
estimatedGasUnitsForTransaction = gasFeesModel.estimatedGasUnits;
maxFeePerGasForTransaction = gasFeesModel.maxFeePerGas;
if (erc20Balance.balance < totalAmount) {
if (currencyBalance.balance < totalAmount) {
throw EVMChainTransactionCreationException(transactionCurrency);
}
} else {
@ -398,7 +397,7 @@ abstract class EVMChainWalletBase
}
if (output.sendAll && transactionCurrency is Erc20Token) {
totalAmount = erc20Balance.balance;
totalAmount = currencyBalance.balance;
}
final gasFeesModel = await calculateActualEstimatedFeeForCreateTransaction(
@ -413,14 +412,15 @@ abstract class EVMChainWalletBase
maxFeePerGasForTransaction = gasFeesModel.maxFeePerGas;
if (output.sendAll && transactionCurrency is! Erc20Token) {
totalAmount = (erc20Balance.balance - estimatedFeesForTransaction);
if (estimatedFeesForTransaction > erc20Balance.balance) {
throw EVMChainTransactionFeesException();
}
totalAmount = (currencyBalance.balance - estimatedFeesForTransaction);
}
if (erc20Balance.balance < totalAmount) {
// check the fees on the base currency (Eth/Polygon)
if (estimatedFeesForTransaction > balance[currency]!.balance) {
throw EVMChainTransactionFeesException();
}
if (currencyBalance.balance < totalAmount) {
throw EVMChainTransactionCreationException(transactionCurrency);
}
}

View file

@ -11,13 +11,12 @@ class EVMChainERC20Balance extends Balance {
final int exponent;
@override
String get formattedAdditionalBalance {
final String formattedBalance = (balance / BigInt.from(10).pow(exponent)).toString();
return formattedBalance.substring(0, min(12, formattedBalance.length));
}
String get formattedAdditionalBalance => _balance();
@override
String get formattedAvailableBalance {
String get formattedAvailableBalance => _balance();
String _balance() {
final String formattedBalance = (balance / BigInt.from(10).pow(exponent)).toString();
return formattedBalance.substring(0, min(12, formattedBalance.length));
}

View file

@ -716,10 +716,10 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
url: "https://pub.dev"
source: hosted
version: "14.2.5"
version: "14.2.4"
watcher:
dependency: "direct overridden"
description:

View file

@ -510,6 +510,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
}
Future<void> updateUnspent() async {
await transaction_history.txHistoryMutex.acquire();
try {
refreshCoins(walletAddresses.account!.id);
@ -538,6 +539,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
if (unspentCoinsInfo.isEmpty) {
unspentCoins.forEach((coin) => _addCoinInfo(coin));
transaction_history.txHistoryMutex.release();
return;
}
@ -562,7 +564,9 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
await _refreshUnspentCoinsInfo();
_askForUpdateBalance();
transaction_history.txHistoryMutex.release();
} catch (e, s) {
transaction_history.txHistoryMutex.release();
printV(e.toString());
onError?.call(FlutterErrorDetails(
exception: e,
@ -758,11 +762,18 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
int _getFrozenBalance() {
var frozenBalance = 0;
for (var coin in unspentCoinsInfo.values.where((element) =>
element.walletId == id &&
element.accountIndex == walletAddresses.account!.id)) {
if (coin.isFrozen && !coin.isSending) frozenBalance += coin.value;
}
unspentCoinsInfo.values.forEach((info) {
unspentCoins.forEach((element) {
if (element.hash == info.hash &&
element.vout == info.vout &&
info.isFrozen &&
element.value == info.value && info.walletId == id &&
info.accountIndex == walletAddresses.account!.id) {
if (element.isFrozen && !element.isSending) frozenBalance+= element.value;
}
});
});
return frozenBalance;
}

View file

@ -12,6 +12,7 @@ import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_service.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_monero/api/account_list.dart';
import 'package:cw_monero/api/transaction_history.dart';
import 'package:cw_monero/api/wallet_manager.dart' as monero_wallet_manager;
import 'package:cw_monero/api/wallet_manager.dart';
import 'package:cw_monero/ledger.dart';
@ -159,7 +160,7 @@ class MoneroWalletService extends WalletService<
}
await wallet.init();
await wallet.updateUnspent();
return wallet;
} catch (e) {
// TODO: Implement Exception for wallet list service.

View file

@ -829,10 +829,10 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
url: "https://pub.dev"
source: hosted
version: "14.2.5"
version: "14.2.4"
watcher:
dependency: "direct overridden"
description:

View file

@ -54,12 +54,12 @@ class NanoClient {
}
}
Map<String, String> getHeaders() {
Map<String, String> getHeaders(String host) {
final headers = Map<String, String>.from(CAKE_HEADERS);
if (_node!.uri.host == "rpc.nano.to") {
if (host == "rpc.nano.to") {
headers["key"] = nano_secrets.nano2ApiKey;
}
if (_node!.uri.host == "nano.nownodes.io") {
if (host == "nano.nownodes.io") {
headers["api-key"] = nano_secrets.nanoNowNodesApiKey;
}
return headers;
@ -68,7 +68,7 @@ class NanoClient {
Future<NanoBalance> getBalance(String address) async {
final response = await http.post(
_node!.uri,
headers: getHeaders(),
headers: getHeaders(_node!.uri.host),
body: jsonEncode(
{
"action": "account_balance",
@ -95,7 +95,7 @@ class NanoClient {
try {
final response = await http.post(
_node!.uri,
headers: getHeaders(),
headers: getHeaders(_node!.uri.host),
body: jsonEncode(
{
"action": "account_info",
@ -116,7 +116,7 @@ class NanoClient {
try {
final response = await http.post(
_node!.uri,
headers: CAKE_HEADERS,
headers: getHeaders(_node!.uri.host),
body: jsonEncode(
{
"action": "block_info",
@ -183,7 +183,7 @@ class NanoClient {
Future<String> requestWork(String hash) async {
final response = await http.post(
_powNode!.uri,
headers: getHeaders(),
headers: getHeaders(_powNode!.uri.host),
body: json.encode(
{
"action": "work_generate",
@ -226,7 +226,7 @@ class NanoClient {
final processResponse = await http.post(
_node!.uri,
headers: getHeaders(),
headers: getHeaders(_node!.uri.host),
body: processBody,
);
@ -425,7 +425,7 @@ class NanoClient {
});
final processResponse = await http.post(
_node!.uri,
headers: getHeaders(),
headers: getHeaders(_node!.uri.host),
body: processBody,
);
@ -441,7 +441,7 @@ class NanoClient {
required String privateKey,
}) async {
final receivableResponse = await http.post(_node!.uri,
headers: getHeaders(),
headers: getHeaders(_node!.uri.host),
body: jsonEncode({
"action": "receivable",
"account": destinationAddress,
@ -493,7 +493,7 @@ class NanoClient {
Future<List<NanoTransactionModel>> fetchTransactions(String address) async {
try {
final response = await http.post(_node!.uri,
headers: getHeaders(),
headers: getHeaders(_node!.uri.host),
body: jsonEncode({
"action": "account_history",
"account": address,
@ -509,15 +509,16 @@ class NanoClient {
.map<NanoTransactionModel>((transaction) => NanoTransactionModel.fromJson(transaction))
.toList();
} catch (e) {
printV(e);
return [];
printV("error fetching transactions: $e");
rethrow;
}
}
Future<List<N2Node>> getN2Reps() async {
final uri = Uri.parse(N2_REPS_ENDPOINT);
final response = await http.post(
Uri.parse(N2_REPS_ENDPOINT),
headers: CAKE_HEADERS,
uri,
headers: getHeaders(uri.host),
body: jsonEncode({"action": "reps"}),
);
try {
@ -531,9 +532,10 @@ class NanoClient {
}
Future<int> getRepScore(String rep) async {
final uri = Uri.parse(N2_REPS_ENDPOINT);
final response = await http.post(
Uri.parse(N2_REPS_ENDPOINT),
headers: CAKE_HEADERS,
uri,
headers: getHeaders(uri.host),
body: jsonEncode({
"action": "rep_info",
"account": rep,

View file

@ -874,10 +874,10 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
url: "https://pub.dev"
source: hosted
version: "14.2.5"
version: "14.2.4"
watcher:
dependency: "direct overridden"
description:

View file

@ -22,11 +22,11 @@ class PolygonClient extends EVMChainClient {
from: from,
to: to,
value: amount,
data: data,
// data: data,
maxGas: maxGas,
gasPrice: gasPrice,
maxFeePerGas: maxFeePerGas,
maxPriorityFeePerGas: maxPriorityFeePerGas,
// gasPrice: gasPrice,
// maxFeePerGas: maxFeePerGas,
// maxPriorityFeePerGas: maxPriorityFeePerGas,
);
}

View file

@ -21,22 +21,23 @@ class SolanaWalletClient {
bool connect(Node node) {
try {
Uri? rpcUri;
String webSocketUrl;
bool isModifiedNodeUri = false;
Uri rpcUri = node.uri;
String webSocketUrl = 'wss://${node.uriRaw}';
if (node.uriRaw == 'rpc.ankr.com') {
isModifiedNodeUri = true;
String ankrApiKey = secrets.ankrApiKey;
rpcUri = Uri.https(node.uriRaw, '/solana/$ankrApiKey');
webSocketUrl = 'wss://${node.uriRaw}/solana/ws/$ankrApiKey';
} else {
webSocketUrl = 'wss://${node.uriRaw}';
} else if (node.uriRaw == 'solana-mainnet.core.chainstack.com') {
String chainStackApiKey = secrets.chainStackApiKey;
rpcUri = Uri.https(node.uriRaw, '/$chainStackApiKey');
webSocketUrl = 'wss://${node.uriRaw}/$chainStackApiKey';
}
_client = SolanaClient(
rpcUrl: isModifiedNodeUri ? rpcUri! : node.uri,
rpcUrl: rpcUri,
websocketUrl: Uri.parse(webSocketUrl),
timeout: const Duration(minutes: 2),
);
@ -115,10 +116,14 @@ class SolanaWalletClient {
final message =
_getMessageForNativeTransaction(ownerKeypair, ownerKeypair.address, lamportsPerSol);
final recentBlockhash = await _getRecentBlockhash(commitment);
final latestBlockhash = await _getLatestBlockhash(commitment);
final estimatedFee =
_getFeeFromCompiledMessage(message, ownerKeypair.publicKey, recentBlockhash, commitment);
final estimatedFee = _getFeeFromCompiledMessage(
message,
ownerKeypair.publicKey,
latestBlockhash,
commitment,
);
return estimatedFee;
}
@ -131,13 +136,25 @@ class SolanaWalletClient {
List<SolanaTransactionModel> transactions = [];
try {
final response = await _client!.rpcClient.getTransactionsList(
publicKey,
final signatures = await _client!.rpcClient.getSignaturesForAddress(
publicKey.toBase58(),
commitment: Commitment.confirmed,
limit: 1000,
);
for (final tx in response) {
final List<TransactionDetails> transactionDetails = [];
for (int i = 0; i < signatures.length; i += 20) {
final response = await _client!.rpcClient.getMultipleTransactions(
signatures.sublist(i, math.min(i + 20, signatures.length)),
commitment: Commitment.confirmed,
encoding: Encoding.jsonParsed,
);
transactionDetails.addAll(response);
// to avoid reaching the node RPS limit
await Future.delayed(Duration(milliseconds: 500));
}
for (final tx in transactionDetails) {
if (tx.transaction is ParsedTransaction) {
final parsedTx = (tx.transaction as ParsedTransaction);
final message = parsedTx.message;
@ -310,16 +327,16 @@ class SolanaWalletClient {
}
}
Future<RecentBlockhash> _getRecentBlockhash(Commitment commitment) async {
final latestBlockhash =
Future<LatestBlockhash> _getLatestBlockhash(Commitment commitment) async {
final latestBlockHashResult =
await _client!.rpcClient.getLatestBlockhash(commitment: commitment).value;
final recentBlockhash = RecentBlockhash(
blockhash: latestBlockhash.blockhash,
feeCalculator: const FeeCalculator(lamportsPerSignature: 500),
final latestBlockhash = LatestBlockhash(
blockhash: latestBlockHashResult.blockhash,
lastValidBlockHeight: latestBlockHashResult.lastValidBlockHeight,
);
return recentBlockhash;
return latestBlockhash;
}
Message _getMessageForNativeTransaction(
@ -342,11 +359,11 @@ class SolanaWalletClient {
Future<double> _getFeeFromCompiledMessage(
Message message,
Ed25519HDPublicKey feePayer,
RecentBlockhash recentBlockhash,
LatestBlockhash latestBlockhash,
Commitment commitment,
) async {
final compile = message.compile(
recentBlockhash: recentBlockhash.blockhash,
recentBlockhash: latestBlockhash.blockhash,
feePayer: feePayer,
);
@ -362,16 +379,18 @@ class SolanaWalletClient {
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;
return true;
// TODO: this is not doing what the name inclines
// 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({
@ -391,12 +410,12 @@ class SolanaWalletClient {
final signers = [ownerKeypair];
RecentBlockhash recentBlockhash = await _getRecentBlockhash(commitment);
LatestBlockhash latestBlockhash = await _getLatestBlockhash(commitment);
final fee = await _getFeeFromCompiledMessage(
message,
signers.first.publicKey,
recentBlockhash,
latestBlockhash,
commitment,
);
@ -422,14 +441,14 @@ class SolanaWalletClient {
message: updatedMessage,
signers: signers,
commitment: commitment,
recentBlockhash: recentBlockhash,
latestBlockhash: latestBlockhash,
);
} else {
signedTx = await _signTransactionInternal(
message: message,
signers: signers,
commitment: commitment,
recentBlockhash: recentBlockhash,
latestBlockhash: latestBlockhash,
);
}
@ -462,6 +481,9 @@ class SolanaWalletClient {
final destinationOwner = Ed25519HDPublicKey.fromBase58(destinationAddress);
final mint = Ed25519HDPublicKey.fromBase58(tokenMint);
// Input by the user
final amount = (inputAmount * math.pow(10, tokenDecimals)).toInt();
ProgramAccount? associatedRecipientAccount;
ProgramAccount? associatedSenderAccount;
@ -484,18 +506,46 @@ class SolanaWalletClient {
}
try {
associatedRecipientAccount ??= await _client!.createAssociatedTokenAccount(
mint: mint,
owner: destinationOwner,
funder: ownerKeypair,
);
if (associatedRecipientAccount == null) {
final derivedAddress = await findAssociatedTokenAddress(
owner: destinationOwner,
mint: mint,
);
final instruction = AssociatedTokenAccountInstruction.createAccount(
mint: mint,
address: derivedAddress,
owner: ownerKeypair.publicKey,
funder: ownerKeypair.publicKey,
);
final _signedTx = await _signTransactionInternal(
message: Message.only(instruction),
signers: [ownerKeypair],
commitment: commitment,
latestBlockhash: await _getLatestBlockhash(commitment),
);
await sendTransaction(
signedTransaction: _signedTx,
commitment: commitment,
);
associatedRecipientAccount = ProgramAccount(
pubkey: derivedAddress.toBase58(),
account: Account(
owner: destinationOwner.toBase58(),
lamports: 0,
executable: false,
rentEpoch: BigInt.zero,
data: null,
),
);
}
} catch (e) {
throw SolanaCreateAssociatedTokenAccountException(e.toString());
}
// Input by the user
final amount = (inputAmount * math.pow(10, tokenDecimals)).toInt();
final instruction = TokenInstruction.transfer(
source: Ed25519HDPublicKey.fromBase58(associatedSenderAccount.pubkey),
destination: Ed25519HDPublicKey.fromBase58(associatedRecipientAccount.pubkey),
@ -507,12 +557,12 @@ class SolanaWalletClient {
final signers = [ownerKeypair];
RecentBlockhash recentBlockhash = await _getRecentBlockhash(commitment);
LatestBlockhash latestBlockhash = await _getLatestBlockhash(commitment);
final fee = await _getFeeFromCompiledMessage(
message,
signers.first.publicKey,
recentBlockhash,
latestBlockhash,
commitment,
);
@ -530,7 +580,7 @@ class SolanaWalletClient {
message: message,
signers: signers,
commitment: commitment,
recentBlockhash: recentBlockhash,
latestBlockhash: latestBlockhash,
);
sendTx() async => await sendTransaction(
@ -552,9 +602,9 @@ class SolanaWalletClient {
required Message message,
required List<Ed25519HDKeyPair> signers,
required Commitment commitment,
required RecentBlockhash recentBlockhash,
required LatestBlockhash latestBlockhash,
}) async {
final signedTx = await signTransaction(recentBlockhash, message, signers);
final signedTx = await signTransaction(latestBlockhash, message, signers);
return signedTx;
}

View file

@ -1,4 +1,5 @@
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/exceptions.dart';
class SolanaTransactionCreationException implements Exception {
final String exceptionMessage;
@ -20,18 +21,17 @@ class SolanaTransactionWrongBalanceException implements Exception {
String toString() => exceptionMessage;
}
class SolanaSignNativeTokenTransactionRentException implements Exception {}
class SolanaSignNativeTokenTransactionRentException
extends SignNativeTokenTransactionRentException {}
class SolanaCreateAssociatedTokenAccountException implements Exception {
final String exceptionMessage;
SolanaCreateAssociatedTokenAccountException(this.exceptionMessage);
class SolanaCreateAssociatedTokenAccountException extends CreateAssociatedTokenAccountException {
SolanaCreateAssociatedTokenAccountException(super.errorMessage);
}
class SolanaSignSPLTokenTransactionRentException implements Exception {}
class SolanaSignSPLTokenTransactionRentException extends SignSPLTokenTransactionRentException {}
class SolanaNoAssociatedTokenAccountException implements Exception {
const SolanaNoAssociatedTokenAccountException(this.account, this.mint);
class SolanaNoAssociatedTokenAccountException extends NoAssociatedTokenAccountException {
SolanaNoAssociatedTokenAccountException(this.account, this.mint);
final String account;
final String mint;

View file

@ -33,7 +33,6 @@ import 'package:shared_preferences/shared_preferences.dart';
import 'package:solana/base58.dart';
import 'package:solana/metaplex.dart' as metaplex;
import 'package:solana/solana.dart';
import 'package:solana/src/crypto/ed25519_hd_keypair.dart';
part 'solana_wallet.g.dart';

View file

@ -11,7 +11,7 @@ environment:
dependencies:
flutter:
sdk: flutter
solana: ^0.30.4
solana: ^0.31.0+1
cw_core:
path: ../cw_core
http: ^1.1.0

View file

@ -757,10 +757,10 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
url: "https://pub.dev"
source: hosted
version: "14.2.5"
version: "14.2.4"
watcher:
dependency: "direct overridden"
description:

View file

@ -1,6 +1,6 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/dashboard/dashboard_page.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/balance_page.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/balance/crypto_balance_widget.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter_test/flutter_test.dart';

View file

@ -1,24 +0,0 @@
import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cw_core/wallet_base.dart';
class PayfuraBuyProvider {
PayfuraBuyProvider({required SettingsStore settingsStore, required WalletBase wallet})
: this._settingsStore = settingsStore,
this._wallet = wallet;
final SettingsStore _settingsStore;
final WalletBase _wallet;
static const _baseUrl = 'exchange.payfura.com';
Uri requestUrl() {
return Uri.https(_baseUrl, '', <String, dynamic>{
'apiKey': secrets.payfuraApiKey,
'to': _wallet.currency.title,
'from': _settingsStore.fiatCurrency.title,
'walletAddress': '${_wallet.currency.title}:${_wallet.walletAddresses.address}',
'mode': 'buy'
});
}
}

View file

@ -204,8 +204,8 @@ class CakePayApi {
/// Get Vendors
Future<List<CakePayVendor>> getVendors({
required String apiKey,
required String country,
int? page,
String? country,
String? countryCode,
String? search,
List<String>? vendorIds,
@ -230,6 +230,7 @@ class CakePayApi {
var headers = {
'accept': 'application/json; charset=UTF-8',
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': 'Api-Key $apiKey',
};
@ -240,14 +241,14 @@ class CakePayApi {
'Failed to fetch vendors: statusCode - ${response.statusCode}, queryParams -$queryParams, response - ${response.body}');
}
final bodyJson = json.decode(response.body);
final bodyJson = json.decode(utf8.decode(response.bodyBytes));
if (bodyJson is List<dynamic> && bodyJson.isEmpty) {
return [];
}
return (bodyJson['results'] as List)
.map((e) => CakePayVendor.fromJson(e as Map<String, dynamic>))
.map((e) => CakePayVendor.fromJson(e as Map<String, dynamic>, country))
.toList();
}
}

View file

@ -1,5 +1,3 @@
import 'dart:convert';
import 'package:cake_wallet/entities/fiat_currency.dart';
class CakePayCard {
@ -38,17 +36,11 @@ class CakePayCard {
});
factory CakePayCard.fromJson(Map<String, dynamic> json) {
final name = stripHtmlIfNeeded(json['name'] as String? ?? '');
final decodedName = fixEncoding(name);
final description = stripHtmlIfNeeded(json['description'] as String? ?? '');
final decodedDescription = fixEncoding(description);
final termsAndConditions = stripHtmlIfNeeded(json['terms_and_conditions'] as String? ?? '');
final decodedTermsAndConditions = fixEncoding(termsAndConditions);
final howToUse = stripHtmlIfNeeded(json['how_to_use'] as String? ?? '');
final decodedHowToUse = fixEncoding(howToUse);
final fiatCurrency = FiatCurrency.deserialize(raw: json['currency_code'] as String? ?? '');
@ -59,10 +51,10 @@ class CakePayCard {
return CakePayCard(
id: json['id'] as int? ?? 0,
name: decodedName,
description: decodedDescription,
termsAndConditions: decodedTermsAndConditions,
howToUse: decodedHowToUse,
name: name,
description: description,
termsAndConditions: termsAndConditions,
howToUse: howToUse,
expiryAndValidity: json['expiry_and_validity'] as String?,
cardImageUrl: json['card_image_url'] as String?,
country: json['country'] as String?,
@ -79,9 +71,4 @@ class CakePayCard {
static String stripHtmlIfNeeded(String text) {
return text.replaceAll(RegExp(r'<[^>]*>|&[^;]+;'), ' ');
}
static String fixEncoding(String text) {
final bytes = latin1.encode(text);
return utf8.decode(bytes, allowMalformed: true);
}
}

View file

@ -29,8 +29,8 @@ class CakePayService {
/// Get Vendors
Future<List<CakePayVendor>> getVendors({
required String country,
int? page,
String? country,
String? countryCode,
String? search,
List<String>? vendorIds,

View file

@ -1,5 +1,3 @@
import 'dart:convert';
import 'cake_pay_card.dart';
class CakePayVendor {
@ -7,7 +5,7 @@ class CakePayVendor {
final String name;
final bool unavailable;
final String? cakeWarnings;
final List<String> countries;
final String country;
final CakePayCard? card;
CakePayVendor({
@ -15,37 +13,35 @@ class CakePayVendor {
required this.name,
required this.unavailable,
this.cakeWarnings,
required this.countries,
required this.country,
this.card,
});
factory CakePayVendor.fromJson(Map<String, dynamic> json) {
factory CakePayVendor.fromJson(Map<String, dynamic> json, String country) {
final name = stripHtmlIfNeeded(json['name'] as String);
final decodedName = fixEncoding(name);
var cardsJson = json['cards'] as List?;
CakePayCard? firstCard;
CakePayCard? cardForVendor;
if (cardsJson != null && cardsJson.isNotEmpty) {
firstCard = CakePayCard.fromJson(cardsJson.first as Map<String, dynamic>);
try {
cardForVendor = CakePayCard.fromJson(cardsJson
.where((element) => element['country'] == country)
.first as Map<String, dynamic>);
} catch (_) {}
}
return CakePayVendor(
id: json['id'] as int,
name: decodedName,
name: name,
unavailable: json['unavailable'] as bool? ?? false,
cakeWarnings: json['cake_warnings'] as String?,
countries: List<String>.from(json['countries'] as List? ?? []),
card: firstCard,
country: country,
card: cardForVendor,
);
}
static String stripHtmlIfNeeded(String text) {
return text.replaceAll(RegExp(r'<[^>]*>|&[^;]+;'), ' ');
}
static String fixEncoding(String text) {
final bytes = latin1.encode(text);
return utf8.decode(bytes, allowMalformed: true);
}
}

View file

@ -140,25 +140,24 @@ abstract class Web3WalletServiceBase with Store {
for (final cId in SolanaChainId.values) {
final node = appStore.settingsStore.getCurrentNode(appStore.wallet!.type);
Uri? rpcUri;
String webSocketUrl;
bool isModifiedNodeUri = false;
Uri rpcUri = node.uri;
String webSocketUrl = 'wss://${node.uriRaw}';
if (node.uriRaw == 'rpc.ankr.com') {
isModifiedNodeUri = true;
//A better way to handle this instead of adding this to the general secrets?
String ankrApiKey = secrets.ankrApiKey;
rpcUri = Uri.https(node.uriRaw, '/solana/$ankrApiKey');
webSocketUrl = 'wss://${node.uriRaw}/solana/ws/$ankrApiKey';
} else {
webSocketUrl = 'wss://${node.uriRaw}';
} else if (node.uriRaw == 'solana-mainnet.core.chainstack.com') {
String chainStackApiKey = secrets.chainStackApiKey;
rpcUri = Uri.https(node.uriRaw, '/$chainStackApiKey');
webSocketUrl = 'wss://${node.uriRaw}/$chainStackApiKey';
}
SolanaChainServiceImpl(
reference: cId,
rpcUrl: isModifiedNodeUri ? rpcUri! : node.uri,
rpcUrl: rpcUri,
webSocketUrl: webSocketUrl,
wcKeyService: walletKeyService,
bottomSheetService: _bottomSheetHandler,

View file

@ -2,12 +2,10 @@ import 'dart:async';
import 'package:cake_wallet/core/generate_wallet_password.dart';
import 'package:cake_wallet/core/key_service.dart';
import 'package:cake_wallet/core/secure_storage.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/main.dart';
import 'package:cake_wallet/reactions/on_authentication_state_change.dart';
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/utils/exception_handler.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';

View file

@ -11,7 +11,6 @@ import 'package:cake_wallet/buy/dfx/dfx_buy_provider.dart';
import 'package:cake_wallet/buy/moonpay/moonpay_provider.dart';
import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
import 'package:cake_wallet/buy/order.dart';
import 'package:cake_wallet/buy/payfura/payfura_buy_provider.dart';
import 'package:cake_wallet/core/new_wallet_arguments.dart';
import 'package:cake_wallet/buy/robinhood/robinhood_buy_provider.dart';
import 'package:cake_wallet/core/auth_service.dart';
@ -80,7 +79,7 @@ import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_wallet
import 'package:cake_wallet/src/screens/dashboard/edit_token_page.dart';
import 'package:cake_wallet/src/screens/dashboard/home_settings_page.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/address_page.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/balance_page.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/balance/balance_page.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/transactions_page.dart';
import 'package:cake_wallet/src/screens/exchange/exchange_page.dart';
import 'package:cake_wallet/src/screens/exchange/exchange_template_page.dart';
@ -1022,11 +1021,6 @@ Future<void> setup({
getIt.registerFactoryParam<WebViewPage, String, Uri>((title, uri) => WebViewPage(title, uri));
getIt.registerFactory<PayfuraBuyProvider>(() => PayfuraBuyProvider(
settingsStore: getIt.get<AppStore>().settingsStore,
wallet: getIt.get<AppStore>().wallet!,
));
getIt.registerFactory(() => ExchangeViewModel(
getIt.get<AppStore>(),
_tradesSource,

View file

@ -1,13 +1,11 @@
import 'dart:convert';
import 'dart:io' show Directory, File, Platform;
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/key_service.dart';
import 'package:cake_wallet/core/secure_storage.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cake_wallet/entities/haven_seed_store.dart';
import 'package:cake_wallet/haven/haven.dart';
import 'package:cw_core/cake_hive.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cake_wallet/entities/secret_store_key.dart';
import 'package:cw_core/root_dir.dart';
@ -37,13 +35,13 @@ const publicBitcoinTestnetElectrumUri =
'$publicBitcoinTestnetElectrumAddress:$publicBitcoinTestnetElectrumPort';
const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002';
const havenDefaultNodeUri = 'nodes.havenprotocol.org:443';
const ethereumDefaultNodeUri = 'ethereum.publicnode.com';
const polygonDefaultNodeUri = 'polygon-bor.publicnode.com';
const ethereumDefaultNodeUri = 'ethereum-rpc.publicnode.com';
const polygonDefaultNodeUri = 'polygon-bor-rpc.publicnode.com';
const cakeWalletBitcoinCashDefaultNodeUri = 'bitcoincash.stackwallet.com:50002';
const nanoDefaultNodeUri = 'nano.nownodes.io';
const nanoDefaultPowNodeUri = 'rpc.nano.to';
const solanaDefaultNodeUri = 'rpc.ankr.com';
const tronDefaultNodeUri = 'trx.nownodes.io';
const solanaDefaultNodeUri = 'solana-mainnet.core.chainstack.com';
const tronDefaultNodeUri = 'api.trongrid.io';
const newCakeWalletBitcoinUri = 'btc-electrum.cakewallet.com:50002';
const wowneroDefaultNodeUri = 'node3.monerodevs.org:34568';
const moneroWorldNodeUri = '.moneroworld.com';
@ -169,7 +167,11 @@ Future<void> defaultSettingsMigration(
break;
case 18:
await addOnionNode(nodes);
await updateWalletTypeNodesWithNewNode(
nodes: nodes,
newNodeUri: "cakexmrl7bonq7ovjka5kuwuyd3f7qnkz6z6s6dmsy3uckwra7bvggyd.onion:18081",
type: WalletType.monero,
);
break;
case 19:
@ -263,15 +265,15 @@ Future<void> defaultSettingsMigration(
await updateTronNodesWithNowNodes(sharedPreferences: sharedPreferences, nodes: nodes);
break;
case 42:
updateBtcElectrumNodeToUseSSL(nodes, sharedPreferences);
_fixNodesUseSSLFlag(nodes);
break;
case 43:
await _updateCakeXmrNode(nodes);
_fixNodesUseSSLFlag(nodes);
_deselectExchangeProvider(sharedPreferences, "THORChain");
_deselectExchangeProvider(sharedPreferences, "SimpleSwap");
break;
case 44:
await _updateCakeXmrNode(nodes);
_fixNodesUseSSLFlag(nodes);
await _changeDefaultNode(
nodes: nodes,
sharedPreferences: sharedPreferences,
@ -299,18 +301,78 @@ Future<void> defaultSettingsMigration(
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,
);
_changeDefaultNode(
nodes: nodes,
sharedPreferences: sharedPreferences,
type: WalletType.tron,
newDefaultUri: tronDefaultNodeUri,
currentNodePreferenceKey: PreferencesKey.currentTronNodeIdKey,
useSSL: true,
oldUri: [
'tron-rpc.publicnode.com:443',
'trx.nownodes.io',
],
);
_changeDefaultNode(
nodes: nodes,
sharedPreferences: sharedPreferences,
type: WalletType.solana,
newDefaultUri: solanaDefaultNodeUri,
currentNodePreferenceKey: PreferencesKey.currentSolanaNodeIdKey,
useSSL: true,
oldUri: ['rpc.ankr.com'],
);
break;
case 46:
_fixNodesUseSSLFlag(nodes);
updateWalletTypeNodesWithNewNode(
newNodeUri: 'litecoin.stackwallet.com:20063',
nodes: nodes,
type: WalletType.litecoin,
useSSL: true,
);
updateWalletTypeNodesWithNewNode(
newNodeUri: 'electrum-ltc.bysh.me:50002',
nodes: nodes,
type: WalletType.litecoin,
useSSL: true,
);
_changeDefaultNode(
nodes: nodes,
sharedPreferences: sharedPreferences,
type: WalletType.solana,
newDefaultUri: solanaDefaultNodeUri,
currentNodePreferenceKey: PreferencesKey.currentSolanaNodeIdKey,
useSSL: true,
oldUri: [
'rpc.ankr.com',
'api.mainnet-beta.solana.com:443',
'solana-rpc.publicnode.com:443',
],
);
_updateNode(
nodes: nodes,
currentUri: "ethereum.publicnode.com",
newUri: "ethereum-rpc.publicnode.com",
useSSL: true,
);
_updateNode(
nodes: nodes,
currentUri: "polygon-bor.publicnode.com",
newUri: "polygon-bor-rpc.publicnode.com",
useSSL: true,
);
break;
default:
break;
}
@ -325,6 +387,24 @@ Future<void> defaultSettingsMigration(
await sharedPreferences.setInt(PreferencesKey.currentDefaultSettingsMigrationVersion, version);
}
void _updateNode({
required Box<Node> nodes,
required String currentUri,
String? newUri,
bool? useSSL,
}) {
for (Node node in nodes.values) {
if (node.uriRaw == currentUri) {
if (newUri != null) {
node.uriRaw = newUri;
}
if (useSSL != null) {
node.useSSL = useSSL;
}
}
}
}
Future<void> _backupHavenSeeds(Box<HavenSeedStore> havenSeedStore) async {
final future = haven?.backupHavenSeeds(havenSeedStore);
if (future != null) {
@ -332,6 +412,7 @@ Future<void> _backupHavenSeeds(Box<HavenSeedStore> havenSeedStore) async {
}
return;
}
/// generic function for changing any wallet default node
/// instead of making a new function for each change
Future<void> _changeDefaultNode({
@ -341,7 +422,8 @@ Future<void> _changeDefaultNode({
required String newDefaultUri,
required String currentNodePreferenceKey,
required bool useSSL,
required List<String> oldUri, // leave empty if you want to force replace the node regardless of the user's current node
required List<String>
oldUri, // leave empty if you want to force replace the node regardless of the user's current node
}) async {
final currentNodeId = sharedPreferences.getInt(currentNodePreferenceKey);
final currentNode = nodes.values.firstWhere((node) => node.key == currentNodeId);
@ -369,11 +451,10 @@ 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,
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;
@ -387,26 +468,6 @@ Future<void> updateWalletTypeNodesWithNewNode({
);
}
Future<void> _updateCakeXmrNode(Box<Node> nodes) async {
final node = nodes.values.firstWhereOrNull((element) => element.uriRaw == newCakeWalletMoneroUri);
if (node != null) {
node.trusted = true;
node.useSSL = true;
await node.save();
}
}
void updateBtcElectrumNodeToUseSSL(Box<Node> nodes, SharedPreferences sharedPreferences) {
final btcElectrumNode =
nodes.values.firstWhereOrNull((element) => element.uriRaw == newCakeWalletBitcoinUri);
if (btcElectrumNode != null) {
btcElectrumNode.useSSL = true;
btcElectrumNode.save();
}
}
void _deselectExchangeProvider(SharedPreferences sharedPreferences, String providerName) {
final Map<String, dynamic> exchangeProvidersSelection =
json.decode(sharedPreferences.getString(PreferencesKey.exchangeProvidersSelection) ?? "{}")
@ -425,8 +486,10 @@ void _fixNodesUseSSLFlag(Box<Node> nodes) {
switch (node.uriRaw) {
case cakeWalletLitecoinElectrumUri:
case cakeWalletBitcoinElectrumUri:
case newCakeWalletBitcoinUri:
case newCakeWalletMoneroUri:
node.useSSL = true;
break;
node.trusted = true;
}
}
}
@ -442,7 +505,7 @@ Future<void> updateNanoNodeList({required Box<Node> nodes}) async {
];
// add new nodes:
for (final node in nodeList) {
if (listOfNewEndpoints.contains(node.uriRaw)) {
if (listOfNewEndpoints.contains(node.uriRaw) && !nodes.values.contains(node)) {
await nodes.add(node);
}
}
@ -560,15 +623,6 @@ Future<void> validateBitcoinSavedTransactionPriority(SharedPreferences sharedPre
}
}
Future<void> addOnionNode(Box<Node> nodes) async {
final onionNodeUri = "cakexmrl7bonq7ovjka5kuwuyd3f7qnkz6z6s6dmsy3uckwra7bvggyd.onion:18081";
// check if the user has this node before (added it manually)
if (nodes.values.firstWhereOrNull((element) => element.uriRaw == onionNodeUri) == null) {
await nodes.add(Node(uri: onionNodeUri, type: WalletType.monero));
}
}
Future<void> replaceNodesMigration({required Box<Node> nodes}) async {
final replaceNodes = <String, Node>{
'eu-node.cakewallet.io:18081':

View file

@ -1,3 +1,5 @@
import 'package:cake_wallet/src/screens/wallet_connect/utils/string_parsing.dart';
class EVMTransactionErrorFeesHandler {
EVMTransactionErrorFeesHandler({
this.balanceWei,
@ -64,14 +66,14 @@ class EVMTransactionErrorFeesHandler {
return EVMTransactionErrorFeesHandler(
balanceWei: balanceWei.toString(),
balanceEth: balanceEth.toString().substring(0, 12),
balanceUsd: balanceUsd.toString().substring(0, 4),
balanceEth: balanceEth.toString().safeSubString(0, 12),
balanceUsd: balanceUsd.toString().safeSubString(0, 4),
txCostWei: txCostWei.toString(),
txCostEth: txCostEth.toString().substring(0, 12),
txCostUsd: txCostUsd.toString().substring(0, 4),
txCostEth: txCostEth.toString().safeSubString(0, 12),
txCostUsd: txCostUsd.toString().safeSubString(0, 4),
overshotWei: overshotWei.toString(),
overshotEth: overshotEth.toString().substring(0, 12),
overshotUsd: overshotUsd.toString().substring(0, 4),
overshotEth: overshotEth.toString().safeSubString(0, 12),
overshotUsd: overshotUsd.toString().safeSubString(0, 4),
);
} else {
// If any value is missing, return an error message

View file

@ -134,6 +134,7 @@ class AddressResolver {
Future<ParsedAddress> resolve(BuildContext context, String text, CryptoCurrency currency) async {
final ticker = currency.title;
try {
// twitter handle example: @username
if (text.startsWith('@') && !text.substring(1).contains('@')) {
if (settingsStore.lookupsTwitter) {
final formattedName = text.substring(1);
@ -165,6 +166,7 @@ class AddressResolver {
}
}
// Mastodon example: @username@hostname.xxx
if (text.startsWith('@') && text.contains('@', 1) && text.contains('.', 1)) {
if (settingsStore.lookupsMastodon) {
final subText = text.substring(1);
@ -242,7 +244,9 @@ class AddressResolver {
if (unstoppableDomains.any((domain) => name.trim() == domain)) {
if (settingsStore.lookupsUnstoppableDomains) {
final address = await fetchUnstoppableDomainAddress(text, ticker);
return ParsedAddress.fetchUnstoppableDomainAddress(address: address, name: text);
if (address.isNotEmpty) {
return ParsedAddress.fetchUnstoppableDomainAddress(address: address, name: text);
}
}
}
@ -258,6 +262,7 @@ class AddressResolver {
if (formattedName.contains(".")) {
if (settingsStore.lookupsOpenAlias) {
final txtRecord = await OpenaliasRecord.lookupOpenAliasRecord(formattedName);
if (txtRecord != null) {
final record = await OpenaliasRecord.fetchAddressAndName(
formattedName: formattedName, ticker: ticker.toLowerCase(), txtRecord: txtRecord);

View file

@ -215,7 +215,7 @@ Future<void> initializeAppConfigs() async {
secureStorage: secureStorage,
anonpayInvoiceInfo: anonpayInvoiceInfo,
havenSeedStore: havenSeedStore,
initialMigrationVersion: 45,
initialMigrationVersion: 46,
);
}

View file

@ -92,6 +92,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
late StreamSubscription<LedgerDevice>? _bleRefresh = null;
bool longWait = false;
Timer? _longWaitTimer;
@override
void initState() {
@ -108,7 +109,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
Timer.periodic(Duration(seconds: 1), (_) => _refreshUsbDevices());
}
Future.delayed(Duration(seconds: 10), () {
_longWaitTimer = Timer(Duration(seconds: 10), () {
if (widget.ledgerVM.bleIsEnabled && bleDevices.isEmpty)
setState(() => longWait = true);
});
@ -121,6 +122,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
_bleStateTimer?.cancel();
_usbRefreshTimer?.cancel();
_bleRefresh?.cancel();
_longWaitTimer?.cancel();
widget.ledgerVM.stopScanning();
super.dispose();
@ -206,7 +208,8 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
offstage: !longWait,
child: Padding(
padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
child: Text(S.of(context).if_you_dont_see_your_device,
child: Text(
S.of(context).if_you_dont_see_your_device,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
@ -235,7 +238,6 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
),
),
),
if (bleDevices.length > 0) ...[
Padding(
padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
@ -277,7 +279,9 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
color: Theme.of(context)
.extension<CakeTextTheme>()!
.titleColor,
),
),
),
@ -299,8 +303,12 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
if (widget.allowChangeWallet) ...[
PrimaryButton(
text: S.of(context).wallets,
color: Theme.of(context).extension<WalletListTheme>()!.createNewWalletButtonBackgroundColor,
textColor: Theme.of(context).extension<WalletListTheme>()!.restoreWalletButtonTextColor,
color: Theme.of(context)
.extension<WalletListTheme>()!
.createNewWalletButtonBackgroundColor,
textColor: Theme.of(context)
.extension<WalletListTheme>()!
.restoreWalletButtonTextColor,
onPressed: _onChangeWallet,
)
],

View file

@ -24,7 +24,7 @@ import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/menu_widget.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/action_button.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/balance_page.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/balance/balance_page.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/transactions_page.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';

View file

@ -8,7 +8,7 @@ import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/utils/version_comparator.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/balance_page.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/balance/balance_page.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
import 'package:cake_wallet/main.dart';
import 'package:cake_wallet/router.dart' as Router;

View file

@ -0,0 +1,88 @@
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/balance/crypto_balance_widget.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/nft_listing_page.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class BalancePage extends StatelessWidget {
BalancePage({
required this.dashboardViewModel,
required this.settingsStore,
required this.nftViewModel,
});
final DashboardViewModel dashboardViewModel;
final NFTViewModel nftViewModel;
final SettingsStore settingsStore;
@override
Widget build(BuildContext context) {
return Observer(
builder: (context) {
final isEVMCompatible = isEVMCompatibleChain(dashboardViewModel.type);
return DefaultTabController(
length: isEVMCompatible ? 2 : 1,
child: Column(
children: [
if (isEVMCompatible)
Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.only(left: 8),
child: TabBar(
indicatorSize: TabBarIndicatorSize.label,
isScrollable: true,
physics: NeverScrollableScrollPhysics(),
labelStyle: TextStyle(
fontSize: 18,
fontFamily: 'Lato',
fontWeight: FontWeight.w600,
color:
Theme.of(context).extension<DashboardPageTheme>()!.pageTitleTextColor,
height: 1,
),
unselectedLabelStyle: TextStyle(
fontSize: 18,
fontFamily: 'Lato',
fontWeight: FontWeight.w600,
color:
Theme.of(context).extension<DashboardPageTheme>()!.pageTitleTextColor,
height: 1,
),
labelColor:
Theme.of(context).extension<DashboardPageTheme>()!.pageTitleTextColor,
dividerColor: Colors.transparent,
indicatorColor:
Theme.of(context).extension<DashboardPageTheme>()!.pageTitleTextColor,
unselectedLabelColor: Theme.of(context)
.extension<DashboardPageTheme>()!
.pageTitleTextColor
.withOpacity(0.5),
tabAlignment: TabAlignment.start,
tabs: [
Tab(text: 'My Crypto'),
Tab(text: 'My NFTs'),
],
),
),
),
Expanded(
child: TabBarView(
physics: NeverScrollableScrollPhysics(),
children: [
CryptoBalanceWidget(dashboardViewModel: dashboardViewModel),
if (isEVMCompatible) NFTListingPage(nftViewModel: nftViewModel)
],
),
),
],
),
);
},
);
}
}

View file

@ -0,0 +1,654 @@
import 'dart:math';
import 'package:auto_size_text/auto_size_text.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/exchange_trade/information_page.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
import 'package:cake_wallet/themes/extensions/sync_indicator_theme.dart';
import 'package:cake_wallet/utils/payment_request.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/unspent_coin_type.dart';
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
class BalanceRowWidget extends StatelessWidget {
BalanceRowWidget({
required this.availableBalanceLabel,
required this.availableBalance,
required this.availableFiatBalance,
required this.additionalBalanceLabel,
required this.additionalBalance,
required this.additionalFiatBalance,
required this.secondAvailableBalanceLabel,
required this.secondAvailableBalance,
required this.secondAvailableFiatBalance,
required this.secondAdditionalBalanceLabel,
required this.secondAdditionalBalance,
required this.secondAdditionalFiatBalance,
required this.frozenBalance,
required this.frozenFiatBalance,
required this.currency,
required this.hasAdditionalBalance,
required this.hasSecondAvailableBalance,
required this.hasSecondAdditionalBalance,
required this.isTestnet,
required this.dashboardViewModel,
super.key,
});
final String availableBalanceLabel;
final String availableBalance;
final String availableFiatBalance;
final String additionalBalanceLabel;
final String additionalBalance;
final String additionalFiatBalance;
final String secondAvailableBalanceLabel;
final String secondAvailableBalance;
final String secondAvailableFiatBalance;
final String secondAdditionalBalanceLabel;
final String secondAdditionalBalance;
final String secondAdditionalFiatBalance;
final String frozenBalance;
final String frozenFiatBalance;
final CryptoCurrency currency;
final bool hasAdditionalBalance;
final bool hasSecondAvailableBalance;
final bool hasSecondAdditionalBalance;
final bool isTestnet;
final DashboardViewModel dashboardViewModel;
@override
Widget build(BuildContext context) {
return Column(
children: [
Container(
margin: const EdgeInsets.only(left: 16, right: 16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30.0),
border: Border.all(
color: Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor,
width: 1,
),
color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
),
child: Container(
margin: const EdgeInsets.only(top: 16, left: 24, right: 8, bottom: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GestureDetector(
onTap: () => dashboardViewModel.balanceViewModel.switchBalanceValue(),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: hasAdditionalBalance
? () => _showBalanceDescription(
context, S.of(context).available_balance_description)
: null,
child: Row(
children: [
Semantics(
hint: 'Double tap to see more information',
container: true,
child: Text('${availableBalanceLabel}',
style: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor,
height: 1)),
),
if (hasAdditionalBalance)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Icon(Icons.help_outline,
size: 16,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor),
),
],
),
),
SizedBox(height: 6),
AutoSizeText(availableBalance,
style: TextStyle(
fontSize: 24,
fontFamily: 'Lato',
fontWeight: FontWeight.w900,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.balanceAmountColor,
height: 1),
maxLines: 1,
textAlign: TextAlign.start),
SizedBox(height: 6),
if (isTestnet)
Text(S.of(context).testnet_coins_no_value,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color:
Theme.of(context).extension<BalancePageTheme>()!.textColor,
height: 1)),
if (!isTestnet)
Text('${availableFiatBalance}',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
fontFamily: 'Lato',
fontWeight: FontWeight.w500,
color:
Theme.of(context).extension<BalancePageTheme>()!.textColor,
height: 1)),
],
),
SizedBox(
width: min(MediaQuery.of(context).size.width * 0.2, 100),
child: Center(
child: Column(
children: [
CakeImageWidget(
imageUrl: currency.iconPath,
height: 40,
width: 40,
displayOnError: Container(
height: 30.0,
width: 30.0,
child: Center(
child: Text(
currency.title.substring(0, min(currency.title.length, 2)),
style: TextStyle(fontSize: 11),
),
),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.grey.shade400,
),
),
),
const SizedBox(height: 10),
Text(
currency.title,
style: TextStyle(
fontSize: 15,
fontFamily: 'Lato',
fontWeight: FontWeight.w800,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.assetTitleColor,
height: 1,
),
),
],
),
),
),
],
),
),
if (frozenBalance.isNotEmpty)
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: hasAdditionalBalance
? () => _showBalanceDescription(
context, S.of(context).unavailable_balance_description)
: null,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 26),
Row(
children: [
Text(
S.of(context).unavailable_balance,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color:
Theme.of(context).extension<BalancePageTheme>()!.labelTextColor,
height: 1,
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Icon(Icons.help_outline,
size: 16,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor),
),
],
),
SizedBox(height: 8),
AutoSizeText(
frozenBalance,
style: TextStyle(
fontSize: 20,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color:
Theme.of(context).extension<BalancePageTheme>()!.balanceAmountColor,
height: 1,
),
maxLines: 1,
textAlign: TextAlign.center,
),
SizedBox(height: 4),
if (!isTestnet)
Text(
frozenFiatBalance,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context).extension<BalancePageTheme>()!.textColor,
height: 1,
),
),
],
),
),
if (hasAdditionalBalance)
GestureDetector(
onTap: () => dashboardViewModel.balanceViewModel.switchBalanceValue(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 24),
Text(
'${additionalBalanceLabel}',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context).extension<BalancePageTheme>()!.labelTextColor,
height: 1,
),
),
SizedBox(height: 8),
AutoSizeText(
additionalBalance,
style: TextStyle(
fontSize: 20,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context).extension<BalancePageTheme>()!.assetTitleColor,
height: 1,
),
maxLines: 1,
textAlign: TextAlign.center,
),
SizedBox(height: 4),
if (!isTestnet)
Text(
'${additionalFiatBalance}',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context).extension<BalancePageTheme>()!.textColor,
height: 1,
),
),
],
),
),
],
),
),
),
if (hasSecondAdditionalBalance || hasSecondAvailableBalance) ...[
SizedBox(height: 10),
Container(
margin: const EdgeInsets.only(left: 16, right: 16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30.0),
border: Border.all(
color: Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor,
width: 1,
),
color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
),
child: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: const EdgeInsets.only(top: 16, left: 24, right: 8, bottom: 16),
child: Stack(
children: [
if (currency == CryptoCurrency.ltc)
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Container(
padding: EdgeInsets.only(right: 16, top: 0),
child: Column(
children: [
Container(
child: ImageIcon(
AssetImage('assets/images/mweb_logo.png'),
color: Theme.of(context)
.extension<BalancePageTheme>()!
.assetTitleColor,
size: 40,
),
),
const SizedBox(height: 10),
Text(
'MWEB',
style: TextStyle(
fontSize: 15,
fontFamily: 'Lato',
fontWeight: FontWeight.w800,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.assetTitleColor,
height: 1,
),
),
],
),
),
],
),
if (hasSecondAvailableBalance)
GestureDetector(
onTap: () => dashboardViewModel.balanceViewModel.switchBalanceValue(),
child: Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => launchUrl(
Uri.parse(
"https://docs.cakewallet.com/cryptos/litecoin.html#mweb"),
mode: LaunchMode.externalApplication,
),
child: Row(
children: [
Text(
'${secondAvailableBalanceLabel}',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor,
height: 1,
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Icon(Icons.help_outline,
size: 16,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor),
)
],
),
),
SizedBox(height: 8),
AutoSizeText(
secondAvailableBalance,
style: TextStyle(
fontSize: 24,
fontFamily: 'Lato',
fontWeight: FontWeight.w900,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.assetTitleColor,
height: 1,
),
maxLines: 1,
textAlign: TextAlign.center,
),
SizedBox(height: 6),
if (!isTestnet)
Text(
'${secondAvailableFiatBalance}',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
fontFamily: 'Lato',
fontWeight: FontWeight.w500,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.textColor,
height: 1,
),
),
],
),
],
),
),
],
),
),
Container(
margin: const EdgeInsets.only(top: 0, left: 24, right: 8, bottom: 16),
child: Stack(
children: [
if (hasSecondAdditionalBalance)
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 24),
Text(
'${secondAdditionalBalanceLabel}',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor,
height: 1,
),
),
SizedBox(height: 8),
AutoSizeText(
secondAdditionalBalance,
style: TextStyle(
fontSize: 20,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.assetTitleColor,
height: 1,
),
maxLines: 1,
textAlign: TextAlign.center,
),
SizedBox(height: 4),
if (!isTestnet)
Text(
'${secondAdditionalFiatBalance}',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.textColor,
height: 1,
),
),
],
),
],
),
],
),
),
IntrinsicHeight(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Semantics(
label: S.of(context).litecoin_mweb_pegin,
child: OutlinedButton(
onPressed: () {
final mwebAddress =
bitcoin!.getUnusedMwebAddress(dashboardViewModel.wallet);
PaymentRequest? paymentRequest = null;
if ((mwebAddress?.isNotEmpty ?? false)) {
paymentRequest = PaymentRequest.fromUri(
Uri.parse("litecoin:${mwebAddress}"));
}
Navigator.pushNamed(
context,
Routes.send,
arguments: {
'paymentRequest': paymentRequest,
'coinTypeToSpendFrom': UnspentCoinType.nonMweb,
},
);
},
style: OutlinedButton.styleFrom(
backgroundColor: Colors.grey.shade400.withAlpha(50),
side: BorderSide(
color: Colors.grey.shade400.withAlpha(50), width: 0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
),
child: Container(
padding: EdgeInsets.symmetric(vertical: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
height: 30,
width: 30,
'assets/images/received.png',
color: Theme.of(context)
.extension<BalancePageTheme>()!
.balanceAmountColor,
),
const SizedBox(width: 8),
Text(
S.of(context).litecoin_mweb_pegin,
style: TextStyle(
color: Theme.of(context)
.extension<BalancePageTheme>()!
.textColor,
),
),
],
),
),
),
),
),
SizedBox(width: 24),
Expanded(
child: Semantics(
label: S.of(context).litecoin_mweb_pegout,
child: OutlinedButton(
onPressed: () {
final litecoinAddress =
bitcoin!.getUnusedSegwitAddress(dashboardViewModel.wallet);
PaymentRequest? paymentRequest = null;
if ((litecoinAddress?.isNotEmpty ?? false)) {
paymentRequest = PaymentRequest.fromUri(
Uri.parse("litecoin:${litecoinAddress}"));
}
Navigator.pushNamed(
context,
Routes.send,
arguments: {
'paymentRequest': paymentRequest,
'coinTypeToSpendFrom': UnspentCoinType.mweb,
},
);
},
style: OutlinedButton.styleFrom(
backgroundColor: Colors.grey.shade400.withAlpha(50),
side: BorderSide(
color: Colors.grey.shade400.withAlpha(50), width: 0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
),
child: Container(
padding: EdgeInsets.symmetric(vertical: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
height: 30,
width: 30,
'assets/images/upload.png',
color: Theme.of(context)
.extension<BalancePageTheme>()!
.balanceAmountColor,
),
const SizedBox(width: 8),
Text(
S.of(context).litecoin_mweb_pegout,
style: TextStyle(
color: Theme.of(context)
.extension<BalancePageTheme>()!
.textColor,
),
),
],
),
),
),
),
),
],
),
),
),
SizedBox(height: 16),
],
),
),
),
],
],
);
}
void _showBalanceDescription(BuildContext context, String content) {
showPopUp<void>(context: context, builder: (_) => InformationPage(information: content));
}
}

View file

@ -0,0 +1,424 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/balance/balance_row_widget.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/home_screen_account_widget.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/dashboard_card_widget.dart';
import 'package:cake_wallet/src/widgets/introducing_card.dart';
import 'package:cake_wallet/src/widgets/standard_switch.dart';
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:url_launcher/url_launcher.dart';
class CryptoBalanceWidget extends StatelessWidget {
const CryptoBalanceWidget({
super.key,
required this.dashboardViewModel,
});
final DashboardViewModel dashboardViewModel;
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Observer(
builder: (_) {
if (dashboardViewModel.getMoneroError != null) {
return Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
child: DashBoardRoundedCardWidget(
title: "Invalid monero bindings",
subTitle: dashboardViewModel.getMoneroError.toString(),
onTap: () {},
),
);
}
return Container();
},
),
Observer(
builder: (_) {
if (dashboardViewModel.getWowneroError != null) {
return Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
child: DashBoardRoundedCardWidget(
title: "Invalid wownero bindings",
subTitle: dashboardViewModel.getWowneroError.toString(),
onTap: () {},
));
}
return Container();
},
),
Observer(
builder: (_) => dashboardViewModel.balanceViewModel.hasAccounts
? HomeScreenAccountWidget(
walletName: dashboardViewModel.name, accountName: dashboardViewModel.subname)
: Column(
children: [
SizedBox(height: 16),
Container(
margin: const EdgeInsets.only(left: 24, bottom: 16),
child: Observer(
builder: (_) {
return Row(
children: [
Text(
dashboardViewModel.balanceViewModel.asset,
style: TextStyle(
fontSize: 24,
fontFamily: 'Lato',
fontWeight: FontWeight.w600,
color: Theme.of(context)
.extension<DashboardPageTheme>()!
.pageTitleTextColor,
height: 1,
),
maxLines: 1,
textAlign: TextAlign.center,
),
if (dashboardViewModel.wallet.isHardwareWallet)
Padding(
padding: const EdgeInsets.all(8.0),
child: Image.asset(
'assets/images/hardware_wallet/ledger_nano_x.png',
width: 24,
color: Theme.of(context)
.extension<DashboardPageTheme>()!
.pageTitleTextColor,
),
),
if (dashboardViewModel
.balanceViewModel.isHomeScreenSettingsEnabled)
InkWell(
onTap: () => Navigator.pushNamed(context, Routes.homeSettings,
arguments: dashboardViewModel.balanceViewModel),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Image.asset(
'assets/images/home_screen_settings_icon.png',
color: Theme.of(context)
.extension<DashboardPageTheme>()!
.pageTitleTextColor,
),
),
),
],
);
},
),
),
],
)),
Observer(
builder: (_) {
if (dashboardViewModel.balanceViewModel.isShowCard && FeatureFlag.isCakePayEnabled) {
return IntroducingCard(
title: S.of(context).introducing_cake_pay,
subTitle: S.of(context).cake_pay_learn_more,
borderColor: Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor,
closeCard: dashboardViewModel.balanceViewModel.disableIntroCakePayCard);
}
return Container();
},
),
Observer(builder: (_) {
if (!dashboardViewModel.showRepWarning) {
return const SizedBox();
}
return Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
child: DashBoardRoundedCardWidget(
title: S.of(context).rep_warning,
subTitle: S.of(context).rep_warning_sub,
onTap: () => Navigator.of(context).pushNamed(Routes.changeRep),
onClose: () {
dashboardViewModel.settingsStore.shouldShowRepWarning = false;
},
),
);
}),
Observer(
builder: (_) {
return ListView.separated(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
separatorBuilder: (_, __) => Container(padding: EdgeInsets.only(bottom: 8)),
itemCount: dashboardViewModel.balanceViewModel.formattedBalances.length,
itemBuilder: (__, index) {
final balance =
dashboardViewModel.balanceViewModel.formattedBalances.elementAt(index);
return Observer(builder: (_) {
return BalanceRowWidget(
dashboardViewModel: dashboardViewModel,
availableBalanceLabel:
'${dashboardViewModel.balanceViewModel.availableBalanceLabel}',
availableBalance: balance.availableBalance,
availableFiatBalance: balance.fiatAvailableBalance,
additionalBalanceLabel:
'${dashboardViewModel.balanceViewModel.additionalBalanceLabel}',
additionalBalance: balance.additionalBalance,
additionalFiatBalance: balance.fiatAdditionalBalance,
frozenBalance: balance.frozenBalance,
frozenFiatBalance: balance.fiatFrozenBalance,
currency: balance.asset,
hasAdditionalBalance:
dashboardViewModel.balanceViewModel.hasAdditionalBalance,
hasSecondAdditionalBalance:
dashboardViewModel.balanceViewModel.hasSecondAdditionalBalance,
hasSecondAvailableBalance:
dashboardViewModel.balanceViewModel.hasSecondAvailableBalance,
secondAdditionalBalance: balance.secondAdditionalBalance,
secondAdditionalFiatBalance: balance.fiatSecondAdditionalBalance,
secondAvailableBalance: balance.secondAvailableBalance,
secondAvailableFiatBalance: balance.fiatSecondAvailableBalance,
secondAdditionalBalanceLabel:
'${dashboardViewModel.balanceViewModel.secondAdditionalBalanceLabel}',
secondAvailableBalanceLabel:
'${dashboardViewModel.balanceViewModel.secondAvailableBalanceLabel}',
isTestnet: dashboardViewModel.isTestnet,
);
});
},
);
},
),
Observer(builder: (context) {
return Column(
children: [
if (dashboardViewModel.isMoneroWalletBrokenReasons.isNotEmpty) ...[
SizedBox(height: 10),
Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
child: DashBoardRoundedCardWidget(
customBorder: 30,
title: "This wallet has encountered an issue",
subTitle: "Here are the things that you should note:\n - " +
dashboardViewModel.isMoneroWalletBrokenReasons.join("\n - ") +
"\n\nPlease restart your wallet and if it doesn't help contact our support.",
onTap: () {},
))
],
if (dashboardViewModel.showSilentPaymentsCard) ...[
SizedBox(height: 10),
Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
child: DashBoardRoundedCardWidget(
customBorder: 30,
title: S.of(context).silent_payments,
subTitle: S.of(context).enable_silent_payments_scanning,
hint: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => launchUrl(
Uri.parse(
"https://docs.cakewallet.com/cryptos/bitcoin#silent-payments"),
mode: LaunchMode.externalApplication,
),
child: Row(
children: [
Text(
S.of(context).what_is_silent_payments,
style: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor,
height: 1,
),
softWrap: true,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Icon(Icons.help_outline,
size: 16,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor),
)
],
),
),
Observer(
builder: (_) => StandardSwitch(
value: dashboardViewModel.silentPaymentsScanningActive,
onTaped: () => _toggleSilentPaymentsScanning(context),
),
)
],
),
],
),
onTap: () => _toggleSilentPaymentsScanning(context),
icon: Icon(
Icons.lock,
color:
Theme.of(context).extension<DashboardPageTheme>()!.pageTitleTextColor,
size: 50,
),
),
),
],
if (dashboardViewModel.showMwebCard) ...[
SizedBox(height: 10),
Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
child: DashBoardRoundedCardWidget(
customBorder: 30,
title: S.of(context).litecoin_mweb,
subTitle: S.of(context).litecoin_mweb_description,
hint: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => launchUrl(
Uri.parse("https://docs.cakewallet.com/cryptos/litecoin/#mweb"),
mode: LaunchMode.externalApplication,
),
child: Text(
S.of(context).learn_more,
style: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color:
Theme.of(context).extension<BalancePageTheme>()!.labelTextColor,
height: 1,
),
softWrap: true,
),
),
SizedBox(height: 8),
Row(
children: [
Expanded(
child: ElevatedButton(
onPressed: () => _dismissMweb(context),
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).primaryColor,
),
child: Text(
S.of(context).litecoin_mweb_dismiss,
style: TextStyle(color: Colors.white),
),
),
),
const SizedBox(width: 8),
Expanded(
child: ElevatedButton(
onPressed: () => _enableMweb(context),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: Colors.black,
),
child: Text(
S.of(context).enable,
maxLines: 1,
),
),
),
],
),
],
),
onTap: () => {},
icon: Container(
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
child: ImageIcon(
AssetImage('assets/images/mweb_logo.png'),
color: Color.fromARGB(255, 11, 70, 129),
size: 40,
),
),
),
),
],
],
);
}),
],
),
);
}
Future<void> _toggleSilentPaymentsScanning(BuildContext context) async {
final isSilentPaymentsScanningActive = dashboardViewModel.silentPaymentsScanningActive;
final newValue = !isSilentPaymentsScanningActive;
dashboardViewModel.silentPaymentsScanningActive = newValue;
final needsToSwitch = !isSilentPaymentsScanningActive &&
await bitcoin!.getNodeIsElectrsSPEnabled(dashboardViewModel.wallet) == false;
if (needsToSwitch) {
return showPopUp<void>(
context: context,
builder: (BuildContext context) => AlertWithTwoActions(
alertTitle: S.of(context).change_current_node_title,
alertContent: S.of(context).confirm_silent_payments_switch_node,
rightButtonText: S.of(context).confirm,
leftButtonText: S.of(context).cancel,
actionRightButton: () {
dashboardViewModel.setSilentPaymentsScanning(newValue);
Navigator.of(context).pop();
},
actionLeftButton: () {
dashboardViewModel.silentPaymentsScanningActive = isSilentPaymentsScanningActive;
Navigator.of(context).pop();
},
));
}
return dashboardViewModel.setSilentPaymentsScanning(newValue);
}
Future<void> _enableMweb(BuildContext context) async {
if (!dashboardViewModel.hasEnabledMwebBefore) {
await showPopUp<void>(
context: context,
builder: (BuildContext context) => AlertWithOneAction(
alertTitle: S.of(context).alert_notice,
alertContent: S.of(context).litecoin_mweb_warning,
buttonText: S.of(context).understand,
buttonAction: () {
Navigator.of(context).pop();
},
));
}
dashboardViewModel.setMwebEnabled();
}
Future<void> _dismissMweb(BuildContext context) async {
await showPopUp<void>(
context: context,
builder: (BuildContext context) => AlertWithOneAction(
alertTitle: S.of(context).alert_notice,
alertContent: S.of(context).litecoin_mweb_enable_later,
buttonText: S.of(context).understand,
buttonAction: () {
Navigator.of(context).pop();
},
));
dashboardViewModel.dismissMweb();
}
}

File diff suppressed because it is too large Load diff

View file

@ -20,7 +20,8 @@ class SeedVerificationPage extends BasePage {
builder: (context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: walletSeedViewModel.isVerificationComplete
child: walletSeedViewModel.isVerificationComplete ||
walletSeedViewModel.verificationIndices.isEmpty
? SeedVerificationSuccessView(
imageColor: titleColor(context),
)

View file

@ -70,8 +70,10 @@ class SeedVerificationStepView extends StatelessWidget {
(option) {
return GestureDetector(
onTap: () async {
if (walletSeedViewModel.wrongEntries > 2) return;
final isCorrectWord = walletSeedViewModel.isChosenWordCorrect(option);
final isSecondWrongEntry = walletSeedViewModel.wrongEntries == 2;
final isSecondWrongEntry = walletSeedViewModel.wrongEntries >= 2;
if (!isCorrectWord) {
await showBar<void>(
context,
@ -81,7 +83,9 @@ class SeedVerificationStepView extends StatelessWidget {
);
if (isSecondWrongEntry) {
Navigator.pop(context);
if (context.mounted) {
Navigator.pop(context);
}
}
}
},

View file

@ -1,4 +1,5 @@
import 'dart:convert';
import 'dart:math';
import 'package:convert/convert.dart';
@ -13,4 +14,8 @@ extension StringParsing on String {
return this;
}
String safeSubString(int start, int end) {
return this.substring(0, min(this.toString().length, 12));
}
}

View file

@ -25,7 +25,7 @@ class WelcomePage extends BasePage {
@override
Widget trailing(BuildContext context) {
final Uri _url = Uri.parse('https://guides.cakewallet.com/docs/basic-features/basic-features/');
final Uri _url = Uri.parse('https://docs.cakewallet.com/get-started/setup/create-first-wallet/');
return IconButton(
icon: Icon(Icons.info_outline),
onPressed: () async {

View file

@ -219,9 +219,9 @@ class ExceptionHandler {
// probably when the device was locked and then opened on Cake
// this is solved by a restart of the app
// just ignoring until we find a solution to this issue or migrate from flutter secure storage
"core/auth_service.dart:63",
"core/auth_service.dart:64",
"core/key_service.dart:14",
"core/wallet_loading_service.dart:132",
"core/wallet_loading_service.dart:131",
];
static Future<void> _addDeviceInfo(File file) async {

View file

@ -1,7 +1,9 @@
import 'package:flutter/foundation.dart';
class FeatureFlag {
static const bool isCakePayEnabled = false;
static const bool isExolixEnabled = true;
static const bool isInAppTorEnabled = false;
static const bool isBackgroundSyncEnabled = false;
static const int verificationWordsCount = 2;
static const int verificationWordsCount = kDebugMode ? 0 : 2;
}

View file

@ -158,17 +158,17 @@ abstract class BalanceViewModelBase with Store {
case WalletType.banano:
case WalletType.solana:
case WalletType.tron:
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.bitcoinCash:
case WalletType.none:
return S.current.xmr_available_balance;
default:
return S.current.confirmed;
}
}
@computed
String get additionalBalanceLabel {
switch (wallet.type) {
case WalletType.monero:
case WalletType.wownero:
case WalletType.haven:
case WalletType.ethereum:
case WalletType.polygon:
@ -357,7 +357,12 @@ abstract class BalanceViewModelBase with Store {
bool mwebEnabled = false;
@computed
bool get hasAdditionalBalance => _hasAdditionalBalanceForWalletType(wallet.type);
bool get hasAdditionalBalance {
bool isWalletTypeActivated = _hasAdditionalBalanceForWalletType(wallet.type);
bool isNotZeroAmount = additionalBalance != "0.0";
return isWalletTypeActivated && isNotZeroAmount;
}
@computed
bool get hasSecondAdditionalBalance =>
@ -373,6 +378,9 @@ abstract class BalanceViewModelBase with Store {
case WalletType.polygon:
case WalletType.solana:
case WalletType.tron:
case WalletType.bitcoin:
case WalletType.bitcoinCash:
case WalletType.litecoin:
return false;
default:
return true;

View file

@ -4,13 +4,11 @@ import 'dart:io' show Platform;
import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/buy/buy_provider.dart';
import 'package:cake_wallet/core/key_service.dart';
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
import 'package:cake_wallet/entities/balance_display_mode.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/entities/provider_types.dart';
import 'package:cake_wallet/entities/service_status.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/generated/i18n.dart';
@ -643,7 +641,7 @@ abstract class DashboardViewModelBase with Store {
transactions.clear();
transactions.addAll(
transactions = ObservableList.of(
wallet.transactionHistory.transactions.values.map(
(transaction) => TransactionListItem(
transaction: transaction,
@ -705,7 +703,7 @@ abstract class DashboardViewModelBase with Store {
monero!.getTransactionInfoAccountId(tx) == monero!.getCurrentAccount(wallet).id)
.toList();
transactions.addAll(
transactions = ObservableList.of(
_accountTransactions.map(
(transaction) => TransactionListItem(
transaction: transaction,
@ -725,7 +723,7 @@ abstract class DashboardViewModelBase with Store {
wow.wownero!.getCurrentAccount(wallet).id)
.toList();
transactions.addAll(
transactions = ObservableList.of(
_accountTransactions.map(
(transaction) => TransactionListItem(
transaction: transaction,

View file

@ -99,47 +99,54 @@ abstract class LedgerViewModelBase with Store {
}
Future<void> connectLedger(sdk.LedgerDevice device, WalletType type) async {
_isConnecting = true;
_connectingWalletType = type;
if (isConnected) {
try {
await _connectionChangeListener?.cancel();
_connectionChangeListener = null;
await _connection!.disconnect().catchError((_) {});
} catch (_) {}
}
final ledger = device.connectionType == sdk.ConnectionType.ble
? ledgerPlusBLE
: ledgerPlusUSB;
if (_connectionChangeListener == null) {
_connectionChangeListener = ledger.deviceStateChanges.listen((event) {
printV('Ledger Device State Changed: $event');
if (event == sdk.BleConnectionState.disconnected) {
_connection = null;
if (type == WalletType.monero) {
monero!.resetLedgerConnection();
Navigator.of( navigatorKey.currentContext!).pushNamed(
Routes.connectDevices,
arguments: ConnectDevicePageParams(
walletType: WalletType.monero,
allowChangeWallet: true,
isReconnect: true,
onConnectDevice: (context, ledgerVM) async {
Navigator.of(context).pop();
},
),
);
}
}
});
if (_connectionChangeSubscription == null) {
_connectionChangeSubscription = ledger.deviceStateChanges
.listen(_connectionChangeListener);
}
_connection = await ledger.connect(device);
_isConnecting = false;
}
StreamSubscription<sdk.BleConnectionState>? _connectionChangeListener;
StreamSubscription<sdk.BleConnectionState>? _connectionChangeSubscription;
sdk.LedgerConnection? _connection;
bool _isConnecting = true;
WalletType? _connectingWalletType;
void _connectionChangeListener(
sdk.BleConnectionState event, ) {
printV('Ledger Device State Changed: $event');
if (event == sdk.BleConnectionState.disconnected && !_isConnecting) {
_connection = null;
if (_connectingWalletType == WalletType.monero) {
monero!.resetLedgerConnection();
Navigator.of(navigatorKey.currentContext!).pushNamed(
Routes.connectDevices,
arguments: ConnectDevicePageParams(
walletType: WalletType.monero,
allowChangeWallet: true,
isReconnect: true,
onConnectDevice: (context, ledgerVM) async {
Navigator.of(context).pop();
},
),
);
}
}
}
bool get isConnected => _connection != null && !(_connection!.isDisconnected);

View file

@ -26,8 +26,6 @@ import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/unspent_coin_type.dart';
import 'package:cake_wallet/view_model/send/output.dart';
import 'package:cake_wallet/view_model/send/send_template_view_model.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_solana/solana_exceptions.dart';
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
@ -100,6 +98,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
outputs
.add(Output(wallet, _settingsStore, _fiatConversationStore, () => selectedCryptoCurrency));
unspentCoinsListViewModel.initialSetup();
}
@observable
@ -675,19 +675,19 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
}
}
if (error is SolanaSignNativeTokenTransactionRentException) {
if (error is SignNativeTokenTransactionRentException) {
return S.current.solana_sign_native_transaction_rent_exception;
}
if (error is SolanaCreateAssociatedTokenAccountException) {
return S.current.solana_create_associated_token_account_exception;
if (error is CreateAssociatedTokenAccountException) {
return "${S.current.solana_create_associated_token_account_exception}\n\n${error.errorMessage}";
}
if (error is SolanaSignSPLTokenTransactionRentException) {
if (error is SignSPLTokenTransactionRentException) {
return S.current.solana_sign_spl_token_transaction_rent_exception;
}
if (error is SolanaNoAssociatedTokenAccountException) {
if (error is NoAssociatedTokenAccountException) {
return S.current.solana_no_associated_token_account_exception;
}
@ -717,9 +717,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
return
'''${S.current.insufficient_funds_for_tx} \n\n'''
'''${S.current.balance}: ${parsedErrorMessageResult.balanceEth} ETH (${parsedErrorMessageResult.balanceUsd} USD)\n\n'''
'''${S.current.transaction_cost}: ${parsedErrorMessageResult.txCostEth} ETH (${parsedErrorMessageResult.txCostUsd} USD)\n\n'''
'''${S.current.overshot}: ${parsedErrorMessageResult.overshotEth} ETH (${parsedErrorMessageResult.overshotUsd} USD)''';
'''${S.current.balance}: ${parsedErrorMessageResult.balanceEth} ${walletType == WalletType.polygon ? "POL" : "ETH"} (${parsedErrorMessageResult.balanceUsd} ${fiatFromSettings.name})\n\n'''
'''${S.current.transaction_cost}: ${parsedErrorMessageResult.txCostEth} ${walletType == WalletType.polygon ? "POL" : "ETH"} (${parsedErrorMessageResult.txCostUsd} ${fiatFromSettings.name})\n\n'''
'''${S.current.overshot}: ${parsedErrorMessageResult.overshotEth} ${walletType == WalletType.polygon ? "POL" : "ETH"} (${parsedErrorMessageResult.overshotUsd} ${fiatFromSettings.name})''';
}
return errorMessage;

View file

@ -11,102 +11,99 @@ class SupportViewModel = SupportViewModelBase with _$SupportViewModel;
abstract class SupportViewModelBase with Store {
SupportViewModelBase()
: items = [
LinkListItem(
title: 'Email',
icon: 'assets/images/support_icon.png',
linkTitle: 'support@cakewallet.com',
link: 'mailto:support@cakewallet.com'),
if (!isMoneroOnly)
LinkListItem(
title: 'Website',
icon: 'assets/images/global.png',
linkTitle: 'cakewallet.com',
link: 'https://cakewallet.com'),
if (!isMoneroOnly)
LinkListItem(
title: 'GitHub',
icon: 'assets/images/github.png',
hasIconColor: true,
linkTitle: S.current.apk_update,
link: 'https://github.com/cake-tech/cake_wallet/releases'),
LinkListItem(
title: 'Discord',
icon: 'assets/images/discord.png',
linkTitle: 'discord.gg/pwmWa6aFpX',
link: 'https://discord.gg/pwmWa6aFpX'),
LinkListItem(
title: 'Telegram',
icon: 'assets/images/Telegram.png',
linkTitle: 't.me/cakewallet',
link: 'https://t.me/cakewalletannouncements'),
LinkListItem(
title: 'Telegram Support Bot',
icon: 'assets/images/Telegram.png',
linkTitle: '@cakewallet_bot',
link: 'https://t.me/cakewallet_bot'),
LinkListItem(
title: 'ChangeNow',
icon: 'assets/images/change_now.png',
linkTitle: 'support@changenow.io',
link: 'mailto:support@changenow.io'),
LinkListItem(
title: 'SideShift',
icon: 'assets/images/sideshift.png',
linkTitle: 'help.sideshift.ai',
link: 'https://help.sideshift.ai/en/'),
LinkListItem(
title: 'SimpleSwap',
icon: 'assets/images/simpleSwap.png',
linkTitle: 'support@simpleswap.io',
link: 'mailto:support@simpleswap.io'),
LinkListItem(
title: 'Exolix',
icon: 'assets/images/exolix.png',
linkTitle: 'support@exolix.com',
link: 'mailto:support@exolix.com'),
LinkListItem(
title: 'Quantex',
icon: 'assets/images/quantex.png',
linkTitle: 'help.myquantex.com',
link: 'mailto:support@exolix.com'),
LinkListItem(
title: 'Trocador',
icon: 'assets/images/trocador.png',
linkTitle: 'mail@trocador.app',
link: 'mailto:mail@trocador.app'),
LinkListItem(
title: 'Onramper',
icon: 'assets/images/onramper_dark.png',
lightIcon: 'assets/images/onramper_light.png',
linkTitle: 'View exchanges',
link: 'https://docs.cakewallet.com/support/buy/#onramper'),
LinkListItem(
title: 'DFX',
icon: 'assets/images/dfx_dark.png',
lightIcon: 'assets/images/dfx_light.png',
linkTitle: 'support@dfx.swiss',
link: 'mailto:support@dfx.swiss'),
if (!isMoneroOnly) ... [
LinkListItem(
title: 'MoonPay',
icon: 'assets/images/moonpay.png',
linkTitle: S.current.submit_request,
link: 'https://support.moonpay.com/hc/en-gb/requests/new'),
LinkListItem(
title: 'Robinhood Connect',
icon: 'assets/images/robinhood_dark.png',
lightIcon: 'assets/images/robinhood_light.png',
linkTitle: S.current.submit_request,
link: 'https://robinhood.com/contact')
]
//LinkListItem(
// title: 'Yat',
// icon: 'assets/images/yat_mini_logo.png',
// hasIconColor: true,
// linkTitle: 'support@y.at',
// link: 'mailto:support@y.at')
];
: items = [
LinkListItem(
title: 'Email',
icon: 'assets/images/support_icon.png',
linkTitle: 'support@cakewallet.com',
link: 'mailto:support@cakewallet.com'),
LinkListItem(
title: 'Website',
icon: 'assets/images/global.png',
linkTitle: 'cakewallet.com',
link: 'https://cakewallet.com'),
LinkListItem(
title: 'Forum',
icon: 'assets/images/discourse.png',
linkTitle: 'forum.cakewallet.com',
link: 'https://forum.cakewallet.com'),
LinkListItem(
title: 'GitHub',
icon: 'assets/images/github.png',
hasIconColor: true,
linkTitle: S.current.apk_update,
link: 'https://github.com/cake-tech/cake_wallet/releases'),
LinkListItem(
title: 'Discord',
icon: 'assets/images/discord.png',
linkTitle: 'discord.gg/pwmWa6aFpX',
link: 'https://discord.gg/pwmWa6aFpX'),
LinkListItem(
title: 'Telegram',
icon: 'assets/images/Telegram.png',
linkTitle: 't.me/cakewallet',
link: 'https://t.me/cakewalletannouncements'),
LinkListItem(
title: 'Telegram Support Bot',
icon: 'assets/images/Telegram.png',
linkTitle: '@cakewallet_bot',
link: 'https://t.me/cakewallet_bot'),
LinkListItem(
title: 'ChangeNow',
icon: 'assets/images/change_now.png',
linkTitle: 'support@changenow.io',
link: 'mailto:support@changenow.io'),
LinkListItem(
title: 'SideShift',
icon: 'assets/images/sideshift.png',
linkTitle: 'help.sideshift.ai',
link: 'https://help.sideshift.ai/en/'),
LinkListItem(
title: 'SimpleSwap',
icon: 'assets/images/simpleSwap.png',
linkTitle: 'support@simpleswap.io',
link: 'mailto:support@simpleswap.io'),
LinkListItem(
title: 'Exolix',
icon: 'assets/images/exolix.png',
linkTitle: 'support@exolix.com',
link: 'mailto:support@exolix.com'),
LinkListItem(
title: 'Quantex',
icon: 'assets/images/quantex.png',
linkTitle: 'help.myquantex.com',
link: 'mailto:support@exolix.com'),
LinkListItem(
title: 'Trocador',
icon: 'assets/images/trocador.png',
linkTitle: 'mail@trocador.app',
link: 'mailto:mail@trocador.app'),
LinkListItem(
title: 'Onramper',
icon: 'assets/images/onramper_dark.png',
lightIcon: 'assets/images/onramper_light.png',
linkTitle: 'View exchanges',
link: 'https://docs.cakewallet.com/support/buy/#onramper'),
LinkListItem(
title: 'DFX',
icon: 'assets/images/dfx_dark.png',
lightIcon: 'assets/images/dfx_light.png',
linkTitle: 'support@dfx.swiss',
link: 'mailto:support@dfx.swiss'),
if (!isMoneroOnly) ...[
LinkListItem(
title: 'MoonPay',
icon: 'assets/images/moonpay.png',
linkTitle: S.current.submit_request,
link: 'https://support.moonpay.com/hc/en-gb/requests/new'),
LinkListItem(
title: 'Robinhood Connect',
icon: 'assets/images/robinhood_dark.png',
lightIcon: 'assets/images/robinhood_light.png',
linkTitle: S.current.submit_request,
link: 'https://robinhood.com/contact')
]
];
final docsUrl = 'https://docs.cakewallet.com';
@ -114,8 +111,7 @@ abstract class SupportViewModelBase with Store {
var supportUrl =
"https://app.chatwoot.com/widget?website_token=${secrets.chatwootWebsiteToken}&locale=${locale}";
if (authToken.isNotEmpty)
supportUrl += "&cw_conversation=$authToken";
if (authToken.isNotEmpty) supportUrl += "&cw_conversation=$authToken";
return supportUrl;
}

View file

@ -29,6 +29,7 @@ abstract class WalletSeedViewModelBase with Store {
List<String> get seedSplit => seed.split(RegExp(r'\s+'));
int get columnCount => seedSplit.length <= 16 ? 2 : 3;
double get columnAspectRatio => seedSplit.length <= 16 ? 1.8 : 2.8;
/// The indices of the seed to be verified.
@ -60,8 +61,10 @@ abstract class WalletSeedViewModelBase with Store {
bool isVerificationComplete = false;
void setupSeedVerification() {
generateRandomIndices();
generateOptions();
if (verificationWordCount != 0) {
generateRandomIndices();
generateOptions();
}
}
/// Generate the indices of the seeds to be verified.

View file

@ -40,11 +40,11 @@ static void my_application_activate(GApplication* application) {
if (use_header_bar) {
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
gtk_widget_show(GTK_WIDGET(header_bar));
gtk_header_bar_set_title(header_bar, "cake_wallet");
gtk_header_bar_set_title(header_bar, "Cake Wallet");
gtk_header_bar_set_show_close_button(header_bar, TRUE);
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
} else {
gtk_window_set_title(window, "cake_wallet");
gtk_window_set_title(window, "Cake Wallet");
}
gtk_window_set_default_size(window, 1280, 720);

View file

@ -106,7 +106,7 @@ dependencies:
flutter_svg: ^2.0.9
polyseed: ^0.0.6
nostr_tools: ^1.0.9
solana: ^0.30.1
solana: ^0.31.0+1
ledger_flutter_plus: ^1.4.1
hashlib: ^1.19.2

View file

@ -330,6 +330,7 @@
"freeze": "تجميد",
"frequently_asked_questions": "الأسئلة الشائعة",
"frozen": "مجمدة",
"frozen_balance": "التوازن المجمد",
"full_balance": "الرصيد الكامل",
"gas_exceeds_allowance": "الغاز المطلوب بالمعاملة يتجاوز البدل.",
"generate_name": "توليد الاسم",

View file

@ -330,6 +330,7 @@
"freeze": "Замразяване",
"frequently_asked_questions": "Често задавани въпроси",
"frozen": "Замразени",
"frozen_balance": "Замразен баланс",
"full_balance": "Пълен баланс",
"gas_exceeds_allowance": "Газът, изискван от транзакцията, надвишава надбавката.",
"generate_name": "Генериране на име",

View file

@ -330,6 +330,7 @@
"freeze": "Zmrazit",
"frequently_asked_questions": "Často kladené otázky",
"frozen": "Zmraženo",
"frozen_balance": "Zmrazená rovnováha",
"full_balance": "Celkový zůstatek",
"gas_exceeds_allowance": "Plyn vyžadovaný transakcí přesahuje příspěvek.",
"generate_name": "Generovat jméno",

View file

@ -330,6 +330,7 @@
"freeze": "Einfrieren",
"frequently_asked_questions": "Häufig gestellte Fragen",
"frozen": "Gefroren",
"frozen_balance": "Gefrorenes Gleichgewicht",
"full_balance": "Gesamtguthaben",
"gas_exceeds_allowance": "Die durch Transaktion erforderliche Gas übertrifft die Zulage.",
"generate_name": "Namen generieren",

View file

@ -330,6 +330,7 @@
"freeze": "Freeze",
"frequently_asked_questions": "Frequently asked questions",
"frozen": "Frozen",
"frozen_balance": "Frozen Balance",
"full_balance": "Full Balance",
"gas_exceeds_allowance": "Gas required by transaction exceeds allowance.",
"generate_name": "Generate Name",

View file

@ -330,6 +330,7 @@
"freeze": "Congelar",
"frequently_asked_questions": "Preguntas frecuentes",
"frozen": "Congelada",
"frozen_balance": "Equilibrio congelado",
"full_balance": "Balance completo",
"gas_exceeds_allowance": "El gas requerido por la transacción excede la asignación.",
"generate_name": "Generar nombre",

View file

@ -330,6 +330,7 @@
"freeze": "Geler",
"frequently_asked_questions": "Foire aux questions",
"frozen": "Gelées",
"frozen_balance": "Équilibre gelé",
"full_balance": "Solde Complet",
"gas_exceeds_allowance": "Le gaz requis par la transaction dépasse l'allocation.",
"generate_name": "Générer un nom",

View file

@ -330,6 +330,7 @@
"freeze": "Daskare",
"frequently_asked_questions": "Tambayoyin da ake yawan yi",
"frozen": "Daskararre",
"frozen_balance": "Daidaituwa mai sanyi",
"full_balance": "DUKAN KUDI",
"gas_exceeds_allowance": "Gas da ake buƙata ta hanyar ma'amala ya wuce izini.",
"generate_name": "Ƙirƙirar Suna",

View file

@ -330,6 +330,7 @@
"freeze": "फ्रीज",
"frequently_asked_questions": "अक्सर पूछे जाने वाले प्रश्न",
"frozen": "जमा हुआ",
"frozen_balance": "जमे हुए संतुलन",
"full_balance": "पूर्ण संतुलन",
"gas_exceeds_allowance": "लेनदेन द्वारा आवश्यक गैस भत्ता से अधिक है।",
"generate_name": "नाम जनरेट करें",

View file

@ -330,6 +330,7 @@
"freeze": "Zamrznuti",
"frequently_asked_questions": "Često postavljana pitanja",
"frozen": "Smrznuto",
"frozen_balance": "Smrznuta ravnoteža",
"full_balance": "Pun iznos",
"gas_exceeds_allowance": "Plin potreban transakcijom premašuje dodatak.",
"generate_name": "Generiraj ime",

View file

@ -330,6 +330,7 @@
"freeze": "Կասեցնել",
"frequently_asked_questions": "Հաճախ տրվող հարցեր",
"frozen": "Կասեցված",
"frozen_balance": "Սառեցված հավասարակշռություն",
"full_balance": "Լրիվ մնացորդ",
"gas_exceeds_allowance": "Գործարքով պահանջվող գազը գերազանցում է նպաստը:",
"generate_name": "Գեներացնել անուն",

View file

@ -330,6 +330,7 @@
"freeze": "Freeze",
"frequently_asked_questions": "Pertanyaan yang sering diajukan",
"frozen": "Dibekukan",
"frozen_balance": "Keseimbangan beku",
"full_balance": "Saldo Penuh",
"gas_exceeds_allowance": "Gas yang dibutuhkan oleh transaksi melebihi tunjangan.",
"generate_name": "Hasilkan Nama",

View file

@ -331,6 +331,7 @@
"freeze": "Congelare",
"frequently_asked_questions": "Domande frequenti",
"frozen": "Congelato",
"frozen_balance": "Equilibrio congelato",
"full_balance": "Saldo Completo",
"gas_exceeds_allowance": "Il gas richiesto dalla transazione supera l'indennità.",
"generate_name": "Genera nome",

View file

@ -330,6 +330,7 @@
"freeze": "氷結",
"frequently_asked_questions": "よくある質問",
"frozen": "凍った",
"frozen_balance": "凍結バランス",
"full_balance": "フルバランス",
"gas_exceeds_allowance": "取引に必要なガスは、手当を超えています。",
"generate_name": "名前の生成",

View file

@ -330,6 +330,7 @@
"freeze": "얼다",
"frequently_asked_questions": "자주 묻는 질문",
"frozen": "겨울 왕국",
"frozen_balance": "냉동 균형",
"full_balance": "풀 밸런스",
"gas_exceeds_allowance": "거래에 필요한 가스는 수당을 초과합니다.",
"generate_name": "이름 생성",

View file

@ -330,6 +330,7 @@
"freeze": "အေးခဲ",
"frequently_asked_questions": "မေးလေ့ရှိသောမေးခွန်းများ",
"frozen": "ဖြူဖြူ",
"frozen_balance": "လက်ကျန်ငွေ",
"full_balance": "Balance အပြည့်",
"gas_exceeds_allowance": "ငွေပေးငွေယူမှလိုအပ်သောဓာတ်ငွေ့ထောက်ပံ့ကြေးကျော်လွန်။",
"generate_name": "အမည်ဖန်တီးပါ။",

View file

@ -330,6 +330,7 @@
"freeze": "Bevriezen",
"frequently_asked_questions": "Veelgestelde vragen",
"frozen": "Bevroren",
"frozen_balance": "Bevroren balans",
"full_balance": "Volledig saldo",
"gas_exceeds_allowance": "Gas vereist door transactie overschrijdt de vergoeding.",
"generate_name": "Naam genereren",

View file

@ -330,6 +330,7 @@
"freeze": "Zamróź",
"frequently_asked_questions": "Często zadawane pytania",
"frozen": "Zamrożone",
"frozen_balance": "Mrożona równowaga",
"full_balance": "Pełne saldo",
"gas_exceeds_allowance": "Gaz wymagany przez transakcję przekracza dodatek.",
"generate_name": "Wygeneruj nazwę",

View file

@ -330,6 +330,7 @@
"freeze": "Congelar",
"frequently_asked_questions": "Perguntas frequentes",
"frozen": "Congeladas",
"frozen_balance": "Equilíbrio congelado",
"full_balance": "Saldo total",
"gas_exceeds_allowance": "O gás exigido pela transação excede o subsídio.",
"generate_name": "Gerar nome",

View file

@ -330,6 +330,7 @@
"freeze": "Заморозить",
"frequently_asked_questions": "Часто задаваемые вопросы",
"frozen": "Заморожено",
"frozen_balance": "Замороженный баланс",
"full_balance": "Весь баланс",
"gas_exceeds_allowance": "Газ, требуемый в результате транзакции, превышает пособие.",
"generate_name": "Создать имя",

View file

@ -330,6 +330,7 @@
"freeze": "ดักจับ",
"frequently_asked_questions": "คำถามที่พบบ่อย",
"frozen": "ถูกดักจับ",
"frozen_balance": "สมดุลแช่แข็ง",
"full_balance": "ยอดคงเหลือทั้งหมด",
"gas_exceeds_allowance": "ก๊าซที่ต้องการโดยการทำธุรกรรมเกินค่าเผื่อ",
"generate_name": "สร้างชื่อ",

View file

@ -330,6 +330,7 @@
"freeze": "I-freeze",
"frequently_asked_questions": "Mga madalas itanong",
"frozen": "Frozen",
"frozen_balance": "Frozen na balanse",
"full_balance": "Buong Balanse",
"gas_exceeds_allowance": "Ang gas na kinakailangan ng transaksyon ay lumampas sa allowance.",
"generate_name": "Bumuo ng pangalan",

View file

@ -330,6 +330,7 @@
"freeze": "Dondur",
"frequently_asked_questions": "Sıkça sorulan sorular",
"frozen": "Dondurulmuş",
"frozen_balance": "Dondurulmuş denge",
"full_balance": "Tüm bakiye",
"gas_exceeds_allowance": "İşlemin gerektirdiği gaz ödeneği aşar.",
"generate_name": "İsim Oluştur",

View file

@ -330,6 +330,7 @@
"freeze": "Заморозити",
"frequently_asked_questions": "Часті запитання",
"frozen": "Заморожено",
"frozen_balance": "Заморожений баланс",
"full_balance": "Весь баланс",
"gas_exceeds_allowance": "Газ, необхідний транзакціям, перевищує надбавку.",
"generate_name": "Згенерувати назву",

View file

@ -330,6 +330,7 @@
"freeze": "منجمد",
"frequently_asked_questions": "اکثر پوچھے گئے سوالات",
"frozen": "منجمد",
"frozen_balance": "منجمد توازن",
"full_balance": "مکمل بیلنس",
"gas_exceeds_allowance": "لین دین کے ذریعہ درکار گیس الاؤنس سے زیادہ ہے۔",
"generate_name": "نام پیدا کریں۔",

View file

@ -329,6 +329,7 @@
"freeze": "Đóng băng",
"frequently_asked_questions": "Các câu hỏi thường gặp",
"frozen": "Đã đóng băng",
"frozen_balance": "Cân bằng đông lạnh",
"full_balance": "Số dư đầy đủ",
"gas_exceeds_allowance": "Gas theo yêu cầu của giao dịch vượt quá trợ cấp.",
"generate_name": "Tạo tên",

View file

@ -331,6 +331,7 @@
"freeze": "Tì pa",
"frequently_asked_questions": "Àwọn ìbéèrè la máa ń béèrè",
"frozen": "Ó l'a tì pa",
"frozen_balance": "Iwontunwonsi ti o tutu",
"full_balance": "Ìyókù owó kíkún",
"gas_exceeds_allowance": "Gaasi ti a beere nipasẹ idunadura ju lọ.",
"generate_name": "Ṣẹda Orukọ",

View file

@ -330,6 +330,7 @@
"freeze": "凍結",
"frequently_asked_questions": "常见问题",
"frozen": "凍結的",
"frozen_balance": "冷冻平衡",
"full_balance": "全部余额",
"gas_exceeds_allowance": "交易要求的气体超出了津贴。",
"generate_name": "生成名称",

View file

@ -15,15 +15,15 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN)
APP_ANDROID_TYPE=$1
MONERO_COM_NAME="Monero.com"
MONERO_COM_VERSION="1.18.2"
MONERO_COM_BUILD_NUMBER=108
MONERO_COM_VERSION="1.19.0"
MONERO_COM_BUILD_NUMBER=109
MONERO_COM_BUNDLE_ID="com.monero.app"
MONERO_COM_PACKAGE="com.monero.app"
MONERO_COM_SCHEME="monero.com"
CAKEWALLET_NAME="Cake Wallet"
CAKEWALLET_VERSION="4.21.2"
CAKEWALLET_BUILD_NUMBER=239
CAKEWALLET_VERSION="4.22.0"
CAKEWALLET_BUILD_NUMBER=240
CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet"
CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet"
CAKEWALLET_SCHEME="cakewallet"

View file

@ -13,13 +13,13 @@ TYPES=($MONERO_COM $CAKEWALLET $HAVEN)
APP_IOS_TYPE=$1
MONERO_COM_NAME="Monero.com"
MONERO_COM_VERSION="1.18.2"
MONERO_COM_BUILD_NUMBER=105
MONERO_COM_VERSION="1.19.0"
MONERO_COM_BUILD_NUMBER=106
MONERO_COM_BUNDLE_ID="com.cakewallet.monero"
CAKEWALLET_NAME="Cake Wallet"
CAKEWALLET_VERSION="4.21.2"
CAKEWALLET_BUILD_NUMBER=284
CAKEWALLET_VERSION="4.22.0"
CAKEWALLET_BUILD_NUMBER=287
CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"
HAVEN_NAME="Haven"

View file

@ -14,8 +14,8 @@ if [ -n "$1" ]; then
fi
CAKEWALLET_NAME="Cake Wallet"
CAKEWALLET_VERSION="1.11.2"
CAKEWALLET_BUILD_NUMBER=40
CAKEWALLET_VERSION="1.12.0"
CAKEWALLET_BUILD_NUMBER=41
if ! [[ " ${TYPES[*]} " =~ " ${APP_LINUX_TYPE} " ]]; then
echo "Wrong app type."

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