keyring-chatbot-coinpool-test
v1.0.13
Published
A React chat widget SDK with floating button and modal
Maintainers
Readme
Keyring Chatbot Agent SDK
An AI-powered Web3 chatbot SDK. Provides a floating chat widget with on-chain capabilities: token swaps, sending tokens/NFTs, viewing balances, and natural-language AI conversation.
Features
- Natural language AI chat — Understands user intent, distinguishes general questions from on-chain tasks
- Token swaps — Best-price routing via deBridge, automatic ERC-20 approval when needed
- Send tokens — ERC-20 and native tokens, supports "max", "50%", "$5" amount expressions
- Send NFTs — ERC-721 and ERC-1155, resolves NFT by name or token ID from the user's wallet
- View balances — Token list with USD values
- Trending tokens — Per-chain trending data with quick-buy buttons
- Wrap / Unwrap — ETH ↔ WETH and equivalents on each chain
- Approve token — ERC-20 spending allowance
- Custom chat UI — Custom title, welcome message, floating button icon, header icon, and suggestion buttons
- Multi-language — English, Japanese, Chinese UI
- Multi-chain — Ethereum, Optimism, BNB Chain, Polygon, Base, Arbitrum, Avalanche, Linea
- Full TypeScript — Complete type declarations included
Installation
npm install keyring-chatbot-agent
# or
yarn add keyring-chatbot-agentPeer dependencies (React 17, 18, or 19):
npm install react react-domReact Usage
Basic example
import { ChatWidget } from 'keyring-chatbot-agent';
import type { Transaction, TransactionResult } from 'keyring-chatbot-agent';
function App() {
const handleTransaction = async (
tx: Transaction
): Promise<TransactionResult> => {
try {
const hash = await walletClient.sendTransaction(tx);
return { status: 'success', transactionHash: hash };
} catch (err: unknown) {
return { status: 'fail', error: (err as Error).message };
}
};
return (
<ChatWidget
account={{ address: '0x...', chainId: 10 }}
onTransaction={handleTransaction}
/>
);
}Full example
import { ChatWidget } from 'keyring-chatbot-agent';
import type { Transaction, TransactionResult } from 'keyring-chatbot-agent';
function App() {
const handleTransaction = async (
tx: Transaction
): Promise<TransactionResult> => {
try {
const hash = await walletClient.sendTransaction(tx);
return { status: 'success', transactionHash: hash };
} catch (err: unknown) {
return { status: 'fail', error: (err as Error).message };
}
};
return (
<ChatWidget
account={{ address: userAddress, chainId: 10 }}
onTransaction={handleTransaction}
position="bottom-right"
language="en"
defaultOpen={false}
theme={{
primaryColor: '#5B7FFF',
buttonSize: 60,
zIndex: 9999,
}}
chatTitle="Keyring Assistant"
welcomeMessage="How can I help you today?"
buttonIcon="https://your-cdn.com/chat-button.svg"
chatIcon="https://your-cdn.com/chat-header-logo.svg"
customSuggestions={[
{ text: 'Show my balance', icon: '💼' },
{ text: 'Swap ETH to USDC', icon: '🔄' },
{ text: 'What tokens are trending?' },
]}
rpcUrls={{
1: 'https://mainnet.infura.io/v3/YOUR_KEY',
10: 'https://optimism-mainnet.infura.io/v3/YOUR_KEY',
56: 'https://bsc-dataseed.binance.org',
137: 'https://polygon-rpc.com',
8453: 'https://mainnet.base.org',
42161: 'https://arb1.arbitrum.io/rpc',
43114: 'https://api.avax.network/ext/bc/C/rpc',
59144: 'https://rpc.linea.build',
}}
onOpen={() => console.log('Chat opened')}
onClose={() => console.log('Chat closed')}
/>
);
}With wagmi v2
import { useSendTransaction, useAccount } from 'wagmi';
import { ChatWidget } from 'keyring-chatbot-agent';
import type { Transaction, TransactionResult } from 'keyring-chatbot-agent';
function App() {
const { address, chainId } = useAccount();
const { sendTransactionAsync } = useSendTransaction();
const handleTransaction = async (
tx: Transaction
): Promise<TransactionResult> => {
try {
const hash = await sendTransactionAsync({
to: tx.to as `0x${string}`,
data: tx.data as `0x${string}`,
value: BigInt(tx.value || '0'),
});
return { status: 'success', transactionHash: hash };
} catch (err: unknown) {
return { status: 'fail', error: (err as Error).message };
}
};
return (
<ChatWidget
account={address ? { address, chainId: chainId ?? 1 } : undefined}
onTransaction={handleTransaction}
/>
);
}<ChatWidget /> Props
| Prop | Type | Default | Required | Description |
| ------------------- | ------------------------------------------------- | ---------------- | -------- | ---------------------------------------------------------------------------------------------------------- |
| account | Account | undefined | No | Connected wallet. Omit when no wallet is connected. |
| onTransaction | (tx: Transaction) => Promise<TransactionResult> | undefined | * | Called when the chatbot needs to sign/send a transaction. |
| position | 'bottom-right' | 'bottom-left' | 'bottom-right' | No | Corner where the floating button appears. |
| language | 'en' | 'ja' | 'cn' | 'en' | No | UI language. |
| theme | ChatWidgetTheme | {} | No | Visual customization (color, size, z-index). |
| defaultOpen | boolean | false | No | Open the chat modal on first render. |
| rpcUrls | Record<number, string> | — | No | Per-chain RPC URL overrides. Takes priority over built-in defaults for balance queries and gas estimation. |
| onOpen | () => void | — | No | Callback fired when the modal opens. |
| onClose | () => void | — | No | Callback fired when the modal closes. |
| chatTitle | string | built-in title | No | Override the title shown in the modal header. |
| welcomeMessage | string | built-in message | No | Override the first assistant message shown in the conversation. |
| customSuggestions | SuggestionButtonConfig[] | built-in list | No | Replace the default quick suggestion buttons. Each item needs text; icon is optional. |
| buttonIcon | string | built-in icon | No | Custom image URL for the floating chat button icon. |
| chatIcon | string | built-in logo | No | Custom image URL for the modal header logo. |
*
onTransactionis required to execute on-chain actions (swap, send, approve, etc.). Without it, the AI can still answer questions and display information.
Chat UI customization
<ChatWidget
account={{ address: userAddress, chainId: 10 }}
onTransaction={handleTransaction}
chatTitle="Keyring Pro"
welcomeMessage="Ask me about swaps, balances, or NFTs."
buttonIcon="https://your-cdn.com/button-icon.svg"
chatIcon="https://your-cdn.com/header-logo.svg"
customSuggestions={[
{ text: 'Check my portfolio', icon: '📊' },
{ text: 'Swap ETH to USDC', icon: '🔄' },
{ text: 'Show trending tokens' },
]}
/>customSuggestions shape:
type SuggestionButtonConfig = {
text: string;
icon?: string;
};Clear chat history
You can clear the current chat history programmatically:
import { ChatWidget, clearChat } from 'keyring-chatbot-agent';
function App() {
return (
<>
<button onClick={() => clearChat()}>Clear chat</button>
<ChatWidget
account={{ address: userAddress, chainId: 10 }}
onTransaction={handleTransaction}
/>
</>
);
}Origin whitelist behavior
For packaged deployments, the widget is rendered only when the current window.location.origin is included in the SDK's internal whitelist. Localhost is allowed for development. If the whitelist is empty, the widget renders normally.
HTML / Web Component Usage
The SDK ships a Web Component build (chat-widget-wc) that can be embedded in any HTML page without a React build pipeline. The Web Component supports the same full feature set as the React component: wallet connection, transaction signing, RPC URL overrides, language selection, and all callbacks.
Configuration via HTML attributes
Simple scalar values (strings, numbers, booleans) can be set directly on the HTML tag:
| Attribute | Type | Default | Description |
| --------------- | --------- | ---------------- | ------------------------------------------------ |
| position | string | 'bottom-right' | 'bottom-right' or 'bottom-left' |
| primary-color | string | '#007bff' | Widget accent color (hex or any CSS color value) |
| button-size | number | 60 | Floating button diameter in pixels |
| z-index | number | 9999 | CSS z-index of the widget |
| default-open | boolean | false | Set to "true" to open the chat on page load |
| language | string | 'en' | UI language: 'en', 'ja', or 'cn' |
Configuration via JavaScript properties
Complex objects (account, rpcUrls, callbacks) are assigned as JavaScript properties on the element. Setting any property triggers an immediate re-render.
| Property | Type | Description |
| ------------------- | ------------------------------------------------- | ------------------------------------- |
| account | { address: string; chainId: number } | Connected wallet info |
| onTransaction | (tx: Transaction) => Promise<TransactionResult> | Transaction signing / sending handler |
| rpcUrls | Record<number, string> | Per-chain RPC URL overrides |
| onOpen | () => void | Callback fired when the modal opens |
| onClose | () => void | Callback fired when the modal closes |
| chatTitle | string | Custom modal header title |
| welcomeMessage | string | Custom first assistant message |
| customSuggestions | { text: string; icon?: string }[] | Custom quick suggestion buttons |
| buttonIcon | string | Custom floating button icon URL |
| chatIcon | string | Custom modal header logo URL |
Web Component method:
| Method | Description |
| ------------- | -------------------------- |
| clearChat() | Clear current chat history |
Via CDN — UMD script tag
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>My dApp</title>
</head>
<body>
<!-- 1. Load the bundle -->
<script src="https://unpkg.com/keyring-chatbot-agent/dist/chat-widget-wc.umd.js"></script>
<!-- 2. Place the custom element with basic attributes -->
<chat-widget
id="my-chat"
position="bottom-right"
primary-color="#5B7FFF"
button-size="60"
z-index="9999"
language="en"
></chat-widget>
<!-- 3. Assign account and onTransaction via JavaScript -->
<script>
const widget = document.getElementById('my-chat');
// Set wallet info (reassign whenever the wallet changes)
widget.account = { address: '0xYourWalletAddress', chainId: 10 };
// Set the transaction handler
widget.onTransaction = async function (tx) {
try {
// Works with any provider: ethers.js, viem, MetaMask, etc.
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
const txResponse = await signer.sendTransaction({
to: tx.to,
data: tx.data,
value: BigInt(tx.value || '0'),
});
const receipt = await txResponse.wait();
return { status: 'success', transactionHash: receipt.hash };
} catch (err) {
return { status: 'fail', error: err.message };
}
};
// Optional: override RPC URLs
widget.rpcUrls = {
10: 'https://optimism-mainnet.infura.io/v3/YOUR_KEY',
8453: 'https://mainnet.base.org',
};
widget.chatTitle = 'Keyring Assistant';
widget.welcomeMessage = 'How can I help you today?';
widget.buttonIcon = 'https://your-cdn.com/chat-button.svg';
widget.chatIcon = 'https://your-cdn.com/chat-header-logo.svg';
widget.customSuggestions = [
{ text: 'Show my balance', icon: '💼' },
{ text: 'Swap ETH to USDC', icon: '🔄' },
{ text: 'Show trending tokens' },
];
// Clear chat history when needed
widget.clearChat();
</script>
</body>
</html>Via CDN — ES module
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>My dApp</title>
</head>
<body>
<chat-widget
id="my-chat"
position="bottom-right"
language="en"
></chat-widget>
<script type="module">
import 'https://unpkg.com/keyring-chatbot-agent/dist/chat-widget-wc.es.js';
const widget = document.getElementById('my-chat');
widget.account = { address: '0xYourWalletAddress', chainId: 10 };
widget.onTransaction = async (tx) => {
// handle transaction signing here
};
</script>
</body>
</html>Full MetaMask integration (plain HTML)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>My dApp</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ethers/6.13.0/ethers.umd.min.js"></script>
<script src="https://unpkg.com/keyring-chatbot-agent/dist/chat-widget-wc.umd.js"></script>
</head>
<body>
<button id="connect-btn">Connect Wallet</button>
<chat-widget
id="my-chat"
position="bottom-right"
language="en"
></chat-widget>
<script>
const widget = document.getElementById('my-chat');
widget.onTransaction = async function (tx) {
try {
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
const txResponse = await signer.sendTransaction({
to: tx.to,
data: tx.data,
value: BigInt(tx.value || '0'),
});
const receipt = await txResponse.wait();
return { status: 'success', transactionHash: receipt.hash };
} catch (err) {
return { status: 'fail', error: err.message };
}
};
document.getElementById('connect-btn').onclick = async function () {
if (!window.ethereum) return alert('Please install MetaMask!');
const provider = new ethers.BrowserProvider(window.ethereum);
const accounts = await provider.send('eth_requestAccounts', []);
const network = await provider.getNetwork();
widget.account = {
address: accounts[0],
chainId: Number(network.chainId),
};
};
// React to wallet / chain changes
if (window.ethereum) {
window.ethereum.on('accountsChanged', async (accounts) => {
if (accounts.length === 0) {
widget.account = undefined;
} else {
const provider = new ethers.BrowserProvider(window.ethereum);
const network = await provider.getNetwork();
widget.account = {
address: accounts[0],
chainId: Number(network.chainId),
};
}
});
window.ethereum.on('chainChanged', async () => {
const provider = new ethers.BrowserProvider(window.ethereum);
const network = await provider.getNetwork();
const accounts = await provider.listAccounts();
if (accounts.length > 0) {
widget.account = {
address: accounts[0].address,
chainId: Number(network.chainId),
};
}
});
}
</script>
</body>
</html>Type Definitions
interface Account {
address: string; // Wallet address (0x...)
chainId: number | string; // EIP-155 chain ID
}
interface Transaction {
from: string;
to: string;
data: string; // ABI-encoded calldata (hex)
value: string; // Native token amount in wei
gasLimit?: string;
gasPrice?: string;
maxFeePerGas?: string;
maxPriorityFeePerGas?: string;
nonce?: number;
chainId?: number | string;
}
interface TransactionResult {
status: 'success' | 'fail';
transactionHash?: string; // Present on success
error?: string; // Present on failure
}
interface ChatWidgetTheme {
primaryColor?: string; // Hex / CSS color
buttonSize?: number; // Floating button size in px
zIndex?: number; // CSS z-index
}
interface SuggestionButtonConfig {
text: string;
icon?: string;
}
interface ChatUICustomization {
chatTitle?: string;
welcomeMessage?: string;
suggestions?: SuggestionButtonConfig[];
buttonIcon?: string;
chatIcon?: string;
}
type Language = 'en' | 'ja' | 'cn';Supported Chains
| Chain | Chain ID | | --------- | -------- | | Ethereum | 1 | | Optimism | 10 | | BNB Chain | 56 | | Polygon | 137 | | Base | 8453 | | Arbitrum | 42161 | | Avalanche | 43114 | | Linea | 59144 |
AI Capabilities
The embedded AI (GPT-4.1-mini via Moralis Cortex) understands natural language and automatically routes to on-chain actions:
| What the user says | Action performed | | ---------------------------------- | ------------------------------------ | | "Swap 1 ETH to USDC" | Token swap via deBridge | | "Swap max USDT to ETH" | Swap with full-balance resolution | | "Buy WBTC" | Trending token list for selection | | "Send 10 USDC to 0x..." | ERC-20 transfer | | "Send 0.05 ETH to 0x..." | Native token transfer | | "Wrap 1 ETH" / "Unwrap 0.5 WETH" | Wrap / unwrap native token | | "Show my NFTs" | Link to NFT gallery | | "Send my Bored Ape #1234 to 0x..." | ERC-721 / ERC-1155 transfer | | "What is the current gas fee?" | General blockchain Q&A | | "What tokens are trending?" | Trending list with quick-buy buttons | | "What is my balance?" | Token list with USD values |
Smart swap flow
The AI handles missing parameters gracefully:
| Available information | Behaviour | | -------------------------------- | --------------------------------------------- | | token_in + token_out + amount | Full auto-swap: estimate → confirm → execute | | token_in + token_out (no amount) | Shows 25 / 50 / 75 / 100 % amount selector | | token_out only | Shows wallet balances to choose source token | | token_in only | Shows trending tokens to choose destination | | Neither token specified | Asks the user to specify both tokens | | ERC-20 needs approval | Prompts Approve step before swap | | Insufficient balance or fee | Warning message, no confirmation button shown |
Build Outputs
| File | Format | Use case |
| ---------------------------- | ------ | ------------------------------------------ |
| dist/chat-widget.es.js | ESM | React / Vite / bundler |
| dist/chat-widget.umd.js | UMD | CommonJS / require |
| dist/chat-widget-wc.es.js | ESM | Web Component via <script type="module"> |
| dist/chat-widget-wc.umd.js | UMD | Web Component via <script src> / CDN |
| dist/lib.d.ts | Types | TypeScript declarations |
Package exports map
{
".": {
"import": "dist/chat-widget.es.js",
"require": "dist/chat-widget.umd.js",
"types": "dist/lib.d.ts"
},
"./web-component": {
"import": "dist/chat-widget-wc.es.js",
"require": "dist/chat-widget-wc.umd.js"
}
}Project Structure
src/
├── components/
│ ├── ActionForm.tsx # Inline transaction forms (send, approve, ...)
│ ├── ChatButton.tsx # Floating action button
│ ├── ChatModal.tsx # Chat UI + all AI and on-chain logic
│ ├── ChatWidget.tsx # Root component — public API entry point
│ ├── MessageContent.tsx # Markdown message renderer
│ ├── ScrollToBottomButton.tsx
│ └── WalletUserInfo.tsx
├── constants/
│ ├── agentActions.ts # AgentActionType definitions + form schemas
│ ├── chains.ts # Supported chain configs
│ ├── storage.ts # localStorage key constants
│ └── systemPrompt.ts # AI system prompt builder
├── contexts/
│ ├── ConfigContext.tsx # RPC URLs + theme config context
│ ├── ConnectContext.tsx # Wallet account context
│ └── LanguageContext.tsx # i18n language context
├── hooks/
│ └── useChatMessages.ts # Chat message state management
├── services/
│ ├── BaseApi.ts # Base HTTP client
│ ├── deBridge.ts # deBridge swap quote + transaction API
│ ├── gemini.ts # Gemini AI: question classification, general chat
│ ├── moralis.ts # Moralis AI chat, wallet balances, NFTs, metadata
│ ├── token.ts # Token info + trending data
│ └── web3.ts # Transaction builder, fee estimation, allowance check
├── types/
│ └── index.ts # All public TypeScript interfaces
├── lib.tsx # React library entry point
└── web-component.tsx # Web Component entry pointPublishing
# Build a specific package name and update README/.env/package.json accordingly
yarn build:package keyring-chatbot-agent
# Publish current package, then auto commit + tag
yarn publish:package
# Build + publish all packages from SDK_PACKAGE_DATA, then push commits and tags
yarn publish:allBuild/publish automation behavior:
build:package <package-name>updatesREADME.md,package.json, and.env, then runs the build.publish:packagepublishes the current package, creates a git commit, and creates a git tag in the form<package>@<version>.publish:allloops through all package names fromSDK_PACKAGE_DATA, builds and publishes them one by one, then pushes commits and tags.
License
ISC
