Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 17 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,24 +279,26 @@ See [docs/configuration.md](docs/configuration.md).

## Protocol support

| Protocol | Status | Pinned revision |
| -------- | ------------ | ------------------------------------------------------------------------------------------------------------ |
| **HTTP** | Supported | native routes |
| **MCP** | Supported | `2026-07-28` and `2025-11-25`, `@modelcontextprotocol/server@2.3.0` |
| **x402** | Supported | x402 v2 (`@x402/core`, `@x402/evm`, `@x402/svm`, `@x402/near`), scheme `exact`, EVM and Solana; NEAR partial |
| **MPP** | Experimental | `mppx@0.13.1`, `charge` + `evm` + EIP-3009, USDC on Base Sepolia and Base |
| **A2A** | Experimental | A2A v1.0.1, binding `JSONRPC`, method `SendMessage`, x402 extension |
| **ACP** | Experimental | ACP `2026-04-17`, REST checkout + discovery |
| **AP2** | Experimental | AP2 `v0.2.0`, Direct Checkout Mandate verification |
| UCP | Planned | no implementation |
| Protocol | Status | Pinned revision |
| ------------ | ------------ | ------------------------------------------------------------------------------------------------------------ |
| **HTTP** | Supported | native routes |
| **MCP** | Supported | `2026-07-28` and `2025-11-25`, `@modelcontextprotocol/server@2.3.0` |
| **x402** | Supported | x402 v2 (`@x402/core`, `@x402/evm`, `@x402/svm`, `@x402/near`), scheme `exact`, EVM and Solana; NEAR partial |
| **MPP** | Experimental | `mppx@0.13.1`, `charge` + `evm` + EIP-3009, USDC on Base Sepolia and Base |
| **A2A** | Experimental | A2A v1.0.1, binding `JSONRPC`, method `SendMessage`, x402 extension |
| **ACP** | Experimental | ACP `2026-04-17`, REST checkout + discovery |
| **AP2** | Experimental | AP2 `v0.2.0`, Direct Checkout Mandate verification |
| **ERC-8409** | Experimental | draft `SergeevDmitry/ERCs@0ee33230`: signed quotes on x402 EVM offers |
| UCP | Planned | no implementation |

[AP2](docs/protocols/ap2.md) is an authorization method, not a transport. It verifies a
mandate before a paid resource settles; the gateway does not issue the mandate
or its Checkout Receipt.

