Pure EIP-155 legacy (type-0) Ethereum transaction serialization and signing.
This module covers exactly the surface needed to produce signed raw transaction bytes from caller-provided fields. There is no JSON-RPC, no nonce/gas fetching, and no submission. Callers are expected to broadcast the bytes through their own client.
Scope
- Legacy type-0 transactions only (RLP-encoded list of 9 fields).
- EIP-155 chain-id-mixed signature (
v = chain_id * 2 + 35 + yParity). - No EIP-1559 type-2 (
max_priority_fee_per_gas/max_fee_per_gas). Polymarket on Polygon transactions today are routinely sent as type-0 withgasPrice, so this is sufficient for the planned CTF / pUSD on-chain helpers. Type-2 support is deferred until a real consumer needs it. - Keccak-256 message hashing via
ExKeccakand signing viaExSecp256k1.sign_compact/2— matchespolymarket_clob's EIP-712 signing path.
Output contract
serialize/1 and hash/1 operate on a Polymarket.Tx struct that
has its :v, :r, :s fields populated. Call sign/2 to fill
them; calling serialize/1 on an unsigned tx raises
ArgumentError.
Output is byte-for-byte verified against ethers.Transaction in
test/fixtures/tx.json § legacy_transactions.
Fields
:nonce— non-negative integer.:gas_price— non-negative integer (wei).:gas_limit— non-negative integer (units of gas).:to—0x-prefixed 20-byte hex address, ornilfor contract creation.:value— non-negative integer (wei).:data— raw bytes (binary). Pass<<>>(ornil) for empty data.:chain_id— positive integer (137 for Polygon, 1 for Ethereum mainnet).:v,:r,:s— populated bysign/2.nilon an unsigned tx.
Summary
Functions
Returns keccak256(serialize(tx)) — the on-chain transaction hash.
Builds a Polymarket.Tx from required keyword fields.
Returns the RLP-encoded signed raw transaction bytes — the
9-field list [nonce, gas_price, gas_limit, to, value, data, v, r, s].
Signs the transaction using a 32-byte private key (raw binary or
0x-prefixed hex). Returns a new Polymarket.Tx with :v, :r,
:s populated.
Returns keccak256(signing_payload(tx)) — the 32-byte digest that
is signed under EIP-155.
Returns the RLP-encoded unsigned signing payload — the 9-field
list [nonce, gas_price, gas_limit, to, value, data, chain_id, 0, 0]. This is the input to signing_hash/1.
Types
@type t() :: %Polymarket.Tx{ chain_id: pos_integer(), data: binary(), gas_limit: non_neg_integer(), gas_price: non_neg_integer(), nonce: non_neg_integer(), r: non_neg_integer() | nil, s: non_neg_integer() | nil, to: String.t() | nil, v: non_neg_integer() | nil, value: non_neg_integer() }
Functions
@spec hash(t()) :: <<_::256>>
Returns keccak256(serialize(tx)) — the on-chain transaction hash.
Builds a Polymarket.Tx from required keyword fields.
Required: :nonce, :gas_price, :gas_limit, :chain_id.
Optional: :to (default nil — contract creation), :value
(default 0), :data (default <<>>).
Raises KeyError if a required key is missing, and ArgumentError
if an unknown key is passed. Unknown keys are rejected — rather
than silently ignored — because a typo in :to (e.g. too: /
recipient:) would otherwise leave :to at its nil default and
sign an unintended contract-creation transaction.
Returns the RLP-encoded signed raw transaction bytes — the
9-field list [nonce, gas_price, gas_limit, to, value, data, v, r, s].
Raises ArgumentError if tx has not been signed.
Signs the transaction using a 32-byte private key (raw binary or
0x-prefixed hex). Returns a new Polymarket.Tx with :v, :r,
:s populated.
:v follows EIP-155: chain_id * 2 + 35 + yParity, where yParity
is the secp256k1 recovery id (0 or 1).
@spec signing_hash(t()) :: <<_::256>>
Returns keccak256(signing_payload(tx)) — the 32-byte digest that
is signed under EIP-155.
Returns the RLP-encoded unsigned signing payload — the 9-field
list [nonce, gas_price, gas_limit, to, value, data, chain_id, 0, 0]. This is the input to signing_hash/1.