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

@arch-network/wallet-connect-kit

v0.1.1

Published

Wallet connect SDK for Arch Network apps: Bitcoin wallets (via LaserEyes), the Arch Wallet extension, and Turnkey Wallet Hub passkey/email wallets — headless core, React hooks, and a themeable connect modal.

Readme

@arch-network/wallet-connect-kit

npm

Wallet connect SDK for Arch Network dApps. Drop in a connect button and modal, get back a connected wallet identity and a signer — for every wallet type Arch users have:

  • Bitcoin extension wallets via LaserEyes — Xverse, Phantom, UniSat, Leather (incl. silent session restore, network-switch settling, and the per-wallet signing quirks).
  • Arch Wallet browser extension (window.arch) with its Arch-native digest signer.
  • Turnkey Wallet Hub passkey + email (OTP) wallets, signing client-side.

Ships in three layers — use as much as you need:

| Layer | What you get | |---|---| | Headless core | Connect flows, identity derivation (pubkeyXCoord, base58 arch address), network guard, storage, signers, a vanilla zustand store | | React | ArchWalletKitProvider, useWallet, useWalletSigner, useWalletHubAccount | | UI | WalletConnectModal (picker / email + OTP / connecting / connected / install views) and ConnectWalletButton, themeable via CSS variables |

A runnable consumer lives in examples/vite-react.

Requirements

  • React 18+ and a bundler (Next.js, Vite, …). dist/ uses extensionless relative imports and is not consumable by plain Node ESM.
  • On Vite: add vite-plugin-node-polyfills — transitive deps of the bip322 stack import node builtins (crypto, stream, events). Next.js ships fallbacks for these out of the box. See examples/vite-react/vite.config.ts.

Setup

1. Install the package and its peers:

pnpm add @arch-network/wallet-connect-kit @omnisat/lasereyes react react-dom
# or: npm install / yarn add — same packages

Installs from the public npm registry. Releases are also mirrored to GitHub Packages (see the sidebar), but npmjs is canonical — GitHub Packages requires a GitHub token even for public installs.

@omnisat/lasereyes must be a single instance shared with your app, which is why it's a peer. The range pins 0.0.162: the kit's Xverse/UniSat signing workarounds are coded against that release's adapter bugs — bump it deliberately, not automatically.

2. Copy the wallet icons from this repo's assets/ into your app's public/ directory (/arch-icon.jpeg, /xverse-logo.png, /phantom-icon.svg, /unisat-logo.png, /leather-icon.svg). (Or skip this and pass your own archWallet / otherWallets descriptors to the modal.)

3. Mount the provider, button, and modal — this is the whole integration:

"use client"; // Next.js app router only

import {
  ArchWalletKitProvider,
  ConnectWalletButton,
  WalletConnectModal,
  type WalletKitConfig,
} from "@arch-network/wallet-connect-kit";
import "@arch-network/wallet-connect-kit/styles.css";

const config: WalletKitConfig = {
  network: "testnet4", // "mainnet" | "testnet" | "testnet4" | "regtest"
  appName: "My Arch App",
};

export function App({ children }: { children: React.ReactNode }) {
  return (
    <ArchWalletKitProvider config={config}>
      <ConnectWalletButton />
      {children}
      <WalletConnectModal />
    </ArchWalletKitProvider>
  );
}

The provider owns session restore (silent reconnect on page load) and modal state; the button and modal wire themselves from context. There is nothing else to wire.

4. Read the connection anywhere under the provider:

import { useWallet } from "@arch-network/wallet-connect-kit";

function Balance() {
  const { wallet } = useWallet();
  if (!wallet) return <p>Not connected</p>;
  return <p>{wallet.archAddress}</p>;
}

useWallet() returns:

