> ## Documentation Index
> Fetch the complete documentation index at: https://hntlabs-feat-x402-v2.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Slash Commands

Slash commands are special message types that:

* Start with `/` (e.g., `/help`, `/poll`, `/weather`)
* Show up in autocomplete when users type `/` in Towns
* Display descriptions to help users understand what they do
* Are handled separately from regular messages (don't trigger `onMessage`)

### Example Commands

```
/help                         → Show bot commands
/ban @john                    → Ban a user
/weather San Francisco        → Get weather for a location
/remind 5m Check the oven     → Set a reminder
```

## Creating commands

Commands are defined in a TypeScript file (typically `src/commands.ts`):

```ts src/commands.ts theme={null}
import type { BotCommand } from '@towns-protocol/bot'

export const commands = [
  {
    name: 'help',
    description: 'Show available bot commands'
  },
  {
    name: 'ping',
    description: 'Check if bot is responding'
  },
  {
    name: 'greet',
    description: 'Greet a user'
  },
  {
    name: 'weather',
    description: 'Get current weather for a location'
  }
] as const satisfies BotCommand[]

export default commands
```

## Handling Commands

You can use `bot.onSlashCommand()` to handle each slash command:

```ts src/index.ts theme={null}
import { makeTownsBot } from '@towns-protocol/bot'
import commands from './commands'

const bot = await makeTownsBot(appPrivateData, jwtSecret, { commands })

// Handle /help command
bot.onSlashCommand('help', async (handler, event) => {
  const commandList = commands
    .map(cmd => `• \`/${cmd.name}\` - ${cmd.description}`)
    .join('\n')

  await handler.sendMessage(
    event.channelId,
    `**Available Commands**\n${commandList}`
  )
})

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

### Working with Arguments

Slash commands can receive arguments after the command name.

You can get them from the `args` property of the event.

```ts theme={null}
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>\nExample: /weather San Francisco'
    )
    return
  }

  const weather = await callWeatherAPI(location)
  await handler.sendMessage(
    channelId,
    `Weather in ${location}: ${weather.temp}°F, ${weather.conditions}`
  )
})
```

### Working with Mentions

Users can mention other users or the bot in slash commands.

You can get the mentions from the `mentions` property of the event.

```ts theme={null}
// /greet @john @jane
bot.onSlashCommand('greet', async (handler, { channelId, mentions}) => {
  if (mentions.length === 0) {
    await handler.sendMessage(
      channelId,
      'Usage: /greet @user1 @user2 ...'
    )
    return
  }

  const greetings = mentions
    .map(m => `Hello <@${m.userId}>!`)
    .join('\n')

  await handler.sendMessage(channelId, greetings, { mentions })
})
```

### Checking Permissions

You may want to check if the user has the necessary permissions to use a command.

You can use the `hasAdminPermission` method to check if the user is an admin.
This is a shortcut for checking if the user has the `ModifyBanning` permission.

If you want to check for a specific permission, you can use the `checkPermission` method.

```ts theme={null}
bot.onSlashCommand('ban', async (handler, { userId, spaceId, channelId, mentions }) => {
  // Check if user is admin
  const isAdmin = await handler.hasAdminPermission(userId, spaceId)
  if (!isAdmin) {
    await handler.sendMessage( channelId, '❌ Only admins can use this command')
    return
  }
  const userToBan = mentions[0]?.userId
  if (!userToBan) {
    await handler.sendMessage( channelId, 'Usage: /ban @user')
    return
  }
  try {
    await handler.ban(userToBan, spaceId)
    await handler.sendMessage(
      channelId,
      `✓ Banned <@${userToBan}>`
    )
  } catch {
    await handler.sendMessage( channelId, '❌ Failed to ban user')
  }
})
```

## Paid Commands

