diff --git a/.github/actions/ci-setup-android-files/action.yml b/.github/actions/ci-setup-android-files/action.yml
new file mode 100644
index 000000000..1ddcdf980
--- /dev/null
+++ b/.github/actions/ci-setup-android-files/action.yml
@@ -0,0 +1,58 @@
+name: 'Setup Android Files'
+description: 'Setup required files for building an Android project'
+
+inputs:
+ root-path:
+ description: 'Path of the react-native project'
+ required: true
+ google-services-file:
+ description: 'Google Services Json file content, base64 encoded'
+ required: false
+ secrets-file:
+ description: 'Secrets properties file content'
+ required: true
+ keystore:
+ description: 'Keystore file content, base64 encoded'
+ required: true
+ keystore-name:
+ description: 'Keystore file name'
+ required: true
+ sentry-file:
+ description: 'Sentry file content'
+ required: true
+ project-id:
+ description: 'Reown Cloud ID'
+ required: true
+ relay-url:
+ description: 'Relay URL'
+ required: true
+ sentry-dsn:
+ description: 'Sentry DSN'
+ required: true
+ release-type:
+ description: 'Release type of the project (debug/internal/production)'
+ default: 'debug'
+
+runs:
+ using: "composite"
+ steps:
+ - name: Create Google Services Json File
+ if: ${{ inputs.google-services-file != '' }}
+ shell: bash
+ run: echo "${{ inputs.google-services-file }}" | base64 --decode > ${{ inputs.root-path }}/android/app/google-services.json
+
+ - name: Create secrets.properties File
+ shell: bash
+ run: echo "${{ inputs.secrets-file }}" > ${{ inputs.root-path }}/android/secrets.properties
+
+ - name: Decode Keystore
+ shell: bash
+ run: echo "${{ inputs.keystore }}" | base64 --decode > ${{ inputs.root-path }}/android/app/${{ inputs.keystore-name }}.keystore
+
+ - name: Create Sentry File
+ shell: bash
+ run: echo "${{ inputs.sentry-file }}" > ${{ inputs.root-path }}/android/sentry.properties
+
+ - name: Create Env File
+ shell: bash
+ run: echo -e "ENV_PROJECT_ID=${{ inputs.project-id }}\nENV_RELAY_URL=${{ inputs.relay-url }}\nENV_SENTRY_DSN=${{ inputs.sentry-dsn }}\nENV_SENTRY_TAG=${{ inputs.release-type }}" >> ${{ inputs.root-path }}/.env.${{ inputs.release-type }}
\ No newline at end of file
diff --git a/.github/actions/ci-setup-ios-files/action.yml b/.github/actions/ci-setup-ios-files/action.yml
new file mode 100644
index 000000000..c3007b92e
--- /dev/null
+++ b/.github/actions/ci-setup-ios-files/action.yml
@@ -0,0 +1,41 @@
+name: 'Setup iOS Files'
+description: 'Setup required files for building an iOS project'
+
+inputs:
+ root-path:
+ description: 'Path of the react-native project'
+ required: true
+ google-services-file:
+ description: 'Google Services Plist file content, base64 encoded'
+ required: false
+ sentry-file:
+ description: 'Sentry file content'
+ required: true
+ project-id:
+ description: 'WalletConnect Cloud ID'
+ required: true
+ relay-url:
+ description: 'WalletConnect Relay URL'
+ required: false
+ sentry-dsn:
+ description: 'Sentry DSN'
+ required: true
+ release-type:
+ description: 'Release type of the project (debug/internal/production)'
+ default: 'debug'
+
+runs:
+ using: "composite"
+ steps:
+ - name: Create .env file
+ shell: bash
+ run: echo -e "ENV_PROJECT_ID=${{ inputs.project-id }}\nENV_RELAY_URL=${{ inputs.relay-url }}\nENV_SENTRY_DSN=${{ inputs.sentry-dsn }}\nENV_SENTRY_TAG=${{ inputs.release-type }}" >> ${{ inputs.root-path }}/.env.${{ inputs.release-type }}
+
+ - name: Create sentry.properties file
+ shell: bash
+ run: echo "${{ inputs.sentry-file }}" > ${{ inputs.root-path }}/ios/sentry.properties
+
+ - name: Create GoogleService-Info.plist file
+ if: ${{ inputs.google-services-file != '' }}
+ shell: bash
+ run: echo "${{ inputs.google-services-file }}" | base64 --decode > ${{ inputs.root-path }}/ios/GoogleService-Info.plist
\ No newline at end of file
diff --git a/.github/workflows/ci_e2e_relay.yaml b/.github/workflows/ci_e2e_relay.yaml
new file mode 100644
index 000000000..e17ddf590
--- /dev/null
+++ b/.github/workflows/ci_e2e_relay.yaml
@@ -0,0 +1,382 @@
+name: CI E2E Relay
+# Cross-app WalletConnect *relay* E2E (connect / sign / switch-chain / disconnect /
+# restore): the native rn_cli_wallet (app under test) drives the appkit-wagmi WEB
+# dapp (served from source in the device browser) via Maestro, one device, one
+# flow. Separate from the WC Pay E2E (ci_e2e_walletkit.yaml). See
+# .context/RELAY_E2E_PLAN.md.
+#
+# Both apps are built/served from the PR's source: the wallet natively (simulator/
+# emulator) and the dapp as a static web export served on the runner. Flows live
+# in .maestro/relay/ (tag: relay-walletkit). Wallet build steps mirror the proven
+# walletkit-build-and-maestro composite (kept self-contained, no pay baggage);
+# the dapp web export mirrors that composite's web leg.
+
+permissions:
+ contents: read
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+on:
+ schedule:
+ - cron: '0 9 * * *' # Daily canary.
+ pull_request:
+ branches: [main]
+ types: [opened, synchronize, reopened, ready_for_review]
+ paths:
+ - 'wallets/rn_cli_wallet/**'
+ - 'dapps/appkit-wagmi/**'
+ - '.maestro/relay/**'
+ - '.github/workflows/ci_e2e_relay.yaml'
+ workflow_dispatch:
+ inputs:
+ platform:
+ description: 'Platform to run'
+ required: false
+ default: ios
+ type: choice
+ options: [ios, android]
+
+env:
+ APP_ID: com.walletconnect.web3wallet.rnsample.internal
+ WALLET_ROOT: wallets/rn_cli_wallet
+ DAPP_ROOT: dapps/appkit-wagmi
+
+jobs:
+ e2e-relay-walletkit-ios:
+ # macOS is expensive but it's the validated platform (the Home->browser resume
+ # is proven on the iOS simulator). Runs on PR / daily cron / dispatch.
+ if: >-
+ (github.event_name == 'schedule')
+ || (github.event_name == 'workflow_dispatch' && inputs.platform == 'ios')
+ || (github.event_name == 'pull_request' && github.event.pull_request.draft == false)
+ name: E2E Relay WalletKit - iOS
+ runs-on: macos-latest-xlarge
+ timeout-minutes: 60
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 22
+
+ # --- Wallet (native, app under test) ---
+ - name: Install wallet dependencies
+ working-directory: ${{ env.WALLET_ROOT }}
+ run: yarn install --immutable
+
+ - name: Write wallet .env
+ working-directory: ${{ env.WALLET_ROOT }}
+ env:
+ WALLET_ENV_FILE: ${{ secrets.WALLETKIT_ENV_FILE }}
+ WALLET_PRIVATE_KEY: ${{ secrets.TEST_WALLET_PRIVATE_KEY }}
+ run: |
+ set -euo pipefail
+ printf '%s\n' "$WALLET_ENV_FILE" > .env
+ {
+ echo "EXPO_PUBLIC_TEST_PRIVATE_KEY=$WALLET_PRIVATE_KEY"
+ echo "EXPO_PUBLIC_TEST_MODE=true"
+ echo "SENTRY_DISABLE_AUTO_UPLOAD=true"
+ } >> .env
+
+ - name: Select Xcode 26.2
+ run: |
+ sudo xcode-select -s /Applications/Xcode_26.2.app/Contents/Developer
+ xcodebuild -version
+
+ - name: Install Ruby
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: 3.3.0
+ bundler-cache: true
+ working-directory: ${{ env.WALLET_ROOT }}
+
+ - name: Expo Prebuild (iOS)
+ working-directory: ${{ env.WALLET_ROOT }}
+ run: APP_VARIANT=internal npx expo prebuild --platform ios --no-install
+
+ - name: Set Xcode paths
+ run: |
+ XCODEPROJ=$(find "$WALLET_ROOT/ios" -maxdepth 1 -name "*.xcodeproj" -print -quit)
+ echo "XCODE_PROJECT_PATH=$XCODEPROJ" >> "$GITHUB_ENV"
+ echo "XCWORKSPACE_PATH=$WALLET_ROOT/ios/ReactNWallet.xcworkspace" >> "$GITHUB_ENV"
+
+ - name: Cache Pods
+ id: pods-cache
+ uses: actions/cache@v4
+ with:
+ path: ${{ env.WALLET_ROOT }}/ios/Pods
+ key: ${{ runner.os }}-pods-relay-${{ hashFiles(format('{0}/ios/Podfile.lock', env.WALLET_ROOT)) }}
+
+ - name: Build wallet for Simulator
+ uses: maierj/fastlane-action@v3.0.0
+ with:
+ lane: build_for_simulator
+ env:
+ SCHEME: ReactNWallet
+ BUNDLE_ID: com.walletconnect.web3wallet.rnsample.internal
+ PODFILE_PATH: ${{ env.WALLET_ROOT }}/ios/Podfile
+ XCWORKSPACE_PATH: ${{ env.XCWORKSPACE_PATH }}
+ XCODE_PROJECT_PATH: ${{ env.XCODE_PROJECT_PATH }}
+ PROJECT_TYPE: wallet
+ RELEASE_TYPE: internal
+ BUILD_FOR_SIMULATOR: true
+ PODS_CACHE_HIT: ${{ steps.pods-cache.outputs.cache-hit }}
+ SENTRY_DISABLE_AUTO_UPLOAD: true
+
+ - name: Find .app path
+ run: echo "APP_PATH=$(cat build/app_path.txt)" >> "$GITHUB_ENV"
+
+ - name: Boot iOS Simulator
+ run: |
+ DEVICE_TYPE=$(xcrun simctl list devicetypes -j | \
+ jq -r '.devicetypes[] | select(.productFamily == "iPhone") | .identifier' | tail -1)
+ DEVICE_ID=$(xcrun simctl create "E2E-iPhone" "$DEVICE_TYPE" 2>/dev/null || \
+ xcrun simctl list devices available -j | \
+ jq -r '.devices | to_entries[] | select(.key | contains("iOS")) | .value[] | select(.name | startswith("iPhone")) | .udid' | head -1)
+ xcrun simctl boot "$DEVICE_ID" || true
+ xcrun simctl bootstatus "$DEVICE_ID" -b
+ xcrun simctl spawn "$DEVICE_ID" defaults write com.apple.UIKit UIAnimationDragCoefficient 0 || true
+ echo "DEVICE_ID=$DEVICE_ID" >> "$GITHUB_ENV"
+
+ - name: Install app on Simulator
+ run: xcrun simctl install "$DEVICE_ID" "$APP_PATH"
+
+ # --- Dapp (web, counterparty) — static export served on the runner ---
+ - name: Install dapp dependencies
+ working-directory: ${{ env.DAPP_ROOT }}
+ run: yarn install --immutable
+
+ - name: Write dapp .env
+ working-directory: ${{ env.DAPP_ROOT }}
+ env:
+ APPKIT_ENV_FILE: ${{ secrets.APPKIT_ENV_FILE }}
+ run: |
+ set -euo pipefail
+ printf '%s\n' "$APPKIT_ENV_FILE" > .env
+ {
+ echo "EXPO_PUBLIC_TEST_MODE=true"
+ echo "SENTRY_DISABLE_AUTO_UPLOAD=true"
+ } >> .env
+
+ - name: Export + serve dapp (web)
+ working-directory: ${{ env.DAPP_ROOT }}
+ run: |
+ set -euo pipefail
+ npx expo export --platform web --output-dir dist
+ # `serve -s` does SPA fallback; background and wait until ready. The iOS
+ # simulator reaches the runner's localhost directly.
+ npx --yes serve -s dist -l 8081 > "${RUNNER_TEMP}/dapp-serve.log" 2>&1 &
+ for i in $(seq 1 60); do
+ curl -fsS -o /dev/null http://localhost:8081 && { echo "dapp up"; break; }
+ sleep 1
+ done
+ curl -fsS -o /dev/null http://localhost:8081 || { echo "dapp serve failed:"; cat "${RUNNER_TEMP}/dapp-serve.log"; exit 1; }
+
+ - name: Install Maestro
+ uses: WalletConnect/actions/maestro/setup@4be7c0112f9651fc63b18c137650b75d2fc9b200
+
+ - name: Run Maestro relay tests (iOS)
+ id: maestro
+ run: |
+ xcrun simctl launch "$DEVICE_ID" "$APP_ID" || true
+ mkdir -p maestro-artifacts
+ "$HOME/.maestro/bin/maestro" test \
+ --env APP_ID="$APP_ID" \
+ --env DAPP_URL="http://localhost:8081" \
+ --include-tags relay-walletkit \
+ --test-output-dir maestro-artifacts \
+ --debug-output "${RUNNER_TEMP}/maestro-debug" \
+ .maestro/relay
+
+ - name: Upload Maestro artifacts (iOS)
+ if: ${{ !cancelled() }}
+ continue-on-error: true
+ uses: actions/upload-artifact@v4
+ with:
+ name: maestro-relay-walletkit-ios-artifacts
+ path: |
+ maestro-artifacts/**
+ ${{ runner.temp }}/dapp-serve.log
+ if-no-files-found: warn
+ retention-days: 14
+
+ e2e-relay-walletkit-android:
+ # UNVERIFIED on Android: the browser is Chrome (not Safari) and the emulator
+ # reaches the runner via 10.0.2.2 — the _focus_browser return step and DAPP_URL
+ # need validating on the emulator. Dispatch-only so it can't red-flag PRs until
+ # proven; promote to PR/schedule once green.
+ if: github.event_name == 'workflow_dispatch' && inputs.platform == 'android'
+ name: E2E Relay WalletKit - Android (unverified)
+ runs-on: ubuntu-latest
+ timeout-minutes: 60
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 22
+
+ - name: Install wallet dependencies
+ working-directory: ${{ env.WALLET_ROOT }}
+ run: yarn install --immutable
+
+ - name: Write wallet .env
+ working-directory: ${{ env.WALLET_ROOT }}
+ env:
+ WALLET_ENV_FILE: ${{ secrets.WALLETKIT_ENV_FILE }}
+ WALLET_PRIVATE_KEY: ${{ secrets.TEST_WALLET_PRIVATE_KEY }}
+ run: |
+ set -euo pipefail
+ printf '%s\n' "$WALLET_ENV_FILE" > .env
+ {
+ echo "EXPO_PUBLIC_TEST_PRIVATE_KEY=$WALLET_PRIVATE_KEY"
+ echo "EXPO_PUBLIC_TEST_MODE=true"
+ echo "SENTRY_DISABLE_AUTO_UPLOAD=true"
+ } >> .env
+
+ - name: Install Java 17
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'zulu'
+ java-version: '17'
+ architecture: x86_64
+
+ - name: Accept Android Licenses
+ uses: SimonMarquis/android-accept-licenses@8d8deab5b7ab2aaea9ed69f0b802e53173f21fd1 # v1
+
+ - name: Install Android NDK 27.0.12077973
+ run: |
+ set +o pipefail
+ yes | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --install "ndk;27.0.12077973"
+
+ - name: Expo Prebuild (Android)
+ working-directory: ${{ env.WALLET_ROOT }}
+ run: npx expo prebuild --platform android --no-install
+
+ - name: Add Secrets file
+ working-directory: ${{ env.WALLET_ROOT }}
+ env:
+ ANDROID_SECRETS_FILE: ${{ secrets.ANDROID_SECRETS_FILE }}
+ run: printf '%s\n' "$ANDROID_SECRETS_FILE" > android/secrets.properties
+
+ - name: Add Keystore file
+ working-directory: ${{ env.WALLET_ROOT }}
+ env:
+ ANDROID_KEYSTORE_BASE64: ${{ secrets.WC_INTERNAL_KEYSTORE }}
+ ANDROID_KEYSTORE_NAME: ${{ vars.WC_INTERNAL_KEYSTORE_NAME }}
+ run: printf '%s' "$ANDROID_KEYSTORE_BASE64" | base64 --decode >> "android/app/${ANDROID_KEYSTORE_NAME}.keystore"
+
+ - name: Optimize Gradle for CI
+ working-directory: ${{ env.WALLET_ROOT }}
+ run: |
+ {
+ echo "org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m"
+ echo "org.gradle.parallel=true"
+ echo "org.gradle.caching=true"
+ echo "reactNativeArchitectures=x86_64"
+ } >> android/gradle.properties
+
+ - name: Cache Gradle
+ uses: actions/cache@v4
+ with:
+ path: |
+ ~/.gradle/caches
+ ~/.gradle/wrapper
+ ${{ env.WALLET_ROOT }}/android/.gradle
+ ${{ env.WALLET_ROOT }}/android/app/.cxx
+ ${{ env.WALLET_ROOT }}/android/app/build/generated
+ key: ${{ runner.os }}-gradle-relay-${{ hashFiles(format('{0}/android/**/*.gradle*', env.WALLET_ROOT), format('{0}/android/**/gradle-wrapper.properties', env.WALLET_ROOT)) }}
+ restore-keys: |
+ ${{ runner.os }}-gradle-relay-
+
+ - name: Build wallet APK
+ working-directory: ${{ env.WALLET_ROOT }}
+ run: |
+ export SENTRY_DISABLE_AUTO_UPLOAD=true
+ yarn run android:build:internal
+
+ - name: Free disk space
+ run: |
+ sudo rm -rf /usr/share/dotnet /usr/local/lib/android/sdk/ndk /opt/ghc /usr/local/share/boost
+ sudo apt-get clean
+ df -h
+
+ - name: Enable KVM
+ run: |
+ echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
+ sudo udevadm control --reload-rules
+ sudo udevadm trigger --name-match=kvm
+
+ # --- Dapp (web, counterparty) — served on the runner; emulator uses 10.0.2.2 ---
+ - name: Install dapp dependencies
+ working-directory: ${{ env.DAPP_ROOT }}
+ run: yarn install --immutable
+
+ - name: Write dapp .env
+ working-directory: ${{ env.DAPP_ROOT }}
+ env:
+ APPKIT_ENV_FILE: ${{ secrets.APPKIT_ENV_FILE }}
+ run: |
+ set -euo pipefail
+ printf '%s\n' "$APPKIT_ENV_FILE" > .env
+ {
+ echo "EXPO_PUBLIC_TEST_MODE=true"
+ echo "SENTRY_DISABLE_AUTO_UPLOAD=true"
+ } >> .env
+
+ - name: Export + serve dapp (web)
+ working-directory: ${{ env.DAPP_ROOT }}
+ run: |
+ set -euo pipefail
+ npx expo export --platform web --output-dir dist
+ npx --yes serve -s dist -l 8081 > "${RUNNER_TEMP}/dapp-serve.log" 2>&1 &
+ for i in $(seq 1 60); do
+ curl -fsS -o /dev/null http://localhost:8081 && { echo "dapp up"; break; }
+ sleep 1
+ done
+ curl -fsS -o /dev/null http://localhost:8081 || { echo "dapp serve failed:"; cat "${RUNNER_TEMP}/dapp-serve.log"; exit 1; }
+
+ - name: Install Maestro
+ uses: WalletConnect/actions/maestro/setup@4be7c0112f9651fc63b18c137650b75d2fc9b200
+
+ - name: Run Maestro relay tests (Android)
+ id: maestro
+ uses: reactivecircus/android-emulator-runner@70f4dee990796918b78d040e3278474bdbd348a7 # v2
+ env:
+ APK_PATH: ${{ env.WALLET_ROOT }}/android/app/build/outputs/apk/internal/app-internal.apk
+ with:
+ api-level: 34
+ arch: x86_64
+ target: default
+ avd-name: test_device
+ ram-size: 4096M
+ disk-size: 4096M
+ heap-size: 576M
+ emulator-boot-timeout: 900
+ force-avd-creation: false
+ emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -no-snapshot-load
+ disable-animations: true
+ # `adb reverse` maps the emulator's localhost:8081 -> the runner's dapp
+ # server. DAPP_URL must be http://localhost:8081 (NOT 10.0.2.2): Chrome
+ # only allows navigator.clipboard.writeText (the dapp's Copy link) from a
+ # secure context, and localhost qualifies while 10.0.2.2 does not.
+ script: |
+ adb install "$APK_PATH"; adb reverse tcp:8081 tcp:8081; mkdir -p maestro-artifacts; "$HOME/.maestro/bin/maestro" test --env APP_ID="$APP_ID" --env DAPP_URL="http://localhost:8081" --include-tags relay-walletkit --test-output-dir maestro-artifacts --debug-output "${RUNNER_TEMP}/maestro-debug" .maestro/relay
+
+ - name: Upload Maestro artifacts (Android)
+ if: ${{ !cancelled() }}
+ continue-on-error: true
+ uses: actions/upload-artifact@v4
+ with:
+ name: maestro-relay-walletkit-android-artifacts
+ path: |
+ maestro-artifacts/**
+ ${{ runner.temp }}/dapp-serve.log
+ if-no-files-found: warn
+ retention-days: 14
diff --git a/.github/workflows/ci_e2e_tests_android.yaml b/.github/workflows/ci_e2e_tests_android.yaml
index 80473c7ef..48f03911f 100644
--- a/.github/workflows/ci_e2e_tests_android.yaml
+++ b/.github/workflows/ci_e2e_tests_android.yaml
@@ -12,4 +12,4 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Do nothing
- run: echo "This is a placeholder workflow to enable manual triggers from feature branches."
\ No newline at end of file
+ run: echo "This is a placeholder workflow to enable manual triggers from feature branches."
diff --git a/.github/workflows/ci_e2e_tests_ios.yaml b/.github/workflows/ci_e2e_tests_ios.yaml
index 92d105670..cd75b8ed1 100644
--- a/.github/workflows/ci_e2e_tests_ios.yaml
+++ b/.github/workflows/ci_e2e_tests_ios.yaml
@@ -12,4 +12,4 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Do nothing
- run: echo "This is a placeholder workflow to enable manual triggers from feature branches."
\ No newline at end of file
+ run: echo "This is a placeholder workflow to enable manual triggers from feature branches."
diff --git a/.maestro/native/connect_confirm.yaml b/.maestro/native/connect_confirm.yaml
index 5bf45da40..a3d3b0286 100644
--- a/.maestro/native/connect_confirm.yaml
+++ b/.maestro/native/connect_confirm.yaml
@@ -6,8 +6,24 @@ name: React Native Dapp to React Native Wallet Connection Confirmed
permissions:
all: allow
- startRecording: "Native Connection Confirmed"
-- tapOn: "Connect"
-- tapOn: "React Native Wallet"
-- tapOn: "Approve"
+- tapOn:
+ id: "connect-button"
+- tapOn:
+ id: "all-wallets"
+- waitForAnimationToEnd
+- tapOn: "React"
+- runFlow:
+ when:
+ visible: "Open"
+ platform: iOS
+ commands:
+ - tapOn: "Open"
+- extendedWaitUntil:
+ visible:
+ id: "approve-button"
+ timeout: 10000
+- tapOn:
+ id: "approve-button"
+- waitForAnimationToEnd
- assertVisible: "Sign message"
- stopRecording
\ No newline at end of file
diff --git a/.maestro/native/connect_reject.yaml b/.maestro/native/connect_reject.yaml
index 2015895ff..2e3e921ff 100644
--- a/.maestro/native/connect_reject.yaml
+++ b/.maestro/native/connect_reject.yaml
@@ -1,11 +1,29 @@
appId: com.walletconnect.web3modal.rnsample.internal
-name: React Native Dapp to React Native Wallet Connection Confirmed
+name: React Native Dapp to React Native Wallet Connection Rejected
---
- clearState
- launchApp:
permissions:
all: allow
-- tapOn: "Connect"
-- tapOn: "React Native Wallet"
-- tapOn: "Reject"
-- assertVisible: "Connection declined"
\ No newline at end of file
+- startRecording: "Native to Native Connection Rejected"
+- tapOn:
+ id: "connect-button"
+- tapOn:
+ id: "all-wallets"
+- waitForAnimationToEnd
+- tapOn: "React"
+- runFlow:
+ when:
+ visible: "Open"
+ platform: iOS
+ commands:
+ - tapOn: "Open"
+- extendedWaitUntil:
+ visible:
+ id: "reject-button"
+ timeout: 10000
+- tapOn:
+ id: "reject-button"
+- waitForAnimationToEnd
+- assertVisible: "Connection declined"
+- stopRecording
\ No newline at end of file
diff --git a/.maestro/native/personal_sign_confirm.yaml b/.maestro/native/personal_sign_confirm.yaml
index 31ec415eb..48ca8dd36 100644
--- a/.maestro/native/personal_sign_confirm.yaml
+++ b/.maestro/native/personal_sign_confirm.yaml
@@ -5,7 +5,21 @@ name: React Native Dapp to React Native Wallet Personal Sign Confirmed
permissions:
all: allow
- startRecording: "Native personal_sign Confirmed"
+- extendedWaitUntil:
+ visible: "Sign message"
+ timeout: 10000
- tapOn: "Sign message"
-- tapOn: "Approve"
+- runFlow:
+ when:
+ visible: "Open"
+ platform: iOS
+ commands:
+ - tapOn: "Open"
+- extendedWaitUntil:
+ visible:
+ id: "approve-button"
+ timeout: 10000
+- tapOn:
+ id: "approve-button"
- assertVisible: "Request response"
- stopRecording
\ No newline at end of file
diff --git a/.maestro/native/personal_sign_reject.yaml b/.maestro/native/personal_sign_reject.yaml
index c102a41f5..5ccd5d517 100644
--- a/.maestro/native/personal_sign_reject.yaml
+++ b/.maestro/native/personal_sign_reject.yaml
@@ -5,7 +5,21 @@ name: React Native Dapp to React Native Wallet Personal Sign Rejected
permissions:
all: allow
- startRecording: "Native personal_sign Rejected"
+- extendedWaitUntil:
+ visible: "Sign message"
+ timeout: 10000
- tapOn: "Sign message"
-- tapOn: "Reject"
+- runFlow:
+ when:
+ visible: "Open"
+ platform: iOS
+ commands:
+ - tapOn: "Open"
+- extendedWaitUntil:
+ visible:
+ id: "reject-button"
+ timeout: 10000
+- tapOn:
+ id: "reject-button"
- assertVisible: "Request failure"
- stopRecording
\ No newline at end of file
diff --git a/.maestro/relay/_connect_walletkit.yaml b/.maestro/relay/_connect_walletkit.yaml
new file mode 100644
index 000000000..72986bf25
--- /dev/null
+++ b/.maestro/relay/_connect_walletkit.yaml
@@ -0,0 +1,56 @@
+# Reusable connect handshake — NOT a standalone test.
+# Untagged on purpose: CI runs `--include-tags relay-walletkit`, so tag filtering
+# never picks this up directly; the tagged flows pull it in via `runFlow`.
+#
+# Native wallet (app under test) <-> WEB appkit dapp (counterparty, in the device
+# browser). The dapp opens the QR view and copies the wc: URI (Web Clipboard API);
+# the native wallet pastes it and approves. To return to the dapp we RESUME Safari
+# (Home + app icon) rather than relaunching it (`launchApp`/`openLink` reload the
+# tab and drop the pending connection) — a resumed tab keeps its live WC client,
+# which fetches the queued session settlement and completes the connection.
+#
+# Env:
+# APP_ID native wallet bundle id (the app under test)
+# DAPP_URL appkit-wagmi WEB dapp base URL (served locally or on Vercel)
+appId: ${APP_ID}
+---
+# Clean wallet so sessions don't accumulate across runs (the disconnect flows
+# need to reach an empty list). The OS clipboard survives clearState.
+- launchApp:
+ appId: ${APP_ID}
+ clearState: true
+ permissions:
+ all: allow
+# --- WEB dapp: open the QR view and copy the wc: URI ---
+- openLink:
+ link: ${DAPP_URL}
+ browser: true
+- extendedWaitUntil:
+ visible: 'AppKit + Multichain'
+ timeout: 60000
+- tapOn: 'Open QR (E2E)'
+- extendedWaitUntil:
+ visible: 'Copy link'
+ timeout: 30000
+- tapOn: 'Copy link'
+# --- Native wallet: pair by pasting the copied wc: URI ---
+- launchApp:
+ appId: ${APP_ID}
+- tapOn:
+ id: 'button-scan'
+- tapOn:
+ id: 'button-paste-url'
+# iOS shows a one-time "… would like to paste" system dialog.
+- runFlow:
+ when:
+ visible: 'Allow Paste'
+ commands:
+ - tapOn: 'Allow Paste'
+- extendedWaitUntil:
+ visible:
+ id: 'approve-button'
+ timeout: 60000
+- tapOn:
+ id: 'approve-button'
+# --- Return to the dapp by RESUMING the browser (Home + icon), not relaunching ---
+- runFlow: _focus_browser.yaml
diff --git a/.maestro/relay/_focus_browser.yaml b/.maestro/relay/_focus_browser.yaml
new file mode 100644
index 000000000..265f88940
--- /dev/null
+++ b/.maestro/relay/_focus_browser.yaml
@@ -0,0 +1,28 @@
+# Resume the device browser tab (NOT relaunch) so the dapp's live WalletConnect
+# client receives the queued session settlement / response. Relaunching
+# (openLink/launchApp) reloads the tab and drops the pending in-memory state.
+#
+# iOS: Home, then tap the Safari icon (resumes the app + tab).
+# Android: Home, then tap the Chrome icon (resumes Chrome + tab). Back isn't
+# reliable here — the wallet's own navigation consumes it instead of
+# returning to the browser.
+appId: ${APP_ID}
+---
+- runFlow:
+ when:
+ platform: iOS
+ commands:
+ - pressKey: Home
+ - extendedWaitUntil:
+ visible: 'Safari'
+ timeout: 10000
+ - tapOn: 'Safari'
+- runFlow:
+ when:
+ platform: Android
+ commands:
+ - pressKey: Home
+ - extendedWaitUntil:
+ visible: 'Chrome'
+ timeout: 10000
+ - tapOn: 'Chrome'
diff --git a/.maestro/relay/config.yaml b/.maestro/relay/config.yaml
new file mode 100644
index 000000000..140d6f6b1
--- /dev/null
+++ b/.maestro/relay/config.yaml
@@ -0,0 +1,14 @@
+# Maestro config for the relay (dapp <-> wallet) E2E suite.
+#
+# maestro test --include-tags relay-walletkit .maestro/relay
+# native rn_cli_wallet (app under test) <-> WEB appkit-wagmi dapp (device browser).
+#
+# Tag filtering keeps the untagged `_*.yaml` subflows (_connect_walletkit,
+# _focus_browser) from running as standalone tests; they're pulled in via runFlow.
+#
+# Suite is intentionally separate from the WC Pay suite (shared flows copied in
+# by ci_e2e_walletkit.yaml) and from the legacy .maestro/native + .maestro/web
+# flows.
+baselineBranch: main
+flows:
+ - '*.yaml'
diff --git a/.maestro/relay/connect_walletkit.yaml b/.maestro/relay/connect_walletkit.yaml
new file mode 100644
index 000000000..7f7365d61
--- /dev/null
+++ b/.maestro/relay/connect_walletkit.yaml
@@ -0,0 +1,12 @@
+# Flow 1 — connect: dapp initiates, wallet approves, session established.
+appId: ${APP_ID}
+tags:
+ - relay-walletkit
+ - connect
+---
+- runFlow: _connect_walletkit.yaml
+# Web dapp reflects the connected account (matched by text — Safari webview
+# doesn't expose testIDs).
+- extendedWaitUntil:
+ visible: 'Connected to'
+ timeout: 60000
diff --git a/.maestro/relay/disconnect_dapp_walletkit.yaml b/.maestro/relay/disconnect_dapp_walletkit.yaml
new file mode 100644
index 000000000..ff91a55ad
--- /dev/null
+++ b/.maestro/relay/disconnect_dapp_walletkit.yaml
@@ -0,0 +1,24 @@
+# Flow 5 — disconnect from the dapp side: dapp tears down the session; the
+# wallet receives session_delete and drops it. Uses the dapp's TEST_MODE
+# "Disconnect (E2E)" button (the AppKit account button isn't text-matchable on web).
+appId: ${APP_ID}
+tags:
+ - relay-walletkit
+ - disconnect-dapp
+---
+- runFlow: _connect_walletkit.yaml
+- extendedWaitUntil:
+ visible: 'Connected to'
+ timeout: 60000
+# Disconnect from the dapp.
+- tapOn: 'Disconnect (E2E)'
+- extendedWaitUntil:
+ notVisible: 'Connected to'
+ timeout: 30000
+# Wallet side dropped the session too (session_delete over the relay).
+- launchApp:
+ appId: ${APP_ID}
+- tapOn: 'Connected apps'
+- extendedWaitUntil:
+ visible: 'No connected apps yet'
+ timeout: 30000
diff --git a/.maestro/relay/disconnect_wallet_walletkit.yaml b/.maestro/relay/disconnect_wallet_walletkit.yaml
new file mode 100644
index 000000000..18fa9dd0f
--- /dev/null
+++ b/.maestro/relay/disconnect_wallet_walletkit.yaml
@@ -0,0 +1,24 @@
+# Flow 6 — disconnect from the wallet side: wallet tears down the session via
+# the session detail modal.
+appId: ${APP_ID}
+tags:
+ - relay-walletkit
+ - disconnect-wallet
+---
+- runFlow: _connect_walletkit.yaml
+# Foreground the wallet and open the connected app's detail.
+- launchApp:
+ appId: ${APP_ID}
+- tapOn: 'Connected apps'
+- tapOn:
+ id: 'session-item'
+- extendedWaitUntil:
+ visible:
+ id: 'session-disconnect-button'
+ timeout: 30000
+- tapOn:
+ id: 'session-disconnect-button'
+# Wallet's session list is now empty.
+- extendedWaitUntil:
+ visible: 'No connected apps yet'
+ timeout: 30000
diff --git a/.maestro/relay/restore_walletkit.yaml b/.maestro/relay/restore_walletkit.yaml
new file mode 100644
index 000000000..ebdcad33f
--- /dev/null
+++ b/.maestro/relay/restore_walletkit.yaml
@@ -0,0 +1,21 @@
+# Flow 7 — restore sessions: after a full restart (no clearState) the wallet
+# rehydrates its active sessions from storage.
+appId: ${APP_ID}
+tags:
+ - relay-walletkit
+ - restore
+---
+- runFlow: _connect_walletkit.yaml
+# Foreground the wallet, then full stop + relaunch WITHOUT clearing state.
+- launchApp:
+ appId: ${APP_ID}
+- stopApp:
+ appId: ${APP_ID}
+- launchApp:
+ appId: ${APP_ID}
+ clearState: false
+- tapOn: 'Connected apps'
+- extendedWaitUntil:
+ visible:
+ id: 'session-item'
+ timeout: 30000
diff --git a/.maestro/relay/sign_other_chain_walletkit.yaml b/.maestro/relay/sign_other_chain_walletkit.yaml
new file mode 100644
index 000000000..69ffa22d1
--- /dev/null
+++ b/.maestro/relay/sign_other_chain_walletkit.yaml
@@ -0,0 +1,29 @@
+# Flow 4 — sign a message on another chain: switch chain, then personal_sign.
+appId: ${APP_ID}
+tags:
+ - relay-walletkit
+ - sign-other-chain
+---
+- runFlow: _connect_walletkit.yaml
+- extendedWaitUntil:
+ visible: 'Connected to'
+ timeout: 60000
+# Switch to Polygon via the dapp's TEST_MODE button.
+- tapOn: 'Switch to Polygon (E2E)'
+- extendedWaitUntil:
+ visible: 'Chain: Polygon'
+ timeout: 30000
+# Sign on the new chain.
+- tapOn: '^Sign$'
+- launchApp:
+ appId: ${APP_ID}
+- extendedWaitUntil:
+ visible:
+ id: 'approve-button'
+ timeout: 60000
+- tapOn:
+ id: 'approve-button'
+- runFlow: _focus_browser.yaml
+- extendedWaitUntil:
+ visible: 'Sign success'
+ timeout: 60000
diff --git a/.maestro/relay/sign_walletkit.yaml b/.maestro/relay/sign_walletkit.yaml
new file mode 100644
index 000000000..bc03f7bce
--- /dev/null
+++ b/.maestro/relay/sign_walletkit.yaml
@@ -0,0 +1,29 @@
+# Flow 2 — sign a message: dapp requests personal_sign, wallet approves.
+# Web dapp (Safari) + native wallet. Dapp actions/asserts are matched by TEXT
+# (Safari webview doesn't expose testIDs); wallet by id. Return to the dapp by
+# resuming Safari (Home + icon), not relaunching.
+appId: ${APP_ID}
+tags:
+ - relay-walletkit
+ - sign
+---
+- runFlow: _connect_walletkit.yaml
+- extendedWaitUntil:
+ visible: 'Connected to'
+ timeout: 60000
+# Trigger personal_sign on the dapp ("^Sign$" to avoid "Sign Typed Data").
+- tapOn: '^Sign$'
+# Foreground the wallet and approve the request.
+- launchApp:
+ appId: ${APP_ID}
+- extendedWaitUntil:
+ visible:
+ id: 'approve-button'
+ timeout: 60000
+- tapOn:
+ id: 'approve-button'
+# Resume the dapp; it shows the persistent result ("Sign success").
+- runFlow: _focus_browser.yaml
+- extendedWaitUntil:
+ visible: 'Sign success'
+ timeout: 60000
diff --git a/.maestro/relay/switch_chain_walletkit.yaml b/.maestro/relay/switch_chain_walletkit.yaml
new file mode 100644
index 000000000..f0b22190f
--- /dev/null
+++ b/.maestro/relay/switch_chain_walletkit.yaml
@@ -0,0 +1,19 @@
+# Flow 3 — switch chain: dapp switches to a second EVM network (client-side; all
+# EVM chains are in the approved namespaces, so no wallet re-approval).
+# Web dapp: matched by text. The NetworkButton shows the current chain name;
+# "^Ethereum$" targets it (vs the "Chain: Ethereum" info text).
+appId: ${APP_ID}
+tags:
+ - relay-walletkit
+ - switch-chain
+---
+- runFlow: _connect_walletkit.yaml
+- extendedWaitUntil:
+ visible: 'Connected to'
+ timeout: 60000
+# Switch to Polygon via the dapp's TEST_MODE button (the AppKit NetworkButton
+# modal isn't reliably driveable by text on web).
+- tapOn: 'Switch to Polygon (E2E)'
+- extendedWaitUntil:
+ visible: 'Chain: Polygon'
+ timeout: 30000
diff --git a/.maestro/web/connect_confirm.yaml b/.maestro/web/connect_confirm.yaml
index 3f5a0c428..5bfbe37ea 100644
--- a/.maestro/web/connect_confirm.yaml
+++ b/.maestro/web/connect_confirm.yaml
@@ -12,12 +12,26 @@ name: AppKit Web to React Native Wallet Connection Confirmed
- tapOn: "Connect Wallet"
- scroll
- scroll
-- tapOn: "React Native Sample Wallet React Native Sample Wallet"
+- runFlow:
+ when:
+ platform: iOS
+ commands:
+ - tapOn: "React Native Sample Wallet"
+- runFlow:
+ when:
+ platform: Android
+ commands:
+ - tapOn: "React Native Sample Wallet React Native Sample Wallet"
- runFlow:
when:
platform: iOS
commands:
- back
-- tapOn: "Approve"
+- extendedWaitUntil:
+ visible:
+ id: "approve-button"
+ timeout: 10000
+- tapOn:
+ id: "approve-button"
- assertVisible: "AppKit Interactions"
- stopRecording
\ No newline at end of file
diff --git a/.maestro/web/connect_reject.yaml b/.maestro/web/connect_reject.yaml
index 83f900ab9..032830b1d 100644
--- a/.maestro/web/connect_reject.yaml
+++ b/.maestro/web/connect_reject.yaml
@@ -12,8 +12,22 @@ name: AppKit Web to React Native Wallet Connection Rejected
- tapOn: "Connect Wallet"
- scroll
- scroll
-- tapOn: "React Native Sample Wallet React Native Sample Wallet"
-- tapOn: "Reject"
+- runFlow:
+ when:
+ platform: iOS
+ commands:
+ - tapOn: "React Native Sample Wallet"
+- runFlow:
+ when:
+ platform: Android
+ commands:
+ - tapOn: "React Native Sample Wallet React Native Sample Wallet"
+- extendedWaitUntil:
+ visible:
+ id: "reject-button"
+ timeout: 10000
+- tapOn:
+ id: "reject-button"
- runFlow:
when:
platform: iOS
diff --git a/.maestro/web/personal_sign_confirm.yaml b/.maestro/web/personal_sign_confirm.yaml
index 61e754783..e2395b15f 100644
--- a/.maestro/web/personal_sign_confirm.yaml
+++ b/.maestro/web/personal_sign_confirm.yaml
@@ -11,11 +11,17 @@ name: AppKit Web to React Native Wallet Sign Request Confirmed
- scroll
- scroll
- tapOn: "Sign Message"
-- tapOn: "Approve"
+- extendedWaitUntil:
+ visible:
+ id: "approve-button"
+ timeout: 10000
+- tapOn:
+ id: "approve-button"
- runFlow:
when:
platform: iOS
commands:
- back
+- waitForAnimationToEnd
- assertVisible: "Signing Succeeded"
- stopRecording
\ No newline at end of file
diff --git a/.maestro/web/personal_sign_reject.yaml b/.maestro/web/personal_sign_reject.yaml
index c69275f9e..ddb6cd487 100644
--- a/.maestro/web/personal_sign_reject.yaml
+++ b/.maestro/web/personal_sign_reject.yaml
@@ -11,11 +11,17 @@ name: AppKit Web to React Native Wallet Sign Request Rejected
- scroll
- scroll
- tapOn: "Sign Message"
-- tapOn: "Reject"
+- extendedWaitUntil:
+ visible:
+ id: "reject-button"
+ timeout: 10000
+- tapOn:
+ id: "reject-button"
- runFlow:
when:
platform: iOS
commands:
- back
+- waitForAnimationToEnd
- assertVisible: "Signing Failed"
- stopRecording
\ No newline at end of file
diff --git a/dapps/appkit-wagmi/src/App.tsx b/dapps/appkit-wagmi/src/App.tsx
index 789a18519..3cd5437be 100644
--- a/dapps/appkit-wagmi/src/App.tsx
+++ b/dapps/appkit-wagmi/src/App.tsx
@@ -56,11 +56,29 @@ Sentry.init({
// 1. Get projectId
const projectId = process.env.EXPO_PUBLIC_PROJECT_ID ?? '';
+// Feature the "React Native Sample Wallet" (WalletConnect Explorer id) so it's a
+// one-tap entry on the connect modal's first screen — used by the web relay E2E,
+// which connects by tapping the wallet by name (no QR).
+const RN_SAMPLE_WALLET_ID =
+ '6880782cf8ff712bf8772b585960346290fd2499c1f1c51df9fe713d9d2e9384';
+
// 2. Create config
const metadata = getMetadata();
const clipboardClient = {
setString: async (value: string) => {
+ // @react-native-clipboard/clipboard is native-only (no-ops on web), so
+ // AppKit's "Copy link" wouldn't actually copy the wc: URI on the web build.
+ // Use the Web Clipboard API on web (secure context: https / localhost) so
+ // the relay E2E can copy the URI and paste it into the native wallet.
+ if (
+ Platform.OS === 'web' &&
+ typeof navigator !== 'undefined' &&
+ navigator.clipboard
+ ) {
+ await navigator.clipboard.writeText(value);
+ return;
+ }
Clipboard.setString(value);
},
};
@@ -84,6 +102,7 @@ const appKit = createAppKit({
debug: __DEV__,
storage,
extraConnectors,
+ featuredWalletIds: [RN_SAMPLE_WALLET_ID],
});
const queryClient = new QueryClient();
diff --git a/dapps/appkit-wagmi/src/screens/Connections/components/OpenQrButton.tsx b/dapps/appkit-wagmi/src/screens/Connections/components/OpenQrButton.tsx
new file mode 100644
index 000000000..573ef3243
--- /dev/null
+++ b/dapps/appkit-wagmi/src/screens/Connections/components/OpenQrButton.tsx
@@ -0,0 +1,66 @@
+import React from 'react';
+import { Pressable, StyleSheet, Text, View } from 'react-native';
+import { useAppKit } from '@reown/appkit-react-native';
+import { useAccount, useDisconnect, useSwitchChain } from 'wagmi';
+
+// Polygon mainnet — the second chain the relay switch-chain tests target.
+const POLYGON_CHAIN_ID = 137;
+
+// TEST-ONLY (EXPO_PUBLIC_TEST_MODE) dapp buttons for the relay E2E. On web the
+// AppKit modal's controls aren't matchable by testID (Safari webview) and some
+// are icon-only, so we expose plain, text-matchable buttons:
+// - "Open QR (E2E)" -> open({ view: 'WalletConnect' }) jumps to the QR view.
+// - "Disconnect (E2E)" -> wagmi disconnect (the AppKit account button shows the
+// address/balance, which isn't reliably text-matchable on web).
+// No-op in normal builds.
+const TEST_MODE = process.env.EXPO_PUBLIC_TEST_MODE === 'true';
+
+export function OpenQrButton() {
+ const { open } = useAppKit();
+ const { isConnected } = useAccount();
+ const { disconnect } = useDisconnect();
+ const { switchChain } = useSwitchChain();
+
+ if (!TEST_MODE) {
+ return null;
+ }
+
+ return (
+
+ open({ view: 'WalletConnect' })}>
+ Open QR (E2E)
+
+ {isConnected ? (
+ <>
+ switchChain({ chainId: POLYGON_CHAIN_ID })}>
+ Switch to Polygon (E2E)
+
+ disconnect()}>
+ Disconnect (E2E)
+
+ >
+ ) : null}
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: { marginTop: 8, gap: 8, alignItems: 'center' },
+ button: {
+ backgroundColor: '#8c8c8c',
+ paddingVertical: 10,
+ paddingHorizontal: 16,
+ borderRadius: 12,
+ alignItems: 'center',
+ },
+ text: { color: 'white', fontWeight: '600' },
+});
diff --git a/dapps/appkit-wagmi/src/screens/Connections/components/WagmiActionsView.tsx b/dapps/appkit-wagmi/src/screens/Connections/components/WagmiActionsView.tsx
index db2d2ef39..f3918c412 100644
--- a/dapps/appkit-wagmi/src/screens/Connections/components/WagmiActionsView.tsx
+++ b/dapps/appkit-wagmi/src/screens/Connections/components/WagmiActionsView.tsx
@@ -1,6 +1,6 @@
-import React from 'react';
+import React, { useState } from 'react';
import { Button, Text, FlexView } from '@reown/appkit-ui-react-native';
-import { StyleSheet } from 'react-native';
+import { StyleSheet, Text as RNText } from 'react-native';
import { useSignMessage, useAccount, useSendTransaction, useEstimateGas, useSignTypedData } from 'wagmi';
import { Hex, parseEther } from 'viem';
import { SendTransactionData, SignMessageData, SignTypedDataData } from 'wagmi/query';
@@ -10,11 +10,17 @@ import { eip712 } from '@/utils/eip712';
export function WagmiActionsView() {
const { isConnected } = useAccount();
+ // Persistent, testID'd sign result for Maestro (the toasts below are
+ // ephemeral and racy to assert on). Cleared on each new sign attempt.
+ const [signResult, setSignResult] = useState(null);
+
const onSignSuccess = (data: SignMessageData) => {
+ setSignResult('Sign success');
ToastUtils.showSuccessToast('Signature successful', data);
};
const onSignError = (error: Error) => {
+ setSignResult('Sign failed');
ToastUtils.showErrorToast('Signature failed', error.message);
};
@@ -73,10 +79,14 @@ export function WagmiActionsView() {
disabled={isPending}
loading={isPending}
testID="sign-message-button"
- onPress={() => signMessage({ message: 'Hello AppKit!' })}
+ onPress={() => {
+ setSignResult(null);
+ signMessage({ message: 'Hello AppKit!' });
+ }}
>
Sign
+ {signResult && {signResult}}
{isGasError && Error estimating gas}