LazorKit LogoLazorKit

Changelog

Protocol and SDK changes that affect integrators.

A running log of the changes that move user-visible SDK or on-chain behaviour. Internal refactors and perf work are summarised when they affect CU budgets or account sizes.

2026-04-22 — React Native SDK: deferred split, cross-device fix, RPC opt

@lazorkit/wallet-mobile-adapter@2.0.0-beta.2.

New

  • authorizeDeferred + executeDeferred + reclaimDeferred exposed on useWallet. The 2-tx deferred flow can now run in pieces across devices / servers / scheduled jobs. authorizeAndExecute stays as the atomic one-call shortcut.
    • authorizeDeferred(payload, options) — passkey-signed TX1. Returns { signature, deferredPayload, deferredExecPda, counter }.
    • executeDeferred({ deferredPayload, refundDestination?, transactionOptions? }, callbacks?) — no passkey. Submit TX2 from a prior payload (same app, another device, or a relayer).
    • reclaimDeferred({ deferredExecPda, refundDestination? }, callbacks?) — no passkey. Close an expired DeferredExec PDA and reclaim its rent.
  • DeferredPayload + wire helpers re-exported. serializeDeferredPayload() / deserializeDeferredPayload() turn the in-memory payload into a JSON-safe string — unblocks cross-machine deferred flows.
  • TxCallbacks interface. Shared shape ({ onSuccess?, onFail? }) for flows that don't go through the passkey portal (no redirectUrl required).

Fixed

  • Cross-device passkey recovery → 0x2 InvalidSignature. When a user signs in on a secondary device with an iCloud-synced / Google Password Manager passkey, the portal-reported compressed pubkey can drift from what the on-chain Authority stored — the secp256r1 precompile rejects the tx with 0x2. Two-part fix:
    • buildSecp256r1Params no longer forwards the cached wallet.passkeyPubkey. The client reads the authoritative pubkey off-chain via readAuthorityPubkey.
    • saveWallet refreshes WalletInfo.passkeyPubkey from readAuthorityPubkey on every recovery (when findWalletsByAuthority returns a match), so persisted records stay in sync with the Authority account.

Performance

  • Single getAccountInfo for counter + pubkey. New readAuthorityState(connection, authorityPda) helper decodes both fields in one round-trip. resolveSecp256r1 now uses it when the caller doesn't pre-cache publicKeyBytesone fewer RPC per passkey-signed tx in the default path (and after the cross-device recovery fix, that default path is now the common path).

Note on WalletInfo convention

The RN SDK's WalletInfo keeps smartWallet pointing to the vault PDA (where funds live) plus a separate walletPda field for the internal metadata account. The React SDK's latest convention flipped this: smartWallet = internal wallet PDA, vaultPda = vault. The two SDKs are therefore semantically different at the storage layer — file an issue if you need them aligned on your project.


2026-04-22 — React SDK: v0/legacy, deferred split, cross-device fixes

New

  • txVersion: 'legacy' | 'v0' per-call in transactionOptions (default 'v0'). Every send-tx method — signAndSendTransaction, signAndSendWithSession, signAndSendWithAuthority, authorizeAndExecute, authorizeDeferred, executeDeferred — accepts it. ALT is v0-only; passing addressLookupTableAccounts with legacy throws.
  • authorizeDeferred + executeDeferred split the 2-tx flow. authorizeDeferred returns a serialized deferredPayload string that any party (server relayer, another device, a cron job) can later submit via executeDeferred without the passkey. authorizeAndExecute stays as the atomic one-call shortcut.
  • Unified SendTxPayload. All send-tx methods now share the same payload shape.

