ExchainDocs
DocsAPI

CosignEVM chains

EVM chains

The guarded Safe on Base, Ethereum, Arbitrum, Optimism and Polygon: contracts, signing, relay, settlement and fees.

On EVM chains, Cosign guards a stock Safe. A payment from it needs the agent's signature and the signature of every member of Exchain's attested quorum. The same Safe pays through two rails: direct (POST /v1/evm/cosign, to any address) and x402 (POST /v1/x402/cosign, to x402 sellers).

To register a Safe, see Create and register a wallet. To co-sign a payment, see Co-sign a payment.

Chains

NodeChains
MainnetBase, Ethereum, Arbitrum, Optimism, Polygon
PreprodBase Sepolia, Sepolia, Arbitrum Sepolia, OP Sepolia, Polygon Amoy

A Safe governs one or more of its chain's listed stablecoins, each with its own mandate: Assets. It lives on the chain of its asset ids. Which chains are live for your node: Chain availability.

The guarded Safe

PropertyValue
ContractStock SafeL2 1.4.1. No custom contract
OwnersThe agent address plus one address per member of the attested quorum
ThresholdAll owners
ERC-1271CompatibilityFallbackHandler 1.4.1
ModuleThe owner address is the only module. It withdraws, rotates the agent or removes Exchain through execTransactionFromModule, with no Exchain signature. It must not also be an owner
GuardEmpty
AddressCREATE2 of exactly this setup. Any other address is refused safe_mismatch
TokensUp to 16 listed tokens on the Safe's chain, set at registration
DeploymentExchain deploys the Safe and pays the gas

GET /v1/x402/signers returns what a Safe must use, for your node's network:

FieldContents
networkpreprod or mainnet
chainIdThe network's Base chain: 84532 or 8453
chainIdsThe chains live for your node
signers[]One Safe owner address per member of the attested quorum, with member and pcr0 (the enclave image measurement) when known
safeversion, singleton, proxyFactory, fallbackHandler, multiSendCallOnly and permit2 addresses

Signing

Every payment is one EIP-712 message from the Safe: a token authorization (EIP-3009, or Permit2 on x402), or a Safe transaction for Ethereum USDT and Polygon USDT0 on the direct rail.

  1. The agent signs the Safe message hash of the typed data as a raw 65-byte signature. Not personal_sign. For a Safe transaction, it signs the SafeTx hash.
  2. Exchain checks the payment against the mandate. If it fits, each member of the attested quorum signs the same hash.
  3. Exchain packs the owners' 65-byte signatures into one Safe signature, sorted by owner address.
  4. On chain, the token or Permit2 verifies that signature through the Safe's ERC-1271 handler. A Safe transaction is verified by the Safe in execTransaction.
MethodValid forbatchId
EIP-3009 TransferWithAuthorizationAt most 600 secondsThe authorization nonce
Permit2 PermitWitnessTransferFromAt most 600 secondsThe Permit2 nonce, 32 bytes big-endian
Safe transaction (SafeTx)Until the Safe's nonce moves past itThe Safe's current nonce, 32 bytes big-endian

USDT on Ethereum has no EIP-3009, and Polygon USDT0's EIP-3009 does not accept a Safe's signature. For both, a direct payment is a SafeTx calling the token's transfer(payee, amount), with value, operation and every gas field 0 and no refund receiver. Its nonce must be the Safe's current nonce, otherwise body_mismatch: safe_nonce.

Permit2

ItemRule
Railx402 only. Direct payments refuse Permit2 (body_mismatch: method)
Spenderx402ExactPermit2Proxy only
ApprovalAt deployment, the Safe approves Permit2 for permit2Cap of each governed token, set at registration
Defaultpermit2Cap is 0: Permit2 payments cannot settle until you register with a cap
Ethereum USDT, Polygon USDT0Permit2 is their only x402 method

Relay

Direct payments only. Send relay: true with POST /v1/evm/cosign.

