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

@web3-blocks/dapp-ui

v1.0.3

Published

The Universal and TypeScript-first Web3 abstraction layer that provides a unified provider, modular network hooks, and chain-specific utilities for building decentralized applications.

Readme

@web3-blocks/dapp-ui

The Universal and TypeScript-first Web3 abstraction layer that provides a unified provider, modular network hooks, and chain-specific utilities for building decentralized applications. It powers the logic of dApp/ui, the component library built on top of shadcn/ui — but can be used independently.

Installation

  • Install with your package manager of choice
npm add @web3-blocks/dapp-ui

Peer Dependencies

Install required peers that your app must provide. The SDK treats these as externals to prevent duplicate bundles and version conflicts:

  • react: ^18 || ^19
  • react-dom: ^18 || ^19
  • viem: ^2
  • wagmi: ^2
  • ethers: ^6
  • @tanstack/react-query: ^4 || ^5

Example:

npm add react react-dom viem wagmi ethers @tanstack/react-query @web3-blocks/dapp-ui

React 19 Compatibility (Suppressing Peer Warnings)

Some transitive dependencies of wagmi may emit a peer warning for React 19 (via use-sync-external-store). To silence the warning without affecting functionality, add an override/resolution in your app:

  • pnpm (package.json):
{
  "pnpm": {
    "overrides": {
      "use-sync-external-store": "^1.6.0"
    }
  }
}
  • npm (package.json):
{
  "overrides": {
    "use-sync-external-store": "^1.6.0"
  }
}
  • yarn (package.json):
{
  "resolutions": {
    "use-sync-external-store": "^1.6.0"
  }
}

This does not change runtime behavior; it only removes the install-time warning when using React 19.

Quick Start

Minimal React/Next.js setup for Ethereum (EVM) with built-in provider:

import React from "react";
import { DAppUiProvider, useEth, Chains } from "@web3-blocks/dapp-ui";

function App() {
  const { connect, disconnect, account, switchChain, chainId } = useEth();

  const { address, isConnected } = account;
  const { connectSafe, isPending } = connect;

  return (
    <div>
      <p>Connected: {isConnected ? address : "-"}</p>
      <p>Chain ID: {chainId ?? "?"}</p>

      <button
        disabled={isPending}
        onClick={() =>
          isConnected
            ? disconnect.disconnect()
            : connectSafe({ connector: connect.injected })
        }
      >
        {isPending ? "Connecting..." : isConnected ? "Disconnect" : "Connect"}
      </button>

      <div>
        <p>Available chains:</p>
        {switchChain.chains?.map((c) => (
          <button
            key={c.id}
            onClick={() => switchChain.switchChain({ chainId: c.id })}
            disabled={switchChain.isPending}
          >
            {c.name}
          </button>
        ))}
      </div>
    </div>
  );
}

export default function Root() {
  return (
    <DAppUiProvider
      network="ethereum"
      contract={{
        address: "0x0000000000000000000000000000000000000000",
        abi: [],
        chains: [Chains.optimismSepolia],
        defaultChain: Chains.optimismSepolia,
        // Optional: rpcUrl for single-chain setups. Multi-chain uses default transports.
      }}
    >
      <App />
    </DAppUiProvider>
  );
}

Transactions with Lifecycle Callbacks

const { contract } = useEth();

async function addTask(content: string) {
  await contract
    .writeFn("addTask", [content], {
      onSwitching: (msg) => console.log(msg),
      onSwitched: (msg) => console.log(msg),
      onSubmitted: (hash) => console.log("Tx submitted:", hash),
      onConfirmed: (receipt) =>
        console.log("Confirmed:", receipt.transactionHash),
    })
    .catch((err) => console.error("Transaction error:", err.message));
}

Subscribe to Contract Events

const { contract } = useEth();

useEffect(() => {
  const offAdded = contract.eventFn("TaskAdded", (user, id, content) => {
    console.log("TaskAdded", { user, id: Number(id), content });
  });

  const offToggled = contract.eventFn("TaskToggled", (user, id, completed) => {
    console.log("TaskToggled", {
      user,
      id: Number(id),
      completed: Boolean(completed),
    });
  });

  return () => {
    offAdded?.();
    offToggled?.();
  };
}, [contract]);

Exports

  • Provider: DAppUiProvider
  • Context: useDAppContext
  • Types: DAppUiProps, NETWORK_TYPES, DAppUiContextType
  • Ethereum (EVM):
    • useEth (combined convenience hook exposing account, connect, disconnect, contract, switchChain, chainId)
    • Individual hooks: useConnect, useDisconnect, useAccount
    • Network hooks: useSwitchChain (Wagmi)
    • Chains: Chains (from viem/chains)
    • Config: createEthereumConfig (internal provider usage; returns Wagmi config if you need it externally)

Hooks mirror wagmi behavior. useConnect includes a convenience flag isWalletAvailable in addition to wagmi’s return.

Contributing

This project is open-source and welcomes contributions. The system is type-first and relies on a small contract config per network. Below is the complete flow, detailed to help you succeed.

