useWallet
Hook API — connect, sign, manage sessions & authorities.
import { useWallet } from '@lazorkit/wallet-mobile-adapter';
// Back-compat alias — both exports point to the same hook:
import { useLazorWallet } from '@lazorkit/wallet-mobile-adapter';useWallet returns the wallet state plus every mutation method. Each mutation opens the
portal, signs the challenge with a passkey, then relays the resulting transaction through
the paymaster.
Every mutation takes options
Passkey-signed methods require a trailing options: SignOptions argument with your
redirectUrl and optional onSuccess / onFail callbacks. Session-signed methods
(no portal) only need callbacks.
Quick reference
| Method | Portal prompt | Use for |
|---|---|---|
connect | yes | Authenticate + create/load the smart wallet |
disconnect | no | Clear local state |
signAndSendTransaction | yes | Single-transaction Execute |
signMessage | yes | Off-chain passkey signature |
createSession | yes | Mint an Ed25519 session key with optional limits |
signAndSendWithSession | no | Send a tx using a stored session keypair |
revokeSession | yes | Close a session, refund rent |
addAuthorityEd25519 | yes | Grant an Ed25519 key admin / spender rights |
removeAuthority | yes | Remove any authority by PDA |
listAuthorities | no | Read all authorities on the wallet |
authorizeAndExecute | yes (×1) | Deferred 2-tx flow for oversized payloads (atomic, bundled) |
authorizeDeferred | yes | TX1 only — returns DeferredPayload to run TX2 later elsewhere |
executeDeferred | no | TX2 only — submit from a previously-authorized payload |
reclaimDeferred | no | Close an expired DeferredExec and reclaim its rent |
transferSol | yes | Convenience — SOL transfer from the vault |
State
| Field | Type | Notes |
|---|---|---|
smartWalletPubkey | PublicKey | null | Vault PDA — where funds live. Use for balances, QRs. |
vaultPubkey | PublicKey | null | Alias of smartWalletPubkey. |
walletPdaPubkey | PublicKey | null | Wallet metadata PDA — only for raw LazorKitClient. |
passkeyPubkey | number[] | null | 33-byte compressed secp256r1 pubkey. |
isConnected | boolean | |
isLoading | boolean | Any async op in progress. |
isConnecting | boolean | |
isSigning | boolean | Portal round-trip in flight. |
error | Error | null | |
connection | Connection | The @solana/web3.js connection from the provider. |
Option shapes
// Passkey-signed flows (open the portal). Every mutation that prompts the user uses this.
interface SignOptions {
redirectUrl: string; // your app's deep-link scheme
onSuccess?: (result: any) => void;
onFail?: (error: Error) => void;
}
// No-passkey flows (session / executeDeferred / reclaimDeferred). No redirectUrl needed.
interface TxCallbacks {
onSuccess?: (signature: string) => void;
onFail?: (error: Error) => void;
}Connection
connect
Open the portal, authenticate the user with their passkey, and either load the existing smart wallet or create a fresh one on-chain.
const wallet = await connect({ redirectUrl: 'myapp://home' });
// wallet.smartWallet → vault PDA (base58)
// wallet.walletPda → metadata PDA
// wallet.walletDevice → authority PDA for this passkeyReturns Promise<WalletInfo>.
disconnect
await disconnect({ onSuccess, onFail });Clears the persisted state. The on-chain wallet is untouched — reconnect any time.
Transactions
signAndSendTransaction
Single-transaction execution. For payloads up to ~574 bytes of inner instructions.
const sig = await signAndSendTransaction(
{
instructions: [ix],
transactionOptions: {
feeToken: 'USDC', // optional: pay fees in any SPL mint
computeUnitLimit: 400_000,
addressLookupTableAccounts: [alt],
clusterSimulation: 'devnet', // portal-side preview cluster
},
},
{ redirectUrl: 'myapp://callback' },
);Returns Promise<string> — confirmed signature.
authorizeAndExecute
Two-transaction deferred flow for payloads that don't fit in a single transaction (Jupiter swaps with routes + ATAs, multi-CPI batches). One passkey prompt covers both txs, and the SDK submits them back-to-back. Use this when everything runs in the same process.
const sig = await authorizeAndExecute(
{
instructions: jupiterSwapIxs,
expiryOffset: 300, // slots the authorization stays valid (~2m)
transactionOptions: { clusterSimulation: 'devnet' },
},
{ redirectUrl: 'myapp://callback' },
);How it works
TX1 Authorize records a SHA-256 hash of your instruction set. TX2 ExecuteDeferred
replays those exact instructions — the program rejects any mismatch. authorizeAndExecute
submits both transactions automatically and returns TX2's signature.
authorizeDeferred
TX1 only. Passkey-signed Authorize. Returns the payload needed for TX2 so you can
run it elsewhere — a server relayer, another device, a scheduled job.
const { signature, deferredPayload, deferredExecPda, counter } = await authorizeDeferred(
{
instructions: jupiterSwapIxs,
expiryOffset: 600, // 10 min
transactionOptions: { clusterSimulation: 'devnet' },
},
{ redirectUrl: 'myapp://callback' },
);Returns Promise<AuthorizeResult>.
executeDeferred
TX2 only. No passkey prompt. Submit ExecuteDeferred from a payload produced by a
prior authorizeDeferred call (either from this device, or deserialized from a wire
format — see serializeDeferredPayload).
// Same machine — use the object directly:
const txSig = await executeDeferred({
deferredPayload, // from authorizeDeferred result
refundDestination: smartWalletPubkey, // optional — rent goes to vault
transactionOptions: { clusterSimulation: 'devnet' },
});reclaimDeferred
No passkey prompt. Close an expired DeferredExec PDA and recover its rent. Gated
on the original payer.
await reclaimDeferred({
deferredExecPda, // from authorizeDeferred result
refundDestination: smartWalletPubkey,
});Cross-machine deferred flows
Any party holding a serialized deferredPayload can submit TX2 — the program verifies
the instruction / accounts hash. Use serializeDeferredPayload() to get a JSON-safe
string; deserializeDeferredPayload() to reconstruct it before calling
executeDeferred.
import {
serializeDeferredPayload,
deserializeDeferredPayload,
} from '@lazorkit/wallet-mobile-adapter';
// Device A:
const { deferredPayload } = await authorizeDeferred(...);
await fetch('/relayer', { method: 'POST', body: serializeDeferredPayload(deferredPayload) });
// Relayer / device B:
const payload = deserializeDeferredPayload(await req.text());
const sig = await executeDeferred({ deferredPayload: payload });transferSol
Shortcut — SystemProgram.transfer from the vault.
await transferSol(
{ recipient: new PublicKey('…'), lamports: 50_000_000 },
{ redirectUrl: 'myapp://callback' },
);Session keys
Sessions let you skip the passkey prompt for a scoped set of operations. Create once with a passkey, then sign many transactions with a local Ed25519 key.
createSession
import { Actions, Keypair } from '@lazorkit/wallet-mobile-adapter';
const sessionKp = Keypair.generate();
const currentSlot = BigInt(await connection.getSlot());
const { signature, sessionPda } = await createSession(
{
sessionKey: sessionKp.publicKey,
expiresAtSlot: currentSlot + 216_000n, // ~24h on mainnet
actions: [
Actions.solRecurringLimit({ limit: 1_000_000_000n, window: 216_000n }),
Actions.solMaxPerTx(500_000_000n),
Actions.programWhitelist(JUPITER_PROGRAM_ID),
],
},
{ redirectUrl: 'myapp://callback' },
);Persist sessionKp.secretKey securely (e.g. expo-secure-store) along with sessionPda.
Actions are immutable
Once a session is created, its action list can't be modified. To change limits, revoke the session and create a new one.
Returns Promise<{ signature: string; sessionPda: PublicKey }>.
signAndSendWithSession
Send a transaction using the stored session keypair. No portal prompt.
const sig = await signAndSendWithSession({
sessionKeypair: sessionKp,
sessionPda,
instructions: [ix],
transactionOptions: { clusterSimulation: 'devnet' },
});Fails with 0xbd0 (ActionSolLimitExceeded) or related action errors once limits are hit.
See Troubleshooting.
revokeSession
await revokeSession(
{
sessionPda,
refundDestination: smartWalletPubkey, // send rent back to the vault
},
{ redirectUrl: 'myapp://callback' },
);Authorities
Give additional keys access to the wallet — typically an Ed25519 device key for a desktop or hardware companion.
addAuthorityEd25519
import { ROLE_ADMIN, ROLE_SPENDER } from '@lazorkit/wallet-mobile-adapter';
const { signature, newAuthorityPda } = await addAuthorityEd25519(
{ newEd25519Pubkey: deviceKp.publicKey, role: ROLE_SPENDER },
{ redirectUrl: 'myapp://callback' },
);Role defaults to ROLE_SPENDER (execute only). Use ROLE_ADMIN to also allow session /
authority management.
removeAuthority
await removeAuthority(
{ targetAuthorityPda, refundDestination: smartWalletPubkey },
{ redirectUrl: 'myapp://callback' },
);You cannot remove the last Owner. Transfer ownership first (use LazorKitClient
directly — transferOwnership isn't exposed through the hook yet).
listAuthorities
Read all authority accounts on the connected wallet. No passkey prompt.
const authorities = await listAuthorities();
// [{
// authorityPda, authorityType, role,
// credential, secp256r1Pubkey?
// }, …]Messages
signMessage
Off-chain passkey signature over an arbitrary string. Ideal for session-less auth.
const { signature, signedPayload } = await signMessage(
'Sign in as user-123',
{ redirectUrl: 'myapp://callback' },
);Both values are base64 strings. Verify server-side against the on-chain authority pubkey.
Returns Promise<{ signature: string; signedPayload: string }>.
Low-level escape hatch
The SDK re-exports everything from the program module — use these for flows the hook doesn't cover (admin protocol ops, manual prepare/finalize, custom signer layouts):
import {
LazorKitClient,
// PDAs
findWalletPda, findVaultPda, findAuthorityPda,
findSessionPda, findDeferredExecPda,
// On-chain state readers (single RPC, combined counter + pubkey)
readAuthorityState, readAuthorityCounter, readAuthorityPubkey,
// Session policy builders
Actions,
// Constants
ROLE_OWNER, ROLE_ADMIN, ROLE_SPENDER,
AUTH_TYPE_ED25519, AUTH_TYPE_SECP256R1,
// Cross-machine deferred helpers
serializeDeferredPayload, deserializeDeferredPayload,
} from '@lazorkit/wallet-mobile-adapter';
const client = new LazorKitClient(connection);
const [walletPda] = findWalletPda(userSeed);
const authorities = await client.listAuthorities(walletPda);Use `readAuthorityState` for custom flows
If you're building your own prepare/finalize loop, call readAuthorityState(connection, authorityPda) to get { counter, publicKeyBytes } in a single getAccountInfo — saves
an RPC round-trip over calling readAuthorityCounter and readAuthorityPubkey separately.