Skip to content

fix(x402): enforce cap on selected payment requirement - #2272

Merged
mattzcarey merged 6 commits into
mainfrom
fix/x402-payment-modernization
Sep 28, 2026
Merged

mattzcarey merged 6 commits into
mainfrom
fix/x402-payment-modernization

Conversation

@mattzcarey

@mattzcarey mattzcarey commented Sep 14, 2026 •

Copy link
Copy Markdown
Member

withX402Client checked maxPaymentValue against the first advertised payment requirement, but x402 picks the requirement it signs afterwards, after applying scheme support and network preference. A server could advertise a cheap first offer on a network the client doesn't prefer and a second, expensive one it does. The client would then sign the expensive one without the cap ever applying to it.

The cap now runs in x402's pre-signing hook, on the requirement that will actually be signed:

paymentClient.onBeforePaymentCreation(async ({ selectedRequirements }) => {
  const { scheme, amount } = selectedRequirements;
  if (scheme !== "exact") throw new PaymentPassthroughError();
  let value: bigint;
  try {
    value = BigInt(amount);
  } catch {
    throw new PaymentPassthroughError(); // malformed amount
  }
  if (value < 0n) throw new PaymentPassthroughError();
  if (value > maxPaymentValue) {
    throw new PaymentCapError(`Payment exceeds client cap: ${value} > ${maxPaymentValue}`);
  }
});
  • x402 runs beforePaymentCreation hooks outside its own try, from 2.0.0 onwards, so the typed errors reach withX402Client unchanged across the whole @x402/core ^2.0.0 peer range. Returning { abort: true } would have been rewrapped as a plain Error.
  • An over-cap selection returns Payment exceeds client cap: … without signing or retrying. A malformed, negative or non-exact selection returns the server's original 402 result, as before.
  • When nothing offered can be signed (no exact offer, or only ones on networks the EVM client can't sign), the original 402 comes back with the server's payment options. x402 selects before it runs any hook, so a request that fails without reaching the cap hook is exactly that case.
  • The advertised requirements are snapshotted once, and the confirmation callback gets its own deep copies, so nothing outside can change what is cap-checked or signed.
  • No API or dependency changes.

@changeset-bot

changeset-bot Bot commented Sep 14, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c8fd906

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
agents Patch
@cloudflare/agent-think Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@agent-think

agent-think Bot commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

✅ agents import sizes: no significant changes (e8379676 → c8fd906d, workflow run)

@devin-ai-integration devin-ai-integration Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Note

This report is out of date. Scroll down for Devin Review's latest report on this PR.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Devin Review

@mattzcarey
mattzcarey force-pushed the fix/x402-payment-modernization branch from e208f73 to 838a5f0 Compare September 14, 2026 15:43
@mattzcarey mattzcarey changed the title fix(x402): enforce payment caps and add composable MCP APIs fix(x402): enforce cap on selected payment requirement Sep 14, 2026
@pkg-pr-new

pkg-pr-new Bot commented Sep 14, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

agents

npm i https://pkg.pr.new/agents@2272

@cloudflare/ai-chat

npm i https://pkg.pr.new/@cloudflare/ai-chat@2272

@cloudflare/codemode

npm i https://pkg.pr.new/@cloudflare/codemode@2272

hono-agents

npm i https://pkg.pr.new/hono-agents@2272

@cloudflare/shell

npm i https://pkg.pr.new/@cloudflare/shell@2272

@cloudflare/think

npm i https://pkg.pr.new/@cloudflare/think@2272

@cloudflare/voice

npm i https://pkg.pr.new/@cloudflare/voice@2272

@cloudflare/worker-bundler

npm i https://pkg.pr.new/@cloudflare/worker-bundler@2272

commit: c8fd906

devin-ai-integration[bot]

This comment was marked as resolved.

Non-exact schemes and malformed amounts return the server's original 402
result again, amounts parse with BigInt as before, and the structuredClone
snapshot and its ordering-dependent test are removed.
A retained reference could change a requirement between the cap check
and signing. Also pin the before-payment hook in the mocked suite.
…ents

A shallow copy still shared nested fields such as extra, which feeds the
signed EIP-712 domain. structuredClone makes the callback's copies fully
independent of what is cap-checked and signed.
@mattzcarey
mattzcarey force-pushed the fix/x402-payment-modernization branch from 3cf2319 to 1e63381 Compare September 28, 2026 14:26
devin-ai-integration[bot]

This comment was marked as resolved.

A non-exact offer followed by an exact offer on a network the EVM client
cannot sign used to reach payload creation and come back as a generic
'Failed to create payment payload', dropping the server's payment
options. x402 selects a requirement before it runs any hook, so a
payment request that fails without reaching the cap hook offered nothing
this client can sign: return the original 402 for it, as the old
first-requirement check did. This replaces the scheme === 'exact'
pre-check with x402's own selection.

Also snapshot the advertised requirements once, so what is cap-checked
and signed is a copy that nothing else aliases.
@mattzcarey
mattzcarey merged commit 5dbf6c2 into main Sep 28, 2026
18 checks passed
@mattzcarey
mattzcarey deleted the fix/x402-payment-modernization branch September 28, 2026 15:02
@github-actions github-actions Bot mentioned this pull request Oct 2, 2026
Sign up for free to 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