🧰 UtlKit

How to Use the ABI Encoder Tool: 4-Byte Selector from keccak256, 32-Byte Words, Offsets and Type-Blind Decode in the Browser

keccak256 over the canonical signature yields the 4-byte selector, each static value becomes one 32-byte zero-padded word, dynamic values carry an offset measured from the first head word, and the type-blind decode tab reads the raw words back, all computed in the browser with js-sha3 and BigInt.

The ABI Encoder turns a Solidity function signature and a list of parameter values into the 0x-prefixed calldata string you would paste into a transaction, and it does all of that in the browser. There is no backend: the page bundles js-sha3 for keccak256, uses native BigInt for the 32-byte words, and uses the built-in UTF-8 encoder to size string payloads. Two tabs cover the workflow: encode takes a signature plus values and produces the selector followed by one 32-byte word per value, while decode takes raw calldata and prints what it can. A live selector preview updates under the signature field as you type, and a counter reports the hex character count of the result, so you always see the size before you copy. Start with the ABI Encoder tool and enter a signature in the canonical form, for example transfer(address,uint256).

How the 4-byte selector is computed

The selector is the first 4 bytes of the keccak256 hash of the signature string exactly as you typed it. keccak256 is a 256-bit hash, so the tool computes the full 64-hex-character digest and keeps the first 8 hex characters, which is the 4-byte prefix that EIP-196 defines for contract method dispatch. That is why formatting matters: transfer(address,uint256) hashes to the well-known 0xa9059cbb, but the same signature with a space after the comma hashes to a completely different digest and produces a different prefix, so the call would target the wrong function or revert. The preview under the signature input recomputes the selector on every keystroke, so a typo is visible immediately. If you want to see the whole digest, the Keccak Hash tool shows the full 64-hex output, the first 4 bytes as the function selector, and the first 20 bytes in address form for the same input.

How static values become 32-byte words

Every static parameter is written as one 32-byte word, right-justified with zero padding. An address must be exactly 40 hex characters, with or without the 0x prefix, and is padded to 64. A boolean accepts true, false, 1 or 0. A bytes32 value accepts 1 to 64 hex characters. Integers cover the unsigned family from the 8-bit type up to uint256 and their signed 128-bit and 256-bit cousins, and each value is range-checked before it is encoded: an int128 must sit between negative 2^127 and 2^127 minus 1, and an int256 between negative 2^255 and 2^255 minus 1. Out-of-range input is rejected instead of silently wrapped. A negative integer is stored in complement form, meaning the value plus 2^bits, where bits is the width of the type: stake(int256) with -1 encodes to selector 0x77b71df0 followed by a word of 64 f characters, which is exactly what a signed -1 looks like in 32 bytes.

How dynamic values get their offset

String, bytes and array parameters are dynamic, so their head slot holds an offset instead of the data. The offset is measured in bytes from the first head word, the 32-byte word that sits right after the 4-byte selector, not from the selector itself. With a single dynamic argument the data starts at the first tail word, which is 32 bytes past the head start, so the offset word is simply 32. The tail then carries a length word followed by the actual data padded to a multiple of 32 bytes. The length counts UTF-8 bytes, not characters, so an emoji consumes 4 bytes in the count. greet(string) with the value hi produces selector 0xead710c4, an offset word of 32, a length word of 2, and a data word that starts with 0x6869, which is h and i in hex, padded out with zeros.

A complete encode, word by word

Put it together with transfer(address,uint256), recipient 0x1234567890123456789012345678901234567890 and amount 100. The result is 0xa9059cbb00000000000000000000000012345678901234567890123456789012345678900000000000000000000000000000000000000000000000000000000000000000064, and the counter reads 136 hex characters, which is exactly 8 for the selector plus 64 for the address word plus 64 for the amount word. The amount 100 is right-justified in the final word, where 100 is 64 in hex, and the 48 hex characters of the address sit at the end of its word with 24 zeros in front.

What the decode tab does and does not do

The decode tab strips the 0x prefix, takes the first 8 hex characters as the selector, and reads every following 64-hex-character chunk as an unsigned 256-bit integer printed in decimal. Feed it the stake(uint256,uint256) calldata and it reports selector 0x7b0472f0 with two parameters, 100 and 2026, which round-trips cleanly. The decoder is type-blind: it has no ABI and no type list, so a transfer calldata comes back with the recipient as the 48-digit decimal 103929005307130220006098923584552504982110632080 instead of an address, and a string parameter comes back as raw offset, length and data words rather than text. When you need the bytes behind a word, paste the hex portion through the Hex Converter to read it back as UTF-8 text.

What this tool is not

The ABI Encoder is not a typed contract explorer: it encodes and decodes raw words, and if you want to look up a transaction, address or token on-chain, the Block Explorer tool deep-links to Etherscan, BscScan, PolygonScan and the major L2 explorers. It is not an address checker either: the recipient word is 32 bytes and the address is the last 40 hex characters of that word, while the ETH Address Check tool re-formats an address into EIP-55 mixed case and flags a checksum mismatch. It is not a fee calculator: calldata length changes gas usage, and the Gas Fee Estimator turns a gas limit plus base and priority fees into an ETH and USD cost. It is not a base converter for values: for small numbers the Number Base Converter confirms decimal-to-hex, like 100 becoming 64, but it only handles 32-bit ranges. And it is not a byte-level text tool: the length word counts UTF-8 bytes, and if you want to see how a text string expands character by character, the Text to Binary guide walks through the 8-bit expansion for ASCII input.

Related Tools

Frequently Asked Questions

Does the ABI Encoder talk to any API or server?

No. The page bundles js-sha3 and runs keccak256 locally, builds each 32-byte word with BigInt, and measures string payloads with the UTF-8 encoder built into the browser. Nothing you type leaves the page except the optional feedback form.

Why does a single space change the selector?

keccak256 runs over the signature string exactly as typed. The canonical form has no spaces after the comma, and it is that exact byte sequence that is hashed. Add a space and you hash a different string, which yields a different digest and a different 8-hex prefix, so the call targets the wrong function or reverts. The live preview exists so you catch this before you copy.

Why does the decoded address word print as a long decimal?

The decoder is type-blind. It has no ABI and no type list, so it reads every 64-hex chunk as an unsigned 256-bit integer in decimal. The address is the last 40 hex characters of its 32-byte word, so work from the hex, not the printed decimal, when you need to hand the value to another tool.

Why is the first string offset 32 and not 4?

Offsets are measured in bytes from the first head word, the 32-byte word right after the 4-byte selector, not from the selector itself. With one dynamic argument, the data starts at the first tail word, which is 32 bytes past the head start, so the offset word is 32. For greet(string) with hi, the result is selector 0xead710c4, offset 32, length 2, and a data word starting with 0x6869 padded to 32 bytes.

Why does the counter report hex characters instead of bytes?

Each byte becomes two hex characters, so a counter reading 136 means 68 bytes on the wire, the 4-byte selector plus two 32-byte words. The tool shows hex characters because that is the form you copy, and it keeps the word structure obvious: every static value you add lengthens the string by 64 characters, and the selector adds 8.

Related Articles