A smart contract ABI (Application Binary Interface) is the JSON description of a contract’s callable functions and emitted events, the piece that tells a wallet, a script, or a query layer what to send and how to read what comes back. Without it, a contract on Ethereum or any EVM chain is just a bytecode blob and an address: everything is there, but nothing is labeled. Every library that calls a contract, viem, ethers, web3.py, ultimately turns your function call into raw calldata and your event logs into typed values by reading this JSON first.
TL;DR
A smart contract ABI is a JSON array describing every public function and event on a contract, its name, argument types, return types, and whether it reads or writes state. Compilers generate it automatically from Solidity or Vyper source, and libraries like viem use it to encode calls and decode results. It is not the same thing as bytecode, and it is not always what Etherscan shows you for a proxy contract.
What an ABI is (and what it isn’t)
An ABI is a data format, not a running service or a piece of the contract itself. It’s a JSON array, one entry per function, event, error, or constructor, and it lives outside the contract on-chain. The compiler emits it as a build artifact; nobody stores it on the blockchain unless a project chooses to publish it somewhere like Etherscan or Sourcify alongside verified source code.
ABI vs API. The two terms get used almost interchangeably by people new to Ethereum, and the confusion is understandable, both describe how to talk to something. A REST API is a live, running server that documents its own endpoints (often as an OpenAPI/Swagger spec) and can change its behavior between requests. An ABI describes a fixed, deployed piece of bytecode that can’t change what functions it exposes once it’s on-chain (barring a proxy upgrade, covered below). There is no ABI server to call. You either already have the JSON file, or you get it from somewhere: a build artifact, a block explorer, or a resolution service.
ABI vs bytecode. Bytecode is what actually executes on the EVM, the opcodes a validator runs when your transaction hits the contract. It contains no function names, no argument labels, no human-readable anything. What it does contain is a dispatch table: a sequence of checks near the top of the runtime code that compares the first four bytes of your calldata against a list of known values and jumps to the matching function’s logic. The ABI is the human-readable map to that dispatch table. Strip the ABI away and the bytecode still runs exactly the same, but nothing you send it makes sense unless you already know what the correct calldata should look like.
Anatomy of an ABI JSON file
Here’s a real, minimal ABI for two of the most common ERC-20 interface members, the balanceOf function and the Transfer event, in the standard JSON shape a compiler outputs:
[ { "type": "function", "name": "balanceOf", "inputs": [{ "name": "account", "type": "address" }], "outputs": [{ "name": "", "type": "uint256" }], "stateMutability": "view" }, { "type": "event", "name": "Transfer", "inputs": [ { "name": "from", "type": "address", "indexed": true }, { "name": "to", "type": "address", "indexed": true }, { "name": "value", "type": "uint256", "indexed": false } ], "anonymous": false }]ABI field reference
typemarks the entry asfunction,event,error,constructor,fallback, orreceive. It’s how a parser tells a callable method from a log definition.nameis the function or event name exactly as written in the source, case-sensitive, and part of what gets hashed into the selector or topic.inputsandoutputsare ordered arrays of{ name, type }. Order matters: it’s positional in the encoded calldata, not keyed by name.stateMutabilityon a function isview,pure,nonpayable, orpayable.viewandpurecalls cost no gas and don’t need a signed transaction, they’re plaineth_callreads.indexedon an event input means that argument is stored as a searchable topic in the log, up to three per event. Non-indexed inputs are ABI-encoded together into the log’sdatafield instead.anonymouson an event, almost alwaysfalse, means the event’s topic0 (its signature hash) is omitted from the log, saving gas at the cost of making the event un-filterable by type.
balanceOf is a view function: it takes one address argument, returns one uint256, and never needs a signed transaction, any node will run it as a free eth_call. Transfer is the event every ERC-20 transfer emits: from and to are indexed, meaning you can filter logs by either address directly at the RPC layer, while value sits in the log’s data payload and has to be decoded after the fact.
The same interface expressed as a viem human-readable ABI, useful when you’d rather write TypeScript than hand-assemble JSON:
import { parseAbi } from "viem";
const erc20Abi = parseAbi([ "function balanceOf(address account) view returns (uint256)", "event Transfer(address indexed from, address indexed to, uint256 value)",]);parseAbi compiles this array of human-readable signatures into the same structured JSON shown above, and viem’s own erc20Abi export ships the full standard interface pre-built if you don’t want to write it out by hand.
How the ABI maps to calldata
The reason an ABI matters at the network level, not just for readability, is that it’s what turns a function call into the bytes a contract actually receives. A transaction (or an eth_call) sends raw calldata: a hex blob with no field names attached. The first four bytes are the function selector, the leading four bytes of the keccak256 hash of the function’s canonical signature, a normalized string of the function name plus its argument types with no spaces or parameter names.
For balanceOf(address), that hash and selector work out to:
keccak256("balanceOf(address)") = 0x70a08231b98ef4ca268c9cc3f6b4590e4bfec28280db06bb5d45e689f2a360be
first 4 bytes (the selector) = 0x70a08231Everything after those four bytes is the ABI-encoded argument list, each argument packed into a 32-byte word in the order the ABI’s inputs array specifies. A client reads the ABI, computes the selector for the function you’re calling, encodes your arguments according to their declared types, and concatenates the two. On the way back, it does the reverse: it reads the outputs types from the same ABI entry and decodes the raw return bytes into typed values.
This is also why a selector alone is a dead end without an ABI. 0x70a08231 is a one-way hash, you can’t reverse it back into balanceOf(address). If you’re staring at raw calldata with no ABI in hand, see how to decode Ethereum calldata without an ABI file for how to resolve it from just the contract address.
How to get the ABI of a smart contract
Where the ABI comes from depends on whether you have the source code, the toolchain output, or just an address.
From Remix, if you’re compiling source directly in the browser: open the Solidity Compiler tab, compile your contract, then scroll to the bottom of the compiler panel and click the ABI copy icon next to the contract name. It copies the JSON array straight to your clipboard.
From Foundry, if you’re working locally with forge:
forge inspect <Contract> abiThis prints the ABI JSON for any contract in your project, resolved from the compiled artifact, no need to dig through out/ manually.
From Hardhat, the ABI lives in the compiled artifact JSON, one file per contract, under:
artifacts/contracts/<ContractName>.sol/<ContractName>.jsonThe abi field inside that file is the array you want; the rest of the file (bytecode, source map, and so on) you can ignore for this purpose.
From Etherscan, for a contract you didn’t compile yourself: open the contract’s page, click the Contract tab, and if it’s verified you’ll find a Contract ABI box with a copy button. The same data is available programmatically via Etherscan’s getabi API action for automation.
By address, in one step, when you don’t want to touch a compiler or an explorer UI at all: paste the address into evmquery’s Contract ABI tool, which resolves the contract’s read functions server-side, following proxies to the implementation automatically, and gives you copy or download options for the JSON. It covers view and pure functions only; for the complete ABI including write functions and events, a verified contract’s Etherscan or Sourcify page is still the source.
Why the ABI on Etherscan is sometimes wrong
Etherscan shows the ABI of the contract at the address you searched. For a proxy contract, that’s the proxy’s own ABI, and a minimal proxy typically exposes almost nothing: an implementation() getter, maybe an admin function or two, and a fallback that forwards everything else via delegatecall. The functions you actually care about, balanceOf, transfer, whatever the token or protocol does, live on a separate implementation contract the proxy points to, and that address is not the one you searched.
If the proxy’s own contract is unverified, Etherscan may show you no ABI at all for the address you’re looking at, even though the underlying protocol is fully verified, because the verified source sits at a different address. Run the address through evmquery’s Proxy Detector to confirm whether it’s a proxy and see which implementation it currently points to. We covered the scope of this across major protocols in which contracts are upgradeable proxies, and getting the ABI of an unverified or proxy contract walks through recovering the right interface in both cases.
Next steps
- Paste an address and get its read-only ABI JSON directly: Contract ABI tool
- Check whether an address is a proxy before trusting its ABI: Proxy Detector
- Need a standard interface to copy: ERC-20, ERC-721, and ERC-1155 ABI JSON
- Have calldata but no ABI file: decode Ethereum calldata without an ABI file
- Building on read access to contracts generally: see what evmquery gives developers



