ABI Lookup & Decoding
Decode raw transaction calldata, function selectors, and event topic hashes against an ever-growing registry backed by 4byte.directory and Sourcify.
The ABI registry exists so that block-explorer-style decoders can answer "what does 0xa9059cbb... mean?" without each consumer maintaining their own selector table. Internally, CryptaChain runs nightly 4byte sync (selectors + event topics) and weekly Sourcify sync (verified-contract full ABIs per chain).
Endpoint map
| Endpoint | Returns | Use |
|---|---|---|
GET /v1/abi/function/{selector} | Function signature for a 4-byte selector | Decode tx.input[:10] |
GET /v1/abi/event/{topicHash} | Event signature for a 32-byte topic-0 | Decode receipt logs |
GET /v1/abi/contract/{chainId}/{address} | Full verified ABI (Sourcify) | Static analysis, codegen |
POST /v1/abi/decode | One-shot decode of {chainId, calldata} | Tx-row enrichment |
Selectors are 4 bytes / 10 hex chars including 0x (e.g. 0xa9059cbb). Topic hashes are 32 bytes / 66 hex chars including 0x. Both are case-insensitive in the request; addresses on contract lookups are lower-cased before the database key is built.
Example 1 — resolve a function selector
The first four bytes of a contract call's input are the function selector. Look it up:
curl 'https://api.cryptachain.com/v1/abi/function/0xa9059cbb' \
-H "X-API-Key: $CRYPTACHAIN_API_KEY"import os, requests
r = requests.get(
"https://api.cryptachain.com/v1/abi/function/0xa9059cbb",
headers={"X-API-Key": os.environ["CRYPTACHAIN_API_KEY"]},
)
print(r.json())const r = await fetch('https://api.cryptachain.com/v1/abi/function/0xa9059cbb', {
headers: { 'X-API-Key': process.env.CRYPTACHAIN_API_KEY! },
});
console.log(await r.json());Response:
{
"selector": "0xa9059cbb",
"signature": "transfer(address,uint256)",
"name": "transfer",
"source": "4BYTE"
}Unknown selector returns 404. The source is 4BYTE (community-submitted), SOURCIFY (verified-contract ABI), MANUAL (curated override), or OTHER.
Example 2 — resolve an event topic
Receipts come back with topics as an array; the first one (topic-0) is the event signature's keccak256 hash. Look it up:
curl 'https://api.cryptachain.com/v1/abi/event/0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef' \
-H "X-API-Key: $CRYPTACHAIN_API_KEY"Response:
{
"topic_hash": "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"signature": "Transfer(address,address,uint256)",
"name": "Transfer",
"source": "4BYTE"
}That hash is the canonical ERC-20 Transfer event. Every wallet integration ends up calling this thousands of times — cache aggressively on your end; CryptaChain also serves this from cache, but a local LRU of the top ~1000 hashes is essentially zero cost and saves a round-trip.
Example 3 — full contract ABI from Sourcify
For verified contracts, the full ABI is mirrored from Sourcify by chain ID + address:
curl 'https://api.cryptachain.com/v1/abi/contract/1/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48' \
-H "X-API-Key: $CRYPTACHAIN_API_KEY"Response (truncated):
{
"chain_id": 1,
"address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"source": "SOURCIFY",
"abi": [
{ "type": "function", "name": "transfer",
"inputs": [{ "name": "to", "type": "address" }, { "name": "amount", "type": "uint256" }],
"outputs": [{ "type": "bool" }],
"stateMutability": "nonpayable" },
{ "type": "event", "name": "Transfer", "anonymous": false,
"inputs": [
{ "indexed": true, "name": "from", "type": "address" },
{ "indexed": true, "name": "to", "type": "address" },
{ "indexed": false, "name": "value", "type": "uint256" }
] }
]
}If the contract is not verified on Sourcify, the endpoint returns 404. Sourcify coverage is best on Ethereum / Polygon / Arbitrum / Optimism / Base; coverage on smaller L2s is patchy. For unverified contracts the function-selector endpoint is your fallback — even if no full ABI exists, the 4byte registry usually has the top-level call.
Example 4 — one-shot decode (selector + raw args)
If you have raw calldata from a transaction and want the function name plus args without picking it apart yourself, use the decode endpoint:
curl -X POST 'https://api.cryptachain.com/v1/abi/decode' \
-H "X-API-Key: $CRYPTACHAIN_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"chainId": 1,
"calldata": "0xa9059cbb000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa9604500000000000000000000000000000000000000000000000000000000000003e8"
}'Response:
{
"selector": "0xa9059cbb",
"signature": "transfer(address,uint256)",
"name": "transfer",
"argsHex": "000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa9604500000000000000000000000000000000000000000000000000000000000003e8",
"status": "OK"
}Status values: OK (decoded), UNKNOWN_SELECTOR (no selector match — name and signature are null but argsHex is still extracted), MALFORMED (calldata is null / non-hex / shorter than 4 bytes).
MVP scope
The decode endpoint currently returns argsHex as a single 32-byte-aligned hex string — it does not yet pre-split args by ABI parameter type. Per-type unpacking (decoded address, uint256, etc.) is on the roadmap. If you need that today, hand off argsHex to your local ethers / web3.py decoder using the returned signature.
Common pitfalls
Selector collisions
4-byte selectors are not unique — keccak256(signature)[:4] can collide. The 4byte registry stores all known collisions, but the endpoint returns the most-popular one by usage count. If you care about disambiguation (audit / forensic work), fetch the verified contract's ABI via /v1/abi/contract/{chainId}/{address} first; the contract ABI is unambiguous.
Cache locally
The top 100 selectors account for >80% of mainnet traffic. Stash them in an in-memory LRU on your side — every cache hit saves a request and a credit.
Try it live
The /playground lets you call any ABI endpoint with your API key. Filter operations by tag ABI.
Next steps
- Blockchain data endpoints — fetching transactions and receipts that produce calldata to decode
- Wallet swaps — pre-decoded DEX swap events with token / amount fields already extracted