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

bnb-middleware-toolkit

v0.1.0

Published

Production-grade React hooks and middleware for building DApps on BNB Chain (BSC + opBNB)

Downloads

108

Readme

BNB Chain Middleware Toolkit

React hooks and utilities for building DApps on BNB Chain. Handles BSC's gas quirks, wallet connection, BEP-20/BEP-721 token interactions, opBNB L2 fees, and gasless transactions — so you don't have to build from scratch, just use the SDK and build your custom apps under BSC chain.

Supports BSC Mainnet (56), BSC Testnet (97), opBNB Mainnet (204), opBNB Testnet (5611).


Installation

npm install bnb-middleware-toolkit

Peer dependencies — install these alongside the SDK if you don't have them:

npm install wagmi viem @tanstack/react-query react

Quick Start

1. Get a WalletConnect project ID

Go to cloud.walletconnect.com, create a free project, and copy the Project ID.

2. Set up providers

All wagmi hooks run on the client. In Next.js App Router, create a providers wrapper with 'use client' and import it into your root layout:

// app/providers.tsx
'use client'

import { createBnbConfig, Web3Provider, BscMiddlewareProvider } from 'bnb-middleware-toolkit'
import type { ReactNode } from 'react'

const config = createBnbConfig({
  walletConnectProjectId: 'your-walletconnect-project-id',
  chains: ['bsc', 'bscTestnet'],  // also supports 'opBNB', 'opBNBTestnet'
})

export function Providers({ children }: { children: ReactNode }) {
  return (
    <Web3Provider config={config}>
      <BscMiddlewareProvider>
        {children}
      </BscMiddlewareProvider>
    </Web3Provider>
  )
}
// app/layout.tsx
import { Providers } from './providers'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  )
}

For Vite / Create React App, wrap your app root directly — no 'use client' needed.

3. Send BNB

'use client'

import { useBscTransaction, useEnsureBscChain } from 'bnb-middleware-toolkit'

export function SendBnb() {
  const { send, status, txHash, error } = useBscTransaction()
  const { isWrongChain, switchToBsc } = useEnsureBscChain()

  if (isWrongChain) {
    return <button onClick={switchToBsc}>Switch to BSC</button>
  }

  return (
    <div>
      <button
        disabled={status === 'pending' || status === 'mining'}
        onClick={() => send({ to: '0xRecipientAddress', valueEther: '0.01' })}
      >
        {status === 'idle' ? 'Send 0.01 BNB' : status}
      </button>

      {txHash && (
        <a href={`https://bscscan.com/tx/${txHash}`} target="_blank" rel="noreferrer">
          View on BscScan
        </a>
      )}

      {error && <p style={{ color: 'red' }}>{error.userMessage} — {error.suggestion}</p>}
    </div>
  )
}

4. BEP-20 token transfer

'use client'

import { useTokenTransfer, useTokenBalance } from 'bnb-middleware-toolkit'

const TOKEN = '0xTokenContractAddress'

export function TokenSend() {
  const { formatted, isLoading } = useTokenBalance(TOKEN)
  const { transfer, status, transferHash, error } = useTokenTransfer(TOKEN)

  // useTokenTransfer handles approve → transfer as one flow.
  // If the wallet hasn't approved the token yet, it requests approval first,
  // waits for confirmation, then submits the transfer — all tracked via `status`.

  return (
    <div>
      <p>Balance: {isLoading ? '...' : formatted}</p>

      <button
        disabled={status !== 'idle'}
        onClick={() => transfer({ to: '0xRecipientAddress', amount: '10' })}
      >
        Send 10 tokens
      </button>

      <p>Status: {status}</p>
      {transferHash && <p>Tx: {transferHash}</p>}
      {error && <p style={{ color: 'red' }}>{error.userMessage}</p>}
    </div>
  )
}

SDK vs Demo App

This repository contains both the SDK and a Next.js demo app that uses it.

| | Location | Shipped to consumers | |---|---|---| | SDK | src/lib/, src/hooks/, src/providers/, src/index.ts | Yes — compiled to dist/ | | Demo app | src/app/, src/components/ | No — runs locally only |

