npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@lumiapassport/ui-kit

v1.14.1

Published

React UI components and hooks for Lumia Passport authentication and Account Abstraction

Readme

@lumiapassport/ui-kit

React UI components and hooks for Lumia Passport - a secure, user-friendly authentication and Account Abstraction wallet solution with MPC (Multi-Party Computation) key management.

Features

  • 🔐 Secure Authentication - Multiple auth methods: Email, Passkey, Telegram, Wallet connect
  • 🔑 MPC Key Management - Distributed key generation with iframe isolation
  • 💼 Account Abstraction - ERC-4337 compliant smart contract wallets
  • 🎨 Pre-built UI Components - Ready-to-use React components with customizable themes
  • Easy Integration - Just wrap your app with LumiaPassportProvider

Installation

npm install @lumiapassport/ui-kit
# or
pnpm add @lumiapassport/ui-kit
# or
yarn add @lumiapassport/ui-kit

Quick Start

1. Setup QueryClient (Required)

The UI Kit requires @tanstack/react-query for query management. First, create a query client:

// queryClient.ts
import { QueryClient } from '@tanstack/react-query'

export const queryClient = new QueryClient({
  defaultOptions: {}
})

2. Wrap your app with providers

import {
  //
  LumiaPassportProvider,
  LumiaPassportSessionProvider,
  LumiaRainbowKitProvider
} from '@lumiapassport/ui-kit'

import { queryClient } from './queryClient'

function Root() {
  return (
    <QueryClientProvider client={queryClient}>
      <LumiaPassportProvider
        projectId="your-project-id" // Get from Lumia Passport Dashboard
      >
        <LumiaRainbowKitProvider>
          <LumiaPassportSessionProvider>
            <YourApp />
          </LumiaPassportSessionProvider>
        </LumiaRainbowKitProvider>
      </LumiaPassportProvider>
    </QueryClientProvider>
  )
}

3. Add the Connect Button

import { ConnectWalletButton } from '@lumiapassport/ui-kit'

function YourApp() {
  return (
    <div>
      <h1>My App</h1>
      <ConnectWalletButton label="Sign in" />
    </div>
  )
}

3.1 (Optional)

Custom unconnected button can be provided via ConnectButton prop. Prop consumes standart HTMLButton component and will provide required onClick to it

import { ConnectWalletButton } from '@lumiapassport/ui-kit'

function CustomButtonComponent(props: HTMLAttributes<HTMLButtonElement>) => {
  return (<button {...props}/>)
}

function YourApp() {
  return (
    <div>
      <h1>My App</h1>
      <ConnectWalletButton label="Sign in" ConnectButton={CustomButtonComponent} />
    </div>
  )
}

That's it! The ConnectWalletButton provides a complete authentication UI with wallet management.

Note: Don't forget to wrap your app with QueryClientProvider from @tanstack/react-query before using LumiaPassportProvider, otherwise you'll get an error: "No QueryClient set, use QueryClientProvider to set one"

Configuration Options

Basic Configuration

<LumiaPassportProvider
  projectId="your-project-id" // Required
  initialConfig={{
    network: {
      name: 'Lumia Beam',
      symbol: 'LUMIA',
      chainId: 2030232745,
      rpcUrl: 'https://beam-rpc.lumia.org',
      explorerUrl: 'https://beam-explorer.lumia.org',
      testnet: true,
    },
  }}
>

Advanced Configuration