Adding Networks

  1. Create src/networks/<network>/config/contract.config.ts (use lowercase for <network>, e.g., ethereum, sui).
  2. Define and export type ContractConfig in that file.
  3. In src/networks/<network>/index.ts, type re-export the contract config:
// src/networks/<network>/index.ts
export type { ContractConfig } from "./config/contract.config";

Example (EVM):

// src/networks/ethereum/config/contract.config.ts
import type { Chain, Abi } from "viem";

export type ContractConfig = {
  address: string; // EVM address for your contract
  abi: Abi; // ABI describing callable functions and events
  chains?: Chain[]; // Optional: supported chains (e.g., mainnet, sepolia)
};
// src/networks/ethereum/index.ts
export type { ContractConfig } from "./config/contract.config";

Type Generation (What Happens and Why)

  • Run the generator:
npm run genotype
  • Don't ask me why I called is genotype 😑
  • The generator scans src/networks, validates folder names and ensures each network exports type ContractConfig.
  • The generator scans src/networks, validates folder names and ensures each network’s index.ts exports type ContractConfig (either a direct alias or a type re-export).
  • It then regenerates src/constants/index.ts, which contains:
    • NETWORK_TYPES: a union of all network keys (e.g., "ethereum" | "sui").
    • NETWORK_TYPES_ARRAY: a readonly array form, useful for iteration and validation.
    • REGISTRIES: dynamic import map so the app can lazy-load network modules (code-splitting).
    • NETWORK_CONTRACT_MAP: compile-time mapping from network → its ContractConfig type.
    • DAppUiProps and DAppUiContextType: canonical provider and context types tied to the selected network.

Why dynamic imports? It avoids bundling every network’s code and lets consumers load only what they need.

Validation & Hooks

  • A pre-commit hook runs the generator. If src/constants/index.ts changes and isn’t staged, the commit fails with instructions.
  • The generator enforces:
    • Lowercase-only folder names.
    • Presence of export type ContractConfig in each network’s index.ts (alias or type re-export).

Troubleshooting

  • Missing index.ts or missing ContractConfig export → add the file and export the type exactly as shown.
  • Invalid folder name (uppercase or symbols) → rename the folder to lowercase letters only.
  • TypeScript errors mentioning NETWORK_CONTRACT_MAP[NETWORK_TYPES] → ensure your ContractConfig matches the fields used by your provider and hooks.

Network Switching & Chain ID

  • Ensure your chains include all networks you want to support (e.g., Optimism Sepolia and Arbitrum). If only one chain is configured with rpcUrl, the provider uses it; multi-chain setups use default transports per chain.
  • chainId comes from Wagmi and updates when MetaMask switches networks. Connect the wallet to reflect connector chain changes.

Transactions Failing with “Failed to fetch” (-32603)

  • This usually indicates the wallet’s RPC endpoint is unreachable or blocked (ad-blockers or corporate proxies). Try disabling blockers on localhost and confirm MetaMask is connected to the intended chain.
  • If the contract call reverts without a reason or the contract isn’t deployed on the active chain, writeFn throws a clear message instead of a raw CALL_EXCEPTION.

Dependency Management (SDK Consumers)

  • The SDK externalizes peers (react, react-dom, wagmi, viem, ethers, @tanstack/react-query) to avoid duplicate installations and reduce bundle bloat.

  • Use consistent version ranges in your app. Recommended:

    • react, react-dom: ^18 || ^19
    • wagmi: ^2
    • viem: ^2
    • ethers: ^6
    • @tanstack/react-query: ^4 || ^5
  • If you are missing a required peer, install it explicitly. The library does not bundle peers by design.

  • If you encounter peer version warnings, set overrides/resolutions:

    • pnpm (package.json):
    {
      "pnpm": {
        "overrides": {
          "use-sync-external-store": "^1.6.0",
          "react": "^19",
          "react-dom": "^19"
        }
      }
    }
    • npm (package.json):
    {
      "overrides": {
        "use-sync-external-store": "^1.6.0",
        "react": "^19",
        "react-dom": "^19"
      }
    }
    • yarn (package.json):
    {
      "resolutions": {
        "use-sync-external-store": "^1.6.0",
        "react": "^19",
        "react-dom": "^19"
      }
    }

License

  • MIT © dApp/ui
  • React 19 peer warning: use-sync-external-store

If you see a peer warning like:

✕ unmet peer react@"^16.8.0 || ^17.0.0 || ^18.0.0": found 19.x

This comes from a transitive dependency chain (wagmi → WalletConnect stack → valtio[email protected]) that only declares React up to 18.

To silence the warning and stay compatible with React 19, override use-sync-external-store to a React-19-compatible release (≥ 1.6.0):

  • npm (package.json):
{
  "overrides": {
    "use-sync-external-store": "^1.6.0"
  }
}
  • pnpm (package.json):
{
  "pnpm": {
    "overrides": {
      "use-sync-external-store": "^1.6.0"
    }
  }
}
  • yarn (package.json):
{
  "resolutions": {
    "use-sync-external-store": "^1.6.0"
  }
}

Alternatively (Yarn Berry), extend the peer range without changing versions:

packageExtensions:
  use-sync-external-store@*:
    peerDependencies:
      react: ">=16.8.0 <21"