How to build a Solana mini app for Cherry chats
Turn a Solana dApp into a mini app that runs inside Cherry chats: the SDK, the wallet bridge, the CSP header, launch-token checks, and registration.
This guide is how to build a Solana mini app that opens inside a chat room, starting from a dApp you already ship. Cherry is a wallet-to-wallet messenger and community app for crypto: you sign in with a wallet, DM any address, and join token-gated, NFT-gated, and paid group chats. A mini app is your web app, loaded by Cherry in a WebView on mobile and an iframe on web, handed the signed-in wallet, the room it was opened from, and a launch token your backend can verify.
Before you start
You need four things in place:
- A web app served over HTTPS from a domain you control, as a static build or an SPA.
- Solana wiring you can identify:
@solana/web3.jswith wallet-adapter, or@solana/kit. - Control of the HTTP response headers on whatever serves your HTML. Step 5 depends on it.
- A wallet signed in to Cherry at
https://chat.cherry.funfor testing.
A backend is optional: add one if you keep state per player. Step 6 verifies the token there.
The SDK repository ships a runnable reference app: Vite and React 19, wired for both Solana paths, plus a small server with a token-verification endpoint and a server-rendered blink route. The SDK is free, like the rest of Cherry’s developer tools.
Step 1: Install the SDK
npm install @cherrydotfun/miniapp-sdk
npm install @solana/wallet-adapter-base @solana/web3.js # web3.js projects
npm install @solana/signers # @solana/kit projects, types only (optional)
The package is @cherrydotfun/miniapp-sdk, version 0.1.21 on npm as of 11 September 2026. It has four entry points, so a kit project never pulls in web3.js:
| Import path | What it holds | Solana dependency |
|---|---|---|
@cherrydotfun/miniapp-sdk | core client, bridge, host detection, launch-token verification | none |
@cherrydotfun/miniapp-sdk/react | provider and hooks | none |
@cherrydotfun/miniapp-sdk/solana | CherryWalletAdapter | @solana/web3.js, @solana/wallet-adapter-base |
@cherrydotfun/miniapp-sdk/kit | createCherrySigner | none (structural typing) |
You know it worked when your bundler resolves @cherrydotfun/miniapp-sdk/react.
Step 2: Detect the host and branch the UI
Cherry marks its two containers differently: the mobile WebView injects window.__cherry = true before your page loads, and Cherry web appends cherry_embed=1 to the launch URL. Read those signals at module load, before any provider mounts.
import { CherryMiniAppProvider, useCherryEnvironment } from '@cherrydotfun/miniapp-sdk/react';
function App() {
const { isEmbedded, platform } = useCherryEnvironment({ strict: true });
if (!isEmbedded) return <StandaloneApp />; // 'standalone'
return (
<CherryMiniAppProvider strict={true}>
<EmbeddedApp /> // 'webview' | 'iframe'
</CherryMiniAppProvider>
);
}
Without strict, the SDK also accepts two fallbacks kept for older Cherry builds, ReactNativeWebView and window.parent !== window. Wallet in-app browsers are the usual false positive, because ReactNativeWebView exists in any React Native WebView and any iframe has a parent. Turn the fallbacks off once your users are on builds that set the Cherry signals. Use the same flag to hide what Cherry already provides: your connect-wallet button, header, and splash screen.
You know it worked when the app in a plain browser tab reports standalone, and the same URL with ?cherry_embed=1 reports iframe.
Step 3: Swap in the Cherry wallet adapter
Inside Cherry the wallet is the one the user signed in with, reached over the bridge. For wallet-adapter projects, replace the wallet list and select the adapter by name:
import { CherryWalletAdapter } from '@cherrydotfun/miniapp-sdk/solana';
import { isInsideCherry } from '@cherrydotfun/miniapp-sdk';
const embedded = isInsideCherry({ strict: true });
const wallets = useMemo(
() => (embedded ? [new CherryWalletAdapter()] : [new PhantomWalletAdapter(), new SolflareWalletAdapter()]),
[embedded],
);
// then select the adapter whose name === 'Cherry' and pass autoConnect={embedded}
For @solana/kit projects there is no adapter to register. Build a TransactionSigner instead:
import { createCherrySigner } from '@cherrydotfun/miniapp-sdk/kit';
const signer = createCherrySigner(useCherryApp());
const [signed] = await signer.signTransactions([{ messageBytes, signatures: {} }]);
Either path goes through the same bridge methods, wallet.signMessage, wallet.signTransaction and wallet.signAndSendTransaction, and signAllTransactions signs several transactions in a single batch.
You know it worked when a transaction inside Cherry raises Cherry’s signing sheet and no connect-wallet modal appears first.
Step 4: Read the user and the room
useCherryMiniApp() returns { user, room, launchToken, isReady }. The room object carries id, title, and memberCount, enough for a per-room leaderboard. The handshake times out at 10,000 ms by default, and the client emits suspended, resumed, and walletDisconnected, so pause a running game when the user returns to the message list.
You know it worked when room.title matches the room you launched from and user.publicKey is populated before your first render after isReady.
Step 5: Let Cherry frame your app
On web, Cherry loads you in an iframe, so every HTML response from your server needs one header:
Content-Security-Policy: frame-ancestors 'self' https://chat.cherry.fun
Remove any X-Frame-Options: DENY or SAMEORIGIN you send. Your own API calls stay same-origin inside the iframe and need no CORS change.
You know it worked when the app renders inside Cherry web with no “Refused to frame” error in the console.
Step 6: Verify the launch token on your backend
The client receives an RS256 JWT signed by Cherry. Verify it before you trust any wallet address:
import { verifyLaunchToken } from '@cherrydotfun/miniapp-sdk';
const payload = await verifyLaunchToken(token, { expectedAppId: 'your-app-id' });
// jwksUrl defaults to https://chat.cherry.fun/.well-known/jwks.json
// payload.sub: wallet address
// payload.room_id: the room the app was opened from
The token expires five minutes after issue, so verify it on arrival rather than holding it for a session. For inline cards the token rides in the launch URL query string, so you can render the card server-side before the client mounts. Keep any shared snapshot inside the token’s signed params; raw query fields are forgeable.
You know it worked when the address your backend reads from payload.sub matches the one the client shows.
Step 7: Register the app and claim its room
A registration is what lets Cherry open your app from a room: a manifest, the origins you serve from, an icon, and a permission set. The permission set includes:
| Permission | What the app may do |
|---|---|
wallet:connect | read the signed-in public key |
inline:render | render as a card inside a message; required for sharing results |
inline:eager | mount a non-interactive card without a tap |
Registration goes through the Cherry team as of September 2026, and the team sets the manifest, allowed origins, icon, and permissions with you. Ask for a miniAppId in Cherry’s public Telegram group
. Keys, embeds and bots for the same project live in Cherry Portal
.
Ask for a public room with a handle too. That room is where your players talk, and it doubles as an indexable storefront page. SOL Miner’s room held 4,388 members and 4,993 messages on 11 September 2026; Tramplin.io, a third-party Solana staking app, 1,292 members the same day.
One registration reaches Cherry users on web, iOS, Android, and the Seeker build from the Solana dApp Store. The contrast with Farcaster is the launch surface: Farcaster’s docs describe mini apps that “can be discovered and used within Farcaster clients” and, among the next steps it lists for distribution, “make it sharable in feeds.” A Cherry mini app opens from a conversation and keeps a member list you can post into afterwards.
Build a Solana mini app with one prompt
The SDK package ships an AI skill, cherry-miniapp-integration, under skills/. Copy it to ~/.claude/skills/ for Claude Code or ~/.agents/skills/ for Codex, then say “Integrate Cherry Mini-App SDK into this project”. It reads the codebase and its wallet setup, asks what to hide when the app runs inside Cherry, then works through steps 2 to 6. Read the diff: it cannot register your app or set a header on your production host. The SDK and its example app are on GitHub
.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Blank frame in Cherry web | X-Frame-Options or a CSP without frame-ancestors | Send frame-ancestors 'self' https://chat.cherry.fun on HTML responses |
isInsideCherry() false in Cherry web | the SPA router stripped cherry_embed=1 before detection ran | Call it at module load, before routing |
| Embedded UI shows in a wallet browser | fallback detection matched ReactNativeWebView | Pass { strict: true } to the detection APIs and the provider |
| Two connect buttons inside Cherry | the standalone wallet modal still mounts | Return null from that component when isEmbedded |
| The handshake times out after 10 seconds | the app never ran inside a Cherry container, or the origin is not registered | Launch it from a room; check the origin on the registration |
CherryWalletAdapter fails to import | imported from the package root | Import it from the /solana entry point |
Buffer is not defined | Node globals in browser code | Use btoa/atob, or add a Buffer polyfill to your bundler |
Related guides
- Add chat to your dApp and the embeddable chat widget : a Cherry room inside your product.
- Distribute a Solana dApp , for after registration.
- Chat app for Solana Seeker , for the Seeker audience.
- Chat for web3 games , if the app is a game.
FAQ
Is there a mini app SDK for Solana?
Yes. Cherry publishes @cherrydotfun/miniapp-sdk on npm (0.1.21 as of 11 September 2026) with React hooks, a CherryWalletAdapter for @solana/web3.js, and createCherrySigner for @solana/kit. Farcaster’s @farcaster/miniapp-sdk covers mini apps inside Farcaster clients instead.
Can I turn an existing dApp into a mini app without forking it?
Yes. The integration is additive: one environment check decides whether your app renders its standalone shell or the embedded one, and the Cherry wallet adapter replaces your wallet list only when the app is running inside Cherry.
Do I need a native app to reach Solana Seeker users?
No. A registered mini app opens inside Cherry on web, iOS, Android, and the Seeker build from the Solana dApp Store, so one web deployment covers all four surfaces.
Why does my mini app load on mobile but stay blank in Cherry web?
Cherry web loads the app in an iframe, so your server must send Content-Security-Policy with frame-ancestors 'self' https://chat.cherry.fun on every HTML response and must not send X-Frame-Options: DENY or SAMEORIGIN.
Which permissions does a mini app declare?
Permissions live in the app’s registration, which the Cherry team sets up with you. They include wallet:connect to read the signed-in wallet, inline:render to render as a card inside a message (required for sharing results), and inline:eager to mount a non-interactive card without a tap.
Sources
Build on Cherry
Add chat to your dApp, ship a mini app, or run bots and rooms through the API. Self-serve at the developer portal.