Real-time execution
without leaving Monad
A contract hands part of its storage to an ephemeral node that confirms a call in 0.26 ms and no gas, then commits the diffs back. Nothing is bridged. Monad stays the source of truth.
- per action
- 256 µs
- median, on chain
- actions per block
- 1,172
- 300 ms block
- gas per action
- 4,356
- amortised
- source of truth
- Monad
- nothing bridged
- Solidity 0.8.28
- Foundry
- Rust · revm
- 111 tests green
- Optimistic security
- Standard eth_* RPC
- Monad testnet
- Solidity 0.8.28
- Foundry
- Rust · revm
- 111 tests green
- Optimistic security
- Standard eth_* RPC
- Monad testnet
The lower lane is drawn solid because 1,172 marks across this width would land under a pixel each. Magnified below until they separate.
Through Interlude
no gas, no pop-up, no wait
Straight to Monad
gas and a signature, every time
Not an animation of the idea. Both lanes run at the speeds a run measured on Monad testnet: 256 µs an action on the left and 332.3 ms on the right, medians. The right lane moves one interaction at a time because each one needs the result of the last, so they cannot share a block. Both lanes reach the same total, and Monad stays the source of truth — Interlude commits every diff back to it. What changes is when the person sees it land.
Some apps are not slow because the chain is slow.
They are slow because they were written for a chain at all. Interlude does not make Monad faster. It moves the hot loop somewhere with no blocks, no gas and no signatures, and keeps settlement exactly where it was.
Block time is a floor, not an average
A fast L1 still confirms on a block boundary. For a chess clock, a tap-trading UI or a game tick, the wait is the product, and no amount of throughput removes it.
Every action is a signature
Users will not approve a wallet pop-up sixty times a minute. Anything interactive ends up custodial or off-chain, and both give up what put it on-chain in the first place.
Gas prices out the small action
A move, a click, a cancelled order: each is worth fractions of a cent. On-chain they cost more than they are worth, so the interesting interactions never get written at all.
The usual answer is an L2, which means bridging assets, a separate liquidity island and a contract that no longer talks to the ecosystem it came from. Interlude is not that. A delegation covers one partition of one contract, lasts as long as a session, and ends with the contract back on Monad, natively composable and never moved.
Four steps, and only the first one is yours.
A delegation is a session: it opens, it runs, it commits, it closes. Your contract stays deployed at the same address on Monad throughout.
- 01
Delegate
myGame.delegateAll()The hub locks the chosen slots on Monad and hands them to the node. From here, on-chain writes to those slots revert. There is exactly one writer, so the two copies can never diverge.
- 02
Execute
eth_sendTransaction → nodeUsers hit the ephemeral node instead of Monad. Same bytecode, same addresses, standard eth_* RPC, so viem and wagmi work unchanged. No blocks, no gas, no pop-ups.
- 03
Commit
hub.commit(batch, diffs, sig)On an interval, the node signs the slots it changed and their expected old values. The hub checks the permissions and sequencing, the app checks the old values, and the state lands on Monad.
- 04
Settle
undelegate → releaseStakeThe contract unlocks immediately and is usable again. The validator's stake stays reserved through the challenge window, so committing a bad batch and running is not an exit.
If the node stops committing, anyone can force the session closed and the contract unlocks. A dead node cannot freeze an app, and that guarantee does not depend on anyone's good behaviour, including ours.
Only the delegated slots are locked. The rest of your contract keeps working on Monad, and once a session ends the whole thing is ordinary Monad state again: no bridge, no wrapped assets, no separate deployment.
Three touch points, and none of them mention a validator.
Interlude is infrastructure, not a framework. You inherit one contract, call one function to go live, and point your client at a different RPC.
Delegated state is a named slot behind a constant. There is no storage variable to assign to, so the lock cannot be bypassed by forgetting a modifier.
import {Delegatable} from "interlude/Delegatable.sol";
import {Delegated} from "interlude/libraries/Delegated.sol";
contract MyGame is Delegatable {
using Delegated for Delegated.Uint256Slot;
Delegated.Uint256Slot internal constant SCORE =
Delegated.Uint256Slot.wrap(keccak256("MyGame.score"));
constructor(IInterludeHub hub_) Delegatable(hub_) {
_registerGlobal(SCORE);
}
// No modifier. The lock lives inside Delegated.add.
function play() external {
SCORE.add(1);
}
function score() external view returns (uint256) {
return SCORE.get();
}
}Delegate the whole contract, one room, or one user.
A delegation covers one partition of one app, and how you register your state decides what a partition is. The mechanism is identical in all three cases.
_registerGlobal(x)■ on the node
The whole app
Everything moves together. Right whenever the fast path touches shared state: a chess board, an order book, a world tick.
_registerPerKey(m)■ on the node□ still on Monad
One instance
Room 42 runs on a node while room 43 stays on Monad. Instances must never touch each other's data.
_registerPerKey(m)■ on the node□ still on Monad
One user
Same mechanism with an address as the key. A tap-trading user delegates only their own balance, so nobody else can click for them.
Per-key delegation hands over the exact derived slots for that key and never the mapping itself, so a validator running room 42 gains no rights over any other room.
On Solana the runtime enforces the lock. On the EVM, nothing does.
This is the one real obstacle to porting the ephemeral-rollup model, and it is worth being precise about how Interlude gets around it.
When MagicBlock delegates on Solana, ownership of the account moves to the ephemeral program. After that the runtime itself rejects any write from the base layer, for free, with nothing for a developer to remember.
The EVM has no equivalent. Storage is inseparable from its contract, ownership cannot be transferred, and a transaction never declares what it will touch. If a delegated variable is still writable on Monad while the node is also writing it, the two copies diverge and the commit overwrites real user state.
The obvious fix is a modifier on every function that writes delegated state. That works right up until someone forgets one, and the failure is silent: you find out once the two states have already drifted.
So we made the unguarded write unwritable
Delegated state lives at a named slot reached through a constant handle. No Solidity variable is bound to it, so there is nothing to assign to and the only write path runs through a function that carries the lock. Forgetting to register a variable is loud, because the first write reverts instead of quietly escaping.
Naming slots by hash removes two more bug classes on the way: no collision with the compiler's own slot assignment, and no variable packing. Packing would put two values in one slot, which slot-level delegation cannot express.
uint256 public score;
// Guarded. Fine.
function play() external whenNotDelegated {
score += 1;
}
// Somebody added this later.
// Compiles. Ships. Corrupts state.
function reset() external {
score = 0;
}Delegated.Uint256Slot internal constant SCORE =
Delegated.Uint256Slot.wrap(keccak256("MyGame.score"));
// Guarded, because there is no other way in.
function play() external {
SCORE.add(1);
}
function reset() external {
SCORE.set(0);
}Optimistic, permissioned, and honest about both.
Interlude has no cryptographic fraud proof. Security rests on money at risk plus a resolver who can rule against the validator. Here is exactly what that buys, and what it does not.
A bond, reserved per session
The validator stakes money up front. One bond backs many sessions, and the slice behind a live session cannot be withdrawn.
A challenge window after the exit
The stake stays locked after a session ends, so committing a bad batch and leaving next block is not an exit route.
An independent resolver
Disputes are judged by a party the validator cannot be. It replays the batch and either slashes the stake or dismisses the claim.
A forced close by anyone
Miss the commit interval and any address can end the session. A dead node can never freeze an app, whoever runs it.
v1 is permissioned on purpose. Interlude operates the validator and curates who may run one or judge a dispute. So the trust assumption right now is us, not a bond. That is the same place MagicBlock is, and we would rather write it down than let a bond imply otherwise.
The bond and challenge machinery runs anyway. It puts a real cost on our own misbehaviour, and it means opening the validator set later is a governance change rather than a redesign.
A challenge can only decide anything if the batch replays identically. A delegation pins a base block for every external read and each commit carries the execution clock it ran under, signed and monotonic. But the node has to honour that, and the node is still a skeleton. Until it does, the reference exists and nothing checks it.
Also open: one resolver rather than a committee, and a state root that is recorded but never verified.
Numbers from a run, not from a pitch.
50 actions, each one needing the result of the one before it, sent three ways. The script is in the repo and the figures on this page are generated by it, so anyone can produce them again and disagree with them.
| wall time | per action | chain txs | chain gas | |
|---|---|---|---|---|
| direct on chain | 17.37 s | 347.4 ms | 50 | 5,433,750 |
| batched on chain | 353.1 ms | 7.1 ms | 1 | 152,299 |
| interlude | 13.7 ms | 274 µs | 1 | 217,814 |
Against sending them to the chain one at a time, Interlude finishes 1268x faster and leaves 25x less gas behind, because 50 transactions become one. The middle row is the number that matters more, and it is the reason the rest of this section exists.
Gas is not the argument
The batch settles for 152,299 and the commit for 217,814. On Monad the commit can be the more expensive of the two: a cold SLOAD costs 8100 there against 2100 on Ethereum, and the declared gas limit is charged whether or not it is used, so the commit's storage verification can outweigh a batch's cheap arithmetic. Anyone claiming this saves gas against a developer who can write a batch function is selling something.
The batch cannot be built
The middle row needed all 50 calls up front, and only had them because the script picked them in advance. Someone choosing their next move from the result of the last one has nothing to batch. Their only other option is the top row: 347.4 ms of waiting, every single time.
Do not quote the big number
Both rows were measured from a client sitting beside its endpoint, so neither paid for the internet. A real user is tens of milliseconds away and that gets added to both. The ratio someone would actually feel is nearer 11x, at 300 ms a block and 30 ms of round trip. Still the difference between reacting and waiting, and not three orders of magnitude.
Transport is 32% of an action. The remaining 175 µs is real work: signing on the client, recovering the signer on the node, and running the call. Execution is a few microseconds of that — the rest is one ECDSA operation at each end, which no amount of tuning removes. The wallet path costs 81 µs more because it asks twice for something the node already knew, and that is the one round trip worth removing. It is also the whole reason to ship an SDK rather than tell people to point a wallet at it.
- Measured on
- Monad testnet
- Date
- 2026-09-03
- Actions
- 50
- The commit, on chain
- 0xf58c8a28…ae830827
- Reproduce
- scripts/bench-monad.sh
This is not an L2, and not a sidechain.
Nothing is bridged and nothing is wrapped. The contract never leaves Monad. It just spends a while with one drawer of its storage open somewhere faster.
Where do your assets live?
- Rollup or sidechain
- Bridged to another chain
- Interlude
- Never leave Monad
What is delegated?
- Rollup or sidechain
- Everything, permanently
- Interlude
- One partition, for one session
Composability with Monad
- Rollup or sidechain
- Broken until you bridge back
- Interlude
- Native between sessions
Deployment
- Rollup or sidechain
- A second deployment to maintain
- Interlude
- The same contract, same address
Ending it
- Rollup or sidechain
- A withdrawal, with a delay
- Interlude
- One call, unlocked immediately