Skip to main content
A position’s terms are stamped onto it at the moment it opens, and settlement reads those stamped terms rather than whatever governance has done since. Governance cannot change a running trade’s terms. Two guardian switches can stop it closing; see settlement. Its size is a different question. The owner — and only the owner — may add collateral to a live position. That averages the entry and moves both price levels with it, so it is the one thing about a live position that is not fixed. See adding to a position. No governed value reaches a live position’s terms, with one exception: a position that stored a borrow grace of 0 reads the live config.params.borrowGraceS (see the borrow fee).

An open is an order

Your stake is taken when the open lands, and the venue’s next price sets your entry. The open carries no price. The program records it as an order with a target slot a short, governed delay after the slot in which it landed (d slots). The entry is the first price of the venue’s price feed at or after that target. A filler on the venue’s list writes it, and the order becomes a position. The barrier and the rung are distances from that entry.
  • The price on your screen when you tap is not your entry. Your entry is the next price after the delay.
  • An order that is not filled in time is returned. If the first price comes too late or does not meet the venue’s quality rule, if the market closes, or if no filler lands inside the fill window, anyone can return the order, and the venue’s keepers do. You get your whole stake back.
  • You cannot fill your own order. Only a filler on the venue’s list can write an entry.
  • A landed open is not yet a position. Read your entry when the order is filled, never from the screen at the tap.
The timing settings, the events that report each step and the SDK calls are in orders and closes.

The five choices

1

Asset and direction

Which market you are trading, and whether you are long or short. The market names the price feed that decides your outcome — see the price that decides.
2

Stake

Your collateral, between the venue’s minCollateral and maxCollateral. It, and any entry fee paid on it, is the entire amount at risk. The bounds are governed and live on chain; read them from the config account.
3

Rung

Your take-profit target, as a multiple of the stake. Any multiple at or above 2.00x is admitted, up to the protocol’s payout cap — the one ceiling, read as config.params.payoutCapMultiple. The presets are presetRungsBps(ceilingBps) at the live payout cap — not the only choices.
4

Leverage

How far the price has to move to reach either end — so, how fast the position resolves. 1000x on every market. Pass tier on open: 0 means the market’s own. A market’s tier is the ceiling and the venue’s config.minTier is the floor. Above the ceiling is refused (LeverageAboveMarketTier), below the floor is refused (LeverageBelowMinTier), and so is a value the program does not list. Both values are governed: read them from the chain.It changes nothing about what you can win or lose. There is no leverage term in the payout. What leverage changes is the DISTANCE to both levels.
5

Timer (optional)

You may set your own expiry. Leave it at zero and you have no clock at all, because the protocol’s tenor is indefinite (see the tenor). A timer you set is at least 26 seconds.Setting a timer is free. A timed open pre-pays nothing. See the fees.

The barrier

Every position has a barrier: a price, fixed at open, sitting a set distance on the losing side of your entry. If the market touches it, the position is over and you receive nothing. Three things about it are worth internalising:
  • It is a distance, not a dollar amount. The distance is set by the leverage you chose, which the market’s tier caps. More leverage means a nearer barrier, which is exactly what makes the position resolve fast. Two positions on the same asset can sit at different distances.
  • It is direction-relative. For a long it sits below entry; for a short, above.
  • It is fixed at your open. Its distance is frozen when your order lands, and its price is set when the order is filled. A governance change later does not move a barrier that already exists.
The exact distance for the terms you are about to open is returned by the SDK’s openPreview().barrierMicroBp, and the resulting price by levels(). Read it, show it to the person taking the risk, and do not hand-compute it.
At the barrier the trader receives nothing. Any interface that tells a user they keep some fraction of their stake on a knockout is wrong. Plan your product around the stake being gone.Nothing is meant literally, not “nothing worth rounding”. At the barrier the loss tax takes the whole of the position’s equity, and the crumb the flooring leaves behind — a few base units, a ten-thousandth of a cent — goes to the LP pool, not back to the trader. settlePreview returns 0 for a knockout and that zero is exact.

The rung

