Upgrading to 3.x
What changes when you move @lazorkit/wallet from 2.x to 3.x, release by release, and what to change in your app.
@lazorkit/wallet 3.0.0 is the release that speaks protocol v2. 3.3.0 is the current
release. Full notes: the package
CHANGELOG
and the Changelog here.
npm install @lazorkit/wallet@^3.3.0 @solana/web3.js@^1.99.0@solana/web3.js must be 1.99 or later (3.0.1): @solana/wallet-adapter-base
0.9.28 requires it, and an app pinned to 1.98.x gets npm ERESOLVE. The package also
declares react, react-dom, zustand, @solana/wallet-adapter-base,
@solana/wallet-standard-features and @wallet-standard/* as peers. It declares
@solana/kit, @solana/kora and @solana-program/token too, but the bundle never loads
them.
Checklist
- Show and fund
vaultPubkey.smartWalletPubkey/wallet.smartWalletis the wallet PDA, which nothing can spend from. UseuseWallet().vaultPubkey(orwallet.vaultPda) as the user's address and asfromPubkey. Do not derive it withfindVaultPda: the exported PDA helpers derive v2 addresses at the mainnet id, which is wrong for v1 wallets and for devnet. Wallets saved withoutvaultPdaget it on the nextconnect. - Sessions need limits.
createSession()withoutspendingLimitsthrows, unless you passunrestricted: true. A session without limits can spend the whole vault through any program until it expires. - Authorities need a role, and Delegates a policy.
addAuthorityhas no default role (since 3.3.0: see below).addAuthority({ role: ROLE_SPENDER })on a v2 wallet throws withoutpolicy: serializeActions([...]). On a v1 wallet,addAuthorityrequiresunrestricted: trueand refuses a policy. - v1 users need their relayer. If
paymasterConfigpoints at a v2 relayer (LazorKit's does not sponsor v1), setv1PaymasterConfigto the relayer you used before. See Migrating from v1. - RPC URLs that do not name a cluster need
cluster="devnet"orcluster="mainnet"; anything unrecognised is taken as mainnet. connectmay ask the user. With no stored wallet,connectuses a wallet on its own only when it is the one wallet the passkey has signed for and nothing else can spend from it; otherwise it shows the built-in chooser. See Wallet Confirmation.- Sends resolve later, and can reject in new ways (3.1.0). Every send resolves once
confirmed, and
isSigningstaystrueuntil then. Handle the new errors; see Sending transactions. - Wallet adapter / Wallet Standard:
publicKeyis now the vault (it used to be the wallet PDA). - No 2.1.0 export was removed. The package CHANGELOG lists the
create*Ixbuilders,appendProtocolFeeAccounts,readAuthorityState,findOwnedCandidates,provenCandidates,chooseOwnWalletandOwnedCandidateas removed, but no published 2.x exported them, so there is nothing to replace. Coming from 2.0.0 or 2.0.1: the contract helpers those exported (LazorkitClient,deriveSmartWalletPda, theassert*/validate*utilities and others) were already dropped in 2.1.0. PROGRAM_ID/PROGRAM_ADDRESSare the v2 mainnet id. Use thePROGRAM_ID_*constants (Networks & versions) when you need a specific one.
Release by release
| Release | What changes for you |
|---|---|
| 3.0.0 (2026-09-29) | Protocol v2 support beside v1, routed per wallet: protocolVersion, v1PaymasterConfig, cluster, V1WalletRetiredError, V1WalletMigratedError. Wallet confirmation: onConfirmWallet, confirmWallet, trustedAuthorities, watchMints, the built-in chooser, PortalCancelledError (closing the portal now rejects at once). The breaking items in the checklist above. |
| 3.0.1 (2026-09-29) | Peer @solana/web3.js ^1.99.0. |
| 3.0.2 (2026-09-29) | No global Buffer polyfill needed. The wallet adapter reads at confirmed (it read at finalized, up to 15 s behind, and could fail a second send with 3006). |
| 3.1.0 (2026-09-30) | Sends resolve once confirmed; new errors TransactionFailedError, TransactionExpiredError, TransactionOutcomeUnknownError, ConfirmationTimeoutError, PreviousTransactionPendingError, SignatureReusedError, PaymasterError. Passkey transactions are sequenced (no more 3006 back to back). A passkey with no wallet can connect: its key is recovered from two signatures, at the cost of one extra prompt. dApp v0 transactions with lookup tables work through the adapter. |
| 3.2.0 (2026-09-30) | Deferred window: expiryOffset 10–9000 slots, default 1500 (was 300). DeferredExpiredError, isDeferredExpiredError, MIN_/MAX_DEFERRED_EXPIRY_SLOTS, DEFERRED_EXPIRED_CODE. |
| 3.2.1 (2026-10-01) | An inner program's 3014 is no longer reported as DeferredExpiredError; isDeferredExpiredError is true for every DeferredExpiredError; DeferredFailureContext on TX2 errors. |
| 3.3.0 (2026-10-02) | Breaking: addAuthority needs a role. Session and authority keys kept as non-extractable WebCrypto keys in IndexedDB, not plaintext localStorage: keyStorage, forgetStoredKeys(). A kept key signs only for its own wallet: KeyWalletMismatchError, isKeyWalletMismatchError. disconnect deletes the session key unless keepSessionKeys; expired session keys are deleted. Callbacks run once the action is over, one per call. The is*Error predicates recognise the SDK's own errors through wrappers. A 4018 is not resent. See below. |
Keys the SDK keeps in the browser
createSession (without sessionKey) and addAuthority generate an Ed25519 key and keep
it, so that signAndSendWithSession / signAndSendWithAuthority sign without a prompt.
Since 3.3.0 each key is a non-extractable WebCrypto key in IndexedDB, signs only while the
wallet it was registered for is connected, and the session key is deleted at disconnect:
see What the SDK stores. A
sessionKey you pass is never stored.
3.2.1 and earlier kept the secret key in localStorage as plain text, under
lazorkit-session and lazorkit-authority, where any script on your origin could read
it, and disconnect did not remove it. 3.3.0 moves such a key to IndexedDB (item 5
below).
Upgrading to 3.3.0
From 3.2.x. Full notes: the package CHANGELOG.
addAuthorityneeds arole(breaking). There is no default any more. A call withoutroledoes not compile, and at runtime a missing role, or one that is notROLE_OWNER,ROLE_ADMINorROLE_SPENDER, throws before anything is read or the passkey is prompted. So doesROLE_OWNERon a v2 wallet, whereaddAuthoritynever adds an Owner. Migration:addAuthority({ role: ROLE_ADMIN, ... })keeps 3.2.1's behaviour; for a key your app spends with, preferrole: ROLE_SPENDERwith apolicy. Ranks: useWallet › addAuthority.- Kept keys sign only for their own wallet.
signAndSendWithSession,signAndSendWithAuthorityandrevokeSession()(withoutsessionPda) need the key's wallet connected. Where you sent beforeconnectresolved (on page load, say), wait for the wallet. Otherwise they reject withKeyWalletMismatchError: handleisKeyWalletMismatchError(e)by creating a session (adding an authority) for the connected wallet, or by asking the user to connect the key's wallet. See Errors. disconnectdeletes the session key, whichever wallet it belongs to, and acreateSessionstill waiting for its transaction keeps no key once it lands. To keep a session across sign-out and sign-in, passdisconnect({ keepSessionKeys: true }); otherwise the user approves a new session after connecting again. The authority key is kept, and signs once the same wallet is connected again.disconnectacts in its own tab: another tab of the app stays connected.- Sign-out code. Removing
lazorkit-session/lazorkit-authorityfrom localStorage, orlocalStorage.clear(), no longer removes the keys: they are in IndexedDB, shared by every tab. CallforgetStoredKeys(), which deletes both (and keeps none for a session or authority still landing). - Keys from 3.2 and earlier move to IndexedDB when
LazorkitProvidermounts, or on first use. On first use each is bound to its wallet, when that can be confirmed from the session or authority PDA or from the account on chain. One whose wallet cannot be confirmed is never used (reason: 'unbound'): create the session (add the authority) again, which replaces it (forgetStoredKeys()would also delete the other kept key). The move is one-way: going back to 3.2.1 finds no key, and the user creates a new session. - Expired session keys are deleted when next read; the call rejects with "No session key found: … expired after slot …". Create a new session.
- Callbacks run once the action is over (
isSigningorisConnectingalreadyfalse), exactly one per call. A send started fromonSuccessruns, and a callback that throws is logged and changes nothing. Refusals ("Already signing", "No wallet connected", "Already connecting") callonFailtoo.disconnect,removeAuthorityandsignMessagetakeonSuccess/onFail, onuseWallet()and on the store. See Sending transactions › Callbacks. - Predicates.
isSignatureReusedError,isRetiredDeploymentErrorandisDeferredExpiredErrorrecognise the SDK's own errors, throughcauseand a wallet-adapterWalletError, so they work whereinstanceofdoes not. - A 4018 is not resent. The paymaster fails on the first answer, with
V1WalletRetiredErrorfor a retired v1 wallet, instead of after 3 attempts. - Optional:
keyStorage="memory"onLazorkitProviderkeeps no key at rest (What the SDK stores).