Skip to content

Build a Farcaster Bot for On-Chain Alerts

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.

Final result

Guide

Step 0: Prerequisites

  • Bun installed (see installation guide here)
  • Alchemy account (sign up here)
  • Basescan API Key (sign up here)
  • Farcaster account (can be yours or a separate one for your bot)

Step 1: Clone the Repository

Clone the bot repository and install dependencies:

Terminal window
git clone https://github.com/3loop/farcaster-onchain-alerts-bot
cd farcaster-onchain-alerts-bot
bun i

Step 2: Configure Environment Variables

Copy the .env.example file to .env and add your API keys:

Terminal window
cp .env.example .env
vim .env

For the Farcaster bot you need to specify:

  • ALCHEMY_API_KEY - Alchemy API key to monitor new transactions via WebSocket
  • ETHERSCAN_API_KEY - Basescan API key, used to fetch and cache ABIs
  • ARCHIVE_RPC_URL - Archive RPC URL for Base (required for transaction tracing)
  • SIGNER_PRIVATE_KEY and ACCOUNT_FID - Farcaster credentials (see Step 3)

Step 3: Create a Farcaster Account Key (Signer)

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:

  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.
  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.
  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, and a public and private key printed in the console. Do not share the private key.
  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.

Step 4: Setup the Transaction Decoder

Loop Decoder requires three components: an RPC provider, ABI store, and contract metadata store. Let’s set up each one:

RPC Provider

Configure your RPC provider in constants.ts for Base Mainnet (chain ID 8453). We use traceAPI: 'geth' for transaction tracing:

src/constants.ts
export const RPC = {
8453: {
archiveUrl: process.env.ARCHIVE_RPC_URL,
traceAPI: 'geth',
},
}
src/decoder/decoder.ts
const getPublicClient = (chainId: number) => {
const rpc = RPC[chainId as keyof typeof RPC]
if (!rpc) throw new Error(`Missing RPC provider for chain ID ${chainId}`)
return {
client: createPublicClient({ transport: http(rpc.archiveUrl) }),
config: { traceAPI: rpc.traceAPI },
}
}

ABI Store

Set up an in-memory ABI cache with Basescan and 4byte.directory strategies:

index.ts
import {
EtherscanStrategyResolver,
FourByteStrategyResolver,
VanillaAbiStore,
ContractABI,
} from '@3loop/transaction-decoder'
// Create an in-memory cache for the ABIs
const abiCache = new Map<string, ContractABI>()
const abiStore: VanillaAbiStore = {
strategies: [
// List of stratagies to resolve new ABIs
EtherscanV2StrategyResolver({
apikey: process.env.ETHERSCAN_API_KEY || '',
}),
FourByteStrategyResolver(),
],
// Get ABI from memory by address, event or signature
// Can be returned the list of all possible ABIs
get: async ({ address, event, signature }) => {
const key = address?.toLowerCase() || event || signature
if (!key) return []
const cached = abiCache.get(key)
return cached
? [
{
...cached,
id: key,
source: 'etherscan',
status: 'success',
},
]
: []
},
set: async (_key, abi) => {
const key =
abi.type === 'address'
? abi.address.toLowerCase()
: abi.type === 'event'
? abi.event
: abi.type === 'func'
? abi.signature
: null
if (key) abiCache.set(key, abi)
},
}

Contract Metadata Store

Set up contract metadata resolution for token or NFT information (name, decimals, symbol):

index.ts
import type { ContractData, VanillaContractMetaStore } from '@3loop/transaction-decoder'
import { ERC20RPCStrategyResolver, NFTRPCStrategyResolver } from '@3loop/transaction-decoder'
// Create an in-memory cache for the contract meta-information
const contractMetaCache = new Map<string, ContractData>()
const contractMetaStore: VanillaContractMetaStore = {
strategies: [ERC20RPCStrategyResolver, NFTRPCStrategyResolver],
get: async ({ address, chainID }) => {
const key = `${address}-${chainID}`.toLowerCase()
const cached = contractMetaCache.get(key)
return cached ? { status: 'success', result: cached } : { status: 'empty', result: null }
},
set: async ({ address, chainID }, result) => {
if (result.status === 'success') {
contractMetaCache.set(`${address}-${chainID}`.toLowerCase(), result.result)
}
},
}

Create Decoder Instance

Combine all components into a TransactionDecoder instance:

src/decoder/decoder.ts
import { TransactionDecoder } from '@3loop/transaction-decoder'
const decoder = new TransactionDecoder({
getPublicClient,
abiStore,
contractMetaStore,
})

Step 5: Decode and Interpret Transactions

With the decoder set up, you can now decode transactions and make them human-readable:

src/index.ts
// 1. Decode the transaction
const decoded = await decoder.decodeTransaction({
chainID: CHAIN_ID,
hash: txHash,
})
// 2. Interpret it (make it human-readable)
const interpreted = interpretTx(decoded)
// 3. Use the result
console.log(interpreted.action) // e.g., "Alice bought 5 shares of Bob for 0.1 ETH"

View a decoded AAVE transaction example in our playground. You can test the interpretTx function by pasting it into the Interpretation field.

Step 6: Monitor AAVE Transactions

Set up real-time monitoring for AAVE trades. Configure the contract address in constants.ts:

src/constants.ts
export const CONTRACT_ADDRESS = '0xa238dd80c259a72e81d7e4664a9801593f98d1c5'
export const CHAIN_ID = 8453

Subscribe to new transactions and process them:

src/index.ts
const wsClient = createPublicClient({
transport: webSocket(ALCHEMY_WS_RPC_URL),
})
// Subscribe to AAVE transactions
wsClient.transport.subscribe({
method: 'eth_subscribe',
params: [
'alchemy_minedTransactions',
{
addresses: [{ to: CONTRACT_ADDRESS }],
includeRemoved: false,
hashesOnly: true,
},
],
onData: (data: any) => {
const hash = data?.result?.transaction?.hash
if (hash) handleTransaction(hash)
},
})
// Process each transaction
async function handleTransaction(txHash: string) {
try {
// 1. Decode
const decoded = await decoder.decodeTransaction({
chainID: CHAIN_ID,
hash: txHash,
})
if (!decoded) return
// 2. Interpret
const interpreted = interpretTx(decoded)
// 3. Format message
const text = `New trade: ${interpreted.trader} ${interpreted.isBuy ? 'Bought' : 'Sold'} ${
interpreted.shareAmount
} shares of ${interpreted.subject} for ${interpreted.price} ETH`
// 4. Post to Farcaster
await publishToFarcaster({
text,
url: `https://basescan.org/tx/${txHash}`,
})
} catch (e) {
console.error(e)
}
}

Step 7: Publish to Farcaster

Use the @standard-crypto/farcaster-js-hub-rest package to publish casts:

src/index.ts
async function publishToFarcaster(cast: { text: string; url: string }) {
await client.submitCast(
{
text: cast.text,
embeds: [{ url: cast.url }],
},
Number(fid),
signerPrivateKey,
)
}

Step 8: Run the Bot

Start the bot locally:

Terminal window
bun run src/index.ts

The bot will now monitor AAVE transactions and post casts to your Farcaster account.

Next Steps

You’ve built a Farcaster bot that:

  • Monitors specific contracts in real-time
  • Decodes transactions automatically
  • Generates human-readable descriptions
  • Posts alerts to Farcaster

Customize it further:

  • Track different contracts by updating CONTRACT_ADDRESS
  • Modify the cast format in handleTransaction
  • Add filters for specific transaction types or amounts
  • Deploy to a server for 24/7 monitoring

Need help? Reach out on X/Twitter @3loop_io or check the full code example.