StarkEx specific concepts

Asset quantities in StarkEx

In the Ethereum blockchain, token quantities are represented by a 256-bit number. However, to be efficient, quantities in StarkEx are represented by a 64-bit number. The largest number of digits that a 64-bit number can represent is much less than that of a 256-bit number can represent. So in order to support trading in significant volume, the smallest unit that StarkEx can use for an asset is larger than the smallest unit that Ethereum can use.

The inputs to all off-chain transactions and some on-chain requests are given in units of the off-chain asset, so the code that generates the transaction signature must use the correct factor for converting 256-bit quantities of on-chain or synthetic ETH or ERC-20 asset quantities to a 64-bit StarkEx representation.

Converting on-chain asset quantities

This factor is called the quantization factor or quantum. The quantum represents the smallest amount of an asset that users can trade off-chain, or with which they can pay off-chain fees.

Converting synthetic asset quantities

This factor is called the resolution factor. The resolution is the total number of the smallest part of a single synthetic token in the StarkEx system.

You decide the value of the quantum and resolution factor according to the requirements of your application’s business logic.

Balance of a synthetic asset

Every balance is a 64-bit value, where one bit represents the sign indicating if the value is positive or negative, and 63-bits represent the number. So every balance falls within the range (-263, 263).

Quantization

The quantum is the smallest unit of a token in the StarkEx system. It is similar to an exchange rate that you use to find the on-chain value of a quantity of a token in StarkEx, according to the following formula:

\$\text{quantum} \cdot \text{StarkEx_amount} = \text{on-chain_amount}\$
Example: Defining the quantum

Consider one possible quantization for ETH, which is an on-chain asset:

On-chain asset

The smallest unit of ETH is 1 WEI, which is \$10^{-18}\$ ETH.

Off-chain asset

The smallest unit with which you can conduct transactions of the off-chain asset that corresponds to ETH represents \$10^{-11}\$ on-chain ETH, or \$10^{7}\$ on-chain WEI.

So the operator defines the quantum for ETH to be \$10^{7}\$.

Example: Calculating the amount of an on-chain asset

This example shows how to find how many units of on-chain WEI are represented by 17 units of the corresponding off-chain asset.

  • In Ethereum, 1 WEI = \$10^{-18}\$ETH.

  • Let \$\text{quantum}=10^7\$.

  • Let \$\text{StarkEx_amount}=17\$. This is the number of corresponding assets in a StarkEx off-chain vault.

Plugging in these values to the formula above gives the following:

\$10^7 \cdot 17 = 170,000,000\$

170,000,000 is the amount of WEI locked on-chain that is associated with the corresponding StarkEx off-chain vault, which is the same as \$10^{-11} \cdot 17\$ ETH.

The quantum is necessary for every asset that can be represented on-chain. For example, in the deposit flow, the on-chain deposit transaction gets an unquantized amount of tokens from the user, yet the parameter to the on-chain call is a quantized amount.

Resolution

In contrast to collateral, synthetic assets do not necessarily correspond to on-chain assets. For example, a barrel of oil can be a synthetic asset. So quantization, which is specifically for working with on-chain assets, does not apply to synthetic assets. However, it is still necessary to work with a representation of the synthetic asset within the StarkEx system that defines the smallest unit of the asset that users can trade.

In StarkEx, synthetic assets are converted to StarkEx units by applying a constant known as a resolution factor.

For collateral, resolution is also necessary to simplify certain calculations.

Resolution provides the following benefits:

  • Signed external oracle prices are given in round units, such as BTC or ETH, rather than satoshi or WEI, so resolution makes the translation to the price used in the StarkEx system valid.

  • Resolution enables trading in synthetic assets that do not necessarily correspond to ERC-20 tokens, such as barrels of oil.

The resolution is the total number of a single asset in the StarkEx system that equals one synthetic token. For example, if the smallest unit of DOGE that you can trade on StarkEx is 10-6 DOGE, that means that one synthetic DOGE = 106 StarkEx units. This number, 106, is the resolution factor for DOGE in StarkEx.

The formula to find the amount of a synthetic asset is:

\$text{synthetic_asset_amount} \cdot \text{resolution} = \text{StarkEx_amount}\$

Because every balance is within the range (-263, 263), then in the example above, each position can contain amounts of (10-6\$*\$-263, 10-6\$*\$263) DOGE.

Example

Consider synthetic ETH.

Consider that in Ethereum, 1 ETH = \$10^{18}\$ WEI.

  • Let \$\text{resolution} = 10^8\$. This means that the operator decided that the smallest unit of ETH their application supports is 10-8 synthetic ETH.

  • Alice has 2.25 synthetic ETH in her position.

What is the StarkEx amount of ETH in her position?

Plugging in these values to the formula above gives the following:

\$2.25 * 10^8 = 225,000,000\$

225,000,000 is the number of StarkEx units Alice holds that correspond to synthetic ETH in her position.

Identifiers for supported assets

The StarkEx API supports the following types of assets:

  • ETH

  • ERC-20

  • ERC-721

  • ERC-1155

  • Synthetics

Off-chain: The StarkEx API and SDK

The StarkEx API and SDK only apply off-chain.

In perpetual trading, synthetic assets are not represented on-chain and thus the off-chain asset id representation is enough. The asset id of a synthetic asset is a 120-bit number that represents their identity and resolution. For example, "ETH-8".encode('ascii') represents Ethereum with a resolution of \$10^8\$.

The SDK and API use two different terms to refer to a unique off-chain asset:

  • The StarkEx SDK uses the term assetId.

  • The StarkEx API uses the term token_id.

The StarkEx API does not include the keyword asset_id. Use the keyword token_id in API calls to refer to a unique off-chain asset.

On-chain: StarkEx contracts

StarkEx contracts use the following terms:

selector

A 4-byte constant that specifies the asset standard: ETH, ERC-20, ERC-721, or ERC-1155.

address

The address of the on-chain contract for a givent asset type.

assetInfo

The string concatenation of selector and address. The assetInfo parameter enables StarkEx to redeem assets according to their initial standard.

Be aware that in the ERC-721 and ERC-1155 on-chain StarkEx contracts, the terms assetId and tokenId have slightly different meanings from those in the contracts for ERC-20, ETH, and mintable ERC-721 assets.

Table 1. Meaning of assetId and tokenId for each type of asset
Asset type Description

ERC-20, ETH

There is no identifier for any one specific coin, unlike with ERC-721 or ERC-1155, because one unit of ETH or of an ERC-20 token has the same value as any other of the same type of token. StarkEx does not track individual assets of these types.

The StarkEx system calculates the value for assetId as follows:

  1. assetInfo = L1_contract_address + erc_type (selector)

  2. assetType = hash(assetInfo, quantum)

  3. assetId = assetType

ERC-721, ERC-1155

An NFT has two identifiers:

tokenId

Identifies the NFT within its on-chain ERC-721 or ERC-1155 contract scope, but another contract can have another asset with the same value for tokenId.

assetId

Uniquely identifies the NFT on the entire Ethereum blockchain.

For example, consider two NFTs within two separate contracts:

  • Mitzy is a digital kitten within contract A. Mitzy’s unique identifier within contract A is tokenId, which has a value of 0x1234.

  • Spot is a digital puppy within contract B. Spot’s unique identifier within contract B is tokenId, which also has a value of 0x1234.

So the value of tokenId is the same for both Mitzy and Spot.

However, both Mitzy and Spot each have their own unique value for assetId within the entire scope of Ethereum.

The value for assetId is calculated as follows:

  1. assetId = hash (selector, assetType, tokenId)

  2. assetType = hash(assetInfo, quantum) (For ERC-721, quantum = 1)

  3. tokenId is a unique identifier within the scope of the contract.

  4. assetInfo = L1_contract_address + erc_type (selector)

Mintable ERC-721

This type of NFT is minted on L2. It does not exist on-chain until your user withdraws it from off-chain to on-chain.

When your user withdraws the NFT from off-chain to on-chain, you need to provide them with the mintingBlob and the assetType and use those values to generate a value for assetId on your on-chain contract.

The value for assetId is calculated as follows:

assetId = hash(MINTABLE_PREFIX, assetType, blob_hash)

Computing assetInfo, assetType, and assetId

Below you can find a pseudo-code of the computation. Full implementation in JS can be found here.

ETH

def getEthAssetInfo():
   ETH_SELECTOR = '0x8322fff2' # '0x8322fff2' = bytes4(keccak256(“ETH()”))
   asset_info = ETH_SELECTOR
   return asset_info


def getEthAssetType(quantum):
   asset_info = getEthAssetInfo()
   asset_type = keccak256(asset_info, quantum)
                & 0x03FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
   return asset_type


def getEthAssetId(quantum):
   asset_id = getEthAssetType(quantum)
   return asset_id

ERC-20

def getErc20AssetInfo(address):
   ERC20_SELECTOR = '0xf47261b0'
   # '0xf47261b0' = bytes4(keccak256('ERC20Token(address)'))
   asset_info = ERC20_SELECTOR + bytes.fromhex(address[2:]).rjust(32, b'\0')
   # For ERC20, asset_info is 36 bytes long
   return asset_info


def getErc20AssetType(quantum, address):
   asset_info = getErc20AssetInfo(address)
   asset_type = keccak256(asset_info, quantum)
                & 0x03FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
   return asset_type


def getErc20AssetId(quantum, address):
   asset_id = getErc20AssetType(quantum, address)
   return asset_id

ERC-721

def getErc721AssetInfo(address):
   ERC721_SELECTOR = '0x02571792'
   # 0x02571792 = bytes4(keccak256('ERC721Token(address,uint256)'))
   asset_info = ERC721_SELECTOR + bytes.fromhex(address[2:]).rjust(32, b'\0')
   # For ERC721, asset_info is 36 bytes long.
   return asset_info


def getErc721AssetType(address):
   asset_info = getErc721AssetInfo(address)
   asset_type = keccak256(asset_info, 1)
                & 0x03FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
   return asset_type


def getErc721AssetId(token_id, address):
   asset_type = getErc721AssetType(address)
   asset_id = keccak256('NFT:', asset_type, token_id)
               & 0x03FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
   return asset_id

ERC-1155

def getErc1155AssetInfo(address):
    ERC1155_SELECTOR = `0x3348691d`
    # 0x3348691d = bytes4(keccak256('ERC1155Token(address,uint256)'))
    asset_info = ERC1155_SELECTOR + bytes.fromhex(address[2:]).rjust(32, b'\0')
    return asset_info


def getErc1155AssetType(address):
    asset_info = getErc1155AssetInfo(address)
    quantum = 1
    asset_type = keccak256(asset_info, quantum)
                 & 0x03FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
    return asset_type


def getErc1155AssetId(token_id, address):
    asset_type = getErc1155AssetType(address)
    asset_id = keccak256("NON_MINTABLE:", asset_type, token_id)
               & 0x03FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
    return asset_id

Mintable ERC-721

def getErcMintable721AssetInfo(address):
   MINTABLE_ERC721_SELECTOR = '0xb8b86672'
   # 0xb8b86672 = bytes4(keccak256('MintableERC721Token(address,uint256)'))
   asset_info = MINTABLE_ERC721_SELECTOR + bytes.fromhex(address[2:]).rjust(32, b'\0')
   # For Mintable ERC721, asset_info is 36 bytes long.
   return asset_info


def getMintableErc721AssetType(address):
   asset_info = getErcMintable721AssetInfo(address)
   asset_type = keccak256(asset_info, 1)
                & 0x03FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
   return asset_type


def getMintableErc721AssetId(minting_blob, address):
   asset_type = getMintableErc721AssetType(address)
   blob_hash = keccak256(minting_blob)
   asset_id = keccak256('MINTABLE:', asset_type, blob_hash)
              & 0x0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
   asset_id = asset_id
              | 0x400000000000000000000000000000000000000000000000000000000000000
   return asset_id

Mintable ERC-20

def getErcMintable20AssetInfo(address):
   MINTABLE_ERC20_SELECTOR = '0x68646e2d'
   # 0xb8b86672 = bytes4(keccak256('MintableERC20Token(address)'))
   asset_info = MINTABLE_ERC20_SELECTOR + bytes.fromhex(address[2:]).rjust(32, b'\0')
   # For Mintable ERC20, asset_info is 36 bytes long.
   return asset_info


def getMintableErc20AssetType(address, quantum):
   asset_info = getErcMintable20AssetInfo(address)
   asset_type = keccak256(asset_info, quantum)
                & 0x03FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
   return asset_type

def getMintableErc20AssetId(minting_blob, address, quantum):
   asset_type = getMintableErc721AssetType(address, quantum)
   blob_hash = keccak256(minting_blob)
   asset_id = keccak256('MINTABLE:', asset_type, blob_hash)
              & 0x0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
   asset_id = asset_id
              | 0x400000000000000000000000000000000000000000000000000000000000000
   return asset_id