Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions pages/ephemeral-rollups-ers/how-to-guide/quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -172,10 +172,13 @@
</Tab>

<Tab title="3. Deploy">
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

Check warning on line 175 in pages/ephemeral-rollups-ers/how-to-guide/quickstart.mdx

View check run for this annotation

Mintlify / Mintlify Validation (magicblock-42) - vale-spellcheck

pages/ephemeral-rollups-ers/how-to-guide/quickstart.mdx#L175

Did you really mean 'deployer'?
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
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion pages/tools/session-keys/usage-examples.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.**_
2 changes: 1 addition & 1 deletion pages/tools/session-keys/use-sessionkey-manager.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,26 +90,26 @@ Any Solana program can request and consume verifiable randomness onchain within
</Tab>
<Tab title="2. Request & Consume Randomness">

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:

<RequestConsumeRandomnessCode />

<Note>
**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 |
| --- | --- | --- |
Expand Down Expand Up @@ -145,7 +145,7 @@ Any Solana program can request and consume verifiable randomness onchain within
anchor test --skip-build --skip-deploy --skip-local-validator
```
<Note>
**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 |
| --- | --- | --- |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) |
| -------- | --------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
Expand Down
4 changes: 3 additions & 1 deletion snippets/notes/endpoints.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
These public RPC endpoints are currently free and supported for development:
<br /> Magic Router Devnet: https://devnet-router.magicblock.app <br />
Solana Devnet: https://api.devnet.solana.com <br />
ER Devnet: https://devnet.magicblock.app <br />
ER Devnet (Asia): https://devnet-as.magicblock.app <br />
ER Devnet (EU): https://devnet-eu.magicblock.app <br />
ER Devnet (US): https://devnet-us.magicblock.app <br />
TEE Devnet: https://devnet-tee.magicblock.app/ <br />
Find out more details{" "}
<a href="/pages/ephemeral-rollups-ers/how-to-guide/local-development">here</a>
Expand Down
34 changes: 26 additions & 8 deletions snippets/roll-dice-code/request-consume-randomness.mdx
Original file line number Diff line number Diff line change
@@ -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::*;
Expand All @@ -8,7 +17,7 @@ pub mod random_dice {
// Request Randomness
pub fn roll_dice(ctx: Context<DoRollDiceCtx>, 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,
Expand All @@ -20,6 +29,7 @@ pub mod random_dice {
is_signer: false,
is_writable: true,
}]),
callback_args: Some(vec![client_seed]),
..Default::default()
});
ctx.accounts
Expand All @@ -31,8 +41,10 @@ pub mod random_dice {
pub fn callback_roll_dice(
ctx: Context<CallbackRollDiceCtx>,
randomness: [u8; 32],
client_seed: u8,
) -> Result<()> {
let rnd_u8 = ephemeral_vrf_sdk::rnd::random_u8_with_range(&randomness, 1, 6);
msg!("client_seed={}", client_seed);
let rnd_u8 = vrf::rnd::random_u8_with_range(&randomness, 1, 6);
msg!("Consuming random number: {:?}", rnd_u8);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
let player = &mut ctx.accounts.player;
player.last_result = rnd_u8; // Update the player's last result
Expand All @@ -48,16 +60,22 @@ 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
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>,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

// `#[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>,
}
Expand Down
79 changes: 65 additions & 14 deletions snippets/roll-dice-code/test.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,80 @@
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<RandomDice>;

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);
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.
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<string>((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<null>((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);
}
});
});
```