jcp-x402: the x402 agentic-payment wire, dual-ceiling gated + settlement coupling #24

Merged
dcharlot merged 3 commits from jcp/x402-bridge into main 2026-07-18 03:29:42 -04:00
Owner

x402 is the open HTTP-native payment wire: a server answers with 402 Payment Required + a PaymentRequirements (a stablecoin charge), the agent retries with a signed X-PAYMENT header, the server settles on chain. It standardizes how an agent pays inline — but the authorization is denominated in a stablecoin and bounds nothing about the compute the paid-for work consumes.

That is precisely the shape SPEC §4.8 was built for. The stablecoin amount is a money ceiling; the compute is a joule ceiling; the two must not be reconciled by a fabricated USDC→joule rate. So the payment supplies one ceiling, the operator supplies the other, and both are enforced independently.

What's here

  • jcp-bridge — x402_resource(network, payTo) content-addresses a merchant as a grant audience an attacker cannot spoof by name; x402_pay_frame + call_x402_priced mirror the a2a priced path. Reuses FrameKind::AgentTask, so no schema change and no conformance-vector churn.
  • jcp-x402 — ingest_requirements parses the real wire shape (tolerating the accepts[] wrapper, and maxAmountRequired as the wire's decimal string or an integer → integer minor units, never a float). X402Profile mints one dual-ceiling grant per merchant and gates each charge, sealing allow and deny alike.

Denials, proven by test and by the runnable example

case result
past the money cap funds-exceeded — while joules still had room
charge in another asset currency-mismatch — refused, never converted
wrong merchant wrong-resource

The settlement coupling

seal_settlement seals a receipt whose payload_hash commits to the settled transaction hash + the measured joules. coupling_id() is the public check: a third party recomputes the content id from the settlement record and compares. Changing either the tx or the joule figure yields a different id, so a receipt cannot be re-pointed at another settlement.

This is the artifact neither side produces alone — the payment rail knows the transaction but not the energy; an energy meter knows the joules but not the transaction.

It required jcp/2 (#23) to be real. Under jcp/1 the receipt didn't commit its payload, and this crate deliberately shipped without the claim rather than assert a coupling the format couldn't carry.

42 suites green, clippy clean, all conformance checks pass.

x402 is the open HTTP-native payment wire: a server answers with `402 Payment Required` + a **PaymentRequirements** (a stablecoin charge), the agent retries with a signed `X-PAYMENT` header, the server settles on chain. It standardizes *how an agent pays inline* — but the authorization is denominated in a stablecoin and bounds **nothing** about the compute the paid-for work consumes. That is precisely the shape **SPEC §4.8** was built for. The stablecoin amount is a **money** ceiling; the compute is a **joule** ceiling; the two must not be reconciled by a fabricated USDC→joule rate. So the payment supplies one ceiling, the operator supplies the other, and both are enforced independently. ### What's here - **`jcp-bridge`** — `x402_resource(network, payTo)` content-addresses a merchant as a grant audience an attacker cannot spoof by name; `x402_pay_frame` + `call_x402_priced` mirror the a2a priced path. Reuses `FrameKind::AgentTask`, so **no schema change** and no conformance-vector churn. - **`jcp-x402`** — `ingest_requirements` parses the real wire shape (tolerating the `accepts[]` wrapper, and `maxAmountRequired` as the wire's decimal **string** or an integer → integer minor units, never a float). `X402Profile` mints one dual-ceiling grant per merchant and gates each charge, sealing allow and deny alike. ### Denials, proven by test and by the runnable example | case | result | |---|---| | past the money cap | `funds-exceeded` — *while joules still had room* | | charge in another asset | `currency-mismatch` — refused, **never converted** | | wrong merchant | `wrong-resource` | ### The settlement coupling `seal_settlement` seals a receipt whose `payload_hash` commits to the settled **transaction hash + the measured joules**. `coupling_id()` is the public check: a third party recomputes the content id from the settlement record and compares. Changing either the tx or the joule figure yields a different id, so a receipt cannot be re-pointed at another settlement. This is the artifact neither side produces alone — the payment rail knows the transaction but not the energy; an energy meter knows the joules but not the transaction. It required `jcp/2` (#23) to be real. Under `jcp/1` the receipt didn't commit its payload, and this crate **deliberately shipped without the claim** rather than assert a coupling the format couldn't carry. 42 suites green, clippy clean, all conformance checks pass.
Ingest an x402 PaymentRequirements; its stablecoin charge becomes the grant's
SPEC §4.8 money ceiling and the operator supplies the joule ceiling. Committing
early — tests green (6) — before adding the example.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
x402 is the open HTTP-native payment wire: a server answers with `402 Payment
Required` + a PaymentRequirements (a stablecoin charge), the agent retries with
a signed X-PAYMENT header, the server settles on chain. It standardizes how an
agent pays inline — but the authorization is denominated in a stablecoin and
bounds nothing about the compute the paid-for work consumes.

That is precisely the shape SPEC §4.8 was built for. The stablecoin amount is a
MONEY ceiling; the compute is a JOULE ceiling; the two must not be reconciled by
a fabricated USDC→joule rate. So the payment supplies one ceiling, the operator
supplies the other, and both are enforced independently.

- jcp-bridge: `x402_resource(network, payTo)` content-addresses a merchant as a
  grant audience an attacker cannot spoof by name; `x402_pay_frame` and
  `call_x402_priced` mirror the a2a priced path. Reuses FrameKind::AgentTask —
  no schema change, so no receipt-format or conformance-vector churn.
- jcp-x402: `ingest_requirements` parses PaymentRequirements (tolerating the
  `accepts[]` wrapper and `maxAmountRequired` as the wire's decimal STRING or an
  integer — parsed to integer minor units, never a float). `price()` and
  `funds_ceiling()` read the charge as money. `X402Profile` mints one
  dual-ceiling grant per merchant and gates each charge, sealing allow and deny
  alike.

Denials proven by test and by the runnable example: past the money cap →
`funds-exceeded` while joules still had room; a charge in another asset →
`currency-mismatch` (refused, never converted); a charge to a merchant the grant
is not scoped to → `wrong-resource`.

DELIBERATELY NOT CLAIMED: a receipt coupling the settled on-chain transaction
hash to the attested joules. The JCR-1 wire commits the joules (`cost_uj`) and
the merchant audience but NOT the frame's `payload_hash`, so a tx hash cannot
yet be sealed into it. Completing that coupling is a JCR-1 receipt-format
decision (adding `payload_hash` to the signed core — which would also let every
tool-call receipt bind its arguments) and belongs in its own change, not smuggled
into a profile crate. The module doc states this limitation plainly.

42 workspace suites green, clippy clean.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
The crate deliberately stopped short of this: under `jcp/1` the receipt did not
commit `payload_hash`, so a transaction hash could not be sealed into it and
claiming the coupling would have been false. The `jcp/2` profile (SPEC §8, now on
main) commits it, so the coupling is real and this delivers it.

`X402Settlement` carries the x402 `X-PAYMENT-RESPONSE` outcome (transaction,
network, payer). `seal_settlement` seals a receipt whose `payload_hash` commits
to the canonical coupling record — settled transaction + resource + MEASURED
joules. `coupling_id()` is the public check: a third party recomputes the content
id from the settlement record and compares it to the receipt's `payload_hash`, so
the binding is cryptographic rather than asserted.

This is the artifact neither side produces alone. The payment rail knows the
transaction but not the energy; an energy meter knows the joules but not the
transaction.

Tested: the receipt binds THIS transaction to THIS energy figure, and changing
either the tx hash or the joule figure yields a different coupling id — so a
settlement receipt cannot be re-pointed at another settlement. It debits nothing
(authorize already charged both ceilings against the estimate), verified by
asserting spend is unchanged after sealing.

42 workspace suites green, clippy clean, all conformance checks pass, example runs.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Transaction-Science/open-standards!24
No description provided.