Skip to content

feat(ts): auth capture sdk v1.1 - #3203

Open
phdargen wants to merge 28 commits into
x402-foundation:mainfrom
phdargen:auth-capture-sdk-v1.1
Open

phdargen wants to merge 28 commits into
x402-foundation:mainfrom
phdargen:auth-capture-sdk-v1.1

Conversation

@phdargen

@phdargen phdargen commented Aug 18, 2026 •

Copy link
Copy Markdown
Collaborator

Description

Continuation of #2308 implementing the v1.1 spec proposed in #3197

Tests

✅ PASSED TESTS:

📊 Breakdown by Facilitator:
 go              ✅ 5 / ❌ 0 (100%)
 typescript      ✅ 8 / ❌ 0 (100%)

📊 Breakdown by Server:
 go/http/gin          ✅ 2 / ❌ 0 (100%)
 typescript/http/fastify ✅ 3 / ❌ 0 (100%)
 typescript/http/express ✅ 2 / ❌ 0 (100%)
 go/http/echo         ✅ 2 / ❌ 0 (100%)
 typescript/http/next ✅ 1 / ❌ 0 (100%)
 go/http/nethttp      ✅ 1 / ❌ 0 (100%)
 typescript/http/hono ✅ 2 / ❌ 0 (100%)

📊 Breakdown by Client:
 typescript/http/axios ✅ 8 / ❌ 0 (100%)
 go/http/go-http      ✅ 2 / ❌ 0 (100%)
 typescript/http/fetch ✅ 3 / ❌ 0 (100%)

📊 Breakdown by Scheme:
 auth-capture    ✅ 13 / ❌ 0 (100%)

📊 Breakdown by Asset Transfer Method:
 eip3009              ✅ 10 / ❌ 0 (100%)
 permit2              ✅ 3 / ❌ 0 (100%)

📊 Breakdown by Transport:
 http            ✅ 13 / ❌ 0 (100%)

📊 Breakdown by Version:
 2               ✅ 13 / ❌ 0 (100%)

Checklist

  • I have formatted and linted my code
  • All new and existing tests pass
  • My commits are signed (required for merge) -- you may need to rebase if you initially pushed unsigned commits
  • I added a changelog fragment for user-facing changes (docs-only changes can skip)

@vercel

vercel Bot commented Aug 18, 2026

Copy link
Copy Markdown

@phdargen is attempting to deploy a commit to the Coinbase Team on Vercel.

A member of the Team first needs to authorize it.

@phdargen
phdargen marked this pull request as draft August 18, 2026 20:19
@github-actions github-actions Bot added typescript sdk Changes to core v2 packages examples Changes to examples evm labels Aug 18, 2026
@phdargen phdargen mentioned this pull request Aug 20, 2026
4 tasks done
@phdargen
phdargen force-pushed the auth-capture-sdk-v1.1 branch 2 times, most recently from da1fca7 to 7c72fa4 Compare August 21, 2026 13:50

@CarsonRoscoe CarsonRoscoe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One comment, might just be a documentation piece; There's a risk with the allowlist where upgradeable/proxy operator contracts could undermine the model. If the allowlist admits an address that was honest and later upgrades to a dishonest implementation, it effectively bypassed the allowlist.

We should document that facilitators' should prefer immutable operator contracts

@CarsonRoscoe CarsonRoscoe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing stops a merchant from registering a route with operatorType: "custom" while leacving captureMode at its default ("sync"), or leaving cancel-triggerred auto-void wired. The resource server will still build and submit an automatic capture after settle or void on cancel, but the facilitator unconditionally rejects lifecycle relay for operatorType: "custom" (See ErrLifecycleNotRelayed).

This would lead to the initial authorize succeeding and escrowing the payer's funds, and the automatic follow-up capture/void always failing silently, leaving funds stuck in escrow with no way to unwind unless the merchant happens to know to force captureMode: "deferred".

While this isn't a theft risk, its a big UX/funds availability trap we should avoid.

I'd suggest adding to the spec that servers MUST not set this combination, and at the server SDK level, fail fast when operatorType: "custom" is combined with anything other than captureMode: "deferred", and not auto-stamping receiverAuthorizer onto custom-operator routes.

@CarsonRoscoe CarsonRoscoe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: verify() for custom operators runs a ~7-9-call eth_simulateV1 batch vs the delegated's single eth_call. This is a much higher RPC cost per request, so spam/DoS cost to a facilitator is higher specifically for custom-operator verify() calls. Worth documenting

@CarsonRoscoe CarsonRoscoe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the facilitators' lifestyle.ts , if a capture succeeds on-chain but the trailing voil call fails for a mundane reason like RPC timeout (any error that isn't ZeroAuthorization or revert), the function returns success: false while discarding the txHash. imo this should be preserved the transaction regardless of the void outcome

@CarsonRoscoe CarsonRoscoe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

