Skip to main content

Formatting Prices and Sizes for Orders

When placing orders on Decibel, you need to convert decimal prices and sizes to chain units that match the market’s precision requirements. This guide explains how to use market configuration data from the v1/markets endpoint to properly format your order parameters.

Market Configuration

Each market returned from the v1/markets endpoint includes precision configuration:
{
  "lot_size": 1,
  "market_addr": "<string>",
  "market_name": "<string>",
  "max_leverage": 1,
  "max_open_interest": 123,
  "min_size": 1,
  "px_decimals": 1,
  "sz_decimals": 1,
  "tick_size": 1
}

Market Configuration Fields

  • px_decimals - The number of decimal places used for price precision. Prices are stored as integers with this many implied decimal places. For example, if px_decimals = 9, a price of 5.67 is stored as 5670000000 (5.67 × 10^9).
  • sz_decimals - The number of decimal places used for size precision. Order sizes are stored as integers with this many implied decimal places. For example, if sz_decimals = 9, a size of 1.5 is stored as 1500000000 (1.5 × 10^9).
  • tick_size - The minimum price increment in chain units. Prices must be multiples of this value. For example, if tick_size = 1000000 and px_decimals = 9, the minimum price increment is 0.001 (1000000 / 10^9).
  • lot_size - The minimum size increment in chain units. Order sizes must be multiples of this value. For example, if lot_size = 100000000 and sz_decimals = 9, the minimum size increment is 0.1 (100000000 / 10^9).
  • min_size - The minimum order size in chain units. Orders smaller than this will be rejected. For example, if min_size = 1000000000 and sz_decimals = 9, the minimum order size is 1.0 (1000000000 / 10^9).

Conversion Functions

Convert Decimal Amount to Chain Units

/**
 * Converts a decimal amount to chain units (e.g., USDC).
 * For USDC, this means multiplying by 10^6.
 * @param amount - The decimal amount to convert
 * @param decimal - The number of decimal places for the token (default is 6 for USDC).
 * @returns The amount in chain units
 */
export function amountToChainUnits(amount: number, decimal = 6): number {
  return Math.floor(amount * 10 ** decimal);
}

Convert Chain Units to Decimal Amount

/**
 * Converts chain units to decimal amount (e.g., USDC).
 * For USDC, this means dividing by 10^6.
 * @param chainUnits - The amount in chain units
 * @param decimal - The number of decimal places for the token (default is 6 for USDC).
 * @returns The decimal amount
 */
export function chainUnitsToAmount(chainUnits: number, decimal = 6): number {
  return chainUnits / 10 ** decimal;
}

Price Formatting

Round Price to Valid Tick Size

Prices must be rounded to the nearest valid tick size. Use this function to ensure your price is valid:
/**
 * Rounds a price to the nearest valid tick size
 * @param price - The decimal price to round
 * @param market - The market configuration object
 * @returns The rounded price
 */
export function roundToValidPrice(price: number, market: PerpMarket): number {
  if (price === 0) {
    return 0;
  }

// Convert to chain units
const denormalizedPrice = price \* 10 \*\* market.px_decimals;

// Round to nearest multiple of tickSize
const roundedPrice =
Math.round(denormalizedPrice / market.tick_size) \* market.tick_size;

// Convert back to decimal
const normalizedPrice = Math.round(roundedPrice) / 10 \*\* market.px_decimals;

return normalizedPrice;
}

Size Formatting

Round Size to Valid Lot Size

Order sizes must be rounded to the nearest valid lot size and meet the minimum size requirement:
/**
 * Rounds an order size to the nearest valid lot size
 * @param orderSize - The decimal order size to round
 * @param market - The market configuration object
 * @returns The rounded order size
 */
