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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@qubic.org/react

v0.2.7

Published

React hooks and context providers for the Qubic SDK, powered by [TanStack Query](https://tanstack.com/query).

Readme

@qubic.org/react

React hooks and context providers for the Qubic SDK, powered by TanStack Query.

Installation

bun add @qubic.org/react

Peer dependency: React 18+.

Security model

Seeds never appear in React state or component code:

  • VaultProvider stores wallets in a Ref — invisible to React DevTools, not serialisable, not part of the render cycle.
  • Components only receive identities: Identity[] (60-char strings).
  • useSendTransfer / useSendContractCall access signing internally — the component passes params but never touches a wallet.
  • Auto-lock on page hide (visibilitychange) and after a configurable idle timeout.

Session persistence (persistSession prop):

  • Seeds re-encrypted with a freshly generated AES-256-GCM key on every unlock.
  • The key is stored in IndexedDB as extractable: false — JS cannot read key bytes even under XSS.
  • The encrypted payload lives in sessionStorage (tab-scoped — cleared automatically when the tab closes).
  • Keys rotate on every restore: the previous IDB key is deleted before wallets are loaded (forward secrecy).
  • If decryption fails for any reason the session is silently cleared and the lock form is shown.

Setup

import { createLiveClient, createQueryClient } from '@qubic.org/rpc'
import { importVault } from '@qubic.org/wallet'
import { QubicProvider, VaultProvider } from '@qubic.org/react'

const live = createLiveClient()
const archive = createQueryClient()
const vault = importVault(localStorage.getItem('qubic-vault')!)

export function App() {
  return (
    <QubicProvider liveClient={live} archiveClient={archive}>
      <VaultProvider
        vaultData={vault}
        persistSession        // survives page refresh; expires on tab close
        autoLockMs={15 * 60_000}  // also auto-locks after 15 min idle
      >
        <YourApp />
      </VaultProvider>
    </QubicProvider>
  )
}

| Prop | Default | Description | |------|---------|-------------| | vaultData | required | VaultData from importVault / createVault | | persistSession | false | Re-encrypt seeds into sessionStorage + IDB on unlock; restore on page refresh | | sessionStorageKey | 'qubic-session' | Key used in sessionStorage — override for multiple vaults | | autoLockMs | none | Auto-lock after N ms idle; also locks on visibilitychange |

QubicProvider also acts as a TanStack QueryClientProvider. Pass your own queryClient to share the cache with the rest of your app.

Vault hooks

useVaultState()

Lock/unlock the vault and read the active identities.

import { useVaultState } from '@qubic.org/react'

function WalletPanel() {
  const { isUnlocked, identities, isUnlocking, error, unlock, lock } = useVaultState()

  if (!isUnlocked) {
    return (
      <form onSubmit={e => { e.preventDefault(); unlock(e.currentTarget.password.value) }}>
        <input name="password" type="password" />
        <button disabled={isUnlocking}>Unlock</button>
        {error && <p>Wrong password</p>}
      </form>
    )
  }

  return (
    <div>
      {identities.map(id => <p key={id}>{id}</p>)}
      <button onClick={lock}>Lock</button>
    </div>
  )
}

Send hooks

useSendTransfer()

Fetches the current tick, builds and signs a QU transfer, broadcasts. Zero wallet exposure.

import { useSendTransfer, useVaultState } from '@qubic.org/react'

function SendPanel() {
  const { identities } = useVaultState()
  const { mutate: send, isPending, data, error } = useSendTransfer()

  return (
    <button
      disabled={isPending || !identities[0]}
      onClick={() => send({
        from: identities[0]!,
        destination: '...' as Identity,
        amount: 1_000_000n,
      })}
    >
      Send
    </button>
  )
}

useSendContractCall()

Signs and broadcasts a smart contract procedure call.

import { useSendContractCall, useVaultState } from '@qubic.org/react'
import { QEARN_LOCK_INPUT_TYPE, buildQearnLockInput } from '@qubic.org/contracts'

function LockPanel() {
  const { identities } = useVaultState()
  const { mutate: send, isPending } = useSendContractCall()

  return (
    <button
      disabled={isPending}
      onClick={() => send({
        from: identities[0]!,
        inputType: QEARN_LOCK_INPUT_TYPE,
        payload: buildQearnLockInput({ amount: 10_000_000n }),
        amount: 10_000_000n,
      })}
    >
      Lock QU
    </button>
  )
}

Contract query hook

useContractQuery(fn, input)

Wraps any typed contract read function from @qubic.org/contracts as a TanStack Query. identityToPublicKey / publicKeyToIdentity are provided automatically.

import { useContractQuery } from '@qubic.org/react'
import { qearnGetStateOfRound } from '@qubic.org/contracts'

function RoundStats({ epoch }: { epoch: number }) {
  const { data, isLoading } = useContractQuery(qearnGetStateOfRound, { epoch })

  if (isLoading) return <span>Loading…</span>
  return <span>Locked: {data?.totalLockedAmount?.toString()}</span>
}

Live API hooks

useTickInfo(refetchIntervalMs?)

const { data } = useTickInfo(3_000)
// data.tick, data.epoch, data.duration

useBalance(identity)

const { data } = useBalance(identities[0])
// data.balance

useIssuedAssets(identity) / useOwnedAssets(identity) / usePossessedAssets(identity)

const { data: issued } = useIssuedAssets(identity)
const { data: owned } = useOwnedAssets(identity)
const { data: possessed } = usePossessedAssets(identity)

Archive API hooks (require archiveClient)

useTransaction(hash)

const { data } = useTransaction(txHash)

useEventLogs(request)

const { data } = useEventLogs({
  filters: { eventType: '0' },
  pagination: { offset: 0, size: 20 },
})

useTransactionsForIdentity(request)

const { data } = useTransactionsForIdentity({
  addressId: identity,
  pagination: { offset: 0, size: 20 },
})

useQubic()

Direct access to liveClient and archiveClient for one-off queries.

const { liveClient, archiveClient } = useQubic()

Sharing your TanStack Query cache

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

const queryClient = new QueryClient()

<QubicProvider liveClient={live} queryClient={queryClient}>
  <App />
</QubicProvider>

Wallet connectors (extension / WalletConnect / MetaMask Snap)

Setup — WalletProvider

Register all wallet backends once and wrap your app. Components call useWallet() with no arguments — no connector plumbing in every component.

bun add @walletconnect/sign-client   # only if using WalletConnect
import SignClient from '@walletconnect/sign-client'
import {
  QubicProvider,
  WalletProvider,
  extensionConnector,
  createWalletConnectConnector,
  createMetaMaskSnapConnector,
} from '@qubic.org/react'
import type { Base64 } from '@qubic.org/types'

// Create connectors outside the component tree (stable references)
const connectors = [
  extensionConnector,

  createWalletConnectConnector({
    // SignClient is initialized lazily on first connect() — no upfront await
    createClient: () => SignClient.init({
      projectId: 'YOUR_WC_PROJECT_ID',
      metadata: { name: 'My App', description: '…', url: 'https://myapp.com', icons: [] },
    }),
  }),

  createMetaMaskSnapConnector({
    broadcast: async (signedBase64) => {
      const result = await liveClient.broadcastTransaction(signedBase64 as Base64)
      if (!result.isOk()) throw result.error
      return { networkTxId: result.value.transactionId, broadcast: result.value }
    },
  }),
]

export function App() {
  return (
    <QubicProvider liveClient={live} archiveClient={archive}>
      <WalletProvider connectors={connectors}>
        <YourApp />
      </WalletProvider>
    </QubicProvider>
  )
}

| Prop | Default | Description | |------|---------|-------------| | connectors | required | List of WalletConnector instances | | storageKey | 'qubic-active-connector' | localStorage key for persisting the active connector |

useWallet()

Single hook for all wallet state. Works with any connector registered in WalletProvider.

import { useWallet } from '@qubic.org/react'

function ConnectModal() {
  const [qrUri, setQrUri] = useState<string>()
  const { connectors, isConnected, isConnecting, account, activeConnector, connect, disconnect, error } = useWallet()

  if (isConnected) {
    return (
      <div>
        <p>{account?.name ?? account?.identity}</p>
        <p>via {activeConnector?.id}</p>
        <button onClick={disconnect}>Disconnect</button>
      </div>
    )
  }

  return (
    <div>
      {connectors.filter(c => c.isAvailable()).map(c => (
        <button
          key={c.id}
          disabled={isConnecting}
          onClick={() => connect(c.id, c.id === 'walletconnect' ? { onUri: setQrUri } : undefined)}
        >
          {c.id}
        </button>
      ))}
      {qrUri && <QRCode value={qrUri} />}
      {error && <p>{error.message}</p>}
    </div>
  )
}

Signing from any component

function SendPanel() {
  const { isConnected, sendTransaction, signMessage } = useWallet()

  const handleSend = async () => {
    const result = await sendTransaction({ toIdentity: '…', amount: '1000', inputType: 0 })
    console.log(result.networkTxId)
  }

  const handleSign = async () => {
    const result = await signMessage({ message: 'hello qubic' })
    console.log(result.signatureHex)
  }
}

Custom connector

Implement WalletConnector to add any other wallet backend:

import type { WalletConnector } from '@qubic.org/react'

export const myConnector: WalletConnector = {
  id: 'my-wallet',
  isAvailable: () => true,
  connect: async () => ({ identity: '…' }),
  getAccount: async () => null,
  disconnect: async () => {},
  sendTransaction: async (_tx) => { throw new Error('not implemented') },
  signTransaction: async (_tx) => { throw new Error('not implemented') },
  signMessage: async (_msg) => { throw new Error('not implemented') },
  on: (_event, _cb) => () => {},
}

Lower-level: useWalletConnector(connector) (no provider)

When you only need a single connector and don't want a provider:

import { extensionConnector, useWalletConnector } from '@qubic.org/react'

function ConnectButton() {
  const { isAvailable, isConnected, account, connect, disconnect } =
    useWalletConnector(extensionConnector)

  if (!isAvailable) return <p>Install the Qubic extension</p>
  if (!isConnected) return <button onClick={() => connect()}>Connect</button>
  return <button onClick={disconnect}>{account?.identity}</button>
}

See also