### 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
; } ```