intrepidshape/elm-web3/1.4.2

NOTE: the latest version is 3.0.0

Web3.Abi.Calldata

Pure-Elm ABI calldata encoding for contract calls.

Produces the exact hex bytes that go on the wire — selector + ABI-encoded parameters — without any JavaScript surface. The result of calldata is a "0x…" string suitable for the data field of eth_call / eth_sendTransaction.

The encoder follows the Solidity ABI specification's "head/tail" layout:

  • Static types (address, uintN, intN, bool, bytesN for N ≤ 32) occupy 32 bytes inline in the head section.
  • Dynamic types (string, bytes, T[], tuples containing any dynamic field) occupy 32 bytes in the head holding the offset of their data within the calldata, with the actual content appended to the tail (length-prefixed for variable-length types).

Codegen tools should bake the 4-byte selector at codegen time (computed via keccak256 of the function signature) and pass it to calldata. That keeps the runtime entirely in Elm — no JS hash function needed.

-- balanceOf(address) — selector is keccak256("balanceOf(address)")[:4] = 70a08231
calldata "70a08231" [ address holder ]
    == "0x70a08231000000000000000000000000abcd…"


-- approve(address,uint256)
calldata "095ea7b3"
    [ address spender
    , uint256 amount
    ]


-- transferBatch(address[], uint256[]) — dynamic
calldata "deadbeef"
    [ list address recipients
    , list uint256 amounts
    ]
type Slot

A piece of an ABI parameter list — either a static 32-byte chunk or a dynamic blob that lives in the tail.

The internals are deliberately opaque: callers construct Slots via the typed helpers below and never inspect the hex themselves.

20-byte address, left-padded to 32 bytes.

Unsigned 256-bit integer, left-padded to 32 bytes.

Negative values are encoded as their absolute value; for signed integers use int256 instead.

uintN : Int -> BigInt -> Slot

Unsigned integer of n bits (8/16/24/.../256), encoded identically to uint256. Width is a hint to callers; on the wire all unsigned integers up to 256 bits occupy one 32-byte slot.

Bounds-checking is the caller's responsibility — a value exceeding 2^n - 1 is encoded as uint256 and will revert on-chain in solc 0.8+.

Signed 256-bit integer, encoded as two's-complement, left-padded to 32 bytes.

int256 (BigInt.fromInt -1)
--> 0xffffffff…ffff  (32 bytes of 0xff)
bool : Bool -> Slot

bool, encoded as a 32-byte slot of zeros (false) or zeros + 01 (true).

bytes32 : String -> Slot

bytes32 — 32 bytes of arbitrary data, accepting either "0x…" or bare hex. Right-padded with zeros if shorter than 32 bytes.

bytesN : Int -> String -> Slot

bytesN for N ∈ {1..32}. Right-padded to 32 bytes.

bytesN 4 "0xdeadbeef"
--> 0xdeadbeef00000000…00 (32 bytes total)
string : String -> Slot

UTF-8 string, encoded as a length-prefixed dynamic blob.

bytes : String -> Slot

Raw bytes (bytes), accepted as "0x…" or bare hex. Encoded as a length-prefixed dynamic blob.

list : (a -> Slot) -> List a -> Slot

Dynamic array T[], encoded as length || encode(elements).

tuple : List Slot -> Slot

Tuple (struct), encoded inline if every component is static, or as a dynamic blob otherwise.

The wire layout matches Solidity's: a tuple of all-static fields occupies the sum of its components' slots inline; a tuple containing any dynamic field is itself dynamic and lives in the tail with the inner layout recursed.

calldata : String -> List Slot -> String

Combine a 4-byte selector and a list of Slots into the full calldata hex string.

The selector is bare hex (no "0x" prefix), exactly 8 characters (4 bytes). Codegen tools should compute this at codegen time via keccak256(signature).slice(0, 8) and bake it as a constant — this module intentionally does not include a keccak implementation, so it stays pure data layout.

calldata "70a08231" [ address holder ]
--> "0x70a08231000000…<holder padded to 32 bytes>"