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

keyring-chatbot-coinpool-test

v1.0.51

Published

React chat widget for keyring-agent-core — floating chatbot UI with AI answers, wallet/token/NFT lookups

Readme

keyring-chatbot-coinpool-test

React chat widget — drops a floating chat button + modal into any React dApp. The widget answers questions and handles wallet / token / NFT lookups in the browser. When it proposes an on-chain action (send / approve / wrap / unwrap / send NFT / swap), it renders a confirmation form and hands the host an unsigned transaction to sign and broadcast.

Install

yarn add keyring-chatbot-coinpool-test
# peer deps
yarn add react react-dom

Peer deps: react / react-dom (17, 18 or 19).

Quick start

import { ChatWidget } from 'keyring-chatbot-coinpool-test';
import type { Transaction, TransactionResult } from 'keyring-chatbot-coinpool-test';

export function App() {
  // Sign + broadcast with your own wallet/provider, then report the result.
  const handleTransaction = async (
    tx: Transaction
  ): Promise<TransactionResult> => {
    try {
      const hash = await wallet.sendTransaction({
        from: tx.from,
        to: tx.to,
        data: tx.data,
        value: tx.value, // wei, decimal string
        chainId: tx.chainId,
      });
      return { status: 'success', transactionHash: hash };
    } catch (err) {
      return { status: 'fail', error: (err as Error).message };
    }
  };

  return (
    <ChatWidget
      account={{ address: '0x…', chainId: 8453 }}
      language="en"
      position="bottom-right"
      theme={{ buttonSize: 56 }}
      onTransaction={handleTransaction}
    />
  );
}

The widget creates one chat session per mount, persists history in localStorage, and forwards the connected wallet address + chain so wallet / token / NFT lookups have the context they need.

The session is built once on mount. Changing account's address/chain after mount does not rebuild it — remount the widget with a new React key (e.g. key={`${address}-${chainId}`}) when you want a fresh session on account switch.

When the widget renders

<ChatWidget> returns null (renders nothing) unless both:

  1. account.address is a valid address and account.chainId is set, and
  2. the current window.location.origin is allowed.

Origins are gated by WHITELIST_ORIGIN in src/shared/app.ts. localhost always passes for local dev; an empty whitelist disables the gate.

onTransaction

Invoked when the user confirms an action form (send / approve / wrap / unwrap / send_nft) or a buy/swap confirmation. The widget passes an unsigned transaction; the host signs, broadcasts, and returns the result. Required for any on-chain action — without it the widget can only answer questions.

type Transaction = {
  from: string;
  to: string;
  data: string;
  value: string; // wei, decimal string
  chainId?: number | string;
  gasLimit?: string;
  gasPrice?: string;
  maxFeePerGas?: string;
  maxPriorityFeePerGas?: string;
  nonce?: number;
};

type TransactionResult = {
  status: 'success' | 'fail';
  transactionHash?: string;
  error?: string;
};

Before signing, the widget pre-flights the tx against the wallet's native balance (gas + value). If it can't cover it, the widget posts an "insufficient gas" message in the user's language and never calls onTransaction. A flaky RPC fails open — the wallet stays the final gatekeeper.

Props

| Prop | Type | Default | Notes | | ----------------------- | ----------------------------------------------- | ---------------- | --------------------------------------------------------------------- | | account | { address: string; chainId: number \| string }| — | Connected wallet. Required for the widget to render. | | onTransaction | (tx) => Promise<TransactionResult> | — | Sign + broadcast handler. Required for on-chain actions. | | language | 'en' \| 'ja' \| 'cn' | 'en' | UI language. Per-message language from the agent is honored too. | | rpcUrls | Record<number, string> | built-in | RPC per numeric chainId. Merged over the built-in defaults. | | position | 'bottom-right' \| 'bottom-left' | 'bottom-right' | Anchor of the floating button + modal. | | theme | ChatWidgetTheme | {} | primaryColor, buttonSize, zIndex, modalChatStyle, offset. | | defaultOpen | boolean | false | Open the modal on mount. | | onOpen / onClose | () => void | — | Fired when the modal opens / closes. | | chatTitle | string | built-in | Header title. | | welcomeMessage | string | localized | First bot bubble. | | customSuggestions | SuggestionButtonConfig[] | built-in chips | Replace the default suggestion chips. | | additionalSuggestions | SuggestionButtonConfig[] | — | Append extra chips after the base set. | | buttonIcon | string | — | Custom floating-button icon. | | chatIcon | string | built-in | Header / avatar icon. | | customChatButton | ReactNode | — | Replace the floating button entirely. | | styleButtonChat | CSSProperties | — | Extra styles on the floating container. | | modalConfig | { isShowIcon?: boolean } | {} | Modal-level toggles. |

