Lifecycle of the Integration

The lifecycle of integrating Ephemeral Rollups in your program is as follows:
1

Write your program

Write your Solana program as you normally would, using Anchor, native Rust, Bolt, C, or even assembly.
2

Add delegation and undelegation hooks

Accounts that are delegated can be used as writable in an Ephemeral Rollup session.
3

Deploy your program on Solana

Deploy your program directly on Solana.
4

Ready to execute transactions for delegation and real-time speed

Execute your transactions without modifications on-chain and off-chain that also comply with the SVM RPC specification.

Step 1: Write your program

A simple counter program have the following instructions initialize and increment:
#[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(())
    }

    // ... Additional instructions will be added here
}

/// 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,
}
Find the full basic counter example for Rust Native and Anchor framework implementation:

Step 2: Add delegation and undelegation hooks

Delegation is the process of transferring ownership of one or more of your program’s PDAs to the delegation program. Ephemeral Validators will then be able to use the PDAs to perform transactions in the SVM runtime.
Undelegation is the process of transferring ownership of the PDAs back to your program. On undelegation, the state is committed and it trigger the finalization process. Once state it validated, the PDAs are unlocked and can be used as normal on mainnet
Anchor framework example:
  1. Add ephemeral-rollups-sdk with Anchor features to your program
cargo add ephemeral-rollups-sdk --features anchor
Import delegate, commit, ephemeral, DelegateConfig, commit_accounts and commit_and_undelegate_accounts:
use ephemeral_rollups_sdk::anchor::{
  commit,
  delegate,
  ephemeral
};
use ephemeral_rollups_sdk::cpi::DelegateConfig;
use ephemeral_rollups_sdk::ephem::{
  commit_accounts,
  commit_and_undelegate_accounts
  };
  1. Add delegate macro and instruction, ephemeral macro, and undelegate instruction to your program. Specify your preferred delegation config such as auto commits and specific ER validator:
These public valdiators are supported for development. Make sure to upgrade your program with the specific ER validator in delegation config:
  • Asia (devnet): MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57
  • EU (devnet): MEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e
  • US (devnet): MUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd
  • Local ER (localhost): mAGicPQYBMvcYveUZA5F5UNNwyHvfYh5xkLS2Fr1mev
#[ephemeral]
#[program]
pub mod anchor_counter {
    use super::*;

    // ... Initialize and Incroment instructions.

    /// Delegate the account to the delegation program
    /// IMPORTANT: Set specific validator based on ER
    pub fn delegate(ctx: Context<DelegateInput>) -> Result<()> {
        ctx.accounts.delegate_pda(
            &ctx.accounts.payer,
            &[TEST_PDA_SEED],
            DelegateConfig {
                commit_frequency_ms: 30_000,
                validator: Some(pubkey!("MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57")),
            },
        )?;
        Ok(())
    }

    /// Undelegate the account from the delegation program
    pub fn undelegate(ctx: Context<IncrementAndCommit>) -> Result<()> {
        commit_and_undelegate_accounts(
            &ctx.accounts.payer,
            vec![&ctx.accounts.counter.to_account_info()],
            &ctx.accounts.magic_context,
            &ctx.accounts.magic_program,
        )?;
        Ok(())
    }
}

/// Add delegate function to the context
#[delegate]
#[derive(Accounts)]
pub struct DelegateInput<'info> {
    pub payer: Signer<'info>,
    /// CHECK The pda to delegate
    #[account(mut, del)]
    pub pda: AccountInfo<'info>,
}

// ... Other context and account struct.

Step 3: Deploy your program on Solana

Now you’re program is upgraded and ready! Build and deploy to the desired cluster:
anchor build && anchor deploy

Step 4: Ready to execute transactions for delegation and real-time speed

Use Anchor IDL file to build and send transactions:
Frontend
// Set Anchor providers
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
const providerEphemeralRollup = new anchor.AnchorProvider(
  new anchor.web3.Connection("https://devnet-as.magicblock.app/", {
    wsEndpoint: "wss://devnet.magicblock.app/",
  }),
  anchor.Wallet.local()
);

// Set program and pda
const program = anchor.workspace.AnchorCounter as Program<AnchorCounter>;
const [pda] = anchor.web3.PublicKey.findProgramAddressSync(
  [Buffer.from(SEED_TEST_PDA)],
  program.programId
);

// Initialize Counter on Base Layer
let initTx = await program.methods
  .increment()
  .accounts({
    counter: pda,
  })
  .transaction();
initTx.feePayer = provider.wallet.publicKey;
initTx.recentBlockhash = (
  await provider.connection.getLatestBlockhash()
).blockhash;
initTx = await provider.connection.signTransaction(initTx);
const dinitTxHash = await provider.sendAndConfirm(initTx);

// Delegate Counter on Base Layer to ER
let delTx = await program.methods
  .increment()
  .accounts({
    counter: pda,
  })
  .transaction();
delTx.feePayer = provider.connection.wallet.publicKey;
delTx.recentBlockhash = (
  await provider.connection.getLatestBlockhash()
).blockhash;
delTx = await provider.wallet.signTransaction(delTx);
const delTxHash = await provider.sendAndConfirm(delTx);

// Increment Counter in real-time on ER
let incTx = await program.methods
  .increment()
  .accounts({
    counter: pda,
  })
  .transaction();
incTx.feePayer = providerEphemeralRollup.wallet.publicKey;
incTx.recentBlockhash = (
  await providerEphemeralRollup.connection.getLatestBlockhash()
).blockhash;
incTx = await providerEphemeralRollup.wallet.signTransaction(incTx);
const incTxHash = await providerEphemeralRollup.sendAndConfirm(incTx);
To make it easier to integrate via the frontend, we created the Magic Router. You send transactions directly to the magic router, and we can determine for you whether it should be routed to the Ephemeral Rollup or base layer.
These public RPC endpoints are currently free and supported for development:
Magic Router Devnet: https://devnet-router.magicblock.app
Solana Devnet: https://api.devnet.solana.com
ER Devnet: https://devnet.magicblock.app
TEE Devnet (test): https://tee.magicblock.app/