From ebe8faf9e62ec730b31010c357bd956b24c42a91 Mon Sep 17 00:00:00 2001 From: dhruvja Date: Thu, 9 Jul 2026 00:01:01 +0530 Subject: [PATCH 1/2] fix: correct copy-paste breakers in session-keys, VRF, and ER quickstarts --- .../how-to-guide/quickstart.mdx | 7 +- .../integrating-sessions-in-your-program.mdx | 2 +- pages/tools/session-keys/usage-examples.mdx | 2 +- .../session-keys/use-sessionkey-manager.mdx | 2 +- .../how-to-guide/quickstart.mdx | 16 ++-- .../introduction/security.mdx | 2 +- .../introduction/solana-vrf.mdx | 2 +- .../introduction/technical-details.mdx | 2 +- snippets/notes/endpoints.mdx | 4 +- .../request-consume-randomness.mdx | 31 ++++++-- snippets/roll-dice-code/test.mdx | 74 +++++++++++++++---- 11 files changed, 106 insertions(+), 38 deletions(-) diff --git a/pages/ephemeral-rollups-ers/how-to-guide/quickstart.mdx b/pages/ephemeral-rollups-ers/how-to-guide/quickstart.mdx index 70f8586..10566c0 100644 --- a/pages/ephemeral-rollups-ers/how-to-guide/quickstart.mdx +++ b/pages/ephemeral-rollups-ers/how-to-guide/quickstart.mdx @@ -172,10 +172,13 @@ Build your program and upgrade it with delegation hooks with MagicBlock's Delega - Now you’re program is upgraded and ready! Build and deploy to the desired - cluster: + Now you’re program is upgraded and ready! Fund your deployer wallet, then build and deploy to the + desired cluster: ```bash + # Devnet: airdrop SOL to your configured keypair before deploying + solana airdrop 2 --url https://api.devnet.solana.com + anchor build && anchor deploy ``` diff --git a/pages/tools/session-keys/integrating-sessions-in-your-program.mdx b/pages/tools/session-keys/integrating-sessions-in-your-program.mdx index 4c9d16f..76c866c 100644 --- a/pages/tools/session-keys/integrating-sessions-in-your-program.mdx +++ b/pages/tools/session-keys/integrating-sessions-in-your-program.mdx @@ -11,7 +11,7 @@ First, add the session-keys crate to your Cargo.toml: ```toml [dependencies] -session-keys = { version = "1.0.0", features = ["no-entrypoint"] } +session-keys = { version = "3.1.1", features = ["no-entrypoint"] } ``` Or use the cargo command: diff --git a/pages/tools/session-keys/usage-examples.mdx b/pages/tools/session-keys/usage-examples.mdx index 74234d5..70309c6 100644 --- a/pages/tools/session-keys/usage-examples.mdx +++ b/pages/tools/session-keys/usage-examples.mdx @@ -86,4 +86,4 @@ Revoking a session ensures that the ephemeral key pair is no longer valid and us These examples should help you get started with implementing session management and wallet functionality in your app. -_**Please refer to the "Create Post" section in the**_ [_**Example App**_](https://github.com/gumhq/gum-example-app) _**to see how the Session Token implementation is done.**_ +_**Please refer to the**_ [_**session-keys example**_](https://github.com/magicblock-labs/magicblock-engine-examples/tree/main/session-keys) _**to see how the Session Token implementation is done.**_ diff --git a/pages/tools/session-keys/use-sessionkey-manager.mdx b/pages/tools/session-keys/use-sessionkey-manager.mdx index 5a2701f..d309369 100644 --- a/pages/tools/session-keys/use-sessionkey-manager.mdx +++ b/pages/tools/session-keys/use-sessionkey-manager.mdx @@ -64,7 +64,7 @@ Here's an example of how to use the `useSessionKeyManager`: ```typescript import { useAnchorWallet, useConnection } from '@solana/wallet-adapter-react'; -import { useSessionKeyManager } from '@gumhq/react-sdk'; +import { useSessionKeyManager } from '@magicblock-labs/gum-react-sdk'; function YourComponent() { const wallet = useAnchorWallet(); diff --git a/pages/verifiable-randomness-functions-vrfs/how-to-guide/quickstart.mdx b/pages/verifiable-randomness-functions-vrfs/how-to-guide/quickstart.mdx index b18ca4e..6efcfac 100644 --- a/pages/verifiable-randomness-functions-vrfs/how-to-guide/quickstart.mdx +++ b/pages/verifiable-randomness-functions-vrfs/how-to-guide/quickstart.mdx @@ -90,18 +90,18 @@ Any Solana program can request and consume verifiable randomness onchain within - 1. Add `ephemeral_vrf_sdk` with Anchor features to your program + 1. Add `ephemeral-rollups-sdk` with the `anchor` and `vrf` features to your program ```bash - cargo add ephemeral_vrf_sdk --features anchor + cargo add ephemeral-rollups-sdk --features anchor,vrf ``` - Import `vrf` , `create_request_randomness_ix`, `RequestRandomnessParams`, and `SerializableAccountMeta`: + Import the `vrf` and `vrf_callback` macros, `create_request_scoped_randomness_ix`, `RequestRandomnessParams`, and `SerializableAccountMeta`: ```rust - use ephemeral_vrf_sdk::anchor::vrf; - use ephemeral_vrf_sdk::instructions::{create_request_randomness_ix, RequestRandomnessParams}; - use ephemeral_vrf_sdk::types::SerializableAccountMeta; + use ephemeral_rollups_sdk::anchor::{vrf, vrf_callback}; + use ephemeral_rollups_sdk::vrf::instructions::{create_request_scoped_randomness_ix, RequestRandomnessParams}; + use ephemeral_rollups_sdk::vrf::types::SerializableAccountMeta; ``` 2. Add instructions `roll_dice` to request randomness and `callback_roll_dice` to consume randomness, along with its context: @@ -109,7 +109,7 @@ Any Solana program can request and consume verifiable randomness onchain within - **VRF SDK constants** (`ephemeral_vrf_sdk::consts`) — reference these instead of hardcoding addresses, both in your program and in client/test code: + **VRF SDK constants** (`ephemeral_rollups_sdk::vrf::consts`) — reference these instead of hardcoding addresses, both in your program and in client/test code: | Constant | Purpose | Address | | --- | --- | --- | @@ -145,7 +145,7 @@ Any Solana program can request and consume verifiable randomness onchain within anchor test --skip-build --skip-deploy --skip-local-validator ``` - **VRF SDK constants** (`ephemeral_vrf_sdk::consts`) — reference these instead of hardcoding addresses, in both your program and client/test code: + **VRF SDK constants** (`ephemeral_rollups_sdk::vrf::consts`) — reference these instead of hardcoding addresses, in both your program and client/test code: | Constant | Purpose | Address | | --- | --- | --- | diff --git a/pages/verifiable-randomness-functions-vrfs/introduction/security.mdx b/pages/verifiable-randomness-functions-vrfs/introduction/security.mdx index 203ff1c..abbc03d 100644 --- a/pages/verifiable-randomness-functions-vrfs/introduction/security.mdx +++ b/pages/verifiable-randomness-functions-vrfs/introduction/security.mdx @@ -6,7 +6,7 @@ description: "How Solana VRF randomness proofs are verified" Solana VRF proofs are cryptographically bound to the input `caller_seed` and to MagicBlock's VRF signer identity. Your callback enforces this with: ```rust -#[account(address = ephemeral_vrf_sdk::consts::VRF_PROGRAM_IDENTITY)] +#[account(address = ephemeral_rollups_sdk::vrf::consts::VRF_PROGRAM_IDENTITY)] pub vrf_program_identity: Signer<'info>, ``` diff --git a/pages/verifiable-randomness-functions-vrfs/introduction/solana-vrf.mdx b/pages/verifiable-randomness-functions-vrfs/introduction/solana-vrf.mdx index 7d4a7d4..e2ddfa7 100644 --- a/pages/verifiable-randomness-functions-vrfs/introduction/solana-vrf.mdx +++ b/pages/verifiable-randomness-functions-vrfs/introduction/solana-vrf.mdx @@ -66,7 +66,7 @@ That proof makes the result auditable. Users and programs can verify that a rand Traditional randomness sources fall short on-chain: users cannot verify how off-chain numbers were generated, validators or operators can influence blockhash-based outcomes, and off-chain delivery does not always align with on-chain execution timing. Solana VRF addresses this: -- **Built for Solana programs**: request randomness through the `ephemeral_vrf_sdk` and consume the result in your own callback instruction. +- **Built for Solana programs**: request randomness through the `ephemeral-rollups-sdk` VRF module and consume the result in your own callback instruction. - **Designed for real-time apps**: MagicBlock's ephemeral rollup execution model keeps randomness delivery low-latency for games and interactive flows. - **Verifiable by design**: proofs are validated on-chain before your callback logic runs. - **Open source and audited**: the VRF program is public, with audit coverage linked from the security docs. diff --git a/pages/verifiable-randomness-functions-vrfs/introduction/technical-details.mdx b/pages/verifiable-randomness-functions-vrfs/introduction/technical-details.mdx index 0702d3e..4a8a36b 100644 --- a/pages/verifiable-randomness-functions-vrfs/introduction/technical-details.mdx +++ b/pages/verifiable-randomness-functions-vrfs/introduction/technical-details.mdx @@ -23,7 +23,7 @@ The flow starts with a “Request for randomness”. ## Oracle queues -Every randomness request names an **oracle queue** account (the `oracle_queue` field of `RequestRandomnessParams`). Like every Solana account, the queue lives on Solana — but a **delegated** queue is directly writable only from inside an ephemeral rollup, while a **non-delegated** queue is directly writable on the base layer. Request randomness from the queue that matches where your transaction runs — the base-layer queue from Solana, or the delegated queue from inside the ephemeral rollup. Reference the SDK constants from `ephemeral_vrf_sdk::consts` instead of hardcoding addresses wherever possible. +Every randomness request names an **oracle queue** account (the `oracle_queue` field of `RequestRandomnessParams`). Like every Solana account, the queue lives on Solana — but a **delegated** queue is directly writable only from inside an ephemeral rollup, while a **non-delegated** queue is directly writable on the base layer. Request randomness from the queue that matches where your transaction runs — the base-layer queue from Solana, or the delegated queue from inside the ephemeral rollup. Reference the SDK constants from `ephemeral_rollups_sdk::vrf::consts` instead of hardcoding addresses wherever possible. | Network | Base-layer queue | Delegated queue (ephemeral rollup) | | -------- | --------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | diff --git a/snippets/notes/endpoints.mdx b/snippets/notes/endpoints.mdx index d1a6ec2..4965f6e 100644 --- a/snippets/notes/endpoints.mdx +++ b/snippets/notes/endpoints.mdx @@ -2,7 +2,9 @@ These public RPC endpoints are currently free and supported for development:
Magic Router Devnet: https://devnet-router.magicblock.app
Solana Devnet: https://api.devnet.solana.com
- ER Devnet: https://devnet.magicblock.app
+ ER Devnet (Asia): https://devnet-as.magicblock.app
+ ER Devnet (EU): https://devnet-eu.magicblock.app
+ ER Devnet (US): https://devnet-us.magicblock.app
TEE Devnet: https://devnet-tee.magicblock.app/
Find out more details{" "} here diff --git a/snippets/roll-dice-code/request-consume-randomness.mdx b/snippets/roll-dice-code/request-consume-randomness.mdx index 70bc7f3..fed203b 100644 --- a/snippets/roll-dice-code/request-consume-randomness.mdx +++ b/snippets/roll-dice-code/request-consume-randomness.mdx @@ -1,4 +1,13 @@ ```rust +use ephemeral_rollups_sdk::{ + anchor::{vrf, vrf_callback}, + vrf::{ + self, + instructions::{create_request_scoped_randomness_ix, RequestRandomnessParams}, + types::SerializableAccountMeta, + }, +}; + #[program] pub mod random_dice { use super::*; @@ -8,7 +17,7 @@ pub mod random_dice { // Request Randomness pub fn roll_dice(ctx: Context, client_seed: u8) -> Result<()> { msg!("Requesting randomness..."); - let ix = create_request_randomness_ix(RequestRandomnessParams { + let ix = create_request_scoped_randomness_ix(RequestRandomnessParams { payer: ctx.accounts.payer.key(), oracle_queue: ctx.accounts.oracle_queue.key(), callback_program_id: ID, @@ -20,6 +29,7 @@ pub mod random_dice { is_signer: false, is_writable: true, }]), + callback_args: Some(vec![client_seed]), ..Default::default() }); ctx.accounts @@ -31,8 +41,9 @@ pub mod random_dice { pub fn callback_roll_dice( ctx: Context, randomness: [u8; 32], + client_seed: u8, ) -> Result<()> { - let rnd_u8 = ephemeral_vrf_sdk::rnd::random_u8_with_range(&randomness, 1, 6); + let rnd_u8 = vrf::rnd::random_u8_with_range(&randomness, 1, 6); msg!("Consuming random number: {:?}", rnd_u8); let player = &mut ctx.accounts.player; player.last_result = rnd_u8; // Update the player's last result @@ -48,16 +59,20 @@ pub struct DoRollDiceCtx<'info> { #[account(seeds = [PLAYER, payer.key().to_bytes().as_slice()], bump)] pub player: Account<'info, Player>, /// CHECK: The oracle queue - #[account(mut, address = ephemeral_vrf_sdk::consts::DEFAULT_QUEUE)] - pub oracle_queue: AccountInfo<'info>, + #[account( + mut, + constraint = + oracle_queue.key() == vrf::consts::DEFAULT_QUEUE || // Devnet + oracle_queue.key() == vrf::consts::DEFAULT_TEST_QUEUE // Local + )] + pub oracle_queue: UncheckedAccount<'info>, } +// `#[vrf_callback]` enforces that only the VRF program (via CPI) can invoke the +// callback — omitting it leaves the callback spoofable by any caller. +#[vrf_callback] #[derive(Accounts)] pub struct CallbackRollDiceCtx<'info> { - /// This check ensure that the vrf_program_identity (which is a PDA) is a singer - /// enforcing the callback is executed by the VRF program trough CPI - #[account(address = ephemeral_vrf_sdk::consts::VRF_PROGRAM_IDENTITY)] - pub vrf_program_identity: Signer<'info>, #[account(mut)] pub player: Account<'info, Player>, } diff --git a/snippets/roll-dice-code/test.mdx b/snippets/roll-dice-code/test.mdx index 1659eed..66de3f9 100644 --- a/snippets/roll-dice-code/test.mdx +++ b/snippets/roll-dice-code/test.mdx @@ -2,29 +2,77 @@ import * as anchor from "@coral-xyz/anchor"; import { Program, web3 } from "@coral-xyz/anchor"; import { RandomDice } from "../target/types/random_dice"; +import { PublicKey } from "@solana/web3.js"; + +// Devnet base-layer VRF queue (override with VRF_BASE_QUEUE for local runs) +const DEFAULT_BASE_QUEUE = new PublicKey( + process.env.VRF_BASE_QUEUE || "Cuj97ggrhhidhbu39TijNVqE74xvKJ69gDervRUXAxGh", +); describe("roll-dice", () => { - // Configure the client to use the local cluster. anchor.setProvider(anchor.AnchorProvider.env()); - + const provider = anchor.getProvider() as anchor.AnchorProvider; const program = anchor.workspace.RandomDice as Program; + const playerPda = web3.PublicKey.findProgramAddressSync( + [Buffer.from("playerd"), provider.publicKey!.toBytes()], + program.programId, + )[0]; + it("Initialized player!", async () => { - const tx = await program.methods.initialize().rpc(); + const tx = await program.methods + .initialize() + .rpc({ skipPreflight: true, commitment: "confirmed" }); console.log("Your transaction signature", tx); }); it("Do Roll Dice!", async () => { - const tx = await program.methods.rollDice(0).rpc(); - console.log("Your transaction signature", tx); - const playerPk = web3.PublicKey.findProgramAddressSync( - [Buffer.from("playerd"), anchor.getProvider().publicKey.toBytes()], - program.programId - )[0]; - let player = await program.account.player.fetch(playerPk, "processed"); - await new Promise((resolve) => setTimeout(resolve, 3000)); - console.log("Player PDA: ", playerPk.toBase58()); - console.log("player: ", player); + // Generate the seed BEFORE subscribing so the handler closes over it. + // The program logs "client_seed=N" inside callback_roll_dice — we match + // on that exact substring to pin the callback to our specific request. + const clientSeed = Math.floor(Math.random() * 256); + const seedTag = `client_seed=${clientSeed}`; + + // Pre-arm a one-shot promise that the onLogs handler resolves with the + // matching signature. No polling — we just await it, racing a timeout. + let resolveSig!: (sig: string) => void; + const sigPromise = new Promise((r) => { + resolveSig = r; + }); + const callbackSubId = provider.connection.onLogs( + program.programId, + (info) => { + if ( + !info.err && + info.logs.some((l) => l.includes("CallbackRollDice")) && + info.logs.some((l) => l.includes(seedTag)) + ) { + resolveSig(info.signature); + } + }, + "confirmed", + ); + + try { + const tx = await program.methods + .rollDice(clientSeed) + .accounts({ oracleQueue: DEFAULT_BASE_QUEUE }) + .rpc({ skipPreflight: true, commitment: "confirmed" }); + console.log("rollDice tx:", tx); + + // Base-chain VRF response is slower than ER (~1-5s typical) so 10s timeout. + const sig = await Promise.race([ + sigPromise, + new Promise((r) => setTimeout(() => r(null), 10_000)), + ]); + if (!sig) throw new Error("callbackRollDice not observed within 10s."); + console.log("callbackRollDice tx:", sig); + + const player = await program.account.player.fetch(playerPda, "processed"); + console.log("player:", player); + } finally { + await provider.connection.removeOnLogsListener(callbackSubId); + } }); }); ``` From e4ae41fc60e424ba58245cfaaba34da06a877cdb Mon Sep 17 00:00:00 2001 From: dhruvja Date: Thu, 9 Jul 2026 13:15:02 +0530 Subject: [PATCH 2/2] fix: address coderabbit review on roll-dice snippets --- snippets/roll-dice-code/request-consume-randomness.mdx | 7 +++++-- snippets/roll-dice-code/test.mdx | 5 ++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/snippets/roll-dice-code/request-consume-randomness.mdx b/snippets/roll-dice-code/request-consume-randomness.mdx index fed203b..d0acd8f 100644 --- a/snippets/roll-dice-code/request-consume-randomness.mdx +++ b/snippets/roll-dice-code/request-consume-randomness.mdx @@ -43,6 +43,7 @@ pub mod random_dice { randomness: [u8; 32], client_seed: u8, ) -> Result<()> { + msg!("client_seed={}", client_seed); let rnd_u8 = vrf::rnd::random_u8_with_range(&randomness, 1, 6); msg!("Consuming random number: {:?}", rnd_u8); let player = &mut ctx.accounts.player; @@ -62,8 +63,10 @@ pub struct DoRollDiceCtx<'info> { #[account( mut, constraint = - oracle_queue.key() == vrf::consts::DEFAULT_QUEUE || // Devnet - oracle_queue.key() == vrf::consts::DEFAULT_TEST_QUEUE // Local + oracle_queue.key() == vrf::consts::DEFAULT_QUEUE || // Devnet + oracle_queue.key() == vrf::consts::DEFAULT_TEST_QUEUE || // Local + oracle_queue.key() == vrf::consts::DEFAULT_EPHEMERAL_QUEUE || // ER Devnet + oracle_queue.key() == vrf::consts::DEFAULT_EPHEMERAL_TEST_QUEUE // ER Local )] pub oracle_queue: UncheckedAccount<'info>, } diff --git a/snippets/roll-dice-code/test.mdx b/snippets/roll-dice-code/test.mdx index 66de3f9..17a79b6 100644 --- a/snippets/roll-dice-code/test.mdx +++ b/snippets/roll-dice-code/test.mdx @@ -26,7 +26,10 @@ describe("roll-dice", () => { console.log("Your transaction signature", tx); }); - it("Do Roll Dice!", async () => { + it("Do Roll Dice!", async function () { + // The base-chain callback can take up to 10s, so raise Mocha's timeout. + this.timeout(20_000); + // Generate the seed BEFORE subscribing so the handler closes over it. // The program logs "client_seed=N" inside callback_roll_dice — we match // on that exact substring to pin the callback to our specific request.