LazorKit LogoLazorKit
React SDK

Types

TypeScript interfaces, constants, error classes, and the SpendingLimits wrapper of @lazorkit/wallet.

The public types of @lazorkit/wallet 3.4.0, grouped by what you're doing. Each block says whether the type is exported (import type { … } from '@lazorkit/wallet') or a shape only: the SDK uses it internally without exporting it, so derive it from an exported type when you need it in your code:

import type { WalletHookInterface } from '@lazorkit/wallet';

type SendTxPayload = Parameters<WalletHookInterface['signAndSendTransaction']>[0];
type CreateSessionOptions = NonNullable<Parameters<WalletHookInterface['createSession']>[0]>;

Wallet state

WalletInfo

Exported from @lazorkit/wallet.

interface WalletInfo {
  readonly credentialId: string;        // WebAuthn credential ID (base64)
  readonly passkeyPubkey: number[];     // 33-byte compressed secp256r1 pubkey
  readonly expo: string;                // 'web' for the React SDK
  readonly platform: string;
  /** Wallet PDA — internal account. Never send funds here. */
  readonly smartWallet: string;
  /** Vault PDA — where funds live; what users see and fund. base58. */
  readonly vaultPda?: string;
  readonly walletDevice: string;        // this passkey's authority PDA
  readonly accountName?: string;
  /** 1 = made before LazorKit v2, 2 = since. Absent on records saved by 2.x (= v1). */
  readonly protocolVersion?: 1 | 2;
}

smartWallet ≠ vaultPda

smartWallet is the internal wallet PDA. Funds live at vaultPda (useWallet().vaultPubkey). connect fills vaultPda in for records saved by releases that did not store it; do not derive it with findVaultPda.

Provider props

Shape only: not exported.

interface LazorkitProviderProps {
  children: ReactNode;
  rpcUrl?: string;
  portalUrl?: string;
  paymasterConfig?: PaymasterConfig;      // the relayer for v2 wallets
  v1PaymasterConfig?: PaymasterConfig;    // the relayer for v1 wallets (3.0.0+)
  cluster?: 'mainnet' | 'devnet';         // when rpcUrl does not say (3.0.0+)
  onConfirmWallet?: OnConfirmWallet;      // default 'builtin'
  trustedAuthorities?: string[];          // your own Ed25519 keys, base58
  watchMints?: string[];                  // SPL Token mints your app receives, base58
  keyStorage?: 'auto' | 'memory';         // where generated keys are kept; default 'auto' (3.3.0+)
}

interface PaymasterConfig {
  paymasterUrl: string;                   // Kora JSON-RPC endpoint
  apiKey?: string;                        // sent as x-api-key; not a secret
}