The rung is the other side of the same idea: a price, also fixed at open, sitting the distance away that corresponds to your chosen multiple. Touch it and the position has a gross gain of the multiple less one, times your stake: a 2.00x rung is a gain equal to the stake. What you receive is that gain less the share of it the terms withhold and less the exit fee, so a rung pays less than its multiple. Fees and limits has a worked example. Your rung can be any multiple of your stake at or above 2.00x — not a fixed list. The floor is RUNG_FLOOR_BPS, 2.00x. The presets are presetRungsBps(ceilingBps), which returns four values in bps of stake and ends at the live payout cap. A preset above the live cap is refused RungAbovePayoutCap: compute the presets from the live cap. The SDK’s RUNGS export — [2, 3, 5, 10] — is not the preset list. A client may offer any value at or above the floor. There is exactly one ceiling above that floor, and it is config.params.payoutCapMultiple. Read it from the config account; a market carries no take-profit ceiling of its own.
RungAboveMarketCap (6050) keeps its code in the error catalogue, but nothing in the program raises it: a client branching on 6050 is handling a refusal that cannot arrive. RungAbovePayoutCap is the live one.

Adding to a position

A trader may add collateral to a position that is already live. It is one order, not two: the stake grows, the entry becomes the average of the prices actually paid, and both levels move with it. Opening a second position instead would leave a trader holding two trades that knock out at two different prices, which is the outcome this instruction exists to prevent. An add is an order too. It takes the added stake when it lands (CollateralCommitted) and carries no price. The add is priced at the first price of the venue’s price feed at or after the same short delay, and then one of two things happens:
  • It is filled (CollateralAdded): the entry becomes the blend, and both levels move with it.
  • It is returned (AddReversed), in full: if no price or no filler comes in time, or if at the add’s price the position is already at its barrier or its target. A returned add keeps the time it added to your timer.
Until the add is filled or returned, and for a short, governed time after a filled add’s price (M_add slots), you cannot close the position, move its target or add again (AddPending). In that time a price from before the add that breached the old barrier still proves a knockout: the position is knocked out on its old stake, and the added stake comes back. A touch of the target while an add waits for its price does not count, and it is lost if the add is then filled.
Adding while you are AHEAD moves the barrier TOWARD the live price. This is the single fact an interface must not bury. Leverage is fixed by the POSITION’s tier — chosen at open, frozen into the slot — so an add buys size at market — it is not “adding margin” and there is no top-up here that de-levers you.Both levels are distances from the entry, and the entry always moves toward the price the add filled at. So for a long:A short is the mirror in price and the identical statement in distance. A trader who is ahead spends their profit cushion on size. Do not offer this control to a winning position with copy that suggests it protects one.
Adding neither realises nor creates a profit or a loss. A trader who is 0.40downandadds0.40 down and adds 1 is still $0.40 down at the add’s price: the entry is the collateral-weighted harmonic mean of the old entry and the fill price, which is the only blend with that property, and it is also the average cost basis every trading interface already shows.
What else it does, all of which a client has to handle:
  • The add is fee-inclusive, exactly as an open is. You hand added_budget — the total to add — the venue takes its entry fee from inside it, and the debit is at most that. min_added_collateral_out is the trader’s mandatory floor on the increment SIZE, the signature over what the add buys.
  • The stake bounds bind on two different quantities. The minimum binds on the increment, the maximum on the total. So a position already holding more than maxCollateral − minCollateral has no legal add at all — quote the admissible range and never render a control whose every value is refused.
  • The clock may only be lengthened. extend_timer_s is a delta in seconds and it only ever goes forward, and never past the tenor frozen on the position. A double-down is the only way to move a position’s own timer, and it cannot shorten it.
  • A position that has touched a level cannot be added to. An add whose price is at or through the barrier, or at or through the take-profit, is returned in full (AddReversed).
  • The hold clock restarts. A filled add re-prices the position, so the minimum hold before a close you ask for runs again from the add’s price.
The borrow fee below is the one clock an add does not reset: it is measured from the original open, because the pool capital the first stake froze has been frozen the whole time.

Sizing an add from a total

The trader can name what leaves their wallet instead of naming the increment. sizeAddForTotal is that inverse, and it is not sizeForTotal with a different argument.
Two things differ from the open path’s answer, and each is money:
  • setsTimer does not change the debit. Setting a timer is free, on an open or an extension.
  • The bounds are admissibleAdd’s interval, not config.params. The ceiling binds on the total, so what a position may still take is the governed ceiling less what it already holds.
size.debit is never above the total, so the number the trader typed is the added_budget you sign and the chain debits at most that. A fee change landing between the quote and the signature can only shrink the increment, and the min_added_collateral_out floor you set from size.collateral turns that into a BudgetBelowCollateralFloor revert rather than a silent shrink.
addPreview, admissibleAdd, addCollateral and addImpact answer “what would this add do” and “what increments are legal”, which covers the blended entry, the admissible range and the barrier’s new price. Quote with them; do not derive the blend by hand from the formula above, and do not offer the control without showing where the barrier moves to. addGates is the gate report for an add — see reading gates.

