> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rekt.money/llms.txt
> Use this file to discover all available pages before exploring further.

# Opening a position

> Build the order, size it, send it, and read its sequence number. An open carries no price: it takes the stake when it lands, and the venue's next price after a short delay fills it.

**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](/sdk/orders) 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.

```text theme={"dark"}
0  ComputeBudget  setComputeUnitLimit
1  ComputeBudget  setComputeUnitPrice
2  open                            yours
```

<Warning>
  **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](/sdk/sessions#the-byte-wall).

  **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.
</Warning>

## 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](#create-and-trade-in-one-transaction).

```ts theme={"dark"}
import { Connection, PublicKey } from '@solana/web3.js';
import { FRESH_TRADER_ACCOUNT_BYTES, rentExemptLamports, traderPda } from '@rekt/1000x';

export async function accountCost(
  connection: Connection,
  owner: PublicKey,
): Promise<{ willCreate: boolean; rentLamports: bigint }> {
  const info = await connection.getAccountInfo(traderPda(owner).address, 'confirmed');
  return info === null
    ? { willCreate: true, rentLamports: rentExemptLamports(FRESH_TRADER_ACCOUNT_BYTES) }
    : { willCreate: false, rentLamports: 0n };
}
```

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.

```ts theme={"dark"}
import { ix, toHex, traderPda, vaultPda } from '@rekt/1000x';
import { PublicKey } from '@solana/web3.js';

export function buildInit(args: {
  trader: PublicKey;
  payer: PublicKey;
  slots: number;
  vaultToken: PublicKey;
  payoutDestination: PublicKey;
}) {
  return ix.initTraderAccount(
    {
      trader: toHex(args.trader),
      payer: toHex(args.payer),
      positions: toHex(traderPda(args.trader).address),
      vault: toHex(vaultPda().address),
      vault_token: toHex(args.vaultToken),
      // Where every settlement pays this trader. It must be a USDC account the trader owns, on the
      // vault's mint, and not frozen.
      payout_destination: toHex(args.payoutDestination),
    },
    args.slots, // 1..=12; resizeTraderAccount grows or shrinks the book later
  );
}
```

<Note>
  **`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](/sdk/positions#where-settlements-pay). If the account is later closed or
  frozen, settlement still completes: the payout is held on the book and claimed with `claimPayout`.
</Note>

<Note>
  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.
</Note>

### 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.

```ts theme={"dark"}
import { ComputeBudgetProgram, type TransactionInstruction } from '@solana/web3.js';
import { computeUnitLimit, ourInstruction, type BuiltInstruction } from '@rekt/1000x';

export function createAndOpen(
  prelude: BuiltInstruction, // the `initTraderAccount`
  trade: BuiltInstruction, // the `open`
): TransactionInstruction[] {
  const units =
    computeUnitLimit(trade.name, { quotePost: false }) +
    computeUnitLimit(prelude.name, { quotePost: false });
  return [
    ComputeBudgetProgram.setComputeUnitLimit({ units }),
    ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1_000 }),
    ourInstruction(prelude),
    ourInstruction(trade),
  ];
}
```

<Warning>
  **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.
</Warning>

## 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:

```ts theme={"dark"}
import { sizeForTotal, parseUsdc, type ConfigView } from '@rekt/1000x';

export function sizeFor(config: ConfigView, budgetText: string) {
  const budget = parseUsdc(budgetText); // exact; never through a float
  const size = sizeForTotal(budget, config.params); // the collateral this budget resolves to
  // Sign `budget` as the instruction's `budget`, and `size.collateral` as `min_collateral_out`.
  return { budget, collateral: size.collateral };
}
```

## 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.

```ts theme={"dark"}
import { sizeForTotal, type ConfigView } from '@rekt/1000x';

export function sizeHalfDollar(config: ConfigView) {
  // Read the live entry fee. At an entry fee of 0 the stake equals the budget.
  const size = sizeForTotal(500_000n, config.params);
  // size.collateral  500000n   the position the budget buys; pass it as `min_collateral_out`
  // size.debit       500000n   what leaves the wallet (at most the budget you sign)
  // size.dust             0n   what the budget could not spend
  // size.summary     "$0.50 buys a $0.50 stake and costs exactly $0.50."
  // size.refusal        null   or a Gate-shaped refusal naming the bound that was missed
  return size;
}
```

<Warning>
  **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.099999 charge as `$0.10\`.
</Warning>

`refusal` is a [`Gate`](/sdk/reference): 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.

```ts theme={"dark"}
import { PublicKey } from '@solana/web3.js';
import {
  assetId,
  configPda,
  ix,
  marketPda,
  priceAccountsFor,
  toHex,
  traderPda,
  vaultPda,
  type BuiltInstruction,
  type ConfigView,
  type MarketView,
} from '@rekt/1000x';

export function buildOpen(args: {
  /** The decoded config, read for this request. */
  config: ConfigView;
  symbol: string;
  market: MarketView;
  trader: PublicKey;
  traderToken: PublicKey;
  vaultToken: PublicKey;
  protocolToken: PublicKey;
  /** The fee-inclusive TOTAL to spend. The venue takes its fee from inside it. */
  budget: bigint;
  direction: 'long' | 'short';
  rungBps: number;
  userTimerS: number;
  /** The floor on the position SIZE: `sizeForTotal(budget, config.params).collateral`. */
  minCollateralOut: bigint;
  /** A durable key you choose, or `0n`. Read by nothing on chain. */
  openerTag: bigint;
}): BuiltInstruction {
  return ix.open(
    {
      // Whoever SIGNS: the trader, or a session key they authorised. The owner is read from
      // `positions`, so there is no separate trader account.
      authority: toHex(args.trader),
      trader_token: toHex(args.traderToken),
      config: toHex(configPda().address),
      market: toHex(marketPda(assetId(args.symbol)).address),
      vault: toHex(vaultPda().address),
      vault_token: toHex(args.vaultToken),
      protocol_token: toHex(args.protocolToken),
      positions: toHex(traderPda(args.trader).address),
      // null for a wallet-signed open. Pass `delegatePda(owner).address` when a session key signs.
      delegate: null,
    },
    {
      // The fee-inclusive total. The debit is AT MOST this.
      budget: args.budget,
      direction: args.direction === 'long' ? 0 : 1,
      // Basis points of stake, not a whole multiple: 20_000 is 2.00x, the floor. A bare `2` asks
      // for 2 bps and is refused as `RungBelowFloor`.
      rung_bps: args.rungBps,
      user_timer_s: args.userTimerS,
      // The trader's MANDATORY floor on the position SIZE. A fee rise between quote and execution
      // then reverts instead of shrinking the position. `0n` is refused (`MinCollateralOutRequired`).
      min_collateral_out: args.minCollateralOut,
      // Optional attribution. `null` unless you have one.
      referrer: null,
      // The leverage to open at; `0` means the market's own tier. A market's tier is a CEILING: any
      // value in `TIERS` at or below it opens, a value above it is `LeverageAboveMarketTier`, and a
      // value not in `TIERS` is refused even when it is lower.
      tier: 0,
      // A durable key YOU choose, written onto the position and read by nothing on chain.
      // `(trader, seq)` does not survive a `closeTraderAccount`, so key your own records off this.
      opener_tag: args.openerTag,
    },
    // The remaining accounts of an open: the market's fee-terms account when it has terms, and
    // nothing else. An order carries no quote. Never `market.feeds`. An open without the fee-terms
    // account is accepted, and it is priced on the legal maximum of the terms, which sets a larger
    // reserve aside than the terms it would have frozen.
    priceAccountsFor(args.market, args.config, 'open'),
  );
}
```

<Note>
  **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))`.
</Note>

<Warning>
  **`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`.
</Warning>

## 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](/sdk/sessions) 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.

```ts theme={"dark"}
import {
  ComputeBudgetProgram,
  Connection,
  Keypair,
  TransactionMessage,
  VersionedTransaction,
} from '@solana/web3.js';
import {
  computeUnitLimit,
  errorForCode,
  ourInstruction,
  programErrorCode,
  type BuiltInstruction,
} from '@rekt/1000x';

export async function sendOpen(args: {
  connection: Connection;
  payer: Keypair;
  built: BuiltInstruction;
  /** Leave true until you have watched this path work. It sends nothing. */
  dryRun: boolean;
}): Promise<string | null> {
  const { blockhash, lastValidBlockHeight } = await args.connection.getLatestBlockhash('confirmed');
  const message = new TransactionMessage({
    payerKey: args.payer.publicKey,
    recentBlockhash: blockhash,
    instructions: [
      ComputeBudgetProgram.setComputeUnitLimit({ units: computeUnitLimit('open', { quotePost: false }) }),
      ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1_000 }),
      ourInstruction(args.built),
    ],
  }).compileToV0Message();
  const tx = new VersionedTransaction(message);
  tx.sign([args.payer]);

  const sim = await args.connection.simulateTransaction(tx, { commitment: 'confirmed' });
  if (sim.value.err !== null) {
    // A refusal is data: the name, the cause, the fix and whether retrying can ever help.
    const refusal = errorForCode(programErrorCode(sim.value.err) ?? -1);
    throw new Error(refusal ? `${refusal.name} (${refusal.retryable}): ${refusal.fix}` : JSON.stringify(sim.value.err));
  }
  if (args.dryRun) return null;

  const signature = await args.connection.sendTransaction(tx);
  const done = await args.connection.confirmTransaction({ signature, blockhash, lastValidBlockHeight }, 'confirmed');
  if (done.value.err !== null) throw new Error(`reverted: ${signature}`);
  return signature;
}
```

<Note>
  **`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.
</Note>

## 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](/sdk/orders#opening-an-order-then-a-fill).

**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.

```ts theme={"dark"}
import { landedOpenedSeq, toHex } from '@rekt/1000x';
import type { Connection, PublicKey } from '@solana/web3.js';

export async function mintedSeq(
  connection: Connection,
  signature: string,
  owner: PublicKey,
  market: PublicKey,
): Promise<bigint | null> {
  const landed = await connection.getTransaction(signature, {
    commitment: 'confirmed',
    maxSupportedTransactionVersion: 0,
  });
  const minted = landedOpenedSeq({
    logs: landed?.meta?.logMessages ?? null,
    returnData: null,
    owner: toHex(owner),
    market: toHex(market),
  });
  if (minted.seq === null) {
    // Nothing was guessed. `why` says what the transaction did not carry.
    console.warn(minted.why);
  }
  return minted.seq;
}
```

`seq`, with the owner and the slot, is the handle:

```text theme={"dark"}
PositionRef = { trader, slot, seq }
```

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.

<Warning>
  **Send one `open` per transaction if you intend to read the sequence back.** Two `open`s for the
  same book and market in one transaction emit two `PositionOpened` events, and `landedOpenedSeq`
  refuses rather than naming one.
</Warning>

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](/sdk/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](/sdk/errors). 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`](/sdk/gates) 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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.