Skip to content
LogoLogo

Prices, sizes, and units

There are two number systems to know about:

  • On the wire (the gRPC APIs), Decimal fields express prices, sizes, balances and USD amounts as decimal strings in human units.
  • On-chain, everything is an integer on the contract's grid — sizes in lots, prices in ticks — or a fixed-point number.

Use on-chain units when you build instructions, decode accounts or reconcile against the chain.

Decimal strings on the wire

Market, account and fill money fields in flint.margin.user.v1 and flint.margin.maker.v1 use Decimal { value } wrapping a string such as "155.14". Parse it with a fixed-precision type, never a float:

LanguageLibrary
Rustrust_decimal::Decimal
TypeScriptbignumber.js or decimal.js
FieldExampleMeaning
price, mark_price, entry_price, limit_price"150.125"Settlement asset per base unit (USDC per SOL)
index_price"150.12531"Oracle fair price per base unit, before tick rounding
quantity (positions)"-2.5"Base units, signed: positive long, negative short
quantity (orders, fills, book)"2.5"Base units, unsigned; the side is a separate field
*_usd"1298.25"US dollars
balances (raw_deposit, lent, borrowed)"1.5"Token units, already scaled by the mint's decimals
initial_margin_rate, maintenance_margin_rate"0.1"Fractions of notional
taker_fee_rate, maker_rebate_rate"0.001"Fractions of notional
FundingRate.rate"0.0002"Fraction of notional per interval_seconds (3600); positive means longs pay

The server converts these Decimal fields using the contract grid and mint decimals. Raw quoting state is an exception: GetQuotingState.payload_json contains grid integers, including ticks and lots. Transaction bytes, account bytes and fields explicitly named in lamports or other raw units also require their documented on-chain units.

Sign conventions

FieldPositive means
PerpPosition.quantityLong
FundingRate.rateLongs pay shorts
PerpPosition.accrued_funding_usd, FundingPayment.amount_usdA credit to the account
PerpPnlHistoryPoint.fees_usdNet rebates (fees are negative)
AccountHealth.deficitBelow maintenance, by that much

The contract grid

Each perp fixes two scales at listing, and every on-chain size and price lives on them:

1 lot  = 10^(−lot_scale) units of the underlying
1 tick = tick_size atoms of the PnL currency
price  = ticks per lot

A price on-chain is therefore "how many ticks one lot costs". The human forms follow directly:

lot_size     = 10^(−lot_scale)                                base units
price_step   = tick_size / 10^(settlement decimals) / lot_size     settlement per base unit

quantity     = lots  × lot_size
price        = ticks × price_step
fill value   = lots  × ticks × tick_size                      settlement atoms

PerpMarketService.ListPerpMarkets returns lot_size and tick_size already in human form: its tick_size is the price_step above (the price increment per base unit), not the raw on-chain atom count. min_order_quantity is one lot.

Illustrative grids that settle in USDC (6 decimals). Read the current grid from ListPerpMarkets before building a transaction:

Contractlot_scaletick_sizeLotPrice step$150 / $100,000 as ticks
SOL-USDC-PERP310.001 SOL$0.001150,000
BTC-USDC-PERP510.00001 BTC$0.101,000,000
JLP-USDC-PERP210.01 JLP$0.0001—

So "buy 2.5 SOL with a worst price of $150.25" on SOL-USDC-PERP is lots = 2,500, limit = 150,250.

Converting a limit price

Round a limit toward the side that protects you: a buy limit down, a sell limit up, so the on-chain price is never worse than the one you meant.

import BigNumber from "bignumber.js";
 
const priceStep = new BigNumber(market.tickSize!.value);   // from ListPerpMarkets
const lotSize = new BigNumber(market.lotSize!.value);
 
const lots = new BigNumber("2.5").div(lotSize);             // must be a whole number
const raw = new BigNumber("150.25").div(priceStep);
const limit = side === "buy"
  ? raw.integerValue(BigNumber.ROUND_FLOOR)
  : raw.integerValue(BigNumber.ROUND_CEIL);

Reject a size that is not a whole number of lots rather than rounding it silently.

Grid limits

QuantityOn-chain typeBound
Position sizei32 lots±2,147,483,647 lots
Order size (lots)u32, refused past the i32 position width2,147,483,647 lots
Mark and limit priceu32 ticks per lot1 to 4,294,967,295; zero is refused
Entry notionali64 ticksFits by construction: i32 × u32