The experimental entries above ship off by default with documented subsets:
[A2A](docs/protocols/a2a.md), [ACP](docs/protocols/acp.md),
[AP2](docs/protocols/ap2.md#not-implemented) and [MPP](docs/protocols/mpp.md). Supported
[AP2](docs/protocols/ap2.md#not-implemented), [MPP](docs/protocols/mpp.md) and
[ERC-8409 quotes](docs/protocols/x402.md#erc-8409-signed-quotes). Supported
[MCP](docs/protocols/mcp.md) and [x402](docs/protocols/x402.md) are also scoped.
ACP exposes only its five checkout operations; merchant `payment_data` is never
converted into a gateway payment proof.
Expand All @@ -308,7 +310,8 @@ prints the `unsupported` lists for A2A, ACP and AP2 when they are enabled.
### Versioning

Minor releases may break experimental configurations, integrations or types.
This applies to MPP, A2A, ACP, AP2 and `agent-commerce import openapi`.
This applies to MPP, A2A, ACP, AP2, ERC-8409 quotes and
`agent-commerce import openapi`.
Runtime adapters and providers report `status: experimental` in
`GET /.well-known/agent-commerce`. Pin an exact version and test upgrades
before deploying them. Since 2.1, some supported x402 behavior and types
Expand All @@ -318,7 +321,8 @@ differ from 2.0; see [Upgrading from 2.0 to 2.x](docs/migration-2.1.md).

- **Non-custodial.** Buyer payments go directly to the merchant address in
`payTo` (`recipient` for MPP). The gateway does not require buyer or merchant
private keys. Local facilitator mode does hold a separate gas-paying signer.
private keys. Local facilitator mode uses a separate key to pay gas.
Experimental ERC-8409 quotes use a dedicated, unfunded signing key.
- **Fail closed.** Missing, malformed, expired, replayed, wrong-amount,
wrong-recipient, wrong-network and wrong-asset payments are rejected.
- **Replay-safe twice over.** The EIP-3009 or Permit2 nonce stops a double
Expand Down
5 changes: 3 additions & 2 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ Agent Commerce Gateway is **not** a payment processor, wallet, exchange or
custodian.

- The payment flow does not require a merchant or buyer private key or seed
phrase. Local facilitator mode does accept a configured gas-paying signer key;
do not reuse a merchant or buyer key for it.
phrase. Local facilitator mode uses a configured key to pay gas, and
experimental ERC-8409 quotes use a dedicated, unfunded signing key. Do not
reuse a merchant or buyer key for either purpose.
- The merchant settlement destination is plain configuration
(`payments.x402.payTo: ${MERCHANT_WALLET}`), an address the merchant
controls. It is never a gateway-owned wallet.
Expand Down
10 changes: 8 additions & 2 deletions config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,12 @@ payments:
payTo: ${MERCHANT_WALLET}
# Seconds a payment challenge stays valid
maxTimeoutSeconds: 120
# Experimental ERC-8409 quotes for EVM offers in x402 challenges.
# Use a dedicated, unfunded key, never the merchant's. See
# docs/configuration.md#erc-8409-quotes.
# quote:
# enabled: true
# signerPrivateKey: ${X402_QUOTE_SIGNER_KEY}
# Settlement order: authorization (the default) calls the backend first
# and settles only if it succeeds; upfront settles first. A paid resource
# can set its own paymentFlow, which applies to the rail that serves it.
Expand All @@ -136,8 +142,8 @@ payments:
# on mainnet, and a well-known Anvil key is refused against
# any RPC that is not local or private.
#
# mode: remote an HTTP facilitator does the signing; this gateway holds no
# signing key. Requires https, except on a local or private
# mode: remote an HTTP facilitator handles settlement; the gateway holds no
# facilitator signing key. Requires https, except on a local or private
# host off mainnet, and on mainnet a credential unless
# allowUnauthenticatedFacilitator is set.
facilitator:
Expand Down
44 changes: 44 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,50 @@ own tokens and decimals. Config load rejects empty lists, repeated entries,
and `x402.accepts` on free resources, without `x402` in `payments`, or without
`payments.x402`.

### ERC-8409 quotes

Experimental and disabled by default. Enable
[ERC-8409 quotes](protocols/x402.md#erc-8409-signed-quotes) to sign EVM offers
in x402 challenges.

```yaml
payments:
x402:
quote:
enabled: true
signerPrivateKey: ${X402_QUOTE_SIGNER_KEY} # dedicated; holds no funds
```

Use a dedicated key that signs nothing else and holds no funds. Never reuse a
merchant's or buyer's key. Anyone with the key can sign quotes as the merchant;
signing a quote does not transfer funds. The gateway keeps the key in memory
and publishes the issuer address.

When `enabled` is true, `signerPrivateKey` is required. `validate` checks that
it is a nonempty string; the provider checks the key during construction.
Construction fails with `CONFIG_INVALID` for a malformed key, a well-known
Anvil key outside the local chain, a key matching an EVM `payTo`, or a provider
with no EVM offer. Restart the gateway after rotating the key. `doctor`
compares the configured key's address with the published issuer to detect a
gateway still using the old key.

Quotes increase header sizes. Each quoted offer adds about 0.9 KB of base64
to `PAYMENT-REQUIRED`, plus about 1 KB for the extension's schema. A small
challenge grows from roughly 0.6 KB to 2.5 KB with one offer, or 4.9 KB with
three; sizes vary with the offer and resource metadata.

Behind nginx, size
[`proxy_buffer_size`](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffer_size)
to fit the response headers. Its default is one 4 or 8 KB memory page;
oversized headers cause nginx to reject the response. SDK clients echo quotes
in `PAYMENT-SIGNATURE`, which can reach about 7.4 KB with six quoted offers.
This approaches nginx's default 8 KB limit for a single request header field,
set by
[`large_client_header_buffers`](https://nginx.org/en/docs/http/ngx_http_core_module.html#large_client_header_buffers).

Each cache miss requires a signature per EVM offer. Rate-limit unpaid requests
at the edge; see [Denial of service](security.md#denial-of-service).

### `payments.mpp`

MPP accepts the `USDC` currency label on the local chain `eip155:31337`, Base
Expand Down
1 change: 1 addition & 0 deletions docs/contract-surface.txt
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ interface PaymentChallenge {
interface PaymentContext {
readonly amount: string;
readonly currency: string;
readonly input?: unknown;
readonly metadata?: Readonly<Record<string, unknown>>;
readonly requestId: string;
readonly requestedAt: string;
Expand Down
29 changes: 28 additions & 1 deletion docs/contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,10 @@ persistence must not fail the commerce flow.
envelope fields are deprecated: read individual offers in `accepts`. With
`strictNullChecks`, callers passing either `destination` as a required
`string` must now handle `undefined`.
- Optional `PaymentContext.input` carries validated resource input to
`createRequirement`, allowing a rail to bind its challenge to the request.
x402's ERC-8409 quotes hash this input with the resource ID. A rail must not
send the input to a third party. Providers that ignore the field are unaffected.

### Protocols and gateway

Expand Down Expand Up @@ -258,6 +262,16 @@ persistence must not fail the commerce flow.
`<network> <asset> <payTo>` with lowercase EVM addresses. Any failing offer
fails the provider. `/.well-known` copies statuses into `options` and
`resourceOptions`; `/ready` is unchanged.
- Experimental ERC-8409 quotes on x402 EVM offers. Without `quote`, challenges
are unchanged.
- `X402ProviderOptions.quote` enables signing.
`GatewayConfig.payments.x402.quote` is present only when
`payments.x402.quote.enabled` is true.
- `createX402PaymentProvider` returns `X402PaymentProvider`, a
`PaymentProvider` whose optional `quote` describes the issuer, request scheme
and extension key. The descriptor gains the capability `erc-8409-quotes`.
- `/.well-known` publishes `payments.x402.quote` (`WellKnownX402Quote`), read
from the signing provider.

- `ProtocolName` gained `a2a` and `acp`; `PROTOCOL_NAMES` is the runtime list
used by validation. It is derived from a record keyed by the union, so a
Expand Down Expand Up @@ -434,6 +448,8 @@ export interface X402ProviderOptions extends X402OfferOptions {
readonly customNetworks?: Readonly<
Record<string, { readonly kind: 'testnet' | 'mainnet'; readonly displayName: string }>
>;
/** Experimental: signs ERC-8409 quotes for EVM offers with a dedicated key when request data can be canonicalized */
readonly quote?: { readonly signerPrivateKey: string };
readonly logger?: Logger;
readonly clock?: Clock;
readonly ids?: IdGenerator;
Expand All @@ -454,9 +470,18 @@ export interface X402MultiOfferProviderOptions
readonly offers: readonly X402OfferOptions[];
}

/** Experimental `quote`: present when the provider signs ERC-8409 quotes */
export interface X402PaymentProvider extends PaymentProvider {
readonly quote?: {
readonly issuer: `0x${string}`;
readonly requestScheme: string; // 'agent-commerce/request/v1'
readonly extension: string; // 'erc-8409', the PaymentRequired.extensions key
};
}

export function createX402PaymentProvider(
options: X402ProviderOptions | X402MultiOfferProviderOptions,
): PaymentProvider;
): X402PaymentProvider;

export type DeploymentMode = 'local' | 'testnet' | 'mainnet';
export const SUPPORTED_NETWORK_IDS: readonly string[];
Expand Down Expand Up @@ -778,6 +803,8 @@ export interface GatewayConfig {
readonly options?: readonly X402OptionConfig[]; // the fields above mirror the first
// Each resource's own offers, replacing `options` for it
readonly resourceOptions?: Readonly<Record<string, readonly X402OptionConfig[]>>;
// Experimental; present only when `payments.x402.quote.enabled` is true
readonly quote?: { readonly signerPrivateKey: string };
};
readonly mpp?: {
readonly enabled: boolean;
Expand Down
2 changes: 2 additions & 0 deletions docs/payment-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ the reserved `_payment` input field. MCP also accepts the selected rail's
`_meta` carrier; see [Payment over MCP](protocols/mcp.md#payment-over-mcp). A2A
also accepts the x402 extension's task metadata; see
[x402 extension](protocols/a2a.md#x402-extension).
With [ERC-8409 quotes](protocols/x402.md#erc-8409-signed-quotes) enabled, the
challenge also carries a signed quote for each EVM offer.

### MPP

Expand Down
21 changes: 11 additions & 10 deletions docs/protocols.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@ subset. Each protocol's page describes that subset in detail.

## Support matrix

| Protocol | Status | Revision | Implemented subset |
| -------- | ------------ | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| **HTTP** | Supported | native | resource routes and rail-specific payment headers |
| **MCP** | Supported | MCP `2026-07-28` and `2025-11-25` through `@modelcontextprotocol/server@2.3.0` | tool discovery, invocation, payment-required and error mapping |
| **x402** | Supported | x402 v2, `@x402/core@2.25.0`, `@x402/evm@2.25.0`, `@x402/svm@2.25.0`, `@x402/near@2.25.0` | `exact` on EVM through EIP-3009 or Permit2, on Solana, and on NEAR (partial): challenge, verification, settlement and replay binding |
| **A2A** | Experimental | v1.0.1; negotiation `1.0`; `JSONRPC` binding | Agent Card, `SendMessage`, paid flow and the x402 extension |
| **ACP** | Experimental | stable snapshot `2026-04-17`; REST binding | discovery and the five checkout operations |
| **AP2** | Experimental | v0.2.0, tag 2026-04-28, commit `b4587ac`; Direct mode | closed Checkout Mandate verification before settlement |
| **MPP** | Experimental | drafts at `tempoxyz/mpp-specs@806fdb8`; `mppx@0.13.1` | `charge`/`evm`/EIP-3009 over HTTP, MCP and A2A |
| UCP | Planned | - | no implementation |
| Protocol | Status | Revision | Implemented subset |
| ------------ | ------------ | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| **HTTP** | Supported | native | resource routes and rail-specific payment headers |
| **MCP** | Supported | MCP `2026-07-28` and `2025-11-25` through `@modelcontextprotocol/server@2.3.0` | tool discovery, invocation, payment-required and error mapping |
| **x402** | Supported | x402 v2, `@x402/core@2.25.0`, `@x402/evm@2.25.0`, `@x402/svm@2.25.0`, `@x402/near@2.25.0` | `exact` on EVM through EIP-3009 or Permit2, on Solana, and on NEAR (partial): challenge, verification, settlement and replay binding |
| **A2A** | Experimental | v1.0.1; negotiation `1.0`; `JSONRPC` binding | Agent Card, `SendMessage`, paid flow and the x402 extension |
| **ACP** | Experimental | stable snapshot `2026-04-17`; REST binding | discovery and the five checkout operations |
| **AP2** | Experimental | v0.2.0, tag 2026-04-28, commit `b4587ac`; Direct mode | closed Checkout Mandate verification before settlement |
| **MPP** | Experimental | drafts at `tempoxyz/mpp-specs@806fdb8`; `mppx@0.13.1` | `charge`/`evm`/EIP-3009 over HTTP, MCP and A2A |
| **ERC-8409** | Experimental | draft `SergeevDmitry/ERCs@0ee33230` | signed quotes on x402 EVM offers; issuer published in `/.well-known/agent-commerce` |
| UCP | Planned | - | no implementation |

Experimental components are disabled unless configured and support only the
subsets their pages describe. Planned means there is no adapter or endpoint.
Expand Down
Loading
Loading