Module 7

Interactive Features & Web3

Build rich interactive experiences with buttons, forms, transaction requests, signatures, and miniapps. Learn how to integrate Web3 functionality and create engaging bot experiences.

5 sections
Advanced

Learning Objectives

By the end of this module, you will be able to:

Create interactive buttons and forms for user input
Request blockchain transactions from users
Collect signatures for authentication and verification
Embed miniapps for rich experiences
Execute batch operations for complex workflows
Get user smart account addresses for Web3 operations
1

Interactive Buttons & Forms

Create buttons and forms that users can click and interact with directly in chat.

Breaking Change - SDK 408+

The sendInteractionRequest API changed in SDK 408. The recipient parameter moved from inside the object to the 3rd parameter. Make sure you're using SDK 408+!

Sending Interactive Buttons

Send images and videos from public URLs:

Interactive Buttons Example
import { hexToBytes } from 'viem'

bot.onSlashCommand('game', async (handler, event) => {
  // Send interactive buttons to the user
  await handler.sendInteractionRequest(
    event.channelId,
    {
      case: "form",
      value: {
        id: "game-actions",
        title: "🎮 Choose Your Action",
        subtitle: "What will you do?",
        components: [
          {
            id: "attack-btn",
            component: {
              case: "button",
              value: { label: "⚔️ Attack" }
            }
          },
          {
            id: "defend-btn",
            component: {
              case: "button",
              value: { label: "🛡️ Defend" }
            }
          },
          {
            id: "heal-btn",
            component: {
              case: "button",
              value: { label: "❤️ Heal" }
            }
          }
        ]
      }
    },
    hexToBytes(event.userId as `0x${string}`)  // Recipient is 3rd parameter (SDK 408+)
  )
})

// Handle button clicks
bot.onInteractionResponse(async (handler, event) => {
  const { response } = event

  if (response.payload.content?.case !== "form") {
    return
  }

  const formResponse = response.payload.content?.value

  for (const component of formResponse.components) {
    if (component.component.case === "button") {
      const buttonId = component.id

      switch(buttonId) {
        case "attack-btn":
          await handler.sendMessage(event.channelId, "⚔️ You attacked for 10 damage!")
          break
        case "defend-btn":
          await handler.sendMessage(event.channelId, "🛡️ You raised your shield!")
          break
        case "heal-btn":
          await handler.sendMessage(event.channelId, "❤️ You healed 5 HP!")
          break
      }
    }
  }
})

Button Best Practices

  • • Keep button labels short (max ~25 characters)
  • • Don't use emojis in button labels (use them in title/subtitle instead)
  • • Each button needs a unique ID
  • • Store game/form state externally (Map or database)
  • • Always validate response.payload.content?.case === "form"
2

Transaction Requests

Request users to sign and execute blockchain transactions for payments, NFTs, and more.

Get User's Smart Account

First, get the user's smart account address to interact with their wallet.

Get Smart Account Address
import { getSmartAccountFromUserId } from '@towns-protocol/bot'

bot.onSlashCommand('wallet', async (handler, event) => {
  // Get user's smart account (wallet) address
  const walletAddress = await getSmartAccountFromUserId(bot, {
    userId: event.userId
  })

  await handler.sendMessage(
    event.channelId,
    `Your wallet address: ${walletAddress}`
  )
})

Request Transaction

Prompt users to sign transactions for payments, swaps, or contract interactions.

Transaction Request Example
bot.onSlashCommand('tip', async (handler, event) => {
  const amount = event.args[0] || "0.001"
  const recipient = event.mentions[0]?.userId || bot.botId

  // Request ETH transfer transaction
  await handler.sendInteractionRequest(event.channelId, {
    case: "transaction",
    value: {
      id: "tip-transaction",
      title: "Send Tip",
      subtitle: `Send ${amount} ETH to user`,
      content: {
        case: "evm",
        value: {
          chainId: "8453", // Base mainnet
          to: recipient,
          value: (parseFloat(amount) * 1e18).toString(), // Convert to wei
          data: "0x", // No data for simple transfer
          signerWallet: undefined, // Any wallet (user chooses)
        }
      }
    }
  })
})

