Skip to main content

Quick Access

Check out basic counter example:

Step-By-Step Guide

Build your program and upgrade it with delegation hooks with MagicBlock’s Delegation Program DELeGGvXpWV2fqJUhqcF5ZSYMS4JTLjteaAMARRSaeSh:
1
Write your Solana program as you normally would.
2
Add CPI hooks to delegate, commit and undelegate state accounts through Ephemeral Rollup sessions.

These public validators are supported for development. Make sure to add the specific ER validator in your instruction when delegating:

  • Asia (devnet-as.magicblock.app): MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57
  • EU (devnet-eu.magicblock.app): MEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e
  • US (devnet-us.magicblock.app): MUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd
  • TEE (tee.magicblock.app): FnE6VJT5QNZdedZPnCoLsARgBwoE6DeJNjBs2H1gySXA
  • Local ER (localhost): mAGicPQYBMvcYveUZA5F5UNNwyHvfYh5xkLS2Fr1mev
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

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

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