Skip to main content
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 or the barrier is hit. Minute opens a real position here through CPI, handing the venue a fee-inclusive budget; the venue does not know it is being wrapped. 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:
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.
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.
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:
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 (Minute’s MinCollateralOutRequired; Minute refuses the zero before the CPI) — it is the absence of a floor, not a floor.
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 RedemptionExpired).
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.
The wallet debit has no rounding gap. It is a single transfer of the typed amount, so there is no floor division on the wallet side to lose a unit to. The fees still round, and they round the POSITION, which is why min_collateral_out exists.
The venue’s entry fee is 0, so the venue takes nothing from the budget it is handed. A round’s close pays the venue’s exit fee on the same terms as a Knockout position on that market: see Fees and limits. 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:
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.
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): Minute checks the venue’s ceiling before attempting the add. 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. A round on a market with trading hours is refused outside them, with the venue’s own codes. See markets.

Who pays, and who may sponsor

Three roles, and they are separate on purpose: 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).
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.
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
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. 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:
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. 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), 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: the program pauses new positions, never the exit. A consumer that refuses a close because the market is paused re-creates the very trap the program designs out: a trader who cannot get out. 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). 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.