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
| Node | Chains |
|---|---|
| Mainnet | Base, Ethereum, Arbitrum, Optimism, Polygon |
| Preprod | Base 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
| Property | Value |
|---|---|
| Contract | Stock SafeL2 1.4.1. No custom contract |
| Owners | The agent address plus one address per member of the attested quorum |
| Threshold | All owners |
| ERC-1271 | CompatibilityFallbackHandler 1.4.1 |
| Module | The 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 |
| Guard | Empty |
| Address | CREATE2 of exactly this setup. Any other address is refused safe_mismatch |
| Tokens | Up to 16 listed tokens on the Safe's chain, set at registration |
| Deployment | Exchain deploys the Safe and pays the gas |
GET /v1/x402/signers returns what a Safe must use, for your node's network:
| Field | Contents |
|---|---|
network | preprod or mainnet |
chainId | The network's Base chain: 84532 or 8453 |
chainIds | The 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 |
safe | version, 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.
- 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. - Exchain checks the payment against the mandate. If it fits, each member of the attested quorum signs the same hash.
- Exchain packs the owners' 65-byte signatures into one Safe signature, sorted by owner address.
- 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.
| Method | Valid for | batchId |
|---|---|---|
EIP-3009 TransferWithAuthorization | At most 600 seconds | The authorization nonce |
Permit2 PermitWitnessTransferFrom | At most 600 seconds | The Permit2 nonce, 32 bytes big-endian |
Safe transaction (SafeTx) | Until the Safe's nonce moves past it | The 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
| Item | Rule |
|---|---|
| Rail | x402 only. Direct payments refuse Permit2 (body_mismatch: method) |
| Spender | x402ExactPermit2Proxy only |
| Approval | At deployment, the Safe approves Permit2 for permit2Cap of each governed token, set at registration |
| Default | permit2Cap is 0: Permit2 payments cannot settle until you register with a cap |
| Ethereum USDT, Polygon USDT0 | Permit2 is their only x402 method |
Relay
Direct payments only. Send relay: true with POST /v1/evm/cosign.
| Answer | Meaning |
|---|---|
relayed.txHash | Exchain sent the transaction and paid the gas |
relayError | Exchain 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.
| Event | Result |
|---|---|
| The authorization's nonce is used on chain | The 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 s | Its budget returns |
A Safe transaction: the Safe logs ExecutionSuccess for its hash | The payment has landed |
| A Safe transaction: another transaction takes its Safe nonce, at a finalized block | Its budget returns |
The journal shows the rail of every payment.
| Journal rail | Payment |
|---|---|
Direct (evm) | POST /v1/evm/cosign |
x402 (x402) | POST /v1/x402/cosign |
evm-shadow, x402-shadow | The same rails from a shadow wallet |
Fees
Fees apply to direct and x402 payments alike.
| Item | Rule |
|---|---|
| Rate | 0.5% by default of each payment that lands, rounded up to the next minor unit. The live rate is bps in Fee status |
| Token | Fees accrue and settle per token, in the token that was paid |
| Settlement threshold | 1000000 minor units per token by default (1 unit of a 6-decimal token). The live value is settleAt |
| Expired authorizations | Cost nothing. A fee accrues only when the payment's nonce is used on chain |
| Shadow wallets | Pay nothing |
| Exemptions | A 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:
{
"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.
| Error | Meaning |
|---|---|
409 fee_not_due | Nothing is due, or the wallet is not charged |
400 fee_mismatch | Not 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_unpayable | The fee cannot be paid |
401 agent_signature_invalid | The agent signature does not recover to the agent |
502 fee_settlement_failed | The relay failed |
503 quorum_unavailable | Retry later |
Fee status
GET /v1/x402/wallets/{walletId}/fees returns:
| Field | Meaning |
|---|---|
charged | Whether the wallet pays fees |
bps | The rate in basis points (50 = 0.5%); 0 if not charged |
settleAt | The amount due in one token that triggers 402 fee_due, minor units |
accrued, pending, paid, due | Fee totals of the wallet's first token, minor units |
assets[] | Each governed token: asset, symbol, decimals, accrued, pending, paid, due |
payTo | Exchain'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) |