Everything exported from src/index.ts is the public API. The demo app (src/app/, src/components/) is not bundled or exported — it exists to prove the SDK works end-to-end against real wallets and testnets.


Hook API Reference

createBnbConfig(options)

Creates a wagmi config pre-wired for BNB Chain. Pass it to <Web3Provider>.

import { createBnbConfig } from 'bnb-middleware-toolkit'

const config = createBnbConfig({
  walletConnectProjectId: 'your-id',
  chains: ['bsc', 'bscTestnet', 'opBNB', 'opBNBTestnet'],  // pick any subset
  rpcUrls: {
    bsc: 'https://your-private-rpc.nodereal.io/...',  // optional — overrides public endpoints
  },
})

For production use, provide a private RPC via rpcUrls (NodeReal, Ankr, QuickNode). The defaults are public endpoints and rate-limited.


useEnsureBscChain()

Detects whether the connected wallet is on a BNB chain and exposes switch utilities.

const {
  isWrongChain,        // boolean — true if not on any BNB chain
  isSwitching,         // boolean — switch in progress
  switchToBsc,         // () => void — switch to BSC mainnet (56)
  switchToTestnet,     // () => void — BSC testnet (97)
  switchToOpBnb,       // () => void — opBNB mainnet (204)
  switchToOpBnbTestnet // () => void — opBNB testnet (5611)
} = useEnsureBscChain()

If the target chain is not in the user's wallet, wagmi automatically sends wallet_addEthereumChain before switching — no manual "Add Network" required.


useBscTransaction()

Sends BNB with BSC-correct gas handling. Always sets an explicit gasPrice (+10% buffer) and gasLimit (+20% buffer) — never relies on wallet defaults, which can misapply EIP-1559 on BSC.

const { send, status, txHash, error, reset } = useBscTransaction()

await send({ to: '0x...', valueEther: '0.01' })

| Field | Type | Description | |---|---|---| | send | (params) => Promise<void> | Submit the transaction | | status | TxStatus | idle \| pending \| mining \| confirmed \| failed | | txHash | `0x${string}` \| undefined | Hash once submitted | | error | ParsedBscError \| null | Structured error — see parseBscError | | reset | () => void | Reset to idle |


useBscGasEstimate({ to, value, enabled? })

Live gas cost preview before the user submits. Re-fetches every 4 seconds.

const { estimate, isLoading } = useBscGasEstimate({
  to: '0x...',
  value: parseEther('0.01'),
})

console.log(estimate?.estimatedCostBnb)  // e.g. "0.000063"
console.log(estimate?.gasLimit)          // bigint
console.log(estimate?.gasPrice)          // bigint

useTokenBalance(tokenAddress)

BEP-20 balance for the connected wallet.

const { rawBalance, formatted, decimals, isLoading, refetch } = useTokenBalance('0x...')
// formatted → "1234.56"
// rawBalance → 1234560000000000000000n

useTokenMetadata(tokenAddress)

Fetches name, symbol, decimals, totalSupply in one multicall.

const { metadata, isLoading } = useTokenMetadata('0x...')
// metadata.name, metadata.symbol, metadata.decimals
// metadata.formattedTotalSupply → "1,000,000,000"

useTokenApproval({ tokenAddress, spenderAddress, amount })

Checks the current allowance and exposes an approval flow. Use this when a contract (e.g. a DEX router) needs to move tokens on the user's behalf.

const { needsApproval, requestApproval, isApproving, isApproved } = useTokenApproval({
  tokenAddress: '0x...',
  spenderAddress: '0x...',
  amount: parseUnits('100', 18),
})

if (needsApproval) requestApproval()  // approves MAX_UINT256, tracks confirmation

useTokenTransfer(tokenAddress, spenderAddress?)

Full approve-then-transfer flow in one hook. Pass spenderAddress when a contract executes the transfer (DEX, vault). Omit for wallet-to-wallet — skips the approval step entirely.

const { transfer, status, transferHash, approvalHash, error, reset } = useTokenTransfer(
  '0x...',  // token contract
  '0x...',  // spender — omit for direct wallet transfers
)

transfer({ to: '0x...', amount: '100' })