Use React.ComponentProps<typeof LazorkitProvider> in your code. The same settings, without children, are the exported WalletConfig (the stored config) and LazorkitAdapterConfig (the wallet adapter's, which also takes clusterSimulation).


Wallet confirmation

Everything here is exported, from 3.0.0. How it is used: Wallet Confirmation.

ConnectHookOptions

interface ConnectHookOptions {
  feeMode?: 'paymaster' | 'user';      // accepted and ignored by the web SDK
  confirmWallet?: string;              // vault (or wallet PDA) the user chose
  onConfirmWallet?: OnConfirmWallet;   // overrides the provider's for this call
  onSuccess?: (wallet: WalletInfo) => void; // once isConnecting is false
  onFail?: (error: Error) => void;
}

WalletChoice

A wallet the passkey is proven to hold, offered for the user to recognise.

interface WalletChoice {
  wallet: string;                      // wallet PDA — an internal account
  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 besides this passkey
    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 public key
    sameRelyingParty?: boolean;        // 'passkey' only: same portal as this passkey
    trusted: boolean;                  // listed in trustedAuthorities; a passkey never is
  }[];
  liveSessions: {                      // session keys that can still sign
    address: string;
    sessionKey: string | null;         // null when unreadable
    approxExpiresAt: number | null;    // ms since the epoch (400 ms a slot)
    trusted: boolean;
  }[];
  pendingDeferred: {                   // authorized, not yet executed. 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;                // the passkey's credential id (base64)
  candidates: WalletChoice[];          // in the order found — not a recommendation
}

ConfirmWalletHandler

Your own chooser: resolve with the chosen vault (or wallet), or null when the user recognises none. A throw is passed on as connect's error.

type ConfirmWalletHandler = (
  request: ConfirmWalletRequest,
) => Promise<{ wallet: string } | null> | { wallet: string } | null;

OnConfirmWallet

type OnConfirmWallet = ConfirmWalletHandler | 'builtin' | 'throw';

Errors

All exported as classes. When each is thrown and what to do: Errors.

// Connecting
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 Error {}             // portal closed, or disconnect mid-connect

// Sending (3.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 } // never landed
class TransactionOutcomeUnknownError extends Error {    // check before resending
  readonly signature: string | undefined;
}
class ConfirmationTimeoutError extends TransactionOutcomeUnknownError {
  readonly signature: string;
  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;          // JSON-RPC error code
  readonly data?: unknown;         // JSON-RPC error data
  readonly httpStatus?: number;
  readonly signature?: string;
  maybeSent: boolean;
}

// Deferred execution (3.2.0+)
class DeferredExpiredError extends Error {
  readonly code: 3014;
  readonly deferredExecPda: PublicKey;
  readonly authorizeSignature: string | undefined;
  readonly expiresAtSlot: bigint | undefined;
}
interface DeferredFailureContext {  // on every TX2 error (3.2.1+)
  deferredExecPda?: PublicKey;
  authorizeSignature?: string;
  expiresAtSlot?: bigint;
}

// v1 wallets (3.0.0+)
class V1WalletRetiredError extends Error { readonly code: 4018 }
class V1WalletMigratedError extends Error {}

// Kept session and authority keys (3.3.0+)
class KeyWalletMismatchError extends Error {
  readonly code: 'KEY_WALLET_MISMATCH';
  readonly slot: 'session' | 'authority';
  readonly reason: KeyWalletMismatchReason;
  readonly keyWallet: string | undefined;       // the wallet PDA the key signs for; undefined when unbound
  readonly connectedWallet: string | undefined; // the connected wallet's PDA; undefined when none
}
// Exported type. 'disconnected' (3.4.0): the key's wallet is connected again, but a
// disconnect ran during this send, which is not finished. Send again.
type KeyWalletMismatchReason = 'no-wallet' | 'other-wallet' | 'unbound' | 'disconnected';

// A session or Delegate send moving an asset its policy does not name (3.4.0+), from the
// v2 program release that adds errors 3037 and 3038. Nothing in it ran.
type PolicySigner = 'session' | 'authority';           // exported type
class UnlistedSolOutflowError extends Error {          // "This session is not allowed to spend SOL"
  readonly code: 3037;                                 // ActionUnlistedSolOutflow
  readonly signer: PolicySigner;                       // 'authority': a Delegate ("This key …")
  readonly cause?: unknown;                            // the failure as it came
}
class UnlistedTokenOutflowError extends Error {        // "This session is not allowed to spend this token"
  readonly code: 3038;                                 // ActionUnlistedTokenOutflow
  readonly signer: PolicySigner;
  readonly cause?: unknown;
}

Predicates: isDeferredExpiredError, isSignatureReusedError, isRetiredDeploymentError, isKeyWalletMismatchError, and since 3.4.0 isUnlistedSolOutflowError and isUnlistedTokenOutflowError. Each is true for its class from any copy of the package, also wrapped in cause or in a wallet-adapter WalletError (Errors › Telling errors apart). Since 3.3.0: in 3.2.1 isSignatureReusedError and isRetiredDeploymentError read only the error's text and missed the SDK's own errors.


Transaction payloads

Shape only: not exported.

interface TransactionOptions {
  addressLookupTableAccounts?: AddressLookupTableAccount[]; // v0 only
  txVersion?: 'legacy' | 'v0';                              // default 'v0'
  clusterSimulation?: 'devnet' | 'mainnet';                 // the portal's preview
  feeToken?: string;                                        // ignored by the web SDK
  computeUnitLimit?: number;                                // ignored by the web SDK
}

// Every send-tx method on the hook.
interface SendTxPayload {
  instructions: TransactionInstruction[];
  transactionOptions?: TransactionOptions;
}

DeferredTxPayload

Exported from @lazorkit/wallet.

authorizeAndExecute and authorizeDeferred:

interface DeferredTxPayload extends SendTxPayload {
  expiryOffset?: number;   // slots after TX1 that TX2 is accepted, 10–9000; default 1500
}

executeDeferred takes { deferredPayload: string; transactionOptions? }.

Callbacks

Exported from @lazorkit/wallet (3.3.0+). Every action of useWallet() and the store takes them; how they run: Sending transactions › Callbacks.

interface ActionCallbacks<T> {
  readonly onSuccess?: (result: T) => void;
  readonly onFail?: (error: Error) => void;
}

interface DisconnectOptions {
  readonly keepSessionKeys?: boolean;   // keep the session key; default false (deleted)
  readonly onSuccess?: () => void;
  readonly onFail?: (error: Error) => void;
}

interface RemoveAuthorityOptions {
  readonly onSuccess?: () => void;
  readonly onFail?: (error: Error) => void;
}

type SignMessageOptions = ActionCallbacks<SignMessageResult>;

LazorkitAdapterDisconnectOptions

Exported from @lazorkit/wallet (3.3.1+). LazorkitWalletAdapter.disconnect's argument; wallet-adapter UIs call disconnect() without it, which deletes the session key the SDK keeps. Since 3.4.0 the adapter's disconnect() also disconnects the store, with or without it: keepSessionKeys decides only whether the session key is deleted.

type LazorkitAdapterDisconnectOptions = Pick<DisconnectOptions, 'keepSessionKeys'>;

Signed messages

Exported from @lazorkit/wallet (3.3.1+). 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;     // the same string, or the same bytes
  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;                   // the authenticatorData's rpIdHash must be its SHA-256
  readonly origin?: string;                 // clientDataJSON's origin must equal it
}

