Types
TypeScript interfaces, constants, and session action builders.
Every public type grouped by what you're doing. Import anything below from
@lazorkit/wallet-mobile-adapter directly.
Wallet state
WalletInfo
The persisted record written to AsyncStorage after connect.
interface WalletInfo {
credentialId: string; // WebAuthn credential ID (base64)
passkeyPubkey: number[]; // 33-byte compressed secp256r1 pubkey
expo: string;
platform: string; // 'ios' | 'android' | …
/** Vault PDA (base58) — the address funds live at. Show this to users. */
smartWallet: string;
/** Wallet metadata PDA (base58). Needed for raw LazorKitClient calls. */
walletPda: string;
/** Authority PDA (base58) tied to this passkey credential. */
walletDevice: string;
/** 1 = made before LazorKit v2, 2 = since. Absent on records saved before 2.0.0 (= v1). */
protocolVersion?: 1 | 2;
}On mobile, smartWallet is the vault PDA (since the April 2026 store format; older
persisted records are migrated on load). On the web SDK the same field is the wallet
PDA — see Networks & versions.
WalletConfig / LazorKitProviderProps
interface WalletConfig {
portalUrl: string;
configPaymaster: { paymasterUrl: string; apiKey?: string }; // v2 wallets
v1ConfigPaymaster?: { paymasterUrl: string; apiKey?: string }; // v1 wallets (2.0.0+)
rpcUrl?: string;
cluster?: 'mainnet' | 'devnet'; // when rpcUrl does not say (2.0.0+)
rpId?: string;
}
interface LazorKitProviderProps {
rpcUrl?: string;
portalUrl?: string;
configPaymaster?: { paymasterUrl: string; apiKey?: string };
v1ConfigPaymaster?: { paymasterUrl: string; apiKey?: string };
cluster?: 'mainnet' | 'devnet';
rpId?: string;
isDebug?: boolean;
children: ReactNode;
}Wallet confirmation
Version note
Everything in this section is in @lazorkit/wallet-mobile-adapter 2.0.0 and later.
Earlier versions — 1.x, and the 2.0.0 betas up to 2.0.0-beta.4 — do not have it.
How it is used: Wallet Confirmation.
Provider and connect options
interface LazorKitProviderProps {
// ...the props above, plus:
onConfirmWallet?: OnConfirmWallet; // default 'builtin'
trustedAuthorities?: readonly string[]; // your own Ed25519 keys, base58
watchMints?: readonly string[]; // SPL Token mints your app receives, base58
}
interface ConnectOptions {
redirectUrl: string;
confirmWallet?: string; // vault (or wallet PDA) the user chose
onConfirmWallet?: OnConfirmWallet; // overrides the provider's for this call
onSuccess?: (wallet: WalletInfo) => void;
onFail?: (error: Error) => void;
}WalletChoice
A wallet the passkey is proven to hold a key of, that connect will not adopt without the
user. Show the vault address; the order is not a recommendation.
interface WalletChoice {
wallet: string; // wallet PDA
vault: string; // vault PDA — where the funds are. Show this one.
version: 1 | 2; // 1 = made before LazorKit v2, not migrated yet
lamports: number; // vault balance
signatureCount: number; // 0 = not used with this passkey yet
vaultIsSystemAccount: boolean; // false: vault handed to another program
otherAuthorities: { // every authority except this passkey's
address: string; // the authority account
type: 'passkey' | 'key'; // passkey = secp256r1, key = Ed25519
role: 'owner' | 'admin' | 'spender' | 'unknown'; // 'spender' = Delegate
key?: string; // 'key' only: the Ed25519 key
sameRelyingParty?: boolean; // 'passkey' only: same relying party
trusted: boolean; // in trustedAuthorities; a passkey never is
}[];
liveSessions: { // sessions that can still sign
address: string;
sessionKey: string | null; // null when unreadable
approxExpiresAt: number | null; // ms since the epoch (slots × 400 ms)
trusted: boolean;
}[];
pendingDeferred: { // authorized, not yet run or expired. Never trusted.
address: string;
approxExpiresAt: number | null;
}[];
tokenGrants: { // rights over the vault's token accounts
tokenAccount: string;
tokenProgram: string;
mint: string | null;
kind: 'delegate' | 'closeAuthority' | 'owner' | 'unreadable';
grantee: string | null;
trusted: boolean;
}[];
}ConfirmWalletRequest
interface ConfirmWalletRequest {
credentialId: string;
candidates: WalletChoice[];
}ConfirmWalletHandler
Your own chooser. Resolve with the chosen candidate's vault (or wallet), or null when
the user recognises none of them. Never choose for the user.
type ConfirmWalletHandler = (
request: ConfirmWalletRequest,
) => Promise<{ wallet: string } | null> | { wallet: string } | null;OnConfirmWallet
type OnConfirmWallet = ConfirmWalletHandler | 'builtin' | 'throw';<WalletChooser />
The built-in chooser, for rendering inside your own <Modal> on iOS — while it is mounted
it draws instead of the provider's. See
Wallet Confirmation › iOS.
Errors
class WalletNeedsConfirmationError extends Error { // onConfirmWallet: 'throw'
readonly credentialId: string;
readonly candidates: WalletChoice[];
}
class WalletConfirmationDeclinedError extends Error {} // the user chose none; nothing saved
class PortalCancelledError extends LazorKitError {} // code 'PORTAL_CANCELLED'
class WalletChooserNotShownError extends LazorKitError {} // code 'WALLET_CHOOSER_NOT_SHOWN' (iOS)Options & payloads
SignOptions
Trailing argument on every passkey-signed method (opens the portal).
interface SignOptions {
redirectUrl: string; // your app deep-link scheme
onSuccess?: (result: any) => void;
onFail?: (error: Error) => void;
}DisconnectOptions
disconnect's argument, on the hook and on the store (2.3.0+).
interface DisconnectOptions {
onSuccess?: () => void;
onFail?: (error: Error) => void;
}TxCallbacks
Trailing argument on non-passkey flows — session-signed txs, executeDeferred,
reclaimDeferred. No redirectUrl needed.
interface TxCallbacks {
onSuccess?: (signature: string) => void;
onFail?: (error: Error) => void;
}Transaction payloads
interface TransactionOptions {
feeToken?: string; // SPL mint base58
addressLookupTableAccounts?: AddressLookupTableAccount[];
computeUnitLimit?: number;
clusterSimulation?: 'devnet' | 'mainnet';
}
interface SignAndSendTransactionPayload {
instructions: TransactionInstruction[];
transactionOptions?: TransactionOptions;
}
interface AuthorizeExecutePayload extends SignAndSendTransactionPayload {
expiryOffset?: number; // slots after TX1, 10–9000; default 1500
}
// For `authorizeDeferred` — TX1 only, returns payload for later TX2.
interface AuthorizePayload {
instructions: TransactionInstruction[];
expiryOffset?: number; // slots after TX1, 10–9000; default 1500
transactionOptions?: TransactionOptions;
}
// Returned from `authorizeDeferred`.
interface AuthorizeResult {
signature: string; // TX1 signature
deferredPayload: DeferredPayload; // feed to executeDeferred later
deferredExecPda: PublicKey;
counter: number;
}
// For `executeDeferred` — TX2 only.
interface ExecuteDeferredPayload {
deferredPayload: DeferredPayload;
refundDestination?: PublicKey; // leave unset: the program accepts only the Authorize payer
transactionOptions?: TransactionOptions;
}
// For `reclaimDeferred` — cleanup path, after the window.
interface ReclaimDeferredPayload {
deferredExecPda: PublicKey;
refundDestination?: PublicKey; // leave unset: the program accepts only the Authorize payer
}
interface SessionSignPayload {
sessionKeypair: Keypair; // Ed25519 keypair from createSession
sessionPda: PublicKey;
instructions: TransactionInstruction[];
transactionOptions?: TransactionOptions;
}
// Internal shape produced by authorize*/used by executeDeferred. Re-exported from
// the SDK so you can persist it across sessions / machines via the wire helpers.
// Serialized, it carries `version: 2` and is refused across the v1/v2 boundary.
interface DeferredPayload {
walletPda: PublicKey;
deferredExecPda: PublicKey;
compactInstructions: {
programIdIndex: number;
accountIndexes: number[];
data: Uint8Array;
}[];
remainingAccounts: {
pubkey: PublicKey;
isSigner: boolean;
isWritable: boolean;
}[];
executor?: PublicKey; // who the accounts hash expects to send TX2
refundDestination?: PublicKey; // the Authorize payer
}
// Wire helpers (JSON-safe strings) for cross-machine deferred flows.
serializeDeferredPayload(p: DeferredPayload): string;
deserializeDeferredPayload(s: string): DeferredPayload;
interface TransferSolPayload {
recipient: PublicKey;
lamports: bigint | number;
transactionOptions?: TransactionOptions;
}Session payloads
interface CreateSessionPayload {
sessionKey: PublicKey; // fresh Ed25519 public key; you keep the secret key
expiresAtSlot: bigint; // absolute slot, at most 6,480,000 slots ahead
actions?: SessionAction[]; // required unless unrestricted. Name every asset it
// may spend; at most 16, within 244 bytes (not checked)
unrestricted?: boolean; // a session with no limits, explicitly (2.0.0+)
}
interface RevokeSessionPayload {
sessionPda: PublicKey;
refundDestination?: PublicKey; // default: paymaster fee payer
}Authority payloads
interface AddAuthorityPayload {
newEd25519Pubkey: PublicKey;
role: number; // required, no default: ROLE_SPENDER (2, a Delegate) | ROLE_ADMIN (1) | ROLE_OWNER (0, v1 only)
policy?: Uint8Array; // required for ROLE_SPENDER on v2: serializeActions([...]).
// Names every asset the key may spend; within 244 bytes
unrestricted?: boolean; // required to add a key to a v1 wallet
}
interface RemoveAuthorityPayload {
targetAuthorityPda: PublicKey;
refundDestination?: PublicKey;
}
interface AuthorityEntry {
authorityPda: PublicKey;
authorityType: number; // 0 = ed25519, 1 = secp256r1
role: number; // 0 = Owner, 1 = Admin, 2 = Delegate
credential: Uint8Array; // 32 bytes
secp256r1Pubkey?: Uint8Array; // 33 bytes, secp256r1 only
}
type ListAuthoritiesResult = AuthorityEntry[];Changed in 2.3.0
Breaking: AddAuthorityPayload.role is required. In 2.2.1 it was optional and defaulted
to ROLE_SPENDER. See
useWallet › addAuthorityEd25519.
Signed messages
Exported from 2.3.1, the same as @lazorkit/wallet's. How they are used:
useWallet › Messages.
const SIGNED_MESSAGE_DOMAIN = 'LazorKit signed message v1'; // the challenge's tag
const OWNERSHIP_PROOF_DOMAIN = 'LazorKit ownership proof v1'; // connect's proofs
type SignedMessageInput = string | Uint8Array; // a string is signed as its UTF-8 bytes
// What signMessage resolves with: the passkey's assertion over signedMessageChallenge(message).
interface SignMessageResult {
readonly signature: string; // P-256, 64 bytes (r || s, low-S), base64
readonly signedPayload: string; // authenticatorData || SHA-256(clientDataJSON), base64
readonly clientDataJsonBase64: string; // its challenge is base64url(signedMessageChallenge(message))
readonly authenticatorDataBase64: string;
}
interface VerifySignedMessageParams {
readonly message: SignedMessageInput;
readonly signature: string | Uint8Array; // 64 bytes, or their base64
readonly clientDataJsonBase64: string;
readonly authenticatorDataBase64: string;
readonly signedPayload?: string; // checked when given
readonly publicKey: string | Uint8Array | ArrayLike<number>; // P-256: 33, 65 or 64 bytes, or base64. Read it from chain.
readonly rpId?: string;
readonly origin?: string;
}
interface VerifyWalletMessageParams extends Omit<VerifySignedMessageParams, 'publicKey' | 'rpId'> {
readonly connection: Connection;
readonly wallet: string | PublicKey; // the claimed wallet: vault or wallet PDA
readonly credentialId: string; // base64
readonly rpId: string; // the passkey's relying party (the provider's rpId)
readonly cluster?: 'mainnet' | 'devnet';
}
function signedMessageChallenge(message: SignedMessageInput): Uint8Array; // 58 bytes
function verifySignedMessage(params: VerifySignedMessageParams): boolean; // offline; never throws
function verifyWalletMessage(params: VerifyWalletMessageParams): Promise<boolean>; // key read from chain
function createOwnershipChallenge(): Uint8Array; // OWNERSHIP_PROOF_DOMAIN || 32 random bytes, 59 bytesLazorWalletHook.signMessage and the store's resolve with SignMessageResult (2.3.1; it
was { signature, signedPayload }).
Session actions
Actions are immutable policies attached to a session at creation — or, serialized with
serializeActions, to a Delegate authority. The on-chain program enforces them on every
Execute signed by that key. From the v2 program release that adds errors 3037 and 3038,
an asset no action names cannot leave the wallet: SOL needs a sol* action (rent the vault
pays counts) and each token a token* action for its mint. See
Session Keys › What a policy bounds.
| Action | Bytes |
|---|---|
solLimit, solMaxPerTx | 19 |
solRecurringLimit | 43 |
tokenLimit, tokenMaxPerTx | 51 |
tokenRecurringLimit | 75 |
programWhitelist, programBlacklist | 43 |
Keep a session's actions, or a policy, within 244 bytes (serializeActions(actions).length):
that is what fits in the transaction beside the passkey's response. The adapter does not
check it before the portal opens.
Builder — Actions
Prefer these helpers over hand-building the raw object:
import { Actions } from '@lazorkit/wallet-mobile-adapter';
// SOL
Actions.solLimit(remainingLamports, expiresAt?)
Actions.solRecurringLimit({ limit, window, expiresAt? })
Actions.solMaxPerTx(maxLamports, expiresAt?)
// SPL tokens
Actions.tokenLimit({ mint, remaining, expiresAt? })
Actions.tokenRecurringLimit({ mint, limit, window, expiresAt? })
Actions.tokenMaxPerTx({ mint, max, expiresAt? })
// CPI filters
Actions.programWhitelist(programId, expiresAt?)
Actions.programBlacklist(programId, expiresAt?)Raw enum / union
enum SessionActionType {
SolLimit = 1,
SolRecurringLimit = 2,
SolMaxPerTx = 3,
TokenLimit = 4,
TokenRecurringLimit = 5,
TokenMaxPerTx = 6,
ProgramWhitelist = 10,
ProgramBlacklist = 11,
}
type SessionAction =
| { type: SessionActionType.SolLimit; remaining: bigint; expiresAt?: bigint }
| { type: SessionActionType.SolRecurringLimit; limit: bigint; window: bigint; expiresAt?: bigint }
| { type: SessionActionType.SolMaxPerTx; max: bigint; expiresAt?: bigint }
| { type: SessionActionType.TokenLimit; mint: PublicKey; remaining: bigint; expiresAt?: bigint }
| { type: SessionActionType.TokenRecurringLimit; mint: PublicKey; limit: bigint; window: bigint; expiresAt?: bigint }
| { type: SessionActionType.TokenMaxPerTx; mint: PublicKey; max: bigint; expiresAt?: bigint }
| { type: SessionActionType.ProgramWhitelist; programId: PublicKey; expiresAt?: bigint }
| { type: SessionActionType.ProgramBlacklist; programId: PublicKey; expiresAt?: bigint };Common limits (cheat sheet)
| Slot window | at 400 ms a slot |
|---|---|
9_000n | 1 hour |
54_000n | 6 hours |
216_000n | 1 day |
1_512_000n | 7 days |
A slot's length varies by cluster and load (devnet ran near 230 ms in September 2026), so these are approximations.
Roles & auth types
export const ROLE_OWNER = 0; // manages every authority, including other Owners
export const ROLE_ADMIN = 1; // manages Delegates and sessions
export const ROLE_SPENDER = 2; // a Delegate: manages nothing, spends within its policy
export const AUTH_TYPE_ED25519 = 0;
export const AUTH_TYPE_SECP256R1 = 1;Errors
All exported as classes. When each is thrown and what to do: Errors.
class LazorKitError extends Error { code?: string }
class WalletConnectionError extends LazorKitError {} // "Already connecting", bad redirect
class SigningError extends LazorKitError {} // another sign action is running; no wallet
class PortalCancelledError extends LazorKitError {} // the portal was closed
class WalletChooserNotShownError extends LazorKitError {} // iOS: chooser under another modal
// Sending (2.1.0+)
class TransactionFailedError extends Error { // landed and failed
readonly signature: string;
readonly transactionError: TransactionError;
readonly slot: number;
readonly logs?: string[];
}
class TransactionExpiredError extends Error { readonly signature: string }
class TransactionOutcomeUnknownError extends Error { readonly signature: string | undefined }
class ConfirmationTimeoutError extends TransactionOutcomeUnknownError { readonly waitedMs: number }
class PreviousTransactionPendingError extends Error { readonly pendingSignature: string | undefined }
class SignatureReusedError extends Error { readonly code: 3006 }
class PaymasterError extends Error {
readonly code?: number; readonly data?: unknown; readonly httpStatus?: number;
readonly signature?: string; maybeSent: boolean;
}
// Deferred execution (2.2.0+)
class DeferredExpiredError extends Error {
readonly code: 3014;
readonly deferredExecPda: PublicKey; // holds the rent: pass it to reclaimDeferred
readonly authorizeSignature: string | undefined; // TX1, when the same call sent it
readonly expiresAtSlot: bigint | undefined;
}
interface DeferredFailureContext { deferredExecPda?: PublicKey; authorizeSignature?: string; expiresAtSlot?: bigint } // 2.2.1+
// v1 wallets (2.0.0+)
class V1WalletRetiredError extends Error { readonly code: 4018 }
class V1WalletMigratedError extends Error {}
// A session send moving an asset its actions do not name (2.4.0+), from the v2 program
// release that adds errors 3037 and 3038. Nothing in it ran.
type PolicySigner = 'session' | 'authority'; // always 'session' from this adapter
class UnlistedSolOutflowError extends Error { // "This session is not allowed to spend SOL"
readonly code: 3037;
readonly signer: PolicySigner;
readonly cause?: unknown; // the failure as it came
}
class UnlistedTokenOutflowError extends Error { // "This session is not allowed to spend this token"
readonly code: 3038;
readonly signer: PolicySigner;
readonly cause?: unknown;
}
// Re-exported from @lazorkit/sdk-legacy
class MinContextSlotNotReachedError extends Error {}Also exported: isDeferredExpiredError, isSignatureReusedError and
isRetiredDeploymentError, each true for its class from any copy of the package, also
wrapped in cause or a wallet-adapter WalletError (since 2.3.0; in 2.2.1 the last two
read only the error's text and missed the adapter's own errors); since 2.4.0
isUnlistedSolOutflowError and isUnlistedTokenOutflowError, which also recognise a raw
3037 / 3038 (0xbdd, 0xbde) unless its logs name another program; SIGNATURE_REUSED_CODE,
DEFERRED_EXPIRED_CODE, RETIRED_DEPLOYMENT_CODE, UNLISTED_SOL_OUTFLOW_CODE (3037),
UNLISTED_TOKEN_OUTFLOW_CODE (3038), MIN_DEFERRED_EXPIRY_SLOTS (10) and
MAX_DEFERRED_EXPIRY_SLOTS (9000). ERROR_NAMES and errorFromCode name 3036–3038 and
4018 since 2.4.0.
On-chain program errors appear inside error messages and logs as hex codes —
0xbd0, 0xbd1, etc. See Errors › Program error codes.