The simple WalletConnect Pay flow signs an EIP-3009Documentation Index
Fetch the complete documentation index at: https://docs.walletconnect.com/llms.txt
Use this file to discover all available pages before exploring further.
transferWithAuthorization typed-data payload — the gasless transfer used by USDC and similar stablecoins. USDT does not implement EIP-3009, so WalletConnect Pay routes it through the Permit2 contract instead.
The first time a user pays a given token on a given chain, the wallet must approve Permit2 on-chain. Subsequent payments on the same token only require the typed-data signature.
The two-action flow
When an option needs a Permit2 approval,getRequiredPaymentActions returns two actions — in the order they must execute:
eth_sendTransaction— ERC-20approve(Permit2, amount)on the token contracteth_signTypedData_v4— Permit2 typed-data payload authorizing the transfer
Detecting an approval-required option
Inspect the action list for aneth_sendTransaction entry. In standalone SDK flows, this means option.actions; in WalletKit flows, this means the actions returned by getRequiredPaymentActions. If one is present, the option is a Permit2 flow and your wallet should prepare for an on-chain step.
- Kotlin
- Swift
- TypeScript
- Dart
Showing the approval gas estimate
The approve tx costs native gas (e.g. POL on Polygon, ETH on mainnet) — separate from the token amount the user is paying. Estimate it and show the user the expected fee before they confirm so they aren’t surprised by a wallet prompt for a one-time on-chain cost. This can live wherever your wallet collects user intent — a review screen, a confirmation sheet, or inline on the option row.- Kotlin
- Swift
- TypeScript
- Dart
Executing the actions in order
Because the Permit2 typed data signs against the token allowance the approve tx grants, the approve must be mined before you sign.- Submit
eth_sendTransaction(action 1) and wait for the receipt. - Parse the typed data from
eth_signTypedData_v4(action 2) and sign it. - Push both results into
signatures[]in the order the actions were returned. - Call
confirmPayment(signatures).
- Kotlin
- Swift
- TypeScript
- Dart
EIP-712 library quirks. EIP-712 signing libraries disagree on whether
types.EIP712Domain must be present in the payload. The Permit2 typed data returned by WalletConnect Pay omits it.- Libraries that require
EIP712Domain(e.g.eth_sig_util_pluson Flutter, some Android libraries): synthesize anEIP712Domainentry intypesfrom the fields actually present indomainbefore signing. - Libraries that reject
EIP712Domain(e.g. ethers v5_signTypedData): strip it fromtypesbefore signing. - Yttrium’s
signTypedDatais currently hardcoded to ERC-3009 (from/to/value/validAfter/validBefore/nonce) and rejects Permit2. Wallets using Yttrium need a generic EIP-712 hasher — seeEIP712TypedData.swiftfor a reference implementation.
Loader UX
The two-action flow has two distinct user-visible steps that can each take several seconds:- The approve tx is broadcast and you wait for the receipt.
- The user is prompted to sign the Permit2 typed data.
Recommended UX
Follow these patterns to provide the best user experience when implementing USDT and Permit2-based payments:Pre-load fee estimates
Preload per-option fee estimates before user selection. This allows users to make informed decisions by comparing realistic gas costs across different payment options.Display one-time setup messaging
When approval actions are present (first-time Permit2 setup), clearly communicate to users that this is a one-time on-chain approval. Subsequent payments with the same token will only require a signature.Separate gas cost display
Explain native-token gas costs separately from the token amounts being paid. Users should understand that:- The token amount goes to the merchant
- The gas fee (in the chain’s native token) goes to network validators
Remember user preferences
Retain and auto-select the last-paid token when it’s available in future payments. This reduces friction for repeat users who prefer specific payment methods.Guard against stale data
Implement safeguards against stale data from rapid option switching and expired payment sessions. Always validate that payment data is current before executing actions.Validate your integration
Before shipping your USDT support implementation, verify these scenarios work correctly:First-time ERC-20 with Permit2
Test a payment with a token requiring Permit2 approval for the first time. The flow should:
- Present the approval transaction first
- Wait for on-chain confirmation
- Then prompt for the Permit2 signature
- Complete successfully with both results in
signatures[]
Repeat ERC-20 payment
After completing the first-time flow, make another payment with the same token. This should:
- Skip the approval transaction entirely
- Only require a single Permit2 signature
- Complete faster than the first-time flow
Native token payment
Test a payment using the chain’s native token (e.g., ETH on Base). Verify that:
- The transaction hash is correctly included in
signatures[] - The payment completes successfully
Rapid option switching
Quickly switch between payment options and verify:
- No stale data leaks between options
- Fee estimates update correctly for each option
- The correct actions execute for the final selected option
Sample wallets
The four reference wallets below all implement this two-action flow and are good starting points to copy from.React Native
wallets/rn_cli_wallet — see PaymentUtil.ts, PaymentTransactionUtil.ts, PaymentStore.ts.Kotlin
sample/wallet — see PaymentUtil.kt, PaymentTransactionUtil.kt, PaymentViewModel.kt.Swift
Example/WalletApp — see PaymentUtil.swift, PayTransactionService.swift, PayPresenter.swift, EIP712TypedData.swift.Flutter
packages/reown_walletkit/example — see evm_service.dart, wcp_payment_details.dart, wcp_confirming_payment.dart.