RoadToChain Logo
RoadToChain
T1/M1.3/ABI as translator — mapping frontend to bytecode payloads
beginner 11m read

ABI as translator — mapping frontend to bytecode payloads

JSON interfaces, function selectors, encoding args, and translating frontend calls to raw hex payloads.

#abi #encoding #selectors

When I first looked at a frontend Web3 codebase, I saw developers importing a massive, ugly JSON file called the ABI (Application Binary Interface) and passing it to ethers.js:

types.ts
typescript
const contract = new ethers.Contract(address, ABI, signer);

I kept asking: Why do we need this massive JSON file? Can't JavaScript just talk directly to the smart contract? Why does a Web3 app fail to compile if the ABI is slightly wrong?

The truth is, the ABI is the Rosetta Stone of Web3. It translates human-friendly JavaScript calls into raw hexadecimal payloads that the EVM can execute.


1. The Problem: EVM Speaks Hex, JS Speaks Objects

In a standard Web2 app, when you want to call an API, you send a JSON payload: POST /users { "id": 12, "name": "Alice" }. The server reads the string, parses the JSON, and maps it.

In Web3:

  • The EVM has no concept of strings, object keys, or JSON.
  • The EVM only understands hexadecimal bytes.
  • The EVM doesn't know what a function named transfer(address,uint256) is.

When you call contract.transfer("0x99...", 1000) in JavaScript, a library like ethers.js or viem uses the ABI JSON to format and serialize that function call into a single, compact hex string:

SmartAccount.sol
0xa9059cbb00000000000000000000000099...000000000000000000000000000003e8

This raw hex string is packed into the data field of your transaction.


2. Anatomy of a Hex Payload: Function Selectors

Every transaction data payload calling a smart contract is structured into two parts:

  1. The Function Selector (First 4 bytes / 8 characters): This tells the EVM which function inside the contract to execute.
  2. The Encoded Arguments (Remaining bytes): This passes the parameters to the function.

How is the Function Selector calculated?

The EVM uses a cryptographic hash (Keccak-256) of the canonical function signature.

For example, to call transfer(address,uint256):

  1. Canonical signature: transfer(address,uint256) (no spaces, uint256 instead of uint!)
  2. Take Keccak-256 hash:
    SmartAccount.sol
    Keccak256("transfer(address,uint256)") = 0xa9059cbbb25097...
    
  3. Take the first 4 bytes: 0xa9059cbb.

Every time the EVM runs your transaction, it looks at the first 4 bytes (0xa9059cbb), matches it against its internal list of function selectors, and jumps directly to that block of code!


3. Layman Explanation: The Translator Booth

Imagine you are trying to order food at a restaurant in a country where the chef only speaks a very specific code language (e.g. they only understand index numbers: "Option 42, Value 12").

You only speak English: "I want to order 3 pizzas."

The ABI is your printed menu listing English names next to the chef's secret index numbers:

  • "orderPizza(uint256)" $\rightarrow$ Menu Item 0x882a1b

Your frontend library is the translator booth. It takes your request ("3 pizzas"), looks at the ABI menu, translates it to 0x882a1b0000000000000000000000000000000000000000000000000000000000000003 (Menu Item 0x882a1b, Quantity 3 encoded), and hands it to the waiter (the RPC node) to deliver to the chef (the EVM).

If the menu is wrong or outdated, you translate the wrong code, the chef gets confused, and your order (the transaction) fails.

ABI Encoding — From Function Call to Hex Calldata
The ABI acts as the schema translation layer, mapping human-readable frontend calls to structured hexadecimal bytes that the EVM can parse.

4. Technical Breakdown: ABI JSON Structure

An ABI is generated automatically by the compiler. It is an array of objects describing variables, functions, and events:

package.json
json
[
  {
    "inputs": [
      { "name": "recipient", "type": "address" },
      { "name": "amount", "type": "uint256" }
    ],
    "name": "transfer",
    "outputs": [{ "type": "bool" }],
    "stateMutability": "nonpayable",
    "type": "function"
  }
]

This tells the frontend:

  • The function transfer exists.
  • It accepts an address and a uint256.
  • It returns a bool.
  • It does not accept payments (nonpayable).

With this metadata, ethers can perform client-side validation: if you pass a string instead of a number for amount, it rejects the transaction before broadcasting, saving you failed transaction gas fees!


// Reality Check

Many developers copy and paste ABIs manually into their frontend code, which leads to compilation errors when contracts evolve. Always use compiler artifact pipelines (like Hardhat's artifacts output or Wagmi's CLI/types integrations) to keep your ABIs synced automatically.

— Production Engineering Principle

System Design Challenge
Think Active

Take a signature like balanceOf(address). Calculate the Keccak-256 hash of that string using an online tool (like keccak-256.net). Extract the first 4 bytes. Compare it with the function selector shown on Etherscan for standard ERC-20 token contract calls.

[ Think Before Continuing ]

Was this lesson helpful?

Let us know what you think of this specification. (submitting anonymously)