> ## Documentation Index
> Fetch the complete documentation index at: https://docs.actioncodes.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Telegram Bot

> Build a Telegram bot that accepts Action Codes

Telegram bots are a great use case for Action Codes — users can interact with blockchain actions directly in chat.

## The flow

1. User starts chat with your bot
2. Bot asks for an action code
3. User gets code from [actioncode.app](https://actioncode.app) and sends it
4. Bot attaches an action (transaction or message)
5. User approves in actioncode.app
6. Bot confirms completion

## Example bot

Using [grammY](https://grammy.dev/) (works similarly with other libraries):

```typescript theme={null}
import { Bot, Context } from 'grammy'
import { ActionCodesClient } from '@actioncodes/sdk'

const bot = new Bot(process.env.BOT_TOKEN!)
const client = new ActionCodesClient({
  authToken: process.env.ACTION_CODES_TOKEN
})

// /start command
bot.command('start', (ctx) => {
  ctx.reply(
    'Welcome! I can help you sign messages with your Solana wallet.\n\n' +
    'Commands:\n' +
    '/sign - Sign a message\n' +
    '/verify - Verify wallet ownership'
  )
})

// /sign command
bot.command('sign', async (ctx) => {
  await ctx.reply(
    'To sign a message:\n\n' +
    '1. Open actioncode.app in your Solana wallet\n' +
    '2. Connect and get your code\n' +
    '3. Send me the 8-digit code'
  )

  // Store state that we're expecting a code for signing
  await setUserState(ctx.from!.id, { action: 'sign' })
})

// Handle code input
bot.on('message:text', async (ctx) => {
  const text = ctx.message.text

  // Check if it looks like an action code (8 digits)
  if (!/^\d{8}$/.test(text)) {
    return // Not a code, ignore or handle other messages
  }

  const userState = await getUserState(ctx.from!.id)
  if (!userState?.action) {
    await ctx.reply('Send /sign first to start a signing session.')
    return
  }

  await ctx.reply('Verifying code...')

  try {
    // Resolve the code
    const actionCode = await client.resolve(text)

    await ctx.reply(
      `Code valid!\n` +
      `Wallet: ${actionCode.pubkey.slice(0, 8)}...\n\n` +
      `Attaching message for you to sign...`
    )

    // Create and attach message
    const message = `Signed via Telegram bot\nUser: @${ctx.from!.username}\nTime: ${new Date().toISOString()}`

    await client.attachMessage(text, message, {
      description: 'Telegram signature request'
    })

    await ctx.reply(
      'Message attached!\n\n' +
      'Now go to actioncode.app and approve the signing request.'
    )

    // Wait for signature
    for await (const status of client.observeStatus(text, { timeout: 120000 })) {
      if (status.signedMessage) {
        await ctx.reply(
          `Message signed!\n\n` +
          `Signature: ${status.signedMessage.slice(0, 20)}...`
        )
        await clearUserState(ctx.from!.id)
        return
      }
    }

    // If we get here, it timed out
    await ctx.reply('Code expired. Use /sign to try again.')

  } catch (error: any) {
    await ctx.reply(`Error: ${error.message}\n\nUse /sign to try again.`)
  }

  await clearUserState(ctx.from!.id)
})

// Start bot
bot.start()
```

## State management helpers

```typescript theme={null}
// Simple in-memory state (use Redis or DB in production)
const userStates = new Map<number, { action: string }>()

async function setUserState(userId: number, state: { action: string }) {
  userStates.set(userId, state)
}

async function getUserState(userId: number) {
  return userStates.get(userId)
}

async function clearUserState(userId: number) {
  userStates.delete(userId)
}
```

## Transaction example

For sending SOL:

```typescript theme={null}
bot.command('send', async (ctx) => {
  await ctx.reply(
    'To send SOL:\n\n' +
    '1. Reply with: <amount> <recipient>\n' +
    '   Example: 0.1 ABC123...\n\n' +
    '2. Then send your action code from actioncode.app'
  )
  await setUserState(ctx.from!.id, { action: 'send' })
})

// Handle "0.1 ABC123..." format
bot.hears(/^(\d+\.?\d*)\s+(\w{32,44})$/, async (ctx) => {
  const amount = parseFloat(ctx.match[1])
  const recipient = ctx.match[2]

  await setUserState(ctx.from!.id, {
    action: 'send',
    amount,
    recipient
  })

  await ctx.reply(
    `Ready to send ${amount} SOL to ${recipient.slice(0, 8)}...\n\n` +
    `Now send your 8-digit code from actioncode.app`
  )
})
```

## User experience tips

1. **Guide users clearly** — Not everyone knows what actioncode.app is
2. **Show progress** — Send messages at each step
3. **Handle timeouts gracefully** — Codes expire in \~2 minutes
4. **Confirm success** — Show the result clearly

```typescript theme={null}
// Good UX: Clear instructions
await ctx.reply(
  'Getting ready to sign!\n\n' +
  '1. Open your Solana wallet (Phantom, Solflare, etc.)\n' +
  '2. In the wallet browser, go to: actioncode.app\n' +
  '3. Tap "Get Code" and copy the 8 digits\n' +
  '4. Paste the code here\n\n' +
  'The code expires in 2 minutes, so do this quickly!'
)
```

## Live example

Try the official example bot: [@action\_codes\_bot](https://t.me/action_codes_bot)

Source code: [github.com/otaprotocol/telegram-bot-example](https://github.com/otaprotocol/telegram-bot-example)