| Status | Meaning | |---|---| | idle | Ready | | approving | Approval tx pending in wallet | | approval-mining | Waiting for approval confirmation | | transferring | Transfer tx pending in wallet | | transfer-mining | Waiting for transfer confirmation | | confirmed | Done | | failed | Error at any step — check error |


useNFTBalance(contractAddress)

BEP-721 collection balance and metadata for the connected wallet.

const { info, isLoading } = useNFTBalance('0x...')
// info.name, info.symbol
// info.balance → 3n  (BigInt — number of NFTs owned)

useNFTMetadata(contractAddress, tokenId)

Owner and token URI for a specific token. Automatically parses data:application/json on-chain URIs (fully on-chain NFTs).

const { metadata } = useNFTMetadata('0x...', 42n)
// metadata.owner
// metadata.tokenURI
// If on-chain JSON → metadata.name, metadata.description, metadata.image

useNFTTransfer(contractAddress, operatorAddress?)

setApprovalForAllsafeTransferFrom as a single flow. Pass operatorAddress for marketplace/contract transfers. Omit for direct wallet transfers.

const { transfer, status, transferHash, isApprovedForAll, error, reset } = useNFTTransfer(
  '0x...',  // NFT contract
  '0x...',  // operator — omit for wallet-to-wallet
)

transfer({ to: '0x...', tokenId: 42n })

Status values: approving | approval-mining | transferring | transfer-mining | confirmed | failed


useOpBnbFee({ data?, gasLimit?, enabled? })

Full L2 + L1 data fee breakdown for opBNB transactions. Returns breakdown: null on non-opBNB chains — safe to call unconditionally.

const { breakdown, isOpBnb } = useOpBnbFee({
  data: '0x',       // encoded calldata ('0x' for plain BNB transfers)
  gasLimit: 21000n,
})

if (isOpBnb && breakdown) {
  console.log(breakdown.l2FeeBnb)    // execution cost
  console.log(breakdown.l1FeeBnb)    // data posting cost to BSC L1
  console.log(breakdown.totalFeeBnb) // what the user actually pays
}

Wallets (including MetaMask) typically show only the L2 portion. This hook reads the L1 Gas Price Oracle (0x420000...000F) deployed on opBNB and exposes both components.


useGaslessTransaction()

Sends via the BSC Paymaster (Megafuel) when a Paymaster URL is configured — the user pays zero gas. Automatically falls back to a normal gas transaction if the Paymaster rejects the tx or is unreachable. Safe to use unconditionally.

const { send, status, mode, txHash, error, reset } = useGaslessTransaction()

// BNB transfer
await send({ to: '0x...', valueEther: '0.01' })

// Contract call
await send({
  to: '0x...',
  abi: MY_ABI,
  functionName: 'stake',
  args: [parseEther('10')],
  gasLimit: 150000n,
})

// mode === 'sponsored'       → Paymaster covered gas
// mode === 'fallback-normal' → user paid, Paymaster skipped

| Status | Meaning | |---|---| | checking | Calling pm_isSponsorable | | pending | Waiting for wallet signature | | mining | Submitted, waiting for confirmation | | confirmed / failed | Final states |

To enable, set NEXT_PUBLIC_PAYMASTER_RPC_URL in your environment before building:

# .env.local (Next.js)
NEXT_PUBLIC_PAYMASTER_RPC_URL=https://bsc-mainnet.nodereal.io/v1/YOUR_KEY

useGaslessTransaction calls getPaymasterUrl() at runtime, which reads this variable. If the variable is unset, the hook skips the sponsored path and always sends a normal gas transaction — no errors, no config needed to disable it.


parseBscError(error)

Classifies any chain/wallet error into a structured object with a user-friendly message and a fix suggestion. Used internally by all hooks — also available directly.

import { parseBscError } from 'bnb-middleware-toolkit'

try {
  await sendTransaction(...)
} catch (err) {
  const { code, userMessage, suggestion } = parseBscError(err)
  // Show userMessage in your UI, log code for debugging
}