// Handle transaction response
bot.onInteractionResponse(async (handler, event) => {
  if (event.response.payload.content?.case === "transaction") {
    const txData = event.response.payload.content.value

    await handler.sendMessage(
      event.channelId,
      `✅ Transaction confirmed!

Hash: \`${txData.txHash}\`
View on Basescan: https://basescan.org/tx/${txData.txHash}`
    )
  }
})

Transaction Use Cases

  • • 💸 Request payments from users
  • • 🎨 NFT minting flows
  • • 🔄 Token swaps and DeFi interactions
  • • 🎮 In-game purchases
  • • 📝 Contract deployments
3

Signature Requests

Request cryptographic signatures for authentication, agreements, and gasless operations.

EIP-712 Typed Signatures

Request structured signatures that are human-readable and secure.

Signature Request Example
import { InteractionRequestPayload_Signature_SignatureType } from "@towns-protocol/proto"

bot.onSlashCommand('verify', async (handler, event) => {
  // EIP-712 Typed Data
  const typedData = {
    domain: {
      name: "My Bot",
      version: "1",
      chainId: 8453, // Base
      verifyingContract: "0x0000000000000000000000000000000000000000"
    },
    types: {
      Message: [
        { name: "from", type: "address" },
        { name: "content", type: "string" },
        { name: "timestamp", type: "uint256" }
      ]
    },
    primaryType: "Message",
    message: {
      from: event.userId,
      content: "I verify my account ownership",
      timestamp: Math.floor(Date.now() / 1000)
    }
  }

  await handler.sendInteractionRequest(event.channelId, {
    case: "signature",
    value: {
      id: "verify-signature",
      title: "Verify Account",
      subtitle: "Sign to prove you own this account",
      chainId: "8453",
      data: JSON.stringify(typedData),
      type: InteractionRequestPayload_Signature_SignatureType.TYPED_DATA,
      signerWallet: undefined // Any wallet
    }
  })
})

// Handle signature response
bot.onInteractionResponse(async (handler, event) => {
  if (event.response.payload.content?.case === "signature") {
    const sigData = event.response.payload.content.value

    // You can now verify this signature on-chain or off-chain
    await handler.sendMessage(
      event.channelId,
      `✅ Account verified!

Signature: \`${sigData.signature.slice(0, 20)}...\``
    )
  }
})

Signature Use Cases

  • • 🔐 Authentication and login
  • • ✅ Agreement verification
  • • 🎫 Ticket validation
  • • 📋 Gasless permissions
  • • 🤝 Off-chain commitments
4

Miniapps

Embed interactive web applications directly in Towns chat.

Sending Miniapp Attachments

Launch web apps that users can interact with inside Towns.

Miniapp Example
bot.onSlashCommand('app', async (handler, event) => {
  const publicUrl = process.env.PUBLIC_URL || 'https://my-app.com'
  const cacheBust = Date.now() // Prevent caching during dev

  await handler.sendMessage(event.channelId, 'Launch My App', {
    attachments: [{
      type: 'miniapp',  // Must be 'miniapp', NOT 'link'
      url: `${publicUrl}/miniapp?v=${cacheBust}`
    }]
  })
})

Miniapp HTML Template

Your miniapp HTML must include specific meta tags and SDK integration.

miniapp.html
<!DOCTYPE html>
<html>
<head>
    <!-- REQUIRED: Miniapp metadata -->
    <meta name="fc:miniapp" content='{
      "version":"1",
      "imageUrl":"https://my-app.com/preview.png",
      "button":{
        "title":"Launch App",
        "action":{
          "type":"launch_miniapp",
          "name":"My App",
          "url":"https://my-app.com",
          "splashImageUrl":"https://my-app.com/splash.png",
          "splashBackgroundColor":"#667eea"
        }
      }
    }' />
