Docs
Quickstart
Three accounts. One CPI call. That's the entire integration.
Trana works with any Anchor program and any FIDO2 device: passkeys (Touch ID, Face ID, iCloud Keychain), YubiKey, Google Titan, Windows Hello.
Guard program ID (devnet): 572t8Ctxx1nrHgxJZ1EHSNZTLcMH4oxV1R6g2pRAqba6
Step 1 — Add the Guard as a CPI Dependency
In your program's Cargo.toml:
[dependencies]
anchor-lang = "0.30"
trana-guard = { version = "0.1", features = ["cpi"] }
Step 2 — Add the Enforce CPI to Your Instruction
Import the guard and call enforce() at the start of any instruction you want to protect:
use trana_guard::cpi::accounts::Enforce;
use trana_guard::program::TranaGuard;
use trana_guard::Policy;
#[derive(Accounts)]
pub struct Withdraw<'info> {
// ... your existing accounts ...
// Three Trana accounts — add these:
pub guard_program: Program<'info, TranaGuard>,
#[account(
mut,
seeds = [b"2fa", owner.key().as_ref()],
bump,
seeds::program = guard_program.key(),
)]
pub two_factor_registry: Account<'info, trana_guard::TwoFactorRegistry>,
/// CHECK: Instructions sysvar — required by the guard
#[account(address = anchor_lang::solana_program::sysvar::instructions::ID)]
pub instructions_sysvar: UncheckedAccount<'info>,
}
pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> {
// Enforce passkey approval before any logic runs
trana_guard::cpi::enforce(
CpiContext::new(
ctx.accounts.guard_program.to_account_info(),
Enforce {
registry: ctx.accounts.two_factor_registry.to_account_info(),
instructions_sysvar: ctx.accounts.instructions_sysvar.to_account_info(),
},
),
Policy::Threshold { param_offset: 0, threshold: 1_000_000_000 }, // require 2FA for >= 1 SOL
)?;
// Your withdrawal logic here
// ...
Ok(())
}
That's the entire program-side integration. No changes to your account structure beyond the three Trana accounts.
Step 3 — Install the SDK
npm install @trana/sdk
Step 4 — Wrap Your App with TranaProvider
import { TranaProvider, TranaModal } from "@trana/sdk"
import { PublicKey } from "@solana/web3.js"
const GUARD_PROGRAM_ID = new PublicKey("572t8Ctxx1nrHgxJZ1EHSNZTLcMH4oxV1R6g2pRAqba6")
export default function App({ children }: { children: React.ReactNode }) {
return (
<TranaProvider config={{ guardProgramId: GUARD_PROGRAM_ID, cluster: "devnet" }}>
{children}
<TranaModal /> {/* handles passkey registration + approval prompts */}
</TranaProvider>
)
}
Step 5 — Call authorizeAndSend
Replace your existing sendTransaction call with authorizeAndSend:
import { useTrana } from "@trana/sdk"
export function WithdrawButton({ vaultPda, amount }: Props) {
const { authorizeAndSend } = useTrana()
const { publicKey } = useWallet()
async function handleWithdraw() {
const lamports = BigInt(Math.floor(amount * 1e9))
await authorizeAndSend({
// Describe the action — shown in the confirmation modal
buildIntent: () => ({
targetProgramId: MY_VAULT_PROGRAM_ID,
instructionDiscriminator: WITHDRAW_DISCRIMINATOR, // first 8 bytes of ix data
accounts: [vaultPda, publicKey!],
params: u64LE(lamports), // raw instruction params
label: `Withdraw ${amount} SOL`,
}),
// Build the actual transaction — called after passkey approval
buildTransaction: async ({ recentBlockhash }) => {
const tx = new Transaction({ recentBlockhash, feePayer: publicKey! })
tx.add(buildWithdrawIx(vaultPda, publicKey!, lamports))
return tx
},
})
}
return <button onClick={handleWithdraw}>Withdraw</button>
}
The SDK handles the rest:
- Simulates the transaction to detect which policy fires
- Lazy-registers the passkey if this is the user's first time
- Shows a confirmation modal with the decoded amount
(from transaction) - Prompts Touch ID / YubiKey for approval
- Constructs the secp256r1 + record_proof instructions
- Retries on blockhash expiry without a second biometric prompt
How It Works
The transaction that lands onchain has this shape:
ix[0]: secp256r1 precompile ← Solana validates this P-256 signature natively
ix[1]: guard::record_proof ← carries WebAuthn authenticator data + intent binding
ix[2]: your_program::withdraw → calls guard::cpi::enforce() internally
The guard program reads ix[0] and ix[1] via the Instructions sysvar at the moment ix[2] executes. If the proof is missing, expired, or tampered — execution fails inside your program, before any state changes.
Supported FIDO2 Devices
Any WebAuthn authenticator using P-256 (COSE algorithm ES256 / -7):
| Device | Notes |
|---|---|
| Touch ID (Mac, iPhone) | Synced via iCloud Keychain |
| Face ID (iPhone, iPad) | Synced via iCloud Keychain |
| Android biometric | Synced via Google Password Manager |
| YubiKey 5 series | Hardware-bound, not synced — best for enterprise |
| Google Titan | Hardware-bound |
| Windows Hello | Platform authenticator |
Next Steps
- Security Model — understand what the guarantee covers
- Policy Reference — choose the right enforcement policy
- Trana vs Multisig — how to combine with Squads