<LumiaPassportProvider
  projectId="your-project-id"
  initialConfig={{
    // UI customization
    preferedColorMode?: 'light', // 'light'  | 'dark'

    ui: {
      title: 'Welcome to MyApp',
      subtitle: 'Sign in to continue',

      dialogClassName: 'string', // beta

      authOrder: ['email', 'passkey', 'social'],

      branding: {
        tagline: 'Powered by MPC',
        link: { text: 'Learn More', url: \'https\:\/\/example.com/docs\' },
      },
    },

    // Authentication providers
    email: {
      enabled: true,
      placeholder: 'Enter your email',
      buttonText: 'Continue',
      verificationTitle: 'Check your email',
    },
    passkey: {
      enabled: true,
      showCreateButton: true,
      primaryButtonText: 'Sign in with Passkey',
    },
    social: {
      enabled: true,
      gridColumns: 2,
      providers: [
        { id: 'Discord', name: 'Discord', enabled: true, comingSoon: false },
        { id: 'telegram', name: 'Telegram', enabled: true, comingSoon: false },
      ],
    },
    wallet: {
      enabled: true,
      supportedChains: [994873017, 2030232745],
      requireSignature: true,
      walletConnectProjectId: 'your-walletconnect-id',
    },

    // Features
    features: {
      mpcSecurity: true,
      strictMode: false,
      requestDeduplication: true,
      kycNeeded: false,
      displayNameNeeded: false,
    },

    // KYC configuration (if needed)
    kyc: {
      provider: 'sumsub',
      options: { levelName: 'basic-kyc', flowName: 'default' }
    },

    // Warnings
    warnings: {
      backupWarning: true,
      emailNotConnectedWarning: true,
    },

    // Network configuration
    network: {
      name: 'Lumia Beam',
      symbol: 'LUMIA',
      chainId: 2030232745,
      rpcUrl: 'https\:\/\/beam-rpc.lumia.org',
      explorerUrl: 'https\:\/\/beam-explorer.lumia.org',
      testnet: true,
    },
  }}
  callbacks={{
    onLumiaPassportConnecting: ({ method, provider }) => {
      console.log('Connecting with:', method, provider);
    },
    onLumiaPassportConnect: ({ address, session }) => {
      console.log('Connected:', address);
    },
    onLumiaPassportAccount: ({ userId, address, session, hasKeyshare }) => {
      console.log('Account ready:', userId);
    },
    onLumiaPassportUpdate: ({ providers }) => {
      console.log('Profile updated:', providers);
    },
    onLumiaPassportDisconnect: ({ address, userId }) => {
      console.log('Disconnected:', address);
    },
    onLumiaPassportError: ({ error, message }) => {
      console.error('Error:', message);
    },
    onWalletReady: (status) => {
      console.log('Wallet ready:', status.ready);
    },
  }}
>

Using Hooks

Note: The useLumiaPassportSession hook is based on pure Zustand store so if you're already using useLumiaPassportSession hook please consider 2 options: 1) refactor state extarction so it uses zustand state extraction feature. 2) consider using dedicated LumiaPassport shared store values hooks: useLumiaPassportAccountSession, useLumiaPassportAddress etc. Otherwise you might experience excessive re-rendering issues as LumiaPassport shares its internal store and might update some state values which should not affect app render.

import { useLumiaPassportAccountSession, useLumiaPassportLoadingStatus } from '@lumiapassport/ui-kit'

function MyComponent() {
  // const session = useLumiaPassportSession(s => s.session) - with prev hook & Zustand state extraction feature, please prefer this instead:
  const session = useLumiaPassportAccountSession()
  const { isSessionLoading } = useLumiaPassportLoadingStatus()

  if (isSessionLoading) return <div>Loading...</div>

  if (!session) {
    return <div>Not authenticated. Please connect your wallet.</div>
  }

  return (
    <div>
      <p>Welcome!</p>
      <p>User ID: {session.userId}</p>
      <p>Wallet Address: {session.ownerAddress}</p>
      <p>Smart Account: {session.accountAddress}</p>
    </div>
  )
}

Lumia Passport shared store values hooks

  • useLumiaPassportIsMobileView - Returns boolean indicating if UI is in mobile view mode
  • useLumiaPassportAccountSession - Returns current user session object with userId, addresses, and auth info
  • useLumiaPassportLoadingStatus - Returns { isSessionLoading, sessionStatus } for tracking authentication state
  • useLumiaPassportBalance - Returns wallet balance data: { walletBalance, fiatBalance, cryptoRate, fiatSymbol, cryptoSymbol }
  • useLumiaPassportIFrameReady - Returns boolean indicating if the MPC iframe is ready for operations
  • useLumiaPassportAddress - Returns the current user's wallet address
  • useLumiaPassportError - Returns any error that occurred during authentication or operations
  • useLumiaPassportRecoveryUserId - Returns userId for account recovery flow
  • useLumiaPassportHasServerVault - Returns boolean indicating if user has server-side keyshare backup

useSendTransaction - Send Transactions

import { useSendTransaction } from '@lumiapassport/ui-kit'

function TransactionExample() {
  const { sendTransaction, isPending } = useSendTransaction()

  const handleSend = async () => {
    try {
      const userOpHash = await sendTransaction({
        to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
        value: '1000000000000000000', // 1 ETH in wei
        data: '0x' // Optional contract call data
      })
      console.log('UserOp hash:', userOpHash)
    } catch (error) {
      console.error('Transaction failed:', error)
    }
  }

  return (
    <div>
      <button onClick={handleSend} disabled={isPending}>
        {isPending ? 'Sending...' : 'Send Transaction'}
      </button>
    </div>
  )
}

sendUserOperation - Direct Transaction Submission

For direct control without using the React hook, you can use sendUserOperation function:

import { sendUserOperation, useLumiaPassportAccountSession } from '@lumiapassport/ui-kit'

function DirectTransactionExample() {
  const session = useLumiaPassportAccountSession()

  const handleSend = async () => {
    if (!session) {
      console.error('No active session')
      return
    }

    try {
      // Send transaction directly with full control
      const userOpHash = await sendUserOperation(
        session, // Required: session from useLumiaPassportAccountSession
        '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb', // to address
        '1000000000000000000', // value in wei (1 ETH)
        '0x', // data (optional contract call)
        'standard', // fee type: 'economy' | 'standard' | 'fast'
        'v0.7' // EntryPoint version
      )

      console.log('Transaction submitted:', userOpHash)
    } catch (error) {
      console.error('Transaction failed:', error)
    }
  }

  return <button onClick={handleSend}>Send Transaction</button>
}

When to use:

  • ✅ Use useSendTransaction() hook for React components (automatic session management)
  • ✅ Use sendUserOperation() function for custom logic, utility functions, or non-React code

deployAccount - Deploy Smart Account (Optional)

Deploy the smart account contract immediately after registration. This is optional - accounts are automatically deployed on first transaction.

Smart behavior: Automatically checks if account is already deployed and skips transaction to save gas.

import { deployAccount, useLumiaPassportAccountSession } from '@lumiapassport/ui-kit'

function DeployAccountExample() {
  const session = useLumiaPassportAccountSession()

  const handleDeploy = async () => {
    if (!session) return

    try {
      // Deploy account with minimal gas cost (skips if already deployed)
      const userOpHash = await deployAccount(session, 'economy')

      if (userOpHash) {
        console.log('Account deployed:', userOpHash)
      } else {
        console.log('Account already deployed, skipped')
      }
    } catch (error) {
      console.error('Deployment failed:', error)
    }
  }

  return <button onClick={handleDeploy}>Deploy Account</button>
}

Return values:

  • Returns userOpHash (string) - if deployment was needed and executed
  • Returns null - if account already deployed (saves gas)

Advanced usage:

// Force deployment even if already deployed (not recommended)
const hash = await deployAccount(session, 'economy', { force: true })

Why use this?

  • Pre-deploy account before first real transaction
  • No user consent required (safe minimal operation)
  • Cleaner UX - separate deployment from first payment
  • Smart gas savings - auto-skips if already deployed
  • ⚠️ Optional - accounts auto-deploy on first transaction anyway

How it works:

  1. Checks if smart account contract exists on-chain
  2. If exists: returns null immediately (no gas cost)
  3. If not exists: sends minimal UserOperation (to=0x0, value=0, data=0x)
  4. Factory deploys contract without user confirmation

signTypedData - Sign EIP712 Structured Messages

Sign structured data according to EIP-712 standard. This is commonly used for off-chain signatures in dApps (e.g., NFT marketplace orders, gasless transactions, permit signatures).

import { signTypedData, useLumiaPassportAccountSession } from '@lumiapassport/ui-kit'

function SignatureExample() {
  const session = useLumiaPassportAccountSession()

  const handleSign = async () => {
    if (!session) return

    try {
      // Define EIP712 typed data
      const signature = await signTypedData(session, {
        domain: {
          name: 'MyDApp', // Your dApp name (must match contract)
          version: '1', // Contract version
          chainId: 994, // Lumia Prism Testnet
          verifyingContract: '0x...' // Your contract address (REQUIRED in production!)
        },
        types: {
          Order: [
            { name: 'tokenIds', type: 'uint256[]' },
            { name: 'price', type: 'uint256' },
            { name: 'deadline', type: 'uint256' }
          ]
        },
        primaryType: 'Order',
        message: {
          tokenIds: [1n, 2n, 3n],
          price: 1000000000000000000n, // 1 token in wei
          deadline: BigInt(Math.floor(Date.now() / 1000) + 3600) // 1 hour from now
        }
      })

      console.log('Signature:', signature)

      // Verify signature (optional)
      const { recoverTypedDataAddress } = await import('viem')
      const recoveredAddress = await recoverTypedDataAddress({
        domain: {
          /* same domain */
        },
        types: {
          /* same types */
        },
        primaryType: 'Order',
        message: {
          /* same message */
        },
        signature
      })

      console.log('Signer:', recoveredAddress) // Should match session.ownerAddress
    } catch (error) {
      console.error('Signing failed:', error)
    }
  }

  return <button onClick={handleSign}>Sign Message</button>
}

Important Notes about ERC-4337 Smart Accounts:

In Account Abstraction (ERC-4337), there are two addresses:

  1. Owner Address (EOA) - The address that signs messages/transactions
  2. Smart Account Address - The contract wallet address

⚠️ Critical: The signature is created by the owner address (EOA), NOT the smart account address!

Compatibility with existing protocols:

  • Works: Protocols that verify signatures off-chain (e.g., your backend verifies the owner EOA signature)
  • ⚠️ May not work: Protocols designed for EOA wallets that store and verify against msg.sender or wallet address
    • Example: Uniswap Permit2, some NFT marketplaces
    • These protocols expect the signer address to match the wallet address
    • With smart accounts: signer = owner EOA, wallet = smart account contract
    • Solution: Use ERC-1271 signature validation in your smart contracts (allows contracts to validate signatures)

Domain Configuration:

  • In production, use your actual verifyingContract address (not zero address!)
  • The domain parameters must match exactly between frontend and smart contract
  • The chainId should match the network you're deploying to

Technical Details:

  • Shows a MetaMask-like confirmation modal with structured message preview
  • All BigInt values are supported in the message
  • Signature can be verified using viem.recoverTypedDataAddress() - will return owner EOA address

When to use signTypedData:

  • ✅ Custom backend signature verification (you control the verification logic)
  • ✅ Gasless transactions with meta-transaction relayers
  • ✅ DAO voting and governance (off-chain signatures)
  • ✅ Custom smart contracts with ERC-1271 support
  • ⚠️ Be cautious with protocols designed exclusively for EOA wallets

prepareUserOperation - Prepare for Backend Submission

import { prepareUserOperation, useLumiaPassportAccountSession } from '@lumiapassport/ui-kit'

function BackendSubmissionExample() {
  const session = useLumiaPassportAccountSession()

  const handlePrepare = async () => {
    if (!session) return

    // Prepare and sign UserOp without sending to bundler
    const { userOp, userOpHash } = await prepareUserOperation(
      session,
      '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb', // to
      '1000000000000000000', // 1 ETH in wei
      '0x', // data
      'standard', // fee type: 'economy' | 'standard' | 'fast'
      'v0.7' // EntryPoint version
    )

    // Send to backend for validation and submission
    await fetch('/api/submit-transaction', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        userOp,
        userOpHash,
        ownerAddress: session.ownerAddress // for signature verification
      })
    })
  }

  return <button onClick={handlePrepare}>Prepare & Send to Backend</button>
}