interface VerifyWalletMessageParams extends Omit<VerifySignedMessageParams, 'publicKey' | 'rpId'> {
  readonly connection: Connection;          // the cluster the wallet lives on
  readonly wallet: string | PublicKey;      // the claimed wallet: vault or wallet PDA
  readonly credentialId: string;            // the passkey's credential id, base64
  readonly rpId: string;                    // the passkey's relying party, e.g. 'portal.lazor.sh'
  readonly cluster?: 'mainnet' | 'devnet';  // for an RPC URL that does not say
}

function signedMessageChallenge(message: SignedMessageInput): Uint8Array; // tag || SHA-256(tag || message), 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 bytes

verifySignatureBrowser (and useWallet().verifyMessage, which calls it) is deprecated since 3.3.1: it checks only that signature is over signedPayload, not which message was signed. Never use it to authenticate.


Deferred execution payloads

DeferredPayload

Exported from @lazorkit/wallet.

What TX1 registered on chain, as authorizeDeferred returns it (serialized). The SDK converts to and from its wire form with serializeDeferredPayload / deserializeDeferredPayload, both exported.

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: the only refund destination the program accepts
}

The serialized form carries version: 2; a payload from the other protocol version is refused. Re-authorize rather than replaying one across the boundary.


Session payloads

Shape only: not exported.

interface CreateSessionPayload {
  expiresInSlots?: bigint;          // default 50,000 slots
  spendingLimits?: SpendingLimits;  // required unless unrestricted; checked before the prompt
  unrestricted?: boolean;           // a session with no limits, explicitly
  sessionKey?: PublicKey | string;  // register an external key; nothing generated or stored.
                                    // One that already has a session: resolves with that
                                    // session as it was made, whatever the limits (3.4.0:
                                    // still checked first). Revoke it to change them.
}

interface RevokeSessionPayload {
  sessionPda?: PublicKey | string;  // default: the session whose key the SDK keeps (the connected wallet's)
}

SpendingLimits

Exported from @lazorkit/wallet.

High-level wrapper around the on-chain action catalogue. The SDK translates SpendingLimits into SessionAction[] before submission (spendingLimitsToActions). At least one limit is required unless you pass unrestricted: true.

interface SpendingLimits {
  /** Lifetime SOL cap (lamports) — session dies once spent. */
  solLifetimeCap?: bigint;

  /** Maximum lamports per single execute. */
  solPerTxMax?: bigint;

  /** SOL cap that resets every `windowSlots` slots. */
  solRecurring?: {
    limit: bigint;
    windowSlots: bigint;
  };

  /** The tokens the session may spend, one entry per mint (3.4.0+). */
  tokens?: readonly TokenSpendingLimit[];
}

/** Exported (3.4.0+). The limits on one token, in the mint's base units; at least one. */
interface TokenSpendingLimit {
  mint: PublicKey | string;         // the token's mint
  lifetimeCap?: bigint;             // exhausted for this token once spent
  perTxMax?: bigint;                // max per single execute
  recurring?: {                     // cap that resets every windowSlots slots
    limit: bigint;
    windowSlots: bigint;
  };
}

