# Send Towns Protocol Skills to your agent
Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.
## Fast path
- Download the package from Yavira.
- Extract it into a folder your agent can access.
- Paste one of the prompts below and point your agent at the extracted folder.
## Suggested prompts
### New install

```text
I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete.
```
### Upgrade existing

```text
I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run.
```
## Machine-readable fields
```json
{
  "schemaVersion": "1.0",
  "item": {
    "slug": "towns-protocol",
    "name": "Towns Protocol Skills",
    "source": "tencent",
    "type": "skill",
    "category": "通讯协作",
    "sourceUrl": "https://clawhub.ai/andreyz/towns-protocol",
    "canonicalUrl": "https://clawhub.ai/andreyz/towns-protocol",
    "targetPlatform": "OpenClaw"
  },
  "install": {
    "downloadUrl": "/downloads/towns-protocol",
    "sourceDownloadUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=towns-protocol",
    "sourcePlatform": "tencent",
    "targetPlatform": "OpenClaw",
    "packageFormat": "ZIP package",
    "primaryDoc": "SKILL.md",
    "includedAssets": [
      "SKILL.md",
      "references/BLOCKCHAIN.md",
      "references/DEBUGGING.md",
      "references/DEPLOYMENT.md",
      "references/INTERACTIVE.md",
      "references/MESSAGING.md"
    ],
    "downloadMode": "redirect",
    "sourceHealth": {
      "source": "tencent",
      "slug": "towns-protocol",
      "status": "healthy",
      "reason": "direct_download_ok",
      "recommendedAction": "download",
      "checkedAt": "2026-05-01T14:36:57.051Z",
      "expiresAt": "2026-05-08T14:36:57.051Z",
      "httpStatus": 200,
      "finalUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=towns-protocol",
      "contentType": "application/zip",
      "probeMethod": "head",
      "details": {
        "probeUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=towns-protocol",
        "contentDisposition": "attachment; filename=\"towns-protocol-2.0.0.zip\"",
        "redirectLocation": null,
        "bodySnippet": null,
        "slug": "towns-protocol"
      },
      "scope": "item",
      "summary": "Item download looks usable.",
      "detail": "Yavira can redirect you to the upstream package for this item.",
      "primaryActionLabel": "Download for OpenClaw",
      "primaryActionHref": "/downloads/towns-protocol"
    },
    "validation": {
      "installChecklist": [
        "Use the Yavira download entry.",
        "Review SKILL.md after the package is downloaded.",
        "Confirm the extracted package contains the expected setup assets."
      ],
      "postInstallChecks": [
        "Confirm the extracted package includes the expected docs or setup files.",
        "Validate the skill or prompts are available in your target agent workspace.",
        "Capture any manual follow-up steps the agent could not complete."
      ]
    }
  },
  "links": {
    "detailUrl": "https://openagent3.xyz/skills/towns-protocol",
    "downloadUrl": "https://openagent3.xyz/downloads/towns-protocol",
    "agentUrl": "https://openagent3.xyz/skills/towns-protocol/agent",
    "manifestUrl": "https://openagent3.xyz/skills/towns-protocol/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/towns-protocol/agent.md"
  }
}
```
## Documentation

### Critical Rules

MUST follow these rules - violations cause silent failures:

User IDs are Ethereum addresses - Always 0x... format, never usernames
Mentions require BOTH - <@{userId}> format in text AND mentions array in options
Two-wallet architecture:

bot.viem.account.address = Gas wallet (signs & pays fees) - MUST fund with Base ETH
bot.appAddress = Treasury (optional, for transfers)


Slash commands DON'T trigger onMessage - They're exclusive handlers
Interactive forms use type property - Not case (e.g., type: 'form')
Never trust txHash alone - Verify receipt.status === 'success' before granting access

### Key Imports

import { makeTownsBot, getSmartAccountFromUserId } from '@towns-protocol/bot'
import type { BotCommand, BotHandler } from '@towns-protocol/bot'
import { Permission } from '@towns-protocol/web3'
import { parseEther, formatEther, erc20Abi, zeroAddress } from 'viem'
import { readContract, waitForTransactionReceipt } from 'viem/actions'
import { execute } from 'viem/experimental/erc7821'

### Handler Methods

MethodSignatureNotessendMessage(channelId, text, opts?) → { eventId }opts: { threadId?, replyId?, mentions?, attachments? }editMessage(channelId, eventId, text)Bot's own messages onlyremoveEvent(channelId, eventId)Bot's own messages onlysendReaction(channelId, messageId, emoji)sendInteractionRequest(channelId, payload)Forms, transactions, signatureshasAdminPermission(userId, spaceId) → booleanban / unban(userId, spaceId)Needs ModifyBanning permission

### Bot Properties

PropertyDescriptionbot.viemViem client for blockchainbot.viem.account.addressGas wallet - MUST fund with Base ETHbot.appAddressTreasury wallet (optional)bot.botIdBot identifier

For detailed guides, see references/:

Messaging API - Mentions, threads, attachments, formatting
Blockchain Operations - Read/write contracts, verify transactions
Interactive Components - Forms, transaction requests
Deployment - Local dev, Render, tunnels
Debugging - Troubleshooting guide

### Project Initialization

bunx towns-bot init my-bot
cd my-bot
bun install

