feat(x402): optional beforePayment hook to screen the recipient before paying - #1454

Open
hypeprinter007-stack wants to merge 1 commit into
coinbase:mainfrom
hypeprinter007-stack:x402-before-payment-hook
Open

feat(x402): optional beforePayment hook to screen the recipient before paying#1454
hypeprinter007-stack wants to merge 1 commit into
coinbase:mainfrom
hypeprinter007-stack:x402-before-payment-hook

Conversation

@hypeprinter007-stack

Copy link
Copy Markdown

What & why

Implements the neutral pre-payment hook proposed in #1402.

Today the x402 action provider gates where the agent can pay (registeredServices) and how much (maxPaymentUsdc), but nothing checks who the recipient is. A compromised or mistaken agent can pay a sanctioned / drainer / phishing address that passes both existing gates. This adds the missing seat: an optional recipient check at the pre-payment chokepoint.

The change

A new optional beforePayment config hook:

constconfig: X402Config={registeredServices: ["https://api.example.com"],beforePayment: async({ payTo, url, network, asset, amount, method })=>{if(payTo&&(awaitisSanctioned(payTo)))return{abort: true,reason: "recipient flagged"};},};
  • Runs in retry_http_request_with_x402after amount + network validation and immediately before any signing/settlement.
  • Receives the payment context including the selected option's payTo.
  • Returning { abort: true, reason } refuses the payment — no signature, no settlement; the action returns a structured error.
  • Provider-neutral by design: no screening backend is imported. Any implementation plugs in (sanctions / reputation / allowlist). The README shows anchor-x402-safe-pay's allow/review/block verdict as one example.

This matches the design discussion on #1402 (three-state-friendly: the hook returns a decision + reason and is agnostic to how the verdict was reached).

Scope & compatibility

  • Backward compatible — no behavior change unless beforePayment is set.
  • Covers the recommended two-step flow (make_http_requestretry_http_request_with_x402), which exposes payTo before paying. The one-shot make_http_request_with_x402 auto-settles a 402 inside wrapFetchWithPayment and does not expose the recipient pre-settlement, so it is intentionally not gated (documented in the README). Gating it would need a larger refactor of that path.

Tests

Two cases added mirroring the existing retryWithX402 tests:

  • beforePayment returns { abort: true } → hook sees the payTo/context, wrapFetchWithPayment is never called, action returns the abort error.
  • beforePayment allows → payment proceeds normally.

Notes for reviewers

  • Files: schemas.ts (config field + X402BeforePaymentContext / X402BeforePaymentDecision types), x402ActionProvider.ts (resolve + call site), x402ActionProvider.test.ts (2 tests), README.md (docs + example).
  • I wasn't able to run the full TypeScript monorepo suite in my environment; the added tests mirror the existing retry tests exactly and the types are consistent by construction — please let CI validate.
  • Happy to add Python parity in the same PR (or a follow-up) if you'd like it here — kept this TS-first to get a read on the shape before duplicating.

Closes#1402 (or partially addresses, if you'd prefer to keep it open for the Python side).

