All articles
solanamagicblockrollupsgaming

MagicBlock: ephemeral rollups for real-time Solana, 10ms blocks and all

MagicBlock's ephemeral rollups extend Solana with 10ms block times via delegated accounts. Here's the architecture, the delegation flow, and when to use it.

Solana ships ~400ms slots — fast for L1, slow for real-time gaming or in-fight order updates in a perpetual exchange. MagicBlock is the protocol that gives Solana ~10ms block times when needed, via a primitive called ephemeral rollups (ERs).

Critically, it's not a separate L2. It's a temporary delegation layer that runs your existing Solana accounts in a faster runtime, then commits state back to mainnet when you're done.

The delegation flow

text
1. Account lives on Solana mainnet (owned by your program)

2. You call MagicBlock's delegation_program to "delegate" the account
   to an ephemeral rollup
   ↓
   The mainnet account becomes write-locked
   A copy of the account state appears in the ER

3. Inside the ER, the same program logic runs against the account
   at ~10ms block times, free of transaction fees
   Many txs per second update the account state

4. When the session ends, MagicBlock posts the final account state
   back to mainnet via a commit transaction
   ↓
   The mainnet account is unlocked + updated

The user's perspective: connect wallet, play / trade / move at console-tier latency, then settle to L1 when finished. The program's perspective: the same code runs in both places without changes — MagicBlock's runtime is bytecode- compatible with Solana's SVM.

What makes it ephemeral

The ER doesn't have persistent state of its own. It exists only while accounts are delegated to it. When all delegations end, the ER goes idle — the canonical state lives on Solana mainnet at all times. No bridges, no fragmented liquidity, no cross-chain risk.

Compared to a traditional rollup (Arbitrum/Optimism-style), this is a fundamentally different architecture:

text
                Traditional rollup           MagicBlock ER
────────────────────────────────────────────────────────────────
State            Lives in the rollup         Lives on Solana
Bridge required  Yes (deposit/withdraw)      No (delegate/commit)
Canonical chain  Rollup, until withdrawn     Always Solana
Block time       ~1-12s (varies)             ~10ms
Compatibility    EVM (different runtime)     SVM (same runtime)
Delegation       Long-lived (forever)        Per-session, returnable

The delegation_program

rust
// Inside your program, mark an account as delegatable
use ephemeral_rollups_sdk::cpi::delegate_account;
use ephemeral_rollups_sdk::ephem::commit_and_undelegate_accounts;

#[derive(Accounts)]
pub struct Delegate<'info> {
    #[account(mut)]
    pub payer: Signer<'info>,
    /// CHECK: the account you're delegating
    #[account(mut)]
    pub pda: AccountInfo<'info>,
    /// CHECK: validated by SDK
    pub owner_program: AccountInfo<'info>,
    /// CHECK: validated by SDK
    pub buffer: AccountInfo<'info>,
    /// CHECK: validated by SDK
    pub delegation_record: AccountInfo<'info>,
    /// CHECK: validated by SDK
    pub delegation_metadata: AccountInfo<'info>,
    /// CHECK: validated by SDK
    pub delegation_program: AccountInfo<'info>,
    pub system_program: Program<'info, System>,
}

pub fn handle_delegate(ctx: Context<Delegate>, pda_seeds: Vec<Vec<u8>>) -> Result<()> {
    delegate_account(
        &ctx.accounts.payer,
        &ctx.accounts.pda,
        &ctx.accounts.owner_program,
        &ctx.accounts.buffer,
        &ctx.accounts.delegation_record,
        &ctx.accounts.delegation_metadata,
        &ctx.accounts.delegation_program,
        &ctx.accounts.system_program,
        pda_seeds,
        /* commit_frequency_ms */ 30_000,
        /* validator */ None,
    )?;
    Ok(())
}

// Inside the ER, run as many updates as you want:
//   update_my_state(...)  // ~10ms confirmation, free

// When done, undelegate to push state back to mainnet:
pub fn handle_undelegate(ctx: Context<Undelegate>) -> Result<()> {
    commit_and_undelegate_accounts(
        &ctx.accounts.payer,
        vec![&ctx.accounts.pda],
        &ctx.accounts.magic_context,
        &ctx.accounts.magic_program,
    )?;
    Ok(())
}

What this is good for

  • Real-time games. An FPS or RTS where position updates need to round-trip in <100ms. Delegate per-match state, settle the result to L1 at end-of-match.
  • Order books on perpetual DEXes. Place, modify, cancel at human-typing speed without paying gas per action. Settle when positions close.
  • High-frequency on-chain state. Live auctions, rapid bidding, real-time leaderboards.
  • Account abstraction-style flows. Group many user actions into one mainnet commit, dramatically reducing per-action cost and friction.

What it's not good for

  • Long-lived state that needs L1 finality at all times (treasury balances, governance) — delegate locks the account; the L1 version becomes a snapshot until you undelegate.
  • Cross-program interactions with non-delegated programs from inside the ER. The ER can only see and write delegated accounts; CPI into unrelated programs needs to come back to L1.
  • Use cases where 400ms is already fast enough. Don't add complexity for nothing.

What ships in the SDK

sh
cargo add ephemeral-rollups-sdk

# or for client-side:
npm install @magicblock-labs/ephemeral-rollups-sdk

Two ergonomic helpers: the Rust SDK gives you delegate_account and commit_and_undelegate macros for your program; the TypeScript SDK gives you the client-side flow to route transactions to the ER endpoint instead of mainnet.

References

MagicBlock isn't an L2 — it's "mainnet, but with a temporary fast lane." If your app needs sub-100ms feedback loops and your state still lives canonically on Solana, this is the only mechanism that gives you both.

MagicBlock: ephemeral rollups for real-time Solana, 10ms blocks and all | devrels.xyz