Skip to main content

Quick Access

Explore private program and test scripts for Anchor, Native Rust, and Pinocchio:

Anchor Private Counter

Anchor implementation with permissions.

Pinocchio Private Counter

Pinocchio implementation with magic permission accounts.

Why a Local PER Setup Is Different

In production a Private Ephemeral Rollup runs the Ephemeral Rollup inside a Trusted Execution Environment (TEE) on Intel TDX. Clients don’t reach the ER directly — they connect through a token-gated TEE endpoint that enforces privacy and compliance (IP geofencing, OFAC screening, and per-account permission filtering) at ingress, before any transaction is accepted or executed. You can’t run real TDX hardware locally. Instead, the TEE ingress is emulated by a dedicated process — the Query Filtering Service (QFS) — which replicates the same token-auth and permission-filtering logic without hardware attestation, and fronts an ordinary local ephemeral validator. That is the only structural difference from a plain Ephemeral Rollup local setup: you run the QFS in front of the ER and point your client at the QFS instead of the ER directly.

Topology

Four processes, each fronting the next:
ProcessRPC / WSRole
mb-test-validator8899 / 8900Base layer. Wraps solana-test-validator and pre-clones the MagicBlock delegation + permission programs.
ephemeral-validator7799 / 7800The Ephemeral Rollup itself.
query-filtering-service6699 / 6700PER-specific. Emulates the TEE ingress — token auth + permission filtering.
vrf-oracle (optional)Only needed if the program under test uses VRF.
Point your client at the QFS (6699) to test privacy features. Point it at the ER (7799) directly when you don’t need the privacy layer.

Quickstart with mb-stack

mb-stack is a single CLI that wraps mb-test-validator, ephemeral-validator, and query-filtering-service, and handles the startup ordering between them for you, in a single command. It ships in the same package as ephemeral-validator.
1

Install

npm install -g @magicblock-labs/ephemeral-validator@latest
2

Bring up the full stack

mb-stack --reset
This starts mb-test-validator (8899 / 8900), ephemeral-validator (7799 / 7800), and query-filtering-service (6699 / 6700) together, on the same default ports used throughout this guide.
3

Deploy your program

Once the stack is up, deploy against the base layer as usual:
anchor build && anchor deploy --provider.cluster localnet

Per-Service Setup

1

Install the binaries

mb-stack, ephemeral-validator, query-filtering-service, and mb-test-validator ship together:
npm install -g @magicblock-labs/ephemeral-validator@latest
2

Start the base validator (port 8899)

mb-test-validator --reset
3

Deploy or preload your program

anchor build && anchor deploy --provider.cluster localnet
4

Start the ephemeral validator (port 7799)

ephemeral-validator \
  --lifecycle ephemeral \
  --remotes http://127.0.0.1:8899 \
  --remotes ws://127.0.0.1:8900 \
  --listen 127.0.0.1:7799 \
  --reset
5

Start the Query Filtering Service (port 6699)

This is the process that makes it a private rollup. It points upstream at the ER and exposes the client-facing endpoint:
RUST_LOG=info query-filtering-service \
  --listen-addr 127.0.0.1:6699 \
  --listen-addr-ws 127.0.0.1:6700 \
  --ephemeral-url http://127.0.0.1:7799 \
  --ephemeral-url-ws ws://127.0.0.1:7800 \
  --token-expiry-days 180 \
  --add-cors-headers
--add-cors-headers is required if you test from a browser app. Wait for port 6699 to accept a connection before firing any request.
6

(Optional) Start the VRF oracle

If your program requests randomness, run one oracle against the base layer and one against the ER:
# Base-layer VRF requests
VRF_ORACLE_SKIP_PREFLIGHT=true RPC_URL=http://localhost:8899 \
  WEBSOCKET_URL=ws://localhost:8900 RUST_LOG=info vrf-oracle &

# ER VRF requests
VRF_ORACLE_SKIP_PREFLIGHT=true RPC_URL=http://localhost:7799 \
  WEBSOCKET_URL=ws://localhost:7800 RUST_LOG=info vrf-oracle &

