orao-network / solana-vrf

SDK for verifiable randomness function (VRF / RNG) on Solana

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

ORAO Solana VRF

Generate on-chain randomness on Solana. ORAO's Verifiable Random Function for Solana offers unbiased, fast and affordable randomness for your Solana programs. Create unique NFT characteristics, generate random levels for games and weapons, randomize airdrops and provide secure, verifiable lottery. Built using Anchor framework.


This repository provides off-chain Rust and JS web3 SDKs for requesting on-chain randomness using ORAO VRF program.

Program account (devnet/mainnet): VRFzZoJdhFWL8rkvu87LpKM3RbcVezpMEc6X5GVDr7y

Developer Integration Guide - CPI Example

CPI is an abbreviation for Cross Program Invocation on Solana – a way for one contract to call another contract within a single transaction. This section will illustrate this (full code is available in on GitHub).

The contract we'll use to illustrate the CPI is a simple single-player Russian Roulette where the outcome of a round is derived from a fulfilled randomness.

Note: the randomness will not be immediately available for your contract, so you'll need to design it in a way that it'll wait for randomness being fulfilled. In our example a player won't be able to start another round until the current one is finished (until the randomness is fulfilled).

1. Create your contract

This examples is based on the Anchor Framework. Please consult the Anchor Book on how to create a contract.

To perform a CPI call you'll need to add the orao VRF rust SDK with the cpi feature into the list of your dependencies:

[dependencies]
# ...
orao-solana-vrf = { version = "0.2.5", default-features = false, features = ["cpi"] }

2. Collect the necessary accounts

Each Solana instruction requires a proper list of accounts. We'll need to call the Request instruction so here is the list of required accounts:

  • payer – VRF client
  • network_state – VRF on-chain state address
  • treasury - address of the VRF treasury (taken from the VRF on-chain state)
  • request - PDA to store the randomness (derived from the seed)
  • system_program – required to create the request account

Above means that our instruction needs all of these accounts besides it's own accounts. Particularly our Russian-Roulette instruction will require the following list of accounts:

#[derive(Accounts)]
#[instruction(force: [u8; 32])]
pub struct SpinAndPullTheTrigger<'info> {
    /// Player will be the `payer` account in the CPI call.
    #[account(mut)]
    player: Signer<'info>,

    /// This is the player state account, it is required by Russian-Roulette to store player data
    // (number of rounds played and info to derive the last round outcome)
    #[account(
        init_if_needed,
        payer = player,
        space = 8 + PlayerState::SIZE,
        seeds = [
            PLAYER_STATE_ACCOUNT_SEED,
            player.key().as_ref()
        ],
        bump
    )]
    player_state: Account<'info, PlayerState>,

    /// This account points to the last VRF request, it is necessary to validate that the player
    /// is alive and is able to play another round.
    /// CHECK:
    #[account(
        seeds = [RANDOMNESS_ACCOUNT_SEED.as_ref(), player_state.force.as_ref()],
        bump,
        seeds::program = orao_solana_vrf::ID
    )]
    prev_round: AccountInfo<'info>,

    /// This account is the current VRF request account, it'll be the `request` account in the CPI call.
    /// CHECK:
    #[account(
        mut,
        seeds = [RANDOMNESS_ACCOUNT_SEED.as_ref(), &force],
        bump,
        seeds::program = orao_solana_vrf::ID
    )]
    random: AccountInfo<'info>,

    /// VRF treasury account, it'll be the `treasury` account in the CPI call.
    /// CHECK:
    #[account(mut)]
    treasury: AccountInfo<'info>,
    #[account(
        mut,
        seeds = [CONFIG_ACCOUNT_SEED.as_ref()],
        bump,
        seeds::program = orao_solana_vrf::ID
    )]

    /// VRF on-chain state account, it'll be the `network_state` account in the CPI call.
    config: Account<'info, NetworkState>,

    /// VRF program address to invoke CPI
    vrf: Program<'info, OraoVrf>,

    /// System program address to create player_state and to be used in CPI call.
    system_program: Program<'info, System>,
}

3. Perform a CPI call

In the Anchor Framework there is a CpiContext for this purpose (please consult the corresponding section of the Anchor Book):

let cpi_program = ctx.accounts.vrf.to_account_info();
let cpi_accounts = orao_solana_vrf::cpi::accounts::Request {
    payer: ctx.accounts.player.to_account_info(),
    network_state: ctx.accounts.config.to_account_info(),
    treasury: ctx.accounts.treasury.to_account_info(),
    request: ctx.accounts.random.to_account_info(),
    system_program: ctx.accounts.system_program.to_account_info(),
};
let cpi_ctx = anchor_lang::context::CpiContext::new(cpi_program, cpi_accounts);
orao_solana_vrf::cpi::request(cpi_ctx, force)?;

4. Use the fulfilled randomness

Our contract derives round outcome from the fulfilled randomness, round considered to be in-progress if randomness is not yet fulfilled:

/// Last round outcome.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CurrentState {
    /// Player is alive and able to play.
    Alive,
    /// Player is dead and can't play anymore.
    Dead,
    /// Player is waiting for current round to finish.
    Playing,
}

/// Derives last round outcome.
pub fn current_state(randomness: &Randomness) -> CurrentState {
    if let Some(randomness) = randomness.fulfilled() {
        if is_dead(randomness) {
            CurrentState::Dead
        } else {
            CurrentState::Alive
        }
    } else {
        CurrentState::Playing
    }
}

/// Decides whether player is dead or alive.
fn is_dead(randomness: &[u8; 64]) -> bool {
    // use only first 8 bytes for simplicyty
    let value = randomness[0..size_of::<u64>()].try_into().unwrap();
    u64::from_le_bytes(value) % 6 == 0
}
  1. Rust SDK (source code)is based on the anchor-client library, so you'll need to acquire the Program instance to use it:
let payer: Keypair = ..; // get this from the solana configuration
let client = Client::new_with_options(Cluster::Devnet, Rc::new(payer), CommitmentConfig::finalized());
let program = client.program(orao_solana_vrf::id());

Rust SDK

Check out source code.

It's simple to integrate ORAO VRF into an on-chain game. We've built a Russian Roulette contract and CLI. New developers can reference it to get insight into doing Solana CPI - Cross Program Invocation.

JS / TS SDK

Browse through js SDK and it's subdirectories for more info. Check out sample Typescript integration

How to run a test validator.

Note that anchor test will run it for the cpi tests.

Here is an example:

solana-test-validator -r \
    --bpf-program VRFzZoJdhFWL8rkvu87LpKM3RbcVezpMEc6X5GVDr7y js/dist/orao_vrf.so \
    --ledger /tmp/test-ledger

About

SDK for verifiable randomness function (VRF / RNG) on Solana

License:Apache License 2.0


Languages

Language:TypeScript 52.2%Language:Rust 47.8%