keyring-chatbot-agent-sdk-test 1.0.26 → 1.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,679 +1,439 @@
1
- # Keyring Chatbot Agent SDK
1
+ # keyring-chatbot-agent-sdk-test
2
2
 
3
- 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.
3
+ React chat widget drops a floating chat button + modal into any React dApp.
4
+ The widget answers questions and handles wallet / token / NFT lookups in the
5
+ browser. When it proposes an on-chain action (send / approve / wrap / unwrap /
6
+ send NFT / swap), it renders a confirmation form and hands the host an unsigned
7
+ transaction to sign and broadcast.
4
8
 
5
- ---
6
-
7
- ## Features
8
-
9
- - **Natural language AI chat** — Understands user intent, distinguishes general questions from on-chain tasks
10
- - **Token swaps** — Best-price routing via deBridge, automatic ERC-20 approval when needed
11
- - **Send tokens** — ERC-20 and native tokens, supports "max", "50%", "$5" amount expressions
12
- - **Send NFTs** — ERC-721 and ERC-1155, resolves NFT by name or token ID from the user's wallet
13
- - **View balances** — Token list with USD values
14
- - **Trending tokens** — Per-chain trending data with quick-buy buttons
15
- - **Wrap / Unwrap** — ETH ↔ WETH and equivalents on each chain
16
- - **Approve token** — ERC-20 spending allowance
17
- - **Custom chat UI** — Custom title, welcome message, floating button icon, header icon, suggestion buttons, and fully custom chat button
18
- - **Multi-language** — English, Japanese, Chinese UI
19
- - **Multi-chain** — Ethereum, Optimism, BNB Chain, Polygon, Base, Arbitrum, Avalanche, Linea
20
- - **Full TypeScript** — Complete type declarations included
21
-
22
- ---
23
-
24
- ## Installation
9
+ ## Install
25
10
 
26
11
  ```bash
27
- npm install keyring-chatbot-agent-sdk-test
28
- # or
29
12
  yarn add keyring-chatbot-agent-sdk-test
13
+ # peer deps
14
+ yarn add react react-dom
30
15
  ```
31
16
 
32
- **Peer dependencies** (React 17, 18, or 19):
33
-
34
- ```bash
35
- npm install react react-dom
36
- ```
37
-
38
- ---
39
-
40
- ## React Usage
17
+ Peer deps: `react` / `react-dom` (17, 18 or 19).
41
18
 
42
- ### Basic example
19
+ ## Quick start
43
20
 
44
21
  ```tsx
45
22
  import { ChatWidget } from 'keyring-chatbot-agent-sdk-test';
46
- import type {
47
- Transaction,
48
- TransactionResult,
49
- } from 'keyring-chatbot-agent-sdk-test';
23
+ import type { Transaction, TransactionResult } from 'keyring-chatbot-agent-sdk-test';
50
24
 
51
- function App() {
25
+ export function App() {
26
+ // Sign + broadcast with your own wallet/provider, then report the result.
52
27
  const handleTransaction = async (
53
28
  tx: Transaction
54
29
  ): Promise<TransactionResult> => {
55
30
  try {
56
- const hash = await walletClient.sendTransaction(tx);
31
+ const hash = await wallet.sendTransaction({
32
+ from: tx.from,
33
+ to: tx.to,
34
+ data: tx.data,
35
+ value: tx.value, // wei, decimal string
36
+ chainId: tx.chainId,
37
+ });
57
38
  return { status: 'success', transactionHash: hash };
58
- } catch (err: unknown) {
39
+ } catch (err) {
59
40
  return { status: 'fail', error: (err as Error).message };
60
41
  }
61
42
  };
62
43
 
63
44
  return (
64
45
  <ChatWidget
65
- account={{ address: '0x...', chainId: 10 }}
46
+ account={{ address: '0x', chainId: 8453 }}
47
+ language="en"
48
+ position="bottom-right"
49
+ theme={{ buttonSize: 56 }}
66
50
  onTransaction={handleTransaction}
67
51
  />
68
52
  );
69
53
  }
70
54
  ```
71
55
 
72
- ### Full example
56
+ The widget creates **one chat session per mount**, persists history in
57
+ `localStorage`, and forwards the connected wallet address + chain so wallet /
58
+ token / NFT lookups have the context they need.
73
59
 