An asset the limits do not name cannot leave the wallet, from the v2 program release that adds errors 3037 and 3038 (not deployed yet; until then it is not bounded at all): with no SOL limit the session spends no SOL, rent the vault pays for a new account included, and it spends a token only with a tokens entry for its mint. wSOL is a mint of its own. See Session Keys › What a policy bounds.

createSession checks the limits before anything is read or the passkey is asked, and throws on a tokens entry with no limit, a mint named twice, an amount outside a u64, a window of 0 slots, more than 16 actions, or more than 244 bytes of actions. A SOL limit takes 19 bytes (solRecurring 43), a token's lifetimeCap or perTxMax 51 and its recurring 75.

// The actions a SpendingLimits stands for, checked the same way (3.4.0+). Also a
// Delegate's policy: serializeActions(spendingLimitsToActions(limits)).
function spendingLimitsToActions(limits: SpendingLimits | undefined): SessionAction[];

Need program limits?

SpendingLimits covers SOL and tokens. For program whitelisting or per-action expiries, use the raw SessionAction catalogue with LazorKitClient.createSession.

Common windows

windowSlotsat 400 ms a slot
9_000n1 hour
54_000n6 hours
216_000n1 day
1_512_000n7 days

A slot's length varies by cluster and load (devnet ran near 230 ms in September 2026), so these are approximations.


Authority payloads

Shape only: not exported.

interface AddAuthorityPayload {
  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 (not checked)
  unrestricted?: boolean;  // required to add a key to a v1 wallet
}

removeAuthority takes a positional targetAuthorityPda: string and optional RemoveAuthorityOptions.

Changed in 3.3.0

Breaking: role is required. In 3.2.1 it was optional and defaulted to ROLE_ADMIN. See useWallet › addAuthority.


Ranks & 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;

See Ranks & policies.


Low-level SessionAction

The raw action catalogue, for sessions created with LazorKitClient.createSession and for Delegate policies (serializeActions). Actions, SessionActionType, SessionAction and serializeActions are exported.

enum SessionActionType {
  SolLimit            = 1,
  SolRecurringLimit   = 2,
  SolMaxPerTx         = 3,
  TokenLimit          = 4,
  TokenRecurringLimit = 5,
  TokenMaxPerTx       = 6,
  ProgramWhitelist    = 10,
  ProgramBlacklist    = 11,
}
import { Actions, serializeActions } from '@lazorkit/wallet';
import { PublicKey } from '@solana/web3.js';

const mint = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v');
const policy = serializeActions([
  Actions.solLimit(1_000_000_000n),
  Actions.solRecurringLimit({ limit: 100_000_000n, window: 216_000n }),
  Actions.solMaxPerTx(50_000_000n),
  Actions.tokenLimit({ mint, remaining: 100_000_000n }),
  Actions.tokenRecurringLimit({ mint, limit: 10_000_000n, window: 216_000n }),
  Actions.tokenMaxPerTx({ mint, max: 5_000_000n }),
  Actions.programWhitelist(new PublicKey('11111111111111111111111111111111')),
]);

Every builder takes an optional expiresAt slot as its last argument (inside the object for the object forms).


Constants

ExportValue
DEFAULTS.PORTAL_URLhttps://portal.lazor.sh
DEFAULTS.PAYMASTER_URLhttps://kora.devnet.lazorkit.com
DEFAULTS.RPC_ENDPOINThttps://api.devnet.solana.com
DEFAULTS.SESSION_EXPIRY_SLOTS50000n
DEFAULTS.DEFERRED_EXPIRY_SLOTS1500
MIN_DEFERRED_EXPIRY_SLOTS / MAX_DEFERRED_EXPIRY_SLOTS10 / 9000
SIGNATURE_REUSED_CODE3006
DEFERRED_EXPIRED_CODE3014
RETIRED_DEPLOYMENT_CODE4018
UNLISTED_SOL_OUTFLOW_CODE / UNLISTED_TOKEN_OUTFLOW_CODE3037 / 3038 (3.4.0+)
PROGRAM_ID / PROGRAM_ADDRESSthe v2 mainnet id
PROGRAM_ID_MAINNET, PROGRAM_ID_DEVNET, PROGRAM_ID_MAINNET_V1, PROGRAM_ID_DEVNET_V1see Networks & versions
SIGNED_MESSAGE_DOMAIN'LazorKit signed message v1' (3.3.1+)
OWNERSHIP_PROOF_DOMAIN'LazorKit ownership proof v1' (3.3.1+)

