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

# Integrating Minute

> The typed dependency, the governed dials, the fee model, who pays, who settles, the per-market gate, and the transaction a round needs.

**Minute to Win It** is a separate on-chain program that wraps this venue. A player commits a stake,
a sixty-second clock starts, and the round ends when the clock runs out, the barrier is hit or the
take-profit is reached.
Minute opens a real position here through CPI, handing the venue a fee-inclusive `budget`.

Program: `MinXAXaPZezFMc8mm1uZnK8NcYt7Fhbr7Eifdnw7cfH` · Config PDA:
`3WQ7QsWJJhqa1REGjUJFzCUNPSonbaXKcZoJbDwQnYq4`

## Decode with the SDK. Do not transcribe the layouts.

The SDK decodes Minute's accounts and events, so no layout needs to be copied into your repository:

```ts theme={"dark"}
import {
  decodeMinuteConfig,
  decodePlayerAccount,
  decodeMinuteEvent,
  minuteConfigPda,
} from '@rekt-trading/1000x';
```

A decoder turns a layout change into a compile error or a refused read, where a transcribed offset
drifts silently. If you decode bytes off the wire instead (an indexer has no choice: the bytes *are*
the interface), use `decodeMinuteEvent` and the event decoders next to it rather than deriving your
own.

> [!WARNING]
>
> **On `RoundOpened`, `gross_stake − stake` is Minute's fee plus the venue's.** `stake` is the
> collateral the *venue* resolved. Minute's own fee is `floor(gross_stake × entry_fee_bps / 10_000)`,
> from the event alone; subtract it from the difference to get the venue's cut.
> `RoundExtended.added_collateral` is the **measured** position delta, not the amount the player
> typed.

## Read the governed dials from the chain. Every time.

`min_stake`, `max_stake`, `entry_fee_bps`, `exit_fee_bps` and `initial_timer_s` are **governed**.
They move by a transaction, with no release on your side and no warning.

> [!WARNING]
>
> An integrator who copies today's rate into their own config ships a display that is wrong the day
> it moves — **and every test passes**, because their number and their display agree with each other
> perfectly. Self-consistency is the failure mode, not the defence.

Read them with `decodeMinuteConfig(bytes)` from the account at `minuteConfigPda()`, and prefer
failing closed when the config is unreadable over falling back to a remembered value.

## The fee model: fee-inclusive, top to bottom

Both layers take their fee from INSIDE the number handed to them, and neither adds anything on top.
**The number the player types is the number that leaves the wallet.**

The two money instructions:

```rust theme={"dark"}
open_round(game_kind: u8, direction: u8, total_in: u64, min_collateral_out: u64)
double_down(seq: u64, expected_add_count: u16, added_total_in: u64, min_added_collateral_out: u64)
```

`total_in` / `added_total_in` is the fee-inclusive total — **the exact number that leaves the
wallet**, and what `min_stake` / `max_stake` bound. `min_collateral_out` /
`min_added_collateral_out` is a **floor on the resolved position**: forwarded to the venue and
re-checked by Minute against what the venue actually minted, so a venue fee that moves between your
quote and execution **reverts** instead of quietly buying less. **`0` is refused by name**
(`MinCollateralOutRequired`: 6050 in Minute's table, 6168 in the venue's) — it is the absence of a
floor, not a floor. The venue's own 6050 (`RungAboveMarketCap`) is reserved and raised by nothing.

> [!WARNING]
>
> **There is no `max_total_debit` argument.** `min_collateral_out` pins what the player GETS, not
> what they pay.

An add is always exactly the round's opening total: `added_total_in == round.opening_gross`, which
is also the open's `RoundOpened.gross_stake`. One field, read off the round or off the event — not a
range, and not `round.stake` or `round.opening_stake`, both of which hold the venue-resolved
collateral and are refused by name (`AddMustEqualOpeningStake`, 6042 — Minute's code; the venue's 6042 is reserved and never raised).

**1. Minute's fee is ABSORBED**

