keyring-chatbot-agent
v1.0.4
Published
A React chat widget SDK with floating button and modal
Downloads
516
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
- 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,
}}
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. |
*
onTransactionis required to execute on-chain actions (swap, send, approve, etc.). Without it, the AI can still answer questions and display information.
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 |
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',
};
</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
}
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"
}
}Development
# Clone
git clone [email protected]:bacoorteam/keyring-chatbot-agent-sdk.git
cd keyring-chatbot-agent-sdk
# Install dependencies
yarn install
# Start demo dev server
yarn dev
# Build both React and Web Component bundles
yarn build
# Build React bundle only
yarn build:react
# Build Web Component bundle only
yarn build:wc
# Watch mode (auto-rebuild on change)
yarn watch
# Lint
yarn lint
yarn lint:fix
# Format
yarn formatProject 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
# 1. Bump version in package.json
# 2. Build
yarn build
# 3. Login to npm
npm login
# 4. Publish
npm publishLicense
ISC
Repository
Bitbucket — keyring-chatbot-agent-sdk
