Towns 백서

Towns

원문

bitpro가 원문에서 글자만 모은 내용이에요. 표·그림·수식은 빠지거나 깨질 수 있어요. 전체 17쪽 중 2쪽

[render.com](https://render.com) and create an account (can use GitHub login) 2. **Create New Web Service** * Click "New +" → "Web Service" * Connect your GitHub account if prompted * Select your bot repository 3. **Configure the Service** Fill in the deployment settings: | Setting | Value | | ----------------- | --------------------------------- | | **Name** | `my-bot` (or any name you prefer) | | **Language** | `Node` | | **Build Command** | `bun install` | | **Start Command** | `bun run start` | 4. **Add Environment Variables** In the "Environment" section, you can either paste your `.env` file contents or add each variable manually. | Key | Value | | ------------------ | --------------------------------------- | | `APP_PRIVATE_DATA` | Your app private data from bot creation | | `JWT_SECRET` | Your JWT secret from bot creation | | `PORT` | `5123` | 5. **Deploy** Click "Create Web Service". Render will: * Clone your repository * Run `bun install` to build your bot * Start your bot with `bun run start` Wait for the deploy to finish. You'll get a URL like: ``` https://my-bot.onrender.com ``` <Note> Free tier services on Render may spin down after 15 minutes of inactivity. The first request after spin-down may take 30-60 seconds. Consider upgrading to a paid plan for production bots. </Note> ## Configure in Towns Now that your bot is deployed and publicly accessible, connect it to Towns. ### Set Webhook URL 1. Go back to [app.towns.com/developer](https://app.towns.com/developer) 2. Click on your bot 3. Under "Webhook URL", enter: ``` https://my-bot.onrender.com/webhook ``` <Warning> Don't forget the `/webhook` path at the end! </Warning> 4. Click "Save" ### Configure Forwarding Settings In the bot settings page, under "Forwarding Settings", choose which messages your bot receives. * **All Messages** - Bot receives every event in channels it's in * All event handlers available. See [Events](/build/bots/events) for more information. * Use for: AI agents, moderation bots, analytics, or when you need tip/membership events * **Mentions, Commands, Replies & Reactions** (Default) - Bot only receives: * Direct @mentions, replies to bot's messages, reactions, and slash commands * Available handlers: `onMessage` (filtered), `onSlashCommand`, `onReaction`, `onMessageEdit`, `onRedaction` * NOT available: `onTip`, `onChannelJoin`, `onChannelLeave`, `onEventRevoke` * Use for: Most interactive bots * **No Messages** - Bot receives no message events * Use for: External-only bots (e.g., GitHub webhooks, scheduled tasks) Click "Save" after selecting your preference. ### Install Bot to a Space Finally, install your bot to a space to start using it: 1. In Towns app, go to a space you own or admin 2. Go to Space Settings → Bots 3. Search for your bot by username 4. Click "Install" 5. Grant permissions as needed Your bot is now live! 🎉 ## Understanding Your Bot's Wallet Architecture Your bot has two wallets that work together: ### Bot Treasury Wallet The **Bot Treasury Wallet** (`bot.appAddress`) is a SimpleAccount (ERC-4337) smart contract that holds your bot's funds: * **Receives funds**: All tips and payments sent to your bot are stored here * **Stores assets**: This is where ETH and tokens are held * **Source for payments**: When your bot sends tips to users, funds come from this wallet To fund your bot treasury, send Base ETH directly to `bot.appAddress` from any wallet. ### Gas Wallet The **Gas Wallet** (`bot.viem.account`) pays for gas fees on bot transactions: * **Signs transactions**: Authorizes all operations on behalf of the bot treasury * **Pays gas fees**: Needs Base ETH to cover transaction costs * **Fund this wallet**: Required for any blockchain transactions <Warning> Your gas wallet MUST be funded with Base ETH to execute any blockchain transactions (sending tips, banning users, etc.). Without gas, your bot cannot perform onchain operations. </Warning> ### When to Fund Each **Fund your Gas Wallet when:** * Your bot needs to send tips to users * Your bot performs moderation actions (ban/unban) * Your bot interacts with smart contracts * You see "insufficient funds for gas" errors **Fund your Bot Treasury when:** * Your bot needs to send tips from its treasury * You want to receive payments and tips * Your bot needs to hold ETH/tokens for operations <Note> The gas wallet credentials are provided in `APP_PRIVATE_DATA` during bot creation. You can extract the wallet address from this value to fund it. </Note> ## Testing Your Bot Try these to verify everything works: In the channel you installed your bot to, mention your bot: ``` @mybot hello ``` The bot should respond with "Hello there! 👋" ## Next Steps Now that your bot is running, explore more capabilities: * [Local development](/build/bots/local-development) - Learn how to develop bots locally to fast iterate * [Learn about event handlers](/build/bots/events) - Handle different event types * [Add slash commands](/build/bots/slash-commands) - Create custom `/help`, `/poll`, etc. * [External interactions](/build/bots/external-interactions) - Timers, webhooks, custom APIs # Interactions Source: https://docs.towns.com/build/bots/interactions Interactions let users interact with your bot through forms, buttons, text inputs, transaction requests, and signature requests. ## Sending Interactions Use `handler.sendInteractionRequest()` to send interactive UI elements to users. The method takes a channel ID and a payload object that defines the interaction type. ```ts theme={null} await handler.sendInteractionRequest(channelId, payload) ``` ### Forms (Buttons & Text Inputs) Forms display buttons and text inputs that users can interact with. Use forms for menus, confirmations, polls, feedback collection, and multi-step flows. Set `recipient` to target a specific user, or omit it for public interactions anyone can respond to. ```ts theme={null} await handler.sendInteractionRequest(event.channelId, { type: 'form', id: 'my-form', components: [ { id: 'name', type: 'textInput', placeholder: 'Enter name...' }, { id: 'confirm', type: 'button', label: 'Submit' }, { id: 'cancel', type: 'button', label: 'Cancel' } ], recipient: event.userId // Optional: omit for public interactions }) ``` **Component Types:** | Type | Properties | | ----------- | ---------------------------------------- | | `button` | `id`, `type: 'button'`, `label` | | `textInput` | `id`, `type: 'textInput'`, `placeholder` | ### Transaction Requests Prompt users to sign and execute blockchain transactions from their wallets. Use transactions for payments, token transfers, NFT minting, contract interactions, and DeFi operations. Set `tx.signerWallet` to require a specific wallet, or omit it to let users choose. ```ts theme={null} import { encodeFunctionData, erc20Abi, parseUnits } from 'viem' await handler.sendInteractionRequest(event.channelId, { type: 'transaction', id: 'token-transfer', title: 'Send Tokens', subtitle: 'Transfer 50 USDC', tx: { chainId: '8453', to: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', value: '0', data: encodeFunctionData({ abi: erc20Abi, functionName: 'transfer', args: [recipientAddress, parseUnits('50', 6)] }), signerWallet: smartAccountAddress // Optional: omit to let user choose wallet }, recipient: event.userId }) ``` ### Signature Requests Request cryptographic signatures without executing transactions. Use signatures for authentication, permissions, off-chain agreements, and gasless interactions. Supports EIP-712 typed data (`'typed_data'`) and personal sign (`'personal_sign'`). ```ts theme={null} await handler.sendInteractionRequest(event.channelId, { type: 'signature', id: 'agreement', title: 'Sign Agreement', subtitle: 'I agree to the terms', chainId: '8453', data: JSON.stringify({ domain: { name: 'My Bot', version: '1', chainId: 8453, verifyingContract: '0x0000000000000000000000000000000000000000' }, types: { Message: [{ name: 'content', type: 'string' }] }, primaryType: 'Message', message: { content: 'I agree to the terms' } }), method: 'typed_data', signerWallet: event.userId, recipient: event.userId }) ``` ## Handling Responses Use `bot.onInteractionResponse()` to handle user interactions. This callback fires whenever a user clicks a button, submits a form, completes a transaction, or signs a message. **Available properties:** * `event.userId` - The user who responded * `event.channelId` - Channel where interaction occurred * `event.response.payload.content?.case` - Type: `'form'`, `'transaction'`, or `'signature'` * `event.response.payload.content?.value` - Response data ```ts theme={null} bot.onInteractionResponse(async (handler, event) => { const { response } = event switch (response.payload.content?.case) { case 'form': { const form = response.payload.content.value for (const component of form.components) { if (component.component.case === 'button') { // Button clicked: component.id } if (component.component.case === 'textInput') { const value = component.component.value.value // Text input: component.id = value } } break } case 'transaction': { const tx = response.payload.content.value if (tx.txHash) { await handler.sendMessage(event.channelId, `Success: ${tx.txHash}`) } else if (tx.error) { await handler.sendMessage(event.channelId, `Failed: ${tx.error}`) } break } case 'signature': { const sig = response.payload.content.value if (sig.signature) { await handler.sendMessage(event.channelId, 'Signed successfully') } else if (sig.error) { await handler.sendMessage(event.channelId, `Failed: ${sig.error}`) } break } } }) ``` ## Complete Example: Poll ```ts theme={null} const polls = new Map<string, { yes: number; no: number; voters: Set<string> }>() bot.onSlashCommand('poll', async (handler, event) => { const pollId = `poll-${Date.now()}` polls.set(pollId, { yes: 0, no: 0, voters: new Set() }) await handler.sendInteractionRequest(event.channelId, { type: 'form', id: pollId, components: [ { id: 'yes', type: 'button', label: 'Yes' }, { id: 'no', type: 'button', label: 'No' } ] // No recipient = public poll }) }) bot.onInteractionResponse(async (handler, event) => { if (event.response.payload.content?.case !== 'form') return const form = event.response.payload.content.value const poll = polls.get(form.id) if (!poll) return // Prevent duplicate votes if (poll.voters.has(event.userId)) return poll.voters.add(event.userId) for (const c of form.components) { if (c.component.case === 'button') { if (c.id === 'yes') poll.yes++ if (c.id === 'no') poll.no++ } } await handler.sendMessage(event.channelId, `Yes: ${poll.yes}, No: ${poll.no}`) }) ``` ## Reference ### Form Request | Field | Type | Required | Description | | ------------ | -------- | -------- | ------------------------------------- | | `type` | `'form'` | Yes | Interaction type | | `id` | string | Yes | Unique ID for matching responses | | `components` | array | Yes | Buttons and text inputs | | `recipient` | string | No | Target user address (omit for public) | ### Transaction Request | Field | Type | Required | Description | | ----------------- | --------------- | -------- | -------------------------------------- | | `type` | `'transaction'` | Yes | Interaction type | | `id` | string | Yes | Unique ID for matching responses | | `title` | string | Yes | Heading shown to user | | `subtitle` | string | Yes | Description of transaction | | `tx.chainId` | string | Yes | Chain ID (e.g., `'8453'` for Base) | | `tx.to` | string | Yes | Contract or recipient address | | `tx.value` | string | Yes | ETH amount in wei | | `tx.data` | string | Yes | Encoded function call | | `tx.signerWallet` | string | No | Required wallet (omit for user choice) | | `recipient` | string | No | Target user address | ### Signature Request | Field | Type | Required | Description | | -------------- | ------------- | -------- | ----------------------------------- | | `type` | `'signature'` | Yes | Interaction type | | `id` | string | Yes | Unique ID for matching responses | | `chainId` | string | Yes | Chain ID | | `data` | string | Yes | JSON stringified EIP-712 typed data | | `method` | string | Yes | `'typed_data'` or `'personal_sign'` | | `signerWallet` | string | Yes | Wallet address to sign | | `title` | string | No | Heading shown to user | | `subtitle` | string | No | Description | | `recipient` | string | No | Target user address | ## Next Steps * Learn about [onchain integrations](/build/bots/onchain-integrations) for bot blockchain operations * Explore [slash commands](/build/bots/slash-commands) * Read about [event handlers](/build/bots/events) # Introduction Source: https://docs.towns.com/build/bots/introduction Bots in Towns Protocol are programmable applications that can interact with spaces, channels, and users through end-to-end encrypted messaging. They enable automation, integrations, and enhanced user experiences within Towns communities. ## Architecture Towns bots consist of two interconnected pieces: ### Onchain Component Bots are smart contracts registered through the **App Registry** on Base and installed in a `Space`. The onchain component provides: * **Secure identity**: Each bot has a unique address and public key * **Fine-grained permissions**: Control exactly what the bot can do (read/write messages, ban users, moderate, etc.) * **Recurring revenue**: Bots can be paid monthly or yearly by spaces that install them * **ERC-4337 Account Abstraction**: Bots use SimpleAccount contracts enabling advanced onchain interactions * **Batch execution support**: Execute multiple contract calls atomically using ERC-7821 This allows bots to: * Have their own app contract (treasury) for receiving and storing ETH/tokens * Have a separate wallet for signing transactions and paying gas fees * Interact with any smart contract (DeFi, NFTs, DAOs, etc.) * Execute complex multi-step transactions in one atomic operation * Receive payments and tips onchain ### Server Component The server component is a webhook endpoint that receives events from Towns Protocol nodes and processes them. A bot operates like a specialized user in the Towns Protocol network. It can: * **Send and receive messages** with full end-to-end encryption * **React to events** in real-time (messages, tips, joins, leaves) * **Execute slash commands** with custom logic * **Moderate channels** (ban/unban users with proper permissions) * **Check permissions** before executing sensitive operations * **Receive tips** just like regular users All of this with the same security guarantees as a user: **end-to-end encryption** for all messages. Additionally, the server can communicate with the onchain component, allowing it to: * Read from any smart contract * Interact with any smart contract using ERC-7821 batch execution * Execute complex DeFi operations (swaps, staking, liquidity provision) * Mint NFTs, manage DAOs, and interact with any Web3 protocol Your bot has two wallets: * **Bot Treasury Wallet** (`bot.appAddress`) - Holds funds and receives tips * **Gas Wallet** (`bot.viem.account`) - Signs transactions and pays for gas Learn more in the [Getting Started guide](/build/bots/getting-started#understanding-your-bots-wallet-architecture). ## Use Cases With these capabilities, you can build bots that: ### Communication & Engagement * AI assistants that answer questions using LLMs * Welcome bots that greet new members * Announcement bots for important updates * Poll and voting systems ### Moderation & Safety * Auto-moderation based on rules * Spam detection and prevention * Content filtering * User verification systems ### Onchain Integration * Tip tracking and leaderboards * Token price monitoring and alerts * NFT minting and distribution * DeFi operations (swaps, staking, yield farming) * DAO proposal notifications and voting ### External Services * GitHub integration for code updates * Calendar and meeting reminders * Weather and news updates * Custom webhooks from any service ## Next Steps Ready to build your first bot? Continue to the [Getting Started guide](/build/bots/getting-started) to create and deploy your bot in minutes. # Local Development Source: https://docs.towns.com/build/bots/local-development Testing your bot locally before deploying to production lets you iterate quickly, see logs in real-time, and debug issues efficiently. This guide will teach you how to set up a local development environment with ngrok. ## Prerequisites Before you begin, make sure you have: * A bot created in the [Developer Portal](https://app.towns.com/developer) * Your bot project set up locally with dependencies installed * Your `.env` file configured with `APP_PRIVATE_DATA` and `JWT_SECRET` ## Using ngrok for Local Testing ngrok creates a secure tunnel from a public URL to your local development server, allowing Towns to send webhook events to your bot running on `localhost`. ### 1. Start Your Bot Locally Run your bot in development mode: ```bash theme={null} bun run dev ``` Your bot will start on `http://localhost:5123` (or the port specified in your `.env` file). ### 2. Install and Start ngrok Follow the [ngrok installation guide](https://ngrok.com/download) to install ngrok for your platform. Once installed, start ngrok to forward port 5123 (or the port specified in your `.env` file): ```bash theme={null} ngrok http 5123 ``` You'll see output like this: ``` Forwarding https://abc123.ngrok-free.app -> http://localhost:5123 ``` Copy the HTTPS URL (e.g., `https://abc123.ngrok-free.app`). This is your public-facing URL that Towns will use to send events to your bot. <Note> The free tier of ngrok provides a random URL each time you restart. You can use a paid plan to get a consistent URL. </Note> ### 3. Configure Webhook URL Update your bot's webhook URL to point to your ngrok tunnel: 1. Go to [app.towns.com/developer](https://app.towns.com/developer) 2. Click on your bot 3. Under "Webhook URL", enter your ngrok URL with the `/webhook` path: ``` https://abc123.ngrok-free.app/webhook ``` 4. Click "Save" ### 4. Test Your Bot Now you're ready to test! Install your bot in a test space and interact with it: 1. In the Towns app, go to a space you own or admin 2. Go to Space Settings → Bots 3. Search for your bot and install it 4. Try sending messages, mentioning the bot, or using slash commands You'll see webhook events arrive in your local terminal in real-time, along with any console logs you've added for debugging. ## Debugging By default, your bot template includes request logging. You'll see each incoming webhook event in your console: ```bash theme={null} [2025-10-28 10:30:45] POST /webhook 200 - 45ms ``` You can also use `DEBUG=csb:bot bun run dev` to see more detailed logs about events in your local terminal. ## Switching Back to Production When you're ready to deploy, remember to: 1. Push your changes to GitHub 2. Deploy to your production hosting (e.g., Render) 3. Update your bot's webhook URL back to your production URL: ``` https://your-bot.onrender.com/webhook ``` <Warning> Don't forget to switch the webhook URL! Leaving it pointed at ngrok will break your bot once you close the ngrok tunnel. </Warning> ## Next Steps * [Learn about event handlers](/build/bots/events) - Handle different event types * [Add slash commands](/build/bots/slash-commands) - Create custom `/help`, `/poll`, etc. * [External interactions](/build/bots/external-interactions) - Timers, webhooks, custom APIs # Onchain Integrations Source: https://docs.towns.com/build/bots/onchain-integrations Towns bots are onchain apps with two blockchain capabilities: direct contract interactions using your bot's wallet, and requesting users to sign transactions or messages. ## Bot Wallet Architecture Your bot has two addresses: * **Gas w
프로젝트로 돌아가기