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

# Orders and closes

> An open and a double-down are orders: the stake is taken when the order lands, and the venue's next price fills it. A close is a request that anyone completes. The rules a client or a bot builds against, the events that report each step, and the refusals.

An `open` and an `add_collateral` carry no price. Each one takes the stake when it lands and becomes
an **order** that waits for the venue's next price. A close is a **request** that carries no price,
and anyone may complete it with a priced `settle`.

This page is the contract a client or a bot builds against. A bot needs a wallet and an RPC
endpoint. It needs no access to the price feed, no token and no key from us.

<Note>
  **Build an open with `ix.open(accounts, args, priceAccountsFor(market, config, 'open'))`**, as
  [opening a position](/sdk/opening#build-the-instruction) shows, and an add with
  `addCollateralForMode`. Both read the governed `config`: decode it on every request. Never build
  an open with `openForMode`: it leaves out the market's fee-terms account, so the position freezes
  the legal maximum of the exit fee and no rung check runs.
</Note>

## The timing settings

Every wait on this page is a count of slots, and every count is a governed setting. Read them
through the SDK on each request. Do not compile one into a client.

| Setting | What it sets | Read it |
| - | - | - |
| `d` | The delay from an order, an add, a close request or an expiry mark to its **target slot** | `timing.d` |
| `T_q` | The latest slot after the target at which the first price can still fill an order or an add | `timing.tQ` |
| `T_fill` | The slots after the target in which a filler can fill. After them, anyone can return the order | `timing.tFill`, and `fillDeadlineSlots(timing)` for `d + T_fill` from the order |
| `W` | The exit window: the slots in which one price prices a requested or marked exit | `timing.wExit`, and `exitWindowSlots(timing)` for `d + W` from the request |
| `W_ko` | How old a price can be and still prove a knockout or a take-profit | `timing.wKo` |
| `M_add` | The slots after a filled double-down's price in which the position is held | `timing.mAdd`, and the copy on the position (`addMemo(position)`) |

Each setting can change by a governed write with no program upgrade. The SDK is the source:

```ts theme={"dark"}
import { Connection } from '@solana/web3.js';
import {
  configPda,
  decodeConfig,
  decodeGovTable,
  exitWindowSlots,
  fillDeadlineSlots,
  govTablePda,
  timingInForce,
} from '@rekt/1000x';

export async function readTiming(connection: Connection) {
  const [configInfo, govInfo] = await connection.getMultipleAccountsInfo(
    [configPda().address, govTablePda().address],
    'confirmed',
  );
  if (configInfo === null) throw new Error('the config account is missing');
  const config = decodeConfig(new Uint8Array(configInfo.data));
  const gov = govInfo === null ? null : decodeGovTable(new Uint8Array(govInfo.data));
  // `null` for a setting that is not in force. There is no default: do not invent one.
  const timing = timingInForce(config, gov);
  return {
    config,
    timing,
    fillDeadline: fillDeadlineSlots(timing), // d + T_fill, from the slot the order landed in
    firstExitWindowEnds: exitWindowSlots(timing), // d + W, from the slot the request landed in
  };
}
```

## Opening: an order, then a fill

1. **Send `open`.** It carries no quote. Its remaining accounts are the market's fee-terms account
   when the market has terms (`priceAccountsFor(market, config, 'open')`). When it lands, the stake is taken, the slot holds the order with
   `entryPrice` 0, and `PositionOpened` carries the sequence number. It is an order, not yet a
   position.
2. **The entry price is the first price of the venue's price feed at or after `d` slots from the slot
   in which the `open` landed.** A listed filler writes it. Read it from the event `EntryFilled`
   (`entryPrice`, `quoteSlot`), or from a non-zero `entryPrice` on your own book.
3. **Or the order is returned.** The event is `OpenVoided`: `cause`, `refundTotal`, `feeKept` and
   `delivered`. An order is returned when the first price came too late or did not meet the venue's
   quality rule, when the market closed, or when no filler landed inside `d + T_fill` slots of the
   order. `refundTotal` is the whole stake, and `feeKept` reads 0. `delivered` false means the payout
   destination could not receive the refund, and it is held on the book: claim it with `claimPayout`.
4. **You cannot fill your own order** (`NotAListedFiller`, 6205). You need not return it either:
   after `d + T_fill` slots anyone may send `void_open`, and the venue's keepers do.

| `cause` | What it means |
| - | - |
| 1 | The price came late. |
| 2, 3, 4 | The price did not meet the venue's quality rule. |
| 5, 6 | The market is closed or about to close. |
| 7 | Not filled in time. |

`voidReasonOf(cause)` names the cause, and `VOID_REASON_TEXT` has the words for a trader. The fill
is the step that judges the price. So the price rows of `openGates` do not decide an order, and the
quote refusals of [quotes and freshness](/sdk/quotes#related-refusals) cannot refuse one.

Build with `ix.open` and `priceAccountsFor(market, config, 'open')`, send, and then watch the book. The `EntryFilled` or `OpenVoided` event is
in the filler's or the keeper's transaction, not in yours:

```ts theme={"dark"}
import { Connection, PublicKey } from '@solana/web3.js';
import { decodeTraderAccount, findBySeq, phase, traderPda, type Phase } from '@rekt/1000x';

/** Where an order stands: 'PendingOpen' until the fill, then the position's phase, or 'gone'. */
export async function orderState(
  connection: Connection,
  owner: PublicKey,
  seq: bigint,
): Promise<Phase | 'gone'> {
  const [info, nowSlot] = await Promise.all([
    connection.getAccountInfo(traderPda(owner).address, 'confirmed'),
    connection.getSlot('confirmed'),
  ]);
  if (info === null) return 'gone';
  const position = findBySeq(decodeTraderAccount(new Uint8Array(info.data)), seq);
  // Gone: returned (`OpenVoided`) or already settled. The events say which.
  if (position === null) return 'gone';
  // After the fill, `position.entryPrice` is the entry and `position.openedSlot` its slot.
  return phase(position, nowSlot);
}
```

## Doubling down

Send `add_collateral` with no quote. `CollateralCommitted` is in your own transaction:
the added stake is taken (`addedCollateral`, `totalDebit`), the timer extension is written at once
(`timerAfterS`), and `addTarget` is the slot `d` after the add. Then one of two:

* **`CollateralAdded`.** A filler priced the add at the first price at or after `addTarget`. It
  carries the new entry (`entryPrice`) and the entry before it (`entryPriceBefore`).
* **`AddReversed`.** The added stake is returned in full (`refundTotal`), as for a returned order.
  `cause` is one of the open's causes, or 8 (the price
  was outside the position's limits: at the add's price the position was already at its barrier or
  its target) or 9 (the position was knocked out first).

Rules a client must handle:

* **Until one of the two arrives, and then for `M_add` slots after the price of a filled add**, a
  close request, a retarget, an expiry mark and a second add return `AddPending` (6211).
  `closableFromSlotOf` includes that time.
* **A reversed add keeps the time it added to the timer.** Its stake comes back.
* **A knockout price from before the add still counts in that time.** A price that breached the
  barrier of the entry in force at its slot can still prove the knockout. `KnockoutBeforeAdd`
  reports it, the position is knocked out on its old stake, and the added stake comes back
  (`AddReversed`, cause 9).
* **A touch of the take-profit target while an add waits for its price cannot be stamped**, and it
  is lost if the add is then filled.
* `addCollateralForMode` refuses to build an add while adds are off (`AddsDisabled`, 6212).

## Closing: a request, then a settle

1. **Send `request_close`**, signed by the owner or a session key. It carries no quote, so it cannot
   fail on a stale price. `CloseRequested.exitTarget` is the slot `d` after the request.
2. **The exit price is the first price of the venue's price feed at or after `exitTarget`.** The
   close is done when `SettlementPriced` and `SettlementReceipt` arrive.
3. **Anyone may send that settle, and the venue's keepers do.** `isPermissionless('voluntary',
   phase)` is true once the phase is `ExitRequested`.
4. **The window rule.** If nobody settles inside `W` slots of the target, the price becomes the first
   price of the next window of `W` slots, and so on. `SettlementReceipt.window` is the count of
   windows that rolled: 0 for a close settled in its first window.
5. **A request cannot be withdrawn.** A second request returns `ExitPending` (6213): treat it as
   success.
6. **`SettlementPriced.unverified` reads `true` while a market lists one price feed.** A price from
   a single feed is `unverified` by construction; it does not mean the price was wrong. Do not show
   a warning or a badge from it.

<Warning>
  **Never show "your close price is locked".** Until the settle lands, the next window's price can
  apply, and a knockout price in between counts.
</Warning>

```ts theme={"dark"}
import { PublicKey } from '@solana/web3.js';
import { assetId, configPda, ix, marketPda, toHex, traderPda } from '@rekt/1000x';

