|
| 1 | +--- |
| 2 | +title: Build a Farcaster Bot for On-Chain Alerts |
| 3 | +description: Create a Farcaster bot that monitors blockchain transactions and posts human-readable casts |
| 4 | +sidebar: |
| 5 | + order: 2 |
| 6 | +--- |
| 7 | + |
| 8 | +import { Content as MemoryAbiLoader } from '../../components/memory-abi-loader.md' |
| 9 | +import { Content as MemoryContractLoader } from '../../components/memory-contract-loader.md' |
| 10 | +import { Steps } from '@astrojs/starlight/components' |
| 11 | + |
| 12 | +In this guide, you will learn how to create a Farcaster bot that sends human-readable alerts about transactions happening on-chain. You can customize this bot for any EVM-compatible blockchain, and you don't need any specific knowledge about EVM transaction decoding and interpretation. |
| 13 | + |
| 14 | +:::tip |
| 15 | +Jump to the repo to view the full code example [3loop/farcaster-onchain-alerts-bot](https://github.com/3loop/farcaster-onchain-alerts-bot) |
| 16 | +::: |
| 17 | + |
| 18 | +:::note |
| 19 | +This example tracks AAVE trades on Base Mainnet. You can easily adapt it to monitor any contract on any EVM chain. |
| 20 | +::: |
| 21 | + |
| 22 | + |
| 23 | + |
| 24 | +## Guide |
| 25 | + |
| 26 | +### Step 0: Prerequisites |
| 27 | + |
| 28 | +- Bun installed (see installation guide [here](https://bun.sh/docs/installation)) |
| 29 | +- Alchemy account (sign up [here](https://www.alchemy.com/)) |
| 30 | +- Basescan API Key (sign up [here](https://basescan.org/register)) |
| 31 | +- Farcaster account (can be yours or a separate one for your bot) |
| 32 | + |
| 33 | +### Step 1: Clone the Repository |
| 34 | + |
| 35 | +Clone the bot [repository](https://github.com/3loop/farcaster-onchain-alerts-bot) and install dependencies: |
| 36 | + |
| 37 | +```bash |
| 38 | +git clone https://github.com/3loop/farcaster-onchain-alerts-bot |
| 39 | +cd farcaster-onchain-alerts-bot |
| 40 | +bun i |
| 41 | +``` |
| 42 | + |
| 43 | +### Step 2: Configure Environment Variables |
| 44 | + |
| 45 | +Copy the `.env.example` file to `.env` and add your API keys: |
| 46 | + |
| 47 | +```bash |
| 48 | +cp .env.example .env |
| 49 | +vim .env |
| 50 | +``` |
| 51 | + |
| 52 | +For the Farcaster bot you need to specify: |
| 53 | + |
| 54 | +- `ALCHEMY_API_KEY` - Alchemy API key to monitor new transactions via WebSocket |
| 55 | +- `ETHERSCAN_API_KEY` - Basescan API key, used to fetch and cache ABIs |
| 56 | +- `ARCHIVE_RPC_URL` - Archive RPC URL for Base (required for transaction tracing) |
| 57 | +- `SIGNER_PRIVATE_KEY` and `ACCOUNT_FID` - Farcaster credentials (see Step 3) |
| 58 | + |
| 59 | +### Step 3: Create a Farcaster Account Key (Signer) |
| 60 | + |
| 61 | +A Farcaster signer is a separate Ed25519 public and private key pair connected to your Farcaster account that you need for posting messages on your behalf. To connect the key pair, you have to send a transaction from your Farcaster wallet to the Key Registry Farcaster smart contract. At the moment of writing this guide, there was no simple way to create and connect the signer without using 3rd party APIs. So we made a script to generate the required transaction, and to run it you need to do the following: |
| 62 | + |
| 63 | +<Steps> |
| 64 | + |
| 65 | +1. **Fund your Farcaster custody wallet on Optimism:**: You need some ETH on the Optimism chain to pay for the gas. A few dollars would be enough. Click on the 3 dots near your profile, press "About," and there you will find your custody address. |
| 66 | +2. **Get your Farcaster recovery phrase**: On your phone, go to settings -> advanced -> recovery phrase, and write this recovery phrase into the `MNEMONIC` variable in the `scripts/create-signer.ts` file. |
| 67 | +3. **Run the script**: Run the following command `bun run scripts/create-signer.ts`. The result of this script will be an Optimism transaction like [this](https://optimistic.etherscan.io/tx/0x9eecacefceb6f120c3ef50222eabb15d86fd5feac6dae3fdf09dccb7687c70d4), and a public and private key printed in the console. Do not share the private key. |
| 68 | +4. **Add env variables**: Add the private key generated from the script and the bot's account FID into the `SIGNER_PRIVATE_KEY` and `ACCOUNT_FID` variables. |
| 69 | + |
| 70 | +</Steps> |
| 71 | + |
| 72 | +### Step 4: Setup the Transaction Decoder |
| 73 | + |
| 74 | +Loop Decoder requires three components: an RPC provider, ABI store, and contract metadata store. Let's set up each one: |
| 75 | + |
| 76 | +#### RPC Provider |
| 77 | + |
| 78 | +Configure your RPC provider in `constants.ts` for Base Mainnet (chain ID 8453). We use `traceAPI: 'geth'` for transaction tracing: |
| 79 | + |
| 80 | +```ts title="src/constants.ts" |
| 81 | +export const RPC = { |
| 82 | + 8453: { |
| 83 | + archiveUrl: process.env.ARCHIVE_RPC_URL, |
| 84 | + traceAPI: 'geth', |
| 85 | + }, |
| 86 | +} |
| 87 | +``` |
| 88 | + |
| 89 | +```ts title="src/decoder/decoder.ts" |
| 90 | +const getPublicClient = (chainId: number) => { |
| 91 | + const rpc = RPC[chainId as keyof typeof RPC] |
| 92 | + if (!rpc) throw new Error(`Missing RPC provider for chain ID ${chainId}`) |
| 93 | + |
| 94 | + return { |
| 95 | + client: createPublicClient({ transport: http(rpc.archiveUrl) }), |
| 96 | + config: { traceAPI: rpc.traceAPI }, |
| 97 | + } |
| 98 | +} |
| 99 | +``` |
| 100 | + |
| 101 | +#### ABI Store |
| 102 | + |
| 103 | +Set up an in-memory ABI cache with Basescan and 4byte.directory strategies: |
| 104 | + |
| 105 | +<MemoryAbiLoader /> |
| 106 | + |
| 107 | +#### Contract Metadata Store |
| 108 | + |
| 109 | +Set up contract metadata resolution for token or NFT information (name, decimals, symbol): |
| 110 | + |
| 111 | +<MemoryContractLoader /> |
| 112 | + |
| 113 | +#### Create Decoder Instance |
| 114 | + |
| 115 | +Combine all components into a `TransactionDecoder` instance: |
| 116 | + |
| 117 | +```ts title="src/decoder/decoder.ts" |
| 118 | +import { TransactionDecoder } from '@3loop/transaction-decoder' |
| 119 | + |
| 120 | +const decoder = new TransactionDecoder({ |
| 121 | + getPublicClient, |
| 122 | + abiStore, |
| 123 | + contractMetaStore, |
| 124 | +}) |
| 125 | +``` |
| 126 | + |
| 127 | +### Step 5: Decode and Interpret Transactions |
| 128 | + |
| 129 | +With the decoder set up, you can now decode transactions and make them human-readable: |
| 130 | + |
| 131 | +```ts title="src/index.ts" |
| 132 | +// 1. Decode the transaction |
| 133 | +const decoded = await decoder.decodeTransaction({ |
| 134 | + chainID: CHAIN_ID, |
| 135 | + hash: txHash, |
| 136 | +}) |
| 137 | + |
| 138 | +// 2. Interpret it (make it human-readable) |
| 139 | +const interpreted = interpretTx(decoded) |
| 140 | + |
| 141 | +// 3. Use the result |
| 142 | +console.log(interpreted.action) // e.g., "Alice bought 5 shares of Bob for 0.1 ETH" |
| 143 | +``` |
| 144 | + |
| 145 | +View a [decoded AAVE transaction example](https://loop-decoder-web.vercel.app/interpret/1/0xc0bd04d7e94542e58709f51879f64946ff4a744e1c37f5f920cea3d478e115d7) in our playground. You can test the `interpretTx` function by pasting it into the Interpretation field. |
| 146 | + |
| 147 | +### Step 6: Monitor AAVE Transactions |
| 148 | + |
| 149 | +Set up real-time monitoring for AAVE trades. Configure the contract address in `constants.ts`: |
| 150 | + |
| 151 | +```ts title="src/constants.ts" |
| 152 | +export const CONTRACT_ADDRESS = '0xa238dd80c259a72e81d7e4664a9801593f98d1c5' |
| 153 | +export const CHAIN_ID = 8453 |
| 154 | +``` |
| 155 | + |
| 156 | +Subscribe to new transactions and process them: |
| 157 | + |
| 158 | +```ts title="src/index.ts" |
| 159 | +const wsClient = createPublicClient({ |
| 160 | + transport: webSocket(ALCHEMY_WS_RPC_URL), |
| 161 | +}) |
| 162 | + |
| 163 | +// Subscribe to AAVE transactions |
| 164 | +wsClient.transport.subscribe({ |
| 165 | + method: 'eth_subscribe', |
| 166 | + params: [ |
| 167 | + 'alchemy_minedTransactions', |
| 168 | + { |
| 169 | + addresses: [{ to: CONTRACT_ADDRESS }], |
| 170 | + includeRemoved: false, |
| 171 | + hashesOnly: true, |
| 172 | + }, |
| 173 | + ], |
| 174 | + onData: (data: any) => { |
| 175 | + const hash = data?.result?.transaction?.hash |
| 176 | + if (hash) handleTransaction(hash) |
| 177 | + }, |
| 178 | +}) |
| 179 | + |
| 180 | +// Process each transaction |
| 181 | +async function handleTransaction(txHash: string) { |
| 182 | + try { |
| 183 | + // 1. Decode |
| 184 | + const decoded = await decoder.decodeTransaction({ |
| 185 | + chainID: CHAIN_ID, |
| 186 | + hash: txHash, |
| 187 | + }) |
| 188 | + |
| 189 | + if (!decoded) return |
| 190 | + |
| 191 | + // 2. Interpret |
| 192 | + const interpreted = interpretTx(decoded) |
| 193 | + |
| 194 | + // 3. Format message |
| 195 | + const text = `New trade: ${interpreted.trader} ${interpreted.isBuy ? 'Bought' : 'Sold'} ${ |
| 196 | + interpreted.shareAmount |
| 197 | + } shares of ${interpreted.subject} for ${interpreted.price} ETH` |
| 198 | + |
| 199 | + // 4. Post to Farcaster |
| 200 | + await publishToFarcaster({ |
| 201 | + text, |
| 202 | + url: `https://basescan.org/tx/${txHash}`, |
| 203 | + }) |
| 204 | + } catch (e) { |
| 205 | + console.error(e) |
| 206 | + } |
| 207 | +} |
| 208 | +``` |
| 209 | + |
| 210 | +### Step 7: Publish to Farcaster |
| 211 | + |
| 212 | +Use the `@standard-crypto/farcaster-js-hub-rest` package to publish casts: |
| 213 | + |
| 214 | +```ts title="src/index.ts" |
| 215 | +async function publishToFarcaster(cast: { text: string; url: string }) { |
| 216 | + await client.submitCast( |
| 217 | + { |
| 218 | + text: cast.text, |
| 219 | + embeds: [{ url: cast.url }], |
| 220 | + }, |
| 221 | + Number(fid), |
| 222 | + signerPrivateKey, |
| 223 | + ) |
| 224 | +} |
| 225 | +``` |
| 226 | + |
| 227 | +### Step 8: Run the Bot |
| 228 | + |
| 229 | +Start the bot locally: |
| 230 | + |
| 231 | +```bash |
| 232 | +bun run src/index.ts |
| 233 | +``` |
| 234 | + |
| 235 | +The bot will now monitor AAVE transactions and post casts to your Farcaster account. |
| 236 | + |
| 237 | +## Next Steps |
| 238 | + |
| 239 | +You've built a Farcaster bot that: |
| 240 | + |
| 241 | +- Monitors specific contracts in real-time |
| 242 | +- Decodes transactions automatically |
| 243 | +- Generates human-readable descriptions |
| 244 | +- Posts alerts to Farcaster |
| 245 | + |
| 246 | +**Customize it further:** |
| 247 | + |
| 248 | +- Track different contracts by updating `CONTRACT_ADDRESS` |
| 249 | +- Modify the cast format in `handleTransaction` |
| 250 | +- Add filters for specific transaction types or amounts |
| 251 | +- Deploy to a server for 24/7 monitoring |
| 252 | + |
| 253 | +--- |
| 254 | + |
| 255 | +Need help? Reach out on X/Twitter [@3loop_io](https://x.com/3loop_io) or check the [full code example](https://github.com/3loop/farcaster-onchain-alerts-bot). |
0 commit comments