settlementHooks.ts's persistCollect unconditionally overwrites storage without checking if the record exists. I'll let you decide what we should do in that case, but calling out as-is a duplicate or retried settlement for the same paymentInfoHash would reset capturableAmount/refundableAmount

@CarsonRoscoe CarsonRoscoe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe there's a ERC-6492 signature verify/settle mismatch in facilitator/connect.ts and utils.ts. verifyCollect unwraps and verifies the inner 6492 signature, but unpackForSettle also passes the original wrapped signature bytes as colelctorData for the real on-chain call. Circle's SignatureChecker / Permit2's verifier don't understand the 6492 wrapper, so any client submitting a 6492-wrapped signature will pass verify() and then revert on settle()

Similarly, there's no counterfactual/undeployed wallet path. If we're going straight to the facilitator implementation, this should be considered.

@phdargen
phdargen force-pushed the auth-capture-sdk-v1.1 branch from 675bbc0 to 0513ce2 Compare August 25, 2026 16:38
@github-actions github-actions Bot added specs Spec changes or additions go labels Aug 25, 2026
@phdargen
phdargen force-pushed the auth-capture-sdk-v1.1 branch 2 times, most recently from 68d239f to f393735 Compare August 27, 2026 07:40
@zjzJoez

zjzJoez commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Reading #3197 while working against this — one boundary question. The answer settles an open question on a proposal of mine, not anything in this PR.

v1.1 moves in two directions at once and I can't tell which is the rule.

Toward the enum: extra.autoCapture is removed and paymentFlow is "the only flow selector", with autoCapture: true a hard reject rather than a fall-through to escrow.

Toward extra: captureMode: "deferred" — "skips that settle so the server captures later from durable state" — but narrowly: escrow-only, and it MUST NOT be set when paymentFlow is "authorization". The chain-agnostic scheme_auth_capture.md declines to name a mode at all — "Sync versus async is a server choice; the wire format does not name a mode" (L48), and captureMode appears nowhere in that file.

Read together that looks like a rule: anything that changes which phases run belongs in the enum, and extra only modulates a phase the enum has already selected. Is that the boundary you intended?

Asking because #3182 leaves exactly this open — its first protocol question is whether response-before-settlement is a fourth core payment flow, an EVM-specific binding, a generic asynchronous lifecycle, or only an application pattern. Under the rule above it's the first, since the delay changes whether a settle can run at all rather than when a selected one finalizes. I'd rather you drew that line than that I picked the reading that suits me.

One data point in case it makes the question cheaper than it sounds: on the permit2 binding the primitive is already merged and deployed. Witness(address to,uint256 validAfter) is signature-committed (specs/schemes/exact/scheme_exact_evm.md L380, L383) and _settleInternal enforces require(block.timestamp >= witness.validAfter) (L443) at 0x402085c248EeA27D92E8b30b2C58ed07f9E20001. The only thing rejecting a future value on that path is one parenthetical in verifier step 5: "Verify the deadline (not expired) and witness.validAfter (active)" (L191).

@shunhe-wang

Copy link
Copy Markdown

I noticed one narrow v1.1 fixture mismatch that may be worth correcting before the broader draft lands.

test/contracts/auth-capture/ForwardingOperator.sol targets the v1.1 escrow, but its charge wrapper still accepts uint16 feeBps and forwards the v1.0 selector.

The current live custom-operator integration path exercises authorize, so that selector mismatch is not caught onchain.

I prepared a small local patch that changes the wrapper to uint256 feeAmount and adds a Base Sepolia integration case for custom charge, including opaque collectorData, canonical PaymentCharged, exact state and token deltas, and zero facilitator-funded value movement. Would you prefer that as a focused contribution against this branch, or as a commit for you to cherry-pick while the draft is being rebased?

On another note, /supported.extra.operators seems to be the main way merchants find out which custom operators a facilitator supports. We're interested in being a custom operator, so it'd be helpful to know how an operator gets added, etc.

@phdargen
phdargen force-pushed the auth-capture-sdk-v1.1 branch from 9979a20 to 9f72d29 Compare September 30, 2026 14:08
@github-actions github-actions Bot removed specs Spec changes or additions go labels Sep 30, 2026
@phdargen
phdargen force-pushed the auth-capture-sdk-v1.1 branch from 9f72d29 to 9185dbf Compare September 30, 2026 14:23
@phdargen
phdargen marked this pull request as ready for review October 2, 2026 16:04
@phdargen
phdargen force-pushed the auth-capture-sdk-v1.1 branch from 711b833 to 575ccb9 Compare October 5, 2026 16:03
@github-actions github-actions Bot added the go label Oct 5, 2026
@PhilBot402 PhilBot402 mentioned this pull request Oct 6, 2026
2 tasks
@github-actions github-actions Bot added the specs Spec changes or additions label Oct 6, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

evm examples Changes to examples go sdk Changes to core v2 packages specs Spec changes or additions typescript

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants