Skip to content

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:

Terminal window
mkdir example-decode && cd example-decode
bun init

Install required packages:

Terminal window
bun install @3loop/transaction-decoder viem

Setup Loop Decoder

Loop Decoder requires three components:

  1. RPC Provider: Fetches raw transaction data
  2. ABI Data Store: Retrieves and caches contract ABIs
  3. 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.

provider.ts
import { createPublicClient, http } from 'viem'
// Create a public client for the Ethereum Mainnet network
const 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:

vanilla.ts
export interface VanillaAbiStore {
strategies?: readonly ContractAbiResolverStrategy[]
get: (key: AbiParams) => Promise<ContractAbiResult>
set: (key: AbiParams, val: ContractABI) => Promise<void>
}

Example: an ABI data store with Etherscan and 4byte data loaders and in-memory cache

index.ts
import {
EtherscanStrategyResolver,
FourByteStrategyResolver,
VanillaAbiStore,
ContractABI,
} from '@3loop/transaction-decoder'
// 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 = {
strategies: [
// List of stratagies to resolve new ABIs
EtherscanV2StrategyResolver({
apikey: process.env.ETHERSCAN_API_KEY || '',
}),
FourByteStrategyResolver(),
],
// Get ABI from memory by address, event or signature
// Can be returned the list of all possible ABIs
get: async ({ address, event, signature }) => {
const key = address?.toLowerCase() || event || signature
if (!key) return []
const cached = abiCache.get(key)
return cached
? [
{
...cached,
id: key,
source: 'etherscan',
status: 'success',
},
]
: []
},
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)
},
}

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:

vanilla.ts
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

index.ts
import type { ContractData, VanillaContractMetaStore } from '@3loop/transaction-decoder'
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 = {
strategies: [ERC20RPCStrategyResolver],
get: async ({ address, chainID }) => {
const key = `${address}-${chainID}`.toLowerCase()
const cached = contractMetaCache.get(key)
return cached ? { status: 'success', result: cached } : { status: 'empty', result: null }
},
set: async ({ address, chainID }, result) => {
if (result.status === 'success') {
contractMetaCache.set(`${address}-${chainID}`.toLowerCase(), 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.