
True Onchain Randomness with Verifiable Random Functions on Magicblock

Note: This blog assumes you have basic Solana and Rust knowledge. For logical separation, this is about implementing a VRF outside of an Ephemeral Rollup. Implementing within an Ephemeral rollup is covered here, and is largely the same.
One of my favorite childhood memories is rolling my character stats in Maplestory, a Korean MMORPG that’s still somehow alive today (22 years later!). Back then, every new character started with four randomized stats between 4-13. Completely pointless in the long run - you gain hundreds of stat points by late game - but necessary for flexing on your 12 year old friends.

I would sit there like a tiny slot machine addict with infinite time and no adult supervision, chasing the perfect roll. So obviously, now as an adult, I created my own stat roll generator on Solana, powered by MagicBlock’s VRFs.
Let me show you how I did it.
The Problem With Randomness
But first, let's talk about randomness.
In modern computer systems, when you call a random function—like Math.random() in JS or rand() in C—you’re just asking the machine to give you something that feels random enough. Under the hood, though, it’s not some quantum dice-rolling gremlin, it’s just math. Deterministic math.
Most systems use what's called a pseudorandom number generator (PRNG). These take an initial value called a seed (like the current time, mouse movement, CPU noise, etc.) and run it through an algorithm to spit out numbers that look random. But they’re not truly random - if you know the seed, you can predict every number it'll ever generate.
And that’s fine in most situations. Games, simulations, flipping a coin to decide which memecoin to ape - PRNGs are good enough.
But this becomes a massive problem on blockchains.
Because blockchains are public, deterministic, and adversarial. They’re not allowed to just ask the OS for a random number. So... what should they sample from?
You might think something sufficiently random, like the blockhash or timestamp could work. But: if there’s enough money on the line—say, I’m flipping a coin to win 1000 SOL—validators can and will rig it. All they need to do is wait for their turn to produce a block (aka their leader slot), simulate a bunch of outcomes, and then only publish the block if the coin flip goes their way.

Without proper randomness, it’s like poker, but the dealer is also playing and can see everyone’s hand. Your blockchain game or lottery or anything involving chance is totally cooked.
How does the MagicBlock VRF work?
MagicBlock VRFs create randomness you can prove - and trust - onchain. Our VRF is designed to allow a party to prove that they know a random value derived from a secret key, with the proof being verifiable by any third party. Unlike some systems that offload verification to oracles, MagicBlock verifies everything on-chain, end to end.
Flow

- The flow starts with a “Request for randomness”. Your program will CPI into the MagicBlock VRF program and append a request to the queue.
- Once your randomness request is in the queue, an oracle will release the request and perform the randomness computation.
- Upon completion, it returns the result and proof to the MagicBlock VRF program.
- After verifying the proof, the VRF program will call back into your program into a predefined function that will “consume” the randomness.
For a more technical overview, check out the open source implementation here.
Let’s review how we plug this flow into our own game logic.
Implementation Walkthrough
I’ll walk you through the instructions that we use to get our randomness for character rolls. We have two main functions, roll_dice() and callback_roll_dice(). These are essentially a “request for” and “consume” randomness.
The Player Struct
First, let’s take a look at our data structure. This is our character struct, representing the character that was rolled. Every “roll” will give us the class of the character, as well as three stats: attack (ATK), defense (DEF), and dexterity (DEX).

Request for randomness: roll_dice()
Here, we request randomness in my user program. It CPIs into the MagicBlock VRF program and queues a request. You can see that we pass it [callback_program_id] and [callback_discriminator]. This is defining the callback function that will consume the randomness we receive, which we’ll take a look at soon. We need to make sure that we also pass it all the accounts that it needs for the callback function in [accounts_meta].

Consume randomness: callback_roll_dice()
This is the callback function that we passed into our request for randomness. To consume this randomness, we offer a SDK with some handy functions. I use two of them here:
- [ephmeral_vrf_sdk::rnd::random_u8_with_range()] : as the name suggests, this gives a random u8 within your defined range. I use this to get a roll for determining my character class.
- [ephmeral_vrf_sdk::rnd::random_u32()]: here, this rolls a single u32 number. Using this u32, I get 3 stat rolls from it by using a bitshift to grab 10 bits at a time.

There are a couple of things to notice here:
- I only use 1 shred of randomness to get multiple “rolls”. This is possible because I’m getting different pieces of it. The functions are deterministic, so pulling multiple values from the same range with the same input will always yield the same result.
- That’s why I generate a single u32, then split it into three 10-bit chunks to create my stat rolls. Each chunk gives me a slice of entropy, which I reduce via modulo to get a value from 0–99.
- This does introduce modulo bias: unless the input range is perfectly divisible by the modulus, lower numbers skew slightly more likely. We’ll live with that for this demo.
All the sdk methods can be viewed here.
Security
Is this secure? Let's unwind this by asking: who could cheat?
Can the player cheat?
No. The player can’t mess with the randomness because it’s generated outside their program. The randomness comes from a callback CPI - our system calls them, not the other way around. So they can't influence the result, reject it, or re-roll if they don’t like it.
Can the randomness provider cheat?
Still no. We use a VRF - a verifiable random function - which means every random output comes with a cryptographic proof. The user submits a commitment (a hash of something they know), and from that point, any result must verify against that commitment. If the proof doesn’t verify onchain, the callback fails. The randomness is discarded before it ever reaches the user.
Can a colluding player and randomness provider cheat?
Also no. Because the player locks in their request before the randomness is generated. The commitment hash ensures that the result is baked in before anyone, including the provider, sees it. Think of it like sealing an envelope before the dice are rolled, and only then checking if the result matches what was committed.
For more details on security and our hashing approach, click here.
Next Steps
This concludes our demo. If you’ve read this far, then congratulations! You’re now contractually required to build something cool ;) . Here are a couple of ideas to extend upon this demo and build a full-fledged game.
If you are interested in building any of these, join our Discord and feel free to ping me!
Idle Game
Turn your stat-rolled characters into dungeon explorers. Each character NFT could have unique strengths and explore rooms over time in a classic idle loop - think incremental roguelike. Randomness could decide what loot or events they encounter. Characters with higher stats get deeper, better loot, and rarer encounters. Add cooldowns, progression, and upgrades, and you’ve got a loop.
PvP Minigames
Generate combat scenarios between player characters. The outcome can be a function of stats + a touch of randomness - like a card game or rock-paper-scissors with hidden modifiers. Log the results onchain, create a ranked ladder, and let people bet on outcomes (or just ego).
Deckbuilder
Each character gets a small deck of "moves" that are randomly generated during their origin. Think Slay the Spire, but onchain. Randomness is used during battles to draw cards, determine crits, and resolve effects. Characters evolve their deck as they level.
Wrap Up on Randomness
Onchain randomness isn’t just about dice rolls; it’s a design primitive. Once you can trust randomness onchain, you unlock entire genres of gameplay that used to be off-limits to smart contracts: loot tables, dungeon RNG, crits, misfires, crafting outcomes, procedural maps, weird permadeath rules, all the messy, delightful chaos that makes games fun.
In this demo, we showed how to roll character stats using MagicBlock’s verifiable random function. But this is just the surface. Randomness becomes really interesting when it’s entangled with identity, economy, and story — when your stats aren’t just numbers, but consequences.
If that excites you, come build with us and explore what makes onchain fun.
Join our Discord, we’d love to see what you make.