Skip to content

feat(x402): Agent/x402 model - #194

Open
niallroche wants to merge 2 commits into
accordproject:mainfrom
niallroche:agent/x402-model
Open

feat(x402): Agent/x402 model#194
niallroche wants to merge 2 commits into
accordproject:mainfrom
niallroche:agent/x402-model

Conversation

@niallroche

Copy link
Copy Markdown
Contributor

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

  • Adds org.accordproject.protocol.x402@0.2.0 (src/protocol/x402@0.2.0.cto) — a
    Concerto model of the x402
    internet-native payments protocol, spec v2 (source snapshot pinned in the file
    header): PaymentRequired / PaymentRequirements, PaymentPayload with the
    exact/EVM (EIP-3009) and exact/SVM scheme payloads, facilitator /verify and
    /settle responses, /supported, the HTTP header transport binding
    (PAYMENT-REQUIRED / PAYMENT-SIGNATURE / PAYMENT-RESPONSE), the discovery
    (Bazaar) types, and the standard error-code enum.
  • Demonstrates org.accordproject.money@1.0.0.PreciseAmount in use: it is carried
    on 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 + asset and carry no scale.

Flags

  • Declares concerto version "^4.0.0" so its ExtensionMap map type builds with
    map support enabled by default — no build.js change required, consistent
    with how money-reference@1.0.0 was fixed in fix(money): build reference model with Concerto 4 #193.
  • Imports org.accordproject.money@1.0.0 by URL; the build resolves it via
    updateExternalModels(). Depends on money@1.0.0 already being published (it is).
  • This is a wire-faithful projection, not a byte-identical schema: Concerto adds
    $class discriminators, and open/scheme-specific objects (extra, extension
    info/schema) are carried as JSON strings. Field names and casing follow the
    x402 v2 spec; adapters convert at the boundary. Validated with
    node build.js 'x402@0\.2\.0\.cto' (processes under Concerto v4.0.2, all codegen
    artifacts generated).
  • Placed under src/protocol/ to match the namespace; happy to relocate if a
    different grouping is preferred.
  • First of a family of agentic-commerce protocol models (a2a, ap2, ucp, acp,
    verifiable-intent, lcp, aoep) to follow as separate PRs.

Screenshots or Video

N/A — model-only change.

Related Issues

Author Checklist

  • Ensure you provide a DCO sign-off for your commits using the --signoff option of git commit.
  • Vital features and changes captured in unit and/or integration tests — validated with node build.js 'x402@0\.2\.0\.cto' (model-only; repo has no unit-test framework, validation is via the models build).
  • Commits messages follow AP format
  • Extend the documentation, if necessary — model docs are generated by the models build.
  • Merging to main from niallroche:agent/x402-model.

niallroche and others added 2 commits July 20, 2026 21:09
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>
@niallroche
niallroche requested a review from mttrbrts July 22, 2026 13:26
@mttrbrts mttrbrts changed the title Agent/x402 model feat(x402): Agent/x402 model Aug 5, 2026

@mttrbrts mttrbrts left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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]*)$/

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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"})
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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"})
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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, ...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

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.

2 participants