From cd3407ee469267a5cf4b398843b5bd7bb6e3d8fd Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 11 Jun 2026 14:29:34 -0300 Subject: [PATCH 1/5] test(walletkit-e2e): wire up USDT-on-Arbitrum Permit2 pay test Consumer side of WalletConnect/actions#97 (adds pay_usdt_arbitrum). USDT is a Permit2 token, so the wallet sends approve (allowance) then payment. - LoadingView: add pay-loading-setup-note testID on the token-setup note so the test can observe the approve step by id instead of a copy string. - walletkit-build-and-maestro action: after the Maestro run (if: always(), gated to the pay suite), reset the USDT-on-Arbitrum Permit2 allowance to 0 via revoke-permit2-approval.js so each run re-exercises approve. Add arbitrum-rpc-url input. Runs as a Node step (Maestro's runScript sandbox can't sign). - revoke-permit2-approval.js: make --walletAddress optional (derive from the private key; verify only when explicitly passed). - e2e-balance-check: monitor USDT + ETH (gas) on Arbitrum, Slack-alert when low. - Pin maestro/pay-tests to the #97 head SHA for testing (re-pin on merge). - AGENTS.md: document the flow, testID, and allowance reset. Co-Authored-By: Claude Opus 4.8 --- .../walletkit-build-and-maestro/action.yml | 31 +++++- .github/workflows/e2e-balance-check.yml | 94 +++++++++++++++++++ wallets/rn_cli_wallet/AGENTS.md | 4 + .../scripts/revoke-permit2-approval.js | 16 ++-- .../PaymentOptionsModal/LoadingView.tsx | 1 + 5 files changed, 138 insertions(+), 8 deletions(-) diff --git a/.github/actions/walletkit-build-and-maestro/action.yml b/.github/actions/walletkit-build-and-maestro/action.yml index 303377100..3003a690e 100644 --- a/.github/actions/walletkit-build-and-maestro/action.yml +++ b/.github/actions/walletkit-build-and-maestro/action.yml @@ -40,6 +40,13 @@ inputs: wallet-private-key: description: 'Test wallet private key (sets ENV_TEST_PRIVATE_KEY).' required: true + arbitrum-rpc-url: + description: | + Arbitrum One RPC URL used by the Permit2 allowance-reset step. Defaults to + the public endpoint. A custom RPC is recommended because the WalletConnect + Blockchain API gates some methods on Arbitrum. + required: false + default: 'https://arb1.arbitrum.io/rpc' merchant-api-key-single-nokyc: description: 'Partner API key for the single-option no-KYC merchant (WPAY_CUSTOMER_KEY_SINGLE_NOKYC).' required: true @@ -399,9 +406,10 @@ runs: sudo udevadm trigger --name-match=kvm # --- Common: Maestro setup + run --- - # Pinned to WalletConnect/actions master + # TEMP: pinned to the WalletConnect/actions PR #97 head (adds pay_usdt_arbitrum). + # Re-pin to the squash-merge commit on master once #97 lands. - name: Copy shared Pay test flows - uses: WalletConnect/actions/maestro/pay-tests@3a145abaa0dcf9f609fabe17f6e6722e0db49535 + uses: WalletConnect/actions/maestro/pay-tests@3ccda39148c71e8d85ee8ae324d23a0b8b02f02f - name: Install Maestro uses: WalletConnect/actions/maestro/setup@3a145abaa0dcf9f609fabe17f6e6722e0db49535 @@ -562,6 +570,25 @@ runs: # download+rename step if both attempts' artifacts are needed. overwrite: true + # Reset the USDT Permit2 allowance back to 0 so the next pay_usdt_arbitrum run + # re-exercises the approve step. Runs even when the suite failed (if: always()) + # so a mid-flow failure doesn't leave the allowance set. Must be a Node step — + # Maestro's runScript sandbox can't sign transactions. Non-fatal: a reset hiccup + # shouldn't fail an otherwise-green run. + - name: Reset USDT Permit2 allowance (Arbitrum) + if: always() && contains(inputs.maestro-tags, 'pay') + shell: bash + working-directory: ${{ steps.paths.outputs.wallet_root }} + env: + WALLET_PRIVATE_KEY: ${{ inputs.wallet-private-key }} + ARBITRUM_RPC_URL: ${{ inputs.arbitrum-rpc-url }} + run: | + # walletAddress is derived from the private key (no separate var needed). + node ./scripts/revoke-permit2-approval.js \ + --chainId eip155:42161 \ + --privateKey "$WALLET_PRIVATE_KEY" \ + --rpcUrl "$ARBITRUM_RPC_URL" || echo "::warning::Permit2 allowance reset failed; next run may skip the approve step." + - name: Log Maestro outcome if: always() shell: bash diff --git a/.github/workflows/e2e-balance-check.yml b/.github/workflows/e2e-balance-check.yml index 25530446e..764e5bf34 100644 --- a/.github/workflows/e2e-balance-check.yml +++ b/.github/workflows/e2e-balance-check.yml @@ -15,6 +15,8 @@ env: BASE_RPC: 'https://mainnet.base.org' OP_USDC: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85' OP_RPC: 'https://mainnet.optimism.io' + ARB_USDT: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9' + ARB_RPC: 'https://arb1.arbitrum.io/rpc' jobs: check-balance: @@ -115,3 +117,95 @@ jobs: env.SLACK_FAUCETBOT_WEBHOOK_URL == '' run: | echo "::warning::SLACK_FAUCETBOT_WEBHOOK_URL not configured, skipping Optimism USDC alert" + + # USDT on Arbitrum funds the pay_usdt_arbitrum Permit2 flow (the actual payment). + - name: Check USDT balance on Arbitrum + id: arb_usdt_balance + run: | + BALANCE=$(cast call --rpc-url "$ARB_RPC" "$ARB_USDT" \ + "balanceOf(address)(uint256)" \ + "${{ vars.TEST_WALLET_ADDRESS }}" | sed 's/\[.*\]//' | tr -d '[:space:]') + echo "balance=$BALANCE" >> $GITHUB_OUTPUT + BALANCE_HUMAN=$(echo "scale=2; $BALANCE / 1000000" | bc) + echo "USDT on Arbitrum: $BALANCE_HUMAN ($BALANCE raw)" + + - name: Prepare USDT on Arbitrum alert + id: arb_usdt_alert + run: | + BALANCE="${{ steps.arb_usdt_balance.outputs.balance }}" + THRESHOLD="${{ vars.USDT_ARBITRUM_THRESHOLD_UNITS || '5000000' }}" + if [ "$BALANCE" -lt "$THRESHOLD" ]; then + BALANCE_HUMAN=$(echo "scale=2; $BALANCE / 1000000" | bc) + THRESHOLD_HUMAN=$(echo "scale=2; $THRESHOLD / 1000000" | bc) + ALERT_TEXT="[request] Can i have ${THRESHOLD_HUMAN} USDT on Arbitrum in wallet: \ + ${{ vars.TEST_WALLET_ADDRESS }}" + echo "should_alert=true" >> "$GITHUB_OUTPUT" + echo "text=$ALERT_TEXT" >> "$GITHUB_OUTPUT" + echo "::warning::$ALERT_TEXT" + fi + + - name: Send Slack alert (USDT on Arbitrum) + if: | + steps.arb_usdt_alert.outputs.should_alert == 'true' && + env.SLACK_FAUCETBOT_WEBHOOK_URL != '' + uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52 # v2.1.0 + with: + webhook: ${{ env.SLACK_FAUCETBOT_WEBHOOK_URL }} + webhook-type: incoming-webhook + payload: | + { + "text": "${{ steps.arb_usdt_alert.outputs.text }}" + } + + - name: Skip Slack alert (USDT on Arbitrum - no webhook) + if: | + steps.arb_usdt_alert.outputs.should_alert == 'true' && + env.SLACK_FAUCETBOT_WEBHOOK_URL == '' + run: | + echo "::warning::SLACK_FAUCETBOT_WEBHOOK_URL not configured, skipping Arbitrum USDT alert" + + # Native ETH gas on Arbitrum: needed for both the payment txs (approve + pay) + # and the post-test Permit2 allowance-reset tx. + - name: Check ETH gas balance on Arbitrum + id: arb_eth_balance + run: | + BALANCE=$(cast balance --rpc-url "$ARB_RPC" "${{ vars.TEST_WALLET_ADDRESS }}" | tr -d '[:space:]') + echo "balance=$BALANCE" >> $GITHUB_OUTPUT + BALANCE_HUMAN=$(echo "scale=6; $BALANCE / 1000000000000000000" | bc) + echo "ETH on Arbitrum: $BALANCE_HUMAN ($BALANCE wei)" + + - name: Prepare Arbitrum ETH gas alert + id: arb_eth_alert + run: | + BALANCE="${{ steps.arb_eth_balance.outputs.balance }}" + THRESHOLD="${{ vars.ARBITRUM_ETH_THRESHOLD_WEI || '1000000000000000' }}" + # Use bc for the comparison: ETH wei values overflow shell arithmetic. + if [ "$(echo "$BALANCE < $THRESHOLD" | bc)" -eq 1 ]; then + BALANCE_HUMAN=$(echo "scale=6; $BALANCE / 1000000000000000000" | bc) + THRESHOLD_HUMAN=$(echo "scale=6; $THRESHOLD / 1000000000000000000" | bc) + ALERT_TEXT="[request] Can i have ${THRESHOLD_HUMAN} ETH (gas) on Arbitrum in wallet: \ + ${{ vars.TEST_WALLET_ADDRESS }} (currently ${BALANCE_HUMAN})" + echo "should_alert=true" >> "$GITHUB_OUTPUT" + echo "text=$ALERT_TEXT" >> "$GITHUB_OUTPUT" + echo "::warning::$ALERT_TEXT" + fi + + - name: Send Slack alert (Arbitrum ETH gas) + if: | + steps.arb_eth_alert.outputs.should_alert == 'true' && + env.SLACK_FAUCETBOT_WEBHOOK_URL != '' + uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52 # v2.1.0 + with: + webhook: ${{ env.SLACK_FAUCETBOT_WEBHOOK_URL }} + webhook-type: incoming-webhook + payload: | + { + "text": "${{ steps.arb_eth_alert.outputs.text }}" + } + + - name: Skip Slack alert (Arbitrum ETH gas - no webhook) + if: | + steps.arb_eth_alert.outputs.should_alert == 'true' && + env.SLACK_FAUCETBOT_WEBHOOK_URL == '' + run: | + echo "::warning::SLACK_FAUCETBOT_WEBHOOK_URL not configured, skipping Arbitrum ETH gas alert" diff --git a/wallets/rn_cli_wallet/AGENTS.md b/wallets/rn_cli_wallet/AGENTS.md index 1e56ad7f8..4a8aa8e3f 100644 --- a/wallets/rn_cli_wallet/AGENTS.md +++ b/wallets/rn_cli_wallet/AGENTS.md @@ -186,6 +186,7 @@ The app uses standardized `testID` props for Maestro E2E testing. These IDs are - `.maestro/pay_single_option_nokyc.yaml`: Single payment option, no KYC — goes straight to review screen - `.maestro/pay_multiple_options_nokyc.yaml`: Multiple payment options, no KYC — option selection then review - `.maestro/pay_multiple_options_kyc.yaml`: Multiple payment options with KYC — option selection, webview KYC flow, then review +- `.maestro/pay_usdt_arbitrum.yaml`: USDT on Arbitrum — Permit2 token, so the wallet sends an `approve` (allowance) tx then the payment tx. Best-effort observes the setup step via the `pay-loading-setup-note` testID (soft screenshot), then asserts the success screen. The allowance is reset to 0 after the run (see below) so each run re-exercises `approve`. - `.maestro/flows/pay_open_and_paste_url.yaml`: Shared sub-flow — opens wallet, pastes payment URL, waits for merchant info - `.maestro/flows/pay_confirm_and_verify.yaml`: Shared sub-flow — taps Pay, verifies success screen - `.maestro/scripts/create-payment.js`: Creates a payment via the WalletConnect Pay API (called via `runScript`) @@ -204,6 +205,9 @@ When set, the wallet auto-loads this private key on startup (if no stored wallet ### CI Workflow `.github/workflows/ci_e2e_walletkit.yaml` runs Maestro tests on both iOS (simulator) and Android (emulator). Triggers on PRs/pushes to main when `wallets/rn_cli_wallet/` or `.maestro/` files change. +### Permit2 allowance reset (USDT) +After the suite runs, the composite action (`.github/actions/walletkit-build-and-maestro`) runs `scripts/revoke-permit2-approval.js` (yarn `permit2:revoke`) to reset the USDT-on-Arbitrum Permit2 allowance back to 0, so `pay_usdt_arbitrum` always re-exercises the `approve` step. This is a Node step, not a Maestro `runScript` — Maestro's script sandbox cannot sign transactions. `.github/workflows/e2e-balance-check.yml` also monitors USDT + ETH (gas) on Arbitrum and pings the faucet bot on Slack when low. + ## Development ### Prerequisites diff --git a/wallets/rn_cli_wallet/scripts/revoke-permit2-approval.js b/wallets/rn_cli_wallet/scripts/revoke-permit2-approval.js index 72d05cb7a..63beb994c 100644 --- a/wallets/rn_cli_wallet/scripts/revoke-permit2-approval.js +++ b/wallets/rn_cli_wallet/scripts/revoke-permit2-approval.js @@ -23,13 +23,14 @@ const MIN_PRIORITY_FEE_GWEI_BY_CHAIN = { function printUsage() { const supportedChains = Object.keys(USDT_BY_CHAIN).join(', '); console.log(`Usage: - yarn permit2:revoke --chainId --walletAddress <0x...> --privateKey <0x...> (--projectId | --rpcUrl ) [--tokenAddress <0x...>] [--minPriorityFeeGwei ] + yarn permit2:revoke --chainId --privateKey <0x...> (--projectId | --rpcUrl ) [--walletAddress <0x...>] [--tokenAddress <0x...>] [--minPriorityFeeGwei ] Example: - yarn permit2:revoke --chainId eip155:137 --walletAddress 0xYourAddress --privateKey 0xYourPrivateKey --projectId yourProjectId - yarn permit2:revoke --chainId eip155:42161 --walletAddress 0xYourAddress --privateKey 0xYourPrivateKey --rpcUrl https://arb1.arbitrum.io/rpc + yarn permit2:revoke --chainId eip155:137 --privateKey 0xYourPrivateKey --projectId yourProjectId + yarn permit2:revoke --chainId eip155:42161 --privateKey 0xYourPrivateKey --rpcUrl https://arb1.arbitrum.io/rpc Defaults: + If --walletAddress is omitted, it is derived from --privateKey. If provided, it is verified to match the key. If --tokenAddress is omitted, the script uses the USDT address for the selected chain. If --rpcUrl is provided it takes precedence over --projectId (use this for chains where the WalletConnect Blockchain API gates methods like eth_blockNumber, e.g. Arbitrum). @@ -223,7 +224,9 @@ async function main() { } const chainId = normalizeChainId(args.chainId); - const walletAddress = normalizeAddress('walletAddress', args.walletAddress); + const walletAddressArg = args.walletAddress + ? normalizeAddress('walletAddress', args.walletAddress) + : null; const privateKey = normalizePrivateKey(args.privateKey); const tokenAddress = normalizeAddress( 'tokenAddress', @@ -251,11 +254,12 @@ async function main() { const signer = new ethers.Wallet(privateKey, provider); const signerAddress = ethers.utils.getAddress(signer.address); - if (signerAddress !== walletAddress) { + if (walletAddressArg && signerAddress !== walletAddressArg) { throw new Error( - `walletAddress (${walletAddress}) does not match private key (${signerAddress}).`, + `walletAddress (${walletAddressArg}) does not match private key (${signerAddress}).`, ); } + const walletAddress = signerAddress; const token = new ethers.Contract(tokenAddress, ERC20_ABI, signer); diff --git a/wallets/rn_cli_wallet/src/modals/PaymentOptionsModal/LoadingView.tsx b/wallets/rn_cli_wallet/src/modals/PaymentOptionsModal/LoadingView.tsx index 5817df293..7145838f0 100644 --- a/wallets/rn_cli_wallet/src/modals/PaymentOptionsModal/LoadingView.tsx +++ b/wallets/rn_cli_wallet/src/modals/PaymentOptionsModal/LoadingView.tsx @@ -89,6 +89,7 @@ export function LoadingView({ color="text-secondary" center style={styles.loadingNote} + testID="pay-loading-setup-note" > {note} From 04a43e4324facf6c2cbcefd094bbad2f0445b945 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 11 Jun 2026 15:11:36 -0300 Subject: [PATCH 2/5] refactor(walletkit-e2e): target USDT on Polygon instead of Arbitrum MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit USDT on Arbitrum (0xFd086…) is EIP-3009 (signature-based, gasless), so WC Pay never returns an on-chain approve action for it — the approve step the test is meant to cover would never run. USDT on Polygon (0xc2132D…) is a plain ERC-20, so WC Pay uses the Permit2 approve + pay path. - Reset teardown now targets --chainId eip155:137 --rpcUrl https://polygon-rpc.com; rename the action input arbitrum-rpc-url -> polygon-rpc-url. - balance-check: monitor USDT + POL (gas) on Polygon instead of USDC/ETH on Arbitrum. - Bump maestro/pay-tests pin to the updated PR #97 head (pay_usdt_polygon). - AGENTS.md: document the Polygon rationale. Co-Authored-By: Claude Opus 4.8 --- .../walletkit-build-and-maestro/action.yml | 26 +++---- .github/workflows/e2e-balance-check.yml | 70 +++++++++---------- wallets/rn_cli_wallet/AGENTS.md | 4 +- 3 files changed, 51 insertions(+), 49 deletions(-) diff --git a/.github/actions/walletkit-build-and-maestro/action.yml b/.github/actions/walletkit-build-and-maestro/action.yml index 3003a690e..44140dcd2 100644 --- a/.github/actions/walletkit-build-and-maestro/action.yml +++ b/.github/actions/walletkit-build-and-maestro/action.yml @@ -40,13 +40,14 @@ inputs: wallet-private-key: description: 'Test wallet private key (sets ENV_TEST_PRIVATE_KEY).' required: true - arbitrum-rpc-url: + polygon-rpc-url: description: | - Arbitrum One RPC URL used by the Permit2 allowance-reset step. Defaults to - the public endpoint. A custom RPC is recommended because the WalletConnect - Blockchain API gates some methods on Arbitrum. + Polygon RPC URL used by the Permit2 allowance-reset step. Defaults to the + public endpoint. USDT on Polygon is a plain ERC-20 (no EIP-3009/2612), so + WC Pay uses the Permit2 approve path — this lets the reset revoke that + allowance so each run re-exercises the approve step. required: false - default: 'https://arb1.arbitrum.io/rpc' + default: 'https://polygon-rpc.com' merchant-api-key-single-nokyc: description: 'Partner API key for the single-option no-KYC merchant (WPAY_CUSTOMER_KEY_SINGLE_NOKYC).' required: true @@ -406,10 +407,10 @@ runs: sudo udevadm trigger --name-match=kvm # --- Common: Maestro setup + run --- - # TEMP: pinned to the WalletConnect/actions PR #97 head (adds pay_usdt_arbitrum). + # TEMP: pinned to the WalletConnect/actions PR #97 head (adds pay_usdt_polygon). # Re-pin to the squash-merge commit on master once #97 lands. - name: Copy shared Pay test flows - uses: WalletConnect/actions/maestro/pay-tests@3ccda39148c71e8d85ee8ae324d23a0b8b02f02f + uses: WalletConnect/actions/maestro/pay-tests@e78552d1094d43321bb9b9a34d138216a2bafb5c - name: Install Maestro uses: WalletConnect/actions/maestro/setup@3a145abaa0dcf9f609fabe17f6e6722e0db49535 @@ -570,24 +571,25 @@ runs: # download+rename step if both attempts' artifacts are needed. overwrite: true - # Reset the USDT Permit2 allowance back to 0 so the next pay_usdt_arbitrum run + # Reset the USDT Permit2 allowance back to 0 so the next pay_usdt_polygon run # re-exercises the approve step. Runs even when the suite failed (if: always()) # so a mid-flow failure doesn't leave the allowance set. Must be a Node step — # Maestro's runScript sandbox can't sign transactions. Non-fatal: a reset hiccup # shouldn't fail an otherwise-green run. - - name: Reset USDT Permit2 allowance (Arbitrum) + - name: Reset USDT Permit2 allowance (Polygon) if: always() && contains(inputs.maestro-tags, 'pay') shell: bash working-directory: ${{ steps.paths.outputs.wallet_root }} env: WALLET_PRIVATE_KEY: ${{ inputs.wallet-private-key }} - ARBITRUM_RPC_URL: ${{ inputs.arbitrum-rpc-url }} + POLYGON_RPC_URL: ${{ inputs.polygon-rpc-url }} run: | # walletAddress is derived from the private key (no separate var needed). + # Polygon (eip155:137) min priority fee defaults to 25 gwei in the script. node ./scripts/revoke-permit2-approval.js \ - --chainId eip155:42161 \ + --chainId eip155:137 \ --privateKey "$WALLET_PRIVATE_KEY" \ - --rpcUrl "$ARBITRUM_RPC_URL" || echo "::warning::Permit2 allowance reset failed; next run may skip the approve step." + --rpcUrl "$POLYGON_RPC_URL" || echo "::warning::Permit2 allowance reset failed; next run may skip the approve step." - name: Log Maestro outcome if: always() diff --git a/.github/workflows/e2e-balance-check.yml b/.github/workflows/e2e-balance-check.yml index 764e5bf34..3ee5e55f2 100644 --- a/.github/workflows/e2e-balance-check.yml +++ b/.github/workflows/e2e-balance-check.yml @@ -15,8 +15,8 @@ env: BASE_RPC: 'https://mainnet.base.org' OP_USDC: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85' OP_RPC: 'https://mainnet.optimism.io' - ARB_USDT: '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9' - ARB_RPC: 'https://arb1.arbitrum.io/rpc' + POLYGON_USDT: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F' + POLYGON_RPC: 'https://polygon-rpc.com' jobs: check-balance: @@ -118,35 +118,35 @@ jobs: run: | echo "::warning::SLACK_FAUCETBOT_WEBHOOK_URL not configured, skipping Optimism USDC alert" - # USDT on Arbitrum funds the pay_usdt_arbitrum Permit2 flow (the actual payment). - - name: Check USDT balance on Arbitrum - id: arb_usdt_balance + # USDT on Polygon funds the pay_usdt_polygon Permit2 flow (the actual payment). + - name: Check USDT balance on Polygon + id: poly_usdt_balance run: | - BALANCE=$(cast call --rpc-url "$ARB_RPC" "$ARB_USDT" \ + BALANCE=$(cast call --rpc-url "$POLYGON_RPC" "$POLYGON_USDT" \ "balanceOf(address)(uint256)" \ "${{ vars.TEST_WALLET_ADDRESS }}" | sed 's/\[.*\]//' | tr -d '[:space:]') echo "balance=$BALANCE" >> $GITHUB_OUTPUT BALANCE_HUMAN=$(echo "scale=2; $BALANCE / 1000000" | bc) - echo "USDT on Arbitrum: $BALANCE_HUMAN ($BALANCE raw)" + echo "USDT on Polygon: $BALANCE_HUMAN ($BALANCE raw)" - - name: Prepare USDT on Arbitrum alert - id: arb_usdt_alert + - name: Prepare USDT on Polygon alert + id: poly_usdt_alert run: | - BALANCE="${{ steps.arb_usdt_balance.outputs.balance }}" - THRESHOLD="${{ vars.USDT_ARBITRUM_THRESHOLD_UNITS || '5000000' }}" + BALANCE="${{ steps.poly_usdt_balance.outputs.balance }}" + THRESHOLD="${{ vars.USDT_POLYGON_THRESHOLD_UNITS || '5000000' }}" if [ "$BALANCE" -lt "$THRESHOLD" ]; then BALANCE_HUMAN=$(echo "scale=2; $BALANCE / 1000000" | bc) THRESHOLD_HUMAN=$(echo "scale=2; $THRESHOLD / 1000000" | bc) - ALERT_TEXT="[request] Can i have ${THRESHOLD_HUMAN} USDT on Arbitrum in wallet: \ + ALERT_TEXT="[request] Can i have ${THRESHOLD_HUMAN} USDT on Polygon in wallet: \ ${{ vars.TEST_WALLET_ADDRESS }}" echo "should_alert=true" >> "$GITHUB_OUTPUT" echo "text=$ALERT_TEXT" >> "$GITHUB_OUTPUT" echo "::warning::$ALERT_TEXT" fi - - name: Send Slack alert (USDT on Arbitrum) + - name: Send Slack alert (USDT on Polygon) if: | - steps.arb_usdt_alert.outputs.should_alert == 'true' && + steps.poly_usdt_alert.outputs.should_alert == 'true' && env.SLACK_FAUCETBOT_WEBHOOK_URL != '' uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52 # v2.1.0 with: @@ -154,45 +154,45 @@ jobs: webhook-type: incoming-webhook payload: | { - "text": "${{ steps.arb_usdt_alert.outputs.text }}" + "text": "${{ steps.poly_usdt_alert.outputs.text }}" } - - name: Skip Slack alert (USDT on Arbitrum - no webhook) + - name: Skip Slack alert (USDT on Polygon - no webhook) if: | - steps.arb_usdt_alert.outputs.should_alert == 'true' && + steps.poly_usdt_alert.outputs.should_alert == 'true' && env.SLACK_FAUCETBOT_WEBHOOK_URL == '' run: | - echo "::warning::SLACK_FAUCETBOT_WEBHOOK_URL not configured, skipping Arbitrum USDT alert" + echo "::warning::SLACK_FAUCETBOT_WEBHOOK_URL not configured, skipping Polygon USDT alert" - # Native ETH gas on Arbitrum: needed for both the payment txs (approve + pay) + # Native POL gas on Polygon: needed for both the payment txs (approve + pay) # and the post-test Permit2 allowance-reset tx. - - name: Check ETH gas balance on Arbitrum - id: arb_eth_balance + - name: Check POL gas balance on Polygon + id: poly_pol_balance run: | - BALANCE=$(cast balance --rpc-url "$ARB_RPC" "${{ vars.TEST_WALLET_ADDRESS }}" | tr -d '[:space:]') + BALANCE=$(cast balance --rpc-url "$POLYGON_RPC" "${{ vars.TEST_WALLET_ADDRESS }}" | tr -d '[:space:]') echo "balance=$BALANCE" >> $GITHUB_OUTPUT BALANCE_HUMAN=$(echo "scale=6; $BALANCE / 1000000000000000000" | bc) - echo "ETH on Arbitrum: $BALANCE_HUMAN ($BALANCE wei)" + echo "POL on Polygon: $BALANCE_HUMAN ($BALANCE wei)" - - name: Prepare Arbitrum ETH gas alert - id: arb_eth_alert + - name: Prepare Polygon POL gas alert + id: poly_pol_alert run: | - BALANCE="${{ steps.arb_eth_balance.outputs.balance }}" - THRESHOLD="${{ vars.ARBITRUM_ETH_THRESHOLD_WEI || '1000000000000000' }}" - # Use bc for the comparison: ETH wei values overflow shell arithmetic. + BALANCE="${{ steps.poly_pol_balance.outputs.balance }}" + THRESHOLD="${{ vars.POLYGON_POL_THRESHOLD_WEI || '1000000000000000000' }}" + # Use bc for the comparison: wei values overflow shell arithmetic. if [ "$(echo "$BALANCE < $THRESHOLD" | bc)" -eq 1 ]; then BALANCE_HUMAN=$(echo "scale=6; $BALANCE / 1000000000000000000" | bc) THRESHOLD_HUMAN=$(echo "scale=6; $THRESHOLD / 1000000000000000000" | bc) - ALERT_TEXT="[request] Can i have ${THRESHOLD_HUMAN} ETH (gas) on Arbitrum in wallet: \ + ALERT_TEXT="[request] Can i have ${THRESHOLD_HUMAN} POL (gas) on Polygon in wallet: \ ${{ vars.TEST_WALLET_ADDRESS }} (currently ${BALANCE_HUMAN})" echo "should_alert=true" >> "$GITHUB_OUTPUT" echo "text=$ALERT_TEXT" >> "$GITHUB_OUTPUT" echo "::warning::$ALERT_TEXT" fi - - name: Send Slack alert (Arbitrum ETH gas) + - name: Send Slack alert (Polygon POL gas) if: | - steps.arb_eth_alert.outputs.should_alert == 'true' && + steps.poly_pol_alert.outputs.should_alert == 'true' && env.SLACK_FAUCETBOT_WEBHOOK_URL != '' uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52 # v2.1.0 with: @@ -200,12 +200,12 @@ jobs: webhook-type: incoming-webhook payload: | { - "text": "${{ steps.arb_eth_alert.outputs.text }}" + "text": "${{ steps.poly_pol_alert.outputs.text }}" } - - name: Skip Slack alert (Arbitrum ETH gas - no webhook) + - name: Skip Slack alert (Polygon POL gas - no webhook) if: | - steps.arb_eth_alert.outputs.should_alert == 'true' && + steps.poly_pol_alert.outputs.should_alert == 'true' && env.SLACK_FAUCETBOT_WEBHOOK_URL == '' run: | - echo "::warning::SLACK_FAUCETBOT_WEBHOOK_URL not configured, skipping Arbitrum ETH gas alert" + echo "::warning::SLACK_FAUCETBOT_WEBHOOK_URL not configured, skipping Polygon POL gas alert" diff --git a/wallets/rn_cli_wallet/AGENTS.md b/wallets/rn_cli_wallet/AGENTS.md index 4a8aa8e3f..b2a91db43 100644 --- a/wallets/rn_cli_wallet/AGENTS.md +++ b/wallets/rn_cli_wallet/AGENTS.md @@ -186,7 +186,7 @@ The app uses standardized `testID` props for Maestro E2E testing. These IDs are - `.maestro/pay_single_option_nokyc.yaml`: Single payment option, no KYC — goes straight to review screen - `.maestro/pay_multiple_options_nokyc.yaml`: Multiple payment options, no KYC — option selection then review - `.maestro/pay_multiple_options_kyc.yaml`: Multiple payment options with KYC — option selection, webview KYC flow, then review -- `.maestro/pay_usdt_arbitrum.yaml`: USDT on Arbitrum — Permit2 token, so the wallet sends an `approve` (allowance) tx then the payment tx. Best-effort observes the setup step via the `pay-loading-setup-note` testID (soft screenshot), then asserts the success screen. The allowance is reset to 0 after the run (see below) so each run re-exercises `approve`. +- `.maestro/pay_usdt_polygon.yaml`: USDT on Polygon — a plain ERC-20 (no EIP-3009/2612), so WC Pay uses the Permit2 path: the wallet sends an `approve` (allowance) tx then the payment tx. Best-effort observes the setup step via the `pay-loading-setup-note` testID (soft screenshot), then asserts the success screen. The allowance is reset to 0 after the run (see below) so each run re-exercises `approve`. (Note: USDT on Arbitrum is EIP-3009 / signature-based, so it never needs an on-chain approve — Polygon is used precisely because it does.) - `.maestro/flows/pay_open_and_paste_url.yaml`: Shared sub-flow — opens wallet, pastes payment URL, waits for merchant info - `.maestro/flows/pay_confirm_and_verify.yaml`: Shared sub-flow — taps Pay, verifies success screen - `.maestro/scripts/create-payment.js`: Creates a payment via the WalletConnect Pay API (called via `runScript`) @@ -206,7 +206,7 @@ When set, the wallet auto-loads this private key on startup (if no stored wallet `.github/workflows/ci_e2e_walletkit.yaml` runs Maestro tests on both iOS (simulator) and Android (emulator). Triggers on PRs/pushes to main when `wallets/rn_cli_wallet/` or `.maestro/` files change. ### Permit2 allowance reset (USDT) -After the suite runs, the composite action (`.github/actions/walletkit-build-and-maestro`) runs `scripts/revoke-permit2-approval.js` (yarn `permit2:revoke`) to reset the USDT-on-Arbitrum Permit2 allowance back to 0, so `pay_usdt_arbitrum` always re-exercises the `approve` step. This is a Node step, not a Maestro `runScript` — Maestro's script sandbox cannot sign transactions. `.github/workflows/e2e-balance-check.yml` also monitors USDT + ETH (gas) on Arbitrum and pings the faucet bot on Slack when low. +After the suite runs, the composite action (`.github/actions/walletkit-build-and-maestro`) runs `scripts/revoke-permit2-approval.js` (yarn `permit2:revoke`) to reset the USDT-on-Polygon Permit2 allowance back to 0, so `pay_usdt_polygon` always re-exercises the `approve` step. This is a Node step, not a Maestro `runScript` — Maestro's script sandbox cannot sign transactions. `.github/workflows/e2e-balance-check.yml` also monitors USDT + POL (gas) on Polygon and pings the faucet bot on Slack when low. ## Development From 2a31fdc4242495b47759b14e39a8caec226fad94 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 11 Jun 2026 15:16:59 -0300 Subject: [PATCH 3/5] fix(walletkit-e2e): use publicnode Polygon RPC (polygon-rpc.com is gated in CI) polygon-rpc.com returns HTTP 401 "tenant disabled" from CI runners, which made the Permit2 allowance-reset step fail (eth_estimateGas) and would also break the balance-check cast calls. Switch the reset default and the balance-check RPC to https://polygon-bor-rpc.publicnode.com (keyless, returns 200). Co-Authored-By: Claude Opus 4.8 --- .github/actions/walletkit-build-and-maestro/action.yml | 5 +++-- .github/workflows/e2e-balance-check.yml | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/actions/walletkit-build-and-maestro/action.yml b/.github/actions/walletkit-build-and-maestro/action.yml index 44140dcd2..abeb9cf77 100644 --- a/.github/actions/walletkit-build-and-maestro/action.yml +++ b/.github/actions/walletkit-build-and-maestro/action.yml @@ -45,9 +45,10 @@ inputs: Polygon RPC URL used by the Permit2 allowance-reset step. Defaults to the public endpoint. USDT on Polygon is a plain ERC-20 (no EIP-3009/2612), so WC Pay uses the Permit2 approve path — this lets the reset revoke that - allowance so each run re-exercises the approve step. + allowance so each run re-exercises the approve step. Default avoids + polygon-rpc.com, which gates requests (HTTP 401 "tenant disabled") in CI. required: false - default: 'https://polygon-rpc.com' + default: 'https://polygon-bor-rpc.publicnode.com' merchant-api-key-single-nokyc: description: 'Partner API key for the single-option no-KYC merchant (WPAY_CUSTOMER_KEY_SINGLE_NOKYC).' required: true diff --git a/.github/workflows/e2e-balance-check.yml b/.github/workflows/e2e-balance-check.yml index 3ee5e55f2..d087e43a8 100644 --- a/.github/workflows/e2e-balance-check.yml +++ b/.github/workflows/e2e-balance-check.yml @@ -16,7 +16,8 @@ env: OP_USDC: '0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85' OP_RPC: 'https://mainnet.optimism.io' POLYGON_USDT: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F' - POLYGON_RPC: 'https://polygon-rpc.com' + # polygon-rpc.com gates requests (HTTP 401 "tenant disabled") in CI; use publicnode. + POLYGON_RPC: 'https://polygon-bor-rpc.publicnode.com' jobs: check-balance: From 555a2679b7058ce7e4e4830ac76f73b10d8ab1a9 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 11 Jun 2026 16:01:24 -0300 Subject: [PATCH 4/5] feat(pay): add stable network+token testID to payment options MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The payment-options screen keyed rows only by order-dependent index, with the network in the accessibilityLabel — so a test couldn't deterministically pick a specific asset+network (e.g. USDT on Polygon vs the same token on Arbitrum). Add an additive `pay-option-${assetSymbol}-${networkName}` testID (e.g. `pay-option-usdt-polygon`) via a thin wrapper, leaving `pay-option-${index}` intact for the existing multi-options flow and other platforms. Bump the maestro/pay-tests pin to the actions PR head that selects USDT-on-Polygon by this id. Co-Authored-By: Claude Opus 4.8 --- .../walletkit-build-and-maestro/action.yml | 2 +- .../PaymentOptionsModal/SelectOptionView.tsx | 51 +++++++++++-------- 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/.github/actions/walletkit-build-and-maestro/action.yml b/.github/actions/walletkit-build-and-maestro/action.yml index abeb9cf77..fee88c718 100644 --- a/.github/actions/walletkit-build-and-maestro/action.yml +++ b/.github/actions/walletkit-build-and-maestro/action.yml @@ -411,7 +411,7 @@ runs: # TEMP: pinned to the WalletConnect/actions PR #97 head (adds pay_usdt_polygon). # Re-pin to the squash-merge commit on master once #97 lands. - name: Copy shared Pay test flows - uses: WalletConnect/actions/maestro/pay-tests@e78552d1094d43321bb9b9a34d138216a2bafb5c + uses: WalletConnect/actions/maestro/pay-tests@06e4e04ca111ab27cdcb35b73d70325607510f83 - name: Install Maestro uses: WalletConnect/actions/maestro/setup@3a145abaa0dcf9f609fabe17f6e6722e0db49535 diff --git a/wallets/rn_cli_wallet/src/modals/PaymentOptionsModal/SelectOptionView.tsx b/wallets/rn_cli_wallet/src/modals/PaymentOptionsModal/SelectOptionView.tsx index eb1634ae5..7d8f3f383 100644 --- a/wallets/rn_cli_wallet/src/modals/PaymentOptionsModal/SelectOptionView.tsx +++ b/wallets/rn_cli_wallet/src/modals/PaymentOptionsModal/SelectOptionView.tsx @@ -78,28 +78,37 @@ export function SelectOptionView({ !!(option as PaymentOptionWithCollectData).collectData?.url && !collectDataCompletedIds.includes(option.id); + // Stable, network+token-keyed testID for deterministic selection + // (e.g. `pay-option-usdt-polygon`), additive to the order-dependent + // `pay-option-${index}`. Lets a test pick a specific asset+network + // when several options share a token symbol across networks. + const optionTestID = `pay-option-${`${option.amount.display.assetSymbol}-${option.amount.display.networkName}` + .toLowerCase() + .replace(/\s+/g, '-')}`; + return ( - - } - onIconRightPress={hasCollectData ? onInfoPress : undefined} - onPress={() => onOptionPress(option)} - /> + + + } + onIconRightPress={hasCollectData ? onInfoPress : undefined} + onPress={() => onOptionPress(option)} + /> + ); })} From bda83026503e8986c1965f11eb98fe1740e1dadb Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 11 Jun 2026 17:18:40 -0300 Subject: [PATCH 5/5] fix(e2e-balance-check): ask for 1 USDT0 on Polygon (was 5 USDT) Lower the Polygon stablecoin threshold default from 5 to 1 to match the other balances, and label the faucet request "USDT0" (the asset the Polygon option actually uses) instead of "USDT". Co-Authored-By: Claude Opus 4.8 --- .github/workflows/e2e-balance-check.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/e2e-balance-check.yml b/.github/workflows/e2e-balance-check.yml index d087e43a8..990e24a76 100644 --- a/.github/workflows/e2e-balance-check.yml +++ b/.github/workflows/e2e-balance-check.yml @@ -128,17 +128,17 @@ jobs: "${{ vars.TEST_WALLET_ADDRESS }}" | sed 's/\[.*\]//' | tr -d '[:space:]') echo "balance=$BALANCE" >> $GITHUB_OUTPUT BALANCE_HUMAN=$(echo "scale=2; $BALANCE / 1000000" | bc) - echo "USDT on Polygon: $BALANCE_HUMAN ($BALANCE raw)" + echo "USDT0 on Polygon: $BALANCE_HUMAN ($BALANCE raw)" - name: Prepare USDT on Polygon alert id: poly_usdt_alert run: | BALANCE="${{ steps.poly_usdt_balance.outputs.balance }}" - THRESHOLD="${{ vars.USDT_POLYGON_THRESHOLD_UNITS || '5000000' }}" + THRESHOLD="${{ vars.USDT_POLYGON_THRESHOLD_UNITS || '1000000' }}" if [ "$BALANCE" -lt "$THRESHOLD" ]; then BALANCE_HUMAN=$(echo "scale=2; $BALANCE / 1000000" | bc) THRESHOLD_HUMAN=$(echo "scale=2; $THRESHOLD / 1000000" | bc) - ALERT_TEXT="[request] Can i have ${THRESHOLD_HUMAN} USDT on Polygon in wallet: \ + ALERT_TEXT="[request] Can i have ${THRESHOLD_HUMAN} USDT0 on Polygon in wallet: \ ${{ vars.TEST_WALLET_ADDRESS }}" echo "should_alert=true" >> "$GITHUB_OUTPUT" echo "text=$ALERT_TEXT" >> "$GITHUB_OUTPUT"