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.
Overview
Testing programs with Ephemeral Rollups requires connecting to both the base layer (Solana) and the Ephemeral Rollup. This guide covers test patterns, environment detection, and running tests across different networks.
Test Architecture
Dual Provider Pattern
All examples use a dual provider pattern to interact with both layers:
import * as anchor from "@coral-xyz/anchor" ;
// Base Layer Provider (Solana)
const provider = anchor . AnchorProvider . env ();
anchor . setProvider ( provider );
// Ephemeral Rollup Provider
const providerEphemeralRollup = new anchor . AnchorProvider (
new anchor . web3 . Connection (
process . env . EPHEMERAL_PROVIDER_ENDPOINT || "https://devnet-as.magicblock.app/" ,
{
wsEndpoint: process . env . EPHEMERAL_WS_ENDPOINT || "wss://devnet-as.magicblock.app/" ,
}
),
anchor . Wallet . local ()
);
The base layer provider handles initialization, delegation, and undelegation. The Ephemeral Rollup provider handles high-speed transactions.
Test Workflow
A typical test suite follows this pattern:
Initialize on Base Layer
Create and initialize accounts on Solana: it ( "Initialize counter on Solana" , async () => {
const start = Date . now ();
let tx = await program . methods
. initialize ()
. accounts ({
user: provider . wallet . publicKey ,
})
. transaction ();
const txHash = await provider . sendAndConfirm ( tx , [ provider . wallet . payer ], {
skipPreflight: true ,
commitment: "confirmed" ,
});
const duration = Date . now () - start ;
console . log ( ` ${ duration } ms (Base Layer) Initialize txHash: ${ txHash } ` );
});
Delegate to Ephemeral Rollup
Delegate accounts to the Ephemeral Rollup: it ( "Delegate counter to ER" , async () => {
const start = Date . now ();
// Add local validator identity for localnet
const remainingAccounts =
providerEphemeralRollup . connection . rpcEndpoint . includes ( "localhost" ) ||
providerEphemeralRollup . connection . rpcEndpoint . includes ( "127.0.0.1" )
? [
{
pubkey: new web3 . PublicKey ( "mAGicPQYBMvcYveUZA5F5UNNwyHvfYh5xkLS2Fr1mev" ),
isSigner: false ,
isWritable: false ,
},
]
: [];
let tx = await program . methods
. delegate ()
. accounts ({
payer: provider . wallet . publicKey ,
pda: counterPDA ,
})
. remainingAccounts ( remainingAccounts )
. transaction ();
const txHash = await provider . sendAndConfirm ( tx , [ provider . wallet . payer ], {
skipPreflight: true ,
commitment: "confirmed" ,
});
const duration = Date . now () - start ;
console . log ( ` ${ duration } ms (Base Layer) Delegate txHash: ${ txHash } ` );
// Wait for delegation to propagate
await new Promise (( resolve ) => setTimeout ( resolve , 3000 ));
});
Always wait 2-3 seconds after delegation for the account to be available in the Ephemeral Rollup.
Execute on Ephemeral Rollup
Run high-speed transactions on the Ephemeral Rollup: it ( "Increase counter on ER" , async () => {
const start = Date . now ();
let tx = await program . methods
. increment ()
. accounts ({
counter: counterPDA ,
})
. transaction ();
tx . feePayer = providerEphemeralRollup . wallet . publicKey ;
tx . recentBlockhash = (
await providerEphemeralRollup . connection . getLatestBlockhash ()
). blockhash ;
tx = await providerEphemeralRollup . wallet . signTransaction ( tx );
const txHash = await providerEphemeralRollup . sendAndConfirm ( tx );
const duration = Date . now () - start ;
console . log ( ` ${ duration } ms (ER) Increment txHash: ${ txHash } ` );
});
Commit State
Commit Ephemeral Rollup state back to Solana: import { GetCommitmentSignature } from "@magicblock-labs/ephemeral-rollups-sdk" ;
it ( "Commit counter state on ER to Solana" , async () => {
const start = Date . now ();
let tx = await program . methods
. commit ()
. accounts ({
payer: providerEphemeralRollup . wallet . publicKey ,
})
. transaction ();
tx . feePayer = providerEphemeralRollup . wallet . publicKey ;
tx . recentBlockhash = (
await providerEphemeralRollup . connection . getLatestBlockhash ()
). blockhash ;
tx = await providerEphemeralRollup . wallet . signTransaction ( tx );
const txHash = await providerEphemeralRollup . sendAndConfirm ( tx , [], {
skipPreflight: true ,
});
const duration = Date . now () - start ;
console . log ( ` ${ duration } ms (ER) Commit txHash: ${ txHash } ` );
// Get the commitment signature on the base layer
const comfirmCommitStart = Date . now ();
const txCommitSgn = await GetCommitmentSignature (
txHash ,
providerEphemeralRollup . connection
);
const commitDuration = Date . now () - comfirmCommitStart ;
console . log ( ` ${ commitDuration } ms (Base Layer) Commit txHash: ${ txCommitSgn } ` );
});
Undelegate
Return accounts to Solana: import { createUndelegateInstruction } from "@magicblock-labs/ephemeral-rollups-sdk" ;
it ( "Increment and undelegate counter on ER to Solana" , async () => {
const start = Date . now ();
let tx = await program . methods
. incrementAndUndelegate ()
. accounts ({
payer: providerEphemeralRollup . wallet . publicKey ,
})
. transaction ();
tx . feePayer = provider . wallet . publicKey ;
tx . recentBlockhash = (
await providerEphemeralRollup . connection . getLatestBlockhash ()
). blockhash ;
tx = await providerEphemeralRollup . wallet . signTransaction ( tx );
const txHash = await providerEphemeralRollup . sendAndConfirm ( tx );
const duration = Date . now () - start ;
console . log ( ` ${ duration } ms (ER) Increment and Undelegate txHash: ${ txHash } ` );
});
Environment Detection
The fullstack-test.sh script automatically detects the cluster from Anchor.toml:
CLUSTER = $( grep -A 1 "^\[provider\]" " $ANCHOR_TOML " | grep "cluster" | sed 's/.*cluster = "\(.*\)".*/\1/' | tr -d ' ' )
For cluster = "localnet", the script:
Starts mb-test-validator on port 8899
Starts ephemeral-validator on port 7799
Airdrops SOL to the upgrade authority
Builds and deploys programs
Runs tests with:
EPHEMERAL_PROVIDER_ENDPOINT = http://localhost:7799 \
EPHEMERAL_WS_ENDPOINT=ws://localhost:7800 \
PROVIDER_ENDPOINT=http://localhost:8899 \
WS_ENDPOINT=http://localhost:8900 \
anchor test --skip-build --skip-deploy --skip-local-validator
For cluster = "devnet", the script:
Sets ANCHOR_PROVIDER_URL=https://api.devnet.solana.com
Uses remote Ephemeral Rollup endpoints
Runs tests directly:
yarn run ts-mocha --colors -p ./tsconfig.json -t 1000000 tests/ ** / * .ts
Running Tests
Basic Commands
Full Test (Auto-detect cluster)
Skip Deployment
Specific Test File
With Environment Variables
Using fullstack-test.sh
The fullstack test script provides automated testing with progress indicators:
Features:
Auto-detects cluster from Anchor.toml
Starts validators automatically for localnet
Shows progress with spinners and timings
Runs multiple examples in sequence
Cleans up validators on completion
The script will kill existing validators on ports 8899 and 7799 if running when cluster is localnet.
Test-Locally Script
For project-specific testing:
This script:
Checks for ephemeral-validator installation
Handles the --skip-local-validator flag
Manages validator lifecycle
Airdrops SOL for testing
Builds, deploys, and tests programs
Test Configuration
Package.json Scripts
{
"scripts" : {
"test" : "../fullstack-test.sh" ,
"lint:fix" : "prettier */*.js \" */**/*{.js,.ts} \" -w" ,
"lint" : "prettier */*.js \" */**/*{.js,.ts} \" --check"
},
"dependencies" : {
"@coral-xyz/anchor" : "0.32.1" ,
"@magicblock-labs/ephemeral-rollups-sdk" : "0.6.5"
},
"devDependencies" : {
"@types/mocha" : "^10.0.10" ,
"chai" : "^5.2.0" ,
"mocha" : "^11.2.2" ,
"ts-mocha" : "^11.1.0" ,
"typescript" : "^5.8.3"
}
}
TypeScript Configuration
Tests use ts-mocha with extended timeouts:
yarn ts-mocha --colors -p ./tsconfig.json -t 1000000 --exit tests/ ** / * .ts
Testing Different Networks
Localnet Testing
Set cluster in Anchor.toml:
[ provider ]
cluster = "localnet"
wallet = "~/.config/solana/id.json"
Run tests:
Devnet Testing
Set cluster in Anchor.toml:
[ provider ]
cluster = "devnet"
wallet = "~/.config/solana/id.json"
Use devnet Ephemeral Rollup:
EPHEMERAL_PROVIDER_ENDPOINT = https://devnet-as.magicblock.app/ \
EPHEMERAL_WS_ENDPOINT=wss://devnet-as.magicblock.app/ \
anchor test
Local ER with Devnet
Test against devnet but use a local Ephemeral Rollup:
# Start local ER pointing to devnet
ACCOUNTS_REMOTE = https://rpc.magicblock.app/devnet \
ACCOUNTS_LIFECYCLE=ephemeral \
ephemeral-validator
# Run tests
PROVIDER_ENDPOINT = http://localhost:8899 \
WS_ENDPOINT=ws://localhost:8900 \
anchor test --skip-build --skip-deploy --skip-local-validator
Test Utilities
Checking Balances
before ( async function () {
const balance = await provider . connection . getBalance (
anchor . Wallet . local (). publicKey
);
console . log ( "Current balance is" , balance / LAMPORTS_PER_SOL , " SOL" );
});
Timing Transactions
const start = Date . now ();
// ... transaction code
const duration = Date . now () - start ;
console . log ( ` ${ duration } ms (ER) Transaction completed` );
Environment Detection in Tests
const isLocalnet =
providerEphemeralRollup . connection . rpcEndpoint . includes ( "localhost" ) ||
providerEphemeralRollup . connection . rpcEndpoint . includes ( "127.0.0.1" );
const remainingAccounts = isLocalnet
? [{
pubkey: new web3 . PublicKey ( "mAGicPQYBMvcYveUZA5F5UNNwyHvfYh5xkLS2Fr1mev" ),
isSigner: false ,
isWritable: false ,
}]
: [];
Best Practices
Always wait after delegation : Wait 2-3 seconds after delegating accounts before executing transactions on the Ephemeral Rollup.
Use skipPreflight for commits : When committing state, use skipPreflight: true to avoid simulation failures.
Set proper timeouts : Use extended timeouts (-t 1000000) for mocha tests to accommodate network delays.
Track both layers : Always log transaction signatures and timings for both base layer and Ephemeral Rollup operations.
Debugging Tests
Enable Verbose Logging
RUST_LOG = debug anchor test
Check RPC Endpoints
console . log ( "Base Layer Connection:" , provider . connection . rpcEndpoint );
console . log ( "Ephemeral Rollup Connection:" , providerEphemeralRollup . connection . rpcEndpoint );
console . log ( "Wallet Public Key:" , anchor . Wallet . local (). publicKey . toString ());
View Transaction Details
const tx = await provider . connection . getTransaction ( txHash , {
commitment: "confirmed" ,
});
console . log ( "Transaction:" , JSON . stringify ( tx , null , 2 ));
Next Steps