74
- ```tsx
75
- import { ChatWidget } from 'keyring-chatbot-agent-sdk-test';
76
- import type {
77
- Transaction,
78
- TransactionResult,
79
- } from 'keyring-chatbot-agent-sdk-test';
60
+ > The session is built once on mount. Changing `account`'s address/chain after
61
+ > mount does not rebuild it — remount the widget with a new React `key` (e.g.
62
+ > ``key={`${address}-${chainId}`}``) when you want a fresh session on account
63
+ > switch.
80
64
 
81
- function App() {
82
- const handleTransaction = async (
83
- tx: Transaction
84
- ): Promise<TransactionResult> => {
85
- try {
86
- const hash = await walletClient.sendTransaction(tx);
87
- return { status: 'success', transactionHash: hash };
88
- } catch (err: unknown) {
89
- return { status: 'fail', error: (err as Error).message };
90
- }
91
- };
65
+ ## When the widget renders
92
66
 
93
- return (
94
- <ChatWidget
95
- account={{ address: userAddress, chainId: 10 }}
96
- onTransaction={handleTransaction}
97
- position="bottom-right"
98
- language="en"
99
- defaultOpen={false}
100
- theme={{
101
- primaryColor: '#5B7FFF',
102
- buttonSize: 60,
103
- zIndex: 9999,
104
- }}
105
- chatTitle="Keyring Assistant"
106
- welcomeMessage="How can I help you today?"
107
- buttonIcon="https://your-cdn.com/chat-button.svg"
108
- chatIcon="https://your-cdn.com/chat-header-logo.svg"
109
- customChatButton={<MyCustomButton />}
110
- styleButtonChat={{ bottom: 100, right: 30 }}
111
- customSuggestions={[
112
- { text: 'Show my balance', icon: '💼' },
113
- { text: 'Swap ETH to USDC', icon: '🔄' },
114
- { text: 'What tokens are trending?' },
115
- ]}
116
- rpcUrls={{
117
- 1: 'https://mainnet.infura.io/v3/YOUR_KEY',
118
- 10: 'https://optimism-mainnet.infura.io/v3/YOUR_KEY',
119
- 56: 'https://bsc-dataseed.binance.org',
120
- 137: 'https://polygon-rpc.com',
121
- 8453: 'https://mainnet.base.org',
122
- 42161: 'https://arb1.arbitrum.io/rpc',
123
- 43114: 'https://api.avax.network/ext/bc/C/rpc',
124
- 59144: 'https://rpc.linea.build',
125
- }}
126
- onOpen={() => console.log('Chat opened')}
127
- onClose={() => console.log('Chat closed')}
128
- />
129
- );
130
- }
67
+ `<ChatWidget>` returns `null` (renders nothing) unless **both**:
68
+
69
+ 1. `account.address` is a valid address and `account.chainId` is set, and
70
+ 2. the current `window.location.origin` is allowed.
71
+
72
+ Origins are gated by `WHITELIST_ORIGIN` in `src/shared/app.ts`. `localhost`
73
+ always passes for local dev; an empty whitelist disables the gate.
74
+
75
+ ## `onTransaction`
76
+
77
+ Invoked when the user confirms an action form (`send` / `approve` / `wrap` /
78
+ `unwrap` / `send_nft`) or a buy/swap confirmation. The widget passes an unsigned
79
+ transaction; the host signs, broadcasts, and returns the result. **Required**
80
+ for any on-chain action — without it the widget can only answer questions.
81
+
82
+ ```ts
83
+ type Transaction = {
84
+ from: string;
85
+ to: string;
86
+ data: string;
87
+ value: string; // wei, decimal string
88
+ chainId?: number | string;
89
+ gasLimit?: string;
90
+ gasPrice?: string;
91
+ maxFeePerGas?: string;
92
+ maxPriorityFeePerGas?: string;
93
+ nonce?: number;
94
+ };
95
+
96
+ type TransactionResult = {
97
+ status: 'success' | 'fail';
98
+ transactionHash?: string;
99
+ error?: string;
100
+ };
131
101
  ```
132
102
 
133
- ### With wagmi v2
103
+ Before signing, the widget pre-flights the tx against the wallet's native
104
+ balance (gas + value). If it can't cover it, the widget posts an
105
+ "insufficient gas" message in the user's language and never calls
106
+ `onTransaction`. A flaky RPC fails open — the wallet stays the final gatekeeper.
107
+
108
+ ## Props
109
+
110
+ | Prop | Type | Default | Notes |
111
+ | ----------------------- | ----------------------------------------------- | ---------------- | --------------------------------------------------------------------- |
112
+ | `account` | `{ address: string; chainId: number \| string }`| — | Connected wallet. Required for the widget to render. |
113
+ | `onTransaction` | `(tx) => Promise<TransactionResult>` | — | Sign + broadcast handler. Required for on-chain actions. |
114
+ | `language` | `'en' \| 'ja' \| 'cn'` | `'en'` | UI language. Per-message language from the agent is honored too. |
115
+ | `rpcUrls` | `Record<number, string>` | built-in | RPC per **numeric** chainId. Merged over the built-in defaults. |
116
+ | `position` | `'bottom-right' \| 'bottom-left'` | `'bottom-right'` | Anchor of the floating button + modal. |
117
+ | `theme` | `ChatWidgetTheme` | `{}` | `primaryColor`, `buttonSize`, `zIndex`, `modalChatStyle`, `offset`. |
118
+ | `defaultOpen` | `boolean` | `false` | Open the modal on mount. |
119
+ | `onOpen` / `onClose` | `() => void` | — | Fired when the modal opens / closes. |
120
+ | `chatTitle` | `string` | built-in | Header title. |
121
+ | `welcomeMessage` | `string` | localized | First bot bubble. |
122
+ | `customSuggestions` | `SuggestionButtonConfig[]` | built-in chips | Replace the default suggestion chips. |
123
+ | `additionalSuggestions` | `SuggestionButtonConfig[]` | — | Append extra chips after the base set. |
124
+ | `buttonIcon` | `string` | — | Custom floating-button icon. |
125
+ | `chatIcon` | `string` | built-in | Header / avatar icon. |
126
+ | `customChatButton` | `ReactNode` | — | Replace the floating button entirely. |
127
+ | `styleButtonChat` | `CSSProperties` | — | Extra styles on the floating container. |
128
+ | `modalConfig` | `{ isShowIcon?: boolean }` | `{}` | Modal-level toggles. |
129
+
130
+ `SuggestionButtonConfig` is `{ icon?: string; text: string }`.
131
+
132
+ > Agent behavior (model, subagents, storage key, data sources, …) is **not** a
133
+ > prop — it's fixed by the package descriptor in `src/shared/app.ts`. The only
134
+ > agent-related prop is `rpcUrls`.
135
+
136
+ ### Prop details & examples
137
+
138
+ #### `account` (required to render)
139
+
140
+ The connected wallet. Pass `undefined` while disconnected — the widget then
141
+ renders nothing. `chainId` may be a number (`8453`) or hex string (`"0x2105"`).
134
142
 
135
143
  ```tsx
