Skip to main content

Quick Access

Check out basic counter example:

Step-By-Step Guide

The lifecycle of integrating Ephemeral Rollups in your program is as follows:
1
Write your Solana program as you normally would.
2
Add CPI hooks to delegate, commit and undelegate state accounts through Ephemeral Rollup sessions.
3
Deploy your program directly on Solana using Anchor or Solana CLI.
4
Send transactions without modifications on-chain and off-chain that also comply with the SVM RPC specification.

Counter Example

Counter GIF The following software packages may be required, other versions may also be compatible:
SoftwareVersionInstallation Guide
Solana2.3.13Install Solana
Rust1.85.0Install Rust
Anchor0.32.1Install Anchor
Node24.10.0Install Node

Code Snippets

  • 1. Write program
  • 2. Delegate
  • 3. Deploy
  • 4. Test
The program implements two main instructions:
  1. initialize: Sets the counter to 0
  2. increment: Increments the counter by 1
The program implements specific instructions for delegating and undelegating the counter:
  1. Delegate: Delegates counter from Base Layer to ER (called on Base Layer)
  2. CommitAndUndelegate: Schedules sync of counter from ER to Base Layer, and undelegates counter on ER (called on ER)
  3. Commit: Schedules sync of counter from ER to Base Layer (called on ER)
  4. Undelegate:
    • Schedules sync and undelegation of counter (called on ER)
    • Undelegation triggered through callback instruction injected through #[ephemeral] (called on Base Layer through validator CPI)
#[ephemeral]
#[program]
pub mod anchor_counter {
    use super::*;

    /// Initialize the counter.
    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        let counter = &mut ctx.accounts.counter;
        counter.count = 0;
        Ok(())
    }

    /// Increment the counter.
    pub fn increment(ctx: Context<Increment>) -> Result<()> {
        let counter = &mut ctx.accounts.counter;
        counter.count += 1;
        Ok(())
    }

    /// Delegate the account to the delegation program
    /// Set specific validator based on ER, see https://docs.magicblock.gg/pages/get-started/how-integrate-your-program/local-setup
    pub fn delegate(ctx: Context<DelegateInput>) -> Result<()> {
    // ...
    }

    /// Increment the counter and manually commit the account in the Ephemeral Rollup session.
    pub fn increment_and_commit(ctx: Context<IncrementAndCommit>) -> Result<()> {
    // ...
    }

    /// Undelegate the account from the delegation program
    pub fn undelegate(ctx: Context<IncrementAndCommit>) -> Result<()> {
    // ...
    }
}

/// Context for initializing counter
#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(init_if_needed, payer = user, space = 8 + 8, seeds = [TEST_PDA_SEED], bump)]
    pub counter: Account<'info, Counter>,
    #[account(mut)]
    pub user: Signer<'info>,
    pub system_program: Program<'info, System>,
}


/// Context for incrementing counter
#[derive(Accounts)]
pub struct Increment<'info> {
    #[account(mut, seeds = [TEST_PDA_SEED], bump)]
    pub counter: Account<'info, Counter>,
}

/// Counter struct
#[account]
pub struct Counter {
    pub count: u64,
}

/// Other context for delegation


Nothing special here, just a simple Anchor program that increments a counter. The only difference is that we’re adding the ephemeral macro for undelegation and delegate macro to inject some useful logic to interact with the delegation program.⬆️ Back to Top

Advanced Code Snippets

  • Resize PDA
  • Magic Router
  • Magic Action
  • On-Curve Delegation
When resizing a delegated PDA:
  • PDA must have enough lamports to remain rent-exempt for the new account size.
  • If additional lamports are needed, the payer account must be delegated to provide the difference.
  • PDA must be owned by the program, and the transaction must include any signer(s) required for transferring lamports.
  • Use system_instruction::allocate
#[account]
pub struct Counter {
    pub count: u64,
    pub extra_data: Vec<u8>,
}

#[derive(Accounts)]
pub struct ResizeCounter<'info> {
    #[account(mut)]
    pub counter: Account<'info, Counter>,
    #[account(mut)]
    pub payer: Signer<'info>,
    pub system_program: Program<'info, System>,
}

    // Resize the counter (e.g., to store more extra_data)
    pub fn resize_counter(ctx: Context<ResizeCounter>, new_size: usize) -> Result<()> {
        let account_to_resize = &mut ctx.accounts.counter.to_account_info();
        let payer = &mut ctx.accounts.payer.to_account_info();

        // Calculate rent-exemption for the new size
        let rent = Rent::get()?;
        let min_balance = rent.minimum_balance(new_size);

        // Top up lamports if needed
        let current_lamports = **account_to_resize.lamports.borrow();
        if current_lamports < min_balance {
            let to_transfer = min_balance - current_lamports;
            **payer.try_borrow_mut_lamports()? -= to_transfer;
            **account_to_resize.try_borrow_mut_lamports()? += to_transfer;
        }

        // Resize account
        account_to_resize.resize(new_size)?;

        Ok(())
    }
⬆️ Back to Top

Quick Access

Learn more about private ER, Rust Native implementation, and local development:

Solana Explorer

Get insights about your transactions and accounts on Solana:

Solana RPC Providers

Send transactions and requests through existing RPC providers:

Solana Validator Dashboard

Find real-time updates on Solana’s validator infrastructure:

Server Status

Subscribe to Solana’s and MagicBlock’s server status:

MagicBlock Products