IOU + EIP-712 domain
The IOU is an EIP-712 typed-data message. The agent signs it with an EOA private key; the middleware recovers and compares the signer against the agentAddress in the envelope.
Domain
{
name: "Tollgate",
version: "1",
chainId: 8453, // Base
verifyingContract: "0x<ANCHOR_ADDRESS>", // packages/core/src/constants.ts
salt: "0x...keccak256('Tollgate-v1-production')"
}verifyingContract is the anchor contract — deployed via CREATE2 so the same address is reachable on every supported chain. salt is keccak256(utf8("Tollgate-v1-production")) (AUDIT #10 separator). Use buildDomain(chainId) from @tollgatepay/core/eip712 to build non-default-chain domains.
Typed-data
types: {
IOU: [
{ name: "developer", type: "address" },
{ name: "amountMicros", type: "uint256" },
{ name: "chainId", type: "uint256" },
{ name: "nonce", type: "uint256" },
{ name: "apiKeyHash", type: "bytes32" },
{ name: "path", type: "string" },
{ name: "deadline", type: "uint256" }
]
},
primaryType: "IOU"Fields
developer— payout address (msg.senderat settlement).amountMicros— cumulative micro-USDC for this(agent, developer, path)tuple. Monotonic.chainId— the chain this IOU settles on (AUDIT #10).nonce— strictly monotonic per tuple.apiKeyHash—HMAC-SHA256(apiKey, developerSalt), 32 bytes.path— canonicalized path (server-supplied in the 402).deadline— unix seconds. ≤ now + 120s on issuance.
Wire envelope
Agents base64-encode a JSON envelope in x-tollgate-iou:
{
"version": "iou-v1",
"agentAddress": "0x...",
"developerAddress": "0x...",
"chainId": "8453",
"amountMicros": "150000",
"nonce": "3",
"apiKeyHash": "0x...",
"path": "/api/premium",
"deadline": "1744816000",
"signature": "0x..."
}Every uint256 is a decimal string so JSON stays safe for clients that cannot represent uint256 as a number. Use encodeIOUHeader / parseIOUHeaderSafe from @tollgatepay/core/eip712 for the codec.