Skip to main content

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)
ParameterDescriptionType
minPriceMinimum supported priceuint128
tickSpacePrice increment per indexuint128
priceIndexIndex from 0 to 65,535uint16

Example

For an EURC/USDC market with:
  • minPrice = 0.90 (90 cents per EURC)
  • tickSpace = 0.0001 (0.01 cent increments)
Price IndexActual Price
00.9000
5000.9500
10001.0000
15001.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
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");
  }
}