export function buildRequestClose(args: {
  owner: PublicKey;
  /** The owner, or a session key they authorised. */
  signer: PublicKey;
  /** `delegatePda(owner).address` when a session key signs, else null. */
  delegate: PublicKey | null;
  symbol: string;
  seq: bigint;
}) {
  return ix.requestClose(
    {
      authority: toHex(args.signer),
      config: toHex(configPda().address),
      // The position's own market: it carries the market's hold.
      market: toHex(marketPda(assetId(args.symbol)).address),
      positions: toHex(traderPda(args.owner).address),
      delegate: args.delegate === null ? null : toHex(args.delegate),
    },
    args.seq,
  );
}
```

`request_close`, `mark_expiry` and `open` need no quote instruction: send each one with the
compute-budget pair. `computeUnitLimit(built.name)` gives the declared limit, and for the
open `computeUnitLimit('open', { quotePost: false })`.

## The minimum hold

A position cannot be closed by its owner in the first slots after its entry price.

* The venue has one hold for every market: `resolveMinHoldSlots(config.minHoldSlots)`. A market can
  have a longer one: `market.closeHoldSlots` (0 is none). The hold in force is the larger of the two
  (`holdInForce`).
* It counts from the entry price: `EntryFilled.quoteSlot`, and after a filled double-down from the
  add's price, which is the position's `openedSlot` on the book.
* A close request inside it returns `MinimumHoldNotMet` (6103) for the venue's hold or
  `MarketHoldNotMet` (6236) for the market's. Neither one is a failure of the position.
* **"Can close in N seconds", with no guess:** `closableFromSlotOf(position, config, market)` is the
  first slot at which a request can land, from three public accounts. It is `null` while the
  position is an order or an add waits. `slotsUntil(slot, nowSlot)` counts the slots, and
  `estimatedSecondsUntil(slots, slotMs)` turns them into an estimate that you label as one.
* The request must land at or after that slot.
* A take-profit proof must also clear both holds, on the slot of its own price.
* A knockout is never held back. A market's own hold never holds back a timer end.

## Timers

* From the deadline, anyone may send `mark_expiry` with reason 3 (`userExpiry`) or 4
  (`tenorExpiry`). `ExpiryMarked.exitTarget` is the slot `d` after the mark, and the exit price is
  the first price at or after it, by the same window rule as a close.
* A settle with an expiry reason on a position that has no exit target returns `ExitNotAnchored`
  (6214): mark it first.
* A wrapper such as Minute sends the mark and then its own settle from slot `exitTarget` on: two
  transactions, at least `d` slots apart.
* A timer is 0 or at least 26 seconds, at the open and after an add (`TimerBelowFloor`, 6225).

## Knockouts and take-profits

* **Anyone may prove a level with any signed price that is newer than the entry and at most `W_ko`
  slots old.** So a position can be knocked out by a price up to `W_ko` slots old.
* After a close request or an expiry mark, only a price that is not later than the exit price can
  prove a level (`ExitPending` otherwise).
* While an add waits for its price, a touch of the take-profit target cannot be stamped, and it is
  lost if the add is then filled.
* After a take-profit retarget, only a price at least `d` slots after the retarget proves the new
  target (`TakeProfitProofTooEarly`, 6220).
* `stamp_book` records the level on the book; the settle that pays it follows. It is the only stamp
  that can prove a knockout with a price from before a filled double-down.

## Settling or stamping for others

This is optional. The venue's keepers send every settle and every stamp.

* A settle of a requested or marked exit needs the **window quote**: the first price of the venue's
  price feed at or after the start of the current window, `exitTarget + K × W`, where `K` is the
  count of whole windows since the target. Any other quote returns `NotWindowQuote` (6217).
* `stamp_book` needs any signed price that breaches the level, inside the `W_ko` look-back.
* To obtain a quote, see [Getting prices](/sdk/prices).

## Three more rules

* **Show "side full" before the tap when a limit is set.** A market can carry a limit on the stake
  of one side: an amount, a share of the pool, or both, and the smaller one is in force. With no
  limit set, nothing refuses. `sideRoom(direction, market, vault, vaultBalance)` reads the room from
  the market's two stake counters, its two limits and the pool's assets. On a full side whoever lands
  first gets the room: there is no waiting list for it. A full side returns `SideStakeCapReached`
  (6224). (The queue of [how a win is paid](/liquidity#how-a-win-is-paid)
  is a different thing: it orders payouts, not room.)
* **A client may settle its own requested close** in the first window, with the window quote. That
  keeps the first price. If nobody settles in `W` slots, the next window's price applies, and a
  knockout price in between counts. The count of rolled exits is public: `SettlementReceipt.window`
  is not 0 for them.
* **Read a position through the SDK's accessors**: `phase`, `isUnfilled`, `pendingAdd`,
  `addTargetSlot`, `addMemo`, `memoIsLive`, `exitTargetSlot`, `exitReason` and `tpFloorSlot`. The
  same raw bytes of a position mean different things in different states. See
  [reading positions](/sdk/positions#orders-and-pending-steps).

## What a client must never assume

* **That the price on screen at the tap is the entry or the exit.** It is the next price after the
  delay.
* **That a landed `open` is a position.** It is an order until `EntryFilled`.
* **That a landed `request_close` is a close.** It is a close at `SettlementPriced`.
* **That `PositionOpened` carries the entry.** It has no price field. A position's `entryPrice` on
  the book is 0 until `EntryFilled`. Take entries from `EntryFilled` and `CollateralAdded`, and
  returns from `OpenVoided` and `AddReversed`.
* **That a price in one of Minute's round events is a price.** See
  [integrating Minute](/minute/integrating#events-of-an-order).
* **That a second transaction is safe after a timeout.** Never sign a second order for the same
  intent inside one blockhash life. Send the same signed bytes again.
* **That adds are on.** `addCollateralForMode` refuses to build one while they are off.
* **That a void or a reversal is an error.** It is a normal outcome, and the stake comes back
  (`refundTotal`).
* **That two events in one slot are in time order.** Order by `quoteSlot`.

Join every venue event on `openerTag`.

## Events

| Event | When | What to read |
| - | - | - |
| `PositionOpened` | the order lands | `seq`, `openerTag`. No price |
| `EntryFilled` | a filler writes the entry | `entryPrice`, `entryExpo`, `quoteSlot`, `commitSlot`, `collateral` |
| `OpenVoided` | the order is returned | `cause`, `refundTotal`, `feeKept`, `delivered`, `commitSlot` |
| `CollateralCommitted` | the add lands | `addedCollateral`, `totalDebit`, `timerAfterS`, `addTarget` |
| `CollateralAdded` | a filler prices the add | `entryPrice`, `entryPriceBefore`, `addedCollateral` |
| `AddReversed` | the add is returned | `cause`, `refundTotal`, `feeKept`, `timerAfterS`, `delivered` |
| `CloseRequested` | the request lands | `exitTarget`, `viaSettle` |
| `ExpiryMarked` | the mark lands | `reason`, `exitTarget` |
| `KnockoutBeforeAdd` | a price from before a filled add proves a knockout | `quoteSlot`, `oldEntryPrice`, `addedCollateral` |
| `SettlementPriced`, `SettlementReceipt` | the close | the receipt's `window`, `exitFeeTotal`, `payoutTrader`, `exitFeeBps`, `entryQuoteSlot`, `exitQuoteSlot` |

Each has a decoder: `decodeEntryFilled`, `decodeOpenVoided`, `decodeCollateralCommitted`,
`decodeAddReversed`, `decodeCloseRequested`, `decodeExpiryMarked`, `decodeKnockoutBeforeAdd` and
`decodeSettlementReceipt`. On a landed transaction, `receiptOf` and
`venueEventsNamed(receipt, 'EntryFilled')` read them all. Every event starts with `owner`,
`market`, `seq` and `openerTag`.

## Refusals a client can get

The codes are fixed. `retryClassOf(code)` gives the class of each one, and `RETRY_CLASS_LABEL` the
words: see [error handling](/sdk/errors#orders-closes-and-stamps).

| Instruction | Error | What the client does |
| - | - | - |
| `open` | `SideStakeCapReached` (6224) | A limit is set on that side of the market and the side is full. Try later, or the other side. |
| `open`, `add_collateral` | `TimerBelowFloor` (6225) | Use a timer of 26 seconds or more, or none. |
| `open`, `add_collateral` | `ProtocolPaused` (6046), `MarketPaused` (6047), `OracleFrozen` (6190), `SettlesDisabled` (6191) | The venue or the market is stopped. Do not retry in a loop. |
| `open`, `add_collateral` | `MarketClosed` (6094), `SessionEndsBeforeTenor` (6095), `InsideEventWindow` (6048) | The market's calendar. |
| `add_collateral` | `AddsDisabled` (6212), `EntryNotFilled` (6203), `AddPending` (6211), `ExitPending` (6213) | Adds are off; wait for the entry; wait for the earlier add; a close is on its way. |
| `request_close` | `EntryNotFilled` (6203), `AddPending` (6211), `ExitPending` (6213) | Wait; wait; the close is already requested. |
| `request_close` | `MinimumHoldNotMet` (6103), `MarketHoldNotMet` (6236) | Wait for the hold: `closableFromSlotOf` gives the slot. |
| `request_close` | `OracleFrozen` (6190), `SettlesDisabled` (6191) | The venue is stopped. Do not retry in a loop. |
| `request_close` | `ModeBitOff` (6228) | Close requests are switched off. Do not retry in a loop. |
| `settle` sent by a client | `ExitNotAnchored` (6214), `ExitPending` (6213) | Request or mark first; the owner's close is already requested, so treat it as success. |
| `settle` sent by a client | `ExitQuoteNotYetDue` (6216), `NotWindowQuote` (6217), `ExitReasonMismatch` (6215) | Wait for the target slot; use the quote of the current window; send the recorded reason. |
| `settle` sent by a client | `NoFreshPrice` (6007) | The quote is bad or dated ahead of the chain, or the venue is frozen. |
| `settle` sent by a client | `OracleFrozen` (6190), `SettlesDisabled` (6191), `WrongMarketForPosition` (6070) | The venue is stopped, so do not retry in a loop; pass the position's own market. |
| `stamp_book` sent by a client | `ProofNotAfterEntry` (6218), `ProofTooOld` (6219) | That quote can never prove it; it is too old. |
| `stamp_book` sent by a client | `AddPending` (6211), `ExitPending` (6213), `OracleFrozen` (6190), `SettlesDisabled` (6191) | Wait for the end of the add's window; the quote is later than the exit price; the venue is stopped. |
| `fill` | `NotAListedFiller` (6205) | A client cannot fill. |

When the venue stops taking orders, `open` and `add_collateral` return `ProtocolPaused` (6046) and
`config.paused` is true, as for any pause. Closes, timer ends and knockouts keep working, and an
order already placed is filled or returned as usual. See [risk](/risk#when-the-venue-pauses).


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