Skip to content

WalletConnect Pay USDT support

Choose a tag to compare

@ignaciosantise ignaciosantise released this 19 May 14:20
c791039

This release captures the changes that make the React Native CLI wallet a working reference implementation for WalletConnect Pay with ERC-20 / USDT support, end-to-end. It is intended as a documentation/reference checkpoint for other wallet teams integrating WCPay — this repo has no prior GitHub releases and does not follow a semver product stream.

Scope is intentionally narrow: only the three PRs listed under Included PRs below. Unrelated CI, dependabot, and other-wallet changes on main between those PRs are not part of this release. See the attached patches for the exact diff.


What this enables

  • First-time USDT (and other ERC-20) payments on chains that require Permit2 (e.g. Polygon). The wallet now executes the WCPay-returned two-action flow — an eth_sendTransaction (Permit2 approval) followed by an eth_signTypedData_v4 (Permit2 typed-data signature) — and forwards the per-action results to confirmPayment in order.
  • Repeat USDT payments with no approval step. After the one-time Permit2 setup, WCPay returns a single typed-data action; the wallet signs and submits without an on-chain tx.
  • Native-token payments (e.g. ETH on Base) now complete reliably. The wallet broadcasts the action's eth_sendTransaction, waits for confirmation, and includes the resulting transaction hash in the signatures array passed to confirmPayment. Prior to this release, native flows posted an empty signatures array and the backend rejected confirmation with 400: Validation error: Next result not present.
  • Per-option fee estimates and one-time-setup UX so users can compare options and understand why USDT requires an extra step the first time.

Implementation requirements for other wallets

These are the wallet-integration contracts other implementers must follow. They are the public takeaway from this release.

  1. Call pay.getRequiredPaymentActions({ paymentId, optionId }) exactly once, at the moment the user confirms payment — not during option preview, not when fee estimates are computed, not on selection change. The call is committal: once it returns, the wallet must execute every action and call confirmPayment. There is no "preview" or "go back" path after this point. Surface fee estimates and option previews from your own price/fee pipeline (the reference wallet uses PaymentTransactionUtil), not from this RPC. Do not assume one action per option — the returned array can have one or more entries.
  2. Execute every returned action, in order. The action array is ordered; you must execute index 0, then 1, etc. Do not reorder, skip, or batch.
  3. For eth_sendTransaction actions: broadcast the tx, wait for confirmation, then append the transaction hash to a signatures array. (This is the rule #495 enforces. It applies to native-token payments and to Permit2 approval txs.)
  4. For eth_signTypedData, eth_signTypedData_v3, eth_signTypedData_v4 actions: sign the typed data and append the returned signature to the same signatures array.
  5. Maintain the invariant signatures.length === paymentActions.length when calling pay.confirmPayment({ paymentId, optionId, signatures }). Ordering must match the action ordering returned by WCPay — index i in signatures corresponds to action i.
  6. Reject unknown wallet-RPC methods. Throw rather than silently skipping; partial result arrays will be rejected by the backend.

Reference implementation: PaymentStore.approvePayment — this handler runs when the user presses PAY. getRequiredPaymentActions is called once at line 519, and only inside this handler — never during option preview. The loop at lines 529–667 is the canonical action-chain executor; the signatures.push(tx.hash) at line 613 is the rule from #495.


Recommended UX

These are guidance, not protocol requirements. The reference wallet implements all of them.

  • Preload per-option fee estimates before the user picks an option, so options can be compared with realistic gas. See PaymentTransactionUtil and the fee-estimate pipeline added in #480.
  • Show one-time-setup messaging when the action chain includes an approval. The reference wallet uses shouldShowSetupLoader (PaymentUtil.ts) — true when actions.length > 1 and an approval action is present — and surfaces "Setting up TOKEN for one-time setup… Future TOKEN payments will be instant."
  • Explain gas separately when ERC-20 payments carry a native-token gas cost. The reference wallet ships a gas explainer view.
  • Persist the last-paid token unit for faster repeat payments — auto-select it next time the user pays, but only when the saved unit is present in the new payment's available options. If the saved unit isn't offered (different merchant, different chains, expired token, etc.), fall back to manual option selection. The reference wallet does this via findPreferredOption(options, lastPaidTokenUnit) returning null when there's no match.
  • Guard against stale action fetches (e.g. user changes option mid-fetch) and locally-expired payments (drop the request before signing). The reference wallet implements both.

Validate your integration

Run your wallet through these flows to confirm it implements the contracts above correctly:

  • First-time ERC-20 payment requiring Permit2: approval eth_sendTransaction succeeds and is confirmed, followed by an eth_signTypedData_v4 signature; confirmPayment receives [txHash, signature] and returns succeeded.
  • Repeat ERC-20 payment (Permit2 already in place): a single eth_signTypedData_v4; confirmPayment receives [signature] and returns succeeded. No approval prompt is shown.
  • Native-token payment: a single eth_sendTransaction; the broadcast tx hash is included in signatures; confirmPayment receives [txHash] and returns succeeded. (Regression test for #495.)
  • Rapid option switching: repeatedly tapping different options does not leak stale fee or action data into the currently selected option (stale-fetch guard).
  • Locally-expired payment: if expiresAt has passed before the user taps "Approve", the wallet sets an expired result and does not prompt for signatures.

Included PRs

This release contains exactly these three merged PRs and nothing else. Per-PR and combined patches are attached to this release for verification.


Tagged at c791039e on origin/main.