Skip to main content
This is the reason the SDK exists rather than a generated IDL client. A generated client lets you build a transaction and find out afterwards. openGates tells you first: every gate that can refuse the action, with its measured value and its threshold, in the program’s order for an open. On a Minute book, MinuteDisabled comes first, then the guardian freeze. The table below lists the rest in that order. An open carries no quote, and the fill judges the price: see the other gate reports and orders and closes.
CollateralOutOfRange teaches nothing. stake $0.10 is below the minimum $0.20 teaches everything.

What a gate is

Never parse value back into a number. value and threshold are strings formatted for a reader. If you need a magnitude — for a progress bar, a colour, a chart — use gate.scale, which carries measured, limit and a headroom from 0 to 1. Recovering 0.115 from "11.5867 bp" means recomputing a protocol quantity in your presentation layer, which is exactly how an interface and a chain come to disagree.
scale is absent on categorical gates — paused or not, above the take-profit floor or below it — because a switch has no headroom and inventing one would put a half-full bar under a fact that is simply true or false.

Building the context for an open

openGates needs the state that the refusals are judged against. Everything in it is a read you already know how to do.
OpenRequest is { collateral: bigint; direction: 'long' | 'short'; rungBps: number; userTimerS: number; tier?: number }. rungBps is basis points of stake — 20_000 is 2.00x, the floor — not a whole multiple. Set userTimerS to 0 for no timer of your own.tier is optional and 0 or absent means the market’s own. It matters because a market’s tier is a CEILING rather than the only leverage it sells: every distance the quote returns is something / L, so a report derived from market.tier while the request names 250x on a 1000x market shows a barrier four times too tight and calls every gate green. Pass the same tier to openPreview — it takes one as its seventh argument — or use client.openGates, which threads the whole request through both.

The refusals, in the order the program reaches them

Most of these are states, not faults. A frozen oracle, a paused market, a shut session, a market inside an event window, a pool at capacity and a trader whose book is full are all the protocol working. Show the sentence, not a stack trace — and see error handling for which ones retrying can ever fix. openGates also reports open.balance last, which is not a refusal of this program at all: it is the trader’s SPL balance against the total debit, and the token program raises SplInsufficientFunds. It is in the report because a client that shows twelve green protocol gates and then fails on funds has explained nothing.
A market with no session calendar always passes the session row. On a market that has one, a position must be dead before the close, with the market’s own settlement buffer to spare, so no position is ever live while the market is shut. An open near the close is given a shorter tenor rather than refused, until less than a whole minimum is left. MarketClosed means there is no session open now; SessionEndsBeforeTenor means there is one, but not enough of it left.open.timer comes before the collateral rows. A timer past the tenor and a zero minCollateralOut together are refused TimerExceedsTenor, which is the program’s order.
openGates does not model a session key’s own refusals — a second report does. If authority is a session key rather than the trader’s own wallet, the program checks it very early, before the other gates in the table above, and can refuse with DelegateExpired, DelegateWindowLimitReached, NotTheDelegate or DelegateMayNotSign. None of them appear in the table above or in report.gates.Call delegateGates(ctx, collateral) for those four, and sessionAllowanceGates(ctx, collateral) for the SPL token allowance — the one clock no account of this program can see, and the one that ends most long sessions. A clean openGates report says nothing about either. See session keys for the whole shape.

Rendering the whole table

Show every gate, not only the failure. A user who can see that every gate but one passes, and that the one still passing is close to binding, understands the system; a user who sees a single red error learns nothing. Render report.gates rather than a count of your own — the row set depends on the market (a single-feed market has no quorum row).

The other gate reports

The same shape covers everything the program can refuse: Settlement’s gates are covered in settling a position; the redemption three in providing liquidity; the two session reports in session keys.
An open and an add read no price. The rows that judge a quote (quote.*, price.*) and the forecast rows (open.forecast, add.forecast) do not refuse an order: the fill judges the first price, and a price that fails returns the order. Pass sideRoom in the context, and openGates and addGates add the side-limit row. Orders and closes has the rest.
addGates is the report for add_collateral. An add takes open’s account list, open’s entry fee and open’s side limit, so it is refused by nearly everything openGates covers, plus its own rows: the position must be live and on this market, the engine bounds the increment against the position’s total after the blend, and add.forecast compares the position’s remaining life with the oracle’s forecast of the close.

Why this shape and not exceptions

A refusal here is data, never a thrown string. Three reasons, all of them learned the hard way:
  • An interface renders several gates at once, and a user deliberately provoking one — trying a stake above the cap to see where the cap is — is the normal case, not the exceptional one.
  • The measured value is the whole explanation. An exception carrying only a name throws away the half of the message that helps.
  • Predicting the refusal client-side costs nothing and saves a transaction fee, a round trip, and a confusing failure at the worst moment.