Skip to content

feat: add Gasless USDC Payments demo (EIP-3009 transferWithAuthorization) - #136

Open
osr21 wants to merge 3 commits into
base:masterfrom
osr21:gasless-usdc-payments
Open

feat: add Gasless USDC Payments demo (EIP-3009 transferWithAuthorization)#136
osr21 wants to merge 3 commits into
base:masterfrom
osr21:gasless-usdc-payments

Conversation

@osr21

@osr21 osr21 commented Aug 15, 2026

Copy link
Copy Markdown

What

Adds a minimal, runnable demo of gasless USDC transfers on Base using EIP-3009 transferWithAuthorization, under apps/gasless-usdc-payments/:

  • pages/index.tsx — connect an EOA wallet, sign the EIP-712 TransferWithAuthorization typed data (no ETH needed), POST to the relay endpoint
  • pages/api/relay.ts — validates the authorization and submits it onchain, paying gas
  • lib/eip3009.ts — USDC domain/types/addresses for Base + Base Sepolia
  • README with a flow diagram and a production security checklist

Also adds a row to the root README demo table.

Why

"User has USDC but no ETH" is one of the most common onboarding walls on Base, and EIP-3009 is the canonical answer for EOA wallets — yet there's no demo of the pattern in this repo (the paymaster demos cover contract-wallet sponsorship, not EOA authorization relaying). This distills the pattern we run in production at BasePay into the smallest correct example.

Deliberate design choices

  • validBefore is capped at 1 hour server-side — signed authorizations are bearer instruments; the demo teaches the cap rather than leaving it to a footnote.
  • The relay endpoint rejects smart-contract senders (getCode check) with an actionable error, because EIP-3009 signatures can't come from ERC-4337/ERC-1271 accounts and the alternative (letting it revert at submission) wastes the user's signature and the relayer's gas. This is the single most common integration failure we've seen with this pattern.
  • Explicit validation, no silent fallbacks — malformed input fails with a specific 400; revert reasons (authorization is used, etc.) are surfaced to the client.
  • Defaults to Base Sepolia with Circle faucet instructions; mainnet is a one-variable switch.

Testing

The signing payload, domain values, and relay flow are distilled from the EIP-3009 relayer we run in production at BasePay on Base mainnet (same domain/types/ABI, same validation checklist). This extracted demo has been reviewed for correctness against that implementation but not yet exercised end-to-end on Base Sepolia as a standalone app — happy to record a run-through or add adjustments if maintainers want changes to structure or dependencies first.

@osr21

osr21 commented Aug 15, 2026

Copy link
Copy Markdown
Author

End-to-end test results — Base Sepolia ✅

I exercised the relay endpoint against Base Sepolia before this review. One bug was found and fixed in this PR: the EIP-712 domain name.

Bug fixed: EIP-712 name mismatch

Base Sepolia testnet USDC (0x036CbD53842c5426634e7929541eC2318f3dCF7e) returns name() = "USDC", not "USD Coin". The original usdcDomain() hardcoded "USD Coin", causing transferWithAuthorization to revert with FiatTokenV2: invalid signature on Sepolia.

Fix: NETWORKS now carries a usdcName field per network ("USD Coin" for mainnet, "USDC" for Sepolia). usdcDomain() accepts it as a parameter. See commit 2038bb1.


Test results

Test Result
Happy path: 0.01 USDC transfer on Base Sepolia ✅ confirmed on-chain
Expired validBefore ✅ rejected before submission
validBefore > 1-hour cap ✅ rejected before submission
Malformed nonce (< 32 bytes) ✅ rejected before submission
Smart-contract sender (EOA check via getCode) ✅ detected and would reject

Successful transfer tx: 0x1480796...7428ea — block 45518363, Base Sepolia

TypeScript compiles clean (tsc --noEmit, no errors).

osr21 added 2 commits August 15, 2026 15:46
Base Sepolia testnet USDC returns name() = "USDC", not "USD Coin".
Using the wrong name causes FiatTokenV2: invalid signature on Sepolia.

- Add usdcName to each NETWORKS entry
- Update usdcDomain() to accept name as a parameter

Verified on Base Sepolia: tx 0x148079622924611868790ee3997098cb82494813894ccd62b93a89420b7428ea (block 45518363)
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