> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rekt.money/llms.txt
> Use this file to discover all available pages before exploring further.

# Reading gates

> Every refusal the program can raise, before you send — with the value it measured and the threshold it was judged against.

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**. The guardian stops come first, the freeze and then the settle
stop, and on a Minute book `MinuteDisabled` follows them. The table below lists them and the rest in
that order. An open carries no quote, and the fill judges the price: see
[the other gate reports](#the-other-gate-reports) and [orders and closes](https://docs.rekt.money/sdk/orders.md).

> `CollateralOutOfRange` teaches nothing.
> `stake $0.10 is below the minimum $0.20` teaches everything.

## What a gate is

```ts theme={"dark"}
import { type Gate } from '@rekt-trading/1000x';

export function render(gate: Gate): string {
  return [
    gate.ok ? 'PASS' : 'FAIL',
    gate.label,             // a short name for the rule
    gate.value,             // what was measured, already formatted for a human
    gate.threshold,         // what it had to clear
    gate.error ?? '',       // the ErrorCode name the program raises when this gate fails
    gate.detail,            // one sentence on what this gate is protecting
  ].join('  ');
}
```

> [!WARNING]
>
> **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.

```ts theme={"dark"}
import { APPROACHING, approaching, headroom, type Gate } from '@rekt-trading/1000x';

export function bar(gate: Gate): { fill: number | null; warn: boolean } {
  return {
    // null on a categorical gate: draw a tick or a cross, not a bar.
    fill: headroom(gate),
    // Still passing, but inside the last APPROACHING of its room. Paint it.
    warn: approaching(gate),
  };
}

export const warnBand = APPROACHING;
```

## 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.

```ts theme={"dark"}
import { Connection, PublicKey } from '@solana/web3.js';
import {
  INITIAL_POSITION_SLOTS,
  decodeTraderAccount,
  openGates,
  slotCapacity,
  traderPda,
  type ConfigView,
  type GateReport,
  type MarketView,
  type OpenRequest,
  type PriceReport,
  type TraderAccountView,
  type VaultView,
} from '@rekt-trading/1000x';

export async function gatesForOpen(
  connection: Connection,
  state: {
    config: ConfigView;
    market: MarketView;
    vault: VaultView;
    price: PriceReport;
    nowUnix: bigint;
  },
  owner: PublicKey,
  traderToken: PublicKey,
  req: OpenRequest,
): Promise<GateReport> {
  const traderAddress = traderPda(owner).address;
  const info = await connection.getAccountInfo(traderAddress, 'confirmed');

  // `null` is a legitimate state: a trader who has never traded. It is not an error.
  const trader: TraderAccountView | null = info
    ? decodeTraderAccount(new Uint8Array(info.data))
    : null;

  const balance = await connection.getTokenAccountBalance(traderToken, 'confirmed');

  return openGates(
    {
      config: state.config,
      market: state.market,
      vault: state.vault,
      // The quotes the price report admitted, plus the ones it did not — pass what you read. The
      // price rows do not decide an order: the fill judges the price.
      quotes: state.price.admitted,
      trader,
      // The account's ALLOCATION, not the length of its position vector. They differ the moment
      // anything settles, and using the vector's length refuses opens the program would admit.
      slotCapacity: info ? slotCapacity(info.data.length) : INITIAL_POSITION_SLOTS,
      traderBalance: BigInt(balance.value.amount),
      nowUnix: state.nowUnix,
    },
    req,
  );
}
```

> [!NOTE]
>
> `OpenRequest` is `{ budget: bigint; minCollateralOut: bigint; direction: 'long' | 'short'; rungBps: number; userTimerS: number; tier?: number }`.
> `budget` is the most that leaves the wallet, and `minCollateralOut` is your floor on the stake it
> buys; zero is refused. `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, or to 26 seconds or more.
>
> **`tier` is optional and `0` or absent means the market's own.** Every live market sells 1000x,
> so every open is 1000x: a lower tier is refused `LeverageBelowMinTier`, and a higher one
> `LeverageAboveMarketTier`. Every distance a quote returns is `something / L`, so 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

| Gate | Refuses with |
| - | - |
| the oracle is frozen by the guardian — not in `report.gates`; read `config.oracleFrozen` | `OracleFrozen` |
| settles are disabled by the guardian — not in `report.gates`; read `config.settlesDisabled` | `SettlesDisabled` |
| a Minute book opens on a market where Minute is off — not in `report.gates`; read `market.minuteEnabled` | `MinuteDisabled` |
| protocol paused | `ProtocolPaused` |
| market paused | `MarketPaused` |
| the market's session calendar is shut, or less than a whole minimum tenor remains | `MarketClosed`, `SessionEndsBeforeTenor` |
| inside a scheduled event window | `InsideEventWindow` |
| user timer past the tenor | `TimerExceedsTenor` |
| `minCollateralOut` left at zero | `MinCollateralOutRequired` |
| collateral outside the governed bounds, or a budget above the ceiling | `CollateralOutOfRange`, `BudgetExceedsMaxCollateral` |
| the budget buys less than the `minCollateralOut` you signed | `BudgetBelowCollateralFloor` |
| leverage above the market's tier | `LeverageAboveMarketTier` |
| leverage below the venue's floor for this market (every open is 1000x) — the floor is `min(config.minTier, market.tier)`, so resolve it against the market's own tier, never from `minTier` alone | `LeverageBelowMinTier` |
| leverage the program does not offer | `RateOutOfRange` |
| rung below the take-profit floor (2.00x) | `RungBelowFloor` |
| rung above the payout cap | `RungAbovePayoutCap` |
| a user timer of 1 to 25 seconds — not in `report.gates`; send zero, or 26 seconds or more | `TimerBelowFloor` |
| the cover rule does not admit the fill — the filler skips the order, and the timeout return gives back the whole stake with no fee | none: a returned order is an outcome, not a refusal; not in `report.gates` |
| this side of the market is at its stake limit — only where a market has a side limit set, and none has | `SideStakeCapReached` |
| the trader has no free slot | `PositionSlotsFull` |

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](https://docs.rekt.money/sdk/errors.md) 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.

> [!NOTE]
>
> **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.

> [!WARNING]
>
> **`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 right after
> the first three rows of the table above and before the rest, 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,
>     debit)`** (the whole debit, never the stake alone) 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](https://docs.rekt.money/sdk/sessions.md#gate-before-you-send) 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).

```ts theme={"dark"}
import { approaching, headroom, type GateReport } from '@rekt-trading/1000x';

