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.
Token Transfer
The Token Transfer example demonstrates how to work with custom token accounts in Ephemeral Rollups. While this example uses a simple balance account for clarity, the same patterns apply to SPL tokens, allowing you to build high-frequency token operations with ultra-low latency.Overview
This example shows:- Creating and delegating balance accounts
- Transferring tokens between accounts in ERs
- Configurable delegation parameters
- Committing and undelegating accounts
While this example uses a simple
Balance account, the same delegation patterns work with SPL Token Accounts for real token transfers.How It Works
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
let acc = &mut ctx.accounts.balance;
acc.balance = 100;
Ok()
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = user, space = 8 + 8, seeds = [user.key.as_ref()], bump)]
pub balance: Account<'info, Balance>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[account]
pub struct Balance {
pub balance: u64,
}
pub fn delegate(ctx: Context<DelegateBalance>, params: DelegateParams) -> Result<()> {
let config = DelegateConfig {
commit_frequency_ms: params.commit_frequency_ms,
validator: params.validator,
};
ctx.accounts.delegate_balance(
&ctx.accounts.payer,
&[ctx.accounts.payer.key.as_ref()],
config,
)?;
Ok()
}
#[delegate]
#[derive(Accounts)]
pub struct DelegateBalance<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(mut, del, seeds = [payer.key.as_ref()], bump)]
pub balance: AccountInfo<'info>,
}
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub struct DelegateParams {
pub commit_frequency_ms: u32,
pub validator: Option<Pubkey>,
}
pub fn transfer(ctx: Context<Transfer>, amount: u64) -> Result<()> {
let balance = &mut ctx.accounts.balance;
let receiver_balance = &mut ctx.accounts.receiver_balance;
if balance.balance < amount {
return Err(error!(ErrorCode::InsufficientBalance));
}
balance.balance -= amount;
receiver_balance.balance += amount;
Ok()
}
#[derive(Accounts)]
pub struct Transfer<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(mut, seeds = [payer.key.as_ref()], bump)]
pub balance: Account<'info, Balance>,
/// CHECK: anyone can receive the tokens
pub receiver: AccountInfo<'info>,
#[account(init_if_needed, payer = payer, space = 8 + 8, seeds = [receiver.key.as_ref()], bump)]
pub receiver_balance: Account<'info, Balance>,
pub system_program: Program<'info, System>,
}
#[error_code]
pub enum ErrorCode {
#[msg("Insufficient balance for transfer")]
InsufficientBalance,
}
pub fn undelegate(ctx: Context<UndelegateBalance>) -> Result<()> {
commit_and_undelegate_accounts(
&ctx.accounts.payer,
vec![&ctx.accounts.balance.to_account_info()],
&ctx.accounts.magic_context,
&ctx.accounts.magic_program,
)?;
Ok()
}
#[commit]
#[derive(Accounts)]
pub struct UndelegateBalance<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(mut, seeds = [payer.key.as_ref()], bump)]
pub balance: Account<'info, Balance>,
}
Program Annotations
The program uses the#[ephemeral] macro to enable ER support:
programs/dummy-transfer/src/lib.rs
Delegation Configuration
Complete Flow Example
SPL Token Integration
To use this pattern with real SPL tokens:use anchor_spl::token::{Token, TokenAccount};
#[derive(Accounts)]
pub struct Transfer<'info> {
#[account(mut)]
pub authority: Signer<'info>,
#[account(
mut,
associated_token::mint = mint,
associated_token::authority = authority
)]
pub from: Account<'info, TokenAccount>,
#[account(
mut,
associated_token::mint = mint,
associated_token::authority = to_authority
)]
pub to: Account<'info, TokenAccount>,
pub mint: Account<'info, Mint>,
pub token_program: Program<'info, Token>,
}
What Makes This Advanced?
This example demonstrates:- Custom Delegation Parameters: Fine-grained control over commit frequency and validator selection
- Multi-Account Operations: Transferring between multiple delegated accounts
- Init-If-Needed Pattern: Automatically creating receiver accounts during transfers
- Error Handling: Proper balance validation with custom errors
- PDA Management: Using PDAs for user-specific balance accounts
Use Cases
- Gaming
- DeFi
- In-game currency transfers
- Rapid item trading between players
- Reward distributions
- Marketplace transactions
Performance Benefits
Compared to base layer token transfers:- Latency: ~400ms → ~10ms (40x faster)
- Cost: ~0.000005 SOL → negligible in ER
- Throughput: Thousands of transfers per second
- Batching: Execute 100s of transfers before committing
Testing Locally
Testing on Devnet
To run tests on devnet:Make sure you have devnet SOL in your wallet before running devnet tests.
Security Considerations
Adapting for Real Tokens
The key differences when using SPL tokens:- Account Type:
TokenAccountinstead of customBalance - Authority: Token account authority, not PDA seeds
- Transfer Logic: Use SPL token CPI instead of direct balance updates
- Mint Validation: Ensure all accounts use the same mint
Next Steps
- Explore Session Keys for gasless token transfers
- Learn about Magic Actions to trigger base layer logic on commit
- Check out SPL Token documentation for working with real tokens
- Review the full source code