Skip to main content
The Passkey Manager Program is the core authorization program for passkey-backed flows on Thru. From a developer perspective, the key idea is that the passkey does not sign the outer Thru transaction directly. Instead, the program verifies a WebAuthn signature over a challenge built from the wallet nonce, the ordered account list, and the trailing instruction bytes that the program action is authorizing.

Use This When

  • you need passkey-backed authorization for a transfer, CPI flow, or other nonce-bound program action
  • you need to reason about authority records, credential lookup accounts, or nonce advancement
  • you are integrating WebAuthn or native passkey signatures into a Thru transaction flow

Quickstart

Fetch the ABI, generate TypeScript builders, then build instruction bytes and send the outer transaction.
thru-cli abi account get --include-data --out ./passkey-manager.abi.yaml <ABI_ACCOUNT_ADDRESS>

thru-cli abi codegen \
  --files ./passkey-manager.abi.yaml \
  --language typescript \
  --output ./generated
import { decodeAddress } from "@thru/helpers";
import type { Thru } from "@thru/thru-sdk/client";
import {
  PasskeyInstruction,
  TransferArgs,
} from "./generated/thru/program/passkey_manager/types";

const instructionData = PasskeyInstruction.builder()
  .payload()
  .select("transfer")
  .writePayload(
    TransferArgs.builder()
      .set_wallet_account_idx(2)
      .set_to_account_idx(3)
      .set_amount(1_000_000)
  )
  .finish()
  .build();

const tx = await thru.transactions.build({
  feePayer: { publicKey: decodeAddress(feePayerAddress) },
  program: "taUDdQyFxvM5i0HFRkEK3W45kWLyblAHSnMg4zplgUnz6Z",
  accounts: {
    readWrite: [walletAddress, destinationAddress],
    readOnly: [],
  },
  instructionData,
});

const signedWire = await signTransaction(tx.toWireForSigning());
const signature = await thru.transactions.send(signedWire);
Real user-facing passkey flows usually prepend a generated validate instruction before the trailing program action. If you want the full validate-and-send path instead of raw generated builders, use @thru/passkey-manager.
The passkey proof and the outer transaction signature are different layers. A successful validate step authorizes the wallet action inside the program, but the outer transaction still needs to be built and submitted normally.

How It Works

Most passkey-managed flows follow this pattern:
  1. fetch the nonce from the on-chain wallet account
  2. build the trailing instruction payload you want the program to authorize
  3. hash nonce || ordered account addresses || trailing instruction bytes
  4. sign that challenge with WebAuthn
  5. encode validate
  6. concatenate validate with the trailing instruction payload
  7. submit the resulting transaction against the Passkey Manager Program
This validate-then-execute model works whether the caller is a first-party wallet, a custom web app, or a mobile/backend integration.

Account Model

AccountWhat it storesNotes
WalletAccountWallet header with num_auth and nonceIn practice, wallet data also includes trailing 65-byte authority records after the fixed header.
CredentialLookupWallet pubkey for a registered credentialUseful when a passkey needs a lookup account for registration or recovery flows.

Authority Records

Wallet authority entries are 65-byte tagged records:
  • tag = 1: passkey authority, stored as P-256 x[32] + y[32]
  • tag = 2: pubkey authority, stored as pubkey[32] + padding[32]

Required Accounts

Passkey-manager instructions rely on stable account indices:
  • the fee payer is index 0
  • the Passkey Manager Program is index 1
  • the wallet account must appear as a non-fee-payer account in the transaction
  • trailing instructions reference accounts by their position in the final ordered account list
If you are using the Web SDK, buildAccountContext(...) handles this ordering and index lookup.

Events

EventWhat it means
wallet_createdA new passkey-managed wallet account was created.
wallet_validatedA validate step succeeded and advanced the wallet nonce.
wallet_transferThe wallet transferred balance to another account.
credential_registeredA credential lookup account was registered for the wallet.

Instructions

InstructionUse it whenNotes
createCreate a new passkey-managed walletIncludes the initial authority and a state proof for the new wallet account.
validateSubmit a WebAuthn proof for the trailing wallet actionCarries r, s, authenticatorData, and clientDataJSON.
transferMove native balance from the managed walletUsually follows validate in the same transaction payload.
invokeHave the managed wallet call another programThis is the main path for wallet-controlled CPI flows.
add_authorityAdd another passkey or pubkey authorityUseful for multi-authority or recovery flows.
remove_authorityRemove an existing authority by indexUse with care because authority ordering matters.
register_credentialCreate a credential lookup accountUsed when you want a stable on-chain mapping for a credential.

Integration Surfaces

The program stands on its own, but most developers will interact with it through one of these integration patterns:

Web

  • Use @thru/passkey-manager when your app needs to build validate, transfer, invoke, or authority-management instructions directly.
  • Use @thru/passkey when you need the browser WebAuthn registration and signing layer that feeds signatures into passkey-manager transactions.
  • Embedded-wallet integrations typically use the wallet or provider layer for the outer transaction while relying on passkey-manager for the inner authorization payload.

Mobile

  • Mobile apps can use native passkey surfaces to register a credential and produce the WebAuthn proof locally.
  • A common mobile pattern is challenge-submit: fetch or construct the passkey-manager challenge, sign it on-device, then send the signature components and WebAuthn payload back to a backend or transaction service that assembles the final transaction.