Comparing magicblock with surfpool
magicblock
View full →Author
@0xinit
Stars
53
Repository
0xinit/cryptoskills
MagicBlock Ephemeral Rollups Guide
A comprehensive guide for building high-performance Solana applications with MagicBlock Ephemeral Rollups - enabling sub-10ms latency and gasless transactions.
Overview
MagicBlock Ephemeral Rollups (ER) are specialized SVM runtimes that enhance Solana with:
- Sub-10ms latency (vs ~400ms on base Solana)
- Gasless transactions for seamless UX
- Full composability with existing Solana programs
- Horizontal scaling via on-demand rollups
Architecture
┌─────────────────────────────────────────────────────────────┐
│ Your Application │
├─────────────────────────────────────────────────────────────┤
│ Base Layer (Solana) │ Ephemeral Rollup (ER) │
│ - Initialize accounts │ - Execute operations │
│ - Delegate accounts │ - Process at ~10-50ms │
│ - Final state commits │ - Zero gas fees │
│ - ~400ms finality │ - Commit state to Solana │
└─────────────────────────────────────────────────────────────┘
Core Flow
- Initialize - Create accounts on Solana base layer
- Delegate - Transfer account ownership to delegation program
- Execute - Run fast operations on Ephemeral Rollup
- Commit - Sync state back to base layer
- Undelegate - Return ownership to your program
Prerequisites
# Required versions
Solana: 2.3.13
Rust: 1.85.0
Anchor: 0.32.1
Node: 24.10.0
# Install Anchor (if needed)
cargo install --git https://github.com/coral-xyz/anchor anchor-cli
Quick Start
1. Add Dependencies (Cargo.toml)
[dependencies]
anchor-lang = "0.32.1"
ephemeral-rollups-sdk = { version = "0.6.5", features = ["anchor", "disable-realloc"] }
2. Program Setup (lib.rs)
use anchor_lang::prelude::*;
use ephemeral_rollups_sdk::anchor::{delegate_account, commit_accounts, ephemeral};
use ephemeral_rollups_sdk::cpi::DelegationProgram;
declare_id!("YourProgramId111111111111111111111111111111");
#[ephemeral] // Required: enables ER support
#[program]
pub mod my_program {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
ctx.accounts.state.value = 0;
Ok(())
}
#[delegate] // Auto-injects delegation accounts
pub fn delegate(ctx: Context<Delegate>) -> Result<()> {
Ok(())
}
pub fn increment(ctx: Context<Update>) -> Result<()> {
ctx.accounts.state.value += 1;
Ok(())
}
#[commit] // Auto-injects commit accounts
pub fn undelegate(ctx: Context<Undelegate>) -> Result<()> {
Ok(())
}
}
3. TypeScript Client Setup
import { Connection, PublicKey } from "@solana/web3.js";
import { AnchorProvider, Program } from "@coral-xyz/anchor";
import { DELEGATION_PROGRAM_ID } from "@magicblock-labs/ephemeral-rollups-sdk";
// CRITICAL: Separate connections for each layer
const baseConnection = new Connection("https://api.devnet.solana.com");
const erConnection = new Connection("https://devnet.magicblock.app");
// Create providers
const baseProvider = new AnchorProvider(baseConnection, wallet, { commitment: "confirmed" });
const erProvider = new AnchorProvider(erConnection, wallet, {
commitment: "confirmed",
skipPreflight: true, // Required for ER
});
// Check delegation status
async function isDelegated(pubkey: PublicKey): Promise<boolean> {
const info = await baseConnection.getAccountInfo(pubkey);
return info?.owner.equals(DELEGATION_PROGRAM_ID) ?? false;
}
Key Concepts
Delegation
Delegation transfers PDA ownership to the delegation program, allowing the Ephemeral Validator to process transactions.
#[derive(Accounts)]
pub struct Delegate<'info> {
#[account(mut)]
pub payer: Signer<'info>,
/// CHECK: Will be delegated
#[account(mut, del)] // 'del' marks for delegation
pub state: AccountInfo<'info>,
pub delegation_program: Program<'info, DelegationProgram>,
}
Commit
Commits update PDA state from ER to base layer without undelegating.
use ephemeral_rollups_sdk::anchor::commit_accounts;
pub fn commit(ctx: Context<Commit>) -> Result<()> {
commit_accounts(
&ctx.accounts.payer,
vec![&ctx.accounts.state.to_account_info()],
&ctx.accounts.magic_context,
&ctx.accounts.magic_program,
)?;
Ok(())
}
Undelegation
Returns PDA ownership to your program while committing final state.
#[commit] // Handles commit + undelegate
pub fn undelegate(ctx: Context<Undelegate>) -> Result<()> {
Ok(())
}
ER Validators (Devnet)
| Region | Validator Identity |
|---|---|
| Asia | MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57 |
| EU | MEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e |
| US | MUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd |
| TEE | FnE6VJT5QNZdedZPnCoLsARgBwoE6DeJNjBs2H1gySXA |
Magic Router (auto-selects best): https://devnet-router.magicblock.app
Critical Rules
DO:
- Maintain separate connections for base layer and ER
- Use
skipPreflight: truefor all ER transactions - Verify delegation status before sending to ER
- Use
AccountInfofor delegated accounts in Rust - Match PDA seeds exactly between Rust and TypeScript
DON'T:
- Send delegated account operations to base layer
- Mix base layer and ER operations in single transaction
- Assume account ownership without checking
- Skip commitment verification before base layer reads
Products
| Product | Description |
|---|---|
| Ephemeral Rollup (ER) | High-performance, gasless transactions |
| Private ER (PER) | Privacy-preserving computation with Intel TDX |
| VRF | Verifiable random function for on-chain randomness |
| BOLT Framework | ECS architecture for fully on-chain games |
| Solana Plugins | App-specific extensions for enhanced capabilities |
Solana Plugins (New)
Solana Plugins are modular capabilities that can be added to your dApp to extend what's possible on Solana. Think of them as your custom toolkit: plug in what you need, when you need it.
Available Plugins
| Plugin | Description | Use Cases |
|---|---|---|
| Verifiable Randomness (VRF) | Provably fair on-chain randomness | Games, lotteries, NFT drops |
| Real-Time Price Feeds | Up-to-the-millisecond market data | DEXs, trading bots, DeFi |
| AI Oracles | Call AI models directly from smart contracts | Dynamic NFTs, AI agents |
Using VRF Plugin
import { requestRandomness, getRandomnessResult } from "@magicblock-labs/vrf-sdk";
// Request randomness
const requestTx = await requestRandomness({
payer: wallet.publicKey,
seed: Buffer.from("my_game_seed"),
});
// Get result after confirmation
const result = await getRandomnessResult(requestId);
console.log("Random value:", result.randomness);
Privacy with Intel TDX
MagicBlock enables privacy in any Solana program state account through Ephemeral Rollups running in Trusted Execution Environments (TEE) on Intel TDX. This allows:
- Private computation without exposing state
- Verifiable execution guarantees
- Selective disclosure of results
Resources
- Documentation: https://docs.magicblock.gg
- GitHub: https://github.com/magicblock-labs
- Examples: https://github.com/magicblock-labs/magicblock-engine-examples
- Starter Kits: https://github.com/magicblock-labs/starter-kits
- BOLT Book: https://book.boltengine.gg
- Discord: Join for testnet access
Skill Structure
magicblock/
├── SKILL.md # This file
├── resources/
│ ├── api-reference.md # Complete API reference
│ └── program-ids.md # All program IDs and constants
├── examples/
│ ├── anchor-counter/README.md # Basic counter with delegation
│ ├── delegation-flow/README.md # Full delegation lifecycle
│ ├── vrf-randomness/README.md # VRF integration
│ └── crank-automation/README.md # Scheduled tasks
├── templates/
│ ├── program-template.rs # Rust program starter
│ └── client-template.ts # TypeScript client starter
└── docs/
├── advanced-patterns.md # Complex patterns
└── troubleshooting.md # Common issues
surfpool
View full →Author
@0xinit
Stars
53
Repository
0xinit/cryptoskills
Surfpool - Solana Development Environment
The definitive guide for Surfpool - where developers start their Solana journey. A drop-in replacement for solana-test-validator that enables local program simulation using Mainnet accounts fetched just-in-time.
What is Surfpool?
Surfpool is a comprehensive development environment that combines local-first testing with real Mainnet data access:
- Mainnet Forking - Clone accounts, programs, and token balances from Mainnet instantly
- Cheatcodes - Special RPC methods for time travel, balance manipulation, and state control
- Infrastructure as Code - Reproducible, auditable deployments using txtx DSL
- Surfpool Studio - Embedded dashboard with transaction inspection and profiling
- Universal Faucet - Get SOL, USDC, USDT, BONK from a single interface
Key Benefits
| Feature | Description |
|---|---|
| Instant Boot | No 2TB snapshots, runs on Raspberry Pi |
| Lazy Forking | Copy-on-read strategy pulls mainnet data as needed |
| Full Compatibility | Works with solana-cli, Anchor, wallets, explorers |
| Zero Config | Auto-detects Anchor projects and deploys programs |
Statistics
- 460+ GitHub stars
- 100+ forks
- Apache 2.0 license
- Current version: v1.0.0
Installation
Automated Installer (Recommended)
curl -sL https://run.surfpool.run/ | bash
Homebrew (macOS)
brew install txtx/taps/surfpool
From Source
git clone https://github.com/txtx/surfpool.git
cd surfpool
cargo surfpool-install
Docker
docker pull surfpool/surfpool
docker run -p 8899:8899 -p 18488:18488 surfpool/surfpool
Quick Start
Start Local Network
# Start with default configuration
surfpool start
# Start with custom RPC source
surfpool start -u https://api.mainnet-beta.solana.com
# Start without terminal UI
surfpool start --no-tui
# Start with debug logging
surfpool start --debug
Access Points
| Service | URL | Description |
|---|---|---|
| RPC Endpoint | http://127.0.0.1:8899 | Standard Solana RPC |
| WebSocket | ws://127.0.0.1:8900 | Real-time subscriptions |
| Surfpool Studio | http://127.0.0.1:18488 | Web dashboard |
CLI Commands
surfpool start
Start the local Surfnet network.
surfpool start [OPTIONS]
Options:
| Option | Default | Description |
|---|---|---|
-m, --manifest-file-path | ./Surfpool.toml | Path to manifest file |
-p, --port | 8899 | RPC port |
-o, --host | 127.0.0.1 | Host address |
-s, --slot-time | 400 | Slot time in ms |
-u, --rpc-url | https://api.mainnet-beta.solana.com | Source RPC URL |
--no-tui | - | Disable terminal UI |
--debug | - | Enable debug logs |
--no-deploy | - | Disable auto deployments |
-r, --runbook | deployment | Runbooks to execute |
-a, --airdrop | - | Pubkeys to airdrop |
-q, --airdrop-amount | 10000000000000 | Airdrop amount (lamports) |
-k, --airdrop-keypair-path | - | Keypair path for airdrop |
--no-explorer | - | Disable explorer |
Example Usage
# Start with airdrop to specific address
surfpool start -a YOUR_PUBKEY -q 100000000000
# Start with custom slot time (faster blocks)
surfpool start -s 100
# Start with specific runbook
surfpool start -r deployment -r setup
Surfpool.toml Configuration
Create a Surfpool.toml in your project root:
[network]
slot_time = 400
epoch_duration = 432000
rpc_url = "https://api.mainnet-beta.solana.com"
[behavior]
# Fork from mainnet genesis
genesis = false
# Fork from specific point
point_fork = true
[accounts]
# Pre-clone specific accounts
clone = [
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", # Token Program
"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL", # ATA Program
]
[programs]
# Auto-deploy local programs
deploy = ["./target/deploy/my_program.so"]
[airdrop]
# Default airdrop recipients
addresses = ["YOUR_PUBKEY"]
amount = 10000000000000 # 10,000 SOL
Cheatcodes
Surfpool provides special RPC methods for advanced state manipulation during testing.
Account Manipulation
surfnet_setAccount
Set arbitrary account data:
await connection.send("surfnet_setAccount", [
{
pubkey: "AccountPubkey...",
lamports: 1000000000,
data: "base64EncodedData",
owner: "OwnerPubkey...",
executable: false,
},
]);
surfnet_setTokenAccount
Create or modify token accounts:
await connection.send("surfnet_setTokenAccount", [
{
owner: "OwnerPubkey...",
mint: "MintPubkey...",
tokenProgram: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
update: {
amount: "1000000000",
delegate: null,
state: "initialized",
},
},
]);
surfnet_cloneProgramAccount
Clone a program from mainnet:
await connection.send("surfnet_cloneProgramAccount", [
{
source: "SourceProgramPubkey...",
destination: "DestinationPubkey...",
},
]);
surfnet_resetAccount
Reset account to mainnet state:
await connection.send("surfnet_resetAccount", [
{
pubkey: "AccountPubkey...",
includeOwnedAccounts: true,
},
]);
Time Control
surfnet_timeTravel
Advance network time:
await connection.send("surfnet_timeTravel", [
{
epoch: 100,
slot: 50000,
timestamp: 1700000000,
},
]);
surfnet_pauseClock / surfnet_resumeClock
Control block production:
// Pause
await connection.send("surfnet_pauseClock", []);
// Resume
await connection.send("surfnet_resumeClock", []);
surfnet_advanceClock
Advance clock incrementally:
await connection.send("surfnet_advanceClock", [
{ slots: 100 },
]);
Transaction Profiling
surfnet_profileTransaction
Profile transaction execution:
const result = await connection.send("surfnet_profileTransaction", [
{
transaction: "base64EncodedTx",
tag: "my-test-tag",
},
]);
console.log("Compute units:", result.computeUnits);
console.log("Account changes:", result.accountChanges);
surfnet_getProfileResults
Get profiling results by tag:
const results = await connection.send("surfnet_getProfileResults", [
{ tag: "my-test-tag" },
]);
Network Control
surfnet_resetNetwork
Reset entire network to initial state:
await connection.send("surfnet_resetNetwork", []);
surfnet_getClock
Get current network time:
const clock = await connection.send("surfnet_getClock", []);
console.log("Slot:", clock.slot);
console.log("Epoch:", clock.epoch);
console.log("Timestamp:", clock.timestamp);
Surfpool Studio
Access the web dashboard at http://127.0.0.1:18488 for:
- Transaction Inspector - View transaction details with byte-level diffs
- Account Browser - Explore account state and history
- Compute Profiler - Analyze compute unit usage per instruction
- Universal Faucet - Request SOL and tokens
- Network Status - Monitor slots, epochs, and block production
Infrastructure as Code
Surfpool integrates txtx DSL for reproducible deployments.
Runbook Structure
# deployment.tx
// Define signers
signer "deployer" "svm::secret_key" {
secret_key = env.DEPLOYER_KEY
}
// Deploy program
action "deploy_program" "svm::deploy_program" {
program_path = "./target/deploy/my_program.so"
signer = signer.deployer
}
// Initialize program
action "initialize" "svm::send_transaction" {
transaction {
instruction {
program_id = action.deploy_program.program_id
data = encode_instruction("initialize", {})
}
}
signers = [signer.deployer]
}
Running Runbooks
# Run specific runbook
surfpool start -r deployment
# Run in unsupervised mode
surfpool start -r deployment --unsupervised
Scenarios and Fixtures
Scenarios
Define account state sequences for testing:
await connection.send("surfnet_registerScenario", [
{
name: "high-volume-trading",
slots: [
{
slot: 100,
accounts: {
"PoolPubkey...": { lamports: 1000000000000 },
},
},
{
slot: 200,
accounts: {
"PoolPubkey...": { lamports: 500000000000 },
},
},
],
},
]);
Fixtures
Export transaction fixtures for reproducible tests:
const fixture = await connection.send("surfnet_exportSnapshot", [
{
transaction: "txSignature...",
format: "json",
},
]);
// Save fixture for CI/CD
fs.writeFileSync("fixtures/my-test.json", JSON.stringify(fixture));
Integration with Anchor
Surfpool auto-detects Anchor projects and handles deployment:
# In an Anchor project directory
surfpool start
# Programs in target/deploy/ are automatically deployed
Testing with Anchor
import * as anchor from "@coral-xyz/anchor";
describe("My Program", () => {
// Use local Surfnet
const provider = anchor.AnchorProvider.local("http://127.0.0.1:8899");
anchor.setProvider(provider);
it("works with mainnet state", async () => {
// Your tests automatically have access to mainnet accounts
});
});
Best Practices
1. Use Cheatcodes for Setup
// Set up test state before each test
beforeEach(async () => {
await connection.send("surfnet_resetNetwork", []);
await connection.send("surfnet_setTokenAccount", [...]);
});
2. Profile Critical Paths
// Tag transactions for profiling
const result = await connection.send("surfnet_profileTransaction", [
{ transaction: tx, tag: "swap-operation" },
]);
expect(result.computeUnits).toBeLessThan(200000);
3. Use Scenarios for Edge Cases
// Test with specific mainnet conditions
await connection.send("surfnet_registerScenario", [
{ name: "low-liquidity", slots: [...] },
]);
4. Export Fixtures for CI
// Create reproducible test fixtures
const fixture = await connection.send("surfnet_exportSnapshot", [...]);
Resources
Official Links
- Website: https://surfpool.run
- Documentation: https://docs.surfpool.run
- GitHub: https://github.com/txtx/surfpool
Community
- Discord: https://discord.gg/surfpool
- Twitter: @surfaboratory
- Telegram: Announcements channel
Tutorials
Skill Structure
surfpool/
├── SKILL.md # This file
├── resources/
│ ├── cheatcodes.md # Complete cheatcodes reference
│ ├── cli-reference.md # CLI commands reference
│ └── github-repos.md # Repository links
├── examples/
│ ├── basic/
│ │ └── getting-started.ts # Basic setup example
│ ├── cheatcodes/
│ │ └── state-manipulation.ts # Cheatcode examples
│ └── iac/
│ └── deployment.tx # Infrastructure as Code example
├── templates/
│ ├── Surfpool.toml # Configuration template
│ └── test-setup.ts # Test setup template
└── docs/
└── troubleshooting.md # Common issues