An ABI — Application Binary Interface — is a JSON file that describes the functions and events of a smart contract. Every wallet, every dApp, every block explorer, every SDK that talks to a specific contract uses the contract's ABI as the translation layer. If you have ever wondered how MetaMask knows to show "Approve USDC" as a friendly button rather than raw hex data, the ABI is the answer.
Understanding the ABI is not required to be a crypto user, but it is required to be a serious dApp developer, a smart-contract security researcher, or anyone doing manual contract interaction. It's also the fastest way to feel less mystified by how everything on-chain fits together.
What the ABI contains
An ABI is a JSON array where each entry describes one function or event of the contract. For a simple ERC-20 token like USDC, the ABI looks like:
``` [ { "type": "function", "name": "transfer", "inputs": [ {"name": "recipient", "type": "address"}, {"name": "amount", "type": "uint256"} ], "outputs": [{"name": "", "type": "bool"}], "stateMutability": "nonpayable" }, ... more entries for other functions and events ] ```
Each entry specifies:
- The name of the function
- The types and names of its inputs
- The types and names of its outputs
- Whether it modifies state (nonpayable, payable, view, pure)
That's it. It's a machine-readable description of what the contract can do.
Why we need it
Ethereum contracts don't run source code — they run compiled bytecode. When you call a function on a contract, you don't send "transfer(0xABC, 100)". You send raw hex data that encodes the function selector (4 bytes derived from the function signature) followed by ABI-encoded arguments.
For "transfer(0xABC, 100)" the actual calldata is something like: ``` 0xa9059cbb 000000000000000000000000abc... 0000000000000000000000000000000000000000000000000000000000000064 ```
A wallet or dApp can't display this raw hex to a user. It needs to translate. The ABI is the translator: "0xa9059cbb means the function transfer(address, uint256), the first argument is an address, the second is a uint256, this transaction is calling transfer with parameters 0xABC and 100."
Without the ABI, every function call is opaque. With the ABI, tools can parse the calldata into human-readable form.
Where the ABI comes from
The ABI is generated automatically when you compile a smart contract. Solidity compilers produce it as a build artifact alongside the bytecode.
For a deployed contract, you can get the ABI from:
- The project's GitHub repo (usually in the artifacts/ or build/ directory)
- Etherscan, on the Contract tab — Etherscan extracts and displays the ABI for verified contracts
- The project's SDK, which usually embeds ABIs for the contracts it interacts with
If a contract is verified on Etherscan, its ABI is publicly available. If not, the ABI can be reverse-engineered from the bytecode (harder, less reliable).
How wallets and dApps use ABIs
When you interact with a dApp like Uniswap:
1. The dApp's frontend has the Uniswap router contract's ABI embedded. 2. When you click "swap," the frontend uses the ABI to construct the correct calldata for the swap function with your parameters. 3. The frontend requests your wallet to sign a transaction with this calldata. 4. Your wallet, seeing the transaction is to a known contract, uses the ABI (which it might have from Etherscan or from a curated list) to display the transaction in human-readable form: "Swap 1 ETH for approximately 2,500 USDC." 5. You approve. The wallet signs. The transaction is broadcast.
The ABI is present at every step. Without it, you'd see raw hex and have to trust that it means what the dApp says it means.
Reading an ABI
You rarely need to read an ABI by hand — tools do the parsing. But if you want to see one, load it in a text editor or browser. Common patterns:
- **Type "function"**: a callable function
- **Type "event"**: a log entry the contract emits (Transfer, Approval, etc.)
- **Type "constructor"**: the constructor that ran at deployment
- **Type "error"**: a custom error type the contract can revert with
- **Type "fallback"**: the function called for unrecognized calldata
- **Type "receive"**: the function called for plain ETH transfers
Each function has:
- **stateMutability**: nonpayable (default), payable (can receive ETH), view (reads state, doesn't modify), pure (no state access at all)
- **inputs**: what arguments to pass
- **outputs**: what the function returns
- **name**: the function name
The security implication
Because the ABI is what determines how transactions are decoded and displayed, ABI-related failures can lead to user losses.
Example: a scam contract can implement a function called "SafeTransfer" (with capital S) that looks like transferFrom but actually does something malicious. If your wallet uses only the function name to decide what to show, it might display "Safe Transfer" as a benign action while the actual transfer is stealing from you.
Real wallets protect against this by:
- Using function selectors (4-byte hashes of the function signature) as the primary identifier, not just the name
- Cross-referencing with known good ABIs from Etherscan
- Warning when the target contract is not on a trusted allowlist
But the underlying risk exists whenever you're interacting with an unverified contract whose ABI is generated on the fly.
ABI encoding details (nice to have)
For developers, the ABI-encoding rules for arguments are worth knowing:
- **address**: 20 bytes, right-padded to 32 bytes
- **uint256**: 32 bytes, big-endian
- **bool**: 32 bytes (0 or 1)
- **bytes**: variable length, tail-encoded with a pointer + length prefix
- **string**: same as bytes (variable length, tail-encoded)
- **arrays**: variable length, tail-encoded
The specifics rarely matter for using contracts. They matter a lot for writing them.
What if the ABI is wrong
If a contract's ABI is incorrect (mismatched types, wrong names), calls will fail with errors like "invalid decoded data" or the function will silently do nothing.
This happens in practice when:
- The ABI you have is from a different version of the contract than the deployed one (upgraded, but you have the old ABI).
- The ABI was hand-edited and diverged from the actual bytecode.
- You're using an unverified contract's ABI generated by reverse-engineering.
Getting the ABI directly from Etherscan for verified contracts sidesteps most of these problems.
For developers
Standard development workflow includes the ABI in every step:
- **Hardhat** and **Foundry** generate ABIs during compilation into a `artifacts/` or `out/` directory.
- **Ethers.js** and **Viem** libraries take an ABI and produce a JavaScript/TypeScript interface for calling the contract.
- **Wagmi** for React uses ABIs (via viem) to type-check hook calls to contracts.
- **web3.py** for Python takes an ABI to construct call objects.
The pattern is universal: get the ABI, feed it to a library, use the library to construct and send transactions.
When you might read an ABI directly
Practical cases where you'd handle an ABI as a user rather than a developer:
- Verifying a contract's interface via Etherscan (the ABI is shown on the Contract tab)
- Using Etherscan's Write Contract feature (it uses the ABI to render forms for each function)
- Debugging a failed transaction by looking at the calldata and mapping it back to a function
- Working with an unverified contract by supplying the ABI from another source (say, a project's GitHub)
For most crypto users, the ABI is invisible infrastructure. But when things go wrong, understanding that the ABI exists and how it works is the first step to figuring out why.
The one-sentence version: the ABI is what turns "0xa9059cbb..." into "transfer 100 tokens to Bob," and it exists at every layer of the stack between the contract and the human.