SuggestionButtonConfig is { icon?: string; text: string }.

Agent behavior (model, subagents, storage key, data sources, …) is not a prop — it's fixed by the package descriptor in src/shared/app.ts. The only agent-related prop is rpcUrls.

Prop details & examples

account (required to render)

The connected wallet. Pass undefined while disconnected — the widget then renders nothing. chainId may be a number (8453) or hex string ("0x2105").

<ChatWidget account={{ address: '0xAbc…123', chainId: 8453 }} />

// Disconnected → widget renders null:
<ChatWidget account={connected ? { address, chainId } : undefined} />

Remount on identity change to start a fresh session:

<ChatWidget key={`${address}-${chainId}`} account={{ address, chainId }} />

onTransaction (required for on-chain actions)

Called when the user confirms a wallet action. Sign + broadcast, then return the result. See onTransaction for the full Transaction / TransactionResult shapes.

<ChatWidget
  account={{ address, chainId }}
  onTransaction={async (tx) => {
    try {
      const hash = await wallet.sendTransaction(tx);
      return { status: 'success', transactionHash: hash };
    } catch (e) {
      return { status: 'fail', error: (e as Error).message };
    }
  }}
/>

language

UI language for the widget's own strings: 'en' | 'ja' | 'cn'. The agent also tags each reply with a language, which is honored per-message.

<ChatWidget account={acct} language="ja" />

rpcUrls

Override RPC endpoints, keyed by numeric chainId. Merged over the built-in defaults, so you only list the chains you want to change.

<ChatWidget
  account={acct}
  rpcUrls={{
    1: 'https://my-eth-rpc.example',
    8453: 'https://my-base-rpc.example',
  }}
/>

position

Which corner the floating button + modal anchor to: 'bottom-right' (default) or 'bottom-left'.

<ChatWidget account={acct} position="bottom-left" />

theme

type ChatWidgetTheme = {
  primaryColor?: string;
  buttonSize?: number; // px
  zIndex?: number;
  modalChatStyle?: React.CSSProperties; // styles on the modal surface
  offset?: { x: number; y: number }; // px from the anchored corner
};
<ChatWidget
  account={acct}
  theme={{
    primaryColor: '#0b3988',
    buttonSize: 56,
    zIndex: 9999,
    offset: { x: 24, y: 24 },
    modalChatStyle: { width: 420, maxHeight: '70vh' },
  }}
/>

defaultOpen, onOpen, onClose

Open on mount and observe open/close.

<ChatWidget
  account={acct}
  defaultOpen
  onOpen={() => analytics.track('chat_open')}
  onClose={() => analytics.track('chat_close')}
/>

chatTitle, welcomeMessage

Header title and the first bot bubble.

<ChatWidget
  account={acct}
  chatTitle="Ask Keyring"
  welcomeMessage="Hi! Ask me about your wallet, tokens, or NFTs."
/>

customSuggestions vs additionalSuggestions

Suggestion chips above the input. customSuggestions replaces the built-in set; additionalSuggestions appends after the base set. Each chip is { icon?: string; text: string }text is sent as the prompt when tapped.

// Replace the defaults entirely:
<ChatWidget
  account={acct}
  customSuggestions={[
    { icon: '💸', text: 'Send 0.01 ETH' },
    { icon: '📈', text: 'Show my token balances' },
  ]}
/>

