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.
Magic Actions
Magic Actions enable you to execute automatic on-chain handlers when committing accounts from Ephemeral Rollups back to the base layer. This powerful pattern allows you to perform base chain operations (like updating global state) automatically whenever ER state is committed.What Are Magic Actions?
Magic Actions are instruction handlers that execute automatically on the base chain when you commit delegated accounts from an Ephemeral Rollup. They enable:- Updating global leaderboards when player scores are committed
- Triggering reward distributions based on ER activity
- Synchronizing cross-program state
- Executing base-layer-only operations (like SPL token transfers) on commit
Magic Actions execute on the base layer (Solana mainnet/devnet) using funds from an escrow account, allowing automated base chain interactions without user signatures.
How It Works
#[action]
#[derive(Accounts)]
pub struct UpdateLeaderboard<'info> {
#[account(mut, seeds = [LEADERBOARD_SEED], bump)]
pub leaderboard: Account<'info, Leaderboard>,
/// CHECK: PDA owner depends on: 1) Delegated: Delegation Program; 2) Undelegated: Your program ID
pub counter: UncheckedAccount<'info>,
}
pub fn update_leaderboard(ctx: Context<UpdateLeaderboard>) -> Result<()> {
let leaderboard = &mut ctx.accounts.leaderboard;
let counter_info = &mut ctx.accounts.counter.to_account_info();
let mut data: &[u8] = &counter_info.try_borrow_data()?;
let counter = Counter::try_deserialize(&mut data)?;
if counter.count > leaderboard.high_score {
leaderboard.high_score = counter.count;
}
msg!(
"Leaderboard updated! High score: {}",
leaderboard.high_score
);
Ok()
}
pub fn commit_and_update_leaderboard(ctx: Context<CommitAndUpdateLeaderboard>) -> Result<()> {
// Create action instruction
let instruction_data =
anchor_lang::InstructionData::data(&crate::instruction::UpdateLeaderboard {});
let action_args = ActionArgs::new(instruction_data);
let action_accounts = vec![
ShortAccountMeta {
pubkey: ctx.accounts.leaderboard.key(),
is_writable: true,
},
ShortAccountMeta {
pubkey: ctx.accounts.counter.key(),
is_writable: false,
},
];
let action = CallHandler {
destination_program: crate::ID,
accounts: action_accounts,
args: action_args,
escrow_authority: ctx.accounts.payer.to_account_info(), // Signer authorized to pay transaction fees for action from escrow PDA
compute_units: 200_000,
};
// Build commit and action instruction
let magic_action = MagicInstructionBuilder {
payer: ctx.accounts.payer.to_account_info(),
magic_context: ctx.accounts.magic_context.to_account_info(),
magic_program: ctx.accounts.magic_program.to_account_info(),
magic_action: MagicAction::Commit(CommitType::WithHandler {
commited_accounts: vec![ctx.accounts.counter.to_account_info()],
call_handlers: vec![action],
}),
};
// Invoke
magic_action.build_and_invoke()?;
Ok()
}
import {
createTopUpEscrowInstruction,
createCloseEscrowInstruction,
escrowPdaFromEscrowAuthority,
} from "@magicblock-labs/ephemeral-rollups-sdk";
// Create and fund the escrow
const topUpEscrowIx = createTopUpEscrowInstruction(
escrowPdaFromEscrowAuthority(anchor.Wallet.local().publicKey),
anchor.Wallet.local().publicKey,
anchor.Wallet.local().publicKey,
10000 // top-up amount in lamports
);
// Combine with delegation
const delegateIx = await program.methods
.delegate()
.accounts({
payer: anchor.Wallet.local().publicKey,
pda: pda
})
.remainingAccounts(remainingAccounts)
.instruction();
const tx = new Transaction().add(topUpEscrowIx, delegateIx);
const signature = await sendAndConfirmTransaction(
routerConnection,
tx,
[anchor.Wallet.local().payer],
{ skipPreflight: true }
);
const tx = await program.methods
.commitAndUpdateLeaderboard()
.accounts({
payer: anchor.Wallet.local().publicKey,
programId: program.programId,
})
.transaction();
const signature = await sendAndConfirmTransaction(
routerConnection,
tx,
[anchor.Wallet.local().payer],
{ skipPreflight: true }
);
// The leaderboard will be updated on the base layer automatically
Required Imports
Account Context Annotations
Managing Escrow Accounts
- Create & Fund
- Close Escrow
What Makes This Advanced?
Magic Actions represent an advanced pattern because they:- Bridge ER and Base Layer: Automatically execute base chain logic when ER state is committed
- Gasless Automation: Use escrow accounts to pay for base layer fees without user interaction
- Cross-Program Coordination: Update global state (like leaderboards) based on individual player actions in ERs
- Composability: Chain multiple actions together in a single commit operation
Use Cases
- Global Leaderboards: Update rankings when player scores are committed from game ERs
- Tournament Systems: Trigger prize distributions when tournament state is finalized
- Cross-Chain State: Synchronize ER state with base layer contracts
- Token Operations: Execute SPL token transfers on the base layer when ER conditions are met
Example: Counter with Leaderboard
programs/magic-actions/src/lib.rs
Next Steps
- Learn about Cranks for scheduled automated execution
- Explore Session Keys for gasless user transactions
- Check the full source code