Checks on a Channel
The math and mechanism behind otomat-channel, where a thousand calls settle as one

The single-payment scheme in x402 is clean and complete: one request, one signed transaction, one settlement. For a call that happens once, that is exactly the right shape. Pay the coin, open the door, take the dish.
But agents do not make one call. An agent working through a task might hit the same paid endpoint hundreds of times in a minute. Settling every one of those on chain is correct but wasteful: hundreds of transactions, hundreds of fees, hundreds of trips to the network for a session that is really one relationship between one caller and one server.
The otomat-channel scheme exists for that case. It borrows an idea that has been proven for years on Bitcoin's Lightning Network and ports it, carefully, to Solana. The idea is simple to state and subtle to make safe: do the accounting off chain, and settle only the final number.
The shape of a channel
A channel has three phases: open, use, and close.
Open. The caller deposits funds into an escrow account controlled by an on-chain program. This is one transaction, and it is the only mandatory on-chain write until the very end. The deposit is the ceiling on everything that follows; the channel can never pay out more than what was locked.
Use. For each call, the caller does not send a transaction. Instead it signs a tiny off-chain message — a check — that says, in effect: "of my deposit, the total I now owe you is X." Crucially, the number is cumulative, not per-call. The tenth call's check does not say "pay 2 more cents"; it says "the running total is now 20 cents." Each new check supersedes the last.
Close. When the session ends, only the most recent check is submitted to the program. The program moves that cumulative amount from escrow to the server and refunds the rest to the caller. A thousand calls, two on-chain transactions: the open and the close.
The check
A check is the whole trick, so it is worth seeing exactly. It is a small structured message and a signature over it:
{
"channelPda": "<escrow account address>",
"cumulativeAmount": "200000",
"nonce": 42,
"expiry": 1780000000
}- channelPda binds the check to one specific escrow account. A check for one channel is meaningless against another.
cumulativeAmountis the running total owed so far, in the token's smallest unit.nonceis a counter that increases with every check.expiryis the time after which the check is no longer valid.
The caller signs the canonical bytes of that message with its Ed25519 key. The signature is what turns a claim into a promise: only the caller could have produced it, and it cannot be altered without breaking.
Why the math is safe
Off-chain accounting sounds dangerous. The safety comes from three rules, each small, that together leave no room to cheat.
Rule one: the amount only goes up. Every accepted check must carry a cumulativeAmount at least as large as the last one. The server never accepts a check that lowers the total, because that would be the caller trying to walk back what it already owes. Since the total only rises and is capped by the deposit, the server always holds a check for the true amount owed, and the caller can never be charged more than it locked.
Rule two: the nonce only goes up. Each check must have a strictly higher nonce than the previous one. This makes the checks ordered and makes "the latest check" a well-defined thing. It is what stops an old check from being replayed. If someone tries to settle with check number 20 after check number 42 exists, the program sees the stale nonce and rejects it.
Rule three: expiry bounds the whole thing. No check is valid forever. If the server disappears, the caller waits out the expiry and reclaims the deposit. If the caller disappears, the server settles with the last check it holds. Neither side can strand the other's money indefinitely.
Put together: the server always has a signed, monotonically increasing claim on a bounded escrow, and the newest such claim always wins. There is no state where one side can profitably lie.
Verifying a signature inside a program
The hardest part is the last one, and it is where the on-chain program earns its keep. When the final check is submitted, the program cannot simply trust that the signature is valid. It has to check it. But a Solana program has no built-in verb for "verify this Ed25519 signature."
The standard pattern, and the one Otomat uses, leans on Solana's native Ed25519 program together with the instructions sysvar. The signature verification is placed as its own instruction ahead of the channel-close instruction in the same transaction. The native program verifies the signature. Then, inside the close instruction, the Otomat program reads back the earlier instruction through the sysvar and confirms three things:
- 1The signature that was verified was made by the channel's registered caller.
- 2The message that was signed rebuilds, byte for byte, to the exact check the caller is trying to settle — same channel, same amount, same nonce.
- 3That amount does not exceed the deposit, and that nonce beats the stored one.
Only when all three hold does escrow move. The program does not take the caller's word for the signature; it reconstructs the signed message on chain and compares. If the rebuilt message and the verified message disagree by a single bit, the settlement fails. This is what makes an off-chain check enforceable: the chain has the final say, and it re-derives everything it needs to say it.
pub fn close_channel(
ctx: Context<CloseChannel>,
cumulative_amount: u64,
nonce: u64,
signature: [u8; 64],
) -> Result<()> {
// 1. read the Ed25519 verify instruction back through the instructions sysvar
// 2. rebuild the signed message from (channel, cumulative_amount, nonce, expiry)
// 3. require: rebuilt message == verified message, signed by channel.payer
// 4. require: cumulative_amount <= channel.deposit
// 5. require: nonce > channel.nonce
// 6. move cumulative_amount from escrow to payee, refund remainder to payer
// 7. close the channel account and reclaim rent
}What the caller actually gains
The economics are the point. A high-frequency agent that would otherwise pay a network fee on every single call now pays it twice per session, no matter how many calls it makes in between. The per-call cost of the payment mechanism falls toward zero, which leaves only the price of the resource itself. That is what makes charging a fifth of a cent per call genuinely workable rather than a rounding error swallowed by overhead.
The image, again, is the Automat. Opening a channel is putting a stack of coins on the counter and saying "run a tab." Every call slides a coin across without a trip to the register. Closing the channel is settling the tab once, at the end, for the exact total. The doors kept opening the whole time. The register only rang twice.