# OpenAPI 3.1 spec for the Trade Router public API.
#
# Drop this at https://traderouter.ai/openapi.yaml so HTTP clients in any
# language can be auto-generated.
#
# This file is the YAML rendering of openapi.json — they describe the SAME
# API. The JSON copy is the canonical source; this YAML is regenerated from
# it. If the two ever drift, the JSON wins.
#
# Conversion: `python -c "import json,yaml; print(yaml.dump(json.load(open('openapi.json')),sort_keys=False,allow_unicode=True))" > openapi.yaml`

openapi: 3.1.0
info:
  title: Trade Router – Solana Swap & Limit Order API
  version: 1.3.0
  description: REST API for instant Solana swaps and WebSocket for market-cap-based limit orders. Returns unsigned transactions for client-side signing.
  contact:
    url: https://traderouter.ai
    name: Trade Router
  x-social:
    x: https://x.com/trade_router
servers:
- url: https://api.traderouter.ai
  description: Production
paths:
  /:
    get:
      operationId: rootStatus
      summary: Health and websocket hint
      description: Basic health endpoint.
      responses:
        '200':
          description: Service status
          content:
            application/json:
              schema:
                type: object
                required:
                - status
                - message
                properties:
                  status:
                    type: string
                    const: ok
                  message:
                    type: string
                    const: WebSocket at /ws
  /mcap:
    get:
      operationId: getMcap
      summary: Market cap data for tokens
      description: 'Return market cap (and price/pool) data for given token addresses. Query param tokens: comma-delimited Solana mint addresses. Returns object keyed by token address; empty object if no tokens or none found.'
      parameters:
      - name: tokens
        in: query
        required: true
        description: Comma-delimited Solana token mint addresses
        schema:
          type: string
      responses:
        '200':
          description: Object keyed by token address with marketCap, pair_address, pool_type, priceUsd
  /flex:
    get:
      operationId: getFlexCard
      summary: Flex trade card PNG
      description: Generate flex trade card image for a wallet and token mint. Returns image/png. 400 invalid params; 501 if deps not available; 500 on error.
      parameters:
      - name: wallet_address
        in: query
        required: true
        schema:
          type: string
          description: Solana wallet pubkey
      - name: token_address
        in: query
        required: true
        schema:
          type: string
          description: SPL token mint
      responses:
        '200':
          description: PNG image
          content:
            image/png:
              schema:
                type: string
                format: binary
        '400':
          description: Invalid parameters
        '501':
          description: Flex card deps not available
        '500':
          description: Server error
  /swap:
    post:
      operationId: createSwap
      summary: Build an unsigned Solana swap transaction
      description: Returns a base58-encoded unsigned Solana transaction. The client must sign it and submit via POST /protect (base64). Buy requires amount (lamports). Sell requires at least one of amount or holdings_percentage.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SwapRequest'
            example:
              wallet_address: 2AzjvaNS7AbdBHuQ2JWPnFwVFnXGAKiPp86JQ3c8Rj4U
              token_address: GXDSgxcGwJhTEZnCQkXpKUKVL8rmK9UYPXbNPszPpump
              action: sell
              holdings_percentage: 10000
              slippage: 1500
      responses:
        '200':
          description: Swap transaction built successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SwapResponse'
  /holdings:
    post:
      operationId: getHoldings
      summary: Scan wallet token holdings
      description: Scan Solana wallet token holdings. This endpoint can be slow — set HTTP timeout to at least 100 seconds. Endpoint is intended to return sellable tokens, but clients should defensively filter valueNative > 0 before sell attempts.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WalletScanner'
      responses:
        '200':
          description: Holdings data or empty object
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HoldingsResponse'
  /protect:
    post:
      operationId: protect
      summary: Submit signed tx via MEV protected priority lane
      description: Submit a signed base64 transaction through the MEV-protected priority lane. The endpoint blocks until on-chain confirmation and can hang during congestion; use a 30s client timeout and check tx status before retrying. On 503, clients may fallback to direct RPC (less safe, no MEV protection).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ProtectRequest'
            example:
              signed_tx_base64: YOUR_SIGNED_TX
      responses:
        '200':
          description: Tx confirmed; returns signature and balance changes
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProtectResponse'
        '400':
          description: Invalid request (e.g. invalid tx)
        '503':
          description: Protect lane unavailable on server; fallback to direct RPC is possible (less safe, no MEV protection).
  /security:
    get:
      operationId: getSecurity
      summary: Server signing public keys
      description: 'Returns current and optional next server signing public keys (base58) used to verify order_filled.server_signature. Key rotation: 14 days; next key published alongside current.'
      responses:
        '200':
          description: Server signing keys
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SecurityResponse'
  /ws:
    get:
      operationId: websocketConnect
      summary: WebSocket endpoint for limit orders
      description: 'WebSocket wss://api.traderouter.ai/ws. Connection flow: (1) Connect. (2) Server sends {"type":"challenge","nonce":"..."} or {"type":"subscribed"}. (3) Client sends {"action":"register","wallet_address":"<pubkey>","signature":"<base58>"} where signature is challenge nonce (UTF-8 bytes) signed by wallet private key. (4) Server sends {"type":"registered","wallet_address":"...","authenticated":true}. (5) Only authenticated sessions may send order actions (sell, buy, trailing_sell, trailing_buy, twap_buy, twap_sell, limit_twap_sell, limit_twap_buy, trailing_twap_sell, trailing_twap_buy, limit_trailing_sell, limit_trailing_buy, limit_trailing_twap_sell, limit_trailing_twap_buy, check_order, list_orders, cancel_order, extend_order). Server may also emit heartbeat/error/echo messages. Verify order_filled.server_signature (canonical JSON keys: order_id, order_type, status, token_address, entry_mcap, triggered_mcap, filled_mcap, target_mcap, triggered_at, filled_at, data; SHA-256 digest, Ed25519) with GET /security public key before signing/submitting. order_created may include params_hash and server_signature (Rec 2) for parameter commitment. Order expiry is silent; poll list_orders/check_order.'
      responses:
        '101':
          description: WebSocket upgrade
      x-websocket-actions:
        client_to_server:
        - $ref: '#/components/schemas/WsRegisterRequest'
        - $ref: '#/components/schemas/WsSellRequest'
        - $ref: '#/components/schemas/WsBuyRequest'
        - $ref: '#/components/schemas/WsTrailingSellRequest'
        - $ref: '#/components/schemas/WsTrailingBuyRequest'
        - $ref: '#/components/schemas/WsTwapSellRequest'
        - $ref: '#/components/schemas/WsTwapBuyRequest'
        - $ref: '#/components/schemas/WsLimitTwapSellRequest'
        - $ref: '#/components/schemas/WsLimitTwapBuyRequest'
        - $ref: '#/components/schemas/WsTrailingTwapSellRequest'
        - $ref: '#/components/schemas/WsTrailingTwapBuyRequest'
        - $ref: '#/components/schemas/WsLimitTrailingSellRequest'
        - $ref: '#/components/schemas/WsLimitTrailingBuyRequest'
        - $ref: '#/components/schemas/WsLimitTrailingTwapSellRequest'
        - $ref: '#/components/schemas/WsLimitTrailingTwapBuyRequest'
        - $ref: '#/components/schemas/WsCheckOrderRequest'
        - $ref: '#/components/schemas/WsListOrdersRequest'
        - $ref: '#/components/schemas/WsCancelOrderRequest'
        - $ref: '#/components/schemas/WsExtendOrderRequest'
        server_to_client:
        - $ref: '#/components/schemas/WsSubscribed'
        - $ref: '#/components/schemas/WsChallenge'
        - $ref: '#/components/schemas/WsRegistered'
        - $ref: '#/components/schemas/WsHeartbeat'
        - $ref: '#/components/schemas/WsError'
        - $ref: '#/components/schemas/WsEcho'
        - $ref: '#/components/schemas/WsOrderCreated'
        - $ref: '#/components/schemas/WsOrderFilled'
        - $ref: '#/components/schemas/WsTwapOrderCreated'
        - $ref: '#/components/schemas/WsTwapExecution'
        - $ref: '#/components/schemas/WsTwapOrderCompleted'
        - $ref: '#/components/schemas/WsTwapOrderCancelled'
        - $ref: '#/components/schemas/WsOrderStatus'
        - $ref: '#/components/schemas/WsOrderList'
        - $ref: '#/components/schemas/WsOrderCancelled'
        - $ref: '#/components/schemas/WsOrderExtended'
