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, 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:
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 (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.
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.
The wallet debit is a single transfer of the typed amount. The fees 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.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. 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). 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). 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: 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. 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.