| Field | What it is | |---|---| | wallet | WalletState \| null — address, pubkey, pubkeyXCoord, archAddress, balances | | connectionPhase / connectionError | Connect-flow progress and failure copy | | openConnectModal() / handleDisconnect() | Open the picker / tear down the session | | handleConnect(walletId, options?) | Start a connect flow imperatively | | repairOnboarding() | Re-run your onboard hook without disconnecting — surface it when a transaction fails with an account-not-ready error | | laserEyes, detectedWallets, showConnect, … | Lower-level state for custom UIs |

5. Sign with the active wallet:

import { useWalletSigner } from "@arch-network/wallet-connect-kit";

const signer = useWalletSigner(); // ChallengeSigner | DigestSigner
const signature =
  typeof signer === "function"
    ? await signer(challenge)          // BIP-322 challenge signer
    : await signer.signDigest(digestHex); // digest signer (Arch ext, Turnkey)

All wallet types produce signatures your transaction runner can treat identically (BIP-322 witness / raw 64-byte Schnorr shapes). Outside React, use resolveActiveTransactionSigner(...).

Configuration reference

const config: WalletKitConfig = {
  // Required. A network name, or the full triple
  // { bitcoinNetwork, xverseNetworkType, networkId } when the derived
  // Xverse network.type / Hub x-network values don't fit your deployment.
  network: "testnet4",

  // Optional — names the app in passkey prompts and Hub wallet labels.
  appName: "My Arch App", // default "Arch"

  // Optional — prefix for every localStorage/sessionStorage key.
  storagePrefix: "myapp", // default "arch"

  // Optional — Turnkey Wallet Hub endpoint (see "Wallet Hub" below).
  hub: { baseUrl: "/api/wallet-hub", apiKey: undefined },

  // Optional — console diagnostics, filterable with "[WalletConnect]".
  debug: false,

  // Optional host hooks — see "Host hooks" below.
  onboard: async ({ identity, signer, onPhase }) => {},
  fetchBalances: async (identity) => ({ balances: {}, totalUsdValue: null }),
};

The config is captured once on mount. To switch networks, remount the provider (key={network}) or reload the page — per-network derived state is not valid across networks.

Host hooks

The kit owns connecting; your app owns what happens on-chain afterwards:

  • onboard({ identity, signer, onPhase }) — run post-connect provisioning (e.g. Arch create_account + associated token accounts). Report progress via onPhase("checking-account" | "creating-account" | …) and the modal renders stage copy for each step. Throwing aborts the connect and tears down partial state. Omit it and connect finishes after identity validation.
  • fetchBalances(identity) — return the initial { balances, totalUsdValue } snapshot committed with the wallet. Omit it and the wallet connects with empty balances; keep your own polling layer updating walletKitStore afterwards.

Wallet Hub (passkey / email wallets)

  • hub.baseUrl may be https://hub.arch.network or (recommended in browsers) a same-origin proxy that attaches x-api-key server-side and avoids CORS.
  • hub.apiKey is a public platform gate, not a user credential.
  • The Hub derives wallets on the network named by network.networkId (x-network header).

Same-origin proxy (Next.js)

Browser requests to the hosted Hub with x-api-key/x-network trigger a CORS preflight the Hub doesn't serve on every route. Keep the browser on your origin and attach the key server-side:

// app/api/wallet-hub/[...path]/route.ts
import { NextRequest } from "next/server";

const UPSTREAM = process.env.WALLET_HUB_BASE_URL ?? "https://hub.arch.network";

async function proxy(request: NextRequest, { params }: { params: Promise<{ path?: string[] }> }) {
  const { path } = await params;
  const headers = new Headers(request.headers);
  headers.delete("host");
  headers.delete("connection");
  headers.delete("content-length");
  if (process.env.WALLET_HUB_API_KEY) headers.set("x-api-key", process.env.WALLET_HUB_API_KEY);
  // Trust the client's x-network (the kit always sends it); default otherwise.
  if (!headers.has("x-network")) headers.set("x-network", "mainnet");

  const method = request.method.toUpperCase();
  const upstream = await fetch(
    `${UPSTREAM.replace(/\/+$/, "")}/${(path ?? []).join("/")}${request.nextUrl.search}`,
    {
      method,
      headers,
      body: ["GET", "HEAD"].includes(method) ? undefined : await request.text(),
      cache: "no-store",
    },
  );
  return new Response(upstream.body, { status: upstream.status, headers: upstream.headers });
}

