Forms with Buttons
Create interactive UI elements like buttons for games, polls, and confirmations.Copy
Ask AI
import { hexToBytes } from 'viem'
bot.onSlashCommand('play', async (handler, event) => {
await handler.sendInteractionRequest(
event.channelId,
{
case: 'form',
value: {
id: 'game-menu',
title: '🎮 Game Menu',
subtitle: 'Choose your action:',
components: [
{
id: 'start-button',
component: {
case: 'button',
value: { label: '▶️ Start Game' }
}
},
{
id: 'help-button',
component: {
case: 'button',
value: { label: '❓ Help' }
}
}
]
}
},
hexToBytes(event.userId as `0x${string}`) // recipient
)
})
Handling Button Clicks
Copy
Ask AI
bot.onInteractionResponse(async (handler, event) => {
if (event.response.payload.content?.case !== 'form') return
const form = event.response.payload.content?.value
for (const component of form.components) {
if (component.component.case === 'button') {
if (component.id === 'start-button') {
await handler.sendMessage(event.channelId, '🎮 Starting game...')
} else if (component.id === 'help-button') {
await handler.sendMessage(event.channelId, '❓ Help...')
}
}
}
})
Transaction Requests
Prompt users to sign and execute blockchain transactions. Perfect for payments, NFT minting, token swaps, and contract interactions. Any Wallet:Copy
Ask AI
bot.onSlashCommand('send-usdc', async (handler, event) => {
const usdcAddress = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' // Base
const recipient = '0x1234567890123456789012345678901234567890'
const amount = '50000000' // 50 USDC (6 decimals)
// Encode ERC20 transfer: transfer(address,uint256)
const recipientPadded = recipient.slice(2).padStart(64, '0')
const amountPadded = parseInt(amount).toString(16).padStart(64, '0')
const data = `0xa9059cbb${recipientPadded}${amountPadded}`
await handler.sendInteractionRequest(event.channelId, {
case: 'transaction',
value: {
id: 'usdc-transfer',
title: 'Send USDC',
subtitle: 'Send 50 USDC to recipient',
content: {
case: 'evm',
value: {
chainId: '8453',
to: usdcAddress,
value: '0',
data: data,
signerWallet: undefined // User chooses wallet
}
}
}
})
})
Copy
Ask AI
import { getSmartAccountFromUserId } from '@towns-protocol/bot'
bot.onSlashCommand('send-usdc-sm', async (handler, event) => {
const smartAccount = await getSmartAccountFromUserId(bot, {
userId: event.userId
})
if (!smartAccount) {
await handler.sendMessage(event.channelId, "No smart account found")
return
}
// ... same transaction setup ...
await handler.sendInteractionRequest(event.channelId, {
case: 'transaction',
value: {
// ...
content: {
case: 'evm',
value: {
// ...
signerWallet: smartAccount // Only this wallet can sign
}
}
}
})
})
Handle Transaction Response
Copy
Ask AI
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!
Request ID: ${txData.requestId}
Transaction Hash: \`${txData.txHash}\`
View on explorer: https://basescan.org/tx/${txData.txHash}`
)
}
})
Signature Requests
Request cryptographic signatures without executing transactions. Perfect for authentication, permissions, off-chain agreements, and gasless interactions.Copy
Ask AI
import { InteractionRequestPayload_Signature_SignatureType } from '@towns-protocol/proto'
bot.onSlashCommand('sign', async (handler, event) => {
// EIP-712 Typed Data Structure
const typedData = {
domain: {
name: 'My Towns Bot',
version: '1',
chainId: 8453,
verifyingContract: '0x0000000000000000000000000000000000000000'
},
types: {
Message: [
{ name: 'from', type: 'address' },
{ name: 'content', type: 'string' },
{ name: 'timestamp', type: 'uint256' }
]
},
primaryType: 'Message',
message: {
from: event.userId,
content: 'I agree to the terms',
timestamp: Math.floor(Date.now() / 1000)
}
}
await handler.sendInteractionRequest(event.channelId, {
case: 'signature',
value: {
id: 'message-signature',
title: 'Sign Message',
subtitle: `Sign: "${typedData.message.content}"`,
chainId: '8453',
data: JSON.stringify(typedData),
type: InteractionRequestPayload_Signature_SignatureType.TYPED_DATA,
signerWallet: undefined // User chooses wallet
}
})
})
Handle Signature Response
Copy
Ask AI
bot.onInteractionResponse(async (handler, event) => {
if (event.response.payload.content?.case === 'signature') {
const signatureData = event.response.payload.content.value
await handler.sendMessage(
event.channelId,
`✅ Signature Received!
Request ID: ${signatureData.requestId}
Signature:
\`\`\`
${signatureData.signature}
\`\`\`
You can now verify this signature on-chain or use it for authentication.`
)
}
})
Complete Response Handler
Copy
Ask AI
bot.onInteractionResponse(async (handler, event) => {
const { response } = event
switch (response.payload.content?.case) {
case 'form':
const formData = response.payload.content.value
// Handle button clicks
for (const component of formData.components) {
if (component.component.case === 'button') {
// Route based on component.id
}
}
break
case 'transaction':
const txData = response.payload.content.value
await handler.sendMessage(
event.channelId,
`Transaction confirmed: ${txData.txHash}`
)
break
case 'signature':
const signatureData = response.payload.content.value
await handler.sendMessage(
event.channelId,
`Signature received: ${signatureData.signature}`
)
break
}
})