export function table(report: GateReport): string[] {
  return report.gates.map((g) => {
    const room = headroom(g);
    const mark = !g.ok ? 'x' : approaching(g) ? '!' : '.';
    const pct = room === null ? '' : ` ${Math.round(room * 100)}% room`;
    return `${mark} ${g.label.padEnd(38)} ${g.value} / ${g.threshold}${pct}`;
  });
}
```

## The other gate reports

The same shape covers everything the program can refuse:

| Function | Answers |
| - | - |
| `openGates(ctx, req)` | can this position be opened |
| `addGates(ctx, req)` | can this position take this add |
| `delegateGates(ctx, collateral)` | may this session key sign this open — owner, identity, expiry, spend window |
| `sessionAllowanceGates(ctx, debit)` | is the SPL token allowance still large enough for the whole debit of this trade |
| `settleGates(ctx)` | can this close be submitted, by this submitter, on this market |
| `priceGates(quotes, market)` | is there an admissible price at all, and what is the median |
| `sideRoom(direction, market, vault, vaultBalance)` | the stake a side can still take under a side limit, for a market that sets one |
| `closableFromSlotOf(position, config, market)` | the first slot at which a close request can land, or `null` while there is none |

Settlement's gates are covered in [settling a position](https://docs.rekt.money/sdk/settling.md); the two session reports in
[session keys](https://docs.rekt.money/sdk/sessions.md).

> [!NOTE]
>
> **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, which has nothing to judge while no market sets a limit.
> [Orders and closes](https://docs.rekt.money/sdk/orders.md) has the rest.

> [!NOTE]
>
> **`addGates` is the report for `add_collateral`.** An add takes `open`'s account list, `open`'s
> entry fee and `open`'s side-limit rule, 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:

* 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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.