export const GET = proxy;
export const POST = proxy;
export async function OPTIONS() {
  return new Response(null, { status: 204 });
}

Then set hub: { baseUrl: "/api/wallet-hub" } — the kit appends /v1 itself. On Vite, the dev server can do the same with a server.proxy entry (see the example app).

Customization

Theming

  • Import @arch-network/wallet-connect-kit/styles.css once.
  • Override --awck-* CSS variables (--awck-surface, --awck-fg, --awck-primary, --awck-accent, …). Dark mode follows prefers-color-scheme and can be forced with data-awck-theme="dark" on <html>.

Component props

Every modal/button prop overrides its context default, so partial customization is a prop away:

<WalletConnectModal
  network={{ label: "Testnet4", tone: "test", onSwitch: flipNetwork }}
  explorerAccountUrl={(arch) => `https://explorer.arch.network/accounts/${arch}`}
  archWallet={myArchDescriptor}
  otherWallets={myWalletList}
/>

Outside the provider, both components run fully controlled — pass the complete prop set (they throw a named error if something's missing).

Picker tags and analytics

The modal's recentWalletId / connectedWalletId props and connect/disconnect analytics are host concerns:

import { getStoredWalletProvider, useWallet, type WalletId } from "@arch-network/wallet-connect-kit";

const LASEREYES_IDS = new Set(["xverse", "phantom", "unisat", "leather"]);

function asWalletId(value: unknown): WalletId | null {
  return typeof value === "string" && LASEREYES_IDS.has(value)
    ? (value as WalletId)
    : null;
}

function AppShell() {
  const { wallet, laserEyes } = useWallet();

  // "Recent" tag in the picker: the last provider we stored.
  const recentWalletId = asWalletId(getStoredWalletProvider());
  // Source of truth for the active connection is the live LaserEyes provider;
  // fall back to the persisted id.
  const liveProvider = asWalletId(laserEyes.provider);
  const connectedWalletId = wallet ? (liveProvider ?? recentWalletId) : null;

  // Analytics: watch the wallet transition edges with an effect on `wallet`
  // and emit your own events.

  return (
    <WalletConnectModal
      recentWalletId={recentWalletId}
      connectedWalletId={connectedWalletId}
    />
  );
}

Headless usage

Everything the UI does is exported: runConnectFlow, runArchExtensionConnectFlow, runTurnkeyHubConnectFlow, the vanilla walletKitStore (+ selectors), identity/encoding helpers, and the signers. Build your own UI on useWallet() — or skip React entirely and drive the core. If you skip <ArchWalletKitProvider>, mount useSyncWalletSession yourself to get silent reconnect.

Storage

Everything the kit persists is prefixed with storagePrefix (default "arch"): "<prefix>:last-connected-wallet" (localStorage) plus the Turnkey account/session keys (<prefix>_turnkey_*). Wallet state itself is deliberately session-only — every page load starts disconnected and silent-reconnects.

Not included (by design)

  • On-chain onboarding and balance/indexer reads — inject via onboard / fetchBalances.
  • Analytics — observe walletKitStore (or useWallet()) and emit your own events.

Development

pnpm install
pnpm typecheck
pnpm build     # tsc → dist/ (preserves "use client" directives)
pnpm test      # vitest

CI runs typecheck + build + test on PRs and main pushes. Releases are automated from Conventional Commits — see RELEASING.md. Engineering invariants (BIP-322 port, LaserEyes pin, layering rules) live in AGENTS.md.

License

MIT