Backend example (using @lumiapassport/core):

import { getUserOperationReceipt, sendUserOperationRaw } from '@lumiapassport/core'
import { recoverAddress } from 'viem'

// Receive from frontend
const { userOp, userOpHash, ownerAddress } = await request.json()

// Verify signature
const recoveredAddress = await recoverAddress({
  hash: userOpHash,
  signature: userOp.signature
})

if (recoveredAddress.toLowerCase() !== ownerAddress.toLowerCase()) {
  throw new Error('Invalid signature')
}

// Submit to bundler - returns userOpHash
const submittedUserOpHash = await sendUserOperationRaw(userOp)

// Poll for receipt to get transaction hash and status
const waitForReceipt = async (userOpHash: string, maxAttempts = 60, delayMs = 1000) => {
  for (let i = 0; i < maxAttempts; i++) {
    const receipt = await getUserOperationReceipt(userOpHash as `0x${string}`)
    if (receipt) {
      return {
        success: receipt.success,
        transactionHash: receipt.receipt?.transactionHash,
        blockNumber: receipt.receipt?.blockNumber
      }
    }
    await new Promise((resolve) => setTimeout(resolve, delayMs))
  }
  throw new Error('Transaction timeout')
}

const result = await waitForReceipt(submittedUserOpHash)
return {
  success: result.success,
  transactionHash: result.transactionHash,
  blockNumber: result.blockNumber
}