export function roundToValidOrderSize(
  orderSize: number,
  market: PerpMarket
): number {
  if (orderSize === 0) {
    return 0;
  }

const normalizedMinSize = market.min_size / 10 \*\* market.sz_decimals;

// Ensure size meets minimum requirement
if (orderSize < normalizedMinSize) {
return normalizedMinSize;
}

// Convert to chain units
const denormalizedOrderSize = orderSize \* 10 \*\* market.sz_decimals;

// Round to nearest multiple of lotSize
const roundedOrderSize =
Math.round(denormalizedOrderSize / market.lot_size) \* market.lot_size;

// Convert back to decimal
const normalizedOrderSize =
Math.round(roundedOrderSize) / 10 \*\* market.sz_decimals;

return normalizedOrderSize;
}

Complete Example

Here’s a complete example of formatting prices and sizes for placing an order:
// Fetch market data from v1/markets endpoint
const market = {
  market_addr: "0x456...def",
  market_name: "APT-USD",
  px_decimals: 9,
  sz_decimals: 9,
  tick_size: 1000000, // 0.001 in decimal (1000000 / 10^9)
  lot_size: 100000000, // 0.1 in decimal (100000000 / 10^9)
  min_size: 1000000000, // 1.0 in decimal (1000000000 / 10^9)
};

// User wants to place an order at $5.6789 with size 1.234
const userPrice = 5.6789;
const userSize = 1.234;

// Step 1: Round price to valid tick size
const roundedPrice = roundToValidPrice(userPrice, market);
// Result: 5.679 (rounded to nearest 0.001)

// Step 2: Round size to valid lot size and check minimum
const roundedSize = roundToValidOrderSize(userSize, market);
// Result: 1.2 (rounded to nearest 0.1, meets minimum of 1.0)

// Step 3: Convert to chain units for the transaction
const chainPrice = amountToChainUnits(roundedPrice, market.px_decimals);
// Result: 5679000000 (5.679 × 10^9)

const chainSize = amountToChainUnits(roundedSize, market.sz_decimals);
// Result: 1200000000 (1.2 × 10^9)

// Step 4: Build and submit transaction
const transaction = await aptos.transaction.build.simple({
sender: account.accountAddress,
data: {
function: `${PACKAGE}::dex_accounts_entry::place_order_to_subaccount`,
typeArguments: [],
functionArguments: [
"0x123...abc", // subaccountAddr
market.market_addr, // marketAddr
chainPrice, // price in chain units
chainSize, // size in chain units
true, // isBuy
0, // timeInForce
false, // isReduceOnly
null, // clientOrderId
null, // stopPrice
null, // tpTriggerPrice
null, // tpLimitPrice
null, // slTriggerPrice
null, // slLimitPrice
null, // builderAddress
null, // builderFees
],
},
});

Understanding the Values

Example Market Configuration

Let’s say a market has the following configuration:
{
  "px_decimals": 9,
  "sz_decimals": 9,
  "tick_size": 1000000,
  "lot_size": 100000000,
  "min_size": 1000000000
}
What this means:
  • Price Precision: Prices can have up to 9 decimal places. A price of 5.67 is stored as 5670000000 chain units.
  • Size Precision: Sizes can have up to 9 decimal places. A size of 1.5 is stored as 1500000000 chain units.
  • Tick Size: The minimum price increment is 0.001 (1000000 / 10^9). Valid prices are: 5.000, 5.001, 5.002, etc. Invalid: 5.0005.
  • Lot Size: The minimum size increment is 0.1 (100000000 / 10^9). Valid sizes are: 1.0, 1.1, 1.2, etc. Invalid: 1.05.
  • Min Size: The minimum order size is 1.0 (1000000000 / 10^9). Orders smaller than 1.0 will be rejected.

Market-Specific Values

For the actual min_size, lot_size, and tick_size values for each live market, see Market Parameters.

Common Pitfalls

  1. Not rounding prices: Prices must be multiples of tick_size. Always use roundToValidPrice() before converting to chain units.
  2. Not rounding sizes: Sizes must be multiples of lot_size. Always use roundToValidOrderSize() before converting to chain units.
  3. Forgetting minimum size: Orders below min_size will be rejected. The rounding function handles this automatically.
  4. Using wrong decimals: Always use px_decimals for prices and sz_decimals for sizes when calling amountToChainUnits().