Other exported helpers: forgetStoredKeys() (deletes the session and authority keys the SDK keeps; 3.3.0+), registerCluster(rpcUrl, cluster) (what the cluster prop does), getCredentialHash(credentialIdBase64), signedMessageChallenge, verifySignedMessage and verifyWalletMessage (above), createOwnershipChallenge (since 3.3.1 the domain-separated 59-byte challenge: the bytes of @lazorkit/sdk-legacy 1.4.0's createTaggedOwnershipChallenge, not of its createOwnershipChallenge, which stays 32 bare random bytes), verifyOwnershipProof, pickOwnWallet, selectWalletByAddress, readAuthorityCounter, readAuthorityPubkey, extractErrorCode, errorFromCode, ERROR_NAMES (3036–3038 and 4018 named since 3.4.0), spendingLimitsToActions (above), and the account readers AuthorityAccount / SessionAccount.

Paymaster (exported) takes { protocolVersion?, beforeAttempt? } as its second argument. beforeAttempt (3.4.0) runs right before each attempt to send, retries included, of every send that paymaster makes, and what it throws stops the send. The SDK uses it to check a kept key before each attempt.


Hook interface

WalletHookInterface (exported) is the full return type of useWallet:

interface WalletHookInterface {
  // State
  smartWalletPubkey: PublicKey | null;   // the wallet PDA (internal)
  vaultPubkey: PublicKey | null;         // the vault
  wallet: WalletInfo | null;
  protocolVersion: 1 | 2 | null;
  isConnected: boolean;
  isLoading: boolean;
  isConnecting: boolean;
  isSigning: boolean;
  error: Error | null;

  // Core. Every action takes onSuccess / onFail (ActionCallbacks), in its payload or options.
  connect: (options?: ConnectHookOptions) => Promise<WalletInfo>;
  disconnect: (options?: DisconnectOptions) => Promise<void>;
  signAndSendTransaction: (payload: SendTxPayload & ActionCallbacks<string>) => Promise<string>;
  signMessage: (message: string, options?: SignMessageOptions) => Promise<SignMessageResult>;
  /** @deprecated Never use it to authenticate: use verifyWalletMessage. */
  verifyMessage: (args: {
    signedPayload: Uint8Array;
    signature: Uint8Array;
    publicKey: Uint8Array;
  }) => Promise<boolean>;

  // Session
  createSession: (payload?: {
    expiresInSlots?: bigint;
    spendingLimits?: SpendingLimits;
    sessionKey?: PublicKey | string;
    unrestricted?: boolean;
    onSuccess?: (sessionPda: string, sessionPublicKey: string) => void;
    onFail?: (error: Error) => void;
  }) => Promise<{ sessionPda: string; sessionPublicKey: string }>;
  revokeSession: (payload?: {
    sessionPda?: PublicKey | string;
    onSuccess?: () => void;
    onFail?: (error: Error) => void;
  }) => Promise<void>;
  signAndSendWithSession: (payload: SendTxPayload & ActionCallbacks<string>) => Promise<string>;

  // Authority
  addAuthority: (payload: {
    role: number;
    policy?: Uint8Array;
    unrestricted?: boolean;
    onSuccess?: (authorityPda: string, authorityPublicKey: string) => void;
    onFail?: (error: Error) => void;
  }) => Promise<{ authorityPda: string; authorityPublicKey: string }>;
  removeAuthority: (targetAuthorityPda: string, options?: RemoveAuthorityOptions) => Promise<void>;
  signAndSendWithAuthority: (payload: SendTxPayload & ActionCallbacks<string>) => Promise<string>;

  // Deferred execution
  authorizeAndExecute: (payload: DeferredTxPayload & ActionCallbacks<string>) => Promise<string>;
  authorizeDeferred: (
    payload: DeferredTxPayload & ActionCallbacks<{ signature: string; deferredPayload: string }>,
  ) => Promise<{ signature: string; deferredPayload: string }>;
  executeDeferred: (payload: {
    deferredPayload: string;
    transactionOptions?: TransactionOptions;
  } & ActionCallbacks<string>) => Promise<string>;
}