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: ContractAbiResult) => 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>()
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:

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>()
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.