# FLINT Perps FLINT Perps — on-chain perpetual futures on Solana. Trader guides, maker onboarding, SDKs, and API reference. # 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](/overview/margin), [funding](/overview/funding), [liquidations](/overview/liquidations), [fees](/overview/fees) and [units](/overview/units). ## Account and market structure ```text ┌──────────────── 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: 1. **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. 2. **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](/overview/funding). 3. **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. 4. **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](/overview/units) for how the grid maps to human prices. :::tip[Example listing] The devnet `SOL-USDC-PERP` uses `lot_scale = 3` (1 lot = 0.001 SOL), `tick_size = 1` USDC atom (a price step of $0.001 per SOL), `imf = 10%`, `mmf = 5%`, `unpl_weight = 0.9`, a 0.10% taker fee with a 50% maker rebate share. The worked examples on these pages use those numbers. Always read the live values for the network you trade on. ::: ## 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_book` PDA) 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. `Gtc` orders have no expiry; `Gtd` orders 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](/makers/onboarding) and [Quoting](/makers/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](/traders/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_positions` spot balances (collateral, lent deposits, borrows), and * up to `max_linear_perp_positions` perp 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](/traders/accounts). The account may also hold itself to a **leverage cap** stricter than the markets' own; see [Collateral & margin](/overview/margin#leverage-cap). 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](/overview/margin). ## The lifecycle of a trade 1. **Fund.** Create a trader account and deposit collateral. The deposit lands as idle ("isolated") balance in that asset. 2. **Price.** Keepers keep the oracle cache published and each contract's mark and funding fresh. 3. **Order.** You (or your delegate) send `place_perp_order` with the maker accounts to cross. The app selects them from the quoting account and order book. 4. **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 `Gtc` or `Gtd` remainder rests on the book; with `post_only`, any immediate fill is refused. An `Ioc` remainder is dropped, and `Fok` requires the full size to fill. 5. **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. 6. **Hold.** The position marks to the oracle and accrues funding continuously. Health includes unrealized PnL, accrued funding and unsettled fees at all times. 7. **Settle.** Closing realizes PnL onto the position as unsettled ticks. A permissionless `settle_realised_pnl` moves 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. 8. **Withdraw.** Withdrawals spend idle balance and must leave the account above initial margin. If health falls below maintenance at any step, anyone may [liquidate](/overview/liquidations) 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** | `|size| × mark`, valued in USD | | **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 | # Collateral & margin Every trader account is **cross-margined**: all collateral, all borrows and all perp positions fold into one health number. There is no isolated-margin mode — a loss on one perp draws on the same equity as every other position, and a gain on one supports the rest. "Leverage" in the app is a sizing helper over that one account, not a per-position setting. On-chain, an account can opt into one account-wide [leverage cap](/overview/margin#leverage-cap). ## The health formula The program keeps four account totals in USD: ```text net_equity = Σ deposit_value × deposit_weight − Σ borrow_value + Σ perp PnL (routed through the settlement asset's balance) net_exposure = Σ borrow_value + Σ perp notional IM = Σ borrow_value × borrow_imf + Σ perp notional × imf + Σ order reserve × imf MM = Σ borrow_value × borrow_mmf + Σ perp notional × mmf perp notional = |size| × mark order reserve = lots your resting orders would add if filled × mark ``` and two gates: | Condition | Meaning | | --- | --- | | `net_equity ≥ IM` | You may add risk: open or grow a position, rest an order, borrow, withdraw. With a [leverage cap](/overview/margin#leverage-cap) set, also `net_equity ≥ cap × net_exposure` | | `net_equity ≥ MM` | You are not liquidatable | `IM ≥ MM` always holds (a listing with `mmf > imf` is refused), so between them sits a band where you cannot add risk but are not liquidated — the buffer that stops a fresh position from being liquidatable the moment it opens. The API reports these as `AccountHealth`: `net_equity`, `initial_margin`, `maintenance_margin`, `free_collateral` (`net_equity − IM`, floored at zero), `deficit` (`MM − net_equity`, floored at zero) and `health_ratio` (`net_equity / MM`; below 1 is liquidatable). Every margin value rounds **up** and every collateral value rounds **down**, so rounding never works against the protocol. ## How a perp position enters health A perp contributes in two places: * **Market risk.** Its notional adds to `net_exposure`, and `notional × imf` / `notional × mmf` add to IM and MM. Notional uses the contract's mark, valued in USD through the settlement asset's price. * **PnL.** Its PnL is added to your balance of the contract's settlement asset (USDC today), and that balance is then valued like any other spot balance. The PnL that flows into the settlement balance has two legs: | Leg | Treatment | | --- | --- | | Unrealized PnL `size × mark − entry` | A **gain** is haircut by `unpl_weight`; a loss counts in full | | Realized PnL, accrued funding, unsettled fees | Counted at face value — already crystallized | Because the PnL lands in a spot balance, the settlement asset's own weights apply on top: * While your USDC balance (deposits plus PnL) is positive, it counts at USDC's `deposit_weight`. A paper gain is therefore haircut twice: by `unpl_weight`, then by `deposit_weight`. * If losses push that balance **negative**, it is treated exactly like a USDC **borrow**: it subtracts in full from equity and adds `borrow_imf` / `borrow_mmf` of itself to IM and MM. ### Resting orders A resting limit order that isn't reduce-only reserves **initial** margin on the lots it would add to the position if it filled whole, valued like the position at the mark. Bids and asks on one contract don't add up: the reserve is the worse of the two sides, and lots that would only shrink the position reserve nothing. Reduce-only orders reserve nothing at all. The reserve is in IM only. It isn't exposure and doesn't touch MM, so resting orders never make an account liquidatable. They do use up free collateral, and once an account falls below IM anyone may remove its margin-reserving orders with `force_cancel_perp_orders`. See [Orders](/traders/orders#resting). :::warning[Keep settlement-asset cash] If a loss on a USDC-settled perp exceeds your USDC balance, it creates USDC debt. Under current devnet parameters, this debt adds 75% of its value to initial margin and 50% to maintenance margin. ::: ## Accepted collateral Any listed spot market is collateral. Each one has its own parameters, read with `MarketService.ListMarkets` (`MarketInfo`): | Parameter | Meaning | | --- | --- | | `deposit_weight` | Fraction of a deposit's USD value counted as equity | | `borrow_imf` / `borrow_mmf` | Margin charged on a borrow of the asset | | `liquidation_bonus` | What a liquidator earns per dollar seized in this asset | | `deposit_limit` / `borrow_limit` | Market-wide caps | | flow caps | Rate limits on deposits and withdrawals over time | On devnet the listed collateral is SOL, cbBTC, JLP and USDC; each network lists its own set. A deposit lands as **idle** balance: collateral immediately, earning nothing. You can **lend** it into the market's pool to earn interest (still collateral), or **borrow** against your account. Borrowing, withdrawing, and lending are spot operations on the same account and are gated by the same health check. Prices are oracle prices. For risk-increasing actions (opening, borrowing, withdrawing) collateral is valued at the oracle's *collateral-credit* price and debts and perp notional at its *liability-cover* price — the fair price shaded against you by a confidence band. Liquidation uses the unbanded fair price. See [How Flint perps work](/overview/exchange#mark-index-and-the-oracle). ## Leverage limits A contract's maximum leverage on equity is `1 / imf`. On devnet `SOL-USDC-PERP` has `imf = 10%` (10×) and liquidates at `mmf = 5%` (20× on equity). Because collateral counts at its `deposit_weight`, leverage measured against your *deposit* is lower: ```text max notional = net_equity / imf = deposit × deposit_weight / imf ``` There are no tiered margin rates on-chain: `imf` and `mmf` are flat fractions of notional at any size. What bounds size instead is the contract's open-interest cap and `max_position_lots`, the depth makers quote, and the `i32` lot width of a position. `AccountService.GetTradingLimits` currently returns `UNIMPLEMENTED`. Use `OrderService.PreviewOrder` for an IOC estimate against maker quotes. It excludes resting orders, and a market preview uses a fixed 0.5% price band. Simulate the transaction you intend to send over Solana RPC to check its actual order kind, bounds, and account list. `AccountService.GetActionLimits` reports limits for collateral actions, not perp order sizes. ### Account leverage cap An account can hold itself to a lower maximum leverage than the markets allow. `set_account_leverage_cap` (owner or a `TRADE` delegate, passkeys included) stores `1 / max_leverage` on the trader account; zero lifts the cap. From then on, every move gated on initial margin must also leave ```text net_equity ≥ leverage_cap × net_exposure (rounded up) ``` or it fails with `AccountLeverageExceeded`. That covers opening or growing a perp position as taker or maker (a maker's levels are trimmed to it), resting or enlarging an order, withdrawing, borrowing, ending a flash loan, backstopping another account, and repaying debt as a spot liquidator. The cap never applies to maintenance, so it can't make an account liquidatable, and an account already past a newly set cap can still reduce. ```ts import { getSetAccountLeverageCapInstruction } from "@superis-labs/flint-idl"; // Hold the account to 5× of net equity. const ix = getSetAccountLeverageCapInstruction({ authority: wallet, traderAccount, globalConfig, leverageCap: (1n << 64n) / 5n, // Q0.64 fraction; 0n removes the cap auth: { __kind: "Wallet" }, }); ``` The app doesn't set or show the cap yet. ## Worked example Parameters (devnet listings, read live values before relying on them): `SOL-USDC-PERP` `imf = 0.10`, `mmf = 0.05`, `unpl_weight = 0.9`, taker fee 0.10%; USDC `deposit_weight = 0.9`. USDC is taken at $1 and the oracle confidence band is ignored. **1. Deposit 1,000 USDC.** ```text net_equity = 1,000 × 0.9 = 900 max notional = 900 / 0.10 = 9,000 ``` **2. Buy 50 SOL-PERP at $150** (notional $7,500). ```text taker fee = 7,500 × 0.10% = 7.50 USDC balance = 1,000 − 7.50 = 992.50 net_equity = 992.50 × 0.9 = 893.25 IM = 7,500 × 0.10 = 750.00 MM = 7,500 × 0.05 = 375.00 free collateral = 893.25 − 750 = 143.25 health ratio = 893.25 / 375 = 2.38 ``` **3a. SOL falls to $140.** Unrealized PnL is −$500. ```text USDC balance + PnL = 992.50 − 500 = 492.50 net_equity = 492.50 × 0.9 = 443.25 IM / MM = 700 / 350 health ratio = 443.25 / 350 = 1.27 ``` Equity is below IM, so the account can only reduce exposure, not add it — but it is above MM, so it is not liquidatable. **3b. SOL rises to $160 instead.** Unrealized PnL is +$500. ```text weighted gain = 500 × 0.9 (unpl_weight) = 450 net_equity = (992.50 + 450) × 0.9 = 1,298.25 IM / MM = 800 / 400 ``` **4. Liquidation price.** The account is liquidatable once `0.9 × (992.50 + 50 × (P − 150)) < 0.05 × 50 × P`: ```text 893.25 + 45P − 6,750 = 2.5P → P ≈ $137.81 ``` The API's `liquidation_price` fields solve the same equation from the live account; they are indicative and do not reproduce every fixed-point rounding boundary. The [liquidation page](/overview/liquidations#worked-example) continues this example below $137.81. ## Withdrawals A withdrawal spends **idle** balance only and must leave the account at or above **initial** margin. Beyond that: * **Unsettled PnL is not cash.** A perp gain becomes withdrawable after `settle_realised_pnl` moves it into your balance, and a gain is paid only as far as the contract's settle pool holds losses already paid in. Keepers run settlement periodically; an unpaid gain stays owed to you. * **Lent balance must be redeemed first**, and a redeem is refused while the pool's utilisation is at its `throttle_redeem_utilisation`. * **Market caps apply:** a market's withdrawal flow cap, and any pause on withdrawals for that market. `AccountService.GetActionLimits` returns the largest amount of each spot action the account can take right now and which constraint binds (`LIMIT_CONSTRAINT_INITIAL_MARGIN`, `…_FLOW_CAP`, and so on). ## Borrowing Borrowing mints debt in a spot market and credits the tokens to your idle balance. It needs initial margin after the borrow and is refused at the market's `throttle_borrow_utilisation`. Debt accrues interest on the market's utilisation curve (see [Fees](/overview/fees#borrow-interest)). You can also end up borrowing without asking: when a realized perp loss is settled and your idle settlement-asset balance cannot cover it, the shortfall is borrowed. That debt is margined and accrues interest like any other. ## Limits per account | Limit | Where it lives | | --- | --- | | Spot positions per account | `GlobalConfig.max_spot_positions` | | Perp positions per account | `GlobalConfig.max_linear_perp_positions` | | Open interest per contract | `LinearPerp.max_open_interest_lots` | | Net size per account per contract | `LinearPerp.max_position_lots`, resting orders counted as filled | | Resting orders per account per contract | 16 | | Position size | `i32` lots (about ±2.1 billion lots) | A perp fill that would take you past the position-count limit is refused; a maker without room for a new contract is skipped rather than filled. Resting orders on a contract take a position slot even while you hold no position there. # Funding A perpetual never expires, so something has to pull its traded price toward the underlying. On Flint that is **funding**: a continuous transfer between longs and shorts, sized by how far the makers' book sits from the index. * When the book bids **above** the index, the rate is positive and **longs pay shorts**. * When the book offers **below** the index, the rate is negative and **shorts pay longs**. Funding is peer-to-peer. The protocol takes no cut of it. ## The rate The rate is computed in the contract's own units — ticks per lot per hour — from three readings taken at the moment of a funding crank. **1. Impact prices.** The program walks a probe of `impact_notional_atoms` (PnL-currency atoms) into each side of the makers' merged book and takes the average fill price: * Only levels within `max_level_divergence_bps` of the index count. * A side that runs out of depth, or whose average is not richer than the index, contributes nothing. **2. Premium.** How far each side sits on the rich side of the index: ```text premium = max(0, impact_bid − index) − max(0, index − impact_ask) ``` A two-sided book that straddles the index has a premium of zero. **3. Carry, clamp and cap.** ```text rate = premium + clamp(carry × index − premium, ±carry_clamp × index) cap = min(max_rate_per_hour, max_rate_margin_factor × (imf − mmf)) × index rate = clamp(rate, ±cap) // ticks per lot per hour ``` `carry_per_hour` is a baseline hourly rate the premium is pulled toward, by at most `carry_clamp`. The cap is the smaller of a fixed hourly ceiling and a multiple of the contract's margin band, so funding can never outrun the room between initial and maintenance margin. As a fraction, the hourly rate is `rate / index`. That is the figure `FundingService.GetFundingRates` reports: `rate` is a decimal fraction per `interval_seconds = 3600`, positive when longs pay. ## Accrual The rate is converted to ticks per lot **per second** and integrated continuously into a cumulative **funding index** on the contract (one for each side). A position owes: ```text funding = −lots × (index_now − index_at_last_settlement) ``` so a long (positive lots) pays when the index rises and a short receives the same amount. Keepers crank each contract with `sync_linear_perp_cache`, which does three things in order: 1. **Charges** the time since the last crank at the rate held over it. At most `max_hold_secs` is charged per crank; time beyond it goes uncharged, so a stalled crank never back-charges a large span. 2. **Samples** a fresh rate from the book. 3. **Holds** the next rate: * Within a UTC hour, the held rate moves toward each fresh sample as an exponential average, weighted by the time since the last sample (`rate_per_second_retention`). * At the first sample after the UTC hour changes, the period closes and the fresh sample replaces the held rate outright. A new rate only applies from the moment it is sampled; no span is ever charged at a rate measured after it. ## Parameters Each contract carries its own funding parameters, set at listing and changeable by Flint (rate-moving ones only through the 24-hour timelock). | Parameter | Listing default | Role | | --- | --- | --- | | `impact_notional_atoms` | 1,000 USDC | Probe size for the impact prices | | `max_level_divergence_bps` | 500 (5%) | How far from the index a level still counts | | `carry_per_hour` | 0 | Baseline hourly rate | | `carry_clamp` | 0 | Max pull of the carry on the rate | | `max_rate_per_hour` | 0.05% | Hard hourly ceiling | | `max_rate_margin_factor` | 1 | `k` in the `k × (imf − mmf)` ceiling | | `rate_per_second_retention` | ≈ 0.99923 | Exponential average; 15-minute half-life | | `max_hold_secs` | 300 | Longest span one crank may charge | | `funding_active` | on | Whether funding accrues at all | With the defaults, the carry is zero and the rate is the clamped premium. Turning `funding_active` off stops accrual (the index holds) without pausing trading. ## How it hits your PnL Funding never moves tokens by itself. It is booked onto your position's **funding leg** in three ways: | When | Booked through | | --- | --- | | Any fill on the position | The current index — the old size pays for all the time it was held | | `settle_funding` (permissionless; keepers run it periodically) | The index as it stood when the current UTC-hour period began | | Margin checks | Not booked, but the accrued amount through the current index always counts toward health | The funding leg then moves into your settlement-asset balance with the rest of your unsettled PnL when `settle_realised_pnl` runs (see [Collateral & margin](/overview/margin#withdrawals)). Credits and debits are whole ticks, rounded down; the sub-tick remainder is carried, so many small settlements add up to exactly what one large one would. ## Worked example On `SOL-USDC-PERP` (1 lot = 0.001 SOL, 1 tick = 1 USDC atom), the index is $150, which is 150,000 ticks per lot. A $1,000 probe walked into the bids fills at an average of 150,030 ticks; the asks are no richer than the index. ```text premium = 150,030 − 150,000 = 30 ticks/lot/hour hourly rate = 30 / 150,000 = 0.02% per hour cap = min(0.05%, 1 × (10% − 5%)) × 150,000 = 0.05% × 150,000 = 75 ticks/lot/hour charged = 30 ticks/lot/hour (inside the cap) ``` A long of 10 SOL (10,000 lots) held for an hour at that rate: ```text funding = −10,000 lots × 30 ticks = −300,000 ticks = −0.30 USDC ``` The matching shorts receive 0.30 USDC between them. Check: $1,500 notional × 0.02% = $0.30. ## Reading funding from the API | RPC | Returns | | --- | --- | | `FundingService.GetFundingRates` / `SubscribeFundingRates` | The held rate as an hourly fraction per contract | | `PerpPosition.accrued_funding_usd` | Funding accrued since the position's last booking | | `GetAccountHistory(include_perp_pnl=true)` | Estimated cumulative funding in perp PnL history | Funding accrues continuously, so no scheduled payment time is advertised (`next_funding_at` is unset). Funding-rate history and per-payment history RPCs are defined but not yet served. # Liquidations When an account's equity can no longer cover its maintenance margin, the program lets anyone reduce its risk. Liquidation on Flint is **permissionless, partial and on-chain**: a keeper closes just enough of the account to bring it back above maintenance, at prices the program bounds, and is paid a share of a fee for doing it. ## When an account is liquidatable ```text liquidatable ⇔ net_equity < MM ``` measured at **forced-action fair prices** — the oracle's unbanded fair price, not the conservative risk-on prices used to open positions. `net_equity` and `MM` are the same totals described in [Collateral & margin](/overview/margin): weighted collateral, debt, weighted perp PnL (including accrued funding and unsettled fees), and the maintenance margin of every borrow and perp position. The API flags it as `meets_maintenance_margin = false`, a positive `deficit`, or a `health_ratio` below 1. Resting limit orders reserve initial margin only, so they never make an account liquidatable. Before that, once an account falls below **initial** margin, anyone may call `force_cancel_perp_orders` to take its margin-reserving orders off one contract's book; its reduce-only orders stay. See [Orders](/traders/orders#resting). ## Who liquidates Anyone. There is no privileged liquidator: a keeper with its own trader account calls the instruction, and the program checks every condition itself. Flint runs keepers that watch account health and liquidate; any third party can do the same. In a perp close the liquidator takes on no position — it is paid in fee ticks only. ## Closing perp positions `liquidate_perp` closes part of one position on one contract. It first cancels every order the liquidatee rests on that contract, reduce-only ones included. The **liquidatee is then the taker**: the close crosses the makers' quotes and resting orders exactly like an order, and the makers take the other side. The liquidator must pass every enabled maker quoting slot except the liquidatee's, so a close can't skip live quotes; owners of resting orders are optional. **Sizing.** The program sizes the close itself, level by level, until the account's maintenance deficit plus a **1% buffer** is covered, so the account lands just above maintenance rather than on the line. The close is then bounded per call by the contract's: | Term | Listing default | Effect | | --- | --- | --- | | `min_liquidation_notional_usd` | $1,000 | A smaller close is lifted to it; a smaller position is closed whole | | `max_liquidation_notional_usd` | $100,000 | Large positions unwind in tranches across several calls | | `max_liquidation_band` | 0.1% | Every fill must land within `mark × (1 ± band)` | The band is the liquidatee's protection against a bad choice of makers: a close that cannot fill inside it does not fill. **Charges.** The liquidatee pays the normal taker fee on the close, plus a `liquidation_fee` on the closed notional: | Charge | Listing default | Paid to | | --- | --- | --- | | Taker fee | Contract's `taker_fee` | Maker rebate share to the makers, the rest to the protocol | | Liquidation fee | 0.05% of notional (cap 5%) | `liquidator_share` (default 50%) to the liquidator, the rest to the protocol | The liquidation fee is **capped at the equity the account has left**, so the fee can never itself create bad debt. **Guarantees.** The instruction reverts unless the account's maintenance surplus is no worse after the close than before, and every maker filled still passes its own margin check. ## Repaying borrows Spot debt — including debt created by settling perp losses — is reduced with `liquidate_spot`, one (borrow, collateral) pair per call. The liquidator repays part of the borrow and seizes collateral worth `1 + liquidation_bonus` per dollar repaid, where the bonus is set per seized asset. The pair and size are chosen to clear the maintenance deficit plus the same 1% buffer, and the liquidator must itself stay above initial margin. ## Backstop Some accounts fall too far, too fast for small closes to help. Once ```text net_equity ≤ MM × backstop_mm_ratio (default ratio: 0.5) ``` or equity is outright negative, **any account** may call `backstop` and take over the same percentage of every spot and perp position of the distressed account at once. The takeover: * must not reduce the distressed account's maintenance surplus, * must leave the backstopper above initial margin, and * must take everything, or leave the distressed account with at least $500 of net equity. The distressed account's resting orders stay on the book. Backstopping is voluntary. It replaces a dedicated backstop fund with last-resort takers who judge the account worth absorbing. ## Bad debt If an account's debts outlive all of its collateral, what remains is bad debt. * **Spot debt** is written off by `socialize_loss` (Flint admin, only for an account with strictly negative unweighted equity and at most $1 of collateral left). The insurance fund's balance of the same asset is drawn first, up to the configured fraction per call; any remainder lowers the market's deposit share price, spreading the loss across that market's depositors. * **Perp winners** are paid from each contract's **settle pool**, which holds the losses losers have actually paid in. A gain is settled only as far as the pool reaches; the rest stays owed and is paid as further losses arrive. The insurance fund is funded by a configurable share of collected fees (see [Fees](/overview/fees)); `RiskService.GetInsuranceFund` reports its balances. :::info[Not yet live] An auto-deleveraging (ADL) stage and a perp-side loss socialization are designed but not enabled. Today the perp waterfall is liquidation, then backstop. ::: ## Worked example Continuing the [margin example](/overview/margin#worked-example): long 50 SOL-PERP from $150, USDC balance 992.50 after fees, devnet parameters (`mmf = 5%`, USDC `deposit_weight = 0.9`, taker fee 0.10%, liquidation fee 0.05%, `liquidator_share = 50%`, min close $1,000). The liquidation price was about $137.81. **SOL marks at $137.** ```text USDC balance + PnL = 992.50 − 50 × 13 = 342.50 net_equity = 342.50 × 0.9 = 308.25 MM = 0.05 × 50 × 137 = 342.50 deficit = 342.50 − 308.25 = 34.25 target (+1%) ≈ 34.59 ``` Each SOL closed at the mark releases `0.05 × 137 = 6.85` of MM and costs 0.15% in fees (`0.2055`, or `0.185` of equity after the 0.9 weight), a net gain of about 6.67 per SOL. Clearing the target needs about 5.2 SOL ($711) — below the $1,000 floor, so the close is lifted to it: **7.289 SOL**, $998.59 of notional. ```text taker fee = 0.10% × 998.59 ≈ 1.00 (0.50 to makers, 0.50 protocol) liquidation fee = 0.05% × 998.59 ≈ 0.50 (0.25 liquidator, 0.25 protocol) after the close (filled at the mark): remaining position = 42.711 SOL net_equity = (342.50 − 1.50) × 0.9 ≈ 306.90 MM = 0.05 × 42.711 × 137 ≈ 292.57 health ratio ≈ 1.05 ``` The account is back above maintenance and keeps the rest of its position. Fills worse than the mark (up to the 0.1% band) cost a little more equity per SOL and would size the close slightly larger. ## Avoiding liquidation * **Watch health, not price.** Every position, borrow and funding accrual feeds one number. `SubscribeAccount` streams it; aim to stay well above a `health_ratio` of 1. * **Hold settlement-asset cash.** Perp losses settle in USDC. Without USDC behind them they become USDC debt with its own, much larger margin requirement (see the [warning](/overview/margin#how-a-perp-position-enters-health)). * **Size with free collateral to spare.** Opening at exactly initial margin leaves only the `imf − mmf` band before liquidation. * **Reduce early.** Below initial margin you can still close: a reduce-only fill only has to clear maintenance. * **Expect to lose resting orders under stress.** Below initial margin anyone can force-cancel your margin-reserving orders, and a liquidation cancels all of them on the contract it closes. * **Mind funding.** A large adverse funding rate drains equity hour after hour, capped by the contract's maximum rate. * **Use the simulators.** `RiskService.SimulateAccountRisk` applies price shocks to your account; `PreviewOrder` shows the margin an order leaves you with. # Fees Flint charges a trading fee on every perp trade, a liquidation fee on forced closes, and a protocol share of borrow interest. Funding is not a fee — it moves between traders and the protocol takes none of it. Solana network costs sit on top. ## Trading fees Each contract has two rates, set **once at listing** and never changeable afterwards: | Rate | Meaning | Bound | | --- | --- | --- | | `taker_fee` | Fraction of each trade's notional the taker pays | At most 1% (100 bps) | | `maker_rebate_share` | Fraction of that fee credited to the maker filled | 0–100% | Makers pay no fee. The maker's rebate comes out of the taker's fee, and the protocol keeps the rest: ```text taker pays = ceil(trade_ticks × taker_fee) rounded up maker earns = floor(taker_fee_ticks × maker_rebate_share) rounded down protocol keeps = taker pays − maker earns ``` There are no volume tiers, account tiers or per-account discounts: every account pays the same rates on a given contract. **Maker or taker is per trade.** The maker is whoever's resting liquidity is filled: a market maker's quote or any trader's resting limit order. A resting-kind (`Gtc` or `Gtd`) order that crosses on arrival pays the taker fee on what crosses; the remainder it rests earns the maker rebate on whatever later fills against it. **Per trade, in ticks.** A *trade* is one crossing with one maker at one price, and the fee is computed in the contract's ticks per trade. Any non-zero rate on a non-zero trade therefore costs at least one tick, and an order that sweeps several makers or levels can pay up to one tick more per extra trade than the same size at a single price. **Margin sees the fee first.** The taker's fee is booked before the taker's margin check, so an order you cannot afford the fee on reverts whole. Fees and rebates sit on the position's fee leg until they are settled into your settlement-asset balance with the rest of your PnL. `AccountService.GetTradingFees` reports, per contract, `taker_fee_rate`, `maker_fee_rate` (always `"0"`) and `maker_rebate_rate` (`taker_fee × maker_rebate_share`). The nominal rates are exact; fills apply the per-trade rounding above. :::tip[Example] On devnet `SOL-USDC-PERP` (taker fee 0.10%, rebate share 50%), a taker buying 10 SOL at $150 from one maker pays $1.50; the maker earns $0.75 and the protocol keeps $0.75. Read the live rates for your network. ::: ## Liquidation fee A forced close of a perp position pays the normal taker fee plus a liquidation fee on the closed notional: | Field | Meaning | Bound / listing default | | --- | --- | --- | | `liquidation_fee` | Fraction of the closed notional charged to the liquidatee | At most 5%; default 0.05% | | `liquidator_share` | Fraction of that charge paid to the liquidator | Default 50% | The rest goes to the protocol. The charge is capped at the equity the account has left, so it never creates bad debt. Spot liquidations work differently: the liquidator seizes collateral at `1 + liquidation_bonus` per dollar of debt repaid, with the bonus set per collateral asset. See [Liquidations](/overview/liquidations). ## Borrow interest Borrowing a spot asset — directly, or by settling a perp loss you had no cash for — accrues interest that compounds per second. Each market's rate follows a piecewise-linear curve in pool utilisation: ```text utilisation: 0% ──────────► target ──────────► 100% borrow rate: C / 250 C / 120 C ``` `C`, the rate at full utilisation, adjusts itself over time: while utilisation stays more than one percentage point above the market's `target_utilisation`, `C` rises; below it, `C` falls. It is clamped between about 71.67% and 179.18% nominal APR (a maximum effective APY of 500%). Lenders earn that interest minus the market's `lending_rate_fee`, the protocol's share (at most 10%). `MarketInfo.borrow_apr` and `deposit_apr` report the current nominal rates. ## Where protocol fees go The protocol's share of trading and liquidation fees accrues on the contract (`accrued_protocol_fee_ticks`). A permissionless `collect_linear_perp_fees` sweeps it out of the contract's settle pool in the PnL currency, splitting it between the **insurance fund** (`GlobalConfig.if_perp_fee_shares`) and the protocol's fee destination. Borrow-lend fees are collected the same way, split by `if_borrow_lend_fee_shares`. Both shares are global config. Because the sweep is clamped to the settle pool, collecting fees moves only atoms that losing traders have actually paid in. ## Network costs Instructions execute inside Solana transactions. Several instructions can share one transaction. The following costs apply: | Cost | Who pays | | --- | --- | | Base and priority fee | The transaction's fee payer — you, or Flint's sponsor (below) | | Rent for a new trader account | The `rent_payer` of `create_trader_account` | | Rent when a fill grows a position list | The order's `rent_payer`, for the taker *and* any maker whose account grows | | Rent when a resting order grows the contract's order book | The order's `rent_payer`; not refunded when the order is cancelled | The app sets a priority-fee budget per transaction (0.00003 SOL by default, adjustable in trade settings). **Fee sponsorship.** `SponsorshipService` lets Flint's server act as fee payer for app transactions that place perp orders through a trading delegate (or revoke a passkey delegate). The client builds the transaction with the sponsor's key as fee payer and its signature slot empty; the server signs and submits it. Sponsored transactions are rate-limited and must stay under a priority-fee cap, and only those instructions qualify. See [Wallets & sessions](/traders/accounts). # 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: | Language | Library | | --- | --- | | Rust | `rust_decimal::Decimal` | | TypeScript | `bignumber.js` or `decimal.js` | | Field | Example | Meaning | | --- | --- | --- | | `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 | Field | Positive means | | --- | --- | | `PerpPosition.quantity` | Long | | `FundingRate.rate` | Longs pay shorts | | `PerpPosition.accrued_funding_usd`, `FundingPayment.amount_usd` | A credit to the account | | `PerpPnlHistoryPoint.fees_usd` | Net rebates (fees are negative) | | `AccountHealth.deficit` | Below maintenance, by that much | ## The contract grid Each perp fixes two scales at listing, and every on-chain size and price lives on them: ```text 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: ```text 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: | Contract | `lot_scale` | `tick_size` | Lot | Price step | $150 / $100,000 as ticks | | --- | --- | --- | --- | --- | --- | | `SOL-USDC-PERP` | 3 | 1 | 0.001 SOL | $0.001 | 150,000 | | `BTC-USDC-PERP` | 5 | 1 | 0.00001 BTC | $0.10 | 1,000,000 | | `JLP-USDC-PERP` | 2 | 1 | 0.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. :::code-group ```ts [TypeScript] 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); ``` ```rust [Rust] use rust_decimal::Decimal; use std::str::FromStr; let price_step = Decimal::from_str(&market.tick_size.unwrap().value)?; let lot_size = Decimal::from_str(&market.lot_size.unwrap().value)?; let lots = Decimal::from_str("2.5")? / lot_size; // must be a whole number let raw = Decimal::from_str("150.25")? / price_step; let limit = if is_buy { raw.floor() } else { raw.ceil() }; ``` ::: Reject a size that is not a whole number of lots rather than rounding it silently. ### Grid limits | Quantity | On-chain type | Bound | | --- | --- | --- | | Position size | `i32` lots | ±2,147,483,647 lots | | Order size (`lots`) | `u32`, refused past the `i32` position width | 2,147,483,647 lots | | Mark and limit price | `u32` ticks per lot | 1 to 4,294,967,295; zero is refused | | Entry notional | `i64` ticks | Fits 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 | Concept | Representation | Notes | | --- | --- | --- | | Token amounts | `u64` atoms | Scaled by the mint's decimals | | USD amounts | atoms, 1 USD = 10^6 | Account totals are 96-bit; per-balance values fit `i64` | | Oracle price | `U32.32` fixed point | USD atoms per token atom | | Spot share prices | `Q64.64` fixed point | Token atoms per pool share | | Margin fractions, weights | `LessThanOneX64` (`Q0.64`) | `imf`, `mmf`, `unpl_weight`, `deposit_weight`, `borrow_imf`, bands. Value = raw / 2^64, always below 1 | | Fee rates | `PercentU32` | `taker_fee`, `maker_rebate_share`, `liquidation_fee`, `liquidator_share`. Value = raw / (2^32 − 1), so 100% is representable | | Perp position size | `i32` lots | Signed | | Entry notional and unsettled legs | `i64` ticks | Realized PnL, funding and fees are separate legs | | Funding index | `i128` ticks per lot, 32 fractional bits | One per side, cumulative | | Funding rate | `i64` ticks per lot per second, 32 fractional bits | Hourly fraction = `rate × 3600 / 2^32 / mark_ticks` | | Carry | Signed `Q0.63` fraction | Hourly, of mark | | Account leverage cap | `u64`, `Q0.64` fraction | `1 / max leverage`; zero means no cap. Set with `setAccountLeverageCap` | | Resting order | `quantity` in `u32` lots; `expiry` a `u64` slot | One order per trader per price in the contract's `LinearPerpOrderBook` | | Order-book key | `OrderKey { 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 | Id | Width | On the wire | | --- | --- | --- | | `TraderId` | `u64`, chosen by the account's creator | `TraderId { id }` | | `LinearPerpId` | `u32`, dense from 0 | `PerpId { id }` | | `SpotId` | `u32`, dense from 0 | `SpotId { id }` | | `OracleId` | `u32` | `OracleInfo.oracle_id` | | `product_id` | catalog-wide id across spots and perps | `SpotMetadata.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](/sdks/onchain) for derivation helpers. ## Time | Where | Unit | | --- | --- | | Wire `Timestamp` | Unix **microseconds** (`micros`); zero means unset | | On-chain clocks, funding, timelocks | Unix **seconds** | | `slot` | Solana slot; quote expiry is counted in slots | | `Gtd` order `expires_at_slot` | The order is expired from this slot on; a `Gtc` order never expires. An expired order keeps reserving margin until pruned | | History windows | `start` 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 | Enum | Values | | --- | --- | | `TradeSide` | `TRADE_SIDE_BUY`, `TRADE_SIDE_SELL` | | `OrderType` | `ORDER_TYPE_LIMIT`, `ORDER_TYPE_MARKET`, … (stop and take-profit types are rejected) | | `TimeInForce` | Not read by `PreviewOrder`; on-chain the order's `PerpOrderKind` decides | | `PerpPositionStatus` | `OPEN`, `CLOSED`, `LIQUIDATED` | | `PerpMarketStatus` | Indexed markets report `ACTIVE` | | `Commitment` | `PROCESSED`, `CONFIRMED`, `FINALIZED` | | `LimitConstraint` | Which 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](/sdks/onchain#perp-orders-and-the-book). A resting order's side is a `MakerSide`, `Buy` or `Sell`. ## SDK values vs on-chain values | Layer | Takes | Example | | --- | --- | --- | | gRPC clients (`flint-margin-user-client`, `flint-margin-maker-client`) | Decimal strings in human units | `quantity: { value: "2.5" }` | | Instruction builders (`@superis-labs/flint-idl`, `flint-margin-onchain`) | Raw integers on the grid | `lots: 2500`, `kind: { __kind: "Gtc", limit: 150250, postOnly: true }` | | Account decoders | Raw fixed-point and grid integers | `entry_contract_lots`, `entry_ticks` | Convert exactly once, at the boundary where you build the instruction, with the grid from `ListPerpMarkets`. See [TypeScript](/sdks/typescript), [Rust](/sdks/rust) and [On-chain clients](/sdks/onchain). # Endpoints Use the RPC, public API and maker API URLs supplied by your operator. Set them explicitly: SDK defaults select the mainnet hosts and do not discover the network from your trader ID. For an isolated network, use [Local maker testing](/makers/local-testing) and its generated manifest. The tables below identify SDK endpoint constants and program addresses. They do not establish availability or version compatibility. The operator must verify the program, gateway and SDK combination before onboarding a maker. ## Environments **Devnet reference values** | What | Value | | --- | --- | | Public API (`flint.margin.user.v1`, plus maker `AuthService` / `MakerService` / `NodeService`) | `https://devnet.api.flint.trade` | | Authenticated API (every `flint.margin.maker.v1` service) | `https://devnet.makerapi.flint.trade` | | Flint margin program | `4MxEr7W5f9xtajcTTpTTd5wfdBmwrF72j2NMg6YzXgMo` | | Flint oracle program | `fnt2ARonSBGhBY1RTxVSvZ3p8JSYi9iAE9Hkq5jXcmf` | | Trading app | [devnet.app.flint.trade/perps](https://devnet.app.flint.trade/perps) | | Dashboard (Kellnr token) | [devnet.dash.flint.trade](https://devnet.dash.flint.trade/maker/developer) | | Solana RPC | Bring your own devnet RPC (e.g. `https://api.devnet.solana.com`). | **Mainnet SDK defaults** | What | Value | | --- | --- | | Public API | `https://mainnet.api.flint.trade` (SDK default) | | Authenticated API | `https://mainnet.makerapi.flint.trade` (SDK default) | | Flint margin program | Obtain the tested address and SDK combination from the operator. | Collateral assets, mints, and perp contracts are discovered at runtime — `MarketService.ListMarkets` returns each collateral market's mint and decimals, and `PerpMarketService.ListPerpMarkets` returns every perp contract. Read them before selecting a collateral mint or perp ID. ## Public and authenticated hosts Margin uses the spot API's two listeners. Each host can have a different ingress policy: | Host | Margin services | Auth | | --- | --- | --- | | `api` (public) | All eight `flint.margin.user.v1` services; maker `AuthService`, `MakerService`, and `NodeService` | None for user services and `NodeService`; bearer session for `MakerService`. | | `makerapi` (authenticated) | Maker `AuthService`, `MakerService`, `NodeService`, `TxService`, `SessionService`, `SpreadProtectService`, `EscalationService` | Bearer session for all except `AuthService` and `NodeService`. | `TxService`, `SessionService`, `SpreadProtectService`, and `EscalationService` exist only on the authenticated host. Makers should point their whole maker client — `AuthService` included — at the authenticated host, so the session and the services that use it share one connection. ## Node discovery Each hostname can resolve to several Flint nodes. The Rust maker client picks one for you: * It resolves the host to every address and probes each with `NodeService.GetNodeInfo` — connect time plus the median of three calls — skipping nodes that report themselves degraded or halted. * It pins the channel to the fastest healthy node, keeping the hostname for TLS, and re-probes every 10 s. It moves at once when the current node fails its probe, and for latency only when another node is both 10% and 5 ms faster on two probes in a row. * The maker and public channels are discovered independently. `discovered_endpoint()` and `discovered_public_endpoint()` report the pick; `reprobe_endpoints()` probes now. * `.endpoint_discovery(false)` on `Client::builder()` turns it off. IP literals and channels you supply connect directly. `NodeService.ListNodes` returns the published endpoints for a cluster; a listed endpoint is not a promise that it is healthy. The Rust user client connects to the endpoint you give it. The TypeScript transport can race a list of hosts you pass it — see [TypeScript](/sdks/typescript#racing-several-endpoints). ## Choosing the endpoint in an SDK Every SDK exports `MAINNET_*`, `DEVNET_*`, and `DEFAULT_*` endpoint constants (`DEFAULT_*` is mainnet): | Package | Constants | | --- | --- | | `flint-margin-user-client` / `@superis-labs/flint-margin-user-client` | `MAINNET_PUBLIC_ENDPOINT`, `DEVNET_PUBLIC_ENDPOINT`, `DEFAULT_PUBLIC_ENDPOINT` | | `flint-margin-maker-client` / `@superis-labs/flint-margin-maker-client` | `MAINNET_MAKER_ENDPOINT`, `DEVNET_MAKER_ENDPOINT`, `DEFAULT_MAKER_ENDPOINT` | :::code-group ```rust [Rust] use std::sync::Arc; use flint_margin_maker_client::DEVNET_MAKER_ENDPOINT; use flint_margin_user_client::DEVNET_PUBLIC_ENDPOINT; // Public user API. let user = flint_margin_user_client::Client::connect(DEVNET_PUBLIC_ENDPOINT).await?; // Maker API — authenticated host, so TxService is available. let maker = flint_margin_maker_client::Client::connect( DEVNET_MAKER_ENDPOINT, Arc::new(keypair), ) .await?; ``` ```ts [TypeScript] import { DEVNET_PUBLIC_ENDPOINT, createPerpMarketClient, } from "@superis-labs/flint-margin-user-client"; import { AuthFlow, DEVNET_MAKER_ENDPOINT, createMakerClient, } from "@superis-labs/flint-margin-maker-client"; const perps = createPerpMarketClient({ baseUrl: DEVNET_PUBLIC_ENDPOINT }); const auth = new AuthFlow({ baseUrl: DEVNET_MAKER_ENDPOINT, signer }); const maker = createMakerClient({ auth }); ``` ::: The program address is baked into the on-chain clients as `FLINT_ID` (Rust) and `FLINT_PROGRAM_ADDRESS` (TypeScript). See [On-chain clients](/sdks/onchain#program-ids). # Errors Failures come from two places: * **The API** returns a standard gRPC status — a code plus a human-readable message. The SDKs surface it verbatim. * **The margin program** rejects a transaction with a custom program error code (`custom program error: 0x…`). You see these from Solana RPC, from `SponsorshipService.SponsorTransaction`, or in `TxService.SubscribeTxStatus` failure events. Generated unary service calls need an explicit retry policy. Managed SDK components retry selected failures: authentication can refresh a session, streams can reconnect, and `QuotingCore` can resend the same signed transaction. See [Rust SDK](/sdks/rust#quoting). Do not build a replacement transaction merely because a submission timed out. ## gRPC status codes | gRPC code | When | Retry? | | --- | --- | --- | | `INVALID_ARGUMENT` | A required id is missing (`trader_id is required`, `perp_id is required`), a list is too long (at most 100 ids, book `depth` ≤ 100), a bad page token or time range, an unsupported candle interval, a malformed or under-signed transaction. | No — fix the request. | | `NOT_FOUND` | Unknown trader account, perp, or collateral market. | No. | | `UNAUTHENTICATED` | Missing, malformed, expired, or revoked session token. On `Authenticate`: no outstanding nonce, a bad signature, or a key that controls no trader account. | Re-run the auth handshake once, then retry. | | `PERMISSION_DENIED` | Valid session but wrong scope: the requested `trader_id` isn't controlled by the key, the session key isn't a required signer of a `SubmitTx` transaction, an organization without perps access, a trader with no organization calling `GetSdkCredentials` or `PageTheTeam`, or a `RenameAccount` signer that doesn't control the account. | No. | | `RESOURCE_EXHAUSTED` | Rate limit hit, the server is at capacity, a simulation pool is busy, the transaction queue is full, or the sponsor refused for capacity. Also ends `SubscribeAccountsAtRisk` when the client falls behind. | Yes, with exponential backoff. | | `FAILED_PRECONDITION` | A user or maker history RPC (`GetActivity`, `GetAccountHistory`, `GetInterestHistory`, `GetFills`, `ListRecentQuotes`, …) on a deployment without the history archive, or a risk view whose backing feature isn't enabled. | No — the feature isn't available on this deployment. | | `UNAVAILABLE` | Transport drop; the node is restarting; margin state hasn't loaded or is stale; sponsorship or simulation isn't configured; an upstream read failed. | Yes, with backoff. | | `DATA_LOSS` | A live stream (`SubscribeActivity`, `SubscribeFills`, `SubscribeCandles`) fell behind the server's buffer and was closed. | Reconnect, then reconcile with the matching unary RPC. | | `DEADLINE_EXCEEDED` | Your deadline, or the server's own request deadline, elapsed. | Yes, for idempotent reads. | | `UNIMPLEMENTED` | The RPC is declared in the schema but not served yet (see the [API overview](/api)), or an unsupported option such as mark/index candles. | No. | | `INTERNAL` | A server bug. | Capped retry (1–3 attempts), then report it. | The message string is for humans and may change; branch on the code. ### Session-specific failures Margin maker sessions last 6 hours and challenge nonces 30 seconds. `AuthFlow` in both SDKs refreshes ahead of expiry and runs `Challenge → sign → Authenticate` from scratch, so nonce expiry is not your concern. Sessions are not guaranteed to survive a server restart or a move to another node, though: a previously good token can start returning `UNAUTHENTICATED`. Treat that as a cue to re-authenticate once with `auth.refresh()` — on the `AuthFlow` in either SDK. The Rust maker `Client`'s managed streams and session heartbeat already do this on `UNAUTHENTICATED`. ## Reading errors :::code-group ```rust [Rust] use tonic::Code; match account.get_account(req).await { Ok(res) => { /* ... */ } Err(status) => match status.code() { Code::ResourceExhausted | Code::Unavailable => backoff_and_retry().await, Code::NotFound => { /* no such trader account */ } _ => return Err(status.into()), }, } ``` ```ts [TypeScript] import { GrpcCode, statusCode } from "@superis-labs/flint-margin-user-client"; try { await account.getAccount({ traderId: { id: 42n } }); } catch (err) { switch (statusCode(err)) { case GrpcCode.ResourceExhausted: case GrpcCode.Unavailable: await sleep(backoff); break; case GrpcCode.NotFound: // no such trader account break; default: throw err; } } ``` ::: In Rust, generated service clients return `tonic::Status`; the SDK's own helpers (`Client::connect`, `AuthFlow`) return `flint_client_core::CoreError`, re-exported as each crate's `Error`. In TypeScript, every failure is a `GrpcError` with a numeric `code`; `asGrpcError`, `statusCode`, `statusMessage`, and `responseMetadata` narrow an unknown error safely. ## Transaction outcomes `TxService.SubmitTx` returns once the server has accepted the transaction — not when it lands. Follow it on `SubscribeTxStatus`: | Event | Meaning | | --- | --- | | `ack` | Accepted for submission. Not confirmation. | | `landed` | Observed at processed commitment with no error. | | `failed` | Reverted after landing, or timed out before landing. `reason` carries the cause, including program errors. | Save the signature before dispatch. Require RPC status at confirmed or finalized commitment with `err: null` before reporting success. If the outcome is unknown, follow [transaction recovery](/makers/example#unknown-transaction-outcome). `SponsorshipService.SponsorTransaction` waits for confirmation and returns the signature; a transaction the sponsor won't sign or that fails comes back as `INVALID_ARGUMENT` with the reason. ## Program error codes The margin program's custom errors, as numbered in the IDL (decimal / hex). The program also logs the failing source location and, for some codes, the subject (trader, asset, or perp id) — read the transaction logs for the detail the code alone can't carry. Both on-chain clients ship the enum: `FlintError` in `flint_margin_onchain::errors` (Rust, `FromPrimitive`), and `FLINT_ERROR__*` constants with `isFlintError(...)` / `getFlintErrorMessage(...)` in `@superis-labs/flint-idl`. ### General | Code | Name | Meaning | | --- | --- | --- | | 1 / `0x1` | `MathError` | Arithmetic overflow, underflow, or division by zero inside the program. Usually an extreme amount. | | 2 / `0x2` | `Generic` | A rejection whose reason is only in the program log. Read the logs. | | 8 / `0x8` | `InvalidClock` | The program couldn't read a usable clock. | | 15 / `0xF` | `OperationDisabled` | The operation is paused for this market or globally. The log names the operation. | | 23 / `0x17` | `UnsupportedMintExtension` | The mint uses a Token-2022 extension outside the allowlist. | | 62 / `0x3E` | `PositionDoesntExist` | A balance move on an asset the account holds no position in. | ### Margin and collateral | Code | Name | Meaning | | --- | --- | --- | | 3 / `0x3` | `HealthCheckFailed` | An account touched by the instruction would fail its margin requirement afterwards. For a perp order this can be the taker or one of the makers; the log names the trader id. | | 6 / `0x6` | `UtilisationThrottled` | The pool's utilisation is at or above the throttle for this borrow or redeem. | | 7 / `0x7` | `UtilisationOverflow` | The action would leave the pool with borrows ≥ deposits. | | 11 / `0xB` | `RedeemBelowMinimum` | The redeem returned less than the caller's minimum. | | 12 / `0xC` | `DepositLimitExceeded` | The market's deposit cap would be exceeded. | | 13 / `0xD` | `BorrowLimitExceeded` | The market's borrow cap would be exceeded. | | 14 / `0xE` | `FlowCapExceeded` | The market's net-flow limiter would be exceeded for this window. | | 28 / `0x1C` | `SpotPositionLimitExceeded` | The account would hold more collateral positions than allowed. | | 48 / `0x30` | `MarketBankrupt` | The market's borrow/lend pool is bankrupt (deposit price is zero). | | 63 / `0x3F` | `InsufficientIsolatedDeposit` | A withdraw or lend larger than the position's idle (unlent) balance. | | 64 / `0x40` | `InsufficientDepositToRepay` | A repay larger than the position's idle balance plus its own lent deposit. | | 78 / `0x4E` | `SpotProductNotCached` | The asset isn't in the program's product cache yet; a keeper sync must list it first. | | 87 / `0x57` | `AccountLeverageExceeded` | Initial margin clears, but the account's own leverage cap (`setAccountLeverageCap`) does not. | `GetActionLimits` and `SimulateAction` predict most of these before you sign — the limit's `constraint` names which one binds. ### Perp orders | Code | Name | Meaning | | --- | --- | --- | | 50 / `0x32` | `PerpOrderLotsExceedPositionWidth` | The order's lot count is too large for a position (it must fit in a signed 32-bit integer). | | 51 / `0x33` | `PerpOrderAverageBeyondLimit` | The average-price bound is worse than the single-fill limit: above it for a buy, or below it for a sell. | | 52 / `0x34` | `PerpOrderFilledBelowMinimum` | The order filled less than its `min_fill_lots`. | | 53 / `0x35` | `PerpOrderUnfilled` | An immediate-or-cancel order crossed nothing inside its limit. | | 54 / `0x36` | `LinearPerpPositionLimitExceeded` | The account would hold more perp positions than allowed. | | 55 / `0x37` | `LinearPerpOpenInterestCapExceeded` | The fill would push the contract past its open-interest cap. | | 56 / `0x38` | `LinearPerpMarkExpired` | The contract's cached mark is past its validity window; retry after the next cache sync. | | 57 / `0x39` | `PerpOrderNothingToReduce` | A reduce-only order on a perp the taker is flat on, or already holds in the order's direction. | | 58 / `0x3A` | `PerpOrderReducibleBelowMinimum` | A reduce-only order whose position is smaller than its minimum fill. | | 76 / `0x4C` | `PerpOrderSelfMatch` | The taker passed itself as a maker. | | 77 / `0x4D` | `PerpOrderMakersUnordered` | Maker accounts must be passed strictly ascending by trader id, without repeats. | | 79 / `0x4F` | `LinearPerpNotCached` | The contract isn't in the program's perp cache yet. | | 88 / `0x58` | `PerpOrderWouldCross` | A post-only order would cross the makers passed in. | | 89 / `0x59` | `PerpOrderNotFound` | No resting order of the trader's at that side and price. | | 90 / `0x5A` | `PerpOpenOrderLimitExceeded` | The trader is at the ceiling on resting orders on this contract. | | 91 / `0x5B` | `LinearPerpPositionSizeExceeded` | The position would pass its size cap, counting resting orders as filled. | | 92 / `0x5C` | `PerpOrderExpired` | A `Gtd` expiry, or a modified resting-order expiry, is at or before the current slot. | | 94 / `0x5E` | `PerpOrderMinimumUnreachable` | The `Ioc` kind's `min_fill_lots` exceeds the order's lots. | `OrderService.PreviewOrder` simulates an order against the current book and margin before you sign it. ### Order-book upkeep The permissionless `prunePerpOrders` and `forceCancelPerpOrders` instructions, usually run by keepers. | Code | Name | Meaning | | --- | --- | --- | | 93 / `0x5D` | `PerpNothingToPrune` | No dead order among the traders passed to `prunePerpOrders`. | | 95 / `0x5F` | `PerpForceCancelMarginMet` | `forceCancelPerpOrders` on a trader that meets initial margin. | | 96 / `0x60` | `PerpForceCancelNothingReserved` | `forceCancelPerpOrders` on a book whose orders reserve no margin. | ### PnL and funding settlement | Code | Name | Meaning | | --- | --- | --- | | 59 / `0x3B` | `NoRealisedPnlToSettle` | Nothing is pending on the position. | | 60 / `0x3C` | `PnlSettlePoolExhausted` | A gain to settle, but the pool holds less than one tick. | | 61 / `0x3D` | `LinearPerpPnlCurrencyMismatch` | The market passed isn't the contract's PnL currency. | | 75 / `0x4B` | `SettlementExceedsPendingTicks` | The settlement exceeds, or points the other way from, what's pending. | | 80 / `0x50` | `SettlementBelowMinimum` | Less is settleable than the caller's `min_settle_ticks`. | ### Liquidation, backstop, and loss socialization Mostly relevant to keepers. | Code | Name | Meaning | | --- | --- | --- | | 4 / `0x4` | `HealthyAccountCantBeLiquidated` | The account meets maintenance margin. | | 5 / `0x5` | `LiquidationDidNotDecreaseDeficit` | The liquidation wouldn't improve the account's margin deficit. | | 16 / `0x10` | `LiquidationCapLeavesDustBorrow` | The capped repay would leave a dust-sized borrow. | | 17 / `0x11` | `SelfPositionTransfer` | Liquidator and liquidatee are the same account. | | 18 / `0x12` | `AccountNotDistressed` | Loss socialization on an account that isn't distressed. | | 65 / `0x41` | `NoDebtToSocialize` | The account has no debt in that asset. | | 66 / `0x42` | `SocializedAccountHoldsCollateral` | The account still holds collateral above the dust line. | | 67 / `0x43` | `LiquidationPairMismatch` | The debt or collateral asset passed differs from the plan. | | 68 / `0x44` | `LiquidationTransferredNothing` | The liquidation would move nothing. | | 69 / `0x45` | `LiquidationExchangeUnfair` | The liquidatee would give up more value than the bonus allows. | | 70 / `0x46` | `LiquidationCollateralBelowMinimum` | Collateral received is below the liquidator's `min_collateral_received`. | | 71 / `0x47` | `LiquidationDebtAboveMaximum` | Debt taken on exceeds the liquidator's `max_borrow_transfer`. | | 72 / `0x48` | `BackstopGivesDebt` | The backstop would leave the liquidatee owing more. | | 73 / `0x49` | `BackstopAssetNotHeld` | The backstop names an asset the liquidatee holds no position in. | | 74 / `0x4A` | `BackstopExchangeUnfair` | The backstopper takes more value than it gives at fair prices. | | 82 / `0x52` | `PerpLiquidationNothingToClose` | The liquidatee has no position on the contract. | | 83 / `0x53` | `PerpLiquidationMakersIncomplete` | An enabled quoting slot wasn't passed as a remaining account. | | 84 / `0x54` | `BackstopPerpNotHeld` | The backstop names a perp the liquidatee doesn't hold. | | 85 / `0x55` | `BackstopTakeNothing` | A backstop with a zero percent. | | 86 / `0x56` | `BackstopLeftoverBelowMinimum` | A partial backstop would leave the liquidatee under $500 of equity. | ### Delegates and passkeys | Code | Name | Meaning | | --- | --- | --- | | 29 / `0x1D` | `InvalidDelegateScope` | An empty delegate grant (reserved to mean revoked). | | 30 / `0x1E` | `Secp256r1InstructionMalformed` | The secp256r1 verification instruction is malformed. | | 31 / `0x1F` | `PasskeyNotTopLevel` | A passkey-authorized instruction must be top level, not a CPI. | | 32 / `0x20` | `PasskeyPrecompileMissing` | The secp256r1 verification instruction must immediately precede the order. | | 33 / `0x21` | `PasskeyIntrospectionFailed` | The program couldn't read the instructions sysvar. | | 34 / `0x22` | `PasskeyTooManyAccounts` | The passkey intent binds more accounts than allowed. | | 35 / `0x23` | `PasskeyCredentialMismatch` | The assertion's credential isn't the delegate's. | | 36 / `0x24` | `PasskeyRpIdMismatch` | The relying-party id doesn't match the registered one. | | 37 / `0x25` | `PasskeyUserNotVerified` | The authenticator didn't report user verification. | | 38 / `0x26` | `PasskeyClientDataMismatch` | `clientDataJSON` doesn't hash to what the authenticator signed, or doesn't carry the expected type, challenge, or origin. | | 39 / `0x27` | `PasskeyIntentExpired` | The signed intent's validity window has passed. | | 40 / `0x28` | `PasskeyIntentLifetimeTooLong` | The signed intent's validity window is too long. | | 41 / `0x29` | `PasskeyNonceMismatch` | The intent's nonce isn't the delegate's next nonce. | | 42 / `0x2A` | `PasskeyNotAuthorized` | The delegate isn't a passkey, or its `TRADE` grant is revoked or expired. | | 43 / `0x2B` | `PasskeyCredentialInvalid` | The passkey credential is malformed (credential id or origin length out of bounds). | | 44 / `0x2C` | `UnknownDelegateCredential` | The delegate credential's tag names no known kind. | | 45 / `0x2D` | `PasskeyDomainInvalid` | The configured challenge domain is empty or not printable ASCII. | ### Admin, keeper, and protocol operations You won't see these from trading or quoting flows. | Code | Name | Meaning | | --- | --- | --- | | 9 / `0x9` | `FlashloanDisabled` | Flash loans are disabled. | | 10 / `0xA` | `IllegalFlashloan` | The flash-loan start/end pair is malformed. | | 19 / `0x13` | `InvalidFeeDestination` | Fee collection to an account other than the configured destination. | | 20 / `0x14` | `NothingToCollect` | No fees to collect. | | 21 / `0x15` | `UpdateNotYetEffective` | A proposed market update applied before its delay elapsed. | | 22 / `0x16` | `UpdateExpired` | A proposed market update applied after it expired. | | 24 / `0x18` | `VaultAlreadyInitialized` | The market vault already exists. | | 25 / `0x19` | `SharePoolNotEmpty` | A share pool reset while it still holds shares. | | 26 / `0x1A` | `LendingRateFeeTooHigh` | The lending-rate fee is out of range. | | 27 / `0x1B` | `TargetUtilisationOutOfRange` | The target utilisation is out of range. | | 46 / `0x2E` | `OracleIngestConfiguration` | Invalid oracle ingest configuration. | | 47 / `0x2F` | `OracleTierConfiguration` | Invalid oracle tier configuration. | | 49 / `0x31` | `OracleRevisionAlreadyPublished` | That oracle revision was already published. | | 81 / `0x51` | `FeeCollectionBelowMinimum` | Less pool-backed fee accrual than the caller's `min_collect_ticks`. | :::info Codes are assigned by declaration order in the program, so this table tracks a specific program build. The on-chain clients carry the same list as the IDL they were generated from; if you see a code outside 1–96, your client is older than the deployed program. ::: # For LLMs (+ MCP) Use these files to give a coding assistant the Flint perps guide and API schema. For a single page, the **Copy as Markdown** button above the title on every doc page copies the Markdown source to your clipboard. Paste it into the assistant's context. ## Documentation files | File | Use it for | Link | | ----------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | | [`llms.txt`](/llms.txt) | A short manifest with absolute links to every page and schema file, following the [llmstxt.org](https://llmstxt.org) convention. Give this URL to an assistant that can fetch the linked pages. | `/llms.txt` | | [`llms-full.txt`](/llms-full.txt) | Authored guides and API references combined in one Markdown file. Load it into the assistant's context when it cannot fetch individual pages. | `/llms-full.txt` | | [`margin-user.proto`](/margin-user.proto) | The bundled protobuf schema for the public `flint.margin.user.v1` API. | `/margin-user.proto` | | [`margin-maker.proto`](/margin-maker.proto) | The bundled protobuf schema for the authenticated `flint.margin.maker.v1` API. | `/margin-maker.proto` | | [`margin-notifications.proto`](/margin-notifications.proto) | The bundled protobuf schema for the owner-authenticated notification API. | `/margin-notifications.proto` | | [`margin-user.openapi.yaml`](/margin-user.openapi.yaml) | OpenAPI 3.1 for the public user API's gRPC-Web URLs. | `/margin-user.openapi.yaml` | | [`codama.json`](/codama.json) | Codama IDL for the on-chain Flint margin program — instructions, accounts, PDAs, errors. | `/codama.json` | ## MCP server [`flint-server-mcp`](https://github.com/superis-labs/flint-server/blob/master/docs/mcp.md) exposes Flint API reads as [MCP](https://modelcontextprotocol.io) tools. An assistant can inspect perps markets, collateral, account state, quotes and fills. ### Connect Add the endpoint for your Flint network as a **Streamable HTTP** server in your MCP client. Use the same network as your dashboard and bot: | Network | Public reads | Maker reads | | --- | --- | --- | | Devnet | `https://devnet.mcp.flint.trade/mcp` | `https://devnet.mcp.flint.trade/mcp/maker` | | Mainnet | `https://mainnet.mcp.flint.trade/mcp` | `https://mainnet.mcp.flint.trade/mcp/maker` | Public reads need no sign-in. For maker reads, complete the browser OAuth flow with your Flint email code. Your organization needs a perps maker ID. The [client setup guide](https://docs.flintlabs.dev/llms#mcp-server) covers the dashboard's **AI Connector** page and supported MCP clients. ### Read perps state Useful tools include: * `margin_perp_market_list_perp_markets`: list perps markets. * `margin_account_get_account`: read collateral and positions. * `margin_maker_get_quoting_state`: inspect quoting slots and levels. * `margin_maker_get_fills`: read maker fills when history is available. Arguments use protobuf field names. Pass 64-bit IDs as decimal strings and use the schema returned by your client's tool list. The availability limits in the [API overview](/api) also apply to MCP reads. MCP tools do not sign or submit transactions, stream updates or return SDK access tokens. Use the dashboard and SDK for account changes and quoting. Maker-spec and transfer previews apply to spot. ### Local testing The [local maker launcher](/makers/local-testing) does not start MCP. Run `flint-server-mcp` separately from the same server checkout; see the [gateway guide](https://github.com/superis-labs/flint-server/blob/master/docs/mcp.md). Set `FLINT_GRPC_URL` to the manifest's `public_endpoint` (`http://127.0.0.1:15061` by default). Add the gateway's `/mcp` URL to your MCP client. ## Tips for coding agents Give the assistant these ground rules along with the docs: * Use the operator's explicit RPC and gateway URLs. SDK defaults select mainnet hosts. For isolated tests, use [Local maker testing](/makers/local-testing). * Every trading write — deposits, orders, quotes — is an on-chain transaction. The gRPC API reads state, simulates, and submits; it has no "place order" RPC. * API `Decimal` fields use strings in human units. Parse them with `rust_decimal` or `bignumber.js`. Raw quoting state and instruction builders use ticks, lots and atoms; see [Units](/overview/units). * Decode program accounts through `parse::FlintAccount` (Rust) or with explicit owner and discriminator checks (TypeScript), and read slab accounts only up to their `length`. * Some RPCs are declared but not served yet; the [API overview](/api) marks them. # Getting started From a fresh wallet to a closed trade on Flint perps. Flint is a cross-margin exchange: one **trading account** holds your collateral, your perp positions, and any lending or borrowing, and its health is measured across all of them. Market orders must fill in full immediately. Limit orders can fill immediately or leave size resting on the book, according to their time in force. Read [How Flint perps work](/overview/exchange) for the background; this page is the hands-on path. :::tip[Read-only?] Market data, account snapshots, fills, and the leaderboard are public and need no account or login. Skip to [Programmatic access](/traders#programmatic) if you only want to read. ::: ## What you need | You need | Why | | --- | --- | | A Solana wallet that supports Wallet Standard (Phantom, Solflare, Backpack, …) | It owns your trading account and signs deposits, withdrawals, and account changes. | | Collateral in that wallet | Any asset with a listed margin market counts; USDC is the default. See [Collateral & margin](/overview/margin). | | A little SOL | Wallet-signed transactions pay their own network fees and the rent for your account. | ## The path | # | Step | Where | | --- | --- | --- | | 1 | [Connect a wallet](/traders#step-1) | App header | | 2 | [Create a trading account](/traders#step-2) | Account panel (optional; a first deposit does it) | | 3 | [Deposit collateral](/traders#step-3) | **Deposit** dialog | | 4 | [Open a position](/traders#step-4) | Order ticket | | 5 | [Close it](/traders#step-5) | Positions tab → **Close** | | 6 | [Settle PnL and withdraw](/traders#step-6) | Settlements tab, **Withdraw** dialog | The Flint web app currently runs on **devnet** at [devnet.app.flint.trade/perps](https://devnet.app.flint.trade/perps). ::::steps ## Connect a wallet Click **Connect** in the header and pick your wallet from **Select wallet**. Every installed Wallet Standard wallet appears in the list. If none is installed, the app links to Phantom, Solflare, and Backpack. Make sure the wallet is on the same network as the app. A wallet with no SOL on that network can't pay fees, and the app says so before sending. ## Create a trading account A trading account is an on-chain account owned by your wallet. It's identified by a numeric **trader id** that every API call uses. With a wallet connected and no account, the app shows **No trading account** with two buttons: * **Create account** creates an empty account. * **Deposit** creates the account and funds it in the same transaction. Either is fine. You can hold several accounts per wallet. Use **New trading account** in the account menu, and switch between them in **Trading accounts**. Each account is margined on its own. :::tip[Name your account] The pencil icon next to an account opens **Rename trader \{id}**. Your wallet signs a message rather than a transaction, so renaming is free. Names are public and at most 32 characters. ::: ## Deposit collateral Open **Deposit** from the account bar or the account drawer. 1. **Deposit from**: your connected wallet, or another of your trading accounts. 2. **Asset**: any listed margin asset. Native SOL is wrapped for you, and the app keeps 0.005 SOL back for fees. 3. **Amount**: click the **Depositable** figure to use the maximum. 4. Check the **After this transfer** preview (equity, free collateral, health ratio), then press **Deposit \{asset}** and **Confirm**. There's no minimum deposit. Deposits sit as idle collateral. They count toward margin at the asset's collateral weight but earn no interest unless you lend them. See [Collateral & margin](/overview/margin). ## Open a position The order ticket is on the right of the trading screen. 1. Pick **Long** or **Short**. 2. Pick **Market** or **Limit**. Market orders use fill-or-kill (FOK): the full size must fill or nothing changes. Limit orders offer **GTC**, **GTD**, **IOC**, or **FOK**. GTC and GTD can leave unfilled size resting on the book. See [Orders](/traders/orders) for expiry, post-only, and reduce-only controls. 3. Enter a **Size** in the base asset, or toggle to **USD**. The **25% / 50% / 75% / Max** chips size against your free collateral. 4. Set **Leverage** with the slider. Leverage only sizes the order; your account is cross-margined, so there's no per-position leverage on-chain. 5. Review **Estimates**: average fill price, margin required, liquidation price, slippage, taker fee, and health before and after. 6. Press the main button, e.g. **Open long · SOL**. Your wallet signs the order, unless you've set up [1-click trading or a passkey](/traders/accounts), in which case Flint pays the fee. Turn on **Order confirmations** under Configuration → Trading if you want every ticket to need a second press. ## Close it In the account drawer's **Positions** tab, press **Close** on the row. The **Close position** dialog closes 25%, 50%, 75%, or all of the position with a reduce-only FOK order at your max slippage. The full selected size must fill or nothing changes. See [Positions & PnL](/traders/positions#closing). ## Settle PnL and withdraw Closing a position *realises* its PnL, but realised PnL isn't cash in your account until it's settled into a balance. Open the **Settlements** tab and press **Settle** (or **Settle all**). Losses are paid from your idle balance and borrowed if that runs short. Gains are paid from the contract's PnL pool. See [Positions & PnL](/traders/positions#settlement). Then open **Withdraw**, pick the asset and amount (the **Available** figure is the most you can take), and confirm. A withdrawal is allowed only if the account still meets **initial margin** afterwards, so you can't withdraw collateral that's backing open positions. :::warning[Withdrawals only take idle balance] A withdrawal debits idle (unlent) collateral. If funds are lent, redeem them first. The app won't let you withdraw more than the account holds. ::: :::: ## Programmatic access Bots and dashboards use two layers: | Layer | What it does | Package | | --- | --- | --- | | Margin user API (`flint.margin.user.v1`) | Public reads: markets, books, account health, positions, fills, PnL history, leaderboard. Order previews and fee sponsorship. | [`@superis-labs/flint-margin-user-client`](/sdks/typescript), [`flint-margin-user-client`](/sdks/rust) | | Margin program | Every state change (create account, deposit, trade, settle, withdraw) is a Solana transaction you build and sign. | [On-chain clients](/sdks/onchain), re-exported by both SDKs | The API never holds keys and has no "submit order" endpoint. You build the instruction, sign it, and send it to Solana (or, for passkey and delegate trades, to the [sponsorship service](/traders/accounts#sponsorship)). ### Read an account Every margin user service is public, so there's no auth step. Endpoints are listed on [Endpoints](/overview/endpoints). :::code-group ```rust [Rust] use flint_margin_user_client::{Client, DEVNET_PUBLIC_ENDPOINT}; use flint_margin_user_client::proto::{GetAccountRequest, TraderId}; let client = Client::connect(DEVNET_PUBLIC_ENDPOINT).await?; let snapshot = client .account() .get_account(GetAccountRequest { trader_id: Some(TraderId { id: 42 }) }) .await? .into_inner() .account; ``` ```ts [TypeScript] import { createAccountClient, DEVNET_PUBLIC_ENDPOINT } from "@superis-labs/flint-margin-user-client"; const account = createAccountClient({ baseUrl: DEVNET_PUBLIC_ENDPOINT }); const { account: snapshot } = await account.getAccount({ traderId: { id: 42n } }); console.log(snapshot?.health?.netEquity?.value); for (const p of snapshot?.perpPositions ?? []) { console.log(p.perpId?.id, p.quantity?.value, p.unrealizedPnlUsd?.value); } ``` ::: `AccountService.ListAccounts({ authority })` finds the trader ids a wallet owns or is a delegate of. ### Create a trading account The program derives a trading account's address from the trader id alone (`["trader_account", u64_le(trader_id)]`), so ids are global. Pick one nobody holds yet; the app checks ids upward from 0 until it finds a free one. ```ts import type { Address } from "@solana/kit"; import { findTraderAccountPda, getCreateTraderAccountInstruction, } from "@superis-labs/flint-margin-user-client"; const traderId = 42n; const [traderAccount] = await findTraderAccountPda({ traderId }, { programAddress }); const ix = getCreateTraderAccountInstruction( { rentPayer: wallet, owner: wallet, traderAccount, systemProgram: "11111111111111111111111111111111" as Address, traderId, }, { programAddress }, ); ``` `wallet` is a `@solana/kit` `TransactionSigner`; `createTransactionSigner` adapts a browser wallet's `signTransaction` to one. Pass the `programAddress` for your cluster. Deposits (`deposit`), withdrawals (`withdraw`), and PnL settlement (`settleRealisedPnl`) are built the same way; see [On-chain clients](/sdks/onchain). To place orders, see [Orders → Placing orders from code](/traders/orders#programmatic). :::tip[Simulate spot actions first] `AccountService.SimulateAction` runs a deposit, withdraw, lend, redeem, borrow, or repay against current state and returns the resulting account snapshot or the program error name, without signing anything. `GetActionLimits` returns the most each action allows right now. ::: # 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 | Order | In the app | On-chain | | --- | --- | --- | | Market | **Market** tab | `Fok`, with a price limit derived from the mark and **Max slippage** | | Resting limit | **Limit** tab, **GTC** or **GTD** | `Gtc` or `Gtd`: cross within your limit, then rest the remainder | | Limit IOC | **Limit** tab, **IOC** | `Ioc { bound, minFillLots }`: fill within your price bounds and drop the remainder | | Fill-or-kill | **Market** tab, or **Limit** tab with **FOK** | `Fok { bound }`: fill every requested lot within the bounds or revert | | Post-only | **Post only** on a GTC or GTD limit | `postOnly: true` on `Gtc` or `Gtd`: rest whole; refuse an immediate crossing | | Reduce-only | Ticket's **Reduce only**, or **Close position** dialog | `reduceOnly: true`; the close dialog sends `Fok` | | Good-till-slot | **Limit → GTD → Expiry slot** | `Gtd.expiresAtSlot`, an absolute future slot | | Cancel | **Positions → Open orders**, **Cancel** or **Cancel all** | `modifyPerpOrder` with zero lots, or `cancelAllPerpOrders` | | Modify size, flag or expiry | Build an instruction | `modifyPerpOrder` | | Average price bound | Build an instruction | `average` 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. :::info[Two kinds of resting liquidity] Market makers post standing quotes through a separate quoting interface; see [Quoting](/makers/quoting). Any trader can rest limit orders on the contract's order book. A taker crosses both in one book, and whoever's resting side is filled is the maker on that trade. ::: ## 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](/overview/exchange) for the full model and [Fees](/overview/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](/overview/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 | Instruction | Who | What it does | | --- | --- | --- | | `modifyPerpOrder` | Owner or `TRADE` delegate | Changes 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. | | `cancelAllPerpOrders` | Owner or `TRADE` delegate | Cancels every resting order on one contract, including reduce-only orders. It succeeds even when there are no orders. | | `prunePerpOrders` | Anyone | Removes the expired orders of the trader accounts passed. Refused if none were expired (`PerpNothingToPrune`). | | `forceCancelPerpOrders` | Anyone | Removes 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](/overview/liquidations). ## The order ticket | Control | What it does | | --- | --- | | **Long** / **Short** | Direction (keys **B** / **S**). | | **Market** / **Limit** | Order type (keys **M** / **L**). Market uses FOK. Limit offers GTC, GTD, IOC and FOK. | | **Size** | In 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% / Max** | Size against your free collateral. | | **Leverage** | 0× up to the market maximum (1 ÷ initial margin rate). Sets size and margin estimates; it is not an order instruction argument. | | **Limit price** | Limit only. Immediate fills must be at this price or better; GTC and GTD rest the remainder at it. | | **Time in force** | GTC (the Limit default), GTD, IOC or FOK. GTD also requires an **Expiry slot**. | | **Post only** | GTC and GTD limits only. Refuse the order if any quantity would cross an included maker. | | **Reduce only** | Permit fills only against an opposing position. | | **Estimates** | Order 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. | Setting | Default | Range | | --- | --- | --- | | **Max slippage** | 5% | 0–10%, presets 1% / 2.5% / 5% | | **Priority fee** | 0.00003 SOL | **Normal** / **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](/traders/positions#history)). 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](/overview/errors). | Code | Name | What happened | What to do | | --- | --- | --- | --- | | 3 | `HealthCheckFailed` | After 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. | | 50 | `PerpOrderLotsExceedPositionWidth` | `lots` is past the `i32` position width. | Trade smaller. | | 51 | `PerpOrderAverageBeyondLimit` | The 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. | | 52 | `PerpOrderFilledBelowMinimum` | Fewer than `Ioc.minFillLots` crossed, or a `Fok` order did not fill every requested lot. | Reduce size or review price bounds and the minimum fill. | | 53 | `PerpOrderUnfilled` | An `Ioc` order with a zero minimum crossed nothing within the price bounds. | Refresh quotes and review price bounds. | | 54 | `LinearPerpPositionLimitExceeded` | The fill or resting order would use more perp position slots than an account may hold. | Close a position or cancel orders in another market. | | 55 | `LinearPerpOpenInterestCapExceeded` | The market's open-interest cap would be exceeded. | Trade smaller, or reduce instead. | | 56 | `LinearPerpMarkExpired` | The market's mark is stale. | Include `syncLinearPerpCache` before the order (the app does). | | 57 | `PerpOrderNothingToReduce` | An immediate reduce-only order has no opposing position to close. | Check the current position and order direction. | | 58 | `PerpOrderReducibleBelowMinimum` | The opposing position is smaller than `Ioc.minFillLots`, or smaller than the requested `Fok` quantity. | Lower the IOC minimum or the requested quantity. | | 76 | `PerpOrderSelfMatch` | Your own account was passed as a maker. | Exclude it. | | 77 | `PerpOrderMakersUnordered` | Maker accounts were not strictly ascending by trader id. | Sort them. | | 87 | `AccountLeverageExceeded` | The account meets initial margin but fails its leverage cap. | Trade smaller, or change the cap ([Collateral & margin](/overview/margin#leverage-cap)). | | 88 | `PerpOrderWouldCross` | A `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. | | 89 | `PerpOrderNotFound` | `modifyPerpOrder` named no order of yours at that side and price. | — | | 90 | `PerpOpenOrderLimitExceeded` | You already rest 16 orders on this contract. | Cancel some. | | 91 | `LinearPerpPositionSizeExceeded` | The fill, or your resting orders counted as filled, would take your size past the contract's `max_position_lots`. | Trade smaller or cancel orders. | | 92 | `PerpOrderExpired` | A `Gtd` expiry, or a modified resting-order expiry, is at or before the current slot. | Use a future slot or remove the expiry. | | 94 | `PerpOrderMinimumUnreachable` | `Ioc.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](/traders/accounts#passkeys). 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. :::code-group ```rust [Rust] 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; ``` ```ts [TypeScript] import { createOrderClient, DEVNET_PUBLIC_ENDPOINT } from "@superis-labs/flint-margin-user-client"; import { OrderType } from "@superis-labs/flint-margin-user-client/gen/flint/margin/user/v1/order/messages_pb.js"; import { TradeSide } from "@superis-labs/flint-margin-user-client/gen/flint/margin/user/v1/perp/messages_pb.js"; const order = createOrderClient({ baseUrl: DEVNET_PUBLIC_ENDPOINT }); const { preview } = await order.previewOrder({ traderId: { id: 42n }, perpId: { id: 0 }, side: TradeSide.BUY, quantity: { value: "1.5" }, orderType: OrderType.MARKET, }); console.log(preview?.averagePrice?.value, preview?.estimatedFeeUsd?.value); ``` ::: `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. :::info[Read resting orders on-chain] `OrderService.ListOrders`, `GetOrder`, and `SubscribeOrders` currently return `UNIMPLEMENTED`, and the API does not read the order book. Use fills (`AccountService.GetFills`) as the record of what traded, and read resting orders from the contract's `LinearPerpOrderBook` account (the `orderBook` PDA) on-chain. Your position's `openBidLots`, `openAskLots`, and `openOrderCount` in the trader account summarise what you rest on each contract. ::: ### Build the instruction `placePerpOrder` takes: | Argument | Meaning | | --- | --- | | `linearPerpId` | The market. | | `direction` | `Direction.Long` buys; `Direction.Short` sells. A fill can increase, reduce, close, or reverse the existing position. | | `clientOrderId` | Your own `u64` identifier, echoed on the order's events. | | `lots` | Size budget in contract lots. Never zero. | | `reduceOnly` | Limit 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. | | `kind` | `Gtc { 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](/traders/accounts#passkeys)). | 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. ```ts 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](/sdks/onchain). 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](/overview/units). ### Manage resting orders ```ts 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](/sdks/onchain). # Positions & PnL How Flint tracks a perp position, what each number in the **Positions** tab means, and where to find your history. ## A position You hold at most one position per perp market. It's a signed size, positive for long and negative for short, inside your trading account. Every position shares the account's collateral (cross-margin), so there's no per-position margin to add or remove. To give a position more room, deposit collateral or shrink another position. An account can hold a limited number of perp positions at once (`LinearPerpPositionLimitExceeded` past that), and resting orders on a market take one of those slots even while you hold no position there. A market may also cap each account's net size (`max_position_lots`, `LinearPerpPositionSizeExceeded` past it). | Field | API (`PerpPosition`) | Meaning | | --- | --- | --- | | Size | `quantity` | Signed base units. | | Entry | `entry_price` | Average entry price of the open size. | | Mark | `mark_price` | The price PnL and margin are measured at (below). | | Value | `notional_usd` | |size × mark| in USD. | | Unrealised PnL | `unrealized_pnl_usd` | What closing at the mark would realise, before fees. | | Realised PnL | `realized_pnl_usd` | Booked by reductions and closes. | | Funding | `accrued_funding_usd` | Funding accrued on this position. Positive is a credit to you, negative a debit. | | Liq. price | `liquidation_price` | Mark at which the account would reach maintenance margin (below). | ## Entry price Entry is a running average. The program stores the position's size and the cash paid for it, and the entry price is the ratio. * **Adding** on the same side blends the fill into the average. * **Reducing** keeps the entry price and realises PnL on the closed part. * **Flipping** (selling more than a long, or buying more than a short) closes the old position in full and opens the remainder at the fill price. ## Mark price The mark is the underlying asset's **oracle price**, put on the contract's tick grid. It is not the book mid or the last trade. Unrealised PnL, margin, and liquidation all use it. The mark expires quickly, and a market with a stale mark can't trade until someone refreshes it. See [Prices, sizes, units](/overview/units). ## PnL ``` unrealised PnL = size × (mark − entry) ``` This is in the contract's PnL currency, converted to USD at that asset's oracle price. A long gains when the mark rises; a short gains when it falls. Your position's PnL has four parts, all of which end up in your cash balance once [settled](/traders/positions#settlement): | Leg | When it's booked | | --- | --- | | Realised trading PnL | On every reduce, close, or flip. | | Fees | On every fill (taker fee; makers earn a rebate). See [Fees](/overview/fees). | | Funding | Lazily: on every fill in that market and whenever anyone runs `settleFunding` for the account. Margin checks always count funding accrued so far. | | Unrealised PnL | Not booked; it's marked to market continuously. | For margin purposes, **positive** unrealised PnL counts at a haircut, while losses count in full. So an open winning position adds less buying power than its PnL figure suggests. ### Funding Funding is quoted as an hourly rate and accrues between longs and shorts. A positive rate means longs pay shorts. The **Funding** column shows the market's current per-hour rate; `FundingService.GetFundingRates` returns it from the API. How the rate is set is on [Funding](/overview/funding). ## Margin and liquidation price Each position needs margin based on its notional: * **Initial margin** = notional × the market's initial margin rate. You need this to open or add. * **Maintenance margin** = notional × the maintenance rate. Fall below this and the account can be liquidated. Both are summed across the whole account and compared with its net equity. The account snapshot's `health` carries `net_equity`, `initial_margin`, `maintenance_margin`, `free_collateral`, and `health_ratio` (net equity ÷ maintenance margin). The maximum leverage on a market is 1 ÷ its initial margin rate. See [Collateral & margin](/overview/margin). **Liquidation price** is the mark at which the account's equity would fall to its maintenance margin if **only this market moved**. Every other position and balance is held fixed. Because margin is shared, another position's PnL or a withdrawal moves it. It's unset when the account is already below maintenance, the position isn't priced, or no positive price would trigger liquidation. The app estimates it client-side across the account, shown with **≈**, or **None** when no price would liquidate the position. The ticket's **Liquidation price** row previews it before you trade. What happens at liquidation is on [Liquidations](/overview/liquidations). ## The Positions tab Columns: **Type**, **Market**, **Side**, **Size**, **Entry**, **Mark**, **Value**, **Liq. price**, **Unrealised PnL**, **Margin** (initial margin for the position), **Funding** (per-hour rate), and a **Close** button. Lend and borrow legs appear in the same table with **Redeem** / **Repay**. ## Adding and reducing Trade in the same market from the order ticket. The main button says what will happen: **Add to long**, **Reduce long**, **Close long**, or **Flip short**. Reducing realises PnL on the closed part; the entry price of the rest doesn't change. ## Closing **Close** on a position row opens **Close position**: * Choose **25%**, **50%**, **75%**, or **Max**. * Review **Closing**, **Mark**, **Exit value**, **Realised PnL (est.)**, and **Fee (est.)**. * Press **Close position** (or **Reduce \{n}%**). It sends a reduce-only FOK order at your **Max slippage** from the current mark. The selected size is rounded down to whole lots and must fill in full or nothing changes. If it cannot fill, review the available liquidity, close size, and price bound before trying again. There's no "close all" button; close each position separately. ## Settlement Realised PnL, fees, and funding stay on the position until they're **settled** into a cash balance in the contract's settlement asset. The **Settlements** tab lists **Unsettled PnL** per market, with **Settle** and **Settle all** buttons. Each sends `settleRealisedPnl`, signed by your wallet. * **A net loss** is paid from your idle balance of that asset, and borrowed if the balance runs short. * **A net gain** is paid from the contract's PnL pool, up to what the pool can pay in whole ticks. An oversized gain can settle partially; the unpaid amount stays on the position. `PnlSettlePoolExhausted` means no payable ticks remain. `SettlementBelowMinimum` means the payable amount is below the caller's `minSettleTicks`. `settleRealisedPnl` is permissionless. Anyone can settle any account, and it reverts with `NoRealisedPnlToSettle` if there's nothing to do. Settle before withdrawing profits. ## History In the app's account drawer: | Tab | Shows | | --- | --- | | **History** | Fills plus deposits, withdrawals, lending, and borrowing in one table: time, market / asset, action, amount, price, value, realised PnL, fee. | | **Performance** | **Account value**, **Account PnL**, **Health**, and **Perp PnL** charts over 24h / 7d / 30d. **Perp PnL** → **Outstanding** breaks unsettled PnL into trading, funding, and fees. | From the API (all public, keyed by trader id): | RPC | Returns | | --- | --- | | `AccountService.GetFills` | Your fills, newest first: price, quantity, side, maker flag, fee and rebate in USD. Paginated (`page_size` up to 1000). | | `AccountService.SubscribeFills` | Live fills. On `DATA_LOSS`, reconcile with `GetFills`. | | `AccountService.GetAccountHistory` | Account value (`nav_usd`), PnL, debt, and health over time. With `include_perp_pnl`, also total, realised trading, fee, and funding PnL. | | `AccountService.GetActivity` | Deposits, withdrawals, lend, redeem, borrow, repay. | | `FundingService.GetFundingRates` | Current funding rate per market. | :::warning[Not live yet] `AccountService.GetPositionHistory` and `GetTradingLimits`, and `FundingService.GetFundingPayments`, `SubscribeFundingPayments`, and `GetFundingRateHistory`, currently return `UNIMPLEMENTED`. For funding you've paid or received, use the `funding_usd` leg of `GetAccountHistory`'s perp PnL. ::: :::code-group ```rust [Rust] use flint_margin_user_client::{Client, DEVNET_PUBLIC_ENDPOINT}; use flint_margin_user_client::proto::{GetAccountHistoryRequest, GetFillsRequest, TraderId}; let client = Client::connect(DEVNET_PUBLIC_ENDPOINT).await?; let fills = client .account() .get_fills(GetFillsRequest { trader_id: Some(TraderId { id: 42 }), perp_ids: vec![], start: None, end: None, page_size: 100, page_token: String::new(), }) .await? .into_inner(); let history = client .account() .get_account_history(GetAccountHistoryRequest { trader_id: Some(TraderId { id: 42 }), start: None, end: None, resolution_seconds: 0, include_perp_pnl: true, }) .await? .into_inner(); ``` ```ts [TypeScript] import { createAccountClient, DEVNET_PUBLIC_ENDPOINT } from "@superis-labs/flint-margin-user-client"; const account = createAccountClient({ baseUrl: DEVNET_PUBLIC_ENDPOINT }); const { fills, nextPageToken } = await account.getFills({ traderId: { id: 42n }, pageSize: 100, }); const { points, perpPnl } = await account.getAccountHistory({ traderId: { id: 42n }, includePerpPnl: true, }); ``` ::: All money fields are decimal strings. Parse them with `rust_decimal` or `bignumber.js`, never floats. Unset `start` / `end` default to the last 24 hours for `GetAccountHistory`. ## Leaderboard The app's **Leaderboard** page ranks accounts by **PnL**, **PnL %**, **Volume**, **Account value**, **Open interest**, **Closest to liquidation**, **Largest liquidation**, or **Sharpe ratio**. It covers **24h**, **7d**, **30d**, or **All time**, with an optional minimum account value and the top 20, 50, or 100. The same data is public through `AccountService.GetRankedAccounts`: | Metric | Definition | | --- | --- | | `PNL` | Change in account value minus deposits and withdrawals. | | `PNL_PERCENT` | Linked hourly Modified Dietz returns, in percent. | | `VOLUME` | Perp notional traded, as taker or maker. | | `SHARPE_RATIO` | Annualised hourly Sharpe, zero risk-free rate; needs 24 hourly returns. | | `LARGEST_LIQUIDATION` | Largest single liquidation in the window. | | `ACCOUNT_VALUE`, `OPEN_INTEREST`, `CLOSEST_TO_LIQUIDATION` | Current state; the timeframe is ignored. | Windows are rolling 24 hours, 7 days, or 30 days of completed hours. Every metric sorts descending except `CLOSEST_TO_LIQUIDATION`. Results are capped at 100 and aren't paginated. `history_complete` is always `false` while the archive may have gaps. :::info[Account data is public] Anyone can read any account's positions, fills, and history by trader id, and the leaderboard lists owner and account addresses. ::: # Wallets & sessions Your wallet owns your trading account. Two optional signers let you trade without a wallet popup on every order: **1-click trading** (a key held in your browser) and **trading passkeys**. Both can only trade, and Flint pays the network fee for the trades they sign. | Signer | Can do | Expires | Who pays fees | Set up by | | --- | --- | --- | --- | --- | | **Connected wallet** (owner) | Everything | Never | You | Connecting | | **1-click trading** | Place orders and close positions | 4h, 12h, or 7d | Flint | Owner wallet, once | | **Trading passkey** | Place orders and close positions | 90 days | Flint | Owner wallet, once | Pick which one signs your trades under Configuration → Trading → **Sign trades with**: **Connected wallet**, **1-click trading**, or **Passkey \{address}**. ## Wallet Connect any Wallet Standard wallet from the header (**Connect** → **Select wallet**). The wallet that creates a trading account is its **owner**, and only the owner can: * deposit into a new asset; * withdraw, lend, or borrow, unless it has granted a delegate those rights (see [below](/traders/accounts#delegates)); * add or remove 1-click trading and passkeys. Owner-signed transactions are never sponsored; you pay fees and rent in SOL. There's no email or passkey login. The app always starts from a connected wallet. :::danger[The owner key is the account] Anyone with the owner wallet can withdraw everything. Delegates and passkeys can't widen their own rights, but the owner can grant any right. Keep the owner on a hardware wallet if you hold meaningful balances. ::: ## Delegates 1-click trading and passkeys are both built on on-chain **delegates**. A delegate is an extra key the owner authorises on one trading account, with a set of permissions and an optional expiry. The program checks both on every instruction the delegate signs. | Scope | Allows | | --- | --- | | `TRADE` | `placePerpOrder`, `modifyPerpOrder`, `cancelAllPerpOrders`, `setAccountLeverageCap` (and opening, releasing and updating a maker quoting slot, for makers) | | `WITHDRAW` | `withdraw` | | `BORROW_LEND` | `lend`, `redeemLend`, `borrow`, `repayBorrow` | | `QUOTE` | Maker fast-quoting instructions | The owner bypasses scope and expiry. Flash loans and the liquidator and backstopper roles can't be delegated at all. Delegates carry no size or notional limits; scope and expiry are the whole policy. Revoking (`revokeDelegateTraderAccount`, owner-signed) empties the delegate's scope. The account stays on-chain so its replay nonce can't be reset. From code, an owner grants an Ed25519 delegate with `createDelegateTraderAccount` (`ops`: a list of `DelegateOp`, and `expiryUnixSeconds`, where `0` means never). An empty list is rejected. `AccountService.ListDelegates` lists an account's delegates with their credential kind (`ED25519` or `PASSKEY`), scope bits, expiry, and whether they're active. `ListAccounts({ authority })` returns accounts where a key is owner *or* delegate. ## 1-click trading A signing key generated in your browser and authorised to trade for one account. **Enable it:** 1. Configuration → Trading → **1-click trading**. 2. Pick a **Session duration**: 4h, 12h, or 7d. 3. Press **Enable 1-click trading** and approve the one transaction in your wallet. The panel then shows **Ready for 1-click trades** and **Expires \{date}**. Choose it under **Sign trades with**, and orders and closes go out without a wallet popup. The ticket shows "1-click signing · Flint sponsors trade fees". Order confirmations are skipped when signing this way. **What it is:** an Ed25519 key created as non-extractable in your browser's storage (IndexedDB) and tied to this browser, wallet, trading account, and network. "The local signing key cannot be exported." On-chain it's a delegate with `TRADE` scope only, and the expiry you picked is enforced by the program. **What it can't do:** withdraw, deposit, lend or borrow, settle PnL, create accounts, or manage delegates. Those still go through your wallet. **Remove it:** **Remove delegate**, confirmed by your wallet. Only the owner can enable or remove 1-click trading. A wallet that's only a delegate of an account can't set it up. :::warning[What a leaked session can do] Anyone who can use your browser profile while the session is live can trade your account until it expires or you remove it, including trading at bad prices. They can't move funds out. Pick the shortest duration that suits you, and remove the delegate on shared machines. ::: ## Trading passkeys A passkey (Face ID, Touch ID, Windows Hello, a security key, or a synced passkey) registered as a trade-only delegate. Each order is approved with a biometric or PIN prompt instead of a wallet popup. **Add one:** 1. Configuration → Trading → **Trading passkeys**. 2. Press **Add passkey** and complete your device's passkey prompt. 3. Approve the registration transaction in your wallet. "New passkeys can trade for 90 days. Deposits, withdrawals, and passkey management require your wallet." **Remove access** revokes a passkey, signed by your wallet. A passkey is bound to the site that created it. One registered elsewhere shows "Registered for another app or site" and can't be used here. **How a passkey order is checked on-chain:** * The program only ever gives a passkey `TRADE` scope and refuses to widen it, so a passkey can never withdraw or borrow. * The device signs a challenge built from the order itself: the program, every account in the instruction (including the fee payer and each maker), and the order's arguments. Nothing about the order can change after you approve it. * Each approval carries the delegate's next **nonce** and a **valid-until slot** at most 150 slots ahead, so it can be used once and only for about a minute. Reusing one fails with `PasskeyNonceMismatch`: "This passkey was used for another trade. Review and retry your order." * User verification is required (`PasskeyUserNotVerified` without it), and the origin and relying-party id must match the ones registered. * The signature is checked by Solana's secp256r1 precompile, which must be the instruction immediately before the order. Passkey errors are codes 30–45; see [Errors](/overview/errors). :::info[Mobile device pairing] Pairing a phone's passkey to a browser account by QR code isn't in the app yet. The TypeScript SDK already ships the pairing helpers (`encodePairingRequest`, `decodePairingResponse`, `pairingCode`). If you build on them, have the user compare `pairingCode` on both screens before the owner signs. The QR exchange alone can't prove which device made the credential. ::: ### Passkey orders from code `@superis-labs/flint-margin-user-client` builds both instructions: ```ts import { buildRegisterPasskeyDelegateInstruction, passkeyRegistrationFromWebAuthn, preparePasskeyPerpOrder, } from "@superis-labs/flint-margin-user-client"; // Once, owner-signed: register the credential as a TRADE delegate. const registerIx = buildRegisterPasskeyDelegateInstruction({ owner, traderId, registration, programAddress, expiryUnixSeconds, // absolute unix seconds; 0n never expires }); // Per order: get the challenge, run navigator.credentials.get with it, // then complete into [secp256r1 verify, placePerpOrder]. const prepared = preparePasskeyPerpOrder({ /* market accounts, order, nonce, validUntilSlot, … */ }); const [verifyIx, orderIx] = prepared.complete({ authenticatorData, signature, clientDataJson }); ``` `preparePasskeyPerpOrder` builds immediate-or-cancel orders only and accepts at most two maker accounts. The two instructions must stay adjacent and top-level in the transaction. On-chain, a passkey can also authorise `modifyPerpOrder`, `cancelAllPerpOrders`, and `setAccountLeverageCap`, but the SDK has no helper for those yet. ## Transaction sponsorship Trades signed by 1-click trading or a passkey are sponsored: Flint's sponsor key pays the network fee and any rent the order needs. Your wallet needs no SOL for them. Everything signed by your wallet (account creation, deposits, withdrawals, settlement, adding or removing delegates) is wallet-paid. If sponsorship is unavailable, the app doesn't fall back to wallet payment; it shows "Transaction sponsorship is unavailable on the selected endpoint." Switch **Sign trades with** to **Connected wallet** to keep trading and pay your own fees. ### Sponsorship rules `SponsorshipService` co-signs only as **fee payer**. The server checks every transaction before signing: | Rule | Detail | | --- | --- | | Programs | Only the margin program, Compute Budget, and secp256r1. Each secp256r1 instruction must sit right before a margin instruction. | | Instructions | `placePerpOrder` or `revokePasskeyDelegate`, on an existing trading account. The transaction may also include `syncLinearPerpCache`. Modifying or cancelling orders and setting a leverage cap are not sponsored. | | Signatures | At most 3, counting the sponsor, any delegate wallet, and passkey verifications. All but the sponsor's must already be present and valid. | | Priority fee | At most 100,000 lamports. | | Simulation | Must succeed before the sponsor signs. | | Rate | Currently one sponsored transaction per 10 seconds per trading-account owner (`RESOURCE_EXHAUSTED` past that). | ### Using it from code 1. `GetSponsorshipPubkey` returns the sponsor's address (`UNAVAILABLE` if the endpoint has no sponsor). 2. Build the transaction with that address as fee payer (account 0), and leave its signature slot empty. 3. Sign with your delegate (or attach the passkey instructions). 4. Send the serialized transaction to `SponsorTransaction`. It returns the signature **after confirmation**. Policy failures return `INVALID_ARGUMENT` with the reason. :::warning[Don't blindly retry a confirmation failure] If the sponsor submitted your transaction but couldn't confirm it in time, you get `INVALID_ARGUMENT` with "could not confirm sponsored transaction \{signature}; check its status before retrying". The transaction may still have landed. Look up the signature before sending the order again. ::: ## Security summary | Threat | Owner wallet | 1-click key | Passkey | | --- | --- | --- | --- | | Withdraw funds | Yes | No | No | | Trade | Yes | Until expiry or removal | Until expiry or removal; each order needs user verification | | Grant itself more rights | — | No | No (program refuses) | | Replay a signed order | Normal Solana rules | Normal Solana rules | No: one nonce, ~150-slot window | | Stolen with the device | Depends on the wallet | Usable from that browser profile | Needs the device's biometric or PIN | Account data isn't private: anyone can read any trading account's balances, positions, and fills by trader id. # Maker onboarding Set up your perps maker account, connect a bot, and verify your first quote and fill. Use the dashboard for account setup, or the SDK example if you prefer file keys and commands. Both routes use a bot to publish quotes. The examples use **SDK 7.0.4 / IDL 0.10.2**. Use the network and compatible versions supplied by your Flint contact. To run your own test network, start with [Local maker testing](/makers/local-testing). :::::steps ## Get access Ask your Flint contact for: * A reserved **perps maker ID**, linked to your organization and login email. * Your dashboard, RPC and gateway URLs, supported collateral and a test market. * How to obtain collateral tokens. Your perps maker ID is the trader account ID used as `TRADER_ID` in the SDK. It is separate from your spot maker ID. The operator must set `perp_maker_id` on your organization before you can access the perps dashboard. If you already own a perps account on this network and program, give your Flint contact its trader ID, owner wallet address and login email. Ask them to confirm the owner and link the same ID to your organization. Skip account creation in [step 4](/makers/onboarding#step-4); check collateral and bot permissions there. A perps-only organization needs no spot maker ID. Sign in with your email code and select **Perps**. This login gives dashboard access; your owner wallet and bot key authorize transactions. :::tip[Optional: use an AI assistant] Use [flint-server-mcp](/llms#mcp-server) to check markets, account state, quotes and fills with an AI assistant. The MCP guide explains how to connect and sign in. ::: ## Prepare the bot Use your existing bot with the approved SDK, or follow [SDK example setup](/makers/sdk-setup) to download, configure and run the Rust or TypeScript perps maker example. That page covers tools, package access, downloads and network configuration. For Rust, get **an SDK access token** from **Perps → Developer → Get SDK credentials**. The server creates your Kellnr token on the first request and returns it on later requests. Contact Flint if this step fails. TypeScript uses separate [GitHub Packages access](/makers/sdk-setup#package-access). A Kellnr token does not grant access to those packages. ## Prepare keys and funds | Role | Purpose | Needs | | --- | --- | --- | | Owner wallet | Controls funds and bot permissions | Collateral and SOL for account rent | | Bot key (`QUOTER_KEYPAIR`) | Signs quotes | Flint-provided fee SOL | Use a fresh bot key. Keep file keys outside the repository. The [SDK key and configuration steps](/makers/sdk-setup#keys-and-configuration) show how to generate keys and set the example's variables. The owner is separate from Flint's protocol administrator. Flint sponsors fee SOL for maker wallets. The signing wallet remains the transaction fee payer. Account rent and collateral are separate from network fees. USDC is a useful first test asset when supported. For SOL collateral, open **Funds**, select **WSOL** as the asset and **Native SOL** as the wallet source. The dashboard wraps SOL during the deposit. The CLI example requires tokens in the owner's WSOL token account. ## Create and fund the account Open **Perps → On Chain**. Use the checklist to track account setup, your first quote and a maker fill. Check your perps maker ID and connected owner wallet. [![Maker onboarding checklist in the dashboard](/images/makers/onboarding-checklist.png)](/images/makers/onboarding-checklist.png) 1. Create the account if needed. 2. In **Funds**, deposit collateral from the owner wallet. 3. In **Delegates**, grant the fresh bot key the **Maker bot** role (**Trade + Quote**) and choose an expiry. [![Collateral deposit controls in the dashboard](/images/makers/collateral.png)](/images/makers/collateral.png) *For a native SOL deposit, select WSOL and the Native SOL wallet source in Funds.* Check collateral and account health before quoting. Collateral is shared across the account's markets. The owner signs the first deposit of each asset. Token deposits use the owner's associated token account. ::::details[Use SDK commands for these owner actions] From the configured example directory: :::code-group ```sh [Rust] cargo run --locked -- setup cargo run --locked -- deposit ``` ```sh [TypeScript] npm run start -- setup npm run start -- deposit ``` ::: `setup` checks ownership, creates a missing trader and grants Trade + Quote with **no expiry**. See [Delegate expiry](/makers/sdk-setup#delegate-expiry) to change that default. Do not run `setup` for a key already granted in the dashboard. Choose one route for each owner action. :::: ## Open a market slot After the account exists, ask your Flint contact to check **MAY_QUOTE** and grant it if missing. This account permission is separate from the bot's Trade + Quote grant. Run `state` to check the selected market. Reuse an entry returned for your `PERP_ID`; if none is returned, open a slot in the dashboard or run: :::code-group ```sh [Rust] cargo run --locked -- open cargo run --locked -- state ``` ```sh [TypeScript] npm run start -- open npm run start -- state ``` ::: A new slot is disabled and empty. Check that `state` shows the selected `PERP_ID` and record its entry index. See [Slot rules](/makers/quoting#one-slot-per-perp) if opening fails. ## Publish a quote and verify a fill Run `markets` to check the selected market's grid and mark. Stop if the mark is missing or stale, or account health is unavailable. Prices use **ticks**, sizes use **lots**, and expiry uses **slots**. Use [Choose quote inputs](/makers/sdk-setup#quote-inputs) for conversion and rounding. If you select another perp, repeat step 5 before quoting it. Use a small size your collateral supports and prices near the mark. These commands publish one level per side. **For local inspection only**, `2000` slots leaves time to read the result; choose a shorter expiry and refresh interval appropriate to your bot on another network. :::code-group ```sh [Rust] cargo run --locked -- markets cargo run --locked -- quote order-list 2000 cargo run --locked -- state ``` ```sh [TypeScript] npm run start -- markets npm run start -- quote order-list 2000 npm run start -- state ``` ::: Keep one writer per trader and market. This example publishes once; it does not run a quote-refresh loop. Use [Quoting](/makers/quoting) when building your bot. Save the printed transaction signature. Require **confirmed** or **finalized** RPC status with `err: null`, then check both sides in `state` before expiry. After a timeout, [reconcile the signature](/makers/example#unknown-transaction-outcome) before rebuilding. The `fills` command requires the server's historical store. Check a fill: 1. Have an independently funded taker cross a small quote. Save the taker's confirmed transaction signature. 2. Run `fills` and find that signature. 3. Run `account` to check the maker's position. 4. Check the taker's position with its own client. A visible quote alone does not prove it can fill. The first test is complete when the dashboard's **seven checks** are complete, or the SDK shows the same evidence: * Account, collateral and an active Trade + Quote delegate. * MAY_QUOTE permission and an open slot. * A confirmed quote, a confirmed maker fill and the resulting position. [![Completed maker onboarding checks](/images/makers/onboarding-complete.png)](/images/makers/onboarding-complete.png) *The seven checks confirm account setup, a quote and a maker fill.* ## Finish the test Stop the writer, disable each slot and verify its state. Cancel resting orders and close filled positions separately: disabling a quote does neither. Follow [Shutdown](/makers/example#shutdown) for settlement, withdrawal and revocation. Before continuous quoting, review [Risk and inventory](/makers/risk) and [Key rotation](/makers/example#key-rotation). For errors, use [Commands and recovery](/makers/example). ::::: # SDK example setup Choose Rust or TypeScript to run the perps maker example. Follow the installation steps below, then [download and build](/makers/sdk-setup#download-and-build) the example, configure your keys and network, and return to [Create and fund the account](/makers/onboarding#step-4). The examples use **SDK 7.0.4 / IDL 0.10.2**. Your Flint contact must confirm the matching program and SDK for your network. The examples check program identity. `disable-rpc` checks through RPC instead of the gateway. ## Install Use macOS, Linux or WSL2. Run WSL2 commands inside its Linux environment. | Example | Tools | | --- | --- | | Rust | [Rust 1.89+](https://doc.rust-lang.org/book/ch01-01-installation.html), a C compiler and [Protocol Buffers (`protoc`)](https://protobuf.dev/installation/) | | TypeScript | [Node.js 22 and npm](https://nodejs.org/en/download) | | Both | [Solana CLI](https://solana.com/docs/intro/installation/dependencies#install-solana-cli) for wallet keys | For Rust, also install the build tools for your platform: * **macOS:** [Xcode command-line tools](https://developer.apple.com/documentation/xcode/installing-the-command-line-tools) and Homebrew `protobuf`. * **Ubuntu or WSL2:** `build-essential`, `pkg-config` and `protobuf-compiler`. ### Package access For Rust, open **Perps → Developer → Get SDK credentials** to get an SDK access token. The server creates a Kellnr token on first use and reuses it on later requests. Contact Flint if issuance fails. Add this section to `~/.cargo/config.toml`. Preserve existing settings and avoid duplicate sections. ```toml [registries.kellnr] credential-provider = "cargo:token" index = "sparse+https://kellnr.xantasoft.com/api/v1/crates/" ``` Then authenticate to the registry for your language: :::code-group ```sh [Rust] cargo login --registry kellnr # Paste the dashboard token at the prompt. ``` ```sh [TypeScript] npm login --scope=@superis-labs --auth-type=legacy --registry=https://npm.pkg.github.com # Use your GitHub username and an operator-approved token with read:packages. ``` ::: For TypeScript, your GitHub account needs access to the maker SDK, margin user SDK, spot SDK dependency and `flint-idl`. A Kellnr token does not grant npm access. Resolve any install 401/403 with your Flint contact before continuing. ### Download and build The canonical examples live in `flint-api-client/examples/margin-maker`: browse the [Rust source](https://github.com/superis-labs/flint-api-client/tree/a089abc0d1b45019c71e78ad56fb55d7ef5828e5/examples/margin-maker/rust) or [TypeScript source](https://github.com/superis-labs/flint-api-client/tree/a089abc0d1b45019c71e78ad56fb55d7ef5828e5/examples/margin-maker/typescript), or download an archive below. Download the ZIP for your language and extract it into a new directory named `maker`. The archive contains a `rust` or `typescript` subdirectory. | Language | Download | | ---------- | --------------------------------------------------------------- | | Rust | [Rust example ZIP](/examples/margin-maker/rust.zip) | | TypeScript | [TypeScript example ZIP](/examples/margin-maker/typescript.zip) | :::code-group ```sh [Rust] cd maker/rust cargo build --locked ``` ```sh [TypeScript] cd maker/typescript npm install npm run check ``` ::: Keep the lockfile and use the pinned versions for later runs. ## Keys and configuration The bot uses `QUOTER_KEYPAIR`. Set `OWNER_KEYPAIR` only if you use the example for owner actions such as setup, deposits or withdrawals. Dashboard owner actions use your connected wallet instead. For file-key use, set `OWNER_KEYPAIR` to an existing owner key file. If you need a new owner, create a separate file. Do not overwrite an existing wallet. Create a fresh bot key. Keep both files outside the source repository. ```sh mkdir -p "$HOME/.config/solana" # Run this owner command only when you need a new owner key. solana-keygen new --no-bip39-passphrase --outfile "$HOME/.config/solana/perps-owner.json" solana-keygen new --no-bip39-passphrase --outfile "$HOME/.config/solana/perps-quoter.json" ``` ### Configure the example Set these values in the example's shell. Use the configuration supplied by your Flint contact. The loopback URLs below work only with the running [local fixture](/makers/local-testing); replace them for another network. Skip the `OWNER_KEYPAIR` export if you use the dashboard for owner actions. Otherwise, replace its example filename with your actual owner key path. Keep `TRADER_ID` as a decimal string: it is an unsigned 64-bit ID, separate from your wallet address and `PERP_ID`. `COLLATERAL_ATOMS` uses the mint's smallest unit: 10 tokens of a six-decimal mint is `10000000` atoms. ```sh export RPC_URL=http://127.0.0.1:8899 export MAKER_ENDPOINT=http://127.0.0.1:15062 export PUBLIC_ENDPOINT=http://127.0.0.1:15061 export OWNER_KEYPAIR="$HOME/.config/solana/perps-owner.json" export QUOTER_KEYPAIR="$HOME/.config/solana/perps-quoter.json" export TRADER_ID='' export PERP_ID='' export COLLATERAL_MINT='' export COLLATERAL_ATOMS='' ``` ### Fee sponsorship and local funding Check the bot address with the `QUOTER_KEYPAIR` command. Run the `OWNER_KEYPAIR` command only for SDK owner actions: ```sh solana-keygen pubkey "$OWNER_KEYPAIR" solana-keygen pubkey "$QUOTER_KEYPAIR" ``` Flint provides fee SOL for maker wallets. The examples use the signing wallet as fee payer; they do not use the separate trader transaction-sponsorship route. Owner actions that create accounts can also require SOL for rent. The local fixture supplies synthetic SOL. If you use new keys on that fixture, use the airdrops below. If you use the dashboard for owner actions, run only the airdrop command that uses `QUOTER_KEYPAIR`. Run these commands **only on the local fixture**: ```sh solana airdrop 1 --url "$RPC_URL" --keypair "$OWNER_KEYPAIR" solana airdrop 1 --url "$RPC_URL" --keypair "$QUOTER_KEYPAIR" ``` Obtain collateral separately. A SOL airdrop does not supply USDC or wrapped SOL. ## Choose quote inputs Run `markets` to read the selected perp's grid, mark statistics and collateral metadata. Match the records by perp ID. If the mark is missing or stale, ask your Flint contact to restore the feeds before testing a fill. The API's `tick_size` is the price step per base unit. For example, if the selected market has a price step of `0.001` USDC/SOL and a lot size of `0.001` SOL, a bid of `149.900` and an ask of `150.100` are `149900` and `150100` ticks. A size of `0.010` SOL is `10` lots. These are calculation examples; select prices near your market's current mark and a size your collateral can support. Convert with decimal arithmetic. Round bid prices down and ask prices up to the grid. Require a whole number of lots. Do not copy inputs from another market. Prices and quantities in the example commands must be positive UInt32 integers, with the ask above the bid. Quote expiry is measured in slots. ## Delegate expiry The example's `setup` grants **Trade + Quote** with expiry `0` (no expiry). To use an expiring grant, change `expiry_unix_seconds` (Rust) or `expiryUnixSeconds` (TypeScript) in the source to an absolute Unix timestamp before granting a fresh key. An expired or revoked key cannot be re-granted. See [Key hygiene](/makers/risk#key-hygiene). Return to [Maker onboarding](/makers/onboarding#step-4) for setup, quoting and fill verification. [Commands and recovery](/makers/example) lists every command. # Commands and recovery Use these commands from the [SDK example directory](/makers/sdk-setup). Each `quote` command publishes once. Use one quote writer per trader and perp. :::code-group ```sh [Rust] cargo run --locked -- account ``` ```sh [TypeScript] npm run start -- account ``` ::: Replace `account` with a command from the tables below. ## Commands `markets` needs only `PUBLIC_ENDPOINT`. Every other command needs `TRADER_ID`, `PERP_ID` and `RPC_URL`, plus `PUBLIC_ENDPOINT` for the program check except `disable-rpc`. Keys are Solana JSON keypair files: * Owner commands use `OWNER_KEYPAIR`. * Bot commands use `QUOTER_KEYPAIR`. * Maker API commands also need `MAKER_ENDPOINT`. The tables list any extra inputs. ### Read state | Command | Effect | Extra inputs | Uses | | --- | --- | --- | --- | | `markets` | List perps, marks and collateral markets | None | Public API | | `account` | Read collateral, health and perp positions | None | Public API; no key required | | `state` | Read levels, sequences, slot, strategy and enabled flag | None | Bot, Maker API | | `fills` | Read the last 50 maker fills on the selected perp | Historical store required | Bot, Maker API | `state --raw` includes strategy bytes. ### Publish and disable quotes | Command | Effect | Extra inputs | Uses | | --- | --- | --- | --- | | `quote MODE BID ASK LOTS TTL` | Reset the strategy and publish both sides | [Quote arguments](/makers/example#quote-arguments) | Bot, Maker API | | `disable` | Keep quote bytes; set enabled=false | None | Bot, Maker API | | `disable-rpc` | Disable quotes without the gateway | None | Bot, RPC | Both disable commands require the bot's on-chain `Trade` permission. Follow [Stop quote writes](/makers/example#stop-quote-writes) before the final disable. ### Manage the account | Command | Effect | Extra inputs | Uses | | --- | --- | --- | --- | | `setup` | Create a missing trader; grant Trade + Quote | `QUOTER_KEYPAIR` | Owner, RPC | | `deposit` | Deposit collateral | `COLLATERAL_MINT`, `COLLATERAL_ATOMS` | Owner, RPC | | `open` | Open a slot after the MAY_QUOTE grant | None | Owner, RPC | | `release` | Release the slot for reuse | None | Owner, RPC | | `revoke` | Permanently revoke the selected bot key on this trader | `QUOTER_KEYPAIR` | Owner, RPC | | `settle-funding` | Book accrued funding | None | Owner as fee payer, RPC | | `settle-pnl` | Settle available realised PnL | `COLLATERAL_MINT` | Owner as fee payer, RPC | | `withdraw` | Withdraw free collateral | `COLLATERAL_MINT`, `WITHDRAW_ATOMS` | Owner, RPC | Deposit and withdrawal use existing owner associated token accounts. `withdraw` does not create one. ### Operator permission | Command | Effect | Extra inputs | Uses | | --- | --- | --- | --- | | `grant` | Grant MAY_QUOTE to an existing trader | Operator's `ADMIN_KEYPAIR` | Operator, RPC | The maker asks the Flint operator for this grant. ### Quote arguments * MODE: `order-list`, `density` or `relative-density`. * BID and ASK: prices in ticks, with ASK greater than BID. * LOTS: quantity in contract lots. * TTL: lifetime in slots. All four numbers must be positive UInt32 integers. Every mode expires automatically. Density modes install a one-tick band at each fair. ### Settlement mint For `settle-pnl`, use the perp's PnL currency in `COLLATERAL_MINT`, even if you deposited another asset. Restore your collateral mint before deposit or withdrawal. Settlement changes the trader account's balances; withdrawal moves funds to the owner's wallet. ## Check a complete trade Both examples require `err == null` at **confirmed** or **finalized** RPC commitment. After a timeout, follow [Unknown transaction outcome](/makers/example#unknown-transaction-outcome) before submitting a replacement. 1. Submit a small, collateral-backed quote and wait for confirmation. 2. Read `state` before expiry. Check the enabled flag, strategy and both sides. 3. Have an independently funded taker cross one side. Save its signature. 4. Run `fills` and `account`. Check the maker's direction, price, quantity, rebate and position against the taker's confirmed transaction. 5. Stop maker quotes and cancel resting orders before closing **both positions**. Follow [Shutdown](/makers/example#shutdown) to settle and test a small withdrawal. Use the [order instructions](/traders/orders) in your integration for taker and reduce-only orders. [Local maker testing](/makers/local-testing) provides an isolated network, synthetic keys and an independent taker example. ## Stop quote writes Use this procedure before shutdown, planned key rotation, sequence reset or reopening a slot: 1. Stop every writer for the affected account and perps, including submission queues and retries that can send signed transactions. 2. Submit a disable if quotes are still fillable. A pending update can re-enable the slot, so repeat the disable after the remaining steps. 3. Resolve every outstanding signed transaction. Wait for its confirmed or finalized result. If its outcome is unknown, follow [Unknown transaction outcome](/makers/example#unknown-transaction-outcome). Continue until no earlier transaction can execute later. 4. Read the current slot and account state. Then perform the final disable, cancellation, reset or reopen required by your procedure. Stopping a process leaves its stored quotes and resting orders active until they expire or you remove them. Revoking a key removes its authority; stored quotes and orders remain active under their existing rules. ## Planned key rotation For a compromised key, [revoke its on-chain authority first](/makers/risk#operating-controls). 1. Follow [Stop quote writes](/makers/example#stop-quote-writes) for the old key. Disable each slot, cancel resting orders and confirm the results. Use `disable-rpc` with the old bot key if the gateway is unavailable. 2. Set `QUOTER_KEYPAIR` to a new key with fee SOL and run `setup` with the owner. Flint supplies fee SOL. This reuses the owned trader account. 3. Read `state` and confirm a short-lived quote with the new key. 4. Set `QUOTER_KEYPAIR` to the old key and run `revoke` with the owner. 5. Restore the new key path, verify the old key cannot write, then start the writer. Revocation is permanent for that key on that trader. An API session cannot restore the on-chain grant. ## Shutdown 1. Follow [Stop quote writes](/makers/example#stop-quote-writes). Disable each slot and cancel resting orders. Confirm the transactions and read state to check the results. 2. Read `account` and close exposure with reduce-only orders if leaving the market. Removing quotes does not close positions. 3. Settle funding and PnL. Verify flat positions and enough free collateral for the intended withdrawal. A flat position can still owe fees or funding; a refused no-op settlement does not prove exposure is closed. 4. Release unused slots and revoke any bot grant with the owner. 5. Set `WITHDRAW_ATOMS` to integer token atoms and run `withdraw`, or use the dashboard. Check account collateral and the owner's token balance. WSOL withdrawals return wrapped tokens to the owner's token account. Releasing a slot returns no rent, settles no cash and leaves exposure open. ## Fix an error | Observation | Next step | | --- | --- | | Package install 401/403 | Ask the operator for registry/package access. | | No email sign-in code | Check the address and spam folder. Ask your Flint contact to check email registration and code delivery. | | No Perps after email login | Ask the operator to check `perp_maker_id`. | | Account ID taken | Check ownership; ask for another ID if someone else owns it. | | Delegate exists | Inspect the grant. Expired or revoked grants need a new key; `setup` cannot renew them. | | No MAY_QUOTE | Ask the operator to grant the existing trader account. | | No slot / maker limit | Open after permission, or wait for a released slot if all 32 are occupied. | | Sequence did not advance | Stop competing writers, read state and allocate a counter above the accepted value. | | Sequence exhausted | Stop all writers and use the [guarded reset](/makers/quoting#guarded-sequence-reset). | | Transaction outcome unknown | Follow [the procedure below](/makers/example#unknown-transaction-outcome). | | Rust chain streams not ready | Stop the bot; ask the operator to restore blockhash, slot and fee feeds. | | Stale mark/oracle or absent health | Stop quoting; ask the operator to restore prices and keeper updates. | | Nothing to settle | Check outstanding funding/PnL; a no-op may be refused. | | PnL exceeds pool liquidity | Accept a partial payout or wait for pool funding. Unsettled gains are not wallet cash. | ## Unknown transaction outcome Both examples print `prepared blockhash=` before submission; TypeScript also prints `lastValidBlockHeight`. Save those values, the command, trader/perp IDs and intended change. A submission error can occur after acceptance. 1. Stop retries that build new transactions. Keep the original signed bytes if available. 2. Query `getSignatureStatuses` on your configured RPC with `searchTransactionHistory: true`. Use `getTransaction` at confirmed or finalized commitment for instructions and logs. 3. If confirmed or finalized with `err: null`, do not repeat the action. Reconcile `state` and `account`, plus `fills` for a trade. 4. If execution failed, inspect logs and state; correct the cause before rebuilding. 5. If no status is available, check lifetime: compare TypeScript's `lastValidBlockHeight` with `getBlockHeight`; for Rust, check the saved blockhash with `isBlockhashValid` at confirmed commitment. While valid, keep observing. Transport retries must send the same signed bytes. 6. After expiry, search history again and reconcile account and slot state. Expiry stops future execution but does not rule out earlier execution. If history is unavailable or state inconsistent, keep the writer stopped and ask the operator to reconcile before rebuilding. Quote expiry is separate: an empty or expired quote does not prove its transaction failed. # Quoting Publish quotes through your bot into one slot per perp. Taker orders can fill those quotes, and maker fills earn a rebate. Use [Onboarding](/makers/onboarding) to prepare the account, bot key and first slot. The [SDK examples](/makers/example) publish once; your bot controls continuous quoting. ## Operating flow 1. Read the market grid, mark, account health and [slot sequences](/makers/quoting#sequence-numbers). Use one writer per trader and perp. 2. Choose a [strategy](/makers/quoting#strategies), prices, sizes and an expiry. Keep quote sizes within the account's [margin limits](/makers/risk#margin-at-fill-time). 3. [Write the quote](/makers/quoting#quote-update-loop), save its signature before submission and [confirm it](/makers/quoting#submitting) through RPC. 4. Check slot state and [watch fills](/makers/quoting#watching-fills). Reconcile after a disconnect before allocating new sequences or replacing quotes. 5. [Stop and reconcile pending writes](/makers/example#stop-quote-writes), then [disable quotes](/makers/quoting#pulling-quotes). Cancel resting orders and close positions separately. :::info[SDK support] In Rust, `quoting::QuotingCore` manages entry indices, sequences, signing, transaction packing and submission tracking. `QuoteBuilder` builds `update_quoting` instructions through that core. The TypeScript SDK provides generated instruction builders, re-exported from `@superis-labs/flint-idl`, signed-nonce `AuthFlow` and `TxService`. Your integration builds, signs, submits and confirms its transactions. ::: ## Quote update loop For OrderList, replace each changed side with current levels and absolute expiry slots. A fill consumes quantity until you rewrite that side. For Density or RelativeDensity, install the bands and fair, then update the fair on each refresh cycle: 1. **Install or restore depth.** Stop and reconcile prior writes before resetting the strategy. Send one transaction with `update_quoting` instructions for `Reset(Density)` followed by `DensityBands`, or `Reset(RelativeDensity)` followed by `RelativeDensityBands`, then `DensityFair` with `enable: true`. The `density` and `relative-density` modes in [onboarding step 6](/makers/onboarding#step-6) demonstrate this installation. 2. **Refresh prices.** Send `update_density_fairs` with a current fair for each perp. Set `fair.slot` to the slot of the price observation. 3. **Refresh before expiry.** With a current price source, re-stamp the fair before `slot_delta_expiry` even when the price is unchanged. If the price source fails, stop the loop and [disable the quote](/makers/quoting#pulling-quotes). Choose the refresh interval and expiry from your maximum acceptable price age. Measure the network's slot rate when converting slots to time. ::::details[SDK helpers for batched fair updates] These helpers update existing density strategies. They use partial success; read each perp's quoting state to check which updates applied. Keep each instruction within the [transaction size limit](/makers/quoting#size-and-compute-units). :::code-group ```rust [Rust] use flint_margin_maker_client::Result; use flint_margin_maker_client::onchain::accounts::LinearPerpQuoting; use flint_margin_maker_client::onchain::instructions::UpdateDensityFairsBuilder; use flint_margin_maker_client::onchain::types::{DensityFair, DensityFairUpdate}; use flint_margin_maker_client::quoting::{QuotingCore, RetryConfig, SubmitResult}; use solana_sdk::instruction::AccountMeta; use solana_sdk::pubkey::Pubkey; use solana_sdk::signer::Signer; use solana_sdk_ids::sysvar::clock; pub struct Fair { pub perp_id: u32, pub entry_index: u32, pub bid_ticks: u32, pub ask_ticks: u32, } /// Re-stamps the fair on every perp in `fairs` with one `update_density_fairs`. pub async fn restamp( core: &mut QuotingCore, delegate_account: Pubkey, fairs: &[Fair], slot: u64, ) -> Result { let mut updates = Vec::with_capacity(fairs.len()); let mut quoting = Vec::with_capacity(fairs.len()); for fair in fairs { updates.push(DensityFairUpdate { linear_perp_id: fair.perp_id, entry_index: fair.entry_index, sequence: core.next_fair_sequence(fair.perp_id)?, fair: DensityFair { slot, bid_fair: fair.bid_ticks, ask_fair: fair.ask_ticks }, }); let (account, _) = LinearPerpQuoting::find_pda(fair.perp_id); quoting.push(AccountMeta::new(account, false)); } let instruction = UpdateDensityFairsBuilder::new() .signer(core.signer().pubkey()) .authority(delegate_account) .clock(clock::ID) .allow_partial_success(true) .updates(updates) .add_remaining_accounts("ing) .instruction(); let transactions = core.build_txs(&[vec![instruction]])?; core.submit(transactions, RetryConfig::default()).await } ``` ```ts [TypeScript] import { AccountRole, address, type Address, type TransactionSigner, } from "@solana/kit"; import { findQuotingPda, getUpdateDensityFairsInstruction, withRemainingAccounts, } from "@superis-labs/flint-margin-maker-client"; const SYSVAR_CLOCK_ADDRESS = address("SysvarC1ock11111111111111111111111111111111"); export interface Fair { perpId: number; entryIndex: number; bidTicks: number; askTicks: number; } /** One instruction re-stamping the fair on every perp in `fairs`. */ export async function restamp( quoter: TransactionSigner, delegateTraderAccount: Address, fairs: Fair[], slot: bigint, nextSequence: () => bigint, ) { const quoting = await Promise.all( fairs.map(async ({ perpId }) => (await findQuotingPda({ linearPerpId: perpId }))[0]), ); const instruction = getUpdateDensityFairsInstruction({ signer: quoter, authority: delegateTraderAccount, clock: SYSVAR_CLOCK_ADDRESS, allowPartialSuccess: true, updates: fairs.map((fair) => ({ linearPerpId: fair.perpId, entryIndex: fair.entryIndex, sequence: nextSequence(), fair: { slot, bidFair: fair.bidTicks, askFair: fair.askTicks }, })), }); return withRemainingAccounts( instruction, quoting.map((address) => ({ address, role: AccountRole.WRITABLE })), ); } ``` ::: The Rust helper allocates sequences from the same core as `QuoteBuilder`. `build_txs` adds the compute budget and signs the transactions; `submit` opens status tracking and submits them. The caller must wait for the receipt and confirm each signature through RPC. A `Quote` delegate can send these batch updates. Using `QuoteBuilder` with one `density_fair` per perp sends one `update_quoting` per perp, requires `Trade` permission and sets `enable: true` on every refresh. `update_order_lists` is the batch equivalent for order lists. It carries one shared pool of `levels` plus, per perp, a `bids` and `asks` range into that pool (`offset`, `count`): a `count` of `255` preserves the side, `0` empties it. :::: ## Submitting ### Through TxService `TxService.SubmitTx` takes a **fully signed** legacy or v0 transaction and returns its signature once the server accepts it for submission. `SubscribeTxStatus` reports processed outcomes. Confirm the signature through RPC before reporting success. The server rejects a transaction when: * the authenticated quoting key is not one of its required signers; * any required signature is missing; * it invokes any program other than the Flint margin program, the Compute Budget program and the System program (or does not invoke the margin program at all). Submit and track each transaction: 1. Open `SubscribeTxStatus` **before** you submit to receive the outcome. It streams every transaction submitted under your session's trader. Match each event by signature. 2. Save the signed transaction's signature and blockhash before dispatch. Send it with `SubmitTx` and compare the returned signature with the saved one. 3. Wait for `landed` (seen at processed commitment with no error) or `failed` (reverted, or expired before landing, with a `reason`). `ack` means accepted for submission only. Before reporting success, require `err == null` at confirmed or finalized RPC commitment for the same signature. A processed landing can still roll back. 4. Reconcile failures against RPC status and current account state before rebuilding. A transport error or missing acknowledgement can leave execution unknown. Follow [transaction recovery](/makers/example#unknown-transaction-outcome), then use a fresh blockhash, fresh sequence numbers and current prices for any replacement. In Rust, `QuotingCore::submit` opens the status stream before submission. `QuoteBuilder::commit` calls it. Transient submission failures (`UNAVAILABLE`, a deadline, an expired session or a full queue) trigger retries of the same signed bytes: three attempts by default (`RetryConfig`). The signature stays unchanged. `submit` returns a `SubmitResult` after submission attempts, without waiting for execution. Inspect `failed` for rejected or unacknowledged submissions. Wait on `receipt.settled_within(timeout)` for acknowledged transactions; the receipt tracks processed outcomes only. With `QuoteBuilder::commit`, it also sorts the builder's intents into `landed`, `failed` and `unknown`. Then require confirmed or finalized RPC status with `err: null` for each signature. Treat unknown outcomes, timeouts and lost status streams as unresolved execution; check signatures and state before rebuilding. With the crate's `rpc` feature, `with_rpcs` also broadcasts each signed transaction through your own Solana RPC nodes and `with_quic` sends it directly to the upcoming leaders over QUIC. The receipt still learns outcomes only from the server's status stream. The other `TxService` streams provide chain data: | Stream | Carries | | --- | --- | | `SubscribeBlockhash` | `blockhash`, `last_valid_block_height` and `recommended_cu_price` (micro-lamports per CU; `0` means unavailable) | | `SubscribeSlots` | the current slot, for stamping fairs and expiries | | `SubscribeLeaders` | upcoming leader windows (`start_slot`, `end_slot`, TPU address) | ### Directly over RPC You can submit quote transactions directly through Solana RPC. Save each signature before dispatch and confirm it through RPC. The server's `SubscribeTxStatus` does not track direct submissions. In TypeScript, `withTxFallback(txClient, rpc)` sends `SubmitTx`, `SubscribeBlockhash` and `SubscribeSlots` requests to RPC when opening the server request fails with a supported transport error. It does not switch a stream that disconnects after delivering data. `pollTxStatus` tracks one explicit signature at processed commitment; confirm it separately through RPC. It does not replace the trader-scoped `SubscribeTxStatus` stream. ## Watching fills Fills reach you on `MakerService` (same bearer session as `TxService`): | RPC | Use | | --- | --- | | `SubscribeFills(perp_ids)` | Live fills where you were the maker. Empty `perp_ids` means all. | | `GetFills` | Paged history, newest first (`limit` up to 1,000, `cursor`). | | `SubscribeMarketSnapshots` | Each perp's best bid/ask, depth, last price, and **your** best bid and ask (`maker_bid`, `maker_ask`). | | `GetQuotingState` | Your slots as the program last saw them: `entry_index`, `enabled`, strategy, `order_sequence`, `fair_sequence`, and the decoded strategy as `payload_json`. | | `SubscribeQuotes` / `ListRecentQuotes` | Your quote writes that landed, decoded: kind, `sequence`, `payload_json`, `applied` and `enabled_after` when available. Partial-success batches do not report individual results; reconcile current quoting state. Filter by `perp_ids` and `quoters`. `GetQuote` fetches one by signature and instruction. | | `GetVolumeBreakdown` / `GetVolumeSeries` | Your maker volume by perp and window. | | `GetMarkoutSummary` | Markout totals, buckets and per-perp rows over a window. `GetFills` can also sort by markout (`MAKER_FILL_ORDER_ABS_MARKOUT_DESC`). | | `GetStats` | Rates of fills, transactions sent, landed and failed, and quote updates. | Read collateral and margin from the public `AccountService.GetAccount` / `SubscribeAccount` (in Rust, `client.account()` and `client.subscribe_account(...)`). See [Positions & PnL](/traders/positions). A `FillEvent` gives `perp_id`, your `side` (the maker's direction), `price` and `size` (human units, decimal strings), `slot`, `ts`, `signature`, `taker_id` and `maker_rebate` (in the perp's PnL currency). After a fill, reconcile positions and remaining quote depth before updating prices or sizes. A density fill consumes depth until a strategy reset; see [Fill risk](/makers/quoting#fill-risk-on-density-quotes). The Rust `Client` wraps the streams: `subscribe_maker_fills`, `subscribe_quotes` and `subscribe_maker_market_snapshots` reconnect, resend their filters and re-authenticate on their own. Fill and quote streams do not replay events missed while disconnected. After a gap (when `disconnects()` increases), reconcile fills with `GetFills` and quote writes with `ListRecentQuotes` (or `GetQuotingState` if you only need the current state). ## Stop quoting First [stop and reconcile pending writes](/makers/example#stop-quote-writes). A signed update can execute after a disable and set `enable: true` again. This step also applies before planned key rotation, a strategy reset or reopening a released slot. Disable each slot and require confirmed or finalized RPC success. Read its state to verify `enabled: false`. Cancel resting orders separately. Close existing positions with reduce-only orders if leaving the market. | Tool | Signer | Effect | Undo | | --- | --- | --- | --- | | `update_quoting { enable: false, update: Keep }` | Owner or `Trade` delegate | Slot offers no fillable quote; strategy bytes kept | `enable: true` with `Keep` restores retained levels that have not expired; check prices first | | Empty sides | Owner, `Trade` (`update_quoting`) or `Quote` (`update_order_lists` with `count: 0`) | Clears levels | Write new levels | | Stop re-stamping density fairs | None | Density bands expire `slot_delta_expiry` slots after the last fair | Publish a fresh fair; reset and reinstall if filled depth must be restored | | Stop rewriting order-list levels | None | Each level expires at its absolute `slot_expiry` | Rewrite levels with current prices, quantities, and new `slot_expiry` values | | `release_quoting_entry` | Owner, `Trade` delegate, or Flint's admin | Slot off the book and open to takeover | `open_quoting_entry` again, starting from an empty slot | | `cancel_all_perp_orders` | Owner or `Trade` delegate | Cancels your resting orders on one perp, including reduce-only orders | Place new orders | A disable removes the slot's liquidity when it executes. It uses one instruction per perp; several can fit in one transaction. In Rust, build `QuoteBuilder::new().enable(perp_id, false)` for each perp, call `commit`, then wait for the receipt and RPC confirmation. **Revoking a bot key does not remove stored quotes or resting orders.** It removes that key's delegated authority on the trader. Disable quotes and cancel orders separately, or verify that they have expired. The revoked key cannot receive another grant on this trader. Follow [key rotation](/makers/example#key-rotation) for the full procedure. **Expiry** stops quotes automatically if your process, network, or `TxService` fails. Choose the maximum price age you can accept in slots. For density bands, use that age as `slot_delta_expiry`. For order-list levels, set the absolute `slot_expiry` to the price's slot plus that age. Batched fair updates cannot change the `enabled` flag. A `Quote` delegate can stop re-stamping; a `Trade` delegate can also disable the slot. Grant `Trade` if your bot must disable its quotes. `QuoteBuilder` strategy writes set `enable: true`, so reconcile pending writes before the final disable. ## One slot per perp Each perp has one **quoting account** (PDA `["quoting", perp_id]`) holding up to 32 maker slots. Your slot holds: * your `trader_id`; * an `enabled` flag (a disabled slot offers no fillable quote, even when it contains levels or bands); * one **strategy**, a fixed 1 KiB buffer holding your levels or bands. You open your own slot with `open_quoting_entry` once Flint has granted your account `MAY_QUOTE` ([Onboarding step 5](/makers/onboarding#step-5)). It starts disabled, running an empty order list. Slots never move, so the `entry_index` from `MakerService.GetQuotingState` stays valid while you hold the slot. `release_quoting_entry` disables your slot and allows another maker to reuse it. The owner, a `Trade` delegate or Flint's admin can sign it. The slot stays where it is and stays yours (you can open it again) until another maker opening a slot on that perp takes it over. Its rent stays with the quoting account. Opening again resets it: disabled, an empty order list, sequences back at `0`. Before reopening, follow [Stop and reconcile pending writes](/makers/example#stop-quote-writes). If another maker took your old slot, you get a different one. Read `GetQuotingState` after opening. When a taker crosses, the program reads the maker slots included in the order and merges their live levels by price. Each quoted level must pass a margin check for its full quantity after the position cap is applied (see [Risk → Margin at fill time](/makers/risk#margin-at-fill-time)). ## Quotes and resting orders Each perp also has an **order book** of resting limit orders, placed with `place_perp_order` (`Gtc` or `Gtd`, with optional `post_only`; see [Orders](/traders/orders)). It needs no grant: any trader can rest orders, makers included, alongside a slot or instead of one. A taker's order crosses both in one pass: * quotes and resting orders are merged **best price first**; * levels and orders at the same price share partial fills pro rata, with no time priority; * the taker can cross only the maker accounts included in its order, for both quoting slots and resting orders; * your slot and your orders use one margin allowance per crossing, so together they cannot fill more than your account can support. The slot and order book have these rules: | | Quoting slot | Resting orders | | --- | --- | --- | | Access | Flint's `MAY_QUOTE` grant; one slot per perp | Open to anyone; up to 16 orders per trader per perp | | Writes | Sequence-ordered batch writes | One `place_perp_order` / `modify_perp_order` per order | | Margin | Checked only at fill time; nothing reserved | A non-reduce-only order reserves initial margin when placed | | A level that fails margin | None of that level or deeper quote levels is offered for that taker order | That order is skipped; later ones can still trade | | Liquidations | Every enabled slot must be included | Optional for the liquidator | | Removing liquidity | Disable, or let it expire | `cancel_all_perp_orders` per perp, or a `Gtd` order's `expires_at_slot` | Once your account is below initial margin, anyone can send `force_cancel_perp_orders` to cancel your margin-reserving orders on a perp; reduce-only orders stay. The order book is not indexed by the API yet, so watch your own orders on chain. ## Strategies A slot runs exactly one strategy at a time. Switching means a `Reset`, which discards the old one. | Strategy | You publish | Each refresh | Capacity per side | | --- | --- | --- | --- | | **OrderList** | Explicit levels: price, quantity, your `order_id`, an expiry slot | The changed side(s), whole | 21 levels | | **Density** | A fair (bid and ask) plus bands at fixed **tick** offsets from it | The fair | 30 bands | | **RelativeDensity** | A fair plus bands at a **fraction** of the fair | The fair | 30 bands | These capacities fit the 1 KiB strategy buffer. A write that exceeds capacity is refused. The transaction limit (4,096 bytes for v1, 1,232 for legacy or v0) usually binds before the strategy buffer. :::info[Spline] `QuotingStrategy` and `QuoteUpdateKind` in the `MakerService` protos list spline variants. The program has no spline strategy, so you will only see OrderList, Density and RelativeDensity. ::: ### Prices and sizes Every price in a quote is in **ticks** and every size in **lots** on the perp's grid. Convert from human units with the market's `tick_size` and `lot_size` (`PerpMarketService.ListPerpMarkets` on the public API); [Prices, sizes, units](/overview/units) has the arithmetic. Fills and balances come back from `MakerService` as decimal strings already scaled to human units; parse them with a decimal library, never a float. ### OrderList A list of levels per side, **best price first** (bids descending, asks ascending). Each `OrderListLevel` is: | Field | Meaning | | --- | --- | | `price` | Ticks. `0` ends the side; nothing after it is read. | | `quantity` | Lots available. `0` offers no quantity here but does not end the side. | | `order_id` | Your own identifier, `u64`. The program does not interpret it. | | `slot_expiry` | Absolute slot at which the level stops quoting. | The program stops at the first level whose price is **not strictly deeper** than the previous price. It ignores that level and all later levels without returning an error. Expired or empty levels are skipped without ending the side, so an expired level can precede live levels. Fills reduce `quantity` on the filled levels, best price first. A filled level stays empty until you rewrite that side. ### Density A **fair**, your chosen reference price, plus **bands** at offsets from it: * `DensityFair { slot, bid_fair, ask_fair }`: bid prices decrease from `bid_fair`; ask prices increase from `ask_fair`. Two fairs let you skew or widen without changing the bands. `slot` records when you obtained the price. * `DensityBandOffset { density, start_offset, end_offset, slot_delta_expiry }`: `density` lots on **every** tick from `start_offset` to `end_offset` ticks away from the fair (inclusive; `0` is the fair itself). The band stops quoting once the current slot is `slot_delta_expiry` or more slots past the fair's stamp. Bands are listed nearest the fair first. A zero `density`, or an end offset below its start, ends the side. Every band's expiry counts from the fair's stamp. **Re-stamp the fair to extend the bands' expiry.** If you stop publishing fairs, the quote expires automatically. See [Stop quoting](/makers/quoting#pulling-quotes). :::tip[Use the price observation slot] The program accepts your `slot` without checking it against the chain or an oracle. Use the slot for which you obtained the price. A future stamp extends expiry beyond the intended price age. ::: ### RelativeDensity The same as Density, but each band's `start_offset` and `end_offset` are fractions of the fair (`DensityBandRelative`, scaled by `2^32 − 1`). Both edges round **away** from the fair, so a band never quotes tighter than you asked. A ladder written once keeps its width in basis points as the price moves. ### Fill risk on density quotes A density strategy stores a per-side filled-lot counter. Each fill adds to that counter, and that many lots are subtracted from the **nearest ticks** of that side before anything is quoted. After a 30-lot hit on your bid, the first 30 lots of bid depth stay consumed while the bands stay as written. :::warning[Filled depth does not return automatically] The program has no instruction that sets decay or offsetting for this counter, so it never shrinks. Re-stamping fairs or rewriting bands preserves it. Only a `Reset` clears the counter. A reset also clears the strategy, so reinstall the bands and fair to restore depth. Check remaining depth after fills. Before a reset, follow [Stop and reconcile pending writes](/makers/example#stop-quote-writes). ::: ### Fair prices Every fair and level is an absolute tick price that you publish. RelativeDensity scales offsets with that fair. Perps have no oracle-pegged strategy. The market's oracle-derived mark affects quote execution: * your margin is checked against the perp's **mark** at fill time, and a fill priced worse than the mark reduces available margin; * when the perp's mark goes stale, the program refuses orders on it, so nothing fills (see [Risk → Stale prices](/makers/risk#stale-prices)). Compare your fair with the mark and set deviation limits in your bot. Poll the public `PerpMarketService.GetMarketStats` for `mark_price`, `index_price` and `oracle_price`; `SubscribeMarketStats` is not served yet. ## Sequence numbers Transactions can execute in a different order from submission. Level, band and fair writes carry sequence numbers. The program refuses a write at or below its stream's accepted value. Accepted values increase until a reset. A refused write can fail its transaction. * Each slot has an `Order` stream for levels or bands. Density strategies also have a `Fair` stream. The streams have separate counters. * A new stream starts at `0`. Its first write uses `1`. Gaps are allowed. * Read `order_sequence` and `fair_sequence` from `GetQuotingState` at startup. Allocate counters above the accepted values. Use one writer per trader and perp. A timestamp does not guarantee a higher sequence. * Rust's `QuotingCore` allocates values above those it has read or sent. `QuoteBuilder` uses the same counters. Call `refresh_quoting_state()` after opening a slot or after another process writes to it. * `QuotingUpdate::Reset` clears the strategy and starts its counters at `0`. Stop and reconcile pending writes before resetting, then reinstall the quote data before enabling the slot. * A guarded sequence reset sets only the selected counter to `0`. It keeps the strategy, levels, fairs, filled-depth counters and enabled flag. `Reset` and the enabled flag have no sequence counter. Concurrent enable and disable writes apply in execution order. Resetting a counter can also allow a pending older transaction to pass its sequence check. Follow [Stop and reconcile pending writes](/makers/example#stop-quote-writes) before a reset, reopening, shutdown or planned key rotation. ### Guarded sequence reset Use this procedure when a counter reaches `u64::MAX`, or when you need to restart a counter without clearing quote data. If the counter is only ahead of your saved value, you can read it and allocate a higher value instead. The downloadable CLI examples do not expose a sequence-reset command. Build this instruction through the SDK: 1. [Stop and reconcile pending writes](/makers/example#stop-quote-writes) for the trader and perp. No earlier signed transaction may remain able to execute after the reset. Disable and confirm the slot if its stored quote should remain unavailable during recovery. 2. Read `GetQuotingState`. Select the entry for the trader and perp. Save its exact `entry_index`, stream and accepted sequence as `expected_sequence`. An `OrderList` has only an `Order` stream. 3. Build one `reset_quoting_sequences` update, with `allow_partial_success: false`. Use the owner with the trader PDA as `authority`, or a Quote delegate with its delegate PDA as `authority`. Append the writable quoting PDA, as shown below. 4. Sign and submit through your configured RPC or `TxService`. Save the signature and require confirmed or finalized status with `err: null`. Apply [transaction recovery](/makers/example#unknown-transaction-outcome) if the outcome is unknown. 5. Read the entry again. The selected counter must be `0`; the other counter and quote data must match the saved state. If the guard failed, read the current state and investigate another writer before trying again. 6. Restart the writer and allocate `1` for the reset stream's next write. **Recreate Rust's `QuotingCore` after a confirmed reset.** Refreshing an existing core keeps its highest local counter and cannot clear an exhausted counter. Re-read both counters before enabling or updating the quote. ::::details[SDK helpers for a guarded reset] These helpers build the instruction. Use the [transaction submission path](/sdks/onchain#submitting) to sign, submit and confirm it. :::code-group ```rust [Rust] use flint_margin_maker_client::onchain::accounts::LinearPerpQuoting; use flint_margin_maker_client::onchain::instructions::ResetQuotingSequencesBuilder; use flint_margin_maker_client::onchain::types::{QuoteStream, QuotingSequenceReset}; use solana_sdk::instruction::{AccountMeta, Instruction}; use solana_sdk::pubkey::Pubkey; use solana_sdk_ids::sysvar::clock; pub fn reset_sequence( signer: Pubkey, authority: Pubkey, perp_id: u32, entry_index: u32, stream: QuoteStream, expected_sequence: u64, ) -> Instruction { let (quoting, _) = LinearPerpQuoting::find_pda(perp_id); ResetQuotingSequencesBuilder::new() .signer(signer) .authority(authority) .clock(clock::ID) .allow_partial_success(false) .updates(vec![QuotingSequenceReset { linear_perp_id: perp_id, entry_index, stream, expected_sequence, }]) .add_remaining_account(AccountMeta::new(quoting, false)) .instruction() } ``` ```ts [TypeScript] import { AccountRole, address, type Address, type TransactionSigner } from "@solana/kit"; import { findQuotingPda, getResetQuotingSequencesInstruction, QuoteStream, withRemainingAccounts, } from "@superis-labs/flint-margin-maker-client"; export async function resetSequence( signer: TransactionSigner, authority: Address, perpId: number, entryIndex: number, stream: QuoteStream, expectedSequence: bigint, ) { const [quoting] = await findQuotingPda({ linearPerpId: perpId }); const instruction = getResetQuotingSequencesInstruction({ signer, authority, clock: address("SysvarC1ock11111111111111111111111111111111"), allowPartialSuccess: false, updates: [{ linearPerpId: perpId, entryIndex, stream, expectedSequence }], }); return withRemainingAccounts(instruction, [{ address: quoting, role: AccountRole.WRITABLE }]); } ``` ::: :::: ## The instructions | Instruction | Signer | Writes | Entry index | | --- | --- | --- | --- | | `update_quoting` | Owner, or `Trade` delegate | One slot: one `QuotingUpdate` **and** the `enable` flag | Hint; the program searches if incorrect | | `update_density_fairs` | Owner, or `Quote` delegate | Fairs for many perps; keeps flags, bands and fill risk | Must be exact | | `update_order_lists` | Owner, or `Quote` delegate | Order-list sides for many perps; keeps flags | Must be exact | | `reset_quoting_sequences` | Owner, or `Quote` delegate | Restarts one stream per entry | Must be exact | | `open_quoting_entry` | Owner, or `Trade` delegate, with `MAY_QUOTE` | Opens your slot on one perp | None; read it from `GetQuotingState` | | `release_quoting_entry` | Owner, `Trade` delegate, or Flint's admin | Disables your slot and allows reuse | A hint | `update_quoting` carries one `QuotingUpdate`: | Variant | Effect | Stream | | --- | --- | --- | | `Keep` | Nothing; use it to flip `enable` alone | none | | `Reset(kind)` | Discard the strategy; start an empty `OrderList`, `Density` or `RelativeDensity` | resets all | | `OrderList { sequence, bids, asks }` | Replace sides of an order list | Order | | `DensityBands { sequence, bids, asks }` | Replace sides of a density strategy | Order | | `RelativeDensityBands { sequence, bids, asks }` | Replace sides of a relative-density strategy | Order | | `DensityFair { sequence, fair }` | Move the fair (either density kind) | Fair | For each side, `None` leaves it as it was, `Some([])` empties it, and `Some(levels)` replaces it whole, clearing anything past the new run. Update both sides when both prices change. An unchanged stale side can still fill at its old price. The strategy write applies first and the flag second, so a rejected write cannot enable the slot with an unintended quote. In Rust, `QuoteBuilder` has a method per variant: `reset`, `enable`, `order_list`, `density_bands`, `relative_density_bands` and `density_fair`, plus `update` for a raw `QuotingUpdate`. Each write uses one `update_quoting`, and all of one perp's writes in a build go in one transaction. `reset` writes `enable: false`; the four strategy writes all write `enable: true`, so any of them switches a disabled slot back on. To write a slot and keep it disabled, use `update(perp_id, false, ...)`. The three batch instructions update many perps in one instruction. Their accounts are: 1. `signer`: the key signing the transaction; 2. `authority`: your trader account if the owner signs, or the delegate account (PDA `["delegate_trader", trader_id, quoter]`) if a delegate signs. It must differ from `signer`; 3. `clock`: the Clock sysvar; 4. then one **writable quoting account per update**, in the same order as the updates, each one distinct. Use one update per perp per batch. `allow_partial_success: true` skips entries that fail, such as a stale sequence, and succeeds if any applied; `false` makes the batch atomic. ### Size and compute units A v1 transaction (SIMD-0385) is capped at 4,096 bytes and 64 distinct accounts; legacy and v0 transactions at 1,232 bytes. Pack updates within the limit and split batches across transactions when needed. The program's [density-fair benchmarks](https://github.com/superis-labs/perps/blob/435c48b/programs/flint/tests/bench_density_fair.rs) and [order-list tests](https://github.com/superis-labs/perps/blob/435c48b/programs/flint/tests/update_order_lists.rs) use these compute-unit ceilings for eight-perp batches. Measure your own transactions before choosing a limit: | Instruction, 8 perps | CU ceiling in the program's benchmarks | | --- | --- | | `update_density_fairs`, owner signing | 506 | | `update_density_fairs`, delegate (no expiry / with expiry) | 522 / 561 | | `update_density_fairs`, partial success | 607 | | `update_order_lists`, atomic / partial success | 1,999 / 2,191 | The priority fee is charged on the requested limit. Add a Compute Budget `SetComputeUnitLimit` sized to your measured usage plus headroom, and a `SetComputeUnitPrice` from `recommended_cu_price`. Simulate your own transaction once to get the number; account count and level count affect compute usage. A v1 transaction carries these in its message header instead of as instructions, and states the priority fee as total lamports: `ceil(price × limit / 1,000,000)`, the same fee the legacy instructions cost. An absent v1 limit means 0, not the runtime default, so always set the CU limit and the loaded-accounts cap. The Rust `QuotingCore` sets both on every transaction it builds: the limit from `QuotingConfig::compute_unit_limit`, which defaults to 200,000, and the price from `compute_unit_price` or, when that is unset, the stream's `recommended_cu_price`. It builds v1 transactions by default (`QuotingConfig::tx_version`). Lower the limit to your measured usage. It packs the builder's per-perp groups into as few transactions as fit. Each perp's group must fit in one transaction; the core rejects a group that exceeds the limit. ## Leader-window advisories `SpreadProtectService.SubscribeSpreadProtect` (authenticated listeners only) streams Flint's advice on when to quote wider. It opens with a `ResetState` carrying the baseline `default_spread_mult`, then sends a `WidenWindow` (`start_slot`, `end_slot` inclusive, `spread_mult` of at least `1.0` as a decimal string) for each upcoming leader window Flint flags. The same windows go to every maker. The program does not read this advice or widen your quote. In Rust, `QuotingCore` follows the feed itself: `core.spread_mult()` returns the multiplier for the current slot as a `Decimal`, and `core.spread_protect_window()` the window in force. Your bot chooses whether to apply the multiplier to its spreads. ## Where to go next | Goal | Page | | --- | --- | | Margin, caps, liquidation, key rotation | [Risk & inventory](/makers/risk) | | Tick and lot arithmetic | [Prices, sizes, units](/overview/units) | | Maker rebates | [Fees](/overview/fees) | | Builders, PDAs and decoders | [On-chain clients](/sdks/onchain) | | Error codes from refused writes | [Errors](/overview/errors) | # Risk & inventory A perps maker uses a trader account with a quoting slot. Each fill changes the account's position. Maker positions follow the same margin, funding, and liquidation rules as taker positions. ## Operating controls Set limits and failure responses in your bot before you enable quotes. | Condition | Response | | --- | --- | | Free collateral is too low for the intended fills | Reduce quote size or exposure. Do not use maintenance-margin headroom as a quote-size allowance. | | Net equity reaches your maintenance-margin alert threshold | Reduce exposure or add collateral. Disabling quotes does not close existing positions. | | Your price source stops or its data is stale | Stop refreshing quotes and disable them. If the disable cannot land, existing quotes remain subject to their expiry. | | Account health is unavailable or stale | Stop refreshing quotes and disable them until current health data is available. | | A quote update fails or exceeds your latency limit | Check transaction status and accepted slot state. An unsuccessful update leaves the previous quote active until expiry. | | You stop, restart or rotate a writer | Follow [Stop quote writes](/makers/example#stop-quote-writes). Reconcile pending transactions before the final disable or a sequence reset. | | A quoting key is compromised | Revoke its on-chain delegate authority. Also disable its stored quotes and cancel resting orders; revocation does not remove them. | For account calculations, see [Collateral & margin](/overview/margin), [Funding](/overview/funding) and [Liquidations](/overview/liquidations). ## One account, cross-margined Account health includes every asset and perp in your trader account. All its perps use the same collateral. A loss on one perp reduces the collateral available for the others. Check both margin thresholds: * **Initial margin**: you need `net_equity ≥ initial margin` to increase exposure. * **Maintenance margin**: once `net_equity` falls below it, the account can be liquidated. Initial margin is always at least maintenance margin. Between the two thresholds, you can reduce exposure but cannot increase it. ## Margin at fill time The program checks margin separately for each quoted level the taker reaches. Perps have no per-market inventory balance or spot-style soft cap. The program first limits the level's quantity to the position cap. It then checks whether your account can support the **whole remaining level**, before and after the fill: * a fill that **grows** your position must leave you at or above **initial** margin; * a fill that **reduces** it must leave you at or above **maintenance**; * a fill that **flips** the position through zero must meet initial margin for the whole fill. A fill priced worse than the mark (a bid above it, an ask below it) reduces the equity available for the margin check. The program accounts for the adverse price difference as a debit to the PnL-currency balance. It excludes the maker rebate from this check, so the rebate cannot make an otherwise unfillable level pass. If the full level fails the margin check, none of it is offered. No deeper level on that side is offered to that taker order, even if it is smaller. The taker's order can continue to other makers. An account already below maintenance margin offers no fillable quote. If you also rest limit orders on the perp's order book, your quote and your orders share one margin allowance per taker order. An order that fails its margin check is skipped, so later orders can still trade. See [Quoting → Quotes and resting orders](/makers/quoting#quotes-and-resting-orders). ### Your own leverage cap Use `set_account_leverage_cap` to set a maximum account leverage (stored as `1 / max_leverage`; `0` removes the cap). The owner or a `Trade` delegate sets it. Once set, every fill that grows exposure, quote fills included, must also leave `net_equity ≥ leverage_cap × net_exposure`. A quoted level that fails this check is refused whole, and deeper levels are not offered for that taker order. The leverage cap does not apply to reducing fills or to the maintenance-margin check. :::tip[Size quotes to free collateral] Stored quote depth can exceed the depth your account can fill. Size your quotes from free collateral and each perp's initial-margin requirement. Allow for fills on other perps that use the same collateral. A level that fails the margin check prevents deeper levels on that side from filling for that taker order. ::: ## Caps and limits Each listing can cap one trader's net position with `max_position_lots`. The following limits also apply: | Limit | Scope | Effect | | --- | --- | --- | | Margin | Your account | A level that fails the margin check prevents deeper quote levels on that side from filling for that taker order. | | Leverage cap (`set_account_leverage_cap`) | Your account, if you set one | A quoted level that would increase exposure beyond the cap is refused whole; deeper quote levels are not offered for that taker order. | | Position cap (`max_position_lots`) | Each trader on the perp | Caps net lots in either direction; reductions remain possible. | | Open-interest cap (`max_open_interest_lots`) | Each perp | A trade that would push the perp's open interest past the cap is refused. | | Position slots (`max_positions`) | Your account | If your account has no room for a new perp position, your slot is skipped on that perp's orders. A perp with only resting orders also uses a position slot. | | Makers per perp | Each perp's quoting account | 32 slots. A new maker can open one only if a slot is available. | | Slot capacity | Your quoting slot | 21 order-list levels or 30 density bands per side. | | Resting orders | Your account, per perp | 16 orders per trader per perp. | | Density fill risk | Your quoting slot | Filled lots remain subtracted from the ticks nearest your fair until a `Reset`; see [Quoting → Fill risk](/makers/quoting#fill-risk-on-density-quotes). | The density fill counter reduces available depth near the fair after fills on that side. It never decreases on its own. Decide when to reset and restore depth; do not reset automatically on every refresh cycle. ## Inventory and funding Every fill changes your position. The program does not rebalance it. Track fills with `SubscribeFills` and reconcile positions against account state. Adjust your fair prices to encourage fills that reduce inventory. A density quote lets you move `bid_fair` and `ask_fair` independently without rewriting bands. Open positions pay or receive funding. Funding accrues on your net position, including fills from quotes and hedges on Flint. See [Funding](/overview/funding) for the rate and settlement interval. ## Liquidation Monitor liquidation risk for your account and for accounts that can fill your quotes. **Your own account.** Below maintenance margin your quote offers no fillable levels, and the account can be liquidated. Permissionless keepers can close part of your perp positions and seize collateral for a bonus. See [Liquidations](/overview/liquidations). Before liquidation, once you are below initial margin, anyone can cancel your margin-reserving resting orders with `force_cancel_perp_orders`. Size quotes to your free collateral. Disable quotes when account health no longer supports the intended fills. **Another account.** A liquidation closes the distressed account's position against makers' resting quotes, with the liquidated account as the taker. Unlike an ordinary order, a liquidation must include **every enabled maker slot** on that perp. Its fills still require the maker margin checks described above. The liquidator chooses which resting order accounts to include. In a fast move, liquidation orders can fill at your quoted prices. Choose prices and sizes that your account can support during a series of liquidations. ## Hedging Choose a hedge method for your inventory: * **Skew quotes.** Adjust your fair prices to encourage fills that reduce inventory. * **Hedge elsewhere.** Hedge your net position on another venue. Collateral there does not support margin on Flint. Size your Flint quotes to Flint's free collateral alone. * **Hedge on Flint.** The quoting key's `Trade` scope also lets it place `Ioc` or `Fok` immediate orders, or `Gtc` and `Gtd` resting orders. Set `post_only` on a resting order to reject an immediate crossing. Set `reduce_only` to allow only position reductions (see [Orders](/traders/orders)). An order cannot cross your own slot or orders; self-matching is refused. Leave your own trader account out of the makers it crosses. Funding continues to accrue on your net position on Flint while it is open. ## Monitoring `MakerService` and `TxService` use your bot's bearer session. `AccountService` and `PerpMarketService` are public. See [Quoting → Watching fills](/makers/quoting#watching-fills) for fields. | What | Where | | --- | --- | | Fills as they happen | `MakerService.SubscribeFills` | | Fill history | `MakerService.GetFills` (paged) | | Your best bid and ask alongside the order book | `MakerService.SubscribeMarketSnapshots` | | Slot state: enabled, strategy, sequences | `MakerService.GetQuotingState` | | Quote writes observed on chain | `MakerService.SubscribeQuotes` / `ListRecentQuotes` | | Send, land and fail rates; fill and quote-update rates | `MakerService.GetStats` | | Markouts | `MakerService.GetMarkoutSummary`, or `GetFills` sorted by markout | | Keys and sessions quoting for you | `MakerService.ListQuotingAuthorities` / `ListQuoterSessions` | | Maker volume by perp and window | `MakerService.GetVolumeBreakdown` / `GetVolumeSeries` | | Transaction landing and failure status | `TxService.SubscribeTxStatus` | | Collateral, account health, positions, margin | Public `AccountService.GetAccount` / `SubscribeAccount`; see [Positions & PnL](/traders/positions) | | Mark, index and funding rate per perp | `PerpMarketService.GetMarketStats` (poll; `SubscribeMarketStats` is not served yet) | `ListQuotingAuthorities` and `ListQuoterSessions` show the keys the server has observed quoting or holding a live session heartbeat for your trader, with regions, SDK versions, round-trip times and last submission and landing times. These lists can omit a key that authenticates but never sends a heartbeat or transaction. Investigate any entry you do not recognize. Revoke unauthorized delegate keys on chain. To audit every key with authority, read your delegate accounts on chain. :::warning[Resting order monitoring] The maker dashboard shows perps accounts, positions, and fills. The API does not index the resting order book: track those orders on chain. ::: If transactions stop landing or a Flint feed stops updating, use `EscalationService.PageTheTeam` to send a message to Flint's on-call team (`client.escalation()` in Rust). The trader must be linked to your organization. Otherwise, the call returns `PERMISSION_DENIED`. Alert on at least: * **free collateral and initial-margin headroom**: compare net equity with initial margin before adding exposure. Free collateral is `max(net_equity - initial_margin, 0)`; * **maintenance-margin headroom**: compare net equity with maintenance margin to monitor liquidation risk; * **submit-to-land latency and failures** on your quote transactions: failed updates leave the previous quote active until expiry; * **the quoting key's SOL balance**: Flint supplies fee SOL. If that balance is too low, quote updates fail and the existing quotes expire; * **price-source age and fair versus mark**: stop refreshing quotes when price data exceeds your age or deviation limits. ## Operational safety ### Key hygiene | Key | Holds | Can | Keep | | --- | --- | --- | --- | | Owner | Account ownership | Deposit, withdraw, delegate, revoke, quote and trade | Offline: hardware wallet or multisig | | Quoting key | `Trade` + `Quote` delegate | Sign in, quote, enable/disable, open and release slots, place and cancel orders, set the leverage cap | On the bot host, with Flint-provided fee SOL | A stolen quoting key can quote and trade your account into losses. It has no withdrawal authority when it has only `Trade` and `Quote`. Limit its permissions and monitor its activity: * **Never grant `Withdraw` or `BorrowLend`** to a quoting key. * **Set an expiry** on the delegate (`expiry_unix_seconds` in Rust, `expiryUnixSeconds` in TypeScript). Rotate to a new funded key before it expires. An existing delegate grant cannot be renewed with `create_delegate_trader_account`. * **Watch unexpected fills**, as well as your own submissions: any transaction signed by the key acts on the account, whether or not it went through `TxService`. The downloadable examples set the delegate expiry to `0`, which means no expiry. To create an expiring grant, change that argument to the intended absolute Unix timestamp before running `setup` with a new key. For a planned key rotation, first follow [Stop quote writes](/makers/example#stop-quote-writes), then the [key rotation procedure](/makers/example#key-rotation). Sequence numbers belong to the slot, so the new writer must read `order_sequence` and `fair_sequence` from `GetQuotingState` and allocate higher counters. The Rust `QuotingCore` does this on startup. Also set `QuotingConfig::delegate_trader_account` to the new key's delegate account. Revocation permanently removes that key's delegate authority on this trader account. The delegate account remains allocated with empty permissions. You cannot grant that key again on the same trader account. This prevents an old signed intent from being replayed against a new grant. Revocation does not disable stored quotes or cancel resting orders. Use the owner or another authorized key to remove them. A revoked key cannot submit those cleanup transactions. Revoking an API session (`AuthService.Revoke`, or `revoke()` on the SDK's `AuthFlow`) invalidates that bearer token. The key retains its on-chain authority and can submit transactions through an RPC node until you revoke its delegate authority. ### Stale quotes A quote can remain fillable until it expires, you disable it, or it is rewritten. The program does not compare your fair to the mark or an oracle to detect stale quote prices: * **Use short expiries.** `slot_delta_expiry` on density bands (from the fair's stamp) and `slot_expiry` on order-list levels are your automatic expiry controls. If your bot, network or `TxService` path fails, quotes expire without another transaction. * **Do not re-stamp a fair without a current price source.** A fresh slot extends expiry even if the price is stale. If your price source fails, stop the update loop and disable the quote. If you cannot submit a disable, the quote remains subject to its existing expiry. * **Disable on shutdown.** Follow [Stop quote writes](/makers/example#stop-quote-writes) before the final disable. Confirm that each live slot is disabled and that resting orders are canceled on each perp. Keep an authorized key available until this is complete. * **Recover sequence counters.** Read the accepted value before restarting a writer. If it cannot advance, use the [guarded sequence reset](/makers/quoting#guarded-sequence-reset). ### Stale prices Each perp's mark is published from the oracle and is valid only for a fixed window. Once it expires, the program refuses orders on that perp with `LinearPerpMarkExpired` until a keeper publishes a fresh mark. No quote fills during this period. Your slot state does not change. When the mark becomes valid again, unexpired quotes can become fillable immediately. If your own fair stopped updating during the outage, check that the quote has expired or is disabled before then. Collateral also requires a valid price. If your collateral cannot be priced when a taker reaches your book, that order reverts. Expired oracle prices on your collateral prevent quote fills. ## Where to go next | Goal | Page | | --- | --- | | Quote writes, the quote update loop, disabling quotes | [Quoting](/makers/quoting) | | Account margin math | [Collateral & margin](/overview/margin) | | Funding rate and settlement | [Funding](/overview/funding) | | Liquidation waterfall and keepers | [Liquidations](/overview/liquidations) | | Maker rebates | [Fees](/overview/fees) | # Local maker testing Run a perps network on your computer. Use the Rust or TypeScript example to create a maker account, publish a quote and check a fill. The network provides test wallets and tokens. Use its local URLs and keys throughout this guide. To use the dashboard for account setup, follow [Local dashboard setup](/makers/local-dashboard) after starting the network. Keep the SDK example for publishing quotes. ## Before you start ### Install dependencies Expand the scripts for your platform. Read a block, then use its **Copy code** button to copy it. Use **1a or 1b**, then **2**. Use **3** for TypeScript or the dashboard. The scripts install tools; they do not start a Solana network. :::details[1a. macOS — build tools and Docker Desktop] Uses [Homebrew](https://docs.brew.sh/Installation). If the Xcode tools installer opens, complete it and run this block again. Complete Docker Desktop's first-run setup and wait for its engine to start. ```bash bash <<'BASH' set -euo pipefail [[ "$(uname -s)" == Darwin ]] || { echo 'Run this script on macOS.' >&2; exit 1; } if ! xcode-select -p >/dev/null 2>&1; then xcode-select --install echo 'Finish the Xcode tools installer, then run this block again.' exit 1 fi if ! command -v brew >/dev/null; then for candidate in /opt/homebrew/bin/brew /usr/local/bin/brew; do if [[ -x "$candidate" ]]; then eval "$("$candidate" shellenv)"; break; fi done fi if ! command -v brew >/dev/null; then /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" case "$(uname -m)" in arm64) eval "$(/opt/homebrew/bin/brew shellenv)" ;; x86_64) eval "$(/usr/local/bin/brew shellenv)" ;; esac fi brew install git gh protobuf cmake pkg-config openssl@3 python@3.12 if ! command -v docker >/dev/null; then brew install --cask docker-desktop; fi open -a Docker BASH ``` ::: :::details[1b. Ubuntu 24.04 or WSL2 — build tools] Use x86-64 Ubuntu 24.04. On Windows, install [Ubuntu in WSL2](https://learn.microsoft.com/en-us/windows/wsl/install), then run this block in the Ubuntu terminal. ```bash bash <<'BASH' set -euo pipefail [[ "$(uname -s)" == Linux && "$(uname -m)" == x86_64 ]] || { echo 'Use an x86-64 Ubuntu or WSL2 environment.' >&2; exit 1; } # shellcheck source=/dev/null . /etc/os-release [[ "$ID" == ubuntu && "$VERSION_ID" == 24.04 ]] || { echo 'Use Ubuntu 24.04, including inside WSL2.' >&2; exit 1; } sudo apt-get update sudo apt-get install -y \ ca-certificates curl git gh python3 python3-venv unzip xz-utils bzip2 \ clang cmake g++ libclang-dev libssl-dev libudev-dev make perl \ pkg-config protobuf-compiler zlib1g-dev BASH ``` On Windows, enable [Docker Desktop's WSL2 integration](https://docs.docker.com/desktop/features/wsl/). On native Ubuntu, use the next block to install [Docker Engine](https://docs.docker.com/engine/install/ubuntu/). Keep an existing Docker installation. ```bash bash <<'BASH' set -euo pipefail if command -v docker >/dev/null; then echo 'Docker is already installed. Keep your existing installation.' exit 0 fi sudo install -m 0755 -d /etc/apt/keyrings sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc sudo chmod a+r /etc/apt/keyrings/docker.asc printf '%s\n' \ 'Types: deb' \ 'URIs: https://download.docker.com/linux/ubuntu' \ 'Suites: noble' \ 'Components: stable' \ 'Architectures: amd64' \ 'Signed-By: /etc/apt/keyrings/docker.asc' \ | sudo tee /etc/apt/sources.list.d/docker.sources >/dev/null sudo apt-get update sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin sudo systemctl enable --now docker sudo usermod -aG docker "$(id -un)" echo 'Sign out and sign in again before using Docker without sudo.' BASH ``` Sign out and sign in again after joining the Docker group. ::: :::details[2. All platforms — Rust, Solana and Surfpool] Installs [Rust 1.95](https://www.rust-lang.org/tools/install), [Solana 3.1.7](https://solana.com/docs/intro/installation/dependencies) and [Surfpool 1.5.0](https://github.com/solana-foundation/surfpool/releases/tag/v1.5.0). Selects Rust 1.95 as the default toolchain. The Surfpool archive is checked against its release checksum. ```bash bash <<'BASH' set -euo pipefail check_sha256() { if command -v sha256sum >/dev/null; then sha256sum -c -; else shasum -a 256 -c -; fi } case "$(uname -s)/$(uname -m)" in Darwin/arm64) platform=darwin-arm64; checksum=418d1464050cb1e09225b37b907952383f712648544318a0ca7ce13304e7915a ;; Darwin/x86_64) platform=darwin-x64; checksum=64ea0d703a52a28b0e80eeb06bae706d9ec6a8e6d4da7ddab820aa2321b74f21 ;; Linux/x86_64) platform=linux-x64; checksum=5b20a3b46e60c4f819af7b4da5c3ea211f76041710617841cc23247d15887ddc ;; *) echo 'Use macOS, or x86-64 Linux/WSL2.' >&2; exit 1 ;; esac if ! command -v rustup >/dev/null && [[ ! -x "$HOME/.cargo/bin/rustup" ]]; then curl --proto '=https' --tlsv1.2 -fsSL https://sh.rustup.rs \ | sh -s -- -y --profile minimal --default-toolchain 1.95 fi export PATH="$HOME/.cargo/bin:$PATH" rustup toolchain install 1.95 --profile minimal rustup default 1.95 sh -c "$(curl -fsSL https://release.anza.xyz/v3.1.7/install)" export PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH" work_dir="$(mktemp -d)" trap 'rm -rf "$work_dir"' EXIT curl -fsSL \ "https://github.com/solana-foundation/surfpool/releases/download/v1.5.0/surfpool-$platform.tar.gz" \ -o "$work_dir/surfpool.tar.gz" printf '%s %s\n' "$checksum" "$work_dir/surfpool.tar.gz" | check_sha256 mkdir -p "$HOME/.local/bin" "$HOME/.local/share/flint-maker-tools" tar -xzf "$work_dir/surfpool.tar.gz" -C "$HOME/.local/bin" surfpool cat > "$HOME/.local/share/flint-maker-tools/env" <<'PATHS' export PATH="$HOME/.local/bin:$HOME/.cargo/bin:$HOME/.local/share/solana/install/active_release/bin:$HOME/.local/share/flint-maker-tools/node/bin:$HOME/.bun/bin:$PATH" PATHS # shellcheck source=/dev/null . "$HOME/.local/share/flint-maker-tools/env" rustc +1.95 --version solana --version | grep -F 'solana-cli 3.1.7' test "$(surfpool --version)" = 'surfpool 1.5.0' BASH . "$HOME/.local/share/flint-maker-tools/env" ``` For another terminal, load the same paths with `. "$HOME/.local/share/flint-maker-tools/env"`. The server checkout selects its Rust toolchain. ::: :::details[3. TypeScript or dashboard — Node.js and Bun] Skip this block for the Rust-only route. It installs the current [Node.js 22 release](https://nodejs.org/en/download) and [Bun 1.3.14](https://bun.sh/docs/installation) in your home directory. The Node archive is checked against Node's release checksums. ```bash bash <<'BASH' set -euo pipefail check_sha256() { if command -v sha256sum >/dev/null; then sha256sum -c -; else shasum -a 256 -c -; fi } case "$(uname -s)/$(uname -m)" in Darwin/arm64) platform=darwin-arm64 ;; Darwin/x86_64) platform=darwin-x64 ;; Linux/x86_64) platform=linux-x64 ;; *) echo 'Use macOS, or x86-64 Linux/WSL2.' >&2; exit 1 ;; esac work_dir="$(mktemp -d)" trap 'rm -rf "$work_dir"' EXIT base=https://nodejs.org/dist/latest-v22.x curl -fsSL "$base/SHASUMS256.txt" -o "$work_dir/SHASUMS256.txt" archive="$(awk -v suffix="-$platform.tar.xz" '$2 ~ (suffix "$") {print $2}' "$work_dir/SHASUMS256.txt")" [[ -n "$archive" && "$archive" != */* && "$archive" != *$'\n'* ]] || { echo 'Could not select one Node.js archive.' >&2; exit 1; } curl -fsSL "$base/$archive" -o "$work_dir/$archive" (cd "$work_dir"; grep -F " $archive" SHASUMS256.txt | check_sha256) mkdir -p "$HOME/.local/share/flint-maker-tools/node" tar -xJf "$work_dir/$archive" --strip-components=1 -C "$HOME/.local/share/flint-maker-tools/node" curl -fsSL https://bun.sh/install | bash -s -- 'bun-v1.3.14' # shellcheck source=/dev/null . "$HOME/.local/share/flint-maker-tools/env" node --version | grep -E '^v22\.' npm --version test "$(bun --version)" = '1.3.14' BASH . "$HOME/.local/share/flint-maker-tools/env" ``` ::: ### Check the tools ```sh git --version gh --version python3 --version protoc --version rustc +1.95 --version solana --version surfpool --version docker info ``` If `docker info` fails, start Docker Desktop or your Docker Engine service before continuing. ### Get access Ask Flint for GitHub read access to: * [flint-server](https://github.com/superis-labs/flint-server) and its source dependencies. * [Program fixtures](https://github.com/superis-labs/superis-program-fixtures). Then [authenticate Git](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/about-authentication-to-github#authenticating-with-the-command-line) and sign in with [`gh auth login`](https://cli.github.com/manual/gh_auth_login). For SDK packages: * **Rust:** get your token from **Perps → Developer → Get SDK credentials**. * **TypeScript:** obtain GitHub Packages access from Flint. Follow [SDK package setup](/makers/sdk-setup#package-access) for registry login. ### Download the local environment Clone the server and select the tested local-environment revision: ```sh git clone https://github.com/superis-labs/flint-server.git cd flint-server git fetch origin 7b164580045d3a0da32593693362f3609cc76529 git checkout --detach 7b164580045d3a0da32593693362f3609cc76529 ``` Run the server commands below from this directory. :::details[Environment versions] | Component | Version or ID | | --- | --- | | Rust / Solana / Surfpool | 1.95 / 3.1.7 / 1.5.0 | | Node.js / Bun | 22.x / 1.3.14 | | ClickHouse | 25.8 | | Protocol source | [`435c48b3`](https://github.com/superis-labs/perps/commit/435c48b3219f4233b47514fe44c51d333a956734) | | Maker example SDK / IDL | 7.0.4 / 0.10.2 | | Program ID | `4MxEr7W5f9xtajcTTpTTd5wfdBmwrF72j2NMg6YzXgMo` | SDK versions come from the downloaded examples' manifests. Tool versions match the pinned fixture. CI checks both sources when they change. ::: ::::steps ## Start ClickHouse Start a separate [ClickHouse](https://clickhouse.com/docs/get-started/setup/self-managed/docker) container for this environment. Use the version shown below. ```sh docker run -d --name maker-docs-clickhouse --ulimit nofile=262144:262144 \ -p 127.0.0.1:18123:8123 -e CLICKHOUSE_SKIP_USER_SETUP=1 \ clickhouse/clickhouse-server:25.8 curl -fsS http://127.0.0.1:18123/ping export MARGIN_FIXTURE_CLICKHOUSE_URL=http://127.0.0.1:18123 ``` Repeat the ping command until it returns `Ok`. Use an unused container name and port. If you change the port, update both the mapping and URL. ## Start the network For local SDK-token tests, prepare the [package registry](/makers/local-testing#optional-local-sdk-registry) before this step. Otherwise, continue below. ```sh MARGIN_OFFLINE=true MARGIN_ACCEPTANCE=true TUI_MARGIN_HEADLESS=true make tui-margin ``` Wait for `FLINT MARGIN DEV STACK — ready`. Keep this terminal running. ### Configure the SDK example Open `.cache/maker-onboarding/manifest.json` in the server directory. Select the wallet entries for your example: | Example | Owner | Bot | | --- | --- | --- | | Rust | `rust-owner` | `rust-quoter` | | TypeScript | `typescript-owner` | `typescript-quoter` | Copy these values into the [SDK configuration](/makers/sdk-setup#configure-the-example): | Setting | Manifest value | | --- | --- | | `RPC_URL` | `rpc_url` | | `PUBLIC_ENDPOINT` | `public_endpoint` | | `MAKER_ENDPOINT` | `maker_endpoint` | | `OWNER_KEYPAIR` | Selected owner entry's `keypair_path` | | `QUOTER_KEYPAIR` | Selected bot entry's `keypair_path` | | `TRADER_ID` | Selected owner entry's `trader_id` | | `PERP_ID` | Selected `perps[]` entry's `linear_perp_id` | | `COLLATERAL_MINT`, `COLLATERAL_ATOMS` | `collateral_mint`, `collateral_atoms` | The wallets already hold fee SOL and collateral tokens. Use USDC for this test. ## Create and fund the maker account Install and configure the [SDK example](/makers/sdk-setup) in another terminal. Use the manifest's keys and choose one account-setup route: * **SDK:** follow the [file-key steps](/makers/onboarding#step-4) to run `setup`, then `deposit`, once. * **Dashboard:** follow [Local dashboard setup](/makers/local-dashboard) to create the account, deposit collateral and grant the manifest's bot key. Return here to grant quoting permission. Use the SDK example to publish quotes. ### Grant quoting permission Use `wallets["admin"].keypair_path` as the admin key. From the SDK example directory, grant **MAY_QUOTE** to your maker: :::code-group ```sh [Rust] ADMIN_KEYPAIR='' cargo run --locked -- grant ``` ```sh [TypeScript] ADMIN_KEYPAIR='' npm run start -- grant ``` ::: Keep your maker's `TRADER_ID`, `PERP_ID`, RPC and gateway settings for this command. ### Open a slot and publish a quote Follow [Open a market slot](/makers/onboarding#step-5), then [Publish a quote](/makers/onboarding#step-6). Keep both quote sides active while you run the fill test. ## Check a fill The maker and taker must have no open position in the selected market. Keep both quote sides active and unexpired. Use one writer for this maker and market. Open another terminal in the server directory. Use: * The taker's `keypair_path` and `trader_id` from `wallets["taker"]`. * Your selected perp ID. * The manifest's USDC `collateral_mint`. * Your perps maker ID (`TRADER_ID`) and a small lot count within both quote sides' depth. ```sh cargo build --release --locked -p tui-margin --bin margin-taker-proof target/release/margin-taker-proof \ --rpc-url --keypair \ --trader-id --perp-id \ --collateral-mint \ --makers --lots ``` The command creates a missing taker account, deposits collateral, opens and closes IOC positions, and settles both accounts. It uses USDC for PnL settlement. ### Check the result In the maker SDK example, run `fills` and `account`. Check the buy and sell fills. The maker position should be zero after the round trip. Check the taker account with its ID. The prefix changes the ID for this command only: :::code-group ```sh [Rust] TRADER_ID='' cargo run --locked -- account ``` ```sh [TypeScript] TRADER_ID='' npm run start -- account ``` ::: The taker position should also be zero. Finish with [Shutdown](/makers/example#shutdown). Stop the network with **Ctrl+C** in its terminal. Stop the Docker containers you started for this guide: `maker-docs-clickhouse` and, if used, `maker-docs-kellnr-current7`. Use `docker stop ` with the names you selected. :::: ## Optional maintainer checks For a fresh SDK acceptance run, use [`make maker-onboarding-smoke`](https://github.com/superis-labs/flint-server/blob/7b164580045d3a0da32593693362f3609cc76529/scripts/maker-onboarding-smoke.sh). It can check both SDK languages across both fixture markets. Run it separately from the manual environment above. For dashboard automation, see the [browser regression guide](/makers/local-dashboard#optional-maintainer-checks). ## Optional local SDK registry :::details[Set up a local package registry] Use this section for SDK-token issuance tests or cached package installs. Prepare it before starting the network. ### Start the registry Use Python **3.11+**. First, [build the Rust example](/makers/sdk-setup#download-and-build) to cache its SDK **7.0.4** packages. The helper reads the archives from `~/.cargo/registry/cache`. If you use another Cargo home, pass its cache path with `--crate-cache`. Clone the [SDK source](https://github.com/superis-labs/flint-api-client) in a separate directory: ```sh git clone --branch maker-onboarding-docs https://github.com/superis-labs/flint-api-client.git cd flint-api-client ``` Then run the helper: ```sh python3 scripts/local_maker_registry.py \ --state .tmp/maker-registry-current7 \ --version 7.0.4 \ --program-id 4MxEr7W5f9xtajcTTpTTd5wfdBmwrF72j2NMg6YzXgMo \ --port 18081 --container maker-docs-kellnr-current7 ``` The helper starts Kellnr **6.3.0** on loopback. Its reader can download packages but cannot publish. It prints a report and saves it to `.tmp/maker-registry-current7/report.json`. ### Build from the registry Open the report and copy `maker_cargo_home`. From the Rust example directory, use that path to fetch the packages from the local registry, then build offline. This preserves the package lockfile and checksums: ```sh CARGO_HOME='' cargo fetch --locked CARGO_HOME='' cargo build --locked --offline ``` ### Enable local token issuance Find the path in the report's `gateway_env_file` field. Replace `` below with that path. Before starting the network, run these commands in the server terminal: ```sh set -a . set +a ``` Keep these local credentials in ignored `.tmp` and use only loopback URLs. ::: # Local dashboard setup Use the dashboard with the [local maker network](/makers/local-testing) to create an account, deposit collateral, grant a bot key and open a quoting entry. Use your bot or the SDK example to publish quotes. ## Before you start * Complete the prerequisites and **1. Start ClickHouse** in [Local maker testing](/makers/local-testing). Keep `MARGIN_FIXTURE_CLICKHOUSE_URL` set in the terminal that starts the network. * [Start a fresh network](/makers/local-testing#start-the-local-network) and wait for its ready message. Choose this route before running SDK `setup` or `deposit`. * Install [Bun](https://bun.sh/docs/installation) and [Node.js 22 with npm](https://nodejs.org/en/download). * Obtain GitHub access to [the dashboard](https://github.com/superis-labs/ui-flint-dash) and its private package dependencies. This walkthrough uses synthetic local keys and tokens. The default fixture links its organization to maker **401**. Use the manifest's `rust-owner` and `rust-quoter` wallets with either SDK language. For a maker on another network, follow [Maker onboarding](/makers/onboarding) with your own wallet and bot key. ## 1. Start the dashboard Clone the dashboard in a separate directory: ```sh git clone https://github.com/superis-labs/ui-flint-dash.git cd ui-flint-dash git fetch origin 8173d59175bf4e6efc864a5e64ebd0b5e0e25488 git checkout --detach 8173d59175bf4e6efc864a5e64ebd0b5e0e25488 git submodule update --init --recursive npm login --scope=@superis-labs --auth-type=legacy --registry=https://npm.pkg.github.com bun install --frozen-lockfile bun run build:shared ``` Create `.env.local` in this checkout. Replace the placeholders with values from `.cache/maker-onboarding/manifest.json` in the server checkout: ```dotenv VITE_SITE_MODE=dash VITE_NETWORK_LABEL=LOCAL VITE_REST_URL='' VITE_REST_URLS= VITE_PERPS_NETWORK=local VITE_PERPS_RPC_URL='' VITE_PERPS_WS_URL='' VITE_PERPS_PROGRAM_ID='' VITE_ENABLE_ANALYTICS=false VITE_SENTRY_DSN= VITE_WALLETCONNECT_PROJECT_ID= ``` Run `bun run dev`. Open [the local dashboard](http://127.0.0.1\:5173/maker/onchain) and keep this terminal running. ## 2. Sign in with the fixture email With `MARGIN_FIXTURE_CLICKHOUSE_URL` set, the network launcher creates the login organization. Check `.cache/maker-onboarding/organization.json` in the server checkout. The default fixture has `email` set to `maker@example.test` and `trader_id` set to `401`. If the file is missing, complete the ClickHouse and network startup steps before continuing. 1. Open **Account → Sign in** and enter that email. 2. Select **Continue**. 3. Read the new login-code line in the network terminal. It includes the fixture email and `code=` followed by the current six-digit code. 4. Enter that code and select **Verify & sign in**. 5. After sign-in completes, select **Perps → On Chain**. Check that the dashboard shows maker **401**. Use the code from your current request. ## 3. Connect the local owner wallet Register the disposable `rust-owner` wallet from the manifest with the local development signer below. This loads the fixture key into the browser page. :::details[Register a fixture wallet through the browser console] Open the browser's developer tools on the local dashboard and select **Console**. Paste this snippet. When the file picker opens, select the JSON file at `wallets["rust-owner"].keypair_path` from the manifest: ```js const picker = document.createElement("input"); picker.type = "file"; picker.accept = ".json"; picker.onchange = async () => { const file = picker.files?.[0]; if (!file) return; const { registerLocalTestWallet } = await import("/src/lib/flint/perps/localTestWallet.ts"); const address = await registerLocalTestWallet(JSON.parse(await file.text())); console.info(`Local fixture wallet: ${address}`); }; picker.click(); ``` The console prints the wallet address. This helper is available only in local development. Repeat registration after reloading the page. ::: Select **Connect Local fixture**. Check that the connected address matches `wallets["rust-owner"].public_key` in the manifest. ## 4. Create, fund and delegate Follow [Create and fund the account](/makers/onboarding#step-4): 1. Select **Review account creation** to create the assigned account. 2. In **Funds**, deposit a small amount of the fixture's USDC. 3. In **Delegates**, paste `wallets["rust-quoter"].public_key`, choose **Maker bot** (**Trade + Quote**) and set an expiry. For each action, select its **Review** button and check the transaction details. After **Simulation passed** appears, select **Sign & send** and wait for **Transaction confirmed**. Close the review to continue. Check the account's collateral and active delegate. ## 5. Configure the SDK example In another terminal, follow [SDK example setup](/makers/sdk-setup) for Rust or TypeScript. Complete package access, then download and build the example. Use the existing fixture keys and these values from the same manifest: | Setting | Manifest value | | --- | --- | | `RPC_URL` | `rpc_url` | | `PUBLIC_ENDPOINT` | `public_endpoint` | | `MAKER_ENDPOINT` | `maker_endpoint` | | `TRADER_ID` | `401` | | `OWNER_KEYPAIR` | `wallets["rust-owner"].keypair_path` | | `QUOTER_KEYPAIR` | `wallets["rust-quoter"].keypair_path` | | `PERP_ID` | Selected `perps[]` entry's `linear_perp_id` | | `COLLATERAL_MINT` | `collateral_mint` | Use the [SDK configuration steps](/makers/sdk-setup#configure-the-example) to export these values in the example's terminal. Keep `OWNER_KEYPAIR` set for later SDK settlement and shutdown commands. Account creation, deposit and the bot grant are complete; do not repeat SDK `setup` or `deposit`. ## 6. Open an entry and test quotes 1. Run [Grant quoting permission](/makers/local-testing#grant-quoting-permission) with the fixture admin key, then return to the dashboard. 2. Select **Refresh**, then **Markets**. Find the market for your `PERP_ID` and select **Open entry**. Review, sign and send the transaction. Wait for **Transaction confirmed**. 3. Follow [Publish a quote](/makers/onboarding#step-6), then [Check a fill](/makers/local-testing#test-an-independent-taker). Keep the example configured for maker **401** and the same bot key throughout the test. When finished, follow [Shutdown](/makers/example#shutdown) and the [local network cleanup steps](/makers/local-testing#check-the-result). Stop the dashboard terminal with **Ctrl+C**. ## Optional maintainer checks The dashboard's [browser regression guide](https://github.com/superis-labs/ui-flint-dash/blob/8173d59175bf4e6efc864a5e64ebd0b5e0e25488/README.md#local-perps-onboarding-checks) describes the automated lifecycle test and its OTP capture inputs. # API The Flint perps exchange exposes these gRPC packages: | Package | Audience | Auth | Services | | --- | --- | --- | --- | | `flint.margin.user.v1` | Traders, frontends, analytics, keepers | None — public | `PerpMarketService`, `MarketDataService`, `OrderService`, `FundingService`, `AccountService`, `MarketService`, `RiskService`, `SponsorshipService` | | `flint.margin.user.v1.notifications` | Account owners | Owner bearer session | `NotificationService` | | `flint.margin.maker.v1` | Market makers | Bearer session (see below); `NodeService` is public | `AuthService`, `MakerService`, `TxService`, `SessionService`, `SpreadProtectService`, `NodeService`, `EscalationService` | These packages are served by the same Flint server processes that serve the spot API, on the same two listeners. Public margin services live on the **public** host. `TxService`, `SessionService`, `SpreadProtectService`, and `EscalationService` live only on the **authed** (maker) host; `AuthService`, `MakerService`, and `NodeService` are on both. See [Endpoints](/overview/endpoints). `NotificationService` lives on the public host and authenticates every method. It is unavailable until the server enables notifications. Field-level detail for every message, enum, and RPC is generated from the proto: [User API reference](/api/user-reference) and [Maker API reference](/api/maker-reference). Every service speaks vanilla gRPC and gRPC-Web on the same URL. Pick whichever transport fits your stack — see [Protocols & transports](/api/transports). :::warning[Not every RPC is served yet] The schema is ahead of the server. User RPCs marked **not yet served** below are declared in the proto but currently return gRPC `UNIMPLEMENTED`. Build against them only behind a feature check. Every maker RPC is served. ::: ## Writes happen on-chain There is no "place order" or "deposit" RPC. Every trading state change — deposits, withdrawals, perp orders, maker quotes, liquidations — is a Solana transaction against the Flint margin program. The API helps you build, price, submit, and observe those transactions: * Build instructions with the on-chain clients — see [On-chain clients](/sdks/onchain). * Dry-run them with `OrderService.PreviewOrder`, `AccountService.SimulateAction`, or `AccountService.GetActionLimits`. * Submit them yourself over Solana RPC, through `SponsorshipService.SponsorTransaction` (Flint pays the fee), or — for makers — through `TxService.SubmitTx`. Email notification settings are off-chain writes. The notification service exposes `GetSettings`, `RequestEmailVerification`, `VerifyEmail`, and `DisableEmail`. Public fill history and client-local read/dismiss state power the notification bell independently of email delivery. See the [notification SDK guide](/sdks/typescript#margin-account-notifications). ## `flint.margin.user.v1` — public No credentials. Every RPC is keyed for rate limiting by client IP. ### Perp markets — `PerpMarketService` | RPC | Kind | What it does | | ---------------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ListPerpMarkets` | unary | Perp contract catalog: symbol, assets, tick/lot size, margin rates and tiers, open-interest cap. Filtering by `statuses` and `page_token` is not supported yet. | | `GetPerpMarket` | unary | One perp contract by `PerpId`. | | `GetMarketStats` | unary | Mark, index, oracle, and last-trade prices, 24 h volume and change, open interest, funding, and top of book for one or more perps. | | `GetVolumeBreakdown` | unary | Per-perp volume over rolling windows. | | `GetVolumeSeries` | unary | Bucketed volume time series. | | `SubscribeMarketStats` | stream | Live market stats. Not yet served | ### Order books, trades, candles — `MarketDataService` | RPC | Kind | What it does | | -------------------- | ------ | ------------------------------------------------------------------------------------------------------ | | `GetOrderBook` | unary | L2 book snapshot for one perp (`depth` ≤ 100). | | `SubscribeOrderBook` | stream | Snapshot, then sequenced deltas. A delta replaces the size at a price; zero deletes the level. | | `GetTrades` | unary | Archived public trades for a time range. | | `SubscribeTrades` | stream | Live public trades. Not yet served | | `GetCandles` | unary | OHLCV candles. Only the trade price source is archived; mark and index sources return `UNIMPLEMENTED`. | | `SubscribeCandles` | stream | Live candle updates. | ### Orders — `OrderService` | RPC | Kind | What it does | | ----------------- | ------ | ---------------------------------------------------------------------------------------------------------- | | `PreviewOrder` | unary | Simulates a perp order against the current book and the account's margin without signing or submitting it. | | `ListOrders` | unary | An account's orders. Not yet served | | `GetOrder` | unary | One order. Not yet served | | `SubscribeOrders` | stream | Live order updates. Not yet served | ### Funding — `FundingService` | RPC | Kind | What it does | | -------------------------- | ------ | ------------------------------------------------------------------------------------------------------------ | | `GetFundingRates` | unary | Current funding rate per perp. `rate` is a fraction per `interval_seconds`; positive means longs pay shorts. | | `SubscribeFundingRates` | stream | Live funding-rate updates. | | `GetFundingRateHistory` | unary | Historical funding rates. Not yet served | | `GetFundingPayments` | unary | An account's funding payments. Not yet served | | `SubscribeFundingPayments` | stream | Live funding payments. Not yet served | ### Trader accounts — `AccountService` | RPC | Kind | What it does | | -------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `GetAccount` | unary | One trader account: spot collateral legs, perp positions, and cross-margin health. | | `SubscribeAccount` | stream | Snapshot on subscribe, then account and valuation updates (valuation-driven updates may be coalesced). | | `BatchGetAccounts` | unary | Several accounts by `TraderId`. | | `SubscribeAccounts` | stream | Live updates for a set of accounts; each message replaces the selected set. | | `ListAccounts` | unary | Trader accounts an authority (base58 pubkey) owns or is a delegate on. | | `ListDelegates` | unary | Wallet and passkey delegates registered on an account. | | `GetActionLimits` | unary | Max deposit / withdraw / lend / redeem / borrow / repay per collateral market, with the binding constraint. | | `SimulateAction` | unary | Simulates a collateral action (deposit, withdraw, lend, redeem, borrow, repay) and returns the post-state, without signing or submitting. Rate limited as a simulation. | | `GetFills` | unary | An account's perp fills. | | `SubscribeFills` | stream | Live perp fills for an account. | | `GetTradingFees` | unary | Fee rates that apply to an account. | | `GetTradingLimits` | unary | Per-account trading limits. Not yet served | | `GetPositionHistory` | unary | Perp position history. Not yet served | | `GetActivity` | unary | Archived collateral activity — deposits, withdrawals, lend/redeem, borrow/repay, liquidations, loss socialization. Needs the history archive; observed activity may have gaps and is not proof of settlement. | | `SubscribeActivity` | stream | Live activity only. Reconcile with `GetActivity` after a disconnect. | | `GetAccountHistory` | unary | Archived account value (NAV) and PnL history. | | `GetInterestHistory` | unary | Archived interest accrued on collateral legs. | | `GetRankedAccounts` | unary | Leaderboard ranking by metric and timeframe. | | `RenameAccount` | unary | Sets an account's display label. The request carries an ed25519 signature from the controlling key. | ### Collateral markets — `MarketService` Collateral is held in cross-margin spot markets that also lend and borrow. | RPC | Kind | What it does | | ------------------ | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ListMarkets` | unary | Every collateral market: asset metadata (mint, decimals), collateral weight, borrow margin rates, oracle price, APRs, utilisation, caps, flags, and oracle state. | | `GetMarket` | unary | One collateral market by `SpotId`. | | `SubscribeMarkets` | stream | One snapshot per market, then updates. | | `GetMarketHistory` | unary | Archived APR, utilisation, supply, and price history, bucketed. | ### Risk views — `RiskService` | RPC | Kind | What it does | | ------------------------- | ------ | ----------------------------------------------------------------------------------- | | `GetMarketOverview` | unary | Protocol- and market-level risk aggregates, including liquidation shock tables. | | `SimulateAccountRisk` | unary | Re-values an account under hypothetical price shocks. Rate limited as a simulation. | | `GetInsuranceFund` | unary | Insurance-fund balances. Rate limited as a simulation. | | `ListAccountsAtRisk` | unary | Accounts ordered by health ratio, lowest first. | | `SubscribeAccountsAtRisk` | stream | Full snapshot first, then deltas. | | `ListLargestAccounts` | unary | Accounts ordered by gross supplied assets. | ### Fee sponsorship — `SponsorshipService` Flint can pay the network fee for margin transactions. The sponsor is fee payer only; the transaction may invoke only the margin program, the compute-budget program, and the secp256r1 precompile. | RPC | Kind | What it does | | ---------------------- | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `GetSponsorshipPubkey` | unary | Base58 fee-payer key to put in the transaction's fee-payer slot. `UNAVAILABLE` when sponsorship isn't configured. | | `SponsorTransaction` | unary | Takes a wire-format `VersionedTransaction` with every signature except the sponsor's, co-signs, submits, and returns the signature **after confirmation**. Rate limited as a transaction. | ## `flint.margin.maker.v1` — authenticated ### Sessions — `AuthService` | RPC | Kind | What it does | | -------------- | ----- | ---------------------------------------------------------------------------------------------------------------------------- | | `Challenge` | unary | Issues a single-use nonce (30 s TTL) for a base58 public key. | | `Authenticate` | unary | Verifies a signature over `FLINT-MARGIN-AUTH-V1:` ‖ nonce and mints a bearer session (6 h TTL) scoped to one trader account. | | `Revoke` | unary | Revokes a session token. | The signing key must be the **owner** of a trader account or an active **`TRADE`- or `QUOTE`-scoped delegate** on it. `Authenticate` returns every `trader_ids` the key controls and selects one (`trader_id` in the request, or the lowest controlled id). The domain prefix differs from the spot API's, so a signed margin challenge can never be replayed as a spot login. `AuthService` is on both hosts. Authenticate against the **authed** host if you also need `TxService`. ### Maker reads — `MakerService` Every `MakerService` RPC is scoped to the session's selected trader account. | RPC | Kind | What it does | | -------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------ | | `SubscribeMarketSnapshots` | stream | Perp book snapshots annotated with your best resting quotes. | | `GetFills` | unary | Your maker fills, with markouts. | | `SubscribeFills` | stream | Live maker fills. | | `GetVolumeBreakdown` | unary | Your maker volume per perp over rolling windows. | | `GetVolumeSeries` | unary | Your maker volume as a time series. | | `GetMarkoutSummary` | unary | Whole-window markout summary, without `GetFills`' page limit. | | `GetQuotingState` | unary | Latest observed state of your on-chain quoting entries, including sequence numbers. | | `SubscribeQuotes` | stream | Live quoting updates, filtered by perp and quoter key. | | `ListRecentQuotes` | unary | Archived quoting updates for a time range, paged by cursor. | | `GetQuote` | unary | One quoting update by signature, instruction index, and update index. | | `GetStats` | unary | Your fill, transaction, oracle-update, and quote-update rates over 1 s, 1 m, and 5 m. | | `GetActivity` | unary | Archived actions by your trader account, newest first (at most 1000). | | `GetDecodedTransaction` | unary | One of your transactions by signature, decoded into Flint events. | | `ListQuotingAuthorities` | unary | Keys that opened sessions for your trader: open sessions, best RTT, regions, SDKs, last submit and landing, SOL balance. | | `ListQuoterSessions` | unary | Live and recent sessions, most recently seen first, with IP, SDK, RTT, and clock drift. | | `GetSdkCredentials` | unary | The read-only Kellnr registry token for the Rust SDK. Needs an organization (see [Authentication](/api#authentication)). | `GetFills`, `GetVolumeBreakdown`, `GetVolumeSeries`, `GetMarkoutSummary`, `ListRecentQuotes`, `GetQuote`, `GetActivity`, `GetDecodedTransaction`, and `GetSdkCredentials` read the history archive and return `FAILED_PRECONDITION` on a deployment without it. There is no maker balance RPC. Read collateral, positions, and health with the public `AccountService.GetAccount` or `SubscribeAccount`. ### Transactions — `TxService` (authed host only) | RPC | Kind | What it does | | -------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `SubmitTx` | unary | Submits a fully signed legacy, v0 or v1 transaction. The session's key must be one of its required signers. Returns on acceptance, not confirmation. | | `SubscribeTxStatus` | stream | Ack / landed / failed transitions for every transaction submitted under this session's trader. | | `SubscribeBlockhash` | stream | Recent blockhash, `last_valid_block_height`, and a priority-fee recommendation. | | `SubscribeSlots` | stream | Slot ticks. | | `SubscribeLeaders` | stream | Upcoming leader schedule with TPU addresses. | ### Session heartbeat — `SessionService` (authed host only) | RPC | Kind | What it does | | --- | --- | --- | | `Session` | bidi stream | The server sends a `Ping` every 30 s; reply at once with a `Pong` echoing its `seq`. Feeds the RTT and clock drift that `ListQuoterSessions` reports. | `Session` is bidirectional, so it needs HTTP/2 gRPC; gRPC-Web can't carry it. The Rust maker client answers it for you. ### Spread protection — `SpreadProtectService` (authed host only) | RPC | Kind | What it does | | ------------------------ | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `SubscribeSpreadProtect` | stream | A `reset_state` with the baseline `default_spread_mult`, then current and future `widen_window`s: an inclusive slot range and a `spread_mult` of at least 1.0. | The feed is advisory and the same for every maker. Nothing on-chain enforces it; widen your own quotes by the highest multiplier active at the current slot. ### Nodes — `NodeService` Public: no session needed, on both hosts. | RPC | Kind | What it does | | ------------- | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `GetNodeInfo` | unary | This node's build, proto revision, cluster, region, margin program id, leader proximity, and health. A node whose margin state is stale or not loaded reports itself degraded or halted. | | `ListNodes` | unary | Published endpoints, optionally for one cluster, with region and a `current` flag. Listing does not imply health. | ### Escalation — `EscalationService` (authed host only) | RPC | Kind | What it does | | ------------- | ----- | ---------------------------------------------------------------------------------------------------- | | `PageTheTeam` | unary | Pages Flint's on-call team with your message and returns a page id. Confirms dispatch, not response. | ## Authentication `flint.margin.user.v1` and `NodeService` need nothing. The rest of `flint.margin.maker.v1` expects: ``` authorization: Bearer ``` Mint the token with `AuthService.Challenge → sign → Authenticate` (the [Rust](/sdks/rust#authentication) and [TypeScript](/sdks/typescript#maker-client) SDKs wrap this as `AuthFlow`). | Service | Accepts | | --- | --- | | `MakerService` (except `GetSdkCredentials`) | A keypair session, or a Flint organization session or organization API key (`x-api-key: `) for an organization with perps access. | | `MakerService.GetSdkCredentials`, `EscalationService` | A keypair session whose trader belongs to an organization, or the organization credentials above. | | `TxService`, `SessionService`, `SpreadProtectService` | Keypair sessions only. | ## URL convention ``` POST https:///flint.margin.user.v1./ POST https:///flint.margin.maker.v1./ Content-Type: application/grpc (gRPC binary) application/grpc-web+proto (gRPC-Web, browsers) ``` No URL parameters and no versioning beyond the package name. ## Wire conventions * Money, prices, quantities, and rates are `Decimal { value: string }` — for example `"155.14"`. Never parse them as floats. See [Prices, sizes, units](/overview/units). * Timestamps are `Timestamp { micros: uint64 }`, Unix microseconds. * `PerpId` and `SpotId` are `uint32`; `TraderId` is `uint64`. * Many responses carry `DataProvenance` (commitment, slot, `observed_at`) — observation metadata, not a claim that the node is caught up with the chain. ## Rate limits The same per-key token-bucket limiter as the spot API applies. The key is your trader session on authenticated calls and your client IP otherwise. Going over budget returns `RESOURCE_EXHAUSTED`; back off exponentially and retry. | Class | Applies to | Default limit | | --- | --- | --- | | Reads | Every unary RPC not listed below | 50 requests/sec | | Simulations | `AccountService.SimulateAction`, `RiskService.SimulateAccountRisk`, `RiskService.GetInsuranceFund` | 5 requests/sec | | Transactions | `TxService.SubmitTx`, `SponsorshipService.SponsorTransaction` | 10 tx/sec **and** 300 tx/min | | Streams | Every streaming RPC, including `SessionService.Session` | 100 concurrent streams | A stream holds one slot for its lifetime rather than drawing on the per-second budget, so prefer one long-lived subscription to polling. Limits are configurable server-side. ## Schema downloads * [`margin-user.proto`](/margin-user.proto) — every service and message in `flint.margin.user.v1`, bundled into one file. * [`margin-maker.proto`](/margin-maker.proto) — the same for `flint.margin.maker.v1`. * [`margin-user.openapi.yaml`](/margin-user.openapi.yaml) ([JSON](/margin-user.openapi.json)) — OpenAPI 3.1 for the public user API's gRPC-Web URLs, for Postman, Insomnia, or client generators. * [`margin-maker.openapi.yaml`](/margin-maker.openapi.yaml) ([JSON](/margin-maker.openapi.json)) — the same for the authenticated maker API. * [`codama.json`](/codama.json) — [Codama](https://github.com/codama-idl/codama) IDL for the on-chain Flint margin program. See [On-chain clients](/sdks/onchain). The bundles are generated from the canonical per-file tree under `proto/flint/margin/` in the `flint-protos` repository. # Protocols & transports Every margin service is reachable over two wire protocols on the same URL: | Protocol | Content-Type | Best for | | --- | --- | --- | | gRPC (binary, HTTP/2) | `application/grpc` | Native clients (Rust, Go, Python, …) — the hot path. | | gRPC-Web (binary) | `application/grpc-web+proto` | Browsers and other HTTP/1.1 clients. | The server is tonic with `tonic_web`, so gRPC-Web is translated in process — there is no separate proxy. CORS allows any origin, method, and header, and exposes response headers, so browser apps can call the public host directly. There is no JSON / Connect-protocol endpoint; the OpenAPI file describes the gRPC-Web URLs for tooling, not a REST API. The Flint SDKs use: | SDK | Default transport | | --- | --- | | Rust (`flint-margin-user-client`, `flint-margin-maker-client`) | gRPC over HTTPS (HTTP/2) | | TypeScript (`@superis-labs/flint-margin-user-client`, `@superis-labs/flint-margin-maker-client`) | gRPC-Web binary over HTTPS | The one bidirectional RPC, the maker `SessionService.Session` heartbeat, needs HTTP/2 gRPC; gRPC-Web can't carry it. Everything else works on either transport. ## Request headers | Header | Sent by | Meaning | | --- | --- | --- | | `authorization: Bearer ` | You / SDK `AuthFlow` | Required on every maker service except `AuthService` and `NodeService`. Ignored by the public user services. | | `x-api-key: ` | You | Alternative credential for `MakerService` and `EscalationService`, for organizations with perps access. | | `x-flint-sdk` | SDK | SDK identity: `rust/` or `ts-web/`. | | `x-flint-proto-hash` | SDK | Fingerprint of the proto tree the SDK was generated from. | | `x-flint-proto-rev` | SDK | `flint-protos` git revision the SDK was generated from. | | `grpc-timeout` | You / SDK | Client deadline, standard gRPC encoding (e.g. `5000m` = 5 s). | The `x-flint-*` identity headers are diagnostics: the server logs them alongside each request so support can tell which SDK build made a call. They do not gate compatibility today. Hand-rolled clients may omit them. ## Response headers Every response — including rate-limit rejections — carries the server's identity in its headers (and, for gRPC, its trailers): | Header | Meaning | | --- | --- | | `x-flint-server-version` | Server release version. | | `x-flint-server-commit` | Server git commit. | | `x-flint-server-proto-hash` | Proto fingerprint the server was built from. | | `x-flint-server-proto-rev` | Proto revision the server was built from. | Comparing `x-flint-server-proto-hash` with the SDK's own `PROTO_HASH` constant is the quickest way to confirm a client and server agree on the schema. In TypeScript, `responseMetadata(err)` and `serverMetadataFromHeaders(headers)` read these for you. ## Deadlines and timeouts * **Server side.** The server bounds how long it spends producing a response and returns `DEADLINE_EXCEEDED` when that limit (15 s by default) is hit. * **Rust.** Channels built by the SDK — including the ones endpoint discovery dials — use a 5 s connect timeout and a 10 s per-request timeout, with HTTP/2 keep-alive pings every 30 s (also while idle) and TCP keep-alive every 60 s. Override per call with `tonic::Request::set_timeout`. * **TypeScript.** No timeout by default. Pass `timeoutMs` on a call (sent as `grpc-timeout`) or `signal` for cancellation; the `createXxxClient` factories in the user SDK also take a client-wide `timeoutMs`, which applies to that client's streams as well. Don't put a short deadline on a server stream: the deadline covers the whole call, so it would cut a healthy subscription. ## Streaming Every `Subscribe*` RPC is a long-lived server stream and is the recommended path for anything event-driven: * `MarketDataService.SubscribeOrderBook` / `SubscribeCandles` * `FundingService.SubscribeFundingRates` * `AccountService.SubscribeAccount` / `SubscribeAccounts` / `SubscribeFills` / `SubscribeActivity` * `MarketService.SubscribeMarkets` * `RiskService.SubscribeAccountsAtRisk` * `MakerService.SubscribeMarketSnapshots` / `SubscribeFills` / `SubscribeQuotes` * `TxService.SubscribeTxStatus` / `SubscribeBlockhash` / `SubscribeSlots` / `SubscribeLeaders` * `SpreadProtectService.SubscribeSpreadProtect` * `SessionService.Session` (bidirectional heartbeat) Things to know: * **Snapshot first.** Most streams open with a full snapshot (flagged `snapshot = true` where the message has the field), then send updates. On reconnect you get a fresh snapshot — replace local state rather than merging. * **Order book sequencing.** `SubscribeOrderBook` messages carry `sequence` and `previous_sequence`. If `previous_sequence` doesn't match the last `sequence` you applied, drop the book and resubscribe. * **Slow consumers are cut off.** If you fall behind the server's buffer, the stream ends with `DATA_LOSS` (account activity, fills, candles) or `RESOURCE_EXHAUSTED` (accounts at risk). Reconnect and reconcile against the matching unary RPC — `GetActivity`, `GetFills`, `GetCandles`, or `ListAccountsAtRisk`. * **Live-only streams.** `SubscribeActivity` and `SubscribeFills` do not replay history. Backfill with the unary RPC after a gap. * **Reconnect with backoff.** The TypeScript SDKs export `streamWithBackoff`, which re-opens a stream on a 500 ms → 30 s exponential schedule. The Rust maker client's `subscribe_*` methods return a `ResilientStream` that does the same, re-authenticating as needed. With a bare generated client, loop over the stream and reopen on error. Each open stream holds one of your 100 concurrent stream slots until it closes — see [Rate limits](/api#rate-limits). ## gRPC examples The SDKs are the supported clients. For quick checks from a shell, `grpcurl` works against the bundled protos. These commands use the running [local fixture](/makers/local-testing). For another network, use the operator's `host:port` values and omit `-plaintext` when the gateway uses TLS. Use a margin bearer session from that same gateway. ```sh # Public — perp market catalog. grpcurl -plaintext \ -proto margin-user.proto \ -d '{}' \ 127.0.0.1:15061 \ flint.margin.user.v1.PerpMarketService/ListPerpMarkets # Public — one trader account. grpcurl -plaintext \ -proto margin-user.proto \ -d '{"trader_id":{"id":"42"}}' \ 127.0.0.1:15061 \ flint.margin.user.v1.AccountService/GetAccount # Authenticated — pass the session token as metadata. grpcurl -plaintext \ -proto margin-maker.proto \ -H "authorization: Bearer ${FLINT_MARGIN_TOKEN}" \ -d '{}' \ 127.0.0.1:15062 \ flint.margin.maker.v1.MakerService/GetQuotingState # Public — this node's identity, program id, and health. grpcurl -plaintext \ -proto margin-maker.proto \ -d '{}' \ 127.0.0.1:15062 \ flint.margin.maker.v1.NodeService/GetNodeInfo ``` `grpcurl` takes JSON for convenience; `uint64` fields such as `trader_id.id` are strings in proto JSON. The request still goes over the wire as protobuf. # flint.margin.user.v1 reference ## Table of Contents * [flint/margin/user/v1/account/messages.proto](/api/user-reference#flint_margin_user_v1_account_messages-proto) * [AccountHealth](/api/user-reference#flint-margin-user-v1-AccountHealth) * [AccountHistoryPoint](/api/user-reference#flint-margin-user-v1-AccountHistoryPoint) * [AccountRef](/api/user-reference#flint-margin-user-v1-AccountRef) * [AccountSnapshot](/api/user-reference#flint-margin-user-v1-AccountSnapshot) * [ActionLimit](/api/user-reference#flint-margin-user-v1-ActionLimit) * [ActivityEntry](/api/user-reference#flint-margin-user-v1-ActivityEntry) * [BatchGetAccountsRequest](/api/user-reference#flint-margin-user-v1-BatchGetAccountsRequest) * [BatchGetAccountsResponse](/api/user-reference#flint-margin-user-v1-BatchGetAccountsResponse) * [DelegateInfo](/api/user-reference#flint-margin-user-v1-DelegateInfo) * [GetAccountHistoryRequest](/api/user-reference#flint-margin-user-v1-GetAccountHistoryRequest) * [GetAccountHistoryResponse](/api/user-reference#flint-margin-user-v1-GetAccountHistoryResponse) * [GetAccountRequest](/api/user-reference#flint-margin-user-v1-GetAccountRequest) * [GetAccountResponse](/api/user-reference#flint-margin-user-v1-GetAccountResponse) * [GetActionLimitsRequest](/api/user-reference#flint-margin-user-v1-GetActionLimitsRequest) * [GetActionLimitsResponse](/api/user-reference#flint-margin-user-v1-GetActionLimitsResponse) * [GetActivityRequest](/api/user-reference#flint-margin-user-v1-GetActivityRequest) * [GetActivityResponse](/api/user-reference#flint-margin-user-v1-GetActivityResponse) * [GetRankedAccountsRequest](/api/user-reference#flint-margin-user-v1-GetRankedAccountsRequest) * [GetRankedAccountsResponse](/api/user-reference#flint-margin-user-v1-GetRankedAccountsResponse) * [ListAccountsRequest](/api/user-reference#flint-margin-user-v1-ListAccountsRequest) * [ListAccountsResponse](/api/user-reference#flint-margin-user-v1-ListAccountsResponse) * [ListDelegatesRequest](/api/user-reference#flint-margin-user-v1-ListDelegatesRequest) * [ListDelegatesResponse](/api/user-reference#flint-margin-user-v1-ListDelegatesResponse) * [MarketActionLimits](/api/user-reference#flint-margin-user-v1-MarketActionLimits) * [RankedAccount](/api/user-reference#flint-margin-user-v1-RankedAccount) * [RenameAccountRequest](/api/user-reference#flint-margin-user-v1-RenameAccountRequest) * [RenameAccountResponse](/api/user-reference#flint-margin-user-v1-RenameAccountResponse) * [SimulateActionRequest](/api/user-reference#flint-margin-user-v1-SimulateActionRequest) * [SimulateActionResponse](/api/user-reference#flint-margin-user-v1-SimulateActionResponse) * [SpotPosition](/api/user-reference#flint-margin-user-v1-SpotPosition) * [SubscribeAccountRequest](/api/user-reference#flint-margin-user-v1-SubscribeAccountRequest) * [SubscribeAccountsRequest](/api/user-reference#flint-margin-user-v1-SubscribeAccountsRequest) * [SubscribeAccountsResponse](/api/user-reference#flint-margin-user-v1-SubscribeAccountsResponse) * [SubscribeActivityRequest](/api/user-reference#flint-margin-user-v1-SubscribeActivityRequest) * [AccountRankingMetric](/api/user-reference#flint-margin-user-v1-AccountRankingMetric) * [AccountRankingSize](/api/user-reference#flint-margin-user-v1-AccountRankingSize) * [AccountRankingTimeframe](/api/user-reference#flint-margin-user-v1-AccountRankingTimeframe) * [ActivityKind](/api/user-reference#flint-margin-user-v1-ActivityKind) * [DelegateCredentialKind](/api/user-reference#flint-margin-user-v1-DelegateCredentialKind) * [LimitConstraint](/api/user-reference#flint-margin-user-v1-LimitConstraint) * [SpotAction](/api/user-reference#flint-margin-user-v1-SpotAction) * [flint/margin/user/v1/account/service.proto](/api/user-reference#flint_margin_user_v1_account_service-proto) * [AccountService](/api/user-reference#flint-margin-user-v1-AccountService) * [flint/margin/user/v1/action/messages.proto](/api/user-reference#flint_margin_user_v1_action_messages-proto) * [ActionDetail](/api/user-reference#flint-margin-user-v1-ActionDetail) * [ActionPreviewRow](/api/user-reference#flint-margin-user-v1-ActionPreviewRow) * [AssetAction](/api/user-reference#flint-margin-user-v1-AssetAction) * [CancelOrderAction](/api/user-reference#flint-margin-user-v1-CancelOrderAction) * [ClosePositionAction](/api/user-reference#flint-margin-user-v1-ClosePositionAction) * [PlaceOrderAction](/api/user-reference#flint-margin-user-v1-PlaceOrderAction) * [PreviewActionsRequest](/api/user-reference#flint-margin-user-v1-PreviewActionsRequest) * [PreviewActionsResponse](/api/user-reference#flint-margin-user-v1-PreviewActionsResponse) * [SettlePnlAction](/api/user-reference#flint-margin-user-v1-SettlePnlAction) * [TraderAction](/api/user-reference#flint-margin-user-v1-TraderAction) * [TransferAction](/api/user-reference#flint-margin-user-v1-TransferAction) * [ActionOrderSide](/api/user-reference#flint-margin-user-v1-ActionOrderSide) * [ActionOrderType](/api/user-reference#flint-margin-user-v1-ActionOrderType) * [ActionPreviewStatus](/api/user-reference#flint-margin-user-v1-ActionPreviewStatus) * [ActionTimeInForce](/api/user-reference#flint-margin-user-v1-ActionTimeInForce) * [flint/margin/user/v1/action/service.proto](/api/user-reference#flint_margin_user_v1_action_service-proto) * [ActionService](/api/user-reference#flint-margin-user-v1-ActionService) * [flint/margin/user/v1/common.proto](/api/user-reference#flint_margin_user_v1_common-proto) * [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) * [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) * [HistoryCoverage](/api/user-reference#flint-margin-user-v1-HistoryCoverage) * [SpotId](/api/user-reference#flint-margin-user-v1-SpotId) * [SpotMetadata](/api/user-reference#flint-margin-user-v1-SpotMetadata) * [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) * [TraderId](/api/user-reference#flint-margin-user-v1-TraderId) * [Commitment](/api/user-reference#flint-margin-user-v1-Commitment) * [flint/margin/user/v1/funding/messages.proto](/api/user-reference#flint_margin_user_v1_funding_messages-proto) * [FundingPayment](/api/user-reference#flint-margin-user-v1-FundingPayment) * [FundingRate](/api/user-reference#flint-margin-user-v1-FundingRate) * [GetFundingPaymentsRequest](/api/user-reference#flint-margin-user-v1-GetFundingPaymentsRequest) * [GetFundingPaymentsResponse](/api/user-reference#flint-margin-user-v1-GetFundingPaymentsResponse) * [GetFundingRateHistoryRequest](/api/user-reference#flint-margin-user-v1-GetFundingRateHistoryRequest) * [GetFundingRateHistoryResponse](/api/user-reference#flint-margin-user-v1-GetFundingRateHistoryResponse) * [GetFundingRatesRequest](/api/user-reference#flint-margin-user-v1-GetFundingRatesRequest) * [GetFundingRatesResponse](/api/user-reference#flint-margin-user-v1-GetFundingRatesResponse) * [SubscribeFundingPaymentsRequest](/api/user-reference#flint-margin-user-v1-SubscribeFundingPaymentsRequest) * [SubscribeFundingPaymentsResponse](/api/user-reference#flint-margin-user-v1-SubscribeFundingPaymentsResponse) * [SubscribeFundingRatesRequest](/api/user-reference#flint-margin-user-v1-SubscribeFundingRatesRequest) * [SubscribeFundingRatesResponse](/api/user-reference#flint-margin-user-v1-SubscribeFundingRatesResponse) * [flint/margin/user/v1/funding/service.proto](/api/user-reference#flint_margin_user_v1_funding_service-proto) * [FundingService](/api/user-reference#flint-margin-user-v1-FundingService) * [flint/margin/user/v1/history/messages.proto](/api/user-reference#flint_margin_user_v1_history_messages-proto) * [GetInterestHistoryRequest](/api/user-reference#flint-margin-user-v1-GetInterestHistoryRequest) * [GetInterestHistoryResponse](/api/user-reference#flint-margin-user-v1-GetInterestHistoryResponse) * [GetMarketHistoryRequest](/api/user-reference#flint-margin-user-v1-GetMarketHistoryRequest) * [GetMarketHistoryResponse](/api/user-reference#flint-margin-user-v1-GetMarketHistoryResponse) * [InterestHistoryPoint](/api/user-reference#flint-margin-user-v1-InterestHistoryPoint) * [MarketHistoryPoint](/api/user-reference#flint-margin-user-v1-MarketHistoryPoint) * [flint/margin/user/v1/market/messages.proto](/api/user-reference#flint_margin_user_v1_market_messages-proto) * [FlowCap](/api/user-reference#flint-margin-user-v1-FlowCap) * [GetMarketRequest](/api/user-reference#flint-margin-user-v1-GetMarketRequest) * [GetMarketResponse](/api/user-reference#flint-margin-user-v1-GetMarketResponse) * [InterestRateCurve](/api/user-reference#flint-margin-user-v1-InterestRateCurve) * [ListMarketsRequest](/api/user-reference#flint-margin-user-v1-ListMarketsRequest) * [ListMarketsResponse](/api/user-reference#flint-margin-user-v1-ListMarketsResponse) * [MarketFlags](/api/user-reference#flint-margin-user-v1-MarketFlags) * [MarketInfo](/api/user-reference#flint-margin-user-v1-MarketInfo) * [OracleInfo](/api/user-reference#flint-margin-user-v1-OracleInfo) * [OracleSource](/api/user-reference#flint-margin-user-v1-OracleSource) * [SubscribeMarketsRequest](/api/user-reference#flint-margin-user-v1-SubscribeMarketsRequest) * [OracleProvider](/api/user-reference#flint-margin-user-v1-OracleProvider) * [flint/margin/user/v1/market/service.proto](/api/user-reference#flint_margin_user_v1_market_service-proto) * [MarketService](/api/user-reference#flint-margin-user-v1-MarketService) * [flint/margin/user/v1/market_data/messages.proto](/api/user-reference#flint_margin_user_v1_market_data_messages-proto) * [Candle](/api/user-reference#flint-margin-user-v1-Candle) * [GetCandlesRequest](/api/user-reference#flint-margin-user-v1-GetCandlesRequest) * [GetCandlesResponse](/api/user-reference#flint-margin-user-v1-GetCandlesResponse) * [GetOrderBookRequest](/api/user-reference#flint-margin-user-v1-GetOrderBookRequest) * [GetOrderBookResponse](/api/user-reference#flint-margin-user-v1-GetOrderBookResponse) * [GetTradesRequest](/api/user-reference#flint-margin-user-v1-GetTradesRequest) * [GetTradesResponse](/api/user-reference#flint-margin-user-v1-GetTradesResponse) * [OrderBookLevel](/api/user-reference#flint-margin-user-v1-OrderBookLevel) * [OrderBookSnapshot](/api/user-reference#flint-margin-user-v1-OrderBookSnapshot) * [PublicTrade](/api/user-reference#flint-margin-user-v1-PublicTrade) * [SubscribeCandlesRequest](/api/user-reference#flint-margin-user-v1-SubscribeCandlesRequest) * [SubscribeCandlesResponse](/api/user-reference#flint-margin-user-v1-SubscribeCandlesResponse) * [SubscribeOrderBookRequest](/api/user-reference#flint-margin-user-v1-SubscribeOrderBookRequest) * [SubscribeOrderBookResponse](/api/user-reference#flint-margin-user-v1-SubscribeOrderBookResponse) * [SubscribeTradesRequest](/api/user-reference#flint-margin-user-v1-SubscribeTradesRequest) * [SubscribeTradesResponse](/api/user-reference#flint-margin-user-v1-SubscribeTradesResponse) * [CandleInterval](/api/user-reference#flint-margin-user-v1-CandleInterval) * [CandlePriceSource](/api/user-reference#flint-margin-user-v1-CandlePriceSource) * [flint/margin/user/v1/market_data/service.proto](/api/user-reference#flint_margin_user_v1_market_data_service-proto) * [MarketDataService](/api/user-reference#flint-margin-user-v1-MarketDataService) * [flint/margin/user/v1/notifications/messages.proto](/api/user-reference#flint_margin_user_v1_notifications_messages-proto) * [RequestEmailVerificationRequest](/api/user-reference#flint-margin-user-v1-notifications-RequestEmailVerificationRequest) * [RequestEmailVerificationResponse](/api/user-reference#flint-margin-user-v1-notifications-RequestEmailVerificationResponse) * [Settings](/api/user-reference#flint-margin-user-v1-notifications-Settings) * [VerifyEmailRequest](/api/user-reference#flint-margin-user-v1-notifications-VerifyEmailRequest) * [flint/margin/user/v1/notifications/service.proto](/api/user-reference#flint_margin_user_v1_notifications_service-proto) * [NotificationService](/api/user-reference#flint-margin-user-v1-notifications-NotificationService) * [flint/margin/user/v1/order/messages.proto](/api/user-reference#flint_margin_user_v1_order_messages-proto) * [GetOrderRequest](/api/user-reference#flint-margin-user-v1-GetOrderRequest) * [GetOrderResponse](/api/user-reference#flint-margin-user-v1-GetOrderResponse) * [ListOrdersRequest](/api/user-reference#flint-margin-user-v1-ListOrdersRequest) * [ListOrdersResponse](/api/user-reference#flint-margin-user-v1-ListOrdersResponse) * [Order](/api/user-reference#flint-margin-user-v1-Order) * [PreviewOrder](/api/user-reference#flint-margin-user-v1-PreviewOrder) * [PreviewOrderRequest](/api/user-reference#flint-margin-user-v1-PreviewOrderRequest) * [PreviewOrderResponse](/api/user-reference#flint-margin-user-v1-PreviewOrderResponse) * [SubscribeOrdersRequest](/api/user-reference#flint-margin-user-v1-SubscribeOrdersRequest) * [SubscribeOrdersResponse](/api/user-reference#flint-margin-user-v1-SubscribeOrdersResponse) * [OrderStatus](/api/user-reference#flint-margin-user-v1-OrderStatus) * [OrderType](/api/user-reference#flint-margin-user-v1-OrderType) * [TimeInForce](/api/user-reference#flint-margin-user-v1-TimeInForce) * [flint/margin/user/v1/order/service.proto](/api/user-reference#flint_margin_user_v1_order_service-proto) * [OrderService](/api/user-reference#flint-margin-user-v1-OrderService) * [flint/margin/user/v1/perp/messages.proto](/api/user-reference#flint_margin_user_v1_perp_messages-proto) * [Fill](/api/user-reference#flint-margin-user-v1-Fill) * [GetFillsRequest](/api/user-reference#flint-margin-user-v1-GetFillsRequest) * [GetFillsResponse](/api/user-reference#flint-margin-user-v1-GetFillsResponse) * [GetPositionHistoryRequest](/api/user-reference#flint-margin-user-v1-GetPositionHistoryRequest) * [GetPositionHistoryResponse](/api/user-reference#flint-margin-user-v1-GetPositionHistoryResponse) * [GetTradingFeesRequest](/api/user-reference#flint-margin-user-v1-GetTradingFeesRequest) * [GetTradingFeesResponse](/api/user-reference#flint-margin-user-v1-GetTradingFeesResponse) * [GetTradingLimitsRequest](/api/user-reference#flint-margin-user-v1-GetTradingLimitsRequest) * [GetTradingLimitsResponse](/api/user-reference#flint-margin-user-v1-GetTradingLimitsResponse) * [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) * [PerpPnlHistory](/api/user-reference#flint-margin-user-v1-PerpPnlHistory) * [PerpPnlHistoryPoint](/api/user-reference#flint-margin-user-v1-PerpPnlHistoryPoint) * [PerpPosition](/api/user-reference#flint-margin-user-v1-PerpPosition) * [PositionHistoryEntry](/api/user-reference#flint-margin-user-v1-PositionHistoryEntry) * [SubscribeFillsRequest](/api/user-reference#flint-margin-user-v1-SubscribeFillsRequest) * [TradingFees](/api/user-reference#flint-margin-user-v1-TradingFees) * [TradingLimits](/api/user-reference#flint-margin-user-v1-TradingLimits) * [PerpPositionStatus](/api/user-reference#flint-margin-user-v1-PerpPositionStatus) * [TradeSide](/api/user-reference#flint-margin-user-v1-TradeSide) * [flint/margin/user/v1/perp_market/messages.proto](/api/user-reference#flint_margin_user_v1_perp_market_messages-proto) * [GetMarketHistoryStatsRequest](/api/user-reference#flint-margin-user-v1-GetMarketHistoryStatsRequest) * [GetMarketHistoryStatsResponse](/api/user-reference#flint-margin-user-v1-GetMarketHistoryStatsResponse) * [GetMarketStatsRequest](/api/user-reference#flint-margin-user-v1-GetMarketStatsRequest) * [GetMarketStatsResponse](/api/user-reference#flint-margin-user-v1-GetMarketStatsResponse) * [GetPerpMarketRequest](/api/user-reference#flint-margin-user-v1-GetPerpMarketRequest) * [GetPerpMarketResponse](/api/user-reference#flint-margin-user-v1-GetPerpMarketResponse) * [GetVolumeBreakdownRequest](/api/user-reference#flint-margin-user-v1-GetVolumeBreakdownRequest) * [GetVolumeBreakdownResponse](/api/user-reference#flint-margin-user-v1-GetVolumeBreakdownResponse) * [GetVolumeSeriesRequest](/api/user-reference#flint-margin-user-v1-GetVolumeSeriesRequest) * [GetVolumeSeriesResponse](/api/user-reference#flint-margin-user-v1-GetVolumeSeriesResponse) * [ListPerpMarketsRequest](/api/user-reference#flint-margin-user-v1-ListPerpMarketsRequest) * [ListPerpMarketsResponse](/api/user-reference#flint-margin-user-v1-ListPerpMarketsResponse) * [MarginTier](/api/user-reference#flint-margin-user-v1-MarginTier) * [MarketStats](/api/user-reference#flint-margin-user-v1-MarketStats) * [PerpMarket](/api/user-reference#flint-margin-user-v1-PerpMarket) * [PerpMarketHistoryStats](/api/user-reference#flint-margin-user-v1-PerpMarketHistoryStats) * [PerpVolume](/api/user-reference#flint-margin-user-v1-PerpVolume) * [SubscribeMarketStatsRequest](/api/user-reference#flint-margin-user-v1-SubscribeMarketStatsRequest) * [SubscribeMarketStatsResponse](/api/user-reference#flint-margin-user-v1-SubscribeMarketStatsResponse) * [VolumeBucket](/api/user-reference#flint-margin-user-v1-VolumeBucket) * [VolumeWindow](/api/user-reference#flint-margin-user-v1-VolumeWindow) * [PerpMarketStatus](/api/user-reference#flint-margin-user-v1-PerpMarketStatus) * [StatsWindow](/api/user-reference#flint-margin-user-v1-StatsWindow) * [flint/margin/user/v1/perp_market/service.proto](/api/user-reference#flint_margin_user_v1_perp_market_service-proto) * [PerpMarketService](/api/user-reference#flint-margin-user-v1-PerpMarketService) * [flint/margin/user/v1/risk/messages.proto](/api/user-reference#flint_margin_user_v1_risk_messages-proto) * [AccountAtRisk](/api/user-reference#flint-margin-user-v1-AccountAtRisk) * [AssetPriceShock](/api/user-reference#flint-margin-user-v1-AssetPriceShock) * [GetInsuranceFundRequest](/api/user-reference#flint-margin-user-v1-GetInsuranceFundRequest) * [GetInsuranceFundResponse](/api/user-reference#flint-margin-user-v1-GetInsuranceFundResponse) * [GetMarketOverviewRequest](/api/user-reference#flint-margin-user-v1-GetMarketOverviewRequest) * [GetMarketOverviewResponse](/api/user-reference#flint-margin-user-v1-GetMarketOverviewResponse) * [InsuranceFund](/api/user-reference#flint-margin-user-v1-InsuranceFund) * [InsuranceFundBalance](/api/user-reference#flint-margin-user-v1-InsuranceFundBalance) * [LiquidationShockRow](/api/user-reference#flint-margin-user-v1-LiquidationShockRow) * [ListAccountsAtRiskRequest](/api/user-reference#flint-margin-user-v1-ListAccountsAtRiskRequest) * [ListAccountsAtRiskResponse](/api/user-reference#flint-margin-user-v1-ListAccountsAtRiskResponse) * [ListLargestAccountsRequest](/api/user-reference#flint-margin-user-v1-ListLargestAccountsRequest) * [ListLargestAccountsResponse](/api/user-reference#flint-margin-user-v1-ListLargestAccountsResponse) * [MarketRiskOverview](/api/user-reference#flint-margin-user-v1-MarketRiskOverview) * [MonitoredAccountAtRisk](/api/user-reference#flint-margin-user-v1-MonitoredAccountAtRisk) * [ProtocolRiskOverview](/api/user-reference#flint-margin-user-v1-ProtocolRiskOverview) * [SimulateAccountRiskRequest](/api/user-reference#flint-margin-user-v1-SimulateAccountRiskRequest) * [SimulateAccountRiskResponse](/api/user-reference#flint-margin-user-v1-SimulateAccountRiskResponse) * [SubscribeAccountsAtRiskRequest](/api/user-reference#flint-margin-user-v1-SubscribeAccountsAtRiskRequest) * [SubscribeAccountsAtRiskResponse](/api/user-reference#flint-margin-user-v1-SubscribeAccountsAtRiskResponse) * [MonitoredAccountStatus](/api/user-reference#flint-margin-user-v1-MonitoredAccountStatus) * [flint/margin/user/v1/risk/service.proto](/api/user-reference#flint_margin_user_v1_risk_service-proto) * [RiskService](/api/user-reference#flint-margin-user-v1-RiskService) * [flint/margin/user/v1/sponsorship/messages.proto](/api/user-reference#flint_margin_user_v1_sponsorship_messages-proto) * [GetSponsorshipPubkeyRequest](/api/user-reference#flint-margin-user-v1-GetSponsorshipPubkeyRequest) * [GetSponsorshipPubkeyResponse](/api/user-reference#flint-margin-user-v1-GetSponsorshipPubkeyResponse) * [SponsorTransactionRequest](/api/user-reference#flint-margin-user-v1-SponsorTransactionRequest) * [SponsorTransactionResponse](/api/user-reference#flint-margin-user-v1-SponsorTransactionResponse) * [flint/margin/user/v1/sponsorship/service.proto](/api/user-reference#flint_margin_user_v1_sponsorship_service-proto) * [SponsorshipService](/api/user-reference#flint-margin-user-v1-SponsorshipService) * [Scalar Value Types](/api/user-reference#scalar-value-types)

Top

## flint/margin/user/v1/account/messages.proto ### AccountHealth Cross-margin account health in USD. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | net_equity | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | initial_margin | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Required to borrow or withdraw collateral. | | maintenance_margin | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | net_exposure | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | meets_initial_margin | [bool](/api/user-reference#bool) | | | | meets_maintenance_margin | [bool](/api/user-reference#bool) | | | | free_collateral | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | net_equity minus initial_margin, floored at zero. | | deficit | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | maintenance_margin minus net_equity, floored at zero. | | health_ratio | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | net_equity / maintenance_margin; unset when maintenance margin is zero. | | can_open_position | [bool](/api/user-reference#bool) | | Requires initial margin and positive equity. | | can_be_backstopped | [bool](/api/user-reference#bool) | | Whether a backstopper may take over the account's legs. | ### AccountHistoryPoint One archived account-value sample. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | ts | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | Bucket start; uses its newest sample. | | observed_at | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | Server observation time, not block time. | | slot | [uint64](/api/user-reference#uint64) | | Highest observed tip; may not increase between points. | | nav_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Unweighted signed account value. | | pnl_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | nav_usd minus deposits; estimated and unset without complete ledger coverage. | | net_exposure | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | debt_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | health_ratio | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | net_equity / maintenance_margin; unset when maintenance margin is zero. | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | ### AccountRef | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | trader_id | [TraderId](/api/user-reference#flint-margin-user-v1-TraderId) | | | | owner | [string](/api/user-reference#string) | | | | is_delegate | [bool](/api/user-reference#bool) | | True when the requested authority is a delegate rather than the owner. | | address | [string](/api/user-reference#string) | | | | name | [string](/api/user-reference#string) | | World-readable display label, including when is_name_public is false; empty when unset. | | is_name_public | [bool](/api/user-reference#bool) | | Whether the label is shown on rankings; defaults to false, not an access-control flag. | ### AccountSnapshot | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | trader_id | [TraderId](/api/user-reference#flint-margin-user-v1-TraderId) | | | | owner | [string](/api/user-reference#string) | | | | positions | [SpotPosition](/api/user-reference#flint-margin-user-v1-SpotPosition) | repeated | | | health | [AccountHealth](/api/user-reference#flint-margin-user-v1-AccountHealth) | | | | slot | [uint64](/api/user-reference#uint64) | | | | ts | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | address | [string](/api/user-reference#string) | | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | | perp_positions | [PerpPosition](/api/user-reference#flint-margin-user-v1-PerpPosition) | repeated | | | perp_positions_supported | [bool](/api/user-reference#bool) | | False when combined spot/perp health is unavailable. | ### ActionLimit | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | amount | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Token units; zero when unavailable. | | constraint | [LimitConstraint](/api/user-reference#flint-margin-user-v1-LimitConstraint) | | | ### ActivityEntry | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | kind | [ActivityKind](/api/user-reference#flint-margin-user-v1-ActivityKind) | | | | spot_id | [SpotId](/api/user-reference#flint-margin-user-v1-SpotId) | | | | slot | [uint64](/api/user-reference#uint64) | | | | ts | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | Server observation time, not block time. | | signature | [string](/api/user-reference#string) | | | | amount | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Unsigned token units moved; unset when unknown. | | value_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Estimated USD value at ingestion; unset without a usable price. | | liquidation | [bool](/api/user-reference#bool) | | True when this account was the liquidator. | | event_id | [string](/api/user-reference#string) | | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | | perp_settlement | [bool](/api/user-reference#bool) | | | ### BatchGetAccountsRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | trader_ids | [TraderId](/api/user-reference#flint-margin-user-v1-TraderId) | repeated | Required; at most 100 unique IDs. Results preserve request order. | ### BatchGetAccountsResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | accounts | [AccountSnapshot](/api/user-reference#flint-margin-user-v1-AccountSnapshot) | repeated | | | missing_trader_ids | [TraderId](/api/user-reference#flint-margin-user-v1-TraderId) | repeated | No local valued snapshot; does not prove on-chain absence. | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | ### DelegateInfo | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | address | [string](/api/user-reference#string) | | | | credential_kind | [DelegateCredentialKind](/api/user-reference#flint-margin-user-v1-DelegateCredentialKind) | | | | authority | [string](/api/user-reference#string) | | | | scope_bits | [uint32](/api/user-reference#uint32) | | | | expires_at | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | active | [bool](/api/user-reference#bool) | | | ### GetAccountHistoryRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | trader_id | [TraderId](/api/user-reference#flint-margin-user-v1-TraderId) | | | | start | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | Inclusive; unset selects 24 hours before end. | | end | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | Exclusive; unset selects now. | | resolution_seconds | [uint32](/api/user-reference#uint32) | | Zero lets the server choose; non-zero is exact or INVALID_ARGUMENT. | | include_perp_pnl | [bool](/api/user-reference#bool) | | | ### GetAccountHistoryResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | points | [AccountHistoryPoint](/api/user-reference#flint-margin-user-v1-AccountHistoryPoint) | repeated | Oldest first; empty buckets are omitted. | | resolution_seconds | [uint32](/api/user-reference#uint32) | | Actual bucket width. | | coverage | [HistoryCoverage](/api/user-reference#flint-margin-user-v1-HistoryCoverage) | | | | perp_pnl | [PerpPnlHistory](/api/user-reference#flint-margin-user-v1-PerpPnlHistory) | | | ### GetAccountRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | trader_id | [TraderId](/api/user-reference#flint-margin-user-v1-TraderId) | | | ### GetAccountResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | account | [AccountSnapshot](/api/user-reference#flint-margin-user-v1-AccountSnapshot) | | | ### GetActionLimitsRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | trader_id | [TraderId](/api/user-reference#flint-margin-user-v1-TraderId) | | | | spot_ids | [SpotId](/api/user-reference#flint-margin-user-v1-SpotId) | repeated | Empty selects every listed market. | ### GetActionLimitsResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | markets | [MarketActionLimits](/api/user-reference#flint-margin-user-v1-MarketActionLimits) | repeated | | | slot | [uint64](/api/user-reference#uint64) | | | | ts | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | ### GetActivityRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | trader_id | [TraderId](/api/user-reference#flint-margin-user-v1-TraderId) | | | | start | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | Inclusive; unset selects the whole ledger. | | end | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | Exclusive; unset selects now. | | limit | [uint32](/api/user-reference#uint32) | | Zero uses the default (50); maximum 1000. | | kinds | [ActivityKind](/api/user-reference#flint-margin-user-v1-ActivityKind) | repeated | Empty selects every kind; UNSPECIFIED values are ignored. | | page_token | [string](/api/user-reference#string) | | | ### GetActivityResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | activities | [ActivityEntry](/api/user-reference#flint-margin-user-v1-ActivityEntry) | repeated | Descending by slot, signature, instruction index, then event index; pagination is not a snapshot. | | next_page_token | [string](/api/user-reference#string) | | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | | coverage | [HistoryCoverage](/api/user-reference#flint-margin-user-v1-HistoryCoverage) | | | ### GetRankedAccountsRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | metric | [AccountRankingMetric](/api/user-reference#flint-margin-user-v1-AccountRankingMetric) | | All metrics sort descending except CLOSEST_TO_LIQUIDATION. | | timeframe | [AccountRankingTimeframe](/api/user-reference#flint-margin-user-v1-AccountRankingTimeframe) | | Historical windows are rolling 24h, 7d, or 30d completed hours. Current-state rankings ignore this field and leave response windows unset. | | minimum_account_value | [AccountRankingSize](/api/user-reference#flint-margin-user-v1-AccountRankingSize) | | Minimum unweighted USD economic NAV. | | limit | [uint32](/api/user-reference#uint32) | | Zero uses the default (20); maximum 100. Results are not paginated. | ### GetRankedAccountsResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | accounts | [RankedAccount](/api/user-reference#flint-margin-user-v1-RankedAccount) | repeated | | | as_of | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | Timestamp at which the ranking was built. | | window_start | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | window_end | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | | history_complete | [bool](/api/user-reference#bool) | | Always false while processed archive history may have gaps. | ### ListAccountsRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | authority | [string](/api/user-reference#string) | | Base58 owner or delegate pubkey. | ### ListAccountsResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | accounts | [AccountRef](/api/user-reference#flint-margin-user-v1-AccountRef) | repeated | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | ### ListDelegatesRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | trader_id | [TraderId](/api/user-reference#flint-margin-user-v1-TraderId) | | | | include_inactive | [bool](/api/user-reference#bool) | | | ### ListDelegatesResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | delegates | [DelegateInfo](/api/user-reference#flint-margin-user-v1-DelegateInfo) | repeated | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | ### MarketActionLimits Per-market action limits at the response state; they may change before submission. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | spot_id | [SpotId](/api/user-reference#flint-margin-user-v1-SpotId) | | | | deposit | [ActionLimit](/api/user-reference#flint-margin-user-v1-ActionLimit) | | | | withdraw | [ActionLimit](/api/user-reference#flint-margin-user-v1-ActionLimit) | | | | lend | [ActionLimit](/api/user-reference#flint-margin-user-v1-ActionLimit) | | | | redeem | [ActionLimit](/api/user-reference#flint-margin-user-v1-ActionLimit) | | | | borrow | [ActionLimit](/api/user-reference#flint-margin-user-v1-ActionLimit) | | | | repay | [ActionLimit](/api/user-reference#flint-margin-user-v1-ActionLimit) | | | ### RankedAccount | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | trader_id | [TraderId](/api/user-reference#flint-margin-user-v1-TraderId) | | | | owner | [string](/api/user-reference#string) | | Live identity and statistics may be absent for historical-only accounts. | | address | [string](/api/user-reference#string) | | | | account_value_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Unweighted USD economic NAV. | | metric_value | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | rank | [uint32](/api/user-reference#uint32) | | | | open_interest_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Current statistics are independent of the historical ranking window. | | leverage | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Gross perp notional / positive economic NAV; absent otherwise. | | health_ratio | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | open_position_count | [uint32](/api/user-reference#uint32) | optional | Number of perp positions with nonzero quantity. | | account_equity_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Net equity from current account health, including collateral weights. | | performance_start | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | First observation used for performance; all-time begins with eligible NAV history. | | name | [string](/api/user-reference#string) | | Only public labels are returned; empty when unset or private. | ### RenameAccountRequest The signed bytes are UTF-8, no trailing newline, fields in this order. Insert the is_public line only when the field is present, using lowercase true or false. Explicit false signs the line; omission leaves it out. The server verifies only that layout. flint:rename-account\n expires_at=\\n trader_id=\\n is_public=\\n name=\ | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | trader_id | [TraderId](/api/user-reference#flint-margin-user-v1-TraderId) | | | | authority | [string](/api/user-reference#string) | | Base58 signing owner or delegate pubkey. | | name | [string](/api/user-reference#string) | | Empty clears the label. Trimmed; maximum 32 characters. Sign the trimmed value. | | expires_at | [uint64](/api/user-reference#uint64) | | Unix seconds, at most 120 seconds ahead. | | signature | [bytes](/api/user-reference#bytes) | | | | is_public | [bool](/api/user-reference#bool) | optional | Omission resets leaderboard visibility to false, including for an already-public label. | ### RenameAccountResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | name | [string](/api/user-reference#string) | | | | is_public | [bool](/api/user-reference#bool) | | | ### SimulateActionRequest Simulates an action without signing or submitting it. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | trader_id | [TraderId](/api/user-reference#flint-margin-user-v1-TraderId) | | | | spot_id | [SpotId](/api/user-reference#flint-margin-user-v1-SpotId) | | | | action | [SpotAction](/api/user-reference#flint-margin-user-v1-SpotAction) | | | | amount | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Token units; excess asset precision is rejected. | | authority | [string](/api/user-reference#string) | | Base58 owner or registered delegate pubkey used for simulation. | | create_token_account | [bool](/api/user-reference#bool) | | Creates the signer's token account; rejected for actions that move no tokens. | | wrap_lamports | [uint64](/api/user-reference#uint64) | | Lamports to wrap before a wrapped-SOL deposit; zero disables wrapping. | ### SimulateActionResponse Simulation result and post-state. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | error | [string](/api/user-reference#string) | | Empty on success; otherwise the node's transaction error. | | logs | [string](/api/user-reference#string) | repeated | | | compute_units | [uint64](/api/user-reference#uint64) | | Minimum compute-unit limit for the simulated transaction. | | account | [AccountSnapshot](/api/user-reference#flint-margin-user-v1-AccountSnapshot) | | Simulated post-state; unset on error. | | ts | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | program_error | [string](/api/user-reference#string) | | Stable margin-program error name; empty for failures from other programs. | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | ### SpotPosition One spot leg. Amounts are token units. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | spot_id | [SpotId](/api/user-reference#flint-margin-user-v1-SpotId) | | | | raw_deposit | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Idle collateral that earns no interest. | | lent | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | borrowed | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | value_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Unweighted USD value. | | deposit_shares | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Raw pool share counts. | | borrow_shares | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | weighted_collateral_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | USD collateral after the deposit-weight haircut; zero for a net borrow. | | debt_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | USD debt; zero for a net deposit. | | initial_margin_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | maintenance_margin_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | liquidation_price | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | USD token price at maintenance margin; unset when no single-asset move can reach it. | ### SubscribeAccountRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | trader_id | [TraderId](/api/user-reference#flint-margin-user-v1-TraderId) | | | ### SubscribeAccountsRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | trader_ids | [TraderId](/api/user-reference#flint-margin-user-v1-TraderId) | repeated | | ### SubscribeAccountsResponse Each message replaces the selected account set; intermediate states may coalesce. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | accounts | [AccountSnapshot](/api/user-reference#flint-margin-user-v1-AccountSnapshot) | repeated | | | missing_trader_ids | [TraderId](/api/user-reference#flint-margin-user-v1-TraderId) | repeated | No local valued snapshot; does not prove on-chain absence. | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | ### SubscribeActivityRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | trader_id | [TraderId](/api/user-reference#flint-margin-user-v1-TraderId) | | | | kinds | [ActivityKind](/api/user-reference#flint-margin-user-v1-ActivityKind) | repeated | | ### AccountRankingMetric | Name | Number | Description | | ---- | ------ | ----------- | | ACCOUNT_RANKING_METRIC_UNSPECIFIED | 0 | | | ACCOUNT_RANKING_METRIC_PNL | 1 | Signed USD NAV change minus external cash flows. | | ACCOUNT_RANKING_METRIC_PNL_PERCENT | 2 | Linked hourly Modified Dietz returns, in percent (10 means 10%). | | ACCOUNT_RANKING_METRIC_VOLUME | 3 | Sum over completed-hour buckets in the selected timeframe. | | ACCOUNT_RANKING_METRIC_ACCOUNT_VALUE | 4 | | | ACCOUNT_RANKING_METRIC_OPEN_INTEREST | 5 | | | ACCOUNT_RANKING_METRIC_CLOSEST_TO_LIQUIDATION | 6 | Health ratio ascending; accounts without usable prices are excluded. | | ACCOUNT_RANKING_METRIC_LARGEST_LIQUIDATION | 7 | Largest single liquidation, not the sum, over completed-hour buckets. | | ACCOUNT_RANKING_METRIC_SHARPE_RATIO | 8 | Annualized hourly Sharpe, zero risk-free rate; requires 24 returns. | ### AccountRankingSize | Name | Number | Description | | ---- | ------ | ----------- | | ACCOUNT_RANKING_SIZE_UNSPECIFIED | 0 | No minimum account value. | | ACCOUNT_RANKING_SIZE_USD_100 | 1 | | | ACCOUNT_RANKING_SIZE_USD_1000 | 2 | | | ACCOUNT_RANKING_SIZE_USD_10000 | 3 | | | ACCOUNT_RANKING_SIZE_USD_100000 | 4 | | | ACCOUNT_RANKING_SIZE_USD_1000000 | 5 | | ### AccountRankingTimeframe | Name | Number | Description | | ---- | ------ | ----------- | | ACCOUNT_RANKING_TIMEFRAME_UNSPECIFIED | 0 | | | ACCOUNT_RANKING_TIMEFRAME_DAY | 1 | | | ACCOUNT_RANKING_TIMEFRAME_WEEK | 2 | | | ACCOUNT_RANKING_TIMEFRAME_MONTH | 3 | | | ACCOUNT_RANKING_TIMEFRAME_ALL_TIME | 4 | All observed history. | ### ActivityKind | Name | Number | Description | | ---- | ------ | ----------- | | ACTIVITY_KIND_UNSPECIFIED | 0 | | | ACTIVITY_KIND_DEPOSIT | 1 | | | ACTIVITY_KIND_WITHDRAW | 2 | | | ACTIVITY_KIND_LEND | 3 | | | ACTIVITY_KIND_REDEEM | 4 | | | ACTIVITY_KIND_BORROW | 5 | | | ACTIVITY_KIND_REPAY | 6 | | | ACTIVITY_KIND_SOCIALIZE_LOSS | 7 | Bad debt written off this account. | ### DelegateCredentialKind | Name | Number | Description | | ---- | ------ | ----------- | | DELEGATE_CREDENTIAL_KIND_UNSPECIFIED | 0 | | | DELEGATE_CREDENTIAL_KIND_ED25519 | 1 | | | DELEGATE_CREDENTIAL_KIND_PASSKEY | 2 | | ### LimitConstraint Constraint on an action amount. | Name | Number | Description | | ---- | ------ | ----------- | | LIMIT_CONSTRAINT_UNSPECIFIED | 0 | | | LIMIT_CONSTRAINT_BALANCE | 1 | The available balance; `deposit` uses uint64 maximum because wallet balance is unknown. | | LIMIT_CONSTRAINT_INITIAL_MARGIN | 2 | | | LIMIT_CONSTRAINT_POOL_LIQUIDITY | 3 | | | LIMIT_CONSTRAINT_UTILISATION_THROTTLE | 4 | | | LIMIT_CONSTRAINT_MARKET_CAP | 5 | | | LIMIT_CONSTRAINT_FLOW_CAP | 6 | | | LIMIT_CONSTRAINT_OPERATION_DISABLED | 7 | For borrow, the redeem flag also applies. | | LIMIT_CONSTRAINT_POSITION_LIMIT | 8 | A new position would exceed the account's spot-position limit. | ### SpotAction | Name | Number | Description | | ---- | ------ | ----------- | | SPOT_ACTION_UNSPECIFIED | 0 | | | SPOT_ACTION_DEPOSIT | 1 | | | SPOT_ACTION_WITHDRAW | 2 | Debits only the idle deposit and never borrows; must leave initial margin met. | | SPOT_ACTION_LEND | 3 | | | SPOT_ACTION_REDEEM | 4 | | | SPOT_ACTION_BORROW | 5 | | | SPOT_ACTION_REPAY | 6 | |

Top

## flint/margin/user/v1/account/service.proto ### AccountService | Method Name | Request Type | Response Type | Description | | ----------- | ------------ | ------------- | ------------| | BatchGetAccounts | [BatchGetAccountsRequest](/api/user-reference#flint-margin-user-v1-BatchGetAccountsRequest) | [BatchGetAccountsResponse](/api/user-reference#flint-margin-user-v1-BatchGetAccountsResponse) | | | SubscribeAccounts | [SubscribeAccountsRequest](/api/user-reference#flint-margin-user-v1-SubscribeAccountsRequest) | [SubscribeAccountsResponse](/api/user-reference#flint-margin-user-v1-SubscribeAccountsResponse) stream | | | ListDelegates | [ListDelegatesRequest](/api/user-reference#flint-margin-user-v1-ListDelegatesRequest) | [ListDelegatesResponse](/api/user-reference#flint-margin-user-v1-ListDelegatesResponse) | | | SubscribeActivity | [SubscribeActivityRequest](/api/user-reference#flint-margin-user-v1-SubscribeActivityRequest) | [ActivityEntry](/api/user-reference#flint-margin-user-v1-ActivityEntry) stream | | | GetInterestHistory | [GetInterestHistoryRequest](/api/user-reference#flint-margin-user-v1-GetInterestHistoryRequest) | [GetInterestHistoryResponse](/api/user-reference#flint-margin-user-v1-GetInterestHistoryResponse) | | | GetPositionHistory | [GetPositionHistoryRequest](/api/user-reference#flint-margin-user-v1-GetPositionHistoryRequest) | [GetPositionHistoryResponse](/api/user-reference#flint-margin-user-v1-GetPositionHistoryResponse) | | | GetFills | [GetFillsRequest](/api/user-reference#flint-margin-user-v1-GetFillsRequest) | [GetFillsResponse](/api/user-reference#flint-margin-user-v1-GetFillsResponse) | | | SubscribeFills | [SubscribeFillsRequest](/api/user-reference#flint-margin-user-v1-SubscribeFillsRequest) | [Fill](/api/user-reference#flint-margin-user-v1-Fill) stream | | | GetTradingLimits | [GetTradingLimitsRequest](/api/user-reference#flint-margin-user-v1-GetTradingLimitsRequest) | [GetTradingLimitsResponse](/api/user-reference#flint-margin-user-v1-GetTradingLimitsResponse) | | | GetTradingFees | [GetTradingFeesRequest](/api/user-reference#flint-margin-user-v1-GetTradingFeesRequest) | [GetTradingFeesResponse](/api/user-reference#flint-margin-user-v1-GetTradingFeesResponse) | | | ListAccounts | [ListAccountsRequest](/api/user-reference#flint-margin-user-v1-ListAccountsRequest) | [ListAccountsResponse](/api/user-reference#flint-margin-user-v1-ListAccountsResponse) | | | GetRankedAccounts | [GetRankedAccountsRequest](/api/user-reference#flint-margin-user-v1-GetRankedAccountsRequest) | [GetRankedAccountsResponse](/api/user-reference#flint-margin-user-v1-GetRankedAccountsResponse) | | | GetAccount | [GetAccountRequest](/api/user-reference#flint-margin-user-v1-GetAccountRequest) | [GetAccountResponse](/api/user-reference#flint-margin-user-v1-GetAccountResponse) | | | SubscribeAccount | [SubscribeAccountRequest](/api/user-reference#flint-margin-user-v1-SubscribeAccountRequest) | [AccountSnapshot](/api/user-reference#flint-margin-user-v1-AccountSnapshot) stream | | | GetActionLimits | [GetActionLimitsRequest](/api/user-reference#flint-margin-user-v1-GetActionLimitsRequest) | [GetActionLimitsResponse](/api/user-reference#flint-margin-user-v1-GetActionLimitsResponse) | | | SimulateAction | [SimulateActionRequest](/api/user-reference#flint-margin-user-v1-SimulateActionRequest) | [SimulateActionResponse](/api/user-reference#flint-margin-user-v1-SimulateActionResponse) | | | GetActivity | [GetActivityRequest](/api/user-reference#flint-margin-user-v1-GetActivityRequest) | [GetActivityResponse](/api/user-reference#flint-margin-user-v1-GetActivityResponse) | | | GetAccountHistory | [GetAccountHistoryRequest](/api/user-reference#flint-margin-user-v1-GetAccountHistoryRequest) | [GetAccountHistoryResponse](/api/user-reference#flint-margin-user-v1-GetAccountHistoryResponse) | | | RenameAccount | [RenameAccountRequest](/api/user-reference#flint-margin-user-v1-RenameAccountRequest) | [RenameAccountResponse](/api/user-reference#flint-margin-user-v1-RenameAccountResponse) | |

Top

## flint/margin/user/v1/action/messages.proto ### ActionDetail | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | label | [string](/api/user-reference#string) | | For example "Est. fill". | | value | [string](/api/user-reference#string) | | For example "102.31". | ### ActionPreviewRow | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | index | [uint32](/api/user-reference#uint32) | | The action's index in the request. | | step | [uint32](/api/user-reference#uint32) | | When it runs: 1 deposits; 2 cancels, settles, lends, repays and borrows; 3 orders and closes; 4 withdrawals, transfers and redeems. Within a step the request's order is kept. | | title | [string](/api/user-reference#string) | | The sentence the trading app shows for this action. | | status | [ActionPreviewStatus](/api/user-reference#flint-margin-user-v1-ActionPreviewStatus) | | | | details | [ActionDetail](/api/user-reference#flint-margin-user-v1-ActionDetail) | repeated | | | warnings | [string](/api/user-reference#string) | repeated | | | blocked_reason | [string](/api/user-reference#string) | | | ### AssetAction | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | asset | [string](/api/user-reference#string) | | | | amount | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | ### CancelOrderAction Set target: unset is INVALID_ARGUMENT. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | market | [string](/api/user-reference#string) | | | | order_id | [string](/api/user-reference#string) | | `<perpId>:<buy|sell>:<priceTicks>`, as the trading app names resting orders. | | all | [bool](/api/user-reference#bool) | | Every resting order on the market. Must be true; false is INVALID_ARGUMENT. Resting orders aren't indexed by the server: whether a single order still rests is checked by the app when the link opens. | ### ClosePositionAction | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | market | [string](/api/user-reference#string) | | | | fraction | [uint32](/api/user-reference#uint32) | | Percent of the position to close at market: 25, 50, 75 or 100, else INVALID_ARGUMENT. | | max_slippage_bps | [uint32](/api/user-reference#uint32) | optional | 1 to 1000, as for a market order. Absent is 500, written into the link. | ### PlaceOrderAction | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | market | [string](/api/user-reference#string) | | | | side | [ActionOrderSide](/api/user-reference#flint-margin-user-v1-ActionOrderSide) | | | | size | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | A multiple of the market's lot size; never rounded. | | order_type | [ActionOrderType](/api/user-reference#flint-margin-user-v1-ActionOrderType) | | | | limit_price | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Limit orders only, and required for them. A multiple of the market's tick. | | max_slippage_bps | [uint32](/api/user-reference#uint32) | optional | Market orders only: 1 to 1000, else INVALID_ARGUMENT. Absent is 500. The link always carries the value, so the app enforces the band checked here. | | time_in_force | [ActionTimeInForce](/api/user-reference#flint-margin-user-v1-ActionTimeInForce) | | Limit orders only; on a market order, anything but UNSPECIFIED is INVALID_ARGUMENT. | | reduce_only | [bool](/api/user-reference#bool) | | | | post_only | [bool](/api/user-reference#bool) | | Limit GTC orders only, else INVALID_ARGUMENT. | ### PreviewActionsRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | trader_id | [TraderId](/api/user-reference#flint-margin-user-v1-TraderId) | | The account the actions are for. Its owner reviews and signs them. | | actions | [TraderAction](/api/user-reference#flint-margin-user-v1-TraderAction) | repeated | 1 to 10 actions. | | expires_in_secs | [uint32](/api/user-reference#uint32) | optional | How long the link stays valid. Absent is 600; 1 to 900, else INVALID_ARGUMENT. | ### PreviewActionsResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | rows | [ActionPreviewRow](/api/user-reference#flint-margin-user-v1-ActionPreviewRow) | repeated | One per action, in the order they run. | | link | [string](/api/user-reference#string) | | The trading app's review page with every action. Empty when any action is BLOCKED, or when no trading app is deployed for this cluster; warnings say which. | | expires_at | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | slot | [uint64](/api/user-reference#uint64) | | The slot the checks read. | | warnings | [string](/api/user-reference#string) | repeated | About the batch as a whole: why there is no link, and that actions were checked alone. | ### SettlePnlAction Set target: unset is INVALID_ARGUMENT. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | market | [string](/api/user-reference#string) | | | | all | [bool](/api/user-reference#bool) | | Every market with pending PnL. Must be true; false is INVALID_ARGUMENT. | ### TraderAction One trader action. Markets are perp symbols as the trading app shows them ("SOL-USDC-PERP"), or a base ("SOL") when only one market trades it. Assets are token names ("USDC"); mints are also accepted. Sizes are base units, amounts token units, and prices quote prices, all as decimal strings. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | place_order | [PlaceOrderAction](/api/user-reference#flint-margin-user-v1-PlaceOrderAction) | | | | close_position | [ClosePositionAction](/api/user-reference#flint-margin-user-v1-ClosePositionAction) | | | | cancel_order | [CancelOrderAction](/api/user-reference#flint-margin-user-v1-CancelOrderAction) | | | | settle_pnl | [SettlePnlAction](/api/user-reference#flint-margin-user-v1-SettlePnlAction) | | | | deposit | [AssetAction](/api/user-reference#flint-margin-user-v1-AssetAction) | | | | withdraw | [AssetAction](/api/user-reference#flint-margin-user-v1-AssetAction) | | Pays only the account owner's wallet. | | transfer | [TransferAction](/api/user-reference#flint-margin-user-v1-TransferAction) | | | | lend | [AssetAction](/api/user-reference#flint-margin-user-v1-AssetAction) | | | | redeem | [AssetAction](/api/user-reference#flint-margin-user-v1-AssetAction) | | | | borrow | [AssetAction](/api/user-reference#flint-margin-user-v1-AssetAction) | | | | repay | [AssetAction](/api/user-reference#flint-margin-user-v1-AssetAction) | | | ### TransferAction | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | asset | [string](/api/user-reference#string) | | | | amount | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | to_trader_id | [TraderId](/api/user-reference#flint-margin-user-v1-TraderId) | | Another account with the same owner. | ### ActionOrderSide UNSPECIFIED is INVALID_ARGUMENT. | Name | Number | Description | | ---- | ------ | ----------- | | ACTION_ORDER_SIDE_UNSPECIFIED | 0 | | | ACTION_ORDER_SIDE_LONG | 1 | | | ACTION_ORDER_SIDE_SHORT | 2 | | ### ActionOrderType UNSPECIFIED is INVALID_ARGUMENT. | Name | Number | Description | | ---- | ------ | ----------- | | ACTION_ORDER_TYPE_UNSPECIFIED | 0 | | | ACTION_ORDER_TYPE_MARKET | 1 | Fill or kill within max_slippage_bps of the mark the trader reviews. | | ACTION_ORDER_TYPE_LIMIT | 2 | | ### ActionPreviewStatus | Name | Number | Description | | ---- | ------ | ----------- | | ACTION_PREVIEW_STATUS_UNSPECIFIED | 0 | | | ACTION_PREVIEW_STATUS_READY | 1 | | | ACTION_PREVIEW_STATUS_WARNING | 2 | Can be approved; see warnings. | | ACTION_PREVIEW_STATUS_BLOCKED | 3 | Can't be approved; see blocked_reason. Any BLOCKED row means no link. | ### ActionTimeInForce | Name | Number | Description | | ---- | ------ | ----------- | | ACTION_TIME_IN_FORCE_UNSPECIFIED | 0 | GTC. | | ACTION_TIME_IN_FORCE_GTC | 1 | | | ACTION_TIME_IN_FORCE_IOC | 2 | | | ACTION_TIME_IN_FORCE_FOK | 3 | |

Top

## flint/margin/user/v1/action/service.proto ### ActionService | Method Name | Request Type | Response Type | Description | | ----------- | ------------ | ------------- | ------------| | PreviewActions | [PreviewActionsRequest](/api/user-reference#flint-margin-user-v1-PreviewActionsRequest) | [PreviewActionsResponse](/api/user-reference#flint-margin-user-v1-PreviewActionsResponse) | Checks a batch of trader actions (orders, closes, cancels, settles, deposits, withdrawals, transfers, lending) for one margin account and returns a link to the trading app's review page, where the account owner reviews, excludes and signs them. Builds, signs, and sends nothing: the link carries intent only and grants nothing, and the app checks everything again before signing. Each action is checked alone against the account as it stands now; balances and positions are not chained through the batch. A deposit that funds an order, or two withdrawals that together overdraw, can each come back READY. Actions run in steps, keeping the request's order within one: 1 deposits; 2 cancels, settles, lends, repays and borrows; 3 orders and closes; 4 withdrawals, transfers and redeems. Malformed input is INVALID_ARGUMENT, naming `actions[i].field`; problems with live state are BLOCKED rows. If any row is BLOCKED there is no link: fix or drop those actions and preview again. The link is also empty, with a warning, when no trading app is deployed for this cluster. |

Top

## flint/margin/user/v1/common.proto ### DataProvenance Observation metadata, not a claim that the node is caught up with the chain. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | commitment | [Commitment](/api/user-reference#flint-margin-user-v1-Commitment) | | | | observed_at | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | slot | [uint64](/api/user-reference#uint64) | | | | revision | [uint64](/api/user-reference#uint64) | optional | | | fork_reconciled | [bool](/api/user-reference#bool) | | | ### Decimal Decimal string (e.g. "155.14"), parsed with rust_decimal-compatible semantics. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | value | [string](/api/user-reference#string) | | | ### HistoryCoverage Effective query window \[start, end). Complete only when coverage is proven. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | start | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | end | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | complete | [bool](/api/user-reference#bool) | | | | reason | [string](/api/user-reference#string) | | | ### SpotId On-chain spot-asset id (u32 on-chain, unlike flint.spot.v1's u64). | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | id | [uint32](/api/user-reference#uint32) | | | ### SpotMetadata | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | id | [SpotId](/api/user-reference#flint-margin-user-v1-SpotId) | | | | name | [string](/api/user-reference#string) | | | | mint | [string](/api/user-reference#string) | | | | decimals | [uint32](/api/user-reference#uint32) | | | | token_program | [string](/api/user-reference#string) | | Base58 mint owner program (SPL Token or Token-2022). | | icon | [string](/api/user-reference#string) | | | | product_id | [uint64](/api/user-reference#uint64) | optional | On-chain catalog product ID; separate from `id` and unset on older servers. | ### Timestamp Unix microseconds since epoch. Zero means unspecified. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | micros | [uint64](/api/user-reference#uint64) | | | ### TraderId On-chain margin-account ID chosen by its creator. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | id | [uint64](/api/user-reference#uint64) | | | ### Commitment | Name | Number | Description | | ---- | ------ | ----------- | | COMMITMENT_UNSPECIFIED | 0 | | | COMMITMENT_PROCESSED | 1 | | | COMMITMENT_CONFIRMED | 2 | | | COMMITMENT_FINALIZED | 3 | |

Top

## flint/margin/user/v1/funding/messages.proto ### FundingPayment | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | payment_id | [string](/api/user-reference#string) | | | | trader_id | [TraderId](/api/user-reference#flint-margin-user-v1-TraderId) | | | | perp_id | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | | | | amount_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Positive is a credit to the account; negative is a debit. | | position_quantity | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | funding_rate | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | funded_at | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | ### FundingRate | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_id | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | | | | rate | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Fraction per interval_seconds; positive means longs pay shorts. | | funding_at | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | next_funding_at | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | interval_seconds | [uint64](/api/user-reference#uint64) | | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | ### GetFundingPaymentsRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | trader_id | [TraderId](/api/user-reference#flint-margin-user-v1-TraderId) | | | | perp_ids | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | repeated | | | start | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | end | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | page_size | [uint32](/api/user-reference#uint32) | | | | page_token | [string](/api/user-reference#string) | | | ### GetFundingPaymentsResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | payments | [FundingPayment](/api/user-reference#flint-margin-user-v1-FundingPayment) | repeated | | | next_page_token | [string](/api/user-reference#string) | | | | coverage | [HistoryCoverage](/api/user-reference#flint-margin-user-v1-HistoryCoverage) | | | ### GetFundingRateHistoryRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_ids | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | repeated | | | start | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | end | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | page_size | [uint32](/api/user-reference#uint32) | | | | page_token | [string](/api/user-reference#string) | | | | bucket_seconds | [uint32](/api/user-reference#uint32) | | 0 returns every sample. Otherwise returns only each perp's last sample in every epoch-aligned bucket_seconds window of funding_at. | ### GetFundingRateHistoryResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | rates | [FundingRate](/api/user-reference#flint-margin-user-v1-FundingRate) | repeated | | | next_page_token | [string](/api/user-reference#string) | | | | coverage | [HistoryCoverage](/api/user-reference#flint-margin-user-v1-HistoryCoverage) | | | ### GetFundingRatesRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_ids | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | repeated | | ### GetFundingRatesResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | rates | [FundingRate](/api/user-reference#flint-margin-user-v1-FundingRate) | repeated | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | ### SubscribeFundingPaymentsRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | trader_id | [TraderId](/api/user-reference#flint-margin-user-v1-TraderId) | | | | perp_ids | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | repeated | | ### SubscribeFundingPaymentsResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | payments | [FundingPayment](/api/user-reference#flint-margin-user-v1-FundingPayment) | repeated | | | snapshot | [bool](/api/user-reference#bool) | | | | sequence | [uint64](/api/user-reference#uint64) | | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | ### SubscribeFundingRatesRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_ids | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | repeated | | ### SubscribeFundingRatesResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | rates | [FundingRate](/api/user-reference#flint-margin-user-v1-FundingRate) | repeated | | | snapshot | [bool](/api/user-reference#bool) | | | | sequence | [uint64](/api/user-reference#uint64) | | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | |

Top

## flint/margin/user/v1/funding/service.proto ### FundingService | Method Name | Request Type | Response Type | Description | | ----------- | ------------ | ------------- | ------------| | GetFundingRates | [GetFundingRatesRequest](/api/user-reference#flint-margin-user-v1-GetFundingRatesRequest) | [GetFundingRatesResponse](/api/user-reference#flint-margin-user-v1-GetFundingRatesResponse) | | | SubscribeFundingRates | [SubscribeFundingRatesRequest](/api/user-reference#flint-margin-user-v1-SubscribeFundingRatesRequest) | [SubscribeFundingRatesResponse](/api/user-reference#flint-margin-user-v1-SubscribeFundingRatesResponse) stream | | | GetFundingRateHistory | [GetFundingRateHistoryRequest](/api/user-reference#flint-margin-user-v1-GetFundingRateHistoryRequest) | [GetFundingRateHistoryResponse](/api/user-reference#flint-margin-user-v1-GetFundingRateHistoryResponse) | | | GetFundingPayments | [GetFundingPaymentsRequest](/api/user-reference#flint-margin-user-v1-GetFundingPaymentsRequest) | [GetFundingPaymentsResponse](/api/user-reference#flint-margin-user-v1-GetFundingPaymentsResponse) | | | SubscribeFundingPayments | [SubscribeFundingPaymentsRequest](/api/user-reference#flint-margin-user-v1-SubscribeFundingPaymentsRequest) | [SubscribeFundingPaymentsResponse](/api/user-reference#flint-margin-user-v1-SubscribeFundingPaymentsResponse) stream | |

Top

## flint/margin/user/v1/history/messages.proto ### GetInterestHistoryRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | trader_id | [TraderId](/api/user-reference#flint-margin-user-v1-TraderId) | | | | spot_ids | [SpotId](/api/user-reference#flint-margin-user-v1-SpotId) | repeated | Empty selects every observed spot asset. | | start | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | Inclusive. Unset / zero-micros selects 24 hours before end. | | end | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | Exclusive. Unset / zero-micros selects now. | | resolution_seconds | [uint32](/api/user-reference#uint32) | | Zero lets the server choose a bucket width that fits its point limit. | ### GetInterestHistoryResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | points | [InterestHistoryPoint](/api/user-reference#flint-margin-user-v1-InterestHistoryPoint) | repeated | | | resolution_seconds | [uint32](/api/user-reference#uint32) | | | | coverage | [HistoryCoverage](/api/user-reference#flint-margin-user-v1-HistoryCoverage) | | | ### GetMarketHistoryRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | spot_id | [SpotId](/api/user-reference#flint-margin-user-v1-SpotId) | | | | start | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | Inclusive. Unset / zero-micros selects 24 hours before end. | | end | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | Exclusive. Unset / zero-micros selects now. | | resolution_seconds | [uint32](/api/user-reference#uint32) | | Zero lets the server choose a bucket width that fits its point limit. | ### GetMarketHistoryResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | points | [MarketHistoryPoint](/api/user-reference#flint-margin-user-v1-MarketHistoryPoint) | repeated | | | resolution_seconds | [uint32](/api/user-reference#uint32) | | | | coverage | [HistoryCoverage](/api/user-reference#flint-margin-user-v1-HistoryCoverage) | | | ### InterestHistoryPoint | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | spot_id | [SpotId](/api/user-reference#flint-margin-user-v1-SpotId) | | | | ts | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | Start of the requested bucket. | | earned | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Token interest earned; unset when the ledger cannot establish it. | | paid | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Token interest paid; unset when the ledger cannot establish it. | | unavailable_reason | [string](/api/user-reference#string) | | Why one or both values are absent. Empty only when both are known. | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | ### MarketHistoryPoint | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | ts | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | Start of the requested bucket. Empty buckets are omitted. | | observed_at | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | Newest coherent observation in the bucket. | | slot | [uint64](/api/user-reference#uint64) | | | | deposit_apr | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | borrow_apr | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | utilisation | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | vault_utilisation | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | total_deposits | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | total_borrows | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | total_isolated_deposit | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | deposit_share_price | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | borrow_share_price | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | oracle_price | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | protocol_fee | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | | available_liquidity | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | |

Top

## flint/margin/user/v1/market/messages.proto ### FlowCap Per-market net-flow limiter. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | deposit_capped | [bool](/api/user-reference#bool) | | | | withdraw_capped | [bool](/api/user-reference#bool) | | | | max_deposit | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Net-inflow ceiling in token units; unset when uncapped. | | max_withdraw | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Net-outflow ceiling, token units. Unset when uncapped. | | deposit_headroom | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Current token-unit headroom; unset when uncapped. | | withdraw_headroom | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | ### GetMarketRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | spot_id | [SpotId](/api/user-reference#flint-margin-user-v1-SpotId) | | | ### GetMarketResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | market | [MarketInfo](/api/user-reference#flint-margin-user-v1-MarketInfo) | | | ### InterestRateCurve Live piecewise-linear borrow-rate curve in utilisation. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | target_utilisation | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Curve kink; bounded on-chain to \[0.05, 0.95]. | | apr_at_zero | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | apr_at_target | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | apr_at_full | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | ### ListMarketsRequest ### ListMarketsResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | markets | [MarketInfo](/api/user-reference#flint-margin-user-v1-MarketInfo) | repeated | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | ### MarketFlags Operations currently accepted by the market. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | deposit_enabled | [bool](/api/user-reference#bool) | | | | withdraw_enabled | [bool](/api/user-reference#bool) | | | | lend_enabled | [bool](/api/user-reference#bool) | | | | borrow_enabled | [bool](/api/user-reference#bool) | | | | redeem_enabled | [bool](/api/user-reference#bool) | | | ### MarketInfo Lending-market state. Token amounts are token units. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | asset | [SpotMetadata](/api/user-reference#flint-margin-user-v1-SpotMetadata) | | | | deposit_weight | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Deposit-value haircut for collateral. | | borrow_imf | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | borrow_mmf | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | oracle_price | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | USD per token unit. | | deposit_share_price | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Token units per pool share. | | borrow_share_price | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | total_deposits | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Total supplied token units, including isolated deposits. | | total_borrows | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | utilisation | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | total_borrows / total_deposits; distinct from vault_utilisation. | | deposit_apr | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Nominal annual rates at current utilisation. | | borrow_apr | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | slot | [uint64](/api/user-reference#uint64) | | | | ts | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | liquidation_bonus | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Collateral bonus per unit of debt repaid by a liquidator. | | lending_rate_fee | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Protocol share of interest, already reflected in deposit_apr. | | protocol_fee | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Unpaid protocol interest in token units. | | throttle_borrow_utilisation | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Vault utilisation at or above which the chain rejects a new borrow. | | throttle_redeem_utilisation | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Vault utilisation at which the chain rejects redeeming lent deposits. | | deposit_limit | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Total-asset ceiling in token units; unset when uncapped. | | borrow_limit | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Pool-borrow ceiling in token units; unset when uncapped. | | flags | [MarketFlags](/api/user-reference#flint-margin-user-v1-MarketFlags) | | | | flow_cap | [FlowCap](/api/user-reference#flint-margin-user-v1-FlowCap) | | | | total_isolated_deposit | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Idle, unlent deposits in token units. | | vault_utilisation | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | total_borrows / lent pool; capped at 1. | | available_liquidity | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Current borrow/redeem headroom in token units. | | total_deposit_shares | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Raw pool-share totals. | | total_borrow_shares | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | last_accrual_at | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | Interest-accrual time; share prices and APRs are as of this time. | | rate_curve | [InterestRateCurve](/api/user-reference#flint-margin-user-v1-InterestRateCurve) | | | | address | [string](/api/user-reference#string) | | | | vault | [string](/api/user-reference#string) | | | | oracle | [OracleInfo](/api/user-reference#flint-margin-user-v1-OracleInfo) | | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | ### OracleInfo Market oracle source and cache freshness. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | last_synced_at | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | valid_until | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | On-chain reads after this time are rejected. | | oracle_id | [uint32](/api/user-reference#uint32) | | Registry oracle ID; markets with the same ID share a price. | | sources | [OracleSource](/api/user-reference#flint-margin-user-v1-OracleSource) | repeated | Configured slots in order; empty until indexed. | ### OracleSource One oracle source slot. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | provider | [OracleProvider](/api/user-reference#flint-margin-user-v1-OracleProvider) | | | | feed | [string](/api/user-reference#string) | | Base58 source account. | ### SubscribeMarketsRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | spot_ids | [SpotId](/api/user-reference#flint-margin-user-v1-SpotId) | repeated | Empty selects every listed market. | ### OracleProvider | Name | Number | Description | | ---- | ------ | ----------- | | ORACLE_PROVIDER_UNSPECIFIED | 0 | | | ORACLE_PROVIDER_DEV | 1 | | | ORACLE_PROVIDER_PYTH | 2 | | | ORACLE_PROVIDER_CHAINLINK | 3 | |

Top

## flint/margin/user/v1/market/service.proto ### MarketService Public catalog and live state of lending markets. | Method Name | Request Type | Response Type | Description | | ----------- | ------------ | ------------- | ------------| | GetMarket | [GetMarketRequest](/api/user-reference#flint-margin-user-v1-GetMarketRequest) | [GetMarketResponse](/api/user-reference#flint-margin-user-v1-GetMarketResponse) | | | GetMarketHistory | [GetMarketHistoryRequest](/api/user-reference#flint-margin-user-v1-GetMarketHistoryRequest) | [GetMarketHistoryResponse](/api/user-reference#flint-margin-user-v1-GetMarketHistoryResponse) | | | ListMarkets | [ListMarketsRequest](/api/user-reference#flint-margin-user-v1-ListMarketsRequest) | [ListMarketsResponse](/api/user-reference#flint-margin-user-v1-ListMarketsResponse) | | | SubscribeMarkets | [SubscribeMarketsRequest](/api/user-reference#flint-margin-user-v1-SubscribeMarketsRequest) | [MarketInfo](/api/user-reference#flint-margin-user-v1-MarketInfo) stream | |

Top

## flint/margin/user/v1/market_data/messages.proto ### Candle | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_id | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | | | | interval | [CandleInterval](/api/user-reference#flint-margin-user-v1-CandleInterval) | | | | price_source | [CandlePriceSource](/api/user-reference#flint-margin-user-v1-CandlePriceSource) | | | | opened_at | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | closed_at | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | open | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | high | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | low | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | close | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | volume | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | trade_count | [uint64](/api/user-reference#uint64) | | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | ### GetCandlesRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_id | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | | | | interval | [CandleInterval](/api/user-reference#flint-margin-user-v1-CandleInterval) | | | | price_source | [CandlePriceSource](/api/user-reference#flint-margin-user-v1-CandlePriceSource) | | | | start | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | end | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | page_size | [uint32](/api/user-reference#uint32) | | | | page_token | [string](/api/user-reference#string) | | | ### GetCandlesResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | candles | [Candle](/api/user-reference#flint-margin-user-v1-Candle) | repeated | | | next_page_token | [string](/api/user-reference#string) | | | | coverage | [HistoryCoverage](/api/user-reference#flint-margin-user-v1-HistoryCoverage) | | | ### GetOrderBookRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_id | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | | | | depth | [uint32](/api/user-reference#uint32) | | | ### GetOrderBookResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | order_book | [OrderBookSnapshot](/api/user-reference#flint-margin-user-v1-OrderBookSnapshot) | | | ### GetTradesRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_ids | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | repeated | | | start | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | end | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | page_size | [uint32](/api/user-reference#uint32) | | | | page_token | [string](/api/user-reference#string) | | | ### GetTradesResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | trades | [PublicTrade](/api/user-reference#flint-margin-user-v1-PublicTrade) | repeated | | | next_page_token | [string](/api/user-reference#string) | | | | coverage | [HistoryCoverage](/api/user-reference#flint-margin-user-v1-HistoryCoverage) | | | ### OrderBookLevel | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | price | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | quantity | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | order_count | [uint64](/api/user-reference#uint64) | | | ### OrderBookSnapshot | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_id | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | | | | bids | [OrderBookLevel](/api/user-reference#flint-margin-user-v1-OrderBookLevel) | repeated | | | asks | [OrderBookLevel](/api/user-reference#flint-margin-user-v1-OrderBookLevel) | repeated | | | sequence | [uint64](/api/user-reference#uint64) | | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | ### PublicTrade | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | trade_id | [string](/api/user-reference#string) | | | | perp_id | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | | | | aggressor_side | [TradeSide](/api/user-reference#flint-margin-user-v1-TradeSide) | | | | price | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | quantity | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | executed_at | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | ### SubscribeCandlesRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_id | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | | | | interval | [CandleInterval](/api/user-reference#flint-margin-user-v1-CandleInterval) | | | | price_source | [CandlePriceSource](/api/user-reference#flint-margin-user-v1-CandlePriceSource) | | | ### SubscribeCandlesResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | candle | [Candle](/api/user-reference#flint-margin-user-v1-Candle) | | | | snapshot | [bool](/api/user-reference#bool) | | | | sequence | [uint64](/api/user-reference#uint64) | | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | ### SubscribeOrderBookRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_id | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | | | | depth | [uint32](/api/user-reference#uint32) | | | ### SubscribeOrderBookResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | order_book | [OrderBookSnapshot](/api/user-reference#flint-margin-user-v1-OrderBookSnapshot) | | Snapshot replaces the book. Deltas replace sizes by price; zero deletes. | | snapshot | [bool](/api/user-reference#bool) | | | | sequence | [uint64](/api/user-reference#uint64) | | | | previous_sequence | [uint64](/api/user-reference#uint64) | | | ### SubscribeTradesRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_ids | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | repeated | | ### SubscribeTradesResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | trades | [PublicTrade](/api/user-reference#flint-margin-user-v1-PublicTrade) | repeated | | | snapshot | [bool](/api/user-reference#bool) | | | | sequence | [uint64](/api/user-reference#uint64) | | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | ### CandleInterval | Name | Number | Description | | ---- | ------ | ----------- | | CANDLE_INTERVAL_UNSPECIFIED | 0 | | | CANDLE_INTERVAL_1_MINUTE | 1 | | | CANDLE_INTERVAL_5_MINUTES | 2 | | | CANDLE_INTERVAL_15_MINUTES | 3 | | | CANDLE_INTERVAL_1_HOUR | 4 | | | CANDLE_INTERVAL_4_HOURS | 5 | | | CANDLE_INTERVAL_1_DAY | 6 | | ### CandlePriceSource | Name | Number | Description | | ---- | ------ | ----------- | | CANDLE_PRICE_SOURCE_UNSPECIFIED | 0 | | | CANDLE_PRICE_SOURCE_TRADES | 1 | | | CANDLE_PRICE_SOURCE_MARK | 2 | | | CANDLE_PRICE_SOURCE_INDEX | 3 | |

Top

## flint/margin/user/v1/market_data/service.proto ### MarketDataService | Method Name | Request Type | Response Type | Description | | ----------- | ------------ | ------------- | ------------| | GetOrderBook | [GetOrderBookRequest](/api/user-reference#flint-margin-user-v1-GetOrderBookRequest) | [GetOrderBookResponse](/api/user-reference#flint-margin-user-v1-GetOrderBookResponse) | | | SubscribeOrderBook | [SubscribeOrderBookRequest](/api/user-reference#flint-margin-user-v1-SubscribeOrderBookRequest) | [SubscribeOrderBookResponse](/api/user-reference#flint-margin-user-v1-SubscribeOrderBookResponse) stream | | | GetTrades | [GetTradesRequest](/api/user-reference#flint-margin-user-v1-GetTradesRequest) | [GetTradesResponse](/api/user-reference#flint-margin-user-v1-GetTradesResponse) | | | SubscribeTrades | [SubscribeTradesRequest](/api/user-reference#flint-margin-user-v1-SubscribeTradesRequest) | [SubscribeTradesResponse](/api/user-reference#flint-margin-user-v1-SubscribeTradesResponse) stream | | | GetCandles | [GetCandlesRequest](/api/user-reference#flint-margin-user-v1-GetCandlesRequest) | [GetCandlesResponse](/api/user-reference#flint-margin-user-v1-GetCandlesResponse) | | | SubscribeCandles | [SubscribeCandlesRequest](/api/user-reference#flint-margin-user-v1-SubscribeCandlesRequest) | [SubscribeCandlesResponse](/api/user-reference#flint-margin-user-v1-SubscribeCandlesResponse) stream | |

Top

## flint/margin/user/v1/notifications/messages.proto ### RequestEmailVerificationRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | email | [string](/api/user-reference#string) | | | ### RequestEmailVerificationResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | challenge_id | [string](/api/user-reference#string) | | | | expires_at_ms | [uint64](/api/user-reference#uint64) | | Unix epoch milliseconds. | | retry_after_ms | [uint64](/api/user-reference#uint64) | | Milliseconds before another verification email may be requested. | ### Settings Settings belong to the owner and selected trader account in the authenticated margin session. Delegates cannot read or change notification settings. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | delivery_available | [bool](/api/user-reference#bool) | | False when this server cannot send verification or fill emails. | | email_address | [string](/api/user-reference#string) | | Verified destination only; empty when no email destination is enabled. | | email_enabled | [bool](/api/user-reference#bool) | | | ### VerifyEmailRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | challenge_id | [string](/api/user-reference#string) | | | | code | [string](/api/user-reference#string) | | |

Top

## flint/margin/user/v1/notifications/service.proto ### NotificationService Manages email delivery preferences. Every method requires a margin-session bearer token for the selected trader account's current owner. Public fill history is available through AccountService.GetFills. Reading or dismissing fills in a client never suppresses email delivery. | Method Name | Request Type | Response Type | Description | | ----------- | ------------ | ------------- | ------------| | GetSettings | [.google.protobuf.Empty](/api/user-reference#google-protobuf-Empty) | [Settings](/api/user-reference#flint-margin-user-v1-notifications-Settings) | Returns current settings without enabling email delivery. | | RequestEmailVerification | [RequestEmailVerificationRequest](/api/user-reference#flint-margin-user-v1-notifications-RequestEmailVerificationRequest) | [RequestEmailVerificationResponse](/api/user-reference#flint-margin-user-v1-notifications-RequestEmailVerificationResponse) | Sends a verification code. Requesting a code does not enable email or change the currently verified destination when requesting a replacement. | | VerifyEmail | [VerifyEmailRequest](/api/user-reference#flint-margin-user-v1-notifications-VerifyEmailRequest) | [Settings](/api/user-reference#flint-margin-user-v1-notifications-Settings) | Verifies a single-use code and enables the destination for new fills from verification onward, replacing any previous destination without a backlog. | | DisableEmail | [.google.protobuf.Empty](/api/user-reference#google-protobuf-Empty) | [Settings](/api/user-reference#flint-margin-user-v1-notifications-Settings) | Disables email and cancels pending verification for this account. Re-enabling email requires fresh verification. |

Top

## flint/margin/user/v1/order/messages.proto ### GetOrderRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | trader_id | [TraderId](/api/user-reference#flint-margin-user-v1-TraderId) | | | | order_id | [string](/api/user-reference#string) | | | | client_order_id | [string](/api/user-reference#string) | | | ### GetOrderResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | order | [Order](/api/user-reference#flint-margin-user-v1-Order) | | | ### ListOrdersRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | trader_id | [TraderId](/api/user-reference#flint-margin-user-v1-TraderId) | | | | perp_ids | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | repeated | | | statuses | [OrderStatus](/api/user-reference#flint-margin-user-v1-OrderStatus) | repeated | | | start | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | end | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | page_size | [uint32](/api/user-reference#uint32) | | | | page_token | [string](/api/user-reference#string) | | | ### ListOrdersResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | orders | [Order](/api/user-reference#flint-margin-user-v1-Order) | repeated | | | next_page_token | [string](/api/user-reference#string) | | | | coverage | [HistoryCoverage](/api/user-reference#flint-margin-user-v1-HistoryCoverage) | | | ### Order | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | order_id | [string](/api/user-reference#string) | | | | client_order_id | [string](/api/user-reference#string) | | | | trader_id | [TraderId](/api/user-reference#flint-margin-user-v1-TraderId) | | | | perp_id | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | | | | side | [TradeSide](/api/user-reference#flint-margin-user-v1-TradeSide) | | | | order_type | [OrderType](/api/user-reference#flint-margin-user-v1-OrderType) | | | | time_in_force | [TimeInForce](/api/user-reference#flint-margin-user-v1-TimeInForce) | | | | status | [OrderStatus](/api/user-reference#flint-margin-user-v1-OrderStatus) | | | | quantity | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | filled_quantity | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | remaining_quantity | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | limit_price | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | trigger_price | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | reduce_only | [bool](/api/user-reference#bool) | | | | post_only | [bool](/api/user-reference#bool) | | | | created_at | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | updated_at | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | expires_at | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | rejection_reason | [string](/api/user-reference#string) | | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | ### PreviewOrder | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_id | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | | | | side | [TradeSide](/api/user-reference#flint-margin-user-v1-TradeSide) | | | | requested_quantity | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | executable_quantity | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | average_price | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | worst_price | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | price_impact | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | estimated_fee_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | resulting_position_quantity | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | resulting_initial_margin_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | resulting_maintenance_margin_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | ### PreviewOrderRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | trader_id | [TraderId](/api/user-reference#flint-margin-user-v1-TraderId) | | | | perp_id | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | | | | side | [TradeSide](/api/user-reference#flint-margin-user-v1-TradeSide) | | | | quantity | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | order_type | [OrderType](/api/user-reference#flint-margin-user-v1-OrderType) | | | | limit_price | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | reduce_only | [bool](/api/user-reference#bool) | | | ### PreviewOrderResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | preview | [PreviewOrder](/api/user-reference#flint-margin-user-v1-PreviewOrder) | | | ### SubscribeOrdersRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | trader_id | [TraderId](/api/user-reference#flint-margin-user-v1-TraderId) | | | | perp_ids | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | repeated | | ### SubscribeOrdersResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | orders | [Order](/api/user-reference#flint-margin-user-v1-Order) | repeated | | | snapshot | [bool](/api/user-reference#bool) | | | | sequence | [uint64](/api/user-reference#uint64) | | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | ### OrderStatus | Name | Number | Description | | ---- | ------ | ----------- | | ORDER_STATUS_UNSPECIFIED | 0 | | | ORDER_STATUS_OPEN | 1 | | | ORDER_STATUS_PARTIALLY_FILLED | 2 | | | ORDER_STATUS_FILLED | 3 | | | ORDER_STATUS_CANCELLED | 4 | | | ORDER_STATUS_REJECTED | 5 | | | ORDER_STATUS_EXPIRED | 6 | | | ORDER_STATUS_TRIGGERED | 7 | | ### OrderType | Name | Number | Description | | ---- | ------ | ----------- | | ORDER_TYPE_UNSPECIFIED | 0 | | | ORDER_TYPE_LIMIT | 1 | | | ORDER_TYPE_MARKET | 2 | | | ORDER_TYPE_STOP_MARKET | 3 | | | ORDER_TYPE_STOP_LIMIT | 4 | | | ORDER_TYPE_TAKE_PROFIT_MARKET | 5 | | | ORDER_TYPE_TAKE_PROFIT_LIMIT | 6 | | ### TimeInForce | Name | Number | Description | | ---- | ------ | ----------- | | TIME_IN_FORCE_UNSPECIFIED | 0 | | | TIME_IN_FORCE_GOOD_TIL_CANCELLED | 1 | | | TIME_IN_FORCE_IMMEDIATE_OR_CANCEL | 2 | | | TIME_IN_FORCE_FILL_OR_KILL | 3 | | | TIME_IN_FORCE_POST_ONLY | 4 | |

Top

## flint/margin/user/v1/order/service.proto ### OrderService | Method Name | Request Type | Response Type | Description | | ----------- | ------------ | ------------- | ------------| | ListOrders | [ListOrdersRequest](/api/user-reference#flint-margin-user-v1-ListOrdersRequest) | [ListOrdersResponse](/api/user-reference#flint-margin-user-v1-ListOrdersResponse) | | | GetOrder | [GetOrderRequest](/api/user-reference#flint-margin-user-v1-GetOrderRequest) | [GetOrderResponse](/api/user-reference#flint-margin-user-v1-GetOrderResponse) | | | SubscribeOrders | [SubscribeOrdersRequest](/api/user-reference#flint-margin-user-v1-SubscribeOrdersRequest) | [SubscribeOrdersResponse](/api/user-reference#flint-margin-user-v1-SubscribeOrdersResponse) stream | | | PreviewOrder | [PreviewOrderRequest](/api/user-reference#flint-margin-user-v1-PreviewOrderRequest) | [PreviewOrderResponse](/api/user-reference#flint-margin-user-v1-PreviewOrderResponse) | |

Top

## flint/margin/user/v1/perp/messages.proto ### Fill | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | fill_id | [string](/api/user-reference#string) | | | | order_id | [string](/api/user-reference#string) | | | | client_order_id | [string](/api/user-reference#string) | | | | trader_id | [TraderId](/api/user-reference#flint-margin-user-v1-TraderId) | | | | perp_id | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | | | | side | [TradeSide](/api/user-reference#flint-margin-user-v1-TradeSide) | | | | price | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | quantity | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | fee_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | rebate_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | maker | [bool](/api/user-reference#bool) | | | | executed_at | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | | realized_pnl_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | ### GetFillsRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | trader_id | [TraderId](/api/user-reference#flint-margin-user-v1-TraderId) | | | | perp_ids | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | repeated | | | start | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | end | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | page_size | [uint32](/api/user-reference#uint32) | | | | page_token | [string](/api/user-reference#string) | | | ### GetFillsResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | fills | [Fill](/api/user-reference#flint-margin-user-v1-Fill) | repeated | | | next_page_token | [string](/api/user-reference#string) | | | | coverage | [HistoryCoverage](/api/user-reference#flint-margin-user-v1-HistoryCoverage) | | | ### GetPositionHistoryRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | trader_id | [TraderId](/api/user-reference#flint-margin-user-v1-TraderId) | | | | perp_ids | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | repeated | | | start | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | end | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | page_size | [uint32](/api/user-reference#uint32) | | | | page_token | [string](/api/user-reference#string) | | | ### GetPositionHistoryResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | positions | [PositionHistoryEntry](/api/user-reference#flint-margin-user-v1-PositionHistoryEntry) | repeated | | | next_page_token | [string](/api/user-reference#string) | | | | coverage | [HistoryCoverage](/api/user-reference#flint-margin-user-v1-HistoryCoverage) | | | ### GetTradingFeesRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | trader_id | [TraderId](/api/user-reference#flint-margin-user-v1-TraderId) | | | | perp_ids | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | repeated | | ### GetTradingFeesResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | fees | [TradingFees](/api/user-reference#flint-margin-user-v1-TradingFees) | repeated | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | ### GetTradingLimitsRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | trader_id | [TraderId](/api/user-reference#flint-margin-user-v1-TraderId) | | | | perp_ids | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | repeated | | ### GetTradingLimitsResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | limits | [TradingLimits](/api/user-reference#flint-margin-user-v1-TradingLimits) | repeated | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | ### PerpId | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | id | [uint32](/api/user-reference#uint32) | | | ### PerpPnlHistory | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | points | [PerpPnlHistoryPoint](/api/user-reference#flint-margin-user-v1-PerpPnlHistoryPoint) | repeated | | ### PerpPnlHistoryPoint | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | ts | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | total_pnl_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | realized_trading_pnl_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | fees_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | - fees; + rebates/rewards. | | funding_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | + credit; - debit. | ### PerpPosition | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_id | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | | | | quantity | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Signed base units: positive long, negative short. | | entry_price | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | mark_price | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | notional_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | unrealized_pnl_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | realized_pnl_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | accrued_funding_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Positive is an estimated credit to the account, negative a debit. | | initial_margin_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | maintenance_margin_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | liquidation_price | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | status | [PerpPositionStatus](/api/user-reference#flint-margin-user-v1-PerpPositionStatus) | | | | opened_at | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | updated_at | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | ### PositionHistoryEntry | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | position_id | [string](/api/user-reference#string) | | | | position | [PerpPosition](/api/user-reference#flint-margin-user-v1-PerpPosition) | | | | closed_at | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | total_fees_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | total_funding_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | ### SubscribeFillsRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | trader_id | [TraderId](/api/user-reference#flint-margin-user-v1-TraderId) | | | | perp_ids | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | repeated | | ### TradingFees | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_id | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | | | | maker_fee_rate | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | taker_fee_rate | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | maker_rebate_rate | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | tier | [string](/api/user-reference#string) | | | | effective_at | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | ### TradingLimits | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_id | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | | | | max_buy_quantity | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | max_sell_quantity | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | reduce_only_buy_quantity | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | reduce_only_sell_quantity | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | available_margin_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | ### PerpPositionStatus | Name | Number | Description | | ---- | ------ | ----------- | | PERP_POSITION_STATUS_UNSPECIFIED | 0 | | | PERP_POSITION_STATUS_OPEN | 1 | | | PERP_POSITION_STATUS_CLOSED | 2 | | | PERP_POSITION_STATUS_LIQUIDATED | 3 | | ### TradeSide | Name | Number | Description | | ---- | ------ | ----------- | | TRADE_SIDE_UNSPECIFIED | 0 | | | TRADE_SIDE_BUY | 1 | | | TRADE_SIDE_SELL | 2 | |

Top

## flint/margin/user/v1/perp_market/messages.proto ### GetMarketHistoryStatsRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_ids | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | repeated | Empty selects every perpetual market; servers accept at most 100 IDs. | ### GetMarketHistoryStatsResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | stats | [PerpMarketHistoryStats](/api/user-reference#flint-margin-user-v1-PerpMarketHistoryStats) | repeated | | | as_of | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | coverage | [HistoryCoverage](/api/user-reference#flint-margin-user-v1-HistoryCoverage) | | | ### GetMarketStatsRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_ids | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | repeated | | ### GetMarketStatsResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | stats | [MarketStats](/api/user-reference#flint-margin-user-v1-MarketStats) | repeated | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | ### GetPerpMarketRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_id | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | | | ### GetPerpMarketResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | market | [PerpMarket](/api/user-reference#flint-margin-user-v1-PerpMarket) | | | ### GetVolumeBreakdownRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_ids | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | repeated | Empty selects every perpetual market. | ### GetVolumeBreakdownResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perps | [PerpVolume](/api/user-reference#flint-margin-user-v1-PerpVolume) | repeated | | | coverage | [HistoryCoverage](/api/user-reference#flint-margin-user-v1-HistoryCoverage) | | | ### GetVolumeSeriesRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_id | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | | Unset aggregates every perpetual market. | | window | [StatsWindow](/api/user-reference#flint-margin-user-v1-StatsWindow) | | | ### GetVolumeSeriesResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | buckets | [VolumeBucket](/api/user-reference#flint-margin-user-v1-VolumeBucket) | repeated | Ordered oldest first, including empty buckets. | | bucket_seconds | [uint32](/api/user-reference#uint32) | | | | coverage | [HistoryCoverage](/api/user-reference#flint-margin-user-v1-HistoryCoverage) | | | ### ListPerpMarketsRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_ids | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | repeated | | | statuses | [PerpMarketStatus](/api/user-reference#flint-margin-user-v1-PerpMarketStatus) | repeated | | | page_size | [uint32](/api/user-reference#uint32) | | | | page_token | [string](/api/user-reference#string) | | | ### ListPerpMarketsResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | markets | [PerpMarket](/api/user-reference#flint-margin-user-v1-PerpMarket) | repeated | | | next_page_token | [string](/api/user-reference#string) | | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | ### MarginTier | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | min_position_notional_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | initial_margin_rate | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | maintenance_margin_rate | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | ### MarketStats | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_id | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | | | | mark_price | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | index_price | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | oracle_price | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | last_trade_price | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | best_bid_price | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | best_ask_price | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | volume_24h | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | price_change_24h | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Fractional price change over the trailing 24 hours. | | open_interest | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Base units, counted once per matched long/short pair. | | funding_rate | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | next_funding_at | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | | funding_interval_seconds | [uint64](/api/user-reference#uint64) | | | | best_bid_quantity | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Base quantity available at the best quoted price. | | best_ask_quantity | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | ### PerpMarket | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_id | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | | | | symbol | [string](/api/user-reference#string) | | | | base_asset | [SpotMetadata](/api/user-reference#flint-margin-user-v1-SpotMetadata) | | | | quote_asset | [SpotMetadata](/api/user-reference#flint-margin-user-v1-SpotMetadata) | | | | settlement_asset | [SpotMetadata](/api/user-reference#flint-margin-user-v1-SpotMetadata) | | | | tick_size | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | lot_size | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | min_order_quantity | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | max_order_quantity | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | max_position_notional_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | initial_margin_rate | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | maintenance_margin_rate | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | status | [PerpMarketStatus](/api/user-reference#flint-margin-user-v1-PerpMarketStatus) | | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | | margin_tiers | [MarginTier](/api/user-reference#flint-margin-user-v1-MarginTier) | repeated | | | max_open_interest | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | ### PerpMarketHistoryStats | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_id | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | | | | volume_24h_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Trailing 24-hour USD volume; absent if unknown or unpriced. | | price_change_24h_fraction | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Fractional price change: current / reference - 1. | | price_change_reference_at | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | Candle end for the exact minute \[minute(as_of) - 24h - 1m, minute(as_of) - 24h). | | price_change_as_of | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | Candle end for the latest traded minute in \[minute(as_of) - 4m, as_of), capped at as_of. | ### PerpVolume | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_id | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | | | | one_hour | [VolumeWindow](/api/user-reference#flint-margin-user-v1-VolumeWindow) | | | | twenty_four_hour | [VolumeWindow](/api/user-reference#flint-margin-user-v1-VolumeWindow) | | | | thirty_day | [VolumeWindow](/api/user-reference#flint-margin-user-v1-VolumeWindow) | | | | last_price | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | ### SubscribeMarketStatsRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_ids | [PerpId](/api/user-reference#flint-margin-user-v1-PerpId) | repeated | | ### SubscribeMarketStatsResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | stats | [MarketStats](/api/user-reference#flint-margin-user-v1-MarketStats) | repeated | | | snapshot | [bool](/api/user-reference#bool) | | | | sequence | [uint64](/api/user-reference#uint64) | | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | ### VolumeBucket | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | ts | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | volume_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Absent if any fills in the bucket cannot be valued in USD. | | fill_count | [uint64](/api/user-reference#uint64) | | | ### VolumeWindow | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | volume_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | USD notional counted once per trade; absent if any fills cannot be valued. | | fill_count | [uint64](/api/user-reference#uint64) | | | ### PerpMarketStatus | Name | Number | Description | | ---- | ------ | ----------- | | PERP_MARKET_STATUS_UNSPECIFIED | 0 | | | PERP_MARKET_STATUS_ACTIVE | 1 | | | PERP_MARKET_STATUS_PAUSED | 2 | | | PERP_MARKET_STATUS_REDUCE_ONLY | 3 | | | PERP_MARKET_STATUS_SETTLING | 4 | | | PERP_MARKET_STATUS_CLOSED | 5 | | ### StatsWindow | Name | Number | Description | | ---- | ------ | ----------- | | STATS_WINDOW_UNSPECIFIED | 0 | | | STATS_WINDOW_1H | 1 | | | STATS_WINDOW_24H | 2 | | | STATS_WINDOW_30D | 3 | |

Top

## flint/margin/user/v1/perp_market/service.proto ### PerpMarketService | Method Name | Request Type | Response Type | Description | | ----------- | ------------ | ------------- | ------------| | ListPerpMarkets | [ListPerpMarketsRequest](/api/user-reference#flint-margin-user-v1-ListPerpMarketsRequest) | [ListPerpMarketsResponse](/api/user-reference#flint-margin-user-v1-ListPerpMarketsResponse) | | | GetPerpMarket | [GetPerpMarketRequest](/api/user-reference#flint-margin-user-v1-GetPerpMarketRequest) | [GetPerpMarketResponse](/api/user-reference#flint-margin-user-v1-GetPerpMarketResponse) | | | GetMarketStats | [GetMarketStatsRequest](/api/user-reference#flint-margin-user-v1-GetMarketStatsRequest) | [GetMarketStatsResponse](/api/user-reference#flint-margin-user-v1-GetMarketStatsResponse) | | | GetMarketHistoryStats | [GetMarketHistoryStatsRequest](/api/user-reference#flint-margin-user-v1-GetMarketHistoryStatsRequest) | [GetMarketHistoryStatsResponse](/api/user-reference#flint-margin-user-v1-GetMarketHistoryStatsResponse) | | | GetVolumeBreakdown | [GetVolumeBreakdownRequest](/api/user-reference#flint-margin-user-v1-GetVolumeBreakdownRequest) | [GetVolumeBreakdownResponse](/api/user-reference#flint-margin-user-v1-GetVolumeBreakdownResponse) | | | GetVolumeSeries | [GetVolumeSeriesRequest](/api/user-reference#flint-margin-user-v1-GetVolumeSeriesRequest) | [GetVolumeSeriesResponse](/api/user-reference#flint-margin-user-v1-GetVolumeSeriesResponse) | | | SubscribeMarketStats | [SubscribeMarketStatsRequest](/api/user-reference#flint-margin-user-v1-SubscribeMarketStatsRequest) | [SubscribeMarketStatsResponse](/api/user-reference#flint-margin-user-v1-SubscribeMarketStatsResponse) stream | |

Top

## flint/margin/user/v1/risk/messages.proto ### AccountAtRisk | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | account | [AccountSnapshot](/api/user-reference#flint-margin-user-v1-AccountSnapshot) | | | | total_deposited_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | total_borrowed_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | net_assets_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | maintenance_buffer_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | net_equity - maintenance_margin; negative means liquidatable. | | health_ratio | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Below 1 means liquidatable. | ### AssetPriceShock | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | spot_id | [SpotId](/api/user-reference#flint-margin-user-v1-SpotId) | | | | price_change | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Signed fractional price change in \[-1, 10]; at most 20 unique spot IDs. | ### GetInsuranceFundRequest ### GetInsuranceFundResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | fund | [InsuranceFund](/api/user-reference#flint-margin-user-v1-InsuranceFund) | | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | ### GetMarketOverviewRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | non_stable_price_changes | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | repeated | Simultaneous signed fractional non-stable oracle changes in \[-1, 0]; empty uses defaults. | ### GetMarketOverviewResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | overview | [ProtocolRiskOverview](/api/user-reference#flint-margin-user-v1-ProtocolRiskOverview) | | | | markets | [MarketRiskOverview](/api/user-reference#flint-margin-user-v1-MarketRiskOverview) | repeated | | | liquidation_shocks | [LiquidationShockRow](/api/user-reference#flint-margin-user-v1-LiquidationShockRow) | repeated | Request order, or ascending price fall when using defaults. | | slot | [uint64](/api/user-reference#uint64) | | | | ts | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | ### InsuranceFund | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | address | [string](/api/user-reference#string) | | | | borrow_lend_socialize_loss_max_draw | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Maximum fraction one socialize-loss instruction may draw; 1 permits all. | | balances | [InsuranceFundBalance](/api/user-reference#flint-margin-user-v1-InsuranceFundBalance) | repeated | | | complete | [bool](/api/user-reference#bool) | | False when the fund or a listed market vault was not observed. | | unavailable_reason | [string](/api/user-reference#string) | | | ### InsuranceFundBalance | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | asset | [SpotMetadata](/api/user-reference#flint-margin-user-v1-SpotMetadata) | | | | vault | [string](/api/user-reference#string) | | | | balance | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | ### LiquidationShockRow | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | non_stable_price_change | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | liquidatable_account_count | [uint64](/api/user-reference#uint64) | | | | bad_debt_account_count | [uint64](/api/user-reference#uint64) | optional | Unset when unavailable; liquidation_usd and bad_debt_usd are then unset too. | | liquidation_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Debt value a liquidation would transfer, including its buffer. | | bad_debt_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | deficit_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | ### ListAccountsAtRiskRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | min_account_size_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Gross unweighted deposits in USD; unset bounds are open. | | max_account_size_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | max_health_ratio | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Maximum health ratio; unset uses the configured threshold. Accounts without one are excluded. | | liquidatable_only | [bool](/api/user-reference#bool) | optional | True restricts results to liquidatable accounts. | | page_size | [uint32](/api/user-reference#uint32) | | Zero uses the server default. | | page_token | [string](/api/user-reference#string) | | Opaque token anchoring pagination to one risk snapshot. | ### ListAccountsAtRiskResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | accounts | [AccountAtRisk](/api/user-reference#flint-margin-user-v1-AccountAtRisk) | repeated | Ordered by health ratio ascending, account size descending, then trader ID ascending. | | total_count | [uint64](/api/user-reference#uint64) | | Total matching accounts across all pages. | | next_page_token | [string](/api/user-reference#string) | | | | max_health_ratio | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Effective health-ratio cutoff. | | slot | [uint64](/api/user-reference#uint64) | | | | ts | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | ### ListLargestAccountsRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | page_size | [uint32](/api/user-reference#uint32) | | Zero uses the server default. | | page_token | [string](/api/user-reference#string) | | Opaque token anchoring pagination to one risk snapshot. | ### ListLargestAccountsResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | accounts | [AccountAtRisk](/api/user-reference#flint-margin-user-v1-AccountAtRisk) | repeated | Ordered by gross supplied assets descending, then trader ID ascending. Accounts without maintenance margin remain included with no health ratio. | | total_count | [uint64](/api/user-reference#uint64) | | Total accounts across all pages. | | next_page_token | [string](/api/user-reference#string) | | | | slot | [uint64](/api/user-reference#uint64) | | | | ts | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | ### MarketRiskOverview | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | market | [MarketInfo](/api/user-reference#flint-margin-user-v1-MarketInfo) | | | | is_stable | [bool](/api/user-reference#bool) | | Held fixed in liquidation-shock scenarios. | | total_deposited_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | total_borrowed_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | available_liquidity_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | net_deposits_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | deposit_limit_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | USD market caps; unset when uncapped. | | borrow_limit_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | deposit_limit_headroom_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Remaining cap headroom, floored at zero; unset when uncapped. | | borrow_limit_headroom_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | initial_borrow_weight | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Debt-value factors: 1 + borrow_imf and 1 + borrow_mmf. | | maintenance_borrow_weight | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | deposit_limit_utilisation | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Cap usage; may exceed 1 after a cap reduction and is unset when uncapped. | | borrow_limit_utilisation | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | ### MonitoredAccountAtRisk | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | account | [AccountSnapshot](/api/user-reference#flint-margin-user-v1-AccountSnapshot) | | | | status | [MonitoredAccountStatus](/api/user-reference#flint-margin-user-v1-MonitoredAccountStatus) | | | ### ProtocolRiskOverview | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | account_count | [uint64](/api/user-reference#uint64) | | | | depositor_count | [uint64](/api/user-reference#uint64) | | | | borrower_count | [uint64](/api/user-reference#uint64) | | | | loan_count | [uint64](/api/user-reference#uint64) | | Number of non-zero borrow positions. | | accounts_at_risk | [uint64](/api/user-reference#uint64) | | | | liquidatable_accounts | [uint64](/api/user-reference#uint64) | | | | total_deposited_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | total_borrowed_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | available_liquidity_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | net_deposits_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | protocol_fees_usd | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | at_risk_health_ratio | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | ### SimulateAccountRiskRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | trader_id | [TraderId](/api/user-reference#flint-margin-user-v1-TraderId) | | | | price_shocks | [AssetPriceShock](/api/user-reference#flint-margin-user-v1-AssetPriceShock) | repeated | Applied simultaneously by oracle; conflicting oracle changes are rejected. | ### SimulateAccountRiskResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | current | [AccountSnapshot](/api/user-reference#flint-margin-user-v1-AccountSnapshot) | | | | stressed | [AccountSnapshot](/api/user-reference#flint-margin-user-v1-AccountSnapshot) | | | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | ### SubscribeAccountsAtRiskRequest ### SubscribeAccountsAtRiskResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | upserts | [MonitoredAccountAtRisk](/api/user-reference#flint-margin-user-v1-MonitoredAccountAtRisk) | repeated | Upserts ordered by trader ID; complete replacement when snapshot is true. | | removed_trader_ids | [TraderId](/api/user-reference#flint-margin-user-v1-TraderId) | repeated | IDs to remove; empty for snapshots. | | snapshot | [bool](/api/user-reference#bool) | | | | entry_health_ratio | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | Admission and release thresholds; their gap provides hysteresis. | | exit_health_ratio | [Decimal](/api/user-reference#flint-margin-user-v1-Decimal) | | | | slot | [uint64](/api/user-reference#uint64) | | | | ts | [Timestamp](/api/user-reference#flint-margin-user-v1-Timestamp) | | | | revision | [uint64](/api/user-reference#uint64) | | Monotonically non-decreasing valuation revision. | | provenance | [DataProvenance](/api/user-reference#flint-margin-user-v1-DataProvenance) | | | ### MonitoredAccountStatus | Name | Number | Description | | ---- | ------ | ----------- | | MONITORED_ACCOUNT_STATUS_UNSPECIFIED | 0 | | | MONITORED_ACCOUNT_STATUS_WATCHING | 1 | | | MONITORED_ACCOUNT_STATUS_LIQUIDATABLE | 2 | | | MONITORED_ACCOUNT_STATUS_UNAVAILABLE | 3 | Account is the last known snapshot and may be stale. |

Top

## flint/margin/user/v1/risk/service.proto ### RiskService Public read-only margin-risk views. | Method Name | Request Type | Response Type | Description | | ----------- | ------------ | ------------- | ------------| | GetMarketOverview | [GetMarketOverviewRequest](/api/user-reference#flint-margin-user-v1-GetMarketOverviewRequest) | [GetMarketOverviewResponse](/api/user-reference#flint-margin-user-v1-GetMarketOverviewResponse) | | | SimulateAccountRisk | [SimulateAccountRiskRequest](/api/user-reference#flint-margin-user-v1-SimulateAccountRiskRequest) | [SimulateAccountRiskResponse](/api/user-reference#flint-margin-user-v1-SimulateAccountRiskResponse) | | | GetInsuranceFund | [GetInsuranceFundRequest](/api/user-reference#flint-margin-user-v1-GetInsuranceFundRequest) | [GetInsuranceFundResponse](/api/user-reference#flint-margin-user-v1-GetInsuranceFundResponse) | | | ListAccountsAtRisk | [ListAccountsAtRiskRequest](/api/user-reference#flint-margin-user-v1-ListAccountsAtRiskRequest) | [ListAccountsAtRiskResponse](/api/user-reference#flint-margin-user-v1-ListAccountsAtRiskResponse) | | | SubscribeAccountsAtRisk | [SubscribeAccountsAtRiskRequest](/api/user-reference#flint-margin-user-v1-SubscribeAccountsAtRiskRequest) | [SubscribeAccountsAtRiskResponse](/api/user-reference#flint-margin-user-v1-SubscribeAccountsAtRiskResponse) stream | | | ListLargestAccounts | [ListLargestAccountsRequest](/api/user-reference#flint-margin-user-v1-ListLargestAccountsRequest) | [ListLargestAccountsResponse](/api/user-reference#flint-margin-user-v1-ListLargestAccountsResponse) | |

Top

## flint/margin/user/v1/sponsorship/messages.proto ### GetSponsorshipPubkeyRequest ### GetSponsorshipPubkeyResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | pubkey | [string](/api/user-reference#string) | | Base58 fee payer; leave its signature slot empty for the server. | ### SponsorTransactionRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | transaction | [bytes](/api/user-reference#bytes) | | Wire-format VersionedTransaction with only the empty sponsor signature. | ### SponsorTransactionResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | signature | [string](/api/user-reference#string) | | Base58 confirmed transaction signature. |

Top

## flint/margin/user/v1/sponsorship/service.proto ### SponsorshipService Pays fees for pre-authorized margin transactions; the sponsor is fee payer only. Transactions may invoke only the margin, compute-budget, and secp256r1 programs. | Method Name | Request Type | Response Type | Description | | ----------- | ------------ | ------------- | ------------| | GetSponsorshipPubkey | [GetSponsorshipPubkeyRequest](/api/user-reference#flint-margin-user-v1-GetSponsorshipPubkeyRequest) | [GetSponsorshipPubkeyResponse](/api/user-reference#flint-margin-user-v1-GetSponsorshipPubkeyResponse) | | | SponsorTransaction | [SponsorTransactionRequest](/api/user-reference#flint-margin-user-v1-SponsorTransactionRequest) | [SponsorTransactionResponse](/api/user-reference#flint-margin-user-v1-SponsorTransactionResponse) | | ## Scalar Value Types | .proto Type | Notes | C++ | Java | Python | Go | C# | PHP | Ruby | | ----------- | ----- | --- | ---- | ------ | -- | -- | --- | ---- | | double | | double | double | float | float64 | double | float | Float | | float | | float | float | float | float32 | float | float | Float | | int32 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) | | int64 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. | int64 | long | int/long | int64 | long | integer/string | Bignum | | uint32 | Uses variable-length encoding. | uint32 | int | int/long | uint32 | uint | integer | Bignum or Fixnum (as required) | | uint64 | Uses variable-length encoding. | uint64 | long | int/long | uint64 | ulong | integer/string | Bignum or Fixnum (as required) | | sint32 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) | | sint64 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. | int64 | long | int/long | int64 | long | integer/string | Bignum | | fixed32 | Always four bytes. More efficient than uint32 if values are often greater than 2^28. | uint32 | int | int | uint32 | uint | integer | Bignum or Fixnum (as required) | | fixed64 | Always eight bytes. More efficient than uint64 if values are often greater than 2^56. | uint64 | long | int/long | uint64 | ulong | integer/string | Bignum | | sfixed32 | Always four bytes. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) | | sfixed64 | Always eight bytes. | int64 | long | int/long | int64 | long | integer/string | Bignum | | bool | | bool | boolean | boolean | bool | bool | boolean | TrueClass/FalseClass | | string | A string must always contain UTF-8 encoded or 7-bit ASCII text. | string | String | str/unicode | string | string | string | String (UTF-8) | | bytes | May contain any arbitrary sequence of bytes. | string | ByteString | str | \[]byte | ByteString | string | String (ASCII-8BIT) | # flint.margin.maker.v1 reference ## Table of Contents * [flint/margin/maker/v1/auth/messages.proto](/api/maker-reference#flint_margin_maker_v1_auth_messages-proto) * [AuthenticateRequest](/api/maker-reference#flint-margin-maker-v1-AuthenticateRequest) * [AuthenticateResponse](/api/maker-reference#flint-margin-maker-v1-AuthenticateResponse) * [ChallengeRequest](/api/maker-reference#flint-margin-maker-v1-ChallengeRequest) * [ChallengeResponse](/api/maker-reference#flint-margin-maker-v1-ChallengeResponse) * [RevokeRequest](/api/maker-reference#flint-margin-maker-v1-RevokeRequest) * [RevokeResponse](/api/maker-reference#flint-margin-maker-v1-RevokeResponse) * [flint/margin/maker/v1/auth/service.proto](/api/maker-reference#flint_margin_maker_v1_auth_service-proto) * [AuthService](/api/maker-reference#flint-margin-maker-v1-AuthService) * [flint/margin/maker/v1/common.proto](/api/maker-reference#flint_margin_maker_v1_common-proto) * [Blockhash](/api/maker-reference#flint-margin-maker-v1-Blockhash) * [Decimal](/api/maker-reference#flint-margin-maker-v1-Decimal) * [LatLng](/api/maker-reference#flint-margin-maker-v1-LatLng) * [PerpId](/api/maker-reference#flint-margin-maker-v1-PerpId) * [SpotId](/api/maker-reference#flint-margin-maker-v1-SpotId) * [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) * [TraderId](/api/maker-reference#flint-margin-maker-v1-TraderId) * [HealthState](/api/maker-reference#flint-margin-maker-v1-HealthState) * [Side](/api/maker-reference#flint-margin-maker-v1-Side) * [flint/margin/maker/v1/escalation/messages.proto](/api/maker-reference#flint_margin_maker_v1_escalation_messages-proto) * [PageTheTeamRequest](/api/maker-reference#flint-margin-maker-v1-PageTheTeamRequest) * [PageTheTeamResponse](/api/maker-reference#flint-margin-maker-v1-PageTheTeamResponse) * [flint/margin/maker/v1/escalation/service.proto](/api/maker-reference#flint_margin_maker_v1_escalation_service-proto) * [EscalationService](/api/maker-reference#flint-margin-maker-v1-EscalationService) * [flint/margin/maker/v1/maker/events.proto](/api/maker-reference#flint_margin_maker_v1_maker_events-proto) * [DecodedEvent](/api/maker-reference#flint-margin-maker-v1-DecodedEvent) * [FillEvent](/api/maker-reference#flint-margin-maker-v1-FillEvent) * [MakerActivity](/api/maker-reference#flint-margin-maker-v1-MakerActivity) * [MakerMarketSnapshot](/api/maker-reference#flint-margin-maker-v1-MakerMarketSnapshot) * [MakerMarketsSnapshotEvent](/api/maker-reference#flint-margin-maker-v1-MakerMarketsSnapshotEvent) * [MakerQuote](/api/maker-reference#flint-margin-maker-v1-MakerQuote) * [PerpL2Level](/api/maker-reference#flint-margin-maker-v1-PerpL2Level) * [QuoteUpdate](/api/maker-reference#flint-margin-maker-v1-QuoteUpdate) * [QuoterSeriesBucket](/api/maker-reference#flint-margin-maker-v1-QuoterSeriesBucket) * [QuoterSession](/api/maker-reference#flint-margin-maker-v1-QuoterSession) * [QuotingAuthority](/api/maker-reference#flint-margin-maker-v1-QuotingAuthority) * [QuotingState](/api/maker-reference#flint-margin-maker-v1-QuotingState) * [DecodedEventKind](/api/maker-reference#flint-margin-maker-v1-DecodedEventKind) * [MakerActivityType](/api/maker-reference#flint-margin-maker-v1-MakerActivityType) * [QuoteUpdateKind](/api/maker-reference#flint-margin-maker-v1-QuoteUpdateKind) * [QuotingStrategy](/api/maker-reference#flint-margin-maker-v1-QuotingStrategy) * [TransactionStatus](/api/maker-reference#flint-margin-maker-v1-TransactionStatus) * [flint/margin/maker/v1/maker/messages.proto](/api/maker-reference#flint_margin_maker_v1_maker_messages-proto) * [GetDecodedTransactionRequest](/api/maker-reference#flint-margin-maker-v1-GetDecodedTransactionRequest) * [GetDecodedTransactionResponse](/api/maker-reference#flint-margin-maker-v1-GetDecodedTransactionResponse) * [GetFillsRequest](/api/maker-reference#flint-margin-maker-v1-GetFillsRequest) * [GetFillsResponse](/api/maker-reference#flint-margin-maker-v1-GetFillsResponse) * [GetMakerActivityRequest](/api/maker-reference#flint-margin-maker-v1-GetMakerActivityRequest) * [GetMakerActivityResponse](/api/maker-reference#flint-margin-maker-v1-GetMakerActivityResponse) * [GetMakerMarkoutSummaryRequest](/api/maker-reference#flint-margin-maker-v1-GetMakerMarkoutSummaryRequest) * [GetMakerMarkoutSummaryResponse](/api/maker-reference#flint-margin-maker-v1-GetMakerMarkoutSummaryResponse) * [GetMakerStatsRequest](/api/maker-reference#flint-margin-maker-v1-GetMakerStatsRequest) * [GetMakerStatsResponse](/api/maker-reference#flint-margin-maker-v1-GetMakerStatsResponse) * [GetMakerVolumeBreakdownRequest](/api/maker-reference#flint-margin-maker-v1-GetMakerVolumeBreakdownRequest) * [GetMakerVolumeBreakdownResponse](/api/maker-reference#flint-margin-maker-v1-GetMakerVolumeBreakdownResponse) * [GetMakerVolumeSeriesRequest](/api/maker-reference#flint-margin-maker-v1-GetMakerVolumeSeriesRequest) * [GetMakerVolumeSeriesResponse](/api/maker-reference#flint-margin-maker-v1-GetMakerVolumeSeriesResponse) * [GetQuoteRequest](/api/maker-reference#flint-margin-maker-v1-GetQuoteRequest) * [GetQuoteResponse](/api/maker-reference#flint-margin-maker-v1-GetQuoteResponse) * [GetQuotingStateRequest](/api/maker-reference#flint-margin-maker-v1-GetQuotingStateRequest) * [GetQuotingStateResponse](/api/maker-reference#flint-margin-maker-v1-GetQuotingStateResponse) * [GetSdkCredentialsRequest](/api/maker-reference#flint-margin-maker-v1-GetSdkCredentialsRequest) * [GetSdkCredentialsResponse](/api/maker-reference#flint-margin-maker-v1-GetSdkCredentialsResponse) * [ListQuoterSessionsRequest](/api/maker-reference#flint-margin-maker-v1-ListQuoterSessionsRequest) * [ListQuoterSessionsResponse](/api/maker-reference#flint-margin-maker-v1-ListQuoterSessionsResponse) * [ListQuotingAuthoritiesRequest](/api/maker-reference#flint-margin-maker-v1-ListQuotingAuthoritiesRequest) * [ListQuotingAuthoritiesResponse](/api/maker-reference#flint-margin-maker-v1-ListQuotingAuthoritiesResponse) * [ListRecentQuotesRequest](/api/maker-reference#flint-margin-maker-v1-ListRecentQuotesRequest) * [ListRecentQuotesResponse](/api/maker-reference#flint-margin-maker-v1-ListRecentQuotesResponse) * [MarkoutBucket](/api/maker-reference#flint-margin-maker-v1-MarkoutBucket) * [MarkoutHistogramBin](/api/maker-reference#flint-margin-maker-v1-MarkoutHistogramBin) * [MarkoutPerpRow](/api/maker-reference#flint-margin-maker-v1-MarkoutPerpRow) * [PerpVolume](/api/maker-reference#flint-margin-maker-v1-PerpVolume) * [SubscribeFillsRequest](/api/maker-reference#flint-margin-maker-v1-SubscribeFillsRequest) * [SubscribeMakerMarketSnapshotsRequest](/api/maker-reference#flint-margin-maker-v1-SubscribeMakerMarketSnapshotsRequest) * [SubscribeQuotesRequest](/api/maker-reference#flint-margin-maker-v1-SubscribeQuotesRequest) * [UpdateRate](/api/maker-reference#flint-margin-maker-v1-UpdateRate) * [VolumeBucket](/api/maker-reference#flint-margin-maker-v1-VolumeBucket) * [VolumeWindow](/api/maker-reference#flint-margin-maker-v1-VolumeWindow) * [MakerFillOrder](/api/maker-reference#flint-margin-maker-v1-MakerFillOrder) * [MarkoutHorizon](/api/maker-reference#flint-margin-maker-v1-MarkoutHorizon) * [StatsWindow](/api/maker-reference#flint-margin-maker-v1-StatsWindow) * [flint/margin/maker/v1/maker/service.proto](/api/maker-reference#flint_margin_maker_v1_maker_service-proto) * [MakerService](/api/maker-reference#flint-margin-maker-v1-MakerService) * [flint/margin/maker/v1/node/messages.proto](/api/maker-reference#flint_margin_maker_v1_node_messages-proto) * [GetNodeInfoRequest](/api/maker-reference#flint-margin-maker-v1-GetNodeInfoRequest) * [GetNodeInfoResponse](/api/maker-reference#flint-margin-maker-v1-GetNodeInfoResponse) * [LeaderProximity](/api/maker-reference#flint-margin-maker-v1-LeaderProximity) * [ListNodesRequest](/api/maker-reference#flint-margin-maker-v1-ListNodesRequest) * [ListNodesResponse](/api/maker-reference#flint-margin-maker-v1-ListNodesResponse) * [NodeEndpoint](/api/maker-reference#flint-margin-maker-v1-NodeEndpoint) * [flint/margin/maker/v1/node/service.proto](/api/maker-reference#flint_margin_maker_v1_node_service-proto) * [NodeService](/api/maker-reference#flint-margin-maker-v1-NodeService) * [flint/margin/maker/v1/session/messages.proto](/api/maker-reference#flint_margin_maker_v1_session_messages-proto) * [Ping](/api/maker-reference#flint-margin-maker-v1-Ping) * [Pong](/api/maker-reference#flint-margin-maker-v1-Pong) * [SessionRequest](/api/maker-reference#flint-margin-maker-v1-SessionRequest) * [SessionResponse](/api/maker-reference#flint-margin-maker-v1-SessionResponse) * [flint/margin/maker/v1/session/service.proto](/api/maker-reference#flint_margin_maker_v1_session_service-proto) * [SessionService](/api/maker-reference#flint-margin-maker-v1-SessionService) * [flint/margin/maker/v1/spread_protect/events.proto](/api/maker-reference#flint_margin_maker_v1_spread_protect_events-proto) * [ResetState](/api/maker-reference#flint-margin-maker-v1-ResetState) * [SpreadProtectEvent](/api/maker-reference#flint-margin-maker-v1-SpreadProtectEvent) * [WidenWindow](/api/maker-reference#flint-margin-maker-v1-WidenWindow) * [flint/margin/maker/v1/spread_protect/messages.proto](/api/maker-reference#flint_margin_maker_v1_spread_protect_messages-proto) * [SubscribeSpreadProtectRequest](/api/maker-reference#flint-margin-maker-v1-SubscribeSpreadProtectRequest) * [flint/margin/maker/v1/spread_protect/service.proto](/api/maker-reference#flint_margin_maker_v1_spread_protect_service-proto) * [SpreadProtectService](/api/maker-reference#flint-margin-maker-v1-SpreadProtectService) * [flint/margin/maker/v1/tx/events.proto](/api/maker-reference#flint_margin_maker_v1_tx_events-proto) * [BlockhashEvent](/api/maker-reference#flint-margin-maker-v1-BlockhashEvent) * [LeaderEvent](/api/maker-reference#flint-margin-maker-v1-LeaderEvent) * [SlotEvent](/api/maker-reference#flint-margin-maker-v1-SlotEvent) * [TxAckEvent](/api/maker-reference#flint-margin-maker-v1-TxAckEvent) * [TxFailedEvent](/api/maker-reference#flint-margin-maker-v1-TxFailedEvent) * [TxLandedEvent](/api/maker-reference#flint-margin-maker-v1-TxLandedEvent) * [TxStatusEvent](/api/maker-reference#flint-margin-maker-v1-TxStatusEvent) * [flint/margin/maker/v1/tx/messages.proto](/api/maker-reference#flint_margin_maker_v1_tx_messages-proto) * [SubmitTxRequest](/api/maker-reference#flint-margin-maker-v1-SubmitTxRequest) * [SubmitTxResponse](/api/maker-reference#flint-margin-maker-v1-SubmitTxResponse) * [SubscribeBlockhashRequest](/api/maker-reference#flint-margin-maker-v1-SubscribeBlockhashRequest) * [SubscribeLeadersRequest](/api/maker-reference#flint-margin-maker-v1-SubscribeLeadersRequest) * [SubscribeSlotsRequest](/api/maker-reference#flint-margin-maker-v1-SubscribeSlotsRequest) * [SubscribeTxStatusRequest](/api/maker-reference#flint-margin-maker-v1-SubscribeTxStatusRequest) * [flint/margin/maker/v1/tx/service.proto](/api/maker-reference#flint_margin_maker_v1_tx_service-proto) * [TxService](/api/maker-reference#flint-margin-maker-v1-TxService) * [Scalar Value Types](/api/maker-reference#scalar-value-types)

Top

## flint/margin/maker/v1/auth/messages.proto ### AuthenticateRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | pubkey | [string](/api/maker-reference#string) | | Base58 public key. | | signature | [string](/api/maker-reference#string) | | Base58 signature over the domain prefix and nonce returned by Challenge. | | trader_id | [TraderId](/api/maker-reference#flint-margin-maker-v1-TraderId) | | Account to scope the session to. Omit to select the lowest authorized ID. | ### AuthenticateResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | session_token | [string](/api/maker-reference#string) | | Send as `authorization: Bearer <session_token>` metadata. | | trader_ids | [TraderId](/api/maker-reference#flint-margin-maker-v1-TraderId) | repeated | Accounts controlled as owner or active TRADE or QUOTE delegate. | | expires_at | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | The server returns UNAUTHENTICATED after this time. | | trader_id | [TraderId](/api/maker-reference#flint-margin-maker-v1-TraderId) | | Account selected for this session. | ### ChallengeRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | pubkey | [string](/api/maker-reference#string) | | Base58 owner or active TRADE- or QUOTE-delegate public key. | ### ChallengeResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | nonce | [bytes](/api/maker-reference#bytes) | | Sign `FLINT-MARGIN-AUTH-V1:` followed by this single-use nonce. | | expires_at | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | Server rejects Authenticate after this time. | ### RevokeRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | session_token | [string](/api/maker-reference#string) | | | ### RevokeResponse

Top

## flint/margin/maker/v1/auth/service.proto ### AuthService Issues sessions through a signed challenge; TxService requires the token. | Method Name | Request Type | Response Type | Description | | ----------- | ------------ | ------------- | ------------| | Challenge | [ChallengeRequest](/api/maker-reference#flint-margin-maker-v1-ChallengeRequest) | [ChallengeResponse](/api/maker-reference#flint-margin-maker-v1-ChallengeResponse) | Issues a single-use nonce. | | Authenticate | [AuthenticateRequest](/api/maker-reference#flint-margin-maker-v1-AuthenticateRequest) | [AuthenticateResponse](/api/maker-reference#flint-margin-maker-v1-AuthenticateResponse) | Returns UNAUTHENTICATED for missing or expired nonces, invalid signatures, or keys that control no trader account. | | Revoke | [RevokeRequest](/api/maker-reference#flint-margin-maker-v1-RevokeRequest) | [RevokeResponse](/api/maker-reference#flint-margin-maker-v1-RevokeResponse) | |

Top

## flint/margin/maker/v1/common.proto ### Blockhash | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | hash | [bytes](/api/maker-reference#bytes) | | | ### Decimal Decimal string parsed with rust_decimal-compatible semantics. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | value | [string](/api/maker-reference#string) | | | ### LatLng Absent when the location is unavailable. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | lat | [double](/api/maker-reference#double) | | | | lng | [double](/api/maker-reference#double) | | | ### PerpId | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | id | [uint32](/api/maker-reference#uint32) | | | ### SpotId On-chain margin spot-asset id. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | id | [uint32](/api/maker-reference#uint32) | | | ### Timestamp Unix microseconds since epoch; zero is unspecified. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | micros | [uint64](/api/maker-reference#uint64) | | | ### TraderId | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | id | [uint64](/api/maker-reference#uint64) | | | ### HealthState | Name | Number | Description | | ---- | ------ | ----------- | | HEALTH_STATE_UNSPECIFIED | 0 | | | HEALTH_STATE_HEALTHY | 1 | | | HEALTH_STATE_DEGRADED | 2 | | | HEALTH_STATE_HALTED | 3 | | ### Side | Name | Number | Description | | ---- | ------ | ----------- | | SIDE_UNSPECIFIED | 0 | | | SIDE_BUY | 1 | | | SIDE_SELL | 2 | |

Top

## flint/margin/maker/v1/escalation/messages.proto ### PageTheTeamRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | message | [string](/api/maker-reference#string) | | Required non-empty message, delivered verbatim to on-call. | ### PageTheTeamResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | page_id | [string](/api/maker-reference#string) | | Server-assigned page ID. | | received_at | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | When the server accepted the page. |

Top

## flint/margin/maker/v1/escalation/service.proto ### EscalationService Organization-authenticated escalation to the on-call team. | Method Name | Request Type | Response Type | Description | | ----------- | ------------ | ------------- | ------------| | PageTheTeam | [PageTheTeamRequest](/api/maker-reference#flint-margin-maker-v1-PageTheTeamRequest) | [PageTheTeamResponse](/api/maker-reference#flint-margin-maker-v1-PageTheTeamResponse) | Confirms dispatch, not response. Rejects unauthenticated callers and empty messages. |

Top

## flint/margin/maker/v1/maker/events.proto ### DecodedEvent Decoded Flint margin-program event. Type-specific data is JSON. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | kind | [DecodedEventKind](/api/maker-reference#flint-margin-maker-v1-DecodedEventKind) | | | | trader_id | [TraderId](/api/maker-reference#flint-margin-maker-v1-TraderId) | | Trader account touched by the event; unset when venue-global. | | perp_id | [PerpId](/api/maker-reference#flint-margin-maker-v1-PerpId) | | Perp touched by the event; unset for collateral-only actions. | | side | [Side](/api/maker-reference#flint-margin-maker-v1-Side) | | | | price | [Decimal](/api/maker-reference#flint-margin-maker-v1-Decimal) | | | | size | [Decimal](/api/maker-reference#flint-margin-maker-v1-Decimal) | | | | instruction_index | [uint32](/api/maker-reference#uint32) | | | | event_index | [uint32](/api/maker-reference#uint32) | | | | payload_json | [string](/api/maker-reference#string) | | | ### FillEvent Fill where the authenticated trader provided liquidity. Side is the maker's direction. Price and size are scaled from the perp's tick and lot grids; maker_rebate is denominated in the perp's PnL currency. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_id | [PerpId](/api/maker-reference#flint-margin-maker-v1-PerpId) | | | | trader_id | [TraderId](/api/maker-reference#flint-margin-maker-v1-TraderId) | | | | side | [Side](/api/maker-reference#flint-margin-maker-v1-Side) | | | | price | [Decimal](/api/maker-reference#flint-margin-maker-v1-Decimal) | | | | size | [Decimal](/api/maker-reference#flint-margin-maker-v1-Decimal) | | | | slot | [uint64](/api/maker-reference#uint64) | | | | ts | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | | | signature | [string](/api/maker-reference#string) | | | | maker_rebate | [Decimal](/api/maker-reference#flint-margin-maker-v1-Decimal) | | | | taker_id | [TraderId](/api/maker-reference#flint-margin-maker-v1-TraderId) | | | | ref_price_0s | [Decimal](/api/maker-reference#flint-margin-maker-v1-Decimal) | | Quote-denominated markout prices. Set only by GetFills; each horizon is unset when no price was captured. | | ref_price_1s | [Decimal](/api/maker-reference#flint-margin-maker-v1-Decimal) | | | | ref_price_5s | [Decimal](/api/maker-reference#flint-margin-maker-v1-Decimal) | | | | ref_price_15s | [Decimal](/api/maker-reference#flint-margin-maker-v1-Decimal) | | | | ref_price_30s | [Decimal](/api/maker-reference#flint-margin-maker-v1-Decimal) | | | | notional_usd | [Decimal](/api/maker-reference#flint-margin-maker-v1-Decimal) | | | ### MakerActivity One decoded account or quoting action associated with a trader. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | trader_id | [TraderId](/api/maker-reference#flint-margin-maker-v1-TraderId) | | | | perp_id | [PerpId](/api/maker-reference#flint-margin-maker-v1-PerpId) | | Unset for collateral-only actions. | | slot | [uint64](/api/maker-reference#uint64) | | | | ts | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | | | signature | [string](/api/maker-reference#string) | | | | type | [MakerActivityType](/api/maker-reference#flint-margin-maker-v1-MakerActivityType) | | | | payload_json | [string](/api/maker-reference#string) | | Decoded event detail; fields depend on type. | ### MakerMarketSnapshot Perp market snapshot with the authenticated trader's best quotes. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_id | [PerpId](/api/maker-reference#flint-margin-maker-v1-PerpId) | | | | slot | [uint64](/api/maker-reference#uint64) | | | | best_bid | [PerpL2Level](/api/maker-reference#flint-margin-maker-v1-PerpL2Level) | | | | best_ask | [PerpL2Level](/api/maker-reference#flint-margin-maker-v1-PerpL2Level) | | | | bid_depth_levels | [PerpL2Level](/api/maker-reference#flint-margin-maker-v1-PerpL2Level) | repeated | | | ask_depth_levels | [PerpL2Level](/api/maker-reference#flint-margin-maker-v1-PerpL2Level) | repeated | | | last_price | [Decimal](/api/maker-reference#flint-margin-maker-v1-Decimal) | | | | maker_bid | [MakerQuote](/api/maker-reference#flint-margin-maker-v1-MakerQuote) | | | | maker_ask | [MakerQuote](/api/maker-reference#flint-margin-maker-v1-MakerQuote) | | | ### MakerMarketsSnapshotEvent | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | slot | [uint64](/api/maker-reference#uint64) | | | | ts | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | | | markets | [MakerMarketSnapshot](/api/maker-reference#flint-margin-maker-v1-MakerMarketSnapshot) | repeated | | ### MakerQuote The authenticated trader's resting quote on one side of a perp. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | price | [Decimal](/api/maker-reference#flint-margin-maker-v1-Decimal) | | | | size | [Decimal](/api/maker-reference#flint-margin-maker-v1-Decimal) | | | | level | [uint32](/api/maker-reference#uint32) | | | ### PerpL2Level One price level on a perp market book. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | price | [Decimal](/api/maker-reference#flint-margin-maker-v1-Decimal) | | | | size | [Decimal](/api/maker-reference#flint-margin-maker-v1-Decimal) | | | ### QuoteUpdate Submitted quoting intent in a successfully executed transaction. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_id | [PerpId](/api/maker-reference#flint-margin-maker-v1-PerpId) | | | | block_time | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | | | slot | [uint64](/api/maker-reference#uint64) | | | | signature | [string](/api/maker-reference#string) | | | | instruction_index | [uint32](/api/maker-reference#uint32) | | | | entry_index_hint | [uint32](/api/maker-reference#uint32) | | Lookup hint for UpdateQuoting; exact entry index for compact batches. | | trader_id | [TraderId](/api/maker-reference#flint-margin-maker-v1-TraderId) | | | | quoter | [string](/api/maker-reference#string) | | | | strategy | [QuotingStrategy](/api/maker-reference#flint-margin-maker-v1-QuotingStrategy) | | | | sequence | [uint64](/api/maker-reference#uint64) | | | | payload_json | [string](/api/maker-reference#string) | | | | update_kind | [QuoteUpdateKind](/api/maker-reference#flint-margin-maker-v1-QuoteUpdateKind) | | | | update_index | [uint32](/api/maker-reference#uint32) | | Zero for UpdateQuoting, otherwise the one-based item within a compact batch. | | applied | [bool](/api/maker-reference#bool) | optional | Absent when a partial-success batch does not report individual outcomes. | | enabled_after | [bool](/api/maker-reference#bool) | optional | Absent when the update preserves the current enabled flag. | ### QuoterSeriesBucket | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | ts | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | | | rtt_samples | [uint32](/api/maker-reference#uint32) | | | | p50_rtt_micros | [uint64](/api/maker-reference#uint64) | | | | p99_rtt_micros | [uint64](/api/maker-reference#uint64) | | | | quotes | [uint64](/api/maker-reference#uint64) | | | | transactions_sent | [uint64](/api/maker-reference#uint64) | | | | transactions_landed | [uint64](/api/maker-reference#uint64) | | | | transactions_failed | [uint64](/api/maker-reference#uint64) | | | | land_samples | [uint32](/api/maker-reference#uint32) | | | | p50_land_micros | [uint64](/api/maker-reference#uint64) | | | | p99_land_micros | [uint64](/api/maker-reference#uint64) | | | ### QuoterSession One currently-open session for a selected trader. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | session_id | [string](/api/maker-reference#string) | | Server-minted identifier; never the bearer token itself. | | trader_id | [TraderId](/api/maker-reference#flint-margin-maker-v1-TraderId) | | | | quoter | [string](/api/maker-reference#string) | | | | connected_at | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | | | expires_at | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | | | last_seen | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | | | ip | [string](/api/maker-reference#string) | | | | sdk | [string](/api/maker-reference#string) | | | | proto_rev | [string](/api/maker-reference#string) | | | | rtt_micros | [uint64](/api/maker-reference#uint64) | | | | drift_micros | [int64](/api/maker-reference#int64) | | | | region | [string](/api/maker-reference#string) | | | | server_instance | [string](/api/maker-reference#string) | | | | session_lat_lng | [LatLng](/api/maker-reference#flint-margin-maker-v1-LatLng) | optional | | | server_lat_lng | [LatLng](/api/maker-reference#flint-margin-maker-v1-LatLng) | optional | | | last_submitted_at | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | optional | | | last_landed_at | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | optional | | ### QuotingAuthority Aggregate of one authority's currently-open sessions for a trader. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | trader_id | [TraderId](/api/maker-reference#flint-margin-maker-v1-TraderId) | | | | quoter | [string](/api/maker-reference#string) | | Base58 public key that authenticated the sessions. | | open_sessions | [uint32](/api/maker-reference#uint32) | | | | last_seen | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | | | min_rtt_micros | [uint64](/api/maker-reference#uint64) | | | | regions | [string](/api/maker-reference#string) | repeated | | | sdks | [string](/api/maker-reference#string) | repeated | | | last_submitted_at | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | optional | | | last_landed_at | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | optional | | | sol_lamports | [uint64](/api/maker-reference#uint64) | optional | | | sol_balance_as_of | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | optional | | | p50_rtt_micros | [uint64](/api/maker-reference#uint64) | | | | p99_rtt_micros | [uint64](/api/maker-reference#uint64) | | | | rtt_samples | [uint32](/api/maker-reference#uint32) | | | | series | [QuoterSeriesBucket](/api/maker-reference#flint-margin-maker-v1-QuoterSeriesBucket) | repeated | | ### QuotingState Latest observed state for one admitted on-chain quoting entry. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_id | [PerpId](/api/maker-reference#flint-margin-maker-v1-PerpId) | | | | trader_id | [TraderId](/api/maker-reference#flint-margin-maker-v1-TraderId) | | | | entry_index | [uint32](/api/maker-reference#uint32) | | | | sequence | [uint64](/api/maker-reference#uint64) | | Maximum of the independent streams; use their fields when reconciling writes. | | enabled | [bool](/api/maker-reference#bool) | | | | strategy | [QuotingStrategy](/api/maker-reference#flint-margin-maker-v1-QuotingStrategy) | | | | payload_json | [string](/api/maker-reference#string) | | | | slot | [uint64](/api/maker-reference#uint64) | | | | observed_at | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | | | order_sequence | [uint64](/api/maker-reference#uint64) | | | | fair_sequence | [uint64](/api/maker-reference#uint64) | optional | | ### DecodedEventKind | Name | Number | Description | | ---- | ------ | ----------- | | DECODED_EVENT_KIND_UNSPECIFIED | 0 | | | DECODED_EVENT_KIND_COLLATERAL | 1 | | | DECODED_EVENT_KIND_PERP_TRADE | 2 | | | DECODED_EVENT_KIND_PERP_FILL | 3 | | | DECODED_EVENT_KIND_QUOTING_UPDATE | 4 | | | DECODED_EVENT_KIND_PERP_LIQUIDATED | 5 | | | DECODED_EVENT_KIND_FUNDING_UPDATED | 6 | | | DECODED_EVENT_KIND_FUNDING_SETTLED | 7 | | | DECODED_EVENT_KIND_PERP_CONFIG_UPDATED | 8 | | | DECODED_EVENT_KIND_FUNDING_ADVANCED | 9 | | | DECODED_EVENT_KIND_PERP_UPDATE_PROPOSED | 10 | | | DECODED_EVENT_KIND_PERP_UPDATE_CANCELLED | 11 | | ### MakerActivityType | Name | Number | Description | | ---- | ------ | ----------- | | MAKER_ACTIVITY_TYPE_UNSPECIFIED | 0 | | | MAKER_ACTIVITY_TYPE_COLLATERAL | 1 | | | MAKER_ACTIVITY_TYPE_PERP_TRADE | 2 | | | MAKER_ACTIVITY_TYPE_QUOTING_UPDATE | 3 | | ### QuoteUpdateKind | Name | Number | Description | | ---- | ------ | ----------- | | QUOTE_UPDATE_KIND_UNSPECIFIED | 0 | | | QUOTE_UPDATE_KIND_KEEP | 1 | | | QUOTE_UPDATE_KIND_RESET | 2 | | | QUOTE_UPDATE_KIND_ORDER_LIST | 3 | | | QUOTE_UPDATE_KIND_SPLINE_FAIR | 4 | | | QUOTE_UPDATE_KIND_SPLINE_BANDS | 5 | | | QUOTE_UPDATE_KIND_DENSITY_FAIR | 6 | | | QUOTE_UPDATE_KIND_DENSITY_BANDS | 7 | | | QUOTE_UPDATE_KIND_RELATIVE_DENSITY_BANDS | 8 | | ### QuotingStrategy | Name | Number | Description | | ---- | ------ | ----------- | | QUOTING_STRATEGY_UNSPECIFIED | 0 | | | QUOTING_STRATEGY_ORDER_LIST | 1 | | | QUOTING_STRATEGY_SPLINE | 2 | | | QUOTING_STRATEGY_DENSITY | 3 | | | QUOTING_STRATEGY_RELATIVE_DENSITY | 4 | | ### TransactionStatus | Name | Number | Description | | ---- | ------ | ----------- | | TRANSACTION_STATUS_UNSPECIFIED | 0 | | | TRANSACTION_STATUS_CONFIRMED | 1 | | | TRANSACTION_STATUS_FAILED | 2 | |

Top

## flint/margin/maker/v1/maker/messages.proto ### GetDecodedTransactionRequest Address a transaction by its base58 signature. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | signature | [string](/api/maker-reference#string) | | | ### GetDecodedTransactionResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | signature | [string](/api/maker-reference#string) | | | | slot | [uint64](/api/maker-reference#uint64) | | | | block_time | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | | | status | [TransactionStatus](/api/maker-reference#flint-margin-maker-v1-TransactionStatus) | | | | fee_lamports | [uint64](/api/maker-reference#uint64) | optional | Unset when transaction metadata was not archived. | | events | [DecodedEvent](/api/maker-reference#flint-margin-maker-v1-DecodedEvent) | repeated | Ordered by instruction_index, then event_index. | ### GetFillsRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_ids | [PerpId](/api/maker-reference#flint-margin-maker-v1-PerpId) | repeated | | | start | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | | | end | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | | | limit | [uint32](/api/maker-reference#uint32) | | | | cursor | [string](/api/maker-reference#string) | | | | order_by | [MakerFillOrder](/api/maker-reference#flint-margin-maker-v1-MakerFillOrder) | | | | markout_horizon | [MarkoutHorizon](/api/maker-reference#flint-margin-maker-v1-MarkoutHorizon) | | Horizon used by ABS_MARKOUT_DESC; ignored for TIME_DESC. | ### GetFillsResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | fills | [FillEvent](/api/maker-reference#flint-margin-maker-v1-FillEvent) | repeated | | | next_cursor | [string](/api/maker-reference#string) | | | ### GetMakerActivityRequest Historical actions performed by the session's selected trader. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | start | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | | | end | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | | | limit | [uint32](/api/maker-reference#uint32) | | Zero uses the server default; maximum 1000. | | types | [MakerActivityType](/api/maker-reference#flint-margin-maker-v1-MakerActivityType) | repeated | | ### GetMakerActivityResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | activities | [MakerActivity](/api/maker-reference#flint-margin-maker-v1-MakerActivity) | repeated | Ordered newest first. | ### GetMakerMarkoutSummaryRequest Whole-window markout summary, without GetFills' page limit. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_ids | [PerpId](/api/maker-reference#flint-margin-maker-v1-PerpId) | repeated | | | start | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | Inclusive lower bound; unset selects the server retention window. | | end | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | Exclusive upper bound; unset selects now. | | horizon | [MarkoutHorizon](/api/maker-reference#flint-margin-maker-v1-MarkoutHorizon) | | | ### GetMakerMarkoutSummaryResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | net | [double](/api/maker-reference#double) | | | | adverse | [double](/api/maker-reference#double) | | Absolute USD value of negative-markout fills. | | favorable | [double](/api/maker-reference#double) | | | | volume_usd | [double](/api/maker-reference#double) | | | | fills | [uint64](/api/maker-reference#uint64) | | | | fills_with_markout | [uint64](/api/maker-reference#uint64) | | | | fills_unpriced | [uint64](/api/maker-reference#uint64) | | Non-zero means USD totals are lower bounds. | | fills_missing_ref | [uint64](/api/maker-reference#uint64) | | | | avg_bps | [double](/api/maker-reference#double) | optional | | | avg_per_fill | [double](/api/maker-reference#double) | optional | | | buckets | [MarkoutBucket](/api/maker-reference#flint-margin-maker-v1-MarkoutBucket) | repeated | | | perps | [MarkoutPerpRow](/api/maker-reference#flint-margin-maker-v1-MarkoutPerpRow) | repeated | Ordered by adverse selection descending. | | histogram | [MarkoutHistogramBin](/api/maker-reference#flint-margin-maker-v1-MarkoutHistogramBin) | repeated | | | effective_start | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | Actual queried window after retention clamping. | | effective_end | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | | ### GetMakerStatsRequest ### GetMakerStatsResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | trader_id | [TraderId](/api/maker-reference#flint-margin-maker-v1-TraderId) | | | | fills | [UpdateRate](/api/maker-reference#flint-margin-maker-v1-UpdateRate) | | | | transactions_sent | [UpdateRate](/api/maker-reference#flint-margin-maker-v1-UpdateRate) | | | | transactions_landed | [UpdateRate](/api/maker-reference#flint-margin-maker-v1-UpdateRate) | | | | transactions_failed | [UpdateRate](/api/maker-reference#flint-margin-maker-v1-UpdateRate) | | | | oracle_updates | [UpdateRate](/api/maker-reference#flint-margin-maker-v1-UpdateRate) | | | | quote_updates | [UpdateRate](/api/maker-reference#flint-margin-maker-v1-UpdateRate) | | | ### GetMakerVolumeBreakdownRequest Per-trader maker volume, split by rolling window. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_ids | [PerpId](/api/maker-reference#flint-margin-maker-v1-PerpId) | repeated | Empty selects every perp where the session's trader has maker fills. | ### GetMakerVolumeBreakdownResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perps | [PerpVolume](/api/maker-reference#flint-margin-maker-v1-PerpVolume) | repeated | Ordered by 24-hour USD volume descending. | ### GetMakerVolumeSeriesRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_id | [PerpId](/api/maker-reference#flint-margin-maker-v1-PerpId) | | Unset aggregates every perp where the session's trader has maker fills. | | window | [StatsWindow](/api/maker-reference#flint-margin-maker-v1-StatsWindow) | | | ### GetMakerVolumeSeriesResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | buckets | [VolumeBucket](/api/maker-reference#flint-margin-maker-v1-VolumeBucket) | repeated | Ordered oldest first. | | bucket_seconds | [uint32](/api/maker-reference#uint32) | | | ### GetQuoteRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | signature | [string](/api/maker-reference#string) | | | | instruction_index | [uint32](/api/maker-reference#uint32) | | | | update_index | [uint32](/api/maker-reference#uint32) | | | ### GetQuoteResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | quote | [QuoteUpdate](/api/maker-reference#flint-margin-maker-v1-QuoteUpdate) | | | ### GetQuotingStateRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_ids | [PerpId](/api/maker-reference#flint-margin-maker-v1-PerpId) | repeated | | ### GetQuotingStateResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | entries | [QuotingState](/api/maker-reference#flint-margin-maker-v1-QuotingState) | repeated | | ### GetSdkCredentialsRequest ### GetSdkCredentialsResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | token | [string](/api/maker-reference#string) | | Plaintext read-only registry token. Repeated calls return the same token. | | token_name | [string](/api/maker-reference#string) | | | | index_url | [string](/api/maker-reference#string) | | | | registry_name | [string](/api/maker-reference#string) | | | | created_at | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | | ### ListQuoterSessionsRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | quoter | [string](/api/maker-reference#string) | | Empty includes every session observed for the selected trader. | ### ListQuoterSessionsResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | sessions | [QuoterSession](/api/maker-reference#flint-margin-maker-v1-QuoterSession) | repeated | Ordered most-recently-seen first. | ### ListQuotingAuthoritiesRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | quoter | [string](/api/maker-reference#string) | | Empty includes every authority observed for the selected trader. | ### ListQuotingAuthoritiesResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | authorities | [QuotingAuthority](/api/maker-reference#flint-margin-maker-v1-QuotingAuthority) | repeated | Ordered by open session count descending. | | bucket_seconds | [uint32](/api/maker-reference#uint32) | | | | series_start | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | | | series_end | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | | ### ListRecentQuotesRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_ids | [PerpId](/api/maker-reference#flint-margin-maker-v1-PerpId) | repeated | | | quoters | [string](/api/maker-reference#string) | repeated | | | start | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | optional | | | end | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | optional | | | limit | [uint32](/api/maker-reference#uint32) | | | | cursor | [string](/api/maker-reference#string) | | | ### ListRecentQuotesResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | quotes | [QuoteUpdate](/api/maker-reference#flint-margin-maker-v1-QuoteUpdate) | repeated | | | next_cursor | [string](/api/maker-reference#string) | | | ### MarkoutBucket | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | index | [uint32](/api/maker-reference#uint32) | | | | net | [double](/api/maker-reference#double) | | | | adverse | [double](/api/maker-reference#double) | | | | favorable | [double](/api/maker-reference#double) | | | ### MarkoutHistogramBin | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | min | [double](/api/maker-reference#double) | optional | Unset edges are unbounded. | | max | [double](/api/maker-reference#double) | optional | | | count | [uint64](/api/maker-reference#uint64) | | | | volume_usd | [double](/api/maker-reference#double) | | | | net | [double](/api/maker-reference#double) | | | ### MarkoutPerpRow | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_id | [PerpId](/api/maker-reference#flint-margin-maker-v1-PerpId) | | | | net | [double](/api/maker-reference#double) | | | | adverse | [double](/api/maker-reference#double) | | | | favorable | [double](/api/maker-reference#double) | | | | volume_usd | [double](/api/maker-reference#double) | | | | fills | [uint64](/api/maker-reference#uint64) | | | | fills_with_markout | [uint64](/api/maker-reference#uint64) | | | | avg_bps | [double](/api/maker-reference#double) | optional | | ### PerpVolume | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_id | [PerpId](/api/maker-reference#flint-margin-maker-v1-PerpId) | | | | one_hour | [VolumeWindow](/api/maker-reference#flint-margin-maker-v1-VolumeWindow) | | | | twenty_four_hour | [VolumeWindow](/api/maker-reference#flint-margin-maker-v1-VolumeWindow) | | | | thirty_day | [VolumeWindow](/api/maker-reference#flint-margin-maker-v1-VolumeWindow) | | | | last_price | [Decimal](/api/maker-reference#flint-margin-maker-v1-Decimal) | | Most recent observed fill price. | ### SubscribeFillsRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_ids | [PerpId](/api/maker-reference#flint-margin-maker-v1-PerpId) | repeated | | ### SubscribeMakerMarketSnapshotsRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_ids | [PerpId](/api/maker-reference#flint-margin-maker-v1-PerpId) | repeated | Empty selects every perp admitted for the session's trader. | ### SubscribeQuotesRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | perp_ids | [PerpId](/api/maker-reference#flint-margin-maker-v1-PerpId) | repeated | | | quoters | [string](/api/maker-reference#string) | repeated | | ### UpdateRate | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | per_second_1s | [double](/api/maker-reference#double) | | | | per_second_1m | [double](/api/maker-reference#double) | | | | per_second_5m | [double](/api/maker-reference#double) | | | ### VolumeBucket | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | ts | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | | | volume_usd | [Decimal](/api/maker-reference#flint-margin-maker-v1-Decimal) | | | | fill_count | [uint64](/api/maker-reference#uint64) | | | ### VolumeWindow | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | volume_usd | [Decimal](/api/maker-reference#flint-margin-maker-v1-Decimal) | | Notional in USD, counted once for each matched long/short pair. | | fill_count | [uint64](/api/maker-reference#uint64) | | | ### MakerFillOrder | Name | Number | Description | | ---- | ------ | ----------- | | MAKER_FILL_ORDER_UNSPECIFIED | 0 | | | MAKER_FILL_ORDER_TIME_DESC | 1 | | | MAKER_FILL_ORDER_ABS_MARKOUT_DESC | 2 | Largest absolute USD markout first; fills without one sort last. | ### MarkoutHorizon | Name | Number | Description | | ---- | ------ | ----------- | | MARKOUT_HORIZON_UNSPECIFIED | 0 | | | MARKOUT_HORIZON_SEC_0 | 1 | | | MARKOUT_HORIZON_SEC_1 | 2 | | | MARKOUT_HORIZON_SEC_5 | 3 | | | MARKOUT_HORIZON_SEC_15 | 4 | | | MARKOUT_HORIZON_SEC_30 | 5 | | ### StatsWindow | Name | Number | Description | | ---- | ------ | ----------- | | STATS_WINDOW_UNSPECIFIED | 0 | | | STATS_WINDOW_1H | 1 | | | STATS_WINDOW_24H | 2 | | | STATS_WINDOW_30D | 3 | |

Top

## flint/margin/maker/v1/maker/service.proto ### MakerService Session-authenticated maker queries and streams. | Method Name | Request Type | Response Type | Description | | ----------- | ------------ | ------------- | ------------| | SubscribeMarketSnapshots | [SubscribeMakerMarketSnapshotsRequest](/api/maker-reference#flint-margin-maker-v1-SubscribeMakerMarketSnapshotsRequest) | [MakerMarketsSnapshotEvent](/api/maker-reference#flint-margin-maker-v1-MakerMarketsSnapshotEvent) stream | | | SubscribeFills | [SubscribeFillsRequest](/api/maker-reference#flint-margin-maker-v1-SubscribeFillsRequest) | [FillEvent](/api/maker-reference#flint-margin-maker-v1-FillEvent) stream | | | GetFills | [GetFillsRequest](/api/maker-reference#flint-margin-maker-v1-GetFillsRequest) | [GetFillsResponse](/api/maker-reference#flint-margin-maker-v1-GetFillsResponse) | | | GetVolumeBreakdown | [GetMakerVolumeBreakdownRequest](/api/maker-reference#flint-margin-maker-v1-GetMakerVolumeBreakdownRequest) | [GetMakerVolumeBreakdownResponse](/api/maker-reference#flint-margin-maker-v1-GetMakerVolumeBreakdownResponse) | | | GetVolumeSeries | [GetMakerVolumeSeriesRequest](/api/maker-reference#flint-margin-maker-v1-GetMakerVolumeSeriesRequest) | [GetMakerVolumeSeriesResponse](/api/maker-reference#flint-margin-maker-v1-GetMakerVolumeSeriesResponse) | | | GetMarkoutSummary | [GetMakerMarkoutSummaryRequest](/api/maker-reference#flint-margin-maker-v1-GetMakerMarkoutSummaryRequest) | [GetMakerMarkoutSummaryResponse](/api/maker-reference#flint-margin-maker-v1-GetMakerMarkoutSummaryResponse) | | | GetQuotingState | [GetQuotingStateRequest](/api/maker-reference#flint-margin-maker-v1-GetQuotingStateRequest) | [GetQuotingStateResponse](/api/maker-reference#flint-margin-maker-v1-GetQuotingStateResponse) | | | SubscribeQuotes | [SubscribeQuotesRequest](/api/maker-reference#flint-margin-maker-v1-SubscribeQuotesRequest) | [QuoteUpdate](/api/maker-reference#flint-margin-maker-v1-QuoteUpdate) stream | | | ListRecentQuotes | [ListRecentQuotesRequest](/api/maker-reference#flint-margin-maker-v1-ListRecentQuotesRequest) | [ListRecentQuotesResponse](/api/maker-reference#flint-margin-maker-v1-ListRecentQuotesResponse) | | | GetQuote | [GetQuoteRequest](/api/maker-reference#flint-margin-maker-v1-GetQuoteRequest) | [GetQuoteResponse](/api/maker-reference#flint-margin-maker-v1-GetQuoteResponse) | | | GetStats | [GetMakerStatsRequest](/api/maker-reference#flint-margin-maker-v1-GetMakerStatsRequest) | [GetMakerStatsResponse](/api/maker-reference#flint-margin-maker-v1-GetMakerStatsResponse) | | | GetActivity | [GetMakerActivityRequest](/api/maker-reference#flint-margin-maker-v1-GetMakerActivityRequest) | [GetMakerActivityResponse](/api/maker-reference#flint-margin-maker-v1-GetMakerActivityResponse) | | | GetDecodedTransaction | [GetDecodedTransactionRequest](/api/maker-reference#flint-margin-maker-v1-GetDecodedTransactionRequest) | [GetDecodedTransactionResponse](/api/maker-reference#flint-margin-maker-v1-GetDecodedTransactionResponse) | | | ListQuotingAuthorities | [ListQuotingAuthoritiesRequest](/api/maker-reference#flint-margin-maker-v1-ListQuotingAuthoritiesRequest) | [ListQuotingAuthoritiesResponse](/api/maker-reference#flint-margin-maker-v1-ListQuotingAuthoritiesResponse) | | | ListQuoterSessions | [ListQuoterSessionsRequest](/api/maker-reference#flint-margin-maker-v1-ListQuoterSessionsRequest) | [ListQuoterSessionsResponse](/api/maker-reference#flint-margin-maker-v1-ListQuoterSessionsResponse) | | | GetSdkCredentials | [GetSdkCredentialsRequest](/api/maker-reference#flint-margin-maker-v1-GetSdkCredentialsRequest) | [GetSdkCredentialsResponse](/api/maker-reference#flint-margin-maker-v1-GetSdkCredentialsResponse) | |

Top

## flint/margin/maker/v1/node/messages.proto ### GetNodeInfoRequest ### GetNodeInfoResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | version | [string](/api/maker-reference#string) | | Server build identity. | | git_commit | [string](/api/maker-reference#string) | | | | proto_hash | [string](/api/maker-reference#string) | | Schema revision used to build this node. | | proto_rev | [string](/api/maker-reference#string) | | | | cluster | [string](/api/maker-reference#string) | | Solana cluster this node is pointed at (e.g. "mainnet-beta"). | | region | [string](/api/maker-reference#string) | | Deploy region owning this node. | | program_id | [string](/api/maker-reference#string) | | Base58 program id of the on-chain venue this node serves. | | started_at | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | Process start time. | | leader_proximity | [LeaderProximity](/api/maker-reference#flint-margin-maker-v1-LeaderProximity) | | | | server_instance | [string](/api/maker-reference#string) | | Per-process server instance ID. | | region_lat_lng | [LatLng](/api/maker-reference#flint-margin-maker-v1-LatLng) | optional | Approximate region location; absent when unavailable. | | health | [HealthState](/api/maker-reference#flint-margin-maker-v1-HealthState) | | Health of this node only. | | health_reason | [string](/api/maker-reference#string) | | Why `health` is not HEALTH_STATE_HEALTHY. Empty when it is. | ### LeaderProximity Observed round-trip distance to block leaders. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | measured | [uint32](/api/maker-reference#uint32) | | Zero means no sample; distance fields are then meaningless. | | mean_distance_micros | [uint32](/api/maker-reference#uint32) | | | | median_distance_micros | [uint32](/api/maker-reference#uint32) | | | | p90_distance_micros | [uint32](/api/maker-reference#uint32) | | | ### ListNodesRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | cluster | [string](/api/maker-reference#string) | | Empty includes every cluster. | ### ListNodesResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | nodes | [NodeEndpoint](/api/maker-reference#flint-margin-maker-v1-NodeEndpoint) | repeated | Published endpoints only; entries do not imply current health. | ### NodeEndpoint | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | endpoint | [string](/api/maker-reference#string) | | gRPC endpoint, as host:port or URL. | | region | [string](/api/maker-reference#string) | | | | region_lat_lng | [LatLng](/api/maker-reference#flint-margin-maker-v1-LatLng) | optional | Approximate region location; absent when unavailable. | | cluster | [string](/api/maker-reference#string) | | | | current | [bool](/api/maker-reference#bool) | | True for the endpoint serving this request. |

Top

## flint/margin/maker/v1/node/service.proto ### NodeService Public node identity and endpoint discovery. | Method Name | Request Type | Response Type | Description | | ----------- | ------------ | ------------- | ------------| | GetNodeInfo | [GetNodeInfoRequest](/api/maker-reference#flint-margin-maker-v1-GetNodeInfoRequest) | [GetNodeInfoResponse](/api/maker-reference#flint-margin-maker-v1-GetNodeInfoResponse) | | | ListNodes | [ListNodesRequest](/api/maker-reference#flint-margin-maker-v1-ListNodesRequest) | [ListNodesResponse](/api/maker-reference#flint-margin-maker-v1-ListNodesResponse) | |

Top

## flint/margin/maker/v1/session/messages.proto ### Ping Server liveness probe; clients echo seq in an immediate Pong. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | local_time | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | Server wall-clock at send time. | | seq | [uint64](/api/maker-reference#uint64) | | Per-stream correlation nonce. | ### Pong Client reply to a `Ping`, sent immediately on receipt. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | local_time | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | Client wall-clock at reply time; used to estimate clock drift. | | seq | [uint64](/api/maker-reference#uint64) | | Echo of the originating `Ping.seq`. | ### SessionRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | pong | [Pong](/api/maker-reference#flint-margin-maker-v1-Pong) | | | ### SessionResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | ping | [Ping](/api/maker-reference#flint-margin-maker-v1-Ping) | | |

Top

## flint/margin/maker/v1/session/service.proto ### SessionService Server sends a Ping every 30s; clients reply immediately with a Pong. Requires a keypair session via `authorization: Bearer <token>` metadata. | Method Name | Request Type | Response Type | Description | | ----------- | ------------ | ------------- | ------------| | Session | [SessionRequest](/api/maker-reference#flint-margin-maker-v1-SessionRequest) stream | [SessionResponse](/api/maker-reference#flint-margin-maker-v1-SessionResponse) stream | |

Top

## flint/margin/maker/v1/spread_protect/events.proto ### ResetState Replace the client's local SpreadProtect state. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | default_spread_mult | [Decimal](/api/maker-reference#flint-margin-maker-v1-Decimal) | | Baseline multiplier to apply when no widen window is active. | ### SpreadProtectEvent Spread-protection stream event. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | reset_state | [ResetState](/api/maker-reference#flint-margin-maker-v1-ResetState) | | | | widen_window | [WidenWindow](/api/maker-reference#flint-margin-maker-v1-WidenWindow) | | | ### WidenWindow Idempotent advisory for an inclusive slot range; expires after end_slot. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | start_slot | [uint64](/api/maker-reference#uint64) | | Inclusive first slot of the window. | | end_slot | [uint64](/api/maker-reference#uint64) | | Inclusive last slot of the window. | | spread_mult | [Decimal](/api/maker-reference#flint-margin-maker-v1-Decimal) | | Exact decimal multiplier, at least 1.0. | | ts | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | Server send time, for client-side latency/attribution. Not policy. |

Top

## flint/margin/maker/v1/spread_protect/messages.proto ### SubscribeSpreadProtectRequest Subscribe to the SpreadProtect widen-window feed. No filters: the policy is server-side and the same windows apply to every maker.

Top

## flint/margin/maker/v1/spread_protect/service.proto ### SpreadProtectService Keypair-session-authenticated advisory spread-protection feed. | Method Name | Request Type | Response Type | Description | | ----------- | ------------ | ------------- | ------------| | SubscribeSpreadProtect | [SubscribeSpreadProtectRequest](/api/maker-reference#flint-margin-maker-v1-SubscribeSpreadProtectRequest) | [SpreadProtectEvent](/api/maker-reference#flint-margin-maker-v1-SpreadProtectEvent) stream | Sends a reset followed by current and future widen windows. |

Top

## flint/margin/maker/v1/tx/events.proto ### BlockhashEvent | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | blockhash | [Blockhash](/api/maker-reference#flint-margin-maker-v1-Blockhash) | | | | recommended_cu_price | [uint64](/api/maker-reference#uint64) | | Priority-fee recommendation in microlamports per CU; zero is unavailable. | | ts | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | | | last_valid_block_height | [uint64](/api/maker-reference#uint64) | | Solana lastValidBlockHeight; zero is unavailable. | ### LeaderEvent | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | start_slot | [uint64](/api/maker-reference#uint64) | | | | end_slot | [uint64](/api/maker-reference#uint64) | | | | tpu_ip | [string](/api/maker-reference#string) | | | | tpu_quic_port | [uint32](/api/maker-reference#uint32) | | | | ts | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | | ### SlotEvent | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | slot | [uint64](/api/maker-reference#uint64) | | | | ts | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | | ### TxAckEvent Accepted for submission; not confirmation. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | signature | [string](/api/maker-reference#string) | | | | ts | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | | ### TxFailedEvent Reverted after landing or timed out before landing. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | signature | [string](/api/maker-reference#string) | | | | reason | [string](/api/maker-reference#string) | | | | ts | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | | ### TxLandedEvent Observed at Processed commitment with no error. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | signature | [string](/api/maker-reference#string) | | | | slot | [uint64](/api/maker-reference#uint64) | | | | ts | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | | ### TxStatusEvent | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | ack | [TxAckEvent](/api/maker-reference#flint-margin-maker-v1-TxAckEvent) | | | | landed | [TxLandedEvent](/api/maker-reference#flint-margin-maker-v1-TxLandedEvent) | | | | failed | [TxFailedEvent](/api/maker-reference#flint-margin-maker-v1-TxFailedEvent) | | |

Top

## flint/margin/maker/v1/tx/messages.proto ### SubmitTxRequest | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | transaction | [bytes](/api/maker-reference#bytes) | | Fully signed legacy or v0 transaction. The authenticated key must be a required static signer. The selected trader scopes status attribution; the server does not infer transaction account ownership. | | client_sent_at | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | Client send time for latency telemetry only. | ### SubmitTxResponse | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | signature | [string](/api/maker-reference#string) | | Base58 signature for correlating TxStatusEvents. | | ts | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | Server time when the submission was accepted. | | server_replied_at | [Timestamp](/api/maker-reference#flint-margin-maker-v1-Timestamp) | | Server reply time. Subtract ts for server handling time without clock skew. | ### SubscribeBlockhashRequest ### SubscribeLeadersRequest ### SubscribeSlotsRequest ### SubscribeTxStatusRequest Streams status transitions attributed to this session's selected trader.

Top

## flint/margin/maker/v1/tx/service.proto ### TxService Requires `authorization: Bearer <token>` metadata from AuthService. | Method Name | Request Type | Response Type | Description | | ----------- | ------------ | ------------- | ------------| | SubmitTx | [SubmitTxRequest](/api/maker-reference#flint-margin-maker-v1-SubmitTxRequest) | [SubmitTxResponse](/api/maker-reference#flint-margin-maker-v1-SubmitTxResponse) | Returns after acceptance; status transitions arrive on SubscribeTxStatus. | | SubscribeBlockhash | [SubscribeBlockhashRequest](/api/maker-reference#flint-margin-maker-v1-SubscribeBlockhashRequest) | [BlockhashEvent](/api/maker-reference#flint-margin-maker-v1-BlockhashEvent) stream | | | SubscribeSlots | [SubscribeSlotsRequest](/api/maker-reference#flint-margin-maker-v1-SubscribeSlotsRequest) | [SlotEvent](/api/maker-reference#flint-margin-maker-v1-SlotEvent) stream | | | SubscribeTxStatus | [SubscribeTxStatusRequest](/api/maker-reference#flint-margin-maker-v1-SubscribeTxStatusRequest) | [TxStatusEvent](/api/maker-reference#flint-margin-maker-v1-TxStatusEvent) stream | | | SubscribeLeaders | [SubscribeLeadersRequest](/api/maker-reference#flint-margin-maker-v1-SubscribeLeadersRequest) | [LeaderEvent](/api/maker-reference#flint-margin-maker-v1-LeaderEvent) stream | | ## Scalar Value Types | .proto Type | Notes | C++ | Java | Python | Go | C# | PHP | Ruby | | ----------- | ----- | --- | ---- | ------ | -- | -- | --- | ---- | | double | | double | double | float | float64 | double | float | Float | | float | | float | float | float | float32 | float | float | Float | | int32 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) | | int64 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. | int64 | long | int/long | int64 | long | integer/string | Bignum | | uint32 | Uses variable-length encoding. | uint32 | int | int/long | uint32 | uint | integer | Bignum or Fixnum (as required) | | uint64 | Uses variable-length encoding. | uint64 | long | int/long | uint64 | ulong | integer/string | Bignum or Fixnum (as required) | | sint32 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) | | sint64 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. | int64 | long | int/long | int64 | long | integer/string | Bignum | | fixed32 | Always four bytes. More efficient than uint32 if values are often greater than 2^28. | uint32 | int | int | uint32 | uint | integer | Bignum or Fixnum (as required) | | fixed64 | Always eight bytes. More efficient than uint64 if values are often greater than 2^56. | uint64 | long | int/long | uint64 | ulong | integer/string | Bignum | | sfixed32 | Always four bytes. | int32 | int | int | int32 | int | integer | Bignum or Fixnum (as required) | | sfixed64 | Always eight bytes. | int64 | long | int/long | int64 | long | integer/string | Bignum | | bool | | bool | boolean | boolean | bool | bool | boolean | TrueClass/FalseClass | | string | A string must always contain UTF-8 encoded or 7-bit ASCII text. | string | String | str/unicode | string | string | string | String (UTF-8) | | bytes | May contain any arbitrary sequence of bytes. | string | ByteString | str | \[]byte | ByteString | string | String (ASCII-8BIT) | # SDK overview Flint perps ships clients in three languages. All three release together as one SDK suite version and decode the same program ABI, pinned by the same golden fixtures. | Language | Packages | Guide | | --- | --- | --- | | Rust | `flint-margin-user-client`, `flint-margin-maker-client`, `flint-margin-onchain` | [Rust](/sdks/rust) | | Python | `flint-api-client` (`flint.margin.api`, `flint.margin.onchain`, `flint.margin.quoting`) | [Python](/sdks/python) | | TypeScript | `@superis-labs/flint-margin-user-client`, `@superis-labs/flint-margin-maker-client`, `@superis-labs/flint-idl` | [TypeScript](/sdks/typescript) | ## Capabilities | Capability | Rust | Python | TypeScript | | --- | --- | --- | --- | | Instruction builders, account and type codecs, PDAs, error table | Yes | Yes | Yes | | Checked account decode (owner, discriminator, exact length) | `parse::FlintAccount` | `decode_flint_account` | `decodeFlintAccount` | | Event batch decode | Yes | Yes | Yes | | Transaction decode, including inner instructions | `decode_flint_transaction` | `decode_flint_transaction` | `decodeFlintTransaction` | | Program error name from code | `error_name` | `flint_error_name` | `getFlintErrorMessage` | | Trader spot and perp positions | Yes | Yes | Yes | | Resting order book reader | `read_resting_orders` | `read_resting_orders` | `readRestingOrders` | | Maker quote book decode | `decode_aggregated_quote_book` | `decode_aggregated_quote_book` | `decodeAggregatedQuoteBook` | | Build place-order and close instructions with crossing makers | Yes | Yes | Yes | | Grid math (lots, ticks, slippage bounds) | Yes | Yes | Yes | | Prepare an order from chain (`prepare_perp_order`) | `rpc` feature of the user client | Yes (async) | `preparePerpOrder` | | Public margin-user services | `Client` | `MarginUserClient` | `create*Client` | | Maker auth (signed nonce, refresh) and maker services | Yes | Yes | Yes | | `TxService` with Solana-RPC fallback and status polling | In `QuotingCore` | `with_tx_fallback`, `poll_tx_status` | `withTxFallback`, `pollTxStatus` | | `QuoteBuilder` | Yes | Yes | Yes (builds instructions only) | | `QuotingCore` (sign, submit, retry, receipts, spread protection) | Yes | Yes | No | | Session heartbeat (`SessionService`) | Yes | Yes | No (gRPC-Web has no bidi streams) | | Passkey / WebAuthn delegate helpers | No | No | Yes | | Lazer oracle-report helpers | No | No | Yes | TypeScript targets web apps, so like the spot SDKs it has no long-running quoting core; build quotes with `QuoteBuilder` and submit them yourself. Passkey and Lazer helpers depend on browser APIs and stay TypeScript-only. ## Devnet server gaps On devnet today `RiskService.SimulateAccountRisk` returns `INTERNAL` ("perp risk inputs are unavailable") and `OrderService.ListOrders` returns `UNIMPLEMENTED`, in every language. Read resting orders from chain with the resting order book reader until the server serves them. # Rust SDK Async, tonic-based clients for the Flint perps API, shipped as three crates that release together with the rest of the Flint SDK suite: | Crate | Library | What it covers | | --- | --- | --- | | `flint-margin-user-client` | `flint_margin_user_client` | Market and account services, plus owner-authenticated notifications. | | `flint-margin-maker-client` | `flint_margin_maker_client` | `flint.margin.maker.v1` — a managed `Client` with sessions, node discovery, reconnecting streams, and chain state; a quote builder with landing receipts; every maker service. Re-exports the user client as `public_api`. | | `flint-margin-onchain` | `flint_margin_onchain` | Instruction builders, account decoders, PDAs, and error codes for the margin program. Re-exported by both clients as `onchain`. See [On-chain clients](/sdks/onchain). | All three are edition 2024 with an MSRV of Rust 1.89, and share transport, auth, and version-stamping code through `flint-client-core`, which Cargo pulls in for you. ## Install From the Kellnr registry: ```toml [dependencies] flint-margin-user-client = { version = "=7.0.4", registry = "kellnr" } flint-margin-maker-client = { version = "=7.0.4", registry = "kellnr" } tokio = { version = "1", features = ["macros", "rt-multi-thread"] } ``` Add only the crate you need — a read-only frontend or analytics job uses `flint-margin-user-client`. Each client crate pins its `flint-client-core` and `flint-margin-onchain` — and the maker crate its `flint-margin-user-client` — to the exact same version, so you never mix layouts. Add `features = ["rpc"]` to the maker crate for RPC and QUIC fan-out ([Cargo features](/sdks/rust#cargo-features)). The registry is the same one the spot SDK uses. Point Cargo at it in `~/.cargo/config.toml`: ```toml [registries.kellnr] credential-provider = "cargo:token" index = "sparse+https://kellnr.xantasoft.com/api/v1/crates/" ``` then store your read-only registry token with `cargo login --registry kellnr`, or export `CARGO_REGISTRIES_KELLNR_TOKEN=` in CI. When the operator enables SDK credentials, the **Developer** tab of the maker dashboard and `MakerService.GetSdkCredentials` return a read-only registry token and index URL for a trader linked to an organization. Otherwise, obtain package access from the operator. Use the dashboard URL for your network. ### From git :::info[Source installs] Use the operator's published package versions for onboarding. A Git checkout requires separate source access. ::: Each release is also tagged in the SDK repository (`rust-v`), and the crates build straight from a checkout — `build.rs` falls back to the `flint-protos` submodule when no vendored protos are present. The repository is private, so Cargo needs GitHub credentials that can read it (for example `net.git-fetch-with-cli = true` plus your own git auth). ```toml [dependencies] flint-margin-user-client = { git = "https://github.com/superis-labs/flint-api-client", tag = "rust-v7.0.4" } ``` ## Public data — `flint-margin-user-client` `Client` owns one tonic channel to the public host and hands out a generated client per service. Every call stamps the SDK identity headers; the public data calls below need no credentials. ```rust use std::str::FromStr; use flint_margin_user_client::proto::{ GetAccountRequest, ListPerpMarketsRequest, SubscribeOrderBookRequest, TraderId, }; use flint_margin_user_client::{Client, DEVNET_PUBLIC_ENDPOINT}; use rust_decimal::Decimal; #[tokio::main] async fn main() -> anyhow::Result<()> { // This example selects devnet explicitly; use the operator's URL for another network. let client = Client::connect(DEVNET_PUBLIC_ENDPOINT).await?; let markets = client .perp_market() .list_perp_markets(ListPerpMarketsRequest { perp_ids: vec![], statuses: vec![], page_size: 0, page_token: String::new(), }) .await? .into_inner() .markets; for market in &markets { println!("{} tick={:?}", market.symbol, market.tick_size); } let account = client .account() .get_account(GetAccountRequest { trader_id: Some(TraderId { id: 42 }), }) .await? .into_inner() .account; if let Some(health) = account.and_then(|a| a.health) { let equity = Decimal::from_str(&health.net_equity.unwrap_or_default().value)?; println!("net equity {equity} USD"); } // Server stream: a snapshot, then sequenced deltas. let mut book = client .market_data() .subscribe_order_book(SubscribeOrderBookRequest { perp_id: markets[0].perp_id.clone(), depth: 20, }) .await? .into_inner(); while let Some(update) = book.message().await? { println!("snapshot={} seq={}", update.snapshot, update.sequence); } Ok(()) } ``` `Client` exposes `market()`, `account()`, `risk()`, `sponsorship()`, `funding()`, `perp_market()`, `market_data()`, and `order()`. It is cheap to clone. For full control, build any generated client in `flint_margin_user_client::proto` (for example `proto::account_service_client::AccountServiceClient`) over your own `tonic::transport::Channel`. ## Account notifications `Client::notifications()` exposes owner-only email settings. Each request needs an `authorization: Bearer ` metadata header, and the channel must connect to the same server endpoint that minted the session. You can also pair the generated `proto::notifications::notification_service_client::NotificationServiceClient` with a margin-maker `AuthFlow::for_trader` and its bearer interceptor: ```rust use flint_margin_maker_client::auth::AuthFlow; use flint_margin_user_client::proto::notifications::{ RequestEmailVerificationRequest, VerifyEmailRequest, notification_service_client::NotificationServiceClient, }; let auth = AuthFlow::for_trader(channel.clone(), owner_signer, trader_id); auth.token().await?; let mut notifications = NotificationServiceClient::with_interceptor(channel, auth.interceptor()); let settings = notifications.get_settings(()).await?.into_inner(); let challenge = notifications.request_email_verification(RequestEmailVerificationRequest { email: email.to_owned(), }).await?.into_inner(); // After the user enters the code sent to that address: auth.token().await?; notifications.verify_email(VerifyEmailRequest { challenge_id: challenge.challenge_id, code: code.to_owned(), }).await?; ``` Refresh the session with `auth.token().await?` before subsequent calls; the Rust interceptor cannot refresh it by itself. Delegates cannot access notifications. Requesting a code leaves the current verified destination unchanged. Verifying the single-use code enables email for new fills without a historical backlog. `disable_email(())` disables email and cancels pending verification. Public fill history and client-local read/dismiss state remain independent of email delivery. See the [TypeScript notification guide](/sdks/typescript#margin-account-notifications) for settings and verification response fields. ## Reading the chain `flint_margin_onchain` (re-exported as `onchain` by both clients) adds readers for the market's quoting and order-book accounts and for whole transactions: | Module | Helpers | | --- | --- | | `quoting` | `EncodedPerpQuotingEntry::from(&QuotingEntry)`, `decode_aggregated_quote_book(&entries, slot)`, `decode_quote_entry_side(&entry, slot, QuoteSide::Bid)`, `has_configured_quote_data` | | `resting` | `read_resting_orders(&book)`, `is_resting_live(&order, slot, unix_seconds)`, `order_expiry` | | `transaction` | `decode_flint_transaction(&RpcJsonTransaction)` and `decode_flint_transaction_for(.., &program_id)` | | `error` | `error_name(code)`, `flint_error(code)`, `custom_error_code(&err)` | ```rust use flint_margin_user_client::onchain::accounts::{LinearPerpOrderBook, LinearPerpQuoting}; use flint_margin_user_client::onchain::quoting::{ EncodedPerpQuotingEntry, decode_aggregated_quote_book, }; use flint_margin_user_client::onchain::resting::read_resting_orders; let quoting = LinearPerpQuoting::parse_account(&rpc.get_account(&LinearPerpQuoting::find_pda(perp_id).0).await?)?; let entries: Vec = quoting.live_entries()?.iter().map(EncodedPerpQuotingEntry::from).collect(); let book = decode_aggregated_quote_book(&entries, slot); let order_book = LinearPerpOrderBook::parse_account(&rpc.get_account(&LinearPerpOrderBook::find_pda(perp_id).0).await?)?; let resting = read_resting_orders(&order_book)?; ``` `decode_flint_transaction` takes a `getTransaction` response requested with `"encoding": "json"`, deserialized into `RpcJsonTransaction`, and returns every Flint instruction (inner ones included) with the events it emitted. `complete` is false when an instruction carried bytes this client does not know. ```rust use flint_margin_user_client::onchain::error::{custom_error_code, error_name}; use flint_margin_user_client::onchain::transaction::{RpcJsonTransaction, decode_flint_transaction}; let response: RpcJsonTransaction = serde_json::from_value(raw)?; let decoded = decode_flint_transaction(&response)?; for top in &decoded.instructions { if let Some(flint) = &top.flint { println!("{} {}", top.index, flint.name()); } } if let Some((_, code)) = response.meta.as_ref().and_then(|m| m.err.as_ref()).and_then(custom_error_code) { println!("{:?}", error_name(code)); } ``` ## Preparing perpetual orders `onchain::perp_order` builds `place_perp_order` with the crossing makers' trader accounts attached in ascending ID order — the same rules, rounding, and messages as the TypeScript `buildPerpOrderInstruction`: * `build_perp_order_instruction(&BuildPerpOrderInput, &PerpBookSnapshot)` — market orders through `reference_price_ticks` plus `max_slippage_bps`, limit orders through `limit_price`; GTC, GTD, IOC, and FOK; post-only, reduce-only, minimum fill, and delegates. * `build_perp_close_instruction(&BuildPerpCloseInput, &PerpBookSnapshot)` — rounds an integer fraction of the position down to lots and forces reduce-only FOK. * `select_crossing_makers` and `PerpLiquidityError` (`NoLiquidity`, `OutsidePriceLimit`). * Grid math: `exact_grid_units`, `market_limit_ticks`, `base_for_lots`, `fractional_close_size_base`, `reverse_size_base`. With the user client's `rpc` feature, `prepare_perp_order` reads the quoting account, order book, and Clock sysvar in one processed `getMultipleAccounts` and builds the instruction, retrying while maker quotes are unavailable. It accepts a `solana_rpc_client` nonblocking `RpcClient` or anything implementing `PerpPreparationRpc`. It does not sign or send. ```toml flint-margin-user-client = { version = "=8.1.0", registry = "kellnr", features = ["rpc"] } ``` ```rust use flint_margin_user_client::onchain::perp_order::{BuildPerpOrderInput, OrderTimeInForce}; use flint_margin_user_client::onchain::resting::PerpSide; use flint_margin_user_client::{PerpPreparationOptions, prepare_perp_order}; let mut input = BuildPerpOrderInput::new( payer.pubkey(), trader_id, perp_id, PerpSide::Buy, "0.003", "0.001", "0.001", ); input.reference_price_ticks = Some(mark_ticks.to_string()); input.max_slippage_bps = Some(100); input.time_in_force = Some(OrderTimeInForce::Ioc); let prepared = prepare_perp_order(&input, &rpc, PerpPreparationOptions::default()).await?; println!("{} lots, limit {}, makers {:?}", prepared.lots, prepared.limit_price_ticks, prepared.maker_trader_ids); let instruction = prepared.order.instruction; ``` `PerpPreparationOptions` defaults to 16 attempts, 300 ms between retries, a 5 s request timeout, and a 16-slot quote expiry buffer; `on_waiting_for_quotes` fires once when it starts retrying. Live quotes outside the bound fail immediately with `PerpPreparationError::Order(PerpOrderError::Liquidity(..))`; the SDK never widens the bound. `prepared.slot` is the RPC read context. `rust/margin-user/examples/perps_e2e.rs` places, modifies, cancels, and closes orders and settles funding and PnL on devnet. Run it with `RPC_URL`, `KEYPAIR` (the owner's keypair file) and `TRADER_ID` set: `cargo run -p flint-margin-user-client --features rpc --example perps_e2e`. ## Maker client — `flint-margin-maker-client` ### Authentication The maker API authenticates a Solana key that owns a trader account or is an active `TRADE`- or `QUOTE`-scoped delegate on one. `AuthFlow` runs `Challenge → sign → Authenticate`, signing `b"FLINT-MARGIN-AUTH-V1:" || nonce`, caches the bearer token, and shares one in-flight handshake across concurrent callers. `Client` combines an `AuthFlow`, all maker services and a public client. It manages token refresh, session heartbeats and stream reconnection. Configure it with the authenticated maker endpoint; `TxService` is available only there. ```rust use std::sync::Arc; use flint_margin_maker_client::Client; use flint_margin_maker_client::proto::{GetQuotingStateRequest, SubscribeFillsRequest}; use solana_sdk::signer::keypair::read_keypair_file; #[tokio::main] async fn main() -> anyhow::Result<()> { let keypair = read_keypair_file("/path/to/maker.json").map_err(|e| anyhow::anyhow!("{e}"))?; // This example selects devnet explicitly; use the operator's URLs for another network. let client = Client::builder() .devnet() .wallet(Arc::new(keypair)) .build() .await?; // Mints a session, then refreshes it and answers the session // heartbeat in the background until `shutdown()`. let session = client.authenticate().await?; println!("trader {} (controls {:?})", session.trader_id, session.trader_ids); let state = client .maker() .get_quoting_state(GetQuotingStateRequest { perp_ids: vec![] }) .await? .into_inner(); println!("{:?}", state.entries); // Reconnecting stream: keep the handle alive while you read. let fills = client.subscribe_maker_fills(SubscribeFillsRequest { perp_ids: vec![] }); let mut events = fills.subscribe().await; while let Ok(fill) = events.recv().await { println!("{} {:?} @ {:?}", fill.signature, fill.size, fill.price); } client.shutdown().await; Ok(()) } ``` * A key that controls several trader accounts gets the lowest id by default. Use `.trader_id(id)` on the builder, `Client::connect_for_trader(endpoint, signer, trader_id)`, or `AuthFlow::for_trader` to pick one; the session's `trader_ids` lists them all. * `Client::connect(endpoint, signer)` is shorthand for the builder with defaults. * `Wallet` is `Arc`, so any `solana_sdk::signer::Signer` — keypair, hardware wallet, remote signer — can implement that interface. * `client.auth()` returns the `AuthFlow` for `token()`, `refresh()`, `revoke()`, or `interceptor()`. The interceptor only attaches the cached token; `authenticate()` starts the refresh loop that keeps it fresh. * `shutdown()` stops the refresh, heartbeat, discovery, and chain-tip tasks without revoking the session. They also stop when the last clone of the `Client` drops. * There is no maker balance RPC: read collateral and health with `client.account().get_account(...)` or `client.subscribe_account(...)`. To configure a generated service client directly: ```rust use std::sync::Arc; use flint_margin_maker_client::auth::AuthFlow; use flint_margin_maker_client::proto::maker_service_client::MakerServiceClient; use flint_margin_maker_client::DEVNET_MAKER_ENDPOINT; let channel = tonic::transport::Channel::from_static(DEVNET_MAKER_ENDPOINT) .connect() .await?; let auth = AuthFlow::new(channel.clone(), Arc::new(keypair)); auth.token().await?; let _refresh = auth.spawn_refresh_loop(); let mut maker = MakerServiceClient::with_interceptor(channel, auth.interceptor()); ``` `Channel::from_static` doesn't configure TLS or keep-alives; prefer the `Client`, which does. ### Client options | Builder method | Default | | --- | --- | | `.endpoint(url)`, `.devnet()`, `.mainnet()` | Mainnet authed host. | | `.public_endpoint(url)` | The devnet public host after `.devnet()`, otherwise mainnet's. | | `.channel(ch)`, `.public_channel(ch)` | Supply your own channels; used unchanged. | | `.wallet(wallet)` | Required. | | `.trader_id(id)` | The lowest trader id the key controls. | | `.auth_skew(duration)` | Refresh 30 s before the session expires. | | `.auto_reconnect(bool)` | On: streams and the heartbeat reopen after failures. | | `.auto_session_heartbeat(bool)` | On: `authenticate()` starts the `SessionService` heartbeat. | | `.endpoint_discovery(bool)`, `.discovery_interval(duration)` | On for hostnames, every 10 s. See [Node discovery](/overview/endpoints#node-discovery). | | `.log_messages(bool)`, `.message_log_limit(chars)` | Off. See [Logging](/sdks/rust#logging). | `client.public()` is a full `flint_margin_user_client::Client` on the public host; `market_data()`, `perp_market()`, and `account()` are shortcuts. `client.node()` reaches `NodeService`, and `client.escalation()` pages Flint's on-call team when your trader belongs to an organization: ```rust use flint_margin_maker_client::proto::PageTheTeamRequest; let page = client .escalation() .page_the_team(PageTheTeamRequest { message: "quotes failing since slot 412000000".into() }) .await? .into_inner(); println!("paged: {}", page.page_id); ``` ### Reconnecting streams The `subscribe_*` methods return an `Arc>`. Call `subscribe().await` for a `tokio::sync::broadcast::Receiver`; the RPC opens on the first subscriber, reopens on a 500 ms → 30 s backoff with the original filters, and re-authenticates on `UNAUTHENTICATED`. | Method | Stream | | --- | --- | | `subscribe_maker_market_snapshots`, `subscribe_maker_fills`, `subscribe_quotes` | `MakerService` | | `subscribe_tx_status`, `subscribe_blockhash`, `subscribe_slots`, `subscribe_leaders` | `TxService` | | `subscribe_spread_protect` | `SpreadProtectService` | | `subscribe_order_book`, `subscribe_trades`, `subscribe_candles`, `subscribe_market_stats`, `subscribe_account` | Public, over the public channel | Keep the handle alive while you read the receiver. `state()` and `current_state()` report `Connecting`, `Connected`, or `Disconnected`; `disconnects()` counts drops, so a fast reconnect can't hide a gap. Snapshot streams resend state on reconnect; fill and quote streams don't replay what you missed — reconcile with the matching unary RPC. `shutdown()` on a stream closes its receivers. ### Session heartbeat and chain state After `authenticate()`, the client holds `SessionService.Session` open and answers each server ping, which is what puts your session in `ListQuoterSessions` with its RTT and clock drift. `last_heartbeat()` returns the latest ping answered. `spawn_chain_tip()?` starts the blockhash, slot, and leader streams once per client and caches them in `server_state()`: `get_blockhash()`, `blockhash_age()`, `get_current_slot()`, `get_last_valid_block_height()`, the priority-fee recommendation in `recommended_cu_price`, and `wait_ready()` to block until all three have arrived. A disconnected feed clears its value rather than going stale. ### Organization API key and external submission `ClientBuilder::api_key(key)` plus `trader_id(perp_maker_id)` builds a client with no wallet: the key is sent as `x-api-key`, and margin `MakerService` resolves it to the organization's perps trader. `TxService`, `SessionService`, and `SpreadProtectService` need a keypair session, so such a client can't `authenticate()` or `spawn_chain_tip()`. Publish the chain tip yourself with `server_state().publish_blockhash(..)` and `publish_slot(..)`. To quote without `TxService`, start the core with `QuotingCore::start_with_signer(client, config, signer)` and deliver through `QuoteBuilder::commit_externally(&mut core, &submitter)`. A `QuotingSigner::Local(wallet)` core hands your submitter signed transactions, and a `QuotingSigner::External(pubkey)` core hands it unsigned ones to sign. Sign `tx.message` and send `tx.with_signature(signature)?`, which verifies the signature against the fee payer and returns the wire bytes. Either way the submitter returns the signature, or `Signature::default()` when signing happens later elsewhere: that delivery is not retried or reported as failed, and its intents stay unknown in the receipt. Only `Err` is retried. Report outcomes through `core.tx_outcome_publisher()`; see the spot [auth guide](https://docs.flintlabs.dev/sdks/auth#external-submission) for the full model. ### Market catalog and units `refresh_config().await?` reads the margin program id from `NodeService` and pages through every perp market, then caches the result in `config_cache()`. Each market carries exact `MarketUnits` for converting decimals to the on-chain grid: ```rust use std::str::FromStr; use rust_decimal::Decimal; let config = client.refresh_config().await?; let market = config.catalog.perp(perp_id)?; // or .market("SOL-USDC-PERP")? let bid_ticks = market.units.bid_ticks(Decimal::from_str("150.25")?)?; // rounds down let ask_ticks = market.units.ask_ticks(Decimal::from_str("150.35")?)?; // rounds up let lots = market.units.lots(Decimal::from_str("2.5")?)?; // rounds down ``` ### Quoting `QuotingCore::start(client, config)` checks that the node serves the program in `QuotingConfig::program_id`, authenticates, starts the chain-tip streams, loads your quoting entries, and follows the spread-protection feed. `QuoteBuilder` then builds density, relative-density, and order-list updates in ticks and lots, allocates sequence numbers above what the chain has seen, signs, and submits: ```rust use std::time::Duration; use flint_margin_maker_client::onchain::types::DensityFair; use flint_margin_maker_client::quoting::{QuoteBuilder, QuotingConfig, QuotingCore}; let mut core = QuotingCore::start(client, QuotingConfig::default()).await?; let slot = core.client().server_state().get_current_slot().unwrap_or_default(); // Needs a density entry with configured bands on perp 1. let mut result = QuoteBuilder::new() .density_fair(1, DensityFair { slot, bid_fair: 150_000, ask_fair: 150_010 }) .commit(&mut core) .await?; for failed in &result.failed { eprintln!("{}: {}", failed.transaction.signatures[0], failed.error); } let outcome = result.receipt.settled_within(Duration::from_secs(10)).await?; if !outcome.is_complete() { // Reconcile outcome.failed and outcome.unknown before rebuilding. } ``` * `QuotingConfig` defaults: the devnet program `FLINT_ID`, 200,000 compute units per transaction, the server's priority-fee recommendation, a 10 s readiness timeout, a 15 s maximum blockhash age, and v1 transactions (`tx_version`; set `TxVersion::V0` with `lookup_tables`, or `TxVersion::Legacy`). Set `delegate_trader_account` when the wallet isn't the owner. * The builder's standalone `UpdateQuoting` instructions need the owner or a `TRADE` delegate. A `QUOTE` delegate can authenticate but must build the batch instructions itself. * Updates to one market stay in one transaction; a group too large for one packet (4,096 bytes as v1, 1,232 as legacy or v0) is rejected rather than split. * `commit` retries transport failures with the same signed bytes (`commit_with_retry` takes a `RetryConfig`; default 3 attempts, 100 ms apart). Rejected submissions land in `result.failed`. * Receipts report **processed** observations, not finality. `settled_within` groups intent indices into `landed`, `failed`, and `unknown`; it returns `CommitError::Timeout`, `Lagged`, or `Disconnected` when it can't tell, and `receipt.outcome()` keeps the partial picture. A transport error or timeout leaves execution uncertain — reconcile the signature before rebuilding. * Call `refresh_quoting_state()` after admission or strategy changes. * `core.spread_mult()` is the highest spread-protection multiplier active at the current slot and `spread_protect_window()` the window behind it. Neither touches your quotes; apply them yourself. With the `rpc` feature, `with_rpcs(...)` also broadcasts each signed transaction through your own Solana RPCs, and `with_quic(...)` sends it straight to the next two leaders' TPU over QUIC, using the `SubscribeLeaders` schedule. Both are best effort: a QUIC write is not treated as acceptance. ```rust use std::sync::Arc; use flint_margin_maker_client::quoting::quic::QuicTxClient; let core = core.with_quic(Arc::new(QuicTxClient::new())); ``` ### Submitting transactions To submit transactions you build yourself, sign them and hand the wire bytes to `TxService.SubmitTx` — or to `QuotingCore::submit`, which returns the same receipts. The program's instruction builders are in `flint_margin_maker_client::onchain`. The session's key must be one of the transaction's required signers. ```rust use flint_margin_maker_client::proto::SubmitTxRequest; let ack = client .tx() .submit_tx(SubmitTxRequest { transaction: tx_bytes, // bincode-serialized, fully signed VersionedTransaction client_sent_at: None, }) .await? .into_inner(); println!("accepted {}", ack.signature); ``` `SubmitTx` returns on acceptance. Watch `SubscribeTxStatus` for `landed` or `failed`, and `SubscribeBlockhash` for a fresh blockhash and priority-fee recommendation. ## Decimal handling Every price, size, balance, and rate is `Decimal { value: String }`. Parse with [`rust_decimal`](https://crates.io/crates/rust_decimal) — never `f64`: ```rust use rust_decimal::Decimal; use std::str::FromStr; let price = Decimal::from_str(&level.price.as_ref().unwrap().value)?; let quantity = Decimal::from_str(&level.quantity.as_ref().unwrap().value)?; let notional = price * quantity; ``` Ids are plain integers: `PerpId` and `SpotId` wrap `u32`, `TraderId` wraps `u64`. Timestamps are `Timestamp { micros: u64 }`. ## Errors Generated service calls return `tonic::Status`; branch on `status.code()`. The SDK's own helpers return the crate's `Error` (`flint_client_core::CoreError`): `Config`, `Auth`, `Status`, or `Transport`. Quote receipts add `quoting::CommitError` for outcomes that couldn't be observed. See [Errors](/overview/errors). ## Transport defaults Both clients build channels with a 5 s connect timeout, a 10 s request timeout, HTTP/2 keep-alive pings every 30 s (including while idle), and TCP keep-alive every 60 s. `https://` endpoints use rustls with the platform's native roots. The user client connects to the endpoint you give it; the maker client probes and pins the fastest healthy node behind each hostname — see [Node discovery](/overview/endpoints#node-discovery). ## Logging The crates log through [`tracing`](https://crates.io/crates/tracing): channel connects and endpoint probes at `debug`; node switches and refresh-loop, heartbeat, and stream failures at `warn`. Install a subscriber to see them. `.log_messages(true)` on the maker builder also logs the payloads of SDK-managed RPCs and stream items at `trace`, truncated to `.message_log_limit(chars)` (zero disables truncation). Auth, credential, and signed-submission payloads are redacted and headers are never logged. Calls you make on the generated clients directly aren't covered. ## Cargo features | Crate | Feature | Default | What it adds | | --- | --- | --- | --- | | `flint-margin-user-client`, `flint-margin-maker-client` | `tls` | yes | HTTPS via rustls + native roots. Disable only for plaintext local stacks. | | `flint-margin-user-client` | `rpc` | no | `prepare_perp_order` and the `perp_preparation` module. Pulls in the Solana RPC client. | | `flint-margin-maker-client` | `rpc` | no | `QuotingCore::with_rpcs` and `with_quic`, and the `quoting::quic` module. Pulls in the Solana RPC and QUIC clients. | ## Version constants Each client crate exports `SDK_VERSION`, `PROTO_HASH`, and `PROTO_REV` — the values it stamps on requests as `x-flint-sdk`, `x-flint-proto-hash`, and `x-flint-proto-rev`. Compare `PROTO_HASH` with the server's `x-flint-server-proto-hash` response header when debugging a schema mismatch. # Python SDK The `flint-api-client` Python package carries the perps SDK under `flint.margin`, next to the spot SDK. It is asyncio-based, uses [`grpcio`](https://pypi.org/project/grpcio/) for the API and [`solders`](https://pypi.org/project/solders/) for keys, instructions, and transactions. | Module | What it covers | | --- | --- | | `flint.margin.api` | `MarginUserClient` for the nine public margin-user services, `MarginMakerClient` and `MarginAuthFlow` for the authenticated maker API, and a Solana-RPC fallback for `TxService`. | | `flint.margin.onchain` | The margin program's instruction builders, account and event decoders, PDAs, and error table, generated from the same Codama IDL as the Rust and TypeScript clients. Adds transaction decoding, quote-book and resting-order readers, and the taker helpers `prepare_perp_order` and `build_perp_close_instruction`. | | `flint.margin.quoting` | `QuoteBuilder` and `QuotingCore`, a port of the Rust maker quoting core: sequence allocation, signing, submission with retry and fallback, landing receipts, and spread protection. | See [SDK overview](/sdks) for what each language supports. ## Install Python 3.10 or newer. The package index needs a read-only token from the **Developer** tab of the maker dashboard ([mainnet](https://mainnet.dash.flint.trade/maker/developer), [devnet](https://devnet.dash.flint.trade/maker/developer)), or from the operator. The examples read it from `PYPI_MAKER_TOKEN`. :::code-group ```sh [uv] uv init --bare flint-bot && cd flint-bot cat >> pyproject.toml <<'TOML' [[tool.uv.index]] name = "flint" url = "https://pypi.xantasoft.com/simple/" explicit = true [tool.uv.sources] flint-api-client = { index = "flint" } TOML export UV_INDEX_FLINT_USERNAME=__token__ export UV_INDEX_FLINT_PASSWORD="$PYPI_MAKER_TOKEN" uv add "flint-api-client[quic]>=8.1" uv run python bot.py ``` ```sh [pip] python -m venv .venv source .venv/bin/activate python -m pip install \ --index-url "https://__token__:${PYPI_MAKER_TOKEN}@pypi.xantasoft.com/simple/" \ "flint-api-client[quic]>=8.1" ``` ::: With uv, `explicit = true` and the `flint-api-client` source pin mean only the SDK comes from Flint's index; every other dependency resolves from PyPI. uv reads the credentials from `UV_INDEX_FLINT_USERNAME` and `UV_INDEX_FLINT_PASSWORD`, so the token never lands in `pyproject.toml` or `uv.lock`. Set the same two variables in CI before `uv sync`. Put the examples below in `bot.py`. Perps support (`flint.margin`) needs `flint-api-client` 8.1 or newer. The `quic` extra lets `QuotingCore` send transactions straight to the leader's TPU; drop it for read-only or taker-only use. ## Public data — `MarginUserClient` `MarginUserClient` owns one gRPC channel to the public host and exposes one generated stub per service: `market`, `account`, `risk`, `sponsorship`, `funding`, `perp_market`, `market_data`, `order`, and `notifications`. Every call carries `x-flint-sdk: python/` and the proto hash and revision. ```python import asyncio from flint.margin.api import MarginUserClient from flint.margin.gen.flint.margin.user.v1 import common_pb2 from flint.margin.gen.flint.margin.user.v1.account import messages_pb2 as account_msg from flint.margin.gen.flint.margin.user.v1.market_data import messages_pb2 as md_msg from flint.margin.gen.flint.margin.user.v1.perp_market import messages_pb2 as perp_msg async def main() -> None: async with MarginUserClient.devnet() as client: markets = await client.perp_market.ListPerpMarkets(perp_msg.ListPerpMarketsRequest()) for market in markets.markets: print(market.perp_id.id, market.symbol, market.tick_size.value) trader = common_pb2.TraderId(id=42) account = await client.account.GetAccount(account_msg.GetAccountRequest(trader_id=trader)) print(account.account.health.net_equity.value) book = client.market_data.SubscribeOrderBook( md_msg.SubscribeOrderBookRequest(perp_id=markets.markets[0].perp_id, depth=20) ) async for update in book: print(update.snapshot, update.sequence, update.order_book.bids[:3]) asyncio.run(main()) ``` `MarginUserClient()` with no endpoint connects to mainnet; `MarginUserClient.devnet()`, `MarginUserClient.mainnet()`, and `MarginUserClient(url)` choose explicitly. The endpoint constants `DEVNET_PUBLIC_ENDPOINT`, `MAINNET_PUBLIC_ENDPOINT`, `DEVNET_MAKER_ENDPOINT`, and `MAINNET_MAKER_ENDPOINT` are exported from `flint.margin.api`. `MarginUserClient.from_channel(channel)` wraps a channel you configured. :::warning[Devnet gaps] On devnet today `RiskService.SimulateAccountRisk` returns `INTERNAL` ("perp risk inputs are unavailable") and `OrderService.ListOrders` returns `UNIMPLEMENTED`. Read resting orders from chain with `read_resting_orders` (below) until the server serves them. ::: `stream_with_backoff(factory, stop)` reopens a server stream on a 500 ms → 30 s schedule and yields `StreamEvent`s of kind `connected`, `item`, and `disconnected`. Each reconnect starts with a fresh snapshot. Failures are `grpc.aio.AioRpcError`; `status_code(err)`, `status_message(err)`, and `response_metadata(err)` narrow any exception. ## Reading the chain `decode_flint_account_as` checks the owner, discriminator, and exact length before decoding, then returns the generated dataclass. The generated module `flint.margin.onchain.generated` holds the account types, PDA finders, and instruction builders. ```python from flint.margin.onchain import ( decode_aggregated_quote_book, decode_flint_account_as, decode_trader_positions, encoded_quoting_entries, read_resting_orders, ) from flint.margin.onchain import generated as g trader = decode_flint_account_as(g.TraderAccount, owner, data) positions = decode_trader_positions(trader) for perp in positions.linear_perp: print(perp.linear_perp_id, perp.entry_contract_lots, perp.realised_pnl_ticks) quoting = decode_flint_account_as(g.LinearPerpQuoting, quoting_owner, quoting_data) book = decode_aggregated_quote_book(encoded_quoting_entries(quoting), slot) print(book.bids[:3], book.asks[:3]) order_book = decode_flint_account_as(g.LinearPerpOrderBook, book_owner, book_data) for order in read_resting_orders(order_book): print(order.trader_id, order.side, order.price_ticks, order.lots, order.expiry) ``` `owner` and `data` are the account's owner `Pubkey` and raw bytes from `getAccountInfo`. PDAs come from the generated finders, for example `g.find_trader_account_pda(trader_id)[0]`, `g.find_quoting_pda(perp_id)[0]`, and `g.find_order_book_pda(perp_id)[0]`. | Helper | Returns | | --- | --- | | `decode_flint_account(owner, data)` | `DecodedFlintAccount(kind, account)` for any Flint account. | | `decode_trader_positions(trader)` | `DecodedTraderPositions` with `spot` and `linear_perp` lists, walked from the account's tail. | | `decode_aggregated_quote_book(entries, slot)` | Maker quote depth live at `slot`, summed by price. | | `decode_quote_entry_side(entry, slot, "bid" \| "ask")` | One maker's live levels. | | `read_resting_orders(book)` | `RestingBookOrder`s; `is_resting_live(order, slot, unix_seconds)` filters expired ones. | Quote depth is indicative: maker margin caps are enforced by the program. ## Placing orders `prepare_perp_order` reads the market's quoting account, resting order book, and the Clock sysvar in one `getMultipleAccounts` call, picks the makers whose quotes or resting orders cross your price bound, and returns a `place_perp_order` instruction with their trader accounts attached in ascending ID order. It does not sign or send. Pass any object with an async `request(method, params)` JSON-RPC method: ```python from flint.api.rpc import FailFastRpc from flint.margin.onchain import FLINT_PROGRAM_ID, PreparePerpOrderInput, prepare_perp_order rpc = FailFastRpc(rpc_url, timeout=30) prepared = await prepare_perp_order( PreparePerpOrderInput( program_id=FLINT_PROGRAM_ID, signer=owner.pubkey(), trader_id=42, linear_perp_id=0, minimum_order_size_base="0.001", minimum_price_increment="0.001", side="buy", size_base="0.003", reference_price_ticks=str(mark_ticks), max_slippage_bps=100, time_in_force="IOC", client_order_id=1, ), rpc, on_waiting_for_quotes=lambda: print("waiting for fresh maker quotes"), ) print(prepared.lots, prepared.limit_price_ticks, prepared.maker_trader_ids, prepared.slot) ``` Add `prepared.instruction` to a transaction, sign it, and send it with your own RPC or `TxService`. * Sizes and prices are decimal strings; `exact_grid_units` rejects a size or price that isn't a whole number of lots or ticks. * A market order needs `reference_price_ticks` and `max_slippage_bps`. A limit order sets `limit_price` instead. * The default time in force is FOK for market orders and GTC for limit orders. GTD needs a future `expires_at_slot`; `post_only=True` needs GTC or GTD. `reduce_only`, `min_fill_base`, `rent_payer`, and `is_delegate` are supported. * Defaults: 16 attempts, 300 ms between retries, a 5 s request timeout, and a 16-slot quote expiry buffer (`max_attempts`, `retry_delay_ms`, `request_timeout_ms`, `quote_buffer_slots`). It retries only while quotes are unavailable; live quotes outside the bound raise `PerpLiquidityError` at once. The SDK never widens the bound. A resting limit order: ```python rest = await prepare_perp_order( PreparePerpOrderInput( program_id=FLINT_PROGRAM_ID, signer=owner.pubkey(), trader_id=42, linear_perp_id=0, minimum_order_size_base="0.001", minimum_price_increment="0.001", side="buy", size_base="0.003", limit_price="120.5", time_in_force="GTC", post_only=True, client_order_id=2, ), rpc, ) ``` With chain data you already hold, `build_perp_order_instruction` takes a `BuildPerpOrderInput` with `slot`, `unix_seconds`, `quoting`, and `resting_orders`, and `select_crossing_makers` returns just the crossing makers. `market_limit_ticks`, `base_for_lots`, `fractional_close_size_base`, and `reverse_size_base` are the grid helpers behind them. ### Modify and cancel Modify and cancel use the generated builders. A size of `0` cancels one order; `build_cancel_all_perp_orders` cancels every order on a contract. ```python from solders.system_program import ID as SYSTEM_PROGRAM modify = g.build_modify_perp_order( rent_payer=owner.pubkey(), authority=owner.pubkey(), trader_account=g.find_trader_account_pda(42)[0], global_config=g.find_global_config_pda()[0], spot_product_cache=g.find_spot_product_cache_pda()[0], oracle_cache=g.find_oracle_cache_pda()[0], linear_perp_cache=g.find_linear_perp_cache_pda()[0], linear_perp=g.find_linear_perp_pda(0)[0], order_book=g.find_order_book_pda(0)[0], system_program=SYSTEM_PROGRAM, linear_perp_id=0, side=g.MakerSide.BUY, price_ticks=120_500, lots=g.FixedOptionU32(is_some=True, value=1), reduce_only=g.FixedOptionBool(is_some=False, value=False), expires_at_slot=g.FixedOptionU64(is_some=False, value=0), auth=g.TraderAuthWallet(), ) cancel_all = g.build_cancel_all_perp_orders( authority=owner.pubkey(), trader_account=g.find_trader_account_pda(42)[0], global_config=g.find_global_config_pda()[0], order_book=g.find_order_book_pda(0)[0], linear_perp_id=0, auth=g.TraderAuthWallet(), ) ``` ### Close a position `build_perp_close_instruction` rounds an integer fraction of the position down to whole lots and sends it as a reduce-only FOK order: ```python from flint.margin.onchain import BuildPerpCloseInput, build_perp_close_instruction close = build_perp_close_instruction( BuildPerpCloseInput( program_id=FLINT_PROGRAM_ID, signer=owner.pubkey(), trader_id=42, linear_perp_id=0, minimum_order_size_base="0.001", minimum_price_increment="0.001", position_side="long", position_size_base="0.003", close_numerator=1, close_denominator=2, reference_price_ticks=str(mark_ticks), max_slippage_bps=100, slot=slot, unix_seconds=unix_seconds, quoting=encoded_quoting_entries(quoting), resting_orders=read_resting_orders(order_book), ) ) ``` Pass the chain clock's `unix_seconds` so timestamp expiries use chain time. ## Funding, PnL, deposits, and withdrawals These are plain generated builders. Funding settles closed funding periods, so refresh the perp cache in the same transaction: ```python crank = g.build_sync_linear_perp_cache( linear_perp=g.find_linear_perp_pda(0)[0], quoting=g.find_quoting_pda(0)[0], spot_product_cache=g.find_spot_product_cache_pda()[0], oracle_cache=g.find_oracle_cache_pda()[0], linear_perp_cache=g.find_linear_perp_cache_pda()[0], linear_perp_id=0, ) settle = g.build_settle_funding( trader_account=g.find_trader_account_pda(42)[0], linear_perp_cache=g.find_linear_perp_cache_pda()[0], ) ``` `build_settle_realised_pnl` moves realised PnL into the perp's PnL currency. `build_deposit` takes `amount=g.DepositAmountAtoms(value=atoms)`, and `build_withdraw` takes `amount` in atoms; both need the collateral mint's `g.find_spot_market_pda(mint)[0]` and its vault, the market's associated token account. `python/examples/perps_e2e.py` runs the full sequence on devnet: deposit, IOC buy, post-only rest, modify, cancel all, settle funding, close, settle PnL, and withdraw. Set `RPC_URL` to a devnet RPC endpoint and `KEYPAIR` to the owner's keypair file; `FUNDER_KEYPAIR` tops the owner up when its SOL balance is short. ## Decoding transactions and errors `decode_flint_transaction` takes a `getTransaction` response requested with `"encoding": "json"` and returns every Flint instruction, including inner ones, plus the events they emitted: ```python from flint.margin.onchain import custom_error_code, decode_flint_transaction, flint_error_name decoded = decode_flint_transaction(response) for top in decoded.instructions: if top.flint is not None: print(top.index, top.flint.instruction_type.name) for event in decoded.events: if event.kind == "LinearPerpTrade": print(event.maker_id, event.taker_id, event.price_ticks, event.lots) err = (response.get("meta") or {}).get("err") code = custom_error_code(err) if err else None if code: print(flint_error_name(code[1])) ``` `decoded.complete` is false when an instruction carried bytes this client does not know. `decode_flint_event_batch(data)` and `is_flint_event_batch(data)` decode a raw event-batch instruction, and `FLINT_EVENT_KINDS` lists every event kind. `flint_error_name(code)` and `flint_error_message(code)` map a custom program error code to its name and message. See [Errors](/overview/errors). ## Maker client — `MarginMakerClient` `MarginMakerClient` authenticates a key that owns a trader account or is an active `TRADE`- or `QUOTE`-scoped delegate on one. `MarginAuthFlow` signs `FLINT-MARGIN-AUTH-V1:` followed by the server's nonce, caches the session, refreshes it before expiry, and shares one handshake across callers. ```python from solders.keypair import Keypair from flint.margin.api import MarginMakerClient from flint.margin.gen.flint.margin.maker.v1.maker import messages_pb2 as maker_msg async with MarginMakerClient.devnet(keypair, trader_id=7) as client: session = await client.authenticate() print(session.trader_id, session.trader_ids) stats = await client.maker.GetStats(maker_msg.GetMakerStatsRequest()) ``` * `authenticate()` mints a session, starts the refresh loop, and holds the `SessionService` heartbeat open unless `auto_session_heartbeat=False`. * Stubs: `maker`, `tx`, `session`, `spread_protect`, `escalation`, and `node`. `client.public` is a `MarginUserClient` on the matching public host. * `api_key=` with `trader_id=` authenticates with an organization API key instead of a keypair. `TxService`, `SessionService`, and `SpreadProtectService` still need a keypair session. * `client.tx_with_fallback(rpc_url)` returns a `TxService` wrapper whose `SubmitTx`, `SubscribeBlockhash`, and `SubscribeSlots` fall back to plain Solana RPC when the server is unreachable (`should_fallback(err)` decides). `poll_tx_status(rpc, signature, last_valid_block_height)` is the opt-in per-signature substitute for `SubscribeTxStatus`. ## Quoting — `QuotingCore` and `QuoteBuilder` `QuotingCore.start(client, config)` checks that the node serves `QuotingConfig.program_id`, authenticates, starts the blockhash, slot, and leader streams, loads your quoting entries, and follows the spread-protection feed. `QuoteBuilder` builds `update_quoting` instructions, allocates sequence numbers above what the chain has seen, groups updates by market, signs, and submits: ```python from flint.margin.api import MarginMakerClient from flint.margin.onchain.generated import DensityFair, OrderListLevel from flint.margin.quoting import QuoteBuilder, QuotingConfig, QuotingCore client = MarginMakerClient.devnet(keypair, trader_id=7) core = await QuotingCore.start(client, QuotingConfig()) try: slot = core.server_state.get_current_slot() or 0 result = await ( QuoteBuilder() .order_list( 0, [OrderListLevel(price=120_000, quantity=5, order_id=1, slot_expiry=slot + 30)], [OrderListLevel(price=120_100, quantity=5, order_id=2, slot_expiry=slot + 30)], ) .commit(core) ) for failed in result.failed: print(failed.transaction.signatures[0], failed.error) outcome = await result.receipt.settled_within(10.0) print(outcome.landed, outcome.failed, outcome.unknown, outcome.is_complete()) finally: await core.close() await client.close() ``` * Builder methods: `update`, `reset`, `enable`, `density_fair` (`DensityFair(slot, bid_fair, ask_fair)`), `density_bands`, `relative_density_bands`, and `order_list`. `build(core)` returns the instruction groups without sending. * `commit` retries transport failures with the same signed bytes; `commit_with_retry(core, RetryConfig(max_attempts, delay))` changes the defaults (3 attempts, 0.1 s). Rejected submissions land in `result.failed`. * Receipts report processed observations. `settled_within` raises `CommitTimeout`, `CommitLagged`, or `CommitDisconnected` when it can't tell; `receipt.outcome()` keeps the partial picture. * `QuotingConfig` defaults match Rust: 200,000 compute units, the server's priority-fee recommendation, a 10 s readiness timeout, a 15 s maximum blockhash age, and v1 transactions. Set `delegate_trader_account` when the key isn't the owner. * Call `refresh_quoting_state()` after admission changes. `core.markets` holds each admitted market's entry index and sequences. * `core.spread_mult()` and `core.spread_protect_window()` report spread protection. They don't touch your quotes. * `with_rpcs([...])` also broadcasts each transaction through your RPCs, `with_tx_fallback(rpc)` falls back to RPC when `TxService` is unreachable, and `with_quic(sender)` sends to the next leaders over QUIC. To sign elsewhere, start with `QuotingCore.start_with_signer(client, config, QuotingSigner.external(pubkey))` and deliver through `QuoteBuilder.commit_externally(core, submitter)`; report outcomes with `core.tx_outcome_publisher()`. This is the same model as the [Rust client](/sdks/rust#organization-api-key-and-external-submission). `python/examples/perps_maker_smoke.py` starts a core, refreshes quoting state, commits an order list, and waits for its receipt on devnet. ## Decimal handling Prices, sizes, balances, and rates arrive as `Decimal { value: str }`. Parse them with `decimal.Decimal`, never `float`. The on-chain helpers take decimal strings and return exact integers in ticks and lots. # TypeScript SDK ESM-only TypeScript clients for the Flint perps API. They speak gRPC-Web directly to the Flint server, run in any modern browser and in Node 20.18+, and ship pre-generated code — no build-time codegen for consumers. | Package | What it covers | | --- | --- | | `@superis-labs/flint-margin-user-client` | Market and account services, owner-authenticated notifications, plus passkey-delegate and account-rename helpers. | | `@superis-labs/flint-margin-maker-client` | `flint.margin.maker.v1` — signed-nonce `AuthFlow`, `MakerService`, `TxService`, and a Solana-RPC fallback for `TxService`. The other maker services ship as generated descriptors. | | `@superis-labs/flint-idl` | The margin program's Codama IDL and `@solana/kit` client. Re-exported by both packages above. See [On-chain clients](/sdks/onchain). | ## Install The TypeScript packages are restricted on GitHub Packages. Ask the operator to grant your GitHub account access to the client and its `flint-api-client` and `flint-idl` dependencies. A Kellnr token does not grant npm access. ```sh npm login --scope=@superis-labs --auth-type=legacy --registry=https://npm.pkg.github.com ``` Use your GitHub username and an operator-approved token with `read:packages`. See [Maker onboarding](/makers/onboarding#step-2) for the access check. Install the client you need with its peer dependencies: ```sh npm install @superis-labs/flint-margin-user-client@7.0.4 @bufbuild/protobuf @solana/kit npm install @superis-labs/flint-margin-maker-client@7.0.4 @bufbuild/protobuf @solana/kit ``` `@bufbuild/protobuf` (^2.2) handles serialization and `@solana/kit` (^5.5) handles keys, addresses, and transactions. The gRPC-Web transport ships in-tree. ## Public data — `flint-margin-user-client` One factory per service; each returns a typed client whose unary methods return a `Promise` and whose streaming methods return an `AsyncIterable`. | Factory | Service | | --- | --- | | `createPerpMarketClient` | `PerpMarketService` | | `createMarketDataClient` | `MarketDataService` | | `createOrderClient` | `OrderService` | | `createFundingClient` | `FundingService` | | `createAccountClient` | `AccountService` | | `createMarketClient` | `MarketService` | | `createRiskClient` | `RiskService` | | `createSponsorshipClient` | `SponsorshipService` | ```ts import { DEVNET_PUBLIC_ENDPOINT, createAccountClient, createMarketDataClient, createPerpMarketClient, } from "@superis-labs/flint-margin-user-client"; // This example selects devnet explicitly; use the operator's URL for another network. const options = { baseUrl: DEVNET_PUBLIC_ENDPOINT }; const perps = createPerpMarketClient(options); const { markets } = await perps.listPerpMarkets({}); for (const m of markets) console.log(m.symbol, m.tickSize?.value); const accounts = createAccountClient(options); const { account } = await accounts.getAccount({ traderId: { id: 42n } }); console.log(account?.health?.netEquity?.value); // Server stream: a snapshot, then sequenced deltas. const marketData = createMarketDataClient(options); for await (const update of marketData.subscribeOrderBook({ perpId: markets[0].perpId, depth: 20, })) { console.log(update.snapshot, update.sequence, update.orderBook?.bids); } ``` Every factory takes the same `ClientOptions`: | Option | Meaning | | --- | --- | | `baseUrl` | Endpoint; defaults to `DEFAULT_PUBLIC_ENDPOINT` (mainnet). | | `timeoutMs` | Abort every call — streams included — after this many milliseconds. Default: none. | | `interceptors` | Extra gRPC-Web interceptors. | | `fetch` | Custom `fetch` (SSR, request signing, …). | | `logger`, `logMessages` | Lifecycle and payload logging — see [Logging](/sdks/typescript#logging). | | `rpc` | A `@solana/kit` RPC, exposed as `client.rpc` for direct chain reads. | `uint64` fields are `bigint` (`traderId: { id: 42n }`); `uint32` ids such as `PerpId` and `SpotId` are `number`. ### Racing several endpoints To pin to the fastest of several hosts, build a transport with `endpoints` and pair it with a service descriptor. The transport probes each candidate with `NodeService.GetNodeInfo` (3 s timeout by default) and pins to the fastest responder; calls queue until the race finishes. ```ts import { GrpcWebTransport, PerpMarketService, createServiceClient, } from "@superis-labs/flint-margin-user-client"; const transport = new GrpcWebTransport({ endpoints: ["https://devnet.api.flint.trade", "https://other-node.example"], }); const perps = createServiceClient(PerpMarketService, transport); ``` ### No silent chain fallback The user client never substitutes chain reads for server responses. Account health, rates, and valuations are computed server-side; if the server is down, read raw accounts explicitly with `client.rpc` and the re-exported IDL fetchers, and show health as unavailable rather than recomputing it. ## Margin account notifications The margin-user package exposes `NotificationService`; its RPCs require a wallet-signed margin session for the selected trading account's current owner. Use the margin-maker package's `AuthFlow` to keep the bearer and server endpoint together. The wallet signature should follow an explicit user action. ```ts import { AuthFlow, createServiceClient } from "@superis-labs/flint-margin-maker-client"; import { NotificationService } from "@superis-labs/flint-margin-user-client"; const auth = new AuthFlow({ baseUrl, traderId, signer: { address, signMessage } }); await auth.token(); const notifications = createServiceClient(NotificationService, auth.authenticatedTransport()); const settings = await notifications.getSettings({}); const challenge = await notifications.requestEmailVerification({ email }); // After the user enters the code sent to that address: await notifications.verifyEmail({ challengeId: challenge.challengeId, code }); ``` Requesting a code leaves the current verified destination unchanged. Verifying the single-use code enables email for new fills, replacing any previous destination without a historical backlog. `disableEmail({})` disables email and cancels pending verification; enabling email again requires a fresh code. The notification bell uses public `AccountService.GetFills` history and client-local read/dismiss state, which never suppresses email delivery. Clear account-scoped state when the wallet, trading account, or network changes. Keep session tokens in memory and reauthenticate on a different server endpoint. `expiresAtMs` and `retryAfterMs` are `bigint` milliseconds. `emailAddress` is empty when email is disabled; `deliveryAvailable` reports whether the server can send mail. ## Maker client `flint-margin-maker-client` authenticates a Solana key that owns a trader account or is an active `TRADE`- or `QUOTE`-scoped delegate on one. Point it at the **authed** host — `TxService` exists only there. The TypeScript client is thinner than the Rust one: no node discovery, managed heartbeat, or long-running quoting core. It does include a [quote builder](/sdks/typescript#quote-builder). ### Authentication `AuthFlow` runs `Challenge → sign → Authenticate`, signing `FLINT-MARGIN-AUTH-V1:` followed by the nonce. It caches the session, refreshes 30 s before expiry (`expirySkewMs`), and shares one handshake across concurrent callers. The signer is either a `@solana/kit` message signer or any object with an `address` and a `signMessage(bytes)` that returns the 64-byte signature — the shape browser wallets expose. ```ts import { createKeyPairSignerFromBytes } from "@solana/kit"; import { AuthFlow, DEVNET_MAKER_ENDPOINT, TxService, createMakerClient, createServiceClient, } from "@superis-labs/flint-margin-maker-client"; const signer = await createKeyPairSignerFromBytes(secretKeyBytes); const auth = new AuthFlow({ baseUrl: DEVNET_MAKER_ENDPOINT, signer, // traderId: 7n, // omit to select the lowest trader id the key controls }); const maker = createMakerClient({ auth }); const { entries } = await maker.getQuotingState({ perpIds: [] }); // Any other maker service: reuse the flow's authenticated transport. const tx = createServiceClient(TxService, auth.authenticatedTransport()); for await (const event of tx.subscribeTxStatus({})) { console.log(event.kind.case, event.kind.value); // "ack" | "landed" | "failed" } ``` | `AuthFlow` method | Does | | --- | --- | | `token()` | Returns a fresh `Session` (`token`, `traderIds`, `traderId`, `expiresAt`), authenticating if needed. | | `refresh()` | Forces a new signed challenge. Use after an unexpected `UNAUTHENTICATED`. | | `session()` | The cached session, or `null`. | | `revoke()` | Revokes the token server-side and clears it. | | `clear()` | Drops the local session without an RPC. | | `interceptor()` | Interceptor that attaches `authorization: Bearer …` to each call. | | `authenticatedTransport()` | A transport sharing the flow's endpoint and connection, with the interceptor installed. | Already hold a session token from elsewhere? Pass `bearerInterceptor(token)` as `auth` to `createMakerClient` instead of an `AuthFlow`. There is no maker balance RPC; read collateral and health with the user client's `AccountService.getAccount` or `subscribeAccount`. The package root exports descriptors for `AuthService`, `MakerService`, and `TxService`. The rest are generated under the `/gen` subpath — for example the spread-protection feed: ```ts import { SpreadProtectService } from "@superis-labs/flint-margin-maker-client/gen/flint/margin/maker/v1/spread_protect/service_pb.js"; const spread = createServiceClient(SpreadProtectService, auth.authenticatedTransport()); for await (const event of spread.subscribeSpreadProtect({})) { console.log(event.kind.case, event.kind.value); // "resetState" | "widenWindow" } ``` `node/service_pb.js` and `escalation/service_pb.js` work the same way. `SessionService.Session` is bidirectional, which gRPC-Web can't carry, so the session heartbeat isn't available from TypeScript. ### Submitting transactions Build and sign the transaction with `@solana/kit` and the re-exported program builders, then submit its wire bytes. The session's key must be one of the transaction's required signers. ```ts import { getTransactionEncoder } from "@solana/kit"; const wire = getTransactionEncoder().encode(signedTx); const { signature } = await tx.submitTx({ transaction: new Uint8Array(wire) }); ``` `submitTx` returns on acceptance; follow `subscribeTxStatus` for `ack` → `landed` / `failed`. `subscribeBlockhash` streams a recent blockhash with `lastValidBlockHeight` and a priority-fee recommendation. `withTxFallback(tx, rpcUrl)` wraps a `TxService` client so that `submitTx`, `subscribeBlockhash`, and `subscribeSlots` fall back to a plain Solana RPC when the Flint server is unreachable (`UNAVAILABLE`, `DEADLINE_EXCEEDED`, `UNKNOWN`, `INTERNAL`, or a network error). `subscribeTxStatus` has no faithful RPC equivalent and is not covered; `pollTxStatus` is the opt-in per-signature substitute. ### Quote builder `QuoteBuilder` builds `updateQuoting` instructions with the same methods as the Rust and Python builders — `update`, `reset`, `enable`, `densityFair`, `densityBands`, `relativeDensityBands`, and `orderList` — and allocates sequence numbers above the ones the chain has seen. Seed it with `quotingMarketsFromState`, which turns `MakerService.getQuotingState` entries into each admitted market's entry index and sequences: ```ts import { QuoteBuilder, quotingMarketsFromState } from "@superis-labs/flint-margin-maker-client"; const session = await auth.token(); const { entries } = await maker.getQuotingState({ perpIds: [] }); let markets = quotingMarketsFromState(entries, session.traderId); const built = await new QuoteBuilder() .orderList( 0, [{ price: 120_000, quantity: 5, orderId: 1n, slotExpiry: slot + 30n }], [{ price: 120_100, quantity: 5, orderId: 2n, slotExpiry: slot + 30n }], ) .build({ traderId: session.traderId, signer, markets }); markets = built.markets; ``` `built.groups` holds one instruction list per market, in builder order; send each group as one transaction through your signer and `tx.submitTx`. `built.markets` carries the reserved sequences — pass it to the next build, and pass the previous map as `quotingMarketsFromState`'s third argument when you refresh, so sequences never move backwards. A market you aren't admitted to, or a sequence that isn't newer than the chain's, throws `QuoteBuilderError`. Set `delegateTraderAccount` when the signer is a delegate. Signing, submission, retries, and receipts stay with you: unlike Rust and Python, the TypeScript SDK has no `QuotingCore`, the same split as the spot SDKs. ### Browser wallets `createTransactionSigner({ address, signTransaction })` and `createSendingSigner({ address, signAndSendTransaction })` adapt a wallet's callbacks into `@solana/kit` signers, so wallet-signed transactions go through the same kit pipeline. Both packages export them. ## Preparing perpetual orders `preparePerpOrder` reads maker quoting, the resting order book, and the Clock sysvar together from Solana RPC, filters live counterparties within the price bound, derives their writable trader accounts in ascending ID order, and returns an instruction. It does not sign, simulate, or submit the transaction. ```ts import { address, createNoopSigner } from "@solana/kit"; import { preparePerpOrder, resolveRpc, FLINT_PROGRAM_ADDRESS } from "@superis-labs/flint-margin-user-client"; const controller = new AbortController(); const prepared = await preparePerpOrder({ programAddress: address(FLINT_PROGRAM_ADDRESS), signer: createNoopSigner(address(walletAddress)), traderId: 42n, clientOrderId: 123n, linearPerpId: market.linearPerpId, side: "buy", sizeBase: "0.01", minimumOrderSizeBase: market.minimumOrderSizeBase, minimumPriceIncrement: market.minimumPriceIncrement, referencePriceTicks: market.markPriceTicks, maxSlippageBps: 50, timeInForce: "FOK", }, resolveRpc(rpcUrl), { signal: controller.signal }); ``` Use `prepared.instruction` with your transaction signer and sender. Market orders require an explicit slippage bound. The default time in force is FOK for market orders and GTC for limit orders; explicit IOC is supported. GTD requires a future `expiresAtSlot`, and post-only requires GTC or GTD. `clientOrderId` defaults to zero. Closes can use `buildPerpCloseInstruction`, which rounds an integer position fraction down to lots and forces reduce-only FOK. Preparation defaults to 16 attempts, a 300 ms retry delay, a 5 s request timeout, and an 8-slot expiry buffer. Configure these through `PerpPreparationOptions`. `onWaitingForQuotes` fires once when retrying unavailable liquidity. Live quotes outside the bound fail immediately; the SDK never widens the bound. The returned `slot` is the RPC read context, suitable for simulation's `minContextSlot`. A prepared order can expire while awaiting a signature. For already-loaded chain data, pass both `slot` and chain `unixSeconds` to `buildPerpOrderInstruction` or `selectCrossingMakers` so timestamp expiries use the chain clock rather than a slot or the device clock. `buildPerpOrderInstruction`, `selectCrossingMakers`, `decodeQuoteEntrySide`, `decodeAggregatedQuoteBook`, and `readRestingOrders` are pure helpers exported by `@superis-labs/flint-idl` and re-exported by the margin clients. Quote depth is indicative: maker margin caps and reduce-only resting quantities are enforced by the program. Density expansion is bounded to 64 levels per side, and aggregated depth to 64 prices; `availableLots` is not a fill guarantee. Simulate before sending. ## Streaming and reconnects Server streams are plain `AsyncIterable`s. Cancel with an `AbortSignal` in the call options. For long-lived subscriptions, `streamWithBackoff` reconnects on a 500 ms → 30 s exponential schedule and yields `connected`, `item`, and `disconnected` events: ```ts import { streamWithBackoff } from "@superis-labs/flint-margin-user-client"; const ctrl = new AbortController(); for await (const ev of streamWithBackoff( (signal) => accounts.subscribeAccount({ traderId: { id: 42n } }, { signal }), ctrl.signal, )) { if (ev.kind === "item") render(ev.value); else if (ev.kind === "disconnected") console.warn("dropped", ev.error); } ``` Each reconnect starts with a fresh snapshot — replace local state rather than merging. ## Decimal handling Prices, sizes, balances, and rates arrive as `Decimal { value: string }`. Parse with [`bignumber.js`](https://www.npmjs.com/package/bignumber.js) or [`decimal.js`](https://www.npmjs.com/package/decimal.js) — never `Number` or `parseFloat`: ```ts import BigNumber from "bignumber.js"; const price = new BigNumber(level.price!.value); const quantity = new BigNumber(level.quantity!.value); const notional = price.multipliedBy(quantity); ``` Timestamps are `Timestamp { micros: bigint }`. ## Errors Every failure is a `GrpcError` with a numeric `code` (`GrpcCode`). `statusCode(err)`, `statusMessage(err)`, `asGrpcError(err)`, and `responseMetadata(err)` narrow an `unknown` safely. ```ts import { GrpcCode, statusCode } from "@superis-labs/flint-margin-user-client"; try { await perps.listPerpMarkets({}); } catch (err) { if (statusCode(err) === GrpcCode.ResourceExhausted) await sleep(backoff); else throw err; } ``` See [Errors](/overview/errors) for the error codes. ## Logging The SDKs are silent unless you pass a `logger` with optional `debug` and `trace` functions. At `debug`, transport construction logs the SDK version, proto hash, and proto revision it stamps on requests. ```ts const perps = createPerpMarketClient({ baseUrl: DEVNET_PUBLIC_ENDPOINT, logger: { debug: console.debug, trace: console.debug }, logMessages: { enabled: true, maxMessageChars: 50_000 }, }); ``` Payload logging is opt-in and emitted at `trace`. Auth fields (`nonce`, `signature`, `session_token`) are redacted, and bearer headers are never logged. `logMessages.redact` can rewrite a payload after the built-in redaction. ## Wire format The transport speaks `application/grpc-web+proto` over `fetch`. Every request carries `x-flint-sdk: ts-web/`, `x-flint-proto-hash`, and `x-flint-proto-rev`; the same values are exported as `SDK_VERSION`, `PROTO_HASH`, and `PROTO_REV`. Client- and bidi-streaming RPCs are not supported over gRPC-Web, and none exist in the margin API. # On-chain clients Changes to trader accounts, orders and quotes require a transaction against the Flint margin program. Two clients, generated from the same hand-authored [Codama](https://github.com/codama-idl/codama) IDL, build those transactions and decode the program's accounts: | Language | Package | Version line | | --- | --- | --- | | TypeScript | `@superis-labs/flint-idl` — a `@solana/kit` client | Its own (`0.11.0`); tracks the deployed program. | | Rust | `flint-margin-onchain` — borsh builders and decoders | The SDK suite version (`7.0.5`). | Python's `flint.margin.onchain` is generated from the same IDL and tested against the same fixtures; see the [Python SDK](/sdks/python#reading-the-chain). The examples on this page show Rust and TypeScript. The API clients re-export them: `flint-margin-user-client` and `flint-margin-maker-client` provide these exports in Rust as the `onchain` module, in TypeScript from the package root and the `/idl` subpath. SDK 7.0.5 TypeScript client packages depend on `flint-idl` `^0.11.0`; SDK 7.0.0 through 7.0.4 depend on `^0.10`. The downloadable [maker examples](/makers/sdk-setup) remain pinned to their tested SDK 7.0.4 / IDL 0.10.2 combination. Use the operator's tested program/SDK combination: matching account layouts alone do not prove that order instruction encodings match a deployment. The raw IDL is downloadable as [`codama.json`](/codama.json) for generating clients in other languages. The IDL is written by hand against the program's Rust ABI (Anchor v2 slab accounts are not supported by Anchor's IDL generator) and pinned to it by golden byte fixtures: every instruction encoding, account size, and discriminator is checked against output produced by the program itself. `flint_margin_onchain::IDL_REVISION` (Rust) and `FLINT_ABI_REVISION` (TypeScript) name the program commit the fixtures came from. Both clients are also tested against bytes the program itself wrote: `rust/margin-onchain/idl/scenarios.json` holds every account kind and every event kind, produced by running real instructions through that revision of the program, with the expected values computed from the program's own Rust types; `tests/fixtures/devnet/` holds accounts and transactions recorded from the devnet deployment. ## Program versions Flint accounts carry no layout version: a layout change ships as a new program deployment. A client release decodes exactly one ABI, the one `IDL_REVISION` / `FLINT_ABI_REVISION` names, and refuses bytes that do not fit it instead of misreading them: * a fixed-size account whose length differs from the layout, or any account with bytes left over after decoding, fails to parse (`ParseError::Decode` in Rust, `FlintDecodeError` code `account-size` in TypeScript). The two update-proposal accounts are the exception: the program allocates them at their widest variant. In Rust this is new in the release after 7.0.4: `FlintAccount::parse` used to ignore leftover bytes, so code that parses accounts from a deployment with a different layout now gets an error where it previously got a partial read. * an event batch whose format byte this client does not know fails (`UnsupportedFormat` / `unsupported-event-format`), as does a known event record whose width moved (`RecordLayout` / `event-size`). Event tags are append-only, so a tag newer than the client decodes as `Unknown` and the rest of the batch still reads. * an instruction with bytes after the arguments this client knows is reported as undecoded rather than parsed. Upgrade the client when the program is redeployed. ## Program IDs | Cluster | Margin program | Oracle program | | --- | --- | --- | | Built-in reference | `4MxEr7W5f9xtajcTTpTTd5wfdBmwrF72j2NMg6YzXgMo` | `fnt2ARonSBGhBY1RTxVSvZ3p8JSYi9iAE9Hkq5jXcmf` | | Other network | Use the operator's tested program address and SDK combination | Use the operator's address | The margin program address is built in as `FLINT_ID` (Rust) and `FLINT_PROGRAM_ADDRESS` (TypeScript), and every generated PDA finder and instruction builder defaults to it. Pass `programAddress` (TS) to target another deployment. ## Install Through the client packages (recommended), or directly: :::code-group ```toml [Rust] [dependencies] flint-margin-onchain = { version = "=7.0.5", registry = "kellnr" } ``` ```sh [TypeScript] # Requires GitHub Packages access; see the TypeScript SDK page. npm install @superis-labs/flint-idl@0.11.0 @solana/kit ``` ::: ## PDAs | Account | Seeds | Rust | TypeScript | | --- | --- | --- | --- | | Trader account | `"trader_account"`, `trader_id: u64` | `TraderAccount::find_pda(trader_id)` | `findTraderAccountPda({ traderId })` | | Delegate | `"delegate_trader"`, `trader_id: u64`, `delegate: Pubkey` | `DelegateTraderAccount::find_pda(trader_id, &delegate)` | `findDelegateTraderAccountPda({ traderId, delegate })` | | Collateral market | `"spot_market"`, `mint` | `SpotMarket::find_pda(&mint)` | `findSpotMarketPda({ mint })` | | Perp contract | `"linear_perp"`, `linear_perp_id: u32` | `LinearPerp::find_pda(id)` | `findLinearPerpPda({ linearPerpId })` | | Perp quoting account | `"quoting"`, `linear_perp_id: u32` | `LinearPerpQuoting::find_pda(id)` | `findQuotingPda({ linearPerpId })` | | Perp order book | `"order_book"`, `linear_perp_id: u32` | `LinearPerpOrderBook::find_pda(id)` | `findOrderBookPda({ linearPerpId })` | | Global config | `"global_config"` | `GlobalConfig::find_pda()` | `findGlobalConfigPda()` | | Product catalog | `"product_catalog"` | `ProductCatalog::find_pda()` | `findProductCatalogPda()` | | Spot product cache | `"spot_product_cache"` | `SpotProductCache::find_pda()` | `findSpotProductCachePda()` | | Perp cache | `"linear_perp_cache"` | `LinearPerpCache::find_pda()` | `findLinearPerpCachePda()` | | Oracle cache | `"oracle_cache"` | `OracleCache::find_pda()` | `findOracleCachePda()` | | Insurance fund | `"cross_margin_if"` | `CrossMarginInsuranceFund::find_pda()` | `findCrossMarginInsuranceFundPda()` | | Event authority | `"__event_authority"` | `Pubkey::find_program_address(&[b"__event_authority"], &FLINT_ID)` | `findEventAuthorityPda()` | Trader ids are chosen by the account's creator; the API returns them as `TraderId`, and `AccountService.ListAccounts` finds every account an authority owns or delegates on. ## Decoding accounts The Rust `spot_positions()` and TypeScript `decodeSpotPositions()` helpers in SDK 7.0.4 / IDL 0.10.2 support reserved capacity nodes retained after full collateral withdrawal. Use these versions or newer for account reads throughout the maker lifecycle. :::danger[Validate account identity before decoding] The generated `from_bytes` (Rust) and `decode*` / `fetch*` helpers (TypeScript) are plain deserializers. They do not check the account's owner or its 8-byte discriminator. Check both before decoding data as a Flint account. ::: ### Rust: `parse::FlintAccount` Every program-owned account type implements `flint_margin_onchain::parse::FlintAccount`: * `parse_account(&account)` checks the owner is `FLINT_ID`, then the discriminator, then decodes. Use it whenever you have the whole account from RPC. * `parse(&data)` checks only the discriminator — for data whose owner you've already verified. ```rust use flint_margin_onchain::accounts::{ProductCatalog, TraderAccount}; use flint_margin_onchain::parse::FlintAccount; let trader = TraderAccount::parse_account(&account)?; for position in trader.spot_positions()? { println!("spot {} at node {}", position.position.spot_id, position.node_index); } let catalog = ProductCatalog::parse_account(&catalog_account)?; for product in catalog.live_products()? { println!("{product:?}"); } ``` `trader.linear_perp_positions()` walks the perp list the same way. Failures come back as `ParseError`: `WrongOwner`, `WrongDiscriminator`, `TooShort`, `Decode`, or `TruncatedSlab`. ### Slab accounts: read only the live range `ProductCatalog`, `SpotProductCache`, `LinearPerpCache`, `LinearPerpQuoting`, and `OracleCache` are over-allocated slabs: the trailing array covers every allocated slot, but only the first `length` entries are live. `TraderAccount`'s `tail` is a byte slab holding linked-list position nodes, and its unused capacity can hold stale nodes. `LinearPerpOrderBook`'s `tail` is a byte arena holding the bid, ask, and per-trader red-black trees; only the first `length` bytes are live. | Account | Rust live accessor | TypeScript | | --- | --- | --- | | `ProductCatalog` | `live_products()` | `data.products.slice(0, data.length)` | | `SpotProductCache` | `live_entries()` | `data.entries.slice(0, data.length)` | | `LinearPerpCache` | `live_entries()` | `data.entries.slice(0, data.length)` | | `LinearPerpQuoting` | `live_entries()` — one maker slot each | `data.entries.slice(0, data.length)` | | `OracleCache` | `live_entries()` — the index is the dense oracle id | `data.entries.slice(0, data.length)` | | `TraderAccount` | `live_tail()`, or `spot_positions()` / `linear_perp_positions()` | `decodeTraderPositions(data)` | | `LinearPerpOrderBook` | `live_tail()` — raw arena bytes | `readRestingOrders(data)` | Never read the raw trailing field directly. In Rust the accessors return `TruncatedSlab` if the header claims more entries than the account holds; in TypeScript `decodeFlintAccount` refuses such an account with code `truncated-slab`. The TypeScript client walks the order-book trees with `readRestingOrders`; in Rust, the header's `bids`, `asks`, and `traders` are `RedBlackTreeHead`s, and the generated `RestingOrder`, `OrderKey`, and `TraderOrders` types describe what the trees hold; see [Prices, sizes, units](/overview/units#on-chain-number-formats) for how keys encode price. ### TypeScript: `decodeFlintAccount` `decodeFlintAccount` takes the `EncodedAccount` that `@solana/kit`'s `fetchEncodedAccount` returns, checks its owner, identifies the account by discriminator, and decodes it to its last byte. The result is tagged with its `kind`; `decodeFlintAccountAs` asserts one kind. ```ts import { assertAccountExists, fetchEncodedAccount } from "@solana/kit"; import { decodeFlintAccount, decodeFlintAccountAs, decodeTraderPositions, findTraderAccountPda, } from "@superis-labs/flint-idl"; const [address] = await findTraderAccountPda({ traderId: 42n }); const encoded = await fetchEncodedAccount(rpc, address); assertAccountExists(encoded); const trader = decodeFlintAccountAs("TraderAccount", encoded); const { spot, linearPerp } = decodeTraderPositions(trader); const any = decodeFlintAccount(encoded); if (any.kind === "LinearPerp") console.log(any.account.linearPerpId); ``` Every failure is a `FlintDecodeError` with a `code`: `wrong-owner`, `unknown-account`, `account-size`, `truncated-slab`, or `invalid-position-list`. Every account module exports its `*_DISCRIMINATOR` constant. `u96` values decode as `{ low: bigint, high: number }`; convert with `u96ToBigInt` / `u96FromBigInt` (Rust: `trader::u96_to_u128` / `u96_from_u128`). Prefer the API for valuations. `AccountService.GetAccount` returns health, margin, and PnL computed against the program's caches; recomputing them from raw accounts means reimplementing the risk engine. ## Decoding events and transactions Each Flint instruction writes its events as one batch, carried in the data of an inner instruction where Flint invokes itself. Only trust a batch that Flint invoked: the program rejects an event instruction its own event authority did not sign, but the attempt can still show up in a failed transaction's inner instructions, and any program can put the same bytes in its own instruction data. Event values are the program's raw integers: atoms, lots and ticks as stored, and fixed-point fields (`Q0.64` fractions, `U64.32` share counts, `U32.64` share prices, `Q32` funding indices) as their raw bits. Scale them at the edge of your code; `u96` values come back as `{ low, high }` (TS) and `U96` (Rust). :::code-group ```ts [TypeScript] import { decodeFlintTransaction } from "@superis-labs/flint-idl"; const response = await rpc .getTransaction(signature, { encoding: "json", maxSupportedTransactionVersion: 0 }) .send(); const decoded = decodeFlintTransaction(response!); for (const event of decoded.events) { if (event.kind === "LinearPerpFill") console.log(event.traderId, event.lots, event.ticks); } for (const instruction of decoded.instructions) { console.log(instruction.flint?.instructionType, instruction.undecoded); } ``` ```rust [Rust] use flint_margin_onchain::events::{FlintEvent, decode_event_batch, is_event_batch}; if invoker == flint_margin_onchain::FLINT_ID && inner_program == flint_margin_onchain::FLINT_ID && is_event_batch(&inner_data) { for event in decode_event_batch(&inner_data)? { if let FlintEvent::LinearPerpFill(fill) = event { println!("{} {} {}", fill.trader_id, fill.lots, fill.ticks); } } } ``` ::: `decodeFlintTransaction` parses every top-level and inner Flint instruction, attaches each event batch to the instruction that emitted it, and lists all events in emission order. It works out each inner instruction's invoker from the response's stack heights and takes a batch only when Flint invoked it; a response without stack heights yields no events. `failed` is true when the transaction failed; the program still writes the events of the instruction that failed, so check it before treating them as settled. `complete` is false when any Flint instruction or batch could not be decoded (each carries its reason in `undecoded`), so `events` is missing something: check it before summing fills or balances. `decodeFlintEventBatch` (TS) and `decode_event_batch` (Rust) decode a single batch, and a trade's `source` names the order-list level, density band, or resting order it matched (`decodeLevelSource` / `LevelSource::decode`). ## Building instructions Instruction builders are generated per instruction: * **TypeScript:** `getXxxInstruction(input)` and, for instructions that emit events, `getXxxInstructionAsync(input)`, which derives the `__event_authority` PDA for you. * **Rust:** `XxxBuilder::new()…instruction()` returns a `solana_instruction::Instruction`. Set each required account explicitly. Optional accounts can be omitted. ```ts import { findGlobalConfigPda, findSpotMarketPda, findTraderAccountPda, getDepositInstructionAsync, } from "@superis-labs/flint-idl"; const [globalConfig] = await findGlobalConfigPda(); const [traderAccount] = await findTraderAccountPda({ traderId: 42n }); const [spotMarket] = await findSpotMarketPda({ mint }); const ix = await getDepositInstructionAsync({ globalConfig, rentPayer: signer, signer, traderAccount, spotMarket, mint, from: userTokenAccount, vault, tokenProgram, systemProgram, amount: { __kind: "Atoms", fields: [1_000_000n] }, }); ``` Instructions that take a variable account list expose only the fixed prefix; append the rest with `withRemainingAccounts(ix, metas)` (TS) or `add_remaining_accounts` (Rust): * `placePerpOrder` and `liquidatePerp` — the writable maker trader accounts follow the event accounts, **strictly ascending by trader id** (otherwise `PerpOrderMakersUnordered`). * `prunePerpOrders` — the writable trader accounts whose expired orders to remove follow the event accounts. * `updateOrderLists`, `updateDensityFairs`, `resetQuotingSequences` — one writable quoting account per contract the records name. * `syncSpotProductCache` — the product accounts follow the cache, event authority, and program accounts. `syncOracleSources` also takes a dynamic tail. Wallet-signed perp orders pass `authority: signer` with `auth: { __kind: "Wallet" }`. Passkey orders use the Instructions sysvar as `authority`, a writable delegate, and the passkey variant of `TraderAuth`, with the secp256r1 verification instruction immediately before the order — `@superis-labs/flint-margin-user-client` builds the pair with `buildPasskeyPerpOrderInstructions`. That helper builds `Ioc` orders and accepts optional `averagePriceTicks`. It has no order expiry; `validUntilSlot` expires the passkey authorization. It signs the instruction prefix before the trailing `clientDataJson` length and data: 50 bytes without an average bound, or 54 with one. See [Passkeys](/traders/accounts#passkeys) before building a different order kind. ### Perp orders and the book `placePerpOrder` takes grid `lots`, `direction`, `reduceOnly`, and `kind: PerpOrderKind`. Price and expiry belong to the chosen kind: * `Gtc { limit, postOnly }` crosses now and rests the remainder at `limit` until cancelled. `limit` is positive UInt32 ticks. * `Gtd { limit, postOnly, expiresAtSlot }` also rests the remainder, until the absolute expiry slot. An expiry already in the past is refused. * `Ioc { bound, minFillLots }` crosses and drops the remainder; it is refused if nothing crosses or the fill is below `minFillLots`. * `Fok { bound }` fills every requested lot within the bound or refuses the order. `bound: TakerBound { limit, average }` sets the worst individual fill price and an optional worst volume-weighted average price, before fees. Both prices are positive UInt32 ticks. An average bound above the individual limit for a long, or below it for a short, is refused (`PerpOrderAverageBeyondLimit`). On `Gtc` and `Gtd`, `postOnly: true` refuses the order if any of it would cross the maker accounts passed to the instruction. Makers omitted from that list do not prevent it from resting. The remaining arguments include: * `clientOrderId` — your handle, echoed on the order events. * `bookScan` — `OrderBookScan { maxOrders }`, how many resting orders to read: zero skips the book, `4294967295` is uncapped. Every perp order writes the contract's `orderBook` account. `modifyPerpOrder` changes or cancels one resting order, addressed by `MakerSide` and price; its `lots`, `reduceOnly`, and `expiresAtSlot` are `FixedOption*` values, where `isSome: false` keeps the current one and `lots` of zero cancels. `cancelAllPerpOrders` removes every resting order the trader has on a contract. Anyone can run `prunePerpOrders` to remove expired orders, and `forceCancelPerpOrders` to cancel the margin-reserving orders of a trader below initial margin. ### Quoting slots Flint grants a trader quoting permission with the admin `setTraderQuotingPermission`. The trader then opens its own disabled, empty slot on a perp with `openQuotingEntry` and can free it with `releaseQuotingEntry`. `updateQuoting` takes the owner or a `TRADE`-scoped delegate (with `delegateTraderAccount`); the batch instructions `updateOrderLists`, `updateDensityFairs`, and `resetQuotingSequences` also accept a `QUOTE`-scoped delegate. See [Maker onboarding](/makers/onboarding). ### Instruction catalog | Who | Instructions | | --- | --- | | Traders | `createTraderAccount`, `deposit`, `withdraw`, `lend`, `redeemLend`, `borrow`, `repayBorrow`, `placePerpOrder`, `modifyPerpOrder`, `cancelAllPerpOrders`, `setAccountLeverageCap`, `startFlashloan` / `endFlashloan` | | Delegates | `createDelegateTraderAccount`, `revokeDelegateTraderAccount`, `registerPasskeyDelegate`, `revokePasskeyDelegate` | | Makers | `openQuotingEntry`, `releaseQuotingEntry`, `updateQuoting`, `updateOrderLists`, `updateDensityFairs`, `resetQuotingSequences` | | Keepers | `liquidateSpot`, `liquidatePerp`, `backstop`, `settleFunding`, `settleRealisedPnl`, `prunePerpOrders`, `forceCancelPerpOrders`, `syncSpotProductCache`, `syncLinearPerpCache`, `syncOracleSources`, `syncLazerReport`, `publishOracles`, `applySpotMarketUpdate`, `applyLinearPerpUpdate`, `collectSpotBorrowLendFees`, `collectLinearPerpFees` | | Admin | `initializeProgram`, `updateGlobalConfig`, `claimAdmin`, `createSpotMarket`, `createLinearPerp`, `setTraderQuotingPermission`, `proposeSpotMarketUpdate` / `cancelSpotMarketUpdate`, `proposeLinearPerpUpdate` / `cancelLinearPerpUpdate`, `updateLinearPerpConfig`, `updateInsuranceFundConfig`, `globalPauseSpotMarket`, `socializeLoss`, `initializeOracles`, `registerOracleEntry`, `configureOracleSources`, `setOracleTiers`, `assignOracleTier` | Rust modules and builders use the snake_case / PascalCase forms (`deposit` → `DepositBuilder`). `syncLazerReport` must be top level and immediately follow an ed25519 verification instruction over the signed Lazer message; in TypeScript, `syncLazerReportEd25519InstructionData(message, syncInstructionIndex)` builds that instruction's data for `ED25519_PROGRAM_ADDRESS`. ## Submitting Sign the transaction, then select a submission path: * Your own Solana RPC (`sendTransaction`). * `SponsorshipService.SponsorTransaction`, which pays the fee: put `GetSponsorshipPubkey`'s key in the fee-payer slot, sign everything else, and leave the sponsor's signature empty. Only margin, compute-budget, and secp256r1 instructions are allowed. * `TxService.SubmitTx` for makers, authenticated as one of the transaction's signers. ## Errors Both clients carry the program's error enum — `errors::FlintError` in Rust, `FLINT_ERROR__*` constants with `isFlintError` and `getFlintErrorMessage` in TypeScript. The codes and their meanings are in [Errors](/overview/errors#program-error-codes). ```ts import { FLINT_ERROR__HEALTH_CHECK_FAILED, isFlintError } from "@superis-labs/flint-idl"; if (isFlintError(err, transactionMessage, FLINT_ERROR__HEALTH_CHECK_FAILED)) { // the order would leave an account below its margin requirement } ``` # Flint Perps On-chain perpetual futures on Solana — guides for traders and market makers * [Start trading](/traders) * [Maker onboarding](/makers/onboarding) * [SDKs](/sdks/rust) * [How Flint perps work](/overview/exchange): Markets, margin, funding, liquidations, and fees — the mechanics behind every position. * [I'm a trader](/traders): Fund an account, open and manage positions, and trade from the app or your own code. * [I'm a market maker](/makers/onboarding): Get access, fund a trader account, authorize a bot key, and confirm your first quote and fill.