Skip to content

Paymaster v0.7 + Arc Testnet + Permit2 donate + URL-driven network selection#11

Merged
PierreBesson merged 16 commits into
mainfrom
new-paymaster
Jun 10, 2026
Merged

Paymaster v0.7 + Arc Testnet + Permit2 donate + URL-driven network selection#11
PierreBesson merged 16 commits into
mainfrom
new-paymaster

Conversation

@PierreBesson

Copy link
Copy Markdown
Member

Summary

Multi-theme branch that lands the new paymaster, multi-chain support, gasless donations via permits, and a URL-driven network selector.

Paymaster / sponsored gas

  • Upgrade paymaster to EntryPoint v0.7 and add a Kernel sponsorship path.
  • Lock sponsorship to user-facing selectors and add a min-donation floor.
  • Signed setApprovalForAll + paymaster batch validation for gasless flows.
  • Docs rewritten end-to-end to describe the implementation as it stands today.

Networks

  • Add Arc Testnet (icon, native logo, Circle faucet link).
  • Show the chain's native currency symbol on the sponsorship balance UI instead of hardcoded ETH.
  • Work around Reown's embedded-wallet proxy not routing chains it doesn't know about.

Donate

  • Migrate to Uniswap Permit2 with an ERC-2612 fallback; drop pre-existing sponsorship on the EOA path in favour of permits.

URL-driven network selection (final commit)

  • Every page now lives under /[network]/… (e.g. /baseSepolia/programs, /arcTestnet/program/0x…). useTargetNetwork reads the slug from useParams(); useSelectedNetwork falls back to it instead of a (now-removed) zustand slice.
  • New useNetworkHref() wraps every in-app Link / router.push so navigation preserves the slug.
  • <ChainSync /> (mounted in the providers) nudges the wallet to follow the URL — covers deep links, refreshes, and the network-switcher dropdown (which also fires switchChain on click for a snappier prompt).
  • slugForChain now keeps the first viem export per chain id so baseSepoliaPreconf no longer shadows baseSepolia.

Build / infra

  • Bump viem to 2.51.3, enable viaIR, cast burner-connector boundary.

Test plan

  • yarn next:check-types and yarn lint pass.
  • Navigate /baseSepolia/programs and /arcTestnet/program/0x…; the header network badge and balances match the URL, not the wallet chain.
  • From the connect-button dropdown, switch network — URL updates AND the wallet prompts to switch; "Wrong network" no longer sticks.
  • Deep-link /arcTestnet/... while wallet is on Base Sepolia — ChainSync triggers a single switch prompt; user-rejected prompts don't re-fire on the same URL.
  • Sponsored donation on baseSepolia and arcTestnet (Permit2 + paymaster path); contributions land and sponsorship balance shows the right native symbol.
  • Reown embedded wallet works on Arc Testnet despite the proxy gap.

🤖 Generated with Claude Code

PierreBesson and others added 14 commits May 24, 2026 14:48
…n floor

Prevents griefing attacks against an organization's CGPaymaster budget:
- CGCrowdfunding enforces a min donation of 1 full token (10^decimals),
  resolved defensively from IERC20Metadata at deploy time. Sub-unit dust
  donations now revert, so each sponsored donate dwarfs its gas cost.
- CGPaymaster._isValidCall decodes the inner selector from execute(addr,
  uint256,bytes) and only sponsors a fixed allowlist of donor and CGToken
  user ops, blocking the reverting-admin-call drain vector.
- Frontend marks owner-only writes (createProgram, setBeneficiaries,
  execute, returnUntracked, sweepUntracked, ...) as sponsored:false so the
  EIP-5792 path is skipped for calls the paymaster would now reject.

Also fixes pre-existing TypeScript errors that blocked check-types:
narrow useTransactor's receipt via a local const, type the chains.find
predicate, and remove a defunct openfort route reference.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contracts: switch CGPaymaster to PackedUserOperation, the v0.7 postOp signature
(with actualUserOpFeePerGas + postOpReverted no-double-credit), and the v0.7
paymasterAndData layout that interleaves gas limits before our org-address blob.
Outer-selector allowlist now accepts ERC-7579 execute(bytes32,bytes) alongside
the SimpleAccount execute(address,uint256,bytes), so Kernel v3 accounts can be
sponsored too. Refund is also capped defensively against a buggy entrypoint.

Frontend: add a Kernel-via-Pimlico path for EOA wallets that cannot do EIP-5792
(services/web3/smartAccount.ts + useSponsoredUserOp). UserOps go through a new
/api/bundler/[chainId] proxy that injects the server-side Pimlico key and
allowlists only the methods permissionless needs. The paymaster route returns
the v0.7 separate-fields response shape and rejects non-v0.7 entrypoints.

