{
  "openapi": "3.1.0",
  "info": {
    "title": "Trade Router \u2013 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 \u2014 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" }
        }
      }
    }
  }
}