> ## Documentation Index
> Fetch the complete documentation index at: https://docs.testnet.dev.adipredictstreet.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Wallet leaderboard ranked by realised PnL or volume

> Returns the leaderboard snapshot for the requested period plus the caller's own row when authenticated.

**Variant 3 PnL semantics** — open positions do NOT contribute to PnL. Only sells, on-chain MERGE legs, and market resolutions move the number, so ranks stay stable between snapshots even when prices move.

**Tail-ranking** — every active user with a deployed vault appears in every period snapshot. Wallets with no realised activity in the period sort to the bottom under the `(pnl=0, volume=0, vault ASC)` tie-break and receive a real (numerically last) `rank`.

**Sort field** — `pnl` (default) ranks by realised PnL desc; `volume` re-orders the same period snapshot by notional traded desc and re-stamps the in-page rank.

**Search routing** — when `search` looks like a `0x`-hex prefix the lookup filters wallets; anything else filters usernames. Both branches use a case-insensitive prefix match backed by functional indexes.



## OpenAPI

````yaml /api-reference/openapi.json get /api/leaderboard
openapi: 3.1.0
info:
  title: PredictStreet core-api
  description: >-
    Client-facing HTTP gateway for the PredictStreet prediction-market platform.
    This spec is hand-written against the NestJS controllers and DTOs in
    core-api/src/modules/. For the source-of-truth live spec, run
    `./scripts/pull-openapi.sh` against a running core-api (NestJS exposes it at
    /api/docs-json). **Partner kinds.** Every authenticated endpoint resolves a
    request's *effective wallet* from the partner row. `single_wallet` partners
    bind to one `associatedWallet` set at creation. `multi_wallet` partners
    declare the actor on every request via an `X-User-Wallet: 0x<40-hex>`
    header. See the [Partner kinds](/auth/api-keys#partner-kinds) doc for the
    full contract.
  version: '2026-06-16'
  contact:
    name: PredictStreet partners
    email: partners@predictstreet.com
servers:
  - url: https://core.api.dev.predictstreet.sde.adifoundation.ai
    description: Testnet (partner integrator API — final domain TBD)
security: []
tags:
  - name: Deposits
    description: >-
      Gasless USDC deposit relay - submit a signed EIP-2612 permit and the
      platform broadcasts the on-chain deposit for you (no gas).
  - name: Events
    description: >-
      Polymarket-style event grouping with football metadata (group, stage,
      teams, tags).
  - name: Tags
    description: Curated tag taxonomy used to filter events.
  - name: Markets
    description: 'Public market data: list, detail, orderbook, trades, OHLC.'
  - name: Orders
    description: >-
      Signed-order place / cancel / read. Requires `X-Api-Key` with
      `orders:read` / `orders:write` scope; every write additionally requires an
      EIP-712 signature over the order.
  - name: Portfolio
    description: >-
      Balances, positions, trades, fees, vault info for the key's
      `associatedWallet`. Requires `X-Api-Key` with `portfolio:read` scope.
  - name: Matches
    description: >-
      admin.matches aggregate — groups several events into one fixture/card (1X2
      + first-scorer + over-under under one matchup).
  - name: Vault
    description: >-
      Backend co-signatures for ERC-1155 split / merge, and recovery for
      off-chain locks when the corresponding chain tx never confirmed. Requires
      `X-Api-Key` with `vault:write` scope plus an EIP-712 signature over the
      operation.
  - name: Leaderboard
    description: Public ranked leaderboard across PnL / volume buckets.
  - name: Search
    description: Global search across users, events, and matches.
  - name: Withdrawal Security
    description: >-
      Self-service withdrawal 2FA (TOTP) and withdrawal-address whitelist. Same
      endpoints serve the frontend (Privy JWT) and API-key integrators.
      Mutations need `vault:write`, reads `portfolio:read`. Both are opt-in per
      wallet and only gate withdrawals once the platform enables
      withdrawal-security enforcement.
paths:
  /api/leaderboard:
    get:
      tags:
        - Leaderboard
      summary: Wallet leaderboard ranked by realised PnL or volume
      description: >-
        Returns the leaderboard snapshot for the requested period plus the
        caller's own row when authenticated.


        **Variant 3 PnL semantics** — open positions do NOT contribute to PnL.
        Only sells, on-chain MERGE legs, and market resolutions move the number,
        so ranks stay stable between snapshots even when prices move.


        **Tail-ranking** — every active user with a deployed vault appears in
        every period snapshot. Wallets with no realised activity in the period
        sort to the bottom under the `(pnl=0, volume=0, vault ASC)` tie-break
        and receive a real (numerically last) `rank`.


        **Sort field** — `pnl` (default) ranks by realised PnL desc; `volume`
        re-orders the same period snapshot by notional traded desc and re-stamps
        the in-page rank.


        **Search routing** — when `search` looks like a `0x`-hex prefix the
        lookup filters wallets; anything else filters usernames. Both branches
        use a case-insensitive prefix match backed by functional indexes.
      operationId: LeaderboardController_page
      parameters:
        - name: period
          in: query
          required: false
          schema:
            type: string
            enum:
              - today
              - week
              - month
              - all
            default: today
          description: Which period to rank — defaults to `today`.
        - name: sort
          in: query
          required: false
          schema:
            type: string
            enum:
              - pnl
              - volume
            default: pnl
          description: >-
            Sort field. `pnl` (default) returns the canonical PnL leaderboard;
            `volume` re-orders the same period snapshot by notional traded desc.
        - name: search
          in: query
          required: false
          schema:
            type: string
          description: >-
            Optional case-insensitive prefix. Routes to wallet search when the
            value looks 0x-hex, username search otherwise.
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 50
            default: 20
          description: Page size, 1–50; defaults to 20.
        - name: cursor
          in: query
          required: false
          schema:
            type: string
          description: Opaque cursor returned by a previous page.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LeaderboardPageDto'
        '400':
          description: Invalid period / sort / cursor / limit.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
      security:
        - apiKey: []
        - {}
components:
  schemas:
    LeaderboardPageDto:
      type: object
      required:
        - period
        - snapshotAt
        - total
        - entries
        - nextCursor
        - me
      properties:
        period:
          type: string
          enum:
            - today
            - week
            - month
            - all
        snapshotAt:
          type: string
          format: date-time
          description: >-
            Wall-clock at which the recompute cron sampled the data feeding this
            page. Frontends can render "snapshot 2 min ago" directly off this
            field.
        total:
          type: integer
          description: >-
            Total entries in the leaderboard for this period — independent of
            the page slice. Drives page-count rendering.
        entries:
          type: array
          items:
            $ref: '#/components/schemas/LeaderboardEntry'
        nextCursor:
          type: string
          nullable: true
          description: Opaque keyset cursor for the next page; `null` on the last page.
        me:
          allOf:
            - $ref: '#/components/schemas/LeaderboardEntry'
          nullable: true
          description: >-
            The authenticated caller's row for the same period. Populated when
            the request carries a valid Privy JWT or API key AND the wallet has
            a deployed vault. `rank` is `null` when the caller has no realised
            activity yet (`pnl="0", volume="0"`); the FE should render an
            "unranked" personal block in that case. `null` for anonymous
            traffic, or for authed callers whose vault has not been deployed.
    ErrorEnvelope:
      type: object
      required:
        - error
      properties:
        status:
          type: integer
          example: 400
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              example: bad_request
            message:
              type: string
            details:
              type: object
              additionalProperties: true
            trace_id:
              type: string
    LeaderboardEntry:
      type: object
      required:
        - rank
        - vaultAddress
        - username
        - pnl
        - volume
      properties:
        rank:
          type: integer
          nullable: true
          description: >-
            1-based rank within the period. Always populated for rows inside
            `entries`. May be `null` only on the `me` row when the caller is
            authenticated but has no realised activity in the period yet (the
            snapshot still surfaces vault and username so the FE personal block
            can render an "unranked" state instead of disappearing).
          example: 17
        vaultAddress:
          type: string
          description: >-
            On-chain vault address — the public identity of the trading account.
            Cased as stored. The signing EOA is intentionally NOT exposed.
        username:
          type: string
          nullable: true
          description: Display username, or `null` when the user has not bound one.
        pnl:
          type: string
          description: >-
            Realised PnL for the period in USDC (decimal string). Only closing
            events contribute: orderbook SELL, on-chain MERGE, or market
            RESOLUTION. Open positions do NOT move PnL — ranks stay stable
            between snapshots even when prices move.
          example: '125.50'
        volume:
          type: string
          description: >-
            Notional traded in the period in USDC (decimal string). `SUM(price ·
            quantity)` over every settled fill leg the wallet executed.
            Resolution payouts intentionally do not inflate volume.
          example: '4200.00'

````