跳转至

PriceBook

The PriceBook contract handles price calculations for each market using an arithmetic pricing model.

Pricing Model

Sera uses an arithmetic price book where:

price = minPrice + (tickSpace × priceIndex)
Parameter Description Type
minPrice Minimum supported price uint128
tickSpace Price increment per index uint128
priceIndex Index from 0 to 65,535 uint16

Example

For an EURC/USDC market with:

  • minPrice = 0.90 (90 cents per EURC)
  • tickSpace = 0.0001 (0.01 cent increments)
Price Index Actual Price
0 0.9000
500 0.9500
1000 1.0000
1500 1.0500

View Functions

indexToPrice

Converts a price index to the actual price.

function indexToPrice(uint16 priceIndex) external view returns (uint256)

Parameters:

  • priceIndex - The price book index (0-65535)

Returns:

  • uint256 - The actual price in quote token units per base token

Example:

const price = await priceBook.indexToPrice(6500);
console.log(`Price at index 6500: ${ethers.formatUnits(price, 18)}`);

priceToIndex

Converts a price to the nearest valid price index.

function priceToIndex(
    uint256 price,
    bool roundingUp
) external view returns (uint16 index, uint256 correctedPrice)

Parameters:

  • price - The target price
  • roundingUp - If true, round up to next index; if false, round down

Returns:

  • index - The nearest valid price index
  • correctedPrice - The actual price at that index

Tip

Use roundingUp = false for bids (buy low) and roundingUp = true for asks (sell high).

Example:

const targetPrice = ethers.parseUnits("0.6533", 18);

// For a bid - round down to ensure we don't overpay
const { index: bidIndex, correctedPrice: bidPrice } = 
  await priceBook.priceToIndex(targetPrice, false);

// For an ask - round up to ensure we get at least target price
const { index: askIndex, correctedPrice: askPrice } = 
  await priceBook.priceToIndex(targetPrice, true);

console.log(`Bid index: ${bidIndex}, actual price: ${ethers.formatUnits(bidPrice, 18)}`);
console.log(`Ask index: ${askIndex}, actual price: ${ethers.formatUnits(askPrice, 18)}`);

maxPriceIndex

Returns the maximum supported price index.

function maxPriceIndex() external view returns (uint16)

Returns:

  • uint16 - Always 65535 (type(uint16).max)

priceUpperBound

Returns the maximum supported price.

function priceUpperBound() external view returns (uint256)

Returns:

  • uint256 - minPrice + (65535 × tickSpace)

minPrice

Returns the minimum price (price at index 0).

function minPrice() external view returns (uint128)

tickSpace

Returns the price increment per index.

function tickSpace() external view returns (uint128)

Price Calculation Examples

JavaScript Helper

class PriceCalculator {
  constructor(minPrice, tickSpace) {
    this.minPrice = BigInt(minPrice);
    this.tickSpace = BigInt(tickSpace);
  }

  indexToPrice(priceIndex) {
    return this.minPrice + this.tickSpace * BigInt(priceIndex);
  }

  priceToIndex(price, roundingUp = false) {
    const priceBN = BigInt(price);
    if (priceBN < this.minPrice) {
      throw new Error("Price below minimum");
    }

    const diff = priceBN - this.minPrice;
    let index = diff / this.tickSpace;

    if (roundingUp && diff % this.tickSpace > 0n) {
      index += 1n;
    }

    if (index > 65535n) {
      throw new Error("Price exceeds maximum");
    }

    return {
      index: Number(index),
      correctedPrice: this.indexToPrice(Number(index))
    };
  }
}

// Usage
const calc = new PriceCalculator(
  "900000000000000000",   // 0.9 with 18 decimals
  "100000000000000"       // 0.0001 with 18 decimals
);

console.log(calc.indexToPrice(1000));  // 1.0 with 18 decimals
console.log(calc.priceToIndex("950000000000000000"));  // { index: 500, ... }

Calculating Spread

async function getSpread(orderBook, priceBook) {
  const isEmpty = {
    bid: await orderBook.isEmpty(true),
    ask: await orderBook.isEmpty(false)
  };

  if (isEmpty.bid || isEmpty.ask) {
    return null;
  }

  const bestBidIndex = await orderBook.bestPriceIndex(true);
  const bestAskIndex = await orderBook.bestPriceIndex(false);

  const bestBid = await priceBook.indexToPrice(bestBidIndex);
  const bestAsk = await priceBook.indexToPrice(bestAskIndex);

  const spreadTicks = bestAskIndex - bestBidIndex;
  const spreadBps = (bestAsk - bestBid) * 10000n / bestBid;

  return {
    bidIndex: bestBidIndex,
    askIndex: bestAskIndex,
    bidPrice: bestBid,
    askPrice: bestAsk,
    spreadTicks,
    spreadBps: Number(spreadBps) / 100  // Convert to percentage
  };
}

Error Handling

The PriceBook will revert with INVALID_PRICE if:

  • Price is below minPrice
  • Price is above priceUpperBound
  • Rounding up would exceed maxPriceIndex
try {
  const { index } = await priceBook.priceToIndex(price, true);
} catch (e) {
  if (e.message.includes("INVALID_PRICE")) {
    console.log("Price out of supported range");
  }
}