diff --git a/.github/workflows/cache_dependencies.yml b/.github/workflows/cache_dependencies.yml deleted file mode 100644 index cb2afa3962..0000000000 --- a/.github/workflows/cache_dependencies.yml +++ /dev/null @@ -1,84 +0,0 @@ -name: Cache Dependencies - -on: - workflow_dispatch: - push: - branches: [ main ] - -jobs: - test: - - runs-on: ubuntu-20.04 - - steps: - - 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.4" - channel: stable - - - name: Install package dependencies - run: 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 main - 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: Cache Keystore - id: cache-keystore - uses: actions/cache@v3 - with: - path: /opt/android/cake_wallet/android/app - key: keystore - - - if: ${{ steps.cache-keystore.outputs.cache-hit != 'true' }} - name: Generate KeyStore - run: | - cd /opt/android/cake_wallet/android/app - keytool -genkey -v -keystore key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias testKey -noprompt -dname "CN=CakeWallet, OU=CakeWallet, O=CakeWallet, L=Florida, S=America, C=USA" -storepass $STORE_PASS -keypass $KEY_PASS diff --git a/.github/workflows/pr_test_build_android.yml b/.github/workflows/pr_test_build_android.yml index cdd0e40b44..c5f8751287 100644 --- a/.github/workflows/pr_test_build_android.yml +++ b/.github/workflows/pr_test_build_android.yml @@ -1,169 +1,93 @@ -name: PR Test Build +name: Cake Wallet Android -on: - pull_request: - branches: [main] - workflow_dispatch: - inputs: - branch: - description: "Branch name to build" - required: true - default: "main" +on: [push] +defaults: + run: + shell: bash jobs: PR_test_build: - runs-on: ubuntu-20.04 + runs-on: linux-amd64 + container: + image: ghcr.io/cake-tech/cake_wallet:main-linux + env: + STORE_PASS: test@cake_wallet + KEY_PASS: test@cake_wallet + MONEROC_CACHE_DIR_ROOT: /opt/generic_cache + BRANCH_NAME: ${{ github.head_ref || github.ref_name }} + ANDROID_AVD_HOME: /root/.android/avd + volumes: + - /opt/cw_cache_android/root/.cache:/root/.cache + - /opt/cw_cache_android/root/.android/avd/:/root/.android/avd + - /opt/cw_cache_android/root/.ccache:/root/.ccache + - /opt/cw_cache_android/root/.pub-cache/:/root/.pub-cache + - /opt/cw_cache_android/root/.gradle/:/root/.gradle + - /opt/cw_cache_android/root/.android/:/root/.android + - /opt/cw_cache_android/root/go/pkg:/root/go/pkg + - /opt/cw_cache_android/opt/generic_cache:/opt/generic_cache + - /dev/kvm:/dev/kvm strategy: matrix: api-level: [29] - 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: Clone Repo - run: | - sudo mkdir -p /opt/android - sudo chown $USER /opt/android - cd /opt/android - git clone https://github.com/cake-tech/cake_wallet.git --branch ${{ env.BRANCH_NAME }} - -# - name: Cache Keystore -# id: cache-keystore -# uses: actions/cache@v3 -# with: -# path: /opt/android/cake_wallet/android/app -# key: keystore -# -# - if: ${{ steps.cache-keystore.outputs.cache-hit != 'true' }} - - name: Generate KeyStore - run: | - cd /opt/android/cake_wallet/android/app - keytool -genkey -v -keystore key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias testKey -noprompt -dname "CN=CakeWallet, OU=CakeWallet, O=CakeWallet, L=Florida, S=America, C=USA" -storepass $STORE_PASS -keypass $KEY_PASS - - - name: Execute Build and Setup Commands - run: | - cd /opt/android - -y curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - cargo install cargo-ndk - 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 key properties - run: | - cd /opt/android/cake_wallet - dart 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 - dart run tool/generate_localization.dart - - - name: Build generated code + - name: Fix github actions messing up $HOME... + run: 'echo HOME=/root | sudo tee -a $GITHUB_ENV' + - uses: actions/checkout@v4 + - name: configure git run: | - cd /opt/android/cake_wallet - ./model_generator.sh - + git config --global user.email "ci@cakewallet.com" + git config --global user.name "CakeWallet CI" - 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 + if [[ "x${{ secrets.SALT }}" == "x" ]]; + then + echo "const salt = '954f787f12622067f7e548d9450c3832';" > lib/.secrets.g.dart + else + echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart + fi + if [[ "x${{ secrets.KEY_CHAIN_SALT }}" == "x" ]]; + then + echo "const keychainSalt = '2d2beba777dbf7dff7013b7a';" >> lib/.secrets.g.dart + else + echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart + fi + if [[ "x${{ secrets.KEY }}" == "x" ]]; + then + echo "const key = '638e98820ec10a2945e968435c9397a3';" >> lib/.secrets.g.dart + else + echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart + fi + if [[ "x${{ secrets.WALLET_SALT }}" == "x" ]]; + then + echo "const walletSalt = '8f7f1b70';" >> lib/.secrets.g.dart + else + echo "const walletSalt = '${{ secrets.WALLET_SALT }}';" >> lib/.secrets.g.dart + fi + if [[ "x${{ secrets.SHORT_KEY }}" == "x" ]]; + then + echo "const shortKey = '653f270c2c152bc7ec864afe';" >> lib/.secrets.g.dart + else + echo "const shortKey = '${{ secrets.SHORT_KEY }}';" >> lib/.secrets.g.dart + fi + if [[ "x${{ secrets.BACKUP_SALT }}" == "x" ]]; + then + echo "const backupSalt = 'bf630d24ff0b6f60';" >> lib/.secrets.g.dart + else + echo "const backupSalt = '${{ secrets.BACKUP_SALT }}';" >> lib/.secrets.g.dart + fi + if [[ "x${{ secrets.BACKUP_KEY_CHAIN_SALT }}" == "x" ]]; + then + echo "const backupKeychainSalt = 'bf630d24ff0b6f60';" >> lib/.secrets.g.dart + else + echo "const backupKeychainSalt = '${{ secrets.BACKUP_KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart + fi 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 @@ -213,86 +137,153 @@ jobs: 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 + # for tests + 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: prepare monero_c and cache + run: | + export MONEROC_HASH=$(cat scripts/prepare_moneroc.sh | grep 'git checkout' | xargs | awk '{ print $3 }') + echo MONEROC_HASH=$MONEROC_HASH >> /etc/environment + mkdir -p "$MONEROC_CACHE_DIR_ROOT/moneroc-$MONEROC_HASH/monero_c" + pushd scripts + ln -s "$MONEROC_CACHE_DIR_ROOT/moneroc-$MONEROC_HASH/monero_c" + ./prepare_moneroc.sh + popd + pushd scripts/monero_c + mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/contrib/depends/built" || true + mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/monero/contrib/depends/built" || true + mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/wownero/contrib/depends/built" || true + mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/contrib/depends/sources" || true + mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/monero/contrib/depends/sources" || true + mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/wownero/contrib/depends/sources" || true + + rm -rf "$PWD/contrib/depends/built" "$PWD/monero/contrib/depends/built" "$PWD/wownero/contrib/depends/built" + rm -rf "$PWD/contrib/depends/sources" "$PWD/monero/contrib/depends/sources" "$PWD/wownero/contrib/depends/sources" + mkdir -p contrib/depends || true + ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/contrib/depends/built" "$PWD/contrib/depends/built" + ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/monero/contrib/depends/built" "$PWD/monero/contrib/depends/built" + ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/wownero/contrib/depends/built" "$PWD/wownero/contrib/depends/built" + ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/contrib/depends/sources" "$PWD/contrib/depends/sources" + ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/monero/contrib/depends/sources" "$PWD/monero/contrib/depends/sources" + ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/wownero/contrib/depends/sources" "$PWD/wownero/contrib/depends/sources" + popd - - name: Rename app + - name: Generate KeyStore run: | - echo -e "id=com.cakewallet.test_${{ env.PR_NUMBER }}\nname=${{ env.BRANCH_NAME }}" > /opt/android/cake_wallet/android/app.properties + pushd /opt/generic_cache + if [[ ! -f key.jks ]]; + then + 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 + else + echo "$PWD/key.jks exist, not generating" + fi + popd + cp /opt/generic_cache/key.jks android/app - # Step 3: Download previous build number - - name: Download previous build number - id: download-build-number + - name: Execute Build and Setup Commands run: | - # Download the artifact if it exists - if [[ ! -f build_number.txt ]]; then - echo "1" > build_number.txt - fi + pushd scripts/android + source ./app_env.sh cakewallet + ./app_config.sh + popd + + - name: Build monero_c + run: | + pushd scripts/android/ + source ./app_env.sh cakewallet + ./build_monero_all.sh + popd + + - name: Install Flutter dependencies + run: | + flutter pub get - # Step 4: Read and Increment Build Number - - name: Increment Build Number - id: increment-build-number + - name: Build mwebd + run: | + set -x -e + export MWEBD_HASH=$(cat scripts/android/build_mwebd.sh | grep 'git reset --hard' | xargs | awk '{ print $4 }') + echo MWEBD_HASH=$MWEBD_HASH >> /etc/environment + pushd scripts/android + gomobile init; + ./build_mwebd.sh --dont-install + popd + + - name: Build generated code run: | - # Read current build number from file - BUILD_NUMBER=$(cat build_number.txt) - BUILD_NUMBER=$((BUILD_NUMBER + 1)) - echo "New build number: $BUILD_NUMBER" + ./model_generator.sh async - # Save the incremented build number - echo "$BUILD_NUMBER" > build_number.txt + - name: Generate key properties + run: | + dart run tool/generate_android_key_properties.dart keyAlias=testKey storeFile=key.jks storePassword=$STORE_PASS keyPassword=$KEY_PASS - # Export the build number to use in later steps - echo "BUILD_NUMBER=$BUILD_NUMBER" >> $GITHUB_ENV + - name: Generate localization + run: | + dart run tool/generate_localization.dart - # Step 5: Update pubspec.yaml with new build number - - name: Update build number + - name: Rename app run: | - cd /opt/android/cake_wallet - sed -i "s/^version: .*/version: 1.0.$BUILD_NUMBER/" pubspec.yaml + sanitized_branch_name=${BRANCH_NAME#origin/} # Remove 'origin/' prefix if it exists + sanitized_branch_name=${sanitized_branch_name:0:16} # Take only the first 16 characters + sanitized_branch_name=$(echo "$sanitized_branch_name" | tr '[:upper:]' '[:lower:]') # Convert to lowercase + sanitized_branch_name=$(echo "$sanitized_branch_name" | sed 's/[^a-z0-9]//g') # Remove all special characters + + echo -e "id=com.cakewallet.test_${sanitized_branch_name}\nname=${BRANCH_NAME}" > android/app.properties - name: Build run: | - cd /opt/android/cake_wallet flutter build apk --release --split-per-abi - # - name: Push to App Center - # run: | - # echo 'Installing App Center CLI tools' - # npm install -g appcenter-cli - # echo "Publishing test to App Center" - # appcenter distribute release \ - # --group "Testers" \ - # --file "/opt/android/cake_wallet/build/app/outputs/apk/release/app-release.apk" \ - # --release-notes ${{ env.BRANCH_NAME }} \ - # --app Cake-Labs/Cake-Wallet \ - # --token ${{ secrets.APP_CENTER_TOKEN }} \ - # --quiet - - name: Rename apk file run: | - cd /opt/android/cake_wallet/build/app/outputs/flutter-apk + cd 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/ - - # Re-upload updated build number for the next run - - name: Upload updated build number - uses: actions/upload-artifact@v3 - with: - name: build_number - path: build_number.txt + cp app-arm64-v8a-release.apk test-apk/${BRANCH_NAME}.apk + cp app-x86_64-release.apk test-apk/${BRANCH_NAME}_x86.apk + cd test-apk + cp ${BRANCH_NAME}.apk ${BRANCH_NAME}_slack.apk - - name: Send Test APK + - name: Find APK file + id: find_apk + run: | + set -x + apk_file=$(ls build/app/outputs/flutter-apk/test-apk/*_slack.apk || exit 1) + echo "APK_FILE=$apk_file" >> $GITHUB_ENV + + - name: Upload artifact to slack + if: ${{ !contains(github.event.head_commit.message, 'skip slack') }} 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 + path: ${{ env.APK_FILE }} channel: ${{ secrets.SLACK_APK_CHANNEL }} - title: "${{ env.BRANCH_NAME }}.apk" - filename: ${{ env.BRANCH_NAME }}.apk initial_comment: ${{ github.event.head_commit.message }} + - name: cleanup + run: rm -rf build/app/outputs/flutter-apk/test-apk/ + + - name: Upload Artifact to github + uses: actions/upload-artifact@v4 + with: + path: ${{ github.workspace }}/build/app/outputs/flutter-apk + name: "android apk" \ No newline at end of file diff --git a/.github/workflows/pr_test_build_linux.yml b/.github/workflows/pr_test_build_linux.yml index 891327d1e7..345c95c4f7 100644 --- a/.github/workflows/pr_test_build_linux.yml +++ b/.github/workflows/pr_test_build_linux.yml @@ -1,139 +1,89 @@ -name: PR Test Build linux +name: Cake Wallet Linux -on: - pull_request: - branches: [main] - workflow_dispatch: - inputs: - branch: - description: "Branch name to build" - required: true - default: "main" +on: [push] +defaults: + run: + shell: bash jobs: PR_test_build: - runs-on: ubuntu-20.04 - env: - STORE_PASS: test@cake_wallet - KEY_PASS: test@cake_wallet - PR_NUMBER: ${{ github.event.number }} + runs-on: linux-amd64 + container: + image: ghcr.io/cake-tech/cake_wallet:main-linux + env: + STORE_PASS: test@cake_wallet + KEY_PASS: test@cake_wallet + MONEROC_CACHE_DIR_ROOT: /opt/generic_cache + BRANCH_NAME: ${{ github.head_ref || github.ref_name }} + DESKTOP_FORCE_MOBILE: Y + volumes: + - /opt/cw_cache_linux/root/.cache:/root/.cache + - /opt/cw_cache_linux/root/.ccache:/root/.ccache + - /opt/cw_cache_linux/root/.pub-cache/:/root/.pub-cache + - /opt/cw_cache_linux/root/go/pkg:/root/go/pkg + - /opt/cw_cache_linux/opt/generic_cache:/opt/generic_cache + strategy: + matrix: + api-level: [29] 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_ENVg - - - uses: actions/checkout@v2 - - uses: actions/setup-java@v1 - with: - java-version: "17.x" - - 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-is-python3 libtool libtinfo5 cmake clang - - - name: Install desktop dependencies - run: | - sudo apt update - sudo apt install -y ninja-build libgtk-3-dev gperf - - 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 scripts && ./gen_android_manifest.sh && cd .. - cd cake_wallet/scripts/android/ - source ./app_env.sh cakewallet - ./app_config.sh - cd ../../.. - cd cake_wallet/scripts/linux/ - source ./app_env.sh cakewallet - ./app_config.sh - cd ../../.. - - - 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: linux_${{ 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/linux/ - 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 - # build mwebd: - cd /opt/android/cake_wallet/scripts/android/ - ./build_mwebd.sh --dont-install - - - name: Generate localization + - name: Fix github actions messing up $HOME... + run: 'echo HOME=/root | sudo tee -a $GITHUB_ENV' + - uses: actions/checkout@v4 + - name: configure git run: | - cd /opt/android/cake_wallet - dart run tool/generate_localization.dart - - - name: Build generated code - run: | - cd /opt/android/cake_wallet - ./model_generator.sh - + git config --global user.email "ci@cakewallet.com" + git config --global user.name "CakeWallet CI" - 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 + if [[ "x${{ secrets.SALT }}" == "x" ]]; + then + echo "const salt = '954f787f12622067f7e548d9450c3832';" > lib/.secrets.g.dart + else + echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart + fi + if [[ "x${{ secrets.KEY_CHAIN_SALT }}" == "x" ]]; + then + echo "const keychainSalt = '2d2beba777dbf7dff7013b7a';" >> lib/.secrets.g.dart + else + echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart + fi + if [[ "x${{ secrets.KEY }}" == "x" ]]; + then + echo "const key = '638e98820ec10a2945e968435c9397a3';" >> lib/.secrets.g.dart + else + echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart + fi + if [[ "x${{ secrets.WALLET_SALT }}" == "x" ]]; + then + echo "const walletSalt = '8f7f1b70';" >> lib/.secrets.g.dart + else + echo "const walletSalt = '${{ secrets.WALLET_SALT }}';" >> lib/.secrets.g.dart + fi + if [[ "x${{ secrets.SHORT_KEY }}" == "x" ]]; + then + echo "const shortKey = '653f270c2c152bc7ec864afe';" >> lib/.secrets.g.dart + else + echo "const shortKey = '${{ secrets.SHORT_KEY }}';" >> lib/.secrets.g.dart + fi + if [[ "x${{ secrets.BACKUP_SALT }}" == "x" ]]; + then + echo "const backupSalt = 'bf630d24ff0b6f60';" >> lib/.secrets.g.dart + else + echo "const backupSalt = '${{ secrets.BACKUP_SALT }}';" >> lib/.secrets.g.dart + fi + if [[ "x${{ secrets.BACKUP_KEY_CHAIN_SALT }}" == "x" ]]; + then + echo "const backupKeychainSalt = 'bf630d24ff0b6f60';" >> lib/.secrets.g.dart + else + echo "const backupKeychainSalt = '${{ secrets.BACKUP_KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart + fi 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 @@ -144,8 +94,6 @@ jobs: 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 polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> lib/.secrets.g.dart - echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> 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 @@ -156,8 +104,11 @@ jobs: 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 nowNodesApiKey = '${{ secrets.EVM_NOWNODES_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart @@ -165,7 +116,6 @@ jobs: echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart - echo "const nowNodesApiKey = '${{ secrets.EVM_NOWNODES_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart echo "const 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 @@ -178,39 +128,172 @@ jobs: echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart echo "const meldTestApiKey = '${{ secrets.MELD_TEST_API_KEY }}';" >> lib/.secrets.g.dart - echo "const meldTestPublicKey = '${{ secrets.MELD_TEST_PUBLIC_KEY}}';" >> lib/.secrets.g.dar + echo "const meldTestPublicKey = '${{ secrets.MELD_TEST_PUBLIC_KEY}}';" >> lib/.secrets.g.dart echo "const letsExchangeBearerToken = '${{ secrets.LETS_EXCHANGE_TOKEN }}';" >> lib/.secrets.g.dart echo "const letsExchangeAffiliateId = '${{ secrets.LETS_EXCHANGE_AFFILIATE_ID }}';" >> lib/.secrets.g.dart echo "const stealthExBearerToken = '${{ secrets.STEALTH_EX_BEARER_TOKEN }}';" >> lib/.secrets.g.dart echo "const stealthExAdditionalFeePercent = '${{ secrets.STEALTH_EX_ADDITIONAL_FEE_PERCENT }}';" >> lib/.secrets.g.dart + # tests + 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: prepare monero_c and cache + run: | + export MONEROC_HASH=$(cat scripts/prepare_moneroc.sh | grep 'git checkout' | xargs | awk '{ print $3 }') + echo MONEROC_HASH=$MONEROC_HASH >> /etc/environment + mkdir -p "$MONEROC_CACHE_DIR_ROOT/moneroc-$MONEROC_HASH/monero_c" + pushd scripts + ln -s "$MONEROC_CACHE_DIR_ROOT/moneroc-$MONEROC_HASH/monero_c" + ./prepare_moneroc.sh + popd + pushd scripts/monero_c + mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/contrib/depends/built" || true + mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/monero/contrib/depends/built" || true + mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/wownero/contrib/depends/built" || true + mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/contrib/depends/sources" || true + mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/monero/contrib/depends/sources" || true + mkdir -p "$MONEROC_CACHE_DIR_ROOT/_cache/wownero/contrib/depends/sources" || true + + rm -rf "$PWD/contrib/depends/built" "$PWD/monero/contrib/depends/built" "$PWD/wownero/contrib/depends/built" + rm -rf "$PWD/contrib/depends/sources" "$PWD/monero/contrib/depends/sources" "$PWD/wownero/contrib/depends/sources" + mkdir -p contrib/depends || true + ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/contrib/depends/built" "$PWD/contrib/depends/built" + ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/monero/contrib/depends/built" "$PWD/monero/contrib/depends/built" + ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/wownero/contrib/depends/built" "$PWD/wownero/contrib/depends/built" + ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/contrib/depends/sources" "$PWD/contrib/depends/sources" + ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/monero/contrib/depends/sources" "$PWD/monero/contrib/depends/sources" + ln -sf "$MONEROC_CACHE_DIR_ROOT/_cache/wownero/contrib/depends/sources" "$PWD/wownero/contrib/depends/sources" + popd - - name: Rename app + - name: Execute Build and Setup Commands run: | - echo -e "id=com.cakewallet.test_${{ env.PR_NUMBER }}\nname=${{ env.BRANCH_NAME }}" > /opt/android/cake_wallet/android/app.properties + pushd scripts/linux + source ./app_env.sh cakewallet + ./app_config.sh + popd - - name: Build + - name: Build monero_c + run: | + pushd scripts/linux/ + source ./app_env.sh cakewallet + ./build_monero_all.sh + popd + + - name: Install Flutter dependencies + run: | + flutter pub get + + - name: Build generated code + run: | + ./model_generator.sh async + + - name: Generate localization + run: | + dart run tool/generate_localization.dart + + - name: Build linux run: | - cd /opt/android/cake_wallet flutter build linux --release - - name: Prepare release zip file + - name: Compress release run: | - cd /opt/android/cake_wallet/build/linux/x64/release - zip -r ${{env.BRANCH_NAME}}.zip bundle + pushd build/linux/x64/release + zip -r cakewallet_linux.zip bundle + popd - - name: Upload Artifact - uses: kittaakos/upload-artifact-as-is@v0 + - name: Upload Artifact to github + uses: actions/upload-artifact@v4 with: - path: /opt/android/cake_wallet/build/linux/x64/release/${{env.BRANCH_NAME}}.zip + path: ${{ github.workspace }}/build/linux/x64/release/cakewallet_linux.zip + name: cakewallet_linux + + - name: Prepare virtual desktop + if: ${{ contains(github.event.head_commit.message, 'run tests') }} + run: | + nohup Xvfb :99 -screen 0 720x1280x16 & + echo DISPLAY=:99 | sudo tee -a $GITHUB_ENV + dbus-daemon --system --fork + nohup NetworkManager & + nohup ffmpeg -framerate 60 -video_size 720x1280 -f x11grab -i :99 -c:v libx264 -c:a aac /opt/screen_grab.mkv & -# Just as an artifact would be enough -# - 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/linux/x64/release/${{env.BRANCH_NAME}}.zip -# channel: ${{ secrets.SLACK_APK_CHANNEL }} -# title: "${{ env.BRANCH_NAME }}_linux.zip" -# filename: ${{ env.BRANCH_NAME }}_linux.zip -# initial_comment: ${{ github.event.head_commit.message }} + # Note for people adding tests: + # - Tests are ran on Linux, with some things being mocked out. + # - Screen recording is being provided for the entire length of the test, you can download it in github articats. + # - Screen recordeding is encrypted, look at step "Stop screen recording, encrypt and upload", and add your key if you want + # Reason for encryption is the fact that we restore the wallet from seed, and we don't want to leak that, while there + # isn't much in those wallets anyway, we still wouldn't like to leak it to anyone who is able to access github. + + - name: Test [confirm_seeds_flow_test] + if: ${{ contains(github.event.head_commit.message, 'run tests') }} + timeout-minutes: 20 + run: | + xmessage -timeout 30 "confirm_seeds_flow_test" & + rm -rf ~/.local/share/com.example.cake_wallet/ ~/Documents/cake_wallet/ ~/cake_wallet + exec timeout --signal=SIGKILL 900 flutter drive --driver=test_driver/integration_test.dart --target=integration_test/test_suites/confirm_seeds_flow_test.dart + - name: Test [create_wallet_flow_test] + if: ${{ contains(github.event.head_commit.message, 'run tests') }} + timeout-minutes: 20 + run: | + xmessage -timeout 30 "create_wallet_flow_test" & + rm -rf ~/.local/share/com.example.cake_wallet/ ~/Documents/cake_wallet/ ~/cake_wallet + exec timeout --signal=SIGKILL 900 flutter drive --driver=test_driver/integration_test.dart --target=integration_test/test_suites/create_wallet_flow_test.dart + - name: Test [exchange_flow_test] + if: ${{ contains(github.event.head_commit.message, 'run tests') }} + timeout-minutes: 20 + run: | + xmessage -timeout 30 "exchange_flow_test" & + rm -rf ~/.local/share/com.example.cake_wallet/ ~/Documents/cake_wallet/ ~/cake_wallet + exec timeout --signal=SIGKILL 900 flutter drive --driver=test_driver/integration_test.dart --target=integration_test/test_suites/exchange_flow_test.dart + - name: Test [restore_wallet_through_seeds_flow_test] + if: ${{ contains(github.event.head_commit.message, 'run tests') }} + timeout-minutes: 20 + run: | + xmessage -timeout 30 "restore_wallet_through_seeds_flow_test" & + rm -rf ~/.local/share/com.example.cake_wallet/ ~/Documents/cake_wallet/ ~/cake_wallet + exec timeout --signal=SIGKILL 900 flutter drive --driver=test_driver/integration_test.dart --target=integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart + - name: Stop screen recording, encrypt and upload + if: always() + run: | + if [[ ! -f "/opt/screen_grab.mkv" ]]; + then + exit 0; + fi + killall ffmpeg + sleep 5 + killall -9 ffmpeg || true + sleep 5 + # Feel free to add your own public key if you wish + gpg --keyserver hkps://keyserver.ubuntu.com --recv-keys 6B3199AD9B3D23B8 # konstantin@cakewallet.com + gpg --keyserver hkps://keyserver.ubuntu.com --recv-keys 35C8DBAFB8D9ACAC # cyjan@mrcyjanek.net + gpg --trust-model always --encrypt --output /opt/screen_grab.mkv.gpg \ + --recipient 6B3199AD9B3D23B8 \ + --recipient 35C8DBAFB8D9ACAC \ + /opt/screen_grab.mkv + rm /opt/screen_grab.mkv + mv /opt/screen_grab.mkv.gpg ./screen_grab.mkv.gpg + - name: Upload Artifact to github + if: always() + continue-on-error: true + uses: actions/upload-artifact@v4 + with: + path: ${{ github.workspace }}/screen_grab.mkv.gpg + name: tests_screen_grab diff --git a/assets/images/cakewallet_android_icon/mipmap-anydpi-v26/ic_launcher.xml b/assets/images/cakewallet_android_icon/mipmap-anydpi-v26/ic_launcher.xml index 00d9241710..c8bd4b26c6 100644 --- a/assets/images/cakewallet_android_icon/mipmap-anydpi-v26/ic_launcher.xml +++ b/assets/images/cakewallet_android_icon/mipmap-anydpi-v26/ic_launcher.xml @@ -2,4 +2,5 @@ + \ No newline at end of file diff --git a/assets/images/cakewallet_android_icon/mipmap-hdpi/ic_launcher_adaptive_mono.png b/assets/images/cakewallet_android_icon/mipmap-hdpi/ic_launcher_adaptive_mono.png new file mode 100644 index 0000000000..27f939b416 Binary files /dev/null and b/assets/images/cakewallet_android_icon/mipmap-hdpi/ic_launcher_adaptive_mono.png differ diff --git a/assets/images/cakewallet_android_icon/mipmap-mdpi/ic_launcher_adaptive_mono.png b/assets/images/cakewallet_android_icon/mipmap-mdpi/ic_launcher_adaptive_mono.png new file mode 100644 index 0000000000..7bdb298c12 Binary files /dev/null and b/assets/images/cakewallet_android_icon/mipmap-mdpi/ic_launcher_adaptive_mono.png differ diff --git a/assets/images/cakewallet_android_icon/mipmap-xhdpi/ic_launcher_adaptive_mono.png b/assets/images/cakewallet_android_icon/mipmap-xhdpi/ic_launcher_adaptive_mono.png new file mode 100644 index 0000000000..79e9df08d8 Binary files /dev/null and b/assets/images/cakewallet_android_icon/mipmap-xhdpi/ic_launcher_adaptive_mono.png differ diff --git a/assets/images/cakewallet_android_icon/mipmap-xxhdpi/ic_launcher_adaptive_mono.png b/assets/images/cakewallet_android_icon/mipmap-xxhdpi/ic_launcher_adaptive_mono.png new file mode 100644 index 0000000000..0bb1c74308 Binary files /dev/null and b/assets/images/cakewallet_android_icon/mipmap-xxhdpi/ic_launcher_adaptive_mono.png differ diff --git a/assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher_adaptive_mono.png b/assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher_adaptive_mono.png new file mode 100644 index 0000000000..205f2ad2a5 Binary files /dev/null and b/assets/images/cakewallet_android_icon/mipmap-xxxhdpi/ic_launcher_adaptive_mono.png differ diff --git a/assets/images/monerocom_android_icon/mipmap-anydpi-v26/ic_launcher.xml b/assets/images/monerocom_android_icon/mipmap-anydpi-v26/ic_launcher.xml index 00d9241710..c8bd4b26c6 100644 --- a/assets/images/monerocom_android_icon/mipmap-anydpi-v26/ic_launcher.xml +++ b/assets/images/monerocom_android_icon/mipmap-anydpi-v26/ic_launcher.xml @@ -2,4 +2,5 @@ + \ No newline at end of file diff --git a/assets/images/monerocom_android_icon/mipmap-hdpi/ic_launcher_adaptive_mono.png b/assets/images/monerocom_android_icon/mipmap-hdpi/ic_launcher_adaptive_mono.png new file mode 100644 index 0000000000..af8126ea9f Binary files /dev/null and b/assets/images/monerocom_android_icon/mipmap-hdpi/ic_launcher_adaptive_mono.png differ diff --git a/assets/images/monerocom_android_icon/mipmap-mdpi/ic_launcher_adaptive_mono.png b/assets/images/monerocom_android_icon/mipmap-mdpi/ic_launcher_adaptive_mono.png new file mode 100644 index 0000000000..0778b841f1 Binary files /dev/null and b/assets/images/monerocom_android_icon/mipmap-mdpi/ic_launcher_adaptive_mono.png differ diff --git a/assets/images/monerocom_android_icon/mipmap-xhdpi/ic_launcher_adaptive_mono.png b/assets/images/monerocom_android_icon/mipmap-xhdpi/ic_launcher_adaptive_mono.png new file mode 100644 index 0000000000..318914c59e Binary files /dev/null and b/assets/images/monerocom_android_icon/mipmap-xhdpi/ic_launcher_adaptive_mono.png differ diff --git a/assets/images/monerocom_android_icon/mipmap-xxhdpi/ic_launcher_adaptive_mono.png b/assets/images/monerocom_android_icon/mipmap-xxhdpi/ic_launcher_adaptive_mono.png new file mode 100644 index 0000000000..b1165abe85 Binary files /dev/null and b/assets/images/monerocom_android_icon/mipmap-xxhdpi/ic_launcher_adaptive_mono.png differ diff --git a/assets/images/monerocom_android_icon/mipmap-xxxhdpi/ic_launcher_adaptive_mono.png b/assets/images/monerocom_android_icon/mipmap-xxxhdpi/ic_launcher_adaptive_mono.png new file mode 100644 index 0000000000..ea11a01d31 Binary files /dev/null and b/assets/images/monerocom_android_icon/mipmap-xxxhdpi/ic_launcher_adaptive_mono.png differ diff --git a/build-guide-linux.md b/build-guide-linux.md index 99c2ed0c84..df5f0f6017 100644 --- a/build-guide-linux.md +++ b/build-guide-linux.md @@ -115,7 +115,7 @@ Install Flutter package dependencies with this command: > `$ ./cakewallet.sh` > and back to project root directory: > `$ cd ../..` -> and fetch dependecies again +> and fetch dependencies again > `$ flutter pub get` Your CakeWallet binary will be built with some specific keys for iterate with 3rd party services. You may generate these secret keys placeholders with the following command: diff --git a/build-guide-win.md b/build-guide-win.md index 6ace961af5..8cfd02c4ca 100644 --- a/build-guide-win.md +++ b/build-guide-win.md @@ -16,14 +16,14 @@ These steps will help you configure and execute a build of CakeWallet from its s ### 1. Installing Package Dependencies For build CakeWallet windows application from sources you will be needed to have: -> [Install Flutter]Follow installation guide (https://docs.flutter.dev/get-started/install/windows) and install do not miss to dev tools (install https://docs.flutter.dev/get-started/install/windows/desktop#development-tools) which are required for windows desktop development (need to install Git for Windows and Visual Studio 2022). Then install `Desktop development with C++` packages via GUI Visual Studio 2022, or Visual Studio Build Tools 2022 including: `C++ Build Tools core features`, `C++ 2022 Redistributable Update`, `C++ core desktop features`, `MVC v143 - VS 2022 C++ x64/x86 build tools`, `C++ CMake tools for Windwos`, `Testing tools core features - Build Tools`, `C++ AddressSanitizer`. +> [Install Flutter]Follow installation guide (https://docs.flutter.dev/get-started/install/windows) and install do not miss to dev tools (install https://docs.flutter.dev/get-started/install/windows/desktop#development-tools) which are required for windows desktop development (need to install Git for Windows and Visual Studio 2022). Then install `Desktop development with C++` packages via GUI Visual Studio 2022, or Visual Studio Build Tools 2022 including: `C++ Build Tools core features`, `C++ 2022 Redistributable Update`, `C++ core desktop features`, `MVC v143 - VS 2022 C++ x64/x86 build tools`, `C++ CMake tools for Windows`, `Testing tools core features - Build Tools`, `C++ AddressSanitizer`. > [Install WSL] for building monero dependencies need to install Windows WSL (https://learn.microsoft.com/en-us/windows/wsl/install) and required packages for WSL (Ubuntu): `$ sudo apt update ` `$ sudo apt build-essential cmake gcc-mingw-w64 g++-mingw-w64 autoconf libtool pkg-config` ### 2. Pull CakeWallet source code -You can downlaod CakeWallet source code from our [GitHub repository](github.com/cake-tech/cake_wallet) via git by following next command: +You can download CakeWallet source code from our [GitHub repository](github.com/cake-tech/cake_wallet) via git by following next command: `$ git clone https://github.com/cake-tech/cake_wallet.git --branch MrCyjaneK-cyjan-monerodart` OR you can download it as [Zip archive](https://github.com/cake-tech/cake_wallet/archive/refs/heads/MrCyjaneK-cyjan-monerodart.zip) diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index d383162287..f57b0cf27d 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -452,7 +452,7 @@ abstract class ElectrumWalletBase @action @override - Future startSync() async { + Future startSync({bool isBackgroundSync = false}) async { try { if (syncStatus is SyncronizingSyncStatus) { return; @@ -490,7 +490,7 @@ abstract class ElectrumWalletBase @action @override - Future stopSync() async { + Future stopSync({bool isBackgroundSync = false}) async { syncStatus = NotConnectedSyncStatus(); try { await _receiveStream?.cancel(); diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart index 44d1fa47a0..643f1ca230 100644 --- a/cw_bitcoin/lib/litecoin_wallet.dart +++ b/cw_bitcoin/lib/litecoin_wallet.dart @@ -302,7 +302,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { @action @override - Future startSync() async { + Future startSync({bool isBackgroundSync = false}) async { printV("startSync() called!"); printV("STARTING SYNC - MWEB ENABLED: $mwebEnabled"); if (!mwebEnabled) { @@ -437,7 +437,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { @action @override - Future stopSync() async { + Future stopSync({bool isBackgroundSync = false}) async { printV("stopSync() called!"); _syncTimer?.cancel(); _utxoStream?.cancel(); diff --git a/cw_core/lib/wallet_base.dart b/cw_core/lib/wallet_base.dart index 16c794a25f..a895b35375 100644 --- a/cw_core/lib/wallet_base.dart +++ b/cw_core/lib/wallet_base.dart @@ -60,14 +60,16 @@ abstract class WalletBase walletInfo.isHardwareWallet; + Future init(); + Future connectToNode({required Node node}); // there is a default definition here because only coins with a pow node (nano based) need to override this Future connectToPowNode({required Node node}) async {} - Future startSync(); + Future startSync({bool isBackgroundSync = false}); - Future stopSync() async {} + Future stopSync({bool isBackgroundSync = false}) async {} Future createTransaction(Object credentials); @@ -100,4 +102,7 @@ abstract class WalletBase verifyMessage(String message, String signature, {String? address = null}); bool isTestnet = false; + + // TODO: use proxy layer + Future reopenWallet() async {} } diff --git a/cw_evm/lib/evm_chain_wallet.dart b/cw_evm/lib/evm_chain_wallet.dart index 9ccb05e7f3..b2c9640382 100644 --- a/cw_evm/lib/evm_chain_wallet.dart +++ b/cw_evm/lib/evm_chain_wallet.dart @@ -299,7 +299,7 @@ abstract class EVMChainWalletBase @action @override - Future startSync() async { + Future startSync({bool isBackgroundSync = false}) async { try { syncStatus = AttemptingSyncStatus(); await _updateBalance(); diff --git a/cw_monero/lib/api/wallet.dart b/cw_monero/lib/api/wallet.dart index 537c9802e7..d6063069ce 100644 --- a/cw_monero/lib/api/wallet.dart +++ b/cw_monero/lib/api/wallet.dart @@ -9,6 +9,8 @@ import 'package:flutter/foundation.dart'; import 'package:monero/monero.dart' as monero; import 'package:mutex/mutex.dart'; +bool debugMonero = false; + int getSyncingHeight() { // final height = monero.MONERO_cw_WalletListener_height(getWlptr()); final h2 = monero.Wallet_blockChainHeight(wptr!); @@ -32,8 +34,7 @@ String getFilename() => monero.Wallet_filename(wptr!); String getSeed() { // monero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed); - final cakepolyseed = - monero.Wallet_getCacheAttribute(wptr!, key: "cakewallet.seed"); + final cakepolyseed = monero.Wallet_getCacheAttribute(wptr!, key: "cakewallet.seed"); if (cakepolyseed != "") { return cakepolyseed; } @@ -48,11 +49,21 @@ String getSeed() { String getSeedLegacy(String? language) { var legacy = monero.Wallet_seed(wptr!, seedOffset: ''); switch (language) { - case "Chinese (Traditional)": language = "Chinese (simplified)"; break; - case "Chinese (Simplified)": language = "Chinese (simplified)"; break; - case "Korean": language = "English"; break; - case "Czech": language = "English"; break; - case "Japanese": language = "English"; break; + case "Chinese (Traditional)": + language = "Chinese (simplified)"; + break; + case "Chinese (Simplified)": + language = "Chinese (simplified)"; + break; + case "Korean": + language = "English"; + break; + case "Czech": + language = "English"; + break; + case "Japanese": + language = "English"; + break; } if (monero.Wallet_status(wptr!) != 0) { monero.Wallet_setSeedLanguage(wptr!, language: language ?? "English"); @@ -71,15 +82,22 @@ String getSeedLegacy(String? language) { Map>> addressCache = {}; String getAddress({int accountIndex = 0, int addressIndex = 0}) { - // printV("getaddress: ${accountIndex}/${addressIndex}: ${monero.Wallet_numSubaddresses(wptr!, accountIndex: accountIndex)}: ${monero.Wallet_address(wptr!, accountIndex: accountIndex, addressIndex: addressIndex)}"); - while (monero.Wallet_numSubaddresses(wptr!, accountIndex: accountIndex)-1 < addressIndex) { + + int count = 0; + + while (monero.Wallet_numSubaddresses(wptr!, accountIndex: accountIndex) - 1 < addressIndex) { printV("adding subaddress"); monero.Wallet_addSubaddress(wptr!, accountIndex: accountIndex); + if (count > 50) { + throw Exception("Failed to add subaddress"); + } + count++; } + addressCache[wptr!.address] ??= {}; addressCache[wptr!.address]![accountIndex] ??= {}; - addressCache[wptr!.address]![accountIndex]![addressIndex] ??= monero.Wallet_address(wptr!, - accountIndex: accountIndex, addressIndex: addressIndex); + addressCache[wptr!.address]![accountIndex]![addressIndex] ??= + monero.Wallet_address(wptr!, accountIndex: accountIndex, addressIndex: addressIndex); return addressCache[wptr!.address]![accountIndex]![addressIndex]!; } @@ -132,9 +150,10 @@ Future setupNodeSync( } } - if (kDebugMode) { + if (kDebugMode && debugMonero) { monero.Wallet_init3( - wptr!, argv0: '', + wptr!, + argv0: '', defaultLogBaseName: 'moneroc', console: true, logPath: '', @@ -149,17 +168,38 @@ void startRefreshSync() { monero.Wallet_startRefresh(wptr!); } +void setupBackgroundSync( + {required int backgroundSyncType, + required String walletPassword, + required String backgroundCachePassword}) { + monero.Wallet_setupBackgroundSync(wptr!, + backgroundSyncType: backgroundSyncType, + walletPassword: walletPassword, + backgroundCachePassword: backgroundCachePassword); +} + +bool isBackgroundSyncing() => monero.Wallet_isBackgroundSyncing(wptr!); + +void startBackgroundSync() { + monero.Wallet_startBackgroundSync(wptr!); +} + +void stopBackgroundSync(String walletPassword) { + monero.Wallet_stopBackgroundSync(wptr!, walletPassword); +} + +void stopSync() { + monero.Wallet_init(wptr!, daemonAddress: ""); +} void setRefreshFromBlockHeight({required int height}) => - monero.Wallet_setRefreshFromBlockHeight(wptr!, - refresh_from_block_height: height); + monero.Wallet_setRefreshFromBlockHeight(wptr!, refresh_from_block_height: height); void setRecoveringFromSeed({required bool isRecovery}) => monero.Wallet_setRecoveringFromSeed(wptr!, recoveringFromSeed: isRecovery); final storeMutex = Mutex(); - int lastStorePointer = 0; int lastStoreHeight = 0; void storeSync({bool force = false}) async { @@ -169,7 +209,7 @@ void storeSync({bool force = false}) async { }); if (lastStorePointer == wptr!.address && lastStoreHeight + 5000 > monero.Wallet_blockChainHeight(wptr!) && - !synchronized && + !synchronized && !force) { return; } @@ -195,6 +235,10 @@ void closeCurrentWallet() { monero.Wallet_stop(wptr!); } +void stopWallet() { + monero.Wallet_stop(wptr!); +} + String getSecretViewKey() => monero.Wallet_secretViewKey(wptr!); String getPublicViewKey() => monero.Wallet_publicViewKey(wptr!); @@ -229,8 +273,7 @@ class SyncListener { _cachedBlockchainHeight = 0; _lastKnownBlockHeight = 0; _initialSyncHeight = 0; - _updateSyncInfoTimer ??= - Timer.periodic(Duration(milliseconds: 1200), (_) async { + _updateSyncInfoTimer ??= Timer.periodic(Duration(milliseconds: 1200), (_) async { if (isNewTransactionExist()) { onNewTransaction(); } @@ -269,8 +312,8 @@ class SyncListener { void stop() => _updateSyncInfoTimer?.cancel(); } -SyncListener setListeners(void Function(int, int, double) onNewBlock, - void Function() onNewTransaction) { +SyncListener setListeners( + void Function(int, int, double) onNewBlock, void Function() onNewTransaction) { final listener = SyncListener(onNewBlock, onNewTransaction); // setListenerNative(); return listener; @@ -332,8 +375,7 @@ String getSubaddressLabel(int accountIndex, int addressIndex) { accountIndex: accountIndex, addressIndex: addressIndex); } -Future setTrustedDaemon(bool trusted) async => - monero.Wallet_setTrustedDaemon(wptr!, arg: trusted); +Future setTrustedDaemon(bool trusted) async => monero.Wallet_setTrustedDaemon(wptr!, arg: trusted); Future trustedDaemon() async => monero.Wallet_trustedDaemon(wptr!); @@ -342,5 +384,6 @@ String signMessage(String message, {String address = ""}) { } bool verifyMessage(String message, String address, String signature) { - return monero.Wallet_verifySignedMessage(wptr!, message: message, address: address, signature: signature); + return monero.Wallet_verifySignedMessage(wptr!, + message: message, address: address, signature: signature); } diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index fd596a3029..e46e18b9f0 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -39,6 +39,7 @@ import 'package:hive/hive.dart'; import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; import 'package:mobx/mobx.dart'; import 'package:monero/monero.dart' as monero; +import 'package:cw_monero/api/transaction_history.dart' as transaction_history; part 'monero_wallet.g.dart'; @@ -137,6 +138,7 @@ abstract class MoneroWalletBase Timer? _autoSaveTimer; List unspentCoins; String _password; + bool isBackgroundSyncing = false; Future init() async { await walletAddresses.init(); @@ -157,10 +159,12 @@ abstract class MoneroWalletBase } } - _autoSaveTimer = Timer.periodic( - Duration(seconds: _autoSaveInterval), (_) async => await save()); + _autoSaveTimer?.cancel(); + _autoSaveTimer = + Timer.periodic(Duration(seconds: _autoSaveInterval), (_) async => await save()); + // update transaction details after restore - walletAddresses.subaddressList.update(accountIndex: walletAddresses.account?.id??0); + walletAddresses.subaddressList.update(accountIndex: walletAddresses.account?.id ?? 0); } @override @@ -172,6 +176,7 @@ abstract class MoneroWalletBase _onAccountChangeReaction?.reaction.dispose(); _onTxHistoryChangeReaction?.reaction.dispose(); _autoSaveTimer?.cancel(); + monero_wallet.stopWallet(); } @override @@ -196,27 +201,21 @@ abstract class MoneroWalletBase } @override - Future startSync() async { - try { - _assertInitialHeight(); - } catch (_) { - // our restore height wasn't correct, so lets see if using the backup works: - try { - await resetCache(name); // Resetting the cache removes the TX Keys and Polyseed - _assertInitialHeight(); - } catch (e) { - // we still couldn't get a valid height from the backup?!: - // try to use the date instead: - try { - _setHeightFromDate(); - } catch (_) { - // we still couldn't get a valid sync height :/ - } - } - } - + Future startSync({bool isBackgroundSync = false}) async { try { syncStatus = AttemptingSyncStatus(); + if (isBackgroundSync) { + monero_wallet.setupBackgroundSync( + backgroundSyncType: 2, + walletPassword: password, + backgroundCachePassword: "testing-cache-password", + ); + monero_wallet.startBackgroundSync(); + isBackgroundSyncing = true; + } else { + monero_wallet.stopBackgroundSync(password); + isBackgroundSyncing = false; + } monero_wallet.startRefresh(); _setListeners(); _listener?.start(); @@ -258,19 +257,45 @@ abstract class MoneroWalletBase } bool needExportOutputs(int amount) { + if (int.tryParse(monero.Wallet_secretSpendKey(wptr!)) != 0) { + return false; + } // viewOnlyBalance - balance that we can spend // TODO(mrcyjanek): remove hasUnknownKeyImages when we cleanup coin control - return (monero.Wallet_viewOnlyBalance(wptr!, - accountIndex: walletAddresses.account!.id) < amount) || - monero.Wallet_hasUnknownKeyImages(wptr!); + return (monero.Wallet_viewOnlyBalance(wptr!, accountIndex: walletAddresses.account!.id) < + amount) || + monero.Wallet_hasUnknownKeyImages(wptr!); } @override - Future stopSync() async { + Future stopSync({bool isBackgroundSync = false}) async { syncStatus = NotConnectedSyncStatus(); _listener?.stop(); - // TODO: find a better way to stop syncing than setting an invalid address: - monero_wallet.setupNode(address: ""); + if (isBackgroundSync) { + isBackgroundSyncing = false; + monero_wallet.stopWallet(); + monero_wallet.stopBackgroundSync(password); + return; + } + monero_wallet.stopSync(); + _autoSaveTimer?.cancel(); + monero_wallet.closeCurrentWallet(); + } + + @override + Future reopenWallet() async { + printV("closing wallet"); + final currentWalletDirPath = await pathForWalletDir(name: name, type: type); + final wmaddr = wmPtr.address; + final waddr = openedWalletsByPath["$currentWalletDirPath/$name"]!.address; + await Isolate.run(() { + monero.WalletManager_closeWallet( + Pointer.fromAddress(wmaddr), Pointer.fromAddress(waddr), true); + }); + wptr = monero.WalletManager_openWallet(wmPtr, path: currentWalletDirPath, password: password); + openedWalletsByPath["$currentWalletDirPath/$name"] = wptr!; + transaction_history.txhistory = null; + } @override @@ -279,8 +304,8 @@ abstract class MoneroWalletBase final inputs = []; final outputs = _credentials.outputs; final hasMultiDestination = outputs.length > 1; - final unlockedBalance = monero_wallet.getUnlockedBalance( - accountIndex: walletAddresses.account!.id); + final unlockedBalance = + monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id); PendingTransactionDescription pendingTransactionDescription; @@ -310,8 +335,7 @@ abstract class MoneroWalletBase throw MoneroTransactionCreationException('You do not have enough XMR to send this amount.'); } - if (inputs.isEmpty) MoneroTransactionCreationException( - 'No inputs selected'); + if (inputs.isEmpty) MoneroTransactionCreationException('No inputs selected'); final moneroOutputs = outputs.map((output) { final outputAddress = output.isParsedAddress ? output.extractedAddress : output.address; @@ -327,10 +351,8 @@ abstract class MoneroWalletBase preferredInputs: inputs); } else { final output = outputs.first; - final address = - output.isParsedAddress ? output.extractedAddress : output.address; - final amount = - output.sendAll ? null : output.cryptoAmount!.replaceAll(',', '.'); + final address = output.isParsedAddress ? output.extractedAddress : output.address; + final amount = output.sendAll ? null : output.cryptoAmount!.replaceAll(',', '.'); // if ((formattedAmount != null && unlockedBalance < formattedAmount) || // (formattedAmount == null && unlockedBalance <= 0)) { @@ -340,15 +362,13 @@ abstract class MoneroWalletBase // 'You do not have enough unlocked balance. Unlocked: $formattedBalance. Transaction amount: ${output.cryptoAmount}.'); // } - if (inputs.isEmpty) MoneroTransactionCreationException( - 'No inputs selected'); - pendingTransactionDescription = - await transaction_history.createTransaction( - address: address!, - amount: amount, - priorityRaw: _credentials.priority.serialize(), - accountIndex: walletAddresses.account!.id, - preferredInputs: inputs); + if (inputs.isEmpty) MoneroTransactionCreationException('No inputs selected'); + pendingTransactionDescription = await transaction_history.createTransaction( + address: address!, + amount: amount, + priorityRaw: _credentials.priority.serialize(), + accountIndex: walletAddresses.account!.id, + preferredInputs: inputs); } // final status = monero.PendingTransaction_status(pendingTransactionDescription); @@ -382,7 +402,7 @@ abstract class MoneroWalletBase Future save() async { await walletAddresses.updateUsedSubaddress(); - if (isEnabledAutoGenerateSubaddress) { + if (isEnabledAutoGenerateSubaddress && !isBackgroundSyncing) { walletAddresses.updateUnusedSubaddress( accountIndex: walletAddresses.account?.id ?? 0, defaultLabel: walletAddresses.account?.label ?? ''); @@ -500,7 +520,8 @@ abstract class MoneroWalletBase for (var i = 0; i < coinCount; i++) { final coin = getCoin(i); final coinSpent = monero.CoinsInfo_spent(coin); - if (coinSpent == false && monero.CoinsInfo_subaddrAccount(coin) == walletAddresses.account!.id) { + if (coinSpent == false && + monero.CoinsInfo_subaddrAccount(coin) == walletAddresses.account!.id) { final unspent = MoneroUnspent( monero.CoinsInfo_address(coin), monero.CoinsInfo_hash(coin), @@ -602,8 +623,7 @@ abstract class MoneroWalletBase Future> fetchTransactions() async { transaction_history.refreshTransactions(); return (await _getAllTransactionsOfAccount(walletAddresses.account?.id)) - .fold>( - {}, + .fold>({}, (Map acc, MoneroTransactionInfo tx) { acc[tx.id] = tx; return acc; @@ -632,15 +652,12 @@ abstract class MoneroWalletBase monero_wallet.getSubaddressLabel(accountIndex, addressIndex); Future> _getAllTransactionsOfAccount(int? accountIndex) async => - (await transaction_history - .getAllTransactions()) + (await transaction_history.getAllTransactions()) .map( (row) => MoneroTransactionInfo( row.hash, row.blockheight, - row.isSpend - ? TransactionDirection.outgoing - : TransactionDirection.incoming, + row.isSpend ? TransactionDirection.outgoing : TransactionDirection.incoming, row.timeStamp, row.isPending, row.amount, @@ -710,8 +727,7 @@ abstract class MoneroWalletBase void _askForUpdateBalance() { final unlockedBalance = _getUnlockedBalance(); - final fullBalance = monero_wallet.getFullBalance( - accountIndex: walletAddresses.account!.id); + final fullBalance = monero_wallet.getFullBalance(accountIndex: walletAddresses.account!.id); final frozenBalance = _getFrozenBalance(); if (balance[currency]!.fullBalance != fullBalance || balance[currency]!.unlockedBalance != unlockedBalance || @@ -723,8 +739,8 @@ abstract class MoneroWalletBase Future _askForUpdateTransactionHistory() async => await updateTransactions(); - int _getUnlockedBalance() => monero_wallet.getUnlockedBalance( - accountIndex: walletAddresses.account!.id); + int _getUnlockedBalance() => + monero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id); int _getFrozenBalance() { var frozenBalance = 0; @@ -742,13 +758,17 @@ abstract class MoneroWalletBase if (walletInfo.isRecovery) { await _askForUpdateTransactionHistory(); _askForUpdateBalance(); - walletAddresses.accountList.update(); + if (!isBackgroundSyncing) { + walletAddresses.accountList.update(); + } } if (blocksLeft < 100) { await _askForUpdateTransactionHistory(); _askForUpdateBalance(); - walletAddresses.accountList.update(); + if (!isBackgroundSyncing) { + walletAddresses.accountList.update(); + } syncStatus = SyncedSyncStatus(); if (!_hasSyncAfterStartup) { @@ -805,8 +825,7 @@ abstract class MoneroWalletBase } void setLedgerConnection(LedgerConnection connection) { - final dummyWPtr = wptr ?? - monero.WalletManager_openWallet(wmPtr, path: '', password: ''); + final dummyWPtr = wptr ?? monero.WalletManager_openWallet(wmPtr, path: '', password: ''); enableLedgerExchange(dummyWPtr, connection); } } diff --git a/cw_monero/lib/monero_wallet_addresses.dart b/cw_monero/lib/monero_wallet_addresses.dart index 14b8a2b9b3..2433c1ed40 100644 --- a/cw_monero/lib/monero_wallet_addresses.dart +++ b/cw_monero/lib/monero_wallet_addresses.dart @@ -96,8 +96,9 @@ abstract class MoneroWalletAddressesBase extends WalletAddresses with Store { }); await saveAddressesInBox(); - } catch (e) { + } catch (e, s) { printV(e.toString()); + printV(s.toString()); } } diff --git a/cw_nano/lib/nano_wallet.dart b/cw_nano/lib/nano_wallet.dart index b48335857b..1461bf6287 100644 --- a/cw_nano/lib/nano_wallet.dart +++ b/cw_nano/lib/nano_wallet.dart @@ -343,7 +343,7 @@ abstract class NanoWalletBase @action @override - Future startSync() async { + Future startSync({bool isBackgroundSync = false}) async { try { syncStatus = AttemptingSyncStatus(); diff --git a/cw_solana/lib/solana_wallet.dart b/cw_solana/lib/solana_wallet.dart index 15c0659185..da455649de 100644 --- a/cw_solana/lib/solana_wallet.dart +++ b/cw_solana/lib/solana_wallet.dart @@ -366,7 +366,7 @@ abstract class SolanaWalletBase @action @override - Future startSync() async { + Future startSync({bool isBackgroundSync = false}) async { try { syncStatus = AttemptingSyncStatus(); diff --git a/cw_tron/lib/tron_wallet.dart b/cw_tron/lib/tron_wallet.dart index cfa80f0d34..92f1692799 100644 --- a/cw_tron/lib/tron_wallet.dart +++ b/cw_tron/lib/tron_wallet.dart @@ -273,7 +273,7 @@ abstract class TronWalletBase @action @override - Future startSync() async { + Future startSync({bool isBackgroundSync = false}) async { try { syncStatus = AttemptingSyncStatus(); await _updateBalance(); diff --git a/cw_wownero/lib/api/wallet.dart b/cw_wownero/lib/api/wallet.dart index 21dcd04c7f..fb031c4897 100644 --- a/cw_wownero/lib/api/wallet.dart +++ b/cw_wownero/lib/api/wallet.dart @@ -33,8 +33,7 @@ String getFilename() => wownero.Wallet_filename(wptr!); String getSeed() { // wownero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed); - final cakepolyseed = - wownero.Wallet_getCacheAttribute(wptr!, key: "cakewallet.seed"); + final cakepolyseed = wownero.Wallet_getCacheAttribute(wptr!, key: "cakewallet.seed"); if (cakepolyseed != "") { return cakepolyseed; } @@ -49,11 +48,21 @@ String getSeed() { String getSeedLegacy(String? language) { var legacy = wownero.Wallet_seed(wptr!, seedOffset: ''); switch (language) { - case "Chinese (Traditional)": language = "Chinese (simplified)"; break; - case "Chinese (Simplified)": language = "Chinese (simplified)"; break; - case "Korean": language = "English"; break; - case "Czech": language = "English"; break; - case "Japanese": language = "English"; break; + case "Chinese (Traditional)": + language = "Chinese (simplified)"; + break; + case "Chinese (Simplified)": + language = "Chinese (simplified)"; + break; + case "Korean": + language = "English"; + break; + case "Czech": + language = "English"; + break; + case "Japanese": + language = "English"; + break; } if (wownero.Wallet_status(wptr!) != 0) { wownero.Wallet_setSeedLanguage(wptr!, language: language ?? "English"); @@ -68,17 +77,18 @@ String getSeedLegacy(String? language) { } return legacy; } + Map>> addressCache = {}; String getAddress({int accountIndex = 0, int addressIndex = 1}) { - while (wownero.Wallet_numSubaddresses(wptr!, accountIndex: accountIndex)-1 < addressIndex) { + while (wownero.Wallet_numSubaddresses(wptr!, accountIndex: accountIndex) - 1 < addressIndex) { printV("adding subaddress"); wownero.Wallet_addSubaddress(wptr!, accountIndex: accountIndex); } addressCache[wptr!.address] ??= {}; addressCache[wptr!.address]![accountIndex] ??= {}; - addressCache[wptr!.address]![accountIndex]![addressIndex] ??= wownero.Wallet_address(wptr!, - accountIndex: accountIndex, addressIndex: addressIndex); + addressCache[wptr!.address]![accountIndex]![addressIndex] ??= + wownero.Wallet_address(wptr!, accountIndex: accountIndex, addressIndex: addressIndex); return addressCache[wptr!.address]![accountIndex]![addressIndex]!; } @@ -138,13 +148,30 @@ void startRefreshSync() { wownero.Wallet_startRefresh(wptr!); } +void setupBackgroundSync( + {required int backgroundSyncType, + required String walletPassword, + required String backgroundCachePassword}) { + wownero.Wallet_setupBackgroundSync(wptr!, + backgroundSyncType: backgroundSyncType, + walletPassword: walletPassword, + backgroundCachePassword: backgroundCachePassword); +} + +void startBackgroundSync() { + wownero.Wallet_startBackgroundSync(wptr!); +} + +void stopBackgroundSync(String password) { + wownero.Wallet_stopBackgroundSync(wptr!, password); +} + Future connectToNode() async { return true; } void setRefreshFromBlockHeight({required int height}) => - wownero.Wallet_setRefreshFromBlockHeight(wptr!, - refresh_from_block_height: height); + wownero.Wallet_setRefreshFromBlockHeight(wptr!, refresh_from_block_height: height); void setRecoveringFromSeed({required bool isRecovery}) => wownero.Wallet_setRecoveringFromSeed(wptr!, recoveringFromSeed: isRecovery); @@ -219,8 +246,7 @@ class SyncListener { _cachedBlockchainHeight = 0; _lastKnownBlockHeight = 0; _initialSyncHeight = 0; - _updateSyncInfoTimer ??= - Timer.periodic(Duration(milliseconds: 1200), (_) async { + _updateSyncInfoTimer ??= Timer.periodic(Duration(milliseconds: 1200), (_) async { if (isNewTransactionExist()) { onNewTransaction(); } @@ -259,8 +285,8 @@ class SyncListener { void stop() => _updateSyncInfoTimer?.cancel(); } -SyncListener setListeners(void Function(int, int, double) onNewBlock, - void Function() onNewTransaction) { +SyncListener setListeners( + void Function(int, int, double) onNewBlock, void Function() onNewTransaction) { final listener = SyncListener(onNewBlock, onNewTransaction); // setListenerNative(); return listener; @@ -322,8 +348,7 @@ String getSubaddressLabel(int accountIndex, int addressIndex) { accountIndex: accountIndex, addressIndex: addressIndex); } -Future setTrustedDaemon(bool trusted) async => - wownero.Wallet_setTrustedDaemon(wptr!, arg: trusted); +Future setTrustedDaemon(bool trusted) async => wownero.Wallet_setTrustedDaemon(wptr!, arg: trusted); Future trustedDaemon() async => wownero.Wallet_trustedDaemon(wptr!); @@ -332,5 +357,6 @@ String signMessage(String message, {String address = ""}) { } bool verifyMessage(String message, String address, String signature) { - return wownero.Wallet_verifySignedMessage(wptr!, message: message, address: address, signature: signature); -} \ No newline at end of file + return wownero.Wallet_verifySignedMessage(wptr!, + message: message, address: address, signature: signature); +} diff --git a/cw_wownero/lib/wownero_wallet.dart b/cw_wownero/lib/wownero_wallet.dart index c4c79af11a..963edf8fa9 100644 --- a/cw_wownero/lib/wownero_wallet.dart +++ b/cw_wownero/lib/wownero_wallet.dart @@ -51,7 +51,9 @@ abstract class WowneroWalletBase extends WalletBase with Store { WowneroWalletBase( - {required WalletInfo walletInfo, required Box unspentCoinsInfo, required String password}) + {required WalletInfo walletInfo, + required Box unspentCoinsInfo, + required String password}) : balance = ObservableMap.of({ CryptoCurrency.wow: WowneroBalance( fullBalance: wownero_wallet.getFullBalance(accountIndex: 0), @@ -191,7 +193,19 @@ abstract class WowneroWalletBase } @override - Future startSync() async { + Future startSync({bool isBackgroundSync = false}) async { + if (isBackgroundSync) { + try { + syncStatus = AttemptingSyncStatus(); + wownero_wallet.startBackgroundSync(); + return; + } catch (e) { + syncStatus = FailedSyncStatus(); + printV(e); + rethrow; + } + } + try { _assertInitialHeight(); } catch (_) { diff --git a/integration_test/components/common_test_cases.dart b/integration_test/components/common_test_cases.dart index 83bbb0449c..cc1e6d6d71 100644 --- a/integration_test/components/common_test_cases.dart +++ b/integration_test/components/common_test_cases.dart @@ -32,6 +32,11 @@ class CommonTestCases { expect(textWidget, hasWidget ? findsOneWidget : findsNothing); } + void hasTextAtLestOnce(String text, {bool hasWidget = true}) { + final textWidget = find.text(text); + expect(textWidget, hasWidget ? findsAny : findsNothing); + } + void hasType() { final typeWidget = find.byType(T); expect(typeWidget, findsOneWidget); diff --git a/integration_test/components/common_test_flows.dart b/integration_test/components/common_test_flows.dart index 8350b58590..c9e6053393 100644 --- a/integration_test/components/common_test_flows.dart +++ b/integration_test/components/common_test_flows.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:cake_wallet/entities/seed_type.dart'; import 'package:cake_wallet/reactions/bip39_wallet_utils.dart'; import 'package:cake_wallet/wallet_types.g.dart'; @@ -85,6 +87,7 @@ class CommonTestFlows { await _confirmPreSeedInfo(); await _confirmWalletDetails(); + await _commonTestCases.defaultSleepTime(); } //* ========== Handles flow from welcome to restoring wallet from seeds =============== @@ -168,8 +171,8 @@ class CommonTestFlows { await _walletListPageRobot.navigateToRestoreWalletOptionsPage(); await _commonTestCases.defaultSleepTime(); - await _restoreOptionsPageRobot.navigateToRestoreFromSeedsOrKeysPage(); - await _commonTestCases.defaultSleepTime(); + if (!Platform.isLinux) await _restoreOptionsPageRobot.navigateToRestoreFromSeedsOrKeysPage(); + if (!Platform.isLinux) await _commonTestCases.defaultSleepTime(); await _selectWalletTypeForWallet(walletType); await _commonTestCases.defaultSleepTime(); @@ -180,6 +183,7 @@ class CommonTestFlows { //* ========== Handles setting up pin code for wallet on first install =============== Future setupPinCodeForWallet(List pin) async { + if (Platform.isLinux) return; // ----------- SetupPinCode Page ------------- // Confirm initial defaults - Widgets to be displayed etc await _setupPinCodeRobot.isSetupPinCodePage(); @@ -212,7 +216,7 @@ class CommonTestFlows { await _welcomePageRobot.navigateToRestoreWalletPage(); - await _restoreOptionsPageRobot.navigateToRestoreFromSeedsOrKeysPage(); + if (!Platform.isLinux) await _restoreOptionsPageRobot.navigateToRestoreFromSeedsOrKeysPage(); await _selectWalletTypeForWallet(walletTypeToRestore); } @@ -234,6 +238,12 @@ class CommonTestFlows { await _newWalletPageRobot.generateWalletName(); + if (Platform.isLinux) { + // manual pin input + await _restoreFromSeedOrKeysPageRobot.enterPasswordForWalletRestore(CommonTestConstants.pin.join("")); + await _restoreFromSeedOrKeysPageRobot.enterPasswordRepeatForWalletRestore(CommonTestConstants.pin.join("")); + } + await _newWalletPageRobot.onNextButtonPressed(); } @@ -252,11 +262,15 @@ class CommonTestFlows { _walletSeedPageRobot.confirmWalletSeedReminderDisplays(); - await _walletSeedPageRobot.onCopySeedsButtonPressed(); - - await _walletSeedPageRobot.onNextButtonPressed(); + // await _walletSeedPageRobot.onCopySeedsButtonPressed(); - await _walletSeedPageRobot.onConfirmButtonOnSeedAlertDialogPressed(); + await _walletSeedPageRobot.onSeedPageVerifyButtonPressed(); + // Turns out the popup about "Copied to clipboard" prevents + //the button from being pressed on the first try, by just + //tapping it again we fix it. + // await _walletSeedPageRobot.onSeedPageVerifyButtonPressed(); + + await _walletSeedPageRobot.onOpenWalletButtonPressed(); } //* Main Restore Actions - On the RestoreFromSeed/Keys Page - Restore from Seeds Action @@ -277,6 +291,12 @@ class CommonTestFlows { .enterBlockHeightForWalletRestore(secrets.moneroTestWalletBlockHeight); } + if (Platform.isLinux) { + // manual pin input + await _restoreFromSeedOrKeysPageRobot.enterPasswordForWalletRestore(CommonTestConstants.pin.join("")); + await _restoreFromSeedOrKeysPageRobot.enterPasswordRepeatForWalletRestore(CommonTestConstants.pin.join("")); + } + await _restoreFromSeedOrKeysPageRobot.onRestoreWalletButtonPressed(); } diff --git a/integration_test/funds_related_tests.dart b/integration_test/funds_related_tests.dart index db24fbc0b8..27187dc2fa 100644 --- a/integration_test/funds_related_tests.dart +++ b/integration_test/funds_related_tests.dart @@ -67,6 +67,11 @@ void main() { await authPageRobot.enterPinCode(CommonTestConstants.pin); } + final onAuthPageDesktop = authPageRobot.onAuthPageDesktop(); + if (onAuthPageDesktop) { + await authPageRobot.enterPassword(CommonTestConstants.pin.join("")); + } + // ----------- Exchange Confirm Page ------------- await exchangeConfirmPageRobot.isExchangeConfirmPage(); diff --git a/integration_test/robots/auth_page_robot.dart b/integration_test/robots/auth_page_robot.dart index 6358d43980..2f5c436273 100644 --- a/integration_test/robots/auth_page_robot.dart +++ b/integration_test/robots/auth_page_robot.dart @@ -20,6 +20,11 @@ class AuthPageRobot extends PinCodeWidgetRobot { return hasPin; } + bool onAuthPageDesktop() { + final hasWalletPasswordInput = find.byKey(ValueKey('enter_wallet_password')); + return hasWalletPasswordInput.tryEvaluate(); + } + Future isAuthPage() async { await commonTestCases.isSpecificPage(); } diff --git a/integration_test/robots/pin_code_widget_robot.dart b/integration_test/robots/pin_code_widget_robot.dart index 18dc5fba46..62e606703d 100644 --- a/integration_test/robots/pin_code_widget_robot.dart +++ b/integration_test/robots/pin_code_widget_robot.dart @@ -24,6 +24,20 @@ class PinCodeWidgetRobot { commonTestCases.hasValueKey('pin_code_button_0_key'); } + Future enterPassword(String password, {int pumpDuration = 100}) async { + await commonTestCases.enterText( + password, + 'enter_wallet_password', + ); + await tester.pumpAndSettle(); + await commonTestCases.tapItemByKey( + 'unlock', + ); + await tester.pumpAndSettle(); + + await commonTestCases.defaultSleepTime(); + } + Future enterPinCode(List pinCode, {int pumpDuration = 100}) async { for (int pin in pinCode) { await commonTestCases.tapItemByKey( diff --git a/integration_test/robots/restore_from_seed_or_key_robot.dart b/integration_test/robots/restore_from_seed_or_key_robot.dart index 9d973061bf..015a9e46ff 100644 --- a/integration_test/robots/restore_from_seed_or_key_robot.dart +++ b/integration_test/robots/restore_from_seed_or_key_robot.dart @@ -1,7 +1,9 @@ import 'package:cake_wallet/entities/seed_type.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart'; +import 'package:cake_wallet/src/widgets/seed_widget.dart'; import 'package:cake_wallet/src/widgets/validable_annotated_editable_text.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import '../components/common_test_cases.dart'; @@ -65,12 +67,28 @@ class RestoreFromSeedOrKeysPageRobot { Future enterSeedPhraseForWalletRestore(String text) async { ValidatableAnnotatedEditableTextState seedTextState = - await tester.state(find.byType(ValidatableAnnotatedEditableText)); + await tester.state(find.byType(ValidatableAnnotatedEditableText)); seedTextState.widget.controller.text = text; await tester.pumpAndSettle(); } + Future enterPasswordForWalletRestore(String text) async { + await commonTestCases.enterText( + text, + 'password', + ); + await tester.pumpAndSettle(); + } + + Future enterPasswordRepeatForWalletRestore(String text) async { + await commonTestCases.enterText( + text, + 'repeat_wallet_password', + ); + await tester.pumpAndSettle(); + } + Future enterBlockHeightForWalletRestore(String blockHeight) async { await commonTestCases.enterText( blockHeight, diff --git a/integration_test/robots/send_page_robot.dart b/integration_test/robots/send_page_robot.dart index f8e1a49adc..b705c803f0 100644 --- a/integration_test/robots/send_page_robot.dart +++ b/integration_test/robots/send_page_robot.dart @@ -183,32 +183,15 @@ class SendPageRobot { } Future _handleAuthPage() async { - tester.printToConsole('Inside _handleAuth'); - await tester.pump(); - tester.printToConsole('starting auth checks'); - - final authPage = authPageRobot.onAuthPage(); - - tester.printToConsole('hasAuth:$authPage'); - - if (authPage) { - await tester.pump(); - tester.printToConsole('Starting inner _handleAuth loop checks'); - - try { - await authPageRobot.enterPinCode(CommonTestConstants.pin, pumpDuration: 500); - tester.printToConsole('Auth done'); - - await tester.pump(); + final onAuthPage = authPageRobot.onAuthPage(); + if (onAuthPage) { + await authPageRobot.enterPinCode(CommonTestConstants.pin); + } - tester.printToConsole('Auth pump done'); - } catch (e) { - tester.printToConsole('Auth failed, retrying'); - await tester.pump(); - _handleAuthPage(); - } + final onAuthPageDesktop = authPageRobot.onAuthPageDesktop(); + if (onAuthPageDesktop) { + await authPageRobot.enterPassword(CommonTestConstants.pin.join("")); } - await tester.pump(); } Future handleSendResult() async { @@ -221,7 +204,7 @@ class SendPageRobot { tester.printToConsole('Has an Error in the handle: $hasError'); - int maxRetries = 20; + int maxRetries = 3; int retries = 0; while (hasError && retries < maxRetries) { diff --git a/integration_test/robots/wallet_seed_page_robot.dart b/integration_test/robots/wallet_seed_page_robot.dart index d52f3b1ec9..576bff0d62 100644 --- a/integration_test/robots/wallet_seed_page_robot.dart +++ b/integration_test/robots/wallet_seed_page_robot.dart @@ -14,8 +14,13 @@ class WalletSeedPageRobot { await commonTestCases.isSpecificPage(); } - Future onNextButtonPressed() async { - await commonTestCases.tapItemByKey('wallet_seed_page_next_button_key'); + Future onSeedPageVerifyButtonPressed() async { + await commonTestCases.tapItemByKey('wallet_seed_page_verify_seed_button_key'); + await commonTestCases.defaultSleepTime(); + } + + Future onOpenWalletButtonPressed() async { + await commonTestCases.tapItemByKey('wallet_seed_page_open_wallet_button_key'); await commonTestCases.defaultSleepTime(); } @@ -38,11 +43,14 @@ class WalletSeedPageRobot { final walletSeeds = walletSeedViewModel.seed; commonTestCases.hasText(walletName); - commonTestCases.hasText(walletSeeds); + final seedList = walletSeeds.trim().split(" "); + for (final seedWord in seedList) { + commonTestCases.hasTextAtLestOnce(seedWord); + } } void confirmWalletSeedReminderDisplays() { - commonTestCases.hasText(S.current.seed_reminder); + commonTestCases.hasText(S.current.cake_seeds_save_disclaimer); } Future onSaveSeedsButtonPressed() async { diff --git a/integration_test/test_suites/confirm_seeds_flow_test.dart b/integration_test/test_suites/confirm_seeds_flow_test.dart index 2d11a2cc49..a62ce3f604 100644 --- a/integration_test/test_suites/confirm_seeds_flow_test.dart +++ b/integration_test/test_suites/confirm_seeds_flow_test.dart @@ -1,9 +1,13 @@ +import 'dart:io'; + import 'package:cake_wallet/wallet_types.g.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; +import '../components/common_test_cases.dart'; import '../components/common_test_constants.dart'; import '../components/common_test_flows.dart'; import '../robots/auth_page_robot.dart'; @@ -95,6 +99,10 @@ Future _confirmSeedsFlowForWalletType( await authPageRobot.enterPinCode(CommonTestConstants.pin); } + final onAuthPageDesktop = authPageRobot.onAuthPageDesktop(); + if (onAuthPageDesktop) { + await authPageRobot.enterPassword(CommonTestConstants.pin.join("")); + } await tester.pumpAndSettle(); await walletKeysAndSeedPageRobot.isWalletKeysAndSeedPage(); diff --git a/integration_test/test_suites/exchange_flow_test.dart b/integration_test/test_suites/exchange_flow_test.dart index c36ef93962..8ec2e54e77 100644 --- a/integration_test/test_suites/exchange_flow_test.dart +++ b/integration_test/test_suites/exchange_flow_test.dart @@ -56,6 +56,10 @@ void main() { await authPageRobot.enterPinCode(CommonTestConstants.pin); } + final onAuthPageDesktop = authPageRobot.onAuthPageDesktop(); + if (onAuthPageDesktop) { + await authPageRobot.enterPassword(CommonTestConstants.pin.join("")); + } await exchangeConfirmPageRobot.onSavedTradeIdButtonPressed(); await exchangeTradePageRobot.onGotItButtonPressed(); }); diff --git a/integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart b/integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart index a810aa7221..0589d16ba8 100644 --- a/integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart +++ b/integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart @@ -1,4 +1,7 @@ +import 'dart:io'; + import 'package:cake_wallet/wallet_types.g.dart'; +import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/integration_test_runner.sh b/integration_test_runner.sh index 34c9227c06..86e28f0b85 100755 --- a/integration_test_runner.sh +++ b/integration_test_runner.sh @@ -1,4 +1,5 @@ #!/bin/bash +export DESKTOP_FORCE_MOBILE="Y" declare -a targets declare -a passed_tests @@ -12,6 +13,10 @@ done < <(find integration_test/test_suites -name "*.dart" -type f -print0) # Run each test and collect results for target in "${targets[@]}" do + if [[ "x$REMOVE_DATA_DIRECTORY" == "xY" ]]; + then + rm -rf ~/.local/share/com.example.cake_wallet ~/Documents/cake_wallet + fi echo "Running test: $target" if flutter drive \ --driver=test_driver/integration_test.dart \ diff --git a/lib/core/backup_service.dart b/lib/core/backup_service.dart index f101ed7e10..03f20363d5 100644 --- a/lib/core/backup_service.dart +++ b/lib/core/backup_service.dart @@ -293,6 +293,7 @@ class BackupService { final lookupsUnstoppableDomains = data[PreferencesKey.lookupsUnstoppableDomains] as bool?; final lookupsOpenAlias = data[PreferencesKey.lookupsOpenAlias] as bool?; final lookupsENS = data[PreferencesKey.lookupsENS] as bool?; + final lookupsWellKnown = data[PreferencesKey.lookupsWellKnown] as bool?; final syncAll = data[PreferencesKey.syncAllKey] as bool?; final syncMode = data[PreferencesKey.syncModeKey] as int?; final autoGenerateSubaddressStatus = @@ -403,6 +404,9 @@ class BackupService { if (lookupsENS != null) await _sharedPreferences.setBool(PreferencesKey.lookupsENS, lookupsENS); + if (lookupsWellKnown != null) + await _sharedPreferences.setBool(PreferencesKey.lookupsWellKnown, lookupsWellKnown); + if (syncAll != null) await _sharedPreferences.setBool(PreferencesKey.syncAllKey, syncAll); if (syncMode != null) await _sharedPreferences.setInt(PreferencesKey.syncModeKey, syncMode); @@ -542,6 +546,8 @@ class BackupService { _sharedPreferences.getBool(PreferencesKey.lookupsUnstoppableDomains), PreferencesKey.lookupsOpenAlias: _sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias), PreferencesKey.lookupsENS: _sharedPreferences.getBool(PreferencesKey.lookupsENS), + PreferencesKey.lookupsWellKnown: + _sharedPreferences.getBool(PreferencesKey.lookupsWellKnown), PreferencesKey.syncModeKey: _sharedPreferences.getInt(PreferencesKey.syncModeKey), PreferencesKey.syncAllKey: _sharedPreferences.getBool(PreferencesKey.syncAllKey), PreferencesKey.autoGenerateSubaddressStatusKey: diff --git a/lib/entities/background_tasks.dart b/lib/entities/background_tasks.dart index cf8c09292d..f3d9e486d5 100644 --- a/lib/entities/background_tasks.dart +++ b/lib/entities/background_tasks.dart @@ -23,20 +23,16 @@ import 'package:flutter_background_service/flutter_background_service.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:cake_wallet/main.dart'; import 'package:cake_wallet/di.dart'; -import 'package:intl/intl.dart'; -const moneroSyncTaskKey = "com.fotolockr.cakewallet.monero_sync_task"; -const mwebSyncTaskKey = "com.fotolockr.cakewallet.mweb_sync_task"; - -const initialNotificationTitle = 'Cake Background Sync'; -const standbyMessage = 'On standby - app is in the foreground'; -const readyMessage = 'Ready to sync - waiting until the app has been in the background for a while'; -const startMessage = 'Starting sync - app is in the background'; -const allWalletsSyncedMessage = 'All wallets synced - waiting for next queue refresh'; +const initialNotificationTitle = "Cake Background Sync"; +const standbyMessage = "On standby - app is in the foreground"; +const readyMessage = "Ready to sync - waiting until the app has been in the background for a while"; +const startMessage = "Starting sync - app is in the background"; +const allWalletsSyncedMessage = "All wallets synced - waiting for next queue refresh"; const notificationId = 888; -const notificationChannelId = 'cake_service'; -const notificationChannelName = 'CAKE BACKGROUND SERVICE'; -const notificationChannelDescription = 'Cake Wallet Background Service'; +const notificationChannelId = "cake_service"; +const notificationChannelName = "CAKE BACKGROUND SERVICE"; +const notificationChannelDescription = "Cake Wallet Background Service"; const DELAY_SECONDS_BEFORE_SYNC_START = 15; const spNodeNotificationMessage = "Currently configured Bitcoin node does not support Silent Payments. skipping wallet"; @@ -58,7 +54,7 @@ void setMainNotification( android: AndroidNotificationDetails( notificationChannelId, notificationChannelName, - icon: 'ic_bg_service_small', + icon: "ic_bg_service_small", ongoing: true, silent: true, ), @@ -114,7 +110,7 @@ void setWalletNotification(FlutterLocalNotificationsPlugin flutterLocalNotificat android: AndroidNotificationDetails( "${notificationChannelId}_$walletNum", "${notificationChannelName}_$walletNum", - icon: 'ic_bg_service_small', + icon: "ic_bg_service_small", ongoing: true, silent: true, ), @@ -122,13 +118,36 @@ void setWalletNotification(FlutterLocalNotificationsPlugin flutterLocalNotificat ); } -@pragma('vm:entry-point') +AppLifecycleState appStateFromString(String state) { + switch (state) { + case "AppLifecycleState.paused": + return AppLifecycleState.paused; + case "AppLifecycleState.resumed": + return AppLifecycleState.resumed; + case "AppLifecycleState.hidden": + return AppLifecycleState.hidden; + case "AppLifecycleState.detached": + return AppLifecycleState.detached; + case "AppLifecycleState.inactive": + return AppLifecycleState.inactive; + } + throw Exception("unknown app state: $state"); +} + +@pragma("vm:entry-point") Future onStart(ServiceInstance service) async { printV("BACKGROUND SERVICE STARTED"); bool bgSyncStarted = false; Timer? _syncTimer; Timer? _stuckSyncTimer; Timer? _queueTimer; + Timer? _appStateTimer; + List syncingWallets = []; + List standbyWallets = []; + Timer? _bgTimer; + AppLifecycleState lastAppState = AppLifecycleState.resumed; + final List lastAppStates = []; + String serviceState = "NOT_RUNNING"; // commented because the behavior appears to be bugged: // DartPluginRegistrant.ensureInitialized(); @@ -136,34 +155,72 @@ Future onStart(ServiceInstance service) async { final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); - service.on('stopService').listen((event) async { - printV("STOPPING BACKGROUND SERVICE"); + Future stopAllSyncing() async { _syncTimer?.cancel(); _stuckSyncTimer?.cancel(); _queueTimer?.cancel(); + try { + // stop all syncing wallets: + for (int i = 0; i < syncingWallets.length; i++) { + final wallet = syncingWallets[i]; + await wallet.stopSync(isBackgroundSync: true); + } + // stop all standby wallets (just in case): + for (int i = 0; i < standbyWallets.length; i++) { + final wallet = standbyWallets[i]; + await wallet.stopSync(isBackgroundSync: true); + } + } catch (e) { + printV("error stopping sync: $e"); + } + printV("done stopping sync"); + } + + service.on("stopService").listen((event) async { + printV("STOPPING BACKGROUND SERVICE"); + await stopAllSyncing(); + // stop the service itself: + service.invoke("serviceState", {"state": "NOT_RUNNING"}); await service.stopSelf(); }); - service.on('status').listen((event) async { + service.on("status").listen((event) async { printV(event); }); - service.on('setForeground').listen((event) async { + Future setForeground() async { + serviceState = "FOREGROUND"; bgSyncStarted = false; - _syncTimer?.cancel(); setNotificationStandby(flutterLocalNotificationsPlugin); + } + + service.on("setForeground").listen((event) async { + await setForeground(); + service.invoke("serviceState", {"state": "FOREGROUND"}); + }); + + void setReady() { + if (serviceState != "READY" && serviceState != "BACKGROUND") { + serviceState = "READY"; + setNotificationReady(flutterLocalNotificationsPlugin); + } + } + + service.on("setReady").listen((event) async { + setReady(); }); - service.on('setReady').listen((event) async { - setNotificationReady(flutterLocalNotificationsPlugin); + service.on("appState").listen((event) async { + printV("APP STATE: ${event?["state"]}"); + lastAppState = appStateFromString(event?["state"] as String); }); // we have entered the background, start the sync: - service.on('setBackground').listen((event) async { - if (bgSyncStarted) { - return; - } + void setBackground() async { + // only runs once per service instance: + if (bgSyncStarted) return; bgSyncStarted = true; + serviceState = "BACKGROUND"; await Future.delayed(const Duration(seconds: DELAY_SECONDS_BEFORE_SYNC_START)); printV("STARTING SYNC FROM BG"); @@ -185,21 +242,22 @@ Future onStart(ServiceInstance service) async { final settingsStore = getIt.get(); final walletListViewModel = getIt.get(); - List syncingWallets = []; - List standbyWallets = []; - // get all Monero / Wownero wallets and add them final List moneroWallets = walletListViewModel.wallets .where((element) => [WalletType.monero, WalletType.wownero].contains(element.type)) .toList(); + printV("LOADING MONERO WALLETS"); + for (int i = 0; i < moneroWallets.length; i++) { final wallet = await walletLoadingService.load(moneroWallets[i].type, moneroWallets[i].name); - final node = settingsStore.getCurrentNode(moneroWallets[i].type); - await wallet.stopSync(); + // stop regular sync process if it's been started + // await wallet.stopSync(isBackgroundSync: false); syncingWallets.add(wallet); } + printV("MONERO WALLETS LOADED"); + // get all litecoin wallets and add them: final List litecoinWallets = walletListViewModel.wallets .where((element) => element.type == WalletType.litecoin) @@ -272,7 +330,7 @@ Future onStart(ServiceInstance service) async { printV("${wallet.name} NOT CONNECTED"); final node = settingsStore.getCurrentNode(wallet.type); await wallet.connectToNode(node: node); - wallet.startSync(); + wallet.startSync(isBackgroundSync: true); printV("STARTED SYNC"); } @@ -282,7 +340,7 @@ Future onStart(ServiceInstance service) async { syncedTicks = 0; printV("WALLET $i SYNCED"); try { - await wallet.stopSync(); + await wallet.stopSync(isBackgroundSync: true); } catch (e) { printV("error stopping sync: $e"); } @@ -330,7 +388,7 @@ Future onStart(ServiceInstance service) async { } } else { if (syncStatus is! NotConnectedSyncStatus) { - wallet.stopSync(); + wallet.stopSync(isBackgroundSync: true); } if (progress < SYNC_THRESHOLD) { content = "$progressPercent - Waiting in sync queue"; @@ -396,7 +454,7 @@ Future onStart(ServiceInstance service) async { if (syncStatus is NotConnectedSyncStatus) { final node = settingsStore.getCurrentNode(wallet.type); await wallet.connectToNode(node: node); - await wallet.startSync(); + await wallet.startSync(isBackgroundSync: true); } // wait a while before checking progress: @@ -428,7 +486,7 @@ Future onStart(ServiceInstance service) async { printV("syncing appears to be stuck, restarting..."); try { stuckWallets.add(wallet.name); - await wallet.stopSync(); + await wallet.stopSync(isBackgroundSync: true); } catch (e) { printV("error restarting sync: $e"); } @@ -440,9 +498,38 @@ Future onStart(ServiceInstance service) async { stuckWallets = []; return; } - wallet.startSync(); + wallet.startSync(isBackgroundSync: true); } }); + } + + service.on("setBackground").listen((event) async { + setBackground(); + }); + + // if the app state changes to paused, setReady() + // if the app state has been paused for more than 10 seconds, setBackground() + _appStateTimer = Timer.periodic(const Duration(seconds: 1), (timer) async { + lastAppStates.add(lastAppState); + if (lastAppStates.length > 10) { + lastAppStates.removeAt(0); + } + // printV(lastAppStates); + // if (lastAppState == AppLifecycleState.resumed && serviceState != "FOREGROUND") { + // setForeground(); + // } + if (lastAppStates.length < 5) { + service.invoke("serviceState", {"state": serviceState}); + return; + } + if (lastAppState == AppLifecycleState.paused && serviceState != "READY") { + setReady(); + } + // if all 10 states are paused, setBackground() + if (lastAppStates.every((state) => state == AppLifecycleState.paused) && !bgSyncStarted) { + setBackground(); + } + service.invoke("serviceState", {"state": serviceState}); }); } @@ -523,24 +610,47 @@ class BackgroundTasks { FlutterBackgroundService bgService = FlutterBackgroundService(); FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); + static Timer? _pingTimer; + static String serviceState = "NOT_RUNNING"; void serviceBackground() { bgService.invoke("setBackground"); + _pingTimer?.cancel(); + } + + void foregroundPing() { + bgService.invoke("foregroundPing"); + } + + void lastAppState(AppLifecycleState state) { + bgService.invoke("appState", {"state": state.toString()}); } Future serviceForeground() async { + printV("SERVICE FOREGROUNDED"); final settingsStore = getIt.get(); bool showNotifications = settingsStore.showSyncNotification; - bgService.invoke('stopService'); - await Future.delayed(const Duration(seconds: 2)); + bgService.invoke("stopService"); + await Future.delayed(const Duration(seconds: 5)); initializeService(bgService, showNotifications); + bgService.invoke("setForeground"); + } + + Future isServiceRunning() async { + return await bgService.isRunning(); + } + + Future isBackgroundSyncing() async { + printV("serviceState: ${serviceState}"); + printV("isRunning: ${await bgService.isRunning()}"); + return await bgService.isRunning() && serviceState == "BACKGROUND"; } void serviceReady() { final settingsStore = getIt.get(); bool showNotifications = settingsStore.showSyncNotification; if (showNotifications) { - bgService.invoke('setReady'); + bgService.invoke("setReady"); } } @@ -587,7 +697,18 @@ class BackgroundTasks { REFRESH_QUEUE_DURATION = syncMode.frequency; + _pingTimer?.cancel(); + _pingTimer = Timer.periodic(const Duration(seconds: 1), (timer) async { + getIt.get().foregroundPing(); + }); + + bgService.on("serviceState").listen((event) { + printV("UPDATING SERVICE STATE: ${event?["state"]}"); + serviceState = event?["state"] as String; + }); + await initializeService(bgService, useNotifications); + bgService.invoke("setForeground"); } catch (error, stackTrace) { printV(error); printV(stackTrace); diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index 25140f106e..049bfa15c2 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -334,20 +334,20 @@ Future defaultSettingsMigration( ); break; case 46: - _fixNodesUseSSLFlag(nodes); - updateWalletTypeNodesWithNewNode( + await _fixNodesUseSSLFlag(nodes); + await updateWalletTypeNodesWithNewNode( newNodeUri: 'litecoin.stackwallet.com:20063', nodes: nodes, type: WalletType.litecoin, useSSL: true, ); - updateWalletTypeNodesWithNewNode( + await updateWalletTypeNodesWithNewNode( newNodeUri: 'electrum-ltc.bysh.me:50002', nodes: nodes, type: WalletType.litecoin, useSSL: true, ); - _changeDefaultNode( + await _changeDefaultNode( nodes: nodes, sharedPreferences: sharedPreferences, type: WalletType.solana, @@ -360,13 +360,13 @@ Future defaultSettingsMigration( 'solana-rpc.publicnode.com:443', ], ); - _updateNode( + await _updateNode( nodes: nodes, currentUri: "ethereum.publicnode.com", newUri: "ethereum-rpc.publicnode.com", useSSL: true, ); - _updateNode( + await _updateNode( nodes: nodes, currentUri: "polygon-bor.publicnode.com", newUri: "polygon-bor-rpc.publicnode.com", @@ -387,12 +387,12 @@ Future defaultSettingsMigration( await sharedPreferences.setInt(PreferencesKey.currentDefaultSettingsMigrationVersion, version); } -void _updateNode({ +Future _updateNode({ required Box nodes, required String currentUri, String? newUri, bool? useSSL, -}) { +}) async { for (Node node in nodes.values) { if (node.uriRaw == currentUri) { if (newUri != null) { @@ -401,6 +401,7 @@ void _updateNode({ if (useSSL != null) { node.useSSL = useSSL; } + await node.save(); } } } @@ -481,7 +482,7 @@ void _deselectExchangeProvider(SharedPreferences sharedPreferences, String provi ); } -void _fixNodesUseSSLFlag(Box nodes) { +Future _fixNodesUseSSLFlag(Box nodes) async { for (Node node in nodes.values) { switch (node.uriRaw) { case cakeWalletLitecoinElectrumUri: @@ -490,6 +491,7 @@ void _fixNodesUseSSLFlag(Box nodes) { case newCakeWalletMoneroUri: node.useSSL = true; node.trusted = true; + await node.save(); } } } diff --git a/lib/entities/parse_address_from_domain.dart b/lib/entities/parse_address_from_domain.dart index b13dfa9ad4..9be1250812 100644 --- a/lib/entities/parse_address_from_domain.dart +++ b/lib/entities/parse_address_from_domain.dart @@ -5,6 +5,7 @@ import 'package:cake_wallet/entities/openalias_record.dart'; import 'package:cake_wallet/entities/parsed_address.dart'; import 'package:cake_wallet/entities/unstoppable_domain_address.dart'; import 'package:cake_wallet/entities/emoji_string_extension.dart'; +import 'package:cake_wallet/entities/wellknown_record.dart'; import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart'; import 'package:cake_wallet/mastodon/mastodon_api.dart'; import 'package:cake_wallet/nostr/nostr_api.dart'; @@ -208,6 +209,17 @@ class AddressResolver { } } + // .well-known scheme: + if (text.contains('.') && text.contains('@')) { + if (settingsStore.lookupsWellKnown) { + final record = + await WellKnownRecord.fetchAddressAndName(formattedName: text, currency: currency); + if (record != null) { + return ParsedAddress.fetchWellKnownAddress(address: record.address, name: text); + } + } + } + if (!text.startsWith('@') && text.contains('@') && !text.contains('.')) { final bool isFioRegistered = await FioAddressProvider.checkAvail(text); if (isFioRegistered) { diff --git a/lib/entities/parsed_address.dart b/lib/entities/parsed_address.dart index cfd69acbe3..eabc606db7 100644 --- a/lib/entities/parsed_address.dart +++ b/lib/entities/parsed_address.dart @@ -12,7 +12,8 @@ enum ParseFrom { contact, mastodon, nostr, - thorChain + thorChain, + wellKnown } class ParsedAddress { @@ -142,6 +143,14 @@ class ParsedAddress { ); } + factory ParsedAddress.fetchWellKnownAddress({required String address, required String name}) { + return ParsedAddress( + addresses: [address], + name: name, + parseFrom: ParseFrom.wellKnown, + ); + } + final List addresses; final String name; final String description; diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index 427c57cd01..04c4d7146c 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -80,6 +80,7 @@ class PreferencesKey { static const lookupsUnstoppableDomains = 'looks_up_unstoppable_domain'; static const lookupsOpenAlias = 'looks_up_open_alias'; static const lookupsENS = 'looks_up_ens'; + static const lookupsWellKnown = 'looks_up_well_known'; static const showCameraConsent = 'show_camera_consent'; static String moneroWalletUpdateV1Key(String name) => diff --git a/lib/entities/wellknown_record.dart b/lib/entities/wellknown_record.dart new file mode 100644 index 0000000000..edc972f76c --- /dev/null +++ b/lib/entities/wellknown_record.dart @@ -0,0 +1,92 @@ +import 'dart:convert'; + +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/utils/print_verbose.dart'; +import 'package:http/http.dart' as http; + +class WellKnownRecord { + WellKnownRecord({ + required this.address, + required this.name, + }); + + final String name; + final String address; + + static Future checkWellKnownUsername(String username, CryptoCurrency currency) async { + String jsonLocation = ""; + switch (currency) { + case CryptoCurrency.nano: + jsonLocation = "nano-currency"; + break; + // TODO: add other currencies + default: + return null; + } + + // split the string by the @ symbol: + try { + final List splitStrs = username.split("@"); + String name = splitStrs.first.toLowerCase(); + final String domain = splitStrs.last; + + if (splitStrs.length == 3) { + // for username like @alice@domain.org instead of alice@domain.org + name = splitStrs[1]; + } + + if (name.isEmpty) { + name = "_"; + } + + // lookup domain/.well-known/nano-currency.json and check if it has a nano address: + final http.Response response = await http.get( + Uri.parse("https://$domain/.well-known/$jsonLocation.json?names=$name"), + headers: {"Accept": "application/json"}, + ); + + if (response.statusCode != 200) { + return null; + } + final Map decoded = json.decode(response.body) as Map; + + // Access the first element in the names array and retrieve its address + final List names = decoded["names"] as List; + for (final dynamic item in names) { + if (item["name"].toLowerCase() == name) { + return item["address"] as String; + } + } + } catch (e) { + printV("error checking well-known username: $e"); + } + return null; + } + + static String formatDomainName(String name) { + String formattedName = name; + + if (name.contains("@")) { + formattedName = name.replaceAll("@", "."); + } + + return formattedName; + } + + static Future fetchAddressAndName({ + required String formattedName, + required CryptoCurrency currency, + }) async { + String name = formattedName; + + print("formattedName: $formattedName"); + + final address = await checkWellKnownUsername(formattedName, currency); + + if (address == null) { + return null; + } + + return WellKnownRecord(address: address, name: name); + } +} diff --git a/lib/src/screens/new_wallet/new_wallet_page.dart b/lib/src/screens/new_wallet/new_wallet_page.dart index e63a01f61c..e2d5a953c0 100644 --- a/lib/src/screens/new_wallet/new_wallet_page.dart +++ b/lib/src/screens/new_wallet/new_wallet_page.dart @@ -221,6 +221,7 @@ class _WalletNameFormState extends State { ), if (_walletNewVM.hasWalletPassword) ...[ TextFormField( + key: ValueKey('password'), onChanged: (value) => _walletNewVM.walletPassword = value, controller: _passwordController, textAlign: TextAlign.center, @@ -257,6 +258,7 @@ class _WalletNameFormState extends State { ), ), TextFormField( + key: ValueKey('repeat_wallet_password'), onChanged: (value) => _walletNewVM.repeatedWalletPassword = value, controller: _repeatedPasswordController, textAlign: TextAlign.center, diff --git a/lib/src/screens/restore/wallet_restore_from_keys_form.dart b/lib/src/screens/restore/wallet_restore_from_keys_form.dart index 83772f866c..0c9af69103 100644 --- a/lib/src/screens/restore/wallet_restore_from_keys_form.dart +++ b/lib/src/screens/restore/wallet_restore_from_keys_form.dart @@ -148,12 +148,14 @@ class WalletRestoreFromKeysFromState extends State { ), if (widget.displayWalletPassword) ...[Container( + key: ValueKey('password'), padding: EdgeInsets.only(top: 20.0), child: BaseTextFormField( controller: passwordTextEditingController, hintText: S.of(context).password, obscureText: true)), Container( + key: ValueKey('repeat_wallet_password'), padding: EdgeInsets.only(top: 20.0), child: BaseTextFormField( controller: repeatedPasswordTextEditingController, diff --git a/lib/src/screens/restore/wallet_restore_from_seed_form.dart b/lib/src/screens/restore/wallet_restore_from_seed_form.dart index 1684f6f925..6a493087bf 100644 --- a/lib/src/screens/restore/wallet_restore_from_seed_form.dart +++ b/lib/src/screens/restore/wallet_restore_from_seed_form.dart @@ -223,12 +223,14 @@ class WalletRestoreFromSeedFormState extends State { ), if (widget.displayWalletPassword) ...[BaseTextFormField( + key: ValueKey('password'), controller: passwordTextEditingController, hintText: S .of(context) .password, obscureText: true), BaseTextFormField( + key: ValueKey('repeat_wallet_password'), controller: repeatedPasswordTextEditingController, hintText: S .of(context) diff --git a/lib/src/screens/root/root.dart b/lib/src/screens/root/root.dart index 9e984f1337..ae7a4b9ea1 100644 --- a/lib/src/screens/root/root.dart +++ b/lib/src/screens/root/root.dart @@ -133,7 +133,7 @@ class RootState extends State with WidgetsBindingObserver { } @override - void didChangeAppLifecycleState(AppLifecycleState state) { + void didChangeAppLifecycleState(AppLifecycleState state) async { final syncingWalletTypes = [WalletType.litecoin, WalletType.monero, WalletType.bitcoin]; switch (state) { case AppLifecycleState.paused: @@ -145,10 +145,6 @@ class RootState extends State with WidgetsBindingObserver { setState(() => _setInactive(true)); } - if (FeatureFlag.isBackgroundSyncEnabled && syncingWalletTypes.contains(widget.appStore.wallet?.type)) { - widget.appStore.wallet?.stopSync(); - } - break; case AppLifecycleState.resumed: widget.authService.requireAuth().then((value) { @@ -163,39 +159,47 @@ class RootState extends State with WidgetsBindingObserver { break; } + // _stateTimer?.cancel(); + // _stateTimer = Timer(const Duration(seconds: 1), () async { + // getIt.get().lastAppState(state); + // }); + + getIt.get().lastAppState(state); + // background service handling: + printV("state: $state"); + final appStore = widget.appStore; + final wallet = appStore.wallet; switch (state) { case AppLifecycleState.resumed: - // restart the background service if it was running before: - getIt.get().serviceForeground(); - _stateTimer?.cancel(); - if (!wasInBackground) { + bool isBackgroundSyncing = await getIt.get().isBackgroundSyncing(); + + printV("isBackgroundSyncing: $isBackgroundSyncing"); + + if (!isBackgroundSyncing) { return; } - wasInBackground = false; - if (syncingWalletTypes.contains(widget.appStore.wallet?.type)) { - // wait a few seconds before starting the sync make sure the background service is fully exited: - Future.delayed(const Duration(seconds: 3), () { - widget.appStore.wallet?.startSync(); - }); - } + + // await wallet?.closeWallet(); + // restart the background service if it was running before: + await getIt.get().serviceForeground(); + + await Future.delayed(const Duration(seconds: 10)); + + await wallet?.stopSync(isBackgroundSync: true); + + await Future.delayed(const Duration(seconds: 10)); + + await wallet?.reopenWallet(); + break; - case AppLifecycleState.paused: - // TODO: experimental: maybe should uncomment this: - // getIt.get().serviceBackground(false, showNotifications); - getIt.get().serviceReady(); + case AppLifecycleState.hidden: case AppLifecycleState.inactive: case AppLifecycleState.detached: - default: - // anything other than resumed update the notification to say we're in the "ready" state: - // if we enter any state other than resumed start a timer for 30 seconds - // after which we'll consider the app to be in the background - _stateTimer?.cancel(); - // TODO: bump this to > 30 seconds when testing is done: - _stateTimer = Timer(const Duration(seconds: 10), () async { - wasInBackground = true; - getIt.get().serviceBackground(); - }); + break; + case AppLifecycleState.paused: + await wallet?.stopSync(); + await wallet?.close(); break; } _previousState = state; diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index a52bd11e97..2d64c55a37 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -525,6 +525,10 @@ class SendPage extends BasePage { if (state is TransactionCommitted) { WidgetsBinding.instance.addPostFrameCallback((_) async { + if (!context.mounted) { + return; + } + final successMessage = S.of(context).send_success( sendViewModel.selectedCryptoCurrency.toString()); diff --git a/lib/src/screens/send/widgets/extract_address_from_parsed.dart b/lib/src/screens/send/widgets/extract_address_from_parsed.dart index 9ce3ca2b11..106be97ee8 100644 --- a/lib/src/screens/send/widgets/extract_address_from_parsed.dart +++ b/lib/src/screens/send/widgets/extract_address_from_parsed.dart @@ -30,6 +30,11 @@ Future extractAddressFromParsed( content = S.of(context).extracted_address_content('${parsedAddress.name} (OpenAlias)'); address = parsedAddress.addresses.first; break; + case ParseFrom.wellKnown: + title = S.of(context).address_detected; + content = S.of(context).extracted_address_content('${parsedAddress.name} (Well-Known)'); + address = parsedAddress.addresses.first; + break; case ParseFrom.fio: title = S.of(context).address_detected; content = S.of(context).extracted_address_content('${parsedAddress.name} (FIO)'); diff --git a/lib/src/screens/settings/domain_lookups_page.dart b/lib/src/screens/settings/domain_lookups_page.dart index aa7e68cd0c..0eb559817a 100644 --- a/lib/src/screens/settings/domain_lookups_page.dart +++ b/lib/src/screens/settings/domain_lookups_page.dart @@ -45,6 +45,10 @@ class DomainLookupsPage extends BasePage { title: 'Ethereum Name Service', value: _privacySettingsViewModel.looksUpENS, onValueChange: (_, bool value) => _privacySettingsViewModel.setLookupsENS(value)), + SettingsSwitcherCell( + title: '.well-known', + value: _privacySettingsViewModel.looksUpWellKnown, + onValueChange: (_, bool value) => _privacySettingsViewModel.setLookupsWellKnown(value)), //if (!isHaven) it does not work correctly ], diff --git a/lib/src/screens/wallet_unlock/wallet_unlock_page.dart b/lib/src/screens/wallet_unlock/wallet_unlock_page.dart index 3e6966f9d2..4afbfe2c17 100644 --- a/lib/src/screens/wallet_unlock/wallet_unlock_page.dart +++ b/lib/src/screens/wallet_unlock/wallet_unlock_page.dart @@ -170,6 +170,7 @@ class WalletUnlockPageState extends AuthPageState { SizedBox(height: 24), Form( child: TextFormField( + key: ValueKey('enter_wallet_password'), onChanged: (value) => null, controller: _passwordController, textAlign: TextAlign.center, @@ -205,6 +206,7 @@ class WalletUnlockPageState extends AuthPageState { ), ), Padding( + key: ValueKey('unlock'), padding: EdgeInsets.only(bottom: 24), child: Observer( builder: (_) => LoadingPrimaryButton( diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index c0b103c861..866cb0067d 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -119,6 +119,7 @@ abstract class SettingsStoreBase with Store { required this.lookupsUnstoppableDomains, required this.lookupsOpenAlias, required this.lookupsENS, + required this.lookupsWellKnown, required this.customBitcoinFeeRate, required this.silentPaymentsCardDisplay, required this.silentPaymentsAlwaysScan, @@ -485,6 +486,11 @@ abstract class SettingsStoreBase with Store { reaction((_) => lookupsENS, (bool looksUpENS) => _sharedPreferences.setBool(PreferencesKey.lookupsENS, looksUpENS)); + reaction( + (_) => lookupsWellKnown, + (bool looksUpWellKnown) => + _sharedPreferences.setBool(PreferencesKey.lookupsWellKnown, looksUpWellKnown)); + // secure storage keys: reaction( (_) => allowBiometricalAuthentication, @@ -798,6 +804,8 @@ abstract class SettingsStoreBase with Store { @observable bool lookupsENS; + @observable + bool lookupsWellKnown; @observable SyncMode currentSyncMode; @@ -1005,6 +1013,7 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getBool(PreferencesKey.lookupsUnstoppableDomains) ?? true; final lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true; final lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true; + final lookupsWellKnown = sharedPreferences.getBool(PreferencesKey.lookupsWellKnown) ?? true; final customBitcoinFeeRate = sharedPreferences.getInt(PreferencesKey.customBitcoinFeeRate) ?? 1; final silentPaymentsCardDisplay = sharedPreferences.getBool(PreferencesKey.silentPaymentsCardDisplay) ?? true; @@ -1287,6 +1296,7 @@ abstract class SettingsStoreBase with Store { lookupsUnstoppableDomains: lookupsUnstoppableDomains, lookupsOpenAlias: lookupsOpenAlias, lookupsENS: lookupsENS, + lookupsWellKnown: lookupsWellKnown, customBitcoinFeeRate: customBitcoinFeeRate, silentPaymentsCardDisplay: silentPaymentsCardDisplay, silentPaymentsAlwaysScan: silentPaymentsAlwaysScan, @@ -1460,6 +1470,7 @@ abstract class SettingsStoreBase with Store { sharedPreferences.getBool(PreferencesKey.lookupsUnstoppableDomains) ?? true; lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true; lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true; + lookupsWellKnown = sharedPreferences.getBool(PreferencesKey.lookupsWellKnown) ?? true; customBitcoinFeeRate = sharedPreferences.getInt(PreferencesKey.customBitcoinFeeRate) ?? 1; silentPaymentsCardDisplay = sharedPreferences.getBool(PreferencesKey.silentPaymentsCardDisplay) ?? true; diff --git a/lib/view_model/settings/privacy_settings_view_model.dart b/lib/view_model/settings/privacy_settings_view_model.dart index eaa9f9e840..67f0d88a03 100644 --- a/lib/view_model/settings/privacy_settings_view_model.dart +++ b/lib/view_model/settings/privacy_settings_view_model.dart @@ -94,6 +94,9 @@ abstract class PrivacySettingsViewModelBase with Store { @computed bool get looksUpENS => _settingsStore.lookupsENS; + @computed + bool get looksUpWellKnown => _settingsStore.lookupsWellKnown; + bool get canUseEtherscan => _wallet.type == WalletType.ethereum; bool get canUsePolygonScan => _wallet.type == WalletType.polygon; @@ -130,6 +133,9 @@ abstract class PrivacySettingsViewModelBase with Store { @action void setLookupsENS(bool value) => _settingsStore.lookupsENS = value; + @action + void setLookupsWellKnown(bool value) => _settingsStore.lookupsWellKnown = value; + @action void setLookupsYatService(bool value) => _settingsStore.lookupsYatService = value; diff --git a/linux/my_application.cc b/linux/my_application.cc index 49ed75499b..49f9ae139a 100644 --- a/linux/my_application.cc +++ b/linux/my_application.cc @@ -46,8 +46,11 @@ static void my_application_activate(GApplication* application) { } else { gtk_window_set_title(window, "Cake Wallet"); } - - gtk_window_set_default_size(window, 1280, 720); + if (getenv("DESKTOP_FORCE_MOBILE")) { + gtk_window_set_default_size(window, 720, 1280); + } else { + gtk_window_set_default_size(window, 1280, 720); + } gtk_widget_show(GTK_WIDGET(window)); g_autoptr(FlDartProject) project = fl_dart_project_new(); diff --git a/model_generator.sh b/model_generator.sh index 730817c24f..1443b0fc98 100755 --- a/model_generator.sh +++ b/model_generator.sh @@ -1,18 +1,24 @@ #!/bin/bash set -x -e -cd cw_core; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd .. -cd cw_evm; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd .. -cd cw_monero; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd .. -cd cw_bitcoin; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd .. -cd cw_haven; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd .. -cd cw_nano; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd .. -cd cw_bitcoin_cash; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd .. -cd cw_solana; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd .. -cd cw_tron; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd .. -cd cw_wownero; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd .. -cd cw_polygon; flutter pub get; cd .. -cd cw_ethereum; flutter pub get; cd .. -cd cw_mweb && flutter pub get && cd .. -dart run build_runner build --delete-conflicting-outputs +for cwcoin in cw_{core,evm,monero,bitcoin,haven,nano,bitcoin_cash,solana,tron,wownero} +do + if [[ "x$1" == "xasync" ]]; + then + bash -c "cd $cwcoin; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd .." & + else + bash -c "cd $cwcoin; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd .." + fi +done +for cwcoin in cw_{polygon,ethereum,mwebd}; +do + if [[ "x$1" == "xasync" ]]; + then + bash -c "cd $cwcoin; flutter pub get; cd .." & + else + bash -c "cd $cwcoin; flutter pub get; cd .." + fi +done +flutter pub get +dart run build_runner build --delete-conflicting-outputs diff --git a/pubspec_base.yaml b/pubspec_base.yaml index a4ced8c6f8..be1dba1349 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -110,7 +110,10 @@ dependencies: polyseed: ^0.0.6 nostr_tools: ^1.0.9 solana: ^0.31.0+1 - ledger_flutter_plus: ^1.4.1 + ledger_flutter_plus: + git: + url: https://github.com/vespr-wallet/ledger-flutter-plus + ref: c2e341d8038f1108690ad6f80f7b4b7156aacc76 hashlib: ^1.19.2 dev_dependencies: @@ -149,6 +152,10 @@ dependency_overrides: url: https://github.com/cake-tech/bitcoin_base ref: cake-update-v9 ffi: 2.1.0 + ledger_flutter_plus: + git: + url: https://github.com/vespr-wallet/ledger-flutter-plus + ref: c2e341d8038f1108690ad6f80f7b4b7156aacc76 flutter_icons: image_path: "assets/images/app_logo.png" diff --git a/scripts/android/.gitignore b/scripts/android/.gitignore new file mode 100644 index 0000000000..f7e94b7c08 --- /dev/null +++ b/scripts/android/.gitignore @@ -0,0 +1 @@ +mwebd \ No newline at end of file diff --git a/scripts/android/app_env.sh b/scripts/android/app_env.sh index 6b19074511..45e28379d9 100644 --- a/scripts/android/app_env.sh +++ b/scripts/android/app_env.sh @@ -23,7 +23,7 @@ MONERO_COM_SCHEME="monero.com" CAKEWALLET_NAME="Cake Wallet" CAKEWALLET_VERSION="4.22.1" -CAKEWALLET_BUILD_NUMBER=241 +CAKEWALLET_BUILD_NUMBER=242 CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet" CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet" CAKEWALLET_SCHEME="cakewallet" diff --git a/scripts/android/build_monero_all.sh b/scripts/android/build_monero_all.sh index 261ebd5601..71a6b62282 100755 --- a/scripts/android/build_monero_all.sh +++ b/scripts/android/build_monero_all.sh @@ -8,50 +8,20 @@ cd "$(dirname "$0")" NPROC="-j$(nproc)" -if [[ "x$(uname)" == "xDarwin" ]]; -then - USE_DOCKER="ON" - NPROC="-j1" -fi - ../prepare_moneroc.sh -if [[ ! "x$RUNNER_OS" == "x" ]]; -then - REMOVE_CACHES=ON -fi - -# NOTE: -j1 is intentional. Otherwise you will run into weird behaviour on macos -if [[ ! "x$USE_DOCKER" == "x" ]]; -then - for COIN in monero wownero; - do - pushd ../monero_c - docker run --platform linux/amd64 -v$HOME/.cache/ccache:/root/.ccache -v$PWD:$PWD -w $PWD --rm -it git.mrcyjanek.net/mrcyjanek/debian:buster bash -c "git config --global --add safe.directory '*'; apt update; apt install -y ccache gcc g++ libtinfo5 gperf; ./build_single.sh ${COIN} x86_64-linux-android $NPROC" - # docker run --platform linux/amd64 -v$PWD:$PWD -w $PWD --rm -it git.mrcyjanek.net/mrcyjanek/debian:buster bash -c "git config --global --add safe.directory '*'; apt update; apt install -y ccache gcc g++ libtinfo5 gperf; ./build_single.sh ${COIN} i686-linux-android $NPROC" - docker run --platform linux/amd64 -v$HOME/.cache/ccache:/root/.ccache -v$PWD:$PWD -w $PWD --rm -it git.mrcyjanek.net/mrcyjanek/debian:buster bash -c "git config --global --add safe.directory '*'; apt update; apt install -y ccache gcc g++ libtinfo5 gperf; ./build_single.sh ${COIN} armv7a-linux-androideabi $NPROC" - docker run --platform linux/amd64 -v$HOME/.cache/ccache:/root/.ccache -v$PWD:$PWD -w $PWD --rm -it git.mrcyjanek.net/mrcyjanek/debian:buster bash -c "git config --global --add safe.directory '*'; apt update; apt install -y ccache gcc g++ libtinfo5 gperf; ./build_single.sh ${COIN} aarch64-linux-android $NPROC" - popd - done -else - for COIN in monero wownero; - do - pushd ../monero_c - env -i ./build_single.sh ${COIN} x86_64-linux-android $NPROC - [[ ! "x$REMOVE_CACHES" == "x" ]] && rm -rf ${COIN}/contrib/depends/x86_64-linux-android - # ./build_single.sh ${COIN} i686-linux-android $NPROC - # [[ ! "x$REMOVE_CACHES" == "x" ]] && rm -rf ${COIN}/contrib/depends/i686-linux-android - env -i ./build_single.sh ${COIN} armv7a-linux-androideabi $NPROC - [[ ! "x$REMOVE_CACHES" == "x" ]] && rm -rf ${COIN}/contrib/depends/armv7a-linux-androideabi - env -i ./build_single.sh ${COIN} aarch64-linux-android $NPROC - [[ ! "x$REMOVE_CACHES" == "x" ]] && rm -rf ${COIN}/contrib/depends/aarch64-linux-android - - popd - unxz -f ../monero_c/release/${COIN}/x86_64-linux-android_libwallet2_api_c.so.xz - - unxz -f ../monero_c/release/${COIN}/armv7a-linux-androideabi_libwallet2_api_c.so.xz - - unxz -f ../monero_c/release/${COIN}/aarch64-linux-android_libwallet2_api_c.so.xz - [[ ! "x$REMOVE_CACHES" == "x" ]] && rm -rf ${COIN}/contrib/depends/{built,sources} - done -fi +for COIN in monero wownero; +do + pushd ../monero_c + for target in {x86_64,aarch64}-linux-android armv7a-linux-androideabi + do + if [[ -f "release/${COIN}/${target}_libwallet2_api_c.so" ]]; + then + echo "file exist, not building monero_c for ${COIN}/$target."; + else + env -i ./build_single.sh ${COIN} $target $NPROC + unxz -f ../monero_c/release/${COIN}/${target}_libwallet2_api_c.so.xz + fi + done + popd +done \ No newline at end of file diff --git a/scripts/android/build_mwebd.sh b/scripts/android/build_mwebd.sh index 4434e30f1e..cd4e2c1f49 100755 --- a/scripts/android/build_mwebd.sh +++ b/scripts/android/build_mwebd.sh @@ -16,7 +16,4 @@ cd mwebd git reset --hard 555349415f76a42ec5c76152b64c4ab9aabc448f gomobile bind -target=android -androidapi 21 . mkdir -p ../../../cw_mweb/android/libs/ -mv ./mwebd.aar $_ -# cleanup: -cd .. -rm -rf mwebd \ No newline at end of file +cp ./mwebd.aar $_ \ No newline at end of file diff --git a/scripts/ios/app_env.sh b/scripts/ios/app_env.sh index c1747c5021..079b12391f 100644 --- a/scripts/ios/app_env.sh +++ b/scripts/ios/app_env.sh @@ -19,7 +19,7 @@ MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" CAKEWALLET_VERSION="4.22.1" -CAKEWALLET_BUILD_NUMBER=288 +CAKEWALLET_BUILD_NUMBER=289 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" HAVEN_NAME="Haven" diff --git a/scripts/linux/Dockerfile.linux b/scripts/linux/Dockerfile.linux new file mode 100644 index 0000000000..c8f4d3bdef --- /dev/null +++ b/scripts/linux/Dockerfile.linux @@ -0,0 +1,148 @@ +# Usage: +# docker build . -f Dockerfile.linux -t ghcr.io/cake-tech/cake_wallet:main-linux +# docker push ghcr.io/cake-tech/cake_wallet:main-linux + +FROM --platform=linux/amd64 docker.io/debian:12 + +LABEL org.opencontainers.image.source=https://github.com/cake-tech/cake_wallet + +ENV GOLANG_VERSION=1.23.4 +# comes from https://developer.android.com/studio/#command-tools +ENV ANDROID_SDK_TOOLS_VERSION=11076708 +# https://developer.android.com/studio/releases/build-tools +ENV ANDROID_PLATFORM_VERSION=34 +ENV ANDROID_BUILD_TOOLS_VERSION=34.0.0 + +ENV FLUTTER_VERSION=3.24.0 + +# If we ever need to migrate the home directory... +RUN sed -i 's|^root:[^:]*:[^:]*:[^:]*:[^:]*:/root:|root:x:0:0:root:/root:|' /etc/passwd +# mkdir -p /root && rm -rf /root && cp -a /root /root +ENV HOME=/root +# Heavily inspired by cirrusci images +# https://github.com/cirruslabs/docker-images-android/blob/master/sdk/tools/Dockerfile +# https://github.com/cirruslabs/docker-images-android/blob/master/sdk/34/Dockerfile +# https://github.com/cirruslabs/docker-images-android/blob/master/sdk/34-ndk/Dockerfile +# https://github.com/cirruslabs/docker-images-flutter/blob/master/sdk/Dockerfile + +ENV ANDROID_HOME=/opt/android-sdk-linux \ + LANG=en_US.UTF-8 \ + LC_ALL=en_US.UTF-8 \ + LANGUAGE=en_US:en + +ENV ANDROID_SDK_ROOT=$ANDROID_HOME \ + PATH=${PATH}:${ANDROID_HOME}/cmdline-tools/latest/bin:${ANDROID_HOME}/platform-tools:${ANDROID_HOME}/emulator + +RUN set -o xtrace \ + && cd /opt \ + && apt-get update \ + && apt-get upgrade -y \ + && apt-get install -y jq \ + && apt-get install -y default-jdk \ + && apt-get install -y sudo wget zip unzip git openssh-client curl bc software-properties-common build-essential ruby-full ruby-bundler libstdc++6 libpulse0 libglu1-mesa locales lcov libsqlite3-dev --no-install-recommends \ + # for x86 emulators + && apt-get install -y libxtst6 libnss3-dev libnspr4 libxss1 libatk-bridge2.0-0 libgtk-3-0 libgdk-pixbuf2.0-0 \ + && apt-get install -y -qq xxd \ + && apt-get install -y lftp \ + && apt-get install -qq -y sqlite3 libsqlite3-dev \ + # linux desktop dependencies + && apt-get install -y clang cmake ninja-build pkg-config libgtk-3-dev \ + # monero_c dependencies + && apt-get install -y ccache build-essential autoconf libtool gperf llvm \ + # extra stuff for KVM + && apt-get install -y udev qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils \ + # for linux tests + && apt-get install -y xvfb network-manager ffmpeg x11-utils \ + && rm -rf /var/lib/apt/lists/* \ + && sh -c 'echo "en_US.UTF-8 UTF-8" > /etc/locale.gen' \ + && locale-gen \ + && update-locale LANG=en_US.UTF-8 + +# install nodejs for actions +RUN apt-get update && \ + apt-get install -y curl && \ + curl -fsSL https://deb.nodesource.com/setup_23.x | bash - && \ + apt-get install -y nodejs && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +RUN wget https://go.dev/dl/go${GOLANG_VERSION}.linux-amd64.tar.gz &&\ + rm -rf /usr/local/go &&\ + tar -C /usr/local -xzf go${GOLANG_VERSION}.linux-amd64.tar.gz + +ENV PATH=${PATH}:/usr/local/go/bin:${HOME}/go/bin +ENV GOROOT=/usr/local/go +ENV GOPATH=${HOME}/go +RUN go install golang.org/x/mobile/cmd/gomobile@latest +RUN gomobile init + +RUN wget -q https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_SDK_TOOLS_VERSION}_latest.zip -O android-sdk-tools.zip \ + && mkdir -p ${ANDROID_HOME}/cmdline-tools/ \ + && unzip -q android-sdk-tools.zip -d ${ANDROID_HOME}/cmdline-tools/ \ + && mv ${ANDROID_HOME}/cmdline-tools/cmdline-tools ${ANDROID_HOME}/cmdline-tools/latest \ + && chown -R root:root $ANDROID_HOME \ + && rm android-sdk-tools.zip \ + && echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers \ + && yes | sdkmanager --licenses \ + && wget -O /usr/bin/android-wait-for-emulator https://raw.githubusercontent.com/travis-ci/travis-cookbooks/master/community-cookbooks/android-sdk/files/default/android-wait-for-emulator \ + && chmod +x /usr/bin/android-wait-for-emulator \ + && sdkmanager platform-tools \ + && mkdir -p ${HOME}/.android \ + && touch ${HOME}/.android/repositories.cfg \ + && git config --global user.email "czarek@cakewallet.com" \ + && git config --global user.name "CakeWallet CI" + +# emulator is not available on linux/arm64 (https://issuetracker.google.com/issues/227219818) +RUN if [ $(uname -m) == "x86_64" ]; then sdkmanager emulator ; fi + +# Extra dependencies to not download them for cake wallet build +RUN yes | sdkmanager \ + "platforms;android-$ANDROID_PLATFORM_VERSION" \ + "build-tools;$ANDROID_BUILD_TOOLS_VERSION" \ + "platforms;android-33" \ + "build-tools;33.0.2" \ + "build-tools;33.0.1" \ + "build-tools;33.0.0" \ + "build-tools;35.0.0" + +ENV ANDROID_NDK_VERSION=27.2.12479018 + +# Extra ndk dependency for sp_scanner +RUN yes | sdkmanager "ndk;$ANDROID_NDK_VERSION" \ + "ndk;27.0.12077973" + +# https://github.com/ReactiveCircus/android-emulator-runner dependencies for tests +RUN yes | sdkmanager "system-images;android-29;default;x86" \ + "system-images;android-29;default;x86_64" \ + "system-images;android-31;default;x86_64" \ + "platforms;android-29" + +# fake the KVM status so android emulator doesn't complain (that much) +RUN (addgroup kvm || true) && \ + adduser root kvm && \ + mkdir -p /etc/udev/rules.d/ && \ + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | tee /etc/udev/rules.d/99-kvm4all.rules + +ENV PATH=${HOME}/.cargo/bin:${PATH} + +RUN curl https://sh.rustup.rs -sSf | bash -s -- -y && \ + cargo install cargo-ndk && \ + for target in aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android x86_64-unknown-linux-gnu; \ + do \ + rustup target add --toolchain stable $target; \ + done + + +ENV HOME=${HOME} +ENV FLUTTER_HOME=${HOME}/sdks/flutter/${FLUTTER_VERSION} +ENV FLUTTER_ROOT=$FLUTTER_HOME + +ENV PATH=${PATH}:${FLUTTER_HOME}/bin:${FLUTTER_HOME}/bin/cache/dart-sdk/bin + +RUN git clone --depth 1 --branch ${FLUTTER_VERSION} https://github.com/flutter/flutter.git ${FLUTTER_HOME} + +RUN yes | flutter doctor --android-licenses \ + && flutter doctor \ + && chown -R root:root ${FLUTTER_HOME} + +RUN flutter precache diff --git a/scripts/linux/app_env.sh b/scripts/linux/app_env.sh index f0ec8e9e60..5103eeccc5 100755 --- a/scripts/linux/app_env.sh +++ b/scripts/linux/app_env.sh @@ -15,7 +15,7 @@ fi CAKEWALLET_NAME="Cake Wallet" CAKEWALLET_VERSION="1.12.1" -CAKEWALLET_BUILD_NUMBER=42 +CAKEWALLET_BUILD_NUMBER=43 if ! [[ " ${TYPES[*]} " =~ " ${APP_LINUX_TYPE} " ]]; then echo "Wrong app type." diff --git a/scripts/linux/build_monero_all.sh b/scripts/linux/build_monero_all.sh index 5dc5125271..558423219a 100755 --- a/scripts/linux/build_monero_all.sh +++ b/scripts/linux/build_monero_all.sh @@ -1,9 +1,5 @@ #!/bin/bash - -. ./config.sh - - set -x -e cd "$(dirname "$0")" @@ -15,7 +11,15 @@ NPROC="-j$(nproc)" for COIN in monero wownero; do pushd ../monero_c - ./build_single.sh ${COIN} $(gcc -dumpmachine) $NPROC + for target in x86_64-linux-gnu + do + if [[ -f "release/${COIN}/${target}_libwallet2_api_c.so" ]]; + then + echo "file exist, not building monero_c for ${COIN}/$target."; + else + ./build_single.sh ${COIN} $target $NPROC + unxz -f ../monero_c/release/${COIN}/${target}_libwallet2_api_c.so.xz + fi + done popd - unxz -f ../monero_c/release/${COIN}/$(gcc -dumpmachine)_libwallet2_api_c.so.xz -done +done \ No newline at end of file diff --git a/scripts/macos/app_env.sh b/scripts/macos/app_env.sh index fe3d806d8d..af2b6ab1ef 100755 --- a/scripts/macos/app_env.sh +++ b/scripts/macos/app_env.sh @@ -22,7 +22,7 @@ MONERO_COM_BUNDLE_ID="com.cakewallet.monero" CAKEWALLET_NAME="Cake Wallet" CAKEWALLET_VERSION="1.15.1" -CAKEWALLET_BUILD_NUMBER=100 +CAKEWALLET_BUILD_NUMBER=101 CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet" if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then diff --git a/scripts/prepare_moneroc.sh b/scripts/prepare_moneroc.sh index c345408dd5..c0de33f6f1 100755 --- a/scripts/prepare_moneroc.sh +++ b/scripts/prepare_moneroc.sh @@ -4,9 +4,9 @@ set -x -e cd "$(dirname "$0")" -if [[ ! -d "monero_c" ]]; +if [[ ! -d "monero_c/.git" ]]; then - git clone https://github.com/mrcyjanek/monero_c --branch master + git clone https://github.com/mrcyjanek/monero_c --branch master monero_c cd monero_c git checkout af5277f96073917185864d3596e82b67bee54e78 git reset --hard diff --git a/scripts/windows/.gitignore b/scripts/windows/.gitignore new file mode 100644 index 0000000000..bb28076cdc --- /dev/null +++ b/scripts/windows/.gitignore @@ -0,0 +1 @@ +actions-runner \ No newline at end of file diff --git a/scripts/windows/Dockerfile.windows b/scripts/windows/Dockerfile.windows new file mode 100644 index 0000000000..f2a08b41ce --- /dev/null +++ b/scripts/windows/Dockerfile.windows @@ -0,0 +1,68 @@ +# Usage: +# docker build . -f Dockerfile.windows -t ghcr.io/cake-tech/cake_wallet:main-windows +# docker push ghcr.io/cake-tech/cake_wallet:main-windows + +FROM mcr.microsoft.com/windows/servercore:ltsc2022 + +ENV FLUTTER_VERSION=3.24.0 +ENV GIT_VERSION=2.47.1 +ENV VS_INSTALLED_DIR="C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools" +ENV PATH="C:\Users\ContainerAdministrator\.cargo\bin;C:\ProgramData\chocolatey\bin;C:\flutter\flutter\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\;C:\Users\ContainerAdministrator\AppData\Local\Microsoft\WindowsApps" +ENV RUNNER_VERSION=2.321.0 +ENV RUNNER_URL=https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-win-x64-${RUNNER_VERSION}.zip +ENV RUNNER_WORKDIR=_work + +RUN powershell -Command \ + curl.exe -L https://aka.ms/vs/17/release/vc_redist.x64.exe -o vc_redist.x64.exe ; \ + Start-Process -Wait -FilePath .\vc_redist.x64.exe -ArgumentList '/quiet', '/install' ; \ + Remove-Item -Force vc_redist.x64.exe + +RUN powershell -Command \ + $GIT_VERSION = [Environment]::GetEnvironmentVariable('GIT_VERSION'); \ + curl.exe -L https://github.com/git-for-windows/git/releases/download/v$($GIT_VERSION).windows.1/Git-$($GIT_VERSION)-64-bit.exe -o git_installer.exe ; \ + Start-Process -Wait -FilePath .\git_installer.exe -ArgumentList '/SILENT', '/NOICONS' ; \ + Remove-Item -Force git_installer.exe + +RUN powershell -NoProfile -ExecutionPolicy Bypass -Command \ + Set-ExecutionPolicy RemoteSigned -Scope Process; \ + [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; \ + Invoke-WebRequest https://chocolatey.org/install.ps1 -UseBasicP -OutFile install.ps1; \ + powershell -NoProfile -ExecutionPolicy Bypass -File install.ps1; \ + Remove-Item -Force install.ps1 + +RUN choco install -y visualstudio2022community +RUN choco install -y visualstudio2022-workload-nativedesktop +RUN choco install -y nodejs +RUN choco install -y go +RUN choco install -y 7zip + +RUN powershell -Command \ + curl.exe -L https://win.rustup.rs -o rustup-init.exe; \ + Start-Process -Wait -FilePath .\rustup-init.exe -ArgumentList "-y"; \ + Remove-Item -Force .\rustup-init.exe + +RUN powershell -Command \ + curl.exe -L https://dist.nuget.org/win-x86-commandline/latest/nuget.exe -o C:\Windows\System32\nuget.exe + +RUN powershell -Command \ + $FLUTTER_VERSION = [Environment]::GetEnvironmentVariable('FLUTTER_VERSION'); \ + curl.exe -L https://storage.googleapis.com/flutter_infra_release/releases/stable/windows/flutter_windows_$($FLUTTER_VERSION)-stable.zip -o flutter.zip ; \ + 7z x flutter.zip -oC:\flutter -bsp1 -bse1 ; \ + Remove-Item -Force flutter.zip + +RUN flutter precache + +WORKDIR C:\\actions-runner + +RUN powershell -Command \ + curl.exe -L $env:RUNNER_URL -o 'actions-runner.zip'; \ + 7z x actions-runner.zip -oC:\actions-runner -bsp1 -bse1 ; \ + Remove-Item -Path 'actions-runner.zip' + +COPY actions-runner/.credentials /actions-runner/.credentials +COPY actions-runner/.credentials_rsaparams /actions-runner/.credentials_rsaparams +COPY actions-runner/.runner /actions-runner/.runner + +COPY ci_entrypoint.ps1 /actions-runner/ci_entrypoint.ps1 + +ENTRYPOINT ["powershell", "-File", "ci_entrypoint.ps1"] \ No newline at end of file diff --git a/scripts/windows/ci_entrypoint.ps1 b/scripts/windows/ci_entrypoint.ps1 new file mode 100644 index 0000000000..d68d0f5ecc --- /dev/null +++ b/scripts/windows/ci_entrypoint.ps1 @@ -0,0 +1,5 @@ +$runnerDir = "C:\actions-runner" +$runCmd = "$runnerDir\run.cmd" + +Write-Host "Starting the runner..." +& $runCmd \ No newline at end of file diff --git a/scripts/windows/ci_register.ps1 b/scripts/windows/ci_register.ps1 new file mode 100644 index 0000000000..a39048cdb6 --- /dev/null +++ b/scripts/windows/ci_register.ps1 @@ -0,0 +1,30 @@ +# Variables for paths and config +$runnerDir = "C:\actions-runner" +$configCmd = "$runnerDir\config.cmd" +$runCmd = "$runnerDir\run.cmd" + +# Check required environment variables +if (-not $env:RUNNER_TOKEN) { + Write-Error "RUNNER_TOKEN is not set. Exiting." + exit 1 +} +if (-not $env:RUNNER_REPO_URL) { + Write-Error "RUNNER_REPO_URL is not set. Exiting." + exit 1 +} +$env:RUNNER_NAME = "windows-amd64-cake" +$env:RUNNER_WORKDIR = "_work" + +# Register the runner +Write-Host "Registering the runner..." +Write-Host "--url $env:RUNNER_REPO_URL" +Write-Host "--token $env:RUNNER_TOKEN" +Write-Host "--name $env:RUNNER_NAME" +Write-Host "--work $env:RUNNER_WORKDIR" + +& $configCmd --url $env:RUNNER_REPO_URL ` + --token $env:RUNNER_TOKEN ` + --name $env:RUNNER_NAME ` + --work $env:RUNNER_WORKDIR ` + --unattended ` + --replace \ No newline at end of file