keyring-chatbot-agent-sdk-test
v1.0.51
Published
React chat widget for keyring-agent-core — floating chatbot UI with AI answers, wallet/token/NFT lookups
Maintainers
Readme
keyring-chatbot-agent-sdk-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-agent-sdk-test
# peer deps
yarn add react react-domPeer deps: react / react-dom (17, 18 or 19).
Quick start
import { ChatWidget } from 'keyring-chatbot-agent-sdk-test';
import type { Transaction, TransactionResult } from 'keyring-chatbot-agent-sdk-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 Reactkey(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:
account.addressis a valid address andaccount.chainIdis set, and- the current
window.location.originis 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 isrpcUrls.
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-agent-sdk-test';
import type { Transaction, TransactionResult } from 'keyring-chatbot-agent-sdk-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-agent-sdk-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-agent-sdk-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-agent-sdk-test';
// Imperative
import { clearChat } from 'keyring-chatbot-agent-sdk-test';
// Compose-your-own
import {
ConfigProvider,
useConfig,
ConnectProvider,
useConnect,
LanguageProvider,
useLanguage,
useAgent,
useAgentChat,
ActionForm,
} from 'keyring-chatbot-agent-sdk-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-agent-sdk-test';