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
| Quantity | Decimals | Constant (Rust) | JS type |
|---|
| Base (SOL) | 9 | BASE_PRECISION = 1_000_000_000 | bigint |
| Quote / collateral (USDC) | 6 | QUOTE_PRECISION = 1_000_000 | bigint |
| Price | 6 | PRICE_PRECISION = 1_000_000 | bigint |
| Funding rate | 9 | FUNDING_PRECISION = 1_000_000_000 | bigint |
| Fixed-point (yield math) | 9 | ONE = 1_000_000_000 | bigint |
| BPS / margin ratio | 4 | BPS_PRECISION = 10_000 | number |
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).