# Your users' names and avatars By default the chat labels people by their **wallet identity**: a `.sol` domain if they own one, otherwise a shortened address like `7xKX…gAsU`. If your app already has usernames and avatars, it can supply them, and the widget renders your users instead. This is a **visual overlay, scoped to one running widget**: - Cherry stores none of these names. Nothing is written to a profile. - The **wallet remains the author** of every message. Moderation, deletes, and mention routing all keep working off the wallet. - Nothing changes how that person appears in the Cherry app, or in anyone else's embed. > **Note:** This is a label, not a login. It does not sign anyone in, and it is not a > substitute for tying chat identity to your users: that is what [Authentication](https://portal.cherry.fun/docs/embed/authentication.md) does. ## 1. Turn it on Open the embed in the [dashboard](https://portal.cherry.fun/dashboard), then **General** → **Who your users appear as** → **Show your app's names and avatars**. Until that switch is on, the widget never asks, and whatever your code returns is ignored. There is one switch per embed, so a staging embed can use it while production does not. ## 2. Choose where the widget asks Two transports, one contract. Pick either; you do not need both. | | Answers from | Configure | |---|---|---| | **Your page** | the browser, over the SDK | pass `resolveUsers` in the config | | **Your backend** | a profile endpoint the widget calls directly | set **Profile endpoint** on the same card | The endpoint wins when both are available. Answering from the page needs no backend work; answering from your backend is the better fit for mobile WebViews (where the host page is a thin shim), and it is unaffected by your app's render loop. ### From your page > **Warning:** The SDK transport, `resolveUsers`, `searchUsers`, `userProfiles`, `setUserProfiles()`, `invalidateUserProfiles()` and bearer auth through `chat.setIdentityToken()`, needs `@cherrydotfun/chat-embed-sdk` **0.1.7 or newer**. On 0.1.6 these options are ignored, so use the backend transport below until you have upgraded. ```ts const chat = new CherryEmbed({ appId: 'YOUR_EMBED_ID', container: '#cherry-chat', roomId: 'YOUR_ROOM_ID', // Called with up to 50 wallets per call, batched over a 16 ms window. // Return null (or omit a wallet) for anyone you don't know. resolveUsers: async (wallets) => { const rows = await myApi.usersByWallet(wallets); return Object.fromEntries( wallets.map((w) => [ w, rows[w] ? { displayName: rows[w].name, avatarUrl: rows[w].photo } : null, ]), ); }, // Optional: makes @mention autocomplete search YOUR directory. searchUsers: async ({ query, cursor, limit }) => { const page = await myApi.searchUsers({ query, cursor, limit }); return { users: page.items, nextCursor: page.next }; }, }); ``` Register nothing and the previous behavior is unchanged: the widget asks once, learns your page cannot answer, and stops asking. ### From your backend Set **Profile endpoint** to a base URL. The widget appends its own paths: | Request | Body / query | Response | |---|---|---| | `POST {url}/resolve` | `{ ids: string[] }` | `{ users: { [wallet]: profile \| null } }` | | `GET {url}/search` | `?query=&cursor=&limit=` (`limit` is capped at 100) | `{ users: [{ id, displayName?, avatarUrl? }], nextCursor? }` | | `GET {url}/users/:wallet` | none | `profile \| null` (reserved for the profile view) | Four things to get right: - **CORS.** The caller is the **iframe**, not your page: allow the origin `https://embed.cherry.fun`. - **HTTPS.** The iframe is served over HTTPS, so an `http://` endpoint is blocked as mixed content. Plain HTTP works only for `localhost` during development. - **No cookies.** Requests are sent with `credentials: 'omit'`, deliberately: the widget must never be walked into replaying a visitor's ambient session at your API. For auth, pass a bearer token with `chat.setIdentityToken(token)`. It is held in memory only, and sent on these requests as `Authorization: Bearer …`. - **Who is asking.** Each request carries `X-Cherry-App-Id` with the embed's ID, so one endpoint can serve several embeds. > **Warning:** Wallet addresses are the only thing the widget sends you, and its request > arrives unauthenticated unless you set a token. Return the same public profile > you would show any visitor, never an email, a role, or anything else you would > not print next to a message. The URL is delivered to the iframe by Cherry, never by your page, so a script on your site cannot repoint identity resolution somewhere else. Turning the switch off stops the widget from calling it immediately, without clearing the field. ## 3. Push changes as they happen The widget only asks about wallets it hasn't resolved yet, so a rename in your app is invisible to an already-open chat. Push it: ```ts chat.setUserProfiles({ [wallet]: { displayName: 'New name' } }); // avatar kept chat.invalidateUserProfiles([wallet]); // re-ask your resolver chat.invalidateUserProfiles(); // re-ask for everyone ``` - Pushed fields are **merged** onto what the widget already knows: sending only `displayName` leaves the avatar alone. Include a field with an empty value to clear just that field. - Push `null` for a wallet to say you no longer know that person, and the chat falls back to their Cherry identity. - `invalidateUserProfiles` means *refresh*, not *forget*: the current name stays on screen while the fresh answer is in flight, so the row updates once instead of blinking through the fallback. `userProfiles` in the config does the same as `setUserProfiles` for people you already know at mount time, so the first paint is right with no round-trip. ## What the widget does with your answer **It never blocks rendering.** Cherry's own label paints immediately and is replaced when your answer lands. A slow resolver delays a name, never the message. **It caches per widget, in memory.** Nothing is persisted (no `localStorage`, no server round-trip), and closing the widget forgets every name. Answers are re-asked after five minutes, or when you invalidate them. A wallet you answered `null` for is remembered as unknown, so it is asked about once, not once per render. **It stops asking a resolver that is broken.** Three consecutive failures, or timeouts (the deadline is a few seconds, since a name is decorative), disable the transport until you push an update or the visitor reloads. That keeps a long scroll from hammering a dead endpoint. **Everything you send is sanitized before it reaches the DOM**, because it arrives from outside Cherry: | Field | Rules | |---|---| | `displayName` | One line: newlines and tabs become spaces. Zero-width characters and bidi overrides are stripped (both are used to mint lookalikes of an existing member). Trimmed to 48 characters with an ellipsis. | | `avatarUrl` | Absolute `http(s)` only. `data:` and `blob:` are refused: they are an unbounded byte channel into the chat surface. | Anything that does not survive is dropped, and that field falls back to the Cherry identity. Unknown fields are ignored. Bot and verified markers, and moderation roles, are never overridden by a supplied name. ## Mentions With `searchUsers` registered, `@`-autocomplete searches your directory instead of only the room's history. That is the difference between "@Alice finds nothing" and "@Alice finds Alice", since Cherry's own search only knows domains and wallets. Picking a suggestion inserts the name with spaces turned into underscores (`@Alice_Smith`): the mention grammar stops at the first space, so an untouched name would be cut in half. The wallet travels with the mention invisibly, so routing and notifications are unaffected. Only the label changes. ## Try it The SDK repository ships a runnable bench for this feature: [`example/host-identity/`](https://github.com/cherrydotfun/chat-embed-sdk/tree/main/example/host-identity). It exercises both transports, lets you edit a name or avatar per user by hand, and includes a probe that answers with deliberately dangerous values so you can watch the sanitizer work. ## Next steps - Every option and method: [Configuration](https://portal.cherry.fun/docs/embed/configuration.md) · [SDK API reference](https://portal.cherry.fun/docs/embed/api-reference.md) - Tie chat identity to your logged-in users: [Authentication](https://portal.cherry.fun/docs/embed/authentication.md) - Run the widget in a mobile app: [React Native & Flutter](https://portal.cherry.fun/docs/embed/mobile.md)