Skip to content

feat(pay-tests): add USDT-on-Polygon Permit2 pay flow + allowance-reset script#97

Draft
ignaciosantise wants to merge 4 commits into
masterfrom
feat/maestro-pay-usdt-arbitrum
Draft

feat(pay-tests): add USDT-on-Polygon Permit2 pay flow + allowance-reset script#97
ignaciosantise wants to merge 4 commits into
masterfrom
feat/maestro-pay-usdt-arbitrum

Conversation

@ignaciosantise

@ignaciosantise ignaciosantise commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

What

Adds a shared Maestro Pay flow for a USDT (Permit2) payment on Polygon, plus the cross-platform allowance-reset helper it depends on.

Files

  • .maestro/pay_usdt_polygon.yaml — creates a payment (MULTI_NOKYC merchant, which offers USDT on Polygon), opens the wallet, selects the USDT/Polygon option, pays, and asserts the success screen. Tagged pay + pay-usdt-polygon.
  • scripts/revoke-permit2-approval.js + scripts/package.json — shared CI-helper that resets the Permit2 allowance back to 0 after the test, so every run re-exercises the approve step. Pins ethers@5.
  • README.md — new pay-loading-setup-note testID, a Test Catalog row, and a "Permit2 tokens & the allowance reset" section.

Why Polygon (not Arbitrum)

The wallet just executes whatever actions the Pay backend returns; it does no local allowance check. USDT on Arbitrum (0xFd086…) is EIP-3009 (transferWithAuthorization, signature-based / gasless), so the backend never returns an on-chain approve action — no gas, no approval step, and the approve path we want to cover would never run. USDT on Polygon (0xc2132D…) is a plain ERC-20, so WC Pay uses the Permit2 path (one-time on-chain approve(PERMIT2) + signature) — exactly the two-tx approve + pay flow under test.

Key design notes

  • The reset is a real Node step, not a Maestro runScript. Maestro's runScript runs in a GraalJS sandbox (no require, no signing), so it can't sign the reset tx. The script lives in a sibling scripts/ CI-helper dir (not .maestro/scripts/) and is run as a post-test Node step.
  • --walletAddress is optional — derived from the private key when omitted; verified to match when passed. Consumers need only the key + an RPC.
  • The approve step is observed softly (a when: visible screenshot on pay-loading-setup-note, never an assertion) since the setup note can flash by quickly; the success screen is the only hard assertion.
  • Reset runs --chainId eip155:137 --rpcUrl https://polygon-rpc.com (Polygon min priority fee defaults to 25 gwei in the script).

Consumer wiring (react-native-examples)

Companion PR reown-com/react-native-examples#533 wires the post-test reset step into its WalletKit Maestro action, adds USDT + POL(gas)-on-Polygon balance monitoring, and pins maestro/pay-tests to this PR's head SHA for testing (re-pin to the merge commit once this lands).

Open items / reviewer notes

  • ⚠️ Confirm the merchant + option selector: the flow assumes MULTI_NOKYC surfaces USDT on Polygon and that tapOn: text: ".*USDT.*" is unambiguous (no USDT on a second network). Pin with maestro studio before un-drafting.
  • The test wallet must hold USDT and a little POL (gas) on Polygon.
  • Possible follow-up: wrap the reset in a thin maestro/permit2-reset composite action so consumers uses: it instead of copy-pasting the run step.

🤖 Generated with Claude Code

…set script

Add pay_usdt_arbitrum.yaml: creates a payment, selects USDT on Arbitrum, pays,
and asserts the success screen. USDT is a Permit2 token, so the wallet sends an
approve (allowance) tx then the payment tx; the flow best-effort observes the
approve step via the new pay-loading-setup-note testID.

