open carries no price. It takes the stake when it lands and becomes an order, and the
venue’s next price after a short delay fills it. Orders and closes has the rules, the
timing settings, the events and the refusals. This page covers what building an open needs: the
first-trade account, sizing, the instruction, sending it and the sequence number.
The transaction shape
An open is theopen instruction and the compute-budget pair. There is no quote instruction and no
price account, so a person can sign it in a wallet.
The first trade needs an account
A trader’s positions live in one account, the book.open does not create it:
initTraderAccount does, as its own instruction, at the capacity you ask for (1 to 12 slots). The
SDK sizes a new book at INITIAL_POSITION_SLOTS (12), and FRESH_TRADER_ACCOUNT_BYTES is the
matching account size. initTraderAccount and open fit in one transaction, so a first-time trader
can sign once and pay the account’s rent once, but you compose that transaction yourself. The
recipe is below.
payoutDestination is recorded on the book and every settlement pays that account. The owner
can move it later with setPayoutDestination, signed by the owner alone; see
reading positions. If the account is later closed or
frozen, settlement still completes: the payout is held on the book and claimed with claimPayout.Need more room later?
resizeTraderAccount grows or shrinks an existing book: up to 12 slots, and
down to whatever still holds the trader’s live positions (a shrink never discards an occupied
slot; it refuses instead). The owner and the book’s recorded rent payer both sign it: the
rent payer funds a grow and receives the refund from a shrink.Create-and-trade in one transaction
PutinitTraderAccount before the open, and declare the sum of the two compute limits. A
transaction that exceeds its declared limit is charged and reverted, so the prelude’s own measured
cost is added to the open’s.
What the trader transfers
At most thebudget you hand it. open is fee-inclusive: you pass a total, the venue takes its
entry fee from inside it and opens the largest position the remainder buys. The number that leaves
the wallet is at most the budget you sign. It undershoots the budget by at most one base
unit at the venue (floor rounding of the fee), and up to two when composed with a wrapper that
rounds its own fee, such as Minute: always under, never over. A ledger reconciler should expect
that gap of at most two base units.
Setting a user timer costs nothing extra.
What you do still compute is the position the budget will buy, so you can set min_collateral_out
(the floor below) and show the trader their size:
Sizing from a total the trader typed
The trader names what leaves their wallet, you hand that number to the venue asbudget, and the
venue sizes the position inside it. sizeForTotal returns the collateral that budget resolves to,
which is what you pass as min_collateral_out, and checks it against the governed collateral
bounds. collateralForTotal(total, entryFeeBps) is the same inversion without the bounds check.
refusal is a Gate: same fields, same error, so an interface that already
draws the open gate report draws this with no new code. It carries two extra values: bound, the
governed limit that was missed, and boundTotal, the total that buys exactly that bound and is
therefore the correction to offer. The stake is never clamped to the bound: a total that buys
more than the maximum collateral reports what it really buys and refuses, as the chain does with
BudgetExceedsMaxCollateral.
Build the instruction
Every account the program needs, addressed by name, with the market’s remaining accounts appended. Addresses are passed as hex:toHex converts a PublicKey, toBase58 goes back.
The rung is basis points of stake, not a whole multiple.
20_000 is 2.00x, the floor below
which the protocol will not price a take-profit, and the ceiling is the payout cap. Any rung
between the two is admitted. For four preset buttons that always end at the ceiling, use
presetRungsBps(rungCeilingBps(market, config.params)).Who signs
An open carries no quote, so a person can approve it in a wallet. After a timeout, never sign a second open for the same intent inside one blockhash life. Send the same signed bytes again. A session key saves the wallet prompt on every trade. Two arguments on this page are the whole of it:authorityis whoever signs: the trader’s own wallet, or their session key.delegateisnullfor a wallet-signed open, and the session’s account (delegatePda(owner).address) when the session signs. Passingnullwith a signer who is not the owner is refused by the program, so this cannot open against someone else’s book.
Simulate, then send
Simulate first: it costs nothing and returns a refusal as a name. Send only when the simulation is clean.dryRun: true is the posture to default to. It builds and signs exactly what would be sent,
simulates it and stops. It is safe against mainnet and spends nothing.What comes back
open returns the position’s sequence number as Solana return data: the per-transaction
buffer, not an account you have to read. Eight little-endian bytes, no extra transaction bytes, no
extra account, no extra signature. It also emits a PositionOpened event carrying the same number.
That number names an order. PositionOpened has no price field, and the position’s entryPrice
is 0 until a filler writes it: read the entry from EntryFilled, or watch the book until the phase
leaves PendingOpen. See orders and closes.
Read the sequence number from the landed transaction’s logs. landedOpenedSeq reads the
PositionOpened event for your book and market, and the return value that same open logged, and
refuses if any two witnesses disagree.
Pass the book owner, not the signer: a session key signs for the owner’s book, and an open made
through Minute is on Minute’s book. When no PositionOpened in the transaction names that owner on
that market, the answer is seq: null, and the logged return value is not read. Two opens for two
books or two markets in one transaction each read their own.
seq, with the owner and the slot, is the handle:
seq. Never hold the bare slot index: slots are reused, and settle addresses by seq,
so a stale handle finds nothing instead of closing somebody’s live position.
If the transaction cannot say (seq: null), read the trader account and match on the market and the
opened slot. That read is exact but it is a second round trip, and it is a snapshot: anything that
settled in between has already left the book. Never take the largest live sequence as the answer.
Next: reading positions.
If it is refused
Every refusal you can meet here is named, with a cause, a fix, and whether retrying can ever help. See error handling. The common ones on an open are a market that is paused or closed and a stake outside the governed bounds. All of them are states rather than faults, andopenGates predicts the ones it models before you spend a fee finding out. It does not
model the guardian’s oracle freeze (OracleFrozen, read config.oracleFrozen).
An order that lands can still be returned at the fill (OpenVoided), for instance when the pool
has no room to cover it. That is not a refusal, and the stake comes back whole. The price checks and
the cover rule decide at the fill, not at the open.