Quickstart
Three instructions. One CPI call. Five minutes.
1. Add the dependency
[dependencies]
anchor-lang = "0.32.1"
trana_guard = { version = "0.1.0", features = ["cpi", "devnet"] }
# localnet: features = ["cpi", "localnet"]2. Add Trana accounts
Add a trana_cpi_ctx() helper on your accounts struct so the enforce call is one line everywhere.
use trana_guard::{cpi::accounts::Enforce, program::TranaGuard, Policy};
#[derive(Accounts)]
pub struct Withdraw<'info> {
#[account(mut, has_one = owner)]
pub vault: Account<'info, VaultState>,
#[account(mut)]
pub owner: Signer<'info>,
/// CHECK: withdrawal destination
#[account(mut)]
pub destination: UncheckedAccount<'info>,
// --- trana_guard ---
pub trana_guard_program: Program<'info, TranaGuard>,
/// CHECK: guard validates ownership and proof internally
#[account(mut)]
pub trana_registry: UncheckedAccount<'info>,
/// CHECK: instructions sysvar
#[account(address = anchor_lang::solana_program::sysvar::instructions::ID)]
pub instructions: UncheckedAccount<'info>,
}
impl<'info> Withdraw<'info> {
fn trana_ctx(&self) -> CpiContext<'_, '_, '_, 'info, Enforce<'info>> {
CpiContext::new(
self.trana_guard_program.to_account_info(),
Enforce {
registry: self.trana_registry.to_account_info(),
owner: self.owner.to_account_info(),
instructions: self.instructions.to_account_info(),
},
)
}
}The registry PDA is derived client-side with seeds ["passkey", owner] and passed in as UncheckedAccount. The guard validates it.
3. Call enforce
pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> {
require!(amount > 0, VaultError::ZeroAmount);
require!(amount <= ctx.accounts.vault.balance, VaultError::InsufficientFunds);
// Passkey required when amount >= 1 SOL. Guard reads amount directly from
// instruction data — the caller cannot fake it.
trana_guard::cpi::enforce(
ctx.accounts.trana_ctx(),
Policy::Limit { param_offset: 0, limit: 1_000_000_000 },
)?;
**ctx.accounts.vault.to_account_info().try_borrow_mut_lamports()? -= amount;
**ctx.accounts.destination.to_account_info().try_borrow_mut_lamports()? += amount;
ctx.accounts.vault.balance -= amount;
Ok(())
}Below 1 SOL the guard returns Ok(()) immediately — no proof needed. Above the limit it verifies the secp256r1 proof from ix[N-2].
param_offset is the byte position of your u64 after the 8-byte Anchor discriminator:
| Instruction | param_offset |
|---|---|
fn withdraw(ctx, amount: u64) | 0 |
fn transfer(ctx, recipient: Pubkey, amount: u64) | 32 |
fn action(ctx, flag: bool, amount: u64) | 1 |
4. Client — derive registry PDA
import { PublicKey } from "@solana/web3.js"
const TRANA_GUARD_ID = new PublicKey("TRAqChewX8boPDuBbVXjS7iCQAnh9gDThfBRwXauwsG")
function registryPda(owner: PublicKey): PublicKey {
const [pda] = PublicKey.findProgramAddressSync(
[Buffer.from("passkey"), owner.toBuffer()],
TRANA_GUARD_ID,
)
return pda
}5. Client — build and send a protected transaction
import { TranaGuardClient, Policy } from "@tranaprotocol/sdk"
const client = new TranaGuardClient({ connection, cluster: "devnet" })
// Build your protected instruction as usual
const withdrawIx = await program.methods
.withdraw(new BN(amount))
.accounts({
vault,
owner: wallet.publicKey,
destination: wallet.publicKey,
tranaGuardProgram: TRANA_GUARD_ID,
tranaRegistry: registryPda(wallet.publicKey),
instructions: SYSVAR_INSTRUCTIONS_PUBKEY,
})
.instruction()
// Build the secp256r1 + record_proof proof pair
const { secp256r1Ix, recordProofIx } = await client.buildProof({
protectedIx: withdrawIx,
owner: wallet.publicKey,
credentialId: storedHandle.credentialId, // saved from registerPasskey()
policy: Policy.Limit(0, 1_000_000_000n),
rpId: window.location.hostname,
})
// Assemble the triplet and send
const tx = new Transaction().add(secp256r1Ix, recordProofIx, withdrawIx)
await wallet.sendTransaction(tx, connection)Policies
| Variant | Requires proof when |
|---|---|
Policy::Require | Always |
Policy::Limit { param_offset, limit } | u64 at offset >= limit |
Policy::NotBefore { slot } | current slot < slot |
Policy::NotAfter { slot } | current slot > slot |
Authenticators
Touch ID · Face ID · Android biometric · YubiKey 5 · Google Titan · Windows Hello
Last updated on