A library to fetch and decode smart contract related bytecode data from an EVM node.
EvmDecoder can determine if an address is a contract and (for supported types) return information about and/or properties of the contract. It can also decode transaction input data when provided with the respective (or matching) ABIs.
All feature requests and contributions will be considered!
EvmDecoder aims to identify common types of smart contracts by matching unique function signatures in the deployed bytecode of contracts. When a contract is identified as a type of proxy, the decoder will attempt to determine the contract address and type behind the proxy recusively and classify the final contract in the proxy chain.
Types of contracts that can be identified:
- Proxies: ERC897, ERC1822, ERC1967
- Proxy Clones: ERC1167, ERC3448, Simple Multisig Clone
- GnosisSafe
- Gnosis Multisig
- Diamond Proxies (ERC2535 / multi-facet proxies)
- Simple Tokens (ERC20)
- NFTs (ERC721)
- MultiTokens (ERC1155)
- FlashLoan lenders and receivers (ERC3156)
- Contract Registries (ERC1820)
- Offchain Resolvers (ERC3668)
yarn add evmdecoder
or npm install evmdecoder
to install.
import { EvmDecoder } from 'evmdecoder'
const evmDecoder = new EvmDecoder({
eth: {
url: 'http://localhost:8545'
},
abi: {
directory: './abis'
}
})
await evmDecoder.initialize()
// ethereum tx hash: 0xd698c9191a88c253a926ef1d7eecf22bea9370a0d8dc7fb9e819bad0ae0f1d9e
const input =
'0xe63d38ed000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000083d50977190c592bb9f03054500b1fd81b53dd49000000000000000000000000dc7cd9b725726f5dcd646d3a2c768e4636a89578000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000002c68af0bb14000000000000000000000000000000000000000000000000000002c68af0bb140000'
const functionCall = await evmDecoder.decodeFunctionCall({ input })
console.log(JSON.stringify(functionCall))
Output:
{
"name": "disperseEther",
"signature": "disperseEther(address[],uint256[])",
"params": [
{
"name": "recipients",
"type": "address[]",
"value": ["0x83D50977190c592BB9F03054500B1fd81B53Dd49", "0xDC7cD9b725726F5Dcd646D3A2C768E4636a89578"]
},
{
"name": "values",
"type": "uint256[]",
"value": ["200000000000000000", "200000000000000000"]
}
],
"args": {
"recipients": ["0x83D50977190c592BB9F03054500B1fd81B53Dd49", "0xDC7cD9b725726F5Dcd646D3A2C768E4636a89578"],
"values": ["200000000000000000", "200000000000000000"]
}
}
import { EvmDecoder } from 'evmdecoder'
const evmDecoder = new EvmDecoder({
eth: {
url: 'http://localhost:8545'
},
abi: {
directory: './abis'
}
})
await evmDecoder.initialize()
const input =
'0x23b872dd000000000000000000000000e809e745ebd8e37d2ed783b48d328b2b77b7dd2c0000000000000000000000006a60114b678b04be3fa094eb5abdc2d4ecd80769000000000000000000000000000000000000000000000000000000000000005c'
const address = '0x78d61C684A992b0289Bbfe58Aaa2659F667907f8'
const functionCall = await evmDecoder.decodeFunctionCall({ input, address })
console.log(JSON.stringify(functionCall))
Output:
{
"name": "transferFrom",
"signature": "transferFrom(address,address,uint256)",
"params": [
{
"name": "_from",
"type": "address",
"value": "0xE809E745EbD8E37d2ed783b48d328B2b77B7dD2C"
},
{
"name": "_to",
"type": "address",
"value": "0x6a60114B678b04Be3FA094eB5aBdC2d4ecD80769"
},
{
"name": "_value",
"type": "uint256",
"value": 92
}
],
"args": {
"_from": "0xE809E745EbD8E37d2ed783b48d328B2b77B7dD2C",
"_to": "0x6a60114B678b04Be3FA094eB5aBdC2d4ecD80769",
"_value": 92
},
"extra": {
"tokenUri": "ipfs://QmUAxNgx55CdusaUAxEF1ua3PNmGaUHbas1vGX6qBr1awz/76.json"
}
}
ERC20:
import { EvmDecoder } from 'evmdecoder'
const evmDecoder = new EvmDecoder({
eth: {
url: 'http://localhost:8545'
}
})
await evmDecoder.initialize()
// USDC
const address = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'
const info = await evmDecoder.contractInfo({ address })
console.log(JSON.stringify(info))
Output:
{
"isContract": true,
"fingerprint": "5157940bcbc1f14cc9cd9b04787267e55a3cb6455b14d6eb8fdb2d7f1057a3aa",
"contractName": "ERC20_Proxy",
"contractType": {
"name": "Token",
"proxies": [
{
"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"standard": "ERC897",
"target": "0xa2327a938febf5fec13bacfb16ae10ecbc4cbdcf"
}
],
"standards": ["ERC20"]
},
"properties": {
"name": "USD Coin",
"symbol": "USDC",
"decimals": "6"
}
}
NFT:
import { EvmDecoder } from 'evmdecoder'
const evmDecoder = new EvmDecoder({
eth: {
url: 'http://localhost:8545'
}
})
await evmDecoder.initialize()
// SUPERGUCCI
const address = '0x78d61C684A992b0289Bbfe58Aaa2659F667907f8'
const info = await evmDecoder.contractInfo({ address })
console.log(JSON.stringify(info))
Output:
{
"isContract": true,
"fingerprint": "188e6dbcca2606630ad5b1ac09ae26f317820d1e230b5be637a06ff69436dc86",
"contractType": {
"name": "NFT",
"metadata": true,
"baseUri": false,
"enumeration": true,
"receive": ["ERC721"],
"standards": ["ERC721"]
},
"properties": {
"name": "SUPERPLASTIC: SUPERGUCCI",
"symbol": "SPGCI",
"totalSupply": "500"
}
}
Defaults shown
{
eth: {
http: {
timeout: 60_000,
validateCertificate: false,
requestKeepAlive: true,
maxSockets: 256
},
client: {
maxBatchSize: 100,
maxBatchTime: 0,
individualReceipts: true,
maxRetryTime: 10_000
}
},
abi: {
abiFileExtension: '.json',
directory: './abis',
searchRecursive: true,
fingerprintContracts: true,
requireContractMatch: false,
decodeAnonymous: true,
reconcileStructShapeFromTuples: true
},
contractInfo: {
maxCacheEntries: 25_000
}
}
Prerequisites
- Node.js 12+ (16 recommended)
- Yarn package manager (recent/latest version)
- Rust toolchain (rustc 1.41.0+)
- wasm-pack (recent/latest version)
- Clone Repo
- Install dependencies
yarn install
- Build
yarn build
before committing, run yarn format
and re-add anything updated.