Skip to main content
An RFQ (Request for Quote) goes through several stages from creation to on-chain settlement. In the current app flow, the frontend first opens a live quote request while the user builds the slip; only when the user submits does Totalis atomically create a real RFQ with the selected quote already accepted.

Status Flow

open → quoted → accepted → confirmed → executed → settled
  ↓       ↓        │
  │       │        ├─ MM misses deadline, RFQ still within expires_at → open  (quote → withdrawn)
  │       │        └─ MM misses deadline, RFQ past expires_at        → expired (quote → expired)
expired  cancelled
A few status nuances worth knowing:
  • rejected (quotes) never comes from a missed confirmation. It’s set on sibling pending quotes when the user accepts a different one on the same RFQ, and on the target quote of POST /rfqs/{id}/quotes/{quoteId}/reject.
  • withdrawn (quotes) covers MM-initiated cancellation of a pending quote, and is also what the system writes back onto the previously-accepted quote when the MM misses confirmation and the RFQ reopens.
  • expired (quotes) is used for pending quotes whose own valid_until lapses, and for the accepted quote when the RFQ itself expires.

Step-by-Step Flow

1

User Builds a Live Quote Request

The user adds 2–5 legs to the parlay builder. Each leg references a market on its source venue (kalshi or polymarket) and a side (yes/no). The frontend creates or updates a short-lived quote request instead of immediately creating a real RFQ.market_ticker is venue-polymorphic — see Create Quote Request for the full field reference:
  • Kalshi: the Kalshi market ticker (e.g. KXBTC-25FEB07-T100000).
  • Polymarket: the condition_id, the 0x-prefixed hex string from Polymarket’s CTF (e.g. 0x4d2…). Not the gamma id, question id, or market slug. Both yes and no sides of a Polymarket binary share the same condition_id and are distinguished by side; CLOB token ids are not part of the RFQ surface.
POST /v1/quote-requests
{
  "legs": [
    { "market_ticker": "KXBTC-25FEB07-T100000", "side": "yes", "venue": "kalshi" },
    { "market_ticker": "0x4d2…",                "side": "no",  "venue": "polymarket" }
  ],
  "bet_amount": 25
}
The system validates that each market exists in the cache, enforces leg-exclusion rules (see Get Exclusion Groups), versions the request, and broadcasts it to all connected market makers via the Quote Service SSE stream.Quote request status: active
2

Market Makers Submit Draft Quotes

Market makers subscribe to the Quote Service SSE stream (GET /v1/mm/quote-requests/stream) and submit draft quotes with their payout odds.
PUT /v1/mm/quote-requests/<quote-request-id>/quote
{
  "request_version": 3,
  "request_hash": "sha256...",
  "payout_odds": 4.0,
  "expires_in_ms": 15000,
  "leg_prices": [
    { "leg_id": "8ccf2c5d-3a61-4707-93f2-b6f0a0f0c0c6", "leg_odds": 0.42 },
    { "leg_id": "6e854379-57bb-487a-aec3-197fdd861fc8", "leg_odds": 0.58 }
  ]
}
The payout is calculated as:
  • user_cost = bet_amount (e.g. $25)
  • total_payout = bet_amount × payout_odds (e.g. $100)
  • mm_cost (MM’s max risk) = total_payout − user_cost (e.g. $75)
Quote request status: active
3

User Commits the Best Quote

The frontend shows the best active draft quote. When the user submits, the server selects the best still-valid quote for the displayed version and creates the real RFQ plus accepted quote atomically.
POST /v1/quote-requests/<quote-request-id>/commit
{
  "expected_version": 3,
  "displayed_quote_id": "quote-uuid",
  "displayed_quote_book_seq": 5,
  "min_payout_odds_seen": 4.0
}
The response returns the real rfq_id, quote_id, selected mm_id, payout odds, and confirmation_deadline — the timestamp by which the market maker must confirm.Status: accepted
4

Market Maker Confirms

The market maker confirms before the deadline. Confirmation enqueues vault position creation.
POST /mm/quotes/<quote-id>/confirm
If the MM misses the deadline, the RFQ expiry sweep handles it based on the RFQ’s own expires_at:
  • RFQ still within expires_at — RFQ reverts to open (with accepted_quote_id and market_maker_id cleared so other MMs can quote it again) and the accepted quote becomes withdrawn.
  • RFQ past expires_at — RFQ becomes expired and the accepted quote becomes expired.
Status (on successful confirm): confirmed
5

On-Chain Vault Position Creation

The vault job service executes a single atomic Solana transaction via the parlay_vaults program. The fee payer covers gas so users and MMs only need USDC in their vaults. User/MM signatures are obtained via Privy session signers.create_position — both the user’s stake and the MM’s collateral are locked atomically in their persistent vaults. The MM’s collateral is calculated via ILP (Integer Linear Programming) to reflect incremental worst-case exposure across the maker’s portfolio, not the full risk amount.The transaction signature and position PDA are stored in the database and displayed in the UI with links to Solana Explorer.

Vault Architecture

Learn more about the vault architecture, Privy session-signer integration, and gas-sponsored transactions.
Status: executed
6

Settlement

A background service polls venue resolution feeds and settles positions once all leg markets have resolved. Settlement transfers funds between vaults and deducts a protocol fee on profit:
  • All legs win — MM’s risk amount (minus fee) is transferred from the MM vault to the user vault (settled_win).
  • Any leg loses — User’s stake (minus fee) is transferred from the user vault to the MM vault (settled_loss).
Status: settled

Background Services

Two background services manage the vault position lifecycle:
  • vault-job-service — processes pending positions: resolves Privy wallets, calculates ILP exposure, executes create_position.
  • vault-settlement-service — polls market results, settles or cancels active positions based on outcomes.

Validation Rules

ParameterConstraint
Legs per parlay2–5
Bet amount1–100 USDC
Payout odds1.0001× – 1000×
Quote request expiryServer-set; visible as expires_at on the quote request object
RFQ expirationFixed at 300 s for RFQs created at commit
Quote validity (expires_in_ms on a draft quote)5 s – 60 s (default 15 s)
MM confirmation windowServer-controlled; returned on the commit response as confirmation_deadline. Missing it reverts the RFQ to open (quote → withdrawn) or, past expires_at, expires both.