Documentation for ABI, the function interface language for Solidity. Generally, the ABI describes how to take binary Ethereum and transform it to or from types that Solidity understands.
Agent Discovery
Use ABI.describe/0..2 for progressive API discovery:
ABI.describe() # Level 1: all annotated modules
ABI.describe(:abi) # Level 2: functions in this module
ABI.describe(:abi, :encode) # Level 3: full contract for encode/2A static api_manifest.json covering every public function is emitted by
mix descripex.manifest --app hieroglyph (a dedicated mix hieroglyph.manifest wrapper ships in 1.2.0 alongside Phase 3 of the agent
economy work — see CHANGELOG). Downstream consumers may diff that manifest
across hieroglyph version bumps as a contract-stability check.
API Functions
| Function | Arity | Description | Param Kinds |
|---|---|---|---|
parse_specification | 1 | Parses an ABI specification document into a list of ABI.FunctionSelector structs. | doc: value |
get_abi_item | 3 | Finds an ABI fragment by name with optional input-type disambiguation for overloads. | abi: value, name: value, arg_types: value |
format_abi_item | 1 | Renders a FunctionSelector as its canonical Solidity signature string. | function_selector: value |
event_signature | 1 | Returns the 32-byte topic hash for an event signature. | function_signature: value |
encode_event_topics | 2 | Builds an eth_getLogs topic filter list from an event signature and indexed values. | function_signature: value, indexed_values: value |
decode_event | 4 | Decodes an event from raw log data and indexed topics. | function_signature: value, data: value, topics: value, opts: value |
encode_packed | 2 | Encodes values using Solidity's non-standard packed mode (abi.encodePacked). | signature_or_selector: value, values: value |
decode_error | 3 | Decodes selector-prefixed revert data against known custom-error definitions, plus the Solidity built-in Error(string)/Panic(uint256) errors. | revert_data: value, error_definitions: value, opts: value |
encode_error | 3 | Encodes args into selector-prefixed revert data for a named custom error. | signature_or_selector: value, data: value, opts: value |
decode_call | 3 | Decodes selector-prefixed calldata (4-byte method ID + ABI-encoded args) and verifies the selector matches. | signature_or_selector: value, calldata: value, opts: value |
decode | 3 | Decodes the given data based on the function or tuple signature. | function_signature: value, data: value, opts: value |
method_id | 1 | Returns the 4-byte function selector (method ID) for a function signature. | signature: value |
encode_constructor | 2 | Encodes constructor arguments for contract deployment (no selector prefix). | selector: value, data: value |
encode_call | 3 | Encodes args into selector-prefixed calldata for a named function. | signature_or_selector: value, data: value, opts: value |
encode | 2 | Encodes the given data into the function signature or tuple signature. | function_signature: value, data: value |
Summary
Functions
Return the list of modules registered with this library.
Decodes the given data based on the function or tuple signature.
Decodes selector-prefixed calldata (4-byte method ID followed by ABI-encoded args) and verifies the prefix matches the expected selector.
Decodes selector-prefixed revert data against a list of known custom-error definitions.
Decodes an event, including indexed and non-indexed data.
Return a Level 1 overview of all modules in this library.
Return Level 2 function list for a module (by full atom or short name).
Return Level 3 function detail (or nil if not found).
Encodes the given data into the function signature or tuple signature.
Encodes args into selector-prefixed calldata for a named function.
Encodes constructor arguments for contract deployment.
Encodes args into selector-prefixed revert data for a Solidity 0.8.4+ custom error.
Builds an eth_getLogs topic filter list for indexed event arguments.
Encodes a list of values using Solidity's non-standard packed mode.
Returns the signature for an event.
Renders an ABI.FunctionSelector as its canonical Solidity signature string.
Finds an ABI fragment by name in a parsed specification list.
Returns the 4-byte function selector (method ID) for a function signature.
Parses the given ABI specification document into an array of ABI.FunctionSelectors.
Functions
@spec __descripex_modules__() :: [module()]
Return the list of modules registered with this library.
@spec decode(binary() | ABI.FunctionSelector.t(), binary(), keyword()) :: [any()] | map() | {:error, {:strict_violation, term()}}
Decodes the given data based on the function or tuple signature.
In place of a signature, you can also pass one of the ABI.FunctionSelector structs returned from parse_specification/1.
Options
:decode_structs— whentrue, returns a map keyed by snake_case atoms derived from each parameter's name (instead of the default list). Field-name atoms must already exist in the VM atom table —decode/3callsString.to_existing_atom/1and raisesArgumentErrorwhen an atom has not been interned. See the README "Pre-interning atoms fordecode_structs: true" section for the one-liner migration.:strict— whentrue, rejects non-canonical bool/uint/int padding, trailing bytes after the declared payload, and string/bytes length prefixes that exceed the available data. Strict failures return{:error, {:strict_violation, detail}}.
Examples
iex> ABI.decode("baz(uint,address)", "00000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000001" |> Base.decode16!(case: :lower))
[50, <<1::160>>]
iex> ABI.decode("(address[])", "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000" |> Base.decode16!(case: :lower))
[[]]
iex> ABI.decode("(uint256)", "000000000000000000000000000000000000000000000000000000000000000a" |> Base.decode16!(case: :lower))
[10]
iex> ABI.decode("(string)", "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b457468657220546f6b656e000000000000000000000000000000000000000000" |> Base.decode16!(case: :lower))
["Ether Token"]
iex> ABI.decode("((uint256,uint256),string)", "000000000000000000000000000000000000000000000000000000000000001100000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000b457468657220546f6b656e000000000000000000000000000000000000000000" |> Base.decode16!(case: :lower))
[{0x11, 0x22}, "Ether Token"]
iex> ABI.decode("((uint256,(uint256,uint256)),string)", "0000000000000000000000000000000000000000000000000000000000000011000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000330000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000b457468657220546f6b656e000000000000000000000000000000000000000000" |> Base.decode16!(case: :lower))
[{0x11, {0x22, 0x33}}, "Ether Token"]
iex> File.read!("priv/dog.abi.json")
...> |> Jason.decode!
...> |> ABI.parse_specification
...> |> Enum.find(&(&1.function == "bark")) # bark(address,bool)
...> |> ABI.decode("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001" |> Base.decode16!(case: :lower))
[<<1::160>>, true]
iex> ABI.decode("(uint256 a,bool b)", "000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000001" |> Base.decode16!(case: :lower), decode_structs: true)
%{a: 10, b: true}
@spec decode_call(binary() | ABI.FunctionSelector.t(), binary(), keyword()) :: {:ok, [any()] | map()} | {:error, decode_call_error()}
Decodes selector-prefixed calldata (4-byte method ID followed by ABI-encoded args) and verifies the prefix matches the expected selector.
Symmetric counterpart to encode/2, which produces selector-prefixed
output. Use decode/2 for payload-only data (return values, or calldata
that has already been routed by selector).
Returns:
{:ok, decoded}— selector matched;decodedis the same shapedecode/3returns{:error, :calldata_too_short}— fewer than 4 bytes provided{:error, :selector_mismatch}— first 4 bytes don't match the expected selector{:error, :no_function_name}— the selector hasfunction: nil, so there's no selector to verify against; usedecode/3with the payload directly{:error, {:strict_violation, detail}}—strict: truerejected a non-canonical payload
Note
Only the selector check is wrapped in {:error, _}. When the selector
matches but the payload is malformed (truncated or wrongly-typed bytes),
the underlying decode/3 still raises — same contract as calling
decode/3 directly.
Examples
iex> calldata = ABI.encode("transfer(address,uint256)", [<<1::160>>, 100])
iex> ABI.decode_call("transfer(address,uint256)", calldata)
{:ok, [<<1::160>>, 100]}
iex> ABI.decode_call("deposit()", <<0xd0, 0xe3, 0x0d, 0xb0>>)
{:ok, []}
iex> ABI.decode_call("transfer(address,uint256)", <<0xde, 0xad, 0xbe, 0xef>>)
{:error, :selector_mismatch}
iex> ABI.decode_call("transfer(address,uint256)", <<0xa9, 0x05>>)
{:error, :calldata_too_short}
iex> ABI.decode_call(%ABI.FunctionSelector{function: nil, types: []}, <<0::32>>)
{:error, :no_function_name}
@spec decode_error(binary(), [binary() | ABI.FunctionSelector.t()], keyword()) :: {:ok, %{error: String.t() | nil, args: [any()] | map()}} | {:error, decode_error_error()}
Decodes selector-prefixed revert data against a list of known custom-error definitions.
Solidity 0.8.4 introduced
custom errors;
when a contract reverts with revert MyError(arg1, arg2), the revert data is
keccak256("MyError(type1,type2)")[0..3] ++ abi.encode(arg1, arg2) — exactly
the same shape as a call's selector-prefixed calldata.
This helper mirrors decode_call/3 for that shape: try each candidate error
signature against the revert's 4-byte prefix, decode the payload using
whichever matches first.
Built-in errors
Every Solidity revert path emits one of two compiler-defined errors, so they
are recognized implicitly — no caller needs to register them, and they resolve
even when error_definitions is []:
Error(string)— selector0x08c379a0. The standardrequire/revertreason string. Decodes to%{error: "Error", args: [reason]}.Panic(uint256)— selector0x4e487b71. The 0.8.xassert/arithmetic panic. Decodes to%{error: "Panic", args: [code]}, wherecodeis the integer panic code. The standard codes are:Code Meaning 0x01assertevaluated tofalse0x11arithmetic overflow or underflow 0x12division or modulo by zero 0x32array access out of bounds
A user-supplied definition whose selector collides with a built-in still wins:
error_definitions is consulted first, and the built-ins are only a fallback.
Mirrors viem's decodeErrorResult, which resolves Error/Panic implicitly.
Returns:
{:ok, %{error: name, args: decoded}}— the first definition matching the 4-byte selector, or a built-in error when none does.nameis the error's function name;decodedmatchesdecode/3's shape (a list of args){:error, :calldata_too_short}— fewer than 4 bytes provided{:error, :no_match}— no definition or built-in selector matched{:error, {:strict_violation, detail}}—strict: truerejected a non-canonical payload
Note
Only the selector match is wrapped in {:error, _}. When a selector
matches but the payload is malformed, the underlying decode/3 still
raises — same contract as decode_call/3.
Examples
iex> revert_data = ABI.encode("InsufficientBalance(uint256,uint256)", [10, 100])
iex> ABI.decode_error(revert_data, ["InsufficientBalance(uint256,uint256)"])
{:ok, %{error: "InsufficientBalance", args: [10, 100]}}
iex> revert_data = ABI.encode("Unauthorized(address)", [<<1::160>>])
iex> ABI.decode_error(revert_data, [
...> "InsufficientBalance(uint256,uint256)",
...> "Unauthorized(address)"
...> ])
{:ok, %{error: "Unauthorized", args: [<<1::160>>]}}
iex> revert_data = ABI.encode("Error(string)", ["insufficient balance"])
iex> ABI.decode_error(revert_data, [])
{:ok, %{error: "Error", args: ["insufficient balance"]}}
iex> revert_data = ABI.encode("Panic(uint256)", [0x11])
iex> ABI.decode_error(revert_data, [])
{:ok, %{error: "Panic", args: [17]}}
iex> ABI.decode_error(<<0xde, 0xad, 0xbe, 0xef>>, ["NotFound()"])
{:error, :no_match}
iex> ABI.decode_error(<<0xa9, 0x05>>, ["NotFound()"])
{:error, :calldata_too_short}
@spec decode_event( binary() | ABI.FunctionSelector.t(), binary(), [binary()], keyword() ) :: {:ok, String.t() | nil, map()} | {:error, ABI.Event.decode_error()}
Decodes an event, including indexed and non-indexed data.
Returns:
{:ok, event_name, args_map}on success{:error, {:event_signature_mismatch, %{expected: _, got: _}}}—topics[0]did not matchkeccak256(canonical_signature){:error, {:topics_length_mismatch, %{got: _, expected: _}}}— topic count did not match the indexed-parameter count (plus the implicittopics[0]slot whencheck_event_signature: true){:error, {:malformed_data, message}}— non-indexed payload failed to decode (truncated, wrong types, or otherwise inconsistent withfunction_selector.types)
Examples
iex> ABI.decode_event(
...> "Transfer(address indexed from, address indexed to, uint256 amount)",
...> ~h[0x00000000000000000000000000000000000000000000000000000004a817c800],
...> [
...> ~h[0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef],
...> ~h[0x000000000000000000000000b2b7c1795f19fbc28fda77a95e59edbb8b3709c8],
...> ~h[0x0000000000000000000000007795126b3ae468f44c901287de98594198ce38ea]
...> ]
...> )
{:ok,
"Transfer", %{
"amount" => 20000000000,
"from" => ~h[0xb2b7c1795f19fbc28fda77a95e59edbb8b3709c8],
"to" => ~h[0x7795126b3ae468f44c901287de98594198ce38ea]
}}
iex> ABI.decode_event(
...> "Transfer(address indexed from, address indexed to, uint256 amount)",
...> ~h[0x00000000000000000000000000000000000000000000000000000004a817c800],
...> [
...> ~h[0x000000000000000000000000b2b7c1795f19fbc28fda77a95e59edbb8b3709c8],
...> ~h[0x0000000000000000000000007795126b3ae468f44c901287de98594198ce38ea]
...> ],
...> check_event_signature: false
...> )
{:ok,
"Transfer", %{
"amount" => 20000000000,
"from" => ~h[0xb2b7c1795f19fbc28fda77a95e59edbb8b3709c8],
"to" => ~h[0x7795126b3ae468f44c901287de98594198ce38ea]
}}
iex> ABI.decode_event(
...> %ABI.FunctionSelector{
...> function: "Transfer",
...> types: [
...> %{type: :address, name: "from", indexed: true},
...> %{type: :address, name: "to", indexed: true},
...> %{type: {:uint, 256}, name: "amount"},
...> ]
...> },
...> ~h[0x00000000000000000000000000000000000000000000000000000004a817c800],
...> [
...> ~h[0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef],
...> ~h[0x000000000000000000000000b2b7c1795f19fbc28fda77a95e59edbb8b3709c8],
...> ~h[0x0000000000000000000000007795126b3ae468f44c901287de98594198ce38ea]
...> ]
...> )
{:ok,
"Transfer", %{
"amount" => 20000000000,
"from" => ~h[0xb2b7c1795f19fbc28fda77a95e59edbb8b3709c8],
"to" => ~h[0x7795126b3ae468f44c901287de98594198ce38ea]
}}
@spec describe() :: [map()]
Return a Level 1 overview of all modules in this library.
Return Level 2 function list for a module (by full atom or short name).
Return Level 3 function detail (or nil if not found).
@spec encode(binary() | ABI.FunctionSelector.t(), [any()]) :: binary()
Encodes the given data into the function signature or tuple signature.
In place of a signature, you can also pass one of the ABI.FunctionSelector structs returned from parse_specification/1.
Examples
iex> ABI.encode("(uint256)", [{10}])
...> |> Base.encode16(case: :lower)
"000000000000000000000000000000000000000000000000000000000000000a"
iex> ABI.encode("baz(uint,address)", [50, <<1::160>>])
...> |> Base.encode16(case: :lower)
"a291add600000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000001"
iex> ABI.encode("price(string)", ["BAT"])
...> |> Base.encode16(case: :lower)
"fe2c6198000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000034241540000000000000000000000000000000000000000000000000000000000"
iex> ABI.encode("baz(uint8)", [9999])
** (RuntimeError) Data overflow encoding uint, data `9999` cannot fit in 8 bits
iex> ABI.encode("(uint,address)", [{50, <<1::160>>}])
...> |> Base.encode16(case: :lower)
"00000000000000000000000000000000000000000000000000000000000000320000000000000000000000000000000000000000000000000000000000000001"
iex> ABI.encode("(string)", [{"Ether Token"}])
...> |> Base.encode16(case: :lower)
"0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b457468657220546f6b656e000000000000000000000000000000000000000000"
iex> ABI.encode("((uint256,uint256),string)", [{{0x11, 0x22}, "Ether Token"}])
...> |> Base.encode16(case: :lower)
"000000000000000000000000000000000000000000000000000000000000001100000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000b457468657220546f6b656e000000000000000000000000000000000000000000"
iex> ABI.encode("((uint256,(uint256,uint256)),string)", [{{0x11, {0x22, 0x33}}, "Ether Token"}])
...> |> Base.encode16(case: :lower)
"0000000000000000000000000000000000000000000000000000000000000011000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000330000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000b457468657220546f6b656e000000000000000000000000000000000000000000"
iex> ABI.encode("(string)", [{String.duplicate("1234567890", 10)}])
...> |> Base.encode16(case: :lower)
"000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000643132333435363738393031323334353637383930313233343536373839303132333435363738393031323334353637383930313233343536373839303132333435363738393031323334353637383930313233343536373839303132333435363738393000000000000000000000000000000000000000000000000000000000"
iex> File.read!("priv/dog.abi.json")
...> |> Jason.decode!
...> |> ABI.parse_specification
...> |> Enum.find(&(&1.function == "bark")) # bark(address,bool)
...> |> ABI.encode([<<1::160>>, true])
...> |> Base.encode16(case: :lower)
"b85d0bd200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001"
@spec encode_call(binary() | ABI.FunctionSelector.t(), [any()], keyword()) :: binary()
Encodes args into selector-prefixed calldata for a named function.
This is the encode-side counterpart to decode_call/3: pass a signature or
ABI.FunctionSelector, plus the argument list, and receive the full
transaction calldata blob.
Examples
iex> ABI.encode_call("transfer(address,uint256)", [<<1::160>>, 100])
...> |> Base.encode16(case: :lower)
"a9059cbb00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000064"
iex> ABI.encode_call(%ABI.FunctionSelector{function: nil, types: []}, [])
** (ArgumentError) encode_call/3 requires a function name; use encode/2 for payload-only data
@spec encode_constructor(ABI.FunctionSelector.t(), [any()]) :: binary()
Encodes constructor arguments for contract deployment.
Constructors have no selector — deploy calldata is contract_bytecode <> encoded_args, so this returns the ABI-encoded args with no 4-byte prefix
(unlike encode_call/3). The caller concatenates the bytecode. Mirrors viem's
encodeDeployData, scoped to just the args blob.
Only accepts a %ABI.FunctionSelector{function_type: :constructor} — pass the
:constructor entry from parse_specification/1. A non-constructor selector
raises ArgumentError. Round-trips through decode/3 against the
constructor's parsed types.
Examples
iex> [selector] =
...> ABI.parse_specification([%{
...> "type" => "constructor",
...> "stateMutability" => "nonpayable",
...> "inputs" => [%{"name" => "supply", "type" => "uint256"}]
...> }])
iex> ABI.encode_constructor(selector, [1000])
...> |> Base.encode16(case: :lower)
"00000000000000000000000000000000000000000000000000000000000003e8"
iex> selector = %ABI.FunctionSelector{function_type: :constructor, types: []}
iex> ABI.encode_constructor(selector, [])
""
iex> selector = %ABI.FunctionSelector{function: "transfer", function_type: :function, types: []}
iex> ABI.encode_constructor(selector, [])
** (ArgumentError) encode_constructor/2 requires a constructor selector (function_type: :constructor)
@spec encode_error(binary() | ABI.FunctionSelector.t(), [any()], keyword()) :: binary()
Encodes args into selector-prefixed revert data for a Solidity 0.8.4+ custom error.
This is the encode-side counterpart to decode_error/2: pass an error signature or
ABI.FunctionSelector, plus the argument list, and receive the full revert data blob.
Examples
iex> ABI.encode_error("InsufficientBalance(uint256,uint256)", [10, 100])
...> |> Base.encode16(case: :lower)
"cf479181000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000064"
iex> ABI.encode_error(%ABI.FunctionSelector{function: nil, types: []}, [])
** (ArgumentError) encode_error/3 requires a function name; use encode/2 for payload-only data
@spec encode_event_topics(binary() | ABI.FunctionSelector.t(), [any() | :any]) :: [ binary() | :any ]
Builds an eth_getLogs topic filter list for indexed event arguments.
Pass indexed argument values in event order. Use :any for an unfiltered
indexed slot. Indexed value types encode as 32-byte topics; indexed
reference types encode as keccak256 hashes of their in-place event
encoding.
Examples
iex> ABI.encode_event_topics(
...> "Transfer(address indexed from,address indexed to,uint256 amount)",
...> [~h[0xb2b7c1795f19fbc28fda77a95e59edbb8b3709c8], :any]
...> )
[
~h[0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef],
~h[0x000000000000000000000000b2b7c1795f19fbc28fda77a95e59edbb8b3709c8],
:any
]
@spec encode_packed(binary() | ABI.FunctionSelector.t(), [any()]) :: binary()
Encodes a list of values using Solidity's non-standard packed mode.
Used primarily for keccak256(abi.encodePacked(...)) Merkle leaves and
signature schemes; never used for actual function calls (the spec defines
no decoding function — encoding is ambiguous with multiple dynamic args).
Tuple/struct values and nested arrays raise ArgumentError — the spec
does not define their packed encoding.
Warning
If both a and b are dynamic types, abi.encodePacked(a, b) is
ambiguous: abi.encodePacked("a", "bc") == abi.encodePacked("ab", "c").
Do not feed multiple dynamic args into packed-mode signature schemes
without controlling for that collision.
Examples
iex> ABI.encode_packed("foo(int16,bytes1,uint16,string)", [-1, <<0x42>>, 3, "Hello, world!"])
...> |> Base.encode16(case: :lower)
"ffff42000348656c6c6f2c20776f726c6421"
iex> ABI.encode_packed("leaf(address,uint256)", [<<1::160>>, 100])
...> |> Base.encode16(case: :lower)
"00000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000064"
iex> ABI.encode_packed("foo(uint8[])", [[1, 2, 3]])
...> |> Base.encode16(case: :lower)
"000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003"
@spec event_signature(binary() | ABI.FunctionSelector.t()) :: binary()
Returns the signature for an event.
Examples
iex> ABI.event_signature("Transfer(address indexed from, address indexed to, uint256 amount)")
...> |> Base.encode16(case: :lower)
"ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
@spec format_abi_item(ABI.FunctionSelector.t()) :: String.t()
Renders an ABI.FunctionSelector as its canonical Solidity signature string.
This is the general-purpose formatter behind method_id/1 and
event_signature/1 (both hash this exact string): it reuses
ABI.FunctionSelector.encode/1 — the shared sig-builder — so the formatted
output can never drift from what gets hashed. First consumers are the
api_manifest.json CI artifact and error/log messages that would otherwise
inspect raw structs.
Tuple types expand to parenthesized member lists ((uint256,address)) and
arrays render with [] / [N] suffixes. Anonymous fragments (constructor,
fallback) carry function: nil and format without a leading name. Mirrors
viem's formatAbiItem and round-trips with FunctionSelector.decode/1 — the
produced string re-parses to the same types.
Examples
iex> ABI.parse_specification([%{"type" => "function", "name" => "transfer", "inputs" => [%{"name" => "to", "type" => "address"}, %{"name" => "amount", "type" => "uint256"}]}])
...> |> hd()
...> |> ABI.format_abi_item()
"transfer(address,uint256)"
iex> ABI.format_abi_item(%ABI.FunctionSelector{
...> function: "swap",
...> types: [
...> %{type: {:tuple, [%{type: :address}, %{type: {:uint, 256}}]}},
...> %{type: {:array, {:uint, 256}}},
...> %{type: {:array, :address, 3}}
...> ]
...> })
"swap((address,uint256),uint256[],address[3])"
iex> ABI.format_abi_item(%ABI.FunctionSelector{function: nil, function_type: :constructor, types: [%{type: {:uint, 8}}]})
"(uint8)"
@spec get_abi_item( [ABI.FunctionSelector.t()], String.t(), [ABI.FunctionSelector.type()] | nil ) :: {:ok, ABI.FunctionSelector.t()} | {:error, :not_found} | {:error, {:ambiguous, [ABI.FunctionSelector.t()]}}
Finds an ABI fragment by name in a parsed specification list.
Operates on the output of parse_specification/1. When several fragments
share a name (Solidity overloads, or a function and event with the same
label), pass arg_types — a list of internal type atoms matching each
input's type field (e.g. [:address, {:uint, 256}]) — to select the
intended overload. Mirrors viem's getAbiItem.
Examples
iex> abi =
...> ABI.parse_specification([
...> %{"type" => "function", "name" => "transfer", "inputs" => [
...> %{"type" => "address"},
...> %{"type" => "uint256"}
...> ]}
...> ])
iex> {:ok, %ABI.FunctionSelector{function: "transfer"}} = ABI.get_abi_item(abi, "transfer", nil)
iex> abi =
...> ABI.parse_specification([
...> %{"type" => "function", "name" => "pick", "inputs" => [%{"type" => "uint256"}]},
...> %{"type" => "function", "name" => "pick", "inputs" => [
...> %{"type" => "uint256"},
...> %{"type" => "address"}
...> ]}
...> ])
iex> {:error, {:ambiguous, _}} = ABI.get_abi_item(abi, "pick", nil)
iex> {:ok, %ABI.FunctionSelector{types: [%{type: {:uint, 256}}, %{type: :address}]}} =
...> ABI.get_abi_item(abi, "pick", [{:uint, 256}, :address])
iex> abi = ABI.parse_specification([%{"type" => "function", "name" => "only", "inputs" => []}])
iex> ABI.get_abi_item(abi, "missing", nil)
{:error, :not_found}
@spec method_id(binary() | ABI.FunctionSelector.t()) :: binary()
Returns the 4-byte function selector (method ID) for a function signature.
The selector is keccak256(canonical_signature) truncated to its first 4
bytes. Returns <<>> for selectors with no function name (anonymous /
raw-tuple selectors used for return-value decoding).
Examples
iex> ABI.method_id("transfer(address,uint256)") |> Base.encode16(case: :lower)
"a9059cbb"
iex> ABI.method_id("deposit()") |> Base.encode16(case: :lower)
"d0e30db0"
iex> ABI.method_id(%ABI.FunctionSelector{function: "deposit", types: []}) |> Base.encode16(case: :lower)
"d0e30db0"
iex> ABI.method_id(%ABI.FunctionSelector{function: nil, types: [%{type: {:uint, 256}}]})
""
@spec parse_specification([map()]) :: [ABI.FunctionSelector.t()]
Parses the given ABI specification document into an array of ABI.FunctionSelectors.
Every entry in the document is parsed — including constructor, fallback,
receive, error, and event entries — and returned with its function_type
field set accordingly. Filter by that field if you only want plain
function entries.
This function can be used in combination with a JSON parser, e.g. Jason, to parse ABI specification JSON files.
Examples
iex> File.read!("priv/dog.abi.json")
...> |> Jason.decode!
...> |> ABI.parse_specification
[%ABI.FunctionSelector{function: "bark", function_type: :function, state_mutability: :nonpayable, returns: [], types: [%{name: "at", type: :address}, %{name: "loudly", type: :bool}]},
%ABI.FunctionSelector{function: "rollover", function_type: :function, state_mutability: :nonpayable, returns: [%{name: "is_a_good_boy", type: :bool}], types: []}]
iex> [%{
...> "constant" => true,
...> "inputs" => [
...> %{"name" => "at", "type" => "address"},
...> %{"name" => "loudly", "type" => "bool"}
...> ],
...> "name" => "bark",
...> "outputs" => [],
...> "payable" => false,
...> "stateMutability" => "pure",
...> "type" => "function"
...> }]
...> |> ABI.parse_specification
[
%ABI.FunctionSelector{function: "bark", function_type: :function, state_mutability: :pure, returns: [], types: [
%{type: :address, name: "at"},
%{type: :bool, name: "loudly"}
]}
]
iex> [%{
...> "inputs" => [
...> %{"name" => "_numProposals", "type" => "uint8"}
...> ],
...> "payable" => false,
...> "stateMutability" => "nonpayable",
...> "type" => "constructor"
...> }]
...> |> ABI.parse_specification
[%ABI.FunctionSelector{function: nil, function_type: :constructor, state_mutability: :nonpayable, types: [%{name: "_numProposals", type: {:uint, 8}}], returns: nil}]
iex> ABI.decode("(string)", "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000643132333435363738393031323334353637383930313233343536373839303132333435363738393031323334353637383930313233343536373839303132333435363738393031323334353637383930313233343536373839303132333435363738393000000000000000000000000000000000000000000000000000000000" |> Base.decode16!(case: :lower))
[String.duplicate("1234567890", 10)]
iex> [%{
...> "payable" => false,
...> "stateMutability" => "nonpayable",
...> "type" => "fallback"
...> }]
...> |> ABI.parse_specification
[%ABI.FunctionSelector{function: nil, function_type: :fallback, state_mutability: :nonpayable, returns: nil, types: []}]