Skip to content

Quickstart: any framework

vrand-interface is the zero-dependency floor — not "few" dependencies, zero. It cannot collide with anything in your tree, so it works on any Anchor version, Pinocchio, Steel, raw Rust, today and after the next ecosystem-wide version churn. It is no_std and allocation-free.

What you get: the program id, instruction discriminators, PDA seeds, account orders and flags, instruction-data encoders, and byte offsets for reading a request back. What you bring: your own Pubkey type and PDA derivation — you already have both.

Build the request instruction

rust
use vrand_interface as vi;

// 1. Derive the request address with YOUR toolchain's helper:
let (request, _) = Pubkey::find_program_address(
    &[vi::seeds::REQUEST, requester.as_ref(), &seed],
    &Pubkey::new_from_array(vi::PROGRAM_ID),
);

// 2. Encode the instruction data:
let data = vi::ix::request_data(&seed, at_risk_lamports);

// 3. Accounts, in this exact order (signer/writable flags in
//    vi::ix::REQUEST_ACCOUNTS):
//    payer(s,w), requester(s), comp_dest, network, terms, ack,
//    prover(w), request(w), slot_hashes, system_program

terms/ack are the Terms-of-Use gate: the payer wallet must have executed acknowledge_terms once (per terms version) or request fails with TermsNotAccepted. The PDAs are [b"terms"] and [b"ack", payer] under the VRAND program id.

Read the outcome

rust
// ALWAYS check the account owner against vi::PROGRAM_ID first — this crate
// has no Pubkey type, so it cannot do it for you, and reading
// attacker-supplied bytes at fixed offsets without an owner check is how
// programs get drained.
let data = request_account.try_borrow_data()?;
match vi::request::state(&data) {
    Some(vi::request::FULFILLED) => {
        let beta = vi::request::beta(&data).unwrap(); // 64 bytes
    }
    Some(vi::request::SLASHED) => { /* compensation was paid to comp_dest */ }
    _ => { /* still pending */ }
}

Every accessor validates length and the account discriminator and returns None on anything unexpected — safe to point at bytes you did not create (after the owner check).

Off-chain TypeScript, without the web layer

@vrand.io/client is the low-level TS client: instruction builders (requestIx, acknowledgeTermsIx, closeRequestIx, slashIx), PDA derivations (pinned to the Rust ones by cross-language golden tests), account decoding, awaitFulfillment, the full local proof verifier (verifyBoundBeta), and the bias-free scale module. @vrand.io/web is built on it; server-side integrations can use it directly.

ts
import { VrandClient, requestPda, verifyBoundBeta } from "@vrand.io/client";

const client = new VrandClient(connection);
const ix = client.requestIx({ payer, requester, compDest, prover, seed, atRiskLamports: 0n });
// … sign & send …
const req = await client.awaitFulfillment(requester, seed);

Shaping outcomes

Wherever you read beta, shape it with the sanctioned bias-free helpers — scale::uniform_below, shuffle_indices, weighted, unit_float in Rust; the same names in @vrand.io/client — never a bare % n (biased for any non-power-of-two bound). Both implementations are byte-identical and pinned to each other by golden tests. Details: Verifying outcomes.

Apache-2.0. Live on Solana devnet.