ExchainDocs
DocsAPI

APICo-signCo-sign a direct EVM payment

Co-sign a direct EVM payment

Co-sign a direct payment from a guarded Safe on an EVM chain.

  • Auth Node Token
  • Rate-limited Yes
  • Idempotency Idempotency-Key equal to batchId
  • Guide Co-sign a payment
POST
/v1/evm/cosign

Co-sign one direct payment from a guarded Safe to any address, outside x402: an EIP-3009 TransferWithAuthorization from the Safe, valid at most 600 seconds, with agentSignature the agent’s signature over its Safe message hash. Permit2 is refused here.

In a token without an EIP-3009 a Safe can sign (Ethereum USDT, Polygon USDT0), the payment is a Safe SafeTx instead, on the Safe’s domain {chainId, verifyingContract: Safe}, with exactly to the token, value 0, data the token’s transfer(payee, amount) (selector 0xa9059cbb, nothing more), operation 0 (CALL), safeTxGas, baseGas and gasPrice 0, gasToken and refundReceiver the zero address, and nonce the Safe’s current nonce (else 409 body_mismatch, detail safe_nonce). agentSignature is then the agent’s signature over the SafeTx hash.

Exchain verifies the typed data against intent and checks the mandate of the asset intent.asset names; with seller binding, the payee counts as an unsigned seller. The answer carries transaction, the call that moves the payment (transferWithAuthorization to the token, or execTransaction to the Safe), which any account can send. With relay, Exchain sends it and answers once it is mined. A Safe transaction lands when the Safe logs ExecutionSuccess for its hash; its budget returns once another transaction takes its nonce at a finalized block.

Authorization

nodeToken
headerAuthorizationBearer <token>

Node Token from the Exchain console. Works for wallets this node registered; the node’s network (preprod or mainnet) decides the network of the request. Use a preprod token in the playground.

Header Parameters

Idempotency-Key*string

Must equal batchId, which is the authorization nonce (for a SafeTx, the Safe nonce): 0x and 64 hex characters.

Match^0x[0-9a-fA-F]{64}$

Request Body

application/json
  1. body
batchId*string

The authorization nonce (for a SafeTx, the Safe nonce) as 0x and 64 hex characters. Equal to the Idempotency-Key header.

Match^0x[0-9a-fA-F]{64}$
walletId*WalletId
Match^wal_[0-9A-HJKMNP-TV-Z]{26}$
typedData*

An EIP-3009 TransferWithAuthorization from the Safe on the token’s domain or, in a token without an EIP-3009 a Safe can sign (Ethereum USDT, Polygon USDT0), a SafeTx on the Safe’s domain calling the token’s transfer(payee, amount). Permit2 is refused here.

agentSignature*string

The agent’s 65-byte signature over the Safe message hash of typedData.

Match^0x[0-9a-f]{130}$
relay?boolean

Exchain sends the signed transfer and answers once it is mined, so the agent needs no ETH.

intent*
context?

Response Body

Allowed. With relay, relayed.txHash is the transaction that moved the payment; otherwise send transaction from any account before the authorization expires (a SafeTx, until the Safe’s nonce moves past it).

application/json
  1. response
decisionId*DecisionId
Match^dec_[0-9A-HJKMNP-TV-Z]{26}$
safeMessageHash*Hex32
Match^0x[0-9a-f]{64}$
walletAddress*EvmAddress
Match^0x[0-9a-fA-F]{40}$
digest*Hex32
Match^0x[0-9a-f]{64}$
nonce*Hex32
Match^0x[0-9a-f]{64}$
quorumMembers?array<string>
signature?string

Every owner’s 65-byte signature, sorted by owner address, ready for the payment payload.

Match^0x([0-9a-f]{130})+$
receipt?string

