Skip to main content
An 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 the open instruction and the compute-budget pair. There is no quote instruction and no price account, so a person can sign it in a wallet.
Two hard walls, and both are refusals rather than warnings.1,232 bytes (PACKET_LIMIT_BYTES). One packet. Measure the signed transaction, because the signatures are part of the length: an estimate off the unsigned message understates it by 64 bytes per signature. An open fits with room to spare. A session key adds about 128 bytes; see session keys.The declared compute budget. setComputeUnitLimit bounds the whole transaction. Declare the measured cost of the open, computeUnitLimit('open', { quotePost: false }), and simulate before you send.

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.
The rent returns to whoever paid it when the book is closed, and the close destination is checked on chain, so a builder who sponsors accounts gets that capital back.
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

Put initTraderAccount 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.
If the two do not fit, send two transactions; do not refuse the trade. Treat only the packet-size refusal as the trigger for the two-transaction fallback. A failed simulation is not one: retrying it in two pieces sends the failing half anyway, and the trader pays rent for a book they are then refused the trade for.

What the trader transfers

At most the budget 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 as budget, 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.
Some totals cannot be hit exactly, and the SDK says so rather than rounding. When an entry fee is set it truncates, so the debit steps by one or two base units as the stake increments, and for some totals no stake has a debit equal to them. collateral is always the largest stake that fits and dust is the remainder, never negative.Show the dust. summary states all three figures at the precision they need: a two-decimal currency formatter renders a 0.099999chargeas‘0.099999 charge as `0.10`.
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)).
min_collateral_out protects against a number the CALLER chose. Set it to the collateral you predicted, sizeForTotal(budget, config.params).collateral, never a token value. A client that passes 1 satisfies the check and is protected by nothing: a fee rise between quote and execution would shrink the position silently instead of reverting. 0n is refused outright (MinCollateralOutRequired). There is no debit ceiling to compute: the debit is bounded by budget.

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:
  • authority is whoever signs: the trader’s own wallet, or their session key.
  • delegate is null for a wallet-signed open, and the session’s account (delegatePda(owner).address) when the session signs. Passing null with 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:
Hold the 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.
Send one open per transaction if you intend to read the sequence back. Two opens for the same book and market in one transaction emit two PositionOpened events, and landedOpenedSeq refuses rather than naming one.
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, and openGates 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.