### Start the Example Server
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/example/app-trusted+wallet/README.md
Start the example server for the app-trusted+wallet mode. You can then access the example at http://localhost:3000.
```bash
cd chat-embed-sdk/example
npm run "start:app-trusted+wallet"
# or: node "app-trusted+wallet/server.js"
```
--------------------------------
### Start the Example Server
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/example/app-trusted/README.md
Execute this command from the `chat-embed-sdk/example` directory to start the Express server for the app-trusted example. You can also run the server directly using `node app-trusted/server.js`.
```bash
cd chat-embed-sdk/example
npm run start:app-trusted
# or: node app-trusted/server.js
```
--------------------------------
### Install Dependencies
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/example/app-trusted/README.md
Run this command in the root `example/` directory to install the necessary Node.js dependencies for the example application.
```bash
cd chat-embed-sdk/example
npm install
```
--------------------------------
### Local Development Setup
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/example/wallet-only/README.md
Steps to set up and run the wallet-only demo locally for development. This includes building the SDK, installing demo dependencies, and starting the Vite dev server.
```bash
cd chat-embed-sdk
bun install
bun run build
cd example/wallet-only
bun install
bun run dev
```
--------------------------------
### Run wallet-only Example
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/example/README.md
Starts a minimal static server for the wallet-only example, which does not require a backend authentication server. Access the application at http://localhost:3000.
```bash
npm run start:wallet-only
# open http://localhost:3000
```
--------------------------------
### Run app-trusted Example
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/example/README.md
Starts the Express server for the app-trusted example. Access the application at http://localhost:3000.
```bash
npm run start:app-trusted
# open http://localhost:3000
```
--------------------------------
### Production Build and Deploy
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/example/wallet-only/README.md
Commands to build the production version of the wallet-only example and start the production server. The server is a static Express server that serves the built assets and configuration files.
```bash
cd chat-embed-sdk/example/wallet-only
bun run build
node server.js
```
--------------------------------
### Run app-trusted+wallet Example
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/example/README.md
Starts the Express server for the app-trusted+wallet example, which requires both backend token and Phantom signature. Access the application at http://localhost:3000.
```bash
npm run "start:app-trusted+wallet"
# open http://localhost:3000
```
--------------------------------
### Install JWT Library for Backend
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/integration_instructions.md
Install the 'jsonwebtoken' library for issuing embed tokens on your backend.
```bash
npm install jsonwebtoken
```
--------------------------------
### Configure Environment Variables
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/example/app-trusted/README.md
Copy the example environment file and update it with your Cherry Admin Panel credentials (`APP_ID` and `APP_SECRET`).
```bash
cp .env.example .env
# Edit .env: fill in APP_ID and APP_SECRET from Cherry Admin Panel
```
--------------------------------
### Install Cherry Chat Embed SDK
Source: https://context7.com/cherrydotfun/chat-embed-sdk/llms.txt
Install the SDK using your preferred package manager or load it from a CDN.
```bash
npm install @cherrydotfun/chat-embed-sdk
# yarn add @cherrydotfun/chat-embed-sdk
# pnpm add @cherrydotfun/chat-embed-sdk
# bun add @cherrydotfun/chat-embed-sdk
```
```html
```
--------------------------------
### Install Cherry Embed SDK with bun
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/skills/cherry-embed-integration/SKILL.md
Install the Cherry Embed SDK using bun. Use this command for projects managed with bun.
```bash
bun add @cherrydotfun/chat-embed-sdk
```
--------------------------------
### Configure Environment Variables
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/example/README.md
Copy the example environment file and edit it to include your specific Cherry Admin Panel credentials.
```bash
cd chat-embed-sdk/example
cp .env.example .env
# Edit .env: fill in APP_ID and APP_SECRET from Cherry Admin Panel
```
--------------------------------
### Install Cherry Embed SDK with yarn
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/skills/cherry-embed-integration/SKILL.md
Install the Cherry Embed SDK using yarn. Use this command if your project utilizes yarn as its package manager.
```bash
yarn add @cherrydotfun/chat-embed-sdk
```
--------------------------------
### Install Cherry Embed SDK with pnpm
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/skills/cherry-embed-integration/SKILL.md
Install the Cherry Embed SDK using pnpm. This command is for projects managed with pnpm.
```bash
pnpm add @cherrydotfun/chat-embed-sdk
```
--------------------------------
### Solana Wallet-Adapter Integration
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/README.md
Example of integrating the Cherry Embed SDK with `@solana/wallet-adapter-react` for Solana ecosystem wallets.
```APIDOC
## Solana Wallet-Adapter Integration
For Solana ecosystem wallets (Phantom, Solflare, Backpack, etc.) the host can wire the SDK to `@solana/wallet-adapter-react` directly — no additional Cherry package required:
```tsx
import { useEffect, useRef } from 'react';
import { useWallet } from '@solana/wallet-adapter-react';
import { CherryEmbed } from '@cherrydotfun/chat-embed-sdk';
export function ChatWidget() {
const { publicKey, signMessage } = useWallet();
const containerRef = useRef(null);
const chatRef = useRef(null);
useEffect(() => {
if (!containerRef.current || !publicKey || !signMessage) return;
const chat = new CherryEmbed({
appId: 'your-app-id',
container: containerRef.current,
walletAddress: publicKey.toBase58(),
signChallengeHandler: async (message) => signMessage(message),
});
chatRef.current = chat;
chat.mount();
return () => chat.destroy();
}, [publicKey, signMessage]);
return ;
}
```
For `wallet-only` apps you can skip the wallet integration on the host entirely (Section 3) — the iframe runs its own wallet adapter.
```
--------------------------------
### Install Cherry Chat Embed SDK
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/integration_instructions.md
Install the Cherry Chat Embed SDK using npm. CDN distribution is not yet available.
```bash
npm install @cherrydotfun/chat-embed-sdk
```
--------------------------------
### Configuration Environment Variables
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/example/wallet-only/README.md
Example environment variables for configuring the wallet-only demo. These include application ID, embed URL, optional room ID, and the demo HTTP port.
```ini
APP_ID=your_app_id_here
CHERRY_EMBED_URL=https://embed.cherry.fun # or http://localhost:3002 for local Cherry
ROOM_ID= # optional — preselect a room
PORT=8088 # demo HTTP port
```
--------------------------------
### Solana Wallet-Adapter React Integration for Chat Widget
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/README.md
Integrate the Cherry Embed SDK with `@solana/wallet-adapter-react` for seamless Solana wallet connectivity. This example shows how to initialize `CherryEmbed` with wallet details and mount the chat widget within a React component. The `signChallengeHandler` is crucial for enabling wallet-based authentication flows.
```tsx
import { useEffect, useRef } from 'react';
import { useWallet } from '@solana/wallet-adapter-react';
import { CherryEmbed } from '@cherrydotfun/chat-embed-sdk';
export function ChatWidget() {
const { publicKey, signMessage } = useWallet();
const containerRef = useRef(null);
const chatRef = useRef(null);
useEffect(() => {
if (!containerRef.current || !publicKey || !signMessage) return;
const chat = new CherryEmbed({
appId: 'your-app-id',
container: containerRef.current,
walletAddress: publicKey.toBase58(),
signChallengeHandler: async (message) => signMessage(message),
});
chatRef.current = chat;
chat.mount();
return () => chat.destroy();
}, [publicKey, signMessage]);
return ;
```
--------------------------------
### Page Initialization
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/example/app-trusted/public/index.html
Sets up the initial page load behavior. It logs that the page is loaded, fetches configuration, and then attempts to mount the chat preview if the configuration is valid.
```javascript
window.addEventListener('DOMContentLoaded', async () => {
log('page.loaded');
await loadConfig();
if (appConfig && appConfig.configured) {
try {
await mountPreview();
} catch (err) {
log('preview.mountError', String(err));
}
}
});
```
--------------------------------
### chat.mount()
Source: https://context7.com/cherrydotfun/chat-embed-sdk/llms.txt
Initializes and attaches the iframe. It creates the iframe, establishes the postMessage bridge, sends initial configuration, and waits for the iframe's `ready` event. Returns a `Promise` that rejects if the iframe does not become ready within 30 seconds.
```APIDOC
## `chat.mount()` — Initialize and Attach Iframe
Creates the iframe, establishes the postMessage bridge, sends initial configuration, and waits for the iframe's `ready` event. Returns a `Promise` that rejects if the iframe does not become ready within 30 seconds.
```typescript
import { CherryEmbed } from '@cherrydotfun/chat-embed-sdk';
const chat = new CherryEmbed({
appId: 'app_abc123',
container: document.getElementById('chat-container')!,
roomId: 'room_xyz789',
});
try {
await chat.mount();
console.log('Chat ready, authenticated:', chat.isAuthenticated);
} catch (err) {
// Thrown if iframe does not fire 'ready' within 30 seconds
console.error('Mount failed:', err);
}
// Cleanup on teardown (SPA route change, React useEffect return, etc.)
// return () => chat.destroy();
```
```
--------------------------------
### Build the SDK
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/example/app-trusted/README.md
If the Cherry chat SDK has not been built yet, run this command from the `chat-embed-sdk` directory.
```bash
cd chat-embed-sdk
npm run build
```
--------------------------------
### Initialize and Mount Cherry Chat Embed SDK
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/integration_instructions.md
Connect a wallet, fetch an embed token, and initialize the CherryEmbed SDK. Ensure `signChallengeHandler` is registered in the constructor before mounting. Handles token expiration by refreshing the token and updating the SDK.
```typescript
import { CherryEmbed } from '@cherrydotfun/chat-embed-sdk';
async function initChat() {
// 1. Connect wallet on the host page
const provider = window.phantom?.solana;
const { publicKey } = await provider.connect();
const walletAddress = publicKey.toString();
// 2. Get embed token bound to that wallet
const { token } = await fetch('/api/embed-token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ walletAddress }),
}).then((r) => r.json());
// 3. Construct embed. Register signChallengeHandler BEFORE mount.
const chat = new CherryEmbed({
appId: 'your-app-id',
container: '#chat',
roomId: 'optional-public-room-id',
token,
walletAddress,
signChallengeHandler: async (message) => {
const { signature } = await provider.signMessage(message, 'utf8');
return signature; // Uint8Array, 64 bytes (Ed25519)
},
});
await chat.mount();
// 4. Refresh token when it expires (~5 min embed token, ~15 min Cherry JWT)
chat.on('tokenExpired', async () => {
const { token: fresh } = await fetch('/api/embed-token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ walletAddress }),
}).then((r) => r.json());
chat.setToken(fresh);
});
}
initChat();
```
--------------------------------
### Generate JWT for App-Trusted Authentication (Backend)
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/README.md
Backend Express example to generate a JWT for authenticating users in the App-Trusted mode. Ensure your backend securely stores and uses the CHERRY_APP_SECRET.
```typescript
import jwt from 'jsonwebtoken';
app.get('/api/embed-token', (req, res) => {
// Your own authentication ensures this user is valid
const walletAddress = req.user.walletAddress;
const token = jwt.sign(
{
sub: walletAddress, // User's Solana wallet address
app_id: 'your-app-id',
},
process.env.CHERRY_APP_SECRET, // Shared secret with Cherry admin
{
expiresIn: '5m', // Token expires after 5 minutes
jwtid: crypto.randomUUID(), // Prevent replay attacks
}
);
res.json({ token });
});
```
--------------------------------
### Initialize CherryEmbed with Sign Challenge Handler
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/example/app-trusted+wallet/README.md
Initialize the CherryEmbed SDK, providing necessary configuration including the `signChallengeHandler`. This handler is crucial for verifying wallet ownership by signing challenge messages.
```javascript
const chat = new CherryEmbedSDK.CherryEmbed({
appId: APP_ID,
container: '#chat-container',
token: embedToken,
walletAddress,
roomId: ROOM_ID,
embedUrl: CHERRY_EMBED_URL,
// Register during construction, before initial auth commands are sent.
signChallengeHandler: async (messageBytes) => {
const result = await window.phantom.solana.signMessage(messageBytes, 'utf8');
return result.signature; // Uint8Array, 64 bytes (Ed25519)
},
});
await chat.mount();
```
--------------------------------
### Initialize Cherry Embed with Theming and Layout
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/skills/cherry-embed-integration/SKILL.md
Instantiate the CherryEmbed SDK with custom theme and layout options. Ensure the container element exists in the DOM.
```typescript
const chat = new CherryEmbed({
appId: 'app_xxx',
container: '#cherry-chat',
roomId: 'room_xxx',
position: 'inline',
theme: {
mode: 'dark',
primaryColor: '#7C3AED',
backgroundColor: '#111827',
surfaceColor: '#1F2937',
textColor: '#F9FAFB',
fontFamily: 'Inter, system-ui, sans-serif',
fontSize: 'md',
borderRadius: '8px',
avatarShape: 'circle',
compact: false,
},
layout: {
showHeader: true,
headerTitle: 'Community Chat',
showMemberCount: true,
showAvatars: true,
showTimestamps: true,
showReactions: true,
showInput: true,
},
});
```
--------------------------------
### Verify SDK Changes (chat-embed-sdk)
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/skills/cherry-embed-integration/SKILL.md
Run these commands to perform type checking, run tests, and build the SDK for changes within the chat-embed-sdk directory.
```bash
npm run typecheck
npm test
npm run build
```
--------------------------------
### Load Server Configuration
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/example/app-trusted+wallet/public/index.html
Asynchronously fetches the application configuration from the '/api/config' endpoint and updates the UI with the configuration details. It also logs the configuration status.
```javascript
async function loadConfig() {
try {
const res = await fetch('/api/config');
appConfig = await res.json();
document.getElementById('cfg-appId').textContent = appConfig.appId || '(not set)';
document.getElementById('cfg-embedUrl').textContent = appConfig.embedUrl;
const statusEl = document.getElementById('cfg-status');
if (appConfig.configured) {
statusEl.innerHTML = 'Ready';
} else {
statusEl.innerHTML = 'Missing .env values';
document.getElementById('btn-connect').disabled = true;
}
log('config.loaded', { configured: appConfig.configured });
} catch (err) {
log('config.error', String(err));
}
}
```
--------------------------------
### Initialize CherryEmbed with Wallet-Only Mode
Source: https://context7.com/cherrydotfun/chat-embed-sdk/llms.txt
Create a new CherryEmbed instance for wallet-only authentication. The iframe is not created until mount() is called. Ensure the container element exists in the DOM.
```typescript
import { CherryEmbed } from '@cherrydotfun/chat-embed-sdk';
// Minimal wallet-only setup (no backend required)
const chat = new CherryEmbed({
appId: 'app_abc123', // Cherry Admin app ID
container: '#cherry-chat', // CSS selector or HTMLElement ref
roomId: 'room_xyz789', // Initial room (omit to show room list)
mode: 'single', // 'single' | 'external-controlled' | 'list'
position: 'inline', // 'inline' | 'floating-right' | 'floating-left'
collapsed: false, // Start widget minimised
theme: {
mode: 'dark',
primaryColor: '#7C3AED',
backgroundColor: '#111827',
surfaceColor: '#1F2937',
textColor: '#F9FAFB',
fontFamily: 'Inter, system-ui, sans-serif',
fontSize: 'md',
},
layout: {
showHeader: true,
headerTitle: 'Community Chat',
showMemberCount: true,
showAvatars: true,
showTimestamps: true,
showReactions: true,
showInput: true,
},
});
await chat.mount(); // resolves when iframe fires 'ready' (30 s timeout)
```
--------------------------------
### Initialize Cherry Embed with app-trusted+wallet Auth
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/skills/cherry-embed-integration/SKILL.md
Initialize Cherry Embed with both a backend token and wallet signature. Provide `walletAddress` and `signChallengeHandler` in the constructor before mounting.
```ts
import { CherryEmbed } from '@cherrydotfun/chat-embed-sdk';
const { token } = await fetch('/api/cherry/embed-token').then((res) => res.json());
const chat = new CherryEmbed({
appId: 'app_xxx',
container: '#cherry-chat',
roomId: 'room_xxx',
token,
walletAddress: publicKey.toBase58(),
signChallengeHandler: async (messageBytes) => {
const signed = await wallet.signMessage(messageBytes);
return signed.signature ?? signed;
},
});
await chat.mount();
chat.on('tokenExpired', async () => {
const { token: nextToken } = await fetch('/api/cherry/embed-token').then((res) => res.json());
chat.setToken(nextToken);
});
```
--------------------------------
### Initialize Cherry Embed with Wallet-Only Auth (Backward Compat)
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/README.md
This configuration maintains backward compatibility for wallet-only authentication while allowing the host to provide the connected wallet address and sign challenge handler.
```typescript
const chat = new CherryEmbed({
appId,
container: '#chat',
roomId,
walletAddress, // Optional: host provides connected wallet
signChallengeHandler: async (msg) => {
// Optional: host provides signing logic
return await walletAdapter.signMessage(msg);
},
});
await chat.mount();
```
--------------------------------
### Mount Chat in Preview Mode
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/example/app-trusted/public/index.html
Initializes and mounts the Cherry Embed SDK in preview mode. This allows users to see public messages before signing in. The `walletAddress` is used for display purposes only in this mode.
```javascript
async function mountPreview() {
document.getElementById('chat-container').innerHTML = '';
chat = new CherryEmbedSDK.CherryEmbed({
appId: appConfig.appId,
container: '#chat-container',
// NO token — iframe enters preview mode and shows public messages
// until the host calls chat.setToken(...) below.
walletAddress: DEMO_WALLET_ADDRESS, // for display inside the widget
roomId: appConfig.roomId || undefined,
embedUrl: appConfig.embedUrl,
});
// ── Standard SDK events ────────────────────────────────────────────────
chat.on('ready', () => {
setEvt('ready', 'fired', 'badge-ok');
log('sdk.ready');
});
chat.on('authStateChange', (ok) => {
setEvt('auth', ok ? 'authenticated' : 'signed-out', ok ? 'badge-ok' : 'badge-neutral');
log('sdk.authStateChange', ok);
// When sign-in completes, hide the sign-in button
if (ok) {
const signinBtn = document.getElementById('btn-signin');
signinBtn.disabled = true;
signinBtn.textContent = 'Signed in';
document.getElementById('btn-signout').style.display = '';
} else {
const signinBtn = document.getElementById('btn-signin');
signinBtn.disabled = false;
signinBtn.textContent = 'Sign in as demo user';
document.getElementById('btn-signout').style.display = 'none';
}
});
chat.on('unreadCount', (n) => {
setEvt('unread', String(n), 'badge-ok');
log('sdk.unreadCount', n);
});
chat.on('tokenExpired', () => {
setEvt('expired', 'expired', 'badge-warn');
log('sdk.tokenExpired', 'fetching fresh token');
// Auto re-sign on expiry
signIn().catch((err) => log('autoResign.error', String(err)));
});
chat.on('error', (err) => {
setEvt('error', err.code, 'badge-err');
log('sdk.error', err);
});
// ── Preview / sign-in coordination ─────────────────────────────────────
// Iframe transitioned into preview mode — read-only public messages visible.
chat.on('preview', (data) => {
log('sdk.preview', data);
});
// User clicked send/react inside the iframe while in preview mode.
// In app-trusted that means: trigger sign-in (host backend issues token).
chat.on('walletConnectRequested', () => {
log('sdk.walletConnectRequested', 'iframe asked host to sign in');
signIn().catch((err) => log('signIn.error', String(err)));
});
log('chat.mounting (preview mode)', appConfig.appId);
await chat.mount();
log('chat.mounted');
// Now that the bridge is ready, allow the user to sign in.
document.getElementById('btn-signin').disabled = false;
}
```
--------------------------------
### CherryEmbed Constructor Configuration
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/README.md
Initialize the `CherryEmbed` instance with a configuration object. Key properties include `appId`, `container`, and optional parameters like `token`, `walletAddress`, `roomId`, `theme`, `layout`, `position`, `collapsed`, `embedUrl`, and `signChallengeHandler`.
```typescript
const chat = new CherryEmbed(config: CherryEmbedConfig)
```
--------------------------------
### Load Server Configuration
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/example/app-trusted/public/index.html
Fetches the application configuration from the '/api/config' endpoint. Updates the UI with App ID, Embed URL, and configuration status. Logs the loaded configuration or any errors encountered.
```javascript
async function loadConfig() {
try {
const res = await fetch('/api/config');
appConfig = await res.json();
document.getElementById('cfg-appId').textContent = appConfig.appId || '(not set)';
document.getElementById('cfg-embedUrl').textContent = appConfig.embedUrl;
const statusEl = document.getElementById('cfg-status');
if (appConfig.configured) {
statusEl.innerHTML = 'Ready';
} else {
statusEl.innerHTML = 'Missing .env values';
// btn-signin stays disabled until preview successfully mounts
}
log('config.loaded', { configured: appConfig.configured });
} catch (err) {
log('config.error', String(err));
}
}
```
--------------------------------
### Verify Embed App Changes (messaging-server/embed)
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/skills/cherry-embed-integration/SKILL.md
Execute these commands to run tests and build the embed application for changes within the messaging-server/embed directory.
```bash
bun run test
bun run build
```
--------------------------------
### CherryEmbed Constructor
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/README.md
Initializes the CherryEmbed SDK with a configuration object. The constructor accepts various properties to customize the embed widget's behavior and appearance.
```APIDOC
## CherryEmbed Constructor
```typescript
const chat = new CherryEmbed(config: CherryEmbedConfig)
```
### Parameters
| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `appId` | `string` | Yes | App ID from Cherry Admin Panel |
| `container` | `HTMLElement \| string` | Yes | DOM element or CSS selector |
| `token` | `string` | No | Embed JWT for `app-trusted` mode |
| `walletAddress` | `string` | No | Pre-set wallet address for UI |
| `roomId` | `string` | No | Initial room to display |
| `theme` | `EmbedTheme` | No | Visual customization |
| `layout` | `EmbedLayout` | No | Layout options |
| `position` | `'inline' \| 'floating-right' \| 'floating-left'` | No | Widget position (default: `inline`) |
| `collapsed` | `boolean` | No | Start minimized |
| `embedUrl` | `string` | No | Override embed iframe URL |
| `signChallengeHandler` | `(message: Uint8Array) => Promise` | No | Wallet signer registered before initial auth commands |
```
--------------------------------
### Initialize Chat with Wallet Signature (Frontend)
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/README.md
Frontend code to initialize the Cherry embed, handling wallet connection and signature challenges. The `signChallengeHandler` is crucial for verifying the user's wallet.
```typescript
import { CherryEmbed } from '@cherrydotfun/chat-embed-sdk';
async function initChatWithWallet() {
// 1. User connects Phantom on the host page
const provider = window.phantom?.solana;
const { publicKey } = await provider.connect();
const walletAddress = publicKey.toString();
// 2. Host backend issues the embedToken bound to that wallet
const { token } = await fetch('/api/embed-token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ walletAddress }),
}).then((r) => r.json());
// 3. Construct embed. Pass `signChallengeHandler` BEFORE mount so it is
// registered before Cherry asks for the wallet signature.
const chat = new CherryEmbed({
appId: 'your-app-id',
container: '#chat',
token,
walletAddress,
signChallengeHandler: async (message) => {
// Cherry server sends the bytes; host wallet signs them.
const { signature } = await provider.signMessage(message, 'utf8');
return signature; // Uint8Array
},
});
await chat.mount();
// The iframe will: GET /api/embed/challenge → invoke signChallengeHandler
// → POST /api/embed/auth { embedToken, signature, nonce } → Cherry JWT.
// The user sees one Phantom popup for the signature.
}
```
--------------------------------
### Generic Callback Pattern
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/README.md
Demonstrates the generic callback pattern using `onSignChallenge` for wallet signature modes.
```APIDOC
## Generic Callback Pattern
All wallet-signature modes use the `onSignChallenge` handler:
```typescript
chat.onSignChallenge(
async (message: Uint8Array): Promise => {
// Your wallet adapter signs the message
const signature = await wallet.signMessage(message);
return signature; // Must be Uint8Array
}
);
```
For auth flows that pass `token` and `walletAddress` at construction time, prefer `signChallengeHandler` in the constructor so the handler is registered before initial auth commands are sent to the iframe.
```
--------------------------------
### Initialize Wallet-Only Chat (Frontend)
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/README.md
Frontend integration for the wallet-only chat mode. This requires no backend and handles all wallet interactions within the iframe.
```html
```
--------------------------------
### Lifecycle Methods
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/README.md
Methods for managing the lifecycle of the CherryEmbed instance, including mounting and destroying the widget.
```APIDOC
## Lifecycle Methods
### `mount()`
Initializes the iframe and establishes a connection.
```typescript
await chat.mount()
```
### `destroy()`
Cleans up resources and removes the iframe.
```typescript
chat.destroy()
```
```
--------------------------------
### Simulate Wallet Address Display
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/example/app-trusted/public/index.html
Displays a hardcoded demo wallet address, truncated for brevity. This is for preview purposes and in a real integration, the address would be dynamically determined.
```javascript
const DEMO_WALLET_ADDRESS = '7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU';
document.getElementById('sim-wallet').textContent = DEMO_WALLET_ADDRESS.slice(0, 6) + '...' + DEMO_WALLET_ADDRESS.slice(-4);
```
--------------------------------
### CherryEmbed Constructor
Source: https://context7.com/cherrydotfun/chat-embed-sdk/llms.txt
Creates an instance of the CherryEmbed class. The iframe is not created until `mount()` is called. `appId` and `container` are required; all other fields are optional.
```APIDOC
## `new CherryEmbed(config)` — Constructor
Creates an embed instance. The iframe is not created until `mount()` is called. `appId` and `container` are required; all other fields are optional.
```typescript
import { CherryEmbed } from '@cherrydotfun/chat-embed-sdk';
// Minimal wallet-only setup (no backend required)
const chat = new CherryEmbed({
appId: 'app_abc123', // Cherry Admin app ID
container: '#cherry-chat', // CSS selector or HTMLElement ref
roomId: 'room_xyz789', // Initial room (omit to show room list)
mode: 'single', // 'single' | 'external-controlled' | 'list'
position: 'inline', // 'inline' | 'floating-right' | 'floating-left'
collapsed: false, // Start widget minimised
theme: {
mode: 'dark',
primaryColor: '#7C3AED',
backgroundColor: '#111827',
surfaceColor: '#1F2937',
textColor: '#F9FAFB',
fontFamily: 'Inter, system-ui, sans-serif',
fontSize: 'md',
},
layout: {
showHeader: true,
headerTitle: 'Community Chat',
showMemberCount: true,
showAvatars: true,
showTimestamps: true,
showReactions: true,
showInput: true,
},
});
await chat.mount(); // resolves when iframe fires 'ready' (30 s timeout)
```
```
--------------------------------
### Sign In Flow
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/example/app-trusted/public/index.html
Handles the user sign-in process by fetching an embed token from the backend and setting it in the chat instance. This action transitions the iframe from preview mode to authenticated mode.
```javascript
async function signIn() {
const btn = document.getElementById('btn-signin');
btn.disabled = true;
const originalLabel = btn.textContent;
btn.textContent = 'Signing in…';
try {
const embedToken = await fetchEmbedToken(DEMO_WALLET_ADDRESS);
chat.setToken(embedToken);
log('chat.setToken', 'handed embedToken to iframe');
} catch (err) {
btn.disabled = false;
btn.textContent = originalLabel;
document.getElementById('token-status').innerHTML = `${esc(err.message)}`;
log('signIn.error', String(err));
}
}
```
--------------------------------
### Mount Cherry Embed Chat with Wallet
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/example/app-trusted+wallet/public/index.html
Initializes and mounts the Cherry Embed chat interface after fetching an embed token and setting up wallet event handlers. Includes a sign challenge handler for wallet signature requests.
```javascript
async function mountChatWithWallet(address) {
try {
const embedToken = await fetchEmbedToken(address);
document.getElementById('chat-container').innerHTML = '';
chat = new CherryEmbedSDK.CherryEmbed({
appId: appConfig.appId,
container: '#chat-container',
// embedToken proves app identity (HS256 with appSecret)
token: embedToken,
// walletAddress is sent to iframe so it can display the address
// before the signChallenge exchange completes
walletAddress: address,
// roomId comes from ROOM_ID in .env. Required by the embed iframe —
// without it you get "No room specified".
roomId: appConfig.roomId || undefined,
embedUrl: appConfig.embedUrl,
// Register during construction so the handler is installed before the
// iframe receives the initial token/walletAddress commands.
signChallengeHandler: async (messageBytes) => {
document.getElementById('sign-status').innerHTML = 'Signing...';
log('signChallenge.received', `${messageBytes.length} bytes`);
try {
// Phantom: signMessage(Uint8Array, encoding)
// Returns { publicKey: PublicKey, signature: Uint8Array }
const result = await walletProvider.signMessage(messageBytes, 'utf8');
// result.signature is Uint8Array (Ed25519 signature, 64 bytes)
const sigBytes = result.signature instanceof Uint8Array ? result.signature : new Uint8Array(result.signature);
document.getElementById('sign-status').innerHTML = 'Signed';
log('signChallenge.signed', `${sigBytes.length} bytes`);
return sigBytes;
} catch (err) {
document.getElementById('sign-status').innerHTML = 'Rejected';
log('signChallenge.rejected', err.message || String(err));
throw err;
}
},
});
chat.on('ready', () => {
setEvt('ready', 'fired', 'badge-ok');
log('sdk.ready');
});
chat.on('authStateChange', (ok) => {
setEvt('auth', ok ? 'authenticated' : 'signed-out', ok ? 'badge-ok' : 'badge-neutral');
log('sdk.authStateChange', ok);
});
chat.on('unreadCount', (n) => {
setEvt('unread', String(n), 'badge-ok');
log('sdk.unreadCount', n);
});
chat.on('tokenExpired', () => {
setEvt('expired', 'expired', 'badge-warn');
});
} catch (err) {
log('chat.mount.error', String(err));
document.getElementById('token-status').innerHTML = 'Error';
document.getElementById('sign-status').innerHTML = 'Error';
}
}
```
--------------------------------
### Initialize Cherry Embed with Wallet Verification
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/README.md
When migrating to wallet-verified mode, pass the wallet address and a handler for signing challenges. The embed token remains the same, and the backend does not need changes.
```typescript
const chat = new CherryEmbed({
appId,
container: '#chat',
token, // unchanged — same embedToken
walletAddress, // newly required — host's connected wallet
signChallengeHandler: async (msg) => {
return await walletAdapter.signMessage(msg);
},
});
await chat.mount();
```
--------------------------------
### Mount CherryEmbed Instance
Source: https://context7.com/cherrydotfun/chat-embed-sdk/llms.txt
Mount the CherryEmbed instance to create the iframe and establish communication. This method returns a Promise that resolves when the iframe is ready or rejects on timeout. Handle potential errors during mounting.
```typescript
import { CherryEmbed } from '@cherrydotfun/chat-embed-sdk';
const chat = new CherryEmbed({
appId: 'app_abc123',
container: document.getElementById('chat-container')!,
roomId: 'room_xyz789',
});
try {
await chat.mount();
console.log('Chat ready, authenticated:', chat.isAuthenticated);
} catch (err) {
// Thrown if iframe does not fire 'ready' within 30 seconds
console.error('Mount failed:', err);
}
// Cleanup on teardown (SPA route change, React useEffect return, etc.)
// return () => chat.destroy();
```
--------------------------------
### Wallet-Only Auth Mode with Plain HTML
Source: https://context7.com/cherrydotfun/chat-embed-sdk/llms.txt
Integrates the Cherry Embed SDK in a plain HTML file without a bundler. The SDK is loaded via a CDN, and the CherryEmbed class is accessed through the global CherryEmbedSDK object.
```html
```
--------------------------------
### Theming Methods
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/README.md
Methods for customizing the visual appearance of the chat widget.
```APIDOC
## Theming Methods
### `setTheme(theme)`
Updates the visual theme of the widget.
```typescript
chat.setTheme(theme)
```
### `setLayout(layout)`
Updates the layout options for the widget.
```typescript
chat.setLayout(layout)
```
```
--------------------------------
### Theme Configuration
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/README.md
Customize the visual appearance of the chat embed using the `setTheme` method.
```APIDOC
## Theme Configuration
### Description
Customize the visual appearance of the chat embed using the `setTheme` method.
### Method
`chat.setTheme(themeConfig)`
### Parameters
#### themeConfig
- **themeConfig** (EmbedTheme) - An object defining the theme properties.
- **mode** ('dark' | 'light') - The color mode of the theme.
- **primaryColor** (string) - The primary action color (buttons, links).
- **accentColor** (string) - The highlight color.
- **backgroundColor** (string) - The page background color.
- **surfaceColor** (string) - The card/surface background color.
- **textColor** (string) - The primary text color.
- **textSecondaryColor** (string) - The secondary text color (e.g., timestamps).
- **fontFamily** (string) - The font family to use.
- **fontSize** ('sm' | 'md' | 'lg') - The base font size.
- **borderRadius** (string) - The border radius for elements.
- **avatarShape** ('circle' | 'square') - The shape of user avatars.
- **compact** (boolean) - Whether to use a compact layout (reduced padding/margins).
### Example
```typescript
const theme = {
mode: 'dark',
primaryColor: '#7C3AED',
backgroundColor: '#1a1a2e',
textColor: '#e0e0e0',
fontFamily: 'Inter, sans-serif',
};
chat.setTheme(theme);
```
```
--------------------------------
### Configure Chat Widget Theme
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/README.md
Define a theme object with properties like colors, fonts, and sizes, then apply it using chat.setTheme().
```typescript
const theme: EmbedTheme = {
mode: 'dark',
primaryColor: '#7C3AED',
accentColor: '#FF6B6B',
backgroundColor: '#1a1a2e',
surfaceColor: '#16213e',
textColor: '#e0e0e0',
textSecondaryColor: '#a0a0a0',
fontFamily: 'Inter, sans-serif',
fontSize: 'md',
borderRadius: '12px',
avatarShape: 'circle',
compact: false,
};
chat.setTheme(theme);
```
--------------------------------
### Initialize Cherry Embed with Wallet-Only Auth
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/README.md
For wallet-only authentication, remove the token and sign challenge handler from the CherryEmbed configuration. The iframe handles authentication directly via wallet signature.
```typescript
const chat = new CherryEmbed({ appId, container: '#chat', roomId });
await chat.mount();
```
--------------------------------
### Initialize Cherry Embed with App-Trusted Token (Frontend)
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/README.md
Frontend JavaScript to fetch an authentication token from your backend and initialize the Cherry Embed SDK. The token is used for instant user authentication.
```typescript
import { CherryEmbed } from '@cherrydotfun/chat-embed-sdk';
async function initChat() {
// Fetch token from your backend
const { token } = await fetch('/api/embed-token').then(r => r.json());
const chat = new CherryEmbed({
appId: 'your-app-id',
container: '#chat',
roomId: 'room-id-from-admin',
token, // User is instantly authenticated
});
await chat.mount();
chat.on('ready', () => console.log('Chat ready!'));
}
initChat();
```
--------------------------------
### Copy Skill to Global Skills Directory (Claude Code)
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/skills/README.md
Use this command to copy the `cherry-embed-integration` skill to your global Claude skills directory.
```bash
cp -r node_modules/@cherrydotfun/chat-embed-sdk/skills/cherry-embed-integration ~/.claude/skills/
```
--------------------------------
### Add Skill to Project CLAUDE.md
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/skills/README.md
Include this markdown to reference the Cherry Embed Integration skill within your project's `CLAUDE.md` file.
```markdown
## Skills
- Cherry Embed Integration: `node_modules/@cherrydotfun/chat-embed-sdk/skills/cherry-embed-integration/SKILL.md`
```
--------------------------------
### Wallet Integration: chat.setWalletAddress, chat.onSignChallenge, chat.offSignChallenge
Source: https://context7.com/cherrydotfun/chat-embed-sdk/llms.txt
Manages wallet integration by allowing updates to the wallet address and sign-challenge handler after the embed has been mounted. This is useful when a wallet connects after the embed is already visible on the page.
```APIDOC
## `chat.setWalletAddress(address)` / `chat.onSignChallenge(handler)` / `chat.offSignChallenge()` — Wallet Integration
Update the wallet address or sign-challenge handler after mount, for cases where the wallet connects after the embed is already on the page.
### Methods
- **`setWalletAddress(address: string)`**
- Updates the currently connected wallet address.
- **`onSignChallenge(handler: (msg: string) => Promise)`**
- Registers a handler function to sign challenge messages.
- **`offSignChallenge()`**
- Deregisters the sign challenge handler, typically called when a wallet disconnects.
### Request Example
```typescript
import { useWallet } from '@solana/wallet-adapter-react';
import { CherryEmbed } from '@cherrydotfun/chat-embed-sdk';
// React example — wallet-adapter integration
function ChatWidget() {
const { publicKey, signMessage } = useWallet();
const chatRef = useRef(null);
useEffect(() => {
const chat = new CherryEmbed({
appId: 'app_abc123',
container: '#chat',
// Pass address + handler in constructor if wallet is already connected
walletAddress: publicKey?.toBase58(),
signChallengeHandler: publicKey && signMessage
? async (msg) => signMessage(msg)
: undefined,
});
chatRef.current = chat;
chat.mount();
return () => chat.destroy();
}, []); // mount once
// Update when wallet connects/disconnects after mount
useEffect(() => {
const chat = chatRef.current;
if (!chat) return;
if (publicKey && signMessage) {
chat.setWalletAddress(publicKey.toBase58());
chat.onSignChallenge(async (msg) => signMessage(msg));
} else {
chat.offSignChallenge(); // wallet disconnected
}
}, [publicKey, signMessage]);
return ;
}
```
```
--------------------------------
### Handle Wallet Connection Request
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/README.md
Listen for the 'walletConnectRequested' event to prompt the user to connect their wallet when required for authentication.
```typescript
chat.on('walletConnectRequested', () => {
console.log('User should connect wallet now');
});
```
--------------------------------
### CherryEmbed Lifecycle and Authentication Methods
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/README.md
Manage the lifecycle and authentication of the Cherry Embed widget using methods like `mount`, `destroy`, `onSignChallenge`, `offSignChallenge`, `setWalletAddress`, `setToken`, and `signOut`. These methods allow for dynamic control over the widget's state and user sessions.
```typescript
// Lifecycle
await chat.mount() // Initialize iframe and connect
chat.destroy() // Cleanup and remove iframe
// Authentication (wallet-signature modes)
chat.onSignChallenge(handler) // Register/update challenge signer after mount
chat.offSignChallenge() // Unregister signer
chat.setWalletAddress(address) // Set/update wallet address
// Token management (token-based modes)
chat.setToken(token) // Refresh embedToken (forces re-exchange)
chat.signOut() // Drop iframe sessionStorage JWT and reload
```
--------------------------------
### Copy Skill to Skills Directory (Codex)
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/skills/README.md
Use this command to copy the `cherry-embed-integration` skill to your Codex skills directory.
```bash
cp -r node_modules/@cherrydotfun/chat-embed-sdk/skills/cherry-embed-integration ~/.agents/skills/
```
--------------------------------
### Initialize Cherry Embed with wallet-only Auth
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/skills/cherry-embed-integration/SKILL.md
Initialize Cherry Embed for wallet-only authentication. This mode does not require a backend token endpoint.
```ts
import { CherryEmbed } from '@cherrydotfun/chat-embed-sdk';
const chat = new CherryEmbed({
appId: 'app_xxx',
container: '#cherry-chat',
roomId: 'room_xxx',
});
await chat.mount();
```
--------------------------------
### Mount Chat SDK with Event Listeners
Source: https://github.com/cherrydotfun/chat-embed-sdk/blob/main/example/app-trusted+wallet/public/index.html
Mounts the chat SDK and registers event listeners for token expiration and errors. This code should be used when initializing the chat interface.
```javascript
chat.on('tokenExpired', () => { log('sdk.tokenExpired', 'will auto-refresh'); });
chat.on('error', (err) => { setEvt('error', err.code, 'badge-err'); log('sdk.error', err); });
log('chat.mounting', appConfig.appId);
await chat.mount();
log('chat.mounted');
log('signChallenge.handler', 'registered before mount — waiting for Cherry challenge')
```
--------------------------------
### Integrate Wallet and Sign Challenge Handler
Source: https://context7.com/cherrydotfun/chat-embed-sdk/llms.txt
Update wallet address and sign-challenge handler after the embed is mounted. This is crucial for scenarios where the wallet connects after the embed is already on the page.
```typescript
import { useWallet } from '@solana/wallet-adapter-react';
import { CherryEmbed } from '@cherrydotfun/chat-embed-sdk';
// React example — wallet-adapter integration
function ChatWidget() {
const { publicKey, signMessage } = useWallet();
const chatRef = useRef(null);
useEffect(() => {
const chat = new CherryEmbed({
appId: 'app_abc123',
container: '#chat',
// Pass address + handler in constructor if wallet is already connected
walletAddress: publicKey?.toBase58(),
signChallengeHandler: publicKey && signMessage
? async (msg) => signMessage(msg)
: undefined,
});
chatRef.current = chat;
chat.mount();
return () => chat.destroy();
}, []); // mount once
// Update when wallet connects/disconnects after mount
useEffect(() => {
const chat = chatRef.current;
if (!chat) return;
if (publicKey && signMessage) {
chat.setWalletAddress(publicKey.toBase58());
chat.onSignChallenge(async (msg) => signMessage(msg));
} else {
chat.offSignChallenge(); // wallet disconnected
}
}, [publicKey, signMessage]);
return ;
}
```