mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-01-05 10:29:23 +00:00
Merge branch 'main' into CW-873-fix-airgap-export-output-flow
This commit is contained in:
commit
2e4c5c4470
103 changed files with 2218 additions and 1983 deletions
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
|
@ -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
|
||||
|
|
597
.github/workflows/automated_integration_test.yml
vendored
597
.github/workflows/automated_integration_test.yml
vendored
|
@ -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
|
||||
|
|
3
.github/workflows/pr_test_build_android.yml
vendored
3
.github/workflows/pr_test_build_android.yml
vendored
|
@ -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
|
||||
|
|
3
.github/workflows/pr_test_build_linux.yml
vendored
3
.github/workflows/pr_test_build_linux.yml
vendored
|
@ -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
|
||||
|
|
|
@ -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
BIN
assets/images/discord.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
BIN
assets/images/discourse.png
Normal file
BIN
assets/images/discourse.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 57 KiB |
|
@ -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
|
||||
-
|
||||
|
|
|
@ -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
|
|
@ -1,2 +1,3 @@
|
|||
UI/UX enhancements
|
||||
Bug fixes and app improvements
|
||||
Support Monero Ledger
|
||||
Bug fixes
|
||||
New designs and better user experience
|
|
@ -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
|
|
@ -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
|
|
@ -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) =>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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) ?? [];
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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'
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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':
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -215,7 +215,7 @@ Future<void> initializeAppConfigs() async {
|
|||
secureStorage: secureStorage,
|
||||
anonpayInvoiceInfo: anonpayInvoiceInfo,
|
||||
havenSeedStore: havenSeedStore,
|
||||
initialMigrationVersion: 45,
|
||||
initialMigrationVersion: 46,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
],
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
|
|
88
lib/src/screens/dashboard/pages/balance/balance_page.dart
Normal file
88
lib/src/screens/dashboard/pages/balance/balance_page.dart
Normal 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)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
654
lib/src/screens/dashboard/pages/balance/balance_row_widget.dart
Normal file
654
lib/src/screens/dashboard/pages/balance/balance_row_widget.dart
Normal 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));
|
||||
}
|
||||
}
|
|
@ -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
|
@ -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),
|
||||
)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -330,6 +330,7 @@
|
|||
"freeze": "تجميد",
|
||||
"frequently_asked_questions": "الأسئلة الشائعة",
|
||||
"frozen": "مجمدة",
|
||||
"frozen_balance": "التوازن المجمد",
|
||||
"full_balance": "الرصيد الكامل",
|
||||
"gas_exceeds_allowance": "الغاز المطلوب بالمعاملة يتجاوز البدل.",
|
||||
"generate_name": "توليد الاسم",
|
||||
|
|
|
@ -330,6 +330,7 @@
|
|||
"freeze": "Замразяване",
|
||||
"frequently_asked_questions": "Често задавани въпроси",
|
||||
"frozen": "Замразени",
|
||||
"frozen_balance": "Замразен баланс",
|
||||
"full_balance": "Пълен баланс",
|
||||
"gas_exceeds_allowance": "Газът, изискван от транзакцията, надвишава надбавката.",
|
||||
"generate_name": "Генериране на име",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -330,6 +330,7 @@
|
|||
"freeze": "फ्रीज",
|
||||
"frequently_asked_questions": "अक्सर पूछे जाने वाले प्रश्न",
|
||||
"frozen": "जमा हुआ",
|
||||
"frozen_balance": "जमे हुए संतुलन",
|
||||
"full_balance": "पूर्ण संतुलन",
|
||||
"gas_exceeds_allowance": "लेनदेन द्वारा आवश्यक गैस भत्ता से अधिक है।",
|
||||
"generate_name": "नाम जनरेट करें",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -330,6 +330,7 @@
|
|||
"freeze": "Կասեցնել",
|
||||
"frequently_asked_questions": "Հաճախ տրվող հարցեր",
|
||||
"frozen": "Կասեցված",
|
||||
"frozen_balance": "Սառեցված հավասարակշռություն",
|
||||
"full_balance": "Լրիվ մնացորդ",
|
||||
"gas_exceeds_allowance": "Գործարքով պահանջվող գազը գերազանցում է նպաստը:",
|
||||
"generate_name": "Գեներացնել անուն",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -330,6 +330,7 @@
|
|||
"freeze": "氷結",
|
||||
"frequently_asked_questions": "よくある質問",
|
||||
"frozen": "凍った",
|
||||
"frozen_balance": "凍結バランス",
|
||||
"full_balance": "フルバランス",
|
||||
"gas_exceeds_allowance": "取引に必要なガスは、手当を超えています。",
|
||||
"generate_name": "名前の生成",
|
||||
|
|
|
@ -330,6 +330,7 @@
|
|||
"freeze": "얼다",
|
||||
"frequently_asked_questions": "자주 묻는 질문",
|
||||
"frozen": "겨울 왕국",
|
||||
"frozen_balance": "냉동 균형",
|
||||
"full_balance": "풀 밸런스",
|
||||
"gas_exceeds_allowance": "거래에 필요한 가스는 수당을 초과합니다.",
|
||||
"generate_name": "이름 생성",
|
||||
|
|
|
@ -330,6 +330,7 @@
|
|||
"freeze": "အေးခဲ",
|
||||
"frequently_asked_questions": "မေးလေ့ရှိသောမေးခွန်းများ",
|
||||
"frozen": "ဖြူဖြူ",
|
||||
"frozen_balance": "လက်ကျန်ငွေ",
|
||||
"full_balance": "Balance အပြည့်",
|
||||
"gas_exceeds_allowance": "ငွေပေးငွေယူမှလိုအပ်သောဓာတ်ငွေ့ထောက်ပံ့ကြေးကျော်လွန်။",
|
||||
"generate_name": "အမည်ဖန်တီးပါ။",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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ę",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -330,6 +330,7 @@
|
|||
"freeze": "Заморозить",
|
||||
"frequently_asked_questions": "Часто задаваемые вопросы",
|
||||
"frozen": "Заморожено",
|
||||
"frozen_balance": "Замороженный баланс",
|
||||
"full_balance": "Весь баланс",
|
||||
"gas_exceeds_allowance": "Газ, требуемый в результате транзакции, превышает пособие.",
|
||||
"generate_name": "Создать имя",
|
||||
|
|
|
@ -330,6 +330,7 @@
|
|||
"freeze": "ดักจับ",
|
||||
"frequently_asked_questions": "คำถามที่พบบ่อย",
|
||||
"frozen": "ถูกดักจับ",
|
||||
"frozen_balance": "สมดุลแช่แข็ง",
|
||||
"full_balance": "ยอดคงเหลือทั้งหมด",
|
||||
"gas_exceeds_allowance": "ก๊าซที่ต้องการโดยการทำธุรกรรมเกินค่าเผื่อ",
|
||||
"generate_name": "สร้างชื่อ",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -330,6 +330,7 @@
|
|||
"freeze": "Заморозити",
|
||||
"frequently_asked_questions": "Часті запитання",
|
||||
"frozen": "Заморожено",
|
||||
"frozen_balance": "Заморожений баланс",
|
||||
"full_balance": "Весь баланс",
|
||||
"gas_exceeds_allowance": "Газ, необхідний транзакціям, перевищує надбавку.",
|
||||
"generate_name": "Згенерувати назву",
|
||||
|
|
|
@ -330,6 +330,7 @@
|
|||
"freeze": "منجمد",
|
||||
"frequently_asked_questions": "اکثر پوچھے گئے سوالات",
|
||||
"frozen": "منجمد",
|
||||
"frozen_balance": "منجمد توازن",
|
||||
"full_balance": "مکمل بیلنس",
|
||||
"gas_exceeds_allowance": "لین دین کے ذریعہ درکار گیس الاؤنس سے زیادہ ہے۔",
|
||||
"generate_name": "نام پیدا کریں۔",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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ọ",
|
||||
|
|
|
@ -330,6 +330,7 @@
|
|||
"freeze": "凍結",
|
||||
"frequently_asked_questions": "常见问题",
|
||||
"frozen": "凍結的",
|
||||
"frozen_balance": "冷冻平衡",
|
||||
"full_balance": "全部余额",
|
||||
"gas_exceeds_allowance": "交易要求的气体超出了津贴。",
|
||||
"generate_name": "生成名称",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue