How Flint perps work
Flint perps are linear perpetual futures on Solana. The margin program stores markets, trader accounts, maker quotes and resting orders. It matches orders and enforces margin, funding, settlement and liquidation rules. Flint's off-chain indexer, gRPC gateway, keepers and trading app read state and prepare or submit transactions. The program validates those transactions on-chain.
This page explains the main account and market rules. See margin, funding, liquidations, fees and units.
Account and market structure
┌──────────────── one trader account (cross-margined) ───────────────┐
│ │
│ spot balances perp positions │
│ ───────────── ────────────── │
│ USDC +1,000 ◄──────── PnL, funding and fees of every perp │
│ SOL +10 settle into its settlement asset │
│ … SOL-USDC-PERP long 50 │
│ BTC-USDC-PERP short 0.2 │
│ │
│ account health: net equity vs IM / MM │
└─────────────────────────────────────────────────────────────────────┘These rules apply across the markets:
- Your account is a balance sheet. Deposits of any listed asset are collateral. A perp position adds market risk (its margin) and routes its PnL, funding and fees into a spot balance of the contract's settlement asset. Health is assessed across the account; positions do not have separate isolated margin.
- The oracle determines the mark. A contract's mark is the oracle price of the underlying, placed on the contract's tick grid. Your unrealized PnL, margin and liquidation are all measured against it. Book prices determine execution and contribute to funding.
- Liquidity rests on-chain. Each contract has a set of market makers whose quotes live on-chain, and an order book where any trader can rest limit orders. An order crosses that liquidity in one transaction. Its kind determines whether an unfilled remainder rests, is dropped, or causes the order to revert.
- Each trade instruction is atomic. Fills, fees and margin checks execute in the same instruction. An instruction error reverts all of its state changes.
Markets
A market is a linear perp: a contract whose PnL is
size × Δprice, paid in a spot asset called its PnL currency
(settlement asset). On the current listings that asset is USDC, so
SOL-USDC-PERP is quoted in USDC per SOL and settles in USDC.
Each contract fixes, at listing:
| Field | What it sets |
|---|---|
underlying_oracle_id | The oracle entry the mark is derived from |
pnl_currency | The spot asset PnL, fees and funding settle in |
lot_scale | Size granularity: one lot is 10^-lot_scale of the underlying |
tick_size | Price granularity: PnL-currency atoms per tick |
imf / mmf | Initial / maintenance margin fraction of notional |
unpl_weight | Haircut applied to positive unrealized PnL |
taker_fee / maker_rebate_share | The trading fee and the maker's share of it |
max_open_interest_lots | Cap on long-side lots outstanding |
max_position_lots | Cap on one trader's net size either way, resting orders counted as filled |
| liquidation terms | Fee, liquidator share, price band, per-close notional bounds |
| funding parameters | How the funding rate is measured and capped |
taker_fee and maker_rebate_share can never change after listing.
Margin fractions and most funding parameters change only through a
24-hour timelock (RISK_UPDATE_DELAY_SECS), so a position is
never re-margined without notice.
Read live values with PerpMarketService.ListPerpMarkets (grid,
margin rates, open-interest cap) and AccountService.GetTradingFees.
See Units for how the grid maps to human prices.
Mark, index and the oracle
The program reads prices from an on-chain oracle cache that keepers fill from Pyth and Chainlink feeds. Each asset is registered as a single-source or quorum entry under a risk tier, which sets how fresh a feed must be, how far sources may disagree, and how uncertain a price may get before the program stops trusting it.
From each oracle entry the program materializes two kinds of price:
| Price | Used for | Shape |
|---|---|---|
| Risk-on (collateral credit / liability cover) | Opening risk, borrowing, withdrawing | The fair price shaded against you by a confidence band: collateral valued low, debt and perp notional valued high |
| Forced-action fair | Marks, liquidations, backstops | The unbanded fair price, usable for a bounded time after the feed goes quiet |
The index is the underlying's fair price per base unit. The mark is that same price rounded to the nearest tick on the contract's grid. The PnL currency is taken at par with USD for the mark, so the mark moves only with the underlying; a stablecoin depeg shows up in how your collateral is valued instead.
A mark is valid for 60 seconds (and never longer than the oracle
evidence it came from). An order or liquidation against an expired
mark is refused, so transactions usually bundle a mark refresh
(sync_linear_perp_cache) ahead of the order. A newly listed
contract has no mark until a keeper publishes one, and cannot trade
until then.
The order book as a trader sees it
Each contract holds liquidity in two accounts:
- A quoting account with a slot per maker, up to 32 per contract. A maker writes an explicit order list or bands at offsets from its fair price into the slot. The maker refreshes its prices, and quotes expire at slot-based deadlines.
- An order book (
order_bookPDA) of resting limit orders that any trader can post: at most one per trader per side and price, at most 16 per trader per contract.Gtcorders have no expiry;Gtdorders expire at an absolute slot.
When you trade, the program builds a virtual book for that order: it merges the quotes and resting orders of the makers your transaction includes, best price first. Matching stops when the size is filled or a price bound prevents another fill. Within one price level, makers share fills pro rata, with no time priority. A quoted level is checked for its full quantity after the position cap is applied. If the maker cannot support it under margin or leverage, that level and deeper quote levels are not offered for this order. A resting order that fails its owner's margin check is skipped; later orders can still fill. Other validation failures can revert the transaction.
MarketDataService supplies the merged view of enabled, unexpired
maker quotes. The app combines those quotes with resting orders read
directly from the on-chain book. Displayed depth can exceed executable
depth because of maker margin, the open-interest cap, price bounds,
or transaction account limits. PreviewOrder simulates an immediate
order against the makers included in its simulation.
Flint grants a trader quoting permission (MAY_QUOTE); the
maker then opens and releases its own slot on each contract. See
Maker onboarding and Quoting
for the maker side.
Orders
On-chain, place_perp_order takes a size, direction and kind.
The kind contains the price bounds and controls any unfilled quantity:
| Kind | Price fields | Result |
|---|---|---|
Gtc | limit, post_only | Fill within the limit and rest the remainder until cancelled. |
Gtd | limit, post_only, expires_at_slot | Fill within the limit and rest the remainder until its absolute expiry slot. |
Ioc | bound, min_fill_lots | Fill at least the minimum and at least one lot within the bounds; drop the remainder. |
Fok | bound | Fill every requested lot within the bounds, or revert. |
For Gtc and Gtd, post_only: true refuses the order if any quantity
would cross the makers included in the transaction. An omitted maker
does not prevent the order from resting.
For Ioc and Fok, bound is a TakerBound { limit, average }.
limit bounds each fill; optional average bounds the volume-weighted
average fill price before fees. Prices are positive UInt32 ticks:
a long accepts prices at or below its bound; a short accepts prices at
or above it. The average bound must be at or inside the individual limit.
| Argument | Meaning |
|---|---|
direction | Long (buy) or Short (sell) |
lots | Size budget, in lots |
reduce_only | Limit fills to the opposing position; a resting remainder reserves no margin and each later fill is limited to the position then held |
kind | One of Gtc, Gtd, Ioc, or Fok, with the fields above |
client_order_id | Your own identifier, echoed on the order's events |
book_scan | How many resting orders the crossing may read |
A Market order in the app uses Fok, with an individual price
limit derived from the mark and your maximum slippage. Limit
defaults to Gtc and also offers Gtd, Ioc and Fok. The app sets
no average-price bound. Resting orders are changed with
modify_perp_order and removed with cancel_all_perp_orders; anyone may prune expired ones
or force-cancel those of an account below initial margin. A resting
order that is not reduce-only reserves initial margin. Increasing that
reserve must also satisfy the account's leverage cap. Stop and
take-profit orders are not supported. See Orders.
Accounts and collateral
A trader account is a program-owned PDA seeded by a numeric
trader_id its creator picks, with an owner wallet that signs for
it. One account holds:
- up to
max_spot_positionsspot balances (collateral, lent deposits, borrows), and - up to
max_linear_perp_positionsperp positions,
both set in the program's global config. The owner can authorize delegates — another wallet or a passkey — scoped to trading, so an app can sign orders without the owner key. See Wallets & sessions. The account may also hold itself to a leverage cap stricter than the markets' own; see Collateral & margin.
Any listed spot asset can be deposited as collateral. Each one
counts at a deposit_weight haircut, and may also be lent to earn
interest or borrowed against. See
Collateral & margin.
The lifecycle of a trade
- Fund. Create a trader account and deposit collateral. The deposit lands as idle ("isolated") balance in that asset.
- Price. Keepers keep the oracle cache published and each contract's mark and funding fresh.
- Order. You (or your delegate) send
place_perp_orderwith the maker accounts to cross. The app selects them from the quoting account and order book. - Match. The program checks the mark is current, merges the
makers' quotes and resting orders, walks them up to your limit
and size, prices each trade's fee, and applies the fill to your
position and each maker's. A
GtcorGtdremainder rests on the book; withpost_only, any immediate fill is refused. AnIocremainder is dropped, andFokrequires the full size to fill. - Check. Every account touched must pass margin: initial margin (and the account's own leverage cap, if set) if the fill added exposure or reserve, maintenance if it only reduced it. The open-interest cap, position size cap and position-count limits are enforced. Any failure reverts the whole order.
- Hold. The position marks to the oracle and accrues funding continuously. Health includes unrealized PnL, accrued funding and unsettled fees at all times.
- Settle. Closing realizes PnL onto the position as unsettled
ticks. A permissionless
settle_realised_pnlmoves realized PnL, funding and fees into your settlement-asset balance — gains as far as losers have paid in, losses from idle cash first and then by borrowing the shortfall. - Withdraw. Withdrawals spend idle balance and must leave the account above initial margin.
If health falls below maintenance at any step, anyone may liquidate the account.
On-chain vs off-chain
| On-chain (the program) | Off-chain (Flint services) |
|---|---|
| Markets, grids, risk and fee parameters | gRPC gateway: market data, accounts, risk, funding, history |
| Oracle cache and price validation | Indexer mirroring program accounts in near real time |
| Makers' quotes, resting orders and the matching engine | Order preview by transaction simulation |
| Trader accounts, positions, balances | Historical fills, candles and account history |
| Margin checks, funding index, fees | Optional fee-payer sponsorship for app orders |
| Liquidation, backstop, settlement | Keepers that crank prices, funding, settlement and liquidations |
The upkeep instructions listed here are permissionless: anyone
can refresh a mark, settle funding, settle PnL, prune expired orders,
force-cancel eligible orders, or liquidate. The program validates each
action. API responses include DataProvenance with commitment, slot and
observation time. Use on-chain state to resolve differences with API data.
Glossary
| Term | Meaning |
|---|---|
| Trader account | Your cross-margined account on the program, keyed by trader_id |
| Collateral | Any deposited spot balance; counts at its deposit_weight |
| PnL currency | The spot asset a contract settles in (USDC on current listings) |
| Lot | The contract's size unit: 10^-lot_scale of the underlying |
| Tick | The contract's price unit: tick_size atoms of the PnL currency per lot |
| Index | The underlying's oracle fair price per base unit |
| Mark | The index on the contract's tick grid; what PnL and margin use |
| Notional | ` |
| IM / MM | Initial / maintenance margin: notional × imf / mmf, plus borrow margins |
| Net equity | Weighted collateral − debt + weighted PnL, in USD |
| Free collateral | Net equity − IM, floored at zero |
| Health ratio | Net equity ÷ MM; below 1 is liquidatable |
| Taker / maker | The order that crosses / the quote or resting order it crosses against |
| IOC | Immediate-or-cancel: fill now, drop the rest |
| FOK | Fill-or-kill: fill the full requested size within the price bounds, or revert |
| GTC / GTD | Good-till-cancelled / good-till-slot-expiry resting orders |
| Resting order | A Gtc or Gtd remainder waiting on the contract's order book |
| Post-only | post_only: true on Gtc or Gtd; the order must rest whole and is refused if it would cross an included maker |
| Funding | Periodic transfer between longs and shorts that ties the book to the index |
| Impact bid / ask | Average price to fill a fixed notional into each side of the book |
| Open interest | Long-side lots outstanding on a contract |
| Keeper | Any bot calling the permissionless crank, settle and liquidate instructions |
| Backstop | Voluntary takeover of a deeply underwater account by another account |
| Settle pool | Per-contract pool of losses paid in, from which winners are paid |