WalletConnect Pay USDT support
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 aneth_signTypedData_v4(Permit2 typed-data signature) — and forwards the per-action results toconfirmPaymentin 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 thesignaturesarray passed toconfirmPayment. Prior to this release, native flows posted an emptysignaturesarray and the backend rejected confirmation with400: 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.
- 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 callconfirmPayment. 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 usesPaymentTransactionUtil), not from this RPC. Do not assume one action per option — the returned array can have one or more entries. - Execute every returned action, in order. The action array is ordered; you must execute index
0, then1, etc. Do not reorder, skip, or batch. - For
eth_sendTransactionactions: broadcast the tx, wait for confirmation, then append the transaction hash to asignaturesarray. (This is the rule #495 enforces. It applies to native-token payments and to Permit2 approval txs.) - For
eth_signTypedData,eth_signTypedData_v3,eth_signTypedData_v4actions: sign the typed data and append the returned signature to the samesignaturesarray. - Maintain the invariant
signatures.length === paymentActions.lengthwhen callingpay.confirmPayment({ paymentId, optionId, signatures }). Ordering must match the action ordering returned by WCPay — indexiinsignaturescorresponds to actioni. - 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
PaymentTransactionUtiland 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 whenactions.length > 1and 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_sendTransactionsucceeds and is confirmed, followed by aneth_signTypedData_v4signature;confirmPaymentreceives[txHash, signature]and returnssucceeded. - Repeat ERC-20 payment (Permit2 already in place): a single
eth_signTypedData_v4;confirmPaymentreceives[signature]and returnssucceeded. No approval prompt is shown. - Native-token payment: a single
eth_sendTransaction; the broadcast tx hash is included insignatures;confirmPaymentreceives[txHash]and returnssucceeded. (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
expiresAthas passed before the user taps "Approve", the wallet sets anexpiredresult 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.
- #472 — feat(rn_cli_wallet): support Permit2 two-action payment flow (squash
91d19034). Protocol enabler: introduces the two-action executor inPaymentStore.approvePayment, thePaymentTransactionUtilfee/gas pipeline, the Permit2-revoke test utility, and thegetApprovalAction/requiresApproval/shouldShowSetupLoaderhelpers. - #480 — feat(rn_cli_wallet): redesign payment options UI with per-option fee estimates (squash
a6312b5e). UX layer: per-option preloaded fee estimates, one-time-setup messaging, gas explainer, last-paid-token persistence, shimmer / loading states, and a substantialPaymentStore.test.tssuite. - #495 — fix(wallet): include broadcast tx hash in WCPay signatures (merge
b640016d, fix commit4928f132). Action-chain rule: appends the broadcast tx hash tosignaturesaftereth_sendTransactionactions sosignatures.length === paymentActions.lengthalways holds. Documented here as a generic WCPay contract, not just a bugfix.
Tagged at c791039e on origin/main.