136
- import { useSendTransaction, useAccount } from 'wagmi';
137
- import { ChatWidget } from 'keyring-chatbot-agent-sdk-test';
138
- import type {
139
- Transaction,
140
- TransactionResult,
141
- } from 'keyring-chatbot-agent-sdk-test';
144
+ <ChatWidget account={{ address: '0xAbc…123', chainId: 8453 }} />
142
145
 
143
- function App() {
144
- const { address, chainId } = useAccount();
145
- const { sendTransactionAsync } = useSendTransaction();
146
+ // Disconnected → widget renders null:
147
+ <ChatWidget account={connected ? { address, chainId } : undefined} />
148
+ ```
146
149
 
147
- const handleTransaction = async (
148
- tx: Transaction
149
- ): Promise<TransactionResult> => {
150
- try {
151
- const hash = await sendTransactionAsync({
152
- to: tx.to as `0x${string}`,
153
- data: tx.data as `0x${string}`,
154
- value: BigInt(tx.value || '0'),
155
- });
156
- return { status: 'success', transactionHash: hash };
157
- } catch (err: unknown) {
158
- return { status: 'fail', error: (err as Error).message };
159
- }
160
- };
150
+ Remount on identity change to start a fresh session:
161
151
 
162
- return (
163
- <ChatWidget
164
- account={address ? { address, chainId: chainId ?? 1 } : undefined}
165
- onTransaction={handleTransaction}
166
- />
167
- );
168
- }
152
+ ```tsx
153
+ <ChatWidget key={`${address}-${chainId}`} account={{ address, chainId }} />
169
154
  ```
170
155
 
171
- ### `<ChatWidget />` Props
172
-
173
- | Prop | Type | Default | Required | Description |
174
- | ------------------- | ------------------------------------------------- | ---------------- | -------- | ---------------------------------------------------------------------------------------------------------------- |
175
- | `account` | `Account` | `undefined` | No | Connected wallet. Omit when no wallet is connected. |
176
- | `onTransaction` | `(tx: Transaction) => Promise<TransactionResult>` | `undefined` | \* | Called when the chatbot needs to sign/send a transaction. |
177
- | `position` | `'bottom-right'` \| `'bottom-left'` | `'bottom-right'` | No | Corner where the floating button appears. |
178
- | `language` | `'en'` \| `'ja'` \| `'cn'` | `'en'` | No | UI language. |
179
- | `theme` | `ChatWidgetTheme` | `{}` | No | Visual customization (color, size, z-index). |
180
- | `defaultOpen` | `boolean` | `false` | No | Open the chat modal on first render. |
181
- | `rpcUrls` | `Record<number, string>` | — | No | Per-chain RPC URL overrides. Takes priority over built-in defaults for balance queries and gas estimation. |
182
- | `onOpen` | `() => void` | — | No | Callback fired when the modal opens. |
183
- | `onClose` | `() => void` | — | No | Callback fired when the modal closes. |
184
- | `chatTitle` | `string` | built-in title | No | Override the title shown in the modal header. |
185
- | `welcomeMessage` | `string` | built-in message | No | Override the first assistant message shown in the conversation. |
186
- | `customSuggestions` | `SuggestionButtonConfig[]` | built-in list | No | Replace the default quick suggestion buttons. Each item needs `text`; `icon` is optional. |
187
- | `buttonIcon` | `string` | built-in icon | No | Custom image URL for the floating chat button icon. |
188
- | `chatIcon` | `string` | built-in logo | No | Custom image URL for the modal header logo. |
189
- | `customChatButton` | `React.ReactNode` | `undefined` | No | Fully custom React element to replace the default floating chat button. Click handling is applied automatically. |
190
- | `styleButtonChat` | `React.CSSProperties` | `undefined` | No | Custom CSS styles applied to the floating chat button container. |
191
-
192
- > \* `onTransaction` is required to execute on-chain actions (swap, send, approve, etc.). Without it, the AI can still answer questions and display information.
193
-
194
- ### Chat UI customization
156
+ #### `onTransaction` (required for on-chain actions)
157
+
158
+ Called when the user confirms a wallet action. Sign + broadcast, then return the
159
+ result. See [`onTransaction`](#ontransaction) for the full `Transaction` /
160
+ `TransactionResult` shapes.
195
161
 
196
162
  ```tsx
197
163
  <ChatWidget
198
- account={{ address: userAddress, chainId: 10 }}
199
- onTransaction={handleTransaction}
200
- chatTitle="Keyring Pro"
201
- welcomeMessage="Ask me about swaps, balances, or NFTs."
202
- buttonIcon="https://your-cdn.com/button-icon.svg"
203
- chatIcon="https://your-cdn.com/header-logo.svg"
204
- customChatButton={
205
- <div
206
- style={{
207
- padding: 12,
208
- background: '#5B7FFF',
209
- borderRadius: 16,
210
- color: '#fff',
211
- }}
212
- >
213
- 💬 Chat with AI
214
- </div>
215
- }
216
- customSuggestions={[
217
- { text: 'Check my portfolio', icon: '📊' },
218
- { text: 'Swap ETH to USDC', icon: '🔄' },
219
- { text: 'Show trending tokens' },
220
- ]}
221
- styleButtonChat={{ bottom: 100, right: 30 }}
164
+ account={{ address, chainId }}
165
+ onTransaction={async (tx) => {
166
+ try {
167
+ const hash = await wallet.sendTransaction(tx);
168
+ return { status: 'success', transactionHash: hash };
169
+ } catch (e) {
170
+ return { status: 'fail', error: (e as Error).message };
171
+ }
172
+ }}
222
173
  />
223
174
  ```
224
175
 
225
- `customSuggestions` shape:
176
+ #### `language`
226
177
 
227
- ```ts
228
- type SuggestionButtonConfig = {
229
- text: string;
230
- icon?: string;
231
- };
178
+ UI language for the widget's own strings: `'en'` | `'ja'` | `'cn'`. The agent
179
+ also tags each reply with a language, which is honored per-message.
180
+
181
+ ```tsx
182
+ <ChatWidget account={acct} language="ja" />
232
183
  ```
233
184
 
234
- ### Clear chat history
185
+ #### `rpcUrls`
235
186
 
236
- You can clear the current chat history programmatically:
187
+ Override RPC endpoints, keyed by **numeric** chainId. Merged over the built-in
188
+ defaults, so you only list the chains you want to change.
237
189
 
238
190
  ```tsx
239
- import { ChatWidget, clearChat } from 'keyring-chatbot-agent-sdk-test';
240
-
241
- function App() {
242
- return (
243
- <>
244
- <button onClick={() => clearChat()}>Clear chat</button>
245
- <ChatWidget
246
- account={{ address: userAddress, chainId: 10 }}
247
- onTransaction={handleTransaction}
248
- />
249
- </>
250
- );
251
- }
191
+ <ChatWidget
192
+ account={acct}
193
+ rpcUrls={{
194
+ 1: 'https://my-eth-rpc.example',
195
+ 8453: 'https://my-base-rpc.example',
196
+ }}
197
+ />
252
198
  ```
253
199
 
254
- ### Origin whitelist behavior
200
+ #### `position`
255
201
 
256
- 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.
202
+ Which corner the floating button + modal anchor to: `'bottom-right'` (default)
203
+ or `'bottom-left'`.
257
204
 
258
- ---
205
+ ```tsx
206
+ <ChatWidget account={acct} position="bottom-left" />
207
+ ```
259
208
 
260
- ## HTML / Web Component Usage
209
+ #### `theme`
261
210
 
262
- 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.
211
+ ```ts
212
+ type ChatWidgetTheme = {
213
+ primaryColor?: string;
214
+ buttonSize?: number; // px
215
+ zIndex?: number;
216
+ modalChatStyle?: React.CSSProperties; // styles on the modal surface
217
+ offset?: { x: number; y: number }; // px from the anchored corner
218
+ };
219
+ ```
263
220
 
264
- ### Configuration via HTML attributes
221
+ ```tsx
222
+ <ChatWidget
223
+ account={acct}
224
+ theme={{
225
+ primaryColor: '#0b3988',
226
+ buttonSize: 56,
227
+ zIndex: 9999,
228
+ offset: { x: 24, y: 24 },
229
+ modalChatStyle: { width: 420, maxHeight: '70vh' },
230
+ }}
231
+ />
232
+ ```
265
233
 
266
- Simple scalar values (strings, numbers, booleans) can be set directly on the HTML tag:
234
+ #### `defaultOpen`, `onOpen`, `onClose`
267
235
 
268
- | Attribute | Type | Default | Description |
269
- | --------------- | --------- | ---------------- | ------------------------------------------------ |
270
- | `position` | `string` | `'bottom-right'` | `'bottom-right'` or `'bottom-left'` |
271
- | `primary-color` | `string` | `'#007bff'` | Widget accent color (hex or any CSS color value) |
272
- | `button-size` | `number` | `60` | Floating button diameter in pixels |
273
- | `z-index` | `number` | `9999` | CSS `z-index` of the widget |
274
- | `default-open` | `boolean` | `false` | Set to `"true"` to open the chat on page load |
275
- | `language` | `string` | `'en'` | UI language: `'en'`, `'ja'`, or `'cn'` |
236
+ Open on mount and observe open/close.
276
237
 
277
- ### Configuration via JavaScript properties
238
+ ```tsx
239
+ <ChatWidget
240
+ account={acct}
241
+ defaultOpen
242
+ onOpen={() => analytics.track('chat_open')}
243
+ onClose={() => analytics.track('chat_close')}
244
+ />
245
+ ```
278
246
 
279
- Complex objects (`account`, `rpcUrls`, callbacks) are assigned as JavaScript properties on the element. Setting any property triggers an immediate re-render.
247
+ #### `chatTitle`, `welcomeMessage`
280
248
 
281
- | Property | Type | Description |
282
- | ------------------- | ------------------------------------------------- | ---------------------------------------------------------- |
283
- | `account` | `{ address: string; chainId: number }` | Connected wallet info |
284
- | `onTransaction` | `(tx: Transaction) => Promise<TransactionResult>` | Transaction signing / sending handler |
285
- | `rpcUrls` | `Record<number, string>` | Per-chain RPC URL overrides |
286
- | `onOpen` | `() => void` | Callback fired when the modal opens |
287
- | `onClose` | `() => void` | Callback fired when the modal closes |
288
- | `chatTitle` | `string` | Custom modal header title |
289
- | `welcomeMessage` | `string` | Custom first assistant message |
290
- | `customSuggestions` | `{ text: string; icon?: string }[]` | Custom quick suggestion buttons |
291
- | `buttonIcon` | `string` | Custom floating button icon URL |
292
- | `chatIcon` | `string` | Custom modal header logo URL |
293
- | `customChatButton` | `React.ReactNode` | Fully custom element replacing the default floating button |
294
- | `styleButtonChat` | `React.CSSProperties` | Custom CSS styles for the floating button container |
249
+ Header title and the first bot bubble.
295
250
 
296
- Web Component method:
251
+ ```tsx
252
+ <ChatWidget
253
+ account={acct}
254
+ chatTitle="Ask Keyring"
255
+ welcomeMessage="Hi! Ask me about your wallet, tokens, or NFTs."
256
+ />
257
+ ```
297
258
 
298
- | Method | Description |
299
- | ------------- | -------------------------- |
300
- | `clearChat()` | Clear current chat history |
259
+ #### `customSuggestions` vs `additionalSuggestions`
301
260
 
302
- ### Via CDN UMD script tag
261
+ Suggestion chips above the input. `customSuggestions` **replaces** the built-in
262
+ set; `additionalSuggestions` **appends** after the base set. Each chip is
263
+ `{ icon?: string; text: string }` — `text` is sent as the prompt when tapped.
303
264
 
304
- ```html
305
- <!DOCTYPE html>
306
- <html lang="en">
307
- <head>
308
- <meta charset="UTF-8" />
309
- <title>My dApp</title>
310
- </head>
311
- <body>
312
- <!-- 1. Load the bundle -->
313
- <script src="https://unpkg.com/keyring-chatbot-agent-sdk-test/dist/chat-widget-wc.umd.js"></script>
265
+ ```tsx
266
+ // Replace the defaults entirely:
267
+ <ChatWidget
268
+ account={acct}
269
+ customSuggestions={[
270
+ { icon: '💸', text: 'Send 0.01 ETH' },
271
+ { icon: '📈', text: 'Show my token balances' },
272
+ ]}
273
+ />
314
274
 
315
- <!-- 2. Place the custom element with basic attributes -->
316
- <chat-widget
317
- id="my-chat"
318
- position="bottom-right"
319
- primary-color="#5B7FFF"
320
- button-size="60"
321
- z-index="9999"
322
- language="en"
323
- ></chat-widget>
324
-
325
- <!-- 3. Assign account and onTransaction via JavaScript -->
326
- <script>
327
- const widget = document.getElementById('my-chat');
328
-
329
- // Set wallet info (reassign whenever the wallet changes)
330
- widget.account = { address: '0xYourWalletAddress', chainId: 10 };
331
-
332
- // Set the transaction handler
333
- widget.onTransaction = async function (tx) {
334
- try {
335
- // Works with any provider: ethers.js, viem, MetaMask, etc.
336
- const provider = new ethers.BrowserProvider(window.ethereum);
337
- const signer = await provider.getSigner();
338
- const txResponse = await signer.sendTransaction({
339
- to: tx.to,
340
- data: tx.data,
341
- value: BigInt(tx.value || '0'),
342
- });
343
- const receipt = await txResponse.wait();
344
- return { status: 'success', transactionHash: receipt.hash };
345
- } catch (err) {
346
- return { status: 'fail', error: err.message };
347
- }
348
- };
349
-
350
- // Optional: override RPC URLs
351
- widget.rpcUrls = {
352
- 10: 'https://optimism-mainnet.infura.io/v3/YOUR_KEY',
353
- 8453: 'https://mainnet.base.org',
354
- };
355
-
356
- widget.chatTitle = 'Keyring Assistant';
357
- widget.welcomeMessage = 'How can I help you today?';
358
- widget.buttonIcon = 'https://your-cdn.com/chat-button.svg';
359
- widget.chatIcon = 'https://your-cdn.com/chat-header-logo.svg';
360
- widget.customSuggestions = [
361
- { text: 'Show my balance', icon: '💼' },
362
- { text: 'Swap ETH to USDC', icon: '🔄' },
363
- { text: 'Show trending tokens' },
364
- ];
365
-
366
- // Optional: use a fully custom chat button (React.ReactNode)
367
- // widget.customChatButton = yourCustomElement;
368
-
369
- // Optional: custom style for the chat button container
370
- // widget.styleButtonChat = { bottom: '100px', right: '30px' };
371
-
372
- // Clear chat history when needed
373
- widget.clearChat();
374
- </script>
375
- </body>
376
- </html>
275
+ // Keep the defaults, add two more:
276
+ <ChatWidget
277
+ account={acct}
278
+ additionalSuggestions={[{ icon: '🖼️', text: 'List my NFTs' }]}
279
+ />
377
280
  ```
378
281
 
379
- ### Via CDN — ES module
380
-
381
- ```html
382
- <!DOCTYPE html>
383
- <html lang="en">
384
- <head>
385
- <meta charset="UTF-8" />
386
- <title>My dApp</title>
387
- </head>
388
- <body>
389
- <chat-widget
390
- id="my-chat"
391
- position="bottom-right"
392
- language="en"
393
- ></chat-widget>
282
+ #### `buttonIcon`, `chatIcon`
394
283
 
395
- <script type="module">
396
- import 'https://unpkg.com/keyring-chatbot-agent-sdk-test/dist/chat-widget-wc.es.js';
284
+ Image URLs. `buttonIcon` is the floating button; `chatIcon` is the header /
285
+ avatar icon.
397
286
 
398
- const widget = document.getElementById('my-chat');
287
+ ```tsx
288
+ <ChatWidget
289
+ account={acct}
290
+ buttonIcon="https://cdn.example/chat.png"
291
+ chatIcon="https://cdn.example/avatar.png"
292
+ />
293
+ ```
399
294
 
400
- widget.account = { address: '0xYourWalletAddress', chainId: 10 };
295
+ #### `customChatButton`
401
296
 
402
- widget.onTransaction = async (tx) => {
403
- // handle transaction signing here
404
- };
405
- </script>
406
- </body>
407
- </html>
408
- ```
297
+ Replace the floating button with your own node. The widget still owns the
298
+ open/close behavior, so render a presentational element (no `onClick` needed).
409
299
 
410
- ### Full MetaMask integration (plain HTML)
411
-
412
- ```html
413
- <!DOCTYPE html>
414
- <html lang="en">
415
- <head>
416
- <meta charset="UTF-8" />
417
- <title>My dApp</title>
418
- <script src="https://cdnjs.cloudflare.com/ajax/libs/ethers/6.13.0/ethers.umd.min.js"></script>
419
- <script src="https://unpkg.com/keyring-chatbot-agent-sdk-test/dist/chat-widget-wc.umd.js"></script>
420
- </head>
421
- <body>
422
- <button id="connect-btn">Connect Wallet</button>
423
- <chat-widget
424
- id="my-chat"
425
- position="bottom-right"
426
- language="en"
427
- ></chat-widget>
428
-
429
- <script>
430
- const widget = document.getElementById('my-chat');
431
-
432
- widget.onTransaction = async function (tx) {
433
- try {
434
- const provider = new ethers.BrowserProvider(window.ethereum);
435
- const signer = await provider.getSigner();
436
- const txResponse = await signer.sendTransaction({
437
- to: tx.to,
438
- data: tx.data,
439
- value: BigInt(tx.value || '0'),
440
- });
441
- const receipt = await txResponse.wait();
442
- return { status: 'success', transactionHash: receipt.hash };
443
- } catch (err) {
444
- return { status: 'fail', error: err.message };
445
- }
446
- };
447
-
448
- document.getElementById('connect-btn').onclick = async function () {
449
- if (!window.ethereum) return alert('Please install MetaMask!');
450
- const provider = new ethers.BrowserProvider(window.ethereum);
451
- const accounts = await provider.send('eth_requestAccounts', []);
452
- const network = await provider.getNetwork();
453
- widget.account = {
454
- address: accounts[0],
455
- chainId: Number(network.chainId),
456
- };
457
- };
458
-
459
- // React to wallet / chain changes
460
- if (window.ethereum) {
461
- window.ethereum.on('accountsChanged', async (accounts) => {
462
- if (accounts.length === 0) {
463
- widget.account = undefined;
464
- } else {
465
- const provider = new ethers.BrowserProvider(window.ethereum);
466
- const network = await provider.getNetwork();
467
- widget.account = {
468
- address: accounts[0],
469
- chainId: Number(network.chainId),
470
- };
471
- }
472
- });
473
- window.ethereum.on('chainChanged', async () => {
474
- const provider = new ethers.BrowserProvider(window.ethereum);
475
- const network = await provider.getNetwork();
476
- const accounts = await provider.listAccounts();
477
- if (accounts.length > 0) {
478
- widget.account = {
479
- address: accounts[0].address,
480
- chainId: Number(network.chainId),
481
- };
482
- }
483
- });
484
- }
485
- </script>
486
- </body>
487
- </html>
300
+ ```tsx
301
+ <ChatWidget
302
+ account={acct}
303
+ customChatButton={
304
+ <div className="my-fab">
305
+ <img src="/chat.svg" alt="" /> Chat
306
+ </div>
307
+ }
308
+ />
488
309
  ```
489
310
 
490
- ---
311
+ #### `styleButtonChat`
491
312
 
492
- ## Type Definitions
313
+ Extra inline styles applied to the floating container (merged over the
314
+ position/offset styles).
493
315
 
494
- ```typescript
495
- interface Account {
496
- address: string; // Wallet address (0x...)
497
- chainId: number | string; // EIP-155 chain ID
498
- }
316
+ ```tsx
317
+ <ChatWidget account={acct} styleButtonChat={{ bottom: 96, right: 32 }} />
318
+ ```
499
319
 
500
- interface Transaction {
501
- from: string;
502
- to: string;
503
- data: string; // ABI-encoded calldata (hex)
504
- value: string; // Native token amount in wei
505
- gasLimit?: string;
506
- gasPrice?: string;
507
- maxFeePerGas?: string;
508
- maxPriorityFeePerGas?: string;
509
- nonce?: number;
510
- chainId?: number | string;
511
- }
320
+ #### `modalConfig`
512
321
 
513
- interface TransactionResult {
514
- status: 'success' | 'fail';
515
- transactionHash?: string; // Present on success
516
- error?: string; // Present on failure
517
- }
322
+ Modal-level toggles. Currently `{ isShowIcon?: boolean }` (default `true`) —
323
+ set `false` to hide the header icon.
518
324
 
519
- interface ChatWidgetTheme {
520
- primaryColor?: string; // Hex / CSS color
521
- buttonSize?: number; // Floating button size in px
522
- zIndex?: number; // CSS z-index
523
- }
325
+ ```tsx
326
+ <ChatWidget account={acct} modalConfig={{ isShowIcon: false }} />
327
+ ```
524
328
 
525
- interface SuggestionButtonConfig {
526
- text: string;
527
- icon?: string;
528
- }
329
+ ### A fuller example
529
330
 
530
- interface ChatUICustomization {
531
- chatTitle?: string;
532
- welcomeMessage?: string;
533
- suggestions?: SuggestionButtonConfig[];
534
- buttonIcon?: string;
535
- chatIcon?: string;
536
- }
331
+ ```tsx
332
+ import { ChatWidget } from 'keyring-chatbot-agent-sdk-test';
333
+ import type { Transaction, TransactionResult } from 'keyring-chatbot-agent-sdk-test';
537
334
 
538
- type Language = 'en' | 'ja' | 'cn';
539
- ```
335
+ export function Chat({ address, chainId, connected }) {
336
+ const onTransaction = async (tx: Transaction): Promise<TransactionResult> => {
337
+ try {
338
+ const hash = await wallet.sendTransaction(tx);
339
+ return { status: 'success', transactionHash: hash };
340
+ } catch (e) {
341
+ return { status: 'fail', error: (e as Error).message };
342
+ }
343
+ };
540
344
 
541
- ---
542
-
543
- ## Supported Chains
544
-
545
- | Chain | Chain ID |
546
- | --------- | -------- |
547
- | Ethereum | 1 |
548
- | Optimism | 10 |
549
- | BNB Chain | 56 |
550
- | Polygon | 137 |
551
- | Base | 8453 |
552
- | Arbitrum | 42161 |
553
- | Avalanche | 43114 |
554
- | Linea | 59144 |
555
-
556
- ---
557
-
558
- ## AI Capabilities
559
-
560
- The embedded AI (GPT-4.1-mini via Moralis Cortex) understands natural language and automatically routes to on-chain actions:
561
-
562
- | What the user says | Action performed |
563
- | ---------------------------------- | ------------------------------------ |
564
- | "Swap 1 ETH to USDC" | Token swap via deBridge |
565
- | "Swap max USDT to ETH" | Swap with full-balance resolution |
566
- | "Buy WBTC" | Trending token list for selection |
567
- | "Send 10 USDC to 0x..." | ERC-20 transfer |
568
- | "Send 0.05 ETH to 0x..." | Native token transfer |
569
- | "Wrap 1 ETH" / "Unwrap 0.5 WETH" | Wrap / unwrap native token |
570
- | "Show my NFTs" | Link to NFT gallery |
571
- | "Send my Bored Ape #1234 to 0x..." | ERC-721 / ERC-1155 transfer |
572
- | "What is the current gas fee?" | General blockchain Q&A |
573
- | "What tokens are trending?" | Trending list with quick-buy buttons |
574
- | "What is my balance?" | Token list with USD values |
575
-
576
- ### Smart swap flow
577
-
578
- The AI handles missing parameters gracefully:
579
-
580
- | Available information | Behaviour |
581
- | -------------------------------- | --------------------------------------------- |
582
- | token_in + token_out + amount | Full auto-swap: estimate → confirm → execute |
583
- | token_in + token_out (no amount) | Shows 25 / 50 / 75 / 100 % amount selector |
584
- | token_out only | Shows wallet balances to choose source token |
585
- | token_in only | Shows trending tokens to choose destination |
586
- | Neither token specified | Asks the user to specify both tokens |
587
- | ERC-20 needs approval | Prompts Approve step before swap |
588
- | Insufficient balance or fee | Warning message, no confirmation button shown |
589
-
590
- ---
591
-
592
- ## Build Outputs
593
-
594
- | File | Format | Use case |
595
- | ---------------------------- | ------ | ------------------------------------------ |
596
- | `dist/chat-widget.es.js` | ESM | React / Vite / bundler |
597
- | `dist/chat-widget.umd.js` | UMD | CommonJS / `require` |
598
- | `dist/chat-widget-wc.es.js` | ESM | Web Component via `<script type="module">` |
599
- | `dist/chat-widget-wc.umd.js` | UMD | Web Component via `<script src>` / CDN |
600
- | `dist/lib.d.ts` | Types | TypeScript declarations |
601
-
602
- ### Package exports map
603
-
604
- ```json
605
- {
606
- ".": {
607
- "import": "dist/chat-widget.es.js",
608
- "require": "dist/chat-widget.umd.js",
609
- "types": "dist/lib.d.ts"
610
- },
611
- "./web-component": {
612
- "import": "dist/chat-widget-wc.es.js",
613
- "require": "dist/chat-widget-wc.umd.js"
614
- }
345
+ return (
346
+ <ChatWidget
347
+ key={`${address}-${chainId}`}
348
+ account={connected ? { address, chainId } : undefined}
349
+ onTransaction={onTransaction}
350
+ language="en"
351
+ position="bottom-right"
352
+ theme={{ primaryColor: '#0b3988', buttonSize: 56, offset: { x: 24, y: 24 } }}
353
+ chatTitle="Ask Keyring"
354
+ welcomeMessage="Hi! Ask me about your wallet, tokens, or NFTs."
355
+ additionalSuggestions={[{ icon: '🖼️', text: 'List my NFTs' }]}
356
+ rpcUrls={{ 8453: 'https://my-base-rpc.example' }}
357
+ onOpen={() => console.log('opened')}
358
+ />
359
+ );
615
360
  }
616
361
  ```
617
362
 
618
- ## Project Structure
363
+ ## Clearing history
619
364
 
620
- ```
621
- src/
622
- ├── components/
623
- │ ├── ActionForm.tsx # Inline transaction forms (send, approve, ...)
624
- │ ├── ChatButton.tsx # Floating action button
625
- │ ├── ChatModal.tsx # Chat UI + all AI and on-chain logic
626
- │ ├── ChatWidget.tsx # Root component — public API entry point
627
- │ ├── MessageContent.tsx # Markdown message renderer
628
- │ ├── ScrollToBottomButton.tsx
629
- │ └── WalletUserInfo.tsx
630
- ├── constants/
631
- │ ├── agentActions.ts # AgentActionType definitions + form schemas
632
- │ ├── chains.ts # Supported chain configs
633
- │ ├── storage.ts # localStorage key constants
634
- │ └── systemPrompt.ts # AI system prompt builder
635
- ├── contexts/
636
- │ ├── ConfigContext.tsx # RPC URLs + theme config context
637
- │ ├── ConnectContext.tsx # Wallet account context
638
- │ └── LanguageContext.tsx # i18n language context
639
- ├── hooks/
640
- │ └── useChatMessages.ts # Chat message state management
641
- ├── services/
642
- │ ├── BaseApi.ts # Base HTTP client
643
- │ ├── deBridge.ts # deBridge swap quote + transaction API
644
- │ ├── gemini.ts # Gemini AI: question classification, general chat
645
- │ ├── moralis.ts # Moralis AI chat, wallet balances, NFTs, metadata
646
- │ ├── token.ts # Token info + trending data
647
- │ └── web3.ts # Transaction builder, fee estimation, allowance check
648
- ├── types/
649
- │ └── index.ts # All public TypeScript interfaces
650
- ├── lib.tsx # React library entry point
651
- └── web-component.tsx # Web Component entry point
652
- ```
365
+ Reset the active widget's chat history imperatively — e.g. on logout — without
366
+ holding a ref:
653
367
 
654
- ---
368
+ ```ts
369
+ import { clearChat } from 'keyring-chatbot-agent-sdk-test';
655
370
 
656
- ## Publishing
371
+ function onLogout() {
372
+ clearChat(); // clears in-memory + localStorage history and re-shows suggestions
373
+ }
374
+ ```
657
375
 
658
- ```bash
659
- # Build a specific package name and update README/.env/package.json accordingly
660
- yarn build:package keyring-chatbot-agent-sdk-test
376
+ ## Compose your own UI
661
377
 
662
- # Publish current package, then auto commit + tag
663
- yarn publish:package
378
+ For a custom layout that reuses the same wiring, skip `<ChatWidget>` and compose
379
+ the providers + hooks yourself:
664
380
 
665
- # Build + publish all packages from SDK_PACKAGE_DATA, then push commits and tags
666
- yarn publish:all
381
+ ```tsx
382
+ import {
383
+ ConfigProvider,
384
+ ConnectProvider,
385
+ LanguageProvider,
386
+ useAgent,
387
+ useAgentChat,
388
+ ActionForm,
389
+ } from 'keyring-chatbot-agent-sdk-test';
667
390
  ```
668
391
 
669
- Build/publish automation behavior:
392
+ - `useAgent()` — builds the per-session chat agent (config-driven; takes no args).
393
+ - `useAgentChat({ agent, addMessage })` — `send(text)` → reply + wallet actions.
394
+ - `ActionForm` — the confirmation form primitive for a wallet action.
670
395
 
671
- - `build:package <package-name>` updates `README.md`, `package.json`, and `.env`, then runs the build.
672
- - `publish:package` publishes the current package, creates a git commit, and creates a git tag in the form `<package>@<version>`.
673
- - `publish:all` loops through all package names from `SDK_PACKAGE_DATA`, builds and publishes them one by one, then pushes commits and tags.
396
+ ## Exports
674
397
 
675
- ---
676
-
677
- ## License
398
+ ```ts
399
+ // Widget
400
+ import { ChatWidget, type ChatWidgetProps } from 'keyring-chatbot-agent-sdk-test';
401
+
402
+ // Imperative
403
+ import { clearChat } from 'keyring-chatbot-agent-sdk-test';
404
+
405
+ // Compose-your-own
406
+ import {
407
+ ConfigProvider,
408
+ useConfig,
409
+ ConnectProvider,
410
+ useConnect,
411
+ LanguageProvider,
412
+ useLanguage,
413
+ useAgent,
414
+ useAgentChat,
415
+ ActionForm,
416
+ } from 'keyring-chatbot-agent-sdk-test';
678
417
 
679
- ISC
418
+ // Types
419
+ import type {
420
+ Account,
421
+ ChatWidgetTheme,
422
+ Config,
423
+ Language,
424
+ ChatWidgetLanguage,
425
+ Message,
426
+ MessageButton,
427
+ MessageActionButton,
428
+ MessageWalletAction,
429
+ MessageTokenInfo,
430
+ MessageNftInfo,
431
+ WalletActionStatus,
432
+ SuggestionButtonConfig,
433
+ ChatUICustomization,
434
+ AgentActionType,
435
+ Transaction,
436
+ TransactionStatus,
437
+ TransactionResult,
438
+ } from 'keyring-chatbot-agent-sdk-test';
439
+ ```