You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Integration notes for #798 (moved out of the PR tree — tracking issue instead of a committed doc).
Status update (2026-07-20)
Facilitator side is unblocked: overlay image ghcr.io/obolnetwork/x402-facilitator-prometheus-overlay:2.0.2-auth-capture.2 serves v2-eip155-auth-capture (the .1 tag does not register the blueprint — don't use it). obol-infrastructure#2986 pins .2 for the hosted facilitator.
Proven end-to-end on Base mainnet in a live cluster (fee split 250bps on-chain, cookie mint, free ride, fee metrics materialized) in addition to the Base Sepolia smoke below.
Auth-capture agent-unlock — integration notes
Platform fee capture on the seamless agent chat, via the x402 auth-capture scheme
(native on-chain fee split: the facilitator's charge() pays feeBps → feeRecipient).
Branch: feat/authcapture-unlock (off integration/v0.14.0-rc0). Verifier side complete +
unit-tested; not deployed (see Dependencies).
Design — inline first-message fee (no gate ceremony)
The fee is captured once, on the first chat message, and folded invisibly into the price
(auth-capture bounds the cut with buyer-signed [minFeeBps,maxFeeBps]; the buyer sees one
total, no "platform fee" line). No /unlock endpoint, no button, no "unlock" wording.
first message (no cookie) → 402 { accepts:[auth-capture requirement] }
client pays (1 popup) → facilitator /verify → /settle (charge(): feeBps→obol, net→seller)
settle success → mint obol_siwx session cookie → proxy the answer back
every message after → cookie present → existing free gate:auth proxy path (no payment)
The unlock offer is a gate:auth route whose session is minted by paying instead of by a
SIWX signature. Mechanically: in HandleProxy's rule.IsAuth() branch, when Authenticate
fails AND the rule is the configured unlock offer → handlePaidUnlock (verify→settle→mint→proxy);
otherwise the normal SIWX challenge. The free <offer>/auth sign-in is suppressed for the unlock
offer (handleAuthEndpoints) so paying is the only way to mint the session.
Semantic decision: settle happens before proxying — the payment buys the session, not the
single answer. If the upstream errors on that first message, the session is still established and
the retry is free. This is intended for "fee at unlock" (differs from per-message exact, which
settles only on upstream success).
captureAuthorizer must equal the facilitator's advertised operator (verify.rs rejects a
mismatch). v1 takes it from config; TODO auto-source from /supported.
Config to fill before enabling (PricingConfig.authCaptureUnlock)
Client widget: the chat widget must handle the auth-capture 402 on the first message
(sign the ERC-3009 auth into AuthCaptureEscrow via @x402). Not built here (verifier-only).
Traefik ForwardAuth mode (HandleVerify): no paid-unlock branch — the settle-then-proxy model
doesn't fit an auth-only hop. Agent chat runs via in-process HandleProxy, so unaffected; a
ForwardAuth-mode unlock would need the hop to settle + return Set-Cookie + 200 and let Traefik
forward. Follow-up if ForwardAuth deployments need it.
Per-offer config via CRD: v1 is one global unlock offer (offerPrefix). Multi-offer = CRD
regen + thread the config per-route.
Live smoke: deferred until dependency Bring obolup code to this repo #1 clears (mirror the batch-settlement Base-Sepolia smoke:
first paid message settles + mints cookie on-chain, second rides free).
Test status
go test ./internal/x402/... green. TestBuildAuthCaptureRequirement_ExtraShape (10-key wire shape), TestAuthCaptureUnlockConfig_Validate (table), TestPaidUnlock_InlineFirstMessage (stub facilitator
stub upstream: 402→pay→200+UNLOCKED-OK+cookie→cookie-only free ride with facilitator call count flat).
Smoke — PROVEN end-to-end on Base Sepolia (2026-07-20)
Ran the real Go verifier against a locally-builtx402-facilitator (x402-rs beta/batch-settlement-v2, --features chain-eip155, auth-capture enabled on eip155:84532) — the production facilitator image
is still blocked on CI (Docker-Hub-login step fails) so we built + ran it locally. Escrow AuthCaptureEscrow 0xBdEA0D1bcC5966192B070Fdf62aB4EF5b4420cff + ERC-3009 collector 0x0E3dF9510de65469C4518D7843919c0b8C7A7757 already live on Base Sepolia. Client @x402/evm v2.19.0 AuthCaptureEvmScheme. Roles: payer B 0xb035…27Bf; facilitator signer/operator = captureAuthorizer
Alice 0xC0De…297E; seller 0x04AD…1582; feeRecipient (obol) 0x5Ae2…c9db.
Fixes the smoke surfaced (folded into the feature)
Challenge must advertise x402Version: 2 (was 1). auth-capture is a v2 scheme; the @x402
client rejects x402Version != 2, and the verifier already settles with v2. (unlockgate.go)
Settle against the client's signed payload.accepted, not a rebuilt requirement. auth-capture's
signed PaymentInfo hash commits the server-issued deadlines; rebuilding them (fresh time.Now)
drifts the hash → signature rejected. The verifier now takes the client's signed requirement AND
validates every economically-sensitive field against config (validateSignedUnlockRequirement) so a
client can't redirect the fee, underpay, or swap the authorizer. (unlockgate.go)
Integration notes for #798 (moved out of the PR tree — tracking issue instead of a committed doc).
Status update (2026-07-20)
ghcr.io/obolnetwork/x402-facilitator-prometheus-overlay:2.0.2-auth-capture.2servesv2-eip155-auth-capture(the.1tag does not register the blueprint — don't use it). obol-infrastructure#2986 pins.2for the hosted facilitator.Auth-capture agent-unlock — integration notes
Platform fee capture on the seamless agent chat, via the x402
auth-capturescheme(native on-chain fee split: the facilitator's
charge()paysfeeBps→feeRecipient).Branch:
feat/authcapture-unlock(offintegration/v0.14.0-rc0). Verifier side complete +unit-tested; not deployed (see Dependencies).
Design — inline first-message fee (no gate ceremony)
The fee is captured once, on the first chat message, and folded invisibly into the price
(
auth-capturebounds the cut with buyer-signed[minFeeBps,maxFeeBps]; the buyer sees onetotal, no "platform fee" line). No
/unlockendpoint, no button, no "unlock" wording.The unlock offer is a
gate:authroute whose session is minted by paying instead of by aSIWX signature. Mechanically: in
HandleProxy'srule.IsAuth()branch, whenAuthenticatefails AND the rule is the configured unlock offer →
handlePaidUnlock(verify→settle→mint→proxy);otherwise the normal SIWX challenge. The free
<offer>/authsign-in is suppressed for the unlockoffer (
handleAuthEndpoints) so paying is the only way to mint the session.Semantic decision: settle happens before proxying — the payment buys the session, not the
single answer. If the upstream errors on that first message, the session is still established and
the retry is free. This is intended for "fee at unlock" (differs from per-message
exact, whichsettles only on upstream success).
Files (all
internal/x402/unless noted)authcapture.go(new) —AuthCaptureUnlockConfig,Validate(),BuildAuthCaptureRequirement().unlockgate.go(new) —isUnlockOffer(),handlePaidUnlock()(verify→settle→mint→proxy).config.go—PricingConfig.AuthCaptureUnlock *AuthCaptureUnlockConfig(nil = off).verifier.go—HandleProxyIsAuth branch callshandlePaidUnlockfor the unlock offer.authgate.go— suppresses free/authsign-in for the unlock offer.monetizeapi/types.go— scheme enum widenedexact→exact;auth-capture.Wire format the requirement MUST carry (facilitator
AuthCapturePaymentRequirementsExtra)Strict camelCase
extra, every key required or the facilitator returns HTTP 400invalid_format:name, version, captureAuthorizer, captureDeadline, refundDeadline, feeRecipient, minFeeBps, maxFeeBps, autoCapture(=true), assetTransferMethod.BuildAuthCaptureRequirementmirrors thefacilitator's
verify.rschecks (fee bounds ≤10000, non-zero feeRecipient when maxFeeBps>0,non-zero captureAuthorizer,
captureDeadline > now+6s ≤ refundDeadline) so a bad config failslocally before the round-trip. Source of truth: x402-rs
beta/batch-settlement-v2crates/chains/x402-chain-eip155/src/v2_eip155_auth_capture/{types,constants}.rs+facilitator/verify.rs.Dependencies / blockers (why it's not live)
auth-capture. PR Helm chart merges default tcpSocket probe with custom httpGet probe, causing invalid Pod spec (multiple handler types) #9 is MERGED but only on x402-rsbeta/batch-settlement-v2— still needs a:betaimage built +auth-captureenabled per-chainon
x402.gcp.obol.tech, then/supportedmust advertise it (and hand out thecaptureAuthorizeroperator address). As of writing the production facilitator returns
no available server(down).captureAuthorizermust equal the facilitator's advertised operator (verify.rsrejects amismatch). v1 takes it from config; TODO auto-source from
/supported.Config to fill before enabling (
PricingConfig.authCaptureUnlock)enabled,offerPrefix(the chat offer's StripPrefix),price,network(chains-map key e.g.base),payTo(seller),feeRecipient(obol platform wallet),minFeeBps/maxFeeBps(the platform fee rate — product decision),
captureAuthorizer(from/supported),captureDeadlineSecs/refundDeadlineSecs(default 900/1800).Deferred / known gaps
auth-capture402 on the first message(sign the ERC-3009 auth into
AuthCaptureEscrowvia@x402). Not built here (verifier-only).HandleVerify): no paid-unlock branch — the settle-then-proxy modeldoesn't fit an auth-only hop. Agent chat runs via in-process
HandleProxy, so unaffected; aForwardAuth-mode unlock would need the hop to settle + return Set-Cookie + 200 and let Traefik
forward. Follow-up if ForwardAuth deployments need it.
offerPrefix). Multi-offer = CRDregen + thread the config per-route.
first paid message settles + mints cookie on-chain, second rides free).
Test status
go test ./internal/x402/...green.TestBuildAuthCaptureRequirement_ExtraShape(10-key wire shape),TestAuthCaptureUnlockConfig_Validate(table),TestPaidUnlock_InlineFirstMessage(stub facilitatorUNLOCKED-OK+cookie→cookie-only free ride with facilitator call count flat).Smoke — PROVEN end-to-end on Base Sepolia (2026-07-20)
Ran the real Go verifier against a locally-built
x402-facilitator(x402-rsbeta/batch-settlement-v2,--features chain-eip155,auth-captureenabled oneip155:84532) — the production facilitator imageis still blocked on CI (Docker-Hub-login step fails) so we built + ran it locally. Escrow
AuthCaptureEscrow 0xBdEA0D1bcC5966192B070Fdf62aB4EF5b4420cff+ ERC-3009 collector0x0E3dF9510de65469C4518D7843919c0b8C7A7757already live on Base Sepolia. Client@x402/evmv2.19.0AuthCaptureEvmScheme. Roles: payer B0xb035…27Bf; facilitator signer/operator =captureAuthorizerAlice
0xC0De…297E; seller0x04AD…1582; feeRecipient (obol)0x5Ae2…c9db.auth-capture(v2)UNLOCKED-OK,obol_siwxcookie set0xdfbdd89128b590c4077995ce34f98d7d6e4e28fc042891afb1ecb3613b2c8549UNLOCKED-OK, seller delta 0 — fee captured once per sessionSmoke harness (not committed):
scratchpad/authcap-smoke/{facilitator-config.json,smoke-client.mjs},cmd/authcap-smoke/main.go.Fixes the smoke surfaced (folded into the feature)
x402Version: 2(was 1). auth-capture is a v2 scheme; the@x402client rejects
x402Version != 2, and the verifier already settles with v2. (unlockgate.go)payload.accepted, not a rebuilt requirement. auth-capture'ssigned
PaymentInfohash commits the server-issued deadlines; rebuilding them (freshtime.Now)drifts the hash → signature rejected. The verifier now takes the client's signed requirement AND
validates every economically-sensitive field against config (
validateSignedUnlockRequirement) so aclient can't redirect the fee, underpay, or swap the authorizer. (
unlockgate.go)