How to build a moderation bot for a crypto chat

Build a moderation bot for a Cherry group chat: create a bot key, promote the bot to a room role, wire updates, then mute, kick, ban and delete.

A moderation bot is a named account that deletes spam, mutes offenders and removes repeat abusers from a room without a human watching the chat at 3am. This guide covers how to build a moderation bot on Cherry, the wallet-to-wallet messenger where people sign in with a wallet and join public, token-gated and paid group chats. The step that decides whether any of it works is the authority model: a bot key on its own gives your bot no power over anyone. It moderates only where it holds a role in the room, and that promotion is a separate call made with a different key.

Before you build a moderation bot

You need a Cherry Portal project (sign in with a Solana wallet and create a project), a server to hold secrets, and a room: one your project creates, or one whose owner agrees to promote your bot. Keys are bearer tokens, so every call below runs from your backend, never from a browser.

Two key types do two jobs. A project key (cherry_sk_) manages rooms your project owns and acts as their owner. A bot key (cherry_bot_) acts as one named bot with its own wallet and avatar. Building a moderation bot uses both: the project key sets the room up once, the bot key does the work.

One design fact shapes the rest: group chats on Cherry are not end-to-end encrypted, which is what lets a bot read a room and act on what it reads. DMs are end-to-end encrypted and no bot can filter them, so rules that depend on inspecting private messages are out of reach.

Step 1: Create the bot and copy its key

In Cherry Portal , open Bots and click Create bot. Under Bot identity, set the Display name: the bot’s public face, next to its avatar in every room it posts in. Self-serve names are checked at creation, so a bot cannot call itself after a brand or an authority role. “Cherry Support” is refused, “Supportive Trading Bot” passes.

Create a key in the bot’s Keys section, then open Edit scopes and grant what the bot needs: bots:groups:moderate for the moderation calls, plus bots:updates:poll or bots:webhook:manage for the update transport in step 3. Scopes are per endpoint, and the ones a moderation bot needs are self-serve. A scope granted on the wrong surface does nothing: Apps API scopes belong on project keys, bots:* scopes on bot keys. The token appears once, under the line “Copy your key now. It won’t be shown again.”

Copy the bot’s wallet too, from Bot wallet address or from GET /api/v1/bots/me. Step 2 needs it, and you know this step worked when that call returns the bot’s identity, wallet and granted scopes. Projects, keys and team access are covered in the Cherry Portal guide for developers .

Step 2: Promote the bot to a role in the room

Two independent gates stand in front of every bot moderation call. The scope authorizes the API request. The bot’s role in that specific room authorizes acting on another member. A bot holding bots:groups:moderate that is a plain member of the room, or not in the room at all, is refused with a 403 on every call, which is the usual reason a new bot does nothing.

With a project key, create the room, invite the bot’s wallet and set its role:

const OWNER = {
  Authorization: `Bearer ${process.env.CHERRY_PROJECT_KEY}`,
  'Content-Type': 'application/json',
};
const apps = 'https://api.cherry.fun/api/v1/apps';

const { roomId } = await (
  await fetch(`${apps}/groups`, {
    method: 'POST',
    headers: OWNER,
    body: JSON.stringify({ ownerWallet: HOST_WALLET, title: 'General' }),
  })
).json();

await fetch(`${apps}/groups/${roomId}/members`, {
  method: 'POST',
  headers: OWNER,
  body: JSON.stringify({ wallets: [BOT_WALLET], autoAccept: true }),
});

await fetch(`${apps}/groups/${roomId}/members/${BOT_WALLET}/role`, {
  method: 'POST',
  headers: OWNER,
  body: JSON.stringify({ role: 'moderator' }),
});

For a room your project did not create, the owner does the same by hand: invite the bot’s wallet, open it in the member list and pick the Moderator role, described in the app as “Deletes messages, mutes and kicks members”. Either path ends the same way.

Three rules survive the promotion. A bot cannot moderate the room owner, and a moderator bot cannot act on admins. A bot cannot set roles, so none promotes itself or mints an admin. And a bot always acts as its own wallet, never inheriting the owner’s authority. You know this step worked when a mute call against a test wallet succeeds.

Step 3: Choose long-poll updates or a webhook

Your bot needs something to react to. Long polling is GET /api/v1/bots/getUpdates with the bots:updates:poll scope, Telegram-style: your process asks for new updates and waits. It needs no public URL, which suits local development and a single long-lived worker.

A webhook is POST /api/v1/bots/setWebhook with bots:webhook:manage. Cherry pushes events to your URL, which fits serverless handlers and anything horizontally scaled where no process can hold a poll open. The portal’s Webhook panel puts the choice in one line: “Configure where Cherry pushes bot events. Leave blank to use long-poll (getUpdates) instead.” Its URL field adds the constraint: “Must be HTTPS. Cherry will POST bot events as JSON.”

