openapi: 3.1.0
info:
  title: Lane API
  version: "1"
  summary: Quotes, ready-to-sign swap transactions, limit orders and DCA on Elysium.
  description: |
    Every amount is a decimal string in base units (wei). Native HYPE is written `HYPE`
    (also accepted: `0x0000000000000000000000000000000000000000`, `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE`).
    Every response carries an `x-request-id` header; errors are `{ "error": string, "requestId": string }`.
    Unversioned paths (`/quote`, ...) are aliases of `/v1/...` (use `/v1`).
servers:
  - url: https://api.lane.markets/v1
    description: Lane
  - url: https://api-production-7070.up.railway.app/v1
    description: Testnet instance (Railway)
  - url: http://localhost:3000/v1
    description: Local instance (`pnpm start`)
security:
  - {}
  - partnerKey: []
tags:
  - { name: Swap }
  - { name: Tokens }
  - { name: Orders }
  - { name: Account }
  - { name: Pools }
  - { name: Liquidity }
  - { name: Analytics }
  - { name: Keys }
  - { name: Share }
  - { name: System }

paths:
  /quote:
    get:
      tags: [Swap]
      summary: Best route, exact quote and a simulated swap transaction
      description: |
        Finds the best route (up to 3 swaps across V2/V3/V4, and up to 3 pool-disjoint split legs),
        quotes it exactly on-chain through `Executor.quote`, builds `Executor.swap` calldata and simulates it
        from `sender`. Send `approval` first when it is not null. Never send `tx` when `simulation.ok` is false.
      parameters:
        - { name: in, in: query, required: true, schema: { $ref: "#/components/schemas/TokenId" } }
        - { name: out, in: query, required: true, schema: { $ref: "#/components/schemas/TokenId" } }
        - { name: amount, in: query, required: true, description: Input amount in base units, schema: { $ref: "#/components/schemas/Uint" } }
        - { name: recipient, in: query, required: true, description: Receives the output, schema: { $ref: "#/components/schemas/Address" } }
        - { name: sender, in: query, description: "Signer of the tx, used for the simulation (default: recipient)", schema: { $ref: "#/components/schemas/Address" } }
        - { name: slippageBps, in: query, description: "Tolerance applied to amountOut to get minOut (0-5000), or `auto` for slippage.recommendedBps", schema: { oneOf: [{ type: integer, minimum: 0, maximum: 5000 }, { type: string, enum: [auto] }], default: 50 } }
        - { name: permit2, in: query, description: "ERC20 input only: return Permit2 typed data instead of requiring an approve to the Executor", schema: { type: string, enum: ["true"] } }
        - { name: signature, in: query, description: Permit2 signature (65 bytes hex), 2nd call of the Permit2 flow, schema: { type: string, pattern: "^0x[0-9a-fA-F]{130}$" } }
        - { name: permit2Nonce, in: query, description: Nonce from the 1st call's typedData, schema: { $ref: "#/components/schemas/Uint" } }
        - { name: permit2Deadline, in: query, description: Deadline from the 1st call's typedData, schema: { $ref: "#/components/schemas/Uint" } }
      responses:
        "200":
          description: Quote and transaction
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Quote" }
        "400": { $ref: "#/components/responses/Error" }
        "404": { description: No route between the two tokens, content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
        "422": { description: "Output token is a honeypot (can be bought, not sold)", content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
        "429": { $ref: "#/components/responses/RateLimited" }
        "503": { description: Indexer still backfilling, or Executor not configured, content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }

  /price:
    get:
      tags: [Tokens]
      summary: Spot USDC price of whole tokens
      description: Computed in memory along the best route (net of LP fees, no size impact). No RPC call.
      parameters:
        - { name: ids, in: query, required: true, description: "Comma-separated token ids (max 100)", schema: { type: string, example: "HYPE,0x7D29d8047B905000459c0E80c34a26CeedcB47b2" } }
      responses:
        "200":
          description: Prices
          content:
            application/json:
              schema:
                type: object
                properties:
                  quote: { type: string, const: USDC }
                  data: { type: object, additionalProperties: { type: [number, "null"] } }
        "400": { $ref: "#/components/responses/Error" }

  /tokens:
    get:
      tags: [Tokens]
      summary: Routable tokens, ranked
      parameters:
        - { name: q, in: query, description: Matches symbol, name, or exact address, schema: { type: string } }
        - { name: limit, in: query, schema: { type: integer, default: 50, maximum: 500 } }
        - { name: strict, in: query, description: "`true` = curated verified list only", schema: { type: string, enum: ["true"] } }
        - { name: sort, in: query, description: "`volume` = popular tokens by 24h volume", schema: { type: string, enum: [pools, volume], default: pools } }
      responses:
        "200":
          description: Tokens (native HYPE first, then verified, then by live pool count)
          content:
            application/json:
              schema:
                type: object
                properties:
                  tokens: { type: array, items: { $ref: "#/components/schemas/Token" } }

  /tokens/{address}:
    get:
      tags: [Tokens]
      summary: One token, with trust info and the honeypot/tax probe
      description: Runs the probe now if this token was never checked.
      parameters:
        - { name: address, in: path, required: true, schema: { $ref: "#/components/schemas/Address" } }
      responses:
        "200":
          description: Token
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/Token"
                  - type: object
                    properties: { price: { type: [number, "null"] } }
        "400": { $ref: "#/components/responses/Error" }
        "404": { description: Not in any indexed pool, content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }

  /orders:
    get:
      tags: [Orders]
      summary: Open limit/DCA orders
      parameters:
        - { name: owner, in: query, schema: { $ref: "#/components/schemas/Address" } }
      responses:
        "200":
          description: Orders
          content:
            application/json:
              schema:
                type: object
                properties:
                  orders: { type: array, items: { $ref: "#/components/schemas/Order" } }
        "503": { description: OrderBook not configured, content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }

  /orders/create:
    get:
      tags: [Orders]
      summary: Calldata to place a limit order (slices=1) or a DCA (slices>1)
      parameters:
        - { name: in, in: query, required: true, schema: { $ref: "#/components/schemas/TokenId" } }
        - { name: out, in: query, required: true, schema: { $ref: "#/components/schemas/TokenId" } }
        - { name: amount, in: query, required: true, description: Total input, base units, schema: { $ref: "#/components/schemas/Uint" } }
        - { name: owner, in: query, required: true, schema: { $ref: "#/components/schemas/Address" } }
        - { name: minOut, in: query, required: true, description: "Minimum output for the WHOLE amount (> 0), enforced pro rata per slice", schema: { $ref: "#/components/schemas/Uint" } }
        - { name: slices, in: query, schema: { type: integer, minimum: 1, maximum: 1000, default: 1 } }
        - { name: interval, in: query, description: Seconds between slices (required if slices > 1), schema: { type: integer, default: 0 } }
        - { name: expiresIn, in: query, description: Seconds until expiry, schema: { type: integer, default: 604800 } }
      responses:
        "200":
          description: Simulated create transaction
          content:
            application/json:
              schema:
                type: object
                properties:
                  order:
                    type: object
                    properties:
                      perSlice: { $ref: "#/components/schemas/Uint" }
                      minOutPerSlice: { $ref: "#/components/schemas/Uint" }
                      slices: { $ref: "#/components/schemas/Uint" }
                      interval: { $ref: "#/components/schemas/Uint" }
                      expiry: { $ref: "#/components/schemas/Uint" }
                  tx: { $ref: "#/components/schemas/Tx" }
                  approval: { $ref: "#/components/schemas/Approval" }
                  simulation: { $ref: "#/components/schemas/Simulation" }
        "400": { $ref: "#/components/responses/Error" }

  /orders/cancel:
    get:
      tags: [Orders]
      summary: Calldata to cancel an order
      description: The owner can cancel any time; anyone can cancel after expiry. The refund always goes to the owner.
      parameters:
        - { name: id, in: query, required: true, schema: { $ref: "#/components/schemas/Uint" } }
      responses:
        "200":
          description: Cancel transaction
          content:
            application/json:
              schema:
                type: object
                properties: { tx: { $ref: "#/components/schemas/Tx" } }

  /portfolio:
    get:
      tags: [Account]
      summary: Native and token balances, valued in USD
      parameters:
        - { name: owner, in: query, required: true, schema: { $ref: "#/components/schemas/Address" } }
      responses:
        "200":
          description: Portfolio
          content:
            application/json:
              schema:
                type: object
                properties:
                  owner: { $ref: "#/components/schemas/Address" }
                  totalUsd: { type: number }
                  tokens:
                    type: array
                    items:
                      type: object
                      properties:
                        address: { type: string }
                        symbol: { type: string }
                        name: { type: string }
                        decimals: { type: integer }
                        balance: { $ref: "#/components/schemas/Uint" }
                        price: { type: [number, "null"] }
                        usd: { type: [number, "null"] }

  /history:
    get:
      tags: [Account]
      summary: Swaps and order lifecycle events of an address
      parameters:
        - { name: owner, in: query, required: true, schema: { $ref: "#/components/schemas/Address" } }
        - { name: limit, in: query, schema: { type: integer, default: 50, maximum: 200 } }
      responses:
        "200":
          description: History, newest first
          content:
            application/json:
              schema:
                type: object
                properties:
                  owner: { $ref: "#/components/schemas/Address" }
                  items:
                    type: array
                    items:
                      type: object
                      required: [type, tx, block, time]
                      properties:
                        type: { type: string, enum: [Swapped, OrderCreated, OrderFilled, OrderCancelled] }
                        tx: { type: string }
                        block: { $ref: "#/components/schemas/Uint" }
                        time: { type: integer, description: Block timestamp (s) }
                      additionalProperties: true

  /health:
    get:
      tags: [System]
      summary: Readiness and freshness (not rate limited; ?strict=1 returns 503 unless fresh)
      parameters: [{ name: strict, in: query, schema: { type: string, enum: ["1"] } }]
      responses:
        "200":
          description: Health
          content:
            application/json:
              schema:
                type: object
                properties:
                  ready: { type: boolean }
                  lastBlock: { $ref: "#/components/schemas/Uint" }
                  fresh: { type: boolean }
                  reasons: { type: array, items: { type: string } }
                  chainHead: { $ref: "#/components/schemas/Uint" }
                  lagBlocks: { type: integer }
                  lastSyncSecondsAgo: { type: [integer, "null"] }
                  keeper: { type: [object, "null"], properties: { address: { type: string }, balanceHype: { type: [string, "null"] } } }
                  pools: { type: object, properties: { v3: { type: integer }, v2: { type: integer }, v4: { type: integer }, solidly: { type: integer } } }
                  executor: { type: [string, "null"] }
                  orderBook: { type: [string, "null"] }
                  keeperEnabled: { type: boolean }

  /config:
    get:
      tags: [System]
      summary: Network the instance serves (for wallets)
      responses:
        "200":
          description: Config
          content:
            application/json:
              schema:
                type: object
                properties:
                  name: { type: string }
                  chainId: { type: integer }
                  rpcUrl: { type: string }
                  explorer: { type: string }

  /ws/prices:
    get:
      tags: [Tokens]
      summary: "WebSocket: live prices"
      description: |
        Upgrade to a WebSocket, then send `{"subscribe": ["HYPE", "0x…"]}` (max 100 ids).
        The server answers `{"prices": {id: price}}` at once, then pushes only the ids whose price moved, each block.
      responses:
        "101": { description: Switching protocols }

  /portfolio/pnl:
    get:
      tags: [Account]
      summary: Average-cost PnL, positions, calendar, distribution, equity curve
      parameters: [{ name: owner, in: query, required: true, schema: { $ref: "#/components/schemas/Address" } }]
      responses:
        "200": { description: PnL, content: { application/json: { schema: { type: object, properties: { summary: { type: object }, positions: { type: array, items: { type: object } }, calendar: { type: array, items: { type: object } }, distribution: { type: array, items: { type: object } }, equity: { type: array, items: { type: object } }, notes: { type: array, items: { type: string } } } } } } }

  /portfolio/transfers:
    get:
      tags: [Account]
      summary: Token transfers (explorer API), swap legs flagged
      parameters:
        - { name: owner, in: query, required: true, schema: { $ref: "#/components/schemas/Address" } }
        - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 200, default: 50 } }
      responses:
        "200": { description: Transfers, content: { application/json: { schema: { type: object, properties: { items: { type: array, items: { type: object } }, source: { type: [string, "null"] } } } } } }
        "502": { description: Explorer API unavailable, content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }

  /notifications:
    get:
      tags: [Account]
      summary: Order notifications, newest first
      parameters:
        - { name: owner, in: query, required: true, schema: { $ref: "#/components/schemas/Address" } }
        - { name: since, in: query, description: Last id seen, schema: { type: integer, default: 0 } }
        - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 200, default: 50 } }
      responses:
        "200":
          description: Notifications
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      type: object
                      properties:
                        id: { type: integer }
                        ts: { type: integer }
                        type: { type: string, enum: [order_created, order_filled, order_completed, order_cancelled, order_expired, dca_skipped] }
                        orderId: { type: [string, "null"] }
                        tx: { type: [string, "null"] }
                        data: { type: object }

  /ws/account:
    get:
      tags: [Account]
      summary: "WebSocket: send {\"owner\": \"0x…\"}, receive {\"notification\": {...}}"
      responses: { "101": { description: Switching protocols } }

  /pools:
    get:
      tags: [Pools]
      summary: Pools with TVL, volumes, fees, price, market cap, age
      parameters:
        - { name: venue, in: query, description: "Substring of the venue name (uniswap, aero, nest, solidly, a launchpad)", schema: { type: string } }
        - { name: token, in: query, schema: { $ref: "#/components/schemas/TokenId" } }
        - { name: sort, in: query, schema: { type: string, enum: [tvl, volume24h, fees24h, newest], default: tvl } }
        - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 200, default: 50 } }
        - { name: offset, in: query, schema: { type: integer, minimum: 0, default: 0 } }
      responses:
        "200": { description: Pools, content: { application/json: { schema: { type: object, properties: { total: { type: integer }, pools: { type: array, items: { $ref: "#/components/schemas/Pool" } } } } } } }

  /pools/stats:
    get:
      tags: [Pools]
      summary: Totals for the Pools page header
      responses:
        "200": { description: Totals, content: { application/json: { schema: { type: object, properties: { tvlUsd: { type: number }, pools: { type: integer }, volume24hUsd: { type: number }, fees24hUsd: { type: number }, swaps24h: { type: integer }, openPositions: { type: [integer, "null"] } } } } } }

  /pools/{id}:
    get:
      tags: [Pools]
      summary: One pool, with APR, 7d TVL change, LP count
      parameters: [{ name: id, in: path, required: true, description: Address, or 32-byte v4 pool id, schema: { type: string } }]
      responses:
        "200": { description: Pool, content: { application/json: { schema: { allOf: [{ $ref: "#/components/schemas/Pool" }, { type: object, properties: { tvlChange7d: { type: [number, "null"] }, aprPct: { type: [number, "null"] }, liquidityProviders: { type: integer }, firstSeen: { type: [integer, "null"] }, key: { type: [object, "null"] } } }] } } } }
        "404": { description: Unknown pool, content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }

  /pools/{id}/chart:
    get:
      tags: [Pools]
      summary: Volume, fees and TVL over time
      parameters:
        - { name: id, in: path, required: true, schema: { type: string } }
        - { name: interval, in: query, schema: { type: string, enum: ["1h", "1d"], default: "1h" } }
        - { name: days, in: query, schema: { type: integer, minimum: 1, maximum: 365 } }
      responses:
        "200": { description: Series, content: { application/json: { schema: { type: object, properties: { interval: { type: string }, volume: { type: array, items: { type: object } }, tvl: { type: array, items: { type: object } } } } } } }

  /pools/{id}/activity:
    get:
      tags: [Pools]
      summary: Swaps, adds, removes of a pool
      parameters:
        - { name: id, in: path, required: true, schema: { type: string } }
        - { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 200, default: 50 } }
        - { name: before, in: query, description: Block number (paging), schema: { type: integer } }
      responses:
        "200": { description: Activity, content: { application/json: { schema: { type: object, properties: { items: { type: array, items: { $ref: "#/components/schemas/Activity" } } } } } } }

  /activity:
    get:
      tags: [Pools]
      summary: Latest activity across all pools
      parameters: [{ name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 200, default: 50 } }]
      responses:
        "200": { description: Activity, content: { application/json: { schema: { type: object, properties: { items: { type: array, items: { $ref: "#/components/schemas/Activity" } } } } } } }

  /ws/activity:
    get:
      tags: [Pools]
      summary: "WebSocket: send {\"pools\": [...]} or {\"all\": true}, receive {\"items\": [...]}"
      responses: { "101": { description: Switching protocols } }

  /candles:
    get:
      tags: [Tokens]
      summary: OHLC candles of a token's USD price
      parameters:
        - { name: token, in: query, required: true, schema: { $ref: "#/components/schemas/TokenId" } }
        - { name: interval, in: query, schema: { type: string, enum: ["1m", "5m", "15m", "1h", "4h", "1d"], default: "1h" } }
        - { name: from, in: query, schema: { type: integer } }
        - { name: to, in: query, schema: { type: integer } }
      responses:
        "200": { description: Candles, content: { application/json: { schema: { type: object, properties: { token: { type: string }, interval: { type: string }, priceUsd: { type: [number, "null"] }, change24h: { type: [number, "null"] }, candles: { type: array, items: { type: object, properties: { t: { type: integer }, o: { type: number }, h: { type: number }, l: { type: number }, c: { type: number } } } } } } } } }
        "404": { description: Unknown token, content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }

  /stats:
    get:
      tags: [Analytics]
      summary: Routed volume, swaps, savings, revenue, daily series, top routes, venues, split share, no-route quotes
      responses:
        "200": { description: Analytics, content: { application/json: { schema: { type: object } } } }

  /keys/challenge:
    get:
      tags: [Keys]
      summary: Message to sign to create an API key
      parameters: [{ name: address, in: query, required: true, schema: { $ref: "#/components/schemas/Address" } }]
      responses:
        "200": { description: Challenge, content: { application/json: { schema: { type: object, properties: { message: { type: string }, expiresAt: { type: integer } } } } } }

  /keys:
    post:
      tags: [Keys]
      summary: Create an API key from a signed challenge (key shown once)
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object, required: [address, signature], properties: { address: { $ref: "#/components/schemas/Address" }, signature: { type: string }, label: { type: string, maxLength: 64 } } }
      responses:
        "200": { description: Key, content: { application/json: { schema: { type: object, properties: { key: { type: string }, prefix: { type: string }, owner: { type: string }, label: { type: [string, "null"] }, perMinute: { type: integer }, createdAt: { type: integer } } } } } }
        "401": { description: Signature mismatch, content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
        "409": { description: Wallet already has 3 active keys, content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
        "429": { description: Too many keys from this IP today, content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }

  /keys/me:
    get:
      tags: [Keys]
      summary: The calling key's limit and 30-day usage
      security: [{ partnerKey: [] }]
      responses:
        "200": { description: Key info, content: { application/json: { schema: { type: object } } } }
        "401": { description: Invalid key, content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
    delete:
      tags: [Keys]
      summary: Revoke the calling key
      security: [{ partnerKey: [] }]
      responses:
        "200": { description: Revoked, content: { application/json: { schema: { type: object, properties: { revoked: { type: boolean } } } } } }

  /share/swap/{tx}.png:
    get:
      tags: [Share]
      summary: 1200x630 card of a Lane swap
      parameters: [{ name: tx, in: path, required: true, schema: { type: string } }]
      responses:
        "200": { description: PNG, content: { image/png: {} } }
        "404": { description: Not a Lane swap, content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }

  /share/pnl/{address}.png:
    get:
      tags: [Share]
      summary: 1200x630 card of a wallet's PnL
      parameters: [{ name: address, in: path, required: true, schema: { $ref: "#/components/schemas/Address" } }]
      responses:
        "200": { description: PNG, content: { image/png: {} } }

  /share/swap/{tx}:
    get:
      tags: [Share]
      summary: Page to post on X (og:image = the swap card), redirects to the app
      parameters: [{ name: tx, in: path, required: true, schema: { type: string } }]
      responses: { "200": { description: HTML, content: { text/html: {} } } }

  /share/pnl/{address}:
    get:
      tags: [Share]
      summary: Page to post on X (og:image = the PnL card), redirects to the app
      parameters: [{ name: address, in: path, required: true, schema: { $ref: "#/components/schemas/Address" } }]
      responses: { "200": { description: HTML, content: { text/html: {} } } }

  /liquidity/managers:
    get:
      tags: [Liquidity]
      summary: Known position managers (Uniswap V3 NonfungiblePositionManager, V4 PositionManager)
      responses: { "200": { description: Managers, content: { application/json: { schema: { type: object, properties: { managers: { type: array, items: { type: object } } } } } } } }

  /liquidity/positions:
    get:
      tags: [Liquidity]
      summary: A wallet's V3/V4 positions with value, uncollected fees, time in range, APR estimate
      parameters: [{ name: owner, in: query, required: true, schema: { $ref: "#/components/schemas/Address" } }]
      responses: { "200": { description: Positions, content: { application/json: { schema: { type: object, properties: { positions: { type: array, items: { $ref: "#/components/schemas/LpPosition" } } } } } } } }

  /liquidity/quote:
    get:
      tags: [Liquidity]
      summary: Range preview (aligned ticks, prices, liquidity, the other amount)
      parameters:
        - { name: pool, in: query, required: true, schema: { type: string } }
        - { name: tickLower, in: query, schema: { type: integer } }
        - { name: tickUpper, in: query, schema: { type: integer } }
        - { name: priceLower, in: query, description: token1 per token0, human units, schema: { type: number } }
        - { name: priceUpper, in: query, schema: { type: number } }
        - { name: fullRange, in: query, schema: { type: string, enum: ["true"] } }
        - { name: amount0, in: query, schema: { $ref: "#/components/schemas/Uint" } }
        - { name: amount1, in: query, schema: { $ref: "#/components/schemas/Uint" } }
      responses: { "200": { description: Preview, content: { application/json: { schema: { type: object } } } } }

  /liquidity/add:
    get:
      tags: [Liquidity]
      summary: Build a new position (or add to tokenId)
      parameters:
        - { name: pool, in: query, required: true, schema: { type: string } }
        - { name: owner, in: query, required: true, schema: { $ref: "#/components/schemas/Address" } }
        - { name: tickLower, in: query, schema: { type: integer } }
        - { name: tickUpper, in: query, schema: { type: integer } }
        - { name: priceLower, in: query, description: token1 per token0, human units, schema: { type: number } }
        - { name: priceUpper, in: query, schema: { type: number } }
        - { name: fullRange, in: query, schema: { type: string, enum: ["true"] } }
        - { name: amount0, in: query, schema: { $ref: "#/components/schemas/Uint" } }
        - { name: amount1, in: query, schema: { $ref: "#/components/schemas/Uint" } }
        - { name: tokenId, in: query, schema: { $ref: "#/components/schemas/Uint" } }
        - { name: native, in: query, schema: { type: string, enum: ["true"] } }
        - { name: slippageBps, in: query, schema: { type: integer, minimum: 0, maximum: 5000, default: 50 } }
      responses: { "200": { description: Transaction, content: { application/json: { schema: { $ref: "#/components/schemas/LpTx" } } } } }

  /liquidity/remove:
    get:
      tags: [Liquidity]
      summary: Remove a share of a position (and collect); 100% burns it
      parameters:
        - { name: manager, in: query, required: true, schema: { $ref: "#/components/schemas/Address" } }
        - { name: tokenId, in: query, required: true, schema: { $ref: "#/components/schemas/Uint" } }
        - { name: owner, in: query, required: true, schema: { $ref: "#/components/schemas/Address" } }
        - { name: percent, in: query, schema: { type: integer, minimum: 1, maximum: 100, default: 100 } }
        - { name: slippageBps, in: query, schema: { type: integer, minimum: 0, maximum: 5000, default: 50 } }
      responses: { "200": { description: Transaction, content: { application/json: { schema: { $ref: "#/components/schemas/LpTx" } } } } }

  /liquidity/collect:
    get:
      tags: [Liquidity]
      summary: Collect a position's fees
      parameters:
        - { name: manager, in: query, required: true, schema: { $ref: "#/components/schemas/Address" } }
        - { name: tokenId, in: query, required: true, schema: { $ref: "#/components/schemas/Uint" } }
        - { name: owner, in: query, required: true, schema: { $ref: "#/components/schemas/Address" } }
      responses: { "200": { description: Transaction, content: { application/json: { schema: { $ref: "#/components/schemas/LpTx" } } } } }

components:
  securitySchemes:
    partnerKey:
      type: http
      scheme: bearer
      description: Optional partner key (`API_KEYS` on the server). Raises the rate limit; an unknown key is rejected with 401.
  responses:
    Error:
      description: Invalid input
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    RateLimited:
      description: Too many requests; see the `retry-after` header
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
  schemas:
    LpTx:
      type: object
      properties:
        approvals: { type: array, items: { type: object, properties: { to: { type: string }, data: { type: string }, description: { type: string } } } }
        tx: { $ref: "#/components/schemas/Tx" }
        simulation: { $ref: "#/components/schemas/Simulation" }
      additionalProperties: true
    LpPosition:
      type: object
      properties:
        manager: { type: string }
        protocol: { type: string, enum: [v3, v4] }
        tokenId: { $ref: "#/components/schemas/Uint" }
        pool: { type: [string, "null"] }
        feeBps: { type: number }
        tickLower: { type: integer }
        tickUpper: { type: integer }
        priceLower: { type: number }
        priceUpper: { type: number }
        currentPrice: { type: [number, "null"] }
        liquidity: { $ref: "#/components/schemas/Uint" }
        amounts: { type: object }
        valueUsd: { type: [number, "null"] }
        fees: { type: object }
        inRange: { type: [boolean, "null"] }
        inRangePct30d: { type: [number, "null"] }
        aprEstimatePct: { type: [number, "null"] }
    Pool:
      type: object
      properties:
        address: { type: string }
        venue: { type: string }
        protocol: { type: string }
        factory: { type: string }
        hooks: { type: [string, "null"] }
        feeBps: { type: number }
        token0: { type: object }
        token1: { type: object }
        base: { type: object }
        quote: { type: object }
        priceUsd: { type: [number, "null"] }
        change24h: { type: [number, "null"] }
        marketCapUsd: { type: [number, "null"] }
        tvlUsd: { type: [number, "null"] }
        volumeUsd: { type: object, properties: { "1h": { type: number }, "4h": { type: number }, "12h": { type: number }, "24h": { type: number } } }
        fees24hUsd: { type: number }
        swaps24h: { type: integer }
        createdAt: { type: [integer, "null"] }
        poolsInPair: { type: integer }
    Activity:
      type: object
      properties:
        tx: { type: string }
        block: { type: integer }
        time: { type: integer }
        pool: { type: string }
        type: { type: string, enum: [buy, sell, add, remove] }
        wallet: { type: [string, "null"] }
        valueUsd: { type: [number, "null"] }
        amounts: { type: array, items: { type: object } }
    Address: { type: string, pattern: "^0x[0-9a-fA-F]{40}$" }
    Uint: { type: string, pattern: "^[0-9]+$", description: Unsigned integer as a decimal string }
    TokenId: { type: string, description: "ERC20 address, or HYPE for native", example: HYPE }
    Error:
      type: object
      required: [error]
      properties:
        error: { type: string }
        requestId: { type: string }
    Tx:
      type: object
      properties:
        from: { $ref: "#/components/schemas/Address" }
        to: { $ref: "#/components/schemas/Address" }
        data: { type: string }
        value: { $ref: "#/components/schemas/Uint" }
        chainId: { type: integer }
        warning: { type: string, description: Present when the simulation failed. Do not send. }
    Approval:
      type: [object, "null"]
      description: ERC20 approve to send before `tx` (null if the allowance is enough)
      properties:
        to: { $ref: "#/components/schemas/Address" }
        data: { type: string }
    Simulation:
      type: object
      properties:
        ok: { type: boolean }
        gas: { $ref: "#/components/schemas/Uint" }
        error: { type: string }
    Hop:
      type: object
      properties:
        protocol: { type: string, enum: [v2, v3, v4, wrap, launchpad] }
        pool: { type: string }
        factory: { type: [string, "null"] }
        tokenIn: { $ref: "#/components/schemas/Address" }
        tokenOut: { $ref: "#/components/schemas/Address" }
        fee: { type: integer, description: "Pool fee in hundredths of a bp (3000 = 0.3%)" }
    Quote:
      type: object
      properties:
        amountIn: { $ref: "#/components/schemas/Uint" }
        amountOut: { $ref: "#/components/schemas/Uint", description: "Expected output, net of router fee and measured token taxes" }
        minOut: { $ref: "#/components/schemas/Uint" }
        feeBps: { type: integer }
        priceImpactBps: { type: [integer, "null"] }
        taxBps: { type: integer, description: Measured sell tax of `in` + buy tax of `out`, already deducted }
        routerAmountOut: { $ref: "#/components/schemas/Uint", description: amountOut before token taxes }
        venue: { type: string, enum: [launchpad], description: Present only when the bonding curve beat the aggregator }
        warnings: { type: array, items: { type: string } }
        amountOutUsd: { type: [number, "null"] }
        slippage:
          type: object
          properties:
            bps: { type: integer }
            auto: { type: boolean }
            recommendedBps: { type: integer }
        savings:
          type: [object, "null"]
          description: Our output minus the exact quote of the best single-pool route (null if no direct pool)
          properties:
            vsBestSinglePool: { $ref: "#/components/schemas/Uint" }
            usd: { type: [number, "null"] }
            bestSinglePool: { type: object, properties: { amountOut: { $ref: "#/components/schemas/Uint" }, protocol: { type: string }, pool: { type: string } } }
        scanned:
          type: object
          properties: { pools: { type: integer }, routes: { type: integer }, quoted: { type: integer } }
        gas:
          type: object
          properties:
            estimate: { $ref: "#/components/schemas/Uint" }
            costInTokenOut: { $ref: "#/components/schemas/Uint" }
        splits:
          type: array
          items:
            type: object
            properties:
              amountIn: { $ref: "#/components/schemas/Uint" }
              route: { type: array, items: { $ref: "#/components/schemas/Hop" } }
        wrappedNative: { type: [string, "null"], description: WHYPE used to wrap/unwrap native HYPE }
        tx: { $ref: "#/components/schemas/Tx" }
        approval: { $ref: "#/components/schemas/Approval" }
        permit2:
          type: [object, "null"]
          properties:
            typedData: { type: object, description: EIP-712 PermitTransferFrom to sign with eth_signTypedData_v4 }
        simulation: { $ref: "#/components/schemas/Simulation" }
        block: { $ref: "#/components/schemas/Uint" }
    Token:
      type: object
      properties:
        address: { type: string }
        symbol: { type: string }
        name: { type: string }
        decimals: { type: integer }
        pools: { type: integer, description: Live pools holding this token }
        verified: { type: boolean }
        logoURI: { type: [string, "null"] }
        tags: { type: array, items: { type: string } }
        warnings: { type: array, items: { type: string } }
        price: { type: [number, "null"] }
        change24h: { type: [number, "null"], description: Percent }
        volume24hUsd: { type: number }
        check:
          type: [object, "null"]
          description: Honeypot/tax probe result
          properties:
            status: { type: string, enum: [ok, taxed, honeypot, unknown] }
            buyTaxBps: { type: integer }
            sellTaxBps: { type: integer }
            transferTaxBps: { type: integer }
            transferBlocked: { type: boolean }
    Order:
      type: object
      properties:
        id: { $ref: "#/components/schemas/Uint" }
        owner: { $ref: "#/components/schemas/Address" }
        tokenIn: { type: string }
        tokenOut: { type: string }
        remaining: { $ref: "#/components/schemas/Uint" }
        perSlice: { $ref: "#/components/schemas/Uint" }
        minOutPerSlice: { $ref: "#/components/schemas/Uint" }
        interval: { $ref: "#/components/schemas/Uint" }
        nextFillAt: { $ref: "#/components/schemas/Uint" }
        expiry: { $ref: "#/components/schemas/Uint" }
        status:
          type: [object, "null"]
          description: Keeper's last check
          properties:
            state: { type: string, enum: [scheduled, waiting_price, no_route, filling] }
            bestOut: { type: [string, "null"] }
            minOut: { $ref: "#/components/schemas/Uint" }
            checkedAt: { type: integer }
