Skip to content

docs: add USDC payments dApp guide (BasePay on Base Mainnet)#1579

Open
osr21 wants to merge 2 commits into
base:masterfrom
osr21:add-basepay-usdc-payments-guide
Open

docs: add USDC payments dApp guide (BasePay on Base Mainnet)#1579
osr21 wants to merge 2 commits into
base:masterfrom
osr21:add-basepay-usdc-payments-guide

Conversation

@osr21

@osr21 osr21 commented Jun 3, 2026

Copy link
Copy Markdown

Summary

This guide shows developers how to build a production-grade USDC payments dApp on Base Mainnet — covering the full stack from wallet connection through on-chain smart contract interactions.

Reference implementation: BasePay — live on Base Mainnet
Source code: github.com/osr21/basepay-dapp

What the guide covers

  • Wallet connection with wagmi v2 + viem on Base Mainnet
  • Direct USDC transfer and router-based sends with atomic 0.30% fee deduction
  • Batch payments to up to 200 recipients in a single transaction (BatchPay contract)
  • Shareable payment request links + QR codes
  • Trustless USDC escrow with timeout refund (Escrow contract)
  • Recurring pull-payment subscriptions (SubscriptionManager contract)
  • Security checklist: bounded approvals, CEI pattern, pause mechanism, BaseScan verification

Contracts deployed on Base Mainnet (source-verified)

Contract Address
BasePayRouter 0x2d7ba7ed34f8fa16fe4d0d11b51306dc753812c8
BatchPay 0x82569caf7847040a03ad2c6545ade5af2bdcf47c
Escrow 0x5b3241a47acfda41f15dfd7260339e2a88d52318
SubscriptionManager 0x546093b0476b4b7909cd84f3a0fef813c421d14a

Changes

  • docs/apps/guides/usdc-payments-dapp.mdx — new guide page
  • docs/docs.json — added apps/guides/usdc-payments-dapp to the Apps > Guides navigation group

Fits naturally alongside the existing "base-notifications" technical guide and "migrate-to-standard-web-app" guide in the Apps section.

@cb-heimdall

Copy link
Copy Markdown
Collaborator

🟡 Heimdall Review Status

Requirement Status More Info
Reviews 🟡 0/2
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 0
Global minimum 0
Max 1
1
1 if commit is unverified 1
Sum 2

@osr21

osr21 commented Jun 3, 2026

Copy link
Copy Markdown
Author

check github page for documentation about dapp, some fixes applied, but overall need some feedback

@osr21

osr21 commented Jun 5, 2026

Copy link
Copy Markdown
Author

Updating this PR with what's changed since it was opened:

Security audit completed (June 2026) — full findings and fixes documented at docs/SECURITY.md. Key fixes applied to the gasless relayer:

  • Nonce replay race condition guard (in-memory pending-nonce set)
  • Input validation: zero value rejected, value capped at 1M USDC, zero address rejected, self-transfer rejected
  • Module-level wallet client singleton (private key parsed once at startup)

Gasless transfer guide addeddocs/GASLESS.md covers the full EIP-3009 relay system including: typed data structure, relay API reference with all validation rules, relayer wallet requirements, and the MetaMask Blockaid false-positive explanation.

V2 contract addresses — all four contracts redeployed at new addresses (Solidity 0.8.35, source-verified on BaseScan):

Contract V2 Address
BasePayRouter 0x756f516cdf5eb98e140eba44119b22fc0f0bb63f
BatchPay 0xe40d2292c050566d16cecda74627b70778806c68
Escrow 0x1eb2b1e8dda64fc4ccb0537574f2a2ca9f307499
SubscriptionManager 0x101918a252b3852ac4b50b7bbf2525d3084d5421

Multi-contract integration pattern — for any reviewer or developer reading this, the integration pattern that makes BasePay work across four contracts with a single USDC approval is worth documenting:

Each contract (BatchPay, Escrow, SubscriptionManager) calls USDC.transferFrom(msg.sender, ...) internally. The user approves the exact gross amount to each contract address individually before calling. This is an explicit design choice over a universal router pattern — it keeps each contract independently auditable and avoids the risk of an infinite approval to a shared router. The BasePayRouter is the only contract that splits fee vs. net atomically in one transferFrom, making it the recommended pattern for single sends with fee deduction.

Live: https://base-pay.replit.app

@osr21

osr21 commented Jun 8, 2026

Copy link
Copy Markdown
Author

Update — June 2026: Gasless USDC ↔ EURC Swap via Aerodrome Finance

Since the last update on this PR, BasePay has added a new gasless swap feature that may be worth documenting as a separate guide. Summarising the key technical findings below in case it's useful for the docs team.


What was built: A two-step gasless relay that lets users swap USDC ↔ EURC on Base Mainnet with a single EIP-3009 off-chain signature and zero ETH. The relay:

  1. Calls token.transferWithAuthorization(user → relay) (TX1, confirmed on-chain)
  2. Calls router.swapExactTokensForTokens(relay → Aerodrome pool → user) (TX2)

Reference implementation: github.com/osr21/basepay-dapp/blob/main/docs/GASLESS_SWAP.md


Three production bugs encountered and fixed — documented in detail:

1. Expired() revert — server clock drift
Production server Date.now() runs behind Base's block.timestamp by enough that a 5-minute router deadline expires before TX2 lands. Fix: set deadline to now + 7200s.

2. eth_estimateGas false-revert — pending state
viem calls eth_estimateGas before sending TX2. This simulates against pending block state, which doesn't yet include TX1's confirmed token balance at the relay. The router's transferFrom sees zero balance and reverts. Fix: explicitly set gas: 350_000n on TX2's writeContract call to bypass estimation entirely.

3. HTTP 500 on production proxy — receipt wait timeout
Waiting for TX2's waitForTransactionReceipt inside the HTTP handler caused Replit's production proxy to kill the connection (~15–20s round trip). Fix: return txHash immediately after submission; don't await the receipt in the HTTP response.


Confirmed live on Base Mainnet:

Happy to expand any of these into a dedicated Base docs guide if useful.

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.

2 participants