// Keep the defaults, add two more:
<ChatWidget
  account={acct}
  additionalSuggestions={[{ icon: '🖼️', text: 'List my NFTs' }]}
/>

buttonIcon, chatIcon

Image URLs. buttonIcon is the floating button; chatIcon is the header / avatar icon.

<ChatWidget
  account={acct}
  buttonIcon="https://cdn.example/chat.png"
  chatIcon="https://cdn.example/avatar.png"
/>

customChatButton

Replace the floating button with your own node. The widget still owns the open/close behavior, so render a presentational element (no onClick needed).

<ChatWidget
  account={acct}
  customChatButton={
    <div className="my-fab">
      <img src="/chat.svg" alt="" /> Chat
    </div>
  }
/>

styleButtonChat

Extra inline styles applied to the floating container (merged over the position/offset styles).

<ChatWidget account={acct} styleButtonChat={{ bottom: 96, right: 32 }} />

modalConfig

Modal-level toggles. Currently { isShowIcon?: boolean } (default true) — set false to hide the header icon.

<ChatWidget account={acct} modalConfig={{ isShowIcon: false }} />

A fuller example

import { ChatWidget } from 'keyring-chatbot-coinpool-test';
import type { Transaction, TransactionResult } from 'keyring-chatbot-coinpool-test';

export function Chat({ address, chainId, connected }) {
  const onTransaction = async (tx: Transaction): Promise<TransactionResult> => {
    try {
      const hash = await wallet.sendTransaction(tx);
      return { status: 'success', transactionHash: hash };
    } catch (e) {
      return { status: 'fail', error: (e as Error).message };
    }
  };

  return (
    <ChatWidget
      key={`${address}-${chainId}`}
      account={connected ? { address, chainId } : undefined}
      onTransaction={onTransaction}
      language="en"
      position="bottom-right"
      theme={{ primaryColor: '#0b3988', buttonSize: 56, offset: { x: 24, y: 24 } }}
      chatTitle="Ask Keyring"
      welcomeMessage="Hi! Ask me about your wallet, tokens, or NFTs."
      additionalSuggestions={[{ icon: '🖼️', text: 'List my NFTs' }]}
      rpcUrls={{ 8453: 'https://my-base-rpc.example' }}
      onOpen={() => console.log('opened')}
    />
  );
}

Clearing history

Reset the active widget's chat history imperatively — e.g. on logout — without holding a ref:

import { clearChat } from 'keyring-chatbot-coinpool-test';

function onLogout() {
  clearChat(); // clears in-memory + localStorage history and re-shows suggestions
}

Compose your own UI

For a custom layout that reuses the same wiring, skip <ChatWidget> and compose the providers + hooks yourself:

import {
  ConfigProvider,
  ConnectProvider,
  LanguageProvider,
  useAgent,
  useAgentChat,
  ActionForm,
} from 'keyring-chatbot-coinpool-test';
  • useAgent() — builds the per-session chat agent (config-driven; takes no args).
  • useAgentChat({ agent, addMessage })send(text) → reply + wallet actions.
  • ActionForm — the confirmation form primitive for a wallet action.

Exports

// Widget
import { ChatWidget, type ChatWidgetProps } from 'keyring-chatbot-coinpool-test';

// Imperative
import { clearChat } from 'keyring-chatbot-coinpool-test';

// Compose-your-own
import {
  ConfigProvider,
  useConfig,
  ConnectProvider,
  useConnect,
  LanguageProvider,
  useLanguage,
  useAgent,
  useAgentChat,
  ActionForm,
} from 'keyring-chatbot-coinpool-test';

// Types
import type {
  Account,
  ChatWidgetTheme,
  Config,
  Language,
  ChatWidgetLanguage,
  Message,
  MessageButton,
  MessageActionButton,
  MessageWalletAction,
  MessageTokenInfo,
  MessageNftInfo,
  WalletActionStatus,
  SuggestionButtonConfig,
  ChatUICustomization,
  AgentActionType,
  Transaction,
  TransactionStatus,
  TransactionResult,
} from 'keyring-chatbot-coinpool-test';