Fixed

  • Cross-device passkey recovery (e.g. iCloud Keychain: wallet created on iPhone, reused on Mac). The recovery branch now reads the on-chain compressed pubkey via readAuthorityPubkey on the authority account matched by findWalletsByAuthority, instead of the previous listAuthorities(walletPda).find(...) which could return a non-matching authority on wallets with multiple secp256r1 entries.
  • smartWallet semantics. smartWallet is the internal Wallet PDA; the SOL-holding account is vaultPda. An earlier branch wrote vaultPda into the smartWallet slot which then caused findVault to be applied twice, producing a bogus signer at tx-level and failing sig verification. Guide and examples updated to always surface wallet.vaultPda to users.
  • Stale passkey pubkey cache. All passkey-signing actions (signAndSendTransaction, createSession, revokeSession, addAuthority, removeAuthority, authorizeAndExecute, authorizeDeferred) now source publicKeyBytes from the on-chain authority (via resolvePasskeyWalletreadAuthorityPubkey) instead of the cached wallet.passkeyPubkey. Prevents custom program error 0x2 (InvalidSignature) from the secp256r1 precompile when the cache drifts across sessions or devices.
  • Paymaster request format. Requests now use object params ({ transaction, signer_key }) and include signer_key = feePayer.toBase58() so multi-signer Kora pools always sign with the signer that matches the tx's feePayer position. Fixes tx-level -32002 Transaction did not pass signature verification seen with remote Kora deployments.

Developer experience

  • Iframe signing re-enabled by default in DialogManager; Safari/mobile connect still falls back to popup. Fixes the earlier "both branches opened a popup" bug in the sign path.
  • Session / authority flows default to v0 VersionedTransaction + tx.sign([...]) instead of legacy partialSign, aligning web SDK with wallet-mobile-adapter.

2026-04-20 — Phase 2.1 & API unification

Breaking

  • Mode 0 dropped. The Secp256r1 auth path now only supports raw clientDataJSON from a real browser authenticator. Secp256r1Signer.sign() must return clientDataJson; the typeAndFlags parameter of buildAuthPayload is gone. Bot / programmatic signing should use Ed25519 authorities instead.
  • Authority account layout changed. Secp256r1 authorities now store rpIdHash (32 bytes) instead of the variable-length rpId string. Account size is fixed at 145 bytes (Ed25519 authorities stay at ~80 bytes). Instruction data for CreateWallet / AddAuthority / TransferOwnership is unchanged — clients still send rpId; the program hashes it once at write time.
  • Unified prepare/finalize API. Every Secp256r1 op now has a matching client.prepare* / client.finalize* pair — prepareExecute/finalizeExecute, prepareCreateSession/finalizeCreateSession, etc. The old *Prepare methods that returned an embedded finalize closure are gone.
  • Fee model. Protocol fee is now charged on every transaction when the protocol is enabled — not opt-in per fee_payer. Registering a FeeRecord is opt-in for reward tracking; unregistered payers still pay the fee but don't accumulate a claim at token launch.

Improvements

  • CU reductions. Execute Secp256r1: 10,883 → 9,495 CU (-12.8%). Execute Session: 4,723 → 4,105 CU (-13.1%). Zero-copy compact instructions + elimination of Vec concats in the hot path.
  • publicKeyBytes auto-fetch. Secp256r1Params.publicKeyBytes is now optional — the client reads the pubkey off the on-chain Authority account via the new readAuthorityPubkey helper. Integrators only need to persist credentialIdHash per user.
  • Serializable DeferredPayload. New serializeDeferredPayload() / deserializeDeferredPayload() helpers turn a payload into JSON-safe bytes. Unblocks cross-machine deferred flows (sign TX1 on the user's device, submit TX2 from a server relayer).

Security hardening

  • H1 — vault / token invariants enforced around CPIs in session+actions execute (prevents round-trip swap bypass of SolMaxPerTx).
  • H2 — admin paths require program-owned ProtocolConfig and TreasuryShard accounts (prevents account-spoofing on withdraw / update).
  • M1clientDataJSON parser correctly skips strings nested inside objects.
  • payer and adminAuthority metas switched to writable where required for rent refunds.

What to do


Earlier milestones

  • Solita artifacts removed. The SDK is now fully hand-written for fine-grained control over wire layout. No IDL-generated wrappers remain.
  • Protocol fee system live on mainnet. Sharded treasury (16 shards by default), per-fee_payer FeeRecord counters, fees collected in native SOL.
  • Seedless + session.money launched. First production integrators running on the LazorKit execution layer.

For the authoritative change history, browse the lazorkit-protocol git log.