Skip to content
Logo

Orders

A Flint order has a price bound and an execution kind. It crosses the maker quotes and resting orders included in its transaction. Ioc drops any unfilled remainder; Fok requires the full requested quantity to fill. Gtc and Gtd can leave a remainder on the contract's order book, with an optional post-only requirement. Resting orders can be modified or cancelled.

This page describes SDK 7.0.4, IDL 0.10.2, and program revision 7445b001633e7adce40c187b730cdd8bcf9bb4fb. Use the operator's matching program and SDK versions. Older order argument layouts are incompatible.

Supported orders

OrderIn the appOn-chain
MarketMarket tabFok, with a price limit derived from the mark and Max slippage
Resting limitLimit tab, GTC or GTDGtc or Gtd: cross within your limit, then rest the remainder
Limit IOCLimit tab, IOCIoc { bound, minFillLots }: fill within your price bounds and drop the remainder
Fill-or-killMarket tab, or Limit tab with FOKFok { bound }: fill every requested lot within the bounds or revert
Post-onlyPost only on a GTC or GTD limitpostOnly: true on Gtc or Gtd: rest whole; refuse an immediate crossing
Reduce-onlyTicket's Reduce only, or Close position dialogreduceOnly: true; the close dialog sends Fok
Good-till-slotLimit → GTD → Expiry slotGtd.expiresAtSlot, an absolute future slot
CancelPositions → Open orders, Cancel or Cancel allmodifyPerpOrder with zero lots, or cancelAllPerpOrders
Modify size, flag or expiryBuild an instructionmodifyPerpOrder
Average price boundBuild an instructionaverage on an Ioc or Fok bound

Stop, take-profit / stop-loss, and other trigger orders are not supported. clientOrderId is your own identifier: the program echoes it on the order's events but does not use it to match orders. The API's OrderType and TimeInForce enums name more types than the program executes. Treat the table above as the full list.

How an order matches

Each perp market has a quoting account, with a slot per maker holding their current quotes, and an order book holding traders' resting orders. When your order lands, the program:

  1. Checks the market's mark is fresh. A stale mark refuses all trading (LinearPerpMarkExpired). Anyone can refresh it with syncLinearPerpCache, and the app does so in the same transaction when needed.
  2. Merges the quotes and resting orders of the makers your transaction includes into one book, best price first.
  3. Matches up to your size, splitting pro rata among makers at the same price. It stops at the individual price limit, and for Ioc or Fok, any average-price bound you supplied.
  4. Charges the taker fee and books the fill into your position and each maker's.
  5. Rests the remainder for Gtc or Gtd, or drops it for Ioc. Fok is refused unless the full requested quantity fills. A postOnly order is refused if any quantity would cross the makers included in the transaction.
  6. Re-checks margin for you and every maker you filled against. If anyone fails, the whole transaction reverts.

The transaction names the makers to cross, as extra accounts in ascending trader-id order. That list covers quoting makers and traders with resting orders: a resting order only fills when its owner's trader account is passed. The app reads the quoting account and order book, then selects quote owners and resting-order owners. A disabled slot offers no quote liquidity; the same account's resting orders can still fill. A maker with no room for a new perp position is skipped. After applying the position cap, the program checks each quoted level's full quantity against margin and leverage. If that level fails, it and deeper quote levels are not offered for this order. A resting order that fails its owner's margin check is skipped without being cancelled; later orders can still fill.

See How Flint perps work for the full model and Fees for taker fees and maker rebates.

Resting orders

The unfilled part of a Gtc or Gtd order rests on the contract's order book at its limit price. Gtc has no expiry; Gtd expires at its absolute expiresAtSlot. Either kind can require postOnly: true.

  • One order per price. You hold at most one order per side and price on a contract. Posting again at a price where you already rest replaces that order's size, reduce-only flag, and expiry.
  • No time priority. Everyone resting at one price, quotes included, is filled pro rata.
  • At most 16 resting orders per trader per contract (PerpOpenOrderLimitExceeded past that), reduce-only ones included.
  • Only included makers are crossed. A resting order can have a price that crosses liquidity omitted from your transaction. postOnly: true is refused (PerpOrderWouldCross) only if it would cross the makers you included.
  • Margin is reserved. A resting order that is not reduce-only reserves initial margin on the lots it would add to your position if it filled whole. Bids and asks are not added together: only the worse side counts. Posting more reserve must satisfy initial margin and the account's leverage cap (HealthCheckFailed or AccountLeverageExceeded). The reserve counts toward initial margin only. Unfilled orders do not increase maintenance margin. See Collateral & margin.
  • Reduce-only orders reserve nothing. They rest whole even with no position to close yet, and each fill is cut to what the position then holds, so they never flip you.
  • Orders take a position slot. Resting on a contract uses one of your account's perp position slots even while you hold no position there. The contract's max_position_lots counts non-reduce-only resting orders as if they had filled.

Expiry

Gtd.expiresAtSlot is the first slot at which the order stops being fillable. Takers skip it from that slot, and anyone may prune it. An expiry at or before the current slot is refused (PerpOrderExpired). Use Gtc for no expiry. Ioc and Fok have no order-expiry field. Transaction blockhash expiry still applies to every kind.

modifyPerpOrder can set a resting order's expiry. A value of 18446744073709551615 (u64::MAX) removes the expiry.

An expired order keeps reserving margin until it is removed. A taker removes up to 8 expired orders of the makers it passed, and prunePerpOrders removes the rest (below).

Modify and cancel

InstructionWhoWhat it does
modifyPerpOrderOwner or TRADE delegateChanges one order, named by side (MakerSide.Buy for a bid, MakerSide.Sell for an ask) and price: its size, reduce-only flag, or expiry. Each field is optional; a size of 0 cancels it. It never crosses and keeps the order's price. A new price is a cancel plus a new order.
cancelAllPerpOrdersOwner or TRADE delegateCancels every resting order on one contract, including reduce-only orders. It succeeds even when there are no orders.
prunePerpOrdersAnyoneRemoves the expired orders of the trader accounts passed. Refused if none were expired (PerpNothingToPrune).
forceCancelPerpOrdersAnyoneRemoves a trader's margin-reserving orders on one contract once the account fails initial margin. Reduce-only orders stay. Refused if the account meets initial margin (PerpForceCancelMarginMet) or nothing is reserved (PerpForceCancelNothingReserved).

A modify that increases the reserve must satisfy the position cap, initial margin and the account's leverage cap, like a new post. One that lowers the reserve needs no margin check. A missing order is PerpOrderNotFound. Cancelling releases the margin reserve; it does not refund rent paid for the order-book space.

forceCancelPerpOrders tests initial margin without the account's leverage cap. Exceeding that cap alone does not permit forced cancellation.

Liquidation also cancels orders: liquidatePerp cancels every order the liquidatee rests on that contract before closing. See Liquidations.

The order ticket

ControlWhat it does
Long / ShortDirection (keys B / S).
Market / LimitOrder type (keys M / L). Market uses FOK. Limit offers GTC, GTD, IOC and FOK.
SizeIn the base asset, or toggle to USD. USD size is converted using the mark for Market, or your entered price for Limit, and the PnL currency's USD price. It is rounded down to whole lots.
25% / 50% / 75% / MaxSize against your free collateral.
Leverage0× up to the market maximum (1 ÷ initial margin rate). Sets size and margin estimates; it is not an order instruction argument.
Limit priceLimit only. Immediate fills must be at this price or better; GTC and GTD rest the remainder at it.
Time in forceGTC (the Limit default), GTD, IOC or FOK. GTD also requires an Expiry slot.
Post onlyGTC and GTD limits only. Refuse the order if any quantity would cross an included maker.
Reduce onlyPermit fills only against an opposing position.
EstimatesOrder value, margin required, entry and liquidation prices, slippage, fee, and health before and after, with a Book / Theoretical toggle. Est. avg. fill price is shown for Market.

The main button names what the order does to your position, e.g. Open long · SOL, Add to long, Reduce long, Close long, or Flip short.

Order settings

The wrench icon opens Order settings. Priority fee applies to orders and position closes. Max slippage sets the price band for Market orders and closes; a Limit order uses its entered price.

SettingDefaultRange
Max slippage5%0–10%, presets 1% / 2.5% / 5%
Priority fee0.00003 SOLNormal / Fast / Turbo, max 0.01 SOL

Slippage and price limits

Every order has a price limit in its kind. It is a ceiling for longs and a floor for shorts, and must be positive. For Gtc and Gtd, limit is also the resting price. For Ioc and Fok, TakerBound.limit limits each fill. Optional TakerBound.average limits the volume-weighted average price before fees: at or below that price for longs, at or above it for shorts. The average must be no worse than the individual limit.

  • Market orders in the app set it to the mark plus (long) or minus (short) your Max slippage.
  • Limit orders use your price, which must sit on the market's tick grid.

The app rounds a Market long's price limit down and a short's limit up to keep the rounded price inside the selected slippage band. It does not set an average-price bound (average: null).

The ticket estimates slippage as the absolute difference between the average fill price and the book midpoint, divided by that midpoint. Won't fill can mean that quotes are outside your bound or that available depth cannot fill the full FOK quantity. A resting limit can show Will rest.

Lifecycle

build → sign → execute ─┬→ Ioc: fill minimum or more; drop remainder
                       ├→ Fok: fill every requested lot
                       ├→ Gtc / Gtd: fill within limit; rest remainder
                       └→ refused: no state changes

resting ─▶ filled by later takers ─▶ gone when filled, cancelled,
           modified, pruned after expiry, or force-cancelled

An Ioc order must fill at least minFillLots and at least one lot. A Fok order must fill all requested lots. Otherwise the transaction reverts. A Gtc or Gtd order can cross nothing and rest whole. Fills appear in the History tab and through AccountService.GetFills / SubscribeFills (Positions & PnL).

The app shows resting orders under Positions → Open orders. It reads the selected trader's orders directly from the on-chain book, including orders placed by other clients. Use Cancel for one order or Cancel all for the selected account's resting orders. The app sends a separate cancellation for each affected contract.

Rejections

Program errors surface as Custom(code). The codes below match program revision 7445b001 and the SDK versions named above. Codes follow the program's error list, starting at 1. The full table is on Errors.

CodeNameWhat happenedWhat to do
3HealthCheckFailedAfter the fill or post, you (or a maker you crossed) would fail margin: initial margin if it adds exposure or reserve, maintenance if it only reduces.Trade smaller, add collateral, or retry without that maker.
50PerpOrderLotsExceedPositionWidthlots is past the i32 position width.Trade smaller.
51PerpOrderAverageBeyondLimitThe average bound is above the individual limit for a long, or below it for a short.Use an average bound at or inside the individual limit.
52PerpOrderFilledBelowMinimumFewer than Ioc.minFillLots crossed, or a Fok order did not fill every requested lot.Reduce size or review price bounds and the minimum fill.
53PerpOrderUnfilledAn Ioc order with a zero minimum crossed nothing within the price bounds.Refresh quotes and review price bounds.
54LinearPerpPositionLimitExceededThe fill or resting order would use more perp position slots than an account may hold.Close a position or cancel orders in another market.
55LinearPerpOpenInterestCapExceededThe market's open-interest cap would be exceeded.Trade smaller, or reduce instead.
56LinearPerpMarkExpiredThe market's mark is stale.Include syncLinearPerpCache before the order (the app does).
57PerpOrderNothingToReduceAn immediate reduce-only order has no opposing position to close.Check the current position and order direction.
58PerpOrderReducibleBelowMinimumThe opposing position is smaller than Ioc.minFillLots, or smaller than the requested Fok quantity.Lower the IOC minimum or the requested quantity.
76PerpOrderSelfMatchYour own account was passed as a maker.Exclude it.
77PerpOrderMakersUnorderedMaker accounts were not strictly ascending by trader id.Sort them.
87AccountLeverageExceededThe account meets initial margin but fails its leverage cap.Trade smaller, or change the cap (Collateral & margin).
88PerpOrderWouldCrossA Gtc or Gtd order with postOnly: true would cross an included maker.Change the price or set postOnly: false if an immediate fill is intended.
89PerpOrderNotFoundmodifyPerpOrder named no order of yours at that side and price.—
90PerpOpenOrderLimitExceededYou already rest 16 orders on this contract.Cancel some.
91LinearPerpPositionSizeExceededThe fill, or your resting orders counted as filled, would take your size past the contract's max_position_lots.Trade smaller or cancel orders.
92PerpOrderExpiredA Gtd expiry, or a modified resting-order expiry, is at or before the current slot.Use a future slot or remove the expiry.
94PerpOrderMinimumUnreachableIoc.minFillLots exceeds the requested lots.Lower the minimum or increase the requested quantity.

All prices in the order kind are positive UInt32 ticks. Zero is rejected during instruction decoding; it is not error code 51.

A wallet that is neither the owner nor an authorised delegate gets MissingRequiredSignature. Passkey-signed orders have their own errors (PasskeyIntentExpired, PasskeyNonceMismatch, …); see Wallets & sessions.

In the app, a fill failure reads: "The order could not fill at your price limit before quotes changed. Refresh quotes and try again, or review your minimum fill and price limit."

Placing orders from code

Preview first

OrderService.PreviewOrder simulates an order against live maker quotes and returns the executable size, average and worst price, estimated fee, and the resulting position and margin. It does not sign or submit anything.

use flint_margin_user_client::{Client, DEVNET_PUBLIC_ENDPOINT};
use flint_margin_user_client::proto::{
    Decimal, OrderType, PerpId, PreviewOrderRequest, TradeSide, TraderId,
};
 
let client = Client::connect(DEVNET_PUBLIC_ENDPOINT).await?;
let preview = client
    .order()
    .preview_order(PreviewOrderRequest {
        trader_id: Some(TraderId { id: 42 }),
        perp_id: Some(PerpId { id: 0 }),
        side: TradeSide::Buy as i32,
        quantity: Some(Decimal { value: "1.5".into() }),
        order_type: OrderType::Market as i32,
        limit_price: None,
        reduce_only: false,
    })
    .await?
    .into_inner()
    .preview;

PreviewOrder accepts MARKET, or LIMIT with a limit_price (previewed as IOC). Other types return INVALID_ARGUMENT. A MARKET preview uses a fixed 0.5% band around the mark, which may differ from your own slippage setting. Quantities and prices are decimal strings and must sit on the market's lot and tick grid (lot_size, tick_size from PerpMarketService.GetPerpMarket). Parse them with a decimal library, never floats.

Build the instruction

placePerpOrder takes:

ArgumentMeaning
linearPerpIdThe market.
directionDirection.Long buys; Direction.Short sells. A fill can increase, reduce, close, or reverse the existing position.
clientOrderIdYour own u64 identifier, echoed on the order's events.
lotsSize budget in contract lots. Never zero.
reduceOnlyLimit immediate fills to the opposing position. An immediate order reverts if nothing can be reduced or its minimum cannot be met. A resting remainder keeps the flag and reserves no margin.
kindGtc { limit, postOnly }, Gtd { limit, postOnly, expiresAtSlot }, Ioc { bound, minFillLots }, or Fok { bound }.
bookScan{ maxOrders }: how many resting orders the crossing may read, skipped ones included. 0 crosses quotes only; 4294967295 (u32::MAX) sets no cap.
auth{ __kind: "Wallet" }, or a passkey assertion (Wallets & sessions).

For Gtc and Gtd, limit is the price at which the remainder rests and the worst price accepted for an immediate fill. postOnly: true requires the whole order to rest. Gtd also requires a future expiresAtSlot.

For Ioc and Fok, bound is a TakerBound { limit, average }: limit bounds each fill, and optional average bounds the volume-weighted average fill price before fees. Use average: null in TypeScript or None in Rust for no average bound. Only Ioc has minFillLots; Fok requires the full requested lots.

An Ioc with minFillLots equal to the requested lots also requires a full fill. The app uses the native Fok kind for its FOK orders.

import type { PerpOrderKindArgs } from "@superis-labs/flint-idl";
 
const ioc: PerpOrderKindArgs = {
  __kind: "Ioc",
  bound: { limit: 150_250, average: 150_100 },
  minFillLots: 1,
};
const resting: PerpOrderKindArgs = {
  __kind: "Gtc",
  limit: 150_000,
  postOnly: true,
};

The accounts are the global config, spot product cache, oracle cache, linear-perp cache, the market's linearPerp, quoting, and orderBook PDAs, your trader account (and delegate account, if you sign as a delegate), plus the maker trader accounts to cross as writable remaining accounts: quoting makers and the owners of any resting orders you want to reach. The SDKs export a find…Pda helper for each and withRemainingAccounts to append makers. Picking makers means reading the market's quoting account and order book; see On-chain clients. On success the instruction's return data carries a version byte, then filled_lots and filled_ticks.

The rentPayer must sign and fund any rent increase when a fill grows your account or a maker's position list, or when a resting remainder grows the order book. This role is separate from the transaction fee payer and account owner; one key can fill several roles.

Convert between decimal prices and ticks/lots with the market's grid; see Prices, sizes, units.

Manage resting orders

import {
  findGlobalConfigPda,
  findOrderBookPda,
  findTraderAccountPda,
  getCancelAllPerpOrdersInstructionAsync,
  getModifyPerpOrderInstructionAsync,
  MakerSide,
} from "@superis-labs/flint-idl";
 
const linearPerpId = 0;
const [globalConfig] = await findGlobalConfigPda();
const [orderBook] = await findOrderBookPda({ linearPerpId });
const [traderAccount] = await findTraderAccountPda({ traderId: 42n });
 
// Shrink the bid resting at 150_000 ticks to 5 lots; keep its flag and expiry.
const modifyIx = await getModifyPerpOrderInstructionAsync({
  rentPayer: wallet,
  authority: wallet,
  traderAccount,
  globalConfig,
  spotProductCache,
  oracleCache,
  linearPerpCache,
  linearPerp,
  orderBook,
  systemProgram,
  linearPerpId,
  side: MakerSide.Buy,
  priceTicks: 150_000,
  lots: { isSome: true, value: 5 },
  reduceOnly: { isSome: false, value: false },
  expiresAtSlot: { isSome: false, value: 0n },
  auth: { __kind: "Wallet" },
});
 
// Or take every order on the contract off the book.
const cancelIx = await getCancelAllPerpOrdersInstructionAsync({
  authority: wallet,
  traderAccount,
  globalConfig,
  orderBook,
  linearPerpId,
  auth: { __kind: "Wallet" },
});

The optional fields of modifyPerpOrder are fixed-width { isSome, value } pairs; with isSome: false the value is ignored. The other accounts are found with the PDA helpers listed on On-chain clients.