While a webhook is set, the long-poll endpoint is closed: calls to it are rejected with a conflict error until you delete the webhook. Configure one transport, not both. Both transports draw on one budget: 600 requests per minute and 50,000 per day per app as of September 2026, counted on rolling windows, reads and writes alike.

Step 4: Mute, kick, ban and delete

Four calls cover almost every rule a crypto room needs. Each takes a JSON body, needs bots:groups:moderate, and acts as the bot’s wallet.

ActionEndpointBodyWhat the member gets
Delete a messagePOST /bots/deleteGroupMessage{ roomId, messageId }The message is gone for everyone in the room.
MutePOST /bots/muteGroupMember{ roomId, wallet }The app describes it as “Can read, can’t write”. Their messages are dropped without an error, so a spammer types into a room nobody else sees.
KickPOST /bots/kickGroupMember{ roomId, wallet }Removed from the room, and “Can rejoin later”.
BanPOST /bots/banGroupMember{ roomId, wallet, deleteMessages }Removed and “Can’t rejoin until unbanned”. With deleteMessages: true their message history goes with them.
const BOT = {
  Authorization: `Bearer ${process.env.CHERRY_BOT_KEY}`,
  'Content-Type': 'application/json',
};
const bots = 'https://api.cherry.fun/api/v1/bots';

await fetch(`${bots}/deleteGroupMessage`, {
  method: 'POST',
  headers: BOT,
  body: JSON.stringify({ roomId, messageId }),
});

await fetch(`${bots}/muteGroupMember`, {
  method: 'POST',
  headers: BOT,
  body: JSON.stringify({ roomId, wallet }),
});

unbanGroupMember and unmuteGroupMember take the same { roomId, wallet } shape. When the bot speaks, the message carries the bot’s name and avatar. A project key posts as the project’s own app identity unless you name a bot with botId, which is how one backend keeps an announcement identity and a moderator apart. The full endpoint list and the OpenAPI 3.1 document are on the Cherry API and bots page and in the portal reference below.

What the room already handles before your bot

Write your bot for the gap. Cherry runs several anti-spam layers underneath every group, and the generic attacks are mostly theirs to absorb.

A wallet with no successful on-chain history cannot post in a public group or leave a review, and its cold DMs land in a hidden requests folder: the fresh-burner flood is handled before your code sees a message. A group message containing a blocked word never reaches the room, and the sender sees it in their own view, so the spammer gets no signal to tune against. Any member can also report a message or a profile from the app, choosing Spam, Scam or fraud, Harassment, Inappropriate content, Bot or Other, which puts a human moderator in the loop.

What is left is the part only you know: your community’s rules and the scam pattern aimed at your token this week. That is the bot worth writing.

Where to stop automating

Cherry’s own layers follow one principle: automation drops or limits, humans ban. Copy that. Give your bot deletes, mutes and rate limits, the reversible actions where a false positive costs a member ten minutes, and route bans through a person: post the evidence into a moderators-only room with an inline button (the bots:interactive scope) and let a moderator decide.

A wrong mute annoys someone. A wrong ban on a large holder mid-launch costs you a community. Human-run rooms use the same rule of thumb, covered in the guide to moderating a crypto group chat .

Troubleshooting

Every moderation call returns 403 although the scope is granted: the bot holds no privileged role in that room. Re-run step 2 and check that the promotion landed on the bot’s wallet.

The bot acts on most members but not one: no bot moderates the room owner, and a moderator bot cannot touch admins. Promote it to admin for a longer reach.

The poller is rejected with a conflict error: a webhook is configured for that bot. Delete the webhook, or retire the poller and serve the events.

429s during a raid: back off for the seconds in Retry-After. The budget is per app, so a tight poll loop and your moderation calls compete for the same 600 per minute.

The bot behaves badly: mute it. A muted bot’s calls are rejected, and an owner or admin can mute from the app faster than you can redeploy. If the key is exposed, rotate it in place from the bot’s Keys section; the old secret dies the moment the new one is issued.

FAQ

Can a bot moderate a room my project does not own? Yes. A bot key moderates any room where the bot itself holds an owner, admin or moderator role, including rooms your project never created. The room’s human owner invites the bot’s wallet and promotes it, and the bot can act from then on.

Can a moderation bot read direct messages? No. Direct messages are end-to-end encrypted, so nothing server-side can inspect them. A bot sees group messages in rooms it belongs to, and the DM history of conversations with the bot itself.

How do I stop a misbehaving bot quickly? Mute the bot in the room. A muted bot’s moderation calls are rejected, so muting works as a kill-switch any owner or admin can hit from the Cherry app without touching your deployment.

What are the Cherry API rate limits for a bot? 600 requests per minute and 50,000 per day per app as of September 2026, on rolling windows, with reads and writes counting the same. The budget is shared by every key in the app, so a busy poller eats into your moderation calls.

Create a bot and its first key at portal.cherry.fun .

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.