useLumiaPassportOpen - Programmatic LumiaPassport Dialog Control

Control the Lumia Passport dialog programmatically.

import { PageKey, useLumiaPassportOpen } from '@lumiapassport/ui-kit'

function CustomAuthButton() {
  const { isOpen, open: openLumiaPassport, close } = useLumiaPassportOpen()

  return (
    <div>
      <button onClick={() => openLumiaPassport(PageKey.AUTH)}>Sign In</button>
      <button onClick={() => openLumiaPassport(PageKey.RECEIVE)}>Receive LUMIA</button>

      <button onClick={close}>Close Dialog</button>
    </div>
  )
}

useLumiaPassportColorMode - Theme Control

Control light/dark mode for Lumia Passport UI. Use hook to sync colorMode inside your App instead of any local colorMode states

import { useLumiaPassportColorMode } from '@lumiapassport/ui-kit'

function ThemeSelector() {
  const { colorMode, setColorMode } = useLumiaPassportColorMode()

  return (
    <div>
      <p>Current theme: {colorMode}</p>
      <button onClick={() => setColorMode('light')}>Light</button>
      <button onClick={() => setColorMode('dark')}>Dark</button>
    </div>
  )
}

ThemeToggle - Quick Theme Switcher

Pre-built theme toggle button component to use in combo with useLumiaPassportColorMode.

