Skip to Content
DevelopersVelocity SDKPrecision and Types

Precision and Types

The Velocity SDK uses integer big numbers (BN) for most values. Always convert using the SDK’s precision constants and helpers.

Precision Constants

Each constant is a BN representing a power of 10. Divide a raw value by its precision constant to get the human-readable number.

ConstantValueUsed For
PRICE_PRECISION1e6Oracle prices, order prices
BASE_PRECISION1e9Perp position sizes
QUOTE_PRECISION1e6USD amounts, collateral

Converting Between Raw and Human-Readable Values

BN → Human Number

import { BN, PRICE_PRECISION, BASE_PRECISION, QUOTE_PRECISION, convertToNumber } from "@velocity-exchange/sdk"; // Oracle price: raw 150_500_000 → 150.5 USD const rawPrice = new BN(150_500_000); const price = convertToNumber(rawPrice, PRICE_PRECISION); console.log(price); // 150.5 // Position size: raw 2_500_000_000 → 2.5 SOL const rawBase = new BN(2_500_000_000); const size = convertToNumber(rawBase, BASE_PRECISION); console.log(size); // 2.5 // Collateral: raw 10_000_000 → 10 USDT (or dUSDT on devnet) const rawQuote = new BN(10_000_000); const usd = convertToNumber(rawQuote, QUOTE_PRECISION); console.log(usd); // 10
Function convertToNumberReference ↗

Converts a fixed-point `BN` into a human-readable JS `number` by dividing out `precision`. Correctly handles negative `bigNumber` values: `bn.js`'s `.div()`/`.mod()` truncate toward zero (matching JS `%` semantics), so the whole and fractional parts recombine with consistent sign without needing separate negative-number handling. Precision beyond what a JS `number` (IEEE-754 double) can represent exactly may be lost — this is a display/estimation helper, not for further on-chain-precision math.

ParameterTypeRequired
bigNumber
any
The value to convert; `null`/`undefined` return 0 (a `BN` instance for zero is still an object and thus truthy, so it falls through to the normal division below)
Yes
precision
any
The fixed-point precision to divide out; defaults to `PRICE_PRECISION` (1e6)
No
Returns
number

Human Number → BN

import { BN, BASE_PRECISION, PRICE_PRECISION } from "@velocity-exchange/sdk"; // 1 SOL in base precision const oneSol = new BN(1).mul(BASE_PRECISION); // BN(1_000_000_000) // $21.23 in price precision const price = new BN(21_230_000); // 21.23 * 1e6 // Or use VelocityClient convenience helpers (available after setup): const size = velocityClient.convertToPerpPrecision(1); // 1 base unit as BN const px = velocityClient.convertToPricePrecision(21.23); // price as BN const spot = velocityClient.convertToSpotPrecision(0, 100); // 100 quote-asset units as BN
Example Human Number to BNReference ↗
TypeScript docs unavailable for Human Number to BN.

BigNum: a value that carries its own precision

BigNum wraps a BN together with the number of decimal places that BN is scaled by, so a value and its precision travel as one object. Use it when you would otherwise hand-roll conversion and formatting: it prints, parses, and does arithmetic without you tracking the exponent by hand.

The second constructor argument is the exponent, not the precision constant. Pass PRICE_PRECISION_EXP (6), BASE_PRECISION_EXP (9), QUOTE_PRECISION_EXP (6), and so on, not PRICE_PRECISION (1e6).

