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:
address, uintN, intN, bool, bytesN for N <= 32)
occupy 32 bytes inline in the head section.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
]Unsigned 256-bit integer, left-padded to 32 bytes.
Negative values are encoded as their absolute value; for signed integers use
int256 instead.
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)bytes32 -- 32 bytes of arbitrary data, accepting either "0x..." or bare
hex. Right-padded with zeros if shorter than 32 bytes.
bytesN for N in {1..32}. Right-padded to 32 bytes.
bytesN 4 "0xdeadbeef"
--> 0xdeadbeef00000000...00 (32 bytes total)Raw bytes (bytes), accepted as "0x..." or bare hex. Encoded as a
length-prefixed dynamic blob.
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.
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>"
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.