Guard receipt: a compact EdDSA JWS by the journal key (kid did:web:<host>#journal, published at /.well-known/did.json) over {v, payer, network, asset, amount, payTo, resourceUrl, nonce, decisionId, journalLeaf, mandateHash, ownerBound, issuedAt}. Attach it to the payment payload at extensions["exchain-guard"].receipt.

shadow?boolean
members*array<|||||||>
Items1 <= items <= 1
boundsRemaining*
journalRef*string
asOf*string
Match^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$
Formatdate-time
schemaVersion*string

Wire schema version, "1.1" today.

Value in"1.0""1.1"
chainTip*|null
transaction*

The call that moves the payment, carrying every owner’s signature. It is transferWithAuthorization to the token, or for a SafeTx execTransaction to the Safe. Any account can send it.

relayed?

With relay, the transaction Exchain sent.

relayError?string

With relay, why Exchain could not send it. transaction can still be sent until it expires, or for a SafeTx until the Safe’s nonce moves past it.

curl -X POST 'https://cosign.exchain.network/v1/evm/cosign' \  -H 'Idempotency-Key: 0xd830b1c61b024118f4e1dce0c305f81290222d28c2b830accf22c60b0304f946' \  -H 'Content-Type: application/json' \  -d '{  "batchId": "0xd830b1c61b024118f4e1dce0c305f81290222d28c2b830accf22c60b0304f946",  "walletId": "wal_01M53M3ZNQQ4DDNPE4262QAZ28",  "typedData": {    "domain": {      "name": "USDC",      "version": "2",      "chainId": 84532,      "verifyingContract": "0x036cbd53842c5426634e7929541ec2318f3dcf7e"    },    "types": {      "TransferWithAuthorization": [        {          "name": "from",          "type": "address"        },        {          "name": "to",          "type": "address"        },        {          "name": "value",          "type": "uint256"        },        {          "name": "validAfter",          "type": "uint256"        },        {          "name": "validBefore",          "type": "uint256"        },        {          "name": "nonce",          "type": "bytes32"        }      ]    },    "primaryType": "TransferWithAuthorization",    "message": {      "from": "0x9f2b6c1e4d7a3058b2e6f1c9d4a7b3e5f0c8a216",      "to": "0x6d2f8a4c1e9b3075d8a2f6c4e1b9d3a7f5c0e284",      "value": "500000",      "validAfter": "0",      "validBefore": "1793974027",      "nonce": "0xd830b1c61b024118f4e1dce0c305f81290222d28c2b830accf22c60b0304f946"    }  },  "relay": true,  "agentSignature": "0x8a52bb619f12c4eb51b5297ac6bffd2eba6e81ff075e35d917b104352f93eae2b218c2eaa10c4aee2d8e8128de363bda0c853a72f6f1cfd1b7596ea3c862c7e41c",  "intent": {    "purchaseId": "att_01M53M3ZR1088SNXVGAJ9SFR2T",    "counterparty": "eip155:84532:0x6d2f8a4c1e9b3075d8a2f6c4e1b9d3a7f5c0e284",    "amount": "500000",    "asset": "eip155:84532/erc20:0x036cbd53842c5426634e7929541ec2318f3dcf7e",    "agentIdentifier": "research-agent",    "reference": "invoice 2026-118"  }}'
Example Response
{  "decisionId": "dec_01M53M3ZR1088SNXVGAJ9SFR2V",  "safeMessageHash": "0x26ebe1acc814c822010eb2abe7e27178aebe0fcf2467a5e58e6b50be46ab7fab",  "walletAddress": "0x9f2b6c1e4d7a3058b2e6f1c9d4a7b3e5f0c8a216",  "digest": "0x9132f370787dedf90c6d69f0ff12a9f7a8471d58cfb86c0a842d69f4e72bd81f",  "nonce": "0xd830b1c61b024118f4e1dce0c305f81290222d28c2b830accf22c60b0304f946",  "quorumMembers": [    "exchain-eu-1a",    "exchain-eu-1b",    "exchain-eu-1c"  ],  "signature": "0x32d0fed30f3ccd8de52142a203227ea2d59c5f7ff529ab014b9d95023082dcafe190023975d13442ade43fa17a5ee4424a773791a1e4809d83ef786dbf6703491b7ad110a292193bfd59e7a8edb3e3d8fec0963c9c5b49b5e6e5c334bd1a9c803e58aa61dc2b210d7497844e5be671c1fd5a7f57f3545ffdbd8ef5b5b5788dc7f41b55570466ad06c009dd4dff5135a8e5b88cb20680a5e235b4d85c51d91ad1211213556442ee8a489a860e51048aab630d8695ed509393bbf1e49e8be23eae8f811b9d111cda2f3f27e05c6f3d19ea01e5176fe1c214e1f8bf0847577ba989a1ba3bf5ca770272fe92850b3cd7814c9b8be6202943343daf015e095ac3ee734d11fa1b",  "transaction": {    "to": "0x036cbd53842c5426634e7929541ec2318f3dcf7e",    "data": "0xe3ee160e0000000000000000000000009f2b6c1e4d7a3058b2e6f1c9d4a7b3e5f0c8a2160000000000000000000000006d2f8a4c1e9b3075d8a2f6c4e1b9d3a7f5c0e284000000000000000000000000000000000000000000000000000000000007a1200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006aeddf0bd830b1c61b024118f4e1dce0c305f81290222d28c2b830accf22c60b0304f94600000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000010432d0fed30f3ccd8de52142a203227ea2d59c5f7ff529ab014b9d95023082dcafe190023975d13442ade43fa17a5ee4424a773791a1e4809d83ef786dbf6703491b7ad110a292193bfd59e7a8edb3e3d8fec0963c9c5b49b5e6e5c334bd1a9c803e58aa61dc2b210d7497844e5be671c1fd5a7f57f3545ffdbd8ef5b5b5788dc7f41b55570466ad06c009dd4dff5135a8e5b88cb20680a5e235b4d85c51d91ad1211213556442ee8a489a860e51048aab630d8695ed509393bbf1e49e8be23eae8f811b9d111cda2f3f27e05c6f3d19ea01e5176fe1c214e1f8bf0847577ba989a1ba3bf5ca770272fe92850b3cd7814c9b8be6202943343daf015e095ac3ee734d11fa1b00000000000000000000000000000000000000000000000000000000"  },  "relayed": {    "txHash": "0x4b1e9c27d05a8f3e6b2d71c49a0e8f53d6c2b7a19e4f0d83c5a6b2e17f9d0c48"  },  "members": [    {      "purchaseId": "att_01M53M3ZR1088SNXVGAJ9SFR2T",      "outputIndex": 0,      "verdict": "allowed"    }  ],  "boundsRemaining": {    "per_tx_cap": {      "limit": "1000000",      "used": "500000",      "remaining": "500000"    },    "velocity_burst": {      "limit": "60",      "used": "1",      "remaining": "59",      "windowResetsAt": "2026-11-05T14:23:08Z"    },    "daily_outflow": {      "limit": "500000000",      "used": "12500000",      "remaining": "487500000",      "windowResetsAt": "2026-11-06T08:41:19Z"    },    "per_seller_cap": {      "limit": "50000000",      "used": "3500000",      "remaining": "46500000",      "windowResetsAt": "2026-11-06T08:41:19Z",      "scope": "eip155:84532:0x6d2f8a4c1e9b3075d8a2f6c4e1b9d3a7f5c0e284"    },    "per_agent_cap": {      "limit": "250000000",      "used": "12500000",      "remaining": "237500000",      "windowResetsAt": "2026-11-06T08:41:19Z",      "scope": "0x3c8e1f7a2b9d4e6c0a5f8b1d7e3c9a2f4b6d8e01"    },    "envelope": {      "limit": "20000000000",      "used": "86500000",      "remaining": "19913500000"    }  },  "journalRef": "https://cosign.exchain.network/v1/decisions/dec_01M53M3ZR1088SNXVGAJ9SFR2V",  "asOf": "2026-11-05T14:22:07.318Z",  "schemaVersion": "1.1",  "chainTip": null}

Errors

StatusCodeMeaning
400bad_requestThe body fails the schema, intent.amount is outside 1..10^18, or intent.counterparty is malformed.
400idempotency_key_mismatchThe Idempotency-Key header differs from batchId.
400batch_id_mismatchbatchId differs from the authorization nonce (for a SafeTx, the Safe nonce).
400clock_rejectedExchain’s clock check failed. Contact Exchain.
401unauthorizedThe bearer credential is missing, unknown or revoked.
401agent_signature_invalidagentSignature does not recover to the registered agent over the Safe message hash.
402fee_dueSign fee.typedData’s Safe message hash with the agent key, send it to POST /v1/x402/fees, then send the payment again.
404not_foundThe wallet is not a guarded Safe of this node.
409member_deniedThe payment was refused; members[0].denied has the code (mandate caps, counterparty_blocked, or seller binding: payee_unpinned, registry_gate, price_above_published, body_mismatch).
409asset_not_listedintent.asset is not one of the wallet’s governed assets, or Exchain disabled it on the network.
409body_mismatchThe typed data disagrees with intent or uses a method this route refuses; detail says how (for example payee, amount, window, method, nonce_reused, safe_nonce). Do not retry.
409clock_skewThe authorization window is not open now.
409reservation_conflictAnother payment holds a reservation; retry.
409idempotency_conflictThis nonce was decided before with different content (an Error body, not a refusal).
413payload_too_largeThe request body is larger than 64 KiB.
429rate_limitedOver the limit; retry after Retry-After seconds (also in retryAfterSec).
502rpc_failedThe chain could not be read (the Safe’s nonce, for a SafeTx payment or a fee Safe transaction); retry.
503quorum_unavailableFewer enclaves reachable than the threshold (Retry-After: 5), or detail: "stopped" when Exchain stopped signing (reachable: 0, no retryAfterSec and no Retry-After header).
503unavailabledetail: "record", the audit record could not be written (Retry-After: 5).

Every code and what to do about it: Errors and codes.