AnswerMeaning
relayed.txHashExchain sent the transaction and paid the gas
relayErrorExchain could not send it. transaction still works: any account can send it before it expires, or for a Safe transaction until the Safe's nonce moves past it

A retry with the same batchId returns the stored answer and does not send again. On x402, the seller's facilitator settles the payment.

Settlement

Exchain watches each Safe on its own chain.

EventResult
The authorization's nonce is used on chainThe payment has landed. The decision names the transaction, with a link to that chain's explorer
The authorization is still unused at a finalized block past validBefore + 60 sIts budget returns
A Safe transaction: the Safe logs ExecutionSuccess for its hashThe payment has landed
A Safe transaction: another transaction takes its Safe nonce, at a finalized blockIts budget returns

The journal shows the rail of every payment.

Journal railPayment
Direct (evm)POST /v1/evm/cosign
x402 (x402)POST /v1/x402/cosign
evm-shadow, x402-shadowThe same rails from a shadow wallet

Fees

Fees apply to direct and x402 payments alike.

ItemRule
Rate0.5% by default of each payment that lands, rounded up to the next minor unit. The live rate is bps in Fee status
TokenFees accrue and settle per token, in the token that was paid
Settlement threshold1000000 minor units per token by default (1 unit of a 6-decimal token). The live value is settleAt
Expired authorizationsCost nothing. A fee accrues only when the payment's nonce is used on chain
Shadow walletsPay nothing
ExemptionsA platform agreement can exempt a node

Exchain cannot collect a fee without the agent's signature. A wallet with fees due waits; it is never debited.

Settle fees

Get the fee authorization

When the amount due in a token reaches settleAt, the next co-sign request in that token answers 402 fee_due and decides nothing. The body carries fee.amount, fee.payTo and fee.typedData: an EIP-3009 authorization from the Safe to Exchain's fee address for exactly the amount due, valid 300 seconds. For Ethereum USDT and Polygon USDT0, fee.typedData is a SafeTx calling the token's transfer(payTo, amount) at the Safe's current nonce.

Sign and send it

The agent signs the Safe message hash of fee.typedData (for a SafeTx, its SafeTx hash) and sends it:

POST /v1/x402/fees
{
  "walletId": "wal_01J9XK3M4N5P6Q7R8S9T0V1W2Z",
  "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": "0xSafe…", "to": "<fee.payTo>", "value": "1000005",
                 "validAfter": "0", "validBefore": "<now + 300>", "nonce": "0x8c1e…9f00" }
  },
  "agentSignature": "0x<130 hex>"
}

Send fee.typedData exactly as the 402 gave it. The attested quorum co-signs, and Exchain sends the transfer on the Safe's chain. The answer is the wallet's fee status, with the new settlement pending.

Retry the payment

Send the original co-sign request again.

ErrorMeaning
409 fee_not_dueNothing is due, or the wallet is not charged
400 fee_mismatchNot the authorization Exchain issued, or the wrong amount or payee
409 fee_mismatch, detail: "nonce_reused"The fee nonce was already used
409 fee_mismatch, detail: "safe_nonce"A SafeTx fee is not at the Safe's current nonce. Retry the payment for a new one
409 fee_unpayableThe fee cannot be paid
401 agent_signature_invalidThe agent signature does not recover to the agent
502 fee_settlement_failedThe relay failed
503 quorum_unavailableRetry later

Fee status

GET /v1/x402/wallets/{walletId}/fees returns:

FieldMeaning
chargedWhether the wallet pays fees
bpsThe rate in basis points (50 = 0.5%); 0 if not charged
settleAtThe amount due in one token that triggers 402 fee_due, minor units
accrued, pending, paid, dueFee totals of the wallet's first token, minor units
assets[]Each governed token: asset, symbol, decimals, accrued, pending, paid, due
payToExchain's fee address, when charged
settlements[]Each fee settlement of the first token: nonce, amount, validBefore (or safeTxHash for a Safe transaction), status (pending, paid or void)

On this page