components:
  schemas:
    SwapRequest:
      type: object
      required:
      - wallet_address
      - token_address
      - action
      properties:
        wallet_address:
          type: string
          description: Solana wallet public key
        token_address:
          type: string
          description: SPL token mint address
        action:
          type: string
          enum:
          - buy
          - sell
          description: 'Buy: amount is required (lamports). Sell: send at least one of amount or holdings_percentage (bps, 10000=100%).'
        amount:
          type: integer
          description: Lamports for buy
          minimum: 1
        holdings_percentage:
          type: integer
          description: For sell only. Bps of wallet balance (10000=100%).
          minimum: 1
          maximum: 10000
        slippage:
          type: integer
          default: 500
          description: Basis points. API default 500 (5%). Allowed range 100-2500. For low-liquidity/new tokens, 1500-2500 is commonly required.
          minimum: 100
          maximum: 2500
          x-recommended: 1500
      allOf:
      - if:
          properties:
            action:
              const: buy
        then:
          required:
          - amount
        else:
          anyOf:
          - required:
            - amount
          - required:
            - holdings_percentage
    SwapResponse:
      type: object
      required:
      - status
      - data
      properties:
        status:
          const: success
        data:
          allOf:
          - $ref: '#/components/schemas/SwapData'
          - type: object
            required:
            - swap_tx
    SwapData:
      type: object
      properties:
        token_address:
          type: string
        pool_type:
          type: string
          description: DEX route type (open enum; e.g. raydium, pumpswap, orca, meteora). Handle unknown values gracefully.
        pool_address:
          type: string
          description: AMM pool id
        amount_in:
          type: integer
        min_amount_out:
          type: integer
        swap_tx:
          type: string
          description: Base58-encoded unsigned serialized Solana transaction
        price_impact:
          type: number
        slippage:
          type: integer
        decimals:
          type: integer
    WalletScanner:
      type: object
      required:
      - wallet_address
      description: Request body for wallet holdings scan
      properties:
        wallet_address:
          type: string
          description: Solana wallet public key
    HoldingsResponse:
      type: object
      description: Empty object or token list with data key (lowercase). Endpoint is intended to return sellable tokens; clients should still defensively filter valueNative > 0.
      properties:
        data:
          type: array
          items:
            type: object
            description: Token/pool data
            properties:
              address:
                type: string
                description: SPL token mint
              valueNative:
                type:
                - integer
                - 'null'
                description: Native-value estimate used for sellability checks
              amount:
                type: integer
                description: Raw token amount
              decimals:
                type: integer
                description: Token decimals
            required:
            - address
            - amount
            - decimals
            additionalProperties: true
    ProtectRequest:
      type: object
      required:
      - signed_tx_base64
      description: 'Request body for /protect: submit a base64-encoded signed tx; sent through MEV protected priority lane.'
      properties:
        signed_tx_base64:
          type: string
          description: Base64-encoded signed serialized Solana transaction
          minLength: 100
          pattern: ^[A-Za-z0-9+/]+=*$
    ProtectResponse:
      type: object
      description: 'On success: status, signature, sol_balance_pre, sol_balance_post, token_balances (for updating holdings). On error: status and error message.'
      properties:
        status:
          type: string
          enum:
          - success
          - error
        signature:
          type: string
          description: Transaction signature (when status=success)
        sol_balance_pre:
          type: integer
          description: Wallet SOL balance in lamports before tx (when status=success)
        sol_balance_post:
          type: integer
          description: Wallet SOL balance in lamports after tx (when status=success)
        token_balances:
          type: array
          description: Token balance entries after tx; use to update holdings (when status=success)
          items:
            type: object
            properties:
              mint:
                type: string
                description: SPL token mint address
              balance:
                type: integer
                description: Raw balance
              decimals:
                type: integer
              balance_change:
                type: integer
                description: Change from this tx
              ui_amount_string:
                type: string
                description: Human-readable amount
        error:
          type: string
          description: Error message (when status=error)
    SecurityResponse:
      type: object
      description: 'GET /security response: server signing public keys (base58) for verifying order_filled.server_signature'
      properties:
        server_signing_public_key:
          type: string
          description: Current server signing public key (base58)
        server_signing_public_key_next:
          type: string
          description: Optional next key (base58); used during 14-day rotation
    WsRegisterRequest:
      type: object
      required:
      - action
      - wallet_address
      description: 'Sent after server sends challenge or subscribed. When server sent {"type":"challenge","nonce":"..."}, signature is required: sign the nonce (UTF-8 bytes) with the wallet private key (Ed25519) and encode as base58. Without it the server returns error and authenticated remains false; order actions are then rejected.'
      properties:
        action:
          type: string
          const: register
        wallet_address:
          type: string
          description: Solana wallet public key
        signature:
          type: string
          description: Base58 Ed25519 signature of the challenge nonce (UTF-8 bytes)
    WsSubscribed:
      type: object
      description: Initial subscription confirmation if challenge nonce is unavailable.
      properties:
        type:
          type: string
          const: subscribed
        message:
          type: string
    WsChallenge:
      type: object
      description: Server sends on connect (or when nonce is available). Client must respond with register (wallet_address + signature of this nonce).
      properties:
        type:
          type: string
          const: challenge
        nonce:
          type: string
          description: Nonce to sign (UTF-8) with wallet private key; send result in register.signature (base58)
        message:
          type: string
          description: Human-readable instruction
    WsAuthRequest:
      type: object
      description: 'Deprecated: signature is sent in register, not a separate auth action. Kept for reference.'
      required:
      - action
      - signature
      properties:
        action:
          type: string
          const: auth
        signature:
          type: string
    WsRegistered:
      type: object
      description: Confirmation that wallet is registered. authenticated true only when client sent register with valid signature (challenge nonce signed with wallet). Only then may client send order actions.
      properties:
        type:
          type: string
          const: registered
        wallet_address:
          type: string
        authenticated:
          type: boolean
          description: True when register included valid signature; required for placing/managing orders
    WsHeartbeat:
      type: object
      description: Periodic keep-alive server message.
      properties:
        type:
          type: string
          const: heartbeat
    WsError:
      type: object
      description: Validation or runtime error message.
      properties:
        type:
          type: string
          const: error
        message:
          type: string
      required:
      - type
      - message
    WsEcho:
      type: object
      description: Echo payload returned for unknown actions.
      properties:
        type:
          type: string
          const: echo
        data:
          type: object
          additionalProperties: true
      required:
      - type
    WsOrderCreated:
      type: object
      description: 'Response after creating an order. When params_hash and server_signature are present (Rec 2): params_hash is SHA-256 hex of pipe-delimited canonical params (order_id|token_address|order_type|target_bps or trail_bps|slippage|expiry_hours|amount|holdings_percentage). Server signs SHA-256(params_hash); verify with GET /security public key.'
      required:
      - type
      - order_id
      - order_type
      - entry_mcap
      - target_mcap
      properties:
        type:
          type: string
          const: order_created
        order_id:
          type: string
          format: uuid
        order_type:
          type: string
          enum:
          - sell
          - buy
          - trailing_sell
          - trailing_buy
          - twap_buy
          - twap_sell
          - limit_twap_sell
          - limit_twap_buy
          - trailing_twap_sell
          - trailing_twap_buy
          - limit_trailing_sell
          - limit_trailing_buy
          - limit_trailing_twap_sell
          - limit_trailing_twap_buy
        entry_mcap:
          type: number
        target_mcap:
          type: number
        params_hash:
          type: string
          description: SHA-256 hex of canonical params (pipe-delimited); client can verify on order_filled
        server_signature:
          type: string
          description: Server Ed25519 signature (base58) over params_hash
    WsOrderFilled:
      type: object
      description: 'Server push when mcap crosses the target. Verification (required): Build canonical JSON from keys order_id, order_type, status, token_address, entry_mcap, triggered_mcap, filled_mcap, target_mcap, triggered_at, filled_at, data (only keys present and non-null); sort keys; no whitespace (e.g. separators=('','','':'')). SHA-256(UTF-8(canonical)); verify server_signature (base58 Ed25519) over that digest with GET /security server_signing_public_key. If verification fails or server_signature missing when expected, do not sign/submit. If already_dispatched is true, skip sign/submit (idempotent ack). Authenticated sessions get full payload (data.swap_tx). filled_mcap may be 0 or null.'
      properties:
        type:
          type: string
          const: order_filled
        order_id:
          type: string
          format: uuid
        order_type:
          type: string
          enum:
          - sell
          - buy
          - trailing_sell
          - trailing_buy
          - twap_buy
          - twap_sell
          - limit_twap_sell
          - limit_twap_buy
          - trailing_twap_sell
          - trailing_twap_buy
          - limit_trailing_sell
          - limit_trailing_buy
          - limit_trailing_twap_sell
          - limit_trailing_twap_buy
        status:
          type: string
          enum:
          - success
          - trigger_failed
          - error
        server_signature:
          type: string
          description: Ed25519 signature (base58) over canonical JSON payload; verify with GET /security public key
        already_dispatched:
          type: boolean
          description: If true, fill was already sent; skip sign/submit and treat as idempotent ack
        dispatched_at:
          type: number
          description: Unix timestamp (seconds) when fill was first dispatched; present when already_dispatched is true
        target_bps:
          type: integer
          description: 'Limit orders: target in bps vs entry mcap'
        trail_bps:
          type: integer
          description: 'Trailing orders: trail in bps'
        entry_mcap:
          type: number
        triggered_mcap:
          type: number
          description: Market cap (USD) when target was crossed
        filled_mcap:
          type:
          - number
          - 'null'
          description: Market cap (USD) when tx was built and delivered. Can be 0 or null even on valid fills; treat mcap as unreliable in that case.
        target_mcap:
          type: number
        triggered_at:
          type: number
          description: Unix timestamp (seconds) when target was crossed
        filled_at:
          type: number
          description: Unix timestamp (seconds) when tx was built and delivered. Compare with triggered_at to check staleness.
        token_address:
          type: string
        data:
          allOf:
          - $ref: '#/components/schemas/SwapData'
          - type: object
            required:
            - swap_tx
            - token_address
            - pool_type
          description: Present on full order_filled (authenticated). Omitted when already_dispatched or metadata-only (unauthenticated).
      required:
      - type
      - order_id
      - order_type
      - status
    WsCheckOrderRequest:
      type: object
      required:
      - action
      - order_id
      properties:
        action:
          type: string
          const: check_order
        order_id:
          type: string
    WsOrderStatus:
      type: object
      required:
      - type
      - order_id
      - status
      properties:
        type:
          type: string
          const: order_status
        order_id:
          type: string
        status:
          type: string
    WsListOrdersRequest:
      type: object
      required:
      - action
      description: List active orders for wallet
      properties:
        action:
          type: string
          const: list_orders
        wallet_address:
          type: string
    WsOrderList:
      type: object
      required:
      - type
      - orders
      properties:
        type:
          type: string
          const: order_list
        orders:
          type: array
          items:
            type: object
    WsCancelOrderRequest:
      type: object
      required:
      - action
      - order_id
      properties:
        action:
          type: string
          const: cancel_order
        order_id:
          type: string
    WsOrderCancelled:
      type: object
      required:
      - type
      - order_id
      properties:
        type:
          type: string
          const: order_cancelled
        order_id:
          type: string
    WsExtendOrderRequest:
      type: object
      required:
      - action
      - order_id
      - expiry_hours
      properties:
        action:
          type: string
          const: extend_order
        order_id:
          type: string
        expiry_hours:
          type: integer
          maximum: 336
    WsOrderExtended:
      type: object
      required:
      - type
      - order_id
      properties:
        type:
          type: string
          const: order_extended
        order_id:
          type: string
    WsSellRequest:
      type: object
      required:
      - action
      - token_address
      - holdings_percentage
      - target
      - slippage
      description: 'Limit sell: take-profit (target > 10000) or stop-loss (target < 10000). target is relative to current mcap at order placement time.'
      properties:
        action:
          type: string
          const: sell
        token_address:
          type: string
        holdings_percentage:
          type: integer
          description: Bps (10000=100%). Resolved at trigger time.
          minimum: 1
          maximum: 10000
        slippage:
          type: integer
          minimum: 100
          maximum: 2500
          description: Basis points. Allowed 100-2500. 1500-2500 recommended for low-liquidity/new tokens.
          x-recommended: 1500
        target:
          type: integer
          description: Mcap target in bps relative to current mcap at order placement time (not wallet entry price). Any positive integer; no maximum. e.g. 20000 = doubles, 5000 = halves.
          minimum: 1
        expiry_hours:
          type: integer
          default: 144
          maximum: 336
        wallet_address:
          type: string
          description: Optional if already registered.
    WsBuyRequest:
      type: object
      required:
      - action
      - token_address
      - amount
      - target
      - slippage
      description: 'Limit buy: dip buy (target < 10000) or breakout entry (target > 10000). target is relative to current mcap at order placement time.'
      properties:
        action:
          type: string
          const: buy
        token_address:
          type: string
        amount:
          type: integer
          description: Lamports.
          minimum: 1
        slippage:
          type: integer
          minimum: 100
          maximum: 2500
          description: Basis points. Allowed 100-2500. 1500-2500 recommended for low-liquidity/new tokens.
          x-recommended: 1500
        target:
          type: integer
          description: Mcap target in bps relative to current mcap at order placement time (not wallet entry price). Any positive integer; no maximum. e.g. 5000 = halves, 20000 = doubles.
          minimum: 1
        expiry_hours:
          type: integer
          default: 144
          maximum: 336
        wallet_address:
          type: string
          description: Optional if already registered.
    WsTrailingSellRequest:
      type: object
      required:
      - action
      - token_address
      - holdings_percentage
      - trail
      - slippage
      description: Trailing stop sell. Uses holdings_percentage and trail (bps).
      properties:
        action:
          type: string
          const: trailing_sell
        token_address:
          type: string
        holdings_percentage:
          type: integer
          description: Bps (10000=100%). Resolved at trigger time.
          minimum: 1
          maximum: 10000
        trail:
          type: integer
          description: Trail distance in bps.
          minimum: 1
        slippage:
          type: integer
          minimum: 100
          maximum: 2500
          description: Basis points. Allowed 100-2500. 1500-2500 recommended for low-liquidity/new tokens.
          x-recommended: 1500
        expiry_hours:
          type: integer
          default: 144
          maximum: 336
    WsTrailingBuyRequest:
      type: object
      required:
      - action
      - token_address
      - amount
      - trail
      - slippage
      description: Trailing stop buy. Uses amount (lamports) and trail (bps).
      properties:
        action:
          type: string
          const: trailing_buy
        token_address:
          type: string
        amount:
          type: integer
          description: Lamports.
          minimum: 1
        trail:
          type: integer
          description: Trail distance in bps.
          minimum: 1
        slippage:
          type: integer
          minimum: 100
          maximum: 2500
          description: Basis points. Allowed 100-2500. 1500-2500 recommended for low-liquidity/new tokens.
          x-recommended: 1500
        expiry_hours:
          type: integer
          default: 144
          maximum: 336
    WsTwapSellRequest:
      type: object
      required:
      - action
      - token_address
      - frequency
      - duration
      description: 'TWAP sell: split total amount into frequency slices over duration seconds. Use amount (raw token units) or holdings_percentage (bps, resolved once at creation).'
      properties:
        action:
          type: string
          const: twap_sell
        token_address:
          type: string
        frequency:
          type: integer
          minimum: 1
          maximum: 100
          description: Number of executions
        duration:
          type: integer
          minimum: 60
          description: Total run time in seconds (max 30 days)
        amount:
          type: integer
          description: Total raw token units
        holdings_percentage:
          type: integer
          minimum: 1
          maximum: 10000
          description: Bps of holdings at creation (sell only)
        slippage:
          type: integer
          default: 500
          minimum: 100
          maximum: 2500
    WsTwapBuyRequest:
      type: object
      required:
      - action
      - token_address
      - frequency
      - duration
      - amount
      description: 'TWAP buy: split total amount (SOL lamports) into frequency slices over duration seconds.'
      properties:
        action:
          type: string
          const: twap_buy
        token_address:
          type: string
        frequency:
          type: integer
          minimum: 1
          maximum: 100
        duration:
          type: integer
          minimum: 60
        amount:
          type: integer
          description: Total SOL lamports
        slippage:
          type: integer
          default: 500
          minimum: 100
          maximum: 2500
    WsLimitTwapSellRequest:
      type: object
      required:
      - action
      - token_address
      - target
      - frequency
      - duration
      description: 'Limit then TWAP sell: wait for target (bps), then execute via TWAP. Use amount or holdings_percentage for sell.'
      properties:
        action:
          type: string
          const: limit_twap_sell
        token_address:
          type: string
        target:
          type: integer
          description: Target mcap bps vs entry. Any positive integer; no maximum.
        frequency:
          type: integer
          minimum: 1
          maximum: 100
        duration:
          type: integer
          minimum: 60
        amount:
          type: integer
        holdings_percentage:
          type: integer
          minimum: 1
          maximum: 10000
        slippage:
          type: integer
          default: 500
          minimum: 100
          maximum: 2500
        expiry_hours:
          type: integer
          default: 144
          maximum: 336
    WsLimitTwapBuyRequest:
      type: object
      required:
      - action
      - token_address
      - target
      - amount
      - frequency
      - duration
      description: 'Limit then TWAP buy: wait for target (bps), then execute via TWAP.'
      properties:
        action:
          type: string
          const: limit_twap_buy
        token_address:
          type: string
        target:
          type: integer
          description: Target mcap bps vs entry. Any positive integer; no maximum.
        amount:
          type: integer
          description: Lamports
        frequency:
          type: integer
          minimum: 1
          maximum: 100
        duration:
          type: integer
          minimum: 60
        slippage:
          type: integer
          default: 500
          minimum: 100
          maximum: 2500
        expiry_hours:
          type: integer
          default: 144
          maximum: 336
    WsTrailingTwapSellRequest:
      type: object
      required:
      - action
      - token_address
      - trail
      - frequency
      - duration
      description: 'Trailing then TWAP sell: when trail triggers, spawn TWAP. Use amount or holdings_percentage for sell.'
      properties:
        action:
          type: string
          const: trailing_twap_sell
        token_address:
          type: string
        trail:
          type: integer
          description: Trail bps
        frequency:
          type: integer
          minimum: 1
          maximum: 100
        duration:
          type: integer
          minimum: 60
        amount:
          type: integer
        holdings_percentage:
          type: integer
          minimum: 1
          maximum: 10000
        slippage:
          type: integer
          default: 500
          minimum: 100
          maximum: 2500
        expiry_hours:
          type: integer
          default: 144
          maximum: 336
    WsTrailingTwapBuyRequest:
      type: object
      required:
      - action
      - token_address
      - trail
      - amount
      - frequency
      - duration
      description: 'Trailing then TWAP buy: when trail triggers, spawn TWAP.'
      properties:
        action:
          type: string
          const: trailing_twap_buy
        token_address:
          type: string
        trail:
          type: integer
          description: Trail bps
        amount:
          type: integer
          description: Lamports
        frequency:
          type: integer
          minimum: 1
          maximum: 100
        duration:
          type: integer
          minimum: 60
        slippage:
          type: integer
          default: 500
          minimum: 100
          maximum: 2500
        expiry_hours:
          type: integer
          default: 144
          maximum: 336
    WsLimitTrailingSellRequest:
      type: object
      required:
      - action
      - token_address
      - target
      - trail
      description: 'Limit then trailing sell: wait for target, then trailing stop; when trail triggers, single swap. Use amount or holdings_percentage for sell.'
      properties:
        action:
          type: string
          const: limit_trailing_sell
        token_address:
          type: string
        target:
          type: integer
          description: Target mcap bps vs entry. Any positive integer; no maximum.
        trail:
          type: integer
          description: Trail bps
        amount:
          type: integer
        holdings_percentage:
          type: integer
          minimum: 1
          maximum: 10000
        slippage:
          type: integer
          default: 500
          minimum: 100
          maximum: 2500
        expiry_hours:
          type: integer
          default: 144
          maximum: 336
    WsLimitTrailingBuyRequest:
      type: object
      required:
      - action
      - token_address
      - target
      - trail
      - amount
      description: 'Limit then trailing buy: wait for target, then trailing stop; when trail triggers, single swap.'
      properties:
        action:
          type: string
          const: limit_trailing_buy
        token_address:
          type: string
        target:
          type: integer
          description: Target mcap bps vs entry. Any positive integer; no maximum.
        trail:
          type: integer
          description: Trail bps
        amount:
          type: integer
          description: Lamports
        slippage:
          type: integer
          default: 500
          minimum: 100
          maximum: 2500
        expiry_hours:
          type: integer
          default: 144
          maximum: 336
    WsLimitTrailingTwapSellRequest:
      type: object
      required:
      - action
      - token_address
      - target
      - trail
      - frequency
      - duration
      description: 'Limit then trailing then TWAP sell: limit → trail → on trail trigger, spawn TWAP. Use amount or holdings_percentage for sell.'
      properties:
        action:
          type: string
          const: limit_trailing_twap_sell
        token_address:
          type: string
        target:
          type: integer
          description: Target mcap bps vs entry. Any positive integer; no maximum.
        trail:
          type: integer
          description: Trail bps
        frequency:
          type: integer
          minimum: 1
          maximum: 100
        duration:
          type: integer
          minimum: 60
        amount:
          type: integer
        holdings_percentage:
          type: integer
          minimum: 1
          maximum: 10000
        slippage:
          type: integer
          default: 500
          minimum: 100
          maximum: 2500
        expiry_hours:
          type: integer
          default: 144
          maximum: 336
    WsLimitTrailingTwapBuyRequest:
      type: object
      required:
      - action
      - token_address
      - target
      - trail
      - amount
      - frequency
      - duration
      description: 'Limit then trailing then TWAP buy: limit → trail → on trail trigger, spawn TWAP.'
      properties:
        action:
          type: string
          const: limit_trailing_twap_buy
        token_address:
          type: string
        target:
          type: integer
          description: Target mcap bps vs entry. Any positive integer; no maximum.
        trail:
          type: integer
          description: Trail bps
        amount:
          type: integer
          description: Lamports
        frequency:
          type: integer
          minimum: 1
          maximum: 100
        duration:
          type: integer
          minimum: 60
        slippage:
          type: integer
          default: 500
          minimum: 100
          maximum: 2500
        expiry_hours:
          type: integer
          default: 144
          maximum: 336
    WsTwapOrderCreated:
      type: object
      description: Server response when TWAP order is accepted.
      properties:
        type:
          type: string
          const: twap_order_created
        order_id:
          type: string
          format: uuid
        order_type:
          type: string
          enum:
          - twap_buy
          - twap_sell
        token_address:
          type: string
        pair_address:
          type: string
        frequency:
          type: integer
        duration:
          type: integer
        interval_seconds:
          type: number
        amount_per_execution:
          type: integer
        original_amount:
          type: integer
        slippage:
          type: integer
        expires_at:
          type: number
        holdings_percentage:
          type: integer
    WsTwapExecution:
      type: object
      description: 'One TWAP slice. Verify server_signature before signing/submitting data.swap_tx. Signed string: order_id|order_type|execution_num|executions_total|status|token_address.'
      properties:
        type:
          type: string
          const: twap_execution
        order_id:
          type: string
        order_type:
          type: string
          enum:
          - twap_buy
          - twap_sell
        status:
          type: string
          enum:
          - success
          - error
        token_address:
          type: string
        execution_num:
          type: integer
        executions_total:
          type: integer
        executions_remaining:
          type: integer
        next_execution_at:
          type:
          - number
          - 'null'
        server_signature:
          type: string
        data:
          $ref: '#/components/schemas/SwapData'
        error:
          type: string
    WsTwapOrderCompleted:
      type: object
      properties:
        type:
          type: string
          const: twap_order_completed
        order_id:
          type: string
        order_type:
          type: string
        token_address:
          type: string
        executions_completed:
          type: integer
        status:
          type: string
          const: completed
    WsTwapOrderCancelled:
      type: object
      properties:
        type:
          type: string
          const: twap_order_cancelled
        order_id:
          type: string
        status:
          type: string
          const: cancelled