The tenor

The tenor is the protocol’s own clock for a position. It is config.params.tMaxSeconds, a governed parameter, and it is frozen onto your position at open. The tenor is the indefinite value, so a position has no protocol clock: it ends by its barrier, its target, your own close, your own timer if you set one, the close of its session on a market with trading hours, or the 24 hour refund under How a position ends. A position keeps the tenor it froze at open. The SDK tests for it with isIndefiniteTenor(); show “no limit”, not a date. A position that froze a finite tenor is closable by anyone when the tenor runs out. On gold, the tenor is shorter. It is config.params.tMaxSeconds or the time left until market.sessionBufferS before the session’s close, whichever is shorter, so every gold position’s tenor ends before its session closes. See markets. Frozen is the part that is a guarantee: settlement reads the tenor stamped on your position, so a governance change afterwards cannot change a position you already hold. Read the live value rather than typing it, and do not build an interface that promises a reader a duration from a constant.
Read tMaxSeconds and borrowRateBpsPerHour together; neither one alone tells you how long a position can live. An LP’s withdrawal wait does not follow the tenor — see providing liquidity.
You may also set a shorter timer of your own. The two clocks are independent, and the earlier one is the moment your position first becomes closable by a stranger. A double-down can push your own timer out, never in — see adding to a position. The SDK computes both from the position itself:

How a position ends

The position is over and the trader receives nothing. Plan for it: at high leverage the barrier sits close enough to entry that an ordinary move reaches it. A knockout settles at the barrier no matter how far past it the price ran, so it needs no exit price to price it.Anyone may prove it with any signed price that is newer than the entry and at most W_ko slots old, a governed setting. So a position can be knocked out by a recent price that has already moved back on your screen. After a close request or an expiry mark, only a price that is not later than the exit price can knock the position out.
The position pays the rung’s multiple of your stake, subject to the overall payout cap. It is proved the same way as a knockout, and the price that proves it must also clear the minimum hold. A touch while a double-down waits for its price does not count. After you move your target, only a price at least d slots later proves the new one.
Either your own timer or the protocol’s finite tenor (an indefinite tenor never expires). From the deadline, anyone may mark the expiry (mark_expiry). The position settles at the first price at or after d slots from the mark, by the same window rule as a close you ask for, whatever that price is: a position expiring in profit is paid, and one expiring in loss is settled where it stands.
You ask for the close with request_close, signed by you or by a session key you authorised. The request carries no price, so it cannot fail on a stale one, and it cannot be withdrawn. The exit price is the first price of the venue’s price feed at or after d slots from the request. Anyone may then send the settle that pays it, and the venue’s keepers do.The window rule. If nobody settles inside the exit window (W slots) from that target, the price becomes the first price of the next window, and so on, and a knockout price in between still counts. So the close price is not locked when you ask, and no interface should say it is. You cannot ask inside the minimum hold.
Anybody may close a position with no quote, but only while no fresh price exists, and only on one of two time grounds.A market with no posted price for 24 hours is dark (the governed dark_after_min, read from the governance table as GovTable.darkAfterMin). On a dark market anyone can close any position on it, and the position gets its stake back. Anyone can post a signed price with provePrice to reset the clock.A position with a finite tenor can also be closed once its frozen tenor plus a governed grace (config.lastResortGraceS) has elapsed. A position with the indefinite tenor has only the dark ground.A live position is paid its stake back with the exit fee waived; a position already stamped at a level is paid that level. The close is refused while the oracle is frozen, on an order that is not filled, and while an add waits for its price.A position that is not settled before its session’s close ends at par, on a market with trading hours: a quote from a later session never prices it.
The SDK names six close reasons, because expiry has two flavours: voluntary, knockout, takeProfit, userExpiry, tenorExpiry and lastResort.

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) slots, and a market can have a longer one, market.closeHoldSlots. The longer of the two applies. Both are governed settings: read them from the chain.
  • The hold starts at your entry price, and again at the price of each double-down.
  • A close request inside it is refused (MinimumHoldNotMet, MarketHoldNotMet). The position is not affected. Send the request again from the first slot the hold allows: closableFromSlotOf(position, config, market) gives that slot from three public accounts, so an app can show “can close in N seconds” with no guess.
  • A take-profit must also clear the hold, measured to the slot of the price that proves it. The venue’s hold also applies to the end of your own timer.
  • A knockout is never held back, and a market’s own hold never holds back a timer end.

Settlement is permissionless