| Code | Trigger | |---|---| | INSUFFICIENT_FUNDS | Not enough BNB for gas + value | | GAS_ESTIMATION_FAILED | Contract would revert (bad args, missing approval, paused) | | TX_UNDERPRICED | gasPrice below node minimum | | NONCE_ERROR | Nonce conflict — pending tx in the mempool | | EXECUTION_REVERTED | Contract revert — reason string extracted automatically | | USER_REJECTED | User cancelled in wallet | | UNKNOWN_ERROR | Unclassified — raw message included |


BscMiddleware class

A viem-based, framework-agnostic client. Use this in Node.js scripts, server-side code, or anywhere outside React.

import { BscMiddleware } from 'bnb-middleware-toolkit'

const middleware = new BscMiddleware({ testnet: true })

// Gas estimate with safety buffers applied
const { gasLimit, gasPrice, estimatedCostBnb } = await middleware.estimateGas({
  from: '0x...',
  to: '0x...',
  value: parseEther('0.01'),
})

// Token info — parallel multicall
const { name, symbol, decimals, formattedTotalSupply } = await middleware.getTokenInfo('0x...')

// Balances
const { formatted: bnbBalance } = await middleware.getBnbBalance('0x...')
const { formatted: tokenBalance } = await middleware.getTokenBalance('0xTOKEN', '0xOWNER')

Chain utilities

import { CHAIN_META, explorerTxUrl, explorerAddressUrl } from 'bnb-middleware-toolkit'

explorerTxUrl(56, '0xabc...')        // "https://bscscan.com/tx/0xabc..."
explorerTxUrl(5611, '0xabc...')      // "https://testnet.opbnbscan.com/tx/0xabc..."
explorerAddressUrl(97, '0xdef...')   // "https://testnet.bscscan.com/address/0xdef..."

CHAIN_META[56].label     // "BSC Mainnet"
CHAIN_META[204].isL2     // true

| Chain | ID | Explorer | |---|---|---| | BSC Mainnet | 56 | bscscan.com | | BSC Testnet | 97 | testnet.bscscan.com | | opBNB Mainnet | 204 | opbnbscan.com | | opBNB Testnet | 5611 | testnet.opbnbscan.com |


BSC Design Decisions

No EIP-1559 — explicit gasPrice required

BSC uses legacy Type-0 transactions. There is no base fee or priority fee — only a flat gasPrice. MetaMask sometimes applies EIP-1559 logic to BSC, sending maxFeePerGas instead, which nodes reject as "transaction underpriced."

Every transaction in this SDK calls eth_gasPrice and sets it explicitly — wallet auto-detection is never trusted.

Gas buffers

  • +10% on gas price — protects against short-lived gas spikes on BSC
  • +20% on gas limit — contract gas estimation can be tight; over-estimating is always safer

QueryClient defaults

refetchInterval: 4000ms, staleTime: 3000ms — tuned to BSC's ~3-second block time so balances and allowances stay near-real-time.

Approve → Execute

ERC-20/BEP-20 tokens require approve(spender, amount) before a contract can transfer tokens on the user's behalf. useTokenApproval reads the current allowance first and skips approval if it's already sufficient. useTokenTransfer bundles both steps into one state machine so callers never need to orchestrate them manually.


Running the Demo App

The demo at src/app/ shows every hook running against a real wallet and real testnet.

git clone https://github.com/your-username/bnb-middleware-toolkit
cd bnb-middleware-toolkit
npm install
cp .env.local.example .env.local   # add NEXT_PUBLIC_WC_PROJECT_ID
npm run dev

Open http://localhost:3000.

Get testnet BNB: bnbchain.org/en/testnet-faucet
Find testnet tokens: testnet.bscscan.com


Resources

| Resource | URL | |---|---| | BNB Chain Docs | https://docs.bnbchain.org/ | | BSC RPC Endpoints | https://docs.bnbchain.org/bnb-smart-chain/developers/rpc/ | | BSC Paymaster | https://docs.bnbchain.org/bnb-smart-chain/developers/paymaster/overview/ | | BEP-20 Standard | https://github.com/bnb-chain/BEPs/blob/master/BEPs/BEP20.md | | wagmi Docs | https://wagmi.sh | | viem Docs | https://viem.sh |