How to decode an Ethereum Transaction
In this guide, we will go through the process of decoding an Ethereum transaction using Loop Decoder. For the simplicity of the example, we assume that that contract ABIs involved in the transaction are verified on Etherscan.
We recomend to copy all snipepts to a typescript project and run it at the end of this guide.
Prerequisites
Create a new project
Optionally, you can create a new project to follow along, or skip to Required packages.
- Generate an empty project using npm:
mkdir example-decode && cd example-decodenpm init -ynpm install typescript --save-dev
- Initialize typescript:
npx tsc --init
- Ensure that you have
strict
flag set totrue
intsconfig.json
:
{ "compilerOptions": { "strict": true }}
-
Create a new file
index.ts
where you will add the code snipets from this guide. -
Add a new script to
package.json
:
{ "scripts": { "start": "tsc && node index.js" }}
- Run the example:
npm start
Required packages
For this guide, you will need to have the following packages installed:
npm install @3loop/transaction-decoder viem
Data Sources
Loop Decoder requires some data sources to be able to decode transactions. We will need an RPC provider, a data source to fetch Contracts ABIs and a data source to fetch contract meta-information, such as token name, decimals, symbol, etc.
RPC Provider
We will start by creating a function which will return an object with PublicClient based on the chain ID. For the sake of this example, we will only support mainnet.
import { createPublicClient, http } from 'viem'
const getPublicClient = (chainId: number) => { return { client: createPublicClient({ transport: http('https://rpc.ankr.com/eth'), }), }}
ABI loader
To avoid making unecessary calls to third-party APIs, Loop Decoder uses an API that allows cache. For this example, we will keep it simple and use an in-memory cache. We will also use some strategies to download contract ABIs from Etherscan and 4byte.directory. You can find more information about the strategies in the Strategies reference.
Create a cache for contract ABI:
import { EtherscanStrategyResolver, FourByteStrategyResolver, VanillaAbiStore, ContractABI,} from '@3loop/transaction-decoder'
const abiCache = new Map<string, ContractABI>()
const abiStore: VanillaAbiStore = { strategies: [ EtherscanStrategyResolver({ apikey: 'YourApiKeyToken', }), FourByteStrategyResolver(), ], get: async ({ address, event, signature }) => { const value = abiCache.get(address) if (value) { return { status: 'success', result: value, } } else if (event != null && value) { return { status: 'success', result: value, } } else if (signature != null && value) { return { status: 'success', result: value, } }
return { status: 'empty', result: null, } }, 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) } } },}
Contract Metadata loader
Create an in-memory cache for contract meta-information. Using ERC20RPCStrategyResolver
we will automatically retrieve token meta information from the contract such as token name, decimals, symbol, etc.
import type { ContractData, VanillaContractMetaStore } from '@3loop/transaction-decoder'import { ERC20RPCStrategyResolver } from '@3loop/transaction-decoder'
const contractMetaCache = new Map<string, ContractData>()
const contractMetaStore: VanillaContractMetaStore = { strategies: [ERC20RPCStrategyResolver], 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: async ({ address, chainID }, result) => { const key = `${address}-${chainID}`.toLowerCase()
if (result.status === 'success') { contractMetaCache.set(key, result.result) } },}
Finally, you can create a new instance of the LoopDecoder class:
import { TransactionDecoder } from '@3loop/transaction-decoder'
const decoder = new TransactionDecoder({ getPublicClient: getPublicClient, abiStore: abiStore, contractMetaStore: contractMetaStore,})
Decoding a Transaction
Now that we have all the necessary components, we can start decoding a transaction. For this example, we will use the following transaction:
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()