To keep the approve step exercised on every run, consumers must reset the
Permit2 allowance to 0 after the test. Add a shared CI-helper Node script
(scripts/revoke-permit2-approval.js + package.json pinning ethers@5) for this.
It signs a transaction, so it lives outside .maestro/scripts/ and runs as a Node
post-test step, not a Maestro runScript (the GraalJS sandbox can't sign).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@claude

claude Bot commented Jun 11, 2026

Copy link
Copy Markdown

Claude finished @ignaciosantise's task in 2m 10s —— View job


Code Review

  • Gather context
  • Read changed files
  • License compliance check — ✅ ethers@5.8.0 is MIT, no issues
  • Analyze and post findings

Found 3 issue(s)

Issue 1: Private key exposed in OS process list via CLI argument

ID: revoke-permit2-privatekey-cli-exposure-a3f1
File: maestro/pay-tests/scripts/revoke-permit2-approval.js:230
Severity: HIGH
Category: security

Context:

  • Pattern: --privateKey is accepted as a CLI argument and expanded by the shell before the Node process starts. The full key value lands in /proc/<pid>/cmdline and is visible via ps auxww for the lifetime of the script.
  • Risk: Any process running as the same OS user (or root) can read the key from the process table. In shared CI runners this is a real attack surface; shell history can also capture it.
  • Impact: Compromise of the test wallet holding mainnet USDT + ETH on Arbitrum.
  • Trigger: Any CI step that passes --privateKey "$SECRET" directly.

Recommendation: Accept the private key from an environment variable as a fallback so consumers can avoid passing it on the command line:

// in parseArgs / normalizePrivateKey flow
const privateKeyInput = args.privateKey || process.env.PERMIT2_REVOKE_PRIVATE_KEY;
const privateKey = normalizePrivateKey(privateKeyInput);

Then document the env-var path as the preferred CI usage:

PERMIT2_REVOKE_PRIVATE_KEY="$TEST_WALLET_PRIVATE_KEY" \
  node revoke-permit2-approval.js --chainId eip155:42161 --rpcUrl ...

Issue 2: tx.wait() has no timeout — CI will hang on a dropped transaction

ID: revoke-permit2-txwait-no-timeout-b7c4
File: maestro/pay-tests/scripts/revoke-permit2-approval.js:311
Severity: MEDIUM
Category: reliability

Context:

  • Pattern: await tx.wait() (ethers v5) waits indefinitely; it has no built-in timeout parameter.
  • Risk: A stuck or underpriced transaction (mis-estimated fee, mempool congestion) will block the script forever, consuming the entire CI job timeout.
  • Impact: CI jobs stall until their wall-clock limit is hit, blocking pipelines.
  • Trigger: Any network hiccup, dropped tx, or fee spike on Arbitrum after submission.

Recommendation: Wrap with a race against a deadline:

const WAIT_TIMEOUT_MS = 120_000; // 2 min — generous for Arbitrum L2
const receipt = await Promise.race([
  tx.wait(),
  new Promise((_, reject) =>
    setTimeout(() => reject(new Error(`tx.wait() timed out after ${WAIT_TIMEOUT_MS / 1000}s`)), WAIT_TIMEOUT_MS),
  ),
]);

Issue 3: External domain arb1.arbitrum.io outside the allowed list

ID: revoke-permit2-external-rpc-domain-2e9d
File: maestro/pay-tests/scripts/revoke-permit2-approval.js:26 and README.md:183
Severity: LOW
Category: security

🔒 External Domain URL (Non-blocking)

  • URL: https://arb1.arbitrum.io/rpc
  • Allowed domains: reown.com, walletconnect.com, walletconnect.org
  • This is intentional (the PR documents the reason: WC Blockchain API gates eth_blockNumber on Arbitrum), but it should be confirmed as a deliberate policy exception. Consider whether rpc.walletconnect.org can be made to work with Arbitrum in the future to remove this external dependency.

One additional note (open item from PR description): The tapOn: text: ".*USDT.*" selector (pay_usdt_arbitrum.yaml:33) is ambiguous if the MULTI_NOKYC merchant offers USDT on multiple networks. The downstream assertVisible: id: "pay-review-token-arbitrum" would catch the wrong selection, but a more specific selector (e.g. index-based pay-option-{index}) would be cleaner. This is already flagged as an open item; just confirming the concern is valid.

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. Rename the flow to
pay_usdt_polygon (tag pay-usdt-polygon), assert pay-review-token-polygon, and
update the reset invocation to --chainId eip155:137 --rpcUrl https://polygon-rpc.com.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ignaciosantise ignaciosantise changed the title feat(pay-tests): add USDT-on-Arbitrum Permit2 pay flow + allowance-reset script feat(pay-tests): add USDT-on-Polygon Permit2 pay flow + allowance-reset script Jun 11, 2026
ignaciosantise and others added 2 commits June 11, 2026 15:17
polygon-rpc.com returns HTTP 401 "tenant disabled" from CI runners; use
https://polygon-bor-rpc.publicnode.com (keyless) in the allowance-reset example.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The merchant lists USDT on multiple networks (e.g. Arbitrum + Polygon), so a
"USDT" text match selected the wrong row (Arbitrum, EIP-3009, no approve). Select
the Polygon option by its stable `pay-option-usdt-polygon` testID (scrolling it
into view first). Document the additive `pay-option-{assetSymbol}-{networkName}`
id in the README.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant