Skip to main content
All Kimia math is fixed-point integer. There is no floating-point anywhere in the programs, and client code should follow the same convention.

Precision scales

QuantityDecimalsConstant (Rust)JS type
Base (SOL)9BASE_PRECISION = 1_000_000_000bigint
Quote / collateral (USDC)6QUOTE_PRECISION = 1_000_000bigint
Price6PRICE_PRECISION = 1_000_000bigint
Funding rate9FUNDING_PRECISION = 1_000_000_000bigint
Fixed-point (yield math)9ONE = 1_000_000_000bigint
BPS / margin ratio4BPS_PRECISION = 10_000number

Converting in and out

// USDC user input → on-chain
const usdcAmount = BigInt(Math.round(userInput * 1e6));

// Base (SOL) user input → on-chain
const baseAmount = BigInt(Math.round(userInput * 1e9));

// Price user input → on-chain
const price = BigInt(Math.round(userInputUsd * 1e6));

// On-chain value → display
const humanUsdc = Number(amount) / 1e6;
const humanBase = Number(amount) / 1e9;
Never multiply then divide through Number. Use bigint arithmetic end to end if any intermediate exceeds Number.MAX_SAFE_INTEGER (2^53 − 1).

Rounding convention

Everything rounds down in the user’s favor (i.e. against the trader on the protocol’s side). This is how the Rust crates do it:
pub fn mul_div_down(a: u128, b: u128, denom: u128) -> Option<u64> {
    // floor((a * b) / denom) with u128 intermediate
}
In TS, replicate via integer bigint math:
function mulDivDown(a: bigint, b: bigint, denom: bigint): bigint {
  return (a * b) / denom;   // '/' is floor for positive bigints
}

function mulDivUp(a: bigint, b: bigint, denom: bigint): bigint {
  return (a * b + denom - 1n) / denom;
}

Unrealized PnL example

base_amount  = +1_000_000_000      (1 SOL long)
quote_entry  = -130_000_000        (entered at $130)
mark_price   = 140_000_000         ($140)

pnl = base_amount × mark_price / BASE_PRECISION + quote_entry
    = 1e9 × 140e6 / 1e9 + (-130e6)
    = 140_000_000 + (-130_000_000)
    = +10_000_000
    = +$10.00 USDC
Note: quote_entry is negative for longs and positive for shorts. Signs capture direction.

Funding rate precision

Funding rates are 9-decimal signed i128. A rate of 5_000_000 ≈ 0.005 = 0.5% per period. Over a year of hourly periods, that’s ≈4,380%, an extreme but expressible bound. Cumulative rates live in two separate i128 accumulators (cumulative_funding_rate_long, _short) to preserve V2 asymmetric-funding headroom.

Time

  • Unix seconds everywhere.
  • Stored as i64 for consistency with Solana’s Clock::unix_timestamp.
  • In TS, BigInt(Math.floor(Date.now() / 1000)).

BPS vs fixed-point

Don’t mix them. Margin ratios and fees use BPS (4 decimals, denominator 10k). Funding and yield math use 9-decimal fixed point. Convert explicitly:
// 5% BPS → 9-decimal FP
const fp = (500n * ONE) / 10_000n;    // = 50_000_000n

Overflow protection

All Rust mul_div calls intermediate through u128 and return Option<u64>. In TS, bigint handles arbitrary size natively, overflow is impossible unless you explicitly cast to Number. Every on-chain instruction that overflows returns MathOverflow (error code 6070 on kimia-perp, similar codes on the other programs).