You can create commands that require USDC payments. Payments are processed through the [x402 protocol v2](https://x402.org), supporting multi-chain payments and reusable sessions.

> Note: This bot flow uses a **facilitator** to verify and settle payments after a user signs an authorization inside Towns. It does **not** use the standard x402 HTTP middleware headers (`PAYMENT-SIGNATURE` / `PAYMENT-RESPONSE`) because Towns bots are not HTTP endpoints.

### Defining Paid Commands

Add a `paid` property to your command definition with a price in USDC:

```ts src/commands.ts theme={null}
import type { BotCommand } from '@towns-protocol/bot'

export const commands = [
  {
    name: 'generate',
    description: 'Generate AI content',
    paid: { price: '$0.20' }
  }
] as const satisfies BotCommand[]

export default commands
```

### How it works

When a user invokes a paid command:

1. Bot sends a signature request for USDC transfer authorization
2. User signs the request in their wallet
3. Payment is verified and settled via the x402 facilitator
4. Your command handler executes after successful payment

### Configuring the Facilitator

Configure which x402 facilitator the bot will call for `verify`/`settle`:

```ts src/index.ts theme={null}
const bot = await makeTownsBot(
  process.env.APP_PRIVATE_DATA!,
  process.env.JWT_SECRET!,
  {
    commands,
    paymentConfig: {
      // Public facilitator (recommended default):
      url: 'https://www.x402.org/facilitator',
      // Optional: some facilitators require an API key
      // apiKey: process.env.X402_API_KEY,
    },
  }
)
```

If you’re using Coinbase/CDP’s hosted facilitator, set:

```ts theme={null}
paymentConfig: {
  url: 'https://api.cdp.coinbase.com/platform/v2/x402',
  // apiKey: process.env.X402_API_KEY,
}
```

```ts theme={null}
// Handler only runs after payment succeeds
bot.onSlashCommand('generate', async (handler, event) => {
  // Payment already processed at this point
  await handler.sendMessage(
    event.channelId,
    'Thank you for your purchase! Here is your generated content...'
  )
})
```

### Sessions (Pay Once, Use Many)

Enable session-based access to reduce friction for users who call your command frequently. With sessions, users pay once and can use the command multiple times within a time window.

```ts src/commands.ts theme={null}
export const commands = [
  {
    name: 'generate',
    description: 'Generate AI content',
    paid: {
      price: '$1.00',
      session: {
        duration: 3600,  // 1 hour session
        maxUses: 10      // Optional: limit uses per session
      }
    }
  }
] as const satisfies BotCommand[]
```

When a user with an active session invokes the command:

* The command executes immediately without payment
* Session usage is tracked and displayed to the user
* After session expires or uses are exhausted, payment is required again

### Multi-Chain Support

Accept payments on multiple blockchain networks. The x402 v2 protocol uses [CAIP-2](https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md) network identifiers.

```ts src/commands.ts theme={null}
import { EVM_NETWORKS } from '@towns-protocol/bot'

export const commands = [
  {
    name: 'premium',
    description: 'Premium feature',
    paid: {
      price: '$5.00',
      networks: [
        EVM_NETWORKS.base,           // eip155:8453
        EVM_NETWORKS['base-sepolia'], // eip155:84532
        EVM_NETWORKS.ethereum,        // eip155:1
        EVM_NETWORKS.arbitrum,        // eip155:42161
      ]
    }
  }
] as const satisfies BotCommand[]
```

**Supported Networks:**

| Network          | CAIP-2 Identifier |
| ---------------- | ----------------- |
| Base             | `eip155:8453`     |
| Base Sepolia     | `eip155:84532`    |
| Ethereum         | `eip155:1`        |
| Ethereum Sepolia | `eip155:11155111` |
| Arbitrum One     | `eip155:42161`    |
| Optimism         | `eip155:10`       |
| Polygon          | `eip155:137`      |

> Note: Solana CAIP-2 identifiers are part of the broader x402 v2 ecosystem, but the Towns bot paid-command flow currently supports **EVM networks** only.

### Custom Payment Recipient

By default, payments go to your bot's app address. You can specify a different recipient:

```ts src/commands.ts theme={null}
export const commands = [
  {
    name: 'donate',
    description: 'Donate to charity',
    paid: {
      price: '$10.00',
      payTo: '0x742d35Cc6634C0532925a3b844Bc9e7595f...' // Custom recipient
    }
  }
] as const satisfies BotCommand[]
```

## Updating Commands

Commands are automatically synced with Towns when your bot starts.

To update your bot's slash commands:

1. Modify your command definitions in `src/commands.ts`
2. Restart your bot

The new commands will be automatically registered and appear in Towns' autocomplete.

## Next Steps

* Learn about [handling other events](/build/bots/events)
* Explore [message formatting and attachments](/build/bots/events#sending-attachments)
* See [permission-based features](/build/bots/events#permissions)
