Documentation Index Fetch the complete documentation index at: https://mintlify.com/magicblock-labs/magicblock-engine-examples/llms.txt
Use this file to discover all available pages before exploring further.
A simple counter program demonstrating how to use the Anchor framework with Ephemeral Rollups. This example shows how to delegate accounts, execute low-latency transactions, and commit state back to the base layer using Anchor’s ergonomic macros.
What You’ll Learn
How to use Anchor’s #[ephemeral], #[delegate], and #[commit] macros
How to delegate a PDA to Ephemeral Rollups
How to execute transactions with low latency on delegated accounts
How to commit state changes back to Solana
How to undelegate accounts from Ephemeral Rollups
Program Structure
The Anchor counter program includes the following instructions:
initialize - Initialize the counter PDA to 0
increment - Increment the counter by 1 (with rollover at 1000)
delegate - Delegate the counter account to the delegation program
commit - Manually commit the account state to Solana
undelegate - Commit and undelegate the account
increment_and_commit - Increment and commit in one transaction
increment_and_undelegate - Increment and undelegate in one transaction
Software Requirements
Ensure you have the following software packages installed before building the program.
Software Version Installation Guide Solana 2.3.13 Install Solana Rust 1.85.0 Install Rust Anchor 0.32.1 Install Anchor Node 24.10.0 Install Node
# Check and initialize your Solana version
agave-install list
agave-install init 2.3.13
# Check and initialize your Rust version
rustup show
rustup install 1.85.0
# Check and initialize your Anchor version
avm list
avm use 0.32.1
Build and Test
Run tests (skip build and deploy)
The test script automatically detects the cluster from Anchor.toml and handles Ephemeral Rollup setup for localnet: anchor test --skip-deploy --skip-build --skip-local-validator
Build, deploy and test (optional)
To build, deploy and run tests with a new program: # Delete keypairs in the deploy folder
rm -rf /target/deploy/ * .keypair
# Build, deploy and test program
anchor test
Program Implementation
Delegation Macro
The program uses Anchor’s special macros to enable Ephemeral Rollups integration:
use anchor_lang :: prelude ::* ;
use ephemeral_rollups_sdk :: anchor :: {commit, delegate, ephemeral};
use ephemeral_rollups_sdk :: cpi :: DelegateConfig ;
#[ephemeral]
#[program]
pub mod anchor_counter {
use super ::* ;
// ... instructions
}
The #[ephemeral] macro marks the program as compatible with Ephemeral Rollups, enabling special delegation features.
Delegate Instruction
The delegate instruction uses the #[delegate] macro on the context struct:
/// Delegate the account to the delegation program
pub fn delegate ( ctx : Context < DelegateInput >) -> Result <()> {
ctx . accounts . delegate_pda (
& ctx . accounts . payer,
& [ COUNTER_SEED ],
DelegateConfig {
// Optionally set a specific validator from the first remaining account
validator : ctx . remaining_accounts . first () . map ( | acc | acc . key ()),
.. Default :: default ()
},
) ? ;
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 >,
}
The #[delegate] macro automatically adds the required delegation accounts to the context, and the #[account(mut, del)] attribute marks which account to delegate.
Increment Instruction
The core increment logic is simple:
/// Increment the counter.
pub fn increment ( ctx : Context < Increment >) -> Result <()> {
let counter = & mut ctx . accounts . counter;
counter . count += 1 ;
if counter . count > 1000 {
counter . count = 0 ;
}
msg! ( "PDA {} count: {}" , counter . key (), counter . count);
Ok ()
}
#[derive( Accounts )]
pub struct Increment <' info > {
#[account( mut , seeds = [ COUNTER_SEED ], bump )]
pub counter : Account <' info , Counter >,
}
#[account]
pub struct Counter {
pub count : u64 ,
}
Commit and Undelegate
The #[commit] macro simplifies committing state changes:
/// Manual commit the account in the ER.
pub fn commit ( ctx : Context < IncrementAndCommit >) -> Result <()> {
commit_accounts (
& ctx . accounts . payer,
vec! [ & ctx . accounts . counter . to_account_info ()],
& ctx . accounts . magic_context,
& ctx . accounts . magic_program,
) ? ;
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 ()
}
/// Account for the increment instruction + manual commit.
#[commit]
#[derive( Accounts )]
pub struct IncrementAndCommit <' info > {
#[account( mut )]
pub payer : Signer <' info >,
#[account( mut , seeds = [ COUNTER_SEED ], bump )]
pub counter : Account <' info , Counter >,
}
The #[commit] macro automatically adds the magic_program and magic_context accounts required for committing state.
TypeScript Client Usage
Initialize and Increment on Solana
import * as anchor from "@coral-xyz/anchor" ;
import { Program } from "@coral-xyz/anchor" ;
import { AnchorCounter } from "../target/types/anchor_counter" ;
const provider = anchor . AnchorProvider . env ();
const program = anchor . workspace . AnchorCounter as Program < AnchorCounter >;
const COUNTER_SEED = "counter" ;
const [ counterPDA ] = anchor . web3 . PublicKey . findProgramAddressSync (
[ Buffer . from ( COUNTER_SEED )],
program . programId
);
// Initialize counter
let tx = await program . methods
. initialize ()
. accounts ({
user: provider . wallet . publicKey ,
})
. transaction ();
await provider . sendAndConfirm ( tx , [ provider . wallet . payer ]);
// Increment on Solana
tx = await program . methods
. increment ()
. accounts ({
counter: counterPDA ,
})
. transaction ();
await provider . sendAndConfirm ( tx , [ provider . wallet . payer ]);
Delegate to Ephemeral Rollups
// Add local validator identity if running on localnet
const remainingAccounts = providerEphemeralRollup . connection . rpcEndpoint . includes ( "localhost" )
? [{
pubkey: new web3 . PublicKey ( "mAGicPQYBMvcYveUZA5F5UNNwyHvfYh5xkLS2Fr1mev" ),
isSigner: false ,
isWritable: false ,
}]
: [];
let tx = await program . methods
. delegate ()
. accounts ({
payer: provider . wallet . publicKey ,
pda: counterPDA ,
})
. remainingAccounts ( remainingAccounts )
. transaction ();
await provider . sendAndConfirm ( tx , [ provider . wallet . payer ]);
Execute on Ephemeral Rollups
Increment
Commit
Increment and Undelegate
const providerEphemeralRollup = new anchor . AnchorProvider (
new anchor . web3 . Connection (
process . env . EPHEMERAL_PROVIDER_ENDPOINT || "https://devnet-as.magicblock.app/" ,
{
wsEndpoint: process . env . EPHEMERAL_WS_ENDPOINT || "wss://devnet-as.magicblock.app/" ,
}
),
anchor . Wallet . local ()
);
let tx = await program . methods
. increment ()
. accounts ({
counter: counterPDA ,
})
. transaction ();
tx . feePayer = providerEphemeralRollup . wallet . publicKey ;
tx . recentBlockhash = (
await providerEphemeralRollup . connection . getLatestBlockhash ()
). blockhash ;
tx = await providerEphemeralRollup . wallet . signTransaction ( tx );
const txHash = await providerEphemeralRollup . sendAndConfirm ( tx );
console . log ( "Increment Tx:" , txHash );
Key Features
Ergonomic Macros Use #[ephemeral], #[delegate], and #[commit] macros to simplify Ephemeral Rollups integration
Automatic Account Injection Delegation and commit accounts are automatically added to instruction contexts
Type Safety Anchor’s type-safe framework ensures correct account structures and validation
Flexible Commits Commit state manually or combine with other operations in a single instruction
Source Code
View the complete source code on GitHub:
anchor-counter on GitHub