openapi: 3.1.0
info:
  title: DailyDraft API
  version: 0.5.0-devnet
  summary: Build pack-opening duels on Solana
  description: |
    Preview contract for pack discovery, duel matchmaking, wallet-signed Solana
    transactions, status tracking, and social sharing. The API never accepts
    wallet private keys and does not replace on-chain verification. Every duel
    created by this contract is explicitly Solana devnet-only. Mock provider
    responses never imply funding, settlement, or mainnet readiness.
  license:
    name: MIT
    identifier: MIT
  contact:
    name: DailyDraft
    url: https://github.com/dailydraft
servers:
  - url: http://localhost:3003/v1
    description: Local development
  - url: https://api.CHANGEME/v1
    description: Deployment placeholder for the hosted devnet API pending DNS cutover (not mainnet)
tags:
  - name: Authentication
    description: Replay-safe Solana devnet wallet ownership sessions.
  - name: Packs
    description: Duel-eligible pack definitions and current inventory metadata.
  - name: Gacha
    description: Inspectable Sports Pack Gacha capabilities, inventory, odds, deposit intents, and rips.
  - name: Games
    description: Capability-derived player catalog for runtime games and playable no-value demos.
  - name: Crash
    description: Authenticated, resumable fixture-preview Crash decisions; never production playable.
  - name: RGS
    description: Versioned game-mode configs and independently verifiable round proofs.
  - name: Valuation
    description: Immutable winner-comparison policies committed before either player funds.
  - name: Duels
    description: Public matchmaking intents and canonical duel state.
  - name: Matchmaking
    description: Exact-segment open matchmaking with durable reconnect and explicit fallback choices.
  - name: Transactions
    description: Unsigned Solana transactions for wallet inspection and approval.
  - name: Operations
    description: Authenticated devnet reconciliation workers and operator controls.
  - name: Social
    description: Canonical share pages and generated social-card images.
  - name: Analytics
    description: Privacy-safe anonymous funnel events and integration-only operational health.
  - name: Fantasy
    description: Gated fantasy tournament capabilities pending oracle, snapshot, and payout review.
