feat(circle): Circle Paymaster v0.8 (USDC ガスレス) Phase 1#14
Merged
Conversation
…undation) USDC ガスレスを Circle Paymaster v0.8 に切替える Phase 1 の基盤層 (flag 既定 OFF で本番挙動に影響なし)。chunk3 以降 (二重決済 FSM / receipt verifier+audit / gas quote+法務 / cross-switch gate) は後続。 chunk1 — lib/circlePaymaster.ts: - Circle Paymaster v0.8 アドレスの hardcode allowlist (SoT, C3 信頼境界)。 公式 docs 由来 (mainnet 0x0578..700Ec / testnet 0x3BA9..8966、後者は spike 実証済)。 WebSearch が返す v0.7 (0x6C97../0x31BE..) との取り違えを test で防止。env override 無し。 - resolveUsdcGaslessProvider = 単一の真実点。flag ON + USDC erc20 + allowlist + fee config 全て揃った時のみ circle、欠ければ pimlico erc20 fallback。 - Circle fee = gas の 10% (Arb/Base のみ docs 確認、他 chain は fee config 無し=Circle 無効)。 - permit/paymasterData ヘルパ (deadline=MAX, encodePacked encoding)、deploy/code guard。 - env flag NEXT_PUBLIC_ENABLE_CIRCLE_PAYMASTER (既定 OFF)。 chunk2 — discriminated union + Circle client builder: - 既存 Pimlico bundle に provider:'pimlico'/entryPointVersion:'0.7' タグ付け (C6 union)。 - lib/smartAccount/circleAccount.ts: permissionless to7702SimpleSmartAccount (v0.8・ walletClient owner 可・impl 0xe6Cae83 同一) + viem createBundlerClient。spike レシピ 移植 (permit署名/pimlico gas price/estimate postOp>=15000/full-gas send)。 検証: typecheck / lint / vitest (circle 25 tests + 全体 2426 pass, 0 fail)。 codex review は利用上限/スタールでスキップ (memory 方針)。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
二重決済 (C1) 対策の核。USDC ガスレス決済の送信を fail-closed pending store + 明示 FSM で段階管理し、応答ロスト/timeout 時も auto-fallback せず冪等 rebroadcast のみで復旧する。 - lib/circlePending.ts: fail-closed localStorage 専用 store + FSM (reserved→awaiting_signature→signed→submitting→confirmed/failed/abandoned)。 冪等キー=chainId+sender+paymentAttemptId+callHash (callHash は merchant calls のみ →quote 再生成で不変)。CAS 遷移・sender bound 認可・read-back 書込検証 (private mode silent drop 検出)・findRecoverable (reload 後の submitting スキャン)。 - lib/smartAccount/circleAccount.ts: prepareAndSignCircleUserOp (prepare→postOp floor 強制→sign→hash→formatUserOperationRequest)、broadcastCircleUserOp (raw eth_sendUserOperation・retryCount0・冪等 rebroadcast)、pollCircleReceipt。 ※ viem sendUserOperation は account 有りで毎回 prepareUserOperation 再実行→別 op に なり冪等不可のため broadcast には raw RPC を使う。 - lib/smartAccount/circleSend.ts: executeCirclePayment オーケストレータ。 cross-invocation recovery→reserve→permit(popup1)→prepare+sign(popup2)→署名済 op 永続 →markSubmitting(broadcast前・fail-closed)→raw broadcast→確定。応答ロスト/timeout は CirclePendingError (失敗扱いせず submitting 維持)。submitting 後は fallback/新規 op 禁止。 - hooks/useSmartAccount.ts: pimlico-simple-7702 かつ provider==circle で Circle bundle へ routing。RQ key に provider+entryPointVersion 追加。 - hooks/useBatchPayment.ts: provider exhaustive 分岐。circle 枝は executeCirclePayment 委譲・circlePermitAmount を params threading・gas ceiling 適用・reconcile は circle 除外。 permitAmount 算定は chunk5 (useGasQuoteCircle) 待ち、未指定なら circle 枝は送信拒否。 tests: circlePending 26 + circleSend 9 + useBatchPayment circle 3。 全 green (typecheck/lint clean、full suite 2464 passed/0 failed)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… verifier 監査基盤 (C2/C3/C4-hist) の 2 本柱。 - lib/history.ts: schemaVersion 1→2。Circle 監査フィールド追加 (provider / circlePaymasterAddress / circlePaymasterNetUsdc / circleVerification、すべて nullable)。MIGRATIONS[1] が legacy(v1) を null backfill (drop しない)。isValidEntry / buildHistoryEntry / BuildHistoryBase 更新。新フィールドは optional で既存 caller 無変更。 - lib/circleReceiptVerifier.ts: 徴収額 reconciliation (C2)。balanceOf 差分でなく tx receipt の **per-UserOp scope** で circlePaymasterNetUsdc を再計算。 net = customer→paymaster − paymaster→customer 返金。bundle 内同一 sender 複数 UserOp は UserOperationEvent の log-index range で分離 (tx 全体合算しない)。binding 不変条件 (C3深): pending record の expected userOpHash/sender/paymaster が receipt の UserOperationEvent と 一致して初めて verified、不一致は unreconciled。 tests: history v1→v2 backfill/circle entry、verifier の単一/2-UserOp scope/refund/binding 不一致/ 汚染耐性。全 green (typecheck/lint clean、history 関連 164 + verifier 9)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…→UI) provider / circlePaymasterNetUsdc / paymaster を決済 log から履歴 UI まで貫通させ、 verified(on-chain) と client-reported を区別する (C2/C3)。 - lib/paymentLog.ts: PaymentLogContext/Event/builder に provider/circlePaymasterAddress/ circlePaymasterNetUsdc/circleVerification を追加 (gasless circle のみ)。 - app/api/log/payment/route.ts: 上記 4 field を allowlist 検証して受理 (未知 field は従来通り遮断)。 - app/api/log/payment/stats/route.ts: byProvider 集計を追加。circle の gas 徴収 net を verified / reported に分けて出力 (混同しない・dataSource 注記は従来通り client-reported)。 - hooks/useBatchPayment.ts: BatchPaymentResult に provider+circle 監査フィールド。circle 経路は 確定後に verifyCircleReceiptOnChain で net を best-effort 再計算 (client-reported)、log へ。 - hooks/usePaymentHistory.ts: gasless success entry に provider/circle フィールドを記録。 - lib/historyCsv.ts: CSV 末尾に Paymaster種別 / Circleガス代USDC / 検証 列を追加 (既存列順維持)。 - components/HistoryRow.tsx + messages: circle 行に Circle ガス代を表示 (columnCircleGas)。 tests: route の circle field accept/reject、stats byProvider (verified/reported 分離)、 CSV circle 列、CheckoutForm mock の provider 補完。全 green (full suite 2483 passed/0 failed)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rose 拡張 USDC ガスレスが Circle に解決される場合の quote/permit 算定と、法務 prose を provider 第一級次元に拡張 (C4)。 - hooks/useGasQuoteCircle.ts: 実費 (Pimlico getTokenQuotes の exchangeRate 流用) に per-chain surcharge (CIRCLE_GAS_SURCHARGE_BPS) を上乗せした表示 gasAmount と、 gas ceiling ベースの permitAmount (tight upper bound、過剰 allowance 回避) を算定。 useBatchPayment の circlePermitAmount 必須要件を満たす。postOp は Circle 下限 15000 反映。 - PaymentForm/CheckoutForm: provider が circle のとき activeQuote を circle quote に切替え、 circlePermitAmount を mutate に渡す。gas help を gasInfoUsdcCircle (Circle 明示・約10%手数料・ 当社徴収0) に切替え、UI に 'Pimlico' 名が残らないようにする。flag OFF では従来通り (dormant)。 - Privacy §3 委託先に Circle Internet Financial を追加 (顧客が USDC で gas を Circle Paymaster に 支払い・当社徴収0 を明示)。ja/en 両方。 tests: useGasQuoteCircle (surcharge/permit/postOp 下限)、legal §3 Circle 開示 (ja/en)、 i18n circle help text (Circle 明示・Pimlico 不在・当社徴収0)、form 各 test に circle quote mock。 全 green (full suite 2493 passed/0 failed)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…blocker) 同一 EOA で Pimlico(v0.7/JPYC) ↔ Circle(v0.8/USDC) を receipt 付きで往復実証する 手動ゲートを文書化。delegate アドレス一致だけでは EntryPoint/nonce/validation 差を 捕捉できないため、実機 testnet で send 成功を確認してから flag を有効化する。 - 前提 (disposable testnet EOA・Arb Sepolia USDC + JPYC sponsorship testnet)、 手順 (A:Circle USDC / B:JPYC 往復 + 応答ロスト復旧)、受入基準チェックリスト、 段階リリース (testnet→Base mainnet→拡大)、ロールバック手順。 - 本ゲート未通過のため NEXT_PUBLIC_ENABLE_CIRCLE_PAYMASTER は本番 OFF 維持。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
scripts/smoke-circle-crossswitch.mjs: ローカル使い捨て鍵 1 つで同一 EOA に対し Pimlico → Circle → Pimlico の 3 leg を送信し、receipt / 徴収 USDC / 委任先を検証して PASS/FAIL を出す。MetaMask インポートや flag 再起動が不要 (ローカル鍵が 7702 委任を 自前 bootstrap)。Circle leg は spike の実証済みレシピ、Pimlico leg は本番 simpleAccount.ts と同一スタック (permissionless createSmartAccountClient + ERC20 paymaster)。 調査確定: permissionless/viem の 7702 SimpleAccount は EntryPoint v0.8 専用。本番 Pimlico 経路も Circle 経路も同一 v0.8 EntryPoint + 同一 impl 0xe6Cae83 で、差は paymaster のみ (単一 nonce 空間)。runbook の「v0.7↔v0.8」枠組みを「同一 v0.8・paymaster 往復」に訂正し、 スクリプトを最短実行として追記。SmartAccountBundle.entryPointVersion='0.7' は label の 名残で不正確 (runtime は provider 分岐のため無害・別途整理推奨) を明記。 node --check + 全 import 解決を keygen path で確認済 (on-chain leg は要 funded 鍵)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
v0.7 client + v0.8 account の混在で erc20 paymaster の approve spender (v0.8 0x7777) と paymaster 指定 (v0.7 0x8888) が食い違い AA50 postOp revert していた。Pimlico client を entryPoint08 に揃える。本番 lib/pimlico.ts createPimlico も同根 (要修正・別 task)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
cross-switch smoke で判明した既存バグの修正。permissionless to7702SimpleSmartAccount は **EntryPoint v0.8** だが createPimlico は entryPoint07 固定だった。erc20 経路 (prepareUserOperationForErc20Paymaster) は getTokenQuotes を pimlicoActions decorator 経由で 呼び、decorator が entryPointAddress を client 設定 (v0.7) で上書きするため approve spender が v0.7 paymaster、最終 getPaymasterData は account 由来 v0.8 paymaster になり mixed state → postOp AA50 revert。mainnet USDC erc20 (simpleAccount 経路) が壊れていた (testnet は sponsorship に倒れて露見せず)。 並列監査 workflow (6 agents・ライブラリ型/runtime/実機 receipt で裏取り) で各 builder の実 EntryPoint 版を確定: simpleAccount=v0.8 / circle=v0.8 / metamask=v0.7 (toolkit ハードワイヤ) / mav2=v0.7。createPimlico 一律 v0.8 は metamask(v0.7) を鏡像で壊すため不可。 - lib/pimlico.ts: createPimlico(chainId, entryPointVersion='0.7') に版引数化。既定 '0.7' で metamask/mav2/既存呼出は挙動不変。 - lib/smartAccount/simpleAccount.ts: createPimlico(chainId,'0.8') に変更 (account v0.8 と一致)。 SmartAccountBundle.entryPointVersion を '0.7'→'0.7'|'0.8' union 化し本経路は '0.8' (実体一致)。 ラベルは provider 分岐に未使用 (discriminant は provider) なので consumer 無影響。 - metamask/mav2 は createPimlico(chainId) 既定 '0.7' のまま (account v0.7 と一致・変更不要)。 検証: typecheck/lint clean、full suite 2493 passed/0 failed、C8 guard 無影響。 残: mainnet metamask USDC erc20 の手動 regression smoke (audit verificationSteps)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
両レビュー (Codex 6 + Claude adversarial 14 確定/偽陽性 18 除去) を突き合わせ、実害確定分を修正。 A 二重決済 (最重要・両者一致): cross-invocation ガードが submitting しか見ず、pre-submit 並行 (別タブ/popup 跨ぎ) や fast-confirm 後の再決済で同一 callHash の merchant 転送が二重に なりえた。findLiveByCallHash で同一 callHash の生き record を全状態スキャンし、confirmed→ 既支払い結果返却 / submitting→recover / pre-submit→CirclePendingError。reload 放置の stale orphan は abandon して恒久ブロックを回避。 B testnet 二重徴収: Circle 経路でも resolvePaymasterMode→sponsorship 倒しで gasReimbursement が feeReceiver へ加算され Circle の permit 徴収と二重 + 徴収0違反。isCircle 時は加算しない。 C 監査盲点: verify の unreconciled reason (paymaster/sender 不一致=信頼境界破れ) が握り潰され RPC flake と区別不能だった。binding 違反は logger.error、RPC 失敗は別 key で記録。 D verifier 堅牢化: UserOperationEvent の発火元が EntryPoint であることを必須化 (同名 event の scope 汚染防止)、success=false (revert) は unreconciled。 E API forge 防止: 未認証 endpoint が circleVerification='verified' を受理していた → reject (client は client-reported/unreconciled のみ。verified は server verifier 専用)。 F over-allowance: permitAmount を ceiling ベース (実費の数百〜数万倍) から standard×10 に圧縮。 deadline=MAX の残余 allowance を実費の数倍に。送信時 spike は assertGasCeiling が abort。 G audit: CirclePendingError の userOpHash を onError 監査に載せ submitted op handle を保持。 H success: PendingRecord に receipt.success を永続し、resultFromConfirmed の receipt 取得失敗 フォールバックで success:true 捏造 (revert を成功誤報告) をやめる。 tests: circleSend +4 (confirmed/pre-submit/submitting/stale guard)、verifier +3 (EntryPoint poison/success=false)、log-payment +2 (verified reject)、circlePending/useGasQuoteCircle 追従。 全 green (typecheck/lint clean、full suite 2502 passed/0 failed)。 DEFER (低): collector≠paymaster の mainnet 実測、pre-submit GC、unreconciled UI バッジ、 POST size cap、gas tier コメント、recoverSubmitting エラー分類 (checklist 化)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Codex+Claude の修正分再レビューで判明した cross-invocation ガードの残ギャップを修正。 NEW-1 (Codex HIGH): confirmed-by-callHash が paymentAttemptId 非依存のため正規の同額リピート 決済を恒久抑止していた → CONFIRMED_DEDUP_WINDOW_MS(90s) で時間窓化。窓内 = 偶発二重 (reload/ 再クリック) → 既支払い結果を返す。窓外 = 正規リピート → 新規決済を通す。 NEW-3 (Codex MED): live record を status 優先 (confirmed>submitting>pre-submit) で sort し、 stale でない pre-submit が confirmed/submitting を CirclePendingError で masked するのを防止。 NEW-2 (Codex HIGH→実際は安全): scan→reserve の真の同時 race は localStorage の限界で残るが、 両 attempt は同一 sender の key-0 sequential nonce を取り **ERC-4337 EntryPoint の nonce 一意性が 最終 double-spend ガード** (後発は AA25 で revert、merchant 転送は 1 回)。コメントで明記 (Claude 再レビューが指摘した backstop)。 LOW: stale orphan の abandon を try/catch で握り潰し (並行遷移時の PendingStateError 伝播を防止)。 useGasQuoteCircle の docblock を ceiling→standard×係数 の実装に同期。 5/6 の元修正は両レビューとも solid 確認。Claude 総括 verdict=ship。 tests: circleSend +3 (時間窓/status 優先)。全 green (typecheck/lint、full suite 2505 passed/0 failed)。 DEFER (低・checklist): binding-violation 分類の構造化、producer/endpoint 型同期、stats verified bucket の server verifier 実装、findRecoverable dead export 整理。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
概要
USDC ガスレス決済の gas 支払いを Circle Paymaster v0.8(優先) に対応。Pimlico erc20 は fallback 保持、JPYC は現行 Pimlico sponsorship のまま。
paymasterMode='erc20'は不変(法務 prose / C8 ドリフトガードを壊さない)。OpenPay 徴収は 0。主な変更(chunk 1–6)
lib/circlePaymaster.ts(per-chain hardcode allowlist = SoT・env override 不可・codehash/deploy 検証)+lib/smartAccount/circleAccount.ts(viem/permissionless v0.8 client)+ provider 解決層・discriminated union。lib/circlePending.ts(fail-closed localStorage FSM + read-back 検証)+lib/smartAccount/circleSend.ts(permit→署名済 op 永続→submitting→raw broadcast→確定、応答ロスト時は 新規 op を作らず冪等 rebroadcast のみ、cross-invocation は callHash で dedup)。lib/circleReceiptVerifier.ts(per-UserOp scope で徴収 net 再計算・balanceOf 差分不採用・binding 不変条件 + EntryPoint 発火元検証 + success フラグ)+ HistoryEntry v1→v2 migration(legacy を null backfill で drop しない)+ paymentLog/API/stats/CSV/履歴 UI を end-to-end(verified ⇔ client-reported 分離)。hooks/useGasQuoteCircle.ts(per-chain surcharge + permitAmount を standard×安全係数で算定し過剰 allowance 回避)+ 法務 prose 拡張(Privacy §3 委託先に Circle 追記・UI から "Pimlico" 名排除)。docs/runbooks/circle-paymaster-release-gate.md(投入ゲート runbook)+scripts/smoke-circle-crossswitch.mjs(cross-switch smoke)。既存バグ修正(本 PR で発掘)
to7702SimpleSmartAccountは v0.8 専用。createPimlicoを版引数化し各 builder を account に一致(simpleAccount/circle=v0.8、metamask/mav2=v0.7)。一律 v0.8 は metamask を壊すため per-builder。erc20 fallback の AA50 postOp revert を解消。レビュー
Codex 2 巡 + Claude 多次元 adversarial review 2 巡(implement→review→fix→re-review)で収束。
テスト
本番投入前ゲート(マージ後・有効化前)
docs/runbooks/circle-paymaster-release-gate.md)= 同一 EOA で Pimlico↔Circle 往復を receipt 付きで実証。testnet は実行済(PASS)、Base mainnet で fee 実測 + metamask erc20 regression が残。hardening checklist(DEFER・低)
collector≠paymaster の mainnet 実測 / pre-submit 孤児 GC / unreconciled UI バッジ / POST size cap / binding-violation 分類の構造化 / producer⇔endpoint 型同期 / stats verified bucket(server verifier 実装まで常時 0)
🤖 Generated with Claude Code