diff --git a/apps/docs/src/assets/polymarket-bot.gif b/apps/docs/src/assets/polymarket-bot.gif new file mode 100644 index 00000000..8ede270f Binary files /dev/null and b/apps/docs/src/assets/polymarket-bot.gif differ diff --git a/apps/docs/src/content/components/memory-abi-loader.md b/apps/docs/src/content/components/memory-abi-loader.md index 999c346b..8450fff7 100644 --- a/apps/docs/src/content/components/memory-abi-loader.md +++ b/apps/docs/src/content/components/memory-abi-loader.md @@ -9,7 +9,6 @@ import { // Create an in-memory cache for the ABIs const abiCache = new Map() -// ABI store implementation with caching and multiple resolution strategies const abiStore: VanillaAbiStore = { strategies: [ // List of stratagies to resolve new ABIs diff --git a/apps/docs/src/content/components/memory-contract-loader.md b/apps/docs/src/content/components/memory-contract-loader.md index 5bb5e480..b543f5ec 100644 --- a/apps/docs/src/content/components/memory-contract-loader.md +++ b/apps/docs/src/content/components/memory-contract-loader.md @@ -1,13 +1,12 @@ ```ts title="index.ts" import type { ContractData, VanillaContractMetaStore } from '@3loop/transaction-decoder' -import { ERC20RPCStrategyResolver } 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() -// Contract metadata store implementation with in-memory caching const contractMetaStore: VanillaContractMetaStore = { - strategies: [ERC20RPCStrategyResolver], + strategies: [ERC20RPCStrategyResolver, NFTRPCStrategyResolver], get: async ({ address, chainID }) => { const key = `${address}-${chainID}`.toLowerCase() diff --git a/apps/docs/src/content/docs/recipes/farcaster-bot.mdx b/apps/docs/src/content/docs/recipes/farcaster-bot.mdx new file mode 100644 index 00000000..2ac28d4d --- /dev/null +++ b/apps/docs/src/content/docs/recipes/farcaster-bot.mdx @@ -0,0 +1,255 @@ +--- +title: Build a Farcaster Bot for On-Chain Alerts +description: Create a Farcaster bot that monitors blockchain transactions and posts human-readable casts +sidebar: + order: 2 +--- + +import { Content as MemoryAbiLoader } from '../../components/memory-abi-loader.md' +import { Content as MemoryContractLoader } from '../../components/memory-contract-loader.md' +import { Steps } from '@astrojs/starlight/components' + +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. + +:::tip +Jump to the repo to view the full code example [3loop/farcaster-onchain-alerts-bot](https://github.com/3loop/farcaster-onchain-alerts-bot) +::: + +:::note +This example tracks AAVE trades on Base Mainnet. You can easily adapt it to monitor any contract on any EVM chain. +::: + +![Final result](../../../assets/fc-bot.png) + +## Guide + +### Step 0: Prerequisites + +- Bun installed (see installation guide [here](https://bun.sh/docs/installation)) +- Alchemy account (sign up [here](https://www.alchemy.com/)) +- Basescan API Key (sign up [here](https://basescan.org/register)) +- 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 dependencies: + +```bash +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: + +```bash +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](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. + + + +### 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: + +```ts title="src/constants.ts" +export const RPC = { + 8453: { + archiveUrl: process.env.ARCHIVE_RPC_URL, + traceAPI: 'geth', + }, +} +``` + +```ts title="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: + + + +#### Contract Metadata Store + +Set up contract metadata resolution for token or NFT information (name, decimals, symbol): + + + +#### Create Decoder Instance + +Combine all components into a `TransactionDecoder` instance: + +```ts title="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: + +```ts title="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](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. + +### Step 6: Monitor AAVE Transactions + +Set up real-time monitoring for AAVE trades. Configure the contract address in `constants.ts`: + +```ts title="src/constants.ts" +export const CONTRACT_ADDRESS = '0xa238dd80c259a72e81d7e4664a9801593f98d1c5' +export const CHAIN_ID = 8453 +``` + +Subscribe to new transactions and process them: + +```ts title="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: + +```ts title="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: + +```bash +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](https://x.com/3loop_io) or check the [full code example](https://github.com/3loop/farcaster-onchain-alerts-bot). diff --git a/apps/docs/src/content/docs/recipes/fc-bot.mdx b/apps/docs/src/content/docs/recipes/fc-bot.mdx deleted file mode 100644 index 020bbc2d..00000000 --- a/apps/docs/src/content/docs/recipes/fc-bot.mdx +++ /dev/null @@ -1,256 +0,0 @@ ---- -title: Build a Farcaster Bot for human-readable alerts -description: The simple way to Create a Farcaster Bot for human-readable alerts ---- - -import { Content as MemoryAbiLoader } from '../../components/memory-abi-loader.md' -import { Content as MemoryContractLoader } from '../../components/memory-contract-loader.md' - -import { Steps } from '@astrojs/starlight/components' - -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. - -:::tip -Jump to the repo to view the full code example [3loop/farcaster-onchain-alerts-bot](https://github.com/3loop/farcaster-onchain-alerts-bot) -::: - -:::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. -::: - -![Final result](../../../assets/fc-bot.png) - -## 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: - - - -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. - - - -### 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: { - // Transaciton decoder by default needs archive node for transaction tracing - // becasue Alchemy free plan does not provide archive nodes, we use a different RPC here - archiveUrl: process.env.ARCHIVE_RPC_URL, - - // Provide "none" when transaciton tracing is not needed - traceAPI: 'geth', - }, -} -``` - -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: http(rpc.archiveUrl) }), - config: { traceAPI: rpc.traceAPI }, - } -} -``` - -#### ABI Loader - -To avoid making unecessary calls to third-party APIs, Loop Decoder uses an API that allows cache. For this example, we will keep it simple and use an in-memory cache. We will also use some strategies to download contract ABIs from Etherscan and 4byte.directory. You can find more information about the strategies in the [Strategies](/reference/data-loaders/) reference. - -Create a cache for contract ABI: - - - -#### Contract Metadata Loader - -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. - - - -#### 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 5: 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 `DecodedTransaction` object, which you can inspect using our [playground](https://loop-decoder-web.vercel.app/). [Here](https://loop-decoder-web.vercel.app/interpret/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 `DecodedTransaction` 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 6: 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" -const wsClient = createPublicClient({ - transport: webSocket(ALCHEMY_WS_RPC_URL), -}) - -async function createSubscription(address: string) { - await wsClient.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 7: 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. - -## 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! diff --git a/apps/docs/src/content/docs/recipes/polymarket-bot.mdx b/apps/docs/src/content/docs/recipes/polymarket-bot.mdx new file mode 100644 index 00000000..53f52f1e --- /dev/null +++ b/apps/docs/src/content/docs/recipes/polymarket-bot.mdx @@ -0,0 +1,267 @@ +--- +title: Build a Polymarket On-Chain Bot +description: Create a Polymarket bot that monitors address activities and sends alerts +sidebar: + order: 1 +--- + +import { Content as MemoryAbiLoader } from '../../components/memory-abi-loader.md' +import { Content as MemoryContractLoader } from '../../components/memory-contract-loader.md' +import { Steps } from '@astrojs/starlight/components' + +In this guide, you'll learn how to create a Polymarket bot that monitors Ethereum addresses directly on-chain and sends real-time human-readable alerts about Polymarket trades. + +**What makes this approach powerful:** + +- Monitor transactions directly via RPC WebSocket +- Decode and interpret transactions using only the transaction hash - no external APIs needed +- Get human-readable descriptions like "Bought 100 YES shares" automatically + +:::tip +Jump to the repo to view the full code example [3loop/polymarket-bot](https://github.com/3loop/polymarket-bot) +::: + +![Final result](../../../assets/polymarket-bot.gif) + +## Guide + +### Step 0: Prerequisites + +- Bun installed (see installation guide [here](https://bun.sh/docs/installation)) +- Etherescan API Key (sign up [here](https://etherscan.io/)) +- Polygon WebSocket RPC URL (you can get one from [Alchemy](https://www.alchemy.com/)) +- (Optional) [Neynar](https://neynar.com/) API Key to publish messages on Farcaster + +### Step 1: Clone the Repository + +Clone the bot [repository](https://github.com/3loop/polymarket-bot) and install dependencies: + +```bash +git clone https://github.com/3loop/polymarket-bot +cd polymarket-bot +bun i +``` + +### Step 2: Configure Environment Variables + +Copy the `.env.example` file to `.env` and add your API keys: + +```bash +cp .env.example .env +vim .env +``` + +For the Polymarket bot you need to specify the following keys: + +- `RPC_URL` - Polygon mainnet RPC URL for transaction decoding (non-archive node is sufficient) +- `WS_RPC_URL` - WebSocket URL for real-time subscription (can be the same as `RPC_URL` with `wss` protocol if supported) +- `ETHERSCAN_API_KEY` - Etherescan V2 API key, used in transaction decoding to fetch and cache ABIs +- (optional) `NEYNAR_API_KEY` and `NEYNAR_SIGNER_UUID` - to resolve Polymarket wallet addresses and publish messages to Farcaster + +### Step 3: Setup the Transaciton 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 Polygon Mainnet (chain ID 137). We set `traceAPI` to `'none'` since we do not use traces to understand Polymarket transactions in the interpretation: + +```ts title="src/constants.ts" +export const RPC = { + 137: { + url: process.env.RPC_URL, + traceAPI: 'none', + }, +} +``` + +```ts title="src/decoder/decoder.ts" +export 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.url) }), + config: { traceAPI: rpc.traceAPI }, + } +} +``` + +#### ABI Store + +Set up an in-memory ABI cache with Etherscan and 4byte.directory strategies: + + + +#### Contract Metadata Store + +Set up contract metadata resolution for token or NFT information (name, decimals, symbol): + + + +#### Create Decoder Instance + +Combine all components into a `TransactionDecoder` instance: + +```ts title="src/decoder/decoder.ts" +import { TransactionDecoder } from '@3loop/transaction-decoder' + +export const decoder = new TransactionDecoder({ + getPublicClient, + abiStore, + contractMetaStore, +}) +``` + +### Step 4: Setup the Transaciton Interpreter + +:::note +The `@3loop/transaction-interpreter` package is in Beta. You can copy the utility functions directly into your project if preferred. +::: + +The interpreter converts `DecodedTransaction` into human-readable descriptions. Here's the implementation in `src/decoder/interpreter.ts`: + +```ts title="src/decoder/interpreter.ts" +import { getInterpreter, fallbackInterpreter } from '@3loop/transaction-interpreter' + +export async function interpretTransaction(decodedTx: DecodedTransaction, userAddress?: string) { + // Find the right interpreter for this transaction + const interpreter = getInterpreter(decodedTx) ?? fallbackInterpreter + + // Configure for Polymarket (needs off-chain data fetching) + const config = Layer.succeed(QuickjsConfig, { + variant: variant, + runtimeConfig: { + timeout: 5000, + useFetch: true, // Required for Polymarket market data + }, + }) + + // Apply the interpreter + const layer = Layer.provide(QuickjsInterpreterLive, config) + const runnable = Effect.gen(function* () { + const service = yield* TransactionInterpreter + return yield* service.interpretTransaction(decodedTx, interpreter, { + interpretAsUserAddress: userAddress, // Get user's perspective (bought vs sold) + }) + }).pipe(Effect.provide(layer)) + + return Effect.runPromise(runnable) +} +``` + +The `userAddress` parameter is important - it determines the transaction direction from the user's perspective (e.g., "bought" vs "sold" shares). + +### Step 5: Decode and Interpret Transactions + +With the decoder set up, you can now decode transactions and make them human-readable. Here's how it works: + +```ts title="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 = await interpretTransaction(decoded, userAddress) + +// 3. Use the result +console.log(interpreted.action) // e.g., "Bought 100 YES shares in market 'Will Trump win?'" +``` + +View a [decoded transaction example](https://loop-decoder-web.vercel.app/interpret/137/0xe1429321d4d89f85126d346b01797e7b20b86ba6ea1d53e6c2b782ea46df4247) in our playground. + +### Step 6: Monitor Polymarket Transactions + +Now let's set up real-time monitoring for Polymarket `OrderFilled` events: + +```ts title="src/constants.ts" +export const POLYMARKET_EXCHANGE_ADDRESS = '0x4bfb41d5b3570defd03c39a9a4d8de6bd8b8982e' +export const CHAIN_ID = 137 +``` + +```ts title="src/index.ts" +// List of Polymarket user addresses to track +const ADDRESSES_TO_TRACK = ['0xca85f4b9e472b542e1df039594eeaebb6d466bf2'] + +// Subscribe to OrderFilled events +wsClient.watchEvent({ + address: POLYMARKET_EXCHANGE_ADDRESS, + event: ORDER_FILLED_EVENT_ABI, + args: { + maker: ADDRESSES_TO_TRACK, + }, + onLogs: (logs) => { + logs.forEach((log) => { + handleTransaction(log.transactionHash, log.args.maker) + }) + }, +}) + +// Process each transaction +async function handleTransaction(txHash: string, userAddress: string) { + // 1. Decode + const decoded = await decoder.decodeTransaction({ + chainID: CHAIN_ID, + hash: txHash, + }) + + // 2. Interpret from user's perspective + const interpreted = await interpretTransaction(decoded, userAddress) + + // 3. Create message + const message = { + action: interpreted.action, + txHash, + userAddress, + } +} +``` + +### Step 7: (Optional) Publish to Farcaster + +If you configured Neynar API keys, you can resolve Farcaster usernames and publish alerts: + +```ts title="src/index.ts" +async function publishToFarcaster(message: { action: string; txHash: string; userAddress: string }) { + const farcasterUser = await resolveFarcasterUser(message.userAddress) + + const castText = farcasterUser ? `@${farcasterUser.username} ${message.action}` : `${message.action}` + + await neynarClient.publishCast({ + text: castText, + embeds: [{ url: `https://polygonscan.com/tx/${message.txHash}` }], + }) +} +``` + +### Step 8: Run the Bot + +Start the bot locally: + +```bash +bun run src/index.ts +``` + +The bot will now monitor Polymarket transactions for your tracked addresses and publish human-readable alerts to Farcaster. + +## Next Steps + +You've built a Polymarket bot that: + +- Monitors specific addresses in real-time from the blockchain directly +- Decodes Polymarket transactions +- Generates human-readable descriptions +- Posts alerts to Farcaster + +**Customize it further:** + +- Track different Polymarket addresses by updating `ADDRESSES_TO_TRACK` +- Modify the message format in `publishToFarcaster` +- Add filters for specific market types or trade sizes +- Deploy to a server for 24/7 monitoring + +--- + +Need help? Reach out on X/Twitter [@3loop_io](https://x.com/3loop_io) or check the [full code example](https://github.com/3loop/polymarket-bot). diff --git a/apps/docs/src/content/docs/recipes/telegram-bot.mdx b/apps/docs/src/content/docs/recipes/telegram-bot.mdx new file mode 100644 index 00000000..e2706f24 --- /dev/null +++ b/apps/docs/src/content/docs/recipes/telegram-bot.mdx @@ -0,0 +1,210 @@ +--- +title: Build a Telegram Bot for On-Chain Alerts +description: Create a Telegram bot that monitors blockchain transactions and sends human-readable alerts +sidebar: + order: 3 +--- + +import { Content as MemoryAbiLoader } from '../../components/memory-abi-loader.md' +import { Content as MemoryContractLoader } from '../../components/memory-contract-loader.md' +import { Content as RpcProvider } from '../../components/rpc-provider.md' + +In this guide, you will learn how to create a Telegram 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. + +:::tip +Jump to the repo to view the full code example [3loop/example-tg-bot](https://github.com/3loop/example-tg-bot) +::: + +![Final result](../../../assets/tg-bot.png) + +## Guide + +### Step 0: Prerequisites + +- Bun installed (see installation guide [here](https://bun.sh/docs/installation)) +- Alchemy account (sign up [here](https://www.alchemy.com/)) +- Etherscan API Key (sign up [here](https://etherscan.io/register)) +- Telegram account + +### Step 1: Clone the Repository + +Clone the bot [repository](https://github.com/3loop/example-tg-bot) and install dependencies: + +```bash +git clone https://github.com/3loop/example-tg-bot +cd example-tg-bot +bun i +``` + +### Step 2: Configure Environment Variables + +Copy the `.env.example` file to `.env` and add your API keys: + +```bash +cp .env.example .env +vim .env +``` + +For the Telegram bot you need to specify: + +- `ALCHEMY_API_KEY` - Alchemy API key to monitor new transactions via WebSocket +- `ETHERSCAN_API_KEY` - Etherscan API key, used to fetch and cache ABIs +- `TELEGRAM_BOT_TOKEN` and `TELEGRAM_CHAT_ID` - Telegram bot credentials (see Step 3) + +### Step 3: Create a New Bot on Telegram + +1. **Obtain a bot token**: Start a chat with the [BotFather](https://t.me/BotFather) bot in Telegram, write `/newbot` into the chat, follow the instructions, and copy the bot token. Paste its value into the `TELEGRAM_BOT_TOKEN` variable in the `.env` file. +2. **Obtain a chat ID**: Get the chat ID of the chat where the bot should send notifications. Start a chat with your bot by pressing the `/start` command. Then open to the link `https://api.telegram.org/bot/getUpdates`, where `YourBotToken` is the token you copied from the BotFather. From the `chat` object, copy the `id` and put it into the `TELEGRAM_CHAT_ID` variable in the `.env` file. Check this [StackOverflow answer](https://stackoverflow.com/a/32572159) for more details. + +### 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 Ethereum Mainnet (chain ID 1): + +```ts title="src/constants.ts" +export const RPC = { + 1: { + url: `wss://eth-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY}`, + }, +} +``` + + + +#### ABI Store + +Set up an in-memory ABI cache with Etherscan and 4byte.directory strategies: + + + +#### Contract Metadata Store + +Set up contract metadata resolution for token or NFT information (name, decimals, symbol): + + + +#### Create Decoder Instance + +Combine all components into a `TransactionDecoder` instance: + +```ts title="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: + +```ts title="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., "Supplied 1000 USDC to AAVE V3" +``` + +View a [decoded AAVE V3 transaction example](https://loop-decoder-web.vercel.app/interpret/1/0xb8a923541802ea5017ddccd2c1047dd16ce22048630c1c619b19fdb799b690dd) in our playground. You can test the `interpretTx` function by pasting it into the Interpretation field. + +![Playground](../../../assets/aave-v3.png) + +### Step 6: Monitor AAVE V3 Transactions + +Set up real-time monitoring for AAVE V3 transactions. Update the contract address in `constants.ts`: + +```ts title="src/constants.ts" +export const CONTRACT_ADDRESS = '0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2' +export const CHAIN_ID = 1 +``` + +Subscribe to new transactions and process them: + +```ts title="src/index.ts" +// Subscribe to AAVE V3 transactions +publicClient.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) + + if (!interpreted.action) { + console.log('No defined action for this transaction.', txHash) + return + } + + // 3. Send to Telegram + const botMessage = `${interpreted.action}\nhttps://etherscan.io/tx/${txHash}` + bot.sendMessage(chatId, botMessage) + } catch (e) { + console.error(e) + } +} +``` + +### Step 7: Run the Bot + +Start the bot locally: + +```bash +bun run src/index.ts +``` + +The bot will now monitor AAVE V3 transactions and send alerts to your Telegram chat. + +## Next Steps + +You've built a Telegram bot that: + +- Monitors specific contracts in real-time +- Decodes transactions automatically +- Generates human-readable descriptions +- Sends alerts to Telegram + +**Customize it further:** + +- Track different contracts by updating `CONTRACT_ADDRESS` +- Modify the message 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](https://x.com/3loop_io) or check the [full code example](https://github.com/3loop/example-tg-bot). diff --git a/apps/docs/src/content/docs/recipes/tg-bot.mdx b/apps/docs/src/content/docs/recipes/tg-bot.mdx deleted file mode 100644 index 3a28fcfe..00000000 --- a/apps/docs/src/content/docs/recipes/tg-bot.mdx +++ /dev/null @@ -1,197 +0,0 @@ ---- -title: Build a Telegram Bot for human-readable alerts -description: The simple way to Create a Telegram Bot for human-readable alerts ---- - -import { Content as MemoryAbiLoader } from '../../components/memory-abi-loader.md' -import { Content as MemoryContractLoader } from '../../components/memory-contract-loader.md' -import { Content as RpcProvider } from '../../components/rpc-provider.md' - -In this guide, you will learn how to create a Telegram 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](../../../assets/tg-bot.png) - -## 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 account (sign up [here](https://etherscan.io/register)) -- A Telegram account - -### Step 1: Clone the Repository - -Clone the Bot [repository](https://github.com/3loop/example-tg-bot) from GitHub and install project dependecies: - -```bash -git clone https://github.com/3loop/example-tg-bot -cd example-tg-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 -``` - -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. - -### Step 3: Create a New Bot on Telegram - -1. **Obtain a bot token**: Start a chat with the [BotFather](https://t.me/BotFather) bot in Telegram, write `/newbot` into the chat, follow the instructions, and copy the bot token. Paste its value into the `TELEGRAM_BOT_TOKEN` variable in the `.env` file. -2. **Obtain a chat ID**: Get the chat ID of the chat where the bot should send notifications. Start a chat with your bot by pressing the `/start` command. Then open to the link `https://api.telegram.org/bot/getUpdates`, where `YourBotToken` is the token you copied from the BotFather. From the `chat` object, copy the `id` and put it into the `TELEGRAM_CHAT_ID` variable in the `.env` file. Check this [StackOverflow answer](https://stackoverflow.com/a/32572159) for more details. - -### 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 Ethereum Mainnet. - -The `constants.ts` file contains the RPC URL and configuration: - -```ts title="src/constants.ts" -export const RPC = { - 1: { - url: `wss://eth-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY}`, - }, -} -``` - -The `getPublicClient` returns an RPC client based on the chaind ID and the config from `constants.ts`: - - - -#### ABI Loader - -To avoid making unecessary calls to third-party APIs, Loop Decoder uses an API that allows cache. For this example, we will keep it simple and use an in-memory cache. We will also use some strategies to download contract ABIs from Etherscan and 4byte.directory. You can find more information about the strategies in the [Strategies](/reference/data-loaders/) reference. - -Create a cache for contract ABI: - - - -#### Contract Metadata Loader - -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. - - - -#### 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 5: 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 -const decoded = await decoder.decodeTransaction({ - chainID: chainID, - hash: txHash, -}) -``` - -The `decodeTransaction` method returns a `DecodedTransaction` object, which you can inspect using our [playground](https://loop-decoder-web.vercel.app/). [Here](https://loop-decoder-web.vercel.app/tx/1/0xb8a923541802ea5017ddccd2c1047dd16ce22048630c1c619b19fdb799b690dd) is an example of an AAVE V3 transaction. - -To interpret a decoded transaction, the `interpreter.ts` file contains a `transformEvent` function that transforms the `DecodedTransaction` and adds an action description. You can test the `transformEvent` function by putting it into the Interpretation field on the playground with the AAVE V3 [example](https://loop-decoder-web.vercel.app/tx/1/0xb8a923541802ea5017ddccd2c1047dd16ce22048630c1c619b19fdb799b690dd), and pressing "Interpret". - -![Playground](../../../assets/aave-v3.png) - -### Step 6: Create a Contract Subscription - -The bot is set to monitor AAVE V3 transactions on the Ethereum mainnet by default. Update the contract address and chain ID in the `constants.ts` file: - -```ts title="src/constants.ts" -export const CONTRACT_ADDRESS = '0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2' -export const CHAIN_ID = 1 -``` - -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 7: Handle a new Transaction - -The `handleTransaction` function is responsible for decoding incoming alerts and sending a Telegram message. You can customize the bot message or add extra information by tweaking the botMessage variable. - -```ts -async function handleTransaction(txHash?: string) { - try { - //... - - const decoded = await decoder.decodeTransaction({ - chainID: CHAIN_ID, - hash: txHash, - }) - - if (!decoded) return; - const interpreted = interpretTx(decoded as DecodedTransaction); - - if (!interpreted.action) { - console.log("No defined action for this transaction.", txHash); - return; - } - const botMessage = `${interpreted.action} ; - bot.sendMessage(chatId, botMessage); - } catch (e) { - console.error(e); - } -} - -``` - -### Step 8: Start the Bot - -Use the following command to start the server locally: - -```bash -bun run src/index.ts -``` - -Your Telegram bot is now set up and will monitor blockchain transactions and send alerts to the specified chat or channel. - -## Conculsion - -In this guide, you've learned how to create a Telegram 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!