</head>
<body>
    <script type="module">
        import { sdk } from 'https://esm.sh/@farcaster/miniapp-sdk@0.2.3'

        async function init() {
            // ALWAYS call ready() first
            await sdk.actions.ready()

            // Access Towns context
            const context = await sdk.context

            // Towns user data
            const userId = context.towns.user.userId
            const address = context.towns.user.address
            const spaceId = context.towns.spaceId
            const channelId = context.towns.channelId

            document.getElementById(&apos;user-id&apos;).textContent = userId
        }

        init()
    </script>

    <h1>Welcome to My Miniapp!</h1>
    <p>User ID: <span id="user-id">Loading...</span></p>
</body>
</html>

Miniapp Tips

  • • Always call sdk.actions.ready() before accessing context
  • • Use cache-busting during development: ?v=1766159421092
  • • Don't use emojis in button titles
  • • Test on both Alpha and Omega environments
  • • Must use HTTPS in production
5

Batch Operations

Execute multiple blockchain operations in a single atomic transaction.

Using execute() for Batch Operations

The execute function from ERC-7821 lets you batch multiple operations atomically.

Batch Operations Example
import { execute } from 'viem/experimental/erc7821'
import { parseEther } from 'viem'

bot.onSlashCommand(&apos;airdrop&apos;, async (handler, event) => {
  if (event.mentions.length === 0 || !event.args[0]) {
    await handler.sendMessage(
      event.channelId,
      &apos;Usage: /airdrop @user1 @user2 ... &lt;amount-each&gt;&apos;
    )
    return
  }

  const amountEach = parseEther(event.args[0])

  // Build batch calls - one tip per user
  const calls = event.mentions.map((mention) => ({
    to: tippingContractAddress,
    abi: tippingAbi,
    functionName: &apos;tip&apos;,
    value: amountEach,
    args: [
      {
        receiver: mention.userId,
        tokenId: tokenId,
        currency: ETH_ADDRESS,
        amount: amountEach,
        messageId: event.eventId,
        channelId: event.channelId,
      },
    ],
  }))

  // Execute all tips in one transaction
  const hash = await execute(bot.viem, {
    address: bot.appAddress,
    account: bot.viem.account,
    calls
  })

  await waitForTransactionReceipt(bot.viem, { hash })

  await handler.sendMessage(
    event.channelId,
    `Airdropped ${event.args[0]} ETH to ${event.mentions.length} users!
Transaction: ${hash}`
  )
})

Complex Multi-Step Operations

Combine approve, swap, stake, and more in one atomic transaction.

DeFi Combo Example
bot.onSlashCommand('defi-combo', async (handler, event) => {
  const amount = parseEther(event.args[0] || '100')

  // Approve + Swap + Stake in one transaction
  const hash = await execute(bot.viem, {
    address: bot.appAddress,
    account: bot.viem.account,
    calls: [
      {
        to: tokenAddress,
        abi: erc20Abi,
        functionName: 'approve',
        args: [dexAddress, amount]
      },
      {
        to: dexAddress,
        abi: dexAbi,
        functionName: 'swapExactTokensForTokens',
        args: [amount, minOut, [tokenIn, tokenOut], bot.appAddress]
      },
      {
        to: stakingAddress,
        abi: stakingAbi,
        functionName: 'stake',
        args: [amount]
      }
    ]
  })

  await waitForTransactionReceipt(bot.viem, { hash })

  await handler.sendMessage(
    event.channelId,
    `✅ Swapped and staked ${event.args[0]} tokens in one transaction!`
  )
})

Batch Benefits

  • • Atomic execution - all succeed or all fail
  • • Gas optimized - single transaction fee
  • • Perfect for airdrops, multi-step DeFi, bulk operations
  • • Works with any contract interaction
  • • Fund bot.appAddress for gas costs

Module 7 Complete!

Amazing! You've learned advanced interactive features including buttons, transactions, signatures, miniapps, and batch operations. Your bot can now create rich, interactive Web3 experiences!

What You've Learned:

Interactive buttons and forms
Transaction requests
Signature collection
Miniapp integration
Batch operations

You Can Now Build:

Games with interactive buttons
Payment and tipping systems
Authentication flows
Rich miniapp experiences
Complex DeFi operations