feat(x402): Agent/x402 model - #194
Conversation
Adds org.accordproject.protocol.x402@0.2.0 — a Concerto model of the x402 internet-native payments protocol (Coinbase / x402 Foundation), spec v2: PaymentRequired / PaymentRequirements, PaymentPayload with the exact/EVM scheme, facilitator verify + settle responses, the HTTP header transport binding, and the discovery (Bazaar) types. Self-contained: no imports. Declares Concerto ^4.0.0 so its ExtensionMap map type builds with map support enabled by default (no build.js change), consistent with money-reference@1.0.0. First of a family of agentic-commerce protocol models (a2a, ap2, ucp, acp, verifiable-intent, lcp, aoep) to follow as separate PRs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Niall Roche <niall.roche@gmail.com>
…on binding The x402 wire fields keep their native shape (atomic-unit `amount` + `asset`, no scale). Add an optional canonicalAmount of org.accordproject.money@1.0.0.PreciseAmount to AccordObligationExtensionInfo — the typed Accord obligation binding that rides the x402 extension mechanism — so the owed amount can be expressed exactly at the agreement layer, where the asset's decimals are known. This is the correct seam for the money type: the raw x402 payload cannot be losslessly converted to a PreciseAmount without resolving token decimals, but the obligation that sources the payment already has them. Imports money@1.0.0 by URL (published); build resolves it via updateExternalModels. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Niall Roche <niall.roche@gmail.com>
mttrbrts
left a comment
There was a problem hiding this comment.
Review of x402@0.2.0 model — inline comments below.
| // CAIP-2 namespace/reference grammar and canonical unsigned decimal strings. | ||
| scalar Caip2Network extends String regex=/^[-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,32}$/ | ||
| scalar UnsignedIntegerString extends String regex=/^(0|[1-9][0-9]*)$/ | ||
|
|
There was a problem hiding this comment.
Consider defining a JSON scalar alongside these for use on all opaque JSON string fields in this file:
scalar JSON extends String
This makes intent explicit at the schema level (visible in codegen output and Concerto tooling) without changing wire behaviour. It would also be a good candidate for upstreaming into org.accordproject.concerto.scalar for reuse across the model library.
| // include at least the info received; it may append but not delete/overwrite. | ||
| concept X402Extension { | ||
| o String info // extension-specific data, JSON | ||
| o String schema // JSON Schema for `info`, JSON |
There was a problem hiding this comment.
Once a JSON scalar is defined, these two fields and their counterparts in extra, metadata, and signers should all use it instead of bare String. The comments document intent today, but the type signature should too.
| o String payTo // recipient address or role constant | ||
| o Integer maxTimeoutSeconds range=[0,] | ||
| o String extra optional // scheme-specific JSON (e.g. {"name":"USDC","version":"2"}) | ||
| } |
There was a problem hiding this comment.
Candidate for the JSON scalar. Also a gap worth noting: ExactEvmPayload and ExactSvmPayload are typed concepts, but there is no corresponding ExactEvmExtra (e.g. {"name":"USDC","version":"2"}) or ExactSvmExtra. Scheme-specific extras are unvalidatable at the Concerto layer. A // TODO or follow-up issue would help track this before the agentic-commerce family expands.
| o Integer maxTimeoutSeconds range=[0,] | ||
| o String extra optional // scheme-specific JSON (e.g. {"name":"USDC","version":"2"}) | ||
| } | ||
|
|
There was a problem hiding this comment.
PaymentRequired (container) and PaymentPayload both carry ExtensionMap extensions optional, but PaymentRequirements — the item inside accepts[] — does not. If this is a deliberate projection decision rather than a gap, a comment here explaining the omission would help future readers.
| o String errorReason optional // standard error code if failed | ||
| o String payer optional | ||
| o String transaction // tx hash; empty string if settlement failed | ||
| o Caip2Network network // CAIP-2 |
There was a problem hiding this comment.
Required field with empty-string-on-failure semantics is an anti-pattern — optional exists for this case:
o String transaction optional // tx hash; absent if settlement failed
A consumer checking if (transaction) works today, but if (transaction !== undefined) silently breaks. Let success = false be the signal and leave the field absent.
| o SupportedKind[] kinds | ||
| o String[] extensions | ||
| o String signers optional // JSON map of CAIP-2 pattern → signer addresses | ||
| } |
There was a problem hiding this comment.
Two issues here: (1) candidate for the JSON scalar; (2) the model introduces ExtensionMap (a typed Concerto map) for extension objects but models the structurally simpler signers map as a raw JSON string. Either define a SignersMap type for consistency, or add a comment explaining why signers gets special treatment where extensions did not.
| o Integer x402Version | ||
| o PaymentRequirements[] accepts | ||
| o Long lastUpdated // unix timestamp | ||
| o String metadata optional // JSON: category, provider, ... |
There was a problem hiding this comment.
EvmAuthorization.validAfter/validBefore use UnsignedIntegerString (correct — EIP-712 signed bytes must not change). Using Long here is likely fine if the Bazaar spec defines lastUpdated as a JSON integer, but the inconsistency is a foot-gun for anyone adding new timestamp fields. Worth a brief comment explaining the asymmetry.
Closes
N/A — no corresponding issue. Part of a wider push to model the agentic-payment
standard primitives as Concerto models (first of the agentic-commerce family).
Changes
org.accordproject.protocol.x402@0.2.0(src/protocol/x402@0.2.0.cto) — aConcerto model of the x402
internet-native payments protocol, spec v2 (source snapshot pinned in the file
header):
PaymentRequired/PaymentRequirements,PaymentPayloadwith theexact/EVM (EIP-3009) and exact/SVM scheme payloads, facilitator
/verifyand/settleresponses,/supported, the HTTP header transport binding(
PAYMENT-REQUIRED/PAYMENT-SIGNATURE/PAYMENT-RESPONSE), the discovery(Bazaar) types, and the standard error-code enum.
org.accordproject.money@1.0.0.PreciseAmountin use: it is carriedon the Accord obligation binding (
AccordObligationExtensionInfo.canonicalAmount),where the agreement layer knows the asset's decimals — deliberately not on the
x402 wire fields, which stay atomic-unit strings +
assetand carry no scale.Flags
concerto version "^4.0.0"so itsExtensionMapmap type builds withmap support enabled by default — no
build.jschange required, consistentwith how
money-reference@1.0.0was fixed in fix(money): build reference model with Concerto 4 #193.org.accordproject.money@1.0.0by URL; the build resolves it viaupdateExternalModels(). Depends on money@1.0.0 already being published (it is).$classdiscriminators, and open/scheme-specific objects (extra, extensioninfo/schema) are carried as JSON strings. Field names and casing follow thex402 v2 spec; adapters convert at the boundary. Validated with
node build.js 'x402@0\.2\.0\.cto'(processes under Concerto v4.0.2, all codegenartifacts generated).
src/protocol/to match the namespace; happy to relocate if adifferent grouping is preferred.
verifiable-intent, lcp, aoep) to follow as separate PRs.
Screenshots or Video
N/A — model-only change.
Related Issues
crypto@1.0.0)^4.0.0map approach here mirrors it)Author Checklist
--signoffoption of git commit.node build.js 'x402@0\.2\.0\.cto'(model-only; repo has no unit-test framework, validation is via the models build).mainfromniallroche:agent/x402-model.