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 Bolt ECS (Entity Component System) framework with Ephemeral Rollups. This example shows how to create delegatable components, apply systems, and manage entities in a game-like architecture.
What You’ll Learn
How to create Bolt components with the #[component(delegate)] attribute
How to build systems that operate on components
How to initialize a Bolt world and add entities
How to delegate component accounts to Ephemeral Rollups
How to apply systems for low-latency execution
How to undelegate components back to Solana
Program Structure
The Bolt counter example consists of two main parts:
Component
Counter - A delegatable component that stores a count value
System
Increase - A system that increments the counter component by 1
Software Requirements
Bolt has its own build tool. Ensure you have it installed before building.
Read more about the Bolt framework: Bolt Documentation
Build and Test
Build the program
Build using the Bolt CLI:
Configure Ephemeral Rollup endpoints
Set the environment variables for the Ephemeral Rollup: export PROVIDER_ENDPOINT = "<provided endpoint>"
export WS_ENDPOINT = "<provided endpoint>"
Run tests
Execute the test suite (skip deploy if already deployed):
Component Implementation
The Counter component uses the #[component(delegate)] attribute to make it delegatable:
use bolt_lang ::* ;
declare_id! ( "8G57v8BL4myb9FtXwLiwionAGZcZBGno2Ckps2AsGXwV" );
#[component(delegate)]
#[derive( Default )]
pub struct Counter {
pub count : u64 ,
}
The #[component(delegate)] attribute automatically generates the necessary code to make this component delegatable to Ephemeral Rollups.
System Implementation
The Increase system operates on the Counter component:
use bolt_lang ::* ;
use counter :: Counter ;
declare_id! ( "4uNf52XJbJCqSofxkuYbjTC1DaYinpoUixaQQhrNPZkg" );
#[system]
pub mod increase {
pub fn execute ( ctx : Context < Components >, _args_p : Vec < u8 >) -> Result < Components > {
let counter = & mut ctx . accounts . counter;
counter . count += 1 ;
Ok ( ctx . accounts)
}
#[system_input]
pub struct Components {
pub counter : Counter ,
}
}
Systems in Bolt are pure functions that take components as input and return the modified components.
TypeScript Client Usage
Initialize World and Entity
import * as anchor from "@coral-xyz/anchor" ;
import { Program } from "@coral-xyz/anchor" ;
import { PublicKey } from "@solana/web3.js" ;
import {
InitializeNewWorld ,
AddEntity ,
InitializeComponent ,
ApplySystem ,
FindComponentPda ,
createUndelegateInstruction ,
createDelegateInstruction ,
} from "@magicblock-labs/bolt-sdk" ;
const provider = anchor . AnchorProvider . env ();
const counterComponent = anchor . workspace . Counter as Program < Counter >;
const systemIncrease = anchor . workspace . Increase as Program < Increase >;
let worldPda : PublicKey ;
let entityPda : PublicKey ;
// Initialize a new world
const initNewWorld = await InitializeNewWorld ({
payer: provider . wallet . publicKey ,
connection: provider . connection ,
});
const txSign = await provider . sendAndConfirm ( initNewWorld . transaction );
worldPda = initNewWorld . worldPda ;
// Add an entity
const addEntity = await AddEntity ({
payer: provider . wallet . publicKey ,
world: worldPda ,
connection: provider . connection ,
});
const entityTxSign = await provider . sendAndConfirm ( addEntity . transaction );
entityPda = addEntity . entityPda ;
// Add the counter component to the entity
const initComponent = await InitializeComponent ({
payer: provider . wallet . publicKey ,
entity: entityPda ,
componentId: counterComponent . programId ,
});
await provider . sendAndConfirm ( initComponent . transaction );
Delegate Component to Ephemeral Rollups
const counterPda = FindComponentPda ({
componentId: counterComponent . programId ,
entity: entityPda ,
});
const delegateIx = createDelegateInstruction ({
entity: entityPda ,
account: counterPda ,
ownerProgram: counterComponent . programId ,
payer: provider . wallet . publicKey ,
});
const tx = new anchor . web3 . Transaction (). add ( delegateIx );
tx . feePayer = provider . wallet . publicKey ;
tx . recentBlockhash = (
await provider . connection . getLatestBlockhash ({ commitment: "confirmed" })
). blockhash ;
const txSign = await provider . sendAndConfirm ( tx , [], {
commitment: "confirmed" ,
skipPreflight: true ,
});
Apply System on Ephemeral Rollups
const providerEphemeralRollup = new anchor . AnchorProvider (
new anchor . web3 . Connection (
process . env . PROVIDER_ENDPOINT || "https://devnet.magicblock.app" ,
{
wsEndpoint: process . env . WS_ENDPOINT || "wss://devnet.magicblock.app" ,
}
),
anchor . Wallet . local ()
);
const applySystem = await ApplySystem ({
authority: providerEphemeralRollup . wallet . publicKey ,
world: worldPda ,
entities: [
{
entity: entityPda ,
components: [{ componentId: counterComponent . programId }],
},
],
systemId: systemIncrease . programId ,
});
const tx = applySystem . transaction ;
tx . feePayer = provider . wallet . publicKey ;
tx . recentBlockhash = (
await providerEphemeralRollup . connection . getLatestBlockhash ()
). blockhash ;
const txSign = await providerEphemeralRollup . sendAndConfirm ( tx , [], {
skipPreflight: true ,
});
console . log ( "Applied system:" , txSign );
Systems are applied to entities, and Bolt automatically loads the required components for execution.
Undelegate Component
const counterComponentPda = FindComponentPda ({
componentId: counterComponent . programId ,
entity: entityPda ,
});
const undelegateIx = createUndelegateInstruction ({
payer: provider . wallet . publicKey ,
delegatedAccount: counterComponentPda ,
componentPda: counterComponent . programId ,
});
let tx = new anchor . web3 . Transaction (). add ( undelegateIx );
tx . feePayer = provider . wallet . publicKey ;
tx . recentBlockhash = (
await providerEphemeralRollup . connection . getLatestBlockhash ()
). blockhash ;
tx = await providerEphemeralRollup . wallet . signTransaction ( tx );
const txSign = await providerEphemeralRollup . sendAndConfirm ( tx , [], {
skipPreflight: false ,
});
Running with Local Ephemeral Rollup
Install the local validator
npm install -g @magicblock-labs/ephemeral-validator
Start the local validator
Run the validator pointing to devnet as the reference: ACCOUNTS_REMOTE = https://rpc.magicblock.app/devnet ACCOUNTS_LIFECYCLE = ephemeral ephemeral-validator
Run tests with local validator
Point the tests to the local validator: PROVIDER_ENDPOINT = http://localhost:8899 WS_ENDPOINT = ws://localhost:8900 anchor test --skip-build --skip-deploy --skip-local-validator
Key Features
ECS Architecture Entity-Component-System pattern for composable game logic
Delegatable Components Components can be delegated to Ephemeral Rollups with a single attribute
Composable Systems Systems operate on components and can be combined for complex behaviors
Built for Games Designed specifically for onchain game development patterns
Bolt is optimized for ECS patterns. For simpler use cases, consider using Anchor or native Rust instead.
ECS Concepts
Entities
Entities are unique identifiers (PDAs) that group components together. They represent game objects or actors.
Components
Components are data containers attached to entities. In this example, Counter is a component that stores a count value.
Systems
Systems contain the logic that operates on components. The Increase system increments the counter.
World
A world is the top-level container that manages entities and their components.
Source Code
View the complete source code on GitHub:
bolt-counter on GitHub