Ephemeral execution layer

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
one Monad block · 300 msmeasured on Monad testnet
Monad
1 confirmation
Interlude
1,172 confirmations

The lower lane is drawn solid because 1,172 marks across this width would land under a pixel each. Magnified below until they separate.

the same span, magnified to 2.6 msone mark per action · 256 µs each, median
Monad
still waiting
Interlude
10 confirmations
Monad is not slow. 300 ms is among the fastest finality anywhere, and it is still a floor: a chess clock, an order book or a game tick has to wait for it. Interlude does not make Monad faster. It moves the loop that cannot wait somewhere with no blocks, and settles the result back on Monad.
Replay0 interactions sent

Through Interlude

no gas, no pop-up, no wait

0confirmed

Straight to Monad

gas and a signature, every time

0confirmed

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.

01The problem

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.

01

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.

02

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.

03

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.

02How it works

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.

one session, start to finishsame contract, same address, throughout
MONADINTERLUDEthese slots locked · on-chain writes revertdelegatehub locks the slotscommitdiffs, signedcommitsettleunlocks immediately
Nothing is bridged and nothing is wrapped, which is why Monad's rail never breaks. What the delegation moves is the right to write a chosen set of slots, for as long as the session lasts. Everything else in the contract keeps answering on Monad the whole time.
  1. 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.

  2. 02

    Execute

    eth_sendTransaction → node

    Users 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.

  3. 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.

  4. 04

    Settle

    undelegate → releaseStake

    The 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.

Liveness

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.

Composability

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.

03Build

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.

MyGame.sol
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();
    }
}
04Granularity

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.

A_registerGlobal(x)
appappappapp

■ 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.

B_registerPerKey(m)
42434445

■ 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.

C_registerPerKey(m)
0xa10xb20xc30xd4

■ 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.

05The hard part

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.

the old way: one omission is a silent bug
uint256 public score;

// Guarded. Fine.
function play() external whenNotDelegated {
    score += 1;
}

// Somebody added this later.
// Compiles. Ships. Corrupts state.
function reset() external {
    score = 0;
}
Interlude: there is no variable to touch
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);
}
06Security

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.

01

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.

02

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.

03

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.

04

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.

What you are actually trusting today

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.

Still open

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.

Read the full security model, including every fixed bug
07Measured

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 timeper actionchain txschain gas
direct on chain17.37 s347.4 ms505,433,750
batched on chain353.1 ms7.1 ms1152,299
interlude13.7 ms274 µs1217,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.

Where an action's time goes
one empty round trip to the node81 µs
an action through the SDK, one round trip256 µs
the same action the way a wallet must, send then read receipt337 µs

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
0xf58c8a28ae830827
08To be clear

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