From 210f483812afd4a6f8fe28a5f87553b166191981 Mon Sep 17 00:00:00 2001 From: dhruvja Date: Wed, 8 Jul 2026 22:42:45 +0530 Subject: [PATCH] docs: document ER macros and recommend the solana-dev companion skill --- README.md | 10 +++++ skill/SKILL.md | 24 +++++++++-- skill/vrf.md | 112 ++++++++++++++++++++++++++++++++++--------------- 3 files changed, 110 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index a17ce64..529d0e4 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,16 @@ Produces: - `dist/magicblock.cursor.mdc` — Cursor-formatted rule with `.mdc` frontmatter - `dist/magicblock.zip` — zipped `skill/` folder for Claude.ai upload +## Recommended companion skill: `solana-dev` + +This skill layers **Ephemeral Rollups-specific** patterns (delegation, dual connections, cranks, VRF, Magic Actions, private payments) on top of ordinary Solana development — it assumes base-layer Solana and Anchor fluency rather than teaching it. For the general Solana layer (program scaffolding, PDAs, SPL tokens, client generation, wallet wiring, LiteSVM/Mollusk testing), install the Solana Foundation's [`solana-dev`](https://github.com/solana-foundation/solana-dev-skill) skill alongside this one and let each handle its layer: + +```bash +npx skills add https://github.com/solana-foundation/solana-dev-skill --skill solana-dev +``` + +Skills don't declare dependencies on each other, so this isn't installed automatically — install it yourself (see the [solana-dev-skill repo](https://github.com/solana-foundation/solana-dev-skill) for other install options). Once both are present, agents use `solana-dev` for base-layer Solana work and this skill for the ER-specific pieces. + ## What This Skill Covers - MagicBlock Ephemeral Rollups integration diff --git a/skill/SKILL.md b/skill/SKILL.md index 58df920..15f6f17 100644 --- a/skill/SKILL.md +++ b/skill/SKILL.md @@ -21,6 +21,14 @@ Use this Skill when the user asks for: - Private payments (deposits, transfers, withdrawals, and swaps via the Payments API, with optional bearer-token auth for private reads) - Lifting the default 10-commit sponsorship cap with `magic_fee_vault` +## Pair with the `solana-dev` skill + +This Skill layers **Ephemeral Rollups-specific** concerns (delegation, dual connections, cranks, VRF, +magic actions, private payments) on top of ordinary Solana development — it assumes base-layer Solana +and Anchor fluency rather than teaching it. When a task also needs general Solana/Anchor work — program +scaffolding, PDAs, account layouts, SPL tokens, wallet/client wiring, or testing (LiteSVM/Mollusk/etc.) — +also load the **`solana-dev`** skill for that layer and keep this Skill for the ER-specific pieces. + ## Key Concepts **Ephemeral Rollups** enable high-performance, low-latency transactions by temporarily delegating Solana account ownership to an ephemeral rollup. Ideal for gaming, real-time apps, and fast transaction throughput. @@ -56,11 +64,21 @@ returned by router `getDelegationStatus`, and cloned into the ER with ## Default stack decisions (opinionated) -1) **Programs: Anchor with ephemeral-rollups-sdk** +1) **Programs: Anchor with ephemeral-rollups-sdk** (native/Pinocchio also supported — see below) - Use the target repo's existing `ephemeral-rollups-sdk` / Anchor versions unless the task is an explicit upgrade - The SDK feature flag selects the Anchor line: `anchor` for Anchor 1.0.x programs, or `anchor-compat` for legacy Anchor 0.32.x programs - - Apply `#[ephemeral]` macro before `#[program]` - - Use `#[delegate]` and `#[commit]` macros for delegation contexts + + **Commonly-missed macros:** + - `#[ephemeral]` on the program module, **before** `#[program]` — injects the `process_undelegation` callback (the delegation program CPIs into it to return the account) and the commit/undelegate intent builders. It's what **commit and undelegation** need, not the `delegate` instruction itself — but include it on any program that delegates, since without the callback the account can't be undelegated. + - `#[delegate]` and `#[commit]` on the respective delegation/commit account contexts. + - `#[vrf]` on a VRF *request* context **and** `#[vrf_callback]` on the VRF *callback* context — the + callback macro is the one most often forgotten. Enable the `vrf` feature on `ephemeral-rollups-sdk` + (VRF is no longer a separate `ephemeral-vrf-sdk` crate for new code). See [vrf.md](vrf.md). + + **Non-Anchor programs:** native Rust / Pinocchio is a first-class supported path via the + `ephemeral-rollups-pinocchio` crate (delegation, commit, and VRF have Pinocchio equivalents). The + engine examples repo ships Anchor **and** Pinocchio variants of `roll-dice`; reach for Pinocchio when + the target program is native rather than Anchor. Version-sensitive work: treat versions in this skill as known-good snapshots or compatibility markers, not timeless latest recommendations. Before adding or changing dependencies, inspect the target repo's `Cargo.toml`, `package.json`, `rust-toolchain.toml`, lockfiles, and the relevant upstream manifests/docs. See [resources.md](resources.md) for the current snapshot and source links. diff --git a/skill/vrf.md b/skill/vrf.md index 7195c48..b221ddd 100644 --- a/skill/vrf.md +++ b/skill/vrf.md @@ -2,75 +2,112 @@ VRF provides provably fair randomness for games, lotteries, and any application requiring verifiable randomness. -## Additional Dependencies +## Dependencies + +VRF now ships as a **feature of the main SDK** — enable the `vrf` feature on +`ephemeral-rollups-sdk`. There is no separate `ephemeral-vrf-sdk` crate to add for new Anchor code. ```toml [dependencies] -ephemeral-vrf-sdk = { version = "0.3.0", features = ["anchor"] } +ephemeral-rollups-sdk = { version = "0.15.4", features = ["anchor", "vrf"] } ``` -## VRF Imports +> Older examples import a standalone `ephemeral-vrf-sdk` crate and call the non-scoped +> `create_request_randomness_ix` with a manual `vrf_program_identity` signer account. That path still +> works, but new code should use the scoped API below (`ephemeral_rollups_sdk::vrf` + +> `create_request_scoped_randomness_ix` + `#[vrf]` / `#[vrf_callback]`). + +## Imports ```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}, + vrf::{ + self, + instructions::{create_request_scoped_randomness_ix, RequestRandomnessParams}, + types::SerializableAccountMeta, + }, +}; ``` ## Request Randomness ```rust -pub fn request_randomness(ctx: Context, client_seed: u8) -> Result<()> { - let ix = create_request_randomness_ix(RequestRandomnessParams { +pub fn roll_dice(ctx: Context, client_seed: u8) -> Result<()> { + let ix = create_request_scoped_randomness_ix(RequestRandomnessParams { payer: ctx.accounts.payer.key(), oracle_queue: ctx.accounts.oracle_queue.key(), callback_program_id: ID, - callback_discriminator: instruction::ConsumeRandomness::DISCRIMINATOR.to_vec(), + callback_discriminator: instruction::CallbackRollDice::DISCRIMINATOR.to_vec(), caller_seed: [client_seed; 32], + // Accounts the callback needs accounts_metas: Some(vec![SerializableAccountMeta { - pubkey: ctx.accounts.my_account.key(), + pubkey: ctx.accounts.player.key(), is_signer: false, is_writable: true, }]), + // Extra args forwarded to the callback + callback_args: Some(vec![client_seed]), ..Default::default() }); - ctx.accounts.invoke_signed_vrf(&ctx.accounts.payer.to_account_info(), &ix)?; + ctx.accounts + .invoke_signed_vrf(&ctx.accounts.payer.to_account_info(), &ix)?; Ok(()) } -#[vrf] // Required macro for VRF interactions +#[vrf] // Injects VRF accounts + invoke_signed_vrf #[derive(Accounts)] -pub struct RequestRandomnessCtx<'info> { +pub struct DoRollDiceCtx<'info> { #[account(mut)] pub payer: Signer<'info>, - #[account(seeds = [MY_SEED, payer.key().to_bytes().as_slice()], bump)] - pub my_account: Account<'info, MyAccount>, - /// CHECK: Oracle queue - #[account(mut, address = ephemeral_vrf_sdk::consts::DEFAULT_EPHEMERAL_QUEUE)] - pub oracle_queue: AccountInfo<'info>, + #[account(seeds = [PLAYER, payer.key().to_bytes().as_slice()], bump)] + pub player: Account<'info, Player>, + /// CHECK: The oracle queue + #[account( + mut, + constraint = + oracle_queue.key() == vrf::consts::DEFAULT_QUEUE || // Devnet / Mainnet + oracle_queue.key() == vrf::consts::DEFAULT_TEST_QUEUE // Localnet + )] + pub oracle_queue: UncheckedAccount<'info>, } ``` ## Consume Randomness Callback ```rust -pub fn consume_randomness(ctx: Context, randomness: [u8; 32]) -> Result<()> { - let random_value = ephemeral_vrf_sdk::rnd::random_u8_with_range(&randomness, 1, 6); - ctx.accounts.my_account.last_random = random_value; +pub fn callback_roll_dice( + ctx: Context, + randomness: [u8; 32], + client_seed: u8, // matches the callback_args passed in the request +) -> Result<()> { + let rnd_u8 = vrf::rnd::random_u8_with_range(&randomness, 1, 6); + let player = &mut ctx.accounts.player; + player.last_result = rnd_u8; + player.rollnum = player.rollnum.saturating_add(1); Ok(()) } +#[vrf_callback] // Injects the scoped VRF identity signer check #[derive(Accounts)] -pub struct ConsumeRandomnessCtx<'info> { - /// SECURITY: Validates callback is from VRF program - #[account(address = ephemeral_vrf_sdk::consts::VRF_PROGRAM_IDENTITY)] - pub vrf_program_identity: Signer<'info>, +pub struct CallbackRollDiceCtx<'info> { #[account(mut)] - pub my_account: Account<'info, MyAccount>, + pub player: Account<'info, Player>, } ``` +## Two required macros + +Both contexts need a macro; the callback one is the one people forget: + +- **`#[vrf]`** on the *request* context — injects the `program_identity`, `vrf_program`, + `slot_hashes`, and `system_program` accounts plus the `invoke_signed_vrf` helper. Omit it and + `invoke_signed_vrf` doesn't exist, so the program won't compile. +- **`#[vrf_callback]`** on the *callback* context — injects a `vrf_program_identity: Signer` bound + to this program's scoped identity PDA, so you don't hand-write it. Omit it and the struct still + compiles, but the callback has no identity check and accepts spoofed randomness. + ## Oracle Queue Constants The `oracle_queue` is a state account. Like every Solana account it lives on @@ -78,7 +115,7 @@ 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 the transaction runs — the base-layer queue from Solana, or the delegated queue from inside the -ephemeral rollup. Prefer the `ephemeral_vrf_sdk::consts` constants over +ephemeral rollup. Prefer the `ephemeral_rollups_sdk::vrf::consts` constants over hardcoding addresses. | Constant | Queue | Address | @@ -100,11 +137,20 @@ Mainnet and Devnet share the same default queue addresses — only the cluster differs. Localnet uses dedicated test queues that the local validator clones from Devnet. +## Non-Anchor (Pinocchio / native) programs + +VRF is also supported outside Anchor via the `ephemeral-rollups-pinocchio` crate +(`ephemeral_rollups_pinocchio::vrf` — `RequestRandomness` / `RequestRandomnessCpi`, +`scoped_vrf_identity`, `random_u8_with_range`, `VRF_PROGRAM_IDENTITY`). The flow is the same +(request → oracle callback), but you validate the program identity manually against +`scoped_vrf_identity(program_id)` instead of relying on the `#[vrf_callback]` macro. See the +Pinocchio `roll-dice` example in the engine examples repo. + ## Key Points -- VRF provides cryptographically verifiable randomness -- The callback pattern ensures randomness is delivered asynchronously -- Always validate `vrf_program_identity` signer in the callback to prevent spoofed randomness -- Use `DEFAULT_EPHEMERAL_QUEUE` when requesting from inside the ephemeral rollup (the queue is delegated to the ER) -- Use `DEFAULT_QUEUE` when requesting from the base layer (Solana) -- `caller_seed` can be used to add entropy from the client side +- VRF provides cryptographically verifiable randomness. +- The callback pattern ensures randomness is delivered asynchronously. +- Apply **both** `#[vrf]` (request) and `#[vrf_callback]` (callback) — see above. +- Use `DEFAULT_EPHEMERAL_QUEUE` when requesting from inside the ephemeral rollup (the queue is delegated to the ER). +- Use `DEFAULT_QUEUE` when requesting from the base layer (Solana). +- `caller_seed` adds client-side entropy; `callback_args` forwards extra data to the callback.