A slot index is not a position
The mistake that costs a builder their second trade rather than their first. Positions live in numbered slots inside one trader account, and slots are reused. Index 3 today and index 3 tomorrow are different trades. So the SDK never hands you a bare index:seq is what settle is called with, and the program searches by it rather than indexing — so a
stale handle finds nothing instead of closing a neighbour. Persist seq. Treat slot as a
rendering detail.
Read a trader’s book
heldPositions returns live positions only, each already bound to its handle:
{ ref, position }. Free slots are not returned — an empty slot is not a thing a user has.
PositionSlotsFull is neither a validation error nor an outage. It means close something
first. A slot frees when a settle for its position lands. Offer resizeTraderAccount rather than
telling a trader to wait.What a position remembers
The terms are frozen at open, against governance. A position carries its own barrier, its own rung, its own exit fee, its own cap, its own tenor, its own borrow rate and its own borrow grace, and settlement reads those rather than live config. A governance change does not reach a position that already exists. The freeze is against governance, not against the owner. The owner may add collateral to a live position, and three fields move when they do:collateral, entryPrice and — if the add sets one
— userTimerS. Nothing writes a level, because the barrier and the take-profit are distances
from the entry: writing entryPrice is the whole of “recompute the barrier from the new average
entry”. So a cached levels() result goes stale on an add even though no level was written. See
adding to a position.
Orders and pending steps
A slot can hold an order as well as a position, and a position can be waiting for a step: an add that waits for its price, a short hold after a filled add, or an exit target from a close request or an expiry mark. Read the state withphase(position, nowSlot):
Read the pending steps through the accessors, never through the raw fields. The same last words
of a position hold a pending add, the memo of a filled add, or nothing, depending on a flag, and
several store a slot as an offset from
openedSlot. isUnfilled, pendingAdd, addTargetSlot,
addMemo, memoIsLive, exitTargetSlot, exitReason and tpFloorSlot decode them as the
program does. closableFromSlotOf(position, config, market) gives the first slot at which a close
request can land, or null while there is none.
Do not draw levels for an order: levels needs an entry. See orders and closes.
The two price levels
barrierMicroBp and takeProfitMicroBp are distances. levels turns them into the prices a chart
can draw. Each price is the program’s own threshold: the first price at which the barrier counts as
touched, or the take-profit as reached — never one base unit short of it. takeProfitPrice is null
when the position has no take-profit, and when the level would fall at a price of zero, which no
oracle reports.
The two clocks
What every close would pay, right now
settlePreview prices the five priced close reasons at one price and one instant, using the
same settlement the chain runs, and says which are legal now and why the others are not. It does not
model lastResort, the no-price close; see settling a position.
signer and owner are separate arguments because voluntary is the one reason that needs the
owner’s authority behind it: their own signature, or a session key they authorised. Every other
reason is proved by the price or by the clock and is therefore permissionless. Conflating the two is
how an interface offers a button that cannot work.
Where settlements pay
Every settlement of a position pays the book’s recordedpayoutDestination. It is set at
initTraderAccount and the owner may move it with setPayoutDestination, signed by the owner
alone: no session key and no other authority can. The new account must be a USDC account the owner
owns, on the vault’s mint, and not frozen. Moving it does not touch a payout already held on the
book, and does not restart that payout’s expiry clock.
owner_token does not pay it: the payout is held on the
book instead. Read the destination off the book (decodeTraderAccount(bytes).payoutDestination)
whenever you build a settle.
A payout that has not been collected
When a settlement cannot deliver, because the destination was closed, frozen or not the recorded one, the payout is held on the trader’s book aspendingPayout. Show it: it is money the user
already has and may not know about.
- To the recorded destination, anyone may claim. No owner signature is needed, and the money can go nowhere the settlement would not have sent it.
- To any other account, the owner must be the
payerand sign. Otherwise the claim is refused (ClaimDestinationRequiresOwner, 6155). The destination must still be a USDC account the owner owns.
config.params.unclaimedPayoutExpiryS of when it was held
(pendingSince) may be swept to the LP pool by anyone, with sweepUnclaimedPayout.
Next: settling a position.