UI: useEffectiveAddress resolves the on-chain identity (connected EOA when the
wallet is itself a smart account, counterfactual Kernel otherwise) so donation
lookups, allowance, balance, and the "View wallet" link all key off the address
that actually appears as msg.sender on contributions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires Circle's Arc Testnet (chainId 5042002) into hardhat, scaffold
target networks, SIWE verification, and the donation-currency catalog
with the verified USDC/EURC ERC-20 addresses, and includes the initial
CGRegistry / CGPaymaster deployment artifacts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds Arc's official mark as the network-chip logo with a padded
viewBox so it sits inside the rounded clip, inlines the per-chain
overrides directly in scaffold.config, and restores the wagmi
useAccount import that the Header was relying on.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Donations and donor refunds (cancelContribution / refund) can't honestly
ride the paymaster: msg.sender must match the ERC-20 token holder, but
USDC's permit is ECDSA-only, so a smart-account msg.sender can't permit-
spend its own balance and can't pull tokens from the EOA either. Move
both flows to direct EOA writes and pick the single-tx donateWithPermit
path when currency.permit is set, falling back to approve + donate
otherwise. Prune the now-unreachable donate/cancel/refund selectors from
CGPaymaster's allowlist. Permit helper resolves the EIP-712 domain via
EIP-5267 with a DOMAIN_SEPARATOR-matching fallback for older bytecode.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wallets render Permit2 signatures with a friendlier UI than raw ERC-2612
permits — the spender is the well-known Permit2 contract instead of an
unverified per-campaign CGProgram. Adds donateWithPermit2 on CGProgram
(minimal IPermit2 interface vendored inline) and switches the frontend
to prefer it when getCode(PERMIT2_ADDRESS) shows the canonical deploy at
0x0000…22D473030F116dDEE9F6B43aC78BA3 is live on the active chain. Chains
without Permit2 (localhost) fall back to the existing donateWithPermit
path. Permit helper gains signPermit2 alongside the existing 2612 path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On Arc Testnet the native gas token is USDC, not ETH — surface the USDC
mark in the native-asset row instead of the Ethereum diamond by looking
up the logo from a symbol→asset map (falls back to ETH for any other
chain). In the Receive USDC / Receive EURC top-up modal, when the
connected chain is Arc Testnet, replace the CDP-proxied faucet button
(which doesn't route to Arc) with an external link to Circle's faucet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
viem 2.39.0 was below permissionless@0.3.5's ^2.44.4 peer-dep and
@reown/appkit-adapter-wagmi@1.8.19's >=2.45.0 peer-dep, which made
permissionless's toKernelSmartAccount silently produce account.address
== zeroAddress for some Kernel derivation paths (the sender: 0x0 in
UserOp dumps that turned into AA13). Pin to 2.51.3 which satisfies
every dependent.

Enabling viaIR shrinks every contract by 10-15% on the Yul IR pipeline.
CGComponentFactory embeds creation bytecode for all three child
contracts and bumped against the 24576-byte EIP-170 cap after the
CGToken signed-approval additions; with viaIR it lands at 22149 bytes
with margin to spare.

burner-connector@0.0.20 ships its own pinned RainbowKit + @wagmi/core
under node_modules/burner-connector/node_modules/. After the viem
bump those nested copies emit a structurally identical Wallet/Connector
type that TS treats as a distinct nominal type. Runtime is fine; cast
at the connectorsForWallets boundary instead of forcing a yarn
resolution.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CGToken gains setApprovalForAllWithSignature, an EIP-2612-style permit
for ERC-1155 operator approval. SignatureChecker handles both ECDSA and
ERC-1271, so both EOA and smart-account owners can authorize the
sponsored Kernel as operator with an off-chain typed-data signature —
no gas prep tx required.

CGPaymaster's allowlist gains the new selector and now validates ERC-
7579 batch executions (callType 0x01): every inner call is checked
against the same selector + org-ownership rules as single executions.
Lets the frontend bundle setApprovalForAllWithSignature + the actual
transfer into one sponsored UserOp.

Frontend wiring:
- useSponsoredUserOp.sendCalls / useSponsoredWrite.writeBatch accept
  multiple calls and forward them as one Kernel UserOp or one EIP-5792
  sendCalls envelope.
- useCGTokenWrite probes isApprovedForAll(owner, smartAccount) before
  sponsored transfers/burns. On first use, it signs the typed-data
  permit and batches the approval + actual call in one UserOp. Resolves
  the operator address synchronously via deriveKernelAddress to avoid
  racing useEffectiveAddress's useEffect-driven derivation.
- smartAccount.ts swaps the shallow serializeUserOp helper for a
  BigInt-aware JSON.stringify replacer (catches nested bigints in the
  EIP-7702 authorization stub field that prepareUserOperation generates
  during gas estimation), and pins useMetaFactory: false because Arc
  Testnet's MetaFactory deployment skipped the post-deploy
  approveFactory step and reverts every call with 0xc88357cc.

Tests: DEFAULT_INNER_DATA switched from the (now-pruned) donate(1) to
setApprovalForAll(0, true). Stale "rejects batch execution" test
replaced with four new cases covering accept/reject on inner-selector,
inner-target, and empty-batch axes. Full suite: 240 passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ardcoded ETH

CGOrganizationView's "Gas Sponsorship Balance:" row and the
OrgGasSponsorshipBadge tooltip both hardcoded the suffix "ETH", which
reads as "0 ETH" on Arc Testnet where the native gas token is USDC.
Pull the symbol from targetNetwork.nativeCurrency, falling back to ETH
for any chain whose definition omits it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Expands the original goals-only spec with the actual implementation:
CGPaymaster, the Kernel-via-Pimlico and EIP-5792 smart-account paths,
the /api/bundler and /api/paymaster routes, what selectors are sponsored
(with rationale), what's intentionally not sponsored (admin ops,
donations after the EOA-direct migration), the signed-approval bootstrap
for first-time CGToken transfers, org gas-budget lifecycle, and known
operational constraints (Reown chain coverage, EIP-7702 future path).