A listing is only accepted if one lot is a whole number of the underlying's atoms and the oracle can resolve one lot's price to within half a tick.

On-chain number formats

ConceptRepresentationNotes
Token amountsu64 atomsScaled by the mint's decimals
USD amountsatoms, 1 USD = 10^6Account totals are 96-bit; per-balance values fit i64
Oracle priceU32.32 fixed pointUSD atoms per token atom
Spot share pricesQ64.64 fixed pointToken atoms per pool share
Margin fractions, weightsLessThanOneX64 (Q0.64)imf, mmf, unpl_weight, deposit_weight, borrow_imf, bands. Value = raw / 2^64, always below 1
Fee ratesPercentU32taker_fee, maker_rebate_share, liquidation_fee, liquidator_share. Value = raw / (2^32 − 1), so 100% is representable
Perp position sizei32 lotsSigned
Entry notional and unsettled legsi64 ticksRealized PnL, funding and fees are separate legs
Funding indexi128 ticks per lot, 32 fractional bitsOne per side, cumulative
Funding ratei64 ticks per lot per second, 32 fractional bitsHourly fraction = rate × 3600 / 2^32 / mark_ticks
CarrySigned Q0.63 fractionHourly, of mark
Account leverage capu64, Q0.64 fraction1 / max leverage; zero means no cap. Set with setAccountLeverageCap
Resting orderquantity in u32 lots; expiry a u64 slotOne order per trader per price in the contract's LinearPerpOrderBook
Order-book keyOrderKey { rank: u32, trader_id: u64 }Ascending rank is best price first: price ticks for asks, u32::MAX − price for bids. No time priority within a price

Fee rates quoted in basis points are stored truncated, so a quoted whole-tick fee lands exactly and any other one is at most one tick under the quoted rate. Every value the program derives rounds in the protocol's favour: margins and fees up, collateral and rebates down.

Identifiers

IdWidthOn the wire
TraderIdu64, chosen by the account's creatorTraderId { id }
LinearPerpIdu32, dense from 0PerpId { id }
SpotIdu32, dense from 0SpotId { id }
OracleIdu32OracleInfo.oracle_id
product_idcatalog-wide id across spots and perpsSpotMetadata.product_id

Trader accounts are PDAs seeded by ["trader_account", trader_id]; contract, quoting and cache accounts are PDAs of the program too. See On-chain clients for derivation helpers.

Time

WhereUnit
Wire TimestampUnix microseconds (micros); zero means unset
On-chain clocks, funding, timelocksUnix seconds
slotSolana slot; quote expiry is counted in slots
Gtd order expires_at_slotThe order is expired from this slot on; a Gtc order never expires. An expired order keeps reserving margin until pruned
History windowsstart inclusive, end exclusive

Every live response carries a DataProvenance: the commitment it was read at, the slot, and when the server observed it. Treat it as an observation, not proof of finality.

Enums on the wire

EnumValues
TradeSideTRADE_SIDE_BUY, TRADE_SIDE_SELL
OrderTypeORDER_TYPE_LIMIT, ORDER_TYPE_MARKET, … (stop and take-profit types are rejected)
TimeInForceNot read by PreviewOrder; on-chain the order's PerpOrderKind decides
PerpPositionStatusOPEN, CLOSED, LIQUIDATED
PerpMarketStatusIndexed markets report ACTIVE
CommitmentPROCESSED, CONFIRMED, FINALIZED
LimitConstraintWhich rule bounds an action, e.g. LIMIT_CONSTRAINT_INITIAL_MARGIN

On-chain, an order's side is a Direction — Long or Short — the way the taker wants to end up positioned. Its PerpOrderKind is Gtc, Gtd, Ioc or Fok. Price, expiry and post-only options belong to the selected kind; see On-chain clients. A resting order's side is a MakerSide, Buy or Sell.

SDK values vs on-chain values

LayerTakesExample
gRPC clients (flint-margin-user-client, flint-margin-maker-client)Decimal strings in human unitsquantity: { value: "2.5" }
Instruction builders (@superis-labs/flint-idl, flint-margin-onchain)Raw integers on the gridlots: 2500, kind: { __kind: "Gtc", limit: 150250, postOnly: true }
Account decodersRaw fixed-point and grid integersentry_contract_lots, entry_ticks

Convert exactly once, at the boundary where you build the instruction, with the grid from ListPerpMarkets. See TypeScript, Rust and On-chain clients.