Minimal EIP-1559 (type-2) transaction encoding and signing digests.
Built for X402.Facilitator.Engine's settlement path: the facilitator
assembles a transferWithAuthorization call, signs its digest through the
X402.Signer behaviour, and broadcasts the raw transaction via
eth_sendRawTransaction. The module is deliberately narrow — type-2
transactions only, always with an empty access list — and pure except for
digest/1, which needs the optional ex_keccak dependency.
The signing preimage is 0x02 || rlp([chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gasLimit, to, value, data, accessList]) and the broadcast
form appends the signature as yParity, r, s. EIP-1559 signatures carry
yParity (0/1), not the legacy 27/28 recovery id;
encode_signed/2 accepts both and normalizes.
Examples
iex> tx = %X402.Transaction{
...> chain_id: 1,
...> nonce: 0,
...> max_priority_fee_per_gas: 1,
...> max_fee_per_gas: 2,
...> gas_limit: 21_000,
...> to: "0x1111111111111111111111111111111111111111"
...> }
iex> {:ok, preimage} = X402.Transaction.preimage(tx)
iex> Base.encode16(preimage, case: :lower)
"02df0180010282520894111111111111111111111111111111111111111180" <> "80c0"
Summary
Functions
Computes the 32-byte signing digest: keccak256(preimage).
Encodes the broadcast-ready raw transaction from a 65-byte signature.
Returns the unsigned signing preimage: 0x02 || rlp(unsigned fields).
Types
@type error() :: :invalid_address | :invalid_transaction | :invalid_signature_format
Encoding errors.
@type t() :: %X402.Transaction{ chain_id: non_neg_integer(), data: binary(), gas_limit: non_neg_integer(), max_fee_per_gas: non_neg_integer(), max_priority_fee_per_gas: non_neg_integer(), nonce: non_neg_integer(), to: String.t(), value: non_neg_integer() }
An EIP-1559 transaction.
to is a 0x-prefixed EVM address, data the raw calldata bytes, and all
quantities non-negative integers in wei / gas units. The access list is
always empty.
Functions
Computes the 32-byte signing digest: keccak256(preimage).
Requires the optional ex_keccak dependency and returns
{:error, :missing_dependency} when it is unavailable.
Encodes the broadcast-ready raw transaction from a 65-byte signature.
signature is r || s || v where v may be an EIP-1559 yParity
(0/1) or a legacy recovery id (27/28, as produced by
X402.Signer.sign_eip712/3); both are normalized to yParity. r and
s are RLP-encoded as integers, so leading zero bytes are stripped per
the Ethereum convention.
Returns the raw bytes to submit via eth_sendRawTransaction (hex-encode
with a 0x prefix first).
Examples
iex> tx = %X402.Transaction{
...> chain_id: 1,
...> nonce: 0,
...> max_priority_fee_per_gas: 1,
...> max_fee_per_gas: 2,
...> gas_limit: 21_000,
...> to: "0x1111111111111111111111111111111111111111"
...> }
iex> signature = <<1::unsigned-big-integer-size(256), 2::unsigned-big-integer-size(256), 28>>
iex> {:ok, raw} = X402.Transaction.encode_signed(tx, signature)
iex> Base.encode16(raw, case: :lower)
"02e20180010282520894111111111111111111111111111111111111111180" <> "80c0010102"
iex> tx = %X402.Transaction{
...> chain_id: 1,
...> nonce: 0,
...> max_priority_fee_per_gas: 1,
...> max_fee_per_gas: 2,
...> gas_limit: 21_000,
...> to: "0x1111111111111111111111111111111111111111"
...> }
iex> X402.Transaction.encode_signed(tx, <<0, 1, 2>>)
{:error, :invalid_signature_format}
Returns the unsigned signing preimage: 0x02 || rlp(unsigned fields).
The digest a signer signs is the keccak-256 hash of this preimage — see
digest/1.
Examples
iex> X402.Transaction.preimage(%X402.Transaction{
...> chain_id: 1,
...> nonce: 0,
...> max_priority_fee_per_gas: 1,
...> max_fee_per_gas: 2,
...> gas_limit: 21_000,
...> to: "0xnope"
...> })
{:error, :invalid_address}