…e paying
Implements the neutral pre-payment hook proposed in coinbase#1402. Today the x402 action
provider gates *where* the agent pays (registeredServices) and *how much*
(maxPaymentUsdc), but nothing checks *who* the recipient is — a compromised or
mistaken agent can pay a sanctioned/drainer address.
Adds an optional `beforePayment` config hook that runs in retry_http_request_with_x402
after amount + network validation and immediately before the payment is signed/
settled, receiving the payment context (incl. the selected option's `payTo`).
Returning { abort: true, reason } refuses the payment — no signature, no settlement.
Deliberately provider-neutral: no screening backend is imported. Any implementation
plugs in (sanctions / reputation / allowlist); the README shows anchor-x402-safe-pay's
allow/review/block verdict as one example.
Scope: the hook covers the recommended two-step flow (make_http_request ->
retry_http_request_with_x402), which exposes payTo before paying. The one-shot
make_http_request_with_x402 auto-settles a 402 inside wrapFetchWithPayment and does
not expose the recipient pre-settlement, so it is intentionally not gated (noted in
the README). Fully backward compatible — no behavior change unless beforePayment is set.
Tests: added two cases mirroring the existing retry tests — abort (no payment signed)
and allow (payment proceeds).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@cb-heimdall

Copy link
Copy Markdown

🟡 Heimdall Review Status

RequirementStatusMore Info
Reviews 🟡 0/1
Denominator calculation
Show calculation
1 if user is bot0
1 if user is external0
2 if repo is sensitive0
From .codeflow.yml1
Additional review requirements
Show calculation
Max0
0
From CODEOWNERS0
Global minimum0
Max 1
1
1 if commit is unverified0
Sum1

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation action provider New action provider typescript labels Aug 18, 2026
hypeprinter007-stack added a commit to hypeprinter007-stack/anchor-x402-safe-pay that referenced this pull request Aug 22, 2026
…payment hook
Adds an optional, stateless per-send amount cap that composes with the recipient
verdict at the pre-payment hook (JS composeCapWithScreen / Py compose_cap_with_screen).
Why the hook, not guardedSend: with x402 the amount is payee-set in the 402
challenge, so a cap checked when the agent plans a call can be bypassed by a
challenge-time bump (TOCTOU). guardedSend wraps an opaque thunk that pays
internally and never sees the amount; the hook seat carries the real payTo +
amount at send time (matches coinbase/agentkit#1454's beforePayment context), so
the bound is evaluated against the amount that will actually be paid.
Fail-closed: flagged recipient OR over-cap amount OR screen failure all abort;
verdict wins; maxAmount is inclusive (paying exactly the cap is allowed).
Stateless by design — cumulative/rate budgets stay in the wallet/agent layer.
Structured reasons: flagged_recipient | exceeds_cap.
Test path contributed by @Sergio87Felix (#2); implemented to that contract with
the boundary corrected to inclusive-max. +7 JS, +7 Python regression cases; README
+ llms.txt document the helper.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

action providerNew action providerdocumentationImprovements or additions to documentationtypescript

Development

Successfully merging this pull request may close these issues.

Proposal: a neutral pre-payment recipient hook in the x402 action provider (inspect payTo before paying)

2 participants

@hypeprinter007-stack@cb-heimdall
, '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): optional beforePayment hook to screen the recipient before paying - #1454

Open
hypeprinter007-stack wants to merge 1 commit into
coinbase:mainfrom
hypeprinter007-stack:x402-before-payment-hook
Open

feat(x402): optional beforePayment hook to screen the recipient before paying#1454
hypeprinter007-stack wants to merge 1 commit into
coinbase:mainfrom
hypeprinter007-stack:x402-before-payment-hook

Conversation

@hypeprinter007-stack

Copy link
Copy Markdown

What & why

Implements the neutral pre-payment hook proposed in #1402.

Today the x402 action provider gates where the agent can pay (registeredServices) and how much (maxPaymentUsdc), but nothing checks who the recipient is. A compromised or mistaken agent can pay a sanctioned / drainer / phishing address that passes both existing gates. This adds the missing seat: an optional recipient check at the pre-payment chokepoint.

The change

A new optional beforePayment config hook:

constconfig: X402Config={registeredServices: ["https://api.example.com"],beforePayment: async({ payTo, url, network, asset, amount, method })=>{if(payTo&&(awaitisSanctioned(payTo)))return{abort: true,reason: "recipient flagged"};},};
  • Runs in retry_http_request_with_x402after amount + network validation and immediately before any signing/settlement.
  • Receives the payment context including the selected option's payTo.
  • Returning { abort: true, reason } refuses the payment — no signature, no settlement; the action returns a structured error.
  • Provider-neutral by design: no screening backend is imported. Any implementation plugs in (sanctions / reputation / allowlist). The README shows anchor-x402-safe-pay's allow/review/block verdict as one example.

This matches the design discussion on #1402 (three-state-friendly: the hook returns a decision + reason and is agnostic to how the verdict was reached).

Scope & compatibility

  • Backward compatible — no behavior change unless beforePayment is set.
  • Covers the recommended two-step flow (make_http_requestretry_http_request_with_x402), which exposes payTo before paying. The one-shot make_http_request_with_x402 auto-settles a 402 inside wrapFetchWithPayment and does not expose the recipient pre-settlement, so it is intentionally not gated (documented in the README). Gating it would need a larger refactor of that path.

Tests

Two cases added mirroring the existing retryWithX402 tests:

  • beforePayment returns { abort: true } → hook sees the payTo/context, wrapFetchWithPayment is never called, action returns the abort error.
  • beforePayment allows → payment proceeds normally.

Notes for reviewers

  • Files: schemas.ts (config field + X402BeforePaymentContext / X402BeforePaymentDecision types), x402ActionProvider.ts (resolve + call site), x402ActionProvider.test.ts (2 tests), README.md (docs + example).
  • I wasn't able to run the full TypeScript monorepo suite in my environment; the added tests mirror the existing retry tests exactly and the types are consistent by construction — please let CI validate.
  • Happy to add Python parity in the same PR (or a follow-up) if you'd like it here — kept this TS-first to get a read on the shape before duplicating.

Closes#1402 (or partially addresses, if you'd prefer to keep it open for the Python side).

…e paying
Implements the neutral pre-payment hook proposed in coinbase#1402. Today the x402 action
provider gates *where* the agent pays (registeredServices) and *how much*
(maxPaymentUsdc), but nothing checks *who* the recipient is — a compromised or
mistaken agent can pay a sanctioned/drainer address.
Adds an optional `beforePayment` config hook that runs in retry_http_request_with_x402
after amount + network validation and immediately before the payment is signed/
settled, receiving the payment context (incl. the selected option's `payTo`).
Returning { abort: true, reason } refuses the payment — no signature, no settlement.
Deliberately provider-neutral: no screening backend is imported. Any implementation
plugs in (sanctions / reputation / allowlist); the README shows anchor-x402-safe-pay's
allow/review/block verdict as one example.
Scope: the hook covers the recommended two-step flow (make_http_request ->
retry_http_request_with_x402), which exposes payTo before paying. The one-shot
make_http_request_with_x402 auto-settles a 402 inside wrapFetchWithPayment and does
not expose the recipient pre-settlement, so it is intentionally not gated (noted in
the README). Fully backward compatible — no behavior change unless beforePayment is set.
Tests: added two cases mirroring the existing retry tests — abort (no payment signed)
and allow (payment proceeds).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@cb-heimdall

Copy link
Copy Markdown

🟡 Heimdall Review Status

RequirementStatusMore Info
Reviews 🟡 0/1
Denominator calculation
Show calculation
1 if user is bot0
1 if user is external0
2 if repo is sensitive0
From .codeflow.yml1
Additional review requirements
Show calculation
Max0
0
From CODEOWNERS0
Global minimum0
Max 1
1
1 if commit is unverified0
Sum1

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation action provider New action provider typescript labels Aug 18, 2026
hypeprinter007-stack added a commit to hypeprinter007-stack/anchor-x402-safe-pay that referenced this pull request Aug 22, 2026
…payment hook
Adds an optional, stateless per-send amount cap that composes with the recipient
verdict at the pre-payment hook (JS composeCapWithScreen / Py compose_cap_with_screen).
Why the hook, not guardedSend: with x402 the amount is payee-set in the 402
challenge, so a cap checked when the agent plans a call can be bypassed by a
challenge-time bump (TOCTOU). guardedSend wraps an opaque thunk that pays
internally and never sees the amount; the hook seat carries the real payTo +
amount at send time (matches coinbase/agentkit#1454's beforePayment context), so
the bound is evaluated against the amount that will actually be paid.
Fail-closed: flagged recipient OR over-cap amount OR screen failure all abort;
verdict wins; maxAmount is inclusive (paying exactly the cap is allowed).
Stateless by design — cumulative/rate budgets stay in the wallet/agent layer.
Structured reasons: flagged_recipient | exceeds_cap.
Test path contributed by @Sergio87Felix (#2); implemented to that contract with
the boundary corrected to inclusive-max. +7 JS, +7 Python regression cases; README
+ llms.txt document the helper.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

action providerNew action providerdocumentationImprovements or additions to documentationtypescript

Development

Successfully merging this pull request may close these issues.

Proposal: a neutral pre-payment recipient hook in the x402 action provider (inspect payTo before paying)

2 participants

@hypeprinter007-stack@cb-heimdall
, '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): optional beforePayment hook to screen the recipient before paying - #1454

Open
hypeprinter007-stack wants to merge 1 commit into
coinbase:mainfrom
hypeprinter007-stack:x402-before-payment-hook
Open

feat(x402): optional beforePayment hook to screen the recipient before paying#1454
hypeprinter007-stack wants to merge 1 commit into
coinbase:mainfrom
hypeprinter007-stack:x402-before-payment-hook

Conversation

@hypeprinter007-stack

Copy link
Copy Markdown

What & why

Implements the neutral pre-payment hook proposed in #1402.

Today the x402 action provider gates where the agent can pay (registeredServices) and how much (maxPaymentUsdc), but nothing checks who the recipient is. A compromised or mistaken agent can pay a sanctioned / drainer / phishing address that passes both existing gates. This adds the missing seat: an optional recipient check at the pre-payment chokepoint.

The change

A new optional beforePayment config hook:

constconfig: X402Config={registeredServices: ["https://api.example.com"],beforePayment: async({ payTo, url, network, asset, amount, method })=>{if(payTo&&(awaitisSanctioned(payTo)))return{abort: true,reason: "recipient flagged"};},};
  • Runs in retry_http_request_with_x402after amount + network validation and immediately before any signing/settlement.
  • Receives the payment context including the selected option's payTo.
  • Returning { abort: true, reason } refuses the payment — no signature, no settlement; the action returns a structured error.
  • Provider-neutral by design: no screening backend is imported. Any implementation plugs in (sanctions / reputation / allowlist). The README shows anchor-x402-safe-pay's allow/review/block verdict as one example.

This matches the design discussion on #1402 (three-state-friendly: the hook returns a decision + reason and is agnostic to how the verdict was reached).

Scope & compatibility

  • Backward compatible — no behavior change unless beforePayment is set.
  • Covers the recommended two-step flow (make_http_requestretry_http_request_with_x402), which exposes payTo before paying. The one-shot make_http_request_with_x402 auto-settles a 402 inside wrapFetchWithPayment and does not expose the recipient pre-settlement, so it is intentionally not gated (documented in the README). Gating it would need a larger refactor of that path.

Tests

Two cases added mirroring the existing retryWithX402 tests:

  • beforePayment returns { abort: true } → hook sees the payTo/context, wrapFetchWithPayment is never called, action returns the abort error.
  • beforePayment allows → payment proceeds normally.

Notes for reviewers

  • Files: schemas.ts (config field + X402BeforePaymentContext / X402BeforePaymentDecision types), x402ActionProvider.ts (resolve + call site), x402ActionProvider.test.ts (2 tests), README.md (docs + example).
  • I wasn't able to run the full TypeScript monorepo suite in my environment; the added tests mirror the existing retry tests exactly and the types are consistent by construction — please let CI validate.
  • Happy to add Python parity in the same PR (or a follow-up) if you'd like it here — kept this TS-first to get a read on the shape before duplicating.

Closes#1402 (or partially addresses, if you'd prefer to keep it open for the Python side).

…e paying
Implements the neutral pre-payment hook proposed in coinbase#1402. Today the x402 action
provider gates *where* the agent pays (registeredServices) and *how much*
(maxPaymentUsdc), but nothing checks *who* the recipient is — a compromised or
mistaken agent can pay a sanctioned/drainer address.
Adds an optional `beforePayment` config hook that runs in retry_http_request_with_x402
after amount + network validation and immediately before the payment is signed/
settled, receiving the payment context (incl. the selected option's `payTo`).
Returning { abort: true, reason } refuses the payment — no signature, no settlement.
Deliberately provider-neutral: no screening backend is imported. Any implementation
plugs in (sanctions / reputation / allowlist); the README shows anchor-x402-safe-pay's
allow/review/block verdict as one example.
Scope: the hook covers the recommended two-step flow (make_http_request ->
retry_http_request_with_x402), which exposes payTo before paying. The one-shot
make_http_request_with_x402 auto-settles a 402 inside wrapFetchWithPayment and does
not expose the recipient pre-settlement, so it is intentionally not gated (noted in
the README). Fully backward compatible — no behavior change unless beforePayment is set.
Tests: added two cases mirroring the existing retry tests — abort (no payment signed)
and allow (payment proceeds).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@cb-heimdall

Copy link
Copy Markdown

🟡 Heimdall Review Status

RequirementStatusMore Info
Reviews 🟡 0/1
Denominator calculation
Show calculation
1 if user is bot0
1 if user is external0
2 if repo is sensitive0
From .codeflow.yml1
Additional review requirements
Show calculation
Max0
0
From CODEOWNERS0
Global minimum0
Max 1
1
1 if commit is unverified0
Sum1

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation action provider New action provider typescript labels Aug 18, 2026
hypeprinter007-stack added a commit to hypeprinter007-stack/anchor-x402-safe-pay that referenced this pull request Aug 22, 2026
…payment hook
Adds an optional, stateless per-send amount cap that composes with the recipient
verdict at the pre-payment hook (JS composeCapWithScreen / Py compose_cap_with_screen).
Why the hook, not guardedSend: with x402 the amount is payee-set in the 402
challenge, so a cap checked when the agent plans a call can be bypassed by a
challenge-time bump (TOCTOU). guardedSend wraps an opaque thunk that pays
internally and never sees the amount; the hook seat carries the real payTo +
amount at send time (matches coinbase/agentkit#1454's beforePayment context), so
the bound is evaluated against the amount that will actually be paid.
Fail-closed: flagged recipient OR over-cap amount OR screen failure all abort;
verdict wins; maxAmount is inclusive (paying exactly the cap is allowed).
Stateless by design — cumulative/rate budgets stay in the wallet/agent layer.
Structured reasons: flagged_recipient | exceeds_cap.
Test path contributed by @Sergio87Felix (#2); implemented to that contract with
the boundary corrected to inclusive-max. +7 JS, +7 Python regression cases; README
+ llms.txt document the helper.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

action providerNew action providerdocumentationImprovements or additions to documentationtypescript

Development

Successfully merging this pull request may close these issues.

Proposal: a neutral pre-payment recipient hook in the x402 action provider (inspect payTo before paying)

2 participants

@hypeprinter007-stack@cb-heimdall
, '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): optional beforePayment hook to screen the recipient before paying - #1454

Open
hypeprinter007-stack wants to merge 1 commit into
coinbase:mainfrom
hypeprinter007-stack:x402-before-payment-hook
Open

feat(x402): optional beforePayment hook to screen the recipient before paying#1454
hypeprinter007-stack wants to merge 1 commit into
coinbase:mainfrom
hypeprinter007-stack:x402-before-payment-hook

Conversation

@hypeprinter007-stack

Copy link
Copy Markdown

What & why

Implements the neutral pre-payment hook proposed in #1402.

Today the x402 action provider gates where the agent can pay (registeredServices) and how much (maxPaymentUsdc), but nothing checks who the recipient is. A compromised or mistaken agent can pay a sanctioned / drainer / phishing address that passes both existing gates. This adds the missing seat: an optional recipient check at the pre-payment chokepoint.

The change

A new optional beforePayment config hook:

constconfig: X402Config={registeredServices: ["https://api.example.com"],beforePayment: async({ payTo, url, network, asset, amount, method })=>{if(payTo&&(awaitisSanctioned(payTo)))return{abort: true,reason: "recipient flagged"};},};
  • Runs in retry_http_request_with_x402after amount + network validation and immediately before any signing/settlement.
  • Receives the payment context including the selected option's payTo.
  • Returning { abort: true, reason } refuses the payment — no signature, no settlement; the action returns a structured error.
  • Provider-neutral by design: no screening backend is imported. Any implementation plugs in (sanctions / reputation / allowlist). The README shows anchor-x402-safe-pay's allow/review/block verdict as one example.

This matches the design discussion on #1402 (three-state-friendly: the hook returns a decision + reason and is agnostic to how the verdict was reached).

Scope & compatibility

  • Backward compatible — no behavior change unless beforePayment is set.
  • Covers the recommended two-step flow (make_http_requestretry_http_request_with_x402), which exposes payTo before paying. The one-shot make_http_request_with_x402 auto-settles a 402 inside wrapFetchWithPayment and does not expose the recipient pre-settlement, so it is intentionally not gated (documented in the README). Gating it would need a larger refactor of that path.

Tests

Two cases added mirroring the existing retryWithX402 tests:

  • beforePayment returns { abort: true } → hook sees the payTo/context, wrapFetchWithPayment is never called, action returns the abort error.
  • beforePayment allows → payment proceeds normally.

Notes for reviewers

  • Files: schemas.ts (config field + X402BeforePaymentContext / X402BeforePaymentDecision types), x402ActionProvider.ts (resolve + call site), x402ActionProvider.test.ts (2 tests), README.md (docs + example).
  • I wasn't able to run the full TypeScript monorepo suite in my environment; the added tests mirror the existing retry tests exactly and the types are consistent by construction — please let CI validate.
  • Happy to add Python parity in the same PR (or a follow-up) if you'd like it here — kept this TS-first to get a read on the shape before duplicating.

Closes#1402 (or partially addresses, if you'd prefer to keep it open for the Python side).

…e paying
Implements the neutral pre-payment hook proposed in coinbase#1402. Today the x402 action
provider gates *where* the agent pays (registeredServices) and *how much*
(maxPaymentUsdc), but nothing checks *who* the recipient is — a compromised or
mistaken agent can pay a sanctioned/drainer address.
Adds an optional `beforePayment` config hook that runs in retry_http_request_with_x402
after amount + network validation and immediately before the payment is signed/
settled, receiving the payment context (incl. the selected option's `payTo`).
Returning { abort: true, reason } refuses the payment — no signature, no settlement.
Deliberately provider-neutral: no screening backend is imported. Any implementation
plugs in (sanctions / reputation / allowlist); the README shows anchor-x402-safe-pay's
allow/review/block verdict as one example.
Scope: the hook covers the recommended two-step flow (make_http_request ->
retry_http_request_with_x402), which exposes payTo before paying. The one-shot
make_http_request_with_x402 auto-settles a 402 inside wrapFetchWithPayment and does
not expose the recipient pre-settlement, so it is intentionally not gated (noted in
the README). Fully backward compatible — no behavior change unless beforePayment is set.
Tests: added two cases mirroring the existing retry tests — abort (no payment signed)
and allow (payment proceeds).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@cb-heimdall

Copy link
Copy Markdown

🟡 Heimdall Review Status

RequirementStatusMore Info
Reviews 🟡 0/1
Denominator calculation
Show calculation
1 if user is bot0
1 if user is external0
2 if repo is sensitive0
From .codeflow.yml1
Additional review requirements
Show calculation
Max0
0
From CODEOWNERS0
Global minimum0
Max 1
1
1 if commit is unverified0
Sum1

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation action provider New action provider typescript labels Aug 18, 2026
hypeprinter007-stack added a commit to hypeprinter007-stack/anchor-x402-safe-pay that referenced this pull request Aug 22, 2026
…payment hook
Adds an optional, stateless per-send amount cap that composes with the recipient
verdict at the pre-payment hook (JS composeCapWithScreen / Py compose_cap_with_screen).
Why the hook, not guardedSend: with x402 the amount is payee-set in the 402
challenge, so a cap checked when the agent plans a call can be bypassed by a
challenge-time bump (TOCTOU). guardedSend wraps an opaque thunk that pays
internally and never sees the amount; the hook seat carries the real payTo +
amount at send time (matches coinbase/agentkit#1454's beforePayment context), so
the bound is evaluated against the amount that will actually be paid.
Fail-closed: flagged recipient OR over-cap amount OR screen failure all abort;
verdict wins; maxAmount is inclusive (paying exactly the cap is allowed).
Stateless by design — cumulative/rate budgets stay in the wallet/agent layer.
Structured reasons: flagged_recipient | exceeds_cap.
Test path contributed by @Sergio87Felix (#2); implemented to that contract with
the boundary corrected to inclusive-max. +7 JS, +7 Python regression cases; README
+ llms.txt document the helper.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

action providerNew action providerdocumentationImprovements or additions to documentationtypescript

Development

Successfully merging this pull request may close these issues.

Proposal: a neutral pre-payment recipient hook in the x402 action provider (inspect payTo before paying)

2 participants

@hypeprinter007-stack@cb-heimdall
, '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): optional beforePayment hook to screen the recipient before paying - #1454

Open
hypeprinter007-stack wants to merge 1 commit into
coinbase:mainfrom
hypeprinter007-stack:x402-before-payment-hook
Open

feat(x402): optional beforePayment hook to screen the recipient before paying#1454
hypeprinter007-stack wants to merge 1 commit into
coinbase:mainfrom
hypeprinter007-stack:x402-before-payment-hook

Conversation

@hypeprinter007-stack

Copy link
Copy Markdown

What & why

Implements the neutral pre-payment hook proposed in #1402.

Today the x402 action provider gates where the agent can pay (registeredServices) and how much (maxPaymentUsdc), but nothing checks who the recipient is. A compromised or mistaken agent can pay a sanctioned / drainer / phishing address that passes both existing gates. This adds the missing seat: an optional recipient check at the pre-payment chokepoint.

The change

A new optional beforePayment config hook:

constconfig: X402Config={registeredServices: ["https://api.example.com"],beforePayment: async({ payTo, url, network, asset, amount, method })=>{if(payTo&&(awaitisSanctioned(payTo)))return{abort: true,reason: "recipient flagged"};},};
  • Runs in retry_http_request_with_x402after amount + network validation and immediately before any signing/settlement.
  • Receives the payment context including the selected option's payTo.
  • Returning { abort: true, reason } refuses the payment — no signature, no settlement; the action returns a structured error.
  • Provider-neutral by design: no screening backend is imported. Any implementation plugs in (sanctions / reputation / allowlist). The README shows anchor-x402-safe-pay's allow/review/block verdict as one example.

This matches the design discussion on #1402 (three-state-friendly: the hook returns a decision + reason and is agnostic to how the verdict was reached).

Scope & compatibility

  • Backward compatible — no behavior change unless beforePayment is set.
  • Covers the recommended two-step flow (make_http_requestretry_http_request_with_x402), which exposes payTo before paying. The one-shot make_http_request_with_x402 auto-settles a 402 inside wrapFetchWithPayment and does not expose the recipient pre-settlement, so it is intentionally not gated (documented in the README). Gating it would need a larger refactor of that path.

Tests

Two cases added mirroring the existing retryWithX402 tests:

  • beforePayment returns { abort: true } → hook sees the payTo/context, wrapFetchWithPayment is never called, action returns the abort error.
  • beforePayment allows → payment proceeds normally.

Notes for reviewers

  • Files: schemas.ts (config field + X402BeforePaymentContext / X402BeforePaymentDecision types), x402ActionProvider.ts (resolve + call site), x402ActionProvider.test.ts (2 tests), README.md (docs + example).
  • I wasn't able to run the full TypeScript monorepo suite in my environment; the added tests mirror the existing retry tests exactly and the types are consistent by construction — please let CI validate.
  • Happy to add Python parity in the same PR (or a follow-up) if you'd like it here — kept this TS-first to get a read on the shape before duplicating.

Closes#1402 (or partially addresses, if you'd prefer to keep it open for the Python side).

…e paying
Implements the neutral pre-payment hook proposed in coinbase#1402. Today the x402 action
provider gates *where* the agent pays (registeredServices) and *how much*
(maxPaymentUsdc), but nothing checks *who* the recipient is — a compromised or
mistaken agent can pay a sanctioned/drainer address.
Adds an optional `beforePayment` config hook that runs in retry_http_request_with_x402
after amount + network validation and immediately before the payment is signed/
settled, receiving the payment context (incl. the selected option's `payTo`).
Returning { abort: true, reason } refuses the payment — no signature, no settlement.
Deliberately provider-neutral: no screening backend is imported. Any implementation
plugs in (sanctions / reputation / allowlist); the README shows anchor-x402-safe-pay's
allow/review/block verdict as one example.
Scope: the hook covers the recommended two-step flow (make_http_request ->
retry_http_request_with_x402), which exposes payTo before paying. The one-shot
make_http_request_with_x402 auto-settles a 402 inside wrapFetchWithPayment and does
not expose the recipient pre-settlement, so it is intentionally not gated (noted in
the README). Fully backward compatible — no behavior change unless beforePayment is set.
Tests: added two cases mirroring the existing retry tests — abort (no payment signed)
and allow (payment proceeds).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@cb-heimdall

Copy link
Copy Markdown

🟡 Heimdall Review Status

RequirementStatusMore Info
Reviews 🟡 0/1
Denominator calculation
Show calculation
1 if user is bot0
1 if user is external0
2 if repo is sensitive0
From .codeflow.yml1
Additional review requirements
Show calculation
Max0
0
From CODEOWNERS0
Global minimum0
Max 1
1
1 if commit is unverified0
Sum1

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation action provider New action provider typescript labels Aug 18, 2026
hypeprinter007-stack added a commit to hypeprinter007-stack/anchor-x402-safe-pay that referenced this pull request Aug 22, 2026
…payment hook
Adds an optional, stateless per-send amount cap that composes with the recipient
verdict at the pre-payment hook (JS composeCapWithScreen / Py compose_cap_with_screen).
Why the hook, not guardedSend: with x402 the amount is payee-set in the 402
challenge, so a cap checked when the agent plans a call can be bypassed by a
challenge-time bump (TOCTOU). guardedSend wraps an opaque thunk that pays
internally and never sees the amount; the hook seat carries the real payTo +
amount at send time (matches coinbase/agentkit#1454's beforePayment context), so
the bound is evaluated against the amount that will actually be paid.
Fail-closed: flagged recipient OR over-cap amount OR screen failure all abort;
verdict wins; maxAmount is inclusive (paying exactly the cap is allowed).
Stateless by design — cumulative/rate budgets stay in the wallet/agent layer.
Structured reasons: flagged_recipient | exceeds_cap.
Test path contributed by @Sergio87Felix (#2); implemented to that contract with
the boundary corrected to inclusive-max. +7 JS, +7 Python regression cases; README
+ llms.txt document the helper.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

action providerNew action providerdocumentationImprovements or additions to documentationtypescript

Development

Successfully merging this pull request may close these issues.

Proposal: a neutral pre-payment recipient hook in the x402 action provider (inspect payTo before paying)

2 participants

@hypeprinter007-stack@cb-heimdall
, '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): optional beforePayment hook to screen the recipient before paying - #1454

Open
hypeprinter007-stack wants to merge 1 commit into
coinbase:mainfrom
hypeprinter007-stack:x402-before-payment-hook
Open

feat(x402): optional beforePayment hook to screen the recipient before paying#1454
hypeprinter007-stack wants to merge 1 commit into
coinbase:mainfrom
hypeprinter007-stack:x402-before-payment-hook

Conversation

@hypeprinter007-stack

Copy link
Copy Markdown

What & why

Implements the neutral pre-payment hook proposed in #1402.

Today the x402 action provider gates where the agent can pay (registeredServices) and how much (maxPaymentUsdc), but nothing checks who the recipient is. A compromised or mistaken agent can pay a sanctioned / drainer / phishing address that passes both existing gates. This adds the missing seat: an optional recipient check at the pre-payment chokepoint.

The change

A new optional beforePayment config hook:

constconfig: X402Config={registeredServices: ["https://api.example.com"],beforePayment: async({ payTo, url, network, asset, amount, method })=>{if(payTo&&(awaitisSanctioned(payTo)))return{abort: true,reason: "recipient flagged"};},};
  • Runs in retry_http_request_with_x402after amount + network validation and immediately before any signing/settlement.
  • Receives the payment context including the selected option's payTo.
  • Returning { abort: true, reason } refuses the payment — no signature, no settlement; the action returns a structured error.
  • Provider-neutral by design: no screening backend is imported. Any implementation plugs in (sanctions / reputation / allowlist). The README shows anchor-x402-safe-pay's allow/review/block verdict as one example.

This matches the design discussion on #1402 (three-state-friendly: the hook returns a decision + reason and is agnostic to how the verdict was reached).

Scope & compatibility

  • Backward compatible — no behavior change unless beforePayment is set.
  • Covers the recommended two-step flow (make_http_requestretry_http_request_with_x402), which exposes payTo before paying. The one-shot make_http_request_with_x402 auto-settles a 402 inside wrapFetchWithPayment and does not expose the recipient pre-settlement, so it is intentionally not gated (documented in the README). Gating it would need a larger refactor of that path.

Tests

Two cases added mirroring the existing retryWithX402 tests:

  • beforePayment returns { abort: true } → hook sees the payTo/context, wrapFetchWithPayment is never called, action returns the abort error.
  • beforePayment allows → payment proceeds normally.

Notes for reviewers

  • Files: schemas.ts (config field + X402BeforePaymentContext / X402BeforePaymentDecision types), x402ActionProvider.ts (resolve + call site), x402ActionProvider.test.ts (2 tests), README.md (docs + example).
  • I wasn't able to run the full TypeScript monorepo suite in my environment; the added tests mirror the existing retry tests exactly and the types are consistent by construction — please let CI validate.
  • Happy to add Python parity in the same PR (or a follow-up) if you'd like it here — kept this TS-first to get a read on the shape before duplicating.

Closes#1402 (or partially addresses, if you'd prefer to keep it open for the Python side).

…e paying
Implements the neutral pre-payment hook proposed in coinbase#1402. Today the x402 action
provider gates *where* the agent pays (registeredServices) and *how much*
(maxPaymentUsdc), but nothing checks *who* the recipient is — a compromised or
mistaken agent can pay a sanctioned/drainer address.
Adds an optional `beforePayment` config hook that runs in retry_http_request_with_x402
after amount + network validation and immediately before the payment is signed/
settled, receiving the payment context (incl. the selected option's `payTo`).
Returning { abort: true, reason } refuses the payment — no signature, no settlement.
Deliberately provider-neutral: no screening backend is imported. Any implementation
plugs in (sanctions / reputation / allowlist); the README shows anchor-x402-safe-pay's
allow/review/block verdict as one example.
Scope: the hook covers the recommended two-step flow (make_http_request ->
retry_http_request_with_x402), which exposes payTo before paying. The one-shot
make_http_request_with_x402 auto-settles a 402 inside wrapFetchWithPayment and does
not expose the recipient pre-settlement, so it is intentionally not gated (noted in
the README). Fully backward compatible — no behavior change unless beforePayment is set.
Tests: added two cases mirroring the existing retry tests — abort (no payment signed)
and allow (payment proceeds).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@cb-heimdall

Copy link
Copy Markdown

🟡 Heimdall Review Status

RequirementStatusMore Info
Reviews 🟡 0/1
Denominator calculation
Show calculation
1 if user is bot0
1 if user is external0
2 if repo is sensitive0
From .codeflow.yml1
Additional review requirements
Show calculation
Max0
0
From CODEOWNERS0
Global minimum0
Max 1
1
1 if commit is unverified0
Sum1

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation action provider New action provider typescript labels Aug 18, 2026
hypeprinter007-stack added a commit to hypeprinter007-stack/anchor-x402-safe-pay that referenced this pull request Aug 22, 2026
…payment hook
Adds an optional, stateless per-send amount cap that composes with the recipient
verdict at the pre-payment hook (JS composeCapWithScreen / Py compose_cap_with_screen).
Why the hook, not guardedSend: with x402 the amount is payee-set in the 402
challenge, so a cap checked when the agent plans a call can be bypassed by a
challenge-time bump (TOCTOU). guardedSend wraps an opaque thunk that pays
internally and never sees the amount; the hook seat carries the real payTo +
amount at send time (matches coinbase/agentkit#1454's beforePayment context), so
the bound is evaluated against the amount that will actually be paid.
Fail-closed: flagged recipient OR over-cap amount OR screen failure all abort;
verdict wins; maxAmount is inclusive (paying exactly the cap is allowed).
Stateless by design — cumulative/rate budgets stay in the wallet/agent layer.
Structured reasons: flagged_recipient | exceeds_cap.
Test path contributed by @Sergio87Felix (#2); implemented to that contract with
the boundary corrected to inclusive-max. +7 JS, +7 Python regression cases; README
+ llms.txt document the helper.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

action providerNew action providerdocumentationImprovements or additions to documentationtypescript

Development

Successfully merging this pull request may close these issues.

Proposal: a neutral pre-payment recipient hook in the x402 action provider (inspect payTo before paying)

2 participants

@hypeprinter007-stack@cb-heimdall
, '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): optional beforePayment hook to screen the recipient before paying - #1454

Open
hypeprinter007-stack wants to merge 1 commit into
coinbase:mainfrom
hypeprinter007-stack:x402-before-payment-hook
Open

feat(x402): optional beforePayment hook to screen the recipient before paying#1454
hypeprinter007-stack wants to merge 1 commit into
coinbase:mainfrom
hypeprinter007-stack:x402-before-payment-hook

Conversation

@hypeprinter007-stack

Copy link
Copy Markdown

What & why

Implements the neutral pre-payment hook proposed in #1402.

Today the x402 action provider gates where the agent can pay (registeredServices) and how much (maxPaymentUsdc), but nothing checks who the recipient is. A compromised or mistaken agent can pay a sanctioned / drainer / phishing address that passes both existing gates. This adds the missing seat: an optional recipient check at the pre-payment chokepoint.

The change

A new optional beforePayment config hook:

constconfig: X402Config={registeredServices: ["https://api.example.com"],beforePayment: async({ payTo, url, network, asset, amount, method })=>{if(payTo&&(awaitisSanctioned(payTo)))return{abort: true,reason: "recipient flagged"};},};
  • Runs in retry_http_request_with_x402after amount + network validation and immediately before any signing/settlement.
  • Receives the payment context including the selected option's payTo.
  • Returning { abort: true, reason } refuses the payment — no signature, no settlement; the action returns a structured error.
  • Provider-neutral by design: no screening backend is imported. Any implementation plugs in (sanctions / reputation / allowlist). The README shows anchor-x402-safe-pay's allow/review/block verdict as one example.

This matches the design discussion on #1402 (three-state-friendly: the hook returns a decision + reason and is agnostic to how the verdict was reached).

Scope & compatibility

  • Backward compatible — no behavior change unless beforePayment is set.
  • Covers the recommended two-step flow (make_http_requestretry_http_request_with_x402), which exposes payTo before paying. The one-shot make_http_request_with_x402 auto-settles a 402 inside wrapFetchWithPayment and does not expose the recipient pre-settlement, so it is intentionally not gated (documented in the README). Gating it would need a larger refactor of that path.

Tests

Two cases added mirroring the existing retryWithX402 tests:

  • beforePayment returns { abort: true } → hook sees the payTo/context, wrapFetchWithPayment is never called, action returns the abort error.
  • beforePayment allows → payment proceeds normally.

Notes for reviewers

  • Files: schemas.ts (config field + X402BeforePaymentContext / X402BeforePaymentDecision types), x402ActionProvider.ts (resolve + call site), x402ActionProvider.test.ts (2 tests), README.md (docs + example).
  • I wasn't able to run the full TypeScript monorepo suite in my environment; the added tests mirror the existing retry tests exactly and the types are consistent by construction — please let CI validate.
  • Happy to add Python parity in the same PR (or a follow-up) if you'd like it here — kept this TS-first to get a read on the shape before duplicating.

Closes#1402 (or partially addresses, if you'd prefer to keep it open for the Python side).

…e paying
Implements the neutral pre-payment hook proposed in coinbase#1402. Today the x402 action
provider gates *where* the agent pays (registeredServices) and *how much*
(maxPaymentUsdc), but nothing checks *who* the recipient is — a compromised or
mistaken agent can pay a sanctioned/drainer address.
Adds an optional `beforePayment` config hook that runs in retry_http_request_with_x402
after amount + network validation and immediately before the payment is signed/
settled, receiving the payment context (incl. the selected option's `payTo`).
Returning { abort: true, reason } refuses the payment — no signature, no settlement.
Deliberately provider-neutral: no screening backend is imported. Any implementation
plugs in (sanctions / reputation / allowlist); the README shows anchor-x402-safe-pay's
allow/review/block verdict as one example.
Scope: the hook covers the recommended two-step flow (make_http_request ->
retry_http_request_with_x402), which exposes payTo before paying. The one-shot
make_http_request_with_x402 auto-settles a 402 inside wrapFetchWithPayment and does
not expose the recipient pre-settlement, so it is intentionally not gated (noted in
the README). Fully backward compatible — no behavior change unless beforePayment is set.
Tests: added two cases mirroring the existing retry tests — abort (no payment signed)
and allow (payment proceeds).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@cb-heimdall

Copy link
Copy Markdown

🟡 Heimdall Review Status

RequirementStatusMore Info
Reviews 🟡 0/1
Denominator calculation
Show calculation
1 if user is bot0
1 if user is external0
2 if repo is sensitive0
From .codeflow.yml1
Additional review requirements
Show calculation
Max0
0
From CODEOWNERS0
Global minimum0
Max 1
1
1 if commit is unverified0
Sum1

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation action provider New action provider typescript labels Aug 18, 2026
hypeprinter007-stack added a commit to hypeprinter007-stack/anchor-x402-safe-pay that referenced this pull request Aug 22, 2026
…payment hook
Adds an optional, stateless per-send amount cap that composes with the recipient
verdict at the pre-payment hook (JS composeCapWithScreen / Py compose_cap_with_screen).
Why the hook, not guardedSend: with x402 the amount is payee-set in the 402
challenge, so a cap checked when the agent plans a call can be bypassed by a
challenge-time bump (TOCTOU). guardedSend wraps an opaque thunk that pays
internally and never sees the amount; the hook seat carries the real payTo +
amount at send time (matches coinbase/agentkit#1454's beforePayment context), so
the bound is evaluated against the amount that will actually be paid.
Fail-closed: flagged recipient OR over-cap amount OR screen failure all abort;
verdict wins; maxAmount is inclusive (paying exactly the cap is allowed).
Stateless by design — cumulative/rate budgets stay in the wallet/agent layer.
Structured reasons: flagged_recipient | exceeds_cap.
Test path contributed by @Sergio87Felix (#2); implemented to that contract with
the boundary corrected to inclusive-max. +7 JS, +7 Python regression cases; README
+ llms.txt document the helper.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

action providerNew action providerdocumentationImprovements or additions to documentationtypescript

Development

Successfully merging this pull request may close these issues.

Proposal: a neutral pre-payment recipient hook in the x402 action provider (inspect payTo before paying)

2 participants

@hypeprinter007-stack@cb-heimdall
, '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): optional beforePayment hook to screen the recipient before paying - #1454

Open
hypeprinter007-stack wants to merge 1 commit into
coinbase:mainfrom
hypeprinter007-stack:x402-before-payment-hook
Open

feat(x402): optional beforePayment hook to screen the recipient before paying#1454
hypeprinter007-stack wants to merge 1 commit into
coinbase:mainfrom
hypeprinter007-stack:x402-before-payment-hook

Conversation

@hypeprinter007-stack

Copy link
Copy Markdown

What & why

Implements the neutral pre-payment hook proposed in #1402.

Today the x402 action provider gates where the agent can pay (registeredServices) and how much (maxPaymentUsdc), but nothing checks who the recipient is. A compromised or mistaken agent can pay a sanctioned / drainer / phishing address that passes both existing gates. This adds the missing seat: an optional recipient check at the pre-payment chokepoint.

The change

A new optional beforePayment config hook:

constconfig: X402Config={registeredServices: ["https://api.example.com"],beforePayment: async({ payTo, url, network, asset, amount, method })=>{if(payTo&&(awaitisSanctioned(payTo)))return{abort: true,reason: "recipient flagged"};},};
  • Runs in retry_http_request_with_x402after amount + network validation and immediately before any signing/settlement.
  • Receives the payment context including the selected option's payTo.
  • Returning { abort: true, reason } refuses the payment — no signature, no settlement; the action returns a structured error.
  • Provider-neutral by design: no screening backend is imported. Any implementation plugs in (sanctions / reputation / allowlist). The README shows anchor-x402-safe-pay's allow/review/block verdict as one example.

This matches the design discussion on #1402 (three-state-friendly: the hook returns a decision + reason and is agnostic to how the verdict was reached).

Scope & compatibility

  • Backward compatible — no behavior change unless beforePayment is set.
  • Covers the recommended two-step flow (make_http_requestretry_http_request_with_x402), which exposes payTo before paying. The one-shot make_http_request_with_x402 auto-settles a 402 inside wrapFetchWithPayment and does not expose the recipient pre-settlement, so it is intentionally not gated (documented in the README). Gating it would need a larger refactor of that path.

Tests

Two cases added mirroring the existing retryWithX402 tests:

  • beforePayment returns { abort: true } → hook sees the payTo/context, wrapFetchWithPayment is never called, action returns the abort error.
  • beforePayment allows → payment proceeds normally.

Notes for reviewers

  • Files: schemas.ts (config field + X402BeforePaymentContext / X402BeforePaymentDecision types), x402ActionProvider.ts (resolve + call site), x402ActionProvider.test.ts (2 tests), README.md (docs + example).
  • I wasn't able to run the full TypeScript monorepo suite in my environment; the added tests mirror the existing retry tests exactly and the types are consistent by construction — please let CI validate.
  • Happy to add Python parity in the same PR (or a follow-up) if you'd like it here — kept this TS-first to get a read on the shape before duplicating.

Closes#1402 (or partially addresses, if you'd prefer to keep it open for the Python side).

…e paying
Implements the neutral pre-payment hook proposed in coinbase#1402. Today the x402 action
provider gates *where* the agent pays (registeredServices) and *how much*
(maxPaymentUsdc), but nothing checks *who* the recipient is — a compromised or
mistaken agent can pay a sanctioned/drainer address.
Adds an optional `beforePayment` config hook that runs in retry_http_request_with_x402
after amount + network validation and immediately before the payment is signed/
settled, receiving the payment context (incl. the selected option's `payTo`).
Returning { abort: true, reason } refuses the payment — no signature, no settlement.
Deliberately provider-neutral: no screening backend is imported. Any implementation
plugs in (sanctions / reputation / allowlist); the README shows anchor-x402-safe-pay's
allow/review/block verdict as one example.
Scope: the hook covers the recommended two-step flow (make_http_request ->
retry_http_request_with_x402), which exposes payTo before paying. The one-shot
make_http_request_with_x402 auto-settles a 402 inside wrapFetchWithPayment and does
not expose the recipient pre-settlement, so it is intentionally not gated (noted in
the README). Fully backward compatible — no behavior change unless beforePayment is set.
Tests: added two cases mirroring the existing retry tests — abort (no payment signed)
and allow (payment proceeds).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@cb-heimdall

Copy link
Copy Markdown

🟡 Heimdall Review Status

RequirementStatusMore Info
Reviews 🟡 0/1
Denominator calculation
Show calculation
1 if user is bot0
1 if user is external0
2 if repo is sensitive0
From .codeflow.yml1
Additional review requirements
Show calculation
Max0
0
From CODEOWNERS0
Global minimum0
Max 1
1
1 if commit is unverified0
Sum1

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation action provider New action provider typescript labels Aug 18, 2026
hypeprinter007-stack added a commit to hypeprinter007-stack/anchor-x402-safe-pay that referenced this pull request Aug 22, 2026
…payment hook
Adds an optional, stateless per-send amount cap that composes with the recipient
verdict at the pre-payment hook (JS composeCapWithScreen / Py compose_cap_with_screen).
Why the hook, not guardedSend: with x402 the amount is payee-set in the 402
challenge, so a cap checked when the agent plans a call can be bypassed by a
challenge-time bump (TOCTOU). guardedSend wraps an opaque thunk that pays
internally and never sees the amount; the hook seat carries the real payTo +
amount at send time (matches coinbase/agentkit#1454's beforePayment context), so
the bound is evaluated against the amount that will actually be paid.
Fail-closed: flagged recipient OR over-cap amount OR screen failure all abort;
verdict wins; maxAmount is inclusive (paying exactly the cap is allowed).
Stateless by design — cumulative/rate budgets stay in the wallet/agent layer.
Structured reasons: flagged_recipient | exceeds_cap.
Test path contributed by @Sergio87Felix (#2); implemented to that contract with
the boundary corrected to inclusive-max. +7 JS, +7 Python regression cases; README
+ llms.txt document the helper.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

action providerNew action providerdocumentationImprovements or additions to documentationtypescript

Development

Successfully merging this pull request may close these issues.

Proposal: a neutral pre-payment recipient hook in the x402 action provider (inspect payTo before paying)

2 participants

@hypeprinter007-stack@cb-heimdall