import { ThemeToggle } from '@lumiapassport/ui-kit'

function AppHeader() {
  return (
    <header>
      <h1>My App</h1>
      <ThemeToggle />
    </header>
  )
}

Authentication Methods

Email OTP

Users receive a one-time code via email.

// Configured by default, no additional setup needed

Passkey (WebAuthn)

Secure biometric authentication with device passkeys.

// Configured by default
// Users can register passkey after initial login

Telegram Mini App

Authentication via Telegram for mini apps.

// Configure via social providers:
initialConfig={{
  social: {
    enabled: true,
    providers: [
      { id: 'telegram', name: 'Telegram', enabled: true },
    ],
  },
}}

External Wallet

Connect existing wallets (MetaMask, WalletConnect, etc.).

// Configured by default
// Uses RainbowKit for wallet connections

Styling

...to be updated

TypeScript Support

Full TypeScript support with exported types:

import type { AuthProvider, LumiaPassportConfig, User, WalletInfo } from '@lumiapassport/ui-kit'

Examples

Check out the /examples directory for complete working examples:

  • React + Vite - Modern React setup with Vite
  • Next.js - Next.js App Router integration
  • React + TypeScript - Full TypeScript example

Security

  • 🔒 MPC Key Management - Keys are split between client and server using threshold cryptography
  • 🏝️ Iframe Isolation - Sensitive operations run in isolated iframe context
  • 🔐 No Private Key Exposure - Private keys never exist in complete form on client
  • Non-custodial - Users maintain full control of their accounts

Need Help?

License

MIT License - see LICENSE file for details.

Links