Skip to main content

TokenId

In Panoptic, every option position is represented by a unique ID. This ID is a uint256 and thus contains 256 bits. The reason is is referred to as an "ID" is because it is the ID of an ERC1155 semi-fungible NFT.

Taking a step back, in Uniswap V3 an LP position is represented as an NFT also with a unique ID. This gives each LP a unique position and the NFT represents this. In our case, it would be more beneficial and gas efficient to represent option positions by ERC115s and here is why: An ERC1155 can hold an element with a unique ID. But multiple users can hold a copy of this ID. The classic example is a sword in a video game. There is a single type of sword (the ID), but multiple people can own it (multiple users per ID). Similarly, there is a single type of option position (and thus we don't tie it to the initial creator of the position, we track that simply via balanceOf) but multiple users could hold it simultaneously. This also means that we get access to other gas saving features like batch minting and burning, etc. In comparison, Uniswap V3 uses an ERC721 NFT to represents a position and uses the LP's address as part of its ID (so 1 position has 1 owner at most always; therefore, if multiple users provide liquidity in the exact same range, multiple separate NFTs need to be minted one at a time).

Having explained what the tokenId is and what it represents, we also mentioned its number of bits. Why? Well this is very important because it is here that the details of the option position lies. Specifically, first we realize that the option position is with respect to a specific Uniswap V3 pool. Thus, we take the first 10 bytes (80 bits) of that address and use for the first 80 bits starting at the least significant bit (LSB, in a big endian format).

Then, we follow that up with 4 bits per potential leg which consists of 3 bits quantifying the optionRatio and 1 bit for the numeraire identifier (the bit is 0/1 if token0/1 is the numeraire). Thus, the optionRatio+numeraire segment takes up a total of 16 bits since we support up to 4 legs in one position.

Following this, each leg takes up 40 bits and consists of the following details: The first bit signals whether the position is long or short. If long, liquidity is removed from Uniswap. The next bit signals the tokenType that is moved when deployed and therefore we are signaling whether it's a call or a put. Following this, we specify which potential risk partner this leg has (this would be another leg in the position). Then follows the strike tick for 24 bits stored as (tickUpper+tickLower)/2(tickUpper + tickLower)/2 of the Uniswap V3 liquidity that reprents the position. The width is stored next for 12 bits defined as (tickUppertickLower)/2(tickUpper - tickLower) / 2.

As mentioned, up to four legs can be part of a position. A leg index specifies which of the four legs are meant and takes values in {0,1,2,3}. The leg takes values in {1,2,3,4}. So leg nn has leg index n1n-1 in our nomenclature.

Diagram of the tokenId

So, in summary the 256 bits of the uint256 is a fingerprint of the option position. A unique number representing the ID of the option position and it is broken down into the elements:

(Insert figure)

Technical Details

The following table gives a thorough technical overview of the tokenId composition - use the diagram above as a visual reference:

What is storedSize in bitsRelative offset into ID from LSBDescription
Uni V3 Pool ID800The first 80 bits of the Uni V3 pool address.
optionRatio Leg 1380The optionRatio is how many times the user's ERC1155 balance is moved over in tokens
Numeraire Leg 1183Specify which token is the numeraire: 0/1 if token0/1 is.
optionRatio Leg 2384The optionRatio is how many times the user's ERC1155 balance is moved over in tokens
Numeraire Leg 2187Specify which token is the numeraire: 0/1 if token0/1 is.
optionRatio Leg 3388The optionRatio is how many times the user's ERC1155 balance is moved over in tokens
Numeraire Leg 3191Specify which token is the numeraire: 0/1 if token0/1 is.
optionRatio Leg 4392The optionRatio is how many times the user's ERC1155 balance is moved over in tokens
Numeraire Leg 4195Specify which token is the numeraire: 0/1 if token0/1 is.
Leg 1 (leg_index=0)(leg\_index=0)
\- isLong196The first leg's first bit telling us whether this leg is long (1) or short (0).
\- tokenType197The first leg's second bit telling us whether the token type being moved is token0 (0) or token1 (1).
\- riskPartner298The first leg's risk partner (another leg in this position).
\- strike24100The first leg's strike price represented as the mid point of the tick range spanned in the Uni v3 pool, defined as (tickUpper+tickLower)/2(tickUpper + tickLower)/2.
\- width12124The first leg's width of the liquidity deployed in the Uni v3 pool, defined as (tickUppertickLower)/2(tickUpper - tickLower)/2.
Leg 2 (leg_index=1)(leg\_index=1)
\- isLong1136The second leg's first bit telling us whether this leg is long (1) or short (0).
\- tokenType1137The second leg's second bit telling us whether the token type being moved is token0 (0) or token1 (1).
\- riskPartner2138The second leg's risk partner (another leg in this position).
\- strike24140The second leg's strike price represented as the mid point of the tick range spanned in the Uni v3 pool, defined as (tickUpper+tickLower)/2(tickUpper + tickLower)/2.
\- width12164The second leg's width of the liquidity deployed in the Uni v3 pool, defined as (tickUppertickLower)/2(tickUpper - tickLower)/2.
Leg 3 (leg_index=2)(leg\_index=2)
\- isLong1176The third leg's first bit telling us whether this leg is long (1) or short (0).
\- tokenType1177The third leg's second bit telling us whether the token type being moved is token0 (0) or token1 (1).
\- riskPartner2178The third leg's risk partner (another leg in this position).
\- strike24180The third leg's strike price represented as the mid point of the tick range spanned in the Uni v3 pool, defined as (tickUpper+tickLower)/2(tickUpper + tickLower)/2.
\- width12204The third leg's width of the liquidity deployed in the Uni v3 pool, defined as (tickUppertickLower)/2(tickUpper - tickLower)/2.
Leg 4 (leg_index=3)(leg\_index=3)
\- isLong1216The third leg's first bit telling us whether this leg is long (1) or short (0).
\- tokenType1217The third leg's second bit telling us whether the token type being moved is token0 (0) or token1 (1).
\- riskPartner2218The third leg's risk partner (another leg in this position).
\- strike24220The third leg's strike price represented as the mid point of the tick range spanned in the Uni v3 pool, defined as (tickUpper+tickLower)/2(tickUpper + tickLower)/2.
\- width12244The third leg's width of the liquidity deployed in the Uni v3 pool, defined as (tickUppertickLower)/2(tickUpper - tickLower)/2.

We note that the final piece of information starts at bit index 244 and takes up 12 bits thus ending at 256 (non-inclusive; to be sure, the final bit is located at 255 as expected).

We use the tokenId library to store and extract the information from within this bit pattern. In other words, by leveraging bit shifting and other techniques, we can always extract any piece of information from the tokenId that we see fit. Similarly, we can store into the pattern as well and thus we can both read and write to this.