Packages
polymarket_sdk
0.2.0
Higher-level Elixir SDK for Polymarket: Gamma market discovery, Data API, CTF/pUSD helpers, and convenience facades. Depends on polymarket_clob for the low-level CLOB surface.
Current section
Files
Jump to
Current section
Files
lib/polymarket/rlp.ex
defmodule Polymarket.RLP do
@moduledoc """
Hand-rolled RLP (Recursive Length Prefix) **encoder** for Ethereum
legacy (type-0) transaction serialization.
This module implements only the encoding side — there is no decoder.
The set of inputs we encode is narrow: bytes, non-negative integers,
and lists of already-encoded items. That's enough for legacy
EIP-155 transactions and pure RLP test vectors.
## Encoding rules (per the Ethereum spec)
Bytes / strings:
* Empty: `0x80`.
* Single byte `b` where `0x00 <= b <= 0x7f`: encoded as itself
(single byte, no length prefix).
* Other byte strings of length `len`:
* `1 <= len <= 55`: `<<0x80 + len, ...bytes>>`.
* `len > 55`: `<<0xb7 + size_of_len, ...len_bytes, ...bytes>>`
where `len_bytes` is the minimal big-endian encoding of `len`.
Lists with combined-payload size `len`:
* `0 <= len <= 55`: `<<0xc0 + len, ...payload>>`.
* `len > 55`: `<<0xf7 + size_of_len, ...len_bytes, ...payload>>`.
Non-negative integers are encoded as their minimal-byte big-endian
representation, then RLP-encoded as a byte string. Zero collapses to
the empty string `0x80` — the integer `0` does NOT encode as
`<<0x00>>`.
Output is byte-for-byte verified against `ethers.encodeRlp` in
`test/fixtures/tx.json` § rlp_primitives.
"""
@doc """
Encodes a binary as an RLP string.
"""
@spec encode_bytes(binary()) :: binary()
def encode_bytes(<<>>), do: <<0x80>>
def encode_bytes(<<b>>) when b < 0x80, do: <<b>>
def encode_bytes(bin) when is_binary(bin) and byte_size(bin) <= 55 do
<<0x80 + byte_size(bin)>> <> bin
end
def encode_bytes(bin) when is_binary(bin) do
len_bytes = :binary.encode_unsigned(byte_size(bin), :big)
<<0xB7 + byte_size(len_bytes)>> <> len_bytes <> bin
end
@doc """
Encodes a non-negative integer as an RLP byte string carrying the
minimal big-endian representation. Zero encodes as `0x80` (empty
string), matching the canonical Ethereum/RLP convention.
"""
@spec encode_integer(non_neg_integer()) :: binary()
def encode_integer(0), do: <<0x80>>
def encode_integer(n) when is_integer(n) and n > 0 do
encode_bytes(:binary.encode_unsigned(n, :big))
end
@doc """
Encodes a list of already-RLP-encoded items as an RLP list.
The caller is responsible for encoding each element first
(`encode_bytes/1`, `encode_integer/1`, or a nested `encode_list/1`).
This module deliberately keeps element-encoding decisions in the
caller — there is no value tagging that would let one function
decide between "string" and "integer" interpretations of arbitrary
Elixir terms.
"""
@spec encode_list([binary()]) :: binary()
def encode_list(items) when is_list(items) do
payload = IO.iodata_to_binary(items)
payload_size = byte_size(payload)
if payload_size <= 55 do
<<0xC0 + payload_size>> <> payload
else
len_bytes = :binary.encode_unsigned(payload_size, :big)
<<0xF7 + byte_size(len_bytes)>> <> len_bytes <> payload
end
end
@doc """
Hex-encodes raw RLP output for human display or fixture comparison.
Always lowercase, always `0x`-prefixed.
"""
@spec to_hex(binary()) :: String.t()
def to_hex(bin) when is_binary(bin) do
"0x" <> Base.encode16(bin, case: :lower)
end
end