Quick Access
Check out private transfer example:GitHub
Anchor Implementation
dApp (Coming soon!)
Play Now
Step-By-Step Guide
1
Write your Solana program as you normally.
2
Add CPI hooks that create and manage permissions.
3
Add delegation hooks for permissions and permissioned accounts to enforce
restrictions.
4
Deploy your Solana program using Anchor CLI.
5
Sign user message to retrieve authorization token from TEE endpoint.
6
Request for authorization token and send confidential transactions.
Rock-Paper-Scissor Example
The following software packages may be required, other versions may also be compatible:| 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 |
The latest Permission Program requires SDK version
>=0.8.0. See
migration
guide for
details.
Code Snippets
- 1. Write program
- 2. Restrict
- 3. Delegate
- 4. Deploy
- 5. Authorize
- 6. Test
A simple rock-paper-scissor program where player can make choices and reveal the outcome:⬆️ Back to Top
Copy
Ask AI
#[program]
pub mod anchor_rock_paper_scissor {
use super::*;
// 1️⃣ Create and auto-join as Player 1
pub fn create_game(ctx: Context<CreateGame>, game_id: u64) -> Result<()> {
let game = &mut ctx.accounts.game;
let player1 = ctx.accounts.player1.key();
game.game_id = game_id;
game.player1 = Some(player1);
game.player2 = None;
game.result = GameResult::None;
msg!("Game ID: {}", game_id);
msg!("Player 1 PDA: {}", player1);
// initialize PlayerChoice for player 1
let player_choice = &mut ctx.accounts.player_choice;
player_choice.game_id = game_id;
player_choice.player = player1;
player_choice.choice = None;
msg!("Game {} created and joined by {}", game_id, player1);
Ok(())
}
// 2️⃣ Player 2 joins the game
pub fn join_game(ctx: Context<JoinGame>, game_id: u64) -> Result<()> {
let game = &mut ctx.accounts.game;
let player = ctx.accounts.player.key();
require!(game.player1 != Some(player), GameError::CannotJoinOwnGame);
require!(game.player2.is_none(), GameError::GameFull);
game.player2 = Some(player);
// Create PlayerChoice PDA for player 2
let player_choice = &mut ctx.accounts.player_choice;
player_choice.game_id = game_id;
player_choice.player = player;
player_choice.choice = None;
msg!("{} joined Game {} as player 2", player, game_id);
Ok(())
}
// 3️⃣ Player makes a choice
pub fn make_choice(ctx: Context<MakeChoice>, _game_id: u64, choice: Choice) -> Result<()> {
let player_choice = &mut ctx.accounts.player_choice;
require!(player_choice.choice.is_none(), GameError::AlreadyChose);
player_choice.choice = choice.into();
msg!(
"Player {:?} made choice {:?}",
player_choice.player,
player_choice.choice
);
Ok(())
}
// 4️⃣ Reveal and record the winner
pub fn reveal_winner(ctx: Context<RevealWinner>) -> Result<()> {
let game = &mut ctx.accounts.game;
let player1_choice = &ctx.accounts.player1_choice;
let player2_choice = &ctx.accounts.player2_choice;
// 1️⃣ Clone choices into game
game.player1_choice = player1_choice.choice.clone().into();
game.player2_choice = player2_choice.choice.clone().into();
// 2️⃣ Ensure both players exist
let player1 = game.player1.ok_or(GameError::MissingOpponent)?;
let player2 = game.player2.ok_or(GameError::MissingOpponent)?;
// 3️⃣ Ensure both players made a choice
let choice1 = game
.player1_choice
.clone()
.ok_or(GameError::MissingChoice)?;
let choice2 = game
.player2_choice
.clone()
.ok_or(GameError::MissingChoice)?;
// 4️⃣ Determine winner based on choices
game.result = match (choice1, choice2) {
(Choice::Rock, Choice::Scissors)
| (Choice::Paper, Choice::Rock)
| (Choice::Scissors, Choice::Paper) => GameResult::Winner(player1),
(Choice::Rock, Choice::Paper)
| (Choice::Paper, Choice::Scissors)
| (Choice::Scissors, Choice::Rock) => GameResult::Winner(player2),
_ => GameResult::Tie,
};
msg!("Result: {:?}", &game.result);
Ok(())
}
}
#[derive(Accounts)]
#[instruction(game_id: u64)]
pub struct CreateGame<'info> {
#[account(
init_if_needed,
payer = player1,
space = 8 + Game::LEN,
seeds = [GAME_SEED, &game_id.to_le_bytes()],
bump
)]
pub game: Account<'info, Game>,
#[account(
init_if_needed,
payer = player1,
space = 8 + PlayerChoice::LEN,
seeds = [PLAYER_CHOICE_SEED, &game_id.to_le_bytes(), player1.key().as_ref()],
bump
)]
pub player_choice: Account<'info, PlayerChoice>,
#[account(mut)]
pub player1: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
#[instruction(game_id: u64)]
pub struct JoinGame<'info> {
#[account(
mut,
seeds = [GAME_SEED, &game_id.to_le_bytes()],
bump
)]
pub game: Account<'info, Game>,
#[account(
init_if_needed,
payer = player,
space = 8 + PlayerChoice::LEN,
seeds = [PLAYER_CHOICE_SEED, &game_id.to_le_bytes(), player.key().as_ref()],
bump
)]
pub player_choice: Account<'info, PlayerChoice>,
#[account(mut)]
pub player: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
#[instruction(game_id: u64)]
pub struct MakeChoice<'info> {
#[account(
mut,
seeds = [PLAYER_CHOICE_SEED, &game_id.to_le_bytes(), player.key().as_ref()],
bump
)]
pub player_choice: Account<'info, PlayerChoice>,
#[account(mut)]
pub player: Signer<'info>,
}
#[derive(Accounts)]
pub struct RevealWinner<'info> {
#[account(mut, seeds = [GAME_SEED, &game.game_id.to_le_bytes()], bump)]
pub game: Account<'info, Game>,
/// Player1's choice PDA (derived automatically)
#[account(
mut,
seeds = [PLAYER_CHOICE_SEED, &game.game_id.to_le_bytes(), game.player1.unwrap().as_ref()],
bump
)]
pub player1_choice: Account<'info, PlayerChoice>,
/// Player2's choice PDA (derived automatically)
#[account(
mut,
seeds = [PLAYER_CHOICE_SEED, &game.game_id.to_le_bytes(), game.player2.unwrap().as_ref()],
bump
)]
pub player2_choice: Account<'info, PlayerChoice>,
#[account(mut)]
pub payer: Signer<'info>,
}
#[account]
pub struct Game {
pub game_id: u64,
pub player1: Option<Pubkey>,
pub player2: Option<Pubkey>,
pub player1_choice: Option<Choice>,
pub player2_choice: Option<Choice>,
pub result: GameResult,
}
impl Game {
pub const LEN: usize = 8 // game_id
+ (32 + 1) * 2 // player1, player2
+ (1 + 1) * 2 // player1_choice, player2_choice
+ (1 + 32); // result (1 byte tag + 32 bytes pubkey for Winner variant)
}
#[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, Eq, Debug)]
pub enum GameResult {
Winner(Pubkey),
Tie,
None,
}
#[account]
pub struct PlayerChoice {
pub game_id: u64,
pub player: Pubkey,
pub choice: Option<Choice>,
}
impl PlayerChoice {
pub const LEN: usize = 8 + 8 + 32 + 2;
}
#[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, Eq, Debug)]
pub enum Choice {
Rock,
Paper,
Scissors,
}
#[error_code]
pub enum GameError {
#[msg("You already made your choice.")]
AlreadyChose,
#[msg("You cannot join your own game.")]
CannotJoinOwnGame,
#[msg("Both players must make a choice first.")]
MissingChoice,
#[msg("Opponent not found.")]
MissingOpponent,
#[msg("Game is already full.")]
GameFull,
}
/// ... Other context and accounts for delegation and privacy
Create and update account-level permissions with members:⬆️ Back to Top
Copy
Ask AI
#[program]
pub mod anchor_rock_paper_scissor {
use super::*;
// 4️⃣ Reveal and record the winner
pub fn reveal_winner(ctx: Context<RevealWinner>) -> Result<()> {
let game = &mut ctx.accounts.game;
let player1_choice = &ctx.accounts.player1_choice;
let player2_choice = &ctx.accounts.player2_choice;
let permission_program = &ctx.accounts.permission_program.to_account_info();
let permission_game = &ctx.accounts.permission_game.to_account_info();
let permission1 = &ctx.accounts.permission1.to_account_info();
let permission2 = &ctx.accounts.permission2.to_account_info();
let magic_program = &ctx.accounts.magic_program.to_account_info();
let magic_context = &ctx.accounts.magic_context.to_account_info();
// 1️⃣ Clone choices into game
game.player1_choice = player1_choice.choice.clone().into();
game.player2_choice = player2_choice.choice.clone().into();
// 2️⃣ Ensure both players exist
let player1 = game.player1.ok_or(GameError::MissingOpponent)?;
let player2 = game.player2.ok_or(GameError::MissingOpponent)?;
// 3️⃣ Ensure both players made a choice
let choice1 = game
.player1_choice
.clone()
.ok_or(GameError::MissingChoice)?;
let choice2 = game
.player2_choice
.clone()
.ok_or(GameError::MissingChoice)?;
// 4️⃣ Determine winner based on choices
game.result = match (choice1, choice2) {
(Choice::Rock, Choice::Scissors)
| (Choice::Paper, Choice::Rock)
| (Choice::Scissors, Choice::Paper) => GameResult::Winner(player1),
(Choice::Rock, Choice::Paper)
| (Choice::Paper, Choice::Scissors)
| (Choice::Scissors, Choice::Rock) => GameResult::Winner(player2),
_ => GameResult::Tie,
};
// Update Permissions for reveal
UpdatePermissionCpiBuilder::new(&permission_program)
.permissioned_account(&game.to_account_info(), true)
.authority(&game.to_account_info(), false)
.permission(&permission_game.to_account_info())
.args(MembersArgs { members: None })
.invoke_signed(&[&[GAME_SEED, &game.game_id.to_le_bytes(), &[ctx.bumps.game]]])?;
UpdatePermissionCpiBuilder::new(&permission_program)
.permissioned_account(&player1_choice.to_account_info(), true)
.authority(&player1_choice.to_account_info(), false)
.permission(&permission1.to_account_info())
.args(MembersArgs { members: None })
.invoke_signed(&[&[
PLAYER_CHOICE_SEED,
&player1_choice.game_id.to_le_bytes(),
&player1_choice.player.as_ref(),
&[ctx.bumps.player1_choice],
]])?;
UpdatePermissionCpiBuilder::new(&permission_program)
.permissioned_account(&player2_choice.to_account_info(), true)
.authority(&player2_choice.to_account_info(), false)
.permission(&permission2.to_account_info())
.args(MembersArgs { members: None })
.invoke_signed(&[&[
PLAYER_CHOICE_SEED,
&player2_choice.game_id.to_le_bytes(),
&player2_choice.player.as_ref(),
&[ctx.bumps.player2_choice],
]])?;
msg!("Result: {:?}", &game.result);
Ok(())
}
/// Creates a permission based on account type input.
/// Derives the bump from the account type and seeds, then calls the permission program.
pub fn create_permission(
ctx: Context<CreatePermission>,
account_type: AccountType,
members: Option<Vec<Member>>,
) -> Result<()> {
let CreatePermission {
permissioned_account,
permission,
payer,
permission_program,
system_program,
} = ctx.accounts;
let seed_data = derive_seeds_from_account_type(&account_type);
let (_, bump) = Pubkey::find_program_address(
&seed_data.iter().map(|s| s.as_slice()).collect::<Vec<_>>(),
&crate::ID,
);
let mut seeds = seed_data.clone();
seeds.push(vec![bump]);
let seed_refs: Vec<&[u8]> = seeds.iter().map(|s| s.as_slice()).collect();
CreatePermissionCpiBuilder::new(&permission_program)
.permissioned_account(&permissioned_account.to_account_info())
.permission(&permission)
.payer(&payer)
.system_program(&system_program)
.args(MembersArgs { members })
.invoke_signed(&[seed_refs.as_slice()])?;
Ok(())
}
}
#[derive(Accounts)]
pub struct RevealWinner<'info> {
#[account(mut, seeds = [GAME_SEED, &game.game_id.to_le_bytes()], bump)]
pub game: Account<'info, Game>,
/// Player1's choice PDA (derived automatically)
#[account(
mut,
seeds = [PLAYER_CHOICE_SEED, &game.game_id.to_le_bytes(), game.player1.unwrap().as_ref()],
bump
)]
pub player1_choice: Account<'info, PlayerChoice>,
/// Player2's choice PDA (derived automatically)
#[account(
mut,
seeds = [PLAYER_CHOICE_SEED, &game.game_id.to_le_bytes(), game.player2.unwrap().as_ref()],
bump
)]
pub player2_choice: Account<'info, PlayerChoice>,
/// CHECK: Checked by the permission program
#[account(mut)]
pub permission_game: UncheckedAccount<'info>,
/// CHECK: Checked by the permission program
#[account(mut)]
pub permission1: UncheckedAccount<'info>,
/// CHECK: Checked by the permission program
#[account(mut)]
pub permission2: UncheckedAccount<'info>,
/// Anyone can trigger this
#[account(mut)]
pub payer: Signer<'info>,
/// CHECK: PERMISSION PROGRAM
#[account(address = PERMISSION_PROGRAM_ID)]
pub permission_program: UncheckedAccount<'info>,
}
#[derive(Accounts)]
pub struct CreatePermission<'info> {
/// CHECK: Validated via permission program CPI
pub permissioned_account: UncheckedAccount<'info>,
/// CHECK: Checked by the permission program
#[account(mut)]
pub permission: UncheckedAccount<'info>,
#[account(mut)]
pub payer: Signer<'info>,
/// CHECK: PERMISSION PROGRAM
#[account(address = PERMISSION_PROGRAM_ID)]
pub permission_program: UncheckedAccount<'info>,
pub system_program: Program<'info, System>,
}
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub enum AccountType {
Game { game_id: u64 },
PlayerChoice { game_id: u64, player: Pubkey },
}
fn derive_seeds_from_account_type(account_type: &AccountType) -> Vec<Vec<u8>> {
match account_type {
AccountType::Game { game_id } => {
vec![GAME_SEED.to_vec(), game_id.to_le_bytes().to_vec()]
}
AccountType::PlayerChoice { game_id, player } => {
vec![
PLAYER_CHOICE_SEED.to_vec(),
game_id.to_le_bytes().to_vec(),
player.to_bytes().to_vec(),
]
}
}
}
/// Other context and accounts ...
Enforce privacy of accounts through delegation to TEE validator, both permission and permissioned_account must be delegated.
This is the list of ER validator:⬆️ Back to Top
These public validators are supported for development. Make sure to add the specific ER validator in your instruction when delegating:
- Asia (devnet-as.magicblock.app):
MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57 - EU (devnet-eu.magicblock.app):
MEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e - US (devnet-us.magicblock.app):
MUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd - TEE (tee.magicblock.app):
FnE6VJT5QNZdedZPnCoLsARgBwoE6DeJNjBs2H1gySXA - Local ER (localhost):
mAGicPQYBMvcYveUZA5F5UNNwyHvfYh5xkLS2Fr1mev
Copy
Ask AI
#[ephemeral] // This adds undelegation instruction for ER validator
#[program]
pub mod anchor_rock_paper_scissor {
use super::*;
// Other instructions
// 4️⃣ Reveal and record the winner
pub fn reveal_winner(ctx: Context<RevealWinner>) -> Result<()> {
let game = &mut ctx.accounts.game;
let player1_choice = &ctx.accounts.player1_choice;
let player2_choice = &ctx.accounts.player2_choice;
let permission_program = &ctx.accounts.permission_program.to_account_info();
let permission_game = &ctx.accounts.permission_game.to_account_info();
let permission1 = &ctx.accounts.permission1.to_account_info();
let permission2 = &ctx.accounts.permission2.to_account_info();
let magic_program = &ctx.accounts.magic_program.to_account_info();
let magic_context = &ctx.accounts.magic_context.to_account_info();
// 1️⃣ Clone choices into game
game.player1_choice = player1_choice.choice.clone().into();
game.player2_choice = player2_choice.choice.clone().into();
// 2️⃣ Ensure both players exist
let player1 = game.player1.ok_or(GameError::MissingOpponent)?;
let player2 = game.player2.ok_or(GameError::MissingOpponent)?;
// 3️⃣ Ensure both players made a choice
let choice1 = game
.player1_choice
.clone()
.ok_or(GameError::MissingChoice)?;
let choice2 = game
.player2_choice
.clone()
.ok_or(GameError::MissingChoice)?;
// 4️⃣ Determine winner based on choices
game.result = match (choice1, choice2) {
(Choice::Rock, Choice::Scissors)
| (Choice::Paper, Choice::Rock)
| (Choice::Scissors, Choice::Paper) => GameResult::Winner(player1),
(Choice::Rock, Choice::Paper)
| (Choice::Paper, Choice::Scissors)
| (Choice::Scissors, Choice::Rock) => GameResult::Winner(player2),
_ => GameResult::Tie,
};
UpdatePermissionCpiBuilder::new(&permission_program)
.permissioned_account(&game.to_account_info(), true)
.authority(&game.to_account_info(), false)
.permission(&permission_game.to_account_info())
.args(MembersArgs { members: None })
.invoke_signed(&[&[GAME_SEED, &game.game_id.to_le_bytes(), &[ctx.bumps.game]]])?;
UpdatePermissionCpiBuilder::new(&permission_program)
.permissioned_account(&player1_choice.to_account_info(), true)
.authority(&player1_choice.to_account_info(), false)
.permission(&permission1.to_account_info())
.args(MembersArgs { members: None })
.invoke_signed(&[&[
PLAYER_CHOICE_SEED,
&player1_choice.game_id.to_le_bytes(),
&player1_choice.player.as_ref(),
&[ctx.bumps.player1_choice],
]])?;
UpdatePermissionCpiBuilder::new(&permission_program)
.permissioned_account(&player2_choice.to_account_info(), true)
.authority(&player2_choice.to_account_info(), false)
.permission(&permission2.to_account_info())
.args(MembersArgs { members: None })
.invoke_signed(&[&[
PLAYER_CHOICE_SEED,
&player2_choice.game_id.to_le_bytes(),
&player2_choice.player.as_ref(),
&[ctx.bumps.player2_choice],
]])?;
msg!("Result: {:?}", &game.result);
game.exit(&crate::ID)?;
commit_and_undelegate_accounts(
&ctx.accounts.payer,
vec![&game.to_account_info()],
magic_context,
magic_program,
)?;
Ok(())
}
/// Delegate account to the delegation program based on account type
/// Set specific validator based on ER, see https://docs.magicblock.gg/pages/get-started/how-integrate-your-program/local-setup
pub fn delegate_pda(ctx: Context<DelegatePda>, account_type: AccountType) -> Result<()> {
let seed_data = derive_seeds_from_account_type(&account_type);
let seeds_refs: Vec<&[u8]> = seed_data.iter().map(|s| s.as_slice()).collect();
let validator = ctx.accounts.validator.as_ref().map(|v| v.key());
ctx.accounts.delegate_pda(
&ctx.accounts.payer,
&seeds_refs,
DelegateConfig {
validator,
..Default::default()
},
)?;
Ok(())
}
}
#[commit] // adding magic_context and magic_program accounts to context
#[derive(Accounts)]
pub struct RevealWinner<'info> {
#[account(mut, seeds = [GAME_SEED, &game.game_id.to_le_bytes()], bump)]
pub game: Account<'info, Game>,
/// Player1's choice PDA (derived automatically)
#[account(
mut,
seeds = [PLAYER_CHOICE_SEED, &game.game_id.to_le_bytes(), game.player1.unwrap().as_ref()],
bump
)]
pub player1_choice: Account<'info, PlayerChoice>,
/// Player2's choice PDA (derived automatically)
#[account(
mut,
seeds = [PLAYER_CHOICE_SEED, &game.game_id.to_le_bytes(), game.player2.unwrap().as_ref()],
bump
)]
pub player2_choice: Account<'info, PlayerChoice>,
/// CHECK: Checked by the permission program
#[account(mut)]
pub permission_game: UncheckedAccount<'info>,
/// CHECK: Checked by the permission program
#[account(mut)]
pub permission1: UncheckedAccount<'info>,
/// CHECK: Checked by the permission program
#[account(mut)]
pub permission2: UncheckedAccount<'info>,
/// Anyone can trigger this
#[account(mut)]
pub payer: Signer<'info>,
/// CHECK: PERMISSION PROGRAM
#[account(address = PERMISSION_PROGRAM_ID)]
pub permission_program: UncheckedAccount<'info>,
}
/// Unified delegate PDA context
#[delegate] // enable delegation
#[derive(Accounts)]
pub struct DelegatePda<'info> {
/// CHECK: The PDA to delegate
#[account(mut, del)]
pub pda: AccountInfo<'info>,
pub payer: Signer<'info>,
/// CHECK: Checked by the delegate program
pub validator: Option<AccountInfo<'info>>,
}
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub enum AccountType {
Game { game_id: u64 },
PlayerChoice { game_id: u64, player: Pubkey },
}
fn derive_seeds_from_account_type(account_type: &AccountType) -> Vec<Vec<u8>> {
match account_type {
AccountType::Game { game_id } => {
vec![GAME_SEED.to_vec(), game_id.to_le_bytes().to_vec()]
}
AccountType::PlayerChoice { game_id, player } => {
vec![
PLAYER_CHOICE_SEED.to_vec(),
game_id.to_le_bytes().to_vec(),
player.to_bytes().to_vec(),
]
}
}
}
Now you’re program is upgraded and ready! Build and deploy to the desired cluster:⬆️ Back to Top
Copy
Ask AI
anchor build && anchor deploy
Set up interaction with ER RPC in TEE:⬆️ Back to Top
- Verify integrity of TEE RPC via
https://pccs.phala.network/tdx/certification/v4 - Request an authorization token for user to interact with TEE endpoint
web3js
Copy
Ask AI
import {
verifyTeeRpcIntegrity,
getAuthToken,
} from "@magicblock-labs/ephemeral-rollups-sdk";
// Verify the integrity of TEE RPC
const isVerified = await verifyTeeRpcIntegrity(EPHEMERAL_RPC_URL);
// Get AuthToken before making request to TEE
const token = await getAuthToken(
EPHEMERAL_RPC_URL,
wallet.publicKey,
(message: Uint8Array) =>
Promise.resolve(nacl.sign.detached(message, wallet.secretKey))
);
Test your program with the Private Ephemeral Rollup connection:⬆️ Back to Top
https://tee.magicblock.app?token=${token}These public validators are supported for development. Make sure to add the specific ER validator in your instruction when delegating:
- Asia (devnet-as.magicblock.app):
MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57 - EU (devnet-eu.magicblock.app):
MEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e - US (devnet-us.magicblock.app):
MUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd - TEE (tee.magicblock.app):
FnE6VJT5QNZdedZPnCoLsARgBwoE6DeJNjBs2H1gySXA - Local ER (localhost):
mAGicPQYBMvcYveUZA5F5UNNwyHvfYh5xkLS2Fr1mev
Quick Access
Check out private transfer example:GitHub
Anchor Implementation
dApp (Coming soon!)
Play Now
Solana Explorer
Get insights about your transactions and accounts on Solana:Solana RPC Providers
Send transactions and requests through existing RPC providers:Solana Validator Dashboard
Find real-time updates on Solana’s validator infrastructure:Server Status
Subscribe to Solana’s and MagicBlock’s server status:Solana Status
Subscribe to Solana Server Updates
MagicBlock Status
Subscribe to MagicBlock Server Status
MagicBlock Products
Ephemeral Rollup (ER)
Execute real-time, zero-fee transactions securely on Solana.
Private Ephemeral Rollup (PER)
Protect sensitive data with privacy-preserving computation.
Verifiable Randomness Function (VRF)
Generate provably fair randomness directly on-chain.