Every close ends with a settle that anyone may send. Only one step needs the owner: the request for a voluntary close. A knockout and a take-profit are proved by the price, and the two expiries and lastResort by the clock. Every priced close carries a signed quote from the venue’s price feed; lastResort needs none. Two switches can stop closes: the guardian’s oracle freeze starves every priced close of a price and refuses lastResort, and its settle kill-switch refuses every close. Only the config authority lifts either. Read config.oracleFrozen and config.settlesDisabled. Nobody is paid for sending a close. The protocol pays its submitter nothing — no fee, no share of the settlement, no gas rebate. If you run a keeper, budget for it as a cost.

The payout cap

No target can be set above the payout cap. The cap is the governed payoutCapMultiple, the largest multiple the program lets a target take. A position with no target of its own has the cap as its target. Read it rather than typing it. A win is paid in full. Part of it may be paid later, in order. There is no guaranteed time. The amount queued ahead of you is shown. How, and what a client shows, is in how a win is paid.

The fees

Three, all on chain. Two are charged on an event; the third is charged on time. Setting a timer is free. The rates, with the date they were read, are on Fees and limits.
  • an entry fee (config.params.entryFeeBps, basis points of the stake), taken from inside your budget when you open, and from the increment when you add. It is 0, so an open costs the stake and nothing more. The venue is fee-inclusive: you hand a total, it takes the fee from within it, and the debit is at most the number you signed;
  • an exit fee, charged at every close except a knockout, and waived on a live position closed by lastResort. It is a share of the exit value, the money the position closes with before the fee. The rate depends on the price move at the close: one rate when the close is a gain beyond a small band around the entry price, a higher rate when it is a loss beyond the band, and a rate between the two inside the band. On a gain, the terms also withhold part of the gain: the share you keep is smaller for a small move and rises with the size of the move. Fees and limits has the values, dated, and a worked example. A position freezes its terms when it opens, so a later change to a market’s terms never reaches a position that is already open. The terms of a market are read from its fee-terms account (feeTermsTablePda in the SDK), and the share of each close is on its SettlementFeeTerms event;
  • a borrow fee, charged out of the payout for however long the position was held.
A knockout is excluded from the exit fee because there is nothing left to charge: the stake is already gone. The total that leaves your wallet is at most the budget you hand the venue — it takes its entry fee from inside it, and setting a timer is free, so nothing is added on top. The SDK sizes the position for you:

The borrow fee

It is rent on the pool capital your position freezes, charged out of the payout at settlement for the time held beyond a free grace period, at a flat hourly rate on the collateral you opened with. All of it goes to the pool. It is taken after the loss tax and after the exit fee, never out of your equity, so it does not move the price that ends your position. A position freezes its rate when it opens, and a stored grace of 0 reads the live config.params.borrowGraceS. Fees and limits has the rate in force; read the position’s own borrowRateBpsPerHour to price its settlement.
Every one of these parameters is governed: they can be changed on chain by the protocol’s authority. Do not cache them. Read them from the config account, and show the reader how old your copy is.

The price that decides your outcome

It is the venue’s price feed for the market you traded — not “the price of SOL”, because no such single number exists. Every venue reports a different one. Every price is a quote signed by the venue’s price feed, and the program accepts it only when it is signed by at least market.minSignatures keys that the program recognises.
  • Your entry is the first price at or after your order’s target slot. An order is filled only by a price that meets the venue’s quality rule for an entry. Otherwise it is returned.
  • Your exit is the first price at or after your close’s target slot, by the window rule, or the price that proves a level.
  • If no quote can be obtained at all, lastResort is the backstop.
Markets also close. BTC, ETH, SOL and XRP trade around the clock; gold trades only in its own sessions. A scheduled event — a rate decision, a print — can also close a market to new opens for a window, and a market can be paused or halted. See markets for the rules at each close. The SDK models this as a state, never a boolean. Branch on the state:

Your positions live in one account

A trader has a single account holding several position slots — twelve by default in the SDK, and a client may ask for anywhere from 1 to 12 when the account is created, or resize it later. That account is created by its own instruction, not by opening a position — but account creation and your first open fit in one transaction, so a first-time trader signs once and pays the account’s rent only on that first trade. The rent comes back to whoever paid it when the account is closed. That transaction is one an interface composes; the SDK’s own composer will not build it for you. Opening a position has the recipe. Slots are reused, so a slot index is not a position handle. The SDK gives you { trader, slot, seq } and settlement addresses positions by seq, so a stale handle finds nothing instead of closing a neighbour. See reading positions.