ObolNetwork / ObolNetwork/obol-stack
auth-capture unlock (#798): integration notes, dependencies, deferred work
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 11
- Forks
- 1
- PR merge metrics
- No merged PRs in 30d
Description
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.2servesv2-eip155-auth-capture(the.1tag does not register the blueprint — don't use it). obol-infrastructure#2986 pins.2for 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).
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 400 invalid_format:
name, version, captureAuthorizer, captureDeadline, refundDeadline, feeRecipient, minFeeBps, maxFeeBps, autoCapture(=true), assetTransferMethod. BuildAuthCaptureRequirement mirrors the
facilitator's verify.rs checks (fee bounds ≤10000, non-zero feeRecipient when maxFeeBps>0,
non-zero captureAuthorizer, captureDeadline > now+6s ≤ refundDeadline) so a bad config fails
locally before the round-trip. Source of truth: x402-rs beta/batch-settlement-v2
crates/chains/x402-chain-eip155/src/v2_eip155_auth_capture/{types,constants}.rs + facilitator/verify.rs.
Dependencies / blockers (why it's not live)
- Facilitator must serve
auth-capture. PR #9 is MERGED but only on x402-rs
beta/batch-settlement-v2— still needs a:betaimage built +auth-captureenabled per-chain
onx402.gcp.obol.tech, then/supportedmust advertise it (and hand out thecaptureAuthorizer
operator address). As of writing the production facilitator returnsno available server(down). captureAuthorizermust equal the facilitator's advertised operator (verify.rsrejects a
mismatch). 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
- Client widget: the chat widget must handle the
auth-capture402 on the first message
(sign the ERC-3009 auth intoAuthCaptureEscrowvia@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-processHandleProxy, 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 #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-built x402-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.
| Leg | Result |
|---|---|
First message → 402 auth-capture (v2) |
✓ |
| B signs ERC-3009 into escrow, resubmits | ✓ 200, body UNLOCKED-OK, obol_siwx cookie set |
| On-chain fee split (charge()) | ✓ feeRecipient +25 atomic (2.5%), seller +975 (net) — settle tx 0xdfbdd89128b590c4077995ce34f98d7d6e4e28fc042891afb1ecb3613b2c8549 |
| Fixed-rate exactness (minFeeBps==maxFeeBps==250) | ✓ fee == amount*250/10000 exactly |
| Second message on the cookie, NO payment | ✓ 200 UNLOCKED-OK, seller delta 0 — fee captured once per session |
Smoke 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)
- Challenge must advertise
x402Version: 2(was 1). auth-capture is a v2 scheme; the@x402
client rejectsx402Version != 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
signedPaymentInfohash 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 a
client can't redirect the fee, underpay, or swap the authorizer. (unlockgate.go)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with internal/x402/unlockgate.go, authcapture.go, config.go, verifier.go, and authgate.go, then run go test ./internal/x402/.... The verifier-side tests are already green; remaining work is bounded by the facilitator dependency, client widget support, configuration, and the deferred live smoke described in the issue.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- api, backend, payments
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 28/100