import { BN, BigNum, PRICE_PRECISION_EXP, BASE_PRECISION_EXP, } from "@velocity-exchange/sdk"; // Raw oracle price (1e6) wrapped with its exponent const price = BigNum.from(new BN(150_500_000), PRICE_PRECISION_EXP); price.print(); // "150.5" price.toFixed(2); // "150.50" price.toNotional(); // "$150.50" price.toNum(); // 150.5 // Parse a user-entered string into the right precision const size = BigNum.fromPrint("2.5", BASE_PRECISION_EXP); size.toString(); // "2500000000" (the raw BN, 1e9)
Class BigNumReference ↗
PropertyTypeRequired
val
BN
Yes
precision
BN
Yes
bigNumFromParam
any
Yes
add
(bn: BigNum) => BigNum
Yes
sub
(bn: BigNum) => BigNum
Yes
mul
(bn: any) => BigNum
Yes
scalarMul
(bn: any) => BigNum
Multiplies by another big number then scales the result down by the big number's precision so that we're in the same precision space
Yes
div
(bn: any) => BigNum
Yes
shift
(exponent: any, skipAdjustingPrecision?: boolean | undefined) => BigNum
Shift precision up or down
Yes
shiftTo
(targetPrecision: BN) => BigNum
Shift to a target precision
Yes
scale
(numerator: any, denominator: any) => BigNum
Scale the number by a fraction
Yes
toPercentage
(denominator: BigNum, precision: number) => string
Yes
gt
(bn: any, ignorePrecision?: boolean | undefined) => boolean
Yes
lt
(bn: any, ignorePrecision?: boolean | undefined) => boolean
Yes
gte
(bn: any, ignorePrecision?: boolean | undefined) => boolean
Yes
lte
(bn: any, ignorePrecision?: boolean | undefined) => boolean
Yes
eq
(bn: any, ignorePrecision?: boolean | undefined) => boolean
Yes
eqZero
() => boolean
Yes
gtZero
() => boolean
Yes
ltZero
() => boolean
Yes
gteZero
() => boolean
Yes
lteZero
() => boolean
Yes
abs
() => BigNum
Yes
neg
() => BigNum
Yes
toString
(base?: number | "hex" | undefined, length?: number | undefined) => string
Yes
print
() => string
Pretty print the underlying value in human-readable form. Depends on precision being correct for the output string to be correct
Yes
prettyPrint
(useTradePrecision?: boolean | undefined, precisionOverride?: number | undefined, decimalOverride?: number | undefined) => string
Yes
printShort
(useTradePrecision?: boolean | undefined, precisionOverride?: number | undefined) => string
Print and remove unnecessary trailing zeroes
Yes
debug
() => void
Yes
toFixed
(fixedPrecision: number, rounded?: boolean | undefined) => string
Pretty print with the specified number of decimal places
Yes
getZeroes
any
Yes
toRounded
(roundingPrecision: number) => BigNum
Yes
toPrecision
(fixedPrecision: number, trailingZeroes?: boolean | undefined, rounded?: boolean | undefined) => string
Pretty print to the specified number of significant figures
Yes
toTradePrecision
(rounded?: boolean | undefined) => string
Yes
toNotional
(useTradePrecision?: boolean | undefined, precisionOverride?: number | undefined, decimalOverride?: number | undefined) => string
Print dollar formatted value. Defaults to fixed decimals two unless a given precision is given.
Yes
toMillified
(precision?: number | undefined, rounded?: boolean | undefined, type?: "financial" | "scientific" | undefined) => string
Yes
toJSON
() => { val: string; precision: string; }
Yes
isNeg
() => boolean
Yes
isPos
() => boolean
Yes
toNum
() => number
Get the numerical value of the BigNum. This can break if the BigNum is too large.
Yes

Useful methods, grouped by what they do:

GroupMethods
CreateBigNum.from(val, exponent), BigNum.fromPrint(string, exponent), BigNum.zero(exponent), BigNum.fromJSON
Arithmeticadd, sub, mul, scalarMul, div, scale(numerator, denominator), abs, neg
Precisionshift(exponent), shiftTo(targetExponent)
Comparegt, lt, gte, lte, eq, plus the zero checks gtZero, ltZero, eqZero, gteZero, lteZero
Printprint, printShort, prettyPrint, toFixed, toPrecision, toRounded, toNotional, toMillified, toPercentage
Escape hatchestoString (raw BN string), toNum (JS number), toJSON

Three behaviors worth knowing before you rely on it:

  • add and sub assert that both operands have the same exponent. Call shiftTo first if they do not.
  • mul returns a value whose exponent is the sum of the two exponents. scalarMul shifts the result back down so it stays in the original precision space, which is usually what you want when multiplying a price by a ratio.
  • toNum goes through parseFloat, so it loses accuracy on very large values. Keep money math in BigNum or BN and convert only for display.

BigNum.setLocale(locale) sets the decimal delimiter and thousands separator used by every printing method, globally for the class.

Slots Versus Wall Clock

Solana’s slot time is moving from 400ms down to 200ms through a feature-gate schedule (400, 350, 300, 250, 200). Because of that, a slot count is not a fixed amount of time. The protocol stores durations in milliseconds and converts them to slots using the live slot length, and math/time.ts gives you the same conversions off-chain.

The live slot length comes from the state account. Three fields drive it:

State fieldMeaning
slotDurationMsCurrent slot length in ms. 0 means unset and resolves to the 400ms baseline.
pendingSlotDurationMsA staged next value. 0 means nothing is staged.
slotDurationEffectiveSlotThe slot at which the staged value takes over.

Read it with activeSlotDurationFromState(state, currentSlot), which applies the staged switch once currentSlot reaches the effective slot. slotDurationFromState(raw) only resolves the 0 sentinel on the base field, so it keeps returning the pre-switch value across a gate flip.

import { BN, activeSlotDurationFromState, millisFromSecs, millisToSlotsCeil, millisFromSlots, msToSlotsCeilNum, slotsToMsNum, SLOT_DURATION_BASELINE, } from "@velocity-exchange/sdk"; const state = velocityClient.getStateAccount(); const currentSlot = new BN(await connection.getSlot()); // The slot length the program itself would use right now const slotDuration = activeSlotDurationFromState(state, currentSlot); // A 10 second window, expressed in actual slots const tenSeconds = millisFromSecs(10); const slots = millisToSlotsCeil(tenSeconds, slotDuration); // 25 slots at 400ms, 50 at 200ms // A measured slot delta, back to wall-clock ms const elapsedMs = millisFromSlots(new BN(20), slotDuration); // Plain-number variants for pacing and thresholds const auctionSlots = msToSlotsCeilNum(8_000, slotDuration); const auctionMs = slotsToMsNum(auctionSlots, slotDuration); console.log(SLOT_DURATION_BASELINE); // 400, the pre-gate baseline
Function activeSlotDurationFromStateReference ↗
TypeScript docs unavailable for activeSlotDurationFromState.

The rounding direction matters and mirrors the program exactly:

HelperRoundingUse for
millisFromSlots(slots, d)exactTurning a measured slot delta into elapsed time
millisToSlots(m, d)downStaleness windows, where shorter is the safe direction
millisToSlotsCeil(m, d)upWindows that protect the user, such as auction lengths and minimum cooldowns
msToSlotsNum(ms, d)downThe same as millisToSlots, for plain numbers
msToSlotsCeilNum(ms, d)upDurations that must not fall below their intended wall-clock length
slotsToMsNum(slots, d)exactPlain-number version of millisFromSlots
divPeriods(m, period)downHow many whole periods fit in a duration, for legacy per-period rates

Two more things to keep straight:

  • Millis and SlotDurationMs are branded types. A duration in milliseconds cannot be compared against a slot count without converting through the live slot length, and the type system enforces that. Build durations with millis(ms) or millisFromSecs(secs).
  • STORED_UNIT_MS (400) is a storage codec for older admin-set fields that were encoded in units of the historical 400ms slot. Decode those with millisFromStoredUnits. Do not use it as a general slots-to-seconds conversion factor.

Token Math Helpers

Spot balances are stored as interest-bearing scaled values. These helpers convert them to actual token amounts.

getTokenAmount converts a raw scaled spot balance into a token amount, accounting for accumulated interest since the last update. Pass the user’s scaledBalance, the spot market account (which contains the cumulative interest index), and the balance type (deposit or borrow).

import { getTokenAmount, SpotBalanceType, convertToNumber } from "@velocity-exchange/sdk"; const spotMarket = velocityClient.getSpotMarketAccount(0); // e.g. dUSDT on devnet, USDT on mainnet const user = velocityClient.getUser(); const spotPosition = user.getUserAccount().spotPositions[0]; const tokenAmount = getTokenAmount( spotPosition.scaledBalance, spotMarket, spotPosition.balanceType ); console.log("Token amount:", tokenAmount.toString());
Function getTokenAmountReference ↗

Calculates the spot token amount including any accumulated interest.

ParameterTypeRequired
balanceAmount
any
The balance amount, typically from `SpotPosition.scaledBalance`
Yes
spotMarket
SpotMarketAccount
The spot market account details
Yes
balanceType
SpotBalanceType
The balance type to be used for calculation
Yes

The calculated token amount, scaled by `SpotMarketConfig.precision`

Returns
BN

getSignedTokenAmount wraps getTokenAmount to return a signed value: positive for deposits, negative for borrows. Use this when you need to distinguish between the two in a single number.

import { getTokenAmount, getSignedTokenAmount } from "@velocity-exchange/sdk"; const spotMarket = velocityClient.getSpotMarketAccount(0); const spotPosition = velocityClient.getUser().getUserAccount().spotPositions[0]; const tokenAmount = getTokenAmount( spotPosition.scaledBalance, spotMarket, spotPosition.balanceType ); const signed = getSignedTokenAmount(tokenAmount, spotPosition.balanceType); // signed > 0 means deposit, signed < 0 means borrow console.log("Signed amount:", signed.toString());
Function getSignedTokenAmountReference ↗

Returns the signed (positive for deposit,negative for borrow) token amount based on the balance type.

ParameterTypeRequired
tokenAmount
any
The token amount to convert (from `getTokenAmount`)
Yes
balanceType
SpotBalanceType
The balance type to determine the sign of the token amount.
Yes

- The signed token amount, scaled by `SpotMarketConfig.precision`

Returns
BN
Last updated on