Stochastic.Finance Docs

Last updated 12 September 2026

TokenID format

Every option leg is an ERC-1155 token whose id is its specification. Nothing about an option is stored in a registry or looked up off-chain: unpack the id and you have the underlying, the strike, the expiry, the type, the kind and the side.

Layout

The identifier occupies 124 bits, carried inside a standard uint256 ERC-1155 id. Fields, most significant first:

Field Bit range Width Contents
Pricefeed hash 60–123 64 First 8 bytes of keccak256(feed_address)
Maturity 36–59 24 (year − 2000) << 16 \| month << 8 \| day
Strike 12–35 24 Packed exponential strike code
Option type 8–11 4 0 = Call, 1 = Put
Option kind 4–7 4 1 = European, 0 = American
Direction 0–3 4 0 = long (buyer), 1 = short (seller)

The remaining 132 high bits must be zero, and the contract checks this on every operation. An id with anything set above bit 123 is rejected rather than truncated, so a malformed id can never be mistaken for a valid one.

Note that type, kind and direction each occupy a nibble, not a byte. They are passed as uint8 arguments for convenience, but a value above 15 would run into the neighbouring field, so the packing function rejects it outright instead of masking it away.

Field encodings

Pricefeed

The underlying is identified by the first 64 bits of the keccak hash of its Chainlink aggregator address, not by the address itself — 160 bits would not fit alongside everything else. The contract keeps the reverse mapping, so a hash resolves back to a feed:

priceFeedMapping(uint64 pricefeedHash) returns (address feed)

Collisions across 64 bits are not a practical concern for the number of feeds a protocol registers, and a feed must be explicitly registered before its hash means anything.

Maturity

A packed calendar date, not a timestamp. 2026-07-17 encodes as:

year:  2026 − 2000 = 26  = 0x1A
month: 7                 = 0x07
day:   17                = 0x11
                  ──────────────
maturity = 0x1A0711 = 1705745

The timestamp is derived when needed. Storing the date rather than the timestamp keeps the field to 24 bits and makes ids readable by eye.

Strike

Four significant digits plus a base-10 exponent, in 24 bits:

Sub-field Bits (within the 24) Contents
Mantissa 8–23 Four significant digits
Sign 4–7 0 = scale down, 1 = scale up
Exponent 0–3 Power of ten

Decoding, against an 18-decimal anchor:

sign == 0  →  value = mantissa × 10^(15 − exponent)
sign == 1  →  value = mantissa × 10^(15 + exponent)

This is what lets 248 codes span 0.000001 to 10,000,000. For example 0x1C8012 — mantissa 0x1C80 = 7296, sign 1, exponent 2 — decodes to 729.6.

A worked example

A long European call on a registered feed, strike 729.6, expiring 2026-07-17:

pricefeed hash   0xE775A8F829551E8F   (64 bits)
maturity         0x1A0711             (2026-07-17)
strike           0x1C8012             (729.6)
option type      0x0                  (Call)
option kind      0x1                  (European)
direction        0x0                  (long)

tokenId = 0xE775A8F829551E8F1A07111C8012010
          124 bits · 31 hex digits

Every field lands on a clean nibble boundary — 16 + 6 + 6 + 1 + 1 + 1 = 31 hex digits — so a hex id can be read field by field without any bit shifting.

The last nibble is the side

The final hex digit is the direction. …010 is the long leg; …011 is its short counterpart. If you are about to trade and want one sanity check, check that digit.

Deriving the counterpart leg

The two legs of an option differ only in the direction field, so flipping it gives you the other side:

TokenIdLib.invert_direction_for_tokenid(uint256 id) returns (uint256)

This is what minting uses: you name one leg, and the contract mints that id together with its inverse.

Constructing and validating

// Build an id from parameters
TokenIdLib.construct_token_id(
    pricefeed_hash, maturity, strike, option_type, option_kind, direction
) returns (uint256);

// Unpack an existing id
TokenIdLib.get_pricefeed_hash(id);
TokenIdLib.get_maturity(id);
TokenIdLib.get_strike(id);
TokenIdLib.get_option_type(id);
TokenIdLib.get_option_kind(id);
TokenIdLib.get_direction(id);

// Is every field on the grid, and are the reserved bits clear?
ISFOptions(0xf0022aC3…).if_token_id_exists(id) returns (bool);

A well-formed id is not necessarily a valid one: the fields must also exist on the grid, and the feed must be registered. if_token_id_exists checks all of it in a single view call, and is the same validation the contract applies internally on every mint and exercise.

Why it's built this way

Packing the specification into the id means:

  • Pools are addressable without a registry. A pair's address derives from (option contract, tokenId), so the pool for any option can be computed before it exists.
  • Metadata needs no server. The contract generates a token's name and attributes by unpacking its own id — there is no URL that has to stay alive for the options' lifetime.
  • Over 100,000 instruments live in one contract. No per-option deployment, and any ERC-1155-aware wallet or protocol can hold them.

Next

© Stochastic Finance Protocol — All Rights Reserved