Keeps the role-based framing from the original spec; corrects where
reality diverged (org admin ops aren't sponsored — drain-prevention)
and explains why.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ay's state

Drop self-referential framing ("original spec", "preserved", "where
reality diverged", commit hashes for historical context) and describe
the system as it works now: who is sponsored, who pays their own gas,
and why each path exists. Role sections (beneficiaries, donors, org
owners, registry owner) explain each role's current experience and
rationale without reference to earlier iterations. The donate/cancel/
refund subsection in §4 gets the same treatment.

Cross-reference fix: signed-approval bootstrap is §5 (was §6 in the
selector-table footnote).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three fixes that together let social/email-login users transact on
chains (notably Arc Testnet) that Reown's blockchain-api proxy hasn't
onboarded:

1. customRpcUrls — Reown's support steered us to AppKit's per-chain
   RPC override. Pass every targetNetwork's default HTTP RPC as a
   CustomRpcUrl[] entry so the embedded wallet (W3mFrame) hits the
   chain RPC directly instead of trying the proxy and rejecting on
   "We don't support the chainId".

2. metadata.icons — without it, Reown's modal fetches
   api.web3modal.com/public/getAssetImage/undefined → 404 + loopback
   CORS warning. Point at our own /logo.svg.

3. chainPublicClient() in smartAccount.ts — wagmi's usePublicClient
   gets wrapped by Reown's adapter when an embedded wallet is
   connected and routes some RPC through the wallet's restricted
   provider. Pimlico's getSenderAddress does a creation-style eth_call
   (data + no to) that the wallet rejects with "Requested RPC call is
   not allowed", which surfaces during Kernel address derivation.
   Build a raw viem publicClient bound to chain.rpcUrls.default and use
   that for toKernelSmartAccount, bypassing the wallet entirely. Same
   target URL, no interceptor.

Also trim Reown modal: features.onramp / .swaps / .send all false —
Chain.Giving funds users via the org's paymaster, the on-ramp auto-
popup on zero balance was noise.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Encodes the active chain as the leading URL segment (/baseSepolia/programs,
/arcTestnet/program/0x…) instead of relying on the wallet's chain or a global
store. useTargetNetwork now reads useParams().network; useSelectedNetwork
falls back to that instead of the (now-removed) zustand slice. A new
useNetworkHref() wraps every in-app Link/router.push so navigation preserves
the slug, and <ChainSync /> nudges the wallet to follow the URL (covers deep
links and refreshes; NetworkOptions also fires switchChain on click for a
snappier prompt).

slugForChain keeps the first viem export key per chain id so collisions like
baseSepoliaPreconf no longer shadow baseSepolia.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
chain-giving-nextjs Ready Ready Preview, Comment Jun 10, 2026 5:19pm

PierreBesson and others added 2 commits June 10, 2026 19:16
The [network] URL segment is the canonical active network, but the
hand-written raw useReadContract/useReadContracts calls for the CG
contracts omitted chainId, so wagmi defaulted them to the wallet's
chain. Switching baseSepolia <-> arcTestnet flipped the display network
instantly while reads kept hitting the wallet's (lagging or unswitched)
chain, so the page showed the wrong network's data.

- Add useNetworkRead: useReadContract/useReadContracts wrappers that
  inject chainId: targetNetwork.id (explicit chainId still overrides).
- Swap the raw read imports across the program/org/token/wallet views,
  ProgramCard/ProgramRoleBadges, and useIsAdmin/useProgramOrganization/
  useOrgGasSponsorship.
- Flip the read-side `chainId ?? targetNetwork.id` patterns (native
  balance, donation-currency lookups) to targetNetwork.id; writes and
  wallet-capability lookups stay on the wallet chain.
- On-network "Switch Network" now opens a NetworkOptions dropdown that
  rewrites the URL segment instead of RainbowKit's openChainModal, which
  only switched the wallet and left the URL (and thus the page) stale.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Picks up the new InvalidPaymasterData error and the updated postOp
signature from the latest paymaster deployment.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@PierreBesson PierreBesson merged commit 3dbaed0 into main Jun 10, 2026
2 of 3 checks passed
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