LaWalletdocs
SDK

React Hooks

LaWalletProvider and every hook in @lawallet/sdk/react, with copy-pasteable examples.

@lawallet/sdk/react wraps the SDK in a provider + hooks. One prop — your instance's endpoint — and the provider fetches the instance's public settings automatically, restores remembered logins, and keeps a single SSE stream alive that refreshes the data hooks.

import { LaWalletProvider } from '@lawallet/sdk/react'

export function Root() {
  return (
    <LaWalletProvider endpoint="https://wallet.example.com">
      <App />
    </LaWalletProvider>
  )
}

Provider props:

PropDefaultPurpose
endpointrequiredPublic origin of your LaWallet instance
storage'local''local' restores logins across reloads; 'none' is memory-only
EventSourceImplglobalEventSource injection for tests / unusual runtimes

@lawallet/sdk/react re-exports the entire core, so a React app can import everything — client, signers, types, errors — from this one specifier.

useInstanceInfo — branding without configuration

import { useInstanceInfo } from '@lawallet/sdk/react'

function Header() {
  const { settings, loading } = useInstanceInfo()
  if (loading) return <Skeleton />
  return (
    <header>
      <img src={settings?.logotype_url ?? '/fallback.svg'} alt="" />
      <h1>{settings?.community_name}</h1>
    </header>
  )
}

useAuth — nostr login, four ways

import { useAuth, hasBrowserExtension } from '@lawallet/sdk/react'

function Login() {
  const auth = useAuth()

  if (auth.status === 'authenticated') {
    return (
      <p>
        {auth.npub} <button onClick={auth.logout}>Log out</button>
      </p>
    )
  }

  return (
    <>
      {hasBrowserExtension() && (
        <button onClick={() => auth.loginWithExtension()}>
          Connect NIP-07 extension
        </button>
      )}
      <button
        onClick={async () => {
          const { nsec } = await auth.loginWithNewKey({ remember: true })
          showBackupDialog(nsec) // shown ONCE — it is the account
        }}
      >
        Create a Nostr identity
      </button>
      <button onClick={() => auth.loginWithNsec(input, { remember: true })}>
        Sign in with nsec
      </button>
    </>
  )
}

loginWithSigner(signer) accepts any structural signer (NIP-46 bunker, NDK, custom) — see Authentication. There is no token and no refresh: logging in just hands the client a signer.

useUser — signup is a side effect

import { useUser } from '@lawallet/sdk/react'

function Account() {
  const { user, loading } = useUser()
  if (loading) return <Skeleton />
  return user?.lightningAddress ? (
    <p>⚡ {user.lightningAddress}</p>
  ) : (
    <ClaimFlow />
  )
}

The first authenticated fetch creates the user server-side — for a fresh npub, rendering this hook after login is the entire signup.

useAddresses / useAddress — list, create, route

import { useAddresses, useAddress, LaWalletError } from '@lawallet/sdk/react'

function Addresses() {
  const { addresses, createAddress } = useAddresses()
  return (
    <ul>
      {addresses?.map(a => (
        <li key={a.username}>
          {a.username} {a.isPrimary && '★'} — {a.mode}
        </li>
      ))}
    </ul>
  )
}

function RoutingEditor({ username }: { username: string }) {
  const { address, update, setPrimary, remove } = useAddress(username)
  return (
    <>
      <button
        onClick={() => update({ mode: 'ALIAS', redirect: 'me@getalby.com' })}
      >
        Forward elsewhere
      </button>
      <button onClick={() => update({ mode: 'IDLE' })}>Clear routing</button>
      <button onClick={setPrimary}>Make primary</button>
      <button onClick={remove}>Delete</button>
    </>
  )
}

createAddress rethrows the SDK's LaWalletError — catch status === 402 to branch into the paid flow, or skip the branching entirely with useClaimAddress.

useUsernameAvailability — check as they type

import { useUsernameAvailability } from '@lawallet/sdk/react'

function UsernameField({ value, onChange }: FieldProps) {
  const { available, checking, formatError } = useUsernameAvailability(value)
  return (
    <>
      <input value={value} onChange={e => onChange(e.target.value)} />
      <small>
        {formatError ??
          (checking
            ? 'Checking…'
            : available
              ? 'Available ✓'
              : value && available === false
                ? 'Taken ✗'
                : '')}
      </small>
    </>
  )
}

Debounced 300 ms against the public availability endpoint — safe before login.

useAddressInvoices — live received payments

import { useAddressInvoices } from '@lawallet/sdk/react'

function Payments({ username }: { username: string }) {
  const { invoices } = useAddressInvoices(username)
  return (
    <ul>
      {invoices?.map(inv => (
        <li key={inv.id}>
          {inv.amountSats} sats — {inv.status}
          {inv.comment && <q>{inv.comment}</q>}
        </li>
      ))}
    </ul>
  )
}

No polling code: the provider's SSE stream emits invoices:updated and the hook refetches itself.

useRemoteWallets — NWC connections

import { useRemoteWallets } from '@lawallet/sdk/react'

function Wallets() {
  const { wallets, create, createLncurl, remove, getBalance } =
    useRemoteWallets()

  const connect = (connectionString: string) =>
    create({ name: 'My wallet', type: 'NWC', config: { connectionString } })

  const disposable = () => createLncurl() // server-minted LNCurl wallet

  return (
    <ul>
      {wallets?.map(w => (
        <li key={w.id}>
          {w.name} — {w.status}
          <button onClick={() => remove(w.id)}>Revoke</button>
        </li>
      ))}
    </ul>
  )
}

See NWC & routing for binding a wallet to an address.

useClaimAddress — the whole onboarding state machine

The flagship hook: username picking, availability, the paid branch (invoice QR, WebLN, LUD-21 polling, refresh-resume) and claiming — as one headless state machine. Full guide: Registration flow.

Lower-level building blocks

  • useLaWallet() — the raw LaWalletClient for anything not covered above.
  • useResource(key, fetcher, eventTypes?) — the caching primitive every data hook is built on (in-flight dedupe, cache-until-invalidated, SSE refresh); use it for your own endpoints or plugin routes.
  • useSSEConnected() — connection indicator for a "live" badge.
import { useLaWallet, useResource } from '@lawallet/sdk/react'

function PluginPanel() {
  const { client } = useLaWallet()
  const { data } = useResource('/api/plugins', () =>
    client ? fetchMyPluginData(client) : Promise.reject()
  )
  return <pre>{JSON.stringify(data, null, 2)}</pre>
}

On this page