ShainShain/docs
Shain/Docs/CPI from Anchor
Integration

CPI from Anchor

Gate your own Anchor instructions on a live Shain session via cross-program invocation. The right shape for dapp builders who want a first-class private mode.

When to use CPI

Use CPI when your program wants to gate its instructions on a holder having an active Shain session. Unlike client-side bundling (where an off-chain script stitches the transactions together), CPI gives you guaranteed atomicity and an on-chain audit trail that the gate was checked.

If you're a dapp whose private mode is a UI toggle (not a contract-level rule), you probably want the client bundling pattern instead.

Depending on the Shain program

Add Shain as a Cargo dependency with the cpi feature so only the stub is compiled into your binary:

Cargo.tomltoml
[dependencies]
anchor-lang = "1.0.0"
shain = { path = "../shain-engine/programs/shain", features = ["cpi", "no-entrypoint"] }

In a monorepo or a published crate, pin the version:

Cargo.tomltoml
shain = { version = "0.4.1", features = ["cpi", "no-entrypoint"] }

The CPI instruction

your_program/src/instructions/private_swap.rsrust
use anchor_lang::prelude::*;
use shain::cpi::accounts::GatedAction;
use shain::cpi::gated_action;
use shain::program::Shain;

#[derive(Accounts)]
pub struct PrivateSwap<'info> {
    #[account(mut)]
    pub user: Signer<'info>,

    /// CHECK: validated by the shain program via seeds
    #[account(mut)]
    pub shain_session: UncheckedAccount<'info>,

    pub shain_program: Program<'info, Shain>,

    // ... your own accounts below
}

pub fn handler(ctx: Context<PrivateSwap>, tag: u64) -> Result<()> {
    let cpi_ctx = CpiContext::new(
        ctx.accounts.shain_program.to_account_info(),
        GatedAction {
            user: ctx.accounts.user.to_account_info(),
            shain_session: ctx.accounts.shain_session.to_account_info(),
        },
    );
    gated_action(cpi_ctx, tag)?;

    // From here, you know the caller has a live session.
    perform_private_swap(ctx, /* args */)?;
    Ok(())
}

Client-side

Your Anchor client now needs to pass the Shain session PDA and program alongside your own accounts:

client.tstypescript
import { derivePdas, DEFAULT_PROGRAM_ID } from "@shain/sdk";

const { session } = derivePdas({
  programId: DEFAULT_PROGRAM_ID,
  holder: wallet.publicKey,
});

await yourProgram.methods
  .privateSwap(new BN(tag))
  .accounts({
    user: wallet.publicKey,
    shainSession: session!,
    shainProgram: DEFAULT_PROGRAM_ID,
    // ... your accounts
  })
  .rpc();

Tagging from your program

If you want the tag to be deterministic (so off-chain analytics can label call sites), hash your program's module+fn and reduce to a u64:

tag.rsrust
pub fn tag_for(module: &str, func: &str) -> u64 {
    let s = format!("{module}:{func}");
    let mut h: u64 = 0;
    for b in s.bytes() {
        h = h.wrapping_mul(31).wrapping_add(b as u64);
    }
    if h == 0 { 1 } else { h }
}

The same algorithm is exported from the SDK as tagFromCallsite so JS and Rust agree.