ExchainDocs
DocsAPI

x402Sellers and guard receipts

Sellers and guard receipts

Bind payments to sellers' signed offers, and let sellers verify that a payment came from a guarded wallet.

Hold every payment to the seller's own signed price, and give the seller a receipt it can check against Exchain's public key. Both apply to x402 payments.

Seller binding

Seller binding applies only when the wallet registered sellers (Register a Safe). Each offer arrives in the payment's intent.offer, as eip712 or jws. Exchain takes the host of the offer's resourceUrl, finds that host in sellers, and checks who signed the offer.

FieldValues
sellersUp to 64 { domain, keys? } entries. domain is a lowercase host
sellers[].keysUp to 8. EVM addresses for eip712 offers, did:web key ids for jws offers
unsignedSellers"refuse" (default, also when absent) or { "allowUnder": "<minor units>" }

Seller keys

A seller publishes its offer keys in its own did:web document. When a check needs it, Exchain fetches https://<domain>/.well-known/did.json for the offer's host at co-sign time and keeps it for 10 minutes. The document's id must be did:web:<domain>.

Offerkeys setkeys empty
eip712The recovered signer must be one of keys. The did.json is not fetchedThe signer must match the address in a verificationMethod's blockchainAccountId in the did.json
jwskid must be one of keys. The public key comes from the did.jsonThe public key comes from the did.json

A jws offer always needs the seller's did.json: its kid must start with did:web:<domain>#, and the signature must verify with the publicKeyJwk of the verificationMethod with that id (Ed25519 for EdDSA, secp256k1 for ES256K). An eip712 offer with keys set is checked against keys alone.

CaseCode
No offer, and no allowUnder cap or the amount is above itregistry_gate
Offer cannot be decoded, or the resource URL has no hostbody_mismatch
Host not in sellerspayee_unpinned
Signer not authorized as above, or a needed did.json cannot be fetchedpayee_unpinned
Offer scheme is not exact, or its network, asset or payee differ from the authorizationbody_mismatch
Offer expiredbody_mismatch
Amount above the offerprice_above_published

Paying at or below the offer passes. These codes arrive at member level in the 409.

A direct payment (POST /v1/evm/cosign) carries no offer. On a wallet with sellers, it passes only under unsignedSellers.allowUnder.

Guard receipt

Every co-signed x402 payment returns a compact JWS in receipt. Direct payments and shadow wallets get no receipt. The agent attaches it to the payment at extensions["exchain-guard"].receipt. The receipt is Exchain's own field, not part of the x402 protocol.

Header
{ "alg": "EdDSA", "kid": "did:web:<host>#journal", "typ": "exchain-guard+jws" }
Payload
{
  "v": 1,
  "payer": "eip155:84532:0xSafe…",
  "network": "eip155:84532",
  "asset": "0x036cbd53842c5426634e7929541ec2318f3dcf7e",
  "amount": "500000",
  "payTo": "0xPayTo…",
  "resourceUrl": "https://api.seller.example/forecast",
  "nonce": "0x0909…09",
  "decisionId": "dec_01J9XK3M4N5P6Q7R8S9T0V1W2X",
  "journalLeaf": "<64 hex>",
  "mandateHash": "<64 hex>",
  "ownerBound": true,
  "issuedAt": 1762596000
}
FieldMeaning
payerThe guarded Safe, as CAIP-10
assetThe token contract that was paid
resourceUrlFrom the signed offer, else the intent's resource
journalLeafThe decision's journal leaf, or null if not yet available
mandateHashThe constitutionId of the paid asset's mandate
ownerBoundThe owner address is bound to a console account
issuedAtUnix seconds

Verify a receipt

Exchain's own did.json has nothing to do with seller keys. It publishes one key, #journal: the Ed25519 key that signs guard receipts and bound statements.

  1. Fetch https://cosign.exchain.network/.well-known/did.json.
  2. Check that kid is did:web:cosign.exchain.network#journal. The kid names the host that issued the receipt, so trust only that issuer.
  3. Verify the Ed25519 signature with the #journal key.
  4. Compare payTo, amount, payer, network and resourceUrl with the payment you received.

The receipt proves Exchain co-signed this payment. It does not prove settlement. Check the decision behind decisionId on Verify a decision.

On this page