Skip to Content
SDK Reference

SDK Reference

TypeScript client for the trana_guard and trana_authority programs.

npm install @tranaprotocol/sdk

Import paths

PathContents
@tranaprotocol/sdkBoth clients + all core types (tree-shake friendly)
@tranaprotocol/sdk/guardTranaGuardClient only
@tranaprotocol/sdk/authorityTranaAuthorityClient only
@tranaprotocol/sdk/testingTest helpers — never ship to prod

TranaGuardClient

Manages passkey registration and proof building for the guard program.

import { TranaGuardClient, Policy } from "@tranaprotocol/sdk"
 
const client = new TranaGuardClient({ connection, cluster: "devnet" })
OptionTypeDefault
connectionConnectionrequired
cluster"devnet" | "localnet" | "mainnet-beta" | "custom""devnet"
programIdPublicKeywell-known ID for cluster

registerPasskey

Browser only — triggers the native OS passkey dialog.

const { instruction, handle } = await client.registerPasskey({
  owner:           wallet.publicKey,
  rpId:            window.location.hostname,
  userDisplayName: wallet.publicKey.toBase58().slice(0, 8),
})
 
// include instruction in a transaction and send it
await wallet.sendTransaction(new Transaction().add(instruction), connection)
 
// persist handle — needed for every future buildProof() call
localStorage.setItem("tranaHandle", JSON.stringify({
  pubkeyBytes:  Array.from(handle.pubkeyBytes),
  credentialId: Array.from(handle.credentialId),
}))

buildProof

Browser only — triggers the passkey signing dialog, returns two instructions.

const { secp256r1Ix, recordProofIx } = await client.buildProof({
  protectedIx:  withdrawIx,
  owner:        wallet.publicKey,
  credentialId: handle.credentialId,
  policy:       Policy.Limit(1_000_000_000n),
  rpId:         window.location.hostname,
})
 
// The triplet order is required — the guard reads ix[N-2] relative to your instruction
const tx = new Transaction().add(secp256r1Ix, recordProofIx, withdrawIx)
await wallet.sendTransaction(tx, connection)
OptionTypeNotes
protectedIxTransactionInstructionThe instruction your program enforces
ownerPublicKeyWallet that owns the registry
credentialIdUint8ArrayFrom handle.credentialId
policyPolicyMust match what your program passes to enforce()
rpIdstringUsually window.location.hostname
noncebigintAuto-fetched from registry if omitted
expiryTtlSecnumberDefault 120 s

fetchRegistry

const registry = await client.fetchRegistry(wallet.publicKey)
// null if the owner hasn't registered yet

registryPda

const pda = client.registryPda(wallet.publicKey)
// seeds: ["passkey", owner] — pass as tranaRegistry in your accounts struct

TranaAuthorityClient

Manages upgrade authority transfers guarded by a passkey second factor.

import { TranaAuthorityClient } from "@tranaprotocol/sdk/authority"
 
const client = new TranaAuthorityClient({ connection, cluster: "devnet" })

register

Binds a passkey to an upgrade authority record. No passkey dialog — wallet signature only.

const ix = await client.register({ owner: wallet.publicKey, target: programId })
await wallet.sendTransaction(new Transaction().add(ix), connection)

executeUpgrade

Passkey required. Performs a BPF upgrade while the authority record holds the upgrade key.

const ix = await client.executeUpgrade({ owner, program, programData, buffer, spill })

fetchRecord

const record = await client.fetchRecord(wallet.publicKey, programId)

Policy

Mirrors the on-chain Policy enum. Evaluation is always enforced on-chain.

import { Policy } from "@tranaprotocol/sdk"
 
Policy.Require()                          // always require proof
Policy.Limit(1_000_000_000n)              // require when param >= 1 SOL (paramOffset = 0)
Policy.Limit(1_000_000_000n, 32)          // same, param starts at byte 32
Policy.NotBefore(slot)                    // require until slot is reached
Policy.NotAfter(slot)                     // require after slot passes

paramOffset is the byte position of your u64 after the 8-byte Anchor discriminator:

Instruction signatureparamOffset
fn withdraw(ctx, amount: u64)0
fn transfer(ctx, recipient: Pubkey, amount: u64)32
fn action(ctx, flag: bool, amount: u64)1

PasskeyHandle

Returned by registerPasskey(). Store it — both fields are required for signing.

type PasskeyHandle = {
  pubkeyBytes:  Uint8Array  // 33-byte compressed P-256 public key (stored on-chain)
  credentialId: Uint8Array  // WebAuthn credential handle (NOT a secret)
}

credentialId is not a secret. Store it in localStorage, a database, or a user profile.


Program IDs

ProgramDevnetMainnet
trana_guardGYhng7fbz51319ZwD1uBunBZs777C3KjmS52rYRcKfXn
trana_authorityTRNA8iyPm9AuBGiTeSirJm6F4jsxvq66LqfFeU7G4AN
import { PROGRAM_IDS, createConnection } from "@tranaprotocol/sdk"
 
const connection = createConnection("devnet")
const guardId    = PROGRAM_IDS.guard.devnet

Testing

Use @tranaprotocol/sdk/testing in Mocha/Jest tests to build valid proofs without a browser.

import { generateTestPasskey, buildTestProofIx } from "@tranaprotocol/sdk/testing"
 
const passkey = generateTestPasskey()               // software P-256 keypair
const { secp256r1Ix, recordProofIx } = buildTestProofIx({
  passkey,
  protectedIx,
  owner:   walletKeypair.publicKey,
  policy:  Policy.Require(),
  nonce:   0n,
})

Never import @tranaprotocol/sdk/testing in production code — it bypasses the WebAuthn ceremony entirely.


Transaction logs

Every successful enforce produces a log trace you can inspect with solana logs:

solana logs --url http://127.0.0.1:8899   # localnet
solana logs --url https://api.devnet.solana.com

A successful Policy::Require increment looks like this:

Program GYhng7... invoke [1]
Program log: Instruction: RecordProof
Program GYhng7... success

Program <your_program> invoke [1]
Program log: Instruction: Increment
Program GYhng7... invoke [2]               CPI from your program
Program log: Instruction: Enforce
Program log: TRANA require | policy=trana.require | owner=<wallet>
Program log: TRANA enforce | policy=trana.require | target=<your_program> | nonce=2
Program GYhng7... success
Program <your_program>

Log lineWhat it means
Instruction: RecordProofProof data carrier landed at ix[N-1]
Instruction: EnforceYour program called enforce() via CPI
TRANA require | policy=trana.requirePolicy evaluated — proof required
TRANA enforce | target=… | nonce=2Proof verified, nonce incremented to prevent replay

The nonce increments on every successful enforce — this is how replay attacks are blocked. A proof signed for nonce 2 cannot be reused at nonce 3.

Last updated on