Skip to content

This guide walks you through building a Solana program that integrates Range verification. You’ll learn two patterns: calling Range from your program, and receiving callbacks from Range.

There are two integration patterns:

PatternUse CaseFlow
Your Program → RangeAdd verification to existing functionalityYour instruction calls Range via CPI
Range → Your ProgramAtomic verification + actionRange verifies, then calls your callback
  • Anchor framework installed
  • Range program deployed (or use existing deployment)
  • Basic understanding of Solana CPI

Use this when you want to verify before performing an action.

  1. Add Range as Dependency

    In your Cargo.toml:

    [dependencies]
    anchor-lang = "0.30.0"
    range = { path = "../codama-rust-range" }
    # Or if published:
    # range = { version = "0.1.0", features = ["cpi"] }
  2. Create Your Instruction

    use anchor_lang::prelude::*;
    use range::program::Range;
    use range::cpi::accounts::VerifyRange;
    use range::cpi::verify_range;
    #[program]
    pub mod my_program {
    use super::*;
    pub fn verified_action(
    ctx: Context<VerifiedAction>,
    signature: [u8; 64],
    message: Vec<u8>,
    ) -> Result<()> {
    // 1. Call Range for verification
    let cpi_accounts = VerifyRange {
    signer: ctx.accounts.signer.to_account_info(),
    settings: ctx.accounts.range_settings.to_account_info(),
    instructions_sysvar: ctx.accounts.instructions_sysvar.to_account_info(),
    };
    let cpi_ctx = CpiContext::new(
    ctx.accounts.range_program.to_account_info(),
    cpi_accounts
    );
    verify_range(cpi_ctx, signature, message)?;
    // 2. Verification passed - do your action
    msg!("Verification successful, performing action...");
    // Your business logic here
    ctx.accounts.my_state.verified = true;
    ctx.accounts.my_state.last_verified = Clock::get()?.unix_timestamp;
    Ok(())
    }
    }
    #[derive(Accounts)]
    pub struct VerifiedAction<'info> {
    #[account(mut)]
    pub signer: Signer<'info>,
    /// Range program
    pub range_program: Program<'info, Range>,
    /// Range Settings account
    /// CHECK: Validated by Range program
    pub range_settings: AccountInfo<'info>,
    /// CHECK: Instructions sysvar
    #[account(address = anchor_lang::solana_program::sysvar::instructions::ID)]
    pub instructions_sysvar: AccountInfo<'info>,
    /// Your program's state
    #[account(mut)]
    pub my_state: Account<'info, MyState>,
    }
    #[account]
    pub struct MyState {
    pub verified: bool,
    pub last_verified: i64,
    }
  3. Client-Side Call

    const [settingsPda] = PublicKey.findProgramAddressSync(
    [Buffer.from('settings'), settingsAdmin.toBuffer()],
    RANGE_PROGRAM_ID
    );
    await myProgram.methods
    .verifiedAction(Array.from(signature), Buffer.from(message))
    .accounts({
    signer: user.publicKey,
    rangeProgram: RANGE_PROGRAM_ID,
    rangeSettings: settingsPda,
    instructionsSysvar: SYSVAR_INSTRUCTIONS_PUBKEY,
    myState: myStatePda,
    })
    .signers([user])
    .rpc();

Pattern 2: Range Calls Your Program (Callback)

Section titled “Pattern 2: Range Calls Your Program (Callback)”