security: []
paths:
  /rgs/modes:
    get:
      operationId: listRgsModes
      tags: [RGS]
      summary: List the versioned RGS math configurations and readiness gates
      description: |
        Declares Gacha, Duels, Flip, and Crash through one versioned configuration
        surface. Fixture-only engines remain non-value-bearing, and every real-value
        mode stays behind the existing human-approval policy gate.
      x-dailydraft-availability: devnet-preview
      responses:
        '200':
          description: Versioned RGS mode configurations
          headers:
            Cache-Control:
              schema:
                type: string
                const: no-store
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RgsModeList'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          $ref: '#/components/responses/Unavailable'
  /rgs/rounds/{mode}/{roundId}/proof:
    get:
      operationId: getRgsRoundProof
      tags: [RGS]
      summary: Get an independently verifiable proof for a revealed RGS round
      description: |
        Returns the exact versioned commitment, reveal inputs, evidence hashes,
        and canonical result payload. Gacha uses seeded SHA-256 commit/reveal;
        Duels binds the pre-provider request pair to signed provider evidence.
        Flip and Crash are fixture-only until their gated round services ship.
      x-dailydraft-availability: devnet-preview
      parameters:
        - name: mode
          in: path
          required: true
          schema:
            $ref: '#/components/schemas/RgsMode'
        - name: roundId
          in: path
          required: true
          schema:
            type: string
            pattern: '^[A-Za-z0-9][A-Za-z0-9._:-]{0,239}$'
      responses:
        '200':
          description: Versioned round proof
          headers:
            Cache-Control:
              schema:
                type: string
                const: private, no-store
            X-Robots-Tag:
              schema:
                type: string
                const: noindex, nofollow, noarchive
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RgsProof'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          $ref: '#/components/responses/Unavailable'
  /health:
    get:
      operationId: getHealth
      summary: Check API process health
      x-dailydraft-availability: devnet
      responses:
        '200':
          description: API process is accepting requests
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Health'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          $ref: '#/components/responses/Unavailable'
  /health/capabilities:
    get:
      operationId: getProductCapabilities
      summary: Resolve the currently playable duel modes and pack tiers
      description: |
        This fail-closed response is the product UI source of truth. Disabled
        modes and pack tiers remain visible only as explicit unavailable or
        coming-soon choices and must never be submitted as playable.
      x-dailydraft-availability: devnet
      responses:
        '200':
          description: Current public product capabilities
          headers:
            Cache-Control:
              schema:
                type: string
                const: no-store
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProductCapabilities'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          $ref: '#/components/responses/Unavailable'
  /games/catalog:
    get:
      operationId: getGameCatalog
      tags: [Games]
      summary: Resolve the canonical player-facing game catalog
      description: |
        Publishes the stable Duel, Gacha, Marketplace Flip, and Card Streak taxonomy.
        Duel and Gacha actions derive from their live runtime capability checks.
        Flip and Card Streak expose playable fixture-backed demos while remaining
        explicitly unable to expose a value-bearing action through this response.
      x-dailydraft-availability: devnet-preview
      responses:
        '200':
          description: Current fail-closed game catalog
          headers:
            Cache-Control:
              schema:
                type: string
                const: no-store
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GameCatalog'
        '429':
          $ref: '#/components/responses/RateLimited'
  /games/availability:
    get:
      operationId: getGameAvailability
      tags: [Games]
      summary: Resolve public Duel, Gacha, Flip, and Card Streak availability
      description: |
        Publishes stable player-facing mode identifiers, actions, reasons, and
        capability sources. Duel and Gacha derive from runtime capability checks.
        Flip and Card Streak are playable no-value demos backed by local fixtures.
        This projection cannot enable payment, provider, custody, transfer, or payout.
      x-dailydraft-availability: devnet-preview
      responses:
        '200':
          description: Current fail-closed public mode availability
          headers:
            Cache-Control:
              schema:
                type: string
                const: no-store
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicGameAvailability'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          $ref: '#/components/responses/Unavailable'
  /games/activity:
    get:
      operationId: listVerifiedGameActivity
      tags: [Games]
      summary: List recent publicly verifiable game activity
      description: |
        Returns only settled rounds whose complete non-mock RGS proof and public
        receipt are resolvable. Direct wallet identifiers, private match state,
        transaction intents, and unsettled outcomes are never projected. Results
        are ordered by occurredAt descending, stable mode ID ascending, and
        immutable round ID descending.
        Flip and Crash remain absent until their engines emit the same eligible
        receipt and proof evidence.
      x-dailydraft-availability: devnet
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 50
            default: 20
        - name: cursor
          in: query
          schema:
            type: string
            maxLength: 483
            pattern: '^v1\.[A-Za-z0-9_-]{1,480}$'
      responses:
        '200':
          description: Bounded page of verified public activity
          headers:
            Cache-Control:
              schema:
                type: string
                const: public, max-age=30, stale-while-revalidate=120
            X-Robots-Tag:
              schema:
                type: string
                const: noindex, nofollow, noarchive
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VerifiedGameActivityPage'
        '400':
          $ref: '#/components/responses/BadRequest'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          $ref: '#/components/responses/Unavailable'
  /crash/rounds:
    get:
      operationId: listCrashHistory
      tags: [Crash]
      security:
        - walletSession: []
      summary: List the authenticated wallet's recent fixture-preview Crash rounds
      description: |
        Returns a bounded keyset-paginated history projection for the wallet
        session. Every row is revalidated against its append-only game ledger.
        The private response excludes raw wallets, provider signatures,
        provider evidence, signing payloads, and custody endpoints.
      x-dailydraft-availability: preview-fixture-only
      parameters:
        - name: cursor
          in: query
          required: false
          schema:
            type: string
            pattern: '^v1\.[A-Za-z0-9_-]{1,480}$'
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 50
            default: 20
      responses:
        '200':
          description: Recent wallet-scoped Crash rounds and an optional next cursor
          headers:
            Cache-Control:
              schema:
                type: string
                const: private, no-store
            X-Robots-Tag:
              schema:
                type: string
                const: noindex, nofollow, noarchive
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CrashHistoryPage'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          $ref: '#/components/responses/Unavailable'
  /crash/rounds/{roundId}:
    get:
      operationId: getCrashCurrentStage
      tags: [Crash]
      security:
        - walletSession: []
      summary: Restore the canonical fixture-preview Crash stage
      description: |
        Reconnects the authenticated player to the durable current stage. If
        its deadline has passed, the pre-disclosed forfeit is committed before
        the response is returned. Terminal reads also resume the deterministic
        fixture settlement and expose only its bounded recovery status and
        receipt hash, never provider signatures, custody references, or
        synthetic wallet evidence.
      x-dailydraft-availability: preview-fixture-only
      parameters:
        - $ref: '#/components/parameters/CrashRoundId'
      responses:
        '200':
          description: Canonical current stage, deadline, default, and available actions
          headers:
            Cache-Control:
              schema:
                type: string
                const: private, no-store
            X-Robots-Tag:
              schema:
                type: string
                const: noindex, nofollow, noarchive
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CrashCurrentStage'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          $ref: '#/components/responses/Unavailable'
  /crash/rounds/{roundId}/receipt:
    get:
      operationId: getCrashReceipt
      tags: [Crash]
      security:
        - walletSession: []
      summary: Retrieve a verified private Crash history receipt
      description: |
        Projects the authenticated player's canonical transition ledger,
        custody intent status, and settlement operation status in event order.
        Game-state commitment, custody finality, and settlement finality are
        reported separately. Recovery never implies ownership finality. Raw
        wallets, provider signatures/evidence, and transaction payloads are
        never included.
      x-dailydraft-availability: preview-fixture-only
      parameters:
        - $ref: '#/components/parameters/CrashRoundId'
      responses:
        '200':
          description: Wallet-scoped, privacy-safe durable Crash receipt
          headers:
            Cache-Control:
              schema:
                type: string
                const: private, no-store
            X-Robots-Tag:
              schema:
                type: string
                const: noindex, nofollow, noarchive
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CrashReceipt'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          $ref: '#/components/responses/Unavailable'
  /crash/rounds/{roundId}/decisions:
    post:
      operationId: submitCrashDecision
      tags: [Crash]
      security:
        - walletSession: []
      summary: Continue or cash out the current fixture-preview Crash stage
      description: |
        Submits only the player action and the canonical stage/version pair.
        The server owns every synthetic provider, custody, payment, and
        settlement fixture. Exact Idempotency-Key retries return the same
        canonical result; stale or changed retries are rejected. A deadline
        race commits either the player decision or the disclosed forfeit,
        never both.
      x-dailydraft-availability: preview-fixture-only
      parameters:
        - $ref: '#/components/parameters/CrashRoundId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CrashPlayerDecisionRequest'
      responses:
        '200':
          description: Canonical stage after the accepted, replayed, or deadline-defaulted decision
          headers:
            Cache-Control:
              schema:
                type: string
                const: private, no-store
            X-Robots-Tag:
              schema:
                type: string
                const: noindex, nofollow, noarchive
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CrashCurrentStage'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          $ref: '#/components/responses/Unavailable'
  /crash/rounds/{roundId}/settlement/reconciliation:
    post:
      operationId: reconcileCrashSettlement
      tags: [Crash]
      security:
        - walletSession: []
      summary: Resume a terminal fixture-preview Crash settlement
      description: |
        Reconciles every deterministic provider request before considering a
        retry. Ambiguous or lost responses remain recoverable under the
        original request key and never create a second signed side effect.
        Crash remains fixture-preview only and this route cannot move live
        assets or enable production capability.
      x-dailydraft-availability: preview-fixture-only
      parameters:
        - $ref: '#/components/parameters/CrashRoundId'
      responses:
        '200':
          description: Canonical stage and bounded settlement recovery status
          headers:
            Cache-Control:
              schema:
                type: string
                const: private, no-store
            X-Robots-Tag:
              schema:
                type: string
                const: noindex, nofollow, noarchive
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CrashCurrentStage'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          $ref: '#/components/responses/Unavailable'
  /gacha/capability:
    get:
      operationId: getGachaCapability
      tags: [Gacha]
      summary: Inspect the fail-closed Sports Pack Gacha capability gates
      description: |
        Reports whether provider, odds, acquisition, and settlement are ready.
        Preview availability never implies that real Collector Crypt rips are enabled.
      x-dailydraft-availability: devnet-preview
      responses:
        '200':
          description: Current Sports Pack Gacha capability
          headers:
            Cache-Control:
              schema:
                type: string
                const: no-store
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GachaCapability'
        '429':
          $ref: '#/components/responses/RateLimited'
  /gacha/machines/{machineKey}/inventory:
    get:
      operationId: getGachaMachineInventory
      tags: [Gacha]
      summary: Get the latest sealed inventory snapshot for a Gacha machine
      x-dailydraft-availability: devnet-preview
      parameters:
        - $ref: '#/components/parameters/GachaMachineKey'
      responses:
        '200':
          description: Latest sealed inventory snapshot and its canonical entries
          headers:
            Cache-Control:
              schema:
                type: string
                const: no-store
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GachaInventorySnapshot'
        '400':
          $ref: '#/components/responses/BadRequest'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          $ref: '#/components/responses/Unavailable'
  /gacha/machines/{machineKey}/odds:
    get:
      operationId: getGachaMachineOdds
      tags: [Gacha]
      summary: Get the latest sealed odds commitment for a Gacha machine
      x-dailydraft-availability: devnet-preview
      parameters:
        - $ref: '#/components/parameters/GachaMachineKey'
      responses:
        '200':
          description: Latest sealed probability commitment
          headers:
            Cache-Control:
              schema:
                type: string
                const: no-store
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GachaPullOddsCommitment'
        '400':
          $ref: '#/components/responses/BadRequest'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          $ref: '#/components/responses/Unavailable'
  /gacha/machines/{machineKey}/rip-commitments:
    post:
      operationId: createGachaRipSeedCommitment
      tags: [Gacha]
      security:
        - walletSession: []
      summary: Commit to a secret server seed before a fixture-mode Gacha rip
      description: |
        Generates and stores a secret serverSeed and publishes only its SHA-256
        hash, so the roll cannot be predicted or steered before the client seed
        is known. The commitmentId returned here is consumed by exactly one
        POST /gacha/rips call; the serverSeed itself is revealed only after
        that rip reaches a terminal state. Fails closed unless fixture or
        preview mode is explicitly enabled.
      x-dailydraft-availability: devnet-preview
      parameters:
        - $ref: '#/components/parameters/GachaMachineKey'
      responses:
        '201':
          description: Seed commitment hash, never the raw serverSeed
          headers:
            Cache-Control:
              schema:
                type: string
                const: no-store
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GachaRipSeedCommitment'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          $ref: '#/components/responses/Unavailable'
  /gacha/machines/{machineKey}/payment-intents:
    post:
      operationId: createGachaPaymentIntent
      tags: [Gacha]
      security:
        - walletSession: []
      summary: Open a payment intent for one funded Gacha rip
      description: |
        Issues the exact deposit terms for a single rip: mint, destination
        token account, amount, and a memoNonce the payer must echo in an SPL
        Memo instruction. The nonce is what binds an on-chain transfer to this
        intent, so a transfer that omits it can never be claimed as payment for
        a rip. At most one unresolved intent exists for each payer and machine.
        Repeating this request resumes that intent, including its claimed
        signature, instead of creating deposit terms that could fund twice.
      x-dailydraft-availability: devnet-preview
      parameters:
        - $ref: '#/components/parameters/GachaMachineKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateGachaPaymentIntentRequest'
      responses:
        '201':
          description: New or resumed deposit terms the payer must satisfy on chain
          headers:
            Cache-Control:
              schema:
                type: string
                const: no-store
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GachaPaymentIntent'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '409':
          $ref: '#/components/responses/Conflict'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          $ref: '#/components/responses/Unavailable'
  /gacha/payment-intents/{intentId}/transaction:
    post:
      operationId: prepareGachaPaymentTransaction
      tags: [Gacha]
      security:
        - walletSession: []
      summary: Build the unsigned transfer that funds a Gacha payment intent
      description: |
        Returns the exact unsigned transaction the payer should sign: a
        transferChecked of the intent's amount from the payer's associated token
        account to the house treasury, plus an SPL Memo carrying the memoNonce.
        The server builds it so the memo, mint, and amount cannot drift from the
        intent, and publishes expectedMessageHash so the wallet can prove the
        bytes it is about to sign are the bytes that were prepared.

        This is a POST rather than a GET because each call spends a fresh
        blockhash and can expire a lapsed intent as a side effect. It is safe to
        repeat: preparing does not consume the intent, so a player whose
        blockhash went stale can simply ask again.
      x-dailydraft-availability: devnet-preview
      parameters:
        - $ref: '#/components/parameters/GachaPaymentIntentId'
      responses:
        '201':
          description: Unsigned transaction ready for wallet inspection and signature
          headers:
            Cache-Control:
              schema:
                type: string
                const: no-store
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PreparedGachaPaymentTransaction'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '409':
          $ref: '#/components/responses/Conflict'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          $ref: '#/components/responses/Unavailable'
  /gacha/payment-intents/{intentId}/signature:
    post:
      operationId: claimGachaPaymentSignature
      tags: [Gacha]
      security:
        - walletSession: []
      summary: Prove and atomically bind the signed transaction before broadcast
      description: |
        Accepts the fully signed bytes returned by the payer wallet. The server
        verifies the Ed25519 signature and requires the exact prepared fee payer,
        source token account, mint, destination, amount, memo, and blockhash
        before deriving and storing the authoritative signature. A caller cannot
        reserve another wallet's intent with an arbitrary base58 string.

        The first valid signed transaction wins. Exact replay resumes safely,
        while different signed bytes are rejected before broadcast. The claimed
        slot remains fail-closed until settlement, except when finalized RPC
        evidence proves both signature absence and blockhash expiry.
      x-dailydraft-availability: devnet-preview
      parameters:
        - $ref: '#/components/parameters/GachaPaymentIntentId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ClaimGachaPaymentSignatureRequest'
      responses:
        '200':
          description: Active intent bound to this signature
          headers:
            Cache-Control:
              schema:
                type: string
                const: no-store
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GachaPaymentIntent'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          $ref: '#/components/responses/Unavailable'
  /gacha/payment-intents/{intentId}/verify:
    post:
      operationId: verifyGachaPayment
      tags: [Gacha]
      security:
        - walletSession: []
      summary: Verify the on-chain transfer that funds a Gacha payment intent
      description: |
        Requires the signature to have been claimed before broadcast, then
        re-reads the finalized signature from the chain and checks the transfer
        against the intent it claims to fund: mint, destination token account,
        amount, payer, and the memoNonce. Nothing the client asserts is trusted.
        A verified intent is spendable by exactly one subsequent POST
        /gacha/rips call. Replaying the same signature for the same intent returns
        the previously recorded verification evidence, allowing safe recovery
        from a dropped response; a different signature is rejected.
      x-dailydraft-availability: devnet-preview
      parameters:
        - $ref: '#/components/parameters/GachaPaymentIntentId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VerifyGachaPaymentRequest'
      responses:
        '200':
          description: The verified deposit, now spendable on one rip
          headers:
            Cache-Control:
              schema:
                type: string
                const: no-store
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VerifiedGachaPayment'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          $ref: '#/components/responses/Unavailable'
  /gacha/rips:
    post:
      operationId: createFixtureGachaRip
      tags: [Gacha]
      security:
        - walletSession: []
      summary: Execute one Gacha rip against sealed inventory and odds
      description: |
        Selects against sealed inventory and odds evidence, consuming a
        previously issued rip-commitments seed commitment under the same
        advisory lock, then records reveal, acquisition, and settlement as
        separate lifecycle transitions. This route fails closed unless
        fixture or devnet preview mode is explicitly enabled. An Idempotency-Key
        header (or idempotencyKey body field) replays the original rip
        instead of creating a second one.

        In devnet preview mode the rip must be funded: paymentIntentId is
        required and names a verified payment intent, which is spent inside the
        same transaction that consumes the seed. In fixture mode rips stay
        unfunded so deterministic previews need no chain access, and
        paymentIntentId is ignored.
      x-dailydraft-availability: devnet-preview
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          schema:
            type: string
            minLength: 1
            maxLength: 240
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateFixtureGachaRipRequest'
      responses:
        '201':
          description: Settled fixture rip and the exact odds commitment used
          headers:
            Cache-Control:
              schema:
                type: string
                const: no-store
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GachaRipResult'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '409':
          $ref: '#/components/responses/Conflict'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          $ref: '#/components/responses/Unavailable'
  /fantasy/capabilities:
    get:
      operationId: getFantasyCapabilities
      tags: [Fantasy]
      summary: Resolve the gated fantasy tournament capabilities
      description: |
        Fail-closed probe for the fantasy tournament loop. Every mode reports
        disabled until the match-data oracle, kickoff snapshot, and payout
        settlement are reviewed and approved. The UI must never submit a mode
        marked unavailable as playable.
      x-dailydraft-availability: devnet
      responses:
        '200':
          description: Current gated fantasy tournament capabilities
          headers:
            Cache-Control:
              schema:
                type: string
                const: no-store
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FantasyProductCapabilities'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          $ref: '#/components/responses/Unavailable'
  /valuation-policies/current:
    get:
      operationId: getCurrentValuationPolicy
      tags: [Valuation]
      summary: Get the only valuation policy accepted for new devnet duels
      description: |
        Returns the canonical JSON policy and its SHA-256 hash. The same hash is
        snapshotted on the duel, matchmaking ticket, funding transaction, result
        proof, and escrow account before either player funds.
      x-dailydraft-availability: devnet
      responses:
        '200':
          description: Current immutable valuation policy
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValuationPolicyDocument'
        '429':
          $ref: '#/components/responses/RateLimited'
  /valuation-policies/{policyHash}:
    get:
      operationId: getValuationPolicyByHash
      tags: [Valuation]
      summary: Resolve an immutable valuation policy by its committed hash
      x-dailydraft-availability: devnet
      parameters:
        - $ref: '#/components/parameters/ValuationPolicyHash'
      responses:
        '200':
          description: Immutable valuation policy
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValuationPolicyDocument'
        '404':
          $ref: '#/components/responses/NotFound'
  /packs:
    get:
      operationId: listPacks
      tags: [Packs]
      summary: List duel-eligible pack definitions
      x-dailydraft-availability: devnet
      parameters:
        - $ref: '#/components/parameters/Cursor'
        - $ref: '#/components/parameters/Limit'
        - name: active
          in: query
          schema:
            type: boolean
            default: true
      responses:
        '200':
          description: Pack definitions
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PackList'
        '400':
          $ref: '#/components/responses/BadRequest'
  /packs/{packId}:
    get:
      operationId: getPack
      tags: [Packs]
      summary: Get one pack definition
      x-dailydraft-availability: devnet
      parameters:
        - $ref: '#/components/parameters/PackId'
      responses:
        '200':
          description: Pack definition
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Pack'
        '404':
          $ref: '#/components/responses/NotFound'
  /auth/challenges:
    post:
      operationId: createWalletChallenge
      tags: [Authentication]
      summary: Prepare a Solana devnet wallet-ownership message
      description: Each challenge remains independently usable once and expires after five minutes.
      x-dailydraft-availability: devnet
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateWalletChallengeRequest'
      responses:
        '201':
          description: Domain-bound challenge
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WalletChallenge'
        '400':
          $ref: '#/components/responses/BadRequest'
        '429':
          $ref: '#/components/responses/RateLimited'
  /auth/sessions:
    post:
      operationId: createWalletSession
      tags: [Authentication]
      summary: Exchange one valid Ed25519 signature for an opaque wallet session
      x-dailydraft-availability: devnet
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateWalletSessionRequest'
      responses:
        '201':
          description: Short-lived browser wallet session
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WalletSession'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          $ref: '#/components/responses/Conflict'
  /auth/session:
    get:
      operationId: getWalletSession
      tags: [Authentication]
      summary: Validate the current wallet session
      security:
        - walletSession: []
      responses:
        '200':
          description: Authenticated wallet identity
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required: [wallet, network]
                properties:
                  wallet:
                    $ref: '#/components/schemas/SolanaAddress'
                  network:
                    type: string
                    const: solana-devnet
        '401':
          $ref: '#/components/responses/Unauthorized'
  /auth/session/revoke:
    post:
      operationId: revokeWalletSession
      tags: [Authentication]
      summary: Revoke an opaque wallet session
      security:
        - walletSession: []
      responses:
        '204':
          description: Session revoked
        '401':
          $ref: '#/components/responses/Unauthorized'
  /duels:
    get:
      operationId: listDuels
      tags: [Duels]
      summary: List public duels
      x-dailydraft-availability: devnet
      parameters:
        - $ref: '#/components/parameters/Cursor'
        - $ref: '#/components/parameters/Limit'
        - name: status
          in: query
          schema:
            $ref: '#/components/schemas/DuelStatus'
        - name: wallet
          in: query
          schema:
            $ref: '#/components/schemas/SolanaAddress'
        - name: matchmakingMode
          in: query
          schema:
            $ref: '#/components/schemas/MatchmakingMode'
        - name: packId
          in: query
          schema:
            $ref: '#/components/schemas/PackId'
      responses:
        '200':
          description: Duels
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DuelList'
        '400':
          $ref: '#/components/responses/BadRequest'
    post:
      operationId: createDuel
      tags: [Duels]
      summary: Create an off-chain duel intent
      description: The returned intent is not funded until its Solana transaction confirms.
      x-dailydraft-availability: devnet
      security:
        - walletSession: []
        - integrationKey: []
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateDuelRequest'
      responses:
        '201':
          description: Duel intent created
          headers:
            Location:
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Duel'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '409':
          $ref: '#/components/responses/Conflict'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          $ref: '#/components/responses/Unavailable'
  /matchmaking/search:
    post:
      operationId: searchOpenMatchmaking
      tags: [Matchmaking]
      summary: Start or reconnect to an exact-segment open search
      description: |
        Queues by exact pack tier, valuation policy, provider mode, server-verified
        region, and risk segment. A wallet has at most one active ticket, so retries
        and duplicate tabs reconnect to the same durable state. The endpoint fails
        closed when verified region or risk segmentation is unavailable.
      security:
        - walletSession: []
        - integrationKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MatchmakingSearchRequest'
      responses:
        '200':
          description: Current searching or matched session
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MatchmakingSession'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          $ref: '#/components/responses/Unavailable'
  /matchmaking/continue:
    post:
      operationId: continueOpenMatchmaking
      tags: [Matchmaking]
      summary: Explicitly continue or reconnect to the open search
      security:
        - walletSession: []
        - integrationKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MatchmakingSearchRequest'
      responses:
        '200':
          description: Current searching or matched session
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MatchmakingSession'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '503':
          $ref: '#/components/responses/Unavailable'
  /matchmaking/status:
    post:
      operationId: getOpenMatchmakingStatus
      tags: [Matchmaking]
      summary: Reconnect to a wallet's durable active session
      security:
        - walletSession: []
        - integrationKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MatchmakingWalletRequest'
      responses:
        '200':
          description: Current session
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MatchmakingSession'
        '404':
          $ref: '#/components/responses/NotFound'
  /matchmaking/cancel:
    post:
      operationId: cancelOpenMatchmaking
      tags: [Matchmaking]
      summary: Explicitly abandon an unfunded search or matched slot
      security:
        - walletSession: []
        - integrationKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MatchmakingWalletRequest'
      responses:
        '200':
          description: Search cancelled
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
  /matchmaking/house-fallback:
    post:
      operationId: selectHouseFallback
      tags: [Matchmaking]
      summary: Explicitly select the disclosed house opponent
      description: Never selected automatically. Disabled by default and subject to treasury risk controls.
      security:
        - walletSession: []
        - integrationKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MatchmakingWalletRequest'
      responses:
        '200':
          description: House match selected with a bounded commitment deadline
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MatchmakingSession'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '503':
          $ref: '#/components/responses/Unavailable'
  /duels/{duelId}:
    get:
      operationId: getDuel
      tags: [Duels]
      summary: Get canonical duel state
      x-dailydraft-availability: devnet
      security:
        - walletSession: []
        - integrationKey: []
      parameters:
        - $ref: '#/components/parameters/DuelId'
      responses:
        '200':
          description: Duel state
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Duel'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /duels/{duelId}/rematch-opponent:
    get:
      operationId: getPrivateRematchOpponent
      tags: [Duels]
      summary: Resolve the authenticated participant's private rematch opponent
      description: |
        Returns only the opposite participant after a settled direct duel.
        Spectators, house duels, unsettled duels, and non-participants fail closed.
      x-dailydraft-availability: devnet
      security:
        - walletSession: []
      parameters:
        - $ref: '#/components/parameters/DuelId'
      responses:
        '200':
          description: Private rematch opponent
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required: [side, wallet]
                properties:
                  side:
                    type: string
                    enum: [creator, opponent]
                  wallet:
                    $ref: '#/components/schemas/SolanaAddress'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /duels/{duelId}/receipt:
    get:
      operationId: getPublicDuelReceipt
      tags: [Duels]
      summary: Download the strict public proof for one duel
      description: |
        Returns a no-store, machine-readable snapshot built only from durable
        public duel state. Private transaction metadata, provider errors,
        credentials, and support notes are never included. Missing settlement
        or custody evidence is declared in `availability` instead of inferred.
      x-dailydraft-availability: devnet
      parameters:
        - $ref: '#/components/parameters/DuelId'
      responses:
        '200':
          description: Downloadable public duel receipt
          headers:
            Content-Disposition:
              schema:
                type: string
                example: attachment; filename="duel_example.receipt.json"
            X-Robots-Tag:
              schema:
                type: string
                const: noindex, nofollow, noarchive
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicDuelReceipt'
        '404':
          $ref: '#/components/responses/NotFound'
  /duels/{duelId}/join:
    post:
      operationId: joinDuel
      tags: [Duels]
      summary: Join a direct invitation or open devnet duel
      description: |
        Atomically claims an open duel or accepts a direct invitation. House
        duels are already matched at creation and cannot be joined.
      x-dailydraft-availability: devnet
      security:
        - walletSession: []
        - integrationKey: []
      parameters:
        - $ref: '#/components/parameters/DuelId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/JoinDuelRequest'
      responses:
        '200':
          description: Duel matched
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Duel'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
  /duels/{duelId}/cancel:
    post:
      operationId: cancelDuel
      tags: [Duels]
      summary: Cancel an unfunded devnet duel
      description: |
        A participant can cancel only while the duel is waiting or matched.
        Expired waiting and matched duels deterministically become cancelled
        with the `timeout` reason.
      x-dailydraft-availability: devnet
      security:
        - walletSession: []
        - integrationKey: []
      parameters:
        - $ref: '#/components/parameters/DuelId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CancelDuelRequest'
      responses:
        '200':
          description: Duel cancelled
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Duel'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
  /duels/{duelId}/events:
    get:
      operationId: listDuelEvents
      tags: [Duels]
      summary: List the authenticated immutable duel state timeline
      description: Internal event data is never exposed by an unauthenticated endpoint.
      x-dailydraft-availability: devnet
      security:
        - integrationKey: []
      parameters:
        - $ref: '#/components/parameters/DuelId'
      responses:
        '200':
          description: Ordered audit events
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/DuelEvent'
        '404':
          $ref: '#/components/responses/NotFound'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /duels/{duelId}/transactions:
    get:
      operationId: listDuelTransactions
      tags: [Transactions]
      summary: List authenticated reconciliation-safe Solana transaction records
      description: Raw provider errors and transaction metadata require an integration key.
      x-dailydraft-availability: devnet
      security:
        - integrationKey: []
      parameters:
        - $ref: '#/components/parameters/DuelId'
      responses:
        '200':
          description: Ordered transaction records
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/DuelTransactionRecord'
        '404':
          $ref: '#/components/responses/NotFound'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      operationId: prepareDuelTransaction
      tags: [Transactions]
      summary: Prepare a wallet-signed Solana transaction
      description: |
        Returns an unsigned devnet transaction that wraps and escrows only the configured
        per-side platform fee. The pack purchase is not included. The API never signs for a wallet.
      x-dailydraft-availability: devnet
      security:
        - walletSession: []
        - integrationKey: []
      parameters:
        - $ref: '#/components/parameters/DuelId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PrepareTransactionRequest'
      responses:
        '201':
          description: Transaction ready for wallet inspection and signature
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PreparedTransaction'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '503':
          $ref: '#/components/responses/Unavailable'
  /duels/{duelId}/transactions/{transactionId}/submissions:
    post:
      operationId: recordDuelTransactionSubmission
      tags: [Transactions]
      summary: Bind a broadcast signature to a durable prepared intent
      description: |
        Accepts the signing wallet's authenticated session or an integration
        key. A wallet session can bind only its own prepared transaction.
        The transaction intent must already contain its blockhash, expiry,
        expected signer, escrow program, target instruction data hash, exact
        ordered instruction-account access constraints, and state transition.
        Arbitrary signatures cannot create an intent.
      x-dailydraft-availability: devnet
      security:
        - walletSession: []
        - integrationKey: []
      parameters:
        - $ref: '#/components/parameters/DuelId'
        - $ref: '#/components/parameters/TransactionId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RecordSubmissionRequest'
      responses:
        '202':
          description: Signature recorded for independent reconciliation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BoundSubmission'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '503':
          $ref: '#/components/responses/Unavailable'
  /duels/{duelId}/transactions/{transactionId}/rejections:
    post:
      operationId: recordDuelTransactionWalletRejection
      tags: [Transactions]
      summary: Expire a prepared funding intent rejected before broadcast
      description: |
        Records an explicit wallet rejection only when the authenticated signer
        knows that no signature or broadcast occurred. The exact unsigned fund
        intent is expired idempotently so a replacement can be prepared
        immediately. Wallet, RPC, or transport failures with an uncertain
        broadcast outcome must use reconciliation instead.
      x-dailydraft-availability: devnet
      security:
        - walletSession: []
        - integrationKey: []
      parameters:
        - $ref: '#/components/parameters/DuelId'
        - $ref: '#/components/parameters/TransactionId'
      responses:
        '200':
          description: Exact prepared funding intent expired or already recorded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RejectedFundingIntent'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: Wallet session does not own this funding intent
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
  /duels/{duelId}/transactions/reconciliation:
    post:
      operationId: reconcileDuelTransactions
      tags: [Transactions]
      summary: Reconcile active transactions for one authenticated duel
      description: |
        Performs a bounded Solana devnet finality check for active transactions
        belonging only to this duel. A wallet session must belong to the creator
        or opponent; an integration key may use the same route. This request does
        not scan recovery candidates or transactions from another duel. It is safe
        for browser polling and allows normal finality to progress without a
        high-frequency background worker.
      x-dailydraft-availability: devnet
      security:
        - walletSession: []
        - integrationKey: []
      parameters:
        - $ref: '#/components/parameters/DuelId'
      responses:
        '200':
          description: Current duel-scoped reconciliation state
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DuelReconciliationResult'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: Wallet session does not belong to this duel
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '503':
          $ref: '#/components/responses/Unavailable'
  /internal/reconciliation/solana:
    get:
      operationId: reconcileSolanaTransactionsFromCron
      tags: [Operations]
      summary: Reconcile a bounded batch of submitted Solana transactions
      description: |
        The on-host cron.d timer supplies `Authorization: Bearer $CRON_SECRET`.
        Confirmed transactions remain pending; only finalized and verified
        transactions can advance duel state. The same bounded pass can recover
        an exact persisted prepared funding message from its escrow-PDA history.
      x-dailydraft-availability: devnet
      security:
        - cronSecret: []
        - integrationKey: []
      parameters:
        - $ref: '#/components/parameters/ReconciliationLimit'
      responses:
        '200':
          description: Bounded reconciliation summary
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReconciliationSummary'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '503':
          $ref: '#/components/responses/Unavailable'
    post:
      operationId: reconcileSolanaTransactionsManually
      tags: [Operations]
      summary: Manually reconcile a bounded batch
      description: Accepts the cron secret or an integration API key.
      x-dailydraft-availability: devnet
      security:
        - cronSecret: []
        - integrationKey: []
      parameters:
        - $ref: '#/components/parameters/ReconciliationLimit'
      responses:
        '200':
          description: Bounded reconciliation summary
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReconciliationSummary'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '503':
          $ref: '#/components/responses/Unavailable'
  /duels/{duelId}/social-card:
    get:
      operationId: getDuelSocialCard
      tags: [Social]
      summary: Get canonical share and social-card URLs
      x-dailydraft-availability: devnet
      parameters:
        - $ref: '#/components/parameters/DuelId'
      responses:
        '200':
          description: Canonical social URLs for the current duel status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SocialCard'
        '404':
          $ref: '#/components/responses/NotFound'
  /duels/{duelId}/open-packs:
    post:
      operationId: openDuelPacks
      tags: [Duels]
      summary: Generate, open, and normalize both funded duel packs
      description: |
        Idempotent server-side orchestration for a funded duel. A wallet
        participant or integration may advance the DailyDraft devnet provider,
        which mints two single-supply SPL demo cards, deposits both in escrow,
        commits the result, and settles the winner on Solana devnet.
      x-dailydraft-availability: devnet
      security:
        - walletSession: []
        - integrationKey: []
      parameters:
        - $ref: '#/components/parameters/DuelId'
        - $ref: '#/components/parameters/IdempotencyKey'
      responses:
        '200':
          description: Both results are normalized and ready for settlement orchestration
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Duel'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '502':
          $ref: '#/components/responses/Unavailable'
        '503':
          $ref: '#/components/responses/Unavailable'
  /leaderboard:
    get:
      operationId: getPublicDuelLeaderboard
      tags: [Duels]
      summary: Get bounded pseudonymous devnet standings
      description: |
        Ranks wallets from the latest 5,000 settled, non-mock duel results.
        Ordering is wins, total value won, completed duels, then recency.
        Mock previews and incomplete result proofs never enter the standings.
        The API coalesces and caches the bounded database snapshot for 30 seconds,
        so cache-busting query strings cannot amplify database reads. Public edge
        rate limiting may reject abusive request bursts.
      x-dailydraft-availability: devnet
      responses:
        '200':
          description: Pseudonymous recent leaderboard
          headers:
            Cache-Control:
              schema:
                type: string
                const: public, max-age=30, stale-while-revalidate=120
            X-Robots-Tag:
              schema:
                type: string
                const: noindex, nofollow, noarchive
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicDuelLeaderboard'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          $ref: '#/components/responses/Unavailable'
  /profiles/{wallet}:
    get:
      operationId: getPublicWalletProfile
      tags: [Duels]
      summary: Get a bounded pseudonymous wallet duel summary
      description: |
        Returns recent durable duel activity, record, refunds, and biggest
        recorded win. The wallet remains a public Solana address, while display
        labels are shortened and consumers must not index this response.
      x-dailydraft-availability: devnet
      parameters:
        - $ref: '#/components/parameters/Wallet'
      responses:
        '200':
          description: Pseudonymous wallet profile
          headers:
            X-Robots-Tag:
              schema:
                type: string
                const: noindex, nofollow, noarchive
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicWalletProfile'
        '404':
          $ref: '#/components/responses/NotFound'
  /analytics/events:
    post:
      operationId: ingestProductEvents
      tags: [Analytics]
      summary: Accept a bounded anonymous product-event batch
      description: |
        Accepts at most 20 allowlisted events. The contract has no wallet,
        signature, token, private-key, metadata, or free-form error fields.
        Only UI-intent events are accepted; canonical lifecycle and operational
        events are server-authored. The 120-event anonymous-session cap is
        defense-in-depth and can be bypassed through session churn. Production
        must enforce upstream or host-level IP rate limiting before exposing this route.
      x-dailydraft-availability: devnet
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/IngestProductEventsRequest'
      responses:
        '202':
          description: Event batch accepted
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                required: [accepted]
                properties:
                  accepted:
                    type: integer
                    minimum: 1
                    maximum: 20
        '400':
          $ref: '#/components/responses/BadRequest'
        '429':
          $ref: '#/components/responses/RateLimited'
  /analytics/funnel:
    get:
      operationId: getAnalyticsFunnel
      tags: [Analytics]
      summary: Get aggregate funnel and operational health
      description: |
        Deduplicates UI steps by anonymous session and server-authored lifecycle
        steps by duel, derives match
        and provider latency percentiles, and reports stuck-funded, refund,
        abandonment, settlement-failure, provider-error, and RPC-error health.
      x-dailydraft-availability: devnet
      security:
        - integrationKey: []
      parameters:
        - name: windowHours
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 720
            default: 24
      responses:
        '200':
          description: Aggregate privacy-safe analytics report
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AnalyticsFunnelReport'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /admin/duels:
    get:
      operationId: listAdminAttentionDuels
      tags: [Operations]
      summary: Paginate stuck or failed duels requiring attention
      x-dailydraft-availability: devnet
      security:
        - integrationKey: []
      parameters:
        - $ref: '#/components/parameters/Limit'
        - name: cursor
          in: query
          schema:
            type: string
            pattern: '^duel_[A-Za-z0-9]{12,64}$'
        - name: attention
          in: query
          schema:
            type: string
            enum: [all, failed, stuck]
            default: all
        - name: duelId
          in: query
          schema:
            type: string
            pattern: '^duel_[A-Za-z0-9]{12,64}$'
        - name: wallet
          in: query
          schema:
            $ref: '#/components/schemas/SolanaAddress'
        - name: signature
          in: query
          schema:
            type: string
            pattern: '^[1-9A-HJ-NP-Za-km-z]{64,100}$'
        - name: providerReference
          in: query
          schema:
            type: string
            maxLength: 160
        - name: assetReference
          in: query
          schema:
            type: string
            maxLength: 160
      responses:
        '200':
          description: Attention queue
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AdminPage'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /admin/duels/{duelId}/timeline:
    get:
      operationId: getAdminDuelTimeline
      tags: [Operations]
      summary: Inspect canonical state, transaction, provider, valuation, and custody history
      x-dailydraft-availability: devnet
      security:
        - integrationKey: []
      parameters:
        - $ref: '#/components/parameters/DuelId'
      responses:
        '200':
          description: Complete support timeline without serialized transaction bytes
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /admin/risk:
    get:
      operationId: getAdminRiskSummary
      tags: [Operations]
      summary: Inspect bounded devnet exposure and configured limits
      x-dailydraft-availability: devnet
      security:
        - integrationKey: []
      responses:
        '200':
          description: Aggregate risk summary with hashed wallet references
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
  /admin/readiness:
    get:
      operationId: getAdminReadiness
      tags: [Operations]
      summary: Inspect database, live devnet RPC, provider, treasury, and worker readiness
      description: |
        Reports configured and independently verified state separately. It never
        returns RPC URLs, wallet addresses, API keys, cron secrets, or credentials.
        House balances are verified only when a fresh finalized devnet token-account
        snapshot matches the configured cold owner, mint, and bounded hot-wallet SPL
        delegate policy.
      x-dailydraft-availability: devnet
      security:
        - integrationKey: []
      responses:
        '200':
          description: Secret-free readiness report
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          $ref: '#/components/responses/Unauthorized'
  /admin/treasury:
    get:
      operationId: getHouseTreasurySummary
      tags: [Operations]
      summary: Inspect finalized devnet liquidity, reserved exposure, losses, and inventory risk
      x-dailydraft-availability: devnet-gated
      security:
        - integrationKey: []
      responses:
        '200':
          description: Secret-free house treasury and concentration dashboard
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401': { $ref: '#/components/responses/Unauthorized' }
  /admin/treasury/inventory:
    get:
      operationId: listHouseInventory
      tags: [Operations]
      summary: List durable house-won card inventory and reconciliation state
      x-dailydraft-availability: devnet-gated
      security:
        - integrationKey: []
      parameters:
        - $ref: '#/components/parameters/Limit'
        - name: status
          in: query
          schema:
            type: string
            enum: [held, listed, disposed, reconciliation_required]
      responses:
        '200':
          description: House inventory ledger view
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401': { $ref: '#/components/responses/Unauthorized' }
  /internal/reconciliation/treasury:
    get:
      operationId: reconcileHouseTreasuryFromCron
      tags: [Operations]
      summary: Reconcile finalized devnet liquidity, inventory custody, and reservation lifecycle
      x-dailydraft-availability: internal-devnet
      security:
        - cronSecret: []
        - integrationKey: []
      responses:
        '200':
          description: Bounded treasury reconciliation summary
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401': { $ref: '#/components/responses/Unauthorized' }
        '503': { $ref: '#/components/responses/Unavailable' }
    post:
      operationId: reconcileHouseTreasuryManually
      tags: [Operations]
      summary: Manually reconcile finalized treasury, custody, and reservation state
      description: Accepts the cron secret or an integration API key.
      x-dailydraft-availability: internal-devnet
      security:
        - cronSecret: []
        - integrationKey: []
      responses:
        '200':
          description: Bounded treasury reconciliation summary
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401': { $ref: '#/components/responses/Unauthorized' }
        '503': { $ref: '#/components/responses/Unavailable' }
  /admin/treasury/inventory/{inventoryId}/disposition:
    put:
      operationId: setHouseInventoryDisposition
      tags: [Operations]
      summary: Select an allowed hold, buyback, list, promotion, or review workflow
      description: Records operator intent only; it does not call an undocumented marketplace API.
      x-dailydraft-availability: devnet-gated
      security:
        - integrationKey: []
      parameters:
        - name: inventoryId
          in: path
          required: true
          schema:
            type: string
            pattern: '^hinv_[a-f0-9]{32}$'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required: [disposition, operationKey, reason]
              properties:
                disposition:
                  type: string
                  enum: [hold, buyback, list, promotion, manual_review]
                operationKey:
                  type: string
                  pattern: '^[A-Za-z0-9:_-]{8,160}$'
                provider:
                  type: string
                  pattern: '^[A-Za-z0-9._:-]{3,80}$'
                  description: Required with providerListingReference when disposition is list.
                providerListingReference:
                  type: string
                  pattern: '^[A-Za-z0-9._:-]{8,240}$'
                  description: Durable provider evidence required when disposition is list.
                reason:
                  type: string
                  minLength: 3
                  maxLength: 160
      responses:
        '200':
          description: Durable disposition intent and immutable ledger evidence recorded
        '401': { $ref: '#/components/responses/Unauthorized' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/Conflict' }
  /admin/treasury/inventory/{inventoryId}/disposition/delist:
    post:
      operationId: delistHouseInventory
      tags: [Operations]
      summary: Reconcile provider cancellation evidence and return listed inventory to held custody
      description: >-
        Accepts provider-scoped HMAC cancellation evidence through the authenticated integration
        boundary. The evidence must identify the active provider listing exactly; this endpoint
        does not invent or infer an external cancellation.
      x-dailydraft-availability: devnet-gated
      security:
        - integrationKey: []
      parameters:
        - name: inventoryId
          in: path
          required: true
          schema:
            type: string
            pattern: '^hinv_[a-f0-9]{32}$'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
                - cancelledAt
                - operationKey
                - provider
                - providerCancellationEvidenceHash
                - providerCancellationReference
                - providerCancellationSignature
                - providerListingReference
                - reason
              properties:
                cancelledAt:
                  type: string
                  format: date-time
                operationKey:
                  type: string
                  pattern: '^[A-Za-z0-9:_-]{8,160}$'
                provider:
                  type: string
                  pattern: '^[A-Za-z0-9._:-]{3,80}$'
                providerCancellationEvidenceHash:
                  type: string
                  pattern: '^[a-f0-9]{64}$'
                providerCancellationReference:
                  type: string
                  pattern: '^[A-Za-z0-9._:-]{8,240}$'
                providerCancellationSignature:
                  type: string
                  pattern: '^[a-f0-9]{64}$'
                providerListingReference:
                  type: string
                  pattern: '^[A-Za-z0-9._:-]{8,240}$'
                reason:
                  type: string
                  minLength: 3
                  maxLength: 160
      responses:
        '200':
          description: Provider cancellation evidence appended and inventory returned to held state
        '401': { $ref: '#/components/responses/Unauthorized' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/Conflict' }
  /admin/treasury/inventory/{inventoryId}/disposition/complete:
    post:
      operationId: completeHouseInventoryDisposition
      tags: [Operations]
      summary: Record verified realized USDC proceeds for a completed disposition
      description: >-
        Listing sales require provider-scoped HMAC evidence bound to the exact active listing,
        sale reference, timestamp, gross proceeds, and fee.
      x-dailydraft-availability: devnet-gated
      security:
        - integrationKey: []
      parameters:
        - name: inventoryId
          in: path
          required: true
          schema:
            type: string
            pattern: '^hinv_[a-f0-9]{32}$'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
                [feeAmount, operationKey, realizedAmount, realizedCurrency, realizedDecimals, reason]
              properties:
                feeAmount: { type: string, pattern: '^\d+$' }
                operationKey:
                  type: string
                  pattern: '^[A-Za-z0-9:_-]{8,160}$'
                provider:
                  type: string
                  pattern: '^[A-Za-z0-9._:-]{3,80}$'
                  description: Required for marketplace listing completion.
                providerListingReference:
                  type: string
                  pattern: '^[A-Za-z0-9._:-]{8,240}$'
                  description: Must match the exact active listing.
                providerSaleAt:
                  type: string
                  format: date-time
                providerSaleEvidenceHash:
                  type: string
                  pattern: '^[a-f0-9]{64}$'
                providerSaleReference:
                  type: string
                  pattern: '^[A-Za-z0-9._:-]{8,240}$'
                providerSaleSignature:
                  type: string
                  pattern: '^[a-f0-9]{64}$'
                realizedAmount: { type: string, pattern: '^\d+$' }
                realizedCurrency: { type: string, const: USDC }
                realizedDecimals: { type: integer, const: 6 }
                reason: { type: string, minLength: 3, maxLength: 160 }
      responses:
        '200':
          description: Inventory marked disposed and realized proceeds appended to the ledger
        '401': { $ref: '#/components/responses/Unauthorized' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/Conflict' }
  /admin/emergency-pause:
    get:
      operationId: getEmergencyPause
      tags: [Operations]
      summary: Read the global new-exposure pause state
      x-dailydraft-availability: devnet
      security:
        - integrationKey: []
      responses:
        '200':
          description: Current emergency control state
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmergencyPauseState'
        '401':
          $ref: '#/components/responses/Unauthorized'
    put:
      operationId: setEmergencyPause
      tags: [Operations]
      summary: Idempotently pause or resume new exposure with an immutable audit record
      description: |
        Identical state-and-reason retries are no-ops. The audit actor is the
        fixed integration-key class; the raw bearer key is never persisted.
      x-dailydraft-availability: devnet
      security:
        - integrationKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EmergencyPauseRequest'
      responses:
        '200':
          description: Updated or already-current emergency state
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmergencyPauseState'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          $ref: '#/components/responses/Conflict'
  /admin/audit:
    get:
      operationId: listOperatorAudit
      tags: [Operations]
      summary: Paginate immutable emergency-control audit events
      x-dailydraft-availability: devnet
      security:
        - integrationKey: []
      parameters:
        - $ref: '#/components/parameters/Limit'
        - name: cursor
          in: query
          schema:
            type: string
            pattern: '^oaud_[a-f0-9]{32}$'
      responses:
        '200':
          description: Append-only audit history
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AdminPage'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /internal/provider/duels/{duelId}/escrow/transactions:
    post:
      operationId: prepareProviderEscrowTransaction
      tags: [Transactions]
      summary: Prepare an unsigned provider/result/settlement escrow transaction
      description: |
        Integration-only, devnet-only boundary. The API never signs or submits.
        Real card operations fail closed for mock/incomplete provider outcomes,
        unsupported asset standards, noncanonical comparison values or policies,
        invalid legacy SPL mint metadata, and missing vault custody.
      x-dailydraft-availability: devnet-gated
      security:
        - integrationKey: []
      parameters:
        - $ref: '#/components/parameters/DuelId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PrepareProviderEscrowRequest'
      responses:
        '201':
          description: Unsigned transaction and public proof binding
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PreparedProviderEscrowTransaction'
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '409': { $ref: '#/components/responses/Conflict' }
        '503': { $ref: '#/components/responses/Unavailable' }
components:
  securitySchemes:
    cronSecret:
      type: http
      scheme: bearer
      bearerFormat: random server secret
      description: Host reconciliation timer bearer secret; accepted only by internal worker routes.
    integrationKey:
      type: http
      scheme: bearer
      bearerFormat: opd_live_...
      description: Server-side integration key. Never expose it in browser code.
    walletSession:
      type: http
      scheme: bearer
      bearerFormat: opd_devnet_session_...
      description: Opaque 15-minute session issued after a Solana devnet Ed25519 signature.
  parameters:
    Cursor:
      name: cursor
      in: query
      schema:
        type: string
        maxLength: 256
    Limit:
      name: limit
      in: query
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 20
    PackId:
      name: packId
      in: path
      required: true
      schema:
        $ref: '#/components/schemas/PackId'
    GachaMachineKey:
      name: machineKey
      in: path
      required: true
      schema:
        $ref: '#/components/schemas/GachaMachineKey'
    GachaPaymentIntentId:
      name: intentId
      in: path
      required: true
      schema:
        $ref: '#/components/schemas/GachaPaymentIntentId'
    CrashRoundId:
      name: roundId
      in: path
      required: true
      schema:
        type: string
        pattern: '^crashround_[A-Za-z0-9._:-]{8,128}$'
    DuelId:
      name: duelId
      in: path
      required: true
      schema:
        type: string
        pattern: '^duel_[A-Za-z0-9]{12,64}$'
    Wallet:
      name: wallet
      in: path
      required: true
      schema:
        $ref: '#/components/schemas/SolanaAddress'
    ValuationPolicyHash:
      name: policyHash
      in: path
      required: true
      schema:
        type: string
        pattern: '^[a-f0-9]{64}$'
    TransactionId:
      name: transactionId
      in: path
      required: true
      schema:
        type: string
        pattern: '^tx_[A-Za-z0-9]{12,64}$'
    ReconciliationLimit:
      name: limit
      in: query
      required: false
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 50
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      schema:
        type: string
        minLength: 16
        maxLength: 128
  responses:
    BadRequest:
      description: Invalid request
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    Unauthorized:
      description: Missing, invalid, or expired bearer credential
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    Forbidden:
      description: The authenticated caller is not allowed to access this resource
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    NotFound:
      description: Resource not found
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    Conflict:
      description: Request conflicts with current duel state
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    RateLimited:
      description: Too many requests
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    Unavailable:
      description: Required server configuration or dependency is unavailable
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
  schemas:
    CrashPlayerDecisionRequest:
      type: object
      additionalProperties: false
      required: [action, expectedStage, expectedVersion]
      properties:
        action:
          type: string
          enum: [continue, cash-out]
        expectedStage:
          type: integer
          minimum: 1
        expectedVersion:
          type: integer
          minimum: 1
    CrashCurrentStage:
      type: object
      additionalProperties: false
      required:
        - availableActions
        - decisionDeadline
        - defaultAction
        - mode
        - network
        - pot
        - roundId
        - schemaVersion
        - settlement
        - stage
        - status
        - terminalReason
        - version
      properties:
        availableActions:
          type: array
          uniqueItems: true
          maxItems: 2
          items:
            type: string
            enum: [continue, cash-out]
        decisionDeadline:
          type: [string, 'null']
          format: date-time
        defaultAction:
          type: string
          const: forfeit
        mode:
          type: string
          const: fixture-preview
        network:
          type: string
          const: solana-devnet
        pot:
          $ref: '#/components/schemas/Money'
        roundId:
          type: string
          pattern: '^crashround_[A-Za-z0-9._:-]{8,128}$'
        schemaVersion:
          type: string
          const: dailydraft.crash-player-decision.v1
        settlement:
          type: object
          additionalProperties: false
          required:
            - finalizedOperationCount
            - receiptHash
            - recoveryReason
            - status
          properties:
            finalizedOperationCount:
              type: integer
              minimum: 0
            receiptHash:
              type: [string, 'null']
              pattern: '^[a-f0-9]{64}$'
            recoveryReason:
              type: [string, 'null']
              maxLength: 480
            status:
              type: string
              enum: [not-required, pending, recovery-required, settled]
        stage:
          type: integer
          minimum: 1
        status:
          type: string
          enum: [active, busted, cashed-out, completed, defaulted]
        terminalReason:
          type: [string, 'null']
        version:
          type: integer
          minimum: 1
    CrashHistoryPage:
      type: object
      additionalProperties: false
      required: [data, hasMore, nextCursor, schemaVersion]
      properties:
        data:
          type: array
          maxItems: 50
          items:
            $ref: '#/components/schemas/CrashHistoryItem'
        hasMore:
          type: boolean
        nextCursor:
          type: [string, 'null']
          pattern: '^v1\.[A-Za-z0-9_-]{1,480}$'
        schemaVersion:
          type: string
          const: dailydraft.crash-history.v1
    CrashHistoryItem:
      type: object
      additionalProperties: false
      required:
        - createdAt
        - currentStage
        - decisionDeadline
        - gameState
        - pot
        - receiptHref
        - resolution
        - roundId
        - safeNextAction
        - settlement
        - terminalReason
        - updatedAt
      properties:
        createdAt:
          type: string
          format: date-time
        currentStage:
          type: integer
          minimum: 1
        decisionDeadline:
          type: [string, 'null']
          format: date-time
        gameState:
          type: object
          additionalProperties: false
          required: [committed, status, version]
          properties:
            committed:
              type: boolean
              const: true
            status:
              $ref: '#/components/schemas/CrashStatus'
            version:
              type: integer
              minimum: 1
        pot:
          $ref: '#/components/schemas/Money'
        receiptHref:
          type: string
          pattern: '^/v1/crash/rounds/crashround_[A-Za-z0-9._:-]{8,128}/receipt$'
        resolution:
          $ref: '#/components/schemas/CrashResolutionStatus'
        roundId:
          type: string
          pattern: '^crashround_[A-Za-z0-9._:-]{8,128}$'
        safeNextAction:
          $ref: '#/components/schemas/CrashSafeNextAction'
        settlement:
          type: object
          additionalProperties: false
          required: [finalizedOperationCount, receiptHash, status]
          properties:
            finalizedOperationCount:
              type: integer
              minimum: 0
            receiptHash:
              type: [string, 'null']
              pattern: '^[a-f0-9]{64}$'
            status:
              $ref: '#/components/schemas/CrashSettlementStatus'
        terminalReason:
          type: [string, 'null']
          maxLength: 480
        updatedAt:
          type: string
          format: date-time
    CrashReceipt:
      type: object
      additionalProperties: false
      required:
        - bindings
        - createdAt
        - custody
        - decisionDeadline
        - events
        - finality
        - mode
        - network
        - pot
        - privacy
        - resolution
        - roundId
        - safeNextAction
        - schemaVersion
        - settlement
        - stage
        - status
        - terminalAt
        - terminalReason
        - updatedAt
        - version
      properties:
        bindings:
          type: object
          additionalProperties: false
          required:
            - architectureVersion
            - calculatorVersion
            - custodyPolicyHash
            - custodyPolicyVersion
            - inventoryPolicyHash
            - inventoryPolicyVersion
            - riskRulesHash
            - riskRulesVersion
            - rulesHash
            - rulesVersion
            - settlementPolicyHash
            - settlementPolicyVersion
            - stateMachineRulesHash
            - stateMachineVersion
          properties:
            architectureVersion:
              type: string
            calculatorVersion:
              type: string
            custodyPolicyHash:
              type: [string, 'null']
              pattern: '^[a-f0-9]{64}$'
            custodyPolicyVersion:
              type: [string, 'null']
            inventoryPolicyHash:
              type: [string, 'null']
              pattern: '^[a-f0-9]{64}$'
            inventoryPolicyVersion:
              type: [string, 'null']
            riskRulesHash:
              type: string
              pattern: '^[a-f0-9]{64}$'
            riskRulesVersion:
              type: string
            rulesHash:
              type: string
              pattern: '^[a-f0-9]{64}$'
            rulesVersion:
              type: string
            settlementPolicyHash:
              type: [string, 'null']
              pattern: '^[a-f0-9]{64}$'
            settlementPolicyVersion:
              type: [string, 'null']
            stateMachineRulesHash:
              type: string
              pattern: '^[a-f0-9]{64}$'
            stateMachineVersion:
              type: string
        createdAt:
          type: string
          format: date-time
        decisionDeadline:
          type: [string, 'null']
          format: date-time
        custody:
          type: object
          additionalProperties: false
          required: [preparedIntentCount, recoveryRequiredIntentCount, status]
          properties:
            preparedIntentCount:
              type: integer
              minimum: 0
            recoveryRequiredIntentCount:
              type: integer
              minimum: 0
            status:
              type: string
              enum: [not-started, prepared, recovery-required]
        events:
          type: array
          maxItems: 500
          description: |
            Ordered by occurredAt ascending. At equal timestamps, transition
            events come first by numeric sequence, custody events next by event
            ID, and settlement events last by numeric sequence.
          items:
            $ref: '#/components/schemas/CrashReceiptEvent'
        finality:
          type: object
          additionalProperties: false
          required: [custody, gameState, settlement]
          properties:
            custody:
              type: string
              enum: [not-final, recovery-required, settled]
            gameState:
              type: string
              const: committed
            settlement:
              $ref: '#/components/schemas/CrashSettlementStatus'
        mode:
          type: string
          const: fixture-preview
        network:
          type: string
          const: solana-devnet
        pot:
          $ref: '#/components/schemas/Money'
        privacy:
          type: object
          additionalProperties: false
          required: [exposesProviderSignatures, exposesWalletAddresses]
          properties:
            exposesProviderSignatures:
              type: boolean
              const: false
            exposesWalletAddresses:
              type: boolean
              const: false
        resolution:
          $ref: '#/components/schemas/CrashResolutionStatus'
        roundId:
          type: string
          pattern: '^crashround_[A-Za-z0-9._:-]{8,128}$'
        safeNextAction:
          $ref: '#/components/schemas/CrashSafeNextAction'
        schemaVersion:
          type: string
          const: dailydraft.crash-receipt.v1
        settlement:
          type: object
          additionalProperties: false
          required:
            - expectedOperationCount
            - finalizedOperationCount
            - receiptHash
            - recoveryReason
            - status
          properties:
            expectedOperationCount:
              type: integer
              minimum: 0
            finalizedOperationCount:
              type: integer
              minimum: 0
            receiptHash:
              type: [string, 'null']
              pattern: '^[a-f0-9]{64}$'
            recoveryReason:
              type: [string, 'null']
              maxLength: 480
            status:
              $ref: '#/components/schemas/CrashSettlementStatus'
        stage:
          type: integer
          minimum: 1
        status:
          $ref: '#/components/schemas/CrashStatus'
        terminalAt:
          type: [string, 'null']
          format: date-time
        terminalReason:
          type: [string, 'null']
          maxLength: 480
        updatedAt:
          type: string
          format: date-time
        version:
          type: integer
          minimum: 1
    CrashReceiptEvent:
      type: object
      additionalProperties: false
      required:
        - amount
        - decision
        - eventId
        - kind
        - occurredAt
        - reference
        - scheduledDeadline
        - stage
        - terminalReason
      properties:
        amount:
          oneOf:
            - $ref: '#/components/schemas/Money'
            - type: 'null'
        decision:
          type: [string, 'null']
          enum: [cash-out, continue, forfeit, null]
        eventId:
          type: string
          maxLength: 180
          pattern: '^(?:(?:transition|settlement):[1-9][0-9]{0,9}|custody:crashref_[a-f0-9]{32})$'
        kind:
          type: string
          enum:
            - custody-prepared
            - custody-recovery-required
            - deadline-defaulted
            - round-busted
            - round-cashed-out
            - round-completed
            - round-started
            - settlement-finalized
            - settlement-prepared
            - settlement-recovery-required
            - stage-continued
        occurredAt:
          type: string
          format: date-time
        reference:
          type: string
          maxLength: 240
          pattern: '^crashref_[a-f0-9]{32}$'
        scheduledDeadline:
          type: [string, 'null']
          format: date-time
        stage:
          type: integer
          minimum: 1
        terminalReason:
          type: [string, 'null']
          maxLength: 480
    CrashSafeNextAction:
      type: string
      enum:
        - choose-action
        - reconnect
        - retry-settlement
        - review-receipt
        - wait-for-settlement
    CrashResolutionStatus:
      type: string
      enum: [active, bust, cash-out, disputed, failed, recovering, refunded, timed-out]
    CrashSettlementStatus:
      type: string
      enum: [not-required, pending, recovery-required, settled]
    CrashStatus:
      type: string
      enum: [active, busted, cashed-out, completed, defaulted]
    RgsHash:
      type: string
      pattern: '^[a-f0-9]{64}$'
    RgsMode:
      type: string
      enum: [crash, duel, flip, gacha]
    RgsModeConfig:
      type: object
      additionalProperties: false
      required:
        - activation
        - calculatorVersion
        - configHash
        - contractVersion
        - mode
        - proofKind
        - realValueGate
        - rulesHash
        - schemaVersion
      properties:
        activation:
          type: string
          enum: [devnet, disabled, fixture-only]
        calculatorVersion:
          type: string
          pattern: '^dailydraft\.[a-z0-9-]+\.v[1-9][0-9]*$'
        configHash:
          $ref: '#/components/schemas/RgsHash'
        contractVersion:
          type: string
          const: dailydraft.rgs-contract.v1
        mode:
          $ref: '#/components/schemas/RgsMode'
        proofKind:
          type: string
          enum: [external-provider, seeded-sha256]
        realValueGate:
          type: string
          const: hitl-required
        rulesHash:
          $ref: '#/components/schemas/RgsHash'
        schemaVersion:
          type: string
          const: dailydraft.rgs-mode-config.v1
    RgsModeList:
      type: object
      additionalProperties: false
      required: [modes, schemaVersion]
      properties:
        modes:
          type: array
          minItems: 4
          maxItems: 4
          items:
            $ref: '#/components/schemas/RgsModeConfig'
        schemaVersion:
          type: string
          const: dailydraft.rgs-mode-config.v1
    RgsSeededProof:
      type: object
      additionalProperties: false
      required:
        - clientSeed
        - commitmentHash
        - commitmentId
        - configHash
        - contractVersion
        - entropyHash
        - mode
        - phase
        - proofKind
        - result
        - resultHash
        - roundId
        - rulesHash
        - schemaVersion
        - serverSeed
        - serverSeedHash
      properties:
        clientSeed: { type: string, minLength: 1, maxLength: 240 }
        commitmentHash: { $ref: '#/components/schemas/RgsHash' }
        commitmentId: { type: string, minLength: 1, maxLength: 240 }
        configHash: { $ref: '#/components/schemas/RgsHash' }
        contractVersion: { type: string, const: dailydraft.rgs-contract.v1 }
        entropyHash: { $ref: '#/components/schemas/RgsHash' }
        mode: { $ref: '#/components/schemas/RgsMode' }
        phase: { type: string, enum: [revealed, settled] }
        proofKind: { type: string, const: seeded-sha256 }
        result: {}
        resultHash: { $ref: '#/components/schemas/RgsHash' }
        roundId: { type: string, minLength: 1, maxLength: 240 }
        rulesHash: { $ref: '#/components/schemas/RgsHash' }
        schemaVersion: { type: string, const: dailydraft.rgs-proof.v1 }
        serverSeed: { type: string, minLength: 1, maxLength: 240 }
        serverSeedHash: { $ref: '#/components/schemas/RgsHash' }
    RgsExternalProof:
      type: object
      additionalProperties: false
      required:
        - commitmentHash
        - configHash
        - contractVersion
        - evidence
        - evidenceHash
        - mode
        - phase
        - proofKind
        - request
        - requestHash
        - result
        - resultHash
        - roundId
        - rulesHash
        - schemaVersion
      properties:
        commitmentHash: { $ref: '#/components/schemas/RgsHash' }
        configHash: { $ref: '#/components/schemas/RgsHash' }
        contractVersion: { type: string, const: dailydraft.rgs-contract.v1 }
        evidence: {}
        evidenceHash: { $ref: '#/components/schemas/RgsHash' }
        mode: { type: string, const: duel }
        phase: { type: string, enum: [revealed, settled] }
        proofKind: { type: string, const: external-provider }
        request: {}
        requestHash: { $ref: '#/components/schemas/RgsHash' }
        result: {}
        resultHash: { $ref: '#/components/schemas/RgsHash' }
        roundId: { type: string, minLength: 1, maxLength: 240 }
        rulesHash: { $ref: '#/components/schemas/RgsHash' }
        schemaVersion: { type: string, const: dailydraft.rgs-proof.v1 }
    RgsProof:
      oneOf:
        - $ref: '#/components/schemas/RgsSeededProof'
        - $ref: '#/components/schemas/RgsExternalProof'
    AdminPage:
      type: object
      additionalProperties: false
      required: [data, hasMore, nextCursor]
      properties:
        data:
          type: array
          items:
            type: object
            additionalProperties: true
        hasMore:
          type: boolean
        nextCursor:
          type: [string, 'null']
    OperatorReasonCode:
      type: string
      enum:
        - maintenance
        - provider_degraded
        - rpc_degraded
        - treasury_limit
        - security_incident
        - manual_review
    EmergencyPauseRequest:
      type: object
      additionalProperties: false
      required: [paused, reasonCode]
      properties:
        paused:
          type: boolean
        reasonCode:
          $ref: '#/components/schemas/OperatorReasonCode'
    EmergencyPauseState:
      type: object
      additionalProperties: false
      required: [paused, reasonCode, updatedAt, version]
      properties:
        paused:
          type: boolean
        reasonCode:
          oneOf:
            - $ref: '#/components/schemas/OperatorReasonCode'
            - type: 'null'
        updatedAt:
          type: [string, 'null']
          format: date-time
        version:
          type: integer
          minimum: 0
    ClientProductEventName:
      type: string
      enum:
        - lobby_viewed
        - tier_selected
        - wallet_connected
        - wallet_authenticated
        - pack_reveal_started
        - duel_shared
        - duel_rematched
        - ui_error
    ProductEventInput:
      type: object
      additionalProperties: false
      required: [name]
      properties:
        name:
          $ref: '#/components/schemas/ClientProductEventName'
        duelId:
          type: string
          pattern: '^duel_[A-Za-z0-9]{12,64}$'
        status:
          $ref: '#/components/schemas/DuelStatus'
        tier:
          type: integer
          enum: [25, 50, 100]
        mode:
          $ref: '#/components/schemas/MatchmakingMode'
    IngestProductEventsRequest:
      type: object
      additionalProperties: false
      required: [sessionId, events]
      properties:
        sessionId:
          type: string
          pattern: '^anon_[a-f0-9]{32}$'
          description: Random per-tab correlation ID. It is not derived from a wallet.
        events:
          type: array
          minItems: 1
          maxItems: 20
          items:
            $ref: '#/components/schemas/ProductEventInput'
    AnalyticsFunnelReport:
      type: object
      additionalProperties: false
      required: [generatedAt, window, sampled, funnel, rates, latencyMs, errors, experience, alerts, statusCounts]
      properties:
        generatedAt:
          type: string
          format: date-time
        window:
          type: object
          additionalProperties: true
        sampled:
          type: boolean
          description: True when the bounded 10,000-event reporting window was truncated.
        funnel:
          type: object
          additionalProperties: true
        rates:
          type: object
          additionalProperties:
            type: number
        latencyMs:
          type: object
          additionalProperties: true
        errors:
          type: object
          additionalProperties:
            type: integer
          description: Server-authored provider, settlement, and Solana RPC failures only.
        experience:
          type: object
          additionalProperties:
            type: integer
          description: Client UI errors and reveal-animation starts; never operational alerts.
        alerts:
          type: object
          additionalProperties: true
        statusCounts:
          type: object
          additionalProperties:
            type: integer
    CreateWalletChallengeRequest:
      type: object
      additionalProperties: false
      required: [wallet]
      properties:
        wallet:
          $ref: '#/components/schemas/SolanaAddress'
    WalletChallenge:
      type: object
      additionalProperties: false
      required: [challengeId, wallet, domain, uri, chain, message, expiresAt]
      properties:
        challengeId:
          type: string
          pattern: '^authc_[a-f0-9]{32}$'
        wallet:
          $ref: '#/components/schemas/SolanaAddress'
        domain:
          type: string
        uri:
          type: string
          format: uri
        chain:
          type: string
          const: solana:devnet
        message:
          type: string
          description: Exact UTF-8 message the wallet must display and sign.
        expiresAt:
          type: string
          format: date-time
    CreateWalletSessionRequest:
      type: object
      additionalProperties: false
      required: [challengeId, wallet, signature]
      properties:
        challengeId:
          type: string
          pattern: '^authc_[a-f0-9]{32}$'
        wallet:
          $ref: '#/components/schemas/SolanaAddress'
        signature:
          type: string
          contentEncoding: base64
    WalletSession:
      type: object
      additionalProperties: false
      required: [token, wallet, network, expiresAt]
      properties:
        token:
          type: string
          description: Opaque bearer token returned once; only its hash is stored server-side.
        wallet:
          $ref: '#/components/schemas/SolanaAddress'
        network:
          type: string
          const: solana-devnet
        expiresAt:
          type: string
          format: date-time
    Health:
      type: object
      additionalProperties: false
      required: [service, status, version, dependencies]
      properties:
        service:
          type: string
          const: dailydraft-api
        status:
          type: string
          const: ok
        version:
          type: string
          const: 0.5.0-devnet
        dependencies:
          type: object
          additionalProperties: false
          required: [database]
          properties:
            database:
              type: string
              const: ok
    CapabilityAvailability:
      type: object
      additionalProperties: false
      required: [enabled, reason]
      properties:
        enabled:
          type: boolean
        reason:
          type: [string, 'null']
    PackCapability:
      type: object
      additionalProperties: false
      required: [enabled, id, name, reason, tier]
      properties:
        enabled:
          type: boolean
        id:
          $ref: '#/components/schemas/PackId'
        name:
          type: string
        reason:
          type: [string, 'null']
        tier:
          type: integer
          enum: [25, 50, 100]
    ProductCapabilities:
      type: object
      additionalProperties: false
      required: [modes, network, packs, provider]
      properties:
        modes:
          type: object
          additionalProperties: false
          required: [direct, house, open]
          properties:
            direct:
              $ref: '#/components/schemas/CapabilityAvailability'
            house:
              $ref: '#/components/schemas/CapabilityAvailability'
            open:
              $ref: '#/components/schemas/CapabilityAvailability'
        network:
          type: string
          const: solana-devnet
        packs:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/PackCapability'
        provider:
          type: object
          additionalProperties: false
          required: [mode, ready]
          properties:
            mode:
              type: string
            ready:
              type: boolean
    GameCatalogAction:
      type: object
      additionalProperties: false
      required: [href, id, label]
      properties:
        href:
          type: string
          pattern: '^/games/[a-z0-9-]+$'
        id:
          type: string
          minLength: 1
        label:
          type: string
          minLength: 1
    GameCapabilitySource:
      type: object
      additionalProperties: false
      required: [kind, name, status]
      properties:
        kind:
          type: string
          enum: [fixture, runtime]
        name:
          type: string
          enum: [duel-readiness, gacha-capability, rgs-fixture]
        status:
          type: string
          enum: [degraded, gated, verified]
    GameCatalogMode:
      type: object
      additionalProperties: false
      required:
        - availableActions
        - capabilitySource
        - description
        - id
        - name
        - reason
        - state
      properties:
        availableActions:
          type: array
          items:
            $ref: '#/components/schemas/GameCatalogAction'
        capabilitySource:
          $ref: '#/components/schemas/GameCapabilitySource'
        description:
          type: string
          minLength: 1
        id:
          type: string
          enum: [duel, gacha, flip, crash]
        name:
          type: string
          minLength: 1
        reason:
          type: string
          minLength: 1
        state:
          type: string
          enum: [degraded, playable, preview, unavailable]
    GameCatalog:
      type: object
      additionalProperties: false
      required: [asOf, modes, network, schemaVersion]
      properties:
        asOf:
          type: string
          format: date-time
        modes:
          type: array
          minItems: 4
          maxItems: 4
          items:
            $ref: '#/components/schemas/GameCatalogMode'
        network:
          type: string
          const: solana-devnet
        schemaVersion:
          type: string
          const: dailydraft.game-catalog.v1
    PublicGameModeId:
      type: string
      enum: [duel, gacha, flip, crash]
    PublicGameAvailabilityMode:
      type: object
      additionalProperties: false
      required: [asOf, availableActions, capabilitySource, id, reason, state]
      properties:
        asOf:
          type: string
          format: date-time
        availableActions:
          type: array
          items:
            $ref: '#/components/schemas/GameCatalogAction'
        capabilitySource:
          $ref: '#/components/schemas/GameCapabilitySource'
        id:
          $ref: '#/components/schemas/PublicGameModeId'
        reason:
          type: string
          minLength: 1
        state:
          type: string
          enum: [degraded, playable, preview, unavailable]
    PublicGameAvailability:
      type: object
      additionalProperties: false
      required: [asOf, modes, network, schemaVersion]
      properties:
        asOf:
          type: string
          format: date-time
        modes:
          type: array
          minItems: 4
          maxItems: 4
          items:
            $ref: '#/components/schemas/PublicGameAvailabilityMode'
        network:
          type: string
          const: solana-devnet
        schemaVersion:
          type: string
          const: dailydraft.game-availability.v1
    VerifiedGameActivity:
      type: object
      additionalProperties: false
      required:
        - activityId
        - mode
        - occurredAt
        - participants
        - receiptHref
        - result
        - resultHref
        - resultSummary
        - tier
        - title
        - verification
      properties:
        activityId:
          type: string
          pattern: '^(duel|gacha|flip|crash):[A-Za-z0-9][A-Za-z0-9._:-]{0,239}$'
        mode:
          $ref: '#/components/schemas/PublicGameModeId'
        occurredAt:
          type: string
          format: date-time
        participants:
          type: array
          minItems: 1
          maxItems: 2
          items:
            type: object
            additionalProperties: false
            required: [label, role]
            properties:
              label:
                type: string
                minLength: 1
                maxLength: 32
              role:
                type: string
                enum: [house, player]
        receiptHref:
          type: string
          pattern: '^/v1/[A-Za-z0-9/_:.-]+$'
        result:
          type: string
          description: Extensible mode-specific outcome token, such as acquired, bust, cashed-out, completed, tie, or winner-verified.
          pattern: '^[a-z][a-z0-9-]{1,63}$'
        resultHref:
          type: string
          pattern: '^/v1/rgs/rounds/(duel|gacha|flip|crash)/[A-Za-z0-9][A-Za-z0-9._:-]{0,239}/proof$'
        resultSummary:
          type: string
          minLength: 1
        tier:
          $ref: '#/components/schemas/Money'
        title:
          type: string
          minLength: 1
        verification:
          type: string
          const: settled-rgs-proof
    VerifiedGameActivityPage:
      type: object
      additionalProperties: false
      required: [asOf, data, hasMore, nextCursor, schemaVersion]
      properties:
        asOf:
          type: string
          format: date-time
        data:
          type: array
          maxItems: 50
          items:
            $ref: '#/components/schemas/VerifiedGameActivity'
        hasMore:
          type: boolean
        nextCursor:
          type: [string, 'null']
          maxLength: 483
          pattern: '^v1\.[A-Za-z0-9_-]{1,480}$'
        schemaVersion:
          type: string
          const: dailydraft.verified-game-activity.v1
    GachaMachineKey:
      type: string
      pattern: '^[a-z0-9][a-z0-9._:-]{0,127}$'
    GachaCapabilityGates:
      type: object
      additionalProperties: false
      required: [acquisition, odds, provider, settlement]
      properties:
        acquisition:
          type: boolean
        odds:
          type: boolean
        provider:
          type: boolean
        settlement:
          type: boolean
    GachaCapability:
      type: object
      additionalProperties: false
      required: [availability, gates, providerMode, reason]
      properties:
        availability:
          type: string
          enum: [playable, preview]
        gates:
          $ref: '#/components/schemas/GachaCapabilityGates'
        providerMode:
          type: string
          enum: [collector-crypt, fixture]
        reason:
          type: string
    GachaMachine:
      type: object
      additionalProperties: false
      required:
        - id
        - machineKey
        - sport
        - tierPriceMinor
        - tierPriceCurrency
        - tierPriceDecimals
        - displayName
        - committedPoolSize
        - active
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
        machineKey:
          $ref: '#/components/schemas/GachaMachineKey'
        sport:
          type: string
          enum: [FOOTBALL, SOCCER, BASEBALL, BASKETBALL]
        tierPriceMinor:
          type: string
          pattern: '^[0-9]+$'
        tierPriceCurrency:
          type: string
          const: USDC
        tierPriceDecimals:
          type: integer
          const: 6
        displayName:
          type: string
        committedPoolSize:
          type: integer
          minimum: 0
        active:
          type: boolean
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    GachaInventorySnapshotEntry:
      type: object
      additionalProperties: false
      required:
        - id
        - snapshotId
        - ordinal
        - eligible
        - exclusionReasons
        - providerCardReference
        - assetReference
        - displayName
        - sport
        - graded
        - graderReference
        - vaulted
        - poolOpen
        - tierEnabled
        - insuredValueMinor
        - insuredValueCurrency
        - insuredValueDecimals
        - insuredValueProviderReference
        - valuationSourceReference
        - valuationTimestamp
        - inventorySourceTimestamp
      properties:
        id:
          type: string
        snapshotId:
          type: string
        ordinal:
          type: integer
          minimum: 0
        eligible:
          type: boolean
        exclusionReasons:
          type: array
          items:
            type: string
            enum:
              - MISSING_INSURED_VALUE
              - MISSING_ASSET_REFERENCE
              - MISSING_VALUATION_SOURCE
              - STALE_VALUATION
              - FUTURE_VALUATION
              - POOL_CLOSED
              - TIER_DISABLED
              - NOT_VAULTED
              - UNGRADED
              - SPORT_MISMATCH
              - DUPLICATE_ASSET
              - MAXIMUM_ITEM_COUNT
        providerCardReference:
          type: string
        assetReference:
          type: [string, 'null']
        displayName:
          type: string
        sport:
          type: string
          enum: [FOOTBALL, SOCCER, BASEBALL, BASKETBALL]
        graded:
          type: boolean
        graderReference:
          type: [string, 'null']
        vaulted:
          type: boolean
        poolOpen:
          type: boolean
        tierEnabled:
          type: boolean
        insuredValueMinor:
          type: [string, 'null']
          pattern: '^[0-9]+$'
        insuredValueCurrency:
          type: [string, 'null']
        insuredValueDecimals:
          type: [integer, 'null']
        insuredValueProviderReference:
          type: [string, 'null']
        valuationSourceReference:
          type: [string, 'null']
        valuationTimestamp:
          type: [string, 'null']
          format: date-time
        inventorySourceTimestamp:
          type: string
          format: date-time
    GachaInventorySnapshot:
      type: object
      additionalProperties: false
      required:
        - id
        - poolKey
        - machineKey
        - revision
        - schemaVersion
        - policyVersion
        - provider
        - policyHash
        - contentHash
        - policy
        - committedPoolSize
        - maximumEligibleItems
        - maximumSourceAgeMs
        - maximumFutureSkewMs
        - eligibleCount
        - excludedCount
        - eligibleValueMinor
        - evaluatedAt
        - sealedAt
        - createdAt
        - machine
        - entries
      properties:
        id:
          type: string
        poolKey:
          type: string
        machineKey:
          $ref: '#/components/schemas/GachaMachineKey'
        revision:
          type: integer
          minimum: 1
        schemaVersion:
          type: string
        policyVersion:
          type: string
        provider:
          type: string
        policyHash:
          type: string
          pattern: '^[a-f0-9]{64}$'
        contentHash:
          type: string
          pattern: '^[a-f0-9]{64}$'
        policy:
          type: object
          additionalProperties: true
        committedPoolSize:
          type: integer
          minimum: 0
        maximumEligibleItems:
          type: integer
          minimum: 1
        maximumSourceAgeMs:
          type: integer
          minimum: 0
        maximumFutureSkewMs:
          type: integer
          minimum: 0
        eligibleCount:
          type: integer
          minimum: 0
        excludedCount:
          type: integer
          minimum: 0
        eligibleValueMinor:
          type: string
          pattern: '^[0-9]+$'
        evaluatedAt:
          type: string
          format: date-time
        sealedAt:
          type: [string, 'null']
          format: date-time
        createdAt:
          type: string
          format: date-time
        machine:
          $ref: '#/components/schemas/GachaMachine'
        entries:
          type: array
          items:
            $ref: '#/components/schemas/GachaInventorySnapshotEntry'
    GachaPullOddsCommitment:
      type: object
      additionalProperties: false
      required:
        - id
        - machineKey
        - oddsKey
        - version
        - schemaVersion
        - calculatorVersion
        - rulesHash
        - snapshotContentHash
        - probabilityScalePpm
        - baseProbabilityPpm
        - plusProbabilityPpm
        - premiumProbabilityPpm
        - chaseProbabilityPpm
        - bandMinimums
        - committedAt
        - sealedAt
        - createdAt
      properties:
        id:
          type: string
        machineKey:
          $ref: '#/components/schemas/GachaMachineKey'
        oddsKey:
          type: string
        version:
          type: integer
          minimum: 1
        schemaVersion:
          type: string
        calculatorVersion:
          type: string
        rulesHash:
          type: string
          pattern: '^[a-f0-9]{64}$'
        snapshotContentHash:
          type: string
          pattern: '^[a-f0-9]{64}$'
        probabilityScalePpm:
          type: integer
          const: 1000000
        baseProbabilityPpm:
          type: integer
          minimum: 0
          maximum: 1000000
        plusProbabilityPpm:
          type: integer
          minimum: 0
          maximum: 1000000
        premiumProbabilityPpm:
          type: integer
          minimum: 0
          maximum: 1000000
        chaseProbabilityPpm:
          type: integer
          minimum: 0
          maximum: 1000000
        bandMinimums:
          type: object
          additionalProperties:
            type: string
            pattern: '^[0-9]+$'
        committedAt:
          type: string
          format: date-time
        sealedAt:
          type: [string, 'null']
          format: date-time
        createdAt:
          type: string
          format: date-time
    GachaRipSeedCommitment:
      type: object
      additionalProperties: false
      required:
        - commitmentId
        - configHash
        - contractVersion
        - expiresAt
        - proofKind
        - rgsCommitmentHash
        - rulesHash
        - serverSeedHash
      properties:
        commitmentId:
          type: string
          minLength: 1
          maxLength: 240
        configHash:
          $ref: '#/components/schemas/RgsHash'
        contractVersion:
          type: string
          const: dailydraft.rgs-contract.v1
        proofKind:
          type: string
          const: seeded-sha256
        rgsCommitmentHash:
          $ref: '#/components/schemas/RgsHash'
        rulesHash:
          $ref: '#/components/schemas/RgsHash'
        serverSeedHash:
          $ref: '#/components/schemas/RgsHash'
        expiresAt:
          type: string
          format: date-time
    GachaPaymentIntentId:
      type: string
      pattern: '^gachapay_[a-f0-9]{32}$'
      examples: [gachapay_7f3c1d9e4b2a48c6a0e5f81b2c3d4e5f]
    CreateGachaPaymentIntentRequest:
      type: object
      additionalProperties: false
      required: [payerWallet]
      properties:
        payerWallet:
          $ref: '#/components/schemas/SolanaAddress'
    GachaPaymentIntent:
      type: object
      additionalProperties: false
      required:
        - intentId
        - machineKey
        - payerWallet
        - mint
        - destinationTokenAccount
        - amountMinor
        - amountDecimals
        - amountCurrency
        - memoNonce
        - expiresAt
        - resumed
        - signature
        - status
      properties:
        intentId:
          $ref: '#/components/schemas/GachaPaymentIntentId'
        machineKey:
          $ref: '#/components/schemas/GachaMachineKey'
        payerWallet:
          $ref: '#/components/schemas/SolanaAddress'
        mint:
          $ref: '#/components/schemas/SolanaAddress'
        destinationTokenAccount:
          $ref: '#/components/schemas/SolanaAddress'
        amountMinor:
          type: string
          pattern: '^[0-9]{1,20}$'
          description: Exact deposit amount in minor units; a string so u64 survives JSON.
        amountDecimals:
          type: integer
          minimum: 0
          maximum: 18
        amountCurrency:
          type: string
          minLength: 1
          maxLength: 16
        memoNonce:
          allOf:
            - $ref: '#/components/schemas/GachaPaymentIntentId'
          description: |
            Must be the entire payload of an SPL Memo instruction in the funding
            transaction. This is the only thing binding a transfer to this intent.
        expiresAt:
          type: string
          format: date-time
        resumed:
          type: boolean
          description: True when the request returned an existing unresolved intent.
        signature:
          type: [string, 'null']
          pattern: '^[1-9A-HJ-NP-Za-km-z]{64,96}$'
          description: The first pre-broadcast signature claimed for this intent.
        status:
          type: string
          enum: [PENDING, VERIFIED]
    PreparedGachaPaymentTransaction:
      type: object
      additionalProperties: false
      required:
        - intentId
        - memoNonce
        - amountMinor
        - sourceTokenAccount
        - recentBlockhash
        - lastValidBlockHeight
        - expiresAt
        - expectedMessageHash
        - serializedTransactionBase64
      properties:
        intentId:
          $ref: '#/components/schemas/GachaPaymentIntentId'
        memoNonce:
          allOf:
            - $ref: '#/components/schemas/GachaPaymentIntentId'
          description: Echoed from the intent; already encoded into the memo instruction.
        amountMinor:
          type: string
          pattern: '^[0-9]{1,20}$'
        sourceTokenAccount:
          allOf:
            - $ref: '#/components/schemas/SolanaAddress'
          description: |
            The payer's associated token account for the mint, which the transfer
            debits. No account-creation instruction is included: a payer with no
            token account has no balance to send.
        recentBlockhash:
          type: string
        lastValidBlockHeight:
          type: string
          pattern: '^\d+$'
        expiresAt:
          type: string
          format: date-time
          description: |
            The earlier of the intent's own deadline and the blockhash review
            window, so the deadline shown to a player is the one that binds.
        expectedMessageHash:
          type: string
          pattern: '^[a-f0-9]{64}$'
          description: |
            sha256 of the compiled message. The wallet recomputes it before
            signing to prove the bytes were not altered in transit.
        serializedTransactionBase64:
          type: string
          contentEncoding: base64
    ClaimGachaPaymentSignatureRequest:
      type: object
      additionalProperties: false
      required: [signedTransactionBase64]
      properties:
        signedTransactionBase64:
          type: string
          contentEncoding: base64
          maxLength: 4096
          description: Fully signed serialized transaction returned by the payer wallet.
    VerifyGachaPaymentRequest:
      type: object
      additionalProperties: false
      required: [signature]
      properties:
        signature:
          type: string
          pattern: '^[1-9A-HJ-NP-Za-km-z]{64,96}$'
    VerifiedGachaPayment:
      type: object
      additionalProperties: false
      required: [intentId, signature, amountMinor, mintVerifiedOnChain, verifiedAt]
      properties:
        intentId:
          $ref: '#/components/schemas/GachaPaymentIntentId'
        signature:
          type: string
          pattern: '^[1-9A-HJ-NP-Za-km-z]{64,96}$'
        amountMinor:
          type: string
          pattern: '^[0-9]{1,20}$'
        mintVerifiedOnChain:
          type: boolean
          description: |
            False when the transfer used the unchecked SPL `transfer` instruction,
            which carries no mint account: the deposit is still bound to the
            destination token account, but the mint was not asserted by the chain.
        verifiedAt:
          type: string
          format: date-time
    CreateFixtureGachaRipRequest:
      type: object
      additionalProperties: false
      required: [machineKey, recipientWallet, seed, commitmentId]
      properties:
        machineKey:
          $ref: '#/components/schemas/GachaMachineKey'
        recipientWallet:
          type: string
          minLength: 1
          maxLength: 240
        seed:
          type: string
          minLength: 16
          maxLength: 240
        commitmentId:
          type: string
          minLength: 1
          maxLength: 240
        idempotencyKey:
          type: string
          minLength: 1
          maxLength: 240
        paymentIntentId:
          allOf:
            - $ref: '#/components/schemas/GachaPaymentIntentId'
          description: |
            A verified payment intent to spend on this rip. Required in devnet
            preview mode and rejected as missing there; ignored in fixture mode,
            where rips are unfunded.
        oddsVersion:
          type: integer
          minimum: 1
          maximum: 2147483647
          default: 1
    GachaRip:
      type: object
      additionalProperties: false
      required:
        - id
        - machineKey
        - oddsCommitmentId
        - status
        - snapshotContentHash
        - oddsRulesHash
        - seedCommitmentHash
        - selectedAssetReference
        - failedAssetReference
        - acquisitionReference
        - settlementReference
        - insuredValueMinor
        - insuredValueCurrency
        - insuredValueDecimals
        - rarity
        - idempotencyKey
        - selectedAt
        - revealedAt
        - acquiredAt
        - settledAt
        - failedAt
        - failureReason
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
        machineKey:
          $ref: '#/components/schemas/GachaMachineKey'
        oddsCommitmentId:
          type: string
        status:
          type: string
          enum: [SELECTED, REVEALED, ACQUIRED, SETTLED, FAILED]
        snapshotContentHash:
          type: string
          pattern: '^[a-f0-9]{64}$'
        oddsRulesHash:
          type: string
          pattern: '^[a-f0-9]{64}$'
        seedCommitmentHash:
          type: string
          pattern: '^[a-f0-9]{64}$'
        selectedAssetReference:
          description: |
            The asset this rip claimed from the sealed snapshot. Null once the rip
            has FAILED: a failed acquisition never delivered the asset, so it is
            released back to the eligible pool for the next rip against the same
            snapshot rather than being permanently unavailable.
          type: [string, 'null']
        failedAssetReference:
          description: |
            Audit trail of the asset a FAILED rip had selected before it was
            released back to the eligible pool. Null for every other status.
          type: [string, 'null']
        acquisitionReference:
          type: [string, 'null']
        settlementReference:
          type: [string, 'null']
        insuredValueMinor:
          type: string
          pattern: '^[0-9]+$'
        insuredValueCurrency:
          type: string
          const: USDC
        insuredValueDecimals:
          type: integer
          const: 6
        rarity:
          $ref: '#/components/schemas/PullRarity'
        idempotencyKey:
          type: [string, 'null']
          minLength: 1
          maxLength: 240
        selectedAt:
          type: string
          format: date-time
        revealedAt:
          type: [string, 'null']
          format: date-time
        acquiredAt:
          type: [string, 'null']
          format: date-time
        settledAt:
          type: [string, 'null']
          format: date-time
        failedAt:
          type: [string, 'null']
          format: date-time
        failureReason:
          type: [string, 'null']
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    GachaRipOddsCommitment:
      type: object
      additionalProperties: false
      required:
        - calculatorVersion
        - committedAt
        - oddsKey
        - rulesHash
        - schemaVersion
        - snapshotContentHash
        - version
      properties:
        calculatorVersion:
          type: string
        committedAt:
          type: string
          format: date-time
        oddsKey:
          type: string
        rulesHash:
          type: string
          pattern: '^[a-f0-9]{64}$'
        schemaVersion:
          type: string
        snapshotContentHash:
          type: string
          pattern: '^[a-f0-9]{64}$'
        version:
          type: integer
          minimum: 1
    GachaRipResult:
      type: object
      additionalProperties: false
      required: [oddsCommitment, rip, rgsProof, serverSeed, serverSeedHash]
      properties:
        oddsCommitment:
          $ref: '#/components/schemas/GachaRipOddsCommitment'
        rip:
          $ref: '#/components/schemas/GachaRip'
        rgsProof:
          description: |
            The versioned independently verifiable round proof. Legacy rows
            created before the RGS contract may return null.
          oneOf:
            - $ref: '#/components/schemas/RgsSeededProof'
            - type: 'null'
        serverSeed:
          description: |
            The secret serverSeed committed before the roll, revealed only once
            the rip has reached a terminal (SETTLED or FAILED) state. Hashing
            this value with SHA-256 must equal serverSeedHash, letting a player
            independently verify the roll was not steered after the fact.
          type: [string, 'null']
          pattern: '^[a-f0-9]{64}$'
        serverSeedHash:
          description: |
            The same hash originally published by POST
            /gacha/machines/{machineKey}/rip-commitments, echoed back once the
            rip is terminal.
          type: [string, 'null']
          pattern: '^[a-f0-9]{64}$'
    FantasyProductCapabilities:
      type: object
      additionalProperties: false
      required: [modes, network, oracle]
      properties:
        modes:
          type: object
          additionalProperties: false
          required: [tournaments, entries, payouts]
          properties:
            tournaments:
              $ref: '#/components/schemas/CapabilityAvailability'
            entries:
              $ref: '#/components/schemas/CapabilityAvailability'
            payouts:
              $ref: '#/components/schemas/CapabilityAvailability'
        network:
          type: string
          const: solana-devnet
        oracle:
          type: object
          additionalProperties: false
          required: [mode, live]
          properties:
            mode:
              type: string
            live:
              type: boolean
    SolanaAddress:
      type: string
      pattern: '^[1-9A-HJ-NP-Za-km-z]{32,44}$'
      examples: [9xQeWvG816bUx9EPfEZvD6nGQ3xM4wzHY6zvQ3z9gJ1]
    PackId:
      type: string
      pattern: '^[a-z0-9][a-z0-9_-]{2,63}$'
    Money:
      type: object
      additionalProperties: false
      required: [amount, currency, decimals]
      properties:
        amount:
          type: string
          pattern: '^[0-9]+$'
          description: Integer minor units encoded as a string.
        currency:
          type: string
          enum: [USDC]
        decimals:
          type: integer
          const: 6
    PullRarity:
      type: string
      enum: [common, uncommon, rare, chase]
      description: Presentation-only rarity derived from committed insured value; it is never part of the canonical result hash.
    ValuationPolicyDocument:
      type: object
      additionalProperties: false
      required: [hashAlgorithm, policyHash, policy]
      properties:
        hashAlgorithm:
          type: string
          const: sha256
        policyHash:
          type: string
          enum:
            - b1334fcec0e89380bc0b32b2210a9ca99fb72d64bfd75e4c2c2d64cbe40b43ba
            - 0d8f1654c4d5c86622e207622bea835d029ecc78ed2ca1a24ba739a2c356c9fd
        policy:
          $ref: '#/components/schemas/ValuationPolicy'
    ValuationPolicy:
      type: object
      additionalProperties: false
      required:
        - schemaVersion
        - policyVersion
        - authoritativeField
        - comparisonMetric
        - currency
        - decimals
        - numericRepresentation
        - rounding
        - maxSourceAgeSeconds
        - maxValueMinorUnits
        - maxFutureSkewSeconds
        - tieRule
        - providerCorrectionRule
      properties:
        schemaVersion:
          type: string
          const: dailydraft.valuation-policy.v1
        policyVersion:
          type: string
          enum: [collector-crypt-insured-value-usdc-v1, dailydraft-pokemon-tcg-market-usdc-v1]
        authoritativeField:
          type: string
          enum: [collector-crypt.gacha.result.insuredValue, pokemon-tcg.tcgplayer.prices.<first-supported-variant>.market]
          description: The source field committed before funding. Hosted devnet demos use a persisted Pokémon TCG TCGPlayer market snapshot; the disabled Collector Crypt adapter uses insured value.
        comparisonMetric:
          type: string
          const: insured-value
        currency:
          type: string
          const: USDC
        decimals:
          type: integer
          const: 6
        numericRepresentation:
          type: string
          const: unsigned-integer-minor-units
        rounding:
          type: string
          const: none
        maxSourceAgeSeconds:
          type: integer
          enum: [300, 604800]
        maxValueMinorUnits:
          type: string
          const: '18446744073709551615'
        maxFutureSkewSeconds:
          type: integer
          enum: [0, 30]
        sourceSelection:
          type: object
          additionalProperties: false
          required: [fallbackRule, priceField, updatedAtInterpretation, usdToMicroUsdc, variantOrder]
          properties:
            fallbackRule: { type: string, const: reject-if-no-listed-variant-has-positive-market }
            priceField: { type: string, const: market }
            updatedAtInterpretation: { type: string, const: utc-start-of-day }
            usdToMicroUsdc: { type: string, const: require-two-decimal-usd-then-multiply-by-1000000 }
            variantOrder:
              type: array
              const: [holofoil, reverseHolofoil, normal, 1stEditionHolofoil]
        tieRule:
          type: string
          const: return-original-assets-and-refund-platform-fees
        providerCorrectionRule:
          type: string
          const: immutable-after-result-commit-dispute-or-refund
    Pack:
      type: object
      additionalProperties: false
      required: [id, name, provider, price, active, valuationPolicyHash]
      properties:
        id:
          type: string
        name:
          type: string
        provider:
          type: string
          description: Pack inventory provider identifier.
        providerPackId:
          type: string
        price:
          $ref: '#/components/schemas/Money'
        imageUrl:
          type: string
          format: uri
        active:
          type: boolean
        valuationPolicyHash:
          type: string
          pattern: '^[a-f0-9]{64}$'
    PackList:
      type: object
      additionalProperties: false
      required: [data, hasMore]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Pack'
        nextCursor:
          type: [string, 'null']
        hasMore:
          type: boolean
    MatchmakingMode:
      type: string
      enum: [direct, house, open]
    MatchmakingSearchRequest:
      type: object
      additionalProperties: false
      required: [wallet, packId]
      properties:
        wallet:
          $ref: '#/components/schemas/SolanaAddress'
        packId:
          $ref: '#/components/schemas/PackId'
    MatchmakingWalletRequest:
      type: object
      additionalProperties: false
      required: [wallet]
      properties:
        wallet:
          $ref: '#/components/schemas/SolanaAddress'
    MatchmakingSession:
      type: object
      additionalProperties: false
      required:
        - duelId
        - wallet
        - role
        - state
        - houseOpponent
        - queue
        - searchExpiresAt
        - availableActions
        - cancellationRule
      properties:
        duelId:
          type: string
          pattern: '^duel_[A-Za-z0-9]{12,64}$'
        wallet:
          $ref: '#/components/schemas/SolanaAddress'
        role:
          type: string
          enum: [creator, opponent]
        state:
          type: string
          enum: [searching, matched]
        houseOpponent:
          type: boolean
        opponentWallet:
          oneOf:
            - $ref: '#/components/schemas/SolanaAddress'
            - type: 'null'
        searchExpiresAt:
          type: string
          format: date-time
        commitmentExpiresAt:
          type: [string, 'null']
          format: date-time
        cancellationRule:
          type: string
        availableActions:
          type: array
          items:
            type: object
            additionalProperties: true
            required: [action, available]
            properties:
              action:
                type: string
                enum: [continue_search, cancel_search, house_fallback]
              available:
                type: boolean
              disclosure:
                type: string
        queue:
          type: object
          additionalProperties: false
          required: [packId, tier, valuationPolicyHash, providerMode, regionSegment, riskSegment, queueKey]
          properties:
            packId:
              $ref: '#/components/schemas/PackId'
            tier:
              type: integer
            valuationPolicyHash:
              type: string
              pattern: '^[a-f0-9]{64}$'
            providerMode:
              type: string
              enum: [mock, dailydraft-devnet, collector-crypt-sandbox]
            regionSegment:
              type: string
            riskSegment:
              type: string
            queueKey:
              type: string
              pattern: '^[a-f0-9]{64}$'
    DuelStatus:
      type: string
      enum:
        - waiting
        - matched
        - committing
        - funded
        - opening
        - awaiting_assets
        - settling
        - settled
        - cancelling
        - cancelled
        - refunding
        - refunded
        - failed
    CreateDuelRequest:
      type: object
      additionalProperties: false
      required: [creatorWallet, packId, matchmakingMode, expiresAt]
      properties:
        analyticsSessionId:
          type: string
          pattern: '^anon_[a-f0-9]{32}$'
        creatorWallet:
          $ref: '#/components/schemas/SolanaAddress'
        opponentWallet:
          oneOf:
            - $ref: '#/components/schemas/SolanaAddress'
            - type: 'null'
        packId:
          $ref: '#/components/schemas/PackId'
        matchmakingMode:
          $ref: '#/components/schemas/MatchmakingMode'
        expiresAt:
          type: string
          format: date-time
          description: Must be in the future and no more than 24 hours from creation.
    JoinDuelRequest:
      type: object
      additionalProperties: false
      required: [wallet]
      properties:
        analyticsSessionId:
          type: string
          pattern: '^anon_[a-f0-9]{32}$'
        wallet:
          $ref: '#/components/schemas/SolanaAddress'
    CancelDuelRequest:
      type: object
      additionalProperties: false
      required: [wallet]
      properties:
        analyticsSessionId:
          type: string
          pattern: '^anon_[a-f0-9]{32}$'
        wallet:
          $ref: '#/components/schemas/SolanaAddress'
        reason:
          type: string
          maxLength: 160
    Duel:
      type: object
      additionalProperties: false
      required:
        - id
        - status
        - matchmakingMode
        - creatorWallet
        - environment
        - providerMode
        - houseOpponent
        - pack
        - stake
        - version
        - createdAt
        - expiresAt
      properties:
        id:
          type: string
        status:
          $ref: '#/components/schemas/DuelStatus'
        matchmakingMode:
          $ref: '#/components/schemas/MatchmakingMode'
        creatorWallet:
          $ref: '#/components/schemas/SolanaAddress'
        environment:
          type: string
          const: solana-devnet
        providerMode:
          type: string
          enum: [mock, dailydraft-devnet, collector-crypt-sandbox]
          description: Mock outcomes are valueless and never represent transferred assets.
        houseOpponent:
          type: boolean
        opponentWallet:
          oneOf:
            - $ref: '#/components/schemas/SolanaAddress'
            - type: 'null'
        pack:
          $ref: '#/components/schemas/Pack'
        stake:
          $ref: '#/components/schemas/Money'
        escrowAddress:
          oneOf:
            - $ref: '#/components/schemas/SolanaAddress'
            - type: 'null'
        transactionSignature:
          type: [string, 'null']
        winnerWallet:
          oneOf:
            - $ref: '#/components/schemas/SolanaAddress'
            - type: 'null'
        result:
          oneOf:
            - $ref: '#/components/schemas/DuelResult'
            - type: 'null'
        opponentJoinedAt:
          type: [string, 'null']
          format: date-time
        cancellationReason:
          type: [string, 'null']
        version:
          type: integer
          minimum: 1
        createdAt:
          type: string
          format: date-time
        expiresAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    DuelPackOutcome:
      type: object
      additionalProperties: false
      required: [side, assetReference, displayName, insuredValue, rarity, openedAt, poolVersion, sourceTimestamp, resultHash, isMock, provider, providerReference]
      properties:
        side:
          type: string
          enum: [creator, opponent]
        assetReference:
          type: string
          description: Public asset identity; provider request references remain server-only.
        displayName:
          type: string
          maxLength: 160
        imageUrl:
          type: [string, 'null']
          format: uri
        insuredValue:
          $ref: '#/components/schemas/Money'
          description: Backward-compatible result field. Its authoritative meaning comes from valuationPolicyHash; hosted demo outcomes contain the snapshotted TCGPlayer market value, not Collector Crypt insured value.
        rarity:
          $ref: '#/components/schemas/PullRarity'
        openedAt:
          type: string
          format: date-time
          description: Provider-reported completion time committed as escrow opened_at; its whole-second value must be between finalized funding and expiry and no more than 30 seconds ahead of server time.
        poolVersion:
          type: string
          pattern: '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
          description: Immutable provider inventory/value-pool snapshot shared by both outcomes.
        sourceTimestamp:
          type: string
          format: date-time
          description: Timestamp when the API captured the authoritative provider value snapshot before comparison.
        valuationSourceReference:
          type: [string, 'null']
          description: Exact provider card, selected price variant, value field, and upstream update timestamp bound into the outcome hash when available.
        resultHash:
          type: string
          pattern: '^[a-f0-9]{64}$'
        isMock:
          type: boolean
        provider:
          type: string
        providerReference:
          type: string
          description: Public pack-opening reference recorded for independent provider support.
    DuelResult:
      type: object
      additionalProperties: false
      required:
        - comparisonMetric
        - outcomes
        - resultHash
        - settlementReady
        - tieRule
        - valuationPolicyHash
        - winnerSide
      properties:
        comparisonMetric:
          type: string
          const: insured-value
        outcomes:
          type: array
          minItems: 2
          maxItems: 2
          items:
            $ref: '#/components/schemas/DuelPackOutcome'
        resultHash:
          type: string
          pattern: '^[a-f0-9]{64}$'
        settlementReady:
          type: boolean
          description: Results can be passed to settlement; assets are not yet transferred.
        tieRule:
          type: string
          const: return-original-assets-and-refund-platform-fees
          description: Equal canonical comparison values return each original card and refund both platform fees.
        valuationPolicyHash:
          type: string
          pattern: '^[a-f0-9]{64}$'
        winnerSide:
          type: [string, 'null']
          enum: [creator, opponent, null]
    PublicDuelStatus:
      oneOf:
        - $ref: '#/components/schemas/DuelStatus'
        - type: string
          const: expired
    PublicParticipant:
      type: object
      additionalProperties: false
      required: [address, display, role]
      properties:
        address:
          $ref: '#/components/schemas/SolanaAddress'
        display:
          type: string
        role:
          type: string
          enum: [creator, opponent, house]
    PublicDuelReceipt:
      type: object
      additionalProperties: false
      required: [schemaVersion, duel, pack, participants, result, fees, custody, references, recovery, availability, cardActions, actions, privacy]
      properties:
        schemaVersion:
          type: string
          const: dailydraft.receipt.v1
        duel:
          type: object
          additionalProperties: false
          required: [id, status, mode, network, createdAt, expiresAt, observedAt]
          properties:
            id: { type: string }
            status: { $ref: '#/components/schemas/PublicDuelStatus' }
            mode: { $ref: '#/components/schemas/MatchmakingMode' }
            network: { type: string, const: solana-devnet }
            createdAt: { type: string, format: date-time }
            expiresAt: { type: string, format: date-time }
            observedAt: { type: string, format: date-time }
        pack:
          type: object
          additionalProperties: false
          required: [id, name, provider, providerMode, providerPackId, tier]
          properties:
            id: { type: string }
            name: { type: string }
            provider: { type: string }
            providerMode:
              type: string
              enum: [mock, dailydraft-devnet, collector-crypt-sandbox]
            providerPackId: { type: [string, 'null'] }
            tier: { $ref: '#/components/schemas/Money' }
        participants:
          type: object
          additionalProperties: false
          required: [creator, opponent]
          properties:
            creator: { $ref: '#/components/schemas/PublicParticipant' }
            opponent:
              oneOf:
                - $ref: '#/components/schemas/PublicParticipant'
                - type: 'null'
        result:
          oneOf:
            - $ref: '#/components/schemas/PublicDuelResult'
            - type: 'null'
        fees:
          type: object
          additionalProperties: false
          required: [asset, perSideAmountLamports, finalizedSides, requiredSides, totalFinalizedAmountLamports]
          properties:
            asset: { type: string, const: WSOL }
            perSideAmountLamports: { type: [string, 'null'], pattern: '^\d+$' }
            finalizedSides: { type: integer, minimum: 0, maximum: 2 }
            requiredSides: { type: integer, const: 2 }
            totalFinalizedAmountLamports: { type: [string, 'null'], pattern: '^\d+$' }
        custody:
          type: object
          additionalProperties: false
          required: [platformFee, cardAssets]
          properties:
            platformFee:
              type: object
              additionalProperties: false
              required: [asset, escrowAddress, status]
              properties:
                asset: { type: string, const: WSOL }
                escrowAddress:
                  oneOf:
                    - $ref: '#/components/schemas/SolanaAddress'
                    - type: 'null'
                status: { type: string, enum: [not-started, partially-funded, funded] }
            cardAssets:
              type: object
              additionalProperties: false
              required: [status, detail]
              properties:
                status: { type: string, enum: [not-recorded, provider-results-recorded, settlement-reference-recorded] }
                detail: { type: string }
        references:
          type: object
          additionalProperties: false
          required: [provider, solana]
          properties:
            provider:
              type: array
              items:
                type: object
                additionalProperties: false
                required: [side, provider, providerReference, assetReference]
                properties:
                  side: { type: string, enum: [creator, opponent] }
                  provider: { type: string }
                  providerReference: { type: string }
                  assetReference: { type: string }
            solana:
              type: array
              items:
                type: object
                additionalProperties: false
                required: [action, bindingSource, signature, status, finalizedAt, recoveredAt, explorerUrl]
                properties:
                  action:
                    type: string
                    enum: [fund, cancel, refund, open_pack, commit_result, settle]
                  bindingSource: { type: string, enum: [api-submission, rpc-recovery] }
                  signature: { type: string }
                  status: { type: string, enum: [prepared, submitted, confirmed, finalized, failed, expired] }
                  finalizedAt: { type: [string, 'null'], format: date-time }
                  recoveredAt: { type: [string, 'null'], format: date-time }
                  explorerUrl: { type: string, format: uri }
        recovery:
          type: object
          additionalProperties: false
          required: [status, alerts]
          properties:
            status: { type: string, enum: [none, recovered, attention-required] }
            alerts:
              type: array
              items:
                type: object
                additionalProperties: false
                required: [action, code, detectedAt, signature, explorerUrl]
                properties:
                  action: { type: string, enum: [fund, commit_result, settle] }
                  code: { type: string, const: UNBOUND_FINALIZED_ESCROW_STATE_MISMATCH }
                  detectedAt: { type: string, format: date-time }
                  signature: { type: string }
                  explorerUrl: { type: string, format: uri }
        availability:
          type: object
          additionalProperties: false
          required: [complete, missing]
          properties:
            complete: { type: boolean }
            missing: { type: array, items: { type: string } }
        cardActions:
          $ref: '#/components/schemas/PublicPostDuelCardActions'
        actions:
          $ref: '#/components/schemas/PublicActions'
        privacy:
          $ref: '#/components/schemas/PublicPrivacy'
    PublicDuelResult:
      type: object
      additionalProperties: false
      required: [comparisonMetric, outcomes, policy, proof, resultHash, settlementReady, valuationPolicyHash, winnerSide, winner, margin, totalValue]
      properties:
        comparisonMetric: { type: string, const: insured-value }
        outcomes:
          type: array
          minItems: 2
          maxItems: 2
          items:
            type: object
            additionalProperties: false
            required: [side, assetReference, displayName, insuredValue, rarity, isMock, openedAt, poolVersion, sourceTimestamp, valuationSourceReference, resultHash]
            properties:
              side: { type: string, enum: [creator, opponent] }
              assetReference: { type: string }
              displayName: { type: string }
              imageUrl: { type: [string, 'null'], format: uri }
              insuredValue: { $ref: '#/components/schemas/Money' }
              rarity: { $ref: '#/components/schemas/PullRarity' }
              isMock: { type: boolean }
              openedAt: { type: string, format: date-time }
              poolVersion: { type: string }
              sourceTimestamp: { type: string, format: date-time }
              valuationSourceReference: { type: [string, 'null'] }
              resultHash: { type: string, pattern: '^[a-f0-9]{64}$' }
        policy:
          type: object
          additionalProperties: false
          required: [authoritativeField, currency, decimals, hash, hashAlgorithm, maxSourceAgeSeconds, maxValueMinorUnits, policyVersion, rounding, tieRule]
          properties:
            authoritativeField: { type: string, enum: [collector-crypt.gacha.result.insuredValue, pokemon-tcg.tcgplayer.prices.<first-supported-variant>.market] }
            currency: { type: string, const: USDC }
            decimals: { type: integer, const: 6 }
            hash: { type: string, pattern: '^[a-f0-9]{64}$' }
            hashAlgorithm: { type: string, const: sha256 }
            maxSourceAgeSeconds: { type: integer, enum: [300, 604800] }
            maxValueMinorUnits: { type: string, const: '18446744073709551615' }
            policyVersion: { type: string, enum: [collector-crypt-insured-value-usdc-v1, dailydraft-pokemon-tcg-market-usdc-v1] }
            rounding: { type: string, const: none }
            tieRule: { type: string, const: return-original-assets-and-refund-platform-fees }
        proof:
          type: object
          additionalProperties: false
          required: [schemaVersion, context, creatorResultHash, opponentResultHash, poolVersion, providerAttestation]
          properties:
            schemaVersion: { type: string, const: dailydraft.result-proof.v1 }
            context:
              type: object
              additionalProperties: false
              required: [duelId, creatorWallet, opponentWallet, escrowAddress, network, providerMode]
              properties:
                duelId: { type: string }
                creatorWallet: { $ref: '#/components/schemas/SolanaAddress' }
                opponentWallet: { $ref: '#/components/schemas/SolanaAddress' }
                escrowAddress: { $ref: '#/components/schemas/SolanaAddress' }
                network: { type: string, const: solana-devnet }
                providerMode:
                  type: string
                  enum: [mock, dailydraft-devnet, collector-crypt-sandbox]
            creatorResultHash: { type: string, pattern: '^[a-f0-9]{64}$' }
            opponentResultHash: { type: string, pattern: '^[a-f0-9]{64}$' }
            poolVersion: { type: string }
            providerAttestation:
              type: object
              additionalProperties: false
              required: [required, scope, status]
              properties:
                required: { type: boolean }
                scope: { type: string, enum: [none, escrow-mints-values-policy] }
                status: { type: string, enum: [mock-not-applicable, not-recorded, on-chain-commitment-finalized] }
        resultHash: { type: string, pattern: '^[a-f0-9]{64}$' }
        settlementReady: { type: boolean }
        valuationPolicyHash: { type: string, pattern: '^[a-f0-9]{64}$' }
        winnerSide: { type: [string, 'null'], enum: [creator, opponent, null] }
        winner:
          oneOf:
            - $ref: '#/components/schemas/PublicParticipant'
            - type: 'null'
        margin: { $ref: '#/components/schemas/Money' }
        totalValue: { $ref: '#/components/schemas/Money' }
    PublicPostDuelCardActions:
      type: object
      additionalProperties: false
      required: [schemaVersion, availability, reason, receiptHref, cards]
      properties:
        schemaVersion:
          type: string
          const: dailydraft.card-actions.v1
        availability:
          type: string
          enum: [available, hidden]
        reason:
          type: [string, 'null']
          enum: [duel-not-settled, mock-assets, ownership-mismatch, ownership-pending, null]
        receiptHref:
          type: string
          description: Stable API path linking every card state to its originating duel receipt.
        cards:
          type: array
          maxItems: 2
          description: Empty unless exact finalized settlement reconciles canonical ownership.
          items:
            $ref: '#/components/schemas/PublicPostDuelCardActionState'
    PublicPostDuelCardActionState:
      type: object
      additionalProperties: false
      required: [actionStateId, duelId, side, assetReference, providerReference, displayName, insuredValue, rarity, owner, ownership, receiptHref, actions]
      properties:
        actionStateId:
          type: string
          description: Stable projection identifier; repeated reads do not create an action or transaction.
        duelId: { type: string }
        side: { type: string, enum: [creator, opponent] }
        assetReference: { type: string }
        providerReference: { type: string }
        displayName: { type: string }
        imageUrl: { type: [string, 'null'], format: uri }
        insuredValue: { $ref: '#/components/schemas/Money' }
        rarity: { $ref: '#/components/schemas/PullRarity' }
        owner: { $ref: '#/components/schemas/PublicParticipant' }
        ownership:
          type: object
          additionalProperties: false
          required: [status, basis, settlementSignature]
          properties:
            status: { type: string, const: reconciled }
            basis: { type: string, const: finalized-settlement-reference }
            settlementSignature: { type: string }
        receiptHref: { type: string }
        actions:
          type: array
          minItems: 4
          maxItems: 4
          items:
            $ref: '#/components/schemas/PublicPostDuelCardAction'
    PublicPostDuelCardAction:
      type: object
      additionalProperties: false
      required: [action, label, capability, availability, reason, detail, requiresSignature, transaction, alternative]
      properties:
        action: { type: string, enum: [keep, list, sell-back, redeem] }
        label: { type: string }
        capability:
          type: string
          enum: [ownership-receipt, collector-crypt-marketplace-listing, collector-crypt-buyback, collector-crypt-shipping]
        availability: { type: string, enum: [available, unavailable] }
        reason:
          type: [string, 'null']
          enum: [partner-onboarding-required, null]
        detail: { type: string }
        requiresSignature:
          type: boolean
          const: false
          description: No unavailable partner action returns an unsigned or fake transaction.
        transaction:
          type: 'null'
        alternative:
          oneOf:
            - type: object
              additionalProperties: false
              required: [action, label]
              properties:
                action: { type: string, const: keep }
                label: { type: string, const: Keep card }
            - type: 'null'
    PublicActions:
      type: object
      additionalProperties: false
      required: [primary, rematch, share]
      properties:
        primary: { $ref: '#/components/schemas/PublicAction' }
        rematch:
          oneOf:
            - $ref: '#/components/schemas/PublicAction'
            - type: 'null'
        share: { $ref: '#/components/schemas/PublicAction' }
    PublicAction:
      type: object
      additionalProperties: false
      required: [href, label]
      properties:
        href: { type: string }
        label: { type: string }
    PublicPrivacy:
      type: object
      additionalProperties: false
      required: [indexable, reason]
      properties:
        indexable: { type: boolean, const: false }
        reason: { type: string }
    PublicDuelLeaderboard:
      type: object
      additionalProperties: false
      required: [schemaVersion, entries, methodology, privacy]
      properties:
        schemaVersion: { type: string, const: dailydraft.leaderboard.v1 }
        entries:
          type: array
          maxItems: 50
          items:
            type: object
            additionalProperties: false
            required: [rank, display, profileHref, record, totalWonValue, lastPlayedAt]
            properties:
              rank: { type: integer, minimum: 1, maximum: 50 }
              display: { type: string }
              profileHref: { type: string }
              record:
                type: object
                additionalProperties: false
                required: [completed, wins, losses, ties]
                properties:
                  completed: { type: integer, minimum: 0 }
                  wins: { type: integer, minimum: 0 }
                  losses: { type: integer, minimum: 0 }
                  ties: { type: integer, minimum: 0 }
              totalWonValue: { $ref: '#/components/schemas/Money' }
              lastPlayedAt: { type: string, format: date-time }
        methodology:
          type: object
          additionalProperties: false
          required: [entryLimit, sampleLimit, sampledSettledDuels, hasMoreSettledDuels, excludesMockResults, ranking]
          properties:
            entryLimit: { type: integer, const: 50 }
            sampleLimit: { type: integer, const: 5000 }
            sampledSettledDuels: { type: integer, minimum: 0, maximum: 5000 }
            hasMoreSettledDuels: { type: boolean }
            excludesMockResults: { type: boolean, const: true }
            ranking:
              type: string
              const: wins-total-value-completed-recency
        privacy: { $ref: '#/components/schemas/PublicPrivacy' }
    PublicWalletProfile:
      type: object
      additionalProperties: false
      required: [schemaVersion, wallet, record, biggestWin, duels, pagination, privacy]
      properties:
        schemaVersion: { type: string, const: dailydraft.profile.v1 }
        wallet:
          type: object
          additionalProperties: false
          required: [address, display]
          properties:
            address: { $ref: '#/components/schemas/SolanaAddress' }
            display: { type: string }
        record:
          type: object
          additionalProperties: false
          required: [total, active, completed, wins, losses, refunded, cancelledOrExpired]
          properties:
            total: { type: integer, minimum: 0 }
            active: { type: integer, minimum: 0 }
            completed: { type: integer, minimum: 0 }
            wins: { type: integer, minimum: 0 }
            losses: { type: integer, minimum: 0 }
            refunded: { type: integer, minimum: 0 }
            cancelledOrExpired: { type: integer, minimum: 0 }
        biggestWin:
          oneOf:
            - type: object
              additionalProperties: false
              required: [duelId, prizeValue]
              properties:
                duelId: { type: string }
                prizeValue: { $ref: '#/components/schemas/Money' }
            - type: 'null'
        duels:
          type: array
          items:
            type: object
            additionalProperties: false
            required: [duelId, status, result, packName, tier, opponentDisplay, createdAt, receiptHref, rematchHref]
            properties:
              duelId: { type: string }
              status: { $ref: '#/components/schemas/PublicDuelStatus' }
              result: { type: string, enum: [win, loss, none] }
              packName: { type: string }
              tier: { $ref: '#/components/schemas/Money' }
              opponentDisplay: { type: [string, 'null'] }
              createdAt: { type: string, format: date-time }
              receiptHref: { type: string }
              rematchHref: { type: [string, 'null'] }
        pagination:
          type: object
          additionalProperties: false
          required: [hasMore, sampleLimit]
          properties:
            hasMore: { type: boolean }
            sampleLimit: { type: integer, minimum: 1, maximum: 100 }
        privacy: { $ref: '#/components/schemas/PublicPrivacy' }
    DuelList:
      type: object
      additionalProperties: false
      required: [data, hasMore]
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/Duel'
        nextCursor:
          type: [string, 'null']
        hasMore:
          type: boolean
    DuelEvent:
      type: object
      additionalProperties: false
      required: [id, duelId, sequence, type, createdAt]
      properties:
        id:
          type: string
        duelId:
          type: string
        sequence:
          type: integer
          minimum: 1
        type:
          type: string
        actorWallet:
          oneOf:
            - $ref: '#/components/schemas/SolanaAddress'
            - type: 'null'
        fromStatus:
          oneOf:
            - $ref: '#/components/schemas/DuelStatus'
            - type: 'null'
        toStatus:
          oneOf:
            - $ref: '#/components/schemas/DuelStatus'
            - type: 'null'
        data:
          type: [object, 'null']
          additionalProperties: true
        createdAt:
          type: string
          format: date-time
    PrepareTransactionRequest:
      type: object
      additionalProperties: false
      required: [action, wallet]
      properties:
        action:
          type: string
          const: fund
        wallet:
          $ref: '#/components/schemas/SolanaAddress'
    PrepareProviderEscrowRequest:
      type: object
      additionalProperties: false
      required: [operation, callerWallet]
      properties:
        operation:
          type: string
          enum: [deposit_card, commit_result, settle, refund_card, refund_payment]
        callerWallet: { $ref: '#/components/schemas/SolanaAddress' }
        side: { type: string, enum: [creator, opponent] }
        sourceTokenAccount: { $ref: '#/components/schemas/SolanaAddress' }
        assetStandard: { type: string, const: legacy-spl-nft }
        providerRequestId: { type: string, pattern: '^[a-f0-9]{64}$' }
    PreparedProviderEscrowTransaction:
      type: object
      additionalProperties: false
      required:
        - action
        - chain
        - duelId
        - expectedSigner
        - instruction
        - intentId
        - lastValidBlockHeight
        - messageSha256
        - programId
        - proof
        - recentBlockhash
        - reconciliation
        - serializedTransactionBase64
        - status
        - warnings
      properties:
        action:
          type: string
          enum: [deposit_card, commit_result, settle, refund_card, refund_payment]
        chain: { type: string, const: 'solana:devnet' }
        duelId: { type: string }
        expectedSigner: { $ref: '#/components/schemas/SolanaAddress' }
        instruction: { type: object, additionalProperties: true }
        intentId:
          type: [string, 'null']
          description: Durable monitor intent ID; null only for card deposits pending operator proof.
        lastValidBlockHeight: { type: string, pattern: '^\d+$' }
        messageSha256: { type: string, pattern: '^[a-f0-9]{64}$' }
        programId: { $ref: '#/components/schemas/SolanaAddress' }
        proof: { type: object, additionalProperties: { type: [string, 'null'] } }
        recentBlockhash: { type: string }
        reconciliation:
          type: string
          enum: [operator-proof, submission-monitor]
        serializedTransactionBase64: { type: string, contentEncoding: base64 }
        status: { type: string, const: prepared }
        warnings:
          type: array
          items: { type: string }
    PreparedTransaction:
      type: object
      additionalProperties: false
      required:
        - id
        - duelId
        - action
        - status
        - cluster
        - chain
        - wallet
        - fundingSide
        - feeAmountLamports
        - feeAmountSol
        - feeRecipient
        - escrowAddress
        - paymentMint
        - programId
        - recentBlockhash
        - lastValidBlockHeight
        - expiresAt
        - serializedTransactionBase64
        - warnings
      properties:
        id: { type: string }
        duelId: { type: string }
        action:
          type: string
          const: fund
        status:
          type: string
          const: prepared
        cluster: { type: string, const: devnet }
        chain: { type: string, const: 'solana:devnet' }
        wallet: { $ref: '#/components/schemas/SolanaAddress' }
        fundingSide: { type: string, enum: [creator, opponent] }
        feeAmountLamports: { type: string, pattern: '^\d+$' }
        feeAmountSol: { type: string }
        feeRecipient: { $ref: '#/components/schemas/SolanaAddress' }
        escrowAddress: { $ref: '#/components/schemas/SolanaAddress' }
        paymentMint: { $ref: '#/components/schemas/SolanaAddress' }
        programId: { $ref: '#/components/schemas/SolanaAddress' }
        recentBlockhash: { type: string }
        lastValidBlockHeight: { type: string, pattern: '^\d+$' }
        expiresAt: { type: string, format: date-time }
        serializedTransactionBase64:
          type: string
          contentEncoding: base64
        warnings:
          type: array
          items: { type: string }
          minItems: 1
    DuelTransactionRecord:
      type: object
      additionalProperties: false
      required: [id, duelId, action, status, network, wallet, createdAt, updatedAt]
      properties:
        id:
          type: string
        duelId:
          type: string
        action:
          type: string
          enum: [fund, cancel, refund, open_pack, commit_result, settle]
        status:
          type: string
          enum: [prepared, submitted, confirmed, finalized, failed, expired]
        network:
          type: string
          const: solana-devnet
        wallet:
          $ref: '#/components/schemas/SolanaAddress'
        signature:
          type: [string, 'null']
        recentBlockhash:
          type: [string, 'null']
        recoveredAt:
          type: [string, 'null']
          format: date-time
          description: Set only after bounded escrow-PDA recovery verifies a persisted prepared intent.
        recoveryAlertCode:
          type: [string, 'null']
          enum: [UNBOUND_FINALIZED_ESCROW_STATE_MISMATCH, null]
        recoveryCandidateAt:
          type: [string, 'null']
          format: date-time
        recoveryCandidateSignature:
          type: [string, 'null']
        lastValidBlockHeight:
          type: [string, 'null']
        providerReference:
          type: [string, 'null']
        errorCode:
          type: [string, 'null']
        errorMessage:
          type: [string, 'null']
        submittedAt:
          type: [string, 'null']
          format: date-time
        confirmedAt:
          type: [string, 'null']
          format: date-time
        finalizedAt:
          type: [string, 'null']
          format: date-time
        confirmationStatus:
          type: [string, 'null']
          enum: [confirmed, finalized, null]
        checkAttempts:
          type: integer
          minimum: 0
        lastCheckedAt:
          type: [string, 'null']
          format: date-time
        stuckAt:
          type: [string, 'null']
          format: date-time
        expiresAt:
          type: [string, 'null']
          format: date-time
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    RecordSubmissionRequest:
      type: object
      additionalProperties: false
      required: [signature]
      properties:
        signature:
          type: string
          pattern: '^[1-9A-HJ-NP-Za-km-z]{64,96}$'
    BoundSubmission:
      type: object
      additionalProperties: false
      required: [transactionId, duelId, signature, status]
      properties:
        transactionId:
          type: string
          pattern: '^tx_[A-Za-z0-9]{12,64}$'
        duelId:
          type: string
          pattern: '^duel_[A-Za-z0-9]{12,64}$'
        signature:
          type: string
        status:
          type: string
          const: submitted
    RejectedFundingIntent:
      type: object
      additionalProperties: false
      required: [transactionId, duelId, status, reason]
      properties:
        transactionId:
          type: string
          pattern: '^tx_[A-Za-z0-9]{12,64}$'
        duelId:
          type: string
          pattern: '^duel_[A-Za-z0-9]{12,64}$'
        status:
          type: string
          const: expired
        reason:
          type: string
          const: wallet_rejected_before_broadcast
    ReconciliationSummary:
      type: object
      additionalProperties: false
      required: [checked, confirmed, expired, failed, finalized, pending, recoveryAlerts, recoveryCandidates, recoveryChecked, recoveryDeferred, recoveryErrors, recoveryRejected, recovered, stuck]
      properties:
        checked:
          type: integer
          minimum: 0
        confirmed:
          type: integer
          minimum: 0
        expired:
          type: integer
          minimum: 0
        failed:
          type: integer
          minimum: 0
        finalized:
          type: integer
          minimum: 0
        pending:
          type: integer
          minimum: 0
        recoveryCandidates:
          type: integer
          minimum: 0
        recoveryChecked:
          type: integer
          minimum: 0
        recoveryAlerts:
          type: integer
          minimum: 0
        recoveryDeferred:
          type: integer
          minimum: 0
        recoveryErrors:
          type: integer
          minimum: 0
        recoveryRejected:
          type: integer
          minimum: 0
        recovered:
          type: integer
          minimum: 0
        stuck:
          type: integer
          minimum: 0
    DuelReconciliationResult:
      type: object
      additionalProperties: false
      required: [activeTransactionCount, duelId, duelStatus, reconciliation]
      properties:
        activeTransactionCount:
          type: integer
          minimum: 0
        duelId:
          type: string
          pattern: '^duel_[A-Za-z0-9]{12,64}$'
        duelStatus:
          $ref: '#/components/schemas/DuelStatus'
        reconciliation:
          $ref: '#/components/schemas/ReconciliationSummary'
    SocialCard:
      type: object
      additionalProperties: false
      required: [duelId, status, pageUrl, imageUrl]
      properties:
        duelId:
          type: string
        status:
          $ref: '#/components/schemas/DuelStatus'
        pageUrl:
          type: string
          format: uri
        imageUrl:
          type: string
          format: uri
        shareText:
          type: string
          maxLength: 280
    Problem:
      type: object
      additionalProperties: true
      required: [type, title, status, requestId]
      properties:
        type:
          type: string
          format: uri-reference
        title:
          type: string
        status:
          type: integer
          minimum: 400
          maximum: 599
        detail:
          type: string
        requestId:
          type: string
