Skip to content
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
74 changes: 30 additions & 44 deletions apps/docs/src/content/components/memory-abi-loader.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,60 +9,46 @@ import {
// Create an in-memory cache for the ABIs
const abiCache = new Map<string, ContractABI>()

// 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)
},
}
```
23 changes: 4 additions & 19 deletions apps/docs/src/content/components/memory-contract-loader.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,19 @@ import { ERC20RPCStrategyResolver } from '@3loop/transaction-decoder'
// Create an in-memory cache for the contract meta-information
const contractMetaCache = new Map<string, ContractData>()

// 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)
}
},
}
Expand Down
33 changes: 19 additions & 14 deletions apps/docs/src/content/docs/recipes/fc-bot.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@
description: The simple way to Create a Farcaster Bot for human-readable alerts
---

import { Content as MemoryAbiLoader } from '../../components/memory-abi-loader.md'

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

View workflow job for this annotation

GitHub Actions / pr

'MemoryAbiLoader' is defined but never used
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.
:::
Expand Down Expand Up @@ -72,8 +76,12 @@
```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',
},
}
```
Expand All @@ -85,18 +93,11 @@

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 },
}
}
```
Expand Down Expand Up @@ -140,7 +141,7 @@
})
```

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".

Expand All @@ -158,8 +159,12 @@
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
Expand Down
Loading