Skip to main content
This page assumes the reader is a capable model with no prior context, building a client that decides and sends without a human watching each action. It is short, dense and imperative. Every section links to the page that explains it properly — read this first, then follow the links you need.
Knockout is a capped barrier product. A position can be knocked out by a small move and end at zero. An autonomous client is a client that can lose money quickly and without anyone noticing. Build the read path and the refusal path before you build the send path.

The loop

Five steps, in this order. Skipping step 2 or 3 is how a client ends up learning the protocol from reverted transactions.
1

Read the protocol

Config, vault and market, decoded from the chain — Read the protocol. Addresses are derived locally with configPda, vaultPda, marketPda, so you never trust an address printed on a page. Never cache config.params: every field in it is governed and can move without a program upgrade.
2

Price it

openPreview / costToOpen tell you what the trader is actually charged and the gross payout at the target, which is before the exit fee and the share of the gain the terms withhold. Do not recompute protocol arithmetic yourself — a second derivation of a number is how a client and a chain come to disagree.
3

Ask what would refuse it

openGates and settleGates return every gate with the measured value and the threshold — Reading gates. Branch on the first failing gate.Order is a per-function property. openGates follows the program’s order for an open. settleGates emits its own sequence and does not reproduce the program’s settle order: the program checks the reason before the price, and settleGates appends the reason at the end. Neither report reads the guardian flags; read config.oracleFrozen and config.settlesDisabled yourself. An agent that sends to find out is paying for information it could have had for free.
4

Send, then wait for the step to end

An open, an add and a close request carry no quote. Build an open with ix.open(accounts, args, priceAccountsFor(market, config, 'open')), an add with addCollateralForMode and a close request with ix.requestClose, sign, send, and then wait for the event that ends each step: EntryFilled or OpenVoided for an order, CollateralAdded or AddReversed for an add, SettlementPriced for a close. A landed transaction is an order or a request, never the result. Orders and closes is the contract, and Opening has the code.A quote matters only if you send an instruction that carries one yourself: a settle, a stamp_book or a set_take_profit. Those age out in slots, not seconds, judged when the program runs rather than when you obtained the quote. Obtain a new quote immediately before signing, sign locally, and never put a human approval between the two. See Settling and Quotes and freshness, and Getting prices to obtain one.
5

Reconcile

Compare what you predicted against what the chain moved. A client that never checks is a client that will be wrong quietly. Decode the events rather than inferring from balances: the SDK exports decodeSettlementPriced, decodePositionOpened, decodePositionStamped, decodeEntryFilled, decodeOpenVoided, decodeAddReversed and decodeSettlementReceipt, and readTradeHistory reads them back off a cluster. Join every event on openerTag.

Refusals: branch on retryable, never on the name

The error catalogue is generated from the SDK’s client IDL, so it cannot drift from what the chain raises — Error handling. The one field that matters to an autonomous client is retryable: Resolve with errorForCode / errorForName. Do not pattern-match error strings. The names are an ABI and the codes are stable; your regex is neither. For the order and close codes, 6203 to 6238, bind to retryClassOf(code): it says “done”, “send the other instruction” and “retry with the right quote”, which retryable cannot. Two refusals that mean something different from what they sound like:
  • PositionSlotsFull — the trader’s book is full, not the venue. A book holds a fixed number of positions, chosen when it was created and changeable afterwards.
  • SideStakeCapReached — a market’s limit on one side’s stake is set and that side is full. It clears as other positions settle. Back off; do not spin. A pool that cannot cover an order is not a refusal: the order is returned with the whole stake.

What constrains you, whatever you are trying to do

These hold on chain and no client can talk its way past them:
  • The cover rule. A fill is admitted only if the pool can cover it. The number of concurrent positions the venue admits depends on the pool and the queue. An order that does not fit is returned with the whole stake and no fee: treat it as a state, not a fault.
  • A win may be paid later. A win is paid in full, but part of it may be paid later, in order, and SettlementPriced.trader is only what was paid now. Never present it as the amount won: read SettlementQueued.
  • Money in = money out on every path. The program never mints and never burns.
  • The pauses close the entry, not the exit. A pause of the venue or of one market stops opens, adds and take-profit retargets; settle and LP exits continue. An order already placed is still filled or returned.
  • An order is not a position, and a request is not a close. The entry is the venue’s next price after a short delay, and the exit is the first price after the close’s target, by the window rule. Never show a price from the tap as the entry or the exit.
  • A stop can freeze both prices and closes. An oracle freeze refuses opens, adds and lastResort, and starves every priced close of a price. A settle stop refuses every close reason. Only the config authority lifts either. Read config.oracleFrozen and config.settlesDisabled, and treat either as a stop, not a retry.
  • A market with trading hours only accepts opens and adds during its own session. The calendar is on the market account (market.sessions). Read it, or call availability() for the state and the next open, and do not assume that a market is always open. See markets.

Units

  • USDC in base units, 6 decimals. 250000 is $0.25.
  • A payout can be large relative to collateral. Format money as dollars in anything a human reads; a figure printed as 25007500 will be misread as twenty-five million.
  • Rates and limits are in basis points or micro-basis-points on chain. Read them from the account, never from a constant you copied.

Who signs

An open, an add and a close request carry no quote, so a person can approve each one in a wallet, and an autonomous client signs locally. The instructions that carry a quote are set_take_profit and any settle, stamp_book or prove_price you send yourself. A quote ages out in slots, so a human approval between the quote and the send cannot land: sign those locally. If you are trading a book that is not your own, one option is for the owner to authorise a session-key delegate your agent signs with; if you are trading your own, you are already a local signer. How you sign is your decision. Put any human approval on the authorisation, which carries no price and races nothing. If you choose the program’s session-key delegate, it is four moves — session keys has working code for each:
  1. Authorise. authorizeSession — one instruction the owner signs, setting the expiry, the spend window and the SPL token allowance together.
  2. Fund it. The fee payer must sign, so on this route it is not the human — the session key itself, or a separate sponsoring fee payer you add. Give whichever pays its own lamports and watch the balance.
  3. Sign every trade with it. authority is the session key, delegate is delegatePda(owner).address, and the book, the token account and the money all stay the owner’s.
  4. Revoke when the mandate ends. endSession closes the account and drops the SPL allowance in one instruction, in the same slot, and returns the rent.
Gate before you send: delegateGates covers the four refusals openGates does not model, and sessionAllowanceGates covers the SPL allowance — a total that draws down and never refills, which is what actually ends most long sessions, arriving from the token program as InsufficientFunds in none of this program’s error shapes.
A session key can open AND request a close, unless its grant carries the no-close restriction. The program authorises a request_close for the owner or for an unexpired session key bound to them, and the request saves a wallet prompt and races nothing. The settle that completes it is sent by anyone, and the other five close reasons are permissionless and need no signature from either.

Five things an autonomous client must never do

  1. Never route around a refusal. A guard that refuses you knows something you do not. Read the gate, fix the request.
  2. Never cache governed state. config.params, market parameters and the session calendar all move on chain. Re-read them, and show how old your copy is.
  3. Never put a credentialled RPC endpoint in a command line, a log, a commit or a screenshot. Reference it by variable name — Installation.
  4. Never publish a model output as a fact. Implied odds, projections and annualisations are yours to compute and yours to label. What the chain settled is a fact; what your model expects is not.
  5. Never sign a second order for the same intent after a timeout. Inside one blockhash life, send the same signed bytes again. A second signature is a second order, and both can land.

Where to go next