ERC-7824 and Nitrolite: state channels audited for agent payments
An LLM gateway bills per token, not per request. That is the exact shape state channels were invented for. So we read ERC-7824, read the code that actually shipped under its name, and found they describe two different protocols.
Per-call settlement has a floor. x402 with EIP-3009 costs one signature per request and one on-chain transfer per settlement. Batching pushes the transfer out — that is what deferred settlement and Circle Gateway nanopayments do — but the payer still signs once per call, and the ledger of who owes what lives in the facilitator's database.
A state channel inverts that. Open once on-chain, then exchange co-signed balance updates off-chain for as long as the relationship lasts, and close once. Cost per update is two signatures and zero gas. For an agent streaming a million tokens through a router, that is the right cost curve.
ERC-7824 is the standard that claims this territory. Nitrolite is its reference implementation, now shipped as Yellow Network. This is what we found when we audited both.
The spec that never shipped
ERC-7824 exists as pull request #728 against the ethereum/ERCs repository. It was opened on 2024-11-22 by Louis Bellet with State Channels and the Layer-3 Foundation as co-authors. It is 483 lines in one file, ERCS/erc-7824.md. It has never been merged.
The bot that gates ERC merges posted the same day that the file "requires 1 more review from Editors." That review has not arrived. The author's last substantive comment on the thread is from 2025-04-01. A CI bot flagged errors in the branch on 2025-09-26. The PR was last touched on 2026-07-02 and is still open, still status: Draft. There is no canonical page for ERC-7824 on eips.ethereum.org, because nothing was merged to generate one.
The draft itself is a clean, conventional state-channel design. Channels are configured by a struct:
struct Channel {
address[] participants; // List of participants in the channel
address adjudicator; // Contract that validates state transitions
uint64 challenge; // Duration in seconds for dispute resolution
uint64 nonce; // Unique per participants + adjudicator
}
State carries a version, an Allocation[] array, an intent from {OPERATE, INITIALIZE, RESIZE, FINALIZE}, and participant signatures. A custody contract implements IChannel with create, join, close, resize, challenge and checkpoint. Application logic lives in a pluggable IAdjudicator that validates a candidate state against proofs. The draft ships a worked example — a Remittance adjudicator that checks a payer's allocation went down by exactly what the payee's went up.
The draft's own example opens a channel with challenge: 3600 and the comment // 1 hour challenge period. Hold that number.
What actually got deployed
The implementation lives at layer-3/nitrolite — the erc7824/nitrolite URL redirects there. It tagged v1.0.0 on 2026-02-17 and is at v1.4.1 as of 2026-06-18. Its contracts/src directory contains ChannelHub.sol, ChannelEngine.sol, EscrowDepositEngine.sol and EscrowWithdrawalEngine.sol.
There is no adjudicator. There is no IChannel, no join, no resize, no Allocation[]. Not one of the interfaces the ERC standardises appears in the deployed code.
What replaced it is documented in the repo's own protocol description. A channel is now strictly two parties: a User and a Node. Its state carries two per-chain sub-ledgers, homeLedger and nonHomeLedger, each tracking absolute allocations plus cumulative net flows. Cross-chain movement is a two-phase optimistic escrow, not an atomic bridge. A channel can migrate its home chain, swapping the two ledgers' roles mid-protocol. The mental model the document offers is precise and honest: "Off-chain protocol decides what should happen. On-chain contract enforces the latest authorized accounting state. Bridging is non-atomic but recoverable."
That is a materially different protocol from the one the ERC describes. Anyone who implemented ERC-7824 from the draft text would produce a contract that cannot talk to a single deployed Nitrolite node.
The hard numbers are constants in ChannelHub.sol, and they are the ones that matter for sizing agent exposure:
uint8 public constant VERSION = 1;
uint32 public constant MIN_CHALLENGE_DURATION = 1 days;
uint32 public constant MAX_CHALLENGE_DURATION = 7 days;
uint32 public constant ESCROW_DEPOSIT_UNLOCK_DELAY = 3 hours;
uint32 public constant MAX_DEPOSIT_ESCROW_STEPS = 64;
uint256 public constant TRANSFER_GAS_LIMIT = 100000;
uint64 public constant VALIDATOR_ACTIVATION_DELAY = 1 days;
Line 1398 of the contract requires def.challengeDuration to sit between MIN_CHALLENGE_DURATION and MAX_CHALLENGE_DURATION. The draft ERC's own example — a one-hour challenge period — would revert on every deployed hub.
For an autonomous agent that is not a detail. It is the exit latency. If the node stops co-signing, the agent's fastest unilateral path to its own money is 24 hours of challenge period, and the operator may configure up to seven days. Compare that to x402 over EIP-3009, where funds are never locked in the first place: the agent signs a transfer authorization per call and keeps custody of everything it has not spent.
The on-chain census
We checked what is actually live. Every mainnet deployment record in contracts/deployments/ points at the same address — 0x1a2f750170474d4c54f8d318d9d4343588b4c4d1 — across eight mainnet chain IDs: Ethereum (1), Base (8453), Polygon (137), BNB Smart Chain (56), Linea (59144), World Chain (480), Flare (14) and XRPL EVM Sidechain (1440000). Five testnets carry the same. All are tagged prod v1.3.0 and all were deployed on 2026-05-22, within about an hour of each other.
Reading the hub on Base confirms the record:
$ HUB=0x1a2f750170474d4c54f8d318d9d4343588b4c4d1
$ cast call $HUB 'NODE()(address)' --rpc-url https://mainnet.base.org
0xBffaA37E34FB9Aa11B23eb6cC939abBB45D6CCB6
$ cast call $HUB 'MIN_CHALLENGE_DURATION()(uint32)' --rpc-url https://mainnet.base.org
86400
NODE is an immutable set at construction. _requireValidDefinition rejects any createChannel whose definition names a different node. One hub, one counterparty, for everyone on that chain. This is not a marketplace of nodes; it is a single operator's rail with a public settlement contract underneath it.
Then we counted the transactions. Blockscout's Etherscan-compatible endpoint returns the external transaction list for a contract, and the four-byte selectors decode cleanly:
$ curl -s "https://base.blockscout.com/api?module=account&action=txlist\
&address=$HUB&startblock=0&endblock=99999999&sort=asc" \
| jq -r '.result[].input[0:10]' | sort | uniq -c | sort -rn
Base, from deployment on 2026-05-22 to the last transaction on 2026-09-13: 64 external transactions from 15 distinct senders. Broken down — 23 depositToChannel, 17 withdrawFromChannel, 16 createChannel, 3 depositToNode, 3 closeChannel, 1 registerNodeValidator, plus the deployment itself.
Ethereum mainnet: 13 transactions, 3 senders, 4 channels opened and 4 closed, last activity 2026-06-17. Polygon: 10 transactions, 3 senders, 2 opened and 2 closed, last activity 2026-06-17.
The more interesting absence is in the selector histogram. Across all three chains there is not a single challengeChannel, not a single checkpointChannel, and no escrow or migration call at all. The dispute path — the mechanism that makes the whole trust model work — has never been exercised on mainnet.
Session keys: the agent primitive, and where it stops
For an autonomous agent, the interesting contract is SessionKeyValidator.sol. It lets a wallet delegate signing authority to a hot key, which is exactly how you want an agent to hold spending power: root key in a vault, session key in the process.
The design is two-step and tidy. The participant signs a SessionKeyAuthorization struct — session key address plus a metadataHash covering "expiration timestamp, nonce, permissions" — under a dedicated type hash that prevents unrelated abi.encode(address, bytes32) signatures from being replayed as authorizations. The session key then signs the actual state. On-chain, both signatures are checked.
Two things about that contract matter more than the design.
First, its own header states the security model plainly: "Off-chain enforcement (the Node) should validate session key expiration and usage limits. On-chain validation only checks cryptographic validity." The metadataHash is a hash. The contract never opens it. Expiry, nonce and permissions are node policy, not consensus.
Second, and this is the finding that should change how you deploy:
function validateChallengeSignature(bytes32, bytes calldata, bytes calldata, address)
external pure returns (ValidationResult)
{
revert ChallengeWithSessionKeyNotSupported();
}
A session key can spend. A session key cannot challenge. If the node goes dark, the agent holding only its hot key has no on-chain exit — it must escalate to the root key that the whole delegation pattern existed to keep offline. The key that operates is not the key that can defend.
The off-chain allowance model has its own edges, documented in the repo's session keys reference. Allowances are per-asset caps declared at registration: [{"asset": "usdc", "amount": "100.0"}]. Exceed one and the node rejects with insufficient session key allowance. An empty array means zero spend. So far so good.
But the application field is optional and "defaults to clearnode if not provided" — and session keys registered under the application name clearnode "bypass spending allowance validation and application restrictions" with "full permissions." An integrator who omits one optional string mints a root key instead of a capped one. Only the expiry still binds.
Three more constraints worth encoding in any client: the scope parameter is documented but flagged "not yet implemented"; only one session key can be active per wallet-plus-application pair, so a second agent instance registering under the same application silently invalidates the first; and when you re-authenticate with an existing key, the allowances in the request "will be ignored" in favour of those stored at first registration, so you cannot tighten a cap by re-authenticating.
Set that against the alternatives. In x402 over EIP-3009, validAfter and validBefore are arguments to the token contract — the chain enforces the window. In Spend Permissions the period and cap are contract state. In ERC-7710 delegation caveats are enforced by enforcer contracts at redemption. Nitrolite's session-key limits are enforced by the counterparty's server.
What you are actually trusting
The project's security and limitations document is unusually candid, and it is the right place to start any exposure calculation. Its opening assessment: "the protocol in its current form is not fully trust-minimized."
Users must trust the node for liveness, cross-chain liquidity, cross-chain relay, timely enforcement, asset-symbol equivalence, and reorg-depth configuration. Two entries deserve reading twice.
On off-chain transfer routing: "the on-chain contract cannot enforce atomicity between two independent channel updates. A malicious node could apply the sender's state while withholding the receiver's credit, capturing the transferred funds." Every off-chain payment between two users of the same node is two separate channel updates, and nothing on-chain binds them together.
On the validator registry: the node operator chooses which signature validators the hub accepts. "A malicious or compromised node could register a validator that approves forged user signatures, then use it to create channels or close them without the user's knowledge." The defence is VALIDATOR_ACTIVATION_DELAY — one day. The document is explicit about what that buys: "Once registered, a validator cannot be deactivated — the 1-day window is the entire response budget." Users are told to watch the ValidatorRegistered event and revoke ERC-20 approvals on sight.
For an agent, "watch an event for 24 hours and react" is a monitoring service, not a footnote. And the known-limitations list confirms none of that exists yet: no validator network, no watchtower services, no formal verification, no trustless off-chain state operations. All are on the roadmap.
The settlement room
The most agent-native thing in the repository is not a contract. On 2026-07-15 the project merged an agent-skills/ directory containing yellow-settlement-room — a skill file written for an AI agent to read and act on, teaching it to open a multi-party app session over the Yellow node.
The model it describes is genuinely useful and not something x402 offers. An app session holds N participants with per-participant signatureWeight and a quorum; it opens with zero allocations; depositors commit their own funds; participants reallocate off-chain with no gas per step; the final split is co-signed once. As the skill puts it: "A payment rail moves value from one payer to one payee; a swarm of agents settling over a rail needs a separate escrow per pair. One session settles all of them at once."
The skill's design rules are strict in the right places. One agent, one key, one process. The proposer packs a state hash with packAppStateUpdateV1, ships it to each signer, and collects signatures until the summed weight meets quorum — and the skill notes that "the protocol carries no transport for moving the hash out and the signatures back; that is the integrator's to build." The participant set is immutable after creation.
Its trust boundary section is worth quoting whole, because it is the clearest statement of the trade in the entire project: no agent in the session can take another's allocation, since every change needs quorum. But "funds become enforceable on-chain only once released back to a channel as a node-co-signed state. If the node will not co-sign, there is no on-chain path out of the session." And: "A session has no dispute mechanism, challenge, or timeout of its own. If quorum is never reached, funds stay in the session."
So the channel layer has a 1-to-7-day challenge. The app-session layer on top of it has no challenge at all. The exposure ceiling is what a participant deposited — which is the number to size, deliberately, per session.
Two ecosystems that do not touch
We searched the entire layer-3/nitrolite repository for x402. Zero hits. Not in the contracts, not in the SDKs, not in the docs, not in the agent skill. The most complete state-channel implementation in the EVM ecosystem and the dominant agent-payment protocol have no integration surface between them, in either direction.
Naming drift compounds the distance. erc7824.org — the site the ERC draft itself links as its documentation — still tells developers npm install @erc7824/nitrolite and points them at wss://clearnet.yellow.com/ws. That npm package's latest release is 0.5.3, published 2025-12-18. The SDK the repository actually ships is @yellow-org/sdk, at 1.4.0 on npm, with a companion @yellow-org/sdk-compat described in the repo's own llms.txt as a "migration bridge from v0.5.3." The sandbox endpoint in the current agent skill is wss://nitronode-sandbox.yellow.org/v1/ws. Clearnode has been renamed Nitronode. Even the repository's llms.txt points at sdk/ts-mcp and sdk/mcp-go directories that do not exist — the tree has sdk/mcp and sdk/go.
A developer who finds ERC-7824 through the ERC repository, follows the discussions-to link, lands on the documentation site and installs what it recommends, gets a nine-month-old package for a protocol generation that has been superseded twice. That is a discovery problem, and for a standard whose entire value is interoperability, it is the expensive kind.
The project is aware. It ships deterministic drift guards — ABI drift tests comparing checked-in SDK ABIs against Foundry artifacts, RPC and DTO drift tests, and a runtime smoke that boots a local Nitronode on ws://127.0.0.1:7824/ws and exercises ping, getConfig, getAssets and getAppSessions. The engineering discipline inside the repo is real. It just has not reached the public front door.
What it means for LLM4Agents
The cost argument for channels is correct and we should say so plainly. A gateway relationship is long-lived and high-frequency — precisely the profile where per-call settlement overpays and a channel wins. If an agent runs a hundred thousand completions through us, signing a hundred thousand payment authorizations is the wrong shape, and the per-token granularity a channel allows is strictly better than rounding every call up to a minimum charge.
But the trust ledger does not currently favour adopting it as the default rail, and the reasons are specific rather than vague.
The minimum challenge duration is one day. x402 over EIP-3009 locks nothing: an agent's unspent balance stays in its own wallet, and the worst case of a gateway going dark is a failed request. Under a channel, the worst case is 24 hours minimum before a unilateral exit completes, and that exit requires the root key, because SessionKeyValidator refuses to validate challenge signatures. An agent designed around a hot session key has no self-service recovery. That inverts the property we care most about — agents that operate unattended must be able to recover unattended.
The spending caps an agent operator would rely on are enforced by the counterparty. That is a different security class from the on-chain caps we have been tracking in Spend Permissions and ERC-7710 enforcers, and it should not be described to customers as equivalent. The application: "clearnode" default that silently produces an uncapped key is the kind of footgun that belongs behind a wrapper, not in an integration guide.
And the standard is not a standard yet. Building an IChannel-shaped integration against the ERC-7824 draft buys nothing, because no deployed hub implements it. Building against @yellow-org/sdk v1 is building against one vendor's protocol with a public contract underneath — a legitimate choice, but it should be made with that name on it.
Where channels do fit our stack today is narrower and more defensible: a settlement layer between agents rather than between an agent and us. The app-session model — N agents, weights, quorum, one final split — solves a problem x402 genuinely does not, which is multi-party settlement without pairwise escrows. An orchestrated swarm splitting the cost of a shared research job is a real instance of that shape. The gateway does not have to be a channel participant to be useful inside one.
Staying on the frontier
Concrete order of work, most valuable first.
Publish the exit-latency comparison, with numbers
We already run x402 as the primary rail. Document what an agent's worst-case time-to-funds is under each option: zero for per-call EIP-3009, 1 to 7 days under a Nitrolite channel, unbounded inside an app session with no quorum. Exit latency is the number agent operators should be comparing rails on, and nobody is publishing it. Make it a page, keep it current, cite the constants.
Prototype the settlement room against the sandbox
Build one multi-agent job on wss://nitronode-sandbox.yellow.org/v1/ws using @yellow-org/sdk v1: three agents, equal weights, unanimous quorum, one shared budget, one final split. Learn where the signature-collection transport hurts, because the protocol does not provide one and we would have to. That is a week of work and it settles whether app sessions belong in our cost-sharing story at all.
Make the delegation audit a repeatable check
The finding that mattered here — a hot key that can spend but not exit — came from reading one revert in one validator. Every delegation rail we assess should get the same three questions: what does the chain enforce, what does the counterparty enforce, and can the delegated key sign its own recovery? Run it against every new agent-payment rail before it reaches a customer-facing page.
Ship a skill, not just an SDK
Yellow shipped a SKILL.md that teaches an agent to use its protocol correctly, with the trust boundary stated in the file and an instruction not to soften it. That is the right distribution format for an agent-facing rail, and it is cheap. Our MCP surface should be paired with a skill that encodes our own limits — budget caps, fallback order, what happens on a 402 — so an agent integrates correctly on the first attempt instead of guessing.
Watch for the watchtower
Three items on Nitrolite's roadmap would change this assessment: a validator network, watchtower services, and on-chain enforcement that removes the node-liquidity trust assumption. If watchtowers ship and session keys gain a challenge path, the exit-latency objection largely dissolves and channels become a serious candidate for long-lived gateway relationships. Re-run this audit when the first of those lands.
The honest summary: ERC-7824 named a real problem and the Nitrolite team built a working answer to it, with a security document more candid than most audited protocols publish. What they have not got is a merged standard, a second node operator, an exercised dispute path, or a delegated key that can defend itself. For an LLM gateway billing autonomous agents, those four gaps are exactly the ones that matter — so we track it closely and route through it not at all, yet.
Pay per token, keep custody
x402 over EIP-3009 on an OpenAI-compatible gateway. Nothing locked, nothing to exit.
Register your agent