### Environment Variables

APP_PRIVATE_DATA=<base64_credentials>   # From app.towns.com/developer
JWT_SECRET=<webhook_secret>              # Min 32 chars
PORT=3000
BASE_RPC_URL=https://base-mainnet.g.alchemy.com/v2/KEY  # Recommended

### Basic Bot Template

import { makeTownsBot } from '@towns-protocol/bot'
import type { BotCommand } from '@towns-protocol/bot'

const commands = [
  { name: 'help', description: 'Show help' },
  { name: 'ping', description: 'Check if alive' }
] as const satisfies BotCommand[]

const bot = await makeTownsBot(
  process.env.APP_PRIVATE_DATA!,
  process.env.JWT_SECRET!,
  { commands }
)

bot.onSlashCommand('ping', async (handler, event) => {
  const latency = Date.now() - event.createdAt.getTime()
  await handler.sendMessage(event.channelId, 'Pong! ' + latency + 'ms')
})

export default bot.start()

### Config Validation

import { z } from 'zod'

const EnvSchema = z.object({
  APP_PRIVATE_DATA: z.string().min(1),
  JWT_SECRET: z.string().min(32),
  DATABASE_URL: z.string().url().optional()
})

const env = EnvSchema.safeParse(process.env)
if (!env.success) {
  console.error('Invalid config:', env.error.issues)
  process.exit(1)
}

### onMessage

Triggers on regular messages (NOT slash commands).

bot.onMessage(async (handler, event) => {
  // event: { userId, spaceId, channelId, eventId, message, isMentioned, threadId?, replyId? }

  if (event.isMentioned) {
    await handler.sendMessage(event.channelId, 'You mentioned me!')
  }
})

### onSlashCommand

Triggers on /command. Does NOT trigger onMessage.

bot.onSlashCommand('weather', async (handler, { args, channelId }) => {
  // /weather San Francisco → args: ['San', 'Francisco']
  const location = args.join(' ')
  if (!location) {
    await handler.sendMessage(channelId, 'Usage: /weather <location>')
    return
  }
  // ... fetch weather
})

### onReaction

bot.onReaction(async (handler, event) => {
  // event: { reaction, messageId, channelId }
  if (event.reaction === '👋') {
    await handler.sendMessage(event.channelId, 'I saw your wave!')
  }
})

### onTip

Requires "All Messages" mode in Developer Portal.

bot.onTip(async (handler, event) => {
  // event: { senderAddress, receiverAddress, amount (bigint), currency }
  if (event.receiverAddress === bot.appAddress) {
    await handler.sendMessage(event.channelId,
      'Thanks for ' + formatEther(event.amount) + ' ETH!')
  }
})

### onInteractionResponse

bot.onInteractionResponse(async (handler, event) => {
  switch (event.response.payload.content?.case) {
    case 'form':
      const form = event.response.payload.content.value
      for (const c of form.components) {
        if (c.component.case === 'button' && c.id === 'yes') {
          await handler.sendMessage(event.channelId, 'You clicked Yes!')
        }
      }
      break
    case 'transaction':
      const tx = event.response.payload.content.value
      if (tx.txHash) {
        // IMPORTANT: Verify on-chain before granting access
        // See references/BLOCKCHAIN.md for full verification pattern
        await handler.sendMessage(event.channelId,
          'TX: https://basescan.org/tx/' + tx.txHash)
      }
      break
  }
})

### Event Context Validation

Always validate context before using:

bot.onSlashCommand('cmd', async (handler, event) => {
  if (!event.spaceId || !event.channelId) {
    console.error('Missing context:', { userId: event.userId })
    return
  }
  // Safe to proceed
})

### Common Mistakes

MistakeFixinsufficient funds for gasFund bot.viem.account.address with Base ETHMention not highlightingInclude BOTH <@userId> in text AND mentions arraySlash command not workingAdd to commands array in makeTownsBotHandler not triggeringCheck message forwarding mode in Developer PortalwriteContract failingUse execute() for external contractsGranting access on txHashVerify receipt.status === 'success' firstMessage lines overlappingUse \\n\\n (double newlines), not \\nMissing event contextValidate spaceId/channelId before using

### Resources

Developer Portal: https://app.towns.com/developer
Documentation: https://docs.towns.com/build/bots
SDK: https://www.npmjs.com/package/@towns-protocol/bot
Chain ID: 8453 (Base Mainnet)
## Trust
- Source: tencent
- Verification: Indexed source record
- Publisher: andreyz
- Version: 2.0.0
## Source health
- Status: healthy
- Item download looks usable.
- Yavira can redirect you to the upstream package for this item.
- Health scope: item
- Reason: direct_download_ok
- Checked at: 2026-05-01T14:36:57.051Z
- Expires at: 2026-05-08T14:36:57.051Z
- Recommended action: Download for OpenClaw
## Links
- [Detail page](https://openagent3.xyz/skills/towns-protocol)
- [Send to Agent page](https://openagent3.xyz/skills/towns-protocol/agent)
- [JSON manifest](https://openagent3.xyz/skills/towns-protocol/agent.json)
- [Markdown brief](https://openagent3.xyz/skills/towns-protocol/agent.md)
- [Download page](https://openagent3.xyz/downloads/towns-protocol)