Appearance
Quickstart: Anchor programs
One attribute injects the account plumbing; one method call sends the request; one helper reads the outcome back. Works on Anchor 0.31, 0.32 and 1.x from the same crates.
Pick your crates
toml
[dependencies]
vrand-anchor = { path = "…/vrand/crates/vrand-anchor" } # the attribute
vrand-cpi = { path = "…/vrand/crates/vrand-cpi" } # the CPI + readersvrand-cpi is keyed on the Solana crate major, not the Anchor version: module v2 for Anchor 0.31/0.32 (solana-* 2.x), v3 for Anchor 1.x (solana-* 3.x). vrand-anchor is a pure token-emitting proc-macro with no Anchor dependency of its own, which is why one crate serves every generation.
Request
rust
use anchor_lang::prelude::*;
use vrand_anchor::vrand_request_accounts;
#[vrand_request_accounts(payer = participant, requester = game, prover = config.prover)]
#[derive(Accounts)]
#[instruction(seed: [u8; 32])]
pub struct Flip<'info> {
#[account(mut)]
pub participant: Signer<'info>,
#[account(seeds = [GAME_SEED], bump = game.bump)]
pub game: Account<'info, Game>,
pub config: Account<'info, Config>,
}
pub fn flip(ctx: Context<Flip>, seed: [u8; 32]) -> Result<()> {
let bump = ctx.accounts.game.bump;
let at_risk = ctx.accounts.config.at_risk_lamports;
ctx.accounts
.request_vrand(seed, at_risk, &[&[GAME_SEED, &[bump]]])?;
Ok(())
}The attribute injects vrand_request, vrand_program, vrand_network, vrand_prover, vrand_terms, vrand_ack, slot_hashes (and system_program if you lack one) and generates request_vrand().
Attribute arguments:
| Arg | Required | Meaning |
|---|---|---|
payer | yes | your Signer paying rent + fee; its wallet must hold a current Terms acknowledgment (your frontend sends that once — the web SDK does it automatically) |
requester | yes | the identity the request address derives from — your program's PDA, signing via seeds |
prover | yes | expression the prover account is pinned to (address = constraint) |
comp_dest | no (default requester) | receives slash compensation — an account your program controls |
seed | no (default seed) | name of the [u8; 32] instruction argument |
cpi | no (default vrand_cpi::v3) | use vrand_cpi::v2 on Anchor 0.31/0.32 |
The prover pin is mandatory on purpose
The prover account arrives from the transaction. Unpinned, a caller can pass a prover whose VRF key they hold — and supply their own luck. Pin it to your own config. If you genuinely accept any prover, write prover = vrand_prover.key() so the choice is visible in your code.
Read the outcome
rust
use vrand_cpi::v3 as vrand; // v2 on Anchor 0.31/0.32
use vrand_vrf_core::scale;
pub fn settle(ctx: Context<Settle>) -> Result<()> {
// Owner check + typed pending/slashed errors built in:
let beta = vrand::fulfilled_beta(&ctx.accounts.vrand_request)?;
let roll = scale::uniform_below(&beta, 6); // bias-free — never `% n`
// … your logic …
Ok(())
}fulfilled_beta fails typed: ERR_REQUEST_PENDING while there is no outcome yet, ERR_REQUEST_SLASHED when there never will be one (your comp_dest was already paid — resolve through your backstop), ERR_WRONG_OWNER for an account VRAND does not own.
The three sharp edges
Every integration that has gone wrong has hit one of these:
Size
at_risk_lamportsat or above the maximum output this randomness can trigger. It is your compensation ceiling; nothing warns you if you under-buy. (And never let the caller choose it — every request reserves that amount against the prover's bond, so caller-chosen coverage is a capacity-exhaustion attack on everyone sharing the prover.)Never resolve a timeout for less than the full output. If your timeout path is cheaper than the win path, anyone who can see the outcome early holds a free option: force the cheap path exactly when the outcome favored the participant. The full default output, funded by the slash, is the only non-exploitable resolution.
Key your state on outcome data, never on the request address. A closed request's
(requester, seed)re-derives the same address and can be created afresh with an independent outcome. Key onbeta/fulfilled_slot.
Without the macro
Prefer the accounts spelled out? Copy the documented block from the vrand-cpi crate docs and call request_randomness directly — same wire format, same checks. The macro is convenience, not magic: everything it emits is ordinary Anchor attribute syntax you could have written.
Testing
The repo's examples/coinflip is the smallest complete consumer — request, resolve, and the timeout path (a consumer with only a happy path has a game that can hang). Compile-proof fixtures under tools/anchor-compat build a real consumer on Anchor 0.32.1 and 1.1.2 on every check.