Connect Your Client Through the QFS

Your client treats the QFS endpoint as the TEE endpoint: point TEE_PROVIDER_ENDPOINT at http://localhost:6699 and reuse the same authorization flow from the quickstart — fetch a token by signing a challenge, then open the connection with ?token=... attached. When a PDA is set to private via its EphemeralPermission, the QFS blocks any wallet not in the member list — exactly as the TEE would on devnet.
import { getAuthToken } from "@magicblock-labs/ephemeral-rollups-sdk";
import nacl from "tweetnacl";

// Locally, TEE_PROVIDER_ENDPOINT = http://localhost:6699 (the QFS).
const teeUrl =
  process.env.TEE_PROVIDER_ENDPOINT || "https://devnet-tee.magicblock.app";
const teeWsUrl =
  process.env.TEE_WS_ENDPOINT || "wss://devnet-tee.magicblock.app";

const token = await getAuthToken(
  teeUrl,
  payer.publicKey,
  (message: Uint8Array) =>
    Promise.resolve(nacl.sign.detached(message, payer.secretKey)),
);

const erProvider = new anchor.AnchorProvider(
  new anchor.web3.Connection(`${teeUrl}?token=${token.token}`, {
    wsEndpoint: `${teeWsUrl}?token=${token.token}`,
    commitment: "confirmed",
  }),
  anchor.Wallet.local(),
);
verifyTeeRpcIntegrity checks a real TDX attestation and is meant for the devnet/mainnet TEE endpoints. The local QFS has no hardware attestation, so skip that check when running fully local.

Delegate to the Local Validator Identity

When delegating your PDA in a local test, delegate to the localnet ER identity — not a devnet/mainnet one — so commits and undelegations settle correctly:
mAGicPQYBMvcYveUZA5F5UNNwyHvfYh5xkLS2Fr1mev

These public validators are supported for development. Make sure to add the specific ER validator in your delegation instruction:

Mainnet
  • Asia (as.magicblock.app): MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57
  • EU (eu.magicblock.app): MEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e
  • US (us.magicblock.app): MUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd
  • TEE (mainnet-tee.magicblock.app): MTEWGuqxUpYZGFJQcp8tLN7x5v9BSeoFHYWQQ3n3xzo
Devnet
  • Asia (devnet-as.magicblock.app): MAS1Dt9qreoRMQ14YQuhg8UTZMMzDdKhmkZMECCzk57
  • EU (devnet-eu.magicblock.app): MEUGGrYPxKk17hCr7wpT6s8dtNokZj5U2L57vjYMS8e
  • US (devnet-us.magicblock.app): MUS3hc9TCw4cGC12vHNoYcCGzJG1txjgQLZWVoeNHNd
  • TEE (devnet-tee.magicblock.app): MTEWGuqxUpYZGFJQcp8tLN7x5v9BSeoFHYWQQ3n3xzo
Localnet
  • Local ER (localhost:7799): mAGicPQYBMvcYveUZA5F5UNNwyHvfYh5xkLS2Fr1mev

Endpoint Environment Variables

Centralize the endpoints so tests hit the local cluster instead of silently falling back to devnet. The PER-relevant variables:
export PROVIDER_ENDPOINT=http://localhost:8899            # base layer
export WS_ENDPOINT=ws://localhost:8900
export EPHEMERAL_PROVIDER_ENDPOINT=http://localhost:7799  # ER direct
export EPHEMERAL_WS_ENDPOINT=ws://localhost:7800
export QFS_ENDPOINT=http://localhost:6699                 # QFS
export QFS_WS_ENDPOINT=ws://localhost:6700
export TEE_PROVIDER_ENDPOINT=$QFS_ENDPOINT                # private tests read this
export TEE_WS_ENDPOINT=$QFS_WS_ENDPOINT
export VALIDATOR=mAGicPQYBMvcYveUZA5F5UNNwyHvfYh5xkLS2Fr1mev