Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add fc bot guide #46

Merged
merged 1 commit into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added apps/docs/src/assets/fc-bot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/docs/src/assets/friend-tech.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
284 changes: 284 additions & 0 deletions apps/docs/src/content/docs/guides/fc-bot.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
---
title: Build a Farcaster Bot for human-readable alerts
description: The simple way to Create a Farcaster Bot for human-readable alerts
---

import { Steps } from '@astrojs/starlight/components'

Check warning on line 6 in apps/docs/src/content/docs/guides/fc-bot.mdx

View workflow job for this annotation

GitHub Actions / pr

'Steps' is defined but never used

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.

:::note
For the demo, we've used a [friend.tech](https://www.friend.tech) smart contract to track trading activity on the Base Mainnet network. By modifying the contract address and environment variables, you can create a bot to track smart contracts on any other EVM chain.
:::

## Guide

### Step 0: Prerequisites

- An installed Bun (see installation guide [here](https://bun.sh/docs/installation))
- An Alchemy account (sign up [here](https://www.alchemy.com/))
- An Etherscan(Basescan) account (sign up [here](https://basescan.org/register))
- A Farcaster account (can be yours or a separate one for your bot)

### Step 1: Clone the Repository

Clone the Bot [repository](https://github.com/3loop/farcaster-onchain-alerts-bot) and install project dependencies:

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

### Step 2: Add Etherescan and Alchemy API Keys

Copy and rename the `.env.example` file to `.env`, then paste the Alchemy and Etherescan API keys into the `ALCHEMY_API_KEY` and `ETHERSCAN_API_KEY` variables.

```bash
cp .env.example .env
vim .env
```

To reproduce the Friend.tech alerts bot, you need to create an Alchemy App for the Base Mainnet and get an API key from the Basescan. We use the Alchemy API key to monitor new transactions, and the Etherscan API key (from the free plan) to fetch contract ABIs and avoid hitting rate limits. The Etherscan API could be optional if the transactions you are interested in do not interact with many contracts.

### 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:

<Steps>

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](https://optimistic.etherscan.io/tx/0x9eecacefceb6f120c3ef50222eabb15d86fd5feac6dae3fdf09dccb7687c70d4), 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.

</Steps>

### Step 4: Setup the Decoder

Now, let's go through the code. To begin using the Loop Decoder, we need to set up the following components:

#### RPC Provider

We'll create a function that returns an object with a`PublicClient` based on the chain ID. In this example, we are going to support the Base Mainnet.

The `constants.ts` file contains the RPC URL and configuration. The Base RPCs do not support a trace API, so we need to specify it in the configuration:

```ts title="src/constants.ts"
export const RPC = {
8453: {
url: `wss://base-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY}`,
supportTraceAPI: false,
},
}
```

The `getPublicClient` returns an RPC client based on the chaind ID and the config from `constants.ts`:

```ts title="src/decoder/decoder.ts"
import { createPublicClient } from 'viem'

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: webSocket(rpc.url),
}),
config: {
supportTraceAPI: rpc.supportTraceAPI,
},
}
}
```

#### ABI Loader

We'll use an in-memory cache and strategies to download contract ABIs from Etherscan and 4byte.directory.

```ts title="src/decoder/decoder.ts"
import { EtherscanStrategyResolver, FourByteStrategyResolver } from '@3loop/transaction-decoder'

const abiCache = new Map<string, string>()

const abiStore = {
strategies: [
EtherscanStrategyResolver({
apikey: process.env.ETHERSCAN_API_KEY || '',
}),
FourByteStrategyResolver(),
],
get: async (req: { address: string }) => {
return Promise.resolve(abiCache.get(req.address.toLowerCase()) ?? null)
},
set: async (req: { address?: Record<string, string> }) => {
const addresses = Object.keys(req.address ?? {})

addresses.forEach((address) => {
abiCache.set(address.toLowerCase(), req.address?.[address] ?? '')
})
},
}
```

#### Contract Metadata Loader

We'll create an in-memory cache for contract meta-information. Using `ERC20RPCStrategyResolver` we will automatically retrieve token meta information from the contract such as token name, decimals, symbol, etc.

```ts title="src/decoder/decoder.ts"
import type { ContractData } from '@3loop/transaction-decoder'
import { ERC20RPCStrategyResolver } from '@3loop/transaction-decoder'

const contractMetaCache = new Map<string, ContractData>()

const contractMetaStore = {
strategies: [ERC20RPCStrategyResolver],
get: async (req: { address: string; chainID: number }) => {
return contractMetaCache.get(req.address.toLowerCase()) ?? null
},
set: async (req: { address: string; chainID: number }, data: ContractData) => {
contractMetaCache.set(req.address.toLowerCase(), data)
},
}
```

#### Loop Decoder instance

Finally, we'll create a new instance of the LoopDecoder class.

```ts title="src/decoder/decoder.ts"
import { TransactionDecoder } from '@3loop/transaction-decoder'

const decoder = new TransactionDecoder({
getPublicClient: getPublicClient,
abiStore: abiStore,
contractMetaStore: contractMetaStore,
})
```

### Step 3: Decode and Interpret a Transaction

After creating the `decoder` object in the `decoder.ts` file, we can use its `decodeTransaction` method to decode transactions by hash:

```ts title="src/index.ts"
const decoded = await decoder.decodeTransaction({
chainID: CHAIN_ID,
hash: txHash,
})
```

The `decodeTransaction` method returns a `DecodedTx` object, which you can inspect using our [playground](https://loop-decoder-web.vercel.app/). [Here](https://loop-decoder-web.vercel.app/tx/8453/0x3bc635e07e1cec2b73a896e6d130cffde6b3eeb419f91ea79028927fb7a66a07) is an example of the Friend.tech transaction.

To interpret a decoded transaction, the `interpreter.ts` file contains a `transformEvent` function that transforms the `DecodedTx` and adds an action description. You can test the `transformEvent` function by putting it into the Interpretation field on the playground with the Friend.tech [example](https://loop-decoder-web.vercel.app/tx/8453/0x3bc635e07e1cec2b73a896e6d130cffde6b3eeb419f91ea79028927fb7a66a07), and pressing "Interpret".

![Playground](../../../assets/friend-tech.png)

### Step 4: Create a Contract Subscription

The bot is set to monitor Friend.tech transactions on the Base Mainnet. The configuration variables are located in the `constants.ts` file:

```ts title="src/constants.ts"
export const CONTRACT_ADDRESS = '0xcf205808ed36593aa40a44f10c7f7c2f67d4a4d4'
export const CHAIN_ID = 8453
```

In the program's entry point, we start a WebSocket subscription for new transactions using Alchemy's WebSocket RPC.

```ts title="src/index.ts"
async function createSubscription(address: string) {
await publicClient.transport.subscribe({
method: 'eth_subscribe',
params: [
//@ts-ignore
'alchemy_minedTransactions',
{
addresses: [{ to: address }],
includeRemoved: false,
hashesOnly: true,
},
],
onData: (data: any) => {
const hash = data?.result?.transaction?.hash
if (hash) handleTransaction(hash)
},
//...
})
}

createSubscription(CONTRACT_ADDRESS)
```

### Step 5: Handle a new Transaction

The `handleTransaction` function is responsible for decoding incoming alerts and publishing them as Farcaster casts.

```ts title="src/index.ts"
async function handleTransaction(txHash?: string) {
try {
//...
const decoded = await decoder.decodeTransaction({
chainID: CHAIN_ID,
hash: txHash,
})

if (!decoded) return
const interpreted = interpretTx(decoded)

const text = `New trade: ${interpreted.trader} ${interpreted.isBuy ? 'Bought' : 'Sold'} ${
interpreted.shareAmount
} shares of ${interpreted.subject} for ${interpreted.price} ETH`

const message = { text: text, url: `${ETHERSCAN_ENDPOINT}/tx/${txHash}` }

await publishToFarcaster(message)
} catch (e) {
console.error(e)
}
}
```

#### Publishing to the Farcaster

We use the `@standard-crypto/farcaster-js-hub-rest` package to publish new casts to Farcaster. The `submitCast` function allows us to attach embeds like URLs or frames to the casts. We publish the casts in the bot's profile by including the bot's FID as the second argument.

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

### Step 8: Start the Bot

Use the following command to start bot locally:

```bash
bun run src/index.ts
```

Your Farcaster bot is now set up and will monitor blockchain transactions and send casts to the bot's account.

![Final result](../../../assets/fc-bot.png)

## Conculsion

In this guide, you've learned how to create a Farcaster bot that monitors blockchain transactions and sends human-readable alerts. By using the Loop Decoder library, you can easily set up a bot to track specific contract addresses and chains without needing in-depth knowledge of EVM transaction decoding.

Let us know on X/Twitter ([@3loop_io](https://x.com/3loop_io)) if you encounter any problems or have any questions, we'd love to help you!

Happy coding!
6 changes: 3 additions & 3 deletions apps/docs/src/content/docs/guides/tg-bot.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ In this guide, you will learn how to create a Telegram bot that sends human-read

### Step 0: Prerequisites

- A basic understanding of JavaScript
- An installed NodeJS
- An Alchemy account (sign up [here](https://www.alchemy.com/))
- An Etherscan account (sign up [here](https://etherscan.io/register))
- A Telegram account
Expand Down Expand Up @@ -131,7 +131,7 @@ To interpret a decoded transaction, the `interpreter.ts` file contains a `transf

The bot is set to monitor AAVE V3 transactions on the Ethereum mainnet by default. Update the contract address and chain ID in the `index.ts` file:

```ts
```ts title="src/index.ts"
const contractAddress = '0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2'
const chainID = 1
```
Expand Down Expand Up @@ -186,7 +186,7 @@ Copy and rename the `.env.example` file to `.env`, then paste the Alchemy and Et

```bash
cp .env.example .env
nano .env
vim .env
```

We use the Alchemy API key to monitor new transactions happening on the blockchain, and the Etherscan API key (from the free plan) to fetch contract ABIs and avoid hitting rate limits. The Etherscan API could be optional if the transactions you are interested in do not interact with many contracts, but since we are testing AAVE V3 it will have many interactions.
Expand Down
Loading