If you search for "Chainlink VRF on Solana" you'll find a lot of confident answers. Most of them are wrong. As of October 2026, Chainlink VRF v2.5 runs on EVM chains only: Ethereum, Arbitrum, Base, Avalanche, BNB Chain, OP, Polygon, Ronin and Soneium. Chainlink does run on Solana, through Data Feeds, Data Streams and CCIP, but VRF isn't one of the services there.
That still leaves the real question: how do you get randomness on Solana that nobody can predict or rig? This post covers how VRF works, why the obvious shortcuts fail on Solana, the native options that exist today, and a pattern that brings Chainlink VRF results across to Solana through CCIP if you specifically want Chainlink's model.
Why randomness is hard onchain
Every Solana validator has to replay your program and reach the exact same state. That rules out real randomness inside a program: if the program could roll its own dice, every validator would roll different ones and the network would never agree.
So people reach for something that looks random but is the same everywhere. The usual suspects:
- The clock or the slot number. Anyone can read these before sending a transaction, so they can simulate the outcome and only submit when it's in their favour.
- Recent slot hashes or blockhashes. Harder to predict, but the leader producing the block sees them first and decides which transactions make it in. For a lottery with real money on it, that's a lot of power to hand one validator.
- A seed the admin provides. Now you're trusting the admin, and so is everyone who uses your app.
The pattern is the same each time. Whoever can see or influence the value before it's locked in can game it. What you want is a number that nobody, including you, can know until after the request is committed, plus a proof that it wasn't tampered with.
What a VRF actually gives you
A verifiable random function is a keyed function with one extra property. An oracle holds a secret key. Given your input (a seed derived from your request), it produces two things:
- An output that looks completely random to anyone without the secret key.
- A proof that this output is the only valid one for that input and that public key.
Anyone can check the proof against the oracle's public key. The oracle can't choose a nicer number, because only one output verifies. And because the seed includes data that didn't exist until your request was confirmed, the oracle can't precompute anything useful either.
In Chainlink VRF v2.5 on an EVM chain, the flow looks like this:
- Your contract calls
requestRandomWordson the VRF coordinator with akeyHash(which key and gas lane), yoursubId,requestConfirmations,callbackGasLimitandnumWords. It gets back arequestId. - The VRF service waits for the number of confirmations you asked for, then computes the random words and a proof from the block hash and a nonce.
- It sends both to the coordinator, which verifies the proof onchain and then calls your contract's
fulfillRandomWords.
uint256 requestId = s_vrfCoordinator.requestRandomWords(
VRFV2PlusClient.RandomWordsRequest({
keyHash: keyHash,
subId: subId,
requestConfirmations: 3,
callbackGasLimit: 200_000,
numWords: 1,
extraArgs: VRFV2PlusClient._argsToBytes(
VRFV2PlusClient.ExtraArgsV1({ nativePayment: true })
)
})
);
The confirmation count matters more than it looks. A validator that can reorganise the chain could move your request into a different block and get a different output. More confirmations make that more expensive, so Chainlink's advice is to size it to the value at risk.
The native options on Solana
Several teams run VRF services built for Solana. They all follow the same shape as Chainlink's: your program requests, an oracle answers with randomness and a proof (or signatures), and your program gets the result through a callback or by reading an account. The differences are in the cryptography and the trust model.
MagicBlock VRF. Built on the Ristretto group over Curve25519, with Schnorr-style proofs following RFC 9381. Your program makes a CPI into the MagicBlock VRF program, which queues the request. An oracle computes the output and proof, the VRF program verifies the proof, and then it calls back into a function you specify. The callback is signed by the VRF program's signer PDA, and your program must check that before trusting the bytes. It also works inside MagicBlock's ephemeral rollups if you need results in the same fast execution window.
ORAO VRF. Uses Ed25519 signatures from a set of fulfillment authorities stored in a network config account, with a Byzantine quorum check. You request with RequestV2, and fulfillment lands through FulfillV2. Clients can poll the request account or wait for it to be fulfilled, and there's a CPI interface for requesting from your own program.
Switchboard. This is the provider the official Solana developer course teaches with. In the course's example, the program creates a VRF account whose authority is the program's PDA, requests randomness through a CPI, and receives a 32-byte result in a callback. One detail from that example tells you a lot about the cost of doing this natively: verifying the proof onchain took 276 instructions, about 48 transactions. Switchboard has shipped newer randomness products since, so check their current docs before building on the older flow.
Bringing Chainlink VRF to Solana through CCIP
If you specifically want Chainlink's VRF, say because the rest of your stack already depends on Chainlink or your game logic lives on an EVM chain anyway, you can request it there and carry the result across. Solana joined Chainlink's CCIP in May 2025 as its first non-EVM chain, and CCIP supports sending arbitrary data from EVM chains to Solana programs.
The shape of it:
- Your Solana program records a pending request in a PDA and emits the request id.
- A relayer (or a keeper you run) calls your consumer contract on an EVM chain like Base or Arbitrum, which requests randomness from Chainlink VRF.
fulfillRandomWordsstores the result. Keep it to just that, because if the callback reverts the VRF service won't retry it.- A separate call sends the request id and the random word to your Solana program over CCIP.
- Your program's
ccip_receiveinstruction checks that the message came through CCIP, finds the pending request and settles it.
On the EVM side, the important bits look like this:
function fulfillRandomWords(uint256 vrfRequestId, uint256[] calldata words) internal override {
// store only; never revert here
results[solanaIdByVrfId[vrfRequestId]] = words[0];
}
function forward(bytes32 solanaRequestId) external payable {
uint256 word = results[solanaRequestId];
require(word != 0 && !forwarded[solanaRequestId], "not ready or already sent");
forwarded[solanaRequestId] = true; // one delivery per request
Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({
receiver: abi.encode(SOLANA_PROGRAM_ID), // program id as 32 bytes
data: abi.encode(solanaRequestId, word),
tokenAmounts: new Client.EVMTokenAmount[](0),
feeToken: address(0), // pay the fee in native gas
extraArgs: svmExtraArgs // SVMExtraArgsV1, see below
});
router.ccipSend{value: msg.value}(SOLANA_CHAIN_SELECTOR, message);
}
The Solana-specific part is the extra args. Solana programs can only touch accounts that are passed in, so a message to Solana carries SVMExtraArgsV1 with computeUnits, the list of accounts your ccip_receive needs (in the order it expects them), an accountIsWritableBitmap marking which are writable, allowOutOfOrderExecution, and tokenReceiver.
Be honest with yourself about the costs before choosing this route:
- Latency. You wait for VRF confirmations on the EVM chain, then for CCIP to deliver. Expect this to take far longer than a Solana-native VRF, so it suits draws and raffles, not anything that has to feel instant.
- Two fees. You pay for the VRF request and for the CCIP message.
- Two trust models. You now rely on Chainlink VRF and on CCIP, plus whatever relays the request.
If none of those are dealbreakers and you value Chainlink's track record, it's a clean design. For most Solana apps, a native VRF is the simpler answer.
Rules that apply whichever provider you pick
Most randomness bugs I've seen weren't in the VRF. They were in how the program used the result.
- Lock inputs before you request. Once a request is made, nothing the user does should change what the result means. Chainlink calls this out explicitly: stop accepting bets or entries after the request.
- No re-rolls. Never let anyone cancel a request or request again after seeing a bad result. That turns provable randomness back into a choice.
- Check who's calling back. Verify the callback signer, the VRF account authority or the CCIP sender before you read a single byte.
- Match by request id. Fulfillments can arrive out of order, so tie each result to the request that asked for it.
- Store first, act later. Save the result in the callback and do anything heavy in a separate instruction, so a failed side effect can't lose the randomness.
- Watch for zero and stale buffers. In the Switchboard flow, an all-zero result means verification hasn't finished, and a result equal to the last one means nothing new arrived.
The last trap is modulo bias. If you take one random byte and do byte % 6 for a die, some faces come up slightly more often, because 256 doesn't divide evenly by 6. The fix is rejection sampling: throw away values from the uneven tail and use the next chunk of your 32 bytes.
fn roll(rand: &[u8; 32], sides: u32) -> Option<u32> {
let limit = u32::MAX - (u32::MAX % sides); // largest multiple of `sides`
for chunk in rand.chunks_exact(4) {
let v = u32::from_le_bytes(chunk.try_into().unwrap());
if v < limit {
return Some(v % sides + 1);
}
}
None // all eight draws fell in the tail; astronomically unlikely, request again
}
Which one I'd use
For a Solana game, mint or raffle that needs results in seconds, I'd use a Solana-native VRF and spend my time on the rules above, because that's where the real risk is. I'd only reach for the Chainlink VRF plus CCIP route when the app already lives across chains or needs Chainlink specifically, and the result can wait.
If you're building something with onchain randomness and want a second pair of eyes on the design, write to me. I like this stuff.
Sources
- Chainlink VRF v2.5 supported networks
- Chainlink VRF v2.5 subscription method
- Chainlink VRF security considerations
- Chainlink CCIP: building messages from EVM to SVM
- Chainlink on Solana, Solana Compass
- Solana developer course: verifiable randomness functions
- MagicBlock VRF technical details
- ORAO Solana VRF crate