Use this for atomic operations where verification and action must both succeed.

  1. Implement on_verify Callback

    use anchor_lang::prelude::*;
    use anchor_lang::solana_program::sysvar::instructions::{
    get_instruction_relative,
    load_current_index_checked,
    };
    declare_id!("YourProgramId...");
    // Range program ID - update with actual ID
    const RANGE_PROGRAM_ID: Pubkey = pubkey!("RangeProgramId...");
    #[program]
    pub mod my_callback_program {
    use super::*;
    pub fn on_verify(ctx: Context<OnVerify>, cpi_data: Vec<u8>) -> Result<()> {
    // 1. CRITICAL: Verify caller is Range
    let ix_sysvar = &ctx.accounts.instructions_sysvar;
    let current_index = load_current_index_checked(ix_sysvar)
    .map_err(|_| ErrorCode::InvalidSysvar)?;
    require!(current_index > 0, ErrorCode::CpiOnly);
    let calling_ix = get_instruction_relative(-1, ix_sysvar)
    .map_err(|_| ErrorCode::InvalidSysvar)?;
    require!(
    calling_ix.program_id == RANGE_PROGRAM_ID,
    ErrorCode::CpiOnly
    );
    // 2. Parse cpi_data
    let params: CallbackParams = CallbackParams::try_from_slice(&cpi_data)
    .map_err(|_| ErrorCode::InvalidCpiData)?;
    // 3. Execute callback logic
    msg!("Callback received: action={}", params.action);
    match params.action {
    0 => perform_action_a(&ctx)?,
    1 => perform_action_b(&ctx)?,
    _ => return Err(ErrorCode::UnknownAction.into()),
    }
    Ok(())
    }
    }
    #[derive(Accounts)]
    pub struct OnVerify<'info> {
    pub signer: Signer<'info>,
    /// CHECK: Instructions sysvar for CPI verification
    #[account(address = anchor_lang::solana_program::sysvar::instructions::ID)]
    pub instructions_sysvar: AccountInfo<'info>,
    // Add your accounts here
    #[account(mut)]
    pub user_state: Account<'info, UserState>,
    }
    #[derive(BorshDeserialize)]
    pub struct CallbackParams {
    pub action: u8,
    pub amount: u64,
    }
    #[account]
    pub struct UserState {
    pub verified_count: u64,
    }
    fn perform_action_a(ctx: &Context<OnVerify>) -> Result<()> {
    ctx.accounts.user_state.verified_count += 1;
    Ok(())
    }
    fn perform_action_b(ctx: &Context<OnVerify>) -> Result<()> {
    // Different logic
    Ok(())
    }
    #[error_code]
    pub enum ErrorCode {
    #[msg("This instruction can only be called via CPI from Range")]
    CpiOnly,
    #[msg("Invalid sysvar")]
    InvalidSysvar,
    #[msg("Invalid cpi_data")]
    InvalidCpiData,
    #[msg("Unknown action")]
    UnknownAction,
    }
  2. Client-Side Call with Callback

    import * as borsh from 'borsh';
    // Encode callback params
    class CallbackParams {
    action: number;
    amount: bigint;
    constructor(fields: { action: number; amount: bigint }) {
    this.action = fields.action;
    this.amount = fields.amount;
    }
    }
    const schema = new Map([
    [CallbackParams, { kind: 'struct', fields: [
    ['action', 'u8'],
    ['amount', 'u64'],
    ]}],
    ]);
    const params = new CallbackParams({ action: 0, amount: BigInt(100) });
    const cpiData = borsh.serialize(schema, params);
    // Call Range with callback
    await rangeProgram.methods
    .verifyRangeWithCallback(
    Array.from(signature),
    Buffer.from(message),
    Buffer.from(cpiData)
    )
    .accounts({
    signer: user.publicKey,
    settings: settingsPda,
    targetProgram: MY_CALLBACK_PROGRAM_ID,
    instructionsSysvar: SYSVAR_INSTRUCTIONS_PUBKEY,
    })
    .remainingAccounts([
    { pubkey: user.publicKey, isSigner: true, isWritable: false },
    { pubkey: SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false },
    { pubkey: userStatePda, isSigner: false, isWritable: true },
    ])
    .signers([user])
    .rpc();

A full example combining both patterns for token-gated functionality:

use anchor_lang::prelude::*;
use anchor_spl::token::{self, Token, TokenAccount, Transfer};
declare_id!("YourProgramId...");
const RANGE_PROGRAM_ID: Pubkey = pubkey!("RangeProgramId...");
#[program]
pub mod verified_vault {
use super::*;
/// Initialize a verified vault
pub fn initialize_vault(ctx: Context<InitializeVault>) -> Result<()> {
ctx.accounts.vault.admin = ctx.accounts.admin.key();
ctx.accounts.vault.total_deposited = 0;
Ok(())
}
/// Callback from Range - deposit tokens after verification
pub fn on_verify(ctx: Context<OnVerifyDeposit>, cpi_data: Vec<u8>) -> Result<()> {
// Verify CPI caller
verify_range_cpi(&ctx.accounts.instructions_sysvar)?;
// Parse deposit amount from cpi_data
let amount = u64::from_le_bytes(
cpi_data.try_into().map_err(|_| ErrorCode::InvalidCpiData)?
);
// Transfer tokens to vault
let transfer_accounts = Transfer {
from: ctx.accounts.user_token.to_account_info(),
to: ctx.accounts.vault_token.to_account_info(),
authority: ctx.accounts.signer.to_account_info(),
};
token::transfer(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
transfer_accounts
),
amount
)?;
// Update vault state
ctx.accounts.vault.total_deposited += amount;
msg!("Verified deposit of {} tokens", amount);
Ok(())
}
}
#[derive(Accounts)]
pub struct InitializeVault<'info> {
#[account(mut)]
pub admin: Signer<'info>,
#[account(
init,
payer = admin,
space = 8 + Vault::SIZE,
seeds = [b"vault", admin.key().as_ref()],
bump
)]
pub vault: Account<'info, Vault>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct OnVerifyDeposit<'info> {
pub signer: Signer<'info>,
/// CHECK: Instructions sysvar
#[account(address = anchor_lang::solana_program::sysvar::instructions::ID)]
pub instructions_sysvar: AccountInfo<'info>,
#[account(mut, constraint = user_token.owner == signer.key())]
pub user_token: Account<'info, TokenAccount>,
#[account(mut)]
pub vault_token: Account<'info, TokenAccount>,
#[account(mut)]
pub vault: Account<'info, Vault>,
pub token_program: Program<'info, Token>,
}
#[account]
pub struct Vault {
pub admin: Pubkey,
pub total_deposited: u64,
}
impl Vault {
const SIZE: usize = 32 + 8;
}
fn verify_range_cpi(ix_sysvar: &AccountInfo) -> Result<()> {
use anchor_lang::solana_program::sysvar::instructions::*;
let idx = load_current_index_checked(ix_sysvar)
.map_err(|_| ErrorCode::InvalidSysvar)?;
require!(idx > 0, ErrorCode::CpiOnly);
let ix = get_instruction_relative(-1, ix_sysvar)
.map_err(|_| ErrorCode::InvalidSysvar)?;
require!(ix.program_id == RANGE_PROGRAM_ID, ErrorCode::CpiOnly);
Ok(())
}
#[error_code]
pub enum ErrorCode {
#[msg("CPI only")]
CpiOnly,
#[msg("Invalid sysvar")]
InvalidSysvar,
#[msg("Invalid cpi_data")]
InvalidCpiData,
}
describe('verified vault', () => {
it('deposits after verification', async () => {
// Setup: get signature from backend
const amount = 1000;
const cpiData = Buffer.alloc(8);
cpiData.writeBigUInt64LE(BigInt(amount));
await rangeProgram.methods
.verifyRangeWithCallback(
Array.from(signature),
Buffer.from(message),
cpiData
)
.accounts({
signer: user.publicKey,
settings: settingsPda,
targetProgram: verifiedVault.programId,
instructionsSysvar: SYSVAR_INSTRUCTIONS_PUBKEY,
})
.remainingAccounts([
{ pubkey: user.publicKey, isSigner: true, isWritable: false },
{ pubkey: SYSVAR_INSTRUCTIONS_PUBKEY, isSigner: false, isWritable: false },
{ pubkey: userTokenAccount, isSigner: false, isWritable: true },
{ pubkey: vaultTokenAccount, isSigner: false, isWritable: true },
{ pubkey: vaultPda, isSigner: false, isWritable: true },
{ pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
])
.signers([user])
.rpc();
// Verify deposit
const vault = await verifiedVault.account.vault.fetch(vaultPda);
expect(vault.totalDeposited.toNumber()).to.equal(amount);
});
});