Pred WebSocket API

PRED uses [Ably](https://ably.com/) for real-time WebSocket data. Use Ably's token auth with `POST /api/v1/auth/ably` as your `authCallback`. ## Token auth Call `POST /api/v1/auth/ably` to get raw Ably `TokenDetails` JSON: ```json { "token": "ably-token-string", "keyName": "xxxxx.xxxxx", "clientId": "user-uuid", "capability": "{\"private:user:user-uuid\":[\"presence\",\"subscribe\"],\"market:*\":[\"subscribe\"]}", "expires": 1700000000000, "issued": 1699996400000 } ``` **Capabilities granted:** - Channel `private:user:`: `presence`, `subscribe` - Channel `market:*`: `subscribe` ## Private user channel: `private:user:{userID}` - **Authenticated**: you can only subscribe to your own `user_id` channel - **Must subscribe with Presence** (see below) — PRED only pushes events to clients that have entered presence on this channel - `user_id` comes from the login response ## How to subscribe with presence For the private user channel, you must **enter presence** before subscribing; otherwise PRED does not deliver order events. Use Ably's presence API in this order: 1. Obtain a token: call `POST /api/v1/auth/ably` with `Authorization: Bearer ` (and optionally `X-Wallet-Address`, `X-Proxy-Address`). Use the returned body as Ably's token (e.g. in `authCallback`). 2. Connect to Ably with that token (e.g. `ably.NewRealtime` with `authCallback` that returns the token). 3. Resolve the channel name for your user (e.g. `private:user:` from login; the token's capability may also indicate the channel). 4. Get the channel: `channel := realtime.Channels.Get(channelName)`. 5. **Enter presence** on the channel: call `channel.Presence.Enter(ctx, data)` with a small payload (e.g. `map[string]string{"source": "your-app"}`). This step is required; PRED only sends events to clients that have entered presence. 6. Subscribe to messages: call `channel.SubscribeAll(ctx, messageHandler)` (or `channel.Subscribe(ctx, eventName, handler)` for specific events). Handle `order-created`, `order-cancelled`, `order-updated` (and optionally `order-adjusted` if supported). If you subscribe without entering presence first, you will not receive order events on the private user channel. **Events:** ### `order-created` — New limit order placed ```json { "order_id": "string", "market_id": "string", "market_name": "string", "parent_market_id": "string", "parent_market_name": "string", "side": "long | short", "price": "string (cents)", "quantity": "string (shares)", "amount": "string (USD)", "remaining_quantity": "string", "reduce_only": false, "market_maker": false, "expiration": 1234567890 } ``` ### `order-cancelled` — Order cancelled by user or system ```json { "event_type": "order_cancelled", "order_id": "string", "timestamp": 1234567890 } ``` ### `order-updated` — Order matched on-chain (fully or partially filled) ```json { "event_type": "order_matched_on_chain", "order_id": "string", "remaining_quantity": "string", "filled_quantity": "string", "timestamp": 1234567890 } ``` Note: `matched_on_chain` may be `false` before confirmation; treat as a fill and react. Position from API may not reflect the impact yet. The SDK also handles alternate payload shapes: fields like `order_side`, `order_price`, `order_quantity`, or a nested `order` object with `side`, `price`, `quantity`. Additional fields that may appear: `market_id`, `parent_market_id`, `side`, `price`, `quantity`. ## Public market channels ### `market:orderbook:{marketID}` — Order book snapshot Event name: `orderbook` (throttled to 100ms) ```json { "market_id": "string", "last_updated_at": "2024-01-01T00:00:00Z", "sequence_number": 123, "last_update_id": 123, "bids": [ { "price": "50", "quantity": "100", "total": "50" } ], "asks": [ { "price": "51", "quantity": "100", "total": "51" } ] } ``` The SDK also reads `size` as an alias for `quantity`, and `metadata.cross_matching_enabled` (boolean). ### `market:ltp:{marketID}` — Last traded price Event name: `ltp` (throttled to 100ms) ```json { "price": "string (cents)", "volume": "string", "timestamp": 1234567890 } ``` ## Connection details - Use Ably token auth with `authCallback` calling `POST /api/v1/auth/ably` - Reconnect with exponential backoff: 1s min, 30s max - On the private user channel, enter presence before subscribing (see "How to subscribe with presence" above)

OpenAPI Specification

pred-websocket-api-openapi.yml Raw ↑
openapi: 3.1.0
info:
  title: PRED Trading Platform Authentication WebSocket API
  version: 1.0.0
  description: "Required headers, token refresh, and EIP-712 CreateProxy signature for login. For overview, getting started, and environment configuration, see **Overview**.\n\n## Required headers\n\n- `Authorization: Bearer <token>`\n  (for authenticated endpoints)\n- `X-API-Key: <api_key>`\n  (required for login-with-signature only)\n- `X-Wallet-Address: <wallet_address>`\n  (for order and portfolio endpoints)\n- `X-Proxy-Address: <proxy_wallet_address>`\n  (for order and portfolio endpoints)\n\n**Token refresh:** Access tokens are automatically refreshed by the SDK whenever their remaining lifetime drops below the configured threshold. The default threshold is **5 minutes**, and can be overridden via the `RefreshThresholdSeconds` client option.\n\n## EIP-712 login signature — CreateProxy\n\nFormat: `0x<r><s><v>` (132 hex characters). Low-s normalized: `s <= curveOrderHalf`.\n\n**Domain (NO version field):** All values below are ENV-specific; use the row for your chosen environment (see Environment configuration in **Overview**).\n```\nEIP712Domain type: \"EIP712Domain(string name,uint256 chainId,address verifyingContract)\"\nname: \"Pred Contract Proxy Factory\"\nchainId:        <Chain ID from environment table — Testnet 84532, Mainnet 8453>\nverifyingContract: <Login Contract from environment table>\n```\n\n**Message:**\n```\nCreateProxy type: \"CreateProxy(address paymentToken,uint256 payment,address paymentReceiver)\"\npaymentToken: 0x0000000000000000000000000000000000000000\npayment: 0\npaymentReceiver: 0x0000000000000000000000000000000000000000\n```\n\n**Normalization constants:**\n- `curveOrder = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141`\n- `curveOrderHalf = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0`\n"
servers:
- url: https://testnet.pred.app
  description: Testnet (ENV-specific). Base Sepolia; chain ID 84532. Use with Testnet row in Environment configuration.
- url: https://www.pred.app
  description: Mainnet (ENV-specific). Base; chain ID 8453. Use with Mainnet row in Environment configuration.
tags:
- name: WebSocket
  description: "PRED uses [Ably](https://ably.com/) for real-time WebSocket data. Use Ably's token auth with `POST /api/v1/auth/ably` as your `authCallback`.\n\n## Token auth\n\nCall `POST /api/v1/auth/ably` to get raw Ably `TokenDetails` JSON:\n\n```json\n{\n  \"token\": \"ably-token-string\",\n  \"keyName\": \"xxxxx.xxxxx\",\n  \"clientId\": \"user-uuid\",\n  \"capability\": \"{\\\"private:user:user-uuid\\\":[\\\"presence\\\",\\\"subscribe\\\"],\\\"market:*\\\":[\\\"subscribe\\\"]}\",\n  \"expires\": 1700000000000,\n  \"issued\": 1699996400000\n}\n```\n\n**Capabilities granted:**\n- Channel `private:user:<user_id>`: `presence`, `subscribe`\n- Channel `market:*`: `subscribe`\n\n## Private user channel: `private:user:{userID}`\n\n- **Authenticated**: you can only subscribe to your own `user_id` channel\n- **Must subscribe with Presence** (see below) — PRED only pushes events to clients that have entered presence on this channel\n- `user_id` comes from the login response\n\n## How to subscribe with presence\n\nFor the private user channel, you must **enter presence** before subscribing; otherwise PRED does not deliver order events. Use Ably's presence API in this order:\n\n1. Obtain a token: call `POST /api/v1/auth/ably` with `Authorization: Bearer <access_token>` (and optionally `X-Wallet-Address`, `X-Proxy-Address`). Use the returned body as Ably's token (e.g. in `authCallback`).\n2. Connect to Ably with that token (e.g. `ably.NewRealtime` with `authCallback` that returns the token).\n3. Resolve the channel name for your user (e.g. `private:user:<user_id>` from login; the token's capability may also indicate the channel).\n4. Get the channel: `channel := realtime.Channels.Get(channelName)`.\n5. **Enter presence** on the channel: call `channel.Presence.Enter(ctx, data)` with a small payload (e.g. `map[string]string{\"source\": \"your-app\"}`). This step is required; PRED only sends events to clients that have entered presence.\n6. Subscribe to messages: call `channel.SubscribeAll(ctx, messageHandler)` (or `channel.Subscribe(ctx, eventName, handler)` for specific events). Handle `order-created`, `order-cancelled`, `order-updated` (and optionally `order-adjusted` if supported).\n\nIf you subscribe without entering presence first, you will not receive order events on the private user channel.\n\n**Events:**\n\n### `order-created` — New limit order placed\n\n```json\n{\n  \"order_id\": \"string\",\n  \"market_id\": \"string\",\n  \"market_name\": \"string\",\n  \"parent_market_id\": \"string\",\n  \"parent_market_name\": \"string\",\n  \"side\": \"long | short\",\n  \"price\": \"string (cents)\",\n  \"quantity\": \"string (shares)\",\n  \"amount\": \"string (USD)\",\n  \"remaining_quantity\": \"string\",\n  \"reduce_only\": false,\n  \"market_maker\": false,\n  \"expiration\": 1234567890\n}\n```\n\n### `order-cancelled` — Order cancelled by user or system\n\n```json\n{\n  \"event_type\": \"order_cancelled\",\n  \"order_id\": \"string\",\n  \"timestamp\": 1234567890\n}\n```\n\n### `order-updated` — Order matched on-chain (fully or partially filled)\n\n```json\n{\n  \"event_type\": \"order_matched_on_chain\",\n  \"order_id\": \"string\",\n  \"remaining_quantity\": \"string\",\n  \"filled_quantity\": \"string\",\n  \"timestamp\": 1234567890\n}\n```\n\nNote: `matched_on_chain` may be `false` before confirmation; treat as a fill and react. Position from API may not reflect the impact yet.\n\nThe SDK also handles alternate payload shapes: fields like `order_side`, `order_price`, `order_quantity`, or a nested `order` object with `side`, `price`, `quantity`. Additional fields that may appear: `market_id`, `parent_market_id`, `side`, `price`, `quantity`.\n\n## Public market channels\n\n### `market:orderbook:{marketID}` — Order book snapshot\n\nEvent name: `orderbook` (throttled to 100ms)\n\n```json\n{\n  \"market_id\": \"string\",\n  \"last_updated_at\": \"2024-01-01T00:00:00Z\",\n  \"sequence_number\": 123,\n  \"last_update_id\": 123,\n  \"bids\": [\n    { \"price\": \"50\", \"quantity\": \"100\", \"total\": \"50\" }\n  ],\n  \"asks\": [\n    { \"price\": \"51\", \"quantity\": \"100\", \"total\": \"51\" }\n  ]\n}\n```\n\nThe SDK also reads `size` as an alias for `quantity`, and `metadata.cross_matching_enabled` (boolean).\n\n### `market:ltp:{marketID}` — Last traded price\n\nEvent name: `ltp` (throttled to 100ms)\n\n```json\n{\n  \"price\": \"string (cents)\",\n  \"volume\": \"string\",\n  \"timestamp\": 1234567890\n}\n```\n\n## Connection details\n\n- Use Ably token auth with `authCallback` calling `POST /api/v1/auth/ably`\n- Reconnect with exponential backoff: 1s min, 30s max\n- On the private user channel, enter presence before subscribing (see \"How to subscribe with presence\" above)\n"
paths:
  /api/v1/auth/ably:
    post:
      tags:
      - WebSocket
      summary: Get Ably WebSocket token
      description: 'Generate an Ably token for WebSocket connections. Returns raw Ably `TokenDetails` JSON (not wrapped).


        **Capabilities granted:**

        - `private:user:<user_id>`: `[presence, subscribe]`

        - `market:*`: `[subscribe]`


        Use this token with Ably''s `authCallback` for token-based auth.

        '
      operationId: getAblyToken
      parameters:
      - $ref: '#/components/parameters/XWalletAddress'
      - $ref: '#/components/parameters/XProxyAddress'
      responses:
        '200':
          description: Ably token generated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AblyTokenDetails'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '500':
          description: Failed to authenticate with Ably
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
components:
  parameters:
    XWalletAddress:
      name: X-Wallet-Address
      in: header
      required: true
      description: Your EOA wallet address
      schema:
        type: string
        pattern: ^0x[a-fA-F0-9]{40}$
    XProxyAddress:
      name: X-Proxy-Address
      in: header
      required: true
      description: Your proxy wallet address (from login response)
      schema:
        type: string
        pattern: ^0x[a-fA-F0-9]{40}$
  responses:
    Unauthorized:
      description: Authentication required or failed
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
  schemas:
    ErrorResponse:
      type: object
      properties:
        error:
          $ref: '#/components/schemas/Error'
        message:
          type: string
        success:
          type: boolean
          default: false
    AblyTokenDetails:
      type: object
      description: Raw Ably TokenDetails JSON (not wrapped in success/data envelope)
      properties:
        token:
          type: string
          description: Ably token string
        keyName:
          type: string
          description: Ably key name
        expires:
          type: integer
          format: int64
          description: Expiry time (milliseconds since Unix epoch)
        clientId:
          type: string
          description: Client ID bound to this token (user_id)
        issued:
          type: integer
          format: int64
          description: Issued time (milliseconds since Unix epoch)
        capability:
          type: string
          description: JSON-encoded Ably capability map
    Error:
      type: object
      properties:
        code:
          type: string
        message:
          type: string
        details:
          type: string
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: JWT access token from login endpoint