Decoding Transaction
This guide explains how to decode Ethereum transactions using Loop Decoder. We’ll cover:
- Setting up data loading strategies for ABIs and contract metadata
- Configuring data stores for Contract ABIs and metadata
- Decoding transactions
Learn more about Loop Decoder APIs and the differences between them
Installation
Generate and initialize a new project:
mkdir example-decode && cd example-decodebun init
Install required packages:
bun install @3loop/transaction-decoder viem
Setup Loop Decoder
Loop Decoder requires three components:
- RPC Provider: Fetches raw transaction data
- ABI Data Store: Retrieves and caches contract ABIs
- Contract Metadata Store: Retrieves and caches contract metadata (e.g., token name, symbol, decimals)
1. RPC Provider
Create a getPublicClient
function that accepts a chain ID and returns an object with Viem PublicClient
.
import { createPublicClient, http } from 'viem'
// Create a public client for the Ethereum Mainnet networkconst getPublicClient = (chainId: number) => { return { client: createPublicClient({ transport: http('https://rpc.ankr.com/eth'), }), }}
For detailed configuration options and trace API settings, see the RPC Provider documentation.
2. ABI Data Store
The ABI Data Store handles:
- Fetching ABIs using predefined strategies (e.g., Etherscan, 4byte). Some strategies like Etherscan require an API key. See the full list of strategies in Data Loaders (ABI Strategies)
- Caching fetched ABIs
To create a custom ABI Data Store, implement the VanillaAbiStore
interface:
export interface VanillaAbiStore { strategies?: readonly ContractAbiResolverStrategy[] get: (key: AbiParams) => Promise<ContractAbiResult> set: (key: AbiParams, val: ContractAbiResult) => Promise<void>}
Example: an ABI data store with Etherscan and 4byte data loaders and in-memory cache
import { EtherscanStrategyResolver, FourByteStrategyResolver, VanillaAbiStore, ContractABI,} from '@3loop/transaction-decoder'
// Create an in-memory cache for the ABIsconst abiCache = new Map<string, ContractABI>()
const abiStore: VanillaAbiStore = { // Define the strategies to use for fetching the ABIs strategies: [ EtherscanStrategyResolver({ apikey: 'YourApiKeyToken', }), FourByteStrategyResolver(), ],
// Get the ABI from the cache // Get it by contract address, event name or signature hash 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, } } }
return { status: 'empty', result: null, } },
// 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) } } },}
3. Contract Metadata Store
The Contract Metadata Store handles:
- Fetching contract metadata using predefined strategies (e.g., ERC20, NFT). See the full list of strategies in Data Loaders (Contract Metadata)
- Caching fetched contract metadata
To create a custom Contract Metadata Store, implement the VanillaContractMetaStore
interface:
export interface VanillaContractMetaStore { strategies?: readonly VanillaContractMetaStategy[] get: (key: ContractMetaParams) => Promise<ContractMetaResult> set: (key: ContractMetaParams, val: ContractMetaResult) => Promise<void>}
Example: a Contract Metadata Store with ERC20 data loader and in-memory cache
import type { ContractData, VanillaContractMetaStore } from '@3loop/transaction-decoder'import { ERC20RPCStrategyResolver } from '@3loop/transaction-decoder'
// Create an in-memory cache for the contract meta-informationconst contractMetaCache = new Map<string, ContractData>()
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, } },
// 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) } },}
4. Initializing Loop Decoder
Finally, you can create a new instance of the TransactionDecoder class:
import { TransactionDecoder } from '@3loop/transaction-decoder'
const decoder = new TransactionDecoder({ getPublicClient: getPublicClient, abiStore: abiStore, contractMetaStore: contractMetaStore,})
Example: Decoding a Transaction
Once the TransactionDecoder
is set up, you can use it to decode a transaction by calling the decodeTransaction
method:
async function main() { try { const decoded = await decoder.decodeTransaction({ chainID: 1, hash: '0xc0bd04d7e94542e58709f51879f64946ff4a744e1c37f5f920cea3d478e115d7', })
console.log(JSON.stringify(decoded, null, 2)) } catch (e) { console.error(JSON.stringify(e, null, 2)) }}
main()
Check the full expected output in our Playground or see it below:
{ "txHash": "0xc0bd04d7e94542e58709f51879f64946ff4a744e1c37f5f920cea3d478e115d7", "txType": "contract interaction", "fromAddress": "0xf89a3799b90593317e0a1eb74164fbc1755a297a", "toAddress": "0x7d2768de32b0b80b7a3454c06bdac94a69ddc7a9", "contractName": null, "contractType": "OTHER", "methodCall": { "name": "repay", "type": "function", "signature": "repay(address,uint256,uint256,address)", "params": [ { "name": "asset", "type": "address", "value": "0xdAC17F958D2ee523a2206206994597C13D831ec7" }, { "name": "amount", "type": "uint256", "value": "1238350000" }, { "name": "rateMode", "type": "uint256", "value": "2" }, { "name": "onBehalfOf", "type": "address", "value": "0xf89a3799b90593317E0a1Eb74164fbc1755A297A" } ] } // ...}
Try it live
Try decoding the above or any other transactions in the our playground here.