diff --git a/apps/docs/src/content/components/memory-abi-loader.md b/apps/docs/src/content/components/memory-abi-loader.md index f37dc8a9..999c346b 100644 --- a/apps/docs/src/content/components/memory-abi-loader.md +++ b/apps/docs/src/content/components/memory-abi-loader.md @@ -9,60 +9,46 @@ 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 = { - // Define the strategies to use for fetching the ABIs strategies: [ - EtherscanStrategyResolver({ - apikey: 'YourApiKeyToken', + // List of stratagies to resolve new ABIs + EtherscanV2StrategyResolver({ + apikey: process.env.ETHERSCAN_API_KEY || '', }), FourByteStrategyResolver(), ], - // Get the ABI from the cache - // Get it by contract address, event name or signature hash + // Get ABI from memory by address, event or signature + // Can be returned the list of all possible ABIs get: async ({ address, event, signature }) => { - const value = abiCache.get(address) - if (value) { - return { - status: 'success', - result: value, - } - } else if (event) { - const value = abiCache.get(event) - if (value) { - return { - status: 'success', - result: value, - } - } - } else if (signature) { - const value = abiCache.get(signature) - if (value) { - return { - status: 'success', - result: value, - } - } - } + const key = address?.toLowerCase() || event || signature + if (!key) return [] - return { - status: 'empty', - result: null, - } + const cached = abiCache.get(key) + return cached + ? [ + { + ...cached, + id: key, + source: 'etherscan', + status: 'success', + }, + ] + : [] }, - // Set the ABI in the cache - // Store it by contract address, event name or signature hash - set: async (_key, value) => { - if (value.status === 'success') { - if (value.result.type === 'address') { - abiCache.set(value.result.address, value.result) - } else if (value.result.type === 'event') { - abiCache.set(value.result.event, value.result) - } else if (value.result.type === 'func') { - abiCache.set(value.result.signature, value.result) - } - } + 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) }, } ``` diff --git a/apps/docs/src/content/components/memory-contract-loader.md b/apps/docs/src/content/components/memory-contract-loader.md index d5d98613..5bb5e480 100644 --- a/apps/docs/src/content/components/memory-contract-loader.md +++ b/apps/docs/src/content/components/memory-contract-loader.md @@ -5,34 +5,19 @@ import { ERC20RPCStrategyResolver } 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 = { - // Define the strategies to use for fetching the contract meta-information strategies: [ERC20RPCStrategyResolver], - // Get the contract meta-information from the cache get: async ({ address, chainID }) => { const key = `${address}-${chainID}`.toLowerCase() - const value = contractMetaCache.get(key) - - if (value) { - return { - status: 'success', - result: value, - } - } - - return { - status: 'empty', - result: null, - } + const cached = contractMetaCache.get(key) + return cached ? { status: 'success', result: cached } : { status: 'empty', result: null } }, - // Set the contract meta-information in the cache set: async ({ address, chainID }, result) => { - const key = `${address}-${chainID}`.toLowerCase() - if (result.status === 'success') { - contractMetaCache.set(key, result.result) + contractMetaCache.set(`${address}-${chainID}`.toLowerCase(), result.result) } }, } diff --git a/apps/docs/src/content/docs/recipes/fc-bot.mdx b/apps/docs/src/content/docs/recipes/fc-bot.mdx index 4e12f7d2..020bbc2d 100644 --- a/apps/docs/src/content/docs/recipes/fc-bot.mdx +++ b/apps/docs/src/content/docs/recipes/fc-bot.mdx @@ -10,6 +10,10 @@ 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. ::: @@ -72,8 +76,12 @@ The `constants.ts` file contains the RPC URL and configuration. The Base RPCs do ```ts title="src/constants.ts" export const RPC = { 8453: { - url: `wss://base-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY}`, - traceAPI: 'none', + // 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', }, } ``` @@ -85,18 +93,11 @@ 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}`) - } + if (!rpc) throw new Error(`Missing RPC provider for chain ID ${chainId}`) return { - client: createPublicClient({ - transport: webSocket(rpc.url), - }), - config: { - traceAPI: rpc.traceAPI, - }, + client: createPublicClient({ transport: http(rpc.archiveUrl) }), + config: { traceAPI: rpc.traceAPI }, } } ``` @@ -140,7 +141,7 @@ const decoded = await decoder.decodeTransaction({ }) ``` -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/8453/0x3bc635e07e1cec2b73a896e6d130cffde6b3eeb419f91ea79028927fb7a66a07) is an example of the Friend.tech transaction. +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". @@ -158,8 +159,12 @@ 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 publicClient.transport.subscribe({ + await wsClient.transport.subscribe({ method: 'eth_subscribe', params: [ //@ts-ignore