feat(x402): auth-capture unlock gate + fee revenue metrics - #798

Closed
bussyjd wants to merge 2 commits into
mainfrom
feat/authcapture-unlock
Closed

feat(x402): auth-capture unlock gate + fee revenue metrics#798
bussyjd wants to merge 2 commits into
mainfrom
feat/authcapture-unlock

Conversation

@bussyjd

@bussyjdbussyjd commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Integration notes, dependencies & deferred work: #799

Inline first-message platform-fee capture for gate:auth offers, riding the x402 auth-capture scheme (ObolNetwork/x402-rs#9, escrow-enforced on-chain fee split).

What

  • First chat/agent message pays an auth-capture charge (EIP-3009 single-shot, autoCapture); the SIWX session cookie mints on settle success, and every later request rides the cookie free — one on-chain tx per session, no per-turn wallet interaction.
  • The escrow itself pays feeBps → feeRecipient at charge() within the client-signed bounds — no off-chain split.
  • New fee metrics on the verifier /metrics: obol_x402_verifier_fee_revenue_atomic_total + obol_x402_verifier_settled_volume_atomic_total (labels network/asset/fee_recipient), excluded from route-pruning.
  • Global authCaptureUnlock config block (offerPrefix, price, payTo, feeRecipient, min/maxFeeBps, captureAuthorizer). Config-gated, off by default.

Correctness notes

  • The unlock 402 advertises x402Version: 2 (auth-capture is v2-only; @x402 clients reject otherwise).
  • Settlement uses the client's signedaccepted requirement verbatim — the PaymentInfo hash commits server-issued deadlines, so a rebuilt requirement drifts and breaks the signature. validateSignedUnlockRequirement pins every economic field (amount, payTo, feeRecipient, fee bounds, authorizer) against config so a client can't underpay or redirect the fee.

Proven

End-to-end on Base Sepolia and Base mainnet (live cluster): on-chain fee split (e.g. mainnet settle 0xce7ac4bd7f821017b9707a0cc6e0f4354a9c6bbeaa032eec79f290e5454b1343: 10000 → 250 fee + 9750 payTo at 250bps), cookie mint, free-ride on 2nd request (no double charge), fee counters materialized. Requires a facilitator serving v2-eip155-auth-capture (overlay image 2.0.2-auth-capture.2+; the .1 tag does not register the blueprint).

https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v

@bussyjd
bussyjd changed the base branch from integration/v0.14.0-rc0 to mainJuly 20, 2026 14:39
@bussyjd
bussyjd marked this pull request as draft July 20, 2026 14:39
@bussyjd
bussyjdforce-pushed the feat/authcapture-unlock branch from 8224493 to a95d2caCompareJuly 20, 2026 14:42
Inline first-message fee capture for gate:auth offers: the SIWX session is
minted by settling an x402 auth-capture charge (EIP-3009 single-shot,
autoCapture) instead of a plain signature; subsequent requests ride the
session cookie free. The escrow enforces the client-signed fee split
(feeBps -> feeRecipient) on-chain at charge() time.
- internal/x402/authcapture.go: auth-capture requirement builder + signed
payload validation (validateSignedUnlockRequirement pins every economic
field of the client-signed requirement against config)
- internal/x402/unlockgate.go: unlock flow — 402 (x402Version 2) ->
verify+settle against the signed payload -> mint SIWX cookie
- internal/x402/metrics.go: obol_x402_verifier_fee_revenue_atomic_total +
settled_volume_atomic_total (network/asset/fee_recipient), excluded from
route-pruning
- config: global authCaptureUnlock block (offerPrefix, price, payTo,
feeRecipient, min/maxFeeBps, captureAuthorizer)
Config-gated, off by default. Settlement must use the client's signed
'accepted' requirement verbatim (PaymentInfo hash commits server-issued
deadlines; rebuilding drifts them and breaks the signature).
Proven end-to-end on Base Sepolia and Base mainnet (on-chain fee split,
cookie mint, free-ride, metrics materialization).
Claude-Session: https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v
handlePaidUnlock discarded the settle response when facilitatorSettle
errored, losing the tx hash the facilitator returns when it submits the
settle tx on-chain and then fails on the receipt path. A charged buyer got
a bare settle_failed with no cookie, no answer, and no way to reconcile the
on-chain debit.
Mirror the per-request paid path (HandleProxy): surface settleResp.Transaction
via X-PAYMENT-RESPONSE + a 'you may pay twice' hint, and log it, on both the
transport-error and !Success branches. A failed settle still mints no session.
Claude-Session: https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v
@bussyjd
bussyjd marked this pull request as ready for review July 20, 2026 18:31
@bussyjd

Copy link
Copy Markdown
ContributorAuthor

Subsumed by #800 (integration/v0.14.0-rc1) — this change is already folded into rc1 (0 commits not in rc1 by patch-id) and ships in the v0.14.0-rc1 pre-release. Closing to avoid an individual merge forcing an rc1 rebase; the branch is retained.

https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v

@bussyjdbussyjd closed this Jul 27, 2026
@bussyjdbussyjd mentioned this pull request Aug 6, 2026
Sign up for freeto 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

@bussyjd
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(x402): auth-capture unlock gate + fee revenue metrics - #798

Closed
bussyjd wants to merge 2 commits into
mainfrom
feat/authcapture-unlock
Closed

feat(x402): auth-capture unlock gate + fee revenue metrics#798
bussyjd wants to merge 2 commits into
mainfrom
feat/authcapture-unlock

Conversation

@bussyjd

@bussyjdbussyjd commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Integration notes, dependencies & deferred work: #799

Inline first-message platform-fee capture for gate:auth offers, riding the x402 auth-capture scheme (ObolNetwork/x402-rs#9, escrow-enforced on-chain fee split).

What

  • First chat/agent message pays an auth-capture charge (EIP-3009 single-shot, autoCapture); the SIWX session cookie mints on settle success, and every later request rides the cookie free — one on-chain tx per session, no per-turn wallet interaction.
  • The escrow itself pays feeBps → feeRecipient at charge() within the client-signed bounds — no off-chain split.
  • New fee metrics on the verifier /metrics: obol_x402_verifier_fee_revenue_atomic_total + obol_x402_verifier_settled_volume_atomic_total (labels network/asset/fee_recipient), excluded from route-pruning.
  • Global authCaptureUnlock config block (offerPrefix, price, payTo, feeRecipient, min/maxFeeBps, captureAuthorizer). Config-gated, off by default.

Correctness notes

  • The unlock 402 advertises x402Version: 2 (auth-capture is v2-only; @x402 clients reject otherwise).
  • Settlement uses the client's signedaccepted requirement verbatim — the PaymentInfo hash commits server-issued deadlines, so a rebuilt requirement drifts and breaks the signature. validateSignedUnlockRequirement pins every economic field (amount, payTo, feeRecipient, fee bounds, authorizer) against config so a client can't underpay or redirect the fee.

Proven

End-to-end on Base Sepolia and Base mainnet (live cluster): on-chain fee split (e.g. mainnet settle 0xce7ac4bd7f821017b9707a0cc6e0f4354a9c6bbeaa032eec79f290e5454b1343: 10000 → 250 fee + 9750 payTo at 250bps), cookie mint, free-ride on 2nd request (no double charge), fee counters materialized. Requires a facilitator serving v2-eip155-auth-capture (overlay image 2.0.2-auth-capture.2+; the .1 tag does not register the blueprint).

https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v

@bussyjd
bussyjd changed the base branch from integration/v0.14.0-rc0 to mainJuly 20, 2026 14:39
@bussyjd
bussyjd marked this pull request as draft July 20, 2026 14:39
@bussyjd
bussyjdforce-pushed the feat/authcapture-unlock branch from 8224493 to a95d2caCompareJuly 20, 2026 14:42
Inline first-message fee capture for gate:auth offers: the SIWX session is
minted by settling an x402 auth-capture charge (EIP-3009 single-shot,
autoCapture) instead of a plain signature; subsequent requests ride the
session cookie free. The escrow enforces the client-signed fee split
(feeBps -> feeRecipient) on-chain at charge() time.
- internal/x402/authcapture.go: auth-capture requirement builder + signed
payload validation (validateSignedUnlockRequirement pins every economic
field of the client-signed requirement against config)
- internal/x402/unlockgate.go: unlock flow — 402 (x402Version 2) ->
verify+settle against the signed payload -> mint SIWX cookie
- internal/x402/metrics.go: obol_x402_verifier_fee_revenue_atomic_total +
settled_volume_atomic_total (network/asset/fee_recipient), excluded from
route-pruning
- config: global authCaptureUnlock block (offerPrefix, price, payTo,
feeRecipient, min/maxFeeBps, captureAuthorizer)
Config-gated, off by default. Settlement must use the client's signed
'accepted' requirement verbatim (PaymentInfo hash commits server-issued
deadlines; rebuilding drifts them and breaks the signature).
Proven end-to-end on Base Sepolia and Base mainnet (on-chain fee split,
cookie mint, free-ride, metrics materialization).
Claude-Session: https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v
handlePaidUnlock discarded the settle response when facilitatorSettle
errored, losing the tx hash the facilitator returns when it submits the
settle tx on-chain and then fails on the receipt path. A charged buyer got
a bare settle_failed with no cookie, no answer, and no way to reconcile the
on-chain debit.
Mirror the per-request paid path (HandleProxy): surface settleResp.Transaction
via X-PAYMENT-RESPONSE + a 'you may pay twice' hint, and log it, on both the
transport-error and !Success branches. A failed settle still mints no session.
Claude-Session: https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v
@bussyjd
bussyjd marked this pull request as ready for review July 20, 2026 18:31
@bussyjd

Copy link
Copy Markdown
ContributorAuthor

Subsumed by #800 (integration/v0.14.0-rc1) — this change is already folded into rc1 (0 commits not in rc1 by patch-id) and ships in the v0.14.0-rc1 pre-release. Closing to avoid an individual merge forcing an rc1 rebase; the branch is retained.

https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v

@bussyjdbussyjd closed this Jul 27, 2026
@bussyjdbussyjd mentioned this pull request Aug 6, 2026
Sign up for freeto 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

@bussyjd
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(x402): auth-capture unlock gate + fee revenue metrics - #798

Closed
bussyjd wants to merge 2 commits into
mainfrom
feat/authcapture-unlock
Closed

feat(x402): auth-capture unlock gate + fee revenue metrics#798
bussyjd wants to merge 2 commits into
mainfrom
feat/authcapture-unlock

Conversation

@bussyjd

@bussyjdbussyjd commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Integration notes, dependencies & deferred work: #799

Inline first-message platform-fee capture for gate:auth offers, riding the x402 auth-capture scheme (ObolNetwork/x402-rs#9, escrow-enforced on-chain fee split).

What

  • First chat/agent message pays an auth-capture charge (EIP-3009 single-shot, autoCapture); the SIWX session cookie mints on settle success, and every later request rides the cookie free — one on-chain tx per session, no per-turn wallet interaction.
  • The escrow itself pays feeBps → feeRecipient at charge() within the client-signed bounds — no off-chain split.
  • New fee metrics on the verifier /metrics: obol_x402_verifier_fee_revenue_atomic_total + obol_x402_verifier_settled_volume_atomic_total (labels network/asset/fee_recipient), excluded from route-pruning.
  • Global authCaptureUnlock config block (offerPrefix, price, payTo, feeRecipient, min/maxFeeBps, captureAuthorizer). Config-gated, off by default.

Correctness notes

  • The unlock 402 advertises x402Version: 2 (auth-capture is v2-only; @x402 clients reject otherwise).
  • Settlement uses the client's signedaccepted requirement verbatim — the PaymentInfo hash commits server-issued deadlines, so a rebuilt requirement drifts and breaks the signature. validateSignedUnlockRequirement pins every economic field (amount, payTo, feeRecipient, fee bounds, authorizer) against config so a client can't underpay or redirect the fee.

Proven

End-to-end on Base Sepolia and Base mainnet (live cluster): on-chain fee split (e.g. mainnet settle 0xce7ac4bd7f821017b9707a0cc6e0f4354a9c6bbeaa032eec79f290e5454b1343: 10000 → 250 fee + 9750 payTo at 250bps), cookie mint, free-ride on 2nd request (no double charge), fee counters materialized. Requires a facilitator serving v2-eip155-auth-capture (overlay image 2.0.2-auth-capture.2+; the .1 tag does not register the blueprint).

https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v

@bussyjd
bussyjd changed the base branch from integration/v0.14.0-rc0 to mainJuly 20, 2026 14:39
@bussyjd
bussyjd marked this pull request as draft July 20, 2026 14:39
@bussyjd
bussyjdforce-pushed the feat/authcapture-unlock branch from 8224493 to a95d2caCompareJuly 20, 2026 14:42
Inline first-message fee capture for gate:auth offers: the SIWX session is
minted by settling an x402 auth-capture charge (EIP-3009 single-shot,
autoCapture) instead of a plain signature; subsequent requests ride the
session cookie free. The escrow enforces the client-signed fee split
(feeBps -> feeRecipient) on-chain at charge() time.
- internal/x402/authcapture.go: auth-capture requirement builder + signed
payload validation (validateSignedUnlockRequirement pins every economic
field of the client-signed requirement against config)
- internal/x402/unlockgate.go: unlock flow — 402 (x402Version 2) ->
verify+settle against the signed payload -> mint SIWX cookie
- internal/x402/metrics.go: obol_x402_verifier_fee_revenue_atomic_total +
settled_volume_atomic_total (network/asset/fee_recipient), excluded from
route-pruning
- config: global authCaptureUnlock block (offerPrefix, price, payTo,
feeRecipient, min/maxFeeBps, captureAuthorizer)
Config-gated, off by default. Settlement must use the client's signed
'accepted' requirement verbatim (PaymentInfo hash commits server-issued
deadlines; rebuilding drifts them and breaks the signature).
Proven end-to-end on Base Sepolia and Base mainnet (on-chain fee split,
cookie mint, free-ride, metrics materialization).
Claude-Session: https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v
handlePaidUnlock discarded the settle response when facilitatorSettle
errored, losing the tx hash the facilitator returns when it submits the
settle tx on-chain and then fails on the receipt path. A charged buyer got
a bare settle_failed with no cookie, no answer, and no way to reconcile the
on-chain debit.
Mirror the per-request paid path (HandleProxy): surface settleResp.Transaction
via X-PAYMENT-RESPONSE + a 'you may pay twice' hint, and log it, on both the
transport-error and !Success branches. A failed settle still mints no session.
Claude-Session: https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v
@bussyjd
bussyjd marked this pull request as ready for review July 20, 2026 18:31
@bussyjd

Copy link
Copy Markdown
ContributorAuthor

Subsumed by #800 (integration/v0.14.0-rc1) — this change is already folded into rc1 (0 commits not in rc1 by patch-id) and ships in the v0.14.0-rc1 pre-release. Closing to avoid an individual merge forcing an rc1 rebase; the branch is retained.

https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v

@bussyjdbussyjd closed this Jul 27, 2026
@bussyjdbussyjd mentioned this pull request Aug 6, 2026
Sign up for freeto 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

@bussyjd
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(x402): auth-capture unlock gate + fee revenue metrics - #798

Closed
bussyjd wants to merge 2 commits into
mainfrom
feat/authcapture-unlock
Closed

feat(x402): auth-capture unlock gate + fee revenue metrics#798
bussyjd wants to merge 2 commits into
mainfrom
feat/authcapture-unlock

Conversation

@bussyjd

@bussyjdbussyjd commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Integration notes, dependencies & deferred work: #799

Inline first-message platform-fee capture for gate:auth offers, riding the x402 auth-capture scheme (ObolNetwork/x402-rs#9, escrow-enforced on-chain fee split).

What

  • First chat/agent message pays an auth-capture charge (EIP-3009 single-shot, autoCapture); the SIWX session cookie mints on settle success, and every later request rides the cookie free — one on-chain tx per session, no per-turn wallet interaction.
  • The escrow itself pays feeBps → feeRecipient at charge() within the client-signed bounds — no off-chain split.
  • New fee metrics on the verifier /metrics: obol_x402_verifier_fee_revenue_atomic_total + obol_x402_verifier_settled_volume_atomic_total (labels network/asset/fee_recipient), excluded from route-pruning.
  • Global authCaptureUnlock config block (offerPrefix, price, payTo, feeRecipient, min/maxFeeBps, captureAuthorizer). Config-gated, off by default.

Correctness notes

  • The unlock 402 advertises x402Version: 2 (auth-capture is v2-only; @x402 clients reject otherwise).
  • Settlement uses the client's signedaccepted requirement verbatim — the PaymentInfo hash commits server-issued deadlines, so a rebuilt requirement drifts and breaks the signature. validateSignedUnlockRequirement pins every economic field (amount, payTo, feeRecipient, fee bounds, authorizer) against config so a client can't underpay or redirect the fee.

Proven

End-to-end on Base Sepolia and Base mainnet (live cluster): on-chain fee split (e.g. mainnet settle 0xce7ac4bd7f821017b9707a0cc6e0f4354a9c6bbeaa032eec79f290e5454b1343: 10000 → 250 fee + 9750 payTo at 250bps), cookie mint, free-ride on 2nd request (no double charge), fee counters materialized. Requires a facilitator serving v2-eip155-auth-capture (overlay image 2.0.2-auth-capture.2+; the .1 tag does not register the blueprint).

https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v

@bussyjd
bussyjd changed the base branch from integration/v0.14.0-rc0 to mainJuly 20, 2026 14:39
@bussyjd
bussyjd marked this pull request as draft July 20, 2026 14:39
@bussyjd
bussyjdforce-pushed the feat/authcapture-unlock branch from 8224493 to a95d2caCompareJuly 20, 2026 14:42
Inline first-message fee capture for gate:auth offers: the SIWX session is
minted by settling an x402 auth-capture charge (EIP-3009 single-shot,
autoCapture) instead of a plain signature; subsequent requests ride the
session cookie free. The escrow enforces the client-signed fee split
(feeBps -> feeRecipient) on-chain at charge() time.
- internal/x402/authcapture.go: auth-capture requirement builder + signed
payload validation (validateSignedUnlockRequirement pins every economic
field of the client-signed requirement against config)
- internal/x402/unlockgate.go: unlock flow — 402 (x402Version 2) ->
verify+settle against the signed payload -> mint SIWX cookie
- internal/x402/metrics.go: obol_x402_verifier_fee_revenue_atomic_total +
settled_volume_atomic_total (network/asset/fee_recipient), excluded from
route-pruning
- config: global authCaptureUnlock block (offerPrefix, price, payTo,
feeRecipient, min/maxFeeBps, captureAuthorizer)
Config-gated, off by default. Settlement must use the client's signed
'accepted' requirement verbatim (PaymentInfo hash commits server-issued
deadlines; rebuilding drifts them and breaks the signature).
Proven end-to-end on Base Sepolia and Base mainnet (on-chain fee split,
cookie mint, free-ride, metrics materialization).
Claude-Session: https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v
handlePaidUnlock discarded the settle response when facilitatorSettle
errored, losing the tx hash the facilitator returns when it submits the
settle tx on-chain and then fails on the receipt path. A charged buyer got
a bare settle_failed with no cookie, no answer, and no way to reconcile the
on-chain debit.
Mirror the per-request paid path (HandleProxy): surface settleResp.Transaction
via X-PAYMENT-RESPONSE + a 'you may pay twice' hint, and log it, on both the
transport-error and !Success branches. A failed settle still mints no session.
Claude-Session: https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v
@bussyjd
bussyjd marked this pull request as ready for review July 20, 2026 18:31
@bussyjd

Copy link
Copy Markdown
ContributorAuthor

Subsumed by #800 (integration/v0.14.0-rc1) — this change is already folded into rc1 (0 commits not in rc1 by patch-id) and ships in the v0.14.0-rc1 pre-release. Closing to avoid an individual merge forcing an rc1 rebase; the branch is retained.

https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v

@bussyjdbussyjd closed this Jul 27, 2026
@bussyjdbussyjd mentioned this pull request Aug 6, 2026
Sign up for freeto 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

@bussyjd
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(x402): auth-capture unlock gate + fee revenue metrics - #798

Closed
bussyjd wants to merge 2 commits into
mainfrom
feat/authcapture-unlock
Closed

feat(x402): auth-capture unlock gate + fee revenue metrics#798
bussyjd wants to merge 2 commits into
mainfrom
feat/authcapture-unlock

Conversation

@bussyjd

@bussyjdbussyjd commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Integration notes, dependencies & deferred work: #799

Inline first-message platform-fee capture for gate:auth offers, riding the x402 auth-capture scheme (ObolNetwork/x402-rs#9, escrow-enforced on-chain fee split).

What

  • First chat/agent message pays an auth-capture charge (EIP-3009 single-shot, autoCapture); the SIWX session cookie mints on settle success, and every later request rides the cookie free — one on-chain tx per session, no per-turn wallet interaction.
  • The escrow itself pays feeBps → feeRecipient at charge() within the client-signed bounds — no off-chain split.
  • New fee metrics on the verifier /metrics: obol_x402_verifier_fee_revenue_atomic_total + obol_x402_verifier_settled_volume_atomic_total (labels network/asset/fee_recipient), excluded from route-pruning.
  • Global authCaptureUnlock config block (offerPrefix, price, payTo, feeRecipient, min/maxFeeBps, captureAuthorizer). Config-gated, off by default.

Correctness notes

  • The unlock 402 advertises x402Version: 2 (auth-capture is v2-only; @x402 clients reject otherwise).
  • Settlement uses the client's signedaccepted requirement verbatim — the PaymentInfo hash commits server-issued deadlines, so a rebuilt requirement drifts and breaks the signature. validateSignedUnlockRequirement pins every economic field (amount, payTo, feeRecipient, fee bounds, authorizer) against config so a client can't underpay or redirect the fee.

Proven

End-to-end on Base Sepolia and Base mainnet (live cluster): on-chain fee split (e.g. mainnet settle 0xce7ac4bd7f821017b9707a0cc6e0f4354a9c6bbeaa032eec79f290e5454b1343: 10000 → 250 fee + 9750 payTo at 250bps), cookie mint, free-ride on 2nd request (no double charge), fee counters materialized. Requires a facilitator serving v2-eip155-auth-capture (overlay image 2.0.2-auth-capture.2+; the .1 tag does not register the blueprint).

https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v

@bussyjd
bussyjd changed the base branch from integration/v0.14.0-rc0 to mainJuly 20, 2026 14:39
@bussyjd
bussyjd marked this pull request as draft July 20, 2026 14:39
@bussyjd
bussyjdforce-pushed the feat/authcapture-unlock branch from 8224493 to a95d2caCompareJuly 20, 2026 14:42
Inline first-message fee capture for gate:auth offers: the SIWX session is
minted by settling an x402 auth-capture charge (EIP-3009 single-shot,
autoCapture) instead of a plain signature; subsequent requests ride the
session cookie free. The escrow enforces the client-signed fee split
(feeBps -> feeRecipient) on-chain at charge() time.
- internal/x402/authcapture.go: auth-capture requirement builder + signed
payload validation (validateSignedUnlockRequirement pins every economic
field of the client-signed requirement against config)
- internal/x402/unlockgate.go: unlock flow — 402 (x402Version 2) ->
verify+settle against the signed payload -> mint SIWX cookie
- internal/x402/metrics.go: obol_x402_verifier_fee_revenue_atomic_total +
settled_volume_atomic_total (network/asset/fee_recipient), excluded from
route-pruning
- config: global authCaptureUnlock block (offerPrefix, price, payTo,
feeRecipient, min/maxFeeBps, captureAuthorizer)
Config-gated, off by default. Settlement must use the client's signed
'accepted' requirement verbatim (PaymentInfo hash commits server-issued
deadlines; rebuilding drifts them and breaks the signature).
Proven end-to-end on Base Sepolia and Base mainnet (on-chain fee split,
cookie mint, free-ride, metrics materialization).
Claude-Session: https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v
handlePaidUnlock discarded the settle response when facilitatorSettle
errored, losing the tx hash the facilitator returns when it submits the
settle tx on-chain and then fails on the receipt path. A charged buyer got
a bare settle_failed with no cookie, no answer, and no way to reconcile the
on-chain debit.
Mirror the per-request paid path (HandleProxy): surface settleResp.Transaction
via X-PAYMENT-RESPONSE + a 'you may pay twice' hint, and log it, on both the
transport-error and !Success branches. A failed settle still mints no session.
Claude-Session: https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v
@bussyjd
bussyjd marked this pull request as ready for review July 20, 2026 18:31
@bussyjd

Copy link
Copy Markdown
ContributorAuthor

Subsumed by #800 (integration/v0.14.0-rc1) — this change is already folded into rc1 (0 commits not in rc1 by patch-id) and ships in the v0.14.0-rc1 pre-release. Closing to avoid an individual merge forcing an rc1 rebase; the branch is retained.

https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v

@bussyjdbussyjd closed this Jul 27, 2026
@bussyjdbussyjd mentioned this pull request Aug 6, 2026
Sign up for freeto 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

@bussyjd
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(x402): auth-capture unlock gate + fee revenue metrics - #798

Closed
bussyjd wants to merge 2 commits into
mainfrom
feat/authcapture-unlock
Closed

feat(x402): auth-capture unlock gate + fee revenue metrics#798
bussyjd wants to merge 2 commits into
mainfrom
feat/authcapture-unlock

Conversation

@bussyjd

@bussyjdbussyjd commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Integration notes, dependencies & deferred work: #799

Inline first-message platform-fee capture for gate:auth offers, riding the x402 auth-capture scheme (ObolNetwork/x402-rs#9, escrow-enforced on-chain fee split).

What

  • First chat/agent message pays an auth-capture charge (EIP-3009 single-shot, autoCapture); the SIWX session cookie mints on settle success, and every later request rides the cookie free — one on-chain tx per session, no per-turn wallet interaction.
  • The escrow itself pays feeBps → feeRecipient at charge() within the client-signed bounds — no off-chain split.
  • New fee metrics on the verifier /metrics: obol_x402_verifier_fee_revenue_atomic_total + obol_x402_verifier_settled_volume_atomic_total (labels network/asset/fee_recipient), excluded from route-pruning.
  • Global authCaptureUnlock config block (offerPrefix, price, payTo, feeRecipient, min/maxFeeBps, captureAuthorizer). Config-gated, off by default.

Correctness notes

  • The unlock 402 advertises x402Version: 2 (auth-capture is v2-only; @x402 clients reject otherwise).
  • Settlement uses the client's signedaccepted requirement verbatim — the PaymentInfo hash commits server-issued deadlines, so a rebuilt requirement drifts and breaks the signature. validateSignedUnlockRequirement pins every economic field (amount, payTo, feeRecipient, fee bounds, authorizer) against config so a client can't underpay or redirect the fee.

Proven

End-to-end on Base Sepolia and Base mainnet (live cluster): on-chain fee split (e.g. mainnet settle 0xce7ac4bd7f821017b9707a0cc6e0f4354a9c6bbeaa032eec79f290e5454b1343: 10000 → 250 fee + 9750 payTo at 250bps), cookie mint, free-ride on 2nd request (no double charge), fee counters materialized. Requires a facilitator serving v2-eip155-auth-capture (overlay image 2.0.2-auth-capture.2+; the .1 tag does not register the blueprint).

https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v

@bussyjd
bussyjd changed the base branch from integration/v0.14.0-rc0 to mainJuly 20, 2026 14:39
@bussyjd
bussyjd marked this pull request as draft July 20, 2026 14:39
@bussyjd
bussyjdforce-pushed the feat/authcapture-unlock branch from 8224493 to a95d2caCompareJuly 20, 2026 14:42
Inline first-message fee capture for gate:auth offers: the SIWX session is
minted by settling an x402 auth-capture charge (EIP-3009 single-shot,
autoCapture) instead of a plain signature; subsequent requests ride the
session cookie free. The escrow enforces the client-signed fee split
(feeBps -> feeRecipient) on-chain at charge() time.
- internal/x402/authcapture.go: auth-capture requirement builder + signed
payload validation (validateSignedUnlockRequirement pins every economic
field of the client-signed requirement against config)
- internal/x402/unlockgate.go: unlock flow — 402 (x402Version 2) ->
verify+settle against the signed payload -> mint SIWX cookie
- internal/x402/metrics.go: obol_x402_verifier_fee_revenue_atomic_total +
settled_volume_atomic_total (network/asset/fee_recipient), excluded from
route-pruning
- config: global authCaptureUnlock block (offerPrefix, price, payTo,
feeRecipient, min/maxFeeBps, captureAuthorizer)
Config-gated, off by default. Settlement must use the client's signed
'accepted' requirement verbatim (PaymentInfo hash commits server-issued
deadlines; rebuilding drifts them and breaks the signature).
Proven end-to-end on Base Sepolia and Base mainnet (on-chain fee split,
cookie mint, free-ride, metrics materialization).
Claude-Session: https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v
handlePaidUnlock discarded the settle response when facilitatorSettle
errored, losing the tx hash the facilitator returns when it submits the
settle tx on-chain and then fails on the receipt path. A charged buyer got
a bare settle_failed with no cookie, no answer, and no way to reconcile the
on-chain debit.
Mirror the per-request paid path (HandleProxy): surface settleResp.Transaction
via X-PAYMENT-RESPONSE + a 'you may pay twice' hint, and log it, on both the
transport-error and !Success branches. A failed settle still mints no session.
Claude-Session: https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v
@bussyjd
bussyjd marked this pull request as ready for review July 20, 2026 18:31
@bussyjd

Copy link
Copy Markdown
ContributorAuthor

Subsumed by #800 (integration/v0.14.0-rc1) — this change is already folded into rc1 (0 commits not in rc1 by patch-id) and ships in the v0.14.0-rc1 pre-release. Closing to avoid an individual merge forcing an rc1 rebase; the branch is retained.

https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v

@bussyjdbussyjd closed this Jul 27, 2026
@bussyjdbussyjd mentioned this pull request Aug 6, 2026
Sign up for freeto 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

@bussyjd
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(x402): auth-capture unlock gate + fee revenue metrics - #798

Closed
bussyjd wants to merge 2 commits into
mainfrom
feat/authcapture-unlock
Closed

feat(x402): auth-capture unlock gate + fee revenue metrics#798
bussyjd wants to merge 2 commits into
mainfrom
feat/authcapture-unlock

Conversation

@bussyjd

@bussyjdbussyjd commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Integration notes, dependencies & deferred work: #799

Inline first-message platform-fee capture for gate:auth offers, riding the x402 auth-capture scheme (ObolNetwork/x402-rs#9, escrow-enforced on-chain fee split).

What

  • First chat/agent message pays an auth-capture charge (EIP-3009 single-shot, autoCapture); the SIWX session cookie mints on settle success, and every later request rides the cookie free — one on-chain tx per session, no per-turn wallet interaction.
  • The escrow itself pays feeBps → feeRecipient at charge() within the client-signed bounds — no off-chain split.
  • New fee metrics on the verifier /metrics: obol_x402_verifier_fee_revenue_atomic_total + obol_x402_verifier_settled_volume_atomic_total (labels network/asset/fee_recipient), excluded from route-pruning.
  • Global authCaptureUnlock config block (offerPrefix, price, payTo, feeRecipient, min/maxFeeBps, captureAuthorizer). Config-gated, off by default.

Correctness notes

  • The unlock 402 advertises x402Version: 2 (auth-capture is v2-only; @x402 clients reject otherwise).
  • Settlement uses the client's signedaccepted requirement verbatim — the PaymentInfo hash commits server-issued deadlines, so a rebuilt requirement drifts and breaks the signature. validateSignedUnlockRequirement pins every economic field (amount, payTo, feeRecipient, fee bounds, authorizer) against config so a client can't underpay or redirect the fee.

Proven

End-to-end on Base Sepolia and Base mainnet (live cluster): on-chain fee split (e.g. mainnet settle 0xce7ac4bd7f821017b9707a0cc6e0f4354a9c6bbeaa032eec79f290e5454b1343: 10000 → 250 fee + 9750 payTo at 250bps), cookie mint, free-ride on 2nd request (no double charge), fee counters materialized. Requires a facilitator serving v2-eip155-auth-capture (overlay image 2.0.2-auth-capture.2+; the .1 tag does not register the blueprint).

https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v

@bussyjd
bussyjd changed the base branch from integration/v0.14.0-rc0 to mainJuly 20, 2026 14:39
@bussyjd
bussyjd marked this pull request as draft July 20, 2026 14:39
@bussyjd
bussyjdforce-pushed the feat/authcapture-unlock branch from 8224493 to a95d2caCompareJuly 20, 2026 14:42
Inline first-message fee capture for gate:auth offers: the SIWX session is
minted by settling an x402 auth-capture charge (EIP-3009 single-shot,
autoCapture) instead of a plain signature; subsequent requests ride the
session cookie free. The escrow enforces the client-signed fee split
(feeBps -> feeRecipient) on-chain at charge() time.
- internal/x402/authcapture.go: auth-capture requirement builder + signed
payload validation (validateSignedUnlockRequirement pins every economic
field of the client-signed requirement against config)
- internal/x402/unlockgate.go: unlock flow — 402 (x402Version 2) ->
verify+settle against the signed payload -> mint SIWX cookie
- internal/x402/metrics.go: obol_x402_verifier_fee_revenue_atomic_total +
settled_volume_atomic_total (network/asset/fee_recipient), excluded from
route-pruning
- config: global authCaptureUnlock block (offerPrefix, price, payTo,
feeRecipient, min/maxFeeBps, captureAuthorizer)
Config-gated, off by default. Settlement must use the client's signed
'accepted' requirement verbatim (PaymentInfo hash commits server-issued
deadlines; rebuilding drifts them and breaks the signature).
Proven end-to-end on Base Sepolia and Base mainnet (on-chain fee split,
cookie mint, free-ride, metrics materialization).
Claude-Session: https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v
handlePaidUnlock discarded the settle response when facilitatorSettle
errored, losing the tx hash the facilitator returns when it submits the
settle tx on-chain and then fails on the receipt path. A charged buyer got
a bare settle_failed with no cookie, no answer, and no way to reconcile the
on-chain debit.
Mirror the per-request paid path (HandleProxy): surface settleResp.Transaction
via X-PAYMENT-RESPONSE + a 'you may pay twice' hint, and log it, on both the
transport-error and !Success branches. A failed settle still mints no session.
Claude-Session: https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v
@bussyjd
bussyjd marked this pull request as ready for review July 20, 2026 18:31
@bussyjd

Copy link
Copy Markdown
ContributorAuthor

Subsumed by #800 (integration/v0.14.0-rc1) — this change is already folded into rc1 (0 commits not in rc1 by patch-id) and ships in the v0.14.0-rc1 pre-release. Closing to avoid an individual merge forcing an rc1 rebase; the branch is retained.

https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v

@bussyjdbussyjd closed this Jul 27, 2026
@bussyjdbussyjd mentioned this pull request Aug 6, 2026
Sign up for freeto 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

@bussyjd
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(x402): auth-capture unlock gate + fee revenue metrics - #798

Closed
bussyjd wants to merge 2 commits into
mainfrom
feat/authcapture-unlock
Closed

feat(x402): auth-capture unlock gate + fee revenue metrics#798
bussyjd wants to merge 2 commits into
mainfrom
feat/authcapture-unlock

Conversation

@bussyjd

@bussyjdbussyjd commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Integration notes, dependencies & deferred work: #799

Inline first-message platform-fee capture for gate:auth offers, riding the x402 auth-capture scheme (ObolNetwork/x402-rs#9, escrow-enforced on-chain fee split).

What

  • First chat/agent message pays an auth-capture charge (EIP-3009 single-shot, autoCapture); the SIWX session cookie mints on settle success, and every later request rides the cookie free — one on-chain tx per session, no per-turn wallet interaction.
  • The escrow itself pays feeBps → feeRecipient at charge() within the client-signed bounds — no off-chain split.
  • New fee metrics on the verifier /metrics: obol_x402_verifier_fee_revenue_atomic_total + obol_x402_verifier_settled_volume_atomic_total (labels network/asset/fee_recipient), excluded from route-pruning.
  • Global authCaptureUnlock config block (offerPrefix, price, payTo, feeRecipient, min/maxFeeBps, captureAuthorizer). Config-gated, off by default.

Correctness notes

  • The unlock 402 advertises x402Version: 2 (auth-capture is v2-only; @x402 clients reject otherwise).
  • Settlement uses the client's signedaccepted requirement verbatim — the PaymentInfo hash commits server-issued deadlines, so a rebuilt requirement drifts and breaks the signature. validateSignedUnlockRequirement pins every economic field (amount, payTo, feeRecipient, fee bounds, authorizer) against config so a client can't underpay or redirect the fee.

Proven

End-to-end on Base Sepolia and Base mainnet (live cluster): on-chain fee split (e.g. mainnet settle 0xce7ac4bd7f821017b9707a0cc6e0f4354a9c6bbeaa032eec79f290e5454b1343: 10000 → 250 fee + 9750 payTo at 250bps), cookie mint, free-ride on 2nd request (no double charge), fee counters materialized. Requires a facilitator serving v2-eip155-auth-capture (overlay image 2.0.2-auth-capture.2+; the .1 tag does not register the blueprint).

https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v

@bussyjd
bussyjd changed the base branch from integration/v0.14.0-rc0 to mainJuly 20, 2026 14:39
@bussyjd
bussyjd marked this pull request as draft July 20, 2026 14:39
@bussyjd
bussyjdforce-pushed the feat/authcapture-unlock branch from 8224493 to a95d2caCompareJuly 20, 2026 14:42
Inline first-message fee capture for gate:auth offers: the SIWX session is
minted by settling an x402 auth-capture charge (EIP-3009 single-shot,
autoCapture) instead of a plain signature; subsequent requests ride the
session cookie free. The escrow enforces the client-signed fee split
(feeBps -> feeRecipient) on-chain at charge() time.
- internal/x402/authcapture.go: auth-capture requirement builder + signed
payload validation (validateSignedUnlockRequirement pins every economic
field of the client-signed requirement against config)
- internal/x402/unlockgate.go: unlock flow — 402 (x402Version 2) ->
verify+settle against the signed payload -> mint SIWX cookie
- internal/x402/metrics.go: obol_x402_verifier_fee_revenue_atomic_total +
settled_volume_atomic_total (network/asset/fee_recipient), excluded from
route-pruning
- config: global authCaptureUnlock block (offerPrefix, price, payTo,
feeRecipient, min/maxFeeBps, captureAuthorizer)
Config-gated, off by default. Settlement must use the client's signed
'accepted' requirement verbatim (PaymentInfo hash commits server-issued
deadlines; rebuilding drifts them and breaks the signature).
Proven end-to-end on Base Sepolia and Base mainnet (on-chain fee split,
cookie mint, free-ride, metrics materialization).
Claude-Session: https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v
handlePaidUnlock discarded the settle response when facilitatorSettle
errored, losing the tx hash the facilitator returns when it submits the
settle tx on-chain and then fails on the receipt path. A charged buyer got
a bare settle_failed with no cookie, no answer, and no way to reconcile the
on-chain debit.
Mirror the per-request paid path (HandleProxy): surface settleResp.Transaction
via X-PAYMENT-RESPONSE + a 'you may pay twice' hint, and log it, on both the
transport-error and !Success branches. A failed settle still mints no session.
Claude-Session: https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v
@bussyjd
bussyjd marked this pull request as ready for review July 20, 2026 18:31
@bussyjd

Copy link
Copy Markdown
ContributorAuthor

Subsumed by #800 (integration/v0.14.0-rc1) — this change is already folded into rc1 (0 commits not in rc1 by patch-id) and ships in the v0.14.0-rc1 pre-release. Closing to avoid an individual merge forcing an rc1 rebase; the branch is retained.

https://claude.ai/code/session_01PnhCQLz7CHuDBUhWd5xF8v

@bussyjdbussyjd closed this Jul 27, 2026
@bussyjdbussyjd mentioned this pull request Aug 6, 2026
Sign up for freeto 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

@bussyjd