`entry_fee_bps` comes **out of** the stake, not on top. A player who commits \$1.00 has exactly
\$1.00 leave their wallet toward Minute, of which the fee is a part. Minute never adds to what
the player typed. `min_stake` and `max_stake` bound this **gross** figure, so a typed \$0.25 is
a legal 250,000 stake — **do not gross down for Minute's fee.**

**2. The venue is ALSO fee-inclusive: Minute is a pass-through**

The venue takes its own fee from inside the budget it is handed, exactly as Minute does. Minute
takes its cut off the top and hands the remainder DOWN to the venue as a `budget`; the venue
takes its own `entry_fee_bps` from INSIDE that budget and opens the largest position the
remainder buys. **Nothing is added on top at either layer**, and a round's timer costs nothing
extra. So the wallet debit **is** the number the player typed: one SPL transfer, of exactly
`total_in`, and both fees are paid out of it.

> [!NOTE]
>
> The wallet debit is a single transfer of the typed amount. The fees round the POSITION, which is
> why `min_collateral_out` exists.

> [!NOTE]
>
> The venue's entry fee is 0, so the venue takes nothing from the budget it is handed.
>
> Minute's own entry fee and exit fee are 0 too. A round's close pays the venue's exit fee on the terms of a
> Knockout position on that market: see [Fees and limits](https://docs.rekt.money/limits.md). Read both programs' fees from
> their config accounts and the market's fee-terms account.

Here it is as arithmetic. The number you type is the number that leaves the wallet. Only the last
line depends on the two entry fees:

```
typed total 250,000 (Minute's min_stake)
  -> Minute absorbs its entry fee;  the remainder goes to the venue as a `budget`
  -> the venue absorbs its own entry fee from INSIDE that budget and opens what it buys
  -> TOTAL WALLET DEBIT  =  250,000   exactly.  Not "at most".
  -> RESOLVED POSITION   =  250,000   <- equal to the typed total at Minute 0 bps + venue 0 bps; smaller whenever either fee is above 0
```

**A typed \$0.25 total debits \$0.25.** Any fee comes out of it, not on top of it. When a fee is
above zero, the POSITION is smaller than the typed number, since both fees are absorbed from inside it.
The binding floor is therefore the venue's `min_collateral` on the RESOLVED position, not
a grossed-up stake: read it from the chain, and set the CPI's `min_collateral_out` to the collateral
you predicted for the budget — never a token value like `1`, which satisfies the check and protects
nothing. If you need a specific minimum position, that is a governance question about `min_stake` and
`min_collateral`, re-derived whenever the *venue* moves its own fee, which Minute does not control.

> [!WARNING]
>
> **Do not gross the player's input down.** Shrinking the typed number is a silent under-spend: it
> opens a smaller round than the player was shown. Send the typed number to `total_in` verbatim, and
> read both programs' fees from their config accounts when you predict the position.

### The ceiling is not `max_stake`

`max_stake` bounds the **opening** stake. A double-down adds to a live round, so total exposure runs
past it.

`max_adds = 0` means **uncapped**, not "no adds". The binding constraint is the venue's maximum
collateral per position, `config.params.maxCollateral` on the venue's config, and the refusal is
**Minute's** `AddExceedsVenueCapacity` (6046). So the ceiling is the venue's number and the error is
Minute's; do not look for this code in the venue's table.

## Markets: the venue's per-market gate

Minute keeps no market list of its own: `Round.market` may point at any market **the venue has
listed**, and an unlisted asset has no `Market` PDA to pass. The venue then applies one gate of its
own: a Minute player's book may **open or add** only on a market whose `minuteEnabled` flag is true.
Anywhere else the venue refuses with `MinuteDisabled` (6201). Settles are never gated, so a live round
is never trapped by it. Offer a round only on a market whose `minuteEnabled` is true and whose `paused` is false (both on `decodeMarket(bytes)`). Only the venue's config authority changes either. Minute is enabled on BTC, ETH, SOL and XRP.

### Markets with trading hours

No live market has trading hours. Gold (XAU/USD), which is not available for now, trades in sessions
([markets](https://docs.rekt.money/markets.md#when-gold-trades)). The calendar is on the market
(`sessions`, `sessionCount`, `sessionBufferS` on `decodeMarket(bytes)`); read it there, not from
this page. On such a market the venue's rules apply to a round exactly as to any position:

* **An open is refused outside a session** (`MarketClosed`, 6094) **and near its close**: once less
  than `sessionBufferS` plus 300 seconds of the session remains (`SessionEndsBeforeTenor`, 6095).
  The venue shortens the round's position so that it ends `sessionBufferS` before the close.
* **A double-down is refused outside a session** (`MarketClosed`); inside one the venue admits it,
  because the position already ends before the close. Minute refuses one that would carry the clock
  past the shortened position (`ExtensionExceedsTenor`, 6023, Minute's code).
* **A price signed at or after the close cannot settle the round** (`QuoteOutsideSession`, 6295, the
  venue's code). A round that is still open at the close is closed by the venue's no-price close,
  `lastResort`, which returns the stake at par once the position's tenor plus the venue's last-resort
  grace has passed: about a day later. Minute's keeper sends it, and `reconcile_round` then
  records the round. A round is not left open at the close in the ordinary course: its clock ends
  well before it.

So hide the open button from `sessionBufferS` plus 300 seconds before each close until the session
reopens, and show the player when the market reopens.

## Who pays, and who may sponsor

Three roles, and they are separate on purpose:

| Slot | What it does | Constrained? |
| - | - | - |
| `payer` | pays lamports | **`init_player` only:** must equal the player |
| `player_wallet` | the player's identity | **not a signer** on `open_round` |
| `funder` | authorises the USDC | SPL decides — owner **or an approved delegate** |

**`open_round` and `double_down` are sponsorable.** Your backend can be `payer`, and a delegate the
player approved can be `funder`, so the player never signs the open and never needs SOL.

**`init_player` is the exception:** it requires `payer == player_wallet`, and refuses anything else
with `PayerMustBeThePlayer` (6038).

> [!NOTE]
>
> **The shape that works:** put a lamport transfer to the player and `init_player` in **one
> transaction**, with the player as `payer` and signer. You carry the cost and the player never sees
> a fund-your-wallet step. It happens once per player.

Rent is a **deposit, not a fee**: it returns to the rent payer when the player's accounts are closed
with `close_player`.

## Settlement is permissionless, and a redundant settle is not a no-op

Anyone may settle a round's venue position once its clock has expired. If you run your own closer
alongside Minute's keeper, that is fine and encouraged — but know the shape of the race:

**A second settle of an already-settled position FAILS with the venue's `PositionNotFound` (6057).**
It is an error, not a silent success. There is no double-payout and there cannot be, because the
state transition gates the money rather than following it — the loser of the race gets a reverted
transaction, not a second payout.

> [!WARNING]
>
> Treat `PositionNotFound` on a settle as **success by someone else**, never as an incident. It is the
> ordinary case: the venue's own keepers close Minute's positions without invoking Minute at all, and
> a `reconcile_round` then records the round. A closer that pages on this pages continuously.

## Error codes are meaningless without their program

Anchor numbers **every** program's errors from 6000 — so even 6000 itself collides (Minute's `NotAuthority` vs the venue's `RateOutOfRange`). So:

* `6027` is Minute's `RoundNotLive` **and** the venue's `ReserveUnderflow`
* `6007` is Minute's `WrongMint` **and** the venue's `NoFreshPrice`

> [!WARNING]
>
> A bare-number match reads a real vault accounting failure as "already settled" and writes a
> terminal record while the position is still live. **Key your classification by program**: use the
> Anchor error *name* when present, else prove which program refused from the logs — Minute's
> failure line present and the venue's absent means the refusal happened before the CPI.

Take codes from the IDL, never from arithmetic on the enum. A code quoted in prose without its
program is a number waiting to be matched on wrongly.

## Opening, and the venue's price

`open_round` and `double_down` reach the venue's `open` and `add_collateral` by CPI. Those read no
price, so neither Minute instruction needs a quote instruction in front of it. The round's venue
position is an **order** until a filler writes its entry at the venue's next price after a short
delay, and a double-down is priced the same way or returned. A double-down the pool has no room to
cover is returned in full, and the round's total stays within the venue's maximum position. Read the
entry from the venue's events, not from Minute's: see [events of an order](#events-of-an-order).

Minute forwards its remaining accounts to the venue. The SDK's `minuteIx.openRound` and
`minuteIx.doubleDown` take them as their last argument and refuse an empty list:

```ts theme={"dark"}
import { pricingRemainingAccounts, withFeeTermsTable } from '@rekt-trading/1000x';

// open_round: the sysvar, then the market's fee-terms account when the market has terms. The venue
// freezes the terms of the round from it. Without the account the round freezes the legal maximum
// as its exit-fee bound and no rung check runs, so always pass it.
const openRemaining = withFeeTermsTable(pricingRemainingAccounts(market), market, config, 'open');

// double_down: the venue's add reads no price and no terms. The sysvar satisfies the builder.
const addRemaining = pricingRemainingAccounts(market);
```

Never pass `market.feeds`: `feeds[0]` is a feed id, not an account.

**Closing a round is the venue's `settle` on the player's book, followed by `reconcile_round`.**
`settle_round` is refused while the venue runs its liquidity pool (`CapitalRequired`, 6265, the
venue's code), because the pool settles every position directly so that part of a win can be queued.
A knockout, take-profit or expiry close carries the quote's ed25519 instruction immediately before the
venue's `settle`, and `priceAccountsFor(market, config, 'settle')` gives the whole list of remaining
accounts. An expiry takes two transactions: anyone marks the venue position's expiry (`mark_expiry`)
from the bell, and the settle follows from the mark's `exitTarget` on, with the window quote. See
[settling a position](https://docs.rekt.money/sdk/settling.md#request-or-mark-then-settle). Anyone may send the settle, and the
venue's keepers do. `reconcile_round` then records the round on Minute's side. If no price can be
obtained at all, the venue's no-price close, `lastResort`, applies to the round's venue position
directly (see [settling a position](https://docs.rekt.money/sdk/settling.md#the-no-price-close)), and `reconcile_round` then
records the round.

**Do not gate a close on a pause.** `Market.paused` refuses opens and adds (and a take-profit stamp
or retarget), never `settle`. Pause your OPEN button if you must; never your close.

## Events of an order

Minute's events are `RoundOpened`, `RoundExtended`, `RoundSettled`, `RoundReconciled`, `Swept`,
`PlayerInitialized`, `PlayerResized`, `PlayerClosed`, `ConfigUpdated`, `PauseChanged` and
`LookupTableSet`. It has no event named `DoubledDown`.

Minute's two round events are emitted when the order or the add lands, before the venue has priced
it:

* **`RoundOpened.entry_price` is 0** on every open.
* **`RoundExtended` is emitted at the add's commit**, also for a double-down that is later returned.
  Its `entry_price` equals its `entry_price_before`.
* **`opened_slot` in both events is the slot of the commit**, not the slot of the entry price. The
  holds count from the venue's `EntryFilled.quote_slot`, and after a filled double-down from the
  venue position's `opened_slot` on the book.

So do not read an entry, a blend or "the add is done" from either event. The round's entry is the
venue's `EntryFilled.entry_price`. The entry after a double-down is the venue's
`CollateralAdded.entry_price`, with `entry_price_before`. A returned double-down is the venue's
`AddReversed`. Join them to the round on `opener_tag`.

## The transaction is tight

A wrapped open names the Minute accounts **and** the venue accounts it reaches, against Solana's
1,232-byte packet limit. Compile a v0 message against two lookup tables. The first is the table
Minute's config names (`decodeMinuteConfig(bytes).lookupTable`); read it from the config rather than
from your own settings. The second is the venue table for the market, from `chooseMarketTable`
(see [session keys](https://docs.rekt.money/sdk/sessions.md#the-byte-wall)). Both are frozen, so cache their contents by address. Measure the signed packet before you send it, and refuse one that does not
fit rather than letting the RPC reject it.


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