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:
[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:
shain = { version = "0.4.1", features = ["cpi", "no-entrypoint"] }The CPI instruction
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:
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:
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.